ref: 470f19bc73180395683aa1e14c96ea3c2d3a828f
dir: /game.go/
package main
import (
"fmt"
"log"
"sync"
"time"
"github.com/notnil/chess"
)
type GameState string
type Player int
const (
NewState GameState = "new"
Started = "ongoing"
Draw = "draw"
WhiteWon = "white won"
BlackWon = "black won"
WhiteDrawed = "white offered draw"
BlackDrawed = "black offered draw"
)
const (
White Player = iota
Black
)
// constants
const (
DefaultMaxGames = 8
MaxMaxGames = 4096
)
// A Game encapsulates the game state, settings, and channels.
type Game struct {
sync.RWMutex
id int
board *chess.Game
state GameState
whitePlayer string
whiteTime int64
whiteDrawed bool
blackPlayer string
blackTime int64
blackDrawed bool
lastTick time.Time
}
func NewGame(id int) *Game {
return &Game {
RWMutex: sync.RWMutex{},
id: id,
board: chess.NewGame(),
state: NewState,
whitePlayer: "",
whiteTime: -1,
blackPlayer: "",
blackTime: -1,
}
}
func (g *Game) SetPlayerTime(s int64) error {
g.Lock()
defer g.Unlock()
if g.state != NewState {
return fmt.Errorf("too late, %s", g.state)
}
g.whiteTime = s
g.blackTime = s
return nil
}
func (g *Game) TickPlayers() (int64, int64) {
g.Lock()
defer g.Unlock()
if g.state != Started {
return g.whiteTime, g.blackTime
}
tick := time.Now()
if g.whiteTime == 0 || g.blackTime == 0 {
return g.whiteTime, g.blackTime
}
d := int64(tick.Sub(g.lastTick).Seconds())
switch g.board.Position().Turn() {
case chess.White:
if g.whiteTime == -1 {
break;
}
if g.whiteTime - d > 0 {
g.whiteTime = g.whiteTime - d
} else {
g.whiteTime = 0
g.state = BlackWon
}
case chess.Black:
if g.blackTime == -1 {
break;
}
if g.blackTime - d > 0 {
g.blackTime = g.blackTime - d
} else {
g.blackTime = 0
g.state = WhiteWon
}
}
g.lastTick = tick
return g.whiteTime, g.blackTime
}
func (g *Game) StartGame() error {
g.Lock()
defer g.Unlock()
if g.state != NewState {
return fmt.Errorf("game is either ongoing or ended")
}
g.state = Started
g.lastTick = time.Now()
return nil
}
func (g *Game) GetState() GameState {
g.RLock()
defer g.RUnlock()
if g.state != Draw && g.whiteDrawed {
return WhiteDrawed
}
if g.state != Draw && g.blackDrawed {
return BlackDrawed
}
return g.state
}
func (g *Game) GetTurn() Player {
g.RLock()
defer g.RUnlock()
switch g.board.Position().Turn() {
case chess.White:
return White
case chess.Black:
return Black
}
return White
}
func (g *Game) DrawBoard(player Player) (string, error) {
g.RLock()
defer g.RUnlock()
switch player {
case White:
return g.board.Position().Board().Draw(), nil
case Black:
d := g.board.Position().Board().Flip(chess.UpDown).Flip(chess.LeftRight).Draw()
// quick fix for coordinates
var db = []byte(d)
for i, b := range(db) {
switch b {
case 'A': db[i] = 'H'
case 'B': db[i] = 'G'
case 'C': db[i] = 'F'
case 'D': db[i] = 'E'
case 'E': db[i] = 'D'
case 'F': db[i] = 'C'
case 'G': db[i] = 'B'
case 'H': db[i] = 'A'
case '1': db[i] = '8'
case '2': db[i] = '7'
case '3': db[i] = '6'
case '4': db[i] = '5'
case '5': db[i] = '4'
case '6': db[i] = '3'
case '7': db[i] = '2'
case '8': db[i] = '1'
}
}
return string(db), nil
}
return "", fmt.Errorf("unable to draw board")
}
func (g *Game) Move(move string) error {
g.TickPlayers()
g.Lock()
defer g.Unlock()
switch g.state {
case NewState:
return fmt.Errorf("game hasn't started")
case Draw, WhiteWon, BlackWon:
return fmt.Errorf("game ended, %s", g.state)
}
g.whiteDrawed = false
g.blackDrawed = false
err := g.board.MoveStr(move)
if err != nil {
return err
}
outcome := g.board.Outcome()
switch outcome {
case chess.WhiteWon:
g.state = WhiteWon
case chess.BlackWon:
g.state = BlackWon
case chess.Draw:
g.state = Draw
}
return nil
}
func (g *Game) OfferDraw(player Player) error {
g.TickPlayers()
g.Lock()
defer g.Unlock()
switch g.state {
case NewState:
return fmt.Errorf("game hasn't started")
case Draw, WhiteWon, BlackWon:
return fmt.Errorf("game ended: %s", g.state)
}
switch player {
case White:
g.whiteDrawed = true
case Black:
g.blackDrawed = true
}
if g.whiteDrawed && g.blackDrawed {
g.state = Draw
}
return nil
}
func (g *Game) Resign(player Player) error {
g.TickPlayers()
g.Lock()
defer g.Unlock()
switch g.state {
case NewState:
return fmt.Errorf("game hasn't started")
case Draw, WhiteWon, BlackWon:
return fmt.Errorf("game ended, %s", g.state)
}
switch player {
case White:
g.state = BlackWon
g.board.Resign(chess.White)
break
case Black:
g.state = WhiteWon
g.board.Resign(chess.Black)
break
}
return nil
}
func (g *Game) GetPGN() string {
g.RLock()
defer g.RUnlock()
return g.board.String() + "\n"
}
func (g *Game) GetFEN() string {
g.RLock()
defer g.RUnlock()
return g.board.FEN() + "\n"
}
func (g *Game) LoadFEN(fens string) error {
g.RLock()
if g.state != NewState {
return fmt.Errorf("game already started")
}
g.RUnlock()
g.Lock()
defer g.Unlock()
fen, err := chess.FEN(fens)
if err != nil {
return err
}
g.board = chess.NewGame(fen)
return nil
}
// The GameRoom is responsible for games, and the communication
// between the actual filesystem and them.
type GameRoom struct {
sync.RWMutex
v bool
maxGames int
games []*Game
}
func NewGameRoom(v bool) *GameRoom {
return &GameRoom{
RWMutex: sync.RWMutex{},
v: v,
maxGames: DefaultMaxGames,
games: make([]*Game, DefaultMaxGames),
}
}
func (r *GameRoom) GetMaxGames() int {
r.RLock()
defer r.RUnlock()
return r.maxGames
}
func (r *GameRoom) SetMaxGames(n int) error {
r.Lock()
defer r.Unlock()
if n > MaxMaxGames {
return fmt.Errorf("cannot have more than %d games", MaxMaxGames)
}
r.maxGames = n
return nil
}
func (r *GameRoom) CountGames() int {
var c int = 0
r.RLock()
defer r.RUnlock()
for _, g := range r.games {
if g != nil {
c = c + 1
}
}
return c
}
func (r *GameRoom) NewGame() (*Game, error) {
var id = -1
var games []*Game
c := r.CountGames()
r.Lock()
defer r.Unlock()
if c >= r.maxGames {
return nil, fmt.Errorf("room full, maximum %d games", r.maxGames)
}
for i, g := range r.games {
if g == nil {
id = i
break
}
}
if id == -1 {
id = len(r.games) + 1
games = make([]*Game, len(r.games)*2)
for i := range r.games {
games[i] = r.games[i]
}
r.games = games
}
game := NewGame(id)
r.games[id] = game
if r.v {
log.Printf("created game %d\n", id)
}
return game, nil
}
func (r *GameRoom) CloseGame(id int) error {
r.Lock()
defer r.Unlock()
if r.games[id] == nil {
return fmt.Errorf("game does not exist")
}
r.games[id] = nil
if r.v {
log.Printf("closed game %d\n", id)
}
return nil
}