Class: GitGameShow::ServerHandler

Inherits:
Object
  • Object
show all
Defined in:
lib/git_game_show/server_handler.rb

Overview

Coordinates the various components of the game server

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(port:, password:, rounds:, repo:) ⇒ ServerHandler



6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# File 'lib/git_game_show/server_handler.rb', line 6

def initialize(port:, password:, rounds:, repo:)
  @port = port
  @password = password
  @rounds = rounds
  @repo = repo

  # Initialize core components
  @game_state = GameState.new(rounds)
  @player_manager = PlayerManager.new
  @mini_game_loader = MiniGameLoader.new

  # Initialize UI components
  @renderer = Renderer.new
  @sidebar = Sidebar.new(@renderer)
  @console = Console.new(self, @renderer)

  # Initialize network components - passing self allows circular reference
  @message_handler = MessageHandler.new(@player_manager, @game_state, @renderer, self)
  @message_handler.set_password(password)
  @server = Server.new(port, @message_handler)

  # Finally initialize the question manager
  @question_manager = QuestionManager.new(@game_state, @player_manager)
end

Instance Attribute Details

#game_stateObject (readonly)

Returns the value of attribute game_state.



4
5
6
# File 'lib/git_game_show/server_handler.rb', line 4

def game_state
  @game_state
end

#player_managerObject (readonly)

Returns the value of attribute player_manager.



4
5
6
# File 'lib/git_game_show/server_handler.rb', line 4

def player_manager
  @player_manager
end

#question_managerObject (readonly)

Returns the value of attribute question_manager.



4
5
6
# File 'lib/git_game_show/server_handler.rb', line 4

def question_manager
  @question_manager
end

Instance Method Details

#ask_next_questionObject



142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
# File 'lib/git_game_show/server_handler.rb', line 142

def ask_next_question
  return if @game_state.current_question_index >= @game_state.round_questions.size

  # Log information for debugging
  @renderer.log_message("Preparing question #{@game_state.current_question_index + 1} of #{@game_state.round_questions.size}", :cyan)

  # Prepare the question
  @game_state.prepare_next_question
  current_question = @game_state.current_question

  # Get the appropriate timeout value
  timeout = @question_manager.question_timeout

  # Prepare question data
  begin
    question_data = {
      type: MessageType::QUESTION,
      question_id: @game_state.current_question_id.to_s,
      question: current_question[:question].to_s,
      options: current_question[:options] || [],
      timeout: timeout,
      round: @game_state.current_round.to_i,
      question_number: (@game_state.current_question_index + 1).to_i,
      total_questions: @game_state.round_questions.size.to_i
    }

    # Add additional question data safely
    # Add question_type if it's a special question type (like ordering)
    if current_question && current_question[:question_type]
      question_data[:question_type] = current_question[:question_type].to_s
    end

    # Add commit info if available (for AuthorQuiz)
    if current_question && current_question[:commit_info]
      # Make a safe copy to avoid potential issues with the original object
      if current_question[:commit_info].is_a?(Hash)
        safe_commit_info = {}
        current_question[:commit_info].each do |key, value|
          safe_commit_info[key.to_s] = value.to_s
        end
        question_data[:commit_info] = safe_commit_info
      else
        question_data[:commit_info] = current_question[:commit_info].to_s
      end
    end

    # Add context if available (for BlameGame)
    if current_question && current_question[:context]
      question_data[:context] = current_question[:context].to_s
    end
  rescue => e
    @renderer.log_message("Error preparing question data: #{e.message}", :red)
    # Create a minimal fallback question
    question_data = {
      type: MessageType::QUESTION,
      question_id: @game_state.current_question_id.to_s,
      question: "Question #{@game_state.current_question_index + 1}",
      options: ["Option 1", "Option 2", "Option 3", "Option 4"],
      timeout: timeout,
      round: @game_state.current_round.to_i,
      question_number: (@game_state.current_question_index + 1).to_i,
      total_questions: @game_state.round_questions.size.to_i
    }
  end

  # Don't log detailed question info to prevent author lists from showing
  @renderer.log_message("Question #{@game_state.current_question_index + 1}/#{@game_state.round_questions.size}", :cyan)
  @renderer.log_message("Broadcasting question to players...", :cyan)
  broadcast_message(question_data)

  # Set a timer for question timeout
  EM.add_timer(timeout) do
    @renderer.log_message("Question timeout (#{timeout}s) - evaluating", :yellow)
    evaluate_answers
  end
end

#broadcast_message(message, exclude: nil) ⇒ Object



288
289
290
# File 'lib/git_game_show/server_handler.rb', line 288

def broadcast_message(message, exclude: nil)
  @server.broadcast_message(message, exclude: exclude)
end

#broadcast_scoreboardObject



292
293
294
295
296
297
# File 'lib/git_game_show/server_handler.rb', line 292

def broadcast_scoreboard
  broadcast_message({
    type: MessageType::SCOREBOARD,
    scores: @player_manager.sorted_scores
  })
end

#end_gameObject



262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
# File 'lib/git_game_show/server_handler.rb', line 262

def end_game
  @game_state.end_game

  # Get winner and scores
  winner = @player_manager.top_player
  scores = @player_manager.scores

  # Notify all players
  broadcast_message({
    type: MessageType::GAME_END,
    winner: winner ? winner[0].to_s : "",
    scores: @player_manager.sorted_scores
  })

  # Display the final results
  @renderer.draw_game_over(winner, scores)

  # Reset for next game
  @game_state.reset_game
  @player_manager.reset_scores

  # Update sidebar
  @sidebar.update_player_list(@player_manager.player_names, @player_manager.scores)
  @renderer.log_message("Game ended! Type 'start' to play again or 'exit' to quit.", :cyan)
end

#evaluate_answersObject



219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
# File 'lib/git_game_show/server_handler.rb', line 219

def evaluate_answers
  # Delegate to question manager
  evaluation = @question_manager.evaluate_answers
  return unless evaluation

  # Update player list in sidebar to reflect new scores
  @sidebar.update_player_list(@player_manager.player_names, @player_manager.scores)

  # Broadcast results to all players
  broadcast_message({
    type: MessageType::ROUND_RESULT,
    question: evaluation[:question],
    results: evaluation[:results],
    correct_answer: evaluation[:question][:formatted_correct_answer] || evaluation[:question][:correct_answer],
    scores: @player_manager.sorted_scores
  })

  # Log current scores for the host
  @renderer.log_message("Current scores:", :cyan)
  @player_manager.sorted_scores.each do |player, score|
    truncated_name = player.length > 15 ? "#{player[0...12]}..." : player
    @renderer.log_message("#{truncated_name}: #{score} points", :light_blue)
  end

  # Move to next question or round
  @game_state.move_to_next_question

  if @game_state.current_question_index >= @game_state.round_questions.size
    # End of round
    EM.add_timer(GitGameShow::DEFAULT_CONFIG[:transition_delay]) do
      start_next_round
    end
  else
    # Next question - use mini-game specific timing if available
    display_time = @question_manager.question_display_time

    @renderer.log_message("Next question in #{display_time} seconds...", :cyan)
    EM.add_timer(display_time) do
      ask_next_question
    end
  end
end

#handle_end_commandObject



83
84
85
86
87
88
89
90
91
92
# File 'lib/git_game_show/server_handler.rb', line 83

def handle_end_command
  if @game_state.playing?
    @renderer.log_message("Ending game early...", :yellow)
    end_game
  elsif @game_state.ended?
    @renderer.log_message("Game already ended. Type 'start' to begin a new game.", :yellow)
  else
    @renderer.log_message("No game in progress to end", :yellow)
  end
end

#handle_reset_commandObject



94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/git_game_show/server_handler.rb', line 94

def handle_reset_command
  if @game_state.ended?
    @renderer.log_message("Manually resetting all players to waiting room state...", :yellow)

    # Send a game reset message to all players
    broadcast_message({
      type: MessageType::GAME_RESET,
      message: "Game has been reset by the host. Waiting for a new game to start."
    })

    # Update game state
    @game_state.reset_game
  else
    @renderer.log_message("Can only reset after a game has ended", :yellow)
  end
end

#handle_start_commandObject

Game lifecycle methods



53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# File 'lib/git_game_show/server_handler.rb', line 53

def handle_start_command
  if @player_manager.player_count < 1
    @renderer.log_message("Need at least one player to start", :red)
    return
  end

  # If players are in an ended state, reset them first
  if @game_state.ended?
    @renderer.log_message("Resetting players from previous game...", :light_black)
    broadcast_message({
      type: MessageType::GAME_RESET,
      message: "Get ready! The host is starting a new game..."
    })
    # Give players a moment to see the reset message
    sleep(1)
  end

  # Start the game
  if @game_state.start_game
    broadcast_message({
      type: MessageType::GAME_START,
      rounds: @rounds,
      players: @player_manager.player_names
    })

    @renderer.log_message("Game started with #{@player_manager.player_count} players", :green)
    start_next_round
  end
end

#start_next_roundObject



111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# File 'lib/git_game_show/server_handler.rb', line 111

def start_next_round
  @game_state.start_next_round(@mini_game_loader.select_next_mini_game)

  # Check if we've completed all rounds
  if @game_state.current_round > @rounds
    @renderer.log_message("All rounds completed! Showing final scores...", :green)
    EM.next_tick { end_game } # Use next_tick to ensure it runs after current operations
    return
  end

  # Announce new round
  broadcast_message({
    type: 'round_start',
    round: @game_state.current_round,
    total_rounds: @rounds,
    mini_game: @game_state.current_mini_game.class.name,
    description: @game_state.current_mini_game.class.description,
    example: @game_state.current_mini_game.class.example
  })

  # Generate questions for this round
  @question_manager.generate_questions(@repo)

  @renderer.log_message("Starting round #{@game_state.current_round}: #{@game_state.current_mini_game.class.name}", :cyan)

  # Start the first question after a short delay
  EM.add_timer(3) do
    ask_next_question
  end
end

#start_with_ui(join_link = nil) ⇒ Object



31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/git_game_show/server_handler.rb', line 31

def start_with_ui(join_link = nil)
  # Store join link as instance variable so it's accessible throughout the class
  @join_link = join_link

  # Setup UI
  @renderer.setup
  @renderer.draw_welcome_banner
  @renderer.draw_join_link(@join_link) if @join_link
  @sidebar.draw_header
  @sidebar.update_player_list(@player_manager.player_names, @player_manager.scores)
  @renderer.draw_command_prompt

  # Start event machine
  EM.run do
    # Start the server
    @server.start
    # Setup console commands
    @console.setup_command_handler
  end
end