bchess 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +11 -0
- data/.rspec +3 -0
- data/.travis.yml +5 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +45 -0
- data/LICENSE.txt +21 -0
- data/README.md +65 -0
- data/Rakefile +6 -0
- data/bchess.gemspec +29 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/full_test.sh +1 -0
- data/lib/bchess.rb +33 -0
- data/lib/bchess/bishop.rb +17 -0
- data/lib/bchess/board.rb +131 -0
- data/lib/bchess/game.rb +21 -0
- data/lib/bchess/helpers/board_helpers.rb +43 -0
- data/lib/bchess/helpers/castle_helpers.rb +58 -0
- data/lib/bchess/helpers/en_passant_helpers.rb +22 -0
- data/lib/bchess/helpers/fen_helpers.rb +141 -0
- data/lib/bchess/helpers/field_between_helpers.rb +50 -0
- data/lib/bchess/helpers/validations.rb +16 -0
- data/lib/bchess/king.rb +30 -0
- data/lib/bchess/knight.rb +27 -0
- data/lib/bchess/pawn.rb +54 -0
- data/lib/bchess/piece.rb +133 -0
- data/lib/bchess/queen.rb +21 -0
- data/lib/bchess/rook.rb +25 -0
- data/lib/bchess/version.rb +3 -0
- data/lib/pgn/game.rb +18 -0
- data/lib/pgn/game_body.rb +111 -0
- data/lib/pgn/game_header.rb +55 -0
- data/lib/pgn/move_info_parser.rb +106 -0
- data/lib/pgn/parser.rb +42 -0
- data/lib/pgn/pgn_file.rb +16 -0
- data/lib/pgn/pgn_nodes.rb +84 -0
- data/lib/pgn/pgn_rules.treetop +127 -0
- data/test.sh +1 -0
- metadata +153 -0
data/lib/bchess/game.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
module Bchess
|
2
|
+
class Game
|
3
|
+
attr_reader :fen, :board, :moves
|
4
|
+
|
5
|
+
def initialize(fen='')
|
6
|
+
@fen = fen
|
7
|
+
@board = Board.new(fen)
|
8
|
+
@moves = []
|
9
|
+
end
|
10
|
+
|
11
|
+
def add_move(move)
|
12
|
+
moves << move
|
13
|
+
end
|
14
|
+
|
15
|
+
def validate_game
|
16
|
+
moves.all? do |move|
|
17
|
+
board.execute(move)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Bchess
|
2
|
+
module BoardHelpers
|
3
|
+
def field(column, row)
|
4
|
+
(column+97).chr + (row+1).to_s
|
5
|
+
end
|
6
|
+
|
7
|
+
def invalid_data?(piece, column, row)
|
8
|
+
piece.nil? || !(0..7).include?(column) || !(0..7).include?(row)
|
9
|
+
end
|
10
|
+
|
11
|
+
def castle_detected?(piece, column)
|
12
|
+
piece.kind_of?(Bchess::King) && piece.column == 4 && (column - piece.column).abs == 2
|
13
|
+
end
|
14
|
+
|
15
|
+
def promotion_detected?(piece, row)
|
16
|
+
piece.kind_of?(Bchess::Pawn) && (row == 0 || row == 7)
|
17
|
+
end
|
18
|
+
|
19
|
+
def en_passant_detected?(piece, column, row)
|
20
|
+
piece.kind_of?(Bchess::Pawn) && piece.row != row && piece.column != column && at(column, row).nil? && piece.can_take_on_field?(column, row)
|
21
|
+
end
|
22
|
+
|
23
|
+
def pawn_long_move_detected?(piece, row)
|
24
|
+
piece.kind_of?(Bchess::Pawn) && (piece.row - row).abs == 2
|
25
|
+
end
|
26
|
+
|
27
|
+
def kings_present?
|
28
|
+
!!king(Bchess::WHITE) && !!king(Bchess::BLACK)
|
29
|
+
end
|
30
|
+
|
31
|
+
def to_row(row)
|
32
|
+
row.to_i - 1
|
33
|
+
end
|
34
|
+
|
35
|
+
def to_column(column)
|
36
|
+
column.bytes.first - 97
|
37
|
+
end
|
38
|
+
|
39
|
+
def field(column, row)
|
40
|
+
(column+97).chr + (row+1).to_s
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module CastleHelpers
|
2
|
+
def castle(piece, column, row)
|
3
|
+
validate_castle(piece, column)
|
4
|
+
update_castles_after_king_move(piece.color)
|
5
|
+
execute_castle(piece, column, row)
|
6
|
+
end
|
7
|
+
|
8
|
+
def validate_castle(piece, column)
|
9
|
+
!piece.moved && !rook_moved?(piece, column) && fen_allows?(piece,column)
|
10
|
+
end
|
11
|
+
|
12
|
+
def execute_castle(piece, column, row)
|
13
|
+
piece.move(column, row)
|
14
|
+
lngth = @short_castle ? 2 : 3
|
15
|
+
@castling_rook = castling_rook(piece, column)
|
16
|
+
@castling_rook.move((@castling_rook.column - lngth).abs, row)
|
17
|
+
end
|
18
|
+
|
19
|
+
def white_castle(column)
|
20
|
+
if short_castle?(column)
|
21
|
+
@short_castle = true
|
22
|
+
at(7,0)
|
23
|
+
else
|
24
|
+
@short_castle = false
|
25
|
+
at(0,0)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def black_castle(column)
|
30
|
+
if short_castle?(column)
|
31
|
+
@short_castle = true
|
32
|
+
at(7,7)
|
33
|
+
else
|
34
|
+
@short_castle = false
|
35
|
+
at(0,7)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def rook_moved?(piece, column)
|
40
|
+
castling_rook(piece, column)&.moved
|
41
|
+
end
|
42
|
+
|
43
|
+
def castling_rook(piece, column)
|
44
|
+
select_rook(piece, column)
|
45
|
+
end
|
46
|
+
|
47
|
+
def select_rook(piece, column)
|
48
|
+
if piece.row == 0
|
49
|
+
white_castle(column)
|
50
|
+
elsif piece.row == 7
|
51
|
+
black_castle(column)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def short_castle?(column)
|
56
|
+
column == 6
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module EnPassantHelpers
|
2
|
+
def validate_en_passant(piece, column, row)
|
3
|
+
#TODO
|
4
|
+
true
|
5
|
+
end
|
6
|
+
|
7
|
+
def execute_en_passant(piece, row, column)
|
8
|
+
piece.move(row, column)
|
9
|
+
remove_old_piece(*remove_en_passant(piece, *transform_field(@en_passant)), piece.color)
|
10
|
+
end
|
11
|
+
|
12
|
+
def remove_en_passant(piece, column, row)
|
13
|
+
direction = piece.color == Bchess::WHITE ? 1 : -1
|
14
|
+
[column, row - direction]
|
15
|
+
end
|
16
|
+
|
17
|
+
def long_pawn_move(piece, column, row)
|
18
|
+
piece.move(column, row)
|
19
|
+
direction = piece.color == Bchess::WHITE ? 1 : -1
|
20
|
+
@en_passant = field(column, (row + piece.row)/2 - direction)
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,141 @@
|
|
1
|
+
module Bchess
|
2
|
+
module FenHelpers
|
3
|
+
def fen_hash
|
4
|
+
{
|
5
|
+
'k': {klass: Bchess::King, color: Bchess::BLACK},
|
6
|
+
'q': {klass: Bchess::Queen, color: Bchess::BLACK},
|
7
|
+
'r': {klass: Bchess::Rook, color: Bchess::BLACK},
|
8
|
+
'b': {klass: Bchess::Bishop, color: Bchess::BLACK},
|
9
|
+
'n': {klass: Bchess::Knight, color: Bchess::BLACK},
|
10
|
+
'p': {klass: Bchess::Pawn, color: Bchess::BLACK},
|
11
|
+
'K': {klass: Bchess::King, color: Bchess::WHITE},
|
12
|
+
'Q': {klass: Bchess::Queen, color: Bchess::WHITE},
|
13
|
+
'R': {klass: Bchess::Rook, color: Bchess::WHITE},
|
14
|
+
'B': {klass: Bchess::Bishop, color: Bchess::WHITE},
|
15
|
+
'N': {klass: Bchess::Knight, color: Bchess::WHITE},
|
16
|
+
'P': {klass: Bchess::Pawn, color: Bchess::WHITE},
|
17
|
+
}
|
18
|
+
end
|
19
|
+
|
20
|
+
def write_fen
|
21
|
+
result = ''
|
22
|
+
7.downto(0) do |i|
|
23
|
+
line_pieces = pieces.select{ |p| p.row == i }
|
24
|
+
one_line = create_fen_line(line_pieces)
|
25
|
+
result << one_line
|
26
|
+
result << '/' unless i == 0
|
27
|
+
end
|
28
|
+
result.concat(additional_info)
|
29
|
+
end
|
30
|
+
|
31
|
+
def create_fen_line(pieces)
|
32
|
+
line = ''
|
33
|
+
counter = 0
|
34
|
+
|
35
|
+
0.upto(7) do |i|
|
36
|
+
piece = pieces.select{ |p| p.column == i }.first
|
37
|
+
if !!piece
|
38
|
+
if counter > 0
|
39
|
+
line.concat(counter.to_s)
|
40
|
+
counter = 0
|
41
|
+
line.concat(to_fen(piece))
|
42
|
+
else
|
43
|
+
line.concat(to_fen(piece))
|
44
|
+
end
|
45
|
+
else
|
46
|
+
counter += 1
|
47
|
+
end
|
48
|
+
end
|
49
|
+
line.concat(counter.to_s) if counter > 0
|
50
|
+
line
|
51
|
+
end
|
52
|
+
|
53
|
+
def set_pieces(board)
|
54
|
+
pieces.clear
|
55
|
+
board.split("/").each_with_index do |line, index|
|
56
|
+
column = 0
|
57
|
+
line.each_char do |char|
|
58
|
+
if char.to_i != 0
|
59
|
+
column += char.to_i - 1
|
60
|
+
else
|
61
|
+
pieces << fen_hash[char.to_sym][:klass].new(fen_hash[char.to_sym][:color], column, 7-index)
|
62
|
+
end
|
63
|
+
column += 1
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def fen_allows?(piece, column)
|
69
|
+
if piece.color == Bchess::WHITE
|
70
|
+
if column == 6
|
71
|
+
castles.chars.include?('k')
|
72
|
+
else
|
73
|
+
castles.chars.include?('q')
|
74
|
+
end
|
75
|
+
else
|
76
|
+
if column == 6
|
77
|
+
castles.chars.include?('K')
|
78
|
+
else
|
79
|
+
castles.chars.include?('Q')
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def to_fen(piece)
|
85
|
+
fen_hash.key({ :klass => piece.class, :color => piece.color }).to_s
|
86
|
+
end
|
87
|
+
|
88
|
+
def change_halfmove_clock(piece)
|
89
|
+
if piece.kind_of?(Bchess::Pawn)
|
90
|
+
@halfmove_clock = 0
|
91
|
+
else
|
92
|
+
@halfmove_clock = halfmove_clock + 1
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def update_castles_after_move(piece)
|
97
|
+
if piece == Bchess::King
|
98
|
+
update_castles_after_king_move(piece.color)
|
99
|
+
elsif piece == Bchess::Rook
|
100
|
+
update_castles_after_rook_move(piece)
|
101
|
+
end
|
102
|
+
@castles = '-' if @castles == ''
|
103
|
+
end
|
104
|
+
|
105
|
+
def update_castles_after_king_move(color)
|
106
|
+
if color == Bchess::WHITE
|
107
|
+
@castles.gsub!('K', '').gsub!('Q', '')
|
108
|
+
else
|
109
|
+
@castles.gsub!('k', '').gsub!('q', '')
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def change_move_number
|
114
|
+
@move_number = move_number + 1
|
115
|
+
end
|
116
|
+
|
117
|
+
def set_to_move(fen_colors)
|
118
|
+
@to_move = fen_colors == 'w' ? Bchess::WHITE : Bchess::BLACK
|
119
|
+
end
|
120
|
+
|
121
|
+
def set_castles(fen_castles)
|
122
|
+
@castles = fen_castles
|
123
|
+
end
|
124
|
+
|
125
|
+
def set_en_passant(fen_en_passant)
|
126
|
+
@en_passant = fen_en_passant
|
127
|
+
end
|
128
|
+
|
129
|
+
def set_halfmove_clock(fen_halfmove_clock)
|
130
|
+
@halfmove_clock = fen_halfmove_clock.to_i
|
131
|
+
end
|
132
|
+
|
133
|
+
def set_move_number(fen_move_number)
|
134
|
+
@move_number = fen_move_number.to_i
|
135
|
+
end
|
136
|
+
|
137
|
+
def additional_info
|
138
|
+
" #{to_move} #{castles} #{en_passant} #{halfmove_clock} #{move_number}"
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module FieldBetweenHelpers
|
2
|
+
def row_fields(dcolumn, drow)
|
3
|
+
smaller, bigger = [column, dcolumn].sort
|
4
|
+
(smaller+1..bigger-1).map{|c| [c, row] }
|
5
|
+
end
|
6
|
+
|
7
|
+
def column_fields(dcolumn, drow)
|
8
|
+
smaller, bigger = [row, drow].sort
|
9
|
+
(smaller+1..bigger-1).map{|r| [column, r] }
|
10
|
+
end
|
11
|
+
|
12
|
+
def diagonal_fields(dcolumn, drow)
|
13
|
+
fields = []
|
14
|
+
if dcolumn > column
|
15
|
+
if drow > row
|
16
|
+
(dcolumn - column - 1).times do |i|
|
17
|
+
fields << [dcolumn-(i+1), drow-(i+1)]
|
18
|
+
end
|
19
|
+
else
|
20
|
+
(dcolumn - column - 1).times do |i|
|
21
|
+
fields << [dcolumn-(i+1), drow+(i+1)]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
else
|
25
|
+
if drow > row
|
26
|
+
(column - dcolumn - 1).times do |i|
|
27
|
+
fields << [dcolumn+(i+1), drow-(i+1)]
|
28
|
+
end
|
29
|
+
else
|
30
|
+
(column - dcolumn - 1).times do |i|
|
31
|
+
fields << [dcolumn+(i+1), drow+(i+1)]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
fields
|
36
|
+
end
|
37
|
+
|
38
|
+
def fields_between(dcolumn, drow)
|
39
|
+
return [] unless can_move_to_field?(dcolumn, drow)
|
40
|
+
if same_row?(drow)
|
41
|
+
row_fields(dcolumn, drow)
|
42
|
+
elsif same_column?(dcolumn)
|
43
|
+
column_fields(dcolumn, drow)
|
44
|
+
elsif same_diagonal?(dcolumn, drow)
|
45
|
+
diagonal_fields(dcolumn, drow)
|
46
|
+
else
|
47
|
+
[]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
data/lib/bchess/king.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
module Bchess
|
2
|
+
class King < Piece
|
3
|
+
|
4
|
+
KINGS_REACH = 1
|
5
|
+
|
6
|
+
attr_reader :moved
|
7
|
+
|
8
|
+
def initiialize(*args)
|
9
|
+
super(args)
|
10
|
+
@moved = false
|
11
|
+
end
|
12
|
+
|
13
|
+
def name
|
14
|
+
'K'
|
15
|
+
end
|
16
|
+
|
17
|
+
def move(dcolumn, drow)
|
18
|
+
super(dcolumn, drow)
|
19
|
+
@moved = true
|
20
|
+
end
|
21
|
+
|
22
|
+
def can_move_to_field?(dcolumn, drow)
|
23
|
+
super &&
|
24
|
+
(
|
25
|
+
by_line(dcolumn, drow, KINGS_REACH) ||
|
26
|
+
by_diagonal(dcolumn, drow, KINGS_REACH)
|
27
|
+
)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Bchess
|
2
|
+
class Knight < Piece
|
3
|
+
|
4
|
+
def initiialize(*args)
|
5
|
+
super(args)
|
6
|
+
end
|
7
|
+
|
8
|
+
def name
|
9
|
+
'N'
|
10
|
+
end
|
11
|
+
|
12
|
+
def can_move_to_field?(dcolumn, drow)
|
13
|
+
super && by_jump(dcolumn, drow)
|
14
|
+
end
|
15
|
+
|
16
|
+
def fields_between(column, row)
|
17
|
+
[]
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def by_jump(dcolumn, drow)
|
23
|
+
(row - drow).abs + (column - dcolumn).abs == 3 &&
|
24
|
+
(row - drow).abs != 3 && (column - dcolumn).abs != 3
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/lib/bchess/pawn.rb
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
module Bchess
|
2
|
+
class Pawn < Piece
|
3
|
+
|
4
|
+
def initiialize(*args)
|
5
|
+
super(args)
|
6
|
+
end
|
7
|
+
|
8
|
+
def name
|
9
|
+
''
|
10
|
+
end
|
11
|
+
|
12
|
+
def valid?
|
13
|
+
super && pawn_position?
|
14
|
+
end
|
15
|
+
|
16
|
+
def can_move_to_field?(dcolumn, drow)
|
17
|
+
super &&
|
18
|
+
pawn_move(dcolumn, drow)
|
19
|
+
end
|
20
|
+
|
21
|
+
def can_take_on_field?(dcolumn, drow)
|
22
|
+
direction = white? ? 1 : -1
|
23
|
+
by_diagonal(dcolumn, drow, 1) && (drow - row) == direction
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def pawn_move(dcolumn, drow)
|
29
|
+
row_diff?(dcolumn, drow) &&
|
30
|
+
direction_kept?(dcolumn, drow) &&
|
31
|
+
column_kept?(dcolumn, column)
|
32
|
+
end
|
33
|
+
|
34
|
+
def column_kept?(dcolumn, column)
|
35
|
+
column == dcolumn
|
36
|
+
end
|
37
|
+
|
38
|
+
def direction_kept?(dcolumn, drow)
|
39
|
+
white? ? drow > row : row > drow
|
40
|
+
end
|
41
|
+
|
42
|
+
def row_diff?(dcolumn, drow)
|
43
|
+
(row - drow).abs <= (starting_position? ? 2 : 1)
|
44
|
+
end
|
45
|
+
|
46
|
+
def starting_position?
|
47
|
+
row == (white? ? 1 : 6)
|
48
|
+
end
|
49
|
+
|
50
|
+
def pawn_position?
|
51
|
+
(1..6).include?(row)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|