-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
1 changed file
with
350 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,350 @@ | ||
package rules | ||
|
||
import ( | ||
"bytes" | ||
"errors" | ||
"fmt" | ||
"strings" | ||
) | ||
|
||
const ( | ||
BOARD_DIM = 8 | ||
RED = "red" | ||
BLACK = "black" | ||
ROW_SEP = "|" | ||
) | ||
|
||
type Player struct { | ||
Color string | ||
} | ||
|
||
type Piece struct { | ||
Player Player | ||
King bool | ||
} | ||
|
||
var PieceStrings = map[Player]string{ | ||
RED_PLAYER: "r", | ||
BLACK_PLAYER: "b", | ||
NO_PLAYER: "*", | ||
} | ||
|
||
var NO_PIECE = Piece{NO_PLAYER, false} | ||
|
||
var StringPieces = map[string]Piece{ | ||
"r": Piece{RED_PLAYER, false}, | ||
"b": Piece{BLACK_PLAYER, false}, | ||
"R": Piece{RED_PLAYER, true}, | ||
"B": Piece{BLACK_PLAYER, true}, | ||
"*": NO_PIECE, | ||
} | ||
|
||
type Pos struct { | ||
X int | ||
Y int | ||
} | ||
|
||
var NO_POS = Pos{-1, -1} | ||
|
||
var BLACK_PLAYER = Player{BLACK} | ||
var RED_PLAYER = Player{RED} | ||
var NO_PLAYER = Player{ | ||
Color: "NO_PLAYER", | ||
} | ||
|
||
var Players = map[string]Player{ | ||
RED: RED_PLAYER, | ||
BLACK: BLACK_PLAYER, | ||
} | ||
|
||
var Opponents = map[Player]Player{ | ||
BLACK_PLAYER: RED_PLAYER, | ||
RED_PLAYER: BLACK_PLAYER, | ||
} | ||
|
||
var Usable = map[Pos]bool{} | ||
var Moves = map[Player]map[Pos]map[Pos]bool{} | ||
var Jumps = map[Player]map[Pos]map[Pos]Pos{} | ||
var KingMoves = map[Pos]map[Pos]bool{} | ||
var KingJumps = map[Pos]map[Pos]Pos{} | ||
|
||
func Capture(src, dst Pos) Pos { | ||
return Pos{(src.X + dst.X) / 2, (src.Y + dst.Y) / 2} | ||
} | ||
|
||
func init() { | ||
|
||
// Initialize usable spaces | ||
for y := 0; y < BOARD_DIM; y++ { | ||
for x := (y + 1) % 2; x < BOARD_DIM; x += 2 { | ||
Usable[Pos{X: x, Y: y}] = true | ||
} | ||
} | ||
|
||
// Initialize deep maps | ||
for _, p := range Players { | ||
Moves[p] = map[Pos]map[Pos]bool{} | ||
Jumps[p] = map[Pos]map[Pos]Pos{} | ||
} | ||
|
||
// Compute possible moves, jumps and captures | ||
for pos := range Usable { | ||
KingMoves[pos] = map[Pos]bool{} | ||
KingJumps[pos] = map[Pos]Pos{} | ||
var directions = []int{1, -1} | ||
for i, player := range []Player{BLACK_PLAYER, RED_PLAYER} { | ||
Moves[player][pos] = map[Pos]bool{} | ||
Jumps[player][pos] = map[Pos]Pos{} | ||
movOff := 1 | ||
jmpOff := 2 | ||
for _, direction := range directions { | ||
mov := Pos{pos.X + (movOff * direction), pos.Y + (movOff * directions[i])} | ||
if Usable[mov] { | ||
Moves[player][pos][mov] = true | ||
KingMoves[pos][mov] = true | ||
} | ||
jmp := Pos{pos.X + (jmpOff * direction), pos.Y + (jmpOff * directions[i])} | ||
if Usable[jmp] { | ||
capturePos := Capture(pos, jmp) | ||
Jumps[player][pos][jmp] = capturePos | ||
KingJumps[pos][jmp] = capturePos | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
type Game struct { | ||
Pieces map[Pos]Piece | ||
Turn Player | ||
} | ||
|
||
func New() *Game { | ||
pieces := make(map[Pos]Piece) | ||
game := &Game{pieces, BLACK_PLAYER} | ||
game.addInitialPieces() | ||
return game | ||
} | ||
|
||
func (game *Game) addInitialPieces() { | ||
for pos := range Usable { | ||
if pos.Y >= 0 && pos.Y < 3 { | ||
game.Pieces[pos] = Piece{BLACK_PLAYER, false} | ||
} | ||
if pos.Y >= BOARD_DIM-3 && pos.Y < BOARD_DIM { | ||
game.Pieces[pos] = Piece{RED_PLAYER, false} | ||
} | ||
} | ||
} | ||
|
||
func (game *Game) PieceAt(pos Pos) bool { | ||
_, ok := game.Pieces[pos] | ||
return ok | ||
} | ||
|
||
func (game *Game) TurnIs(player Player) bool { | ||
return game.Turn == player | ||
} | ||
|
||
func (game *Game) Winner() Player { | ||
red_count := 0 | ||
black_count := 0 | ||
for _, piece := range game.Pieces { | ||
switch { | ||
case piece.Player == BLACK_PLAYER: | ||
black_count += 1 | ||
case piece.Player == RED_PLAYER: | ||
red_count += 1 | ||
} | ||
} | ||
if black_count > 0 && red_count <= 0 { | ||
return BLACK_PLAYER | ||
} else if red_count > 0 && black_count <= 0 { | ||
return RED_PLAYER | ||
} | ||
return NO_PLAYER | ||
} | ||
|
||
func (game *Game) ValidMove(src, dst Pos) bool { | ||
if !game.PieceAt(src) || game.PieceAt(dst) { | ||
return false | ||
} | ||
piece := game.Pieces[src] | ||
if (!piece.King && Moves[piece.Player][src][dst]) || (piece.King && KingMoves[src][dst]) { | ||
return !game.playerHasJump(piece.Player) | ||
} | ||
return game.ValidJump(src, dst) | ||
} | ||
|
||
func (game *Game) ValidJump(src, dst Pos) bool { | ||
if !game.PieceAt(src) || game.PieceAt(dst) { | ||
return false | ||
} | ||
piece := game.Pieces[src] | ||
if !piece.King { | ||
capLoc, jumpOk := Jumps[piece.Player][src][dst] | ||
return jumpOk && game.PieceAt(capLoc) && game.Pieces[capLoc].Player == Opponents[piece.Player] | ||
} else { | ||
capLoc, kingJumpOk := KingJumps[src][dst] | ||
return kingJumpOk && game.PieceAt(capLoc) && game.Pieces[capLoc].Player == Opponents[piece.Player] | ||
} | ||
} | ||
|
||
func (game *Game) kingPiece(dst Pos) { | ||
if !game.PieceAt(dst) { | ||
return | ||
} | ||
piece := game.Pieces[dst] | ||
if (dst.Y == 0 && piece.Player == RED_PLAYER) || | ||
(dst.Y == BOARD_DIM-1 && piece.Player == BLACK_PLAYER) { | ||
piece.King = true | ||
game.Pieces[dst] = piece | ||
} | ||
} | ||
|
||
func (game *Game) updateTurn(dst Pos, jumped bool) { | ||
opponent := Opponents[game.Turn] | ||
if (!jumped || !game.jumpPossibleFrom(dst)) && game.playerHasMove(opponent) { | ||
game.Turn = opponent | ||
} | ||
} | ||
|
||
func (game *Game) jumpPossibleFrom(src Pos) bool { | ||
if !game.PieceAt(src) { | ||
return false | ||
} | ||
piece := game.Pieces[src] | ||
if !piece.King { | ||
// enumerate all player jumps and return true if one is valid | ||
for dst := range Jumps[piece.Player][src] { | ||
if game.ValidJump(src, dst) { | ||
return true | ||
} | ||
} | ||
} else { | ||
// enumerate all king jumps and return true if one is valid | ||
for dst := range KingJumps[src] { | ||
if game.ValidJump(src, dst) { | ||
return true | ||
} | ||
} | ||
} | ||
return false | ||
} | ||
|
||
func (game *Game) movePossibleFrom(src Pos) bool { | ||
if !game.PieceAt(src) { | ||
return false | ||
} | ||
piece := game.Pieces[src] | ||
if !piece.King { | ||
for dst := range Moves[piece.Player][src] { | ||
if game.ValidMove(src, dst) { | ||
return true | ||
} | ||
} | ||
} else { | ||
for dst := range KingMoves[src] { | ||
if game.ValidMove(src, dst) { | ||
return true | ||
} | ||
} | ||
} | ||
return false | ||
} | ||
|
||
func (game *Game) playerHasMove(player Player) bool { | ||
for loc, piece := range game.Pieces { | ||
if piece.Player == player && (game.movePossibleFrom(loc) || game.jumpPossibleFrom(loc)) { | ||
return true | ||
} | ||
} | ||
return false | ||
} | ||
|
||
func (game *Game) playerHasJump(player Player) bool { | ||
for loc, piece := range game.Pieces { | ||
if piece.Player == player && game.jumpPossibleFrom(loc) { | ||
return true | ||
} | ||
} | ||
return false | ||
} | ||
|
||
func (game *Game) Move(src, dst Pos) (captured Pos, err error) { | ||
captured = NO_POS | ||
err = nil | ||
if !game.PieceAt(src) { | ||
return NO_POS, errors.New(fmt.Sprintf("No piece at source position: %v", src)) | ||
} | ||
if game.PieceAt(dst) { | ||
return NO_POS, errors.New(fmt.Sprintf("Already piece at destination position: %v", dst)) | ||
} | ||
if !game.TurnIs(game.Pieces[src].Player) { | ||
return NO_POS, errors.New(fmt.Sprintf("Not %v's turn", game.Pieces[src].Player)) | ||
} | ||
if !game.ValidMove(src, dst) { | ||
return NO_POS, errors.New(fmt.Sprintf("Invalid move: %v to %v", src, dst)) | ||
} | ||
if game.ValidJump(src, dst) { | ||
game.Pieces[dst] = game.Pieces[src] | ||
delete(game.Pieces, src) | ||
captured = Capture(src, dst) | ||
delete(game.Pieces, captured) | ||
} else { | ||
game.Pieces[dst] = game.Pieces[src] | ||
delete(game.Pieces, src) | ||
} | ||
game.updateTurn(dst, captured != NO_POS) | ||
game.kingPiece(dst) | ||
return | ||
} | ||
|
||
func (game *Game) String() string { | ||
var buf bytes.Buffer | ||
for y := 0; y < BOARD_DIM; y++ { | ||
for x := 0; x < BOARD_DIM; x++ { | ||
pos := Pos{x, y} | ||
if game.PieceAt(pos) { | ||
piece := game.Pieces[pos] | ||
val := PieceStrings[piece.Player] | ||
if piece.King { | ||
val = strings.ToUpper(val) | ||
} | ||
buf.WriteString(val) | ||
} else { | ||
buf.WriteString(PieceStrings[NO_PLAYER]) | ||
} | ||
} | ||
if y < (BOARD_DIM - 1) { | ||
buf.WriteString(ROW_SEP) | ||
} | ||
} | ||
return buf.String() | ||
} | ||
|
||
func ParsePiece(s string) (Piece, bool) { | ||
piece, ok := StringPieces[s] | ||
return piece, ok | ||
} | ||
|
||
func Parse(s string) (*Game, error) { | ||
if len(s) != BOARD_DIM*BOARD_DIM+(BOARD_DIM-1) { | ||
return nil, errors.New(fmt.Sprintf("invalid board string: %v", s)) | ||
} | ||
pieces := make(map[Pos]Piece) | ||
result := &Game{pieces, BLACK_PLAYER} | ||
for y, row := range strings.Split(s, ROW_SEP) { | ||
for x, c := range strings.Split(row, "") { | ||
if x >= BOARD_DIM || y >= BOARD_DIM { | ||
return nil, errors.New(fmt.Sprintf("invalid board, piece out of bounds: %v, %v", x, y)) | ||
} | ||
if piece, ok := ParsePiece(c); !ok { | ||
return nil, errors.New(fmt.Sprintf("invalid board, invalid piece at %v, %v", x, y)) | ||
} else if piece != NO_PIECE { | ||
result.Pieces[Pos{x, y}] = piece | ||
} | ||
} | ||
} | ||
return result, nil | ||
} |