implement game turn logic

This commit is contained in:
2026-11-02 21:24:32 +03:00
parent 153dc74ff6
commit 189b027a11
7 changed files with 896 additions and 13 deletions
+157
View File
@@ -429,3 +429,160 @@ func TestPlaceShipsOverWebSocket(t *testing.T) {
t.Fatal("p2 board not placed")
}
}
func TestFireOverWebSocket(t *testing.T) {
url, _ := startServerWithManager(t, time.Minute)
exp := time.Now().Add(time.Hour)
gt := gameToken(t, "m-ws-fire", 1, 2, publicAddr, exp)
c1, c2 := dial(t, url), dial(t, url)
join(t, c1, gt, userToken(t, 1))
join(t, c2, gt, userToken(t, 2))
// Initial StatePlacingShips (seq 1)
readMsg(t, c1)
readMsg(t, c2)
_ = c1.Write(context.Background(), websocket.MessageText, protocol.BuildAck(1))
_ = c2.Write(context.Background(), websocket.MessageText, protocol.BuildAck(1))
// Both place ships
ships := validTestShips()
_ = c1.Write(context.Background(), websocket.MessageText, protocol.BuildPlaceShips(ships))
readMsg(t, c1) // seq 2: StateWaitOpponentReady
readMsg(t, c2) // seq 2: StateOpponentReady
_ = c1.Write(context.Background(), websocket.MessageText, protocol.BuildAck(2))
_ = c2.Write(context.Background(), websocket.MessageText, protocol.BuildAck(2))
_ = c2.Write(context.Background(), websocket.MessageText, protocol.BuildPlaceShips(ships))
readMsg(t, c1) // seq 3: StateYourTurn (p1 starts)
readMsg(t, c2) // seq 3: StateWaitOpponentTurn
_ = c1.Write(context.Background(), websocket.MessageText, protocol.BuildAck(3))
_ = c2.Write(context.Background(), websocket.MessageText, protocol.BuildAck(3))
// 1. Player 1 sends invalid position (-1, 0)
_ = c1.Write(context.Background(), websocket.MessageText, protocol.BuildFire(-1, 0))
v1 := readMsg(t, c1)
// [7, 4, 4]
if v1[0] != float64(protocol.OpFireResult) || v1[1] != float64(4) || v1[2] != float64(protocol.FireResultInvalidPosition) {
t.Fatalf("c1 invalid position result = %v, want [7, 4, 4]", v1)
}
_ = c1.Write(context.Background(), websocket.MessageText, protocol.BuildAck(4))
// 2. Player 1 hits ship at (0, 0)
_ = c1.Write(context.Background(), websocket.MessageText, protocol.BuildFire(0, 0))
v1 = readMsg(t, c1)
// [7, 5, 2]
if v1[0] != float64(protocol.OpFireResult) || v1[1] != float64(5) || v1[2] != float64(protocol.FireResultHit) {
t.Fatalf("c1 hit result = %v, want [7, 5, 2]", v1)
}
_ = c1.Write(context.Background(), websocket.MessageText, protocol.BuildAck(5))
// 3. Player 1 misses at (9, 9)
_ = c1.Write(context.Background(), websocket.MessageText, protocol.BuildFire(9, 9))
v1Fire := readMsg(t, c1)
// [7, 6, 1]
if v1Fire[0] != float64(protocol.OpFireResult) || v1Fire[1] != float64(6) || v1Fire[2] != float64(protocol.FireResultMiss) {
t.Fatalf("c1 miss result = %v, want [7, 6, 1]", v1Fire)
}
_ = c1.Write(context.Background(), websocket.MessageText, protocol.BuildAck(6))
// Turn transitions: c1 gets StateWaitOpponentTurn (5) with seq 7
v1State := readMsg(t, c1)
if v1State[0] != float64(protocol.OpState) || v1State[1] != float64(7) || v1State[2] != float64(protocol.StateWaitOpponentTurn) {
t.Fatalf("c1 state after miss = %v, want [3, 7, 5]", v1State)
}
_ = c1.Write(context.Background(), websocket.MessageText, protocol.BuildAck(7))
// c2 gets StateYourTurn (4) with seq 4
v2State := readMsg(t, c2)
if v2State[0] != float64(protocol.OpState) || v2State[1] != float64(4) || v2State[2] != float64(protocol.StateYourTurn) {
t.Fatalf("c2 state after miss = %v, want [3, 4, 4]", v2State)
}
_ = c2.Write(context.Background(), websocket.MessageText, protocol.BuildAck(4))
// 4. Player 1 tries to fire while it's Player 2's turn -> ignored
_ = c1.Write(context.Background(), websocket.MessageText, protocol.BuildFire(5, 5))
// 5. Player 2 fires at (9, 4) -> single ship sunk
_ = c2.Write(context.Background(), websocket.MessageText, protocol.BuildFire(9, 4))
v2Fire := readMsg(t, c2)
if v2Fire[0] != float64(protocol.OpFireResult) || v2Fire[1] != float64(5) || v2Fire[2] != float64(protocol.FireResultSunk) {
t.Fatalf("c2 sunk result = %v, want [7, 5, 3]", v2Fire)
}
}
func TestFireWinOverWebSocket(t *testing.T) {
url, _ := startServerWithManager(t, time.Minute)
exp := time.Now().Add(time.Hour)
gt := gameToken(t, "m-ws-fire-win", 1, 2, publicAddr, exp)
c1, c2 := dial(t, url), dial(t, url)
join(t, c1, gt, userToken(t, 1))
join(t, c2, gt, userToken(t, 2))
// Initial StatePlacingShips
readMsg(t, c1)
readMsg(t, c2)
// Both place ships
ships := validTestShips()
_ = c1.Write(context.Background(), websocket.MessageText, protocol.BuildPlaceShips(ships))
readMsg(t, c1)
readMsg(t, c2)
_ = c2.Write(context.Background(), websocket.MessageText, protocol.BuildPlaceShips(ships))
readMsg(t, c1) // StateYourTurn
readMsg(t, c2) // StateWaitOpponentTurn
// Player 1 destroys all 10 ships of Player 2
for i, ship := range ships {
cells := ship.Cells()
for j, c := range cells {
_ = c1.Write(context.Background(), websocket.MessageText, protocol.BuildFire(c.X, c.Y))
v := readMsg(t, c1)
if j < len(cells)-1 {
if v[0] != float64(protocol.OpFireResult) || v[2] != float64(protocol.FireResultHit) {
t.Fatalf("ship %d cell %d: want Hit, got %v", i, j, v)
}
} else {
if v[0] != float64(protocol.OpFireResult) || v[2] != float64(protocol.FireResultSunk) {
t.Fatalf("ship %d cell %d: want Sunk, got %v", i, j, v)
}
}
}
}
// Player 1 receives END victory
v1End := readMsg(t, c1)
if v1End[0] != float64(protocol.OpEnd) || v1End[2] != float64(protocol.EndCodeVictory) || v1End[3] != "victory" {
t.Fatalf("c1 want END victory, got %v", v1End)
}
// Player 2 receives END defeat
v2End := readMsg(t, c2)
if v2End[0] != float64(protocol.OpEnd) || v2End[2] != float64(protocol.EndCodeDefeat) || v2End[3] != "defeat" {
t.Fatalf("c2 want END defeat, got %v", v2End)
}
// Connections should be closed with 1000
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
_, _, err1 := c1.Read(ctx)
if err1 == nil {
t.Fatal("expected c1 connection to be closed")
}
_, _, err2 := c2.Read(ctx)
if err2 == nil {
t.Fatal("expected c2 connection to be closed")
}
// Late reconnect gets END replayed and connection closed
c1Late := dial(t, url)
join(t, c1Late, gt, userToken(t, 1))
lateEnd := readMsg(t, c1Late)
if lateEnd[0] != float64(protocol.OpEnd) || lateEnd[2] != float64(protocol.EndCodeVictory) {
t.Fatalf("late join want END victory, got %v", lateEnd)
}
}