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
+111 -2
View File
@@ -17,10 +17,34 @@ import (
// that has already ended. The stored END message (if any) is replayed to the
// connection before Join returns.
var (
ErrMatchFinished = errors.New("match already finished")
ErrInvalidSeq = errors.New("invalid seq")
ErrMatchFinished = errors.New("match already finished")
ErrInvalidSeq = errors.New("invalid seq")
ErrMatchNotStarted = errors.New("match not started")
ErrUnexpectedState = errors.New("unexpected state for placing ships")
ErrShipsAlreadyPlaced = errors.New("ships already placed")
ErrInvalidSlot = errors.New("invalid slot index")
)
// BoardSize is the width and height of the Sea Battle board.
const BoardSize = protocol.BoardSize
// Board represents a 10x10 Sea Battle grid.
// 0 indicates water/empty cell.
// 1..10 indicates a ship (ship index 0..9 + 1).
type Board [BoardSize][BoardSize]int8
// NewBoard constructs a Board from validated ships.
func NewBoard(ships []protocol.Ship) Board {
var b Board
for i, ship := range ships {
id := int8(i + 1)
for _, cell := range ship.Cells() {
b[cell.Y][cell.X] = id
}
}
return b
}
// Server-side match states.
const (
StatePlacingShips = 1
@@ -74,6 +98,10 @@ type PlayerSlot struct {
unacked []queuedMessage
lastState int // last state sent to this player, 0 if none
everConnected bool // true after the first successful join
ships []protocol.Ship
board Board
shipsPlaced bool
}
// Match is the state of a single game of sea battle.
@@ -105,6 +133,29 @@ func (m *Match) State() int {
return m.state
}
// Ships returns a copy of the ships placed by the player in slotIdx,
// or nil if ships have not been placed yet.
func (m *Match) Ships(slotIdx int) []protocol.Ship {
m.mu.Lock()
defer m.mu.Unlock()
if slotIdx < 0 || slotIdx >= len(m.slots) || !m.slots[slotIdx].shipsPlaced {
return nil
}
res := make([]protocol.Ship, len(m.slots[slotIdx].ships))
copy(res, m.slots[slotIdx].ships)
return res
}
// Board returns the board of the player in slotIdx and whether ships have been placed.
func (m *Match) Board(slotIdx int) (Board, bool) {
m.mu.Lock()
defer m.mu.Unlock()
if slotIdx < 0 || slotIdx >= len(m.slots) {
return Board{}, false
}
return m.slots[slotIdx].board, m.slots[slotIdx].shipsPlaced
}
func (m *Match) slotIndex(userID int64) int {
switch userID {
case m.playerIDs[0]:
@@ -291,6 +342,64 @@ func (m *Manager) Ack(match *Match, idx int, seq int64) error {
return nil
}
// PlaceShips validates and stores the player's ship placement, advancing to the next match state.
// The state order depends on which player completed the placement first:
// - If Player 1 is first: StatePlacingShips -> StatePlayer1Ready -> StatePlayer1Turn
// - If Player 2 is first: StatePlacingShips -> StatePlayer2Ready -> StatePlayer2Turn
func (m *Manager) PlaceShips(ctx context.Context, match *Match, slotIdx int, ships []protocol.Ship) error {
if err := protocol.ValidateShips(ships); err != nil {
return err
}
match.mu.Lock()
defer match.mu.Unlock()
if match.ended {
return ErrMatchFinished
}
if !match.started {
return ErrMatchNotStarted
}
if slotIdx < 0 || slotIdx >= len(match.slots) {
return ErrInvalidSlot
}
slot := match.slots[slotIdx]
if slot.shipsPlaced {
return ErrShipsAlreadyPlaced
}
var nextState int
switch match.state {
case StatePlacingShips:
if slotIdx == 0 {
nextState = StatePlayer1Ready
} else {
nextState = StatePlayer2Ready
}
case StatePlayer1Ready:
if slotIdx != 1 {
return ErrUnexpectedState
}
nextState = StatePlayer1Turn
case StatePlayer2Ready:
if slotIdx != 0 {
return ErrUnexpectedState
}
nextState = StatePlayer2Turn
default:
return ErrUnexpectedState
}
slot.ships = make([]protocol.Ship, len(ships))
copy(slot.ships, ships)
slot.board = NewBoard(ships)
slot.shipsPlaced = true
m.setStateLocked(ctx, match, nextState)
return nil
}
// SetState moves the match into a new server-side state and notifies both players.
func (m *Manager) SetState(ctx context.Context, match *Match, state int) {
match.mu.Lock()