diff --git a/.gitignore b/.gitignore index f430683..358be18 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ *.log *.ps *.pdf +__pycache__/ diff --git a/kifugen.py b/kifugen.py index 5d977f3..076b388 100644 --- a/kifugen.py +++ b/kifugen.py @@ -3,6 +3,9 @@ import os import sys from subprocess import check_call, DEVNULL, STDOUT, CalledProcessError from shutil import copyfile, rmtree +from pprint import pprint + +import simpleGoBoard parser = argparse.ArgumentParser(description='Convert sgf go records into a kifu format.') @@ -26,6 +29,8 @@ header = sgfData[0] moves = sgfData[1:] def format_date(date): + if date == "": + return "" months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"] ds = date.split("-") # year month day ds.reverse() @@ -67,45 +72,28 @@ def generate_title(): return "".join(out) -def generate_moves(): - finished = False + +def init_simpleGoBoard(): + for move in moves: + if len(move) == 5: + coordinates = (ord(move[2].lower())-ord("a")+1, parsedHeader["boardSize"]-ord(move[3].lower())+ord("a")) + if move[0].lower() == "w": + simpleGoBoard.whiteMoves.append(coordinates) + elif move[0].lower() == "b": + simpleGoBoard.blackMoves.append(coordinates) + + +def generate_boards(): + init_simpleGoBoard() outText = [] for i in range(len(splitBoardAt)-1): currentSplit = splitBoardAt[i] nextSplit = splitBoardAt[i+1] - outText.append("\\begin{psgoboard}\n\t") - - # old moves - for j in range(currentSplit): - firstCoordinate, secondCoordinate = extract_coordinates(moves[j]) - if not (firstCoordinate == -1 or secondCoordinate == -1): - outText.append(f"\\move*{{{firstCoordinate}}}{{{secondCoordinate}}} ") - if j % 5 == 4: - outText.append("\n\t") - elif secondCoordinate < 10: # nice spacing - outText.append(" ") - - # new moves - for j in range(nextSplit-currentSplit): - firstCoordinate, secondCoordinate = extract_coordinates(moves[currentSplit+j]) - if not (firstCoordinate == -1 or secondCoordinate == -1): - outText.append(f"\\move{{{firstCoordinate}}}{{{secondCoordinate}}} ") - if j % 5 == 4: - outText.append("\n\t") - elif secondCoordinate < 10: # nice spacing - outText.append(" ") - - # was it the last move? - if currentSplit+j == len(moves)-1: - finished = True - break - - outText.append("\n\\end{psgoboard}\n") + board, finished = simpleGoBoard.produce_latex(currentSplit, nextSplit, args.cn) + outText.extend(board) if finished: break - elif not args.cn: - outText.append("\n\\setcounter{gomove}{0}\n") return "".join(outText) @@ -135,7 +123,7 @@ if parsedHeader["rankBlack"] != "": playerBlack += f" ({parsedHeader['rankBlack']})" if parsedHeader["rankWhite"] != "": playerWhite += f" ({parsedHeader['rankWhite']})" -moves = generate_moves() +boards = generate_boards() outText = f""" \\documentclass[a4paper]{{article}} @@ -167,7 +155,7 @@ outText = f""" \\end{{tabularx}} \\vspace{{3cm}} -{moves} +{boards} \\end{{center}} \\end{{document}} @@ -195,4 +183,4 @@ if not args.t: rmtree(f"{fileBase}/") # hould output be opened? if args.o: - os.system(f'"{fileBase}.pdf"') + os.startfile(f'"{fileBase}.pdf"') diff --git a/pdfpreview.png b/pdfpreview.png index d9312f3..1df1fc8 100644 Binary files a/pdfpreview.png and b/pdfpreview.png differ diff --git a/simpleGoBoard.py b/simpleGoBoard.py new file mode 100644 index 0000000..9d9c9aa --- /dev/null +++ b/simpleGoBoard.py @@ -0,0 +1,216 @@ +import itertools +from pprint import pprint + +blackMoves = [] +whiteMoves = [] +boardSize = 19 + +board = [] +blackGroups = [] +whiteGroups = [] + +def get_neighbouring_coordinates(point): + neighbours = [] + potentialNeighbours = [ + (point[0]-1,point[1]), + (point[0]+1,point[1]), + (point[0],point[1]-1), + (point[0],point[1]+1) + ] + + for point in potentialNeighbours: + if 1 <= point[0] <= 19 and 1 <= point[1] <= 19: + neighbours.append(point) + + return neighbours + +def get_neighbouring_groups(position, color): + groups = whiteGroups if color == "w" else blackGroups + + neighbouringGroups = set() + neighbours = get_neighbouring_coordinates(position) + + for point in neighbours: + for index, group in enumerate(groups): + if point in group: + neighbouringGroups.add(index) + + return list(neighbouringGroups) + + +def get_liberties(group): + ret = set() + for point in group: + neighbours = get_neighbouring_coordinates(point) + for neighbour in neighbours: + if board[neighbour[0]-1][neighbour[1]-1] == "": + ret.add(neighbour) + return ret + + +def remove_dead_groups(color): + bothGroups = [] + + # get the order right so that black can kill a white + # group by playing into whites only eye (and not killing himself first) + if color == "w": + bothGroups = whiteGroups + blackGroups + else: + bothGroups = blackGroups + whiteGroups + + # remove dead from board + for group in bothGroups: + if len(get_liberties(group)) == 0: + for pos in group: + board[pos[0]-1][pos[1]-1] = "" + + # remove dead from groups lists + whiteGroups[:] = [ group for group in whiteGroups if len(get_liberties(group)) != 0 ] + blackGroups[:] = [ group for group in blackGroups if len(get_liberties(group)) != 0 ] + +def play_move(color, move): + playerGroups = whiteGroups if color == "w" else blackGroups + + neighbouringGroups = get_neighbouring_groups(move, color) + + # no neighbouring groups make new one + if len(neighbouringGroups) == 0: + playerGroups.append([(move[0], move[1])]) + else: + # we want to merge everything into the first group and then pop the others in + # reverse index order so no indecies are getting wrong + neighbouringGroups.sort() + neighbouringGroups[1:] = neighbouringGroups[1:][::-1] + + # 1 or more neighbouring groups -> extend first one + playerGroups[neighbouringGroups[0]].append((move[0], move[1])) + + # if also more than one group -> merge with others + for i, group in enumerate(neighbouringGroups[1:]): + playerGroups[neighbouringGroups[0]].extend(playerGroups.pop(group)) + + board[move[0]-1][move[1]-1] = color + remove_dead_groups("w" if color == "b" else "b") + + +def simulate_board_up_to(lastMoveNumber): + global board + global whiteGroups + global blackGroups + + # reset board and groups + blackGroups = [] + whiteGroups = [] + board = [ [ "" for i in range(boardSize) ] for y in range(boardSize) ] + moveSequence = [ j for i in itertools.zip_longest(blackMoves,whiteMoves) for j in i ] + + for i in range(len(blackMoves) + len(whiteMoves)): + if i == lastMoveNumber: + break + + # blacks turn + if i % 2 == 0: + if len(blackMoves) == 0: continue + play_move("b", blackMoves[i//2]) + else: + if len(whiteMoves) == 0: continue + play_move("w", whiteMoves[i//2]) + + +def show_board(): + print(" "*3 + "A B C D E F G H J K L M N O P Q R S T") + i = 0 + for y in range(boardSize): + i += 1 + if boardSize-i+1 < 10: + print(f" {boardSize-i+1}", end="") + else: + print(f"{boardSize-i+1}", end="") + + for x in range(boardSize): + if board[x][boardSize-y-1] == "": + print(" .", end="") + else: + print(" " + board[x][boardSize-y-1], end ="") + + print(f" {boardSize-i+1}") + print(" "*3 + "A B C D E F G H J K L M N O P Q R S T") + +def get_latex_at_move(fromMove): + latexList = [] + + simulate_board_up_to(fromMove) + # just dumping the baord, nothing special + for y in range(len(board)): + for x in range(len(board)): + if board[x][y] != "": + color = "white" if board[x][y] == "w" else "black" + if ord("a")+x < ord("i"): + firstCoordinate = chr(ord("a")+x) + else: + firstCoordinate = chr(ord("a")+x+1) + latexList.append(f"\\stone{{{color}}}{{{firstCoordinate}}}{{{y+1}}}\n") + + return latexList + +def produce_latex(fromMove, toMove, continousCounting): + # old moves + latexList = ["\\begin{psgoboard}\n\t"] + latexList.extend(get_latex_at_move(fromMove)) + + finished = False + moveCount = 1 + if continousCounting: + moveCount = fromMove + 1 + + # new moves + for i in range(fromMove, len(blackMoves) + len(whiteMoves)): + if i == toMove: break + moves = [] + color = "" + + if i % 2 == 0: # blacks turn + moves = blackMoves + color = "black" + else: # whites turn + moves = whiteMoves + color = "white" + + # if this player has no moves left -> continue + if len(moves) == 0: continue + + x, y = moves[i//2] + + # skip the 'i' coordinate + if ord("a")+x-1 < ord("i"): + firstCoordinate = chr(ord("a")+x-1) + else: + firstCoordinate = chr(ord("a")+x) + + latexList.append(f"\\stone[\\marklb{{{moveCount}}}]{{{color}}}{{{firstCoordinate}}}{{{y}}}") + moveCount += 1 + + else: + # played the last move + finished = True + + latexList.append("\n\\end{psgoboard}\n") + return latexList, finished + + +if __name__ == '__main__': + blackMoves.append((4,4)) + whiteMoves.append((4,3)) + blackMoves.append((5,3)) + whiteMoves.append((5,4)) + blackMoves.append((3,3)) + whiteMoves.append((3,4)) + blackMoves.append((4,2)) + whiteMoves.append((4,5)) + blackMoves.append((10,10)) + whiteMoves.append((4,3)) + blackMoves.append((4,4)) + whiteMoves.append((4,3)) + + produce_latex(7, 200) + show_board()