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.
@@ -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
+
@@ -1,61 +1,63 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- module MOVE
3
+ module Move
4
4
 
5
5
  def constants(piece_mapping, color, piece)
6
6
  @@pieces = piece_mapping
7
- @@color = color
8
- @@enemy_color = (["black", "red"] - ["#{color.downcase}"]).first
9
- @@type = piece
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]["type"]
18
- my_color = manifest[p1]["color"]
19
- constants(manifest, my_color, type)
19
+ type = manifest[p1][:type]
20
+ my_color = manifest[p1][:color]
20
21
 
21
- unless unoccupied?(p1)
22
+ constants(manifest, my_color, type)
22
23
 
23
- if type == "king"
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
- elsif type == "queen"
29
- allowed += [move_lateral(p1)].flatten
30
- allowed += [move_diagonal(p1)].flatten
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
- elsif type == "rook"
33
- allowed += [move_lateral(p1)].flatten
31
+ elsif type == :queen
32
+ allowed += [move_lateral(p1)].flatten
33
+ allowed += [move_diagonal(p1)].flatten
34
34
 
35
- elsif type == "bishop"
36
- allowed += [move_diagonal(p1)].flatten
35
+ elsif type == :rook
36
+ allowed += [move_lateral(p1)].flatten
37
37
 
38
- elsif type == "pawn"
39
- allowed += [pawn(p1)].flatten
38
+ elsif type == :bishop
39
+ allowed += [move_diagonal(p1)].flatten
40
40
 
41
- elsif type == "knight"
42
- allowed += [knight(p1)].flatten
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
- return allowed
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 pawn(p1)
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 == "red"
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]["moved"] && unoccupied?(p1 - 16) && unoccupied?(p1 - 8)
71
+ if !@@pieces[p1][:moved] && unoccupied?(p1 - 8) && unoccupied?(p1 - 16)
70
72
  valid << (p1 - 16)
71
73
  end
72
- elsif @@color == "black"
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]["moved"] && unoccupied?(p1 + 16) && unoccupied?(p1 + 8)
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
- return valid
91
+ valid
88
92
  end
89
93
 
90
94
 
91
95
  # Returns valid positions a knight at index p1 can move to
92
- def knight(p1)
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
- valid_no_ff = []
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
- valid_no_ff << pos
133
+ valid_moves_no_friendly_fire << pos
129
134
  end
130
135
  end
131
136
 
132
- return valid_no_ff
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
- next_pos = index + (i+1)*8
150
- # Valid move if position is unoccupied
151
- if unoccupied?(next_pos)
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
- next_pos = index - (i+1)*8
167
- if unoccupied?(next_pos)
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
- next_pos = index + (i+1)
180
- if unoccupied?(next_pos)
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
- next_pos = index - (i+1)
193
- if unoccupied?(next_pos)
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
- return valid
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
- return valid
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
- # Valid positions for a King to be in order to castle
283
- if index == 5 || index == 61
284
- # Empty space between a King and a Rook
285
- if unoccupied?(index - 1) && unoccupied?(index - 2) && unoccupied?(index - 3) && @@pieces[index - 4]["moved"] == false
286
- # Ensure king does not move through check or into check
287
- if !dangerous_tiles.include?(index - 1) && !dangerous_tiles.include?(index - 2)
288
- # King's castle position
289
- valid << index - 2
290
- end
291
- elsif unoccupied?(index + 1) && unoccupied?(index + 2) && @@pieces[index + 3]["moved"] == false
292
- if !dangerous_tiles.include?(index + 1) && !dangerous_tiles.include?(index + 2)
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
- return valid
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
- if @@pieces[index]["color"].nil?
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
- if @@pieces[index]["moved"]
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
- return @@pieces[index]["color"]
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
- return @@piece_locations[index]["type"] == "king"
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
- return (index - 1)/8 + 1
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
- if index % 8 == 0
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
+