pgn3 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 20fb2f8fb76ff182d4a77f001b3bf55b8d6ba0abc452094bdfe896c754b62501
4
+ data.tar.gz: a898ca24052f81028014c24e49dd2b84d91e12096ab1d4783e71432d8b537d90
5
+ SHA512:
6
+ metadata.gz: 84dbdb900720a5766209979bacf401279873028d35e89ec83c3e0457e0c6ec9325362dccb5cf6e7a7e923a1d1314565627fa4d1cc102b0fed400275973059a67
7
+ data.tar.gz: 5d74af5952a5ad2b4015a666fc24c4cb88e065c7320fc56e576e299dd33eb891ae2501c7b51061cb50f71269f831a17fa2dcaaf48ca8591efd48cee78ae62883
data/.gitignore ADDED
@@ -0,0 +1,20 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+
19
+
20
+ tags
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in pgn.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Stacey Touset
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,123 @@
1
+ # PGN2
2
+
3
+ This is a fork from [pgn](https://github.com/capicue/pgn) gem.
4
+ A PGN parser and FEN generator for ruby.
5
+
6
+ ## Usage
7
+
8
+ ### Creating games from pgn files
9
+
10
+ On the command line, it is easy to read in and play through chess games
11
+ in [portable game notation](http://en.wikipedia.org/wiki/Portable_Game_Notation) format.
12
+
13
+ ```
14
+ > games = PGN.parse(File.read("./examples/immortal_game.pgn"))
15
+ > game = games.first
16
+ > game.play
17
+ ```
18
+
19
+ Play through the game using `a` or left arrow to move backward, and `d`
20
+ or right arrow to move forward. `q` or `^C` quits play mode.
21
+
22
+ ♜ ♞ ♝ ♛ ♚ ♝ ♞ ♜
23
+ ♟ ♟ ♟ ♟ ♟ ♟ ♟ ♟
24
+ _ _ _ _ _ _ _ _
25
+ _ _ _ _ _ _ _ _
26
+ _ _ _ _ _ _ _ _
27
+ _ _ _ _ _ _ _ _
28
+ ♙ ♙ ♙ ♙ ♙ ♙ ♙ ♙
29
+ ♖ ♘ ♗ ♕ ♔ ♗ ♘ ♖
30
+
31
+ ♜ ♞ ♝ ♛ ♚ ♝ ♞ ♜
32
+ ♟ ♟ ♟ ♟ ♟ ♟ ♟ ♟
33
+ _ _ _ _ _ _ _ _
34
+ _ _ _ _ _ _ _ _
35
+ _ _ _ _ _ _ _ _
36
+ _ _ _ _ ♙ _ _ _
37
+ _ _ _ _ _ _ _ _
38
+ ♙ ♙ ♙ ♙ _ ♙ ♙ ♙
39
+ ♖ ♘ ♗ ♕ ♔ ♗ ♘ ♖
40
+
41
+ ...
42
+
43
+ You can also access all of the information about a game.
44
+
45
+ ```
46
+ > game.positions.last
47
+ =>
48
+ ♜ _ ♝ ♚ _ _ _ ♜
49
+ ♟ _ _ ♟ ♗ ♟ ♘ ♟
50
+ ♞ _ _ _ _ ♞ _ _
51
+ _ ♟ _ ♘ ♙ _ _ ♙
52
+ _ _ _ _ _ _ ♙ _
53
+ _ _ _ ♙ _ _ _ _
54
+ ♙ _ ♙ _ ♔ _ _ _
55
+ ♛ _ _ _ _ _ ♝ _
56
+
57
+ > game.positions.last.to_fen
58
+ => r1bk3r/p2pBpNp/n4n2/1p1NP2P/6P1/3P4/P1P1K3/q5b1 b - - 1 22
59
+
60
+ > game.result
61
+ => "1-0"
62
+
63
+ > game.tags["White"]
64
+ => "Adolf Anderssen"
65
+ ```
66
+
67
+ It is possible to create a game without parsing a pgn file.
68
+
69
+ ```
70
+ moves = %w{e4 c5 c3 d5 exd5 Qxd5 d4 Nf6}
71
+ game = PGN::Game.new(moves)
72
+ ```
73
+
74
+ Note that if you simply want an abstract syntax tree from the pgn file,
75
+ you can use `PGN::Parser.parse`.
76
+
77
+ ### Dealing with FEN strings
78
+
79
+ [Forsyth Edwards Notation](http://en.wikipedia.org/wiki/Forsyth%E2%80%93Edwards_Notation)
80
+ is a compact way to represent all of the information about a given chess
81
+ position. It is easy to convert between FEN strings and chess positions.
82
+
83
+ ```
84
+ > fen = PGN::FEN.start
85
+ => rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1
86
+
87
+ > fen = PGN::FEN.new("r1bk3r/p2pBpNp/n4n2/1p1NP2P/6P1/3P4/P1P1K3/q5b1 b - - 1 22")
88
+ > position = fen.to_position
89
+ =>
90
+ ♜ _ ♝ ♚ _ _ _ ♜
91
+ ♟ _ _ ♟ ♗ ♟ ♘ ♟
92
+ ♞ _ _ _ _ ♞ _ _
93
+ _ ♟ _ ♘ ♙ _ _ ♙
94
+ _ _ _ _ _ _ ♙ _
95
+ _ _ _ ♙ _ _ _ _
96
+ ♙ _ ♙ _ ♔ _ _ _
97
+ ♛ _ _ _ _ _ ♝ _
98
+
99
+ > position.to_fen
100
+ => r1bk3r/p2pBpNp/n4n2/1p1NP2P/6P1/3P4/P1P1K3/q5b1 b - - 1 22
101
+ ```
102
+
103
+ ## Installation
104
+
105
+ Add this line to your application's Gemfile:
106
+
107
+ gem 'pgn2'
108
+
109
+ And then execute:
110
+
111
+ $ bundle
112
+
113
+ Or install it yourself as:
114
+
115
+ $ gem install pgn2
116
+
117
+ ## Contributing
118
+
119
+ 1. Fork it
120
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
121
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
122
+ 4. Push to the branch (`git push origin my-new-feature`)
123
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/TODO.md ADDED
@@ -0,0 +1,12 @@
1
+ # TODOs
2
+
3
+ ## Parsing
4
+
5
+ - Accept a more flexible input format
6
+ - Support recursive variations
7
+ - Support numeric annotation glyphs
8
+
9
+ ## Misc
10
+
11
+ - Support converting a game to pgn format
12
+ - Speed up parsing
@@ -0,0 +1,17 @@
1
+ [Event "London"]
2
+ [Site "London"]
3
+ [Date "1851.??.??"]
4
+ [EventDate "?"]
5
+ [Round "?"]
6
+ [Result "1-0"]
7
+ [White "Adolf Anderssen"]
8
+ [Black "Kieseritzky"]
9
+ [ECO "C33"]
10
+ [WhiteElo "?"]
11
+ [BlackElo "?"]
12
+ [PlyCount "45"]
13
+
14
+ 1.e4 e5 2.f4 exf4 3.Bc4 Qh4+ 4.Kf1 b5 5.Bxb5 Nf6 6.Nf3 Qh6 7.d3 Nh5 8.Nh4 Qg5
15
+ 9.Nf5 c6 10.g4 Nf6 11.Rg1 cxb5 12.h4 Qg6 13.h5 Qg5 14.Qf3 Ng8 15.Bxf4 Qf6
16
+ 16.Nc3 Bc5 17.Nd5 Qxb2 18.Bd6 Bxg1 19. e5 Qxa1+ 20. Ke2 Na6 21.Nxg7+ Kd8
17
+ 22.Qf6+ Nxf6 23.Be7# 1-0
data/lib/pgn/board.rb ADDED
@@ -0,0 +1,163 @@
1
+ module PGN
2
+ # {PGN::Board} represents the squares of a chess board and the pieces on
3
+ # each square. It is responsible for translating between a human readable
4
+ # format (white queen's rook on the bottom left) and the obvious
5
+ # internal representation (white queen's rook is position [0,0]). It
6
+ # takes care of converting square names (e4) to actual locations, and
7
+ # can convert to unicode chess pieces for display purposes.
8
+ #
9
+ # @!attribute squares
10
+ # @return [Array<Array<String>>] the pieces on the board
11
+ #
12
+
13
+ class Board
14
+ # The starting, internal representation of a chess board
15
+ #
16
+ START = [
17
+ ['R', 'P', nil, nil, nil, nil, 'p', 'r'],
18
+ ['N', 'P', nil, nil, nil, nil, 'p', 'n'],
19
+ ['B', 'P', nil, nil, nil, nil, 'p', 'b'],
20
+ ['Q', 'P', nil, nil, nil, nil, 'p', 'q'],
21
+ ['K', 'P', nil, nil, nil, nil, 'p', 'k'],
22
+ ['B', 'P', nil, nil, nil, nil, 'p', 'b'],
23
+ ['N', 'P', nil, nil, nil, nil, 'p', 'n'],
24
+ ['R', 'P', nil, nil, nil, nil, 'p', 'r']
25
+ ].freeze
26
+
27
+ FILE_TO_INDEX = ('a'..'h').each_with_index.to_h
28
+ INDEX_TO_FILE = FILE_TO_INDEX.map(&:reverse).to_h
29
+
30
+ RANK_TO_INDEX = ('1'..'8').each_with_index.to_h
31
+ INDEX_TO_RANK = RANK_TO_INDEX.map(&:reverse).to_h
32
+
33
+ # algebraic to unicode piece lookup
34
+ #
35
+ UNICODE_PIECES = {
36
+ 'k' => "\u{265A}",
37
+ 'q' => "\u{265B}",
38
+ 'r' => "\u{265C}",
39
+ 'b' => "\u{265D}",
40
+ 'n' => "\u{265E}",
41
+ 'p' => "\u{265F}",
42
+ 'K' => "\u{2654}",
43
+ 'Q' => "\u{2655}",
44
+ 'R' => "\u{2656}",
45
+ 'B' => "\u{2657}",
46
+ 'N' => "\u{2658}",
47
+ 'P' => "\u{2659}",
48
+ nil => '_'
49
+ }.freeze
50
+
51
+ attr_accessor :squares
52
+
53
+ # @return [PGN::Board] a board in the starting position
54
+ #
55
+ def self.start
56
+ PGN::Board.new(START)
57
+ end
58
+
59
+ # @param squares [<Array<Array<String>>>] the squares of the board
60
+ # @example
61
+ # PGN::Board.new(
62
+ # [
63
+ # ["R", "P", nil, nil, nil, nil, "p", "r"],
64
+ # ["N", "P", nil, nil, nil, nil, "p", "n"],
65
+ # ["B", "P", nil, nil, nil, nil, "p", "b"],
66
+ # ["Q", "P", nil, nil, nil, nil, "p", "q"],
67
+ # ["K", "P", nil, nil, nil, nil, "p", "k"],
68
+ # ["B", "P", nil, nil, nil, nil, "p", "b"],
69
+ # ["N", "P", nil, nil, nil, nil, "p", "n"],
70
+ # ["R", "P", nil, nil, nil, nil, "p", "r"],
71
+ # ]
72
+ # )
73
+ #
74
+ def initialize(squares)
75
+ self.squares = squares
76
+ end
77
+
78
+ # @overload at(str)
79
+ # Looks up a piece based on the string representation of a square (e4)
80
+ # @param str [String] the square in algebraic notation
81
+ # @overload at(file, rank)
82
+ # Looks up a piece based on zero-indexed coordinates (4, 3)
83
+ # @param file [Integer] the file the piece is on
84
+ # @param rank [Integer] the rank the piece is on
85
+ # @return [String, nil] the piece on the square, or nil if it is
86
+ # empty
87
+ # @example
88
+ # board.at(4,3) #=> "P"
89
+ # board.at("e4") #=> "P"
90
+ #
91
+ def at(*args)
92
+ case args.length
93
+ when 1
94
+ at(*coordinates_for(args.first))
95
+ when 2
96
+ squares[args[0]][args[1]]
97
+ end
98
+ end
99
+
100
+ # @param changes [Hash<String, <String, nil>>] changes to make to the board
101
+ # @return [self]
102
+ # @example
103
+ # board.change!({"e2" => nil, "e4" => "P"})
104
+ #
105
+ def change!(changes)
106
+ changes.each do |square, piece|
107
+ update(square, piece)
108
+ end
109
+ self
110
+ end
111
+
112
+ # @param square [String] the square in algebraic notation
113
+ # @param piece [String, nil] the piece to put on the square
114
+ # @return [self]
115
+ # @example
116
+ # board.update("e4", "P")
117
+ #
118
+ def update(square, piece)
119
+ coords = coordinates_for(square)
120
+ squares[coords[0]][coords[1]] = piece
121
+ self
122
+ end
123
+
124
+ # @param position [String] the square in algebraic notation
125
+ # @return [Array<Integer>] the coordinates of the square
126
+ # @example
127
+ # board.coordinates_for("e4") #=> [4, 3]
128
+ #
129
+ def coordinates_for(position)
130
+ file_chr, rank_chr = position.chars.to_a
131
+ file = FILE_TO_INDEX[file_chr]
132
+ rank = RANK_TO_INDEX[rank_chr]
133
+ [file, rank]
134
+ end
135
+
136
+ # @param coordinates [Array<Integer>] the coordinates of the square
137
+ # @return [String] the square in algebraic notation
138
+ # @example
139
+ # board.position_for([4, 3]) #=> "e4"
140
+ #
141
+ def position_for(coordinates)
142
+ file, rank = coordinates
143
+ file_chr = INDEX_TO_FILE[file]
144
+ rank_chr = INDEX_TO_RANK[rank]
145
+ [file_chr, rank_chr].join('')
146
+ end
147
+
148
+ # @return [String] the board in human readable format with unicode
149
+ # pieces
150
+ #
151
+ def inspect
152
+ squares.transpose.reverse.map do |row|
153
+ row.map { |chr| UNICODE_PIECES[chr] }.join(' ')
154
+ end.join("\n")
155
+ end
156
+
157
+ # @return [PGN::Board] a copy of self with duplicated squares
158
+ #
159
+ def dup
160
+ PGN::Board.new(squares.map(&:dup))
161
+ end
162
+ end
163
+ end
data/lib/pgn/fen.rb ADDED
@@ -0,0 +1,148 @@
1
+ module PGN
2
+ # {PGN::FEN} is responsible for translating between strings in FEN
3
+ # notation and an internal representation of the board.
4
+ #
5
+ # @see http://en.wikipedia.org/wiki/Forsyth-Edwards_Notation
6
+ # Forsyth-Edwards notation
7
+ #
8
+ # @!attribute board
9
+ # @return [PGN::Board] a {PGN::Board} object for the current board
10
+ # state
11
+ #
12
+ # @!attribute active
13
+ # @return ['w', 't'] the current player
14
+ #
15
+ # @!attribute castling
16
+ # @return [String] the castling availability
17
+ # @example
18
+ # "Kq" # white can castle kingside and black queenside
19
+ # @example
20
+ # "-" # no one can castle
21
+ #
22
+ # @!attribute en_passant
23
+ # @return [String] the current en passant square
24
+ # @example
25
+ # "e3" # white just moved e2 -> e4
26
+ # "-" # no current en passant square
27
+ #
28
+ # @!attribute halfmove
29
+ # @return [String] the halfmove clock
30
+ # @note This is the number of halfmoves since the last pawn advance or capture
31
+ #
32
+ # @!attribute fullmove
33
+ # @return [String] the fullmove counter
34
+ # @note The number of full moves. This is incremented after black
35
+ # plays.
36
+ #
37
+ class FEN
38
+ # The FEN string representing the starting position in chess
39
+ #
40
+ INITIAL = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"
41
+
42
+ attr_accessor :board, :active, :castling, :en_passant, :halfmove, :fullmove
43
+
44
+ # @return [PGN::FEN] a {PGN::FEN} object representing the starting
45
+ # position
46
+ #
47
+ def self.start
48
+ PGN::FEN.new(INITIAL)
49
+ end
50
+
51
+ # @return [PGN::FEN] a {PGN::FEN} object with the given attributes
52
+ #
53
+ def self.from_attributes(attrs)
54
+ fen = PGN::FEN.new
55
+ attrs.each do |key, val|
56
+ fen.send("#{key}=", val)
57
+ end
58
+ fen
59
+ end
60
+
61
+ # @param fen_string [String] a string in Forsyth-Edwards Notation
62
+ #
63
+ def initialize(fen_string = nil)
64
+ if fen_string
65
+ self.board_string,
66
+ self.active,
67
+ self.castling,
68
+ self.en_passant,
69
+ self.halfmove,
70
+ self.fullmove = fen_string.split
71
+ end
72
+ end
73
+
74
+ def en_passant=(val)
75
+ @en_passant = val.nil? ? "-" : val
76
+ end
77
+
78
+ def castling=(val)
79
+ @castling = (val.nil? || val.empty?) ? "-" : val
80
+ end
81
+
82
+ # @param board_fen [String] the fen representation of the board
83
+ # @example
84
+ # fen.board_string = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR"
85
+ #
86
+ def board_string=(board_fen)
87
+ squares = board_fen.gsub(/\d/) {|match| "_" * match.to_i }
88
+ .split("/")
89
+ .map {|row| row.split('') }
90
+ .map {|row| row.map {|e| e == "_" ? nil : e } }
91
+ .reverse
92
+ .transpose
93
+ self.board = PGN::Board.new(squares)
94
+ end
95
+
96
+ # @return [String] the fen representation of the board
97
+ # @example
98
+ # PGN::FEN.start.board_string #=> "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR"
99
+ #
100
+ def board_string
101
+ self.board
102
+ .squares
103
+ .transpose
104
+ .reverse
105
+ .map {|row| row.map {|e| e.nil? ? "_" : e } }
106
+ .map {|row| row.join }
107
+ .join("/")
108
+ .gsub(/_+/) {|match| match.length }
109
+ end
110
+
111
+ # @return [PGN::Position] a {PGN::Position} representing the current
112
+ # position
113
+ #
114
+ def to_position
115
+ player = self.active == 'w' ? :white : :black
116
+ castling = self.castling.split('') - ['-']
117
+ en_passant = self.en_passant == '-' ? nil : en_passant
118
+
119
+ PGN::Position.new(
120
+ self.board,
121
+ player,
122
+ castling,
123
+ en_passant,
124
+ self.halfmove.to_i,
125
+ self.fullmove.to_i,
126
+ )
127
+ end
128
+
129
+ # @return [String] the FEN string
130
+ # @example
131
+ # PGN::FEN.start.to_s #=> "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"
132
+ #
133
+ def to_s
134
+ [
135
+ self.board_string,
136
+ self.active,
137
+ self.castling,
138
+ self.en_passant,
139
+ self.halfmove,
140
+ self.fullmove,
141
+ ].join(" ")
142
+ end
143
+
144
+ def inspect
145
+ self.to_s
146
+ end
147
+ end
148
+ end
data/lib/pgn/game.rb ADDED
@@ -0,0 +1,148 @@
1
+ require 'io/console'
2
+
3
+ module PGN
4
+ class MoveText
5
+ attr_accessor :notation, :annotation, :comment, :variations
6
+
7
+ def initialize(notation, annotation = nil, comment = nil, variations = [])
8
+ @notation = notation
9
+ @annotation = annotation
10
+ @comment = clean_text(comment)
11
+ @variations = variations
12
+ end
13
+
14
+ def ==(m)
15
+ to_s == m.to_s
16
+ end
17
+
18
+ def eql?(m)
19
+ self == m
20
+ end
21
+
22
+ def hash
23
+ @notation.hash
24
+ end
25
+
26
+ def to_s
27
+ @notation
28
+ end
29
+
30
+ def clean_text(text)
31
+ text&.gsub(/{(.*)}/, '\1')&.gsub(/\s+/, ' ')&.strip
32
+ end
33
+ end
34
+ # {PGN::Game} holds all of the information about a game. It is either
35
+ # the result of parsing a PGN file, or created by hand.
36
+ #
37
+ # A {PGN::Game} has an interactive {#play} method, and can also return
38
+ # a list of positions in {PGN::Position} format or FEN.
39
+ #
40
+ # @!attribute tags
41
+ # @return [Hash<String, String>] metadata about the game
42
+ # @example
43
+ # game.tags #=> {"White" => "Kasparov", "Black" => "Deep Blue"}
44
+ #
45
+ # @!attribute moves
46
+ # @return [Array<String>] a list of the moves in standard algebraic
47
+ # notation
48
+ # @example
49
+ # game.moves #=> ["e4", "c5", "Nf3", "d6", "d4", "cxd4"]
50
+ #
51
+ # @!attribute result
52
+ # @return [String] the outcome of the game
53
+ # @example
54
+ # game.result #=> "1-0"
55
+ #
56
+ class Game
57
+ attr_accessor :tags, :result, :pgn, :comment
58
+ attr_reader :moves
59
+
60
+ LEFT = /(a|\x1B\[D)\z/.freeze
61
+ RIGHT = /(d|\x1B\[C)\z/.freeze
62
+ EXIT = /(q|\x03)\z/.freeze
63
+
64
+ # @param moves [Array<String>] a list of moves in SAN
65
+ # @param tags [Hash<String, String>] metadata about the game
66
+ # @param result [String] the outcome of the game
67
+ #
68
+ def initialize(moves, tags = nil, result = nil, pgn = nil, comment = nil)
69
+ self.moves = moves
70
+ self.tags = tags
71
+ self.result = result
72
+ self.pgn = pgn
73
+ self.comment = comment
74
+ end
75
+
76
+ # @param moves [Array<String>] a list of moves in SAN
77
+ #
78
+ # Standardize castling moves to use O's instead of 0's
79
+ #
80
+ def moves=(moves)
81
+ @moves =
82
+ moves.map do |m|
83
+ if m.is_a? String
84
+ MoveText.new(m.gsub('0', 'O'))
85
+ else
86
+ MoveText.new(m.notation.gsub('0', 'O'), m.annotation, m.comment, m.variations)
87
+ end
88
+ end
89
+ end
90
+
91
+ def initial_fen
92
+ tags && tags['FEN']
93
+ end
94
+
95
+ def starting_position
96
+ @starting_position ||= if initial_fen
97
+ PGN::FEN.new(initial_fen).to_position
98
+ else
99
+ PGN::Position.start
100
+ end
101
+ end
102
+
103
+ # @return [Array<PGN::Position>] list of the {PGN::Position}s in the game
104
+ #
105
+ def positions
106
+ @positions ||= begin
107
+ position = starting_position
108
+ arr = [position]
109
+ moves.each do |move|
110
+ new_pos = position.move(move.notation)
111
+ arr << new_pos
112
+ position = new_pos
113
+ end
114
+ arr
115
+ end
116
+ end
117
+
118
+ # @return [Array<String>] list of the fen representations of the positions
119
+ #
120
+ def fen_list
121
+ positions.map { |p| p.to_fen.inspect }
122
+ end
123
+
124
+ # Interactively step through the game
125
+ #
126
+ # Use +d+ to move forward, +a+ to move backward, and +^C+ to exit.
127
+ #
128
+ def play
129
+ index = 0
130
+ hist = Array.new(3, '')
131
+
132
+ loop do
133
+ puts "\e[H\e[2J"
134
+ puts positions[index].inspect
135
+ hist[0..2] = (hist[1..2] << STDIN.getch)
136
+
137
+ case hist.join
138
+ when LEFT
139
+ index -= 1 if index > 0
140
+ when RIGHT
141
+ index += 1 if index < moves.length
142
+ when EXIT
143
+ break
144
+ end
145
+ end
146
+ end
147
+ end
148
+ end