{"id":15317521,"url":"https://github.com/mehanix/cave-run","last_synced_at":"2026-03-11T10:31:46.547Z","repository":{"id":98053457,"uuid":"434660751","full_name":"mehanix/Cave-Run","owner":"mehanix","description":"👾 Do you have what it takes? - 2-bit Dungeon Escape game implemented on the Arduino Platform","archived":false,"fork":false,"pushed_at":"2023-04-21T17:36:44.000Z","size":5310,"stargazers_count":6,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-15T02:40:54.455Z","etag":null,"topics":["16x2-clcd-display","16x2lcd","8x8-dot-display","8x8-led","8x8-led-matrix","arduino","arduino-game","arduino-uno","atmega328p","game","joystick-control","shift-register"],"latest_commit_sha":null,"homepage":"","language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mehanix.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-12-03T16:21:55.000Z","updated_at":"2023-05-03T10:40:50.000Z","dependencies_parsed_at":null,"dependency_job_id":"fd9145c7-99b6-48c3-98fd-2c2f8ebd8a23","html_url":"https://github.com/mehanix/Cave-Run","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/mehanix/Cave-Run","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mehanix%2FCave-Run","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mehanix%2FCave-Run/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mehanix%2FCave-Run/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mehanix%2FCave-Run/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mehanix","download_url":"https://codeload.github.com/mehanix/Cave-Run/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mehanix%2FCave-Run/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30378075,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-11T06:09:32.197Z","status":"ssl_error","status_checked_at":"2026-03-11T06:09:17.086Z","response_time":84,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["16x2-clcd-display","16x2lcd","8x8-dot-display","8x8-led","8x8-led-matrix","arduino","arduino-game","arduino-uno","atmega328p","game","joystick-control","shift-register"],"created_at":"2024-10-01T08:57:17.544Z","updated_at":"2026-03-11T10:31:46.518Z","avatar_url":"https://github.com/mehanix.png","language":"C","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=center\u003e \u003cimg src=\"./assets/logo.png\" style=\"width:75%\"\u003e\u003c/div\u003e\n\n\u003cbr/\u003e\u003cbr/\u003e\n\u003cbr/\u003e\n\n\u003cdiv\u003e\u003cimg src=\"./assets/intro.png\"\u003e\u003c/div\u003e\n  \u003cbr/\u003e\n\nThis game was created as part of the Introduction to Robotics course I took during my 3rd year of studying Computer Science @ University of Bucharest, Faculty of Mathematics and Computer Science. It is written in C++ and the Arduino libraries, and was created during the span of a month.\n  \nI chose this game because I believed that it would best fit the restrictions of the hardware (8x8 single-color led matrices don't allow lots of expression 😛). I also wanted to implement a game with a panning camera, I thought it would be an interesting technical challenge. \n\n  I'm not aware of any other game that plays similarly - I'd call it a bit similar to Minesweeper, but that's about it.\n  \n\u003cdiv\u003e\u003cimg src=\"./assets/game-description.png\"\u003e\u003c/div\u003e\n  \u003cbr/\u003e\n  \n🔥**Cave Run**🔥 is a game where you must _escape as many rooms as you can_ in 60 seconds ⌚.\n\nTo exit a room, you must collect all the keys 🔑 while avoiding the bombs 💣. \n\nThe bombs are buried underground, but thankfully you are equipped with your trusty radar 📡 that warns you if you're approaching a bomb. \n\n_**Don't get blown up! 💥**_\n\n\u003cimg src=\"./assets/features.png\"\u003e\n\n😎 Complete menu\n\n🗺 Procedural generation\n\n🏋 Multiple difficulty settings\n\n🔲 Multple room sizes\n\n🏆 Scoreboard\n\n🛠 Customizable settings\n\n🎹 Theme song and sound effects!!!! as well as a muting option \n\n⬆ Power-up: on click, see bombs in room\n\n🎉 Endless fun!\n\n\n\u003cdiv \u003e\u003cimg src=\"./assets/how-to-play.png\"\u003e\u003c/div\u003e\n  \u003cbr/\u003e\n    \u003cbr/\u003e\n  \u003cdiv align=center\u003e\u003cimg src=\"./assets/instructions.png\" style=\"width:75%\"\u003e\u003c/div\u003e\n  \u003cbr/\u003e\n\n\u003cdiv\u003e\u003cimg src=\"./assets/components.png\"\u003e\u003c/div\u003e\n  \u003cbr/\u003e\n  \n 💥 1 Arduino Uno\n \n 💥 1 8x8 LED Matrix\n \n 💥 1 16x2 LCD \n \n 💥 1 joystick\n \n 💥 1 passive buzzer\n \n 💥 1 10k Ohm potentiometer\n \n 💥 1 matrix driver\n \n 💥 1 red LED\n \n 💥 lots and lots of wires\n \n\n  \u003cbr/\u003e\n  \u003cbr/\u003e\n\n\u003cdiv\u003e\u003cimg src=\"./assets/media.png\"\u003e\u003c/div\u003e\n\n\n\u003cdiv align=center\u003e\n  \u003cimg src=\"./assets/build.png\" style=\"width:60%\"\u003e\n\u003ca href=\"https://www.youtube.com/watch?v=kI80q4XZILo\"\u003e\u003cimg src=\"https://img.youtube.com/vi/kI80q4XZILo/0.jpg\" alt=\"Cave Run Video\"\u003e\u003c/a\u003e\n\n\u003c/div\u003e\n\n\u003cbr /\u003e\n\n![Cave Run Docs](https://github.com/mehanix/Cave-Run/blob/main/assets/docs-header.png)\n\nBelow is the Cave Run technical overview!\n\n## Architecture\n\nI've said it before, I'll say it again: Cave Run, at its core, is nothing more than a huge state machine.\n\nHere are the main states of the system:\n\n* SPLASH: The greeting screen\n* MENU: The main menu, with its options:\n* MENU_SETTINGS: The settings submenu\n* MENU_HIGHSCORES: Highscores submenu\n* MENU_ABOUT: About screen\n* GAME: The game itself\n* GAME_END: The game end screen, which waits for user interaction before switching back to MENU, thus completing the game interaction circle.\n\n\n\nAll the main arduino loop() function does is check the current global state and run its corresponding loop.\n\n## Loop design\nAll the main state loops do the following:\n\n* Get user input\n* If input, parse it and do action\n* If action results in having to redraw things, redraw what is needed depending on the state (lcd, matrix, etc)\n\nAs an example, the `menuLoop()`:\n\n* Reads joystick Y axis input\n* If there is input, compute action logic\n  * Determine if the action is legal; in this case, if moving the joystick up/down through the menu items, update the selected item index, if not going out of bounds\n  * If the internal state changed, signal that we need to redraw data, by toggling `shouldRedrawMenu` to true\n* If the state did something that toggled `shouldRedrawMenu`, we update the components so that the user gets to see the effects of their action.\n\nAll the submenus function similarly.\n\nAs for the main `gameLoop()`, while obviously more complicated internally, it boils down to:\n\n* Holding an internal game state:\n  * when first entering the game screen, the GAME_SETUP state is put into place, which sets up the game by resetting values and drawing LCD labels\n  * afterwards, we switch to the GAME_LOOP state which loops through checking if there is any update through the various game elements:\n  * `updateTime()`: Computes remaining time and displays it on the LCD when necessary\n  * `updateScore()`: Redraws score if shouldRedrawScore was toggled by another event\n  * `updatePlayer()`: Handles player movement by keeping track of the player's current and previous move. Computes when the player goes out of the current region bound and toggles map redrawing when necessary. Keeps track of player blinking animation.\n  * `updateBombRadar()`: Computes distance between player and bombs and finds the nearest bomb. If it is near enough, light up the radar. If player has stepped on bomb, make the radar LED flash intermittently for a few seconds.\n  * `updateKeys()`: Checks if player has stepped on any of the keys. If they have, remove the key from the game, and toggle redrawing of the map and updating of the key counter.\n  * `updateBomb()`: Checks if player has stepped on any bomb. If they have, remove the bomb from the game, and toggle bomb specific actions (lose a life, radar goes off)\n  * `updateDoor()`: Computes whether to unlock the door, when no keys are left, unlock the door to the next level.\n  * `updatePowerup()`: Shows bombs visible on the current map region.\n\nAdditionally, there are the following draw functions:\n* `drawMap()`: if `shouldRedrawMap` is toggled, clear and redraw the current map segment, otherwise do nothing\n* `drawPlayer()`: if `shouldRedrawPlayer` is toggled, redraw player and clear player from previous position, otherwise do nothing\n\nFinally, we also `checkEndConditions()` to see if any of the end conditions have been satisfied (timeout, no lives), and if so, we end the game, by changing the overall system state to `GAME_END`.\n\nThe score is computed on-demand , that is, whenever there is an action taken that triggers a scoreable event, we trigger score calculation and update the display accordingly.\n\n## State changing\n\nState changing is handled with interrupts in the `StateChanger.h` module. It's a big switch statement (you will notice that I love using switch statements - i feel they work well in this environment) which depending on the current global state (system state + other variables depending on the state) switches the global state and marks redraws to be done where necessary.\n\n! Please note: i tried to keep the interrupt function as simple logically as possible, as per Arduino design guidelines, you're only supposed to do fast operations in interrupts, so I tried to only change variable states and other operations that are done in linear time.\n\n### Game design\nThe rooms are procedurally generated using a customizable algorithm which renders a number of rooms of certain sizes based on the current difficulty.\nThe default configuration is as follows:\n\n* for Easy, 3 small rooms, 3 medium rooms, then large rooms until the time runs out\n* for Medium, 1 small room, 2 medium rooms, then large rooms until the time runs out\n* for Hard, 2 medium rooms, then large rooms until time runs out\n\nThe difficulty setting also increases the amount of items that you have to collect/avoid per room.\n\n## Collision detection\n\nWhen generating objects for the level, I make sure they don't generate on top of one another or block the door through a brute-force algorithm. I simply check if they overlap anything that already exists, and if not, the newly-generated item is good to go. Otherwise, I generate a new pair of coordinates and try again.\n\nYou may call it rudimentary, I call it good enough for the job, due to the small map and item count. It's fast enough to be insesisable when playing, so i deem it ✨appropiate✨ for this purpose.\n\n## The game map, player movement and panning camera\n\nThe map is sized a multiple of 4 - as the matrix is 8x8, i figured this would flow most naturally. I keep the map in memory as a simple bool 2d array, with 1 representing the walls and 0 the floor through which the player can move.\n\nThe items are kept separately in arrays of `Coordinates` - one array for the keys, one for the bombs. Ah, yes, the `Coordinate` is a simple struct I created because I don't like the C++ pair. I wrote this entire game using C-style code, it'd be weird to add C++ elements like that anyway.\n\nOne of the most interesting aspects to program in this project was the panning camera - or at least I thought so, until I figured out i could use a bit of arithmetics to get the job done.\n\nIf you imagine a map as a bunch of 8x8 quadrants, you can take your current position x/y coordinates, divide them by 8, and get the current region segment.\n\n```\nFor example:\nMy coordinates are x=2,y=5 so i am in region {0,0}\nMy coordinates are x=10,y=5 so i am in region {1,0} (divide x and y by 8)\n```\nWhen changing region (when stepping from x=7 to x=8 for instance), I should redraw both the map and player. By doing the opposite operation (multiply region segment data by 8) you get the coordinates of the upper leftmost corner of the region, and you can use that index to draw the map section where the player is located.\n\nI also draw the player as (x%8, y%8) -\u003e once you step out of the current region you wrap around on the other side. This works out nicely as the map has no extra walls on the inside.\n\nCool trick.\n\nAs I'm writing this I just figured out a possible optimisation: I think you can get away without the 2d map entirely, as I only use it for drawing sections of it on the LED matrix and for collision detection. \n\nIt would complicate the code significantly, but it'd also free up a huge chunk of the memory which... I'm not sure what I'd do with. This approach would also sacrifice extensibility: With a 2d map, I can add obstacles such as extra walls in the future, whereas using formulas would force me to keep the map as a square. \n\nOverall, I think i made the right call going for the 2d bitmap route.\n\n## Conclusions\n\n* I enjoyed making this game - the most challenging aspect was actually being flexible and adapting to the restrictions of the hardware.\n* I believe that _polish_ is subjective - this is my vision of how the final version of the game should look. I found it enjoyable to see other people's versions through these projects.\n* Don't write your technical docs in the github wiki panel - i just spent an hour applying forencsics techniques to my Firefox cache in order to recover this page. I succeeded, but still. \n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmehanix%2Fcave-run","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmehanix%2Fcave-run","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmehanix%2Fcave-run/lists"}