almost finish action handling

This commit is contained in:
2026-06-06 20:21:22 +02:00
parent 2d5e31ac6f
commit 724fdd3014
2 changed files with 136 additions and 20 deletions
+6 -2
View File
@@ -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
View File
@@ -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:
}
}
}