shithub: chessfs

ref: e82dcee1a3d54f14c93a19f8fe21965ea72c56ce
dir: /files.go/

View raw version
package main

import (
	"os"
	"io"
	"time"
	"fmt"
	"strings"
	"strconv"
)

// Generic file interface

// Chess root directory
type ChessRootDir struct {
	board	*Board
	files	chan os.FileInfo
	uid		string
	gid		string
}

func NewChessRootDir(board *Board, uid, gid string) *ChessRootDir {
	files := make(chan os.FileInfo, 8)
	allFiles := []os.FileInfo {
		&ChessControlFile {
			board:	board,
			uid:	uid,
			gid:	gid,
		},
		&BoardFile {
			board:	board,
			uid:	uid,
			gid:	gid,
		},
		&ChessStatusFile {
			board:	board,
			uid:	uid,
			gid:	gid,
		},
		&ChessNotifyFile {
			board:	board,
			uid:	uid,
			gid:	gid,
		},
		&GameHistory {
			board:	board,
			uid:	uid,
			gid:	gid,
		},
	}

	for _, file := range allFiles {
		files <- file
	}

	return &ChessRootDir {
		board: board,
		files: files,
	}
}
func (d ChessRootDir) Name() string {
	return "/"
}

func (d ChessRootDir) Size() int64 {
	return 0
}

func (d ChessRootDir) Mode() os.FileMode {
	return 0755
}

func (d ChessRootDir) ModTime() time.Time {
	return time.Unix(0, 0)
}

func (d ChessRootDir) IsDir() bool {
	return true
}

func (d ChessRootDir) Sys() interface{} {
	return nil
}

func (d ChessRootDir) Uid() string {
	return d.uid
}

func (d ChessRootDir) Gid() string {
	return d.gid
}

func (d ChessRootDir) Muid() string {
	return d.uid
}

func (d ChessRootDir) Readdir(n int) ([]os.FileInfo, error) {
	var err error
	files := make([]os.FileInfo, 0, 8)

	ReadLoop:
	for i := 0; i < n; i++ {
		select {
		case file := <-d.files:
			files = append(files, file)
		default:
			err = io.EOF
			break ReadLoop
		}
	}

	return files, err
}

func (d ChessRootDir) Close() error {
	return nil
}

// Control file
type ChessControlFile struct {
	board	*Board
	uid		string
	gid		string
}

func (f ChessControlFile) Name() string {
	return "ctl"
}

func (f ChessControlFile) Size() int64 {
	return 0
}

func (f ChessControlFile) Mode() os.FileMode {
	return 0664
}

func (f ChessControlFile) ModTime() time.Time {
	return time.Unix(0, 0)
}

func (f ChessControlFile) IsDir() bool {
	return false
}

func (f ChessControlFile) Sys() interface{} {
	return nil
}

func (f ChessControlFile) Close() error {
	return nil
}

func (f ChessControlFile) Uid() string {
	return f.uid
}

func (f ChessControlFile) Gid() string {
	return f.gid
}

func (f ChessControlFile) Muid() string {
	return f.uid
}

func (f ChessControlFile) ReadAt(p []byte, off int64) (int, error) {
	return 0, fmt.Errorf("not supported")
}

func (f ChessControlFile) WriteAt(p []byte, off int64) (int, error) {
	if off > 0 {
		return 0, fmt.Errorf("not supported")
	}

	msgRaw := strings.Split(strings.Trim(string(p), " \n\r\t"), " ")
	var msg []string
	for _, part := range msgRaw {
		if part != "" {
			msg = append(msg, part)
		}
	}

	if len(msg) == 0 {
		return 0, fmt.Errorf("unknown command; supported: new, move, load")
	}

	switch (msg[0]) {
	case "new":
		if len(msg) != 1 {
			return 0, fmt.Errorf("wrong arguments; usage: new")
		}
		f.board.Reset()
	case "move":
		if len(msg) != 2 {
			return 0, fmt.Errorf("wrong arguments: usage: move X")
		}

		moveRaw := msg[1]
		move, err := NewMove(moveRaw, f.board)
		if err != nil {
			return 0, err
		}

		err = f.board.MovePiece(move)
		if err != nil {
			return 0, err
		}
	case "load":
		pgn := msg[1:]
		var moves []*Move

		var currentMove = 0
		var relPos = 0

		PgnRead:
		for i := 0; i < len(pgn); i++ {
			if relPos == 0 {
				if pgn[i][len(pgn[i]) - 1] != '.' {
					return 0, fmt.Errorf("unexpected character in element: %s", pgn[i])
				}

				newMoveStr := pgn[i][0:(len(pgn[i]) - 1)]
				newMove, err := strconv.Atoi(newMoveStr)
				if err != nil {
					return 0, err
				}

				if currentMove + 1 != newMove {
					return 0, fmt.Errorf("incorrect move order")
				}

				currentMove = currentMove + 1
				relPos = relPos + 1
				continue PgnRead
			}

			moveStr := pgn[i]
			move, err := NewMove(moveStr, f.board)
			if err != nil {
				return 0, err
			}

			moves = append(moves, move)
			relPos = (relPos + 1) % 3
		}

		f.board.Reset()
		for i := 0; i < len(moves); i++ {
			f.board.MovePiece(moves[i])
		}
	default:
		return 0, fmt.Errorf("unknown command; supported: new, move, load")
	}
	
	return len(p), nil
}

// Board file
type BoardFile struct {
	board	*Board
	uid		string
	gid		string
}

func (f BoardFile) Name() string {
	return "board"
}

func (f BoardFile) Size() int64 {
	return 0
}

func (f BoardFile) Mode() os.FileMode {
	return 0644
}

func (f BoardFile) ModTime() time.Time {
	return time.Unix(0, 0)
}

func (f BoardFile) IsDir() bool {
	return false
}

func (f BoardFile) Sys() interface{} {
	return nil
}

func (f BoardFile) Close() error {
	return nil
}

func (f BoardFile) Uid() string {
	return f.uid
}

func (f BoardFile) Gid() string {
	return f.gid
}

func (f BoardFile) Muid() string {
	return f.uid
}

func (f BoardFile) ReadAt(p []byte, off int64) (int, error) {
	var boardLines [19]string

	// board drawing
	for i := 0; i < 19; i++ {
		switch (i % 2) {
		case 0:
			for j := 0; j < 19; j++ {
				switch(j % 2) {
				case 0:
					boardLines[i] = fmt.Sprintf("%s ", boardLines[i])
				case 1:
					boardLines[i] = fmt.Sprintf("%s|", boardLines[i])
				}
			}
		case 1:
			for j := 0; j < 19; j++ {
				boardLines[i] = fmt.Sprintf("%s-", boardLines[i])
			}
		}
	}

	// board marks
	for i := 0; i < 8; i++ {
		line := []rune(boardLines[(i + 1) * 2])
		line[0] = rune(49 + (7 - i))
		boardLines[(i + 1) * 2] = string(line)
	}

	for i := 0; i < 8; i++ {
		line := []rune(boardLines[18])
		line[(i + 1) * 2] = rune(97 + i)
		boardLines[18] = string(line)
	}

	// board pieces
	for i := 0; i < 8; i++ {
		for j := 0; j < 8; j++ {
			piece := f.board.Board[i][j]
			if piece != nil {
				line := []rune(boardLines[(i + 1) * 2])
				line[(j + 1) * 2] = piece.ToPretty()
				boardLines[(i + 1) * 2] = string(line)
			}
		}
	}

	// final board output
	var content string
	for i := 0; i < 19; i++ {
		content = fmt.Sprintf("%s\n%s", content, boardLines[i])
	}
	content = fmt.Sprintf("%s\n", content)

	if off > int64(len(content)) {
		return 0, io.EOF
	}

	readContent := content[off:]
	n := copy(p, readContent)
	return n, nil
}

func (f BoardFile) WriteAt(p []byte, off int64) (int, error) {
	return 0, fmt.Errorf("not supported")
}

// Status file
type ChessStatusFile struct {
	board	*Board
	uid		string
	gid		string
}

func (f ChessStatusFile) Name() string {
	return "status"
}

func (f ChessStatusFile) Size() int64 {
	return 0
}

func (f ChessStatusFile) Mode() os.FileMode {
	return 0644
}

func (f ChessStatusFile) ModTime() time.Time {
	return time.Unix(0, 0)
}

func (f ChessStatusFile) IsDir() bool {
	return false
}

func (f ChessStatusFile) Sys() interface{} {
	return nil
}

func (f ChessStatusFile) Close() error {
	return nil
}

func (f ChessStatusFile) Uid() string {
	return f.uid
}

func (f ChessStatusFile) Gid() string {
	return f.gid
}

func (f ChessStatusFile) Muid() string {
	return f.uid
}

func (f ChessStatusFile) ReadAt(p []byte, off int64) (int, error) {
	f.board.UpdateTime()
	secondsWhite := f.board.SecondsWhite
	secondsBlack := f.board.SecondsBlack
	playerTime := f.board.PlayerTime
	var turn string

	switch (f.board.Turn) {
	case White:
		turn = "White"
	case Black:
		turn = "Black"
	}

	var status string
	status = fmt.Sprintf("White: %d\n", secondsWhite)
	status = fmt.Sprintf("%sBlack: %d\n", status, secondsBlack)
	status = fmt.Sprintf("%sTurn: %s\n", status, turn)
	status = fmt.Sprintf("%sPlayer time: %d\n", status, playerTime)

	if off > int64(len(status)) {
		return 0, io.EOF
	}

	content := status[off:]
	n := copy(p, content)
	return n, nil
}

func (f ChessStatusFile) WriteAt(p []byte, off int64) (int, error) {
	return 0, fmt.Errorf("not supported")
}

// Notify file
type ChessNotifyFile struct {
	board	*Board
	uid		string
	gid		string
}

func (f ChessNotifyFile) Name() string {
	return "notify"
}

func (f ChessNotifyFile) Size() int64 {
	return 0
}

func (f ChessNotifyFile) Mode() os.FileMode {
	return 0644
}

func (f ChessNotifyFile) ModTime() time.Time {
	return time.Unix(0, 0)
}

func (f ChessNotifyFile) IsDir() bool {
	return false
}

func (f ChessNotifyFile) Sys() interface{} {
	return nil
}

func (f ChessNotifyFile) Close() error {
	return nil
}

func (f ChessNotifyFile) Uid() string {
	return f.uid
}

func (f ChessNotifyFile) Gid() string {
	return f.gid
}

func (f ChessNotifyFile) Muid() string {
	return f.uid
}

func (f ChessNotifyFile) ReadAt(p []byte, off int64) (int, error) {
	turn := f.board.Turn

	if off > 0 {
		return 0, io.EOF
	}

	for turn == f.board.Turn {
		d, _ := time.ParseDuration("100ms")
		time.Sleep(d)
	}

	var turnRep string
	switch (f.board.Turn) {
	case White:
		turnRep = "white\n"
	case Black:
		turnRep = "black\n"
	}

	n := copy(p, turnRep)

	return n, nil
}

func (f ChessNotifyFile) WriteAt(p []byte, off int64) (int, error) {
	return 0, fmt.Errorf("not supported")
}

// History file
type GameHistory struct {
	board	*Board
	uid		string
	gid		string
}

func (f GameHistory) Name() string {
	return "history"
}

func (f GameHistory) Size() int64 {
	return 0
}

func (f GameHistory) Mode() os.FileMode {
	return 0644
}

func (f GameHistory) ModTime() time.Time {
	return time.Unix(0, 0)
}

func (f GameHistory) IsDir() bool {
	return false
}

func (f GameHistory) Sys() interface{} {
	return nil
}

func (f GameHistory) Close() error {
	return nil
}

func (f GameHistory) Uid() string {
	return f.uid
}

func (f GameHistory) Gid() string {
	return f.gid
}

func (f GameHistory) Muid() string {
	return f.uid
}

func (f GameHistory) ReadAt(p []byte, off int64) (int, error) {
	var historyContent = ""

	for i := 0; i < len(f.board.History); i = i + 2 {
		historyContent = fmt.Sprintf("%s %d. %s",
			historyContent,
			(i / 2) + 1,
			f.board.History[i])
		if i + 1 < len(f.board.History) {
			historyContent = fmt.Sprintf("%s %s",
					historyContent,
					f.board.History[i + 1])
		}
	}

	if off > 0 {
		return 0, io.EOF
	}

	historyContent = fmt.Sprintf("%s\n", historyContent)
	n := copy(p, historyContent)

	return n, nil
}

func (f GameHistory) WriteAt(p []byte, off int64) (int, error) {
	return 0, fmt.Errorf("not supported")
}