terminal_chess 0.1.2 → 0.2.0
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 +4 -4
- data/Gemfile.lock +38 -0
- data/README.md +76 -52
- data/lib/board.rb +357 -0
- data/lib/local_chess_client.rb +45 -0
- data/lib/move.rb +97 -107
- data/lib/network_chess_client.rb +104 -0
- data/lib/printer.rb +129 -54
- data/lib/server.rb +65 -0
- data/lib/terminal_chess.rb +7 -26
- data/lib/terminal_chess/messages.rb +27 -0
- data/lib/terminal_chess/version.rb +1 -1
- data/terminal_chess.gemspec +5 -1
- data/test/test_terminal_chess.rb +240 -41
- metadata +65 -4
- data/lib/Board.rb +0 -266
data/lib/printer.rb
CHANGED
@@ -1,77 +1,152 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
module
|
3
|
+
module Printer
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
5
|
+
# TODO : Replace text pieces with unicode symbols
|
6
|
+
PIECE_TO_UNICODE_MAPPING ||= {
|
7
|
+
"pa": "♙",
|
8
|
+
"ro": "♖",
|
9
|
+
"bi": "♗",
|
10
|
+
"kn": "♘",
|
11
|
+
"ki": "♔",
|
12
|
+
"qu": "♕"
|
13
|
+
}
|
14
|
+
COLS ||= ('A'..'H')
|
9
15
|
|
16
|
+
@@subrow = 0 # Reference to current row within a cell
|
17
|
+
@@cell_number = 1 # Reference to the current cell
|
10
18
|
|
11
|
-
|
12
|
-
def
|
13
|
-
|
14
|
-
|
15
|
-
|
19
|
+
|
20
|
+
def print_header
|
21
|
+
# Print chess board Header (Title and then Column Labels A to H)
|
22
|
+
print "\n\t>> Welcome to Terminal Chess v#{TerminalChess::VERSION}\n\n"
|
23
|
+
print "\s\s\s" # cell padding
|
24
|
+
|
25
|
+
COLS.each { |c| print " #{c} " }
|
26
|
+
puts
|
27
|
+
end
|
28
|
+
|
29
|
+
def print_footer
|
30
|
+
# Print chess board footer (Column Labels A to H)
|
31
|
+
print "\s\s\s" # cell padding
|
32
|
+
|
33
|
+
COLS.each { |c| print " #{c} " }
|
34
|
+
puts
|
35
|
+
end
|
36
|
+
|
37
|
+
def print_start_of_row(row_num)
|
38
|
+
# Pad the start of each row with spacing or the row numbers 1..8
|
39
|
+
if row_num
|
40
|
+
print " #{row_num} "
|
41
|
+
else
|
42
|
+
print " " * 3
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def print_end_of_row(row_num)
|
47
|
+
# Print row number 1...8 at end of each row
|
48
|
+
if (@@subrow == 1 || @@subrow == 4) && row_num
|
49
|
+
print " #{row_num}"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def clear_all
|
54
|
+
# Reset counters and clear terminal
|
55
|
+
@@cell_number = 1
|
16
56
|
system "clear" or system "cls"
|
57
|
+
end
|
17
58
|
|
18
|
-
# Header (title & column labels)
|
19
|
-
print "\n\t>> Welcome to Terminal Chess v#{VERSION}\n\n\s\s\s"
|
20
|
-
COLS.each { |c| print " _#{c}__ " }
|
21
|
-
puts "\n"
|
22
59
|
|
23
|
-
|
60
|
+
def printer
|
61
|
+
# Prints the board to terminal, based on layout defined by piece_locations
|
62
|
+
|
63
|
+
clear_all
|
64
|
+
print_header
|
65
|
+
|
66
|
+
# Print first cell of each row, with row number
|
24
67
|
(1..8).each do |row|
|
25
|
-
yield "
|
26
|
-
yield "
|
27
|
-
yield "
|
68
|
+
yield " "
|
69
|
+
yield " XX ", "#{row}"
|
70
|
+
yield " "
|
28
71
|
end
|
29
72
|
|
30
|
-
|
31
|
-
print "\s\s\s"
|
32
|
-
COLS.each { |c| print " #{c} " }
|
33
|
-
puts ""
|
73
|
+
print_footer
|
34
74
|
end
|
35
75
|
|
76
|
+
def substitute_pieces(text, index, color, background_color, piece_locations)
|
77
|
+
piece = piece_to_string(piece_locations[index][:type])
|
78
|
+
|
79
|
+
piece.upcase! unless piece == "pa"
|
80
|
+
piece = piece.colorize(color)
|
81
|
+
|
82
|
+
if background_color == :white
|
83
|
+
text.gsub("XX", piece).on_light_white
|
84
|
+
else
|
85
|
+
text.gsub("XX", piece).on_light_black
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def piece_to_string(piece_name)
|
90
|
+
# Print pieces as two characters
|
91
|
+
# "pawn" -> "pa" , "bishop" -> "BI" , "king" -> "KI" , ...
|
92
|
+
# print nil as " " so it takes up a two character width on the printed board
|
93
|
+
piece_name == nil ? " " : piece_name[0..1]
|
94
|
+
end
|
36
95
|
|
37
96
|
def print_board(piece_locations)
|
38
|
-
printer
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
97
|
+
printer do |tile_text, row_num|
|
98
|
+
|
99
|
+
print_start_of_row(row_num)
|
100
|
+
|
101
|
+
4.times do
|
102
|
+
|
103
|
+
# Print cell and next neighboring cell,
|
104
|
+
# then loop until 4 pairs of 2 cells have been printed, completing row
|
105
|
+
color = piece_locations[@@cell_number][:color] || :black
|
106
|
+
next_color = piece_locations[@@cell_number + 1][:color] || :black
|
107
|
+
|
108
|
+
# Print two rows at a time as every two rows repeat
|
109
|
+
# alternating tile colors
|
110
|
+
# ________________________
|
111
|
+
# | ### ### ### ###| -> subrow 0
|
112
|
+
# | ### ### ### ###| -> subrow 1
|
113
|
+
# | ### ### ### ###| -> subrow 2
|
114
|
+
# |### ### ### ### | -> subrow 3
|
115
|
+
# |### ### ### ### | -> subrow 4
|
116
|
+
# |### ### ### ### | -> subrow 5
|
117
|
+
#
|
118
|
+
|
119
|
+
if @@subrow < 3
|
120
|
+
print substitute_pieces(
|
121
|
+
tile_text, @@cell_number, color, :white, piece_locations
|
122
|
+
)
|
123
|
+
print substitute_pieces(
|
124
|
+
tile_text, @@cell_number + 1, next_color, :black, piece_locations
|
125
|
+
)
|
126
|
+
else
|
127
|
+
print substitute_pieces(
|
128
|
+
tile_text, @@cell_number, color, :black, piece_locations
|
129
|
+
)
|
130
|
+
print substitute_pieces(
|
131
|
+
tile_text, @@cell_number + 1, next_color, :white, piece_locations
|
132
|
+
)
|
53
133
|
end
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
print "#{i}".gsub("XX", piece_locations[@@print_count]["type"][0..1].upcase).colorize(:"#{color}").on_light_black
|
60
|
-
print "#{i}".gsub("XX", piece_locations[@@print_count+1]["type"][0..1].upcase).colorize(:"#{next_color}").on_light_white
|
61
|
-
if "#{i}".include? "XX"
|
62
|
-
@@print_count += 2 unless @@print_count == 63
|
63
|
-
end
|
134
|
+
|
135
|
+
if tile_text.include? "XX"
|
136
|
+
# Incremenet cell_number unless last cell is being printed
|
137
|
+
# to avoid an out of range error
|
138
|
+
@@cell_number += 2 unless @@cell_number == 63
|
64
139
|
end
|
65
140
|
end
|
66
141
|
|
67
|
-
|
68
|
-
if (@@n == 1 || @@n == 4) && j then print " #{j}" end
|
142
|
+
print_end_of_row(row_num)
|
69
143
|
|
70
|
-
# Incriment row index.
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
144
|
+
# Incriment row index.
|
145
|
+
# Reset once n reaches 6 (i.e., two complete cell rows have been printed - the pattern to repeat)
|
146
|
+
@@subrow += 1
|
147
|
+
@@subrow = 0 if @@subrow == 6
|
148
|
+
puts
|
149
|
+
end
|
75
150
|
end
|
76
151
|
|
77
152
|
end
|
data/lib/server.rb
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'em-websocket'
|
2
|
+
|
3
|
+
EventMachine.run do
|
4
|
+
|
5
|
+
clients = []
|
6
|
+
game_ongoing = false
|
7
|
+
p [:start, "Waiting for clients to connect..."]
|
8
|
+
|
9
|
+
EM::WebSocket.start(:host => '0.0.0.0', :port => '4567') do |ws|
|
10
|
+
|
11
|
+
ws.onopen do |handshake|
|
12
|
+
p [:open]
|
13
|
+
if @game_ongoing
|
14
|
+
ws.send "Game already in progress"
|
15
|
+
ws.close
|
16
|
+
else
|
17
|
+
puts "Now connected to client"
|
18
|
+
end
|
19
|
+
|
20
|
+
if clients.length == 0
|
21
|
+
puts "Waiting for second client"
|
22
|
+
clients << ws
|
23
|
+
ws.send "INFO: Player now connected to #{handshake.path}"
|
24
|
+
ws.send "INFO: Awaiting second player..."
|
25
|
+
|
26
|
+
elsif clients.length == 1
|
27
|
+
clients << ws
|
28
|
+
puts "Starting Game..."
|
29
|
+
ws.send "INFO: Player now connected to server at #{handshake.path}"
|
30
|
+
|
31
|
+
clients.each_with_index do |client, idx|
|
32
|
+
client.send "INFO: Connected to remote player"
|
33
|
+
client.send "SETUP: You are player #{idx+1}"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
ws.onmessage do |msg|
|
39
|
+
p [:message, msg]
|
40
|
+
opposing_player = (clients - [ws]).first
|
41
|
+
|
42
|
+
# Send opposing player the new move
|
43
|
+
opposing_player.send msg
|
44
|
+
end
|
45
|
+
|
46
|
+
ws.onerror do |err|
|
47
|
+
p [:error, err]
|
48
|
+
end
|
49
|
+
|
50
|
+
ws.onclose do
|
51
|
+
clients.each do |client|
|
52
|
+
client.send "INFO: Player has left the game"
|
53
|
+
client.close unless client.state == :closed
|
54
|
+
end
|
55
|
+
|
56
|
+
p [:close, "Client disconnected. Disconnecting all players"]
|
57
|
+
|
58
|
+
# End session for all clients
|
59
|
+
clients = []
|
60
|
+
game_ongoing = false
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
data/lib/terminal_chess.rb
CHANGED
@@ -1,32 +1,13 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
$LOAD_PATH << '.'
|
3
|
+
$LOAD_PATH << __FILE__ # '.'
|
4
4
|
|
5
|
-
require_relative
|
6
|
-
require_relative
|
7
|
-
require_relative "move.rb"
|
8
|
-
require_relative "board.rb"
|
5
|
+
require_relative 'local_chess_client'
|
6
|
+
require_relative 'network_chess_client'
|
9
7
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
# Gameplay
|
16
|
-
loop do
|
17
|
-
|
18
|
-
print "\nPiece to Move [#{board.player_turn.capitalize}]: "
|
19
|
-
from = gets.chomp.upcase
|
20
|
-
|
21
|
-
begin
|
22
|
-
print "Valid destinations: #{board.valid_destinations(from).join(", ")}"
|
23
|
-
|
24
|
-
print "\nLocation: "
|
25
|
-
to = gets.chomp.upcase
|
26
|
-
board.move(from, to)
|
27
|
-
|
28
|
-
rescue Exception => e
|
29
|
-
puts "Invalid selection #{e if !ENV["DEV"].nil?}"
|
30
|
-
end
|
8
|
+
if ENV["NGROK"]
|
9
|
+
NetworkChessClient.new(ENV["NGROK"])
|
10
|
+
else
|
11
|
+
LocalChessClient.new
|
31
12
|
end
|
32
13
|
|
@@ -0,0 +1,27 @@
|
|
1
|
+
class Messages
|
2
|
+
|
3
|
+
@red_turn = "It is red's turn. Please move a red piece."
|
4
|
+
@black_turn = "It is black's turn. Please move a black piece."
|
5
|
+
|
6
|
+
@red_in_check = "Please move red king out of check to continue"
|
7
|
+
@black_in_check = "Please move black king out of check to continue"
|
8
|
+
@red_winner = "Checkmate! Red Player Wins!"
|
9
|
+
@black_winner = "Checkmate! Black Player Wins!"
|
10
|
+
@invalid = "Invalid Selection"
|
11
|
+
|
12
|
+
@piece_moved = "Piece moved"
|
13
|
+
|
14
|
+
class << self
|
15
|
+
attr_reader(
|
16
|
+
:red_turn,
|
17
|
+
:black_turn,
|
18
|
+
:red_in_check,
|
19
|
+
:black_in_check,
|
20
|
+
:red_winner,
|
21
|
+
:black_winner,
|
22
|
+
:checkmate,
|
23
|
+
:invalid,
|
24
|
+
:piece_moved
|
25
|
+
)
|
26
|
+
end
|
27
|
+
end
|
data/terminal_chess.gemspec
CHANGED
@@ -6,7 +6,7 @@ require 'terminal_chess/version'
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = 'terminal_chess'
|
8
8
|
s.version = TerminalChess::VERSION
|
9
|
-
s.date = '
|
9
|
+
s.date = '2017-12-12'
|
10
10
|
s.summary = "Chess game playable via the terminal"
|
11
11
|
s.description = "Two player chess game through the terminal"
|
12
12
|
s.authors = ["Jason Willems"]
|
@@ -19,6 +19,10 @@ Gem::Specification.new do |s|
|
|
19
19
|
s.executables << 'terminal_chess'
|
20
20
|
|
21
21
|
s.add_runtime_dependency "colorize"
|
22
|
+
s.add_runtime_dependency "em-websocket"
|
23
|
+
s.add_runtime_dependency "eventmachine"
|
24
|
+
s.add_runtime_dependency "faye-websocket"
|
22
25
|
s.add_development_dependency "bundler", "~> 1.7"
|
23
26
|
s.add_development_dependency "rake", "~> 10.0"
|
27
|
+
s.add_development_dependency "minitest"
|
24
28
|
end
|
data/test/test_terminal_chess.rb
CHANGED
@@ -1,30 +1,37 @@
|
|
1
|
-
#require 'test/unit'
|
2
1
|
require 'minitest/autorun'
|
3
2
|
require "./lib/terminal_chess/version"
|
3
|
+
require "./lib/terminal_chess/messages"
|
4
4
|
require "printer.rb"
|
5
5
|
require "move.rb"
|
6
6
|
require "board.rb"
|
7
7
|
|
8
8
|
class MyIO
|
9
|
-
def gets
|
9
|
+
def gets
|
10
10
|
"Q\n"
|
11
11
|
end
|
12
12
|
end
|
13
13
|
|
14
|
-
|
15
|
-
|
14
|
+
<<CHESSBOARD
|
15
|
+
Tile Positions:
|
16
|
+
|
17
|
+
A,B,C,D,E,F,G,H
|
18
|
+
1 1-8
|
19
|
+
2 9-16
|
20
|
+
3 17-24
|
21
|
+
4 25-32
|
22
|
+
5 33-40
|
23
|
+
6 41-48
|
24
|
+
7 49-56
|
25
|
+
8 57-64
|
26
|
+
CHESSBOARD
|
27
|
+
|
28
|
+
class TestBoard < MiniTest::Test
|
29
|
+
|
16
30
|
def setup
|
17
|
-
|
18
|
-
@columns = %w[A B C D E F G H]
|
19
|
-
@invalid_msg = "Invalid selection"
|
20
|
-
@red_turn_msg = "It is red's turn. Please move a red piece."
|
21
|
-
@black_turn_msg = "It is black's turn. Please move a black piece."
|
22
|
-
@red_in_check = "Please move red king out of check to continue"
|
23
|
-
@black_in_check = "Please move black king out of check to continue"
|
24
31
|
|
32
|
+
@columns = %w[A B C D E F G H]
|
25
33
|
@board = Board.new
|
26
|
-
@board.
|
27
|
-
@board.board_refresh
|
34
|
+
@board.display_board
|
28
35
|
|
29
36
|
def valid_piece_movement(from)
|
30
37
|
@board.valid_destinations(from)
|
@@ -37,33 +44,42 @@ class TestBoard < MiniTest::Unit::TestCase
|
|
37
44
|
def get_board
|
38
45
|
@board.piece_manifest
|
39
46
|
end
|
40
|
-
|
47
|
+
|
41
48
|
def piece_quantity(piece)
|
42
49
|
all_tiles = get_board
|
43
50
|
count = 0
|
44
51
|
all_tiles.each do |num, details|
|
45
|
-
count += 1 if details.fetch(
|
52
|
+
count += 1 if details.fetch(:type) == piece
|
46
53
|
end
|
54
|
+
|
47
55
|
count
|
48
56
|
end
|
49
57
|
|
50
58
|
def tile_empty(lowerbound, upperbound)
|
51
59
|
all_tiles = get_board
|
52
60
|
empty = true
|
61
|
+
|
53
62
|
all_tiles.each do |num, details|
|
54
63
|
if num >= lowerbound && num <= upperbound
|
55
|
-
empty = empty && details.fetch(
|
64
|
+
empty = empty && details.fetch(:type).nil? # == " "
|
56
65
|
end
|
57
66
|
end
|
67
|
+
|
58
68
|
empty
|
59
69
|
end
|
60
|
-
|
70
|
+
|
61
71
|
def type_on_tile(index)
|
62
72
|
all_tiles = get_board
|
63
73
|
all_tiles.each do |num, details|
|
64
|
-
return details.fetch(
|
74
|
+
return details.fetch(:type) if num == index
|
65
75
|
end
|
66
76
|
end
|
77
|
+
|
78
|
+
def normalized_tile_name(indexes)
|
79
|
+
# 13 -> A3 , 24 -> B4 , etc
|
80
|
+
first, second = indexes.to_s.split("")
|
81
|
+
Hash[("1".."8").zip("A".."H")][first] + second
|
82
|
+
end
|
67
83
|
end
|
68
84
|
|
69
85
|
|
@@ -74,36 +90,51 @@ class TestBoard < MiniTest::Unit::TestCase
|
|
74
90
|
|
75
91
|
|
76
92
|
def test_red_cannot_move_out_of_turn
|
77
|
-
assert_equal(
|
93
|
+
assert_equal(Messages.black_turn, @board.move("C7", "C5"))
|
78
94
|
end
|
79
95
|
|
80
96
|
def test_turn_does_not_change_after_invalid_move
|
81
|
-
|
97
|
+
# Error moving black pawn (invalid)
|
98
|
+
move_piece("H2", "H9")
|
82
99
|
assert_equal(false, tile_empty(16, 16))
|
100
|
+
|
101
|
+
# Success moving black pawn
|
83
102
|
move_piece("H2", "H3")
|
84
103
|
assert_equal(true, tile_empty(16, 16))
|
85
104
|
end
|
86
105
|
|
87
106
|
def test_unmoved_pawns_can_move_one_space
|
88
|
-
assert_equal(piece_quantity(
|
107
|
+
assert_equal(piece_quantity(:pawn), 16)
|
108
|
+
|
89
109
|
@columns.each do |c|
|
90
110
|
move_piece("#{c}2", "#{c}3")
|
91
111
|
move_piece("#{c}7", "#{c}6")
|
92
112
|
end
|
93
|
-
|
94
|
-
assert_equal(
|
95
|
-
|
113
|
+
|
114
|
+
assert_equal(16, piece_quantity(:pawn), "There are no longer 16 pawns on the board")
|
115
|
+
|
116
|
+
assert_equal(true, tile_empty(9, 16), "Pawns failed to move out of their starting positions")
|
117
|
+
assert_equal(false, tile_empty(17, 24), "Pawns failed to move forward to positions one tile forward (tiles 17-24)")
|
118
|
+
|
119
|
+
assert_equal(false, tile_empty(41, 48), "Pawns failed to move forward to positions one tile forward")
|
120
|
+
assert_equal(true, tile_empty(49, 56), "Pawns failed to move out of their starting positions (tiles 49-56)")
|
96
121
|
end
|
97
122
|
|
98
123
|
def test_unmoved_pawns_can_move_two_spaces
|
99
|
-
assert_equal(piece_quantity(
|
124
|
+
assert_equal(piece_quantity(:pawn), 16)
|
125
|
+
|
100
126
|
@columns.each do |c|
|
101
127
|
move_piece("#{c}2", "#{c}4")
|
102
128
|
move_piece("#{c}7", "#{c}5")
|
103
129
|
end
|
104
|
-
|
105
|
-
assert_equal(
|
106
|
-
|
130
|
+
|
131
|
+
assert_equal(16, piece_quantity(:pawn), "There are no longer 16 pawns on the board")
|
132
|
+
|
133
|
+
assert_equal(true, tile_empty(9, 24), "Pawns are still in their initial starting positions (tiles 9-24)")
|
134
|
+
assert_equal(false, tile_empty(25, 32), "Pawns failed to move forward two positions")
|
135
|
+
|
136
|
+
assert_equal(false, tile_empty(33, 40), "Pawns failed to move forward two positions")
|
137
|
+
assert_equal(true, tile_empty(41, 56), "Pawns are still in their initial starting position (tiles 41-56)")
|
107
138
|
end
|
108
139
|
|
109
140
|
def test_pawns_only_attack_diagonal
|
@@ -113,8 +144,19 @@ class TestBoard < MiniTest::Unit::TestCase
|
|
113
144
|
move_piece("G5", "G4")
|
114
145
|
move_piece("B5", "B6")
|
115
146
|
move_piece("G4", "G3")
|
147
|
+
|
116
148
|
assert_equal(["A7", "C7"], valid_piece_movement("B6"), "black pawn should only attack diagonally")
|
117
|
-
assert_equal(["F2", "H2"], valid_piece_movement("G3"), "red pawn should only attack diagonally")
|
149
|
+
assert_equal(["F2", "H2"], valid_piece_movement("G3"), "red pawn should only attack diagonally")
|
150
|
+
end
|
151
|
+
|
152
|
+
def test_bishops_can_move_over_pawns
|
153
|
+
# Black
|
154
|
+
assert_equal(["A3", "C3"], valid_piece_movement("B1"), "bishop should be able to jump over pawns")
|
155
|
+
assert_equal(["F3", "H3"], valid_piece_movement("G1"), "bishop should be able to jump over pawns")
|
156
|
+
|
157
|
+
# Red
|
158
|
+
assert_equal(["A6", "C6"], valid_piece_movement("B8"), "bishop should be able to jump over pawns")
|
159
|
+
assert_equal(["F6", "H6"], valid_piece_movement("G8"), "bishop should be able to jump over pawns")
|
118
160
|
end
|
119
161
|
|
120
162
|
def test_king_cannot_castle_through_check_to_right
|
@@ -127,11 +169,12 @@ class TestBoard < MiniTest::Unit::TestCase
|
|
127
169
|
move_piece("G4", "G5") # black pawn
|
128
170
|
move_piece("G6", "G5") # red rook
|
129
171
|
assert_equal(["F1"], valid_piece_movement("E1"), "King should only be allowed to move right one tile")
|
172
|
+
|
130
173
|
move_piece("A2", "A3") # black pawn
|
131
174
|
move_piece("G5", "G2") # red rook
|
132
175
|
move_piece("A3", "A4") # black pawn
|
133
176
|
move_piece("G2", "F2") # red rook
|
134
|
-
assert_equal(
|
177
|
+
assert_equal(Messages.black_in_check, move_piece("E1", "F1"), "King should not be allowed to move into check")
|
135
178
|
end
|
136
179
|
|
137
180
|
def test_king_cannot_castle_through_check_to_left
|
@@ -144,9 +187,11 @@ class TestBoard < MiniTest::Unit::TestCase
|
|
144
187
|
move_piece("D1", "D2") # black queen
|
145
188
|
move_piece("B6", "B2") # black pawn
|
146
189
|
assert_equal(["C1", "D1"], valid_piece_movement("E1"), "King should only be allowed to move left one or two tiles")
|
190
|
+
|
147
191
|
move_piece("F4", "G5") # black bishop
|
148
192
|
move_piece("B2", "C2") # red rook
|
149
193
|
assert_equal(["D1"], valid_piece_movement("E1"), "King should only be allowed to move left one tile (or it's traversing check)")
|
194
|
+
|
150
195
|
move_piece("G5", "H5") # black bishop
|
151
196
|
move_piece("C2", "D2") # red rook
|
152
197
|
assert_equal(["D1"], valid_piece_movement("E1"), "King should only be allowed to move left one tile (or it's traversing check)")
|
@@ -174,6 +219,80 @@ class TestBoard < MiniTest::Unit::TestCase
|
|
174
219
|
assert_equal(["C1", "D1"], valid_piece_movement("E1"), "King should be allowed to move left one or two tiles")
|
175
220
|
end
|
176
221
|
|
222
|
+
def test_king_cannot_castle_to_right_after_king_has_moved
|
223
|
+
move_piece("G2", "G4") # black pawn
|
224
|
+
move_piece("A7", "A5") # red pawn
|
225
|
+
move_piece("F1", "H3") # black bishop
|
226
|
+
move_piece("A8", "A6") # red rook
|
227
|
+
move_piece("G1", "F3") # black knight
|
228
|
+
move_piece("A5", "A4") # red pawn
|
229
|
+
move_piece("E1", "F1") # move king right one
|
230
|
+
move_piece("H7", "H6") # red pawn
|
231
|
+
move_piece("F1", "E1") # move king back to its original position
|
232
|
+
assert_equal(["F1"], valid_piece_movement("E1"), "King should not be allowed to castle right after moving")
|
233
|
+
end
|
234
|
+
|
235
|
+
def test_king_cannot_castle_to_left_after_king_has_moved
|
236
|
+
move_piece("B1", "A3") #
|
237
|
+
move_piece("A7", "A6") #
|
238
|
+
move_piece("D2", "D4") #
|
239
|
+
move_piece("B7", "B6") #
|
240
|
+
move_piece("C1", "F4") #
|
241
|
+
move_piece("C7", "C6") #
|
242
|
+
move_piece("D1", "D2") #
|
243
|
+
move_piece("D7", "D6") #
|
244
|
+
move_piece("E1", "D1") # Move king left one position
|
245
|
+
move_piece("H7", "H6") # red pawn
|
246
|
+
move_piece("D1", "E1") # Move king back to original position
|
247
|
+
assert_equal(["D1"], valid_piece_movement("E1"), "King should not be allowed to castle left after moving")
|
248
|
+
end
|
249
|
+
|
250
|
+
def test_king_cannot_castle_to_right_after_right_rook_has_moved
|
251
|
+
move_piece("G2", "G4") # black pawn
|
252
|
+
move_piece("A7", "A5") # red pawn
|
253
|
+
move_piece("F1", "H3") # black bishop
|
254
|
+
move_piece("A8", "A6") # red rook
|
255
|
+
move_piece("G1", "F3") # black knight
|
256
|
+
move_piece("A5", "A4") # red pawn
|
257
|
+
move_piece("H1", "G1") # move right rook left one position
|
258
|
+
move_piece("H7", "H6") # red pawn
|
259
|
+
move_piece("G1", "H1") # move right rook back to its original position
|
260
|
+
assert_equal(["F1"], valid_piece_movement("E1"), "King should not be allowed to castle right after right rook has moved")
|
261
|
+
end
|
262
|
+
|
263
|
+
def test_king_cannot_castle_to_left_after_left_rook_has_moved
|
264
|
+
move_piece("B1", "A3") #
|
265
|
+
move_piece("A7", "A6") #
|
266
|
+
move_piece("D2", "D4") #
|
267
|
+
move_piece("B7", "B6") #
|
268
|
+
move_piece("C1", "F4") #
|
269
|
+
move_piece("C7", "C6") #
|
270
|
+
move_piece("D1", "D2") #
|
271
|
+
move_piece("D7", "D6") #
|
272
|
+
move_piece("A1", "B1") # Move left rook right one position
|
273
|
+
move_piece("H7", "H6") # red pawn
|
274
|
+
move_piece("B1", "A1") # Move left rook back to original position
|
275
|
+
assert_equal(["D1"], valid_piece_movement("E1"), "King should not be allowed to castle left after left rook has moved")
|
276
|
+
end
|
277
|
+
|
278
|
+
def test_king_can_castle_to_left_or_right_when_both_moves_valid
|
279
|
+
move_piece("B1", "A3") # black knight
|
280
|
+
move_piece("A7", "A6") # red pawn
|
281
|
+
move_piece("D2", "D4") # black pawn
|
282
|
+
move_piece("B7", "B6") # red pawn
|
283
|
+
move_piece("C1", "F4") # black bishop
|
284
|
+
move_piece("C7", "C6") # red pawn
|
285
|
+
move_piece("D1", "D3") # black queen #=> castle left now valid
|
286
|
+
move_piece("D7", "D6") # red pawn
|
287
|
+
move_piece("G1", "H3") # black knight
|
288
|
+
move_piece("E7", "E6") # red pawn
|
289
|
+
move_piece("G2", "G4") # black pawn
|
290
|
+
move_piece("F7", "F6") # red pawn
|
291
|
+
move_piece("F1", "G2") # black bishop #=> castle right now valid
|
292
|
+
move_piece("G7", "G6") # red pawn
|
293
|
+
assert_equal(["C1", "D1", "D2", "F1", "G1"], valid_piece_movement("E1"), "King should be allowed to castle left and right if both moves are valid")
|
294
|
+
end
|
295
|
+
|
177
296
|
def test_king_can_kill_check_attacker
|
178
297
|
move_piece("A2", "A3") # Error moving black pawn
|
179
298
|
move_piece("A7", "A5") # Error moving red pawn
|
@@ -183,8 +302,9 @@ class TestBoard < MiniTest::Unit::TestCase
|
|
183
302
|
move_piece("A6", "E6") # Error moving red rook
|
184
303
|
move_piece("B3", "B4") # Error moving black pawn
|
185
304
|
move_piece("E6", "E2") # Error moving red rook
|
186
|
-
assert_equal(
|
305
|
+
assert_equal(Messages.black_in_check, move_piece("B4", "B5"))
|
187
306
|
assert_equal(false, tile_empty(5, 5))
|
307
|
+
|
188
308
|
move_piece("E1", "E2") # Error moving black king
|
189
309
|
assert_equal(true, tile_empty(5, 5))
|
190
310
|
end
|
@@ -198,8 +318,9 @@ class TestBoard < MiniTest::Unit::TestCase
|
|
198
318
|
move_piece("A6", "E6") # red rook
|
199
319
|
move_piece("B3", "B4") # black pawn
|
200
320
|
move_piece("E6", "E2") # red rook
|
201
|
-
assert_equal(
|
321
|
+
assert_equal(Messages.black_in_check, move_piece("B4", "B5"))
|
202
322
|
assert_equal(false, tile_empty(4, 4))
|
323
|
+
|
203
324
|
move_piece("D1", "E2")
|
204
325
|
assert_equal(true, tile_empty(4, 4))
|
205
326
|
end
|
@@ -213,8 +334,9 @@ class TestBoard < MiniTest::Unit::TestCase
|
|
213
334
|
move_piece("A6", "E6") # red rook
|
214
335
|
move_piece("G4", "G5") # black pawn
|
215
336
|
move_piece("E6", "E2") # red rook
|
216
|
-
assert_equal(
|
337
|
+
assert_equal(Messages.black_in_check, move_piece("G5", "G6"))
|
217
338
|
assert_equal(false, tile_empty(4, 4))
|
339
|
+
|
218
340
|
move_piece("E1", "F1")
|
219
341
|
assert_equal(true, tile_empty(5,5))
|
220
342
|
end
|
@@ -230,7 +352,7 @@ class TestBoard < MiniTest::Unit::TestCase
|
|
230
352
|
move_piece("H4", "H3")
|
231
353
|
$stdin = MyIO.new
|
232
354
|
move_piece("B7", "C8")
|
233
|
-
assert_equal(
|
355
|
+
assert_equal(:queen, type_on_tile(59))
|
234
356
|
end
|
235
357
|
|
236
358
|
def test_pawn_cannot_be_promoted_out_of_turn
|
@@ -241,8 +363,8 @@ class TestBoard < MiniTest::Unit::TestCase
|
|
241
363
|
move_piece("A5", "A6")
|
242
364
|
move_piece("H5", "H4")
|
243
365
|
move_piece("A6", "B7")
|
244
|
-
$stdin = MyIO.new
|
245
|
-
assert_equal(
|
366
|
+
$stdin = MyIO.new # send 'Q' to stdin for pawn promotion
|
367
|
+
assert_equal(Messages.red_turn, move_piece("B7", "C8"))
|
246
368
|
end
|
247
369
|
|
248
370
|
def test_pawn_cannot_be_promoted_while_check
|
@@ -254,12 +376,89 @@ class TestBoard < MiniTest::Unit::TestCase
|
|
254
376
|
move_piece("H6", "E6")
|
255
377
|
move_piece("A6", "B7")
|
256
378
|
move_piece("E6", "E2")
|
257
|
-
assert_equal(
|
258
|
-
assert_equal(
|
259
|
-
assert_equal(
|
379
|
+
assert_equal(Messages.black_in_check, move_piece("B7", "C8"), "Should not be able to promote pawn while it's in check")
|
380
|
+
assert_equal(:pawn, type_on_tile(50))
|
381
|
+
assert_equal(:bishop, type_on_tile(59))
|
382
|
+
end
|
383
|
+
|
384
|
+
def test_checkmate_smothered_mate_kings_pawn
|
385
|
+
move_piece("E2", "E4")
|
386
|
+
move_piece("E7", "E5")
|
387
|
+
move_piece("G1", "E2")
|
388
|
+
move_piece("B8", "C6")
|
389
|
+
move_piece("B1", "C3")
|
390
|
+
move_piece("C6", "D4")
|
391
|
+
move_piece("G2", "G3")
|
392
|
+
assert_equal(Messages.red_winner, move_piece("D4", "F3"), "Checkmate. Red should be victorious!")
|
393
|
+
end
|
394
|
+
|
395
|
+
def test_checkmate_fools_mate
|
396
|
+
move_piece("F2", "F3")
|
397
|
+
move_piece("E7", "E5")
|
398
|
+
move_piece("G2", "G4")
|
399
|
+
assert_equal(Messages.red_winner, move_piece("D8", "H4"), "Game should have ended as black is in checkmate")
|
400
|
+
end
|
401
|
+
|
402
|
+
def test_not_checkmate_when_piece_can_remove_checker
|
403
|
+
# Black Rook should be able to take red bishop that has black king in check
|
404
|
+
# hence piece is not in checkmate
|
405
|
+
move_piece("F2", "F3") # black pawn
|
406
|
+
move_piece("E7", "E5") # red pawn
|
407
|
+
move_piece("G2", "G4") # black pawn
|
408
|
+
move_piece("A7", "A6") # red pawn
|
409
|
+
move_piece("H2", "H4") # black pawn
|
410
|
+
move_piece("A6", "A5") # red pawn
|
411
|
+
move_piece("H4", "H5") # black pawn
|
412
|
+
move_piece("D8", "H4") # Check (from red queen)
|
413
|
+
move_piece("H1", "H4") # Take queen -> no longer in check
|
414
|
+
assert_equal(:rook, type_on_tile(32)) #H5
|
260
415
|
end
|
261
416
|
|
262
|
-
def
|
263
|
-
#
|
417
|
+
def test_not_checkmate_when_piece_can_block_check_path
|
418
|
+
move_piece("F2", "F3") # black pawn
|
419
|
+
move_piece("E7", "E5") # red pawn
|
420
|
+
move_piece("G2", "G4") # black pawn
|
421
|
+
move_piece("A7", "A6") # red pawn
|
422
|
+
move_piece("H2", "H4") # black pawn
|
423
|
+
move_piece("A6", "A5") # red pawn
|
424
|
+
move_piece("H4", "H5") # black pawn
|
425
|
+
move_piece("A5", "A4") # red pawn
|
426
|
+
move_piece("H1", "H2") # black rook (will be able to block check path)
|
427
|
+
move_piece("D8", "H4") # red queen (black now in check)
|
428
|
+
assert_equal(Messages.black_in_check, move_piece("A2", "A3"), "Should only be able to move out of check for next move")
|
429
|
+
move_piece("H2", "F2") # black rook (check now blocked)
|
430
|
+
move_piece("H4", "G3") # red queen
|
431
|
+
move_piece("B1", "A3") # verify black is no longer in check and can move any piece
|
264
432
|
end
|
433
|
+
|
434
|
+
def test_invalid_pawn_moves_not_accepted
|
435
|
+
piece_to_move = (9.. 16).to_a.sample # piece from black pawn row
|
436
|
+
piece_type = type_on_tile(piece_to_move)
|
437
|
+
invalid_location = ((1..8).to_a + (33..64).to_a).sample # rows outside of scope
|
438
|
+
location_type = type_on_tile(invalid_location)
|
439
|
+
|
440
|
+
from = normalized_tile_name(Board.new.get_rowcol_from_index(piece_to_move))
|
441
|
+
to = normalized_tile_name(Board.new.get_rowcol_from_index(invalid_location))
|
442
|
+
|
443
|
+
move_piece(from, to)
|
444
|
+
|
445
|
+
assert_equal(location_type, type_on_tile(invalid_location))
|
446
|
+
assert_equal(piece_type, type_on_tile(piece_to_move))
|
447
|
+
end
|
448
|
+
|
449
|
+
def test_non_knights_can_not_move_over_populated_pawn_row
|
450
|
+
piece_to_move = ((1..8).to_a - [2, 7]).sample # first row without knights
|
451
|
+
piece_type = type_on_tile(piece_to_move)
|
452
|
+
invalid_location = ((17..64).to_a).sample # rows outside of scope
|
453
|
+
location_type = type_on_tile(invalid_location)
|
454
|
+
|
455
|
+
from = normalized_tile_name(Board.new.get_rowcol_from_index(piece_to_move))
|
456
|
+
to = normalized_tile_name(Board.new.get_rowcol_from_index(invalid_location))
|
457
|
+
|
458
|
+
move_piece(from, to)
|
459
|
+
|
460
|
+
assert_equal(location_type, type_on_tile(invalid_location))
|
461
|
+
assert_equal(piece_type, type_on_tile(piece_to_move))
|
462
|
+
end
|
463
|
+
|
265
464
|
end
|