chess 0.3.0 → 0.3.3
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/.github/FUNDING.yml +12 -0
- data/.github/workflows/ruby.yml +58 -0
- data/.gitignore +3 -2
- data/.rubocop.yml +10 -142
- data/Gemfile.lock +74 -0
- data/README.md +26 -22
- data/Rakefile +2 -1
- data/chess.gemspec +11 -7
- data/docs/Chess/BadNotationError.html +237 -0
- data/docs/Chess/Board.html +1759 -0
- data/docs/Chess/CGame.html +2296 -0
- data/docs/Chess/Game.html +1277 -0
- data/docs/Chess/Gnuchess.html +366 -0
- data/docs/Chess/IllegalMoveError.html +137 -0
- data/docs/Chess/InvalidFenFormatError.html +237 -0
- data/docs/Chess/InvalidPgnFormatError.html +217 -0
- data/docs/Chess/Pgn.html +1477 -0
- data/docs/Chess/UTF8Notation.html +270 -0
- data/docs/Chess.html +157 -0
- data/docs/_index.html +217 -0
- data/docs/class_list.html +51 -0
- data/docs/css/common.css +1 -0
- data/docs/css/full_list.css +58 -0
- data/docs/css/style.css +497 -0
- data/docs/file.README.html +116 -0
- data/docs/file_list.html +56 -0
- data/docs/frames.html +17 -0
- data/docs/index.html +116 -0
- data/docs/js/app.js +314 -0
- data/docs/js/full_list.js +216 -0
- data/docs/js/jquery.js +4 -0
- data/docs/method_list.html +531 -0
- data/docs/top-level-namespace.html +110 -0
- data/ext/chess.c +1 -1
- data/ext/common.h +7 -3
- data/ext/extconf.rb +1 -1
- data/lib/chess/game.rb +68 -66
- data/lib/chess/gnuchess.rb +49 -53
- data/lib/chess/pgn.rb +13 -15
- data/lib/chess/version.rb +1 -1
- data/test/test_big_pgn_collection.rb +1 -1
- data/test/test_checkmate.rb +4 -4
- data/test/test_errors.rb +22 -0
- data/test/test_fifty_rule_move.rb +2 -2
- data/test/test_game.rb +82 -0
- data/test/test_helper.rb +15 -0
- data/test/test_insufficient_material.rb +4 -4
- data/test/test_move_generator.rb +3 -2
- data/test/test_pgn.rb +35 -0
- data/test/test_pgn_collection.rb +2 -2
- data/test/test_stalemate.rb +1 -1
- data/test/test_threefold_repetition.rb +1 -1
- data/test/test_uci_castling.rb +34 -0
- metadata +118 -1236
    
        data/lib/chess/game.rb
    CHANGED
    
    | @@ -7,6 +7,7 @@ module Chess | |
| 7 7 | 
             
                # @raise [IllegalMoveError]
         | 
| 8 8 | 
             
                # @raise [BadNotationError]
         | 
| 9 9 | 
             
                def initialize(moves = [])
         | 
| 10 | 
            +
                  super()
         | 
| 10 11 | 
             
                  moves.each { |m| move(m) }
         | 
| 11 12 | 
             
                end
         | 
| 12 13 |  | 
| @@ -20,7 +21,7 @@ module Chess | |
| 20 21 | 
             
                  pgn = Chess::Pgn.new(file)
         | 
| 21 22 | 
             
                  game = Chess::Game.new
         | 
| 22 23 | 
             
                  pgn.moves.each { |m| game.move(m) }
         | 
| 23 | 
            -
                   | 
| 24 | 
            +
                  unless game.over?
         | 
| 24 25 | 
             
                    case pgn.result
         | 
| 25 26 | 
             
                    when '1-0'
         | 
| 26 27 | 
             
                      game.resign(:black)
         | 
| @@ -39,13 +40,11 @@ module Chess | |
| 39 40 | 
             
                # @raise [InvalidFenFormatError]
         | 
| 40 41 | 
             
                # @note This game do not have history before the FEN placement.
         | 
| 41 42 | 
             
                def self.load_fen(fen)
         | 
| 42 | 
            -
                   | 
| 43 | 
            -
             | 
| 44 | 
            -
             | 
| 45 | 
            -
             | 
| 46 | 
            -
                   | 
| 47 | 
            -
                    raise InvalidFenFormatError.new(fen)
         | 
| 48 | 
            -
                  end
         | 
| 43 | 
            +
                  raise InvalidFenFormatError.new(fen) unless /^((?:[PRNBQKprnbqk1-8]{1,8}\/){7}[RNBQKPrnbqkp1-8]{1,8})\s(w|b)\s(K?Q?k?q?|-)\s([a-h][1-8]|-)\s(\d+)\s(\d+)$/.match?(fen)
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                  game = Chess::Game.new
         | 
| 46 | 
            +
                  game.set_fen!(fen)
         | 
| 47 | 
            +
                  return game
         | 
| 49 48 | 
             
                end
         | 
| 50 49 |  | 
| 51 50 | 
             
                # Make a move.
         | 
| @@ -67,11 +66,9 @@ module Chess | |
| 67 66 | 
             
                    super(expand[:name], expand[:dis], expand[:to], expand[:promotion])
         | 
| 68 67 | 
             
                  end
         | 
| 69 68 | 
             
                rescue IllegalMoveError
         | 
| 70 | 
            -
                  if ENV['DEBUG']
         | 
| 71 | 
            -
             | 
| 72 | 
            -
                   | 
| 73 | 
            -
                    raise IllegalMoveError.new("Illegal move '#{notation}'")
         | 
| 74 | 
            -
                  end
         | 
| 69 | 
            +
                  raise IllegalMoveError.new("Illegal move '#{notation}'\nStatus: #{self.status}\nPlayer turn #{self.active_player}\n#{self}") if ENV['DEBUG']
         | 
| 70 | 
            +
             | 
| 71 | 
            +
                  raise IllegalMoveError.new("Illegal move '#{notation}'")
         | 
| 75 72 | 
             
                end
         | 
| 76 73 | 
             
                alias move= move
         | 
| 77 74 | 
             
                alias << move
         | 
| @@ -106,38 +103,28 @@ module Chess | |
| 106 103 | 
             
                # * `black_won_resign`: black player has won for resign.
         | 
| 107 104 | 
             
                # * `stalemate`: draw for stalemate.
         | 
| 108 105 | 
             
                # * `insufficient_material`: draw for insufficient material to checkmate.
         | 
| 109 | 
            -
                # * ` | 
| 110 | 
            -
                # * `threefold_repetition`: draw for  | 
| 111 | 
            -
                #  | 
| 112 | 
            -
                # @return [String]
         | 
| 106 | 
            +
                # * `fifty_move_rule`: draw for fifty-move rule.
         | 
| 107 | 
            +
                # * `threefold_repetition`: draw for threefold repetition.
         | 
| 108 | 
            +
                # @return [Symbol]
         | 
| 113 109 | 
             
                def status
         | 
| 114 110 | 
             
                  case self.result
         | 
| 115 111 | 
             
                  when '*'
         | 
| 116 112 | 
             
                    return :in_progress
         | 
| 117 113 | 
             
                  when '1-0'
         | 
| 118 | 
            -
                    if self.board.checkmate?
         | 
| 119 | 
            -
             | 
| 120 | 
            -
                     | 
| 121 | 
            -
                      return :white_won_resign
         | 
| 122 | 
            -
                    end
         | 
| 114 | 
            +
                    return :white_won if self.board.checkmate?
         | 
| 115 | 
            +
             | 
| 116 | 
            +
                    return :white_won_resign
         | 
| 123 117 | 
             
                  when '0-1'
         | 
| 124 | 
            -
                    if self.board.checkmate?
         | 
| 125 | 
            -
             | 
| 126 | 
            -
                     | 
| 127 | 
            -
                      return :black_won_resign
         | 
| 128 | 
            -
                    end
         | 
| 118 | 
            +
                    return :black_won if self.board.checkmate?
         | 
| 119 | 
            +
             | 
| 120 | 
            +
                    return :black_won_resign
         | 
| 129 121 | 
             
                  when '1/2-1/2'
         | 
| 130 | 
            -
                    if self.board.stalemate?
         | 
| 131 | 
            -
             | 
| 132 | 
            -
                     | 
| 133 | 
            -
             | 
| 134 | 
            -
                     | 
| 135 | 
            -
                      return :fifty_rule_move
         | 
| 136 | 
            -
                    elsif self.threefold_repetition?
         | 
| 137 | 
            -
                      return :threefold_repetition
         | 
| 138 | 
            -
                    end
         | 
| 122 | 
            +
                    return :stalemate if self.board.stalemate?
         | 
| 123 | 
            +
                    return :insufficient_material if self.board.insufficient_material?
         | 
| 124 | 
            +
                    return :fifty_move_rule if self.board.fifty_move_rule?
         | 
| 125 | 
            +
             | 
| 126 | 
            +
                    return :threefold_repetition if self.threefold_repetition?
         | 
| 139 127 | 
             
                  end
         | 
| 140 | 
            -
                  return :unknown
         | 
| 141 128 | 
             
                end
         | 
| 142 129 |  | 
| 143 130 | 
             
                # Returns `true` if the game is over.
         | 
| @@ -146,7 +133,7 @@ module Chess | |
| 146 133 | 
             
                end
         | 
| 147 134 |  | 
| 148 135 | 
             
                # Returns the PGN rappresenting the game.
         | 
| 149 | 
            -
                # @return [ | 
| 136 | 
            +
                # @return [Chess::Pgn]
         | 
| 150 137 | 
             
                def pgn
         | 
| 151 138 | 
             
                  pgn = Chess::Pgn.new
         | 
| 152 139 | 
             
                  pgn.moves = self.moves
         | 
| @@ -156,37 +143,52 @@ module Chess | |
| 156 143 |  | 
| 157 144 | 
             
                private
         | 
| 158 145 |  | 
| 159 | 
            -
             | 
| 160 | 
            -
             | 
| 161 | 
            -
             | 
| 162 | 
            -
             | 
| 163 | 
            -
             | 
| 164 | 
            -
             | 
| 165 | 
            -
             | 
| 166 | 
            -
             | 
| 167 | 
            -
             | 
| 168 | 
            -
             | 
| 169 | 
            -
             | 
| 170 | 
            -
             | 
| 171 | 
            -
             | 
| 172 | 
            -
                     | 
| 173 | 
            -
             | 
| 174 | 
            -
             | 
| 175 | 
            -
                       | 
| 176 | 
            -
             | 
| 177 | 
            -
             | 
| 178 | 
            -
             | 
| 179 | 
            -
                      if  | 
| 180 | 
            -
                        return { name: 'K', dis: nil, from: 'e8', to: 'c8', promotion: nil }
         | 
| 181 | 
            -
                      else # white king long castling
         | 
| 182 | 
            -
                        return { name: 'K', dis: nil, from: 'e1', to: 'c1', promotion: nil }
         | 
| 183 | 
            -
                      end
         | 
| 146 | 
            +
                # Expand the short algebraic chess notation string `notation` in a hash like this:
         | 
| 147 | 
            +
                #
         | 
| 148 | 
            +
                #     Ngxe2 ==> { name: 'N', dis: 'g', from: nil, to: 'e2', promotion: nil }
         | 
| 149 | 
            +
                def expand_move(notation)
         | 
| 150 | 
            +
                  if (match = notation.match(MOVE_REGEXP))
         | 
| 151 | 
            +
                    expand = {
         | 
| 152 | 
            +
                      name: match[1] || 'P',    # Piece name [RNBQK]
         | 
| 153 | 
            +
                      dis: match[2],            # Disambiguating move
         | 
| 154 | 
            +
                      to: match[3],             # Move to
         | 
| 155 | 
            +
                      promotion: match[4]       # Promote with
         | 
| 156 | 
            +
                    }
         | 
| 157 | 
            +
                    expand[:from] = match[2] if match[2] && match[2].size == 2
         | 
| 158 | 
            +
             | 
| 159 | 
            +
                    # Support UCI protocol (Lichess)
         | 
| 160 | 
            +
                    case expand[:from]
         | 
| 161 | 
            +
                    when 'e1'
         | 
| 162 | 
            +
                      expand[:to] = 'g1' if expand[:to] == 'h1' # UCI protocol (Lichess) white king short castling
         | 
| 163 | 
            +
                      expand[:to] = 'c1' if expand[:to] == 'a1' # UCI protocol (Lichess) white king long castling
         | 
| 164 | 
            +
                    when 'e8'
         | 
| 165 | 
            +
                      expand[:to] = 'g8' if expand[:to] == 'h8' # UCI protocol (Lichess) black king short castling
         | 
| 166 | 
            +
                      expand[:to] = 'c8' if expand[:to] == 'a8' # UCI protocol (Lichess) black king long castling
         | 
| 184 167 | 
             
                    end
         | 
| 185 | 
            -
             | 
| 168 | 
            +
             | 
| 169 | 
            +
                    return expand
         | 
| 170 | 
            +
                  end
         | 
| 171 | 
            +
             | 
| 172 | 
            +
                  # Castling notation
         | 
| 173 | 
            +
                  if SHORT_CASTLING_REGEXP.match?(notation)
         | 
| 174 | 
            +
                    return { name: 'K', dis: nil, from: 'e8', to: 'g8', promotion: nil } if self.board.active_color # black king short castling
         | 
| 175 | 
            +
             | 
| 176 | 
            +
                    return { name: 'K', dis: nil, from: 'e1', to: 'g1', promotion: nil } # white king short castling
         | 
| 177 | 
            +
                  end
         | 
| 178 | 
            +
                  if LONG_CASTLING_REGEXP.match?(notation)
         | 
| 179 | 
            +
                    return { name: 'K', dis: nil, from: 'e8', to: 'c8', promotion: nil } if self.board.active_color # black king long castling
         | 
| 180 | 
            +
             | 
| 181 | 
            +
                    return { name: 'K', dis: nil, from: 'e1', to: 'c1', promotion: nil } # white king long castling
         | 
| 186 182 | 
             
                  end
         | 
| 183 | 
            +
             | 
| 184 | 
            +
                  raise BadNotationError.new(notation)
         | 
| 185 | 
            +
                end
         | 
| 187 186 | 
             
              end
         | 
| 188 187 |  | 
| 189 188 | 
             
              MOVE_REGEXP = /^([RNBQK])?([a-h]|[1-8]|[a-h][1-8])?(?:x)?([a-h][1-8])(?:=?([RrNnBbQq]))?(?:ep)?(?:\+|\#)?$/.freeze
         | 
| 190 | 
            -
              SHORT_CASTLING_REGEXP = /^([0O])-([0O])([ | 
| 191 | 
            -
              LONG_CASTLING_REGEXP = /^([0O])-([0O])-([0O])([ | 
| 189 | 
            +
              SHORT_CASTLING_REGEXP = /^([0O])-([0O])([+#])?$/.freeze
         | 
| 190 | 
            +
              LONG_CASTLING_REGEXP = /^([0O])-([0O])-([0O])([+#])?$/.freeze
         | 
| 191 | 
            +
              private_constant :MOVE_REGEXP
         | 
| 192 | 
            +
              private_constant :SHORT_CASTLING_REGEXP
         | 
| 193 | 
            +
              private_constant :LONG_CASTLING_REGEXP
         | 
| 192 194 | 
             
            end
         | 
    
        data/lib/chess/gnuchess.rb
    CHANGED
    
    | @@ -46,69 +46,65 @@ module Chess | |
| 46 46 | 
             
                class << self
         | 
| 47 47 | 
             
                  private
         | 
| 48 48 |  | 
| 49 | 
            -
             | 
| 50 | 
            -
             | 
| 51 | 
            -
             | 
| 49 | 
            +
                  def included(_mod)
         | 
| 50 | 
            +
                    raise_if_gnuchess_is_not_installed
         | 
| 51 | 
            +
                  end
         | 
| 52 52 |  | 
| 53 | 
            -
             | 
| 54 | 
            -
             | 
| 55 | 
            -
             | 
| 53 | 
            +
                  def extended(_mod)
         | 
| 54 | 
            +
                    raise_if_gnuchess_is_not_installed
         | 
| 55 | 
            +
                  end
         | 
| 56 56 |  | 
| 57 | 
            -
             | 
| 58 | 
            -
             | 
| 59 | 
            -
             | 
| 60 | 
            -
             | 
| 61 | 
            -
                      end
         | 
| 62 | 
            -
                    end
         | 
| 57 | 
            +
                  # Raise an exception if Gnuchess is not installed
         | 
| 58 | 
            +
                  def raise_if_gnuchess_is_not_installed
         | 
| 59 | 
            +
                    raise 'You must install Gnuchess to use the module Chess::Gnuchess!' unless find_executable0('gnuchess')
         | 
| 60 | 
            +
                  end
         | 
| 63 61 | 
             
                end
         | 
| 64 62 |  | 
| 65 63 | 
             
                private
         | 
| 66 64 |  | 
| 67 | 
            -
             | 
| 68 | 
            -
             | 
| 69 | 
            -
             | 
| 70 | 
            -
             | 
| 71 | 
            -
             | 
| 72 | 
            -
             | 
| 73 | 
            -
             | 
| 74 | 
            -
             | 
| 75 | 
            -
             | 
| 76 | 
            -
             | 
| 77 | 
            -
             | 
| 78 | 
            -
             | 
| 79 | 
            -
             | 
| 80 | 
            -
             | 
| 81 | 
            -
             | 
| 82 | 
            -
             | 
| 83 | 
            -
             | 
| 84 | 
            -
                           | 
| 85 | 
            -
             | 
| 86 | 
            -
             | 
| 87 | 
            -
                           | 
| 88 | 
            -
             | 
| 89 | 
            -
             | 
| 90 | 
            -
                           | 
| 91 | 
            -
                             | 
| 92 | 
            -
             | 
| 93 | 
            -
             | 
| 94 | 
            -
             | 
| 95 | 
            -
             | 
| 96 | 
            -
             | 
| 97 | 
            -
             | 
| 98 | 
            -
                            when 'Draw by insufficient material'
         | 
| 99 | 
            -
                              done = :insufficient_material
         | 
| 100 | 
            -
                            end
         | 
| 101 | 
            -
                            break
         | 
| 65 | 
            +
                def gen_pgn(file_to_save, moves = [])
         | 
| 66 | 
            +
                  done = false
         | 
| 67 | 
            +
                  pipe = IO.popen('gnuchess', 'r+')
         | 
| 68 | 
            +
                  begin
         | 
| 69 | 
            +
                    pipe.write("depth 1\n")
         | 
| 70 | 
            +
                    pipe.write("force\n")
         | 
| 71 | 
            +
                    pipe.write("name chess.rb\n")
         | 
| 72 | 
            +
                    moves.each do |move|
         | 
| 73 | 
            +
                      pipe.write("#{move}\n")
         | 
| 74 | 
            +
                    end
         | 
| 75 | 
            +
                    until done
         | 
| 76 | 
            +
                      pipe.write("go\n")
         | 
| 77 | 
            +
                      while (line = pipe.gets)
         | 
| 78 | 
            +
                        break if /My move is : /.match?(line) || / : resign/.match?(line)
         | 
| 79 | 
            +
             | 
| 80 | 
            +
                        if / : 1-0 {White mates}/.match?(line)
         | 
| 81 | 
            +
                          done = :white_won
         | 
| 82 | 
            +
                          break
         | 
| 83 | 
            +
                        elsif / : 0-1 {Black mates}/.match?(line)
         | 
| 84 | 
            +
                          done = :black_won
         | 
| 85 | 
            +
                          break
         | 
| 86 | 
            +
                        elsif (m = line.match(/1\/2-1\/2 {(.*?)}/))
         | 
| 87 | 
            +
                          case m[1]
         | 
| 88 | 
            +
                          when 'Stalemate'
         | 
| 89 | 
            +
                            done = :stalemate
         | 
| 90 | 
            +
                          when 'Draw by repetition'
         | 
| 91 | 
            +
                            done = :repetition
         | 
| 92 | 
            +
                          when 'Draw by fifty-move rule'
         | 
| 93 | 
            +
                            done = :fifty_move_rule
         | 
| 94 | 
            +
                          when 'Draw by insufficient material'
         | 
| 95 | 
            +
                            done = :insufficient_material
         | 
| 102 96 | 
             
                          end
         | 
| 97 | 
            +
                          break
         | 
| 103 98 | 
             
                        end
         | 
| 104 99 | 
             
                      end
         | 
| 105 | 
            -
                      dir = File.dirname(file_to_save)
         | 
| 106 | 
            -
                      name = File.basename(file_to_save)
         | 
| 107 | 
            -
                      pipe.write("pgnsave #{File.join(dir, done.to_s, name)}\n")
         | 
| 108 | 
            -
                    ensure
         | 
| 109 | 
            -
                      pipe.write("quit\n")
         | 
| 110 | 
            -
                      pipe.close
         | 
| 111 100 | 
             
                    end
         | 
| 101 | 
            +
                    dir = File.dirname(file_to_save)
         | 
| 102 | 
            +
                    name = File.basename(file_to_save)
         | 
| 103 | 
            +
                    pipe.write("pgnsave #{File.join(dir, done.to_s, name)}\n")
         | 
| 104 | 
            +
                  ensure
         | 
| 105 | 
            +
                    pipe.write("quit\n")
         | 
| 106 | 
            +
                    pipe.close
         | 
| 112 107 | 
             
                  end
         | 
| 108 | 
            +
                end
         | 
| 113 109 | 
             
              end
         | 
| 114 110 | 
             
            end
         | 
    
        data/lib/chess/pgn.rb
    CHANGED
    
    | @@ -40,7 +40,6 @@ module Chess | |
| 40 40 | 
             
                def initialize(filename = nil, check_moves: false)
         | 
| 41 41 | 
             
                  self.load(filename, check_moves: check_moves) if filename
         | 
| 42 42 | 
             
                  @date = '??'
         | 
| 43 | 
            -
                  @round = '1'
         | 
| 44 43 | 
             
                end
         | 
| 45 44 |  | 
| 46 45 | 
             
                # Load a PGN from file.
         | 
| @@ -50,7 +49,7 @@ module Chess | |
| 50 49 | 
             
                # @raise [InvalidPgnFormatError]
         | 
| 51 50 | 
             
                # @raise [IllegalMoveError]
         | 
| 52 51 | 
             
                def load(filename, check_moves: false)
         | 
| 53 | 
            -
                  str = File. | 
| 52 | 
            +
                  str = File.read(filename)
         | 
| 54 53 | 
             
                  load_from_string(str, check_moves: check_moves)
         | 
| 55 54 | 
             
                end
         | 
| 56 55 |  | 
| @@ -70,12 +69,10 @@ module Chess | |
| 70 69 | 
             
                  raise Chess::InvalidPgnFormatError.new if game_index.nil?
         | 
| 71 70 |  | 
| 72 71 | 
             
                  game = str[game_index..-1].strip
         | 
| 73 | 
            -
                  @moves = game.tr("\n", ' ').split(/\d+\./).collect(&:strip)[1..-1]. | 
| 74 | 
            -
                  @moves.delete_at(@moves.size - 1) if @moves.last | 
| 72 | 
            +
                  @moves = game.tr("\n", ' ').split(/\d+\./).collect(&:strip)[1..-1].map(&:split).flatten
         | 
| 73 | 
            +
                  @moves.delete_at(@moves.size - 1) if @moves.last.match?(/(0-1)|(1-0)|(1\/2)|(1\/2-1\/2)|(\*)/)
         | 
| 75 74 | 
             
                  @moves.each do |m|
         | 
| 76 | 
            -
                    if m !~ MOVE_REGEXP && m !~ SHORT_CASTLING_REGEXP && m !~ LONG_CASTLING_REGEXP
         | 
| 77 | 
            -
                      raise Chess::InvalidPgnFormatError.new
         | 
| 78 | 
            -
                    end
         | 
| 75 | 
            +
                    raise Chess::InvalidPgnFormatError.new if m !~ MOVE_REGEXP && m !~ SHORT_CASTLING_REGEXP && m !~ LONG_CASTLING_REGEXP
         | 
| 79 76 | 
             
                  end
         | 
| 80 77 | 
             
                  Chess::Game.new(@moves) if check_moves
         | 
| 81 78 | 
             
                  return self
         | 
| @@ -85,13 +82,13 @@ module Chess | |
| 85 82 | 
             
                def to_s
         | 
| 86 83 | 
             
                  s = ''
         | 
| 87 84 | 
             
                  TAGS.each do |t|
         | 
| 88 | 
            -
                    tag = instance_variable_get("@#{t}")
         | 
| 85 | 
            +
                    tag = instance_variable_defined?("@#{t}") ? instance_variable_get("@#{t}") : ''
         | 
| 89 86 | 
             
                    s << "[#{t.capitalize} \"#{tag}\"]\n"
         | 
| 90 87 | 
             
                  end
         | 
| 91 88 | 
             
                  s << "\n"
         | 
| 92 89 | 
             
                  m = ''
         | 
| 93 90 | 
             
                  @moves.each_with_index do |move, i|
         | 
| 94 | 
            -
                    m << "#{i / 2 + 1}. " if i.even?
         | 
| 91 | 
            +
                    m << "#{(i / 2) + 1}. " if i.even?
         | 
| 95 92 | 
             
                    m << "#{move} "
         | 
| 96 93 | 
             
                  end
         | 
| 97 94 | 
             
                  m << @result unless @result.nil?
         | 
| @@ -101,7 +98,7 @@ module Chess | |
| 101 98 | 
             
                # Write PGN to file.
         | 
| 102 99 | 
             
                # @param [String] filename The path of the PGN file.
         | 
| 103 100 | 
             
                def write(filename)
         | 
| 104 | 
            -
                  File. | 
| 101 | 
            +
                  File.write(filename, self.to_s)
         | 
| 105 102 | 
             
                end
         | 
| 106 103 |  | 
| 107 104 | 
             
                # # @!visibility private
         | 
| @@ -109,11 +106,12 @@ module Chess | |
| 109 106 |  | 
| 110 107 | 
             
                # Set the date tag.
         | 
| 111 108 | 
             
                def date=(value)
         | 
| 112 | 
            -
                   | 
| 113 | 
            -
                     | 
| 114 | 
            -
             | 
| 115 | 
            -
                     | 
| 116 | 
            -
             | 
| 109 | 
            +
                  @date =
         | 
| 110 | 
            +
                    if value.is_a?(Time)
         | 
| 111 | 
            +
                      value.strftime('%Y.%m.%d')
         | 
| 112 | 
            +
                    else
         | 
| 113 | 
            +
                      value
         | 
| 114 | 
            +
                    end
         | 
| 117 115 | 
             
                end
         | 
| 118 116 | 
             
              end
         | 
| 119 117 | 
             
            end
         | 
    
        data/lib/chess/version.rb
    CHANGED
    
    
| @@ -11,7 +11,7 @@ class ChessTest < Minitest::Test | |
| 11 11 | 
             
                  define_method "test_big_pgn_#{filename}" do
         | 
| 12 12 | 
             
                    pgn = Chess::Pgn.new(path)
         | 
| 13 13 | 
             
                    game = Chess::Game.new(pgn.moves)
         | 
| 14 | 
            -
                    assert(game.checkmate?) if pgn.moves.last | 
| 14 | 
            +
                    assert(game.checkmate?) if pgn.moves.last.match?(/\#$/)
         | 
| 15 15 | 
             
                  end
         | 
| 16 16 | 
             
                end
         | 
| 17 17 | 
             
              end
         | 
    
        data/test/test_checkmate.rb
    CHANGED
    
    | @@ -9,11 +9,11 @@ class ChessTest < Minitest::Test | |
| 9 9 | 
             
                  game = Chess::Game.new(pgn.moves)
         | 
| 10 10 | 
             
                  assert(game.board.checkmate?)
         | 
| 11 11 | 
             
                  if file.include?('white_won')
         | 
| 12 | 
            -
                    assert_equal( | 
| 13 | 
            -
                    assert_equal(game.active_player | 
| 12 | 
            +
                    assert_equal('1-0', game.result)
         | 
| 13 | 
            +
                    assert_equal(:black, game.active_player)
         | 
| 14 14 | 
             
                  elsif file.include?('black_won')
         | 
| 15 | 
            -
                    assert_equal( | 
| 16 | 
            -
                    assert_equal(game.active_player | 
| 15 | 
            +
                    assert_equal('0-1', game.result)
         | 
| 16 | 
            +
                    assert_equal(:white, game.active_player)
         | 
| 17 17 | 
             
                  end
         | 
| 18 18 | 
             
                end
         | 
| 19 19 | 
             
              end
         | 
    
        data/test/test_errors.rb
    ADDED
    
    | @@ -0,0 +1,22 @@ | |
| 1 | 
            +
            require 'test_helper'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            class ChessTest < Minitest::Test
         | 
| 4 | 
            +
              def test_bad_notation_error
         | 
| 5 | 
            +
                game = Chess::Game.new
         | 
| 6 | 
            +
                assert_raises(Chess::BadNotationError) do
         | 
| 7 | 
            +
                  game << 'gg'
         | 
| 8 | 
            +
                end
         | 
| 9 | 
            +
              end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
              def test_invalid_pgn_format_error
         | 
| 12 | 
            +
                assert_raises(Chess::InvalidPgnFormatError) do
         | 
| 13 | 
            +
                  Chess::Pgn.new('test/pgn_collection/invalid/0001.pgn')
         | 
| 14 | 
            +
                end
         | 
| 15 | 
            +
              end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
              def test_invalid_fen_format_error
         | 
| 18 | 
            +
                assert_raises(Chess::InvalidFenFormatError) do
         | 
| 19 | 
            +
                  Chess::Game.load_fen('invalid')
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
              end
         | 
| 22 | 
            +
            end
         | 
| @@ -3,10 +3,10 @@ require 'test_helper' | |
| 3 3 | 
             
            class ChessTest < Minitest::Test
         | 
| 4 4 | 
             
              TestHelper.pgns('fifty_move_rule').each do |file|
         | 
| 5 5 | 
             
                name = File.basename(file, '.pgn')
         | 
| 6 | 
            -
                define_method " | 
| 6 | 
            +
                define_method "test_fifty_move_rule_#{name}" do
         | 
| 7 7 | 
             
                  pgn = Chess::Pgn.new(file)
         | 
| 8 8 | 
             
                  game = Chess::Game.new(pgn.moves)
         | 
| 9 | 
            -
                  assert | 
| 9 | 
            +
                  assert game.board.fifty_move_rule?
         | 
| 10 10 | 
             
                end
         | 
| 11 11 | 
             
              end
         | 
| 12 12 | 
             
            end
         | 
    
        data/test/test_game.rb
    ADDED
    
    | @@ -0,0 +1,82 @@ | |
| 1 | 
            +
            require 'test_helper'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            class ChessTest < Minitest::Test
         | 
| 4 | 
            +
              def test_moves
         | 
| 5 | 
            +
                game = Chess::Game.new
         | 
| 6 | 
            +
                game.moves = %w[e4 e5]
         | 
| 7 | 
            +
                assert_equal %w[e4 e5], game.moves
         | 
| 8 | 
            +
              end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
              def test_active_player
         | 
| 11 | 
            +
                game = Chess::Game.new
         | 
| 12 | 
            +
                game << 'a3'
         | 
| 13 | 
            +
                assert_equal :black, game.active_player
         | 
| 14 | 
            +
              end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
              def test_inactive_player
         | 
| 17 | 
            +
                game = Chess::Game.new
         | 
| 18 | 
            +
                game.moves = %w[a3 f5]
         | 
| 19 | 
            +
                assert_equal :black, game.inactive_player
         | 
| 20 | 
            +
              end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
              def test_status_white_won_resign
         | 
| 23 | 
            +
                game = Chess::Game.new
         | 
| 24 | 
            +
                game.resign(:black)
         | 
| 25 | 
            +
                assert_equal :white_won_resign, game.status
         | 
| 26 | 
            +
                assert game.over?
         | 
| 27 | 
            +
                assert_equal '1-0', game.result
         | 
| 28 | 
            +
              end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
              def test_status_black_won_resign
         | 
| 31 | 
            +
                game = Chess::Game.new
         | 
| 32 | 
            +
                game << 'e4'
         | 
| 33 | 
            +
                game.resign(:white)
         | 
| 34 | 
            +
                assert_equal :black_won_resign, game.status
         | 
| 35 | 
            +
                assert game.over?
         | 
| 36 | 
            +
                assert_equal '0-1', game.result
         | 
| 37 | 
            +
              end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
              def test_status_insufficient_material
         | 
| 40 | 
            +
                pgn = TestHelper.pick_pgn('insufficient_material/0001.pgn')
         | 
| 41 | 
            +
                game = Chess::Game.new(pgn.moves)
         | 
| 42 | 
            +
                assert_equal :insufficient_material, game.status
         | 
| 43 | 
            +
              end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
              def test_status_fifty_move_rule
         | 
| 46 | 
            +
                pgn = TestHelper.pick_pgn('fifty_move_rule/0001.pgn')
         | 
| 47 | 
            +
                game = Chess::Game.new(pgn.moves)
         | 
| 48 | 
            +
                game.draw
         | 
| 49 | 
            +
                assert_equal :fifty_move_rule, game.status
         | 
| 50 | 
            +
              end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
              def test_status_threefold_repetition
         | 
| 53 | 
            +
                pgn = TestHelper.pick_pgn('threefold_repetition/0001.pgn')
         | 
| 54 | 
            +
                game = Chess::Game.new(pgn.moves)
         | 
| 55 | 
            +
                game.draw
         | 
| 56 | 
            +
                assert_equal :threefold_repetition, game.status
         | 
| 57 | 
            +
              end
         | 
| 58 | 
            +
             | 
| 59 | 
            +
              def test_pgn
         | 
| 60 | 
            +
                pgn = TestHelper.pick_pgn('valid/0001.pgn')
         | 
| 61 | 
            +
                game = Chess::Game.new(pgn.moves)
         | 
| 62 | 
            +
                expected_pgn = <<~PGN
         | 
| 63 | 
            +
                  [Event ""]
         | 
| 64 | 
            +
                  [Site ""]
         | 
| 65 | 
            +
                  [Date "??"]
         | 
| 66 | 
            +
                  [Round ""]
         | 
| 67 | 
            +
                  [White ""]
         | 
| 68 | 
            +
                  [Black ""]
         | 
| 69 | 
            +
                  [Result "*"]
         | 
| 70 | 
            +
             | 
| 71 | 
            +
                  1. e4 c5 2. Nf3 d6 3. d4 cxd4 4. Qxd4 Nc6 5. Bb5 Bd7 6. Bxc6 Bxc6 7. Bg5 Nf6
         | 
| 72 | 
            +
                  8. Bxf6 gxf6 9. Nc3 e6 10. O-O-O Be7 11. Rhe1 Rg8 12. Qe3 Rxg2 13. Rg1 Rg6 14.
         | 
| 73 | 
            +
                  Nd4 Qb6 15. h4 O-O-O 16. h5 Rg5 17. Nd5 Bxd5 18. exd5 e5 19. Qh3+ Kb8 20. Nf5
         | 
| 74 | 
            +
                  Bf8 21. Rxg5 fxg5 22. Ne3 Qb4 23. c4 g4 24. Qxg4 Bh6 25. Kb1 Rc8 26. Rc1 Qd2
         | 
| 75 | 
            +
                  27. Qf5 Bf4 28. Qc2 Qxc2+ 29. Rxc2 Rg8 30. b4 Rg5 31. c5 e4 32. c6 Rxh5 33.
         | 
| 76 | 
            +
                  Rc4 f5 34. b5 Rg5 35. Rc3 Be5 36. Rc1 Bd4 37. Nc4 Bc5 38. Ne5 dxe5 39. Rxc5
         | 
| 77 | 
            +
                  Rg6 40. a4 Rd6 41. a5 b6 42. axb6 axb6 43. Rc2 Rxd5 44. Rb2 h5 45. Ka2 h4 46.
         | 
| 78 | 
            +
                  Ka3 h3 47. Ka4 Rd1 *
         | 
| 79 | 
            +
                PGN
         | 
| 80 | 
            +
                assert_equal expected_pgn, game.pgn.to_s
         | 
| 81 | 
            +
              end
         | 
| 82 | 
            +
            end
         | 
    
        data/test/test_helper.rb
    CHANGED
    
    | @@ -1,5 +1,15 @@ | |
| 1 | 
            +
            require 'simplecov'
         | 
| 2 | 
            +
            SimpleCov.start do
         | 
| 3 | 
            +
              add_filter 'lib/chess/gnuchess.rb'
         | 
| 4 | 
            +
            end
         | 
| 5 | 
            +
            if ENV['CODECOV'] == 'true'
         | 
| 6 | 
            +
              require 'codecov'
         | 
| 7 | 
            +
              SimpleCov.formatter = SimpleCov::Formatter::Codecov
         | 
| 8 | 
            +
            end
         | 
| 9 | 
            +
             | 
| 1 10 | 
             
            require 'chess'
         | 
| 2 11 | 
             
            require 'minitest/autorun'
         | 
| 12 | 
            +
            require 'byebug'
         | 
| 3 13 |  | 
| 4 14 | 
             
            module TestHelper
         | 
| 5 15 | 
             
              PGN_COLLECTION = 'test/pgn_collection'.freeze
         | 
| @@ -8,4 +18,9 @@ module TestHelper | |
| 8 18 | 
             
              def self.pgns(path, prefix = PGN_COLLECTION)
         | 
| 9 19 | 
             
                Dir[File.join(prefix, path, '**/*.pgn')]
         | 
| 10 20 | 
             
              end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
              def self.pick_pgn(path, prefix = PGN_COLLECTION)
         | 
| 23 | 
            +
                file = File.join(prefix, path)
         | 
| 24 | 
            +
                return Chess::Pgn.new(file)
         | 
| 25 | 
            +
              end
         | 
| 11 26 | 
             
            end
         | 
| @@ -17,15 +17,15 @@ class ChessTest < Minitest::Test | |
| 17 17 | 
             
              FENS.each_with_index do |fen, i|
         | 
| 18 18 | 
             
                define_method("test_insufficient_material_by_fen_#{i}") do
         | 
| 19 19 | 
             
                  game = Chess::Game.load_fen(fen)
         | 
| 20 | 
            -
                  assert | 
| 20 | 
            +
                  assert game.board.insufficient_material?
         | 
| 21 21 | 
             
                end
         | 
| 22 22 | 
             
              end
         | 
| 23 23 |  | 
| 24 24 | 
             
              ONLY_KINGS_FENS.each_with_index do |fen, i|
         | 
| 25 25 | 
             
                define_method("test_only_kings_by_fen_#{i}") do
         | 
| 26 26 | 
             
                  game = Chess::Game.load_fen(fen)
         | 
| 27 | 
            -
                  assert | 
| 28 | 
            -
                  assert | 
| 27 | 
            +
                  assert game.board.insufficient_material?
         | 
| 28 | 
            +
                  assert game.board.only_kings?
         | 
| 29 29 | 
             
                end
         | 
| 30 30 | 
             
              end
         | 
| 31 31 |  | 
| @@ -34,7 +34,7 @@ class ChessTest < Minitest::Test | |
| 34 34 | 
             
                define_method "test_insufficient_material_#{name}" do
         | 
| 35 35 | 
             
                  pgn = Chess::Pgn.new(file)
         | 
| 36 36 | 
             
                  game = Chess::Game.new(pgn.moves)
         | 
| 37 | 
            -
                  assert | 
| 37 | 
            +
                  assert game.board.insufficient_material?
         | 
| 38 38 | 
             
                end
         | 
| 39 39 | 
             
              end
         | 
| 40 40 | 
             
            end
         | 
    
        data/test/test_move_generator.rb
    CHANGED
    
    | @@ -2,7 +2,7 @@ require 'test_helper' | |
| 2 2 |  | 
| 3 3 | 
             
            class ChessTest < Minitest::Test
         | 
| 4 4 | 
             
              GENS = {
         | 
| 5 | 
            -
                'r2qk3/8/2n5/8/8/8/p2B4/4K2R w K - 0 1' => { 'e1' => [ | 
| 5 | 
            +
                'r2qk3/8/2n5/8/8/8/p2B4/4K2R w K - 0 1' => { 'e1' => %w[Kd1 Ke2 Kf2 Kf1 O-O] },
         | 
| 6 6 | 
             
                'r2qk3/8/2n5/8/8/8/p2B4/4K2R w - - 0 1' => { 'e1' => %w[Kd1 Ke2 Kf2 Kf1] },
         | 
| 7 7 | 
             
                'r2qk3/8/2n5/2b5/8/8/p2B4/4K2R w K - 0 1' => { 'e1' => %w[Kd1 Ke2 Kf1] },
         | 
| 8 8 | 
             
                'r2qk3/8/2n5/2b5/8/8/p2B4/4K2R b K - 0 1' => { 'a2' => ['a1=Q'] },
         | 
| @@ -12,7 +12,8 @@ class ChessTest < Minitest::Test | |
| 12 12 | 
             
                '1B1k4/r3q3/2n1n3/2b2pP1/8/8/2nB4/3K3R b - - 0 1' => { 'c6' => %w[Nxb8 Ne5 Nc6d4 N6b4 Na5] },
         | 
| 13 13 | 
             
                '1B1k4/r3q3/2n1n2P/2b5/5p2/3p1RP1/3B4/3K4 w - - 0 1' => { 'f3' => %w[Rxd3 Re3 Rxf4 Rf2 Rf1] },
         | 
| 14 14 | 
             
                'k7/1Q6/2P5/8/8/8/8/3K4 b - - 0 1' => { 'a8' => [] },
         | 
| 15 | 
            -
                'k7/6P1/8/8/8/3r4/3Q4/3K4 w - - 0 1' => { 'd2' => ['Qxd3'] }
         | 
| 15 | 
            +
                'k7/6P1/8/8/8/3r4/3Q4/3K4 w - - 0 1' => { 'd2' => ['Qxd3'] },
         | 
| 16 | 
            +
                'rnbqkbnr/1ppppppp/8/8/pP2P3/P7/2PP1PPP/RNBQKBNR w KQkq - 0 3' => { 'c2' => %w[c3 c4] }
         | 
| 16 17 | 
             
              }.freeze
         | 
| 17 18 |  | 
| 18 19 | 
             
              GENS.each do |fen, generators|
         | 
    
        data/test/test_pgn.rb
    CHANGED
    
    | @@ -67,4 +67,39 @@ class ChessTest < Minitest::Test | |
| 67 67 | 
             
                assert_nil pgn.result
         | 
| 68 68 | 
             
                assert_equal 'Re6', pgn.moves.last
         | 
| 69 69 | 
             
              end
         | 
| 70 | 
            +
             | 
| 71 | 
            +
              def test_write_pgn
         | 
| 72 | 
            +
                tempfile = Tempfile.new('test_pgn')
         | 
| 73 | 
            +
                game = Chess::Game.new
         | 
| 74 | 
            +
                game.moves = %w[e4 e5 d3]
         | 
| 75 | 
            +
                pgn = game.pgn
         | 
| 76 | 
            +
                pgn.event = 'Ruby Chess Tournament'
         | 
| 77 | 
            +
                pgn.site = 'Ruby Chess test suite'
         | 
| 78 | 
            +
                pgn.date = '1984.10.21'
         | 
| 79 | 
            +
                pgn.round = '2'
         | 
| 80 | 
            +
                pgn.white = 'Pioz'
         | 
| 81 | 
            +
                pgn.black = 'Elizabeth Harmon'
         | 
| 82 | 
            +
                pgn.write(tempfile.path)
         | 
| 83 | 
            +
             | 
| 84 | 
            +
                loaded_pgn = Chess::Pgn.new
         | 
| 85 | 
            +
                loaded_pgn.load(tempfile.path)
         | 
| 86 | 
            +
                tempfile.delete
         | 
| 87 | 
            +
                assert_equal 'Ruby Chess Tournament', loaded_pgn.event
         | 
| 88 | 
            +
                assert_equal 'Ruby Chess test suite', loaded_pgn.site
         | 
| 89 | 
            +
                assert_equal '1984.10.21', loaded_pgn.date
         | 
| 90 | 
            +
                assert_equal '2', loaded_pgn.round
         | 
| 91 | 
            +
                assert_equal 'Pioz', loaded_pgn.white
         | 
| 92 | 
            +
                assert_equal 'Elizabeth Harmon', loaded_pgn.black
         | 
| 93 | 
            +
                assert_equal '*', loaded_pgn.result
         | 
| 94 | 
            +
                assert_equal %w[e4 e5 d3], loaded_pgn.moves
         | 
| 95 | 
            +
              end
         | 
| 96 | 
            +
             | 
| 97 | 
            +
              def test_set_date
         | 
| 98 | 
            +
                pgn = Chess::Pgn.new
         | 
| 99 | 
            +
                pgn.date = Time.parse('21-10-1984')
         | 
| 100 | 
            +
                assert_equal '1984.10.21', pgn.date
         | 
| 101 | 
            +
             | 
| 102 | 
            +
                pgn.date = '1984.10.21'
         | 
| 103 | 
            +
                assert_equal '1984.10.21', pgn.date
         | 
| 104 | 
            +
              end
         | 
| 70 105 | 
             
            end
         | 
    
        data/test/test_pgn_collection.rb
    CHANGED
    
    | @@ -8,8 +8,8 @@ class ChessTest < Minitest::Test | |
| 8 8 | 
             
                  game = Chess::Game.new
         | 
| 9 9 | 
             
                  pgn.moves.each do |m|
         | 
| 10 10 | 
             
                    game.move(m)
         | 
| 11 | 
            -
                    assert(game.board.check?) if m | 
| 12 | 
            -
                    assert(game.board.checkmate?) if m | 
| 11 | 
            +
                    assert(game.board.check?) if m.match?(/\+$/)
         | 
| 12 | 
            +
                    assert(game.board.checkmate?) if m.match?(/\#$/)
         | 
| 13 13 | 
             
                  end
         | 
| 14 14 | 
             
                end
         | 
| 15 15 | 
             
                define_method "test_pgn_result_#{name}" do
         |