Building a Twitch Chat Plays Game - Snake
I streamed building this game on Twitch, throw me a follow if that is something you are interested in: https://www.twitch.tv/sadboifeverdreamz
If you want to skip all the narration you can go straight to the source code for the project: https://github.com/nkorai/TwitchPlaysSnake
Background
Ever since I first witnessed TwitchPlaysPokemon I was immediately enthralled. For the uninitiated: Twitch Plays Pokemon is a Twitch stream that plays Pokemon games like Pokemon Red - except the person streaming is not actually playing the game. The stream is controlled by the audience watching the stream and allows anyone via the stream chat to type in the commands that are controlling the game. This of course leads to the kind of hijinks that a hive mind is expected to get up to, but miraculously it also led to the chat beating the elite four and finishing the game. The community behind this in the early days was incredible and I have heavy nostalgia remembering the subreddit culture around it.
I've been wanting to build something similar for a while after running into TwitchPlaysPokemon in college and dabbled in some complicated concepts over the years that didn't really pan out. I also know that in my personal life (vs professional life) I'm great at starting projects and not so great at finishing them. So I thought I'd reset and try to take a basic Twitch chat plays idea from design to completion.
Where I ran into issues dabbling in the genre before was trying to connect an existing game - e.g. something you download from Steam - to interface with Twitch chat, usually requiring me to give up control of my mouse and keyboard so the chat game could control it instead. So instead of integrating with an existing game, I thought why not build the game myself and that way I don't need keyboard or mouse inputs, I can directly interface with game by making Twitch chat the only inputs to the game.
So I asked myself, what is the simplest-ish game I could think of implementing that is still engaging enough for chat to play? I didn't want to dabble too much into 2D game engines at this moment so a grid system game would really be best. That's when I remembered playing the classic Snake game on my first ever phone - the Nokia 3310.
Tech Stack
When it came to selecting tech stack I wanted to build something that anyone could run an independent instance of for their stream. I could do this via web browser and a server running an instance of the game for everyone, but figuring out server hosting felt like a hassle keeping me from the fun parts of this kind of a project. So I went with a desktop application model.
In my day job I work with Typescript and I liked the idea of not having to context switch to something different while working on this project.
Keeping those things in mind I went with ElectronJS for the desktop framework being used for this project. I used electron-quick-start-typescript to start off the project and opted to use webpack to wire up Typescript in the "renderer" portion of the ElectronJS application. All-in-all this was a time consuming process where I did things wrong for a while but I was glad to have figured it out.
I opted late in the project to integrate Bootstrap to start giving a more polished look and feel to the app and rewrote the app to use it correctly.
To integrate with Twitch I first used tmijs and built a standard Twitch read-only chat integration. However at some point I wanted to add a feature to delete game command messages from chat to keep things clean and realized tmijs did not integrate with the newer Twitch APIs that are required to perform a chat message deletion, at which point I migrated over to twurple.js.
How does it work
Once the game is started, a new vote is carried out every x milliseconds. Users can vote on what direction and distance the snake moves in by typing in chat commands like sg:right, sg:left, sg:up and sg:down. Users can also chat sg:right10 to move the snake 10 units on that vote, an option I added to allow the game to be sped up if chat chooses to. The votes are tallied and the most voted on command wins e.g. sg:right, sg:right10, sg:left, sg:left
in a voting session results in sg:left
being executed. Hitting the wall or running over yourself results in dying and a reset.
The game runs in 2 modes:
- Static: If there are no votes in a round, then the snake does not move. If there are votes, the logic described above is executed.
- Continuous: If there are no votes in a round, then the snake keeps moving in the direction it last moved in - 1 unit. If there are votes, the logic described above is executed.
The game keeps track of score and also the high score, which can be reset in the configuration screen. A list of all things that can configured are:
- The channel name
- The game mode
- Minimum chat distance e.g. setting it to 5 makes every vote move the snake 5 units at a minimum
- Maximum chat distance e.g. setting this to 10 (default) prevents chat from moving the snake more than 10 units every vote
- Vote duration (ms): how often to conduct a vote round
How do I use it
You can checkout the github repo here and follow the instructions there. If you are not code savvy and really want to this reach out to me somehow and convince me to release this on the Steam store as a free game. :)
Building it out
Since I was streaming building out the application I decided to keep track of the work I was doing and general ideas in a "design doc" but really it quickly became a stream of consciousness to do list/dev log. I'll summarize it below:
07/25
I was able to get a minimum viable product up and running on this day, get basic commands/all chat messages from Twitch. I copy pasted some snake game code to start with just to see if this idea even had legs. I later rewrote all of the game code to be able to be able to support all of the features I wanted to implement. I added the ability to track a high score, multiple unit movement via sg:right5 etc and made the game conduct vote rounds more often to allow testing.
07/27
I added a death feature if the snake runs over itself to better emulate the original game, and I made reversing direction a death action as well. I modified the game visuals to be square based instead of circle based. I generally worked on the basic color scheme for the actual game itself. I moved rendering the score and high score out of the game canvas and into the HTML doc tree instead for better control and to clear up the game space.
07/29
I worked on the heading, instructions and visuals of the game. I added the ability to perform mode select between static and continuous as an experiment and really liked the ability to do so. I added a progress bar to show how long it was going to be until the next vote. I added the max cap on chat maximum distance so sg:left10000 still gets capped to 10 if that is the maximum distance.
08/01
Added the concept of persistent configurations into the app, using electron-store, so now things like configuration and high score are stored between game instance launches. Worked a bit on the visuals of the progress bar.
08/05
I wanted to delete game command chat messages and found twurple and the Twitch API to delete chat messages: https://dev.twitch.tv/docs/api/reference/#delete-chat-messages.
I added a configuration screen and the concept of "screens". This made me consider using React but I knew the number of screens would be low (probably only 2) so I stuck to using a custom screen rendering function rather than something heavier like React. I started adding the first set of configurable things: channel name, mix/max chat distance and vote duration. I added game mode to the configuration but it was as a text box and I didn't like that so I knew I'd revisit it.
I added bootstrap and rewrote the app to use it correctly.
08/10
I revisited configuring game mode and built out the radio select instead of relying on text input that can be mangled. I worked a little on making things look nicer overall.
08/14
I added the ability to clear high score. I found and fixed a bug where when the game command is greater than the tiles to the death wall edge and the game resets, render frames are still called in the next new game, resulting on the new game snake moving x units instead of starting at the start point.
I made it so if channel name is not configured the app does not allow navigating back to the gameplay, and started defaulting the app to the configuration screen in this case.
I found and fixed a bug where vote duration was only being set on app initialization instead of every time it was changed in the configuration screen.
I started the game after clearing my persistence store to see if that works since that is what anyone else will experience when they see this.
Conclusion
This was really fun for me to work on, I love tiny self-contained projects like this. I might dabble more in canvas based rendering. This was also my first time coding on stream so it was really fun for me to do something I love and share it with a few other people live. Follow me on Twitch if that interests you!