feen 5.0.0.beta0 → 5.0.0.beta1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 53d90951a531e63bc118104956d29e7c71c90075ab4d42d57506ec5197a783cb
4
- data.tar.gz: 2cbf73113cc4a429420c3c4e0f30c448380a397bf39f264e617fa85119193f7f
3
+ metadata.gz: 2fa56bccbb1911f15569f2f5643646986cbf83661efba3165c69978e7c85fd56
4
+ data.tar.gz: 13d706d1bfe1c09f6d015d0a8f28af621754677711074ec60ec96e395f1a1061
5
5
  SHA512:
6
- metadata.gz: 3919811f24c5ad1f82dfd257eeefe3515efbcb6fd433a1feb20597e4c6a0fbe6a24c33bef56ea7f612e87fac3124b6334b261c1e987a02f7ecf43a6da745f950
7
- data.tar.gz: 7b16a8ecd4d80444d1ee14477be61906f3c9503f0a05083dc191822d8a663a6a4fee379c6b0d40fd9bec509c98f0ae863282e1512a6035b7f0624279cb774da0
6
+ metadata.gz: 5479cbd02c902aa8c45cc12ecfab31c7b7283eb1f8ec60b110c889505a7ebbcf76fa7b0d2e6ad44c60da954312fa85ae03cd4981d929207b0612754f1d62a033
7
+ data.tar.gz: b6268ce619808bf2c2f889261b55007edbfc030bc37b8edea7f73f505de55e8c0fc1cd4a268e8e2bdc71fe21c9fa33f904c8b26a23b92b471e406e109494bd97
data/README.md CHANGED
@@ -6,30 +6,18 @@
6
6
  [![RuboCop](https://github.com/sashite/feen.rb/workflows/RuboCop/badge.svg?branch=main)](https://github.com/sashite/feen.rb/actions?query=workflow%3Arubocop+branch%3Amain)
7
7
  [![License](https://img.shields.io/github/license/sashite/feen.rb?label=License&logo=github)](https://github.com/sashite/feen.rb/raw/main/LICENSE.md)
8
8
 
9
- > __FEEN__ (FEN Easy Extensible Notation) support for the Ruby language.
9
+ > __FEEN__ (Forsyth–Edwards Expanded Notation) support for the Ruby language.
10
10
 
11
11
  ## Overview
12
12
 
13
- This is an implementation of [FEEN](https://developer.sashite.com/specs/fen-easy-extensible-notation), a generic format that can be used for serializing and deserializing positions.
14
-
15
- A __FEEN__ string consists of a single line of ASCII text containing three data fields, separated by a space. These are:
16
-
17
- 1. Piece placement
18
- 2. Side to move
19
- 3. Pieces in hand
20
-
21
- The main chess variants may be supported, including [Chess](https://en.wikipedia.org/wiki/Chess), [Janggi](https://en.wikipedia.org/wiki/Janggi), [Makruk](https://en.wikipedia.org/wiki/Makruk), [Shogi](https://en.wikipedia.org/wiki/Shogi), [Xiangqi](https://en.wikipedia.org/wiki/Xiangqi).
22
-
23
- More exotic variants may be also supported, like: [Dai dai shogi](https://en.wikipedia.org/wiki/Dai_dai_shogi), [Four-player chess](https://en.wikipedia.org/wiki/Four-player_chess), or [Three-dimensional chess](https://en.wikipedia.org/wiki/Three-dimensional_chess) 🖖
24
-
25
- ![3D chess on Star Trek (from the episode "Court Martial")](https://github.com/sashite/feen.rb/raw/main/star-trek-chess.jpg)
13
+ This is an implementation of [FEEN](https://github.com/sashite/specs/blob/main/forsyth-edwards-expanded-notation.md), a flexible and minimalist format for describing chess variant positions.
26
14
 
27
15
  ## Installation
28
16
 
29
17
  Add this line to your application's Gemfile:
30
18
 
31
19
  ```ruby
32
- gem "feen", ">= 5.0.0.beta0"
20
+ gem "feen", ">= 5.0.0.beta1"
33
21
  ```
34
22
 
35
23
  And then execute:
@@ -50,22 +38,20 @@ gem install feen --pre
50
38
 
51
39
  A position can be serialized by filling in these fields:
52
40
 
53
- - **Piece placement**: Describes the placement of pieces on the board with a hash that references each piece on the board. The keys could be numbers, or strings of characters representing coordinates.
54
- - **Side to move**: A char that indicates who moves next. In chess, "`w`" would mean that White must move, and "`b`" that Black must move. In Shogi, "`s`" could mean that Sente must move, and "`g`" that Gote must move. In Xiangqi, "`r`" could mean that Red must move, and "`b`" that Black must move.
55
- - **Pieces in hand**: An array of all captured pieces that remain _in hand_, like in Shogi.
56
- - **Board shape**: An array of integers. For instance, it would be `[10, 9]` in Xiangqi. And it would be `[8, 8]` in Chess.
41
+ - **Board shape**: An array of integers. For instance, it would be `[10, 9]` for a Xiangqi board. Or it would be `[8, 8]` for a Chess board.
42
+ - **Piece placement**: Describes the placement of pieces on the board with a hash that references each piece on the board.
43
+ - **Side to move**: A char that indicates who moves next. In chess, "`w`" would mean that White can play a move.
57
44
 
58
- #### Examples
45
+ #### Example
59
46
 
60
- ##### A classic Tsume Shogi problem
47
+ From a classic Tsume Shogi problem:
61
48
 
62
49
  ```ruby
63
50
  require "feen"
64
51
 
65
52
  Feen.dump(
66
- side_to_move: "s",
67
- pieces_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],
68
53
  board_shape: [9, 9],
54
+ side_to_move: "s",
69
55
  piece_placement: {
70
56
  3 => "s",
71
57
  4 => "k",
@@ -74,23 +60,20 @@ Feen.dump(
74
60
  43 => "+B"
75
61
  }
76
62
  )
77
- # => "3,s,k,s,3/9/4,+P,4/9/7,+B,1/9/9/9/9 s S,b,g*4,n*4,p*17,r*2,s"
63
+ # => "3sks3/9/4+P4/9/7+B1/9/9/9/9 s"
78
64
  ```
79
65
 
80
66
  ### Deserialization
81
67
 
82
68
  Serialized positions can be converted back to fields.
83
69
 
84
- #### Examples
85
-
86
- ##### A classic Tsume Shogi problem
70
+ #### Example
87
71
 
88
72
  ```ruby
89
73
  require "feen"
90
74
 
91
- Feen.parse("3,s,k,s,3/9/4,+P,4/9/7,+B,1/9/9/9/9 s S,b,g*4,n*4,p*17,r*2,s")
75
+ Feen.parse("3sks3/9/4+P4/9/7+B1/9/9/9/9 s")
92
76
  # {:board_shape=>[9, 9],
93
- # :pieces_in_hand=>["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"],
94
77
  # :piece_placement=>{3=>"s", 4=>"k", 5=>"s", 22=>"+P", 43=>"+B"},
95
78
  # :side_to_move=>"s"}
96
79
  ```
@@ -44,7 +44,7 @@ module Feen
44
44
  # 88 => "傌",
45
45
  # 89 => "俥"
46
46
  # }
47
- # ).to_s # => "車,馬,象,士,將,士,象,馬,車/9/1,砲,5,砲,1/卒,1,卒,1,卒,1,卒,1,卒/9/9/兵,1,兵,1,兵,1,兵,1,兵/1,炮,5,炮,1/9/俥,傌,相,仕,帥,仕,相,傌,俥"
47
+ # ).to_s # => "車馬象士將士象馬車/9/151/卒1111卒/9/9/兵1111兵/151/9/俥傌相仕帥仕相傌俥"
48
48
  class PiecePlacement
49
49
  # @param indexes [Array] The shape of the board.
50
50
  # @param piece_placement [Hash] The index of each piece on the board.
@@ -79,6 +79,7 @@ module Feen
79
79
  .map { |square| square.nil? ? 1 : square }
80
80
  .join(",")
81
81
  .gsub(/1,[1,]*1/) { |str| str.split(",").length }
82
+ .delete(",")
82
83
  end
83
84
  end
84
85
  end
data/lib/feen/dumper.rb CHANGED
@@ -1,23 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative File.join("dumper", "piece_placement")
4
- require_relative File.join("dumper", "pieces_in_hand")
5
4
 
6
5
  module Feen
7
6
  # The dumper module.
8
7
  module Dumper
9
8
  # Dump position params into a FEEN string.
10
9
  #
11
- # @param side_to_move [String] Identify the active side.
12
- # @param pieces_in_hand [Array, nil] The list of pieces in hand.
13
10
  # @param board_shape [Array] The shape of the board.
11
+ # @param side_to_move [String] Identify the active side.
14
12
  # @param piece_placement [Hash] The index of each piece on the board.
15
13
  #
16
14
  # @example Dump a classic Tsume Shogi problem
17
15
  # call(
18
- # "side_to_move": "s",
19
- # "pieces_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],
20
16
  # "board_shape": [9, 9],
17
+ # "side_to_move": "s",
21
18
  # "piece_placement": {
22
19
  # 3 => "s",
23
20
  # 4 => "k",
@@ -26,17 +23,14 @@ module Feen
26
23
  # 43 => "+B"
27
24
  # }
28
25
  # )
29
- # # => "3,s,k,s,3/9/4,+P,4/9/7,+B,1/9/9/9/9 s S,b,g*4,n*4,p*17,r*2,s"
26
+ # # => "3sks3/9/4+P4/9/7+B1/9/9/9/9 s"
30
27
  #
31
28
  # @return [String] The FEEN string representing the position.
32
- def self.call(board_shape:, side_to_move:, piece_placement:, pieces_in_hand: nil)
33
- array = [
29
+ def self.call(board_shape:, side_to_move:, piece_placement:)
30
+ [
34
31
  PiecePlacement.new(board_shape, piece_placement).to_s,
35
32
  side_to_move
36
- ]
37
-
38
- array << PiecesInHand.dump(pieces_in_hand) if Array(pieces_in_hand).any?
39
- array.join(" ")
33
+ ].join(" ")
40
34
  end
41
35
  end
42
36
  end
@@ -5,11 +5,12 @@ module Feen
5
5
  # The BoardShape class.
6
6
  #
7
7
  # @example Parse the shape of a shogiban
8
- # BoardShape.new("3,s,k,s,3/9/4,+P,4/9/7,+B,1/9/9/9/9").to_a # => [9, 9]
8
+ # BoardShape.new("3sks3/9/4+P4/9/7+B1/9/9/9/9").to_a # => [9, 9]
9
9
  class BoardShape
10
10
  # @param board_str [String] The flatten board.
11
- def initialize(board_str)
11
+ def initialize(board_str, regex: /\+?[a-z]/i)
12
12
  @board_str = board_str
13
+ @regex = regex
13
14
  end
14
15
 
15
16
  # @return [Array] The size of each dimension of the board.
@@ -21,8 +22,9 @@ module Feen
21
22
 
22
23
  def indexes(string, separator)
23
24
  if separator.empty?
24
- last_index = string.split(",").inject(0) do |counter, sub_string|
25
- number = sub_string.match?(/[0-9]+/) ? Integer(sub_string) : 1
25
+ last_index = string.scan(/(\d+|#{@regex})/).inject(0) do |counter, match|
26
+ sub_string = match[0]
27
+ number = sub_string.match?(/\d+/) ? Integer(sub_string) : 1
26
28
  counter + number
27
29
  end
28
30
 
data/lib/feen/parser.rb CHANGED
@@ -1,8 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative File.join("parser", "board_shape")
4
- require_relative File.join("parser", "pieces_in_hand")
5
- require_relative File.join("parser", "piece_placement")
6
4
 
7
5
  module Feen
8
6
  # The parser module.
@@ -12,11 +10,10 @@ module Feen
12
10
  # @param feen [String] The FEEN string representing a position.
13
11
  #
14
12
  # @example Parse a classic Tsume Shogi problem
15
- # call("3,s,k,s,3/9/4,+P,4/9/7,+B,1/9/9/9/9 s S,b,g*4,n*4,p*17,r*2,s")
13
+ # call("3sks3/9/4+P4/9/7+B1/9/9/9/9 s")
16
14
  # # => {
17
- # # "side_to_move": "s",
18
- # # "pieces_in_hand": ["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"],
19
15
  # # "board_shape": [9, 9],
16
+ # # "side_to_move": "s",
20
17
  # # "piece_placement": {
21
18
  # # 3 => "s",
22
19
  # # 4 => "k",
@@ -26,15 +23,29 @@ module Feen
26
23
  # # }
27
24
  #
28
25
  # @return [Hash] The position params representing the position.
29
- def self.call(feen)
30
- piece_placement, side_to_move, pieces_in_hand = feen.split
26
+ def self.call(feen, regex: /\+?[a-z]/i)
27
+ piece_placement_str, side_to_move_str = feen.split
31
28
 
32
29
  {
33
- board_shape: BoardShape.new(piece_placement).to_a,
34
- pieces_in_hand: PiecesInHand.parse(pieces_in_hand),
35
- piece_placement: PiecePlacement.new(piece_placement).to_h,
36
- side_to_move:
30
+ board_shape: BoardShape.new(piece_placement_str, regex:).to_a,
31
+ piece_placement: piece_placement(piece_placement_str, regex:),
32
+ side_to_move: side_to_move_str
37
33
  }
38
34
  end
35
+
36
+ def self.piece_placement(string, regex:)
37
+ hash = {}
38
+ index = 0
39
+ string.scan(/(\d+|#{regex})/) do |match|
40
+ if /\d+/.match?(match[0])
41
+ index += match[0].to_i
42
+ else
43
+ hash[index] = match[0]
44
+ index += 1
45
+ end
46
+ end
47
+ hash
48
+ end
49
+ private_class_method :piece_placement
39
50
  end
40
51
  end
data/lib/feen.rb CHANGED
@@ -6,20 +6,18 @@ require_relative File.join("feen", "parser")
6
6
  # This module provides a Ruby interface for data serialization and
7
7
  # deserialization in FEEN format.
8
8
  #
9
- # @see https://developer.sashite.com/specs/fen-easy-extensible-notation
9
+ # @see https://github.com/sashite/specs/blob/main/forsyth-edwards-expanded-notation.md
10
10
  module Feen
11
11
  # Dumps position params into a FEEN string.
12
12
  #
13
- # @param pieces_in_hand [Array, nil] The list of pieces in hand.
14
13
  # @param board_shape [Array] The shape of the board.
15
14
  # @param side_to_move [String] The identifier of the player who must play.
16
15
  # @param piece_placement [Hash] The index of each piece on the board.
17
16
  #
18
17
  # @example Dump a classic Tsume Shogi problem
19
18
  # dump(
20
- # "side_to_move": "s",
21
- # "pieces_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],
22
19
  # "board_shape": [9, 9],
20
+ # "side_to_move": "s",
23
21
  # "piece_placement": {
24
22
  # 3 => "s",
25
23
  # 4 => "k",
@@ -28,12 +26,11 @@ module Feen
28
26
  # 43 => "+B"
29
27
  # }
30
28
  # )
31
- # # => "3,s,k,s,3/9/4,+P,4/9/7,+B,1/9/9/9/9 s S,b,g*4,n*4,p*17,r*2,s"
29
+ # # => "3sks3/9/4+P4/9/7+B1/9/9/9/9 s"
32
30
  #
33
31
  # @return [String] The FEEN string representing the position.
34
- def self.dump(board_shape:, side_to_move:, piece_placement:, pieces_in_hand: nil)
32
+ def self.dump(board_shape:, side_to_move:, piece_placement:)
35
33
  Dumper.call(
36
- pieces_in_hand:,
37
34
  board_shape:,
38
35
  side_to_move:,
39
36
  piece_placement:
@@ -45,9 +42,8 @@ module Feen
45
42
  # @param feen [String] The FEEN string representing a position.
46
43
  #
47
44
  # @example Parse a classic Tsume Shogi problem
48
- # parse("3,s,k,s,3/9/4,+P,4/9/7,+B,1/9/9/9/9 s S,b,g*4,n*4,p*17,r*2,s")
45
+ # parse("3sks3/9/4+P4/9/7+B1/9/9/9/9 s")
49
46
  # # => {
50
- # # "pieces_in_hand": ["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"],
51
47
  # # "board_shape": [9, 9],
52
48
  # # "side_to_move": "s",
53
49
  # # "piece_placement": {
@@ -59,7 +55,7 @@ module Feen
59
55
  # # }
60
56
  #
61
57
  # @return [Hash] The position params representing the position.
62
- def self.parse(feen)
63
- Parser.call(feen)
58
+ def self.parse(feen, regex: /\+?[a-z]/i)
59
+ Parser.call(feen, regex:)
64
60
  end
65
61
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: feen
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.0.0.beta0
4
+ version: 5.0.0.beta1
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-04-25 00:00:00.000000000 Z
11
+ date: 2023-04-27 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: A Ruby interface for data serialization and deserialization in FEEN format.
14
14
  email: contact@cyril.email
@@ -21,11 +21,8 @@ files:
21
21
  - lib/feen.rb
22
22
  - lib/feen/dumper.rb
23
23
  - lib/feen/dumper/piece_placement.rb
24
- - lib/feen/dumper/pieces_in_hand.rb
25
24
  - lib/feen/parser.rb
26
25
  - lib/feen/parser/board_shape.rb
27
- - lib/feen/parser/piece_placement.rb
28
- - lib/feen/parser/pieces_in_hand.rb
29
26
  homepage: https://github.com/sashite/feen.rb
30
27
  licenses:
31
28
  - MIT
@@ -1,28 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Feen
4
- module Dumper
5
- # A module that serializes pieces in hand lists into a string.
6
- module PiecesInHand
7
- # Serialize pieces in hand lists into a string.
8
- #
9
- # @param piece_names [Array] A list of pieces in hand.
10
- #
11
- # @example Dump a list of pieces in hand
12
- # dump(["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"])
13
- # # => "S,b,g*4,n*4,p*17,r*2,s"
14
- #
15
- # @example Dump an empty list of pieces in hand
16
- # dump([])
17
- # # => nil
18
- #
19
- # @return [String, nil] A serialized list of pieces in hand.
20
- def self.dump(piece_names)
21
- return if piece_names.empty?
22
-
23
- hash = piece_names.group_by(&:itself).transform_values(&:count)
24
- hash.map { |k, v| v > 1 ? "#{k}*#{v}" : k }.sort.join(",")
25
- end
26
- end
27
- end
28
- end
@@ -1,61 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Feen
4
- module Parser
5
- # The PiecePlacement class.
6
- #
7
- # @example Parse a Shogi problem board and return an array
8
- # PiecePlacement.new("3,s,k,s,3/9/4,+P,4/9/7,+B,1/9/9/9/9").to_a
9
- # # => [
10
- # # nil, nil, nil, "s", "k", "s", nil, nil, nil,
11
- # # nil, nil, nil, nil, nil, nil, nil, nil, nil,
12
- # # nil, nil, nil, nil, "+P", nil, nil, nil, nil,
13
- # # nil, nil, nil, nil, nil, nil, nil, nil, nil,
14
- # # nil, nil, nil, nil, nil, nil, nil, "+B", nil,
15
- # # nil, nil, nil, nil, nil, nil, nil, nil, nil,
16
- # # nil, nil, nil, nil, nil, nil, nil, nil, nil,
17
- # # nil, nil, nil, nil, nil, nil, nil, nil, nil,
18
- # # nil, nil, nil, nil, nil, nil, nil, nil, nil
19
- # # ]
20
- #
21
- # @example Parse a Shogi problem board and return a hash
22
- # PiecePlacement.new("3,s,k,s,3/9/4,+P,4/9/7,+B,1/9/9/9/9").to_h
23
- # # => {
24
- # # 3 => "s",
25
- # # 4 => "k",
26
- # # 5 => "s",
27
- # # 22 => "+P",
28
- # # 43 => "+B"
29
- # # }
30
- class PiecePlacement
31
- # @param piece_placement_str [String] The placement of pieces on the board.
32
- def initialize(piece_placement_str)
33
- @piece_placement_str = piece_placement_str
34
- end
35
-
36
- # @return [Array] The list of pieces on the board.
37
- def to_a
38
- @piece_placement_str
39
- .split(%r{[/,]+})
40
- .flat_map { |str| row(str) }
41
- end
42
-
43
- # @return [Hash] The index of each piece on the board.
44
- def to_h
45
- to_a
46
- .each_with_index
47
- .inject({}) do |h, (v, i)|
48
- next h if v.nil?
49
-
50
- h.merge(i => v)
51
- end
52
- end
53
-
54
- private
55
-
56
- def row(string)
57
- string.match?(/[0-9]+/) ? ::Array.new(Integer(string)) : string
58
- end
59
- end
60
- end
61
- end
@@ -1,34 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Feen
4
- module Parser
5
- # The pieces in hand module.
6
- module PiecesInHand
7
- # The list of pieces in hand grouped by players.
8
- #
9
- # @param pieces_in_hand [String, nil] The serialized list of pieces in hand.
10
- #
11
- # @example Parse a list of serialized pieces in hand
12
- # parse("S,b,g*4,n*4,p*17,r*2,s")
13
- # # => ["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"]
14
- #
15
- # @example Parse an empty list of serialized pieces in hand
16
- # parse("-")
17
- # # => []
18
- #
19
- # @return [Array] The list of pieces in hand grouped by players.
20
- def self.parse(pieces_in_hand)
21
- return if pieces_in_hand.nil?
22
-
23
- pieces_in_hand.split(",").flat_map do |piece|
24
- if piece.include?("*")
25
- letter, count = piece.split("*")
26
- [letter] * count.to_i
27
- else
28
- piece
29
- end
30
- end.sort
31
- end
32
- end
33
- end
34
- end