282 lines
6.1 KiB
Go
282 lines
6.1 KiB
Go
package freego
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"math"
|
|
)
|
|
|
|
//GameState is an int representing the current state the game's in
|
|
//go:generate enumer -type=GameState
|
|
type GameState int
|
|
|
|
const (
|
|
gameLobby GameState = iota
|
|
gameSetup
|
|
gameTurnRed
|
|
gameTurnBlue
|
|
gameWinRed
|
|
gameWinBlue
|
|
)
|
|
|
|
//Colour reperensents player colour
|
|
//go:generate enumer -type=Colour
|
|
type Colour int
|
|
|
|
//Colour consts
|
|
const (
|
|
Red Colour = iota
|
|
Blue
|
|
NoColour
|
|
)
|
|
|
|
//Game represents general game state
|
|
type Game struct {
|
|
Board *Board
|
|
State GameState
|
|
}
|
|
|
|
//NewGame creates a new game and sets the state to lobby
|
|
func NewGame() *Game {
|
|
return &Game{
|
|
Board: NewBoard(8),
|
|
State: gameLobby,
|
|
}
|
|
}
|
|
|
|
func (g *Game) String() string {
|
|
var board string
|
|
for i := 0; i < g.Board.size; i++ {
|
|
board = board + "|"
|
|
for j := 0; j < g.Board.size; j++ {
|
|
board = board + g.Board.board[i][j].String()
|
|
}
|
|
board = board + "|\n"
|
|
}
|
|
|
|
return fmt.Sprintf("Status: %v\n%v", g.State, board)
|
|
}
|
|
|
|
//Parse a given RawCommand, return a ParsedCommand or an error
|
|
func (g *Game) Parse(p Player, cmd *RawCommand) (*ParsedCommand, error) {
|
|
if g.State != gameTurnRed && g.State != gameTurnBlue {
|
|
return nil, errors.New("game has not started")
|
|
}
|
|
if !g.Board.validatePoint(cmd.srcX, cmd.srcY) || !g.Board.validatePoint(cmd.dstX, cmd.dstY) {
|
|
return nil, errors.New("invalid location in command")
|
|
}
|
|
if (p.Colour() == Red && g.State != gameTurnRed) || (p.Colour() == Blue && g.State != gameTurnBlue) {
|
|
return nil, errors.New("not your turn")
|
|
}
|
|
start, err := g.Board.GetPiece(cmd.srcX, cmd.srcY)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if cmd.act != "-" && cmd.act != "x" {
|
|
return nil, errors.New("invalid command action")
|
|
}
|
|
end, err := g.Board.GetPiece(cmd.dstX, cmd.dstY)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if start == nil {
|
|
return nil, errors.New("empty start piece")
|
|
}
|
|
startRank := start.Rank.String()
|
|
endRank := ""
|
|
if end != nil {
|
|
endRank = end.Rank.String()
|
|
}
|
|
return &ParsedCommand{cmd.srcX, cmd.srcY, startRank, cmd.dstX, cmd.dstY, endRank, cmd.act}, nil
|
|
}
|
|
|
|
//Mutate the game state given a ParsedCommand
|
|
func (g *Game) Mutate(cmd *ParsedCommand) (bool, error) {
|
|
var res bool
|
|
var err error
|
|
switch cmd.act {
|
|
case "-":
|
|
res, err = g.move(cmd.srcX, cmd.srcY, cmd.dstX, cmd.dstY)
|
|
case "x":
|
|
res, err = g.strike(cmd.srcX, cmd.srcY, cmd.dstX, cmd.dstY)
|
|
default:
|
|
return false, errors.New("invalid mutate action")
|
|
}
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
if res {
|
|
if g.State == gameTurnRed {
|
|
g.State = gameTurnBlue
|
|
} else if g.State == gameTurnBlue {
|
|
g.State = gameTurnRed
|
|
} else {
|
|
return false, errors.New("bad game state")
|
|
}
|
|
}
|
|
return res, nil
|
|
}
|
|
|
|
//SetupPiece adds a piece to the board during setup
|
|
func (g *Game) SetupPiece(x, y int, p *Piece) (bool, error) {
|
|
if g.State != gameSetup {
|
|
return false, errors.New("Trying to setup piece when not in setup")
|
|
}
|
|
if p == nil {
|
|
return false, errors.New("Tried to setup a nil piece")
|
|
}
|
|
if !g.Board.validatePoint(x, y) {
|
|
return false, errors.New("Invalid location")
|
|
}
|
|
if p.Owner != g.Board.GetColor(x, y) {
|
|
return false, fmt.Errorf("Can't setup piece on enemy board: %v != %v", p.Owner, g.Board.GetColor(x, y))
|
|
}
|
|
return g.Board.Place(x, y, p)
|
|
}
|
|
|
|
//Start start the game
|
|
func (g *Game) Start() bool {
|
|
if g.State == gameSetup {
|
|
g.State = gameTurnRed
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
//Setup puts the game in setup mode
|
|
func (g *Game) Setup() bool {
|
|
if g.State == gameLobby {
|
|
g.State = gameSetup
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (g *Game) move(x, y, s, t int) (bool, error) {
|
|
startPiece, err := g.Board.GetPiece(x, y)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
endPiece, err := g.Board.GetPiece(s, t)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
if startPiece == nil {
|
|
return false, errors.New("invalid start piece for move")
|
|
}
|
|
if endPiece != nil {
|
|
return false, nil
|
|
}
|
|
//attempt to remove starting piece first
|
|
err = g.Board.Remove(x, y)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
// then place piece in new location
|
|
res, err := g.Board.Place(s, t, startPiece)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
return res, nil
|
|
}
|
|
|
|
func (g *Game) strike(x, y, s, t int) (bool, error) {
|
|
startPiece, err := g.Board.GetPiece(x, y)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
endPiece, err := g.Board.GetPiece(s, t)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
if startPiece == nil {
|
|
return false, errors.New("invalid start piece for strike")
|
|
}
|
|
if endPiece == nil {
|
|
return false, nil
|
|
}
|
|
|
|
distance := math.Sqrt(math.Pow(float64(s-x), 2) + math.Pow(float64(t-y), 2))
|
|
if distance > 1 && startPiece.Rank != 1 {
|
|
//trying to attack a piece more then 1 square away when not a scout
|
|
return false, nil
|
|
}
|
|
//bombs can't attack
|
|
if startPiece.Rank == 12 {
|
|
return false, nil
|
|
}
|
|
|
|
r, err := g.combat(startPiece, endPiece)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
switch r {
|
|
case -1:
|
|
//startPiece lost
|
|
err = g.Board.Remove(x, y)
|
|
if err != nil {
|
|
return true, err
|
|
}
|
|
case 0:
|
|
//tie
|
|
err = g.Board.Remove(x, y)
|
|
err2 := g.Board.Remove(s, t)
|
|
if err != nil || err2 != nil {
|
|
return true, fmt.Errorf("Errors: %v %v", err, err2)
|
|
}
|
|
case 1:
|
|
//endPiece lost
|
|
err := g.Board.Remove(s, t)
|
|
if err != nil {
|
|
return true, err
|
|
}
|
|
//scouts replace the piece that was destroyed
|
|
if startPiece.Rank == Scout {
|
|
err = g.Board.Remove(x, y)
|
|
if err != nil {
|
|
return true, err
|
|
}
|
|
res, err := g.Board.Place(s, t, startPiece)
|
|
if err != nil {
|
|
return true, err
|
|
}
|
|
if !res {
|
|
return false, errors.New("Combat was valid but somehow placing the new piece was not")
|
|
}
|
|
}
|
|
}
|
|
return true, nil
|
|
}
|
|
|
|
func (g *Game) combat(atk, def *Piece) (int, error) {
|
|
if atk == nil || def == nil {
|
|
return 0, errors.New("invalid attacker or defender")
|
|
}
|
|
if def.Hidden && def.Rank == Unknown {
|
|
return 0, errors.New("defender has not been revealed to us")
|
|
}
|
|
//reveal attacker
|
|
atk.Hidden = false
|
|
//handle special cases first
|
|
//miner hitting bomb
|
|
if atk.Rank == Miner && def.Rank == Bomb {
|
|
return 1, nil
|
|
}
|
|
//anyone else hitting bomb
|
|
if def.Rank == Bomb {
|
|
return -1, nil
|
|
}
|
|
//spy hitting marshal
|
|
if atk.Rank == Spy && def.Rank == Marshal {
|
|
return 1, nil
|
|
}
|
|
//normal cases
|
|
if atk.Rank > def.Rank {
|
|
return 1, nil
|
|
} else if atk.Rank == def.Rank {
|
|
return 0, nil
|
|
} else {
|
|
return -1, nil
|
|
}
|
|
}
|