freego/game.go

283 lines
6.2 KiB
Go
Raw Normal View History

2022-02-21 19:07:28 -05:00
package freego
2022-02-18 18:36:18 -05:00
import (
"errors"
"fmt"
"math"
)
//GameState is an int representing the current state the game's in
2022-03-07 15:23:14 -05:00
//go:generate enumer -type=GameState
2022-02-18 18:36:18 -05:00
type GameState int
const (
gameLobby GameState = iota
gameSetup
gameTurnRed
gameTurnBlue
gameWinRed
gameWinBlue
)
//Colour reperensents player colour
2022-02-20 18:36:52 -05:00
//go:generate enumer -type=Colour
2022-02-18 18:36:18 -05:00
type Colour int
//Colour consts
const (
Red Colour = iota
Blue
NoColour
)
//Game represents general game state
type Game struct {
2022-02-21 19:07:28 -05:00
Board *Board
2022-03-03 13:15:32 -05:00
State GameState
2022-02-18 18:36:18 -05:00
}
//NewGame creates a new game and sets the state to lobby
func NewGame() *Game {
return &Game{
2022-02-21 19:07:28 -05:00
Board: NewBoard(8),
2022-03-03 13:15:32 -05:00
State: gameLobby,
2022-02-18 18:36:18 -05:00
}
}
func (g *Game) String() string {
var board string
2022-02-21 19:07:28 -05:00
for i := 0; i < g.Board.size; i++ {
2022-02-18 18:36:18 -05:00
board = board + "|"
2022-02-21 19:07:28 -05:00
for j := 0; j < g.Board.size; j++ {
board = board + g.Board.board[i][j].String()
2022-02-18 18:36:18 -05:00
}
board = board + "|\n"
}
2022-03-03 13:15:32 -05:00
return fmt.Sprintf("Status: %v\n%v", g.State, board)
2022-02-18 18:36:18 -05:00
}
//Parse a given RawCommand, return a ParsedCommand or an error
func (g *Game) Parse(p Player, cmd *RawCommand) (*ParsedCommand, error) {
2022-03-03 13:15:32 -05:00
if g.State != gameTurnRed && g.State != gameTurnBlue {
2022-02-18 18:36:18 -05:00
return nil, errors.New("game has not started")
}
2022-02-21 19:07:28 -05:00
if !g.Board.validatePoint(cmd.srcX, cmd.srcY) || !g.Board.validatePoint(cmd.dstX, cmd.dstY) {
2022-02-18 18:36:18 -05:00
return nil, errors.New("invalid location in command")
}
2022-03-03 13:15:32 -05:00
if (p.Colour() == Red && g.State != gameTurnRed) || (p.Colour() == Blue && g.State != gameTurnBlue) {
2022-02-18 18:36:18 -05:00
return nil, errors.New("not your turn")
}
2022-02-21 19:07:28 -05:00
start, err := g.Board.GetPiece(cmd.srcX, cmd.srcY)
2022-02-18 18:36:18 -05:00
if err != nil {
return nil, err
}
if cmd.act != "-" && cmd.act != "x" {
return nil, errors.New("invalid command action")
}
2022-02-21 19:07:28 -05:00
end, err := g.Board.GetPiece(cmd.dstX, cmd.dstY)
2022-02-18 18:36:18 -05:00
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 {
2022-03-03 13:15:32 -05:00
if g.State == gameTurnRed {
g.State = gameTurnBlue
} else if g.State == gameTurnBlue {
g.State = gameTurnRed
2022-02-18 18:36:18 -05:00
} else {
return false, errors.New("bad game state")
}
}
2022-02-20 18:36:52 -05:00
return res, nil
2022-02-18 18:36:18 -05:00
}
//SetupPiece adds a piece to the board during setup
func (g *Game) SetupPiece(x, y int, p *Piece) (bool, error) {
2022-03-03 13:15:32 -05:00
if g.State != gameSetup {
2022-02-18 18:36:18 -05:00
return false, errors.New("Trying to setup piece when not in setup")
}
2022-02-20 18:36:52 -05:00
if p == nil {
return false, errors.New("Tried to setup a nil piece")
}
2022-02-21 19:07:28 -05:00
if !g.Board.validatePoint(x, y) {
2022-02-18 18:36:18 -05:00
return false, errors.New("Invalid location")
}
2022-02-21 19:07:28 -05:00
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))
2022-02-18 18:36:18 -05:00
}
2022-02-21 19:07:28 -05:00
return g.Board.Place(x, y, p)
2022-02-18 18:36:18 -05:00
}
//Start start the game
2022-02-20 17:06:39 -05:00
func (g *Game) Start() bool {
2022-03-03 13:15:32 -05:00
if g.State == gameSetup {
g.State = gameTurnRed
2022-02-20 17:06:39 -05:00
return true
}
return false
2022-02-18 18:36:18 -05:00
}
2022-03-07 14:45:14 -05:00
//Setup puts the game in setup mode
func (g *Game) Setup() bool {
if g.State == gameLobby {
g.State = gameSetup
return true
}
return false
}
2022-02-18 18:36:18 -05:00
func (g *Game) move(x, y, s, t int) (bool, error) {
2022-02-21 19:07:28 -05:00
startPiece, err := g.Board.GetPiece(x, y)
2022-02-18 18:36:18 -05:00
if err != nil {
return false, err
}
2022-02-21 19:07:28 -05:00
endPiece, err := g.Board.GetPiece(s, t)
2022-02-18 18:36:18 -05:00
if err != nil {
return false, err
}
if startPiece == nil {
return false, errors.New("invalid start piece for move")
}
if endPiece != nil {
return false, nil
}
2022-02-20 18:43:31 -05:00
//attempt to remove starting piece first
2022-02-21 19:07:28 -05:00
err = g.Board.Remove(x, y)
2022-02-18 18:36:18 -05:00
if err != nil {
return false, err
}
2022-02-20 18:43:31 -05:00
// then place piece in new location
2022-02-21 19:07:28 -05:00
res, err := g.Board.Place(s, t, startPiece)
2022-02-20 18:43:31 -05:00
if err != nil {
return false, err
2022-02-18 18:36:18 -05:00
}
2022-02-20 18:43:31 -05:00
return res, nil
2022-02-18 18:36:18 -05:00
}
func (g *Game) strike(x, y, s, t int) (bool, error) {
2022-02-21 19:07:28 -05:00
startPiece, err := g.Board.GetPiece(x, y)
2022-02-18 18:36:18 -05:00
if err != nil {
return false, err
}
2022-02-21 19:07:28 -05:00
endPiece, err := g.Board.GetPiece(s, t)
2022-02-18 18:36:18 -05:00
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
2022-02-21 19:07:28 -05:00
err = g.Board.Remove(x, y)
2022-02-20 18:43:31 -05:00
if err != nil {
return true, err
}
2022-02-18 18:36:18 -05:00
case 0:
//tie
2022-02-21 19:07:28 -05:00
err = g.Board.Remove(x, y)
err2 := g.Board.Remove(s, t)
2022-02-20 18:43:31 -05:00
if err != nil || err2 != nil {
return true, fmt.Errorf("Errors: %v %v", err, err2)
}
2022-02-18 18:36:18 -05:00
case 1:
//endPiece lost
2022-02-21 19:07:28 -05:00
err := g.Board.Remove(s, t)
2022-02-20 18:43:31 -05:00
if err != nil {
return true, err
}
2022-02-18 18:36:18 -05:00
//scouts replace the piece that was destroyed
2022-02-20 18:36:52 -05:00
if startPiece.Rank == Scout {
2022-02-21 19:07:28 -05:00
err = g.Board.Remove(x, y)
2022-02-20 18:43:31 -05:00
if err != nil {
return true, err
}
2022-02-21 19:07:28 -05:00
res, err := g.Board.Place(s, t, startPiece)
2022-02-20 18:43:31 -05:00
if err != nil {
return true, err
}
if !res {
return false, errors.New("Combat was valid but somehow placing the new piece was not")
}
2022-02-18 18:36:18 -05:00
}
}
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 atk.Hidden {
return 0, errors.New("trying to attack with a piece we know nothing about?")
}
if def.Hidden {
return 0, errors.New("defender has not been revealed to us")
}
//handle special cases first
//miner hitting bomb
2022-02-20 18:36:52 -05:00
if atk.Rank == Miner && def.Rank == Bomb {
2022-02-18 18:36:18 -05:00
return 1, nil
}
2022-02-20 18:36:52 -05:00
//anyone else hitting bomb
if def.Rank == Bomb {
return -1, nil
}
2022-02-18 18:36:18 -05:00
//spy hitting marshal
2022-02-20 18:36:52 -05:00
if atk.Rank == Spy && def.Rank == Marshal {
2022-02-18 18:36:18 -05:00
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
}
}