qi 6.1.1 → 6.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (5) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +300 -87
  3. data/lib/qi.rb +1 -1
  4. data/lib/qi/position.rb +111 -62
  5. metadata +16 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6d85f0a7a03ecc612c24bf29363b843fb88fa0572b8f74bf955eb92039314a54
4
- data.tar.gz: ee40f146832333bf952ba810d1d95fafeabc8e6dc17905e39310b7c746faeb5f
3
+ metadata.gz: 8139006ba5fe335016cb13704d9a23e77b20a64a774cca7242fb9bc46860c520
4
+ data.tar.gz: 01715c0a65f8d4690f06797db4a97be1d81251cd053dfd842e937f23136faa00
5
5
  SHA512:
6
- metadata.gz: 1310f61417a533fb3c6f19350b759f1831d1b9e7afc04ac4946ac36850cd53eea5ed27942af0f9132d93871047f888217f265d6db782e2d153453ec34c4c80fd
7
- data.tar.gz: a2c2ca62646b82063b132d69a2f5fc15b062500a23daa6c5953154b23c798fdbbe9c4c6d4f3406be8b01b07c9737a0570c4d6fa0c27f69021c41a4fba79abb3d
6
+ metadata.gz: ac73beb467897bf8bc8f210edd5891fffed38ee24326fe41fb043f5b0b9cf4b99104247a887496d5cfc51de150bae2e1c99c1e44033992f3f8dd2809006a33c4
7
+ data.tar.gz: 6ccad4356dd09aabc9925f6357e2af3c137fb425348fb08c7d99b1b13afd049b7c23f8627485d7a8d5089393832194eea98518d2ecb7a423bac8a6607f25a4a5
data/README.md CHANGED
@@ -12,7 +12,7 @@
12
12
  Add this line to your application's Gemfile:
13
13
 
14
14
  ```ruby
15
- gem 'qi'
15
+ gem "qi"
16
16
  ```
17
17
 
18
18
  And then execute:
@@ -25,128 +25,341 @@ Or install it yourself as:
25
25
 
26
26
  ## Examples
27
27
 
28
- Let's replay [The Shortest Possible Game of Shogi](https://userpages.monmouth.com/~colonel/shortshogi.html):
28
+ Let's replay [The Shortest Possible Game](https://userpages.monmouth.com/~colonel/shortshogi.html) of [Shogi](https://en.wikipedia.org/wiki/Shogi):
29
29
 
30
30
  ```ruby
31
- require 'qi'
31
+ require "qi"
32
32
 
33
33
  starting_position = Qi::Position.new(
34
- 'l', 'n', 's', 'g', 'k', 'g', 's', 'n', 'l',
35
- nil, 'r', nil, nil, nil, nil, nil, 'b', nil,
36
- 'p', 'p', 'p', 'p', 'p', 'p', 'p', 'p', 'p',
34
+ "l", "n", "s", "g", "k", "g", "s", "n", "l",
35
+ nil, "r", nil, nil, nil, nil, nil, "b", nil,
36
+ "p", "p", "p", "p", "p", "p", "p", "p", "p",
37
37
  nil, nil, nil, nil, nil, nil, nil, nil, nil,
38
38
  nil, nil, nil, nil, nil, nil, nil, nil, nil,
39
39
  nil, nil, nil, nil, nil, nil, nil, nil, nil,
40
- 'P', 'P', 'P', 'P', 'P', 'P', 'P', 'P', 'P',
41
- nil, 'B', nil, nil, nil, nil, nil, 'R', nil,
42
- 'L', 'N', 'S', 'G', 'K', 'G', 'S', 'N', 'L'
40
+ "P", "P", "P", "P", "P", "P", "P", "P", "P",
41
+ nil, "B", nil, nil, nil, nil, nil, "R", nil,
42
+ "L", "N", "S", "G", "K", "G", "S", "N", "L"
43
43
  )
44
44
 
45
- starting_position.topside_in_hand_pieces # => []
46
- starting_position.squares # => ["l", "n", "s", "g", "k", "g", "s", "n", "l",
47
- # nil, "r", nil, nil, nil, nil, nil, "b", nil,
48
- # "p", "p", "p", "p", "p", "p", "p", "p", "p",
49
- # nil, nil, nil, nil, nil, nil, nil, nil, nil,
50
- # nil, nil, nil, nil, nil, nil, nil, nil, nil,
51
- # nil, nil, nil, nil, nil, nil, nil, nil, nil,
52
- # "P", "P", "P", "P", "P", "P", "P", "P", "P",
53
- # nil, "B", nil, nil, nil, nil, nil, "R", nil,
54
- # "L", "N", "S", "G", "K", "G", "S", "N", "L"]
55
- starting_position.bottomside_in_hand_pieces # => []
56
- starting_position.in_hand_pieces # => []
57
- starting_position.turn_to_topside? # => false
58
-
59
- # List of moves in Portable Move Notation (https://developer.sashite.com/specs/portable-move-notation) format.
45
+ starting_position.in_hand_pieces
46
+ # => []
47
+
48
+ starting_position.squares
49
+ # => ["l", "n", "s", "g", "k", "g", "s", "n", "l",
50
+ # nil, "r", nil, nil, nil, nil, nil, "b", nil,
51
+ # "p", "p", "p", "p", "p", "p", "p", "p", "p",
52
+ # nil, nil, nil, nil, nil, nil, nil, nil, nil,
53
+ # nil, nil, nil, nil, nil, nil, nil, nil, nil,
54
+ # nil, nil, nil, nil, nil, nil, nil, nil, nil,
55
+ # "P", "P", "P", "P", "P", "P", "P", "P", "P",
56
+ # nil, "B", nil, nil, nil, nil, nil, "R", nil,
57
+ # "L", "N", "S", "G", "K", "G", "S", "N", "L"]
58
+
59
+ starting_position.pieces_in_hand_grouped_by_sides
60
+ # => [[], []]
61
+
62
+ starting_position.active_side_id
63
+ # => 0
64
+
65
+ starting_position.feen(9, 9)
66
+ # => "l,n,s,g,k,g,s,n,l/1,r,5,b,1/p,p,p,p,p,p,p,p,p/9/9/9/P,P,P,P,P,P,P,P,P/1,B,5,R,1/L,N,S,G,K,G,S,N,L 0 /"
67
+
68
+ # List of moves (see https://github.com/sashite/pmn.rb)
60
69
  moves = [
61
- [ 56, 47, 'P' ],
62
- [ 3, 11, 'g' ],
63
- [ 64, 24, '+B', 'P' ],
64
- [ 5, 14, 'g' ],
65
- [ 24, 14, '+B', 'G' ],
66
- [ 4, 3, 'k' ],
67
- [ nil, 13, 'G' ]
70
+ [ 56, 47, "P" ],
71
+ [ 3, 11, "g" ],
72
+ [ 64, 24, "+B", "P" ],
73
+ [ 5, 14, "g" ],
74
+ [ 24, 14, "+B", "G" ],
75
+ [ 4, 3, "k" ],
76
+ [ nil, 13, "G" ]
68
77
  ]
69
78
 
70
79
  last_position = moves.reduce(starting_position) do |position, move|
71
80
  position.call(move)
72
81
  end
73
82
 
74
- last_position.topside_in_hand_pieces # => []
75
- last_position.squares # => ["l", "n", "s", "k", nil, nil, "s", "n", "l",
76
- # nil, "r", "g", nil, "G", "+B", nil, "b", nil,
77
- # "p", "p", "p", "p", "p", "p", nil, "p", "p",
78
- # nil, nil, nil, nil, nil, nil, nil, nil, nil,
79
- # nil, nil, nil, nil, nil, nil, nil, nil, nil,
80
- # nil, nil, "P", nil, nil, nil, nil, nil, nil,
81
- # "P", "P", nil, "P", "P", "P", "P", "P", "P",
82
- # nil, nil, nil, nil, nil, nil, nil, "R", nil,
83
- # "L", "N", "S", "G", "K", "G", "S", "N", "L"]
84
- last_position.bottomside_in_hand_pieces # => ["P"]
85
- last_position.in_hand_pieces # => []
86
- last_position.turn_to_topside? # => true
83
+ last_position.in_hand_pieces
84
+ # => []
85
+
86
+ last_position.squares
87
+ # => ["l", "n", "s", "k", nil, nil, "s", "n", "l",
88
+ # nil, "r", "g", nil, "G", "+B", nil, "b", nil,
89
+ # "p", "p", "p", "p", "p", "p", nil, "p", "p",
90
+ # nil, nil, nil, nil, nil, nil, nil, nil, nil,
91
+ # nil, nil, nil, nil, nil, nil, nil, nil, nil,
92
+ # nil, nil, "P", nil, nil, nil, nil, nil, nil,
93
+ # "P", "P", nil, "P", "P", "P", "P", "P", "P",
94
+ # nil, nil, nil, nil, nil, nil, nil, "R", nil,
95
+ # "L", "N", "S", "G", "K", "G", "S", "N", "L"]
96
+
97
+ last_position.pieces_in_hand_grouped_by_sides
98
+ # => [["P"], []]
99
+
100
+ last_position.active_side_id
101
+ # => 1
102
+
103
+ last_position.feen(9, 9)
104
+ # => "l,n,s,k,2,s,n,l/1,r,g,1,G,+B,1,b,1/p,p,p,p,p,p,1,p,p/9/9/2,P,6/P,P,1,P,P,P,P,P,P/7,R,1/L,N,S,G,K,G,S,N,L 1 P/"
87
105
  ```
88
106
 
89
- Another example with Xiangqi's Short Double Cannons Checkmate:
107
+ A classic [Tsume Shogi](https://en.wikipedia.org/wiki/Tsume_shogi) problem:
90
108
 
91
109
  ```ruby
92
- require 'qi'
110
+ require "qi"
93
111
 
94
112
  starting_position = Qi::Position.new(
95
- '車', '馬', '象', '士', '將', '士', '象', '馬', '車',
113
+ nil, nil, nil, "s", "k", "s", nil, nil, nil,
96
114
  nil, nil, nil, nil, nil, nil, nil, nil, nil,
97
- nil, '砲', nil, nil, nil, nil, nil, '砲', nil,
98
- '卒', nil, '卒', nil, '卒', nil, '卒', nil, '卒',
115
+ nil, nil, nil, nil, "+P", nil, nil, nil, nil,
99
116
  nil, nil, nil, nil, nil, nil, nil, nil, nil,
117
+ nil, nil, nil, nil, nil, nil, nil, "+B", nil,
100
118
  nil, nil, nil, nil, nil, nil, nil, nil, nil,
101
- '兵', nil, '兵', nil, '兵', nil, '兵', nil, '兵',
102
- nil, '炮', nil, nil, nil, nil, nil, '炮', nil,
103
119
  nil, nil, nil, nil, nil, nil, nil, nil, nil,
104
- '俥', '傌', '相', '仕', '帥', '仕', '相', '傌', '俥'
120
+ nil, nil, nil, nil, nil, nil, nil, nil, nil,
121
+ nil, nil, nil, nil, nil, nil, nil, nil, nil,
122
+ pieces_in_hand_grouped_by_sides: [
123
+ ["S"],
124
+ ["b", "g", "g", "g", "g", "n", "n", "n", "n", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "r", "r", "s"]
125
+ ]
105
126
  )
106
127
 
107
- starting_position.topside_in_hand_pieces # => []
108
- starting_position.squares # => ["", "馬", "象", "士", "將", "士", "象", "馬", "車",
109
- # nil, nil, nil, nil, nil, nil, nil, nil, nil,
110
- # nil, "砲", nil, nil, nil, nil, nil, "砲", nil,
111
- # "卒", nil, "卒", nil, "", nil, "", nil, "卒",
112
- # nil, nil, nil, nil, nil, nil, nil, nil, nil,
113
- # nil, nil, nil, nil, nil, nil, nil, nil, nil,
114
- # "兵", nil, "兵", nil, "兵", nil, "兵", nil, "兵",
115
- # nil, "炮", nil, nil, nil, nil, nil, "", nil,
116
- # nil, nil, nil, nil, nil, nil, nil, nil, nil,
117
- # "俥", "傌", "相", "仕", "帥", "仕", "相", "傌", "俥"]
118
- starting_position.bottomside_in_hand_pieces # => []
119
- starting_position.in_hand_pieces # => []
120
- starting_position.turn_to_topside? # => false
128
+ starting_position.in_hand_pieces
129
+ # => ["S"]
130
+
131
+ starting_position.squares
132
+ # => [nil, nil, nil, "s", "k", "s", nil, nil, nil,
133
+ # nil, nil, nil, nil, nil, nil, nil, nil, nil,
134
+ # nil, nil, nil, nil, "+P", nil, nil, nil, nil,
135
+ # nil, nil, nil, nil, nil, nil, nil, nil, nil,
136
+ # nil, nil, nil, nil, nil, nil, nil, "+B", nil,
137
+ # nil, nil, nil, nil, nil, nil, nil, nil, nil,
138
+ # nil, nil, nil, nil, nil, nil, nil, nil, nil,
139
+ # nil, nil, nil, nil, nil, nil, nil, nil, nil,
140
+ # nil, nil, nil, nil, nil, nil, nil, nil, nil]
141
+
142
+ starting_position.pieces_in_hand_grouped_by_sides
143
+ # => [["S"], ["b", "g", "g", "g", "g", "n", "n", "n", "n", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "r", "r", "s"]]
144
+
145
+ starting_position.active_side_id
146
+ # => 0
121
147
 
148
+ starting_position.feen(9, 9)
149
+ # => "3,s,k,s,3/9/4,+P,4/9/7,+B,1/9/9/9/9 0 S/b,g,g,g,g,n,n,n,n,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,r,r,s"
150
+
151
+ # List of moves (see https://github.com/sashite/pmn.rb)
122
152
  moves = [
123
- [ 64, 67, '炮' ],
124
- [ 25, 22, '砲' ],
125
- [ 70, 52, '炮' ],
126
- [ 19, 55, '砲' ],
127
- [ 67, 31, '炮' ],
128
- [ 22, 58, '砲' ],
129
- [ 52, 49, '炮' ]
153
+ [ 43, 13, "+B" ], [ 5, 13, "s", "b" ],
154
+ [ nil, 14, "S" ]
130
155
  ]
131
156
 
132
157
  last_position = moves.reduce(starting_position) do |position, move|
133
158
  position.call(move)
134
159
  end
135
160
 
136
- last_position.topside_in_hand_pieces # => []
137
- last_position.squares # => ["", "", "", "", "", "", "", "", "",
138
- # nil, nil, nil, nil, nil, nil, nil, nil, nil,
139
- # nil, nil, nil, nil, nil, nil, nil, nil, nil,
140
- # "卒", nil, "卒", nil, "", nil, "卒", nil, "卒",
141
- # nil, nil, nil, nil, nil, nil, nil, nil, nil,
142
- # nil, nil, nil, nil, "", nil, nil, nil, nil,
143
- # "兵", "砲", "兵", nil, "砲", nil, "兵", nil, "兵",
144
- # nil, nil, nil, nil, nil, nil, nil, nil, nil,
145
- # nil, nil, nil, nil, nil, nil, nil, nil, nil,
146
- # "俥", "傌", "相", "仕", "帥", "仕", "相", "傌", "俥"]
147
- last_position.bottomside_in_hand_pieces # => []
148
- last_position.in_hand_pieces # => []
149
- last_position.turn_to_topside? # => true
161
+ last_position.in_hand_pieces
162
+ # => ["b", "g", "g", "g", "g", "n", "n", "n", "n", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "r", "r", "s", "b"]
163
+
164
+ last_position.squares
165
+ # => [nil, nil, nil, "s", "k", nil, nil, nil, nil,
166
+ # nil, nil, nil, nil, "s", "S", nil, nil, nil,
167
+ # nil, nil, nil, nil, "+P", nil, nil, nil, nil,
168
+ # nil, nil, nil, nil, nil, nil, nil, nil, nil,
169
+ # nil, nil, nil, nil, nil, nil, nil, nil, nil,
170
+ # nil, nil, nil, nil, nil, nil, nil, nil, nil,
171
+ # nil, nil, nil, nil, nil, nil, nil, nil, nil,
172
+ # nil, nil, nil, nil, nil, nil, nil, nil, nil,
173
+ # nil, nil, nil, nil, nil, nil, nil, nil, nil]
174
+
175
+ last_position.pieces_in_hand_grouped_by_sides
176
+ # => [[], ["b", "g", "g", "g", "g", "n", "n", "n", "n", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "r", "r", "s", "b"]]
177
+
178
+ last_position.active_side_id
179
+ # => 1
180
+
181
+ last_position.feen(9, 9)
182
+ # => "3,s,k,4/4,s,S,3/4,+P,4/9/9/9/9/9/9 1 /b,b,g,g,g,g,n,n,n,n,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,r,r,s"
183
+ ```
184
+
185
+ Another example with [Xiangqi](https://en.wikipedia.org/wiki/Xiangqi)'s Short Double Cannons Checkmate:
186
+
187
+ ```ruby
188
+ require "qi"
189
+
190
+ starting_position = Qi::Position.new(
191
+ "車", "馬", "象", "士", "將", "士", "象", "馬", "車",
192
+ nil, nil, nil, nil, nil, nil, nil, nil, nil,
193
+ nil, "砲", nil, nil, nil, nil, nil, "砲", nil,
194
+ "卒", nil, "卒", nil, "卒", nil, "卒", nil, "卒",
195
+ nil, nil, nil, nil, nil, nil, nil, nil, nil,
196
+ nil, nil, nil, nil, nil, nil, nil, nil, nil,
197
+ "兵", nil, "兵", nil, "兵", nil, "兵", nil, "兵",
198
+ nil, "炮", nil, nil, nil, nil, nil, "炮", nil,
199
+ nil, nil, nil, nil, nil, nil, nil, nil, nil,
200
+ "俥", "傌", "相", "仕", "帥", "仕", "相", "傌", "俥"
201
+ )
202
+
203
+ starting_position.in_hand_pieces
204
+ # => []
205
+
206
+ starting_position.squares
207
+ # => ["車", "馬", "象", "士", "將", "士", "象", "馬", "車",
208
+ # nil, nil, nil, nil, nil, nil, nil, nil, nil,
209
+ # nil, "砲", nil, nil, nil, nil, nil, "砲", nil,
210
+ # "卒", nil, "卒", nil, "卒", nil, "卒", nil, "卒",
211
+ # nil, nil, nil, nil, nil, nil, nil, nil, nil,
212
+ # nil, nil, nil, nil, nil, nil, nil, nil, nil,
213
+ # "兵", nil, "兵", nil, "兵", nil, "兵", nil, "兵",
214
+ # nil, "炮", nil, nil, nil, nil, nil, "炮", nil,
215
+ # nil, nil, nil, nil, nil, nil, nil, nil, nil,
216
+ # "俥", "傌", "相", "仕", "帥", "仕", "相", "傌", "俥"]
217
+
218
+ starting_position.pieces_in_hand_grouped_by_sides
219
+ # => [[], []]
220
+
221
+ starting_position.active_side_id
222
+ # => 0
223
+
224
+ starting_position.feen(10, 9)
225
+ # => "車,馬,象,士,將,士,象,馬,車/9/1,砲,5,砲,1/卒,1,卒,1,卒,1,卒,1,卒/9/9/兵,1,兵,1,兵,1,兵,1,兵/1,炮,5,炮,1/9/俥,傌,相,仕,帥,仕,相,傌,俥 0 /"
226
+
227
+ # List of moves (see https://github.com/sashite/pmn.rb)
228
+ moves = [
229
+ [ 64, 67, "炮" ],
230
+ [ 25, 22, "砲" ],
231
+ [ 70, 52, "炮" ],
232
+ [ 19, 55, "砲" ],
233
+ [ 67, 31, "炮" ],
234
+ [ 22, 58, "砲" ],
235
+ [ 52, 49, "炮" ]
236
+ ]
237
+
238
+ last_position = moves.reduce(starting_position) do |position, move|
239
+ position.call(move)
240
+ end
241
+
242
+ last_position.in_hand_pieces
243
+ # => []
244
+
245
+ last_position.squares
246
+ # => ["車", "馬", "象", "士", "將", "士", "象", "馬", "車",
247
+ # nil, nil, nil, nil, nil, nil, nil, nil, nil,
248
+ # nil, nil, nil, nil, nil, nil, nil, nil, nil,
249
+ # "卒", nil, "卒", nil, "炮", nil, "卒", nil, "卒",
250
+ # nil, nil, nil, nil, nil, nil, nil, nil, nil,
251
+ # nil, nil, nil, nil, "炮", nil, nil, nil, nil,
252
+ # "兵", "砲", "兵", nil, "砲", nil, "兵", nil, "兵",
253
+ # nil, nil, nil, nil, nil, nil, nil, nil, nil,
254
+ # nil, nil, nil, nil, nil, nil, nil, nil, nil,
255
+ # "俥", "傌", "相", "仕", "帥", "仕", "相", "傌", "俥"]
256
+
257
+ last_position.pieces_in_hand_grouped_by_sides
258
+ # => [[], []]
259
+
260
+ last_position.active_side_id
261
+ # => 1
262
+
263
+ last_position.feen(10, 9)
264
+ # => "車,馬,象,士,將,士,象,馬,車/9/9/卒,1,卒,1,炮,1,卒,1,卒/9/4,炮,4/兵,砲,兵,1,砲,1,兵,1,兵/9/9/俥,傌,相,仕,帥,仕,相,傌,俥 1 /"
265
+ ```
266
+
267
+ Let's do some moves on a [Four-player chess](https://en.wikipedia.org/wiki/Four-player_chess) board:
268
+
269
+ ```ruby
270
+ require "qi"
271
+
272
+ starting_position = Qi::Position.new(
273
+ nil , nil , nil , "yR", "yN", "yB", "yK", "yQ", "yB", "yN", "yR", nil , nil , nil ,
274
+ nil , nil , nil , "yP", "yP", "yP", "yP", "yP", "yP", "yP", "yP", nil , nil , nil ,
275
+ nil , nil , nil , nil , nil , nil , nil , nil , nil , nil , nil , nil , nil , nil ,
276
+ "bR", "bP", nil , nil , nil , nil , nil , nil , nil , nil , nil , nil , "gP", "gR",
277
+ "bN", "bP", nil , nil , nil , nil , nil , nil , nil , nil , nil , nil , "gP", "gN",
278
+ "bB", "bP", nil , nil , nil , nil , nil , nil , nil , nil , nil , nil , "gP", "gB",
279
+ "bK", "bP", nil , nil , nil , nil , nil , nil , nil , nil , nil , nil , "gP", "gQ",
280
+ "bQ", "bP", nil , nil , nil , nil , nil , nil , nil , nil , nil , nil , "gP", "gK",
281
+ "bB", "bP", nil , nil , nil , nil , nil , nil , nil , nil , nil , nil , "gP", "gB",
282
+ "bN", "bP", nil , nil , nil , nil , nil , nil , nil , nil , nil , nil , "gP", "gN",
283
+ "bR", "bP", nil , nil , nil , nil , nil , nil , nil , nil , nil , nil , "gP", "gR",
284
+ nil , nil , nil , nil , nil , nil , nil , nil , nil , nil , nil , nil , nil , nil ,
285
+ nil , nil , nil , "rP", "rP", "rP", "rP", "rP", "rP", "rP", "rP", nil , nil , nil ,
286
+ nil , nil , nil , "rR", "rN", "rB", "rQ", "rK", "rB", "rN", "rR", nil , nil , nil ,
287
+ pieces_in_hand_grouped_by_sides: [
288
+ [],
289
+ [],
290
+ [],
291
+ []
292
+ ]
293
+ )
294
+
295
+ starting_position.in_hand_pieces
296
+ # => []
297
+
298
+ starting_position.squares
299
+ # => [nil , nil , nil , "yR", "yN", "yB", "yK", "yQ", "yB", "yN", "yR", nil , nil , nil ,
300
+ # nil , nil , nil , "yP", "yP", "yP", "yP", "yP", "yP", "yP", "yP", nil , nil , nil ,
301
+ # nil , nil , nil , nil , nil , nil , nil , nil , nil , nil , nil , nil , nil , nil ,
302
+ # "bR", "bP", nil , nil , nil , nil , nil , nil , nil , nil , nil , nil , "gP", "gR",
303
+ # "bN", "bP", nil , nil , nil , nil , nil , nil , nil , nil , nil , nil , "gP", "gN",
304
+ # "bB", "bP", nil , nil , nil , nil , nil , nil , nil , nil , nil , nil , "gP", "gB",
305
+ # "bK", "bP", nil , nil , nil , nil , nil , nil , nil , nil , nil , nil , "gP", "gQ",
306
+ # "bQ", "bP", nil , nil , nil , nil , nil , nil , nil , nil , nil , nil , "gP", "gK",
307
+ # "bB", "bP", nil , nil , nil , nil , nil , nil , nil , nil , nil , nil , "gP", "gB",
308
+ # "bN", "bP", nil , nil , nil , nil , nil , nil , nil , nil , nil , nil , "gP", "gN",
309
+ # "bR", "bP", nil , nil , nil , nil , nil , nil , nil , nil , nil , nil , "gP", "gR",
310
+ # nil , nil , nil , nil , nil , nil , nil , nil , nil , nil , nil , nil , nil , nil ,
311
+ # nil , nil , nil , "rP", "rP", "rP", "rP", "rP", "rP", "rP", "rP", nil , nil , nil ,
312
+ # nil , nil , nil , "rR", "rN", "rB", "rQ", "rK", "rB", "rN", "rR", nil , nil , nil]
313
+
314
+ starting_position.pieces_in_hand_grouped_by_sides
315
+ # => [[], [], [], []]
316
+
317
+ starting_position.active_side_id
318
+ # => 0
319
+
320
+ starting_position.feen(14, 14)
321
+ # => "3,yR,yN,yB,yK,yQ,yB,yN,yR,3/3,yP,yP,yP,yP,yP,yP,yP,yP,3/14/bR,bP,10,gP,gR/bN,bP,10,gP,gN/bB,bP,10,gP,gB/bK,bP,10,gP,gQ/bQ,bP,10,gP,gK/bB,bP,10,gP,gB/bN,bP,10,gP,gN/bR,bP,10,gP,gR/14/3,rP,rP,rP,rP,rP,rP,rP,rP,3/3,rR,rN,rB,rQ,rK,rB,rN,rR,3 0 ///"
322
+
323
+ # List of moves (see https://github.com/sashite/pmn.rb)
324
+ moves = [
325
+ [ 175, 147, "rP" ],
326
+ [ 85, 87, "bP" ],
327
+ [ 20, 48, "yP" ],
328
+ [ 110, 108, "gP" ],
329
+ [ 191, 162, "rN" ]
330
+ ]
331
+
332
+ last_position = moves.reduce(starting_position) do |position, move|
333
+ position.call(move)
334
+ end
335
+
336
+ last_position.in_hand_pieces
337
+ # => []
338
+
339
+ last_position.squares
340
+ # => [nil , nil , nil , "yR", "yN", "yB", "yK", "yQ", "yB", "yN", "yR", nil , nil , nil ,
341
+ # nil , nil , nil , "yP", "yP", "yP", nil , "yP", "yP", "yP", "yP", nil , nil , nil ,
342
+ # nil , nil , nil , nil , nil , nil , nil , nil , nil , nil , nil , nil , nil , nil ,
343
+ # "bR", "bP", nil , nil , nil , nil , "yP", nil , nil , nil , nil , nil , "gP", "gR",
344
+ # "bN", "bP", nil , nil , nil , nil , nil , nil , nil , nil , nil , nil , "gP", "gN",
345
+ # "bB", "bP", nil , nil , nil , nil , nil , nil , nil , nil , nil , nil , "gP", "gB",
346
+ # "bK", nil , nil , "bP", nil , nil , nil , nil , nil , nil , nil , nil , "gP", "gQ",
347
+ # "bQ", "bP", nil , nil , nil , nil , nil , nil , nil , nil , "gP", nil , nil , "gK",
348
+ # "bB", "bP", nil , nil , nil , nil , nil , nil , nil , nil , nil , nil , "gP", "gB",
349
+ # "bN", "bP", nil , nil , nil , nil , nil , nil , nil , nil , nil , nil , "gP", "gN",
350
+ # "bR", "bP", nil , nil , nil , nil , nil , "rP", nil , nil , nil , nil , "gP", "gR",
351
+ # nil , nil , nil , nil , nil , nil , nil , nil , "rN", nil , nil , nil , nil , nil ,
352
+ # nil , nil , nil , "rP", "rP", "rP", "rP", nil , "rP", "rP", "rP", nil , nil , nil ,
353
+ # nil , nil , nil , "rR", "rN", "rB", "rQ", "rK", "rB", nil , "rR", nil , nil , nil]
354
+
355
+ last_position.pieces_in_hand_grouped_by_sides
356
+ # => [[], [], [], []]
357
+
358
+ last_position.active_side_id
359
+ # => 1
360
+
361
+ last_position.feen(14, 14)
362
+ # => "3,yR,yN,yB,yK,yQ,yB,yN,yR,3/3,yP,yP,yP,1,yP,yP,yP,yP,3/14/bR,bP,4,yP,5,gP,gR/bN,bP,10,gP,gN/bB,bP,10,gP,gB/bK,2,bP,8,gP,gQ/bQ,bP,8,gP,2,gK/bB,bP,10,gP,gB/bN,bP,10,gP,gN/bR,bP,5,rP,4,gP,gR/8,rN,5/3,rP,rP,rP,rP,1,rP,rP,rP,3/3,rR,rN,rB,rQ,rK,rB,1,rR,3 1 ///"
150
363
  ```
151
364
 
152
365
  ## License
data/lib/qi.rb CHANGED
@@ -4,4 +4,4 @@
4
4
  module Qi
5
5
  end
6
6
 
7
- require_relative 'qi/position'
7
+ require_relative "qi/position"
@@ -5,86 +5,127 @@ module Qi
5
5
  #
6
6
  # @see https://developer.sashite.com/specs/portable-chess-notation
7
7
  class Position
8
- # The list of squares of on the board.
8
+ # Players are identified by a number according to the order in which they
9
+ # traditionally play from the starting position.
9
10
  #
10
- # @!attribute [r] squares
11
- # @return [Array] The list of squares.
12
- attr_reader :squares
11
+ # @!attribute [r] active_side_id
12
+ # @return [Integer] The identifier of the player who must play.
13
+ attr_reader :active_side_id
13
14
 
14
- # The list of pieces in hand owned by the bottomside player.
15
+ # The list of pieces in hand owned by players.
15
16
  #
16
- # @!attribute [r] bottomside_in_hand_pieces
17
- # @return [Array] The list of bottomside's pieces in hand.
18
- attr_reader :bottomside_in_hand_pieces
17
+ # @!attribute [r] pieces_in_hand_grouped_by_sides
18
+ # @return [Array] The list of pieces in hand for each side.
19
+ attr_reader :pieces_in_hand_grouped_by_sides
19
20
 
20
- # The list of pieces in hand owned by the topside player.
21
+ # The list of squares of on the board.
21
22
  #
22
- # @!attribute [r] topside_in_hand_pieces
23
- # @return [Array] The list of topside's pieces in hand.
24
- attr_reader :topside_in_hand_pieces
23
+ # @!attribute [r] squares
24
+ # @return [Array] The list of squares.
25
+ attr_reader :squares
25
26
 
26
27
  # Initialize a position.
27
28
  #
28
29
  # @param squares [Array] The list of squares of on the board.
29
- # @param is_turn_to_topside [Boolean] The player who must play.
30
- # @param bottomside_in_hand_pieces [Array] The list of bottom-side's pieces in hand.
31
- # @param topside_in_hand_pieces [Array] The list of top-side's pieces in hand.
30
+ # @param active_side_id [Integer] The identifier of the player who must play.
31
+ # @param pieces_in_hand_grouped_by_sides [Array] The list of pieces in hand
32
+ # grouped by players.
32
33
  #
33
34
  # @example Chess's starting position
34
35
  # Position.new(
35
- # '', '', '', '', '', '', '', '',
36
- # '', '', '', '', '', '', '', '',
36
+ # "", "", "", "", "", "", "", "",
37
+ # "", "", "", "", "", "", "", "",
37
38
  # nil, nil, nil, nil, nil, nil, nil, nil,
38
39
  # nil, nil, nil, nil, nil, nil, nil, nil,
39
40
  # nil, nil, nil, nil, nil, nil, nil, nil,
40
41
  # nil, nil, nil, nil, nil, nil, nil, nil,
41
- # '', '', '', '', '', '', '', '',
42
- # '', '', '', '', '', '', '', ''
42
+ # "", "", "", "", "", "", "", "",
43
+ # "", "", "", "", "", "", "", ""
44
+ # )
45
+ #
46
+ # @example Four-player chess's starting position
47
+ # Position.new(
48
+ # nil , nil , nil , "yR", "yN", "yB", "yK", "yQ", "yB", "yN", "yR", nil , nil , nil ,
49
+ # nil , nil , nil , "yP", "yP", "yP", "yP", "yP", "yP", "yP", "yP", nil , nil , nil ,
50
+ # nil , nil , nil , nil , nil , nil , nil , nil , nil , nil , nil , nil , nil , nil ,
51
+ # "bR", "bP", nil , nil , nil , nil , nil , nil , nil , nil , nil , nil , "gP", "gR",
52
+ # "bN", "bP", nil , nil , nil , nil , nil , nil , nil , nil , nil , nil , "gP", "gN",
53
+ # "bB", "bP", nil , nil , nil , nil , nil , nil , nil , nil , nil , nil , "gP", "gB",
54
+ # "bK", "bP", nil , nil , nil , nil , nil , nil , nil , nil , nil , nil , "gP", "gQ",
55
+ # "bQ", "bP", nil , nil , nil , nil , nil , nil , nil , nil , nil , nil , "gP", "gK",
56
+ # "bB", "bP", nil , nil , nil , nil , nil , nil , nil , nil , nil , nil , "gP", "gB",
57
+ # "bN", "bP", nil , nil , nil , nil , nil , nil , nil , nil , nil , nil , "gP", "gN",
58
+ # "bR", "bP", nil , nil , nil , nil , nil , nil , nil , nil , nil , nil , "gP", "gR",
59
+ # nil , nil , nil , nil , nil , nil , nil , nil , nil , nil , nil , nil , nil , nil ,
60
+ # nil , nil , nil , "rP", "rP", "rP", "rP", "rP", "rP", "rP", "rP", nil , nil , nil ,
61
+ # nil , nil , nil , "rR", "rN", "rB", "rQ", "rK", "rB", "rN", "rR", nil , nil , nil ,
62
+ # pieces_in_hand_grouped_by_sides: [
63
+ # [],
64
+ # [],
65
+ # [],
66
+ # []
67
+ # ]
43
68
  # )
44
69
  #
45
70
  # @example Makruk's starting position
46
71
  # Position.new(
47
- # '', '', '', '', '', '', '', '',
72
+ # "", "", "", "", "", "", "", "",
48
73
  # nil, nil, nil, nil, nil, nil, nil, nil,
49
- # '', '', '', '', '', '', '', '',
74
+ # "", "", "", "", "", "", "", "",
50
75
  # nil, nil, nil, nil, nil, nil, nil, nil,
51
76
  # nil, nil, nil, nil, nil, nil, nil, nil,
52
- # '', '', '', '', '', '', '', '',
77
+ # "", "", "", "", "", "", "", "",
53
78
  # nil, nil, nil, nil, nil, nil, nil, nil,
54
- # '', '', '', '', '', '', '', ''
79
+ # "", "", "", "", "", "", "", ""
55
80
  # )
56
81
  #
57
82
  # @example Shogi's starting position
58
83
  # Position.new(
59
- # 'l', 'n', 's', 'g', 'k', 'g', 's', 'n', 'l',
60
- # nil, 'r', nil, nil, nil, nil, nil, 'b', nil,
61
- # 'p', 'p', 'p', 'p', 'p', 'p', 'p', 'p', 'p',
84
+ # "l", "n", "s", "g", "k", "g", "s", "n", "l",
85
+ # nil, "r", nil, nil, nil, nil, nil, "b", nil,
86
+ # "p", "p", "p", "p", "p", "p", "p", "p", "p",
62
87
  # nil, nil, nil, nil, nil, nil, nil, nil, nil,
63
88
  # nil, nil, nil, nil, nil, nil, nil, nil, nil,
64
89
  # nil, nil, nil, nil, nil, nil, nil, nil, nil,
65
- # 'P', 'P', 'P', 'P', 'P', 'P', 'P', 'P', 'P',
66
- # nil, 'B', nil, nil, nil, nil, nil, 'R', nil,
67
- # 'L', 'N', 'S', 'G', 'K', 'G', 'S', 'N', 'L'
90
+ # "P", "P", "P", "P", "P", "P", "P", "P", "P",
91
+ # nil, "B", nil, nil, nil, nil, nil, "R", nil,
92
+ # "L", "N", "S", "G", "K", "G", "S", "N", "L"
93
+ # )
94
+ #
95
+ # @example A classic Tsume Shogi problem
96
+ # Position.new(
97
+ # nil, nil, nil, "s", "k", "s", nil, nil, nil,
98
+ # nil, nil, nil, nil, nil, nil, nil, nil, nil,
99
+ # nil, nil, nil, nil, "+P", nil, nil, nil, nil,
100
+ # nil, nil, nil, nil, nil, nil, nil, nil, nil,
101
+ # nil, nil, nil, nil, nil, nil, nil, "+B", nil,
102
+ # nil, nil, nil, nil, nil, nil, nil, nil, nil,
103
+ # nil, nil, nil, nil, nil, nil, nil, nil, nil,
104
+ # nil, nil, nil, nil, nil, nil, nil, nil, nil,
105
+ # nil, nil, nil, nil, nil, nil, nil, nil, nil,
106
+ # pieces_in_hand_grouped_by_sides: [
107
+ # ["S"],
108
+ # ["b", "g", "g", "g", "g", "n", "n", "n", "n", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "r", "r", "s"]
109
+ # ]
68
110
  # )
69
111
  #
70
112
  # @example Xiangqi's starting position
71
113
  # Position.new(
72
- # '', '', '', '', '', '', '', '', '',
114
+ # "", "", "", "", "", "", "", "", "",
73
115
  # nil, nil, nil, nil, nil, nil, nil, nil, nil,
74
- # nil, '', nil, nil, nil, nil, nil, '', nil,
75
- # '', nil, '', nil, '', nil, '', nil, '',
116
+ # nil, "", nil, nil, nil, nil, nil, "", nil,
117
+ # "", nil, "", nil, "", nil, "", nil, "",
76
118
  # nil, nil, nil, nil, nil, nil, nil, nil, nil,
77
119
  # nil, nil, nil, nil, nil, nil, nil, nil, nil,
78
- # '', nil, '', nil, '', nil, '', nil, '',
79
- # nil, '', nil, nil, nil, nil, nil, '', nil,
120
+ # "", nil, "", nil, "", nil, "", nil, "",
121
+ # nil, "", nil, nil, nil, nil, nil, "", nil,
80
122
  # nil, nil, nil, nil, nil, nil, nil, nil, nil,
81
- # '', '', '', '', '', '', '', '', ''
123
+ # "", "", "", "", "", "", "", "", ""
82
124
  # )
83
- def initialize(*squares, is_turn_to_topside: false, bottomside_in_hand_pieces: [], topside_in_hand_pieces: [])
84
- @squares = squares
85
- @is_turn_to_topside = is_turn_to_topside
86
- @bottomside_in_hand_pieces = bottomside_in_hand_pieces
87
- @topside_in_hand_pieces = topside_in_hand_pieces
125
+ def initialize(*squares, active_side_id: 0, pieces_in_hand_grouped_by_sides: [[], []])
126
+ @squares = squares
127
+ @active_side_id = active_side_id % pieces_in_hand_grouped_by_sides.length
128
+ @pieces_in_hand_grouped_by_sides = pieces_in_hand_grouped_by_sides
88
129
 
89
130
  freeze
90
131
  end
@@ -96,8 +137,7 @@ module Qi
96
137
  # @return [Position] The new position.
97
138
  def call(move)
98
139
  updated_squares = squares.dup
99
- updated_bottomside_in_hand_pieces = bottomside_in_hand_pieces.dup
100
- updated_topside_in_hand_pieces = topside_in_hand_pieces.dup
140
+ updated_in_hand_pieces = in_hand_pieces.dup
101
141
 
102
142
  actions = move.each_slice(4)
103
143
 
@@ -108,13 +148,8 @@ module Qi
108
148
  captured_piece_name = action.fetch(3, nil)
109
149
 
110
150
  if src_square_id.nil?
111
- if turn_to_topside?
112
- piece_in_hand_id = updated_topside_in_hand_pieces.index(moved_piece_name)
113
- updated_topside_in_hand_pieces.delete_at(piece_in_hand_id)
114
- else
115
- piece_in_hand_id = updated_bottomside_in_hand_pieces.index(moved_piece_name)
116
- updated_bottomside_in_hand_pieces.delete_at(piece_in_hand_id)
117
- end
151
+ piece_in_hand_id = updated_in_hand_pieces.index(moved_piece_name)
152
+ updated_in_hand_pieces.delete_at(piece_in_hand_id)
118
153
  else
119
154
  updated_squares[src_square_id] = nil
120
155
  end
@@ -122,17 +157,17 @@ module Qi
122
157
  updated_squares[dst_square_id] = moved_piece_name
123
158
 
124
159
  unless captured_piece_name.nil?
125
- if turn_to_topside?
126
- updated_topside_in_hand_pieces.push(captured_piece_name)
127
- else
128
- updated_bottomside_in_hand_pieces.push(captured_piece_name)
129
- end
160
+ updated_in_hand_pieces.push(captured_piece_name)
130
161
  end
131
162
  end
132
163
 
133
- self.class.new(*updated_squares, is_turn_to_topside: !turn_to_topside?,
134
- bottomside_in_hand_pieces: updated_bottomside_in_hand_pieces,
135
- topside_in_hand_pieces: updated_topside_in_hand_pieces)
164
+ updated_pieces_in_hand_grouped_by_sides = pieces_in_hand_grouped_by_sides.dup
165
+ updated_pieces_in_hand_grouped_by_sides[active_side_id] = updated_in_hand_pieces
166
+
167
+ self.class.new(*updated_squares,
168
+ active_side_id: active_side_id.next,
169
+ pieces_in_hand_grouped_by_sides: updated_pieces_in_hand_grouped_by_sides
170
+ )
136
171
  end
137
172
 
138
173
  # The list of pieces in hand owned by the current player.
@@ -140,14 +175,28 @@ module Qi
140
175
  # @return [Array] Topside's pieces in hand if turn to topside, bottomside's
141
176
  # ones otherwise.
142
177
  def in_hand_pieces
143
- turn_to_topside? ? topside_in_hand_pieces : bottomside_in_hand_pieces
178
+ pieces_in_hand_grouped_by_sides.fetch(active_side_id)
144
179
  end
145
180
 
146
- # The side who must play.
181
+ # Forsyth–Edwards Expanded Notation.
182
+ #
183
+ # @see https://developer.sashite.com/specs/forsyth-edwards-expanded-notation
147
184
  #
148
- # @return [Boolean] True if it is turn to topside, false otherwise.
149
- def turn_to_topside?
150
- @is_turn_to_topside
185
+ # @param indexes [Array] The shape of the board.
186
+ #
187
+ # @example Generate the FEEN string of a Xiangqi position
188
+ # feen(10, 9)
189
+ #
190
+ # @return [String] The FEEN representation of the position.
191
+ def feen(*indexes)
192
+ ::FEEN.dump(
193
+ active_side_id: active_side_id,
194
+ board: squares.each_with_index.inject({}) { |h, (v, i)| v.nil? ? h : h.merge(i.to_s.to_sym => v) },
195
+ indexes: indexes,
196
+ pieces_in_hand_grouped_by_sides: pieces_in_hand_grouped_by_sides
197
+ )
151
198
  end
152
199
  end
153
200
  end
201
+
202
+ require "feen"
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: qi
3
3
  version: !ruby/object:Gem::Version
4
- version: 6.1.1
4
+ version: 6.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Cyril Kato
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-07-27 00:00:00.000000000 Z
11
+ date: 2020-08-25 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: feen
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: brutal
15
29
  requirement: !ruby/object:Gem::Requirement