qi 6.1.1 → 6.2.0

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.
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