pgn 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +122 -0
- data/Rakefile +1 -0
- data/TODO.md +4 -0
- data/examples/immortal_game.pgn +17 -0
- data/lib/pgn.rb +22 -0
- data/lib/pgn/board.rb +183 -0
- data/lib/pgn/fen.rb +146 -0
- data/lib/pgn/game.rb +82 -0
- data/lib/pgn/move.rb +167 -0
- data/lib/pgn/move_calculator.rb +339 -0
- data/lib/pgn/parser.rb +119 -0
- data/lib/pgn/position.rb +129 -0
- data/lib/pgn/version.rb +3 -0
- data/pgn.gemspec +26 -0
- data/spec/fen_spec.rb +87 -0
- data/spec/game_spec.rb +13 -0
- data/spec/parser_spec.rb +14 -0
- data/spec/position_spec.rb +44 -0
- data/spec/spec_helper.rb +19 -0
- metadata +129 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: b151e9dfbad65addcdbcc73985f280a1b7b42a55
|
4
|
+
data.tar.gz: 1aecd46315e114eec514ff627c442dd559bf5237
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 4bb8e0a0e1ca54fb4fb4644e96a68ef3162a16c3dfc6e602993fe6e195dd1fcbe9b31e42239a61f608ca15698ff126a51ea5f0f202433e83aa52e2b5cd92e8d5
|
7
|
+
data.tar.gz: 6f070630bebabc25cd9261819cd2beb0d3d7a0309dcba56244f6712fcd339fab0db154c1ae785b3d33ca1e3f1888576d0b4143d7ddd8d1fe52f7941dd585ee84
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/Gemfile
ADDED
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,122 @@
|
|
1
|
+
# PGN
|
2
|
+
|
3
|
+
A PGN parser and FEN generator for ruby.
|
4
|
+
|
5
|
+
## Usage
|
6
|
+
|
7
|
+
### Creating games from pgn files
|
8
|
+
|
9
|
+
On the command line, it is easy to read in and play through chess games
|
10
|
+
in [portable game notation](http://en.wikipedia.org/wiki/Portable_Game_Notation) format.
|
11
|
+
|
12
|
+
```
|
13
|
+
> games = PGN.parse(File.read("./examples/immortal_game.pgn"))
|
14
|
+
> game = games.first
|
15
|
+
> game.play
|
16
|
+
```
|
17
|
+
|
18
|
+
Play through the game using `a` to move backward and `d` to move
|
19
|
+
forward. `^C` quits play mode.
|
20
|
+
|
21
|
+
♜ ♞ ♝ ♛ ♚ ♝ ♞ ♜
|
22
|
+
♟ ♟ ♟ ♟ ♟ ♟ ♟ ♟
|
23
|
+
_ _ _ _ _ _ _ _
|
24
|
+
_ _ _ _ _ _ _ _
|
25
|
+
_ _ _ _ _ _ _ _
|
26
|
+
_ _ _ _ _ _ _ _
|
27
|
+
♙ ♙ ♙ ♙ ♙ ♙ ♙ ♙
|
28
|
+
♖ ♘ ♗ ♕ ♔ ♗ ♘ ♖
|
29
|
+
|
30
|
+
♜ ♞ ♝ ♛ ♚ ♝ ♞ ♜
|
31
|
+
♟ ♟ ♟ ♟ ♟ ♟ ♟ ♟
|
32
|
+
_ _ _ _ _ _ _ _
|
33
|
+
_ _ _ _ _ _ _ _
|
34
|
+
_ _ _ _ _ _ _ _
|
35
|
+
_ _ _ _ ♙ _ _ _
|
36
|
+
_ _ _ _ _ _ _ _
|
37
|
+
♙ ♙ ♙ ♙ _ ♙ ♙ ♙
|
38
|
+
♖ ♘ ♗ ♕ ♔ ♗ ♘ ♖
|
39
|
+
|
40
|
+
...
|
41
|
+
|
42
|
+
You can also access all of the information about a game.
|
43
|
+
|
44
|
+
```
|
45
|
+
> game.positions.last
|
46
|
+
=>
|
47
|
+
♜ _ ♝ ♚ _ _ _ ♜
|
48
|
+
♟ _ _ ♟ ♗ ♟ ♘ ♟
|
49
|
+
♞ _ _ _ _ ♞ _ _
|
50
|
+
_ ♟ _ ♘ ♙ _ _ ♙
|
51
|
+
_ _ _ _ _ _ ♙ _
|
52
|
+
_ _ _ ♙ _ _ _ _
|
53
|
+
♙ _ ♙ _ ♔ _ _ _
|
54
|
+
♛ _ _ _ _ _ ♝ _
|
55
|
+
|
56
|
+
> game.positions.last.to_fen
|
57
|
+
=> r1bk3r/p2pBpNp/n4n2/1p1NP2P/6P1/3P4/P1P1K3/q5b1 b - - 1 22
|
58
|
+
|
59
|
+
> game.result
|
60
|
+
=> "1-0"
|
61
|
+
|
62
|
+
> game.tags["White"]
|
63
|
+
=> "Adolf Anderssen"
|
64
|
+
```
|
65
|
+
|
66
|
+
It is possible to create a game without parsing a pgn file.
|
67
|
+
|
68
|
+
```
|
69
|
+
moves = %w{e4 c5 c3 d5 exd5 Qxd5 d4 Nf6}
|
70
|
+
game = PGN::Game.new(moves)
|
71
|
+
```
|
72
|
+
|
73
|
+
Note that if you simply want an abstract syntax tree from the pgn file,
|
74
|
+
you can use `PGN::Parser.parse`.
|
75
|
+
|
76
|
+
### Dealing with FEN strings
|
77
|
+
|
78
|
+
[Forsyth Edwards Notation](http://en.wikipedia.org/wiki/Forsyth%E2%80%93Edwards_Notation)
|
79
|
+
is a compact way to represent all of the information about a given chess
|
80
|
+
position. It is easy to convert between FEN strings and chess positions.
|
81
|
+
|
82
|
+
```
|
83
|
+
> fen = PGN::FEN.start
|
84
|
+
=> rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1
|
85
|
+
|
86
|
+
> fen = PGN::FEN.new("r1bk3r/p2pBpNp/n4n2/1p1NP2P/6P1/3P4/P1P1K3/q5b1 b - - 1 22")
|
87
|
+
> position = fen.to_position
|
88
|
+
=>
|
89
|
+
♜ _ ♝ ♚ _ _ _ ♜
|
90
|
+
♟ _ _ ♟ ♗ ♟ ♘ ♟
|
91
|
+
♞ _ _ _ _ ♞ _ _
|
92
|
+
_ ♟ _ ♘ ♙ _ _ ♙
|
93
|
+
_ _ _ _ _ _ ♙ _
|
94
|
+
_ _ _ ♙ _ _ _ _
|
95
|
+
♙ _ ♙ _ ♔ _ _ _
|
96
|
+
♛ _ _ _ _ _ ♝ _
|
97
|
+
|
98
|
+
> position.to_fen
|
99
|
+
=> r1bk3r/p2pBpNp/n4n2/1p1NP2P/6P1/3P4/P1P1K3/q5b1 b - - 1 22
|
100
|
+
```
|
101
|
+
|
102
|
+
## Installation
|
103
|
+
|
104
|
+
Add this line to your application's Gemfile:
|
105
|
+
|
106
|
+
gem 'pgn'
|
107
|
+
|
108
|
+
And then execute:
|
109
|
+
|
110
|
+
$ bundle
|
111
|
+
|
112
|
+
Or install it yourself as:
|
113
|
+
|
114
|
+
$ gem install pgn
|
115
|
+
|
116
|
+
## Contributing
|
117
|
+
|
118
|
+
1. Fork it
|
119
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
120
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
121
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
122
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/TODO.md
ADDED
@@ -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.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require "pgn/board"
|
2
|
+
require "pgn/fen"
|
3
|
+
require "pgn/game"
|
4
|
+
require "pgn/move"
|
5
|
+
require "pgn/move_calculator"
|
6
|
+
require "pgn/parser"
|
7
|
+
require "pgn/position"
|
8
|
+
require "pgn/version"
|
9
|
+
|
10
|
+
module PGN
|
11
|
+
|
12
|
+
# @param pgn [String] a pgn representation of one or more chess games
|
13
|
+
# @return [Array<PGN::Game>] a list of games
|
14
|
+
#
|
15
|
+
def self.parse(pgn)
|
16
|
+
pgn.force_encoding(Encoding::ISO_8859_1)
|
17
|
+
|
18
|
+
PGN::Parser.new.parse(pgn).map do |game|
|
19
|
+
PGN::Game.new(game[:moves], game[:tags], game[:result])
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/pgn/board.rb
ADDED
@@ -0,0 +1,183 @@
|
|
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
|
+
class Board
|
13
|
+
# The starting, internal representation of a chess board
|
14
|
+
#
|
15
|
+
START = [
|
16
|
+
["R", "P", "_", "_", "_", "_", "p", "r"],
|
17
|
+
["N", "P", "_", "_", "_", "_", "p", "n"],
|
18
|
+
["B", "P", "_", "_", "_", "_", "p", "b"],
|
19
|
+
["Q", "P", "_", "_", "_", "_", "p", "q"],
|
20
|
+
["K", "P", "_", "_", "_", "_", "p", "k"],
|
21
|
+
["B", "P", "_", "_", "_", "_", "p", "b"],
|
22
|
+
["N", "P", "_", "_", "_", "_", "p", "n"],
|
23
|
+
["R", "P", "_", "_", "_", "_", "p", "r"],
|
24
|
+
]
|
25
|
+
|
26
|
+
FILE_TO_INDEX = {
|
27
|
+
'a' => 0,
|
28
|
+
'b' => 1,
|
29
|
+
'c' => 2,
|
30
|
+
'd' => 3,
|
31
|
+
'e' => 4,
|
32
|
+
'f' => 5,
|
33
|
+
'g' => 6,
|
34
|
+
'h' => 7,
|
35
|
+
}
|
36
|
+
INDEX_TO_FILE = Hash[FILE_TO_INDEX.map(&:reverse)]
|
37
|
+
|
38
|
+
RANK_TO_INDEX = {
|
39
|
+
'1' => 0,
|
40
|
+
'2' => 1,
|
41
|
+
'3' => 2,
|
42
|
+
'4' => 3,
|
43
|
+
'5' => 4,
|
44
|
+
'6' => 5,
|
45
|
+
'7' => 6,
|
46
|
+
'8' => 7,
|
47
|
+
}
|
48
|
+
INDEX_TO_RANK = Hash[RANK_TO_INDEX.map(&:reverse)]
|
49
|
+
|
50
|
+
# algebraic to unicode piece lookup
|
51
|
+
#
|
52
|
+
UNICODE_PIECES = {
|
53
|
+
'k' => "\u{265A}",
|
54
|
+
'q' => "\u{265B}",
|
55
|
+
'r' => "\u{265C}",
|
56
|
+
'b' => "\u{265D}",
|
57
|
+
'n' => "\u{265E}",
|
58
|
+
'p' => "\u{265F}",
|
59
|
+
'K' => "\u{2654}",
|
60
|
+
'Q' => "\u{2655}",
|
61
|
+
'R' => "\u{2656}",
|
62
|
+
'B' => "\u{2657}",
|
63
|
+
'N' => "\u{2658}",
|
64
|
+
'P' => "\u{2659}",
|
65
|
+
'_' => '_',
|
66
|
+
}
|
67
|
+
|
68
|
+
attr_accessor :squares
|
69
|
+
|
70
|
+
# @return [PGN::Board] a board in the starting position
|
71
|
+
#
|
72
|
+
def self.start
|
73
|
+
PGN::Board.new(START)
|
74
|
+
end
|
75
|
+
|
76
|
+
# @param squares [<Array<Array<String>>>] the squares of the board
|
77
|
+
# @example
|
78
|
+
# PGN::Board.new(
|
79
|
+
# [
|
80
|
+
# ["R", "P", "_", "_", "_", "_", "p", "r"],
|
81
|
+
# ["N", "P", "_", "_", "_", "_", "p", "n"],
|
82
|
+
# ["B", "P", "_", "_", "_", "_", "p", "b"],
|
83
|
+
# ["Q", "P", "_", "_", "_", "_", "p", "q"],
|
84
|
+
# ["K", "P", "_", "_", "_", "_", "p", "k"],
|
85
|
+
# ["B", "P", "_", "_", "_", "_", "p", "b"],
|
86
|
+
# ["N", "P", "_", "_", "_", "_", "p", "n"],
|
87
|
+
# ["R", "P", "_", "_", "_", "_", "p", "r"],
|
88
|
+
# ]
|
89
|
+
# )
|
90
|
+
#
|
91
|
+
def initialize(squares)
|
92
|
+
self.squares = squares
|
93
|
+
end
|
94
|
+
|
95
|
+
# @overload at(str)
|
96
|
+
# Looks up a piece based on the string representation of a square (e4)
|
97
|
+
# @param str [String] the square in algebraic notation
|
98
|
+
# @overload at(file, rank)
|
99
|
+
# Looks up a piece based on zero-indexed coordinates (4, 3)
|
100
|
+
# @param file [Integer] the file the piece is on
|
101
|
+
# @param rank [Integer] the rank the piece is on
|
102
|
+
# @return [String, nil] the piece on the square, or nil if it is
|
103
|
+
# empty
|
104
|
+
# @example
|
105
|
+
# board.at(4,3) #=> "P"
|
106
|
+
# board.at("e4") #=> "P"
|
107
|
+
#
|
108
|
+
def at(*args)
|
109
|
+
str = case args.length
|
110
|
+
when 1
|
111
|
+
self.at(*coordinates_for(args.first))
|
112
|
+
when 2
|
113
|
+
self.squares[args[0]][args[1]]
|
114
|
+
end
|
115
|
+
|
116
|
+
str == "_" ? nil : str
|
117
|
+
end
|
118
|
+
|
119
|
+
# @param changes [Hash<String, <String, nil>>] changes to make to the board
|
120
|
+
# @return [self]
|
121
|
+
# @example
|
122
|
+
# board.change!({"e2" => nil, "e4" => "P"})
|
123
|
+
#
|
124
|
+
def change!(changes)
|
125
|
+
changes.each do |square, piece|
|
126
|
+
self.update(square, piece)
|
127
|
+
end
|
128
|
+
self
|
129
|
+
end
|
130
|
+
|
131
|
+
# @param square [String] the square in algebraic notation
|
132
|
+
# @param piece [String, nil] the piece to put on the square
|
133
|
+
# @return [self]
|
134
|
+
# @example
|
135
|
+
# board.update("e4", "P")
|
136
|
+
#
|
137
|
+
def update(square, piece)
|
138
|
+
coords = coordinates_for(square)
|
139
|
+
self.squares[coords[0]][coords[1]] = piece || "_"
|
140
|
+
self
|
141
|
+
end
|
142
|
+
|
143
|
+
# @param position [String] the square in algebraic notation
|
144
|
+
# @return [Array<Integer>] the coordinates of the square
|
145
|
+
# @example
|
146
|
+
# board.coordinates_for("e4") #=> [4, 3]
|
147
|
+
#
|
148
|
+
def coordinates_for(position)
|
149
|
+
file_chr, rank_chr = position.chars
|
150
|
+
file = FILE_TO_INDEX[file_chr]
|
151
|
+
rank = RANK_TO_INDEX[rank_chr]
|
152
|
+
[file, rank]
|
153
|
+
end
|
154
|
+
|
155
|
+
# @param coordinates [Array<Integer>] the coordinates of the square
|
156
|
+
# @return [String] the square in algebraic notation
|
157
|
+
# @example
|
158
|
+
# board.position_for([4, 3]) #=> "e4"
|
159
|
+
#
|
160
|
+
def position_for(coordinates)
|
161
|
+
file, rank = coordinates
|
162
|
+
file_chr = INDEX_TO_FILE[file]
|
163
|
+
rank_chr = INDEX_TO_RANK[rank]
|
164
|
+
[file_chr, rank_chr].join('')
|
165
|
+
end
|
166
|
+
|
167
|
+
# @return [String] the board in human readable format with unicode
|
168
|
+
# pieces
|
169
|
+
#
|
170
|
+
def inspect
|
171
|
+
self.squares.transpose.reverse.map do |row|
|
172
|
+
row.map{|chr| UNICODE_PIECES[chr] }.join(' ')
|
173
|
+
end.join("\n")
|
174
|
+
end
|
175
|
+
|
176
|
+
# @return [PGN::Board] a copy of self with duplicated squares
|
177
|
+
#
|
178
|
+
def dup
|
179
|
+
PGN::Board.new(self.squares.map(&:dup))
|
180
|
+
end
|
181
|
+
|
182
|
+
end
|
183
|
+
end
|
data/lib/pgn/fen.rb
ADDED
@@ -0,0 +1,146 @@
|
|
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
|
+
.reverse
|
91
|
+
.transpose
|
92
|
+
self.board = PGN::Board.new(squares)
|
93
|
+
end
|
94
|
+
|
95
|
+
# @return [String] the fen representation of the board
|
96
|
+
# @example
|
97
|
+
# PGN::FEN.start.board_string #=> "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR"
|
98
|
+
#
|
99
|
+
def board_string
|
100
|
+
self.board
|
101
|
+
.squares
|
102
|
+
.transpose
|
103
|
+
.reverse
|
104
|
+
.map {|row| row.join }
|
105
|
+
.join("/")
|
106
|
+
.gsub(/_+/) {|match| match.length }
|
107
|
+
end
|
108
|
+
|
109
|
+
# @return [PGN::Position] a {PGN::Position} representing the current
|
110
|
+
# position
|
111
|
+
#
|
112
|
+
def to_position
|
113
|
+
player = self.active == 'w' ? :white : :black
|
114
|
+
castling = self.castling.split('') - ['-']
|
115
|
+
en_passant = self.en_passant == '-' ? nil : en_passant
|
116
|
+
|
117
|
+
PGN::Position.new(
|
118
|
+
self.board,
|
119
|
+
player,
|
120
|
+
castling: castling,
|
121
|
+
en_passant: en_passant,
|
122
|
+
halfmove: self.halfmove.to_i,
|
123
|
+
fullmove: self.fullmove.to_i,
|
124
|
+
)
|
125
|
+
end
|
126
|
+
|
127
|
+
# @return [String] the FEN string
|
128
|
+
# @example
|
129
|
+
# PGN::FEN.start.to_s #=> "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"
|
130
|
+
#
|
131
|
+
def to_s
|
132
|
+
[
|
133
|
+
self.fen_string,
|
134
|
+
self.active,
|
135
|
+
self.castling,
|
136
|
+
self.en_passant,
|
137
|
+
self.halfmove,
|
138
|
+
self.fullmove,
|
139
|
+
].join(" ")
|
140
|
+
end
|
141
|
+
|
142
|
+
def inspect
|
143
|
+
self.to_s
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|