sashite-pan 2.0.0 → 4.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.
- checksums.yaml +4 -4
- data/README.md +507 -171
- data/lib/sashite/pan/action/capture.rb +253 -0
- data/lib/sashite/pan/action/drop.rb +261 -0
- data/lib/sashite/pan/action/drop_capture.rb +261 -0
- data/lib/sashite/pan/action/modify.rb +221 -0
- data/lib/sashite/pan/action/move.rb +253 -0
- data/lib/sashite/pan/action/pass.rb +189 -0
- data/lib/sashite/pan/action/special.rb +255 -0
- data/lib/sashite/pan/action/static_capture.rb +207 -0
- data/lib/sashite/pan/action.rb +180 -0
- data/lib/sashite/pan.rb +30 -47
- data/lib/sashite-pan.rb +10 -3
- metadata +47 -10
- data/lib/sashite/pan/dumper/error.rb +0 -11
- data/lib/sashite/pan/dumper.rb +0 -81
- data/lib/sashite/pan/parser/error.rb +0 -11
- data/lib/sashite/pan/parser.rb +0 -90
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "sashite/cell"
|
|
4
|
+
|
|
5
|
+
module Sashite
|
|
6
|
+
module Pan
|
|
7
|
+
module Action
|
|
8
|
+
# Static capture action class
|
|
9
|
+
#
|
|
10
|
+
# Handles capture actions without movement - removing a piece from the board
|
|
11
|
+
# without the capturing piece moving.
|
|
12
|
+
#
|
|
13
|
+
# Format: +<square>
|
|
14
|
+
# Examples: "+d4", "+e5"
|
|
15
|
+
class StaticCapture
|
|
16
|
+
# Action type
|
|
17
|
+
TYPE = :static_capture
|
|
18
|
+
|
|
19
|
+
# Operator constant
|
|
20
|
+
OPERATOR = "+"
|
|
21
|
+
|
|
22
|
+
# Error messages
|
|
23
|
+
ERROR_INVALID_STATIC_CAPTURE = "Invalid static capture notation: %s"
|
|
24
|
+
ERROR_INVALID_SQUARE = "Invalid square coordinate: %s"
|
|
25
|
+
|
|
26
|
+
# @return [String] destination CELL coordinate (square where piece is captured)
|
|
27
|
+
attr_reader :destination
|
|
28
|
+
|
|
29
|
+
# Check if a string represents a valid static capture action
|
|
30
|
+
#
|
|
31
|
+
# @param pan_string [String] the string to validate
|
|
32
|
+
# @return [Boolean] true if valid static capture notation
|
|
33
|
+
#
|
|
34
|
+
# @example
|
|
35
|
+
# StaticCapture.valid?("+d4") # => true
|
|
36
|
+
# StaticCapture.valid?("+e5") # => true
|
|
37
|
+
# StaticCapture.valid?("d4") # => false
|
|
38
|
+
def self.valid?(pan_string)
|
|
39
|
+
return false unless pan_string.is_a?(::String)
|
|
40
|
+
return false unless pan_string.start_with?(OPERATOR)
|
|
41
|
+
return false if pan_string.length < 2
|
|
42
|
+
|
|
43
|
+
square = pan_string[1..]
|
|
44
|
+
::Sashite::Cell.valid?(square)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Parse a static capture notation string into a StaticCapture instance
|
|
48
|
+
#
|
|
49
|
+
# @param pan_string [String] static capture notation string
|
|
50
|
+
# @return [StaticCapture] static capture action instance
|
|
51
|
+
# @raise [ArgumentError] if the string is not valid static capture notation
|
|
52
|
+
#
|
|
53
|
+
# @example
|
|
54
|
+
# StaticCapture.parse("+d4") # => #<StaticCapture destination="d4">
|
|
55
|
+
def self.parse(pan_string)
|
|
56
|
+
raise ::ArgumentError, format(ERROR_INVALID_STATIC_CAPTURE, pan_string) unless valid?(pan_string)
|
|
57
|
+
|
|
58
|
+
square = pan_string[1..]
|
|
59
|
+
new(square)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Create a new static capture action instance
|
|
63
|
+
#
|
|
64
|
+
# @param square [String] CELL coordinate of piece to capture
|
|
65
|
+
# @raise [ArgumentError] if coordinate is invalid
|
|
66
|
+
#
|
|
67
|
+
# @example
|
|
68
|
+
# StaticCapture.new("d4") # => #<StaticCapture ...>
|
|
69
|
+
def initialize(square)
|
|
70
|
+
raise ::ArgumentError, format(ERROR_INVALID_SQUARE, square) unless ::Sashite::Cell.valid?(square)
|
|
71
|
+
|
|
72
|
+
@destination = square
|
|
73
|
+
|
|
74
|
+
freeze
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Get the action type
|
|
78
|
+
#
|
|
79
|
+
# @return [Symbol] :static_capture
|
|
80
|
+
def type
|
|
81
|
+
TYPE
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Get the source coordinate
|
|
85
|
+
#
|
|
86
|
+
# @return [nil] static capture actions have no source
|
|
87
|
+
def source
|
|
88
|
+
nil
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Get the piece identifier
|
|
92
|
+
#
|
|
93
|
+
# @return [nil] static capture actions have no piece identifier
|
|
94
|
+
def piece
|
|
95
|
+
nil
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Get the transformation piece
|
|
99
|
+
#
|
|
100
|
+
# @return [nil] static capture actions have no transformation
|
|
101
|
+
def transformation
|
|
102
|
+
nil
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Convert the action to its PAN string representation
|
|
106
|
+
#
|
|
107
|
+
# @return [String] static capture notation
|
|
108
|
+
#
|
|
109
|
+
# @example
|
|
110
|
+
# action.to_s # => "+d4"
|
|
111
|
+
def to_s
|
|
112
|
+
"#{OPERATOR}#{destination}"
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Check if this is a pass action
|
|
116
|
+
#
|
|
117
|
+
# @return [Boolean] false
|
|
118
|
+
def pass?
|
|
119
|
+
false
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Check if this is a move action
|
|
123
|
+
#
|
|
124
|
+
# @return [Boolean] false
|
|
125
|
+
def move?
|
|
126
|
+
false
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Check if this is a capture action
|
|
130
|
+
#
|
|
131
|
+
# @return [Boolean] false
|
|
132
|
+
def capture?
|
|
133
|
+
false
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Check if this is a special action
|
|
137
|
+
#
|
|
138
|
+
# @return [Boolean] false
|
|
139
|
+
def special?
|
|
140
|
+
false
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Check if this is a static capture action
|
|
144
|
+
#
|
|
145
|
+
# @return [Boolean] true
|
|
146
|
+
def static_capture?
|
|
147
|
+
true
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# Check if this is a drop action
|
|
151
|
+
#
|
|
152
|
+
# @return [Boolean] false
|
|
153
|
+
def drop?
|
|
154
|
+
false
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# Check if this is a drop capture action
|
|
158
|
+
#
|
|
159
|
+
# @return [Boolean] false
|
|
160
|
+
def drop_capture?
|
|
161
|
+
false
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# Check if this is a modify action
|
|
165
|
+
#
|
|
166
|
+
# @return [Boolean] false
|
|
167
|
+
def modify?
|
|
168
|
+
false
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# Check if this is a movement action
|
|
172
|
+
#
|
|
173
|
+
# @return [Boolean] false
|
|
174
|
+
def movement?
|
|
175
|
+
false
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# Check if this is a drop action (drop or drop_capture)
|
|
179
|
+
#
|
|
180
|
+
# @return [Boolean] false
|
|
181
|
+
def drop_action?
|
|
182
|
+
false
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# Custom equality comparison
|
|
186
|
+
#
|
|
187
|
+
# @param other [Object] object to compare with
|
|
188
|
+
# @return [Boolean] true if actions are equal
|
|
189
|
+
def ==(other)
|
|
190
|
+
return false unless other.is_a?(self.class)
|
|
191
|
+
|
|
192
|
+
destination == other.destination
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# Alias for == to ensure Set functionality works correctly
|
|
196
|
+
alias eql? ==
|
|
197
|
+
|
|
198
|
+
# Custom hash implementation for use in collections
|
|
199
|
+
#
|
|
200
|
+
# @return [Integer] hash value
|
|
201
|
+
def hash
|
|
202
|
+
[self.class, destination].hash
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
end
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "action/pass"
|
|
4
|
+
require_relative "action/move"
|
|
5
|
+
require_relative "action/capture"
|
|
6
|
+
require_relative "action/special"
|
|
7
|
+
require_relative "action/static_capture"
|
|
8
|
+
require_relative "action/drop"
|
|
9
|
+
require_relative "action/drop_capture"
|
|
10
|
+
require_relative "action/modify"
|
|
11
|
+
|
|
12
|
+
module Sashite
|
|
13
|
+
module Pan
|
|
14
|
+
# Action module
|
|
15
|
+
#
|
|
16
|
+
# Orchestrates all action types in PAN (Portable Action Notation) format.
|
|
17
|
+
# Each action type is implemented as a separate, autonomous class.
|
|
18
|
+
#
|
|
19
|
+
# This module provides a unified interface for validation, parsing,
|
|
20
|
+
# and factory methods that delegate to the appropriate action class.
|
|
21
|
+
module Action
|
|
22
|
+
# Error messages
|
|
23
|
+
ERROR_INVALID_PAN = "Invalid PAN string: %s"
|
|
24
|
+
|
|
25
|
+
# Check if a string represents a valid PAN action
|
|
26
|
+
#
|
|
27
|
+
# @param pan_string [String] the string to validate
|
|
28
|
+
# @return [Boolean] true if the string is a valid PAN action
|
|
29
|
+
#
|
|
30
|
+
# @example
|
|
31
|
+
# Action.valid?("e2-e4") # => true
|
|
32
|
+
# Action.valid?("P*e5") # => true
|
|
33
|
+
# Action.valid?("...") # => true
|
|
34
|
+
# Action.valid?("invalid") # => false
|
|
35
|
+
def self.valid?(pan_string)
|
|
36
|
+
return false unless pan_string.is_a?(::String)
|
|
37
|
+
return false if pan_string.empty?
|
|
38
|
+
|
|
39
|
+
# Try each action type's validation
|
|
40
|
+
Pass.valid?(pan_string) ||
|
|
41
|
+
Move.valid?(pan_string) ||
|
|
42
|
+
Capture.valid?(pan_string) ||
|
|
43
|
+
Special.valid?(pan_string) ||
|
|
44
|
+
StaticCapture.valid?(pan_string) ||
|
|
45
|
+
Drop.valid?(pan_string) ||
|
|
46
|
+
DropCapture.valid?(pan_string) ||
|
|
47
|
+
Modify.valid?(pan_string)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Parse a PAN string into an action object
|
|
51
|
+
#
|
|
52
|
+
# @param pan_string [String] PAN notation string
|
|
53
|
+
# @return [Pass, Move, Capture, Special, StaticCapture, Drop, DropCapture, Modify] immutable action instance
|
|
54
|
+
# @raise [ArgumentError] if the PAN string is invalid
|
|
55
|
+
#
|
|
56
|
+
# @example
|
|
57
|
+
# Action.parse("e2-e4") # => #<Move ...>
|
|
58
|
+
# Action.parse("d1+f3") # => #<Capture ...>
|
|
59
|
+
# Action.parse("...") # => #<Pass ...>
|
|
60
|
+
def self.parse(pan_string)
|
|
61
|
+
string_value = String(pan_string)
|
|
62
|
+
|
|
63
|
+
# Try each action type's parser in order of specificity
|
|
64
|
+
return Pass.parse(string_value) if Pass.valid?(string_value)
|
|
65
|
+
return Move.parse(string_value) if Move.valid?(string_value)
|
|
66
|
+
return Capture.parse(string_value) if Capture.valid?(string_value)
|
|
67
|
+
return Special.parse(string_value) if Special.valid?(string_value)
|
|
68
|
+
return StaticCapture.parse(string_value) if StaticCapture.valid?(string_value)
|
|
69
|
+
return Drop.parse(string_value) if Drop.valid?(string_value)
|
|
70
|
+
return DropCapture.parse(string_value) if DropCapture.valid?(string_value)
|
|
71
|
+
return Modify.parse(string_value) if Modify.valid?(string_value)
|
|
72
|
+
|
|
73
|
+
raise ::ArgumentError, format(ERROR_INVALID_PAN, string_value)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Create a pass action
|
|
77
|
+
#
|
|
78
|
+
# @return [Pass] pass action instance
|
|
79
|
+
#
|
|
80
|
+
# @example
|
|
81
|
+
# Action.pass # => #<Pass>
|
|
82
|
+
def self.pass
|
|
83
|
+
Pass.instance
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Create a move action to an empty square
|
|
87
|
+
#
|
|
88
|
+
# @param source [String] source CELL coordinate
|
|
89
|
+
# @param destination [String] destination CELL coordinate
|
|
90
|
+
# @param transformation [String, nil] optional EPIN transformation
|
|
91
|
+
# @return [Move] move action instance
|
|
92
|
+
#
|
|
93
|
+
# @example
|
|
94
|
+
# Action.move("e2", "e4") # => #<Move ...>
|
|
95
|
+
# Action.move("e7", "e8", transformation: "Q") # => #<Move ...>
|
|
96
|
+
def self.move(source, destination, transformation: nil)
|
|
97
|
+
Move.new(source, destination, transformation: transformation)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Create a capture action at destination
|
|
101
|
+
#
|
|
102
|
+
# @param source [String] source CELL coordinate
|
|
103
|
+
# @param destination [String] destination CELL coordinate
|
|
104
|
+
# @param transformation [String, nil] optional EPIN transformation
|
|
105
|
+
# @return [Capture] capture action instance
|
|
106
|
+
#
|
|
107
|
+
# @example
|
|
108
|
+
# Action.capture("d1", "f3") # => #<Capture ...>
|
|
109
|
+
# Action.capture("b7", "a8", transformation: "R") # => #<Capture ...>
|
|
110
|
+
def self.capture(source, destination, transformation: nil)
|
|
111
|
+
Capture.new(source, destination, transformation: transformation)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Create a special move action with implicit side effects
|
|
115
|
+
#
|
|
116
|
+
# @param source [String] source CELL coordinate
|
|
117
|
+
# @param destination [String] destination CELL coordinate
|
|
118
|
+
# @param transformation [String, nil] optional EPIN transformation
|
|
119
|
+
# @return [Special] special action instance
|
|
120
|
+
#
|
|
121
|
+
# @example
|
|
122
|
+
# Action.special("e1", "g1") # => #<Special ...>
|
|
123
|
+
def self.special(source, destination, transformation: nil)
|
|
124
|
+
Special.new(source, destination, transformation: transformation)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Create a static capture action (remove piece without movement)
|
|
128
|
+
#
|
|
129
|
+
# @param square [String] CELL coordinate of piece to capture
|
|
130
|
+
# @return [StaticCapture] static capture action instance
|
|
131
|
+
#
|
|
132
|
+
# @example
|
|
133
|
+
# Action.static_capture("d4") # => #<StaticCapture ...>
|
|
134
|
+
def self.static_capture(square)
|
|
135
|
+
StaticCapture.new(square)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Create a drop action to empty square
|
|
139
|
+
#
|
|
140
|
+
# @param destination [String] destination CELL coordinate
|
|
141
|
+
# @param piece [String, nil] optional EPIN piece identifier
|
|
142
|
+
# @param transformation [String, nil] optional EPIN transformation
|
|
143
|
+
# @return [Drop] drop action instance
|
|
144
|
+
#
|
|
145
|
+
# @example
|
|
146
|
+
# Action.drop("e5", piece: "P") # => #<Drop ...>
|
|
147
|
+
# Action.drop("d4") # => #<Drop ...>
|
|
148
|
+
# Action.drop("c3", piece: "S", transformation: "+S") # => #<Drop ...>
|
|
149
|
+
def self.drop(destination, piece: nil, transformation: nil)
|
|
150
|
+
Drop.new(destination, piece: piece, transformation: transformation)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Create a drop action with capture
|
|
154
|
+
#
|
|
155
|
+
# @param destination [String] destination CELL coordinate
|
|
156
|
+
# @param piece [String, nil] optional EPIN piece identifier
|
|
157
|
+
# @param transformation [String, nil] optional EPIN transformation
|
|
158
|
+
# @return [DropCapture] drop capture action instance
|
|
159
|
+
#
|
|
160
|
+
# @example
|
|
161
|
+
# Action.drop_capture("b4", piece: "L") # => #<DropCapture ...>
|
|
162
|
+
def self.drop_capture(destination, piece: nil, transformation: nil)
|
|
163
|
+
DropCapture.new(destination, piece: piece, transformation: transformation)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Create an in-place transformation action
|
|
167
|
+
#
|
|
168
|
+
# @param square [String] CELL coordinate
|
|
169
|
+
# @param piece [String] EPIN piece identifier (final state)
|
|
170
|
+
# @return [Modify] modification action instance
|
|
171
|
+
#
|
|
172
|
+
# @example
|
|
173
|
+
# Action.modify("e4", "+P") # => #<Modify ...>
|
|
174
|
+
# Action.modify("c3", "k'") # => #<Modify ...>
|
|
175
|
+
def self.modify(square, piece)
|
|
176
|
+
Modify.new(square, piece)
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
end
|
data/lib/sashite/pan.rb
CHANGED
|
@@ -1,61 +1,44 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "pan/
|
|
4
|
-
require_relative "pan/parser"
|
|
3
|
+
require_relative "pan/action"
|
|
5
4
|
|
|
6
5
|
module Sashite
|
|
7
|
-
#
|
|
6
|
+
# PAN (Portable Action Notation) implementation for Ruby
|
|
7
|
+
#
|
|
8
|
+
# Provides functionality for working with atomic actions in abstract strategy board games
|
|
9
|
+
# using a human-readable string format with intuitive operator-based syntax.
|
|
10
|
+
#
|
|
11
|
+
# This implementation is strictly compliant with PAN Specification v1.0.0
|
|
12
|
+
# @see https://sashite.dev/specs/pan/1.0.0/ PAN Specification v1.0.0
|
|
8
13
|
module Pan
|
|
9
|
-
#
|
|
10
|
-
module_function
|
|
11
|
-
|
|
12
|
-
# Parse a PAN string into PMN format
|
|
13
|
-
#
|
|
14
|
-
# @param pan_string [String] The PAN string to parse
|
|
15
|
-
# @return [Array<Hash>] Array of PMN action objects
|
|
16
|
-
# @raise [Parser::Error] If the PAN string is invalid
|
|
17
|
-
def parse(pan_string)
|
|
18
|
-
Parser.call(pan_string)
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
# Convert PMN actions to PAN string
|
|
14
|
+
# Check if a string represents a valid PAN action
|
|
22
15
|
#
|
|
23
|
-
# @param
|
|
24
|
-
# @return [
|
|
25
|
-
# @raise [Dumper::Error] If the PMN data is invalid
|
|
26
|
-
def dump(pmn_actions)
|
|
27
|
-
Dumper.call(pmn_actions)
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
# Validate a PAN string without raising exceptions
|
|
16
|
+
# @param pan_string [String] the string to validate
|
|
17
|
+
# @return [Boolean] true if the string is a valid PAN action
|
|
31
18
|
#
|
|
32
|
-
# @
|
|
33
|
-
#
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
true
|
|
37
|
-
|
|
38
|
-
|
|
19
|
+
# @example
|
|
20
|
+
# Sashite::Pan.valid?("e2-e4") # => true
|
|
21
|
+
# Sashite::Pan.valid?("d1+f3") # => true
|
|
22
|
+
# Sashite::Pan.valid?("...") # => true
|
|
23
|
+
# Sashite::Pan.valid?("P*e5") # => true
|
|
24
|
+
# Sashite::Pan.valid?("invalid") # => false
|
|
25
|
+
def self.valid?(pan_string)
|
|
26
|
+
Action.valid?(pan_string)
|
|
39
27
|
end
|
|
40
28
|
|
|
41
|
-
# Parse a PAN string
|
|
29
|
+
# Parse a PAN string into an Action object
|
|
42
30
|
#
|
|
43
|
-
# @param pan_string [String]
|
|
44
|
-
# @return [
|
|
45
|
-
|
|
46
|
-
parse(pan_string)
|
|
47
|
-
rescue Parser::Error
|
|
48
|
-
nil
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
# Convert PMN actions to PAN string without raising exceptions
|
|
31
|
+
# @param pan_string [String] PAN notation string
|
|
32
|
+
# @return [Pan::Action] immutable action instance
|
|
33
|
+
# @raise [ArgumentError] if the PAN string is invalid
|
|
52
34
|
#
|
|
53
|
-
# @
|
|
54
|
-
#
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
35
|
+
# @example
|
|
36
|
+
# Sashite::Pan.parse("e2-e4") # => #<Pan::Action type=:move ...>
|
|
37
|
+
# Sashite::Pan.parse("d1+f3") # => #<Pan::Action type=:capture ...>
|
|
38
|
+
# Sashite::Pan.parse("...") # => #<Pan::Action type=:pass>
|
|
39
|
+
# Sashite::Pan.parse("P*e5") # => #<Pan::Action type=:drop ...>
|
|
40
|
+
def self.parse(pan_string)
|
|
41
|
+
Action.parse(pan_string)
|
|
59
42
|
end
|
|
60
43
|
end
|
|
61
44
|
end
|
data/lib/sashite-pan.rb
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
require_relative "sashite/pan"
|
|
4
|
+
|
|
5
|
+
# Sashité namespace for board game notation libraries
|
|
6
|
+
#
|
|
7
|
+
# Sashité provides a collection of libraries for representing and manipulating
|
|
8
|
+
# board game concepts according to the Sashité Protocol specifications.
|
|
9
|
+
#
|
|
10
|
+
# @see https://sashite.dev/protocol/ Sashité Protocol
|
|
11
|
+
# @see https://sashite.dev/specs/ Sashité Specifications
|
|
12
|
+
# @author Sashité
|
|
4
13
|
module Sashite
|
|
5
14
|
end
|
|
6
|
-
|
|
7
|
-
require_relative "sashite/pan"
|
metadata
CHANGED
|
@@ -1,15 +1,46 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: sashite-pan
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 4.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Cyril Kato
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
-
dependencies:
|
|
12
|
-
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: sashite-cell
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '2.0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '2.0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: sashite-epin
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '1.1'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '1.1'
|
|
40
|
+
description: |
|
|
41
|
+
Parse and generate Portable Action Notation (PAN) strings for representing atomic actions in abstract strategy board games including chess, shogi, xiangqi, and others. PAN provides an intuitive operator-based syntax with six core operators: "-" (move to empty square), "+" (capture), "~" (special moves with side effects), "*" (drop to board), "." (drop with capture), and "=" (in-place transformation), plus "..." (pass turn).
|
|
42
|
+
Supports coordinates via CELL specification and piece identifiers via EPIN specification. Handles transformations ("e7-e8=Q"), enhanced/diminished states ("+R", "-P"), and style derivation markers ("K'"). Provides comprehensive validation, immutable action objects, and functional API design.
|
|
43
|
+
Examples: "e2-e4" (move), "d1+f3" (capture), "e1~g1" (castling), "P*e5" (drop), "e7-e8=Q" (promotion), "..." (pass), "+d4" (static capture), "e4=+P" (modify).
|
|
13
44
|
email: contact@cyril.email
|
|
14
45
|
executables: []
|
|
15
46
|
extensions: []
|
|
@@ -19,10 +50,15 @@ files:
|
|
|
19
50
|
- README.md
|
|
20
51
|
- lib/sashite-pan.rb
|
|
21
52
|
- lib/sashite/pan.rb
|
|
22
|
-
- lib/sashite/pan/
|
|
23
|
-
- lib/sashite/pan/
|
|
24
|
-
- lib/sashite/pan/
|
|
25
|
-
- lib/sashite/pan/
|
|
53
|
+
- lib/sashite/pan/action.rb
|
|
54
|
+
- lib/sashite/pan/action/capture.rb
|
|
55
|
+
- lib/sashite/pan/action/drop.rb
|
|
56
|
+
- lib/sashite/pan/action/drop_capture.rb
|
|
57
|
+
- lib/sashite/pan/action/modify.rb
|
|
58
|
+
- lib/sashite/pan/action/move.rb
|
|
59
|
+
- lib/sashite/pan/action/pass.rb
|
|
60
|
+
- lib/sashite/pan/action/special.rb
|
|
61
|
+
- lib/sashite/pan/action/static_capture.rb
|
|
26
62
|
homepage: https://github.com/sashite/pan.rb
|
|
27
63
|
licenses:
|
|
28
64
|
- MIT
|
|
@@ -31,7 +67,7 @@ metadata:
|
|
|
31
67
|
documentation_uri: https://rubydoc.info/github/sashite/pan.rb/main
|
|
32
68
|
homepage_uri: https://github.com/sashite/pan.rb
|
|
33
69
|
source_code_uri: https://github.com/sashite/pan.rb
|
|
34
|
-
specification_uri: https://sashite.dev/
|
|
70
|
+
specification_uri: https://sashite.dev/specs/pan/1.0.0/
|
|
35
71
|
rubygems_mfa_required: 'true'
|
|
36
72
|
rdoc_options: []
|
|
37
73
|
require_paths:
|
|
@@ -47,7 +83,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
47
83
|
- !ruby/object:Gem::Version
|
|
48
84
|
version: '0'
|
|
49
85
|
requirements: []
|
|
50
|
-
rubygems_version: 3.
|
|
86
|
+
rubygems_version: 3.7.1
|
|
51
87
|
specification_version: 4
|
|
52
|
-
summary: Portable Action Notation (PAN)
|
|
88
|
+
summary: Portable Action Notation (PAN) - operator-based notation for abstract strategy
|
|
89
|
+
game actions
|
|
53
90
|
test_files: []
|
data/lib/sashite/pan/dumper.rb
DELETED
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative "dumper/error"
|
|
4
|
-
|
|
5
|
-
module Sashite
|
|
6
|
-
module Pan
|
|
7
|
-
# Dumper for converting PMN format to PAN strings
|
|
8
|
-
module Dumper
|
|
9
|
-
# Convert PMN actions to PAN string
|
|
10
|
-
#
|
|
11
|
-
# @param pmn_actions [Array<Hash>] Array of PMN action objects
|
|
12
|
-
# @return [String] PAN string representation
|
|
13
|
-
# @raise [Dumper::Error] If the PMN data is invalid
|
|
14
|
-
def self.call(pmn_actions)
|
|
15
|
-
raise Dumper::Error, "PMN actions cannot be nil" if pmn_actions.nil?
|
|
16
|
-
raise Dumper::Error, "PMN actions cannot be empty" if pmn_actions.empty?
|
|
17
|
-
raise Dumper::Error, "PMN actions must be an array" unless pmn_actions.is_a?(::Array)
|
|
18
|
-
|
|
19
|
-
pmn_actions.map { |action| dump_action(action) }.join(";")
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
private
|
|
23
|
-
|
|
24
|
-
# Convert a single PMN action to PAN format
|
|
25
|
-
#
|
|
26
|
-
# @param action [Hash] PMN action object
|
|
27
|
-
# @return [String] PAN action string
|
|
28
|
-
# @raise [Dumper::Error] If the action is invalid
|
|
29
|
-
def self.dump_action(action)
|
|
30
|
-
validate_pmn_action(action)
|
|
31
|
-
|
|
32
|
-
components = [
|
|
33
|
-
dump_source_square(action["src_square"]),
|
|
34
|
-
action["dst_square"],
|
|
35
|
-
action["piece_name"]
|
|
36
|
-
]
|
|
37
|
-
|
|
38
|
-
components << action["piece_hand"] if action["piece_hand"]
|
|
39
|
-
|
|
40
|
-
components.join(",")
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
# Validate PMN action structure
|
|
44
|
-
#
|
|
45
|
-
# @param action [Hash] PMN action to validate
|
|
46
|
-
# @raise [Dumper::Error] If action is invalid
|
|
47
|
-
def self.validate_pmn_action(action)
|
|
48
|
-
raise Dumper::Error, "Action must be a Hash" unless action.is_a?(::Hash)
|
|
49
|
-
raise Dumper::Error, "Action must have dst_square" unless action.key?("dst_square")
|
|
50
|
-
raise Dumper::Error, "Action must have piece_name" unless action.key?("piece_name")
|
|
51
|
-
|
|
52
|
-
raise Dumper::Error, "dst_square cannot be nil or empty" if action["dst_square"].nil? || action["dst_square"].empty?
|
|
53
|
-
raise Dumper::Error, "piece_name cannot be nil or empty" if action["piece_name"].nil? || action["piece_name"].empty?
|
|
54
|
-
|
|
55
|
-
validate_piece_identifier(action["piece_name"])
|
|
56
|
-
validate_piece_identifier(action["piece_hand"]) if action["piece_hand"]
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
# Convert source square, handling drops
|
|
60
|
-
#
|
|
61
|
-
# @param src_square [String, nil] Source square or nil for drop
|
|
62
|
-
# @return [String] "*" for drops, otherwise the square identifier
|
|
63
|
-
def self.dump_source_square(src_square)
|
|
64
|
-
src_square.nil? ? "*" : src_square
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
# Validate piece identifier follows PNN specification
|
|
68
|
-
#
|
|
69
|
-
# @param piece [String] Piece identifier to validate
|
|
70
|
-
# @raise [Dumper::Error] If piece identifier is invalid
|
|
71
|
-
def self.validate_piece_identifier(piece)
|
|
72
|
-
return if piece.nil?
|
|
73
|
-
|
|
74
|
-
# PNN pattern: optional prefix (+/-), letter (a-z/A-Z), optional suffix (')
|
|
75
|
-
unless piece.match?(/\A[-+]?[a-zA-Z][']?\z/)
|
|
76
|
-
raise Dumper::Error, "Invalid piece identifier: #{piece}"
|
|
77
|
-
end
|
|
78
|
-
end
|
|
79
|
-
end
|
|
80
|
-
end
|
|
81
|
-
end
|