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.
@@ -0,0 +1,4 @@
1
+ module GitGameShow
2
+ # Current version of Git Game Show
3
+ VERSION = "0.1.0"
4
+ end
@@ -0,0 +1,49 @@
1
+ require 'git'
2
+ require 'colorize'
3
+ require 'tty-prompt'
4
+ require 'tty-table'
5
+ require 'tty-cursor'
6
+ require 'json'
7
+ require 'eventmachine'
8
+ require 'websocket-eventmachine-server'
9
+ require 'websocket-client-simple'
10
+ require 'thor'
11
+ require 'uri'
12
+ require 'clipboard'
13
+ require 'readline'
14
+ require 'timeout'
15
+
16
+ # Define module and constants first before loading any other files
17
+ module GitGameShow
18
+ # VERSION is defined in version.rb
19
+
20
+ # Default configuration
21
+ DEFAULT_CONFIG = {
22
+ port: 3030,
23
+ rounds: 3,
24
+ question_timeout: 30, # seconds
25
+ question_display_time: 5, # seconds to show results before next question
26
+ transition_delay: 5 # seconds between rounds
27
+ }.freeze
28
+
29
+ # Message types for WebSocket communication
30
+ module MessageType
31
+ JOIN_REQUEST = 'join_request'
32
+ JOIN_RESPONSE = 'join_response'
33
+ GAME_START = 'game_start'
34
+ QUESTION = 'question'
35
+ ANSWER = 'answer'
36
+ ANSWER_FEEDBACK = 'answer_feedback' # New message type for immediate feedback
37
+ ROUND_RESULT = 'round_result'
38
+ SCOREBOARD = 'scoreboard'
39
+ GAME_END = 'game_end'
40
+ GAME_RESET = 'game_reset' # New message type for resetting the game
41
+ CHAT = 'chat'
42
+ end
43
+ end
44
+
45
+ # Load all files in the git_game_show directory
46
+ Dir[File.join(__dir__, 'git_game_show', '*.rb')].sort.each { |file| require file }
47
+
48
+ # Load all mini-games
49
+ Dir[File.join(__dir__, '..', 'mini_games', '*.rb')].sort.each { |file| require file }
@@ -0,0 +1,142 @@
1
+ module GitGameShow
2
+ class AuthorQuiz < MiniGame
3
+ self.name = "Author Quiz"
4
+ self.description = "Guess which team member made each commit!"
5
+ self.questions_per_round = 5
6
+
7
+ # Custom timing for this mini-game (overrides default config)
8
+ def self.question_timeout
9
+ 15 # 15 seconds per question
10
+ end
11
+
12
+ def self.question_display_time
13
+ 5 # 5 seconds between questions
14
+ end
15
+
16
+ def generate_questions(repo)
17
+ # For testing, manually add some questions if we can't get meaningful ones from repo
18
+ questions = []
19
+
20
+ begin
21
+ # Get commits from the past year
22
+ one_year_ago = Time.now - (365 * 24 * 60 * 60) # One year in seconds
23
+
24
+ # Get all commits
25
+ all_commits = get_all_commits(repo)
26
+
27
+ # Filter commits from the past year, if any
28
+ commits_from_past_year = all_commits.select do |commit|
29
+ # Be safe with date parsing
30
+ begin
31
+ commit_time = commit.date.is_a?(Time) ? commit.date : Time.parse(commit.date.to_s)
32
+ commit_time > one_year_ago
33
+ rescue
34
+ false # If we can't parse the date, exclude it
35
+ end
36
+ end
37
+
38
+ # If we don't have enough commits from the past year, use all commits
39
+ commits = commits_from_past_year.size >= 10 ? commits_from_past_year : all_commits
40
+
41
+ authors = get_commit_authors(commits)
42
+
43
+ # We need at least 2 authors for this game to be meaningful
44
+ if authors.size < 2
45
+ return generate_sample_questions
46
+ end
47
+
48
+ # Shuffle all commits to ensure randomness, then select commits for questions
49
+ selected_commits = commits.shuffle.sample(self.class.questions_per_round)
50
+
51
+ selected_commits.each do |commit|
52
+ # Get the real author
53
+ correct_author = commit.author.name
54
+
55
+ # Get incorrect options (other authors)
56
+ incorrect_authors = shuffled_excluding(authors, correct_author).take(3)
57
+
58
+ # Create options array with the correct answer and incorrect ones
59
+ all_options = ([correct_author] + incorrect_authors).shuffle
60
+
61
+ # Extract the commit message, but handle multi-line messages gracefully
62
+ message_lines = commit.message.lines.reject(&:empty?)
63
+ message_preview = message_lines.first&.strip || "No message"
64
+
65
+ # For longer messages, add an indication that there's more
66
+ if message_lines.size > 1
67
+ message_preview += "..."
68
+ end
69
+
70
+ # Create a more compact question format to avoid overflows
71
+ # Add proper indentation to the commit message with spaces
72
+ questions << {
73
+ question: "Who authored this commit?\n\n \"#{message_preview}\"",
74
+ commit_info: "#{commit.sha[0..7]} (#{commit.date.strftime('%b %d, %Y')})",
75
+ options: all_options,
76
+ correct_answer: correct_author
77
+ }
78
+ end
79
+ rescue => e
80
+ # Silently fail and use sample questions instead
81
+ return generate_sample_questions
82
+ end
83
+
84
+ # If we couldn't generate enough questions, add sample ones
85
+ if questions.empty?
86
+ return generate_sample_questions
87
+ end
88
+
89
+ questions
90
+ end
91
+
92
+ def generate_sample_questions
93
+ # Create sample questions in case the repo doesn't have enough data
94
+ sample_authors = ["Alice", "Bob", "Charlie", "David", "Emma"]
95
+
96
+ questions = []
97
+
98
+ 5.times do |i|
99
+ correct_author = sample_authors.sample
100
+ incorrect_authors = sample_authors.reject { |a| a == correct_author }.sample(3)
101
+
102
+ all_options = ([correct_author] + incorrect_authors).shuffle
103
+
104
+ questions << {
105
+ question: "Who authored this commit?\n\n \"Sample commit message ##{i+1}\"",
106
+ commit_info: "abc123#{i} (Jan #{i+1}, 2025)",
107
+ options: all_options,
108
+ correct_answer: correct_author
109
+ }
110
+ end
111
+
112
+ questions
113
+ end
114
+
115
+ def evaluate_answers(question, player_answers)
116
+ results = {}
117
+
118
+ player_answers.each do |player_name, answer_data|
119
+ answered = answer_data[:answered] || false
120
+ player_answer = answer_data[:answer]
121
+ correct = player_answer == question[:correct_answer]
122
+
123
+ points = correct ? 10 : 0
124
+
125
+ # Bonus points for fast answers (if correct)
126
+ if correct && answer_data[:time_taken] < 5
127
+ points += 5
128
+ elsif correct && answer_data[:time_taken] < 10
129
+ points += 3
130
+ end
131
+
132
+ results[player_name] = {
133
+ answer: player_answer,
134
+ correct: correct,
135
+ points: points
136
+ }
137
+ end
138
+
139
+ results
140
+ end
141
+ end
142
+ end
@@ -0,0 +1,205 @@
1
+ module GitGameShow
2
+ class CommitMessageCompletion < MiniGame
3
+ self.name = "Complete the Commit"
4
+ self.description = "Complete the missing part of these commit messages! (20 seconds per question)"
5
+ self.questions_per_round = 5
6
+
7
+ # Custom timing for this mini-game (20 seconds instead of 15)
8
+ def self.question_timeout
9
+ 20 # 20 seconds per question
10
+ end
11
+
12
+ def self.question_display_time
13
+ 5 # 5 seconds between questions
14
+ end
15
+
16
+ def generate_questions(repo)
17
+ begin
18
+ commits = get_all_commits(repo)
19
+
20
+ # Filter commits with message length > 10 characters
21
+ valid_commits = commits.select { |commit| commit.message.strip.length > 10 }
22
+
23
+ # Fall back to sample questions if not enough valid commits
24
+ if valid_commits.size < self.class.questions_per_round
25
+ return generate_sample_questions
26
+ end
27
+ rescue => e
28
+ # If there's any error, fall back to sample questions
29
+ return generate_sample_questions
30
+ end
31
+
32
+ questions = []
33
+ valid_questions = 0
34
+ attempts = 0
35
+ max_attempts = 100 # Prevent infinite loops
36
+
37
+ # Keep trying until we have exactly 5 questions or reach max attempts
38
+ while valid_questions < self.class.questions_per_round && attempts < max_attempts
39
+ attempts += 1
40
+
41
+ # Get a random commit
42
+ commit = valid_commits.sample
43
+ message = commit.message.strip
44
+
45
+ # Split message into beginning and end parts
46
+ words = message.split(/\s+/)
47
+
48
+ # Skip if too few words
49
+ if words.size < 4
50
+ next
51
+ end
52
+
53
+ # Determine how much to hide (1/3 to 1/2 of words)
54
+ hide_count = [words.size / 3, 2].max
55
+ hide_start = rand(0..(words.size - hide_count))
56
+ hidden_words = words[hide_start...(hide_start + hide_count)]
57
+
58
+ # Replace hidden words with blanks
59
+ words[hide_start...(hide_start + hide_count)] = ['________'] * hide_count
60
+
61
+ # Create question and actual answer
62
+ question_text = words.join(' ')
63
+ correct_answer = hidden_words.join(' ')
64
+
65
+ # Generate incorrect options
66
+ other_messages = get_commit_messages(valid_commits) - [message]
67
+ other_messages_parts = []
68
+
69
+ # Get parts of other messages that have similar length
70
+ other_messages.each do |other_msg|
71
+ other_words = other_msg.split(/\s+/)
72
+ if other_words.size >= hide_count
73
+ part_start = rand(0..(other_words.size - hide_count))
74
+ other_messages_parts << other_words[part_start...(part_start + hide_count)].join(' ')
75
+ end
76
+ end
77
+
78
+ # Only proceed if we have enough options
79
+ if other_messages_parts.size < 3
80
+ next
81
+ end
82
+
83
+ other_options = other_messages_parts.sample(3)
84
+
85
+ # Create options array with the correct answer and incorrect ones
86
+ all_options = ([correct_answer] + other_options).shuffle
87
+
88
+ # Format consistently with other mini-games
89
+ questions << {
90
+ question: "Complete this commit message:\n\n \"#{question_text}\"",
91
+ commit_info: "#{commit.sha[0..7]} (#{commit.date.strftime('%b %d, %Y')})",
92
+ options: all_options,
93
+ correct_answer: correct_answer
94
+ }
95
+
96
+ valid_questions += 1
97
+ end
98
+
99
+ # If we couldn't generate enough questions, fall back to sample questions
100
+ if questions.size < self.class.questions_per_round
101
+ return generate_sample_questions
102
+ end
103
+
104
+ questions
105
+ end
106
+
107
+ # Generate sample questions when there aren't enough commits
108
+ def generate_sample_questions
109
+ questions = []
110
+
111
+ # Sample commit messages that are realistic
112
+ sample_messages = [
113
+ {
114
+ full_message: "Add user authentication with OAuth2 support",
115
+ blank_text: "Add user __________ with OAuth2 support",
116
+ missing_part: "authentication",
117
+ wrong_options: ["registration", "profile", "settings"],
118
+ sha: "f8c7b3e",
119
+ date: "Mar 10, 2025"
120
+ },
121
+ {
122
+ full_message: "Fix memory leak in the background worker process",
123
+ blank_text: "Fix memory __________ in the background worker",
124
+ missing_part: "leak",
125
+ wrong_options: ["usage", "allocation", "error"],
126
+ sha: "2d9a45c",
127
+ date: "Feb 25, 2025"
128
+ },
129
+ {
130
+ full_message: "Update dependencies to latest stable versions",
131
+ blank_text: "Update __________ to latest stable versions",
132
+ missing_part: "dependencies",
133
+ wrong_options: ["documentation", "configurations", "references"],
134
+ sha: "7b3e9d1",
135
+ date: "Mar 5, 2025"
136
+ },
137
+ {
138
+ full_message: "Improve error handling in API response layer",
139
+ blank_text: "Improve error __________ in API response layer",
140
+ missing_part: "handling",
141
+ wrong_options: ["messages", "logging", "codes"],
142
+ sha: "c4e91a2",
143
+ date: "Feb 28, 2025"
144
+ },
145
+ {
146
+ full_message: "Add comprehensive test coverage for payment module",
147
+ blank_text: "Add comprehensive __________ coverage for payment module",
148
+ missing_part: "test",
149
+ wrong_options: ["code", "feature", "security"],
150
+ sha: "9f5d7e3",
151
+ date: "Mar 15, 2025"
152
+ }
153
+ ]
154
+
155
+ # Create a question for each sample
156
+ self.class.questions_per_round.times do |i|
157
+ sample = sample_messages[i % sample_messages.size]
158
+
159
+ # Create options array with the correct answer and incorrect ones
160
+ all_options = ([sample[:missing_part]] + sample[:wrong_options]).shuffle
161
+
162
+ # Format consistently with other mini-games
163
+ questions << {
164
+ question: "Complete this commit message:\n\n \"#{sample[:blank_text]}\"",
165
+ commit_info: "#{sample[:sha]} (#{sample[:date]})",
166
+ options: all_options,
167
+ correct_answer: sample[:missing_part]
168
+ }
169
+ end
170
+
171
+ questions
172
+ end
173
+
174
+ def evaluate_answers(question, player_answers)
175
+ results = {}
176
+
177
+ player_answers.each do |player_name, answer_data|
178
+ player_answer = answer_data[:answer]
179
+ correct = player_answer == question[:correct_answer]
180
+
181
+ points = 0
182
+
183
+ if correct
184
+ points = 10 # Base points for correct answer
185
+
186
+ # Bonus points for fast answers, adjusted for 20-second time limit
187
+ time_taken = answer_data[:time_taken] || 20
188
+ if time_taken < 7 # Increased from 5 to 7 seconds
189
+ points += 5
190
+ elsif time_taken < 13 # Increased from 10 to 13 seconds
191
+ points += 3
192
+ end
193
+ end
194
+
195
+ results[player_name] = {
196
+ answer: player_answer,
197
+ correct: correct,
198
+ points: points
199
+ }
200
+ end
201
+
202
+ results
203
+ end
204
+ end
205
+ end