git commit

Ping.

Bounce the balls!
Move with the L/R arrow keys, and adjust the difficulty by changing the speed or adding more balls.

Score: 0

This game is one of my first javascript projects, specifically intended to familiarize myself with working with the DOM and updating the HTML and CSS on the fly. It was coded pretty much from scratch, with around 160 lines of JS, 50 of CSS, and barely 10 lines of HTML (not counting this writeup, of course).

Making the box:

It's surprisingly tricky to make div height a function of width in plain CSS. I didn't want to code absolute size, which meant using % width of the surrounding container. Unfortunately getting a % of height is harder since, well, scrolling - so I decided to have height proportional to width. Unfortunately, the CSS height attribute can't natively access the container width. For some reason, padding-top can. Don't ask me why it can when height can't, but it gives one way to solve the problem:
div {
  width = 100%;
  padding-top = 100%
}

This seems to be the typical way to solve the problem, but it means that you need to use absolute positioning for all the child elements - unless you want them all pressed down to the bottom. I didn't want to commit to that, so I ended up hijaking the height with javascript. Funnily enough, javascript also didn't have easy access to the div weight, since I didn't set it inline. Luckily, it does provide a workaround:
game = document.getElementById("game");
game_width = game.clientWidth;
game.style.height = game_width * ratio + "px";

Voila! Box. The extra effort was slightly diminished given that I subsequently used only absolute positioning. But hey - at least I have the width and height as javascript variables now!

A movable disk:

Moving the disk on keyboard input is super straightforward with a keydown event listener updating the positional information. A bigger challenge was working out the boundaries. Since the disk has width itself, I slimmed the bounds to make sure it doesn't leave the box. I got super confused for a bit since my last graphics code was written in R, where position refers to the center of an element. In CSS, it's the upper left corner. That baffled me for way too long.
document.addEventListener('keydown', function(event) {
 if (event.keyCode == 37 & catcher_x > 0 + game_width * 0.03) {
  catcher_x = catcher_x - game_width * 0.03;
 } else if (event.keyCode == 39 & catcher_x < game_width * 0.9) {
   catcher_x = catcher_x + game_width * 0.03;
 }
})

Bouncing balls:

These were by far the most complicated, with several pieces coming together to make them work. They needed to move, preferably in different directions. They should also bounce at the walls - unless you miss and they hit the ground.
Basic animation is straightforward with setInterval() wrapping an incremental change in position. Absolute positioning was very useful here. But predictable bounces aren't that fun, so I wanted to endow each ball with random directional velocity - vx and vy values to increment by. I used Math.random() for vx, then trigonometry for vy, meaning that the distance per move will be the same for each dot. Associating these values with balls was my first proper brush with the stringiness of javascript. I'd planned to store them as attributes with the span, but they somehow magically turned into string each time I tried to get them back. Everything turned into strings. Width and height and position all turned into strings, often with a random 'px' appended. Type conversion and regex, sure, but argh such overhead. I ended up just keeping track of everything in the script as well, bundling a dot into an object together with its position and direction.
That made bouncing simpler. Hit a wall, flip the sign on vx. Hit the ceiling or disk, flip the sign on vy. Perambulate as normal. Boundary-detection had the same issue as with the disk, and it took several tries until I was touching the walls without ghousting through them. Disk-detection was also thrown by my misunderstanding of position = center, and for awhile the disk's shadow was more effective than the disk itself. Sigh.

Turning up the heat:

In retrospect, the extra balls may be a touch too far. I've yet to get past two hits with more than one ball in the air. Mostly, it's fun to initialize a bunch and let them fall. Still, a fun addition that required a bit of a coding overhaul.
Plonking an extra circle on the page was surprisingly easy, with the createElement() and addChild() functions. This wouldn't be so streamlined, except all my styling was done direct to the span tag so I didn't need to tediously add extra attributes, children and styling. To be part of the game, that circle was wrapped together with movement info and stashed in an array. Each loop, setInterval() pulls the ball array new, so new balls appear in the game immediately.
Looping through the array with forEach() caused a new issue. With all balls in the air, it ran fine. With a ball on the ground, it also ran fine. Unfortunately, it comes without break capability. God knows why, but it meant the fallen ball stayed on the ground post-death instead of resetting with the others. It was a simple conversion to a for loop, but I remain disappointed.
On the other hand, since the balls only reset when the death function is called, you can add new ones and adjust the speed on the go. Adjusting the speed does pause the game, since I controlled the speed through the millisecond wait in setInterval(). In retrospect, this was stupid and I should just add a multiplier to the vx and vy increments. Whoops.

Overall

Fun to code and fun to play. It looks surprisingly decent - perks of minimalism! Javascript is rather finicky. I definitely don't appreciate how everything turns into a string as soon as it touches the DOM, or the existence of loops that just won't break. On the other hand, the ease of web programming is rather wondrous - especially given this is a static website. Client side scripting for the win!