├── 6502 ├── LICENSE.md └── README.md ├── 2liners ├── LICENSE.md ├── README.md └── the art of 2-liners │ ├── LICENSE.md │ ├── README.md │ ├── SCRN_PLOT_your_sound_routine.md │ └── img │ ├── README.md │ ├── hexstring_incode.gif │ ├── printplotsound1.gif │ ├── printplotsound2.gif │ ├── printsoundroutine.gif │ └── printsoundroutine80.gif ├── LICENSE.md ├── README.md ├── applesoft ├── README.md ├── ampersand │ └── bookmark │ │ └── README.md ├── nfs │ ├── README.md │ ├── calculations │ │ ├── 01_use_addition_instead_of_mul2.md │ │ └── 02_addition_is_faster_than_subtraction.md │ ├── general │ │ ├── 01_variables_for_constants.md │ │ ├── 02_declare_most_used_variables_first.md │ │ ├── 03_use_one_letter_variables_names.md │ │ └── 04_avoid_integer_variables.md │ └── program_structure │ │ └── 01_goto_do_it_right.md └── spc │ ├── README.md │ ├── spc1.png │ ├── spc2.png │ ├── spc3.png │ ├── spc4.png │ ├── spc5.png │ ├── spc6.png │ ├── spc7.png │ └── spc_test.bas ├── honoring_the_code ├── 001 - Space Maze │ ├── README.md │ └── files │ │ ├── README.md │ │ ├── htc1.png │ │ ├── htc1_spacemaze.dsk │ │ ├── htc2.png │ │ ├── htc3.png │ │ ├── spacemaze.bas │ │ ├── spacemaze_htc_v1.bas │ │ ├── spacemaze_quickfix.bas │ │ └── spacemaze_quickfix2.bas ├── 002 - lores tetris │ ├── README.md │ ├── htc2_tetris.bas │ ├── htc2_tetris.dsk │ ├── img │ │ ├── README.md │ │ ├── capture1.png │ │ ├── capture2.png │ │ ├── capture3.png │ │ └── capture4.png │ └── paleotronic_tetris.bas └── README.md ├── stranger_things ├── 003 - Inline Text Modes │ ├── README.md │ ├── img │ │ ├── README.md │ │ ├── hello.png │ │ ├── inverse.png │ │ ├── normal_inverse.png │ │ ├── poke_a.png │ │ └── print_a.png │ ├── meeting_the_beadmaster.bas │ ├── meeting_the_beadmaster.mkv │ └── st3_inline_text_modes.dsk └── README.md └── tools ├── 6502_assembler ├── 6502_SpASM-1.2.0-example.xlsx ├── 6502_SpASM-1.2.2.xlsx ├── 6502_assembler.png ├── 6502_assembler2.png └── README.md ├── LICENSE.md ├── README.md └── bitmap editor ├── LICENSE.md ├── README.md ├── apple2_hires.md ├── img ├── README.md ├── apple2_hires_hplot560.png ├── apple2_hires_hplot560detail.png ├── apple2_hires_line0.png ├── apple2_hires_lines0-64-128.png ├── apple2_hires_lines_8.png ├── apple2_hires_lines_triplets.png ├── apple2_hires_poke0-128.png ├── apple2_hires_poke0-128detail.png ├── apple2_hires_poke0-160detail.png ├── apple2_hires_poke_colorsdetail.png ├── bitmap_creator_bytes.png ├── bitmap_creator_example.png └── bitmap_creator_preshifted.png └── src ├── README.md └── bitmap_creator-1.0.1.xlsx /2liners/LICENSE.md: -------------------------------------------------------------------------------- 1 | # License 2 | All code and content in these pages, except noted otherwise, is copyright François Vander Linden. And although you're free to use and modify them, honesty suggests you give proper credit when due. And if you plan to use what's in here for ANY kind of commercial project, this is not allowed. Please contact me first: don't worry, I might let you use my stuff anyway. 3 | -------------------------------------------------------------------------------- /2liners/README.md: -------------------------------------------------------------------------------- 1 | # Applesoft Two-Liners 2 | Welcome to my Applesoft 2-liners repository ... 3 | 4 | Here you'll find some 2-liners I wrote, with each statement described but also a [guide](https://github.com/tilleul/apple2/tree/master/2liners/the%20art%20of%202-liners), a tutorial, name it how you see fit, about how to write 2-liners in Applesoft. 5 | 6 | If you want to meet other Apple II software enthusiasts, join this [Facebook group](https://www.facebook.com/groups/418327412201896). 7 | -------------------------------------------------------------------------------- /2liners/the art of 2-liners/LICENSE.md: -------------------------------------------------------------------------------- 1 | # License 2 | All code and content in these pages, except noted otherwise, is copyright François Vander Linden. And although you're free to use and modify them, honesty suggests you give proper credit when due. And if you plan to use what's in here for ANY kind of commercial project, this is not allowed. Please contact me first: don't worry, I might let you use my stuff anyway. 3 | -------------------------------------------------------------------------------- /2liners/the art of 2-liners/README.md: -------------------------------------------------------------------------------- 1 | # The Art of 2-liners in Applesoft 2 | 3 | You'll find here a series of articles about writing two-liners in Applesoft. 4 | 5 | ## Contents 6 | * Introduction 7 | * What are 2-liners 8 | * General restrictions 9 | * Common practices 10 | * Essentials 11 | * Living without IF/THEN 12 | * Living without GOTO 13 | * The search for the last character 14 | * Inverting stuff 15 | * Extra tips 16 | * Advanced techniques 17 | * Exiting without END 18 | * Get lost of DATA/READ loops 19 | * Extra techniques 20 | * INVERSE/PRINT your shape tables 21 | * Taking control of the interpreter 22 | * Working with custom assembly routines 23 | * The 1-byte taking up to 3-byte problem 24 | * [SCRN/PLOT your sound routine](SCRN_PLOT_your_sound_routine.md) 25 | * The big list of 2-liner tricks for every Applesoft instruction 26 | 27 | 28 | 29 | 30 | 31 | --- 32 | #### License 33 | See [License](LICENSE.md) 34 | -------------------------------------------------------------------------------- /2liners/the art of 2-liners/img/README.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /2liners/the art of 2-liners/img/hexstring_incode.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tilleul/apple2/9f533af8cd212a39e128679e054990c42e793745/2liners/the art of 2-liners/img/hexstring_incode.gif -------------------------------------------------------------------------------- /2liners/the art of 2-liners/img/printplotsound1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tilleul/apple2/9f533af8cd212a39e128679e054990c42e793745/2liners/the art of 2-liners/img/printplotsound1.gif -------------------------------------------------------------------------------- /2liners/the art of 2-liners/img/printplotsound2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tilleul/apple2/9f533af8cd212a39e128679e054990c42e793745/2liners/the art of 2-liners/img/printplotsound2.gif -------------------------------------------------------------------------------- /2liners/the art of 2-liners/img/printsoundroutine.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tilleul/apple2/9f533af8cd212a39e128679e054990c42e793745/2liners/the art of 2-liners/img/printsoundroutine.gif -------------------------------------------------------------------------------- /2liners/the art of 2-liners/img/printsoundroutine80.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tilleul/apple2/9f533af8cd212a39e128679e054990c42e793745/2liners/the art of 2-liners/img/printsoundroutine80.gif -------------------------------------------------------------------------------- /6502/LICENSE.md: -------------------------------------------------------------------------------- 1 | # License 2 | All code and content in these pages, except noted otherwise, is copyright François Vander Linden. And although you're free to use and modify them, honesty suggests you give proper credit when due. And if you plan to use what's in here for ANY kind of commercial project, this is not allowed. Please contact me first: don't worry, I might let you use my stuff anyway. 3 | -------------------------------------------------------------------------------- /6502/README.md: -------------------------------------------------------------------------------- 1 | This space contains Apple ][ 6502 stuff: code, games, tips'n'tricks, etc. 2 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # License 2 | All code and content in these pages, except noted otherwise, is copyright François Vander Linden. And although you're free to use and modify them, honesty suggests you give proper credit when due. And if you plan to use what's in here for ANY kind of commercial project, this is not allowed. Please contact me first: don't worry, I might let you use my stuff anyway. 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Apple ]\[ repository 2 | This repository will contain stuff related to the venerable Apple ]\[ computer. Mostly programming stuff. We'll see how it evolves. 3 | 4 | The first thing I want to do is write some kind of guide or tutorial to write 2-liners in Applesoft and probably present some of the 2-liners I wrote. 5 | 6 | Then I have several projects in assembly as well and maybe those will land here as well ... 7 | 8 | Only time will tell. 9 | 10 | ## 2-liners 11 | * [The art of 2-liners](https://github.com/tilleul/apple2/tree/master/2liners/the%20art%20of%202-liners): an incomplete attempt guide on how-to write 2-liners in Applesoft 12 | 13 | ## 6502 14 | (this section will feature 6502 code, games and tips'n'tricks 15 | 16 | ## Tools 17 | * [Bitmap Editor](https://github.com/tilleul/apple2/tree/master/tools/bitmap%20editor): An Excel spreadsheet to virtually create and render Apple ]\[ sprites/bitmaps. 18 | 19 | --- 20 | #### License 21 | All code and content in these pages, except noted otherwise, is copyright François Vander Linden. And although you're free to use and modify them, honesty suggests you give proper credit when due. And if you plan to use what's in here for ANY kind of commercial project, this is not allowed. Please contact me first: don't worry, I might let you use my stuff anyway. 22 | -------------------------------------------------------------------------------- /applesoft/README.md: -------------------------------------------------------------------------------- 1 | # Applesoft 2 | Some dedicated articles about Applesoft. This will improve over time. 3 | 4 | ## Tips'n'tricks 5 | * [How to use SPC() to repeat any kind of character](https://github.com/tilleul/apple2/tree/master/applesoft/spc) 6 | -------------------------------------------------------------------------------- /applesoft/ampersand/bookmark/README.md: -------------------------------------------------------------------------------- 1 | # Code Bookmarks in Applesoft 2 | 3 | ## Introducing the concept of bookmarks in code 4 | This is a proof-of-concept based on a simple idea: the values of string variables are referenced by pointers to locations inside the Applesoft tokenized code. 5 | 6 | Using these pointers, it should be possible to reference "bookmarks" (locations) inside the Applesoft code and jump to these bookmarks with an `&` subroutine. 7 | 8 | For example, type 9 | ```basic 10 | NEW 11 | 10 A$ = "HELLO": PRINT A$ 12 | RUN 13 | CALL-151 14 | 7FF (and press return at least 5 times) 15 | ``` 16 | This lists the following: 17 | ``` 18 | 0800: 00 14 08 0A 00 41 24 D0 19 | 0808: 22 48 45 4C 4C 4F 22 3A 20 | 0810: BA 41 24 00 00 00 41 80 21 | 0818: 05 09 08 00 00 22 | ``` 23 | Bytes \$801 to \$813 is the tokenized line 10. 24 | ``` 25 | 0801: 14 08 ; the next line (if any) will be found in $0814 26 | 0803: 0A 00 ; this line is line number 10 ($000A) 27 | 0805: 41 24 ; A$ 28 | 0807: D0 ; = 29 | 0808: 22 ; " (double-quote) 30 | 0809: 48 45 4C 4C 4F ; HELLO 31 | 080E: 22 ; " (double-quote) 32 | 080F: 3A ; : (colon) 33 | 0810: BA ; Token for PRINT statement 34 | 0811: 41 24 ; A$ 35 | 0813: 00 ; 00 marks the end of the line 36 | ``` 37 | Bytes \$814-\$815 indicate the end of the program (two zero bytes) 38 | Bytes \$816-\$81C is the simple variables table and in this case it describes `A$`: 39 | ``` 40 | 0816: 41 ; Character 'A' (first letter of the variable's name) 41 | 0817: 80 ; Second letter of the variable's name but with hi-bit set to indicate as string variable. 42 | As in this case, there's no second letter, the value is $80 (zero with the hi-bit set) 43 | 0818: ; length of the string 44 | 0819: 09 08 ; pointer to the variable's value ($0809) 45 | 081B: 00 00 ; unused bytes 46 | ``` 47 | And surely enough, in $0809, we can find the string value coded on 5 bytes: 48 | ``` 49 | 0809: 48 45 4C 4C 4F ; HELLO 50 | ``` 51 | The pointer to \$809 will point to this location until another `A$="..."` statement is found in the code, and accordingly the pointer will point to this new location. 52 | 53 | >The pointer will reference a location *within the code* as long as the contents of `A$` is a constant. If the contents of `A$` are calculated (as the result of string concatenation (`A$ = "HELLO" + "GOODBYE"`), string extraction (`A$ = LEFT$("HELLO", 4)`) or string conversion (`A$ = STR$(12345)`) then the pointer will reference a location between FRETOP (a value in zero page stored in \$6F-\$70) and MEMSIZ (whose value is in zero page too at \$73-\$74). 54 | 55 | >Also, if a new variable is created and that it is set equal to A$ (like `B$=A$`) then the pointer for the newly created variable will have the same value as the one for `A$`. If the contents of `A$` is subsequently changed, then `A$` will point to a new location while `B$` will still point to the location of the previous `A$`. 56 | 57 | Now that we have a pointer in the code **AND** the length of the string it points to, we can use both these to point to the next instruction **after** the initialisation of A\$ in line 10. In our case that is: 58 | 59 | ```basic 60 | : PRINT A$ 61 | ``` 62 | (notice the colon) 63 | or, as found in $080F 64 | ``` 65 | 080F: 3A BA 41 24 00 66 | ``` 67 | To reach that location, all we have to do is add the value of the pointer ($0809) to the length of the variable plus one: $0809 + 5 + 1 = $080F. 68 | 69 | This value has to be injected in the current location being parsed by Applesoft. This value is in TXTPTR (zero page \$B8-\$B9 within the CHRGOT routine). 70 | 71 | Also, we need to set CURLIN (a value in zero page representing the current line number the Applesoft parser is in). It's not essential as it's only used to print the line number in case of error but we want to be thorough. 72 | 73 | In order to find the current line number, all we have to do is search backwards from $080F until we find a "00" which indicates the end of the previous line. The trick is that this "00" could also be part of the line number if the line number is below 256. So another check is made 4 bytes earlier: if we find another "00" there then what we had was a part of a line number below 256. If not, it really was the end of the previous line. From there we read the actual line number and store it in CURLIN. 74 | 75 | ## What are bookmarks good for ? 76 | 77 | Using bookmarks is interesting, it's like a GOTO that goes back to a line previously parsed but with at least two advantages: 78 | 1. Speed. Searching the variable's table for a pointer is much more faster than searching for a line number, particularly because you probably have many more lines than variables and because the GOTO/GOSUB algorithm in Applesoft is so bad (*). 79 | 2. You can go back in the middle of a line of code. GOTO allows you to go back to the **beginning** of a line, not **anywhere** in the line. 80 | 3. It can be used with **any** string variable of course. 81 | 82 | (*) In fact, searching for a line is almost as fast as searching for a variable (55-65 cycles) but searching for variables from the list is faster simply because there are less variables in your code than there are lines (99.99% of the time). 83 | 84 | Searching for lines is also slower because when you use GOTO/GOSUB you'll either search from the next line in your code or from the very first line. You'll search from the next line in your code **only and only if** the line you search for has a number that is at least the next multiple of 256 in your program. Most of the time it's not the case. This is why the Applesoft ][ User Manual suggest you GOTO line numbers at the start of your code. 85 | 86 | It has some drawbacks too: 87 | 88 | 1. You can only go back on a line that's been parsed before, otherwise the variable won't be found in the table and so you'll have nowhere to go. There might be some ways to circumvent this, though. 89 | 2. You have to be careful to not mess with the variables you use as bookmarks. If you change the value of A$ later in the code, this is the location where the bookmark will be placed, from now on. Also you need to make sure you don't compute the value of A$ as this will bookmark a location outside of the code. 90 | 91 | ## Alpha implementation 92 | Here's a basic implementation of what's been said so far ... it's only a first draft, it might need optimization and improvement, we'll see about that later, it's only a proof of concept. 93 | 94 | The idea is that you can GOTO back to a bookmark with a command like `& A$`. Later this should be improved to use the `GOTO/GOSUB` command like `& GOTO A$`. But it's for later ... 95 | 96 | The following does not handle pointers outside the tokenized code ... yet. 97 | 98 | ```assembly 99 | amper_vector equ $03f5 100 | frmevl equ $dd7b 101 | chkstr equ $dd6c 102 | varpnt equ $83 103 | txtptr equ $b8 104 | chrgot equ $b7 105 | ptrget equ $dfe3 106 | oldlin equ $77 107 | curlin equ $75 108 | underr equ $d97c undefined statement error 109 | 110 | org $300 111 | 112 | 113 | 300: A9 4C lda #$4c prepare amper vector JMP $start 114 | 302: 8D F5 03 sta amper_vector 115 | 305: A9 10 lda #start 118 | 30C: 8D F7 03 sta amper_vector+2 119 | 30F: 60 rts 120 | start 121 | 310: 20 E3 DF jsr ptrget 122 | 313: 20 6C DD jsr chkstr check if string AND set carry ! 123 | 316: A0 00 ldy #0 124 | 318: B1 83 lda (varpnt),y length 125 | 31A: 8D 21 03 sta .offset+1 self modifying code 126 | 127 | 31D: C8 iny 128 | 31E: B1 83 lda (varpnt),y 129 | 130 | 320: 69 00 .offset adc #$00 add WITH carry SET ! (=length+1) 131 | 322: 85 B8 sta txtptr 132 | 324: 85 77 sta oldlin 133 | 326: C8 iny 134 | 327: B1 83 lda (varpnt),y 135 | 329: F0 12 beq .underr if zero then variable has just been created 136 | 32B: 69 00 adc #$00 add carry to pointer hi 137 | 32D: 85 B9 sta txtptr+1 138 | 32F: AA tax now we search the line number 139 | 330: CA dex 140 | 331: 86 78 stx oldlin+1 141 | 333: A0 FF ldy #$ff 142 | 335: B1 77 .loop lda (oldlin),y 143 | 337: F0 07 beq .out 144 | 339: 88 dey 145 | 33A: D0 F9 bne .loop 146 | 33C: 60 .rts rts we arrive here if y=0. We have scanned 255 chars and 147 | 33D: 4C 7C D9 .underr jmp underr failed finding a zero. It's impossible as lines entered 148 | with keyboard are max 239 chars, And tokenized it's less 149 | 340: 8C 4C 03 .out sty .save_y+1 save y 150 | 343: 88 dey y is of course above 3, so we can dey 4 times 151 | 344: 88 dey 152 | 345: 88 dey 153 | 346: 88 dey 154 | 347: B1 77 lda (oldlin),y get byte 4 positions earlier 155 | 349: F0 02 beq .save if it's zero, then we found the end of the previous line 156 | 34B: A0 00 .save_y ldy #$00 we didn't find a zero, restore previous y 157 | 34D: C8 .save iny advance to next line 158 | 34E: C8 iny skip ptrl to next line 159 | 34F: C8 iny skip ptrh 160 | 350: B1 77 lda (oldlin),y 161 | 352: 85 75 sta curlin 162 | 354: C8 iny 163 | 355: B1 77 lda (oldlin),y 164 | 357: 85 76 sta curlin+1 165 | 359: 60 rts 166 | ``` 167 | 168 | To test the code, type the following 169 | ``` 170 | CALL-151 171 | 300: A9 4C 8D F5 03 A9 10 8D F6 03 A9 03 8D F7 03 60 172 | 310: 20 E3 DF 20 6C DD A0 00 B1 83 8D 21 03 C8 B1 83 173 | 320: 69 00 85 B8 85 77 C8 B1 83 F0 12 69 00 85 B9 AA 174 | 330: CA 86 78 A0 FF B1 77 F0 07 88 D0 F9 60 4C 7C D9 175 | 340: 8C 4C 03 88 88 88 88 B1 77 F0 02 A0 00 C8 C8 C8 176 | 350: B1 77 85 75 C8 B1 77 85 76 60 177 | 178 | (press CTRL-C + return, going back to basic) 179 | 180 | CALL 768 (to initiate & vector) 181 | NEW 182 | 10 A$ = "HELLO": PRINT "THIS IS LINE 10 - A$="; A$; " - N=";N 183 | 20 N=N+1: IF N=1 THEN & A$ 184 | 30 A$ = "GOODBYE": PRINT "THIS IS LINE 30 - A$="; A$; " - N=";N 185 | 40 N=N+1: IF N = 2 THEN & A$ 186 | 50 B$ = A$: PRINT "B$=A$ NOW !": A$ = "HOORAY !": PRINT "THIS IS LINE 50 - A$="; A$; " - B$="; B$; " - N=";N 187 | 60 N = N + 1: IF N = 3 THEN & A$ 188 | 70 IF N= 4 THEN & B$ 189 | 80 PRINT "THIS IS THE END" 190 | RUN 191 | ``` 192 | The output should be 193 | ``` 194 | THIS IS LINE 10 - A$=HELLO - N=0 195 | THIS IS LINE 10 - A$=HELLO - N=1 196 | THIS IS LINE 30 - A$=GOODBYE - N=2 197 | B$=A$ NOW ! 198 | THIS IS LINE 50 - A$=HOORAY ! - B$=GOODBYE - N=3 199 | THIS IS LINE 30 - A$=HOORAY ! N=4 200 | B$=A$ NOW ! 201 | THIS IS LINE 50 - A$=HOORAY ! - B$=GOODBYE - N=5 202 | THIS IS THE END 203 | ``` 204 | 205 | I hope to improve this code in many ways: 206 | - use GOTO/GOSUB keywords and actually support `GOSUB` 207 | - support bookmarks that have not been parsed yet. This could be done with something like `& GOTO A$, 20`. This would **try** to go to the bookmark `A$`, but if it has not been parsed yet (or more exactly if `A$` does not exist yet), it will (1) create the variable `A$` and (2) search from line 20 for the code `A$="`. Once found, the pointer to `A$` (+ its length !) is modified accordingly. The next `& GOTO` to `A$` would then use the modified values. 208 | - support `ON >expr< GOTO/GOSUB` as well as `ONERR GOTO`. 209 | - support `GOTO/GOSUB >expr<`: if the code after GOTO/GOSUB is not a string variable, consider it a float `>expr<` and resolve to a line number and go to the next case 210 | - support `GOTO/GOSUB >line number<` and use a better algorithm than Applesoft's. 211 | - If the line number is equal to the current line, search for the current line start and go from there 212 | - if the line number is above the current line, search from the next line (Applesoft only search from the next line if the line to search is above or equal to the next multiple of 256). 213 | - if the line number is below the current line, search from the first line of the program. Exception: if it's statistically faster to search backwards from the current line, this approach should be used. 214 | -------------------------------------------------------------------------------- /applesoft/nfs/README.md: -------------------------------------------------------------------------------- 1 | # Applesoft: Need For Speed 2 | 3 | **So you like Applesoft ? And you think you can write an action game with it ? Or maybe a science program ? Yes, you can ... will it be fast ? ... Probably not ...** 4 | 5 | **BUT, WAIT ! ... Where there's light, there's hope !** 6 | 7 | **Here are several tricks you can use to optimize your Applesoft code for SPEED !** 8 | 9 | 10 | Writing a fast action game in Applesoft is an antinomy: Applesoft is not fast enough for fast action games. 11 | 12 | Well, that's almost always true ... but if your game 13 | 14 | * has simple game mechanic 15 | * does not involve too many moving objects (hero, enemies, missiles, etc.) 16 | * uses simple/minimalist graphics or no graphics at all 17 | * uses few calculations 18 | 19 | then, there might be a chance that it ends up fast enough to be enjoyable. 20 | 21 | Applesoft: The Need For Speed is a series of articles that explain why some coding techniques are faster than others. 22 | 23 | ## Summary 24 | ### Methodology 25 | 1. [Methodology explained](#methodology): learn how I've compared code snippets' speed 26 | ### General tips 27 | 1. [Use variables as placeholders for constant values](general/01_variables_for_constants.md): accessing a known value in a variable is faster than deciphering values in code. 28 | 2. [Declare your most used variables first](general/02_declare_most_used_variables_first.md): create and/or reference the variables you're going to use the most as soon1 as possible 29 | 3. [Use one-letter variables names whenever possible](general/03_use_one_letter_variables_names.md): longer variables names take longer to parse. 30 | 4. [Avoid integer variables](general/04_avoid_integer_variables.md): they are slower to use than float variables except for one case. And even then, they might be slower regardless. 31 | ### Calculations 32 | 1. [Use addition instead of multiplication by 2](calculations/01_use_addition_instead_of_mul2.md): double addition of the same variable is faster than multiplying the variable by 2 33 | 2. [Addition is faster than subtraction](calculations/02_addition_is_faster_than_subtraction.md): avoid subtraction whenever possible but don't use negative constants. 34 | 35 | ### Program Structure 36 | 1. [GOTO: Do it right](program_structure/01_goto_do_it_right.md): learn how to tame the limitations of GOTO and avoid GOSUB. 37 | 38 | (and many others) coming soon... 39 | 40 | ## Methodology 41 | Not only am I going to show you that some code is faster than other, I'm going to prove it ! 42 | 43 | In order to do that, I'm using [AppleWin](https://github.com/AppleWin/AppleWin), an Apple II emulator that has a cycle counting/difference feature. What I do is set a breakpoint within the Applesoft ``NEWSTT`` routine in ``$D801``. The ``NEWSTT`` routine is responsible for checking if there's a (new) statement to process, either on the same line (then, separated with a colon ``:``) or on a new line. In ``$D801`` a new line has been detected and is about to be executed (although there's first a check to see if ``TRACE`` is on and so if it's needed to print on the screen the line number being executed). So, except for a check here and there, setting a breakpoint in ``$D801`` will count the cycles needed to execute a whole line. It gives a good indication of the speed needed and can be used as a base for cycle counts comparisons. 44 | 45 | So, we are going to compare code snippets speed. For example, is it faster to divide a number by 2 or to multiply it by 0.5 ? To make sure we don't enter some special cases where values of ``zero`` are treated differently, we first initiate some variables, usually in line 10. The code we actually want to test will be in line 20 most of the time, while line 30 will be a simple ``END`` statement. ``END`` is not necessary normally to end a program but remember that the breakpoint in ``$D801`` only occurs when a **new line** is found, that's why we must finish our code with an ``END`` statement, on a new line 46 | 47 | Snippet #1: 48 | 49 | ```basic 50 | 10 A=18: B=2 51 | 20 C=A/B 52 | 30 END 53 | ``` 54 | 55 | Line 20 took 3959 cycles 56 | 57 | Snippet #2 58 | 59 | ```basic 60 | 10 A=18: B=0.5 61 | 20 C=A*B 62 | 30 END 63 | ``` 64 | 65 | This is faster as line 20 took only 3236 cycles, a difference of **723 cycles** ! (and you already have a first technique to increase speed, I'll explain it later). 66 | 67 | Notice that both snippets have the exact same result: variable ``C`` now holds the value ``9`` (which is 18 divided by 2 or 18 multiplied by 0.5). 68 | 69 | All our snippets will have the same final effect, otherwise we would not be comparing fairly. For example, the first snippet used a variable assignment in line 20 (``C=A/B``). For the second snippet, it's important we use another variable assignment (``C=A*B``) because we want to compare the speed of the multiplication and the speed of the division. If we had used ``A*B`` with a statement like ``PRINT A*B`` or ``K=PEEK(A*B)`` or ``HTAB A*B``, the cycles taken to handle the statement would disturb our measure and we would be comparing apples and oranges. 70 | 71 | It is also important that we did not use ``A=A*B``: even though it's a variable assignment, we would be reusing ``A`` and it has an impact on speed. If we want to reuse ``A`` then we need to do it in both snippets. 72 | 73 | The actual difference of **723 cycles** does not really matter. What is important is that the second snippet **actually runs** faster. Actual speed depends on several other factors which will be explained in this article. 74 | 75 | ### 🍎 Keep in mind the following 76 | 77 | * The cycles count on this page are only an indication of the speed of the code we want to "benchmark". 78 | * The exact cycle count is **not** what matters. 79 | * **Comparison** of cycles count is what we're studying. 80 | * Smaller cycles counts are faster and are considered as a technique to apply whenever possible. 81 | * Sometimes, if you're not careful, using a technique explained here could be **slower** if you don't pay attention to other factors. If that's the case, it will be explained. 82 | -------------------------------------------------------------------------------- /applesoft/nfs/calculations/01_use_addition_instead_of_mul2.md: -------------------------------------------------------------------------------- 1 | # Use addition instead of multiplication by 2 2 | Where fundamentals in mathematics might be useful for speed. 3 | ## Summary 4 | * [Multiplication is just another form of addition](#-multiplication-is-just-another-form-of-addition) 5 | * [Restrictions](#-restrictions) 6 | 7 | 8 | ## 🍎 Multiplication is just another form of addition. 9 | 10 | And when you're multiplying by 2, it's faster to use the addition counterpart. 11 | 12 | This is **always** true if what you want to do is ``A=2*B`` and that you use variables and replace hardcoded constants with variables. If you don't, you might get mitigated results. 13 | 14 | Demonstration: 15 | 16 | ```basic 17 | 10 A=123: B=2 18 | 20 C = A*B 19 | 30 END 20 | ``` 21 | 22 | Line 20 takes 3236 cycles. 23 | 24 | Snippet #2: 25 | 26 | ```basic 27 | 10 A=123: B=2 28 | 20 C = A+A 29 | 30 END 30 | ``` 31 | 32 | Line 20 takes now 2321 cycles, a bonus of 915 cycles. 33 | 34 | Of course it would be even more drastic if you didn't store (and use !) the constant ``2`` in variable ``B`` 35 | 36 | ```basic 37 | 10 A=123: B=2 38 | 20 C = A*2 39 | 30 END 40 | ``` 41 | 42 | Line 20 takes 3599 cycles, that's 1278 cycles more than using an addition ! 43 | 44 | Unfortunately, this tip does not work for anything else than multiplication by 2. Let's see what happens with multiplication by 3: 45 | 46 | ```basic 47 | 10 A=123: B=3 48 | 20 C = A*B 49 | 30 END 50 | ``` 51 | 52 | Line 20 takes 3236 cycles (again) 53 | While line 20 of snippet #2: 54 | 55 | ```basic 56 | 10 A=123: B=2 57 | 20 C = A+A+A 58 | 30 END 59 | ``` 60 | 61 | takes 3287 cycles, that is 51 cycles slower. Of course it gets worse with higher multiplication values. 62 | 63 | ## 🍎 Restrictions 64 | 65 | It's also important to notice that this will work only if you already have a variable with the value you want to double. 66 | 67 | Let's consider the following, you want to double the result of another calculation, like a division with code like ``D=2*A/B`` 68 | 69 | Snippet #1 70 | 71 | ```basic 72 | 10 A=123: B=45: C=2: D=0: E=0 73 | 20 E=C*A/B 74 | 30 END 75 | ``` 76 | 77 | Line 20 takes 6795 cycles. Notice how line 10 declares five variables ``A-E``. These variables will be used in the subsequent snippets. Declaring them, even though they're not used, allows us to ignore the extra cycles needed to create a new variable. 78 | 79 | Now let's try with the addition: 80 | 81 | ```basic 82 | 10 A=123: B=45: C=2: D=0: E=0 83 | 20 D=A/B+A/B 84 | 30 END 85 | ``` 86 | 87 | Line 20 takes 9072 cycles, which is slower (2277 cycles slower). 88 | Now you might think that storing the result of ``A/B`` would be faster. It's not. Except, maybe if you intend to use that result elsewhere in your code in which case it might be worth to spend those cycles storing a result in a variable. 89 | 90 | First snippet demonstrates the speed if you don't care about the result of ``A/B`` 91 | 92 | ```basic 93 | 10 A=123: B=45: C=2: D=0: E=0 94 | 20 D=A/B: E=D+D 95 | 30 END 96 | ``` 97 | 98 | Line 20 takes 7090 cycles, it's 295 cycles slower than using directly ``E=C*A/B``. 99 | 100 | This second snippet illustrates the speed if the result of ``A/B`` is of any interest and is meant to be reused several other times: it's thus calculated on line 10 and excluded from cycles count. 101 | 102 | ```basic 103 | 10 A=123: B=45: C=2: D=A/B: E=0 104 | 20 E=D+D 105 | 30 END 106 | ``` 107 | 108 | line 20 takes only 2409 cycles. Using ``20 E=C*D`` would take 2283 cycles more. 109 | -------------------------------------------------------------------------------- /applesoft/nfs/calculations/02_addition_is_faster_than_subtraction.md: -------------------------------------------------------------------------------- 1 | # Addition is faster than subtraction 2 | Where we learn that negativity might be a positive thing even though subtraction is not. 3 | ## Summary 4 | * [Simple cycles comparison](#-simple-cycles-comparison) 5 | * [Adding negative numbers is only slightly faster than subtraction](#-adding-negative-numbers-is-only-slightly-faster-than-subtraction) 6 | * [Avoiding subtraction: is it worth it ?](#-avoiding-subtraction-is-it-worth-it-) 7 | * [When is it worth to substitute subtraction with addition then ?](#-when-is-it-worth-to-substitute-subtraction-with-addition-then-) 8 | * [Recommendations](#-recommendations) 9 | 10 | ## 🍎 Simple cycles comparison 11 | 12 | ```basic 13 | 10 A = 123: B = 85: C = 0 14 | 20 C = A+B 15 | 30 END 16 | ``` 17 | 18 | The addition of ``A+B`` and assignment of the result to variable ``C`` in line 20 takes 2171 cycles. 19 | 20 | Now if we had this line instead 21 | 22 | ```basic 23 | 20 C = A-B 24 | ``` 25 | 26 | It would take 2327 cycles, a difference of **156** cycles in favor of addition. 27 | 28 | Knowing that, your initial intuition would be to replace subtraction with additions whenever possible. It's easy as all you have to do is to make the second operand negative. Unfortunately ... 29 | 30 | ## 🍎 Adding negative numbers is only slightly faster than subtraction 31 | 32 | With the previous example, if `B` is negative, we have 33 | 34 | ```basic 35 | 10 A = 123: B = -85: C = 0 36 | 20 C = A+B 37 | 30 END 38 | ``` 39 | 40 | Line 20 takes 2307 cycles, which is marginally faster (20 cycles) than subtracting a positive number. Is it always like that ? 41 | 42 | ## 🍎 Avoiding subtraction: is it worth it ? 43 | 44 | Here's a real life example where you might be tempted to add a negative number instead of subtracting a positive number to obtain the same result. 45 | 46 | Let's say you're trying to center the contents of ```A$``` on screen, your code will look like 47 | 48 | ```basic 49 | 10 D=2: V=20: A$ = "APPLESOFT: THE NEED FOR SPEED" 50 | 20 HTAB V-LEN(A$)/D 51 | 30 PRINT A$ 52 | 40 END 53 | ``` 54 | 55 | Line 20 takes 5846 cycles. Now, if you set `D=-2` and use addition instead of subtraction: 56 | 57 | ```basic 58 | 10 E=-2: V=20: A$ = "APPLESOFT: THE NEED FOR SPEED" 59 | 20 HTAB V+LEN(A$)/E 60 | 30 PRINT A$ 61 | 40 END 62 | ``` 63 | 64 | Line 20 now takes 5826 cycles, which is only 20 cycles faster. 65 | 66 | Even worse: it's likely you'll need the constant ``2`` somewhere in your code and so you'll probably need to assign it to a variable. So now your code looks like this: 67 | 68 | ```basic 69 | 10 D=2: V=20: A$ = "APPLESOFT: THE NEED FOR SPEED": E=-2 70 | 20 HTAB V+LEN(A$)/E 71 | 30 PRINT A$ 72 | 40 END 73 | ``` 74 | 75 | Line 20 takes now 5928 cycles ! This is 82 cycles **slower** than subtraction ! Even if we declare ``E`` earlier: 76 | 77 | ```basic 78 | 10 D=2: E=-2: V=20: A$ = "APPLESOFT: THE NEED FOR SPEED" 79 | ``` 80 | 81 | (line 20 is still an addition), this again takes 5928 cycles ! It's only when we declare ``E`` before ``D`` that 82 | 83 | ```basic 84 | 10 E=-2: D=2: V=20: A$ = "APPLESOFT: THE NEED FOR SPEED" 85 | ``` 86 | 87 | we see an improvement: 5894 cycles ! But compared to negation (5846 cycles), it is still slower ! We need to declare ``D`` last to see an advantage (because ``D`` is not used anymore in our snippets). 88 | 89 | ```basic 90 | 10 E=-2: V=20: A$ = "APPLESOFT: THE NEED FOR SPEED": D=2 91 | ``` 92 | 93 | We now have 5826 cycles for line 20, this is just 20 cycles faster than if we had used subtraction. 94 | 95 | As you can imagine, inverting the order of declaration of ``E`` and ``D`` is not worth it. ``D`` is now declared last, which might have an impact on speed on other parts of our code where the constant ``2`` is more important than ``-2`` ... 96 | 97 | ## 🍎 When is it worth to substitute subtraction with addition then ? 98 | 99 | Adding the negative of a number (instead of subtraction) has rarely a positive impact on speed. 100 | 101 | But there are other times when you can substitute negation and addition and gain something in return: in comparisons. 102 | 103 | You certainly know that 104 | 105 | ```basic 106 | IF A > B-C THEN ... 107 | ``` 108 | 109 | can be rewritten as 110 | 111 | ```basic 112 | IF A+C > B THEN ... 113 | ``` 114 | 115 | This also works with any other comparison operator: ``=``, ``<>``, ``>``, `<`, `>=` and `<=` 116 | 117 | Let's just see how much faster it is with a simple example. 118 | 119 | ```basic 120 | 10 A=10: B=20: C=9 121 | 20 IF A>B-C THEN D=A 122 | 30 END 123 | ``` 124 | 125 | Line 20 took 3253 cycles. Now if we replace line 20 with 126 | 127 | ```basic 128 | 20 IF A+C>B THEN D=A 129 | ``` 130 | 131 | it only takes 3099 cycles, which is 154 cycles faster. 132 | 133 | ## 🍎 Recommendations 134 | 135 | * Addition is faster than subtraction, so whenever possible use addition instead of subtraction, unless it means using a negative number for the second operand of the addition (*). 136 | 137 | * Because of that restriction, you probably won't be able to substitute subtraction with addition much for general calculations. 138 | 139 | * However, you will always improve the speed if the substitution occurs within a comparison. 140 | 141 | (*) Negative constants to avoid subtraction are bad: you would need to declare those before their positive counterparts and you would only win 20 cycles. And more than that, you would probably lose more cycles because your positive constants (and others) are declared later. 142 | -------------------------------------------------------------------------------- /applesoft/nfs/general/01_variables_for_constants.md: -------------------------------------------------------------------------------- 1 | # Use variables as placeholders for constant values 2 | Where we discover that *constantly* parsing *constants* is cycles consuming. 3 | ## Summary 4 | - [How Applesoft works with hardcoded constants](#-how-applesoft-works-with-hardcoded-constants) 5 | - [Use variables instead of constants](#-use-variables-instead-of-constants) 6 | - [Recommendations](#-recommendations) 7 | 8 | ## 🍎 How Applesoft works with hardcoded constants 9 | 10 | Let's consider a code like ``K=PEEK(49152)`` (this code gets the ASCII code of the last key pressed, plus 128 if the keyboard probe has not been reset and store it in variable ``K``). 11 | 12 | When this code is run , the Applesoft parser will perform the following: 13 | 14 | 1. search for a "real/float" variable named ``K``, and create one if needed 15 | 2. encountering the ``=`` sign, the parser knows that an expression will be evaluated and attributed to variable ``K`` 16 | 3. the expression in this case is a memory read request (``PEEK``) 17 | 4. the parser will then collate the memory location by evaluating what's between the parenthesis (it could be a formula involving other variables for instance). In this case it will just read the number, character by character: 18 | * first, ``4`` 19 | * then, ``9`` 20 | * then ``1`` 21 | * then ``5`` 22 | * then ``2`` 23 | 5. Collating these, results in ``4 9 1 5 2`` as 5 ASCII characters. These represent, for us, humans, a decimal number but not yet for Applesoft. 24 | 6. These 5 characters will then be converted to a real number (using a format known as binary floating-point format) 25 | 7. Then, the real number is converted to an integer value (because ``PEEK`` expects a 2-bytes integer) 26 | 8. Once this has been done, the value in the appropriate location is read, converted from byte to a binary floating-point value and attributed to variable K 27 | 28 | The bottleneck here are the steps 4-6. Building a integer representing a memory location from characters is long. 29 | 30 | It is probable that your game will need to read the keyboard regularly. Why do you have to repeat steps 4-6 every time you need to get the last key pressed ? Fortunately, there's a workaround. 31 | 32 | ## 🍎 Use variables instead of constants 33 | 34 | It is actually faster for the Applesoft parser to locate a variable in memory and use its value than to "recreate it from scratch". So, all you need to do is save in a variable the value you want to repeatedly use. 35 | 36 | For example: 37 | 38 | ```basic 39 | 10 N=49152 40 | 20 K=PEEK(N) 41 | 30 END 42 | ``` 43 | 44 | Line 20 takes 2303 cycles while 45 | 46 | ```basic 47 | 10 N=49152 48 | 20 K=PEEK(49152) 49 | 30 END 50 | ``` 51 | 52 | line 20 here takes 7128 cycles, that's a difference of **4825 cycles** ! This is ***HUGE*** especially when it's a statement that's going to be executed every time the main game loop cycles ! 53 | 54 | Other values will produce different results. For a comparison example, let's say we want to read the value in memory location ``zero``. 55 | 56 | ```basic 57 | 10 N=0 58 | 20 K=PEEK(N) 59 | 30 END 60 | ``` 61 | 62 | Line 20 takes 2090 cycles, while 63 | 64 | ```basic 65 | 10 N=0 66 | 20 K=PEEK(0) 67 | 30 END 68 | ``` 69 | 70 | this line 20 only takes **390** more cycles. This is because ``0`` is only 1 character, while ``49152`` is 5 characters. But anyway, even if the difference is not that important, it's faster. 71 | 72 | ## 🍎 Recommendations 73 | 74 | Should you convert all your constants to variables ? My advice is yes, particularly for the constants used in loops or repeatedly. Among those are: 75 | 76 | * Values you might use constantly (often powers of 2) like ``4``, ``8``, ``16``, ``32``, ``64``, ``128`` and ``256`` ... or maybe their lower limits like ``3``, ``7``, ``15``, ``31``, ``63``, ``127`` and ``255`` 77 | * Other values you will certainly use like ``0``, ``1`` and ``2``. I like to put these in variables ``Z``, ``U`` ("unit(ary)") and ``T`` (as in "two") 78 | * Limits in your game like 79 | * the screen limits: think of ``VTAB 24``, ``HTAB 40``, ``SCRN(39,39)``, ``HPLOT 279,159`` or their upper boundaries like ``40``, ``280`` ``160`` and ``192``. 80 | * loops' low and high limits: ``0``, ``1`` up to ``9`` , ``10`` or ``19`` and ``20``, etc. Think of ``FOR I=0 TO ...`` or ``FOR I=1 TO ...`` 81 | * Usual ``PEEK/POKE/CALL`` locations like 82 | * ``49152`` (last key pressed), 83 | * ``49168`` (reset keyboard strobe), 84 | * ``49200`` (click speaker), 85 | * ``-868`` (a ``CALL`` there will clear the text line from the cursor position to the end of the line)... 86 | * maybe zero page locations like the collision counter in ``234``, 87 | * or the next ``DATA`` address in ``125`` and ``126``, 88 | * or the text window limits in ``32-35``, 89 | * etc. 90 | 91 | Whatever the value, whether it's an integer or a real (*), this rule will **always** speed up your code, except if you're not careful about the next technique ... 92 | 93 | (*) strings are an entirely different matter 94 | -------------------------------------------------------------------------------- /applesoft/nfs/general/02_declare_most_used_variables_first.md: -------------------------------------------------------------------------------- 1 | # Declare your most used variables first 2 | Where we learn that first come is first served. 3 | ## Summary 4 | - [How Applesoft variables work](#-how-applesoft-variables-work) 5 | - [Variables declared later in the code are recovered last](#-variables-declared-later-in-the-code-are-recovered-last) 6 | - [Recommendations](#-recommendations) 7 | 8 | ## 🍎 How Applesoft variables work 9 | 10 | Applesoft variables are stored in two separate areas: 11 | 12 | * right after the program's last line is an area pointed by ``VARTAB`` (in zero-page vector ``$69-$6A`` -- decimal ``105-106``) where all the real, integer and string variables are defined and stored (*). It's also where references to "functions" created by ``DEF FN`` are stored. 13 | * just after that area is another area, pointed by the vector ``ARYTAB`` (in ``$6B-$6``, decimal ``107-108``) where all the arrays are stored. 14 | 15 | (*) string variables are not stored in that area, but pointers to their values are stored there. The actual values of string variables being either in the program code itself or in a special area after the array storage area 16 | 17 | You don't need to declare a variable to use it. As soon as a variable name is discovered by the Applesoft parser, the following happens: 18 | 19 | * the type of the variable is determined so Applesoft knows where to look for the variable's name (``VARTAB`` or ``ARYTAB``) 20 | * Applesoft scans the memory repository for the specified variable type and looks for an existing variable of the same name 21 | * if it's not found, it means it's a new variable. 22 | * If the code where it appears is a variable assignment, then the appropriate space (variable name, type, array indices, value) is reserved at the top of the memory pile where all variables of the same type reside (optionally moving the ``ARYTAB`` area up if a new real/integer/string/function variable needs to be declared). 23 | * If it's not a variable assignment, then the variable type's default value is referenced for next step but the variable IS NOT created. 24 | * if the variable already exists, its value is referenced for the next step 25 | * then the value of the variable is used/replaced/computed/etc. (depending on the actual code) 26 | 27 | As you see, numeric (float/real and integer) variables, string variables and arrays are stored in several different ways but they all share one thing in common: once a variable is encountered and once its type has been determined, the Applesoft parser will search for the variable in one of the two memory locations in the same way: from one end to the other. 28 | 29 | This means that variables are not "ordered" by their names ... It means that, in memory, variable Z might be stored/referenced before variable A... It also means that the time spent to look for a variable depends on how soon it was found in the code. How much time ? Let's find out. 30 | 31 | ## 🍎 Variables declared later in the code are recovered last 32 | 33 | Let's create a variable ``A`` and another named ``Z`` with equal values, then let's print the value of variable ``A`` and then in a second snippet, the value of variable ``Z``. 34 | 35 | ```basic 36 | 10 A=123: Z=A 37 | 20 PRINT A 38 | 30 END 39 | ``` 40 | 41 | Line 20 takes 27864 cycles. The second snippet just prints variable ``Z`` instead of ``A``. 42 | 43 | ```basic 44 | 10 A=123: Z=A 45 | 20 PRINT Z 46 | 30 END 47 | ``` 48 | 49 | This takes 27898 cycles. That's a difference of 34 cycles. It looks **insignificant** and, as such, **it is** ! but it has an impact on all the other techniques I'm gonna teach you. 50 | 51 | Let's have another example. Now this time, we will declare 26 different variables named from ``A`` to ``Z`` and see the cycles count difference when accessing the first one or the last one declared. 52 | 53 | ```basic 54 | 10 A=0: B=1: C=1: D=1: E=1: F=1: G=1: H=1: I=1: J=1: K=1: L=1: M=1: N=1: O=1: P=1: Q=1: R=1: S=1: T=1: U=1: V=1: W=1: X=1: Y=1: Z=0 55 | 20 PRINT A 56 | 30 END 57 | ``` 58 | 59 | Line 20 took 20241 cycles. Second snippet is identical except we access variable ``Z`` instead of variable ``A``. You'll notice that the values of these two variables are identical to eliminate the fact that different values are handled with different speeds. 60 | 61 | ```basic 62 | 10 A=0: B=1: C=1: D=1: E=1: F=1: G=1: H=1: I=1: J=1: K=1: L=1: M=1: N=1: O=1: P=1: Q=1: R=1: S=1: T=1: U=1: V=1: W=1: X=1: Y=1: Z=0 63 | 20 PRINT Z 64 | 30 END 65 | ``` 66 | 67 | This took 21026 cycles. The difference is **only** 785 cycles **slower**. Let's be honest, it's not gigantic. 68 | 69 | But ! Wait ! Remember that snippet in the section [Use variables as placeholders for constant values](variables-for-constant.md#constant0) where we handled value ``0`` ? Because we stored the constant ``0`` in a variable, we won 390 cycles every time used that variable instead of the hardcoded constant. Is it the case here ? Let's just replace line 20 with 70 | ```basic 71 | 20 PRINT 0 72 | ``` 73 | It is now actually faster to use the hardcoded constant as this line 20 only takes 20672 cycles, a difference of 354 cycles **faster**. 74 | 75 | It means that if we're not careful, we might lose the advantage we took for granted. 76 | 77 | Does it mean we should not always use constants/variables substitution ? No. 78 | 79 | This is **rule #1**: you must replace hardcoded constants with variables. 80 | 81 | But **rule #2** is equally important: imagine Z is holding a value you need to use **OFTEN** ... myself I like to put **zero** in Z because it's obviously a good variable name for such a value ... 82 | 83 | What you need to do is declare ``Z`` at the right time, before less used variables. You need to count the occurrences of each variable in your main loop and declare the most used variables first. 84 | 85 | ## 🍎 Recommendations 86 | 87 | Your most used variables should be declared first. In fact **you should have a line in your code where all these variables are declared/created before doing anything else**, otherwise you might inadvertently create a variable. The most common error being to display the instructions or a splash screen for the game and then wait for a keypress with something like ``GET K$``, as ``K$`` might be your very first declared variable ! 88 | 89 | So which variables should you declare first ? and with many variables to declare, how do you know if it's best to use a variable or an actual value ? It depends on many factors. 90 | 91 | It's best to declare the variables used in your main game loop first. Most common variables and constants are possibly: 92 | 93 | * the player's position (typically ``X,Y``) 94 | * previous player position (like ``OX,OY`` although you should prefer single-character variables like ``A,B`` or ``V,W``, more about that later) 95 | * loop counters (like ``I,J``) as used in ``FOR/NEXT`` loops or other loops 96 | * ``49152``, memory location to read a key and ``49168`` to clear the keyboard strobe (but more about that later) 97 | * expected ASCII+128 values (``201``, ``202``, ``203`` & ``204`` are for I/J/K/L which are 4 directions keys on EVERY latin keyboard around the world), maybe ``160`` for space bar, etc. 98 | * I like to use single variables for very common values like ``Z`` for ``0``, ``U`` (unit(ary)) for ``1`` and ``T`` for ``2`` ... it depends if you need these or not ... 99 | * a variable to hold an energy meter (``E`` ?) or a score (``S`` ?) 100 | * player speed (horizontal, vertical) 101 | * a shape rotation ? 102 | * enemies positions + previous cycle positions 103 | * missiles/bullets positions 104 | * etc. 105 | 106 | Once you know which variables you use in your main game loop, you need to consider the following: 107 | 108 | * how often do you use that variable in your game loop ? just count the occurrences ... the most used variables should be declared first and foremost 109 | * if the variable is used after an ``IF/THEN`` statement, take into account how likely the condition will evaluate to true or not. 110 | 111 | ## 🍎 Final example to sum it all up 112 | 113 | In this snippet, ``X`` is incremented and checked against a maximum limit. In the extreme case where ``X`` exceeds the limit, its value is set to that limit. 114 | This is the kind of code that typically happens when drawing a moving object on the screen. 115 | 116 | If you consider that line 20 is part of the main loop, then ``X`` is referenced 3 times: 117 | 118 | * two times in a calculation where the final result is stored in ``X`` 119 | * one in a comparison 120 | If the comparison is true, then ``X`` is referenced one more time. 121 | 122 | The limit variable ``M`` is referenced only once during the comparison, then a second time when the comparison result is true. As the comparison will probably be false most of the time, you can consider that ``X`` is referenced 3 times, while ``M`` only once. But even if the comparison was true most of the time, ``X`` would still be referenced more often than ``M``. Obviously ``X`` should be declared before ``M``. 123 | 124 | Snippet #1: 125 | 126 | ```basic 127 | 10 X=279: U=1: M=279 128 | 20 X=X+U: IF X>M THEN X=M 129 | 30 END 130 | ``` 131 | 132 | In this case, line 20 took 5364 cycles. 133 | The second snippet is identical except declaration of variables ``M`` and ``X`` are inverted. 134 | 135 | ```basic 136 | 10 M=279: U=1: X=279 137 | 20 X=X+U: IF X>M THEN X=M 138 | 30 END 139 | ``` 140 | 141 | Line 20 here takes 5500 cycles, that's 136 cycles more. Nothing too drastic but every cycle counts ! 142 | 143 | The same kind of process should be made with the variable ``U``. Should it be declared before ``M`` ? With these two snippets, ``U`` is referenced only once, whereas ``M`` could be referenced twice when ``X>M`` ... but it's probable that ``U`` (placeholder for the constant ``1``) is used elsewhere in the main game loop, while ``M`` has not many other uses than to check X-coordinates maximum limit ... so ``U`` will probably be more efficiently referenced if declared before ``M``. 144 | -------------------------------------------------------------------------------- /applesoft/nfs/general/03_use_one_letter_variables_names.md: -------------------------------------------------------------------------------- 1 | # Use one-letter variables names whenever possible 2 | Where it's clear that longer is not better. 3 | ## Summary 4 | - [Forget about meaningful variables names](#-forget-about-meaningful-variables-names) 5 | - [Recommendations](#-recommendations) 6 | 7 | ## 🍎 Forget about meaningful variables names 8 | 9 | Applesoft only supports variables names made of one or two characters. The first character **must** be a letter, while the second character may be a letter **or** a number. 10 | 11 | It is allowed to name variables with more characters but these are ignored, meaning that variable ``ABRACADABRA`` is really stored as ``AB`` and maybe referenced later in the code with that name. It also means that you can't really have a variable named ``A10`` as it will be in fact variable ``A1``. 12 | 13 | This behavior is true for any kind of variable (float, integer, string, array of (float/integer/strings), and even ``DEF FN`` which are just referenced as another kind of variable). It means that for every variable type, you can't have more than 26 + 26 * (26+10) variables = 962 different variables names. 14 | 15 | The problem is that the Applesoft parser will take 56 cycles for every extra character in a variable name. 16 | 17 | ```basic 18 | 10 A=17 19 | 20 END 20 | ``` 21 | 22 | Line 10 takes 2776 cycles. Now, let's use variable ``AB`` instead of ``A``: 23 | 24 | ```basic 25 | 10 AB=17 26 | 20 END 27 | ``` 28 | 29 | Line 10 now takes 2832 cycles, that's 56 more cycles. Now, for the extreme example: 30 | 31 | ```basic 32 | 10 ABRACADABRA = 17 33 | 20 END 34 | ``` 35 | 36 | Line 10 takes now 3336 cycles, which is 504 cycles slower, which is exactly 56 cycles for each of the extra 9 characters after ``AB``. 37 | 38 | Every time a variable is parsed, it takes 56 more cycles to parse a 2-characters variable name than if the variable name was only 1 character. And this is true for every use of the variable: assignment, calculation, print, memory read/write, 6502 subroutine call, etc. 39 | 40 | ## 🍎 Recommendations 41 | 42 | - Use one-letter variable names as much as possible in your main loop. 43 | 44 | - It means, **yes**, you need to avoid ``OX,OY`` for the previous cycle's ``X,Y`` coordinates. 45 | 46 | - If needed, re-use variables names declared before starting the main loop if these variables hold values you don't care about anymore. 47 | 48 | - Remember to [declare your most used variables first](02_declare_most_used_variables_first.md) ! 49 | 50 | - Use as few two-characters variables names as possible in your main loop, as each 2-characters variable name will take 56 more cycles **just** to parse the name of the variable in the code. 51 | 52 | - **NEVER** use more than two-characters variables names in your main loop (*). 53 | 54 | (*) you should apply this rule even if you're not looking for speed as confusion is waiting behind the corner: variable ``LEVEL`` and variable ``LENGTH`` both refer to variable ``LE`` ... 55 | -------------------------------------------------------------------------------- /applesoft/nfs/general/04_avoid_integer_variables.md: -------------------------------------------------------------------------------- 1 | # Avoid integer variables 2 | Where we learn that Applesoft's integers won't make you whole. 3 | 4 | ## Summary 5 | - [How integer variables are handled](#-how-integer-variables-are-handled) 6 | - [Sacrifice of the integers](#-sacrifice-of-the-integers) 7 | - [Integer variables vs INT()](#-integer-variables-vs-INT-) 8 | - [What's an integer good for ?](#-whats-an-integer-good-for-) 9 | 1. [Do you really need an integer ?](#1-do-you-really-need-an-integer-) 10 | 2. [Do you need to store theReal world examples of integer in a variable ?](#2-do-you-need-to-store-the-integer-in-a-variable-) 11 | 3. [Storage and access of integer variables](#3-storage-and-access-of-integer-variables) 12 | - [Recommendations](#-recommendations) 13 | 14 | ## 🍎 How integer variables are handled 15 | Applesoft integer variables are variables whose name ends with a "%" character. They can hold integer values from -32767 to 32767. That's almost the range of signed integer numbers on 2 bytes, but because of a bug in Applesoft you can't have -32768 ($8000) in an integer variable. 16 | 17 | Integer variables are stored in memory in the same area as the other "simple" variables (floats, strings and ```DEF FN``` variables), by default right after the end of the program. If integer variables' values are limited to two bytes, they are in fact stored on five bytes, the last three being unused. These 3 unused bytes are reset to zero when the variable is created but subsequent assignments won't touch them. 18 | 19 | This memory is free for you to use if you want. To identify where it is, it's easy as the address of the last accessed variable is stored in $83-$84 (131-132). The only thing is you can't store the values in these memory locations directly in a variable as they will then point to the variable you're using as storage. You need to copy the values in another memory location beforehand. 20 | 21 | ```basic 22 | 10 A% = 123: REM A% IS CREATED 23 | 20 POKE 6, PEEK(131): POKE 7, PEEK(132): REM COPY ADDRESS IN $6-$7 24 | 30 A= PEEK(6) + PEEK(7)*256 + 2: REM SKIP THE FIRST TWO BYTES 25 | 40 REM MEMORY LOCATIONS POINTED BY A, A+1 AND A+2 ARE FREE TO USE 26 | ``` 27 | If you didn't pick the address at the creation of the variable, simply reuse the variable in your code. A simple `A%=A%` will do the trick. 28 | 29 | 30 | ## 🍎 Sacrifice of the integers 31 | Applesoft considers every number as a float and can only compute mathematical expressions of float operands. In order to achieve that, Applesoft will convert any numerical value it parses to a float before doing anything else with it. 32 | 33 | When Applesoft parses an expression, it will **not** check if that expression can be evidently evaluated to an integer number (like when using an integer constant -- as in `K=PEEK(49152)` -- or like when using an integer variable -- as in `VTAB V%`). This means that integer numbers are never treated as such by Applesoft: everything falls back **first** into the general case of floating-point numbers. 34 | 35 | Applesoft was written to handle floats at all times and integers were sacrificed, probably because of a lack of memory space to hold dedicated integer operations/routines. 36 | 37 | Integer numbers are not ***special*** for the Applesoft interpreter, they're just floats without a decimal point. Nothing in the Applesoft interpreter is made to specifically handle integers when they're found in the code. 38 | 39 | A notable consequence of this is that **integer values** are more efficient in **real variables** than in **integer variables**: every time an integer variable is used, its value is first converted to a float before anything else. 40 | 41 | Then if the instruction it's being used in requires an integer (like it is with `PEEK`), it's converted back *internally* to an integer ! 42 | 43 | And if the result of an operation needs to be stored in an integer variable, the float result is converted back to a 16-bit integer (with limits validation), slowing down the whole process even more. 44 | 45 | ***Integer variables are slower and take as much space as real variables***. That's the thing to remember. (*) 46 | 47 | (*) The only exception being arrays of integers -- like `A%(n)` -- where the 16-bit value is actually stored on 2 bytes and not 5, but that's all. Accessing items in arrays of integers are always slower nonetheless, because the integer is converted to a float as soon as it's accessed. 48 | 49 | Every time you use an integer variable it will impact speed negatively. How much ? That depends on the final value that is handled and what you do with it, but every time you use an integer variable you lose around 200-350 cycles. 50 | 51 | Let's compare 52 | ```basic 53 | 10 A=-16384 54 | 20 B=PEEK(A) 55 | 30 PRINT B 56 | 40 END 57 | ``` 58 | with 59 | ```basic 60 | 10 A%=-16384 61 | 20 B%=PEEK(A%) 62 | 30 PRINT B% 63 | 40 END 64 | ``` 65 | Line 10 takes 6754 cycles in the first case and 7058 cycles in the second case. A difference of 304 cycles. In both cases, `-16384` is interpreted as a float number. But in the second case, that float needs to be converted to an integer to be stored in an integer variable. 66 | 67 | Line 20 takes 2441 cycles in the first case and 3047 cycles in the second case. A difference of 606 cycles ! This is because, in the second case, `A%` is first converted to float, then to an integer (because `PEEK` requires an integer value) then the memory is read memory and the result (a byte), is converted to float. But because `B%` is an integer variable, it is again converted to integer. That's 4 conversions. In the first case, only two conversions occur: `A` (float) is converted to integer and the resulting byte of `PEEK` is converted to float to be stored into `B`. 68 | 69 | Line 30 takes 26779 cycles in the first case and 27031 cycles in the second case. A difference of 252 cycles. 70 | 71 | Does it really mean that integer variables are useless ? 72 | 73 | Well, in fact there's one case where they're more efficient. 74 | 75 | ## 🍎 Integer variables vs INT( ) 76 | 77 | After all, this is what integer variables do ... convert numbers to integers ! 78 | 79 | ```basic 80 | 10 A=17.1: B=0 81 | 20 B=INT(A) 82 | 30 END 83 | ``` 84 | Line 20 takes 2259 cycles. This is almost twice the time (1194 cycles) it takes to just assign the value of `A` to `B` (`B=A` instead of `B=INT(A)`). 85 | 86 | ```basic 87 | 10 A=17.1: B%=0 88 | 20 B%=A 89 | 30 END 90 | ``` 91 | Line 20 now takes 1538 cycles ! That's 721 cycles **faster** than using `INT` ! Isn't it great !? Well, don't get too excited ... 92 | 93 | Now that you have rounded down the value of ``A``, you'll probably want to do something with it. Let's say you want to `PRINT` it. 94 | 95 | ```basic 96 | 10 A=17.1: B=0 97 | 20 B=INT(A): PRINT B 98 | 30 END 99 | ``` 100 | Line 20 takes 28773 cycles. 101 | 102 | ```basic 103 | 10 A=17.1: B%=0 104 | 20 B%=A: PRINT B% 105 | 30 END 106 | ``` 107 | Line 20 takes 28274 cycles. It's 499 cycles faster but because our code uses `B%` a second time, our advance of 721 cycles has been decreased by 222 cycles. 108 | 109 | This confirms that every time you use an integer variable (like `B%`) more than once in your code, you will lose ~200-350 cycles ... It means you can only use `B%` **two to four times** before losing the advantage of having NOT used `INT` and a real variable. That's a significant drawback you must take into account. 110 | 111 | Knowing that, is it relevant to use integer variables ? 112 | 113 | ## 🍎 What's an integer good for ? 114 | 115 | ### 1) Do you really need an integer ? 116 | There are no Applesoft instruction that **require** an integer value as argument/parameter. All numeric arguments/parameters are considered as floats first, 117 | - then, if it's needed, they are converted to integers either on 2-bytes or 1-byte 118 | - then, their limits are validated, throwing an ILLEGAL QUANTITY ERROR if their value is out of bounds 119 | 120 | It means, you **don't need** to convert floats to integers when using numeric expressions with Applesoft instructions as Applesoft will do the conversion for you. You don't need to use `INT` and you don't need to use integer variables for arguments/parameters. 121 | 122 | This is true for `PEEK`, `POKE`, `CALL`, `HTAB`, `VTAB`, `PLOT`, `HPLOT`, `SCRN`, `VLIN`, `HLIN`, `(X)DRAW`, `PDL` to name a few. 123 | 124 | This is also true for `ROT=`, `SCALE=`, `HCOLOR=`, `COLOR=` and even `SPEED=`, `LOMEM:` and `HIMEM:` ! 125 | 126 | It even applies for numeric parameters used in string-related instructions: `LEFT$`, `MID$`, `RIGHT$` and `CHR$`. 127 | 128 | It applies for dimensioning arrays with `DIM` (e.g. `N=123.456: DIM A(N)` works). 129 | 130 | It applies to indices of arrays. It means that an array like `M(X, Y)` representing for example the player's X-Y position in a maze can be used without worrying about `X` and `Y` being integers. 131 | 132 | It also works with `GOTO` and `GOSUB` but for another reason. Applesoft expects numbers after a `GOTO/GOSUB` instruction and once it finds a character that is not numeric, it will ignore every other character and just do his `GOTO/GOSUB`. 133 | 134 | This means that 135 | - `GOTO 100.123456` works but also 136 | - `GOTO 100 THIS WON'T BE PARSED`, 137 | 138 | as long as you have a line 100 in your code of course. 139 | 140 | But more interestingly, you don't need to use integer values with `ON/GOTO-GOSUB` (*). This means you can do this: 141 | ```basic 142 | ON RND(1)*10 GOTO 100,200,300,400 143 | ``` 144 | Using `INT` here is totally superfluous ! 145 | 146 | (*) As we'll see in another chapter, ON/GOTO is faster than a sequence of multiple IF/THEN conditions and is the preferred method when your code needs branching. 147 | 148 | This is very useful for random events, AI decisions, or simply branching your code based on a calculation, for example you could scale a value and react accordingly without worrying about your scaled value being an integer: 149 | ```basic 150 | 10 D=47: REM DISTANCE COVERED SO FAR 151 | 20 M=100: REM MAX DISTANCE 152 | 30 Q=25: REM DIVIDER (100/25 = 4 DIFFERENT MSGS + 1 EXTRA) 153 | ... 154 | 100 ON D/Q GOTO 120, 130, 140, 150 : REM NO NEED TO USE INT HERE ! 155 | 110 PRINT "GOOD LUCK !": GOTO 160: REM 0-24 156 | 120 PRINT "KEEP GOING !": GOTO 160: REM 25-49 157 | 130 PRINT "HALFWAY THERE !": GOTO 160: REM 50-74 158 | 140 PRINT "ALMOST THERE !": GOTO 160: REM 75-99 159 | 150 PRINT "FINISH !": END 160 | 160 REM CONTINUING ... 161 | ``` 162 | ### 2) Do you need to store the integer in a variable ? 163 | Remember that snippet ? 164 | ```basic 165 | 10 A=17.1: B%=0 166 | 20 B%=A: PRINT B% 167 | 30 END 168 | ``` 169 | Line 20 took 28274 cycles and it proved more efficient than using `INT`. But if you only need an integer value once, an `INT` will be faster because, unlike integer variables, you can use it *inline* a statement. 170 | 171 | Replace line 20 with 172 | ```basic 173 | 20 PRINT INT(A) 174 | ``` 175 | and now line 20 takes only 27602 cycles, which is faster (672 cycles) than using integer variables. 176 | 177 | This kind of *inline conversion* is not possible with integer variables since you actually need a variable to do the integer conversion (duh !). 178 | 179 | Let's say the player of your game opens a treasure chest and finds 1 to 100 gold pieces in the chest. Do you need an integer variable for that ? It depends. Here are two different cases. 180 | 181 | Using an inline `INT`: 182 | ```basic 183 | 10 G=50: H=100: U=1: V=21 184 | 20 G = G+INT(RND(U)*H+U) 185 | 30 VTAB V: PRINT "GOLD: ";G 186 | 40 END 187 | ``` 188 | In this first example, the amount of gold the player found is not important, what matters is the total of gold he has. Use of inline `INT` is therefore faster. 189 | 190 | Second example: 191 | ```basic 192 | 10 G=50: H=100: U=1: R%=0 193 | 20 R%=RND(U)*H+U : G = G+R% 194 | 30 PRINT "YOU'VE FOUND "; R%; " GOLD COINS. YOU HAVE NOW "; G; " GOLD COINS". 195 | 40 END 196 | ``` 197 | In this example, we're interested in both the amount found and the total. It makes sense to store the result in a temporary variable and since it requires an integer **value**, using an integer **variable** (even referenced 3 times in the code) will be around ~200-400 cycles faster than a real variable and using `INT`. 198 | 199 | ### 3) Storage and access of integer variables 200 | As shown in the previous example, you should consider using integer variables when you need to use these integers values more than once in your code, for example for calculations in other formulas. 201 | 202 | However, you need to be careful. 203 | 204 | Imagine your game is played on a grid of some size. The player's XY coordinates are stored in the real variables `X` and `Y` and these are guaranteed to hold integer numbers at all times because the player can only move to one tile/square at a time. 205 | An enemy's on the grid too and he moves randomly in both directions. To move it in the X-direction you use the following code (and a similar code to move the enemy in the Y-direction): 206 | ```basic 207 | EX% = EX% + RND(1)*3 - 1 208 | ``` 209 | (*) Of course, as you know, all hardcoded constants should be replaced with variables. But I've left the constants here for readability.. 210 | 211 | It looks like it's wise to use an integer variable because the same line with real variables 212 | ```basic 213 | EX = EX + INT(RND(1)*3 - 1) 214 | ``` 215 | would be ~600-700 cycles slower. 216 | 217 | But: 218 | - you need to draw the enemy on screen, with 219 | - `HTAB EX%: PRINT "X"` or 220 | - `PLOT EX%, EY%` or 221 | - `HPLOT EX%, EY%` or 222 | - `(X)DRAW E AT EX%,EY%` or 223 | - your own technique, but you still need to access `EX%` and `EY%` 224 | - you need to check if the player's position is the same as the enemy's 225 | - `IF EX%=X AND EY%=Y THEN ...` 226 | - you need to erase the enemy on the next loop iteration, meaning 227 | - you might need to store the enemy's previous position in another set of variables 228 | - `XE=EX%: YE=EY%` (no need to use integer variables like `XE%` or `YE%` since the conversion is already done) 229 | - you'll do another call to `HTAB+PRINT`, `PLOT`, `HPLOT`, `(X)DRAW` 230 | - you might need to check if the enemy was hit by the player's projectile 231 | - `IF EX%=PX AND EY%=PY THEN ...` 232 | - etc. 233 | 234 | Every time you use `EX%` (and `EY%`) you'll lose 200-350 cycles and after 3-5 times, using a real variable is a better choice. 235 | 236 | 237 | ## 🍎 Recommendations 238 | 239 | - The general rule is to **avoid** integer variables: they're much slower than real variables and take as much memory. 240 | - Integer variables are **never** needed as parameters of Applesoft instructions, so don't bother. 241 | - Integer variables **may be** faster than using `INT`. **But**: 242 | - before using `INT`, check if you really need an integer value. 243 | - if the result of `INT` can be used in an inline statement and is never used again, inline `INT` will be faster than using a temporary integer variable. 244 | - if the integer variable is used more than twice in the main loop, you might lose any speed advantage you had. 245 | -------------------------------------------------------------------------------- /applesoft/nfs/program_structure/01_goto_do_it_right.md: -------------------------------------------------------------------------------- 1 | # GOTO: Do it right 2 | Where we discover how to tame `GOTO` and avoid `GOSUB`. 3 | 4 | Unless specified, everything that applies to `GOTO` applies to `GOSUB` as well. Although some of the info provided here applies to `ON X GOTO/GOSUB` and `ONERR GOTO`, these two instructions will be detailed in future articles as they also have their own particularities. 5 | 6 | ## Summary 7 | - [How line numbers work](#-how-line-numbers-work) 8 | - [How `GOTO/GOSUB` decode line numbers](#-how-gotogosub-decode-line-numbers) 9 | - [Going to the line](#-going-to-the-line) 10 | - [A word about `GOSUB`](#-a-word-about-gosub) 11 | - [An ideal program structure](#-an-ideal-program-structure) 12 | - [Wishes ... `GOTO` waste](#-wishes--goto-waste) 13 | - [Recommendations](#-recommendations) 14 | 15 | ## 🍎 How line numbers work 16 | Whenever you type a program in Applesoft, it begins in `$801` in memory. This address is stored in zero page `$67-$68` (a location known as `TXTTAB`). It's possible to modify these values and therefore make Applesoft store your program elsewhere in memory. This process is known as "relocation" but to make it persistent it requires several other things that I'm not going to discuss now. Relocation is often used when you need the hires pages (in locations `$2000-$3FFF` or `$4000-$5FFF`) but your program is so large that its code is stored beyond `$1FFF` which affects the hires pages. In this case, you'll relocate your code after the hires memory. 17 | 18 | The byte just before the location pointed by `TXTTAB` (thus `$800` most of the time), must be zero. If it's not, you'll get a syntax error in an improbable line number when you `RUN` your code. 19 | 20 | In `$801` the lines of your program are encoded. For each line, the structure is the same. 21 | 22 | The first and second bytes represent the address of the next line but it's in fact more exactly the address **right after** the **end** of the **current** line. 23 | 24 | If there's no current line, meaning it's the end the program, these two bytes are zero. In fact, when a program is executed and that Applesoft is interpreting a line of code, only the second byte (the most significant byte of the address) is checked for zero (the first byte is not even read at this stage !). 25 | 26 | If these bytes are non-zero (and thus more particularly the second one), it does not mean that there is a next line, it means that the rest of the code, if any, can be found there. 27 | 28 | These first two bytes (of each line) are **not** copied elsewhere in memory for later use (for instance they could have been copied in zero page). It means that if the interpreter needs to go to the next line (for example when an `IF/THEN` is evaluated to `FALSE`), it will have to search for the end of the current line first. And although searching for the end of a line of code is rather fast, it depends on the length of the line. If the address of the next line had been stored, not only would it be almost instantaneous but the length of the line would not matter. 29 | 30 | The next two bytes represent the actual line number, which is a value between 0 and 63999. The line number is read by Applesoft and stored in zero page `$75-$76`, a location known as `CURLIN`. This value is used to print the line number being executed (if `TRACE` is on) or the line number where an error occurred, It is also used when handling a `GOTO/GOSUB`. 31 | 32 | The next bytes represent the tokenized code. A byte of value zero indicates the end of the line. 33 | 34 | Then the next line, if any, begins: the first two bytes of this next line, as for the previous line, point to the location of the next-next line. Etc. 35 | 36 | The end of any Applesoft program is represented by three zero bytes: the first one being the end of the current line, the two next ones being the indicator that there's no next line. 37 | 38 | ## 🍎 How `GOTO/GOSUB` decode line numbers 39 | In Applesoft it's not possible to `GOTO/GOSUB` to a line number represented as an expression. You cannot `GOTO X*2` for instance (notice that it was possible with Wozniak's Integer Basic). 40 | 41 | It is a limitation of the language but if it was possible to use a mathematical expression, evaluating this expression would mean converting variables to floats, calculate the expression and then convert the float result to an integer. And in the end, `GOTO/GOSUB` would be even slower than what they are. 42 | 43 | So, after a `GOTO/GOSUB`, an integer value is expected. This integer value has not been tokenized, meaning it still consists of ASCII characters. Converting this decimal number (encoded as ASCII) to hexadecimal uses a simple algorithm: from a starting value of zero, every digit is added to the previously evaluated value multiplied by 10. 44 | 45 | For instance, if we had the statement `GOTO 61234`, here's how the line number would be converted from decimal/ASCII to hexadecimal. 46 | 47 | |Digit read| Intermediary computation | Estimated value | Hex | Explanation 48 | |--|--|--|--|--| 49 | | n/a | n/a | 0 | $0000 | The estimated value is first set to zero. 50 | | 6 | 0*10 = 0 | 0 + 6 = 6 | $0006 | The previous value is multiplied by 10 and the digit read is added 51 | | 1 | 6*10 = 60 | 60 + 1 = 61 | $003D | 52 | | 2 | 61*10 = 610 | 610 + 2 = 612 | $0264 | 53 | | 3 | 612*10 = 6120 | 6120 + 3 = 6123 | $17EB | 54 | | 4 | 6123*10 = 61230 | 61230 + 4 = 61234 | $EF32 | 55 | 56 | Every time a digit needs to be "decoded", it takes an additional **114 cycles**. This is only for the decoding. Going to the line number is something else. Thus, `GOTO 6` takes 114 cycles to convert the "6" (in ASCII) to hex `$0006`, while `GOTO 61234` takes 4x114 additional cycles, for a total of 570 cycles to convert the five ASCII numbers into hex `$EF32`. 57 | 58 | ### The 63999 limit 59 | The subroutine used by `GOTO/GOSUB` to decode a line number is also used when you type a line of code at the prompt. The subroutine includes a check to see if the line number is above 63999. The check happens after having confirmed that Applesoft just read a digit (thus assuming it's part of the line number). It will compare if the most significant byte of the estimated value so far is equal or above `$19` (25) meaning we have at least a value of 6400 (`$1900`). If that's the case Applesoft throws an error since multiplying the estimated value will result in a value of 64000 which is above the limit. 60 | 61 | We have here a probable reason why the line numbers are limited to 63999. Of course this check could have been written in another manner but it would have been unnecessarily complicated for the only meager benefit of having 1500 additional lines of code . An intermediary value of 6553 (thus allowing for numbers between 65530 and 65535) is `$1999`. It means Applesoft needs to check if it has a `$19` for the MSB and a `$99` or below for the LSB. Then, it needs to check if the last digit is equal or lower than 5. Three checks instead of one. Not only is it slower but it also eats precious memory. 62 | 63 | ### Skipping non-digit characters 64 | It should also be noted that whatever is after the `GOTO/GOSUB` statement does not need to be an integer. If it's anything else (strings, statements, syntax errors), or even if there is nothing after the statement, the line number is evaluated to zero. This means that `GOTO 0` is in fact 114 cycles slower than just "`GOTO`". And having "`GOTO ANY_GARBAGE_TEXT_HERE`" will do the same as just "`GOTO`", except for one thing: anything after the `GOTO` statement must be skipped to find the the end of the current line. 65 | 66 | This skip occurs every time there is a `GOTO` or a `GOSUB`: everything after the `GOTO/GOSUB` is scanned byte by byte hoping to find the indicator for the end of the line: a zero. If Applesoft had stored the next line address when it had the chance, this could have been avoided. 67 | 68 | Searching for this zero takes 8 cycles for the next byte on the line and then 19 cycles for every other byte that needs to be checked after that (and if the byte represents a double-quote, it's even a little more than that). Thus, a `GOSUB 100: REM PRINT SCORE` will take 3x114 cycles to decode the line number, plus an additional 255 cycles for the `: REM PRINT SCORE` as it's made of one byte for the "`:`" (8 cycles), another byte for the tokenized "`REM`" (19 cycles -- the space between the two does not count as it has not been recorded by Applesoft), then 12 more bytes for the ` PRINT SCORE` text (12x19=228 cycles -- the space between `REM` and `PRINT` **has** been recorded by Applesoft and no, the `PRINT` has not been tokenized as it's part of the `REM` string). So we have 8 + 19 + 12x19 = 255 cycles just to skip the `REM`. As you probably know: you must ban `REM`s from your main loop if you're looking for speed. 69 | 70 | ## 🍎 Going to the line 71 | Ok now that we have a line number to go to, how do we go there ? 72 | 73 | The Applesoft program in memory has all the lines of code in chronological order. Even if you type the line #100 before the line #10, or if you insert lines between others, Applesoft will reorder all the lines in memory: it will find the place where your new line must be inserted, move all the next lines accordingly, insert your line and recalculate all the "next line" pointers (remember: the first two bytes of each line are a pointer to the start of the next line, if any). 74 | 75 | Since all lines are ordered, Applesoft can find the line it has to `GOTO` by starting from the very first line and check the bytes 3 and 4 of each line and see if it corresponds to the target line. If not, it uses the address of the next line and do the same check. If the line being checked has a number below the line being searched then Applesoft emits an `UNDEF'D STATEMENT` error; it does not need to check further as the lines are ordered. The line is therefore considered nonexistent. 76 | 77 | This process takes 55 or 65 cycles for every line (more about **when** it's 55 or 65 in a moment) . Consider the following program: 78 | ```basic 79 | 100 REM 80 | 110 GOTO 140 81 | 120 REM 82 | 130 REM 83 | 140 PRINT "HELLO" 84 | ``` 85 | 86 | In line 110, decoding the line number takes 3x114 cycles, then Applesoft goes to the top of the program and searches for line 140. Every line number that is checked takes 65 cycles, line 140 being the fifth line, it actually takes 65x5=325 cycles to go from line 110 to line 140. 87 | 88 | This behavior is the main reason why the few books that mention optimizing Applesoft programs (and notably the Applesoft Reference Manual in appendix E, page 120) tell you to "place frequently-referenced lines as early in the program as possible". 89 | 90 | While this is true, it's also incomplete as there is a way to tell Applesoft to search for a line from the current line and above and not from the top of the program. 91 | 92 | If **you** were given the task to optimize `GOTO/GOSUB` search for line, you would probably use the current line as a comparison. The simplest basic thing to suggest would be to search from the top of the program if the line is **before** the current line and search from the current line if the line being searched is **after**. And as a matter of fact, it is possible with Applesoft but with a small restriction. 93 | 94 | Let's rewrite our program: 95 | 96 | ```basic 97 | 100 REM 98 | 110 GOTO 260 99 | 120 REM 100 | 130 REM 101 | 260 PRINT "HELLO" 102 | ``` 103 | In this case, the search for line 260 will begin from the current line and only 3 lines will be checked. This process takes 55 cycles for line 120, another 55 cycles for line 130 and 65 cycles for line 260, for a total of 175 cycles, which is almost twice as fast as the previous program. 104 | 105 | The reason why Applesoft goes "forward" is because the integer result of 260 divided by 256 equals 1 while 110 divided by 256 equals 0. Because 1 is above 0, Applesoft will go "forward". Of course, this is the easy-human-using-decimal way to explain it. The Applesoft way is that 260 in hex is `$0104` while 110 in hex is `$006E` and that the most significant byte of the current line (`$00`) is lower than the MSB of the line we want to go to (`$01`). 106 | 107 | Now you probably guessed why sometimes it takes 55 cycles and sometimes 65 cycles, while this is not really related to the previous point. It's simply because if the most significant byte of the line number being checked is different than the most significant byte of the line being searched, it's not needed to check for the least significant bytes. This check takes an additional 10 cycles. This explains why in the first program, each line takes 65 cycles, while in the second, only the last line takes 65 cycles. 108 | 109 | To sum up, if you organize your main loop correctly, you may spare several hundreds of cycles by forcing your `GOTO`s to go forward. Of course every time you add a digit to your line numbers you lose 114 cycles for your `GOTO`. But this is equivalent to less than two lines to check (2x65). It's worth it. 110 | 111 | ## 🍎 A word about `GOSUB` 112 | `GOSUB` is pretty much like `GOTO`. Except that Applesoft has to remember where to go when `RETURN` is encountered. It has to save several info into the stack (you could have multiple `GOSUB`s nested, so you can't save this info in a unique location on zero page). Once this is done, it works exactly like a `GOTO`: it searches for a line number either from the top or from the current line. 113 | 114 | When Applesoft encounters the `RETURN` instruction, it has to search the stack for the last `GOSUB` that was issued, restore all the program pointers and return where it was. This takes around 350-450 cycles. 115 | 116 | Because of these additional steps, I advise to avoid `GOSUB/RETURN` whenever it's possible and instead rewrite the same code whenever you need it as it will be executed faster (I know, this is bad practice but you want speed or not ?). For instance if you have a subroutine that does `VTAB 21: PRINT "SCORE: "; SC: RETURN`, simply delete the subroutine, and rewrite this code every time you need it. You will go much faster than searching for a line either from the top or forward. 117 | 118 | For more complex subroutines, you'll have to consider reworking your code so that the subroutine is part of the main loop flow and is not called as a subroutine anymore. If it's not possible, then the subroutine must be one of the first lines of your program or somewhere in your main loop and then you need to make sure that any `GOSUB` there will go "forward" and that you adequately skip the subroutine with a `GOTO` "forward" too. 119 | 120 | ## 🍎 An ideal program structure 121 | With what we know about `GOTO/GOSUB` and line numbers we can identify some kind of ideal program structure. 122 | 123 | ### Line zero 124 | First you'll want to use line zero to jump at the end of your code (do something like `GOTO 9999`) where you can display an introduction screen, instructions, load data files, relocate your program, etc. But most importantly: declare the most used variables first ! 125 | 126 | Then when it's time to start the game, initialize all the games variables and `GOTO 1` or another entry point in your main loop. 127 | 128 | ### Lines 1-9 129 | Your next lines should all be below 10, because any `GOTO/GOSUB` there will require only 114 cycles to decipher the line number. 130 | 131 | Ideally, lines 1 to 9 should be part of your main loop but if you need to call a "complex" subroutine more often than you'll go back to the start of the loop, then the subroutine should be there. **Before** the main loop. 132 | 133 | But in the opposite case, you should consider putting your subroutine **right after** the main loop, or even maybe **within** the main loop (and you could skip it with a `GOTO` "forward") and make sure that the `GOSUB`s that will call it will go "forward" too. 134 | 135 | Lines 1-9 are are ideal for multiple re-entry points in the loop. But they're also a nice place to skip lines for cheap; even though any `GOTO` there will search "from the top". 136 | 137 | For example, If you have 10 lines of code from 0 to 9 and that line 7 has a `GOTO 9` (to skip line 8 for instance), it takes 764 cycles (114 cycles to decipher "9" plus 10x65 cycles to go there). 138 | 139 | If you wanted to use a `GOTO` "forward" then you'd have line 7 with `GOTO 256` (or above -- line 256 replacing line 9). In this case it takes 3x114 cycles to decipher "256" plus 55 cycles to skip line 8 and 65 cycles to arrive to line 256; total of 462 cycles, which is shorter but you loose all your 2-digit lines (between 10 and 99) ! Except if you use them anyway. And in this case, going to line 256 will take 55-65 additional cycles per line. After 6 lines you'll exceed the initial 764 cycles. 140 | 141 | ### Lines 10-99 142 | The problem with lines 10-99 is that although deciphering the line number for `GOTO` is "only" 2x114 cycles, using `GOTO` will **always** search from the top of the program. If lines 0-20 exist, a `GOTO 20` costs 1528 cycles (2x114 + 65x20). 143 | 144 | ### Lines 100-255 145 | These lines are even more expensive to `GOTO` than lines 10-99. Avoid them if you need to `GOTO` in there. 146 | 147 | ### Lines 256-511 etc. 148 | Most of the time, my main loops go from line 1 to lines 20-25 and then jump to line 260 (or 256). Between lines 256-511, I'll have probably not much more than 10 lines as I'll probably need to jump forward again soon. The same goes for lines 512-767, and so on. 149 | 150 | Remember that you can use the `GOTO` "forward" trick more than once within the same section. For example line 20 could `GOTO 260`, but line 21 could `GOTO 300`: both will `GOTO` "forward" ! 151 | 152 | Just so you know, if there are 10 lines between line numbers 260 and 350 (both included), and if line 260 has `GOTO 520`, it will take 902 cycles (3x114 + 9x55 + 1x65) to reach line 520. 153 | 154 | ### That "complex" subroutine you need to `GOSUB` to from time to time 155 | If placing the subroutine at the top of the program is not a good idea or not possible, then you need to make sure you'll `GOSUB` "forward": place the subroutine **after** any `GOSUB >there<` call, as close as possible to the last `GOSUB >there<`. Even within the main loop if needed: just skip that subroutine with an appropriate `GOTO` forward. 156 | 157 | ## 🍎 Wishes ... `GOTO` waste 158 | The way `GOTO/GOSUB` searches for a line number is inefficient. Some simple things could have been made to make it a little better. Of course, the question is: was there enough memory space in the ROM to implement this ? 159 | 160 | 1. In a program, line numbers (at the start of every line) are converted into 2-bytes integers. Why isn't it the case for the line numbers to `GOTO/GOSUB` ? This would have avoided the conversion from ASCII to integer during runtime AND saved memory. By the way, this is how it's implemented in Wozniak's Integer Basic. As a side note, this would have facilitated line renumbering routines. 161 | 2. Applesoft should have stored the address of the next line in zero page. This would have sped up the search for the end of the current line (which, by the way, occurs even if the search for the line to go to will be from the top of the program). 162 | 3. The search "forward" should have considered the complete line number to go to and not just the most significant byte. 163 | 4. If the line number AND the address of the next line were in a table of their own instead of being at the start of every tokenized line, it would be possible to search for line numbers not only from the top or "forward", but also "backwards". 164 | 165 | For example, knowing that the current line is line 200, a `GOTO 180` is statistically faster by searching "backwards" for line 180 than searching "from the top". A hint could have even been given by the programmer if the syntax was modified. For example a `GOTO <180` would force a backwards search. 166 | 167 | Such a table could have been placed right after the program itself, inserted between the tokenized code and the start of the variables. As a side note, it would have also sped up lines insertion as only this table needs to be modified and no address needs to be recalculated. As a consequence the lines themselves would have been unordered but that's not an issue: only the lookup table needs to be ordered. 168 | 169 | Using a lookup table also gives another information: line count. For example line 200 could be the tenth line in the code. This could easily be detected/computed because the pointer to the lookup table would be 40 (4 bytes per line x10). And this could have been used to determine if we want to search backwards or from the top and even, "from the bottom" ... 170 | 171 | ## 🍎 Recommendations 172 | - Use line zero to `GOTO` the start of your program 173 | - Lines 1-99 is where your main loop should reside 174 | - `GOTO` "from the top" should target a line as close as possible to line zero. 175 | - Use adequate "forward" `GOTO` within your main loop to skip lines 176 | - Avoid `GOSUB`s by rewriting the same code again whenever possible 177 | - `GOSUB` to a subroutine at the very start of your code (lines 1-9) or to a close subroutine **after** the `GOSUB` using "forward" technique 178 | -------------------------------------------------------------------------------- /applesoft/spc/README.md: -------------------------------------------------------------------------------- 1 | # How to use SPC() to repeat any kind of character ! 2 | ## Introduction 3 | You know how `SPC()` can be used to PRINT a number of space characters. For example `PRINT SPC(10)` will print 10 space characters. 4 | 5 | Why didn't they allow to print something else than space characters ? It would have been interesting (?) to have the ability to repeat a sequence of any character. 6 | 7 | Maybe something like `PRINT REPT("*",10)` could have printed 10 asterisks. 8 | 9 | But Applesoft does not provide such an instruction. So are we doomed to use `PRINT "**********"`? 10 | 11 | Here's a technique that will allow you to repeat any character, even in `FLASH` and `INVERSE` without using additional 6502 routines. And it's very easy to implement ! 12 | 13 | ## Discovery 14 | Let's see something weird ... 15 | 16 | At the Applesoft prompt, type `FLASH`. 17 | 18 | Then `PRINT SPC(10)`. You should now see 10 flashing space characters. 19 | Now, press `CTRL-RESET`. This exits the "flash" mode (do no type `NORMAL` !!). 20 | Type `PRINT SPC(10)` again. And ... 21 | 22 | WOW ! WHAT IS THAT ?? 23 | 24 | What are those inverted single quote characters doing here ? "Something" has replaced space characters with those inverted single quotes ... 25 | 26 | If you have a loaded Applesoft program, I encourage you to `LIST` it. If not, quickly type a short one and see the results ... 27 |
28 |
29 | 30 | And finally if you type an invalid command, the usual `?SYNTAX ERROR` message will be a little different ... 31 | 32 | As you can see, something is messed up ! 33 | 34 | ## Explanation 35 | To understand what's happening here, you need to know how characters are printed on screen by Applesoft. 36 | 37 | The Applesoft general routine to print characters on screen is in `$DB5C` and is named `OUTDO`. 38 | 39 | Here's the routine, taken from [S-C documentor website](http://www.txbobsc.com/scsc/scdocumentor/). 40 | ```assembly 41 | 1950 * PRINT CHAR FROM (A) 42 | 1960 * 43 | 1970 * NOTE: POKE 243,32 ($20 IN $F3) WILL CONVERT 44 | 1980 * OUTPUT TO LOWER CASE. THIS CAN BE CANCELLED 45 | 1990 * BY NORMAL, INVERSE, OR FLASH OR POKE 243,0. 46 | 2000 *-------------------------------- 47 | DB5C- 09 80 2010 OUTDO ORA #$80 PRINT (A) 48 | DB5E- C9 A0 2020 CMP #$A0 CONTROL CHR? 49 | DB60- 90 02 2030 BCC .1 SKIP IF SO 50 | DB62- 05 F3 2040 ORA FLASH.BIT =$40 FOR FLASH, ELSE $00 51 | DB64- 20 ED FD 2050 .1 JSR MON.COUT "AND"S WITH $3F (INVERSE), $7F (FLASH) 52 | DB67- 29 7F 2060 AND #$7F 53 | DB69- 48 2070 PHA 54 | DB6A- A5 F1 2080 LDA SPEEDZ COMPLEMENT OF SPEED # 55 | DB6C- 20 A8 FC 2090 JSR MON.WAIT SO SPEED=255 BECOMES (A)=1 56 | DB6F- 68 2100 PLA 57 | DB70- 60 2110 RTS 58 | ``` 59 | The routine is called with the accumulator containing the character to print every time Applesoft needs to print something (like when using `PRINT` or `INPUT` or ... `SPC` !) 60 | 61 | The routine that will effectively print the character on screen is `COUT` (in `$FDED`here named `MON.COUT`) but the `OUTDO` routine here is the pre-treatment of the character to print. 62 | 63 | As you can see, before calling `MON.COUT`, an `ORA` with zero-page memory `$F3` is executed. This `ORA` is needed to display characters in flash mode. The problem is that `$F3`, even after a `CTRL-RESET` is not reset and still contains `$40` (decimal 64), meaning that Applesoft is still (partially -- see below why) in flash mode. 64 | 65 | But if it's in flash mode, how comes it prints NORMAL single quotes and not flashing characters ? Because `$F3` is just a mask and is not enough to flash the characters on screen. Another mask, in zero-page `$32` is also used, but this time by the `MON.COUT` routine. In fact `$32`is usually considered to be the memory that indicates if we are in normal (value `$FF`, decimal `255`), flash (value `$7F`, decimal `127`) or inverse (value `$3F`, decimal `63`) modes. But for the flash mode, the mask in `$F3` is equally primordial. In fact, even in normal and inverse modes, the value in `$F3 `has an impact since the `ORA` is called whatever the display mode is. 66 | 67 | So, before any character is displayed on screen by Applesoft, two masking operations occur on the ASCII value of the character. 68 | 69 | 1. an `ORA` with the value in `$F3` 70 | 2. an `AND` with the value in `$32` 71 | 72 | `CTRL-RESET` resets the value in `$32` to `255` ("normal" display mode) but it does not touch the value in `$F3`. That's why we have these display glitches if we `CTRL-RESET` after `FLASH`. 73 | 74 | Clearly, it's a bug. 75 | 76 | When we press `CTRL-RESET` the system does a lot of resets, among them a call to SETNORM (`$FE84`) that restores the `$32` memory to `$FF`. Then a bit later it calls the routine pointed by the reset vector (`$3F2`), that is Applesoft's warm restart in `$E003` which in turns calls the `RESTART` routine in `$D43C`. This routine's main purpose is to display the `]` prompt and wait for the user to type a series of direct commands or a line of code. The only way I see we could fix this bug is by pointing the reset vector in `$3F2` to a small routine that would reset `$F3` and then call the `RESTART` routine. 77 | 78 | 79 | ## Taking advantage of what we know 80 | Of course Applesoft expects and uses some specific values in `$F3` and `$32`. 81 | 82 | | | NORMAL | FLASH | INVERSE | 83 | |--|--|--|--| 84 | | **$32 (50)** | $FF (255)| $7F (127) | $3F (63) | 85 | | **$F3 (243)** | $00 (0)| $40 (64) | $00 (0) 86 | 87 | Now, if we play a bit with the values in those two memory locations we will alter the way Applesoft displays characters on screen. 88 | 89 | Two well-known (useless but kind of fun) examples are 90 | 91 | 1. `POKE 243,32` to lowercase everything, notably the `LIST` command. `POKE 243,0` will return to normal display. 92 | 2. `POKE 50, 128` to sort-of disable printing. `LIST` and `CATALOG` will appear empty and the `]` prompt will disappear as well. 93 | 94 | Notice that `POKE`ing in 243 influences only Applesoft and not the system ! That's because the `ORA $F3` only occurs within an Applesoft 6502 routine ! 95 | 96 | So ... `SPC()` is an Applesoft display routine. And as such, it will be influenced by both the values in `$32` and `$F3`. Now we just have to tweak those values to get expected results, that is, replace the space character by the character we want. 97 | 98 | It turns out it's quite easy to do. You take the ASCII value of the character you want to display instead of the space character and you `POKE` it in `243` (`$F3`). 99 | 100 | For example, the asterisk has an ASCII value of `42` (or `$2A`), so we just do `POKE 243,42` and then `PRINT SPC(10)`. After that we restore the original state with `POKE 243,0`. 101 | 102 | Wow that was easy ! We now have a REPEAT instruction in Applesoft ! 103 | Oh yeah ? 104 | 105 | Of course, there's a small caveat. 106 | 107 | Remember that what happens behind the hood is that the space character (`$20`) is `ORA`'d with the value in `$F3`. 108 | 109 | - `$20 ORA $2A `equals `$2A`. So, we're good. 110 | - `$20 ORA $61` ("a" lowercase character) equals `$61`. Still good. But ... 111 | - `$20 ORA $41` ("A" uppercase character) equals ... `$61` too ! Oh noo ! 112 | 113 | In fact it will not work with ASCII characters from 64 to 95, those are the uppercase letters, the `@` sign, the square brackets `][`, the backslash `\` , the caret `^` and the underscore `_` . 114 | 115 | But all is not lost since we also have an `AND` mask to apply ! For now, we assumed its value was `$FF` (`255`), meaning it has no effect. But what if ... 116 | 117 | `$20 ORA $41` ("A" uppercase character) equals `$61` but if we do `$61 AND $DF` (thus clearing bit 5), we have `$41` back ! 118 | 119 | So, for ASCII between 64 and 95, we have to change the value in `$32` as well and it has to be `$DF` (`223`). 120 | 121 | 122 | 123 | (Notice how I immediately typed the `POKE`s to restore the normal display, otherwise my next commands would be partially invisible.) 124 | 125 | Now we are able to use `SPC` to repeat any character available ! 126 | 127 | ## One last thing ... 128 | Ok, it works ... but only in NORMAL mode ! How do we make it work in FLASH and INVERSE ? 129 | 130 | It's quite simple. I won't go into details on how it works but we just fiddle with the value in `$32`. 131 | 132 | Basically what we do is subtract `128` (`$80`) from the value in `$32` if we want FLASH characters and subtract `192` (`$C0`) if we want INVERSE characters. 133 | 134 | It works because bytes in the text screen memory (in `$400`) do not use the ASCII values of the characters. Byte values between 0-63 will display INVERSE characters (no lowercase characters), while values between 64-127 will display FLASH characters (no lowercase characters). Values above 127 are NORMAL characters (upper and lowercase). Now 255-192=63 (INVERSE) and 255-128=127 (FLASH) ... I suppose you got it. 135 | 136 | Remember that not all characters are printable in FLASH or INVERSE. 137 | 138 | ## A little program to sum it all 139 | This little program will demonstrate what we learned here. 140 | 141 | It will fill the screen with one kind of character, using four `SPC` statements with a parameter of 240. 142 | ```basic 143 | 10 HOME 144 | 20 X = 255: Y=0: REM INIT VALUES 145 | 30 POKE 243,0: REM RESET ORA MASK 146 | 40 POKE 50, X: REM RESET AND MASK 147 | 50 VTAB 1 148 | 60 INPUT "ASCII (1-255; 0 EXITS) ? ";Z 149 | 70 IF NOT Z THEN END 150 | 80 INPUT "NORMAL/FLASH/INVERSE (N/F/I) ? ";N$ 151 | 90 IF N$ = "F" THEN Y=128 152 | 100 IF N$ = "I" THEN Y=192 153 | 110 IF (Z>=64 AND Z<=95) OR (Z>=192 AND Z<=223) THEN X = 223: REM PREPARE END MASK FOR ASCII 64-95 OR 192-223 154 | 120 POKE 50,X-Y: REM SET NORMAL/FLASH/INVERSE MASK 155 | 130 POKE 243, Z: REM SET ORA MASK 156 | 140 VTAB 1 157 | 150 PRINT SPC(240): REM FILL THE SCREEN ! 158 | 160 PRINT SPC(240) 159 | 170 PRINT SPC(240) 160 | 180 PRINT SPC(240) 161 | 190 GOTO 20 162 | ``` 163 | 164 | 165 | # HAPPY CODING ! 166 | 167 |
168 | © 2021 François Vander Linden 169 | -------------------------------------------------------------------------------- /applesoft/spc/spc1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tilleul/apple2/9f533af8cd212a39e128679e054990c42e793745/applesoft/spc/spc1.png -------------------------------------------------------------------------------- /applesoft/spc/spc2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tilleul/apple2/9f533af8cd212a39e128679e054990c42e793745/applesoft/spc/spc2.png -------------------------------------------------------------------------------- /applesoft/spc/spc3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tilleul/apple2/9f533af8cd212a39e128679e054990c42e793745/applesoft/spc/spc3.png -------------------------------------------------------------------------------- /applesoft/spc/spc4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tilleul/apple2/9f533af8cd212a39e128679e054990c42e793745/applesoft/spc/spc4.png -------------------------------------------------------------------------------- /applesoft/spc/spc5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tilleul/apple2/9f533af8cd212a39e128679e054990c42e793745/applesoft/spc/spc5.png -------------------------------------------------------------------------------- /applesoft/spc/spc6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tilleul/apple2/9f533af8cd212a39e128679e054990c42e793745/applesoft/spc/spc6.png -------------------------------------------------------------------------------- /applesoft/spc/spc7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tilleul/apple2/9f533af8cd212a39e128679e054990c42e793745/applesoft/spc/spc7.png -------------------------------------------------------------------------------- /applesoft/spc/spc_test.bas: -------------------------------------------------------------------------------- 1 | 10 HOME 2 | 20 X = 255: Y=0: REM INIT VALUES 3 | 30 POKE 243,0: REM RESET ORA MASK 4 | 40 POKE 50, X: REM RESET AND MASK 5 | 50 VTAB 1 6 | 60 INPUT "ASCII (1-255; 0 EXITS) ? ";Z 7 | 70 IF NOT Z THEN END 8 | 80 INPUT "NORMAL/FLASH/INVERSE (N/F/I) ? ";N$ 9 | 90 IF N$ = "F" THEN Y=128 10 | 100 IF N$ = "I" THEN Y=192 11 | 110 IF (Z>=64 AND Z<=95) OR (Z>=192 AND Z<=223) THEN X = 223: REM PREPARE END MASK FOR ASCII 64-95 OR 192-223 12 | 120 POKE 50,X-Y: REM SET NORMAL/FLASH/INVERSE MASK 13 | 130 POKE 243, Z: REM SET ORA MASK 14 | 140 VTAB 1 15 | 150 PRINT SPC(240): REM FILL THE SCREEN ! 16 | 160 PRINT SPC(240) 17 | 170 PRINT SPC(240) 18 | 180 PRINT SPC(240) 19 | 190 GOTO 20 20 | -------------------------------------------------------------------------------- /honoring_the_code/001 - Space Maze/README.md: -------------------------------------------------------------------------------- 1 | # Honoring The Code, part 1: Space Maze 2 | Space Maze by Micro-Sparc, as published in Nibble Magazine #1, Jan-Feb 1980. 3 | 4 | ## Summary 5 | * [Introduction](#introduction) 6 | * [Overview](#overview) 7 | * [How the code works](#how-the-code-works) 8 | * [Fixing the game](#fixing-the-game) 9 | * [Further optimization](#further-optimization) 10 | 11 | ## Introduction 12 | For this first episode I've chosen the very first issue of Nibble magazine, dating back to Jan-Feb 1980 ! Were you one of the lucky one to own a copy of this issue back then ? Well, I wasn't. At the time, I was only 5 and I'm not even sure we had an Apple II at home then. And also, Nibble was a US magazine that came to Europe (I'm from Belgium) mostly through their Nibble Express compendiums. 13 | 14 | Although I did not have this issue, for a reason or another, the program we'll be talking about came to me through a disk(ette) of various basic programs. I remember vividly the graphics and the Star Wars music. Unfortunately, as we will see, the game is terrible. It's terrible today but it was also terrible back then. 15 | 16 | Let me present you **SPACE MAZE** ! 17 | 18 | ## Overview 19 | 20 | Here's a link to the DSK file that contains the original code (and all the other code we're going to create): [htc1_spacemaze.dsk](./files/htc1_spacemaze.dsk) 21 | 22 | Simply RUN SPACE MAZE from the DSK ... 23 | 24 | Here are two screenshots of the game... 25 | 26 | Instructions: 27 | 28 | ![instructions](./files/htc1.png) 29 | 30 | Starting screen: 31 | 32 | ![starting screen](./files/htc2.png) 33 | 34 | The objective of the game is to move your spaceship (represented by a dot) to the end of the maze as fast as possible without crashing in the walls of the maze. 35 | Using the Apple II (analog) joystick, you carefully give your spaceship direction and speed and try to go as far as possible. You also have to make sure you don't go out of fuel: even when you're not moving your fuel runs fast, so you have to hurry. 36 | 37 | In itself the game concept is simple and it's not very original even for the time but the idea that this game is Applesoft only (and really short in fact) is appealing. Unfortunately the joystick control is very hard to master and even in "Easy" mode (there's a "Hard" mode where your ship is sometimes pulled in a random direction) it's nearly impossible to beat. And once you beat the game, you're forced to play in Hard mode and the only thing you can do is try to break your hiscore (which means reach the end of the maze in a shorter time). 38 | 39 | The main problem with the game is that it's not very responsive. You can turn on sound and your ship will emit a beep every time it has moved from one point to another and by hearing the beat of the beep you know that the code is too slow to offer responsive controls. 40 | 41 | ## How the code works 42 | Here's the full original code: [spacemaze.bas](./files/spacemaze.bas) 43 | 44 | The main game routine is in lines 100-300. 45 | 46 | Lines 100-175 will detect if the spaceship is out of the maze corridors. It's a serie of IF/THEN checking if the spaceship is within the maze boundaries. 47 | 48 | Lines 210-300 will manage the spaceship movement (reading the joystick), plotting/unplotting the spaceship dot and also print the fuel level and spaceship coordinates. 49 | 50 | How does the program check if the spaceship has hit a wall ? My intuition was that maybe the program used the "collision counter" in $EA (234) but this only works with Hires Shape Tables (you know DRAW/XDRAW routines to draw 2D vector shapes in hires). What it does is more simple: as the maze is hardcoded, the code checks if the spaceship is within one of the maze rectangles sections. This is what lines 100-162 do. They check every rectangle and set a variable Z that contains the rectangle number where the spaceship is (among 16 rectangles). 51 | 52 | Here's a representation of the 16 rectangle zones the maze is made of. 53 | ![rectangle zones](./files/htc3.png) 54 | 55 | The main problem is that the code goes through ALL of the coordinates testing for EACH rectangle zone EVERY time, even if it has found already found the zone where the spaceship is. Each of these tests is made of 4 conditions (testing 2 limits of X and then 2 limits of Y). That's 4x16=64 conditions in a row ... and this is killing the game. 56 | 57 | 16 consecutive lines like this one will kill the game: 58 | ```basic 59 | 100 IF (X > = 10 AND X < = 80) AND (Y > = 80 AND Y < = 100) THEN Z = 1 60 | ``` 61 | 62 | It should be noted that although Z holds the zone number where the spaceship is, this variable is not used for anything else but testing if it's non-zero (in which case the spaceship is in none of the zones, thus out of the maze) and also that Z is not even properly set as lines 135 to 142 all set Z to 6 as if it was the same zone. Also, the comments indicate that there are 11 zones when in fact there are 16. It looks like during the development the maze was modified from 11 to 16 zones. And one last thing, some zones limits overlap but that's ok. 63 | 64 | ## Fixing the game 65 | So the first thing that comes to mind is that once you know in which zone the spaceship is, you need to skip all the other tests. Simply putting a "GOTO 175" at the end of each test line is enough. Running the program with the sound on for the spaceship will prove you that this was really the main bottleneck ! You can already notice how the spaceship is now much more responsive. 66 | 67 | ```basic 68 | 100 IF (X > = 10 AND X < = 80) AND (Y > = 80 AND Y < = 100) THEN Z = 1: GOTO 175 69 | 110 IF (X > = 60 AND X < = 100) AND (Y > = 100 AND Y < = 120) THEN Z = 2: GOTO 175 70 | 120 IF (X > = 80 AND X < = 100) AND (Y > = 120 AND Y < = 158) THEN Z = 3: GOTO 175 71 | 125 IF (X > = 100 AND X < = 140) AND (Y > = 140 AND Y < = 158) THEN Z = 4: GOTO 175 72 | 130 IF (X > = 120 AND X < = 180) AND (Y > = 120 AND Y < = 140) THEN Z = 5: GOTO 175 73 | 135 IF (X > = 160 AND X < = 220) AND (Y > = 140 AND Y < = 158) THEN Z = 6: GOTO 175 74 | 137 IF (X > = 200 AND X < = 220) AND (Y > = 110 AND Y < = 140) THEN Z = 6: GOTO 175 75 | 138 IF (X > = 220 AND X < = 265) AND (Y > = 110 AND Y < = 130) THEN Z = 6: GOTO 175 76 | 139 IF (X > = 245 AND X < = 265) AND (Y > = 40 AND Y < = 110) THEN Z = 6: GOTO 175 77 | 140 IF (X > = 215 AND X < = 245) AND (Y > = 40 AND Y < = 60) THEN Z = 6: GOTO 175 78 | 141 IF (X > = 215 AND X < = 235) AND (Y > = 60 AND Y < = 100) THEN Z = 6: GOTO 175 79 | 142 IF (X > = 180 AND X < = 235) AND (Y > = 80 AND Y < = 100) THEN Z = 6: GOTO 175 80 | 145 IF (X > = 180 AND X < = 200) AND (Y > = 60 AND Y < = 100) THEN Z = 8: GOTO 175 81 | 150 IF (X > = 140 AND X < = 180) AND (Y > = 60 AND Y < = 80) THEN Z = 9: GOTO 175 82 | 160 IF (X > = 100 AND X < = 160) AND (Y > = 40 AND Y < = 60) THEN Z = 10: GOTO 175 83 | 162 IF (X > = 100 AND X < = 120) AND (Y > = 60 AND Y < = 80) THEN Z = 11: GOTO 175 84 | ``` 85 | 86 | Test for yourself, here's the full code: [spacemaze_quickfix.bas](./files/spacemaze_quickfix.bas) 87 | 88 | You may also load it from the DSK file: [htc1_spacemaze.dsk](./files/htc1_spacemaze.dsk) (file "SPACE MAZE QUICK FIX") 89 | 90 | There's one drawback, however: the game will slow down as your spaceship enters more and more deeply into the maze. This is because we always check if the spaceship is in zone 1, then if not, check if it's in zone 2, then if not, check if it's in zone 3, etc. Once we're in zone 16, it will be like we didn't change anything to the code. We've seen that these tests take time and they should be reduced to a minimum. 91 | 92 | So why not use the value of Z to check only what is needed ? Since you're on zone Z, you can only go on zone Z-1 or zone Z+1. So all we have to do is use "ON Z GOSUB" and check for zone Z-1, zone Z and then zone Z+1 and if all failed, it means we're out of the maze. 93 | 94 | So, let's rewrite line 300 with ON GOSUB 95 | ```basic 96 | 300 XO = X:YO = Y: ON Z GOSUB 100, 100, 110, 120, 125,130,135,137,138,139,140,141,142,145,150,160: GOTO 210 97 | ``` 98 | 99 | What this will do is that if Z equals 1, we GOSUB to line 100 and test if we are still in zone 1 or if we moved to zone 2 (which are the only two possible zones where our spaceship can be). If not, then we crash. If Z equals 2, we GOSUB again to line 100 and test if we've moved back to zone 1, are still in zone 2 or moved into zone 3 (it's impossible to be in any other zone so all the other tests will fail and we will crash), etc. 100 | 101 | Lines 100-165 need to be altered so that the zone numbers are correct and so that we return from the calling ON GOSUB asap. Also we need to invert line 162 and 165 otherwise we cannot win the game. 102 | ```basic 103 | 100 IF (X > = 10 AND X < = 80) AND (Y > = 80 AND Y < = 100) THEN Z=1: RETURN 104 | 110 IF (X > = 60 AND X < = 100) AND (Y > = 100 AND Y < = 120) THEN Z=2: RETURN 105 | 120 IF (X > = 80 AND X < = 100) AND (Y > = 120 AND Y < = 158) THEN Z=3: RETURN 106 | 125 IF (X > = 100 AND X < = 140) AND (Y > = 140 AND Y < = 158) THEN Z=4: RETURN 107 | 130 IF (X > = 120 AND X < = 180) AND (Y > = 120 AND Y < = 140) THEN Z=5: RETURN 108 | 135 IF (X > = 160 AND X < = 220) AND (Y > = 140 AND Y < = 158) THEN Z=6: RETURN 109 | 137 IF (X > = 200 AND X < = 220) AND (Y > = 110 AND Y < = 140) THEN Z=7: RETURN 110 | 138 IF (X > = 220 AND X < = 265) AND (Y > = 110 AND Y < = 130) THEN Z=8: RETURN 111 | 139 IF (X > = 245 AND X < = 265) AND (Y > = 40 AND Y < = 110) THEN Z=9: RETURN 112 | 140 IF (X > = 215 AND X < = 245) AND (Y > = 40 AND Y < = 60) THEN Z=10: RETURN 113 | 141 IF (X > = 215 AND X < = 235) AND (Y > = 60 AND Y < = 100) THEN Z=11: RETURN 114 | 142 IF (X > = 180 AND X < = 235) AND (Y > = 80 AND Y < = 100) THEN Z=12: RETURN 115 | 145 IF (X > = 180 AND X < = 200) AND (Y > = 60 AND Y < = 100) THEN Z=13: RETURN 116 | 150 IF (X > = 140 AND X < = 180) AND (Y > = 60 AND Y < = 80) THEN Z=14: RETURN 117 | 160 IF (X > = 100 AND X < = 160) AND (Y > = 40 AND Y < = 60) THEN Z=15: RETURN 118 | 162 IF (X > = 106 AND X < = 114) AND (Y > = 66 AND Y < = 74) THEN 3000: REM BRANCH TO WIN 119 | 165 IF (X > = 100 AND X < = 120) AND (Y > = 60 AND Y < = 80) THEN Z=16: RETURN 120 | ``` 121 | Line 170 does not need to check if Z=0. If we reach line 170 it means we crashed and we can GOTO 4000 immediately. 122 | ```basic 123 | 170 GOTO 4000 124 | ``` 125 | also we can get rid of line 175 as we'll never reach it. 126 | 127 | And that's it ! the game is now smoothier and more responsive. Obviously it goes faster but despite that it's in fact easier to maneuver the spaceship. 128 | 129 | The whole modified code is here: [spacemaze_quickfix2.bas](./files/spacemaze_quickfix2.bas) 130 | 131 | And it can be run from the DSK as "SPACE MAZE QUICK FIX 2" 132 | 133 | ## Further optimization 134 | 135 | This was the main modification that needed to be done in order to "fix" the game but in fact we can do much more if we want to continue optimization. 136 | Without going up until the point where all variables names are one letter only, we can optimize some stuff. 137 | 138 | 1) we can replace the ON GOSUB with an ON GOTO and replace all the RETURNs we've just typed with GOTO 210 as this is what will happen anyway when we return. It means we gain some cycles as the memory address of the next statement does not have to be saved on the stack. 139 | 2) As is, the code goes 16, 8 or 5 times faster than the original: if we go back one zone, it will go 16 times faster (only 1 line of 4 conditions is checked instead of 16), if we stay in the same zone, it goes 8 times faster (2 lines of 4 conditions instead of 16) and if we go to the next zone, it goes 5 times faster as we parse 3 lines of 4 conditions instead of 16. This can be improved if we repeat the same conditions and test first if we are in the actual zone (which is what happens most of the time), then if we are going to the next zone (which happens more than going back to the previous zone), then only check if we are back to the previous zone. 140 | 141 | **It should be noted that this will indeed improve the speed but that it will also lengthen the code and that the code will then reach memory $2000 where the first hires page is located, this will in fact break the program and we won't apply this optimization this time.** 142 | 143 | 3) the sound routine uses two memory addresses to indicate the pitch and the duration of the note being played. The pitch is stored in 780 ($30C), while the duration is in 781 ($30D). The pitch is not modified by the sound routine while the duration is decreased by the sound routine. So when it's time to utter the spaceship beep, we only need to reset back the duration and thus not bother with the pitch (removing one POKE). 144 | 4) to test for difficulty, the program checks the value of the HD$ variable which holds either "H" (hard) or "E" (easy). It's more efficient to check for a number. So we need to replace this check with a numeric variable. 145 | 5) same thing for the spaceship sound, instead of checking for a string variable to see if we must emit a beep, we should check for a numeric variable. 146 | 6) every game cycle, the coordinates of the ship, the fuel left and the hi-score are printed on the screen, and for most of these lines, the programmer used a CALL-868 which clears the line from the cursor to the end of the line. This is inefficient when all you're printing are numeric values. Simply append a space to the numeric value is enough to delete any extra character as those values only increment/decrement by a step of 1. Also the hiscore should be printed once at the start of the game and never again until the next game. 147 | 7) Lines 15 to 50 should be moved at the end of the program as they are used only once. As a rule for any action game in Applesoft: your main game cycling routine should be at the top of the program as any GOTO/GOSUB will search the line number to reach from the top. This is also why the comments at the top should be moved at the end of the program. 148 | 8) I don't know why but the programmer used a lot of CALL-936 which is exactly the same as the HOME command except it's parsed using a few more cycles. Fortunately these calls are not made during the game but CALL-936 is much less readable than HOME. So there's a double reason not to use it. 149 | 9) I have stored common integer values like 49152 in variables as this speeds the parsing. With such a long integer, Applesoft reads 5 numeric characters and transform their ASCII representation to its own numeric format. By using a variable, Applesoft only parse one character (the variable names) and it points to an address in memory where the value is already formatted and ready for use. 150 | 10) The rest of the modifications I've made are either aesthetic or practical ones: 151 | - always try to prompt the user the same way (you either use "Type Y/N" or "Type 'Y' or 'N'" but not both) 152 | - the star wars music is removed from the intro screen as it's really ... dull nowadays :) but you can still ask the program to play it at the start of the game 153 | - I've modified some sounds here and there 154 | - pressing enter when giving choices will default to appropriate values (at least for me) highlighted by INVERSE characters. 155 | - I've added keyboard support, for now the game is easier in that mode than with the joystick but maybe we can alter that a bit in a future version ? 156 | 157 | All in all, here's the full modified code: [spacemaze_htc_v1.bas](./files/spacemaze_htc_v1.bas) 158 | 159 | You can run it from the DSK as "SPACE MAZE HTC V1" 160 | 161 | It certainly can be optimized a little bit more but unless you change the fundamentals of the game (either the game itself or the inner workings) you won't see much difference with any further optimization. 162 | 163 | -------------------------------------------------------------------------------- /honoring_the_code/001 - Space Maze/files/README.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /honoring_the_code/001 - Space Maze/files/htc1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tilleul/apple2/9f533af8cd212a39e128679e054990c42e793745/honoring_the_code/001 - Space Maze/files/htc1.png -------------------------------------------------------------------------------- /honoring_the_code/001 - Space Maze/files/htc1_spacemaze.dsk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tilleul/apple2/9f533af8cd212a39e128679e054990c42e793745/honoring_the_code/001 - Space Maze/files/htc1_spacemaze.dsk -------------------------------------------------------------------------------- /honoring_the_code/001 - Space Maze/files/htc2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tilleul/apple2/9f533af8cd212a39e128679e054990c42e793745/honoring_the_code/001 - Space Maze/files/htc2.png -------------------------------------------------------------------------------- /honoring_the_code/001 - Space Maze/files/htc3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tilleul/apple2/9f533af8cd212a39e128679e054990c42e793745/honoring_the_code/001 - Space Maze/files/htc3.png -------------------------------------------------------------------------------- /honoring_the_code/001 - Space Maze/files/spacemaze.bas: -------------------------------------------------------------------------------- 1 | 3 REM ************************* 2 | 4 REM ** SPACE MAZE ** 3 | 5 REM ** MICRO-SPARC ** 4 | 6 REM ** P.O. BOX 325 ** 5 | 7 REM ** LINCOLN MASS 01773 ** 6 | 8 REM ** COPYRIGHT C 1980 ** 7 | 9 REM ************************* 8 | 12 REM THE FOLLOWING SUBROUTINE GOES INTO MEMORY BLOCK HEX $30E (782), THE TONES RESPOND TO POKE 0 TO 255 9 | 13 REM PITCH= POKE780,P DURATION=POKE781,D 10 | 15 POKE 782,173: POKE 783,48: POKE 784,192: POKE 785,136: POKE 786,208: POKE 787,5: POKE 788,206: POKE 789,13: POKE 790,3 11 | 20 POKE 791,240: POKE 792,9: POKE 793,202: POKE 794,208: POKE 795,245: POKE 796,174: POKE 797,12: POKE 798,3: POKE 799,76 12 | 25 POKE 800,14: POKE 801,3: POKE 802,96 13 | 27 CALL - 936 14 | 30 VTAB 7: INVERSE : HTAB 10: PRINT "** SPACE MAZE **": VTAB 23: HTAB 4: PRINT "COPYRIGHT 1980..MICRO-SPARC, INC.": NORMAL 15 | 31 VTAB 9: PRINT "YOU WILL PILOT A SPACE CRUISER THRU THE": PRINT "STAR MAZE TO REACH THE PRIZED DILITHIUM": PRINT "CRYSTALS AT THE CENTER OF THE MAZE": INVERSE : PRINT 16 | 32 PRINT "BE CAREFUL! IN THE HARD VERSION OF THE": PRINT "GAME YOUR SHIP IS PULLED BY HOSTILE ": PRINT "MAGNETIC FORCES.. SO TAKE CARE " 17 | 33 PRINT "NOT TO CRASH!!!!!!!!!!!!!!!!!!!!!!!!!!!" 18 | 34 NORMAL : PRINT "DO YOU WANT YOUR SHIP SIGNAL SOUNDS? ": INPUT "TYPE Y OR N";NS$: INPUT "EASY OR HARD GAME? TYPE 'E' OR 'H'";HD$ 19 | 38 GOSUB 500: PRINT "DO YOU WANT STARWARS MUSIC EACH GAME?": INPUT "Y OR N ";M$ 20 | 40 HGR : GOSUB 2000 21 | 45 IF M$ < > "N" THEN GOSUB 500 22 | 50 GOTO 200 23 | 97 REM THE FOLLOWING SUBROUTINE TESTS WHETHER X AND Y ARE CONTAINED IN THE SERIES OF 11 RECTANGLES MAKING UP THE MAZE 24 | 98 REM IF X AND Y ARE SENSED, THEN Z IS SET THE NUMBER OF THE RECTANGLE. AT THE END OF THE TEST, Z IS TESTED. IF Z IS GREATER THAN 25 | 99 REM ZERO IT MEANS X AND Y ARE IN BOUNDS. IF Z=0 THEN NO X AND Y HAVE BEEN SENSED IN BOUNDS AND THE PROGRAM GOES TO THE CRASH SUBRTNE. 26 | 100 IF (X > = 10 AND X < = 80) AND (Y > = 80 AND Y < = 100) THEN Z = 1 27 | 110 IF (X > = 60 AND X < = 100) AND (Y > = 100 AND Y < = 120) THEN Z = 2 28 | 120 IF (X > = 80 AND X < = 100) AND (Y > = 120 AND Y < = 158) THEN Z = 3 29 | 125 IF (X > = 100 AND X < = 140) AND (Y > = 140 AND Y < = 158) THEN Z = 4 30 | 130 IF (X > = 120 AND X < = 180) AND (Y > = 120 AND Y < = 140) THEN Z = 5 31 | 135 IF (X > = 160 AND X < = 220) AND (Y > = 140 AND Y < = 158) THEN Z = 6 32 | 137 IF (X > = 200 AND X < = 220) AND (Y > = 110 AND Y < = 140) THEN Z = 6 33 | 138 IF (X > = 220 AND X < = 265) AND (Y > = 110 AND Y < = 130) THEN Z = 6 34 | 139 IF (X > = 245 AND X < = 265) AND (Y > = 40 AND Y < = 110) THEN Z = 6 35 | 140 IF (X > = 215 AND X < = 245) AND (Y > = 40 AND Y < = 60) THEN Z = 6 36 | 141 IF (X > = 215 AND X < = 235) AND (Y > = 60 AND Y < = 100) THEN Z = 6 37 | 142 IF (X > = 180 AND X < = 235) AND (Y > = 80 AND Y < = 100) THEN Z = 6 38 | 145 IF (X > = 180 AND X < = 200) AND (Y > = 60 AND Y < = 100) THEN Z = 8 39 | 150 IF (X > = 140 AND X < = 180) AND (Y > = 60 AND Y < = 80) THEN Z = 9 40 | 160 IF (X > = 100 AND X < = 160) AND (Y > = 40 AND Y < = 60) THEN Z = 10 41 | 162 IF (X > = 100 AND X < = 120) AND (Y > = 60 AND Y < = 80) THEN Z = 11 42 | 165 IF (X > = 106 AND X < = 114) AND (Y > = 66 AND Y < = 74) THEN 3000: REM BRANCH TO WIN 43 | 170 IF Z < = 0 THEN 4000: REM BRANCH TO CRASH...NO FLAGS WERE SET TO INDICATE PRESENCE IN THE MAZE...THEREFORE MUST BE OUTSIDE. 44 | 175 Z = 0: RETURN : REM RESET Z EACH TEST 45 | 200 X = 15:Y = 90:HV = 0:VV = 0:TM = 600:XO = 15:YO = 90: CALL - 936 46 | 210 IF PDL (0) > = 165 THEN HV = HV + 1 47 | 220 IF PDL (0) < = 90 THEN HV = HV - 1 48 | 230 IF PDL (1) > = 165 THEN VV = VV + 1 49 | 231 IF HD$ = "E" THEN 240 50 | 232 IF RND (1) < .05 THEN HV = HV + 1 51 | 233 IF RND (1) > .95 THEN VV = VV + 1 52 | 240 IF PDL (1) < = 90 THEN VV = VV - 1 53 | 242 X = XO + HV:Y = YO + VV 54 | 243 TM = TM - 1: VTAB 21: PRINT TAB( 10)"FUEL LEFT= ";TM: IF TM < 100 THEN VTAB 21: CALL - 868: PRINT TAB( 10)"FUEL LEFT= ";TM 55 | 245 VTAB 22: CALL - 868: PRINT "HORIZ =";HV;: PRINT TAB( 25)"VERTICAL =";VV 56 | 260 HCOLOR= 3: HPLOT X,Y: IF PT = 0 THEN 267 57 | 265 VTAB 23: PRINT TAB( 4)"PREVIOUS RECORD SCORE IS: ";PT 58 | 267 IF TM < = 0 THEN CALL - 936: FLASH : PRINT TAB( 10)"OUT OF FUEL";: PRINT TAB( 10)" ": GOSUB 4000 59 | 270 IF X = XO AND Y = YO THEN 300 60 | 280 HCOLOR= 0: HPLOT XO,YO: IF NS$ = "N" THEN 300 61 | 285 POKE 780,150: POKE 781,10: CALL 782 62 | 300 XO = X:YO = Y: GOSUB 100: GOTO 210 63 | 498 REM THE 500 SUBRTNE SETS UP THE MUSIC. M1=PITCH, M2=DURATION. 700 PLAYS IT. 64 | 500 M1 = 230:M2 = 75: GOSUB 700:M1 = 126:M2 = 250: GOSUB 700:M1 = 170:M2 = 250: GOSUB 700:M1 = 190:M2 = 75: GOSUB 700 65 | 510 M1 = 203:M2 = 75: GOSUB 700:M1 = 230:M2 = 75: GOSUB 700:M1 = 126:M2 = 250: GOSUB 700:M1 = 170:M2 = 250: GOSUB 700 66 | 515 M1 = 190:M2 = 100: GOSUB 700:M1 = 203:M2 = 100: GOSUB 700:M1 = 190:M2 = 100: GOSUB 700:M1 = 230:M2 = 250: GOSUB 700: RETURN 67 | 700 POKE 780,M1: POKE 781,M2: CALL 782: RETURN 68 | 2000 HCOLOR= 3: HPLOT 0,0 TO 279,0 TO 279,159 TO 0,159 TO 0,0 69 | 2001 HPLOT 70,10 TO 60,10 TO 60,20 TO 70,20 TO 70,30 TO 60,30: HPLOT 75,30 TO 75,10 TO 85,10 TO 85,20 TO 75,20: HPLOT 90,30 TO 90,10 TO 100,10 TO 100,30: HPLOT 90,20 TO 100,20 70 | 2002 HPLOT 115,10 TO 105,10 TO 105,30 TO 115,30: HPLOT 130,10 TO 120,10 TO 120,30 TO 130,30: HPLOT 120,20 TO 125,20: HPLOT 140,30 TO 140,10 TO 146,20 TO 152,10 TO 152,30 71 | 2003 HPLOT 158,30 TO 158,10 TO 168,10 TO 168,30: HPLOT 158,20 TO 168,20: HPLOT 173,10 TO 183,10 TO 173,30 TO 183,30: HPLOT 198,10 TO 188,10 TO 188,30 TO 198,30: HPLOT 188,20 TO 193,20 72 | 2005 HPLOT 10,80 TO 80,80 TO 80,100 TO 100,100 TO 100,140 TO 120,140 TO 120,120 TO 180,120 73 | 2010 HPLOT 180,120 TO 180,140 TO 200,140 TO 200,110 TO 245,110 TO 245,60 TO 235,60 TO 235,100 TO 180,100 TO 180,80 TO 140,80 TO 140,60 TO 120,60 TO 120,80 TO 100,80 74 | 2015 HPLOT 100,80 TO 100,40 TO 160,40 TO 160,60 TO 200,60 TO 200,80 TO 215,80 TO 215,40 TO 265,40 TO 265,130 TO 220,130 TO 220,158 TO 160,158 75 | 2020 HPLOT 220,158 TO 160,158 TO 160,140 TO 140,140 TO 140,158 TO 80,158 TO 80,120 TO 60,120 TO 60,100 TO 10,100 76 | 2030 HCOLOR= 3: HPLOT 106,66 TO 114,66 TO 114,74 TO 106,74 TO 106,66 77 | 2033 HPLOT 108,68 TO 112,72: HPLOT 108,72 TO 112,68: RETURN 78 | 3000 POP : TEXT : FOR NN = 250 TO 0 STEP - 15: PRINT "** WINNER **";: POKE 780,NN: POKE 781,10 79 | 3005 CALL 782: NEXT NN: FOR N = 1 TO 500: NEXT N: PRINT : PRINT 80 | 3010 IF TM > PT THEN HOME : VTAB 10: FLASH : PRINT "CONGRATULATIONS!": NORMAL : PRINT "YOU'VE BEATEN THE PREVIOUS HIGH SCORE ": PRINT "OF ";PT;" WITH YOUR SCORE OF ";TM 81 | 3011 GC = GC + 1: IF GC = 1 THEN PRINT : PRINT "IF YOU'VE BEEN PLAYING THE EASY GAME": PRINT "YOU'RE A WINNER! NOW WE'LL ADVANCE TO": PRINT "THE HARD GAME":HD$ = "H" 82 | 3012 IF TM > PT THEN PT = TM 83 | 3015 GOTO 4007 84 | 4000 POP : TEXT : FLASH : FOR NN = 1 TO 100: PRINT "** CRASH **";: NEXT NN: NORMAL 85 | 4005 FOR NN = 1 TO 250 STEP 50: POKE 780,NN: POKE 781,50: CALL 782: NEXT NN 86 | 4006 FOR NN = 1 TO 2000: NEXT NN: CALL - 936 87 | 4007 INPUT "ANOTHER MISSION? HIT RETURN";A$: HGR : GOTO 40 -------------------------------------------------------------------------------- /honoring_the_code/001 - Space Maze/files/spacemaze_htc_v1.bas: -------------------------------------------------------------------------------- 1 | 0 GOTO 5000 2 | 3 | 100 IF (X > = 10 AND X < = 80) AND (Y > = 80 AND Y < = 100) THEN Z=1: GOTO 180 4 | 110 IF (X > = 60 AND X < = 100) AND (Y > = 100 AND Y < = 120) THEN Z=2: GOTO 180 5 | 120 IF (X > = 80 AND X < = 100) AND (Y > = 120 AND Y < = 158) THEN Z=3: GOTO 180 6 | 125 IF (X > = 100 AND X < = 140) AND (Y > = 140 AND Y < = 158) THEN Z=4: GOTO 180 7 | 130 IF (X > = 120 AND X < = 180) AND (Y > = 120 AND Y < = 140) THEN Z=5: GOTO 180 8 | 135 IF (X > = 160 AND X < = 220) AND (Y > = 140 AND Y < = 158) THEN Z=6: GOTO 180 9 | 137 IF (X > = 200 AND X < = 220) AND (Y > = 110 AND Y < = 140) THEN Z=7: GOTO 180 10 | 138 IF (X > = 220 AND X < = 265) AND (Y > = 110 AND Y < = 130) THEN Z=8: GOTO 180 11 | 139 IF (X > = 245 AND X < = 265) AND (Y > = 40 AND Y < = 110) THEN Z=9: GOTO 180 12 | 140 IF (X > = 215 AND X < = 245) AND (Y > = 40 AND Y < = 60) THEN Z=10: GOTO 180 13 | 141 IF (X > = 215 AND X < = 235) AND (Y > = 60 AND Y < = 100) THEN Z=11: GOTO 180 14 | 142 IF (X > = 180 AND X < = 235) AND (Y > = 80 AND Y < = 100) THEN Z=12: GOTO 180 15 | 145 IF (X > = 180 AND X < = 200) AND (Y > = 60 AND Y < = 100) THEN Z=13: GOTO 180 16 | 150 IF (X > = 140 AND X < = 180) AND (Y > = 60 AND Y < = 80) THEN Z=14: GOTO 180 17 | 160 IF (X > = 100 AND X < = 160) AND (Y > = 40 AND Y < = 60) THEN Z=15: GOTO 180 18 | 162 IF (X > = 106 AND X < = 114) AND (Y > = 66 AND Y < = 74) THEN 3000: REM BRANCH TO WIN 19 | 165 IF (X > = 100 AND X < = 120) AND (Y > = 60 AND Y < = 80) THEN Z=16: GOTO 180 20 | 170 GOTO 4000: REM BRANCH TO CRASH...NO FLAGS WERE SET TO INDICATE PRESENCE IN THE MAZE...THEREFORE MUST BE OUTSIDE. 21 | 180 IF J THEN 210 22 | 190 K=PEEK(H): IF K = 201 THEN VV=VV-1: POKE C,0: GOTO 242 23 | 195 IF K = 202 THEN HV=HV-1: POKE C,0: GOTO 242 24 | 200 IF K = 203 THEN VV=VV+1: POKE C,0: GOTO 242 25 | 205 IF K = 204 THEN HV=HV+1: POKE C,0: GOTO 242 26 | 206 GOTO 242 27 | 28 | 210 IF PDL (0) > = 165 THEN HV = HV + 1: REM IF HV> 1 THEN HV=1 29 | 220 IF PDL (0) < = 90 THEN HV = HV - 1: REM IF HV<-1 THEN HV=-1 30 | 230 IF PDL (1) > = 165 THEN VV = VV + 1: REM IF VV>1 THEN VV=1 31 | 32 | 231 IF E THEN 240 33 | 232 IF RND (1) < .05 THEN HV = HV + 1 34 | 233 IF RND (1) > .95 THEN VV = VV + 1 35 | 240 IF PDL (1) < = 90 THEN VV = VV - 1: REM IF VV<-1 THEN VV=-1 36 | 242 X = XO + HV:Y = YO + VV 37 | 38 | 243 TM = TM - 1: VTAB 21: HTAB 25: PRINT TM" " 39 | 245 HTAB 13: PRINT HV" ";: HTAB 38: PRINT VV" " 40 | 41 | 260 HCOLOR= 3: HPLOT X,Y 42 | 43 | 267 IF TM < = 0 THEN HOME: FLASH : PRINT TAB( 10)"OUT OF FUEL";: PRINT TAB( 10)" ": GOTO 4000 44 | 270 IF X = XO AND Y = YO THEN 300 45 | 46 | 47 | 280 HCOLOR= 0: HPLOT XO,YO: IF M THEN 300 48 | 285 POKE D,10: CALL S 49 | 300 XO = X:YO = Y: ON Z GOTO 100, 100, 110, 120, 125,130,135,137,138,139,140,141,142,145,150,160,165: GOTO 210 50 | 51 | 52 | 498 REM THE 500 SUBRTNE SETS UP THE MUSIC. M1=PITCH, M2=DURATION. 700 PLAYS IT. 53 | 499 REM *********** PLAY STAR WARS THEME ********** 54 | 500 M1 = 230:M2 = 75: GOSUB 700:M1 = 126:M2 = 250: GOSUB 700:M1 = 170:M2 = 250: GOSUB 700:M1 = 190:M2 = 75: GOSUB 700 55 | 510 M1 = 203:M2 = 75: GOSUB 700:M1 = 230:M2 = 75: GOSUB 700:M1 = 126:M2 = 250: GOSUB 700:M1 = 170:M2 = 250: GOSUB 700 56 | 515 M1 = 190:M2 = 100: GOSUB 700:M1 = 203:M2 = 100: GOSUB 700:M1 = 190:M2 = 100: GOSUB 700:M1 = 230:M2 = 250: GOSUB 700: RETURN 57 | 700 POKE P,M1: POKE D,M2: CALL S: RETURN 58 | 59 | 60 | 61 | 62 | 1999 REM ******** DRAW MAZE ************** 63 | 2000 HCOLOR= 3: HPLOT 0,0 TO 279,0 TO 279,159 TO 0,159 TO 0,0 64 | 2001 HPLOT 70,10 TO 60,10 TO 60,20 TO 70,20 TO 70,30 TO 60,30: HPLOT 75,30 TO 75,10 TO 85,10 TO 85,20 TO 75,20: HPLOT 90,30 TO 90,10 TO 100,10 TO 100,30: HPLOT 90,20 TO 100,20 65 | 2002 HPLOT 115,10 TO 105,10 TO 105,30 TO 115,30: HPLOT 130,10 TO 120,10 TO 120,30 TO 130,30: HPLOT 120,20 TO 125,20: HPLOT 140,30 TO 140,10 TO 146,20 TO 152,10 TO 152,30 66 | 2003 HPLOT 158,30 TO 158,10 TO 168,10 TO 168,30: HPLOT 158,20 TO 168,20: HPLOT 173,10 TO 183,10 TO 173,30 TO 183,30: HPLOT 198,10 TO 188,10 TO 188,30 TO 198,30: HPLOT 188,20 TO 193,20 67 | 2005 HPLOT 10,80 TO 80,80 TO 80,100 TO 100,100 TO 100,140 TO 120,140 TO 120,120 TO 180,120 68 | 2010 HPLOT 180,120 TO 180,140 TO 200,140 TO 200,110 TO 245,110 TO 245,60 TO 235,60 TO 235,100 TO 180,100 TO 180,80 TO 140,80 TO 140,60 TO 120,60 TO 120,80 TO 100,80 69 | 2015 HPLOT 100,80 TO 100,40 TO 160,40 TO 160,60 TO 200,60 TO 200,80 TO 215,80 TO 215,40 TO 265,40 TO 265,130 TO 220,130 TO 220,158 TO 160,158 70 | 2020 HPLOT 220,158 TO 160,158 TO 160,140 TO 140,140 TO 140,158 TO 80,158 TO 80,120 TO 60,120 TO 60,100 TO 10,100 71 | 2030 HCOLOR= 3: HPLOT 106,66 TO 114,66 TO 114,74 TO 106,74 TO 106,66 72 | 2033 HPLOT 108,68 TO 112,72: HPLOT 108,72 TO 112,68: RETURN 73 | 74 | 2999 REM ******** WINNER ************* 75 | 3000 TEXT : FOR I = 250 TO 0 STEP - 15: PRINT "** WINNER **";: POKE P,I: POKE D,10 76 | 3005 CALL S: NEXT : FOR I = 1 TO 500: NEXT: PRINT : PRINT 77 | 3010 IF TM > PT THEN HOME : VTAB 10: FLASH : PRINT "CONGRATULATIONS!": NORMAL : PRINT "YOU'VE BEATEN THE PREVIOUS HIGH SCORE ": PRINT "OF ";PT;" WITH YOUR SCORE OF ";TM 78 | 3011 GC = GC + 1: IF GC = 1 THEN PRINT : PRINT "IF YOU'VE BEEN PLAYING THE EASY GAME": PRINT "YOU'RE A WINNER! NOW WE'LL ADVANCE TO": PRINT "THE HARD GAME":HD$ = "H" 79 | 3012 IF TM > PT THEN PT = TM 80 | 3015 PRINT: GOTO 4010 81 | 82 | 3999 REM ********** CRASH ! ********** 83 | 4000 TEXT : FLASH : FOR I = 1 TO 80: PRINT "** CRASH **";: POKE P,I: POKE D,2: CALL S: NEXT: NORMAL 84 | 4005 FOR I = 1 TO 250 STEP 50: POKE P,I: POKE D,50: CALL S: NEXT 85 | 4006 FOR I = 1 TO 1000: NEXT: HOME 86 | 87 | 4009 REM ********** PLAY AGAIN ? ******** 88 | 4010 POKE C,0: PRINT "ANOTHER MISSION (";:INVERSE:?"Y";:NORMAL:?"/N)? ";: GET A$: IF A$<>"N" THEN HGR : GOTO 6000 89 | 4020 END 90 | 91 | 92 | 4098 REM THE FOLLOWING SUBROUTINE GOES INTO MEMORY BLOCK HEX $30E (782), THE TONES RESPOND TO POKE 0 TO 255 93 | 4099 REM PITCH= POKE 780,PITCH DURATION=POKE 781,DURATION 94 | 5000 POKE 782,173: POKE 783,48: POKE 784,192: POKE 785,136: POKE 786,208: POKE 787,5: POKE 788,206: POKE 789,13: POKE 790,3 95 | 5010 POKE 791,240: POKE 792,9: POKE 793,202: POKE 794,208: POKE 795,245: POKE 796,174: POKE 797,12: POKE 798,3: POKE 799,76 96 | 5020 POKE 800,14: POKE 801,3: POKE 802,96 97 | 5030 TEXT: HOME: S=782: P=780: D=781: H= 49152: C=49168 98 | 5040 VTAB 2: INVERSE : HTAB 13: PRINT "** SPACE MAZE **": VTAB 22: HTAB 4: PRINT "COPYRIGHT 1980..MICRO-SPARC, INC.": VTAB 24: HTAB 2: NORMAL: PRINT "*CODE HONORED* IN 2022 BY FVL FOR A2SE"; 99 | 5050 FOR I = 250 TO 0 STEP - 15: POKE P, I: POKE D,10: CALL S: NEXT 100 | 5060 VTAB 6: HTAB 1: PRINT "YOU WILL PILOT A SPACE CRUISER THRU THE": PRINT "STAR MAZE TO REACH THE PRIZED DILITHIUM": PRINT "CRYSTALS AT THE CENTER OF THE MAZE": INVERSE : PRINT 101 | 5070 PRINT "BE CAREFUL! IN THE HARD VERSION OF THE": PRINT "GAME YOUR SHIP IS PULLED BY HOSTILE ": PRINT "MAGNETIC FORCES.. SO TAKE CARE " 102 | 5080 PRINT "NOT TO CRASH!!!!!!!!!!!!!!!!!!!!!!!!!!!" 103 | 5090 NORMAL : PRINT: PRINT "DO YOU WANT YOUR SHIP SIGNAL": ?"SOUNDS (";:INVERSE:?"Y";:NORMAL:?"/N)? ";: GET A$: M=A$="N": PRINT: PRINT "EASY OR HARD GAME (";:INVERSE: ?"E";: NORMAL: ?"/H)? "; : GET A$: E= A$<>"H" 104 | 5100 PRINT: PRINT "DO YOU WANT STARWARS MUSIC EACH": PRINT "GAME (Y/";:INVERSE:?"N";:NORMAL:?")? ";: GET M$ 105 | 5110 PRINT: PRINT "JOYSTICK OR KEYBOARD (J/";:INVERSE:?"K";:NORMAL:?")? ";: GET J$: J=J$="J": IF NOT J THEN PRINT: PRINT "USE I/J/K/L TO MOVE. PRESS ANY KEY. ";: GET A$ 106 | 107 | 5999 REM *********** START NEW GAME ************ 108 | 6000 HOME: HGR : GOSUB 2000: IF PT>0 THEN VTAB 24: PRINT TAB(7)"PREVIOUS RECORD SCORE IS: ";PT; 109 | 6010 IF M$ = "Y" THEN GOSUB 500 110 | 6020 Z=1: X = 15:Y = 90:HV = 0:VV = 0:TM = 600:XO = X:YO = Y: POKE P,150 111 | 6030 VTAB 21: HTAB 14: PRINT "FUEL LEFT= "TM" " 112 | 6040 PRINT "HORIZONTAL= "HV;: HTAB 28: PRINT "VERTICAL= "VV 113 | 6100 GOTO 180 114 | 115 | 116 | 117 | 118 | 119 | 7000 REM ************************* 120 | 7010 REM ** SPACE MAZE ** 121 | 7020 REM ** MICRO-SPARC ** 122 | 7030 REM ** P.O. BOX 325 ** 123 | 7040 REM ** LINCOLN MASS 01773 ** 124 | 7050 REM ** COPYRIGHT C 1980 ** 125 | 7060 REM ** ** 126 | 7070 REM ** HONORING THE CODE ** 127 | 7080 REM ** IN 2022 BY FVL ** 128 | 7090 REM ** FOR A2SE FB GROUP ** 129 | 7100 REM ************************* 130 | 131 | 8000 REM VARIABLES 132 | 8010 REM ************** 133 | 8020 REM P = ADDRESS FOR PITCH 134 | 8030 REM D = ADDRESS FOR DURATION 135 | 8040 REM K = KEY READ 136 | 8050 REM C = CLEAR KEYBOARD STROBE ADDRESS (49168) 137 | 8060 REM H = READ LAST KEY PRESSED ADDRESS (49152) 138 | 8070 REM Z = ZONE THE SPACESHIP IS IN 139 | 8080 REM X, Y = SPACESHIP POSITION ON SCREEN 140 | 8090 REM XO, YO = SPACESHIP PREVIOUS POSITION 141 | 8100 REM HV, VV = HORIZONTAL AND VERTICAL SPEED 142 | 8110 REM TM = TIME LEFT (FUEL) 143 | 8120 REM PT = HI SCORE 144 | 8130 REM I = GENERIC FOR/NEXT LOOP COUNTER 145 | 8140 REM J = JOYSTICK CONTROL TRUE/FALSE 146 | 8150 REM E = EASY DIFFICULTY TRUE/FALSE 147 | 8160 REM A$ = GENERIC VARIABLE TO HOLD KEY PRESSED WHEN PROMPTING USER FOR OPTIONS 148 | 8170 REM M$ = MUSIC AT THE START OF EACH GAME (Y/N) -------------------------------------------------------------------------------- /honoring_the_code/001 - Space Maze/files/spacemaze_quickfix.bas: -------------------------------------------------------------------------------- 1 | 3 REM ************************* 2 | 4 REM ** SPACE MAZE ** 3 | 5 REM ** MICRO-SPARC ** 4 | 6 REM ** P.O. BOX 325 ** 5 | 7 REM ** LINCOLN MASS 01773 ** 6 | 8 REM ** COPYRIGHT C 1980 ** 7 | 9 REM ************************* 8 | 12 REM THE FOLLOWING SUBROUTINE GOES INTO MEMORY BLOCK HEX $30E (782), THE TONES RESPOND TO POKE 0 TO 255 9 | 13 REM PITCH= POKE780,P DURATION=POKE781,D 10 | 15 POKE 782,173: POKE 783,48: POKE 784,192: POKE 785,136: POKE 786,208: POKE 787,5: POKE 788,206: POKE 789,13: POKE 790,3 11 | 20 POKE 791,240: POKE 792,9: POKE 793,202: POKE 794,208: POKE 795,245: POKE 796,174: POKE 797,12: POKE 798,3: POKE 799,76 12 | 25 POKE 800,14: POKE 801,3: POKE 802,96 13 | 27 CALL - 936 14 | 30 VTAB 7: INVERSE : HTAB 10: PRINT "** SPACE MAZE **": VTAB 23: HTAB 4: PRINT "COPYRIGHT 1980..MICRO-SPARC, INC.": NORMAL 15 | 31 VTAB 9: PRINT "YOU WILL PILOT A SPACE CRUISER THRU THE": PRINT "STAR MAZE TO REACH THE PRIZED DILITHIUM": PRINT "CRYSTALS AT THE CENTER OF THE MAZE": INVERSE : PRINT 16 | 32 PRINT "BE CAREFUL! IN THE HARD VERSION OF THE": PRINT "GAME YOUR SHIP IS PULLED BY HOSTILE ": PRINT "MAGNETIC FORCES.. SO TAKE CARE " 17 | 33 PRINT "NOT TO CRASH!!!!!!!!!!!!!!!!!!!!!!!!!!!" 18 | 34 NORMAL : PRINT "DO YOU WANT YOUR SHIP SIGNAL SOUNDS? ": INPUT "TYPE Y OR N";NS$: INPUT "EASY OR HARD GAME? TYPE 'E' OR 'H'";HD$ 19 | 38 GOSUB 500: PRINT "DO YOU WANT STARWARS MUSIC EACH GAME?": INPUT "Y OR N ";M$ 20 | 40 HGR : GOSUB 2000 21 | 45 IF M$ < > "N" THEN GOSUB 500 22 | 50 GOTO 200 23 | 97 REM THE FOLLOWING SUBROUTINE TESTS WHETHER X AND Y ARE CONTAINED IN THE SERIES OF 11 RECTANGLES MAKING UP THE MAZE 24 | 98 REM IF X AND Y ARE SENSED, THEN Z IS SET THE NUMBER OF THE RECTANGLE. AT THE END OF THE TEST, Z IS TESTED. IF Z IS GREATER THAN 25 | 99 REM ZERO IT MEANS X AND Y ARE IN BOUNDS. IF Z=0 THEN NO X AND Y HAVE BEEN SENSED IN BOUNDS AND THE PROGRAM GOES TO THE CRASH SUBRTNE. 26 | 100 IF (X > = 10 AND X < = 80) AND (Y > = 80 AND Y < = 100) THEN Z = 1: GOTO 175 27 | 110 IF (X > = 60 AND X < = 100) AND (Y > = 100 AND Y < = 120) THEN Z = 2: GOTO 175 28 | 120 IF (X > = 80 AND X < = 100) AND (Y > = 120 AND Y < = 158) THEN Z = 3: GOTO 175 29 | 125 IF (X > = 100 AND X < = 140) AND (Y > = 140 AND Y < = 158) THEN Z = 4: GOTO 175 30 | 130 IF (X > = 120 AND X < = 180) AND (Y > = 120 AND Y < = 140) THEN Z = 5: GOTO 175 31 | 135 IF (X > = 160 AND X < = 220) AND (Y > = 140 AND Y < = 158) THEN Z = 6: GOTO 175 32 | 137 IF (X > = 200 AND X < = 220) AND (Y > = 110 AND Y < = 140) THEN Z = 6: GOTO 175 33 | 138 IF (X > = 220 AND X < = 265) AND (Y > = 110 AND Y < = 130) THEN Z = 6: GOTO 175 34 | 139 IF (X > = 245 AND X < = 265) AND (Y > = 40 AND Y < = 110) THEN Z = 6: GOTO 175 35 | 140 IF (X > = 215 AND X < = 245) AND (Y > = 40 AND Y < = 60) THEN Z = 6: GOTO 175 36 | 141 IF (X > = 215 AND X < = 235) AND (Y > = 60 AND Y < = 100) THEN Z = 6: GOTO 175 37 | 142 IF (X > = 180 AND X < = 235) AND (Y > = 80 AND Y < = 100) THEN Z = 6: GOTO 175 38 | 145 IF (X > = 180 AND X < = 200) AND (Y > = 60 AND Y < = 100) THEN Z = 8: GOTO 175 39 | 150 IF (X > = 140 AND X < = 180) AND (Y > = 60 AND Y < = 80) THEN Z = 9: GOTO 175 40 | 160 IF (X > = 100 AND X < = 160) AND (Y > = 40 AND Y < = 60) THEN Z = 10: GOTO 175 41 | 162 IF (X > = 100 AND X < = 120) AND (Y > = 60 AND Y < = 80) THEN Z = 11: GOTO 175 42 | 165 IF (X > = 106 AND X < = 114) AND (Y > = 66 AND Y < = 74) THEN 3000: REM BRANCH TO WIN 43 | 170 IF Z < = 0 THEN 4000: REM BRANCH TO CRASH...NO FLAGS WERE SET TO INDICATE PRESENCE IN THE MAZE...THEREFORE MUST BE OUTSIDE. 44 | 175 Z = 0: RETURN : REM RESET Z EACH TEST 45 | 200 X = 15:Y = 90:HV = 0:VV = 0:TM = 600:XO = 15:YO = 90: CALL - 936 46 | 210 IF PDL (0) > = 165 THEN HV = HV + 1 47 | 220 IF PDL (0) < = 90 THEN HV = HV - 1 48 | 230 IF PDL (1) > = 165 THEN VV = VV + 1 49 | 231 IF HD$ = "E" THEN 240 50 | 232 IF RND (1) < .05 THEN HV = HV + 1 51 | 233 IF RND (1) > .95 THEN VV = VV + 1 52 | 240 IF PDL (1) < = 90 THEN VV = VV - 1 53 | 242 X = XO + HV:Y = YO + VV 54 | 243 TM = TM - 1: VTAB 21: PRINT TAB( 10)"FUEL LEFT= ";TM: IF TM < 100 THEN VTAB 21: CALL - 868: PRINT TAB( 10)"FUEL LEFT= ";TM 55 | 245 VTAB 22: CALL - 868: PRINT "HORIZ =";HV;: PRINT TAB( 25)"VERTICAL =";VV 56 | 260 HCOLOR= 3: HPLOT X,Y: IF PT = 0 THEN 267 57 | 265 VTAB 23: PRINT TAB( 4)"PREVIOUS RECORD SCORE IS: ";PT 58 | 267 IF TM < = 0 THEN CALL - 936: FLASH : PRINT TAB( 10)"OUT OF FUEL";: PRINT TAB( 10)" ": GOSUB 4000 59 | 270 IF X = XO AND Y = YO THEN 300 60 | 280 HCOLOR= 0: HPLOT XO,YO: IF NS$ = "N" THEN 300 61 | 285 POKE 780,150: POKE 781,10: CALL 782 62 | 300 XO = X:YO = Y: GOSUB 100: GOTO 210 63 | 498 REM THE 500 SUBRTNE SETS UP THE MUSIC. M1=PITCH, M2=DURATION. 700 PLAYS IT. 64 | 500 M1 = 230:M2 = 75: GOSUB 700:M1 = 126:M2 = 250: GOSUB 700:M1 = 170:M2 = 250: GOSUB 700:M1 = 190:M2 = 75: GOSUB 700 65 | 510 M1 = 203:M2 = 75: GOSUB 700:M1 = 230:M2 = 75: GOSUB 700:M1 = 126:M2 = 250: GOSUB 700:M1 = 170:M2 = 250: GOSUB 700 66 | 515 M1 = 190:M2 = 100: GOSUB 700:M1 = 203:M2 = 100: GOSUB 700:M1 = 190:M2 = 100: GOSUB 700:M1 = 230:M2 = 250: GOSUB 700: RETURN 67 | 700 POKE 780,M1: POKE 781,M2: CALL 782: RETURN 68 | 2000 HCOLOR= 3: HPLOT 0,0 TO 279,0 TO 279,159 TO 0,159 TO 0,0 69 | 2001 HPLOT 70,10 TO 60,10 TO 60,20 TO 70,20 TO 70,30 TO 60,30: HPLOT 75,30 TO 75,10 TO 85,10 TO 85,20 TO 75,20: HPLOT 90,30 TO 90,10 TO 100,10 TO 100,30: HPLOT 90,20 TO 100,20 70 | 2002 HPLOT 115,10 TO 105,10 TO 105,30 TO 115,30: HPLOT 130,10 TO 120,10 TO 120,30 TO 130,30: HPLOT 120,20 TO 125,20: HPLOT 140,30 TO 140,10 TO 146,20 TO 152,10 TO 152,30 71 | 2003 HPLOT 158,30 TO 158,10 TO 168,10 TO 168,30: HPLOT 158,20 TO 168,20: HPLOT 173,10 TO 183,10 TO 173,30 TO 183,30: HPLOT 198,10 TO 188,10 TO 188,30 TO 198,30: HPLOT 188,20 TO 193,20 72 | 2005 HPLOT 10,80 TO 80,80 TO 80,100 TO 100,100 TO 100,140 TO 120,140 TO 120,120 TO 180,120 73 | 2010 HPLOT 180,120 TO 180,140 TO 200,140 TO 200,110 TO 245,110 TO 245,60 TO 235,60 TO 235,100 TO 180,100 TO 180,80 TO 140,80 TO 140,60 TO 120,60 TO 120,80 TO 100,80 74 | 2015 HPLOT 100,80 TO 100,40 TO 160,40 TO 160,60 TO 200,60 TO 200,80 TO 215,80 TO 215,40 TO 265,40 TO 265,130 TO 220,130 TO 220,158 TO 160,158 75 | 2020 HPLOT 220,158 TO 160,158 TO 160,140 TO 140,140 TO 140,158 TO 80,158 TO 80,120 TO 60,120 TO 60,100 TO 10,100 76 | 2030 HCOLOR= 3: HPLOT 106,66 TO 114,66 TO 114,74 TO 106,74 TO 106,66 77 | 2033 HPLOT 108,68 TO 112,72: HPLOT 108,72 TO 112,68: RETURN 78 | 3000 POP : TEXT : FOR NN = 250 TO 0 STEP - 15: PRINT "** WINNER **";: POKE 780,NN: POKE 781,10 79 | 3005 CALL 782: NEXT NN: FOR N = 1 TO 500: NEXT N: PRINT : PRINT 80 | 3010 IF TM > PT THEN HOME : VTAB 10: FLASH : PRINT "CONGRATULATIONS!": NORMAL : PRINT "YOU'VE BEATEN THE PREVIOUS HIGH SCORE ": PRINT "OF ";PT;" WITH YOUR SCORE OF ";TM 81 | 3011 GC = GC + 1: IF GC = 1 THEN PRINT : PRINT "IF YOU'VE BEEN PLAYING THE EASY GAME": PRINT "YOU'RE A WINNER! NOW WE'LL ADVANCE TO": PRINT "THE HARD GAME":HD$ = "H" 82 | 3012 IF TM > PT THEN PT = TM 83 | 3015 GOTO 4007 84 | 4000 POP : TEXT : FLASH : FOR NN = 1 TO 100: PRINT "** CRASH **";: NEXT NN: NORMAL 85 | 4005 FOR NN = 1 TO 250 STEP 50: POKE 780,NN: POKE 781,50: CALL 782: NEXT NN 86 | 4006 FOR NN = 1 TO 2000: NEXT NN: CALL - 936 87 | 4007 INPUT "ANOTHER MISSION? HIT RETURN";A$: HGR : GOTO 40 -------------------------------------------------------------------------------- /honoring_the_code/001 - Space Maze/files/spacemaze_quickfix2.bas: -------------------------------------------------------------------------------- 1 | 3 REM ************************* 2 | 4 REM ** SPACE MAZE ** 3 | 5 REM ** MICRO-SPARC ** 4 | 6 REM ** P.O. BOX 325 ** 5 | 7 REM ** LINCOLN MASS 01773 ** 6 | 8 REM ** COPYRIGHT C 1980 ** 7 | 9 REM ************************* 8 | 12 REM THE FOLLOWING SUBROUTINE GOES INTO MEMORY BLOCK HEX $30E (782), THE TONES RESPOND TO POKE 0 TO 255 9 | 13 REM PITCH= POKE780,P DURATION=POKE781,D 10 | 15 POKE 782,173: POKE 783,48: POKE 784,192: POKE 785,136: POKE 786,208: POKE 787,5: POKE 788,206: POKE 789,13: POKE 790,3 11 | 20 POKE 791,240: POKE 792,9: POKE 793,202: POKE 794,208: POKE 795,245: POKE 796,174: POKE 797,12: POKE 798,3: POKE 799,76 12 | 25 POKE 800,14: POKE 801,3: POKE 802,96 13 | 27 CALL - 936 14 | 30 VTAB 7: INVERSE : HTAB 10: PRINT "** SPACE MAZE **": VTAB 23: HTAB 4: PRINT "COPYRIGHT 1980..MICRO-SPARC, INC.": NORMAL 15 | 31 VTAB 9: PRINT "YOU WILL PILOT A SPACE CRUISER THRU THE": PRINT "STAR MAZE TO REACH THE PRIZED DILITHIUM": PRINT "CRYSTALS AT THE CENTER OF THE MAZE": INVERSE : PRINT 16 | 32 PRINT "BE CAREFUL! IN THE HARD VERSION OF THE": PRINT "GAME YOUR SHIP IS PULLED BY HOSTILE ": PRINT "MAGNETIC FORCES.. SO TAKE CARE " 17 | 33 PRINT "NOT TO CRASH!!!!!!!!!!!!!!!!!!!!!!!!!!!" 18 | 34 NORMAL : PRINT "DO YOU WANT YOUR SHIP SIGNAL SOUNDS? ": INPUT "TYPE Y OR N";NS$: INPUT "EASY OR HARD GAME? TYPE 'E' OR 'H'";HD$ 19 | 38 GOSUB 500: PRINT "DO YOU WANT STARWARS MUSIC EACH GAME?": INPUT "Y OR N ";M$ 20 | 40 HGR : GOSUB 2000 21 | 45 IF M$ < > "N" THEN GOSUB 500 22 | 50 GOTO 200 23 | 97 REM THE FOLLOWING SUBROUTINE TESTS WHETHER X AND Y ARE CONTAINED IN THE SERIES OF 11 RECTANGLES MAKING UP THE MAZE 24 | 98 REM IF X AND Y ARE SENSED, THEN Z IS SET THE NUMBER OF THE RECTANGLE. AT THE END OF THE TEST, Z IS TESTED. IF Z IS GREATER THAN 25 | 99 REM ZERO IT MEANS X AND Y ARE IN BOUNDS. IF Z=0 THEN NO X AND Y HAVE BEEN SENSED IN BOUNDS AND THE PROGRAM GOES TO THE CRASH SUBRTNE. 26 | 100 IF (X > = 10 AND X < = 80) AND (Y > = 80 AND Y < = 100) THEN Z = 1: RETURN 27 | 110 IF (X > = 60 AND X < = 100) AND (Y > = 100 AND Y < = 120) THEN Z = 2: RETURN 28 | 120 IF (X > = 80 AND X < = 100) AND (Y > = 120 AND Y < = 158) THEN Z = 3: RETURN 29 | 125 IF (X > = 100 AND X < = 140) AND (Y > = 140 AND Y < = 158) THEN Z = 4: RETURN 30 | 130 IF (X > = 120 AND X < = 180) AND (Y > = 120 AND Y < = 140) THEN Z = 5: RETURN 31 | 135 IF (X > = 160 AND X < = 220) AND (Y > = 140 AND Y < = 158) THEN Z = 6: RETURN 32 | 137 IF (X > = 200 AND X < = 220) AND (Y > = 110 AND Y < = 140) THEN Z = 6: RETURN 33 | 138 IF (X > = 220 AND X < = 265) AND (Y > = 110 AND Y < = 130) THEN Z = 6: RETURN 34 | 139 IF (X > = 245 AND X < = 265) AND (Y > = 40 AND Y < = 110) THEN Z = 6: RETURN 35 | 140 IF (X > = 215 AND X < = 245) AND (Y > = 40 AND Y < = 60) THEN Z = 6: RETURN 36 | 141 IF (X > = 215 AND X < = 235) AND (Y > = 60 AND Y < = 100) THEN Z = 6: RETURN 37 | 142 IF (X > = 180 AND X < = 235) AND (Y > = 80 AND Y < = 100) THEN Z = 6: RETURN 38 | 145 IF (X > = 180 AND X < = 200) AND (Y > = 60 AND Y < = 100) THEN Z = 8: RETURN 39 | 150 IF (X > = 140 AND X < = 180) AND (Y > = 60 AND Y < = 80) THEN Z = 9: RETURN 40 | 160 IF (X > = 100 AND X < = 160) AND (Y > = 40 AND Y < = 60) THEN Z = 10: RETURN 41 | 162 IF (X > = 100 AND X < = 120) AND (Y > = 60 AND Y < = 80) THEN Z = 11: RETURN 42 | 165 IF (X > = 106 AND X < = 114) AND (Y > = 66 AND Y < = 74) THEN 3000: REM BRANCH TO WIN 43 | 170 GOTO 4000: REM BRANCH TO CRASH...NO FLAGS WERE SET TO INDICATE PRESENCE IN THE MAZE...THEREFORE MUST BE OUTSIDE. 44 | 45 | 200 X = 15:Y = 90:HV = 0:VV = 0:TM = 600:XO = 15:YO = 90: CALL - 936 46 | 210 IF PDL (0) > = 165 THEN HV = HV + 1 47 | 220 IF PDL (0) < = 90 THEN HV = HV - 1 48 | 230 IF PDL (1) > = 165 THEN VV = VV + 1 49 | 231 IF HD$ = "E" THEN 240 50 | 232 IF RND (1) < .05 THEN HV = HV + 1 51 | 233 IF RND (1) > .95 THEN VV = VV + 1 52 | 240 IF PDL (1) < = 90 THEN VV = VV - 1 53 | 242 X = XO + HV:Y = YO + VV 54 | 243 TM = TM - 1: VTAB 21: PRINT TAB( 10)"FUEL LEFT= ";TM: IF TM < 100 THEN VTAB 21: CALL - 868: PRINT TAB( 10)"FUEL LEFT= ";TM 55 | 245 VTAB 22: CALL - 868: PRINT "HORIZ =";HV;: PRINT TAB( 25)"VERTICAL =";VV 56 | 260 HCOLOR= 3: HPLOT X,Y: IF PT = 0 THEN 267 57 | 265 VTAB 23: PRINT TAB( 4)"PREVIOUS RECORD SCORE IS: ";PT 58 | 267 IF TM < = 0 THEN CALL - 936: FLASH : PRINT TAB( 10)"OUT OF FUEL";: PRINT TAB( 10)" ": GOSUB 4000 59 | 270 IF X = XO AND Y = YO THEN 300 60 | 280 HCOLOR= 0: HPLOT XO,YO: IF NS$ = "N" THEN 300 61 | 285 POKE 780,150: POKE 781,10: CALL 782 62 | 300 XO = X:YO = Y: ON Z GOSUB 100, 100, 110, 120, 125,130,135,137,138,139,140,141,142,145,150,160: GOTO 210 63 | 498 REM THE 500 SUBRTNE SETS UP THE MUSIC. M1=PITCH, M2=DURATION. 700 PLAYS IT. 64 | 500 M1 = 230:M2 = 75: GOSUB 700:M1 = 126:M2 = 250: GOSUB 700:M1 = 170:M2 = 250: GOSUB 700:M1 = 190:M2 = 75: GOSUB 700 65 | 510 M1 = 203:M2 = 75: GOSUB 700:M1 = 230:M2 = 75: GOSUB 700:M1 = 126:M2 = 250: GOSUB 700:M1 = 170:M2 = 250: GOSUB 700 66 | 515 M1 = 190:M2 = 100: GOSUB 700:M1 = 203:M2 = 100: GOSUB 700:M1 = 190:M2 = 100: GOSUB 700:M1 = 230:M2 = 250: GOSUB 700: RETURN 67 | 700 POKE 780,M1: POKE 781,M2: CALL 782: RETURN 68 | 2000 HCOLOR= 3: HPLOT 0,0 TO 279,0 TO 279,159 TO 0,159 TO 0,0 69 | 2001 HPLOT 70,10 TO 60,10 TO 60,20 TO 70,20 TO 70,30 TO 60,30: HPLOT 75,30 TO 75,10 TO 85,10 TO 85,20 TO 75,20: HPLOT 90,30 TO 90,10 TO 100,10 TO 100,30: HPLOT 90,20 TO 100,20 70 | 2002 HPLOT 115,10 TO 105,10 TO 105,30 TO 115,30: HPLOT 130,10 TO 120,10 TO 120,30 TO 130,30: HPLOT 120,20 TO 125,20: HPLOT 140,30 TO 140,10 TO 146,20 TO 152,10 TO 152,30 71 | 2003 HPLOT 158,30 TO 158,10 TO 168,10 TO 168,30: HPLOT 158,20 TO 168,20: HPLOT 173,10 TO 183,10 TO 173,30 TO 183,30: HPLOT 198,10 TO 188,10 TO 188,30 TO 198,30: HPLOT 188,20 TO 193,20 72 | 2005 HPLOT 10,80 TO 80,80 TO 80,100 TO 100,100 TO 100,140 TO 120,140 TO 120,120 TO 180,120 73 | 2010 HPLOT 180,120 TO 180,140 TO 200,140 TO 200,110 TO 245,110 TO 245,60 TO 235,60 TO 235,100 TO 180,100 TO 180,80 TO 140,80 TO 140,60 TO 120,60 TO 120,80 TO 100,80 74 | 2015 HPLOT 100,80 TO 100,40 TO 160,40 TO 160,60 TO 200,60 TO 200,80 TO 215,80 TO 215,40 TO 265,40 TO 265,130 TO 220,130 TO 220,158 TO 160,158 75 | 2020 HPLOT 220,158 TO 160,158 TO 160,140 TO 140,140 TO 140,158 TO 80,158 TO 80,120 TO 60,120 TO 60,100 TO 10,100 76 | 2030 HCOLOR= 3: HPLOT 106,66 TO 114,66 TO 114,74 TO 106,74 TO 106,66 77 | 2033 HPLOT 108,68 TO 112,72: HPLOT 108,72 TO 112,68: RETURN 78 | 3000 POP : TEXT : FOR NN = 250 TO 0 STEP - 15: PRINT "** WINNER **";: POKE 780,NN: POKE 781,10 79 | 3005 CALL 782: NEXT NN: FOR N = 1 TO 500: NEXT N: PRINT : PRINT 80 | 3010 IF TM > PT THEN HOME : VTAB 10: FLASH : PRINT "CONGRATULATIONS!": NORMAL : PRINT "YOU'VE BEATEN THE PREVIOUS HIGH SCORE ": PRINT "OF ";PT;" WITH YOUR SCORE OF ";TM 81 | 3011 GC = GC + 1: IF GC = 1 THEN PRINT : PRINT "IF YOU'VE BEEN PLAYING THE EASY GAME": PRINT "YOU'RE A WINNER! NOW WE'LL ADVANCE TO": PRINT "THE HARD GAME":HD$ = "H" 82 | 3012 IF TM > PT THEN PT = TM 83 | 3015 GOTO 4007 84 | 4000 POP : TEXT : FLASH : FOR NN = 1 TO 100: PRINT "** CRASH **";: NEXT NN: NORMAL 85 | 4005 FOR NN = 1 TO 250 STEP 50: POKE 780,NN: POKE 781,50: CALL 782: NEXT NN 86 | 4006 FOR NN = 1 TO 2000: NEXT NN: CALL - 936 87 | 4007 INPUT "ANOTHER MISSION? HIT RETURN";A$: HGR : GOTO 40 -------------------------------------------------------------------------------- /honoring_the_code/002 - lores tetris/htc2_tetris.bas: -------------------------------------------------------------------------------- 1 | REM KEYS 2 | 3 | 4 | REM J/K/L: left/down/right 5 | REM F/G or U/O: rotate left/right 6 | REM I: quick drop 7 | REM P: PAUSE 8 | REM Q: quit 9 | 10 | REM GAME LOOP FROM 1 TO 740 11 | 12 | 0 GOTO 1000 13 | 1 IF W>LV THEN W=Z: GOTO 4 14 | 2 W=W+U: ON PEEK(L)-V GOTO 11,9,1,380,7,3,8,1,1,9,740,720,1,1,1,11: GOTO 1 15 | 3 POKE KC,Z 16 | 17 | REM DOWN 18 | 4 Y=Y+U: J=J+O: R=U 19 | 20 | REM erase_check_n_restore 21 | 5 INVERSE: HTAB M:VTAB N:PRINT Z$;: POKE FA,FI: IF SCRN(I+A,J+B)+ SCRN(I+C,J+D)+SCRN(I+E,J+F)+ SCRN(I+G,J+H) THEN Y=N: X=M: HTAB X: VTAB Y: PRINT A$; : ON R GOTO 400: I=X-U: GOTO 1 22 | 23 | REM draw new position 24 | 6 M=X:N=Y: R=Z: HTAB X: VTAB Y: PRINT A$;: GOTO 1 25 | 26 | REM LEFT 27 | 7 POKE KC,Z: X=I: I=X-U:GOTO 5 28 | 29 | REM RIGHT 30 | 8 POKE KC,Z: I=X: X=X+U: GOTO 5 31 | 32 | 33 | REM ROT_RIGHT 34 | 9 POKE KC,Z: SS=S: S=S-Q: TT=T: T=T-U: IF TJ THEN LJ=J 58 | 420 R=Z: RN=-U: K= J+Q+Q: IF K>NY THEN K=NY 59 | 430 FOR JJ = J TO K STEP O: G=U: FOR I = MX TO NX: IF NOT SCRN(I,JJ) THEN I=NX+U: G=Z 60 | 440 NEXT 61 | 450 IF G THEN RN=RN+U: R(RN) = JJ 62 | 460 NEXT 63 | 470 IF RN=NL THEN NL=NL+TL: LV=LV-U: IF LV>> GAME OVER <<<";:VTAB 23:HTAB N0:CALL A 78 | 705 VTAB T4:HTAB U:NORMAL:PRINT "PLAY AGAIN (Y/N) ? ";:CALL A:GET K$:IF K$<>"N" THEN 2020 79 | 710 TEXT: HOME: NORMAL: PRINT "THANKS FOR PLAYING ! ": IF DS THEN CALL 43089: REM RECONNECT DOS 80 | 715 END 81 | 720 POKE KC,Z: VTAB T4: HTAB U: INVERSE: ? "ARE YOU SURE (Y/N)?"; : GET K$ : IF K$ = "Y" THEN 710 82 | 730 HTAB U: CALL - 868: GOSUB 2600: POKE FA,FI: GOTO 1 83 | 84 | REM PAUSE 85 | 740 POKE KC,Z: VTAB T4: HTAB O: FLASH: PRINT ">>> PAUSED <<<";: GET K$: POKE KC,Z: VTAB T4: HTAB 1: CALL - 868: GOSUB 2600: POKE FA,FI: GOTO 1 86 | 87 | 88 | REM VARIABLES 89 | REM A$: STRING REPRESENTING THE SHAPE TO PRINT 90 | REM A/B/C/D/E/F/G/H: X/Y OFFSETS TO CHECK 91 | REM AA/BB/CC/DD/EE/FF/GG/HH PREVIOUS VALUES OF A/B/C/D/E/F/G/H 92 | REM AA$: PREVIOUS VALUE OF A$ 93 | REM E8: CONSTANT 8 94 | REM FA: CONSTANT 50 95 | REM FI: INVERSE FLAG TO POKE 96 | REM I/J: PIECE POSITION ON LORES SCREEN 97 | REM K: MISC. COUNTER 98 | REM KC: CLEAR KBD STROBE CONSTANT (49168) 99 | REM L: CONSTANT 49152 (LAST KEY HIT ADDR.) 100 | REM LV: LEVEL 101 | REM M/N: PREVIOUS X/Y POSITION 102 | REM MX/NX: PIT LEFT AND PIT RIGHT POSITIONS (15/24) 103 | REM MY/NY: PIT TOP/BOTTOM POS 104 | REM ML: MAX LEVEL 105 | REM NS: SHAPE # 106 | REM N0: CONSTANT 10 107 | REM O: CONSTANT 2 108 | REM P: CONSTANT 3 109 | REM Q: CONSTANT 4 110 | REM R: PIECE GOING DOWN FLAG (0/1) 111 | REM S: SHAPE OFFSET NUMBER 0..27 112 | REM SS: PREVIOUS SHAPE OFFSET 0..27 113 | REM SP: CONSTANT 49200 (SPEAKER) 114 | REM T: 0-3 COUNTER 115 | REM TT: PREVIOUS VALUE OF T 116 | REM T0, T1, T2, T4: CONSTANTS 20, 21, 22, 24 117 | REM U: CONSTANT 1 118 | REM V: CONSTANT 197 = MIN. KEY # 119 | REM X/Y: PIECE POSITION ON TEXT SCREEN 120 | REM Z: CONSTANT 0 121 | REM Z$: STRING TO ERASE SHAPE 122 | REM ZZ$: PREVIOUS VALUE OF Z$ 123 | 124 | 125 | REM DECLARE MOST USED VARIABLES FIRST ! 126 | 127 | 1000 U=1:X=0:Y=0:Z=0:S=0:O=2:M=0:N=0:K=0:R=0:SS=0:A$="":Z$="":A=0:B=0:C=0:D=0:E=0:F=0:G=0:H=0:T=0:TT=0:P=3:Q=4:L=49152:V=197:CE=62248:FA=50:FI=0:KC=49168:S6=16:MX=15:NX=24:MY=4:NY=36:SP=49200:N5=5:N0=10:T1=21:T2=22:T4=24:T0=20:E8=8 128 | 129 | 1010 GOSUB 3000: TEXT: HOME: NORMAL: HTAB 5: PRINT "A TETRIS CLONE IN PURE APPLESOFT": HTAB 13: PRINT "BY FVL (C) 2022" 130 | 1020 VTAB 4: HTAB 17: PRINT "KEYS:" 131 | 1030 VTAB 6: PRINT " J/K/L : MOVE LEFT/DOWN/RIGHT" 132 | 1040 PRINT " F/G OR U/O : ROTATE LEFT/RIGHT" 133 | 1050 PRINT " I : QUICK DROP" 134 | 1060 PRINT " P : PAUSE" 135 | 1070 PRINT " Q : QUIT" 136 | 1080 VTAB 12: HTAB 15: PRINT "POINTS:" 137 | 1090 VTAB 14: PRINT " 1 LINE : 40 POINTS" 138 | 1100 PRINT " 2 LINES : 100 POINTS" 139 | 1110 PRINT " 3 LINES : 300 POINTS" 140 | 1120 PRINT " 4 LINES : 1200 POINTS" 141 | 1130 PRINT " QUICK DROP : 0-30 POINTS": HTAB 15: PRINT "(HEIGHT DEPENDENT)" 142 | 1140 VTAB 21: HTAB 9: PRINT "NEW LEVEL EVERY 5 LINES": VTAB 22: HTAB 5: PRINT "SPEED INCREASES WITH EACH LEVEL" 143 | 144 | 145 | 1380 VTAB T4: HTAB 8: PRINT "PLEASE WAIT >>> .......";: TH= PEEK(36): TA = PEEK(40)+PEEK(41)*256+TH-7 146 | 147 | 148 | 149 | 1390 DIM X(111), Y(111), A$(27), Z$(27), C(6): FOR S=0 TO 6: POKE TA+S, PEEK(TA+S)-128 : FOR J=0 TO 3: FOR I=0 TO 3: READ X(S*S6 + J*Q + I), Y(S*S6 + J*Q + I): NEXT: NEXT: READ C(S): NEXT 150 | 1400 D$ = CHR$(10): L$=CHR$(8): Q$=CHR$(34): F$=CHR$(102) 151 | 152 | 153 | REM SQUARE SHAPE 154 | REM .xX. 155 | REM .xx. 156 | 157 | 1410 A$(0) = L$+"QQ"+D$+L$+L$+"QQ": A$(1)=A$(0): A$(2)=A$(0): A$(3)=A$(0) 158 | 1420 Z$(0) = L$+"@@"+D$+L$+L$+"@@": Z$(1)=Z$(0): Z$(2)=Z$(0): Z$(3)=Z$(0): POKE TA, 160 159 | 160 | REM BAR 161 | REM ..O. 162 | REM xxxx 163 | 164 | 1430 A$(4) = D$ + L$ + L$ + Q$+ Q$+ Q$+ Q$: Z$(4) = + L$ + L$ + D$ + "@@@@" 165 | 166 | REM .xO. 167 | REM .x.. 168 | REM .x.. 169 | REM .x.. 170 | 171 | 1440 A$(5) = L$ + Q$ + D$+L$+Q$+ D$+L$+Q$+ D$+L$+Q$ 172 | 1450 Z$(5) = L$ + "@" + D$+L$+"@"+ D$+L$+"@"+ D$+L$+"@" 173 | 1460 A$(6) = A$(4): A$(7)=A$(5): Z$(6) = Z$(4): Z$(7)=Z$(5): POKE TA+1, 160 174 | 175 | REM T-SHAPE 176 | REM .xO. 177 | REM xxx. 178 | 179 | 1470 A$(8) = L$ + "L" + D$ + L$ + L$ + "LLL": Z$(8) = L$ + "@" + D$+L$ + L$ +"@@@" 180 | 181 | REM .xO. 182 | REM xx.. 183 | REM .x.. 184 | 185 | 1480 A$(9) = L$ + "L" + D$+L$+L$+ "LL" + D$+L$+"L": Z$(9) = L$ + "@" + D$+L$+L$+ "@@" + D$+L$+"@" 186 | 187 | REM ..O. 188 | REM xxx. 189 | REM .x.. 190 | 191 | 1490 A$(10) = D$ + L$ + L$ + "LLL" + D$+L$+L$+"L": Z$(10) = D$ + L$ + L$ + "@@@" + D$+L$+L$+"@" 192 | 193 | REM .xO. 194 | REM .xx. 195 | REM .x.. 196 | 197 | 1500 A$(11) = L$ + "L" + D$ + L$+"LL" + D$+L$+L$+ "L": Z$(11) = L$ + "@" + D$ + L$+"@@" + D$+L$+L$+ "@": POKE TA+2, 160 198 | 199 | REM L-SHAPE 200 | REM ..O. 201 | REM xxx. 202 | REM x... 203 | 204 | 1510 A$(12) = D$ + L$ + L$ + "]]]" + D$ + L$+ L$+ L$+"]": Z$(12) = D$ + L$ + L$ + "@@@" + D$ + L$+ L$+ L$+"@" 205 | 206 | REM .xO. 207 | REM .x.. 208 | REM .xx. 209 | 210 | 1520 A$(13) = L$ + "]"+ D$ + L$ + "]"+ D$ + L$ + "]]": Z$(13) = L$ + "@"+ D$ + L$ + "@"+ D$ + L$ + "@@" 211 | 212 | REM ..X. 213 | REM xxx. 214 | 215 | 1530 A$(14) = "]" + D$ + L$+ L$+ L$+"]]]": Z$(14) = "@" + D$ + L$+ L$+ L$+"@@@" 216 | 217 | REM xxO. 218 | REM .x.. 219 | REM .x.. 220 | 221 | 1540 A$(15) = L$+L$+"]]" + D$ + L$ + "]" + D$ + L$ + "]": Z$(15) = L$+L$+"@@" + D$ + L$ + "@" + D$ + L$ + "@": POKE TA+3, 160 222 | 223 | REM L-SHAPE INVERTED 224 | REM ..O. 225 | REM xxx. 226 | REM ..x. 227 | 228 | 1550 A$(16) = D$ + L$ + L$ + F$ + F$ + F$ + D$ + L$+ F$: Z$(16) = D$ + L$ + L$ + "@@@" + D$ + L$+ "@" 229 | 230 | REM .xX. 231 | REM .x.. 232 | REM .x.. 233 | 234 | 1560 A$(17) = L$ + F$ + F$ + D$ + L$+L$ + F$+ D$ + L$ + F$: Z$(17) = L$ + "@@" + D$ + L$+L$ + "@"+ D$ + L$ + "@" 235 | 236 | REM x.O. 237 | REM xxx. 238 | 239 | 1570 A$(18) = L$ + L$ + F$ + D$ + L$ + F$ + F$ + F$: Z$(18) = L$ + L$ + "@" + D$ + L$ + "@@@" 240 | 241 | REM .xO. 242 | REM .x.. 243 | REM xx.. 244 | 245 | 1580 A$(19) = L$ + F$ + D$+L$ + F$ + D$+L$ + L$ + F$ + F$: Z$(19) = L$ + "@" + D$+L$ + "@" + D$+L$+L$ + "@@": POKE TA+4, 160 246 | 247 | REM Z-SHAPE 248 | 249 | REM xxO. 250 | REM .xx. 251 | 252 | 1590 A$(20) = L$ + L$ + "33" + D$ + L$ + "33": Z$(20) = L$ + L$ + "@@" + D$ + L$ + "@@" 253 | 254 | REM .xO. 255 | REM xx.. 256 | REM x... 257 | 258 | 1600 A$(21) = L$ + "3" + D$ + L$ + L$ + "33" + D$ + L$ + L$ + "3": Z$(21) = L$ + "@" + D$ + L$ + L$ + "@@" + D$ + L$ + L$ + "@" 259 | 1610 A$(22) = A$(20): A$(23) = A$(21): Z$(22) = Z$(20): Z$(23) = Z$(21): POKE TA+5, 160 260 | 261 | REM S-SHAPE 262 | REM .xX. 263 | REM xx.. 264 | 265 | 1620 A$(24) = L$ + ";;" + D$ + L$ + L$ + L$ + ";;": Z$(24) = L$ + "@@" + D$ + L$ + L$ + L$ + "@@" 266 | 267 | REM x.O. 268 | REM xx.. 269 | REM .x.. 270 | 271 | 1630 A$(25) = L$ + L$ + ";" + D$ + L$ + ";;" + D$ + L$ + ";": Z$(25) = L$ + L$ + "@" + D$ + L$ + "@@" + D$ + L$ + "@" 272 | 1640 A$(26) = A$(24): A$(27) = A$(25): Z$(26) = Z$(24): Z$(27) = Z$(25): POKE TA+6, 160 273 | 274 | 1700 VTAB 24: CALL - 868: HTAB 1: INVERSE: PRINT " <<< ANY KEY TO CONTINUE >>> "; : POKE TA-TH+39+7,32: WAIT 49152, 128 275 | 276 | 277 | 2000 P(0)=40: P(1)=100: P(2)=300:P(3)=1200: DS= PEEK(40672) = 162: IF DS THEN CALL 40672: REM DISCONNECT DOS FOR FASTER PRINT 278 | 279 | 2010 Q$ = CHR$(119) + CHR$(119) + CHR$(119) + CHR$(119) + CHR$(119): Q$=Q$+Q$+Q$+Q$+Q$+Q$+Q$+Q$: SL=1: OD=0: OL=5 280 | 2020 HOME: GR: FLASH: FOR I=1 TO 20: VTAB I: ? Q$;: NEXT: INVERSE: FOR I = 1 TO 19: HTAB 16: VTAB I: ? "@@@@@@@@@@";: NEXT: FOR I= 9 TO 13: VTAB I: HTAB 5: PRINT "@@@@@@";: NEXT 281 | 282 | 2080 NORMAL 283 | 2090 POKE KC, Z: VTAB 21: HTAB 7: PRINT "STARTING LEVEL: ";: INVERSE: PRINT SL;: NORMAL : PRINT SPC(SL<10)" (+/-) TO CHANGE"; 284 | 2100 VTAB 22: HTAB 4: PRINT "OBSTACLES DENSITY: ";: INVERSE: PRINT OD;: NORMAL: PRINT " (O/P) TO CHANGE"; 285 | 2110 VTAB 23: HTAB 1: PRINT "OBSTACLES UP TO LINE: ";: INVERSE: PRINT OL;: NORMAL: PRINT SPC(OL<10)" (L/M) TO CHANGE"; 286 | 287 | 2120 INVERSE: VTAB 24: HTAB 3: PRINT "<<< PRESS ANY OTHER KEY TO START >>>"; : GET K$: NORMAL 288 | 2130 IF K$<>"+" AND K$<>"-" THEN 2170 289 | REM CHANGE LEVEL 290 | 2140 SL=SL+44-ASC(K$): IF SL<1 THEN SL=40: GOTO 2090 291 | 2150 IF SL>40 THEN SL=1 292 | 2160 GOTO 2090 293 | 2170 IF K$="L" OR K$="M" THEN 2250: REM CHANGE OBSTACLES LINE 294 | 2180 IF K$<>"O" AND K$<>"P" THEN 2300: REM START GAME 295 | REM CHANGE OBSTACLES DENSITY 296 | 2190 OD=OD-2*ASC(K$)+159: IF OD>7 THEN OD=0 297 | 2200 IF OD<0 THEN OD=7 298 | 2210 GOTO 2090 299 | REM CHANGE OBSTACLES FINAL LINE 300 | 2250 OL=OL-2*ASC(K$)+153: IF OL>15 THEN OL=5 301 | 2260 IF OL<5 THEN OL=15 302 | 2270 GOTO 2090 303 | 304 | 2300 HOME: SC=0: LJ=40: ML=41: LV=ML-SL: PS=INT(RND(U)*7): LI=0: TL=5: NL=TL: GOSUB 2600 305 | 2310 IF NOT OD THEN 2400 306 | 2320 FOR J=NY TO NY+2-OL*2 STEP -2: K=0: FOR I=0 TO 9 307 | 2330 IF K<9 AND RND(1)*10<=OD THEN CC=INT(RND(1)*16): CC=CC*(CC<>7): COLOR=CC: PLOT I+MX,J: PLOT I+MX,J+1: K=K+(CC>0) 308 | 2340 NEXT: NEXT 309 | 310 | 2400 GOTO 600 311 | 312 | 2600 NORMAL: VTAB 21: HTAB 19: PRINT "J/K/L: LEFT/DOWN/RIGHT";: HTAB 15: PRINT "F/G - U/O: ROTATE L/R": HTAB 23: PRINT "I: QUICK DROP": HTAB 21: PRINT "P/Q: PAUSE/QUIT";: HTAB 2: INVERSE: PRINT "=== TETRIS ===";: RETURN 313 | 314 | REM =========== ONERR GOTO 315 | REM ALSO PREPARE AMPERSAND VECTOR TO JMP TO $F328 (RESET STACK POSITION WITHOUT RESUME) 316 | REM ANYTHING AFTER ON ERR ON THE SAME LINE IS IGNORED BY APPLESOFT 317 | 318 | 3000 ON ERR GOTO 3100 319 | 3010 POKE 1013, 76: POKE 1014,40: POKE 1015,243: RETURN 320 | 321 | REM === 3072 is next multiple of 256, so going to line 3100 in case of error will only skip line 3010 ! 322 | REM USE AMPERSAND TO DO A CALL 62248 (-3288) TO SPARE A FEW CYCLES. 323 | 3100 &: GOTO 1 324 | 325 | 326 | 327 | 328 | REM ======================================= CHECKING POINTS 329 | 330 | REM BECAUSE PRINT CAN ONLY MOVE THE CURSOR LEFT AND DOWN, WE HAVE TO ASSUME WE START IN POS (3,0) 331 | REM X MARKS A PLOTTED STARTING POINT, O MARKS AN UNPLOTTED STARTING POINT 332 | 333 | REM 0123 334 | REM 0..X. 335 | REM 1.... 336 | REM 2.... 337 | REM 3.... 338 | 339 | 340 | REM SQUARE SHAPE 341 | REM .xX. 342 | REM .xx. 343 | 344 | 10010 DATA 0, 0,-1, 0, 0, 2,-1, 2 345 | 10020 DATA 0, 0,-1, 0, 0, 2,-1, 2 346 | 10030 DATA 0, 0,-1, 0, 0, 2,-1, 2 347 | 10040 DATA 0, 0,-1, 0, 0, 2,-1, 2,63 : REM [] COLOR=MAGENTA (Q INVERSED) 348 | 349 | REM BAR 350 | REM ..O. 351 | REM xxxx 352 | 353 | 10050 DATA 0, 2,-1, 2,-2, 2, 1, 2 354 | 355 | REM .xO. 356 | REM .x.. 357 | REM .x.. 358 | REM .x.. 359 | 360 | 10060 DATA -1, 0,-1, 2,-1, 4,-1, 6 361 | 10070 DATA 0, 2,-1, 2,-2, 2, 1, 2 362 | 10080 DATA -1, 0,-1, 2,-1, 4,-1, 6 363 | 10090 DATA 63: REM COLOR=DARK BLUE (" INVERSED) 364 | 365 | REM T-SHAPE 366 | REM .xO. 367 | REM xxx. 368 | 369 | 10100 DATA -1, 0,-2, 2,-1, 2, 0, 2 370 | 371 | REM .xO. 372 | REM xx.. 373 | REM .x.. 374 | 375 | 10110 DATA -1, 0,-2, 2,-1, 2, -1, 4 376 | 377 | REM ..O. 378 | REM xxx. 379 | REM .x.. 380 | 381 | 10120 DATA -2, 2,-1, 2, 0, 2, -1, 4 382 | 383 | REM .xO. 384 | REM .xx. 385 | REM .x.. 386 | 387 | 10130 DATA -1, 0,-1, 2, 0, 2,-1, 4 388 | 389 | 10140 DATA 255: REM COLOR=GREEN (L NORMAL) 390 | 391 | REM L-SHAPE 392 | REM ..O. 393 | REM xxx. 394 | REM x... 395 | 396 | 10150 DATA -2,2,-1,2,0,2,-2,4 397 | 398 | REM .xO. 399 | REM .x.. 400 | REM .xx. 401 | 402 | 10160 DATA -1,0,-1,2,-1,4,0,4 403 | 404 | REM ..X. 405 | REM xxx. 406 | 407 | 10170 DATA 0,0,-2,2,-1,2,0,2 408 | 409 | REM xxO. 410 | REM .x.. 411 | REM .x.. 412 | 413 | 10180 DATA -2,0,-1,0,-1,2,-1,4 414 | 415 | 10190 DATA 255: REM COLOR=YELLOW (] NORMAL) 416 | 417 | REM L-SHAPE INVERTED 418 | REM ..O. 419 | REM xxx. 420 | REM ..x. 421 | 422 | 10200 DATA -2,2,-1,2,0,2,0,4 423 | 424 | REM .xX. 425 | REM .x.. 426 | REM .x.. 427 | 428 | 10210 DATA 0,0,-1,0,-1,2,-1,4 429 | 430 | REM x.O. 431 | REM xxx. 432 | 433 | 10220 DATA -2,0,-2,2,-1,2,0,2 434 | 435 | REM .xO. 436 | REM .x.. 437 | REM xx.. 438 | 439 | 10230 DATA -1,0,-1,2,-1,4,-2,4 440 | 441 | 10240 DATA 127: REM COLOR=MEDIUM BLUE (CHAR 102 AND FLAG 127) 442 | 443 | REM Z-SHAPE 444 | 445 | REM xxO. 446 | REM .xx. 447 | 448 | 10250 DATA -2,0,-1,0,-1,2,0,2 449 | 450 | REM .xO. 451 | REM xx.. 452 | REM x... 453 | 454 | 10260 DATA -1,0,-1,2,-2,2,-2,4 455 | 10270 DATA -2,0,-1,0,-1,2,0,2 456 | 10280 DATA -1,0,-1,2,-2,2,-2,4 457 | 458 | 10290 DATA 63: REM COLOR=PURPLE (3 INVERSE) 459 | 460 | REM S-SHAPE 461 | REM .xX. 462 | REM xx.. 463 | 464 | 10300 DATA 0,0,-1,0,-1,2,-2,2 465 | 466 | REM x.O. 467 | REM xx.. 468 | REM .x.. 469 | 470 | 10310 DATA -2,0,-2,2,-1,2,-1,4 471 | 10320 DATA 0,0,-1,0,-1,2,-2,2 472 | 10330 DATA -2,0,-2,2,-1,2,-1,4 473 | 474 | 10340 DATA 255: REM COLOR=PINK (; NORMAL) 475 | -------------------------------------------------------------------------------- /honoring_the_code/002 - lores tetris/htc2_tetris.dsk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tilleul/apple2/9f533af8cd212a39e128679e054990c42e793745/honoring_the_code/002 - lores tetris/htc2_tetris.dsk -------------------------------------------------------------------------------- /honoring_the_code/002 - lores tetris/img/README.md: -------------------------------------------------------------------------------- 1 | Files for HTC #2 2 | -------------------------------------------------------------------------------- /honoring_the_code/002 - lores tetris/img/capture1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tilleul/apple2/9f533af8cd212a39e128679e054990c42e793745/honoring_the_code/002 - lores tetris/img/capture1.png -------------------------------------------------------------------------------- /honoring_the_code/002 - lores tetris/img/capture2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tilleul/apple2/9f533af8cd212a39e128679e054990c42e793745/honoring_the_code/002 - lores tetris/img/capture2.png -------------------------------------------------------------------------------- /honoring_the_code/002 - lores tetris/img/capture3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tilleul/apple2/9f533af8cd212a39e128679e054990c42e793745/honoring_the_code/002 - lores tetris/img/capture3.png -------------------------------------------------------------------------------- /honoring_the_code/002 - lores tetris/img/capture4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tilleul/apple2/9f533af8cd212a39e128679e054990c42e793745/honoring_the_code/002 - lores tetris/img/capture4.png -------------------------------------------------------------------------------- /honoring_the_code/002 - lores tetris/paleotronic_tetris.bas: -------------------------------------------------------------------------------- 1 | 10 GOSUB 1000 2 | 100 W = W +1: IF W >LV THEN W = 0: GOSUB 350 3 | 110 K = PEEK(KB): IF K > = H THEN POKE KC,H:K = K -H: GOSUB 300 4 | 190 GOTO 100 5 | 200 PY = PY *A2: VLIN PY,PY +A1 AT PX: RETURN 6 | 225 PY = PY *A2: HLIN X1,X2 AT PY: HLIN X1,X2 AT PY +A1: RETURN 7 | 300 ON E(K) GOTO 30000,330,340,350,360,30100 8 | 310 RETURN 9 | 330 X = X -1: GOTO 400 10 | 340 X = X +1: GOTO 400 11 | 350 DN = 1:Y = Y +1: GOSUB 400:DN = 0: RETURN 12 | 360 S = S +1: IF S/4 = INT(S/4) THEN S = S -4 13 | 400 GOSUB 500 14 | 410 GOSUB 800: IF F = 0 THEN X = XX:Y = YY:S = SS: GOSUB 420: IF DN THEN GOSUB 900 15 | 420 COLOR= CF: FOR PP = 1 TO 4:PX = X +X(S,PP):PY = Y +Y(S,PP): GOSUB 200: NEXT PP:XX = X:YY = Y:SS = S:D = 0: RETURN 16 | 500 IF DD THEN RETURN 17 | 510 COLOR= CB: FOR PP = 1 TO 4:PX = XX +X(SS,PP):PY = YY +Y(SS,PP): GOSUB 200: NEXT PP:DD = 0: RETURN 18 | 800 F = 1: FOR PP = 1 TO 4:PY = Y +Y(SS,PP): ON ( FN PC(X +X(S,PP)) >0) GOTO 805: NEXT PP: RETURN 19 | 805 F = 0: RETURN 20 | 850 F = 1: RETURN 21 | 900 P = 10: GOSUB 30300 22 | 905 RN = 0:Y = YM 23 | 910 X = XL 24 | 920 PY = Y: IF FN PC(X) = CB THEN 950 25 | 930 X = X +1: IF X < = XR THEN 920 26 | 940 R(RN) = Y:RN = RN +1 27 | 950 Y = Y -1: IF Y > = 0 THEN 910 28 | 960 IF RN THEN GOSUB 30400 29 | 970 Y = 0 30 | 980 X = INT((XR -XL)/2) +XL 31 | 985 S = INT( RND(1) *NS):CF = C(S):S = S *4 32 | 990 GOSUB 800: IF F THEN RETURN 33 | 995 GOTO 31000 34 | 1000 DIM E(127),X(27,4),Y(27,4),R(40) 35 | 1010 TEXT : HOME : GR 36 | 1011 PRINT "WELCOME..." 37 | 1014 LM = 10 38 | 1015 XM = 10:YM = 15 39 | 1016 XL = INT((40 -XM)/2) 40 | 1017 XR = XL +XM -1 41 | 1021 A1 = 1 42 | 1022 A2 = 2 43 | 1030 DEF FN PC(X) = SCRN( X,PY *A2) 44 | 1040 CB = 0 45 | 1050 XX = 20:YY = 0:SS = 0 46 | 1100 KB = -16384 47 | 1110 KC = -16368 48 | 1120 H = 128 49 | 1129 REM KEYBOARD ACTIONS 50 | 1130 REM QUIT 51 | 1131 E( ASC("Q")) = 1 52 | 1132 E( ASC("Q") -64) = 1 53 | 1140 REM MOVE LEFT 54 | 1141 E(8) = 2 55 | 1142 E( ASC(",")) = 2 56 | 1150 REM MOVE RIGHT 57 | 1151 E(21) = 3 58 | 1152 E( ASC(".")) = 3 59 | 1160 REM MOVE DOWN 60 | 1161 E(32) = 4 61 | 1162 E( ASC("Z")) = 4 62 | 1170 REM ROTATE 63 | 1171 E( ASC("R")) = 5 64 | 1172 E(13) = 5 65 | 1173 E( ASC("A")) = 5 66 | 1179 REM PAUSE GAME 67 | 1180 E( ASC("P")) = 6 68 | 1181 E( ASC("P") -64) = 6 69 | 1185 GOSUB 2000 70 | 1186 GOSUB 1300 71 | 1190 PRINT "PRESS ANY KEY TO START..." 72 | 1191 PRINT 73 | 1192 PRINT "PRESS Q TO QUIT." 74 | 1193 GOTO 31020 75 | 1299 REM DRAW THE GAME 76 | 1300 COLOR= 4: FOR I = 0 TO 19:X1 = 0:X2 = 39:PY = I: GOSUB 225: NEXT 77 | 1320 COLOR= CB: FOR I = 0 TO YM:X1 = XL:X2 = XR:PY = I: GOSUB 225: NEXT 78 | 1350 RETURN 79 | 1400 DATA 1 80 | 1401 DATA 0,0,1,0,0,1,1,1 81 | 1402 DATA 0,0,1,0,0,1,1,1 82 | 1403 DATA 0,0,1,0,0,1,1,1 83 | 1404 DATA 0,0,1,0,0,1,1,1 84 | 1410 DATA 2 85 | 1411 DATA 0,1,1,1,2,1,3,1 86 | 1412 DATA 1,0,1,1,1,2,1,3 87 | 1413 DATA 0,1,1,1,2,1,3,1 88 | 1414 DATA 1,0,1,1,1,2,1,3 89 | 1420 DATA 12 90 | 1421 DATA 1,1,0,1,1,0,2,1 91 | 1422 DATA 1,1,0,1,1,0,1,2 92 | 1423 DATA 1,1,0,1,2,1,1,2 93 | 1424 DATA 1,1,1,0,2,1,1,2 94 | 1430 DATA 13 95 | 1431 DATA 1,1,0,1,2,1,0,2 96 | 1432 DATA 1,1,1,0,1,2,2,2 97 | 1433 DATA 1,1,0,1,2,1,2,0 98 | 1434 DATA 1,1,1,0,1,2,0,0 99 | 1440 DATA 9 100 | 1441 DATA 1,1,0,1,2,1,2,2 101 | 1442 DATA 1,1,1,0,1,2,2,0 102 | 1443 DATA 1,1,0,1,2,1,0,0 103 | 1444 DATA 1,1,1,0,1,2,0,2 104 | 1450 DATA 3 105 | 1451 DATA 1,1,1,0,0,0,2,1 106 | 1452 DATA 1,1,1,0,0,1,0,2 107 | 1453 DATA 1,1,1,0,0,0,2,1 108 | 1454 DATA 1,1,1,0,0,1,0,2 109 | 1460 DATA 6 110 | 1461 DATA 1,1,0,1,1,0,2,0 111 | 1462 DATA 1,1,0,1,0,0,1,2 112 | 1463 DATA 1,1,0,1,1,0,2,0 113 | 1464 DATA 1,1,0,1,0,0,1,2 114 | 1990 DATA -1 115 | 2000 X = 0:Y = 0 116 | 2010 NS = 0 117 | 2020 READ C: IF C < > -1 THEN C(NS) = C: FOR J = 0 TO 3: FOR I = 1 TO 4: READ X(NS *4 +J,I): READ Y(NS *4 +J,I): NEXT I: NEXT J:NS = NS +1: GOTO 2020 118 | 2030 RETURN 119 | 21210 P = 1: RETURN 120 | 30000 TEXT : HOME : END 121 | 30100 HOME 122 | 30110 PRINT "GAME PAUSED. PRESS P TO CONTINUE..." 123 | 30120 P = 1 124 | 30130 K = PEEK(KB): IF K > = H THEN POKE KC,H:K = K -H: GOSUB 30200 125 | 30140 IF P THEN 30130 126 | 30150 HOME 127 | 30160 PRINT "SCORE ";SC; TAB( 21);"LEVEL ";LM -LV +1 128 | 30170 RETURN 129 | 30200 ON E(K) GOTO 30000,30210,30210,30210,30210,30220 130 | 30210 RETURN 131 | 30220 P = 0 132 | 30230 RETURN 133 | 30300 SC = SC +P 134 | 30310 VTAB 21: HTAB 7 135 | 30320 PRINT SC; 136 | 30330 RETURN 137 | 30400 RN = RN -1 138 | 30410 FOR C = 0 TO 32 139 | 30415 COLOR= C 140 | 30420 FOR I = 0 TO RN:X1 = XL:X2 = XR:PY = R(I): GOSUB 225: NEXT I 141 | 30430 FOR I = 0 TO 2: NEXT I 142 | 30440 NEXT C 143 | 30450 FOR I = 0 TO RN 144 | 30460 Y = R(I) +I 145 | 30470 YP = Y -1: FOR X = XL TO XR:PY = YP: COLOR= FN PC(X):PX = X:PY = Y: GOSUB 200: NEXT X:Y = Y -1: IF Y >0 THEN 30470 146 | 30480 P = 100: GOSUB 30300 147 | 30490 NEXT I 148 | 30495 RETURN 149 | 31000 VTAB 22: PRINT 150 | 31010 PRINT " GAME OVER" 151 | 31020 P = 1 152 | 31030 K = PEEK(KB): IF K > = H THEN POKE KC,H:K = K -H: GOSUB 31200 153 | 31040 IF P THEN 31030 154 | 31050 D = 1 155 | 31060 SC = 0:LV = LM 156 | 31070 GOSUB 30150 157 | 31080 GOSUB 1300 158 | 31090 GOTO 905 159 | 31200 ON E(K) GOTO 30000 160 | 31210 P = 0: RETURN 161 | 32000 REM END OF LISTING -------------------------------------------------------------------------------- /honoring_the_code/README.md: -------------------------------------------------------------------------------- 1 | # Honoring The Code 2 | 3 | Welcome to a new series of posts that will take a look at forgotten code, written by our elders and made public in several magazines of the Apple II golden era. 4 | In each episode, we will rediscover an old program, written in Applesoft, or assembly, or both, and maybe sometimes in another programming language. 5 | 6 | These programs will be of various interests as sometimes the description in the original article was more thrilling than the program itself ! 7 | 8 | For each program we will briefly describe how it works and, then, criticize the techniques used, and, finally, if needed, we will propose some improvements to the code so that it's more fit for today standards. 9 | 10 | This serie expects you to have some basic knowledge of Applesoft, but nothing too fancy. If you're not familiar with Applesoft and are more comfortable with another brand of basic, this won't be an obstacle. From times to times, the code we review will use some assembly routines. 6502 assembly is not hard to learn but it's definitely less intuitive than Basic, so, whenever possible we will explain how the assembly routines work as if you never learned assembly. 11 | -------------------------------------------------------------------------------- /stranger_things/003 - Inline Text Modes/README.md: -------------------------------------------------------------------------------- 1 | # STRANGER THINGS ABOUT YOUR APPLE II, part 3 2 | An A2SE series about things you did not know about your Apple II or maybe simply forgot. 3 | 4 | # Part 3: Inline Text Modes 5 | 6 | In this article we're going to discover how you can 7 | - integrate `FLASH`/`INVERSE`/`NORMAL` commands within strings and strings variables 8 | - display `FLASH`/`INVERSE` characters in listings 9 | - `PRINT` normally unprintable control-characters in order to plot brown and orange pixels on the lores screen (wtf ?) 10 | 11 | ## Text modes on the Apple II 12 | This is old news: the Apple II support 3 kind of text displays: characters are written on screen either in `NORMAL` mode (white on black), `INVERSE` mode (black on white) or `FLASH` mode (alternating between `NORMAL` and `INVERSE` mode). 13 | 14 | Three commands allow you to change the text mode. They are `NORMAL`, `INVERSE` and `FLASH`. 15 | 16 | If you want your text to be printed in inverse, all you have to do is issue an `INVERSE` statement before a `PRINT`: 17 | ```basic 18 | INVERSE: PRINT "YOU WIN 10 POINTS !" 19 | ``` 20 | ![Inverse](img/inverse.png) 21 | 22 | If you don't issue a `NORMAL` command, then all the subsequent `PRINT`s will display text in `INVERSE`. The same goes for `FLASH`. 23 | 24 | Now, if you want to mix `NORMAL`/`INVERSE`/`FLASH` modes in the same string, your only option is to issue several `PRINT`s and adjust the text mode accordingly in between. 25 | ```basic 26 | PRINT "YOU WIN "; : INVERSE : PRINT "10"; : NORMAL : PRINT " POINTS !" 27 | ``` 28 | ![Normal and Inverse](img/normal_inverse.png) 29 | 30 | ## Integrating text modes within strings 31 | This is something that's possible on several 8-bit computers but not on the Apple II, except if you have a 80-column card. In this case, **and** if the card is active, you can use CTRL-O and CTRL-N to instruct the computer that the following characters are respectively in `INVERSE` or `NORMAL` modes. There's no CTRL code for the `FLASH` mode though, as the 80 column cannot display `FLASH` characters. 32 | 33 | We are going to do exactly the same but without the need for a 80-column card and we will add a CTRL code for the `FLASH` mode too ! 34 | 35 | The way to do that is rather easy as all we have to do is redirect the character output routine of the Apple II to our own routine. 36 | 37 | Fortunately, this redirection has been planned from day one in Woz' monitor. This is basically what allows you to print your listings and screens on a printer (and not on screen). This is also how DOS intercepts CTRL-D commands (you know like `PRINT CHR$(4);"CATALOG"` ) and how DOS and PRODOS commands are handled. 38 | 39 | In `$FDED` is the monitor routine named `COUT`. Its role is to handle the output of characters on screen. But the first thing `COUT` does is give control to another, user-defined, routine, if any. By default (if there's no DOS/PRODOS), this routine is located in `$FDF0` (and is named `COUT1`), that is just 3 bytes after `$FDED` and it's in fact the next instruction after the one in `$FDED`. 40 | 41 | We have this: 42 | ```Assembly 43 | FDED: 6C 36 00 COUT JMP (CSWL) ; jump to location referenced by $36-$37 44 | FDF0: 48 COUT1 PHA ; normal monitor character output routine starts here 45 | FDF1: ... 46 | ``` 47 | So, what we need to do is write in CSWL (zero page $36) and CSWH (zero page $37) the address where we want to handle the character output. 48 | 49 | There, we check if the character that must be output is one of the CTRL characters that will set the `NORMAL`/`INVERSE`/`FLASH` mode, set the mode accordingly and return the control to `$FDF0`. That's for the theory. 50 | 51 | But remember I also want to print normally unprintable control-characters. 52 | 53 | ## How Applesoft and the monitor work together. 54 | When you type a character on the Apple II keyboard, the character that's been typed is retrievable in `$C000` (decimal `-16384` or `49152` ... that's why you do `K=PEEK(49152)` when you want to identify which key was pressed). You also know that this character is a byte, and that it corresponds to the ASCII code of the character **plus 128** because the hi-bit of the byte is set. This value, if the character is meant to be printed on screen, is sent to `COUT`. 55 | 56 | Thus, `COUT` expects a byte with a value above 127. Let's see what happens with Applesoft. 57 | 58 | There are not many routines in Applesoft that output characters. We have `PRINT`. We have `INPUT` (that internally uses `PRINT`). We have `LIST`. We have `TRACE`. We have `TAB()` and `SPC()`. We have error messages too and then we have here and there some routines that output a carriage-return character for various reasons. All these use `COUT`. Thus the first thing that Applesoft does is add 128 to the ASCII value of the character that must be displayed (more precisely, Applesoft applies an ORA-mask on the byte value, forcing the hi-bit to turn on). 59 | 60 | Now with a byte value above 127, Applesoft then checks if the character is a "control-character", that is a character with an ASCII value below 32. But since the byte value is now above 127, it checks if the value is below 160 (=128+32). If it's NOT a control-character, then Applesoft checks if the `FLASH` mode is on. If it is, then it turns on the 6th bit of the byte (equivalent to add 32), and our byte value is now above 191. 61 | 62 | Then the byte is sent to `COUT`. 63 | 64 | So, `COUT` has a value which is either in 65 | - range 128 to 159 if a control character is to be output 66 | - range 160 to 255 if a `NORMAL` or `INVERSE` character is to be output 67 | - range 192 to 255 if a `FLASH` character is to be output 68 | 69 | The first thing that `COUT` does is modify the byte received: 70 | - if `NORMAL` mode is set, the value is not modified. The byte is still **between 128 and 255** 71 | - if `INVERSE` mode is set, bits 6 and 7 of the value are set to 0, which is equivalent to 72 | - subtract 192 if the byte is above 191, or 73 | - subtract 128 if the byte is between 128 and 191. The byte is now **between 0 and 63**. 74 | - if `FLASH` mode is set, bit 7 is set to 0, equivalent to subtract 128. The byte is now **between 64 and 127**. 75 | - if it's a control character, the value is not modified regardless of the current text mode 76 | 77 | So we have now 4 possibilities: 78 | - Values from 0 to 63 for `INVERSE` output 79 | - Values from 64 to 127 for `FLASH` output 80 | - Values from 128 to 159 for control characters 81 | - Values from 160 to 255 for `NORMAL` output 82 | 83 | `INVERSE`, `FLASH` and `NORMAL` characters are output on screen accordingly while control-characters are handled separately. For a standard Apple II, only CTRL-G (bell), CTRL-M (return), CTRL-H (backspace or left-arrow) and CTRL-J (line feed or down-arrow) and handled. The others are simply ignored. 84 | 85 | You can observe the result of each of the 4 possibilities mentioned above. Type 86 | ```basic 87 | TEXT: HOME 88 | VTAB 1: INVERSE: PRINT "A"; : FLASH : PRINT "A"; : NORMAL : PRINT CHR$(1); : PRINT "A"; : VTAB 4 89 | ``` 90 | As you can see, the `PRINT CTRL-A` (or more exactly `PRINT CHR$(1)`) did not output anything because `COUT` ignores control-characters. 91 | Now type 92 | ```basic 93 | PRINT PEEK (1024);" ";PEEK(1025);" ";PEEK(1026);" ";PEEK(1027) 94 | ``` 95 | These are the 4 values of the first 4 characters on the top of the screen. The 1 is A in `INVERSE`, the 65 is A in `FLASH`, the 193 is A in `NORMAL` and the 160 is just the space character in `NORMAL` in the 4th position. The control-character was not printed. 96 | 97 | ![PRINT multiples A](img/print_a.png) 98 | 99 | Now let us do the same but without `PRINT`: 100 | ```basic 101 | TEXT: HOME 102 | POKE 1024,1 : POKE 1025, 65: POKE 1026, 129: POKE 1027, 193 103 | ``` 104 | 105 | ![POKE multiples A](img/poke_a.png) 106 | 107 | Well ! It looks like we can store values of 128 to 159 on the screen memory even though we cannot PRINT those control-characters. 108 | 109 | ## Why print control-characters ? 110 | Let's admit it, at first sight, there's absolutely no interest in printing control-characters. After all, these characters are designed to be invisible: they're used in communications protocols or to move the cursor, etc. What's more they just look like normal characters when you `POKE` them in screen memory. 111 | 112 | Well, there's one minor interest though. The text area memory is in $400-$7FF. And this area is shared with the lo-res display. It means we can use `PRINT` to `PLOT` on the lores screen. `PRINT`ing one character actually draws two contiguous vertical lores pixels. 113 | 114 | For instance, type the following: 115 | ```basic 116 | GR 117 | INVERSE: VTAB 1: PRINT "Q@Q@QAA@Q@@@Q@@@QAQ": PRINT "QAQ@QA@@Q@@@Q@@@Q@Q": PRINT "A@A@AAA@AAA@AAA@AAA": NORMAL: VTAB 22 118 | ``` 119 | 120 | ![Hello in lores](img/hello.png) 121 | 122 | You've just drawn the word "HELLO" the fastest possible way with Applesoft only. 123 | 124 | The problem now is that to `PRINT`/draw all possible combinations of colors you need to be able to `PRINT` all 255 possible values of bytes, including those between 128 to 159, that is the control-characters. 125 | 126 | As `PRINT` is super-fast compared to `PLOT` (or `HLIN` and `VLIN`), this could be used to display sprites on the lores screen faster than ever with Applesoft. 127 | 128 | And since what we want to do is use combinations of CTRL-characters to change the text mode **within** strings, we could draw any shape and store the `INVERSE`/`FLASH`/`NORMAL`/`SPECIAL` statements within the string itself ! 129 | 130 | ## The code explained 131 | The routine will be simple and short, so we can store it in $300. But if you want to store it elsewhere, you can as it's completely relocatable. 132 | 133 | The first thing we want to do is initialize CSWL/CSWH (and thus activate our routine). To do that we can begin in $300 with a simple init routine that needs to be called at the start of your Applesoft program (or at least before you want to take advantage of the new feature). 134 | 135 | We also want to initialize a flag stating that the "Peculiar mode" is not set yet. The "Peculiar mode" is that new mode where we allow to print control-characters. I decided that this flag would be stored in zero page $34. This memory location is normally used by the monitor (it is known as `YSAV`) to store temp data but I estimated that there would be no interest in being in the monitor AND want to print control-characters on the lores screen. As this is the only zero page location that I need, I wanted to avoid any of the usual "free" zero pages locations to make sure I don't interfere with possibly other programs that might use these "free" locations. 136 | 137 | So the code begins with: 138 | 139 | ```Assembly 140 | 300: A9 03 INIT LDA #>START 141 | 302: 85 37 STA CSWH 142 | 304: A9 0D LDA #0 THEN RETURN 41 | 206 F=INT(RND(1)*20) : GOSUB 300: Z=RND(1)*40+15: ON F GOTO 210, 220, 230, 240, 250, 260, 270, 280 42 | 208 Z=0 43 | 209 RETURN 44 | 210 PRINT "I DON'T LIKE THIS NEIGHBORHOOD": RETURN 45 | 220 PRINT "THIS PLACE GIVES ME THE CREEPS": RETURN 46 | 230 PRINT "I'M ON TIME ! WHERE IS HE ?": RETURN 47 | 240 PRINT "HE SAID MIDNIGHT BY THE OLD JUNKYARD": RETURN 48 | 250 PRINT "BE STRONG, MAN, THIS WILL GO WELL": RETURN 49 | 260 PRINT "WHAT ARE YOU AFRAID OF ? NOTHING CAN HAPPEN": RETURN 50 | 270 PRINT "HE'S LATE ! WHY IS HE LATE ?": RETURN 51 | 280 PRINT "MAYBE HE WON'T COME AFTER ALL ?": RETURN 52 | 290 PRINT "WHAT WAS THAT SOUND ? I DON'T SEE ANYTHING": RETURN 53 | 54 | 300 POKE 35,23: HOME: POKE 35,24: VTAB 21: RETURN 55 | 56 | 57 | 58 | 500 HOME: GOSUB 998: PRINT H$(0) 59 | 60 | 510 VTAB 14: HTAB 2: PRINT F$: VTAB 21: SPEED=1: HTAB 30: PRINT "?????????";: SPEED=255 61 | 62 | 515 S=S*15: GOSUB 998: PRINT Y$: GOSUB 300 63 | 516 GOSUB 999: GOSUB 300: HTAB 20 : PRINT "ARE YOU URIBRAS ?" : S=S*2 64 | 517 GOSUB 999: GOSUB 300: HTAB 20 : PRINT "DO YOU KNOW HIM ?" 65 | 518 GOSUB 999: GOSUB 300: HTAB 15 : PRINT "CAN YOU TAKE ME TO HIM ?" 66 | 519 GOSUB 999: GOSUB 300: HTAB 15 : PRINT "I HAVE BROUGHT THE BEAD !" 67 | 68 | 69 | 520 GOSUB 999: S=S/30: GOSUB 300: HTAB 20: PRINT "CAN YOU ... ???": VTAB 14: HTAB 7: PRINT G$: GOSUB 999 70 | 530 VTAB 14: HTAB 7: PRINT H$: GOSUB 999 71 | 540 VTAB 18: HTAB 12: PRINT I$: GOSUB 999 72 | 550 VTAB 17: HTAB 11: PRINT J$: GOSUB 999 73 | 560 VTAB 17: HTAB 11: PRINT K$: GOSUB 999 : S=S/2: GOSUB 300: HTAB 20: PRINT ".**.. ** ** ... ***" 74 | 75 | 570 FOR I=0 TO 30: VTAB 14: HTAB 1: PRINT L$: GOSUB 999: VTAB 17: HTAB 1: PRINT M$: GOSUB 999: NEXT 76 | 77 | 78 | 580 FOR I= 16 TO 19: VTAB I: HTAB 11: PRINT CHR$(15) + "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@";: NEXT: VTAB 14: HTAB 1: PRINT N$ 79 | 80 | 590 FOR I=V-1 TO 16: HTAB H: VTAB I: GOSUB 999: PRINT H$(2): NEXT 81 | 82 | 600 HOME: VTAB 21: ON INT(RND(1)*5) GOTO 620, 630, 640, 650 83 | 610 HTAB 5: PRINT "MAN, A SIMPLE YES OR NO WAS ENOUGH": GOTO 990 84 | 620 HTAB 10: PRINT "YOU KNOW THAT HURTS, RIGHT ?": GOTO 990 85 | 630 HTAB 3: PRINT "BUT ... BUT ... I HAD THE ... BEAD !": GOTO 990 86 | 640 PRINT "GOORGLL.. GUH ... ARG... GHLU.. GGUGNL.": GOTO 990 87 | 650 PRINT "I SEE ... YOU'RE NOT THE TALKATIVE TYPE" : GOTO 990 88 | 89 | 990 CALL 43089 : PRINT: PRINT "*** THE END ***" : VTAB 23: END: REM RESTORE DOS AND EXIT 90 | 91 | 998 HTAB H: VTAB V 92 | 999 FOR J=0 TO S: NEXT: RETURN 93 | 94 | 1000 PRINT F;" ";: F=F+1: A$=CHR$(15)+"@"+CHR$(16)+"@"+CHR$(14)+"8888"+CHR$(15)+"H"+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(10) 95 | 1010 A$=A$+"@"+CHR$(14)+"8;4;4"+CHR$(15)+"@"+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(10) 96 | 1020 A$=A$+"@P"+CHR$(16)+"P[["+CHR$(15)+"P@"+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(10) 97 | 1030 A$=A$+"QQ"+CHR$(16)+"YYY"+CHR$(15)+"Q"+CHR$(14)+"0"+CHR$(15)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(10) 98 | 1040 A$=A$+"K"+CHR$(14)+";"+CHR$(6)+"))99"+CHR$(15)+"K"+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(10) 99 | 1050 A$=A$+"@"+CHR$(6)+CHR$(34)+"&"+CHR$(15)+"@"+CHR$(6)+"7"+CHR$(15)+"G@"+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(10) 100 | 1060 A$=A$+"@"+CHR$(6)+"RP"+CHR$(15)+"@"+CHR$(6)+"VP"+CHR$(15)+"@"+CHR$(14) 101 | 102 | 1069 PRINT F;" ";: F=F+1 103 | 1070 B$=CHR$(15)+"@@@"+CHR$(16)+"@"+CHR$(14)+"8888"+CHR$(15)+"H"+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(10) 104 | 1080 B$=B$+"@@@"+CHR$(14)+"8;4;4"+CHR$(15)+"@"+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(10) 105 | 1090 B$=B$+"@@PP"+CHR$(16)+"P[["+CHR$(15)+"P@"+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(10) 106 | 1100 B$=B$+"@QQ"+CHR$(16)+"YYYY"+CHR$(15)+"Q"+CHR$(14)+"0"+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(10) 107 | 1110 B$=B$+CHR$(15)+"@K"+CHR$(14)+";"+CHR$(15)+"&"+CHR$(6)+"))99"+CHR$(15)+"K"+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(10) 108 | 1120 B$=B$+"@@@@&"+CHR$(6)+"&"+CHR$(15)+"G@@"+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(10) 109 | 1130 B$=B$+"@@@@"+CHR$(6)+"RU"+CHR$(15)+"E@@"+CHR$(14) 110 | 111 | 1139 PRINT F;" ";: F=F+1 112 | 1140 C$=CHR$(15)+"@@@@"+CHR$(16)+"@"+CHR$(14)+"8888"+CHR$(15)+"H"+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(10) 113 | 1150 C$=C$+"@@@@"+CHR$(14)+"8;4;4"+CHR$(15)+"@"+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(10) 114 | 1160 C$=C$+"@@@@P"+CHR$(16)+"P[["+CHR$(15)+"P@"+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(10) 115 | 1170 C$=C$+"@@@QQ"+CHR$(16)+"YYY"+CHR$(15)+"Q"+CHR$(14)+"0"+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(10) 116 | 1180 C$=C$+CHR$(15)+"@@@K"+CHR$(14)+";"+CHR$(6)+"))99"+CHR$(15)+"K"+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(10) 117 | 1190 C$=C$+"@@@@@"+CHR$(6)+"2&&"+CHR$(15)+"@@"+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(10) 118 | 1200 C$=C$+"@@@@"+CHR$(6)+"U"+CHR$(15)+"G@"+CHR$(6)+"VP"+CHR$(15)+"@"+CHR$(14) 119 | 120 | 1209 PRINT F;" ";: F=F+1 121 | 1210 D$=CHR$(15)+"@@@@@"+CHR$(16)+"@"+CHR$(14)+"8888"+CHR$(15)+"H"+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(10) 122 | 1220 D$=D$+ "@@@@@"+CHR$(14)+"8;4;4"+CHR$(15)+"@"+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(10) 123 | 1230 D$=D$+ "@@@@PP"+CHR$(16)+"P[["+CHR$(15)+"P@"+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(10) 124 | 1240 D$=D$+ "@@@QQ"+CHR$(16)+"YYYY"+CHR$(15)+"Q"+CHR$(14)+"0"+CHR$(15) +CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(10) 125 | 1250 D$=D$+ "@@@K"+CHR$(14)+";"+CHR$(15)+"&"+CHR$(6)+"))99"+CHR$(15)+"K" +CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(10) 126 | 1260 D$=D$+ "@@@@@@"+CHR$(6)+"V&7"+CHR$(15)+"@@"+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(10) 127 | 1270 D$=D$+ "@@@@@@E"+CHR$(6)+"VP"+CHR$(15)+"@@"+CHR$(14) 128 | 129 | 1279 PRINT F;" ";: F=F+1 130 | 1280 E$=CHR$(15)+"@@@@@@@"+CHR$(16)+"@"+CHR$(14)+"8888"+CHR$(15)+"H" +CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(10) 131 | 1290 E$=E$ +"@@@@@@@"+CHR$(14)+"8;4;4"+CHR$(15)+"@" +CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(10) 132 | 1300 E$=E$ +"@@@@@@@P"+CHR$(16)+"P[["+CHR$(15)+"P@" +CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(10) 133 | 1310 E$=E$ +"@@@@@@QQ"+CHR$(16)+"YYY"+CHR$(15)+"Q"+CHR$(14)+"0"+CHR$(15) +CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(10) 134 | 1320 E$=E$ +"@@@@@@K"+CHR$(14)+";"+CHR$(6)+"))99"+CHR$(15)+"K" +CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(10) 135 | 1330 E$=E$ +"@@@@@@@"+CHR$(6)+CHR$(98)+"&"+CHR$(15)+"@"+CHR$(6)+"7"+CHR$(15)+"G@" +CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(10) 136 | 1340 E$=E$ +"@@@@@@@"+CHR$(6)+"RP"+CHR$(15)+"@"+CHR$(6)+"VP"+CHR$(15)+"@"+CHR$(14) 137 | 138 | 1349 PRINT F;" ";: F=F+1 139 | 1350 X$ = CHR$(15)+"@"+CHR$(16)+"@"+CHR$(14)+"8888"+CHR$(15)+"H"+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(10) 140 | 1360 X$ = X$+"@"+CHR$(14)+"8;4;4"+CHR$(15)+"@"+CHR$(14) 141 | 142 | 1369 PRINT F;" ";: F=F+1 143 | 1370 H$(0) = CHR$(15)+"H"+CHR$(14)+"8888"+CHR$(16)+"@"+CHR$(15)+"@"+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(10) 144 | 1380 H$(0) = H$(0)+"@"+CHR$(14)+"4;4;8"+CHR$(15)+"@"+CHR$(14) 145 | 146 | 147 | 1389 PRINT F;" ";: F=F+1 148 | 1390 H$(1) = CHR$(15)+"@"+CHR$(16)+"@"+CHR$(14)+"8888"+CHR$(15)+"H"+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(10) 149 | 1400 H$(1) = H$(1)+"@"+CHR$(14)+"8;;;;"+CHR$(15)+"@"+CHR$(14) 150 | 1405 H$(2) = CHR$(15)+"@@@@@@@"+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(10)+H$(0)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(10) 151 | 1406 H$(2) = H$(2) + CHR$(15)+"@P"+CHR$(16)+"[[P"+CHR$(15)+"P@"+CHR$(14) 152 | 153 | 154 | 155 | 156 | 1409 PRINT F;" ";: F=F+1 157 | 1410 Y$=CHR$(15)+"H"+CHR$(14)+"8888"+CHR$(16)+"@"+CHR$(15)+"@@"+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(10) 158 | 1420 Y$=Y$+"@"+CHR$(14)+"4;4;8"+CHR$(15)+"@"+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(10) 159 | 1430 Y$=Y$+"@P"+CHR$(16)+"[[P"+CHR$(15)+"P@"+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(10) 160 | 1440 Y$=Y$+CHR$(14)+"0"+CHR$(15)+"Q"+CHR$(16)+"YYY"+CHR$(15)+"QQ"+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(10) 161 | 1450 Y$=Y$+"K"+CHR$(6)+"99))"+CHR$(14)+";"+CHR$(15)+"K"+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(10) 162 | 1460 Y$=Y$+"@G"+CHR$(6)+"7"+CHR$(15)+"@"+CHR$(6)+"&"+CHR$(98)+CHR$(15)+"@"+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(10) 163 | 1470 Y$=Y$+"@"+CHR$(6)+"PV"+CHR$(15)+"@"+CHR$(6)+"PR"+CHR$(15)+"@"+CHR$(14) 164 | 165 | 166 | 167 | 168 | 169 | REM HTAB 3 170 | 1999 PRINT F;" ";: F=F+1 171 | 2000 F$=CHR$(15)+"@@ @@@H"+CHR$(16)+"@"+CHR$(15)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(10) 172 | 2010 F$=F$+CHR$(34)+CHR$(6)+CHR$(98)+CHR$(15)+"@H"+CHR$(16)+"I"+CHR$(15)+"H"+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(10) 173 | 2020 F$=F$+"B"+CHR$(14)+"22"+CHR$(15)+"B@"+CHR$(16)+"H"+CHR$(15)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(10) 174 | 2030 F$=F$+" ++"+CHR$(6)+" "+CHR$(15)+"@"+CHR$(16)+"H"+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(10) 175 | 2040 F$=F$+CHR$(14)+"2"+CHR$(15)+"&"+CHR$(34)+""+CHR$(34)+""+CHR$(34)+"F"+CHR$(16)+"K"+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(10) 176 | 2050 F$=F$+CHR$(15)+" "+CHR$(34)+CHR$(6)+CHR$(98)+CHR$(15)+CHR$(34)+"&@"+CHR$(16)+"H"+CHR$(14) 177 | 178 | 179 | REM HTAB 7 180 | 2059 PRINT F;" ";: F=F+1 181 | 2060 G$=CHR$(15)+"H"+CHR$(16)+"HH@@"+CHR$(15)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(10) 182 | 2070 G$=G$+"@I"+CHR$(16)+"IXHH"+CHR$(15)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(10) 183 | 2080 G$=G$+"@H"+CHR$(16)+"HYHH"+CHR$(15)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(10) 184 | 2090 G$=G$+"@@"+CHR$(16)+"HHYH@"+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(10) 185 | 2100 G$=G$+CHR$(16)+"FBB"+CHR$(14)+"6"+CHR$(16)+"@@HHHYHH"+CHR$(15)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(10) 186 | 2110 G$=G$+"@@@@HH"+CHR$(14) 187 | 188 | REM HTAB 7 189 | 2119 PRINT F;" ";: F=F+1 190 | 2120 H$=CHR$(15)+"@@@@@"+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(10) 191 | 2130 H$=H$+"@@@@@"+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(10) 192 | 2140 H$=H$+"@@@@@"+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(10) 193 | 2150 H$=H$+"@@@@@"+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(10) 194 | 2160 H$=H$+CHR$(16)+"@@@X"+CHR$(15)+"@"+CHR$(16)+"@"+CHR$(15)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(10) 195 | 2170 H$=H$+"HH"+CHR$(14) 196 | 197 | REM HTAB 12 VTAB 5 198 | 2179 PRINT F;" ";: F=F+1 199 | 2180 I$=CHR$(16)+"PYP@"+CHR$(15)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(10) 200 | 2190 I$=I$+"IH"+CHR$(14) 201 | 202 | REM HTAB 11 VTAB 4 203 | 2199 PRINT F;" ";: F=F+1 204 | 2200 J$=CHR$(15)+"@"+CHR$(16)+"PPP"+CHR$(15)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(10) 205 | 2210 J$=J$+"I@"+CHR$(16)+"P"+CHR$(15)+"@I"+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(10) 206 | 2220 J$=J$+"I"+CHR$(16)+"PPP"+CHR$(15)+"I"+CHR$(14) 207 | 208 | REM HTAB 11 VTAB 4 209 | 2229 PRINT F;" ";: F=F+1 210 | 2230 K$=CHR$(15)+"@@@@"+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(10) 211 | 2240 K$=K$+CHR$(14)+"PP"+CHR$(16)+"]"+CHR$(14)+"PP"+CHR$(15)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(10) 212 | 2250 K$=K$+"@@M@@"+CHR$(14) 213 | 214 | REM HTAB 1: VTAB 1 215 | 2259 PRINT F;" ";: F=F+1 216 | 2260 L$=CHR$(15)+"@ @@@@@"+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(10) 217 | 2270 L$=L$+"@"+CHR$(34)+CHR$(6)+CHR$(98)+CHR$(15)+"@@@@"+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(10) 218 | 2280 L$=L$+"B"+CHR$(14)+"22"+CHR$(15)+"B@@@"+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(10) 219 | 2290 L$=L$+" ++"+CHR$(6)+" "+CHR$(15)+"@@@@@"+CHR$(14)+"PPPPPPPPPPPPPPPPPPPPPPPPPPPPPP" 220 | 2300 L$=L$+"2"+CHR$(16)+"FBB"+CHR$(14)+"6"+CHR$(16)+"@@@@"+CHR$(14)+"]"+CHR$(16)+"YYYYYYYYYYYYYYYYYYYYYYYYYYYYYY" 221 | 2310 L$=L$+CHR$(15)+"@@"+CHR$(34)+""+CHR$(34)+""+CHR$(6)+""+CHR$(98)+""+CHR$(15)+""+CHR$(34)+"& @M"+CHR$(14)+"YYYYYYYYYYYYYYYYYYYYYYYYYYYYYY"+CHR$(14) 222 | 223 | REM HTAB 1: VTAB 4 224 | 2319 PRINT F;" ";: F=F+1 225 | 2320 M$=CHR$(15)+"@ ++"+CHR$(6)+" "+CHR$(15)+"@@@@@@"+CHR$(16)+"PPPPPPPPPPPPPPPPPPPPPPPPPPPPP"+CHR$(14) 226 | 2330 M$=M$+CHR$(15)+"@"+CHR$(14)+"2"+CHR$(16)+"FBF"+CHR$(14)+"0"+CHR$(16)+"@@@@Y"+CHR$(14)+"]]]]]]]]]]]]]]]]]]]]]]]]]]]]]"+CHR$(14) 227 | 2340 M$=M$+CHR$(15)+"@@"+CHR$(34)+""+CHR$(34)+""+CHR$(6)+""+CHR$(98)+""+CHR$(15)+""+CHR$(34)+"& @@I"+CHR$(16)+"]]]]]]]]]]]]]]]]]]]]]]]]]]]]]"+CHR$(14) 228 | 229 | 230 | 2349 PRINT F;" ";: F=F+1 231 | 2350 N$=CHR$(15)+"@@@@ @@@H"+CHR$(16)+"@"+CHR$(13) 232 | 2360 N$=N$+CHR$(15)+"@@@@@"+CHR$(34)+""+CHR$(6)+""+CHR$(98)+""+CHR$(15)+"@H"+CHR$(16)+"I"+CHR$(15)+"H"+CHR$(13) 233 | 2370 N$=N$+"@@@@B"+CHR$(14)+"22"+CHR$(15)+"B@"+CHR$(16)+"H"+CHR$(15)+"@"+CHR$(13) 234 | 2380 N$=N$+"@@@@ ++"+CHR$(6)+" "+CHR$(15)+"@"+CHR$(16)+"H"+CHR$(15)+"@"+CHR$(13) 235 | 2390 N$=N$+"@@@"+CHR$(14)+"2"+CHR$(15)+"&"+CHR$(34)+CHR$(34)+CHR$(34)+"F"+CHR$(16)+"K"+CHR$(15)+"@"+CHR$(13) 236 | 2400 N$=N$+"@@ "+CHR$(34)+CHR$(6)+CHR$(98)+CHR$(15)+CHR$(34)+"&@"+CHR$(16)+"H"+CHR$(15)+"@"+CHR$(14) 237 | 238 | 2409 PRINT F;" ";: F=F+1 239 | 2410 MN$=CHR$(15)+"@"+CHR$(14)+"PPP"+CHR$(15)+"@@@@"+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(10) 240 | 2420 MN$=MN$+"@@M"+CHR$(14)+"]]P"+CHR$(15)+"@@"+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(10) 241 | 2430 MN$=MN$+"@@@M"+CHR$(14)+"]]P"+CHR$(15)+"@"+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(10) 242 | 2440 MN$=MN$+"@@@@"+CHR$(14)+"]]]"+CHR$(15)+"@"+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(10) 243 | 2450 MN$=MN$+"@@@"+CHR$(14)+"P]]"+CHR$(15)+"M@"+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(10) 244 | 2460 MN$=MN$+"@@"+CHR$(14)+"P]]"+CHR$(15)+"M@@"+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(8)+CHR$(10) 245 | 2470 MN$=MN$+"@MMM@@@@"+CHR$(14) 246 | 247 | 248 | 249 | 3000 RETURN 250 | 251 | 252 | -------------------------------------------------------------------------------- /stranger_things/003 - Inline Text Modes/meeting_the_beadmaster.mkv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tilleul/apple2/9f533af8cd212a39e128679e054990c42e793745/stranger_things/003 - Inline Text Modes/meeting_the_beadmaster.mkv -------------------------------------------------------------------------------- /stranger_things/003 - Inline Text Modes/st3_inline_text_modes.dsk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tilleul/apple2/9f533af8cd212a39e128679e054990c42e793745/stranger_things/003 - Inline Text Modes/st3_inline_text_modes.dsk -------------------------------------------------------------------------------- /stranger_things/README.md: -------------------------------------------------------------------------------- 1 | # STRANGER THINGS ABOUT YOUR APPLE II 2 | 3 | An A2SE series about things you did not know about your Apple II or maybe simply forgot. 4 | -------------------------------------------------------------------------------- /tools/6502_assembler/6502_SpASM-1.2.0-example.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tilleul/apple2/9f533af8cd212a39e128679e054990c42e793745/tools/6502_assembler/6502_SpASM-1.2.0-example.xlsx -------------------------------------------------------------------------------- /tools/6502_assembler/6502_SpASM-1.2.2.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tilleul/apple2/9f533af8cd212a39e128679e054990c42e793745/tools/6502_assembler/6502_SpASM-1.2.2.xlsx -------------------------------------------------------------------------------- /tools/6502_assembler/6502_assembler.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tilleul/apple2/9f533af8cd212a39e128679e054990c42e793745/tools/6502_assembler/6502_assembler.png -------------------------------------------------------------------------------- /tools/6502_assembler/6502_assembler2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tilleul/apple2/9f533af8cd212a39e128679e054990c42e793745/tools/6502_assembler/6502_assembler2.png -------------------------------------------------------------------------------- /tools/6502_assembler/README.md: -------------------------------------------------------------------------------- 1 | # 6502 SpASM (6502 Spreadsheet Assembler and Linker) v1.2.2 2 | ![!test](6502_assembler2.png) 3 | 4 | This is a ~~proof-of-concept~~ full-featured 6502 assembler with linker in a spreadsheet (works with Excel, Libre Office, etc.). 5 | 6 | If you're using Libre Office, you need Libre Office v7.1.x or later if you want to use the linker. 7 | 8 | The 6502_SpASM-v.x.y.xlsx file is the latest release. Instructions included in the spreadsheet. 9 | 10 | The 6502_SpASM-v.x.y-example.xlsx file contains an example with different modules linked. Possibly in an older version. 11 | 12 | 13 | ## Features 14 | - all the 6502 instructions plus a couple of pseudo-instructions 15 | - all addressing modes 16 | - global and local labels 17 | - EQUs to declare constants 18 | - use of +/- offsets with constants/labels/numeric values 19 | - use of "#" to reference values 20 | - decimal notation by default (0-65535) 21 | - use of "$" for hexadecimal notation (2 bytes max) 22 | - use of "%" for binary notation (16 bits max) 23 | - use of ">" and "<" to point to MSB and LSB (2 bytes only) and "#>" and "#<" to reference MSB/LSB as values 24 | - display cycle count 25 | - ORG within code to define different starting addresses for modules 26 | - HEX pseudo-instruction to define HEX data and HEX fills. 27 | - ASC/STR pseudo-instructions to define text constants (with ASC, hi-bit is unset, while with STR, it is) 28 | - DCI/ADI/STI support to alternate the hibit of the last char 29 | - INV/FLS/BLK support for INVERSE/FLASH characters 30 | - syntax highlighting 31 | - linking of object modules (sheets) in the same workbook 32 | - global labels across the workbook 33 | - negative values are supported for decimal notation 34 | - error checking (highlighted in brown) 35 | - supports commented code lines 36 | 37 | 38 | 39 | ## Revisions 40 | ### v1.2.2 (Feb 20, 2021) 41 | - fixed some bugs that invited themselves in v1.2.x, most notably two LDX/LDY addressing and globals that were not working anymore 42 | - fixed some highlighting and cleaned the conditional formatting rules 43 | 44 | ### v1.2.1 (Feb 18, 2021) 45 | - now supports commented code lines 46 | - added ADI/DCI 47 | - added SDI 48 | - added INV/FLS/BLK 49 | - added "HEX bytes,n" support 50 | - added "HEX one_byte," support 51 | - moved Example in another workbook that will get updates only if needed 52 | 53 | ### v1.2.0 (Feb 15, 2021) 54 | - added linker 55 | - linker requires Libre Office v7.1.x or above (or MS Excel) 56 | - added globals labels page 57 | - removed more external links 58 | - little fixes here and there 59 | - removed Wagner's code samples 60 | - can reference ASCII values with ' or " 61 | - errors highlighting 62 | - added numerous examples in instructions_code sheet (used as a test sheet) 63 | 64 | ### v1.1.2 (Feb 8, 2021) 65 | - fixed instructions because columns were shifted with latest release 66 | - added syntax highlighting for SUBs 67 | - deleted Excel external link 68 | 69 | ### v1.1.1 (Feb 8, 2021) 70 | - fixed more bytes count for some opcodes 71 | - rewrote the byte count detection in order to avoid possible circular references 72 | - column D is now colored as comments, it's meant to be used as full-line comments 73 | - new official name "SpASM" ! 74 | 75 | ### v1.1 (Feb 6, 2021) 76 | - fixed bytes needed for some opcodes 77 | - added cycles display 78 | - added support of ORG in the middle of the code 79 | - added support for #label and #