qi 6.1.1 → 8.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.
Files changed (5) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +15 -122
  3. data/lib/qi.rb +64 -3
  4. metadata +4 -5
  5. data/lib/qi/position.rb +0 -153
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6d85f0a7a03ecc612c24bf29363b843fb88fa0572b8f74bf955eb92039314a54
4
- data.tar.gz: ee40f146832333bf952ba810d1d95fafeabc8e6dc17905e39310b7c746faeb5f
3
+ metadata.gz: eb79c3951884c03bb69f5b570c776e6154181b2e9c780a568faeb37b079b749e
4
+ data.tar.gz: 180ff6505865b89be300c3f3fbdc99d1f945d49f2469e73c6c3b28ad2cedd3f0
5
5
  SHA512:
6
- metadata.gz: 1310f61417a533fb3c6f19350b759f1831d1b9e7afc04ac4946ac36850cd53eea5ed27942af0f9132d93871047f888217f265d6db782e2d153453ec34c4c80fd
7
- data.tar.gz: a2c2ca62646b82063b132d69a2f5fc15b062500a23daa6c5953154b23c798fdbbe9c4c6d4f3406be8b01b07c9737a0570c4d6fa0c27f69021c41a4fba79abb3d
6
+ metadata.gz: 6e1281cc8424faee3b9be0be0842b816aa824595e3f9e62e54c0bb0949dcf1671ce942bfb47bf5ffcb78abdff03dd11de723b3dba58256570b33b83fad6545a9
7
+ data.tar.gz: e3b0c8103baa427ff22f770c6afc70eef6c5251fee580864c23129c264ea5534528dd365099fa9f5e4bf2b9f5d8d4762094e6bf771bab2acde4eeeeee795d680
data/README.md CHANGED
@@ -5,14 +5,14 @@
5
5
  [![Inline docs](https://inch-ci.org/github/sashite/qi.rb.svg?branch=master)][inchpages]
6
6
  [![Documentation](https://img.shields.io/:yard-docs-38c800.svg)][rubydoc]
7
7
 
8
- > `Qi` (棋) is an abstraction for initializing and updating positions of chess variants (including Chess, Janggi, Markruk, Shogi, Xiangqi).
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 'qi'
15
+ gem "qi"
16
16
  ```
17
17
 
18
18
  And then execute:
@@ -25,128 +25,21 @@ 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
- # List of moves in Portable Move Notation (https://developer.sashite.com/specs/portable-move-notation) format.
60
- moves = [
61
- [ 56, 47, 'P' ],
62
- [ 3, 11, 'g' ],
63
- [ 64, 24, '+B', 'P' ],
64
- [ 5, 14, 'g' ],
65
- [ 24, 14, '+B', 'G' ],
66
- [ 4, 3, 'k' ],
67
- [ nil, 13, 'G' ]
68
- ]
69
-
70
- last_position = moves.reduce(starting_position) do |position, move|
71
- position.call(move)
72
- end
73
-
74
- last_position.topside_in_hand_pieces # => []
75
- last_position.squares # => ["l", "n", "s", "k", nil, nil, "s", "n", "l",
76
- # nil, "r", "g", nil, "G", "+B", nil, "b", nil,
77
- # "p", "p", "p", "p", "p", "p", nil, "p", "p",
78
- # nil, nil, nil, nil, nil, nil, nil, nil, nil,
79
- # nil, nil, nil, nil, nil, nil, nil, nil, nil,
80
- # nil, nil, "P", nil, nil, nil, nil, nil, nil,
81
- # "P", "P", nil, "P", "P", "P", "P", "P", "P",
82
- # nil, nil, nil, nil, nil, nil, nil, "R", nil,
83
- # "L", "N", "S", "G", "K", "G", "S", "N", "L"]
84
- last_position.bottomside_in_hand_pieces # => ["P"]
85
- last_position.in_hand_pieces # => []
86
- last_position.turn_to_topside? # => true
87
- ```
88
-
89
- Another example with Xiangqi's Short Double Cannons Checkmate:
90
-
91
28
  ```ruby
92
- require 'qi'
93
-
94
- starting_position = Qi::Position.new(
95
- '車', '馬', '象', '士', '將', '士', '象', '馬', '車',
96
- nil, nil, nil, nil, nil, nil, nil, nil, nil,
97
- nil, '砲', nil, nil, nil, nil, nil, '砲', nil,
98
- '卒', nil, '卒', nil, '卒', nil, '卒', nil, '卒',
99
- nil, nil, nil, nil, nil, nil, nil, nil, nil,
100
- nil, nil, nil, nil, nil, nil, nil, nil, nil,
101
- '兵', nil, '兵', nil, '兵', nil, '兵', nil, '兵',
102
- nil, '炮', nil, nil, nil, nil, nil, '炮', nil,
103
- nil, nil, nil, nil, nil, nil, nil, nil, nil,
104
- '俥', '傌', '相', '仕', '帥', '仕', '相', '傌', '俥'
29
+ require "qi"
30
+
31
+ Qi.call(
32
+ [43, 13, "+B"],
33
+ *%w[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],
34
+ **{
35
+ 3 => "s",
36
+ 4 => "k",
37
+ 5 => "s",
38
+ 22 => "+P",
39
+ 43 => "+B"
40
+ }
105
41
  )
106
-
107
- starting_position.topside_in_hand_pieces # => []
108
- starting_position.squares # => ["車", "馬", "象", "士", "將", "士", "象", "馬", "車",
109
- # nil, nil, nil, nil, nil, nil, nil, nil, nil,
110
- # nil, "砲", nil, nil, nil, nil, nil, "砲", nil,
111
- # "卒", nil, "卒", nil, "卒", nil, "卒", nil, "卒",
112
- # nil, nil, nil, nil, nil, nil, nil, nil, nil,
113
- # nil, nil, nil, nil, nil, nil, nil, nil, nil,
114
- # "兵", nil, "兵", nil, "兵", nil, "兵", nil, "兵",
115
- # nil, "炮", nil, nil, nil, nil, nil, "炮", nil,
116
- # nil, nil, nil, nil, nil, nil, nil, nil, nil,
117
- # "俥", "傌", "相", "仕", "帥", "仕", "相", "傌", "俥"]
118
- starting_position.bottomside_in_hand_pieces # => []
119
- starting_position.in_hand_pieces # => []
120
- starting_position.turn_to_topside? # => false
121
-
122
- moves = [
123
- [ 64, 67, '炮' ],
124
- [ 25, 22, '砲' ],
125
- [ 70, 52, '炮' ],
126
- [ 19, 55, '砲' ],
127
- [ 67, 31, '炮' ],
128
- [ 22, 58, '砲' ],
129
- [ 52, 49, '炮' ]
130
- ]
131
-
132
- last_position = moves.reduce(starting_position) do |position, move|
133
- position.call(move)
134
- end
135
-
136
- last_position.topside_in_hand_pieces # => []
137
- last_position.squares # => ["車", "馬", "象", "士", "將", "士", "象", "馬", "車",
138
- # nil, nil, nil, nil, nil, nil, nil, nil, nil,
139
- # nil, nil, nil, nil, nil, nil, nil, nil, nil,
140
- # "卒", nil, "卒", nil, "炮", nil, "卒", nil, "卒",
141
- # nil, nil, nil, nil, nil, nil, nil, nil, nil,
142
- # nil, nil, nil, nil, "炮", nil, nil, nil, nil,
143
- # "兵", "砲", "兵", nil, "砲", nil, "兵", nil, "兵",
144
- # nil, nil, nil, nil, nil, nil, nil, nil, nil,
145
- # nil, nil, nil, nil, nil, nil, nil, nil, nil,
146
- # "俥", "傌", "相", "仕", "帥", "仕", "相", "傌", "俥"]
147
- last_position.bottomside_in_hand_pieces # => []
148
- last_position.in_hand_pieces # => []
149
- last_position.turn_to_topside? # => true
42
+ # => {:square=>{3=>"s", 4=>"k", 5=>"s", 22=>"+P", 13=>"+B"}, :in_hand=>["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"]}
150
43
  ```
151
44
 
152
45
  ## License
data/lib/qi.rb CHANGED
@@ -1,7 +1,68 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # The Qi module.
3
+ # The Qi abstraction.
4
+ #
5
+ # @example
6
+ # Qi.call(
7
+ # [43, 13, "+B"],
8
+ # "in_hand": %w[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],
9
+ # "square": {
10
+ # 3 => "s",
11
+ # 4 => "k",
12
+ # 5 => "s",
13
+ # 22 => "+P",
14
+ # 43 => "+B"
15
+ # }
16
+ # )
17
+ # # => {:in_hand=>["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"], :square=>{3=>"s", 4=>"k", 5=>"s", 22=>"+P", 13=>"+B"}}
4
18
  module Qi
5
- end
19
+ # Apply a move to the position.
20
+ #
21
+ # @param move [Array] The move to play.
22
+ # @param in_hand [Array] The list of pieces in hand.
23
+ # @param square [Hash] The index of each piece on the board.
24
+ #
25
+ # @see https://developer.sashite.com/specs/portable-chess-notation
26
+ # @see https://developer.sashite.com/specs/portable-move-notation
27
+ #
28
+ # @example A classic Shogi problem
29
+ # call(
30
+ # [43, 13, "+B"],
31
+ # *%w[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],
32
+ # **{
33
+ # 3 => "s",
34
+ # 4 => "k",
35
+ # 5 => "s",
36
+ # 22 => "+P",
37
+ # 43 => "+B"
38
+ # }
39
+ # )
40
+ # # => {:in_hand=>["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"], :square=>{3=>"s", 4=>"k", 5=>"s", 22=>"+P", 13=>"+B"}}
41
+ #
42
+ # @return [Hash] The next position.
43
+ def self.call(move, *in_hand, **square)
44
+ actions = move.each_slice(4)
45
+
46
+ actions.each do |action|
47
+ src_square_id = action.fetch(0)
48
+ dst_square_id = action.fetch(1)
49
+ moved_piece_name = action.fetch(2)
50
+ captured_piece_name = action.fetch(3, nil)
51
+
52
+ if src_square_id.nil?
53
+ piece_in_hand_id = in_hand.index(moved_piece_name)
54
+ in_hand.delete_at(piece_in_hand_id) unless piece_in_hand_id.nil?
55
+ else
56
+ square.delete(src_square_id)
57
+ end
6
58
 
7
- require_relative 'qi/position'
59
+ square[dst_square_id] = moved_piece_name
60
+ in_hand.push(captured_piece_name) unless captured_piece_name.nil?
61
+ end
62
+
63
+ {
64
+ in_hand: in_hand,
65
+ square: square
66
+ }
67
+ end
68
+ 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: 6.1.1
4
+ version: 8.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-07-27 00:00:00.000000000 Z
11
+ date: 2020-10-02 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: '0'
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: Represent positions and play moves.
145
+ summary: Update positions with a move.
147
146
  test_files: []
@@ -1,153 +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 Chess's starting position
34
- # Position.new(
35
- # '♜', '♞', '♝', '♛', '♚', '♝', '♞', '♜',
36
- # '♟', '♟', '♟', '♟', '♟', '♟', '♟', '♟',
37
- # nil, nil, nil, nil, nil, nil, nil, nil,
38
- # nil, nil, nil, nil, nil, nil, nil, nil,
39
- # nil, nil, nil, nil, nil, nil, nil, nil,
40
- # nil, nil, nil, nil, nil, nil, nil, nil,
41
- # '♙', '♙', '♙', '♙', '♙', '♙', '♙', '♙',
42
- # '♖', '♘', '♗', '♕', '♔', '♗', '♘', '♖'
43
- # )
44
- #
45
- # @example Makruk's starting position
46
- # Position.new(
47
- # '♜', '♞', '♝', '♛', '♚', '♝', '♞', '♜',
48
- # nil, nil, nil, nil, nil, nil, nil, nil,
49
- # '♟', '♟', '♟', '♟', '♟', '♟', '♟', '♟',
50
- # nil, nil, nil, nil, nil, nil, nil, nil,
51
- # nil, nil, nil, nil, nil, nil, nil, nil,
52
- # '♙', '♙', '♙', '♙', '♙', '♙', '♙', '♙',
53
- # nil, nil, nil, nil, nil, nil, nil, nil,
54
- # '♖', '♘', '♗', '♔', '♕', '♗', '♘', '♖'
55
- # )
56
- #
57
- # @example Shogi's starting position
58
- # Position.new(
59
- # 'l', 'n', 's', 'g', 'k', 'g', 's', 'n', 'l',
60
- # nil, 'r', nil, nil, nil, nil, nil, 'b', nil,
61
- # 'p', 'p', 'p', 'p', 'p', 'p', 'p', 'p', 'p',
62
- # nil, nil, nil, nil, nil, nil, nil, nil, nil,
63
- # nil, nil, nil, nil, nil, nil, nil, nil, nil,
64
- # nil, nil, nil, nil, nil, nil, nil, nil, nil,
65
- # 'P', 'P', 'P', 'P', 'P', 'P', 'P', 'P', 'P',
66
- # nil, 'B', nil, nil, nil, nil, nil, 'R', nil,
67
- # 'L', 'N', 'S', 'G', 'K', 'G', 'S', 'N', 'L'
68
- # )
69
- #
70
- # @example Xiangqi's starting position
71
- # Position.new(
72
- # '車', '馬', '象', '士', '將', '士', '象', '馬', '車',
73
- # nil, nil, nil, nil, nil, nil, nil, nil, nil,
74
- # nil, '砲', nil, nil, nil, nil, nil, '砲', nil,
75
- # '卒', nil, '卒', nil, '卒', nil, '卒', nil, '卒',
76
- # nil, nil, nil, nil, nil, nil, nil, nil, nil,
77
- # nil, nil, nil, nil, nil, nil, nil, nil, nil,
78
- # '兵', nil, '兵', nil, '兵', nil, '兵', nil, '兵',
79
- # nil, '炮', nil, nil, nil, nil, nil, '炮', nil,
80
- # nil, nil, nil, nil, nil, nil, nil, nil, nil,
81
- # '俥', '傌', '相', '仕', '帥', '仕', '相', '傌', '俥'
82
- # )
83
- def initialize(*squares, is_turn_to_topside: false, bottomside_in_hand_pieces: [], topside_in_hand_pieces: [])
84
- @squares = squares
85
- @is_turn_to_topside = is_turn_to_topside
86
- @bottomside_in_hand_pieces = bottomside_in_hand_pieces
87
- @topside_in_hand_pieces = topside_in_hand_pieces
88
-
89
- freeze
90
- end
91
-
92
- # Apply a move in PMN (Portable Move Notation) format.
93
- #
94
- # @param move [Array] The move to play.
95
- # @see https://developer.sashite.com/specs/portable-move-notation
96
- # @return [Position] The new position.
97
- def call(move)
98
- updated_squares = squares.dup
99
- updated_bottomside_in_hand_pieces = bottomside_in_hand_pieces.dup
100
- updated_topside_in_hand_pieces = topside_in_hand_pieces.dup
101
-
102
- actions = move.each_slice(4)
103
-
104
- actions.each do |action|
105
- src_square_id = action.fetch(0)
106
- dst_square_id = action.fetch(1)
107
- moved_piece_name = action.fetch(2)
108
- captured_piece_name = action.fetch(3, nil)
109
-
110
- if src_square_id.nil?
111
- if turn_to_topside?
112
- piece_in_hand_id = updated_topside_in_hand_pieces.index(moved_piece_name)
113
- updated_topside_in_hand_pieces.delete_at(piece_in_hand_id)
114
- else
115
- piece_in_hand_id = updated_bottomside_in_hand_pieces.index(moved_piece_name)
116
- updated_bottomside_in_hand_pieces.delete_at(piece_in_hand_id)
117
- end
118
- else
119
- updated_squares[src_square_id] = nil
120
- end
121
-
122
- updated_squares[dst_square_id] = moved_piece_name
123
-
124
- unless captured_piece_name.nil?
125
- if turn_to_topside?
126
- updated_topside_in_hand_pieces.push(captured_piece_name)
127
- else
128
- updated_bottomside_in_hand_pieces.push(captured_piece_name)
129
- end
130
- end
131
- end
132
-
133
- self.class.new(*updated_squares, is_turn_to_topside: !turn_to_topside?,
134
- bottomside_in_hand_pieces: updated_bottomside_in_hand_pieces,
135
- topside_in_hand_pieces: updated_topside_in_hand_pieces)
136
- end
137
-
138
- # The list of pieces in hand owned by the current player.
139
- #
140
- # @return [Array] Topside's pieces in hand if turn to topside, bottomside's
141
- # ones otherwise.
142
- def in_hand_pieces
143
- turn_to_topside? ? topside_in_hand_pieces : bottomside_in_hand_pieces
144
- end
145
-
146
- # The side who must play.
147
- #
148
- # @return [Boolean] True if it is turn to topside, false otherwise.
149
- def turn_to_topside?
150
- @is_turn_to_topside
151
- end
152
- end
153
- end