Kid's Programming Language: Missile Command!

  This article is the last of a series in which we are recreating classic video games in KPL code—in this article, Missile Command!

Difficulty: Easy
Time Required: 1-3 hours
Cost: Free
Software: Kid's Programming Language
Hardware:

Kid's Programming Language (KPL) is carefully designed to make it easy for beginners to learn to program, but all the things that make KPL easy for beginners also make it easy for anyone who wants to code fun stuff fast. Missile Command is our loudest, most colorful, and most complex retro KPL example — and if you really need proof that KPL is a lot more than just a beginner language, playing this game will do it. Missile Command was coded in KPL by Larry Serflaten, a KPL MVP who has contributed a dozen open source games or programs to the community.

Here are some fun historical links about Missile Command, which Atari released in 1980:

http://en.wikipedia.org/wiki/Missile_Command

http://www.klov.com/game_detail.php?letter=M&game_id=8715

http://www.atariprotos.com/2600/software/missilecommand/missilecommand.htm

The Wikipedia link describes how a legendary high-score run of over six hours ended when the three players got bored and went to the pub! It also mentions that the chip running the 1980 game had a clock speed of 1 MHz — some of us are running chips with a clock speed over 3000 times as fast now! That's 12 doubles of clock speed in 26 years, which ironically is a little slow in Moore's Law terms.

Just the Interesting Stuff, Please

This article will assume some programming skill, and rather than explaining every line of code in the KPL program, will focus on key techniques or algorithms which will be useful as you develop your own games:

  • User-defined structures
  • Mouse handling
  • Proximity functions
  • Implementing smart AI
  • Cool end-of-round experience

I will also not repeat topics covered in previous Coding4Fun articles, so if this is the first you have read about KPL and you'd like to catch up on them, they were published in this order:

Kid's Programming Language Kid's Programming Language: Pong!Kid's Programming Language: Christmas Tree ShooterKid's Programming Language: Asteroids!

This Missile Command article will make the most sense if you have installed KPL version 1.1, which includes MissileCommand.KPL in the Games folder. The ideal way to start, of course, is to load MissileCommand.kpl in the KPL IDE, click "Run the program," and play it a few times before examining the code. Prepare to be impressed!

KPL 1.1 is available as a freeware download from http://www.k-p-l.org/download.htm. KPL does have a dependency on the .NET Framework 1.1, and the KPL install will automagically download and install the Framework if it is not already installed on your machine. If you already have KPL installed and Missile Command is not in your games folder, this indicates you don't have the latest version of KPL. For Missile Command and other reasons, it's good to upgrade to 1.1.

User-Defined Structures

MissileCommand.kpl uses user-defined structures in a couple of ways. We'll use the http://en.wikipedia.org/wiki/OnomatopoeiaKaboom structure as our example:

Structure Kaboom
Size As Decimal // Current blast size
Tick As Int // Start time
Busy As Bool // Still exploding
X As Int
Y As Int
End Structure

In the code below, from method DoExplosions(), xpl is an instance of the Kaboom structure, and used to represent and animate an explosion. This code, processed in a polling loop as it is, causes the explosion to expand outward, and then collapse in size as it fades. As you can see, there's nothing unusual about referencing a structure or the fields within it:

MoveTo(xpl.X, xpl.Y)
// Calculate size based on time from detonation
xpl.Size = Sin((Clock - xpl.tick) / 400.0) * kBlastSize

// Draw sky (to erase explosion)
If Clock - xpl.Tick > 550 Then
siz = Sin((Clock - xpl.Tick - 120) / 400.0) * kBlastSize
Color(ColorScheme[kSky])
Circle(siz, True)
xpl.tick = xpl.Tick + 25
End If

// Draw explosion
If Clock - xpl.Tick < 1200 Then
Color(BlastColors[Flash])
Circle(xpl.Size , True)
End If

// Stop
If Clock - xpl.Tick > 1300 Then
xpl.Busy = False
End If

 

Mouse Handling

KPL 1 does support a simple event model for mouse handling:

// MOUSE INPUT
Method MouseInput(Event As String, X As Int, Y As Int, Button As Int )
If Event = "ButtonDown" Then
If Y > kOriginY And Y < (kTargetY - (kBlastSize / 2)) Then
ShotX = X
ShotY = Y
Else
AddSound(8, 200, "PowerDown.wav")
End If
End If
End Method

Method MouseLock(Event As String, X As Int, Y As Int, Button As Int )
// Used to refuse mouse input during scoring phase
End Method

In Method PlayLevel(), these mouse event handlers are alternately set to enable and disable processing of the mouse clicks:

SetMouseEvent("MouseInput")

. . .



// Mouse input locked out for score tally
SetMouseEvent("MouseLock")

Proximity Functions

Proximity testing is an important enough technique that we thought we'd make an example of it here, particularly since this demonstrates a few other points worth noting in KPL:

Function NearExplosion(X As Int, Y As Int) As Bool
Var rsl As Bool
Var idx As Int
Var len As Int = ArrayLength(Blast)
Var dx As Decimal
Var dy As Decimal
Var hyp As Decimal

// Returns True if X, Y is near any explosion
For idx = 1 To len
If Blast[idx].Busy Then
dx = Blast[idx].X - X
dy = Blast[idx].Y - Y
hyp = Sqrt(dx * dx + dy * dy) * 2
rsl = rsl Or (hyp < Blast[idx].size)
End If
Next
Return rsl
End Function

First and most obvious is that proper functions are supported and useful in KPL in the usual way, as a method that returns a value. In this case we return a simple Boolean value of whether or not the (X,Y) location is within the blast radius of any of the explosions that are currently happening on the screen.

Note the use of ArrayLength(), itself a KPL system function which returns an integer value indicating the length of the specified array.

The line below does a classic hypotenuse calculation to get the distance between the (X,Y) point and each explosion. Remember a2 + b2 = c2?

hyp = Sqrt(dx * dx + dy * dy) * 2

Sqrt() is another KPL system function, of course, for calculating the square root of a specified value. The * 2 added to the equation allows the hypotenuse distance calculation (a radius measurement) to be compared correctly against the blast size (a diameter measurement). We could have used (hyp < (Blast[idx].size/2)) on the second line instead.

Implementing Smart AI

Here's a cool quote (and cool challenge!) from the original Missile Command developers:

"These little diamond-shape guys can evade your explosions. The only way you can kill them is if the explosion starts out right on top of them. Programming that was the hardest part. They had to be intelligent because the little guy had to look around on the screen to see what he had to avoid and he had to figure out the best path to go around what there was to avoid. Of course, if I made it too smart, then the player couldn't kill it and they'd be guaranteed instant death. So it had to be a fine line between smarter than the dumb missiles, yet not totally unkillable."

John McCarthy coined the term "artificial intelligence" at a 1956 conference, the first ever devoted to the topic of machine intelligence. It's obviously a stretch to have one term span from our KPL code for incoming drones all the way to Deep Blue, the IBM computer that beat a chess grand master. We'll stick to our guns, though, while acknowledging that some games require a lot more artificial intelligence than others!

Working on game AI is, for many programmers, one of the most fun challenges in game development: figuring out an algorithm that can reliably crush a human opponent. Muah ha ha! As the quote points out, though, it's also a critically important game design point not to overdo it if you actually want people to play your game! Proof of the point is how Gary Kasparov avoided a rematch after Deep Blue kicked his butt! Moral of the story: your game AI will require testing and adjustment — and you'll always want to aim for a playable point that's hard enough but not too hard.

The AI in Missile Command directs the "drones," the small triangles which will intelligently pick and move toward the nearest live target, avoiding nuclear blasts that are in their path. The methods PickNearestTarget() and MoveAwayFromBlast() are unsubtle mathematical algorithms with descriptive names (good programming practice, that), so the one we will instead examine is MoveTowardTarget():

Method MoveTowardTarget(Drone As Ordinance)
Var trg As Int
Var mov As Decimal[3] = {-1.0 * kDroneSpeed * Speed, 0.0, 1.0 *
kDroneSpeed * Speed}

trg = kTargetsX[Drone.Index]
Drone.DY = Speed * kDroneSpeed

// Screen boundries
If (Drone.PX < 3) Then
Drone.DX = mov[3]
End If
If (Drone.PX > 697) Then
Drone.DX = mov[1]
End If

// Very occasional directional change
If Random(1, 50) = 21 Then
Drone.DX = mov[Random(1, 3)]
End If

// Occasional direction change toward target
If (Random(1, 10) = 7) Or (Drone.PY > 380) Then
If Drone.PX > (trg + 5) Then
Drone.DX = mov[1]
End If
If Drone.PX < (trg - 5) Then
Drone.DX = mov[3]
End If
End If
End Method

Note how the unpredictability in the algorithm is based on x-axis movement, which can be a little to the left, no movement along that axis, or a little to the right. When the Drone has proceeded far enough down the screen to get close to its target — (Drone.PY > 380) — it will begin to move more directly toward the target. Until then, based on two uses of Random(), the movement of the drone will have a considerable random element. Experimenting with those uses of Random() specifically, and more broadly with this algorithm, will give you a sense of the playability impact of very subtle algorithmic changes. Keep that original quote in mind from the Missile Command developers 26 years ago: "It had to be a fine line between smarter than the dumb missiles, yet not totally unkillable."

End-of-round Scoring

Missile Command was actually the first arcade game ever to use an end-of-round pause for scoring. If you have good speakers on your computer when you play it, you'll see that the KPL version does a loud and impactful recreation of the original end-of-round. Turn it up! Big Smile

Consider the usefulness of the short break provided by an end-of-round experience. You give the player a short breather and you extend the length of time they play the game, while you keep their attention and interest. Heck, if you do that with as much impact and volume as Missile Command does, you are giving them a minor adrenalin jolt even while they're getting a break. End-of-round scoring isn't appropriate for all games, but it is for many.

Below is the TallyScore() method in MissileCommand.kpl. It's ideal to print this out and consider the code alongside the screen as the end-of-round is being processed. SetFont() is worth noting, as is AddSound(), which is a user-defined method we will also show below:

Function TallyScore() As Bool
Var s As Int
Var x As Int
Var more As Int
Var scr As Int

SetFont("Arial", 18, True, False, False)
Pen(False)

// Tally missiles
x = 200
For s = 1 To 3
While Silo[s] > 0
Silo[s] = Silo[s] - 1
Score = Score + (Bonus * 5)
scr = scr + (Bonus * 5)
DrawSilo(s)
DrawMissile(x, 160, True)
DrawScore(True)
ScreenPrint(150, 150, scr)
AddSound(10, 0, "mortar1.wav")
RefreshScreen()
Delay(80)
x = x + 12
End While
Next

// Tally cities
x = 215
LiveTargets[5] = False
scr = 0
For s = 2 To 8
If LiveTargets[s] Then
more = more + 1
scr = scr + (100 * Bonus)
Score = Score + (100 * Bonus)
EraseCity(s)
DrawCity(x, 205)
DrawScore(True)
ScreenPrint(150, 200, scr)
AddSound(10, 0, "mortar2.wav")
RefreshScreen()
Delay(300)
x = x + 60
End If
Next

// Add bonus cities
scr = 0
LiveTargets[5] = True
While (City <= Score) And (more < 6)

x = Random(2, 8)
While LiveTargets[x] = True
x = Random(2, 8)
End While
scr = scr + 1
more = more + 1
City = City + kCityBonusTrigger
LiveTargets[x] = True
EraseCity(x)
DrawCity(kTargetsX[x], 436)
End While

// Print ADDED message
If scr > 0 Then
AddSound(10, 1, "MessageBeep.wav")
Color(ColorScheme[kMissile])
If scr = 1 Then
MoveTo(265, 300)
Print(scr + " CITY ADDED")
Else
MoveTo(250, 300)
Print(scr + " CITIES ADDED")
End If
End If

// Let player see it a while
Delay(2000)

Return (more = 0)
End Function

AddSound() is a method worth noting because it is easily reusable for other games. It allows sounds to be prioritized as they are played, and it allows sounds to be played for a specific duration before they are replaced with another sound:

Method AddSound(Layer As Int, Duration As Int, File As String)
// Plays sound based on priority, and lets them last for some duration
If (Layer >= Priority) Or (Clock > Hold) Then
PlaySound(File)
Priority = Layer
If Duration > 0 Then
Hold = Clock + Duration
Else
Hold = -1
End If
End If
End Method

What's next?

You know by now I like teasers, right? How does 35 KPL-simple instructions sound, to fly a 3D spaceship model through an interstellar skyscape? KPL version 2, which is now in beta, lets you do exactly that, and a whole lot more. Check it out:

And the code:

SwitchTo3D()

Define cam As Camera
Define frameTime As Decimal = 0.0

Define sky As Skybox3D
sky.LoadMesh( "cell.x" )
sky.Scale( 5, 5, 5 )

Define ship As Model3D
ship.LoadMesh( "Fighter.x" )
ship.MoveTo( 10, 10, 10 )

While Not IsKeyDown( Escape )

Define startTime As Decimal = TickCount()
Define moveAmount As Decimal = 5 * frameTime

If IsKeyDown( Left ) Then
ship.TurnLeft( moveAmount )
End If

If IsKeyDown( Right ) Then
ship.TurnRight( moveAmount )
End If

If IsKeyDown( Up ) Then
Ship.TiltUP( moveAmount )
End If

If IsKeyDown( Down ) Then
Ship.TiltDown( moveAmount )
End If

Ship.Forward( MoveAmount * 5 )

cam.PointAtModel( Ship )

RenderFrame()

frameTime = Math.Min( 0.01, (TickCount()-startTime)*0.001 )

End While

How's that for Coding4Fun? Big Smile If you would like to know about our future plans for KPL, send e-mail to mailto:%20jon@kidsprogramminglanguage.com. Thanks to Coding4Fun for this chance to publish a fun series! And thanks to all of you for reading, blogging, and spreading the word!

Follow the Discussion

Comments Closed

Comments have been closed since this content was published more than 30 days ago, but if you'd like to continue the conversation, please create a new thread in our Forums,
or Contact Us and let us know.