Super Mario Bros.
Objectives
- Read and understand all of the Super Mario Bros. source code from Lecture 4.
- Program it such that when the player is dropped into the level, they’re always done so above solid ground.
- Implement keys and locked blocks.
- Implement levels that get progressively longer.
- Implement impassable obstacles that require ladders to climb.
- Implement powerups.
Getting Started
Download the distribution code for your game from cdn.cs50.net/2d/2025/x/projects/4/mario.zip and unzip mario.zip, which should yield a directory called mario.
Then, in a terminal window, move to the directory where you extracted mario, and run
cd mario
It’s-a Key!
So far, we have a fair foundation for a platforming game present in the distro.
Specification
-
Program it such that when the player is dropped into the level, they’re always done so above solid ground. Just like we generate the level column by column (as can be seen in
LevelMaker.lua), we can check the game’s map column by column and simply ensure that the player isn’t placed above a column that just spawned a chasm by looking at all of the tiles along the Y-axis, going from left to right, until we’ve come across a column where we encounter a solid tile (as by checking whether the id is equal toTILE_ID_GROUND). -
In
LevelMaker.lua, generate a random-colored key and lock block (taken fromkeys_and_locks.pngin thegraphicsfolder of the distro). The key should unlock the block when the player collides with it, triggering the block to disappear. This is something you’ll introduce intoLevelMaker.generatewhile it’s actively generating the level; simply maintaining a flag for whether the key and lock have been spawned and placed and randomly choosing to place them down could do (or you could simply do it after the whole rest of the level is generated). The former will likely be easier so you can conditionally do it when you’re not already spawning a block, since otherwise you’ll have to iterate over all of the blocks you’ve already generated throughout the level and compare their positions with that of where you’d potentially like to generate a key or lock. See how the code for spawning gems works (particularly with theonConsumecallback) for how you might implement picking up the key, and see the code for spawning blocks and theonCollidefunction for how you might implement the key blocks! -
Once the lock has disappeared, trigger a goal post to spawn at the end of the level. Goal posts can be found in
flags.png; feel free to use whichever one you’d like! Note that the flag and the pole are separated, so you’ll have to spawn aGameObjectfor each segment of the flag and one for the flag itself. This is code we can likely add to theonCollidefunction of our lock blocks, once we’ve collided with them and have the key they need to unlock. Just like gems spawn when we collide with some overhead blocks, you’ll simply need to add newGameObjects to the scene that comprise a flag pole. Note that the pole and flag are separate objects, but they should be placed in such a way that makes them look like one unit! (See the scene mockup infull_sheet.pngfor some inspiration). -
When the player touches this goal post, we should regenerate the level, spawn the player at the beginning of it again (this can all be done via just reloading
PlayState), and make it a little longer than it was before. You’ll need to introduceparamsto thePlayState:enterfunction that keeps track of the current level and persists the player’s score for this to work properly. The easiest way to do this is to just add anonConsumecallback to each flag piece when we instantiate them in the last goal; thisonConsumemethod should then just restart ourPlayState, only now we’ll need to ensure we pass in ourscoreandwidthof our game map so that we can generate a map larger than the one before it. For this, you’ll need to implement aPlayState:entermethod accordingly; see prior assignments for plenty of examples on how we can achieve this! And don’t forget to edit the defaultgStateMachine:change('play')call to take in some default score and level width! -
Spawn pillars (using the ground tile, so as part of the tile map itself) that are too tall for the player to jump over (so at least 4 blocks high with the default distro). In order to make it possible to climb over these pillars, generate on the left and righthand sides of these pillars a ladder (comprised not of tiles but of
GameObjects), extending vertically (which you can find sprites for in the included spritesheet), that also is the same height from the ground to the top of the pillar. In order for our player to actually be able to do something with these, extend our player’s capabilities by adding a newPlayerLadderClimbingState. This should be transitioned into when we press the “up” key anywhere we’re overlapping a ladder tile (which means they can’t be collidable!). Not only should this be possible while walking, but be sure to make it possible while in the jump state as well. Also, be sure our player looks different while in this state to visually indicate that we’re indeed in it (there are likewise some appropriate sprites for this animation in the spritesheet). Holding “up” should make the player climb upwards along the ladder, while letting go of “up” when in this state should allow the player to pause while climbing, allowing them to essentially rest halfway should they choose, and not fall as they otherwise would when jumping. Climbing should maximally be allowed up to the topmost ladder block (such that the player is effectively standing atop it in this new animation), at which point you can then either walk or jump your way onto the top of the pillar and thus traverse it (though jumping halfway up the ladder should also be possible). Think about what might need to be not only enabled but also disabled in order for this to work! - Lastly, in lieu of always spawning gems, adjust things such that occasionally a block in the world will spawn a powerup (with a graphic of your choice from the spritesheet; a mushroom or star is a good pick!). This powerup should, when picked up like the gems, confer a “supercharged” status effect to the player for 10 seconds such that for said duration, the player can take no damage from enemies and instead will destroy them simply by walking into them; additionally, to reflect this state visually, apply what we learned in the Breakout lecture by adding a particle effect of your choice to the player that emits from their body (though do make sure it’s clearly visible!).
Be sure to comment your code in the places where you implement the above items. This is true for all implementations, but particularly if you do so in a spot we might not expect you to.
Errata
NONE
How to Submit
When you submit your project, the contents of your branch must match the file structure of the unzipped distribution code exactly as originally received. That is to say, your files should not be nested inside of any other directories of your own creation or otherwise deviate from the file structure we gave you. Your branch should also not contain any code from any other projects, only this one. Failure to adhere to this file structure will result in your submission being rejected.
By way of a simple example, for this project that means that if the grading staff visits https://github.com/me50/USERNAME/blob/games50/projects/2025/x/mario/src/LevelMaker.lua (where USERNAME is your own GitHub username as provided in the form, below) we should be brought to your LevelMaker.lua file for Super Mario Bros. If that’s not how your code is organized when you check (e.g., you get a 404 error or don’t see your edits), reorganize your repository as needed to match this paradigm. Code improperly organized will not be eligible for a passing score.
- If you haven’t already, visit this link, log in with your GitHub account, and click Authorize cs50. Then, check the box indicating that you’d like to grant course staff access to your submissions, and click Join course.
-
Install Git and, optionally, install
submit50. -
Using Git, push your work to
https://github.com/me50/USERNAME.git, whereUSERNAMEis your GitHub username, on a branch calledgames50/projects/2025/x/marioor, if you’ve installedsubmit50, executesubmit50 games50/projects/2025/x/marioinstead.
-
Record a screencast, not to exceed 5 minutes in length in which you demonstrate your game’s functionality. Upload that video to YouTube (as unlisted or public, but not private). This video’s requirements are:
- It is not a YouTube “short”.
- The video begins with a slide or text overlay containing both your edX and GitHub usernames.
- It must show your game live and in action. Do not use this video to walk us through any code.
- It demonstrates that all six (6) items of the specification have been implemented.
- The video description has been timestamped at the (first) point where your video demonstrates each of the above-referenced implementations.
- The video has been uploaded less than one month from the time of your submission of the form for this project (the final step below).
- Submit this form.
You can then go to https://cs50.me/cs502d to view your current progress!