ref: c975372b175fdf14e5c1158c705303fc73c116c7
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 }