Files
hanabee/src/server.odin
T
2026-06-06 14:21:09 +02:00

223 lines
5.6 KiB
Odin

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()
}