shithub: chessfs

ref: eb8f0cbd6b03c83d0ec53c73ce721dae347217de
dir: /chessfs.go/

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