Integrated Winsock-based networking into an existing C++ game framework with minimal code changes, working within strict constraints similar to a production codebase.
Designed a peer-to-peer connection based on lockstep game replication, the same type of networked system that powered games like Doom and Age of Empires 1.
Designed and implemented custom serialization and deserialization of player input data to synchronize game state using UDP data packets.
Utilized non-blocking input/output sockets to ensure gameplay code can still run during the period where networking functions are in a delay state.
Implemented connection, hosting, and error-handling logic to support peer discovery, session setup, and graceful fallback behavior.
Goals & Intent
This was a really interesting project, and a good excuse to get hands-on with low-level networking. The goal was to add hosting, connecting, and game replication to an existing engine that already handled gameplay and rendering. While it wasn’t my first time working inside someone else’s codebase, it was my first experience adding networking to an engine that didn’t already support it, which made for a fun and meaningful challenge.
What Went Well
The lockstep model turned out to be a simple but effective way to keep both peers fully synchronized, with collision resolution remaining deterministic. I initially struggled with how network calls should fit into the main engine loop, but switching the sockets to non-blocking IO made everything click. Instead of the game stalling while waiting on send/receive calls, the game can now continue running and simply check for network activity each frame. That change was small in the context of this project, but in a real-time game it would have a huge impact on responsiveness!
What I Learned / What I’d Improve
This project deepened my understanding of low-level networking by giving me hands-on experience with game replication, custom UDP packets, and peer-to-peer connection logic. If I were to revisit it, I’d like to experiment with a different replication model, such as a server-authoritative client/server setup. It would be a fun opportunity to make it so players can move freely around instead of waiting on the other's input, and shift responsibilities like collision resolution to the server.
Developed core game logic for paddles, ball physics, and game state management from scratch, transforming the standard Pong gameplay into a handheld experience, including analog controls using the Playdate's crank.
Utilized the Playdate SDK to create game objects with finite state machines and implemented custom graphics calls for rendering shapes directly on-screen.
Implemented a debug mode to draw information about enemy AI, frame rate, and other game info, expediting the process of fixing bugs whenever they came up.
Optimized code for hardware compatibility, ensuring a smooth transition from the Playdate simulator to the actual handheld device.
Designed a customizable enemy AI that predicts ball trajectory, with adjustable accuracy settings to dynamically scale difficulty.
I really like the Playdate! It's a very fun and quirky handheld, and the barrier to entry for making games is very low. The SDK is publicly available, and the documentation is easy to understand -- especially if, like me, you already have experience with Lua. This was a fun, low-stakes project that I still come back to every now and then to mess around with when I have the free time.
Learning how to implement draw calls for the Playdate was the most challenging part of this project. Even though the graphics in this game are pretty simple rectangles and circles, it was still tricky to wrap my head around pushing and popping context to the handheld screen. Looking back, this project actually helped me as I learned how to implement custom OpenGL rendering, since the two use similar graphics architecture.
I'm proud of how my enemy AI turned out for this project. When I started I knew I wanted a decently smart enemy paddle that was challenging, but not unbeatable. I did some research, and implemented a few prototypes, but a big challenge was making it so that the AI would run on the device -- since the desktop simulator runs with more power, optimization was needed to port it onto the handheld. In the end, I decided to use a series of raycasts to predict where the ball would make contact with the other side of the screen. What I like about this implementation is that it's easily adjustable: I can choose to add minor offsets on the angle of the casts, and lengthen the time between casts, to mimic the actual imperfections of a human player.
Implemented A* pathfinding algorithm in C++ for a custom professor-provided engine, as part of a game AI course project.
Integrated five different heuristic weight equations: Octile, Chebyshev, Inconsistent, Manhattan, and Euclidean.
Built custom node and grid data structures that fit within the cashe to support efficient pathfinding operations.
Added path smoothing and rubberbanding techniques for more natural movement in resulting paths.
Optimized performance by replacing the open list with a custom bucket queue, precomputing neighbors, and caching computed weights.
This project was extremely interesting and engaging, and my biggest takeaway was my newfound obsession with A*! It's a very versatile algorithm, and even outside of simple wall avoidance like in this demo, I have a lot of interesting ideas for how to implement it in future projects.
Implementation of the algorithm itself was surprisingly the easiest part of this entire project. It's basically just Dijkstra -- which I already understood pretty well -- with extra bells and whistles, so once I understood the differences between the two debugging went pretty quick. The hardest part was everything that came after: A* as a technique is an inch wide and a mile deep, and there's so much additional complexity and extra features to expand on the algorithm itself.
Optimization on this project was also a beast, and trying to do so probably took as much time as programming the actual features did. The most difficult part was programming my own bucket queue data structure, which is basically an array of priority queues arranged in "buckets" with different ranges of values.
Whodunnit -- The Fighting Game! is a project I made for my DES 212: Systems Design class. The project is a one-dimensional fighting game simulation, made in Unity, where the player "interrogates" several enemies, in an attempt to "catch the killer". The goal of the project was to create a well-balanced fighting game, with a variety of attacks and enemies, and to use telemetry data and play testing to ensure the gameplay wasn't too easy or too hard.
I accomplished this by creating a "fast mode" - triggered by pressing the "F" key - which turns off all graphics and makes the simulation run at 6 times the usual speed, running through hundreds of battles with each enemy type, and testing each player AI mode. The AI modes were:
Randomly spamming attacks
Spamming one attack
Playing intelligently, conserving resources and retreating when necessary