I made a Snake clone with RxJS

By Devin Jameson on July 21, 2024

Snake is a great game. It's also pretty simple, so I decided to make a clone as a fun little side project. It also gave me an opportunity to use Effect (which by the way is amazing) and deepen my knowledge of RxJS.

Play the Snake clone here. You can also check out the GitHub repo here if you'd like to peek under the hood.

A fun fact about the way I built this is that it uses only a single slice of React state. All of the game logic happens outside of React and this custom hook connects the last emitted value from the game world observable to the React side of things.

While I've used RxJS in a professional capacity before, I've never built a game with it. I think the game logic would have been a lot harder to build and understand if I hadn't used the observer pattern.

Below is the observable pipeline that updates the game world every time the gameEvent$ observable emits. I love how you can read it like a book from top to bottom and end up with a pretty clear sense of how data is flowing through the program.

gameEvent$
  .pipe(
    Rx.withLatestFrom(direction$),
    Rx.scan(
      (world, [gameEvent, direction]) =>
        E.Effect.runSync(
          determineNextWorld(BOARD_SIZE, direction, gameEvent, world),
        ),
      initialWorld,
    ),
    Rx.startWith(initialWorld),
    Rx.takeWhile(({ gameState }) => gameState !== 'GameOver', true),
    Rx.repeat({ delay: () => changeGameState$ }),
    Rx.observeOn(Rx.animationFrameScheduler),
  )
  .subscribe((world) => {
    world$.next(world)
  })

A Safari window with my Snake clone and the inspector
visible