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
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 4ffa182999d0b42056132dfbd562068e4761d5a1f90b66351a9dc04b280c5c0a
|
|
4
|
+
data.tar.gz: 719c2048abb1e1826ab7abb1b389a5373bc7751b3f6ddbb0fad22abc5ab22e62
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: c555796aa4b5dd6a116174465ec178be2993e747663db5a7fd339f420e64201c5b2cf2c9d2224d8f32fd910d03bc7e252dfc76ff43e846a746f46106249367b8
|
|
7
|
+
data.tar.gz: 1de74e7fb8aab8a626f228250d84c4065608f5b6a422127d54438dfc496ae11f8e79cfc1e86f8feb126f37606f8cc058dcbd2fdc4c078e8159913f56ac93ba5c
|
data/README.md
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Chess
|
|
2
|
+
|
|
3
|
+
<!-- uses shields.io for tags and simpleicons.org for icons -->
|
|
4
|
+
[](https://github.com/XAJX179/Chess/tags)
|
|
5
|
+

|
|
6
|
+
[](https://github.com/XAJX179/Chess/actions/workflows/documentation.yml)
|
|
7
|
+
[](https://github.com/XAJX179/Chess/actions/workflows/tests.yml)
|
|
8
|
+
[](https://github.com/XAJX179/Chess/actions/workflows/rubocop.yml)
|
|
9
|
+
|
|
10
|
+
## About
|
|
11
|
+
|
|
12
|
+
Chess game with terminal ui and mouse click support,
|
|
13
|
+
load game with FEN codes or start new!
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
pre-requisite: ruby >= 3.2
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
gem install chess_tui
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## uninstall
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
gem uninstall chess_tui
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Documentation
|
|
30
|
+
|
|
31
|
+
[Documentation](https://xajx179.github.io/Chess/)
|
|
32
|
+
|
|
33
|
+
## 🫣 Peek
|
|
34
|
+
|
|
35
|
+

|
|
36
|
+
|
|
37
|
+
showcase video -
|
|
38
|
+
|
|
39
|
+
<https://github.com/user-attachments/assets/180d563f-16d9-4f26-9b3a-c16bb66271a2>
|
data/bin/chess_tui
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Chess
|
|
4
|
+
module Chess
|
|
5
|
+
# Board
|
|
6
|
+
class Board
|
|
7
|
+
include BoardFromFenCode
|
|
8
|
+
include BoardPos
|
|
9
|
+
|
|
10
|
+
# @!attribute data
|
|
11
|
+
# 'a’ to ‘h’ are files, rnbqkp are black pieces
|
|
12
|
+
# and RNBQKP are white pieces along the 8 ranks.
|
|
13
|
+
# @example generated data looks like this
|
|
14
|
+
# {
|
|
15
|
+
# "a"=>["R", "P", "", "", "", "", "p", "r"],
|
|
16
|
+
# "b"=>["N", "P", "", "", "", "", "p", "n"],
|
|
17
|
+
# "c"=>["B", "P", "", "", "", "", "p", "b"],
|
|
18
|
+
# "d"=>["Q", "P", "", "", "", "", "p", "q"],
|
|
19
|
+
# "e"=>["K", "P", "", "", "", "", "p", "k"],
|
|
20
|
+
# "f"=>["B", "P", "", "", "", "", "p", "b"],
|
|
21
|
+
# "g"=>["N", "P", "", "", "", "", "p", "n"],
|
|
22
|
+
# "h"=>["R", "P", "", "", "", "", "p", "r"]
|
|
23
|
+
# }
|
|
24
|
+
# @return [Hash] board data
|
|
25
|
+
|
|
26
|
+
attr_accessor :data, :current_player, :castling_rights, :possible_en_passant_target,
|
|
27
|
+
:half_move, :full_move, :white_pieces, :black_pieces, :white_king, :black_king
|
|
28
|
+
|
|
29
|
+
# Returns a new instance of Board.
|
|
30
|
+
# @param fen_code [String]
|
|
31
|
+
def initialize(fen_code)
|
|
32
|
+
@white_pieces = []
|
|
33
|
+
@black_pieces = []
|
|
34
|
+
@data = generate_data(fen_code)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# returns piece at the given position
|
|
38
|
+
# @param file [String]
|
|
39
|
+
# @param rank [Integer]
|
|
40
|
+
# @return any subclass of {Chess::Pieces::Piece}
|
|
41
|
+
def piece_at(file, rank)
|
|
42
|
+
return if @data[file].nil?
|
|
43
|
+
|
|
44
|
+
@data[file][rank]
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# checks if a pos is empty
|
|
48
|
+
# @param file [String]
|
|
49
|
+
# @param rank [Integer]
|
|
50
|
+
def empty_at?(file, rank)
|
|
51
|
+
piece_at(file, rank) == ''
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# removes the piece from board and also from {#black_pieces} or {#white_pieces}
|
|
55
|
+
# @example file and rank are decomposed from array
|
|
56
|
+
# remove_piece_at(['d',0])
|
|
57
|
+
# @return [void]
|
|
58
|
+
def remove_piece_at((file, rank))
|
|
59
|
+
@data[file][rank] = ''
|
|
60
|
+
@black_pieces.reject! { |piece| piece.pos == [file, rank] }
|
|
61
|
+
@white_pieces.reject! { |piece| piece.pos == [file, rank] }
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# inserts given piece at given pos
|
|
65
|
+
# @param piece [Chess::Pieces::Piece]
|
|
66
|
+
# @example file and rank are decomposed from array
|
|
67
|
+
# insert_piece_at(['d',0])
|
|
68
|
+
# @return [void]
|
|
69
|
+
def insert_piece_at(piece, (file, rank))
|
|
70
|
+
@data[file][rank] = piece
|
|
71
|
+
piece.pos = [file, rank]
|
|
72
|
+
if @current_player == 'w'
|
|
73
|
+
@white_pieces << piece unless @white_pieces.include?(piece)
|
|
74
|
+
else
|
|
75
|
+
@black_pieces << piece unless @white_pieces.include?(piece)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# check if enemy at pos
|
|
80
|
+
# @param file [String]
|
|
81
|
+
# @param rank [Integer]
|
|
82
|
+
def enemy_at?(file, rank)
|
|
83
|
+
piece = piece_at(file, rank)
|
|
84
|
+
@current_player != piece.color.chr
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# resets half move to 0
|
|
88
|
+
def reset_half_move
|
|
89
|
+
@half_move = 0
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Chess
|
|
4
|
+
module Chess
|
|
5
|
+
# create board from fen_code
|
|
6
|
+
module BoardFromFenCode
|
|
7
|
+
# generate game’s data from FEN code.
|
|
8
|
+
# @param fen_code [String]
|
|
9
|
+
# @return [Hash] generated_board_data
|
|
10
|
+
def generate_data(fen_code)
|
|
11
|
+
fen_parts_array = fen_code.split
|
|
12
|
+
board_data = fen_parts_array[0]
|
|
13
|
+
@current_player = fen_parts_array[1]
|
|
14
|
+
@castling_rights = fen_parts_array[2]
|
|
15
|
+
@possible_en_passant_target = fen_parts_array[3]
|
|
16
|
+
@half_move = fen_parts_array[4].to_i
|
|
17
|
+
@full_move = fen_parts_array[5].to_i
|
|
18
|
+
|
|
19
|
+
create_board(board_data)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# creates board data
|
|
23
|
+
# @param board_data [String] board_data part of FEN code
|
|
24
|
+
# @return [Hash] board data
|
|
25
|
+
def create_board(board_data)
|
|
26
|
+
ranks = board_data.split('/')
|
|
27
|
+
files = ('a'..'h') # value for 'a' to 'h'
|
|
28
|
+
board = files.to_h { |file| [file, Array.new(8) { '' }] } # empty board
|
|
29
|
+
|
|
30
|
+
board = fill_board(ranks, board)
|
|
31
|
+
@data = board
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# fills board with rank wise data.
|
|
35
|
+
# @param ranks [Array]
|
|
36
|
+
# @param board [Hash]
|
|
37
|
+
# @return [Hash] filled board.
|
|
38
|
+
def fill_board(ranks, board)
|
|
39
|
+
ranks.reverse!
|
|
40
|
+
ranks.each_index do |ranks_array_index|
|
|
41
|
+
rank = ranks[ranks_array_index].chars
|
|
42
|
+
fill_rank(board, rank, ranks_array_index)
|
|
43
|
+
end
|
|
44
|
+
board
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# fills a rank
|
|
48
|
+
# @param board [Hash]
|
|
49
|
+
# @param rank [Array]
|
|
50
|
+
# @param ranks_array_index [Integer]
|
|
51
|
+
# @return [void]
|
|
52
|
+
def fill_rank(board, rank, ranks_array_index)
|
|
53
|
+
index = 0
|
|
54
|
+
rank.each do |letter|
|
|
55
|
+
if letter.to_i.zero?
|
|
56
|
+
index += 1
|
|
57
|
+
insert_piece(board, index, ranks_array_index, letter)
|
|
58
|
+
else
|
|
59
|
+
index += letter.to_i
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# insert a piece on given board
|
|
65
|
+
# @param board [Hash]
|
|
66
|
+
# @param index [Integer]
|
|
67
|
+
# @param ranks_array_index [Integer]
|
|
68
|
+
# @param letter [String]
|
|
69
|
+
# @return [void]
|
|
70
|
+
def insert_piece(board, index, ranks_array_index, letter)
|
|
71
|
+
shift_ord = 96
|
|
72
|
+
file = (shift_ord + index).chr
|
|
73
|
+
rank = ranks_array_index
|
|
74
|
+
|
|
75
|
+
board[file][rank] = create_piece(letter)
|
|
76
|
+
piece_pos = [file, rank]
|
|
77
|
+
board[file][rank].pos = piece_pos
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# create piece from the letter
|
|
81
|
+
# @param letter [String]
|
|
82
|
+
# @return any subclass of {Chess::Pieces::Piece}
|
|
83
|
+
def create_piece(letter)
|
|
84
|
+
if letter == letter.upcase
|
|
85
|
+
piece = white_piece(letter)
|
|
86
|
+
@white_pieces << piece
|
|
87
|
+
else
|
|
88
|
+
piece = black_piece(letter)
|
|
89
|
+
@black_pieces << piece
|
|
90
|
+
end
|
|
91
|
+
piece
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# create white color piece from the letter
|
|
95
|
+
# @param letter [String]
|
|
96
|
+
# @return any subclass of {Chess::Pieces::Piece} with color white
|
|
97
|
+
def white_piece(letter) # rubocop:disable Metrics/MethodLength
|
|
98
|
+
color = 'white'
|
|
99
|
+
case letter
|
|
100
|
+
when 'R'
|
|
101
|
+
Chess::Pieces::Rook.new(color)
|
|
102
|
+
when 'N'
|
|
103
|
+
Chess::Pieces::Knight.new(color)
|
|
104
|
+
when 'B'
|
|
105
|
+
Chess::Pieces::Bishop.new(color)
|
|
106
|
+
when 'Q'
|
|
107
|
+
Chess::Pieces::Queen.new(color)
|
|
108
|
+
when 'K'
|
|
109
|
+
@white_king = Chess::Pieces::King.new(color)
|
|
110
|
+
when 'P'
|
|
111
|
+
Chess::Pieces::Pawn.new(color)
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# create black color piece from the letter
|
|
116
|
+
# @param letter [String]
|
|
117
|
+
# @return any subclass of {Chess::Pieces::Piece} with color black
|
|
118
|
+
def black_piece(letter) # rubocop:disable Metrics/MethodLength
|
|
119
|
+
color = 'black'
|
|
120
|
+
case letter
|
|
121
|
+
when 'r'
|
|
122
|
+
Chess::Pieces::Rook.new(color)
|
|
123
|
+
when 'n'
|
|
124
|
+
Chess::Pieces::Knight.new(color)
|
|
125
|
+
when 'b'
|
|
126
|
+
Chess::Pieces::Bishop.new(color)
|
|
127
|
+
when 'q'
|
|
128
|
+
Chess::Pieces::Queen.new(color)
|
|
129
|
+
when 'k'
|
|
130
|
+
@black_king = Chess::Pieces::King.new(color)
|
|
131
|
+
when 'p'
|
|
132
|
+
Chess::Pieces::Pawn.new(color)
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Chess
|
|
4
|
+
module Chess
|
|
5
|
+
# Board
|
|
6
|
+
module BoardPos
|
|
7
|
+
# the north pos from the given pos
|
|
8
|
+
def north_pos(file, rank)
|
|
9
|
+
[file, rank + 1]
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# the south pos from the given pos
|
|
13
|
+
def south_pos(file, rank)
|
|
14
|
+
[file, rank - 1]
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# the east pos from the given pos
|
|
18
|
+
def east_pos(file, rank)
|
|
19
|
+
[(file.ord - 1).chr, rank]
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# the west pos from the given pos
|
|
23
|
+
def west_pos(file, rank)
|
|
24
|
+
[(file.ord + 1).chr, rank]
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# the north_east_pos pos from the given pos
|
|
28
|
+
def north_east_pos(file, rank)
|
|
29
|
+
[(file.ord - 1).chr, rank + 1]
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# the north_west_pos pos from the given pos
|
|
33
|
+
def north_west_pos(file, rank)
|
|
34
|
+
[(file.ord + 1).chr, rank + 1]
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# the south_east_pos pos from the given pos
|
|
38
|
+
def south_east_pos(file, rank)
|
|
39
|
+
[(file.ord - 1).chr, rank - 1]
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# the south_west_pos pos from the given pos
|
|
43
|
+
def south_west_pos(file, rank)
|
|
44
|
+
[(file.ord + 1).chr, rank - 1]
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# checks if a pos is nil value
|
|
48
|
+
# @example file and rank are decomposed from array
|
|
49
|
+
# pos_nil?(['d',0])
|
|
50
|
+
def pos_nil?((file, rank))
|
|
51
|
+
piece_at(file, rank).nil?
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# checks if a pos is in range
|
|
55
|
+
# @example file and rank are decomposed from array
|
|
56
|
+
# pos_in_range?(['d',0])
|
|
57
|
+
def pos_in_range?((file, rank))
|
|
58
|
+
first_rank = 0
|
|
59
|
+
last_rank = 7
|
|
60
|
+
first_file_ord = 'a'.ord
|
|
61
|
+
last_file_ord = 'h'.ord
|
|
62
|
+
rank.between?(first_rank, last_rank) && file.ord.between?(first_file_ord, last_file_ord)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Chess
|
|
4
|
+
module Chess
|
|
5
|
+
# Console Display
|
|
6
|
+
module Display
|
|
7
|
+
# displays board with ASCII characters
|
|
8
|
+
# @param board_data [Hash]
|
|
9
|
+
# @param valid_moves [Array]
|
|
10
|
+
# @return [void]
|
|
11
|
+
def display_board(board_data, valid_moves = [])
|
|
12
|
+
print_files(board_data)
|
|
13
|
+
print_board_data(board_data, valid_moves)
|
|
14
|
+
print_files(board_data)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# prints the board data
|
|
18
|
+
#
|
|
19
|
+
# @param board_data [Hash]
|
|
20
|
+
# @param valid_moves [Array]
|
|
21
|
+
# @return [void]
|
|
22
|
+
def print_board_data(board_data, valid_moves)
|
|
23
|
+
shift_rank = 1
|
|
24
|
+
(0..7).reverse_each do |rank|
|
|
25
|
+
print rank + shift_rank
|
|
26
|
+
('a'..'h').each do |file|
|
|
27
|
+
bg_color_name = square_bg_color_name(file, rank)
|
|
28
|
+
print_square(board_data, file, rank, bg_color_name, valid_moves)
|
|
29
|
+
end
|
|
30
|
+
print rank + shift_rank
|
|
31
|
+
puts
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# prints files above the board 'a' to 'h'
|
|
36
|
+
#
|
|
37
|
+
# @param board_data [Hash]
|
|
38
|
+
# @return [void]
|
|
39
|
+
def print_files(board_data)
|
|
40
|
+
print ' '
|
|
41
|
+
board_data.each_key { |key| print " #{key} " }
|
|
42
|
+
puts
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# prints a single square
|
|
46
|
+
#
|
|
47
|
+
# @param board_data [Hash]
|
|
48
|
+
# @param file [String]
|
|
49
|
+
# @param rank [Integer]
|
|
50
|
+
# @param bg_color_name [Symbol]
|
|
51
|
+
# @param valid_moves [Array]
|
|
52
|
+
# @return [void]
|
|
53
|
+
def print_square(board_data, file, rank, bg_color_name, valid_moves)
|
|
54
|
+
piece = board_data[file][rank]
|
|
55
|
+
square_pos = [file, rank]
|
|
56
|
+
if piece == ''
|
|
57
|
+
print square_string(piece, ' ', bg_color_name, square_pos, valid_moves)
|
|
58
|
+
else
|
|
59
|
+
print_piece_square(piece, bg_color_name, square_pos, valid_moves)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# helper method for {#print_square}
|
|
64
|
+
#
|
|
65
|
+
# @param piece any subclass of {Chess::Pieces::Piece}
|
|
66
|
+
# @param bg_color_name [Symbol]
|
|
67
|
+
# @param square_pos [Array]
|
|
68
|
+
# @param valid_moves [Array]
|
|
69
|
+
# @return [void]
|
|
70
|
+
def print_piece_square(piece, bg_color_name, square_pos, valid_moves) # rubocop:disable Metrics/MethodLength
|
|
71
|
+
case piece
|
|
72
|
+
when Pieces::Rook
|
|
73
|
+
print square_string(piece, "\u{265C}", bg_color_name, square_pos, valid_moves)
|
|
74
|
+
when Pieces::Knight
|
|
75
|
+
print square_string(piece, "\u{265E}", bg_color_name, square_pos, valid_moves)
|
|
76
|
+
when Pieces::Bishop
|
|
77
|
+
print square_string(piece, "\u{265D}", bg_color_name, square_pos, valid_moves)
|
|
78
|
+
when Pieces::Queen
|
|
79
|
+
print square_string(piece, "\u{265B}", bg_color_name, square_pos, valid_moves)
|
|
80
|
+
when Pieces::King
|
|
81
|
+
print square_string(piece, "\u{265A}", bg_color_name, square_pos, valid_moves)
|
|
82
|
+
when Pieces::Pawn
|
|
83
|
+
print square_string(piece, "\u{265F}", bg_color_name, square_pos, valid_moves)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# returns background color name for the square at
|
|
88
|
+
# given file and rank.
|
|
89
|
+
#
|
|
90
|
+
# @param file [String]
|
|
91
|
+
# @param rank [Integer]
|
|
92
|
+
# @return [Symbol]
|
|
93
|
+
def square_bg_color_name(file, rank)
|
|
94
|
+
if (file.ord.odd? && rank.odd?) || (file.ord.even? && rank.even?)
|
|
95
|
+
:dull_white
|
|
96
|
+
else
|
|
97
|
+
:green
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# arrange and format strings for the square
|
|
102
|
+
#
|
|
103
|
+
# @param piece [Chess::Piece]
|
|
104
|
+
# @param unicode [String]
|
|
105
|
+
# @param bg_color_name [Symbol]
|
|
106
|
+
# @param square_pos [Array]
|
|
107
|
+
# @param valid_moves [Array]
|
|
108
|
+
# @return [String]
|
|
109
|
+
def square_string(piece, unicode, bg_color_name, square_pos, valid_moves)
|
|
110
|
+
if piece == ''
|
|
111
|
+
bg_color("#{move_dots(square_pos, valid_moves)}\u{00A0}\u{00A0}", bg_color_name)
|
|
112
|
+
elsif piece.is_a?(Chess::Pieces::King)
|
|
113
|
+
piece_color = piece.color
|
|
114
|
+
bg_color("#{move_dots(square_pos, valid_moves)}#{color(unicode, piece_color.to_sym)}#{
|
|
115
|
+
king_check_dot(piece)}", bg_color_name)
|
|
116
|
+
else
|
|
117
|
+
piece_color = piece.color
|
|
118
|
+
bg_color("#{move_dots(square_pos, valid_moves)}#{color(unicode, piece_color.to_sym)} ", bg_color_name)
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# gives dot for move for the pos if it's inside valid_moves
|
|
123
|
+
#
|
|
124
|
+
# @param square_pos [Array]
|
|
125
|
+
# @param valid_moves [Array]
|
|
126
|
+
# @return [String] string (with dot or empty)
|
|
127
|
+
def move_dots(square_pos, valid_moves)
|
|
128
|
+
if valid_moves.include?(square_pos)
|
|
129
|
+
color("\u{2022}", :black)
|
|
130
|
+
else
|
|
131
|
+
"\u00A0"
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# gives dot for king in check if given king is in check
|
|
136
|
+
#
|
|
137
|
+
# @param king [Chess::Pieces::King]
|
|
138
|
+
# @return [String] string (with red dot or empty)
|
|
139
|
+
def king_check_dot(king)
|
|
140
|
+
if king.in_check
|
|
141
|
+
color("\u{29BE}", :red)
|
|
142
|
+
else
|
|
143
|
+
"\u00A0"
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# display Save & Exit and Exit buttons
|
|
148
|
+
# return [void]
|
|
149
|
+
def display_buttons
|
|
150
|
+
print ' '
|
|
151
|
+
print bg_color(color('Save & Exit', :green), :black)
|
|
152
|
+
print ' '
|
|
153
|
+
print bg_color(color('Exit', :green), :black)
|
|
154
|
+
puts
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# clears the display and redraws the board with given board_data
|
|
158
|
+
#
|
|
159
|
+
# @param board_data [Hash]
|
|
160
|
+
# @param valid_moves [Array]
|
|
161
|
+
# @return [void]
|
|
162
|
+
def redraw_display(board_data, valid_moves = [])
|
|
163
|
+
system 'clear'
|
|
164
|
+
display_board(board_data, valid_moves)
|
|
165
|
+
display_buttons
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Chess
|
|
4
|
+
module Chess
|
|
5
|
+
# Console Display
|
|
6
|
+
module Display
|
|
7
|
+
# Colors values for use in escape code.
|
|
8
|
+
COLORS = {
|
|
9
|
+
green: '119;149;86',
|
|
10
|
+
dull_white: '235;236;208',
|
|
11
|
+
white: '255;187;143',
|
|
12
|
+
black: '1;1;1',
|
|
13
|
+
red: '249;23;32'
|
|
14
|
+
}.freeze
|
|
15
|
+
|
|
16
|
+
# background color the string
|
|
17
|
+
#
|
|
18
|
+
# @param string [String]
|
|
19
|
+
# @param name [Symbol]
|
|
20
|
+
# @return [String]
|
|
21
|
+
def bg_color(string, name)
|
|
22
|
+
value = COLORS[name]
|
|
23
|
+
"\e[48;2;#{value}m#{string}\e[0m"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# color the string
|
|
27
|
+
#
|
|
28
|
+
# @param string [String]
|
|
29
|
+
# @param name [Symbol]
|
|
30
|
+
# @return [String]
|
|
31
|
+
#
|
|
32
|
+
# @note will not reset the color like #bg_color
|
|
33
|
+
def color(string, name)
|
|
34
|
+
value = COLORS[name]
|
|
35
|
+
"\e[38;2;#{value}m#{string}"
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Chess
|
|
4
|
+
module Chess
|
|
5
|
+
# Console Display
|
|
6
|
+
module Display
|
|
7
|
+
# Prompts user to select between New Game
|
|
8
|
+
# /Load Save/Load Code(FEN) and returns the choice.
|
|
9
|
+
#
|
|
10
|
+
# @return [Integer] 0 for New Game. 1 for Load Save. 2 for Load Code(FEN).
|
|
11
|
+
def prompt_start_choices
|
|
12
|
+
prompt = TTY::Prompt.new
|
|
13
|
+
prompt.select('Create new game or load save/code?') do |menu|
|
|
14
|
+
menu.choice name: 'New Game', value: 1
|
|
15
|
+
if File.file?(SAVE_PATH) # if save.json exists
|
|
16
|
+
menu.choice name: 'Load Save', value: 2
|
|
17
|
+
else
|
|
18
|
+
menu.choice name: 'Load Save', disabled: '(No Save Found.)'
|
|
19
|
+
end
|
|
20
|
+
menu.choice name: 'Load using Code (FEN)', value: 3
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Promps User to select between saved data.
|
|
25
|
+
#
|
|
26
|
+
# @param save_data [Hash]
|
|
27
|
+
# @return [String] the FEN code of selected save name.
|
|
28
|
+
def prompt_select_save(save_data)
|
|
29
|
+
names = save_data.keys
|
|
30
|
+
prompt = TTY::Prompt.new
|
|
31
|
+
selected_save_key = prompt.select('Select a save.', per_page: 15, filter: true) do |menu|
|
|
32
|
+
names.each do |name|
|
|
33
|
+
menu.choice name: name, value: name
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
save_data[selected_save_key]
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Prompts User to enter a FEN code.
|
|
40
|
+
# @note method expects user is entering a valid FEN code.
|
|
41
|
+
#
|
|
42
|
+
# @return [String] the FEN code entered.
|
|
43
|
+
def prompt_enter_code
|
|
44
|
+
prompt = TTY::Prompt.new
|
|
45
|
+
prompt.ask('Enter a valid self attested FEN code : ') do |question|
|
|
46
|
+
regex = %r{\A[prnbqkPRNBQK0-9/ w\-a-h]+\z} # only checks if valid characters
|
|
47
|
+
# are used like 1-8,kqbnrKQBNR, w for white chance(b=bisop already),
|
|
48
|
+
# a-h(for en passant) and the slash(/).
|
|
49
|
+
question.required true
|
|
50
|
+
question.validate regex, 'Invalid FEN Code. '
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Prompts User to choose piece to promote pawn
|
|
55
|
+
#
|
|
56
|
+
# @return [String] the letter of the piece
|
|
57
|
+
def prompt_pawn_promotion_choices
|
|
58
|
+
prompt = TTY::Prompt.new
|
|
59
|
+
prompt.select('Select the piece for pawn promotion!') do |menu|
|
|
60
|
+
menu.choice name: 'Queen', value: 'q'
|
|
61
|
+
menu.choice name: 'Knight', value: 'n'
|
|
62
|
+
menu.choice name: 'Bishop', value: 'b'
|
|
63
|
+
menu.choice name: 'Rook', value: 'r'
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Prompts User to enter a save name
|
|
68
|
+
#
|
|
69
|
+
# @return [String] save_name
|
|
70
|
+
def prompt_save_name
|
|
71
|
+
prompt = TTY::Prompt.new
|
|
72
|
+
prompt.ask('Enter a valid save name : ') do |question|
|
|
73
|
+
regex = /^[\w.]+$/
|
|
74
|
+
question.required true
|
|
75
|
+
question.validate regex, 'Only a-z , A-Z , 0-9 , _ allowed'
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|