git_game_show 0.1.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 +7 -0
- data/LICENSE +21 -0
- data/README.md +110 -0
- data/bin/git-game-show +5 -0
- data/lib/git_game_show/cli.rb +537 -0
- data/lib/git_game_show/game_server.rb +1224 -0
- data/lib/git_game_show/mini_game.rb +57 -0
- data/lib/git_game_show/player_client.rb +1145 -0
- data/lib/git_game_show/version.rb +4 -0
- data/lib/git_game_show.rb +49 -0
- data/mini_games/author_quiz.rb +142 -0
- data/mini_games/commit_message_completion.rb +205 -0
- data/mini_games/commit_message_quiz.rb +589 -0
- data/mini_games/date_ordering_quiz.rb +230 -0
- metadata +245 -0
@@ -0,0 +1,537 @@
|
|
1
|
+
require 'uri'
|
2
|
+
|
3
|
+
module GitGameShow
|
4
|
+
class CLI < Thor
|
5
|
+
map %w[--version -v] => :version
|
6
|
+
|
7
|
+
desc 'version', 'Display Git Game Show version'
|
8
|
+
def version
|
9
|
+
puts "Git Game Show version #{GitGameShow::VERSION}"
|
10
|
+
end
|
11
|
+
|
12
|
+
desc '', 'Show welcome screen'
|
13
|
+
def welcome
|
14
|
+
display_welcome_screen
|
15
|
+
|
16
|
+
prompt = TTY::Prompt.new
|
17
|
+
choice = prompt.select("What would you like to do?", [
|
18
|
+
{name: "Host a new game", value: :host},
|
19
|
+
{name: "Join a game", value: :join},
|
20
|
+
{name: "Exit", value: :exit}
|
21
|
+
])
|
22
|
+
|
23
|
+
case choice
|
24
|
+
when :host
|
25
|
+
prompt_for_host_options
|
26
|
+
when :join
|
27
|
+
prompt_for_join_options
|
28
|
+
when :exit
|
29
|
+
puts "Thanks for playing Git Game Show!"
|
30
|
+
exit(0)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
desc 'host [OPTIONS]', 'Host a new game session'
|
35
|
+
method_option :port, type: :numeric, default: GitGameShow::DEFAULT_CONFIG[:port],
|
36
|
+
desc: 'Port to run the server on'
|
37
|
+
method_option :password, type: :string,
|
38
|
+
desc: 'Optional password for players to join (auto-generated if not provided)'
|
39
|
+
method_option :rounds, type: :numeric, default: GitGameShow::DEFAULT_CONFIG[:rounds],
|
40
|
+
desc: 'Number of rounds to play'
|
41
|
+
method_option :repo_path, type: :string, default: '.',
|
42
|
+
desc: 'Path to git repository'
|
43
|
+
def host
|
44
|
+
begin
|
45
|
+
# Validate git repository
|
46
|
+
repo = Git.open(options[:repo_path])
|
47
|
+
|
48
|
+
# Generate a random password if not provided
|
49
|
+
password = options[:password] || generate_random_password
|
50
|
+
|
51
|
+
# Start the game server
|
52
|
+
server = GameServer.new(
|
53
|
+
port: options[:port],
|
54
|
+
password: password,
|
55
|
+
rounds: options[:rounds],
|
56
|
+
repo: repo
|
57
|
+
)
|
58
|
+
|
59
|
+
# Get IP addresses before clearing screen
|
60
|
+
# Get the local IP address for players to connect to
|
61
|
+
local_ip = `hostname -I 2>/dev/null || ipconfig getifaddr en0 2>/dev/null`.strip
|
62
|
+
|
63
|
+
# Get external IP address using a public service
|
64
|
+
puts "Detecting your external IP address... (this will only take a second)"
|
65
|
+
begin
|
66
|
+
# Try multiple services in case one is down
|
67
|
+
external_ip = `curl -s --connect-timeout 3 https://api.ipify.org || curl -s --connect-timeout 3 https://ifconfig.me || curl -s --connect-timeout 3 https://icanhazip.com`.strip
|
68
|
+
external_ip = nil if external_ip.empty? || external_ip.length > 45 # Sanity check
|
69
|
+
rescue
|
70
|
+
external_ip = nil
|
71
|
+
end
|
72
|
+
|
73
|
+
# Clear the screen
|
74
|
+
clear_screen
|
75
|
+
|
76
|
+
# Ask user which IP to use
|
77
|
+
prompt = TTY::Prompt.new
|
78
|
+
ip_choices = []
|
79
|
+
ip_choices << {name: "Local network only (#{local_ip})", value: {:type => :local, :ip => local_ip}} if !local_ip.empty?
|
80
|
+
ip_choices << {name: "Internet - External IP (#{external_ip}) - requires port forwarding", value: {:type => :external, :ip => external_ip}} if external_ip
|
81
|
+
ip_choices << {name: "Internet - Automatic tunneling with ngrok (requires free account)", value: {:type => :tunnel}}
|
82
|
+
ip_choices << {name: "Custom IP or hostname", value: {:type => :custom}}
|
83
|
+
|
84
|
+
# Format question with explanation
|
85
|
+
puts "┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓"
|
86
|
+
puts "┃ NETWORK SETUP ┃"
|
87
|
+
puts "┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫"
|
88
|
+
puts "┃ • Local IP: Only for players on the same network ┃"
|
89
|
+
puts "┃ • External IP: For internet players (requires router port forwarding) ┃"
|
90
|
+
puts "┃ • Automatic tunneling: Uses ngrok (requires free account & authorization) ┃"
|
91
|
+
puts "┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛"
|
92
|
+
puts ""
|
93
|
+
|
94
|
+
ip_choice = prompt.select("How should players connect to your game?", ip_choices)
|
95
|
+
|
96
|
+
# Handle different connection options
|
97
|
+
case ip_choice[:type]
|
98
|
+
when :local, :external
|
99
|
+
ip = ip_choice[:ip]
|
100
|
+
when :custom
|
101
|
+
ip = prompt.ask("Enter your IP address or hostname:", required: true)
|
102
|
+
when :tunnel
|
103
|
+
# Clear the screen and show informative message about ngrok
|
104
|
+
clear_screen
|
105
|
+
puts "┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓"
|
106
|
+
puts "┃ NGROK TUNNEL SETUP ┃"
|
107
|
+
puts "┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫"
|
108
|
+
puts "┃ The automatic tunneling option uses ngrok, a secure tunneling service. ┃"
|
109
|
+
puts "┃ This will allow players to connect from anywhere without port forwarding. ┃"
|
110
|
+
puts "┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛"
|
111
|
+
puts ""
|
112
|
+
|
113
|
+
# Check if ngrok is available
|
114
|
+
begin
|
115
|
+
# First, try to find if ngrok command is available
|
116
|
+
ngrok_available = system("which ngrok > /dev/null 2>&1") || system("where ngrok > /dev/null 2>&1")
|
117
|
+
|
118
|
+
unless ngrok_available
|
119
|
+
# Offer to install ngrok automatically
|
120
|
+
prompt = TTY::Prompt.new
|
121
|
+
install_ngrok = prompt.yes?("Ngrok is required for tunneling but wasn't found. Would you like to install it now?")
|
122
|
+
|
123
|
+
if install_ngrok
|
124
|
+
puts "Installing ngrok..."
|
125
|
+
|
126
|
+
# Determine platform and architecture
|
127
|
+
os = RbConfig::CONFIG['host_os']
|
128
|
+
arch = RbConfig::CONFIG['host_cpu']
|
129
|
+
|
130
|
+
# Default to 64-bit
|
131
|
+
arch_suffix = arch =~ /64|amd64/ ? '64' : '32'
|
132
|
+
|
133
|
+
# Determine the download URL based on OS
|
134
|
+
download_url = if os =~ /darwin/i
|
135
|
+
"https://bin.equinox.io/c/bNyj1mQVY4c/ngrok-v3-stable-darwin-#{arch =~ /arm|aarch64/ ? 'arm64' : 'amd64'}.zip"
|
136
|
+
elsif os =~ /linux/i
|
137
|
+
"https://bin.equinox.io/c/bNyj1mQVY4c/ngrok-v3-stable-linux-#{arch =~ /arm|aarch64/ ? 'arm64' : 'amd64'}.zip"
|
138
|
+
elsif os =~ /mswin|mingw|cygwin/i
|
139
|
+
"https://bin.equinox.io/c/bNyj1mQVY4c/ngrok-v3-stable-windows-#{arch_suffix}.zip"
|
140
|
+
else
|
141
|
+
nil
|
142
|
+
end
|
143
|
+
|
144
|
+
if download_url.nil?
|
145
|
+
puts "Could not determine your system type. Please install ngrok manually from https://ngrok.com/download"
|
146
|
+
exit(1)
|
147
|
+
end
|
148
|
+
|
149
|
+
# Determine installation directory in user's home folder
|
150
|
+
user_home = ENV['HOME'] || ENV['USERPROFILE']
|
151
|
+
install_dir = File.join(user_home, '.git_game_show')
|
152
|
+
FileUtils.mkdir_p(install_dir) unless Dir.exist?(install_dir)
|
153
|
+
|
154
|
+
# Download ngrok
|
155
|
+
puts "Downloading ngrok from #{download_url}..."
|
156
|
+
require 'open-uri'
|
157
|
+
require 'tempfile'
|
158
|
+
|
159
|
+
temp_zip = Tempfile.new(['ngrok', '.zip'])
|
160
|
+
temp_zip.binmode
|
161
|
+
|
162
|
+
begin
|
163
|
+
URI.open(download_url) do |remote_file|
|
164
|
+
temp_zip.write(remote_file.read)
|
165
|
+
end
|
166
|
+
temp_zip.close
|
167
|
+
|
168
|
+
# Extract zip
|
169
|
+
require 'zip'
|
170
|
+
puts "Extracting ngrok..."
|
171
|
+
|
172
|
+
Zip::File.open(temp_zip.path) do |zip_file|
|
173
|
+
zip_file.each do |entry|
|
174
|
+
entry_path = File.join(install_dir, entry.name)
|
175
|
+
entry.extract(entry_path) { true } # Overwrite if exists
|
176
|
+
FileUtils.chmod(0755, entry_path) if entry.name == 'ngrok' || entry.name == 'ngrok.exe'
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
# Add to PATH for the current process
|
181
|
+
ENV['PATH'] = "#{install_dir}:#{ENV['PATH']}"
|
182
|
+
|
183
|
+
# Check if installation was successful
|
184
|
+
ngrok_path = File.join(install_dir, os =~ /mswin|mingw|cygwin/i ? 'ngrok.exe' : 'ngrok')
|
185
|
+
if File.exist?(ngrok_path)
|
186
|
+
puts "Ngrok installed successfully to #{install_dir}"
|
187
|
+
ngrok_available = true
|
188
|
+
|
189
|
+
# Add a hint about adding to PATH permanently
|
190
|
+
puts "\nTIP: To use ngrok in other terminal sessions, add this to your shell profile:"
|
191
|
+
puts "export PATH=\"#{install_dir}:$PATH\""
|
192
|
+
puts "\nPress Enter to continue..."
|
193
|
+
gets
|
194
|
+
else
|
195
|
+
puts "Failed to install ngrok. Please install manually from https://ngrok.com/download"
|
196
|
+
exit(1)
|
197
|
+
end
|
198
|
+
rescue => e
|
199
|
+
puts "Error installing ngrok: #{e.message}"
|
200
|
+
puts "Please install manually from https://ngrok.com/download"
|
201
|
+
exit(1)
|
202
|
+
ensure
|
203
|
+
temp_zip.unlink
|
204
|
+
end
|
205
|
+
else
|
206
|
+
# User opted not to install
|
207
|
+
puts "Ngrok installation declined. Please choose a different connection option."
|
208
|
+
exit(1)
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
puts "Starting tunnel service... (this may take a few moments)"
|
213
|
+
|
214
|
+
# Start ngrok in non-blocking mode for the specified port
|
215
|
+
require 'open3'
|
216
|
+
require 'json'
|
217
|
+
|
218
|
+
# Kill any existing ngrok processes
|
219
|
+
system("pkill -f ngrok > /dev/null 2>&1 || taskkill /F /IM ngrok.exe > /dev/null 2>&1")
|
220
|
+
|
221
|
+
# Check for ngrok api availability first (might be a previous instance running)
|
222
|
+
puts "Checking for existing ngrok sessions..."
|
223
|
+
api_available = system("curl -s http://localhost:4040/api/tunnels > /dev/null 2>&1")
|
224
|
+
|
225
|
+
if api_available
|
226
|
+
puts "Found existing ngrok session. Attempting to use it or restart if needed..."
|
227
|
+
# Try to kill it to start fresh
|
228
|
+
system("pkill -f ngrok > /dev/null 2>&1 || taskkill /F /IM ngrok.exe > /dev/null 2>&1")
|
229
|
+
# Give it a moment to shut down
|
230
|
+
sleep(1)
|
231
|
+
end
|
232
|
+
|
233
|
+
# Check for ngrok auth status
|
234
|
+
puts "Checking ngrok authentication status..."
|
235
|
+
|
236
|
+
# Check if the user has authenticated with ngrok
|
237
|
+
auth_check = `ngrok config check 2>&1`
|
238
|
+
auth_needed = auth_check.include?("auth") || auth_check.include?("authtoken") || auth_check.include?("ERR") || auth_check.include?("error")
|
239
|
+
|
240
|
+
if auth_needed
|
241
|
+
clear_screen
|
242
|
+
puts "┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓"
|
243
|
+
puts "┃ NGROK AUTHORIZATION REQUIRED ┃"
|
244
|
+
puts "┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫"
|
245
|
+
puts "┃ Starting with ngrok v3, you need to create a free account and authorize ┃"
|
246
|
+
puts "┃ to use TCP tunnels. This is a one-time setup. ┃"
|
247
|
+
puts "┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛"
|
248
|
+
puts ""
|
249
|
+
puts "Steps to authorize ngrok:"
|
250
|
+
puts " 1. Create a free account at https://ngrok.com/signup"
|
251
|
+
puts " 2. Get your authtoken from https://dashboard.ngrok.com/get-started/your-authtoken"
|
252
|
+
puts " 3. Enter your authtoken below"
|
253
|
+
puts ""
|
254
|
+
|
255
|
+
prompt = TTY::Prompt.new
|
256
|
+
token = prompt.mask("Enter your ngrok authtoken:")
|
257
|
+
|
258
|
+
if token && !token.empty?
|
259
|
+
puts "Setting up ngrok authentication..."
|
260
|
+
auth_result = system("ngrok config add-authtoken #{token}")
|
261
|
+
|
262
|
+
if !auth_result
|
263
|
+
puts "Failed to set ngrok authtoken. Please try again manually with:"
|
264
|
+
puts " ngrok config add-authtoken YOUR_TOKEN"
|
265
|
+
puts ""
|
266
|
+
puts "Press Enter to continue with local IP instead..."
|
267
|
+
gets
|
268
|
+
ip = local_ip
|
269
|
+
return
|
270
|
+
else
|
271
|
+
puts "Successfully authenticated with ngrok!"
|
272
|
+
end
|
273
|
+
else
|
274
|
+
puts "No token provided. Falling back to local IP."
|
275
|
+
puts "Press Enter to continue..."
|
276
|
+
gets
|
277
|
+
ip = local_ip
|
278
|
+
return
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
# Start ngrok with enhanced options
|
283
|
+
puts "Starting ngrok tunnel for port #{options[:port]}..."
|
284
|
+
stdin, stdout, stderr, wait_thr = Open3.popen3("ngrok tcp #{options[:port]} --log=stdout")
|
285
|
+
|
286
|
+
# Capture the stderr from ngrok to check for common errors
|
287
|
+
err_thread = Thread.new do
|
288
|
+
while (error_line = stderr.gets)
|
289
|
+
if error_line.include?("ERR") || error_line.include?("error")
|
290
|
+
if error_line.include?("auth") || error_line.include?("authtoken")
|
291
|
+
puts "\nAuthentication error detected: #{error_line.strip}"
|
292
|
+
elsif error_line.include?("connection")
|
293
|
+
puts "\nConnection error detected: #{error_line.strip}"
|
294
|
+
elsif error_line.include?("bind") || error_line.include?("address already in use")
|
295
|
+
puts "\nPort binding error detected: #{error_line.strip}"
|
296
|
+
else
|
297
|
+
puts "\nngrok error: #{error_line.strip}"
|
298
|
+
end
|
299
|
+
end
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
303
|
+
# Wait for ngrok to start and get the URL
|
304
|
+
puts "Waiting for tunnel to be established (this may take up to 30 seconds)..."
|
305
|
+
tunnel_url = nil
|
306
|
+
30.times do |attempt|
|
307
|
+
# Visual feedback for long wait times
|
308
|
+
print "." if attempt > 0 && attempt % 5 == 0
|
309
|
+
|
310
|
+
# Check if we can query the ngrok API
|
311
|
+
status = Open3.capture2("curl -s http://localhost:4040/api/tunnels")
|
312
|
+
if status[1].success? # If API is available
|
313
|
+
tunnels = JSON.parse(status[0])['tunnels']
|
314
|
+
if tunnels && !tunnels.empty?
|
315
|
+
tunnel = tunnels.first
|
316
|
+
# Get the tunnel URL - it should be a tcp URL with format tcp://x.x.x.x:port
|
317
|
+
public_url = tunnel['public_url']
|
318
|
+
if public_url && public_url.start_with?('tcp://')
|
319
|
+
# Extract the host and port
|
320
|
+
public_url = public_url.sub('tcp://', '')
|
321
|
+
host, port = public_url.split(':')
|
322
|
+
|
323
|
+
# Use the host with the ngrok-assigned port
|
324
|
+
ip = host
|
325
|
+
# Create a new port variable instead of modifying the frozen options hash
|
326
|
+
ngrok_port = port.to_i
|
327
|
+
# Log the port change
|
328
|
+
puts "Ngrok assigned port: #{ngrok_port} (original port: #{options[:port]})"
|
329
|
+
|
330
|
+
tunnel_url = public_url
|
331
|
+
|
332
|
+
# Save the process ID for later cleanup
|
333
|
+
at_exit do
|
334
|
+
system("pkill -f ngrok > /dev/null 2>&1 || taskkill /F /IM ngrok.exe > /dev/null 2>&1")
|
335
|
+
end
|
336
|
+
|
337
|
+
clear_screen
|
338
|
+
puts "┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓"
|
339
|
+
puts "┃ TUNNEL ESTABLISHED SUCCESSFULLY! ┃"
|
340
|
+
puts "┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫"
|
341
|
+
puts "┃ • Your game is now accessible over the internet ┃"
|
342
|
+
puts "┃ • The ngrok tunnel is running in the background ┃"
|
343
|
+
puts "┃ • DO NOT close the terminal window until your game is finished ┃"
|
344
|
+
puts "┃ • The tunnel will automatically close when you exit the game ┃"
|
345
|
+
puts "┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛"
|
346
|
+
puts ""
|
347
|
+
puts "Public URL: #{public_url}"
|
348
|
+
puts ""
|
349
|
+
puts "Press Enter to continue..."
|
350
|
+
gets
|
351
|
+
|
352
|
+
break
|
353
|
+
end
|
354
|
+
end
|
355
|
+
end
|
356
|
+
sleep 1
|
357
|
+
end
|
358
|
+
|
359
|
+
unless tunnel_url
|
360
|
+
clear_screen
|
361
|
+
puts "┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓"
|
362
|
+
puts "┃ TUNNEL SETUP FAILED ┃"
|
363
|
+
puts "┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫"
|
364
|
+
puts "┃ • ngrok tunnel could not be established ┃"
|
365
|
+
puts "┃ • Most common reason: Missing or invalid ngrok authentication token ┃"
|
366
|
+
puts "┃ • Falling back to local IP (players will only be able to join locally) ┃"
|
367
|
+
puts "┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛"
|
368
|
+
puts ""
|
369
|
+
puts "Common solutions:"
|
370
|
+
puts " 1. Create a free account at https://ngrok.com/signup"
|
371
|
+
puts " 2. Get your authtoken from https://dashboard.ngrok.com/get-started/your-authtoken"
|
372
|
+
puts " 3. Run this command: ngrok config add-authtoken <YOUR_TOKEN>"
|
373
|
+
puts " 4. Then restart the game and try tunneling again"
|
374
|
+
puts ""
|
375
|
+
puts "Press Enter to continue with local IP..."
|
376
|
+
gets
|
377
|
+
|
378
|
+
ip = local_ip
|
379
|
+
end
|
380
|
+
rescue => e
|
381
|
+
clear_screen
|
382
|
+
puts "┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓"
|
383
|
+
puts "┃ ERROR SETTING UP NGROK TUNNEL ┃"
|
384
|
+
puts "┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫"
|
385
|
+
puts "┃ • An error occurred while trying to set up the ngrok tunnel ┃"
|
386
|
+
puts "┃ • This is likely an authentication issue with ngrok ┃"
|
387
|
+
puts "┃ • Falling back to local IP (players will only be able to join locally) ┃"
|
388
|
+
puts "┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛"
|
389
|
+
puts ""
|
390
|
+
puts "Error details: #{e.message}"
|
391
|
+
puts ""
|
392
|
+
puts "Press Enter to continue with local IP..."
|
393
|
+
gets
|
394
|
+
|
395
|
+
ip = local_ip
|
396
|
+
end
|
397
|
+
end
|
398
|
+
|
399
|
+
# Generate a secure join link with embedded password
|
400
|
+
# If we have a ngrok tunnel, use the ngrok port, otherwise use the original port
|
401
|
+
port_to_use = defined?(ngrok_port) ? ngrok_port : options[:port]
|
402
|
+
secure_link = "gitgame://#{ip}:#{port_to_use}/#{URI.encode_www_form_component(password)}"
|
403
|
+
|
404
|
+
# Start the server with the improved UI and pass the join link
|
405
|
+
server.start_with_ui(secure_link)
|
406
|
+
|
407
|
+
rescue Git::GitExecuteError
|
408
|
+
puts "Error: Not a valid Git repository at #{options[:repo_path]}".colorize(:red)
|
409
|
+
rescue => e
|
410
|
+
puts "Error: #{e.message}".colorize(:red)
|
411
|
+
end
|
412
|
+
end
|
413
|
+
|
414
|
+
desc 'join SECURE_LINK', 'Join an existing game session using a secure link'
|
415
|
+
method_option :name, type: :string, desc: 'Your player name'
|
416
|
+
def join(secure_link)
|
417
|
+
begin
|
418
|
+
# Check if we need to prompt for a name
|
419
|
+
name = options[:name]
|
420
|
+
|
421
|
+
# Parse the secure link
|
422
|
+
if secure_link.start_with?('gitgame://')
|
423
|
+
uri = URI.parse(secure_link.sub('gitgame://', 'http://'))
|
424
|
+
host = uri.host
|
425
|
+
port = uri.port || GitGameShow::DEFAULT_CONFIG[:port]
|
426
|
+
password = URI.decode_www_form_component(uri.path.sub('/', ''))
|
427
|
+
else
|
428
|
+
# Legacy format - assume it's host:port
|
429
|
+
host, port = secure_link.split(':')
|
430
|
+
port ||= GitGameShow::DEFAULT_CONFIG[:port]
|
431
|
+
password = options[:password]
|
432
|
+
|
433
|
+
# If no password provided in legacy format, ask for it
|
434
|
+
unless password
|
435
|
+
prompt = TTY::Prompt.new
|
436
|
+
password = prompt.mask("Enter the game password:")
|
437
|
+
end
|
438
|
+
end
|
439
|
+
|
440
|
+
# If no name provided, ask for it
|
441
|
+
unless name
|
442
|
+
prompt = TTY::Prompt.new
|
443
|
+
name = prompt.ask("Enter your name:") do |q|
|
444
|
+
q.required true
|
445
|
+
end
|
446
|
+
end
|
447
|
+
|
448
|
+
# Create player client
|
449
|
+
client = PlayerClient.new(
|
450
|
+
host: host,
|
451
|
+
port: port.to_i,
|
452
|
+
password: password,
|
453
|
+
name: name
|
454
|
+
)
|
455
|
+
|
456
|
+
puts "=== Git Game Show Client ===".colorize(:green)
|
457
|
+
puts "Connecting to game at #{host}:#{port}".colorize(:light_blue)
|
458
|
+
|
459
|
+
# Connect to the game
|
460
|
+
client.connect
|
461
|
+
|
462
|
+
rescue => e
|
463
|
+
puts "Error: #{e.message}".colorize(:red)
|
464
|
+
end
|
465
|
+
end
|
466
|
+
|
467
|
+
default_task :welcome
|
468
|
+
|
469
|
+
private
|
470
|
+
|
471
|
+
def display_welcome_screen
|
472
|
+
clear_screen
|
473
|
+
|
474
|
+
puts " ██████╗ ██╗████████╗".colorize(:red) + " ██████╗ █████╗ ███╗ ███╗███████╗".colorize(:green)
|
475
|
+
puts "██╔════╝ ██║╚══██╔══╝".colorize(:red) + " ██╔════╝ ██╔══██╗████╗ ████║██╔════╝".colorize(:green)
|
476
|
+
puts "██║ ███╗██║ ██║ ".colorize(:red) + " ██║ ███╗███████║██╔████╔██║█████╗ ".colorize(:green)
|
477
|
+
puts "██║ ██║██║ ██║ ".colorize(:red) + " ██║ ██║██╔══██║██║╚██╔╝██║██╔══╝ ".colorize(:green)
|
478
|
+
puts "╚██████╔╝██║ ██║ ".colorize(:red) + " ╚██████╔╝██║ ██║██║ ╚═╝ ██║███████╗".colorize(:green)
|
479
|
+
puts " ╚═════╝ ╚═╝ ╚═╝ ".colorize(:red) + " ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝".colorize(:green)
|
480
|
+
|
481
|
+
puts " █████╗ ██╗ ██╗ ██████╗ ██╗ ██╗".colorize(:blue)
|
482
|
+
puts "██╔═══╝ ██║ ██║██╔═══██╗██║ ██║".colorize(:blue)
|
483
|
+
puts "███████╗███████║██║ ██║██║ █╗ ██║".colorize(:blue)
|
484
|
+
puts "╚════██║██╔══██║██║ ██║██║███╗██║".colorize(:blue)
|
485
|
+
puts "██████╔╝██║ ██║╚██████╔╝╚███╔███╔╝".colorize(:blue)
|
486
|
+
puts "╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚══╝╚══╝ ".colorize(:blue)
|
487
|
+
|
488
|
+
puts "\nWelcome to Git Game Show version #{GitGameShow::VERSION}!".colorize(:light_blue)
|
489
|
+
puts "Test your team's Git knowledge with fun trivia games.\n\n"
|
490
|
+
end
|
491
|
+
|
492
|
+
def prompt_for_host_options
|
493
|
+
prompt = TTY::Prompt.new
|
494
|
+
|
495
|
+
repo_path = prompt.ask("Enter the path to the Git repository (leave empty for current directory):", default: '.')
|
496
|
+
rounds = prompt.ask("How many rounds would you like to play? (1-10)",
|
497
|
+
convert: :int,
|
498
|
+
default: GitGameShow::DEFAULT_CONFIG[:rounds]) do |q|
|
499
|
+
q.validate(/^([1-9]|10)$/, "Please enter a number between 1 and 10")
|
500
|
+
end
|
501
|
+
port = prompt.ask("Which port would you like to use?",
|
502
|
+
convert: :int,
|
503
|
+
default: GitGameShow::DEFAULT_CONFIG[:port])
|
504
|
+
|
505
|
+
# Call the host method with the provided options (password will be auto-generated)
|
506
|
+
invoke :host, [], {
|
507
|
+
repo_path: repo_path,
|
508
|
+
rounds: rounds,
|
509
|
+
port: port
|
510
|
+
}
|
511
|
+
end
|
512
|
+
|
513
|
+
def prompt_for_join_options
|
514
|
+
prompt = TTY::Prompt.new
|
515
|
+
|
516
|
+
secure_link = prompt.ask("Paste the join link provided by the host:")
|
517
|
+
name = prompt.ask("Enter your name:")
|
518
|
+
|
519
|
+
# Call the join method with the provided options
|
520
|
+
invoke :join, [secure_link], {
|
521
|
+
name: name
|
522
|
+
}
|
523
|
+
end
|
524
|
+
|
525
|
+
def clear_screen
|
526
|
+
system('clear') || system('cls')
|
527
|
+
end
|
528
|
+
|
529
|
+
def generate_random_password
|
530
|
+
# Generate a simple but memorable password: adjective-noun-number
|
531
|
+
adjectives = %w[happy quick brave clever funny orange purple blue green golden shiny lucky awesome mighty rapid swift]
|
532
|
+
nouns = %w[dog cat fox tiger panda bear whale shark lion wolf dragon eagle falcon rocket ship star planet moon river mountain]
|
533
|
+
|
534
|
+
"#{adjectives.sample}-#{nouns.sample}-#{rand(100..999)}"
|
535
|
+
end
|
536
|
+
end
|
537
|
+
end
|