.StringToNumber LDY #0 \ We want to work through the input buffer, converting \ each character in turn from an ASCII digit into a \ number, so set an index in Y to work through the \ buffer, one ASCII digit at a time LDX #0 \ Each pair of ASCII digits gets converted into a value \ that will fit into a single BCD byte, which we store \ in-place, so set an index in X to work through the \ buffer, so we can store the resulting BCD number one \ byte at a time (i.e. two ASCII digits at a time) .snum1 \ We now fetch two digits from the input buffer and \ convert them into a single BCD number, remembering \ that the input buffer is stored as an ascending stack, \ so the digits on the left of the stack (i.e. those \ that were typed first) are lower significance than \ those on the right of the stack (i.e. those that were \ typed last) \ \ Effectively the stack is little-endian, just like the \ 6502 processor \ \ The calls to DigitToNumber will backfill the input \ buffer with &FF if we are reading from the last four \ characters of the input buffer, so the final result \ will have four BCD numbers at the start of inputBuffer \ (from inputBuffer to inputBuffer+3), and the rest of \ the buffer will be padded out with four &FF bytes \ (from inputBuffer+4 to inputBuffer+7) JSR DigitToNumber \ Set T to the numerical value of the character at index STA T \ Y in the input buffer, which is the low significance \ digit of the number we are fetching, and in the range \ 0 to 9 INY \ Increment Y to the next character in the input buffer JSR DigitToNumber \ Set A to the numerical value of the character at index \ Y in the input buffer, which is the high significance \ digit of the number we are fetching, and in the range \ 0 to 9 ASL A \ Shift the high significance digit in A into bits 4-7, ASL A \ so A contains the first digit of the BCD number ASL A ASL A ORA T \ Insert the high significance digit in T into bits 0-3, \ so A now contains both the first and second digits of \ the BCD number STA inputBuffer,X \ Store the BCD number in-place at index X INX \ Increment the result index in X to move on to the next \ BCD number INY \ Increment the buffer index in Y to move on to the next \ pair of digits CPY #8 \ Loop back until we have converted the whole string BNE snum1 \ into a multi-byte BCD number RTS \ Return from the subroutineName: StringToNumber [Show more] Type: Subroutine Category: Text Summary: Convert a string of ASCII digits in the input buffer in-place into a multi-byte BCD numberContext: See this subroutine on its own page References: This subroutine is called as follows: * MainTitleLoop calls StringToNumber.DigitToNumber LDA inputBuffer,Y \ Set A to the ASCII digit from the input buffer that we \ want to convert CPY #4 \ If Y < 4 then jump to dnum1 to skip the following BCC dnum1 \ Y is 4 or more, so we set this character in the input \ buffer to &FF so that as we work through the buffer in \ the StringToNumber routine, converting pairs of ASCII \ digits into single-byte BCD numbers, we backfill the \ buffer with &FF PHA \ Set the Y-th character in the input buffer to &FF, LDA #&FF \ making sure not to corrupt the value of A STA inputBuffer,Y PLA .dnum1 CMP #' ' \ If the character in the input buffer is not a space BNE dnum2 \ then it must be a digit, so jump to dmum2 to convert \ it into a number LDA #'0' \ Otherwise the character from the input buffer is a \ space, so set A to ASCII "0" so we return a value of \ zero in the following subtraction .dnum2 SEC \ Convert the ASCII digit into a number by subtracting SBC #'0' \ ASCII "0" RTS \ Return from the subroutineName: DigitToNumber [Show more] Type: Subroutine Category: Text Summary: Convert a digit from the input buffer into a numberContext: See this subroutine on its own page References: This subroutine is called as follows: * StringToNumber calls DigitToNumber
Arguments: Y The offset into the input buffer of the digit to convert
Returns: A The numerical value of the digit (0 to 9), where spaces are converted to 0.Print2DigitBCD PHA \ Store A on the stack so we can retrieve it later LSR A \ Shift the high nibble of A into bits 0-3, so A LSR A \ contains the first digit of the BCD number LSR A LSR A JSR PrintNumber \ Print the number in A as a single digit PLA \ Retrieve the original value of A, which contains the \ BCD number to print AND #%00001111 \ Extract the low nibble of the BCD number into A JMP PrintNumber \ Print the number in A as a single digit and return \ from the subroutine using a tail callName: Print2DigitBCD [Show more] Type: Subroutine Category: Text Summary: Print a binary coded decimal (BCD) number using two digitsContext: See this subroutine on its own page References: This subroutine is called as follows: * PrintLandscapeNum calls Print2DigitBCD * SpawnSecretCode3D calls Print2DigitBCD
Arguments: A The number to print (in BCD).GetNextSeedAsBCD JSR GetNextSeedNumber \ Set A to the next number from the landscape's sequence \ of seed numbers \ We now convert this into a binary coded decimal (BCD) \ number by ensuring that both the low nibble and high \ nibble are in the range 0 to 9 PHA \ Store A on the stack so we can retrieve it below AND #%00001111 \ Extract the low nibble of A, so it's in the range 0 to \ 15 CMP #10 \ If A >= 10 then set A = A - 6 BCC rbcd1 \ SBC #6 \ This reduces the number in A to the range 0 to 9, so \ it's suitable for the second digit in a BCD number \ \ The subtraction will work because the C flag is set by \ the time we reach the SBC instruction .rbcd1 STA lowNibbleBCD \ Store the low nibble of the result in lowNibbleBCD PLA \ Retrieve the original value of A that we stored on the \ stack above AND #%11110000 \ Extract the high nibble of A, so it's in the range 0 \ to 15 CMP #10<<4 \ If the high nibble in A >= 10 then subtract 6 from the BCC rbcd2 \ high nibble SBC #6<<4 \ \ This reduces the high nibble of the number in A to the \ range 0 to 9, so it's suitable for the first digit in \ a BCD number \ \ The subtraction will work because the C flag is set by \ the time we reach the SBC instruction .rbcd2 ORA lowNibbleBCD \ By this point A contains a BCD digit in the high \ nibble and lowNibbleBCD contains a BCD digit in the \ low nibble, so we can OR them together to produce a \ BCD number in A, which we can return as our result RTS \ Return from the subroutineName: GetNextSeedAsBCD [Show more] Type: Subroutine Category: Maths (Arithmetic) Summary: Set A to the next number from the landscape's sequence of seed numbers, converted to a binary coded decimal (BCD) numberContext: See this subroutine on its own page References: This subroutine is called as follows: * CheckSecretCode (Part 1 of 2) calls GetNextSeedAsBCD * SpawnSecretCode3D calls GetNextSeedAsBCD.lowNibbleBCD EQUB 0Name: lowNibbleBCD [Show more] Type: Variable Category: Maths (Arithmetic) Summary: Storage for the low nibble when constructing a BCD seed number in the GetNextSeedAsBCD routineContext: See this variable on its own page References: This variable is used as follows: * GetNextSeedAsBCD uses lowNibbleBCD.SpawnSecretCode3D LDA #%10000000 + 0 \ Set bit 7 of printTextIn3D so we print the landscape's STA printTextIn3D \ secret code in 3D text when we call Print2DigitBCD \ below JSR SpawnCharacter3D \ Call SpawnCharacter3D with A = #%10000000 + 0 to set \ the tile x-coordinate of the large 3D secret code text \ to zero LDA #%11000000 + 7 \ Call SpawnCharacter3D with A = #%11000000 + 7 to set JSR SpawnCharacter3D \ the tile z-coordinate of the large 3D secret code text \ to 7 \ \ So we draw the secret code in large 3D text blocks at \ tile coordinate (0, 7) on the landscape, so that's \ starting at the left edge of the landscape and seven \ tile rows from the front LSR playerIsOnTower \ If we got here legally (i.e. without crackers getting \ involved, then the ProcessActionKeys will have set \ playerIsOnTower to 6 when the player transferred into \ the robot on the Sentinel's tower, so this sets the \ value of playerIsOnTower to 3 LDX playerIsOnTower \ Set X = 3 to use as a loop in the following code \ \ If crackers have jumped straight here instead of \ playing the game properly, then X will not be 3 (it \ will probably be 64, as playerIsOnTower is initialised \ to 128 at the start of each level) \ \ The following code picks up where the CheckSecretCode \ routine ends, for when bit 7 of doNotPlayLandscape is \ set \ \ In this case, CheckSecretCode only iterates up to the \ point where the secret code is about to be generated, \ and then it stops, so we can finish the generation \ process here \ \ For this to work, we need to set X = 3 so we can \ generate the next four BCD numbers in the landscape's \ sequence of seed numbers, as these will give us the \ secret code \ \ If X is 64, then the secret code that is generated \ will be incorrect .dsec1 JSR GetNextSeedAsBCD \ Set A to the next number from the landscape's sequence \ of seed numbers, converted to a binary coded decimal \ (BCD) number CPX #4 \ If X >= 4, skip printing the BCD number, so that we BCS dsec2 \ skip printing the four correct secret code numbers if \ X starts the loop at 64 JSR Print2DigitBCD \ Draw the binary coded decimal (BCD) number in A in \ large 3D text .dsec2 DEX \ Decrement the loop counter BPL dsec1 \ Loop back until we have printed the four BCD numbers \ in the landscape's secret code STX playerIsOnTower \ Set playerIsOnTower = &FF to overwrite the correct \ value, to make it harder for crackers to work out why \ their secret codes aren't valid JSR GetNextSeedNumber \ Set A to the next number from the landscape's sequence \ of seed numbers LSR printTextIn3D \ Clear bit 7 of printTextIn3D to return to printing \ normal text RTS \ Return from the subroutineName: SpawnSecretCode3D [Show more] Type: Subroutine Category: Title screen Summary: Draw the landscape's secret code by spawning a set of large 3D text block objectsContext: See this subroutine on its own page References: This subroutine is called as follows: * DrawTitleScreen calls SpawnSecretCode3D.PrintLandscapeNum LDA landscapeNumberHi \ Print the high byte of the binary coded decimal (BCD) JSR Print2DigitBCD \ landscape number as a two-digit number LDA landscapeNumberLo \ Print the low byte of the binary coded decimal (BCD) JMP Print2DigitBCD \ landscape number as a two-digit number and return from \ the subroutine using a tail callName: PrintLandscapeNum [Show more] Type: Subroutine Category: Text Summary: Print the four-digit landscape number (0000 to 9999)Context: See this subroutine on its own page References: This subroutine is called as follows: * FinishLandscape calls PrintLandscapeNum * PreviewLandscape calls PrintLandscapeNum.InitialiseSeeds STY seedNumberLFSR+1 \ Initialise the seed number generator by setting bits STX seedNumberLFSR \ 0-15 of the five-byte linear feedback shift register \ to the landscape number \ \ This ensures that the GetNextSeedNumber routine (and \ related routines) will generate a unique sequence of \ pseudo-random numbers for this landscape, and which \ will be the exact same sequence every time we need to \ generate this landscape \ \ It also ensures that the third number to be generated \ by the shift register is the high byte of the \ landscape number that we put into seedNumberLFSR+1, as \ at this early stage of the process the EOR feedback \ does not affect this byte as it passes through the \ shift register, so after three 8-bit shifts the high \ byte reaches seedNumberLFSR+4 and is returned as the \ next seed number \ \ This fact is exploited by the anti-cracker code in the \ SetCrackerSeed routine STY landscapeNumberHi \ Set landscapeNumber(Hi Lo) = (Y X) STX landscapeNumberLo STY landscapeZero \ If the high byte of the landscape number is non-zero, TYA \ then set landscapeZero to this non-zero value (to BNE seed1 \ indicate that we are not playing landscape 0000) and \ jump to seed1 to set maxNumberOfEnemies to 8 TXA \ Set landscapeZero to the low byte of the landscape, STA landscapeZero \ so this sets landscapeZero to zero if we are playing \ landscape 0000, and it sets it to a non-zero value if \ we are not \ \ So landscapeZero is now correctly set to indicate \ whether or not we are playing landscape 0000 LSR A \ Set A to the high byte of the BCD landscape number LSR A \ plus 1, which is the same as saying: LSR A \ LSR A \ A = 1 + (landscapeNumber div 10) CLC \ ADC #1 \ Or A is 1 plus the "tens" digit of the landscape \ number CMP #9 \ If A < 9 then A is in the range 1 to 8, so jump to BCC seed2 \ seed2 to set maxNumberOfEnemies to this value \ Otherwise A is 9 or higher, so we now cap A to 8 as \ the maximum allowed value for maxNumberOfEnemies .seed1 LDA #8 \ Set A = 8 to use as the maximum number of enemies .seed2 STA maxNumberOfEnemies \ Set maxNumberOfEnemies to the value in A, so we get \ the following cap on the number of enemies: \ \ min(8, 1 + (landscapeNumber div 10)) \ \ So landscapes 0000 to 0009 have a maximum enemy count \ of 1, landscapes 0010 to 0019 have a maximum enemy \ count of 2, and so on up to landscapes 0070 and up, \ which have a maximum enemy count of 8 RTS \ Return from the subroutineName: InitialiseSeeds [Show more] Type: Subroutine Category: Landscape Summary: Initialise the seed number generator so it generates the sequence of seed numbers for a specific landscape numberContext: See this subroutine on its own page References: This subroutine is called as follows: * FinishLandscape calls InitialiseSeeds * MainGameLoop calls InitialiseSeeds * MainTitleLoop calls InitialiseSeeds
Arguments: (Y X) A landscape number in BCD (0000 to 9999).ProcessCharacter CMP #200 \ If the character in A >= 200 then it represents a text BCS char1 \ token, so jump to char1 to print the token JMP PrintVduCharacter \ Otherwise the character in A is a simple one-byte \ character or VDU command, so jump to PrintVduCharacter \ to print it .char1 SBC #200 \ Set A = A - 200 \ \ As we store recursive tokens within other tokens by \ encoding then as 200 + the token number, this extracts \ the recursive token number into A, so we can print it \ \ This subtraction works because we jumped here with a \ BCS, so we know that the C flag is set TAX \ Set X to the token we want to print, to pass to the \ PrintTextToken routine TYA \ Store Y on the stack so we can retrieve it below PHA JSR PrintTextToken \ Print the text token in X PLA \ Retrieve Y from the stack, so Y now contains the TAY \ offset of the token we just printed within the parent \ token that we are still printing RTS \ Return from the subroutineName: ProcessCharacter [Show more] Type: Subroutine Category: Text Summary: Process and print a character from a text token, which can encode another text token or be a one-byte character or VDU commandContext: See this subroutine on its own page References: This subroutine is called as follows: * PrintTextToken calls ProcessCharacter
Arguments: A The text token character to be printed Y The offset of the current character within the text token being printed.GetEnemyCount LDA landscapeNumberHi \ Set T = (landscapeNumberHi / 4) + 2 LSR A \ LSR A \ Because the landscape number is in BCD and in the form LSR A \ 0000 to 9999, this extracts the top digit and adds 2 LSR A \ CLC \ So T is in the range 2 to 11, with higher values of T ADC #2 \ for higher landscape numbers STA T .enem1 JSR GetNextSeedNumber \ Set A to the next number from the landscape's sequence \ of seed numbers, which we now use to calculate the \ enemy count for this landscape (so the same number is \ calculated for the same landscape number each time) LDY #7 \ Set Y = 7 to use as the count of clear bits in A when \ A is zero ASL A \ Set the C flag from bit 7 of this landscape's seed \ number and clear bit 0 of A, leaving bits 6 to 0 of \ the original A in bits 7 to 1 PHP \ Store the status flags on the stack, so we can use the \ C flag below to decide whether to negate the result \ We now count the number of continuous clear bits at \ the top of A, ignoring bit 0, so we count zeroes from \ bit 7 down until we hit a 1, and put the result into Y BEQ enem3 \ If A = 0 then jump to enem3 with Y = 7, as we have a \ continuous run of seven clear bits in bits 7 to 1 LDY #&FF \ Otherwise set Y = -1 so the following loop counts the \ number of zeroes correctly .enem2 INY \ Increment the zero counter in Y ASL A \ Shift A to the left, moving the top bit into the C \ flag BCC enem2 \ Loop back to keep shifting and counting zeroes until \ we shift a 1 out of bit 7, at which point Y contains \ the length of the run of zeroes in bits 6 to 0 of the \ landscape's original seed number .enem3 TYA \ At this point Y contains a number in the range 0 to 7, \ so copy this into A PLP \ If the C flag we stored on the stack above was set, BCC enem4 \ invert A, so this flips the result into the range -1 EOR #%11111111 \ to -8 if bit 7 of the landscape's original seed number \ was set .enem4 \ At this point A is in the range -8 to 7 CLC \ Set A = A + T ADC T \ \ T is in the range 2 to 11, so A is now in the range \ -6 to 18 CMP #8 \ If A < 0 or A >= 8 then loop back to enem1 to try BCS enem1 \ again \ If we get here then A is now in the range 0 to 7, with \ higher values for higher landscape numbers ADC #1 \ Set A = A + 1 \ \ This addition works as we know the C flag is clear \ because we just passed through a BCS \ So A is now a number in the range 1 to 8, with higher \ values for higher landscape numbers, which we can use \ as our enemy count (after capping it to the value of \ maxNumberOfEnemies after we return from the \ subroutine) RTS \ Return from the subroutineName: GetEnemyCount [Show more] Type: Subroutine Category: Landscape Summary: Calculate the number of enemies for the current landscapeContext: See this subroutine on its own page References: This subroutine is called as follows: * SpawnEnemies calls GetEnemyCount
Returns: A The enemy count for the landscape (in the range 1 to 8, with higher values for higher landscape numbers).GetNextSeed0To22 JSR GetNextSeedNumber \ Set A to the next number from the landscape's sequence \ of seed numbers PHA \ Set T to bits 0-2 of A AND #%00000111 \ STA T \ So T is a number in the range 0 to 7 PLA LSR A \ Set A to bits 3-6 of A and clear the C flag LSR A \ AND #%00011110 \ So T is a number in the range 0 to 15 LSR A ADC T \ Set A = A + T \ \ So A is a number in the range 0 to 22 RTS \ Return from the subroutineName: GetNextSeed0To22 [Show more] Type: Subroutine Category: Maths (Arithmetic) Summary: Set A to the next number from the landscape's sequence of seed numbers, converted to the range 0 to 22Context: See this subroutine on its own page References: This subroutine is called as follows: * GenerateLandscape calls GetNextSeed0To22 * SpawnTrees calls GetNextSeed0To22.GetPlayerEnergyBCD LDA #0 \ Set A = 0 to use as the player's energy in binary \ coded decimal (BCD) LDX playerEnergy \ Set X to the player's energy level BEQ plen2 \ If X = 0 then jump to plen2 to return A = 0 as the \ player's energy in BCD \ Otherwise we convert X into BCD by simply adding 1 to \ A and repeating this X times \ \ This works because we only call this routine when the \ D flag is set to switch arithmetic to BCD .plen1 CLC \ Increment the BCD number we are building in A ADC #1 DEX \ Decrement the player's energy level in S BNE plen1 \ Loop back until we have added 1 to A, X times, so A \ now contains the original value of X but in BCD .plen2 RTS \ Return from the subroutineName: GetPlayerEnergyBCD [Show more] Type: Subroutine Category: Maths (Arithmetic) Summary: Fetch the player's energy in binary coded decimal (BCD)Context: See this subroutine on its own page References: This subroutine is called as follows: * FinishLandscape calls GetPlayerEnergyBCD
Returns: A The player's energy in BCDSTX soundData+4 \ Set the third parameter of sound data block #0 to X, \ to set the pitch of the first sound STY soundData+12 \ Set the third parameter of sound data block #1 to X, \ to set the pitch of the second sound .MakeSound PHA \ Store the sound number on the stack SEC \ Decrement A, keeping the result positive SBC #1 BCS soun1 ADC #1 .soun1 JSR DefineEnvelope \ Define envelope data A from the envelopeData table, so \ sounds #0 and #1 both define envelope data 0, while \ sounds #2 to #6 define envelope data 1 to 5 PLA \ Retrieve the sound number from the stack and put it TAX \ into X, so we can use it as an index LDA soundNumberData,X \ Set A to the corresponding entry from soundNumberData \ for the sound number in X, which tells us which blocks \ of sound data to use from the soundData table when \ making the sound CMP #1 \ If A <> 1 then jump to soun2 to make the sound using BNE soun2 \ the sound data in soundData block number A \ If A = 1 then this is a two-part sound, with the first \ part using the sound data in soundData block number A \ and the second using the sound data in soundData block \ zero JSR soun2 \ Call soun2 to make the first sound using the sound \ data in soundData block number A LDA #0 \ Set A = 0 and fall through into soun2 to make the \ second sound using the sound data in soundData block \ zero .soun2 \ At this point we have the number of a soundData block \ in A, in the range 0 to 4, so now we make the sound \ using that sound data ASL A \ Set (Y X) = soundData + A * 8 ASL A \ ASL A \ starting with the low byte (we set the high byte in ADC #LO(soundData) \ MakeSoundEnvelope) TAX \ \ Each sound data block in soundData contains 8 bytes \ of data, so this sets (Y X) to the address within \ the soundData table of the data block specified in A LDA #7 \ Set A = 7 for the OSWORD command to make a sound BNE MakeSoundEnvelope \ Jump to MakeSoundEnvelope to set up Y and apply the \ OSWORD command to the (Y X) block, which makes the \ relevant sound (this BNE is effectively a JMP as A is \ never zero)Name: MakeSound [Show more] Type: Subroutine Category: Sound Summary: Make a soundContext: See this subroutine on its own page References: This subroutine is called as follows: * DrainObjectEnergy calls MakeSound * ProcessActionKeys (Part 2 of 2) calls MakeSound * ProcessGameplay calls MakeSound * ProcessMusic calls MakeSound * ProcessSound calls MakeSound * ProcessVolumeKeys calls MakeSound * ApplyTactics (Part 2 of 8) calls via MakeSound-6 * ApplyTactics (Part 6 of 8) calls via MakeSound-6 * ProcessSound calls via MakeSound-6
Arguments: A The number of the sound to make (0 to 6): * 0 = rotating enemy (two-part) * 1 = rotating meanie (two-part) * 2 = create/absorb object white noise * 3 = music * 4 = scanner * 5 = ping * 6 = game over (two-part)
Other entry points: MakeSound-6 Make a two-part sound at the pitches defined in X and Y, where X is the pitch for sound data block #0 and Y is the pitch for sound data block #1.DefineEnvelope STA aStoreEnvelope \ Set (Y X) = envelopeData + (A * 8 - A) * 2 ASL A \ = envelopeData + A * 7 * 2 ASL A \ = envelopeData + A * 14 ASL A \ SEC \ starting with the low byte (we set the high byte in SBC aStoreEnvelope \ MakeSoundEnvelope) ASL A \ ADC #LO(envelopeData) \ Each envelope definition in envelopeData contains 14 TAX \ bytes of data, so this sets A to the address within \ the envelopeData table of the data for the envelope \ number in A LDA #8 \ Set A = 8 for the OSWORD command to define an envelope \ Fall through into MakeSoundEnvelope to set up Y and \ apply the OSWORD command to the (Y X) block, which \ defines the relevant sound envelopeName: DefineEnvelope [Show more] Type: Subroutine Category: Sound Summary: Define a sound envelopeContext: See this subroutine on its own page References: This subroutine is called as follows: * MakeSound calls DefineEnvelope
Arguments: A The number of the sound envelope data block to define in the envelopeData table (0 to 5).MakeSoundEnvelope LDY #HI(soundData) \ Set Y to the high byte of the soundData block address, \ which is the same as the high byte of the envelopeData \ block address, so (Y X) now points to the relevant \ envelope or sound or sound data block JMP OSWORD \ Call OSWORD with action A, as follows: \ \ * A = 7 to make the sound at (Y X) \ \ * A = 8 to set up the sound envelope at (Y X) \ \ and return from the subroutine using a tail callName: MakeSoundEnvelope [Show more] Type: Subroutine Category: Sound Summary: Either make a sound or set up an envelopeContext: See this subroutine on its own page References: This subroutine is called as follows: * MakeSound calls MakeSoundEnvelope
Arguments: A The action: * 7 = make a sound * 8 = define a sound envelope X The low byte of the address of the OSWORD block.aStoreEnvelope EQUB 0Name: aStoreEnvelope [Show more] Type: Variable Category: Sound Summary: Temporary storage for use in the DefineEnvelope routineContext: See this variable on its own page References: This variable is used as follows: * DefineEnvelope uses aStoreEnvelope.soundNumberData EQUB 1 \ Sound #0 = sound data block #1 then block #0 EQUB 1 \ Sound #1 = sound data block #1 then block #0 EQUB 4 \ Sound #2 = sound data block #4 EQUB 2 \ Sound #3 = sound data block #2 EQUB 2 \ Sound #4 = sound data block #2 EQUB 3 \ Sound #5 = sound data block #3 EQUB 1 \ Sound #6 = sound data block #1 then block #0Name: soundNumberData [Show more] Type: Variable Category: Sound Summary: A table to map a sound number (0 to 6) to the sound data block(s) that are used to make the soundContext: See this variable on its own page References: This variable is used as follows: * MakeSound uses soundNumberData.ProcessVolumeKeys LDA focusOnKeyAction \ If bit 7 of focusOnKeyAction is set then the game is BMI volk6 \ focusing effort on a key action such as a landscape \ pan, so jump to volk6 to return from the subroutine \ without checking for volume-related key presses LDA volumeLevel \ Set A to the current volume level LDX keyLogger+3 \ Set X to the key logger entry for "7", "8", COPY and \ DELETE (volume down, volume up, pause, unpause) BEQ volk1 \ If X = 0 then "7" (volume down) has been pressed, so \ jump to volk1 to process it \ If we get here then X must be 1, 2 or 3 (for "8", \ COPY and DELETE) DEX \ If X - 1 <> 0 then the original key logger entry must BNE volk6 \ be 2 or 3 (COPY or DELETE), so jump to volk6 to \ return from the subroutine \ If we get here then the key logger entry must be 1, \ so "8" (volume up) has been pressed CMP #120 \ If A >= 120 then the volume level is already at the BCS volk2 \ maximum level of 120, so jump to volk2 without \ changing it ADC #8 \ Otherwise we can turn the volume up, so add 8 to the \ volume level in A (the addition works because we know \ we just passed through a BCS, so we know the C flag is \ clear) BNE volk2 \ Jump to volk1 to update the volume level .volk1 \ If we get here then "7" (volume down) has been pressed CMP #0 \ If A = 0 then the volume level is already at the BEQ volk2 \ minimum level of 0, so jump to volk2 without changing \ it SBC #8 \ Otherwise we can turn the volume down, so subtract 8 \ from the volume level in A (the subtraction works \ because we know that A > 0, so the CMP above will have \ set the C) .volk2 LDX soundCounter \ If soundCounter >= 2 then a sound is currently being CPX #2 \ made (such as the confirmation ping we make when BCS ProcessVolumeKeys \ changing the volume level) and it hasn't finished yet, \ so loop back to the start of the routine to keep \ checking for key presses and without changing the \ volume, as we only want to change the volume when we \ can make a pinging sound so the player can hear the \ effect of the volume change STA volumeLevel \ Update the volume level in volumeLevel to the new \ value TAY \ Set Y to 0 (if the volume has been turned right down) BEQ volk3 \ or to 8 (if the volume level is non-zero) LDY #8 .volk3 STY envelopeData+42+13 \ Set parameter #13 of envelope 3 to Y, to set the ALD \ for the sound (the amplitude target level at the end \ of the decay phase) STY envelopeData+28+13 \ Set parameter #13 of envelope 2 to Y, to set the ALD \ for the sound (the amplitude target level at the end \ of the decay phase) LDY #11 \ We now work through the envelopeVolumes table, which \ contains offsets into the envelopeData table of the \ bytes that control the volume of each envelope \ \ We update each of the bytes to reflect the new volume \ level, so set a counter in Y to work through all 12 \ bytes of envelope data .volk4 LDX envelopeVolumes,Y \ Fetch the Y-th entry of the envelopeVolumes table into \ X, so it contains the offset within the envelopeData \ table that we need to update CPX #79 \ If this is not the entry at the beginning of the table BNE volk5 \ (which will be the last to be processed), jump to \ volk5 to set the envelope byte to the new volume level EOR #%11111111 \ If this is the entry at the beginning of the table, ADC #0 \ negate the volume level in A, as this entry is for the \ AD parameter (the change of amplitude per step during \ the decay phase), and we want this aspect to drop more \ quickly at higher volume levels .volk5 STA envelopeData,X \ Set the X-th byte of the envelope data to the volume \ level in A DEY \ Decrement the envelope byte counter BPL volk4 \ Loop back until we have updated all 12 bytes with the \ new volume level LDA #12 \ Set soundCounter = 12 to count down while the ping STA soundCounter \ sound is made LDA #5 \ Make sound #5 (ping) so the player can hear the new JSR MakeSound \ volume level JMP ProcessVolumeKeys \ Loop back to the start of the routine to keep checking \ for key presses .volk6 RTS \ Return from the subroutineName: ProcessVolumeKeys [Show more] Type: Subroutine Category: Sound Summary: Adjust the volume of the sound envelopes when the volume keys are pressedContext: See this subroutine on its own page References: This subroutine is called as follows: * ProcessGameplay calls ProcessVolumeKeys.volumeLevel EQUB 88Name: volumeLevel [Show more] Type: Variable Category: Sound Summary: The volume level, which can be changed by pressing "7" and "8"Context: See this variable on its own page References: This variable is used as follows: * ProcessVolumeKeys uses volumeLevel.envelopeVolumes EQUB 5 * 14 + 9 \ Offset for parameter #9 of envelope 5 \ \ AD (change of amplitude per step during decay phase) EQUB 0 * 14 + 12 \ Offset for parameter #12 of envelope 0 \ \ ALA (target amplitude level at end of attack phase) EQUB 0 * 14 + 13 \ Offset for parameter #13 of envelope 0 \ \ ALD (target amplitude level at end of decay phase) EQUB 1 * 14 + 12 \ Offset for parameter #12 of envelope 1 \ \ ALA (target amplitude level at end of attack phase) EQUB 1 * 14 + 13 \ Offset for parameter #13 of envelope 1 \ \ ALD (target amplitude level at end of decay phase) EQUB 2 * 14 + 8 \ Offset for parameter #8 of envelope 2 \ \ AA (change of amplitude per step during attack phase) EQUB 2 * 14 + 12 \ Offset for parameter #12 of envelope 2 \ \ ALA (target amplitude level at end of attack phase) EQUB 3 * 14 + 12 \ Offset for parameter #12 of envelope 3 \ \ ALA (target amplitude level at end of attack phase) EQUB 4 * 14 + 8 \ Offset for parameter #8 of envelope 5 \ \ AA (change of amplitude per step during attack phase) EQUB 4 * 14 + 12 \ Offset for parameter #12 of envelope 4 \ \ ALA (target amplitude level at end of attack phase) EQUB 5 * 14 + 8 \ Offset for parameter #8 of envelope 5 \ \ AA (change of amplitude per step during attack phase) EQUB 5 * 14 + 12 \ Offset for parameter #12 of envelope 5 \ \ ALA (target amplitude level at end of attack phase)Name: envelopeVolumes [Show more] Type: Variable Category: Sound Summary: A table of offsets into the envelope data for bytes that control the volume of each envelope, so we can change their volume levelsContext: See this variable on its own page References: This variable is used as follows: * ProcessVolumeKeys uses envelopeVolumes.ProcessPauseKeys LDA keyLogger+3 \ Set A to the key logger entry for "7", "8", COPY and \ DELETE (volume down, volume up, pause, unpause) BMI paws2 \ If there is no key press in the key logger entry, jump \ to paws2 to return from the subroutine CMP #2 \ If A <> 2 then COPY is not being pressed, so jump to BNE paws2 \ paws2 to check for DELETE \ If we get here then COPY is being pressed, so we need \ to pause the game ROR gamePaused \ Set bit 7 of gamePaused to indicate that the game is \ paused (this works because the CMP above returned an \ equality, so the C flag is set and ready to be rotated \ into bit 7 of gamePaused) LDA #8 \ Update the scanner so it's filled with green, to show JSR UpdateScannerNow \ that the game is paused (calling UpdateScannerNow \ ensures the scanner is updated irrespective of whether \ the scanner is currently enabled in-game) JSR FlushSoundBuffers \ Flush all four sound channel buffers .paws1 LDA keyLogger+3 \ Set A to the key logger entry for "7", "8", COPY and \ DELETE (volume down, volume up, pause, unpause) CMP #3 \ If A <> 3 then DELETE is not being pressed, so jump BNE paws1 \ paws1 to check for key presses (or wait until no key \ is being pressed) LDA #0 \ Update the scanner so it's filled with black, so the JSR UpdateScannerNow \ game can resume with a blank scanner (calling \ UpdateScannerNow ensures the scanner is updated \ irrespective of whether the scanner is currently \ enabled in-game) LSR gamePaused \ Clear bit 7 of gamePaused to indicate that the game is \ unpaused .paws2 RTS \ Return from the subroutineName: ProcessPauseKeys [Show more] Type: Subroutine Category: Keyboard Summary: Pause or unpause the game when COPY or DELETE are pressedContext: See this subroutine on its own page References: This subroutine is called as follows: * ProcessGameplay calls ProcessPauseKeys.ProcessMusic LDX musicCounter \ Set X to the music counter, which points to the \ current place in the music data BMI musi4 \ If bit 7 of musicCounter is set then there is no music \ playing, so jump to musi4 to return from the \ subroutine INC musicCounter \ Otherwise there is some music being played, so \ increment the music counter to move through the music \ data LDA musicData,X \ Set A to the next byte of music data CMP #&FF \ If A = &FF then we just reached the end of this piece BEQ musi3 \ of music, so jump to musi3 to stop playing music \ If we get here then we need to process the byte of \ music data in A CMP #200 \ If A < 200 then jump to musi1 to make the sound BCC musi1 \ If we get here then A contains the sound counter to \ set for all following notes, with the form 200 + x \ setting the counter to 4 * x SBC #200 \ Set noteCounter = (A - 200) * 4 ASL A \ ASL A \ This subtraction works because we just passed through STA noteCounter \ a BCC, so we know the C flag is set JMP ProcessMusic \ Loop back to the start of the routine to process the \ next byte of music data .musi1 STA soundData+20 \ Set the third parameter of sound data block #2 to A, \ to set the pitch LDA noteCounter \ Set soundCounter to the note duration so the note STA soundCounter \ plays for the correct amount of time \ \ This means that the ProcessSound routine will do \ nothing until this counter has run down, so we can \ play three-note chords by setting the delay to zero \ for the first two notes in the chord, so that there is \ no delay between them, and then setting the delay for \ the last note to the duration of the chord, so the \ three chord notes play together for that period \ \ All we need to do is ensure that the three notes can \ play together, which is what we do next LDA soundData+16 \ Set A to the first parameter of sound data block #2, \ which contains the channel \ \ This is in the form &1x for sound channel x, and it \ starts out at &12 for channel 2 \ \ Channels 1 to 3 are for tones, so we work our way \ through channels &11 to &13, playing each note on the \ next channel number \ \ This plays the music with the last three notes being \ sustained, which lets us play three-note chords as \ described above, giving the game music its distinctive \ chord style CLC \ Add 1 to move A onto the number of the next channel ADC #1 CMP #&14 \ If A < &14 then we are still in the range &11 to &13, BCC musi2 \ so jump to musi2 to use this channel LDA #&11 \ Otherwise warp around to channel &11 .musi2 STA soundData+16 \ Set the first parameter of sound data block #2 to A, \ which sets the channel LDA #4 \ Set the second parameter of sound data block #2 to 4, STA soundData+18 \ which sets the amplitude LDA #3 \ Make sound #3 (music), which plays the next note of JMP MakeSound \ music, returning from the subroutine using a tail call .musi3 \ If we get here then we just reached the end of the \ piece of music and A = &FF STA musicCounter \ Set bit 7 of the music counter to flag that we are no \ longer playing any music .musi4 RTS \ Return from the subroutineName: ProcessMusic [Show more] Type: Subroutine Category: Sound Summary: Play the configured music in the backgroundContext: See this subroutine on its own page References: This subroutine is called as follows: * ProcessSound calls ProcessMusic.FlushSoundBuffers LDX #7 \ To flush all four sound channel buffers we need to \ pass the values 4, 5, 6 and 7 to the FlushBuffer \ routine, so set X to loop through those values .fbuf1 JSR FlushBuffer \ Call FlushBuffer to flush the buffer specified in X DEX \ Decrement the loop counter CPX #4 \ Loop back until we have flushed the buffers for all BCS fbuf1 \ four sound channels RTS \ Return from the subroutineName: FlushSoundBuffers [Show more] Type: Subroutine Category: Sound Summary: Flush all four sound channel buffersContext: See this subroutine on its own page References: This subroutine is called as follows: * MainGameLoop calls FlushSoundBuffers * ProcessPauseKeys calls FlushSoundBuffers * ShowGameOverScreen calls FlushSoundBuffers.FlushSoundBuffer0 LDX #4 \ Set X = 4 to denote the sound channel 0 buffer \ Fall through into FlushBuffer to flush the sound \ channel 0 bufferName: FlushSoundBuffer0 [Show more] Type: Subroutine Category: Sound Summary: Flush the sound channel 0 bufferContext: See this subroutine on its own page References: This subroutine is called as follows: * ProcessGameplay calls FlushSoundBuffer0.FlushBuffer LDA #21 \ Call OSBYTE with A = 21 to flush buffer X, returning JMP OSBYTE \ from the subroutine using a tail callName: FlushBuffer [Show more] Type: Subroutine Category: Keyboard Summary: Flush the specified bufferContext: See this subroutine on its own page References: This subroutine is called as follows: * EnableKeyboard calls FlushBuffer * FlushSoundBuffers calls FlushBuffer * GetPlayerDrain calls FlushBuffer
Arguments: X The number of the buffer to flush: * 4 = sound channel 0 buffer * 5 = sound channel 1 buffer * 6 = sound channel 2 buffer * 7 = sound channel 3 buffer.ProcessSound LDA soundCounter \ If soundCounter is non-zero then a sound is already BNE psou1 \ being made, so jump to psou1 to return from the \ subroutine LDA soundEffect \ Set A to the type of sound effect we need to apply, \ which is specified in the soundEffect variable when \ the sound is made CMP #4 \ If A = 4 then this is the scanner sound, so jump to BEQ psou3 \ psou3 to process the scanner sound CMP #3 \ If A = 3 then this is music, so jump to ProcessMusic BEQ psou2 \ via psou2 to process the music being played CMP #6 \ If A <> 6 then there is no sound effect to apply, so BNE psou1 \ jump to psou1 to return from the subroutine \ If we get here then soundEffect = 6, so this is the \ game over sound LDX #7 \ Set X = 7 to pass to MakeSound-6 as the pitch of the \ first part of the game over sound LDY gameOverSoundPitch \ Set Y to the pitch for the second part of the game \ over sound, which starts in the ShowGameOverScreen \ routine at 250 and counts down towards 80 CPY #80 \ If Y < 80 then the game over sound has finished, so BCC psou1 \ jump to psou1 to return from the subroutine without \ making a sound LDA #6 \ Make sound #6 (game over) with the pitches in X and Y JSR MakeSound-6 JSR GetNextSeedNumber \ Set A to the next number from the landscape's sequence \ of seed numbers, which by this point in the game is \ effectively a random number AND #3 \ Convert the random number in A into a random number in CLC \ the range 1 to 4 ADC #1 STA soundCounter \ Set soundCounter to a random number between 1 and 4 to \ control the length of the noise we just made DEC gameOverSoundPitch \ Decrement the pitch of the game over sound so the \ overall effect is of a slowly decaying sound effect .psou1 RTS \ Return from the subroutine .psou2 \ If we get here then soundEffect = 3, so the sound \ effect is music JMP ProcessMusic \ Jump to ProcessMusic to play the music .psou3 \ If we get here then soundEffect = 4, so this is the \ scanner sound LDA #50 \ Set soundCounter = 50 to count down while the next STA soundCounter \ sound is made LDA #34 \ Set the third parameter of sound data block #2 to 34, STA soundData+20 \ to set the pitch LDA #3 \ Set the second parameter of sound data block #2 to 3, STA soundData+18 \ to set the amplitude LDA #4 \ Make sound #4 (scanner) JSR MakeSound RTS \ Return from the subroutine EQUB &B9 \ This byte appears to be unusedName: ProcessSound [Show more] Type: Subroutine Category: Sound Summary: Process any sound effects that have been configured so they play in the background (this is called regularly throughout gameplay)Context: See this subroutine on its own page References: This subroutine is called as follows: * DecayScreenToBlack calls ProcessSound * DitherScreenBuffer calls ProcessSound * DrawLandscapeView (Part 2 of 3) calls ProcessSound * DrawTileAndObjects calls ProcessSound * GetRowVisibility (Part 1 of 2) calls ProcessSound * MainGameLoop calls ProcessSound * ProcessGameplay calls ProcessSound.PlayGame LDA #&83 \ Set the palette to the first set of colours from the JSR SetColourPalette \ colourPalettes table, which the SpawnEnemies routine \ set to the correct palette for playing the game (so \ for landscape 0000 that would be blue, black, white \ and green, for example) \ \ So this displays the landscape preview screen that we \ drew in PreviewLandscape with an all-blue palette \ before jumping here JSR ReadKeyboard \ Enable the keyboard, flush the keyboard buffer and \ read a character from it (so this waits for a key \ press before starting the game, following the "PRESS \ ANY KEY" message on the landscape preview screen) LSR gameInProgress \ Clear bit 7 of gameInProgress to indicate that a game \ now in progress and we are no longer in the title and \ preview screens (so the interrupt handler can now \ update the game)Name: PlayGame [Show more] Type: Subroutine Category: Main game loop Summary: Start playing the generated landscapeContext: See this subroutine on its own page References: This subroutine is called as follows: * CheckSecretCode (Part 2 of 2) calls PlayGame.MainGameLoop \ If we get here then we have either started a brand new \ game or we jumped here from game11 below when one of \ the following occurred: \ \ * The Sentinel has won \ \ * The player has moved to a new tile \ \ * The player has pressed the quit game key JSR FlushSoundBuffers \ Flush all four sound channel buffers LDA quitGame \ If bit 7 of quitGame is clear then the player has not BPL game1 \ pressed function key f1 to quit the game, so jump to \ game 1 to keep playing the game JMP MainTitleLoop \ The player has pressed function key f1 to quit the \ game, so jump to MainTitleLoop to restart the game .game1 \ If we get here then we have either started a brand new \ landscape or we jumped to MainGameLoop from game11 \ below when one of the following occurred: \ \ * The Sentinel has won \ \ * The player has moved to a new tile LDA sentinelHasWon \ If bit 7 of sentinelHasWon is set then the player has BMI game6 \ run out of energy either by trying to hyperspace or by \ being absorbed, so jump to game6 to restart the \ landscape \ If we get here then we have either started a brand new \ landscape or we jumped to MainGameLoop from game11 \ below when the player moved to a new tile \ \ In both cases we need to generate a brand new \ landscape view LDA #4 \ Set all four logical colours to physical colour 4 JSR SetColourPalette \ (blue), so this blanks the entire screen to blue LDA #0 \ Set screenOrBuffer = 0 to configure the drawing STA screenOrBuffer \ routines to draw directly onto the screen (as opposed \ to drawing into the screen buffer) STA lastPanKeyPressed \ Zero lastPanKeyPressed so that when we draw the whole \ landscape view onto the screen below, we draw it in \ character columns, working from left to right, in the \ same order as we would for a pan to the right (which \ is what a zero value of lastPanKeyPressed usually \ represents) STA sightsByteCount \ Set sightsByteCount to zero to reset the sights pixel \ byte stash STA sightsAreVisible \ Clear bit 7 of sightsAreVisible to indicate that the \ sights are not visible JSR SetScannerAndPause \ Set scannerUpdate to zero to prevent scanner updates \ \ This routine also performs a delay of 40 empty loops \ of 256 iterations each (i.e. 10,240 loops) LDA playerObject \ Set viewingObject to the object number of the player, STA viewingObject \ so the landscape view gets drawn from the perspective \ of the player BIT hyperspaceEndsGame \ If bit 7 of hyperspaceEndsGame is clear then the game BPL game2 \ has not ended because of a hyperspace, so jump to \ game2 to draw the landscape \ If we get here then the game has ended because the \ player performed a hyperspace BVS game7 \ If bit 6 of hyperspaceEndsGame is set then the game \ has ended because the player has hyperspaced from the \ Sentinel's tower, thus winning the game, so jump to \ game7 to process winning the landscape \ If we get here then bit 6 of hyperspaceEndsGame is \ clear and the player has run out of energy by trying \ to hyperspace without being able to create a robot \ into which they can hyperspace JSR ClearScreen \ Clear the screen JMP game4 \ Jump to game4 to skip drawing the landscape view, as \ the game has ended .game2 LDA uTurnStatus \ If bit 7 of uTurnStatus is set then we just performed BMI game3 \ a U-turn in the ProcessActionKeys routine, so jump to \ game3 to skip the following instruction, as we don't \ need to recalculate tile visibility if we are turning \ around (as the player hasn't moved) JSR GetTileVisibility \ For each tile in the landscape, calculate whether the \ player can see that tile, to speed up the process of \ drawing the landscape .game3 JSR ClearScreen \ Clear the screen JSR DrawLandscapeView \ Draw the landscape view JSR UpdateIconsScanner \ Update the icons in the top-left corner of the screen \ to show the player's current energy level and redraw \ the scanner box .game4 LDA #25 \ Set screenOrBuffer = 25 to configure the drawing STA screenOrBuffer \ routines to draw into the screen buffer (as opposed \ to drawing directly onto the screen) LDA #2 \ Call ConfigureBuffer with A = 2 to set up the screen JSR ConfigureBuffer \ buffer for use as a column buffer .game5 JSR ProcessSound \ Process any sounds or music that are being made in the \ background LDA musicCounter \ Loop back to keep calling ProcessSound until bit 7 of BPL game5 \ musicCounter is clear, so if any music is being \ played, we wait until it has finished LDA #&83 \ Set the palette to the first set of colours from the JSR SetColourPalette \ colourPalettes table, which the SpawnEnemies routine \ set to the correct palette for playing the game (so \ for landscape 0000 that would be blue, black, white \ and green, for example) LDA hyperspaceEndsGame \ Set A to the hyperspace status flag BPL game11 \ If bit 7 of hyperspaceEndsGame is clear then the game \ has not ended because of a hyperspace, so jump to \ game11 to keep going \ If we get here then the game has ended because of a \ hyperspace, and it must be because the player ran out \ of energy, as otherwise we would have taken the branch \ to game7 above STA sentinelHasWon \ Set bit 7 of sentinelHasWon to indicate that the \ player has run out of energy and the Sentinel has won LDA #6 \ Set soundEffect = 6 so the sound is processed as the STA soundEffect \ game over sound LDA #5 \ The Sentinel has won, so display the game over screen JSR ShowGameOverScreen \ with A = 5, so we decay the screen to black with a \ mass of 5 * 2400 = 12,000 randomly placed black dots .game6 \ If we get here then we restart the landscape by \ resetting all the game variables, initialising the \ the seed number generator and jumping into the main \ title loop to preview the landscape and play the game JSR ResetVariables \ Reset all the game's main variables LDY landscapeNumberHi \ Set (Y X) = landscapeNumber(Hi Lo) LDX landscapeNumberLo JSR InitialiseSeeds \ Initialise the seed number generator to generate the \ sequence of seed numbers for the landscape number in \ (Y X) and set maxNumberOfEnemies and the landscapeZero \ flag accordingly JMP main4 \ Jump to main4 in the main title loop to restart the \ landscape without having to enter the landscape's \ secret code again .game7 \ If we get here then we have successfully completed \ the landscape, so we display the secret code for the \ next landscape and go back to the title screen LDA #4 \ Set all four logical colours to physical colour 4 JSR SetColourPalette \ (blue), so this blanks the entire screen to blue \ In order to display the secret code for the next \ landscape we need to generate it, so we now reset the \ landscape seed generator, the tile visibility table \ and the object flags, to get ready for the generation \ process in the FinishLandscape routine LDX #3 \ We now zero bits 8 to 40 of the five-byte linear \ feedback shift landscape register, so set a byte \ counter in X to count four bytes LDA #0 \ Set A = 0 so we can zero the four bytes STA soundEffect \ Set soundEffect = 0 to disable sound effect processing \ in the ProcessSound routine .game8 STA seedNumberLFSR+1,X \ Zero byte X+1 of seedNumberLFSR(4 3 2 1 0) DEX \ Decrement the byte counter BPL game8 \ Loop back until we have reset all four bytes JSR ResetTilesObjects \ Reset the tile visibility table and deallocate all \ object numbers JSR FinishLandscape \ Add the player's energy to the landscape number to get \ the number of the next landscape and display that \ landscape's secret code as a title screen LDA #&87 \ Set the palette to the second set of colours from the JSR SetColourPalette \ colourPalettes table, which contains the fixed palette \ for the title screens (blue, black, red, yellow) LDA #10 \ Set soundCounter = 10 to count down while the next STA soundCounter \ sound is made LDA #66 \ Call the PlayMusic routine with A = 66 to play the JSR PlayMusic \ music for when the player finishes a landscape .game9 JSR ProcessSound \ Process any sounds or music that are being made in the \ background LDA musicCounter \ Loop back to keep calling ProcessSound until bit 7 of BPL game9 \ musicCounter is clear, so if any music is being \ played, we wait until it has finished LDX #6 \ Print text token 6: Print "PRESS ANY KEY" at (64, 100) JSR PrintTextToken JSR ReadKeyboard \ Enable the keyboard, flush the keyboard buffer and \ read a character from it (so this waits for a key \ press) .game10 JMP MainTitleLoop \ Jump to MainTitleLoop to restart the game from the \ title screen .game11 \ If we get here then the game is still in progress, so \ we progress the gameplay JSR ProcessGameplay \ Run the gameplay loop that processes all game key \ presses, returning here when the player moves, quits, \ loses or pans BCC game12 \ The ProcessGameplay routine will return with the C \ flag clear if we just finished a landscape pan and the \ player is still holding down a pan key, in which case \ jump to game12 to process the pan JMP MainGameLoop \ Otherwise the ProcessGameplay routine returned because \ one of the following is true: \ \ * The Sentinel has won \ \ * The player has moved to a new tile \ \ * The player has pressed the quit game key \ \ so jump to MainGameLoop to process these actions .game12 \ If we get here then the player is holding down a pan \ key and wants to pan the screen, so we need to process \ the pan LDA panKeyBeingPressed \ Update lastPanKeyPressed with the details of the pan STA lastPanKeyPressed \ key being pressed, so we can check later on whether it \ is still being held down LDA #0 \ Set numberOfScrolls = 0 to reset the scroll counter STA numberOfScrolls \ so the interrupt handler will not scroll the screen \ while we set up the new pan STA doNotDitherObject \ Clear bit 7 of doNotDitherObject to enable objects to \ be updated on the screen with a dithered effect BIT sightsAreVisible \ If bit 7 of sightsAreVisible is set then the sights BMI game13 \ are being shown, so jump to game13 to skip the \ following two instructions \ \ This ensures that when we pan the screen as a result \ of the sights moving off the edge of the screen, the \ panning process completes and doesn't get aborted if \ the player releases the pan key SEC \ The sights are not visible so we are panning the ROR keepCheckingPanKey \ screen because the player has pressed a pan key, so \ set bit 7 of keepCheckingPanKey so DrawLandscapeView \ will abort the drawing process if the player releases \ the pan key before the drawing process has finished .game13 JSR PanLandscapeView \ Pan the landscape and update the landscape view LSR keepCheckingPanKey \ Clear bit 7 of keepCheckingPanKey so DrawLandscapeView \ will keep drawing the landscape irrespective of the \ pan keys being held down (so this takes the routine \ back to its default behaviour) JSR UpdateIconsScanner \ Update the icons in the top-left corner of the screen \ to show the player's current energy level and redraw \ the scanner box LDA numberOfScrolls \ Set scrollCounter to the number of scrolls required, STA scrollCounter \ so the interrupt handler will scroll the screen by \ this many steps in the background .game14 LDA scrollCounter \ Loop around until scrollCounter is zero, so this waits BNE game14 \ until the interrupt handler has finished scrolling the \ landscape view, thus implementing the pan on-screen BEQ game11 \ Jump to game11 to continue progressing the gameplay \ (this BEQ is effectively a JMP as we just passed \ through a BNE)Name: MainGameLoop [Show more] Type: Subroutine Category: Main game loop Summary: The main game loop for playing a landscapeContext: See this subroutine on its own page References: This subroutine is called as follows: * GetTileAltitudes calls via game10
Other entry points: game10 Jump to MainTitleLoop to restart the game.ClearIconsScanner LDA #0 \ Set xIconCounter = 0 so we start drawing from the left STA xIconCounter \ edge of the screen .enec1 LDA #0 \ Draw a blank icon into the top part of the screen (via JSR DrawIcon \ the icon screen buffer at iconBuffer) and move along \ to the right, incrementing xIconCounter as we do LDA xIconCounter \ Loop back to keep drawing blank icons until we have CMP #40 \ cleared column 39, at which point we have cleared the BCC enec1 \ entire row JMP ShowIconBuffer \ Display the contents of the icon screen buffer by \ copying it into screen memory, which will clear the \ row on-screen, and return from the subroutine using a \ tail callName: ClearIconsScanner [Show more] Type: Subroutine Category: Scanner/energy row Summary: Clear the energy icon and scanner row at the top of the screenContext: See this subroutine on its own page References: This subroutine is called as follows: * ClearScreen calls ClearIconsScanner * ShowGameOverScreen calls ClearIconsScanner.PrintTextToken LDY tokenOffset,X \ Set Y to the offset for text token X, which we can use \ as a character index to print each character in turn .text1 LDA tokenBase,Y \ Set A to the Y-th character of the text token CMP #&FF \ If A = &FF then we have reached the end of the token, BEQ text2 \ so jump to text2 to return from the subroutine JSR ProcessCharacter \ Process the Y-th character of the text token in A, so \ if A is a token number in the format 200 + token, we \ print the text token, otherwise we print A as a simple \ one-byte character INY \ Increment the character index in Y to point to the \ next character of the text token JMP text1 \ Loop back to print the next character .text2 RTS \ Return from the subroutineName: PrintTextToken [Show more] Type: Subroutine Category: Text Summary: Print a recursive text tokenContext: See this subroutine on its own page References: This subroutine is called as follows: * FinishLandscape calls PrintTextToken * MainGameLoop calls PrintTextToken * MainTitleLoop calls PrintTextToken * PreviewLandscape calls PrintTextToken * ProcessCharacter calls PrintTextToken * SecretCodeError calls PrintTextToken
Arguments: X The number of the text token to print (0 to 17).clearPixelMask EQUB %01110111 \ Pixel bit mask with all pixels set except pixel 0 EQUB %10111011 \ Pixel bit mask with all pixels set except pixel 1 EQUB %11011101 \ Pixel bit mask with all pixels set except pixel 2 EQUB %11101110 \ Pixel bit mask with all pixels set except pixel 3 EQUB %10001000 \ These bytes appear to be unused EQUB %01000100 EQUB %00100010 EQUB %00010001Name: clearPixelMask [Show more] Type: Variable Category: Graphics Summary: A table for converting a pixel number in the range 0 to 3 into a screen mode 5 bit mask with that pixel's bits clear and others setContext: See this variable on its own page References: This variable is used as follows: * DitherScreenBuffer uses clearPixelMask * DrawSights uses clearPixelMask.UpdateIconsScanner LDA #0 \ Set A = 0 to pass to DrawIcon STA xIconCounter \ Set xIconCounter = 0 so we start drawing from the left \ edge of the screen JSR DrawIcon \ Draw a blank icon into the top part of the screen (via \ the icon screen buffer at iconBuffer) and move along \ to the right, incrementing xIconCounter as we do LDA playerEnergy \ Set a loop counter to count down through the player's STA loopCounter \ energy, so we can reduce it by the maximum possible \ amount each time to work out the minimum number of \ icons that represent that energy level \ \ In other words, loopCounter contains the amount of \ energy that we have yet to draw .ener1 LDA loopCounter \ If loopCounter < 15 then we can't represent it with a CMP #15 \ high-energy robot (as that represents 15 energy BCC ener2 \ units), so jump to ener2 to skip to the next level \ down SBC #15 \ Subtract 15 from the loop counter as we are about to STA loopCounter \ draw a high-energy robot that represents 15 energy \ units (this subtraction works because we just passed \ through a BCC, so the C flag must be set) LDA #6 \ Draw a high-energy robot into the top part of the JSR DrawIcon \ screen (via the icon screen buffer at iconBuffer) and \ move along to the right, incrementing xIconCounter as \ we do LDA #0 \ Draw a blank icon into the top part of the screen (via JSR DrawIcon \ the icon screen buffer at iconBuffer) and move along \ to the right, incrementing xIconCounter as we do JMP ener1 \ Loop back to ener1 to draw another high-energy robot, \ if required .ener2 LDA loopCounter \ If loopCounter < 3 then we can't represent it with a CMP #3 \ blue robot (as that represents three energy units), BCC ener3 \ so jump to ener3 to skip to the next level down SBC #3 \ Subtract 3 from the loop counter as we are about to STA loopCounter \ draw a high-energy robot that represents three energy \ units (this subtraction works because we just passed \ through a BCC, so the C flag must be set) LDA #1 \ Draw a blue robot into the top part of the screen (via JSR DrawIcon \ the icon screen buffer at iconBuffer) and move along \ to the right, incrementing xIconCounter as we do LDA #0 \ Draw a blank icon into the top part of the screen (via JSR DrawIcon \ the icon screen buffer at iconBuffer) and move along \ to the right, incrementing xIconCounter as we do JMP ener2 \ Loop back to ener2 to draw another blue robot, if \ required .ener3 \ If we get here then the value of loopCounter in A must \ be 0, 1 or 2, as otherwise we would still be drawing \ robots CMP #1 \ If A < 1 then A must be zero, so jump to ener4 to move BCC ener4 \ on to the scanner as we have no more energy units to \ draw ASL A \ If we get here then A is 1 or 2, so double it to get \ the correct icon numbers for the left part of the \ two-part tree or boulder icons: \ \ * When A = 1, double it to 2 for the left tree icon \ \ * When A = 2, double it to 4 for the left boulder \ icon JSR DrawIcon \ Draw the correct left icon into the top part of the \ screen (via the icon screen buffer at iconBuffer) and \ move along to the right, incrementing xIconCounter as \ we do CLC \ Increment A to get the correct icon number for the ADC #1 \ right part of the tree or boulder JSR DrawIcon \ Draw the correct right icon into the top part of the \ screen (via the icon screen buffer at iconBuffer) and \ move along to the right, incrementing xIconCounter as \ we do \ We have updated the energy icons, so now we blank the \ rest of the top line of the screen before drawing the \ outline of the scanner .ener4 LDA #0 \ Draw a blank icon into the top part of the screen (via JSR DrawIcon \ the icon screen buffer at iconBuffer) and move along \ to the right, incrementing xIconCounter as we do LDA xIconCounter \ Loop back to keep drawing blank icons until we reach CMP #29 \ column 29, which is where we want to draw the scanner BCC ener4 LDA #7 \ Draw the left edge of the scanner box into the top JSR DrawIcon \ part of the screen (via the icon screen buffer at \ iconBuffer) and move along to the right, incrementing \ xIconCounter as we do .ener5 LDA #8 \ Draw the middle part of the scanner box into the top JSR DrawIcon \ part of the screen (via the icon screen buffer at \ iconBuffer) and move along to the right, incrementing \ xIconCounter as we do LDA xIconCounter \ Loop back to keep drawing the middle part of the CMP #38 \ scanner box until we reach column 38 BCC ener5 LDA #9 \ Draw the right edge of the scanner box into the top JSR DrawIcon \ part of the screen (via the icon screen buffer at \ iconBuffer) and move along to the right, incrementing \ xIconCounter as we do LDA #0 \ Draw a blank icon into the top part of the screen (via JSR DrawIcon \ the icon screen buffer at iconBuffer) and move along \ to the right JMP ShowIconBuffer \ Display the contents of the icon screen buffer by \ copying it into screen memory, returning from the \ subroutine using a tail callName: UpdateIconsScanner [Show more] Type: Subroutine Category: Scanner/energy row Summary: Update the icons in the top-left corner of the screen to show the player's current energy level and redraw the scanner boxContext: See this subroutine on its own page References: This subroutine is called as follows: * DrainObjectEnergy calls UpdateIconsScanner * MainGameLoop calls UpdateIconsScanner * ProcessGameplay calls UpdateIconsScanner.DrawIcon PHA \ Store the icon type on the stack so we can preserve it ASL A \ Set X = (A * 8) + 7 ASL A \ ASL A \ Each icon definition in iconData contains eight bytes, ORA #7 \ so we can use this as an index into iconData, where it TAX \ points to the last of the eight bytes for icon A (as \ we added 7 to A * 8 with the ORA instruction) \ We now calculate the address of the icon in the screen \ buffer at iconBuffer \ \ The icon needs to be drawn in column xIconCounter, \ where each column is eight pixels wide, and because \ the screen is split into 8x8-pixel character blocks of \ eight bytes in each, the icon is in screen memory at \ this address, which we store in (Q P): \ \ (Q P) = iconBuffer + xIconCounter * 8 \ \ This is calculated by doing half the sum first, and \ then doubling the result, like this: \ \ (iconBuffer / 2) + (xIconCounter * 4) \ \ The calculation is done this way to cater for values \ of xIconCounter between 32 and 39, as multiplying 32 \ by 8 won't fit into one byte, but multiplying by 4 \ will fit (and doubling just requires a simple shift) LDA xIconCounter \ Set P = xIconCounter * 4 + LO(iconBuffer / 2) ASL A \ ASL A \ so those are the low bytes ADC #LO(iconBuffer)/2 STA P LDA #HI(iconBuffer)/2 \ Set A = 0 + HI(iconBuffer / 2) ADC #0 \ \ and those are the high bytes, with the result of the \ half sum in (A P) ASL P \ Set (Q P) = (A P) * 2 ROL A \ = iconBuffer + xIconCounter * 8 STA Q \ \ So we now have the address within the icon screen \ buffer for the icon we want to draw LDY #7 \ We now copy eight bytes of icon data into the icon \ screen buffer at iconBuffer .deni1 LDA iconData,X \ Copy the X-th byte of icon data to the Y-th byte of STA (P),Y \ (Q P) DEX \ Decrement the source byte counter DEY \ Decrement the destination byte counter BPL deni1 \ Loop back until we have copied all eight bytes INC xIconCounter \ Increment the icon counter to move along to the right \ by one column, as we just drew an icon PLA \ Restore the icon type into A that we stored on the \ stack above RTS \ Return from the subroutineName: DrawIcon [Show more] Type: Subroutine Category: Scanner/energy row Summary: Draw a single icon in the top-left corner of the screen (via the icon screen buffer at iconBuffer) and move along to the rightContext: See this subroutine on its own page References: This subroutine is called as follows: * ClearIconsScanner calls DrawIcon * UpdateIconsScanner calls DrawIcon
Arguments: A The type of icon to draw: * 0 = Blank * 1 = Robot * 2 = Tree (left) * 3 = Tree (right) * 4 = Boulder (left) * 5 = Boulder (right) * 6 = High-energy robot * 7 = Scanner box (left) * 8 = Scanner box (middle) * 9 = Scanner box (right)
Returns: A A is preserved.irqh1 JMP (irq1Address) \ Jump to the original address from IRQ1V to pass \ control to the next interrupt handler .IRQHandler SEI \ Disable interrupts so the following process isn't \ interrupted by another interrupt LDA SHEILA+&6D \ Set A to the 6522 User VIA interrupt flag register IFR \ (SHEILA &6D) AND #%01000000 \ If bit 6 of the IFR is clear then this interrupt has BEQ irqh1 \ not been triggered by the 6522 User VIA timer 1 \ reaching zero, so jump to irqh1 to pass the interrupt \ on to the next interrupt handler \ If we get here then bit 6 of the IFR is set, so this \ interrupt has been triggered by the 6522 User VIA \ timer 1 reaching zero STA SHEILA+&6D \ The AND instruction above leaves A containing bit 6 \ set and all other bits clear, so we can write this to \ the IFR at SHEILA &6D to clear the timer 1 interrupt LDA &FC \ Set A to the interrupt accumulator save register, \ which restores A to the value it had on entering the \ interrupt PHA \ Store A, X and Y on the stack so we can preserve them TXA \ across calls to the interrupt handler PHA TYA PHA CLD \ Clear the D flag to switch arithmetic to normal, in \ case the interrupt was triggered during any BCD \ calculations DEC soundCounter \ Decrement the sound counter so we can detect when the BPL irqh2 \ current sound effect has finished, making sure it INC soundCounter \ doesn't go below zero .irqh2 LDA gameInProgress \ If bit 7 of gameInProgress is set then a game is not BMI irqh8 \ currently in progress and we are in the title and \ preview screens, so jump to irqh8 to skip the \ following and return from the interrupt handler \ without updating the game state LDA sentinelHasWon \ If bit 7 of sentinelHasWon is set then the Sentinel BMI irqh7 \ has won the game, so jump to irqh7 to draw black dots \ on the game over screen (when it is showing) LDA gamePaused \ If bit 7 of gamePaused is set then the game is paused, BMI irqh5 \ so jump to irqh5 to scan for the unpause and volume \ keys and return from the interrupt handler LDA scrollCounter \ If scrollCounter = 0 then we do not need to scroll the BEQ irqh3 \ player's landscape view, so jump to irqh3 to skip the \ scrolling process \ If we get here then we still have scrollCounter steps \ to complete in the current scrolling process, so we \ now do one scrolling step (ScrollPlayerView decrements \ the counter in scrollCounter) JSR ScrollPlayerView \ Scroll the screen and copy data from the view screen \ buffer into screen memory to implement the player's \ scrolling landscape view JSR ShowIconBuffer \ Display the contents of the icon screen buffer by \ copying it into screen memory, as the scrolling \ process will have scrolled this part of the screen, so \ we need to redraw it to prevent the energy icons and \ scanner from moving .irqh3 LDA activateSentinel \ If bit 7 of activateSentinel is set then the Sentinel BMI irqh4 \ has not yet been activated at the start of the game, \ so jump to irqh4 to skip the following JSR UpdateEnemyTimers \ Update the timers that control the enemy tactics JSR UpdateScanner \ Update the scanner, if required .irqh4 LDA focusOnKeyAction \ If bit 7 of focusOnKeyAction is set then the game is BMI irqh8 \ currently focusing effort on implementing a key action \ such as a landscape pan, so jump to irqh8 to skip the \ following keyboard scan JSR CheckForKeyPresses \ Check for various game key presses and update the key \ logger and relevant variables JMP irqh8 \ Jump to irqh8 to return from the interrupt handler .irqh5 \ If we get here then the game is paused, so we scan for \ key presses and discard them all except for the \ unpause and volume keys LDY #13 \ Scan the keyboard for all game keys in the gameKeys JSR ScanForGameKeys \ table except for the last one ("U" for U-turn) \ We now reset the first three entries in the key \ logger (i.e. entries 0 to 2), leaving the last entry \ populated (i.e. entry 3, which records the volume, \ pause and unpause key presses) LDX #2 \ Set a loop counter in X for resetting three entries LDA #%10000000 \ Set A = %10000000 to reset the three entries, as the \ set bit 7 indicates an empty entry in the logger .irqh6 STA keyLogger,X \ Reset the X-th entry in the key logger DEX \ Decrement the loop counter BPL irqh6 \ Loop back until we have reset all four entries JMP irqh8 \ Jump to irqh8 to return from the interrupt handler .irqh7 \ If we get here then the Sentinel has won the game, so \ we draw black dots on the game over screen (when it is \ showing) LDA drawLandscape \ If bit 7 of drawLandscape is clear, skip the following BPL irqh8 \ to return from the interrupt handler \ If bit 7 of drawLandscape is set then we are showing \ the game over screen, which combines dithering of an \ object to the screen while at the same time fading the \ screen to black, to create the hypnotic effect of the \ winning entity fading in and out of the screen as the \ game ends JSR DrawBlackDots \ Draw 80 randomly positioned dots on the screen in \ colour 1 (black) to fade the screen to black in a \ slowly decaying manner .irqh8 PLA \ Restore A, X and Y from the stack TAY PLA TAX PLA RTI \ Return from the interrupt handlerName: IRQHandler [Show more] Type: Subroutine Category: Main game loop Summary: The main interrupt handler, which gets run 50 times a second to update the game state and check for game key pressesContext: See this subroutine on its own page References: This subroutine is called as follows: * ConfigureMachine calls IRQHandler
This routine is called exactly 50 times a second, and it does the following: * Count down the sound timer * If a game is not in progress (i.e. we are in the title screen rather than playing a landscape), return from the handler * If the Sentinel has won and bit 7 of drawLandscape is set, then call DrawBlackDots to draw 80 random black dots for the game over screen, and return from the handler * If the game is paused, scan for the pause and volume keys and return from the handler * If a game is in progress and the Sentinel has not won and the game is not paused, then: * If scrollCounter is non-zero then call ScrollPlayerView (which decrements scrollCounter) and ShowIconBuffer * If the Sentinel has been activated, call UpdateEnemyTimers and UpdateScanner * If bit 7 of focusOnKeyAction is clear then the game is not currently focusing effort on implementing a key action such as a landscape pan, so scan for and process all the game keys.ScrollPlayerView LDY lastPanKeyPressed \ Set Y to the direction of the last pan key that was \ pressed (which may not still be held down) \ \ So this contains the direction of any scrolling that \ we still need to apply, as follows: \ \ * 0 = pan right \ \ * 1 = pan left \ \ * 2 = pan up \ \ * 3 = pan down \ \ We use this as an index into various tables, to look \ up the correct values for the direction in which we \ want to scroll \ We now scroll the screen in the correct direction to \ pan the player's scrolling landscape view \ \ Note that we scroll the screen in the opposite \ direction to the pan direction, so if we are panning \ right we move the screen to the left, for example \ \ The screen address of the player's scrolling landscape \ view is stored in viewScreenAddr(1 0), so this is the \ address of the screen just below the energy icon and \ scanner row \ \ We therefore start the scrolling process by updating \ the screen address in viewScreenAddr(1 0) to point to \ the new address after scrolling \ \ Scrolling is done in individual steps, as follows: \ \ * When scrolling horizontally, we scroll by one \ column that's one character block wide (i.e. one \ byte, two pixels wide) \ \ * When scrolling vertically, we scroll by one row \ that's one character block tall (i.e. eight bytes, \ eight pixels tall) \ \ The amount of change that we need to apply to \ viewScreenAddr(1 0) for each of the four directions is \ given in the tables scrollScreenHi and scrollScreenLo, \ as 16-bit signed values, so we just need to look up \ the correct value and apply it to viewScreenAddr(1 0) \ \ For example, we advance viewScreenAddr(1 0) by eight \ bytes when panning right, as we need to scroll the \ screen to the left, so the start of the screen in \ memory moves on by eight bytes (one character block) \ \ This would scroll the screen to the left by two pixels \ as each character block is two pixels wide \ \ Similarly, we reduce viewScreenAddr(1 0) by 320 bytes \ when panning up, as we need to scroll the screen down, \ so the start of the screen in memory moves on by eight \ bytes (one character row of 40 character blocks with \ eight bytes per character block) \ \ This would scroll the screen to down by eight pixels \ as each character row is eight pixels wide LDA viewScreenAddr \ Apply the change in scrollScreen(Hi Lo) for the CLC \ current pan direction in Y to the screen address of ADC scrollScreenLo,Y \ the player's scrolling landscape, which is stored in STA viewScreenAddr \ viewScreenAddr(1 0) LDA viewScreenAddr+1 \ ADC scrollScreenHi,Y \ We start by calculating this: \ \ (A viewScreenAddr) = viewScreenAddr(1 0) \ + (scrollScreenHi+Y scrollScreenLo+Y) CMP #&80 \ If the high byte in A >= &80 then the new address is BCC scro1 \ past the end of screen memory, so subtract &20 from SBC #&20 \ the high byte so the address wraps around within the \ range of screen memory between &6000 and &8000 \ \ If the high byte is in range, jump to scro1 to check \ the high byte against the start of screen memory JMP scro2 \ Jump to scro2 to skip the next check, as we know it \ doesn't apply .scro1 CMP #&60 \ If the high byte in A < &60 then the new address is BCS scro2 \ before the start of screen memory, so add &20 to the ADC #&20 \ high byte so the address wraps around within the range \ of screen memory between &6000 and &8000 .scro2 STA viewScreenAddr+1 \ Store the high byte of the result, so we now have: \ \ viewScreenAddr(1 0) = viewScreenAddr(1 0) \ + (scrollScreenHi+Y scrollScreenLo+Y) \ \ with the address wrapped around as required \ We now reprogram the 6845 CRTC chip to scroll the \ screen in hardware (so-called "hardware scrolling") \ by setting 6845 registers R12 and R13 to the new \ address for the start of screen memory \ \ The registers actually require the address to be set \ in terms of character rows, so we need to set R12 and \ R12 to the address divided by 8 (see below) JSR GetIconRowAddress \ Set iconRowAddr(1 0) to the address in screen memory \ of the icon and scanner row at the top of the screen \ \ This calculates the address from viewScreenAddr(1 0), \ so the value returned will be the new start of screen \ memory, after the scroll is completed \ \ It also sets A to the high byte of the address of \ screen memory in iconRowAddr(1 0) STA screenAddrHi \ Set (screenAddrHi A) = iconRowAddr(1 0) / 8 LDA iconRowAddr \ LSR screenAddrHi \ So (screenAddrHi A) contains the new address of screen ROR A \ memory, divided by 8, which is suitable for passing to LSR screenAddrHi \ the 6845 CRTC to change the address of screen memory ROR A LSR screenAddrHi ROR A LDX #13 \ Set 6845 register R13 = A, for the low byte STX SHEILA+&00 \ STA SHEILA+&01 \ We do this by writing the register number (13) to \ SHEILA &00, and then the value (A) to SHEILA &01 LDX #12 \ Set 6845 register R12 = &0F, for the high byte STX SHEILA+&00 \ LDA screenAddrHi \ We do this by writing the register number (12) to STA SHEILA+&01 \ SHEILA &00, and then the value (screenAddrHi) to \ SHEILA &01 \ This sets 6845 registers (R12 R13) = (screenAddrHi A) \ to point to the start of screen memory in terms of \ character rows. There are 8 pixel lines in each \ character row, so to get the actual address of the \ start of screen memory, we multiply by 8: \ \ (screenAddrHi A) * 8 \ \ which is iconRowAddr(1 0), as set above \ \ So this whole thing sets the start of screen memory to \ the address of iconRowAddr(1 0), which scrolls the \ screen by the required amount DEC scrollCounter \ Decrement the scroll counter, as we have just scrolled \ the screen by one more step \ We now set viewScreenAddr(1 0) to the new address of \ the start of the player's landscape view, so that's \ the address of the top-left corner of the view, just \ below the energy icon and scanner bar at the top of \ the screen \ \ We now need to set toAddr(1 0) to the address in \ screen memory that we need to update now that the \ screen has been scrolled \ \ Scrolling the screen leads to the following update \ requirements: \ \ * When scrolling the screen to the left, we need to \ update the column on the right \ \ * When scrolling the screen to the right, we need to \ update the column on the left \ \ * When scrolling the screen down, we need to update \ the row along the top \ \ * When scrolling the screen up, we need to update \ the row along the bottom \ \ We want to set toAddr(1 0) to the address of the area \ in screen memory that we need to update, and the \ offset within screen memory of this area for each of \ the four directions is given in the tables at \ updateOffsetHi and updateOffsetLo, as 16-bit signed \ values, so we just need to look up the correct offset \ and apply it to viewScreenAddr(1 0) to get the address \ of the area of screen memory we need to update LDA viewScreenAddr \ Set toAddr(1 0) to the screen address of the player's CLC \ scrolling landscape, plus the offset for the current ADC updateOffsetLo,Y \ pan direction from updateOffset(Hi Lo) STA toAddr \ LDA viewScreenAddr+1 \ We start by calculating this: ADC updateOffsetHi,Y \ \ (A toAddr) = viewScreenAddr(1 0) \ + (updateOffsetHi+Y updateOffsetLo+Y) CMP #&80 \ If the high byte in A >= &80 then the new address is BCC scro3 \ past the end of screen memory, so subtract &20 from SBC #&20 \ the high byte so the address wraps around within the \ range of screen memory between &6000 and &8000 .scro3 STA toAddr+1 \ Store the high byte of the result, so we now have: \ \ toAddr(1 0) = viewScreenAddr(1 0) \ + (updateOffsetHi+Y updateOffsetLo+Y) \ \ with the address wrapped around as required \ By this point we have done the following to implement \ the required screen scrolling: \ \ * Updated viewScreenAddr(1 0) to the new address of \ the player's scrolling landscape \ \ * Updated the 6845 to scroll the screen by changing \ the address of screen memory \ \ * Decremented the counter in scrollCounter \ \ * Set toAddr(1 0) to the address of the area in \ screen memory that we now need to update \ \ We can now fall through into ShowScreenBuffer to copy \ the contents of the screen buffer into the area we \ need to update, so that the scroll reveals the correct \ part of the viewName: ScrollPlayerView [Show more] Type: Subroutine Category: Graphics Summary: Scroll the screen and copy data from the screen buffer into screen memory to implement the player's scrolling landscape viewContext: See this subroutine on its own page References: This subroutine is called as follows: * IRQHandler calls ScrollPlayerView.ShowScreenBuffer \ We start by setting both screenBufferAddr(1 0) and \ fromAddr(1 0) to the address in the screen buffer of \ the content that we need to copy into the screen area \ following the hardware scroll \ \ We do this by taking the current value of the address \ in screenBufferAddr(1 0) and applying the change in \ scrollScreen(Hi Lo) for the current pan direction in \ Y, just as we did when we updated the screen address \ in viewScreenAddr(1 0) in the ScrollPlayerView routine LDA screenBufferAddr \ Add scrollScreen(Hi Lo) for this pan direction to the CLC \ address in screenBufferAddr(1 0), starting with the ADC scrollScreenLo,Y \ low bytes STA screenBufferAddr STA fromAddr \ Store the low byte of the result in fromAddr(1 0) LDA screenBufferAddr+1 \ And then add the high bytes ADC scrollScreenHi,Y STA screenBufferAddr+1 STA fromAddr+1 \ Store the high byte of the result in fromAddr(1 0), so \ fromAddr(1 0) = screenBufferAddr(1 0) and we copy the \ new screen content from the correct address in the \ screen buffer CPY #2 \ If Y >= 2 then we are panning up or down, and we are BCS ShowBufferRow \ scrolling down or up, so jump to ShowBufferRow to \ update the player's scrolling landscape view by \ copying an eight-pixel high character row from the \ screen buffer into screen memory \ Otherwise we are panning left or right, and we are \ scrolling right or left, so fall through into \ ShowBufferColumn to update the player's scrolling \ landscape view by copying a two-pixel wide column from \ the screen buffer into screen memoryName: ShowScreenBuffer [Show more] Type: Subroutine Category: Screen buffer Summary: Update the player's scrolling landscape view by copying the relevant parts of the screen buffer into screen memoryContext: See this subroutine on its own page References: This subroutine is called as follows: * DrawUpdatedObject calls ShowScreenBuffer
Arguments: Y The direction of scrolling that we just applied: * 0 = pan right * 1 = pan left * 2 = pan up * 3 = pan down toAddr(1 0) The address of the area in screen memory that we need to update with the contents of the screen buffer.ShowBufferColumn LDX #24 \ The custom screen mode 5 used by the game contains 25 \ character rows, each of which is eight pixels high, so \ set a row counter in X to count the character rows in \ the screen buffer, which is one row less than the \ screen height, as the top row is the energy icon and \ scanner row .dcol1 JSR ShowBufferBlock \ Copy an eight-byte 8x2-pixel character block from the \ screen buffer at fromAddr(1 0) into screen memory at \ toAddr(1 0) LDA fromAddr \ Set (A fromAddr) = fromAddr(1 0) + &140 CLC \ = fromAddr(1 0) + 320 ADC #&40 \ STA fromAddr \ Each character row in screen mode 5 takes up 320 bytes LDA fromAddr+1 \ (40 character blocks of eight bytes each), so this ADC #&01 \ sets fromAddr(1 0) to the address of the next row down \ in the screen buffer CMP #&53 \ If the result of the addition is less than &5300, then BNE dcol2 \ we have not reached the end of row 15 in the screen \ buffer, so jump to dcol2 to skip the following \ At this point the screen buffer wraps around so the \ buffer entries continue at a lower address \ \ Specifically the last buffer before &5300 is \ screenBufferRow15, at address: \ \ &51C0 to &51FF for character row 15 \ \ and we now wrap around as follows: \ \ &3FA0 to &3FDF for character row 16 \ \ to take us to the buffer at screenBufferRow16 \ \ The address of screenBufferRow16 is &A0 more than the \ start of the screen buffer at screenBufferRow0, and \ the address of the latter is in screenBufferAddr(1 0), \ so we can calculate the new address like this: \ \ (A fromAddr) = screenBufferAddr(1 0) + &A0 \ \ So this wraps the address around so we can keep \ drawing content into the screen buffer \ \ See screenBufferRow0 for more information on the \ structure of the screen buffer LDA screenBufferAddr \ Calculate the following: CLC \ ADC #&A0 \ (A fromAddr) = screenBufferAddr(1 0) + &A0 STA fromAddr LDA screenBufferAddr+1 ADC #&00 .dcol2 STA fromAddr+1 \ Store the high byte of the result, so we now have: \ \ fromAddr(1 0) = fromAddr(1 0) + 320 \ \ with the address wrapped around as required LDA toAddr \ Set (A toAddr) = toAddr(1 0) + &140 CLC \ = toAddr(1 0) + 320 ADC #&40 STA toAddr LDA toAddr+1 ADC #&01 CMP #&80 \ If the high byte in A >= &80 then the new address is BCC dcol3 \ past the end of screen memory, so subtract &20 from SBC #&20 \ the high byte so the address wraps around within the \ range of screen memory between &6000 and &8000 .dcol3 STA toAddr+1 \ Store the high byte of the result, so we now have: \ \ toAddr(1 0) = toAddr(1 0) + 320 \ \ with the address wrapped around as required DEX \ Decrement the counter in X to move on to the next \ character row down BNE dcol1 \ Loop back until we have copied all 24 character blocks \ in the column from fromAddr(1 0) to toAddr(1 0) JMP drow3 \ Jump to drow3 to return from the subroutine, as drow3 \ contains an RTSName: ShowBufferColumn [Show more] Type: Subroutine Category: Screen buffer Summary: Update the player's scrolling landscape view by copying a 2-pixel wide column from the screen buffer into screen memoryContext: See this subroutine on its own page References: No direct references to this subroutine in this source file
Arguments: fromAddr(1 0) The source address from which we copy the character column toAddr(1 0) The destination address to which we copy the character column.ShowBufferRow LDX #40 \ Each character row in screen mode 5 contains 40 \ character blocks of eight bytes, so set a block \ counter in X to count the character blocks .drow1 JSR ShowBufferBlock \ Copy an eight-byte 8x2-pixel character block from the \ screen buffer at fromAddr(1 0) into screen memory at \ toAddr(1 0) LDA fromAddr \ Set fromAddr(1 0) = fromAddr(1 0) + 8 CLC \ ADC #8 \ So this moves the from address to the next character STA fromAddr \ block in the character row LDA fromAddr+1 ADC #0 STA fromAddr+1 LDA toAddr \ Set (A toAddr) = toAddr(1 0) + 8 CLC ADC #8 STA toAddr LDA toAddr+1 ADC #0 CMP #&80 \ If the high byte in A >= &80 then the new address is BCC drow2 \ past the end of screen memory, so subtract &20 from SBC #&20 \ the high byte so the address wraps around within the \ range of screen memory between &6000 and &8000 .drow2 STA toAddr+1 \ Store the high byte of the result, so we now have: \ \ toAddr(1 0) = toAddr(1 0) + 8 \ \ with the address wrapped around as required DEX \ Decrement the counter in X to move on to the next \ character block in the row BNE drow1 \ Loop back until we have copied all 40 character blocks \ in the character row from fromAddr(1 0) to toAddr(1 0) .drow3 RTS \ Return from the subroutineName: ShowBufferRow [Show more] Type: Subroutine Category: Screen buffer Summary: Update the player's scrolling landscape view by copying an 8-pixel high character row from the screen buffer into screen memoryContext: See this subroutine on its own page References: This subroutine is called as follows: * ShowIconBuffer calls ShowBufferRow * ShowScreenBuffer calls ShowBufferRow * ShowBufferColumn calls via drow3
Arguments: fromAddr(1 0) The source address from which we copy the character row toAddr(1 0) The destination address to which we copy the character row
Other entry points: drow3 Contains an RTS.ShowBufferBlock LDY #0 \ Copy byte #0 of fromAddr(1 0) to toAddr(1 0) LDA (fromAddr),Y STA (toAddr),Y INY \ Copy byte #1 of fromAddr(1 0) to toAddr(1 0) LDA (fromAddr),Y STA (toAddr),Y INY \ Copy byte #2 of fromAddr(1 0) to toAddr(1 0) LDA (fromAddr),Y STA (toAddr),Y INY \ Copy byte #3 of fromAddr(1 0) to toAddr(1 0) LDA (fromAddr),Y STA (toAddr),Y INY \ Copy byte #4 of fromAddr(1 0) to toAddr(1 0) LDA (fromAddr),Y STA (toAddr),Y INY \ Copy byte #5 of fromAddr(1 0) to toAddr(1 0) LDA (fromAddr),Y STA (toAddr),Y INY \ Copy byte #6 of fromAddr(1 0) to toAddr(1 0) LDA (fromAddr),Y STA (toAddr),Y INY \ Copy byte #7 of fromAddr(1 0) to toAddr(1 0) LDA (fromAddr),Y STA (toAddr),Y RTS \ Return from the subroutineName: ShowBufferBlock [Show more] Type: Subroutine Category: Screen buffer Summary: Update the player's scrolling landscape view by copying an 8-byte character block from the screen buffer into screen memoryContext: See this subroutine on its own page References: This subroutine is called as follows: * ShowBufferColumn calls ShowBufferBlock * ShowBufferRow calls ShowBufferBlock
Arguments: fromAddr(1 0) The source address from which we copy the eight-byte character block toAddr(1 0) The destination address to which we copy the eight-byte character block.updateOffsetLo EQUB LO(320 - 8) \ Direction 0 = 320 - 8 (pan right, scroll left) EQUB LO(0) \ Direction 1 = 0 (pan left, scroll right) EQUB LO(0) \ Direction 2 = 0 (pan up, scroll down) EQUB LO(320 * 23) \ Direction 3 = 320 * 23 (pan down, scroll up)Name: updateOffsetLo [Show more] Type: Variable Category: Screen buffer Summary: The offset within screen memory for the player's landscape view of the area to update following a scroll (low byte)Context: See this variable on its own page References: This variable is used as follows: * ScrollPlayerView uses updateOffsetLo.updateOffsetHi EQUB HI(320 - 8) \ Direction 0 = 320 - 8 (pan right, scroll left) EQUB HI(0) \ Direction 1 = 0 (pan left, scroll right) EQUB HI(0) \ Direction 2 = 0 (pan up, scroll down) EQUB HI(320 * 23) \ Direction 3 = 320 * 23 (pan down, scroll up)Name: updateOffsetHi [Show more] Type: Variable Category: Screen buffer Summary: The offset within screen memory for the player's landscape view of the area to update following a scroll (high byte)Context: See this variable on its own page References: This variable is used as follows: * ScrollPlayerView uses updateOffsetHi.scrollScreenLo EQUB LO(+8) \ Direction 0 = +8 (pan right, scroll left) EQUB LO(-8) \ Direction 1 = -8 (pan left, scroll right) EQUB LO(-320) \ Direction 2 = -320 (pan up, scroll down) EQUB LO(+320) \ Direction 3 = +320 (pan down, scroll up)Name: scrollScreenLo [Show more] Type: Variable Category: Screen buffer Summary: The amount to change the start of screen memory in order to scroll the player's landscape view through each direction (low byte)Context: See this variable on its own page References: This variable is used as follows: * ScrollPlayerView uses scrollScreenLo * ShowScreenBuffer uses scrollScreenLo.scrollScreenHi EQUB HI(+8) \ Direction 0 = +8 (pan right, scroll left) EQUB HI(-8) \ Direction 1 = -8 (pan left, scroll right) EQUB HI(-320) \ Direction 2 = -320 (pan up, scroll down) EQUB HI(+320) \ Direction 3 = +320 (pan down, scroll up)Name: scrollScreenHi [Show more] Type: Variable Category: Screen buffer Summary: The amount to change the start of screen memory in order to scroll the player's landscape view through each direction (high byte)Context: See this variable on its own page References: This variable is used as follows: * ScrollPlayerView uses scrollScreenHi * ShowScreenBuffer uses scrollScreenHi.screenBufferHi EQUB HI(screenBufferRow0 + 0 * 8 - 8) \ Direction 0 \ \ Pan right, scroll left \ \ screenBufferRow0 = base address of \ screen buffer \ \ 0 * 8 = the left column (column 0) \ of the 16-column screen \ buffer \ \ -8 = - scrollScreen(Hi Lo) \ = - +8 EQUB HI(screenBufferRow0 + 15 * 8 + 8) \ Direction 1 \ \ Pan left, scroll right \ \ screenBufferRow0 = base address of \ screen buffer \ \ 15 * 8 = the right column (column \ 15) of the 16-column \ screen buffer \ \ 8 = - scrollScreen(Hi Lo) \ = - -8 EQUB HI(screenBufferRow0 + 7 * 320 + 320) \ Direction 2 \ \ Pan up, scroll down \ \ screenBufferRow0 = base address of \ screen buffer \ \ 8 * 320 = the bottom row (row 7) \ of the 8-row screen \ buffer \ \ 320 = - scrollScreen(Hi Lo) \ = - -320 EQUB HI(screenBufferRow0 + 0 * 320 - 320) \ Direction 3 \ \ Pan down, scroll up \ \ screenBufferRow0 = base address of \ screen buffer \ \ 8 * 320 = the top row (row 0) of \ the 8-row screen buffer \ \ -320 = - scrollScreen(Hi Lo) \ = - +320Name: screenBufferHi [Show more] Type: Variable Category: Screen buffer Summary: The value to add to scrollScreenHi for each direction to get the high byte of the screen buffer address of the content to scroll inContext: See this variable on its own page References: This variable is used as follows: * SetBufferAddress uses screenBufferHi
This table contains the address within the screen buffer from which the interrupt handler should start pulling screen content to scroll onto the screen, depending on the direction of the scroll. The content that should be pulled onto the screen at the start of the scrolling process is as follows: * When panning right: The left column of the column-shaped screen buffer appears first as the new content scrolls in from the right * When panning left: The right column of the column-shaped screen buffer appears first as the new content scrolls in from the right * When panning up: The bottom row of the row-shaped screen buffer appears first as the new content scrolls in from above * When panning down: The top row of the row-shaped screen buffer appears first as the new content scrolls in from below This table contains the address of the new content for each panning direction, but with a slight complication. The first step in scrolling content onto the screen is to add the scrolling direction in scrollScreen(Hi Lo), so to make sure the first scroll moves the correct content onto the screen, the values in this table already have the corresponding value of scrollScreen(Hi Lo) subtracted from them, so when this is added at the start of the scroll, the address correctly points to the address of the new content in the screen buffer. The screen buffer starts at address screenBufferRow0, each character column takes up eight bytes of buffer space, and each character row takes up 320 bytes of buffer space..screenBufferLo EQUB LO(screenBufferRow0 + 0 * 8 - 8) \ Direction 0 \ \ Pan right, scroll left \ \ screenBufferRow0 = base address of \ screen buffer \ \ 0 * 8 = the left column (column 0) \ of the 16-column screen \ buffer \ \ -8 = - scrollScreen(Hi Lo) \ = - +8 EQUB LO(screenBufferRow0 + 15 * 8 + 8) \ Direction 1 \ \ Pan left, scroll right \ \ screenBufferRow0 = base address of \ screen buffer \ \ 15 * 8 = the right column (column \ 15) of the 16-column \ screen buffer \ \ 8 = - scrollScreen(Hi Lo) \ = - -8 EQUB LO(screenBufferRow0 + 7 * 320 + 320) \ Direction 2 \ \ Pan up, scroll down \ \ screenBufferRow0 = base address of \ screen buffer \ \ 8 * 320 = the bottom row (row 7) \ of the 8-row screen \ buffer \ \ 320 = - scrollScreen(Hi Lo) \ = - -320 EQUB LO(screenBufferRow0 + 0 * 320 - 320) \ Direction 3 \ \ Pan down, scroll up \ \ screenBufferRow0 = base address of \ screen buffer \ \ 8 * 320 = the top row (row 0) of \ the 8-row screen buffer \ \ -320 = - scrollScreen(Hi Lo) \ = - +320Name: screenBufferLo [Show more] Type: Variable Category: Screen buffer Summary: The value to add to scrollScreenLo for each direction to get the low byte of the screen buffer address of the content to scroll inContext: See this variable on its own page References: This variable is used as follows: * SetBufferAddress uses screenBufferLo
See screenBufferHi for an explanation of this table..panAngleToUpdate EQUB 20 \ Direction 0 (pan right, scroll left) EQUB -8 \ Direction 1 (pan left, scroll right) EQUB 4 \ Direction 2 (pan up, scroll down) EQUB -12 \ Direction 3 (pan down, scroll up)Name: panAngleToUpdate [Show more] Type: Variable Category: Drawing the landscape Summary: Pitch and yaw angles for panning the landscape view, so the output of DrawLandscapeView will be the bit we add when updating the viewContext: See this variable on its own page References: This variable is used as follows: * PanLandscapeView uses panAngleToUpdate
This table contains the angles through which we need to pitch or yaw the landscape view when we need to scroll the landscape view during a pan. The DrawLandscapeView routine always renders the full view, and its output is clipped to the size of the screen or screen buffer. This clipping is done on the screen x- and y-coordinates, and the clipping is done from the right or bottom edges of the screen. This means that when we pan the screen, we effectively redraw the whole view, which is 20 yaw angles wide and 12 pitch angles tall, and then use the newly drawn portion from the top-left corner of that redrawn view for the new part that we scroll into the screen to complete the pan. When panning left, we can simply subtract 8 from the player's current yaw angle to rotate the player's view to the left. This moves the player's gaze to the left, so when we draw the new landscape view into the screen buffer, the portion on the left of the view is the new part that the player is now seeing, so clipping the left portion of the view into the screen buffer will give us the content that we need to scroll in from the left to perform the pan. Things are different when panning right. If we simply add 8 to the player's current yaw angle, then the new part of the landscape view will be on the far right of the newly drawn view, so it will be clipped when drawing into the screen buffer. So instead we add 20 to the player's yaw angle to rotate the view to the right by an entire screen's width, so when the view is drawn and clipped, the new portion will be on the left of the newly drawn view, just as it was when panning left. As long as we make sure to set the player's yaw angle correctly after we have finished drawing - by subtracting 12 from the player's yaw angle, leaving them with a net rotation of 8 to the right - then this approach ensures that the new portion of the screen is drawn into the screen buffer correctly. The same approach is applied when panning vertically. If we pan up, then we simply add 4 to the player's yaw angle so they look up higher. This moves the player's gaze up, so when we draw the new landscape view into the screen buffer, the portion at the top of the view is the new part that the player is now seeing, so clipping the top portion of the view into the screen buffer will give us the content that we need to scroll in from the top to perform the pan. However, if we are panning down then we need to jump down a whole screen's height, so we subtract 12 from the player's pitch angle, as this is the height of the screen. We then draw the new view, which puts the new portion of the view into the screen buffer. We then fix the player's pitch angle by adding 8, so the net result is a pitch rotation of -4, as required..StartScrollingView STA numberOfScrolls \ Store the number of scroll steps required in the \ variable \ \ When this variable is non-zero, the interrupt handler \ will scroll the screen by this many steps in the \ background \ Fall through into SetBufferAddress to set the address \ from which the interrupt handler should start fetching \ new content to scroll onto the screenName: StartScrollingView [Show more] Type: Subroutine Category: Screen buffer Summary: Start a scroll process in the background by setting the number of scroll steps and the address to start copying new content fromContext: See this subroutine on its own page References: This subroutine is called as follows: * PanLandscapeView calls StartScrollingView.SetBufferAddress LDA screenBufferLo,Y \ Set screenBufferAddr(1 0) to the Y-th entry from the STA screenBufferAddr \ screenBufferHi and screenBufferLo lookup tables LDA screenBufferHi,Y \ STA screenBufferAddr+1 \ This sets screenBufferAddr(1 0) to the address from \ which the interrupt handler should start pulling \ screen content to scroll onto the screen, so: \ \ * When panning right: The left column of the \ column-shaped screen buffer appears first as the \ new content scrolls in from the right \ \ * When panning left: The right column of the \ column-shaped screen buffer appears first as the \ new content scrolls in from the right \ \ * When panning up: The bottom row of the row-shaped \ screen buffer appears first as the new content \ scrolls in from above \ \ * When panning down: The top row of the row-shaped \ screen buffer appears first as the new content \ scrolls in from below \ \ So this sets screenBufferAddr(1 0) to the address \ where the interrupt handler should start fetching new \ content to scroll onto the screen \ \ See the documentation for screenBufferHi for a \ deeper look into the exact values that are set RTS \ Return from the subroutineName: SetBufferAddress [Show more] Type: Subroutine Category: Screen buffer Summary: Set screenBufferAddr(1 0) to the address from which the interrupt handler should fetch new content to scroll onto the screenContext: See this subroutine on its own page References: This subroutine is called as follows: * DrawUpdatedObject calls SetBufferAddress
Arguments: Y The direction of scrolling that we are applying: * 0 = pan right * 1 = pan left * 2 = pan up * 3 = pan down.UseRowBuffer LDA #0 \ Call ConfigureBuffer with A = 0 to set up the screen JSR ConfigureBuffer \ buffer for use as a row buffer LDY #0 \ Set Y = 0 so we set the maximum and minimum pitch \ angles for the row buffer in the following .rbuf1 LDA bufferMaxPitch,Y \ Set maxPitchAngle to the maximum pitch angle allowed STA maxPitchAngle \ in the buffer specified in Y LDA bufferMinPitch,Y \ Set minPitchAngle to the minimum pitch angle allowed STA minPitchAngle \ in the buffer specified in Y RTS \ Return from the subroutineName: UseRowBuffer [Show more] Type: Subroutine Category: Screen buffer Summary: Configure the row buffer for useContext: See this subroutine on its own page References: This subroutine is called as follows: * PanLandscapeView calls UseRowBuffer * SetColumnBufferMax calls via rbuf1
Other entry points: rbuf1 Set the minimum and maximum pitch angled for the buffer type specified in Y (0 = row buffer, 1 = column buffer).bufferMaxPitch EQUB 240 \ Row buffer contains pitch angles from 176 to 240 EQUB 240 \ Column buffer contains pitch angles from 48 to 240Name: bufferMaxPitch [Show more] Type: Variable Category: Screen buffer Summary: Maximum allowed pitch angles for points in the screen bufferContext: See this variable on its own page References: This variable is used as follows: * UseRowBuffer uses bufferMaxPitch.bufferMinPitch EQUB 176 \ Row buffer contains pitch angles from 176 to 240 EQUB 48 \ Column buffer contains pitch angles from 48 to 240Name: bufferMinPitch [Show more] Type: Variable Category: Screen buffer Summary: Minimum allowed pitch angles for points in the screen bufferContext: See this variable on its own page References: This variable is used as follows: * UseRowBuffer uses bufferMinPitch.UseColumnBuffer LDA #2 \ Call ConfigureBuffer with A = 2 to set up the screen JSR ConfigureBuffer \ buffer for use as a column buffer \ Fall through into SetColumnBufferMax to set the \ maximum and minimum pitch angles for the column bufferName: UseColumnBuffer [Show more] Type: Subroutine Category: Screen buffer Summary: Configure the column buffer for useContext: See this subroutine on its own page References: This subroutine is called as follows: * PanLandscapeView calls UseColumnBuffer.SetColumnBufferMax LDY #1 \ Set Y = 1 so we set the maximum and minimum pitch \ angles for the column buffer when we jump to rbuf1 BNE rbuf1 \ Jump to rbuf1 to set the maximum and minimum pitch \ angles for the column buffer and return from the \ subroutine using a tail call (this BNE is effectively \ a JMP as Y is never zero)Name: SetColumnBufferMax [Show more] Type: Subroutine Category: Screen buffer Summary: Set the maximum and minimum pitch angles for the column bufferContext: See this subroutine on its own page References: This subroutine is called as follows: * PanLandscapeView calls SetColumnBufferMax * ResetTilesObjects calls SetColumnBufferMax[X]Subroutine CheckForKeyPresses (category: Keyboard)Check for various game key presses and update the key logger and relevant variables (during the interrupt handler)[X]Subroutine ClearScreen (category: Graphics)Clear the screen to a specified background[X]Subroutine ConfigureBuffer (category: Screen buffer)Set up the variables required to configure the screen buffer to a specific buffer type[X]Subroutine DefineEnvelope (category: Sound)Define a sound envelope[X]Subroutine DigitToNumber (category: Text)Convert a digit from the input buffer into a number[X]Subroutine DrawBlackDots (category: Graphics)Draw 80 randomly positioned dots on the screen in colour 1 (black)[X]Subroutine DrawIcon (category: Scanner/energy row)Draw a single icon in the top-left corner of the screen (via the icon screen buffer at iconBuffer) and move along to the right[X]Subroutine DrawLandscapeView (Part 1 of 3) (category: Drawing the landscape)Set up a number of variables for drawing the landscape view[X]Subroutine FinishLandscape (category: Main game loop)Add the player's energy to the landscape number to get the number of the next landscape and display that landscape's secret code[X]Subroutine FlushBuffer (category: Keyboard)Flush the specified buffer[X]Subroutine FlushSoundBuffers (category: Sound)Flush all four sound channel buffers[X]Subroutine GetIconRowAddress (category: Scanner/energy row)Calculate the address in screen memory of the icon and scanner row at the top of the screen[X]Subroutine GetNextSeedAsBCD (category: Maths (Arithmetic))Set A to the next number from the landscape's sequence of seed numbers, converted to a binary coded decimal (BCD) number[X]Subroutine GetNextSeedNumber (category: Maths (Arithmetic))Set A to a seed number[X]Subroutine GetTileVisibility (category: Drawing the landscape)For each tile in the landscape, calculate whether the player can see that tile, to speed up the process of drawing the landscape[X]Subroutine InitialiseSeeds (category: Landscape)Initialise the seed number generator so it generates the sequence of seed numbers for a specific landscape number[X]Subroutine MainGameLoop (category: Main game loop)The main game loop for playing a landscape[X]Subroutine MainTitleLoop (category: Main title loop)The main title loop: display the title screen, fetch the landscape number/code, preview the landscape and jump to the main game loop[X]Subroutine MakeSound (category: Sound)Make a sound[X]Entry point MakeSound-6 in subroutine MakeSound (category: Sound)Make a two-part sound at the pitches defined in X and Y, where X is the pitch for sound data block #0 and Y is the pitch for sound data block #1[X]Subroutine MakeSoundEnvelope (category: Sound)Either make a sound or set up an envelope[X]Configuration variable OSBYTEThe address for the OSBYTE routine[X]Configuration variable OSWORDThe address for the OSWORD routine[X]Subroutine PanLandscapeView (category: Drawing the landscape)Pan the landscape and update the landscape view[X]Subroutine PlayMusic (category: Sound)Play a piece of music[X]Subroutine Print2DigitBCD (category: Text)Print a binary coded decimal (BCD) number using two digits[X]Subroutine PrintNumber (category: Text)Print a number as a single digit, printing zero as a capital "O"[X]Subroutine PrintTextToken (category: Text)Print a recursive text token[X]Subroutine PrintVduCharacter (category: Text)Print a one-byte character from a text token or a multi-byte VDU 25 command[X]Subroutine ProcessCharacter (category: Text)Process and print a character from a text token, which can encode another text token or be a one-byte character or VDU command[X]Subroutine ProcessGameplay (category: Gameplay)A gameplay loop that processes all game key presses, returning to the main game loop when the player moves, quits, loses or pans[X]Subroutine ProcessMusic (category: Sound)Play the configured music in the background[X]Subroutine ProcessSound (category: Sound)Process any sound effects that have been configured so they play in the background (this is called regularly throughout gameplay)[X]Subroutine ProcessVolumeKeys (category: Sound)Adjust the volume of the sound envelopes when the volume keys are pressed[X]Subroutine ReadKeyboard (category: Keyboard)Enable the keyboard and read a character from it[X]Subroutine ResetTilesObjects (category: Main title Loop)Reset the tile visibility table and deallocate all object numbers[X]Subroutine ResetVariables (category: Main title Loop)Reset all the game's main variables[X]Configuration variable SHEILAMemory-mapped space for accessing internal hardware, such as the video ULA, 6845 CRTC and 6522 VIAs (also known as SHEILA)[X]Subroutine ScanForGameKeys (category: Keyboard)Scan for game key presses and update the key logger[X]Subroutine ScrollPlayerView (category: Graphics)Scroll the screen and copy data from the screen buffer into screen memory to implement the player's scrolling landscape view[X]Subroutine SetColourPalette (category: Graphics)Set the logical colours for each of the four physical colours in screen mode 5[X]Subroutine SetScannerAndPause (category: Main game loop)Set the scanner update status and delay for 40 empty loops of 256 iterations each (i.e. 10,240 loops)[X]Subroutine ShowBufferBlock (category: Screen buffer)Update the player's scrolling landscape view by copying an 8-byte character block from the screen buffer into screen memory[X]Subroutine ShowBufferRow (category: Screen buffer)Update the player's scrolling landscape view by copying an 8-pixel high character row from the screen buffer into screen memory[X]Subroutine ShowGameOverScreen (category: Title screen)Display the game over screen[X]Subroutine ShowIconBuffer (category: Screen buffer)Display the redrawn icon and scanner row by copying the contents of the icon screen buffer into screen memory[X]Subroutine SpawnCharacter3D (Part 1 of 2) (category: Title screen)Spawn a character on the landscape in large 3D blocks for drawing on the main title screen or secret code screen[X]Subroutine UpdateEnemyTimers (category: Gameplay)Update the timers that control the enemy tactics[X]Subroutine UpdateIconsScanner (category: Scanner/energy row)Update the icons in the top-left corner of the screen to show the player's current energy level and redraw the scanner box[X]Subroutine UpdateScanner (category: Scanner/energy row)Update the scanner, if required[X]Subroutine UpdateScannerNow (category: Scanner/energy row)Update the scanner to a new state[X]Variable aStoreEnvelope (category: Sound)Temporary storage for use in the DefineEnvelope routine[X]Variable activateSentinel in workspace Main variable workspaceA flag to record when the Sentinel is activated at the start of the game (by the player pressing a key that expends or absorbs energy[X]Variable bufferMaxPitch (category: Screen buffer)Maximum allowed pitch angles for points in the screen buffer[X]Variable bufferMinPitch (category: Screen buffer)Minimum allowed pitch angles for points in the screen buffer[X]Label char1 in subroutine ProcessCharacter[X]Label dcol1 in subroutine ShowBufferColumn[X]Label dcol2 in subroutine ShowBufferColumn[X]Label dcol3 in subroutine ShowBufferColumn[X]Label dnum1 in subroutine DigitToNumber[X]Label dnum2 in subroutine DigitToNumber[X]Variable doNotDitherObject in workspace Main variable workspaceControls whether the DitherScreenBuffer routine can update an object on the screen using a dithered effect[X]Variable drawLandscape in workspace Main variable workspaceConfigures whether to draw the landscape behind the object in the DrawUpdatedObject routine[X]Label drow1 in subroutine ShowBufferRow[X]Label drow2 in subroutine ShowBufferRow[X]Entry point drow3 in subroutine ShowBufferRow (category: Screen buffer)Contains an RTS[X]Label dsec1 in subroutine SpawnSecretCode3D[X]Label dsec2 in subroutine SpawnSecretCode3D[X]Label enec1 in subroutine ClearIconsScanner[X]Label enem1 in subroutine GetEnemyCount[X]Label enem2 in subroutine GetEnemyCount[X]Label enem3 in subroutine GetEnemyCount[X]Label enem4 in subroutine GetEnemyCount[X]Label ener1 in subroutine UpdateIconsScanner[X]Label ener2 in subroutine UpdateIconsScanner[X]Label ener3 in subroutine UpdateIconsScanner[X]Label ener4 in subroutine UpdateIconsScanner[X]Label ener5 in subroutine UpdateIconsScanner[X]Variable envelopeData (category: Sound)Data for the six sound envelopes[X]Variable envelopeVolumes (category: Sound)A table of offsets into the envelope data for bytes that control the volume of each envelope, so we can change their volume levels[X]Label fbuf1 in subroutine FlushSoundBuffers[X]Variable focusOnKeyAction in workspace Main variable workspaceA flag that determines whether the game should focus effort on implementing a key action, such as a pan of the landscape view[X]Label game1 in subroutine MainGameLoop[X]Label game11 in subroutine MainGameLoop[X]Label game12 in subroutine MainGameLoop[X]Label game13 in subroutine MainGameLoop[X]Label game14 in subroutine MainGameLoop[X]Label game2 in subroutine MainGameLoop[X]Label game3 in subroutine MainGameLoop[X]Label game4 in subroutine MainGameLoop[X]Label game5 in subroutine MainGameLoop[X]Label game6 in subroutine MainGameLoop[X]Label game7 in subroutine MainGameLoop[X]Label game8 in subroutine MainGameLoop[X]Label game9 in subroutine MainGameLoop[X]Variable gameInProgress in workspace Main variable workspaceFlags whether or not a game is in progress (i.e. the player is playing a landscape rather than interacting with the title and preview screens)[X]Variable gameOverSoundPitch in workspace Main variable workspaceA timer for the game over sound[X]Variable gamePaused in workspace Main variable workspaceA flag to record whether the game is paused[X]Variable hyperspaceEndsGame in workspace Main variable workspaceRecords whether the player performing a hyperspace has just ended the game[X]Variable iconBuffer (category: Graphics)The icon screen buffer, which is used to buffer the energy icon and scanner row before writing to screen memory[X]Variable iconData (category: Graphics)Screen mode 5 bitmap data for the ten icons that make up the energy icon and scanner row at the top of the screen[X]Variable iconRowAddr in workspace Main variable workspaceThe screen address of the icon and scanner row along the top of the screen[X]Variable inputBuffer in workspace Main variable workspaceThe eight-byte keyboard input buffer[X]Variable irq1Address (category: Main game loop)Stores the previous value of IRQ1V before we install our custom IRQ handler[X]Label irqh1 in subroutine IRQHandler[X]Label irqh2 in subroutine IRQHandler[X]Label irqh3 in subroutine IRQHandler[X]Label irqh4 in subroutine IRQHandler[X]Label irqh5 in subroutine IRQHandler[X]Label irqh6 in subroutine IRQHandler[X]Label irqh7 in subroutine IRQHandler[X]Label irqh8 in subroutine IRQHandler[X]Variable keepCheckingPanKey in workspace Main variable workspaceControls whether the DrawLandscapeView routine aborts drawing if the pan key is released before it has finished[X]Variable keyLogger in workspace Main variable workspaceThe four-byte key logger for logging game key presses[X]Variable landscapeNumberHi in workspace Main variable workspaceThe high byte of the four-digit binary coded decimal landscape number (0000 to 9999)[X]Variable landscapeNumberLo in workspace Main variable workspaceThe low byte of the four-digit binary coded decimal landscape number (0000 to 9999)[X]Variable landscapeZero in workspace Main variable workspaceA flag that is set depending on whether we are playing landscape 0000[X]Variable lastPanKeyPressed in workspace Zero pageThe direction of the last pan key that was pressed (which may not still be held down)[X]Variable loopCounter in workspace Zero pageA general-purpose loop counter[X]Variable lowNibbleBCD (category: Maths (Arithmetic))Storage for the low nibble when constructing a BCD seed number in the GetNextSeedAsBCD routine[X]Entry point main4 in subroutine MainTitleLoop (category: Main title loop)The entry point for restarting a landscape after dying, so the player doesn't have to enter the landscape's secret code again[X]Variable maxNumberOfEnemies in workspace Main variable workspaceThe maximum number of enemies that can appear on the current landscape, which is calculated as follows[X]Variable maxPitchAngle in workspace Zero pageThe maximum pitch angle that fits into the current screen buffer[X]Variable minPitchAngle in workspace Zero pageThe minimum pitch angle that fits into the current screen buffer[X]Label musi1 in subroutine ProcessMusic[X]Label musi2 in subroutine ProcessMusic[X]Label musi3 in subroutine ProcessMusic[X]Label musi4 in subroutine ProcessMusic[X]Variable musicCounter in workspace Main variable workspaceA counter for the music currently being made, which counts up in the ProcessMusic routine while the music is being played[X]Variable musicData (category: Sound)Data for the game's music[X]Variable noteCounter in workspace Main variable workspaceThe sound counter for an individual note while playing chords in the music player[X]Variable numberOfScrolls in workspace Main variable workspaceThe total number of scrolls that we need to perform for the current panning operation[X]Variable panKeyBeingPressed in workspace Zero pageThe direction in which the player is currently panning[X]Label paws1 in subroutine ProcessPauseKeys[X]Label paws2 in subroutine ProcessPauseKeys[X]Variable playerEnergy in workspace Main variable workspaceThe player's energy level (in the range 0 to 63)[X]Variable playerIsOnTower in workspace Main variable workspaceA flag to record whether the player is on top of the the Sentinel's tower[X]Variable playerObject in workspace Zero pageThe number of the player object[X]Label plen1 in subroutine GetPlayerEnergyBCD[X]Label plen2 in subroutine GetPlayerEnergyBCD[X]Variable printTextIn3D in workspace Main variable workspaceControls whether we are printing text normally or in 3D (as in the game's title on the title screen)[X]Label psou1 in subroutine ProcessSound[X]Label psou2 in subroutine ProcessSound[X]Label psou3 in subroutine ProcessSound[X]Variable quitGame in workspace Main variable workspaceA flag to record whether the player has pressed function key f1 to quit the game[X]Label rbcd1 in subroutine GetNextSeedAsBCD[X]Label rbcd2 in subroutine GetNextSeedAsBCD[X]Entry point rbuf1 in subroutine UseRowBuffer (category: Screen buffer)Set the minimum and maximum pitch angled for the buffer type specified in Y (0 = row buffer, 1 = column buffer)[X]Variable screenAddrHi in workspace Zero pageThe high byte of the screen memory address in the ScrollPlayerView routine[X]Variable screenBufferAddr (category: Screen buffer)Storage for the address for the current drawing operation in the screen buffer[X]Variable screenBufferHi (category: Screen buffer)The value to add to scrollScreenHi for each direction to get the high byte of the screen buffer address of the content to scroll in[X]Variable screenBufferLo (category: Screen buffer)The value to add to scrollScreenLo for each direction to get the low byte of the screen buffer address of the content to scroll in[X]Subroutine screenBufferRow0 (category: Screen buffer)The screen buffer for character row 0[X]Variable screenOrBuffer in workspace Zero pageControls whether the graphics routines draw directly onto the screen, or into the screen buffer[X]Label scro1 in subroutine ScrollPlayerView[X]Label scro2 in subroutine ScrollPlayerView[X]Label scro3 in subroutine ScrollPlayerView[X]Variable scrollCounter in workspace Main variable workspaceA counter for the number of columns or rows we still need to scroll in the player's scrolling landscape view when the player pans[X]Variable scrollScreenHi (category: Screen buffer)The amount to change the start of screen memory in order to scroll the player's landscape view through each direction (high byte)[X]Variable scrollScreenLo (category: Screen buffer)The amount to change the start of screen memory in order to scroll the player's landscape view through each direction (low byte)[X]Label seed1 in subroutine InitialiseSeeds[X]Label seed2 in subroutine InitialiseSeeds[X]Variable seedNumberLFSR in workspace Main variable workspaceA five-byte linear feedback shift register for[X]Variable sentinelHasWon in workspace Main variable workspaceA flag to record when the player runs out of energy (i.e. the energy level goes negative), at which point the Sentinel wins[X]Variable sightsAreVisible in workspace Main variable workspaceControls whether the sights are being shown[X]Variable sightsByteCount in workspace Main variable workspaceThe number of screen bytes in the sights pixel byte stash that contain the contents of the screen behind the sights (so they can be restored to remove the sights)[X]Label snum1 in subroutine StringToNumber[X]Variable soundCounter in workspace Main variable workspaceA counter for the sound currently being made, which counts down in the IRQHandler routine at a rate of 50 times a second[X]Variable soundData (category: Sound)OSWORD blocks for making the various game sounds[X]Variable soundEffect in workspace Main variable workspaceDetermines how the current sound is processed by the ProcessSound routine[X]Variable soundNumberData (category: Sound)A table to map a sound number (0 to 6) to the sound data block(s) that are used to make the sound[X]Label text1 in subroutine PrintTextToken[X]Label text2 in subroutine PrintTextToken[X]Label tokenBase in variable tokenOffset[X]Variable tokenOffset (category: Text)Address offsets for the text tokens (each offset in the table is the offset of the token from tokenBase)[X]Variable uTurnStatus in workspace Main variable workspaceA flag to record whether we are performing or have just performed a U-turn[X]Variable updateOffsetHi (category: Screen buffer)The offset within screen memory for the player's landscape view of the area to update following a scroll (high byte)[X]Variable updateOffsetLo (category: Screen buffer)The offset within screen memory for the player's landscape view of the area to update following a scroll (low byte)[X]Variable viewScreenAddr in workspace Main variable workspaceThe screen address of the player's scrolling landscape view, which is just below the icon and scanner row at the top of the screen[X]Variable viewingObject in workspace Zero pageThe number of the viewing object[X]Label volk1 in subroutine ProcessVolumeKeys[X]Label volk2 in subroutine ProcessVolumeKeys[X]Label volk3 in subroutine ProcessVolumeKeys[X]Label volk4 in subroutine ProcessVolumeKeys[X]Label volk5 in subroutine ProcessVolumeKeys[X]Label volk6 in subroutine ProcessVolumeKeys[X]Variable volumeLevel (category: Sound)The volume level, which can be changed by pressing "7" and "8"[X]Variable xIconCounter in workspace Main variable workspaceA counter for drawing the icons in the top-left of the screen that show the player's energy level, as we work from left to right along the x-axis