Initial commit

This commit is contained in:
sloumdrone 2019-11-05 06:36:17 -08:00
commit 4ba0b3682f
3 changed files with 429 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
*.sgf

428
main.go Normal file
View File

@ -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, &current)
if err != nil {
return seq, err
}
} else if sgf[current] == ')' {
break
} else if sgf[current] == ';' {
current++
n, err := parseNode(&sgf, &current)
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())
}

BIN
sgf2gemini Executable file

Binary file not shown.