It’s been a while since I last wrote a blog post, let alone a one building a new tiny game. So the latest addition is https://pong.ivaylopavlov.com as you can probably guess, it’s a simple game of pong. I remember that back in the day, many programmers started out with building either Tetris or a pong game in C or C++. Since I already did a Tetris one, I thought why not do the other one. It also gave me an opportunity to try out a new framework in JavaScript, which I haven’t really fiddled with, which turned out to be surprisingly popular and easy to use called p5.

As usual, the entire source code, very far from perfect, for a week-long project, is available here – https://github.com/ivailop7/IvoPong, so feel free to fork and make a better version.

Initial Choices

Since, I wanted to host it on the web behind AWS’ CloudFront CDN, I was stuck with the classics of 2020 – TypeScript, ReactJS and Styled-components for the CSS-in-JS. The interesting choice was p5, since I needed something that would allow drawing. All my games so far were mostly matrix-based, so doing simple images rendered based on a matrix cell, worked out fine for me, but having a bouncy ball go across the screen, would not have looked nice. It didn’t take long to find this well-documented library called p5, which allows you to draw in JavaScript. My only experience with drawing in JS, for all my years of experience has only been D3, so classic timeseries and pie charts and a heatmap here and there. Nothing super fancy. I’ve done more interesting stuff with seaborn and matplotlib in Python in my health analytics post. Other choices were, that I wanted to build something very simple and avoid class components and use react hooks only, no react-router and no redux setup. The choices were a mixed bag, will get on this in just a bit. The intention was desktop-first, mobile later on, maybe.

Drawing in JavaScript for the first time

I always imagined that for 2D drawing, it would be just axis coordinates, some hex values for coloring and maybe extra parameters to make corners round. Et voila, it turned out that I was right, or least in the context of p5. So let’s take the rectangle p5.rect which is the entire foundation – the two paddles and the circle, which is perfect ellipse. The scores are text, which as I learned later, is nearly impossible to make into a hyperlink, unless you do invisible area around the text and detect if the mouse clicked inside. Compared to a hyperlink tag, given it’s in a browser, it’s the definition of overengineering. After a lot of initial code duplication, just to offset the X axis, we end up with this monochrome look:

Then it’s the time-consuming part of detecting paddle and wall hits. If you had a single paddle, easy. You easily find yourself in corner cases, for example if the top pixel of the ball hits perfectly the bottom pixel of the paddle, should this be a hit or a miss, I consider it a hit, but inspecting visually with a fast-moving ball, you would think it’s a bug. That’s one of the cases, there were quite a few I came up with as I was going along, but I kept it very simple, in order to finish the entire thing in under 5 days. Another thing to consider is the ball angle depending on which part of the paddle is hit. If it hits the upper half, should go up, lower half down, otherwise with constant change of the X/Y angle you would often end up in a perfect angle replication if neither player moves the paddle. Here is some “should reduce if statements” sample code of how it looks like:

// Detect collision with right paddle
// if hit with upper half of paddle, redirect up, if lower half, redirect down
if (
    this.xBall >= this.windowWidth - this.borderOffset - this.paddleWidth - (this.diameter / 2) &&
    this.yBall <= this.yPaddleRight + this.paddleHeight &&
    this.yBall >= this.yPaddleRight
) {
    if (
       this.yBall >= this.yPaddleRight &&
       this.yBall < (this.yPaddleRight + (0.5 * this.paddleHeight))
    ) {
       this.yBallSpeed = Math.abs(this.yBallSpeed) * -1;
    }
    if (
       this.yBall > (this.yPaddleRight + (0.5 * this.paddleHeight)) &&
       this.yBall <= (this.yPaddleRight + this.paddleHeight)
    ) {
    this.yBallSpeed = Math.abs(this.yBallSpeed);
    }

    this.xBallSpeed = Math.abs(this.xBallSpeed) * -1;
}

The sketch, the state and the menu components

To get the sketch inside a react component, I used react-p5, which has types and exposes the APIs you would need, such as keyPressed, etc. I had to use a class component, instead of hooks, the reason is because the sketch state was different from the react component state, so if the variables inside the draw() method were put in hooks or in a class component state, you actually don’t get animations, since react re-renders and it’s missing the animations, so the target to use only hooks had to go, since I needed non-react state. The second consideration, avoiding react-router and redux, made the code, despite the simplicity, just ugly in my opinion, for a 3 screen app – menu, rules popup, actual game. Adding links and routes, seemed like an overkill, but then to implement a “start game” button and “go back to menu”, required having the menu component render the game component and the game to listen for a flag to render the menu. Despite being a valid react paradigm, this circular dependency didn’t sit well with me. Kept it, nonetheless. Redux was easily avoided, I believe in total the entire non-sketch state was about 5-6 booleans, which could be further optimized. Unlike the previous games, I went with styled-components, since I’ve been using it more at work and felt confident that I won’t waste much time getting stuff to work. All went smoothly, since I didn’t do anything fancy. Centering an image inside a div in 2020 is still a pain.

The usual considerations

Once I get a web game running in a browser, the natural follow up question is, how would this play on a mobile phone or tablet. I dread this part, since the design for mobile would be different, for example, have the playing field vertical instead of horizontal etc. I was pleasantly surprised that p5 had native support for touch events and provided a very rich event context, a sample of the object below:

It was enough to have the screen virtually split in quadrants and each one to move the respective paddle up and down. If it wasn’t that easy, I probably would have not done the mobile piece at all. It’s just that there’s always that consideration of the inputs, my favorite game is Tetris, yet after so many years, playing on a touch screen device is still as terrible and clumsy as it first came out for iOS and Android. The game was created for a keyboard/gamepad-like input. Resolutions-wise, since p5 had stellar support, this wasn’t much of an issue.

Conclusion

I can strongly encourage to look into p5 for drawing in 2D, if you want to explore your creative side inside JavaScript. It also supports WebGL, if you want to experiment with 3D, but I didn’t get to that part. It’s a great base for simple games and fun projects. ReactJS and its entire ecosystem is not going anywhere and it is getting better and more established by the day. With the strong corporate push behind the core technologies – Chromium, TypeScript, Visual Studio Code, ReactJS, GraphQL from the tech giants, you should explore the ecosystem and create freely! Good luck!


Ivaylo Pavlov

I blog about interesting tech, programming and finance in real life.

0 Comments

Leave a Reply