pgn 0.0.1

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
+ SHA1:
3
+ metadata.gz: b151e9dfbad65addcdbcc73985f280a1b7b42a55
4
+ data.tar.gz: 1aecd46315e114eec514ff627c442dd559bf5237
5
+ SHA512:
6
+ metadata.gz: 4bb8e0a0e1ca54fb4fb4644e96a68ef3162a16c3dfc6e602993fe6e195dd1fcbe9b31e42239a61f608ca15698ff126a51ea5f0f202433e83aa52e2b5cd92e8d5
7
+ data.tar.gz: 6f070630bebabc25cd9261819cd2beb0d3d7a0309dcba56244f6712fcd339fab0db154c1ae785b3d33ca1e3f1888576d0b4143d7ddd8d1fe52f7941dd585ee84
data/.gitignore ADDED
@@ -0,0 +1,17 @@
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
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,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,4 @@
1
+ # TODOs
2
+
3
+ - `Game#to_pgn`
4
+ - 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.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