feen 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'dumper/board'
4
+ require_relative 'dumper/pieces_in_hand'
5
+ require_relative 'dumper/turn'
6
+
7
+ module FEEN
8
+ # The dumper module.
9
+ module Dumper
10
+ # Dump position params into a FEEN string.
11
+ #
12
+ # @param active_side [Integer] The identifier of the player who must play.
13
+ # @param indexes [Array] The shape of the board.
14
+ # @param pieces_in_hand_by_players [Array] The list of pieces in hand
15
+ # grouped by players.
16
+ # @param squares [Array] The list of squares on the board.
17
+ #
18
+ # @example Dump Four-player chess's starting position
19
+ # call(
20
+ # active_side: 0,
21
+ # indexes: [14, 14],
22
+ # pieces_in_hand_by_players: [
23
+ # [],
24
+ # [],
25
+ # [],
26
+ # []
27
+ # ],
28
+ # squares: [
29
+ # nil , nil , nil , "yR", "yN", "yB", "yK", "yQ", "yB", "yN", "yR", nil , nil , nil ,
30
+ # nil , nil , nil , "yP", "yP", "yP", "yP", "yP", "yP", "yP", "yP", nil , nil , nil ,
31
+ # nil , nil , nil , nil , nil , nil , nil , nil , nil , nil , nil , nil , nil , nil ,
32
+ # "bR", "bP", nil , nil , nil , nil , nil , nil , nil , nil , nil , nil , "gP", "gR",
33
+ # "bN", "bP", nil , nil , nil , nil , nil , nil , nil , nil , nil , nil , "gP", "gN",
34
+ # "bB", "bP", nil , nil , nil , nil , nil , nil , nil , nil , nil , nil , "gP", "gB",
35
+ # "bK", "bP", nil , nil , nil , nil , nil , nil , nil , nil , nil , nil , "gP", "gQ",
36
+ # "bQ", "bP", nil , nil , nil , nil , nil , nil , nil , nil , nil , nil , "gP", "gK",
37
+ # "bB", "bP", nil , nil , nil , nil , nil , nil , nil , nil , nil , nil , "gP", "gB",
38
+ # "bN", "bP", nil , nil , nil , nil , nil , nil , nil , nil , nil , nil , "gP", "gN",
39
+ # "bR", "bP", nil , nil , nil , nil , nil , nil , nil , nil , nil , nil , "gP", "gR",
40
+ # nil , nil , nil , nil , nil , nil , nil , nil , nil , nil , nil , nil , nil , nil ,
41
+ # nil , nil , nil , "rP", "rP", "rP", "rP", "rP", "rP", "rP", "rP", nil , nil , nil ,
42
+ # nil , nil , nil , "rR", "rN", "rB", "rQ", "rK", "rB", "rN", "rR", nil , nil , nil
43
+ # ]
44
+ # )
45
+ # # => "3,yR,yN,yB,yK,yQ,yB,yN,yR,3/3,yP,yP,yP,yP,yP,yP,yP,yP,3/14/bR,bP,10,gP,gR/bN,bP,10,gP,gN/bB,bP,10,gP,gB/bK,bP,10,gP,gQ/bQ,bP,10,gP,gK/bB,bP,10,gP,gB/bN,bP,10,gP,gN/bR,bP,10,gP,gR/14/3,rP,rP,rP,rP,rP,rP,rP,rP,3/3,rR,rN,rB,rQ,rK,rB,rN,rR,3 0 ///"
46
+ #
47
+ # @return [String] The FEEN string representing the position.
48
+ def self.call(active_side:, indexes:, pieces_in_hand_by_players:, squares:)
49
+ [
50
+ Board.new(*indexes).to_s(*squares),
51
+ Turn.dump(active_side, pieces_in_hand_by_players.length),
52
+ PiecesInHand.dump(*pieces_in_hand_by_players)
53
+ ].join(' ')
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'inconsistent_size_error'
4
+
5
+ module FEEN
6
+ module Dumper
7
+ # The board class.
8
+ class Board
9
+ # @param indexes [Array] The shape of the board.
10
+ def initialize(*indexes)
11
+ @indexes = indexes
12
+ end
13
+
14
+ # @param squares [Array] The list of squares on the board.
15
+ #
16
+ # @return [String] The string representing the board.
17
+ def to_s(*squares)
18
+ raise InconsistentSizeError unless squares.length == @indexes.inject(:*)
19
+
20
+ unflatten(squares, *@indexes)
21
+ end
22
+
23
+ private
24
+
25
+ def unflatten(squares, *remaining_indexes)
26
+ return row(*squares) if remaining_indexes.length == 1
27
+
28
+ squares
29
+ .each_slice(squares.length / remaining_indexes.fetch(0))
30
+ .to_a
31
+ .map { |sub_squares| unflatten(sub_squares, *remaining_indexes[1..]) }
32
+ .join('/' * remaining_indexes.length.pred)
33
+ end
34
+
35
+ def row(*squares)
36
+ squares
37
+ .map { |square| square.nil? ? 1 : square }
38
+ .join(',')
39
+ .gsub(/1,[1,]*1/) { |str| str.split(',').length }
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FEEN
4
+ module Dumper
5
+ # The inconsistent size error class.
6
+ class InconsistentSizeError < ::StandardError; end
7
+ end
8
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FEEN
4
+ module Dumper
5
+ # The pieces in hand class.
6
+ class PiecesInHand
7
+ # Serialize pieces in hand lists into a string.
8
+ #
9
+ # @param pieces_in_hand_by_players [Array] The list of pieces in hand
10
+ # grouped by players.
11
+ #
12
+ # @return [String] A string representing the pieces in hand of both
13
+ # players.
14
+ def self.dump(*pieces_in_hand_by_players)
15
+ pieces_in_hand_by_players.map { |pieces| new(*pieces).to_s }.join('/')
16
+ end
17
+
18
+ # @param pieces [Array] A list of pieces in hand.
19
+ def initialize(*pieces)
20
+ @pieces = pieces.sort
21
+ end
22
+
23
+ # @return [String] A string representing the pieces in hand.
24
+ def to_s
25
+ @pieces.join(',')
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FEEN
4
+ module Dumper
5
+ # The turn class.
6
+ module Turn
7
+ # @param active_side [Integer] The identifier of the active player.
8
+ # @param sides_count [Integer] The number of players.
9
+ #
10
+ # @return [String] The number that identify the player who have to play.
11
+ def self.dump(active_side, sides_count)
12
+ String(Integer(active_side) % Integer(sides_count))
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'parser/board'
4
+ require_relative 'parser/pieces_in_hand'
5
+ require_relative 'parser/shape'
6
+ require_relative 'parser/turn'
7
+
8
+ module FEEN
9
+ # The parser module.
10
+ module Parser
11
+ # Parse a FEEN string into position params.
12
+ #
13
+ # @param feen [String] The FEEN string representing a position.
14
+ #
15
+ # @example Parse Four-player chess's starting position
16
+ # call("3,yR,yN,yB,yK,yQ,yB,yN,yR,3/3,yP,yP,yP,yP,yP,yP,yP,yP,3/14/bR,bP,10,gP,gR/bN,bP,10,gP,gN/bB,bP,10,gP,gB/bK,bP,10,gP,gQ/bQ,bP,10,gP,gK/bB,bP,10,gP,gB/bN,bP,10,gP,gN/bR,bP,10,gP,gR/14/3,rP,rP,rP,rP,rP,rP,rP,rP,3/3,rR,rN,rB,rQ,rK,rB,rN,rR,3 0 ///")
17
+ # # => {
18
+ # # active_side: 0,
19
+ # # indexes: [14, 14],
20
+ # # pieces_in_hand_by_players: [
21
+ # # [],
22
+ # # [],
23
+ # # [],
24
+ # # []
25
+ # # ],
26
+ # # squares: [
27
+ # # nil , nil , nil , "yR", "yN", "yB", "yK", "yQ", "yB", "yN", "yR", nil , nil , nil ,
28
+ # # nil , nil , nil , "yP", "yP", "yP", "yP", "yP", "yP", "yP", "yP", nil , nil , nil ,
29
+ # # nil , nil , nil , nil , nil , nil , nil , nil , nil , nil , nil , nil , nil , nil ,
30
+ # # "bR", "bP", nil , nil , nil , nil , nil , nil , nil , nil , nil , nil , "gP", "gR",
31
+ # # "bN", "bP", nil , nil , nil , nil , nil , nil , nil , nil , nil , nil , "gP", "gN",
32
+ # # "bB", "bP", nil , nil , nil , nil , nil , nil , nil , nil , nil , nil , "gP", "gB",
33
+ # # "bK", "bP", nil , nil , nil , nil , nil , nil , nil , nil , nil , nil , "gP", "gQ",
34
+ # # "bQ", "bP", nil , nil , nil , nil , nil , nil , nil , nil , nil , nil , "gP", "gK",
35
+ # # "bB", "bP", nil , nil , nil , nil , nil , nil , nil , nil , nil , nil , "gP", "gB",
36
+ # # "bN", "bP", nil , nil , nil , nil , nil , nil , nil , nil , nil , nil , "gP", "gN",
37
+ # # "bR", "bP", nil , nil , nil , nil , nil , nil , nil , nil , nil , nil , "gP", "gR",
38
+ # # nil , nil , nil , nil , nil , nil , nil , nil , nil , nil , nil , nil , nil , nil ,
39
+ # # nil , nil , nil , "rP", "rP", "rP", "rP", "rP", "rP", "rP", "rP", nil , nil , nil ,
40
+ # # nil , nil , nil , "rR", "rN", "rB", "rQ", "rK", "rB", "rN", "rR", nil , nil , nil
41
+ # # ]
42
+ # # }
43
+ #
44
+ # @return [Hash] The position params representing the position.
45
+ def self.call(feen)
46
+ params(*feen.split(' '))
47
+ end
48
+
49
+ # Parse the FEEN string's three fields and return the position params.
50
+ #
51
+ # @param board [String] The flatten board.
52
+ # @param active_side [String] The active side identifier.
53
+ # @param in_hand [String] The captured actors.
54
+ #
55
+ # @return [Hash] The position params representing the position.
56
+ private_class_method def self.params(board, active_side, in_hand)
57
+ {
58
+ active_side: Turn.parse(active_side),
59
+ indexes: Shape.new(board).to_a,
60
+ pieces_in_hand_by_players: PiecesInHand.parse(in_hand),
61
+ squares: Board.new(board).to_a
62
+ }
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FEEN
4
+ module Parser
5
+ # The board class.
6
+ #
7
+ # @example Parse a Shogi problem board
8
+ # Board.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
+ class Board
21
+ # @param board [String] The flatten board.
22
+ def initialize(board)
23
+ @board = board
24
+ end
25
+
26
+ # @return [Array] The list of squares on the board.
27
+ def to_a
28
+ @board
29
+ .split(%r{[/,]+})
30
+ .flat_map { |str| row(str) }
31
+ end
32
+
33
+ private
34
+
35
+ def row(string)
36
+ string.match?(/[0-9]+/) ? ::Array.new(Integer(string)) : string
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FEEN
4
+ module Parser
5
+ # The pieces in hand class.
6
+ module PiecesInHand
7
+ # The list of pieces in hand grouped by players.
8
+ #
9
+ # @param pieces_in_hand_by_players_str [String] The serialized list of
10
+ # pieces in hand grouped by players.
11
+ #
12
+ # @example Parse a list of serialized pieces in hand
13
+ # parse("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
+ # # %w[S],
16
+ # # %w[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]
17
+ # # ]
18
+ #
19
+ # @return [Array] The list of pieces in hand grouped by players.
20
+ def self.parse(pieces_in_hand_by_players_str)
21
+ pieces_in_hand_by_players_str
22
+ .split('/', -1)
23
+ .map { |pieces_in_hand_str| pieces_in_hand_str.split(',') }
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FEEN
4
+ module Parser
5
+ # The shape class.
6
+ #
7
+ # @example Parse the shape of a shogiban
8
+ # Shape.new("3,s,k,s,3/9/4,+P,4/9/7,+B,1/9/9/9/9").to_a # => [9, 9]
9
+ class Shape
10
+ # @param board [String] The flatten board.
11
+ def initialize(board)
12
+ @board = board
13
+ end
14
+
15
+ # @return [Array] The size of each dimension of the board.
16
+ def to_a
17
+ indexes(@board, @board.scan(%r{/+}).sort.fetch(-1))
18
+ end
19
+
20
+ private
21
+
22
+ def indexes(string, separator)
23
+ 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
26
+ counter + number
27
+ end
28
+
29
+ return [last_index]
30
+ end
31
+
32
+ sub_strings = string.split(separator)
33
+ [sub_strings.length] + indexes(sub_strings.fetch(0), separator[1..])
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FEEN
4
+ module Parser
5
+ # The turn class.
6
+ module Turn
7
+ # @param active_side_id [String] The identifier of bottom-side and
8
+ # top-side.
9
+ #
10
+ # @example Parse the number that identify the player who have to play
11
+ # parse("0") # => 0
12
+ #
13
+ # @return [Integer] The number that identify the player who have to play.
14
+ def self.parse(active_side_id)
15
+ Integer(active_side_id)
16
+ end
17
+ end
18
+ end
19
+ end
metadata ADDED
@@ -0,0 +1,156 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: feen
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Cyril Kato
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-08-16 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: byebug
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rubocop-performance
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rubocop-thread_safety
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: simplecov
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: yard
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ description: A Ruby interface for data serialization and deserialization in FEEN format.
112
+ email: contact@cyril.email
113
+ executables: []
114
+ extensions: []
115
+ extra_rdoc_files: []
116
+ files:
117
+ - LICENSE.md
118
+ - README.md
119
+ - lib/feen.rb
120
+ - lib/feen/dumper.rb
121
+ - lib/feen/dumper/board.rb
122
+ - lib/feen/dumper/inconsistent_size_error.rb
123
+ - lib/feen/dumper/pieces_in_hand.rb
124
+ - lib/feen/dumper/turn.rb
125
+ - lib/feen/parser.rb
126
+ - lib/feen/parser/board.rb
127
+ - lib/feen/parser/pieces_in_hand.rb
128
+ - lib/feen/parser/shape.rb
129
+ - lib/feen/parser/turn.rb
130
+ homepage: https://developer.sashite.com/specs/forsyth-edwards-expanded-notation
131
+ licenses:
132
+ - MIT
133
+ metadata:
134
+ bug_tracker_uri: https://github.com/sashite/feen.rb/issues
135
+ documentation_uri: https://rubydoc.info/gems/feen/index
136
+ source_code_uri: https://github.com/sashite/feen.rb
137
+ post_install_message:
138
+ rdoc_options: []
139
+ require_paths:
140
+ - lib
141
+ required_ruby_version: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ required_rubygems_version: !ruby/object:Gem::Requirement
147
+ requirements:
148
+ - - ">="
149
+ - !ruby/object:Gem::Version
150
+ version: '0'
151
+ requirements: []
152
+ rubygems_version: 3.1.2
153
+ signing_key:
154
+ specification_version: 4
155
+ summary: FEEN support for the Ruby language.
156
+ test_files: []