This commit is contained in:
2026-06-06 14:18:39 +02:00
parent 0a5171db84
commit ee246cf08e
3 changed files with 105 additions and 84 deletions
+36 -56
View File
@@ -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)
}
}
}
+45 -19
View File
@@ -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
}
+24 -9
View File
@@ -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()
}