Category: Best Of

A tale of two GML scripts: code optimization through iterative development

Today I wanted to share two versions of a function that I wrote, in order to show how my iterative approach to software development works when I am doing code optimization to improve performance.

This example comes from my iMprOVE_WRAP asset. It’s a function that returns the shortest distance (taking into account the wrap capabilities of the calling object) between the calling instance and a target object.

The first implementation works, in that it correctly does what it’s supposed to do, but I never released it, because I wasn’t satisfied that it was good enough code to ship.

///iw_distance_to_object(target_obj, x1, y1, x2, y2, do_wrap_h, do_wrap_v,)

///@description Returns the distance_to_object from an improve_wrap object calling this function to another instance. 
///Compares all relevant points for the iw_object and returns the nearest distance, taking the wrap range into account.
///@param target_obj id of the target object to determine the distance to.
///@param x1 left x boundary of wrap range
///@param y1 top y boundary of wrap range
///@param x2 right x boundary of wrap range
///@param y2 bottom y boundary of wrap range
///@param do_wrap_h set whether the horizontal wrap is on (true) or off (false)
///@param do_wrap_v set whether the vertical wrap is on (true) or off (false)


//get the distance from the nine virtual positions
//return the shortest distance
var obj = argument[0];
var iw_distance, iw_distance_up, iw_distance_down, iw_distance_left, iw_distance_right, 
    iw_distance_up_left, iw_distance_up_right, iw_distance_down_left, iw_distance_down_right;
var tempx, tempy, shortest;
var x1, y1, x2, y2, range_width, range_height, do_wrap_h, do_wrap_v;

//keep track of original location of target object
tempx = x;
tempy = y;

//set up wrap range
x1 = min(argument[1], argument[3]);
y1 = min(argument[2], argument[4]);
x2 = max(argument[1], argument[3]);
y2 = max(argument[2], argument[4]);
range_width = x2 - x1;
range_height = y2 - y1;

do_wrap_h = argument[5];
do_wrap_v = argument[6];

//check distances
//check center
iw_distance = distance_to_object(obj);

if do_wrap_h && do_wrap_v //wrap vertical and horizontal
{
  //check corners
  x = tempx - range_width;
  y = tempx - range_height;
  iw_distance_up_left = distance_to_object(obj);
 
  y = tempx + range_height;
  iw_distance_down_left = distance_to_object(obj);
 
  x = tempx + range_width;
  iw_distance_down_right = distance_to_object(obj);
 
  y = tempy - range_height;
  iw_distance_up_right = distance_to_object(obj);

  //check left and right
  y = tempy;
  x = tempx - range_width;
  iw_distance_left = distance_to_object(obj);
  x = tempx + range_width;
  iw_distance_right = distance_to_object(obj);

  //check up and down
  x = tempx;
  y = tempy - range_height;
  iw_distance_up = distance_to_object(obj);
  y = tempy + range_height;
  iw_distance_down = distance_to_object(obj);
 
  shortest = min(iw_distance, iw_distance_up, iw_distance_down, iw_distance_left, iw_distance_right, 
                iw_distance_up_left, iw_distance_up_right, iw_distance_down_left, iw_distance_down_right);
}
if do_wrap_h && !do_wrap_v //do_wrap_h
{
  //check left and right
  x = tempx - range_width;
  iw_distance_left = distance_to_object(obj);
  x = tempx + range_width;
  iw_distance_right = distance_to_object(obj);

  shortest = min(iw_distance, iw_distance_left, iw_distance_right);
}

if do_wrap_v && !do_wrap_h //do_wrap_v
{
  //check up and down
  y = tempy - range_height;
  iw_distance_up = distance_to_object(obj);
  y = tempy + range_height;
  iw_distance_down = distance_to_object(obj);

  shortest = min(iw_distance, iw_distance_up, iw_distance_down);
}
if !do_wrap_h && !do_wrap_v
{
  shortest = iw_distance;
}

//return calling instance to original location
x = tempx;
y = tempy;

return shortest;

Let’s take a moment to appreciate this function as it’s written. It’s well-structured, documented, and expressive. First we declare a bunch of variables, then we do stuff with the variables, then we get our answer and return it. And this gives a correct result…

So what’s wrong with the above? It’s an inefficient approach, which checks each virtual position of the wrapping object. If the calling instance wraps vertically and horizontally, it has to temporarily move the calling instance 9 times and check the distance from each of 9 virtual positions, then return it back to its original position, only to return the shortest of those 9 virtual positions.

There’s also a lot of code duplication.

Still, it’s not horrible code. But it’s up to 9x slower than the distance_to_object() function it’s based on, if you’re wrapping in both directions, which will probably be common. I didn’t think that was good enough.

Rather than check each virtual location to see which is the shortest distance, we just need to know whether the horizontal and vertical distances are more than half of the width and height of the wrap region. If they are, then it’s shorter to go around the wrap. To know this, you simply take the x and y values of the two positions, subtract one from the other, and compare to the size of the wrap range. Once you know which virtual position is the closest one, you can temporarily place the calling instance there, and use distance_to_object() to get that distance. Put the calling instance back where it was, and then return the distance.

I realized as well that depending on whether the calling object wraps in both directions, you may not need to check for a wrap shortcut in the horizontal or vertical. So we can potentially avoid doing some or all of the checks depending on whether the do_wrap_h and do_wrap_v arguments are true or false. As well, this means we can avoid declaring certain variables if they’re not needed, which conserves both execution time as well as RAM.

I usually create local script variables in a var declaration, and assign the arguments to them so the code will be more readable, but I wanted to avoid doing that so that this function could be as lean and fast as possible. This might be an unnecessary optimization, but that’s hard to predict since I have no way of knowing ahead of time how this function might be used in a future project. In a project with many wrapping instances, it could very well be called many times per step, and every optimization could be critical. Since the script is intended to be included as a function in an extension, once I have it working properly it shouldn’t be opened for future maintenance, so making the script readable is not as important. So I opted to remove the local variable declarations as much as possible and just use the argument[] variables directly.

Also, to ensure that the wrap range is defined properly, in the non-optimized version of this function, I declare x1, y1, x2, y2 and assign their values using min() and max() so that (x1, y1) is always the top left corner, and (x2, y2) is always the bottom right corner of the wrap range. Technically for this function, we don’t care precisely where the wrap range is, only what the width and height of the wrap range are. That being the case, I can further optimize what I have here, and rather than use min and max, I can just take the absolute value of the difference of these two values.

It turns out that the process I went through to optimize this function is pretty interesting, if you care about optimizing. So I’ll go into greater detail at the end of this article about the approach I took to get there. But for now, let’s skip ahead and look at the finished, optimized function. Here it is, re-implemented, this time doing only the minimum amount of work needed:

///iw_distance_to_object(obj, x1, y1, x2, y2, do_wrap_h, do_wrap_v)

///@description iw_distance_to_object returns the shortest distance in room pixels between two objects in the wrap range, 
///taking into account the horizontal and/or vertical wrap properites of the calling object.
///@param obj the id of the target object
///@param x1 left x boundary of wrap range
///@param y1 top y boundary of wrap range
///@param x2 right x boundary of wrap range
///@param y2 bottom y boundary of wrap range
///@param do_wrap_h set whether the horizontal wrap is on (true) or off (false)
///@param do_wrap_v set whether the vertical wrap is on (true) or off (false)


if !(argument[5] || argument[6]) //not wrapping actually
{
 return distance_to_object(argument[0]);
}
else
{
 //We're going to figure out which virtual position is the nearest to measure from
 //To do that, we have to compare the h-distance and v-distance of the calling instance and the target position
 //If this distance is <half the range size, then the original position of the calling instance is closest
 //Otherwise we have to use one of the virtual positions
 //Then we're going to temporarily put the calling instance in that location, get the distance, and put it back 
 
 //arguments
 var tempx = x, tempy = y;
 
 if argument[5] //do_wrap_h
 {
   var range_width = abs(argument[3] - argument[1]);
   if abs(x - argument[0].x) > (range_width * 0.5)
   {
     x -= sign(x - argument[0].x) * range_width; 
   }
 }
 
 if argument[6] //do_wrap_v
 {
   var range_height = abs(argument[4] - argument[2]);
   if abs(y - argument[0].y) > (range_height * 0.5)
   {
     y -= sign(y - argument[0].y) * range_height;
   }
 }
 
 var d = distance_to_object(argument[0]);
 
 //return calling instance to where it was
 x = tempx;
 y = tempy;
 
 return d;
}

We don’t need to measure all nine distances to know which is the shortest; we can tell by comparing the direct distance to the size of the wrap zone — if it’s less than half as big as the wrap zone, the direct distance is the shortest. If not, then we need to wrap. We can check the x and y axes separately, and if both are called for then we can just combine them.

The second function should be much faster to execute, and uses less RAM. How much faster? Well, let’s do a test project using my Simple Performance Test and compare.

Download the iMprOVE_WRAP distance_to_object test project

It turns out that the improved code runs about 50% faster than the old code! That’s a measurable and worthwhile improvement. Although, that said, the old function ran well enough that I could have released it, and likely it would not have been a problem for many uses, particularly in Windows YYC builds.

Appendix: Optimization through Iteration

(more…)

Review: Pitfall! & Pitfall II: Lost Caverns

One of the most popular and successful games on the Atari 2600 was Activision’s Pitfall!, designed and programmed by David Crane. A proto-platformer, it featured running and jumping adventure in a jungle setting. Coinciding with the iconic blockbuster movie Raiders of the Lost Ark, released one year previously, it was arguably better at capturing the fun and spirit of an Indiana Jones adventure than the official Raiders videogame released by Atari.

As Pitfall Harry, you explore a jungle and found treasures such as gold, silver and diamonds, while avoiding obstacles and deadly animals drawing inspiration from Tarzan and Indiana Jones, such as snakes, scorpions, and crocodiles. Due to its brilliant technical execution which pushed the limits of the 2600 hardware, Pitfall! was one of the top titles of its era, and is still remembered fondly by retro gamers today.

Pitfall! gave us running and jumping, and swinging on vines, but didn’t really have platforms per se. There’s just two elevations to run along: a flat ground level, sometimes with holes to jump over, or vines to swing on, and an underground level, sometimes with ladders and holes connecting the two. The jump mechanic was a bit primitive, and limiting, compared to later platformers — Harry can only jump up or forward, and once you press the jump button, he always jumps the exact same height and distance, and he cannot change course in midair. While this limits the type of platforming action the game can offer, it was nevertheless enough to create an enjoyable, challenging game. A bit monotonous, perhaps, compared with later Super Mario platformers that would follow a few years later, but if we look to Mario in 1981, his jumping physics were also limited in much the same way.

The way the underground level relates to the above world is strange and mysterious. Pitfall! doesn’t scroll, so when Harry runs past the edge of a screen, the game advances one screen and we find him in a new “room”. But when he crosses the edge of the screen while underground, he advances several screens. Thus, the underground is a potential shortcut, allowing Harry to skip over screens and bypass the challenges there, hopefully to pop up closer to the next treasure. This isn’t really explained to the player, who has to discover it and puzzle through it on their own.

As well, Harry can run both left and right, and it’s not entirely clear which direction he should run — due to the direction of rolling log obstacles, it seems to be the intent that you should run to the right, jumping the logs as they approach you. But it’s a bit easier to run left, going with the flow of the logs — and there are treasures to be found either way. These ambiguous choices of this helped give Pitfall! a depth and replayability it would not otherwise have had.

The sequel, Pitfall II: Lost Caverns, released in 1984, was equally well-received, and in a number of ways was an even greater technical feat. In this installment, we to get see Pitfall Harry swim, and catch a ride on a balloon, and a larger variety of dangerous animals that Harry must evade. There’s even dynamic background music that plays throughout the game, changing situationally — pick up a treasure and the music becomes happier and more adventuresome; get injured and the music turns sad. Go for a balloon ride and hear a bit of circus trapeze music. By 1984, games were starting to come with soundtracks, but this sort of dynamic music was still years ahead of its time.

The game also features an innovative waypoint system that replaces “lives” — you can fail as much as you need to, but the game won’t end; instead you’ll be returned to the last checkpoint you touched, and resume from there. In virtually all games up to this point, games granted the player a number of lives, typically three, and allowed extra lives to be earned somehow. Pitfall II was one of the earliest, if not the first, to do away with this, and allow the player to explore and take risks without the threat of a “Game Over” ending the fun. Decades later, this checkpoints without lives system has became a preferred method for making difficult platformer games that aren’t excessively punishing or unnecessarily frustrating.

There were a number of other games released in the Pitfall series on other consoles, but after the disastrous 1986 NES port, Super Pitfall, I had lost interest in the franchise and moved on to other things, so I never played any of the other games.

Of the two VCS titles, most fans seem to prefer the second. But while I do find it to be the more technically impressive of the two, I find that I prefer the original. I feel that Pitfall II suffers a bit from repetitive sequences where you have to pass the same enemies an excessive number times. Toward the end of the game, you have to climb upward while evading level after level of condors, bats, and scorpions — and each enemy requires near-perfect precision. Make a single mistake and you go all the way back down to the last checkpoint. There’s something like 20 creatures in a row that you have to run under, and it’s frustrating and tedious. There’s no other way to get past them — no ducking, no shooting, just time your run perfectly and get under them, or jump over them, and if you screw up even once, it’s back to the last checkpoint to start over. This has always struck me as poor design, rather than a fun challenge, so I’ve always felt like the original had the superior design, even if the sequel may have had a lot of cool, innovative features.

Still, both games are among the best made for the VCS, and are historically significant innovators that established and advanced the platformer genre

Top plays from Ludum Dare 36

In no particular order, here are the best-made games that I’ve played from Ludum Dare 36 so far…

Anachroma by Zillix

Anachroma by Zillix

Anachroma is a delightful puzzle platformer where the puzzles are defined by the topology of the level and the rewards are color-based, and unlock more new puzzles. I really got into this one.

Cognizance by Managore

Cognizance by Managore

Daniel Linnsen’s done it again, with a fantastic platformer mechanic involving a rotating gear wheel that can climb walls and interact with its environment to power treadmills and other cog wheels in order to move platforms and solve puzzles.

Canoe and Spear by BluShine

Canoe and Spear by BluShine

This is a fantastic single-screen death match, basically a tiny Towerfall: Ascention with a unique canoe paddling mechanic. Toggle left/right arrows to paddle/steer, and fire a spear with the Z or X button. Up to 4 players can play head to head, or you can play vs. AI. Built in PICO-8.

Invent the Wheel by Delicious Code

Invent the Wheel by Delicious Code

This simple game is surprisingly fun and addictive. All you have to do is draw a circle, and the resulting shape will roll down a hill. The faster it rolls down the course, the better your time. The more round the shape is that you draw, the better it will roll. It also seems to help to draw as large as you can.

Supercontinent LTD

Supercontinent LTD

A point and click mystery that you solve with a little hacking and social engineering via telephone. Great atmosphere and mood created by the graphics and sound, and the dialog system is fantastic as well.

The Leak by cabbage_

The Leak by cabbage_

A little adventure/RPG that you can play on a real Game Boy(!!)

Old Man’s Sky by Geared Games

Old Man's Sky

A parody of No Man’s Sky, or a de-make in the style of the Atari 2600, Old Man’s Sky pretty well skewers No Man’s Sky for being a pointless game about infinite sameness, as you go from world to pointless world, exploring and finding only differently colored versions of the same old stuff, over and over again. Never do you encounter anything truly interesting, nor does anything really happen. Still, it’s oddly beautiful, in its own way.

Kites by VitasaMode

Kites

A beautiful homage to Missile Command, set in ancient China. Fire rockets to stop a never ending flock of kites. The kites don’t seem to do any harm, other than if you let the blue ones fly over your city, you lose points. And if you accidentally hit a red one, you also lose points. The art direction is very nicely done.

Review: No Mario’s Sky/DMCA’s Sky

In my last post, I talked about the recent copyright and trademark infringement takedown actions initiated by Nintendo against No Mario’s Sky and various other games hosted on GameJolt.

Here’s a review of No Mario’s Sky/DMCA’s Sky.

No Mario’s Sky was made in a weekend for Ludum Dare 36. It is a mashup of Hello Games’ No Man’s Sky and Nintendo’s Super Mario Bros. The theme for Ludum Dare 36 was Ancient Technologies. It’s unclear how this game relates to the theme. However, due to the popularity and familiarity of Mario and No Man’s Sky, the game got quite a lot of attention in very little time, and was picked up by websites such as Kotaku and Polygon.

The premise of the game is that Mario is looking for the Princess on an infinite series of procedurally generated 2D Mario worlds. The worlds wrap around a circle, giving them the appearance of planetoids.

Once you’ve satisfied your curiosity on one world, you can summon your spaceship and take off in search of another world. Apart from the color scheme of each world, there’s not all that much to differentiate them, which may be due to the game being developed in just 72 hours, or may be a deliberate commentary on the procedurally generated sameness that many players of No Man’s Sky have complained about.

No Mario's Sky

From a Mario standpoint, the game only borrows the titular character, the goomba enemy, and the basic concept of jumping on platforms and enemies, collecting coins, and hitting platforms from below. No sprite artwork is taken from Nintendo’s games, as all sprites and tiles appear to have been re-created by the ASMB development team, and while the Mario and Goomba characters are recognizable, they are not in any way confusable with Nintendo art assets. There is no brick breaking, no super mario mushroom, no star man, no fire flower. Again, this is likely due to the compressed schedule under which the game was created. Each world plays its own variant of the Super Mario Bros theme music, which is again a re-done composition, not the original music ripped from the Nintendo game.

In short, from a copyright infringement standpoint, this game is in a gray area, but pretty safe, in that nothing is actually copied directly from the Nintendo games. This game is about as much a Mario ripoff as KC Munchkin was a Pac Man ripoff. (Atari successfully sued Philips to stop the sale of K.C. Munchkin, even though the game was not Pac Man, but the case was bullshit and probably would not have succeeded were similar suit brought today.)

From a trademark infringement standpoint, of course, the game clearly is using the identity and behavior of the famed Nintendo mascot, without authorization or permission of Nintendo. If this were a commercial product, it would certainly be liable for trademark infringement. However, this is probably closer to a parody, or a “fan game” or homage. Unfortunately, the latter two concepts don’t exist as legal categories. It might be that the creators could have successfully defended the game as a parody, but that would have involved going to court and rolling the dice to find out whether they could persuade a judge of that. There’s simply no way an independent developer has the time or resources to try to defend what amounts to a weekend’s worth of work against a company the size of Nintendo for what would surely be months or years of litigation.

If ASMB had avoided use of the Mario name, perhaps renaming him something recognizable, like “Mustachhio”, say, and if the music had been done in a way that was recognizably Mario-eque without having the exact same melody, probably Nintendo would not have had any copyright leg to stand on, and the game could have remained as-is. From a trademark standpoint, though, it probably does run afoul of Nintendo’s trademark on the Mario Bros. franchise, given that it uses the Mario and Goomba names and likenesses.

While the game is fairly bland as-is, the concept is certainly fun and held promise. Were the game to be developed further, to better incorporate the Mario characters and play mechanics, it could have been a very enjoyable game.

DMCA’s Sky removes the Mario and Goomba artwork, replacing them with a generic space man and alien, and the music has also been replaced, but otherwise the game is much the same. Interestingly, the jump, coin and 1-up pickup sounds remain recognizably Mario-esque, but again do not appear to be direct rips from original sources.

DMCA's Sky

I suppose Hello Games could also make an IP infringement claim if they wanted to, and force the game to remove the procedurally generated planet hopping, at which point the game wouldn’t have much left in it anymore. Notably, so far at least, they haven’t.

It turns out, though, that when you break down just about any video game into its fundamentals, pretty much every game is based on, or borrows from, concepts that came from some other game. And — this is the important thing that must not be lost sight of — concepts are not subject to copyright. Not even play mechanics are copyrightable. Only actual works are copyrightable.

Of course, copyright is only one branch of Intellectual Property law, and there’s also potentially opportunity for patent and trademark lawsuits to shut down a game that borrows “too much” from a well known existing game.

Despite this, much of the charm of No Mario’s Sky was in its mash-up-ness, and this charm is effectively stripped from it by removing the Mario references. So clearly, the game derives some value from referencing the source material that it is based on. I don’t think that can be denied. I have a harder time seeing how this game harms either Nintendo or Hello, however. It was available for free, not for sale. It isn’t reasonably mistake-able for a real Nintendo game, and if that were a risk it could be prominently disclaimed on the title screen that it was not in any way connected to Nintendo, who retains full ownership of the “real” Mario characters. I see little evidence that the existence of this game or the numerous other Nintendo-IP infringing games done by fans over the years (including ROM hacks, homebrew games, de-makes, and homages) has in any way diminished the Nintendo brand or harmed Nintendo as a business.

The takedown of unauthorized fan games isn’t anything new — it’s just the latest in a string of consistent defenses of Nintendo’s IP rights. It’s clear that Nintendo is aggressive in protecting their IP rights, and have always been. This has been in part due to their corporate culture, but also in larger part due to the nature of IP law.

But IP law isn’t immutable. We could as a culture elect to shape law differently, if we could agree to.

Nintendo’s takedown of videos on youtube and elsewhere, of people playing their games who do not participate in or follow the rules set forth by Nintendo in the “Nintendo Creator’s Program” is ridiculous — it’s not a copyright infringement for me to play a video game, or to talk about a videogame, or to record me talking about a videogame while playing it, and footage of said videogame that I create should legally be my sole creation (while the characters owned by Nintendo and other IP-holders are still retained by those holders).

If I want to make a video of a videogame for purposes of review, criticism, or parody, I shouldn’t have to obtain the permission of the IP rights holders of the videogame, nor should I have to share revenue with them. They earned their revenue already through sale of the game, and did none of the work to produce the video, so why should they be entitled to a share of revenue generated by the video?

Likewise, if I want to make a videogame that references other videogames, much as a work of literature may reference other works of literature, creators should have some right to do so. Exactly how this should work out so that the original creator’s rights are protected and respected isn’t very clear, however.

Ultimately, the power seems to fall to those who have the deepest pockets with which to pay the most and best lawyers. As as a result, the culture, and the game playing public, is poorer for it.

Mario on iOS, Nintendo copyright takedown

Nintendo announced the first (authorized) appearance of Mario on iPhone a few days ago:

There’s much to be made of this.

Ten years ago, while the Wii was selling phenomenally well, there were some wild rumors about Nintendo and Apple teaming up to bring games to the Apple TV device. But, while tantalizing, these rumors never panned out, nor really made sense. While both companies were extremely successful on their own, they didn’t really seem to need each other, or have any reason to cooperate. Nintendo software licensees could have certainly helped put Apple TV in many more homes, but what could Apple have offered Nintendo, who weren’t having any trouble selling the Wii?

Fast forward to 2016, and the successor to the Wii, the Wii U, is widely regarded as a misstep for Nintendo, and now it appears maybe they do need some help. But rather than looking for it in the living room, where they are poised to launch their next-generation NX console in a few months, right now they are going straight for the pocket. Meanwhile, Apple’s huge hit from 2006, the iPhone, has been a juggernaut for much of these last ten years. And here is where Apple and Nintendo can help each other out.

It’s the first time in decades that Nintendo has put software out on a platform that it does not own. This could be seen as a concession that Nintendo is no longer dominant in gaming hardware, or simply an acknowledgment of the vitality of the mobile gaming market. While Nintendo have been hugely dominant in the handheld market since they released the Game Boy in 1989, smartphone and tablet devices have in the last decade created an even bigger market for games. With the massive success of Pokemon Go earlier this year, the writing was on the wall, and Nintendo making this move now only makes sense. In fact, it’s probably overdue.

Entitled Super Mario Run, it appears to be an endless runner type game rather than a typical 2D platformer. Due to the iPhone touch screen being the only controls, and a desire to make the game playable one-handed, this design addresses the constraints imposed by the user interface in about the only way that would work well.

More Nintendo Copyright Takedowns

Nintendo also made headlines this week by issuing takedown notices for a large number of unauthorized games that infringe upon Nintendo-owned trademarks, particularly Mario and Pokemon. It is not surprising at all that this should happen, but still disappointing for people who built or enjoyed those games. While many of these games may have been derivative and inferior games done in homage of, some were parodies or innovative or just fun, well done fan homages.

It’s too bad there doesn’t exist a legal framework in which fan-made games can co-exist peacefully with official releases by commercial studios, but licensing is only a solution if the IP-holder embraces it. Nintendo are within their rights to take these actions to protect their trademark and intellectual property rights, of course, and perhaps it is necessary for them to vigorously defend their trademarks or risk losing them entirely, but it’s nevertheless possible to set up a legal framework by which these unofficial games could be allowed. While it’s entirely ridiculous in my opinion for Nintendo to claim copyright and trademarks on speed run, Let’s Play, and review videos featuring their products, something like the Nintendo Creators Program would make a lot of sense for fan-produced games.

What might such a program look like? I would propose something like the following…

  1. The fangame creator would acknowledge that Nintendo created and owned whatever they owned.
  2. The fangame creator disclaims that Nintendo do not have any responsibility for content the fangame, and that the fangame is not an official Nintendo release.
  3. Any revenue derived from the fangame would need to be disclosed and shared with Nintendo.
  4. The fangame could be nixed by Nintendo (pulled from release) at their sole discretion at any time.

I very much doubt that a company like Nintendo would ever agree to such terms, but it’s too bad. Apart from perhaps Nintendo, everyone is worse off because of it.

The irony of this situation is that Nintendo can copyright and trademark its characters, but not the mechanics or genre of game. (Nor should it.) Someone can invent the infinite runner, and Nintendo can decide to do a Mario infinite runner game, and not owe anything to the inventor of the infinite runner game. So can anyone else. And Nintendo can make a running and jumping platform game, and anyone else can too, duplicating the Mario mechanics and rules system entirely if they should wish to, but simply can’t use the name Mario or the likeness of any of Nintendo’s graphical or audio assets.

Tutorial: GameMaker Object with multiple collision zones

GM Version: GameMaker:Studio 1.4 (concept should work in earlier versions)
Target Platform: ALL
Example Download: https://csanyk.com/cs/releases/games/LD48-36_AncientTechnologies/LD48_36_Ancient_Technologies_csanyk.gmz
Links: http://ludumdare.com/compo/ludum-dare-36/?action=preview&uid=10473

Summary:

How to make an object that handles multiple collision zones by using multiple sub-objects

Tutorial:

I used this technique in my recent Ludum Dare game, Ancient Technologies. The project has a TON of extraneous code in it, some of which is pretty messy, so I’ll replace the download link with a cleaner example project of just this technique in coming days.

If you look at the Atari 2600 console in this game, it is actually multiple objects, made to look like a single object. This enables each switch to have its own collision detection and mouse events.

I accomplished this with a few simple techniques that work really well:

The console itself is the main object. Then there are 6 objects for the 6 switches on the console.

I then took the image I wanted to use for the sprite, and cut out all the different clickable regions, putting each into its own subimage by itself. Then once all the subimages were created, I removed each subimage into its own sprite, and set the collision mask to cover just the pixels in the image

Atari 2600 clickmap sheet

As you can see, this approach results in a lot of “wasted” space in the sprite sheet in the form of transparent pixels, but if you’re concerned about this, you could always achieve the same effect by using sprite origin and sprite offset to position the images without all the whitespace in the sprite sheet. I skipped using sprite origin because I didn’t feel like bothering with counting pixels and setting each sprite origin differently. That would have been tedious, and if I had ever needed to change the sprites, could have thrown everything off and required extensive rework to recalculate again. If your sprites are tiny, it probably doesn’t matter too much, but if they’re quite large, then you should probably use sprite origin/offset.

(Note: With the small pieces, you’d need to use origin numbers that are larger than the dimensions of the sprite itself, or possibly negative values for the origin. You’d also need to carefully calculate the distance to set the origin so that the images all line up precisely. The goal is to make all the images align when each instance’s x and y are identical. This makes them easy to move around together as a group, without having to deal with offsets and so on. To make everything align easy, I created sprites that have the same height and width, so that getting them to align takes no extra effort or calculation at all.)

Next, use the “main” object to generate the collision zone objects, in the create event:

Note the example code below isn’t exactly the code in my project, but is cleaned up a bit for easier comprehension.

///oConsole Create Event:
power_switch = instance_create(x, y, oPowerSwitch);
tv_type_switch = instance_create(x, y, oTVTypeSwitch);
left_difficulty_switch = instance_create(x, y, oLeftDifficultySwitch);
right_difficulty_switch = instance_create(x, y, oRightDifficultySwitch);
select_switch = instance_create(x, y, oSelectSwitch);
reset_switch = instance_create(x, y, oResetSwitch);

Since these objects all use sprites that are the same size, they all line up together when their x,y position is the same. Now you have an easy way to place just the main object and have it create all of its collision zone objects, perfectly aligned.

If the main object needs to be able to move, you just need to update the x,y of all the objects whenever you need to.

Another great thing is that each of these instances can have its own animation which is completely separate from the rest of the collection of objects. This means you can do really sophisticated animation rigging.

Also, you can access the instance variables of the collision instances by using the main instance, like so:

oConsole.power_switch.position = UP;

is (essentially) equivalent to:

oPowerSwitch.position = UP;

This allows you to use the collision zone instances’ instance variables like you would use properties of a helper class in another language, which may make accessing these variables easier if you’re used to other programming languages.

As well, if each of these switch instances were of the same object, using the id returned by instance_create() would allow you to differentiate between them. This could be useful for, say, a battleship with multiple turrets that are all of the same object type.

Finally, you’ll want to be mindful that if you destroy the main object, you’ll likely want to destroy all the sub-objects. Perhaps not — maybe you’re doing a spaceship with an escape pod or a rocket with multiple stages that break away. But in that case you’ll wan to be sure that once the main object is gone, you are handling whatever remains somehow.

Managing complexity when implementing complex conditionals

Programming is in large part about managing complexity and imposing structure onto a problem domain in order to make it simple (simple enough for a machine to be able to do the solution, as well as simple enough for human programmers to be able to build it).

Something struck me about a technique that I used when I was developing my game last weekend.

The code that handles the setup of the simulated atari 2600 console is full of conditionals like this:

if PowerPlug.is_plugged_in && (console.switch_power.position == POWER_ON) 
 && (TV.switch_power.position == POWER_ON) && Cartridge.is_plugged_in && 
 Joystick.is_plugged_in 
{
 //simulate the game 
}

So one thing that I had to deal with as I was building this was all the preliminary stuff’ you know, to create all the objects that needed to be checked in the conditional statement above. Rather than do that, all at once, and have a lot of conditions that could have bugs in them, any one of which could make the thing not work, and be hard to figure out since multiple bugs could end up canceling each other out, or mask one another in some way, I opted to simplify things at first:

if true
{
 //simulate the game
}

The advantage here is obvious. It’s simple, and you KNOW that conditional is always going to evaluate to true! So you can get right into working out the code inside the branch, without worrying for now about the conditional logic that would cause it to execute.

Once that was working, I added in stuff:

if PowerPlug.is_plugged_in
{
 //simulate the game
}
else
{
 if PowerPlug.was_plugged_in_previously
 {
 kill_game();
 }
}

Then from there it became easier to elaborate on the conditional:

if PowerPlug.is_plugged_in
{
 if Console.switch_power.position == POWER_ON
 {
 //simulate the game
 }
}
else
{
 if PowerPlug.was_plugged_in_previously
 {
 kill_game();
 }
}

//etc.

(Note, the above isn’t the exact code from my project, which is a bit messy at the moment as I have some refactoring to do, but it illustrates the point.)

I found that by coding it in this way, I could iterate and test each little piece of the logic easily, and make sure it was working before trying the next piece.

I found that doing it this way made it much, much easier to handle the complex nested if business, and as well I found it easier to know which branches of the complex conditional tree the next bit of code needed to belong in.

Now that I did it this way, it seems obvious, nothing of great insight… but 6 years ago I would have tried to code the whole thing out, and then pulled my hair out swearing at myself when it didn’t work as expected, and I had to untangle all the logic to figure out wtf it wasn’t working.

Back then I would have thought that a really superior version of me who I aspired to be would be able to code out all the conditions in one go without making a mistake.

Today a really superior version of me knows better. Start simple, add elaboration through iteration.

This is a good lesson to hang onto. And to pass on.

Of course, there’s also often ways to avoid having to code conditionals at all, but that’s a topic for another post.

Appreciating MegaMania

Megamania, published in 1982 by Activision for the Atari VCS and designed and programmed by Steve Cartwright, is one of the all time great video games, and is a standout on the Atari 2600 console and in the vertical fixed shooter genre. Inspired by the Sega arcade game Astro Blaster, but vastly better, it is an extremely well refined shooter for its time, and is a fun and challenging game to this day.

Above: Astro Blaster, an 1981 arcade game by Sega that bears some resemblance to Activision’s 1982 hit on the Atari VCS, Megamania.

Astro Blaster had many features, including digitized speech, that made it technically impressive for its day, but the design did not integrate the features particularly well, making the game overly complicated and clunky. By comparison, Megamania offers a stripped down, almost poetic experience, with elegant symmetry and proportions. Far from a ripoff of an original game, if anything it’s a refinement. Megamania expresses its beauty through minimalism and an elegant orderliness to its structure. This game is all about action and motion, and the original version just gets these things right. There is a rhythm to the game that a good player will develop a feel for, and learn to use to his advantage.

A major hit for Activision on the 2600, Megamania was later ported to the Atari 5200, and Atari 8-bit computer line, but the original remains the best play experience despite modest graphical improvements in the later releases. I’ll be discussing the original VCS version of the game for the rest of this article.

Here’s how Megamania looked on the Atari 2600:

Due to the hardware limitations of the 2600, the player is permitted only one shot on the screen at a time. The player can steer the shot with their ship as it travels upward, giving them the ability to guide their missile into the target. Somehow, despite their varied and erratic motion, the enemies often seem to line up just right so that if you’re in the right position and have the right timing, your next shot will rapidly find your next target, enabling you to clear the wave quickly and resulting in great satisfaction. But if you’re off target, the same proportions of speed and distance that line up your shots on target will cause you to miss frustratingly. It’s an elegant symmetry that provides both challenge (when the player’s timing is off) and reward (when it is on) with the same few, simple mathematical relationships, giving the game a subtle beauty.

The object in Megamania is to survive wave after wave of zany household objects that come at you from the top of the screen, as you shoot up at them for points. Your ship has an energy meter that slowly winds down, providing a time limit to complete the wave; when you complete a wave, your remaining energy meter is converted to bonus points, then refills, and the next wave begins. The waves repeat in cycles, in the following order: Hamburgers, Cookies, Bugs, Radial Tires, Diamonds, Steam Irons, Bow Ties, and Dice.

megamania enemies

There are two variations in the play mechanics, having to do with the way your shots behave:

  • In variation 1, the ship will fire continuously as long as the fire button is held down, and the shots are steerable, moving in line with the player as the player moves. This generates the rhythm that makes the game so fun, as I will show with some detailed explanation to follow.
  • In variation 2, the player must press the fire button each time to fire a shot, and the shot moves vertically only; once it leaves the gun it cannot be guided by the player.

Variation 2 requires more hand-eye coordination and greater attention from the player, and is therefore much more challenging, but I find that the feel of the game is not nearly as immersive as when you are able to steer your shots. In variation one, you feel at one with both your ship and its missile, and while you steer your shots to hit your target, you must simultaneously dodge to keep your ship safe. This creates an inherent conflict that causes the player to constantly be making decisions at a subconscious level. In variation 2, once your shot is launched, you have no further influence over it, and can only watch until it hits something or leaves the screen, leaving you only to avoid enemies and their fire until you can fire again yourself. And since there is no auto-fire in variation 2, the subtly clever timing that results from the relationship between the distance and position of the enemies, their speed, and the speed of your missile, is lost.

The sound effects, while rudimentary, are strong, and fill the game with noise from start to finish, despite being limited to your laser shot, enemy destruction, the energy meter countdown and refresh, and player death. The enemies, rather than explode, disappear with a brassy, synthesized “clang!” , while you fizzle away into nothingness when you are hit by a missile or collide with an enemy. The effects are blaring, loud and harsh, but with the volume turned down low they serve well.

The wave cycle in Megamania is particularly well paced, with a fantastic challenge curve, and a structure that reminds me of a sonnet or a fugue. Certain waves (metaphorically) “rhyme” with others, being similar in their motion patterns. Patterns established in earlier waves are elaborated upon in subsequent “rhyming” waves.

The odd-numbered waves (Hamburgers, Bugs, Diamonds, and Bow Ties) all move horizontally from left to right across the screen. In the first cycle, their motion is constant, while in the second and subsequent waves, their motion pauses periodically for a few seconds, then suddenly accelerates before settling down to normal, and then repeats. Starting with the Bugs wave, the horizontal scrolling waves add a vertical undulation to their motion, which becomes more pronounced with Diamonds and Bow Ties. Diamonds and Bow Ties “rhyme” further with each other by having a “winking” or “spinning” appearance. These are the easiest waves to clear, as the enemies pose no collision risk to the player, who can only be destroyed by enemy shots or running out of energy in these levels. As the first, third, fifth, and seventh levels in the wave cycle, they provide a breather between the more challenging waves. Each odd-numbered wave may be seen as an elaboration of the previous in the series: Hamburgers move horizontally; bugs move horizontally, and with a slight undulating vertical dip; diamonds move horizontally, have a more pronounced dip, and spin; and bow ties move horizontally, have the most dramatic undulation, and spin.

The even numbered waves all feature objects that pass vertically through the screen.

Wave 2, cookies, introduces the player to vertical motion gradually, as the cookies move primarily horizontally, while doing a two-step drop periodically, and reverse their horizontal motion as well. Cookies move in unison, all moving left or all moving right at the same time. Wave 4, radial tires, kinetically “rhymes” with cookies, but the radial tires dip more quickly, and the wave introduces a more complex motion where alternating rows of tires move left or right simultaneously. These levels are particularly dangerous, as in later cycles they descend increasingly rapidly, but a skilled player will learn, after the panic subsides, to make small, economical moves, and let the shots line up and rapidly take out strings of enemies quickly. At this point the levels remain challenging, but reliably beatable by a skilled player. You’ll die quickly if you get out of rhythm and fail to clear out enough enemies to give you adequate space to dodge, or if the computer gets lucky with one of its shots, but if you’re on your toes and in the zone you should be able to clear these waves with only an occasional death.

The next two even-numbered waves are of special difficulty, although their unique patterns do not “rhyme” with each other.

Wave 6, Steam Irons, uses a deceptive and tricky pattern. Three columns of steam irons descend, pausing and then sweeping irregularly from side to size at a speed that is very difficult for the player to track, as they seem to deftly weave right around your shots, and then descend again. The spacing of the formation is such that the player must shoot out at least one from each column, or else that column becomes an unbreakable chain when the column reaches bottom and wraps around to the top again, providing insufficient space between the rows to allow the player to squeeze in and get a shot off. If the player fails to take out at least one steam iron from each column, it is guaranteed that he will die at least once before completing the wave. The interesting thing about wave six is that it is the one wave in the entire game where the behavior pattern never varies, no matter how many times the player cycles through the game, the steam irons always move the same. Despite the lack of increasing challenge, the behavior is so frustrating and erratic that players often ascribe a sinister artificial intelligence to the steam irons. They are a constant threat to the player, no matter their skill level.

Wave 8, Dice, are special in that they are the only wave that is always the same color, yellow, no matter how many cycles the player completes. Dice are also unique in that they are the only objects that do not fire any shots at the player, and are therefore dangerous only due to collisions. Yet this is more than enough to make dice the most challenging wave to survive. The first dice wave is also the only level in the game where the objects move straight down. While their speed in the first cycle may seem overwhelming, their simple vertical motion makes it a fairly safe level. Simply stand your ground beneath a falling pair of dice and shoot, and your shot will surely find its mark, protecting you. But in the second and subsequent cycles, the dice move horizontally as well, in rows that alternate left and right, and create an almost bullet hell-ish level where dodging takes a great deal of finesse. The player has to move constantly on the dice levels to avoid fatal collisions, making it the most strenuous and challenging level, a climactic finish to the wave cycle. A skilled player can still beat the level without getting hit, but it requires great concentration and timing.

If we think of the eight waves that make up the wave cycle as a stanza in a poem, then the “rhyme scheme” suggested by the structure of the eight waves is as follows: A, B, A, B, A, C, A, D. The difficulty curve of a cycle is interesting, in that it does not simply progress in a linear fashion, but instead plots two different curves: the odd-numbered waves follow a more linear progression, while the even-numbered waves follow a steeper progression. This gives the challenge curve a continually escalating trend line while still affording the player a “breather” between two more difficult levels.

megamania difficulty curve

After three or four cycles, the difficulty does not ramp up further, and the game turns into an endurance match to see how many cycles the player can endure. If you can make it to 999,999 points, the game ends, effectively a killscreen.

One of the more interesting things to realize about the mechanics of Megamania is that (with the exception of the first Dice wave) the horizontal speed of all the enemies in the game matches the player’s horizontal speed. After the first cycle in the odd-numbered waves when the enemies accelerate to double time. The rest of the time, the horizontal speed of the enemy objects always matches the player’s horizontal speed exactly. This, combined with the shot-steering in variation 1 makes tracking the enemy objects easier, since you, your shot, and the enemy all move at the same speed, it is trivial to line up and guide the shot into the enemy on the odd-numbered waves. It also means that if you are behind an enemy, there is no way to catch up. Interestingly, players often don’t realize this, and novices and even moderately experienced players will persist in trying, to no avail, to catch up with an enemy that is just past the reach of their fire. Once you realize that it is impossible to catch up, and stop chasing, the player gains an insight that will lead them to higher skill levels — it is very common for a player chasing an enemy that they cannot possibly hit to accidentally run into an enemy missile, or run out of room at the edge of the screen and get pinned. But once you learn to avoid these two common causes of death, you become better at dodging, and the game opens up and becomes easier.

Another important realization is that the positioning of the enemies often is such that when you connect with a shot to destroy one, your very next shot will also connect with another enemy if you don’t move. It’s very common to chain together “string” of two, three or even more hits in a row, in very rapid sequence. This is key to success, and especially critical on the later cycles on the even-numbered waves, where the falling enemies present a collision danger, and taking a chain of them out immediately when the wave begins is crucial to carving out enough space to enable you to dodge and survive. When you realize this, the game becomes less about chasing aggressively and aiming, and more about being in the right position, and letting the enemies come to you. This is where the auto fire feature of variation 1 comes in to play, as once you have connected with a target, you are likely to hit again with your very next shot, and may start a chain of hits just by holding position and keeping the fire button pressed.

A final note of strategy helps with avoiding being shot by enemy missiles. Only two enemy missiles are capable being on the screen at any given point in time. What’s more, there are only two enemies at any given time who are capable of firing. If you see an enemy shooting bullets, you should avoid it and concentrate on eliminating the enemies that are not shooting, as they are less of a thread and easier to safely destroy. Don’t go under them when they stop moving, and wait for them to move again before tracking them. Then, take out the shooting enemies when they are moving, by matching pace with them. Enemy shots do not steer, so if you move in sync directly below a horizontally-moving enemy that enemy cannot hit you, and you cannot miss them. The most dangerous time in the odd-numbered stages is when are moving against their motion, from right to left, since this is the only time when you are likely to hit an enemy missiles.

Wrapping a formation of enemies

Another point of refinement that I find interesting is in the way the enemy objects wrap around the edge of the screen. Enemies in Megamania move together in large formations, but the way they wrap around the edge of the screen is interesting.

What I find innovative in this is that it doesn’t matter how large the formation is — looking at the odd-numbered waves, if you don’t shoot any of the enemies, they will form an unbroken chain as the first to appear wrap immediately behind the last. If you shoot a few, leaving holes in the formation, the holes persist and are not closed up — except if you shrink the formation at the leading or trailing edge. When that happens, the formation wraps sooner, closing the gap between the last still-extant enemy in the formation and the first. Thus, when the last Hamburger, Bug, Diamond, or Bow Tie is left in the wave, when it reaches the right edge of the screen, it wraps immediately to the left, rather than waiting for the space taken up by the no-longer-existing members of its formation. This is important because it avoids wasting the player’s time, as the energy meter winds down while no enemies are visible on the screen.

The tight, precise nature of the motion of the enemies makes Megamania a satisfying and exciting play experience, and feels complete despite a relatively small feature set. Megamania demonstrate that refinement and polish matter far more than feature count.

KB Tester utility helps you see what value to check for when a key is pressed.

KBTester is a utility/demo I made to help out with coding your keyboard_check routines. It is born out of frustration and necessity for handling certain inputs from a very fundamental input device for computer games, the standard keyboard, which are not supported out of the box.

If you program keyboard input in your games, you’ll find that, for most keys on a computer’s keyboard, you can use vk_constants and ord(letter)… but for some odd reason YYG didn’t create a vk_constant for every key on the keyboard, and don’t plan to. Not only that, but there are certain keys that don’t return the right value for ord() to work with keyboard_check.

For example, say you want to check if the period key is pressed. You might think that you can do keyboard_check(vk_period) but to your surprise, there is no vk_period constant defined in GML. So, then it must be that you need to do keyboard_check(ord(“.”)) only… it doesn’t work!

That’s because ord(“.”) returns a value of 46. But for some reason, if you want keboard_check() to return true when the period key is pressed, you need to check for the value 190. Why? Why are certain keys on the standard keyboard treated as second-class citizens? Because, sadly it’s not in YYG’s vision to improve keyboard support.

To paraphrase a certain “Evil YoYo Games Employee” who commented on my suggestions for ways the current keyboard support badly needs to be improved:

<paraphrase>Why should we improve keyboard support when you can just research what codes map to your keyboard keys, make an extension that has a few constants in it, and then hope that these will work with all keyboards and all target platforms? Just code it once and then put it up on the Marketplace. Now that the marketplace exists to provide stopgap coverage of GM:S shortcomings, we don’t have to pay our own programmers to fix those holes anymore.</paraphrase>

So, I guess we’re supposed to figure out the numbers and then code some constants for the missing vk_constants, and use those. This, despite the helpfile recommending against using hardcoded numeric values in keyboard_check because you never know if it’ll work on the target platform if it’s not Windows/Mac/Ubuntu:

NOTE: These functions are designed for Windows/Mac/Ubuntu desktop platforms only. You may find some of the in-built variables and constants aren’t valid on other platforms and many of the functions won’t work on mobiles.
Now, each key is normally defined by a number, called the ascii code, and you can directly input this number into these functions and they will work fine… But, as it’s a bit difficult to remember so many numbers and the relationship that they have with your keyboard, GameMaker: Studio has a series of constants for the most used keyboard special keys and a special function ord() to return the number from ordinary typed characters (either letters or numbers).

The implication is that, YYG seem to be saying, “Despite the promise of GM:S to be a development environment that supports multiple target platforms, we didn’t see the need to ensure that your code will run the same on all target platforms we support, or all region/localities, or with all keyboard layouts. After looking into it we decided it was too hard for us to deal with, so we’re passing it along to you to figure out for yourself. So just be aware that these may or may not work on all platforms, and that’s all the info we’re going to give you about that. You’re on your own to figure out how to solve keyboard input from any platforms that don’t work with our keyboard input functions.”

Well, for whatever reason, YYG doesn’t provide FULL keyboard coverage between ord() and vk_constants, and it’s not in their vision to address this shortcoming, so I guess you’re going to have to go out and find some reference that will tell you what numbers represent what key, and then hope they still work.

In the meantime, you can use KBTester, press a key, and get the answer without having to hunt the info down on the internet and hope it’s correct. If you’re having trouble getting keyboard_check to work, and need to verify that the magic number you’re using is indeed the right one, you can run KBTester. Press the key you want to use, and KBTester will tell you the value that GameMaker sees when it is pressed.

Recipe: really good very cute boyfriend stir fry

When I’m cooking at home, I like to do stir fry dishes in my wok. Recently I have been doing this, and have been finding that I have been feeling extra-well, and more energized on days after I have the following for dinner.

So maybe it’ll help you too. It is easy to make, reheats well, and you end up with an amount of food that you can turn into about four or five good meals.

really good very cute boyfriend stir fry

Ingredients:

  • rice
  • tofu
  • broccoli crowns
  • white onion
  • green onion
  • canned water chestnuts
  • canned baby corn
  • snow peas
  • crimini mushrooms
  • baby bean sprouts
  • sesame oil
  • teriyaki sauce
  • hoisin sauce
  • soy sauce

Equipment:

  • Rice cooker
  • Wok
  • wood spoon
  • large pot with lid

Preparation:

Rice.

Start the rice first, because it takes the longest. Depending on the type of rice you use, it will take a varying amount of water and cook time, so I can’t get too specific on this. Just watch carefully the first few times you’re cooking the rice and learn what works with a specific variety.

Some people say you should soak and rinse the rice before cooking. I don’t really bother with that; just a couple minutes of soaking, while I’m chopping the vegetables, and no rinsing.

Press tofu.

Take the tofu out of the container, and press it between two plates with a weight on top. This will squeeze out excess water, resulting in firmer tofu. After pressing for about 20-30 minutes, you can cut it into cubes. You can get away with less press time if you want, but less than 10-15 minutes is not recommended.

Chop vegetables.

Chop the different vegetables up into bite-size pieces. If you want you can save some time by buying pre-sliced vegetables. I usually get pre-sliced mushrooms, water chestnuts, and baby corn.

For the green onions, you just want the pieces to be about a quarter of an inch long.

The water chestnuts and baby corn come pre-sliced in cans, but you’ll need to strain them.

Cooking

Note: With a few exceptions, I don’t really bother with measuring ingredients. Learning to cook isn’t about following precise steps exactly every single time. It’s about exploring and experimenting and being observant and understanding.

Measuring makes it easier to repeat a result, but only if the ingredients are constant. The thing with vegetables and other ingredients is, they’re not very constant. They vary depending on season and freshness.

So the amount of oil or sauce or temperature or cook time that might be good for one session might not work for another, or according to your taste.

You develop a feel for this over time. Vary and experiment until you feel like you know what you’re doing and know what works for you. Use your eyeballs and your head. Use your tongue. This recipe will tell you what I look for when I’m cooking and how I estimate, not how to be scientific and rigid with your cooking method.

  1. Start the rice cooker about 10 minutes before you start cooking the rest of the vegetables.

    I’ve found the type of rice I usually cook takes about 20-25 min to be done, and the wok items take about 10-12 minutes or so to do.I use a rice cooker that I bought at a drug store for $10 like 17 years ago, nothing special (but it’s a really well made and well designed appliance and I’m happy with it).

    Some people use super fancy rice cookers made in Japan, that cost $150+ and use sophisticated sensors and computer programming with AI and fuzzy logic to do perfect rice every time with no fuss. There’s nothing wrong with that. At all. The Japanese know WTF they’re doing with their rice. Someday maybe I’ll buy one myself.

    With my rice cooker, it just has a setting for “warm” and a setting for “cook”. It’s supposed to switch automatically to “warm” after the water boils off. I’m not sure how it knows to do this, I expect maybe it’s by weight but who knows.

    But usually it’s wrong, so I have learned to watch it and flip the switch manually. I just need to keep an eye on it, checking on it after about 15 minutes to see how much water has evaporated.There’s a narrow window (maybe a minute or two, tops) between the water boiling away and the rice at the bottom of the pan starting to burn. That’s what you watch for. When the rice is done, the water should be boiled away leaving moist steamy grains of rice that may stick together or not depending on the variety and whether you washed and rinsed it.
    Sometimes, I see little holes in the rice, that look almost like someone took a bunch of chopsticks and stuck them in the rice, then pulled them out when the rice firmed up enough to stick that way. These holes are created by the streams of steam bubbles coming up through the boiling water. You don’t see them until toward the end when the rice is ready, and they don’t always form. But if you do see them it’s a good sign that the rice is ready.

    My rice cooker has a second stage to it, a basket that goes on the top of the cooking bowl, which you can steam vegetables, dumplings, or other food in. I put the broccoli in here. If your cooker has this feature, you can do that too, if not just stir fry it in the wok.

    Broccoli is done cooking when it is still bright green and stiff and crunchy. It is overdone when it starts to wilt, turns a darker green with a brownish tinge, or gets mushy. Basically, you just want the broccoli to be hot, not to break down the cells of the plant that make it crunchy and crisp.

    Fortunately it takes about the same amount of time to steam the broccoli as it does to cook the rice. But you may need to pull the broccoli a little early. Just lift the lid and check on it after about 10 minutes and see if it looks good.

    I don’t put anything on the broccoli while it’s steaming, just let it steam on its own. After it’s done steaming, I’ll throw it in the wok for a few seconds to a minute, taking care not to overcook it, to get some flavor from the sauces and oil.

    As soon as the rice cooker is set up and going, I turn my attention to the wok and the vegetables.

  2. Throw some sesame oil into your wok, and ignite the burner under it. Gas stove top is the only way to fly here. A “splash” of oil, enough to coat the cooking surface and leave a slight puddle is sufficient. You’re not deep frying, so you don’t need to cover the ingredients in oil, you just need enough to keep them from burning to the cooking surface of the wok. As you run through adding the different ingredients, you may need to add a little more oil at times.

    Tilt the wok so the oil flows over the entire inner surface, as close to the edge as you can get without spilling the oil. Then put the wok on the burner and let the oil flow back down to the center and get good and hot. If it starts smoking, it’s ready, but it’s also ready before then. It only takes a minute or so to heat up good and hot. Woks are made from thin metal that heats up quickly, and don’t retain the heat very long. It should be hot enough to start cooking very quickly.

  3. Now, cook the vegetables and tofu in the wok. Wait, tofu’s a vegetable too, right? Whatever, just cook the vegetables.

    Starting with the thickest, sturdiest vegetables, working my way down to the flimsiest, I cook each ingredient with a little bit of teriyaki and hoisin sauce, and throw on a little soy sauce as well.

    The sauces help flavor the food as well as keep it from drying out while it’s cooking in the oil. Oil heats up much hotter than the boiling point of water, so it can really dry out food from the outside in and turn it crispy. That’s what deep frying is all about — drying out the surface of the food, making it crispy. But with stir frying, you want to preserve the moisture in the food, and just give it a light coating of oil so it won’t stick to the wok. The oil helps transfer the heat into the food. But you don’t want it to dry out or it will burn. So you add a little sauce to help balance moisture and add flavor.

    It’s easy to use too much sauce, but there’s no strict guideline on how much to use. When I first started my wok experiments, I used too much, and my food didn’t taste like the food it was, all I could taste was the sauce. Now, I use less, and it flavors the food, the sauce doesn’t mask the food and become the entire flavor.

    You don’t want a deep pool of sauce at the bottom of the wok that the food is boiling in; you want a coating of sauce that you can stir the food in.

    Soy sauce is the most watery of the three, and adds moisture to the food while also adding a salty flavor. Teriyaki is sweet. Hoisin is sweet and a little hot. I find these blend well together, but you can experiment with other types of sauces. Fish sauce, oyster sauce, sate sauce are all worth looking at, as well as others.

    If you want to you can also toss in other spices, such as ginger or dried chili peppers, or whatever else you want. These can add even more flavor. But resist the temptation to overwhelm the dish with these flavors. Strong spicy dishes can taste great, but if you dial back and let the spice accent the food flavor rather than smother it, it’s even better.

    I know lots of people like to flip and toss and catch their food with the wok, and this is considered the correct way to use a wok, but I don’t find this to be all that necessary. I just stir it with a large flat wooden spoon/spatula thing, and it works just as well, without risk of spilling the food or straining my wrist. If you enjoy flipping the food around and being a showboat, knock yourself out.

    The purpose of stirring the food is to move it around so it doesn’t stick to the wok and burn. Also it helps even out hot spots so that everything cooks to an even temperature. Also it helps distribute the spice and sauce over the whole surface of each piece of food. Also, it helps the food flip over so it gets cooked from all sides, not just the side it happened to land on when it landed in the wok. And if you’re cooking more than one ingredient together, it helps them to mix. That’s it.

    As each ingredient is cooking, I sample a piece every now and then to see how it’s doing. Once it’s done how I like it, I transfer the food from the wok to the pot, and put the lid on it to keep things warm. I work my way through the ingredients, doing the thicker, sturdier foods first, and cooking them longer, and the lighter ingredients last, cooking them briefer. Items like baby corn, water chestnuts, onions, mushrooms, and tofu all take longer, several minutes, and can be done together if they fit in the wok, or done separately if there’s too much. Broccoli takes long too, unless it’s already steamed, in which case it just needs a short bath in the sauces. Items like spinach leaves, green onion, and baby bean sprouts don’t take long at all, and can be thrown in toward the end for a minute or less.

    Tofu is done when it starts to brown on the edges and develop a bit of a skin. If it’s not pressed enough to remove enough moisture, it may not brown on the edges or develop a skin, so keep that in mind. If you want to, you can do meat instead of tofu: chicken, beef, shrimp, or something else. Be sure to cook chicken thoroughly.

    Once all the ingredients are done, mix them up in the pot and then scoop it out and serve with the rice.