Playing DOOM as my Math Project

written on Aug 10, 2019 by Hao Qi Wu

In 2017, for my final project in my geometry class, me and my group were tasked to make a game for the class to play that would help everyone in the class to study for the final exam. Most groups went for a game of Kahoot or Jeopardy. But since I have programming skills, I decided to do something else, make a quiz game in DOOM. I never touched the source code for DOOM before or ever made modifications to a game like DOOM, so it was a pretty crazy idea. However, with years of amateur programming experience, I was confident that I could do it.

YouTube Link

Here's where you can download it and play it for yourself.

As many of you may know, the source code for DOOM is freely available online under the Doom Source License and GNU GPL. Because of this, the DOOM Community has made source ports that were cross-platform, fixed bugs, enhancements, and altered gameplay.

Because this was a group project, people need something to do and I was the only one with the skills do the programming. So we had 2 writers, one for the questions, and one for things like documentation. There was one other person in the team, but I'll be honest and say that I completely forgot what he even did in this project. Sorry, Dakota.

First thing, I did to get this project off the ground was to pick a source port to work with. I needed something that was simple and worked on modern machines. I can't exactly remember how I chose the source ports, but I chose Chocolate Doom. Which has 666 stars as of the writing of this article but that's not important.

The idea we had was that every time you take damage in the game, you would be presented with a question to answer. The amount of damage you take would be instead based on whether or not you answered the question correctly.

So, the first thing I needed to do was find the where in the game's code was the logic that handles the player taking damage. P_DamageMobj was exactly the function that I was looking for. Next, we needed some way to display the message on to the screen and we needed a way to pause the game. M_StartMessage did both, killing 2 demons with one gunshot.

Ok great, time to display the question. This part is pretty easy. Just store the questions, possible answers, and correct answers. Then have a function that puts the question and answers next to each other and passes them to the start message function.

This was taken on my phone because I used my phone to show off the progress to my teammates and it was easier this way. I did some transformations on the images so that they don't look too bad.

Now, we need some way for the question to be answered. Messages with M_StartMessage only checked for a few keys like y, n, and escape. So, I needed to add more keys for it to check for. To do this, I need to find where in DOOM's code does it handle inputs for M_StartMessage. M_Responder is the function where it happens, it also seems to be where the game handles all inputs.

Next, to tell the user that they got the correct answer. The game already has a system for telling the user stuff like this. When you put up health, it would tell you in text that you got some health at the top of the screen. The game does this by editing the player's message variable.

Great, everything is starting to come together.

Many of questions needed a picture to go along with them, so I needed to figure out how the game add and display images. The game uses its own file format but thankfully people in the DOOM community have already figured how to add images to the game for me and I just had to use some premade tools to do this. DOOM stores them in the WAD file that isn't part of the game's codebase. The WAD file contains level data, images, sound, etc. However, I still need to figure out how to get the Game to display them. I took a look at how the game displays menu elements like the DOOM logo on the main menu.

//
// M_DrawMainMenu
//
void M_DrawMainMenu(void)
{
    V_DrawPatchDirect(94, 2,
                      W_CacheLumpName(DEH_String("M_DOOM"), PU_CACHE));
}

That's where I found V_DrawPatchDirect, it takes in the x, y coordinates for the images, and whatever this cache stuff is about. All I know is that if I change the value "M_DOOM" it changes what image is drawn onto the screen. However, when I go to see my image, I noticed that the image is stretched. In fact, I realized that all the images are squished to fix this.

Here's what the image file looks like.

Here's what it looks like in-game. Notice how it's not as wide as the image file.

Cool, now we need to display an image when the game displays the question. To do this, I found where, in the code, the game displays its messages. That place is the function called M_Drawer, so in there I made it so that it gets the name of the image of the current question and then display it.

Great, more cringe humor.

in case you were wondering, wrong answer is a placeholder, those were all replaced.

Now, that's all the work that I finished. There were other things I tried to get working but ran out of time. I'm not mad or upset about it, since they weren't really all that important. It's the thing with deadlines, you are going to have to cut some stuff to finish on time.

The one feature that was cut was some audio that would play when you get the correct answer and when you answered all the questions. I added the audio into the WAD, added to the game's list of sound effects, and sound effect number type and used the function S_StartSound to play it but it only worked some of the time. Each sound has a priority value and I couldn't figure out how to set that value correctly before the deadline. But, oh well, things happen and it's not something I felt strongly about.

I'll be honest the code doesn't exactly follow all the good standards but it works. Here, take a look at it yourself if you like.

The presentation went without any issues. There was one bug that I was afraid I might hit during the presentation. The game starts with replay, the game calls them demos, playing in the background. When the player gets hurt in the replay, displays the question as if you were the one playing and after that, it would be hard to get back to the main menu. I didn't have time to fix it but I thankfully speedran through the menus and didn't trigger the bug.