Jump to content
IGNORED

Gauntlet by Donald R. Lebeau 1984


highendsystems

Recommended Posts

I can kinda understand Don's point of view though. When I was learning certain programming languages, three different ones as a matter of fact. I used alot of tricks I learned in Basic which didn't go over to well.

 

One had my college teacher stumped he had to take it to another fellow teacher to figure out what was going on. Turned out the compiler was not designed to handle what I was trying. Another time in an assembly class I did the same thing, optimized something and it didn't work. The teacher refused to look at it since I did not do it "exactly" as he told the class to do it. Same thing happened in my Pascal class. In once case I managed to get it to work, in the other it worked but the teacher argued that it was incorrect and argued that since he wrote several "books" on pascal that he was right. :lol:

 

Anyways in those cases my "experience" was getting in the way and I should have focused on making sure it worked first.

Link to comment
Share on other sites

Great discussion. I actually fall on both sides of this issue and there's no hard and fast rule. Each project is different. I'm very aware of that anytime I type out the "mantra". But it does make people stop and think about when and why to optimize, which is a Good Thing.

 

David makes some really valid points. It's experience that tells you when and where to optimize. In my case, a lot of that experience ammounted to "I'll never do that again!" :)

 

Sometimes it's hard to explain to the company president that he's going to lose the sale because you made the code too clever and you can't possibly put that new feature in on time. They never seem interested in how clean the code is and how fast it runs. Some people... ;)

 

I hope nobody gets the impression from my posts that I think that optimization is a bad thing. It isn't. Doing it prematurely, needlessly, or cryptically is a bad thing. I've worked with a some programmers who write the "fastest" code first and then spend way too much time rewriting sections of it because they didn't have the whole picture before they worried about throughput. The major optimizations should be done either during design like David said, or after the application is "working" and you can identify the real bottlenecks. Again, there's stuff the you can do along the way, but usually the biggest gains are when you're looking at the overall picture, either while your designing the system or after you have something to actually measure performance on.

 

David also has the best line: "Optimizing blindly can be worse than not optimizing at all." I should make a poster out of that! I've run projects that turned into nightmares because one of the programmer totally optimized his section (which was nowhere near the time critical path) and then later on everything he wrote had to be thrown out and rewritten because a customer requirement changed and his code was totally inflexible. Most of the projects I've worked on lately have to be very flexible because each customer has different requirements. In that environment, optimization is second to flexibility.

 

Anyways, it's great talking about this stuff and discussing different approaches.

 

Oh HappyMonster, I actually used ring buffers in Gauntlet. Trust me. Really. I'm not kidding :) (I actually didn't intend to spend so much time on ring buffers, but I didn't think I could describe how I used them without everyone knowing what they were. Didn't think it was going to take so long, but they're useful enough to be worth spending the time on. They're central to the core of Gauntlet and are basically the part that I built the rest of the game around.

 

Sorry for the slower updates. I'm at my new house and still don't have internet yet, so I'm writing my posts during lunch. Hoping to get past this ring buffer stuff and back onto the fun stuff soon... :)

Link to comment
Share on other sites

Great stuff Don! I remember stumbling upon this after the other Gauntlet was out in the arcades, so seeing this on my local BBS naturally piqued my curiosity. :) When I saw the size of it, I thought, "Could it be? The size seems reasonable. Maybe..." Then, after I ran it, I thought, "What a ripoff! Somebody is trying to cash in on the popularity of Gauntlet with this crappy saucer shooter!" Then, after I'd played it for a while I thought, "Wow, what a cool game!" It's too bad that I was just a kid at the time with little to no disposable income (I spent most of my time watching other people shoving their quarters into the other Gauntlet). :P :)

 

I just have to say that the discussion in this thread is really quite interesting and that it's funny that you should be writing about ring buffers when those are the very things causing Virtual Jaguar problems at the moment. :) And funny you should mention optimization--I had to rip out lots of "clever" code just to get things to work properly when I inherited the code base. And there's still much to do to get it working correctly! ;)

 

@HappyMonster: I know this is probably a long shot, but since it seems like you intend to give away the full version of your game how would you feel about opening up the source? It would make a lot of people who don't use windows happy. ;)

Edited by Shamus
Link to comment
Share on other sites

Wish I had found this game back in the day - it's fun and really clever (tough though!) - fascinating info about how it works - thanks for sharing, Don.

 

@happymonster

Been familiar with Storm for a while, as you know, but hadn't realised 'till now it was inspired by Gauntletak. Sorry to hear it didn't work out as shareware - it's a really decent game.

Link to comment
Share on other sites

After the Gauntletak high score club contest, I fired up the demo game of Gauntlet that I played through all the years, I was greeted with this intro screen:

 

GAUNTLET

 

Copyright © 1984 by Donald R. Lebeau

 

Press <SPC> for more, <RET> for game.

 

>>I was intribued by this. Definately basic compared to the Gauntletak screen, but more on comparisons later. :D I hit the Space bar on my Atari 800, and I saw<<

 

This program is yours, enjoy it!

 

GAUNTLET is marketed as user supported software.

 

This means that it is distributed by people like you, by making copies for your friend or bringing it to user-group meetings.

 

>>That was so cool... especially in a time when you would pay $40 for some piece of crap game only to find out that it was maybe worth $5.<<

 

Because GAUNTLET is not sold in stores, this is the only way other people will get to see it.

 

Press <SPC> for more, <RET> for game.

 

GAUNTLET is a 28K assembly language program that runs in a 48K machine.

 

>>Oh well, so much for my jokings that it would have made a good 5200 game<<

 

If you got GAUNTLET from a BBS, renaming it "AUTORUN.SYS" will make it boot automatically from disk.

 

If you want to put GAUNTLET on a BBS (I'd appreciate that),

 

>>So cool!!<<

 

please send it under the name "GAUNTLET.OBJ" so I can find it if I happen to be snooping around on that system.

 

Press <SPC> for more, <RET> for game.

 

Play this game for a while, if you like it, you can send a donation to show your appreciation.

 

Just send what you think the program is worth to you. Feel free to make comments and suggestions for improvement, if there is enough support, I'll be able to take the time to improve it.

 

Where else can you get (legally) software like this for the price YOU want to pay?

 

Press <SPC> for more, <RET> for game.

 

>>Well luckily the trend of shareware and demoware did continue. I wonder if this was one of the first demowares released?<<

 

My address is:

 

Donald R. Lebeau

P.O Box XXX

Pepperell, Mass 01463

 

Please include a self addressed stamped envelope for replies to your questions.

 

Press <SPC> for more, <RET> for game.

 

Donations of $35 or more (or more?)

 

>>Love that (or more?) LOL! <<

 

makes you a RESTIGERED USER.

 

Registered users will receive the Registered version of GAUNTLET

 

The registered version includes:

 

1. A complete illustration manual

2. A 6 play level, feature packed registered version of GAUNTLET

3. Notice of new releases.

4. Entrance into the high score contest for a $1000 prize.

5. For every friend you get to register, you'll receive $5.

6. Every 100th person registering will receive a check for $100.

 

Press <SPC> for more, <RET> for game.

 

The registed version of GAUNTLET has six play levels, new enemy ships and new weapons, including Taknukes and the dreaded mobber.

 

>> Taknukes.. basically Nukes right? And the mobber was the Robot ship?<<

 

If you have a friend register, just have him/her include your name and address when they register and you'll get a $5 check. (Wasn't that easy?)

 

>>Pretty cool.<<

 

There is no limit to the number of friends you can register.

 

User groups are welcome to put GAUNTLET in their public domain libraries after registering the program. All checks will be sent to the group representative or account registered for that group. BBS's don't count as users or friends!

 

>>I like this incentitive program! <<

 

Press <SPC> for more, <RET> for game.

 

Registered users may enter their high scores in the contest, the highest score in a 6 month period will get $1000. Details will be provided with the manual. (So you think you're pretty good, Eh?)

 

>>Yeh... I do... but then I've had two decades to practice :P <<

 

The registed version plays a lot differently than this version,

 

>>No kid... still difficult, but different <<

 

so register soon if you want a chance to win.

 

Please allow 6 to 8 weeks for delivery.

 

Press <SPC> for more, <RET> for game.

 

Enjoy this program, I had a lot of fun writing it. You'll find each enemy ship has its own personality and must be dealt with in a different way, and wait till they team up against you!

 

>>Over time you learn that is when they die the fastest :D <<

 

Any time you want to read the manual portion of this text, just reboot the game and type "M".

 

Good luck! I hope I've provided you with many hours of entertainment....

 

Press <SPC> for more, <RET> for game.

 

In GAUNTLET, you are the pilot of a lon spacecraft trying to penetrate the defenses of an enemy battlegroup dug in on a small moon. You must get through 50 screen of terrain and fight 17 types of enemy ships armed with an assortment of 10 different weapons. Each enemy ship is intelligent and most fire more than one type weapon. Some take lots of damage to kill, others will dodge your shells or shoot them out of the sky. There are camouflaged gunbases that are almost invisible and ships that drop small robot ships that fight on their own. Mines will chase you as the scouts and hunters use the terrain to trap and kill you.

 

Welcome to the world of GAUNTLET!

 

Press <SPC> for more, <RET> for game.

 

Your ship is equipped with rapid fire fusion bolts and a special weapon. You can use homing missiles, flares that burn through rock, or TRIDEX that explodes along a plane. Your ship is fitted with a disruption field that vaporizes matter on contact and recharges by absorbing stray energy from explosions. As long as the field is charged, you are indestructable. But each hit drains the field a little bit...

 

When the field is completely drained it implodes, and the game's over.

 

Press <SPC> for more, <RET> for game.

 

The title screen:

 

The game options are selected by using the <OPTION> and <SELECT> keys. To change the options, press <SELECT> until the line you want is highlighted, then press <OPTION> until the option you want is displayed. When everything is set up the way you want it, press <START> or the joystick button to play the game.

 

Here's the list of options:

 

LEVEL:

NOVICE is the easiest play level.

NORMAL is hard.

ELITE is suicidal.

 

 

Press <SPC> for more, <RET> for game.

 

EXIT TYPE:

HIT + RUN let's you exit a screen at any time.

RAMPAGE keeps you on a screen until all enemy are destroyed.

 

GAME LENGTH:

ALL plays all 50 screens.

FIRST plays only the first 25.

LAST plays the last 25.

 

WEAPON:

MISSILES arms you with missiles.

FLARES arms you with flares.

TRIDEX arms you with TRIDEX.

 

TERRAIN:

NORMAL users terrain.

FREE SPACE plays the game with no terrain at all.

 

Press <SPC> for more, <RET> for game.

 

GAUNTLET users one joystick in port one. It operates in two modes:

 

BUTTON UP:

Moving the joystick accelerates the ship in a selected direction.

 

BUTTON DOWN:

Moving the stick fires in the desired direction. Holding the button down will fire as fast as possible in the direction you move the stick. There is no need to press the button once for each shot.

 

Press <SPC> for more, <RET> for game.

 

The ship drifts along when you're not accelerating. This allows you to move in on direction and fire in another. Be careful of getting "Trigger happy" and drifting into a hill!

 

Press <SPC> for more, <RET> for game.

 

KEYBOARD CONTROLS:

 

THRUST:

 

Pressing the numbers 1-0 varies the amount of thrust that is applied each time you move the stick "1" is the lowest and "0" (meaning 10) is the highest. The game defaults to 10 when it starts, a setting of 5 is good for inside the caves. Thrust does not change your maximum speed, only how fast you can change course.

 

Press <SPC> for more, <RET> for game.

 

WEAPON SELECTION:

 

By pressing the space bar, you can select between fusion bolts and your special weapon. The weapon currently selected will appear on the display at the bottom of the screen. The type of special weapon you carry is chosen before the game starts. The number is brackets how many of the special weapons you have left to fire. Whenever there are none left the computer will automatically select fusion bolts.

 

>>This really surprised me. The registered game has UNLIMITED missiles. I remember playing it thinking "Were'nt the missiles limited. Yep, in the demo they were.<<

 

Press <SPC> for more, <RET> for game.

 

STOP SHIP:

 

Pressing the "X" key stops your ship from drifting. Good for ambushes and tight caves.

 

FREEZE GAME:

 

Pressing the logo key (/|\) will freeze the game, pressing it again will make it continue.

 

>>Neat to know, but when I needed to take a break, I cleared the screen and hit "X" and all was good.<<

 

Press <SPC> for more, <RET> for game.

 

YOUR SHIP:

 

Your ship is equipped with a disruption field that protects it from damage. As long as the field is charged, you're indestructable and can even bore through solid rock!

 

>>Such power to wield!!! Mohahahah!!!<<

 

The current power of the field is displayed under the heading "DAMAGE" in the center of the display panel at the bottom of the screen. The higher the "DAMAGE" number, the more damage your ship can take. DAMAGE can go as high as 99, if it reaches zero, the field collapses and the ship explodes. Just like real life, the game's over when you die!

 

Press <SPC> for more, <RET> for game.

 

Every time the field is drained by an explosion or collision, the screen will flash. You can tell how badly you were hit by how long the flash lasts. The amount of damage you take varies greatly depending on the circumstances. An average hit causes from 1 to 4 points while crashing head-on into an exploding spaceship could cause up to 20! Enemy ships take damage the same way you do, except they can't build their fields back up (in this version of GAUNTLET anyway).

 

Press <SPC> for more, <RET> for game.

 

Every time you enter a new screen you get two points added to the damage point you have left from the last screen adds 100 to your score. Every 10,000 points adds 10 points to the strength of the field. The way to get the highest score is to keep thee field as charged as possible at all times.

 

Press <SPC> for more, <RET> for game.

 

YOUR WEAPONS:

 

FUSION BOLTS are rapid fire beams of energy that explode on contact. When fired in a steady stream, the explosions will tend to "walk back" to your ship. This effect can be used to create a temproary shield or to fry yourself, depending on your skill. You can have 5 fusion bolts on the screen at once.

 

>>Love the walk back effect, and have used it as a shield from time to time. <<

 

MISSILES lock-on to enemy ships or bases and chase them until they are destroyed. Missiles choose their own targets and can take a lot of damage. This gives them the ability to kill more than one target at a time. You can have 2 missiles on the screen at one time.

 

Press <SPC> for more, <RET> for game.

 

FLARES burn through solid rock and are fired like fusion bolts, you can have 5 on the screen at once.

 

>>Found it interesting that flares got points for blowing up the landscape here too, but since you only got a limited number, not as noticable.<<

 

TRIDEX explodes on a plane, on some planets it's used to clear trees. It causes lots of damage and can usually destroy any ship with only one hit. Strafing gunbases and digging holes are very effective with TRIDEX. You can have 5 on the screen at once.

 

>>I remember I used to like these when I played this demo version. <<

 

Press <SPC> for more, <RET> for game.

 

TERRAIN and SCREENS:

 

Playing the game consists of crossing a set number of terrain sections called "Screens". To exit a screen, move off the right edge. In RAMPAGE mode, you won't be allowed to exit until all enemy on that scrren are killed.

 

The first 30 screen are above ground and the last 20 are in caves. In some caves you will have to blast through solid walls to get through.

 

On the last screen, (Scrren 50 in ALL and LAST mode, and screen 25 in FIRST mode) resides the Kingship. The Kingship is mean and if you kill it you win the game.

 

Press <SPC> for more, <RET> for game.

 

This manual will get you started and I've included everything I could fit in the space I had. The printed version of the manual illustrates all the enemy ships and weapons and what they do, but for now you'll just have to be surprised (Shocked?). Enjoy it!

 

Press <SPC> for more, <RET> for game.

 

----------------------------------------------

 

Well, all that comes to 12K of text, which means that GAUNTLET was actually a 16K game in the demo version. :D Might be possible for the 5200 afteral.

Link to comment
Share on other sites

Well, after playing GAUNTLET and GAUNTLETAK all the way through, I have some observations and questions:

 

1) GAUNTLETAK.. what does the last "AK" stand for?

2) The level SCREENS were the same, though on the GAUNTLETAK version they were more polished.

3) In the demo you get BOLT and whatever weapon you choose at the start. Not like the full version where you get all weapons, and all weapons are UNLIMITED! :D

4) The demo version greets you on the first screen with mines and kinda builds up. The full version greets you with mines and things you might not see till 4 screens in on the demo version.

5) Those jammer ships are not in the registered version. LOL! I hate those. Though brillant.

 

And one thing I'd love to hear about now that we've completed a Gauntletak high score contest is how did the $1000 contest go? I know it's been a while, but how many entries did you have, what scores did you get, and what was the high score then?

 

I think back in the 80's I finally got into the manual we saw above and saw the contest and the idea of the registered game. I might have even tried to write to the address enclosed for more information hoping something would be possible. But alas, but the time I saw this manual, the $1000 contest, and even the registered version were a memory, and I figured Atari was a memory as well, and I strangely believed I was the last that cared about my Atari computer.

 

Then years later the internet came along, and this is like a whole new age, maybe better, for Atari computing and gaming. :D

 

I certainly had no idea we'd be talking to the creator of this game years later! Truly cool.

 

So how did that $1000 high score contest go, or if nothing else, what were the scores like that you got in?

Edited by doctorclu
Link to comment
Share on other sites

1) GAUNTLETAK.. what does the last "AK" stand for?

He's already explained that it stands for "Please Atari don't sue me".

 

LOL... like the word "Gauntlet" was never used before the game with mazes and archers with pointed ears. Too funny.

Link to comment
Share on other sites

Shamus:

 

Actually Atari was trying to cash in on the popularity of Gauntlet with their crappy elf game. :)

 

Glad you're getting something out of the ring buffer discussion. I had a ring buffer bug in Gauntlet that drove me crazy for almost two months. With just the right conditions, very odd things would happen. I had accidently allocated my buffers as 255 elements instead of the 256 that I was using (I had indexes of 0-255 in my head at the time.) This made each buffer "share" it's end locations with it's neighbors. So stuff I put into the Think queue could end up in the Clear queue and stuff I put into the Draw queue could end up in the Fire queue.

 

This one drove me crazy! The game would be running fine and all of a sudden one of the ships would just vanish, or a bullet would launch a big ship out of itself. It seemed like requests were getting sent to the wrong queues, and it took me a while to realize that the "odd things" always happened to functions in neighboring queues. As soon as I looked at the queues themselves, it was obvious what I had done. And I felt pretty stupid for not checking that when I first saw the problem.

 

And I feel for you for have into rip out all that clever code. Had to do that too many times. And it hurts more when it's clever stuff you did. :)

 

doctorclu:

 

(From the Demo speil)

 

>> Taknukes.. basically Nukes right? And the mobber was the Robot ship?<<

 

Yeah, by the time I published the registered version I had forgotton what I had called them in the demo blurb.

 

>>Over time you learn that is when they die the fastest <<

 

Shush you! :)

 

>>Found it interesting that flares got points for blowing up the landscape here too, but since you only got a limited number, not as noticable.<<

 

Guess that bug was in the demo version too. Ammo the the player fores isn't supposed to give you points when you fire it. There's a flag that some dummy was supposed to set when he decided to let the player fire flares. Of course that was one of those 2am "Wouldn't it be cool if you could shoot these" type of ideas that was really easy to throw in and I never went back and checked if I did it right.

 

TRIDEX:

 

Odd fact: I was reading a book in Gordan Dickson's Dorsai series at the time and it mentioned Tridex and how it was used to clear landing sites and trees. It sounded cool having something that exploded along a plane, so I put it in. It's still my favorite ammo type.

 

Well, all that comes to 12K of text, which means that GAUNTLET was actually a 16K game in the demo version. Might be possible for the 5200 afteral.

 

Sorry, I mentioned it before, but Gauntlet is such a memory hog that I can't just let 12k worth of memory just sit around. As soon as the game starts I wipe the text area clean and use it for something else.

 

That's why I said in the manual "Any time you want to read the manual portion of this text, just reboot the game and type "M"." You couldn't see the manual after you started playing because I ate it.

 

Here's answers to your questions:

 

1) GAUNTLETAK.. what does the last "AK" stand for?

 

I wanted the new name to have the word Gauntlet in it for consistancy and I tried to think up ssothing that wasn't too lame. Gauntletak is the most less lame of the bunch. :) I started with "Gauntlet Attack" and shortened it. Tried Gauntattack and that sucked. The "letak" in Gauntletak sounds like "attack" and the "Tak" reminded me of Tactics, so I kept it. But it still makes me gag everytime I see it.

 

2) The level SCREENS were the same, though on the GAUNTLETAK version they were more polished.

 

I think I was able to fit more terrain data in the registered version so I could make them more detailed. Don't remember doing it though...

 

3) In the demo you get BOLT and whatever weapon you choose at the start. Not like the full version where you get all weapons, and all weapons are UNLIMITED!

 

I always hated running out of ammo, so I changed it. But then you could just spew missiles so I fixed that by creating the jammer ships. That kept the ballance.

 

4) The demo version greets you on the first screen with mines and kinda builds up. The full version greets you with mines and things you might not see till 4 screens in on the demo version.

 

In the demo version the order of battle is set for every screen. In the full version, it's randomized so you don't know what to expect and it's a lot more replayable. The full version also adds more enemies sooner because I figured anyone buying it would be a fairly good player and I didn't want to bore tham.

 

5) Those jammer ships are not in the registered version. LOL! I hate those. Though brillant.

 

But you can fire unlimited missiles at them. :)

 

And one thing I'd love to hear about now that we've completed a Gauntletak high score contest is how did the $1000 contest go? I know it's been a while, but how many entries did you have, what scores did you get, and what was the high score then?

 

Except for the <50 registrations I got:

 

No donations.

No friend referals.

No high score entries.

 

It was like I had thrown Gauntlet into a huge black hole. :( Most of the registrations came in in the first three months and then I got maybe one a month for the next year. After that it dried up completely and I stopped renting the P.O. box after that. I had moved and once a month I would drive two hours to find it empty. Not fun.

 

I got maybe 50 letters from broke teenagers telling me what a good game it was and I cherished those because it meant that the game wasn't a total waste of time and that some people were really enjoying it.

 

1) GAUNTLETAK.. what does the last "AK" stand for?

 

He's already explained that it stands for "Please Atari don't sue me".

 

LOL... like the word "Gauntlet" was never used before the game with mazes and archers with pointed ears. Too funny.

 

Actually the time line was:

 

1. I copywrited the name Gauntlet six months before releasing it.

2. I release Gauntlet by sending it out to every BBS I could find.

3. One month later, Atari comes out with their Gauntlet arcade game.

4. I call a lawyer and tell him I'd like to sue Atari for copywrite infringement.

5. I can still hear him laughing today.

6. Figured I have to change Gauntlet's name so it wouldn't get confused with the arcade game.

 

 

So how did that $1000 high score contest go, or if nothing else, what were the scores like that you got in?

 

I think my personal best was around 32K, but I didn't know about the flare trick. If I did know about the flare trick my score would have been still around 32k because I would have fixed it. :) About 20K - 30K were the avarage scores from the Atari Users group I was in. These guys were "playtesters" so they couldn't enter the contest.

 

The odd thing was I had banked the Gauntlet money in it's own account and if someone entered the contest, they would have won and I would have sent it to them. (They would have ended up making more from Gauntlet than I had. That's a pretty odd thought.) I really was hoping Gauntlet would do well and I had set everything up properly for tax purposes. The money was sitting there until I finally shut down the P.O. Box after the letters stopped coming in.

Link to comment
Share on other sites

Shamus:

 

Actually Atari was trying to cash in on the popularity of Gauntlet with their crappy elf game. :)

 

LOL!! Agreed. Well, to a point. I have noticed that creative ideas seems to float through the air, and a few manage to grab the idea at the same time.

 

>> Taknukes.. basically Nukes right? And the mobber was the Robot ship?<<

 

Yeah, by the time I published the registered version I had forgotton what I had called them in the demo blurb.

 

>>Over time you learn that is when they die the fastest <<

 

Shush you! :)

 

LOL!!

 

>>Found it interesting that flares got points for blowing up the landscape here too, but since you only got a limited number, not as noticable.<<

 

Guess that bug was in the demo version too. Ammo the the player fores isn't supposed to give you points when you fire it. There's a flag that some dummy was supposed to set when he decided to let the player fire flares. Of course that was one of those 2am "Wouldn't it be cool if you could shoot these" type of ideas that was really easy to throw in and I never went back and checked if I did it right.

 

I like how someone brought that up in the High Score Club contest and you replied with "Oops!"

 

The funny thing is that having the flares get points for hitting landscape, "Gauntletak" became "Space Strip Miner".

 

TRIDEX:

 

Odd fact: I was reading a book in Gordan Dickson's Dorsai series at the time and it mentioned Tridex and how it was used to clear landing sites and trees. It sounded cool having something that exploded along a plane, so I put it in. It's still my favorite ammo type.

 

It does have a NICE explosion.

 

1) GAUNTLETAK.. what does the last "AK" stand for?

 

I wanted the new name to have the word Gauntlet in it for consistancy and I tried to think up ssothing that wasn't too lame. Gauntletak is the most less lame of the bunch. :) I started with "Gauntlet Attack" and shortened it. Tried Gauntattack and that sucked. The "letak" in Gauntletak sounds like "attack" and the "Tak" reminded me of Tactics, so I kept it. But it still makes me gag everytime I see it.

 

Well, same name, but different gameplay entirely. Though both are "Gauntlet" like. Actually I came to know it as Gauntlet, but Gauntletak, maybe after seeing a lot of Klingon words, just seems normal somehow. :D

 

2) The level SCREENS were the same, though on the GAUNTLETAK version they were more polished.

 

I think I was able to fit more terrain data in the registered version so I could make them more detailed. Don't remember doing it though...

 

3) In the demo you get BOLT and whatever weapon you choose at the start. Not like the full version where you get all weapons, and all weapons are UNLIMITED!

 

I always hated running out of ammo, so I changed it. But then you could just spew missiles so I fixed that by creating the jammer ships. That kept the ballance.

 

Yes it did. You get on a screen and learn to look for those jammer ships. THEN fire off a round of missiles.

 

4) The demo version greets you on the first screen with mines and kinda builds up. The full version greets you with mines and things you might not see till 4 screens in on the demo version.

 

In the demo version the order of battle is set for every screen. In the full version, it's randomized so you don't know what to expect and it's a lot more replayable. The full version also adds more enemies sooner because I figured anyone buying it would be a fairly good player and I didn't want to bore tham.

 

That definately worked.

 

5) Those jammer ships are not in the registered version. LOL! I hate those. Though brillant.

 

But you can fire unlimited missiles at them. :)

 

Unlimited to get yourself killed! :o

 

And one thing I'd love to hear about now that we've completed a Gauntletak high score contest is how did the $1000 contest go? I know it's been a while, but how many entries did you have, what scores did you get, and what was the high score then?

Except for the <50 registrations I got:

 

No donations.

No friend referals.

No high score entries.

 

It was like I had thrown Gauntlet into a huge black hole. :( Most of the registrations came in in the first three months and then I got maybe one a month for the next year. After that it dried up completely and I stopped renting the P.O. box after that. I had moved and once a month I would drive two hours to find it empty. Not fun.

 

You know, I think the ideas you had for marketing were good and ahead of their time. The first three months push that you mention sounds about right. If you ever get a time machine :D and revisit 1984, maybe hit the BBS's about every quarter either posting a message... OR making another demo game to followup the other demo, but this one only having 10 screens but random... some over land, some in tunnels, and showing the range of enemies.

 

After a year give discounts. All the while giving the Atari community little reminders that you are still out there. And of course upgrades. :D

 

But I think the way you marketed was really cool. I think the only reason Doom, the first toated shareware did any better by using a similar method was mainly due to the mega-BBS, the Internet. ;) If the internet existed in 1984 to the public, I think Gauntletak would have had a much greater sale.

 

Shoulda, coulda, woulda right? ;) I have a few of those like everyone else.

 

I got maybe 50 letters from broke teenagers telling me what a good game it was and I cherished those because it meant that the game wasn't a total waste of time and that some people were really enjoying it.

 

And we did. Hey, is my letter in there? I was a broke teenager back then.

 

However, we broke teenagers are not broke anymore. :) I think that is what makes this new ATARIAGE ;) working so well, we want to see all the games that we could not afford. That was why we were pirates then, and collectors now. I own every Joust & Star Raiders for all Atari systems, a good collection of Infocom and Electronic Arts games, and almost every Bubsy Bobcat game (and associated game system) just to play those games. Collectors. And we grown up teens would love to own a part of the game that we loved for years. You just PAID IT FORWARD for us broke teens.

 

So this ship may not have completely sailed. ;)

 

1) GAUNTLETAK.. what does the last "AK" stand for?

 

He's already explained that it stands for "Please Atari don't sue me".

LOL... like the word "Gauntlet" was never used before the game with mazes and archers with pointed ears. Too funny.

Actually the time line was:

 

1. I copywrited the name Gauntlet six months before releasing it.

2. I release Gauntlet by sending it out to every BBS I could find.

3. One month later, Atari comes out with their Gauntlet arcade game.

4. I call a lawyer and tell him I'd like to sue Atari for copywrite infringement.

5. I can still hear him laughing today.

6. Figured I have to change Gauntlet's name so it wouldn't get confused with the arcade game.

 

Well, while I think you were in the right, like I said before, I really think Atari and the Gauntlet game designers probably had the name in the works as you were pushing this. LOL You could have stood up them, maybe even won. Heck, you had $1000 to put towards it. Just do what Atari did with Sega... when you're failing... sue!

 

Nah... glad you're not that way actually. And Gauntletak has a neat ring to it.

 

So how did that $1000 high score contest go, or if nothing else, what were the scores like that you got in?

 

I think my personal best was around 32K, but I didn't know about the flare trick. If I did know about the flare trick my score would have been still around 32k because I would have fixed it. :) About 20K - 30K were the avarage scores from the Atari Users group I was in. These guys were "playtesters" so they couldn't enter the contest.

 

The odd thing was I had banked the Gauntlet money in it's own account and if someone entered the contest, they would have won and I would have sent it to them. (They would have ended up making more from Gauntlet than I had. That's a pretty odd thought.) I really was hoping Gauntlet would do well and I had set everything up properly for tax purposes. The money was sitting there until I finally shut down the P.O. Box after the letters stopped coming in.

 

Their loss. I mean they OBVIOUSLY saw the contest invitation when they got the instructions on how to register the game.

 

LOL! You know, if you ever get that time machine... :D on the text title screen change "<spc> for manual" to "<spc> for manual, registration info, and how to win $1000"

 

$1000 in 1984... that was a LOT of money back then. Not bad pocket change now. ;) I think sales would have gone for maybe four to five months, with probably more buyers.

 

Broke teenagers would have begged HARDER for the $35 from their parents, and parents would have forked it out on the deal that if their kid won, they would get most of that $1000.

 

Conversations between parents and kids would have gone something like this back in the 80's ...

 

"Ok mom, I'm done with homework and I'm going to bed now."

"Now you need to play GAUNTLETAK to beat the score you sent in LAST WEEK."

"Awww Man..."

"You get in there and play GAUNTLETAK young man and not another word..." :D

 

------

 

Well anyway, they had their chance, you had the money there, and with the Atariage high score contest you finally got the high score entries (probably much higher than expected... I know I didn't think a million points was possible before I saw Fandal's score), and your prize of the autographed manual art to a lot of us competing was worth more than $1000... it was priceless. :D

 

So that long time contest will have closure at last. ;)

Link to comment
Share on other sites

Hi Donald.

 

Been following your most excellent writing on ring buffers. Starting to really grok them huge. Don't know how much time and or energy you have, but is there any chance you could comment on state machines?

 

Seems to me, that's another key game element, and I don't know how to do them well.

 

Thanks again for what you've already told us. Appreciated huge!

Link to comment
Share on other sites

Hi potatohead,

 

Glad you're getting something out of it. It's actually fun writing this stuff up and kinda neat to see it laid out in an organized fashion instead of just floating around in my head.

 

I was going to cover state machines somewhat when I discussed the AI and think modules. The method I used there was basically a task manager approach and is one way to implement a state machine. It worked great for the 6502 and I've used it elsewhere as well.

 

Maybe ten years ago I ran across another method to implement a state machine in C that was amazingly simple, fast, and bombproof at the same time. I was writing a camera controller for a videoconference system at the time and saw how it was done in the old code. It was one of those "Man, I've been doing it the hard way all these years!" type of discoveries. It's just one of those simple approaches that you normally wouldn't think of to implement something so potentially complex.

 

Since you asked, I'll cover it after I show how I did the Gauntlet AI. It's one of those "basic toolkit" techniques you find yourself using all over the place, so I'm hoping someone here finds it useful.

 

doctorrclu:

 

Well, while I think you were in the right, like I said before, I really think Atari and the Gauntlet game designers probably had the name in the works as you were pushing this. LOL You could have stood up them, maybe even won. Heck, you had $1000 to put towards it.

 

My lawyer said two things (between chuckles). The first was that Atari had more lawyers than I had relatives. The second was that in his experencewith the little guy sueing a large company like Atari, their first line of defense is to tie things up legally and constantly delay every step in the process by getting extensions to wear you down. He said not to expect to see the inside of a courtroom for at least two years and after spending almost a hundred thousand in legal fees. Two lawyer friends of mine pretty much said the same thing, you've got to have deep pockets and lots of persistance to win a case like this.

 

But you gave me an idea. Next time someone asks where the name Gauntletak came from, I'll say it's Klingon for Gauntlet. :)

Link to comment
Share on other sites

ah... and just to add... the state engine of monsters is working very basic... i don't know if correct...

 

monster has several states and there for i am doing things like

 

if monster is in state 0 then do this. when something special case reached put monster in state 1 etc etc etc

 

monster state 0 = dead

monster state 1 = monster appears

monster state 2 = guarding

monster state 3 = player is in sight --> attacking

monster state 4 = monster attacking player until dead

monster state 5 = dying

...

 

so i can implement even different monster state engines for different monster types (the stupid ones, the more clever ones, end bosses etc...)

 

but i don't know if that is done clever or even fast... ;)

 

and each monster has a structure of 32 bytes... so what i am doing is feeding the monster engine with an the data of each active monster... so i am not parsing through an array & memory but what I am doing is each frame...copy monster_struct[id] into zero page struct holding the active monster var.

 

so on this working array all realtime things are done, like xpos,ypos,hit points,state changes etc... after the game went through the game cycle they are copied back to the monster_struct[id*32]. as demo coder I first found this horrible to copy back&forth data instead of using pointers but the code itself is very readable and i can easily debug and monitor variables...

Edited by Heaven/TQA
Link to comment
Share on other sites

here is the code snipped of the mentioned engine parts. Not optimes but readable... ;) © by K.Nadj... ;)

 

 

 

monster_state_engine: 
monster_state_loop	
	jsr copy_monster_stats_in
	jsr state_engine
	rts

;mo_state = 0 --> dead
;mo_state = 1 --> monster appears
;mo_state = 2 --> moving
;mo_state = 3 --> hunting
;mo_state = 4 --> dying
state_engine: lda mo_state
	bne state_engine0
;mo_state = 0 --> dead
	lda #0
	sta mx
	sta my
	ldx room_no			;in which room?
	lda mo_count
	cmp mo_roomtab,x
	bcs state_engine97
	lda	#1				;monster should appear
	ldy mo_roomtab,x	;how many monsters in that room?
	sty monster_flag	;will be used for the get_new_monster
	beq state_engine99	;room already cleared
	cpy maximum_monsters ;should monster remain in "dead"-status?
	beq state_engine98	;no if they both have same values = no killed monster yet
	cpy #mo_max_frame	;is monster_per_room already <as maximum?
	bcs state_engine98
state_engine97	lda #0				;remain monster in dead status
state_engine98 sta mo_state
	ldy #mo_max_frame	
	sty mo_max_per_room	;saves the value how many monsters max active per room
state_engine99	rts
;mo_state = 1 --> monster appears
;test
state_engine0 cld	
	sec
	lda px
	sbc mx
	bpl state_engine1
	eor #$ff
	clc
	adc #1
state_engine1 	sta mo_temp
	sec
	lda py
	sbc my
	bpl state_engine2
	eor #$ff
	clc
	adc #1
state_engine2	clc
	adc mo_temp
	sta dist
mo_state_1:	lda mo_state
	cmp #1
	bne mo_state_2
	jsr	get_new_monster ;monster appears
	jsr copy_monster_stats_in
	jsr set_monster
	lda #2
	sta mo_state
	rts
;mo_state = 2 --> moving
mo_state_2: cmp #2
	bne mo_state_3
	jsr move_monster
;jsr check_collision_mo		;check for collision and attack
	rts
;mo_state =3 --> hunting
mo_state_3: cmp #3
	bne mo_state_4
	jsr monster_hunting_player ;when player kills monster subroutine sets mo_state to =4
	rts
;mo_state_4 --> dying
mo_state_4: cmp #4
	bne mo_state_5
	jsr monster_dying
	lda #0 ;dead
	sta mo_state
mo_state_5:	rts

monster_dying: rts

;move monster routine
;at the moment each monster is waiting until player gets in sight 
;and then starts to attack
move_monster: cld
	sec
	lda px
	sbc mx
	bpl move_monster_20
	eor #$ff
	clc
	adc #1
move_monster_20 sta mo_temp
	sec
	lda py
	sbc my
	bpl move_monster_21
	eor #$ff
	clc
	adc #1
move_monster_21	clc
	adc mo_temp
	cmp mo_dist		;monsters line of sight
	bcs move_monster_22
	lda #3
	sta mo_state
move_monster_22 rts

monster_hunting_player: 
	cld
	sec
	lda px
	sbc mx
	bpl hunting_monster0
	eor #$ff
	clc
	adc #1
hunting_monster0 	sta mo_temp
	sec
	lda py
	sbc my
	bpl hunting_monster00
	eor #$ff
	clc
	adc #1
hunting_monster00	clc
	adc mo_temp
	cmp mo_dist		;monsters line of sight
	bcc hunting_monster1
;if player is out of sight then put monster back into moving state
	lda #2
	sta mo_state
	rts	
;simple monster routine should be adapted depending on the monster type
hunting_monster1:
;delym will be adjusted depending on how many active monsters per room 
;so the monster speed is adjusted as one frame/one monster rule is applied
		;dec delaym
	sec
	lda delaym
	sbc mo_max_per_room	;1-4
	sta delaym
	bmi hunting_mo
	beq hunting_mo
	rts
hunting_mo lda mx		;save the position if the new position is blocked
	sta old_mo_x
	lda my		;save the position if the new position is blocked
	sta old_mo_y
	lda mo_speed
	sta delaym
	jsr clr_monster
	lda px
	sec
	sbc mx
	bpl mmm0
	cmp #$fe
	bcc mm0
	bne mm1
mmm0	cmp #3
	bcc mm1
	inc mx
	lda #1
	sta mo_dir
	bne mm1
mm0		dec mx
	lda #0
	sta mo_dir
mm1		lda py
	sec
	sbc my
	bpl mmm2
	cmp #$ff
	bcc mm2
	bne mm9
mmm2 	cmp #1
	bcc mm9
	inc my
	bne mm9
mm2		dec my
mm9 	lda my
	bpl mm10
	lda #0
	sta my
mm10	cmp #<screen_y-3
	bcc mm11
	lda #screen_y-3
	sta my
mm11	lda mx
	bpl mm12
	lda #0
	sta mx
mm12	cmp #<screen_x
	bcc mm13
	lda #screen_x
	sta mx
mm13	jsr set_monster
	ldy #128
	bcc mm14
	lda old_mo_x
	sta mx
	lda old_mo_y
	sta my
	jsr set_monster
mm14	jmp check_collision_mo		;check for collision and attack
;		jmp set_monster
mo_dir	dta 0

copy_monster_stats_in: 
	lda mo_count
	clc
:5		asl				;32 bytes per monster
	tay
;		ldy #0
	lda mo_buffer,y
	sta mo_char
	lda mo_buffer+13,y
	sta mo_charr
	lda mo_buffer+1,y
	sta mo_ac
	lda mo_buffer+2,y
	sta mo_th
	lda mo_buffer+3,y
	sta mo_mind
	lda mo_buffer+4,y
	sta mo_maxd
	lda mo_buffer+5,y
	sta mo_hp
	lda mo_buffer+6,y
	sta mo_lev
	lda mo_buffer+7,y
	sta mo_speed
	lda mo_buffer+8,y
	sta mo_exp
	lda mo_buffer+9,y
	sta mo_exp+1
	lda mo_buffer+10,y
	sta mx
	lda mo_buffer+11,y
	sta my		
	lda mo_buffer+12,y
	sta mo_dist
	lda mo_buffer+14,y
	sta mo_temp
	lda mo_buffer+15,y
	sta mo_state
	lda mo_buffer+16,y
	sta delaym
	lda mo_buffer+17,y
	sta mo_drop_rate
	rts
;**** monster engine
;**** puts updated stats back to monster buffer
copy_monster_stats_back: 
	lda mo_count
	clc
:5		asl
	tay
;		ldy #0
	lda mo_char
	sta mo_buffer,y
	lda mo_charr
	sta mo_buffer+13,y
	lda mo_ac
	sta mo_buffer+1,y
	lda mo_th
	sta mo_buffer+2,y
	lda mo_mind
	sta mo_buffer+3,y
	lda mo_maxd
	sta mo_buffer+4,y
	lda mo_hp
	sta mo_buffer+5,y
	lda mo_lev
	sta mo_buffer+6,y
	lda mo_speed
	sta mo_buffer+7,y
	lda mo_exp
	sta mo_buffer+8,y
	lda mo_exp+1
	sta mo_buffer+9,y
	lda mx
	sta mo_buffer+10,y
	lda my
	sta mo_buffer+11,y		
	lda mo_dist
	sta mo_buffer+12,y
	lda mo_temp
	sta mo_buffer+14,y
	lda mo_state
	sta mo_buffer+15,y
	lda delaym
	sta mo_buffer+16,y
mmmm	rts			


Link to comment
Share on other sites

Interesting approach Heaven. The thing I always liked about doing the state machine with the tests and branches is that it's very readable and easy to follow. It's also extremely fast as long as you don't have a huge number of states. If the number of states gets too big, then I would go to either an array of vectors where I keep the state as an index into the array, or a "Sleep" system with a task manager like I did in Gauntlet.

 

As far as copying the monster variables in and back out, in Gauntlet I found that most of the time it faster to just do it in place instead of moving them. To move each variable in and out, you have two regular memory calls and two page zero calls, so it takes roughly the same time as doing three regular memory calls just to load up and save a variable. That means that if you don't access that variable more than three times during your routine, it's actually slower to move it into page zero first than to just access it directly. Add to that the fact that you're moving in all 32 bytes each time and probably not using half of them during each call, and that extra overhead really piles up.

 

In Gauntlet I used buffers for each variable, like XPOS[100] to store the X position for my 100 ships. Then to access it I would just load the ship number into the X register and use LDA XPOS,X to get at the info directly. It was faster that way and since I wasn't operating on copies of the data, I could have the interrupt and background routines operating on different parts of the ships data structure at the same time. Some variables were owned by the interrupt and others by the background routines and any routine could get at the data in the same ship without checking if the object was locked by another process or worrying that the data would be overwritten because another process was working on it's own copy and was just about to write it back.

 

I also allowed me to use the data index number as a "ship ID" that I could them pass around everywhere. Once a routine got the "Ship ID", it had access to all it's data directly. I then used the evil ring buffers to queue these Ship IDs between processes, so for example when the video interrupt determined that it was time to redraw a ship, it just stuffed the Ship ID into the Draw queue and it was done. Later on the Draw module would grab the Ship ID from its queue and would do the work.

 

Again, that's what I did for Gauntlet. Doesn't make that approach "better" but it suited Gauntlet better because I had over 50 variables per ship and had to be able to access them from the video interrupt and from the background simultaneously.

Edited by donlebeau
Link to comment
Share on other sites

Don... the interesting thing in my game is that I have this kind of tables... first i thought about storing monster stucts like this

 

monster_struct_1:

monster.xpos .byte 0

monster.ypos .byte 0

monster.level .byte 0

monster.delay .byte 0

...

 

and pars and access the data via ZP pointers... but problem with that is that the battle engine esp. has a complex algorithm similar to Diablo 1 and therefore i would need often stats... and as you know (ZP),y is not the quickest way to do that...

(i will post later the monster tables)

 

so... i have used tables for each var and monster_id as index.

 

so...accessing monster #7s drop rate i would do a LDX monster_id, LDA monster_droprate_tab,x

Link to comment
Share on other sites

Ring Buffers step 4 - Make it work reliably and fast:

 

Whew! We're finally at the fun section! I'd like to cover a couple of general tips first and then focus more on the 6502 specific stuff.

 

- Make the ring buffer functions "inline" or use macros. In C you can declare your functions as "inline" so that instead of them being called the code is inserted directly in line with the surrounding. This saves all the overhead of calling and returning from the function and pushing and popping the parameters off the stack.

 

In assembly language you can make a macro instead of a routine to handle your ring buffer. You save the on function overhead and macros ensure that all the places that use the ring buffer have the same code. You can just put in ring buffer Put and Get instructions directly in your code without using macros, but that's dangerous. Say you have five places that put stuff into a the buffer and you didn't use macros. If you decide to add a feature like a counter and don't catch one of those places, it's going to take you a long time to figure out what's going on.

 

- Know exactly what type of functionaly you'll need and only add those essential features. I can't stress this one enough! For a ring buffer, every feature adds overhead everytime a piece of data enters and leaves the buffer, so it piles up fast.

 

 

Almost all the optimization you do will be done at design time. That's when you determine what features you absolutely need and how time critical everything must be.

 

The first question you have to ask yourself is "How big should the buffer be?". This is the most important question for number of reasons and depending on you application it will be the easiest or hardest to answer. For example:

 

1. If you have a set numeber of items that will be stored in your buffer, the answer is easy - make the buffer larger than the number of items you need to hold.

 

2. If there is an unlimited number of items - like from a communication stream - you must calculate how many items could possibly be in the buffer at any one time based on the rate that they arrive and the rate that you can take them out of the buffer. This can get tricky because you have to account for things like flow control, or if there's no flow control, you're math better be correct or you're going to have problems.

 

3. If there is going to be flow control, you must determine how fast the flow control reacts and adjust you high and low water marks to accomedate that behavior. For example, if it takes 10 items before the flow is shut off, make sure your high water mark gives you space for them.

 

4. Know the charasteristics of you input and output data streams. For instance, your input might receive blocks of 256 items. If you implement flow control on that input, you might have to leave room for a whole block of data above your high water mark. Or you might need to size your buffer as a even multiple of 256 to handle the blocks smoothly.

 

5. Beware of "deathgrips" when designing your flow control. Suppose your output takes commands of 20 characters at a time - it waits until there's 20 characters in the buffer and then takes them all. You implement flow control on this buffer to throttle the input and decide to set the high water mark at fifteen. . You are dead. How? Fifteen characters come in and you hit the high water mark, so tell the input to stop. Now the input stops - forever. Your output looking at the buffer count and is waiting for 20 characters. It has only fifteen so it won't take any characters out of the buffer. You will never reach your low water mark and turn on the input until you start taking items out of the buffer. Hence you're in what's called a Deadly Embrace or a DeathGrip. This is an obvious example of this type of problem, but I've seen so very subtle variations of it so it's something to look out for.

 

6. No amount of clever design can account for you taking out data slower than you get it. If you can't throttle the input, make sure your process is consuming data faster than it gets it.

 

This may seem obvious, but I've seen too many people try to handle a throughput problem by first making bigger buffers, and then after that doesn't work, try some elaborate double buffering scheme or some other solutio that only postponed the inevitable.

 

Magic Buffer Sizes:

 

The size of the buffer has the biggest impact on the ring buffer performance because some buffer sizes allow you use tricks to eliminate a lot of buffer overhead.

 

Rule of thumb: After determining your minimum buffer size requirement, you want to increase it to the next practical magic buffer size, if possible.

 

What's "Magic Buffer Size"? It's a buffer size that lets you take advantage of the properties of binary to eliminate or optimize the wrapping of your Put and Get indexes. Let's go back to our example of PutData() with a buffer size of 100:

 

 

#define TESTBUFSIZE 100

char TestBuf[TESTBUFSIZE];
int TestPut = 0;

void PutData(char value)
{
 TestBuf[TestPut++] = value;
 TestPut = TestPut MOD TESTBUFSIZE;
}

 

We put the character in the buffer and then increment and wrap the TestPut index. Let's increase the buffer size to then next "Magic Buffer Size" of 256 and see what happens:

 

 

#define TESTBUFSIZE 256

char TestBuf[TESTBUFSIZE];
int TestPut = 0;

void PutData(char value)
{
 TestBuf[TestPut++] = value;
 TestPut = TestPut MOD TESTBUFSIZE;
}

 

 

Look at that! I'll give you a moment to regain your composure because of the awesomness of this example... What? It doesn't look that much different? But I used the "Magic Buffer Size" and now it's blindingly fast... Oh, it isn't? Oh, I'm sorry. it's how you use the Magic Buffer Size that makes it fast, and I haven't done anything yet.

 

First, what makes the Magic Buffer Size of 256 so great? It happens to fall on a byte boundry, or in other words, it's eight bit wide. What's also eight bits wide? A char (or byte). A byte holds a value of 0 to 255. Here's what's cool: If you have a byte with 255 in it and you add 1 to it, what does it do? It wraps around to 0! That's because a byte holds eight bits and it takes nine bits to "say" 256 in binary (100000000b), and the ninth bit just falls off the end. So bytes can be used as self wrapping indexes.

 

That means that you don't have to wrap the index for a 256 byte byffer if you use a byte to hold the index. Let's see how the code looks now:

 

 

#define TESTBUFSIZE 256

char TestBuf[TESTBUFSIZE];
char TestPut = 0;

void PutData(char value)
{
 TestBuf[TestPut++] = value;
}

 

 

We've changed the TestPut index to a char, and then were able to completely eliminate the wrapping line. You can do this for both byte (256) or word (65536) sized buffers, although word sized buffers are less used because they're pretty big. The rule of thumb is that if you can use either a 256 or a 65536 sized buffer, use it and then make your indexes either bytes or words so that they're self wrapping. That's the way to make the fastest ring buffers.

 

What can you do if you can use either of these sizes? The next best thing is to round up to the next bit boundry and change the way that you wrap your Put and Get Indexes. For example, say that your minimum buffer size is 100 and you don't have the space to allocate 256 bytes for this buffer. The next higher bit boundry from 100 is 128 (which is held in six bits). Here's out first pass at this new approach:

 

 

#define TESTBUFSIZE 128

char TestBuf[TESTBUFSIZE];
byte TestPut = 0;

void PutData(char value)
{
 TestBuf[TestPut++] = value;
 TestPut = TestPut MOD TESTBUFSIZE;
}

 

Ok, the only things that we changed was the TESTBUFSIZE and we made TestPut a byte because 128 fits easily into a byte. To optimize, we can change the way that we wrap the TestPut index.

 

Right now we use MOD, but we're going to change it to masking bits. (This optimization is really faster and cleaner in 6502 assembly language, but you still pick up some speed in C, although the code doesn't look any "faster".)

 

What we want to do is emulate the "self-wrapping" behavior of bytes. They work because when you increment a number, if it gets too big, the highest bit "falls off the end" and the number wraps back to zero. We can do the same thing by masking off the highest unused bits after we increment the value. Here's the code:

 

#define TESTBUFSIZE 128
#define TESTWRAPMASK 0x7F

char TestBuf[TESTBUFSIZE];
byte TestPut = 0;

void PutData(char value)
{
 TestBuf[TestPut++] = value;
 TestPut = TestPut & TESTWRAPMASK;
}

 

The first thing that we did was define TESTWRAPMASK to be the lower seven bits of the byte (7F in Hex or 0111111 in binary). Seven bits gives us a value range of 0-127. Then instead of wrapping the testPut index using the MOD instruction, we can AND it with TESTWRAPMASK to lop off the unused bits. (Again this simplifies the code in assembly language and speeds it up, although the C code doesn't look much different.)

 

So your first choice is to use the byte or word boundry to make a "self-wrapping" index. If you can't do that, see if you can make your buffer size on te next highest bit boundry and use masking to wrap your indexes. Luckily the bit boundries span a bunch of useful sizes, so you can do this a lot. For instance, if you need a 1000 byte buffer, you can size it as 1024 bytes, and the performance gain more than makes up for you "wasting" the extra 24 bytes. There's no way you can get the same performance gain by "spending" that 24 bytes elsewhere, either in memory or code.

 

- One more tip, in the 6502, try to use Page Zero for Put and Get indexes and other buffer controls. This is going to get you a big performance gain. If you have a small buffer and can fit it into page Zero, that's even better, but you usually don't have the space to do that.

 

Addressing Gymnastics:

 

For those rare people who can modify the hardware for performance purposes - you can make the hardware do the index wrapping for you.

 

Here's an example from a typesetter I worked on. It's a good example of how you can "think outside the box" and use some very simple concepts to get some pretty impressive results.

 

The typesetter had an embedded 68000 controller and luckily I had a good rapport with the hardware designer and we worked together to get the system as fast as possible. A page of data 3mb of data. These pages came across as packets of 256 bytes. I could process this data faster than it came in, but I didn't have a lot of spare CPU overhead. We decide that it would be really cool to offload the CPU by DMAing the data directly into the buffer - which uses no CPU cycles. (DMA = Direct Memory Access. In effect, you tell the DMA controller to move the block of data form one location to another and it uses unused buss cycles to move the data without affecting the CPU.)

 

It was a great idea, but here's the problem: The DMA controller just moves the entire block of data and it has no concept of Put Indexes or buffer wrapping or any of that stuff. It's very fast and very dumb at the same time. If you happened to tell it to move the block of data too close to the end of the buffer, you were hosed because it would smear whatever data lived right after your buffer.

 

So here's what we did. We made a special 4mb area in the address space for our ring buffer. We tied the address lines together so that we had four consecutive 1mb blocks of address space that all addressed the same 1mb block of physical memory. In effect I had one 1mb block of memory mirrored into four consecutive 1mb address ranges. In other words, if I started filling up the memory with 4mb, it would overwrite the same 1mb area four times. Usually you fix problems like this, but in this case it was what we wanted. :)

 

I allocated my ring buffer as 4mb long and my indexes were words, but I never actually had to wrap them. Here's how it worked:

 

Suppose I told the DMA controller to move a block of data near the end of the "first" 1mb of the buffer. It would move the block of data into the buffer and some of the data would overflow the buffer and go into the "second" 1mb block. But since the address lines were tied together, it actually "overflowed" back into the beginning of the buffer. In effect, the address lines would do the address wrapping for me and I could "wrap" the index of the DMA controller without actually having access to it. That meant that I could blast a block of data into the buffer and it would self wrap if it went off the end.

 

I could also use another DMA channel to pull the data out of the buffer, which I happened to take in 512 byte blocks. Because of the mirrored address space, this operation was also "self wrapping".

 

Why did I choose to use 4 1mb blocks for the buffer? Because a page consisted of 3mb of data and because I could keep up, I could reset my Put and Get pointers to zero, stream in the entire page and process it, and then reset them back to for the next page. That's the only index wrapping that I did myself - once per page insead of six million times per page.

 

I used a buffer count to tell when there was enough data in the buffer to DMA out my 512 byte block. Again I didn't have to update this count for every byte, I just added 256 to it every time I DMAed in a block and subtracted 512 from it every time I pulled one out.

 

The initial design calculations said we would use about 30% of the CPU time just moving the date from the communications port to the formatting area. After using this technique, we used 0% of the CPU and the data arrived almost instantly.

 

Anyways, that's about it for ring buffers. Next we'll see how they were used in Gauntlet and why they were central to the design.

Link to comment
Share on other sites

  • 2 weeks later...

Sorry, haven't had a lot of time to post lately between work and moving. Here's some random (an hopefully useful) stuff I wrote just before I got into the ring buffer discussion:

 

Page Zero Memory:

 

This was the single biggest challenge that I faced during development. It was a constant problem and it didn't go away until I took some pretty drastic measures.

 

The 6502 has a special area of memory called "Page Zero" memory. It's a block of 256 bytes that can be addressed with a single byte address instead of the normal two bytes. Page zero instructions are almost twice as fast as their normal counterparts, so you try to put all of your time critical variables in page zero. Of course Atari also puts all their time critical variables and they leave you about 50 bytes to use for your own programs. Sheesh, it's like they think they own the machine or something! Of course I ran out of page zero space very early in the project - on the second week, :( It was a constant challenge to find more page zero space or to find usuable alternatives.

 

At first, I searched for variables that were outside of the "safe" zone but weren't actually used by the OS. I wrote a utility that would constantly monitor the page zero memory block and tell me what locations changed. I'd run this in the background while I ran all kinds of applications and see which of the "reserved" locations Atari actually used. Then I had my utility constantly change those locations while I ran all the applications again to see if anything broke. That netted me another 50 or so bytes.

 

Initially I "played nice", but I still needed more room. So I started looking at the functions I didn't need to run Gauntlet and then stole all their page zero locations. I had written my own graphics and sound systems which drove the hardware directly, so those went first. Then the floppy disk system and a few others that weren't used after the game started running.

 

That's where I was when the shareware version shipped.

 

After that I got pretty desperate. I completely rewrote my draw routines to be faster AND use less page zero memory. Took about two months of head banging, but I ended up with something that was really slick. It still wasn't enough, so I completely turned to the dark side. I realized that Atari had no business using ANY of MY precious page zero space! I was my machine now and I didn't need any of their stupid software anyways! So I gutted all the Atari code I could and wrote my own to drive all the hardware directly. I think that the only piece of Atari software that I ended up keeping was the keyboard driver becasue it just wasn't worth rewriting. Outside of that there is no Atari code active when Gauntlet is running and I use all of Page Zero except for the 50 or so bytes that the hardware needs.

 

Random stuff:

 

When I started working on the game, I bought the Atari Assembly Language Cartrige and immediately overflowed the symbol table. So i had to buy their Professional Assembler on disk for $90. Found out that the disk was copy protected, so my first project was to defeat the copy protection using a hex editor since I didn't want to risk using my $90 disk and ruining it. Good thing too, because I wore out about five assembler disks over the course of the project. Ah, those were the days!

 

When objects are cloaked, they are drawn on the screen using all black pixels. Collision detection still works because they erase pixels on the object they collide with and they detect the non-black pixels of other objects. A lot of the ships have black pixels as part of their fonts so they look good and the collision detection still works. I didn't figure out that trick until pretty late in development so the earilier ships I designed are "uglier" than the later ones.

 

Unfortunately, cloaked objects can't damage each other, but it's not really a problem since you can't see them "cheating". :) Once in a while you'll see two of them uncloak on top of each other and promptly explode, but that't the price you pay for being sneaky.

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...