From 76c720500a65ae548c4348ad8d686d909985be06 Mon Sep 17 00:00:00 2001
From: Paolo Capriotti
Date: Thu, 25 Jun 2009 15:59:23 +0200
Subject: [PATCH] Add pgn parser.
---
lib/games/chess/main.rb | 4 +-
lib/games/chess/pgn.rb | 58 +++++++++++++++++++++-
test/games/chess/test_pgn.rb | 116 +++++++++++++++++++++++++++++++++++++------
3 files changed, 160 insertions(+), 18 deletions(-)
diff --git a/lib/games/chess/main.rb b/lib/games/chess/main.rb
index 5603551..ef40686 100644
--- a/lib/games/chess/main.rb
+++ b/lib/games/chess/main.rb
@@ -27,7 +27,9 @@ Game.add :chess do
:keywords => %w(chess),
:game_writer_component => PGN,
:game_writer => lambda {
- game_writer_component.new(serializer.new(:compact)) }
+ game_writer_component.new(serializer.new(:compact),
+ state) },
+ :game_reader => lambda { game_writer }
end
Game.add :chess5x5, [:chess] do |chess|
diff --git a/lib/games/chess/pgn.rb b/lib/games/chess/pgn.rb
index 3071a5c..410f15d 100644
--- a/lib/games/chess/pgn.rb
+++ b/lib/games/chess/pgn.rb
@@ -1,8 +1,14 @@
+require 'strscan'
+require 'history'
+
module Chess
class PGN
- def initialize(serializer)
+ ParseError = Class.new(Exception)
+
+ def initialize(serializer, state_factory)
@serializer = serializer
+ @state_factory = state_factory
end
def write(info, history)
@@ -18,10 +24,60 @@ class PGN
tag(:white, info.fetch(:players, {})[:white]) +
tag(:black, info.fetch(:players, {})[:black]) +
tag(:result, result(info[:result])) +
+ "\n" +
game(history) + " " +
result(info[:result]) + "\n"
end
+ def read(text, info)
+ # read tags
+ scanner = StringScanner.new(text)
+ while scanner.scan(/\[(.*) "(.*)"\]\n/)
+ info[scanner[1].downcase.to_sym] = scanner[2]
+ end
+ scanner.scan(/\n*/)
+
+ # insert players into info[:players]
+ info[:players] = {
+ :white => info[:white],
+ :black => info[:black] }
+ info.delete(:white)
+ info.delete(:black)
+
+ state = @state_factory.new
+ state.setup
+ history = History.new(state)
+ index = 1
+
+ while scanner.scan(/(\d+)\.\s*/)
+ if index != scanner[1].to_i
+ raise ParseError.new("Unexpected index #{index}")
+ end
+
+ wmove = @serializer.deserialize(scanner, state)
+ raise ParseError.new("Expected move at index #{index}") unless wmove
+ state = state.dup
+ state.perform! wmove
+ history.add_move(state, wmove)
+
+ scanner.scan(/\s+/)
+ bmove = @serializer.deserialize(scanner, state)
+ break unless bmove
+ state = state.dup
+ state.perform! bmove
+ history.add_move(state, bmove)
+
+ scanner.scan(/\s+/)
+
+ index += 1
+ end
+
+ result = scanner.scan(/1-0|0-1|1\/2-1\/2|\*/)
+ raise ParseError.new("Expected result") unless result
+ info[:result] = result
+ history
+ end
+
def tag(key, value)
if value
%{[#{key.to_s.capitalize} "#{value}"]\n}
diff --git a/test/games/chess/test_pgn.rb b/test/games/chess/test_pgn.rb
index 785b681..67d7b72 100644
--- a/test/games/chess/test_pgn.rb
+++ b/test/games/chess/test_pgn.rb
@@ -6,25 +6,83 @@ require 'helpers/validation_helper'
class TestPGN < Test::Unit::TestCase
include ValidationHelper
+ PGN1 = <<-END_OF_PGN
+[Result "0-1"]
+
+1.e4 e5 0-1
+ END_OF_PGN
+ PGN2 = <<-END_OF_PGN
+[Event "Oktoberfest"]
+[White "Doe, John"]
+[Black "Smith, Bob"]
+[Result "1-0"]
+
+1.Nf3 1-0
+ END_OF_PGN
+ PGN3 = <<-END_OF_PGN
+[Event "Blindfold"]
+[Site "Hollywood (USA)"]
+[Date "1954.??.??"]
+[EventDate "?"]
+[Round "?"]
+[Result "0-1"]
+[White "Heinz Steiner"]
+[Black "Arthur Bisguier"]
+[ECO "B70"]
+[WhiteElo "?"]
+[BlackElo "?"]
+[PlyCount "124"]
+
+1.e4 c5 2.Nf3 d6 3.d4 cxd4 4.Nxd4 Nf6 5.Nc3 g6 6.Bg5 Bg7 7.Qd2
+Nc6 8.Nb3 h6 9.Bh4 g5 10.Bg3 Nh5 11.Be2 Nxg3 12.hxg3 a5 13.a4
+Be6 14.f4 Bxb3 15.cxb3 Nd4 16.Bc4 Rc8 17.Rd1 Qb6 18.Qf2 Qc5
+19.fxg5 Ne6 20.g6 Qxf2+ 21.Kxf2 Ng5 22.gxf7+ Nxf7 23.Ke2 Ng5
+24.Ke3 Rc5 25.Rd5 Ne6 26.Rf1 Be5 27.g4 Rf8 28.Rf5 Rf6 29.Rd1
+Rg6 30.Rh1 Kd7 31.Nd5 Nc7 32.Nb6+ Kc6 33.Nd5 Kd7 34.Rf7 Nxd5+
+35.exd5 Bf6 36.Kf3 Ke8 37.Rh7 Bg7 38.Bd3 Kf7 39.Bxg6+ Kxg6
+40.Rxg7+ Kxg7 41.Re1 Kf7 42.Ke4 Rc2 43.g3 Rxb2 44.Re3 e6
+45.dxe6+ Kxe6 46.Kd4+ Kd7 47.Kc4 Kc6 48.Re6 Rc2+ 49.Kd3 Rg2
+50.Re3 Kc5 51.Ke4 d5+ 52.Kf5 d4 53.Re5+ Kb4 54.Rb5+ Kc3
+55.Rxa5 d3 56.Rc5+ Kd4 57.Rc7 d2 58.Rd7+ Ke3 59.g5 hxg5 60.g4
+Rg1 61.Kxg5 d1=Q 62.Rxd1 Rxd1 0-1
+ END_OF_PGN
+
+ PGN4 = <<-END_OF_PGN
+[Event "Cheliabinsk"]
+[Site "Cheliabinsk"]
+[Date "1962.??.??"]
+[Round "?"]
+[White "Anatoli Karpov"]
+[Black "Tarinin"]
+[Result "1-0"]
+
+1.e4 e5 2.Nf3 Nc6 3.Bb5 a6 4.Ba4 d6 5.Bxc6+ bxc6 6.d4 f6
+7.O-O Bg4 8.dxe5 fxe5 9.Qd3 Be7 10.c4 Nf6 11.h3 Bxf3
+12.Qxf3 O-O 13.Nc3 Nd7 14.Qg4 Bf6 15.Be3 Qe8 16.Rad1 Rb8
+17.b3 Rf7 18.Rd3 Nf8 19.Ne2 Qd7 20.Qxd7 Rxd7 21.Rfd1 Rbd8
+22.Nc3 Kf7 23.g3 Ke6 24.Kg2 Kf7 25.f4 exf4 26.gxf4 Bxc3
+27.Rxc3 c5 28.Kf3 g6 29.Bf2 Ne6 30.Rcd3 c6 31.Bh4 Nd4+
+32.Rxd4 cxd4 33.Bxd8 Rxd8 34.Rxd4 Ke7 35.c5 d5 36.exd5
+cxd5 37.Ra4 Ra8 38.Ke3 Kd7 39.Kd4 Kc6 40.Rb4 a5 41.Rb6+
+Kc7 42.Kxd5 Rd8+ 43.Rd6 Rf8 44.Ra6 Rf5+ 45.Kc4 Rxf4+
+46.Kb5 a4 47.Ra7+ Kb8 48.Rxh7 axb3 49.axb3 Rf6 50.c6 Rf3
+51.b4 Rf4 52.Ka5 Rc4 53.b5 1-0
+ END_OF_PGN
+
def setup
@game = Game.get(:chess)
@state = @game.state.new
@state.setup
@history = History.new(@state)
- @writer = @game.game_writer.new
+ @pgn = @game.game_writer.new
end
def test_pgn_black_wins
add_move 4, 6, 4, 4
add_move 4, 1, 4, 3
info = { :result => :black }
-
- expected = <<-END_OF_PGN
-[Result "0-1"]
-1.e4 e5 0-1
- END_OF_PGN
-
- assert_equal expected, @writer.write(info, @history)
+
+ assert_equal PGN1, @pgn.write(info, @history)
end
def test_pgn_white_wins
@@ -33,16 +91,36 @@ class TestPGN < Test::Unit::TestCase
:event => 'Oktoberfest',
:players => { :white => 'Doe, John',
:black => 'Smith, Bob' } }
+
+ assert_equal PGN2, @pgn.write(info, @history)
+ end
+
+ def test_pgn_read_tags
+ info = {}
+ @pgn.read(PGN2, info)
- expected = <<-END_OF_PGN
-[Event "Oktoberfest"]
-[White "Doe, John"]
-[Black "Smith, Bob"]
-[Result "1-0"]
-1.Nf3 1-0
-END_OF_PGN
+ assert_equal "Oktoberfest", info[:event]
+ assert_equal "Doe, John", info[:players][:white]
+ assert_equal "Smith, Bob", info[:players][:black]
+ assert_equal "1-0", info[:result]
+ end
+
+ def test_pgn_read_moves
+ info = {}
+ @history = @pgn.read(PGN3, info)
- assert_equal expected, @writer.write(info, @history)
+ assert_move 1, 4, 6, 4, 4
+ assert_move 2, 2, 1, 2, 3
+ assert_move 3, 6, 7, 5, 5
+ assert_move 12, 5, 0, 6, 1
+ end
+
+ def test_pgn_read_write
+# info = {}
+# @history = @pgn.read(PGN4, info)
+# text = @pgn.write(info, @history)
+# assert_equal PGN4, text
+ # TODO reenable when suffixes work
end
private
@@ -55,4 +133,10 @@ END_OF_PGN
state.perform! move
@history.add_move(state, move)
end
+
+ def assert_move(index, *args)
+ move = unpack_move(*args)
+ assert_equal move.src, @history[index].move.src
+ assert_equal move.dst, @history[index].move.dst
+ end
end
--
2.11.4.GIT