initial skeleton
This commit is contained in:
commit
bd149c3d5d
86
board.go
Normal file
86
board.go
Normal file
@ -0,0 +1,86 @@
|
||||
package main
|
||||
|
||||
import "errors"
|
||||
|
||||
//Board represents main game board
|
||||
type Board struct {
|
||||
board [][]*Tile
|
||||
size int
|
||||
}
|
||||
|
||||
//NewBoard creates a new board instance
|
||||
func NewBoard(size int) *Board {
|
||||
b := make([][]*Tile, size)
|
||||
var colour Colour
|
||||
for i := 0; i < size; i++ {
|
||||
b[i] = make([]*Tile, size)
|
||||
if i < size/2 {
|
||||
colour = Blue
|
||||
} else {
|
||||
colour = Red
|
||||
}
|
||||
for j := 0; j < size; j++ {
|
||||
b[i][j] = &Tile{i, j, true, nil, colour}
|
||||
}
|
||||
}
|
||||
return &Board{
|
||||
board: b,
|
||||
size: size,
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Board) validatePoint(x, y int) bool {
|
||||
if x < 0 || x >= len(b.board) {
|
||||
return false
|
||||
}
|
||||
if y < 0 || y >= len(b.board) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
//GetPiece returns the piece at a given location
|
||||
func (b *Board) GetPiece(x, y int) (*Piece, error) {
|
||||
if !b.validatePoint(x, y) {
|
||||
return nil, errors.New("GetPiece invalid location")
|
||||
}
|
||||
return b.board[x][y].Piece(), nil
|
||||
}
|
||||
|
||||
//Place a piece on the board; returns false if a piece is already there
|
||||
func (b *Board) Place(x, y int, p *Piece) (bool, error) {
|
||||
if !b.validatePoint(x, y) {
|
||||
return false, errors.New("Place invalid location")
|
||||
}
|
||||
if b.board[x][y].Piece() != nil {
|
||||
return false, nil
|
||||
}
|
||||
err := b.board[x][y].Place(p)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
//Remove a piece from a tile
|
||||
func (b *Board) Remove(x, y int) error {
|
||||
if !b.validatePoint(x, y) {
|
||||
return errors.New("Remove invalid location")
|
||||
}
|
||||
b.board[x][y].Remove()
|
||||
return nil
|
||||
}
|
||||
|
||||
//GetColor returns color of tile
|
||||
func (b *Board) GetColor(x, y int) Colour {
|
||||
return b.board[x][y].Colour()
|
||||
}
|
||||
|
||||
//AddRiver puts a river tile at specified location
|
||||
func (b *Board) AddRiver(x, y int) (bool, error) {
|
||||
if !b.validatePoint(x, y) {
|
||||
return false, errors.New("River invalid location")
|
||||
}
|
||||
b.board[x][y].AddTerrain()
|
||||
return true, nil
|
||||
}
|
63
command.go
Normal file
63
command.go
Normal file
@ -0,0 +1,63 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var cmdRegxp = regexp.MustCompile("([a-zA-Z])([1-9])(x|-)([a-zA-Z])([1-9])")
|
||||
var ranks = "ABCDEFHI"
|
||||
|
||||
//RawCommand is a game command, converted from algebraic notation
|
||||
type RawCommand struct {
|
||||
srcX int
|
||||
srcY int
|
||||
dstX int
|
||||
dstY int
|
||||
act string
|
||||
}
|
||||
|
||||
//NewRawCommand creates a RawCommand struct from a algebraic notation string
|
||||
func NewRawCommand(cmd string) (*RawCommand, error) {
|
||||
res := cmdRegxp.FindAllString(cmd, -1)
|
||||
if res == nil {
|
||||
return nil, errors.New("error creating command from string")
|
||||
}
|
||||
sx := strings.Index(ranks, res[0])
|
||||
dx := strings.Index(ranks, res[3])
|
||||
sy, err := strconv.Atoi(res[1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dy, err := strconv.Atoi(res[4])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &RawCommand{sx, sy, dx, dy, res[2]}, nil
|
||||
}
|
||||
|
||||
func (c *RawCommand) String() string {
|
||||
return fmt.Sprintf("%s%d%s%s%d", string(ranks[c.srcX]), c.srcY, c.act, string(ranks[c.dstX]), c.dstY)
|
||||
}
|
||||
|
||||
//ParsedCommand is a game command after being run through the engine
|
||||
type ParsedCommand struct {
|
||||
srcX int
|
||||
srcY int
|
||||
srcRank string
|
||||
dstX int
|
||||
dstY int
|
||||
dstRank string
|
||||
act string
|
||||
}
|
||||
|
||||
func (c *ParsedCommand) String() string {
|
||||
if c.dstRank != "" {
|
||||
return fmt.Sprintf("%s %s%d %s %s %s%d", c.srcRank, string(ranks[c.srcX]), c.srcY, c.act, c.dstRank, string(ranks[c.dstX]), c.dstY)
|
||||
}
|
||||
return fmt.Sprintf("%s %s%d %s %s %s%d", c.srcRank, string(ranks[c.srcX]), c.srcY, c.act, "empty", string(ranks[c.dstX]), c.dstY)
|
||||
|
||||
}
|
242
game.go
Normal file
242
game.go
Normal file
@ -0,0 +1,242 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
)
|
||||
|
||||
//GameState is an int representing the current state the game's in
|
||||
type GameState int
|
||||
|
||||
const (
|
||||
gameLobby GameState = iota
|
||||
gameSetup
|
||||
gameTurnRed
|
||||
gameTurnBlue
|
||||
gameWinRed
|
||||
gameWinBlue
|
||||
)
|
||||
|
||||
//Colour reperensents player 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 true, 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 !g.board.validatePoint(x, y) {
|
||||
return false, errors.New("Invalid location")
|
||||
}
|
||||
if p.Owner != g.board.GetColor(x, y) {
|
||||
return false, errors.New("Can't setup piece on enemy board")
|
||||
}
|
||||
return g.board.Place(x, y, p)
|
||||
}
|
||||
|
||||
//Start start the game
|
||||
func (g *Game) Start() {
|
||||
g.state = gameTurnRed
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
res, err := g.board.Place(s, t, startPiece)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if res {
|
||||
err = g.board.Remove(x, y)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
return true, 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
|
||||
g.board.Remove(x, y)
|
||||
case 0:
|
||||
//tie
|
||||
g.board.Remove(x, y)
|
||||
g.board.Remove(s, t)
|
||||
case 1:
|
||||
//endPiece lost
|
||||
g.board.Remove(s, t)
|
||||
//scouts replace the piece that was destroyed
|
||||
if startPiece.Rank == 1 {
|
||||
g.board.Remove(x, y)
|
||||
g.board.Place(s, t, startPiece)
|
||||
}
|
||||
}
|
||||
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
|
||||
if atk.Rank == 3 && def.Rank == 12 {
|
||||
return 1, nil
|
||||
}
|
||||
//spy hitting marshal
|
||||
if atk.Rank == 0 && def.Rank == 10 {
|
||||
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
|
||||
}
|
||||
}
|
59
main.go
Normal file
59
main.go
Normal file
@ -0,0 +1,59 @@
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func main() {
|
||||
red := NewDummyPlayer(Red)
|
||||
blue := NewDummyPlayer(Blue)
|
||||
game := NewGame()
|
||||
game.state = gameSetup
|
||||
addpiece(game, 0, Blue, 0, 0)
|
||||
addpiece(game, 1, Blue, 0, 1)
|
||||
addpiece(game, 2, Blue, 1, 2)
|
||||
addpiece(game, 3, Blue, 2, 4)
|
||||
addpiece(game, 3, Blue, 1, 3)
|
||||
addpiece(game, 4, Blue, 2, 6)
|
||||
addpiece(game, 5, Blue, 3, 3)
|
||||
addpiece(game, 9, Blue, 0, 6)
|
||||
addpiece(game, 9, Blue, 0, 7)
|
||||
addpiece(game, 0, Blue, 3, 5)
|
||||
addriver(game, 3, 2)
|
||||
addriver(game, 4, 2)
|
||||
addriver(game, 3, 6)
|
||||
addriver(game, 4, 6)
|
||||
addpiece(game, 0, Red, 4, 0)
|
||||
addpiece(game, 1, Red, 4, 1)
|
||||
addpiece(game, 2, Red, 5, 2)
|
||||
addpiece(game, 3, Red, 6, 4)
|
||||
addpiece(game, 3, Red, 5, 3)
|
||||
addpiece(game, 4, Red, 6, 6)
|
||||
addpiece(game, 5, Red, 7, 3)
|
||||
addpiece(game, 9, Red, 4, 5)
|
||||
addpiece(game, 9, Red, 4, 7)
|
||||
addpiece(game, 0, Red, 7, 5)
|
||||
|
||||
fmt.Printf("%v %v\n%v\n", red, blue, game)
|
||||
game.Start()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func addpiece(game *Game, rank int, c Colour, x int, y int) {
|
||||
res, err := game.SetupPiece(x, y, NewPiece(rank, c))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if !res {
|
||||
panic("can't setup")
|
||||
}
|
||||
}
|
||||
|
||||
func addriver(game *Game, x int, y int) {
|
||||
res, err := game.board.AddRiver(x, y)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if !res {
|
||||
panic("can't river")
|
||||
}
|
||||
}
|
34
piece.go
Normal file
34
piece.go
Normal file
@ -0,0 +1,34 @@
|
||||
package main
|
||||
|
||||
import "strconv"
|
||||
|
||||
//Rank represents the rank of a piece
|
||||
type Rank int
|
||||
|
||||
func (r Rank) String() string {
|
||||
return strconv.Itoa(int(r))
|
||||
}
|
||||
|
||||
//Piece :game piece
|
||||
type Piece struct {
|
||||
Rank Rank
|
||||
Owner Colour
|
||||
Hidden bool
|
||||
}
|
||||
|
||||
//NewPiece creates a new piece
|
||||
func NewPiece(r int, o Colour) *Piece {
|
||||
return &Piece{
|
||||
Rank: Rank(r),
|
||||
Owner: o,
|
||||
Hidden: false,
|
||||
}
|
||||
}
|
||||
|
||||
//NewHiddenPiece creates a new hidden piece
|
||||
func NewHiddenPiece(o Colour) *Piece {
|
||||
return &Piece{
|
||||
Owner: o,
|
||||
Hidden: true,
|
||||
}
|
||||
}
|
35
player.go
Normal file
35
player.go
Normal file
@ -0,0 +1,35 @@
|
||||
package main
|
||||
|
||||
//Player represents a player of the game
|
||||
type Player interface {
|
||||
ID() int
|
||||
Colour() Colour
|
||||
}
|
||||
|
||||
//DummyPlayer is the simplest implementation of a player
|
||||
type DummyPlayer struct {
|
||||
id Colour
|
||||
}
|
||||
|
||||
//ID returns player ID
|
||||
func (d *DummyPlayer) ID() int {
|
||||
return int(d.id)
|
||||
}
|
||||
|
||||
//Colour returns player colour (same as ID)
|
||||
func (d *DummyPlayer) Colour() Colour {
|
||||
return d.id
|
||||
}
|
||||
|
||||
//NewDummyPlayer creates a new player
|
||||
func NewDummyPlayer(i Colour) *DummyPlayer {
|
||||
return &DummyPlayer{i}
|
||||
}
|
||||
|
||||
func (d *DummyPlayer) String() string {
|
||||
if d.id == Red {
|
||||
return "Red Player"
|
||||
} else {
|
||||
return "Blue Player"
|
||||
}
|
||||
}
|
103
tile.go
Normal file
103
tile.go
Normal file
@ -0,0 +1,103 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
//Tile represents a spot on the board
|
||||
type Tile struct {
|
||||
x int
|
||||
y int
|
||||
passable bool
|
||||
entity *Piece
|
||||
colour Colour
|
||||
}
|
||||
|
||||
func (t *Tile) String() string {
|
||||
icon := "?"
|
||||
if !t.passable {
|
||||
icon = "X"
|
||||
} else if t.entity != nil {
|
||||
if !t.entity.Hidden {
|
||||
icon = t.entity.Rank.String()
|
||||
} else {
|
||||
icon = "O"
|
||||
}
|
||||
} else {
|
||||
icon = " "
|
||||
}
|
||||
return fmt.Sprintf("|%v|", icon)
|
||||
}
|
||||
|
||||
//Place a piece on the tile
|
||||
func (t *Tile) Place(p *Piece) error {
|
||||
if p == nil {
|
||||
return errors.New("tried to place nil piece")
|
||||
}
|
||||
if !t.passable {
|
||||
return errors.New("tile not passable")
|
||||
}
|
||||
if t.entity != nil {
|
||||
return errors.New("entity already present")
|
||||
}
|
||||
t.entity = p
|
||||
return nil
|
||||
}
|
||||
|
||||
//Remove the piece from a tile
|
||||
func (t *Tile) Remove() {
|
||||
t.entity = nil
|
||||
}
|
||||
|
||||
//Empty returns true if tile is empty
|
||||
func (t *Tile) Empty() bool {
|
||||
return t.entity == nil
|
||||
}
|
||||
|
||||
//Team returns the team of the piece currently occupying it, or 0
|
||||
func (t *Tile) Team() Colour {
|
||||
if t.entity != nil {
|
||||
return t.entity.Owner
|
||||
}
|
||||
return NoColour
|
||||
}
|
||||
|
||||
//Occupied returns true if the tile is currently occupied
|
||||
func (t *Tile) Occupied() bool {
|
||||
return t.entity != nil
|
||||
}
|
||||
|
||||
//Piece returns the current Piece if any
|
||||
func (t *Tile) Piece() *Piece {
|
||||
return t.entity
|
||||
}
|
||||
|
||||
//Passable returns if the tile is passable
|
||||
func (t *Tile) Passable() bool {
|
||||
return t.passable
|
||||
}
|
||||
|
||||
//Colour returns the tile colour
|
||||
func (t *Tile) Colour() Colour {
|
||||
return t.colour
|
||||
}
|
||||
|
||||
//X returns tile X position
|
||||
func (t *Tile) X() int {
|
||||
return t.x
|
||||
}
|
||||
|
||||
//Y returns tile y position
|
||||
func (t *Tile) Y() int {
|
||||
return t.y
|
||||
}
|
||||
|
||||
//AddTerrain adds specified terrain to position
|
||||
func (t *Tile) AddTerrain() bool {
|
||||
if t.entity != nil {
|
||||
return false
|
||||
}
|
||||
t.passable = false
|
||||
return true
|
||||
}
|
Loading…
Reference in New Issue
Block a user