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
@@ -0,0 +1,45 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$LOAD_PATH << __FILE__ # '.'
|
4
|
+
|
5
|
+
require_relative "terminal_chess/version"
|
6
|
+
require_relative "printer.rb"
|
7
|
+
require_relative "move.rb"
|
8
|
+
require_relative "board.rb"
|
9
|
+
|
10
|
+
class LocalChessClient
|
11
|
+
def initialize
|
12
|
+
@board = Board.new
|
13
|
+
@turn_by_turn_playback = []
|
14
|
+
@board.display_board
|
15
|
+
|
16
|
+
start
|
17
|
+
end
|
18
|
+
|
19
|
+
def start
|
20
|
+
# Gameplay
|
21
|
+
loop do
|
22
|
+
if @board.checkmate
|
23
|
+
puts "\nTurn by Turn Playback : #{@turn_by_turn_playback}\n"
|
24
|
+
exit
|
25
|
+
end
|
26
|
+
|
27
|
+
print "\nPiece to Move [#{@board.player_turn.capitalize}]: "
|
28
|
+
from = gets.chomp.upcase
|
29
|
+
|
30
|
+
begin
|
31
|
+
print "Valid destinations: #{@board.valid_destinations(from).join(", ")}"
|
32
|
+
|
33
|
+
print "\nLocation: "
|
34
|
+
to = gets.chomp.upcase
|
35
|
+
@board.move(from, to)
|
36
|
+
|
37
|
+
rescue Exception => e
|
38
|
+
puts "Invalid selection #{e if !ENV["DEV"].nil?}"
|
39
|
+
else
|
40
|
+
@turn_by_turn_playback << [from, to]
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
data/lib/move.rb
CHANGED
@@ -1,61 +1,63 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
module
|
3
|
+
module Move
|
4
4
|
|
5
5
|
def constants(piece_mapping, color, piece)
|
6
6
|
@@pieces = piece_mapping
|
7
|
-
@@color
|
8
|
-
@@
|
9
|
-
@@
|
7
|
+
@@color = color
|
8
|
+
@@type = piece
|
9
|
+
@@enemy_color = ([:black, :red] - [color]).first
|
10
10
|
end
|
11
11
|
|
12
12
|
|
13
13
|
# Calls methods below to return a list of positions which are valid moves
|
14
14
|
# for piece at index p1, given the current board layout as defined in manifest
|
15
15
|
def possible_moves(p1, manifest, castling = false)
|
16
|
+
return [] if manifest[p1][:type].nil?
|
17
|
+
|
16
18
|
allowed = []
|
17
|
-
type = manifest[p1][
|
18
|
-
my_color = manifest[p1][
|
19
|
-
constants(manifest, my_color, type)
|
19
|
+
type = manifest[p1][:type]
|
20
|
+
my_color = manifest[p1][:color]
|
20
21
|
|
21
|
-
|
22
|
+
constants(manifest, my_color, type)
|
22
23
|
|
23
|
-
|
24
|
-
allowed += [move_lateral(p1, 1)].flatten
|
25
|
-
allowed += [move_diagonal(p1, 1)].flatten
|
26
|
-
allowed += [castle(p1)].flatten if castling
|
24
|
+
return [] if unoccupied?(p1)
|
27
25
|
|
28
|
-
|
29
|
-
|
30
|
-
|
26
|
+
if type == :king
|
27
|
+
allowed += [move_lateral(p1, 1)].flatten
|
28
|
+
allowed += [move_diagonal(p1, 1)].flatten
|
29
|
+
allowed += [castle(p1)].flatten if castling
|
31
30
|
|
32
|
-
|
33
|
-
|
31
|
+
elsif type == :queen
|
32
|
+
allowed += [move_lateral(p1)].flatten
|
33
|
+
allowed += [move_diagonal(p1)].flatten
|
34
34
|
|
35
|
-
|
36
|
-
|
35
|
+
elsif type == :rook
|
36
|
+
allowed += [move_lateral(p1)].flatten
|
37
37
|
|
38
|
-
|
39
|
-
|
38
|
+
elsif type == :bishop
|
39
|
+
allowed += [move_diagonal(p1)].flatten
|
40
40
|
|
41
|
-
|
42
|
-
|
43
|
-
end
|
41
|
+
elsif type == :pawn
|
42
|
+
allowed += [move_pawn(p1)].flatten
|
44
43
|
|
44
|
+
elsif type == :knight
|
45
|
+
allowed += [move_knight(p1)].flatten
|
45
46
|
end
|
46
|
-
|
47
|
+
|
48
|
+
allowed
|
47
49
|
end
|
48
50
|
|
49
51
|
|
50
52
|
# Returns all valid positions a pawn at index p1 can move to
|
51
|
-
def
|
53
|
+
def move_pawn(p1)
|
52
54
|
row = get_row_from_index(p1)
|
53
55
|
col = get_col_from_index(p1)
|
54
56
|
valid = []
|
55
57
|
|
56
58
|
# Piece color defines direction of travel. Enemy presence defines
|
57
59
|
# the validity of diagonal movements
|
58
|
-
if @@color ==
|
60
|
+
if @@color == :red
|
59
61
|
if unoccupied?(p1 - 8)
|
60
62
|
valid << (p1 - 8)
|
61
63
|
end
|
@@ -66,10 +68,12 @@ module MOVE
|
|
66
68
|
valid << (p1 - 9)
|
67
69
|
end
|
68
70
|
# Only if the pieces is unmoved, can it move forward two rows
|
69
|
-
if !@@pieces[p1][
|
71
|
+
if !@@pieces[p1][:moved] && unoccupied?(p1 - 8) && unoccupied?(p1 - 16)
|
70
72
|
valid << (p1 - 16)
|
71
73
|
end
|
72
|
-
|
74
|
+
|
75
|
+
elsif @@color == :black
|
76
|
+
|
73
77
|
if unoccupied?(p1 + 8)
|
74
78
|
valid << (p1 + 8)
|
75
79
|
end
|
@@ -79,21 +83,22 @@ module MOVE
|
|
79
83
|
if piece_color(p1 + 9) == @@enemy_color && col < 8
|
80
84
|
valid << (p1 + 9)
|
81
85
|
end
|
82
|
-
if !@@pieces[p1][
|
86
|
+
if !@@pieces[p1][:moved] && unoccupied?(p1 + 8) && unoccupied?(p1 + 16)
|
83
87
|
valid << (p1 + 16)
|
84
88
|
end
|
85
89
|
end
|
86
90
|
|
87
|
-
|
91
|
+
valid
|
88
92
|
end
|
89
93
|
|
90
94
|
|
91
95
|
# Returns valid positions a knight at index p1 can move to
|
92
|
-
def
|
96
|
+
def move_knight(p1)
|
93
97
|
row = get_row_from_index(p1)
|
94
98
|
col = get_col_from_index(p1)
|
99
|
+
|
95
100
|
valid = []
|
96
|
-
|
101
|
+
valid_moves_no_friendly_fire = []
|
97
102
|
|
98
103
|
# Valid knight moves based on its board position
|
99
104
|
if row < 7 && col < 8
|
@@ -125,11 +130,11 @@ module MOVE
|
|
125
130
|
# This iterator filters for friendly fire, and removes indexes pointing to same color pices
|
126
131
|
valid.each do |pos|
|
127
132
|
unless piece_color(pos) == @@color
|
128
|
-
|
133
|
+
valid_moves_no_friendly_fire << pos
|
129
134
|
end
|
130
135
|
end
|
131
136
|
|
132
|
-
|
137
|
+
valid_moves_no_friendly_fire
|
133
138
|
end
|
134
139
|
|
135
140
|
|
@@ -140,66 +145,70 @@ module MOVE
|
|
140
145
|
def move_lateral(index, limit = 8)
|
141
146
|
row = get_row_from_index(index)
|
142
147
|
col = get_col_from_index(index)
|
148
|
+
|
143
149
|
left, right = [col-1, limit].min, [8-col, limit].min
|
144
150
|
up, down = [row-1, limit].min, [8-row, limit].min
|
151
|
+
|
145
152
|
valid = []
|
146
153
|
|
147
154
|
# Move down N places until board limit, piece in the way, or specified limit
|
148
155
|
down.times do |i|
|
149
|
-
|
150
|
-
|
151
|
-
|
156
|
+
next_pos = index + (i+1)*8
|
157
|
+
# Valid move if position is unoccupied
|
158
|
+
if unoccupied?(next_pos)
|
159
|
+
valid << next_pos
|
160
|
+
else
|
161
|
+
# Valid move is piece is an enemy, but then no subsequent tiles are attackable
|
162
|
+
# if the piece is not an enemy, it's not added as a valid move, and no subsequent tiles are attackable
|
163
|
+
# This function doesn't filter out the king from a valid enemy, but the Board class will drop King indexes
|
164
|
+
if piece_color(next_pos) == @@enemy_color
|
152
165
|
valid << next_pos
|
153
|
-
else
|
154
|
-
# Valid move is piece is an enemy, but then no subsequent tiles are attackable
|
155
|
-
# if the piece is not an enemy, it's not added as a valid move, and no subsequent tiles are attackable
|
156
|
-
# This function doesn't filter out the king from a valid enemy, but the Board class will drop King indexes
|
157
|
-
if piece_color(next_pos) == @@enemy_color
|
158
|
-
valid << next_pos
|
159
|
-
end
|
160
|
-
break
|
161
166
|
end
|
167
|
+
|
168
|
+
break
|
169
|
+
end
|
162
170
|
end
|
163
171
|
|
164
172
|
# Move up N places until board limit, piece in the way, or specified limit
|
165
173
|
up.times do |i|
|
166
|
-
|
167
|
-
|
174
|
+
next_pos = index - (i+1)*8
|
175
|
+
if unoccupied?(next_pos)
|
176
|
+
valid << next_pos
|
177
|
+
else
|
178
|
+
if piece_color(next_pos) == @@enemy_color
|
168
179
|
valid << next_pos
|
169
|
-
else
|
170
|
-
if piece_color(next_pos) == @@enemy_color
|
171
|
-
valid << next_pos
|
172
|
-
end
|
173
|
-
break
|
174
180
|
end
|
181
|
+
break
|
182
|
+
end
|
175
183
|
end
|
176
184
|
|
177
185
|
# Move right N places until board limit, piece in the way, or specified limit
|
178
186
|
right.times do |i|
|
179
|
-
|
180
|
-
|
187
|
+
next_pos = index + (i+1)
|
188
|
+
if unoccupied?(next_pos)
|
189
|
+
valid << next_pos
|
190
|
+
else
|
191
|
+
if piece_color(next_pos) == @@enemy_color
|
181
192
|
valid << next_pos
|
182
|
-
else
|
183
|
-
if piece_color(next_pos) == @@enemy_color
|
184
|
-
valid << next_pos
|
185
|
-
end
|
186
|
-
break
|
187
193
|
end
|
194
|
+
break
|
195
|
+
end
|
188
196
|
end
|
189
197
|
|
190
198
|
# Move left N places until board limit, piece in the way, or specified limit
|
191
199
|
left.times do |i|
|
192
|
-
|
193
|
-
|
200
|
+
next_pos = index - (i+1)
|
201
|
+
if unoccupied?(next_pos)
|
202
|
+
valid << next_pos
|
203
|
+
else
|
204
|
+
if piece_color(next_pos) == @@enemy_color
|
194
205
|
valid << next_pos
|
195
|
-
else
|
196
|
-
if piece_color(next_pos) == @@enemy_color
|
197
|
-
valid << next_pos
|
198
|
-
end
|
199
|
-
break
|
200
206
|
end
|
207
|
+
break
|
208
|
+
end
|
201
209
|
end
|
202
|
-
|
210
|
+
|
211
|
+
valid
|
203
212
|
end
|
204
213
|
|
205
214
|
|
@@ -271,7 +280,7 @@ module MOVE
|
|
271
280
|
end
|
272
281
|
end
|
273
282
|
|
274
|
-
|
283
|
+
valid
|
275
284
|
end
|
276
285
|
|
277
286
|
# Castle: king cannot move into check, or through check
|
@@ -279,70 +288,51 @@ module MOVE
|
|
279
288
|
valid = []
|
280
289
|
dangerous_tiles = attack_vectors
|
281
290
|
|
282
|
-
#
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
valid << index + 2
|
294
|
-
end
|
295
|
-
end
|
291
|
+
# King may never have moved
|
292
|
+
return valid unless [5, 61].include?(index) && @@pieces[index][:moved] == false
|
293
|
+
|
294
|
+
# Ensure empty space between a King and a Rook
|
295
|
+
if unoccupied?(index - 1) && unoccupied?(index - 2) && unoccupied?(index - 3) && @@pieces[index - 4][:moved] == false
|
296
|
+
# Ensure king does not move through check or into check, and then add its castle position
|
297
|
+
valid << index - 2 if !dangerous_tiles.include?(index - 1) && !dangerous_tiles.include?(index - 2)
|
298
|
+
end
|
299
|
+
|
300
|
+
if unoccupied?(index + 1) && unoccupied?(index + 2) && @@pieces[index + 3][:moved] == false
|
301
|
+
valid << index + 2 if !dangerous_tiles.include?(index + 1) && !dangerous_tiles.include?(index + 2)
|
296
302
|
end
|
297
|
-
|
303
|
+
|
304
|
+
valid
|
298
305
|
end
|
299
306
|
|
300
307
|
|
301
308
|
# Check if board tile currently has a piece
|
302
309
|
def unoccupied?(index)
|
303
|
-
|
304
|
-
return true
|
305
|
-
else
|
306
|
-
return false
|
307
|
-
end
|
310
|
+
@@pieces[index][:color] == nil ? true : false
|
308
311
|
end
|
309
312
|
|
310
|
-
|
311
313
|
# Return true if the piece has moved before
|
312
314
|
def moved?(index)
|
313
|
-
|
314
|
-
return true
|
315
|
-
else
|
316
|
-
return false
|
317
|
-
end
|
315
|
+
@@pieces[index][:moved] ? true : false
|
318
316
|
end
|
319
317
|
|
320
|
-
|
321
318
|
# Return piece color ("red" or "black") from index (1 - 64)
|
322
319
|
def piece_color(index)
|
323
|
-
|
320
|
+
@@pieces[index][:color]
|
324
321
|
end
|
325
322
|
|
326
|
-
|
327
323
|
# Method used when moving, to verify the piece at index (1 - 64) is not of type "king"
|
328
324
|
def not_king(index)
|
329
|
-
|
325
|
+
@@piece_locations[index][:type] == :king
|
330
326
|
end
|
331
327
|
|
332
|
-
|
333
328
|
# Obtain chess board row number (1 + 8) from an index (1 - 64)
|
334
329
|
def get_row_from_index(index)
|
335
|
-
|
330
|
+
(index - 1)/8 + 1
|
336
331
|
end
|
337
332
|
|
338
|
-
|
339
333
|
# Obtain chess board column number (1 - 8) from an index (1 - 64)
|
340
334
|
def get_col_from_index(index)
|
341
|
-
|
342
|
-
return 8
|
343
|
-
else
|
344
|
-
return index % 8
|
345
|
-
end
|
335
|
+
index % 8 == 0 ? 8 : index % 8
|
346
336
|
end
|
347
337
|
|
348
338
|
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
|
2
|
+
$LOAD_PATH << __FILE__ # '.'
|
3
|
+
|
4
|
+
require 'eventmachine'
|
5
|
+
require 'faye/websocket'
|
6
|
+
|
7
|
+
require_relative "terminal_chess/version"
|
8
|
+
require_relative "terminal_chess/messages"
|
9
|
+
require_relative "printer.rb"
|
10
|
+
require_relative "move.rb"
|
11
|
+
require_relative "board.rb"
|
12
|
+
|
13
|
+
|
14
|
+
class NetworkChessClient
|
15
|
+
def initialize(ngrok)
|
16
|
+
@board = Board.new
|
17
|
+
@turn_by_turn_playback = []
|
18
|
+
@game_started = false
|
19
|
+
@player_num = nil
|
20
|
+
@player_turn = false
|
21
|
+
@messages = []
|
22
|
+
@socket_url = "ws://#{ngrok}.ngrok.io"
|
23
|
+
start_client
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def start_client
|
29
|
+
Thread.new {
|
30
|
+
EM.run do
|
31
|
+
ws = Faye::WebSocket::Client.new(@socket_url)
|
32
|
+
|
33
|
+
ws.on :open do
|
34
|
+
p [:open]
|
35
|
+
end
|
36
|
+
|
37
|
+
ws.on :message do |msg|
|
38
|
+
p [:message, msg.data]
|
39
|
+
|
40
|
+
if msg.data.match "SETUP: You are player [1|2]"
|
41
|
+
@player_num = msg.data.split(' ').last.strip.to_i
|
42
|
+
@player_turn = true if @player_num == 1
|
43
|
+
@game_started = true
|
44
|
+
|
45
|
+
p [:local, "Awaiting opponent move"] if @player_num == 2
|
46
|
+
end
|
47
|
+
|
48
|
+
if msg.data.match "MOVE: [a-zA-Z][0-9], [a-zA-Z][0-9]"
|
49
|
+
from, to = msg.data.match("MOVE: \([a-zA-Z][0-9]\), \([a-zA-Z][0-9]\)").captures
|
50
|
+
|
51
|
+
@board.move(from, to)
|
52
|
+
@player_turn = true
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
ws.on :close do |e|
|
57
|
+
p [:closed, e.code, e.reason]
|
58
|
+
ws = nil
|
59
|
+
EventMachine::stop_event_loop
|
60
|
+
end
|
61
|
+
|
62
|
+
EventMachine::PeriodicTimer.new(1) do
|
63
|
+
next unless @player_turn && @game_started
|
64
|
+
|
65
|
+
piece_moved = local_move
|
66
|
+
if piece_moved
|
67
|
+
@player_turn = false
|
68
|
+
ws.send "MOVE: #{@turn_by_turn_playback.last[0]}, #{@turn_by_turn_playback.last[1]}"
|
69
|
+
puts "Awaiting remote player move"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
}.join
|
74
|
+
end
|
75
|
+
|
76
|
+
|
77
|
+
def local_move
|
78
|
+
if @board.checkmate
|
79
|
+
puts "\nTurn by Turn Playback : #{@turn_by_turn_playback}\n"
|
80
|
+
exit
|
81
|
+
end
|
82
|
+
|
83
|
+
print "\nPiece to Move [#{@board.player_turn.capitalize}]: "
|
84
|
+
from = gets.chomp.upcase
|
85
|
+
|
86
|
+
begin
|
87
|
+
print "Valid destinations: #{@board.valid_destinations(from).join(", ")}"
|
88
|
+
|
89
|
+
print "\nLocation: "
|
90
|
+
to = gets.chomp.upcase
|
91
|
+
moved = @board.move(from, to)
|
92
|
+
|
93
|
+
return local_move() unless moved == Messages.piece_moved
|
94
|
+
|
95
|
+
rescue Exception => e
|
96
|
+
puts "Invalid selection #{e if !ENV["DEV"].nil?}"
|
97
|
+
else
|
98
|
+
@turn_by_turn_playback << [from, to]
|
99
|
+
true
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
|