qi 10.0.0.beta12 → 10.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +63 -48
- data/lib/qi.rb +100 -114
- metadata +5 -19
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4a98ed653f1c1d21ae215f4a8ab481a0a19dd93551a9a3ba0faf9ee8e541291f
|
4
|
+
data.tar.gz: d1291d436f366c70e07a64a3a6390a3a9d99d56cfeb8b0b85c1002addbe7b540
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 154aa839e292e2cadfbafe8c64e324285216da01a491ef98815479bf1bd735dc2ec747ad31e6ef479ea4d913b262dbcb9ad6399f13cdf5199dddb3403d7091ee
|
7
|
+
data.tar.gz: 52c5ca680fb11577fc2aa84931900b9f8c712e6c300ca5774f918f144fd1c999f1e34387e8827fe5f7f3d55ed192d04dabb310acc538c19bee3b941effb68637
|
data/README.md
CHANGED
@@ -6,16 +6,19 @@
|
|
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
|
-
|
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
10
|
|
11
|
-
|
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
12
|
|
13
|
-
## Features
|
13
|
+
## Features
|
14
14
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
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`.
|
19
22
|
|
20
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.
|
21
24
|
|
@@ -24,7 +27,7 @@ While `Qi` does not generate game moves itself, it serves as a solid foundation
|
|
24
27
|
Add this line to your application's Gemfile:
|
25
28
|
|
26
29
|
```ruby
|
27
|
-
gem "qi"
|
30
|
+
gem "qi"
|
28
31
|
```
|
29
32
|
|
30
33
|
And then execute:
|
@@ -36,58 +39,70 @@ bundle install
|
|
36
39
|
Or install it yourself as:
|
37
40
|
|
38
41
|
```sh
|
39
|
-
gem install qi
|
42
|
+
gem install qi
|
40
43
|
```
|
41
44
|
|
42
|
-
##
|
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:
|
43
53
|
|
44
54
|
```ruby
|
45
55
|
require "qi"
|
46
56
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
qi0.
|
60
|
-
|
61
|
-
|
62
|
-
qi0.
|
63
|
-
#
|
64
|
-
#
|
65
|
-
#
|
66
|
-
#
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
qi1.
|
73
|
-
|
74
|
-
qi1.
|
75
|
-
qi1.
|
76
|
-
qi1.
|
77
|
-
qi1.
|
78
|
-
qi1.
|
79
|
-
# [
|
80
|
-
|
81
|
-
|
82
|
-
# true]
|
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
|
83
92
|
```
|
84
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
|
+
|
85
96
|
## License
|
86
97
|
|
87
98
|
The code is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
88
99
|
|
89
100
|
## About Sashité
|
90
101
|
|
91
|
-
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.
|
92
107
|
|
93
|
-
|
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,132 +1,118 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
# The Qi class represents a state of games such as Shogi.
|
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.
|
7
6
|
class Qi
|
8
|
-
#
|
9
|
-
|
10
|
-
|
11
|
-
#
|
12
|
-
|
13
|
-
|
14
|
-
#
|
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
|
15
37
|
#
|
16
|
-
# @
|
17
|
-
#
|
18
|
-
#
|
19
|
-
#
|
20
|
-
#
|
21
|
-
#
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
@
|
27
|
-
@
|
28
|
-
|
29
|
-
|
30
|
-
end
|
31
|
-
|
32
|
-
#
|
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)
|
50
|
+
|
51
|
+
freeze
|
52
|
+
end
|
53
|
+
|
54
|
+
# Return an array of captures containing piece names.
|
33
55
|
#
|
34
|
-
# @
|
35
|
-
# @
|
36
|
-
#
|
37
|
-
|
38
|
-
|
39
|
-
def commit(capture: nil, drop: nil, is_in_check: false, **diffs)
|
40
|
-
self.class.new(capture, *captures, drop:, is_in_check:, is_north_turn: south_turn?, **squares.merge(diffs))
|
41
|
-
end
|
42
|
-
|
43
|
-
# @return [Boolean] whether the current player is in check.
|
44
|
-
def in_check?
|
45
|
-
@is_in_check
|
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
|
46
61
|
end
|
47
62
|
|
48
|
-
#
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
#
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
#
|
59
|
-
def
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
63
|
+
# Commit a change to the game state and return a new Qi object.
|
64
|
+
#
|
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
|
+
)
|
81
|
+
end
|
82
|
+
|
83
|
+
# Check if the current Qi object is equal to another Qi object.
|
84
|
+
#
|
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
|
64
87
|
def eql?(other)
|
65
|
-
return false unless other.
|
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
|
66
92
|
|
67
|
-
|
93
|
+
true
|
68
94
|
end
|
69
95
|
alias == eql?
|
70
96
|
|
71
|
-
#
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
squares,
|
77
|
-
in_check?
|
78
|
-
]
|
79
|
-
end
|
80
|
-
|
81
|
-
# @return [Hash] the hash representation of the game state.
|
82
|
-
def to_h
|
83
|
-
{
|
84
|
-
is_north_turn: north_turn?,
|
85
|
-
captures:,
|
86
|
-
squares:,
|
87
|
-
is_in_check: in_check?
|
88
|
-
}
|
89
|
-
end
|
90
|
-
|
91
|
-
# @return [String] the SHA-256 hash of the serialized game state.
|
92
|
-
def hash
|
93
|
-
::Digest::SHA256.hexdigest(serialize)
|
94
|
-
end
|
95
|
-
|
96
|
-
# @return [String] the string representation of the game state.
|
97
|
-
def serialize
|
98
|
-
[
|
99
|
-
serialized_turn,
|
100
|
-
serialized_captures,
|
101
|
-
serialized_squares,
|
102
|
-
serialized_in_check
|
103
|
-
].join(" ")
|
104
|
-
end
|
105
|
-
|
106
|
-
# @return [String] the string representation of the object.
|
107
|
-
def inspect
|
108
|
-
"<#{self.class} #{serialize}>"
|
97
|
+
# Get the current turn.
|
98
|
+
#
|
99
|
+
# @return [Object] the current turn
|
100
|
+
def turn
|
101
|
+
turns.fetch(0)
|
109
102
|
end
|
110
103
|
|
111
104
|
private
|
112
105
|
|
113
|
-
#
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
# @return [
|
119
|
-
def
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
def serialized_squares
|
125
|
-
squares.keys.sort.map { |i| "#{squares.fetch(i)}@#{i}" }.join(";")
|
126
|
-
end
|
127
|
-
|
128
|
-
# @return [String] the serialized check state.
|
129
|
-
def serialized_in_check
|
130
|
-
in_check? ? "+" : "."
|
106
|
+
# Edits the captures hash and returns it.
|
107
|
+
#
|
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 }
|
115
|
+
|
116
|
+
hash
|
131
117
|
end
|
132
118
|
end
|
metadata
CHANGED
@@ -1,29 +1,15 @@
|
|
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
|
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
|
-
dependencies:
|
13
|
-
- !ruby/object:Gem::Dependency
|
14
|
-
name: kernel-boolean
|
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'
|
11
|
+
date: 2023-05-29 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
27
13
|
description: A flexible and customizable library for representing and manipulating
|
28
14
|
game states, ideal for developing board games like chess, shogi, or xiangqi.
|
29
15
|
email: contact@cyril.email
|
@@ -50,9 +36,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
50
36
|
version: 3.2.0
|
51
37
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
52
38
|
requirements:
|
53
|
-
- - "
|
39
|
+
- - ">="
|
54
40
|
- !ruby/object:Gem::Version
|
55
|
-
version:
|
41
|
+
version: '0'
|
56
42
|
requirements: []
|
57
43
|
rubygems_version: 3.4.10
|
58
44
|
signing_key:
|