ref: 03a1f26a9e4fdd5c7ccaeae9c82e0af4b074db11
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" } // 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 }