qi 6.1.0 → 7.0.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.
- checksums.yaml +4 -4
- data/README.md +22 -124
- data/lib/qi.rb +80 -3
- metadata +4 -5
- data/lib/qi/position.rb +0 -114
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 18ca7bb7fca15a8ce854e8a205160b05a14127970039a904c3ce8ad6f8baf8fc
|
4
|
+
data.tar.gz: fc4b915c93488f39835f762b1f89fb44a74ab25ca3faa47fb4a6ea846db66c77
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6079a5cb3f9b5b83a5bbc0db41694e6037a46a8e5ef6ce8bd4e65f36863d17008cb5d08d806681c4b17daff89ef2d43abd4e260c4ba068b7ac499fe51a36053c
|
7
|
+
data.tar.gz: 164bb2211d24d9119061a9c771438d0b77f96c0e2d51d4f18c3cfe3f82acdeaf50bd532d4eafb071441a64a18be68cefc9bae9a6dcfcf81b633cf3498912e76c
|
data/README.md
CHANGED
@@ -1,18 +1,18 @@
|
|
1
|
-
#
|
1
|
+
# Qi.rb
|
2
2
|
|
3
3
|
[](https://travis-ci.org/sashite/qi.rb)
|
4
4
|
[][gem]
|
5
5
|
[][inchpages]
|
6
6
|
[][rubydoc]
|
7
7
|
|
8
|
-
>
|
8
|
+
> `Qi` (棋) is an abstraction for updating positions of chess variants (including Chess, Janggi, Markruk, Shogi, Xiangqi), with a move.
|
9
9
|
|
10
10
|
## Installation
|
11
11
|
|
12
12
|
Add this line to your application's Gemfile:
|
13
13
|
|
14
14
|
```ruby
|
15
|
-
gem
|
15
|
+
gem "qi"
|
16
16
|
```
|
17
17
|
|
18
18
|
And then execute:
|
@@ -25,136 +25,34 @@ 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):
|
29
|
-
|
30
|
-
```ruby
|
31
|
-
require 'qi'
|
32
|
-
|
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',
|
37
|
-
nil, nil, nil, nil, nil, nil, nil, nil, nil,
|
38
|
-
nil, nil, nil, nil, nil, nil, nil, nil, nil,
|
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'
|
43
|
-
)
|
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
|
-
moves = [
|
60
|
-
[ 56, 47, 'P' ],
|
61
|
-
[ 3, 11, 'g' ],
|
62
|
-
[ 64, 24, '+B', 'P' ],
|
63
|
-
[ 5, 14, 'g' ],
|
64
|
-
[ 24, 14, '+B', 'G' ],
|
65
|
-
[ 4, 3, 'k' ],
|
66
|
-
[ nil, 13, 'G' ]
|
67
|
-
]
|
68
|
-
|
69
|
-
last_position = moves.reduce(starting_position) do |position, move|
|
70
|
-
position.call(move)
|
71
|
-
end
|
72
|
-
|
73
|
-
last_position.topside_in_hand_pieces # => []
|
74
|
-
last_position.squares # => ["l", "n", "s", "k", nil, nil, "s", "n", "l",
|
75
|
-
# nil, "r", "g", nil, "G", "+B", nil, "b", nil,
|
76
|
-
# "p", "p", "p", "p", "p", "p", nil, "p", "p",
|
77
|
-
# nil, nil, nil, nil, nil, nil, nil, nil, nil,
|
78
|
-
# nil, nil, nil, nil, nil, nil, nil, nil, nil,
|
79
|
-
# nil, nil, "P", nil, nil, nil, nil, nil, nil,
|
80
|
-
# "P", "P", nil, "P", "P", "P", "P", "P", "P",
|
81
|
-
# nil, nil, nil, nil, nil, nil, nil, "R", nil,
|
82
|
-
# "L", "N", "S", "G", "K", "G", "S", "N", "L"]
|
83
|
-
last_position.bottomside_in_hand_pieces # => ["P"]
|
84
|
-
last_position.in_hand_pieces # => []
|
85
|
-
last_position.turn_to_topside? # => true
|
86
|
-
```
|
87
|
-
|
88
|
-
Another example with Xiangqi's Short Double Cannons Checkmate:
|
89
|
-
|
90
28
|
```ruby
|
91
|
-
require
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
29
|
+
require "qi"
|
30
|
+
|
31
|
+
Qi.call(
|
32
|
+
[43, 13, "+B"],
|
33
|
+
"side_id": 0,
|
34
|
+
"board": {
|
35
|
+
3 => "s",
|
36
|
+
4 => "k",
|
37
|
+
5 => "s",
|
38
|
+
22 => "+P",
|
39
|
+
43 => "+B"
|
40
|
+
},
|
41
|
+
"hands": [
|
42
|
+
%w[S],
|
43
|
+
%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]
|
44
|
+
]
|
104
45
|
)
|
105
|
-
|
106
|
-
starting_position.topside_in_hand_pieces # => []
|
107
|
-
starting_position.squares # => ["車", "馬", "象", "士", "將", "士", "象", "馬", "車",
|
108
|
-
# nil, nil, nil, nil, nil, nil, nil, nil, nil,
|
109
|
-
# nil, "砲", nil, nil, nil, nil, nil, "砲", nil,
|
110
|
-
# "卒", nil, "卒", nil, "卒", nil, "卒", nil, "卒",
|
111
|
-
# nil, nil, nil, nil, nil, nil, nil, nil, nil,
|
112
|
-
# nil, nil, nil, nil, nil, nil, nil, nil, nil,
|
113
|
-
# "兵", nil, "兵", nil, "兵", nil, "兵", nil, "兵",
|
114
|
-
# nil, "炮", nil, nil, nil, nil, nil, "炮", nil,
|
115
|
-
# nil, nil, nil, nil, nil, nil, nil, nil, nil,
|
116
|
-
# "俥", "傌", "相", "仕", "帥", "仕", "相", "傌", "俥"]
|
117
|
-
starting_position.bottomside_in_hand_pieces # => []
|
118
|
-
starting_position.in_hand_pieces # => []
|
119
|
-
starting_position.turn_to_topside? # => false
|
120
|
-
|
121
|
-
moves = [
|
122
|
-
[ 64, 67, '炮' ],
|
123
|
-
[ 25, 22, '砲' ],
|
124
|
-
[ 70, 52, '炮' ],
|
125
|
-
[ 19, 55, '砲' ],
|
126
|
-
[ 67, 31, '炮' ],
|
127
|
-
[ 22, 58, '砲' ],
|
128
|
-
[ 52, 49, '炮' ]
|
129
|
-
]
|
130
|
-
|
131
|
-
last_position = moves.reduce(starting_position) do |position, move|
|
132
|
-
position.call(move)
|
133
|
-
end
|
134
|
-
|
135
|
-
last_position.topside_in_hand_pieces # => []
|
136
|
-
last_position.squares # => ["車", "馬", "象", "士", "將", "士", "象", "馬", "車",
|
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, "卒",
|
140
|
-
# nil, nil, nil, nil, nil, nil, nil, nil, nil,
|
141
|
-
# nil, nil, nil, nil, "炮", nil, nil, nil, nil,
|
142
|
-
# "兵", "砲", "兵", nil, "砲", nil, "兵", nil, "兵",
|
143
|
-
# nil, nil, nil, nil, nil, nil, nil, nil, nil,
|
144
|
-
# nil, nil, nil, nil, nil, nil, nil, nil, nil,
|
145
|
-
# "俥", "傌", "相", "仕", "帥", "仕", "相", "傌", "俥"]
|
146
|
-
last_position.bottomside_in_hand_pieces # => []
|
147
|
-
last_position.in_hand_pieces # => []
|
148
|
-
last_position.turn_to_topside? # => true
|
46
|
+
# => {:side_id=>1, :board=>{3=>"s", 4=>"k", 5=>"s", 22=>"+P", 13=>"+B"}, :hands=>[["S"], ["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"]]}
|
149
47
|
```
|
150
48
|
|
151
49
|
## License
|
152
50
|
|
153
|
-
The
|
51
|
+
The code is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
154
52
|
|
155
53
|
## About Sashite
|
156
54
|
|
157
|
-
|
55
|
+
This [gem](https://rubygems.org/gems/qi) is maintained by [Sashite](https://sashite.com/).
|
158
56
|
|
159
57
|
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!
|
160
58
|
|
data/lib/qi.rb
CHANGED
@@ -1,7 +1,84 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
# The Qi
|
3
|
+
# The Qi abstraction.
|
4
|
+
#
|
5
|
+
# @example
|
6
|
+
# Qi.call(
|
7
|
+
# [43, 13, "+B"],
|
8
|
+
# "side_id": 0,
|
9
|
+
# "board": {
|
10
|
+
# 3 => "s",
|
11
|
+
# 4 => "k",
|
12
|
+
# 5 => "s",
|
13
|
+
# 22 => "+P",
|
14
|
+
# 43 => "+B"
|
15
|
+
# },
|
16
|
+
# "hands": [
|
17
|
+
# %w[S],
|
18
|
+
# %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]
|
19
|
+
# ]
|
20
|
+
# )
|
21
|
+
# # => {:side_id=>1, :board=>{3=>"s", 4=>"k", 5=>"s", 22=>"+P", 13=>"+B"}, :hands=>[["S"], ["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"]]}
|
4
22
|
module Qi
|
5
|
-
|
23
|
+
# Apply a move to the position.
|
24
|
+
#
|
25
|
+
# @param move [Array] The move to play.
|
26
|
+
# @param side_id [Integer] The identifier of the player who must play.
|
27
|
+
# @param board [Hash] The indexes of each piece on the board.
|
28
|
+
# @param hands [Array] The list of pieces in hand grouped by players.
|
29
|
+
#
|
30
|
+
# @see https://developer.sashite.com/specs/portable-chess-notation
|
31
|
+
# @see https://developer.sashite.com/specs/portable-move-notation
|
32
|
+
#
|
33
|
+
# @example
|
34
|
+
# call(
|
35
|
+
# [43, 13, "+B"],
|
36
|
+
# "side_id": 0,
|
37
|
+
# "board": {
|
38
|
+
# 3 => "s",
|
39
|
+
# 4 => "k",
|
40
|
+
# 5 => "s",
|
41
|
+
# 22 => "+P",
|
42
|
+
# 43 => "+B"
|
43
|
+
# },
|
44
|
+
# "hands": [
|
45
|
+
# %w[S],
|
46
|
+
# %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]
|
47
|
+
# ]
|
48
|
+
# )
|
49
|
+
# # => {:side_id=>1, :board=>{3=>"s", 4=>"k", 5=>"s", 22=>"+P", 13=>"+B"}, :hands=>[["S"], ["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"]]}
|
50
|
+
#
|
51
|
+
# @return [Hash] The next position.
|
52
|
+
def self.call(move, side_id:, board:, hands:)
|
53
|
+
updated_board = board.dup
|
54
|
+
updated_in_hand_pieces = hands.fetch(side_id).dup
|
55
|
+
|
56
|
+
actions = move.each_slice(4)
|
57
|
+
|
58
|
+
actions.each do |action|
|
59
|
+
src_square_id = action.fetch(0)
|
60
|
+
dst_square_id = action.fetch(1)
|
61
|
+
moved_piece_name = action.fetch(2)
|
62
|
+
captured_piece_name = action.fetch(3, nil)
|
63
|
+
|
64
|
+
if src_square_id.nil?
|
65
|
+
piece_in_hand_id = updated_in_hand_pieces.index(moved_piece_name)
|
66
|
+
updated_in_hand_pieces.delete_at(piece_in_hand_id) unless piece_in_hand_id.nil?
|
67
|
+
else
|
68
|
+
updated_board.delete(src_square_id)
|
69
|
+
end
|
6
70
|
|
7
|
-
|
71
|
+
updated_board[dst_square_id] = moved_piece_name
|
72
|
+
updated_in_hand_pieces.push(captured_piece_name) unless captured_piece_name.nil?
|
73
|
+
end
|
74
|
+
|
75
|
+
updated_hands = hands.dup
|
76
|
+
updated_hands[side_id] = updated_in_hand_pieces
|
77
|
+
|
78
|
+
{
|
79
|
+
side_id: side_id.succ % hands.length,
|
80
|
+
board: updated_board,
|
81
|
+
hands: updated_hands
|
82
|
+
}
|
83
|
+
end
|
84
|
+
end
|
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:
|
4
|
+
version: 7.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: 2020-
|
11
|
+
date: 2020-09-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: brutal
|
@@ -117,7 +117,6 @@ files:
|
|
117
117
|
- LICENSE.md
|
118
118
|
- README.md
|
119
119
|
- lib/qi.rb
|
120
|
-
- lib/qi/position.rb
|
121
120
|
homepage: https://developer.sashite.com/specs/
|
122
121
|
licenses:
|
123
122
|
- MIT
|
@@ -133,7 +132,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
133
132
|
requirements:
|
134
133
|
- - ">="
|
135
134
|
- !ruby/object:Gem::Version
|
136
|
-
version:
|
135
|
+
version: 2.7.0
|
137
136
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
138
137
|
requirements:
|
139
138
|
- - ">="
|
@@ -143,5 +142,5 @@ requirements: []
|
|
143
142
|
rubygems_version: 3.1.2
|
144
143
|
signing_key:
|
145
144
|
specification_version: 4
|
146
|
-
summary:
|
145
|
+
summary: Update positions with a move.
|
147
146
|
test_files: []
|
data/lib/qi/position.rb
DELETED
@@ -1,114 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Qi
|
4
|
-
# The position class.
|
5
|
-
#
|
6
|
-
# @see https://developer.sashite.com/specs/portable-chess-notation
|
7
|
-
class Position
|
8
|
-
# The list of squares of on the board.
|
9
|
-
#
|
10
|
-
# @!attribute [r] squares
|
11
|
-
# @return [Array] The list of squares.
|
12
|
-
attr_reader :squares
|
13
|
-
|
14
|
-
# The list of pieces in hand owned by the bottomside player.
|
15
|
-
#
|
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
|
19
|
-
|
20
|
-
# The list of pieces in hand owned by the topside player.
|
21
|
-
#
|
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
|
25
|
-
|
26
|
-
# Initialize a position.
|
27
|
-
#
|
28
|
-
# @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.
|
32
|
-
#
|
33
|
-
# @example The Shogi's starting position
|
34
|
-
# Position.new(
|
35
|
-
# 'l', 'n', 's', 'g', 'k', 'g', 's', 'n', 'l',
|
36
|
-
# nil, 'r', nil, nil, nil, nil, nil, 'b', nil,
|
37
|
-
# 'p', 'p', 'p', 'p', 'p', 'p', 'p', 'p', 'p',
|
38
|
-
# nil, nil, nil, nil, nil, nil, nil, nil, nil,
|
39
|
-
# nil, nil, nil, nil, nil, nil, nil, nil, nil,
|
40
|
-
# nil, nil, nil, nil, nil, nil, nil, nil, nil,
|
41
|
-
# 'P', 'P', 'P', 'P', 'P', 'P', 'P', 'P', 'P',
|
42
|
-
# nil, 'B', nil, nil, nil, nil, nil, 'R', nil,
|
43
|
-
# 'L', 'N', 'S', 'G', 'K', 'G', 'S', 'N', 'L'
|
44
|
-
# )
|
45
|
-
def initialize(*squares, is_turn_to_topside: false, bottomside_in_hand_pieces: [], topside_in_hand_pieces: [])
|
46
|
-
@squares = squares
|
47
|
-
@is_turn_to_topside = is_turn_to_topside
|
48
|
-
@bottomside_in_hand_pieces = bottomside_in_hand_pieces
|
49
|
-
@topside_in_hand_pieces = topside_in_hand_pieces
|
50
|
-
|
51
|
-
freeze
|
52
|
-
end
|
53
|
-
|
54
|
-
# Apply a move in PMN (Portable Move Notation) format.
|
55
|
-
#
|
56
|
-
# @see https://developer.sashite.com/specs/portable-move-notation
|
57
|
-
# @return [Position] The new position.
|
58
|
-
def call(move)
|
59
|
-
updated_squares = squares.dup
|
60
|
-
updated_bottomside_in_hand_pieces = bottomside_in_hand_pieces.dup
|
61
|
-
updated_topside_in_hand_pieces = topside_in_hand_pieces.dup
|
62
|
-
|
63
|
-
actions = move.each_slice(4)
|
64
|
-
|
65
|
-
actions.each do |action|
|
66
|
-
src_square_id = action.fetch(0)
|
67
|
-
dst_square_id = action.fetch(1)
|
68
|
-
moved_piece_name = action.fetch(2)
|
69
|
-
captured_piece_name = action.fetch(3, nil)
|
70
|
-
|
71
|
-
if src_square_id.nil?
|
72
|
-
if turn_to_topside?
|
73
|
-
piece_in_hand_id = updated_topside_in_hand_pieces.index(moved_piece_name)
|
74
|
-
updated_topside_in_hand_pieces.delete_at(piece_in_hand_id)
|
75
|
-
else
|
76
|
-
piece_in_hand_id = updated_bottomside_in_hand_pieces.index(moved_piece_name)
|
77
|
-
updated_bottomside_in_hand_pieces.delete_at(piece_in_hand_id)
|
78
|
-
end
|
79
|
-
else
|
80
|
-
updated_squares[src_square_id] = nil
|
81
|
-
end
|
82
|
-
|
83
|
-
updated_squares[dst_square_id] = moved_piece_name
|
84
|
-
|
85
|
-
unless captured_piece_name.nil?
|
86
|
-
if turn_to_topside?
|
87
|
-
updated_topside_in_hand_pieces.push(captured_piece_name)
|
88
|
-
else
|
89
|
-
updated_bottomside_in_hand_pieces.push(captured_piece_name)
|
90
|
-
end
|
91
|
-
end
|
92
|
-
end
|
93
|
-
|
94
|
-
self.class.new(*updated_squares, is_turn_to_topside: !turn_to_topside?,
|
95
|
-
bottomside_in_hand_pieces: updated_bottomside_in_hand_pieces,
|
96
|
-
topside_in_hand_pieces: updated_topside_in_hand_pieces)
|
97
|
-
end
|
98
|
-
|
99
|
-
# The list of pieces in hand owned by the current player.
|
100
|
-
#
|
101
|
-
# @return [Array] Topside's pieces in hand if turn to topside, bottomside's
|
102
|
-
# ones otherwise.
|
103
|
-
def in_hand_pieces
|
104
|
-
turn_to_topside? ? topside_in_hand_pieces : bottomside_in_hand_pieces
|
105
|
-
end
|
106
|
-
|
107
|
-
# The side who must play.
|
108
|
-
#
|
109
|
-
# @return [Boolean] True if it is turn to topside, false otherwise.
|
110
|
-
def turn_to_topside?
|
111
|
-
@is_turn_to_topside
|
112
|
-
end
|
113
|
-
end
|
114
|
-
end
|