Reducer Logic and Game Events

Making the Reducer the “Brain” of the Tick

Welcome back. In the previous lesson, you set up the game loop in GameScreen.jsx using useEffect and setInterval. As a reminder, that loop dispatches a TICK action every second while the game is running and not paused. Today, you will process that TICK inside the reducer to simulate time passing, customer arrivals, satisfaction decay, and automatic win/loss transitions. This keeps your components simple and moves complex logic into one reliable place: the reducer.

Mapping Real Seconds to In-Game Time

We will use a small helper to convert the remaining real-time seconds into an in-game clock. This keeps time math out of the TICK case and makes your reducer easier to read.

src/reducers/gameReducer.js

function updateGameTime(timeRemaining) {
  const totalMinutes = 180 - timeRemaining;
  const gameMinutesElapsed = totalMinutes * 3;
  const gameHour = 6 + Math.floor(gameMinutesElapsed / 60);
  const gameMinute = gameMinutesElapsed % 60;
  return { gameHour, gameMinute };
}

Explanation:

  • Given timeRemaining (seconds left in the shift), the function computes a fast-forwarded in-game clock starting at 6:00.
  • Each real second advances the in-game clock by 3 minutes.
  • The function returns the derived hour and minute so the UI can show time without extra logic.

Writing the TICK Case: Time, Events, and Transitions

Now, let’s implement the core logic that runs every second. This includes guarding when the game is paused, updating time, spawning customers based on a probability, adjusting satisfaction, and moving to the results screen when the player wins or loses.

src/reducers/gameReducer.js

    case 'TICK': {
      if (state.phase !== 'PLAYING' || state.timers.isPaused) return state;

      const newTime = Math.max(0, state.timers.timeRemaining - 1);
      const { gameHour, gameMinute } = updateGameTime(newTime);

      // Customer arrival logic
      const arrivalRate = 0.5;
      const isPeakHour = gameHour === 7;
      const arrivalChance = isPeakHour ? Math.min(1, arrivalRate + 0.25) : arrivalRate;
      const newCustomer = Math.random() < arrivalChance ? 1 : 0;
      const newQueue = Math.min(state.config.maxQueue, state.shop.queueLength + newCustomer);

      // Satisfaction loss
      const satisfactionLoss = newQueue > 10 ? 2 : 0;
      const newSatisfaction = Math.max(0, state.resources.satisfaction - satisfactionLoss);

      // Check Loss Condition
      if (newQueue >= state.config.maxQueue) {
        return {
          ...state,
          phase: 'RESULTS',
          ui: { ...state.ui, screen: 'results' },
          result: { won: false, reason: 'Queue overflow!' }
        };
      }

      if (newTime === 0) {
        return {
          ...state,
          timers: { ...state.timers, timeRemaining: newTime, gameHour, gameMinute },
          shop: { ...state.shop, queueLength: newQueue },
          resources: { ...state.resources, satisfaction: newSatisfaction },
          phase: 'RESULTS',
          ui: { ...state.ui, screen: 'results' },
          result: { won: true, reason: 'Shift complete!' }
        };
      }

      return {
        ...state,
        timers: { ...state.timers, timeRemaining: newTime, gameHour, gameMinute },
        shop: { ...state.shop, queueLength: newQueue },
        resources: { ...state.resources, satisfaction: newSatisfaction }
      };
    }

What this does:

  • Guard: If not PLAYING or isPaused, return the current state to keep behavior predictable.
  • Time: Decrease timeRemaining by 1, clamp at 0, and recalculate the in-game clock via updateGameTime.
  • Customers: Use a base arrivalRate (0.5). At peak hour (7 AM), increase the chance by 0.25. Add at most one customer per tick and cap the queue at config.maxQueue.
  • Satisfaction: If the line is long (over 10), reduce satisfaction by 2, but never below 0.
  • Loss: If the queue hits maxQueue, move to RESULTS with a failure reason and stop the loop.
  • Win: If time hits 0, update the state one last time and move to RESULTS with a success reason.
  • Immutability: Each update uses object spreads so React can detect changes and re-render correctly.

Wrap-Up and What’s Next

You just centralized the café’s “brain” inside the reducer:

  • A helper that converts seconds to an in-game clock.
  • A robust TICK handler that updates time, simulates arrivals, adjusts satisfaction, and handles win/loss transitions.

In the practice that follows, you will bring this logic to life and make the café feel dynamic and responsive. Let’s dive in and make each second count.

Sign up

Join the 1M+ learners on CodeSignal

Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal