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/piece.rb
ADDED
@@ -0,0 +1,133 @@
|
|
1
|
+
module Bchess
|
2
|
+
class Piece
|
3
|
+
attr_reader :color, :column, :row
|
4
|
+
|
5
|
+
include BoardHelpers
|
6
|
+
include FieldBetweenHelpers
|
7
|
+
|
8
|
+
def initialize(color, column, row)
|
9
|
+
@color = color
|
10
|
+
@column = column
|
11
|
+
@row = row
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_s
|
15
|
+
name + field(column, row)
|
16
|
+
end
|
17
|
+
|
18
|
+
def moved
|
19
|
+
false
|
20
|
+
end
|
21
|
+
|
22
|
+
def name
|
23
|
+
raise Exception.new("Should be defined in subclass")
|
24
|
+
end
|
25
|
+
|
26
|
+
def at?(dcolumn, drow)
|
27
|
+
column == dcolumn && row == drow
|
28
|
+
end
|
29
|
+
|
30
|
+
def move(dcolumn, drow)
|
31
|
+
@column = dcolumn
|
32
|
+
@row = drow
|
33
|
+
end
|
34
|
+
|
35
|
+
def can_make_move?(info, board)
|
36
|
+
row = to_row(info[:row])
|
37
|
+
column = to_column(info[:column])
|
38
|
+
|
39
|
+
board.at(column, row)&.color != color &&
|
40
|
+
kind_of?(info[:piece_type]) &&
|
41
|
+
info[:piece_color] == color &&
|
42
|
+
can_move_or_take?(column, row, board) &&
|
43
|
+
additional_info?(info[:additional_info])
|
44
|
+
end
|
45
|
+
|
46
|
+
def can_move_or_take?(column, row, board)
|
47
|
+
clear_path?(column, row, board) &&
|
48
|
+
(
|
49
|
+
can_move?(column, row) ||
|
50
|
+
can_take?(column, row, board) ||
|
51
|
+
can_en_passant?(column, row, board)
|
52
|
+
) && valid_position_after?(column, row, board)
|
53
|
+
end
|
54
|
+
|
55
|
+
def clear_path?(column, row, board)
|
56
|
+
fields_between(column, row).none?{|f| board.at(*f)}
|
57
|
+
end
|
58
|
+
|
59
|
+
def can_move?(column, row)
|
60
|
+
can_move_to_field?(column, row)
|
61
|
+
end
|
62
|
+
|
63
|
+
def can_take?(column, row, board, en_passant = false)
|
64
|
+
can_take_on_field?(column, row) && (!!board.at(column, row) || en_passant)
|
65
|
+
end
|
66
|
+
|
67
|
+
def can_en_passant?(column, row, board)
|
68
|
+
board.en_passant == field(column, row) && can_take?(column, row, board, true)
|
69
|
+
end
|
70
|
+
|
71
|
+
def valid_position_after?(dcolumn, drow, board)
|
72
|
+
fen = board.write_fen
|
73
|
+
|
74
|
+
helper_board = Bchess::Board.new(fen)
|
75
|
+
helper_board.read_fen
|
76
|
+
helper_piece = helper_board.at(column, row)
|
77
|
+
helper_board.move(helper_piece, dcolumn, drow, Bchess::Queen) # TODO
|
78
|
+
end
|
79
|
+
|
80
|
+
def additional_info?(info)
|
81
|
+
info.nil? || to_column(info) == column || to_row(info) == row
|
82
|
+
end
|
83
|
+
|
84
|
+
def can_move_to_field?(dcolumn, drow)
|
85
|
+
!(column == dcolumn && drow == row) &&
|
86
|
+
(0..7).include?(dcolumn) && (0..7).include?(drow)
|
87
|
+
end
|
88
|
+
|
89
|
+
def can_take_on_field?(dcolumn, drow)
|
90
|
+
can_move_to_field?(dcolumn, drow)
|
91
|
+
end
|
92
|
+
|
93
|
+
def can_be_promoted?
|
94
|
+
false
|
95
|
+
end
|
96
|
+
|
97
|
+
def valid?
|
98
|
+
(0..7).include?(column) && (0..7).include?(row)
|
99
|
+
end
|
100
|
+
|
101
|
+
def by_line(dcolumn, drow, range)
|
102
|
+
same_row?(drow) && (column - dcolumn).abs <= range ||
|
103
|
+
same_column?(dcolumn) && (row - drow).abs <= range
|
104
|
+
end
|
105
|
+
|
106
|
+
def by_diagonal(dcolumn, drow, range)
|
107
|
+
same_diagonal?(dcolumn, drow) &&
|
108
|
+
(column - dcolumn).abs <= range
|
109
|
+
end
|
110
|
+
|
111
|
+
def white?
|
112
|
+
color == Bchess::WHITE
|
113
|
+
end
|
114
|
+
|
115
|
+
def black?
|
116
|
+
color == Bchess::BLACK
|
117
|
+
end
|
118
|
+
|
119
|
+
private
|
120
|
+
|
121
|
+
def same_row?(drow)
|
122
|
+
row == drow
|
123
|
+
end
|
124
|
+
|
125
|
+
def same_column?(dcolumn)
|
126
|
+
column == dcolumn
|
127
|
+
end
|
128
|
+
|
129
|
+
def same_diagonal?(dcolumn, drow)
|
130
|
+
(column - dcolumn).abs == (row - drow).abs
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
data/lib/bchess/queen.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
module Bchess
|
2
|
+
class Queen < Piece
|
3
|
+
QUEEN_REACH = 7
|
4
|
+
|
5
|
+
def initiialize(*args)
|
6
|
+
super(args)
|
7
|
+
end
|
8
|
+
|
9
|
+
def name
|
10
|
+
'Q'
|
11
|
+
end
|
12
|
+
|
13
|
+
def can_move_to_field?(dcolumn, drow)
|
14
|
+
super &&
|
15
|
+
(
|
16
|
+
by_line(dcolumn, drow, QUEEN_REACH) ||
|
17
|
+
by_diagonal(dcolumn, drow, QUEEN_REACH)
|
18
|
+
)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/lib/bchess/rook.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
module Bchess
|
2
|
+
class Rook < Piece
|
3
|
+
ROOK_REACH = 7
|
4
|
+
|
5
|
+
attr_accessor :moved
|
6
|
+
|
7
|
+
def initiialize(*args)
|
8
|
+
super(args)
|
9
|
+
@moved = false
|
10
|
+
end
|
11
|
+
|
12
|
+
def name
|
13
|
+
'R'
|
14
|
+
end
|
15
|
+
|
16
|
+
def move(dcolumn, drow)
|
17
|
+
super(dcolumn, drow)
|
18
|
+
@moved = true
|
19
|
+
end
|
20
|
+
|
21
|
+
def can_move_to_field?(dcolumn, drow)
|
22
|
+
super && by_line(dcolumn, drow, ROOK_REACH)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/pgn/game.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
module Bchess
|
2
|
+
module PGN
|
3
|
+
class Game
|
4
|
+
attr_accessor :header, :body, :moves
|
5
|
+
|
6
|
+
def initialize(parsed_game)
|
7
|
+
@header = Bchess::PGN::GameHeader.new(parsed_game.elements.first)
|
8
|
+
@body = Bchess::PGN::GameBody.new(parsed_game.elements.last)
|
9
|
+
end
|
10
|
+
|
11
|
+
def convert_body_to_moves
|
12
|
+
body.extract_moves
|
13
|
+
@moves = body.moves
|
14
|
+
true
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
module Bchess
|
2
|
+
module PGN
|
3
|
+
class GameBody
|
4
|
+
attr_reader :body, :moves, :board
|
5
|
+
|
6
|
+
include BoardHelpers
|
7
|
+
|
8
|
+
C_COLUMN = 2
|
9
|
+
G_COLUMN = 6
|
10
|
+
|
11
|
+
WHITE_ROW = 0
|
12
|
+
BLACK_ROW = 7
|
13
|
+
|
14
|
+
def initialize(body)
|
15
|
+
@body = body
|
16
|
+
@board = Bchess::Board.new
|
17
|
+
@moves = []
|
18
|
+
end
|
19
|
+
|
20
|
+
def extract_moves
|
21
|
+
@board.read_fen
|
22
|
+
|
23
|
+
body.elements&.each do |move|
|
24
|
+
if is_castle?(move)
|
25
|
+
move_info = extract_castle(move)
|
26
|
+
elsif is_move?(move)
|
27
|
+
move_info = extract_move(move)
|
28
|
+
else
|
29
|
+
next
|
30
|
+
end
|
31
|
+
|
32
|
+
board.move(*move_info.values)
|
33
|
+
@moves << move_info
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def extract_move(move)
|
38
|
+
move_text = move.text_value
|
39
|
+
info = return_move_information(move_text)
|
40
|
+
|
41
|
+
piece = get_moving_piece(board, info)
|
42
|
+
{
|
43
|
+
piece: piece,
|
44
|
+
column: to_column(info[:column]),
|
45
|
+
row: to_row(info[:row]),
|
46
|
+
promoted_piece: info[:promoted_piece]
|
47
|
+
}
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def get_moving_piece(board, info)
|
53
|
+
pieces = board.get_possible_pieces(info)
|
54
|
+
|
55
|
+
if pieces.size != 1
|
56
|
+
raise Bchess::InvalidMoveException.new("Too many or too few pieces to make move: #{info} : #{pieces.size}")
|
57
|
+
end
|
58
|
+
|
59
|
+
pieces.first
|
60
|
+
end
|
61
|
+
|
62
|
+
def extract_castle(move)
|
63
|
+
move_text = move.text_value
|
64
|
+
info = return_move_information(move_text, true)
|
65
|
+
|
66
|
+
if C_COLUMN == move_text.split('-').size
|
67
|
+
column = G_COLUMN
|
68
|
+
row = is_white?(info) ? WHITE_ROW : BLACK_ROW
|
69
|
+
else
|
70
|
+
column = C_COLUMN
|
71
|
+
row = is_white?(info) ? WHITE_ROW : BLACK_ROW
|
72
|
+
end
|
73
|
+
|
74
|
+
{
|
75
|
+
piece: board.king(info[:piece_color]),
|
76
|
+
column: column,
|
77
|
+
row: row,
|
78
|
+
promoted_piece: false
|
79
|
+
}
|
80
|
+
end
|
81
|
+
|
82
|
+
def return_move_information(move_text,castle=false)
|
83
|
+
Bchess::PGN::MoveInfoParser.new(move_text, castle).parse_move_string
|
84
|
+
end
|
85
|
+
|
86
|
+
def is_move?(move)
|
87
|
+
move.kind_of?(Sexp::PMove)
|
88
|
+
end
|
89
|
+
|
90
|
+
def is_castle?(move)
|
91
|
+
move.kind_of?(Sexp::PCastle)
|
92
|
+
end
|
93
|
+
|
94
|
+
def is_comment?(move)
|
95
|
+
move.kind_of?(Sexp::PCommentWithBracket)
|
96
|
+
end
|
97
|
+
|
98
|
+
def is_variation?(move)
|
99
|
+
move.kind_of?(Sexp::PVariation)
|
100
|
+
end
|
101
|
+
|
102
|
+
def is_white?(info)
|
103
|
+
Bchess::WHITE == info[:piece_color]
|
104
|
+
end
|
105
|
+
|
106
|
+
def promotion?(info)
|
107
|
+
info[:promoted_piece]
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Bchess
|
2
|
+
module PGN
|
3
|
+
class GameHeader
|
4
|
+
attr_reader :header, :player_white, :player_black
|
5
|
+
|
6
|
+
def initialize(header)
|
7
|
+
@header = header
|
8
|
+
end
|
9
|
+
|
10
|
+
def player_white
|
11
|
+
@player_white ||= values["White"]
|
12
|
+
end
|
13
|
+
|
14
|
+
def player_black
|
15
|
+
@player_black ||= values["Black"]
|
16
|
+
end
|
17
|
+
|
18
|
+
def elo_white
|
19
|
+
@elo_white ||= values["WhiteElo"]
|
20
|
+
end
|
21
|
+
|
22
|
+
def elo_black
|
23
|
+
@elo_black ||= values["BlackElo"]
|
24
|
+
end
|
25
|
+
|
26
|
+
def event
|
27
|
+
@event ||= values["Event"]
|
28
|
+
end
|
29
|
+
|
30
|
+
def site
|
31
|
+
@site ||= values["Site"]
|
32
|
+
end
|
33
|
+
|
34
|
+
def date
|
35
|
+
@date ||= values["Date"]
|
36
|
+
end
|
37
|
+
|
38
|
+
def round
|
39
|
+
@round ||= values["Round"]
|
40
|
+
end
|
41
|
+
|
42
|
+
def eco
|
43
|
+
@eco ||= values["Eco"]
|
44
|
+
end
|
45
|
+
|
46
|
+
def result
|
47
|
+
@result ||= values["Result"]
|
48
|
+
end
|
49
|
+
|
50
|
+
def values
|
51
|
+
@values ||= header.create_value_hash
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
module Bchess
|
2
|
+
module PGN
|
3
|
+
class MoveInfoParser
|
4
|
+
attr_accessor :info, :move, :castle, :move_string
|
5
|
+
|
6
|
+
CHECK = "+"
|
7
|
+
TAKE = "x"
|
8
|
+
TAKE_2 = ":"
|
9
|
+
MATE = "#"
|
10
|
+
PROMOTION = "="
|
11
|
+
|
12
|
+
def initialize(move, castle)
|
13
|
+
@move = move
|
14
|
+
@castle = castle
|
15
|
+
end
|
16
|
+
|
17
|
+
def parse_move_string
|
18
|
+
info = {}
|
19
|
+
info.merge!(extract_promotion)
|
20
|
+
move_split = @move.split('.')
|
21
|
+
|
22
|
+
info.merge!(basic_move_data(move_split))
|
23
|
+
return info if @castle
|
24
|
+
|
25
|
+
move_string.tr!('+x:#', '')
|
26
|
+
info.merge!(additional_info(move_string))
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def additional_info(move_string)
|
32
|
+
{
|
33
|
+
piece_type: extract_piece_type(move_string),
|
34
|
+
column: extract_column(move_string),
|
35
|
+
row: extract_row(move_string),
|
36
|
+
additional_info: extract_additional_info(move_string)
|
37
|
+
}
|
38
|
+
end
|
39
|
+
|
40
|
+
def extract_piece_type(move_string)
|
41
|
+
if 2 == move_string.size || move_string.size && pawn_taking?(move_string)
|
42
|
+
Bchess::Pawn
|
43
|
+
else
|
44
|
+
get_piece_from_letter(move_string[0])
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def pawn_taking?(move_string)
|
49
|
+
move_string[0].ord >= 'a'.ord && move_string[0].ord <= 'h'.ord
|
50
|
+
end
|
51
|
+
|
52
|
+
def extract_column(move_string)
|
53
|
+
move_string[move_string.size - 2]
|
54
|
+
end
|
55
|
+
|
56
|
+
def extract_row(move_string)
|
57
|
+
move_string[move_string.size - 1]
|
58
|
+
end
|
59
|
+
|
60
|
+
def extract_additional_info(move_string)
|
61
|
+
if (3 == move_string.size) && pawn_taking?(move_string)
|
62
|
+
move_string[0]
|
63
|
+
elsif (4 == move_string.size)
|
64
|
+
move_string[1]
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def extract_promotion
|
69
|
+
promotion = @move.split(PROMOTION)
|
70
|
+
|
71
|
+
if promotion.size == 2
|
72
|
+
@move = promotion.first
|
73
|
+
{ promoted_piece: get_piece_from_letter(promotion.last[0]) }
|
74
|
+
else
|
75
|
+
{}
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def basic_move_data(move_split)
|
80
|
+
@move_string = move_split.last.strip
|
81
|
+
|
82
|
+
raise InvalidMoveException.new("Wrong move description") if move_string.size < 2
|
83
|
+
color = move_split.size == 2 ? Bchess::WHITE : Bchess::BLACK
|
84
|
+
number = move_split.first.strip if move_split.size == 2
|
85
|
+
{
|
86
|
+
piece_color: color,
|
87
|
+
move_number: number,
|
88
|
+
check: move_string.include?(CHECK),
|
89
|
+
take: move_string.include?(TAKE) || move_string.include?(TAKE_2),
|
90
|
+
mate: move_string.include?(MATE),
|
91
|
+
}
|
92
|
+
end
|
93
|
+
|
94
|
+
|
95
|
+
def get_piece_from_letter(letter)
|
96
|
+
{
|
97
|
+
"K" => Bchess::King,
|
98
|
+
"Q" => Bchess::Queen,
|
99
|
+
"R" => Bchess::Rook,
|
100
|
+
"B" => Bchess::Bishop,
|
101
|
+
"N" => Bchess::Knight
|
102
|
+
}[letter]
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|