connect_cuatro 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 07ff3f60ceb45977060c65c2589af0eafd766e1730ce122d53f3bcdde980b3ef
4
+ data.tar.gz: 3f5fc98c83e067b825386028088218edbba01b4b42c88a4fb61204205e2e180c
5
+ SHA512:
6
+ metadata.gz: f930ad3eacd2bf8f7312905ec7fc77b4721543ea238077593ec51a1b863a1aa402e4150cc37c35929b13a93c99fa73c2acfc39dafa5d8b11e4fd06fc3dab11d6
7
+ data.tar.gz: d7efd9647459774b052e88dccab13f207f819b1a290da20820ae55ba10da155a99ce9f65b426d0914e820be6c8754ca5e938f641db6faef6ba28cae4839848ab
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ require 'connect_four'
3
+
4
+ ConnectFour.new.main_menu
data/lib/board.rb ADDED
@@ -0,0 +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
+ self.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.77
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 ADDED
@@ -0,0 +1,9 @@
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
@@ -0,0 +1,90 @@
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 MSG, IP_CONFIG
9
+
10
+ attr_reader :game_engine
11
+
12
+ def initialize
13
+ @game_engine = nil
14
+ end
15
+
16
+ def main_menu
17
+ puts WELCOME_MSG
18
+ play_quit = CLI.get_input
19
+ # play_quit = "P" # testing
20
+ if play_quit == "Q"
21
+ abort(BYE_MSG)
22
+ elsif play_quit == "P"
23
+ puts PLAY_MSG
24
+ set_game_engine
25
+ else
26
+ puts P_OR_Q_ERR_MSG
27
+ main_menu
28
+ end
29
+ end
30
+
31
+ #returns game engine MP or SP
32
+ def set_game_engine
33
+ puts ONEPLAYER_TWOPLAYER
34
+ game_mode = CLI.get_input
35
+ # game_mode = "2P" # testing
36
+ if game_mode == "1P"
37
+ @game_engine = GameEngine.new
38
+ self.start
39
+ elsif game_mode == "2P"
40
+ @game_engine = MPGameEngine.new
41
+ self.start
42
+ else
43
+ puts INPUT_ERR_MSG(game_mode)
44
+ set_game_engine
45
+ end
46
+ end
47
+
48
+ def start
49
+ if @game_engine.class == MPGameEngine
50
+ self.loiter # wait in lobby
51
+ @game_engine.play_game
52
+ self.main_menu
53
+ else
54
+ @game_engine.play_game
55
+ self.main_menu
56
+ end
57
+ end
58
+
59
+ def loiter
60
+ # make initial curl request
61
+ username = ENV['USER']
62
+ foe = ""
63
+ p1 = ""
64
+ # send /init request
65
+ response = `curl -s "#{P2P_IP}/init?player=#{username}"`.chomp # => '...patient'
66
+ until response == "start"
67
+ print WAITING_MSG
68
+ # send /start request; if game ready to start, will return foe
69
+ response = `curl -s "#{P2P_IP}/start?player=#{username}"`.chomp
70
+ if response.include?("start")
71
+ start_responses = response.split(" ")
72
+ foe = start_responses[1]
73
+ p1 = start_responses[2] # p1 used to set @current_player
74
+ response = "start"
75
+ break
76
+ end
77
+ LOADER_MSG(7) # wait 7 seconds
78
+ end
79
+
80
+ puts ""
81
+ puts OPP_FOUND
82
+ @game_engine.player1 = Player.new(username, token = "X")
83
+ @game_engine.player2 = Player.new(foe, token = "O")
84
+ @game_engine.set_current_player(p1)
85
+ print "game ready to start"
86
+ LOADER_MSG(3) # wait 3 seconds
87
+ end
88
+ end
89
+
90
+ # ConnectFour.new.main_menu
@@ -0,0 +1,99 @@
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
+ return true
76
+ else
77
+ return false
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
+ @current_player = @players[1]
86
+ else
87
+ @current_player = @players[0]
88
+ end
89
+ return plyr
90
+ end
91
+
92
+ def drop_token(column, token)
93
+ idx = @board.column_to_index(column)
94
+ @board.drop_token(column, token)
95
+ end
96
+ end
97
+
98
+ # session = GameEngine.new
99
+ # session.main_menu
data/lib/ip_config.rb ADDED
@@ -0,0 +1,4 @@
1
+ module IP_CONFIG
2
+ P2P_IP = '52.53.215.135:3333'
3
+ TEST_IP = 'localhost:3333'
4
+ end
data/lib/msg.rb ADDED
@@ -0,0 +1,52 @@
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
+ MP_BYE_MSG = "If you want to play again, rerun the script. Goodbye!"
13
+
14
+ P_OR_Q_ERR_MSG = "Sorry, I didn't understand that selection. press 'p' to play; 'q' to quit:"
15
+
16
+ PLAYER_TURN_MSG = "Choose where you want to drop your token"
17
+
18
+ TIE_GAME_MSG = "The board is full!! It's a draw!"
19
+
20
+ WAITING_MSG = "Waiting for opponent to join"
21
+
22
+ OPP_FOUND = "Opponent found...establishing connection"
23
+
24
+ def TIMES_UP_MSG(column)
25
+ "Time's up: you randomly chose column #{column}"
26
+ end
27
+ def WAITING_FOR_PLYR_MSG(plyr_name)
28
+ "Waiting for #{plyr_name} to make a move"
29
+ end
30
+
31
+ def LOADER_MSG(i)
32
+ i.times do
33
+ sleep 1.6
34
+ print " . "
35
+ end
36
+ puts ""
37
+ end
38
+
39
+ def VICTORY_MSG(plyr_name)
40
+ if plyr_name == "HAL"
41
+ "HAL won!! You just lost to an unliving being!! Boom!!"
42
+ else
43
+ "#Hot Dog, we have a Winner! Congratulations #{plyr_name}!!!"
44
+ end
45
+ end
46
+
47
+ def INPUT_ERR_MSG(input)
48
+ "#{input} is an invalid selection"
49
+ end
50
+
51
+ # X_OR_O_MSG = "Pick your piece! X or O??"
52
+ end
@@ -0,0 +1,149 @@
1
+ require_relative 'board'
2
+ require_relative 'player'
3
+ require_relative 'msg'
4
+ require_relative 'cli'
5
+ require_relative 'ip_config'
6
+ require 'thread'
7
+
8
+ class MPGameEngine
9
+ include MSG, IP_CONFIG
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 = self.players.filter { |player| player.name == p1 }.first
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
+ column = column
57
+ end
58
+ else
59
+ puts INPUT_ERR_MSG(column)
60
+ # continue to run until local client enters valid input
61
+ # loops back to until turn_over
62
+ end
63
+ end
64
+ end
65
+ end
66
+
67
+ timer_thread = Thread.new do
68
+ 10.times do |i|
69
+ print "#{10 - i }. . "
70
+ sleep 1.6
71
+ break if mutex.synchronize { input_received }
72
+ end
73
+ # if the player doesn't select a column, random valid selection will occur
74
+ until turn_over
75
+ column = Board::COLUMNS.sample
76
+ if valid_input(column)
77
+ puts TIMES_UP_MSG(column)
78
+ mutex.synchronize do
79
+ input_received = true
80
+ turn_over = true
81
+ column = column
82
+ end
83
+ end
84
+ end
85
+ end
86
+
87
+ [input_thread, timer_thread].each(&:join)
88
+
89
+ token_x, token_y = drop_token(column, plyr.token)
90
+ # send valid column selection to server
91
+ `curl -s "#{P2P_IP}/move?player=#{plyr.name}?column=#{column}"`
92
+ else # remote client || @player2
93
+ until turn_over
94
+ 11.times do
95
+ puts WAITING_FOR_PLYR_MSG(plyr.name)
96
+ response = `curl -s "#{P2P_IP}/status?player=#{@player1.name}"`.chomp
97
+ # require 'pry'; binding.pry
98
+ unless response.include?("patience") # unless this fails => response == <valid letter>
99
+ token_x, token_y = drop_token(response, plyr.token)
100
+ turn_over = true
101
+ break
102
+ end
103
+ sleep 1.6
104
+ end
105
+ end
106
+ end
107
+
108
+ if @board.check_win(token_x, token_y, plyr.token)
109
+ puts @board.display
110
+ puts "#{VICTORY_MSG(plyr.name)}"
111
+ game_over = true
112
+ end
113
+
114
+ if @board.board_full?
115
+ puts @board.display
116
+ puts TIE_GAME_MSG
117
+ game_over = true
118
+ end
119
+ end
120
+ sleep 1.6
121
+ # send /reset command to server to clear out stored variables
122
+ `curl -s "#{P2P_IP}/reset"`
123
+ end
124
+
125
+ def valid_input(column)
126
+ idx = @board.column_to_index(column)
127
+ if Board::COLUMNS.include?(column) && @board.board[0][idx] == '.'
128
+ true
129
+ else
130
+ false
131
+ end
132
+ end
133
+
134
+ # returns player object and increments queue
135
+ def whose_turn
136
+ plyr = @current_player
137
+ if plyr == players[0]
138
+ @current_player = players[1]
139
+ else
140
+ @current_player = players[0]
141
+ end
142
+ plyr
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
data/lib/player.rb ADDED
@@ -0,0 +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
metadata ADDED
@@ -0,0 +1,55 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: connect_cuatro
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Matt Darlington
8
+ - Taylor Pubins
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2023-09-14 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: Digital version of the Classic Connect Four boardgame.
15
+ email:
16
+ - matthewsdarlington@gmail.com
17
+ - tpubz@icloud.com
18
+ executables:
19
+ - connect_cuatro
20
+ extensions: []
21
+ extra_rdoc_files: []
22
+ files:
23
+ - bin/connect_cuatro
24
+ - lib/board.rb
25
+ - lib/cli.rb
26
+ - lib/connect_four.rb
27
+ - lib/game_engine.rb
28
+ - lib/ip_config.rb
29
+ - lib/msg.rb
30
+ - lib/multiplayer_game_engine.rb
31
+ - lib/player.rb
32
+ homepage: http://rubygems.org/gems/connect_cuatro
33
+ licenses:
34
+ - MIT
35
+ metadata: {}
36
+ post_install_message:
37
+ rdoc_options: []
38
+ require_paths:
39
+ - lib
40
+ required_ruby_version: !ruby/object:Gem::Requirement
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ version: '0'
45
+ required_rubygems_version: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: '0'
50
+ requirements: []
51
+ rubygems_version: 3.4.19
52
+ signing_key:
53
+ specification_version: 4
54
+ summary: Connect Four -- Turing School of Software & Design -- Mod1 Project
55
+ test_files: []