Advanced Arcade Techniques
Maze games achieved the height of popularity with the debut of eat-the-dot games like Pacman and Ms Pacman. They weren't the first eat-the dots games: that honor goes to a car game called Head-on. They were, however, the first games to transpose the usual open field chase or pursuit games to the narrow confine of maze corridors.
Topographically, a maze is merely a network of interconnecting paths. These pathways constrain the player's movement. In a sense each individual section of the maze gives the player a set of movement rules. The passage walls that are open allow the player to reach the next section, while the closed walls block movement in other directions.
The maze can be divided into a number of small blocks, each the height and width of the passageway. Depending on the graphics mode, each of these blocks could assume the size of a character. That way each character becomes one of the blocks in the maze. The characters can be open blocks, with various combinations of open exterior walls, or they can be floors and ladders for use in a climbing, jumping arcade game. While most people do not think of games like Donkey Kong and Apple Panic as maze games, they too require a set of movement rules to keep the player confined to floors and ladders.
The instructions that guide a player about the maze can be very complicated, or they can be quite simple. They can take thousands of bytes or just several hundred. Naturally, each individual block needs to be a part of the playfield and requires one byte per block. If we drew out maze in graphics one characters (eight dots by eight rows), we could have a maze twenty blocks wide by twelve rows deep. That takes 240 bytes of memory. Next we need instructions to tell the player if he can move up, down, left or right from the center of the block where he is. That could take as many as four bytes per block. These could all be placed in tables so that if we knew which block we were in, we could index into each table look up the legal moves for that block.
Storing a zero for an open pathway and a one for a closed pathway doesn't use a memory location to its capacity. Only the lowest bit is used. If we could combine all four directions into one byte, with each direction using one bit, we would only use a fourth as much memory and still have half of the byte left. With Left using the lowest bit or 0th bit, Right the first bit, Up the second, and Down the third, we can test each of these bits by ANDing with a MASK that contains a I bit in the bit position we wish to test and 0 bits everywhere else.
LEFT CLOSED 0000 0001 RIGHT CLOSED 0000 0010 UP CLOSED 0000 0100 DOWN CLOSED 0000 1000Thus to test if the up direction is closed we AND the instruction byte with $04. In the following example only the right direction is open.
0 0 0 0 1 1 0 1 INSTRUCTION BYTE 0 0 0 0 0 1 0 0 AND #$04 _______________ 0 0 0 0 0 1 0 0 RESULT IS POSITIVE IF UP DIRECTION IS CLOSEDWe could set a flag for the gates that we find open. If we did the above test and found the result positive, or the up direction closed, we could set FLAGU = 1. The four flags FLAGU, FLAGD, FLAGL, and FLAGR, would be set to 0 if found open and to I if closed.
Since we need to design a character set to visually show which walls are open and which ones are closed, it would be beneficial to mirror the byte value of the block's instruction byte. Thus, if a character had the only the left wall closed, its instruction byte would have a value of one. Therefore, we will design the first character in the character set to have only the left wall closed. The Oth character has no walls closed, and the fifteenth character in our table has all four walls closed. The instruction byte that reflects this has a value of 15 ($0F). Each of the characters in our set is shown below.
The playfield consists of twelve rows, each with twenty of these characters. As a whole they make up an entire maze. A player in the top left hand block in the maze is in the Oth position of the character data that ANTIC uses to generate the playfield or maze. If the player moves one row down to position XB,YB (0, 1) it is at the twentieth position of the character data; and one position to the right (1,1), the twenty-first position position in the data. This can be formalized as:
BLOCK = (YB*20) + XB
If we index into the screen data, SCREEN,Y where the Y register contains the value of the character at the player's position. That number is the same as the sum of the individual instruction bits for legal movement in each of the four directions. The fact that only one table is needed for both the screen data and the legal movement instructions is not a coincidence. It is simply a clever way to condense data.
The design of any maze game should include a simple premise with a simple set of rules. The object of PacMan is to eat all of the dots on the maze floor in order to advance to the next level. The object of the maze game is for the player's letter to chase two higher letters in the alphabet. The player catches the next higher letter in order to become that letter as it advances toward the letter Z and a harder maze. just as the four ghosts are the adversaries in PacMan, the red minus sign is the adversary here. It constantly pursues your player and if it catches it, your letter value decreases by one. Thus, the game begins with the letter A chasing letters B and C. If it catches the B, it becomes a B and continues chasing a C and D. If the minus sign catches your player at that point, it reverts back one letter to the letter A.
There is no point scorekeeping system for this game. The progress you make during the game becomes a visual scorekeeping system. The goal is to progress forward, not to gain points. If we were to award 100 points for each letter gained, and 50 points for each letter lost, players might prolong the game indefinitely, advancing several letters than losing a few.
All the players move at exactly the same speed. This forces strategic play, since you can't catch the fleeing letters by simply tailing them. The game must appear winnable, so we never make the player start the game over when he is caught by the minus sign. The game would become frustrating if a player advanced to the letter T and reverted back to an A just because the minus sign caught him repeatedly. Beginners would also find the game frustrating if the minus sign caught them and put them in another random position in the maze, just as they were about to trap a letter.
The maze game code is much too long to run within the time frame provided by vertical blank. Since I had tested my theory of maze game logic with a single player within the vertical blank period, I decided to place the remainder of the game code outside the vertical blank time, but sychronized with it. Normally code entirely within VBLANK runs in a endless loop (FOREVER JMP FOREVER) and only leaves this loop during the interrupt period. When your code runs outside VBLANK it runs until the VBLANK interrupts it, then executes the code in the interrupt routine. At the end of the vertical blank period it returns to where it left off in the code. With the 6502 running at 1.8 MHz a moderately short program is likely to cycle through twenty or thirty passes between VBLANK interrupts. The only solution to synchronize the two pieces of code is to allow the code to run once then wait in a tight loop testing for some flag that can only be set if the program reaches the vertical blank routine. This is precisely what we did. Our VBLANK routine sets a flag call VBFLAG = 1. The main program code sits in an endless loop testing this flag. It can Only exit the loop to the beginning of the program code when VBFLAG = 1.
FOREVER LDA VBFLAG ;TESTFLAG CMP #$01 BNE .1 JMP LOOPM ;EXITS WHEN VBFLAG=1 .1 JMP FOREVER
Player MovementThe player is joystick guided. When a player orders his player to move in a certain direction a number of things happen. First the program checks if the player is at the center of a block and if so checks which movement directions are legal. For example if a player initially started at the top left corner, it can only move right or down. If the player began moving down, he could no longer move left, but only back in the direction he came from, at least until he reached the center of the next block. So once a player begins moving the legal direction flags must be reset to reflect the legal moves between adjacent blocks. Second, since player movement should be automatic once a player begins moving in the desired direction, a auto flags denoted as DR, DL, DU, and DD must be set. The auto flag is on when set to I and off when set to 0. So if our player is heading downwards, DD=1.
Joystick SubroutineThe joystick subroutine is similar to other joysticks routines except in its treatment of diagonals. Only a single bit has to be tested if it is off to determine the desired direction for the up, down, left and right positions. But in the four diagonal directions two bits are off. Since we can only move horizontally or vertically in the maze, a decision must be made to which direction the player means. If a player were moving right and he wanted to turn up at the next intersection he would likely point his stick diagonally up and right. Since he is travelling right in an automatic mode, he probably doesn't mean to order his player right but instead up. So if the right auto flag is on DR=1, it means that he is already travelling right and intends to go up the next time he reaches a block that allows him to move in that direction. Similarly, if the is moving up, DU=1 so he would probably intend to go right at the next intersection.
If you look at the flow chart for the joystick subroutine, you will notice that in addition to setting auto flags and resetting legal flags for each of the four primary joystick directions, a flag called INHIBIT is set sometimes in the diagonal test. This is necessary because if for example we had an up and right diagonal and we were traveling the logic would say that we want to test for a right move. Since the right bit is off, the apropriate flags will be set correctly, but when it reaches the up bit test that bit is also off so that it will try to set latches for that direction too. But if it has to pass a INHIBIT = 1? test, it will prevent the program from reaching the second bit position test, The INHIBIT flags are only set for situations where the code will branch to the right and left bit tests first. Code that branches to either the up or down bit tests can not reach, the left and right bit tests to cause problems.
Whenever a joystick command is given to change the player's direction, the joystick routine checks to see if the player can move in that direction from its current position. It checks to see if the legal move direction flag is open or closed in the desired direction. If it is open (0), it shuts off all the auto flags except the one for the new direction. Finally it resets the legal move flags so that it can only go forward or reverse between blocks, and then moves the player one unit forward. If you try to command the player to move in a direction that it can't go, it will keep on going forward automatically if it can.
Auto Mode CodeThe auto mode is what makes the joystick routine particularly smooth and responsive. If it didn't exist, you would have to exert more effort to get your man to steer properly. Besides always needing to push the joystick one way or the other, you would have to be careful to reach the exact center of a block before successfully negotiating a turn.
The auto mode code simply checks to see which auto flag is on and checks if it is legal to continue moving in that direction. If not the player stops until it receives a new command.
Legal Move SubroutineA subroutine appropriately called LEGAL determines in which directions the player is allowed to move. You input the player # in the X-register and its current X and Y positions, and it sets the legal move flags FLAGL,X, FLAGR,X, FLAGU,X, and FLAGD,X for the appropriate player. It is a two step process. First it decides if the player is at the center of a block. Each block is 8 pixels wide by 16 pixels deep. For a player to be at the center of a block it must be at an exact multiple of 8 horizontally and an exact multiple of 16 vertically. The player's position is in player-missile coordinates. The top left corner of the maze is at location 48,32. And the 8 scan line high pla'yer is initially 4 scan lines lower in order to center it in a 16 scan line higher to center it in a 16 scan line high
block. We can get two values TEMPX and TEMPY by subtracting 48 and 36 from the player's horizontal and vertical positions respectively. The coordinates of the block XB,YB that the player is currently in are calculated as follows.
TEMPX = XP-48 XB = TEMPX/8 TEMPY = YP-36 YB = TEMPY/16 BLOCK = YB*20 + XBIt is quite simple to determine if the player is at the center of the block by checking the values in TEMPX and TEMPY. If any of the first three bit positions in TEMPX containing anything (a remainder) we have a value that is not an exact multiple of 8. You can AND with #$07 to check the first three positions of TEMPX for a non zero value. For example:
00001001 TEMPX=#$09 00000111 AND #$07 ________ 00000001 RESULT positiveSimilarly we can AND with #$0F to test the first four positions of TEMPY for a non-zero value or a remainder.
If the above two tests prove that we are at the center of the blocks, we can test the individual direction bits for the character number for that block. We open all of the flags before testing each direction. The address to the screen data or maze map has previously been put in zero page at locations MAPL, MAPH. The code for testing if the block's left direction is open is shown below.
LDY BLOCK ;BLOCK WE TEST IS USED AS INDEX INTO TABLE LDA (MAPL),Y ;GET VALUE OF BLOCK AND #$01 ;TEST LEFT DIRECTION BEQ ;IF RESULT 0 THEN LEAVE FLAG OPEN LDA #$01 ;SET FLAG CLOSED STA FLAG,X ;X REG. CONTAINS PLAYER .2 LDA (MAPL),Y
Computer Controlled PlayersThe three other players, the two letters and the minus sign, are computer controlled. The two letters must continually flee from the player's joystick controlled letter, while the minus sign homes in on the position of the player's letter.
Each of the computer controlled sprites must determine the direction or directions that they wish to travel relative to the player's letter in order to either seek it or flee from it. If the playfield were completely open the logic would be rather simple. The minus sign would approach the player in one of two random directions, horizontally or vertically until it was even in that axis, then move towards it in along the opposite axis. A letter fleeing from the player's letter would would move away from the player along one axis until it reached the playfield boundary, then move towards the corner opposite that of the player's letter. Of course it would get trapped in the corner.
Mazes have lots of corners where fleeing letters and pursuing minus signs could become trapped. A pursuing player that found itself in a parallel corridor would closely follow your motions as you moved back and forth safe but next to it. There might not even be an escape since you and it would reach the next turn at exactly the same time. On the other hand, a player that fled would often get stuck in a corner awaiting for you to become parallel with it along one corridor before fleeing along the other. As many maze game designers will testify, the only practical solution is to force both the pursueing and fleeing computer controlled players to always move forward in the corridors, never in reverse.
The logic needed to keep a player moving forward is not always simple, for sometimes it disobeys the general rules given to each type of player to either pursue: or flee from the joystick controlled letter. In general, if a player is at a decision making point, the center of a block, it must decide if there is a direction available, other than the direction it is traveling in or reverse. If it doesn't have a choice it just continues in its current direction. But if there is a choice it must decide if one of those directions is a better choice than the direction it is currently travelling.
A set of four relative direction flags, RELL,X, RELR,X, RELU,X and RELD,X, can be set to indicate whether the computer controlled letters and minus sign should move towards our letter or not. Each of their X and Y coordinates are checked against the joystick controlled letter's X,Y coordinate. Obviously if the player's letter is to the left of the minus sign, the minus sign would want to move left so RELL,X is set to 1. The minus sign is player #3 (X-register = 3) so that RELL,3 = 1. The minus sign would also like to move up so that RELU,3 =1. On the other hand, the letter B, which has a similar relative positioning as the - sign in the diagram below, wants to escape and move in the exact opposite direction. The testing algorithm is the same for both types of players but instead of setting RELU,1=1 the letter B would like RELU,1=-1. If it wants to go the opposite direction it is much simpler to just set the flag for the opposite direction than to test for negative relative flags. ThusRELD,1=1 is the exact equivalent and the letter subsequently flees.
The next step is to decide if any of these relative direction flags match any of the legal direction movement flags. For example if RELL,1=1 and FLAGL,1=l then either the fleeing letters or the pursuing minus sign can take the turn. If the move is possible it sets a move flag MFLAGL,1=1 for that direction. The reader at this point is probably muttering, "Not another flag!" It is actually necessary because there can be more than one possible direction to move. In that case the program will have to choose one direction to move. There is a counter called NUM that increments each time it sets a move flag. If NUM >1 we will have to choose a direction randomly. The choice of directions are arranged in pairs left-right and up-down. Depending on the random number value, the test will be on either the vertical or horizontal direction first. There is no danger that the program will miss finding a set move flag if it branches past the horizontal axis since it only occurs when NUM=2. The two flags that have been set can't be both left and right since a fleeing or pursuing player would choose only one direction to move in any axis. Once it determines which move flag has been set it resets the auto flag and moves in the appropriate new direction.
There are cases especially in corners where the relative flags and the legal flags don't match. In these cases where the player would become trapped, the player must be forced to make the only legal turn even if it means moving the wrong way towards the joystick controlled letter. I called the subroutine CORECT. The routine, realizing that the player was moving forward when it became stuck, tests which auto flag is set and compares it with the legal move flag for that direction. If it can continue moving forward it goes to AUTOP, the automatic mode for the player, and bypasses the code to force it to make the corner against its will. However, if it becomes stuck, it, it tests the legal move flags for the two directions along the opposite axis. For example if it were heading right and became stuck it would check FLAGU,X and FLAGD,X. There is no need to check the FLAGL,X because the player is not allowed to reverse itself. Once we have determined the new direction we reset the auto flags and move it one pixel in that direction.
Player CollisionsEventually, either the joystick controlled letter is going to catch the next higher letter or the minus sign is going to catch it. When any two players overlap a collision register is set which we can test. If the joystick controlled letter catches the next higher letter it must become the next higher letter and the letter that was just caught becomes a letter two higher than it was. Thus letter A which catches B becomes a letter B, letter C remains the same, and letter B becomes a D. The caught letter must be repositioned somewhere else on the screen preferably at the opposite end of the
screen. If our hero is at the bottom left of the maze, we put the new letter at the top right. The four possible placement positions aren't random but in specific spots with specific starting directions. The actual repositioning is accomplished in a subroutine called PLACE. It is very simple and clearcut routine. It does randomize the left-right starting positions for the bottom two locations because occasionally two letters are caught almost simultaneously in either of the top two outer pathways.
A collision of your letter with the minus sign results in a decrement of one letter except when you are the letter A. In fact all three letters are decremented one letter so that if you are the letter C chasing a D, after the collision with the minus sign you are a B chasing a C in exactly the same position. Although the minus sign is repositioned at the bottom of the screen on the opposite side of the maze from the player, it was felt that penalizing your player by repositioning it at the top corner was frustrating to beginners who got caught all too often and felt that it was like starting over.
Each of the three lettered players has a variable that points to which letter it currently is. POINTO refers to the joystick controlled letter, and POINT1 and POINT2 refer to the two fleeing letters. This variable also controls which letter shape is taken from the shape table during the PLOTSET subroutine. The shapes are arranged in numerical order. Shape #0 is the minus sign. The 26 letters follow in sequential order. Two blanks are placed at the end so that when the player reaches Y it is chasing only a Z and one invisible player. When the player reaches Z there are two invisible players still on the screen,
While it is possible to test whether player #1 or player #2 has collided with our joystick controlled letter, you can't be sure that the collision is with the next higher letter because that letter alternates between the two players. Therefore a test comparing the values of the two colliding letters must also be done to determine if the two letters are one apart. Say our player is an E (POINTO=5) and it collides mistakenly with player #1 that currently is a G (POINTI=7). The test POINT1-POINTO gives a value greater than 1. Therefore we ignore the collision. But if we collide with player #2 that is an F (POINT2=6) then POINT2-POINTO is equal to 1. We then increment POINTO our player to a F and increment POINT2 twice to H. Now we have a F chasing a G and H.
Second Maze Level
Eventually the player reaches the letter Z in the game. There is a four second pause before a new maze appears. Setting up the screen is simple. The 220 bytes of character data for the second screen is moved into screen memory starting at location SCREEN. The zero page pointers MAPL and MAPH, which are used by the subroutine LEGAL to obtain the value of a particular block, are also set to point to the character data at DSCREEN2.
LDX #$00 SLOOP1 LDA DSCREEN2,X ;LOAD NEW MAZE DATA STA SCREEN,X ;STORE ON SCREEN CPX #$F0 ;DONE? BNE SLOOP1 ;NEXT BLOCK LDA #DSCREEN2 ;SETUP ZERO PAGE POINTERS TO DATA STA MAPL LDA /DSCREEN2 STA MAPH
The layout of the second maze was designed to be quite harder. The only open pathways are along the bottom and two sides. The interior pathways are designed so that if you don't concentrate and choose your pathways with care, a wrong turn will nearly always give the pursued letter a bigger lead.
It is possible to add many more levels to the game. If there is only a few you could duplicate the above code and by testing which level you have just finished branch to the appropriate block of code to put the maze character data in screen memory. More advanced programmers should set up an indexed table for the hi byte pointer to each 256 byte block of character maze data if there are more than four maze levels.
Pause FeatureA pause game feature is important to most arcade games, especially one in which the game might last more than several minutes. The problem with an Atari is that game code within the vertical blank executes 60 times per second regardless of any pause control in the main program loop. If the game is to stop, the pause code must branch past the game code within vertical blank to XITVBK. In our case, if we branch past the VBLANK flag which we use to syncronize the non-vertical blank code, the rest of the game action will also stop. The pause code shown in the flow chart below is placed at the very beginning of vertical blank.
Download ALPHMAZE.EXE (Executable program)
Download ALPHMAZE.OBJ (Object code)
Download / View ALPHMAZE.LST (Assembler listing)
Download / View ALPHMAZE.S (Source file)
Download / View ALPHMAZE.RAW (As printed in book)
Tank GameA tank duel between two or more tanks was one of the first few classic/games developed for coin-op play in the arcades. It was quickly translated to the Atari 2600 game system under the name Combat, and was supplied with the first four to five million game systems. Regrettably, it was never rewritten for their home computers. While the cartridge had some interesting variations like Tank-Pong, in which the shells bounced off walls for quite some distance, it was unrealistic in that the tank turret could only fire in the direction the tank pointed.
Basic Design of GameOur game, Tank Battle, is designed to be much more realistic. The tanks, which can turn and drive in eight directions, are equipped with rotatable turrets so that they can fire in directions other than the one that they are traveling. The terrain features or barriers, which are useful for hiding behind, can be blown away by tank fire. It takes two shots to completely fracture one of the terrain blocks. Tanks that are blown away are immune to enemy fire for several seconds upon reappearance. This is necessary since the tank always reappears in the same position and it would be only fair to allow the player to move his tank or fire back at a tank waiting in ambush.
Regrettably, the control system is a compromise. Game design would be so much easier if joysticks had two buttons. The single button serves a dual function for turning the turret and firing the gun. When the button isn't pressed, the tank is in the steering-drive mode. Pushing to the left or right rotates the tank, and pushing forward or back moves the tank in those directions. Since the tank can't penetrate barriers, it often needs to be backed up to turn away from the obstacle. Pressing the button puts it into the turret-fire mode. The tank can still be driven forward and backward, but pushing the stick left or right rotates the turret counterclockwise and clockwise respectively. The gun is fired when the button is released.
The use of two players for each tank, one for the body and one for the turret, thwarted any attempts to make it a four player game. We're not saying that it couldn't be done, but we could just see the flickering that would occur if two tanks using the same players ended up on the same scan lines. Actually, now that we think about it, it could have been done if we sacrificed the distinct second color of the turret. The turret might not show clearly, but it would reduce each complete tank to one player. Now you would need sixty-four tank shapes, one for each combination of tank direction and turret direction. As it is we use eight tank shapes and eight separate turret shapes for each of our eight joystick-controlled directions.
Only one missile can appear on the screen at a time for each tank. Each missile continues along its directed path until it exits the playfield or reaches either the enemy tank or a blow-away wall. Since the missile is armed upon pressing the trigger, it becomes increasingly important to be able to not only adjust the turret's direction but also the tank's forward and backward position before the shell is actually fired by releasing the trigger. The inablity to reload and fire before the previous missile completes its trajectory may be quite realistic in tank warfare, but can be a liability in an arcade game. Perhaps this forces the game to transcend the quick reflexes genre to one of strategic play.
The game was virtually designed to run in Deferred Vertical Blank. While collisions, explosions, and missile movement are performed for both tanks every VBlank cycle, reading the joystick and the actual calculation and updating of tank and turret positions alternate for each tank every VBlank cycle. Tank #0 is updated on even cycles and tank #I is updated on odd cycles. This was necessary for it appeared that there were far too many instructions to fit within the Deferred Vertical Blank period.
Updating the tank's position is one of the more intriguing problems in this game. Calculating the tank's next position based on the direction that it is moving is quite straightforward. The formulas are:
PLAYERH = PLAYERH + HOFFS(NEWD)
PLAYERV = PLAYERV + VOFFS(NEWD)
where NEWD is the tank's new direction.
Tank Collision with PlayfieldThe tank obviously has to be prevented from moving into a wall. We wanted to avoid the unsightly bounce that occurs in many games that test illegal positions using player-playfield collisions. The problem is that you can't test the collision until you actually move there, but once you are there you have to move back from the illegal position, or in this case, away from the wall.
The solution is to test for a collision at the tank's next calculated position before the tank is actually moved there. This can be accomplished exactly the same way collisions are detected when rastering shapes on the screen, by ANDing the player byte against the playfield byte beneath it. If any two bits overlap, the result is a positive number. Remember that to do the comparison, the tank doesn't have to be physically where it is supposed to be. You need only calculate which player and playfield character bytes intersect and compare the data for an overlap.
By calculating the tank's new position NEWH, NEWV, the offset into the character XOFF, YOFF can be determined simply by ANDing the position with #$07. This is equivalent to the expression NEWH -8 (INT(NEWH/8)). It masks off the higher bits above 8 so that only the remainder is left. The actual character involved in the intersection also has to be obtained. Its position is at CHRX, CHRY and is obtained by dividing the new player position NEWH, NEWV by 8 in each axis. Its position in memory is CHRHI*256 + (CHRX +(20*CHRY)).
When the player shape is offset horizontally into the character, part of its shape is in that character and the rest extends into the next character to the right. If we are going to make a comparison in the data, we will need to obtain data from that character also. The eight bits that we need to compare are beneath the player byte but span the two different character bytes. If we look at the diagram below, out player shape is offset into the first character by five bits. Thus, three bits of the first character byte need to be combined with five bits from the adjacent character. Once we have a byte we can AND the character data with that of the tank data. The data is tested for each byte of the vertical overlap or until we detect a collision. If we don't detect a collision, we clear the carry before exiting the subroutine. However, if we do, we set the carry before exiting. Upon return we need only perform a BCS instruction to determine if the desired move is indeed legal.
0th byte overlapped character data $FF intersects tank data $38
1st byte overlapped character data $FF intersects tank data $C6
LDA (P0TMP1),Y $38 1 1 0 0 0 1 1 0 AND BYTE $FF 1 1 1 1 1 1 1 1 ________________________________________________ RESULT >0 (Collision) $38 1 1 0 0 0 1 1 0
Updating Tank Position and RotationThe two tanks are updated on different VBlank cycles. While it is true that tank #0 is updated on even cycles and tank #1 is updated on odd cycles, there is a rest of two additional cycles before they are updated again. Essentially, we are in a four-cycle loop with two null cycles. If you look at the code beginning with the label UPDATE, you will notice that we loaded the Accumulator with the clock timer value and then ANDed it with #$02. If you look at the bit patterns for the numbers 0-3 and perform the operation, you will get the following:
ACCUM. 00 01 10 11 AND #$02 10 10 10 10 ______________ 00 00 10 10Notice that we obtain non-zero values for the second and third cycles. We could branch on a non-zero test and skip updating the tank on these two cycles. Incidentally, the clock doesn't need to be reset every four cycles. It continues to count to 255. However, as far as the last two bits are concerned, 4 is the same as 0, and 5 is the same as 1, etc. We can then determine which tank to update by ANDing the clock RTCLOC with #$01. The value is either going to be zero or one, and that can be placed in the Xregister to obtain the appropriate tank's variables.
There are several more tests that have to be performed. Obviously, if the tank is dead, we don't have to worry about updating it. Next we need to decide if we are in the turret mode. This is important because we need to distinguish between a button that hasn't been pressed and one that has been just released to fire. In the latter case, the turret mode is set so it branches past the first button test that would have shunted it to code to move or rotate the tank, and instead reaches the second button test. If the button is actually released, it means that the button had just been released and it trips the gun to fire. The turret mode is turned of f in that event. However, if the button is still held down it branches to ROTATE where a new turret direction is calculated every fourth jiffy.
Tank RotationsThe calculations to rotate either the tank or turret are very straightforward. The new direction is either incremented or decremented depending on joystick direction. Both TURRETD, the turret direction, and PLAYERF, the tank direction, have values between 0 and 7. These direction values are used to obtain the correct shapes to plot in the player-missile area of memory for both tank and turret. The tank and turret rotations are coupled such that the turret's position relative to the tank is constant as the tank is turned. If the turret points out the front of the tank, it must remain in that position when the tank turns. We update both the tank and turret directions in the code when turning the tank. You'll notice that we are always keeping temporary values for movement, including new tank and turret directions. Since the act of rotating the tank could cause a collision with the wall, it may be necessary to ignore the new values and simply not rotate the tank or turret.
Missile or Tank FireThe beginning section of the VBlank code concerns collisions between missiles and playfield, missiles and tanks, and between two tanks. When a missile strikes the playfield it has to distinquish between border characters and barrier characters. The character set is as follows:
0 -Blank 1-6 -Border characters 7 -Unused 8-15 -Walls (in pairs)The missile track ends when it hits a border character. If you choose to change the maze so that border characters aren't used, you will need to remove the comment field from some of the statements in lines 2540 to 2760. These statements actually test boundary conditions rather than simply character numbers from 1 to 6 to determine if the missile reaches the border. When the missile strikes a character that makes up a wall, it decrements it if it is an odd numbered character, or erases it if it is an even numbered character. The characters are set in pairs. The higher numbered character is a complete shape while the lower numbered character is the fractured shape.
Tank ExplosionsOf course, it is very likely that the player's missile will hit its intended target, the other player's tank. If it does, the hit tank begins to explode. In order to prevent the tank from reinitiating its explosion by another closely fired missile, we need to test if CYCLES >0. We also need to determine if the tank is immune from enemy fire just after it reappears. Again we test for a value IMMUNE >0. Both CYCLES and IMMUNE are counters that count down every VBlank.
The tank's explosion pattern is a series of horizontal lines that rapidly expand vertically outward along the tank's 8-bit-wide player band. The tank shape is replaced by a series of horizontal lines. Their vertical positions are stored in two arrays, DZAP and UZAP. These two arrays each store the vertical positions of eight of these horizontal lines. The lines are moved at different rates by adding or subtracting different values to their vertical positions. For example, if we consider the eight lines in the DZAP array, they all begin initally at X=80. Then DZAP(INDEX) = DZAP(INDEX) + INDEX, where INDEX ranges from 0 to 7 for the eight values in the table. The higher values in the table move the fastest downward. Eventually all of the lines exceed the screen boundary at #$CO and the explosion ends. Similarly, the eight lines in UZAP move upward as UZAP(INDEX) = UZAP(INDEX) -INDEX. They, too, eventually exceed the screen boundary at #$20 and the explosion sequence ends.
The collision routine that detects collisions between the two tanks has to be more selective than usual. If we aren't careful, the debris from the exploding tank could blow up the other tank if it had a similar horizontal position. This can be avoided by checking the explosion cycle counter to see if it is greater than zero.
The blown up tank is put back on the screen once the CYCLE counter runs out after four seconds. The IMMUNITY counter is then set to 255 jiffies or four seconds so that the enemy tank can't sit in ambush. It does give the defeated tank a slight edge for several seconds but this can be compensated by having the other tank stay out of the line of fire.
The game is timed to last three minutes. This is set up initally as two minutes and sixty seconds, and can be changed to suit the player. The timer operates in the decimal mode. Both the ones and tens digits for seconds are stored in the lower and upper nibbles of the variable SECONDS. The nibbles are separated and converted into character data before being stored in score screen memory at locations LINE3+10 and LINE3+11. The value for minutes, which is in the lower nibble only, is easier to obtain. It is eventually stored at LINE3+8. The game ends, GAME=0, when the timer reaches zero. ENABLE, a VBI flag, is also set to zero at that time so that the entire VBI routine is skipped when the game isn't in progress.
Download TANK.EXE (Executable program)
Download / View TANK.LST (Complete assembler listing)
Download / View TANKMAST.SRC (Master source file)
Download / View TANK.EQU (Source file)
Download / View TANKDATA.SRC (Source file)
Download / View TANKSUBS.SRC (Source file)
Download / View TANKVAR.SRC (Source file)
Download / View TANKVBI.SRC (Source file)
Download / View TANK.SRC (Source file)
Download / View EQUATES (Source file)
Return to Table of Contents | Previous Chapter | Next Chapter