sashite-feen 0.2.0 → 0.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 98fd7fceb1edd4b6479a217c0932443c76335b4d41238b2ee0d3f2e3fc2dd397
4
- data.tar.gz: 6f31b473124e985baab36d30bf84d3c7cf05d129d66bbfe16b4b60068a44c545
3
+ metadata.gz: a8182f6c48f6a615f38d75d3060f216c06c6606b118544cb4f4f0e40b9615e89
4
+ data.tar.gz: 1150aacab981e71aa32aaae585ec1998cd678bb5997432135b0089394eef2482
5
5
  SHA512:
6
- metadata.gz: 6f5c62abd73d7628ded615367f74644933615488fdffdceecb85f06f2c371fe96ad269b3c4722343fd59d6145c5650492f8e1b9ec9f660c1ed57be83877b7eaa
7
- data.tar.gz: 19e3e8f686a1d114dc9da267209716b3c28372585c90d061c604ac6d13692b67cb15abbdf967ff62f125518c26a63506e73525b2115421a659c18094c19e270e
6
+ metadata.gz: b8ed4a473fce336ce73cb89c684e9ea7820842b78e12766bc32440f25da7f61998c13077ce3743972ef234a85771c63f58778835dd11bfba1fd97ef63dfb7154
7
+ data.tar.gz: 679296955c3b032e0ebe7b9f6ca4776b42b617f67a876e5de6bee015d186e40287d9cc964a3bb2d3faec0dbaf0dd98d810e3bf522cde197d340f168ba9be3a84
data/README.md CHANGED
@@ -9,7 +9,15 @@
9
9
 
10
10
  ## What is FEEN?
11
11
 
12
- FEEN (Forsyth–Edwards Enhanced Notation) is a universal, rule-agnostic notation for representing board game positions. It extends traditional FEN to support multiple game systems, cross-style games, multi-dimensional boards, and captured pieces.
12
+ FEEN (Forsyth–Edwards Enhanced Notation) is a universal, rule-agnostic notation for representing board game positions. It extends traditional FEN to support:
13
+
14
+ - **Multiple game systems** (Chess, Shōgi, Xiangqi, and more)
15
+ - **Cross-style games** where players use different piece sets
16
+ - **Multi-dimensional boards** (2D, 3D, and beyond)
17
+ - **Captured pieces** (pieces-in-hand for drop mechanics)
18
+ - **Arbitrarily large boards** with efficient empty square encoding
19
+ - **Completely irregular structures** (any valid combination of ranks and separators)
20
+ - **Board-less positions** (positions without piece placement, useful for pure style/turn tracking)
13
21
 
14
22
  This gem implements the [FEEN Specification v1.0.0](https://sashite.dev/specs/feen/1.0.0/) as a pure functional library with immutable data structures.
15
23
 
@@ -19,94 +27,480 @@ This gem implements the [FEEN Specification v1.0.0](https://sashite.dev/specs/fe
19
27
  gem "sashite-feen"
20
28
  ```
21
29
 
22
- ## API
30
+ Or install manually:
31
+
32
+ ```sh
33
+ gem install sashite-feen
34
+ ```
23
35
 
24
- The library provides two methods for converting between FEEN strings and position objects:
36
+ ## Quick Start
25
37
 
26
38
  ```ruby
27
39
  require "sashite/feen"
28
40
 
29
- # Parse a FEEN string into a position object
41
+ # Parse a FEEN string into an immutable position object
30
42
  position = Sashite::Feen.parse("+rnbq+kbn+r/+p+p+p+p+p+p+p+p/8/8/8/8/+P+P+P+P+P+P+P+P/+RNBQ+KBN+R / C/c")
31
43
 
32
- # Dump a position object into a canonical FEEN string
33
- feen_string = Sashite::Feen.dump(position)
44
+ # Access position components
45
+ position.placement # Board configuration
46
+ position.hands # Captured pieces
47
+ position.styles # Game styles and active player
48
+
49
+ # Convert placement to array based on dimensionality
50
+ position.placement.to_a # => [[pieces...], [pieces...], ...] for 2D boards
51
+
52
+ # Convert back to canonical FEEN string
53
+ feen_string = Sashite::Feen.dump(position) # or position.to_s
34
54
  ```
35
55
 
36
- ### Methods
56
+ ## FEEN Format
57
+
58
+ A FEEN string consists of three space-separated fields:
59
+
60
+ ```
61
+ <piece-placement> <pieces-in-hand> <style-turn>
62
+ ```
63
+
64
+ **Example:**
65
+ ```txt
66
+ +rnbq+kbn+r/+p+p+p+p+p+p+p+p/8/8/8/8/+P+P+P+P+P+P+P+P/+RNBQ+KBN+R / C/c
67
+ ```
68
+
69
+ 1. **Piece placement**: Board configuration using EPIN notation with `/` separators (can be empty for board-less positions)
70
+ 2. **Pieces in hand**: Captured pieces for each player (format: `first/second`)
71
+ 3. **Style-turn**: Game styles and active player (format: `active/inactive`)
72
+
73
+ See the [FEEN Specification](https://sashite.dev/specs/feen/1.0.0/) for complete format details.
74
+
75
+ ## API Reference
76
+
77
+ ### Module Methods
37
78
 
38
79
  #### `Sashite::Feen.parse(string)`
39
80
 
40
- Parses a FEEN string and returns an immutable `Position` object.
81
+ Parses a FEEN string into an immutable `Position` object.
41
82
 
42
- - **Input**: FEEN string with three space-separated fields
43
- - **Returns**: `Sashite::Feen::Position` instance
83
+ - **Parameter**: `string` (String) - FEEN notation string
84
+ - **Returns**: `Position` - Immutable position object
44
85
  - **Raises**: `Sashite::Feen::Error` subclasses on invalid input
45
86
 
87
+ ```ruby
88
+ position = Sashite::Feen.parse("8/8/8/8/8/8/8/8 / C/c")
89
+
90
+ # Board-less position (empty piece placement)
91
+ position = Sashite::Feen.parse(" / C/c")
92
+ ```
93
+
46
94
  #### `Sashite::Feen.dump(position)`
47
95
 
48
- Converts a position object into its canonical FEEN string representation.
96
+ Converts a position object into its canonical FEEN string.
49
97
 
50
- - **Input**: `Sashite::Feen::Position` instance
51
- - **Returns**: Canonical FEEN string
98
+ - **Parameter**: `position` (Position) - Position object
99
+ - **Returns**: `String` - Canonical FEEN string
52
100
  - **Guarantees**: Deterministic output (same position always produces same string)
53
101
 
102
+ ```ruby
103
+ feen_string = Sashite::Feen.dump(position)
104
+ ```
105
+
54
106
  ### Position Object
55
107
 
56
- The `Position` object returned by `parse` is immutable and provides read-only access to the three FEEN components:
108
+ The `Position` object is immutable and provides read-only access to three components:
109
+
110
+ ```ruby
111
+ position.placement # => Placement (board configuration)
112
+ position.hands # => Hands (pieces in hand)
113
+ position.styles # => Styles (style-turn information)
114
+ position.to_s # => String (canonical FEEN)
115
+ ```
57
116
 
117
+ **Equality and hashing:**
58
118
  ```ruby
59
- position.placement # => Placement object (board arrangement)
60
- position.hands # => Hands object (pieces in hand)
61
- position.styles # => Styles object (style-turn information)
62
- position.to_s # => Canonical FEEN string (equivalent to dump)
119
+ position1 == position2 # Component-wise equality
120
+ position1.hash # Consistent hash for same positions
63
121
  ```
64
122
 
65
- ## Format
123
+ ### Placement Object
66
124
 
67
- A FEEN string consists of three space-separated fields:
125
+ Represents the board configuration as a flat array of ranks with explicit separators.
68
126
 
127
+ ```ruby
128
+ placement.ranks # => Array<Array> - Flat array of all ranks
129
+ placement.separators # => Array<String> - Separators between ranks (e.g., ["/", "//"])
130
+ placement.dimension # => Integer - Board dimensionality (1 + max consecutive slashes)
131
+ placement.rank_count # => Integer - Total number of ranks
132
+ placement.one_dimensional? # => Boolean - True if dimension is 1
133
+ placement.all_pieces # => Array - All pieces (nils excluded)
134
+ placement.total_squares # => Integer - Total square count
135
+ placement.to_s # => String - Piece placement field
136
+ placement.to_a # => Array - Array representation (dimension-aware)
69
137
  ```
70
- <piece-placement> <pieces-in-hand> <style-turn>
138
+
139
+ #### `to_a` - Dimension-Aware Array Conversion
140
+
141
+ The `to_a` method returns an array representation that adapts to the board's dimensionality:
142
+
143
+ - **1D boards**: Returns a single rank array (or empty array if no ranks)
144
+ - **2D+ boards**: Returns array of ranks
145
+
146
+ ```ruby
147
+ # 1D board - Returns flat array
148
+ feen = "K2P3k / C/c"
149
+ position = Sashite::Feen.parse(feen)
150
+ position.placement.to_a
151
+ # => [K, nil, nil, P, nil, nil, nil, k]
152
+
153
+ # 2D board - Returns array of arrays
154
+ feen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR / C/c"
155
+ position = Sashite::Feen.parse(feen)
156
+ position.placement.to_a
157
+ # => [[r,n,b,q,k,b,n,r], [p,p,p,p,p,p,p,p], [nil×8], ...]
158
+
159
+ # 3D board - Returns array of ranks (to be structured by application)
160
+ feen = "5/5//5/5 / R/r"
161
+ position = Sashite::Feen.parse(feen)
162
+ position.placement.to_a
163
+ # => [[nil×5], [nil×5], [nil×5], [nil×5]]
164
+
165
+ # Empty board
166
+ placement = Sashite::Feen::Placement.new([], [], 1)
167
+ placement.to_a
168
+ # => []
71
169
  ```
72
170
 
73
- 1. **Piece placement**: Board configuration using EPIN notation
74
- 2. **Pieces in hand**: Captured pieces held by each player
75
- 3. **Style-turn**: Game styles and active player
171
+ **Other methods:**
172
+
173
+ ```ruby
174
+ # Access specific positions
175
+ first_rank = placement.ranks[0]
176
+ piece_at_a1 = first_rank[0] # Piece object or nil
177
+
178
+ # Check dimensionality
179
+ placement.dimension # => 2 (2D board)
180
+
181
+ # Inspect separator structure
182
+ placement.separators # => ["/", "/", "/", "/", "/", "/", "/"]
183
+ ```
184
+
185
+ ### Hands Object
186
+
187
+ Represents captured pieces held by each player.
188
+
189
+ ```ruby
190
+ hands.first_player # => Array - Pieces held by first player
191
+ hands.second_player # => Array - Pieces held by second player
192
+ hands.empty? # => Boolean - True if both hands are empty
193
+ hands.to_s # => String - Pieces-in-hand field
194
+ ```
195
+
196
+ **Example:**
197
+ ```ruby
198
+ # Count pieces in hand
199
+ first_player_pawns = hands.first_player.count { |p| p.to_s == "P" }
200
+
201
+ # Check if any captures
202
+ hands.empty? # => false
203
+ ```
204
+
205
+ ### Styles Object
206
+
207
+ Represents game styles and indicates the active player.
208
+
209
+ ```ruby
210
+ styles.active # => SIN identifier - Active player's style
211
+ styles.inactive # => SIN identifier - Inactive player's style
212
+ styles.to_s # => String - Style-turn field
213
+ ```
214
+
215
+ **Example:**
216
+ ```ruby
217
+ # Determine active player
218
+ styles.active.to_s # => "C" (first player Chess)
219
+ styles.inactive.to_s # => "c" (second player Chess)
220
+
221
+ # Check if cross-style
222
+ styles.active.to_s.upcase != styles.inactive.to_s.upcase
223
+ ```
224
+
225
+ ## Examples
226
+
227
+ ### Chess Positions
228
+
229
+ ```ruby
230
+ # Starting position
231
+ chess_start = Sashite::Feen.parse(
232
+ "+rnbq+kbn+r/+p+p+p+p+p+p+p+p/8/8/8/8/+P+P+P+P+P+P+P+P/+RNBQ+KBN+R / C/c"
233
+ )
234
+
235
+ # After 1.e4
236
+ after_e4 = Sashite::Feen.parse(
237
+ "+rnbq+kbn+r/+p+p+p+p+p+p+p+p/8/8/4P3/8/+P+P+P+P1+P+P+P/+RNBQ+KBN+R / c/C"
238
+ )
239
+
240
+ # Ruy Lopez opening
241
+ ruy_lopez = Sashite::Feen.parse(
242
+ "r1bqkbnr/+p+p+p+p1+p+p+p/2n5/1B2p3/4P3/5N2/+P+P+P+P1+P+P+P/RNBQK2R / c/C"
243
+ )
244
+ ```
76
245
 
77
- For complete format details, see the [FEEN Specification](https://sashite.dev/specs/feen/1.0.0/).
246
+ ### Shōgi with Captured Pieces
247
+
248
+ ```ruby
249
+ # Starting position
250
+ shogi_start = Sashite::Feen.parse(
251
+ "lnsgkgsnl/1r5b1/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL / S/s"
252
+ )
253
+
254
+ # Position with pieces in hand
255
+ shogi_midgame = Sashite::Feen.parse(
256
+ "lnsgkgsnl/1r5b1/pppp1pppp/9/4p4/9/PPPP1PPPP/1B5R1/LNSGKGSNL P/p s/S"
257
+ )
258
+
259
+ # Access captured pieces
260
+ position = shogi_midgame
261
+ position.hands.first_player # => [P] (one pawn)
262
+ position.hands.second_player # => [p] (one pawn)
263
+
264
+ # Count specific pieces in hand
265
+ position.hands.first_player.count { |p| p.to_s == "P" } # => 1
266
+ ```
267
+
268
+ ### Cross-Style Games
269
+
270
+ ```ruby
271
+ # Chess vs Makruk
272
+ chess_vs_makruk = Sashite::Feen.parse(
273
+ "rnsmksnr/8/pppppppp/8/8/8/+P+P+P+P+P+P+P+P/+RNBQ+KBN+R / C/m"
274
+ )
275
+
276
+ # Chess vs Shōgi
277
+ chess_vs_shogi = Sashite::Feen.parse(
278
+ "lnsgkgsnl/1r5b1/pppppppp/9/9/9/+P+P+P+P+P+P+P+P/+RNBQ+KBN+R / C/s"
279
+ )
280
+
281
+ # Check styles
282
+ position = chess_vs_makruk
283
+ position.styles.active.to_s # => "C" (Chess, first player)
284
+ position.styles.inactive.to_s # => "m" (Makruk, second player)
285
+ ```
286
+
287
+ ### Multi-Dimensional Boards
288
+
289
+ ```ruby
290
+ # 3D Chess (Raumschach)
291
+ raumschach = Sashite::Feen.parse(
292
+ "rnknr/+p+p+p+p+p/5/5/5//buqbu/+p+p+p+p+p/5/5/5//5/5/5/5/5//5/5/5/+P+P+P+P+P/BUQBU//5/5/5/+P+P+P+P+P/RNKNR / R/r"
293
+ )
294
+
295
+ # Check dimensionality
296
+ raumschach.placement.dimension # => 3 (3D board)
297
+ raumschach.placement.ranks.size # => 25 (total ranks)
298
+
299
+ # Inspect separator structure
300
+ level_seps = raumschach.placement.separators.count { |s| s == "//" }
301
+ rank_seps = raumschach.placement.separators.count { |s| s == "/" }
302
+ # level_seps => 4 (separates 5 levels)
303
+ # rank_seps => 20 (separates ranks within levels)
304
+ ```
305
+
306
+ ### Irregular Boards
307
+
308
+ ```ruby
309
+ # Diamond-shaped board
310
+ diamond = Sashite::Feen.parse("3/4/5/4/3 / G/g")
311
+
312
+ # Check structure
313
+ diamond.placement.ranks.map(&:size) # => [3, 4, 5, 4, 3]
314
+
315
+ # Very large board
316
+ large_board = Sashite::Feen.parse("100/100/100 / G/g")
317
+ large_board.placement.total_squares # => 300
318
+
319
+ # Single square
320
+ single = Sashite::Feen.parse("K / C/c")
321
+ single.placement.rank_count # => 1
322
+ ```
323
+
324
+ ### Completely Irregular Structures
325
+
326
+ FEEN supports any valid combination of ranks and separators:
327
+
328
+ ```ruby
329
+ # Extreme irregularity with variable separators
330
+ feen = "99999/3///K/k//r / G/g"
331
+ position = Sashite::Feen.parse(feen)
332
+
333
+ # Access the structure
334
+ position.placement.ranks.size # => 5 ranks
335
+ position.placement.separators # => ["/", "///", "/", "//"]
336
+ position.placement.dimension # => 4 (max separator is "///")
337
+
338
+ # Each rank can have different sizes
339
+ position.placement.ranks[0].size # => 99999
340
+ position.placement.ranks[1].size # => 3
341
+ position.placement.ranks[2].size # => 1
342
+ position.placement.ranks[3].size # => 1
343
+ position.placement.ranks[4].size # => 1
344
+
345
+ # Round-trip preservation
346
+ Sashite::Feen.dump(position) == feen # => true
347
+ ```
348
+
349
+ ### Empty Ranks
350
+
351
+ FEEN supports empty ranks (ranks with no pieces):
352
+
353
+ ```ruby
354
+ # Trailing separator creates empty rank
355
+ feen = "K/// / C/c"
356
+ position = Sashite::Feen.parse(feen)
357
+
358
+ position.placement.ranks.size # => 2
359
+ position.placement.ranks[0] # => [K]
360
+ position.placement.ranks[1] # => [] (empty rank)
361
+ position.placement.separators # => ["///"]
362
+
363
+ # Round-trip preserves structure
364
+ Sashite::Feen.dump(position) == feen # => true
365
+ ```
366
+
367
+ ### Board-less Positions
368
+
369
+ FEEN supports positions without piece placement, useful for tracking only style and turn information:
370
+
371
+ ```ruby
372
+ # Position with empty board (no piece placement)
373
+ board_less = Sashite::Feen.parse(" / C/c")
374
+
375
+ board_less.placement.ranks.size # => 1
376
+ board_less.placement.dimension # => 1
377
+ board_less.placement.to_a # => []
378
+
379
+ # Convert back to FEEN
380
+ Sashite::Feen.dump(board_less) # => " / C/c"
381
+ ```
382
+
383
+ ### Working with Positions
384
+
385
+ ```ruby
386
+ # Compare positions
387
+ position1 = Sashite::Feen.parse("8/8/8/8/8/8/8/8 / C/c")
388
+ position2 = Sashite::Feen.parse("8/8/8/8/8/8/8/8 / C/c")
389
+ position1 == position2 # => true
390
+
391
+ # Round-trip parsing
392
+ original = "+rnbq+kbn+r/+p+p+p+p+p+p+p+p/8/8/8/8/+P+P+P+P+P+P+P+P/+RNBQ+KBN+R / C/c"
393
+ position = Sashite::Feen.parse(original)
394
+ Sashite::Feen.dump(position) == original # => true
395
+
396
+ # Extract specific information
397
+ position.placement.ranks[0] # First rank (array of pieces/nils)
398
+ position.hands.first_player.size # Number of captured pieces
399
+ ```
400
+
401
+ ### State Modifiers and Derivation
402
+
403
+ ```ruby
404
+ # Enhanced pieces (promoted, with special rights)
405
+ enhanced = Sashite::Feen.parse("+K+Q+R+B/8/8/8/8/8/8/8 / C/c")
406
+
407
+ # Diminished pieces (weakened, vulnerable)
408
+ diminished = Sashite::Feen.parse("-K-Q-R-B/8/8/8/8/8/8/8 / C/c")
409
+
410
+ # Foreign pieces (using opponent's style)
411
+ foreign = Sashite::Feen.parse("K'Q'R'B'/k'q'r'b'/8/8/8/8/8/8 / C/s")
412
+ ```
78
413
 
79
414
  ## Error Handling
80
415
 
81
- The library defines specific error classes for different validation failures:
416
+ FEEN defines specific error classes for different validation failures:
417
+
418
+ ```ruby
419
+ begin
420
+ position = Sashite::Feen.parse("invalid feen")
421
+ rescue Sashite::Feen::Error => e
422
+ # Base error class catches all FEEN errors
423
+ warn "FEEN error: #{e.message}"
424
+ end
425
+ ```
426
+
427
+ ### Error Hierarchy
82
428
 
83
429
  ```txt
84
- Sashite::Feen::Error # Base error class
85
- ├── Error::Syntax # Malformed FEEN structure
86
- ├── Error::Piece # Invalid EPIN notation
87
- ├── Error::Style # Invalid SIN notation
88
- ├── Error::Count # Invalid piece counts
89
- └── Error::Validation # Other semantic violations
430
+ Sashite::Feen::Error # Base error class
431
+ ├── Error::Syntax # Malformed FEEN structure
432
+ ├── Error::Piece # Invalid EPIN notation
433
+ ├── Error::Style # Invalid SIN notation
434
+ ├── Error::Count # Invalid piece counts
435
+ └── Error::Validation # Other semantic violations
436
+ ```
437
+
438
+ ### Common Errors
439
+
440
+ ```ruby
441
+ # Syntax error - wrong field count
442
+ Sashite::Feen.parse("8/8/8/8/8/8/8/8 /")
443
+ # => Error::Syntax: "FEEN must have exactly 3 space-separated fields, got 2"
444
+
445
+ # Style error - invalid SIN
446
+ Sashite::Feen.parse("8/8/8/8/8/8/8/8 / 1/2")
447
+ # => Error::Style: "failed to parse SIN '1': invalid SIN notation: '1' (must be a single letter A-Z or a-z)"
448
+
449
+ # Count error - invalid quantity
450
+ Sashite::Feen.parse("8/8/8/8/8/8/8/8 0P/ C/c")
451
+ # => Error::Count: "piece count must be at least 1, got 0"
90
452
  ```
91
453
 
92
454
  ## Properties
93
455
 
94
456
  - **Purely functional**: Immutable data structures, no side effects
95
- - **Canonical output**: Deterministic string generation
96
- - **Specification compliant**: Strict adherence to FEEN v1.0.0
97
- - **Minimal API**: Two methods for complete functionality
98
- - **Composable**: Built on EPIN and SIN specifications
457
+ - **Canonical output**: Deterministic string generation (same position → same string)
458
+ - **Specification compliant**: Strict adherence to [FEEN v1.0.0](https://sashite.dev/specs/feen/1.0.0/)
459
+ - **Minimal API**: Two methods (`parse` and `dump`) for complete functionality
460
+ - **Universal**: Supports any abstract strategy board game
461
+ - **Completely flexible**: Accepts any valid combination of ranks and separators
462
+ - **Perfect round-trip**: `parse(dump(position)) == position` guaranteed
463
+ - **Dimension-aware**: Intelligent array conversion based on board structure
464
+ - **Composable**: Built on [EPIN](https://github.com/sashite/epin.rb) and [SIN](https://github.com/sashite/sin.rb) specifications
99
465
 
100
466
  ## Dependencies
101
467
 
102
- - [sashite-epin](https://github.com/sashite/epin.rb) Extended Piece Identifier Notation
103
- - [sashite-sin](https://github.com/sashite/sin.rb) Style Identifier Notation
468
+ - [sashite-epin](https://github.com/sashite/epin.rb) Extended Piece Identifier Notation
469
+ - [sashite-sin](https://github.com/sashite/sin.rb) Style Identifier Notation
104
470
 
105
471
  ## Documentation
106
472
 
107
- - [FEEN Specification v1.0.0](https://sashite.dev/specs/feen/1.0.0/)
108
- - [FEEN Examples](https://sashite.dev/specs/feen/1.0.0/examples/)
109
- - [API Documentation](https://rubydoc.info/github/sashite/feen.rb/main)
473
+ - [FEEN Specification v1.0.0](https://sashite.dev/specs/feen/1.0.0/) — Complete technical specification
474
+ - [FEEN Examples](https://sashite.dev/specs/feen/1.0.0/examples/) — Comprehensive examples
475
+ - [API Documentation](https://rubydoc.info/github/sashite/feen.rb/main) — Full API reference
476
+ - [GitHub Wiki](https://github.com/sashite/feen.rb/wiki) — Advanced usage and patterns
477
+
478
+ ## Development
479
+
480
+ ```sh
481
+ # Clone the repository
482
+ git clone https://github.com/sashite/feen.rb.git
483
+ cd feen.rb
484
+
485
+ # Install dependencies
486
+ bundle install
487
+
488
+ # Run tests
489
+ ruby test.rb
490
+
491
+ # Generate documentation
492
+ yard doc
493
+ ```
494
+
495
+ ## Contributing
496
+
497
+ 1. Fork the repository
498
+ 2. Create a feature branch (`git checkout -b feature/new-feature`)
499
+ 3. Add tests for your changes
500
+ 4. Ensure all tests pass (`ruby test.rb`)
501
+ 5. Commit your changes (`git commit -am 'Add new feature'`)
502
+ 6. Push to the branch (`git push origin feature/new-feature`)
503
+ 7. Create a Pull Request
110
504
 
111
505
  ## License
112
506
 
@@ -114,4 +508,4 @@ Available as open source under the [MIT License](https://opensource.org/licenses
114
508
 
115
509
  ## About
116
510
 
117
- Maintained by [Sashité](https://sashite.com/) promoting chess variants and sharing the beauty of board game cultures.
511
+ Maintained by [Sashité](https://sashite.com/) promoting chess variants and sharing the beauty of board game cultures.