forked from frero/hanabi
223 lines
5.6 KiB
Odin
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()
|
|
}
|