qi 6.1.1 → 8.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 +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