Skip to content

Commit

Permalink
Now parsing games.pgn fully
Browse files Browse the repository at this point in the history
  • Loading branch information
oerc0122 committed Mar 20, 2021
1 parent 777c0b2 commit 1d91f04
Show file tree
Hide file tree
Showing 12 changed files with 2,495 additions and 119 deletions.
5 changes: 2 additions & 3 deletions Board.gd
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
extends TileMap

class_name Board
onready var PIECE = preload("res://Piece.tscn")
onready var game = get_parent()

Expand Down Expand Up @@ -94,7 +94,7 @@ func move_piece(pos:Vector2, newLoc:Vector2):
emit_signal("error","Cannot move piece", "Tile occupied")
return

if not moving.can_move(newLoc, turn.capture):
if not moving.can_move(newLoc, turn.capture, self):
emit_signal("error","Cannot move piece", "Piece cannot move there")
return

Expand Down Expand Up @@ -150,4 +150,3 @@ func to_pos() -> Dictionary:
if child is Piece:
dictOut[child.ID] = child.gridPos
return dictOut

Binary file modified BoardTile.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
171 changes: 125 additions & 46 deletions FileParser.gd
Original file line number Diff line number Diff line change
Expand Up @@ -6,65 +6,114 @@ const COLOURS = ["White", "Black"]
signal read(game)
signal error(title, message)

func read(filepath) -> Game:
const moveRER = ("(?<Piece>[NKQRBP]?)"+
"(?<Disambiguation>(?:[a-h]|[1-8]|[a-h][1-8])(?=x?[a-h][1-8]))?"+
"(?<Capture>x)?"+
"(?<Location>[a-h][1-8])"+
"(?:=(?<Promotion>[NKQRB]))?"+
"(?<Check>[+#])?")
const castleRER = "(?<Castle>O-O(?:-O)?)"
var plyRER = "(?:(?:{move})|{castle})".format({"move":moveRER, "castle":castleRER})
const commentRER = "\\{(?<comment>[^}]+)\\}"
const rawCommentRER = ";(?<comment>.*)"
const suggestionRER = "\\((?<comment>[^)]+)\\)"
const NAGRER = "(?<nag>\\$[0-9]+)"
const tagRER = "\\[(?<key>[A-Za-z]+) *\"(?<value>[^\"]*)\"\\]\\s*(?:"+commentRER+")?"
const finalRER = "(?<WhiteWin>(?:0|1/2|1(?!/2)))-(?<BlackWin>(?:0|1/2|1(?!/2)))(?!\\s*\")"

var fullTurnRER = ("(?<turn>[0-9]+)\\.\\s*" +
plyRER.replace("?<","?<White")+"\\s+" +
plyRER.replace("?<","?<Black")+"?\\s*" +
"(?:"+commentRER+")?"
)
var halfTurnRER = ("(?<turn>[0-9]+)\\.{3}\\s*" +
plyRER.replace("?<","?<Black")+"\\s*" +
"(?:"+commentRER+")?"
)


func read(filepath) -> Array:
self.path = filepath
var file := File.new()
var status = file.open(self.path, File.READ)

if status:
push_error("File not found: Cannot open file: "+self.path)
null.get_node("crash")
emit_signal("error", "File not found...", "File not found: Cannot open file: "+self.path)
return []
var content := file.get_as_text()
file.close()

var rawCommentRE = RegEx.new()
rawCommentRE.compile(";(?<comment>.*)")
rawCommentRE.compile(rawCommentRER)
var suggestionRE = RegEx.new()
suggestionRE.compile(suggestionRER)
var nagRE = RegEx.new()
nagRE.compile(NAGRER)
var tagRE = RegEx.new()
tagRE.compile("\\[(?<key>[A-Za-z]+) *\"(?<value>[^\"]+)\"\\]")
var moveRE = ("(?<Piece>[NKQRBP]?)"+
"(?<Disambiguation>(?:[a-h]|[1-8]|[a-h][1-8])(?=x?[a-h][1-8]))?"+
"(?<Capture>x)?"+
"(?<Location>[a-h][1-8])"+
"(?:=(?<Promotion>[NKQRB]))?"+
"(?<Check>[+#])?")
var commentRE = "\\{(?<comment>[^}]+)\\}"
tagRE.compile(tagRER)
var turnRE = RegEx.new()
turnRE.compile("(?<turn>[0-9]+)\\.\\s*" +
"("+moveRE.replace("?<","?<White")+"|(?<WhiteCastle>O-O(?:-O)?))\\s*" +
"("+moveRE.replace("?<","?<Black")+"|(?<BlackCastle>O-O(?:-O)?))\\s*" +
"(?:"+commentRE+")?"
)

content = rawCommentRE.sub(content, "{$comment}", true).strip_escapes()

var loaded = Game.new()
loaded.path = filepath.get_file()
for tag in tagRE.search_all(content):
loaded.data[tag.get_string("key")] = tag.get_string("value")
content = content.replace(tag.get_string(), "")

var currTurn : Turn
if not "SetUp" in loaded.data:
currTurn = Turn.new()
currTurn.raw = "0. Start game"
else:
if not "FEN" in loaded.data:
emit_signal("error", "Bad input pgn", "FEN data not found")
currTurn = parse_FEN(loaded.data["FEN"])
currTurn.raw = "0. Start game"
currTurn.ID = "Start Game"

loaded.turns = [currTurn]



turnRE.compile("(?:%s|%s)" % [fullTurnRER, halfTurnRER])
var finalRE = RegEx.new()
finalRE.compile(finalRER)

content = rawCommentRE.sub(content, "{$comment}", true).replace("\n", " ").strip_escapes()
# content = suggestionRE.sub(content, "", true) # For now ignore suggestions, later may want to branch
content = nagRE.sub(content, "", true) # Remove NAGing
var prevInd = 0
var matches = []

for game in finalRE.search_all(content):
var ind = game.get_end()
matches.push_back(content.substr(prevInd, ind-prevInd))
prevInd = ind

var games = []
for game in matches:
game = handle_branches(game)
var loaded = Game.new()
loaded.path = filepath.get_file()
for tag in tagRE.search_all(game):
loaded.data[tag.get_string("key")] = tag.get_string("value")
game = game.replace(tag.get_string(), "")

var turns = turnRE.search_all(game)
var parsed_turns = []
for turn in range(len(turns)): # Stitch half turns
var currTurn = turns[turn]
if currTurn.get_string("BlackLocation").empty() and currTurn.get_string("BlackCastle").empty():
pass # Skip: handled by stitch
elif currTurn.get_string("WhiteLocation").empty() and currTurn.get_string("WhiteCastle").empty():
var prevTurn = turns[turn-1]
var stitch = self.stitch_turns(prevTurn, currTurn)
parsed_turns.push_back(turnRE.search(stitch))
else:
parsed_turns.push_back(currTurn)
turns.clear() # Clean up

var currTurn : Turn
if not "SetUp" in loaded.data:
currTurn = Turn.new()
currTurn.raw = "0. Start game"
else:
if not "FEN" in loaded.data:
emit_signal("error", "Bad input pgn", "FEN data not found")
currTurn = parse_FEN(loaded.data["FEN"])
currTurn.raw = "0. Start game"
currTurn.ID = "Start Game"

loaded.turns = [currTurn]

for turn in turnRE.search_all(content):
currTurn = Turn.new(Turn.COLOUR.WHITE, currTurn.positions, turn, currTurn.promotions)
loaded.turns.push_back(currTurn)
currTurn = Turn.new(Turn.COLOUR.BLACK, currTurn.positions, turn, currTurn.promotions)
loaded.turns.push_back(currTurn)
for turn in parsed_turns:
currTurn = Turn.new(Turn.COLOUR.WHITE, currTurn.positions, turn, currTurn.promotions)
loaded.turns.push_back(currTurn)
currTurn = Turn.new(Turn.COLOUR.BLACK, currTurn.positions, turn, currTurn.promotions)
loaded.turns.push_back(currTurn)
games.push_back(loaded)

emit_signal("read", loaded)
return loaded
emit_signal("read", games)
return games

func parse_FEN(FEN: String):
# Parse FEN notation of the form:
Expand Down Expand Up @@ -94,3 +143,33 @@ func parse_FEN(FEN: String):
turn.player = "wb".find(line[1])
turn.turnNo = line[5]
return turn

func stitch_turns(prevTurn: RegExMatch, currTurn: RegExMatch) -> String:
var plyRE = RegEx.new()
plyRE.compile(plyRER)
if prevTurn.get_string("turn") != currTurn.get_string("turn"):
null.get_node("Crash")

var whitePly = plyRE.search(prevTurn.strings[0])
var blackPly = plyRE.search(currTurn.strings[0])

var stitch = "{turn}.{whitePly} {blackPly} ".format({"turn": currTurn.get_string("turn"),
"whitePly": whitePly.strings[0],
"blackPly": blackPly.strings[0]})
stitch += " {"+prevTurn.get_string("comment") + ";" + currTurn.get_string("comment") + "}"
return stitch

func handle_branches(content: String) -> String:
while content.find("(") > 0:
var init = content.find("(")
var count = 1
var curr = init+1
while count > 0:
match content[curr]:
"(":
count += 1
")":
count -= 1
curr += 1
content = content.substr(0, init) + content.substr(curr)
return content
2 changes: 1 addition & 1 deletion MainGame.gd
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ func _on_Board_piece_moved(turn) -> void:
func new_game(game: Game):
self.games.push_back(game)
emit_signal("new_game", game)
load_turn(self.currTurn, len(games)-1)
load_turn(0, len(games)-1)

func branch(toCopy: Game = null, turn: int = -1) -> Game:
if not toCopy:
Expand Down
48 changes: 31 additions & 17 deletions Piece.gd
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,12 @@ const KNIGHT_MOVES = [Vector2(1,2),
Vector2(2,-1)]
const TYPES_SAN = "KQBNRP"

onready var board = self.get_parent()

var gridPos : Vector2
var type : int
var moved : bool = false
var colour : int
onready var board = get_parent()
var castled : int = 0
var ID : String

Expand All @@ -31,43 +32,53 @@ func init(colour: int, type: int) -> void:
self.set_frame(colour*6 + type)
self.ID = get_name()

func can_move(newLoc, capture=false) -> bool:
func init_from_name(code: String, pos: Vector2):
self.init("WB".find(code[0]), TYPES_SAN.find(code[1]))
self.gridPos = pos

func can_move(newLoc, capture: bool, boardIn) -> bool:
var raw = newLoc - self.gridPos
var ref = raw.abs()

var mag = max(ref.x,ref.y)
var dir = raw / mag

if type != TYPES.KNIGHT: # Check not jumping
for step in range(1,mag): # May land on piece on capture
if self.gridPos + step*dir in boardIn.positions:
return false

match type:
TYPES.QUEEN:
return ref.x == 0 or ref.y == 0 or ref.x == ref.y
TYPES.KING:
if not self.moved and newLoc in [Vector2(3, self.colour*7+1), Vector2(7, self.colour*7+1)]:
match int(newLoc.x):
3:
if (not newLoc in board.positions and
not newLoc + Vector2.RIGHT in board.positions and
not newLoc + Vector2.LEFT in board.positions and
Vector2(1,self.colour*7+1) in board.positions and
not board.positions[Vector2(1,self.colour*7+1)].moved):
if (not newLoc in boardIn.positions and
not newLoc + Vector2.RIGHT in boardIn.positions and
not newLoc + Vector2.LEFT in boardIn.positions and
Vector2(1,self.colour*7+1) in boardIn.positions and
not boardIn.positions[Vector2(1,self.colour*7+1)].moved):
self.castled = -1
return true
7:
if (not newLoc in board.positions and
not newLoc + Vector2.LEFT in board.positions and
Vector2(8,self.colour*7+1) in board.positions and
not board.positions[Vector2(8,self.colour*7+1)].moved):
if (not newLoc in boardIn.positions and
not newLoc + Vector2.LEFT in boardIn.positions and
Vector2(8,self.colour*7+1) in boardIn.positions and
not boardIn.positions[Vector2(8,self.colour*7+1)].moved):
self.castled = 1
return true
return ref.length_squared() < 2
return ref.x < 2 and ref.y < 2
TYPES.KNIGHT:
return ref in KNIGHT_MOVES
TYPES.ROOK:
return ref.x == 0 or ref.y == 0
TYPES.BISHOP:
return ref.x == ref.y
TYPES.PAWN:
if not self.moved:
return ref.x == int(capture) and raw.y in [PAWN_DIR[self.colour], PAWN_DIR[self.colour]*2]
else:
return ref.x == int(capture) and raw.y == PAWN_DIR[self.colour]
if not self.moved and not capture:
return (ref.x == 0 and raw.y in [self.move_dir(), 2*self.move_dir()])
return ref.x == int(capture) and raw.y == self.move_dir()

return false

Expand All @@ -82,3 +93,6 @@ func move_to_pos():

func get_ID() -> String:
return "WB"[self.colour] + TYPES_SAN[self.type]

func move_dir() -> int:
return PAWN_DIR[self.colour]
25 changes: 23 additions & 2 deletions Root.gd
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ extends Node

onready var UIMain = $VBoxContainer/HBoxContainer/UIPanel
onready var MainGame = $VBoxContainer/HBoxContainer/ViewportContainer/MainGame
onready var GamesList = $GamesDialog/ScrollContainer/VBoxContainer


func host(port: int, players: int):
var peer = NetworkedMultiplayerENet.new()
Expand All @@ -24,8 +26,20 @@ func error(title: String, message: String):
$ErrorPopup/ErrorMessage.text = message
$ErrorPopup.popup_centered()

func _on_FileParser_read(game) -> void:
new_game(game)
func _on_FileParser_read(games: Array) -> void:
# Build list
for game in games:
add_button(game)

$GamesDialog.popup_centered()
yield($GamesDialog, "popup_hide")
for idx in range(GamesList.get_child_count()):
if GamesList.get_child(idx).pressed:
new_game(games[idx])

for child in GamesList.get_children():
child.queue_free()
GamesList.remove_child(child)

func _on_TopMenu_load_game(file) -> void:
$FileParser.read(file)
Expand All @@ -34,3 +48,10 @@ func _on_TopMenu_new_game() -> void:
var game = Game.new()
game.path = "New game"
new_game(game)

func add_button(game: Game):
var button = Button.new()
button.toggle_mode = true
button.text = "%s v. %s" % [game.data.get("White", "Unknown"), game.data.get("Black", "Unknown")]
GamesList.add_child(button)
GamesList.move_child(button, GamesList.get_child_count()-1)
Loading

0 comments on commit 1d91f04

Please sign in to comment.