package hanabi import "core:fmt" import "core:math/rand" import "core:mem" import "core:net" run_server :: proc(listen_addr: net.Endpoint, num_players: int, num_hints: int, num_lives: int) { listener, list_err := net.listen_tcp(listen_addr) if list_err != nil do panic(fmt.tprintln("failed to listen:", net.to_string(listen_addr))) defer net.close(listener) fmt.println("creating game instance...") fmt.println("\tnumber of players:", num_players) fmt.println("\tnumber of hints:", num_hints) fmt.println("\tnumber of lives:", num_lives) g := create_game(num_hints, num_lives, num_players) defer delete_game(&g) 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(comm) current_player := rand.int_max(num_players) GAME_LOOP: for { for i in 0 ..< num_players { 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") } fmt.printfln("player %d's turn!", current_player) // for p in g.players { // fmt.println() // for c in p.hand { // fmt.println(c.value, c.color) // } // } action: Action POKE: for { 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 fmt.println("invalid action received. try again.") } perform_action(&g, action) current_player = (current_player + 1) % num_players free_all(context.temp_allocator) break GAME_LOOP } } /********************* COMM **********************/ Comm_World :: distinct []net.TCP_Socket create_comm_world :: proc( listener: net.TCP_Socket, num_conn: int, ) -> ( 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) < num_conn { client_sock, source := net.accept_tcp(listener) or_return append(&w, client_sock) fmt.println("player connected:", net.to_string(source)) } return Comm_World(w[:]), nil } delete_comm_world :: proc(comm: Comm_World) { for sock in comm { net.close(sock) } delete(comm) } /****************************** MESSAGE *******************************/ Msg_Type :: enum u8 { State_Update, Poke, Info, } 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_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 for &c in g.player_hands[i] { append(&cards, mem.reinterpret_copy(Card, &c)) } } 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, 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 } send_poke :: proc(sock: net.TCP_Socket) -> net.Network_Error { header := Msg_Type.Poke net.send(sock, mem.ptr_to_bytes(&header)) or_return 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 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, Discard_Action, Hint_Action, } receive_action :: proc(player: net.TCP_Socket) -> Action { // validates user input and creates correct action unimplemented() } perform_action :: proc(game: ^Game, action: Action) { unimplemented() }