Initial commit
This commit is contained in:
commit
4ba0b3682f
|
@ -0,0 +1 @@
|
|||
*.sgf
|
|
@ -0,0 +1,428 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type property struct {
|
||||
kind string
|
||||
value string
|
||||
}
|
||||
|
||||
type node []property
|
||||
|
||||
type game struct {
|
||||
bPlayer string
|
||||
bRank string
|
||||
bTeam string
|
||||
commentor string
|
||||
copyright string
|
||||
date string
|
||||
event string
|
||||
eventRnd string
|
||||
gameName string
|
||||
genCom string
|
||||
handicap string
|
||||
komi string
|
||||
place string
|
||||
result string
|
||||
rules string
|
||||
size int
|
||||
source string
|
||||
time string
|
||||
wPlayer string
|
||||
wRank string
|
||||
wTeam string
|
||||
board [][]string
|
||||
}
|
||||
|
||||
var black string = " X"
|
||||
var white string = " O"
|
||||
|
||||
func (g *game) overview(result bool) string {
|
||||
var out strings.Builder
|
||||
out.WriteString("\n\n")
|
||||
if g.gameName != "" {
|
||||
out.WriteString(" Game: ")
|
||||
out.WriteString(g.gameName)
|
||||
out.WriteString("\n")
|
||||
}
|
||||
if g.event != "" {
|
||||
out.WriteString(" Event: ")
|
||||
out.WriteString(g.event)
|
||||
out.WriteString("\n")
|
||||
}
|
||||
if g.eventRnd != "" {
|
||||
out.WriteString(" Round: ")
|
||||
out.WriteString(g.eventRnd)
|
||||
out.WriteString("\n")
|
||||
}
|
||||
if g.date != "" {
|
||||
out.WriteString(" Date: ")
|
||||
out.WriteString(g.date)
|
||||
out.WriteString("\n")
|
||||
}
|
||||
if g.place != "" {
|
||||
out.WriteString(" Place: ")
|
||||
out.WriteString(g.place)
|
||||
out.WriteString("\n")
|
||||
}
|
||||
if g.commentor != "" {
|
||||
out.WriteString(" Commentor: ")
|
||||
out.WriteString(g.commentor)
|
||||
out.WriteString("\n")
|
||||
}
|
||||
if g.source != "" {
|
||||
out.WriteString(" Kifu Source: ")
|
||||
out.WriteString(g.source)
|
||||
out.WriteString("\n")
|
||||
}
|
||||
if g.copyright != "" {
|
||||
out.WriteString(" Copyright: ")
|
||||
out.WriteString(g.copyright)
|
||||
out.WriteString("\n")
|
||||
}
|
||||
|
||||
out.WriteString("\n")
|
||||
out.WriteString(" Black ( X ): ")
|
||||
out.WriteString(g.bPlayer)
|
||||
out.WriteString("\n")
|
||||
out.WriteString(" White ( O ): ")
|
||||
out.WriteString(g.wPlayer)
|
||||
out.WriteString("\n")
|
||||
if g.komi != "" {
|
||||
out.WriteString(" Komi: ")
|
||||
out.WriteString(g.komi)
|
||||
out.WriteString("\n")
|
||||
}
|
||||
if g.time != "" {
|
||||
out.WriteString(" Time: ")
|
||||
out.WriteString(g.time)
|
||||
out.WriteString("\n")
|
||||
}
|
||||
if g.rules != "" {
|
||||
out.WriteString(" Rules: ")
|
||||
out.WriteString(g.rules)
|
||||
out.WriteString("\n")
|
||||
}
|
||||
if result {
|
||||
out.WriteString("\n")
|
||||
out.WriteString(" Result: ")
|
||||
out.WriteString(g.result)
|
||||
out.WriteString("\n")
|
||||
}
|
||||
out.WriteString("\n")
|
||||
|
||||
return out.String()
|
||||
}
|
||||
|
||||
func (g *game) writeboard() string {
|
||||
var out strings.Builder
|
||||
out.WriteString(" ")
|
||||
for i, _ := range g.board {
|
||||
out.WriteString(" ")
|
||||
out.WriteRune(rune(i + 65))
|
||||
}
|
||||
out.WriteString("\n")
|
||||
|
||||
for i, row := range g.board {
|
||||
out.WriteString(fmt.Sprintf("%2d", i + 1))
|
||||
for _, col := range row {
|
||||
out.WriteString(col)
|
||||
}
|
||||
out.WriteString("\n")
|
||||
}
|
||||
return out.String()
|
||||
}
|
||||
|
||||
|
||||
// Parse the SGF into a slice of nodes
|
||||
|
||||
func generateAST(path string) []node {
|
||||
file, err := os.Open(path)
|
||||
defer file.Close()
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
bytes, err := ioutil.ReadAll(file)
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
ast, err := parseInputFile(string(bytes))
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
return ast
|
||||
}
|
||||
|
||||
func parseInputFile(sgf string) ([]node, error) {
|
||||
seq := make([]node, 0, 10)
|
||||
current := 0
|
||||
|
||||
// get to start of game
|
||||
for sgf[current] != '(' {
|
||||
current++
|
||||
}
|
||||
current++
|
||||
|
||||
for ;; current++ {
|
||||
if current >= len(sgf) {
|
||||
return seq, fmt.Errorf("Invalid kifu: no end to game tree before EOF")
|
||||
} else if sgf[current] == '(' {
|
||||
err := eatSubTree(&sgf, ¤t)
|
||||
if err != nil {
|
||||
return seq, err
|
||||
}
|
||||
} else if sgf[current] == ')' {
|
||||
break
|
||||
} else if sgf[current] == ';' {
|
||||
current++
|
||||
n, err := parseNode(&sgf, ¤t)
|
||||
if err == nil {
|
||||
seq = append(seq, n)
|
||||
}
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
return seq, nil
|
||||
}
|
||||
|
||||
func validateKind(k string) bool {
|
||||
switch k {
|
||||
case "B", "C", "W", "AB", "AN", "AW",
|
||||
"BR", "BT", "CA", "CP", "DT", "EV",
|
||||
"GC", "GM", "GN", "HA", "KM",
|
||||
"PB", "PL", "PW", "RE", "RO", "RU",
|
||||
"SO", "SZ", "TM", "WR", "WT":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func parseProperty(sgf *string, cur *int) (property, error) {
|
||||
var prop = property{}
|
||||
var kindBuild strings.Builder
|
||||
var valBuild strings.Builder
|
||||
for {
|
||||
if (*sgf)[*cur] >= 'A' && (*sgf)[*cur] <= 'Z' {
|
||||
kindBuild.WriteByte((*sgf)[*cur])
|
||||
} else if (*sgf)[*cur] == '[' {
|
||||
break
|
||||
}
|
||||
*cur++
|
||||
}
|
||||
|
||||
k := kindBuild.String()
|
||||
if !validateKind(k) {
|
||||
for (*sgf)[*cur] != ']' {
|
||||
*cur++
|
||||
continue
|
||||
}
|
||||
return property{}, fmt.Errorf("Ignore prop")
|
||||
}
|
||||
prop.kind = k
|
||||
|
||||
if (*sgf)[*cur] == '[' {
|
||||
*cur++
|
||||
} else {
|
||||
for (*sgf)[*cur] != '[' {
|
||||
*cur++
|
||||
continue
|
||||
}
|
||||
}
|
||||
for (*sgf)[*cur] != ']' {
|
||||
valBuild.WriteByte((*sgf)[*cur])
|
||||
*cur++
|
||||
}
|
||||
prop.value = valBuild.String()
|
||||
*cur++
|
||||
|
||||
return prop, nil
|
||||
}
|
||||
|
||||
func parseNode(sgf *string, cur *int) (node, error) {
|
||||
// TODO finish node parsing
|
||||
n := make(node, 0, 1)
|
||||
for (*sgf)[*cur] != ';' {
|
||||
if (*sgf)[*cur] == ')' {
|
||||
break
|
||||
}
|
||||
prop, err := parseProperty(sgf, cur)
|
||||
if err == nil {
|
||||
n = append(n, prop)
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if len(n) == 0 {
|
||||
return n, fmt.Errorf("Bad node. Do not add.")
|
||||
}
|
||||
*cur--
|
||||
return n, nil
|
||||
}
|
||||
|
||||
|
||||
func eatSubTree(sgf *string, cur *int) error {
|
||||
for (*sgf)[*cur] != ')' {
|
||||
*cur++
|
||||
if *cur >= len(*sgf) {
|
||||
return fmt.Errorf("Invalid kifu: no end to sub tree befor EOF")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Generate a game struct
|
||||
|
||||
func makeGame() game {
|
||||
return game{
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
19,
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
[][]string{},
|
||||
}
|
||||
}
|
||||
|
||||
func gameStruct(a []node) game {
|
||||
if len(a) < 1 {
|
||||
fmt.Println("Invalid kifu: No game metadata found")
|
||||
os.Exit(1)
|
||||
}
|
||||
game := makeGame()
|
||||
adds := make([]property,0,1)
|
||||
for _, prop := range a[0] {
|
||||
switch prop.kind {
|
||||
case "SZ":
|
||||
sz, err := strconv.Atoi(prop.value)
|
||||
if err != nil {
|
||||
fmt.Println("Invalid board size supplied")
|
||||
os.Exit(1)
|
||||
}
|
||||
game.size = sz
|
||||
game.board = make([][]string, sz, sz)
|
||||
for ri, _ := range game.board {
|
||||
cols := make([]string, sz, sz)
|
||||
for ci, _ := range cols {
|
||||
cols[ci] = " ."
|
||||
}
|
||||
game.board[ri] = cols
|
||||
}
|
||||
case "GC":
|
||||
game.genCom = prop.value
|
||||
case "HA":
|
||||
game.handicap = prop.value
|
||||
case "KM":
|
||||
game.komi = prop.value
|
||||
case "TM":
|
||||
game.time = prop.value
|
||||
case "BR":
|
||||
game.bRank = prop.value
|
||||
case "BT":
|
||||
game.bTeam = prop.value
|
||||
case "WR":
|
||||
game.wRank = prop.value
|
||||
case "WT":
|
||||
game.wTeam = prop.value
|
||||
case "CP":
|
||||
game.copyright = prop.value
|
||||
case "PL":
|
||||
game.place = prop.value
|
||||
case "PB":
|
||||
game.bPlayer = prop.value
|
||||
case "PW":
|
||||
game.wPlayer = prop.value
|
||||
case "RE":
|
||||
game.result = prop.value
|
||||
case "RO":
|
||||
game.eventRnd = prop.value
|
||||
case "RU":
|
||||
game.rules = prop.value
|
||||
case "SO":
|
||||
game.source = prop.value
|
||||
case "EV":
|
||||
game.event = prop.value
|
||||
case "DT":
|
||||
game.date = prop.value
|
||||
case "GN":
|
||||
game.gameName = prop.value
|
||||
case "GM":
|
||||
if prop.value != "1" {
|
||||
fmt.Println("This is not a Go kifu, it is for a different game")
|
||||
os.Exit(1)
|
||||
}
|
||||
case "AN":
|
||||
game.commentor = prop.value
|
||||
case "AB", "AW":
|
||||
adds = append(adds, prop)
|
||||
}
|
||||
}
|
||||
for _, p := range adds {
|
||||
var piece string
|
||||
if p.kind == "AB" {
|
||||
piece = black
|
||||
} else {
|
||||
piece = white
|
||||
}
|
||||
r, c := getCoords(p.value, game.size)
|
||||
game.board[r][c] = piece
|
||||
}
|
||||
return game
|
||||
}
|
||||
|
||||
func getCoords(co string, boardSize int) (int, int) {
|
||||
if len([]rune(co)) != 2 {
|
||||
fmt.Printf("Invalid ADD or MOVE property: %s\n", co)
|
||||
os.Exit(1)
|
||||
}
|
||||
var row, col int
|
||||
co = strings.ToUpper(co)
|
||||
col = int(co[0]) - 65
|
||||
row = int(co[1]) - 65
|
||||
if col < 0 || col > boardSize - 1 || row < 0 || row > boardSize - 1 {
|
||||
fmt.Println("Invalid move coordinates found")
|
||||
os.Exit(1)
|
||||
}
|
||||
return row, col
|
||||
}
|
||||
|
||||
|
||||
func main() {
|
||||
if len(os.Args) != 2 {
|
||||
fmt.Println("Must provide path to input sgf")
|
||||
os.Exit(1)
|
||||
}
|
||||
ast := generateAST(os.Args[1])
|
||||
game := gameStruct(ast)
|
||||
overview := game.overview(false)
|
||||
fmt.Print(overview)
|
||||
fmt.Print(game.writeboard())
|
||||
}
|
Binary file not shown.
Loading…
Reference in New Issue