pgn3 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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