From ee246cf08e256da250fc8c19a6eb041095b8b248 Mon Sep 17 00:00:00 2001 From: Fredrik Robertsen Date: Sat, 6 Jun 2026 14:18:39 +0200 Subject: [PATCH] wip --- src/client.odin | 92 +++++++++++++++++++------------------------------ src/game.odin | 64 ++++++++++++++++++++++++---------- src/server.odin | 33 +++++++++++++----- 3 files changed, 105 insertions(+), 84 deletions(-) diff --git a/src/client.odin b/src/client.odin index 0907fa5..70a6df6 100644 --- a/src/client.odin +++ b/src/client.odin @@ -2,70 +2,19 @@ package hanabi import "core:bufio" import "core:fmt" +import "core:io" import "core:mem" import "core:net" import "core:os" -Client_View :: struct { - num_players: int, - num_hints: int, - num_lives: int, - other_players: []Player, -} - -recv_payload :: proc(sock: net.TCP_Socket, allocator := context.allocator) -> (pl: Player_View) { - header: [4]u8 - net.recv(sock, header[:]) - num_cards := header[3] - body := make([]u8, num_cards) - net.recv(sock, body) - pl = {header[0], header[1], header[2], header[3], transmute([]Card)body} - // sanity checks - assert(2 <= pl.num_players && pl.num_players <= 5) - assert(pl.num_hints >= 0 && pl.num_lives >= 0) - for c in pl.cards do assert(1 <= c.value && c.value <= 5) - return -} - -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 - for cast(u8)len(players) < pl.num_players - 1 { - hand: [dynamic]Card - for cast(u8)len(hand) < cards_per_hand { - append(&hand, pl.cards[i]) - i += 1 - } - append(&players, Player{hand = hand}) - } - - return { - num_players = int(pl.num_players), - num_hints = int(pl.num_hints), - num_lives = int(pl.num_lives), - other_players = players[:], - } -} - -render_view :: proc(view: Client_View) { - fmt.println("number of hints left:", view.num_hints) - fmt.println("number of lives left:", view.num_lives) - for p in view.other_players { - fmt.println() - for c in p.hand { - fmt.println(c.value, c.color) - } - } -} - run_client :: proc(host_addr: net.Endpoint) { serv, list_err := net.dial_tcp(host_addr) if list_err != nil do panic(fmt.tprintln("failed to dial:", net.to_string(host_addr))) defer net.close(serv) + // player input from stdin scanner: bufio.Scanner - bufio.scanner_init(&scanner, os.stream_from_handle(os.stdin), context.temp_allocator) + bufio.scanner_init(&scanner, io.to_reader(transmute(io.Stream)os.stdin.stream)) LISTEN: for { msg_header: Msg_Type @@ -77,11 +26,42 @@ run_client :: proc(host_addr: net.Endpoint) { action := bufio.scanner_text(&scanner) net.send(serv, transmute([]byte)action) case .State_Update: - pl := recv_payload(serv, context.temp_allocator) - view := create_view(pl, context.temp_allocator) + view, recv_err := receive_view(serv) + if recv_err != nil do panic(fmt.tprintln("failed to receive view", recv_err)) render_view(view) + case .Info: + unimplemented() } free_all(context.temp_allocator) } } +receive_view :: proc( + serv: net.TCP_Socket, + allocator := context.allocator, +) -> ( + view: Player_View, + err: net.Network_Error, +) { + // constant header + header_size := offset_of(Player_View, cards) + net.recv(serv, (cast([^]byte)&view)[:header_size]) or_return + // body of cards + body_data := make([]Card, view.num_cards) + body := raw_data(view.cards[:])[:view.num_cards * size_of(Card)] + net.recv(serv, transmute([]byte)body) or_return + return +} + +render_view :: proc(view: Player_View) { + fmt.println("GAME:") + fmt.println("\tnumber of hints left:", view.game_state.num_hints) + fmt.println("\tnumber of lives left:", view.game_state.num_lives) + fmt.println("PLAYERS:") + for p in 0 ..< view.constant.num_players - 1 { + fmt.printfln("\tplayer %d's hand:", p) + for c in view.cards { + if c._player == p do fmt.println("\t\t", c) + } + } +} diff --git a/src/game.odin b/src/game.odin index 61b9160..0db963e 100644 --- a/src/game.odin +++ b/src/game.odin @@ -5,12 +5,12 @@ import "core:math/rand" VALUES :: []u8{1, 1, 1, 2, 2, 3, 3, 4, 4, 5} COLORS :: enum u8 { - RED, - GREEN, - WHITE, - BLUE, - YELLOW, - RAINBOW, + RED = 0b000001, + GREEN = 0b000010, + WHITE = 0b000100, + BLUE = 0b001000, + YELLOW = 0b010000, + RAINBOW = 0b100000, } Card :: bit_field u8 { @@ -26,14 +26,24 @@ Hand :: distinct []Card // player can only place card of value `played[color]+1`. Played_Card_Piles :: distinct [6]u8 +Belief_State :: []bit_field u16 { + // `11111` can be any value 1-5. + // `00010` must be value 4. + value: u8 | 5, + // `111111` can be any color. + // `100001` can be red or rainbow. + color: u8 | 6, +} + Game :: struct { - num_players: int, - num_hints: int, - num_lives: int, - hand_size: int, - player_hands: [dynamic]Hand, - deck: [dynamic]Card, - played: Played_Card_Piles, + num_players: int, + num_hints: int, + num_lives: int, + hand_size: int, + played: Played_Card_Piles, + player_hands: []Hand, + player_beliefs: []Belief_State, + deck: [dynamic]Card, } create_deck :: proc() -> (deck: [dynamic]Card) { @@ -48,18 +58,33 @@ create_deck :: proc() -> (deck: [dynamic]Card) { deal_hands :: proc( deck: ^[dynamic]Card, - num_players: int, - hand_size: int, -) -> ( - player_hands: [dynamic]Hand, -) { + num_players, hand_size: int, + allocator := context.allocator, +) -> []Hand { + player_hands: [dynamic]Hand for i in 0 ..< num_players { start := len(deck) - hand_size hand := Hand(deck[start:]) append(&player_hands, hand) resize(deck, start) } - return + return player_hands[:] +} + +init_beliefs :: proc( + num_players, hand_size: int, + allocator := context.allocator, +) -> []Belief_State { + player_beliefs: [dynamic]Belief_State + for p in 0 ..< num_players { + bs: Belief_State + for c in 0 ..< hand_size { + bs[c].value = 0b11111 + bs[c].color = 0b111111 + } + append(&player_beliefs, bs) + } + return player_beliefs[:] } create_game :: proc(hint_tokens, lives_left, num_players: int) -> (s: Game) { @@ -70,6 +95,7 @@ create_game :: proc(hint_tokens, lives_left, num_players: int) -> (s: Game) { s.hand_size = num_players <= 3 ? 5 : 4 s.deck = create_deck() s.player_hands = deal_hands(&s.deck, num_players, s.hand_size) + s.player_beliefs = init_beliefs(num_players, s.hand_size) return } diff --git a/src/server.odin b/src/server.odin index 089140f..ba69d4a 100644 --- a/src/server.odin +++ b/src/server.odin @@ -154,10 +154,20 @@ send_payload :: proc(sock: net.TCP_Socket, pl: Payload) -> net.Network_Error { 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)) or_return - net.send(sock, mem.ptr_to_bytes(pl)) or_return +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") + header_size := offset_of(Player_View, cards) + header_ptr := raw_data(mem.byte_slice(view, size_of(Player_View))) + net.send(sock, header_ptr[:header_size]) or_return + // send dynamic `cards` body field + body := raw_data(view.cards)[:len(view.cards) * size_of(Card)] + net.send(sock, transmute([]byte)body) or_return return nil } @@ -186,7 +196,15 @@ send_info :: proc(sock: net.TCP_Socket, data: Info_Data) -> net.Network_Error { Play_Action :: distinct Card Discard_Action :: distinct Card -Hint_Action :: distinct Card +Value_Hint :: distinct int +Color_Hint :: distinct int +Hint_Action :: struct { + target_player: int, // global index + type: union { + Value_Hint, + Color_Hint, + }, +} Action :: union { Play_Action, @@ -195,10 +213,7 @@ Action :: union { } receive_action :: proc(player: net.TCP_Socket) -> Action { - unimplemented() -} - -validate_action :: proc(action: Action) -> bool { + // validates user input and creates correct action unimplemented() }