qi 10.0.0.beta11 → 10.0.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 +68 -42
  3. data/lib/qi.rb +91 -242
  4. metadata +7 -7
  5. data/lib/qi/error/drop.rb +0 -13
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 79ffab5e654d14cf9f3f7dac1c50bc5b5c612452d0fdea35a00d3529337fbff2
4
- data.tar.gz: b8fecf9ff5dd97923070809b6a906c8b44cebbee9695ea242e314d53afb9fad8
3
+ metadata.gz: 4a98ed653f1c1d21ae215f4a8ab481a0a19dd93551a9a3ba0faf9ee8e541291f
4
+ data.tar.gz: d1291d436f366c70e07a64a3a6390a3a9d99d56cfeb8b0b85c1002addbe7b540
5
5
  SHA512:
6
- metadata.gz: cb36bd477ba3e756b116e3f2ab55bdb15ea50834461cc614865d8fdda56e271eff4675d998172bd766b8ca33e6944be3bbf567a1eba96b6215816234c485f6c9
7
- data.tar.gz: e56b5f73bf2c0a1a3a256f813acfcf24af3bc3cdd570f2fb6ab1aa2f7ebc194ddd0b82e5ade5cbc052229377a8c768d3f49f45188d811a756ab7781481c69f08
6
+ metadata.gz: 154aa839e292e2cadfbafe8c64e324285216da01a491ef98815479bf1bd735dc2ec747ad31e6ef479ea4d913b262dbcb9ad6399f13cdf5199dddb3403d7091ee
7
+ data.tar.gz: 52c5ca680fb11577fc2aa84931900b9f8c712e6c300ca5774f918f144fd1c999f1e34387e8827fe5f7f3d55ed192d04dabb310acc538c19bee3b941effb68637
data/README.md CHANGED
@@ -6,14 +6,28 @@
6
6
  [![RuboCop](https://github.com/sashite/qi.rb/workflows/RuboCop/badge.svg?branch=main)](https://github.com/sashite/qi.rb/actions?query=workflow%3Arubocop+branch%3Amain)
7
7
  [![License](https://img.shields.io/github/license/sashite/qi.rb?label=License&logo=github)](https://github.com/sashite/qi.rb/raw/main/LICENSE.md)
8
8
 
9
- > `Qi` (Chinese: 棋; pinyin: _qí_) is an abstraction that could help to update positions for games like Shogi.
9
+ **Qi** (Chinese: 棋; pinyin: _qí_) is a lightweight, flexible, and adaptable tool for representing board game positions, built in Ruby. It is designed to be game-agnostic and can be used with a variety of board games such as Chess, Four-Player Chess, Go, Makruk, Shogi, and Xiangqi.
10
+
11
+ Qi uses a unique approach where the state of a game is represented through capturing the pieces in play, the arrangement of pieces on the board, the sequence of turns, and other possible states that a game can have.
12
+
13
+ ## Features
14
+
15
+ 1. **Game Agnostic:** Qi can be used to represent board game positions for a wide variety of games. Whether you are playing Chess, Makruk, Shogi, or Xiangqi, Qi's flexible structure allows you to accurately capture the state of your game.
16
+ 2. **Flexible Position Representation:** Qi captures the state of the game by recording the pieces in play, their arrangement on the board, the sequence of turns, and other additional states of the game. This enables a comprehensive view of the game at any given point.
17
+ 3. **State Manipulation:** Qi allows for manipulation and update of game states through the `commit` method, allowing transitions between game states.
18
+ 4. **Equality Checks:** With the `eql?` method, Qi allows for comparisons between different game states, which can be useful for tracking game progress, detecting repeats, or even in creating AI for your games.
19
+ 5. **Turn Management:** Qi keeps track of the sequence of turns allowing users to identify whose turn it is currently.
20
+ 6. **Access to Game Data:** Qi provides methods to access the current arrangement of pieces on the board (`squares_hash`) and the pieces captured by each player (`captures_hash`), helping users understand the current status of the game. It also allows access to a list of captured pieces (`captures_array`).
21
+ 7. **Customizability:** Qi is flexible and allows for customization as per your needs. The keys and values of the `captures_hash` and `squares_hash` can be any kind of object, as well as the items from `turns` and values from `state`.
22
+
23
+ While `Qi` does not generate game moves itself, it serves as a solid foundation upon which game engines can be built. Its design is focused on providing a robust and adaptable representation of game states, paving the way for the development of diverse board game applications.
10
24
 
11
25
  ## Installation
12
26
 
13
27
  Add this line to your application's Gemfile:
14
28
 
15
29
  ```ruby
16
- gem "qi", ">= 10.0.0.beta11"
30
+ gem "qi"
17
31
  ```
18
32
 
19
33
  And then execute:
@@ -25,58 +39,70 @@ bundle install
25
39
  Or install it yourself as:
26
40
 
27
41
  ```sh
28
- gem install qi --pre
42
+ gem install qi
29
43
  ```
30
44
 
31
- ## Example
45
+ ## Usage
46
+
47
+ The following usage example is derived from a classic _tsume shogi_ (詰将棋) problem, which translates to _mate shogi_ - a popular genre of shogi problems where the goal is to checkmate the opponent's king.
48
+ In the provided setup, the attacking side is in possession of a silver general (S), a promoted bishop (+B) positioned on square 43, and a promoted pawn (+P) on square 22.
49
+
50
+ On the defending side, there is a king (k) situated on square 4, surrounded by two silver generals (s) on squares 3 and 5 respectively.
51
+
52
+ In this scenario, `Qi` allows us to represent the state of the game and apply changes as moves are made. Please follow the given example to understand how to create such a representation and how to update it:
32
53
 
33
54
  ```ruby
34
55
  require "qi"
35
56
 
36
- is_north_turn = false
37
- 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
- south_captures = %w[S]
39
- squares = { 3 => "s", 4 => "k", 5 => "s", 22 => "+P", 43 => "+B" }
40
-
41
- qi0 = Qi.new(is_north_turn, north_captures, south_captures, squares)
42
-
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
- qi0.south_captures # => ["S"]
45
- qi0.squares # => {3=>"s", 4=>"k", 5=>"s", 22=>"+P", 43=>"+B"}
46
- qi0.north_turn? # => false
47
- qi0.south_turn? # => true
48
- qi0.serialize # => "South-turn===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,S===3:s,4:k,5:s,22:+P,43:+B"
49
- qi0.inspect # => "<Qi South-turn===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,S===3:s,4:k,5:s,22:+P,43:+B>"
50
- qi0.to_a
51
- # [false,
52
- # ["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"],
53
- # ["S"],
54
- # {3=>"s", 4=>"k", 5=>"s", 22=>"+P", 43=>"+B"},
55
- # {}]
56
-
57
- qi1 = qi0.commit(43, 13, "+B", nil)
58
-
59
- 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
- qi1.south_captures # => ["S"]
61
- qi1.squares # => {3=>"s", 4=>"k", 5=>"s", 22=>"+P", 13=>"+B"}
62
- qi1.north_turn? # => true
63
- qi1.south_turn? # => false
64
- qi1.serialize # => "North-turn===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,S===3:s,4:k,5:s,13:+B,22:+P"
65
- qi1.inspect # => "<Qi North-turn===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,S===3:s,4:k,5:s,13:+B,22:+P>"
66
- qi1.to_a
67
- # [true,
68
- # ["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"],
69
- # ["S"],
70
- # {3=>"s", 4=>"k", 5=>"s", 22=>"+P", 13=>"+B"},
71
- # {}]
57
+ # Initialize an array for each player's captured pieces
58
+ 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]
59
+ south_captures = %w[S]
60
+
61
+ # Combine and count each player's captured pieces
62
+ captures = Hash.new(0)
63
+ (north_captures + south_captures).each { |piece| captures[piece] += 1 }
64
+
65
+ # Define the squares occupied by each piece on the board
66
+ squares = { 3 => "s", 4 => "k", 5 => "s", 22 => "+P", 43 => "+B" }
67
+
68
+ # Create a new game position
69
+ qi0 = Qi.new(captures, squares, [0, 1])
70
+
71
+ # Verify the properties of the game position
72
+ qi0.captures_array # => ["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"]
73
+ qi0.captures_hash # => {"r"=>2, "b"=>1, "g"=>4, "s"=>1, "n"=>4, "p"=>17, "S"=>1}
74
+ qi0.squares_hash # => {3=>"s", 4=>"k", 5=>"s", 22=>"+P", 43=>"+B"}
75
+ qi0.state # => {}
76
+ qi0.turn # => 0
77
+ qi0.turns # => [0, 1]
78
+ qi0.eql?(Qi.new(captures, squares, [0, 1])) # => true
79
+ qi0.eql?(Qi.new(captures, squares, [1, 0])) # => false
80
+
81
+ # Move a piece on the board and check the game state
82
+ qi1 = qi0.commit([], [], { 43 => nil, 13 => "+B" }, in_check: true)
83
+
84
+ qi1.captures_array # => ["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"]
85
+ qi1.captures_hash # => {"r"=>2, "b"=>1, "g"=>4, "s"=>1, "n"=>4, "p"=>17, "S"=>1}
86
+ qi1.squares_hash # => {3=>"s", 4=>"k", 5=>"s", 22=>"+P", 13=>"+B"}
87
+ qi1.state # => {:in_check=>true}
88
+ qi1.turn # => 1
89
+ qi1.turns # => [1, 0]
90
+ qi1.eql?(Qi.new(captures, squares, [0, 1])) # => false
91
+ qi1.eql?(Qi.new(captures, squares, [1, 0])) # => false
72
92
  ```
73
93
 
94
+ In this example, we first create a `Qi` object to represent a game position with `Qi.new`. Then, we check various aspects of the game state using the methods provided by `Qi`. After that, we create a new game state `qi1` by committing changes to the existing state `qi0`. Finally, we again check various aspects of the new game state.
95
+
74
96
  ## License
75
97
 
76
98
  The code is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
77
99
 
78
100
  ## About Sashité
79
101
 
80
- This [gem](https://rubygems.org/gems/qi) is maintained by [Sashité](https://sashite.com/).
102
+ This [gem](https://rubygems.org/gems/qi) is proudly maintained and developed by [Sashité](https://sashite.com/). Our mission is to promote intercultural understanding and appreciation through the universal language of board games.
103
+
104
+ At Sashité, we believe in the power of games as a medium for sharing and appreciating the richness of different cultures. From Chinese to Japanese, and Western traditions, every culture has its unique representation in the world of board games, particularly in chess.
105
+
106
+ Our `Qi` gem is a testament to this belief - a flexible, efficient, and inclusive software that allows for the representation and interaction of diverse chess systems. This piece of software is not just a tool; it is a bridge connecting different cultures under the love of strategic play.
81
107
 
82
- With some [lines of code](https://github.com/sashite/), let's share the beauty of Chinese, Japanese and Western cultures through the game of chess!
108
+ Join us in our journey as we continue to write [code](https://github.com/sashite/) to share the beauty of these cultures, one game at a time.
data/lib/qi.rb CHANGED
@@ -1,269 +1,118 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "digest"
4
- require_relative "qi/error/drop"
5
-
6
- # Qi is a class for representing a state of games like Shogi. It includes
7
- # information about the current turn, captures, game board, and other game options.
3
+ # The Qi class provides a consistent representation of a game state
4
+ # and supports changes in the game state through the commit method.
5
+ # It is designed to be used in board games such as chess, makruk, shogi, xiangqi.
8
6
  class Qi
9
- # Constants for representing the North and South players.
10
- North = :North
11
- South = :South
12
-
13
- # @return [Array] the pieces captured by the north player
14
- attr_reader :north_captures
15
-
16
- # @return [Array] the pieces captured by the south player
17
- attr_reader :south_captures
18
-
19
- # @return [Hash] the current state of the game board
20
- attr_reader :squares
7
+ # @!attribute [r] captures_hash
8
+ # @return [Hash<Object, Integer>] a hash of captured pieces
9
+ # @example
10
+ # {"r"=>2, "b"=>1, "g"=>4, "s"=>1, "n"=>4, "p"=>17, "S"=>1}
11
+
12
+ # @!attribute [r] squares_hash
13
+ # @return [Hash<Object, Object>] A hash where the keys represent square
14
+ # identifiers and the values represent the piece that will occupy each square.
15
+ # Both the keys and values can be any type of Ruby object, such as integers, strings, symbols, etc.
16
+ # @example
17
+ # {A3: "s", E4: "k", B5: "s", C22: "+P", D43: "+B"}
18
+
19
+ # @!attribute [r] state
20
+ # @return [Hash<Symbol, Object>] a hash of game states
21
+ # @example
22
+ # {:in_check=>true}
23
+
24
+ # @!attribute [r] turns
25
+ # @return [Array<Object>] a rotation of turns
26
+ # @example
27
+ # ["Sente", "Gote"]
28
+
29
+ attr_reader :captures_hash, :squares_hash, :state, :turns
30
+
31
+ # @param captures_hash [Hash<Object, Integer>] a hash of captured pieces
32
+ # @param squares_hash [Hash<Object, Object>] A hash where the keys represent square
33
+ # identifiers and the values represent the piece that will occupy each square.
34
+ # Both the keys and values can be any type of Ruby object, such as integers, strings, symbols, etc.
35
+ # @param turns [Array<Object>] a rotation of turns
36
+ # @param state [Hash<Symbol, Object>] a hash of game states
37
+ #
38
+ # @example
39
+ # captures = Hash.new(0)
40
+ # 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]
41
+ # south_captures = %w[S]
42
+ # (north_captures + south_captures).each { |piece| captures[piece] += 1 }
43
+ # squares = { 3 => "s", 4 => "k", 5 => "s", 22 => "+P", 43 => "+B" }
44
+ # Qi.new(captures, squares, [0, 1])
45
+ def initialize(captures_hash, squares_hash, turns, **state)
46
+ @captures_hash = ::Hash.new(0).merge(captures_hash.select { |_, v| v > 0 })
47
+ @squares_hash = squares_hash.compact
48
+ @turns = turns
49
+ @state = state.transform_keys(&:to_sym)
21
50
 
22
- # @return [Hash] additional game options
23
- attr_reader :options
51
+ freeze
52
+ end
24
53
 
25
- # Initialize a new Qi object.
54
+ # Return an array of captures containing piece names.
26
55
  #
27
- # @example Creating a new Qi object
28
- # qi = Qi.new(true, ['P', 'G'], ['B', '+B'], {56 => 'P', 3 => 'g', 64 => '+B'}, fullmove_number: 42, is_in_check: false)
29
- #
30
- # @param [Boolean] is_north_turn true if it's the north player's turn, false otherwise
31
- # @param [Array] north_captures the pieces captured by the north player
32
- # @param [Array] south_captures the pieces captured by the south player
33
- # @param [Hash] squares the current state of the game board
34
- # @param [Hash] options additional game options
35
- def initialize(is_north_turn, north_captures, south_captures, squares, **options)
36
- @is_north_turn = is_north_turn
37
- @north_captures = north_captures.sort
38
- @south_captures = south_captures.sort
39
- @squares = squares.compact
40
- @options = options
56
+ # @return [Array<Object>] an array of captures
57
+ # @example
58
+ # ["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"]
59
+ def captures_array
60
+ captures_hash.flat_map { |piece, count| ::Array.new(count, piece) }.sort
41
61
  end
42
62
 
43
- # Apply a move or a drop on the board.
63
+ # Commit a change to the game state and return a new Qi object.
44
64
  #
45
- # The method creates a new instance of Qi representing the state of the game after the move.
46
- # This includes updating the captures and the positions of the pieces on the board.
47
- #
48
- # @param src_square [Object, nil] the source square, or nil if dropping a piece from hand
49
- # @param dst_square [Object] the destination square
50
- # @param piece_name [String] the name of the piece to move
51
- # @param in_hand [String, nil] the piece in hand, or nil if moving a piece from a square
52
- # @param options [Hash] options to pass to the new instance
53
- #
54
- # @example Applying a move
55
- # qi = Qi.new(true, ['P', 'B'], ['G', '+B'], {56 => 'P', 64 => '+B'})
56
- # qi.commit(56, 47, 'P', nil) #=> <Qi South-turn===B,P,+B,G===47:P,64:+B>
57
- #
58
- # @example Applying a capture
59
- # qi = Qi.new(true, ['P', 'B'], ['G', '+B'], {56 => 'P', 64 => '+B'})
60
- # qi.commit(56, 47, 'P', 'G') #=> <Qi South-turn===B,G,P,+B,G===47:P,64:+B>
61
- #
62
- # @example Applying a drop
63
- # qi = Qi.new(true, ['P', 'B', 'G'], ['G', '+B'], {56 => 'P', 64 => '+B'})
64
- # qi.commit(nil, 47, 'G', 'G') #=> <Qi South-turn===B,P,+B,G===47:G,56:P,64:+B>
65
- #
66
- # @return [Qi] the new game state after the move
67
- def commit(src_square, dst_square, piece_name, in_hand, **options)
68
- raise ::ArgumentError, "Both src_square and in_hand cannot be nil" if src_square.nil? && in_hand.nil?
69
-
70
- modified_captures = update_captures(src_square, in_hand)
71
- modified_squares = squares.merge({ src_square => nil, dst_square => piece_name })
72
- self.class.new(south_turn?, *modified_captures, modified_squares, **options)
65
+ # @param add_captures_array [Array<Object>] an array of pieces to be added to captures
66
+ # @param del_captures_array [Array<Object>] an array of pieces to be deleted from captures
67
+ # @param edit_squares_hash [Hash<Object, Object>] A hash where the keys represent square
68
+ # identifiers and the values represent the piece that will occupy each square.
69
+ # Both the keys and values can be any type of Ruby object, such as integers, strings, symbols, etc.
70
+ # @param state [Hash<Symbol, Object>] a hash of new game states
71
+ # @return [Qi] a new Qi object representing the updated game state
72
+ # @example
73
+ # qi0.commit([], [], { D43: nil, B13: "+B" }, in_check: true)
74
+ def commit(add_captures_array, del_captures_array, edit_squares_hash, **state)
75
+ self.class.new(
76
+ edit_captures_hash(add_captures_array.compact, del_captures_array.compact, **captures_hash),
77
+ squares_hash.merge(edit_squares_hash),
78
+ turns.rotate,
79
+ **state
80
+ )
73
81
  end
74
82
 
75
- # Compare this Qi object with another for equality.
83
+ # Check if the current Qi object is equal to another Qi object.
76
84
  #
77
- # @example Comparing two Qi objects
78
- # qi1 = Qi.new(...)
79
- # qi2 = Qi.new(...)
80
- # qi1.eql?(qi2) #=> true or false
81
- #
82
- # @param [Qi] other the other Qi object to compare with
83
- # @return [Boolean] true if the two objects represent the same game state, false otherwise
85
+ # @param other [Qi] another Qi object
86
+ # @return [Boolean] returns true if the captures_hash, squares_hash, turn, and state of both Qi objects are equal, false otherwise
84
87
  def eql?(other)
85
- return false unless other.respond_to?(:serialize)
88
+ return false unless other.captures_hash == captures_hash
89
+ return false unless other.squares_hash == squares_hash
90
+ return false unless other.turn == turn
91
+ return false unless other.state == state
86
92
 
87
- other.serialize == serialize
93
+ true
88
94
  end
89
95
  alias == eql?
90
96
 
91
- # Get the captures of the current player.
92
- #
93
- # The method returns the north player's captures when it's their turn,
94
- # and the south player's captures when it's their turn.
95
- #
96
- # @example When it's the north player's turn
97
- # qi = Qi.new(true, ['P', 'B'], ['G', '+B'], {56 => 'P', 64 => '+B'})
98
- # qi.current_captures #=> ['B', 'P']
99
- #
100
- # @example When it's the south player's turn
101
- # qi = Qi.new(false, ['P', 'B'], ['G', '+B'], {56 => 'P', 64 => '+B'})
102
- # qi.current_captures #=> ['+B', 'G']
103
- #
104
- # @return [Array] the captures of the current player
105
- def current_captures
106
- north_turn? ? north_captures : south_captures
107
- end
108
-
109
- # Get the captures of the opponent player.
110
- #
111
- # @return [Array] the captures of the opponent player
112
- def opponent_captures
113
- north_turn? ? south_captures : north_captures
114
- end
115
-
116
97
  # Get the current turn.
117
98
  #
118
- # @return [Symbol] :North if it's the north player's turn, :South otherwise
119
- def current_turn
120
- north_turn? ? North : South
121
- end
122
-
123
- # Get the next turn.
124
- #
125
- # @return [Symbol] :South if it's the north player's turn, :North otherwise
126
- def next_turn
127
- north_turn? ? South : North
128
- end
129
-
130
- # Check if it's the north player's turn.
131
- #
132
- # @return [Boolean] true if it's the north player's turn, false otherwise
133
- def north_turn?
134
- @is_north_turn
135
- end
136
-
137
- # Check if it's the south player's turn.
138
- #
139
- # @return [Boolean] true if it's the south player's turn, false otherwise
140
- def south_turn?
141
- !north_turn?
142
- end
143
-
144
- # Convert the state to an array.
145
- #
146
- # @example Converting the state to an array
147
- # qi.to_a #=> [true, ['P', 'G'], ['B', '+B'], {56 => 'P', 3 => 'g', 64 => '+B'}, {}]
148
- #
149
- # @return [Array] an array representing the game state
150
- def to_a
151
- [
152
- north_turn?,
153
- north_captures,
154
- south_captures,
155
- squares,
156
- options
157
- ]
158
- end
159
-
160
- # Convert the state to a hash.
161
- #
162
- # @example Converting the state to a hash
163
- # qi.to_h #=> {is_north_turn: true, north_captures: ['P', 'G'], south_captures: ['B', '+B'], squares: {56 => 'P', 3 => 'g', 64 => '+B'}, options: {}}
164
- #
165
- # @return [Hash] a hash representing the game state
166
- def to_h
167
- {
168
- is_north_turn: north_turn?,
169
- north_captures:,
170
- south_captures:,
171
- squares:,
172
- options:
173
- }
174
- end
175
-
176
- # Generate a hash value for the game state.
177
- #
178
- # @example Generating a hash value
179
- # qi.hash #=> "10dfb778f6559dd368c510a27e3b00bdb5b4ad88d4d67f38864d7e36de7c2f9c"
180
- #
181
- # @return [String] a SHA256 hash of the serialized game state
182
- def hash
183
- ::Digest::SHA256.hexdigest(serialize)
184
- end
185
-
186
- # Serialize the game state.
187
- #
188
- # @example Serializing the game state
189
- # qi.serialize #=> "North-turn===P,G,B,+B===3:g,56:P,64:+B"
190
- #
191
- # @return [String] a string representation of the game state
192
- def serialize
193
- serialized_turn = "#{current_turn}-turn"
194
- serialized_captures = (north_captures + south_captures).join(",")
195
- serialized_squares = squares.keys.sort.map { |i| "#{i}:#{squares.fetch(i)}" }.join(",")
196
-
197
- "#{serialized_turn}===#{serialized_captures}===#{serialized_squares}"
198
- end
199
-
200
- # Provide a string representation of the game state for debugging.
201
- #
202
- # @example Getting a string representation of the game state
203
- # qi.inspect #=> "<Qi North-turn===P,G,B,+B===3:g,56:P,64:+B>"
204
- #
205
- # @return [String] a string representation of the game state
206
- def inspect
207
- "<#{self.class} #{serialize}>"
99
+ # @return [Object] the current turn
100
+ def turn
101
+ turns.fetch(0)
208
102
  end
209
103
 
210
104
  private
211
105
 
212
- # Update captures based on the source square and the piece in hand.
213
- #
214
- # If the source square is not nil and in_hand is nil (i.e., we're moving a piece on the board),
215
- # the captures remain the same. If the source square is not nil and in_hand is not nil
216
- # (i.e., we're capturing a piece that will remain in hand), the piece is added to the
217
- # current player's captures. If the source square is nil (i.e., we're dropping a piece from hand),
218
- # the piece is removed from the current player's captures.
219
- #
220
- # @param src_square [Object, nil] the source square, or nil if dropping a piece from hand
221
- # @param in_hand [String, nil] the piece in hand, or nil if moving a piece from a square
222
- #
223
- # @example When moving a piece on the board
224
- # qi = Qi.new(true, ['P', 'B'], ['G', '+B'], {56 => 'P', 64 => '+B'})
225
- # qi.send(:update_captures, 56, nil) #=> [['B', 'P'], ['G', '+B']]
226
- #
227
- # @example When capturing a piece that will remain in hand
228
- # qi = Qi.new(true, ['P', 'B'], ['G', '+B'], {56 => 'P', 64 => '+B'})
229
- # qi.send(:update_captures, 56, 'G') #=> [['B', 'P', 'G'], ['G', '+B']]
230
- #
231
- # @example When dropping a piece from hand
232
- # qi = Qi.new(true, ['P', 'B', 'G'], ['G', '+B'], {56 => 'P', 64 => '+B'})
233
- # qi.send(:update_captures, nil, 'G') #=> [['B', 'P'], ['G', '+B']]
234
- #
235
- # @return [Array] a two-element array with the updated north and south captures
236
- def update_captures(src_square, in_hand)
237
- return [north_captures, south_captures] if in_hand.nil?
238
-
239
- captures = if src_square.nil?
240
- remove_from_captures(in_hand, *current_captures)
241
- else
242
- current_captures + [in_hand]
243
- end
244
-
245
- north_turn? ? [captures, opponent_captures] : [opponent_captures, captures]
246
- end
247
-
248
- # Removes a piece from the captures.
249
- #
250
- # If the piece is not found in the captures, an Error::Drop exception is raised.
251
- #
252
- # @param piece [String] the piece to remove from the captures
253
- # @param captures [Array] the captures to update
254
- #
255
- # @example
256
- # qi = Qi.new(true, ['P', 'B', 'G'], ['G', '+B'], {56 => 'P', 64 => '+B'})
257
- # qi.send(:remove_from_captures, 'G', *qi.current_captures) #=> ['P', 'B']
258
- #
259
- # @raise [Error::Drop] if the piece is not found in the captures
106
+ # Edits the captures hash and returns it.
260
107
  #
261
- # @return [Array] the captures after removing the piece
262
- def remove_from_captures(piece, *captures)
263
- index = captures.rindex(piece)
264
- raise Error::Drop, "There are no #{piece} in hand" if index.nil?
108
+ # @param add_captures_array [Array<Object>] an array of pieces to be added to captures
109
+ # @param del_captures_array [Array<Object>] an array of pieces to be deleted from captures
110
+ # @param hash [Hash<Object, Integer>] the current captures hash
111
+ # @return [Hash<Object, Integer>] the updated captures hash
112
+ def edit_captures_hash(add_captures_array, del_captures_array, **hash)
113
+ add_captures_array.each { |piece_name| hash[piece_name] += 1 }
114
+ del_captures_array.each { |piece_name| hash[piece_name] -= 1 }
265
115
 
266
- captures.delete_at(index)
267
- captures
116
+ hash
268
117
  end
269
118
  end
metadata CHANGED
@@ -1,16 +1,17 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: qi
3
3
  version: !ruby/object:Gem::Version
4
- version: 10.0.0.beta11
4
+ version: 10.0.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: 2023-05-12 00:00:00.000000000 Z
11
+ date: 2023-05-29 00:00:00.000000000 Z
12
12
  dependencies: []
13
- description: An abstraction that could help to update positions for games like Shogi.
13
+ description: A flexible and customizable library for representing and manipulating
14
+ game states, ideal for developing board games like chess, shogi, or xiangqi.
14
15
  email: contact@cyril.email
15
16
  executables: []
16
17
  extensions: []
@@ -19,7 +20,6 @@ files:
19
20
  - LICENSE.md
20
21
  - README.md
21
22
  - lib/qi.rb
22
- - lib/qi/error/drop.rb
23
23
  homepage: https://github.com/sashite/qi.rb
24
24
  licenses:
25
25
  - MIT
@@ -36,12 +36,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
36
36
  version: 3.2.0
37
37
  required_rubygems_version: !ruby/object:Gem::Requirement
38
38
  requirements:
39
- - - ">"
39
+ - - ">="
40
40
  - !ruby/object:Gem::Version
41
- version: 1.3.1
41
+ version: '0'
42
42
  requirements: []
43
43
  rubygems_version: 3.4.10
44
44
  signing_key:
45
45
  specification_version: 4
46
- summary: An abstraction that could help to update positions for games like Shogi.
46
+ summary: Versatile Board Game Position Representation
47
47
  test_files: []
data/lib/qi/error/drop.rb DELETED
@@ -1,13 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class Qi
4
- # The Error module contains custom error classes for the Qi game.
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
10
- class Drop < ::IndexError
11
- end
12
- end
13
- end