From 0a5171db84ffb3c871cc82785de86d6e30766fd9 Mon Sep 17 00:00:00 2001 From: Fredrik Robertsen Date: Mon, 1 Jun 2026 15:03:18 +0200 Subject: [PATCH] wip --- src/client.odin | 4 +- src/game.odin | 7 ++- src/main.odin | 1 - src/server.odin | 117 ++++++++++++++++++++++++++++++++++-------------- src/test.nu | 1 - 5 files changed, 92 insertions(+), 38 deletions(-) diff --git a/src/client.odin b/src/client.odin index 749d8fe..0907fa5 100644 --- a/src/client.odin +++ b/src/client.odin @@ -13,7 +13,7 @@ Client_View :: struct { other_players: []Player, } -recv_payload :: proc(sock: net.TCP_Socket, allocator := context.allocator) -> (pl: Payload) { +recv_payload :: proc(sock: net.TCP_Socket, allocator := context.allocator) -> (pl: Player_View) { header: [4]u8 net.recv(sock, header[:]) num_cards := header[3] @@ -27,7 +27,7 @@ recv_payload :: proc(sock: net.TCP_Socket, allocator := context.allocator) -> (p return } -create_view :: proc(pl: Payload, allocator := context.allocator) -> Client_View { +create_view :: proc(pl: Player_View, allocator := context.allocator) -> Client_View { cards_per_hand := pl.num_cards / (pl.num_players - 1) players: [dynamic]Player i := 0 diff --git a/src/game.odin b/src/game.odin index f7752d3..61b9160 100644 --- a/src/game.odin +++ b/src/game.odin @@ -21,6 +21,11 @@ Card :: bit_field u8 { Hand :: distinct []Card +// 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 + Game :: struct { num_players: int, num_hints: int, @@ -28,6 +33,7 @@ Game :: struct { hand_size: int, player_hands: [dynamic]Hand, deck: [dynamic]Card, + played: Played_Card_Piles, } create_deck :: proc() -> (deck: [dynamic]Card) { @@ -71,4 +77,3 @@ delete_game :: proc(g: ^Game) { delete(g.deck) delete(g.player_hands) } - diff --git a/src/main.odin b/src/main.odin index 9072231..fac0756 100644 --- a/src/main.odin +++ b/src/main.odin @@ -85,4 +85,3 @@ main :: proc() { } ok = true } - diff --git a/src/server.odin b/src/server.odin index 3123169..089140f 100644 --- a/src/server.odin +++ b/src/server.odin @@ -17,15 +17,15 @@ run_server :: proc(listen_addr: net.Endpoint, num_players: int, num_hints: int, g := create_game(num_hints, num_lives, num_players) defer delete_game(&g) - w, comm_err := create_comm_world(listener, num_players) + comm, comm_err := create_comm_world(listener, num_players) if comm_err != nil do panic(fmt.tprintln("failed to create comm world:", comm_err)) - defer delete_comm_world(&w) + defer delete_comm_world(comm) current_player := rand.int_max(num_players) GAME_LOOP: for { for i in 0 ..< num_players { - pl := create_payload(g, i, context.temp_allocator) - send_err := send_payload(w.sockets[i], pl) + view := create_view_from_game(g, i, context.temp_allocator) + send_err := send_payload(comm[i], {.State_Update, view}) if send_err != nil do panic("failed to send payload") } @@ -40,7 +40,7 @@ run_server :: proc(listen_addr: net.Endpoint, num_players: int, num_hints: int, action: Action POKE: for { - poke_err := send_poke(w.sockets[current_player]) + poke_err := send_poke(comm[current_player]) if poke_err != nil do panic(fmt.tprintfln("failed to poke player %d; %s", current_player, poke_err)) // action, is_valid := receive_action(w.sockets[current_player]) // if is_valid do break POKE @@ -57,68 +57,107 @@ run_server :: proc(listen_addr: net.Endpoint, num_players: int, num_hints: int, COMM **********************/ -Comm_World :: struct { - sockets: [dynamic]net.TCP_Socket, -} +Comm_World :: distinct []net.TCP_Socket create_comm_world :: proc( listener: net.TCP_Socket, num_conn: int, ) -> ( - w: Comm_World, + comm: Comm_World, err: net.Network_Error, ) { + w := make([dynamic]net.TCP_Socket, num_conn) fmt.printfln("waiting for players (%d)...", num_conn) - for len(w.sockets) < num_conn { + for len(w) < num_conn { client_sock, source := net.accept_tcp(listener) or_return - append(&w.sockets, client_sock) + append(&w, client_sock) fmt.println("player connected:", net.to_string(source)) } - return + return Comm_World(w[:]), nil } -delete_comm_world :: proc(w: ^Comm_World) { - for sock in w.sockets { +delete_comm_world :: proc(comm: Comm_World) { + for sock in comm { net.close(sock) } - delete(w.sockets) + delete(comm) } /****************************** - MESSAGE (PAYLOAD/POKE) + MESSAGE *******************************/ Msg_Type :: enum u8 { State_Update, Poke, + Info, } -Payload :: struct #packed { - num_players: u8, - num_hints: u8, - num_lives: u8, - num_cards: u8, - cards: []Card, +Player_View :: struct #packed { + constant: bit_field u8 { + num_players: u8 | 3, + hand_size: u8 | 3, + }, + game_state: bit_field u8 { + num_hints: u8 | 5, + num_lives: u8 | 3, + }, + played: Played_Card_Piles, + num_cards: u8, + cards: []Card, } -create_payload :: proc(g: Game, player: int, allocator := context.allocator) -> Payload { +create_view_from_game :: proc( + g: Game, + player: int, + allocator := context.allocator, +) -> ( + view: Player_View, +) { + view.constant.num_players = u8(g.num_players) + view.constant.hand_size = u8(g.hand_size) + view.game_state.num_hints = u8(g.num_hints) + view.game_state.num_lives = u8(g.num_lives) + view.played = g.played cards: [dynamic]Card for i in 0 ..< g.num_players { if i == player do continue - hand := g.players[i].hand[:] - for c in hand do append(&cards, c) + for &c in g.player_hands[i] { + append(&cards, mem.reinterpret_copy(Card, &c)) + } } - return {u8(g.num_players), u8(g.num_hints), u8(g.linum_lives u8(len(cards)), cards[:]} + view.num_cards = u8(len(cards)) + view.cards = cards[:] + return +} + +Info_Data :: distinct []byte + +Payload :: struct #packed { + type: Msg_Type, + body: union { + Player_View, + Info_Data, + }, } send_payload :: proc(sock: net.TCP_Socket, pl: Payload) -> net.Network_Error { + switch pl.type { + case .State_Update: + view := pl.body.(Player_View) + send_state_update(sock, &view) or_return + case .Poke: + send_poke(sock) or_return + case .Info: + send_info(sock, pl.body.(Info_Data)) + } + return nil +} + +send_state_update :: proc(sock: net.TCP_Socket, pl: ^Player_View) -> net.Network_Error { header := Msg_Type.State_Update - net.send(sock, mem.ptr_to_bytes(&header)) - data: [dynamic]u8 - defer delete(data) - append(&data, pl.num_players, pl.num_hints, pl.num_lives, pl.num_cards) - for c in pl.cards do append(&data, transmute(u8)c) - net.send(sock, data[:]) or_return + net.send(sock, mem.ptr_to_bytes(&header)) or_return + net.send(sock, mem.ptr_to_bytes(pl)) or_return return nil } @@ -128,10 +167,23 @@ send_poke :: proc(sock: net.TCP_Socket) -> net.Network_Error { return nil } +send_info :: proc(sock: net.TCP_Socket, data: Info_Data) -> net.Network_Error { + header := Msg_Type.Info + net.send(sock, mem.ptr_to_bytes(&header)) or_return + net.send(sock, transmute([]byte)data) or_return + return nil +} + /************************* ACTION RESOLUTION **************************/ +// actions should be handled in the Game kernel. +// belief states should be handled here in the server, +// or rather the translation between the local _player index +// 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 Hint_Action :: distinct Card @@ -153,4 +205,3 @@ validate_action :: proc(action: Action) -> bool { perform_action :: proc(game: ^Game, action: Action) { unimplemented() } - diff --git a/src/test.nu b/src/test.nu index 894afc9..cc5d447 100755 --- a/src/test.nu +++ b/src/test.nu @@ -1,5 +1,4 @@ #!/usr/bin/env nu - (interleave { odin run . -- h localhost:42069 2 | lines | each { "HOST | " ++ $in } } { sleep 500ms