feen 5.0.0.beta0 → 5.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
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