terminal_chess 0.1.2 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|