chess 0.3.0 → 0.3.3
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 +4 -4
- data/.github/FUNDING.yml +12 -0
- data/.github/workflows/ruby.yml +58 -0
- data/.gitignore +3 -2
- data/.rubocop.yml +10 -142
- data/Gemfile.lock +74 -0
- data/README.md +26 -22
- data/Rakefile +2 -1
- data/chess.gemspec +11 -7
- data/docs/Chess/BadNotationError.html +237 -0
- data/docs/Chess/Board.html +1759 -0
- data/docs/Chess/CGame.html +2296 -0
- data/docs/Chess/Game.html +1277 -0
- data/docs/Chess/Gnuchess.html +366 -0
- data/docs/Chess/IllegalMoveError.html +137 -0
- data/docs/Chess/InvalidFenFormatError.html +237 -0
- data/docs/Chess/InvalidPgnFormatError.html +217 -0
- data/docs/Chess/Pgn.html +1477 -0
- data/docs/Chess/UTF8Notation.html +270 -0
- data/docs/Chess.html +157 -0
- data/docs/_index.html +217 -0
- data/docs/class_list.html +51 -0
- data/docs/css/common.css +1 -0
- data/docs/css/full_list.css +58 -0
- data/docs/css/style.css +497 -0
- data/docs/file.README.html +116 -0
- data/docs/file_list.html +56 -0
- data/docs/frames.html +17 -0
- data/docs/index.html +116 -0
- data/docs/js/app.js +314 -0
- data/docs/js/full_list.js +216 -0
- data/docs/js/jquery.js +4 -0
- data/docs/method_list.html +531 -0
- data/docs/top-level-namespace.html +110 -0
- data/ext/chess.c +1 -1
- data/ext/common.h +7 -3
- data/ext/extconf.rb +1 -1
- data/lib/chess/game.rb +68 -66
- data/lib/chess/gnuchess.rb +49 -53
- data/lib/chess/pgn.rb +13 -15
- data/lib/chess/version.rb +1 -1
- data/test/test_big_pgn_collection.rb +1 -1
- data/test/test_checkmate.rb +4 -4
- data/test/test_errors.rb +22 -0
- data/test/test_fifty_rule_move.rb +2 -2
- data/test/test_game.rb +82 -0
- data/test/test_helper.rb +15 -0
- data/test/test_insufficient_material.rb +4 -4
- data/test/test_move_generator.rb +3 -2
- data/test/test_pgn.rb +35 -0
- data/test/test_pgn_collection.rb +2 -2
- data/test/test_stalemate.rb +1 -1
- data/test/test_threefold_repetition.rb +1 -1
- data/test/test_uci_castling.rb +34 -0
- metadata +118 -1236
data/lib/chess/game.rb
CHANGED
@@ -7,6 +7,7 @@ module Chess
|
|
7
7
|
# @raise [IllegalMoveError]
|
8
8
|
# @raise [BadNotationError]
|
9
9
|
def initialize(moves = [])
|
10
|
+
super()
|
10
11
|
moves.each { |m| move(m) }
|
11
12
|
end
|
12
13
|
|
@@ -20,7 +21,7 @@ module Chess
|
|
20
21
|
pgn = Chess::Pgn.new(file)
|
21
22
|
game = Chess::Game.new
|
22
23
|
pgn.moves.each { |m| game.move(m) }
|
23
|
-
|
24
|
+
unless game.over?
|
24
25
|
case pgn.result
|
25
26
|
when '1-0'
|
26
27
|
game.resign(:black)
|
@@ -39,13 +40,11 @@ module Chess
|
|
39
40
|
# @raise [InvalidFenFormatError]
|
40
41
|
# @note This game do not have history before the FEN placement.
|
41
42
|
def self.load_fen(fen)
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
raise InvalidFenFormatError.new(fen)
|
48
|
-
end
|
43
|
+
raise InvalidFenFormatError.new(fen) unless /^((?:[PRNBQKprnbqk1-8]{1,8}\/){7}[RNBQKPrnbqkp1-8]{1,8})\s(w|b)\s(K?Q?k?q?|-)\s([a-h][1-8]|-)\s(\d+)\s(\d+)$/.match?(fen)
|
44
|
+
|
45
|
+
game = Chess::Game.new
|
46
|
+
game.set_fen!(fen)
|
47
|
+
return game
|
49
48
|
end
|
50
49
|
|
51
50
|
# Make a move.
|
@@ -67,11 +66,9 @@ module Chess
|
|
67
66
|
super(expand[:name], expand[:dis], expand[:to], expand[:promotion])
|
68
67
|
end
|
69
68
|
rescue IllegalMoveError
|
70
|
-
if ENV['DEBUG']
|
71
|
-
|
72
|
-
|
73
|
-
raise IllegalMoveError.new("Illegal move '#{notation}'")
|
74
|
-
end
|
69
|
+
raise IllegalMoveError.new("Illegal move '#{notation}'\nStatus: #{self.status}\nPlayer turn #{self.active_player}\n#{self}") if ENV['DEBUG']
|
70
|
+
|
71
|
+
raise IllegalMoveError.new("Illegal move '#{notation}'")
|
75
72
|
end
|
76
73
|
alias move= move
|
77
74
|
alias << move
|
@@ -106,38 +103,28 @@ module Chess
|
|
106
103
|
# * `black_won_resign`: black player has won for resign.
|
107
104
|
# * `stalemate`: draw for stalemate.
|
108
105
|
# * `insufficient_material`: draw for insufficient material to checkmate.
|
109
|
-
# * `
|
110
|
-
# * `threefold_repetition`: draw for
|
111
|
-
#
|
112
|
-
# @return [String]
|
106
|
+
# * `fifty_move_rule`: draw for fifty-move rule.
|
107
|
+
# * `threefold_repetition`: draw for threefold repetition.
|
108
|
+
# @return [Symbol]
|
113
109
|
def status
|
114
110
|
case self.result
|
115
111
|
when '*'
|
116
112
|
return :in_progress
|
117
113
|
when '1-0'
|
118
|
-
if self.board.checkmate?
|
119
|
-
|
120
|
-
|
121
|
-
return :white_won_resign
|
122
|
-
end
|
114
|
+
return :white_won if self.board.checkmate?
|
115
|
+
|
116
|
+
return :white_won_resign
|
123
117
|
when '0-1'
|
124
|
-
if self.board.checkmate?
|
125
|
-
|
126
|
-
|
127
|
-
return :black_won_resign
|
128
|
-
end
|
118
|
+
return :black_won if self.board.checkmate?
|
119
|
+
|
120
|
+
return :black_won_resign
|
129
121
|
when '1/2-1/2'
|
130
|
-
if self.board.stalemate?
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
return :fifty_rule_move
|
136
|
-
elsif self.threefold_repetition?
|
137
|
-
return :threefold_repetition
|
138
|
-
end
|
122
|
+
return :stalemate if self.board.stalemate?
|
123
|
+
return :insufficient_material if self.board.insufficient_material?
|
124
|
+
return :fifty_move_rule if self.board.fifty_move_rule?
|
125
|
+
|
126
|
+
return :threefold_repetition if self.threefold_repetition?
|
139
127
|
end
|
140
|
-
return :unknown
|
141
128
|
end
|
142
129
|
|
143
130
|
# Returns `true` if the game is over.
|
@@ -146,7 +133,7 @@ module Chess
|
|
146
133
|
end
|
147
134
|
|
148
135
|
# Returns the PGN rappresenting the game.
|
149
|
-
# @return [
|
136
|
+
# @return [Chess::Pgn]
|
150
137
|
def pgn
|
151
138
|
pgn = Chess::Pgn.new
|
152
139
|
pgn.moves = self.moves
|
@@ -156,37 +143,52 @@ module Chess
|
|
156
143
|
|
157
144
|
private
|
158
145
|
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
if
|
180
|
-
return { name: 'K', dis: nil, from: 'e8', to: 'c8', promotion: nil }
|
181
|
-
else # white king long castling
|
182
|
-
return { name: 'K', dis: nil, from: 'e1', to: 'c1', promotion: nil }
|
183
|
-
end
|
146
|
+
# Expand the short algebraic chess notation string `notation` in a hash like this:
|
147
|
+
#
|
148
|
+
# Ngxe2 ==> { name: 'N', dis: 'g', from: nil, to: 'e2', promotion: nil }
|
149
|
+
def expand_move(notation)
|
150
|
+
if (match = notation.match(MOVE_REGEXP))
|
151
|
+
expand = {
|
152
|
+
name: match[1] || 'P', # Piece name [RNBQK]
|
153
|
+
dis: match[2], # Disambiguating move
|
154
|
+
to: match[3], # Move to
|
155
|
+
promotion: match[4] # Promote with
|
156
|
+
}
|
157
|
+
expand[:from] = match[2] if match[2] && match[2].size == 2
|
158
|
+
|
159
|
+
# Support UCI protocol (Lichess)
|
160
|
+
case expand[:from]
|
161
|
+
when 'e1'
|
162
|
+
expand[:to] = 'g1' if expand[:to] == 'h1' # UCI protocol (Lichess) white king short castling
|
163
|
+
expand[:to] = 'c1' if expand[:to] == 'a1' # UCI protocol (Lichess) white king long castling
|
164
|
+
when 'e8'
|
165
|
+
expand[:to] = 'g8' if expand[:to] == 'h8' # UCI protocol (Lichess) black king short castling
|
166
|
+
expand[:to] = 'c8' if expand[:to] == 'a8' # UCI protocol (Lichess) black king long castling
|
184
167
|
end
|
185
|
-
|
168
|
+
|
169
|
+
return expand
|
170
|
+
end
|
171
|
+
|
172
|
+
# Castling notation
|
173
|
+
if SHORT_CASTLING_REGEXP.match?(notation)
|
174
|
+
return { name: 'K', dis: nil, from: 'e8', to: 'g8', promotion: nil } if self.board.active_color # black king short castling
|
175
|
+
|
176
|
+
return { name: 'K', dis: nil, from: 'e1', to: 'g1', promotion: nil } # white king short castling
|
177
|
+
end
|
178
|
+
if LONG_CASTLING_REGEXP.match?(notation)
|
179
|
+
return { name: 'K', dis: nil, from: 'e8', to: 'c8', promotion: nil } if self.board.active_color # black king long castling
|
180
|
+
|
181
|
+
return { name: 'K', dis: nil, from: 'e1', to: 'c1', promotion: nil } # white king long castling
|
186
182
|
end
|
183
|
+
|
184
|
+
raise BadNotationError.new(notation)
|
185
|
+
end
|
187
186
|
end
|
188
187
|
|
189
188
|
MOVE_REGEXP = /^([RNBQK])?([a-h]|[1-8]|[a-h][1-8])?(?:x)?([a-h][1-8])(?:=?([RrNnBbQq]))?(?:ep)?(?:\+|\#)?$/.freeze
|
190
|
-
SHORT_CASTLING_REGEXP = /^([0O])-([0O])([
|
191
|
-
LONG_CASTLING_REGEXP = /^([0O])-([0O])-([0O])([
|
189
|
+
SHORT_CASTLING_REGEXP = /^([0O])-([0O])([+#])?$/.freeze
|
190
|
+
LONG_CASTLING_REGEXP = /^([0O])-([0O])-([0O])([+#])?$/.freeze
|
191
|
+
private_constant :MOVE_REGEXP
|
192
|
+
private_constant :SHORT_CASTLING_REGEXP
|
193
|
+
private_constant :LONG_CASTLING_REGEXP
|
192
194
|
end
|
data/lib/chess/gnuchess.rb
CHANGED
@@ -46,69 +46,65 @@ module Chess
|
|
46
46
|
class << self
|
47
47
|
private
|
48
48
|
|
49
|
-
|
50
|
-
|
51
|
-
|
49
|
+
def included(_mod)
|
50
|
+
raise_if_gnuchess_is_not_installed
|
51
|
+
end
|
52
52
|
|
53
|
-
|
54
|
-
|
55
|
-
|
53
|
+
def extended(_mod)
|
54
|
+
raise_if_gnuchess_is_not_installed
|
55
|
+
end
|
56
56
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
end
|
62
|
-
end
|
57
|
+
# Raise an exception if Gnuchess is not installed
|
58
|
+
def raise_if_gnuchess_is_not_installed
|
59
|
+
raise 'You must install Gnuchess to use the module Chess::Gnuchess!' unless find_executable0('gnuchess')
|
60
|
+
end
|
63
61
|
end
|
64
62
|
|
65
63
|
private
|
66
64
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
when 'Draw by insufficient material'
|
99
|
-
done = :insufficient_material
|
100
|
-
end
|
101
|
-
break
|
65
|
+
def gen_pgn(file_to_save, moves = [])
|
66
|
+
done = false
|
67
|
+
pipe = IO.popen('gnuchess', 'r+')
|
68
|
+
begin
|
69
|
+
pipe.write("depth 1\n")
|
70
|
+
pipe.write("force\n")
|
71
|
+
pipe.write("name chess.rb\n")
|
72
|
+
moves.each do |move|
|
73
|
+
pipe.write("#{move}\n")
|
74
|
+
end
|
75
|
+
until done
|
76
|
+
pipe.write("go\n")
|
77
|
+
while (line = pipe.gets)
|
78
|
+
break if /My move is : /.match?(line) || / : resign/.match?(line)
|
79
|
+
|
80
|
+
if / : 1-0 {White mates}/.match?(line)
|
81
|
+
done = :white_won
|
82
|
+
break
|
83
|
+
elsif / : 0-1 {Black mates}/.match?(line)
|
84
|
+
done = :black_won
|
85
|
+
break
|
86
|
+
elsif (m = line.match(/1\/2-1\/2 {(.*?)}/))
|
87
|
+
case m[1]
|
88
|
+
when 'Stalemate'
|
89
|
+
done = :stalemate
|
90
|
+
when 'Draw by repetition'
|
91
|
+
done = :repetition
|
92
|
+
when 'Draw by fifty-move rule'
|
93
|
+
done = :fifty_move_rule
|
94
|
+
when 'Draw by insufficient material'
|
95
|
+
done = :insufficient_material
|
102
96
|
end
|
97
|
+
break
|
103
98
|
end
|
104
99
|
end
|
105
|
-
dir = File.dirname(file_to_save)
|
106
|
-
name = File.basename(file_to_save)
|
107
|
-
pipe.write("pgnsave #{File.join(dir, done.to_s, name)}\n")
|
108
|
-
ensure
|
109
|
-
pipe.write("quit\n")
|
110
|
-
pipe.close
|
111
100
|
end
|
101
|
+
dir = File.dirname(file_to_save)
|
102
|
+
name = File.basename(file_to_save)
|
103
|
+
pipe.write("pgnsave #{File.join(dir, done.to_s, name)}\n")
|
104
|
+
ensure
|
105
|
+
pipe.write("quit\n")
|
106
|
+
pipe.close
|
112
107
|
end
|
108
|
+
end
|
113
109
|
end
|
114
110
|
end
|
data/lib/chess/pgn.rb
CHANGED
@@ -40,7 +40,6 @@ module Chess
|
|
40
40
|
def initialize(filename = nil, check_moves: false)
|
41
41
|
self.load(filename, check_moves: check_moves) if filename
|
42
42
|
@date = '??'
|
43
|
-
@round = '1'
|
44
43
|
end
|
45
44
|
|
46
45
|
# Load a PGN from file.
|
@@ -50,7 +49,7 @@ module Chess
|
|
50
49
|
# @raise [InvalidPgnFormatError]
|
51
50
|
# @raise [IllegalMoveError]
|
52
51
|
def load(filename, check_moves: false)
|
53
|
-
str = File.
|
52
|
+
str = File.read(filename)
|
54
53
|
load_from_string(str, check_moves: check_moves)
|
55
54
|
end
|
56
55
|
|
@@ -70,12 +69,10 @@ module Chess
|
|
70
69
|
raise Chess::InvalidPgnFormatError.new if game_index.nil?
|
71
70
|
|
72
71
|
game = str[game_index..-1].strip
|
73
|
-
@moves = game.tr("\n", ' ').split(/\d+\./).collect(&:strip)[1..-1].
|
74
|
-
@moves.delete_at(@moves.size - 1) if @moves.last
|
72
|
+
@moves = game.tr("\n", ' ').split(/\d+\./).collect(&:strip)[1..-1].map(&:split).flatten
|
73
|
+
@moves.delete_at(@moves.size - 1) if @moves.last.match?(/(0-1)|(1-0)|(1\/2)|(1\/2-1\/2)|(\*)/)
|
75
74
|
@moves.each do |m|
|
76
|
-
if m !~ MOVE_REGEXP && m !~ SHORT_CASTLING_REGEXP && m !~ LONG_CASTLING_REGEXP
|
77
|
-
raise Chess::InvalidPgnFormatError.new
|
78
|
-
end
|
75
|
+
raise Chess::InvalidPgnFormatError.new if m !~ MOVE_REGEXP && m !~ SHORT_CASTLING_REGEXP && m !~ LONG_CASTLING_REGEXP
|
79
76
|
end
|
80
77
|
Chess::Game.new(@moves) if check_moves
|
81
78
|
return self
|
@@ -85,13 +82,13 @@ module Chess
|
|
85
82
|
def to_s
|
86
83
|
s = ''
|
87
84
|
TAGS.each do |t|
|
88
|
-
tag = instance_variable_get("@#{t}")
|
85
|
+
tag = instance_variable_defined?("@#{t}") ? instance_variable_get("@#{t}") : ''
|
89
86
|
s << "[#{t.capitalize} \"#{tag}\"]\n"
|
90
87
|
end
|
91
88
|
s << "\n"
|
92
89
|
m = ''
|
93
90
|
@moves.each_with_index do |move, i|
|
94
|
-
m << "#{i / 2 + 1}. " if i.even?
|
91
|
+
m << "#{(i / 2) + 1}. " if i.even?
|
95
92
|
m << "#{move} "
|
96
93
|
end
|
97
94
|
m << @result unless @result.nil?
|
@@ -101,7 +98,7 @@ module Chess
|
|
101
98
|
# Write PGN to file.
|
102
99
|
# @param [String] filename The path of the PGN file.
|
103
100
|
def write(filename)
|
104
|
-
File.
|
101
|
+
File.write(filename, self.to_s)
|
105
102
|
end
|
106
103
|
|
107
104
|
# # @!visibility private
|
@@ -109,11 +106,12 @@ module Chess
|
|
109
106
|
|
110
107
|
# Set the date tag.
|
111
108
|
def date=(value)
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
109
|
+
@date =
|
110
|
+
if value.is_a?(Time)
|
111
|
+
value.strftime('%Y.%m.%d')
|
112
|
+
else
|
113
|
+
value
|
114
|
+
end
|
117
115
|
end
|
118
116
|
end
|
119
117
|
end
|
data/lib/chess/version.rb
CHANGED
@@ -11,7 +11,7 @@ class ChessTest < Minitest::Test
|
|
11
11
|
define_method "test_big_pgn_#{filename}" do
|
12
12
|
pgn = Chess::Pgn.new(path)
|
13
13
|
game = Chess::Game.new(pgn.moves)
|
14
|
-
assert(game.checkmate?) if pgn.moves.last
|
14
|
+
assert(game.checkmate?) if pgn.moves.last.match?(/\#$/)
|
15
15
|
end
|
16
16
|
end
|
17
17
|
end
|
data/test/test_checkmate.rb
CHANGED
@@ -9,11 +9,11 @@ class ChessTest < Minitest::Test
|
|
9
9
|
game = Chess::Game.new(pgn.moves)
|
10
10
|
assert(game.board.checkmate?)
|
11
11
|
if file.include?('white_won')
|
12
|
-
assert_equal(
|
13
|
-
assert_equal(game.active_player
|
12
|
+
assert_equal('1-0', game.result)
|
13
|
+
assert_equal(:black, game.active_player)
|
14
14
|
elsif file.include?('black_won')
|
15
|
-
assert_equal(
|
16
|
-
assert_equal(game.active_player
|
15
|
+
assert_equal('0-1', game.result)
|
16
|
+
assert_equal(:white, game.active_player)
|
17
17
|
end
|
18
18
|
end
|
19
19
|
end
|
data/test/test_errors.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class ChessTest < Minitest::Test
|
4
|
+
def test_bad_notation_error
|
5
|
+
game = Chess::Game.new
|
6
|
+
assert_raises(Chess::BadNotationError) do
|
7
|
+
game << 'gg'
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_invalid_pgn_format_error
|
12
|
+
assert_raises(Chess::InvalidPgnFormatError) do
|
13
|
+
Chess::Pgn.new('test/pgn_collection/invalid/0001.pgn')
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_invalid_fen_format_error
|
18
|
+
assert_raises(Chess::InvalidFenFormatError) do
|
19
|
+
Chess::Game.load_fen('invalid')
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -3,10 +3,10 @@ require 'test_helper'
|
|
3
3
|
class ChessTest < Minitest::Test
|
4
4
|
TestHelper.pgns('fifty_move_rule').each do |file|
|
5
5
|
name = File.basename(file, '.pgn')
|
6
|
-
define_method "
|
6
|
+
define_method "test_fifty_move_rule_#{name}" do
|
7
7
|
pgn = Chess::Pgn.new(file)
|
8
8
|
game = Chess::Game.new(pgn.moves)
|
9
|
-
assert
|
9
|
+
assert game.board.fifty_move_rule?
|
10
10
|
end
|
11
11
|
end
|
12
12
|
end
|
data/test/test_game.rb
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class ChessTest < Minitest::Test
|
4
|
+
def test_moves
|
5
|
+
game = Chess::Game.new
|
6
|
+
game.moves = %w[e4 e5]
|
7
|
+
assert_equal %w[e4 e5], game.moves
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_active_player
|
11
|
+
game = Chess::Game.new
|
12
|
+
game << 'a3'
|
13
|
+
assert_equal :black, game.active_player
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_inactive_player
|
17
|
+
game = Chess::Game.new
|
18
|
+
game.moves = %w[a3 f5]
|
19
|
+
assert_equal :black, game.inactive_player
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_status_white_won_resign
|
23
|
+
game = Chess::Game.new
|
24
|
+
game.resign(:black)
|
25
|
+
assert_equal :white_won_resign, game.status
|
26
|
+
assert game.over?
|
27
|
+
assert_equal '1-0', game.result
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_status_black_won_resign
|
31
|
+
game = Chess::Game.new
|
32
|
+
game << 'e4'
|
33
|
+
game.resign(:white)
|
34
|
+
assert_equal :black_won_resign, game.status
|
35
|
+
assert game.over?
|
36
|
+
assert_equal '0-1', game.result
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_status_insufficient_material
|
40
|
+
pgn = TestHelper.pick_pgn('insufficient_material/0001.pgn')
|
41
|
+
game = Chess::Game.new(pgn.moves)
|
42
|
+
assert_equal :insufficient_material, game.status
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_status_fifty_move_rule
|
46
|
+
pgn = TestHelper.pick_pgn('fifty_move_rule/0001.pgn')
|
47
|
+
game = Chess::Game.new(pgn.moves)
|
48
|
+
game.draw
|
49
|
+
assert_equal :fifty_move_rule, game.status
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_status_threefold_repetition
|
53
|
+
pgn = TestHelper.pick_pgn('threefold_repetition/0001.pgn')
|
54
|
+
game = Chess::Game.new(pgn.moves)
|
55
|
+
game.draw
|
56
|
+
assert_equal :threefold_repetition, game.status
|
57
|
+
end
|
58
|
+
|
59
|
+
def test_pgn
|
60
|
+
pgn = TestHelper.pick_pgn('valid/0001.pgn')
|
61
|
+
game = Chess::Game.new(pgn.moves)
|
62
|
+
expected_pgn = <<~PGN
|
63
|
+
[Event ""]
|
64
|
+
[Site ""]
|
65
|
+
[Date "??"]
|
66
|
+
[Round ""]
|
67
|
+
[White ""]
|
68
|
+
[Black ""]
|
69
|
+
[Result "*"]
|
70
|
+
|
71
|
+
1. e4 c5 2. Nf3 d6 3. d4 cxd4 4. Qxd4 Nc6 5. Bb5 Bd7 6. Bxc6 Bxc6 7. Bg5 Nf6
|
72
|
+
8. Bxf6 gxf6 9. Nc3 e6 10. O-O-O Be7 11. Rhe1 Rg8 12. Qe3 Rxg2 13. Rg1 Rg6 14.
|
73
|
+
Nd4 Qb6 15. h4 O-O-O 16. h5 Rg5 17. Nd5 Bxd5 18. exd5 e5 19. Qh3+ Kb8 20. Nf5
|
74
|
+
Bf8 21. Rxg5 fxg5 22. Ne3 Qb4 23. c4 g4 24. Qxg4 Bh6 25. Kb1 Rc8 26. Rc1 Qd2
|
75
|
+
27. Qf5 Bf4 28. Qc2 Qxc2+ 29. Rxc2 Rg8 30. b4 Rg5 31. c5 e4 32. c6 Rxh5 33.
|
76
|
+
Rc4 f5 34. b5 Rg5 35. Rc3 Be5 36. Rc1 Bd4 37. Nc4 Bc5 38. Ne5 dxe5 39. Rxc5
|
77
|
+
Rg6 40. a4 Rd6 41. a5 b6 42. axb6 axb6 43. Rc2 Rxd5 44. Rb2 h5 45. Ka2 h4 46.
|
78
|
+
Ka3 h3 47. Ka4 Rd1 *
|
79
|
+
PGN
|
80
|
+
assert_equal expected_pgn, game.pgn.to_s
|
81
|
+
end
|
82
|
+
end
|
data/test/test_helper.rb
CHANGED
@@ -1,5 +1,15 @@
|
|
1
|
+
require 'simplecov'
|
2
|
+
SimpleCov.start do
|
3
|
+
add_filter 'lib/chess/gnuchess.rb'
|
4
|
+
end
|
5
|
+
if ENV['CODECOV'] == 'true'
|
6
|
+
require 'codecov'
|
7
|
+
SimpleCov.formatter = SimpleCov::Formatter::Codecov
|
8
|
+
end
|
9
|
+
|
1
10
|
require 'chess'
|
2
11
|
require 'minitest/autorun'
|
12
|
+
require 'byebug'
|
3
13
|
|
4
14
|
module TestHelper
|
5
15
|
PGN_COLLECTION = 'test/pgn_collection'.freeze
|
@@ -8,4 +18,9 @@ module TestHelper
|
|
8
18
|
def self.pgns(path, prefix = PGN_COLLECTION)
|
9
19
|
Dir[File.join(prefix, path, '**/*.pgn')]
|
10
20
|
end
|
21
|
+
|
22
|
+
def self.pick_pgn(path, prefix = PGN_COLLECTION)
|
23
|
+
file = File.join(prefix, path)
|
24
|
+
return Chess::Pgn.new(file)
|
25
|
+
end
|
11
26
|
end
|
@@ -17,15 +17,15 @@ class ChessTest < Minitest::Test
|
|
17
17
|
FENS.each_with_index do |fen, i|
|
18
18
|
define_method("test_insufficient_material_by_fen_#{i}") do
|
19
19
|
game = Chess::Game.load_fen(fen)
|
20
|
-
assert
|
20
|
+
assert game.board.insufficient_material?
|
21
21
|
end
|
22
22
|
end
|
23
23
|
|
24
24
|
ONLY_KINGS_FENS.each_with_index do |fen, i|
|
25
25
|
define_method("test_only_kings_by_fen_#{i}") do
|
26
26
|
game = Chess::Game.load_fen(fen)
|
27
|
-
assert
|
28
|
-
assert
|
27
|
+
assert game.board.insufficient_material?
|
28
|
+
assert game.board.only_kings?
|
29
29
|
end
|
30
30
|
end
|
31
31
|
|
@@ -34,7 +34,7 @@ class ChessTest < Minitest::Test
|
|
34
34
|
define_method "test_insufficient_material_#{name}" do
|
35
35
|
pgn = Chess::Pgn.new(file)
|
36
36
|
game = Chess::Game.new(pgn.moves)
|
37
|
-
assert
|
37
|
+
assert game.board.insufficient_material?
|
38
38
|
end
|
39
39
|
end
|
40
40
|
end
|
data/test/test_move_generator.rb
CHANGED
@@ -2,7 +2,7 @@ require 'test_helper'
|
|
2
2
|
|
3
3
|
class ChessTest < Minitest::Test
|
4
4
|
GENS = {
|
5
|
-
'r2qk3/8/2n5/8/8/8/p2B4/4K2R w K - 0 1' => { 'e1' => [
|
5
|
+
'r2qk3/8/2n5/8/8/8/p2B4/4K2R w K - 0 1' => { 'e1' => %w[Kd1 Ke2 Kf2 Kf1 O-O] },
|
6
6
|
'r2qk3/8/2n5/8/8/8/p2B4/4K2R w - - 0 1' => { 'e1' => %w[Kd1 Ke2 Kf2 Kf1] },
|
7
7
|
'r2qk3/8/2n5/2b5/8/8/p2B4/4K2R w K - 0 1' => { 'e1' => %w[Kd1 Ke2 Kf1] },
|
8
8
|
'r2qk3/8/2n5/2b5/8/8/p2B4/4K2R b K - 0 1' => { 'a2' => ['a1=Q'] },
|
@@ -12,7 +12,8 @@ class ChessTest < Minitest::Test
|
|
12
12
|
'1B1k4/r3q3/2n1n3/2b2pP1/8/8/2nB4/3K3R b - - 0 1' => { 'c6' => %w[Nxb8 Ne5 Nc6d4 N6b4 Na5] },
|
13
13
|
'1B1k4/r3q3/2n1n2P/2b5/5p2/3p1RP1/3B4/3K4 w - - 0 1' => { 'f3' => %w[Rxd3 Re3 Rxf4 Rf2 Rf1] },
|
14
14
|
'k7/1Q6/2P5/8/8/8/8/3K4 b - - 0 1' => { 'a8' => [] },
|
15
|
-
'k7/6P1/8/8/8/3r4/3Q4/3K4 w - - 0 1' => { 'd2' => ['Qxd3'] }
|
15
|
+
'k7/6P1/8/8/8/3r4/3Q4/3K4 w - - 0 1' => { 'd2' => ['Qxd3'] },
|
16
|
+
'rnbqkbnr/1ppppppp/8/8/pP2P3/P7/2PP1PPP/RNBQKBNR w KQkq - 0 3' => { 'c2' => %w[c3 c4] }
|
16
17
|
}.freeze
|
17
18
|
|
18
19
|
GENS.each do |fen, generators|
|
data/test/test_pgn.rb
CHANGED
@@ -67,4 +67,39 @@ class ChessTest < Minitest::Test
|
|
67
67
|
assert_nil pgn.result
|
68
68
|
assert_equal 'Re6', pgn.moves.last
|
69
69
|
end
|
70
|
+
|
71
|
+
def test_write_pgn
|
72
|
+
tempfile = Tempfile.new('test_pgn')
|
73
|
+
game = Chess::Game.new
|
74
|
+
game.moves = %w[e4 e5 d3]
|
75
|
+
pgn = game.pgn
|
76
|
+
pgn.event = 'Ruby Chess Tournament'
|
77
|
+
pgn.site = 'Ruby Chess test suite'
|
78
|
+
pgn.date = '1984.10.21'
|
79
|
+
pgn.round = '2'
|
80
|
+
pgn.white = 'Pioz'
|
81
|
+
pgn.black = 'Elizabeth Harmon'
|
82
|
+
pgn.write(tempfile.path)
|
83
|
+
|
84
|
+
loaded_pgn = Chess::Pgn.new
|
85
|
+
loaded_pgn.load(tempfile.path)
|
86
|
+
tempfile.delete
|
87
|
+
assert_equal 'Ruby Chess Tournament', loaded_pgn.event
|
88
|
+
assert_equal 'Ruby Chess test suite', loaded_pgn.site
|
89
|
+
assert_equal '1984.10.21', loaded_pgn.date
|
90
|
+
assert_equal '2', loaded_pgn.round
|
91
|
+
assert_equal 'Pioz', loaded_pgn.white
|
92
|
+
assert_equal 'Elizabeth Harmon', loaded_pgn.black
|
93
|
+
assert_equal '*', loaded_pgn.result
|
94
|
+
assert_equal %w[e4 e5 d3], loaded_pgn.moves
|
95
|
+
end
|
96
|
+
|
97
|
+
def test_set_date
|
98
|
+
pgn = Chess::Pgn.new
|
99
|
+
pgn.date = Time.parse('21-10-1984')
|
100
|
+
assert_equal '1984.10.21', pgn.date
|
101
|
+
|
102
|
+
pgn.date = '1984.10.21'
|
103
|
+
assert_equal '1984.10.21', pgn.date
|
104
|
+
end
|
70
105
|
end
|
data/test/test_pgn_collection.rb
CHANGED
@@ -8,8 +8,8 @@ class ChessTest < Minitest::Test
|
|
8
8
|
game = Chess::Game.new
|
9
9
|
pgn.moves.each do |m|
|
10
10
|
game.move(m)
|
11
|
-
assert(game.board.check?) if m
|
12
|
-
assert(game.board.checkmate?) if m
|
11
|
+
assert(game.board.check?) if m.match?(/\+$/)
|
12
|
+
assert(game.board.checkmate?) if m.match?(/\#$/)
|
13
13
|
end
|
14
14
|
end
|
15
15
|
define_method "test_pgn_result_#{name}" do
|