almost finish action handling
This commit is contained in:
+6
-2
@@ -19,7 +19,7 @@ Card :: bit_field u8 {
|
||||
_player: u8 | 2,
|
||||
}
|
||||
|
||||
Hand :: distinct []Card
|
||||
Hand :: distinct [dynamic]Card
|
||||
|
||||
// 6 piles, one for each in COLORS.
|
||||
// u8 between 0-5, representing highest card in pile.
|
||||
@@ -37,6 +37,7 @@ Belief_State :: []bit_field u16 {
|
||||
|
||||
Game :: struct {
|
||||
num_players: int,
|
||||
max_hints: int,
|
||||
num_hints: int,
|
||||
num_lives: int,
|
||||
hand_size: int,
|
||||
@@ -44,6 +45,7 @@ Game :: struct {
|
||||
player_hands: []Hand,
|
||||
player_beliefs: []Belief_State,
|
||||
deck: [dynamic]Card,
|
||||
discarded: [dynamic]Card,
|
||||
}
|
||||
|
||||
create_deck :: proc() -> (deck: [dynamic]Card) {
|
||||
@@ -64,7 +66,8 @@ deal_hands :: proc(
|
||||
player_hands: [dynamic]Hand
|
||||
for i in 0 ..< num_players {
|
||||
start := len(deck) - hand_size
|
||||
hand := Hand(deck[start:])
|
||||
hand: Hand
|
||||
for c in deck[start:] do append(&hand, c)
|
||||
append(&player_hands, hand)
|
||||
resize(deck, start)
|
||||
}
|
||||
@@ -90,6 +93,7 @@ init_beliefs :: proc(
|
||||
create_game :: proc(hint_tokens, lives_left, num_players: int) -> (s: Game) {
|
||||
assert(num_players >= 2 && num_players <= 5)
|
||||
s.num_players = num_players
|
||||
s.max_hints = hint_tokens
|
||||
s.num_hints = hint_tokens
|
||||
s.num_lives = lives_left
|
||||
s.hand_size = num_players <= 3 ? 5 : 4
|
||||
|
||||
+130
-18
@@ -4,6 +4,8 @@ import "core:fmt"
|
||||
import "core:math/rand"
|
||||
import "core:mem"
|
||||
import "core:net"
|
||||
import "core:strconv"
|
||||
import "core:strings"
|
||||
|
||||
run_server :: proc(listen_addr: net.Endpoint, num_players: int, num_hints: int, num_lives: int) {
|
||||
listener, list_err := net.listen_tcp(listen_addr)
|
||||
@@ -23,8 +25,10 @@ run_server :: proc(listen_addr: net.Endpoint, num_players: int, num_hints: int,
|
||||
|
||||
current_player := rand.int_max(num_players)
|
||||
GAME_LOOP: for {
|
||||
views := make([dynamic]Player_View, context.temp_allocator)
|
||||
for i in 0 ..< num_players {
|
||||
view := create_view_from_game(g, i, context.temp_allocator)
|
||||
append(&views, view)
|
||||
send_err := send_payload(comm[i], {.State_Update, view})
|
||||
if send_err != nil do panic(fmt.tprintln("failed to send payload:", send_err))
|
||||
}
|
||||
@@ -39,7 +43,7 @@ run_server :: proc(listen_addr: net.Endpoint, num_players: int, num_hints: int,
|
||||
}
|
||||
|
||||
action := receive_action(comm[current_player])
|
||||
|
||||
translate_action(action, current_player)
|
||||
perform_action(&g, action)
|
||||
current_player = (current_player + 1) % num_players
|
||||
free_all(context.temp_allocator)
|
||||
@@ -129,7 +133,7 @@ Info_Data :: distinct []byte
|
||||
|
||||
Payload :: struct #packed {
|
||||
type: Msg_Type,
|
||||
body: union {
|
||||
body: union #no_nil {
|
||||
Player_View,
|
||||
Info_Data,
|
||||
},
|
||||
@@ -149,10 +153,6 @@ send_payload :: proc(sock: net.TCP_Socket, pl: Payload) -> net.Network_Error {
|
||||
}
|
||||
|
||||
send_state_update :: proc(sock: net.TCP_Socket, view: ^Player_View) -> net.Network_Error {
|
||||
// header := Msg_Type.State_Update
|
||||
// net.send(sock, mem.ptr_to_bytes(&header)) or_return
|
||||
// net.send(sock, mem.ptr_to_bytes(pl)) or_return
|
||||
// return nil
|
||||
type := Msg_Type.State_Update
|
||||
net.send(sock, mem.ptr_to_bytes(&type)) or_return
|
||||
// everything up to `cards` field ("header")
|
||||
@@ -188,13 +188,13 @@ send_info :: proc(sock: net.TCP_Socket, data: Info_Data) -> net.Network_Error {
|
||||
// and the global player index used by g.player_hands.
|
||||
// thus, most of this section should likely be moved into game.odin
|
||||
|
||||
Play_Action :: distinct Card
|
||||
Discard_Action :: distinct Card
|
||||
Play_Action :: distinct int
|
||||
Discard_Action :: distinct int
|
||||
Value_Hint :: distinct int
|
||||
Color_Hint :: distinct int
|
||||
Color_Hint :: distinct COLORS
|
||||
Hint_Action :: struct {
|
||||
target_player: int, // global index
|
||||
type: union {
|
||||
type: union #no_nil {
|
||||
Value_Hint,
|
||||
Color_Hint,
|
||||
},
|
||||
@@ -216,15 +216,14 @@ where color is one of
|
||||
- w[hite]
|
||||
- b[lue]
|
||||
- y[ellow]
|
||||
- r[ainbow]
|
||||
- [x|rainbow]
|
||||
and value is one of 1, 2, 3, 4, 5.
|
||||
|
||||
whitespace does not matter, so you could write shorthands like "play1" or "d3".
|
||||
|
||||
example:
|
||||
$ hint 2 red
|
||||
$ play 5
|
||||
$ hint 3 5`
|
||||
$ hint 3 5
|
||||
$ h 1 b`
|
||||
|
||||
receive_action :: proc(player: net.TCP_Socket) -> (action: Action) {
|
||||
POKE: for {
|
||||
@@ -242,10 +241,123 @@ receive_action :: proc(player: net.TCP_Socket) -> (action: Action) {
|
||||
return
|
||||
}
|
||||
|
||||
parse_action :: proc(raw_action: string) -> Action {
|
||||
unimplemented()
|
||||
parse_action :: proc(raw_action: string) -> (action: Action) {
|
||||
command := strings.split(raw_action, " ")
|
||||
if len(command) < 1 do return nil
|
||||
switch command[0] {
|
||||
case "play", "p":
|
||||
if len(command) - 1 < 1 do return nil
|
||||
card_id, ok := strconv.parse_int(command[1])
|
||||
if !ok || card_id < 1 || card_id > 5 do return nil
|
||||
action = Play_Action(card_id)
|
||||
case "discard", "d":
|
||||
if len(command) - 1 < 1 do return nil
|
||||
card_id, ok := strconv.parse_int(command[1])
|
||||
if !ok || card_id < 1 || card_id > 5 do return nil
|
||||
action = Discard_Action(card_id)
|
||||
case "hint", "h":
|
||||
if len(command) - 1 < 2 do return nil
|
||||
player_id, pok := strconv.parse_int(command[1])
|
||||
if !pok || player_id < 1 || player_id > 4 do return nil
|
||||
hint: Hint_Action
|
||||
hint.target_player = player_id
|
||||
switch command[2] {
|
||||
case "red", "r":
|
||||
hint.type = .RED
|
||||
case "green", "g":
|
||||
hint.type = .GREEN
|
||||
case "white", "w":
|
||||
hint.type = .WHITE
|
||||
case "blue", "b":
|
||||
hint.type = .BLUE
|
||||
case "yellow", "y":
|
||||
hint.type = .YELLOW
|
||||
case "rainbow", "x":
|
||||
hint.type = .RAINBOW
|
||||
case:
|
||||
value, vok := strconv.parse_int(command[2])
|
||||
if !vok || value < 1 || value > 5 do return nil
|
||||
hint.type = Value_Hint(value)
|
||||
}
|
||||
action = hint
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
perform_action :: proc(game: ^Game, action: Action) {
|
||||
unimplemented()
|
||||
// the action produced by `parse_action` is the raw action provided by the player,
|
||||
// but the player uses their own local ids to reference cards and players.
|
||||
// this function modifies an action with local ids to use the global indices kept
|
||||
// track of by the server. this translation happens in-place and is necessary to
|
||||
// do before performing the action with `perform_action`.
|
||||
// more specifically, this is what happens:
|
||||
// - play/discard decrement their value to reflect the index of the card,
|
||||
// i.e. card id 5 -> 4, such that game.player_hands[current_player][id]
|
||||
// yields the relevant card. thus the local indices for the player
|
||||
// should *always* reflect the underlying indices into the player hand.
|
||||
// - hint actions need to resolve which target player is the correct global one.
|
||||
// since we may have players with indices 0-4, this would necessitate the use of
|
||||
// 3 bits, but each card only has a 2-bit field _player for indexing purposes.
|
||||
// this field thus references the player's local view, so that each player sees
|
||||
// players 0-3. this local value encoded in the hint action thus far needs to
|
||||
// be converted to its global value.
|
||||
translate_action :: proc(action: ^Action, current_player: int) {
|
||||
switch &a in action {
|
||||
case Play_Action:
|
||||
a -= 1
|
||||
assert(a >= 0 && a <= 4) // for some player-numbers, a == 4 should be disallowed
|
||||
case Discard_Action:
|
||||
a -= 1
|
||||
assert(a >= 0 && a <= 4) // --||--
|
||||
case Hint_Action:
|
||||
if current_player > a.target_player {
|
||||
a.target_player -= 1
|
||||
} else {
|
||||
a.target_player += 1
|
||||
}
|
||||
a.target_player -= 1
|
||||
assert(a.target_player >= 0 && a.target_player <= 4)
|
||||
}
|
||||
}
|
||||
|
||||
// // 6 piles, one for each in COLORS.
|
||||
// // u8 between 0-5, representing highest card in pile.
|
||||
// // player can only place card of value `played[color]+1`.
|
||||
// Played_Card_Piles :: distinct [6]u8
|
||||
|
||||
|
||||
// assumes action is translated. see `translate_action`.
|
||||
perform_action :: proc(game: ^Game, action: Action, current_player: int) {
|
||||
switch a in action {
|
||||
case Play_Action:
|
||||
hand := &game.player_hands[current_player]
|
||||
card := hand[a]
|
||||
ordered_remove(hand, a)
|
||||
if card.value == game.played[card.color] + 1 {
|
||||
game.played[card.color] += 1
|
||||
} else {
|
||||
append(&game.discarded, card)
|
||||
}
|
||||
new_card := pop(&game.deck)
|
||||
append(hand, new_card)
|
||||
case Discard_Action:
|
||||
hand := &game.player_hands[current_player]
|
||||
card := hand[a]
|
||||
ordered_remove(hand, a)
|
||||
append(&game.discarded, card)
|
||||
new_card := pop(&game.deck)
|
||||
append(hand, new_card)
|
||||
if game.num_hints < game.max_hints do game.num_hints += 1
|
||||
case Hint_Action:
|
||||
belief := game.player_beliefs[a.target_player]
|
||||
switch hint in a.type {
|
||||
case Value_Hint:
|
||||
// TODO: restructure Game to couple Card and Belief, since the belief is per card,
|
||||
// having them in separate arrays where indices get messy is a bad idea. they
|
||||
// should be unified in a single super Card.
|
||||
// maybe change Card to Card_Data, which is then the data that will be sent in the
|
||||
// view, and then the new Card will contain all other server-side metadata such as
|
||||
// belief states and global player ids.
|
||||
case Color_Hint:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user