Contents
Will ISDN Steal CD-ROM’s Thunder?
Chris Crawford
Programming M. C. Kids Part II
Gregg Iz-Tavares and Dan Chang
Representing Stories
Jorn Barger
PC Joystick Calibration
Scott Messier
A Medley of Business Issues
Chris Crawford
Editor Chris Crawford
Subscriptions The Journal of Computer Game Design is published six times a year. To subscribe to The Journal, send a check or money order for $36 to:
The Journal of Computer Game Design
5251 Sierra Road
San Jose, CA 95132
Submissions Material for this Journal is solicited from the readership. Articles should address artistic or technical aspects of computer game design at a level suitable for professionals in the industry. Reviews of games are not published by this Journal. All articles must be submitted electronically, either on Macintosh disk , through MCI Mail (my username is CCRAWFORD), through the JCGD BBS, or via direct modem. No payments are made for articles. Authors are hereby notified that their submissions may be reprinted in Computer Gaming World.
Back Issues Back issues of the Journal are available. Volume 1 may be purchased only in its entirety; the price is $30. Individual numbers from Volume 2 cost $5 apiece.
Copyright The contents of this Journal are copyright © Chris Crawford 1992.
_________________________________________________________________________________________________________
Will ISDN Steal CD-ROM’s Thunder?
Chris Crawford
It now appears that CD-ROM is finally taking off. You can go into a mass market software outlet and find a section devoted to CD-ROM titles, and there is actually a selection of titles from which to choose. Most of the stuff available is still weak, but we’re starting to see new titles with some regularity. Moreover, shipments of new CD-ROM titles are building up some impressive numbers. It looks as if CD-ROM might actually be taking off at long last.
But looming on the horizon is another technology: ISDN (Integrated Services Digital Network). ISDN is a protocol for shooting digital information through optical fibers. The telephone network is now almost completely digital. All the major links between local stations, all the satellite links, all the microwave links, everything transmits digital information. The only part of the system that is still analog is the twisted-pair of copper wires running from the local telephone switching station to your home. It’s called “the last mile”, and it’s the only part of the phone system that’s still analog.
The basic idea behind ISDN is simple: make that last mile digital by installing optical cable into each home, and then let people tap directly into the long-distance digital lines without recourse to modems, either in their homes or at the switching station. Of course, installing all that optical cable is a huge job that will take years, but the phone companies have committed to making the leap. Within ten years, many of the country’s copper wires will have been replaced with optical cable.
When this happens, ISDN will become a serious possibility. What strikes me as significant is the possibility that ISDN might supplant CD-ROM as the primary delivery medium for entertainment software. Why? Because ISDN offers everything that CD-ROM has, and more.
Let’s walk through the basic parameters. Consider capacity. Sure, CD-ROM has lots of capacity. But a server computer can offer vastly more capacity than a CD-ROM. ISDN’s capacity is as much greater than CD-ROM’s than CD-ROM’s is greater than floppy disks.
Or consider data transfer rates. CD-ROM boasts a sustained data transfer rate of 150 Kbytes/second. Narrow-band ISDN comes in at 18 KBytes/second — but the smart money is now betting that narrow-band ISDN will be dropped in favor of wide-band ISDN, with transfer rates in excess of 150 KBytes/second.
Access times are trickier to compare. Slow CD-ROM drives have average access times of 700 ms, while the best (and most expensive) drives have gotten that down to 300 ms. Access times on a network are more difficult to estimate. A well-endowed network with lots of excess capacity can get its access times down to 10 or 20 ms, but most economically operated networks run more slowly than this. Access times can easily run into several seconds.
Next we turn to cost considerations. The first such consideration is the cost of the hardware that gives the user access to the medium. In the case of CD-ROM, that hardware is the CD-ROM drive. It started off at $1000 five years ago, and despite predictions of steep price plunges, the price drifted downwards slowly. Current prices of CD-ROM drives range from a low of $200 for the slowest drives to $900 for the fastest and best units. Thus, it’s difficult to say what the average price of a drive might be. From my own examination of all the drives out there, my guess would put the figure at $400, falling to $300 by 1994.
The cost of an ISDN interface box is harder to estimate, because there’s nothing on the consumer market. Note, however, the close similarity between this box and a CD-ROM drive’s electrical components. Both convert fast laser signals into electrical signals. Thus, the ISDN interface box could be thought of as the electronic side of the CD-ROM drive, without any of the mechanical components. It’s gotta be cheaper. And surely the biggest lesson of microcomputer pricing is that purely electronic systems enjoy steep economies of scale, where mechanical components do not.
The cost of delivering the information is the real kicker that puts ISDN ahead of CD-ROM. The problem with CD-ROM is that it puts information onto a physical object and then sells the object to transfer the information. That requires the use of a physical distribution system. The overhead costs of the retail distribution system are staggering. Consider the case of a product for which a customer pays a price of $50. Where does his $50 go? About $25 goes directly to the retailer, and $5 goes to the distributor. Another $10 goes to the costs associated with manufacturing and shipping the product, including sales costs, management costs, inventory, warehousing, and so forth. About $3 will be allocated to marketing costs. That leaves $7 to cover all the costs of development, publishing house overhead, and such. Here we have a system that is truly screwed up. It costs $7 to create the product, $3 to market it, and $40 to actually get it into the customer’s hands.
This is the beauty of ISDN: the distribution cost is lower. Right now, the cost to distribute information on a 2400 baud network is about 10**-4 cents/bit. CD-ROM’s actual cost to deliver information (when you add in the distribution costs) is about a hundred times lower than that: 10**-6 cents/bit. ISDN, however, will be even cheaper. Current cost projections are hard to place much confidence in, but we can bet that a system that is 400 times faster will likely be a lot cheaper. Indeed, for the last forty years, the cost of telecommunications has fallen with each year, and there are no indications that the price fall is anywhere near slowing down.
Moreover, ISDN offers a huge advantage that CD-ROM can never touch: it can tailor the information transmission — and the charges — to the precise needs of the user. With CD-ROM, the customer has to buy the whole kit ‘n kaboodle. But with ISDN, the customer can download an initializing chunk of information (say, the graphic front end) and then grab only those bits and pieces he needs to play the game. If he plays a lot, he pays a lot; if he chooses to play little, it doesn’t cost much. With the costs of games approaching the $100 mark, the ability to sample the game without committing such a big chunk of money becomes all the more valuable to consumers.
There’s another big advantage to ISDN: it is absolutely pirate-proof. CD-ROM offers only a temporary respite from piracy; writeable optical media are already available, and it will be only a matter of time before they are within the reach of too many people to preserve the security of CD-ROM products. By contrast, a game on an ISDN system can enjoy total security merely by requiring occasional access to the host computer.
On almost every count, then, ISDN is clearly superior to CD-ROM. There is only one factor that gives CD-ROM an advantage: CD-ROM is here now, while ISDN is still on the horizon. But CD-ROM’s arrival has been tentative. It “burst” upon the scene with all force of a tidal wave of molasses. This “revolutionary” technology has crept in on little cat feet, slowly insinuating itself into the marketplace in an oh so discreet manner. Those of us who have waited for years for the glorious predictions to come to pass have begun to wonder if or when they’ll ever be realized. For a long time I’ve been claiming that CD-ROM won’t surpass floppy disk entertainment until 1995. When I first made that prediction I was laughed at. A few years later people just argued with me; now there are actually a few souls who agree with me (Hmm, this is disturbing; I’d better come up with some new heresy.)
If indeed CD-ROM fails to accelerate, there is a very real possibility that a steeply climbing ISDN curve could surpass the CD-ROM curve before it is fully established. In other words, ISDN could steal the show from CD-ROM before it gets its chance to blast off. Even more serious is the possibility that the anticipation of ISDN could have this effect. After all, how many people have been working on CD-ROM all these years in anticipation of its heyday? Could not the same effect apply to ISDN?
All this hinges on how quickly optical fibers are installed and ISDN services are made available. Predictions of the installation times are variable. The most optimistic projections suggest that we’ll have a viable little system by 1995; the most pessimistic ones put this off until well after the year 2000. If the ISDN pessimists are right, then CD-ROM will have at least five years of unchallenged supremacy. On the other hand, if the ISDN optimists are right, then the window of opportunity for CD-ROM shrinks to almost nothing.
_________________________________________________________________________________________________________
Programming M. C. Kids Part II
Gregg Iz-Tavares and Dan Chang
[Part One of this essay appeared in Volume 5 #6.]
Objects
Our system allows for N objects to be active at any one time; for M.C. Kids, N is 16. This is a large number, as the program begins to bog down with more than about seven objects. We discovered this limit during development, and we re-tuned most of the levels so the player can’t lead more than six monsters together. Still, to deal with some unforeseen situation, the program will handle 16 objects. If you have a third party NES controller with a turbo button, stand in front of a snow pile and press the button. You should be able to get 10 or more snowballs in the air at once. Games like Mario 2 and Mario 3 are carefully designed so that the program almost never introduces more than five objects at once.
Objects are introduced into the object system through various means. The most common are objects that appear as the player moves through the level. For each level in the game there is an object introduction table. This table describes where various objects appear in the level such as gophers, fish, crabs and moving platforms.
Each entry in the table has 3 data fields: X position, Y position and object type. The object table is sorted in ascending X position order. The object introduction routines keep an index for the current position in the table.
When the player moves to the right, the current screen position is compared with the current object in the table. If the object is inside the screen it is introduced to the active list of objects and the index is incremented. If the object is not inside the screen then all objects further along in the table are also not inside the screen since they are farther to the right.
The routines also handle object introduction when scrolling left, up and down in a similar manner. For moving up and down there is a table of indices sorted in ascending Y position.
There is also an active flag table in RAM with one flag for each entry in the object introduction table. When an object is introduced from the object introduction table its corresponding active flag is set. When the object introduction routines are about to introduce a new object they first check its active flag to see if the object has already been introduced and if it is they don’t introduce the object again.
Objects may be added for other reasons. For example, when the kid steps on a SpringBlock tile, a SpringBlock object is added to animate the SpringBlock. When the kid picks up a 1Up, a 1Up object is added which spirals and flashes a 1Up shape. When the boat hits water a splash object is added. When a leaf generator object is present it adds falling leaf objects at regular intervals.
For every object there are flags (bits) for the following:
OB_HASBIT: Is the object from the object introduction table? If so, then the object has an active flag. The active flag indicates whether the object is currently active on the screen. The active flag is used so that the object will not get introduced more than once.
OB_EARLY: Can the object be removed prematurely? Trying to add a fifth object with this flag set will remove the oldest object with this flag set from the active list. (E.g., if the player runs quickly across several falling bridge tiles, he will slow the game down by introducing too many animations. However, by setting this flag for the falling bridge objects, the total number of active falling bridge objects is limited to four.)
OB_XPHYS: Does the object use X Physics?
OB_YPHYS: Does the object use Y Physics?
OB_XCHK: Should the object check left/right tile collisions?
OB_YCHK: Should the object check up/down tile collisions?
OB_ONCE: Should the object never re-appear? Each object in the map has an active flag which is set when the object is introduced. This prevents the object from getting introduced multiple times. The active flag is cleared when the object is removed (such as when the object scrolls off the screen) so that it can be introduced again when the player ventures back to that part of the level. If OB_ONCE is set then the object’s active flag will never be cleared and therefore the object will never be introduced again.
OB_REMOVE: Should the object be removed when it is four tiles off screen? If this bit is not set, then it is the object’s responsibility to remove itself.
For each object in the list the main object loop will do the following things.
1) Call the specific object’s routine (e.g., call MCKidDoit for the Kid, call GopherDoit for the Gopher, and call CrabDoit for the Hermit Crabs).
These routines do object-specific things. (E.g., the beaver routine looks for the Kid, and if he is below the kid, he will stand and look back and forth; if he is above the kid, he will run toward the kid. A platform routine follows its track, The kid’s routine reads the joystick and acts accordingly. )
2) If the object has X Physics turned on, then the X Physics are computed using Acceleration, Velocity and Position. Object positions in the game are 16 bits significant and have a resolution of 1/4 pixel. This is our ’decimal place’ and allows the objects to move smoothly at various speeds. This provides that Mario “feel”. Some games force all monsters and the player to move at some integer multiple of pixels per vblank, and thus lose some of that fluid “feel”.
There are two types of X movement routines. One routine is used when the object is in the air, and the other is used when the object is on the ground. The routine for movement in the air just uses normal physics. For movement on the ground, we need to make the object follow the contour of the ground, so for each pixel moved left or right, the Y position is adjusted to the correct height.
3) Object to Object collisions are determined. The current object is compared with all other objects it needs to be compared to, and if there is a collision, the object’s collision routine is called. (E.g., for a Gopher, the routine GopherClid is called. GopherClid turns the Gopher around. For the Fill-In-Block, it checks to see if the object collided with the Kid; and if so, it sets the correct variables to allow the kid to pick it up [if the Kid chooses to do so]). It also checks if it collided with a monster, in which case the monster gets hurt.
Every object can only collide with one other object per frame, so every object is assigned a collision priority so that it will collide with the most important object. It is more important to know that the kid hit a monster than to know that the kid hit a PickupBlock.
All objects have a collision matrix, so that they can only collide with certain other objects. The kid collides with everything. A PickupBlock only collides with the kid and monsters. Some monsters collide with the kid, other monsters, and various project tiles. Certain other monsters don’t collide with other monsters. Not having to check every possible collision saves tons of time.
4) If the object has horizontal tile collisions turned on, then left or right tile collisions are checked depending on whether the object it moving left or right.
To do the collision, either the left most or right most pixel of the current object is sent to the left or right tile collision routines. The tile type of the tile in the map that contains this pixel is looked up. A tile routine exists for each particular tile type; it decides whether a collision has taken place (or what other action should be taken, like collecting an Arch or crossing the ending line). If a collision occurs, then the specific directional collision routine for the particular object is called. (E.g., if we’re a gopher and we collided left, then GopherLeft is called which makes the gopher turn around and go right; if we were going right then GopherRight is called, which makes the gopher turn around and go left.)
5) If the object has Y physics turned on then Y physics are computed.
6) If the object has vertical tile collisions turned on then Y tile collisions are done, similar to the X tile collisions, but only if the object is in the air. If the object is on the ground, then ’Ground’ Collisions are done.
This means that every tile type can have up to five routines (TileLeft, TileRight, TileUp, TileDown, TileGround). Most tiles point to the same routines, either SolidLeft, SolidRight, SolidUp, SolidDown, and SolidGround, or SkyLeft, SkyRight, SkyUp, SkyDown, and SkyGround. Every tile also has two contour tables, each 16 bytes in size, one for walking to the left, the other for walking to the right. The tables tell the routines how to adjust the Y position for each pixel moved in the X direction.
Every object has eight routines: ObjectInit, ObjectDoit, ObjectClid, ObjectLeft, ObjectRight, ObjectUp, ObjectDown, and ObjectGround.
If I were to do this again I might get rid of many of the flags and just have each object call the routines it needs. Thus, instead of there being an X physics flag, I would just have any object that needs X physics call a routine to compute it.
Collisions
Now for the fun part. NOT! First, here are all the kid’s points that are checked for collisions against the tiles:
Point H is the Hot Spot and is considered the position of the kid. This spot will usually be inside the top of a tile:
This means the kid is standing in/on a normal solid tile. If he had been one pixel higher, he’d be ’in’ a sky tile.
Points 1 and 2 are used for checking when the kid should fall off a vertical ledge. If both points are not on something then the kid should fall.
Point A, B, C and D are used for checking left and right tile collisions. Only A and B or C and D are checked depending on the direction the kid is traveling. Points B and D are checked for every type of object, but points A and C are only checked for the kid.
Point T is used for checking collisions when the kid is going up, like when he bonks his head into a platform.
Most of these points are adjustable in some way. Points 1, A and B always have the same relative height but can be moved left or right together. The same is true for points 2, C and D. Point T can be moved up. Points H, 1 and 2 can be moved down together.
Terrain
One of the hardest things to program is the object-terrain interaction. Here is an example slope:
For easy reference, the tiles are numbered 0 through 9, as well as being assigned a letter representing its type, where N = Normal Tile, H = Hill Tile and S = Sky Tile.
I use offset tables for each tile that states for each horizontal pixel, how to get to the next pixel, left or right in the Y direction. Thus, if you are on pixel eight on a left rising hill slope, and you move left to pixel seven, the table might say move up one pixel. This may not be the best way, because it means that all movements are relative instead of absolute, so if there is an error somewhere it could get exaggerated. By using a table, we can have irregular shaped slopes.
Some of the problems we encountered in implementing the terrain logic:
1) When walking left on tile 9 the kid will eventually move into tile 8 (where he doesn’t belong). To solve this I have to ’look-up’ one tile to see if the kid should start walking up a hill. One possible solution is to make another tile type B for ’Beginning of hill’ and placed it at tile 9. Then, when the kid walks into a type B tile we would know what to do. This could be a problem for the artist, though, because if he or she put a type N where there should be a type B, the kid would walk into hills.
2) When walking left on tile 3 the kid will eventually walk into tile 0 (the sky). This bug remains in M.C. Kids (check it out). It could be fixed similarly to problem 1 by making a new tile type E for ’End of Hill’ that would be placed at tile 0. Then when the kid walks into a type E tile, we would know what to do.
3) Often there are type N tiles up in the sky as platforms. Usually the kid is allowed to walk right until the kid’s point 1 is ’off the tile’, and then the kid will fall. This is the way we want it. However, what if the next tile is a Hill tile (type H)? When the kid walks down the hill, the kid’s hot spot (point H) should always be on the ’slope’. However, since we don’t allow the kid to fall until his point 1 is off the edge of tile 2, he won’t walk down the slope. To solve this problem we created a new tile type, type A, for Adjacent to hill, to place at tile 2. This tile type is placed next to Hill tiles, and causes the kid to fall when point H leaves the tile, instead of when point 1 leaves the tile.
Mario 3 solves this problem differently. It uses type N tiles for platforms and other solid tiles. They act like our type N tiles, in that the kid won’t fall until his point 1 is off the tile. However, if the level contains slopes, than all type N tiles change their behavior to something similar to our type A tiles. Thus, in Mario 3, you cannot jump up and down with only your point 1 on the tile in any level that has slopes.
Many of the reasons for these contortions have to do with speed. Most video game systems must run at 60 frames a second, and if you have to look up 4 or 5 tile positions per collision pixel per frame you are going to run out of time. All this is something you should consider when designing a platform game. It is much easier the create a platform game with no slopes, so if you are short on time, then design your game so that it doesn’t need slopes.
Altogether, we have around 100 tile types, such as Solid, Left Slope, Right Slope, JumpThru, Death, Spring Board, Slippery Solid, Slippery Left Slope, Slippery Right Slope, Go behind Left Slope, Go behind Solid, Ending Line, Falling Bridge, and Guide Tiles (for moving platforms).
For each tile type, we also have information like: Maximum speed (slower going up hill), Extra Acceleration (used for speeding down hill and or slowing up hill), Friction (used for slipperiness), and Turn around speed (how quickly the kid can reverse direction). If we wanted to do Sonic the Hedgehog, we could have also added animation information (for what direction Sonic should be facing), and slope angle information (so that when the player presses jump, he jumps at some angle relative to the slope).
If I were to do this again, I would probably add additional tile types for the above problems. I might also change the offset tables to be absolute instead of relative depending on the target processor. Parallel tables were used extensively for most of the data in the game mostly because a 6502 can more easily deal with parallel data than it can with records or structures. With other processors, like the 68000, it would be far more efficient to use structures or records.
M.C. Kids Data Statistics
Here’s a look at how many items we had in each category, how many bytes the items required in compressed form, how many bytes the items expanded to, and how much was saved by compressing the data:
PackedUnpackedSavingsLevels56+5887617794467%World Maps202947485639%Houses7878186753%Text Msgs64+4211783647%Pictures91683359054%Tilesets29+5691780127%SpriteFonts1734816TextFonts33072CardFonts44096TilesetsFonts4445056PictureFonts9-1016384ShapeTables5778098Object+Tile Tables5854Music+Driver12372Monster Routines13030Kid Routines4089Kid Map Routines688Tile Routines1000+
Missing: scrolling, animation, sprite, vblank, setup, game flow, unpacking and other routines.
Shameless Plug
Well, there you have it. Obviously, if you need a platform game, you should come to us. Our engine can handle nearly any kind of platform game currently designed, and we can tailor it quickly. Of course, now that you’ve read this article, you know how to do it too!
_________________________________________________________________________________________________________
Journal Articles Hit Home!
Despite its small circulation, this Journal does seem to hit sensitive spots on occasion. The most recent example of this is the brouhaha generated by my editorial (“Repent! The End is Near!”) in the April 1992 issue. Brian Walker over at Strategy Plus magazine judged it a thought-provoking piece, so he reprinted it in his magazine. That in turn touched off a firestorm on both CompuServe and GEnie. It seems that the games aficionados took offense to my suggestion that they might be contributing to the decline of the industry by pushing publishers towards ever more complex games that chase away beginning players. Boy, did they ever take offense! The modem lines burned with ferocious invective aimed at me. Nothing was accomplished, but they sure sounded off. Meanwhile, over at Computer Gaming World, Chris Lombardi, who’s had it in for me ever since I lambasted him privately some years ago, managed to insert an oblique reference to the editorial in an otherwise unrelated review of a game, calling me a ‘chicken little’.
All of which generates a smug sense of satisfaction that my role here as Industry Pyromaniac seems confirmed. If I hadn’t hit a soft spot, they wouldn’t have howled so loudly.
_________________________________________________________________________________________________________
Representing Stories
Jorn Barger
Abstract: To pack the maximum story content into the smallest memory, you need a concise vocabulary that can summarize the full range of human behavior. If person, place, thing, and motive are taken to be the simple elements of such a vocabulary, the primitive compounds should be the standard relationships that any two (or more) of these normally display.
While the ancestry of story representation includes the I Ching, Roget’s Thesaurus, and Polti’s 36 Dramatic Situations, the modern ambassador of story representation in the world of artificial intelligence is surely Roger Schank. During the 70s at Yale, Schank pioneered an approach to AI that involved building storybases in memory, and achieved what still stand as the only successful natural language translation systems, albeit within extremely limited domains.
Schank’s student James Meehan’s TaleSpin was an excellent prototype for interactive fiction, stumblingly building aesop-like fables. It was impossible to scale up, though, and was absurdly brittle, lacking the sort of common-sense background knowledge that Doug Lenat’s Cyc project is hoping to provide, through its slow, painful, memory-extravagant brute-force attack.
The prospect of a videogame appealing to a Cyc server for every little nuance of projected reality, is horrifying to contemplate: realtime is no time for inferencing! I want to propose a neat, Schankian end run, in the form of a database of standard stories that, by simple recombinations, give birth to a rich microworld.
The cornerstone of this system lies in that hoary old trio: person place and thing. But we add to that a little psychology: a typology of human motives: food, comfort, sex, respect, family, self-expression. And then we ask, for any two of these “initials” — person, place, thing, motive — what are their normal relationships?
Persons may go to places, or leave them, or traverse them, etc. Persons may make things, use things, acquire things. Persons may suffer motives, pursue satisfaction of them, satisfy them. And persons should have standard relationships to other persons, too. They may be kin, or sexual partners, they may communicate, or cooperate, or conflict with each other.
A story may involve one person weighing two motives, or one person achieving gratification of a motive by an exchange with another person whose motive may be different.
A story will be a sequence of relationships, changing in time. By exploring these relationships combinatorically, and the simple stories they generate... well, it might give a deeper weight to the concept of “virual reality’.
Bibliography: Schank, Lenat, Meehan
Table
person place thing motive skill
person communicates controls makes suffers learns
begets occupies uses enjoys uses
etc discovers
place imprisons beside contains
inside
far-from
thinghas-part gratifies enables
becomes
motiveexchanged-for
_________________________________________________________________________________________________________
PC Joystick Calibration
Scott Messier
The joystick is probably the most familiar input device known to users and programmers alike, and in some circles is considered obsolete in the face of the mouse, datapads, lightpens, etc. As a gaming tool, however, the joystick’s value has been demonstrated endlessly in jet and tank simulations and many other games with a hands-on graphical interface. In the face of the continuing need for familiarity with this overly-familiar device, there still exists the issue of calibrating the joystick for smooth integration into a game program. It is my opinion that this issue has received no satisfactory, up-to-date treatment, leaving programmers who may be new to game programming to search long and hard through stacks of old Byte magazines for solutions.
Having recently been asked to construct a library of joystick routines for an adventure game without any prior experience in these devices, I did what most good programmers would do and opened my DOS reference manual. In one minute, I had located everything DOS has for dealing with the joystick, namely interrupt 15h, function 8400h. There were two subfunctions for reading input from the joystick; one for buttons, one for position. Suddenly I realized why no one ever bothers to write about this stuff: it’s too easy. So I dutifully cranked out the simple assembly routines which call these DOS functions and wrote a program to test them, figuring that I had completed a good day’s work. Now, imagine my horror...
The nature of the joystick is imprecision. If you want to locate a precise point on the screen, buy a mouse or a light pen. If you want to pull back on a stick and throw your jet fighter into a barrel-roll, buy a joystick. Using a joystick is fun because it makes no demands upon how much pressure you exert on the handle, or on where the handle is positioned when it is at rest. I mean, if you want to break out of that barrel-roll and fly straight, then all you have to do is release the stick, which then rights itself. This is great for the user, most of whom are not reading this article.
The programmer, however, must ask the following questions: Where is the center of the joystick? If the user releases the stick, where does it wind up? Is this point the same for every joystick on the market, or do I need to keep a library of such information? Is the input from the joystick 100% reliable, or will it tend to move even when its handle is not being manipulated? If its tendency is to move randomly, how to I damp this motion so that I can tell the difference between a random move and an actual move.
You may be able to tell by the trend of these questions that the joystick is indeed not a well-behaved device like our friend, the mouse, but is instead the most ornery of devices. Add to this ill-behavior the fact that the joystick’s owner is usually given the ability to adjust the taughtness of the handle, which affects where the rest position is located. If we are to master this unruly beast, we must find the means to chain it and bend it to our uses. So, now that we know a problem exists, it is wise to go into the problematical issues in detail.
The nastiest problem encountered in joysticks are spikes in the readings. Even a new joystick may suffer from these data errors which result in position values which are up to a hundred times larger than any real position value. The problem of recognizing and ignoring such spikes is what led to the development of my calibration algorithm. A less serious problem, which nevertheless appears to be present in every joystick, is drift. Left unmolested, the reported position of the joystick will still vary by a point or two, oscillating back and forth as the metal contacts at the base of the handle shuttle electrons back and forth. There also seems to be no guarantee that the x,y boundaries of the handle will be consistent at 0 degrees, at 45 degrees, at 90 degrees, etc. I believe this to be another aspect of drift.
With knowledge about a specific device, it is possible to recognize spikes and drifts and eliminate them from what appears on the user’s display. The question is how to gather this information without troubling the user. The answer is, I believe, that it is impossible to calibrate a specific joystick without the aid of the user. This assertion has undoubtly astonished some of you, causing you to question my judgement. I, therefore, will explain my line of reasoning.
What we can assume about the position parameters of a given device would fit on the nail of your thumb. All that is known is that both the x and y coordinates must be positive integers. What the actual minimum and maximum values are is anyone’s guess; they may change from clock tick to clock tick. It is reasonable to assume, given my own experience, that the minimum values lie between 4 and 6 and the maximum values are less than 500. A spike, therefore is any negative x,y reading, or a reading which is less than 4 or greater than 500. So what if, while reading the value for x, we encounter a 3? This value is not so unreasonable that we should just throw it away, especially if it repeats. The same holds for a value of 501. But what if we should repeatedly encounter a value of 500 when the real maximum value is 33 (as is the case with an actual device which I have tested)? If we are duped into believing that this is not a spike, then our jet fighter may go into a fatal tailspin every five minutes.
There appears to be no perfect solution to this problem. What is required is a very good guess with which both the programmer and the user will be satisfied. As with every guess, the more data that can be accumulated, the better the guess will be. This is where the user comes in, and why the calibration routine described hereafter requires user input to function.
This first step in the algorithm is to find a rest position for the joystick. After giving the user a warning to leave his joystick in the rest position, we take ten sample readings for x and ten for y. Now, the odds that even one of these values is a spike are very slim indeed, although there’s a good chance that some drift is present. What we can assume, though, is that the true idle position is not only present in the table of ten x,y values, but that it appears more frequently than any other value. Therefore, we simply count the number of times a given value appears and take the one that appears most often. This, for our purposes, is the true rest position.
The second step is to make our guess at the minimum and maximum values for the joystick position. For minimum, we will be flexible and say that any value greater than zero is a good one. Spikes are almost always either very negative or very positive, so this is a pretty safe assumption. The maximum value is much harder to approximate. If we choose a value that’s too large, then the possibility of spikes sneaking in under our defenses increases. If we guess too small, then a dead zone is created wherein the user may push the stick with all his might and his jet fighter will still fly straight ahead, because we have assumed his moves to be all spikes. Through experiment, a good approximation seems to be to multiply the center values by four. Any reading which is less than this product is good and all other values are spikes.
You may be asking yourself: So why not just ask the user to move his joystick handle to all points of the device and determine the bounding rectangle that way? Why make any guesses about maximum and minimum values? Okay, let’s test this algorithm. The user’s input begins to describe a bounding rectangle whose x values lie in the range 4 to 100. For an instant, we get an x value of 1000. Since we’re taking this on faith alone, we accept 1000 as the new maximum x value and continue sampling. Eventually, the user clicks his button and ends calibration.
To make use of joystick readings, they must to be scaled according to the max, min, and mid values. Since our range lies between 4 and 1000, our mid-range value is 502. Any value which we read in the future which is less than 502 is therefore the user pushing the handle to the left of center. Any value greater than 502 is to the right. Now our jet fighter simulation begins. Guess what’s happening on the screen. Our fighter is going into an uncontrollable counter-clockwise roll, eventually crashing and burning despite the user frantically pushing the joystick to the right.
The solution to this problem was that we discard the value 1000 immediately because our predicted maximum is very much less than 1000. Instead of a scale which is ten times too large, our bounding rectangle is nearly perfect. This success leaves only one issue: How does the user interact with the calibration routine in a way which is consistant with the spirit of the calling program?
The answer to this last question is simple: the calling program calls the shots on calibration feedback by passing the address of a procedure which takes two integer arguments, x and y, and translates them into any graphic, text, or audio display desired. The values which are passed can’t have a complex value if they are to be used in any program, so they simply lie in the range {-1,0,1}. Of course, it’s a little tricky to produce values in this simplified range, especially when the max and min values aren’t even known. The solution used in the calibration algorithm is to take the current max and min values, divide the range into quadrants, let the two inner quadrants be zero and let the two outer quadrants be either -1 or 1. As the user expands the bounding rectangle, the values of -1 and 1 slide toward the edges of the new rectangle as the 0 zone expands.
You might wonder if another solution exists which depends on sampling a lot more values than just 10 for x and y. Of course it’s possible to write an algorithm which takes a thousand values, discards the top and bottom 20 percentile and reaches a reasonable conclusion, but there will never be any guarantees, no matter how sophisticated your algorithm becomes. The elegance of this algorithm is that it has very small time and space requirements, and the results are perfect for a joystick that is not suffering from spikes. In the unlikely event that this algorithm fails to calibrate correctly, the user may simply recalibrate and be guaranteed good results the second time through. The more entertaining the game maker makes his feedback procedure, the less the user will mind having to perform this task.
What follows is a listing of the source code used to develop this article. Any suggestions as to improvements in these algorithms will be much appreciated.
To build the test application (large model), JOYTEST.EXE, using Microsoft C v5.0+, enter the following commands:
CL /AL /G0 /W3 joyst.c /c
CL /AL /G0 /W3 joyst2.c /c
CL /AL /G0 /W3 joytest.c /c
LINK joytest+joyst+joyst2;
/***********************************************************************
* Scott Messier AUG 19 92 JOYST.C
* Joystick Routines implemented in Microsoft C v5.0+.
*
* Provides various access points to the standard DOS
* joystick interface. Uses DOS interrupt 15h subfunctions
* ax=8400h,dx=0 (read switches) and ax=8400h,dx=1
* (read position).
**********************************************************************/
#include <dos.h>
#include joyst.h /* joystick prototypes */
static int joystactive = 0; /* storage for joystick status */
int jinstall ()
/* Installs the joystick by calling the read position service
* and verifying that the returned joystick position is
* non-zero. Sets the static variable joystactive to a
* boolean result value.
*/
{ union REGS regs;
regs.x.ax = 0x8400; /* joystick support routines */
regs.x.dx = 1; /* read joystick position */
int86 (0x15, ®s, ®s);
joystactive = regs.x.ax != 0;
return joystactive;
}
int jready()
/* Report the status of the joystick as determined at installation time */
{ return joystactive; }
void jread (int *x, int *y, int *buttons)
/* Read the position and switch status of joystick A */
{ union REGS regs;
regs.x.ax = 0x8400;
regs.x.dx = 0; /* read joystick switches */
int86 (0x15, ®s, ®s);
*buttons = regs.h.al;
regs.x.ax = 0x8400;
regs.x.dx = 1; /* read joystick position */
int86 (0x15, ®s, ®s);
*x = regs.x.ax; /* Ax */
*y = regs.x.bx; /* Ay */
}
void jreadAB (int *Ax, int *Ay, int *Bx, int *By, int *buttons)
/* Read the position and switch status of joysticks A and B */
{ union REGS regs;
regs.x.ax = 0x8400;
regs.x.dx = 0; /* read joystick switches */
int86 (0x15, ®s, ®s);
*buttons = regs.h.al;
regs.x.ax = 0x8400;
regs.x.dx = 1; /* read joystick position */
int86 (0x15, ®s, ®s);
*Ax = regs.x.ax; *Ay = regs.x.bx;
*Bx = regs.x.cx; *By = regs.x.dx;
}
int Joyst_X ()
/* Report the x coordinate of Joystick A */
{ union REGS regs;
regs.x.ax = 0x8400;
regs.x.dx = 1; /* read joystick position */
int86 (0x15, ®s, ®s);
return regs.x.ax;
}
int Joyst_Y ()
/* Report the y coordinate of Joystick A */
{ union REGS regs;
regs.x.ax = 0x8400;
regs.x.dx = 1; /* read joystick position */
int86 (0x15, ®s, ®s);
return regs.x.bx;
}
int Joystbuttons ()
/* Report the status of the switches on Joystick A */
{ union REGS regs;
regs.x.ax = 0x8400;
regs.x.dx = 0; /* read joystick position */
int86 (0x15, ®s, ®s);
return (int)regs.h.al;
}
------------------------------------------------------------------------
/***********************************************************************
* Scott Messier AUG 19 92 JOYST2.C
* Joystick calibration implemented in Microsoft C v5.0+.
*
* Uses joystick routines found in JOYST.C to calibrate
* the joystick.
***********************************************************************/
#include joyst.h
void jcalibrate (int range[2][2], void (*showxy)(int,int))
/* Normalizes joystick input.
* Samples signals received while polling the standard
* DOS joystick routines, compiles a history of signals,
* finds a joystick rest point, and continues sampling until
* the user presses a joystick button. All remaining
*samples are used to define a bounding rectangle
* wherein the calling program can be assured
* (99% certain) that the values it reads from the joystick
* readings are normal. Requires the user to first put the
* joystick in the rest position prior to calling jcalibrate(),
* then to move the joystick to all points reachable on
* the device while the routine returns telemetry through
* an external data-processing routine passed as a
* parameter to jcalibrate(). This external routine may
* display the telemetry in text, graphic, or audio form as
* desired, depending on the application which makes use
* of the calibrated joystick.
* INPUT :
* showxy(x,y) <- user-defined display routine which
* accepts joystick calibration telemetry as x,y values in the
* range (x:-1,1), (y:-1,1). gives the user feedback while calibration is in progress.
* OUTPUT :
* range[][] <- bounding rectangle for joystick as
* {{left,top},{right,bottom}}
*/
{ int x, y, b, maxx, maxy, left, top, right, bottom, cx, cy, sx, sy, ex, ey,
best, bestcount, sample[10];
/* sample to find a centered x position */
for (x = 0; x < 10; x++) jread (&sample[x], &y, &b);
bestcount = 0;
for (x = 0; x < 9; x++)
{ b = 1;
for (y = x + 1; y < 10; y++) if (sample[x] == sample[y]) b++;
if (b > bestcount) { bestcount = b; best = x; }
}
cx = sample[best];
maxx = cx << 2; /* speculate a maximum joystick x value */
/* sample to find a centered y position */
for (x = 0; x < 10; x++) jread (&y, &sample[x], &b);
bestcount = 0;
for (x = 0; x < 9; x++)
{ b = 1;
for (y = x + 1; y < 10; y++) if (sample[x] == sample[y]) b++;
if (b > bestcount) { bestcount = b; best = x; }
}
cy = sample[best];
maxy = cy << 2; /* speculate a maximum joystick y value */
/* sample for bounding values, ignoring spiked readings */
CLEARJOYST /* wait for the joystick button(s) to be released */
left = right = cx;
top = bottom = cy;
do
{ jread (&x, &y, &b); /* get joystick position */
/* alter bounding rectangle based on most recent reading */
if (x < left && x > 0)
left = x;
else if (x > right && right < maxx)
right = x;
if (y < top && y > 0)
top = y;
else if (y > bottom && y < maxy)
bottom = y;
/* generate the inner rectangle which will be considered the dead area */
cx = (left + right) >> 1; /* find new center cx,cy */
cy = (top + bottom) >> 1;
sx = cx - ((cx - left) >> 1); /* find upper left-hand corner */
sy = cy - ((cy - top) >> 1);
ex = cx + ((right - cx) >> 1); /* find lower right-hand corner */
ey = cy + ((bottom - cy) >> 1);
/* generate telemetry as x,y values in the range {-1,0,1} */
x = x <= sx ? -1 : (x >= ex ? 1 : 0);
y = y <= sy ? -1 : (y >= ey ? 1 : 0);
(*showxy)(x,y); /* report x,y */
} while (b == 0x00F0); /* until joystick button is pressed */
/* return live rectangle */
range[0][0] = left; range[0][1] = top;
range[1][0] = right; range[1][1] = bottom;
CLEARJOYST /* wait for joystick button to be released */
}
------------------------------------------------------------------------
/***********************************************************************
* Scott Messier AUG 19 92 JOYTEST.C
* Tests joystick routines, implemented in Microsoft C v5.0+.
*
* Gives examples of usage for the jcalibrate() routine and
* other joystick routines found in JOYST.C
**********************************************************************/
#include <stdio.h>
#include joyst.h /* for joystick prototypes */
/* prototypes */
void printxy (int x, int y);
void main (void);
void printxy (int x, int y)
/* uses printf() to display calibration telemetry in scrolling text format */
{ printf (x = %d, y = %d \n, x, y); }
void main()
/* Demonstrates joystick calibration and use of calibrated device */
{ int x, y, b, cx, cy,
live[2][2]; /* live area, where joystick is displaced from center */
if (!jinstall()) /* install the joystick */
puts (JOYTEST: Error...joystick not responding);
else
{ puts (JOYTEST: Written by Scott Messier\t\tv1.0);
puts (This program demonstrates joystick calibration, which requires);
puts (you, the user, to move the joystick to all points reachable and);
puts (then click the joystick button to end the calibration process.);
puts (You will then enter a joystick readout stage which can again be);
puts (aborted by clicking a joystick button.\n);
puts (Click and release a joystick button to begin calibration...);
PAUSEJOYST
jcalibrate (live, printxy);
cx = (live[0][0] + live[1][0]) >> 3;
cy = (live[0][1] + live[1][1]) >> 3;
do
{ jread (&x, &y, &b);
if (x >= live[0][0] && x <= live[1][0] &&
y >= live[0][1] && y <= live[1][1])
{ /* reading is not an erroneous spike */
/* shift readings down two bits to eliminate random drift */
x = (x >> 2) - cx;
y = (y >> 2) - cy;
} else /* report no displacement */
x = y = 0;
printf (x = %d, y = %d, buttons = %d \n, x, y, b);
} while (b == 0x00F0); /* until button is pressed */
}
}
_________________________________________________________________________________________________________
A Medley of Business Issues
Chris Crawford
GEnie Reminder
The JCGD has its own RT (Round Table) on GEnie. As a subscriber to the Journal, your time in the Game Design RT is absolutely free!
GEnie can only be accessed during off-hours: weekday evenings after 6:00 PM, and all day weekends and holidays. Set up your tele-communications software to dial 800 - 638 - 8369. This is a national GEnie access number; you will want to find your local access number once you are logged on. You may need to prod it with a carriage return. At the prompt “U#=”, type “XTX99623,JCGD” and a carriage return. This is our secret ID number that identifies you as a JCGD subscriber and allows you to sign up for GEnie without paying the normal sign-up fee. You will still need to supply a credit card number, and you will still have to pay normal charges for the time that you spend in other areas of GEnie; however, you will probably find these other areas well worth the expense.
The signup process should also give you information about local access numbers, a GEnie ID number, and allow you to set your password.
Once you have signed up, you will need to send an EMail message to get set up. So move to the mail section of GEnie. Simply type MAIL<CR>, or navigate through the menu system to get there. Once there, select menu item 6 (Enter a Text Letter Online) to send a mail message to “CCRAWFOR” (that’s me.) The subject of the letter should be “JCGD Free Flag”. The content should be a short sentence such as, “Hello, here I am.” When I receive your letter, I will request a free flag that insures that your time in the Game Design RT is not billed. This will take a day or two.
After waiting two days for your free flag to be set, get back on to GEnie using the local access number with your new logon ID and password. Once on, type “JCGD” to go to the JCGD RT. If your free flag has been set successfully, it will give you a message noting this fact; if you don’t get the message, your credit card will be billed for the time you spend in the JCGD RT.
It will next ask you if you wish to enter the JCGD area; respond affirmatively and enter. Select menu item 1 (Bulletin Board) to enter the Bulletin Board area of the RT. Type “BROWSE <CR>” and you’ll be reading messages. To reply to a message topic, just type “REPLY<CR>” at the prompt “REP?”.
Don’t be intimidated by all this; the GEnie system, while a bit clumsy, is completely menu-driven, so it is difficult to screw up, even if you don’t have a manual. Moreover, if you need help at any point, just type “HELP” and hit the carriage return and it will explain your options.
I urge you to join the Game Design RT on GEnie and participate in the discussions there. Our discussion group there is witty, erudite and gentlemanly — and as sysop, I keep the place flame-free. I hope to see you there!
Call For Papers
The Computer Game Developers’ Conference has issued a call for papers for next year’s conference. If you’d like to speak, you can get the call for papers by calling Anne Westfall at (415) 965-4900 between 10:00 AM and 6:00 PM PDT. You’d better hurry — the deadline for submissions is October 15th.
And while you’re at it, please remember that I’m always pleading for articles for this Journal. If you have an idea that you’d like to present to the world, the Journal is the ideal way to do so, and you get a free one-year subscription extension if I print your article. So get me those articles!
Things for Sale
I have a variety of items to sell directly. The first is Siboot, the first (and still only) character interaction game. Not much of a commercial success, but a Mother Lode of ideas for game designers interested in character interaction. I have both IBM and Macintosh versions available for $25 plus $5 shipping. Also available is the Siboot Source Code and Notes package. This is the whole shebang: the game, manual, the source code in Pascal, and two hundred pages of designer’s notes. All for $150, $175 outside of North America. Specify Mac or IBM.
_________________________________________________________________________________________________________