sashite-pan 3.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 +506 -157
- 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 +28 -149
- data/lib/sashite-pan.rb +9 -10
- metadata +45 -12
- data/lib/sashite/pan/dumper.rb +0 -121
- data/lib/sashite/pan/parser.rb +0 -104
|
@@ -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,165 +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 structured move data
|
|
13
|
-
#
|
|
14
|
-
# @param pan_string [String] The PAN string to parse
|
|
15
|
-
# @return [Hash] Structured move data with type, source, and destination
|
|
16
|
-
# @raise [Parser::Error] If the PAN string is invalid
|
|
17
|
-
# @example
|
|
18
|
-
# Sashite::Pan.parse("e2-e4")
|
|
19
|
-
# # => {type: :move, source: "e2", destination: "e4"}
|
|
14
|
+
# Check if a string represents a valid PAN action
|
|
20
15
|
#
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
# Sashite::Pan.parse("*e4")
|
|
25
|
-
# # => {type: :drop, destination: "e4"}
|
|
26
|
-
def parse(pan_string)
|
|
27
|
-
Parser.call(pan_string)
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
# Convert structured move data to PAN string
|
|
16
|
+
# @param pan_string [String] the string to validate
|
|
17
|
+
# @return [Boolean] true if the string is a valid PAN action
|
|
31
18
|
#
|
|
32
|
-
# @param move_data [Hash] Structured move data with type, source, and destination
|
|
33
|
-
# @return [String] PAN string representation
|
|
34
|
-
# @raise [Dumper::Error] If the move data is invalid
|
|
35
19
|
# @example
|
|
36
|
-
# Sashite::Pan.
|
|
37
|
-
# # =>
|
|
38
|
-
#
|
|
39
|
-
# Sashite::Pan.
|
|
40
|
-
# # =>
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
# # => "*e4"
|
|
44
|
-
def dump(move_data)
|
|
45
|
-
Dumper.call(move_data)
|
|
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)
|
|
46
27
|
end
|
|
47
28
|
|
|
48
|
-
#
|
|
29
|
+
# Parse a PAN string into an Action object
|
|
49
30
|
#
|
|
50
|
-
# @param pan_string [String]
|
|
51
|
-
# @return [
|
|
52
|
-
# @
|
|
53
|
-
# Sashite::Pan.valid?("e2-e4") # => true
|
|
54
|
-
# Sashite::Pan.valid?("*e4") # => true
|
|
55
|
-
# Sashite::Pan.valid?("e4xd5") # => true
|
|
56
|
-
# Sashite::Pan.valid?("") # => false
|
|
57
|
-
# Sashite::Pan.valid?("e2-e2") # => false
|
|
58
|
-
# Sashite::Pan.valid?("E2-e4") # => false
|
|
59
|
-
def valid?(pan_string)
|
|
60
|
-
parse(pan_string)
|
|
61
|
-
true
|
|
62
|
-
rescue Parser::Error
|
|
63
|
-
false
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
# Parse a 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
|
|
67
34
|
#
|
|
68
|
-
# @param pan_string [String] The PAN string to parse
|
|
69
|
-
# @return [Hash, nil] Structured move data or nil if invalid
|
|
70
35
|
# @example
|
|
71
|
-
# Sashite::Pan.
|
|
72
|
-
# # =>
|
|
73
|
-
#
|
|
74
|
-
# Sashite::Pan.
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
parse(pan_string)
|
|
78
|
-
rescue Parser::Error
|
|
79
|
-
nil
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
# Convert structured move data to PAN string without raising exceptions
|
|
83
|
-
#
|
|
84
|
-
# @param move_data [Hash] Structured move data with type, source, and destination
|
|
85
|
-
# @return [String, nil] PAN string or nil if invalid
|
|
86
|
-
# @example
|
|
87
|
-
# Sashite::Pan.safe_dump({type: :move, source: "e2", destination: "e4"})
|
|
88
|
-
# # => "e2-e4"
|
|
89
|
-
#
|
|
90
|
-
# Sashite::Pan.safe_dump({invalid: :data})
|
|
91
|
-
# # => nil
|
|
92
|
-
def safe_dump(move_data)
|
|
93
|
-
dump(move_data)
|
|
94
|
-
rescue Dumper::Error
|
|
95
|
-
nil
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
# Check if a coordinate is valid according to PAN specification
|
|
99
|
-
#
|
|
100
|
-
# @param coordinate [String] The coordinate to validate
|
|
101
|
-
# @return [Boolean] True if valid, false otherwise
|
|
102
|
-
# @example
|
|
103
|
-
# Sashite::Pan.valid_coordinate?("e4") # => true
|
|
104
|
-
# Sashite::Pan.valid_coordinate?("a1") # => true
|
|
105
|
-
# Sashite::Pan.valid_coordinate?("E4") # => false (uppercase)
|
|
106
|
-
# Sashite::Pan.valid_coordinate?("e10") # => false (multi-digit rank)
|
|
107
|
-
def valid_coordinate?(coordinate)
|
|
108
|
-
return false unless coordinate.is_a?(::String)
|
|
109
|
-
coordinate.match?(/\A[a-z][0-9]\z/)
|
|
110
|
-
end
|
|
111
|
-
|
|
112
|
-
# Get the regular expression pattern used for PAN validation
|
|
113
|
-
#
|
|
114
|
-
# @return [Regexp] The regex pattern for PAN strings
|
|
115
|
-
# @example
|
|
116
|
-
# pattern = Sashite::Pan.pattern
|
|
117
|
-
# pattern.match?("e2-e4") # => true
|
|
118
|
-
def pattern
|
|
119
|
-
Parser::PAN_PATTERN
|
|
120
|
-
end
|
|
121
|
-
|
|
122
|
-
# Convert a PAN string to a human-readable description
|
|
123
|
-
#
|
|
124
|
-
# @param pan_string [String] The PAN string to describe
|
|
125
|
-
# @return [String] Human-readable description
|
|
126
|
-
# @raise [Parser::Error] If the PAN string is invalid
|
|
127
|
-
# @example
|
|
128
|
-
# Sashite::Pan.describe("e2-e4")
|
|
129
|
-
# # => "Move from e2 to e4"
|
|
130
|
-
#
|
|
131
|
-
# Sashite::Pan.describe("e4xd5")
|
|
132
|
-
# # => "Capture from e4 to d5"
|
|
133
|
-
#
|
|
134
|
-
# Sashite::Pan.describe("*e4")
|
|
135
|
-
# # => "Drop to e4"
|
|
136
|
-
def describe(pan_string)
|
|
137
|
-
move_data = parse(pan_string)
|
|
138
|
-
|
|
139
|
-
case move_data[:type]
|
|
140
|
-
when :move
|
|
141
|
-
"Move from #{move_data[:source]} to #{move_data[:destination]}"
|
|
142
|
-
when :capture
|
|
143
|
-
"Capture from #{move_data[:source]} to #{move_data[:destination]}"
|
|
144
|
-
when :drop
|
|
145
|
-
"Drop to #{move_data[:destination]}"
|
|
146
|
-
end
|
|
147
|
-
end
|
|
148
|
-
|
|
149
|
-
# Convert a PAN string to a human-readable description without raising exceptions
|
|
150
|
-
#
|
|
151
|
-
# @param pan_string [String] The PAN string to describe
|
|
152
|
-
# @return [String, nil] Human-readable description or nil if invalid
|
|
153
|
-
# @example
|
|
154
|
-
# Sashite::Pan.safe_describe("e2-e4")
|
|
155
|
-
# # => "Move from e2 to e4"
|
|
156
|
-
#
|
|
157
|
-
# Sashite::Pan.safe_describe("invalid")
|
|
158
|
-
# # => nil
|
|
159
|
-
def safe_describe(pan_string)
|
|
160
|
-
describe(pan_string)
|
|
161
|
-
rescue Parser::Error
|
|
162
|
-
nil
|
|
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)
|
|
163
42
|
end
|
|
164
43
|
end
|
|
165
44
|
end
|
data/lib/sashite-pan.rb
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "sashite/pan"
|
|
4
|
+
|
|
3
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
|
-
# Portable Action Notation (PAN) implementation for Ruby
|
|
6
|
-
#
|
|
7
|
-
# PAN is a compact, string-based format for representing executed moves
|
|
8
|
-
# in abstract strategy board games played on coordinate-based boards.
|
|
9
|
-
#
|
|
10
|
-
# @see https://sashite.dev/documents/pan/1.0.0/ PAN Specification v1.0.0
|
|
11
|
-
# @author Sashité
|
|
12
|
-
# @since 1.0.0
|
|
13
14
|
end
|
|
14
|
-
|
|
15
|
-
require_relative "sashite/pan"
|