qi 10.0.0.beta9 → 10.0.0.beta10
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +11 -9
- data/lib/qi/error/drop.rb +5 -0
- data/lib/qi.rb +153 -70
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a009476d5905ba232aed52cafd6f8331632167af3a56c331be1d057b4a1ec964
|
4
|
+
data.tar.gz: f9a652c101b851e7e07d3b5fd69df6cb1965351697163e73c9600ff4b05251f4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 31c3aa57ebd829dcf24ca9604ef9a17dfb6a44fd0fd727dae76a730d3908c2f8cd2ba17d6dbc037ca9de88ac5913b4b4e759a967426d3e0c39b2100abb4891b1
|
7
|
+
data.tar.gz: 7b95a0bfdddd2be4fcdda01b64b2e42fc120f84d8becc5a5561383a41d88394a8d3a4e7b58a6d0721505d316c5e6e1357a0ddfff28f4179dec25c6af8e4b7688
|
data/README.md
CHANGED
@@ -13,7 +13,7 @@
|
|
13
13
|
Add this line to your application's Gemfile:
|
14
14
|
|
15
15
|
```ruby
|
16
|
-
gem "qi", ">= 10.0.0.
|
16
|
+
gem "qi", ">= 10.0.0.beta10"
|
17
17
|
```
|
18
18
|
|
19
19
|
And then execute:
|
@@ -38,37 +38,39 @@ north_captures = %w[r r b g g g g s n n n n p p p p p p p p p p p p p p p p p]
|
|
38
38
|
south_captures = %w[S]
|
39
39
|
squares = { 3 => "s", 4 => "k", 5 => "s", 22 => "+P", 43 => "+B" }
|
40
40
|
|
41
|
-
qi0 = Qi.new(is_north_turn, north_captures, south_captures, squares)
|
41
|
+
qi0 = Qi.new(is_north_turn, north_captures, south_captures, squares, false)
|
42
42
|
|
43
43
|
qi0.north_captures # => ["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"]
|
44
44
|
qi0.south_captures # => ["S"]
|
45
45
|
qi0.squares # => {3=>"s", 4=>"k", 5=>"s", 22=>"+P", 43=>"+B"}
|
46
46
|
qi0.north_turn? # => false
|
47
47
|
qi0.south_turn? # => true
|
48
|
-
qi0.serialize # => "
|
49
|
-
qi0.inspect # => "<Qi
|
48
|
+
qi0.serialize # => "South-turn===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===3:s,4:k,5:s,22:+P,43:+B===not-in-check"
|
49
|
+
qi0.inspect # => "<Qi South-turn===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===3:s,4:k,5:s,22:+P,43:+B===not-in-check>"
|
50
50
|
|
51
51
|
qi0.to_a
|
52
52
|
# [false,
|
53
53
|
# ["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"],
|
54
54
|
# ["S"],
|
55
|
-
# {3=>"s", 4=>"k", 5=>"s", 22=>"+P", 43=>"+B"}
|
55
|
+
# {3=>"s", 4=>"k", 5=>"s", 22=>"+P", 43=>"+B"},
|
56
|
+
# false]
|
56
57
|
|
57
|
-
qi1 = qi0.commit({ 43 => nil, 13 => "+B" })
|
58
|
+
qi1 = qi0.commit({ 43 => nil, 13 => "+B" }, nil, is_drop: nil, is_in_check: true)
|
58
59
|
|
59
60
|
qi1.north_captures # => ["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"]
|
60
61
|
qi1.south_captures # => ["S"]
|
61
62
|
qi1.squares # => {3=>"s", 4=>"k", 5=>"s", 22=>"+P", 13=>"+B"}
|
62
63
|
qi1.north_turn? # => true
|
63
64
|
qi1.south_turn? # => false
|
64
|
-
qi1.serialize # => "
|
65
|
-
qi1.inspect # => "<Qi
|
65
|
+
qi1.serialize # => "North-turn===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===3:s,4:k,5:s,22:+P,13:+B===in-check"
|
66
|
+
qi1.inspect # => "<Qi North-turn===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===3:s,4:k,5:s,22:+P,13:+B===in-check>"
|
66
67
|
|
67
68
|
qi1.to_a
|
68
69
|
# [true,
|
69
70
|
# ["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"],
|
70
71
|
# ["S"],
|
71
|
-
# {3=>"s", 4=>"k", 5=>"s", 22=>"+P", 13=>"+B"}
|
72
|
+
# {3=>"s", 4=>"k", 5=>"s", 22=>"+P", 13=>"+B"},
|
73
|
+
# true]
|
72
74
|
```
|
73
75
|
|
74
76
|
## License
|
data/lib/qi/error/drop.rb
CHANGED
@@ -1,7 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
class Qi
|
4
|
+
# The Error module contains custom error classes for the Qi game.
|
4
5
|
module Error
|
6
|
+
# The Drop class is a custom error class that inherits from IndexError.
|
7
|
+
# It is raised when a drop operation in the game of Qi is invalid.
|
8
|
+
#
|
9
|
+
# @see IndexError
|
5
10
|
class Drop < ::IndexError
|
6
11
|
end
|
7
12
|
end
|
data/lib/qi.rb
CHANGED
@@ -3,45 +3,69 @@
|
|
3
3
|
require "digest"
|
4
4
|
require_relative "qi/error/drop"
|
5
5
|
|
6
|
-
#
|
6
|
+
# The Qi class represents the current state of a game, tracking both the positions of pieces on the board and the pieces captured by each player.
|
7
|
+
# It provides methods for manipulating the game state, including moving pieces around the board, capturing opponent's pieces, and dropping pieces from hand onto the board.
|
8
|
+
# Additionally, it maintains information about the current game turn (which player's turn it is) and whether a player is in check.
|
7
9
|
class Qi
|
10
|
+
# Constant representing the North player.
|
11
|
+
North = "North"
|
12
|
+
|
13
|
+
# Constant representing the South player.
|
14
|
+
South = "South"
|
15
|
+
|
8
16
|
# @!attribute [r] north_captures
|
9
|
-
#
|
17
|
+
# @return [Array] The list of pieces captured by the North player.
|
18
|
+
attr_reader :north_captures
|
19
|
+
|
10
20
|
# @!attribute [r] south_captures
|
11
|
-
#
|
21
|
+
# @return [Array] The list of pieces captured by the South player.
|
22
|
+
attr_reader :south_captures
|
23
|
+
|
12
24
|
# @!attribute [r] squares
|
13
|
-
#
|
14
|
-
attr_reader :
|
25
|
+
# @return [Hash] The current state of the board, represented as a hash where each key is a position and each value is the state of that position.
|
26
|
+
attr_reader :squares
|
15
27
|
|
16
|
-
# Initializes a new
|
28
|
+
# Initializes a new instance of the game state.
|
17
29
|
#
|
18
|
-
# @param is_north_turn [Boolean]
|
19
|
-
# @param north_captures [Array
|
20
|
-
# @param south_captures [Array
|
21
|
-
# @param squares [Hash
|
22
|
-
|
23
|
-
|
30
|
+
# @param is_north_turn [Boolean] True if it's North's turn, false otherwise.
|
31
|
+
# @param north_captures [Array] An array representing the pieces captured by North.
|
32
|
+
# @param south_captures [Array] An array representing the pieces captured by South.
|
33
|
+
# @param squares [Hash] A hash representing the state of the squares on the board.
|
34
|
+
# @param is_in_check [Boolean] True if the current player is in check, false otherwise.
|
35
|
+
def initialize(is_north_turn, north_captures, south_captures, squares, is_in_check)
|
24
36
|
@is_north_turn = is_north_turn
|
25
37
|
@north_captures = north_captures.sort
|
26
38
|
@south_captures = south_captures.sort
|
27
39
|
@squares = squares.compact
|
40
|
+
@is_in_check = is_in_check
|
28
41
|
end
|
29
42
|
|
30
|
-
#
|
43
|
+
# Creates a new game state by applying a set of diffs to the squares and the captures.
|
44
|
+
# Raises an error if in_hand is provided but is_drop is not, or vice versa.
|
31
45
|
#
|
32
|
-
# @param diffs [Hash
|
33
|
-
# @param in_hand [
|
34
|
-
# @param is_drop [Boolean]
|
35
|
-
# @
|
36
|
-
#
|
37
|
-
#
|
38
|
-
|
39
|
-
|
46
|
+
# @param diffs [Hash] A hash representing the changes to the squares. Each key is a position, and each value is the new state of that position.
|
47
|
+
# @param in_hand [String, nil] A string representing the piece that the current player has in hand, or nil if no piece is in hand.
|
48
|
+
# @param is_drop [Boolean, nil] True if the current player is dropping a piece, false if they are not, or nil if there is no piece in hand.
|
49
|
+
# @param is_in_check [Boolean] True if the current player is in check after the move, false otherwise.
|
50
|
+
# @return [Qi] The new game state after applying the diffs.
|
51
|
+
# @raise [ArgumentError] If in_hand is provided but is_drop is not, or vice versa.
|
52
|
+
def commit(diffs, in_hand, is_drop:, is_in_check:)
|
53
|
+
if !in_hand.nil? && is_drop.nil?
|
54
|
+
raise ::ArgumentError, "A piece is in hand, but is_drop is not provided"
|
55
|
+
elsif in_hand.nil? && !is_drop.nil?
|
56
|
+
raise ::ArgumentError, "No piece is in hand, but is_drop is provided"
|
57
|
+
end
|
58
|
+
|
40
59
|
modified_squares = squares.merge(diffs)
|
41
60
|
modified_captures = update_captures(in_hand, is_drop:)
|
42
|
-
self.class.new(
|
61
|
+
self.class.new(south_turn?, *modified_captures, modified_squares, is_in_check)
|
43
62
|
end
|
44
63
|
|
64
|
+
# Checks if this game state is equal to another.
|
65
|
+
# Two game states are considered equal if they can be serialized to the same string.
|
66
|
+
#
|
67
|
+
# @param other [Object] The object to compare with this game state.
|
68
|
+
# @return [Boolean] True if the other object can be serialized and its serialized form is equal to this game state's serialized form, false otherwise.
|
45
69
|
def eql?(other)
|
46
70
|
return false unless other.respond_to?(:serialize)
|
47
71
|
|
@@ -49,7 +73,21 @@ class Qi
|
|
49
73
|
end
|
50
74
|
alias == eql?
|
51
75
|
|
52
|
-
|
76
|
+
# Returns the captures of the current player.
|
77
|
+
#
|
78
|
+
# @return [Array] An array or other iterable representing the pieces captured by the current player.
|
79
|
+
def current_captures
|
80
|
+
if north_turn?
|
81
|
+
north_captures
|
82
|
+
else
|
83
|
+
south_captures
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# Returns the list of pieces that the current player's opponent has captured.
|
88
|
+
#
|
89
|
+
# @return [Array] An array of pieces that the opponent has captured.
|
90
|
+
def opponent_captures
|
53
91
|
if north_turn?
|
54
92
|
south_captures
|
55
93
|
else
|
@@ -57,65 +95,98 @@ class Qi
|
|
57
95
|
end
|
58
96
|
end
|
59
97
|
|
60
|
-
|
98
|
+
# Returns the name of the current side.
|
99
|
+
#
|
100
|
+
# @return [String] "North" if it's North's turn, "South" otherwise.
|
101
|
+
def current_turn
|
61
102
|
if north_turn?
|
62
|
-
|
103
|
+
North
|
63
104
|
else
|
64
|
-
|
105
|
+
South
|
65
106
|
end
|
66
107
|
end
|
67
108
|
|
68
|
-
|
109
|
+
# Returns the name of the next side.
|
110
|
+
# If it's currently the North player's turn, this method will return "South", and vice versa.
|
111
|
+
#
|
112
|
+
# @return [String] "South" if it's North's turn, "North" otherwise.
|
113
|
+
def next_turn
|
69
114
|
if north_turn?
|
70
|
-
|
115
|
+
South
|
71
116
|
else
|
72
|
-
|
117
|
+
North
|
73
118
|
end
|
74
119
|
end
|
75
120
|
|
76
|
-
# Checks if it
|
121
|
+
# Checks if it's North's turn.
|
77
122
|
#
|
78
|
-
# @return [Boolean]
|
123
|
+
# @return [Boolean] True if it's North's turn, false otherwise.
|
79
124
|
def north_turn?
|
80
125
|
@is_north_turn
|
81
126
|
end
|
82
127
|
|
83
|
-
# Checks if it
|
128
|
+
# Checks if it's South's turn.
|
84
129
|
#
|
85
|
-
# @return [Boolean]
|
130
|
+
# @return [Boolean] True if it's South's turn, false otherwise.
|
86
131
|
def south_turn?
|
87
132
|
!north_turn?
|
88
133
|
end
|
89
134
|
|
90
|
-
#
|
135
|
+
# Checks if the current player is in check.
|
136
|
+
#
|
137
|
+
# @return [Boolean] True if the current player is in check, false otherwise.
|
138
|
+
def in_check?
|
139
|
+
@is_in_check
|
140
|
+
end
|
141
|
+
|
142
|
+
# Checks if the current player is not in check.
|
143
|
+
#
|
144
|
+
# @return [Boolean] True if the current player is not in check, false otherwise.
|
145
|
+
def not_in_check?
|
146
|
+
!in_check?
|
147
|
+
end
|
148
|
+
|
149
|
+
# Converts the current game state to an array. The resulting array includes:
|
150
|
+
# whether it's North's turn, the pieces captured by North, the pieces captured by South,
|
151
|
+
# the state of the squares, and whether the current player is in check.
|
152
|
+
# This array can be used for various purposes, such as saving the game state,
|
153
|
+
# transmitting the game state over a network, or analyzing the game state.
|
91
154
|
#
|
92
|
-
# @return [Array
|
93
|
-
# -
|
94
|
-
# -
|
95
|
-
# -
|
96
|
-
# -
|
155
|
+
# @return [Array] The game state, represented as an array. The elements of the array are:
|
156
|
+
# - A boolean indicating whether it's North's turn,
|
157
|
+
# - An array or other iterable representing the pieces captured by North,
|
158
|
+
# - An array or other iterable representing the pieces captured by South,
|
159
|
+
# - A data structure representing the state of the squares on the board,
|
160
|
+
# - A boolean indicating whether the current player is in check.
|
97
161
|
def to_a
|
98
162
|
[
|
99
163
|
north_turn?,
|
100
164
|
north_captures,
|
101
165
|
south_captures,
|
102
|
-
squares
|
166
|
+
squares,
|
167
|
+
in_check?
|
103
168
|
]
|
104
169
|
end
|
105
170
|
|
106
|
-
#
|
171
|
+
# Converts the current game state to a hash. The resulting hash includes:
|
172
|
+
# whether it's North's turn, the pieces captured by North, the pieces captured by South,
|
173
|
+
# the state of the squares, and whether the current player is in check.
|
174
|
+
# This hash can be used for various purposes, such as saving the game state,
|
175
|
+
# transmitting the game state over a network, or analyzing the game state.
|
107
176
|
#
|
108
|
-
# @return [Hash
|
109
|
-
# - is_north_turn
|
110
|
-
# - north_captures
|
111
|
-
# - south_captures
|
112
|
-
# - squares
|
177
|
+
# @return [Hash] The game state, represented as a hash. The keys of the hash are:
|
178
|
+
# - :is_north_turn, a boolean indicating whether it's North's turn,
|
179
|
+
# - :north_captures, an array or other iterable representing the pieces captured by North,
|
180
|
+
# - :south_captures, an array or other iterable representing the pieces captured by South,
|
181
|
+
# - :squares, a data structure representing the state of the squares on the board,
|
182
|
+
# - :is_in_check, a boolean indicating whether the current player is in check.
|
113
183
|
def to_h
|
114
184
|
{
|
115
185
|
is_north_turn: north_turn?,
|
116
186
|
north_captures:,
|
117
187
|
south_captures:,
|
118
|
-
squares
|
188
|
+
squares:,
|
189
|
+
is_in_check: in_check?
|
119
190
|
}
|
120
191
|
end
|
121
192
|
|
@@ -124,55 +195,67 @@ class Qi
|
|
124
195
|
::Digest::SHA256.hexdigest(serialize)
|
125
196
|
end
|
126
197
|
|
127
|
-
# Returns a
|
198
|
+
# Returns a sorted list of all pieces currently on the board.
|
199
|
+
#
|
200
|
+
# @return [Array] An array of all pieces currently on the board.
|
201
|
+
def board_pieces
|
202
|
+
squares.keys.sort
|
203
|
+
end
|
204
|
+
|
205
|
+
# Serialize the current game state to a string. The serialized state includes:
|
206
|
+
# the current turn, the captured pieces, the state of the squares, and whether
|
207
|
+
# the current player is in check. The serialized state can be used to save
|
208
|
+
# the game, or to transmit the game state over a network.
|
128
209
|
#
|
129
|
-
# @return [String]
|
130
|
-
# - the current turn, either "NorthTurn" or "SouthTurn"
|
131
|
-
# - the captures, sorted and joined by ","
|
132
|
-
# - the squares, mapped to "coordinate:piece" pairs and joined by ","
|
210
|
+
# @return [String] The serialized game state.
|
133
211
|
def serialize
|
134
|
-
serialized_turn = "#{
|
212
|
+
serialized_turn = "#{current_turn}-turn"
|
135
213
|
serialized_captures = (north_captures + south_captures).sort.join(",")
|
136
|
-
serialized_squares =
|
214
|
+
serialized_squares = board_pieces.map { |i| "#{i}:#{squares.fetch(i)}" }.join(",")
|
215
|
+
serialized_check = (in_check? ? "in-check" : "not-in-check")
|
137
216
|
|
138
|
-
"#{serialized_turn}===#{serialized_captures}===#{serialized_squares}"
|
217
|
+
"#{serialized_turn}===#{serialized_captures}===#{serialized_squares}===#{serialized_check}"
|
139
218
|
end
|
140
219
|
|
141
|
-
#
|
220
|
+
# Provides a string representation of the game state, including the class name and the serialized game state.
|
142
221
|
#
|
143
|
-
# @return [String]
|
222
|
+
# @return [String] A string representation of the game state.
|
144
223
|
def inspect
|
145
224
|
"<#{self.class} #{serialize}>"
|
146
225
|
end
|
147
226
|
|
148
227
|
private
|
149
228
|
|
150
|
-
# Updates the captures
|
229
|
+
# Updates the list of captures based on the piece in hand and whether the move is a drop.
|
230
|
+
# If the in_hand parameter is nil, the method returns the current captures without modification.
|
231
|
+
# Otherwise, the method either adds the piece in hand to the current player's captures (if is_drop is false),
|
232
|
+
# or removes the piece in hand from the current player's captures (if is_drop is true).
|
151
233
|
#
|
152
|
-
# @param in_hand [
|
153
|
-
# @param is_drop [Boolean]
|
154
|
-
# @return [Array]
|
234
|
+
# @param in_hand [String, nil] The piece in hand, or nil if no piece is in hand.
|
235
|
+
# @param is_drop [Boolean] True if the current move is a drop, false otherwise.
|
236
|
+
# @return [Array] The updated list of captures for North and South.
|
155
237
|
def update_captures(in_hand, is_drop:)
|
156
238
|
return [north_captures, south_captures] if in_hand.nil?
|
157
239
|
|
158
240
|
captures = if is_drop
|
159
|
-
remove_from_captures(in_hand, *
|
241
|
+
remove_from_captures(in_hand, *current_captures)
|
160
242
|
else
|
161
|
-
|
243
|
+
current_captures + [in_hand]
|
162
244
|
end
|
163
245
|
|
164
|
-
north_turn? ? [captures,
|
246
|
+
north_turn? ? [captures, opponent_captures] : [opponent_captures, captures]
|
165
247
|
end
|
166
248
|
|
167
|
-
# Removes
|
249
|
+
# Removes a piece from the captures.
|
250
|
+
# The method raises an error if the piece is not found in the captures.
|
168
251
|
#
|
169
|
-
# @param piece [
|
170
|
-
# @param captures [Array
|
171
|
-
# @return [Array
|
172
|
-
# @raise [
|
252
|
+
# @param piece [String] The piece to remove from the captures.
|
253
|
+
# @param captures [Array] The list of captures.
|
254
|
+
# @return [Array] The updated list of captures.
|
255
|
+
# @raise [Error::Drop] If the piece is not found in the captures.
|
173
256
|
def remove_from_captures(piece, *captures)
|
174
257
|
index = captures.rindex(piece)
|
175
|
-
raise Error::Drop, "There are no #{piece} in hand
|
258
|
+
raise Error::Drop, "There are no #{piece} in hand" if index.nil?
|
176
259
|
|
177
260
|
captures.delete_at(index)
|
178
261
|
captures
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: qi
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 10.0.0.
|
4
|
+
version: 10.0.0.beta10
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Cyril Kato
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-05-
|
11
|
+
date: 2023-05-11 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: An abstraction that could help to update positions for games like Shogi.
|
14
14
|
email: contact@cyril.email
|
@@ -40,7 +40,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
40
40
|
- !ruby/object:Gem::Version
|
41
41
|
version: 1.3.1
|
42
42
|
requirements: []
|
43
|
-
rubygems_version: 3.4.
|
43
|
+
rubygems_version: 3.4.10
|
44
44
|
signing_key:
|
45
45
|
specification_version: 4
|
46
46
|
summary: An abstraction that could help to update positions for games like Shogi.
|