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.
@@ -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