SAM A to Z
I've been dabbling in programming for over 10 years, and have had a SAM right from the start. Whenever I need some information, such as a system variable or short routine, I look it up and scribble it onto a bit of paper. Well, recently I got so snowed under with scraps of paper that I shoved them all into a big untidy pile, and this article is really just a compilation of that litter! Obviously you'll need to alter things such as the variables' names in program segments, and I certainly can't guarantee that it's all correct or is the best way of doing things, but hopefully some of it may be of some use to some of you!
* A is for Arrays
You can put multiple array definitions in a list, in the same way as you do for multiple LETs e.g. DIM a$(5,100),b$(6),c(5,5) (yeah, I know, a minor point!). When moving parts of large string arrays around, use MEM$ to POKE things rather than having slow loops e.g.: For array a$(100,100), remove a$(l) and move the lines above it down with:
LET a=LENGTH(0,a$):POKE a+(l-1)*100,MEM$(a+l*100 TO a+100*100-1)+STRING$(100," ")
To insert a line in the same array:
POKE a+(l-1)*100,STRING$(100," ")+MEM$(a+l*100 TO a+99*100-1)
The above examples don't allow for l=100, and always be careful when moving bytes upwards in memory with POKE and MEM$ - unless you force BASIC to make a temporary copy of the memory you're moving (by involving some form of string manipulation) it may overwrite itself as it's being copied directly, resulting in repetitive copying of the first part.
* B is for Border
To store the current border colour use something like LET b=PEEK SVAR 587 , then when you've finished messing about with it you can put it back with POKE SVAR 587,b: OUT 254,b .
Unfortunately the SAM's ROM has its fair share of these, but luckily it's possible to write quite substantial BASIC programs if you're very careful. I've had BIG problems with things such as programs behaving strangely across page boundaries, variables going haywire, and whole programs suddenly sulking and refusing to work for no apparent reason. I don't know very much about the ins and outs of the ROM, so I'm not claiming to know what I'm doing works for certain, but I now follow these points when writing large programs:
1. Don't have any bits of program crossing a page boundary (such as at 32767-32768) e.g. avoid having a DO on one side and a LOOP on the other, and don't let any procedures cross. Bung in a big REM to cover the 'danger area' (use MEM$ or something to look at the relevant area of memory to check it's OK).
2. Use CLEAR before adding/removing anything. I use a small procedure to do this and set the display after test-RUNning:
DEF PROC w: MODE 3: CLS #: CSIZE 8,8: CLEAR: STOP: END PROC
3. Watch out for local variables going haywire!
4. When you start a program, place a REM line at the end of it (e.g. 65000 REM The End!) and leave it permanently at the end of the program.
If you've not had any trouble then ignore all that! According to Dr. Wright, if you use the latest versions of MasterBASIC and MasterDOS then the bugs are cured, and even programs designed to run without either will benefit from being written and modified using them (although I sometimes still seem to have to follow point 4 to avoid hassles).
* C is for Character Set
UDG " " gives the location of the character set (which is normally 20880), so that's where you can load a different one to. Before doing so, your program should (if you want a little gold star in your exercise book) store the existing character set e.g. in a string with LET char$=MEM$(UDG " " to 767+UDG " "), then put it back later with POKE UDG " ",char$ (if you're using sets saved straight from FLASH! or some other set longer than 96 characters then you'll have to store a longer string).
* D is for Default Drive
MasterDOS has things like DIR * to make life easier, but with SAMDOS you're stuck if you even just want a detailed directory of the current default drive. Luckily, SVAR 7 holds this useful number, so use DIR PEEK SVAR 7 and your program will be compatible with both.
* E is for ESCape Key
Disable: POKE SVAR 321,1 Enable: POKE SVAR 321,0
* F is for Fred!
For your masterpiece to be usable for Fred, it simply needs to be small enough (generally under 70K I think, and I'm sure they'd prefer it to not be distributed amongst dozens of small files), compatible with SAMDOS (unless it's of special use for MasterDOS or MasterBASIC users), and of interest to people in some way! Don't leave your programs all lonesome on a disk in a dusty drawer, send them in!
* G is for Games Master
Games Master doesn't let you avoid the technicalities of programming, I'm afraid; you can't just easily make a game from pre-designed 'templates'. It is very versatile though, despite a few problems... Watch out for dodgy collision-detection - you may find that the player's sprite can't be killed if it's pushed against an obstacle! This is because although the player sprite may appear to be obstructed, for sprite collisions it's overlapping with the block (the position hasn't been corrected yet), so if you have a player sprite with a Y-speed of 8 pushing up against a block then its bottom 8 pixel rows are immune. Generally this isn't a problem with slow-moving sprites, but can be disastrous for jerkier movement. If you use an enclosing block around the edge of the screen then moving left continuously will take the sprite's X coord off the left of the screen and make the whole sprite invincible! (I've seen several GM games where you can do that) In that situation, use the every-cycle module to restrict coords rather than relying on block detection.
Unfortunately, SCLEAR doesn't properly erase multiple sprite copies from the screen, so you just have to either KILL them off individually or use SCLEAR then redraw the screen. Perhaps these bugs (and more!) are an intentional attempt to accurately simulate real machine-code programming...?
Allowing the user to quit a GM game isn't all that straightforward - if you allow ESC and F9 to operate normally then your pride and joy can be broken into, but with them disabled you can't end the program. The best solution is to disable exits from the game, then define a spare key as, say, F9. Have a player-controlled sprite on the title-screen (it can be invisible, or a non-moving logo) which has a module for that key (or have a module for that key for an in-game sprite if you want to quit from within the game). The module should contain:
If your GM game is one where new sprites occasionally arrive on the scene, you might find everything gets very slow if a lot of them happen to be around at once. If the program uses the COUNT function to add up how many exist (perhaps multiplying the numbers of larger sprites by 2 or 3 to represent their greater share of processor time) to give a grand total, you can then stop churning out more baddies if this total exceeds a certain maximum, and hopefully avoid sudden attacks of slow-motion!
* H is for Heap Space
The system heap starts at 16384, and is mainly intended to be a cosy home for short machine code routines e.g. interrupt-handlers. To avoid overwriting existing code, your program should reserve space (see the technical manual for details), but if you don't want the hassle of sorting that out, and the program doesn't use very much BASIC, then rather than wiping out anything already properly installed (e.g. a mouse driver) just have RAMTOP low enough and put the routine between RAMTOP and 32768 (it'll be in the same page and so there's no added difficulty in the coding). There's some buffer space at 20224 (the standard mouse driver loads to there before installing itself into the system heap), but I'm not sure how safely code can be left there indefinitely!
* I is for Interrupt-Disabling
You can actually disable interrupts and still continue a BASIC program (with restrictions though, of course!).
Disable: DPOKE 20224,51699: CALL 20224
Enable: DPOKE 20224,51707: CALL 20224
It generally only improves speed by a few % or so (see Screen Off).
* J is for Joystick Keys
To read diagonals, LET j=255-IN 61438, then
IF j BAND 16 THEN left
IF j BAND 8 THEN right
IF j BAND 4 THEN down
IF j BAND 2 THEN up
IF j BAND 1 THEN fire
or use something like LET x=x+((j BAND 8)=8)-((j BAND 16)=16) For the other joystick use IN 63486 and reverse the order of the BAND numbers (so that it's j BAND 1 for left, and so on). Unfortunately, pressing lots of keys at once on both sets of keys wreaks havoc (on my SAM it does, anyway!).
...and Justified PRINTing
Don't you just hate wading through things like DATA shifting text about so that it looks tidy on the screen? This procedure will PRINT a string so that no words are split (unless they're longer than the screen width!), i.e. left-justified, and you may want to modify it for right-justification, centering etc.. You can set the margins with WINDOW, it works with any mode, and assumes the current PRINT position is at the leftmost column of the window.
DEF PROC jp p$
LET m=1+(PEEK SVAR 56)-PEEK SVAR 57
IF LEN p$>m
LET p$=p$+" ": DO: LET z=m+2
DO: LET z=z-1: LOOP UNTIL p$(z)=" " OR z=1: LET z=z+(m-1)*(z=1)
PRINT p$( TO z-(z=m+1)): LET p$=p$(z+1 TO )
LOOP UNTIL LEN p$<m+2: LET p$=p$( TO LEN p$-1)
* K is for KEYIN
Although this command is bug-ridden and so can't reliably be used to make self-modifying programs, it can be useful for having SAMBASIC command-line input within programs, e.g.:
10 DEF PROC command
20 CLS: PRINT "SAMBASIC command-line"
30 PRINT "Return empty line to leave"
40 PRINT "*WARNING* Commands are unrestricted!"
50 INPUT "Command>";LINE c$: IF c$="" THEN GOTO 70
60 ON ERROR GOTO 50: KEYIN c$: GOTO 50
70 ON ERROR STOP
80 END PROC
To empty the keyboard buffer use CALL 358 . The following routines read the keyboard and mouse. Pressing both mouse buttons is the same as pressing neither - this is for simplicity (both BUTTONs seem pressed without a mouse driver installed).
To detect if anything's being pressed (returns 1 or 0): DEF FN anykeys=INKEY$<>"" OR ABS (BUTTON 1-BUTTON 3) Empties buffer and waits until nothing is pressed: DEF PROC nokey:CALL 358:DO:LOOP UNTIL NOT FN anykeys:END PROC The final routine takes a keypress from the buffer if there is one, or waits for something to be pressed if it's empty. It returns the character CODE in k, or a value of 1 for either mouse button.
DEF PROC getkey
DO: LET k=ABS (BUTTON 1-BUTTON 3): LOOP UNTIL k OR 32 BAND PEEK SVAR 571: IF NOT k THEN LET k=PEEK SVAR 520: POKE SVAR 520,0: POKE SVAR 571,223 BAND PEEK SVAR 571
Remember to POKE SVAR 618,0 first if you don't want capitals!
* L is for LIST
Don't use LIST FORMAT 1 or LIST FORMAT 2 with MODE 3 because you're likely to get a crash eventually (I think it may be OK with MODE 4, but I don't know).
* M is for MERGE
Don't use MERGE if you're just trying to LOAD some BASIC without it auto-RUNning - LOAD "filename" LINE 65535 (or any big line number will usually do) is MUCH quicker with larger files.
The XMOUSE and YMOUSE variables can be modified by DPOKEing SVAR 406 and POKEing SVAR 408 respectively. The mouse driver routine installs itself regardless of whether or not there's a mouse driver already in place. This is unlikely to cause problems, but many programs (especially demos) need it to be deactivated, and repeated installation will use up heap space, so I use the following routines to make it 'hibernate' by utilising the fact that a mouse vector value which is less than 256 is ignored. (mouse$ holds the mouse driver - MERGE it into memory and extract it, or replace the POKEs and CALLs with LOAD "mdriver" CODE if you prefer)
IF (DPEEK SVAR 252)=0 THEN POKE 20224,mouse$: CALL 20224: ELSE IF (DPEEK SVAR 252)<256 THEN IF DPEEK (16383+DPEEK SVAR 252)=64475 THEN DPOKE SVAR 252,16383+DPEEK SVAR 252: ELSE POKE 20224,mouse$: CALL 20224
IF (DPEEK SVAR 252)>16383 AND (DPEEK SVAR 252)<16639 THEN DPOKE SVAR 252,(DPEEK SVAR 252)-16383
(If you want to make sure the mouse driver is disabled even if it can't be hibernated then put :ELSE DPOKE SVAR 252,0 on the end) Those routines will only install the driver if there isn't one there, or reactivate a hibernating one, and afterwards hibernate the driver if possible (if something else has previously installed enough stuff it's not possible).
* N is for NMI/Break Button
Disable: DPOKE SVAR 224,0 Enable: DPOKE SVAR 224,7326
* O is for ON ERROR
This command is easily used not only to avoid error messages but also to let the program know what's happened. For example, to attempt to load a file:
1000 LET diskerr=1: ON ERROR GOTO 1010: LOAD "filename" CODE: LET diskerr=0
1010 ON ERROR STOP: IF diskerr THEN oops
The variable diskerr only gets set to 0 (which signifies everything's OK) if the program isn't forced to line 1010 by an error. ON ERROR STOP restores error messages, and "oops" would be an error-handling routine that slaps the user's wrist or whatever.
* P is for Palette
If you need to know the current PALETTE values, you can find them in tables at 21976 and 21996. These two sets are flashed between (speed of flashing is at SVAR 8) but are initially identical so you only see that if you start creating differences with POKEs or PALETTE position,colour1,colour2 . As well as reading values, you can POKE a whole palette there as a string to quickly change all the colours (the table values are used to set the colours at the start of every frame). The 4 bytes following each table are used as a store for MODE 3's palette when not in MODE 3, or for the first 4 MODE 1/2/4 palette positions when in MODE 3.
The line-interrupt palette changes are stored at 22016, with enough room for 127 changes. The data is arranged as follows: line for 1st change, palette position, colour1, colour2, line for 2nd change, palette position, colour1, colour2,... ..., 255 The line number is the one above where the palette change occurs, and goes from 0 at the top down to 191. The two colours will be identical unless flashing colours are required. The line numbers must be in ascending order (i.e. down the screen), and 255 is the end marker. For example, the data to make position 3 change to dark blue at the top of the main screen area and then go to bright white at the bottom would be: 0,3,1,1,191,3,127,127,255 A line byte value of 191 can't be set with the PALETTE...LINE command (190 is the maximum) but works when POKEd in. If you're adding or removing data with POKEs, make sure the table has an end-marker at all times (put the new one in place before overwriting the old one, or just POKE 22016,255 , mess about as much as you like, then finally POKE 22016 with the first line number).
..and PUT Strings
PUT strings consist of CHR$ 0, followed by a character for the width (in double-pixels, or sets of four MODE 3 thin pixels) then one for the height in pixels. The graphics data is next, starting at the top-left and working across each row in turn. So, an 8x8 pixels GRABbed graphics string will consist of CHR$ 0, CHR$ 4, CHR$ 8 and 32 characters/bytes of graphics data, making a total of 35. If you are storing large numbers of GRABbed graphics of the same size in memory you can omit the first 3 bytes and simply add them on when they're required for PUT.
* Q is for Quitting
All good things must come to an end... and it's better if the user can quit without having to reach for the reset button. If you've used multiple screens, do SCREEN 1: DISPLAY 0 then CLOSE all the others. To get the display back to defaults:
MODE 4: CLS #: CSIZE 8,9: WINDOW: FATPIX 0: LET XOS=0,YOS=0,XRG=256,YRG=192: SCROLL RESTORE: BLOCKS 1
If you've set any protection (see NMI/Break Button and ESCape Key) turn it off, and you may want to deactivate the mouse driver if you've used it (see Mouse). The default memory setup is 4 pages OPEN and a RAMTOP of 81919. If your program is smallish and uses RAMTOP lower than this, you can probably just do OPEN TO 4: CLEAR 81919 . For a higher RAMTOP use CLEAR 81919: OPEN TO 4 , but if the program is too big to fit under that then you'll have to put the quit routine towards the start and do DELETE suitable-line-number TO to make room first. Finally, use ON ERROR NEW: BOOT to hop out into any menus/front-ends there may be.
* R is for Rectangle
This procedure quickly draws a solid rectangle in MODE 3/4:
DEF PROC rect p,x,y,w,h LET q=PEEK SVAR 72: PAPER p: SCROLL 2,h,x,y,w,h: POKE SVAR 72,q :END PROC
p=colour of rectangle, x&y=top left coords, w&h=width and height. x and w get rounded down to even numbers, and with MODE 3 and FATPIX 0 only x is in thin pixels. q is used to restore the current PAPER.
...and Restore DATA-Pointer
If you want to switch between different bits of DATA, or are using DEF PROC procname DATA (which changes the DATA pointer), the following routines might come in handy:
DEF PROC pushptr
DEFAULT dataptr$="": LET dataptr$=dataptr$+MEM$(SVAR 138 TO SVAR 140)
DEF PROC popptr
POKE SVAR 138,dataptr$(LEN dataptr$-2 TO ) : LET dataptr$=dataptr$( TO LEN dataptr$-3)
The variable dataptr$ is used as a stack - you push the current DATA position onto it then pop it back off later. Like any other stack, your pops must balance your pushes otherwise the string will either keep growing or be emptied, resulting in an error. Also, bear in mind that if anything alters the memory location of the program any stacked positions will be incorrect.
* S is for Screen Off
The screen blanks out after around 22 minutes without a keypress...
Disable: POKE SVAR 50,1
Enable: POKE SVAR 50,0
Without the display the processor runs faster and 'smoothly', and so you may want to turn the screen off for speed or to allow sampled sound to play properly...
Screen off: POKE SVAR 587, 128 BOR PEEK SVAR 587:
OUT 254, PEEK SVAR 587
Screen on: POKE SVAR 587, 127 BAND PEEK SVAR 587:
OUT 254, PEEK SVAR 587
(Only works with MODEs 3 & 4)
...and Striped PEN & PAPER
For MODE 4, SVAR 72 holds PAPER+PAPER*16 i.e. it has the current PAPER value in both 'nibbles' (half a byte), so for PAPER 4 it's 68. SVAR 73 applies to PEN in the same way. You can get striped PEN and PAPER by making each nibble hold a different value e.g. striped 2 and 6 (normally red and yellow) can be either 98 or 38 depending on which stripe is on which side. MODE 3 is similar but uses pairs of bits, and so you have four stripes to play with instead of two (try MODE 3: POKE SVAR 72,27: CLS ).
* T is for Time
At SVAR 632 there are 3 bytes which count the number of 50-per-second frames which have elapsed. SVAR 632 is the 'least significant', so the total= (PEEK SVAR 632)+256*(DPEEK SVAR 633), but because changes may have occurred in the gap between the PEEKs you instead have to put them into variables then re-read the first value e.g.: LET t1=PEEK SVAR 632,t2=DPEEK SVAR 633,t3=PEEK SVAR 632
IF t3<t1 THEN LET t1=t3
OR use PAUSE 1: LET t=(PEEK SVAR 632)+256*(DPEEK SVAR 633) so that it's all done safely between the start of frames.
SAMBUS owners have a clock chip which keeps on ticking away. From MasterDOS you can read it with special commands, but for SAMDOS compatibility you can use the following ports to read/write values (use 15 BAND IN xxxx to mask off unwanted bits):
Reading Tens port Units port
Year 45295 41199
Month 37103 33007
Day 28911 24815
Hour 20719 16623
Minutes 12527 8431
Seconds 4335 239
To detect whether or not there's a clock chip connected, see if DATE$="00/00/00" or look for impossible values if using the ports listed above.
* U is for USING$
This is a function for formatting the display of numbers. Unfortunately it's only available with MasterBASIC, but (yes, you guessed it) here's an alternative that'll work with good ol' standard SAMBASIC:
DEF FN i(n,p)=INT ((INT (0.5+(ABS n)*10^p))/10^p)
DEF FN r(n,p)=((INT (0.5+(ABS n)*10^p))/10^p)-FN i(n,p)
DEF FN using$(n,o,p)=(" "+("-" AND n<0)+STR$ FN i(n,p))(9+(n<0)+LEN STR$ FN i(n,p)-o TO )+("." AND p)+(STR$ FN r(n,p)+"00000")(3-2*((LEN STR$ FN r(n,p))=1) TO 2*((LEN STR$ FN r(n,p))>1)+p)
(8 spaces in the quotes) I know it looks monstrous, but it works! The functions i and r simply serve the main function using$(n,o,p). n: the number, which must be more than -10000000 and less than 10000000.
o: number of characters before the decimal point, in the range 0-9 (which just about accommodates -9999999.999999 if it gets rounded to -10000000!).p: number of decimal places, from 0-5, with no decimal point for 0. Decimals which are 'trimmed' are rounded up/down as appropriate.
e.g. PRINT "£";FN using$(23.475,3,2) produces
and PRINT "$";FN using$(-5894.67,8,3) produces
With larger numbers you may find some corruption of lower digits, which is due to the inaccurate way the computer handles the calculations, but other than that it should be fine.
* V is for VMPR
The Video Memory Page Register at port 252 controls the screen page and MODE. Bits 0-4 are for the screen page, and 5 and 6 are for the MODE.
Location of currently-displayed screen= ((IN 252 BAND 31)+1)*16384
Screen MODE= 1+(96 BAND IN 252)/32
* W is for WINDOW-Extending
You can use the WINDOW command to create lots of professional-looking effects, but it's irritating that you normally can't include the editing area. To avoid having to write separate routines for PRINTing things there, use SVAR 59 to set the lower limit. So, for CSIZE 8,8 and MODE 4, use WINDOW: POKE SVAR 59,23 to get full-screen PRINTing.
* X is for X-Rated
I couldn't think of anything useful beginning with X, so I thought I'd get a few sad, inadequate individuals to head straight here from the contents page! <evil cackle>
* Y is for YOS
Combine this with the WINDOW trick described earlier to at last get PRINT and PLOT axes along the screen edges without awkward lower regions (!). For a CSIZE height of 8 use LET YOS=-16, or LET YOS=-18 for a height of 9, and so on.
* Z is for Zzzzz...
...coz I'm going to get some sleep now...