add PlaceShips message

This commit is contained in:
2026-10-27 17:31:41 +03:00
parent 31ef12b5f2
commit 6392c2ef03
7 changed files with 843 additions and 27 deletions
+190
View File
@@ -454,3 +454,193 @@ func mustMatch(t *testing.T, m *Manager, id string) *Match {
}
return match
}
func validTestShips() []protocol.Ship {
return []protocol.Ship{
// 4 size 1
{Orientation: protocol.OrientationHorizontal, X: 9, Y: 4, Size: 1},
{Orientation: protocol.OrientationVertical, X: 7, Y: 4, Size: 1},
{Orientation: protocol.OrientationHorizontal, X: 5, Y: 4, Size: 1},
{Orientation: protocol.OrientationHorizontal, X: 3, Y: 4, Size: 1},
// 3 size 2
{Orientation: protocol.OrientationHorizontal, X: 0, Y: 4, Size: 2},
{Orientation: protocol.OrientationHorizontal, X: 7, Y: 2, Size: 2},
{Orientation: protocol.OrientationHorizontal, X: 4, Y: 2, Size: 2},
// 2 size 3
{Orientation: protocol.OrientationHorizontal, X: 0, Y: 2, Size: 3},
{Orientation: protocol.OrientationHorizontal, X: 5, Y: 0, Size: 3},
// 1 size 4
{Orientation: protocol.OrientationHorizontal, X: 0, Y: 0, Size: 4},
}
}
func TestPlaceShipsP1First(t *testing.T) {
m := NewManager(time.Minute)
gc := claims(time.Now().Add(time.Hour))
c1, c2 := &fakeConn{}, &fakeConn{}
match, _, err := m.Join(context.Background(), c1, gc, gc.Player1ID)
if err != nil {
t.Fatalf("join p1: %v", err)
}
if _, _, err := m.Join(context.Background(), c2, gc, gc.Player2ID); err != nil {
t.Fatalf("join p2: %v", err)
}
if match.State() != StatePlacingShips {
t.Fatalf("state = %d, want StatePlacingShips", match.State())
}
ships := validTestShips()
// Player 1 places ships first
if err := m.PlaceShips(context.Background(), match, 0, ships); err != nil {
t.Fatalf("p1 place ships: %v", err)
}
if match.State() != StatePlayer1Ready {
t.Fatalf("state = %d, want StatePlayer1Ready (2)", match.State())
}
// Verify stored ships and board for p1
p1Ships := match.Ships(0)
if len(p1Ships) != len(ships) {
t.Fatalf("got %d ships, want %d", len(p1Ships), len(ships))
}
p1Board, placed := match.Board(0)
if !placed {
t.Fatal("expected p1 board placed to be true")
}
// Check that ship 9 (size 4 at 0,0 horizontal) is in p1 board with id 10
for x := 0; x < 4; x++ {
if p1Board[0][x] != 10 {
t.Fatalf("p1Board[0][%d] = %d, want 10", x, p1Board[0][x])
}
}
// Verify states delivered to p1 and p2
p1Msgs, p2Msgs := c1.messages(), c2.messages()
if v := parseMsg(t, p1Msgs[len(p1Msgs)-1]); v[2] != float64(protocol.StateWaitOpponentReady) {
t.Fatalf("p1 got state %v, want StateWaitOpponentReady (3)", v)
}
if v := parseMsg(t, p2Msgs[len(p2Msgs)-1]); v[2] != float64(protocol.StateOpponentReady) {
t.Fatalf("p2 got state %v, want StateOpponentReady (2)", v)
}
// Player 1 cannot place ships again
if err := m.PlaceShips(context.Background(), match, 0, ships); err == nil {
t.Fatal("expected error when player 1 places ships again")
}
// Player 2 places ships
if err := m.PlaceShips(context.Background(), match, 1, ships); err != nil {
t.Fatalf("p2 place ships: %v", err)
}
if match.State() != StatePlayer1Turn {
t.Fatalf("state = %d, want StatePlayer1Turn (4)", match.State())
}
// Verify states delivered to p1 and p2 after game starts
p1Msgs, p2Msgs = c1.messages(), c2.messages()
if v := parseMsg(t, p1Msgs[len(p1Msgs)-1]); v[2] != float64(protocol.StateYourTurn) {
t.Fatalf("p1 got state %v, want StateYourTurn (4)", v)
}
if v := parseMsg(t, p2Msgs[len(p2Msgs)-1]); v[2] != float64(protocol.StateWaitOpponentTurn) {
t.Fatalf("p2 got state %v, want StateWaitOpponentTurn (5)", v)
}
// Subsequent PlaceShips calls are rejected
if err := m.PlaceShips(context.Background(), match, 1, ships); err == nil {
t.Fatal("expected error when match is in turn phase")
}
}
func TestPlaceShipsP2First(t *testing.T) {
m := NewManager(time.Minute)
gc := claims(time.Now().Add(time.Hour))
c1, c2 := &fakeConn{}, &fakeConn{}
match, _, err := m.Join(context.Background(), c1, gc, gc.Player1ID)
if err != nil {
t.Fatalf("join p1: %v", err)
}
if _, _, err := m.Join(context.Background(), c2, gc, gc.Player2ID); err != nil {
t.Fatalf("join p2: %v", err)
}
ships := validTestShips()
// Player 2 places ships first
if err := m.PlaceShips(context.Background(), match, 1, ships); err != nil {
t.Fatalf("p2 place ships: %v", err)
}
if match.State() != StatePlayer2Ready {
t.Fatalf("state = %d, want StatePlayer2Ready (3)", match.State())
}
// Verify states delivered to p1 and p2
p1Msgs, p2Msgs := c1.messages(), c2.messages()
if v := parseMsg(t, p1Msgs[len(p1Msgs)-1]); v[2] != float64(protocol.StateOpponentReady) {
t.Fatalf("p1 got state %v, want StateOpponentReady (2)", v)
}
if v := parseMsg(t, p2Msgs[len(p2Msgs)-1]); v[2] != float64(protocol.StateWaitOpponentReady) {
t.Fatalf("p2 got state %v, want StateWaitOpponentReady (3)", v)
}
// Player 1 places ships
if err := m.PlaceShips(context.Background(), match, 0, ships); err != nil {
t.Fatalf("p1 place ships: %v", err)
}
if match.State() != StatePlayer2Turn {
t.Fatalf("state = %d, want StatePlayer2Turn (5)", match.State())
}
// Player 2 finished first, so Player 2 gets first turn!
p1Msgs, p2Msgs = c1.messages(), c2.messages()
if v := parseMsg(t, p1Msgs[len(p1Msgs)-1]); v[2] != float64(protocol.StateWaitOpponentTurn) {
t.Fatalf("p1 got state %v, want StateWaitOpponentTurn (5)", v)
}
if v := parseMsg(t, p2Msgs[len(p2Msgs)-1]); v[2] != float64(protocol.StateYourTurn) {
t.Fatalf("p2 got state %v, want StateYourTurn (4)", v)
}
}
func TestPlaceShipsValidationFailure(t *testing.T) {
m := NewManager(time.Minute)
gc := claims(time.Now().Add(time.Hour))
c1, c2 := &fakeConn{}, &fakeConn{}
match, _, err := m.Join(context.Background(), c1, gc, gc.Player1ID)
if err != nil {
t.Fatalf("join p1: %v", err)
}
if _, _, err := m.Join(context.Background(), c2, gc, gc.Player2ID); err != nil {
t.Fatalf("join p2: %v", err)
}
invalidShips := validTestShips()
invalidShips[0].X = invalidShips[1].X // overlapping ships
if err := m.PlaceShips(context.Background(), match, 0, invalidShips); err == nil {
t.Fatal("expected validation error")
}
if match.State() != StatePlacingShips {
t.Fatalf("state changed to %d on error, want StatePlacingShips", match.State())
}
if match.Ships(0) != nil {
t.Fatal("expected nil ships for p1 after failed placement")
}
}
func TestPlaceShipsBeforeMatchStarts(t *testing.T) {
m := NewManager(time.Minute)
gc := claims(time.Now().Add(time.Hour))
c1 := &fakeConn{}
match, _, err := m.Join(context.Background(), c1, gc, gc.Player1ID)
if err != nil {
t.Fatalf("join p1: %v", err)
}
if err := m.PlaceShips(context.Background(), match, 0, validTestShips()); err != ErrMatchNotStarted {
t.Fatalf("got err = %v, want ErrMatchNotStarted", err)
}
}