shithub: chessfs

ref: c975372b175fdf14e5c1158c705303fc73c116c7
dir: /game.go/

View raw version
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
}