connect_cuatro 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.
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: []