sashite-gan 2.1.0 → 3.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/LICENSE.md +1 -1
 - data/README.md +164 -165
 - data/lib/sashite/gan/dumper.rb +94 -0
 - data/lib/sashite/gan/parser.rb +47 -20
 - data/lib/sashite/gan/validator.rb +23 -0
 - data/lib/sashite/gan.rb +63 -36
 - data/lib/sashite-gan.rb +4 -3
 - metadata +22 -108
 - data/lib/sashite/gan/abbr.rb +0 -76
 - data/lib/sashite/gan/error/string.rb +0 -10
 - data/lib/sashite/gan/error/style.rb +0 -12
 - data/lib/sashite/gan/error/type.rb +0 -12
 - data/lib/sashite/gan/error.rb +0 -12
 - data/lib/sashite/gan/piece.rb +0 -146
 
    
        checksums.yaml
    CHANGED
    
    | 
         @@ -1,7 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            ---
         
     | 
| 
       2 
2 
     | 
    
         
             
            SHA256:
         
     | 
| 
       3 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       4 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 3 
     | 
    
         
            +
              metadata.gz: 361cb6527615fdfe0c4246c3f5f603598af97a46e6be9ec0cc300d03651dcb4a
         
     | 
| 
      
 4 
     | 
    
         
            +
              data.tar.gz: 50fea949d4a759c2a68792085e1457c16c49966adf075533eb2e5cd1da5f852a
         
     | 
| 
       5 
5 
     | 
    
         
             
            SHA512:
         
     | 
| 
       6 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       7 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 6 
     | 
    
         
            +
              metadata.gz: '0298e95b598a17d558f85072321ee9006470f83e83aa20fd58235ec5550df98e1ec20f594b715962d8a063f0757ba927c0093577eb9c3c98ec9dd9b5180cdb4a'
         
     | 
| 
      
 7 
     | 
    
         
            +
              data.tar.gz: 705ad8e30abe365dedbe108d941625d6c06725d0507c2e853f622bfe9da992137109b9fd4c44a09162cfbe1aed5c1f7bb15c1689977b131123095d69897cbebf
         
     | 
    
        data/LICENSE.md
    CHANGED
    
    
    
        data/README.md
    CHANGED
    
    | 
         @@ -1,181 +1,180 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
            #  
     | 
| 
      
 1 
     | 
    
         
            +
            # Gan.rb
         
     | 
| 
       2 
2 
     | 
    
         | 
| 
       3 
     | 
    
         
            -
            [](https://github.com/sashite/gan.rb/tags)
         
     | 
| 
      
 4 
     | 
    
         
            +
            [](https://rubydoc.info/github/sashite/gan.rb/main)
         
     | 
| 
      
 5 
     | 
    
         
            +
            
         
     | 
| 
      
 6 
     | 
    
         
            +
            [](https://github.com/sashite/gan.rb/raw/main/LICENSE.md)
         
     | 
| 
       4 
7 
     | 
    
         | 
| 
       5 
     | 
    
         
            -
            >  
     | 
| 
      
 8 
     | 
    
         
            +
            > **GAN** (General Actor Notation) support for the Ruby language.
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
            ## What is GAN?
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
            GAN (General Actor Notation) defines a consistent and rule-agnostic format for representing game actors in abstract strategy board games. Building upon Piece Name Notation (PNN), GAN eliminates ambiguity by associating each piece with its originating game, allowing for unambiguous gameplay application and cross-game distinctions.
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
            This gem implements the [GAN Specification v1.0.0](https://sashite.dev/documents/gan/1.0.0/), providing a Ruby interface for:
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
            - Serializing game actors to GAN strings
         
     | 
| 
      
 17 
     | 
    
         
            +
            - Parsing GAN strings into their component parts
         
     | 
| 
      
 18 
     | 
    
         
            +
            - Validating GAN strings according to the specification
         
     | 
| 
       6 
19 
     | 
    
         | 
| 
       7 
20 
     | 
    
         
             
            ## Installation
         
     | 
| 
       8 
21 
     | 
    
         | 
| 
       9 
     | 
    
         
            -
             
     | 
| 
      
 22 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 23 
     | 
    
         
            +
            # In your Gemfile
         
     | 
| 
      
 24 
     | 
    
         
            +
            gem "sashite-gan"
         
     | 
| 
      
 25 
     | 
    
         
            +
            ```
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
            Or install manually:
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
            ```sh
         
     | 
| 
      
 30 
     | 
    
         
            +
            gem install sashite-gan
         
     | 
| 
      
 31 
     | 
    
         
            +
            ```
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
            ## GAN Format
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
            A GAN record consists of a game identifier, followed by a colon, followed by a piece identifier that follows the PNN specification:
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
            ```
         
     | 
| 
      
 38 
     | 
    
         
            +
            <game-id>:<piece-id>
         
     | 
| 
      
 39 
     | 
    
         
            +
            ```
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
            Where:
         
     | 
| 
       10 
42 
     | 
    
         | 
| 
       11 
     | 
    
         
            -
             
     | 
| 
       12 
     | 
    
         
            -
             
     | 
| 
       13 
     | 
    
         
            -
             
     | 
| 
      
 43 
     | 
    
         
            +
            - `<game-id>` is a sequence of alphabetic characters identifying the game variant.
         
     | 
| 
      
 44 
     | 
    
         
            +
            - `:` is a literal colon character, serving as a separator.
         
     | 
| 
      
 45 
     | 
    
         
            +
            - `<piece-id>` is a piece representation following the PNN specification: `[<prefix>]<letter>[<suffix>]`.
         
     | 
| 
       14 
46 
     | 
    
         | 
| 
       15 
     | 
    
         
            -
             
     | 
| 
      
 47 
     | 
    
         
            +
            The casing of the game identifier reflects the player:
         
     | 
| 
       16 
48 
     | 
    
         | 
| 
       17 
     | 
    
         
            -
             
     | 
| 
      
 49 
     | 
    
         
            +
            - **Uppercase** game identifiers (e.g., `CHESS:`) denote pieces belonging to the first player.
         
     | 
| 
      
 50 
     | 
    
         
            +
            - **Lowercase** game identifiers (e.g., `chess:`) denote pieces belonging to the second player.
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
            ## Basic Usage
         
     | 
| 
      
 53 
     | 
    
         
            +
             
     | 
| 
      
 54 
     | 
    
         
            +
            ### Parsing GAN Strings
         
     | 
| 
      
 55 
     | 
    
         
            +
             
     | 
| 
      
 56 
     | 
    
         
            +
            Convert a GAN string into a structured Ruby hash:
         
     | 
| 
       18 
57 
     | 
    
         | 
| 
       19 
58 
     | 
    
         
             
            ```ruby
         
     | 
| 
       20 
     | 
    
         
            -
            require  
     | 
| 
       21 
     | 
    
         
            -
             
     | 
| 
       22 
     | 
    
         
            -
             
     | 
| 
       23 
     | 
    
         
            -
             
     | 
| 
       24 
     | 
    
         
            -
             
     | 
| 
       25 
     | 
    
         
            -
             
     | 
| 
       26 
     | 
    
         
            -
             
     | 
| 
       27 
     | 
    
         
            -
             
     | 
| 
       28 
     | 
    
         
            -
             
     | 
| 
       29 
     | 
    
         
            -
             
     | 
| 
       30 
     | 
    
         
            -
             
     | 
| 
       31 
     | 
    
         
            -
             
     | 
| 
       32 
     | 
    
         
            -
             
     | 
| 
       33 
     | 
    
         
            -
             
     | 
| 
       34 
     | 
    
         
            -
            piece 
     | 
| 
       35 
     | 
    
         
            -
             
     | 
| 
       36 
     | 
    
         
            -
             
     | 
| 
       37 
     | 
    
         
            -
             
     | 
| 
       38 
     | 
    
         
            -
            # Chess (Western chess)'s King, Black
         
     | 
| 
       39 
     | 
    
         
            -
            piece = Sashite::GAN.parse("c:-k")
         
     | 
| 
       40 
     | 
    
         
            -
             
     | 
| 
       41 
     | 
    
         
            -
            piece.abbr.to_s # => "-k"
         
     | 
| 
       42 
     | 
    
         
            -
            piece.style # => "c"
         
     | 
| 
       43 
     | 
    
         
            -
            piece.topside? # => true
         
     | 
| 
       44 
     | 
    
         
            -
            piece.bottomside? # => false
         
     | 
| 
       45 
     | 
    
         
            -
            piece.to_s # => "c:-k"
         
     | 
| 
       46 
     | 
    
         
            -
            piece.topside.to_s # => "c:-k"
         
     | 
| 
       47 
     | 
    
         
            -
            piece.bottomside.to_s # => "C:-K"
         
     | 
| 
       48 
     | 
    
         
            -
            piece.oppositeside.to_s # => "C:-K"
         
     | 
| 
       49 
     | 
    
         
            -
            piece.promote.to_s # => "c:+-k"
         
     | 
| 
       50 
     | 
    
         
            -
            piece.unpromote.to_s # => "c:-k"
         
     | 
| 
       51 
     | 
    
         
            -
             
     | 
| 
       52 
     | 
    
         
            -
             
     | 
| 
       53 
     | 
    
         
            -
            # Makruk (Thai chess)'s Bishop, White
         
     | 
| 
       54 
     | 
    
         
            -
            piece = Sashite::GAN.parse("M:B")
         
     | 
| 
       55 
     | 
    
         
            -
             
     | 
| 
       56 
     | 
    
         
            -
            piece.abbr.to_s # => "B"
         
     | 
| 
       57 
     | 
    
         
            -
            piece.style # => "M"
         
     | 
| 
       58 
     | 
    
         
            -
            piece.topside? # => false
         
     | 
| 
       59 
     | 
    
         
            -
            piece.bottomside? # => true
         
     | 
| 
       60 
     | 
    
         
            -
            piece.to_s # => "M:B"
         
     | 
| 
       61 
     | 
    
         
            -
            piece.topside.to_s # => "m:b"
         
     | 
| 
       62 
     | 
    
         
            -
            piece.bottomside.to_s # => "M:B"
         
     | 
| 
       63 
     | 
    
         
            -
            piece.oppositeside.to_s # => "m:b"
         
     | 
| 
       64 
     | 
    
         
            -
            piece.promote.to_s # => "M:+B"
         
     | 
| 
       65 
     | 
    
         
            -
            piece.unpromote.to_s # => "M:B"
         
     | 
| 
       66 
     | 
    
         
            -
             
     | 
| 
       67 
     | 
    
         
            -
             
     | 
| 
       68 
     | 
    
         
            -
            # Shogi (Japanese chess)'s King, Gote
         
     | 
| 
       69 
     | 
    
         
            -
            piece = Sashite::GAN.parse("s:-k")
         
     | 
| 
       70 
     | 
    
         
            -
             
     | 
| 
       71 
     | 
    
         
            -
            piece.abbr.to_s # => "-k"
         
     | 
| 
       72 
     | 
    
         
            -
            piece.style # => "s"
         
     | 
| 
       73 
     | 
    
         
            -
            piece.topside? # => true
         
     | 
| 
       74 
     | 
    
         
            -
            piece.bottomside? # => false
         
     | 
| 
       75 
     | 
    
         
            -
            piece.to_s # => "s:-k"
         
     | 
| 
       76 
     | 
    
         
            -
            piece.topside.to_s # => "s:-k"
         
     | 
| 
       77 
     | 
    
         
            -
            piece.bottomside.to_s # => "S:-K"
         
     | 
| 
       78 
     | 
    
         
            -
            piece.oppositeside.to_s # => "S:-K"
         
     | 
| 
       79 
     | 
    
         
            -
            piece.promote.to_s # => "s:+-k"
         
     | 
| 
       80 
     | 
    
         
            -
            piece.unpromote.to_s # => "s:-k"
         
     | 
| 
       81 
     | 
    
         
            -
             
     | 
| 
       82 
     | 
    
         
            -
             
     | 
| 
       83 
     | 
    
         
            -
            # Shogi (Japanese chess)'s King, Sente
         
     | 
| 
       84 
     | 
    
         
            -
            piece = Sashite::GAN.parse("S:-K")
         
     | 
| 
       85 
     | 
    
         
            -
             
     | 
| 
       86 
     | 
    
         
            -
            piece.abbr.to_s # => "-K"
         
     | 
| 
       87 
     | 
    
         
            -
            piece.style # => "S"
         
     | 
| 
       88 
     | 
    
         
            -
            piece.topside? # => false
         
     | 
| 
       89 
     | 
    
         
            -
            piece.bottomside? # => true
         
     | 
| 
       90 
     | 
    
         
            -
            piece.to_s # => "S:-K"
         
     | 
| 
       91 
     | 
    
         
            -
            piece.topside.to_s # => "s:-k"
         
     | 
| 
       92 
     | 
    
         
            -
            piece.bottomside.to_s # => "S:-K"
         
     | 
| 
       93 
     | 
    
         
            -
            piece.oppositeside.to_s # => "s:-k"
         
     | 
| 
       94 
     | 
    
         
            -
            piece.promote.to_s # => "S:+-K"
         
     | 
| 
       95 
     | 
    
         
            -
            piece.unpromote.to_s # => "S:-K"
         
     | 
| 
       96 
     | 
    
         
            -
             
     | 
| 
       97 
     | 
    
         
            -
             
     | 
| 
       98 
     | 
    
         
            -
            # Shogi (Japanese chess)'s promoted Pawn, Sente
         
     | 
| 
       99 
     | 
    
         
            -
            piece = Sashite::GAN.parse("S:+P")
         
     | 
| 
       100 
     | 
    
         
            -
             
     | 
| 
       101 
     | 
    
         
            -
            piece.abbr.to_s # => "+P"
         
     | 
| 
       102 
     | 
    
         
            -
            piece.style # => "S"
         
     | 
| 
       103 
     | 
    
         
            -
            piece.topside? # => false
         
     | 
| 
       104 
     | 
    
         
            -
            piece.bottomside? # => true
         
     | 
| 
       105 
     | 
    
         
            -
            piece.to_s # => "S:+P"
         
     | 
| 
       106 
     | 
    
         
            -
            piece.topside.to_s # => "s:+p"
         
     | 
| 
       107 
     | 
    
         
            -
            piece.bottomside.to_s # => "S:+P"
         
     | 
| 
       108 
     | 
    
         
            -
            piece.oppositeside.to_s # => "s:+p"
         
     | 
| 
       109 
     | 
    
         
            -
            piece.promote.to_s # => "S:+P"
         
     | 
| 
       110 
     | 
    
         
            -
            piece.unpromote.to_s # => "S:P"
         
     | 
| 
       111 
     | 
    
         
            -
             
     | 
| 
       112 
     | 
    
         
            -
             
     | 
| 
       113 
     | 
    
         
            -
            # Xiangqi (Chinese chess)'s General, Red
         
     | 
| 
       114 
     | 
    
         
            -
            piece = Sashite::GAN.parse("X:-G")
         
     | 
| 
       115 
     | 
    
         
            -
             
     | 
| 
       116 
     | 
    
         
            -
            piece.abbr.to_s # => "-G"
         
     | 
| 
       117 
     | 
    
         
            -
            piece.style # => "X"
         
     | 
| 
       118 
     | 
    
         
            -
            piece.topside? # => false
         
     | 
| 
       119 
     | 
    
         
            -
            piece.bottomside? # => true
         
     | 
| 
       120 
     | 
    
         
            -
            piece.to_s # => "X:-G"
         
     | 
| 
       121 
     | 
    
         
            -
            piece.topside.to_s # => "x:-g"
         
     | 
| 
       122 
     | 
    
         
            -
            piece.bottomside.to_s # => "X:-G"
         
     | 
| 
       123 
     | 
    
         
            -
            piece.oppositeside.to_s # => "x:-g"
         
     | 
| 
       124 
     | 
    
         
            -
            piece.promote.to_s # => "X:+-G"
         
     | 
| 
       125 
     | 
    
         
            -
            piece.unpromote.to_s # => "X:-G"
         
     | 
| 
       126 
     | 
    
         
            -
             
     | 
| 
       127 
     | 
    
         
            -
             
     | 
| 
       128 
     | 
    
         
            -
            # Xiangqi (Chinese chess)'s Flying General, Red
         
     | 
| 
       129 
     | 
    
         
            -
            piece = Sashite::GAN.parse("X:+-G")
         
     | 
| 
       130 
     | 
    
         
            -
             
     | 
| 
       131 
     | 
    
         
            -
            piece.abbr.to_s # => "+-G"
         
     | 
| 
       132 
     | 
    
         
            -
            piece.style # => "X"
         
     | 
| 
       133 
     | 
    
         
            -
            piece.topside? # => false
         
     | 
| 
       134 
     | 
    
         
            -
            piece.bottomside? # => true
         
     | 
| 
       135 
     | 
    
         
            -
            piece.to_s # => "X:+-G"
         
     | 
| 
       136 
     | 
    
         
            -
            piece.topside.to_s # => "x:+-g"
         
     | 
| 
       137 
     | 
    
         
            -
            piece.bottomside.to_s # => "X:+-G"
         
     | 
| 
       138 
     | 
    
         
            -
            piece.oppositeside.to_s # => "x:+-g"
         
     | 
| 
       139 
     | 
    
         
            -
            piece.promote.to_s # => "X:+-G"
         
     | 
| 
       140 
     | 
    
         
            -
            piece.unpromote.to_s # => "X:-G"
         
     | 
| 
       141 
     | 
    
         
            -
             
     | 
| 
       142 
     | 
    
         
            -
             
     | 
| 
       143 
     | 
    
         
            -
            # Dai Dai Shogi (huge Japanese chess)'s Phoenix, Sente
         
     | 
| 
       144 
     | 
    
         
            -
            piece = Sashite::GAN.parse("DAI_DAI_SHOGI:PH")
         
     | 
| 
       145 
     | 
    
         
            -
             
     | 
| 
       146 
     | 
    
         
            -
            piece.abbr.to_s # => "PH"
         
     | 
| 
       147 
     | 
    
         
            -
            piece.style # => "DAI_DAI_SHOGI"
         
     | 
| 
       148 
     | 
    
         
            -
            piece.topside? # => false
         
     | 
| 
       149 
     | 
    
         
            -
            piece.bottomside? # => true
         
     | 
| 
       150 
     | 
    
         
            -
            piece.to_s # => "DAI_DAI_SHOGI:PH"
         
     | 
| 
       151 
     | 
    
         
            -
            piece.topside.to_s # => "dai_dai_shogi:ph"
         
     | 
| 
       152 
     | 
    
         
            -
            piece.bottomside.to_s # => "DAI_DAI_SHOGI:PH"
         
     | 
| 
       153 
     | 
    
         
            -
            piece.oppositeside.to_s # => "dai_dai_shogi:ph"
         
     | 
| 
       154 
     | 
    
         
            -
            piece.promote.to_s # => "DAI_DAI_SHOGI:+PH"
         
     | 
| 
       155 
     | 
    
         
            -
            piece.unpromote.to_s # => "DAI_DAI_SHOGI:PH"
         
     | 
| 
       156 
     | 
    
         
            -
             
     | 
| 
       157 
     | 
    
         
            -
             
     | 
| 
       158 
     | 
    
         
            -
            # A random FOO chess variant's promoted Z piece, Bottom-side
         
     | 
| 
       159 
     | 
    
         
            -
            piece = Sashite::GAN.parse("FOO:+Z")
         
     | 
| 
       160 
     | 
    
         
            -
             
     | 
| 
       161 
     | 
    
         
            -
            piece.abbr.to_s # => "+Z"
         
     | 
| 
       162 
     | 
    
         
            -
            piece.style # => "FOO"
         
     | 
| 
       163 
     | 
    
         
            -
            piece.topside? # => false
         
     | 
| 
       164 
     | 
    
         
            -
            piece.bottomside? # => true
         
     | 
| 
       165 
     | 
    
         
            -
            piece.to_s # => "FOO:+Z"
         
     | 
| 
       166 
     | 
    
         
            -
            piece.topside.to_s # => "foo:+z"
         
     | 
| 
       167 
     | 
    
         
            -
            piece.bottomside.to_s # => "FOO:+Z"
         
     | 
| 
       168 
     | 
    
         
            -
            piece.oppositeside.to_s # => "foo:+z"
         
     | 
| 
       169 
     | 
    
         
            -
            piece.promote.to_s # => "FOO:+Z"
         
     | 
| 
       170 
     | 
    
         
            -
            piece.unpromote.to_s # => "FOO:Z"
         
     | 
| 
      
 59 
     | 
    
         
            +
            require "sashite-gan"
         
     | 
| 
      
 60 
     | 
    
         
            +
             
     | 
| 
      
 61 
     | 
    
         
            +
            # Basic actor
         
     | 
| 
      
 62 
     | 
    
         
            +
            result = Sashite::Gan.parse("CHESS:K")
         
     | 
| 
      
 63 
     | 
    
         
            +
            # => { game_id: "CHESS", letter: "K" }
         
     | 
| 
      
 64 
     | 
    
         
            +
             
     | 
| 
      
 65 
     | 
    
         
            +
            # With piece prefix
         
     | 
| 
      
 66 
     | 
    
         
            +
            result = Sashite::Gan.parse("SHOGI:+P")
         
     | 
| 
      
 67 
     | 
    
         
            +
            # => { game_id: "SHOGI", letter: "P", prefix: "+" }
         
     | 
| 
      
 68 
     | 
    
         
            +
             
     | 
| 
      
 69 
     | 
    
         
            +
            # With piece suffix
         
     | 
| 
      
 70 
     | 
    
         
            +
            result = Sashite::Gan.parse("CHESS:K'")
         
     | 
| 
      
 71 
     | 
    
         
            +
            # => { game_id: "CHESS", letter: "K", suffix: "'" }
         
     | 
| 
      
 72 
     | 
    
         
            +
             
     | 
| 
      
 73 
     | 
    
         
            +
            # With both piece prefix and suffix
         
     | 
| 
      
 74 
     | 
    
         
            +
            result = Sashite::Gan.parse("SHOGI:+R'")
         
     | 
| 
      
 75 
     | 
    
         
            +
            # => { game_id: "SHOGI", letter: "R", prefix: "+", suffix: "'" }
         
     | 
| 
       171 
76 
     | 
    
         
             
            ```
         
     | 
| 
       172 
77 
     | 
    
         | 
| 
       173 
     | 
    
         
            -
             
     | 
| 
      
 78 
     | 
    
         
            +
            ### Safe Parsing
         
     | 
| 
      
 79 
     | 
    
         
            +
             
     | 
| 
      
 80 
     | 
    
         
            +
            Parse a GAN string without raising exceptions:
         
     | 
| 
      
 81 
     | 
    
         
            +
             
     | 
| 
      
 82 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 83 
     | 
    
         
            +
            require "sashite-gan"
         
     | 
| 
      
 84 
     | 
    
         
            +
             
     | 
| 
      
 85 
     | 
    
         
            +
            # Valid GAN string
         
     | 
| 
      
 86 
     | 
    
         
            +
            result = Sashite::Gan.safe_parse("CHESS:K'")
         
     | 
| 
      
 87 
     | 
    
         
            +
            # => { game_id: "CHESS", letter: "K", suffix: "'" }
         
     | 
| 
      
 88 
     | 
    
         
            +
             
     | 
| 
      
 89 
     | 
    
         
            +
            # Invalid GAN string
         
     | 
| 
      
 90 
     | 
    
         
            +
            result = Sashite::Gan.safe_parse("invalid gan string")
         
     | 
| 
      
 91 
     | 
    
         
            +
            # => nil
         
     | 
| 
      
 92 
     | 
    
         
            +
            ```
         
     | 
| 
       174 
93 
     | 
    
         | 
| 
       175 
     | 
    
         
            -
             
     | 
| 
      
 94 
     | 
    
         
            +
            ### Creating GAN Strings
         
     | 
| 
      
 95 
     | 
    
         
            +
             
     | 
| 
      
 96 
     | 
    
         
            +
            Convert actor components into a GAN string:
         
     | 
| 
      
 97 
     | 
    
         
            +
             
     | 
| 
      
 98 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 99 
     | 
    
         
            +
            require "sashite-gan"
         
     | 
| 
      
 100 
     | 
    
         
            +
             
     | 
| 
      
 101 
     | 
    
         
            +
            # Basic actor
         
     | 
| 
      
 102 
     | 
    
         
            +
            Sashite::Gan.dump(game_id: "CHESS", letter: "K")
         
     | 
| 
      
 103 
     | 
    
         
            +
            # => "CHESS:K"
         
     | 
| 
      
 104 
     | 
    
         
            +
             
     | 
| 
      
 105 
     | 
    
         
            +
            # With piece prefix
         
     | 
| 
      
 106 
     | 
    
         
            +
            Sashite::Gan.dump(game_id: "SHOGI", letter: "P", prefix: "+")
         
     | 
| 
      
 107 
     | 
    
         
            +
            # => "SHOGI:+P"
         
     | 
| 
      
 108 
     | 
    
         
            +
             
     | 
| 
      
 109 
     | 
    
         
            +
            # With piece suffix
         
     | 
| 
      
 110 
     | 
    
         
            +
            Sashite::Gan.dump(game_id: "CHESS", letter: "K", suffix: "'")
         
     | 
| 
      
 111 
     | 
    
         
            +
            # => "CHESS:K'"
         
     | 
| 
      
 112 
     | 
    
         
            +
             
     | 
| 
      
 113 
     | 
    
         
            +
            # With both piece prefix and suffix
         
     | 
| 
      
 114 
     | 
    
         
            +
            Sashite::Gan.dump(game_id: "SHOGI", letter: "R", prefix: "+", suffix: "'")
         
     | 
| 
      
 115 
     | 
    
         
            +
            # => "SHOGI:+R'"
         
     | 
| 
      
 116 
     | 
    
         
            +
            ```
         
     | 
| 
      
 117 
     | 
    
         
            +
             
     | 
| 
      
 118 
     | 
    
         
            +
            ### Validation
         
     | 
| 
      
 119 
     | 
    
         
            +
             
     | 
| 
      
 120 
     | 
    
         
            +
            Check if a string is valid GAN notation:
         
     | 
| 
      
 121 
     | 
    
         
            +
             
     | 
| 
      
 122 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 123 
     | 
    
         
            +
            require "sashite-gan"
         
     | 
| 
      
 124 
     | 
    
         
            +
             
     | 
| 
      
 125 
     | 
    
         
            +
            Sashite::Gan.valid?("CHESS:K")      # => true
         
     | 
| 
      
 126 
     | 
    
         
            +
            Sashite::Gan.valid?("SHOGI:+P")     # => true
         
     | 
| 
      
 127 
     | 
    
         
            +
            Sashite::Gan.valid?("CHESS:K'")     # => true
         
     | 
| 
      
 128 
     | 
    
         
            +
            Sashite::Gan.valid?("chess:k")      # => true
         
     | 
| 
      
 129 
     | 
    
         
            +
             
     | 
| 
      
 130 
     | 
    
         
            +
            Sashite::Gan.valid?("")             # => false
         
     | 
| 
      
 131 
     | 
    
         
            +
            Sashite::Gan.valid?("CHESS:k")      # => false (mismatched casing)
         
     | 
| 
      
 132 
     | 
    
         
            +
            Sashite::Gan.valid?("CHESS::K")     # => false
         
     | 
| 
      
 133 
     | 
    
         
            +
            Sashite::Gan.valid?("CHESS-K")      # => false
         
     | 
| 
      
 134 
     | 
    
         
            +
            ```
         
     | 
| 
      
 135 
     | 
    
         
            +
             
     | 
| 
      
 136 
     | 
    
         
            +
            ## Casing Rules
         
     | 
| 
      
 137 
     | 
    
         
            +
             
     | 
| 
      
 138 
     | 
    
         
            +
            The casing of the game identifier must match the piece letter casing:
         
     | 
| 
      
 139 
     | 
    
         
            +
             
     | 
| 
      
 140 
     | 
    
         
            +
            - **Uppercase** game IDs must have **uppercase** piece letters for the first player
         
     | 
| 
      
 141 
     | 
    
         
            +
            - **Lowercase** game IDs must have **lowercase** piece letters for the second player
         
     | 
| 
      
 142 
     | 
    
         
            +
             
     | 
| 
      
 143 
     | 
    
         
            +
            This ensures consistency with the FEEN specification's third field.
         
     | 
| 
      
 144 
     | 
    
         
            +
             
     | 
| 
      
 145 
     | 
    
         
            +
            ## Examples
         
     | 
| 
      
 146 
     | 
    
         
            +
             
     | 
| 
      
 147 
     | 
    
         
            +
            ### Chess Pieces
         
     | 
| 
      
 148 
     | 
    
         
            +
             
     | 
| 
      
 149 
     | 
    
         
            +
            | PNN   | GAN (First Player) | GAN (Second Player) |
         
     | 
| 
      
 150 
     | 
    
         
            +
            |-------|--------------------|--------------------|
         
     | 
| 
      
 151 
     | 
    
         
            +
            | `K'`  | `CHESS:K'`         | `chess:k'`         |
         
     | 
| 
      
 152 
     | 
    
         
            +
            | `Q`   | `CHESS:Q`          | `chess:q`          |
         
     | 
| 
      
 153 
     | 
    
         
            +
            | `R`   | `CHESS:R`          | `chess:r`          |
         
     | 
| 
      
 154 
     | 
    
         
            +
            | `B`   | `CHESS:B`          | `chess:b`          |
         
     | 
| 
      
 155 
     | 
    
         
            +
            | `N`   | `CHESS:N`          | `chess:n`          |
         
     | 
| 
      
 156 
     | 
    
         
            +
            | `P`   | `CHESS:P`          | `chess:p`          |
         
     | 
| 
      
 157 
     | 
    
         
            +
             
     | 
| 
      
 158 
     | 
    
         
            +
            ### Disambiguated Collisions
         
     | 
| 
      
 159 
     | 
    
         
            +
             
     | 
| 
      
 160 
     | 
    
         
            +
            These examples show how GAN resolves ambiguities between pieces that would have identical PNN representation:
         
     | 
| 
      
 161 
     | 
    
         
            +
             
     | 
| 
      
 162 
     | 
    
         
            +
            | Description | PNN   | GAN                 |
         
     | 
| 
      
 163 
     | 
    
         
            +
            |-------------|-------|---------------------|
         
     | 
| 
      
 164 
     | 
    
         
            +
            | Chess Rook (white) | `R`   | `CHESS:R`           |
         
     | 
| 
      
 165 
     | 
    
         
            +
            | Makruk Rook (white) | `R`   | `MAKRUK:R`          |
         
     | 
| 
      
 166 
     | 
    
         
            +
            | Shogi Rook (sente) | `R`   | `SHOGI:R`           |
         
     | 
| 
      
 167 
     | 
    
         
            +
            | Promoted Shogi Rook (sente) | `+R`  | `SHOGI:+R`          |
         
     | 
| 
      
 168 
     | 
    
         
            +
             
     | 
| 
      
 169 
     | 
    
         
            +
            ## Documentation
         
     | 
| 
      
 170 
     | 
    
         
            +
             
     | 
| 
      
 171 
     | 
    
         
            +
            - [Official GAN Specification](https://sashite.dev/documents/gan/1.0.0/)
         
     | 
| 
      
 172 
     | 
    
         
            +
            - [API Documentation](https://rubydoc.info/github/sashite/gan.rb/main)
         
     | 
| 
      
 173 
     | 
    
         
            +
             
     | 
| 
      
 174 
     | 
    
         
            +
            ## License
         
     | 
| 
       176 
175 
     | 
    
         | 
| 
       177 
     | 
    
         
            -
             
     | 
| 
      
 176 
     | 
    
         
            +
            The [gem](https://rubygems.org/gems/sashite-gan) is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
         
     | 
| 
       178 
177 
     | 
    
         | 
| 
       179 
     | 
    
         
            -
             
     | 
| 
      
 178 
     | 
    
         
            +
            ## About Sashité
         
     | 
| 
       180 
179 
     | 
    
         | 
| 
       181 
     | 
    
         
            -
             
     | 
| 
      
 180 
     | 
    
         
            +
            This project is maintained by [Sashité](https://sashite.com/) — promoting chess variants and sharing the beauty of Chinese, Japanese, and Western chess cultures.
         
     | 
| 
         @@ -0,0 +1,94 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            require "pnn"
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            module Sashite
         
     | 
| 
      
 6 
     | 
    
         
            +
              module Gan
         
     | 
| 
      
 7 
     | 
    
         
            +
                # Serializes actor components into GAN (General Actor Notation) strings.
         
     | 
| 
      
 8 
     | 
    
         
            +
                #
         
     | 
| 
      
 9 
     | 
    
         
            +
                # The dumper transforms piece data and game identifiers into properly
         
     | 
| 
      
 10 
     | 
    
         
            +
                # formatted GAN strings, ensuring consistency between game ID casing
         
     | 
| 
      
 11 
     | 
    
         
            +
                # and piece letter casing according to the GAN specification.
         
     | 
| 
      
 12 
     | 
    
         
            +
                #
         
     | 
| 
      
 13 
     | 
    
         
            +
                # According to the specification, game IDs must be either all uppercase
         
     | 
| 
      
 14 
     | 
    
         
            +
                # or all lowercase, and their casing must match the casing of the piece letter.
         
     | 
| 
      
 15 
     | 
    
         
            +
                class Dumper
         
     | 
| 
      
 16 
     | 
    
         
            +
                  # Pattern for validating game identifiers - must be all uppercase OR all lowercase
         
     | 
| 
      
 17 
     | 
    
         
            +
                  GAME_ID_PATTERN = /\A([A-Z]+|[a-z]+)\z/
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                  # Error message templates
         
     | 
| 
      
 20 
     | 
    
         
            +
                  INVALID_GAME_ID_ERROR = "Game ID must be a non-empty string containing only ASCII letters and must be either all uppercase or all lowercase: %s"
         
     | 
| 
      
 21 
     | 
    
         
            +
                  CASING_MISMATCH_ERROR = "Game ID casing (%s) must match piece letter casing (%s)"
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                  # Serializes actor components into a GAN string
         
     | 
| 
      
 24 
     | 
    
         
            +
                  #
         
     | 
| 
      
 25 
     | 
    
         
            +
                  # @param game_id [String] The game identifier (e.g., "CHESS", "shogi")
         
     | 
| 
      
 26 
     | 
    
         
            +
                  # @param piece_params [Hash] Piece parameters as accepted by Pnn.dump:
         
     | 
| 
      
 27 
     | 
    
         
            +
                  #   @option piece_params [String] :letter The single ASCII letter identifier (required)
         
     | 
| 
      
 28 
     | 
    
         
            +
                  #   @option piece_params [String, nil] :prefix Optional prefix modifier for the piece ("+", "-")
         
     | 
| 
      
 29 
     | 
    
         
            +
                  #   @option piece_params [String, nil] :suffix Optional suffix modifier for the piece ("'")
         
     | 
| 
      
 30 
     | 
    
         
            +
                  # @return [String] A properly formatted GAN notation string (e.g., "CHESS:K'")
         
     | 
| 
      
 31 
     | 
    
         
            +
                  # @raise [ArgumentError] If game_id is invalid or casing is inconsistent with piece letter
         
     | 
| 
      
 32 
     | 
    
         
            +
                  # @example Create a GAN string for a white chess king with castling rights
         
     | 
| 
      
 33 
     | 
    
         
            +
                  #   Dumper.dump(game_id: "CHESS", letter: "K", suffix: "'")
         
     | 
| 
      
 34 
     | 
    
         
            +
                  #   # => "CHESS:K'"
         
     | 
| 
      
 35 
     | 
    
         
            +
                  # @example Create a GAN string for a promoted shogi pawn
         
     | 
| 
      
 36 
     | 
    
         
            +
                  #   Dumper.dump(game_id: "SHOGI", letter: "P", prefix: "+")
         
     | 
| 
      
 37 
     | 
    
         
            +
                  #   # => "SHOGI:+P"
         
     | 
| 
      
 38 
     | 
    
         
            +
                  def self.dump(game_id:, **piece_params)
         
     | 
| 
      
 39 
     | 
    
         
            +
                    game_id = String(game_id)
         
     | 
| 
      
 40 
     | 
    
         
            +
                    validate_game_id!(game_id)
         
     | 
| 
      
 41 
     | 
    
         
            +
             
     | 
| 
      
 42 
     | 
    
         
            +
                    # Build the piece string using the PNN gem
         
     | 
| 
      
 43 
     | 
    
         
            +
                    pnn_string = ::Pnn.dump(**piece_params)
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
                    # Verify casing consistency
         
     | 
| 
      
 46 
     | 
    
         
            +
                    validate_casing_consistency!(game_id, pnn_string)
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
                    "#{game_id}:#{pnn_string}"
         
     | 
| 
      
 49 
     | 
    
         
            +
                  end
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
                  # @api private
         
     | 
| 
      
 52 
     | 
    
         
            +
                  # Validates that the game_id contains only ASCII letters
         
     | 
| 
      
 53 
     | 
    
         
            +
                  #
         
     | 
| 
      
 54 
     | 
    
         
            +
                  # @param game_id [String] The game identifier to validate
         
     | 
| 
      
 55 
     | 
    
         
            +
                  # @return [void]
         
     | 
| 
      
 56 
     | 
    
         
            +
                  # @raise [ArgumentError] If game_id contains non-letter characters
         
     | 
| 
      
 57 
     | 
    
         
            +
                  def self.validate_game_id!(game_id)
         
     | 
| 
      
 58 
     | 
    
         
            +
                    return if game_id.match?(GAME_ID_PATTERN)
         
     | 
| 
      
 59 
     | 
    
         
            +
             
     | 
| 
      
 60 
     | 
    
         
            +
                    raise ::ArgumentError, format(INVALID_GAME_ID_ERROR, game_id)
         
     | 
| 
      
 61 
     | 
    
         
            +
                  end
         
     | 
| 
      
 62 
     | 
    
         
            +
                  private_class_method :validate_game_id!
         
     | 
| 
      
 63 
     | 
    
         
            +
             
     | 
| 
      
 64 
     | 
    
         
            +
                  # @api private
         
     | 
| 
      
 65 
     | 
    
         
            +
                  # Validates that the casing of the game_id is consistent with the piece letter
         
     | 
| 
      
 66 
     | 
    
         
            +
                  #
         
     | 
| 
      
 67 
     | 
    
         
            +
                  # According to GAN specification, if game_id is uppercase, piece letter must be uppercase,
         
     | 
| 
      
 68 
     | 
    
         
            +
                  # and if game_id is lowercase, piece letter must be lowercase.
         
     | 
| 
      
 69 
     | 
    
         
            +
                  #
         
     | 
| 
      
 70 
     | 
    
         
            +
                  # @param game_id [String] The game identifier
         
     | 
| 
      
 71 
     | 
    
         
            +
                  # @param pnn_string [String] The PNN string
         
     | 
| 
      
 72 
     | 
    
         
            +
                  # @return [void]
         
     | 
| 
      
 73 
     | 
    
         
            +
                  # @raise [ArgumentError] If casing is inconsistent
         
     | 
| 
      
 74 
     | 
    
         
            +
                  def self.validate_casing_consistency!(game_id, pnn_string)
         
     | 
| 
      
 75 
     | 
    
         
            +
                    return if casing_consistent?(game_id, pnn_string)
         
     | 
| 
      
 76 
     | 
    
         
            +
             
     | 
| 
      
 77 
     | 
    
         
            +
                    raise ::ArgumentError, format(CASING_MISMATCH_ERROR, game_id, pnn_string)
         
     | 
| 
      
 78 
     | 
    
         
            +
                  end
         
     | 
| 
      
 79 
     | 
    
         
            +
                  private_class_method :validate_casing_consistency!
         
     | 
| 
      
 80 
     | 
    
         
            +
             
     | 
| 
      
 81 
     | 
    
         
            +
                  # @api private
         
     | 
| 
      
 82 
     | 
    
         
            +
                  # Verifies that the casing of the game_id matches the casing of the piece letter
         
     | 
| 
      
 83 
     | 
    
         
            +
                  #
         
     | 
| 
      
 84 
     | 
    
         
            +
                  # @param game_id [String] The game identifier
         
     | 
| 
      
 85 
     | 
    
         
            +
                  # @param pnn_string [String] The PNN string
         
     | 
| 
      
 86 
     | 
    
         
            +
                  # @return [Boolean] True if casing is consistent
         
     | 
| 
      
 87 
     | 
    
         
            +
                  def self.casing_consistent?(game_id, pnn_string)
         
     | 
| 
      
 88 
     | 
    
         
            +
                    # Both must be uppercase or both must be lowercase
         
     | 
| 
      
 89 
     | 
    
         
            +
                    (game_id == game_id.upcase) == (pnn_string == pnn_string.upcase)
         
     | 
| 
      
 90 
     | 
    
         
            +
                  end
         
     | 
| 
      
 91 
     | 
    
         
            +
                  private_class_method :casing_consistent?
         
     | 
| 
      
 92 
     | 
    
         
            +
                end
         
     | 
| 
      
 93 
     | 
    
         
            +
              end
         
     | 
| 
      
 94 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/sashite/gan/parser.rb
    CHANGED
    
    | 
         @@ -1,30 +1,57 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            # frozen_string_literal: true
         
     | 
| 
       2 
2 
     | 
    
         | 
| 
       3 
     | 
    
         
            -
             
     | 
| 
       4 
     | 
    
         
            -
            require_relative 'piece'
         
     | 
| 
      
 3 
     | 
    
         
            +
            require "pnn"
         
     | 
| 
       5 
4 
     | 
    
         | 
| 
       6 
5 
     | 
    
         
             
            module Sashite
         
     | 
| 
       7 
     | 
    
         
            -
              module  
     | 
| 
       8 
     | 
    
         
            -
                #  
     | 
| 
       9 
     | 
    
         
            -
                 
     | 
| 
       10 
     | 
    
         
            -
                   
     | 
| 
       11 
     | 
    
         
            -
             
     | 
| 
       12 
     | 
    
         
            -
             
     | 
| 
       13 
     | 
    
         
            -
             
     | 
| 
       14 
     | 
    
         
            -
             
     | 
| 
       15 
     | 
    
         
            -
             
     | 
| 
       16 
     | 
    
         
            -
             
     | 
| 
       17 
     | 
    
         
            -
             
     | 
| 
       18 
     | 
    
         
            -
             
     | 
| 
       19 
     | 
    
         
            -
             
     | 
| 
       20 
     | 
    
         
            -
             
     | 
| 
       21 
     | 
    
         
            -
                    )
         
     | 
| 
      
 6 
     | 
    
         
            +
              module Gan
         
     | 
| 
      
 7 
     | 
    
         
            +
                # Parses GAN strings into their component parts
         
     | 
| 
      
 8 
     | 
    
         
            +
                class Parser
         
     | 
| 
      
 9 
     | 
    
         
            +
                  # GAN regex pattern for parsing
         
     | 
| 
      
 10 
     | 
    
         
            +
                  PATTERN = /\A(?<game_id>[a-zA-Z]+):(?<pnn_part>[-+]?[a-zA-Z][']?)\z/
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                  # Parse a GAN string into its components
         
     | 
| 
      
 13 
     | 
    
         
            +
                  #
         
     | 
| 
      
 14 
     | 
    
         
            +
                  # @param gan_string [String] The GAN string to parse
         
     | 
| 
      
 15 
     | 
    
         
            +
                  # @return [Hash] Hash containing the parsed components
         
     | 
| 
      
 16 
     | 
    
         
            +
                  # @raise [ArgumentError] If the GAN string is invalid
         
     | 
| 
      
 17 
     | 
    
         
            +
                  def self.parse(gan_string)
         
     | 
| 
      
 18 
     | 
    
         
            +
                    gan_string = String(gan_string)
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
                    matches = PATTERN.match(gan_string)
         
     | 
| 
      
 21 
     | 
    
         
            +
                    raise ArgumentError, "Invalid GAN string: #{gan_string}" if matches.nil?
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                    game_id = matches[:game_id]
         
     | 
| 
      
 24 
     | 
    
         
            +
                    pnn_part = matches[:pnn_part]
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
                    # Parse the PNN part using the PNN gem
         
     | 
| 
      
 27 
     | 
    
         
            +
                    pnn_result = Pnn.parse(pnn_part)
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                    # Verify casing consistency
         
     | 
| 
      
 30 
     | 
    
         
            +
                    unless casing_consistent?(game_id, pnn_result[:letter])
         
     | 
| 
      
 31 
     | 
    
         
            +
                      raise ArgumentError, "Game ID casing (#{game_id}) must match piece letter casing (#{pnn_result[:letter]})"
         
     | 
| 
      
 32 
     | 
    
         
            +
                    end
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
                    # Merge the game_id with the piece parameters for a flatter structure
         
     | 
| 
      
 35 
     | 
    
         
            +
                    { game_id: game_id }.merge(pnn_result)
         
     | 
| 
       22 
36 
     | 
    
         
             
                  end
         
     | 
| 
       23 
37 
     | 
    
         | 
| 
       24 
     | 
    
         
            -
                   
     | 
| 
       25 
     | 
    
         
            -
             
     | 
| 
      
 38 
     | 
    
         
            +
                  # Safely parse a GAN string without raising exceptions
         
     | 
| 
      
 39 
     | 
    
         
            +
                  #
         
     | 
| 
      
 40 
     | 
    
         
            +
                  # @param gan_string [String] The GAN string to parse
         
     | 
| 
      
 41 
     | 
    
         
            +
                  # @return [Hash, nil] Hash containing the parsed components or nil if invalid
         
     | 
| 
      
 42 
     | 
    
         
            +
                  def self.safe_parse(gan_string)
         
     | 
| 
      
 43 
     | 
    
         
            +
                    parse(gan_string)
         
     | 
| 
      
 44 
     | 
    
         
            +
                  rescue ArgumentError
         
     | 
| 
      
 45 
     | 
    
         
            +
                    nil
         
     | 
| 
      
 46 
     | 
    
         
            +
                  end
         
     | 
| 
       26 
47 
     | 
    
         | 
| 
       27 
     | 
    
         
            -
             
     | 
| 
      
 48 
     | 
    
         
            +
                  # Verifies that the casing of the game_id matches the casing of the piece letter
         
     | 
| 
      
 49 
     | 
    
         
            +
                  #
         
     | 
| 
      
 50 
     | 
    
         
            +
                  # @param game_id [String] The game identifier
         
     | 
| 
      
 51 
     | 
    
         
            +
                  # @param letter [String] The piece letter
         
     | 
| 
      
 52 
     | 
    
         
            +
                  # @return [Boolean] True if casing is consistent
         
     | 
| 
      
 53 
     | 
    
         
            +
                  def self.casing_consistent?(game_id, letter)
         
     | 
| 
      
 54 
     | 
    
         
            +
                    (game_id == game_id.upcase) == (letter == letter.upcase)
         
     | 
| 
       28 
55 
     | 
    
         
             
                  end
         
     | 
| 
       29 
56 
     | 
    
         
             
                end
         
     | 
| 
       30 
57 
     | 
    
         
             
              end
         
     | 
| 
         @@ -0,0 +1,23 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            require "pnn"
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            module Sashite
         
     | 
| 
      
 6 
     | 
    
         
            +
              module Gan
         
     | 
| 
      
 7 
     | 
    
         
            +
                # Validates GAN strings
         
     | 
| 
      
 8 
     | 
    
         
            +
                class Validator
         
     | 
| 
      
 9 
     | 
    
         
            +
                  # GAN regex pattern for validation
         
     | 
| 
      
 10 
     | 
    
         
            +
                  PATTERN = /\A([A-Z]+:[-+]?[A-Z][']?|[a-z]+:[-+]?[a-z][']?)\z/
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                  # Validates if the given string is a valid GAN string
         
     | 
| 
      
 13 
     | 
    
         
            +
                  #
         
     | 
| 
      
 14 
     | 
    
         
            +
                  # @param gan_string [String] The GAN string to validate
         
     | 
| 
      
 15 
     | 
    
         
            +
                  # @return [Boolean] True if the string is a valid GAN string
         
     | 
| 
      
 16 
     | 
    
         
            +
                  def self.valid?(gan_string)
         
     | 
| 
      
 17 
     | 
    
         
            +
                    return false unless gan_string.is_a?(String)
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                    PATTERN.match?(gan_string)
         
     | 
| 
      
 20 
     | 
    
         
            +
                  end
         
     | 
| 
      
 21 
     | 
    
         
            +
                end
         
     | 
| 
      
 22 
     | 
    
         
            +
              end
         
     | 
| 
      
 23 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/sashite/gan.rb
    CHANGED
    
    | 
         @@ -1,49 +1,76 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            # frozen_string_literal: true
         
     | 
| 
       2 
2 
     | 
    
         | 
| 
       3 
     | 
    
         
            -
            require_relative  
     | 
| 
      
 3 
     | 
    
         
            +
            require_relative File.join("gan", "dumper")
         
     | 
| 
      
 4 
     | 
    
         
            +
            require_relative File.join("gan", "parser")
         
     | 
| 
      
 5 
     | 
    
         
            +
            require_relative File.join("gan", "validator")
         
     | 
| 
       4 
6 
     | 
    
         | 
| 
       5 
7 
     | 
    
         
             
            module Sashite
         
     | 
| 
       6 
     | 
    
         
            -
              #  
     | 
| 
      
 8 
     | 
    
         
            +
              # This module provides a Ruby interface for serialization and
         
     | 
| 
      
 9 
     | 
    
         
            +
              # deserialization of game actors in GAN format.
         
     | 
| 
       7 
10 
     | 
    
         
             
              #
         
     | 
| 
       8 
     | 
    
         
            -
              #  
     | 
| 
       9 
     | 
    
         
            -
               
     | 
| 
       10 
     | 
    
         
            -
             
     | 
| 
       11 
     | 
    
         
            -
             
     | 
| 
       12 
     | 
    
         
            -
             
     | 
| 
       13 
     | 
    
         
            -
             
     | 
| 
       14 
     | 
    
         
            -
                #  
     | 
| 
       15 
     | 
    
         
            -
                #   GAN.parse("C:R")
         
     | 
| 
       16 
     | 
    
         
            -
                #
         
     | 
| 
       17 
     | 
    
         
            -
                # @example Chess (Western chess)'s King, Black
         
     | 
| 
       18 
     | 
    
         
            -
                #   GAN.parse("c:-k")
         
     | 
| 
       19 
     | 
    
         
            -
                #
         
     | 
| 
       20 
     | 
    
         
            -
                # @example Makruk (Thai chess)'s Bishop, White
         
     | 
| 
       21 
     | 
    
         
            -
                #   GAN.parse("M:B")
         
     | 
| 
       22 
     | 
    
         
            -
                #
         
     | 
| 
       23 
     | 
    
         
            -
                # @example Shogi (Japanese chess)'s King, Gote
         
     | 
| 
       24 
     | 
    
         
            -
                #   GAN.parse("s:-k")
         
     | 
| 
       25 
     | 
    
         
            -
                #
         
     | 
| 
       26 
     | 
    
         
            -
                # @example Shogi (Japanese chess)'s King, Sente
         
     | 
| 
       27 
     | 
    
         
            -
                #   GAN.parse("S:-K")
         
     | 
| 
       28 
     | 
    
         
            -
                #
         
     | 
| 
       29 
     | 
    
         
            -
                # @example Shogi (Japanese chess)'s promoted Pawn, Sente
         
     | 
| 
       30 
     | 
    
         
            -
                #   GAN.parse("S:+P")
         
     | 
| 
      
 11 
     | 
    
         
            +
              # GAN (General Actor Notation) defines a consistent and rule-agnostic
         
     | 
| 
      
 12 
     | 
    
         
            +
              # format for representing game actors in abstract strategy board games,
         
     | 
| 
      
 13 
     | 
    
         
            +
              # building upon Piece Name Notation (PNN).
         
     | 
| 
      
 14 
     | 
    
         
            +
              #
         
     | 
| 
      
 15 
     | 
    
         
            +
              # @see https://sashite.dev/documents/gan/1.0.0/
         
     | 
| 
      
 16 
     | 
    
         
            +
              module Gan
         
     | 
| 
      
 17 
     | 
    
         
            +
                # Serializes an actor into a GAN string.
         
     | 
| 
       31 
18 
     | 
    
         
             
                #
         
     | 
| 
       32 
     | 
    
         
            -
                # @ 
     | 
| 
       33 
     | 
    
         
            -
                # 
     | 
| 
      
 19 
     | 
    
         
            +
                # @param game_id [String] The game identifier
         
     | 
| 
      
 20 
     | 
    
         
            +
                # @param piece_params [Hash] Piece parameters as accepted by Pnn.dump
         
     | 
| 
      
 21 
     | 
    
         
            +
                # @option piece_params [String] :letter The single ASCII letter identifier (required)
         
     | 
| 
      
 22 
     | 
    
         
            +
                # @option piece_params [String, nil] :prefix Optional prefix modifier for the piece ("+", "-")
         
     | 
| 
      
 23 
     | 
    
         
            +
                # @option piece_params [String, nil] :suffix Optional suffix modifier for the piece ("'")
         
     | 
| 
      
 24 
     | 
    
         
            +
                # @return [String] GAN notation string
         
     | 
| 
      
 25 
     | 
    
         
            +
                # @raise [ArgumentError] If any parameter is invalid
         
     | 
| 
      
 26 
     | 
    
         
            +
                # @example
         
     | 
| 
      
 27 
     | 
    
         
            +
                #   Sashite::Gan.dump(game_id: "CHESS", letter: "K", suffix: "'")
         
     | 
| 
      
 28 
     | 
    
         
            +
                #   # => "CHESS:K'"
         
     | 
| 
      
 29 
     | 
    
         
            +
                def self.dump(game_id:, **piece_params)
         
     | 
| 
      
 30 
     | 
    
         
            +
                  Dumper.dump(game_id:, **piece_params)
         
     | 
| 
      
 31 
     | 
    
         
            +
                end
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                # Parses a GAN string into its component parts.
         
     | 
| 
       34 
34 
     | 
    
         
             
                #
         
     | 
| 
       35 
     | 
    
         
            -
                # @ 
     | 
| 
       36 
     | 
    
         
            -
                # 
     | 
| 
      
 35 
     | 
    
         
            +
                # @param gan_string [String] GAN notation string
         
     | 
| 
      
 36 
     | 
    
         
            +
                # @return [Hash] Hash containing the parsed actor data with the following keys:
         
     | 
| 
      
 37 
     | 
    
         
            +
                #   - :game_id [String] - The game identifier
         
     | 
| 
      
 38 
     | 
    
         
            +
                #   - :letter [String] - The base letter identifier
         
     | 
| 
      
 39 
     | 
    
         
            +
                #   - :prefix [String, nil] - The prefix modifier if present
         
     | 
| 
      
 40 
     | 
    
         
            +
                #   - :suffix [String, nil] - The suffix modifier if present
         
     | 
| 
      
 41 
     | 
    
         
            +
                # @raise [ArgumentError] If the GAN string is invalid
         
     | 
| 
      
 42 
     | 
    
         
            +
                # @example
         
     | 
| 
      
 43 
     | 
    
         
            +
                #   Sashite::Gan.parse("CHESS:K'")
         
     | 
| 
      
 44 
     | 
    
         
            +
                #   # => { game_id: "CHESS", letter: "K", suffix: "'" }
         
     | 
| 
      
 45 
     | 
    
         
            +
                def self.parse(gan_string)
         
     | 
| 
      
 46 
     | 
    
         
            +
                  Parser.parse(gan_string)
         
     | 
| 
      
 47 
     | 
    
         
            +
                end
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
                # Safely parses a GAN string into its component parts without raising exceptions.
         
     | 
| 
       37 
50 
     | 
    
         
             
                #
         
     | 
| 
       38 
     | 
    
         
            -
                # @ 
     | 
| 
       39 
     | 
    
         
            -
                # 
     | 
| 
      
 51 
     | 
    
         
            +
                # @param gan_string [String] GAN notation string
         
     | 
| 
      
 52 
     | 
    
         
            +
                # @return [Hash, nil] Hash containing the parsed actor data or nil if parsing fails
         
     | 
| 
      
 53 
     | 
    
         
            +
                # @example
         
     | 
| 
      
 54 
     | 
    
         
            +
                #   # Valid GAN string
         
     | 
| 
      
 55 
     | 
    
         
            +
                #   Sashite::Gan.safe_parse("CHESS:K'")
         
     | 
| 
      
 56 
     | 
    
         
            +
                #   # => { game_id: "CHESS", letter: "K", suffix: "'" }
         
     | 
| 
       40 
57 
     | 
    
         
             
                #
         
     | 
| 
       41 
     | 
    
         
            -
                #  
     | 
| 
       42 
     | 
    
         
            -
                #    
     | 
| 
      
 58 
     | 
    
         
            +
                #   # Invalid GAN string
         
     | 
| 
      
 59 
     | 
    
         
            +
                #   Sashite::Gan.safe_parse("invalid")
         
     | 
| 
      
 60 
     | 
    
         
            +
                #   # => nil
         
     | 
| 
      
 61 
     | 
    
         
            +
                def self.safe_parse(gan_string)
         
     | 
| 
      
 62 
     | 
    
         
            +
                  Parser.safe_parse(gan_string)
         
     | 
| 
      
 63 
     | 
    
         
            +
                end
         
     | 
| 
      
 64 
     | 
    
         
            +
             
     | 
| 
      
 65 
     | 
    
         
            +
                # Validates if the given string is a valid GAN string
         
     | 
| 
       43 
66 
     | 
    
         
             
                #
         
     | 
| 
       44 
     | 
    
         
            -
                # @ 
     | 
| 
       45 
     | 
    
         
            -
                 
     | 
| 
       46 
     | 
    
         
            -
             
     | 
| 
      
 67 
     | 
    
         
            +
                # @param gan_string [String] GAN string to validate
         
     | 
| 
      
 68 
     | 
    
         
            +
                # @return [Boolean] True if the string is a valid GAN string
         
     | 
| 
      
 69 
     | 
    
         
            +
                # @example
         
     | 
| 
      
 70 
     | 
    
         
            +
                #   Sashite::Gan.valid?("CHESS:K'") # => true
         
     | 
| 
      
 71 
     | 
    
         
            +
                #   Sashite::Gan.valid?("invalid") # => false
         
     | 
| 
      
 72 
     | 
    
         
            +
                def self.valid?(gan_string)
         
     | 
| 
      
 73 
     | 
    
         
            +
                  Validator.valid?(gan_string)
         
     | 
| 
       47 
74 
     | 
    
         
             
                end
         
     | 
| 
       48 
75 
     | 
    
         
             
              end
         
     | 
| 
       49 
76 
     | 
    
         
             
            end
         
     | 
    
        data/lib/sashite-gan.rb
    CHANGED
    
    
    
        metadata
    CHANGED
    
    | 
         @@ -1,114 +1,32 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            --- !ruby/object:Gem::Specification
         
     | 
| 
       2 
2 
     | 
    
         
             
            name: sashite-gan
         
     | 
| 
       3 
3 
     | 
    
         
             
            version: !ruby/object:Gem::Version
         
     | 
| 
       4 
     | 
    
         
            -
              version:  
     | 
| 
      
 4 
     | 
    
         
            +
              version: 3.0.0
         
     | 
| 
       5 
5 
     | 
    
         
             
            platform: ruby
         
     | 
| 
       6 
6 
     | 
    
         
             
            authors:
         
     | 
| 
       7 
7 
     | 
    
         
             
            - Cyril Kato
         
     | 
| 
       8 
     | 
    
         
            -
            autorequire: 
         
     | 
| 
       9 
8 
     | 
    
         
             
            bindir: bin
         
     | 
| 
       10 
9 
     | 
    
         
             
            cert_chain: []
         
     | 
| 
       11 
     | 
    
         
            -
            date:  
     | 
| 
      
 10 
     | 
    
         
            +
            date: 1980-01-02 00:00:00.000000000 Z
         
     | 
| 
       12 
11 
     | 
    
         
             
            dependencies:
         
     | 
| 
       13 
12 
     | 
    
         
             
            - !ruby/object:Gem::Dependency
         
     | 
| 
       14 
     | 
    
         
            -
              name:  
     | 
| 
      
 13 
     | 
    
         
            +
              name: pnn
         
     | 
| 
       15 
14 
     | 
    
         
             
              requirement: !ruby/object:Gem::Requirement
         
     | 
| 
       16 
15 
     | 
    
         
             
                requirements:
         
     | 
| 
       17 
     | 
    
         
            -
                - - " 
     | 
| 
      
 16 
     | 
    
         
            +
                - - "~>"
         
     | 
| 
       18 
17 
     | 
    
         
             
                  - !ruby/object:Gem::Version
         
     | 
| 
       19 
     | 
    
         
            -
                    version:  
     | 
| 
       20 
     | 
    
         
            -
              type: : 
     | 
| 
      
 18 
     | 
    
         
            +
                    version: 1.1.0
         
     | 
| 
      
 19 
     | 
    
         
            +
              type: :runtime
         
     | 
| 
       21 
20 
     | 
    
         
             
              prerelease: false
         
     | 
| 
       22 
21 
     | 
    
         
             
              version_requirements: !ruby/object:Gem::Requirement
         
     | 
| 
       23 
22 
     | 
    
         
             
                requirements:
         
     | 
| 
       24 
     | 
    
         
            -
                - - " 
     | 
| 
      
 23 
     | 
    
         
            +
                - - "~>"
         
     | 
| 
       25 
24 
     | 
    
         
             
                  - !ruby/object:Gem::Version
         
     | 
| 
       26 
     | 
    
         
            -
                    version:  
     | 
| 
       27 
     | 
    
         
            -
             
     | 
| 
       28 
     | 
    
         
            -
               
     | 
| 
       29 
     | 
    
         
            -
               
     | 
| 
       30 
     | 
    
         
            -
             
     | 
| 
       31 
     | 
    
         
            -
                - - ">="
         
     | 
| 
       32 
     | 
    
         
            -
                  - !ruby/object:Gem::Version
         
     | 
| 
       33 
     | 
    
         
            -
                    version: '0'
         
     | 
| 
       34 
     | 
    
         
            -
              type: :development
         
     | 
| 
       35 
     | 
    
         
            -
              prerelease: false
         
     | 
| 
       36 
     | 
    
         
            -
              version_requirements: !ruby/object:Gem::Requirement
         
     | 
| 
       37 
     | 
    
         
            -
                requirements:
         
     | 
| 
       38 
     | 
    
         
            -
                - - ">="
         
     | 
| 
       39 
     | 
    
         
            -
                  - !ruby/object:Gem::Version
         
     | 
| 
       40 
     | 
    
         
            -
                    version: '0'
         
     | 
| 
       41 
     | 
    
         
            -
            - !ruby/object:Gem::Dependency
         
     | 
| 
       42 
     | 
    
         
            -
              name: rake
         
     | 
| 
       43 
     | 
    
         
            -
              requirement: !ruby/object:Gem::Requirement
         
     | 
| 
       44 
     | 
    
         
            -
                requirements:
         
     | 
| 
       45 
     | 
    
         
            -
                - - ">="
         
     | 
| 
       46 
     | 
    
         
            -
                  - !ruby/object:Gem::Version
         
     | 
| 
       47 
     | 
    
         
            -
                    version: '0'
         
     | 
| 
       48 
     | 
    
         
            -
              type: :development
         
     | 
| 
       49 
     | 
    
         
            -
              prerelease: false
         
     | 
| 
       50 
     | 
    
         
            -
              version_requirements: !ruby/object:Gem::Requirement
         
     | 
| 
       51 
     | 
    
         
            -
                requirements:
         
     | 
| 
       52 
     | 
    
         
            -
                - - ">="
         
     | 
| 
       53 
     | 
    
         
            -
                  - !ruby/object:Gem::Version
         
     | 
| 
       54 
     | 
    
         
            -
                    version: '0'
         
     | 
| 
       55 
     | 
    
         
            -
            - !ruby/object:Gem::Dependency
         
     | 
| 
       56 
     | 
    
         
            -
              name: rubocop-performance
         
     | 
| 
       57 
     | 
    
         
            -
              requirement: !ruby/object:Gem::Requirement
         
     | 
| 
       58 
     | 
    
         
            -
                requirements:
         
     | 
| 
       59 
     | 
    
         
            -
                - - ">="
         
     | 
| 
       60 
     | 
    
         
            -
                  - !ruby/object:Gem::Version
         
     | 
| 
       61 
     | 
    
         
            -
                    version: '0'
         
     | 
| 
       62 
     | 
    
         
            -
              type: :development
         
     | 
| 
       63 
     | 
    
         
            -
              prerelease: false
         
     | 
| 
       64 
     | 
    
         
            -
              version_requirements: !ruby/object:Gem::Requirement
         
     | 
| 
       65 
     | 
    
         
            -
                requirements:
         
     | 
| 
       66 
     | 
    
         
            -
                - - ">="
         
     | 
| 
       67 
     | 
    
         
            -
                  - !ruby/object:Gem::Version
         
     | 
| 
       68 
     | 
    
         
            -
                    version: '0'
         
     | 
| 
       69 
     | 
    
         
            -
            - !ruby/object:Gem::Dependency
         
     | 
| 
       70 
     | 
    
         
            -
              name: rubocop-thread_safety
         
     | 
| 
       71 
     | 
    
         
            -
              requirement: !ruby/object:Gem::Requirement
         
     | 
| 
       72 
     | 
    
         
            -
                requirements:
         
     | 
| 
       73 
     | 
    
         
            -
                - - ">="
         
     | 
| 
       74 
     | 
    
         
            -
                  - !ruby/object:Gem::Version
         
     | 
| 
       75 
     | 
    
         
            -
                    version: '0'
         
     | 
| 
       76 
     | 
    
         
            -
              type: :development
         
     | 
| 
       77 
     | 
    
         
            -
              prerelease: false
         
     | 
| 
       78 
     | 
    
         
            -
              version_requirements: !ruby/object:Gem::Requirement
         
     | 
| 
       79 
     | 
    
         
            -
                requirements:
         
     | 
| 
       80 
     | 
    
         
            -
                - - ">="
         
     | 
| 
       81 
     | 
    
         
            -
                  - !ruby/object:Gem::Version
         
     | 
| 
       82 
     | 
    
         
            -
                    version: '0'
         
     | 
| 
       83 
     | 
    
         
            -
            - !ruby/object:Gem::Dependency
         
     | 
| 
       84 
     | 
    
         
            -
              name: simplecov
         
     | 
| 
       85 
     | 
    
         
            -
              requirement: !ruby/object:Gem::Requirement
         
     | 
| 
       86 
     | 
    
         
            -
                requirements:
         
     | 
| 
       87 
     | 
    
         
            -
                - - ">="
         
     | 
| 
       88 
     | 
    
         
            -
                  - !ruby/object:Gem::Version
         
     | 
| 
       89 
     | 
    
         
            -
                    version: '0'
         
     | 
| 
       90 
     | 
    
         
            -
              type: :development
         
     | 
| 
       91 
     | 
    
         
            -
              prerelease: false
         
     | 
| 
       92 
     | 
    
         
            -
              version_requirements: !ruby/object:Gem::Requirement
         
     | 
| 
       93 
     | 
    
         
            -
                requirements:
         
     | 
| 
       94 
     | 
    
         
            -
                - - ">="
         
     | 
| 
       95 
     | 
    
         
            -
                  - !ruby/object:Gem::Version
         
     | 
| 
       96 
     | 
    
         
            -
                    version: '0'
         
     | 
| 
       97 
     | 
    
         
            -
            - !ruby/object:Gem::Dependency
         
     | 
| 
       98 
     | 
    
         
            -
              name: yard
         
     | 
| 
       99 
     | 
    
         
            -
              requirement: !ruby/object:Gem::Requirement
         
     | 
| 
       100 
     | 
    
         
            -
                requirements:
         
     | 
| 
       101 
     | 
    
         
            -
                - - ">="
         
     | 
| 
       102 
     | 
    
         
            -
                  - !ruby/object:Gem::Version
         
     | 
| 
       103 
     | 
    
         
            -
                    version: '0'
         
     | 
| 
       104 
     | 
    
         
            -
              type: :development
         
     | 
| 
       105 
     | 
    
         
            -
              prerelease: false
         
     | 
| 
       106 
     | 
    
         
            -
              version_requirements: !ruby/object:Gem::Requirement
         
     | 
| 
       107 
     | 
    
         
            -
                requirements:
         
     | 
| 
       108 
     | 
    
         
            -
                - - ">="
         
     | 
| 
       109 
     | 
    
         
            -
                  - !ruby/object:Gem::Version
         
     | 
| 
       110 
     | 
    
         
            -
                    version: '0'
         
     | 
| 
       111 
     | 
    
         
            -
            description: A Ruby interface for data serialization in GAN format ♟️
         
     | 
| 
      
 25 
     | 
    
         
            +
                    version: 1.1.0
         
     | 
| 
      
 26 
     | 
    
         
            +
            description: A Ruby interface for serialization and deserialization of game actors
         
     | 
| 
      
 27 
     | 
    
         
            +
              in GAN format. GAN is a consistent and rule-agnostic format for representing game
         
     | 
| 
      
 28 
     | 
    
         
            +
              actors in abstract strategy board games, providing a standardized way to identify
         
     | 
| 
      
 29 
     | 
    
         
            +
              pieces with their originating game.
         
     | 
| 
       112 
30 
     | 
    
         
             
            email: contact@cyril.email
         
     | 
| 
       113 
31 
     | 
    
         
             
            executables: []
         
     | 
| 
       114 
32 
     | 
    
         
             
            extensions: []
         
     | 
| 
         @@ -118,22 +36,19 @@ files: 
     | 
|
| 
       118 
36 
     | 
    
         
             
            - README.md
         
     | 
| 
       119 
37 
     | 
    
         
             
            - lib/sashite-gan.rb
         
     | 
| 
       120 
38 
     | 
    
         
             
            - lib/sashite/gan.rb
         
     | 
| 
       121 
     | 
    
         
            -
            - lib/sashite/gan/ 
     | 
| 
       122 
     | 
    
         
            -
            - lib/sashite/gan/error.rb
         
     | 
| 
       123 
     | 
    
         
            -
            - lib/sashite/gan/error/string.rb
         
     | 
| 
       124 
     | 
    
         
            -
            - lib/sashite/gan/error/style.rb
         
     | 
| 
       125 
     | 
    
         
            -
            - lib/sashite/gan/error/type.rb
         
     | 
| 
      
 39 
     | 
    
         
            +
            - lib/sashite/gan/dumper.rb
         
     | 
| 
       126 
40 
     | 
    
         
             
            - lib/sashite/gan/parser.rb
         
     | 
| 
       127 
     | 
    
         
            -
            - lib/sashite/gan/ 
     | 
| 
       128 
     | 
    
         
            -
            homepage: https:// 
     | 
| 
      
 41 
     | 
    
         
            +
            - lib/sashite/gan/validator.rb
         
     | 
| 
      
 42 
     | 
    
         
            +
            homepage: https://github.com/sashite/gan.rb
         
     | 
| 
       129 
43 
     | 
    
         
             
            licenses:
         
     | 
| 
       130 
44 
     | 
    
         
             
            - MIT
         
     | 
| 
       131 
45 
     | 
    
         
             
            metadata:
         
     | 
| 
       132 
46 
     | 
    
         
             
              bug_tracker_uri: https://github.com/sashite/gan.rb/issues
         
     | 
| 
       133 
     | 
    
         
            -
              documentation_uri: https://rubydoc.info/ 
     | 
| 
      
 47 
     | 
    
         
            +
              documentation_uri: https://rubydoc.info/github/sashite/gan.rb/main
         
     | 
| 
      
 48 
     | 
    
         
            +
              homepage_uri: https://github.com/sashite/gan.rb
         
     | 
| 
       134 
49 
     | 
    
         
             
              source_code_uri: https://github.com/sashite/gan.rb
         
     | 
| 
       135 
     | 
    
         
            -
               
     | 
| 
       136 
     | 
    
         
            -
             
     | 
| 
      
 50 
     | 
    
         
            +
              specification_uri: https://sashite.dev/documents/gan/1.0.0/
         
     | 
| 
      
 51 
     | 
    
         
            +
              rubygems_mfa_required: 'true'
         
     | 
| 
       137 
52 
     | 
    
         
             
            rdoc_options: []
         
     | 
| 
       138 
53 
     | 
    
         
             
            require_paths:
         
     | 
| 
       139 
54 
     | 
    
         
             
            - lib
         
     | 
| 
         @@ -141,15 +56,14 @@ required_ruby_version: !ruby/object:Gem::Requirement 
     | 
|
| 
       141 
56 
     | 
    
         
             
              requirements:
         
     | 
| 
       142 
57 
     | 
    
         
             
              - - ">="
         
     | 
| 
       143 
58 
     | 
    
         
             
                - !ruby/object:Gem::Version
         
     | 
| 
       144 
     | 
    
         
            -
                  version: 2. 
     | 
| 
      
 59 
     | 
    
         
            +
                  version: 3.2.0
         
     | 
| 
       145 
60 
     | 
    
         
             
            required_rubygems_version: !ruby/object:Gem::Requirement
         
     | 
| 
       146 
61 
     | 
    
         
             
              requirements:
         
     | 
| 
       147 
62 
     | 
    
         
             
              - - ">="
         
     | 
| 
       148 
63 
     | 
    
         
             
                - !ruby/object:Gem::Version
         
     | 
| 
       149 
64 
     | 
    
         
             
                  version: '0'
         
     | 
| 
       150 
65 
     | 
    
         
             
            requirements: []
         
     | 
| 
       151 
     | 
    
         
            -
            rubygems_version: 3. 
     | 
| 
       152 
     | 
    
         
            -
            signing_key: 
         
     | 
| 
      
 66 
     | 
    
         
            +
            rubygems_version: 3.6.7
         
     | 
| 
       153 
67 
     | 
    
         
             
            specification_version: 4
         
     | 
| 
       154 
     | 
    
         
            -
            summary:  
     | 
| 
      
 68 
     | 
    
         
            +
            summary: GAN (General Actor Notation) support for the Ruby language.
         
     | 
| 
       155 
69 
     | 
    
         
             
            test_files: []
         
     | 
    
        data/lib/sashite/gan/abbr.rb
    DELETED
    
    | 
         @@ -1,76 +0,0 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
            # frozen_string_literal: true
         
     | 
| 
       2 
     | 
    
         
            -
             
     | 
| 
       3 
     | 
    
         
            -
            module Sashite
         
     | 
| 
       4 
     | 
    
         
            -
              module GAN
         
     | 
| 
       5 
     | 
    
         
            -
                # The piece's abbreviation.
         
     | 
| 
       6 
     | 
    
         
            -
                class Abbr
         
     | 
| 
       7 
     | 
    
         
            -
                  # The piece's type.
         
     | 
| 
       8 
     | 
    
         
            -
                  #
         
     | 
| 
       9 
     | 
    
         
            -
                  # @!attribute [r] type
         
     | 
| 
       10 
     | 
    
         
            -
                  #   @return [String] The type of the piece.
         
     | 
| 
       11 
     | 
    
         
            -
                  attr_reader :type
         
     | 
| 
       12 
     | 
    
         
            -
             
     | 
| 
       13 
     | 
    
         
            -
                  def initialize(type, is_promoted:, is_king:)
         
     | 
| 
       14 
     | 
    
         
            -
                    @type = TypeString(type)
         
     | 
| 
       15 
     | 
    
         
            -
                    @is_promoted = Boolean(is_promoted)
         
     | 
| 
       16 
     | 
    
         
            -
                    @is_king = Boolean(is_king)
         
     | 
| 
       17 
     | 
    
         
            -
             
     | 
| 
       18 
     | 
    
         
            -
                    freeze
         
     | 
| 
       19 
     | 
    
         
            -
                  end
         
     | 
| 
       20 
     | 
    
         
            -
             
     | 
| 
       21 
     | 
    
         
            -
                  # @return [Boolean] Is the piece a king?
         
     | 
| 
       22 
     | 
    
         
            -
                  def king?
         
     | 
| 
       23 
     | 
    
         
            -
                    @is_king
         
     | 
| 
       24 
     | 
    
         
            -
                  end
         
     | 
| 
       25 
     | 
    
         
            -
             
     | 
| 
       26 
     | 
    
         
            -
                  # @return [Boolean] Is the piece promoted?
         
     | 
| 
       27 
     | 
    
         
            -
                  def promoted?
         
     | 
| 
       28 
     | 
    
         
            -
                    @is_promoted
         
     | 
| 
       29 
     | 
    
         
            -
                  end
         
     | 
| 
       30 
     | 
    
         
            -
             
     | 
| 
       31 
     | 
    
         
            -
                  # @return [String] The abbreviation of the piece.
         
     | 
| 
       32 
     | 
    
         
            -
                  def to_s
         
     | 
| 
       33 
     | 
    
         
            -
                    str = type
         
     | 
| 
       34 
     | 
    
         
            -
                    str = "-#{str}" if king?
         
     | 
| 
       35 
     | 
    
         
            -
                    str = "+#{str}" if promoted?
         
     | 
| 
       36 
     | 
    
         
            -
                    str
         
     | 
| 
       37 
     | 
    
         
            -
                  end
         
     | 
| 
       38 
     | 
    
         
            -
             
     | 
| 
       39 
     | 
    
         
            -
                  def inspect
         
     | 
| 
       40 
     | 
    
         
            -
                    to_s
         
     | 
| 
       41 
     | 
    
         
            -
                  end
         
     | 
| 
       42 
     | 
    
         
            -
             
     | 
| 
       43 
     | 
    
         
            -
                  def ==(other)
         
     | 
| 
       44 
     | 
    
         
            -
                    other.to_s == to_s
         
     | 
| 
       45 
     | 
    
         
            -
                  end
         
     | 
| 
       46 
     | 
    
         
            -
             
     | 
| 
       47 
     | 
    
         
            -
                  def eql?(other)
         
     | 
| 
       48 
     | 
    
         
            -
                    self == other
         
     | 
| 
       49 
     | 
    
         
            -
                  end
         
     | 
| 
       50 
     | 
    
         
            -
             
     | 
| 
       51 
     | 
    
         
            -
                  private
         
     | 
| 
       52 
     | 
    
         
            -
             
     | 
| 
       53 
     | 
    
         
            -
                  # rubocop:disable Naming/MethodName
         
     | 
| 
       54 
     | 
    
         
            -
             
     | 
| 
       55 
     | 
    
         
            -
                  # Ensures `arg` is a boolean, and returns it.  Otherwise, raises a
         
     | 
| 
       56 
     | 
    
         
            -
                  #   `TypeError`.
         
     | 
| 
       57 
     | 
    
         
            -
                  def Boolean(arg)
         
     | 
| 
       58 
     | 
    
         
            -
                    raise ::TypeError, arg.class.inspect unless [false, true].include?(arg)
         
     | 
| 
       59 
     | 
    
         
            -
             
     | 
| 
       60 
     | 
    
         
            -
                    arg
         
     | 
| 
       61 
     | 
    
         
            -
                  end
         
     | 
| 
       62 
     | 
    
         
            -
             
     | 
| 
       63 
     | 
    
         
            -
                  # Ensures `arg` is a type, and returns it.  Otherwise, raises an error.
         
     | 
| 
       64 
     | 
    
         
            -
                  def TypeString(arg)
         
     | 
| 
       65 
     | 
    
         
            -
                    raise ::TypeError, arg.class.inspect unless arg.is_a?(::String)
         
     | 
| 
       66 
     | 
    
         
            -
                    raise Error::Type, arg.inspect unless arg.match?(/\A[a-z]{1,2}\z/i)
         
     | 
| 
       67 
     | 
    
         
            -
             
     | 
| 
       68 
     | 
    
         
            -
                    arg
         
     | 
| 
       69 
     | 
    
         
            -
                  end
         
     | 
| 
       70 
     | 
    
         
            -
             
     | 
| 
       71 
     | 
    
         
            -
                  # rubocop:enable Naming/MethodName
         
     | 
| 
       72 
     | 
    
         
            -
                end
         
     | 
| 
       73 
     | 
    
         
            -
              end
         
     | 
| 
       74 
     | 
    
         
            -
            end
         
     | 
| 
       75 
     | 
    
         
            -
             
     | 
| 
       76 
     | 
    
         
            -
            require_relative 'error'
         
     | 
    
        data/lib/sashite/gan/error.rb
    DELETED
    
    
    
        data/lib/sashite/gan/piece.rb
    DELETED
    
    | 
         @@ -1,146 +0,0 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
            # frozen_string_literal: true
         
     | 
| 
       2 
     | 
    
         
            -
             
     | 
| 
       3 
     | 
    
         
            -
            module Sashite
         
     | 
| 
       4 
     | 
    
         
            -
              module GAN
         
     | 
| 
       5 
     | 
    
         
            -
                # A piece abstraction.
         
     | 
| 
       6 
     | 
    
         
            -
                class Piece
         
     | 
| 
       7 
     | 
    
         
            -
                  # The abbreviation of the piece.
         
     | 
| 
       8 
     | 
    
         
            -
                  #
         
     | 
| 
       9 
     | 
    
         
            -
                  # @!attribute [r] abbr
         
     | 
| 
       10 
     | 
    
         
            -
                  #   @return [String] The abbreviation of the piece.
         
     | 
| 
       11 
     | 
    
         
            -
                  attr_reader :abbr
         
     | 
| 
       12 
     | 
    
         
            -
             
     | 
| 
       13 
     | 
    
         
            -
                  # The piece's style.
         
     | 
| 
       14 
     | 
    
         
            -
                  #
         
     | 
| 
       15 
     | 
    
         
            -
                  # @!attribute [r] style
         
     | 
| 
       16 
     | 
    
         
            -
                  #   @return [String] The piece's style.
         
     | 
| 
       17 
     | 
    
         
            -
                  attr_reader :style
         
     | 
| 
       18 
     | 
    
         
            -
             
     | 
| 
       19 
     | 
    
         
            -
                  # Initialize a piece.
         
     | 
| 
       20 
     | 
    
         
            -
                  #
         
     | 
| 
       21 
     | 
    
         
            -
                  # @param type [String] The type of the piece.
         
     | 
| 
       22 
     | 
    
         
            -
                  # @param is_king [Boolean] Is it a King (or a Xiangqi General),
         
     | 
| 
       23 
     | 
    
         
            -
                  #   so it could be checkmated?
         
     | 
| 
       24 
     | 
    
         
            -
                  # @param is_promoted [Boolean] Is it promoted?
         
     | 
| 
       25 
     | 
    
         
            -
                  # @param is_topside [Boolean] Is it owned by top-side player?
         
     | 
| 
       26 
     | 
    
         
            -
                  # @param style [String] The piece's style.
         
     | 
| 
       27 
     | 
    
         
            -
                  def initialize(type, is_king:, is_promoted:, is_topside:, style:)
         
     | 
| 
       28 
     | 
    
         
            -
                    @abbr = Abbr.new(type, is_king: is_king, is_promoted: is_promoted)
         
     | 
| 
       29 
     | 
    
         
            -
                    @is_topside = Boolean(is_topside)
         
     | 
| 
       30 
     | 
    
         
            -
                    @style = StyleString(style)
         
     | 
| 
       31 
     | 
    
         
            -
             
     | 
| 
       32 
     | 
    
         
            -
                    freeze
         
     | 
| 
       33 
     | 
    
         
            -
                  end
         
     | 
| 
       34 
     | 
    
         
            -
             
     | 
| 
       35 
     | 
    
         
            -
                  # Is it owned by top-side player?
         
     | 
| 
       36 
     | 
    
         
            -
                  #
         
     | 
| 
       37 
     | 
    
         
            -
                  # @return [Boolean] Returns `true` if the top-side player own the piece,
         
     | 
| 
       38 
     | 
    
         
            -
                  #   `false` otherwise.
         
     | 
| 
       39 
     | 
    
         
            -
                  def topside?
         
     | 
| 
       40 
     | 
    
         
            -
                    @is_topside
         
     | 
| 
       41 
     | 
    
         
            -
                  end
         
     | 
| 
       42 
     | 
    
         
            -
             
     | 
| 
       43 
     | 
    
         
            -
                  # Is it owned by bottom-side player?
         
     | 
| 
       44 
     | 
    
         
            -
                  #
         
     | 
| 
       45 
     | 
    
         
            -
                  # @return [Boolean] Returns `true` if the bottom-side player own the
         
     | 
| 
       46 
     | 
    
         
            -
                  #   piece, `false` otherwise.
         
     | 
| 
       47 
     | 
    
         
            -
                  def bottomside?
         
     | 
| 
       48 
     | 
    
         
            -
                    !topside?
         
     | 
| 
       49 
     | 
    
         
            -
                  end
         
     | 
| 
       50 
     | 
    
         
            -
             
     | 
| 
       51 
     | 
    
         
            -
                  # @see https://developer.sashite.com/specs/general-actor-notation
         
     | 
| 
       52 
     | 
    
         
            -
                  # @return [String] The notation of the piece.
         
     | 
| 
       53 
     | 
    
         
            -
                  def to_s
         
     | 
| 
       54 
     | 
    
         
            -
                    topside? ? raw.downcase : raw.upcase
         
     | 
| 
       55 
     | 
    
         
            -
                  end
         
     | 
| 
       56 
     | 
    
         
            -
             
     | 
| 
       57 
     | 
    
         
            -
                  def inspect
         
     | 
| 
       58 
     | 
    
         
            -
                    to_s
         
     | 
| 
       59 
     | 
    
         
            -
                  end
         
     | 
| 
       60 
     | 
    
         
            -
             
     | 
| 
       61 
     | 
    
         
            -
                  # @return [Piece] The top-side side version of the piece.
         
     | 
| 
       62 
     | 
    
         
            -
                  def topside
         
     | 
| 
       63 
     | 
    
         
            -
                    topside? ? self : oppositeside
         
     | 
| 
       64 
     | 
    
         
            -
                  end
         
     | 
| 
       65 
     | 
    
         
            -
             
     | 
| 
       66 
     | 
    
         
            -
                  # @return [Piece] The bottom-side side version of the piece.
         
     | 
| 
       67 
     | 
    
         
            -
                  def bottomside
         
     | 
| 
       68 
     | 
    
         
            -
                    topside? ? oppositeside : self
         
     | 
| 
       69 
     | 
    
         
            -
                  end
         
     | 
| 
       70 
     | 
    
         
            -
             
     | 
| 
       71 
     | 
    
         
            -
                  # @return [Piece] The opposite side version of the piece.
         
     | 
| 
       72 
     | 
    
         
            -
                  def oppositeside
         
     | 
| 
       73 
     | 
    
         
            -
                    self.class.new(abbr.type,
         
     | 
| 
       74 
     | 
    
         
            -
                      is_king: abbr.king?,
         
     | 
| 
       75 
     | 
    
         
            -
                      is_promoted: abbr.promoted?,
         
     | 
| 
       76 
     | 
    
         
            -
                      is_topside: !topside?,
         
     | 
| 
       77 
     | 
    
         
            -
                      style: style
         
     | 
| 
       78 
     | 
    
         
            -
                    )
         
     | 
| 
       79 
     | 
    
         
            -
                  end
         
     | 
| 
       80 
     | 
    
         
            -
             
     | 
| 
       81 
     | 
    
         
            -
                  # @return [Piece] The promoted version of the piece.
         
     | 
| 
       82 
     | 
    
         
            -
                  def promote
         
     | 
| 
       83 
     | 
    
         
            -
                    self.class.new(abbr.type,
         
     | 
| 
       84 
     | 
    
         
            -
                      is_king: abbr.king?,
         
     | 
| 
       85 
     | 
    
         
            -
                      is_promoted: true,
         
     | 
| 
       86 
     | 
    
         
            -
                      is_topside: topside?,
         
     | 
| 
       87 
     | 
    
         
            -
                      style: style
         
     | 
| 
       88 
     | 
    
         
            -
                    )
         
     | 
| 
       89 
     | 
    
         
            -
                  end
         
     | 
| 
       90 
     | 
    
         
            -
             
     | 
| 
       91 
     | 
    
         
            -
                  # @return [Piece] The unpromoted version of the piece.
         
     | 
| 
       92 
     | 
    
         
            -
                  def unpromote
         
     | 
| 
       93 
     | 
    
         
            -
                    self.class.new(abbr.type,
         
     | 
| 
       94 
     | 
    
         
            -
                      is_king: abbr.king?,
         
     | 
| 
       95 
     | 
    
         
            -
                      is_promoted: false,
         
     | 
| 
       96 
     | 
    
         
            -
                      is_topside: topside?,
         
     | 
| 
       97 
     | 
    
         
            -
                      style: style
         
     | 
| 
       98 
     | 
    
         
            -
                    )
         
     | 
| 
       99 
     | 
    
         
            -
                  end
         
     | 
| 
       100 
     | 
    
         
            -
             
     | 
| 
       101 
     | 
    
         
            -
                  def ==(other)
         
     | 
| 
       102 
     | 
    
         
            -
                    other.to_s == to_s
         
     | 
| 
       103 
     | 
    
         
            -
                  end
         
     | 
| 
       104 
     | 
    
         
            -
             
     | 
| 
       105 
     | 
    
         
            -
                  def eql?(other)
         
     | 
| 
       106 
     | 
    
         
            -
                    self == other
         
     | 
| 
       107 
     | 
    
         
            -
                  end
         
     | 
| 
       108 
     | 
    
         
            -
             
     | 
| 
       109 
     | 
    
         
            -
                  private
         
     | 
| 
       110 
     | 
    
         
            -
             
     | 
| 
       111 
     | 
    
         
            -
                  # @return [String] The style and the abbreviation of the piece (without
         
     | 
| 
       112 
     | 
    
         
            -
                  #   case).
         
     | 
| 
       113 
     | 
    
         
            -
                  def raw
         
     | 
| 
       114 
     | 
    
         
            -
                    params.join(SEPARATOR_CHAR)
         
     | 
| 
       115 
     | 
    
         
            -
                  end
         
     | 
| 
       116 
     | 
    
         
            -
             
     | 
| 
       117 
     | 
    
         
            -
                  # @return [Array] The style and the abbreviation of the piece.
         
     | 
| 
       118 
     | 
    
         
            -
                  def params
         
     | 
| 
       119 
     | 
    
         
            -
                    [style, abbr]
         
     | 
| 
       120 
     | 
    
         
            -
                  end
         
     | 
| 
       121 
     | 
    
         
            -
             
     | 
| 
       122 
     | 
    
         
            -
                  # rubocop:disable Naming/MethodName
         
     | 
| 
       123 
     | 
    
         
            -
             
     | 
| 
       124 
     | 
    
         
            -
                  # Ensures `arg` is a boolean, and returns it.  Otherwise, raises a
         
     | 
| 
       125 
     | 
    
         
            -
                  #   `TypeError`.
         
     | 
| 
       126 
     | 
    
         
            -
                  def Boolean(arg)
         
     | 
| 
       127 
     | 
    
         
            -
                    raise ::TypeError, arg.class.inspect unless [false, true].include?(arg)
         
     | 
| 
       128 
     | 
    
         
            -
             
     | 
| 
       129 
     | 
    
         
            -
                    arg
         
     | 
| 
       130 
     | 
    
         
            -
                  end
         
     | 
| 
       131 
     | 
    
         
            -
             
     | 
| 
       132 
     | 
    
         
            -
                  # Ensures `arg` is a style, and returns it.  Otherwise, raises an error.
         
     | 
| 
       133 
     | 
    
         
            -
                  def StyleString(arg)
         
     | 
| 
       134 
     | 
    
         
            -
                    raise ::TypeError, arg.class.inspect unless arg.is_a?(::String)
         
     | 
| 
       135 
     | 
    
         
            -
                    raise Error::Style, arg.inspect unless arg.match?(/\A[a-z_]+\z/i)
         
     | 
| 
       136 
     | 
    
         
            -
             
     | 
| 
       137 
     | 
    
         
            -
                    arg
         
     | 
| 
       138 
     | 
    
         
            -
                  end
         
     | 
| 
       139 
     | 
    
         
            -
             
     | 
| 
       140 
     | 
    
         
            -
                  # rubocop:enable Naming/MethodName
         
     | 
| 
       141 
     | 
    
         
            -
                end
         
     | 
| 
       142 
     | 
    
         
            -
              end
         
     | 
| 
       143 
     | 
    
         
            -
            end
         
     | 
| 
       144 
     | 
    
         
            -
             
     | 
| 
       145 
     | 
    
         
            -
            require_relative 'abbr'
         
     | 
| 
       146 
     | 
    
         
            -
            require_relative 'error'
         
     |