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.
@@ -0,0 +1,166 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Chess
4
+ module Chess
5
+ # Pieces
6
+ module Pieces
7
+ # King
8
+ class King < Piece
9
+ include Chess::Save::FenCodeFromBoard
10
+
11
+ attr_accessor :in_check
12
+
13
+ def initialize(color)
14
+ super
15
+ @in_check = false
16
+ end
17
+
18
+ # all possible_moves of Bishop
19
+ #
20
+ # @param board [Chess::Board]
21
+ # @return [Array] possible_moves_arr
22
+ def possible_moves(board)
23
+ file = @pos[0]
24
+ rank = @pos[1]
25
+ moves = []
26
+ moves += one_step_all_direction_moves(board, file, rank)
27
+ moves += castling_moves(board)
28
+ moves
29
+ end
30
+
31
+ # gets one step possible move in all direction for king
32
+ #
33
+ # @param board [Chess::Board]
34
+ # @param file [String]
35
+ # @param rank [Integer]
36
+ # @return [Array] all_dir_possible_moves_arr
37
+ def one_step_all_direction_moves(board, file, rank)
38
+ north = board.north_pos(file, rank)
39
+ south = board.south_pos(file, rank)
40
+ west = board.west_pos(file, rank)
41
+ east = board.east_pos(file, rank)
42
+ north_west = board.north_west_pos(file, rank)
43
+ south_east = board.south_east_pos(file, rank)
44
+ south_west = board.south_west_pos(file, rank)
45
+ north_east = board.north_east_pos(file, rank)
46
+ directions = north, south, west, east, north_west, south_east, south_west, north_east
47
+
48
+ get_moves_on_direction(board, directions)
49
+ end
50
+
51
+ # helper method for {#one_step_all_direction_moves}
52
+ #
53
+ # @param board [Chess::Board]
54
+ # @param directions [Array]
55
+ # @return [Array] all_dir_possible_moves_arr
56
+ def get_moves_on_direction(board, directions)
57
+ moves = []
58
+ directions.each do |direction|
59
+ if board.pos_in_range?(direction) && (board.empty_at?(*direction) || board.enemy_at?(*direction))
60
+ moves << direction
61
+ end
62
+ end
63
+ moves
64
+ end
65
+
66
+ # finds possible castling moves
67
+ #
68
+ # @param board [Chess::Board]
69
+ # @return [Array] castling_moves_arr
70
+ def castling_moves(board)
71
+ moves = []
72
+ if valid_castling_rights?(board)
73
+ if white?
74
+ moves += white_castling_moves(board)
75
+ elsif black?
76
+ moves += black_castling_moves(board)
77
+ end
78
+ end
79
+ moves
80
+ end
81
+
82
+ # check if valid_castling_rights exits
83
+ #
84
+ # @param board [Chess::Board]
85
+ def valid_castling_rights?(board)
86
+ return false if board.castling_rights == '-'
87
+
88
+ board.castling_rights.include?('K') ||
89
+ board.castling_rights.include?('Q') ||
90
+ board.castling_rights.include?('k') ||
91
+ board.castling_rights.include?('q')
92
+ end
93
+
94
+ # helper method for {#castling_moves}
95
+ # @param (see #castling_moves)
96
+ # @return (see #castling_moves)
97
+ def white_castling_moves(board)
98
+ moves = []
99
+ moves << ['g', 0] if board.castling_rights.include?('K') && can_castle?(board, 'K')
100
+ moves << ['c', 0] if board.castling_rights.include?('Q') && can_castle?(board, 'Q')
101
+ moves
102
+ end
103
+
104
+ # helper method for {#castling_moves}
105
+ # @param (see #castling_moves)
106
+ # @return (see #castling_moves)
107
+ def black_castling_moves(board)
108
+ moves = []
109
+ moves << ['g', 7] if board.castling_rights.include?('k') && can_castle?(board, 'k')
110
+ moves << ['c', 7] if board.castling_rights.include?('q') && can_castle?(board, 'q')
111
+ moves
112
+ end
113
+
114
+ # check if can castle(no_piece_in_between) for the given castling_right
115
+ #
116
+ # @param board [Chess::Board]
117
+ # @param castling_right [String]
118
+ def can_castle?(board, castling_right)
119
+ case castling_right
120
+ when 'Q'
121
+ no_piece_in_between?(board, ['a', 0], ['e', 0])
122
+ when 'K'
123
+ no_piece_in_between?(board, ['e', 0], ['h', 0])
124
+ when 'q'
125
+ no_piece_in_between?(board, ['a', 7], ['e', 7])
126
+ when 'k'
127
+ no_piece_in_between?(board, ['e', 7], ['h', 7])
128
+ end
129
+ end
130
+
131
+ # checks if no piece in between start and finish
132
+ # helper method for {#can_castle?}
133
+ #
134
+ # @param board [Chess::Board]
135
+ # @param start [Array]
136
+ # @param finish [Array]
137
+ def no_piece_in_between?(board, start, finish)
138
+ curr_file = start.first
139
+ rank = start.last
140
+ pieces = []
141
+ loop do
142
+ curr_file = (curr_file.ord + 1).chr
143
+ break if curr_file == finish.first
144
+
145
+ pieces << board.piece_at(curr_file, rank)
146
+ end
147
+ pieces.all?('')
148
+ end
149
+
150
+ # checks if king is in check by one or more opponent_piece
151
+ #
152
+ # @param board [Chess::Board]
153
+ # @param opponent_pieces [Chess::Board#white_pieces, Chess::Board#black_pieces]
154
+ def in_check?(board, opponent_pieces)
155
+ @in_check = false
156
+ opponent_pieces.each do |piece|
157
+ break if @in_check
158
+
159
+ possible_moves = piece.possible_moves(board)
160
+ @in_check = true if possible_moves.include?(@pos)
161
+ end
162
+ @in_check
163
+ end
164
+ end
165
+ end
166
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Chess
4
+ module Chess
5
+ # Pieces
6
+ module Pieces
7
+ # Knight
8
+ class Knight < Piece
9
+ # all possible_moves of Knight
10
+ #
11
+ # @param board [Chess::Board]
12
+ # @return [Array] possible_moves_arr
13
+ def possible_moves(board)
14
+ file = @pos[0]
15
+ rank = @pos[1]
16
+ moves = []
17
+ moves += north_moves(board, file, rank)
18
+ moves += south_moves(board, file, rank)
19
+ moves += west_moves(board, file, rank)
20
+ moves += east_moves(board, file, rank)
21
+ moves
22
+ end
23
+
24
+ def north_moves(board, file, rank)
25
+ moves = []
26
+ north_steps = 2
27
+ two_step_north = [file, rank + north_steps]
28
+
29
+ east = board.east_pos(*two_step_north)
30
+ west = board.west_pos(*two_step_north)
31
+ moves << east if board.pos_in_range?(east) && (board.empty_at?(*east) || board.enemy_at?(*east))
32
+ moves << west if board.pos_in_range?(west) && (board.empty_at?(*west) || board.enemy_at?(*west))
33
+ moves
34
+ end
35
+
36
+ def south_moves(board, file, rank)
37
+ moves = []
38
+ south_steps = 2
39
+ two_step_south = [file, rank - south_steps]
40
+
41
+ return moves unless board.pos_in_range?(two_step_south)
42
+
43
+ east = board.east_pos(*two_step_south)
44
+ west = board.west_pos(*two_step_south)
45
+ [east, west].each do |pos|
46
+ moves << pos if board.pos_in_range?(pos) && (board.empty_at?(*pos) || board.enemy_at?(*pos))
47
+ end
48
+ moves
49
+ end
50
+
51
+ def west_moves(board, file, rank)
52
+ moves = []
53
+ west_steps = 2
54
+ two_step_west = [(file.ord - west_steps).chr, rank]
55
+
56
+ return moves unless board.pos_in_range?(two_step_west)
57
+
58
+ north = board.north_pos(*two_step_west)
59
+ south = board.south_pos(*two_step_west)
60
+ [north, south].each do |pos|
61
+ moves << pos if board.pos_in_range?(pos) && (board.empty_at?(*pos) || board.enemy_at?(*pos))
62
+ end
63
+ moves
64
+ end
65
+
66
+ def east_moves(board, file, rank)
67
+ moves = []
68
+ east_steps = 2
69
+ two_step_east = [(file.ord + east_steps).chr, rank]
70
+ north = board.north_pos(*two_step_east)
71
+ south = board.south_pos(*two_step_east)
72
+ [north, south].each do |pos|
73
+ moves << pos if board.pos_in_range?(pos) && (board.empty_at?(*pos) || board.enemy_at?(*pos))
74
+ end
75
+ moves
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,163 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Chess
4
+ module Chess
5
+ # Pieces
6
+ module Pieces
7
+ # Pawn
8
+ class Pawn < Piece
9
+ # possible_moves of the pawn
10
+ # @param board [Chess::Board]
11
+ # @return [Array] moves
12
+ def possible_moves(board)
13
+ moves = []
14
+ file = @pos[0]
15
+ rank = @pos[1]
16
+ moves += pawn_moves_by_color(board, file, rank)
17
+ moves
18
+ end
19
+
20
+ # checks if it's first move of pawn
21
+ # @param rank [Integer]
22
+ def first_move?(rank)
23
+ (rank == 1 && white?) || (rank == 6 && black?)
24
+ end
25
+
26
+ # creates all the possible first moves
27
+ # @param board [Chess::Board]
28
+ # @param file [String]
29
+ # @param rank [Integer]
30
+ # @return [Array] moves
31
+ def pawn_moves_by_color(board, file, rank)
32
+ moves = []
33
+ moves += one_step_forward(board, file, rank)
34
+ moves += two_step_forward(board, file, rank) if first_move?(rank)
35
+ moves += attack_en_passant(board, file, rank) unless first_move?(rank)
36
+ moves += cross_side_attack(board, file, rank)
37
+ moves.reject!(&:empty?)
38
+ moves
39
+ end
40
+
41
+ # one step move of pawn
42
+ # @param board [Chess::Board]
43
+ # @param file [String]
44
+ # @param rank [Integer]
45
+ # @return [Array] moves
46
+ def one_step_forward(board, file, rank)
47
+ moves = []
48
+ if white?
49
+ north = board.north_pos(file, rank)
50
+ moves << north if board.empty_at?(*north)
51
+ else
52
+ south = board.south_pos(file, rank)
53
+ moves << south if board.empty_at?(*south) && board.pos_in_range?(south)
54
+ end
55
+ moves
56
+ end
57
+
58
+ # two step move of pawn
59
+ # @param board [Chess::Board]
60
+ # @param file [String]
61
+ # @param rank [Integer]
62
+ # @return [Array] moves
63
+ def two_step_forward(board, file, rank)
64
+ moves = []
65
+ moves << if white?
66
+ north_two_step(board, file, rank)
67
+ else
68
+ south_two_step(board, file, rank)
69
+ end
70
+ moves
71
+ end
72
+
73
+ # helper method for {#two_step_forward}
74
+ # @param (see #two_step_forward)
75
+ # @return (see #two_step_forward)
76
+ def north_two_step(board, file, rank)
77
+ north_one_step = board.north_pos(file, rank)
78
+ north_two_step = board.north_pos(*north_one_step)
79
+ return [] unless board.empty_at?(*north_one_step) && board.empty_at?(*north_two_step)
80
+
81
+ north_two_step
82
+ end
83
+
84
+ # helper method for {#two_step_forward}
85
+ # @param (see #two_step_forward)
86
+ # @return (see #two_step_forward)
87
+ def south_two_step(board, file, rank)
88
+ south_one_step = board.south_pos(file, rank)
89
+ south_two_step = board.south_pos(*south_one_step)
90
+ unless board.empty_at?(*south_one_step) && board.empty_at?(*south_two_step) &&
91
+ board.pos_in_range?(south_one_step) && board.pos_in_range?(south_two_step)
92
+
93
+ return []
94
+ end
95
+
96
+ south_two_step
97
+ end
98
+
99
+ # cross attack moves of pawn
100
+ # @param board [Chess::Board]
101
+ # @param file [String]
102
+ # @param rank [Integer]
103
+ # @return [Array] moves
104
+ def cross_side_attack(board, file, rank)
105
+ moves = []
106
+ moves += if white?
107
+ north_attack(board, file, rank)
108
+ else
109
+ south_attack(board, file, rank)
110
+ end
111
+ moves
112
+ end
113
+
114
+ # helper method for {#cross_side_attack}
115
+ # @param (see #cross_side_attack)
116
+ # @return (see #cross_side_attack)
117
+ def north_attack(board, file, rank)
118
+ moves = []
119
+ north_east = board.north_east_pos(file, rank)
120
+ north_west = board.north_west_pos(file, rank)
121
+ [north_east, north_west].each do |pos|
122
+ moves << pos if !(board.empty_at?(*pos) || board.pos_nil?(pos)) && board.piece_at(*pos).black?
123
+ end
124
+ moves
125
+ end
126
+
127
+ # helper method for {#cross_side_attack}
128
+ # @param (see #cross_side_attack)
129
+ # @return (see #cross_side_attack)
130
+ def south_attack(board, file, rank)
131
+ moves = []
132
+ south_east = board.south_east_pos(file, rank)
133
+ south_west = board.south_west_pos(file, rank)
134
+ [south_east, south_west].each do |pos|
135
+ moves << pos if !(board.empty_at?(*pos) || board.pos_nil?(pos)) &&
136
+ board.piece_at(*pos).white? &&
137
+ board.pos_in_range?(pos)
138
+ end
139
+ moves
140
+ end
141
+
142
+ # moves for attacking an pawn which is a {Chess::Board#possible_en_passant_target}
143
+ #
144
+ # @param (see #cross_side_attack)
145
+ # @return (see #cross_side_attack)
146
+ def attack_en_passant(board, file, rank) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
147
+ moves = []
148
+ if white?
149
+ north_east = board.north_east_pos(file, rank)
150
+ moves << north_east if (north_east.first + north_east.last.to_s) == board.possible_en_passant_target
151
+ north_west = board.north_west_pos(file, rank)
152
+ moves << north_west if (north_west.first + north_west.last.to_s) == board.possible_en_passant_target
153
+ else
154
+ south_east = board.south_east_pos(file, rank)
155
+ moves << south_east if (south_east.first + south_east.last.to_s) == board.possible_en_passant_target
156
+ south_west = board.south_west_pos(file, rank)
157
+ moves << south_west if (south_west.first + south_west.last.to_s) == board.possible_en_passant_target
158
+ end
159
+ moves
160
+ end
161
+ end
162
+ end
163
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Chess
4
+ module Chess
5
+ # Pieces
6
+ module Pieces
7
+ # Piece
8
+ class Piece
9
+ attr_reader :color
10
+ attr_accessor :pos, :valid_moves
11
+
12
+ def initialize(color)
13
+ @color = color
14
+ @pos = []
15
+ @valid_moves = []
16
+ end
17
+
18
+ # checks if piece color is black
19
+ def black?
20
+ @color == 'black'
21
+ end
22
+
23
+ # checks if piece color is white
24
+ def white?
25
+ @color == 'white'
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Chess
4
+ module Chess
5
+ # Pieces
6
+ module Pieces
7
+ # Queen
8
+ class Queen < Piece
9
+ include RookMoves
10
+ include BishopMoves
11
+
12
+ # all possible_moves of Queen
13
+ #
14
+ # @param board [Chess::Board]
15
+ # @return [Array] possible_moves_arr
16
+ def possible_moves(board)
17
+ file = @pos[0]
18
+ rank = @pos[1]
19
+ moves = []
20
+ moves += rook_moves(board, file, rank)
21
+ moves += bishop_moves(board, file, rank)
22
+ moves
23
+ end
24
+
25
+ def rook_moves(board, file, rank)
26
+ moves = []
27
+ moves += north_moves(board, file, rank)
28
+ moves += south_moves(board, file, rank)
29
+ moves += west_moves(board, file, rank)
30
+ moves += east_moves(board, file, rank)
31
+ moves
32
+ end
33
+
34
+ def bishop_moves(board, file, rank)
35
+ moves = []
36
+ moves += north_west_moves(board, file, rank)
37
+ moves += north_east_moves(board, file, rank)
38
+ moves += south_west_moves(board, file, rank)
39
+ moves += south_east_moves(board, file, rank)
40
+ moves
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Chess
4
+ module Chess
5
+ # Pieces
6
+ module Pieces
7
+ # RookMoves
8
+ module RookMoves
9
+ def north_moves(board, file, rank)
10
+ north = board.north_pos(file, rank)
11
+
12
+ moves = []
13
+ while board.empty_at?(*north)
14
+ moves << north if board.pos_in_range?(north)
15
+ north = board.north_pos(*north)
16
+ end
17
+ moves << north if board.pos_in_range?(north) && board.enemy_at?(*north)
18
+ moves
19
+ end
20
+
21
+ def south_moves(board, file, rank)
22
+ south = board.south_pos(file, rank)
23
+
24
+ moves = []
25
+ while board.empty_at?(*south)
26
+ moves << south if board.pos_in_range?(south)
27
+ south = board.south_pos(*south)
28
+ end
29
+ moves << south if board.pos_in_range?(south) && board.enemy_at?(*south)
30
+ moves
31
+ end
32
+
33
+ def east_moves(board, file, rank)
34
+ east = board.east_pos(file, rank)
35
+
36
+ moves = []
37
+ while board.empty_at?(*east)
38
+ moves << east if board.pos_in_range?(east)
39
+ east = board.east_pos(*east)
40
+ end
41
+ moves << east if board.pos_in_range?(east) && board.enemy_at?(*east)
42
+ moves
43
+ end
44
+
45
+ def west_moves(board, file, rank)
46
+ west = board.west_pos(file, rank)
47
+
48
+ moves = []
49
+ while board.empty_at?(*west)
50
+ moves << west if board.pos_in_range?(west)
51
+ west = board.west_pos(*west)
52
+ end
53
+ moves << west if board.pos_in_range?(west) && board.enemy_at?(*west)
54
+ moves
55
+ end
56
+ end
57
+
58
+ # Rook
59
+ class Rook < Piece
60
+ include RookMoves
61
+
62
+ # all possible_moves of Rook
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_moves(board, file, rank)
71
+ moves += south_moves(board, file, rank)
72
+ moves += west_moves(board, file, rank)
73
+ moves += east_moves(board, file, rank)
74
+ moves
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This is a script file handling procedular stuff of this game.
4
+
5
+ # Starts a Chess Game
6
+ #
7
+ # see {Chess::Display#prompt_start_choices} for start choices.
8
+ #
9
+ # @return [void]
10
+ def start_game
11
+ game = Chess::Game.new
12
+
13
+ choice = game.prompt_start_choices
14
+ case choice
15
+ when 1 then load_game_with_code(
16
+ game,
17
+ 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1'
18
+ ) # new game FEN code
19
+ when 2 then select_save(game)
20
+ when 3 then enter_code(game)
21
+ else start_game end
22
+ end
23
+
24
+ # choice to resume a saved game.
25
+ #
26
+ # {Chess::Save#read} is used to read the file data
27
+ # @param game [Chess::Game]
28
+ # @return [void]
29
+ def select_save(game)
30
+ fen_code = game.prompt_select_save(game.read)
31
+ load_game_with_code(game, fen_code)
32
+ end
33
+
34
+ # choice to start a game using FEN code
35
+ #
36
+ # {Chess::Display#prompt_enter_code} is used for prompting user.
37
+ # @param game [Chess::Game]
38
+ # @return [void]
39
+ def enter_code(game)
40
+ fen_code = game.prompt_enter_code
41
+ load_game_with_code(game, fen_code)
42
+ end
43
+
44
+ # loads a game with given code
45
+ #
46
+ # @param game [Chess::Game]
47
+ # @param fen_code [String] Fen notation for chess board.
48
+ # @return [void]
49
+ def load_game_with_code(game, fen_code)
50
+ board = Chess::Board.new(fen_code)
51
+ system 'clear'
52
+ game.display_board(board.data)
53
+ game.display_buttons
54
+ game.start_mouse_input(board)
55
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Chess
4
+ module Chess
5
+ # PlayKingMoves
6
+ module PlayKingMoves
7
+ # plays the given castling move or normal move for king
8
+ #
9
+ # @param piece [Chess::Pieces::King]
10
+ # @param board [Chess::Board]
11
+ # @param move_pos [Array]
12
+ # @return [void]
13
+ def play_king_move(piece, board, move_pos)
14
+ if castling_move?(piece, board, move_pos)
15
+ play_castling_move(piece, board, move_pos)
16
+ else
17
+ play_normal_move(piece, board, move_pos)
18
+ end
19
+ end
20
+
21
+ # check if move is a castling_move
22
+ #
23
+ # @param (see #play_king_move)
24
+ def castling_move?(piece, board, move_pos)
25
+ castling_moves = piece.castling_moves(board)
26
+ castling_moves.include?(move_pos)
27
+ end
28
+
29
+ # plays the castling move (a special move which moves 2 pieces a king and a rook)
30
+ #
31
+ # @param (see #play_king_move)
32
+ # @return [void]
33
+ def play_castling_move(piece, board, move_pos)
34
+ play_normal_move(piece, board, move_pos)
35
+ play_rook_jump_move(piece, board, move_pos)
36
+ end
37
+
38
+ # plays the move of jumping rook over to the other side of king
39
+ #
40
+ # @param piece [Chess::Pieces::King]
41
+ # @param board [Chess::Board]
42
+ # @param rook_move_pos [Array]
43
+ # @return [void]
44
+ def play_rook_jump_move(piece, board, rook_move_pos) # rubocop:disable Metrics/MethodLength
45
+ king_file = piece.pos.first
46
+ king_rank = piece.pos.last
47
+ if king_file == 'c'
48
+ rook = board.piece_at('a', king_rank)
49
+ rook_move_pos = ['d', king_rank]
50
+ play_normal_move(rook, board, rook_move_pos)
51
+ elsif king_file == 'g'
52
+ rook = board.piece_at('h', king_rank)
53
+ rook_move_pos = ['f', king_rank]
54
+ play_normal_move(rook, board, rook_move_pos)
55
+ end
56
+ end
57
+ end
58
+ end