chess_tui 0.41.4
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/README.md +39 -0
- data/bin/chess_tui +6 -0
- data/lib/chess/board/board.rb +92 -0
- data/lib/chess/board/board_from_fen.rb +136 -0
- data/lib/chess/board/board_pos.rb +65 -0
- data/lib/chess/display/board.rb +168 -0
- data/lib/chess/display/color.rb +38 -0
- data/lib/chess/display/prompt.rb +79 -0
- data/lib/chess/game/game.rb +165 -0
- data/lib/chess/game/valid_moves.rb +131 -0
- data/lib/chess/game/win_and_draw.rb +106 -0
- data/lib/chess/mouse/mouse_input.rb +87 -0
- data/lib/chess/mouse/mouse_position.rb +74 -0
- data/lib/chess/pieces/bishop.rb +78 -0
- data/lib/chess/pieces/king.rb +166 -0
- data/lib/chess/pieces/knight.rb +79 -0
- data/lib/chess/pieces/pawn.rb +163 -0
- data/lib/chess/pieces/piece.rb +29 -0
- data/lib/chess/pieces/queen.rb +44 -0
- data/lib/chess/pieces/rook.rb +78 -0
- data/lib/chess/play_game.rb +55 -0
- data/lib/chess/play_moves/play_king_moves.rb +58 -0
- data/lib/chess/play_moves/play_moves_by_type.rb +53 -0
- data/lib/chess/play_moves/play_pawn_moves.rb +115 -0
- data/lib/chess/player/player.rb +39 -0
- data/lib/chess/save/fen_from_board.rb +82 -0
- data/lib/chess/save/save.rb +31 -0
- data/lib/chess/save/serializer.rb +29 -0
- data/lib/chess/version.rb +6 -0
- data/lib/chess.rb +44 -0
- data/save.json +9 -0
- metadata +94 -0
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Chess
|
|
4
|
+
module Chess
|
|
5
|
+
# Game class
|
|
6
|
+
class Game
|
|
7
|
+
include Display
|
|
8
|
+
include Mouse
|
|
9
|
+
include Save
|
|
10
|
+
include ValidMoves
|
|
11
|
+
include WinAndDraw
|
|
12
|
+
|
|
13
|
+
def initialize(player = Chess::Player)
|
|
14
|
+
@white_player = player.new
|
|
15
|
+
@black_player = player.new
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# select different actions based on what was clicked
|
|
19
|
+
# @param board [Chess::Board]
|
|
20
|
+
# @param clicked [String]
|
|
21
|
+
# @param board_pos [Array]
|
|
22
|
+
# @param mouse_coord [Array]
|
|
23
|
+
# @return [nil,Integer] nil or Exit Code 0
|
|
24
|
+
def select_click_action(board, clicked, board_pos, mouse_coord)
|
|
25
|
+
return if clicked == 'outside'
|
|
26
|
+
|
|
27
|
+
if clicked == 'board'
|
|
28
|
+
board_action(board, board_pos)
|
|
29
|
+
else
|
|
30
|
+
button_action(board, mouse_coord)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# actions for clicks on board
|
|
35
|
+
# @param board [Chess:Board]
|
|
36
|
+
# @param board_pos [Array]
|
|
37
|
+
# @return [nil,Integer] nil or Exit Code 0
|
|
38
|
+
def board_action(board, board_pos)
|
|
39
|
+
player = if board.current_player == 'w'
|
|
40
|
+
@white_player
|
|
41
|
+
else
|
|
42
|
+
@black_player
|
|
43
|
+
end
|
|
44
|
+
update_castling_rights(board)
|
|
45
|
+
valid_moves = player_turn(player, board, board_pos)
|
|
46
|
+
update_king_status(board)
|
|
47
|
+
redraw_display(board.data, valid_moves)
|
|
48
|
+
detect_win_or_draws(board)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# actions for clicks on buttons
|
|
52
|
+
# @param board [Chess::Board]
|
|
53
|
+
# @param mouse_coord [Array]
|
|
54
|
+
# @return [nil,Integer] nil or Exit Code 0
|
|
55
|
+
def button_action(board, mouse_coord)
|
|
56
|
+
type = button_type(mouse_coord)
|
|
57
|
+
if type == 'save&exit'
|
|
58
|
+
save_and_exit(board)
|
|
59
|
+
else
|
|
60
|
+
game_exit
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# player turn to select move
|
|
65
|
+
# @param player [Chess::Player]
|
|
66
|
+
# @param board [Chess::Board]
|
|
67
|
+
# @param board_pos [Array]
|
|
68
|
+
# @return [void]
|
|
69
|
+
def player_turn(player, board, board_pos)
|
|
70
|
+
piece = board.piece_at(*board_pos)
|
|
71
|
+
if player.selected_piece == '' || same_color?(board.current_player, piece)
|
|
72
|
+
select_piece(piece, player, board)
|
|
73
|
+
else
|
|
74
|
+
select_move(player, board, board_pos)
|
|
75
|
+
[]
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# player turn to select move
|
|
80
|
+
# @param piece any subclass of {Chess::Pieces::Piece}
|
|
81
|
+
# @param player [Chess::Player]
|
|
82
|
+
# @param board [Chess::Board]
|
|
83
|
+
# @return [void]
|
|
84
|
+
def select_piece(piece, player, board)
|
|
85
|
+
player.selected_piece = piece if same_color?(board.current_player, piece)
|
|
86
|
+
if player.selected_piece == ''
|
|
87
|
+
[]
|
|
88
|
+
else
|
|
89
|
+
player.selected_piece.valid_moves = valid_moves(piece, board)
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# selects move to play
|
|
94
|
+
#
|
|
95
|
+
# @param player [Chess::Player]
|
|
96
|
+
# @param board [Chess::Board]
|
|
97
|
+
# @param move_pos [Array]
|
|
98
|
+
# @return [void]
|
|
99
|
+
def select_move(player, board, move_pos)
|
|
100
|
+
player.select_move(board, move_pos)
|
|
101
|
+
player.selected_piece.valid_moves = []
|
|
102
|
+
player.selected_piece = ''
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# checks if current_player and piece have same color
|
|
106
|
+
#
|
|
107
|
+
# @param current_player [String] from {Chess::Board#current_player}
|
|
108
|
+
# @param piece any subclass of {Chess::Pieces::Piece}
|
|
109
|
+
def same_color?(current_player, piece)
|
|
110
|
+
piece.color.chr == current_player unless piece == ''
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# updates king's @in_check attribute
|
|
114
|
+
#
|
|
115
|
+
# @param board [Chess::Board]
|
|
116
|
+
# @return [void]
|
|
117
|
+
def update_king_status(board)
|
|
118
|
+
current_player = board.current_player
|
|
119
|
+
board.current_player = 'b'
|
|
120
|
+
board.white_king.in_check?(board, board.black_pieces)
|
|
121
|
+
board.current_player = 'w'
|
|
122
|
+
board.black_king.in_check?(board, board.white_pieces)
|
|
123
|
+
board.current_player = current_player
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# updates castling_rights for board
|
|
127
|
+
#
|
|
128
|
+
# @param board [Chess::Board]
|
|
129
|
+
# @return [void]
|
|
130
|
+
def update_castling_rights(board)
|
|
131
|
+
return if board.castling_rights == ''
|
|
132
|
+
|
|
133
|
+
location_piece_color_and_rights = {
|
|
134
|
+
h0: [Chess::Pieces::Rook, 'w', 'K'],
|
|
135
|
+
a0: [Chess::Pieces::Rook, 'w', 'Q'],
|
|
136
|
+
e0: [Chess::Pieces::King, 'w', 'KQ'],
|
|
137
|
+
h7: [Chess::Pieces::Rook, 'b', 'k'],
|
|
138
|
+
a7: [Chess::Pieces::Rook, 'b', 'q'],
|
|
139
|
+
e7: [Chess::Pieces::King, 'b', 'kq']
|
|
140
|
+
}
|
|
141
|
+
update_castling_rights_for_each_pos(board, location_piece_color_and_rights)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# helper method for {#update_castling_rights}
|
|
145
|
+
#
|
|
146
|
+
# @param board [Chess::Board]
|
|
147
|
+
# @param location_piece_color_and_rights [Hash]
|
|
148
|
+
# @return [void]
|
|
149
|
+
def update_castling_rights_for_each_pos(board, location_piece_color_and_rights) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
|
|
150
|
+
location_piece_color_and_rights.each_key do |pos|
|
|
151
|
+
type = location_piece_color_and_rights[pos].first
|
|
152
|
+
color = location_piece_color_and_rights[pos][1]
|
|
153
|
+
rights = location_piece_color_and_rights[pos].last
|
|
154
|
+
position = pos.to_s.chars
|
|
155
|
+
file = position.first
|
|
156
|
+
rank = position.last.to_i
|
|
157
|
+
piece = board.piece_at(file, rank)
|
|
158
|
+
rights = rights.chars
|
|
159
|
+
rights.each do |right|
|
|
160
|
+
board.castling_rights.sub!(right, '') unless piece.is_a?(type) && same_color?(color, piece)
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Chess
|
|
4
|
+
module Chess
|
|
5
|
+
# ValidMoves
|
|
6
|
+
module ValidMoves
|
|
7
|
+
# find valid_moves for given piece on the given board
|
|
8
|
+
#
|
|
9
|
+
# @param piece any subclass of {Chess::Pieces::Piece}
|
|
10
|
+
# @param board [Chess::Board]
|
|
11
|
+
# @return [Array] valid_moves_arr
|
|
12
|
+
def valid_moves(piece, board)
|
|
13
|
+
possible_moves = piece.possible_moves(board)
|
|
14
|
+
new_player = Player.new
|
|
15
|
+
fen_code = generate_fen_code(board)
|
|
16
|
+
|
|
17
|
+
if piece.is_a?(Pieces::King)
|
|
18
|
+
valid_moves_for_king(board, piece, possible_moves, fen_code, new_player)
|
|
19
|
+
else
|
|
20
|
+
valid_moves_from_possible_moves(piece, possible_moves, fen_code, new_player)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# find valid_moves from possible_moves
|
|
25
|
+
#
|
|
26
|
+
# @param piece any subclass of {Chess::Pieces::Piece}
|
|
27
|
+
# @param possible_moves [Array]
|
|
28
|
+
# @param fen_code [String]
|
|
29
|
+
# @param new_player [Chess::Player]
|
|
30
|
+
# @return [Array] valid_moves_arr
|
|
31
|
+
def valid_moves_from_possible_moves(piece, possible_moves, fen_code, new_player)
|
|
32
|
+
valid_moves_arr = []
|
|
33
|
+
possible_moves.each do |move|
|
|
34
|
+
invalid = invalid_normal_move?(move, piece, fen_code, new_player)
|
|
35
|
+
valid_moves_arr << move unless invalid
|
|
36
|
+
end
|
|
37
|
+
valid_moves_arr
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# check if given move is invalid
|
|
41
|
+
#
|
|
42
|
+
# @param move [Array]
|
|
43
|
+
# @param piece any subclass of {Chess::Pieces::Piece}
|
|
44
|
+
# @param fen_code [String]
|
|
45
|
+
# @param new_player [Chess::Player]
|
|
46
|
+
def invalid_normal_move?(move, piece, fen_code, new_player)
|
|
47
|
+
new_board = Chess::Board.new(fen_code)
|
|
48
|
+
new_player.selected_piece = new_board.piece_at(*piece.pos)
|
|
49
|
+
new_player.play_move_by_type(new_player.selected_piece, new_board, move, inside_valid_moves_flag: true)
|
|
50
|
+
king_comes_in_check?(piece, new_board)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# find valid_moves for given king on the given board
|
|
54
|
+
#
|
|
55
|
+
# @param board [Chess::Board]
|
|
56
|
+
# @param piece any subclass of {Chess::Pieces::Piece}
|
|
57
|
+
# @param possible_moves [Array]
|
|
58
|
+
# @param fen_code [String]
|
|
59
|
+
# @param new_player [Chess::Player]
|
|
60
|
+
# @return [Array] valid_moves_arr
|
|
61
|
+
def valid_moves_for_king(board, piece, possible_moves, fen_code, new_player) # rubocop:disable Metrics/MethodLength
|
|
62
|
+
valid_moves_arr = []
|
|
63
|
+
castling_moves = piece.castling_moves(board)
|
|
64
|
+
invalid = false
|
|
65
|
+
possible_moves.each do |move|
|
|
66
|
+
invalid = if castling_moves.include?(move)
|
|
67
|
+
invalid_castling_move?(move, piece, fen_code, new_player)
|
|
68
|
+
else
|
|
69
|
+
invalid_normal_move?(move, piece, fen_code, new_player)
|
|
70
|
+
end
|
|
71
|
+
valid_moves_arr << move unless invalid
|
|
72
|
+
end
|
|
73
|
+
valid_moves_arr
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Check if given castling move is invalid
|
|
77
|
+
#
|
|
78
|
+
# @param move [Array]
|
|
79
|
+
# @param piece any subclass of {Chess::Pieces::Piece}
|
|
80
|
+
# @param fen_code [String]
|
|
81
|
+
# @param new_player [Chess::Player]
|
|
82
|
+
def invalid_castling_move?(move, piece, fen_code, new_player)
|
|
83
|
+
return true if piece.in_check
|
|
84
|
+
|
|
85
|
+
file = move.first
|
|
86
|
+
if file.downcase == 'c'
|
|
87
|
+
invalid_queen_side_castle?(move, piece, fen_code, new_player)
|
|
88
|
+
elsif file.downcase == 'g'
|
|
89
|
+
invalid_king_side_castle?(move, piece, fen_code, new_player)
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# helper method for {#invalid_castling_move?}
|
|
94
|
+
# @param (see #invalid_castling_move?)
|
|
95
|
+
def invalid_queen_side_castle?(move, piece, fen_code, new_player)
|
|
96
|
+
comes_in_check = castling_king_passes_check?(move, piece, fen_code, new_player)
|
|
97
|
+
move = ['d', move.last]
|
|
98
|
+
passes_check = castling_king_passes_check?(move, piece, fen_code, new_player)
|
|
99
|
+
passes_check || comes_in_check
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# helper method for {#invalid_castling_move?}
|
|
103
|
+
# @param (see #invalid_castling_move?)
|
|
104
|
+
def invalid_king_side_castle?(move, piece, fen_code, new_player)
|
|
105
|
+
comes_in_check = castling_king_passes_check?(move, piece, fen_code, new_player)
|
|
106
|
+
move = ['f', move.last]
|
|
107
|
+
passes_check = castling_king_passes_check?(move, piece, fen_code, new_player)
|
|
108
|
+
passes_check || comes_in_check
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# helper method for {#invalid_castling_move?}
|
|
112
|
+
# @param (see #invalid_castling_move?)
|
|
113
|
+
def castling_king_passes_check?(move, piece, fen_code, new_player)
|
|
114
|
+
new_board = Chess::Board.new(fen_code)
|
|
115
|
+
new_player.selected_piece = new_board.piece_at(*piece.pos)
|
|
116
|
+
new_player.play_move_by_type(new_player.selected_piece, new_board, move, inside_valid_moves_flag: true)
|
|
117
|
+
king_comes_in_check?(piece, new_board)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Checks if king comes in check
|
|
121
|
+
# @param piece any subclass of {Chess::Pieces::Piece}
|
|
122
|
+
# @param new_board new board obj of [Chess::Board]
|
|
123
|
+
def king_comes_in_check?(piece, new_board)
|
|
124
|
+
if piece.white?
|
|
125
|
+
new_board.white_king.in_check?(new_board, new_board.black_pieces)
|
|
126
|
+
else
|
|
127
|
+
new_board.black_king.in_check?(new_board, new_board.white_pieces)
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Chess
|
|
4
|
+
module Chess
|
|
5
|
+
# WinAndDraw
|
|
6
|
+
module WinAndDraw
|
|
7
|
+
# detects wins or draws and exits the game
|
|
8
|
+
#
|
|
9
|
+
# @param board [Chess::Board]
|
|
10
|
+
# @return [nil,Integer] nil or Exit Code 0
|
|
11
|
+
def detect_win_or_draws(board) # rubocop:disable Metrics/MethodLength
|
|
12
|
+
if checkmate?(board)
|
|
13
|
+
puts 'Checkmate!'
|
|
14
|
+
return game_exit
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
if stalemate?(board)
|
|
18
|
+
puts 'Stalemate!'
|
|
19
|
+
return game_exit
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
return unless fifty_move_draw?(board)
|
|
23
|
+
|
|
24
|
+
puts 'draw'
|
|
25
|
+
game_exit
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# check if player is in checkmate
|
|
29
|
+
#
|
|
30
|
+
# @param board [Chess::Board]
|
|
31
|
+
def checkmate?(board)
|
|
32
|
+
in_check = if board.current_player == 'w'
|
|
33
|
+
board.white_king.in_check
|
|
34
|
+
else
|
|
35
|
+
board.black_king.in_check
|
|
36
|
+
end
|
|
37
|
+
return false unless in_check
|
|
38
|
+
|
|
39
|
+
return false if any_legal_move?(board)
|
|
40
|
+
|
|
41
|
+
true
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# check if player is in stalemate
|
|
45
|
+
#
|
|
46
|
+
# @param board [Chess::Board]
|
|
47
|
+
def stalemate?(board)
|
|
48
|
+
in_check = if board.current_player == 'w'
|
|
49
|
+
board.white_king.in_check
|
|
50
|
+
else
|
|
51
|
+
board.black_king.in_check
|
|
52
|
+
end
|
|
53
|
+
return false if in_check
|
|
54
|
+
|
|
55
|
+
return false if any_legal_move?(board)
|
|
56
|
+
|
|
57
|
+
true
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# check if any legal move available
|
|
61
|
+
#
|
|
62
|
+
# @param board [Chess::Board]
|
|
63
|
+
def any_legal_move?(board)
|
|
64
|
+
pieces = if board.current_player == 'w'
|
|
65
|
+
board.white_pieces
|
|
66
|
+
else
|
|
67
|
+
board.black_pieces
|
|
68
|
+
end
|
|
69
|
+
total_moves = []
|
|
70
|
+
pieces.each do |piece|
|
|
71
|
+
total_moves += valid_moves(piece, board)
|
|
72
|
+
end
|
|
73
|
+
total_moves != []
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# check if there's a fifty_move draw (100 half moves)
|
|
77
|
+
#
|
|
78
|
+
# @param board [Chess::Board]
|
|
79
|
+
def fifty_move_draw?(board)
|
|
80
|
+
board.half_move >= 100
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# save the game
|
|
84
|
+
#
|
|
85
|
+
# @param board [Chess::Board]
|
|
86
|
+
def save_game(board)
|
|
87
|
+
save_name = prompt_save_name
|
|
88
|
+
fen = generate_fen_code(board)
|
|
89
|
+
save(save_name, fen)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# exits the game
|
|
93
|
+
# @return [Integer] Exit Code 0
|
|
94
|
+
def game_exit
|
|
95
|
+
pp 'exiting...'
|
|
96
|
+
0
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# save and exit the game
|
|
100
|
+
# @return [Integer] Exit Code 0
|
|
101
|
+
def save_and_exit(board)
|
|
102
|
+
save_game(board)
|
|
103
|
+
game_exit
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Chess
|
|
4
|
+
module Chess
|
|
5
|
+
# Mouse
|
|
6
|
+
module Mouse
|
|
7
|
+
# starts listening mouse input in console
|
|
8
|
+
# @param board [Chess::Board]
|
|
9
|
+
def start_mouse_input(board)
|
|
10
|
+
warn_tmux_users if ENV['TMUX']
|
|
11
|
+
system('stty -icanon -echo') # Disable canonical mode and echo in terminal
|
|
12
|
+
enable_mouse_tracking
|
|
13
|
+
begin
|
|
14
|
+
puts 'Waiting for mouse click... Press Ctrl+C to quit.'
|
|
15
|
+
input_loop(board)
|
|
16
|
+
ensure # run these even if Ctrl+C was pressed
|
|
17
|
+
disable_mouse_tracking
|
|
18
|
+
system('stty icanon echo') # Restore terminal to sane mode
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# warns tmux users that mouse may not work
|
|
23
|
+
def warn_tmux_users
|
|
24
|
+
warn 'Mouse input may not work as expected in tmux'
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# enable the mouse tracking using xterm control sequences -
|
|
28
|
+
# https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
|
|
29
|
+
def enable_mouse_tracking
|
|
30
|
+
print "\e[?9h" # Enable X10 mouse tracking
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# disable the mouse tracking using xterm control sequences -
|
|
34
|
+
# https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
|
|
35
|
+
def disable_mouse_tracking
|
|
36
|
+
print "\e[?9l" # Disable X10 mouse tracking
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# runs loop for mouse input
|
|
40
|
+
# @param board [Chess::Board]
|
|
41
|
+
def input_loop(board) # rubocop:disable Metrics/MethodLength
|
|
42
|
+
file_coords = generate_file_coords
|
|
43
|
+
rank_coords = generate_rank_coords
|
|
44
|
+
loop do
|
|
45
|
+
char = $stdin.getc
|
|
46
|
+
next unless char == "\e"
|
|
47
|
+
|
|
48
|
+
coord = read_input(char)
|
|
49
|
+
board_pos = clicked_element(coord, file_coords, rank_coords)
|
|
50
|
+
clicked = read_clicked(board_pos, coord)
|
|
51
|
+
return_value = select_click_action(board, clicked, board_pos, coord)
|
|
52
|
+
break if game_exit?(return_value)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# reads whole input sequence and returns the coords X and Y
|
|
57
|
+
# @param char [Char]
|
|
58
|
+
# @return [Array(x,y)]
|
|
59
|
+
def read_input(char)
|
|
60
|
+
shift = 32
|
|
61
|
+
sequence = char + $stdin.read(5) # Read the full ESC [ M SPACE Cx Cy stored in buffer
|
|
62
|
+
sequence.bytes[4..].map { |e| e - shift } # shift and return the Cx Cy only
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# read what was clicked
|
|
66
|
+
# @example file and rank are decomposed from array
|
|
67
|
+
# read_clicked([file,rank],coord)
|
|
68
|
+
#
|
|
69
|
+
# @param coord [Array]
|
|
70
|
+
# @return [String]
|
|
71
|
+
def read_clicked((file, rank), coord)
|
|
72
|
+
if !(file.nil? || rank.nil?)
|
|
73
|
+
'board'
|
|
74
|
+
elsif buttons_touched?(coord)
|
|
75
|
+
'button'
|
|
76
|
+
else
|
|
77
|
+
'outside'
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# checks if exit code 0 is returned
|
|
82
|
+
# @param value [Integer,nil]
|
|
83
|
+
def game_exit?(value)
|
|
84
|
+
value == 0 # rubocop:disable Style/NumericPredicate
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Chess
|
|
4
|
+
module Chess
|
|
5
|
+
# Mouse
|
|
6
|
+
module Mouse
|
|
7
|
+
# returns the position of board where mouse clicked on console display
|
|
8
|
+
# @example x and y are decomposed from array
|
|
9
|
+
# clicked_element([3,2],file_coords,rank_coords)
|
|
10
|
+
#
|
|
11
|
+
# @param file_coords [Hash]
|
|
12
|
+
# @param rank_coords [Hash]
|
|
13
|
+
# @return [Array(file,rank)]
|
|
14
|
+
def clicked_element((x, y), file_coords, rank_coords)
|
|
15
|
+
[file_coords[x], rank_coords[y]]
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# generate file coords for the display
|
|
19
|
+
# @return [Hash] file_coords
|
|
20
|
+
def generate_file_coords
|
|
21
|
+
file_coords = {}
|
|
22
|
+
file_ord = 97 # ord for "a"
|
|
23
|
+
board_start_x = 2
|
|
24
|
+
board_end_x = 25
|
|
25
|
+
(board_start_x..board_end_x).each_slice(3) do |arr| # a board square's string length on display is 3.
|
|
26
|
+
arr.each { |elem| file_coords[elem] = file_ord.chr }
|
|
27
|
+
file_ord += 1
|
|
28
|
+
end
|
|
29
|
+
file_coords
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# generate rank coord for the display
|
|
33
|
+
# @return [Hash] rank_coords
|
|
34
|
+
def generate_rank_coords
|
|
35
|
+
rank_coords = {}
|
|
36
|
+
rank = 7 # black is on upper side of board so ranks go from 8 to 1 (7 to 0 cuz index start = 0) from top to bottom
|
|
37
|
+
board_start_y = 2
|
|
38
|
+
board_end_y = 9
|
|
39
|
+
(board_start_y..board_end_y).each do |elem|
|
|
40
|
+
rank_coords[elem] = rank
|
|
41
|
+
rank -= 1
|
|
42
|
+
end
|
|
43
|
+
rank_coords
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# checks if a button was touched
|
|
47
|
+
# @example x and y are decomposed from array
|
|
48
|
+
# buttons_touched?([x,y])
|
|
49
|
+
def buttons_touched?((coord_x, coord_y))
|
|
50
|
+
y = 11
|
|
51
|
+
return false if coord_y != y
|
|
52
|
+
|
|
53
|
+
start_x = 5
|
|
54
|
+
end_x = 15
|
|
55
|
+
save_and_exit = Array(start_x..end_x)
|
|
56
|
+
start_x = 19
|
|
57
|
+
end_x = 22
|
|
58
|
+
exit = Array(start_x..end_x)
|
|
59
|
+
(save_and_exit + exit).include?(coord_x)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# returns which button was clicked
|
|
63
|
+
# @example x and y are decomposed from array
|
|
64
|
+
# button_type([x,y])
|
|
65
|
+
# @return [String]
|
|
66
|
+
def button_type((x, _y))
|
|
67
|
+
if x.between?(5, 15)
|
|
68
|
+
'save&exit'
|
|
69
|
+
else
|
|
70
|
+
'game_exit'
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Chess
|
|
4
|
+
module Chess
|
|
5
|
+
# Pieces
|
|
6
|
+
module Pieces
|
|
7
|
+
# BishopMoves
|
|
8
|
+
module BishopMoves
|
|
9
|
+
def north_west_moves(board, file, rank)
|
|
10
|
+
north_west = board.north_west_pos(file, rank)
|
|
11
|
+
|
|
12
|
+
moves = []
|
|
13
|
+
while board.empty_at?(*north_west)
|
|
14
|
+
moves << north_west if board.pos_in_range?(north_west)
|
|
15
|
+
north_west = board.north_west_pos(*north_west)
|
|
16
|
+
end
|
|
17
|
+
moves << north_west if board.pos_in_range?(north_west) && board.enemy_at?(*north_west)
|
|
18
|
+
moves
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def south_west_moves(board, file, rank)
|
|
22
|
+
south_west = board.south_west_pos(file, rank)
|
|
23
|
+
|
|
24
|
+
moves = []
|
|
25
|
+
while board.empty_at?(*south_west)
|
|
26
|
+
moves << south_west if board.pos_in_range?(south_west)
|
|
27
|
+
south_west = board.south_west_pos(*south_west)
|
|
28
|
+
end
|
|
29
|
+
moves << south_west if board.pos_in_range?(south_west) && board.enemy_at?(*south_west)
|
|
30
|
+
moves
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def north_east_moves(board, file, rank)
|
|
34
|
+
north_east = board.north_east_pos(file, rank)
|
|
35
|
+
|
|
36
|
+
moves = []
|
|
37
|
+
while board.empty_at?(*north_east)
|
|
38
|
+
moves << north_east if board.pos_in_range?(north_east)
|
|
39
|
+
north_east = board.north_east_pos(*north_east)
|
|
40
|
+
end
|
|
41
|
+
moves << north_east if board.pos_in_range?(north_east) && board.enemy_at?(*north_east)
|
|
42
|
+
moves
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def south_east_moves(board, file, rank)
|
|
46
|
+
south_east = board.south_east_pos(file, rank)
|
|
47
|
+
|
|
48
|
+
moves = []
|
|
49
|
+
while board.empty_at?(*south_east)
|
|
50
|
+
moves << south_east if board.pos_in_range?(south_east)
|
|
51
|
+
south_east = board.south_east_pos(*south_east)
|
|
52
|
+
end
|
|
53
|
+
moves << south_east if board.pos_in_range?(south_east) && board.enemy_at?(*south_east)
|
|
54
|
+
moves
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Bishop
|
|
59
|
+
class Bishop < Piece
|
|
60
|
+
include BishopMoves
|
|
61
|
+
|
|
62
|
+
# all possible_moves of Bishop
|
|
63
|
+
#
|
|
64
|
+
# @param board [Chess::Board]
|
|
65
|
+
# @return [Array] possible_moves_arr
|
|
66
|
+
def possible_moves(board)
|
|
67
|
+
file = @pos[0]
|
|
68
|
+
rank = @pos[1]
|
|
69
|
+
moves = []
|
|
70
|
+
moves += north_west_moves(board, file, rank)
|
|
71
|
+
moves += north_east_moves(board, file, rank)
|
|
72
|
+
moves += south_west_moves(board, file, rank)
|
|
73
|
+
moves += south_east_moves(board, file, rank)
|
|
74
|
+
moves
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|