2019-11-05 14:36:17 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2019-11-06 06:36:36 +00:00
|
|
|
"bufio"
|
2019-11-07 05:37:47 +00:00
|
|
|
"flag"
|
2019-11-05 14:36:17 +00:00
|
|
|
"fmt"
|
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
2019-11-07 05:37:47 +00:00
|
|
|
"path/filepath"
|
2019-11-05 14:36:17 +00:00
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
)
|
|
|
|
|
|
|
|
type property struct {
|
|
|
|
kind string
|
|
|
|
value string
|
|
|
|
}
|
|
|
|
|
|
|
|
type node []property
|
|
|
|
|
|
|
|
type game struct {
|
|
|
|
bPlayer string
|
|
|
|
bRank string
|
|
|
|
bTeam string
|
2019-11-06 06:36:36 +00:00
|
|
|
comment string
|
2019-11-05 14:36:17 +00:00
|
|
|
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"
|
2019-11-06 06:36:36 +00:00
|
|
|
var empty string = " ."
|
|
|
|
var check string = "--"
|
|
|
|
|
2019-11-07 05:37:47 +00:00
|
|
|
var host string
|
|
|
|
var folder string
|
|
|
|
var port string
|
|
|
|
var out string
|
2019-11-05 14:36:17 +00:00
|
|
|
|
|
|
|
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")
|
|
|
|
}
|
|
|
|
|
2019-11-07 05:37:47 +00:00
|
|
|
out.WriteString("\n - - -\n\n")
|
|
|
|
|
|
|
|
if g.bPlayer != "" {
|
|
|
|
out.WriteString(" Black ( X ): ")
|
|
|
|
out.WriteString(g.bPlayer)
|
|
|
|
if g.bRank != "" {
|
|
|
|
out.WriteString(" - ")
|
|
|
|
out.WriteString(g.bRank)
|
|
|
|
}
|
|
|
|
out.WriteString("\n")
|
|
|
|
}
|
2019-11-05 14:36:17 +00:00
|
|
|
out.WriteString("\n")
|
2019-11-07 05:37:47 +00:00
|
|
|
|
|
|
|
if g.wPlayer != "" {
|
|
|
|
out.WriteString(" White ( O ): ")
|
|
|
|
out.WriteString(g.wPlayer)
|
|
|
|
if g.wRank != "" {
|
|
|
|
out.WriteString(" - ")
|
|
|
|
out.WriteString(g.wRank)
|
|
|
|
}
|
|
|
|
out.WriteString("\n")
|
|
|
|
}
|
2019-11-05 14:36:17 +00:00
|
|
|
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")
|
|
|
|
}
|
2019-11-06 06:36:36 +00:00
|
|
|
out.WriteString("\n")
|
2019-11-05 14:36:17 +00:00
|
|
|
return out.String()
|
|
|
|
}
|
|
|
|
|
2019-11-06 06:36:36 +00:00
|
|
|
func (g *game) handleNode(n node, num, max int) {
|
|
|
|
g.comment = ""
|
|
|
|
moved := false
|
|
|
|
for _, p := range n {
|
|
|
|
switch p.kind {
|
|
|
|
case "C":
|
|
|
|
g.comment = p.value
|
|
|
|
case "B", "W":
|
|
|
|
if moved {
|
|
|
|
panic("Encountered two moves in one node")
|
|
|
|
}
|
|
|
|
moved = true
|
|
|
|
if p.kind == "B" {
|
|
|
|
g.move(p.value, black)
|
|
|
|
} else {
|
|
|
|
g.move(p.value, white)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-11-07 05:37:47 +00:00
|
|
|
if !moved && num > 0 {
|
2019-11-06 06:36:36 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-11-07 05:37:47 +00:00
|
|
|
fileName := fmt.Sprintf("move%d.map", num)
|
|
|
|
if num == 0 {
|
|
|
|
fileName = "gophermap"
|
|
|
|
}
|
|
|
|
outputPath := filepath.Join(out, fileName)
|
|
|
|
f, err := os.Create(outputPath)
|
2019-11-06 06:36:36 +00:00
|
|
|
if err != nil {
|
|
|
|
panic("Couldnt create file")
|
|
|
|
}
|
|
|
|
defer f.Close()
|
|
|
|
w := bufio.NewWriter(f)
|
|
|
|
if num == max {
|
|
|
|
w.WriteString(g.overview(true))
|
|
|
|
} else {
|
|
|
|
w.WriteString(g.overview(false))
|
|
|
|
}
|
|
|
|
w.WriteString(g.writeboard())
|
|
|
|
w.WriteString(g.comment)
|
|
|
|
w.WriteString("\n\n")
|
2019-11-07 05:37:47 +00:00
|
|
|
gopherpath := filepath.Join(folder, "move")
|
|
|
|
if num > 1 {
|
|
|
|
w.WriteString(fmt.Sprintf("1 PREV Move\t%s%d.map\t%s\t%s\n", gopherpath, num - 1, host, port))
|
|
|
|
} else if num == 1 {
|
|
|
|
firstfile := filepath.Join(folder, "gophermap")
|
|
|
|
w.WriteString(fmt.Sprintf("1 PREV Move\t%s\t%s\t%s\n", firstfile, host, port))
|
2019-11-06 06:36:36 +00:00
|
|
|
}
|
|
|
|
if num < max {
|
2019-11-07 05:37:47 +00:00
|
|
|
w.WriteString(fmt.Sprintf("1 NEXT Move\t%s%d.map\t%s\t%s\n", gopherpath, num + 1, host, port))
|
|
|
|
}
|
|
|
|
if num-5 >= 0 {
|
|
|
|
w.WriteString(fmt.Sprintf("1 Jump BACK\t%s%d.map\t%s\t%s\n", gopherpath, num - 5, host, port))
|
|
|
|
} else if num > 0 {
|
|
|
|
w.WriteString(fmt.Sprintf("1 Jump BACK\t%s%d.map\t%s\t%s\n", gopherpath, 0, host, port))
|
|
|
|
}
|
|
|
|
if num+5 <= max {
|
|
|
|
w.WriteString(fmt.Sprintf("1 Jump AHEAD\t%s%d.map\t%s\t%s\n", gopherpath, num + 5, host, port))
|
|
|
|
} else if num < max {
|
|
|
|
w.WriteString(fmt.Sprintf("1 Jump AHEAD\t%s%d.map\t%s\t%s\n", gopherpath, max, host, port))
|
2019-11-06 06:36:36 +00:00
|
|
|
}
|
|
|
|
w.WriteString("\n\n")
|
|
|
|
w.Flush()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (g *game) updateBoard(row, col int, match string) {
|
|
|
|
var val int
|
|
|
|
if row - 1 >= 0 && g.board[row - 1][col] == match {
|
|
|
|
val = g.flood(row-1,col,match,check)
|
|
|
|
if val > 0 {
|
|
|
|
g.flood(row-1,col,check,match)
|
|
|
|
} else {
|
|
|
|
g.flood(row-1,col,check,empty)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if row + 1 < g.size && g.board[row + 1][col] == match {
|
|
|
|
val = g.flood(row+1,col,match,check)
|
|
|
|
if val > 0 {
|
|
|
|
g.flood(row+1,col,check,match)
|
|
|
|
} else {
|
|
|
|
g.flood(row+1,col,check,empty)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if col - 1 >= 0 && g.board[row][col - 1] == match {
|
|
|
|
val = g.flood(row,col-1,match,check)
|
|
|
|
if val > 0 {
|
|
|
|
g.flood(row,col-1,check,match)
|
|
|
|
} else {
|
|
|
|
g.flood(row,col-1,check,empty)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if col + 1 < g.size && g.board[row][col + 1] == match {
|
|
|
|
val = g.flood(row,col+1,match,check)
|
|
|
|
if val > 0 {
|
|
|
|
g.flood(row,col+1,check,match)
|
|
|
|
} else {
|
|
|
|
g.flood(row,col+1,check,empty)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
func (g *game) flood(row, col int, match, update string) int {
|
|
|
|
if g.board[row][col] != match {
|
|
|
|
if g.board[row][col] == empty {
|
|
|
|
return 1
|
|
|
|
}
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
g.board[row][col] = update
|
|
|
|
|
|
|
|
var total int
|
|
|
|
|
|
|
|
if row - 1 >= 0 {
|
|
|
|
total += g.flood(row - 1, col, match, update)
|
|
|
|
}
|
|
|
|
|
|
|
|
if row + 1 < g.size {
|
|
|
|
total += g.flood(row + 1, col, match, update)
|
|
|
|
}
|
|
|
|
|
|
|
|
if col - 1 >= 0 {
|
|
|
|
total += g.flood(row, col - 1, match, update)
|
|
|
|
}
|
|
|
|
|
|
|
|
if col + 1 < g.size {
|
|
|
|
total += g.flood(row, col + 1, match, update)
|
|
|
|
}
|
|
|
|
|
|
|
|
return total
|
|
|
|
}
|
|
|
|
|
|
|
|
func (g *game) move(move, piece string) {
|
|
|
|
row, col := parseMove(move)
|
|
|
|
g.board[row][col] = piece
|
|
|
|
if piece == black {
|
|
|
|
g.updateBoard(row, col, white)
|
|
|
|
} else {
|
|
|
|
g.updateBoard(row, col, black)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func parseMove(m string) (int, int) {
|
|
|
|
if len(m) != 2 {
|
|
|
|
fmt.Printf("Invalid move: %s", m)
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
m = strings.ToUpper(m)
|
|
|
|
|
|
|
|
return int(m[0])-65, int(m[1])-65
|
|
|
|
}
|
|
|
|
|
2019-11-05 14:36:17 +00:00
|
|
|
|
|
|
|
// 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)
|
|
|
|
}
|
2019-11-07 00:35:17 +00:00
|
|
|
ast := generateTree([]rune(string(bytes)))
|
2019-11-05 14:36:17 +00:00
|
|
|
if err != nil {
|
|
|
|
fmt.Println(err.Error())
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
return ast
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Generate a game struct
|
|
|
|
|
|
|
|
func makeGame() game {
|
|
|
|
return game{
|
|
|
|
"",
|
|
|
|
"",
|
|
|
|
"",
|
|
|
|
"",
|
|
|
|
"",
|
|
|
|
"",
|
|
|
|
"",
|
|
|
|
"",
|
|
|
|
"",
|
|
|
|
"",
|
|
|
|
"",
|
|
|
|
"",
|
|
|
|
"",
|
|
|
|
"",
|
|
|
|
"",
|
2019-11-06 06:36:36 +00:00
|
|
|
"",
|
2019-11-05 14:36:17 +00:00
|
|
|
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() {
|
2019-11-07 05:37:47 +00:00
|
|
|
flag.StringVar(&host, "host", "colorfield.space", "The gopher host")
|
|
|
|
flag.StringVar(&port, "port", "70", "Gopher port being used")
|
|
|
|
flag.StringVar(&folder, "folder", "/", "Gopher folder")
|
|
|
|
flag.StringVar(&out, "out", "./", "Local folder to generate files")
|
|
|
|
flag.Parse()
|
|
|
|
if len(flag.Args()) < 1 {
|
2019-11-05 14:36:17 +00:00
|
|
|
fmt.Println("Must provide path to input sgf")
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
2019-11-07 05:37:47 +00:00
|
|
|
ast := generateAST(flag.Args()[0])
|
2019-11-07 17:39:04 +00:00
|
|
|
fmt.Println(ast)
|
2019-11-07 05:37:47 +00:00
|
|
|
game := gameStruct(ast)
|
|
|
|
numMoves := len(ast) - 1
|
|
|
|
if numMoves > 0 {
|
|
|
|
for i, n := range ast {
|
|
|
|
game.handleNode(n, i, numMoves)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
overview := game.overview(false)
|
|
|
|
fmt.Print(overview)
|
|
|
|
fmt.Print(game.writeboard())
|
2019-11-05 14:36:17 +00:00
|
|
|
}
|