Arcade Legends: the Ultimate Raspberry Pi Arcade Ecosystem

by ShivanshCodes in Circuits > Raspberry Pi

21 Views, 0 Favorites, 0 Comments

Arcade Legends: the Ultimate Raspberry Pi Arcade Ecosystem

Arcade Legends wiring diagram.png
Arcade Legends product launch poster.png

Most arcade projects focus on a single game. I wanted to create something much bigger: an entire arcade ecosystem.

Arcade Legends is a Raspberry Pi powered smart arcade platform that combines RFID player accounts, voice interaction, achievement tracking, automatic winner photography, live leaderboards, custom physical arcade games, and a real-time web dashboard into one connected experience. Every player has their own profile, progress, achievements, and Hall of Fame history.

When a player approaches the machine, a motion sensor wakes the system. They scan an RFID card to log in, the machine greets them by name, loads their profile, and gives them access to a collection of custom-built arcade challenges including Simon Speaks, Reaction Master, LED Roulette, Whack-A-Mole, Boss Battles, and more. As they play, they earn XP, unlock achievements, climb the leaderboards, and compete for a place among the arcade champions.

What began as a simple electronics project gradually evolved into a system that combines Python programming, electronics, speech recognition, voice synthesis, computer vision, game design, web development, and physical computing. Rather than building a single game, I set out to create an arcade experience that feels alive, interactive, and rewarding every time a player walks up to the machine.

In this Instructable, I'll show exactly how I designed, programmed, tested, and assembled Arcade Legends from the ground up.

Supplies

IMG_4809.jpeg
56149F0D-8A8C-4B66-9A59-A9E66AB838CB.JPG
7217090F-78B2-406C-89FC-33348B237E66.JPG
DE797761-F9E3-4C8C-890E-BABD6557BE4C.JPG
B906A8B8-E6E3-4F46-B3F5-A8B05C71B48F.JPG
1A9A1842-1F73-4837-99C6-7224DD5C9AA6.JPG
F8A16277-10E4-4BA9-8757-BA81F2DAFA6A.JPG
9F4BC7F3-B650-4DD3-BC74-7DD8E2AA63AB.JPG
3BF1DC6D-B6A6-47CB-A1AD-42F2B454E340.JPG
FFCC9D6F-8CF0-41B4-AC41-82DF63423412.JPG
39D3D70D-1726-46AE-B4FE-DFFD2459C11F.JPG
IMG_4978 (1).jpeg
IMG_4977 (1).jpeg
IMG_4976 (1).jpeg
IMG_4975 (1).jpeg
IMG_4974 (1).jpeg
šŸ”„ I Spent 6 Months Building the Ultimate Raspberry Pi Arcade

Raspberry Pi 5

Camera

LEDs(x5)

RFID Card & Reader

Raspberry Pi 5 Case

Active & Passive Buzzer

LCD1602 Screen

MFRC-522 RFID Reader

Wires(x44)

Push Button (Red Cap)

Resistors(x10)

Power Source

Electrical Tape

Raspberry Pi GPIO Extension Board

Project Goals & Design Requirements

arcade.jpg
red.jpg
gam.jpg
lmem.jpg
Screenshot 2026-10-25 155954.png
Screenshot 2026-10-25 160004.png
Screenshot 2026-10-25 160016.png
Screenshot 2026-10-25 160136.png
Screenshot 2026-10-25 160149.png
IMG_4971 (1).jpeg
IMG_4973 (1).jpeg
Screenshot 2026-10-25 160741.png
Screenshot 2026-10-25 160821.png
šŸ”„ I Spent 6 Months Building the Ultimate Raspberry Pi Arcade

I've always loved arcade games.

Whether it was chasing a high score, competing with friends, or simply trying to beat my previous record, there was something exciting about walking up to a machine and knowing that every game was a new challenge. As I became more interested in electronics and programming, I started wondering what it would be like to build my own arcade machine from scratch.

At first, the idea was surprisingly simple. I wanted to create a small reaction game using a few LEDs and a Raspberry Pi. However, the more I experimented, the more possibilities I discovered. One game quickly turned into several games. A simple score counter became an XP System. A basic login idea evolved into RFID Player Authentication. An ordinary camera became a Winner Photography System. Before long, I wasn't building a game anymore.

I was building an entire arcade platform.

From the beginning, I wanted Arcade Legends to feel different from most DIY arcade projects. I didn't want players to simply start a game, get a score, and walk away. I wanted the machine to remember who they were. I wanted players to have personal profiles, achievements, levels, records, and a place in the Hall Of Fame. Every victory should matter. Every achievement should be saved. Every player should have a reason to come back and improve.

I began sketching ideas, drawing diagrams, and writing lists of features. Some ideas worked immediately. Others seemed impossible at first. Looking back, many of my favourite features weren't part of the original plan at all. They appeared during development as the project continued to grow.

What started as a small Raspberry Pi experiment eventually became Arcade Legends, a system combining electronics, Python programming, speech recognition, RFID technology, computer vision, web development, physical gaming, and retro arcade experiences into a single machine.

At this point, nothing had been built yet. There were no LEDs flashing, no profiles being loaded, and no games running. There was only an idea and a goal:

Build an arcade machine that people would genuinely want to play again and again.

Everything that follows in this Instructable grew from that single idea. šŸ†šŸŽ®šŸ”„

Planning the Arcade Legends Architecture

Screenshot 2026-10-25 181745.png
Arcade Legends wiring diagram (1).png
Arcade Legends wiring diagram.png
šŸ”„ I Spent 6 Months Building the Ultimate Raspberry Pi Arcade

Once I had decided what I wanted Arcade Legends to become, the next challenge was figuring out how all of the pieces would work together. It quickly became obvious that this was not going to be a simple project. By this point, I already wanted to include RFID authentication, player profiles, multiple physical arcade games, retro desktop games, an LCD interface, sound effects, voice feedback, winner photography, a Hall of Fame, and a complete web-based command centre. If I started building without a plan, the project would quickly become impossible to manage.

To solve this problem, I began drawing diagrams and mapping out the flow of information throughout the system. At the center of everything was the Raspberry Pi, which would act as the brain of the entire arcade. Every component would communicate with it in some way. The RC522 RFID reader would identify players. The LCD1602 display would provide feedback and instructions. The LEDs, buzzer, and buttons would be used by the physical arcade games. The Pi Camera would capture winner photographs. The ultrasonic sensor would detect approaching players and wake the machine automatically. Finally, a Flask-based dashboard would provide a visual interface for launching games, viewing leaderboards, and managing the arcade.

One of the most important decisions I made during this stage was to separate the project into independent systems rather than trying to write one giant Python file. Every major feature would have its own program. For example, Simon Speaks, Reaction Master, Whack-A-Mole, LED Roulette, and Stop-The-Light would each have their own dedicated game file. This approach made development significantly easier because I could work on one feature without accidentally breaking another.

I also designed the project around the idea of persistent progression. A player's journey should not end when the machine is powered off. Because of this, every major action needed to interact with profile data stored on the Raspberry Pi. Games would award points, points would contribute toward XP, XP would increase levels, achievements would be unlocked, winners would appear in the Hall of Fame, and all of this information would be saved for future sessions. Planning these relationships ahead of time prevented many software design problems later in development.

Looking back, this planning phase saved an enormous amount of time. It allowed me to identify potential problems before writing code and gave me a clear roadmap for development. While the arcade still existed only on paper at this stage, the foundation for everything that followed was already in place.

Before I built Arcade Legends, I first had to build a blueprint for how the entire arcade ecosystem would function. šŸŽ®šŸ†

Selecting the Components

IMG_4806.jpeg
IMG_4808.jpeg
F8A16277-10E4-4BA9-8757-BA81F2DAFA6A.JPG
9F4BC7F3-B650-4DD3-BC74-7DD8E2AA63AB.JPG
3BF1DC6D-B6A6-47CB-A1AD-42F2B454E340.JPG
7D9BCDAD-AD07-4C76-84B4-52E9528A743A.JPG
šŸ”„ I Spent 6 Months Building the Ultimate Raspberry Pi Arcade

With the overall architecture planned out, the next step was choosing the hardware that would power Arcade Legends. This turned out to be much more important than I initially expected. Since the project was designed to combine physical arcade games, player authentication, speech recognition, achievement tracking, winner photography, and an online Arcade Command Centre, every component needed to serve a specific purpose rather than simply filling space on a breadboard.

At the heart of the project is a Raspberry Pi, which acts as the central controller for the entire arcade. All of the games, player profiles, dashboard systems, achievement tracking, and camera features run through it. I chose the Raspberry Pi because it provided enough processing power to handle multiple systems simultaneously while also offering direct access to GPIO pins for physical hardware interaction.

To create the game controls, I selected five LEDs, a push button, and a buzzer. These three components form the foundation of many of the physical arcade games. The LEDs provide visual cues, targets, and animations, while the button acts as the primary input device for reaction-based challenges. The buzzer provides immediate feedback whenever a player succeeds, fails, unlocks an achievement, or completes a challenge. Although these components may seem simple, they are responsible for most of the physical interaction in the arcade.

One of the most important components in the entire project is the RC522 RFID Reader. Rather than creating a traditional login screen, I wanted players to authenticate themselves using physical RFID cards. Each card acts as a unique identity token, allowing the system to load player statistics, achievements, levels, and game history automatically. This single component transformed Arcade Legends from a collection of games into a platform capable of remembering its players.

For player feedback, I chose an LCD1602 Display. While modern monitors are capable of displaying everything, I wanted a dedicated status screen that could provide instructions, scores, notifications, achievement unlocks, and profile information independently of the main arcade interface. Throughout development, this display became one of the most frequently used components because it provides constant feedback without requiring players to look at a separate monitor.

To give the arcade awareness of its surroundings, I integrated an HC-SR04 Ultrasonic Distance Sensor. Rather than requiring players to manually wake the machine, the ultrasonic sensor allows Arcade Legends to detect approaching users automatically. This makes the arcade feel more interactive and alive because it can react to a player's presence before they even start a game.

Another feature I wanted from the beginning was voice interaction. To achieve this, I incorporated a USB microphone for speech recognition and a speaker system for audio output. The microphone is heavily used in Simon Speaks, where players must verbally repeat colour sequences shown by the LEDs. Implementing speech recognition introduced additional challenges later in development, particularly when dealing with background noise and varying speech patterns, but it ultimately became one of the most unique features of the project.

For winner tracking and champion records, I integrated a Pi Camera Module. Initially, the camera was simply intended for experimentation, but it eventually became the foundation of the Winner Photography System and Hall Of Fame. Whenever a player achieved something noteworthy, the system could capture and store photographic records, allowing the arcade to celebrate achievements visually rather than simply storing numbers in a file.

Throughout the component selection process, I followed a simple rule:

Every component needed to enhance the player experience.

If a part didn't contribute to gameplay, progression, immersion, feedback, or user interaction, it didn't belong in Arcade Legends. By carefully selecting components with clear purposes, I ensured that every piece of hardware contributed to the larger vision rather than becoming unnecessary complexity.

Looking back, this stage established the foundation for everything that followed. Every achievement unlocked, every profile loaded, every photograph captured, and every game played would ultimately depend on the collection of components chosen during this phase of the project.

Wiring the Hardware

Screenshot 2026-10-25 093143.png
Arcade Legends wiring diagram.png
IMG_4972.jpeg
IMG_4971.jpeg
IMG_4978.jpeg
IMG_4977.jpeg
IMG_4976.jpeg
IMG_4975.jpeg
IMG_4974.jpeg
IMG_4973.jpeg
šŸ”„ I Spent 6 Months Building the Ultimate Raspberry Pi Arcade

With the planning complete and all of the components selected, it was finally time to start building the physical arcade itself. This was the point where Arcade Legends began transitioning from sketches, diagrams, and ideas into an actual working machine.

I decided very early on that I would not connect everything at once. With multiple LEDs, buttons, sensors, RFID hardware, a camera, an LCD display, a buzzer, and various supporting modules, attempting to wire the entire system in a single session would have made troubleshooting almost impossible. Instead, I adopted a modular approach, connecting and verifying one subsystem at a time before moving on to the next.

The first hardware connected to the Raspberry Pi consisted of the arcade game controls. Five LEDs were connected to GPIO pins 23, 12, 16, 20, and 21, while the primary arcade button was connected to GPIO 5 and the buzzer was connected to GPIO 4. These components would eventually become the foundation of several physical games, including Simon Speaks, Reaction Master, Whack-A-Mole, LED Roulette, and Stop-The-Light.

Once the basic arcade controls were connected, I began adding the supporting systems. The RC522 RFID reader was connected through the SPI interface, allowing players to authenticate themselves using RFID cards. The LCD1602 display was added to provide real-time instructions and status updates. The HC-SR04 ultrasonic sensor was wired to detect approaching players, while the Pi Camera Module was connected through the CSI interface to support winner photography and Hall Of Fame functionality.

Although the wiring itself seems straightforward when looking at the finished project, the process involved a significant amount of troubleshooting. Several issues appeared throughout development. One particularly memorable mistake occurred while relocating components on the breadboard. During a late-night wiring session, I accidentally reversed a VCC and GND connection. Within seconds I noticed the smell of overheating electronics and immediately disconnected power. Fortunately, the mistake was caught before any permanent damage occurred, but it reinforced the importance of verifying every connection before energizing a circuit.

Loose jumper wires also became a recurring source of problems. On several occasions, components appeared to be malfunctioning when the actual issue was simply an unreliable breadboard connection. The LCD occasionally displayed corrupted characters, the RFID reader sometimes failed to detect cards, and LEDs would occasionally flicker unexpectedly. In almost every case the root cause was eventually traced back to wiring rather than software.

To minimize future problems, I started creating wiring diagrams and documenting GPIO assignments as development progressed. Maintaining a clear record of which component was connected to each pin proved incredibly useful later when integrating multiple systems together. Without this documentation, adding new hardware would have become increasingly difficult as the project expanded.

By the end of this phase, the Raspberry Pi was connected to all of the major hardware systems required for Arcade Legends. While none of the advanced software features existed yet, the physical foundation of the arcade was finally in place. The machine could now interact with the outside world through lights, sounds, sensors, communication devices, and user input, making it ready for the next stage of development: testing every component individually before combining them into a larger system.

The RFID reader and LCD1602 were connected using the Raspberry Pi's SPI and I²C interfaces respectively. The Pi Camera was connected through the dedicated CSI camera port, allowing Arcade Legends to capture profile photos, winner photographs, and Hall Of Fame images without requiring any additional USB camera hardware.

A project can have perfect software, but if the hardware is unreliable, the entire experience falls apart. Getting the wiring right was one of the most important investments I made during the development of Arcade Legends. šŸ†


art of a player's history.

Testing & Validating Each Component

šŸ”„ I Spent 6 Months Building the Ultimate Raspberry Pi Arcade

With all of the hardware connected, I deliberately resisted the temptation to jump straight into game development. While it was exciting to see everything wired together, I knew that building software on top of untested hardware would almost certainly create problems later. Instead, I spent a significant amount of time testing every component individually before attempting to integrate them into the larger Arcade Legends ecosystem.

I started with the simplest devices: the LEDs, button, and buzzer. Using the gpiozero library, I wrote a series of small Python scripts to verify that each GPIO pin was functioning correctly. Every LED was tested individually to ensure it illuminated when expected, and the buzzer was used to generate confirmation sounds whenever a test completed successfully. Although the tests themselves were simple, they confirmed that the wiring matched the GPIO assignments in my planning documents.

The next stage involved validating the input systems. The arcade uses a physical push button extensively throughout games such as Reaction Master, Stop-The-Light, Whack-A-Mole, and LED Roulette, so reliability was critical. I created small test programs that continuously monitored button presses and displayed the results in the terminal. During this phase I discovered that accidental multiple triggers could occur if a button was held for too long, prompting me to adjust portions of my game logic later to account for this behaviour.

Once the basic arcade controls were functioning correctly, I moved on to the LCD1602 display. Using a custom LCD1602 library, I tested character output, menu screens, and scrolling messages. Initially, the display occasionally showed corrupted characters or incomplete text. After several frustrating hours of debugging, I eventually traced the problem back to loose jumper wires rather than software. Replacing a few unreliable connections solved the issue completely, highlighting an important lesson that would repeat throughout the project: not every bug originates from code.

The RC522 RFID reader required another round of dedicated testing. Using the pirc522 library, I wrote a script that continuously scanned RFID cards and printed their unique identifiers to the terminal. At first, card detection appeared inconsistent. Some cards worked immediately while others seemed to fail entirely. After careful investigation, I discovered that the issue was actually related to positioning and communication stability. Once I adjusted the wiring and placement of the module, card detection became extremely reliable and laid the groundwork for the player authentication system.

One of the more challenging components to test was the HC-SR04 ultrasonic sensor. Because the sensor would later be responsible for detecting approaching players and waking up the arcade automatically, I needed distance measurements to be consistent. Using Python and the gpiozero DistanceSensor class, I measured how the sensor behaved at various distances. During testing I discovered that reflective surfaces and unusual object angles occasionally produced inaccurate readings. To make the system more reliable, I eventually incorporated thresholds and repeated measurements rather than relying on a single sensor reading.

The audio subsystem was also tested independently. Since a major feature of Simon Speaks relies on speech recognition, I needed to confirm that the microphone could consistently capture voice input. Using the speech_recognition library, I experimented with different microphone positions, speaking volumes, and environmental conditions. One of the biggest problems I encountered was ambient noise. Even relatively quiet rooms could affect recognition accuracy. To improve performance, I implemented:

Python:

recognizer.adjust_for_ambient_noise(source,duration=0.5)

This calibration process significantly improved recognition accuracy and eventually became a permanent part of the Simon Speaks workflow.

The Pi Camera Module also underwent extensive validation. Before integrating it into the Hall Of Fame system, I tested image capture, image storage, and file management. During these tests, the camera occasionally failed to initialize. After checking software settings, I eventually discovered that the ribbon cable was not fully seated in the CSI connector. Reseating the cable immediately resolved the issue.

Not every test ended successfully. In fact, some of the most valuable lessons came from mistakes. One particularly memorable incident occurred while reconfiguring part of the wiring. I accidentally reversed a power and ground connection during testing. Within moments I noticed a faint burning smell and immediately disconnected power. Thankfully, I reacted quickly enough to prevent permanent damage, but the experience reinforced the importance of checking every connection before powering a circuit.

By the end of this stage, every major component had been validated independently. The LEDs responded correctly, the button registered input reliably, the buzzer provided feedback, the LCD displayed information properly, RFID authentication worked consistently, the ultrasonic sensor could detect approaching players, the microphone could process spoken commands, and the camera could capture images successfully.

Most importantly, I now had confidence that any future problems would likely originate from software rather than hardware. That confidence became invaluable as the project grew into thousands of lines of code, multiple games, a profile system, a Hall Of Fame, and the complete Arcade Legends platform.

Testing each component individually may have slowed development in the short term, but it saved countless hours of debugging later. Before Arcade Legends could become a complete system, every individual piece first had to prove that it could stand on its own. šŸ†

Setting Up the Rasberry Pi and Software Environment

šŸ”„ I Spent 6 Months Building the Ultimate Raspberry Pi Arcade

With the hardware tested and functioning reliably, it was finally time to build the software foundation that would power Arcade Legends. Although the LEDs, sensors, RFID reader, LCD display, and camera were now connected, they were essentially just individual pieces of hardware with no way of working together. Before I could create games, profiles, achievements, or the dashboard, I first needed to configure a software environment capable of controlling every part of the arcade.

I started by installing Raspberry Pi OS and updating the system to ensure all packages were running the latest versions. One lesson I learned early on was that many mysterious bugs are caused by outdated packages, missing dependencies, or incompatible library versions rather than actual programming errors. Taking the time to fully configure the system at the beginning prevented a lot of frustration later in development.

Once the operating system was configured, I installed the Python libraries that would become the backbone of the project. For hardware control, I used gpiozero, which provided simple access to LEDs, buttons, buzzers, and sensors. Almost every physical game in Arcade Legends relies heavily on this library. For RFID authentication, I installed pirc522, allowing the RC522 reader to communicate through the Raspberry Pi's SPI interface. To control the LCD display, I developed and modified my own LCD1602 library, enabling me to display menus, scores, instructions, achievement notifications, and profile information.

One of the most interesting areas of the project involved voice interaction. To make Simon Speaks possible, I installed and configured SpeechRecognition, which allows Python to convert spoken words into text. Since speech recognition accuracy can be heavily affected by environmental noise, I spent considerable time testing microphones, adjusting volume levels, and experimenting with recognition settings. This process later led to the use of automatic ambient-noise calibration before every recognition attempt.

To allow the arcade to speak back to the player, I also integrated edge-tts, Microsoft's text-to-speech engine. This enabled the machine to generate dynamic voice announcements rather than relying on pre-recorded audio clips. Combined with asyncio, the arcade could generate and play speech efficiently without blocking other parts of the program.

As the project expanded, I quickly realized I needed a way to store player information permanently. Instead of introducing a complex database system, I chose a simpler approach using Python's built-in json and file handling capabilities. This allowed player profiles, achievements, XP, levels, and leaderboards to be saved directly to text and JSON files. The simplicity of this approach made debugging easier and reduced system complexity while still providing everything the project needed.

Another major milestone was setting up Flask, the framework that powers the entire Arcade Legends Command Centre. This decision fundamentally changed the project. Rather than displaying information only on the LCD screen, Flask allowed me to create a complete browser-based interface featuring live leaderboards, champion displays, winner photographs, game launchers, Hall of Fame pages, and player statistics. Looking back, choosing Flask was one of the most important software decisions I made because it transformed the arcade from a collection of games into a connected ecosystem.

At this stage, my project directory was already beginning to grow significantly. Separate folders were created for profiles, winners, profile_photos, audio files, game assets, and various Python modules. Although it may have seemed excessive at the time, staying organized from the beginning made it much easier to manage what would eventually become thousands of lines of code spread across dozens of files.

By the end of this step, Arcade Legends finally had a solid software foundation. The Raspberry Pi could communicate with hardware, identify players, store data, process speech, generate voice feedback, launch games, capture images, and serve web pages. For the first time, the project started to feel less like a collection of components and more like the beginnings of a real arcade platform.

Hardware gave Arcade Legends its body, but software gave it a personality. From this point onward, every new feature would build upon the foundation created in this step. šŸŽ®šŸ†

Building the RFID Authentication System

šŸ”„ I Spent 6 Months Building the Ultimate Raspberry Pi Arcade

One of the first major systems I built for Arcade Legends was the RFID Authentication System. While many DIY arcade projects treat every player as the same user, I wanted Arcade Legends to recognize returning players automatically and load their personal progress, achievements, XP, level, statistics, and Hall Of Fame history.

Rather than forcing players to type usernames and passwords, I decided to use an RC522 RFID Reader and a collection of RFID cards. Every card would act as a digital identity. When a player taps their card against the reader, the arcade instantly identifies them and loads their profile.

To communicate with the RC522 module, I used the pirc522 Python library. Before integrating it into the arcade, I created a small test program whose only purpose was to verify that I could reliably read the UID (Unique Identifier) stored on each card.


Python:

from pirc522 import RFID


rdr = RFID()


while True:

rdr.wait_for_tag()


(error, tag_type) = rdr.request()


if not error:

(error, uid) = rdr.anticoll()


if not error:

print(uid)

The first few successful scans were exciting because they proved that the Raspberry Pi could uniquely distinguish between different RFID cards.

When a card is scanned, the RC522 returns a UID similar to:

[168, 42, 199, 103]

Each card produces a unique combination of numbers. I used this UID as the player's identity throughout the system.

At first, I simply displayed UIDs in the terminal, but this wasn't useful for actual players. To solve this, I created a simple user database where each RFID card could be linked to a username.

Example:

168-42-199-103 = Shivansh

92-16-44-201 = Guest

The authentication logic worked as follows:

  1. Wait for RFID card.
  2. Read UID.
  3. Search user database.
  4. If UID exists:
  5. Load profile.
  6. If UID does not exist:
  7. Create new account.
  8. Register user.
  9. Save details.

This transformed the RFID system from a card scanner into a complete authentication platform.

One challenge I encountered involved false reads and multiple detections. During early testing, if a player left their card on the reader for several seconds, the program would continuously detect the same card over and over again. This resulted in duplicate login events and unnecessary profile loading operations.

To solve this, I added timing controls so that recently scanned cards would be ignored temporarily until removed and scanned again. This ensured that one physical tap always resulted in exactly one login.

Another issue involved unstable SPI communication. Occasionally the reader would fail to detect a card even when it was physically touching the module. After checking my code for hours, I eventually discovered that the problem was not software at all. One of the jumper wires connected to the SPI interface had become loose. Re-seating the connection immediately solved the problem. Experiences like this taught me that debugging hardware often requires investigating both the code and the wiring simultaneously.

Once authentication was functioning properly, I integrated it with the LCD1602 display. This dramatically improved the user experience.

Instead of seeing information in a terminal window, players were greeted with messages such as:

SCAN RFID

and

WELCOME

SHIVANSH

For the first time, the arcade felt personal.

The moment a card was scanned, the machine knew exactly who was standing in front of it. This single feature became the foundation of almost every advanced system that followed. Without RFID authentication, there would be no way to connect players to their XP, achievements, Hall Of Fame photos, rankings, or game history.

Looking back, this was the moment when Arcade Legends stopped being a collection of games and started becoming a true arcade platform.

Every achievement, every level gained, every Hall Of Fame photo, and every leaderboard entry begins with a simple RFID scan. šŸ†šŸŽ®


Creating Persistent Player Profiles

šŸ”„ I Spent 6 Months Building the Ultimate Raspberry Pi Arcade

With the RFID Authentication System working reliably, the next challenge was giving each player a place to store their progress. At this stage, the arcade could identify who was scanning in, but once the game ended there was still nowhere to save scores, achievements, XP, levels, or statistics. Without a profile system, every player would essentially be starting from the beginning each time they returned.

I decided to keep the system simple and lightweight by using individual profile files rather than a traditional database. Every player would have their own file stored inside a dedicated profiles folder. This approach had several advantages. It was easy to read, easy to debug, easy to back up, and most importantly, it allowed me to inspect player data directly without requiring any additional software.

When a new RFID card was scanned for the first time, the arcade automatically generated a new profile file. Each profile was linked directly to the player's username, allowing information to be stored permanently between gaming sessions.

A simplified version of the profile creation process looked like this:


Python

with open(f"profiles/{username}.txt","w") as file:


file.write("0\n") # XP

file.write("1\n") # Level


When a returning player scanned their RFID card, the arcade would automatically load the stored profile information.


Python:

with open( f"profiles/{username}.txt") as file:


xp = int(file.readline())

level = int(file.readline())


This simple structure became the foundation for almost every progression-based feature in Arcade Legends.

An example profile file looked like:

12580

14

MEMORY PRO

MEMORY MASTER

REACTION KING

The first line stored the player's XP.

The second line stored the player's current level.

Every line after that represented an unlocked achievement.

This design allowed me to add new achievements without changing the structure of the program.

To link RFID cards to player accounts, I created a separate user database that stored mappings between card UIDs and usernames.

Example:

168-42-199-103,Shivansh

84-16-77-201,Guest

Whenever an RFID card was scanned, the system searched this file for a matching UID. If a match was found, the corresponding profile was loaded automatically. If no match existed, the player registration process would begin.

One challenge I encountered early on was data loss. During testing, players would earn XP, complete games, and unlock achievements successfully, but after restarting the program their progress would vanish. After investigating the issue, I realized I had only implemented profile loading and had forgotten to save updated values after modifications were made.

To solve this problem, I created a dedicated profile-saving function that would execute whenever important events occurred.


Python:

def SaveProfile():


with open(f"profiles/{current_user}.txt","w") as file:


file.write(str(xp)+"\n")

file.write(str(level)+"\n")


From that point forward, profile updates became permanent.

Another problem emerged as the project became more complex. Since achievements, XP, Hall Of Fame records, and leaderboard rankings were all connected to profile information, a corrupted profile file could potentially affect several systems simultaneously. To minimize this risk, I added validation checks and default values whenever profile data was loaded.

As soon as profile storage was functioning reliably, I integrated it with the LCD1602 Display.

Instead of simply showing generic messages, the arcade could now display:

WELCOME

SHIVANSH

or

LEVEL 14

12580 XP

This small feature had a surprisingly large impact. For the first time, players were not interacting with a machine. They were interacting with their machine, complete with their own history, progress, and accomplishments.

Looking back, the profile system was one of the most important milestones in the entire project. The RFID system could identify players, but the profile system gave those players a reason to return. Every later feature, including the XP System, Achievement System, Leaderboards, Champion Tracking, and Hall Of Fame, would ultimately depend on the foundation created here.

The RFID reader tells Arcade Legends who the player is. The profile system tells Arcade Legends who that player has become. šŸ†šŸŽ®


Designing the LCD User Interface

šŸ”„ I Spent 6 Months Building the Ultimate Raspberry Pi Arcade

With RFID authentication and player profiles now working, I encountered a new problem. The Raspberry Pi could identify players, load profiles, and store progress, but almost all of the information was being displayed inside the Python terminal. While this worked for development, it completely broke the arcade experience. Players shouldn't need to look at code or terminal output to understand what is happening. The machine itself should communicate directly with them.

To solve this, I added an LCD1602 display to the project and began designing a dedicated user interface for the arcade. The display would act as the machine's voice, constantly informing players about what was happening. Whether a player was scanning an RFID card, starting a game, earning XP, unlocking an achievement, or receiving instructions, the LCD would always provide immediate feedback.

To control the display, I created and modified a custom LCD1602 Python library and built a helper function called:

Python:


LCDOutput(column,row,text)


This allowed me to display messages anywhere on the screen without having to repeatedly write low-level LCD commands throughout the project.

For example:

Python:

LCDOutput(0,0,"SCAN RFID")

LCDOutput(0,1,"TO LOGIN")

would display:

SCAN RFID

TO LOGIN

As development progressed, the LCD quickly became one of the most heavily used components in the entire arcade. Almost every game relied on it.

When a player scanned their RFID card:

WELCOME

SHIVANSH

When Simon Speaks was waiting for input:

YOUR TURN

SPEAK NOW

When a player unlocked an achievement:

ACHIEVEMENT

MEMORY PRO

When a game ended:

GAME OVER

TRY AGAIN

One of the biggest design goals for the LCD system was reducing confusion. During early testing, I noticed that players often didn't know what they were supposed to do next. Were they supposed to scan an RFID card? Press a button? Speak into the microphone? Select a game? The LCD solved this problem by providing constant guidance throughout the entire arcade experience.

Another challenge involved message length. Since the LCD1602 only provides 16 characters per row, many of the messages I wanted to display were simply too long. Achievement names, usernames, game titles, and instructions frequently exceeded the available space.

For example:

REACTION MASTER

contains more than 16 characters.

To solve this problem, I shortened some messages and divided others across two lines:

REACTION

MASTER

This small change significantly improved readability.

During development, I also encountered occasional display corruption. Random characters would sometimes appear, text would overlap, or messages would fail to update completely. Initially I spent hours searching through my Python code looking for bugs. Eventually I discovered the real problem was a loose connection on the breadboard. After securing the wiring, the LCD became completely stable.

One feature I particularly liked was integrating the LCD into the game logic itself. Rather than simply displaying static information, it became part of the gameplay experience.

In Simon Speaks, the display announces colours, instructions, scores, and achievements.

In Reaction Master, it can show reaction times and performance rankings.

In the authentication system, it welcomes players and displays profile information.

In the achievement system, it becomes a reward mechanism that celebrates milestones.

By the time this stage was complete, the LCD had evolved far beyond a simple display. It had become the primary communication channel between the player and Arcade Legends. Almost every action performed by the arcade resulted in some form of LCD feedback, helping transform the project from a collection of electronics into something that felt like a real arcade machine.

The LEDs provided excitement, the buzzer provided feedback, but the LCD gave Arcade Legends a way to communicate directly with its players. šŸ†šŸŽ®


Implementing Automatic Player Detection

šŸ”„ I Spent 6 Months Building the Ultimate Raspberry Pi Arcade

At this point, Arcade Legends could authenticate players, load profiles, and display information on the LCD. However, there was still one problem: the arcade felt passive. It just sat there waiting for someone to interact with it. Real arcade machines demand attention. They flash lights, play sounds, and immediately let you know they're ready to be played. I wanted Arcade Legends to have that same sense of presence.

To achieve this, I added an HC-SR04 Ultrasonic Distance Sensor. Rather than requiring users to wake the machine manually, the sensor allows Arcade Legends to detect when somebody approaches and automatically transition into an active state. This makes the system feel much more alive because it is constantly aware of its surroundings.

Using the gpiozero library, I configured the sensor through the DistanceSensor class and continuously measured the distance between the arcade and nearby objects.

A simplified version of the detection system is shown below:


Python:


from gpiozero import DistanceSensor


sensor = DistanceSensor(echo=24,trigger=25)


while True:


distance = sensor.distance


if distance < 0.5:

print("Player Detected")

The sensor returns a distance value in meters, allowing me to determine whether somebody is standing close enough to interact with the machine.

Initially, I assumed this feature would be straightforward. In reality, it required a surprising amount of experimentation. During testing, I discovered that ultrasonic sensors do not always behave perfectly. Certain object angles produced inconsistent readings, and occasionally the sensor would report brief spikes that caused false detections.

For example, a single bad reading could cause the arcade to incorrectly assume that a player had arrived. To prevent this, I avoided relying on one measurement alone. Instead, I required multiple successful detections before triggering the wake-up sequence. This simple change dramatically improved reliability.

Once the detection logic was working properly, I integrated it with the LCD display and feedback systems. Whenever somebody approached the arcade, the machine could automatically display messages such as:

WELCOME

SCAN RFID

or:

PLAYER

DETECTED

This immediately made the project feel more professional. Instead of acting like a program running on a Raspberry Pi, Arcade Legends began behaving like an actual arcade cabinet that was aware of its environment.

I also experimented with combining player detection and lighting effects. When the ultrasonic sensor detected movement within the activation range, LEDs could illuminate and the arcade could begin displaying welcome messages. This created a much more engaging first impression for new players.

One unexpected benefit of the ultrasonic system was power and resource management. Since many parts of Arcade Legends involve animations, displays, and user interaction, it made sense to keep certain visual elements idle when nobody was nearby. The sensor provided a simple way to distinguish between idle periods and active gameplay periods.

Looking back, the ultrasonic sensor may seem like a relatively small addition compared to features such as RFID authentication, speech recognition, or the Hall Of Fame. However, it played an important role in making Arcade Legends feel like a complete experience. It was one of the first features that allowed the machine to react to the real world rather than simply waiting for input.

For the first time, the arcade could notice when someone approached it, prepare itself for interaction, and invite a player to begin their journey.

The RFID reader allowed Arcade Legends to recognize players. The ultrasonic sensor allowed Arcade Legends to recognize that a player was even there. šŸ†šŸŽ®


Building the Arcade Audio & Feedback System

šŸ”„ I Spent 6 Months Building the Ultimate Raspberry Pi Arcade

As Arcade Legends continued to grow, I noticed an important problem. While the LEDs and LCD display provided visual feedback, there was nothing that gave the machine an audible personality. Players could see what was happening, but they couldn't hear it. Real arcade machines are constantly providing sounds, jingles, alerts, and rewards. The sounds are often just as important as the gameplay itself. I wanted Arcade Legends to capture that same feeling.

To accomplish this, I connected a buzzer to GPIO 4 and began experimenting with ways of using sound throughout the arcade. Initially, the buzzer was only used to indicate simple events such as button presses or game over states. However, as development progressed, it became a core part of almost every game.

Using the gpiozero library, controlling the buzzer was extremely simple:


Python:


from gpiozero import Buzzer

buzzer = Buzzer(4)

buzzer.on()

time.sleep(0.1)

buzzer.off()


Although this small piece of code only generates a short beep, combining it with LEDs, scoring systems, achievements, and game events made the arcade feel significantly more responsive.

One of the first things I learned was that feedback needs to be immediate. If a player presses a button and nothing happens for even half a second, the machine feels unresponsive. Because of this, successful actions almost always trigger an instant buzzer response.

For example, in Whack-A-Mole, a successful hit generates a confirmation sound:


Python:


buzzer.on()

sleep(0.05)

buzzer.off()


This tiny sound effect gives the player immediate confirmation that their reaction was successful.

A similar approach was used in Reaction Master. When the target LED appears, the buzzer produces a short signal indicating that the player should react. This creates a much more engaging experience than relying solely on visual indicators.


Python:


leds[target].on()


buzzer.on()

sleep(0.05)

buzzer.off()


The result feels more like a commercial arcade machine because the player receives both visual and auditory information at the same time.

I also wanted the buzzer to communicate failure. Losing a life should sound different from winning points. Unlocking an achievement should sound different from losing a game. Throughout development I experimented with different patterns, durations, and sequences of sounds until important events became instantly recognizable.

One of my favourite examples appears in Simon Speaks. When a player successfully repeats a sequence, the game executes a celebration routine:


Python:

for led in leds:

led.on()

buzzer.on()

time.sleep(0.1)

buzzer.off()

for led in leds:

led.off()

This combines flashing lights with audio feedback to create a satisfying reward sequence.

For major milestones such as achievements, I introduced larger celebration effects. When players unlock awards such as MEMORY PRO or MEMORY MASTER, the system flashes all LEDs repeatedly while generating accompanying sound effects. These moments help transform achievements from simple text messages into memorable events.

During development I experimented with much longer sounds and continuous tones. However, testing revealed that long buzzes quickly became annoying, especially when repeated during gameplay. After multiple revisions, I found that short, distinct sounds worked much better because they delivered information quickly without becoming distracting.

Another important design principle emerged during testing:

Every sound should have a meaning.

Players should eventually be able to recognize success, failure, level progression, achievements, warnings, and notifications without even looking at the display.

As the project expanded, the audio system also became closely tied to the visual systems. Almost every major event now affects multiple outputs simultaneously. An achievement might trigger:

  1. LCD notification
  2. LED animation
  3. Audio feedback
  4. XP reward

Similarly, a failed challenge might trigger:

  1. Game Over message
  2. Red LED pattern
  3. Failure tone

By combining multiple feedback systems together, the machine feels significantly more responsive and interactive.

Although a buzzer may seem like a small component compared to features such as RFID authentication, speech recognition, or the Hall Of Fame, it plays an important role in shaping how the arcade feels to use. Many of the most satisfying moments in Arcade Legends become much less impactful when sound is removed.

By the end of this stage, Arcade Legends could finally do more than just show information. It could react, celebrate, warn, and communicate through sound, making the entire experience feel far more immersive and much closer to a real arcade machine.

The LEDs gave Arcade Legends excitement. The LCD gave Arcade Legends a voice. The buzzer gave Arcade Legends a personality. šŸ†šŸŽ®

Developing Simon Speaks

šŸ”„ I Spent 6 Months Building the Ultimate Raspberry Pi Arcade

With the authentication system, profile management, sensors, LCD interface, and hardware controls complete, I finally began working on the first major original game in Arcade Legends: Simon Speaks.

Most people are familiar with Simon Says, where players repeat a sequence by pressing coloured buttons. While designing the arcade, I wanted to create something that felt more unique. Since the project already included a microphone and speech processing capabilities, I began experimenting with the idea of combining a traditional memory game with voice recognition. Instead of pressing buttons to repeat a sequence, players would have to speak the colours aloud from memory.

This immediately transformed the challenge. Players were no longer testing only their memory. They were also testing concentration, pronunciation, reaction speed, and the ability to recall increasingly complex sequences under pressure.

The game was built using several core hardware components:

  1. 5 LEDs
  2. USB Microphone
  3. LCD1602 Display
  4. Buzzer
  5. Raspberry Pi

The LEDs were connected to:

GPIO 23 -> Red LED

GPIO 12 -> Green LED

GPIO 16 -> Yellow LED

GPIO 20 -> White LED

GPIO 21 -> Blue LED

The software relied primarily on these libraries:


Python:

gpiozero

speech_recognition

random

time


For speech recognition, I initialized the recognizer using:


Python:

recognizer = sr.Recognizer()

mic = sr.Microphone(device_index=0)


One of the first challenges I encountered was environmental noise. During testing, even small sounds such as moving chairs, conversations in another room, or a running fan could dramatically reduce recognition accuracy.

To improve reliability, I implemented automatic ambient noise calibration before each recording session:


Python:

recognizer.adjust_for_ambient_noise(source,duration=0.5)


This allows the recognizer to measure background noise levels and compensate for them automatically before listening for player input.

Without this single line of code, recognition accuracy was inconsistent.

The game begins by displaying:


SIMON SPEAKS

GET READY


The LCD then transitions to the current level:


LEVEL 1

WATCH LEDs


Internally, the game stores a sequence list:


Python:

sequence = []


At the start of every round, a new random LED is added:

Python:

sequence.append(random.randint(0,4))


As the level increases, the sequence grows longer and increasingly difficult to remember.

Once the sequence has been generated, the LEDs play back the pattern for the player:


Python:

for item in sequence:

flash_led(item)


The flash_led() function was designed specifically for Simon Speaks.

It performs three actions simultaneously:

  1. Displays the colour name on the LCD.
  2. Illuminates the corresponding LED.
  3. Controls timing to ensure every colour is clearly visible.

Example:


Python:

LCDOutput(0,1,colors[index].upper())


This means players don't just see colours.

They associate spoken colours with visual colours, making the game easier to learn while still remaining challenging at higher levels.

Once the sequence finishes, the player enters the recall stage.

The LCD displays:


REPEAT

THE COLORS


followed by:


YOUR TURN

SPEAK NOW


At this point, the microphone begins listening.


Python:


audio = recognizer.listen(source,timeout=10)


Google Speech Recognition is then used to convert the recording into text.


Python:


text = recognizer.recognize_google(audio).lower()


Example:

A player might say:


red green yellow


The system converts that speech into text and compares it against the original LED sequence.

I specifically chose word matching rather than strict sentence matching.


Python:

for word in expected:

if word not in spoken_words:

correct = False


This makes the game more forgiving because players can pause naturally between colours without being penalized.

When the player succeeds, they receive:


CORRECT!

WELL DONE


along with LED animations and buzzer effects.

The score is calculated using:


Python:

score = level * 100


This means higher levels offer larger rewards.

A Level 1 completion gives:


100 points


while a Level 10 completion awards:


1000 points


I also integrated the first achievement system directly into this game.

When a player reaches Level 5:


ACHIEVEMENT

MEMORY PRO


is unlocked.

This is triggered using:


Python:

if level == 5:


Similarly, reaching Level 10 unlocks:


ACHIEVEMENT

MEMORY MASTER

using:


Python:

if level == 10:


These achievements later become part of the larger Arcade Legends progression system.

Failure was also carefully designed.

When a player provides an incorrect sequence, the arcade doesn't simply display "wrong."

Instead, it explains what happened.

The LCD displays:


EXPECTED

RED


followed by:


YOU SAID

GREEN


This allows the player to see exactly where they made a mistake.

The game then displays:


BEST LEVEL

8


or whatever record the player achieved.

Finally, the failure animation runs:


Python:


GAME OVER

TRY AGAIN


accompanied by prolonged buzzer feedback and LED effects.

One thing I discovered during testing was that voice recognition creates a completely different experience compared to traditional Simon Says games. Players become surprisingly nervous when speaking long colour sequences aloud. Even sequences they can easily remember become difficult when they must verbalize them correctly.

This unexpectedly made the game far more engaging than I originally anticipated.

By the end of development, Simon Speaks had evolved from a simple LED memory challenge into a complete voice-controlled arcade experience combining:

  1. Memory
  2. Speech Recognition
  3. Progression
  4. Achievements
  5. Scoring
  6. LCD Feedback
  7. Audio Feedback
  8. Dynamic Difficulty

and became one of the most unique games in the entire Arcade Legends platform.

Simon Speaks was the first game that truly demonstrated what made Arcade Legends different. It wasn't just testing buttons or reaction times. It was combining hardware, software, speech recognition, memory, and game design into a completely original arcade experience. šŸ†šŸŽ®

Developing Reaction Master

šŸ”„ I Spent 6 Months Building the Ultimate Raspberry Pi Arcade

After completing Simon Speaks, I wanted to create a game that tested a completely different skill. Simon Speaks is focused on memory, concentration, and voice recognition. For the next game, I wanted players to rely purely on their reaction speed.

This led to the creation of Reaction Master, a game designed around one simple concept:

When the signal appears, react as quickly as possible.

Although the concept sounds straightforward, creating a fair and exciting reaction game required considerably more work than I originally expected.

The game uses the same hardware foundation already developed for Arcade Legends:

  1. 5 LEDs
  2. 1 Arcade Button
  3. 1 Buzzer
  4. Raspberry Pi
  5. gpiozero library

The LEDs are connected to:


Python:

LED(23)

LED(12)

LED(16)

LED(20)

LED(21)


The main reaction button is connected to:


Python:

Button(5)


while the buzzer remains connected to:


Python:

Buzzer(4)


One of the biggest design challenges was preventing players from simply predicting when the signal would appear.

If the LED always appeared after the same delay, nobody would need actual reflexes. Players could simply learn the timing and press the button before the signal appeared.

To prevent this, I generated a completely random waiting period before each round:


Python:

delay = random.uniform(1.5,5)


This means the game randomly waits anywhere between:

1.5 seconds and 5 seconds before showing the signal.

As a result, players can never anticipate exactly when the LED will activate.

However, this introduced another issue.

What happens if a player presses the button before the signal appears?

I decided to treat this as a False Start.

During the waiting phase, the game continuously monitors the button:


Python

while time.time() - start_wait < delay:

if button.is_pressed:


If the player presses too early, the game immediately triggers:


Python

TOO EARLY!


and removes one life.


Python

lives -= 1


This rule was intentionally added because I wanted Reaction Master to reward genuine reflexes rather than random button mashing.

Once the random delay has expired, the target LED illuminates:


Python:

leds[target].on()


and the buzzer emits a short signal:


Python:

buzzer.on()

sleep(0.05)

buzzer.off()


At this exact moment, a timer starts.


Python:

start_time = time.time()


The game then waits for the player to press the arcade button:


Python:

button.wait_for_press()


When the button is pressed, the reaction time is calculated:


Python:

reaction = time.time() - start_time


This provides a highly accurate measurement of how quickly the player reacted.

Rather than simply displaying a number, I wanted the game to evaluate the player's performance.

To do this, I created a ranking system.


Python:

if t < 0.15:

return "LIGHTNING"

elif t < 0.25:

return "ELITE"

elif t < 0.35:

return "PRO"

elif t < 0.50:

return "GOOD"

else:

return "SLOW"


This turned raw reaction times into meaningful achievements.

For example:


0.142 seconds becomes LIGHTNING

while:

0.280 seconds becomes PRO


During testing, this simple grading system ended up becoming one of the most enjoyable parts of the game because players became obsessed with improving their rank rather than just improving their score.

Scoring was another area where I spent significant time experimenting.

I wanted fast reactions to feel significantly more valuable than slow ones.


Eventually I implemented:


Python:

points = max(10,int((1.0 - reaction) * 100))


This means faster reactions generate more points.

Example:


0.18 seconds might produce:


82 points

while 0.65 seconds might only produce:


35 points


This scoring system naturally rewards skill without requiring additional difficulty settings.

I also wanted players to have a reason to continue improving over multiple sessions.

For that reason, I added a personal record system.


Python:

best_time = reaction


Whenever a player achieves a new personal best, the game announces:


NEW RECORD!


and displays the reaction time.

To make this moment feel special, I created a celebration sequence:


Python

flash_all()


which illuminates every LED repeatedly.

The game also uses a life system:


Python:

lives = 3


Every false start removes a life.

This means players must balance aggression and patience.

Pressing too early is punished.

Waiting too long results in poor scores.

Only perfect timing leads to success.

Looking back, Reaction Master became one of the most competitive games in Arcade Legends.

Unlike Simon Speaks, which focuses on memory and concentration, Reaction Master focuses entirely on human reflexes.

Players immediately understand the rules, but mastering them is surprisingly difficult.

The combination of:

  1. Randomized delays
  2. False-start detection
  3. Reaction measurement
  4. Skill rankings
  5. Dynamic scoring
  6. Personal records
  7. LED feedback
  8. Audio feedback

transformed what could have been a simple button-press game into a complete arcade experience.

By the end of development, Reaction Master had become one of the most popular physical games in the project because every round feels fair, competitive, and intensely rewarding.

Reaction Master taught me an important lesson about game design: sometimes the simplest concepts create the most addictive experiences. All it takes is one button, one light, and the desire to beat your previous best time. šŸ†šŸŽ®


Developing Stop-The-light

šŸ”„ I Spent 6 Months Building the Ultimate Raspberry Pi Arcade

After building Reaction Master, I wanted to create a game that tested a different type of skill. Reaction Master is primarily about speed. Once the LED appears, the player simply reacts as quickly as possible. While that makes for a great reaction game, I wanted the next challenge to reward timing, precision, and patience rather than pure reflexes.

This led to the creation of Stop-The-Light, one of the most addictive physical games in Arcade Legends.

The concept is simple to understand but surprisingly difficult to master. A light moves continuously back and forth across five LEDs, and the player must press a button at exactly the right moment to stop it on the center target LED. Although the rules sound easy, the game becomes increasingly difficult because the light gradually speeds up over time.

Like the previous games, Stop-The-Light was built using the existing arcade hardware:

  1. 5 LEDs
  2. 1 Arcade Button
  3. 1 Buzzer
  4. Raspberry Pi
  5. gpiozero library

The LEDs are connected to:


Python:

LED(23)

LED(12)

LED(16)

LED(20)

LED(21)


The arcade button remains connected to:


Python:

Button(5)


and the buzzer is connected to:


Python:

Buzzer(4)


The first design decision I made was determining where the target should be located.

I eventually chose the center LED:


Python:

target = 2


Since the LEDs are stored in a list starting from zero, LED number 2 corresponds to the middle position.

This choice was deliberate.

During planning, I considered randomly moving the target each round, but after several tests I realized that doing so introduced too much luck. Players would spend more time adjusting to target positions than actually improving their timing skills.

By fixing the target at the center, the game becomes entirely skill-based.

Every player knows where they need to stop the light.

The challenge comes from controlling the timing.

Rather than spinning in a circular loop, the light uses a bouncing movement pattern.

The game tracks both the current position and direction:


Python:

current = 0

direction = 1


Every cycle the position updates:


Python:

current += direction


When the light reaches the edge of the board:


Python:

if current >= 4:

direction = -1


the direction reverses.

Likewise:


Python:

if current <= 0:

direction = 1


causes the light to move back toward the opposite side.

The result is a smooth back-and-forth motion that feels much more natural than continuously wrapping around the LEDs.

At the start of development, the light moved at a fixed speed.

This quickly became boring.

After only a few rounds, players learned the timing pattern and could consistently achieve high scores.

To make the game progressively harder, I added dynamic difficulty scaling.

The initial speed is:


Python:

speed = 0.12


After every update:


Python:

speed *= 0.999


is executed.

Although the change seems tiny, it accumulates over time.

As the game progresses, the LED moves faster and faster, forcing players to react more precisely.

Without this mechanic, the game lacks long-term challenge.

However, I also needed to prevent the speed from becoming impossible.

To solve this, I introduced a minimum cap:


Python:

if speed < 0.03:

speed = 0.03


This ensures the game remains difficult while still being playable.

Scoring was another important design consideration.

I wanted to reward accuracy rather than provide only a binary win-or-lose outcome.

As a result, there are three possible outcomes.

A perfect center hit:


Python:

if current == target:


awards:


Python:

PERFECT! +100


and increases the score by:


Python:

score += 100


This is the ideal outcome and rewards precise timing.

If the player lands on an LED directly next to the target:


Python:

elif abs(current - target) == 1:


they receive:


Python:

CLOSE! +25


This provides a smaller reward while still encouraging improvement.

Anything else is considered a miss:


Python:

MISS


and costs a life:


Python:

lives -= 1


This scoring system creates a much more satisfying experience because players can clearly see the difference between an average timing attempt and a perfect one.

To increase tension, the game uses a life system:


Python:

lives = 3


Each miss removes one life.

Once all lives are gone:


Python:

GAME OVER


appears and the session ends.

One of my favourite parts of the game is the celebration effect.

Whenever a player successfully lands on the center target, all LEDs flash repeatedly:


Python:

flash_all()


The function cycles all LEDs on and off several times, creating a satisfying visual reward.

This might seem like a small feature, but it dramatically improves the feel of the game. Immediate visual feedback makes success much more exciting and encourages players to keep playing.

Throughout development, I repeatedly tested different movement speeds, scoring values, and difficulty scaling settings. Even small changes could drastically affect the balance of the game. Too slow and it becomes trivial. Too fast and it becomes frustrating. Finding that balance took far more iteration than I originally expected.

What makes Stop-The-Light interesting is that it appears incredibly simple when someone first sees it. There are no complicated rules, menus, or instructions. Players immediately understand what they need to do.

Yet achieving consistent perfect scores requires concentration, timing, rhythm, and practice.

By the end of development, Stop-The-Light had become one of the most competitive games in Arcade Legends. Players would often challenge themselves to improve their timing by just a fraction of a second, proving that simple mehanics can sometimes produce the most engaging gameplay.

Stop-The-Light taught me that great arcade games don't always require complex rules. Sometimes all you need is a moving light, a single button, and the determination to hit the perfect moment. šŸ†šŸŽ®

Developing LED Roulette

šŸ”„ I Spent 6 Months Building the Ultimate Raspberry Pi Arcade

After building Stop-The-Light, I wanted to create a game that felt less like a skill challenge and more like something you might find on a real arcade midway. Many arcade machines create excitement by combining skill, anticipation, and luck. With that goal in mind, I developed LED Roulette, a game centered around suspense and timing.

Although LED Roulette is one of the simpler games in Arcade Legends from a programming perspective, it taught me an important lesson about game design:

A game does not need complicated mechanics to be entertaining. It needs anticipation.

Like my other physical arcade games, LED Roulette was built using the existing hardware platform:

  1. 5 LEDs connected to GPIO pins 23, 12, 16, 20, and 21
  2. 1 Arcade Button connected to GPIO 5
  3. Raspberry Pi
  4. gpiozero library

The game starts by secretly selecting a winning LED:


Python:

jackpot = random.randint(0,4)


This generates a random number between 0 and 4, corresponding to one of the five LEDs.

The selected jackpot LED remains hidden from the player. Only the software knows which LED is the winner.

I wanted the LEDs to resemble a spinning roulette wheel, so I created a continuously rotating light animation. The variable:


Python:

current = 0


tracks the currently illuminated LED.

Inside the main game loop, every LED is turned off before the active LED is illuminated:


Python:

for led in leds:

led.off()

leds[current].on()


The animation advances by moving to the next LED:


Python:

current = (current + 1) % 5


The modulus operator allows the sequence to loop continuously from LED 5 back to LED 1, creating an endless spinning effect.

Initially, I considered allowing the player to stop the wheel instantly by pressing the button. However, after testing this version, the game felt unsatisfying. Real roulette wheels don't stop immediately. They gradually lose momentum before finally settling on a result.

To recreate that feeling, I implemented a deceleration sequence.

When the button is pressed:


Python:

if button.is_pressed:


the wheel does not stop right away.

Instead, it goes through a list of progressively larger delays:


Python:

for delay in [0.05,0.1,0.15,0.2,0.3]:


Each delay causes the wheel to slow down slightly more than the previous one.

The effect is surprisingly convincing. The LEDs appear to lose momentum naturally before finally stopping on a single light.

Even though the underlying code is simple, this feature dramatically improved the game's feel.

The final LED position is then stored:


Python:

final_led = current


and compared against the jackpot position:


Python:

if final_led == jackpot:


If the values match, the player wins.

The system displays:


JACKPOT!!!!


and immediately triggers a celebration sequence.

To make victories feel rewarding, I created a flashing animation:


Python:

for i in range(5):


for led in leds:

led.on()


sleep(0.1)


for led in leds:

led.off()


sleep(0.1)


This rapidly flashes all five LEDs multiple times, creating a classic arcade jackpot effect.

If the player lands on any other LED, the game displays:


Try Again


and the session ends.

One design choice that I intentionally kept was the element of luck.

Most of the physical games in Arcade Legends reward skill:

  1. Simon Speaks rewards memory.
  2. Reaction Master rewards reflexes.
  3. Stop-The-Light rewards timing.
  4. Whack-A-Mole rewards speed and concentration.

LED Roulette is different.

No matter how experienced a player becomes, there is always an element of unpredictability. This makes it a nice change of pace from the other games and provides a different gameplay experience.

During testing, I discovered something interesting. Players often became more excited during LED Roulette than during some of the more technically complicated games. They would watch the slowing LEDs, hoping the light would stop one position later or one position sooner.

That excitement wasn't caused by complexity.

It was caused by suspense.

Looking back, LED Roulette became one of the clearest examples of how important presentation is in arcade game design. The gradual slowdown, flashing jackpot effect, and simple rules all work together to create a game that is immediately understandable and surprisingly addictive.

By the end of development, LED Roulette provided Arcade Legends with a game focused not on reflexes or memory, but on anticipation, excitement, and the thrill of hitting the jackpot.

LED Roulette proved that sometimes the most memorable arcade moments aren't the ones you control completely. They're the moments when you're holding your breath, waiting to see where the wheel lands. šŸŽ°šŸ†šŸŽ®

Developing Whack-a-mole

šŸ”„ I Spent 6 Months Building the Ultimate Raspberry Pi Arcade

With Simon Speaks, Reaction Master, Stop-The-Light, and LED Roulette completed, I wanted to create a game that felt faster, more chaotic, and increasingly intense the longer it was played. Traditional arcade machines often become more difficult as players improve, forcing them to constantly adapt rather than repeating the same actions over and over again. This idea became the foundation for Whack-A-Mole.

Although the final game appears simple, a significant amount of thought went into balancing speed, difficulty progression, scoring, and player feedback. The goal was to create a challenge where players must quickly identify a target, react within a limited time window, and continue doing so as the game becomes progressively faster.

Like the previous arcade games, Whack-A-Mole was built using the core Arcade Legends hardware platform:

  1. 5 LEDs
  2. 1 Arcade Button
  3. 1 Buzzer
  4. Raspberry Pi
  5. gpiozero

The LEDs were configured as:


Python:

leds = [LED(23),LED(12),LED(16),LED(20),LED(21)]


The primary game button remained connected to:


Python:

button = Button(5)


and the buzzer was connected to:


Python:

buzzer = Buzzer(4)


The concept behind the game is straightforward. At the beginning of each round, the system randomly selects one LED that will act as the current "mole".

Using:


Python:

target = random.randint(0,4)


the game chooses one of the five available LEDs.

The selected LED immediately illuminates:


Python:

leds[target].on()


signaling that the player must react before time runs out.

One design decision I made very early was that reaction windows should not remain fixed. If every round gave players the same amount of time, they would eventually settle into a comfortable rhythm. I wanted the experience to become increasingly stressful and exciting.

To accomplish this, each round begins with a variable called:


Python:

speed = 1.5


This represents how long the player has to react.

The game then continuously reduces that value after every successful round:


Python:

speed *= 0.95


This means the available reaction time shrinks by approximately 5% after each cycle.

For example:


Round 1: 1.50 seconds

Round 2: 1.42 seconds

Round 3: 1.35 seconds

Round 4: 1.28 seconds


and so on.

As the game progresses, the LEDs appear for shorter and shorter periods, forcing players to react faster.

However, completely unrestricted difficulty scaling eventually becomes unfair. After testing multiple versions, I noticed that reaction windows eventually became so small that gameplay turned frustrating rather than challenging.

To solve this issue, I introduced a lower difficulty boundary:


Python:

if speed < 0.25:

speed = 0.25


This guarantees that players always have at least a quarter of a second to react.

The result is a game that becomes significantly more difficult while still remaining playable.

To track player performance, I created a simple scoring system:


Python



score += 1

Every successful hit increases the score.

Although this approach may seem basic, it works extremely well because the score directly represents the number of successful reactions achieved before the player runs out of lives.

I also implemented a high-score system:


Python:

highscore = 0


Whenever a player exceeds their previous best:


Python:

if score > highscore:

highscore = score

the record updates automatically.

This provides a strong incentive for repeated play sessions because players are constantly competing against their own best performance.

A successful hit is determined using:


Python:

if button.is_pressed:


When the player reacts quickly enough, several events occur simultaneously.

First, the hit is registered:


Python:

hit = True

The score increases:


Python:

score += 1


The buzzer provides instant feedback:


Python:

buzzer.on()

sleep(0.05)

buzzer.off()


and finally a celebration animation is triggered:


Python:

flash_all()


The flash effect rapidly illuminates all five LEDs multiple times, creating a rewarding visual confirmation that the hit was successful.

This immediate feedback is extremely important.

Throughout development, I discovered that games feel dramatically more satisfying when successful actions trigger instant responses. Even a tiny delay can make controls feel sluggish or disconnected.

Failure was handled very differently.

If the player fails to react before time expires:


Python



if not hit:

a life is removed:


Python:

lives -= 1


and the buzzer produces a longer warning tone:


Python:

buzzer.on()

sleep(0.3)

buzzer.off()


Unlike the short success beep, this longer tone immediately communicates that something has gone wrong.

The game begins with:


Python:

lives = 3


and continues until all lives have been lost.

I deliberately chose three lives because it provides players with enough opportunities to recover from mistakes without making failure meaningless.

During testing, I experimented with a single-life system, but players became frustrated because one missed target immediately ended the game. Three lives produced a much better balance between challenge and fairness.

One of the most interesting discoveries during development was how quickly difficulty scales. Because the reaction window continuously shrinks, even experienced players eventually reach a point where simply identifying the illuminated LED and pressing the button becomes genuinely difficult.

This creates a natural progression curve without requiring multiple difficulty settings.

Unlike Reaction Master, which measures precise reaction times, or Stop-The-Light, which focuses on timing accuracy, Whack-A-Mole rewards sustained performance under pressure. Players must repeatedly react correctly while the game becomes faster and faster around them.

Looking back, Whack-A-Mole became one of the most intense games in the Arcade Legends lineup. The combination of random targets, shrinking reaction windows, score tracking, lives, visual effects, and audio feedback creates a surprisingly addictive experience despite being built from only a handful of components and a relatively small amount of code.

Whack-A-Mole demonstrated that difficulty doesn't need to come from complicated rules. Sometimes all it takes is making players react a little faster with every round until their reflexes finally fail. šŸ†šŸŽ®

Building the XP & Progression System

šŸ”„ I Spent 6 Months Building the Ultimate Raspberry Pi Arcade

By this stage, Arcade Legends already contained multiple games, player authentication, profile storage, an LCD interface, and various feedback systems. However, I noticed an important problem during testing. Although players enjoyed the games, there was very little reason for them to come back once they had played everything. Winning a game felt good in the moment, but there was no long-term sense of progression.

To solve this problem, I decided to build a complete XP (Experience Points) System. Instead of treating every game as an isolated activity, I wanted every action a player performed to contribute toward a larger goal. Whether someone was playing Simon Speaks, Reaction Master, Stop-The-Light, Whack-A-Mole, or LED Roulette, their performance would help them earn XP, level up, unlock achievements, and eventually climb the leaderboard.

The first challenge was deciding how XP should be stored. Since I had already built the profile system in the previous step, it made sense to save XP directly inside each player's profile file.

A simplified profile structure looked something like this:


12580

14

MEMORY PRO

REACTION KING


The first line stores total XP:


12580


while the second line stores the player's level:


14


This allowed the arcade to load player progression immediately after RFID login.

Whenever a game was completed, XP could be awarded using simple logic such as:


Python:

xp += reward


followed by:


Python:

SaveProfile()


to ensure the data was permanently stored.

One design decision I made early was that different accomplishments should award different amounts of XP.

For example:

  1. Completing a game awards XP.
  2. Achieving a high score awards bonus XP.
  3. Unlocking an achievement awards bonus XP.
  4. Reaching special milestones awards bonus XP.

This approach encourages players to improve rather than simply repeat the easiest challenge over and over again.

Once XP existed, I needed a way for players to progress.

This led to the creation of the Level System.

Rather than storing levels directly inside each game, I wanted level progression to be universal across the entire arcade.

A player's level would therefore reflect their overall performance, not just their skill in one particular game.

The basic concept was simple:


Python:

level = xp // 1000 + 1


Under this system:


0 XP -> Level 1

1000 XP -> Level 2

2000 XP -> Level 3

3000 XP -> Level 4

and so on.


As players accumulated more XP, their level increased automatically.

One of my goals throughout Arcade Legends was making progression visible.

Hidden systems do not feel rewarding.

Because of this, I integrated levels into multiple parts of the arcade.

When a player logs in:


WELCOME

LEVEL 14


can appear on the LCD.

The web dashboard displays:


12580 XP

Level 14


The Hall Of Fame can use progression information when ranking players.

Even before adding achievements, the XP system alone gave players a reason to continue improving.

During testing, I discovered that visible progress significantly changed player behaviour. People who normally would have stopped after a few rounds suddenly wanted to gain just a little more XP to reach the next level.

This was exactly the behaviour I was hoping to encourage.

Another important feature was persistence.

A player who reached:


Level 10


needed to remain at Level 10 the next day.

Because XP and levels were loaded directly from profile files after RFID login, progression became permanent.

A player's history would survive:

  1. Program restarts
  2. Raspberry Pi reboots
  3. New game installations
  4. Software updates

This transformed the arcade from a temporary experience into an evolving platform.

While building this system, I also began thinking ahead.

XP alone is interesting, but XP becomes much more meaningful when connected to additional systems.

The progression framework created here would later become the foundation for:

  1. Achievements
  2. Leaderboards
  3. Champion Rankings
  4. Hall Of Fame Entries
  5. Player Statistics
  6. Winner Tracking

In many ways, the XP system became the glue that connected every part of Arcade Legends together.

Before this stage, players simply played games.

After this stage, players began building a legacy.

Every victory mattered.

Every record mattered.

Every game contributed to something larger.

Looking back, this was one of the most impactful systems in the entire project because it turned Arcade Legends from a collection of mini-games into a platform where players could continuously improve, compete, and progress over time.

Games provide moments of excitement. Progression systems provide reasons to return. The XP System transformed Arcade Legends from something players could enjoy once into something they would want to keep coming back to. šŸ†šŸŽ®

Building the Achievement System

šŸ”„ I Spent 6 Months Building the Ultimate Raspberry Pi Arcade

Once the XP System was working, I realized that experience points alone were not enough. A player might earn thousands of XP over time, but XP is just a number. I wanted Arcade Legends to recognize specific accomplishments and preserve them permanently. If somebody reached an exceptional milestone, the arcade should remember it long after the score itself had been forgotten.

This led to the creation of the Achievement System.

Unlike many arcade machines where progress disappears after a game ends, Arcade Legends records important accomplishments directly inside each player's profile file. This means achievements are not temporary. A player who unlocks an achievement keeps it permanently, even if the Raspberry Pi is rebooted or the arcade is powered off.

Earlier in development, I had already created a profile format that stored player information inside individual files located in the:


profiles/


directory.

A typical profile might look like:


12580

14

MEMORY PRO

MEMORY MASTER


The first line stores:


XP


The second line stores:


Level


Every line after that is treated as an unlocked achievement.

This design turned out to be incredibly useful because it allowed me to add new achievements without redesigning the entire system.

For example, if a player unlocked a new award, I could simply append it to the file:


Python:

with open(f"profiles/{current_user}.txt","a") as file:

file.write("MEMORY MASTER\n")


The next time the player scanned their RFID card, the achievement would still be there.

One challenge I quickly encountered was preventing duplicate achievements.

Consider a player who has already reached Level 10 in Simon Speaks. If they play the game again and reach Level 10 a second time, the system should not add:


MEMORY MASTER


to the profile file multiple times.

To solve this problem, I first load every achievement stored inside the profile:


Python:

achievements = profile_lines[2:]


Before awarding a new achievement, the system checks whether it already exists:


Python:

if "MEMORY MASTER" not in achievements:


Only then is the achievement added.

This simple validation prevents profiles from becoming cluttered with duplicate entries.

One of the first achievements I implemented was:


MEMORY PRO


which is awarded in Simon Speaks when the player reaches Level 5.

Inside the game code:


Python:

if level == 5:

lcd.clear()

LCDOutput(0,0,"ACHIEVEMENT")

LCDOutput(0,1,"MEMORY PRO")

flash_all()

time.sleep(3)


When this condition is reached, the achievement screen appears on the LCD and every LED begins flashing.

The celebration sequence uses:


Python:

flash_all()


which was specifically created to make major milestones feel special.

Rather than silently updating a file in the background, the machine visibly celebrates the achievement.

Players immediately know they have accomplished something important.

The same approach was used for the higher-level award:


MEMORY MASTER


which is unlocked at Level 10.

The code inside Simon Speaks looks like:


Python:

if level == 10:

lcd.clear()

LCDOutput(0,0,"ACHIEVEMENT")

LCDOutput(0,1,"MEMORY MASTER")

flash_all()

time.sleep(4)


I intentionally made the Level 10 celebration longer because reaching that stage is significantly more difficult than reaching Level 5.

At this point, achievements were doing three different things simultaneously:

  1. Rewarding players
  2. Tracking progression
  3. Creating memorable moments

This was an important realization.

The achievement itself is only one line of text.

What players actually remember is the experience surrounding it.

The LCD displays a special message.

The LEDs begin flashing dramatically.

The buzzer activates.

The achievement is permanently saved.

Together, these systems create the feeling that something significant has occurred.

As Arcade Legends continued to grow, the achievement framework became increasingly valuable because every game could contribute to it.

For example:

  1. Simon Speaks could unlock memory achievements.
  2. Reaction Master could unlock reaction achievements.
  3. Future games could unlock entirely different achievement categories.

Because the system stores achievement names as plain text, adding new milestones became extremely simple.

A future achievement might look like:


REACTION KING


or


LIGHTNING REFLEXES


without requiring any changes to the profile structure.

Another benefit of this approach is transparency.

If I ever need to debug a profile, I can simply open the file and immediately see exactly what the player has accomplished:


12580

14

MEMORY PRO

MEMORY MASTER


No databases.

No complex queries.

No additional software.

Everything is easily readable.

Looking back, the Achievement System became one of the most important progression systems inside Arcade Legends. The XP System tells players how much they have played, but achievements tell players what they have accomplished.

By storing achievements permanently, integrating them into gameplay, and celebrating them visually through LEDs, the LCD display, and audio feedback, Arcade Legends became much more than a collection of games. It became a platform that remembers success.

XP measures progress. Achievements measure greatness. The Achievement System gave Arcade Legends a way to recognize and preserve the moments that players were most proud of. šŸ†šŸŽ®

Integrating the Pi Camera

šŸ”„ I Spent 6 Months Building the Ultimate Raspberry Pi Arcade

Up until this point, Arcade Legends could recognize players, save profiles, award XP, unlock achievements, and run games. However, I wanted the arcade to do something that very few DIY arcade projects attempt:

I wanted the arcade to remember not just scores, but people.

This idea led to the integration of a Raspberry Pi Camera Module, which eventually became one of the most important components in the entire project. The camera is not simply attached for decoration or surveillance. It is directly integrated into the authentication system, winner celebration system, profile system, and eventually the Hall of Fame.

To control the camera, I used the modern Picamera2 library:


Python:

from picamera2 import Picamera2


During startup, the camera is initialized immediately when Arcade Legends launches:


Python:

picam2 = Picamera2()

picam2.start()

sleep(2)


The two-second delay is important because it gives the camera enough time to initialize correctly before any photographs are taken.

When developing this feature, one of the first problems I encountered was camera startup reliability. Occasionally the camera would fail to respond after boot. At first, I assumed I had made a programming mistake. After spending a significant amount of time debugging the code, I discovered that the actual issue was the CSI ribbon cable connection. Re-seating the cable correctly solved the problem completely.

One design decision I made very early was separating photographs into two categories:


profile_photos/

and

winners/


Both serve very different purposes.

The profile_photos directory stores the picture associated with a player's account.

The winners directory stores photographs captured during victory celebrations and special events.

Because these folders are essential to the operation of Arcade Legends, they are automatically created during startup:


Python:

if not os.path.exists("winners"):

os.mkdir("winners")

if not os.path.exists("profile_photos"):

os.mkdir("profile_photos")


This means the arcade can recover automatically if either directory is accidentally deleted.

The first major use of the camera was the player profile photo system.

When a new user creates an account, a profile photograph is immediately captured.

The function responsible for this is:


Python:

TakeProfilePhoto()


When the function executes, the LCD displays:


NEW USER

LOOK CAMERA


using:


Python:

lcd.clear()

LCDOutput(0,0,"NEW USER")

LCDOutput(0,1,"LOOK CAMERA")


The player is then given a few seconds to position themselves in front of the camera:


Python:

sleep(3)


The filename for the profile image is constructed using the player's username:


Python:

filename = f"profile_photos/{current_user}.jpg"


For example:

profile_photos/Shivansh.jpg


The image is then captured using:


Python:

picam2.capture_file(filename)


Once completed, Arcade Legends confirms the successful capture through both speech and the LCD display.


Python:

Speak("Photo saved")


followed by:


PHOTO SAVED

SHIVANSH


At this point, every player account now has both a digital identity and a corresponding photograph.

This immediately made the system feel much more personal.

Instead of simply knowing that a player called "Shivansh" exists, the arcade now had a visual record associated with that account.

As development continued, I realized that the camera could do much more than merely capture profile pictures.

The success of the profile photo system inspired an even bigger idea:

What if the arcade photographed winners automatically?

That idea eventually evolved into the Winner Photography System and Hall Of Fame, which became some of my favourite features in the entire project.

Looking back, integrating the Pi Camera was the moment Arcade Legends began moving beyond traditional arcade functionality. Most arcade machines can store names and scores. Very few can visually document the people who earned those achievements.

The camera transformed Arcade Legends from a machine that tracked statistics into a machine that could preserve memories.

The RFID system gave players an identity. The profile system remembered their progress. The Pi Camera gave Arcade Legends the ability to remember the people behind those achievements. šŸ“øšŸ†šŸŽ®

Creating the Winner Photography System

šŸ”„ I Spent 6 Months Building the Ultimate Raspberry Pi Arcade

While integrating the camera, I realized something important.

Most arcade machines only remember scores.

Some modern systems might remember usernames.

Very few actually remember the moment somebody achieves something special.

I wanted Arcade Legends to celebrate victories in a way that felt meaningful. If a player completed a major challenge, defeated a boss battle, or achieved an exceptional result, the machine should do more than simply increase a number in a file.

It should capture the moment.

This idea eventually became the Winner Photography System.

Rather than requiring players to manually take photos, the process is completely automated. Whenever a player wins a major event inside Arcade Legends, the system launches a celebration routine called:


Python:

Prize()


This function acts as the central winner workflow for the entire arcade.

The very first thing it does is update the arcade's live state:


Python:

UpdateState(current_game="WINNER",status="CELEBRATION",celebration=True)


This allows other parts of the system, including the dashboard, to know that a victory event is currently taking place.

The system then plays a custom victory theme:


Python:

VictoryTheme()

Unlike the simple buzzer effects used elsewhere in the project, the victory theme uses a TonalBuzzer connected to:


Python:

GPIO 19

and plays an actual melody.

For example:


Python:

note("C4",0.15)

note("E4",0.15)

note("G4",0.15)

note("C5",0.35)


This creates a much more exciting celebration compared to a standard beep.

Once the music begins, the winner photography sequence starts automatically:


Python:

TakeWinnerPhoto()


However, I didn't want the camera to suddenly snap a picture without warning.

Instead, I created a complete countdown system to give players time to prepare.

The countdown starts by running:


Python:

SmoothRainbowCountdown()

This function transforms the RGB LED into a visual countdown device.

The RGB LED transitions smoothly through:

  1. Red
  2. Yellow
  3. Green
  4. Cyan
  5. Blue
  6. Purple
  7. Pink

using:


Python:

led.color = (r,g,b)


values that gradually change over time.

The effect feels far more professional than simply flashing a light on and off.

At the same time, the LCD displays:


LOOK CAMERA

3...2...1...


using:


Python:

LCDOutput(0,0,"LOOK CAMERA")

LCDOutput(0,1,"3...2...1...")


I also integrated the voice system to guide the player through the process.

The arcade announces:


Python:

Speak("Look At the Camera ... 3..2..1..")


which is generated using Microsoft's edge-tts speech synthesis engine.

This means that three different systems are working together simultaneously:

  1. LCD display
  2. RGB lighting effects
  3. Voice announcements

The player immediately knows that a photograph is about to be taken.

After the countdown finishes, the camera captures the image:


Python:

picam2.capture_file(filename)


One design problem I needed to solve was file naming.

If every photograph were called:


winner.jpg


the previous winner would be overwritten.

To prevent this, I incorporated both the username and a Unix timestamp into the filename:


Python:

filename = (f"winners/"f"{current_user}_"f"{int(time.time())}.jpg")


This produces files such as:

winners/Shivansh_1792863372.jpg


Every photograph therefore receives a completely unique filename.

This allows Arcade Legends to preserve an entire history of winners rather than only storing the latest one.

After the photograph has been captured, the LCD displays:


PHOTO TAKEN!

CONGRATS!


while the filename is written to the terminal:


Python:

print("Saved:", filename)


The celebration does not stop there.

Following the photo capture, the arcade activates a second celebration sequence.

The RGB LED cycles through:


Python:

Red

Green

Blue


multiple times:


Python:

led.color = (1,0,0)

led.color = (0,1,0)

led.color = (0,0,1)


The five game LEDs then begin flashing repeatedly:


Python:

for l in LEDS1:

l.on()


while the buzzer produces synchronized sound effects.

Finally, the LCD displays:


YOU ARE A

WINNER!


This may seem like a small detail, but it dramatically changes how victories feel.

During testing, I noticed that players reacted very differently once photographs were involved. A high score is easy to forget. A photograph creates a memory.

Winning suddenly felt significant.

Players started smiling, posing for the camera, and competing specifically to appear in the winner system.

The photography feature achieved exactly what I hoped it would accomplish.

It transformed victories from data into experiences.

It also became the foundation for the next major component of Arcade Legends:

The Hall Of Fame.

Because now the arcade didn't just know who won.

It had photographic evidence of those victories.

By the end of this stage, Arcade Legends wasn't simply storing numbers inside text files. It was building a visual history of its players and preserving the moments they would be most proud of.

Scores can be forgotten. Levels can be surpassed. Photographs preserve the moment forever. The Winner Photography System allowed Arcade Legends to celebrate players in a way that felt personal, memorable, and uniquely human. šŸ†šŸ“øšŸŽ®

Building the Hall of Fame

šŸ”„ I Spent 6 Months Building the Ultimate Raspberry Pi Arcade

By this stage, Arcade Legends could do something that very few hobby arcade projects can achieve.

It could:

  1. Identify players using RFID authentication
  2. Save player progression using persistent profiles
  3. Award XP
  4. Unlock Achievements
  5. Capture Winner Photographs
  6. Track player statistics across multiple sessions

However, I wanted one final system that would bring all of these features together.

I wanted players to have something bigger than individual scores to aim for.

I wanted them to leave their mark on the arcade.

This idea became the Hall Of Fame.

Unlike a traditional leaderboard that only stores numbers, the Hall Of Fame combines player profiles, winner photographs, XP progression, and achievement history into a permanent visual record of the arcade's greatest players.

The foundation of the Hall Of Fame was built much earlier in the project without me even realizing it.

Every time a profile was created, information was stored inside:


profiles/


Every time a winner was photographed, images were stored inside:


winners/


Every time XP was earned, new levels were unlocked.

Every time achievements were completed, they were written to profile files.

As development progressed, I realized all of this information could be combined into something much larger.

The first step was ensuring that winner photographs were stored permanently rather than overwritten.

Inside the Winner Photography System, photographs are saved using:


Python:

filename = (f"winners/"f"{current_user}_"f"{int(time.time())}.jpg")


This generates filenames such as:


winners/Shivansh_1792863372.jpg


The username identifies the player.

The timestamp ensures that every photograph is unique.

As a result, Arcade Legends doesn't simply store the latest winner.

It stores an entire historical archive of victories.

During RFID login, I added logic that searches the winners directory:


Python:

photos = []

for file in os.listdir("winners"):

if file.startswith(current_user + "_"):

photos.append(file)


This allows Arcade Legends to locate every winner photograph associated with a particular player.

If photographs exist, the most recent image is selected:


Python:

latest_photo = sorted(photos)[-1]

and displayed.

This simple feature transformed winner photographs from isolated image files into part of a player's history.

A player's victories could now follow them throughout the arcade.

Another important requirement was ranking players.

The Hall Of Fame isn't simply a photo gallery.

It is a record of achievement.

To support this, player progression data stored inside profile files could be analyzed and compared.

For example:


12580 XP

Level 14


might rank higher than:


4200 XP

Level 5


The Hall Of Fame therefore serves two purposes simultaneously:

  1. Preserving memorable moments.
  2. Recognizing long-term achievement.

One thing I quickly learned during testing was that photographs dramatically changed player behaviour.

Before introducing the Hall Of Fame, players cared primarily about winning.

After introducing it, players also cared about being remembered.

There is something surprisingly motivating about knowing your photograph may become part of a permanent archive that future players will see.

This psychological shift was exactly what I had hoped for.

The Hall Of Fame transformed success from something temporary into something lasting.

Another major benefit of the Hall Of Fame was that it connected multiple systems together.

Without it:

  1. RFID Profiles are isolated.
  2. Winner Photos are isolated.
  3. XP is isolated.
  4. Achievements are isolated.

With the Hall Of Fame:

  1. RFID identifies the player.
  2. Profiles load their statistics.
  3. XP measures their progression.
  4. Achievements record accomplishments.
  5. Winner photographs preserve special moments.

All of these systems suddenly become part of a single player story.

That level of integration was one of my primary goals throughout the project.

I never wanted Arcade Legends to feel like a collection of unrelated features.

I wanted every system to contribute to a larger ecosystem.

Looking back, the Hall Of Fame represents one of the most important moments in the development of Arcade Legends because it changed the purpose of the project.

Players were no longer simply playing games.

They were building a legacy.

Every achievement unlocked.

Every level gained.

Every boss defeated.

Every photo captured.

All of it now had a permanent place inside the arcade's history.

What began as a collection of LEDs, sensors, and Python scripts had evolved into something much more personal.

It had become a machine capable of remembering the people who played it.

Most arcade machines remember scores. Arcade Legends remembers the players who earned them. The Hall Of Fame became the place where every achievement, every victory, and every unforgettable moment could live on long after the game itself had ended. šŸ†šŸ“øšŸŽ®

Building the Voice and Personality System

šŸ”„ I Spent 6 Months Building the Ultimate Raspberry Pi Arcade

By this point, Arcade Legends could recognize players, save profiles, award XP, unlock achievements, take photographs, and record winners. However, despite all of these features, something still felt missing.

The machine could display information.

The machine could flash lights.

The machine could play sounds.

But the machine couldn't speak.

I wanted Arcade Legends to feel less like a collection of electronics and more like a living arcade machine that could interact directly with players. Rather than displaying everything through text, the arcade should be able to welcome players, announce achievements, celebrate victories, mock poor performance, and guide users through the experience using its own voice.

This led to the development of one of my favourite systems in the project: the Voice System.

To implement voice synthesis, I used Microsoft's Edge Text-To-Speech (edge-tts) library.


Python:

import edge_tts

import asyncio


Unlike pre-recorded audio files, edge-tts allows speech to be generated dynamically at runtime. This means the arcade can say a player's name, announce achievements, or generate entirely new messages without requiring hundreds of prerecorded sound clips.

I selected the voice:


Python:

VOICE = "en-US-GuyNeural"


because it sounded clear, natural, and suitable for an arcade environment.

The central function that powers all voice feedback throughout Arcade Legends is:


Python:

def Speak(text):


Whenever text is passed into this function, the arcade automatically converts it into speech.

The process begins by creating speech audio using Edge TTS:


Python:

communicate = edge_tts.Communicate(text=text,voice=VOICE,rate="+5%")


I slightly increased the speech rate:


Python:

rate="+5%"

because the default voice felt a little too slow for an arcade environment.

Once the audio is generated, it is saved as:


Python:

speech.mp3


using:


Python:

await communicate.save("speech.mp3")


Before generating new speech, the previous file is removed:


Python:

if os.path.exists("speech.mp3"):

os.remove("speech.mp3")


This prevents old audio files from interfering with newly generated speech.

Finally, the generated audio is played through the Raspberry Pi speaker system using:


Python:

subprocess.run(["mpg123", "-q", "speech.mp3"])


This entire process happens automatically whenever:


Python:

Speak("message")


is called.

One of the first places I integrated speech was the RFID login system.

When a returning player scans their RFID card:


Python:

Speak(f"Welcome back {current_user}")


is executed.

Instead of silently loading a profile, the arcade greets the player by name.

This sounds like a small feature, but it dramatically improves immersion.

The machine no longer feels like software running on a Raspberry Pi.

It feels aware.

Achievements were another perfect use case for voice feedback.

Inside the achievement system:


Python:

Speak(f"Achievement unlocked. {name}")


announces every unlocked achievement.

When a player earns:


MEMORY MASTER


the arcade literally tells them.

Combined with LCD messages, LED effects, and buzzer feedback, achievements suddenly feel much more important.

I also used speech extensively in gameplay.

For example, the grading system in Reflex-based games delivers custom responses based on performance.

If a player achieves an exceptional time:


Python:

Speak("WOW! AMAZING!")


If they perform poorly:


Python:

Speak("Just quit at this point")


While intentionally humorous, these voice responses made testing far more entertaining and gave the arcade a distinct personality.

The voice system is also heavily integrated into Color Roulette.

When a round begins, the game announces the target color:


Python:

Speak(f"Stop on {names[target]}")


This means players receive instructions through both visual and audio channels simultaneously.

Similarly, the Winner Photography System uses voice guidance during the countdown:


Python:

Speak("Look At the Camera ... 3..2..1..")


This ensures the player is looking toward the camera when the photograph is taken.

One of the biggest challenges I encountered while building this feature was dealing with temporary audio files and playback reliability.

Early versions occasionally attempted to play a new file before the previous one had been deleted or fully generated.

To solve this, I carefully structured the sequence so that:

  1. Old file is deleted.
  2. New speech is generated.
  3. Audio is saved.
  4. Playback begins.

This completely eliminated the issue.

As more systems were added to Arcade Legends, voice synthesis became increasingly valuable because it connected otherwise separate features.

The voice system now interacts with:

  1. RFID Authentication
  2. Achievements
  3. Player Profiles
  4. Winner Photography
  5. Game Instructions
  6. Reflex Grading
  7. Boss Battles
  8. Color Roulette
  9. Champion Celebrations

In many ways, it became the personality layer sitting on top of all the underlying hardware and software.

Looking back, adding speech transformed the overall experience.

Before this feature, Arcade Legends could display information.

After this feature, Arcade Legends could communicate.

Lights can attract attention. Screens can display information. A voice creates personality. The Voice System transformed Arcade Legends from a machine that players used into a machine that players could interact with. šŸŽ®šŸ†šŸ—£ļø

Building the Retro Arcade Framework

šŸ”„ I Spent 6 Months Building the Ultimate Raspberry Pi Arcade

Up until this point, every game in Arcade Legends was played using physical hardware. Players interacted with LEDs, buttons, buzzers, microphones, RFID cards, and the LCD display. While these games were fun and highly interactive, I wanted the arcade to feel larger than a single hardware project.

I started thinking about the arcade machines I loved growing up. Most of them didn't contain just one game. They contained entire libraries of experiences. Some focused on reflexes, some focused on skill, and others simply challenged players to chase higher scores. I wanted Arcade Legends to capture that same feeling.

This led to one of the biggest expansions in the entire project:

Adding a complete collection of retro desktop arcade games alongside the physical hardware games.

Rather than replacing the physical games, these new additions would complement them. A player could test their memory in Memory Master, compete in Quick Draw, or jump into classic arcade experiences such as Pac-Man, Snake, Galaga, Asteroids, and Flappy Bird without leaving the Arcade Legends ecosystem.

At first, integrating desktop games seemed like a completely different problem from building hardware games.

The physical games relied on:


Python:

gpiozero


for hardware interaction.

The new games needed graphics, animation, movement systems, collision detection, sprite management, sound effects, and keyboard controls.

After researching several different options, I eventually chose Pygame Zero.

Pygame Zero sits on top of Pygame and dramatically simplifies game development.

Instead of manually creating windows, handling game loops, and setting up rendering pipelines, Pygame Zero allows developers to focus primarily on gameplay logic.

Many of my desktop games were launched using:


Python:

py -m pgzero


For example:


Python:

subprocess.Popen(["py","-m","pgzero",r"Pacman.py"])


This starts the Pac-Man game directly from within Arcade Legends.

One challenge I encountered immediately was figuring out how the arcade dashboard would communicate with these games.

I didn't want users to manually open Python files.

I wanted them to launch games through the arcade interface exactly as they would at a real arcade machine.

To accomplish this, I built a Flask-based launcher system.

Inside the dashboard, each game receives its own route.

For example:


Python:

@app.route("/pacman")


When the player clicks:


START GAME


the route executes:


Python:

subprocess.Popen(["py","-m","pgzero",r"Pacman.py"])


This launches the selected game while allowing the dashboard to remain active.

The same architecture was used for multiple games throughout Arcade Legends:


Python:

Pacman.py


Python:

Snake.py


Python:

Galaga.py


Python:

main1.py


for Asteroids

and


Python:

flappy bird/main.py


for Flappy Bird.

Using this approach provided several major advantages.

First, every game remained isolated.

If one game crashed, the rest of Arcade Legends continued functioning.

Second, new games could be added simply by creating a new Python file and launcher route.

Third, every game could have its own assets, logic, and mechanics without affecting the rest of the system.

As the number of games increased, I realized another problem:

Players often had no idea what a game actually was before launching it.

Someone unfamiliar with Galaga might not know they are about to enter a space-shooter.

Someone unfamiliar with Asteroids might not understand the objective.

To solve this, I began creating dedicated information pages for every game.

Rather than launching instantly, Arcade Legends provides:

  1. Game artwork
  2. Mission objectives
  3. Difficulty ratings
  4. Reward descriptions
  5. Start buttons

For example, Pac-Man displays:


MISSION

Travel through the maze eating every pellet.

Avoid the ghosts hunting you down.

Collect power pellets to turn the tables.

Before a single line of game code executes, the player understands exactly what to expect.

This dramatically improved the user experience and made the arcade feel significantly more professional.

Another design decision involved categorization.

Inside the dashboard, I deliberately separated games into two groups:


PHYSICAL ARCADE GAMES


and


RETRO ARCADE


The physical games include:

  1. Memory Master
  2. Quick Draw
  3. Color Roulette
  4. Boss Battle
  5. Hardware-based challenges

The retro section includes:

  1. Pac-Man
  2. Snake
  3. Galaga
  4. Asteroids
  5. Flappy Bird

This separation helps players understand the different types of experiences available within Arcade Legends.

Looking back, building the Retro Arcade Framework was one of the most important architectural decisions I made.

Before this point, Arcade Legends was primarily a hardware project.

After this point, it became a complete gaming platform.

The arcade was no longer limited by LEDs, buttons, or sensors.

Any game that could be developed in Python could potentially become part of the system.

Most importantly, the framework gave me a way to continuously expand the arcade in the future without redesigning everything from scratch.

Future games simply need:

  1. Their own Python file.
  2. Their own launcher route.
  3. Their own information page.

The entire infrastructure already exists.

This step laid the foundation for all of the desktop arcade experiences that follow, and transformed Arcade Legends from a multi-game machine into a truly expandable arcade ecosystem.

The physical games gave Arcade Legends a unique identity. The Retro Arcade Framework gave it unlimited room to grow. šŸŽ®šŸ†šŸš€

Recreating Pac-man

Screenshot 2026-10-25 091749.png
Screenshot 2026-10-25 091913.png
Screenshot 2026-10-25 091924.png
Screenshot 2026-10-25 091931.png
Screenshot 2026-10-25 092240.png
Screenshot 2026-10-25 092320.png
Screenshot 2026-10-25 092336.png
Screenshot 2026-10-25 092402.png
Screenshot 2026-10-25 092415.png
Screenshot 2026-10-25 092451.png
Screenshot 2026-10-25 092517.png
Screenshot 2026-10-25 092538.png
Screenshot 2026-10-25 092550.png
šŸ”„ I Spent 6 Months Building the Ultimate Raspberry Pi Arcade

After successfully building several physical arcade games, I wanted to bring one of the most iconic arcade experiences ever created into Arcade Legends: Pac-Man. While Pac-Man appears simple on the surface, recreating it accurately turned out to be one of the most technically challenging desktop games in the entire project.

I developed the game using Pygame Zero, a framework built on top of Pygame that simplifies game development while still providing full control over graphics, animation, collision detection, keyboard input, audio, and game logic. The project begins by importing the required libraries:


Python:

import pgzrun

import pygame

import math

import random


To create a more authentic arcade atmosphere, I added looping background music:


Python:

pygame.mixer.music.load("static/sounds/galaga.mp3")

pygame.mixer.music.play(-1)


The -1 parameter causes the soundtrack to loop indefinitely while the game is running.

One of the first challenges was constructing the maze itself. Rather than drawing each wall manually, I used a tile-based board system represented as a large two-dimensional array.


Python:

board = [[6,4,4,4,4,4,5],...]


Each number represents a different object within the maze:


0 = Empty Path

1 = Pellet

2 = Power Pellet

3-9 = Wall Sections


Using a tile map offered several advantages. Collision detection became significantly easier, pellets could be tracked programmatically, and editing the maze only required changing numbers rather than redrawing the entire level.

The finished maze contains over 900 individual tiles, closely resembling the structure of the original arcade game.

For movement, I chose a system based on tile coordinates rather than absolute screen positions.


Python:

player_tx

player_ty


store Pac-Man's location within the maze grid.

To make movement feel smooth rather than jumping from square to square, I added:


Python:

player_progress


which interpolates movement between tiles.

This approach creates a far more polished appearance and closely resembles the original game.

Another important feature I implemented was input buffering.


Python:

player_next_dir


stores the next desired movement direction.

This means players can press a direction key slightly before reaching an intersection and Pac-Man will automatically turn when that route becomes available. Without this system, movement feels noticeably less responsive.

To animate Pac-Man's iconic mouth motion, I created four animation frames:


Python:


pacman1

pacman2

pacman3

pacman4

and cycle between them during movement:


Python:

player_anim_frame =(player_anim_frame + 1)% 4


I also rotate the sprite based on travel direction:


Python:

pacman_actor.angle = angle


allowing Pac-Man to visually face left, right, up, and down.

One aspect of development that took much longer than expected was preparing the game assets. Many of the image files required resizing before they would fit correctly within the game grid. I adjusted sprite sizes to match the tile dimensions used throughout the maze and removed unwanted backgrounds from various assets so that characters could be rendered cleanly over the game environment. This was especially important for Pac-Man, ghosts, UI graphics, and other arcade artwork because visible backgrounds would immediately break the illusion of the game world.

The most technically complex part of the project was the ghost AI system.

I created four ghosts:


Python:

Blinky

Pinky

Inky

Clyde


using:


Python:

ghosts = [...]


Each ghost stores:

  1. Position
  2. Movement progress
  3. Current direction
  4. State
  5. Identity
  6. Activation status

Unlike many simple Pac-Man clones where ghosts randomly wander around the maze, I wanted mine to demonstrate recognizable behaviour.

To accomplish this, I implemented both Scatter Mode and Chase Mode.


Python:

SCATTER_TIME = 420

CHASE_TIME = 1200


During Scatter Mode, ghosts travel toward assigned corner positions:


Python:

ghost_corners = {"blinky": (27,0),"pinky": (2,0),"inky": (27,32),"clyde": (0,32)}


During Chase Mode, they actively pursue Pac-Man.

The game automatically alternates between these modes:


Python:

current_mode


using:


Python:

mode_timer


This behaviour recreates one of the defining mechanics of the original Pac-Man and makes the ghosts feel significantly more intelligent.

To support navigation, every ghost evaluates possible movement directions and selects the route that minimizes distance to its target.


Python:

dist = get_dist(nx,ny,tx,ty)


This produces convincing pathfinding without requiring a complicated AI framework.

I also recreated one of the most iconic features of Pac-Man: Power Pellets.

Whenever Pac-Man consumes a tile containing:


Python:

2


the game activates:


Python:

power_mode = True


and rewards:


Python:

player_score += 50


All ghosts then enter a frightened state:


Python:

g[4] = "frightened"


During this mode, ghosts become vulnerable and behave unpredictably.

If Pac-Man catches a frightened ghost:


Python:

if g[4] == "frightened":


the ghost changes to:


Python:

g[4] = "eaten"


and the player receives:


Python:

player_score += 200


The ghost then returns to the ghost house before rejoining the game.

Implementing this required separate logic for:

  1. Normal state
  2. Frightened state
  3. Eaten state
  4. Ghost house state
  5. Leaving house state

Another feature I recreated was the classic tunnel system. Using modular arithmetic:


Python:

(player_tx + 1) % 30


Pac-Man can travel off one side of the maze and reappear on the opposite side, just like in the arcade original.

Collision detection was handled using distance calculations:


Python:

if get_dist(gx,gy,px,py)< 15:


This determines whether Pac-Man has collided with a ghost and triggers the appropriate behaviour depending on that ghost's current state.

Finally, I implemented a complete reset system:


Python:

reset_game()


which restores:

  1. Player position
  2. Ghost positions
  3. Animation states
  4. Spawn timers
  5. Movement variables

whenever a life is lost.

What surprised me most about this project was how much engineering exists behind a game that appears so simple. Players only see a maze, a yellow character, and a few ghosts. Behind the scenes, however, the game contains tile-based navigation, animation systems, AI state machines, pathfinding algorithms, multiple ghost behaviours, frightened states, pellet management, collision detection, tunnel mechanics, score tracking, and dynamic mode switching.

Recreating these systems gave me a much deeper appreciation for the original Pac-Man developers and demonstrated how much sophisticated logic can exist beneath apparently simple gameplay.

By the end of development, Pac-Man became one of the flagship desktop games within Arcade Legends and proved that the platform could successfully combine custom hardware experiences with faithful recreations of classic arcade legends.

Rebuilding Pac-Man taught me that great arcade games are not built from complex graphics. They are built from elegant systems working together perfectly, a lesson that heavily influenced the rest of Arcade Legends. šŸ†šŸ‘»šŸŽ®

Downloads

Recreating Snake

Screenshot 2026-10-25 183749.png
Screenshot 2026-10-25 183931.png
Screenshot 2026-10-25 185214.png
Screenshot 2026-10-25 185223.png
Screenshot 2026-10-25 185235.png
Screenshot 2026-10-25 185249.png
Screenshot 2026-10-25 185257.png
Screenshot 2026-10-25 185304.png
Screenshot 2026-10-25 185316.png

After recreating Pac-Man, I wanted to add another arcade classic that was instantly recognizable and easy for anyone to play. The obvious choice was Snake.

Unlike Pac-Man, which relies on maze navigation, ghost AI, pathfinding, and multiple gameplay states, Snake is built around a deceptively simple concept:

Eat apples, grow longer, and avoid crashing into yourself.

Although the rules are easy to understand, creating a polished version required much more than simply moving a line of blocks around the screen.

Like my other retro games, Snake was developed using Pygame Zero.


Python:

import pgzrun

import pygame

import random


To create a more immersive arcade experience, I also added background music that continuously loops while the game is running:


Python:

pygame.mixer.music.load("static/sounds/snake.mp3")

pygame.mixer.music.play(-1)


The snake itself is represented using a list of coordinate positions.


Python:

snake = [(COLS // 2, ROWS // 2),(COLS // 2 - 1, ROWS // 2),(COLS // 2 - 2, ROWS // 2),(COLS // 2 - 3, ROWS // 2)]

Each tuple stores the location of a body segment within the game grid.

The first coordinate represents the snake's head:


Python:

snake[0]

while every following coordinate represents another section of the body.

This approach makes the snake easy to grow because new body segments can simply be inserted into the list.

Building a Proper Sprite System

One area where I wanted this version to differ from many beginner Snake projects was the graphics.

Most simple Snake games are built entirely from coloured rectangles.

I wanted something that felt much closer to a real arcade game.

To accomplish this, I created separate sprites for:


Python

snake_head

snake_body

snake_corner

snake_tail

apple


These assets can be seen in the image above.

Before they could be used, I needed to prepare each graphic manually.

This involved:

  1. Resizing images
  2. Removing unwanted backgrounds
  3. Creating transparent PNG files
  4. Ensuring every sprite matched the game grid correctly

Without proper transparency, visible white backgrounds would appear around the snake pieces and immediately ruin the visual presentation.

The final result allows the snake to appear as a complete creature rather than a collection of blocks.

Defining the Play Area

The game operates inside a grid-based environment.


Python:

WIDTH = 600

HEIGHT = 400


Every movement occurs within cells of:


Python:

CELL_SIZE = 20


This creates:


Python:

COLS = WIDTH // CELL_SIZE

ROWS = HEIGHT // CELL_SIZE


which define the dimensions of the playable area.

Using a grid simplifies movement, collision detection, food spawning, and positioning.

Smooth Direction Control

Player movement is controlled using:


Python:

direction


and


Python:

next_direction


The reason for having two variables is to prevent impossible turns.

For example, if the snake is moving right:


Python:

(1, 0)


the player should not be able to instantly move left:


Python:

(-1, 0)


because that would result in immediate self-collision.

To prevent this, I implemented:


Python:

opposite = (-direction[0],-direction[1])

if next_direction != opposite:


This simple check dramatically improves gameplay by preventing accidental instant deaths.

Timing the Movement

Instead of moving every frame, Snake moves at fixed intervals.

The game uses:


Python:

MOVE_DELAY = 0.15


This means the snake advances once every:


0.15 seconds


Movement timing is controlled using:


Python:

move_timer += dt


and:


Python:

if move_timer < MOVE_DELAY:

return


This approach ensures consistent speed regardless of frame rate.

Apple Generation System

Whenever a new game begins, an apple needs to appear on the board.

I implemented a dedicated function:


Python:

def place_apple():


which searches for every free grid position:


Python:

free = [(x,y)for x in range(COLS)for y in range(ROWS)if (x,y) not in snake]

A random location is then selected:


Python:

apple_pos = random.choice(free)


This guarantees apples never spawn inside the snake itself.

Without this validation, players could occasionally receive impossible-to-reach food items.

Growing the Snake

When the snake reaches an apple:


Python:

if new_head == apple_pos:


a new apple is generated.

Normally, movement removes the final body segment:


Python:

snake.pop()


However, when an apple is consumed, the tail is not removed.

This causes the snake to grow by one segment.

As more apples are collected, the snake becomes progressively longer and more difficult to control.

Collision Detection

The primary challenge in Snake comes from avoiding collisions.

To detect failure, I used:


Python:

if new_head in snake:


If the head enters a coordinate already occupied by the body, the game instantly ends.

Unlike many modern Snake games, I chose to immediately restart the game using:


Python:

reset_game()


This keeps gameplay fast and encourages players to jump straight back into another attempt.

Screen Wrapping

One design decision I particularly liked was adding screen wrapping.

When the snake reaches the edge of the board:


Python:

new_head = ((hx + direction[0]) % COLS,(hy + direction[1]) % ROWS


the snake appears on the opposite side.

Rather than crashing into walls, players can use the edges strategically to navigate around their growing body.

This small mechanic adds a surprisingly large amount of depth to the gameplay.

Dynamic Snake Rendering

One of the most technically interesting parts of the project was drawing the snake correctly.

Straight body segments use:


Python:

snake_body


Corner turns use:


Python:

snake_corner


The game automatically analyses neighbouring segments:


Python:

d1 = vec(cur, prev)

d2 = vec(cur, nxt)


and determines whether the current section should be drawn as a straight section or a turning corner.

This allows the snake to bend naturally around the board.

Without this system, the snake would look rigid and artificial.

Instead, every turn appears smooth and visually pleasing.

The tail also rotates automatically:


Python:

tail.angle =angle_from_dir(d)


while the head rotates according to the current movement direction.

This creates a professional appearance despite being built from only a handful of sprite assets.

Integration with Arcade Legends

Like every other retro game, Snake integrates directly into the Arcade Legends launcher.

Through the dashboard, players can navigate to:


Python:

/ snake_info


to learn about the game before launching it.

The game itself is started using:


Python:

subprocess.Popen(["py","-m","pgzero",r"Snake.py"]


This architecture allows Snake to run independently while remaining fully integrated into the broader Arcade Legends ecosystem.

Lessons Learned

Snake taught me an important lesson about game design.

Pac-Man required pathfinding, animation systems, state transitions, and numerous gameplay mechanics.

Snake, by comparison, relies on only a handful of systems:

  1. Movement
  2. Growth
  3. Food Collection
  4. Self-Collision

Yet it remains incredibly addictive.

The tension created by an increasingly large snake and a shrinking amount of free space makes every apple feel important.

Sometimes the most engaging arcade experiences come from the simplest ideas.

By the end of development, Snake became a valuable addition to the Retro Arcade collection and demonstrated that a game does not need complicated mechanics to keep players coming back.

Snake proved that great game design isn't about complexity. With nothing more than movement, growth, and survival, it remains one of the most addictive arcade games ever created. šŸšŸ†šŸŽ®

Downloads

Recreating Asteroids

Screenshot 2026-10-25 184227.png
Screenshot 2026-10-25 184251.png
Screenshot 2026-10-25 185437.png
Screenshot 2026-10-25 185446.png
Screenshot 2026-10-25 185454.png
Screenshot 2026-10-25 185507.png
Screenshot 2026-10-25 185521.png
Screenshot 2026-10-25 185531.png
Screenshot 2026-10-25 185550.png
Screenshot 2026-10-25 185642.png
Screenshot 2026-10-25 185651.png
Screenshot 2026-10-25 185703.png
Screenshot 2026-10-25 185713.png

After recreating Pac-Man and Snake, I wanted to tackle a game with a completely different style of gameplay. Instead of navigating a maze or controlling a growing creature, I wanted players to feel like they were piloting a spacecraft through a dangerous asteroid field.

This led to the development of Asteroids, a fast-paced arcade shooter where survival depends on accuracy, reaction speed, and resource management.

Like the other retro games in Arcade Legends, Asteroids was built using Pygame Zero. However, this project also introduced something new:


Python:

from pgzhelper import *


PGZHelper extends Pygame Zero with additional functionality such as:

  1. Advanced Actor controls
  2. Animation systems
  3. Directional movement
  4. Collision detection
  5. Sprite transformations
  6. Distance calculations

Using this library allowed me to focus on gameplay rather than repeatedly implementing common game mechanics.

Creating the Space Environment

The first step was constructing the game world.

I selected a dedicated background image:


Python:

background = Actor('spacebg')


which immediately transformed the game window into a deep-space environment.

Unlike the grid-based worlds used in Snake and Pac-Man, Asteroids takes place in an open arena:


Python:

WIDTH = 800

HEIGHT = 600


This larger play area gives asteroids room to travel naturally across the screen from different directions.

Building the Player Ship

The player controls a spacecraft represented by:


Python:

ship = Actor('playership')

The ship begins near the bottom of the screen:


Python:

ship.x = 400

ship.y = 550


To make the controls feel responsive, I linked the ship directly to mouse movement:


Python:

def on_mouse_move(pos, rel, buttons):

ship.x = pos[0]

ship.y = pos[1]


Wherever the cursor moves, the spacecraft follows.

This creates a much more arcade-like experience than traditional keyboard movement.

I also added a second damaged sprite:


Python:

ship.images = [ship.image,'playership_damage']


allowing the ship to visibly react after collisions.

Designing the Asteroids

The game contains multiple asteroid designs:


Python:

meteorbrown1

meteorbrown2

meteorbrown3

meteorbrown4

meteorgrey1

meteorgrey2

meteorgrey3

meteorgrey4


These assets required preparation before they could be used.

Many images needed:

  1. Background removal
  2. Resizing
  3. Transparency correction

to ensure they blended naturally with the space environment.

The finished game contains a mixture of brown and grey asteroid types which gives the battlefield greater visual variety.

At startup, the asteroids are placed randomly around the play area:


Python:

for meteor in meteors:

relocate(meteor)


The relocation function ensures that asteroids never spawn directly on top of the player ship.

Rotating Meteors

One feature I particularly liked was continuously rotating asteroids.

Every update cycle executes:


Python:

rotate(meteor)


which increases the asteroid's angle:


Python:

actor.angle += 1


This simple effect dramatically improves visual realism.

Rather than appearing as static images sliding across the screen, the asteroids feel alive and dynamic.

Asteroid Movement

Each asteroid is assigned a random direction:


Python:

actor.direction = random.randint(0,359)


They then travel through space using:


Python:

meteor.move_in_direction(...)


with a randomly selected speed.

This ensures that every asteroid behaves differently, making each playthrough unique.

Whenever an asteroid leaves the screen boundaries:


Python:

if meteor.x > 800


or


Python:

if meteor.y > 600


the asteroid is relocated and reintroduced into the game.

This creates an endless stream of incoming threats.

Implementing the Laser System

No space shooter would be complete without weapons.

When the player clicks the mouse:


Python:

def on_mouse_down()


a laser projectile is created:


Python:

laser = Actor('laser')


The laser is positioned directly in front of the spacecraft:


Python:

laser.x = ship.x

laser.y = ship.y - 30


and added to the active projectile list:


Python:

lasers.append(laser)


I also added a firing sound effect:


Python:

sounds.laser.play()


to make combat feel more satisfying.

Collision Detection

Whenever a laser intersects with an asteroid:


Python:

meteor.collidelist(lasers)

the asteroid is destroyed.


This immediately triggers:


Python:

score += 1


allowing the player to earn points.

The asteroid is then relocated and returned to the battlefield.

This creates constant action and encourages accurate shooting.

Explosion Animations

I wanted destroyed asteroids to feel rewarding.

Instead of simply disappearing, each asteroid triggers a full explosion animation.

The animation consists of ten separate frames:


Python:

explosion_1

explosion_2

...

explosion_10


These are loaded into:


Python:

explotion.images = [...]


and animated automatically:


Python:

explotion.animate()


The result is a dramatic explosion sequence whenever a meteor is destroyed.

Combined with:


Python:

sounds.explosion.play()


this creates a much more polished arcade experience.

Health System

Unlike many simple space shooters, Asteroids includes a proper health system.

The player begins with:


Python:

health = 19


which is displayed using a dynamic health bar:


Python:

health_bar =

Actor(f'health{health}')


To support this mechanic, I created twenty separate health assets:


Python:

health0

health1

...

health19


Each image visually represents the player's remaining health.

Whenever the ship collides with a meteor:


Python:

ship.collidelist(meteors)


health is reduced:


Python:

health -= 1


and a damage sound effect plays.

This gives players multiple chances to recover rather than ending the game immediately after a single mistake.

Power-Up System

To help players survive longer, I also implemented collectible power-ups.

When health falls below maximum:


Python:

if health < 19


a power-up can spawn:


Python:

powerup = Actor('powerup')


The player then has an opportunity to collect it.

If the ship collides with the power-up:


Python:

powerup.colliderect(ship)


health increases:


Python:

health += 1


and a power-up sound effect is played.

This feature adds a risk-versus-reward element because players must decide whether to chase healing while avoiding incoming asteroids.

Music and Audio

To strengthen the arcade atmosphere, I added looping background music:


Python:

music.play('music1')


which plays throughout the session.

Combined with:

  1. Laser effects
  2. Explosion sounds
  3. Damage sounds
  4. Power-up sounds
  5. Game-over effects

the game feels significantly more immersive.

Game Over System

Once the player's health reaches zero:


Python:

if health <= 0


the game enters a dedicated game-over state.

The screen displays:


Score: X

Game Over


while all active objects are removed:


Python:

powerups.clear()

meteors.clear()

explosions.clear()

lasers.clear()


The background music stops:


Python:

music.stop()


and a final game-over sound is played.

This provides a clear and satisfying conclusion to each run.

Lessons Learned

Asteroids taught me a lot about combining animation, audio, collision detection, and visual effects into a cohesive experience.

Compared to Snake's simplicity and Pac-Man's AI systems, Asteroids focused heavily on moment-to-moment action.

Building rotating asteroids, laser combat, health management, power-ups, explosions, and sound design gave me valuable experience in creating games that feel responsive and exciting.

It was also one of the first games in Arcade Legends where visual feedback became just as important as gameplay mechanics.

By the end of development, Asteroids became one of the most polished titles within the Retro Arcade collection and demonstrated how far the Arcade Legends platform had evolved from its original hardware-only beginnings.

Asteroids transformed Arcade Legends from a collection of retro challenges into a true arcade experience, combining fast-paced action, visual effects, audio feedback, and player progression into a single explosive package. ā˜„ļøšŸš€šŸ†

Downloads

Recreating Galaga

Screenshot 2026-10-25 183127.png
Screenshot 2026-10-25 183159.png
Screenshot 2026-10-25 183215.png
Screenshot 2026-10-25 185848.png
Screenshot 2026-10-25 185854.png
Screenshot 2026-10-25 185906.png

After recreating Pac-Man, I wanted to add a game that focused on an entirely different type of gameplay. While Pac-Man revolves around navigation, pathfinding, and avoiding enemies, I wanted my next game to centre around fast reactions, precision shooting, and surviving increasingly difficult enemy waves. For this reason, I chose to recreate one of the most iconic space shooters in arcade history: Galaga.

Like the other retro games in Arcade Legends, Galaga was developed using Pygame Zero. This allowed me to focus on gameplay systems rather than spending large amounts of time building a rendering engine from scratch. The result was a fully playable space shooter featuring enemy formations, projectile systems, explosions, boss battles, level progression, sound effects, music, and score tracking.

The game begins by loading and continuously looping background music:


Python:

pygame.mixer.music.load("galaga.mp3")

pygame.mixer.music.play(-1)


I deliberately chose looping music because arcade shooters feel significantly more exciting when gameplay is accompanied by an energetic soundtrack. Rather than making the game feel silent or empty, the music immediately establishes the atmosphere of a classic space arcade.

The player controls a starfighter positioned near the bottom of the screen:


Python:

player = Actor("galaga")

player.center = (WIDTH // 2,HEIGHT - 80)

Using the keyboard, players can move left and right across the screen while attempting to eliminate incoming enemies and avoid enemy projectiles.

One of the first features I added was a fully animated starfield background.

Instead of drawing a static image, I generated stars dynamically:


Python:

stars.append([random.randint(0, WIDTH),random.randint(0, HEIGHT),random.randint(1, 4)])


A total of:


Python:

150


individual stars are created.

Each star moves independently:


Python:

star[1] += star[2]


creating the illusion that the player is travelling through space.

Small details like this do not affect gameplay directly, but they dramatically improve immersion.

Unlike some simple Galaga recreations that only feature one enemy type, I wanted multiple enemies with different visual identities.

Enemy waves can contain:


Python:

bug


and


Python:

bee

ships


Python:

enemy_type =random.choice(["bug","bee"])


These enemies are spawned through a dedicated wave generation system:


Python:

create_wave()


This function automatically constructs formations of enemies based on the player's current level.

The number of columns increases with progression:


Python:

cols = min(12,8 + level)


while the number of rows also grows as players advance further into the game.

As a result, later levels contain significantly larger enemy formations than the opening stages.

This creates an automatic difficulty curve without requiring players to select a difficulty setting manually.

One of the mechanics I was most interested in building was wave movement.

Instead of moving independently, enemies behave like a formation and move together across the screen.


Python:

for enemy in enemies:

enemy.x +=

enemy_speed *direction


When the formation reaches the edge of the screen:


Python:

if rightmost >= WIDTH - 50


the direction reverses:


Python:

direction = -1


and the entire wave descends closer to the player.

Likewise, when enemies reach the opposite edge:


Python:

direction = 1


is triggered.

This behaviour recreates one of the defining mechanics of Galaga and constantly increases pressure on the player.

As development progressed, I wanted every level to feel more dangerous than the previous one.

To achieve this, enemy speed increases automatically whenever a wave is cleared:


Python:

enemy_speed += 0.5


This simple modification creates a highly effective progression system because even familiar enemy patterns become more difficult at higher speeds.

The most exciting feature in the game is undoubtedly the Boss Battle System.

Every third level:


Python:

if level % 3 == 0:


a boss enemy is added to the formation.


Python:

boss = Actor("boss")


Unlike standard enemies, the boss has its own health pool:


Python:

boss.health = 50

boss.max_health = 50

and cannot be destroyed with a single shot.

To help players track their progress during these battles, I created a dedicated health bar:


Python:

screen.draw.filled_rect(...)

The health display updates dynamically as the boss takes damage.

This transforms the gameplay from a simple shooting gallery into a more strategic encounter where players must survive while gradually reducing the boss's health.

Combat itself is based around the projectile system.

When the player presses:


Python:

SPACE


a projectile is created:


Python:

bullet = Actor("bullet")


and added to the active bullet list.


Python:

bullets.append(bullet)


Each frame, bullets travel upward:


Python:

bullet.y -= 14


until either:

  1. They hit an enemy.
  2. They leave the screen.

Enemy collisions are detected using:


Python:

bullet.colliderect(enemy)


When an enemy is destroyed, I wanted the event to feel impactful rather than simply making the sprite disappear.

To accomplish this, I built an explosion animation system.


Python:

create_explosion(enemy.x,enemy.y)


This creates a temporary explosion actor using:


Python:

explosion1

and


Python:

explosion2

sprites.


The explosion is removed automatically after its animation finishes.


Python:

e.timer -= 1

The result is a much more satisfying visual effect than instantly deleting the enemy.

Enemy ships are also capable of fighting back.

At random intervals:


Python:

if random.random() < 0.015:


an enemy is selected:


Python:

shooter =random.choice(enemies)


and fires a projectile toward the player.


Python:

enemy_bullets.append(b)


These enemy attacks force players to remain mobile rather than simply holding the fire button.

The game also tracks:


Python:

Score

Lives

Level

High Score


throughout the entire session.

Destroying standard enemies awards:


Python:

10 points


while defeating a boss awards:


Python:

500 points


Every time the player achieves a new record:


Python:

if score > highscore:


the high score value updates automatically.

To balance difficulty, I included a life system:


Python:

lives = 3


Whenever an enemy projectile collides with the player's ship:


Python:

lives -= 1


Once all lives are lost, the game transitions into a dedicated game-over state where players can restart and attempt a higher score.

One area of development that required more work than expected was asset preparation. The game relies on a collection of custom sprites including player ships, enemies, bullets, explosions, bosses, and space backgrounds. Many of these images required resizing to fit the game's scale correctly, while backgrounds and unwanted elements needed to be removed so the sprites could be rendered cleanly within the gameplay environment. Proper asset preparation was essential because even well-written gameplay can look unpolished when visual elements are inconsistent.

Looking back, Galaga became one of the most action-packed experiences in Arcade Legends. Unlike Pac-Man, which focuses on planning and navigation, Galaga rewards reflexes, accuracy, and survival under pressure. The combination of wave systems, increasing difficulty, enemy projectiles, boss battles, animated explosions, dynamic backgrounds, sound effects, and high-score chasing helped recreate the excitement that made the original game an arcade classic.

Galaga demonstrated how much depth can emerge from a simple idea. By combining progression systems, bosses, visual effects, music, and fast-paced combat, I transformed a straightforward shooter into one of the most addictive retro experiences inside Arcade Legends. šŸ†šŸš€šŸ‘¾

Downloads

Recreating Flappy Bird

Screenshot 2026-10-25 184711.png
Screenshot 2026-10-25 184728.png
Screenshot 2026-10-25 184832.png
Screenshot 2026-10-25 190008.png
Screenshot 2026-10-25 190014.png
Screenshot 2026-10-25 190021.png
Screenshot 2026-10-25 190037.png
Screenshot 2026-10-25 190047.png
Screenshot 2026-10-25 190057.png
Screenshot 2026-10-25 190110.png

After developing larger games such as Pac-Man and Galaga, I wanted to add something that demonstrated an entirely different type of challenge. Rather than relying on enemies, projectiles, power-ups, or complex AI systems, I wanted a game where difficulty emerged from a single mechanic that players could understand instantly but spend hours trying to master.

This led to the development of Flappy Bird, one of the most deceptively difficult games ever created.

Like the other retro titles in Arcade Legends, Flappy Bird was developed using Pygame Zero. Despite the game's simple appearance, recreating it required careful handling of physics, animation, collision detection, scoring, audio feedback, and asset management.

The game begins by defining the screen dimensions:


Python:

WIDTH = 640

HEIGHT = 480


and loading a collection of custom visual assets, including:


Python:

bird1

bird2

bird3

bird4

bird

dead


along with:


Python:

top

bottom

ground

gameover

start

bg

bg-night


Unlike many beginner Flappy Bird clones that use a single image for the player, I wanted the bird to feel alive.

To accomplish this, I created an animated sprite system using:


Python:

bird.images = ['bird1','bird2','bird3','bird4']


combined with:


Python:

bird.fps = 10


and:


Python:

bird.animate()


This continuously cycles through multiple wing positions, producing the iconic flapping animation that gives the game its name.

One of the most important systems in Flappy Bird is gravity.

Without gravity, the player would simply float across the screen.

I implemented this using:


Python:

gravity = 0.3


and a velocity value:


Python:

bird.speed


During every frame:


Python:

bird.y += bird.speed

bird.speed += gravity


This creates the realistic falling motion that constantly pulls the bird toward the ground.

To counteract gravity, the player can click the mouse.

When a click occurs:


Python:

on_mouse_down()


executes.

If the bird is alive, an upward impulse is applied:


Python:

bird.speed = -6.5


This instantly launches the bird upward.

Finding the correct values for gravity and jump strength required considerable testing. If gravity was too weak, the game felt slow and unchallenging. If gravity was too strong, the bird became nearly impossible to control.

Eventually, I found a combination that felt responsive while still maintaining the difficulty that made the original game so addictive.

A major obstacle system was also required.

I created two pipe actors:


Python:

top_pipe

bottom_pipe


which form the gaps the player must fly through.

The gap size is defined by:


Python:

gap = 150


and the pipes move continuously across the screen using:


Python:

pipe_speed = -4


Every frame:


Python:

top_pipe.x += pipe_speed

bottom_pipe.x += pipe_speed


causing the obstacles to travel from right to left.

Once a pipe leaves the screen:


Python:

if top_pipe.x < -50:


a completely new obstacle is generated.

The vertical position is randomized:


Python:

move = random.uniform(-100,-200)


This ensures every obstacle layout is different, preventing players from simply memorizing patterns.

The new pipe locations are then calculated automatically:


Python:

top_pipe.midleft =(WIDTH,move)

bottom_pipe.midleft =(WIDTH,move +top_pipe.height +gap)


This system continuously generates fresh challenges without requiring manually designed levels.

Scoring is intentionally simple.

Whenever a pipe successfully exits the screen, the player receives:


Python:

score += 1


This creates a scoring system directly tied to survival.

The longer a player remains alive, the higher their score becomes.

To encourage replayability, I also implemented a permanent session record:


Python:

maxscore


Whenever a new record is achieved:


Python:

if maxscore < score:

maxscore = score


the value updates automatically.

This gives players a constant reason to improve.

Collision detection was another crucial component.

The bird can fail in three different ways:

Hitting the ground:


Python:

bird.y > HEIGHT - 40


Flying too high:


Python:

bird.y < -150


or colliding with a pipe:


Python:

bird.colliderect(top_pipe)

bird.colliderect(bottom_pipe)


If any of these conditions occur:


Python:

bird.alive = False


and the game transitions into the game-over state.

To make failure feel impactful, I added multiple sound effects.

Different events trigger different sounds:

Wing flap:


Python:

sounds.wing.play()


Point earned:


Python:

sounds.point.play()


Pipe collision:


Python:

sounds.hit.play()


Death:


Python:

sounds.die.play()


These sounds provide immediate feedback and make successful runs feel significantly more rewarding.

Another feature I added was support for multiple backgrounds.

The game stores:


Python:

bg


and


Python:

bg-night


versions of the environment.

After a game over, players can cycle between background themes, giving the game slightly more variety and visual customization.

I also created dedicated states for:


Python:

GAME_MENU


Python:

GAME_PLAYING


Python:

GAME_OVER


This state-based architecture prevents gameplay, menus, and restart logic from interfering with one another and makes the game significantly easier to maintain.

One aspect of development that required more effort than expected was asset preparation. The game relies on multiple bird animation frames, pipe graphics, menu screens, backgrounds, and game-over overlays. Many of these assets needed resizing so that they fit the game's resolution correctly, while others required cleaning, cropping, and background removal to ensure a consistent visual style throughout the project. Organising all artwork into dedicated image folders also made future development significantly easier.

What makes Flappy Bird particularly interesting from a game-design perspective is that it contains very few mechanics. There are no enemies, weapons, bosses, inventories, or upgrade systems. Everything depends on a player's ability to control a single character moving through a series of obstacles.

Yet despite its simplicity, the combination of physics, timing, scoring, sound effects, animation, and randomized obstacles creates an experience that is incredibly difficult to master.

By the time development was complete, Flappy Bird became one of the most accessible games in Arcade Legends. New players could understand the objective within seconds, but achieving a high score still required significant concentration and practice.

Flappy Bird proved that great games do not need complicated systems. With the right balance of physics, timing, challenge, and feedback, even a single-button mechanic can become an addictive arcade experience. šŸ†šŸ¦šŸŽ®

Downloads

Developing ARC-X: the Last Stand

Screenshot 2026-10-25 190624.png
Screenshot 2026-10-25 190641.png
Screenshot 2026-10-25 190703.png
Screenshot 2026-10-25 190548.png
Screenshot 2026-10-25 190830.png
Screenshot 2026-10-25 190845.png
Screenshot 2026-10-25 190908.png
Screenshot 2026-10-25 190919.png
Screenshot 2026-10-25 190929.png
Screenshot 2026-10-25 190943.png
Screenshot 2026-10-25 190955.png
Screenshot 2026-10-25 191005.png
Screenshot 2026-10-25 191016.png

After successfully recreating several classic arcade games, I wanted to challenge myself in a completely different way. Rather than rebuilding an existing title, I decided to design an original game from the ground up. This project eventually became ARC-X: The Last Stand, a side-scrolling action shooter featuring custom levels, enemy artificial intelligence, animated characters, weapon systems, explosives, environmental hazards, pickups, scrolling backgrounds, and a dedicated level editor.

Unlike the retro recreations contained within Arcade Legends, ARC-X was developed entirely in Pygame. This provided much greater control over the game world and allowed me to build systems that extended beyond traditional arcade mechanics.

From the beginning, one of my main goals was to create levels that felt large and immersive. Instead of keeping the player inside a single fixed screen, ARC-X uses a scrolling world spanning:


Python:

COLS = 150


tiles across multiple independently designed levels.

The game world itself is generated from level files:


Python:

level1_data.csv

level2_data.csv

level3_data.csv


which store every object, platform, obstacle, pickup, enemy, and environmental feature. By reading these files at runtime, the game can dynamically construct an entire level rather than hard-coding every object individually.

To make the environment feel more alive, I implemented a layered parallax scrolling system. Separate background layers include:


Sky

Mountains

Pine Forest Layer 1

Pine Forest Layer 2


Each layer moves at a slightly different speed:


Python:

bg_scroll * 0.5

bg_scroll * 0.6

bg_scroll * 0.7

bg_scroll * 0.8


creating a convincing illusion of depth. Distant mountains move slowly while nearby trees move much faster, giving the appearance of a much larger world beyond the playable area.

At the centre of the game is the player character, which is represented by the:


Python:

Soldier

class.


The soldier contains a complete animation framework supporting multiple states:


Python:

Idle

Run

Jump

Death


Each animation automatically loads every frame from dedicated folders:


Python:

img/player/Idle

img/player/Run

img/player/Jump

img/player/Death


and transitions between states depending on player actions and game events.

Movement is built around a physics system that includes gravity:


Python:

GRAVITY = 0.75


vertical velocity calculations:


Python:

self.vel_y


and collision-aware jumping.

This allows the character to move naturally through the environment rather than simply teleporting between positions.

Combat plays a major role throughout the game.

The player can fire projectiles using:


Python:

player.shoot()


which creates independent bullet objects containing:


Python:

position

direction

speed

collision logic


Every shot consumes ammunition:


Python:

player.ammo -= 1


forcing players to think strategically rather than firing continuously.

To make combat more dynamic, I also implemented a grenade system.

Unlike bullets, grenades follow an arc influenced by gravity:


Python:

self.vel_y += GRAVITY


and bounce against terrain before exploding.

Each grenade uses a countdown timer:


Python:

self.timer


which eventually triggers an explosion that damages all nearby enemies.

This creates tactical opportunities where players can eliminate multiple targets with a single attack.

Enemy behaviour was another area that received significant attention.

Rather than standing motionless, enemy soldiers patrol the environment using a dedicated AI system.

Each enemy maintains its own detection zone:


Python:

self.vision


which is represented by a rectangular field of view.

Whenever the player enters this area:


Python:

self.vision.colliderect(player.rect)


the enemy immediately stops patrolling and begins attacking.

This simple system creates enemies that appear aware of the player and capable of reacting to changing situations.

To support progression and exploration, I added collectible item boxes containing:


Python:

Health

Ammo

Grenades


When collected, these pickups immediately replenish vital resources and encourage players to explore the environment rather than simply sprinting toward the exit.

The game also contains environmental hazards such as water and deadly falls. Players must carefully navigate platforms while simultaneously managing enemy encounters and resource usage.

One of the most technically rewarding systems was the explosion framework.

Whenever a grenade detonates or a major combat event occurs, the game loads a sequence of animated explosion frames:


Python:

exp1

exp2

exp3

exp4

exp5


which are then displayed in rapid succession.

This produces far more satisfying visual feedback than simply removing enemies from the screen.

Audio was equally important in creating immersion.

ARC-X contains:


Python:

music2.mp3

jump.wav

shot.wav

grenade.wav


which provide feedback for movement, combat, and environmental interactions.

Combined with the visual effects system, these sounds help every action feel impactful.

Perhaps the feature I am most proud of is the custom Level Editor that was developed alongside the game.

Rather than manually placing platforms and enemies using code, I built a dedicated editor that allows levels to be created visually.

The editor supports:


Python:

21


different tile types and allows them to be placed directly onto the game map using the mouse.

Completed levels can then be saved as CSV files:


Python:

level1_data.csv

level2_data.csv

level3_data.csv


and loaded automatically by the game engine.

This transformed level creation from a tedious programming task into a much faster design process, allowing me to experiment with different layouts and gameplay ideas.

Developing ARC-X taught me more about software engineering than almost any other part of Arcade Legends. Unlike recreating existing arcade games, every system had to be designed from scratch. I needed to build character controllers, enemy AI, projectile systems, collision detection, world generation, inventory mechanics, level progression, pickups, explosions, sound integration, and even the tools used to create the levels themselves.

Most importantly, ARC-X demonstrated that Arcade Legends was no longer just a collection of recreations. It had evolved into a platform capable of supporting completely original games and development tools.

ARC-X: The Last Stand became the largest and most ambitious game in Arcade Legends, combining custom level design, enemy AI, animation systems, weapon mechanics, environmental interactions, and a dedicated level editor into a complete original gaming experience. šŸ†šŸš€šŸŽ®

Building the Arcade Legends Command Centre Backend

By this stage, Arcade Legends had grown far beyond a collection of individual games. I had RFID authentication, player profiles, physical arcade challenges, retro recreations, winner photography, Hall of Fame rankings, achievements, XP systems, and even original game development. The next challenge was obvious:

How could all of these systems be unified into a single arcade platform?

To solve this problem, I developed the Arcade Legends Command Centre Backend, a Flask-powered web application that acts as the central control system for the entire project.

Rather than forcing players to launch games individually, navigate through folders, or interact directly with Python scripts, the backend provides one location where every major feature of Arcade Legends can communicate with one another.

Why Build a Backend?

Most Raspberry Pi arcade projects stop once the hardware works.

I wanted to go much further.

My goal was to create an experience that felt closer to a commercial arcade network than a collection of disconnected programs.

The backend would become responsible for:

  1. Loading player information
  2. Managing profile data
  3. Displaying progress and XP
  4. Showing Hall of Fame rankings
  5. Launching games
  6. Displaying winner photographs
  7. Tracking champions
  8. Hosting the web dashboard
  9. Managing physical arcade challenges
  10. Connecting every subsystem together

In other words, it became the digital heart of Arcade Legends.

Choosing Flask

To build the Command Centre, I selected Flask.


Python:

from flask import Flask


Flask provided a lightweight yet powerful solution for creating a web-based interface while remaining small enough to run efficiently on a Raspberry Pi.

The entire system is driven through:


Python:

app = Flask(__name__)


which serves as the foundation of the web application.

Every game, leaderboard, profile page, launcher, and information panel is accessed through Flask routes.

Creating a Central Hub

The home page became the player's primary destination.


Python:

@app.route('/')

def home():


Instead of displaying a simple menu, the backend dynamically generates a complete arcade dashboard.

Whenever a player visits the site, the backend automatically gathers information from multiple parts of the system.

This includes:

  1. Profile information
  2. XP values
  3. Current player status
  4. Leaderboard rankings
  5. Winner photographs
  6. Active game information

and then combines everything into a single interface.

This approach ensures that players always see the latest arcade data.

Reading Profile Data

One of the most important responsibilities of the backend is loading player profiles.

Every user has a dedicated profile file stored inside:

profiles/

When the dashboard loads, the backend reads each profile:


Python:

for file in os.listdir("profiles")


and extracts XP information.


Python:

xp = int(f.readline())


These values are then used to generate rankings and track progression across the entire platform.

This means every achievement earned inside Arcade Legends contributes toward a player's overall standing.

Creating the Hall of Fame

Once player data became available, I could create one of my favourite features:

The Hall of Fame

The backend automatically collects every profile:


Python:

leaderboard.append((xp, username))


and sorts them by XP:


Python:

leaderboard.sort(reverse=True)


The result is a live ranking system that displays the strongest players inside the arcade.

Unlike a static scoreboard, this leaderboard updates automatically whenever players earn additional XP.

This creates a sense of competition and encourages players to return to the platform.

Winner Photography Integration

Earlier in the project, I built a camera system capable of capturing photographs of successful players.

The backend provides access to this feature through:


Python:

@app.route('/winners/<filename>')


which serves images directly from the winners directory.

Whenever the dashboard loads, it scans:


Python:

winners/


for available images.


Python:

if file.endswith(".jpg")


The latest photograph is then selected and displayed as the current champion.

This transforms the Hall of Fame from a simple leaderboard into a visual gallery of successful players.

Rather than seeing a list of names, visitors can immediately see who achieved victory.

Building a Game Launch System

One of the biggest challenges was creating a consistent method for launching games.

Every title required its own route.

Examples include:


Python:

/reactionmaster

/stopthelight

/whackamole

/ledroulette

/simonspeaks


along with retro games such as:

Python:

/pacman

/snake

/galaga

/asteroids

/flappybird


and:


Python:

/pyshooter


for ARC-X.

When a route is selected, the backend launches the relevant game automatically.

For example:


Python:

subprocess.Popen(["py","-m","pgzero",r"Pacman.py"])


The player only needs to click a button.

The backend handles everything else.

Physical Arcade Integration

A major goal of Arcade Legends was combining physical challenges with software experiences.

The backend therefore supports both categories simultaneously.

Examples include:

  1. Reaction Master
  2. Stop-The-Light
  3. Simon Speaks
  4. LED Roulette
  5. Whack-A-Mole

These games communicate with Python programs responsible for LEDs, sensors, RFID systems, buttons, buzzers, microphones, and other physical hardware.

As a result, the web dashboard can launch real-world arcade experiences directly from the browser.

This creates a seamless connection between software and hardware.

Dynamic Champion Tracking

The backend also keeps track of arcade champions.

Instead of displaying generic placeholder content, information can be updated dynamically based on current player performance.

The Command Centre continuously combines information from:

  1. Profiles
  2. Winners
  3. Rankings
  4. XP systems

to create a living arcade environment.

This makes the dashboard feel active rather than static.

Error Handling and Reliability

During development I quickly discovered that missing profile files, absent images, or invalid data could cause problems.

To improve reliability, I implemented exception handling:


Python:

try:

and:


Python:

except Exception as e:


This allows Arcade Legends to continue running even if certain resources are unavailable.

Rather than crashing the entire system, fallback values are displayed:


Python:

status = "Idle"


or:


Python:

current_user = "None"


This proved especially important when testing new hardware modules and gameplay systems.

Bringing Everything Together

The most significant achievement of the backend was not any individual route or feature.

It was the ability to bring together systems that were originally developed separately.

Before the Command Centre existed:

  1. RFID login worked independently.
  2. Games worked independently.
  3. Hall of Fame worked independently.
  4. Profile systems worked independently.
  5. Camera systems worked independently.

After the backend was completed, every component became part of a unified ecosystem.

Players could log in with RFID, build XP, appear on leaderboards, launch games, earn victories, have photographs captured, and see their achievements reflected throughout the platform.

For the first time, Arcade Legends truly felt like a complete arcade network.

Lessons Learned

Building the Command Centre taught me more about software architecture than any other stage of the project.

Creating individual games was rewarding, but constructing the infrastructure that connected everything together was an entirely different challenge.

The backend became responsible for communication, organization, automation, data processing, and presentation across countless independent systems.

Without it, Arcade Legends would have remained a collection of separate ideas.

With it, everything became part of a single experience.

The Arcade Legends Command Centre transformed the project from a set of games into a unified arcade ecosystem. By combining profiles, leaderboards, champion tracking, winner photography, physical hardware, and digital entertainment into one central platform, it became the foundation that brought every feature together. šŸ†šŸŽ®šŸš€

Designing the Arcade Legends Web Interface

With the backend architecture complete, Arcade Legends finally had a powerful foundation capable of managing profiles, leaderboards, winner photographs, game launching, and player progression. However, a powerful backend alone was not enough.

Players never see the backend.

They experience the interface.

At this stage, my focus shifted toward creating a web experience that felt worthy of the systems operating behind it.

Rather than designing a simple menu filled with buttons and hyperlinks, I wanted Arcade Legends to feel like a professional arcade platform. My goal was to create something that looked modern, exciting, and alive from the moment it loaded.

Creating a Digital Command Centre

The Arcade Legends dashboard was designed to act as the central hub for the entire project.

Everything needed to be accessible from one location:

  1. Physical Arcade Games
  2. Retro Arcade Games
  3. Player Profiles
  4. Hall Of Fame Rankings
  5. Champion Information
  6. XP Progress
  7. Winner Photography
  8. Featured Games
  9. Live Status Information

Instead of forcing players to switch between multiple applications, every major feature was organised into a single web-based Command Centre.

This transformed Arcade Legends from a collection of programs into a fully connected gaming platform.

Building the Interface With HTML, CSS and Flask

The dashboard is generated directly through Flask using:


Python:

return render_template_string(...)


This allowed me to dynamically inject information from the backend into the user interface.

Profile information:


Python:

{{state.current_user}}


Current game information:


Python:

{{state.current_game}}


XP totals:


Python:

{{state.xp}}

Player levels:


Python:

{{state.level}}


and champion information are all displayed automatically using live backend data.

As information changes, the interface updates accordingly without requiring manual modifications.

Creating a Distinct Visual Identity

I did not want Arcade Legends to look like a traditional business dashboard.

Instead, I wanted something that reflected the energy of arcade gaming.

The interface uses glowing colours, animated gradients, glass-style panels, neon effects, and dynamic backgrounds.

Base colours include:


CSS

#06b6d4

#8b5cf6

#ec4899

#22c55e

gold


These colours were selected to create a futuristic arcade aesthetic while keeping important information highly visible.

The result feels far closer to a modern gaming platform than a standard web application.

Animated Headers and Effects

One of the first visual elements I created was the animated title system.

The Arcade Legends heading uses moving gradient animations:


CSS

background-size:400%;

animation: gradientFlow


Rather than displaying static text, the title continuously shifts colours across the page.

This immediately captures attention and helps establish the personality of the platform.

Additional visual effects include:

  1. Animated glowing borders
  2. Floating light effects
  3. Gradient transitions
  4. Dynamic shadows
  5. Animated background grids

These details make the interface feel active even when a player is not interacting with it.

Designing the Champion Banner

One of the most important visual sections is the Champion Display.

Rather than showing rankings as plain text, I wanted the current champion to feel important.

The dashboard prominently displays:


HTML

CURRENT CHAMPION


alongside:

  1. Winner photographs
  2. Champion information
  3. Titles
  4. Status information

The winner's photograph becomes the centrepiece of the page, helping create a genuine Hall Of Fame atmosphere.

This section was inspired by the kinds of displays often seen in professional esports events and gaming tournaments.

Building the XP Progression Display

Player progression plays a major role throughout Arcade Legends.

I wanted XP to be immediately visible.

To accomplish this, I designed a dedicated level display using:


CSS

conic-gradient(...)


and custom circular progress indicators.

The interface displays:

  1. Current level
  2. XP total
  3. Progress toward the next level

This gives players instant feedback regarding their progression and encourages continued participation.

Much like modern gaming platforms, seeing experience bars increase over time becomes highly motivating.

Creating the Hall Of Fame

The Hall Of Fame became one of the most important sections of the dashboard.

Using data generated by the backend, rankings are displayed automatically.

Top players are sorted according to XP and then presented inside animated leaderboard cards.

Special styling was added for:


CSS

.rank1

.rank2

.rank3


allowing the top three positions to visually stand out.

Gold, silver, and bronze styling immediately communicates achievement without the need for additional explanation.

This creates a much stronger sense of competition than a simple numbered table.

Organising Game Libraries

At this point, Arcade Legends contained a large number of games.

Without careful organisation, the interface would quickly become confusing.

To solve this problem, I divided games into separate categories.

The first section contains:


Physical Arcade Games

including:

  1. Reaction Master
  2. Stop-The-Light
  3. Whack-A-Mole
  4. LED Roulette
  5. Simon Speaks

The second section contains:


Retro Arcade

including:

  1. Pac-Man
  2. Snake
  3. Asteroids
  4. Galaga
  5. Flappy Bird

This structure helps players understand the different experiences available without feeling overwhelmed.

Featured Game System

One of my favourite interface features is the Featured Game section.

Rather than giving every title equal prominence, I created a dedicated showcase area.

At launch, this position was assigned to:


ARC-X: The Last Stand

The featured game receives:

  1. Larger artwork
  2. Greater visibility
  3. Dedicated presentation space

This creates a stronger sense of identity and allows major projects to stand out from the rest of the library.

Interactive Game Cards

Every game entry is displayed using custom game cards.

Each card contains:

  1. Game artwork
  2. Hover animations
  3. Links
  4. Launch functionality

Hover effects cause cards to:


CSS

scale(1.08)


while adding glow effects and dynamic shadows.

These visual cues help communicate that the elements are interactive while making the interface more enjoyable to use.

Creating a Live Arcade Experience

One idea that influenced many design decisions was the concept of a "living arcade."

I wanted the dashboard to feel active at all times.

This led to:


HTML:

LIVE


status indicators,

animated particles,

champion banners,

dynamic profile information,

and real-time leaderboard generation.

Even when no game is currently running, the dashboard still feels connected to an active arcade ecosystem.

Responsive Information Panels

The interface makes heavy use of reusable information cards.

These are used to display:

  1. Current player
  2. Current game
  3. XP
  4. Level
  5. Status

Because every card follows the same visual style, information remains easy to understand even as the platform continues to grow.

This consistency became increasingly important as more features were added.

Lessons Learned

Designing the Arcade Legends interface taught me that presentation matters just as much as functionality.

The backend may provide the intelligence behind the platform, but the interface determines how players experience it.

A simple menu could have launched the games just as effectively.

However, by creating an immersive dashboard filled with progression systems, rankings, visual effects, champion displays, and integrated game libraries, Arcade Legends became something much larger than a launcher.

It became a destination.

For many users, the Command Centre is the first thing they see when interacting with the project, meaning it ultimately defines their first impression of the entire platform.

The Arcade Legends Web Interface transformed a powerful backend into a complete user experience. By combining visual design, player progression, leaderboards, champion tracking, and integrated game libraries, it turned the project into a professional-looking arcade ecosystem rather than simply a collection of games. šŸ†šŸŽ®šŸš€

Creating Dedicated Game Information Pages

As Arcade Legends continued to grow, I noticed a problem.

When a player clicked on a game, it launched immediately. While this worked technically, it didn't feel like a real arcade experience. New players had no idea what the objective was, how difficult the game might be, or why they should choose one game over another.

I wanted each game to feel important.

Instead of launching players straight into gameplay, I decided to create dedicated Game Briefing Pages for every title in Arcade Legends.

These pages became the first thing players see before entering a game.

Whether someone chooses Pac-Man, Galaga, Flappy Bird, Reaction Master, Simon Speaks, or even my original game ARC-X: The Last Stand, they are first presented with a custom introduction page designed specifically for that experience.

Each briefing page includes:

  1. Large game artwork
  2. A mission overview
  3. Objectives
  4. Difficulty ratings
  5. Reward information
  6. A dedicated launch button

My goal was to make each game feel like a featured attraction inside the arcade rather than just another program.

For example, before launching Simon Speaks, players are introduced to the challenge of remembering increasingly complex sequences. Before starting Reaction Master, players are warned that speed and reflexes are critical. Before entering ARC-X, players are presented with a full mission briefing explaining the story and stakes behind the game.

Every page was individually designed using Flask routes such as:


Python:

@app.route("/pacman_info")


Python:

@app.route("/galaga_info")

Python:

@app.route("/arcx_info")


allowing each game to have its own identity while remaining part of the wider Arcade Legends platform.

I also introduced difficulty ratings ranging from:

⭐ Easy

⭐⭐ Medium

⭐⭐⭐ Hard

⭐⭐⭐⭐ Expert

⭐⭐⭐⭐⭐ Legendary

This gave players a quick way to decide which challenge they wanted to tackle next.

One of my favourite pages to create was the ARC-X introduction page. Because ARC-X is my own original game, I wanted it to feel special. It received a "Featured Game" presentation with larger artwork, custom styling, gold accent colours, and a dedicated showcase section that made it stand out from the rest of the library.

What surprised me most was how much these pages changed the overall feel of Arcade Legends.

Technically, they were not necessary. Players could have launched the games directly from the dashboard. However, once the briefing pages were added, the platform felt far more professional. Instead of selecting a game from a list and immediately being thrown into gameplay, players were given a moment to understand what they were about to experience.

It created anticipation.

More importantly, it made Arcade Legends feel like a genuine arcade platform rather than a collection of Python projects connected together.

By the end of development, every major game within Arcade Legends had its own introduction page, complete with artwork, mission details, difficulty indicators, and launch controls. This simple addition significantly improved the user experience and helped give each game its own personality.

The Game Briefing Pages transformed game selection into part of the experience itself. By introducing artwork, objectives, difficulty ratings, and custom presentations before gameplay begins, every title in Arcade Legends feels like its own attraction within a larger arcade universe. šŸ†šŸŽ®šŸš€

Building the Arcade Legends Game Launcher System

This step is surprisingly important because it explains how every game, challenge, and arcade activity becomes accessible from a single platform.

Without the Launcher System, Arcade Legends would simply be a collection of Python projects stored in different folders.

The launcher is what transforms them into a unified arcade experience.

As Arcade Legends expanded, the number of games and challenges grew rapidly. By this stage, the platform contained multiple physical arcade games, several retro recreations, and even an original game. While each game functioned correctly on its own, moving between them was becoming increasingly inconvenient. Players would have needed to navigate folders, open scripts manually, and remember file names, which would have immediately broken the arcade experience.

I wanted every game to be accessible through a single click.

To achieve this, I developed a dedicated Launcher System within the Arcade Legends Command Centre.

Rather than interacting directly with Python files, players simply select a game from the dashboard. The launcher then handles everything behind the scenes.

The system is built using Flask routes, with every game receiving its own dedicated endpoint.

Examples include:


Python:

@app.route("/pacman")

@app.route("/snake")

@app.route("/galaga")

@app.route("/asteroids")

@app.route("/flappybird")


and:


Python:

@app.route("/pyshooter")


for ARC-X: The Last Stand.

When one of these routes is selected, the launcher automatically starts the corresponding game.

For example:


Python:

subprocess.Popen(["py","-m","pgzero",r"Pacman.py"])


launches Pac-Man directly from the dashboard.

Similarly:


Python:

subprocess.Popen(["py","-m","pgzero",r"Snake.py"])


starts Snake, while other routes launch Galaga, Asteroids, Flappy Bird, and additional games.

Using:


Python:

subprocess.Popen()


was particularly useful because it allows Flask to continue running while the game opens in a separate process. The dashboard remains available instead of freezing while the game is active.

The launcher is also responsible for integrating the physical arcade challenges.

Games such as:

  1. Reaction Master
  2. Stop-The-Light
  3. Whack-A-Mole
  4. LED Roulette
  5. Simon Speaks

can all be started directly from the dashboard in exactly the same way as the retro games.

From a player's perspective, there is no difference between launching a physical challenge and launching a software game. Everything feels like part of the same arcade platform.

This consistency became one of the most important design goals of the project.

To improve the experience further, each launcher route was connected to a dedicated briefing page developed in the previous step. Instead of immediately loading a game, players first receive information about the challenge ahead, including objectives, difficulty ratings, artwork, and rewards.

The result is a much smoother experience:


Select Game

↓

Read Briefing

↓

Launch Game

↓

Earn XP

↓

Return To Dashboard


rather than jumping directly from menu to gameplay without context.

As the project grew, the launcher became much more than a collection of buttons. It evolved into the central navigation system for the entire arcade. Every major feature, whether physical or digital, could be accessed from a single location.

Looking back, the launcher played a crucial role in transforming Arcade Legends from a collection of separate projects into a complete ecosystem. Players no longer interact with individual Python files. Instead, they experience a seamless arcade platform where everything is connected and accessible through a unified interface.

The Launcher System became the bridge between every part of Arcade Legends. By allowing physical games, retro recreations, and original projects to be launched from a single location, it transformed dozens of independent programs into one cohesive arcade experience. šŸ†šŸŽ®šŸš€

Developing Hall of Fame and Champion System

Step 34: Developing the Hall of Fame and Champion System

As Arcade Legends continued to grow, I realised that players needed more than just games. They needed a reason to come back.

Many arcade games are exciting at first, but once a player has completed them, there is often little incentive to continue playing. I wanted Arcade Legends to feel different. I wanted every challenge, victory, and accomplishment to contribute towards something larger.

This led to the creation of the Hall of Fame and Champion System, one of the most important features in the entire project.

Instead of treating every game as an isolated experience, Arcade Legends tracks player progression across the entire platform. Whether a player is competing in Reaction Master, earning a new high score in Pac-Man, surviving a round of Galaga, or completing a physical challenge, their achievements contribute to their overall standing within the arcade.

The foundation of the system is the player profile architecture developed earlier in the project.

Each player is assigned a unique profile containing information such as:

  1. Username
  2. XP
  3. Progression
  4. Achievements
  5. Historical activity

Whenever a player earns XP, the value is stored within their profile and becomes part of their overall arcade record.

To transform this information into something meaningful, I created a Hall of Fame system.

When the dashboard loads, Arcade Legends automatically scans every available profile:


Python:

for file in os.listdir("profiles")


The stored XP values are extracted:


Python:

xp = int(f.readline())


and added to a leaderboard:


Python:

leaderboard.append((xp, username))


The rankings are then automatically sorted:


Python:

leaderboard.sort(reverse=True)


allowing the highest-performing players to rise to the top.

This creates a live competitive environment where rankings constantly change as players continue to participate in arcade challenges.

However, I wanted the Hall of Fame to be more than a simple list of names and numbers.

To make achievements feel significant, I developed the Champion System.

The Champion System highlights the current leading player and places them at the centre of the Arcade Legends experience.

Rather than showing a small ranking entry somewhere on the page, the active champion receives a dedicated showcase area featuring:

  1. Champion status
  2. Profile information
  3. XP totals
  4. Level progress
  5. Winner photography
  6. Hall of Fame recognition

This allows major achievements to become visible throughout the platform.

One of the most unique features of the Champion System is its integration with the camera subsystem.

Earlier in the project, I developed a Raspberry Pi camera system capable of capturing photographs of successful players. These images are stored inside the:

winners directory.

Whenever the dashboard loads, Arcade Legends automatically scans this folder:


Python:

for file in os.listdir("winners")


to locate available winner photographs.

The most recent image is then displayed directly within the Champion section of the dashboard.

This was one of my favourite features to create because it transforms digital achievements into something real and personal.

Rather than simply seeing a score, players see an actual record of success.

The Hall of Fame therefore becomes a living archive of past achievements rather than a temporary scoreboard.

To reinforce competition even further, I designed special visual styling for the top-ranked players.

First place, second place, and third place each receive unique formatting inspired by traditional medals:


šŸ„‡ Gold

🄈 Silver

šŸ„‰ Bronze


The goal was to immediately communicate achievement without requiring players to analyse large amounts of information.

Even a quick glance at the Hall of Fame reveals who currently dominates the arcade.

As more systems were integrated into Arcade Legends, the value of the Hall of Fame increased.

Games were no longer being played purely for enjoyment. Every challenge became an opportunity to earn XP, improve rankings, defend champion status, and climb the leaderboard.

This transformed the platform from a collection of games into a connected ecosystem driven by progression and competition.

Looking back, the Hall of Fame became one of the most successful systems in Arcade Legends because it introduced a sense of purpose. Players were no longer just trying to beat a game. They were trying to become a legend.

For me, this represented a major milestone in the project. It demonstrated how software, profiles, photography, databases, progression mechanics, and web technologies could work together to create an experience far richer than any individual game.

The Hall of Fame and Champion System transformed Arcade Legends from a collection of arcade experiences into a competitive gaming platform. By tracking progression, recognising achievements, showcasing winners, and celebrating champions, it gave players a reason to return, improve, and earn their place among the legends. šŸ†šŸ‘‘šŸŽ®šŸš€

Bringing Everything Together - Creating the Complete Arcade Legends Experience

As I approached the final stages of development, I realised something surprising.

The biggest challenge was no longer building games.

It was connecting everything together.

By this point, Arcade Legends contained:

  1. RFID Authentication
  2. Player Profiles
  3. XP Systems
  4. Level Progression
  5. Physical Arcade Games
  6. Retro Arcade Games
  7. Winner Photography
  8. Hall of Fame Rankings
  9. Champion Tracking
  10. Voice Features
  11. A Flask Dashboard
  12. A Custom Web Interface
  13. Game Briefing Pages
  14. A Unified Launcher System
  15. An Original Game (ARC-X)

Individually, every component worked.

The real challenge was transforming them into one seamless experience.

This became the final integration phase of the project.

Designing a Complete User Journey

One of the questions I constantly asked myself was:

"What does the experience feel like from a player's perspective?"

A player should not see dozens of separate Python programs.

A player should experience a single arcade platform.

I redesigned every system around a unified journey:


Scan RFID Card

↓

Load Player Profile

↓

View Dashboard

↓

Choose Game

↓

Read Mission Briefing

↓

Play Challenge

↓

Earn XP

↓

Update Rankings

↓

Capture Winner Photo

↓

Appear In Hall Of Fame


Once this workflow was in place, Arcade Legends finally felt like a real arcade ecosystem.

Connecting Hardware and Software

One of the most unique aspects of the project was the way physical hardware and software experiences coexist.

The same dashboard can launch:

  1. Reaction Master
  2. Stop-The-Light
  3. Simon Speaks
  4. LED Roulette
  5. Whack-A-Mole

while also providing access to:

  1. Pac-Man
  2. Snake
  3. Galaga
  4. Asteroids
  5. Flappy Bird
  6. ARC-X

To the player, there is no distinction.

Everything appears as part of a single platform.

Behind the scenes, however, completely different technologies are operating simultaneously.

Some games rely on:

  1. LEDs
  2. Buttons
  3. RFID Readers
  4. Buzzers
  5. Sensors
  6. Cameras

while others are entirely software-based.

Making these systems work together was one of the most rewarding parts of the project.

Creating a Sense of Progression

Another major goal was making every session feel meaningful.

Traditional arcade machines usually forget everything when the game ends.

Arcade Legends works differently.

Every challenge can contribute towards:

  1. XP
  2. Levels
  3. Rankings
  4. Champion Status
  5. Hall Of Fame Position

As a result, players are constantly building something.

The more games they play, the more their profile evolves.

This progression system became one of the key features that transformed Arcade Legends from a collection of games into a long-term experience.

Building a Living Arcade

Throughout development, I wanted the platform to feel alive.

That idea influenced countless decisions.

The dashboard contains:

  1. Live status indicators
  2. Dynamic rankings
  3. Champion displays
  4. Progress tracking
  5. Winner photography
  6. Featured content

The Hall of Fame updates.

Champion photographs change.

Rankings evolve.

XP increases.

Achievements accumulate.

The result is a platform that always feels active.

Even when nobody is playing, Arcade Legends feels like a real arcade waiting for the next competitor.

Overcoming Integration Challenges

Combining so many independent systems was not straightforward.

Throughout development I encountered challenges involving:

  1. File management
  2. Hardware communication
  3. Sensor reliability
  4. Game launching
  5. Data persistence
  6. Asset organisation
  7. User interface design
  8. Software architecture

Many features worked perfectly on their own but required significant modification before they could operate within a shared ecosystem.

Solving these integration problems taught me valuable lessons about software engineering, debugging, modular design, and project management.

In many ways, connecting everything together was harder than creating the individual features themselves.

Looking Back

When I started Arcade Legends, my goal was simple.

I wanted to build an arcade machine.

What emerged was something much larger.

Instead of one game, I created a platform.

Instead of isolated hardware experiments, I created a connected ecosystem.

Instead of a scoreboard, I created a community-driven Hall of Fame.

Instead of a launcher, I built an arcade command centre.

Arcade Legends ultimately became a combination of hardware engineering, software development, game design, user experience design, web development, photography, electronics, artificial intelligence, and creative problem solving.

Every feature I created contributed something unique to the final experience.

And when those features finally came together, Arcade Legends became far more than the sum of its parts.

Arcade Legends began as a Raspberry Pi project, but it evolved into a complete arcade ecosystem. By combining hardware, software, progression, competition, creativity, and game design into one unified experience, I transformed a collection of ideas into a living digital arcade. šŸ†šŸŽ®šŸš€

And that is what makes Arcade Legends special.

Conclusion-more Than an Arcade

When I started Arcade Legends, I thought I was building an arcade machine.

What I actually built was something much bigger.

Over the course of this project, I designed physical arcade challenges, recreated classic retro games, developed an original title, built a web-based command centre, created player profiles, implemented RFID authentication, designed progression systems, captured winner photographs, developed a Hall of Fame, and connected dozens of independent systems into a single platform.

Every feature began as a small idea.

A card reader became a login system.

A camera became a celebration system.

A scoreboard became a Hall of Fame.

A collection of games became an arcade ecosystem.

Throughout this project I learned far more than I expected. I explored programming, electronics, game design, web development, software architecture, user experience design, debugging, asset creation, and hardware integration. More importantly, I learned how powerful it can be when different technologies work together toward a single goal.

Arcade Legends is not defined by any individual game or feature.

It is defined by the experience created when everything works together.

A player scans their RFID card, loads their profile, competes in challenges, earns XP, climbs the rankings, appears in the Hall of Fame, and becomes part of the story of the arcade.

That is what I set out to build.

An arcade that remembers its players.

An arcade that rewards achievement.

An arcade that feels alive.

And after countless hours of development, testing, redesigning, fixing bugs, and improving every detail, Arcade Legends finally became exactly that.

Arcade Legends is more than a machine. It is a celebration of creativity, technology, competition, and play. What began as a simple idea grew into a complete arcade ecosystem, proving that with enough imagination, persistence, and problem-solving, a Raspberry Pi can become something truly legendary. šŸ†šŸŽ®šŸš€āœØ