chess 0.2.0 → 0.3.2

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.
Files changed (70) hide show
  1. checksums.yaml +5 -5
  2. data/.github/FUNDING.yml +12 -0
  3. data/.github/workflows/ruby.yml +58 -0
  4. data/.gitignore +2 -2
  5. data/.rubocop.yml +51 -0
  6. data/Gemfile +1 -1
  7. data/Gemfile.lock +70 -0
  8. data/README.md +26 -22
  9. data/Rakefile +5 -4
  10. data/chess.gemspec +15 -11
  11. data/docs/Chess.html +157 -0
  12. data/docs/Chess/BadNotationError.html +237 -0
  13. data/docs/Chess/Board.html +1759 -0
  14. data/docs/Chess/CGame.html +2296 -0
  15. data/docs/Chess/Game.html +1277 -0
  16. data/docs/Chess/Gnuchess.html +366 -0
  17. data/docs/Chess/IllegalMoveError.html +137 -0
  18. data/docs/Chess/InvalidFenFormatError.html +237 -0
  19. data/docs/Chess/InvalidPgnFormatError.html +217 -0
  20. data/docs/Chess/Pgn.html +1477 -0
  21. data/docs/Chess/UTF8Notation.html +270 -0
  22. data/docs/_index.html +217 -0
  23. data/docs/class_list.html +51 -0
  24. data/docs/css/common.css +1 -0
  25. data/docs/css/full_list.css +58 -0
  26. data/docs/css/style.css +497 -0
  27. data/docs/file.README.html +116 -0
  28. data/docs/file_list.html +56 -0
  29. data/docs/frames.html +17 -0
  30. data/docs/index.html +116 -0
  31. data/docs/js/app.js +314 -0
  32. data/docs/js/full_list.js +216 -0
  33. data/docs/js/jquery.js +4 -0
  34. data/docs/method_list.html +531 -0
  35. data/docs/top-level-namespace.html +110 -0
  36. data/ext/bitboard.c +1 -1
  37. data/ext/bitboard.h +1 -1
  38. data/ext/board.c +1 -1
  39. data/ext/board.h +1 -1
  40. data/ext/chess.c +2 -2
  41. data/ext/chess.h +1 -1
  42. data/ext/common.c +1 -1
  43. data/ext/common.h +8 -4
  44. data/ext/extconf.rb +7 -6
  45. data/ext/game.c +2 -2
  46. data/ext/game.h +1 -1
  47. data/ext/special.c +10 -3
  48. data/ext/special.h +3 -2
  49. data/lib/chess/exceptions.rb +2 -5
  50. data/lib/chess/game.rb +55 -77
  51. data/lib/chess/gnuchess.rb +24 -27
  52. data/lib/chess/pgn.rb +32 -26
  53. data/lib/chess/utf8_notation.rb +3 -3
  54. data/lib/chess/version.rb +1 -1
  55. data/test/test_big_pgn_collection.rb +3 -4
  56. data/test/test_checkmate.rb +4 -6
  57. data/test/test_errors.rb +22 -0
  58. data/test/test_fifty_rule_move.rb +2 -4
  59. data/test/test_game.rb +82 -0
  60. data/test/test_helper.rb +16 -3
  61. data/test/test_illegal_moves.rb +1 -3
  62. data/test/test_insufficient_material.rb +6 -7
  63. data/test/test_load_fen.rb +11 -1
  64. data/test/test_move_generator.rb +13 -14
  65. data/test/test_particular_situations.rb +0 -2
  66. data/test/test_pgn.rb +82 -1
  67. data/test/test_pgn_collection.rb +2 -2
  68. data/test/test_stalemate.rb +1 -3
  69. data/test/test_threefold_repetition.rb +1 -3
  70. metadata +145 -15
@@ -1,7 +1,6 @@
1
1
  require 'mkmf'
2
2
 
3
3
  module Chess
4
-
5
4
  # Use Gnuchess to I.A. _(Only a draft)_.
6
5
  #
7
6
  # To use this module, extend a game object with {Chess::Gnuchess}.
@@ -12,7 +11,6 @@ module Chess
12
11
  # g.gnuchess_move!
13
12
  # puts g
14
13
  module Gnuchess
15
-
16
14
  # Returns the next move calculated by Gnuchess.
17
15
  # @return [String] Returns the short algebraic chess notation of the move.
18
16
  def gnuchess_move
@@ -24,8 +22,9 @@ module Chess
24
22
  pipe.puts(m)
25
23
  end
26
24
  pipe.puts('go')
27
- while line = pipe.gets
25
+ while (line = pipe.gets)
28
26
  raise IllegalMoveError if line.include?('Invalid move')
27
+
29
28
  match = line.match(/My move is : ([a-h][1-8][a-h][1-8][rkbq]?)/)
30
29
  return match[1] if match
31
30
  end
@@ -44,16 +43,25 @@ module Chess
44
43
  self.move(next_move) if next_move
45
44
  end
46
45
 
47
- private
46
+ class << self
47
+ private
48
48
 
49
- def self.included(mod)
50
- raise_if_gnuchess_is_not_installed
51
- end
49
+ def included(_mod)
50
+ raise_if_gnuchess_is_not_installed
51
+ end
52
+
53
+ def extended(_mod)
54
+ raise_if_gnuchess_is_not_installed
55
+ end
52
56
 
53
- def self.extended(mod)
54
- raise_if_gnuchess_is_not_installed
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
55
61
  end
56
62
 
63
+ private
64
+
57
65
  def gen_pgn(file_to_save, moves = [])
58
66
  done = false
59
67
  pipe = IO.popen('gnuchess', 'r+')
@@ -64,20 +72,18 @@ module Chess
64
72
  moves.each do |move|
65
73
  pipe.write("#{move}\n")
66
74
  end
67
- while(!done)
75
+ until done
68
76
  pipe.write("go\n")
69
- while line = pipe.gets
70
- if line =~ /My move is : /
71
- break
72
- elsif line =~ / : resign/
73
- break
74
- elsif line =~ / : 1-0 {White mates}/
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)
75
81
  done = :white_won
76
82
  break
77
- elsif line =~ / : 0-1 {Black mates}/
83
+ elsif / : 0-1 {Black mates}/.match?(line)
78
84
  done = :black_won
79
85
  break
80
- elsif m = line.match(/1\/2-1\/2 {(.*?)}/)
86
+ elsif (m = line.match(/1\/2-1\/2 {(.*?)}/))
81
87
  case m[1]
82
88
  when 'Stalemate'
83
89
  done = :stalemate
@@ -100,14 +106,5 @@ module Chess
100
106
  pipe.close
101
107
  end
102
108
  end
103
-
104
- # Raise an exception if Gnuchess is not installed
105
- def self.raise_if_gnuchess_is_not_installed
106
- unless find_executable0('gnuchess')
107
- raise 'You must install Gnuchess to use the module Chess::Gnuchess!'
108
- end
109
- end
110
-
111
109
  end
112
-
113
110
  end
@@ -1,10 +1,8 @@
1
1
  module Chess
2
-
3
2
  # Rappresents a game in PGN (Portable Game Notation) format.
4
3
  class Pgn
5
-
6
4
  # Array that include PGN standard tags.
7
- TAGS = %w(event site date round white black result)
5
+ TAGS = %w[event site date round white black result].freeze
8
6
 
9
7
  # The name of the tournament or match event.
10
8
  # @return [String]
@@ -16,7 +14,7 @@ module Chess
16
14
  attr_accessor :site
17
15
  # The starting date of the game, in YYYY.MM.DD form. ?? is used for unknown values.
18
16
  # @return [String]
19
- attr_accessor :date
17
+ attr_reader :date
20
18
  # The playing round ordinal of the game within the event.
21
19
  # @return [String]
22
20
  attr_accessor :round
@@ -42,7 +40,6 @@ module Chess
42
40
  def initialize(filename = nil, check_moves: false)
43
41
  self.load(filename, check_moves: check_moves) if filename
44
42
  @date = '??'
45
- @round = '1'
46
43
  end
47
44
 
48
45
  # Load a PGN from file.
@@ -52,21 +49,30 @@ module Chess
52
49
  # @raise [InvalidPgnFormatError]
53
50
  # @raise [IllegalMoveError]
54
51
  def load(filename, check_moves: false)
55
- data = File.open(filename, 'r').read
56
- data.gsub!(/\{.*?\}/, '') # remove comments
52
+ str = File.open(filename, 'r').read
53
+ load_from_string(str, check_moves: check_moves)
54
+ end
55
+
56
+ # Load a PGN from string.
57
+ # @param [String] str The PGN string to load.
58
+ # @param [Boolean] check_moves If true check if the moves are legal.
59
+ # @return [Pgn] Returns `self`.
60
+ # @raise [InvalidPgnFormatError]
61
+ # @raise [IllegalMoveError]
62
+ def load_from_string(str, check_moves: false)
63
+ str.gsub!(/\{.*?\}/, '') # remove comments
57
64
  TAGS.each do |t|
58
- instance_variable_set("@#{t}", data.match(/^\[#{t.capitalize} ".*"\]\s?$/).to_s.strip[t.size+3..-3])
65
+ instance_variable_set("@#{t}", str.match(/^\[#{t.capitalize} ".*"\]\s?$/).to_s.strip[t.size + 3..-3])
59
66
  end
60
67
  @result = '1/2-1/2' if @result == '1/2'
61
- game_index = data.index(/^1\./)
62
- raise Chess::InvalidPgnFormatError.new(filename) if game_index.nil?
63
- game = data[game_index..-1].strip
64
- @moves = game.gsub("\n", ' ').split(/\d+\./).collect{|t| t.strip}[1..-1].collect{|t| t.split(' ')}.flatten
65
- @moves.delete_at(@moves.size-1) if @moves.last =~ /(0-1)|(1-0)|(1\/2)|(1\/2-1\/2)|(\*)/
68
+ game_index = str.index(/^1\./)
69
+ raise Chess::InvalidPgnFormatError.new if game_index.nil?
70
+
71
+ game = str[game_index..-1].strip
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)|(\*)/)
66
74
  @moves.each do |m|
67
- if m !~ MOVE_REGEXP && m !~ SHORT_CASTLING_REGEXP && m !~ LONG_CASTLING_REGEXP
68
- raise Chess::InvalidPgnFormatError.new(filename)
69
- end
75
+ raise Chess::InvalidPgnFormatError.new if m !~ MOVE_REGEXP && m !~ SHORT_CASTLING_REGEXP && m !~ LONG_CASTLING_REGEXP
70
76
  end
71
77
  Chess::Game.new(@moves) if check_moves
72
78
  return self
@@ -76,13 +82,13 @@ module Chess
76
82
  def to_s
77
83
  s = ''
78
84
  TAGS.each do |t|
79
- tag = instance_variable_get("@#{t}")
85
+ tag = instance_variable_defined?("@#{t}") ? instance_variable_get("@#{t}") : ''
80
86
  s << "[#{t.capitalize} \"#{tag}\"]\n"
81
87
  end
82
88
  s << "\n"
83
89
  m = ''
84
90
  @moves.each_with_index do |move, i|
85
- m << "#{i/2+1}. " if i % 2 == 0
91
+ m << "#{i / 2 + 1}. " if i.even?
86
92
  m << "#{move} "
87
93
  end
88
94
  m << @result unless @result.nil?
@@ -95,17 +101,17 @@ module Chess
95
101
  File.open(filename, 'w') { |f| f.write(self.to_s) }
96
102
  end
97
103
 
98
- # @!visibility private
99
- alias :old_date= :date=
104
+ # # @!visibility private
105
+ # alias old_date= date=
100
106
 
101
107
  # Set the date tag.
102
108
  def date=(value)
103
- if value.is_a?(Time)
104
- @date = value.strftime('%Y.%m.%d')
105
- else
106
- @data = value
107
- end
109
+ @date =
110
+ if value.is_a?(Time)
111
+ value.strftime('%Y.%m.%d')
112
+ else
113
+ value
114
+ end
108
115
  end
109
-
110
116
  end
111
117
  end
@@ -1,4 +1,5 @@
1
1
  module Chess
2
+ # rubocop:disable Style/AsciiComments
2
3
 
3
4
  # With this module is possible call the method {#to_utf8} on a string. This
4
5
  # method convert the chess piece identifier character into UTF8 chess
@@ -12,7 +13,6 @@ module Chess
12
13
  # @note To use this utility explicit require is needed: <tt>require
13
14
  # 'chess/utf8_notation'</tt>
14
15
  module UTF8Notation
15
-
16
16
  # Map a piece identifier character with the corresponding UTF8 chess
17
17
  # character
18
18
  UTF8_MAP = {
@@ -28,15 +28,15 @@ module Chess
28
28
  'b' => '♝',
29
29
  'q' => '♛',
30
30
  'k' => '♚'
31
- }
31
+ }.freeze
32
32
 
33
33
  # Replace the piece identifier characters with UTF8 chess characters.
34
34
  # @return [String]
35
35
  def to_utf8
36
36
  self.gsub(/[PRNBQKprnbqk]/, UTF8_MAP)
37
37
  end
38
-
39
38
  end
39
+ # rubocop:enable Style/AsciiComments
40
40
  end
41
41
 
42
42
  String.prepend(Chess::UTF8Notation)
@@ -1,5 +1,5 @@
1
1
  # The Chess library module.
2
2
  module Chess
3
3
  # The library version.
4
- VERSION = '0.2.0'
4
+ VERSION = '0.3.2'.freeze
5
5
  end
@@ -1,19 +1,18 @@
1
1
  require 'test_helper'
2
2
 
3
3
  class ChessTest < Minitest::Test
4
-
5
4
  if ENV['BIG_PGN_COLLECTION'] && File.exist?(TestHelper::BIG_PGN_COLLECTION)
6
5
  puts 'Loading tests for a big PGN collection...'
7
6
  300_000.times do |i|
8
- filename = sprintf("%06d.pgn", i+1)
7
+ filename = format('%06d.pgn', i + 1)
9
8
  path = File.join(TestHelper::BIG_PGN_COLLECTION, filename)
10
9
  break unless File.exist?(path)
10
+
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
18
-
19
18
  end
@@ -1,7 +1,6 @@
1
1
  require 'test_helper'
2
2
 
3
3
  class ChessTest < Minitest::Test
4
-
5
4
  TestHelper.pgns('checkmate').each do |file|
6
5
  name = File.basename(file, '.pgn')
7
6
  win = file.include?('white') ? 'white' : 'black'
@@ -10,13 +9,12 @@ class ChessTest < Minitest::Test
10
9
  game = Chess::Game.new(pgn.moves)
11
10
  assert(game.board.checkmate?)
12
11
  if file.include?('white_won')
13
- assert_equal(game.result, '1-0')
14
- assert_equal(game.active_player, :black)
12
+ assert_equal('1-0', game.result)
13
+ assert_equal(:black, game.active_player)
15
14
  elsif file.include?('black_won')
16
- assert_equal(game.result, '0-1')
17
- assert_equal(game.active_player, :white)
15
+ assert_equal('0-1', game.result)
16
+ assert_equal(:white, game.active_player)
18
17
  end
19
18
  end
20
19
  end
21
-
22
20
  end
@@ -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
@@ -1,14 +1,12 @@
1
1
  require 'test_helper'
2
2
 
3
3
  class ChessTest < Minitest::Test
4
-
5
4
  TestHelper.pgns('fifty_move_rule').each do |file|
6
5
  name = File.basename(file, '.pgn')
7
- define_method "test_fifty_rule_move_#{name}" do
6
+ define_method "test_fifty_move_rule_#{name}" do
8
7
  pgn = Chess::Pgn.new(file)
9
8
  game = Chess::Game.new(pgn.moves)
10
- assert(game.board.fifty_rule_move?)
9
+ assert game.board.fifty_move_rule?
11
10
  end
12
11
  end
13
-
14
12
  end
@@ -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 '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 '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
@@ -1,13 +1,26 @@
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
-
6
- PGN_COLLECTION = 'test/pgn_collection'
7
- BIG_PGN_COLLECTION = '/Users/pioz/Code/prog/misc/pgn_collection/pgn'
15
+ PGN_COLLECTION = 'test/pgn_collection'.freeze
16
+ BIG_PGN_COLLECTION = '/Users/pioz/Code/prog/misc/pgn_collection/pgn'.freeze
8
17
 
9
18
  def self.pgns(path, prefix = PGN_COLLECTION)
10
19
  Dir[File.join(prefix, path, '**/*.pgn')]
11
20
  end
12
21
 
22
+ def self.pick_pgn(path, prefix = PGN_COLLECTION)
23
+ file = File.join(prefix, path)
24
+ return Chess::Pgn.new(file)
25
+ end
13
26
  end