chess 0.3.1 → 0.3.2
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 +2 -2
- data/.rubocop.yml +9 -144
- data/Gemfile.lock +70 -0
- data/README.md +26 -22
- data/Rakefile +1 -0
- data/chess.gemspec +8 -5
- data/docs/Chess.html +157 -0
- 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/_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 +53 -67
- data/lib/chess/gnuchess.rb +49 -53
- data/lib/chess/pgn.rb +10 -12
- data/lib/chess/utf8_notation.rb +3 -0
- 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
- metadata +112 -10
@@ -0,0 +1,110 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<meta charset="utf-8">
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
6
|
+
<title>
|
7
|
+
Top Level Namespace
|
8
|
+
|
9
|
+
— Chess
|
10
|
+
|
11
|
+
</title>
|
12
|
+
|
13
|
+
<link rel="stylesheet" href="css/style.css" type="text/css" />
|
14
|
+
|
15
|
+
<link rel="stylesheet" href="css/common.css" type="text/css" />
|
16
|
+
|
17
|
+
<script type="text/javascript">
|
18
|
+
pathId = "";
|
19
|
+
relpath = '';
|
20
|
+
</script>
|
21
|
+
|
22
|
+
|
23
|
+
<script type="text/javascript" charset="utf-8" src="js/jquery.js"></script>
|
24
|
+
|
25
|
+
<script type="text/javascript" charset="utf-8" src="js/app.js"></script>
|
26
|
+
|
27
|
+
|
28
|
+
</head>
|
29
|
+
<body>
|
30
|
+
<div class="nav_wrap">
|
31
|
+
<iframe id="nav" src="class_list.html?1"></iframe>
|
32
|
+
<div id="resizer"></div>
|
33
|
+
</div>
|
34
|
+
|
35
|
+
<div id="main" tabindex="-1">
|
36
|
+
<div id="header">
|
37
|
+
<div id="menu">
|
38
|
+
|
39
|
+
<a href="_index.html">Index</a> »
|
40
|
+
|
41
|
+
|
42
|
+
<span class="title">Top Level Namespace</span>
|
43
|
+
|
44
|
+
</div>
|
45
|
+
|
46
|
+
<div id="search">
|
47
|
+
|
48
|
+
<a class="full_list_link" id="class_list_link"
|
49
|
+
href="class_list.html">
|
50
|
+
|
51
|
+
<svg width="24" height="24">
|
52
|
+
<rect x="0" y="4" width="24" height="4" rx="1" ry="1"></rect>
|
53
|
+
<rect x="0" y="12" width="24" height="4" rx="1" ry="1"></rect>
|
54
|
+
<rect x="0" y="20" width="24" height="4" rx="1" ry="1"></rect>
|
55
|
+
</svg>
|
56
|
+
</a>
|
57
|
+
|
58
|
+
</div>
|
59
|
+
<div class="clear"></div>
|
60
|
+
</div>
|
61
|
+
|
62
|
+
<div id="content"><h1>Top Level Namespace
|
63
|
+
|
64
|
+
|
65
|
+
|
66
|
+
</h1>
|
67
|
+
<div class="box_info">
|
68
|
+
|
69
|
+
|
70
|
+
|
71
|
+
|
72
|
+
|
73
|
+
|
74
|
+
|
75
|
+
|
76
|
+
|
77
|
+
|
78
|
+
|
79
|
+
</div>
|
80
|
+
|
81
|
+
<h2>Defined Under Namespace</h2>
|
82
|
+
<p class="children">
|
83
|
+
|
84
|
+
|
85
|
+
<strong class="modules">Modules:</strong> <span class='object_link'><a href="Chess.html" title="Chess (module)">Chess</a></span>
|
86
|
+
|
87
|
+
|
88
|
+
|
89
|
+
|
90
|
+
</p>
|
91
|
+
|
92
|
+
|
93
|
+
|
94
|
+
|
95
|
+
|
96
|
+
|
97
|
+
|
98
|
+
|
99
|
+
|
100
|
+
</div>
|
101
|
+
|
102
|
+
<div id="footer">
|
103
|
+
Generated on Thu Jan 28 17:31:40 2021 by
|
104
|
+
<a href="http://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
|
105
|
+
0.9.26 (ruby-2.6.1).
|
106
|
+
</div>
|
107
|
+
|
108
|
+
</div>
|
109
|
+
</body>
|
110
|
+
</html>
|
data/ext/chess.c
CHANGED
@@ -723,7 +723,7 @@ Init_chess ()
|
|
723
723
|
rb_define_method (board_klass, "stalemate?", board_stalemate, 0);
|
724
724
|
rb_define_method (board_klass, "insufficient_material?", board_insufficient_material, 0);
|
725
725
|
rb_define_method (board_klass, "only_kings?", board_only_kings, 0);
|
726
|
-
rb_define_method (board_klass, "
|
726
|
+
rb_define_method (board_klass, "fifty_move_rule?", board_fifty_move_rule, 0);
|
727
727
|
rb_define_method (board_klass, "active_color", board_active_color, 0);
|
728
728
|
rb_define_method (board_klass, "halfmove_clock", board_halfmove_clock, 0);
|
729
729
|
rb_define_method (board_klass, "fullmove_number", board_fullmove_number, 0);
|
data/ext/common.h
CHANGED
@@ -13,9 +13,15 @@
|
|
13
13
|
#include <string.h>
|
14
14
|
#include <ctype.h>
|
15
15
|
|
16
|
-
//
|
16
|
+
// Boolean definition
|
17
|
+
|
18
|
+
#ifndef RUBY_BACKWARD2_BOOL_H
|
17
19
|
#define FALSE 0
|
18
20
|
#define TRUE 1
|
21
|
+
typedef unsigned short bool;
|
22
|
+
#endif
|
23
|
+
|
24
|
+
// Macros
|
19
25
|
|
20
26
|
#define A1 0
|
21
27
|
#define B1 1
|
@@ -95,8 +101,6 @@
|
|
95
101
|
#define DRAW 2
|
96
102
|
#define IN_PROGRESS 3
|
97
103
|
|
98
|
-
typedef unsigned short bool;
|
99
|
-
|
100
104
|
char square_to_file (int square);
|
101
105
|
char square_to_rank (int square);
|
102
106
|
int coord_to_square (const char *coord);
|
data/ext/extconf.rb
CHANGED
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,36 @@ 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
|
-
|
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
|
184
|
-
end
|
185
|
-
raise BadNotationError.new(notation)
|
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
|
+
return expand
|
159
|
+
elsif SHORT_CASTLING_REGEXP.match?(notation)
|
160
|
+
return { name: 'K', dis: nil, from: 'e8', to: 'g8', promotion: nil } if self.board.active_color # black king short castling
|
161
|
+
|
162
|
+
return { name: 'K', dis: nil, from: 'e1', to: 'g1', promotion: nil } # white king short castling
|
163
|
+
elsif LONG_CASTLING_REGEXP.match?(notation)
|
164
|
+
return { name: 'K', dis: nil, from: 'e8', to: 'c8', promotion: nil } if self.board.active_color # black king long castling
|
165
|
+
|
166
|
+
return { name: 'K', dis: nil, from: 'e1', to: 'c1', promotion: nil } # white king long castling
|
186
167
|
end
|
168
|
+
raise BadNotationError.new(notation)
|
169
|
+
end
|
187
170
|
end
|
188
171
|
|
189
172
|
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])([
|
173
|
+
SHORT_CASTLING_REGEXP = /^([0O])-([0O])([+#])?$/.freeze
|
174
|
+
LONG_CASTLING_REGEXP = /^([0O])-([0O])-([0O])([+#])?$/.freeze
|
175
|
+
private_constant :MOVE_REGEXP
|
176
|
+
private_constant :SHORT_CASTLING_REGEXP
|
177
|
+
private_constant :LONG_CASTLING_REGEXP
|
192
178
|
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.
|
@@ -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,7 +82,7 @@ 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"
|
@@ -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
|