bchess 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -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
@@ -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
@@ -0,0 +1,3 @@
1
+ module Bchess
2
+ VERSION = "0.1.1"
3
+ 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