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.
Files changed (5) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +11 -9
  3. data/lib/qi/error/drop.rb +5 -0
  4. data/lib/qi.rb +153 -70
  5. metadata +3 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 807fc7b14621e81663ea476f2d737352d455d4d2ab1d49efbe722c3ce571ed15
4
- data.tar.gz: 9244deacf36f2717897a23b56de132af40ce308cd5977ee92899f57c49368f0e
3
+ metadata.gz: a009476d5905ba232aed52cafd6f8331632167af3a56c331be1d057b4a1ec964
4
+ data.tar.gz: f9a652c101b851e7e07d3b5fd69df6cb1965351697163e73c9600ff4b05251f4
5
5
  SHA512:
6
- metadata.gz: 87cedbb94ddcecb3026647893aac63ce6559c92d37dc65121850efca0eb1ddd7a1616e441a0c40463a2bbe664de59d4498bbcfcfd3aa3caae6075a65851c57c5
7
- data.tar.gz: 62a213dcaeb4d68cc6c4bc01b4d9b4a2df33b3d8bcca09c5e494e6a5b0eb349b236907437caa12c11777c6eaac559402e927f3db972f89acb4cd07f77408c4d9
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.beta9"
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 # => "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"
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>"
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 # => "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"
65
- 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>"
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
- # A class that represents the state of a game.
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
- # @return [Array] an array of pieces captured by the north player
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
- # @return [Array] an array of pieces captured by the south player
21
+ # @return [Array] The list of pieces captured by the South player.
22
+ attr_reader :south_captures
23
+
12
24
  # @!attribute [r] squares
13
- # @return [Hash] a hash of pieces on the board
14
- attr_reader :north_captures, :south_captures, :squares
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 Qi object with the given attributes.
28
+ # Initializes a new instance of the game state.
17
29
  #
18
- # @param is_north_turn [Boolean] a boolean value indicating whose turn it is
19
- # @param north_captures [Array<Object>] an array of pieces captured by the north player
20
- # @param south_captures [Array<Object>] an array of pieces captured by the south player
21
- # @param squares [Hash<Object, Object>] a hash of squares on the board
22
- def initialize(is_north_turn, north_captures, south_captures, squares)
23
- # Assign the parameters to instance variables.
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
- # Returns a new Qi object that represents the state after applying the given changes.
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<Object, Object>] a hash of changes to apply to the squares hash
33
- # @param in_hand [Object, nil] the piece that is in hand or nil if none
34
- # @param is_drop [Boolean] a boolean value indicating whether the in hand piece is dropped or not
35
- # @return [Qi] a new Qi object with modified attributes, where:
36
- # - the turn is switched
37
- # - the captures are updated according to the in hand piece and the drop flag
38
- # - the squares are merged with the diffs hash
39
- def commit(diffs = {}, in_hand = nil, is_drop: false)
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(!north_turn?, *modified_captures, modified_squares)
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
- def other_captures
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
- def owned_captures
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
- north_captures
103
+ North
63
104
  else
64
- south_captures
105
+ South
65
106
  end
66
107
  end
67
108
 
68
- def side_name
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
- "north"
115
+ South
71
116
  else
72
- "south"
117
+ North
73
118
  end
74
119
  end
75
120
 
76
- # Checks if it is the north turn or not.
121
+ # Checks if it's North's turn.
77
122
  #
78
- # @return [Boolean] true if it is the north turn and false otherwise
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 is not the north turn or not.
128
+ # Checks if it's South's turn.
84
129
  #
85
- # @return [Boolean] true if it is not the north turn and false otherwise
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
- # Returns an array representation of the Qi object's attributes.
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(Boolean, Array<Object>, Array<Object>, Hash<Object, Object>)] an array containing four elements:
93
- # - a boolean value indicating whose turn it is
94
- # - an array of pieces captured by the north player
95
- # - an array of pieces captured by the south player
96
- # - a hash of squares on the board
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
- # Returns a hash representation of the Qi object's attributes.
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{Symbol => Object}] a hash containing four key-value pairs:
109
- # - is_north_turn: a boolean value indicating whose turn it is
110
- # - north_captures: an array of pieces captured by the north player
111
- # - south_captures: an array of pieces captured by the south player
112
- # - squares: a hash of squares on the board
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 string representation of the Qi object's attributes.
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] a string containing three parts separated by "===":
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 = "#{side_name}-turn"
212
+ serialized_turn = "#{current_turn}-turn"
135
213
  serialized_captures = (north_captures + south_captures).sort.join(",")
136
- serialized_squares = squares.keys.map { |i| "#{i}:#{squares.fetch(i)}" }.join(",")
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
- # Returns a human-readable representation of the Qi object.
220
+ # Provides a string representation of the game state, including the class name and the serialized game state.
142
221
  #
143
- # @return [String] a string containing the class name and the serialized attributes
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 arrays based on the piece in hand and whether it is dropped or not.
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 [Object, nil] the piece that is in hand or nil if none
153
- # @param is_drop [Boolean] a boolean value indicating whether the in hand piece is dropped or not
154
- # @return [Array] an array containing the updated north and south captures arrays
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, *owned_captures)
241
+ remove_from_captures(in_hand, *current_captures)
160
242
  else
161
- owned_captures + [in_hand]
243
+ current_captures + [in_hand]
162
244
  end
163
245
 
164
- north_turn? ? [captures, other_captures] : [other_captures, captures]
246
+ north_turn? ? [captures, opponent_captures] : [opponent_captures, captures]
165
247
  end
166
248
 
167
- # Removes the last occurrence of a piece from an array of captures and returns the modified array.
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 [Object] the piece to be removed
170
- # @param captures [Array<Object>] the array of captures
171
- # @return [Array<Object>] the modified array of captures
172
- # @raise [Qi::Error::Drop] if the piece is not found in the array
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." if index.nil?
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.beta9
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-06 00:00:00.000000000 Z
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.6
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.