This commit is contained in:
2026-06-01 15:03:18 +02:00
parent bd778f4c56
commit 0a5171db84
5 changed files with 92 additions and 38 deletions
+2 -2
View File
@@ -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
+6 -1
View File
@@ -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)
}
-1
View File
@@ -85,4 +85,3 @@ main :: proc() {
}
ok = true
}
+84 -33
View File
@@ -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()
}
-1
View File
@@ -1,5 +1,4 @@
#!/usr/bin/env nu
(interleave
{ odin run . -- h localhost:42069 2 | lines | each { "HOST | " ++ $in } }
{ sleep 500ms