Project IV: Lunar Lander

Project narrative

Land your Lunar Excursion Module [LEM] on the surface of the moon. This is a storied and much beloved game from early digital video-gaming (ca. 1969).

Here’s a re-creation of the earliest iteration of the game — text-based!

https://lunar69.uber.space/lunar.html

And a good overview of the game’s multi-platform history:

Project Requirements: Code

  • Make use of basic collision detection in GameMaker (esp. recommended,e.g.: place_meeting(), position_empty(), etc. For special circumstances, consider also the “advanced collision detection” functions (e.g., collision_point(), collision_line(), etc.);
  • Make use of at least two different sound effects that are synced to player or game behavior;
  • Make use of at least two cameras (e.g., one close-up and one distant; one on the game field and one trained at a control panel, etc.);
  • Make use of at least one simple particle generator (built by you, and not part of the GameMaker physics engine proper).
  • Special Aside: Your code should NOT MAKE USE of the BOX2D PHYSICS simulator in GameMaker.

Advanced Options: Code

  • Consider using procedurally-generated terrain via an implementation of Perlin Noise or Perlin’s more recent innovation, Simplex Noise.
  • Game data should be clearly and intuitively modeled for the player. While a purely numeric dashboard is a good start, for example (displaying remaining fuel, rate of descent, etc.) a graphical presentation of that information — in the form of dials, gauges, etc. — might make it more fun.

Project Requirements: Gameplay

Please consult any of the variants upon the original Lunar Lander (Atari) for game-play ideas. In essence, here’s how gameplay proceeds:

  1. Your LEM has a finite amount of fuel as it attempts to land on some lunar surface;
  2. The player guides her ship, using a main (vertical) thruster and two smaller horizontal thrusters, towards a promising surface for touchdown.
  3. Feel free to vary difficulty (randomly, or by level) by varying (1) gravity, (2) fuel reserves, (3) rate of fuel consumption, and/or (4) how rigidly you evaluate a “soft landing” versus a crash.
  4. The pleasure of this game — aside from its simulative nature — is the tight integration between our input (thrust) and the moon’s pull (gravity, inertia), as both are understood vis a vis our dwindling resources (fuel).

Project Three: Requirements

Procedurally-Generated Bestiary

  1. Write a program that generates and draws the head and face of an imaginary creature;
  2. In order to accomplish this, include at three separate “custom functions” (scripts) in your GameMaker code;
  3. At least one of those custom functions should return() a value (the other two, if you wish, do not need to return() any values;
  4. Creature faces should have at least 3 features (for example, your faces may all feature eyes, a mouth, and a nose);
  5. Parameterize those features across one or more dimensions of variability:
    1. Parameters for any single facial feature might include:  Size, position, color, quantity, angle, transparency, symmetry.
    2. In particular, placement of features on faces should be within random ranges on at least 2 of these features (e.g., you might always put the eyes in the same place, but the exact mouth position for each creature is randomized from a predetermined range);
  6. Your goal is to ensure that every randomized head is unique and surprising (and “makes sense”).

Output

Remember: You’re modeling procedurally-generated creature FACES/HEADS only. Try to steer clear of worrying about bodies, as that introduces so many more variables to this circus.

Your program should generate at least two dozen combinations of features, heads, colors, etc., saving each one independently as a PNG file, at least 800×800 in size.

Advanced Challenges

  • Allow for player input:  Player defines parametric ranges for randomized outcomes (e.g., between 3 and 5 eyes);
  • Introduce basic animation to your procedural beast:  Eye blinking, slight tilt of the head, etc.;
  • Create a short code that describes all of the parameters for any specific creature, and include that DNA-like code on each image you output. 
    • Double-up on the challenge: Players should be able to input that code later and generate a perfect replica of the creature.

Notes on Appearance

This project takes a break from building-out gameplay and highly-interactive dynamics, and instead allows you to focus on the aesthetics of the project. Consider carefully, for example, your color palette, aim for sprites that share a similar visual style, and so on.

Where resized, sprites should still appear relatively clear and not be over-interpolated.

Give thought to the presentation: Is it realistic, pseudo-3D, minimalistic, cartoonish, hand-drawn, etc.? Is there a sense of humor (or an earnest seriousness) visible across all of the creatures? Are faces consistent in their definition?

For a real-world parallel, consider this composite character sheet featuring characters from the animated TV program The Simpsons.

The artists use a lot of shared design features to create a sense of characters that “hang together.”

Look at the eyes in the image above, for example: Even though each pair is unique — you couldn’t easily swap Mr. Burns’ eyes for Marge’s, for example — the fact is they have a lot in common:

  • They tend to occupy a LOT of space on the characters’ faces;
  • the eyeballs have a strongly spherical shape, and often look as though they are barely contained by the characters’ brows;
  • they almost always sit higher on the face than the characters’ ears.
  • In the case of some of the characters, the eyeballs actually touch one another, and are seated atop the nose.
  • Where characters’ eyes don’t touch, they are separated only by the single width of a nose.
  • Most characters do not have eyebrows.
  • Even where they are expressing considerable emotion, the eyes of Groening’s characters tend to continue to operate symmetrically.
  • The line work on the Simpson’s characters is always consistent: Uniformly wide, precisely drawn (not “sketchy”), yet still very recognizably drawn by hand.
  • The artists use an absolute minimum of lines to convey a shape, and they seldom if ever resort to artistic techniques like “shading,” “shadow,” “foreshortening,” etc.

Ideally, we want creatures from our bestiary to look as though they are drawn from the same source material.

Code for review: Bestiary

Here’s another version of the procedurally-generative Bestiary that I’m building. Note that it does not yet meet the requirements for this project (I’ll post those here, too) (for example, my code fills the screen with creature faces, but in your final version of the project, your code will create at least 24 beasts, drawn one at a time to the screen, where each is saved as serial PNG file: For example, beast-001.png, beast-002.png, etc.).

As you’re reviewing this code, pay special attention to the three “custom function” scripts.

Working code: Procedural Bestiary

I’m sharing this code — which we discussed very briefly on Thursday of this past week — so that you can review some of the techniques I’m using in order to generate random faces and save that information inside each “obj_head” instance (in case I want to do something with it later).

An early bestiary

NB that the code mostly lacks comments, because it is such an early draft. But that’s a great opportunity for you to try your hand at reading through the code and making sense of it where you can. I have tried to be consistent and explicit in the code itself about what is happening — there won’t be much weirdness. So have a look!

Also worth looking at: There are two “custom functions” (which appear as “script assets”) in this game. Take a look at them, too.

Word Parsing Exercise

Update: Here’s a more polished version of the magnetic-poetry demo, which uses a “custom function” version of the word-parsing exercise from this week:

Here’s some material to help you think through the word-parsing exercise from class today (5 October 2021). The PDF file (look for it below) contains all of the code printed out for legibility, while the gamemaker zip can be unzipped and run inside gamemaker. They are almost exactly the same body of code and comments, but the gamemaker code lacks the “arrays are tables” illustration, which I’ve included below.

An array is really just another kind of table.

Also worth looking at: This “refrigerator-magnet-poetry” prototype, which uses the word parser code we looked at today.

A Clock Too Far

Here’s a silly “alternative chronograph” I put together on Friday. It took about 3 hours, all told, including comments. The chronograph doesn’t really make any sense: It’s more of a fanciful design. In itself, though, it was an interesting exercise in playing with sinusoidal waves and simple particles. Download the GameMaker zip below if you want to walk through the code.

Wait, what time is it? Seriously, I have no idea.

Becoming a Slower Coder

Project 2: Additional observations about our approach

This project (“Whack-A-Mole,” Project 2) is fundamentally more challenging than the last. It has more moving parts — literally: It has to store different kinds of data, is looking to understand varieties of player input, and to top it off, is built on top of an endlessly-repeating loop (which is built into the gopher’s STEP event), whereas the previous project was fairly linear (the EightBall code started up, waited for a cue, and then delivered its data to the player).

All of this makes for a more challenging project than the first one. That is intentional. But it is also clear — based on the conversations that we’ve been having in and out of class, that this challenge often feels alien and uncomfortable, especially for those of you for whom code is new (AKA, you’ve been writing code for fewer than 2 years). In the same vein, many of you have suggested to me that this process is completely different from the experiences you are having in other courses.

That’s a great observation, so let’s talk about that for a moment.

For the most part, nothing that most of us encounter in college is completely new: Say you take a course on post-WWII Japanese cinema, or Grand Guignol theater in early 20th Century Paris, or the aesthetics of designing for mobile platforms. Even if you know nothing about the history of Japanese cinema, for example, or the French stage, or digital aesthetics, those courses are still likely to feel loosely familiar because you already know a lot about, say, how to read novels, or how to watch movies, or even how to use a web browser on your phone. Because, look: Even if you aren’t consciously aware of those parallels (“the Grand Guignol theatre is like a horror film,” “contemporary app design standards owe a lot to digital aesthetics on mobile platforms”) your infinitely plastic, endlessly playful brain is constantly looking for patterns that seem to repeat across superficially dissimilar concepts, disciplines, institutions and behaviors. Pattern recognition is what we do: Identifying even the smallest similarities between a thing we know, and a thing we don’t know, is how we get our foot in the door to better, deeper, and faster understanding of… well, nearly everything.

Nearly everything.

Code — especially for novices — does not work like this, though. In this respect, coding can feel like an alien experience: Complex and full of knots and tangles and confusion, and it often seems fundamentally different than most of the other knowledge domains you’ve been carefully mastering for over a decade now.

So why can code often feel so alien, so endlessly confusing? Why is learning to code sometimes infuriating, in a way that working through your Intro to Health Sciences textbook is not?

In my experience, code is hardest when we approach it like we do our courses in Japanese cinema, or French theatre, or mobile design. The thing is that we are GREAT at learning almost-new things. We pick that stuff up quickly, and often intuitively. When we read a textbook, or watch a film in a foreign language, or learn about the history of some technology, we are constantly short-circuiting the process: We don’t advance the film one frame at a time; we don’t learn about a city by identifying each building and every citizen, one by one.

We’ve learned to work in chunks, a process at which our brains excel: A word (like the word “word“) is really a series of 4 letters, one after another. And each of those letters is really just a collection of black strokes carefully organized on the page, precisely arranged, one next to the other.

BUT WE SELDOM SEE THEM THAT WAY: Instead of a collection of vertical and horizontal black strokes organized into 4 tiny groupings; instead of the letters W, O, R, and then D, we perceive them as a single unit, “word.” In fact, in many cases, we can look at a sentence or a paragraph, a chart or a photograph, and understand what we’re seeing immediately: We don’t always have to reduce a thing to its component parts to understand it. Despite what our parents told us, we have learned that we can judge a book by its cover, or an album from one song. We are very often right, and we get better at it every year.

But (to my mind, at least) that’s where learning to code causes trouble. We keep wanting to understand and manipulate code in chunks, but we can’t. Not yet.

The time you invest in each project will be far more remunerative if you insist on slowing down and try not to see things in “chunks.” Instead, force yourself to work line-by-line.

Before you move from one line to the next in your code, for example, ask yourself:

When GameMaker encounters the line of code I just wrote, do I know exactly what will happen? Or do I just have a sense that the line of code I just wrote seems always to appear in programs like this one… but I don’t know exactly what it does. There’s nothing wrong with the latter (we’ve got to start somewhere). But you want to insist on getting to the former more often.

  • If you often find that your code works, and then it doesn’t, and then it kinda does, but finally you have absolutely no idea why the outcome keeps changing;
  • if you look at the code in your own game and discover that you’re pretty sure there is stuff in there you have never seen before; or
  • if you find yourself changing one seemingly trivial detail and suddenly unable to run your program;

Then the best fix is probably a good opportunity to rethink how you are approaching this process.

And that is what I want to emphasize, as I have before: In my experience, learning to code is mostly about learning to make sense of things on a much smaller, much more exacting scale than we do otherwise in our lives. Learning to code is about forcing yourself to read your code line-by-line, slowly and methodically, instead of trying to understand everything in big, baroque chunks.

Now: Doing this does not magically make all of your problems go away. But you knew that already. What it can do is help prevent the downward spiral of confusion and frustration that lots of novice and intermediate programmers get ourselves into.

SO HOW TO DO THAT?

These are things I’ve already suggested, but I’ll repeat them, now and later, as they are fundamental to this practice:

Whether you are attacking the whole game, from splash screen to game over screen, or if you are trying to make a specific thing happen at a particular moment, you must force yourself to begin from a non-technical version of what you’re building. You will not be successful by just jumping into GameMaker and writing code. You will not be successful by just “thinking it through,” in your head, and then jumping into GameMaker and writing code. All of us shoot from the hip occasionally, of course, but that will seldom work out well for you. Yes: It often feels foolish; slow; childish. Dumb. It is not even even vaguely sexy. But in the end, it is an absolutely necessary part of this process. We’ve already modeled several approaches, and will continue to model more: Some of you may make use of comprehensive outlines, some of you may describe events in paragraph form, some of you may create flow-charts or graphical descriptions of the code you’re writing. But — one way or another — it has got to happen.

One side benefit of doing this, of course, is that you can worry about the logic of a process without necessarily worrying about the specifics of the programming language. It lets you think about things on a level of abstraction that we commonly associate with fun (the same abstraction as is common, e.g., in crosswords and television procedurals).

I’ll come back to this later this week as we start Project 3. But to provide you with another concrete example of what it looks like to insist on this kind of specificity, this kind of granularity, let me share with you four or five iterations of my Whack-A-Mole pseudo-code. Each pass is another opportunity to un-chunk my assumptions and work towards irreducible statements.

“Draw the gopher holes and have the gophers randomly appear”

“Controller creates gopher holes and gophers in one swoop; each gopher goes up and down according to an internal clock; if we click on them they die.”

Controller creates back of hole on bottom layer,
Gopher on middle layer,
Front of hole on top layer, using same x value for consistency;
Each gopher has a clock that increments regularly, in tiny amounts;
Redraw each gopher with new Y based on sin() of that clock;

By pass FIVE (excerpted here, because it has gotten pretty long by now), I’ve finally gotten to asking YES/NO questions (which is what I’ll need the computer to do, too):

With every single loop of the STEP event, ask: Did someone just click? AND Was the click ON a gopher? AND Is the mouse NOT on top of the gophers’ burrow? AND is the gopher NOT dead? Affirmative to all? Then: Show the “dead” gopher, add to score, and slow down the animation.

The code that comes out of this ends up at the bottom of my gophers’ STEP event loop. It’s a fairly 1:1 translation of the above pseudo-code.

if (isLMBPressed && isMouseEnter && !isDead && !myCraterID.isMouseEnter ) {
	image_index = 1;
	image_speed = 0;
	clockspeed = 0.009;
	score = score + 5;
	isDead=true;
}

While we’re here: That first line is a great place to ask “Do I know exactly what this code is up to?” Here’s the low-down:

If (condition1 && condition2 && condition3 && condition4){ 
    do this;
    do this;
    and then do this;
}

“IF( )” always means “If the following expression (or formulae or variables or statements) can be resolved to be TRUE, then do the code that appears inside the curly braces. Otherwise, skip the whole thing.”

&& means “perform a Boolean AND operation.” Boolean operations are just teeny tiny little rulesets. They never ever vary. To explain the above code, let’s imagine you are writing a search query in order to find a file on your computer. You know a few things about the file:

  1. It is a JPG file;
  2. It is bigger than 200k in size;
  3. It is definitely NOT in the WALLPAPER folder.

So I could create a set of outcomes for each search requirement, and gradually shrink my field of possible answers by looking for common members across all the sets. OR I could let the computer do it. Maybe it would look something like:

file type == .jpg
&& 
size > 200k
&& 
folder != "user/garrison/wallpapers"

If we test every file on your computer against these parameters, the computer will only return the files that answer TRUE to each of these three conditions: Is it a .jpg file? AND is it more than 200k in size? AND is the folder it is store in NOT /user/garrison/wallpapers?

So each “condition” can be isolated and determined to be TRUE or FALSE. And the boolean operations allow us to say ensure that all three conditions have to be TRUE for a file to be included in the results.

Which brings me to that last thing: The exclamation point (sometimes called a “bang” or a “boolean NOT”). Do you remember “opposite day” from middle school (ah, the endless charm of public education!)? A Boolean NOT is basically a symbol for “opposite day.” In the case above, it specifies that the folder must NOT equal “/user/garrison/wallpapers.” Earlier, one of the conditions I was checking was to see that my gopher “!isDead”. Remember first that “isDead” and “isDead == true” are both the same statement: We often just take out the “==true” part because if() conditionals are always ONLY interested in finding out what things are TRUE. So in this case, “if (!isDead)” means “if (isDead == false)”.

Sorry, way too long. We’ll pick this up again on Tuesday.