ref: 470f19bc73180395683aa1e14c96ea3c2d3a828f
dir: /chessfs.go/
package main
import (
"fmt"
"strconv"
"strings"
"sync"
"github.com/knusbaum/go9p/fs"
"github.com/knusbaum/go9p/proto"
)
// CtlFile is the file setting and reading options
// from the game server.
type CtlFile struct {
fs.WrappedFile
room *GameRoom
}
func NewCtlFile(room *GameRoom, s *proto.Stat) *CtlFile {
return &CtlFile{
WrappedFile: fs.WrappedFile{
File: fs.NewBaseFile(s),
},
room: room,
}
}
func (f *CtlFile) Read(_ uint64, offset uint64, count uint64) ([]byte, error) {
roomSize := f.room.GetMaxGames()
gameCount := f.room.CountGames()
var content = ""
content = fmt.Sprintf("roomsize %d\n", roomSize)
content = fmt.Sprintf("%sgames %d\n", content, gameCount)
data := []byte(content)
flen := uint64(len(data))
if offset >= flen {
return []byte{}, nil
}
if offset+count > flen {
count = flen - offset
}
return data[offset : offset+count], nil
}
func (f *CtlFile) Write(_ uint64, offset uint64, data []byte) (uint32, error) {
if offset != 0 {
return 0, fmt.Errorf("cannot append to ctl")
}
n := uint32(len(data))
args := strings.Fields(string(data))
switch args[0] {
case "roomsize":
if len(args) != 2 {
return 0, fmt.Errorf("bad command")
}
newsz, err := strconv.Atoi(args[1])
if err != nil {
return 0, err
}
f.room.SetMaxGames(newsz)
default:
return 0, fmt.Errorf("bad command")
}
return n, nil
}
// CloneFile creates a new game
type CloneFile struct {
fs.WrappedFile
fs *fs.FS
room *GameRoom
gameDir *fs.StaticDir
fidm map[uint64]int
fidl sync.RWMutex
}
func NewCloneFile(room *GameRoom, s *proto.Stat, chessfs *fs.FS, gameDir *fs.StaticDir) *CloneFile {
return &CloneFile{
WrappedFile: fs.WrappedFile{
File: fs.NewBaseFile(s),
},
fs: chessfs,
room: room,
gameDir: gameDir,
fidm: make(map[uint64]int),
fidl: sync.RWMutex{},
}
}
func (f *CloneFile) Open(fid uint64, omode proto.Mode) error {
err := f.WrappedFile.Open(fid, omode)
if err != nil {
return err
}
game, err := f.room.NewGame()
if err != nil {
return err
}
// no need to lock - the game is not accessible until the return
uname := f.Stat().Uid
gid := f.Stat().Gid
f.CreateGameDirectory(game, uname, gid)
f.fidl.Lock()
defer f.fidl.Unlock()
f.fidm[fid] = game.id
return nil
}
func (f *CloneFile) Read(fid uint64, offset uint64, count uint64) ([]byte, error) {
f.fidl.RLock()
id, ok := f.fidm[fid]
f.fidl.RUnlock()
if !ok {
return nil, fmt.Errorf("fid not found")
}
data := []byte(fmt.Sprintf("%d\n", id+1))
flen := uint64(len(data))
if offset >= flen {
return []byte{}, nil
}
if offset+count > flen {
count = flen - offset
}
return data[offset : offset+count], nil
}
func (f *CloneFile) CreateGameDirectory(game *Game, uname, gid string) {
statdir := f.fs.NewStat(fmt.Sprint(game.id+1), uname, gid, 0555)
statctl := f.fs.NewStat("ctl", uname, gid, 0644)
statfen := f.fs.NewStat("fen", uname, gid, 0644)
statmoves := f.fs.NewStat("moves", uname, gid, 0444)
statwhite := f.fs.NewStat("white", uname, gid, 0664)
statblack := f.fs.NewStat("black", uname, gid, 0664)
d := fs.NewStaticDir(statdir)
d.AddChild(NewGameCtlFile(statctl, f, game))
d.AddChild(NewFENFile(statfen, game))
d.AddChild(NewMoveFile(statmoves, game))
d.AddChild(NewBoardFile(statwhite, game, White))
d.AddChild(NewBoardFile(statblack, game, Black))
f.gameDir.AddChild(d)
}
func (f *CloneFile) RemoveGame(id int) error {
err := f.room.CloseGame(id)
if err != nil {
return err
}
err = f.gameDir.DeleteChild(fmt.Sprintf("%d", id + 1))
return err
}
// Game files and functions
type GameCtlFile struct {
fs.WrappedFile
id int
clone *CloneFile
game *Game
}
func NewGameCtlFile(s *proto.Stat, clone *CloneFile, game *Game) *GameCtlFile {
return &GameCtlFile {
WrappedFile: fs.WrappedFile{
File: fs.NewBaseFile(s),
},
id: game.id,
clone: clone,
game: game,
}
}
func (f *GameCtlFile) Read(_ uint64, offset uint64, count uint64) ([]byte, error) {
tw, tb := f.game.TickPlayers()
state := f.game.GetState()
turn := f.game.GetTurn()
var turnfmt = ""
switch(turn) {
case White:
turnfmt = "white's turn"
case Black:
turnfmt = "black's turn"
}
var content = ""
content = fmt.Sprintf("%s%s\n", content, state)
content = fmt.Sprintf("%s%s\n", content, turnfmt)
content = fmt.Sprintf("%swhite timer %d\n", content, tw)
content = fmt.Sprintf("%sblack timer %d\n", content, tb)
data := []byte(content)
flen := uint64(len(data))
if offset >= flen {
return []byte{}, nil
}
if offset+count > flen {
count = flen - offset
}
return data[offset : offset+count], nil
}
func (f *GameCtlFile) Write(_ uint64, offset uint64, data []byte) (uint32, error) {
if offset != 0 {
return 0, fmt.Errorf("cannot append to ctl")
}
n := uint32(len(data))
args := strings.Fields(string(data))
switch args[0] {
case "time":
if len(args) != 2 {
return 0, fmt.Errorf("bad command")
}
newamt, err := strconv.Atoi(args[1])
if err != nil {
return 0, err
}
err = f.game.SetPlayerTime(int64(newamt))
if err != nil {
return 0, err
}
case "start":
if len(args) != 1 {
return 0, fmt.Errorf("bad command")
}
err := f.game.StartGame()
if err != nil {
return 0, err
}
case "close":
if len(args) != 1 {
return 0, fmt.Errorf("bad command")
}
err := f.clone.RemoveGame(f.id)
if err != nil {
return 0, err
}
default:
return 0, fmt.Errorf("bad command")
}
return n, nil
}
// write stat
type BoardFile struct {
fs.WrappedFile
player Player
game *Game
}
func NewBoardFile(s *proto.Stat, game *Game, p Player) *BoardFile {
return &BoardFile {
WrappedFile: fs.WrappedFile{
File: fs.NewBaseFile(s),
},
player: p,
game: game,
}
}
func (f *BoardFile) Read(_ uint64, offset uint64, count uint64) ([]byte, error) {
content, err := f.game.DrawBoard(f.player)
if err != nil {
return nil, err
}
data := []byte(content)
flen := uint64(len(data))
if offset >= flen {
return []byte{}, nil
}
if offset+count > flen {
count = flen - offset
}
return data[offset : offset+count], nil
}
func (f *BoardFile) Write(_ uint64, offset uint64, data []byte) (uint32, error) {
if offset != 0 {
return 0, fmt.Errorf("cannot append to board file")
}
n := uint32(len(data))
cmdline := strings.Join(strings.Fields(string(data)), "")
switch cmdline {
case "draw":
err := f.game.OfferDraw(f.player)
if err != nil {
return 0, err
}
case "resign":
turn := f.game.GetTurn()
if turn != f.player {
return 0, fmt.Errorf("not your turn")
}
err := f.game.Resign(f.player)
if err != nil {
return 0, err
}
default:
turn := f.game.GetTurn()
if turn != f.player {
return 0, fmt.Errorf("not your turn")
}
err := f.game.Move(cmdline)
if err != nil {
return 0, err
}
}
return n, nil
}
type MoveFile struct {
fs.WrappedFile
game *Game
}
func NewMoveFile(s *proto.Stat, game *Game) *MoveFile {
return &MoveFile {
WrappedFile: fs.WrappedFile{
File: fs.NewBaseFile(s),
},
game: game,
}
}
func (f *MoveFile) Read(_ uint64, offset uint64, count uint64) ([]byte, error) {
data := []byte(f.game.GetPGN())
flen := uint64(len(data))
if offset >= flen {
return []byte{}, nil
}
if offset+count > flen {
count = flen - offset
}
return data[offset : offset+count], nil
}
// FEN file for gave saving/loading
type FENFile struct {
fs.WrappedFile
game *Game
}
func NewFENFile(s *proto.Stat, game *Game) *FENFile {
return &FENFile {
WrappedFile: fs.WrappedFile{
File: fs.NewBaseFile(s),
},
game: game,
}
}
func (f *FENFile) Read(_ uint64, offset uint64, count uint64) ([]byte, error) {
data := []byte(f.game.GetFEN())
flen := uint64(len(data))
if offset >= flen {
return []byte{}, nil
}
if offset+count > flen {
count = flen - offset
}
return data[offset : offset+count], nil
}
func (f *FENFile) Write(_ uint64, offset uint64, data []byte) (uint32, error) {
if offset != 0 {
return 0, fmt.Errorf("cannot append to board file")
}
n := uint32(len(data))
err := f.game.LoadFEN(string(data))
if err != nil {
return 0, err
}
return n, nil
}