!0.......^.........^.........^.. !B \H11\H07\H10\H02 SMOOTH MOVES \H11\H01 !2.......^.........^.........^.........^.........^.........^.... Does your Speccy output sometimes look like it's suffering from a bad case of the shakes? Relax ... let things slide with Simon Goodwin's cure for jerky graphics! !1.......^.........^.........^.........^........ Presented here is a short machine code routine that lets you move graphics smoothly around the screen, without suffering the restrictions of the Spectrum character grid. Graphics and ASCII characters can be positioned at any high res- olution coordinate with a simple Basic command. You're not restricted to the 704 'PRINT pos- itions' - rows zero to 21 and columns zero to 31 - you can place characters at any point on the 256 by 176 Hi-res grid. The program uses only 120 bytes of memory and is completely relocatable, which means that you can load it anywhere in memory. It works without problems on a 16K computer. Another advantage over the usual PRINT AT command is that this one allows you to use an extra ninety-odd user- defined graphics. In addition to the usual 21 user-defined graphics (character codes 144-164), you can define and position characters 165 to 255 with the YS Smooth Move routine. !0.......^.........^.........^.. !B INTO LOAD MODE !1.......^.........^.........^.........^........ The Basic listing loads the machine code into any area of memory. Type in the listing, taking care over the DATA statements, and then decide where you wish to store the code. On a 48K Spec- trum you might want to put the code at address 64500. Type CLEAR 64499 to tell Basic that it must not use addresses above 64499, and then RUN the program. You'll be asked for a "Load address" - enter 64500. The program reads the DATA and stores it from memory address 64500 onwards. If you've made a typing error in the DATA, an appropriate message will appear. Correct the error and RUN again. Don't test the routine until the "Position character ..." message appears; the code is then ready for use. It's a good idea to SAVE everything, just in case an error has slipped past the 'check' in the loader. On a 16K Spectrum (assuming you have no other machine code in memory) you might put the code at address 31670. Type CLEAR 31669 and then specify a "Load address" of 31670. Of course, you can load the code anywhere you like, although it's best to protect it with the CLEAR command, or it could be overwritten by Basic. The routine provides a Hi-res version of the command: !0.......^.........^.........^.. PRINT INK 8; PAPER 8; OVER 1; AT y,x;CHR$ c !1.......^.........^.........^.........^........ The syntax is rather different: !0.......^.........^.........^.. RANDOMIZE x AND y=c+USR a !1.......^.........^.........^.........^........ Where 'x' and 'y' are the horizontal and vert- ical coordinates of the top left corner of the character, 'c' is the ASCII code of the char- acter and 'a' is the address where you stored the routine. So: !0.......^.........^.........^.. RANDOMIZE 0 AND 175=65+USR 64500 !1.......^.........^.........^.........^........ will position a letter 'A' (character 65) at the top left corner of the screen, assuming you stored the machine code at address 645000. The RANDOMIZE is a 'dummy' to hold the result of the USR call. If your program uses random numbers you should replace RANDOMIZE with a dummy variable assignment, such as: !0.......^.........^.........^.. LET dummy=0 AND 175=65+USR 64500 !1.......^.........^.........^.........^........ You're allowed to specify coordinates or char- acter codes with expressions as well as vari- ables or numbers. For instance: !0.......^.........^.........^.. RANDOMIZE xpos+xdir AND ypos- ydir=CODE "*"+USR move !1.......^.........^.........^.........^........ is allowed. Our routine uses a neat technique to fetch the three previous expressions on the line, before the USR call. If there are more or less than three values, a 'Parameter' error will be reported. Make sure that you've used the correct separators - AND, equals and plus - between the coordinates, the character code and the USR call. If you're using calculations more complicated than addition, subtraction, multi- plication and division, you may need to put each coordinate or character code in brackets - so that the routine can distinguish them. The machine code contains extensive error trapping. It won't let you use y coordinates less than seven, since each character is eight lines high. A character at coordinates 0,6 would have its bottom line at y coordinate minus one! The "Integer out of range" error message appears if you use vertical coordinates less than seven or greater than 175. X coordinates beyond 248 simply "wrap around" to the opposite side of the screen; decimal values are rounded to the nearest whole number. !0.......^.........^.........^.. !B A SMOOTH OPERATOR !1.......^.........^.........^.........^........ The second Basic program shows the features of the routine quite clearly. A ball bounces around the screen at a variety of speeds. It's pos- itioned using "LET d=" rather than "RANDOMIZE" so that the random number sequence is not constantly re-started whenever the ball moves. The listing is fairly straightforward, but make sure you type commas (not semi-colons) in line 330. The machine code always uses the OVER 1 setting, so that any character can be erased without destroying the background, simple by re- drawing it in the same place. The characters take on the colour of the INK where they are plotted. This avoids the need for complicated code to save and restore colours, and prevents weird effects as objects pass one another. The Spectrum normally uses character codes 165 to 255 to represent keywords - words like THEN, PRINT and so on. There's never any need to zoom those around the screen, so our machine code program lets you define an extra 91 user- defined graphics in their place. Together with the 21 standard user-defined graphics, this gives you 122 characters to play with. !0.......^.........^.........^.. !B -------------------------------- LABELS |ADDRESS |COMMENT -------------------------------- | |SYSTEM POINTERS -------------------------------- UDGS | 5C7B |User graphics | |pointer CHARS | 5C36 |Character set | |pointer BLKCH | 5C92 |Block graphic | |buffer STACK | 5C65 |Maths stack end | |pointer STBOT | 5C63 |Maths stack | |start pointer -------------------------------- | |ROM ROUTINES -------------------------------- MAKEB | 0B38 |Make graphic | |in B PIXEL | 22AA |Find address of | |pixel POP_A | 2DA2 |Pop A from | |maths stack -------------------------------- !2.......^.........^.........^.........^.........^.........^.... The table above shows the system pointers and ROM routines used in Smooth Move, giving their labels and addresses. This is for the assembler's use only, and need not be typed in. !1.......^.........^.........^.........^........ !B In principle the new characters are defined in exactly the same way as the old ones. They follow the others in memory, which means that you will have to expand the user-defined graphics area before you can define more than the standard 21 characters. On a 48K computer you'd use the following commands to expand the graphics area to cope with 122 characters: !0.......^.........^.........^.. CLEAR 64559: POKE 23675,48: POKE 23676,252 !1.......^.........^.........^.........^........ The POKEs adjust the system variable UDG so that it points an extra 728 (91*8) bytes further down memory. They don't reserve memory for any machine code, so you'd probably use CLEAR 64499 and load the Smooth Move code at 64500 (or thereabouts). On a 16K computer load the machine code at 31670 and reserve space with: !0.......^.........^.........^.. CLEAR 31669: POKE 23675,48: POKE 23676,124 !1.......^.........^.........^.........^........ When you come to program the user-defined graphics, you POKE the patterns into memory as usual. The only difference is that you can't use the USR "letter" function to locate characters after USR "u". The extra characters still follow at eight-byte intervals. User-defined graphic "a" has character code 144, so you can find the definition of the character with code 'n' by typing: !0.......^.........^.........^.. PRINT USR "a"+8*(n-144) !B TRICKS OF THE TRADE !1.......^.........^.........^.........^........ Smooth Move uses some interesting machine code tricks, so I've listed the assembler code of the program as well as the Basic loader. This is the longest listing, assembled using version 2.1 of Picturesque's excellent EDITAS assembler. !0.......^.........^.........^.. 100 REM SMOOTH MOVE DEMO 110 REM By Simon N Goodwin 120 REM 130 REM Load code 140 CLEAR 30999 150 LOAD "Mover"CODE 31000 160 REM Set area 170 LET xmax=247 180 LET ymax=168 190 REM Set up positions 200 LET xpos=INT (RND*200) 210 LET ypos=17 220 LET xdir=INT (RND*6+1) 230 LET ydir=-INT (RND*5+1) 240 REM Define ball 250 RESTORE 260 FOR i=USR "a" TO USR "e" 270 READ d 280 POKE i,d 290 NEXT i 300 LET shape=144 310 INK 6: PAPER 2: BORDER 5 320 FOR i=0 TO 21 330 PRINT AT i,0, INVERSE 1, 350 NEXT i 360 GO TO 480 390 REM Move ball 400 LET oldx=xpos 410 LET oldy=ypos 420 LET xpos=xpos+xdir 425 IF xpos>1 THEN IF xpos7 THEN IF ypos13017 THEN PRINT "Er ror in DATA": STOP 280 PRINT "Position character c AT x,y with" 290 PRINT "RANDOMIZE x AND y = c +USR ";l 300 PRINT '"Save everything, ju st in case..." 310 SAVE "SMOOTH/BAS" 320 SAVE "SMOOTH/COD"CODE l,120 330 STOP 400 DATA 42,101,92,229,235,42 410 DATA 99,92,1,15,0,9 420 DATA 237,82,40,2,207,25 430 DATA 205,162,45,254,128,56 440 DATA 11,71,214,144,56,19 450 DATA 237,91,123,92,24,4 460 DATA 237,91,54,92,38,0 470 DATA 111,41,41,41,25,24 480 DATA 6,205,56,11,33,146 490 DATA 92,229,221,225,205,162 500 DATA 45,103,229,205,162,45 510 DATA 225,111,229,14,8,225 520 DATA 37,229,36,197,68,77 530 DATA 205,170,34,193,71,175 540 DATA 176,221,126,0,40,17 550 DATA 235,38,0,111,62,8 560 DATA 144,71,41,16,253,235 570 DATA 126,170,119,35,123,174 580 DATA 119,221,35,13,32,213 590 DATA 225,225,34,101,92,201 !2.......^.........^.........^.........^.........^.........^.... If you've not yet got hold of an assembler, here's a Basic loader program allowing you to load the Smooth Move. !1.......^.........^.........^.........^........ !B This gives us a convenient way of passing numbers from Basic to machine code, without the hassle of PEEKing and POKEing. We can ensure that temporary results are ready by our choice of separators between the coordinates. In the command: !0.......^.........^.........^.. RANDOMIZE x AND y=c+USR a !1.......^.........^.........^.........^........ Basic must do the addition first, to get the correct answer - adding is always done before comparison (=) and comparisons are done before AND. This idea is explained on page 12 of the thin "Introduction" manual which came with your Speccy. On the way through the expression, Basic works out any calculations needed to find x, y and c, since those calculations should have a higher priority than the 'plus' at the end of the line. If you want to use equals, AND, or other so- called "logical operations" in your calculation of x, y and c, you must put the relevant calculations in brackets, so that Basic will work out the whole value before it reaches the USR call. A list of priorities is on page 201 of the Spectrum manual. Logical operations have priority '2', '3', '4' or '5'. Basic puts temporary results in an area called the "maths stack". Two system variables are used to mark the top and bottom of this area; each value stored within it takes up five bytes. Lines 1340-1450 of the assembler are used to check that the maths stack is 15 bytes long when the USR call is reached; this means three values are ready. If the stack does not contain three values, the routine stops with a 'Parameter' error - this is generated by lines 1440 & 1450. !0.......^.........^.........^.. !B A CHARACTER STUDY !1.......^.........^.........^.........^........ The ROM subroutine POP_A is used to read a number from the maths stack and into the A register. Lines 1470-1560 fetch the character code, which is the last thing calculated and hence at the 'top' of the stack. They test the code to decide whether the character is a block graphic, a user-defined graphic or an ASCII character. There's no definition of the block graphics in the ROM - those are generated as required by a routine called MAKEB, which puts the pattern specified by a code in the B regis- ter at address BLKCH. In the case of ASCII or user-defined charac- ters, the routine finds the appropriate start address from the system variables (UDGS points to the user-defined graphics and CHARS points to the ASCII symbols). The character code is multi- plied by eight (since each definition takes eight bytes) and the location of the character is found by adding the start address to the resultant value. GFONT copies the address of the character definition into register IX, for safe- keeping. POP_A is used twice more to fetch the y and x coordinates where the character is to be displayed. The loop from PLINE onwards puts the character into video memory, one line at a time. Register C is used to count the lines. The program takes the coordinates of each line and uses a ROM call to find the address where that line should appear. The ROM subroutine named PIXEL takes x and y coordinates in regis- ters C and B, returning with the address of the byte required in HL and the position within the byte in A. The character definition is fetched and shifted sideways if need be. Each line of the Spectrum display corresponds to 32 bytes of video memory. The contents of that memory determines what's displayed on the line. The normal Spectrum PRINT routine uses one byte per character on each line, which means that characters cannot be printed partly in one byte and partly in the next. Smooth Move allows you to split characters between one byte and the next (hence the finer control over positioning). The character code is put into one end of the HL register pair, which is shifted sideways until it's at the required place on the boundary between H and L. The ADD Hl,HL instruction is used to shift the value - every time you add a binary value to itself it moves one place to the left, because each column has twice the value of the one to its right. At STORE the graphic line is mixed into the display with the XOR instruction - the machine code equivalent of PRINT OVER. The program loops back to PLINE until all eight lines of the character have been positioned. !0.......^.........^.........^.. !B TIDYING UP !1.......^.........^.........^.........^........ The routine can't return to Basic until both stacks - the processor stack and the maths stack - have been put back the way they were found. Line 2330 throws away the coordinate information which was on the processor stack. Finally, the value of the maths stack pointer is retrieved, so that Basic doesn't get confused by the sudden loss of three data items. The USR function returns the value zero, since B and C have both counted down to nothing. This program is only an introduction to Hi-res animation on the Spectrum. The best graphic routines handle large shapes, with automatic animation and motion, collision detection, and so forth. One day I might divulge the secrets of the YS Sprite System, which puts most of the power of a dedicated arcade machine at your fingertips - that's if I ever finish writing it ...! !2.......^.........^.........^.........^.........^.........^.... !B The assembler code for Smooth Move - just so that you can see the interesting machine code tricks that Simon's used. FDE8 1320 ORG 65000 1330 ; "Fetch end of stack" FDE8 2A655C 1340 MOVER LD HL,(STACK) FDEB E5 1350 PUSH HL 1360 ; "3 numbers on stack?" FDEC EB 1370 EX DE,HL FDED 2A635C 1380 LD HL,(STBOT) FDF0 010F00 1390 LD BC,15 FDF3 09 1400 ADD HL,BC FDF4 ED52 1410 SBC HL,DE FDF6 2802 1420 JR Z,FCODE 1430 ; "3 parameters needed!" FDF8 CF 1440 RST #08 FDF9 19 1450 DEFB 25 1460 ; "Find the char. code" FDFA CDA22D 1470 FCODE CALL POP_A 1480 ; "Divide into 3 groups:" 1490 ; " 0-127 ASCII chars" 1500 ; "128-143 block graphics" 1510 ; "144-255 user defined" FDFD FE80 1520 CP 128 FDFF 380B 1530 JR C,ASCII FE01 47 1540 LD B,A FE02 D690 1550 SUB 144 FE04 3813 1560 JR C,BLOCK 1570 ; "Must be a UDG" FE06 ED5B7B5C 1580 LD DE,(UDGS) FE0A 1804 1590 JR INDEX 1600 ; FE0C ED5B365C 1610 ASCII LD DE,(CHARS) 1620 ; "Find character form" FE10 2600 1630 INDEX LD H,#00 FE12 6F 1640 LD L,A FE13 29 1650 ADD HL,HL FE14 29 1660 ADD HL,HL FE15 29 1670 ADD HL,HL FE16 19 1680 ADD HL,DE FE17 1806 1690 JR GFONT 1700 ; FE19 CD380B 1710 BLOCK CALL MAKEB FE1C 21925C 1720 LD HL,BLKCH FE1F E5 1730 GFONT PUSH HL FE20 DDE1 1740 POP IX 1750 ; "Fetch Y coordinate" FE22 CDA22D 1760 CALL POP_A FE25 67 1770 LD H,A 1780 ; "Fetch X coordinate" FE26 E5 1790 PUSH HL FE27 CDA22D 1800 CALL POP_A FE2A E1 1810 POP HL FE2B 6F 1820 LD L,A FE2C E5 1830 PUSH HL 1840 ; "Process 8 screen lines" FE2D 0E08 1850 LD C,8 FE2F E1 1860 PLINE POP HL 1870 ; "Step up to next line" FE30 25 1880 DEC H FE31 E5 1890 PUSH HL FE32 24 1900 INC H 1910 ; "Convert coord in H,L" 1920 ; "to address in HL & A" FE33 C5 1930 PUSH BC FE34 44 1940 LD B,H FE35 4D 1950 LD C,L FE36 CDAA22 1960 CALL PIXEL FE39 C1 1970 POP BC 1980 ; "Copy bit offset to B" FE3A 47 1990 LD B,A 2000 ; "See if char is on grid" FE3B AF 2010 XOR A FE3C B0 2020 OR B 2030 ; "Read font anyway" FE3D DD7E00 2040 LD A,(IX+0) 2050 ; "Store NOW if on grid" FE40 2811 2060 JR Z,STORE 2070 ; "Generate 16 bit mask" FE42 EB 2080 EX DE,HL FE43 2600 2090 LD H,0 FE45 6F 2100 LD L,A 2110 ; "Reverse shift count" FE46 3E08 2120 LD A,8 FE48 90 2130 SUB B FE49 47 2140 LD B,A FE4A 29 2150 SHIFT ADD HL,HL FE4B 10FD 2160 DJNZ SHIFT 2170 ; "Put mask in DE" FE4D EB 2180 EX DE,HL 2190 ; "Mix into display" FE4E 7E 2200 LD A,(HL) FE4F AA 2210 XOR D FE50 77 2220 LD (HL),A FE51 23 2230 INC HL FE52 7B 2240 LD A,E FE53 AE 2250 STORE XOR (HL) FE54 77 2260 LD (HL),A 2270 ; "Advance through font" FE55 DD23 2280 INC IX 2290 ; "Count one line done" FE57 0D 2300 DEC C FE58 20D5 2310 JR NZ,PLINE 2320 ; "Tidy stacks; n.b. BC=0" FE5A E1 2330 POP HL FE5B E1 2340 POP HL FE5C 22655C 2350 LD (STACK),HL FE5F C9 2360 RET 2370 END !1.......^.........^.........^.........^........ !B -- from Your Spectrum #7 (Sep.1984) -- !$