179 lines
4.4 KiB
Go
179 lines
4.4 KiB
Go
package games
|
|
|
|
import (
|
|
"fmt"
|
|
"localrepo/plugins/structs"
|
|
)
|
|
|
|
//
|
|
// BATTLESHIP RULES IMPLEMENTATION
|
|
//
|
|
// NOTES:
|
|
// - 2 players
|
|
// - Each player has 2 boards:
|
|
// 1. Their own ship board (state.Board is not reused here)
|
|
// 2. Their "shots" board (hits/misses on opponent)
|
|
// - We store boards in Player.Metadata as JSON strings
|
|
// (simplest method without changing your structs).
|
|
//
|
|
|
|
// ShipBoard and ShotBoard are encoded inside Metadata:
|
|
//
|
|
// Metadata["ship_board"] = JSON string of [][]string
|
|
// Metadata["shot_board"] = JSON string of [][]string
|
|
//
|
|
|
|
// ------------------------------
|
|
// Helpers: encode/decode
|
|
// ------------------------------
|
|
|
|
func encodeBoard(b [][]string) string {
|
|
out := "["
|
|
for i, row := range b {
|
|
out += "["
|
|
for j, col := range row {
|
|
out += fmt.Sprintf("%q", col)
|
|
if j < len(row)-1 {
|
|
out += ","
|
|
}
|
|
}
|
|
out += "]"
|
|
if i < len(b)-1 {
|
|
out += ","
|
|
}
|
|
}
|
|
out += "]"
|
|
return out
|
|
}
|
|
|
|
func decodeBoard(s string) [][]string {
|
|
var out [][]string
|
|
// should never fail; safe fallback
|
|
_ = json.Unmarshal([]byte(s), &out)
|
|
return out
|
|
}
|
|
|
|
// ------------------------------
|
|
// BattleshipRules
|
|
// ------------------------------
|
|
|
|
type BattleshipRules struct{}
|
|
|
|
func (b *BattleshipRules) MaxPlayers() int { return 2 }
|
|
|
|
// ------------------------------
|
|
// Assign player boards
|
|
// ------------------------------
|
|
func (b *BattleshipRules) AssignPlayerSymbols(players []*Player) {
|
|
// Battleship has no symbols like X/O,
|
|
// but we use this hook to initialize per-player boards.
|
|
|
|
for _, p := range players {
|
|
// 10x10 boards
|
|
empty := make([][]string, 10)
|
|
for r := range empty {
|
|
empty[r] = make([]string, 10)
|
|
}
|
|
|
|
// ship board → players place ships manually via a "setup" phase
|
|
p.Metadata["ship_board"] = encodeBoard(empty)
|
|
|
|
// shot board → empty grid that tracks hits/misses
|
|
p.Metadata["shot_board"] = encodeBoard(empty)
|
|
}
|
|
}
|
|
|
|
// ------------------------------
|
|
// ValidateMove
|
|
// payload.data = { "row": int, "col": int }
|
|
// ------------------------------
|
|
|
|
func (b *BattleshipRules) ValidateMove(state *structs.MatchState, playerIdx int, payload MovePayload) bool {
|
|
rF, ok1 := payload.Data["row"].(float64)
|
|
cF, ok2 := payload.Data["col"].(float64)
|
|
if !ok1 || !ok2 {
|
|
return false
|
|
}
|
|
|
|
r := int(rF)
|
|
c := int(cF)
|
|
|
|
if r < 0 || r > 9 || c < 0 || c > 9 {
|
|
return false
|
|
}
|
|
|
|
// Check if this spot was already shot before
|
|
shotBoard := decodeBoard(state.Players[playerIdx].Metadata["shot_board"])
|
|
return shotBoard[r][c] == ""
|
|
}
|
|
|
|
// ------------------------------
|
|
// ApplyMove
|
|
// ------------------------------
|
|
func (b *BattleshipRules) ApplyMove(state *structs.MatchState, playerIdx int, payload MovePayload) {
|
|
|
|
attacker := state.Players[playerIdx]
|
|
defenderIdx := 1 - playerIdx
|
|
defender := state.Players[defenderIdx]
|
|
|
|
r := int(payload.Data["row"].(float64))
|
|
c := int(payload.Data["col"].(float64))
|
|
|
|
shotBoard := decodeBoard(attacker.Metadata["shot_board"])
|
|
shipBoard := decodeBoard(defender.Metadata["ship_board"])
|
|
|
|
if shipBoard[r][c] == "S" {
|
|
// hit
|
|
shotBoard[r][c] = "H"
|
|
shipBoard[r][c] = "X" // ship cell destroyed
|
|
} else {
|
|
// miss
|
|
shotBoard[r][c] = "M"
|
|
}
|
|
|
|
// Save back
|
|
attacker.Metadata["shot_board"] = encodeBoard(shotBoard)
|
|
defender.Metadata["ship_board"] = encodeBoard(shipBoard)
|
|
}
|
|
|
|
// ------------------------------
|
|
// CheckGameOver
|
|
// ------------------------------
|
|
func (b *BattleshipRules) CheckGameOver(state *structs.MatchState) (bool, int) {
|
|
|
|
for i, p := range state.Players {
|
|
ships := decodeBoard(p.Metadata["ship_board"])
|
|
|
|
alive := false
|
|
for r := range ships {
|
|
for c := range ships[r] {
|
|
if ships[r][c] == "S" {
|
|
alive = true
|
|
}
|
|
}
|
|
}
|
|
|
|
if !alive {
|
|
// this player has no ships left → opponent wins
|
|
return true, 1 - i
|
|
}
|
|
}
|
|
|
|
return false, -1
|
|
}
|
|
|
|
// ------------------------------
|
|
// Forfeit Winner
|
|
// ------------------------------
|
|
func (b *BattleshipRules) ForfeitWinner(state *structs.MatchState, leaverIndex int) int {
|
|
|
|
// If player leaves, opponent automatically wins.
|
|
if leaverIndex == 0 {
|
|
return 1
|
|
}
|
|
if leaverIndex == 1 {
|
|
return 0
|
|
}
|
|
return -1
|
|
}
|