git_game_show 0.1.9 → 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.
@@ -2,24 +2,37 @@ module GitGameShow
2
2
  class CommitMessageCompletion < MiniGame
3
3
  self.name = "Complete the Commit"
4
4
  self.description = "Complete the missing part of these commit messages! (20 seconds per question)"
5
+ self.example = <<~EXAMPLE
6
+ Complete this commit message:
7
+
8
+ "Fix memory __________ in the background worker"
9
+
10
+ 2d9a45c (Feb 25, 2025)
11
+
12
+ Choose your answer:
13
+ usage
14
+ allocation
15
+ \e[0;32;49m> leak\e[0m
16
+ error
17
+ EXAMPLE
5
18
  self.questions_per_round = 5
6
-
19
+
7
20
  # Custom timing for this mini-game (20 seconds instead of 15)
8
21
  def self.question_timeout
9
22
  20 # 20 seconds per question
10
23
  end
11
-
24
+
12
25
  def self.question_display_time
13
26
  5 # 5 seconds between questions
14
27
  end
15
-
28
+
16
29
  def generate_questions(repo)
17
30
  begin
18
31
  commits = get_all_commits(repo)
19
-
32
+
20
33
  # Filter commits with message length > 10 characters
21
34
  valid_commits = commits.select { |commit| commit.message.strip.length > 10 }
22
-
35
+
23
36
  # Fall back to sample questions if not enough valid commits
24
37
  if valid_commits.size < self.class.questions_per_round
25
38
  return generate_sample_questions
@@ -28,44 +41,44 @@ module GitGameShow
28
41
  # If there's any error, fall back to sample questions
29
42
  return generate_sample_questions
30
43
  end
31
-
44
+
32
45
  questions = []
33
46
  valid_questions = 0
34
47
  attempts = 0
35
48
  max_attempts = 100 # Prevent infinite loops
36
-
49
+
37
50
  # Keep trying until we have exactly 5 questions or reach max attempts
38
51
  while valid_questions < self.class.questions_per_round && attempts < max_attempts
39
52
  attempts += 1
40
-
53
+
41
54
  # Get a random commit
42
55
  commit = valid_commits.sample
43
56
  message = commit.message.strip
44
-
57
+
45
58
  # Split message into beginning and end parts
46
59
  words = message.split(/\s+/)
47
-
60
+
48
61
  # Skip if too few words
49
62
  if words.size < 4
50
63
  next
51
64
  end
52
-
65
+
53
66
  # Determine how much to hide (1/3 to 1/2 of words)
54
67
  hide_count = [words.size / 3, 2].max
55
68
  hide_start = rand(0..(words.size - hide_count))
56
69
  hidden_words = words[hide_start...(hide_start + hide_count)]
57
-
70
+
58
71
  # Replace hidden words with blanks
59
72
  words[hide_start...(hide_start + hide_count)] = ['________'] * hide_count
60
-
73
+
61
74
  # Create question and actual answer
62
75
  question_text = words.join(' ')
63
76
  correct_answer = hidden_words.join(' ')
64
-
77
+
65
78
  # Generate incorrect options
66
79
  other_messages = get_commit_messages(valid_commits) - [message]
67
80
  other_messages_parts = []
68
-
81
+
69
82
  # Get parts of other messages that have similar length
70
83
  other_messages.each do |other_msg|
71
84
  other_words = other_msg.split(/\s+/)
@@ -74,17 +87,17 @@ module GitGameShow
74
87
  other_messages_parts << other_words[part_start...(part_start + hide_count)].join(' ')
75
88
  end
76
89
  end
77
-
90
+
78
91
  # Only proceed if we have enough options
79
92
  if other_messages_parts.size < 3
80
93
  next
81
94
  end
82
-
95
+
83
96
  other_options = other_messages_parts.sample(3)
84
-
97
+
85
98
  # Create options array with the correct answer and incorrect ones
86
99
  all_options = ([correct_answer] + other_options).shuffle
87
-
100
+
88
101
  # Format consistently with other mini-games
89
102
  questions << {
90
103
  question: "Complete this commit message:\n\n \"#{question_text}\"",
@@ -92,22 +105,22 @@ module GitGameShow
92
105
  options: all_options,
93
106
  correct_answer: correct_answer
94
107
  }
95
-
108
+
96
109
  valid_questions += 1
97
110
  end
98
-
111
+
99
112
  # If we couldn't generate enough questions, fall back to sample questions
100
113
  if questions.size < self.class.questions_per_round
101
114
  return generate_sample_questions
102
115
  end
103
-
116
+
104
117
  questions
105
118
  end
106
-
119
+
107
120
  # Generate sample questions when there aren't enough commits
108
121
  def generate_sample_questions
109
122
  questions = []
110
-
123
+
111
124
  # Sample commit messages that are realistic
112
125
  sample_messages = [
113
126
  {
@@ -151,14 +164,14 @@ module GitGameShow
151
164
  date: "Mar 15, 2025"
152
165
  }
153
166
  ]
154
-
167
+
155
168
  # Create a question for each sample
156
169
  self.class.questions_per_round.times do |i|
157
170
  sample = sample_messages[i % sample_messages.size]
158
-
171
+
159
172
  # Create options array with the correct answer and incorrect ones
160
173
  all_options = ([sample[:missing_part]] + sample[:wrong_options]).shuffle
161
-
174
+
162
175
  # Format consistently with other mini-games
163
176
  questions << {
164
177
  question: "Complete this commit message:\n\n \"#{sample[:blank_text]}\"",
@@ -167,22 +180,22 @@ module GitGameShow
167
180
  correct_answer: sample[:missing_part]
168
181
  }
169
182
  end
170
-
183
+
171
184
  questions
172
185
  end
173
-
186
+
174
187
  def evaluate_answers(question, player_answers)
175
188
  results = {}
176
-
189
+
177
190
  player_answers.each do |player_name, answer_data|
178
191
  player_answer = answer_data[:answer]
179
192
  correct = player_answer == question[:correct_answer]
180
-
193
+
181
194
  points = 0
182
-
195
+
183
196
  if correct
184
197
  points = 10 # Base points for correct answer
185
-
198
+
186
199
  # Bonus points for fast answers, adjusted for 20-second time limit
187
200
  time_taken = answer_data[:time_taken] || 20
188
201
  if time_taken < 7 # Increased from 5 to 7 seconds
@@ -191,15 +204,15 @@ module GitGameShow
191
204
  points += 3
192
205
  end
193
206
  end
194
-
207
+
195
208
  results[player_name] = {
196
209
  answer: player_answer,
197
210
  correct: correct,
198
211
  points: points
199
212
  }
200
213
  end
201
-
214
+
202
215
  results
203
216
  end
204
217
  end
205
- end
218
+ end
@@ -2,23 +2,38 @@ module GitGameShow
2
2
  class DateOrderingQuiz < MiniGame
3
3
  self.name = "Commit Timeline"
4
4
  self.description = "Put these commits in chronological order! (20 seconds per question)"
5
+ self.example = <<~EXAMPLE
6
+ Put these commits in chronological order (oldest to newest):
7
+
8
+ Current order:
9
+ Add payment gateway integration (m2n3o4p)
10
+ Fix login page styling issues (e4f5g6h)
11
+ Update README with API documentation (q5r6s7t)
12
+ Implement password reset functionality (i7j8k9l)
13
+
14
+ Drag items to reorder:
15
+ \e[0;32;49m1. Create README.md (q5r6s7t)\e[0m
16
+ \e[0;32;49m2. Fix login page styling issues (e4f5g6h)\e[0m
17
+ \e[0;32;49m3. Implement password reset functionality (i7j8k9l)\e[0m
18
+ \e[0;32;49m4. Add payment gateway integration (m2n3o4p)\e[0m
19
+ EXAMPLE
5
20
  self.questions_per_round = 5
6
-
21
+
7
22
  # Custom timing for this mini-game
8
23
  def self.question_timeout
9
24
  20 # 20 seconds per question
10
25
  end
11
-
26
+
12
27
  def self.question_display_time
13
28
  5 # 5 seconds between questions
14
29
  end
15
-
30
+
16
31
  def generate_questions(repo)
17
32
  begin
18
33
  # Get commits with valid dates
19
34
  all_commits = get_all_commits(repo)
20
35
  commits = all_commits.select { |commit| commit.date.is_a?(Time) || commit.date.respond_to?(:to_time) }
21
-
36
+
22
37
  # If we don't have enough commits for the game, generate sample questions
23
38
  if commits.size < 10
24
39
  return generate_sample_questions
@@ -27,9 +42,9 @@ module GitGameShow
27
42
  # If any errors occur, fall back to sample questions
28
43
  return generate_sample_questions
29
44
  end
30
-
45
+
31
46
  questions = []
32
-
47
+
33
48
  # Generate questions
34
49
  self.class.questions_per_round.times do
35
50
  begin
@@ -37,30 +52,30 @@ module GitGameShow
37
52
  # to avoid ties in the ordering
38
53
  pool = commits.dup
39
54
  selected_commits = []
40
-
55
+
41
56
  # Select 4 commits with different dates
42
57
  while selected_commits.size < 4 && !pool.empty?
43
58
  commit = pool.sample
44
59
  pool.delete(commit)
45
-
60
+
46
61
  # Check if date is unique among selected commits
47
62
  date_duplicate = selected_commits.any? do |selected|
48
63
  # Convert dates to Time objects for comparison
49
64
  commit_time = commit.date.is_a?(Time) ? commit.date : commit.date.to_time
50
65
  selected_time = selected.date.is_a?(Time) ? selected.date : selected.date.to_time
51
-
66
+
52
67
  # Check if times are within 1 minute of each other (avoid duplicates)
53
68
  (commit_time - selected_time).abs < 60
54
69
  end
55
-
70
+
56
71
  selected_commits << commit unless date_duplicate
57
72
  end
58
-
73
+
59
74
  # If we couldn't get 4 commits with different dates, try again with the full pool
60
75
  if selected_commits.size < 4
61
76
  selected_commits = commits.sample(4)
62
77
  end
63
-
78
+
64
79
  # Create the options using commit message and short SHA
65
80
  options = selected_commits.map do |commit|
66
81
  # Get first line of commit message, handle multi-line messages
@@ -69,23 +84,23 @@ module GitGameShow
69
84
  message = message.length > 40 ? "#{message[0...37]}..." : message
70
85
  "#{message} (#{commit.sha[0..6]})"
71
86
  end
72
-
87
+
73
88
  # Shuffle options for initial display
74
89
  shuffled_options = options.shuffle
75
-
90
+
76
91
  # Determine correct order by date
77
92
  correct_order = selected_commits.sort_by(&:date).map do |commit|
78
93
  message = commit.message.lines.first&.strip || "No message"
79
94
  message = message.length > 40 ? "#{message[0...37]}..." : message
80
95
  "#{message} (#{commit.sha[0..6]})"
81
96
  end
82
-
97
+
83
98
  # Get commit dates for displaying in results
84
99
  commit_dates = selected_commits.map do |commit|
85
100
  date_string = commit.date.strftime('%Y-%m-%d %H:%M:%S')
86
101
  "#{commit.sha[0..6]}: #{date_string}"
87
102
  end
88
-
103
+
89
104
  # Store the question data
90
105
  questions << {
91
106
  question: "Put these commits in chronological order (oldest to newest):",
@@ -99,20 +114,20 @@ module GitGameShow
99
114
  next
100
115
  end
101
116
  end
102
-
117
+
103
118
  # If we couldn't generate enough questions, fill with sample questions
104
119
  if questions.size < self.class.questions_per_round
105
120
  sample_questions = generate_sample_questions
106
121
  questions += sample_questions[0...(self.class.questions_per_round - questions.size)]
107
122
  end
108
-
123
+
109
124
  questions
110
125
  end
111
-
126
+
112
127
  # Generate sample questions when we don't have enough commits
113
128
  def generate_sample_questions
114
129
  questions = []
115
-
130
+
116
131
  # Sample data sets with commit messages and dates
117
132
  sample_data = [
118
133
  [
@@ -146,57 +161,58 @@ module GitGameShow
146
161
  { message: "Fix responsive design issues", sha: "y1z2a3b", date: "2023-05-25 16:45:00" }
147
162
  ]
148
163
  ]
149
-
164
+
150
165
  # Generate one question from each sample set
151
166
  self.class.questions_per_round.times do |i|
152
167
  # Use modulo to cycle through samples if we have fewer samples than questions
153
168
  sample_set = sample_data[i % sample_data.size]
154
-
169
+
155
170
  # Format the options
156
171
  options = sample_set.map { |item| "#{item[:message]} (#{item[:sha]})" }
157
-
172
+
158
173
  # Determine correct order (they're already in order in our samples)
159
174
  correct_order = options.dup
160
-
175
+
161
176
  # Shuffle options for initial display
162
177
  shuffled_options = options.shuffle
163
-
178
+
164
179
  # Format dates for feedback
165
180
  date_strings = sample_set.map { |item| "#{item[:sha]}: #{item[:date]}" }
166
-
181
+
167
182
  questions << {
168
183
  question: "Put these commits in chronological order (oldest to newest):",
169
184
  options: shuffled_options,
170
- correct_answer: correct_order,
185
+ correct_answer: correct_order,
171
186
  question_type: 'ordering',
172
187
  commit_dates: date_strings.join("\n")
173
188
  }
174
189
  end
175
-
190
+
176
191
  questions
177
192
  end
178
-
193
+
194
+
179
195
  def evaluate_answers(question, player_answers)
180
196
  results = {}
181
-
197
+
182
198
  # Safety check for nil or empty responses
183
199
  return results if player_answers.nil? || player_answers.empty?
184
200
  return results unless question && question[:correct_answer]
185
-
201
+
186
202
  # Get total number of items in the correct answer
187
203
  total_items = question[:correct_answer].size
188
-
204
+
189
205
  player_answers.each do |player_name, answer_data|
190
206
  # Skip nil entries
191
207
  next unless player_name && answer_data
192
-
208
+
193
209
  # Extract player's answer with defensive checks
194
210
  player_answer = answer_data[:answer]
195
211
  time_taken = answer_data[:time_taken] || 20
196
-
212
+
197
213
  # Initialize points
198
214
  points = 0
199
-
215
+
200
216
  # New scoring system: checks positions relative to each other item
201
217
  if player_answer && !player_answer.empty?
202
218
  # Create a mapping from item to its position in player's answer
@@ -204,26 +220,26 @@ module GitGameShow
204
220
  player_answer.each_with_index do |item, index|
205
221
  item_positions[item] = index if item # Skip nil items
206
222
  end
207
-
223
+
208
224
  # Create mapping from item to correct position
209
225
  correct_positions = {}
210
226
  question[:correct_answer].each_with_index do |item, index|
211
227
  correct_positions[item] = index if item # Skip nil items
212
228
  end
213
-
229
+
214
230
  # For each item, calculate points based on relative positions
215
231
  question[:correct_answer].each_with_index do |item, correct_index|
216
232
  # Skip if the item isn't in the player's answer
217
233
  next unless item && item_positions.key?(item)
218
-
234
+
219
235
  player_index = item_positions[item]
220
-
236
+
221
237
  # Check position relative to other items
222
238
  question[:correct_answer].each_with_index do |other_item, other_correct_index|
223
239
  next if !other_item || item == other_item || !item_positions.key?(other_item)
224
-
240
+
225
241
  other_player_index = item_positions[other_item]
226
-
242
+
227
243
  # If this item should be before the other item
228
244
  if correct_index < other_correct_index
229
245
  points += 1 if player_index < other_player_index
@@ -234,12 +250,12 @@ module GitGameShow
234
250
  end
235
251
  end
236
252
  end
237
-
253
+
238
254
  # Bonus points for perfect answer
239
255
  perfect_score = total_items * (total_items - 1)
240
256
  perfect_score = 1 if perfect_score <= 0 # Prevent division by zero
241
257
  fully_correct = points == perfect_score
242
-
258
+
243
259
  if fully_correct
244
260
  # Additional time-based bonus (faster answers get more points)
245
261
  if time_taken < 8 # Really fast (under 8 seconds)
@@ -248,7 +264,7 @@ module GitGameShow
248
264
  points += 2
249
265
  end
250
266
  end
251
-
267
+
252
268
  # Create detailed feedback
253
269
  max_possible = total_items * (total_items - 1)
254
270
  max_possible = 1 if max_possible <= 0 # Prevent division by zero
@@ -260,7 +276,7 @@ module GitGameShow
260
276
  feedback += "\n • #{date_line}"
261
277
  end
262
278
  end
263
-
279
+
264
280
  # Ensure we return all required fields
265
281
  results[player_name] = {
266
282
  answer: player_answer || [],
@@ -269,7 +285,7 @@ module GitGameShow
269
285
  partial_score: feedback || ""
270
286
  }
271
287
  end
272
-
288
+
273
289
  # Return a default result if somehow we ended up with empty results
274
290
  if results.empty? && !player_answers.empty?
275
291
  player_name = player_answers.keys.first
@@ -280,8 +296,8 @@ module GitGameShow
280
296
  partial_score: "Error calculating score"
281
297
  }
282
298
  end
283
-
299
+
284
300
  results
285
301
  end
286
302
  end
287
- end
303
+ end
@@ -5,6 +5,19 @@ module GitGameShow
5
5
  class FileQuiz < MiniGame
6
6
  self.name = "File Quiz"
7
7
  self.description = "Match the commit message to the right changed file!"
8
+ self.example = <<~EXAMPLE
9
+ Which file was most likely changed in this commit?
10
+
11
+ "Update documentation with new API endpoints"
12
+
13
+ abc123f (Mar 15, 2025)
14
+
15
+ Choose your answer:
16
+ src/main.js
17
+ css/styles.css
18
+ \e[0;32;49m> README.md\e[0m
19
+ lib/utils.js
20
+ EXAMPLE
8
21
  self.questions_per_round = 5
9
22
 
10
23
  # Custom timing for this mini-game (same as AuthorQuiz)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: git_game_show
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.9
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Justin Paulson
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-03-07 00:00:00.000000000 Z
11
+ date: 2025-03-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thor
@@ -213,6 +213,8 @@ files:
213
213
  - lib/git_game_show/updater.rb
214
214
  - lib/git_game_show/version.rb
215
215
  - mini_games/author_quiz.rb
216
+ - mini_games/blame_game.rb
217
+ - mini_games/branch_detective.rb
216
218
  - mini_games/commit_message_completion.rb
217
219
  - mini_games/date_ordering_quiz.rb
218
220
  - mini_games/file_quiz.rb