sashite-pcn 0.1.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 +7 -0
- data/LICENSE.md +21 -0
- data/README.md +509 -0
- data/lib/sashite/pcn/error.rb +38 -0
- data/lib/sashite/pcn/game.rb +436 -0
- data/lib/sashite/pcn/meta.rb +275 -0
- data/lib/sashite/pcn/player.rb +186 -0
- data/lib/sashite/pcn/sides.rb +194 -0
- data/lib/sashite/pcn.rb +68 -0
- data/lib/sashite-pcn.rb +14 -0
- metadata +107 -0
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Sashite
|
|
4
|
+
module Pcn
|
|
5
|
+
# Immutable representation of player information.
|
|
6
|
+
#
|
|
7
|
+
# All fields are optional. Player provides identification and
|
|
8
|
+
# rating information for game participants.
|
|
9
|
+
#
|
|
10
|
+
# @see https://sashite.dev/specs/pcn/1.0.0/
|
|
11
|
+
class Player
|
|
12
|
+
# @return [String, nil] Style name in SNN format
|
|
13
|
+
attr_reader :style
|
|
14
|
+
|
|
15
|
+
# @return [String, nil] Player name or identifier
|
|
16
|
+
attr_reader :name
|
|
17
|
+
|
|
18
|
+
# @return [Integer, nil] Elo rating
|
|
19
|
+
attr_reader :elo
|
|
20
|
+
|
|
21
|
+
# Parse a player hash into a Player object.
|
|
22
|
+
#
|
|
23
|
+
# @param hash [Hash] Player hash
|
|
24
|
+
# @return [Player] Immutable player object
|
|
25
|
+
# @raise [Error::Validation] If validation fails
|
|
26
|
+
#
|
|
27
|
+
# @example
|
|
28
|
+
# player = Player.parse({
|
|
29
|
+
# "name" => "Magnus Carlsen",
|
|
30
|
+
# "elo" => 2830,
|
|
31
|
+
# "style" => "CHESS"
|
|
32
|
+
# })
|
|
33
|
+
def self.parse(hash)
|
|
34
|
+
raise Error::Validation, "Player must be a Hash, got #{hash.class}" unless hash.is_a?(::Hash)
|
|
35
|
+
|
|
36
|
+
new(
|
|
37
|
+
style: hash["style"],
|
|
38
|
+
name: hash["name"],
|
|
39
|
+
elo: hash["elo"]
|
|
40
|
+
)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Validate a player hash without raising exceptions.
|
|
44
|
+
#
|
|
45
|
+
# @param hash [Hash] Player hash
|
|
46
|
+
# @return [Boolean] true if valid, false otherwise
|
|
47
|
+
#
|
|
48
|
+
# @example
|
|
49
|
+
# Player.valid?({ "name" => "Alice" }) # => true
|
|
50
|
+
def self.valid?(hash)
|
|
51
|
+
parse(hash)
|
|
52
|
+
true
|
|
53
|
+
rescue Error
|
|
54
|
+
false
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Create a new Player.
|
|
58
|
+
#
|
|
59
|
+
# @param style [String, nil] Style name (SNN format)
|
|
60
|
+
# @param name [String, nil] Player name
|
|
61
|
+
# @param elo [Integer, nil] Elo rating
|
|
62
|
+
# @raise [Error::Validation] If validation fails
|
|
63
|
+
#
|
|
64
|
+
# @example
|
|
65
|
+
# player = Player.new(
|
|
66
|
+
# name: "Magnus Carlsen",
|
|
67
|
+
# elo: 2830,
|
|
68
|
+
# style: "CHESS"
|
|
69
|
+
# )
|
|
70
|
+
def initialize(style: nil, name: nil, elo: nil)
|
|
71
|
+
@style = style
|
|
72
|
+
@name = name
|
|
73
|
+
@elo = elo
|
|
74
|
+
|
|
75
|
+
validate!
|
|
76
|
+
|
|
77
|
+
freeze
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Check if the player is valid.
|
|
81
|
+
#
|
|
82
|
+
# @return [Boolean] true if valid
|
|
83
|
+
def valid?
|
|
84
|
+
validate!
|
|
85
|
+
true
|
|
86
|
+
rescue Error
|
|
87
|
+
false
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Check if player is empty (all fields nil).
|
|
91
|
+
#
|
|
92
|
+
# @return [Boolean] true if all fields are nil
|
|
93
|
+
def empty?
|
|
94
|
+
style.nil? && name.nil? && elo.nil?
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Convert to hash representation.
|
|
98
|
+
#
|
|
99
|
+
# @return [Hash] Player hash (excludes nil values)
|
|
100
|
+
#
|
|
101
|
+
# @example
|
|
102
|
+
# player.to_h # => { "name" => "Alice", "elo" => 2800 }
|
|
103
|
+
def to_h
|
|
104
|
+
hash = {}
|
|
105
|
+
|
|
106
|
+
hash["style"] = style unless style.nil?
|
|
107
|
+
hash["name"] = name unless name.nil?
|
|
108
|
+
hash["elo"] = elo unless elo.nil?
|
|
109
|
+
|
|
110
|
+
hash
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# String representation.
|
|
114
|
+
#
|
|
115
|
+
# @return [String] Inspectable representation
|
|
116
|
+
def to_s
|
|
117
|
+
fields = []
|
|
118
|
+
fields << "name=#{name.inspect}" unless name.nil?
|
|
119
|
+
fields << "elo=#{elo}" unless elo.nil?
|
|
120
|
+
fields << "style=#{style.inspect}" unless style.nil?
|
|
121
|
+
|
|
122
|
+
"#<#{self.class} #{fields.join(' ')}>"
|
|
123
|
+
end
|
|
124
|
+
alias inspect to_s
|
|
125
|
+
|
|
126
|
+
# Equality comparison.
|
|
127
|
+
#
|
|
128
|
+
# @param other [Player] Other player
|
|
129
|
+
# @return [Boolean] true if equal
|
|
130
|
+
def ==(other)
|
|
131
|
+
other.is_a?(self.class) &&
|
|
132
|
+
other.style == style &&
|
|
133
|
+
other.name == name &&
|
|
134
|
+
other.elo == elo
|
|
135
|
+
end
|
|
136
|
+
alias eql? ==
|
|
137
|
+
|
|
138
|
+
# Hash code for equality.
|
|
139
|
+
#
|
|
140
|
+
# @return [Integer] Hash code
|
|
141
|
+
def hash
|
|
142
|
+
[self.class, style, name, elo].hash
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
private
|
|
146
|
+
|
|
147
|
+
# Validate all fields.
|
|
148
|
+
def validate!
|
|
149
|
+
validate_style!
|
|
150
|
+
validate_name!
|
|
151
|
+
validate_elo!
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# Validate style field.
|
|
155
|
+
def validate_style!
|
|
156
|
+
return if style.nil?
|
|
157
|
+
|
|
158
|
+
raise Error::Validation, "Player 'style' must be a String, got #{style.class}" unless style.is_a?(::String)
|
|
159
|
+
|
|
160
|
+
return if ::Sashite::Snn.valid?(style)
|
|
161
|
+
|
|
162
|
+
raise Error::Validation, "Player 'style' must be valid SNN format, got #{style.inspect}"
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# Validate name field.
|
|
166
|
+
def validate_name!
|
|
167
|
+
return if name.nil?
|
|
168
|
+
|
|
169
|
+
return if name.is_a?(::String)
|
|
170
|
+
|
|
171
|
+
raise Error::Validation, "Player 'name' must be a String, got #{name.class}"
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# Validate elo field.
|
|
175
|
+
def validate_elo!
|
|
176
|
+
return if elo.nil?
|
|
177
|
+
|
|
178
|
+
raise Error::Validation, "Player 'elo' must be an Integer, got #{elo.class}" unless elo.is_a?(::Integer)
|
|
179
|
+
|
|
180
|
+
return unless elo < 0
|
|
181
|
+
|
|
182
|
+
raise Error::Validation, "Player 'elo' must be >= 0, got #{elo}"
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
end
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Sashite
|
|
4
|
+
module Pcn
|
|
5
|
+
# Immutable representation of player information for both sides.
|
|
6
|
+
#
|
|
7
|
+
# Contains player information for first and second player.
|
|
8
|
+
# At least one player must be defined when sides are present.
|
|
9
|
+
#
|
|
10
|
+
# @see https://sashite.dev/specs/pcn/1.0.0/
|
|
11
|
+
class Sides
|
|
12
|
+
# @return [Player, nil] First player information
|
|
13
|
+
attr_reader :first
|
|
14
|
+
|
|
15
|
+
# @return [Player, nil] Second player information
|
|
16
|
+
attr_reader :second
|
|
17
|
+
|
|
18
|
+
# Parse a sides hash into a Sides object.
|
|
19
|
+
#
|
|
20
|
+
# @param hash [Hash] Sides hash
|
|
21
|
+
# @return [Sides] Immutable sides object
|
|
22
|
+
# @raise [Error::Validation] If validation fails
|
|
23
|
+
#
|
|
24
|
+
# @example
|
|
25
|
+
# sides = Sides.parse({
|
|
26
|
+
# "first" => { "name" => "Alice", "elo" => 2800 },
|
|
27
|
+
# "second" => { "name" => "Bob", "elo" => 2750 }
|
|
28
|
+
# })
|
|
29
|
+
def self.parse(hash)
|
|
30
|
+
raise Error::Validation, "Sides must be a Hash, got #{hash.class}" unless hash.is_a?(::Hash)
|
|
31
|
+
|
|
32
|
+
first = parse_player(hash["first"], "first")
|
|
33
|
+
second = parse_player(hash["second"], "second")
|
|
34
|
+
|
|
35
|
+
new(first:, second:)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Validate a sides hash without raising exceptions.
|
|
39
|
+
#
|
|
40
|
+
# @param hash [Hash] Sides hash
|
|
41
|
+
# @return [Boolean] true if valid, false otherwise
|
|
42
|
+
#
|
|
43
|
+
# @example
|
|
44
|
+
# Sides.valid?({ "first" => { "name" => "Alice" } }) # => true
|
|
45
|
+
def self.valid?(hash)
|
|
46
|
+
parse(hash)
|
|
47
|
+
true
|
|
48
|
+
rescue Error
|
|
49
|
+
false
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Create a new Sides.
|
|
53
|
+
#
|
|
54
|
+
# @param first [Player, Hash, nil] First player information
|
|
55
|
+
# @param second [Player, Hash, nil] Second player information
|
|
56
|
+
# @raise [Error::Validation] If validation fails
|
|
57
|
+
#
|
|
58
|
+
# @example
|
|
59
|
+
# sides = Sides.new(
|
|
60
|
+
# first: Player.new(name: "Alice", elo: 2800),
|
|
61
|
+
# second: Player.new(name: "Bob", elo: 2750)
|
|
62
|
+
# )
|
|
63
|
+
def initialize(first: nil, second: nil)
|
|
64
|
+
@first = normalize_player(first, "first")
|
|
65
|
+
@second = normalize_player(second, "second")
|
|
66
|
+
|
|
67
|
+
validate!
|
|
68
|
+
|
|
69
|
+
freeze
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Check if the sides are valid.
|
|
73
|
+
#
|
|
74
|
+
# @return [Boolean] true if valid
|
|
75
|
+
def valid?
|
|
76
|
+
validate!
|
|
77
|
+
true
|
|
78
|
+
rescue Error
|
|
79
|
+
false
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Check if both sides are empty.
|
|
83
|
+
#
|
|
84
|
+
# @return [Boolean] true if both players are nil
|
|
85
|
+
def empty?
|
|
86
|
+
first.nil? && second.nil?
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Convert to hash representation.
|
|
90
|
+
#
|
|
91
|
+
# @return [Hash] Sides hash (excludes nil values)
|
|
92
|
+
#
|
|
93
|
+
# @example
|
|
94
|
+
# sides.to_h # => { "first" => {...}, "second" => {...} }
|
|
95
|
+
def to_h
|
|
96
|
+
hash = {}
|
|
97
|
+
|
|
98
|
+
hash["first"] = first.to_h unless first.nil?
|
|
99
|
+
hash["second"] = second.to_h unless second.nil?
|
|
100
|
+
|
|
101
|
+
hash
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# String representation.
|
|
105
|
+
#
|
|
106
|
+
# @return [String] Inspectable representation
|
|
107
|
+
def to_s
|
|
108
|
+
fields = []
|
|
109
|
+
fields << "first=#{first.name.inspect}" if first && first.name
|
|
110
|
+
fields << "second=#{second.name.inspect}" if second && second.name
|
|
111
|
+
|
|
112
|
+
"#<#{self.class} #{fields.join(' ')}>"
|
|
113
|
+
end
|
|
114
|
+
alias inspect to_s
|
|
115
|
+
|
|
116
|
+
# Equality comparison.
|
|
117
|
+
#
|
|
118
|
+
# @param other [Sides] Other sides
|
|
119
|
+
# @return [Boolean] true if equal
|
|
120
|
+
def ==(other)
|
|
121
|
+
other.is_a?(self.class) &&
|
|
122
|
+
other.first == first &&
|
|
123
|
+
other.second == second
|
|
124
|
+
end
|
|
125
|
+
alias eql? ==
|
|
126
|
+
|
|
127
|
+
# Hash code for equality.
|
|
128
|
+
#
|
|
129
|
+
# @return [Integer] Hash code
|
|
130
|
+
def hash
|
|
131
|
+
[self.class, first, second].hash
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
private
|
|
135
|
+
|
|
136
|
+
# Parse player field.
|
|
137
|
+
def self.parse_player(value, field_name)
|
|
138
|
+
return nil if value.nil?
|
|
139
|
+
|
|
140
|
+
Player.parse(value)
|
|
141
|
+
rescue Error => e
|
|
142
|
+
raise Error::Validation, "Invalid '#{field_name}' player: #{e.message}"
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Normalize player to Player object.
|
|
146
|
+
def normalize_player(value, field_name)
|
|
147
|
+
return nil if value.nil?
|
|
148
|
+
return value if value.is_a?(Player)
|
|
149
|
+
|
|
150
|
+
Player.parse(value)
|
|
151
|
+
rescue Error => e
|
|
152
|
+
raise Error::Validation, "Invalid '#{field_name}' player: #{e.message}"
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Validate all fields.
|
|
156
|
+
def validate!
|
|
157
|
+
validate_structure!
|
|
158
|
+
validate_first!
|
|
159
|
+
validate_second!
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Validate that at least one player is defined.
|
|
163
|
+
def validate_structure!
|
|
164
|
+
return unless first.nil? && second.nil?
|
|
165
|
+
|
|
166
|
+
raise Error::Validation, "Sides must have at least one player defined"
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# Validate first player.
|
|
170
|
+
def validate_first!
|
|
171
|
+
return if first.nil?
|
|
172
|
+
|
|
173
|
+
raise Error::Validation, "Sides 'first' must be a Player object, got #{first.class}" unless first.is_a?(Player)
|
|
174
|
+
|
|
175
|
+
return if first.valid?
|
|
176
|
+
|
|
177
|
+
raise Error::Validation, "Sides 'first' player validation failed"
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# Validate second player.
|
|
181
|
+
def validate_second!
|
|
182
|
+
return if second.nil?
|
|
183
|
+
|
|
184
|
+
unless second.is_a?(Player)
|
|
185
|
+
raise Error::Validation, "Sides 'second' must be a Player object, got #{second.class}"
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
return if second.valid?
|
|
189
|
+
|
|
190
|
+
raise Error::Validation, "Sides 'second' player validation failed"
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
end
|
data/lib/sashite/pcn.rb
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "sashite/pmn"
|
|
4
|
+
require "sashite/feen"
|
|
5
|
+
require "sashite/snn"
|
|
6
|
+
|
|
7
|
+
require_relative "pcn/error"
|
|
8
|
+
require_relative "pcn/meta"
|
|
9
|
+
require_relative "pcn/player"
|
|
10
|
+
require_relative "pcn/sides"
|
|
11
|
+
require_relative "pcn/game"
|
|
12
|
+
|
|
13
|
+
module Sashite
|
|
14
|
+
# PCN (Portable Chess Notation) implementation.
|
|
15
|
+
#
|
|
16
|
+
# Provides a comprehensive, rule-agnostic format for representing complete
|
|
17
|
+
# chess game records across variants, integrating PMN, FEEN, and SNN
|
|
18
|
+
# specifications.
|
|
19
|
+
#
|
|
20
|
+
# @see https://sashite.dev/specs/pcn/1.0.0/
|
|
21
|
+
module Pcn
|
|
22
|
+
# Parse a PCN hash into a Game object.
|
|
23
|
+
#
|
|
24
|
+
# @param hash [Hash] PCN document hash
|
|
25
|
+
# @return [Game] Immutable game object
|
|
26
|
+
# @raise [Error] If parsing or validation fails
|
|
27
|
+
#
|
|
28
|
+
# @example
|
|
29
|
+
# game = Sashite::Pcn.parse({
|
|
30
|
+
# "setup" => "8/8/8/8/8/8/8/8 / C/c",
|
|
31
|
+
# "moves" => []
|
|
32
|
+
# })
|
|
33
|
+
def self.parse(hash)
|
|
34
|
+
Game.parse(hash)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Validate a PCN hash without raising exceptions.
|
|
38
|
+
#
|
|
39
|
+
# @param hash [Hash] PCN document hash
|
|
40
|
+
# @return [Boolean] true if valid, false otherwise
|
|
41
|
+
#
|
|
42
|
+
# @example
|
|
43
|
+
# Sashite::Pcn.valid?({ "setup" => "...", "moves" => [] }) # => true
|
|
44
|
+
# Sashite::Pcn.valid?({ "setup" => "" }) # => false
|
|
45
|
+
def self.valid?(hash)
|
|
46
|
+
Game.valid?(hash)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Create a new game from components.
|
|
50
|
+
#
|
|
51
|
+
# @param attributes [Hash] Game attributes as keyword arguments
|
|
52
|
+
# @option attributes [Feen::Position, String] :setup Initial position (required)
|
|
53
|
+
# @option attributes [Array<Pmn::Move, Array>] :moves Move sequence (required)
|
|
54
|
+
# @option attributes [String, nil] :status Game status (optional)
|
|
55
|
+
# @option attributes [Meta, Hash, nil] :meta Metadata (optional)
|
|
56
|
+
# @option attributes [Sides, Hash, nil] :sides Player information (optional)
|
|
57
|
+
# @return [Game] Immutable game object
|
|
58
|
+
#
|
|
59
|
+
# @example
|
|
60
|
+
# game = Sashite::Pcn.new(
|
|
61
|
+
# setup: Sashite::Feen.parse("8/8/8/8/8/8/8/8 / C/c"),
|
|
62
|
+
# moves: []
|
|
63
|
+
# )
|
|
64
|
+
def self.new(**attributes)
|
|
65
|
+
Game.new(**attributes)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
data/lib/sashite-pcn.rb
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "sashite/pcn"
|
|
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é
|
|
13
|
+
module Sashite
|
|
14
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: sashite-pcn
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Cyril Kato
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: sashite-feen
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0.3'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0.3'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: sashite-pmn
|
|
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
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: sashite-snn
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '3.1'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '3.1'
|
|
54
|
+
description: |
|
|
55
|
+
PCN (Portable Chess Notation) provides a comprehensive, JSON-based format for representing
|
|
56
|
+
complete chess game records across variants. This gem implements the PCN Specification v1.0.0
|
|
57
|
+
with a modern Ruby interface featuring immutable game objects and functional programming
|
|
58
|
+
principles. PCN integrates the Sashité ecosystem specifications (PMN for moves, FEEN for
|
|
59
|
+
positions, and SNN for style identification) to create a unified, rule-agnostic game recording
|
|
60
|
+
system. Supports traditional single-variant games and cross-variant scenarios where players
|
|
61
|
+
use different game systems, with complete metadata tracking including player information,
|
|
62
|
+
tournament context, and game status. Perfect for game engines, database storage, game analysis
|
|
63
|
+
tools, and archival systems requiring comprehensive game record management across diverse
|
|
64
|
+
abstract strategy board games.
|
|
65
|
+
email: contact@cyril.email
|
|
66
|
+
executables: []
|
|
67
|
+
extensions: []
|
|
68
|
+
extra_rdoc_files: []
|
|
69
|
+
files:
|
|
70
|
+
- LICENSE.md
|
|
71
|
+
- README.md
|
|
72
|
+
- lib/sashite-pcn.rb
|
|
73
|
+
- lib/sashite/pcn.rb
|
|
74
|
+
- lib/sashite/pcn/error.rb
|
|
75
|
+
- lib/sashite/pcn/game.rb
|
|
76
|
+
- lib/sashite/pcn/meta.rb
|
|
77
|
+
- lib/sashite/pcn/player.rb
|
|
78
|
+
- lib/sashite/pcn/sides.rb
|
|
79
|
+
homepage: https://github.com/sashite/pcn.rb
|
|
80
|
+
licenses:
|
|
81
|
+
- MIT
|
|
82
|
+
metadata:
|
|
83
|
+
bug_tracker_uri: https://github.com/sashite/pcn.rb/issues
|
|
84
|
+
documentation_uri: https://rubydoc.info/github/sashite/pcn.rb/main
|
|
85
|
+
homepage_uri: https://github.com/sashite/pcn.rb
|
|
86
|
+
source_code_uri: https://github.com/sashite/pcn.rb
|
|
87
|
+
specification_uri: https://sashite.dev/specs/pcn/1.0.0/
|
|
88
|
+
rubygems_mfa_required: 'true'
|
|
89
|
+
rdoc_options: []
|
|
90
|
+
require_paths:
|
|
91
|
+
- lib
|
|
92
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
93
|
+
requirements:
|
|
94
|
+
- - ">="
|
|
95
|
+
- !ruby/object:Gem::Version
|
|
96
|
+
version: 3.2.0
|
|
97
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
98
|
+
requirements:
|
|
99
|
+
- - ">="
|
|
100
|
+
- !ruby/object:Gem::Version
|
|
101
|
+
version: '0'
|
|
102
|
+
requirements: []
|
|
103
|
+
rubygems_version: 3.6.9
|
|
104
|
+
specification_version: 4
|
|
105
|
+
summary: PCN (Portable Chess Notation) implementation for Ruby with comprehensive
|
|
106
|
+
game record representation
|
|
107
|
+
test_files: []
|