connect_cuatro 0.2.0 → 0.3.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/lib/board.rb +101 -101
- data/lib/cli.rb +9 -9
- data/lib/connect_four.rb +85 -90
- data/lib/game_engine.rb +98 -99
- data/lib/ip_config.rb +4 -4
- data/lib/msg.rb +51 -52
- data/lib/multiplayer_game_engine.rb +142 -149
- data/lib/player.rb +9 -9
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d32f7b0ef5a218571b39947d24d9ed600e68b89820be801790ca8ae70f9d8c79
|
4
|
+
data.tar.gz: 577496c0635027112f11e0cf06797475c549ce258f6cb79a645cb88e889d7805
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: af2e17e8e65cd9f16f942fd341a2a7b513ca049cea105771845a3c8b844d6451ae60d0ece75b33506d862effd3e3f59015803ae9c4c39e957e7b4079d02f5a6c
|
7
|
+
data.tar.gz: 55d87d9d5eeee647b8f813dbe3e7af31c32a8da43ef64ccccc1f09a216c4e08b3a9edce92a3d11dd7e85ddcd114e0c305201d90be1e9c5e6c30c2b3ffe58f871
|
data/lib/board.rb
CHANGED
@@ -1,101 +1,101 @@
|
|
1
|
-
class Board
|
2
|
-
attr_reader :board
|
3
|
-
|
4
|
-
def initialize(rows = 6, columns = 7)
|
5
|
-
# board 'height'
|
6
|
-
@num_rows = rows
|
7
|
-
# board 'width'
|
8
|
-
@num_columns = columns
|
9
|
-
# Initialize an empty board filled with '.'
|
10
|
-
@board = Array.new(@num_rows) { Array.new(@num_columns,
|
11
|
-
end
|
12
|
-
|
13
|
-
# return x, y coordinates
|
14
|
-
def drop_token(column, token)
|
15
|
-
col_idx = column_to_index(column)
|
16
|
-
row_idx = @num_rows - 1
|
17
|
-
@board.reverse_each do |row|
|
18
|
-
if row[col_idx] == "."
|
19
|
-
# animate dropping of token before return
|
20
|
-
|
21
|
-
row[col_idx] = token
|
22
|
-
return col_idx, row_idx
|
23
|
-
end
|
24
|
-
row_idx -= 1
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
def animate_token(col_idx, row_idx, token)
|
29
|
-
row_idx.times do |i|
|
30
|
-
sleep 0.
|
31
|
-
CLI.clear
|
32
|
-
@board.each_with_index do |row, idx|
|
33
|
-
pseudo_row = row.dup
|
34
|
-
if idx == i
|
35
|
-
pseudo_row[col_idx] = token
|
36
|
-
puts pseudo_row.join(
|
37
|
-
else
|
38
|
-
puts row.join(
|
39
|
-
end
|
40
|
-
end
|
41
|
-
# Print column numbers for easier reference
|
42
|
-
puts COLUMNS[0...@num_columns].join(
|
43
|
-
end
|
44
|
-
sleep 0.77
|
45
|
-
CLI.clear
|
46
|
-
end
|
47
|
-
|
48
|
-
def display
|
49
|
-
# Loop through each row and print it
|
50
|
-
@board.each do |row|
|
51
|
-
puts row.join(
|
52
|
-
end
|
53
|
-
# Print column numbers for easier reference
|
54
|
-
puts COLUMNS[0...@num_columns].join(
|
55
|
-
end
|
56
|
-
|
57
|
-
def column_to_index(column)
|
58
|
-
COLUMNS.index(column)
|
59
|
-
end
|
60
|
-
|
61
|
-
def board_full?
|
62
|
-
!@board.flatten.include?(".")
|
63
|
-
end
|
64
|
-
|
65
|
-
def check_win(column, row, token)
|
66
|
-
DIRECTIONS.each do |dx, dy|
|
67
|
-
count = 1 # Start with the token just placed
|
68
|
-
|
69
|
-
# Check one direction
|
70
|
-
count += check_direction(column, row, dx, dy, token)
|
71
|
-
# Check the opposite direction
|
72
|
-
count += check_direction(column, row, -dx, -dy, token)
|
73
|
-
|
74
|
-
return true if count >= 4 # Four or more in a row
|
75
|
-
end
|
76
|
-
false
|
77
|
-
end
|
78
|
-
|
79
|
-
def check_direction(column, row, dx, dy, token)
|
80
|
-
count = 0
|
81
|
-
x, y = column + dx, row - dy
|
82
|
-
|
83
|
-
while y.between?(0, @num_rows - 1) && x.between?(0, @num_columns - 1) && @board[y][x] == token
|
84
|
-
count += 1
|
85
|
-
x += dx
|
86
|
-
y -= dy
|
87
|
-
end
|
88
|
-
|
89
|
-
count
|
90
|
-
end
|
91
|
-
|
92
|
-
|
93
|
-
COLUMNS = %w
|
94
|
-
|
95
|
-
DIRECTIONS = [
|
96
|
-
[0, 1], # Horizontal
|
97
|
-
[1, 0], # Vertical
|
98
|
-
[1, 1], # Diagonal right
|
99
|
-
[1, -1] # Diagonal left
|
100
|
-
]
|
101
|
-
end
|
1
|
+
class Board
|
2
|
+
attr_reader :board
|
3
|
+
|
4
|
+
def initialize(rows = 6, columns = 7)
|
5
|
+
# board 'height'
|
6
|
+
@num_rows = rows
|
7
|
+
# board 'width'
|
8
|
+
@num_columns = columns
|
9
|
+
# Initialize an empty board filled with '.'
|
10
|
+
@board = Array.new(@num_rows) { Array.new(@num_columns, ".") }
|
11
|
+
end
|
12
|
+
|
13
|
+
# return x, y coordinates
|
14
|
+
def drop_token(column, token)
|
15
|
+
col_idx = column_to_index(column)
|
16
|
+
row_idx = @num_rows - 1
|
17
|
+
@board.reverse_each do |row|
|
18
|
+
if row[col_idx] == "."
|
19
|
+
# animate dropping of token before return
|
20
|
+
animate_token(col_idx, row_idx, token)
|
21
|
+
row[col_idx] = token
|
22
|
+
return col_idx, row_idx
|
23
|
+
end
|
24
|
+
row_idx -= 1
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def animate_token(col_idx, row_idx, token)
|
29
|
+
row_idx.times do |i|
|
30
|
+
sleep 0.26
|
31
|
+
CLI.clear
|
32
|
+
@board.each_with_index do |row, idx|
|
33
|
+
pseudo_row = row.dup
|
34
|
+
if idx == i
|
35
|
+
pseudo_row[col_idx] = token
|
36
|
+
puts pseudo_row.join(" ")
|
37
|
+
else
|
38
|
+
puts row.join(" ")
|
39
|
+
end
|
40
|
+
end
|
41
|
+
# Print column numbers for easier reference
|
42
|
+
puts COLUMNS[0...@num_columns].join(" ")
|
43
|
+
end
|
44
|
+
sleep 0.77
|
45
|
+
CLI.clear
|
46
|
+
end
|
47
|
+
|
48
|
+
def display
|
49
|
+
# Loop through each row and print it
|
50
|
+
@board.each do |row|
|
51
|
+
puts row.join(" ")
|
52
|
+
end
|
53
|
+
# Print column numbers for easier reference
|
54
|
+
puts COLUMNS[0...@num_columns].join(" ")
|
55
|
+
end
|
56
|
+
|
57
|
+
def column_to_index(column)
|
58
|
+
COLUMNS.index(column)
|
59
|
+
end
|
60
|
+
|
61
|
+
def board_full?
|
62
|
+
!@board.flatten.include?(".")
|
63
|
+
end
|
64
|
+
|
65
|
+
def check_win(column, row, token)
|
66
|
+
DIRECTIONS.each do |dx, dy|
|
67
|
+
count = 1 # Start with the token just placed
|
68
|
+
|
69
|
+
# Check one direction
|
70
|
+
count += check_direction(column, row, dx, dy, token)
|
71
|
+
# Check the opposite direction
|
72
|
+
count += check_direction(column, row, -dx, -dy, token)
|
73
|
+
|
74
|
+
return true if count >= 4 # Four or more in a row
|
75
|
+
end
|
76
|
+
false
|
77
|
+
end
|
78
|
+
|
79
|
+
def check_direction(column, row, dx, dy, token)
|
80
|
+
count = 0
|
81
|
+
x, y = column + dx, row - dy
|
82
|
+
|
83
|
+
while y.between?(0, @num_rows - 1) && x.between?(0, @num_columns - 1) && @board[y][x] == token
|
84
|
+
count += 1
|
85
|
+
x += dx
|
86
|
+
y -= dy
|
87
|
+
end
|
88
|
+
|
89
|
+
count
|
90
|
+
end
|
91
|
+
|
92
|
+
|
93
|
+
COLUMNS = %w[A B C D E F G]
|
94
|
+
|
95
|
+
DIRECTIONS = [
|
96
|
+
[0, 1], # Horizontal
|
97
|
+
[1, 0], # Vertical
|
98
|
+
[1, 1], # Diagonal right
|
99
|
+
[1, -1] # Diagonal left
|
100
|
+
]
|
101
|
+
end
|
data/lib/cli.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
|
-
module CLI
|
2
|
-
def self.get_input
|
3
|
-
STDIN.gets.chomp.upcase
|
4
|
-
end
|
5
|
-
|
6
|
-
def self.clear
|
7
|
-
system(
|
8
|
-
end
|
9
|
-
end
|
1
|
+
module CLI
|
2
|
+
def self.get_input
|
3
|
+
STDIN.gets.chomp.upcase
|
4
|
+
end
|
5
|
+
|
6
|
+
def self.clear
|
7
|
+
system("clear")
|
8
|
+
end
|
9
|
+
end
|
data/lib/connect_four.rb
CHANGED
@@ -1,90 +1,85 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
require_relative
|
3
|
-
require_relative
|
4
|
-
require_relative
|
5
|
-
require_relative
|
6
|
-
|
7
|
-
class ConnectFour
|
8
|
-
include
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
elsif game_mode == "2P"
|
40
|
-
@game_engine = MPGameEngine.new
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
end
|
47
|
-
|
48
|
-
def start
|
49
|
-
if @game_engine.
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
#
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
LOADER_MSG(3) # wait 3 seconds
|
87
|
-
end
|
88
|
-
end
|
89
|
-
|
90
|
-
# ConnectFour.new.main_menu
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require_relative "game_engine"
|
3
|
+
require_relative "multiplayer_game_engine"
|
4
|
+
require_relative "msg"
|
5
|
+
require_relative "ip_config"
|
6
|
+
|
7
|
+
class ConnectFour
|
8
|
+
include IpConfig
|
9
|
+
include MSG
|
10
|
+
|
11
|
+
attr_reader :game_engine
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@game_engine = nil
|
15
|
+
end
|
16
|
+
|
17
|
+
def main_menu
|
18
|
+
puts WELCOME_MSG
|
19
|
+
play_quit = CLI.get_input
|
20
|
+
# play_quit = "P" # testing
|
21
|
+
if play_quit == "Q"
|
22
|
+
abort(BYE_MSG)
|
23
|
+
elsif play_quit == "P"
|
24
|
+
puts PLAY_MSG
|
25
|
+
set_game_engine
|
26
|
+
else
|
27
|
+
puts P_OR_Q_ERR_MSG
|
28
|
+
main_menu
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# returns game engine MP or SP
|
33
|
+
def set_game_engine
|
34
|
+
puts ONEPLAYER_TWOPLAYER
|
35
|
+
game_mode = CLI.get_input
|
36
|
+
# game_mode = "2P" # testing
|
37
|
+
if game_mode == "1P"
|
38
|
+
@game_engine = GameEngine.new
|
39
|
+
elsif game_mode == "2P"
|
40
|
+
@game_engine = MPGameEngine.new
|
41
|
+
else
|
42
|
+
puts INPUT_ERR_MSG(game_mode)
|
43
|
+
set_game_engine
|
44
|
+
end
|
45
|
+
start
|
46
|
+
end
|
47
|
+
|
48
|
+
def start
|
49
|
+
loiter if @game_engine.instance_of?(MPGameEngine)
|
50
|
+
@game_engine.play_game
|
51
|
+
main_menu
|
52
|
+
end
|
53
|
+
|
54
|
+
def loiter
|
55
|
+
# make initial curl request
|
56
|
+
username = ENV["USER"]
|
57
|
+
foe = ""
|
58
|
+
p1 = ""
|
59
|
+
# send /init request
|
60
|
+
response = `curl -s "#{P2P_IP}/init?player=#{username}"`.chomp # => '...patient'
|
61
|
+
until response == "start"
|
62
|
+
print WAITING_MSG
|
63
|
+
# send /start request; if game ready to start, will return foe
|
64
|
+
response = `curl -s "#{P2P_IP}/start?player=#{username}"`.chomp
|
65
|
+
if response.include?("start")
|
66
|
+
start_responses = response.split(" ")
|
67
|
+
foe = start_responses[1]
|
68
|
+
p1 = start_responses[2] # p1 used to set @current_player
|
69
|
+
response = "start"
|
70
|
+
break
|
71
|
+
end
|
72
|
+
LOADER_MSG(7) # wait 7 seconds
|
73
|
+
end
|
74
|
+
|
75
|
+
puts ""
|
76
|
+
puts OPP_FOUND
|
77
|
+
@game_engine.player1 = Player.new(username, "X")
|
78
|
+
@game_engine.player2 = Player.new(foe, "O")
|
79
|
+
@game_engine.set_current_player(p1)
|
80
|
+
print "game ready to start"
|
81
|
+
LOADER_MSG(3) # wait 3 seconds
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# ConnectFour.new.main_menu
|
data/lib/game_engine.rb
CHANGED
@@ -1,99 +1,98 @@
|
|
1
|
-
require_relative
|
2
|
-
require_relative
|
3
|
-
require_relative
|
4
|
-
require_relative
|
5
|
-
|
6
|
-
class GameEngine
|
7
|
-
include MSG
|
8
|
-
|
9
|
-
attr_reader :player1,
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
def initialize
|
17
|
-
@player1 = Player.new(ENV[
|
18
|
-
@ai = Player.new("HAL", "O")
|
19
|
-
@players = [@player1, @ai]
|
20
|
-
@board = Board.new
|
21
|
-
@current_player = @players[0]
|
22
|
-
end
|
23
|
-
|
24
|
-
def play_game # Player has hit 'p'
|
25
|
-
game_over = false
|
26
|
-
until game_over
|
27
|
-
puts @board.display
|
28
|
-
# keep false until valid input => in-range, un-filled column
|
29
|
-
turn_over = false
|
30
|
-
plyr = whose_turn
|
31
|
-
token_x, token_y = nil, nil # returned from #drop_token and passed to #win_condition
|
32
|
-
# if human player
|
33
|
-
if plyr == @player1
|
34
|
-
# force user to enter valid input
|
35
|
-
until turn_over
|
36
|
-
# only runs once (right after opponent takes turn)
|
37
|
-
puts PLAYER_TURN_MSG
|
38
|
-
column = CLI.get_input
|
39
|
-
if valid_input(column)
|
40
|
-
token_x, token_y = drop_token(column, plyr.token)
|
41
|
-
turn_over = true
|
42
|
-
else
|
43
|
-
# continue to run until HUMAN user enters valid input
|
44
|
-
puts INPUT_ERR_MSG(column)
|
45
|
-
end
|
46
|
-
end
|
47
|
-
else
|
48
|
-
# computer's turn
|
49
|
-
# TODO trash talk
|
50
|
-
# keep false until computer picks unfilled column (idx always in-range)
|
51
|
-
until turn_over
|
52
|
-
column = Board::COLUMNS.sample
|
53
|
-
if valid_input(column)
|
54
|
-
token_x, token_y = drop_token(column, plyr.token)
|
55
|
-
turn_over = true
|
56
|
-
end
|
57
|
-
end
|
58
|
-
end
|
59
|
-
if @board.check_win(token_x, token_y, plyr.token)
|
60
|
-
puts @board.display
|
61
|
-
puts
|
62
|
-
game_over = true
|
63
|
-
end
|
64
|
-
if @board.board_full?
|
65
|
-
puts @board.display
|
66
|
-
puts TIE_GAME_MSG
|
67
|
-
game_over = true
|
68
|
-
end
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
|
-
def valid_input(column)
|
73
|
-
idx = @board.column_to_index(column)
|
74
|
-
if Board::COLUMNS.include?(column) && @board.board[0][idx] ==
|
75
|
-
|
76
|
-
else
|
77
|
-
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
# returns player object and increments queue
|
82
|
-
def whose_turn
|
83
|
-
plyr = @current_player
|
84
|
-
if plyr == @players[0]
|
85
|
-
@
|
86
|
-
else
|
87
|
-
@
|
88
|
-
end
|
89
|
-
|
90
|
-
end
|
91
|
-
|
92
|
-
def drop_token(column, token)
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
# session
|
99
|
-
# session.main_menu
|
1
|
+
require_relative "board"
|
2
|
+
require_relative "player"
|
3
|
+
require_relative "msg"
|
4
|
+
require_relative "cli"
|
5
|
+
|
6
|
+
class GameEngine
|
7
|
+
include MSG
|
8
|
+
|
9
|
+
attr_reader :player1,
|
10
|
+
:ai,
|
11
|
+
:players,
|
12
|
+
:current_player,
|
13
|
+
:board,
|
14
|
+
:piece_count
|
15
|
+
|
16
|
+
def initialize
|
17
|
+
@player1 = Player.new(ENV["USER"], "X")
|
18
|
+
@ai = Player.new("HAL", "O")
|
19
|
+
@players = [@player1, @ai]
|
20
|
+
@board = Board.new
|
21
|
+
@current_player = @players[0]
|
22
|
+
end
|
23
|
+
|
24
|
+
def play_game # Player has hit 'p'
|
25
|
+
game_over = false
|
26
|
+
until game_over
|
27
|
+
puts @board.display
|
28
|
+
# keep false until valid input => in-range, un-filled column
|
29
|
+
turn_over = false
|
30
|
+
plyr = whose_turn
|
31
|
+
token_x, token_y = nil, nil # returned from #drop_token and passed to #win_condition
|
32
|
+
# if human player
|
33
|
+
if plyr == @player1
|
34
|
+
# force user to enter valid input
|
35
|
+
until turn_over
|
36
|
+
# only runs once (right after opponent takes turn)
|
37
|
+
puts PLAYER_TURN_MSG
|
38
|
+
column = CLI.get_input
|
39
|
+
if valid_input(column)
|
40
|
+
token_x, token_y = drop_token(column, plyr.token)
|
41
|
+
turn_over = true
|
42
|
+
else
|
43
|
+
# continue to run until HUMAN user enters valid input
|
44
|
+
puts INPUT_ERR_MSG(column)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
else
|
48
|
+
# computer's turn
|
49
|
+
# TODO trash talk
|
50
|
+
# keep false until computer picks unfilled column (idx always in-range)
|
51
|
+
until turn_over
|
52
|
+
column = Board::COLUMNS.sample
|
53
|
+
if valid_input(column)
|
54
|
+
token_x, token_y = drop_token(column, plyr.token)
|
55
|
+
turn_over = true
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
if @board.check_win(token_x, token_y, plyr.token)
|
60
|
+
puts @board.display
|
61
|
+
puts VICTORY_MSG(plyr.name)
|
62
|
+
game_over = true
|
63
|
+
end
|
64
|
+
if @board.board_full?
|
65
|
+
puts @board.display
|
66
|
+
puts TIE_GAME_MSG
|
67
|
+
game_over = true
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def valid_input(column)
|
73
|
+
idx = @board.column_to_index(column)
|
74
|
+
if Board::COLUMNS.include?(column) && @board.board[0][idx] == "."
|
75
|
+
true
|
76
|
+
else
|
77
|
+
false
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# returns player object and increments queue
|
82
|
+
def whose_turn
|
83
|
+
plyr = @current_player
|
84
|
+
@current_player = if plyr == @players[0]
|
85
|
+
@players[1]
|
86
|
+
else
|
87
|
+
@players[0]
|
88
|
+
end
|
89
|
+
plyr
|
90
|
+
end
|
91
|
+
|
92
|
+
def drop_token(column, token)
|
93
|
+
@board.drop_token(column, token)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# session = GameEngine.new
|
98
|
+
# session.main_menu
|
data/lib/ip_config.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
module
|
2
|
-
P2P_IP =
|
3
|
-
TEST_IP =
|
4
|
-
end
|
1
|
+
module IpConfig
|
2
|
+
P2P_IP = "52.53.215.135:3333"
|
3
|
+
TEST_IP = "localhost:3333"
|
4
|
+
end
|
data/lib/msg.rb
CHANGED
@@ -1,52 +1,51 @@
|
|
1
|
-
module MSG
|
2
|
-
ONEPLAYER_TWOPLAYER = "1P || 2P"
|
3
|
-
|
4
|
-
WELCOME_MSG = "Let's play **** CONNECT FOUR ****
|
5
|
-
Connect four of your checkers in a row while preventing your opponent from doing the same.\n
|
6
|
-
press 'p' to play; 'q' to quit:"
|
7
|
-
|
8
|
-
PLAY_MSG = "#{ENV["USER"]} says we're playing Connect Four!!"
|
9
|
-
|
10
|
-
BYE_MSG = "Sucka’s only see what’s in front of them while real game playa’s see the whole board, – 777\nDeuces!"
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
end
|
1
|
+
module MSG
|
2
|
+
ONEPLAYER_TWOPLAYER = "1P || 2P"
|
3
|
+
|
4
|
+
WELCOME_MSG = "Let's play **** CONNECT FOUR ****
|
5
|
+
Connect four of your checkers in a row while preventing your opponent from doing the same.\n
|
6
|
+
press 'p' to play; 'q' to quit:"
|
7
|
+
|
8
|
+
PLAY_MSG = "#{ENV["USER"]} says we're playing Connect Four!!"
|
9
|
+
|
10
|
+
BYE_MSG = "Sucka’s only see what’s in front of them while real game playa’s see the whole board, – 777\nDeuces!"
|
11
|
+
|
12
|
+
P_OR_Q_ERR_MSG = "Sorry, I didn't understand that selection. press 'p' to play; 'q' to quit:"
|
13
|
+
|
14
|
+
PLAYER_TURN_MSG = "Choose where you want to drop your token"
|
15
|
+
|
16
|
+
TIE_GAME_MSG = "The board is full!! It's a draw!"
|
17
|
+
|
18
|
+
WAITING_MSG = "Waiting for opponent to join"
|
19
|
+
|
20
|
+
OPP_FOUND = "Opponent found...establishing connection"
|
21
|
+
|
22
|
+
def TIMES_UP_MSG(column)
|
23
|
+
"Time's up: you randomly chose column #{column}"
|
24
|
+
end
|
25
|
+
|
26
|
+
def WAITING_FOR_PLYR_MSG(plyr_name)
|
27
|
+
"Waiting for #{plyr_name} to make a move"
|
28
|
+
end
|
29
|
+
|
30
|
+
def LOADER_MSG(i)
|
31
|
+
i.times do
|
32
|
+
sleep 1.6
|
33
|
+
print " . "
|
34
|
+
end
|
35
|
+
puts ""
|
36
|
+
end
|
37
|
+
|
38
|
+
def VICTORY_MSG(plyr_name)
|
39
|
+
if plyr_name == "HAL"
|
40
|
+
"HAL won!! You just lost to an unliving being!! Boom!!"
|
41
|
+
else
|
42
|
+
"#Hot Dog, we have a Winner! Congratulations #{plyr_name}!!!"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def INPUT_ERR_MSG(input)
|
47
|
+
"#{input} is an invalid selection"
|
48
|
+
end
|
49
|
+
|
50
|
+
# X_OR_O_MSG = "Pick your piece! X or O??"
|
51
|
+
end
|
@@ -1,149 +1,142 @@
|
|
1
|
-
require_relative
|
2
|
-
require_relative
|
3
|
-
require_relative
|
4
|
-
require_relative
|
5
|
-
require_relative
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
include MSG
|
10
|
-
|
11
|
-
attr_reader :board
|
12
|
-
attr_accessor :player1,
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
def initialize
|
17
|
-
@player1 = nil
|
18
|
-
@player2 = nil
|
19
|
-
@current_player = nil
|
20
|
-
@board = Board.new
|
21
|
-
end
|
22
|
-
|
23
|
-
def players
|
24
|
-
[@player1, @player2]
|
25
|
-
end
|
26
|
-
|
27
|
-
def set_current_player(p1)
|
28
|
-
@current_player =
|
29
|
-
end
|
30
|
-
|
31
|
-
def play_game # Player has hit 'p'
|
32
|
-
game_over = false
|
33
|
-
until game_over
|
34
|
-
puts @board.display
|
35
|
-
turn_over = false
|
36
|
-
plyr = whose_turn
|
37
|
-
token_x, token_y = nil, nil # returned from #drop_token and passed to #win_condition
|
38
|
-
# local client
|
39
|
-
if plyr == @player1
|
40
|
-
input_received = false
|
41
|
-
column = nil
|
42
|
-
mutex = Mutex.new
|
43
|
-
|
44
|
-
input_thread = Thread.new do
|
45
|
-
puts PLAYER_TURN_MSG
|
46
|
-
# keep false until valid input => in-range, un-filled column
|
47
|
-
until turn_over
|
48
|
-
ready = IO.select([
|
49
|
-
|
50
|
-
if ready
|
51
|
-
column =
|
52
|
-
if valid_input(column)
|
53
|
-
mutex.synchronize do
|
54
|
-
input_received = true
|
55
|
-
turn_over = true
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
#
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
column
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
response
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
plyr
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
end
|
144
|
-
|
145
|
-
def drop_token(column, token)
|
146
|
-
idx = @board.column_to_index(column)
|
147
|
-
@board.drop_token(column, token)
|
148
|
-
end
|
149
|
-
end
|
1
|
+
require_relative "board"
|
2
|
+
require_relative "player"
|
3
|
+
require_relative "msg"
|
4
|
+
require_relative "cli"
|
5
|
+
require_relative "ip_config"
|
6
|
+
|
7
|
+
class MPGameEngine
|
8
|
+
include IpConfig
|
9
|
+
include MSG
|
10
|
+
|
11
|
+
attr_reader :board
|
12
|
+
attr_accessor :player1,
|
13
|
+
:player2,
|
14
|
+
:current_player
|
15
|
+
|
16
|
+
def initialize
|
17
|
+
@player1 = nil
|
18
|
+
@player2 = nil
|
19
|
+
@current_player = nil
|
20
|
+
@board = Board.new
|
21
|
+
end
|
22
|
+
|
23
|
+
def players
|
24
|
+
[@player1, @player2]
|
25
|
+
end
|
26
|
+
|
27
|
+
def set_current_player(p1)
|
28
|
+
@current_player = players.find { |player| player.name == p1 }
|
29
|
+
end
|
30
|
+
|
31
|
+
def play_game # Player has hit 'p'
|
32
|
+
game_over = false
|
33
|
+
until game_over
|
34
|
+
puts @board.display
|
35
|
+
turn_over = false
|
36
|
+
plyr = whose_turn
|
37
|
+
token_x, token_y = nil, nil # returned from #drop_token and passed to #win_condition
|
38
|
+
# local client
|
39
|
+
if plyr == @player1
|
40
|
+
input_received = false
|
41
|
+
column = nil
|
42
|
+
mutex = Mutex.new
|
43
|
+
|
44
|
+
input_thread = Thread.new do
|
45
|
+
puts PLAYER_TURN_MSG
|
46
|
+
# keep false until valid input => in-range, un-filled column
|
47
|
+
until turn_over
|
48
|
+
ready = IO.select([$stdin], nil, nil, 1.6) # 4th arg is 'release after time' (s)
|
49
|
+
|
50
|
+
if ready
|
51
|
+
column = $stdin.gets.chomp.upcase
|
52
|
+
if valid_input(column)
|
53
|
+
mutex.synchronize do
|
54
|
+
input_received = true
|
55
|
+
turn_over = true
|
56
|
+
end
|
57
|
+
else
|
58
|
+
puts INPUT_ERR_MSG(column)
|
59
|
+
# continue to run until local client enters valid input
|
60
|
+
# loops back to until turn_over
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
timer_thread = Thread.new do
|
67
|
+
10.times do |i|
|
68
|
+
print "#{10 - i}. . "
|
69
|
+
sleep 1.6
|
70
|
+
break if mutex.synchronize { input_received }
|
71
|
+
end
|
72
|
+
# if the player doesn't select a column, random valid selection will occur
|
73
|
+
until turn_over
|
74
|
+
column = Board::COLUMNS.sample
|
75
|
+
if valid_input(column)
|
76
|
+
puts TIMES_UP_MSG(column)
|
77
|
+
mutex.synchronize do
|
78
|
+
input_received = true
|
79
|
+
turn_over = true
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
[input_thread, timer_thread].each(&:join)
|
86
|
+
|
87
|
+
token_x, token_y = drop_token(column, plyr.token)
|
88
|
+
# send valid column selection to server
|
89
|
+
`curl -s "#{P2P_IP}/move?player=#{plyr.name}?column=#{column}"`
|
90
|
+
else # remote client || @player2
|
91
|
+
until turn_over
|
92
|
+
11.times do
|
93
|
+
puts WAITING_FOR_PLYR_MSG(plyr.name)
|
94
|
+
response = `curl -s "#{P2P_IP}/status?player=#{@player1.name}"`.chomp
|
95
|
+
# require 'pry'; binding.pry
|
96
|
+
unless response.include?("patience") # unless this fails => response == <valid letter>
|
97
|
+
token_x, token_y = drop_token(response, plyr.token)
|
98
|
+
turn_over = true
|
99
|
+
break
|
100
|
+
end
|
101
|
+
sleep 1.6
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
if @board.check_win(token_x, token_y, plyr.token)
|
107
|
+
puts @board.display
|
108
|
+
puts VICTORY_MSG(plyr.name)
|
109
|
+
game_over = true
|
110
|
+
end
|
111
|
+
|
112
|
+
if @board.board_full?
|
113
|
+
puts @board.display
|
114
|
+
puts TIE_GAME_MSG
|
115
|
+
game_over = true
|
116
|
+
end
|
117
|
+
end
|
118
|
+
sleep 1.6
|
119
|
+
# send /reset command to server to clear out stored variables
|
120
|
+
`curl -s "#{P2P_IP}/reset"`
|
121
|
+
end
|
122
|
+
|
123
|
+
def valid_input(column)
|
124
|
+
idx = @board.column_to_index(column)
|
125
|
+
Board::COLUMNS.include?(column) && @board.board[0][idx] == "."
|
126
|
+
end
|
127
|
+
|
128
|
+
# returns player object and increments queue
|
129
|
+
def whose_turn
|
130
|
+
plyr = @current_player
|
131
|
+
@current_player = if plyr == players[0]
|
132
|
+
players[1]
|
133
|
+
else
|
134
|
+
players[0]
|
135
|
+
end
|
136
|
+
plyr
|
137
|
+
end
|
138
|
+
|
139
|
+
def drop_token(column, token)
|
140
|
+
@board.drop_token(column, token)
|
141
|
+
end
|
142
|
+
end
|
data/lib/player.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
|
-
class Player
|
2
|
-
attr_reader :name
|
3
|
-
attr_accessor :token
|
4
|
-
|
5
|
-
def initialize(name, token="X")
|
6
|
-
@name = name
|
7
|
-
@token = token
|
8
|
-
end
|
9
|
-
end
|
1
|
+
class Player
|
2
|
+
attr_reader :name
|
3
|
+
attr_accessor :token
|
4
|
+
|
5
|
+
def initialize(name, token = "X")
|
6
|
+
@name = name
|
7
|
+
@token = token
|
8
|
+
end
|
9
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: connect_cuatro
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Matt Darlington
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2023-09-
|
12
|
+
date: 2023-09-16 00:00:00.000000000 Z
|
13
13
|
dependencies: []
|
14
14
|
description: Digital version of the Classic Connect Four boardgame.
|
15
15
|
email:
|