Skip to content

Point somewhere else

Use eight stored directions to rotate a triangle without moving it.

  • Sinclair ZX Spectrum
  • BASIC

Turn the ship without moving it. O turns anticlockwise, P turns clockwise, and eight turns bring the nose back to its starting direction.

We will calculate eight versions of the triangle once, store their offsets, and choose one each time we draw. There are several arrays in this change, but they all describe the same eight headings.

A direction has two parts

A pair of numbers can describe a direction: how far right and how far up. We call that pair a vector. East is (1,0); north is (0,1); west is (−1,0).

SIN and COS give the vertical and horizontal parts of a direction from an angle. Spectrum BASIC measures these angles in radians: a full turn is 2*PI. Dividing that turn into eight gives PI/4 per step.

Heading h Angle Direction
1 0 East
2 PI/4 Northeast
3 PI/2 North
4 3*PI/4 Northwest
5 PI West
6 5*PI/4 Southwest
7 3*PI/2 South
8 7*PI/4 Southeast

The calculation (i-1)*PI/4 makes the first entry start at zero. a(i) holds COS angle; b(i) holds SIN angle. Together they give a vector of length one, apart from the small approximation in the computer’s arithmetic.

Build the corners from that direction

Imagine an arrow forward along the nose and another arrow left at a right angle to it:

Code
             left = (-b,a)
                 ^
                 |
              centre ----> forward = (a,b)

This diagram shows the arrows when the ship faces east. They rotate together.

The nose sits five forward. Each rear corner sits three backwards, then three to one side:

Corner Construction Horizontal Vertical Stored in
Nose 5 forward 5*a 5*b c,d
Left rear −3 forward + 3 left −3a−3b −3b+3a e,f
Right rear −3 forward − 3 left −3a+3b −3b−3a g,j

Facing east, a=1 and b=0. These produce (5,0), (−3,3) and (−3,−3), our original triangle.

Facing northeast, a and b are both about .7071. Before rounding, the nose is about (3.5355,3.5355), the left rear (−4.2426,0), and the right rear (0,−4.2426). Rounded for drawing, those become (4,4), (−4,0) and (0,−4). The shape now points diagonally.

Store eight answers

DIM a(8) reserves eight numeric entries, addressed as a(1) through a(8). We use eight arrays as columns of one table: a/b for direction, c/d for the nose, e/f for one rear corner and g/j for the other. At heading h, every lookup uses that same h.

FOR i=1 TO 8 and NEXT i fill the entries in order. We round the corner offsets but keep a/b fractional, ready to use them for thrust later. A Box That Holds Many explores indexed collections further; here our fixed-size BASIC arrays use parentheses.

Delete lines 20, 30. Enter each unwanted line number on its own to remove it.

Replace lines 10, 1000, 3010, 3020, 3030, 3040.

Add lines 100, 110, 200, 210, 220, 230, 240, 250, 260, 270, 340, 350, 370, 1010, 1020, 1060, 1070, 1080, 7000, 7010, 7020, 7030, 7040, 7050, 7060, 9000.

changes.bas
  10 GO SUB 7000
 100 LET x=48: LET y=56: LET h=2: LET steps=0
 110 GO SUB 1000: GO SUB 3000
 200 LET k$=INKEY$
 210 IF k$="q" OR k$="Q" THEN GO TO 9000
 220 IF k$="r" OR k$="R" THEN GO TO 100
 230 GO SUB 3000
 240 IF k$="o" OR k$="O" THEN LET h=h+1
 250 IF k$="p" OR k$="P" THEN LET h=h-1
 260 IF h=9 THEN LET h=1
 270 IF h=0 THEN LET h=8
 340 LET steps=steps+1
 350 GO SUB 3000
 370 GO TO 200
1000 BORDER 0: PAPER 0: INK 7: CLS
1010 PRINT AT 0,2; INK 5;"DRIFT";AT 0,19; INK 7;"HEADING"
1020 PRINT AT 1,2;"Point somewhere. Stay here."
1060 PRINT AT 19,2; INK 5;"O/P turn"
1070 PRINT AT 20,2; INK 7;"Eight headings. One position."
1080 PRINT AT 21,2; INK 5;"R retry              Q quit";
3010 PLOT INK 7; OVER 1;px+c(h),py+d(h)
3020 DRAW INK 7; OVER 1;e(h)-c(h),f(h)-d(h)
3030 DRAW INK 7; OVER 1;g(h)-e(h),j(h)-f(h)
3040 DRAW INK 7; OVER 1;c(h)-g(h),d(h)-j(h): RETURN
7000 DIM a(8): DIM b(8): DIM c(8): DIM d(8): DIM e(8): DIM f(8): DIM g(8): DIM j(8)
7010 FOR i=1 TO 8: LET angle=(i-1)*PI/4
7020 LET a(i)=COS angle: LET b(i)=SIN angle
7030 LET c(i)=INT (5 *a(i)+.5): LET d(i)=INT (5 *b(i)+.5)
7040 LET e(i)=INT (-3 *a(i)-3 *b(i)+.5): LET f(i)=INT (-3 *b(i)+3 *a(i)+.5)
7050 LET g(i)=INT (-3 *a(i)+3 *b(i)+.5): LET j(i)=INT (-3 *b(i)-3 *a(i)+.5)
7060 NEXT i: RETURN
9000 PAPER 0: INK 7: PRINT AT 21,0;"Finished. RUN to try again.     ";: STOP

RUN the changed program.

Complete source at this checkpoint
drift.bas
  10 GO SUB 7000
 100 LET x=48: LET y=56: LET h=2: LET steps=0
 110 GO SUB 1000: GO SUB 3000
 200 LET k$=INKEY$
 210 IF k$="q" OR k$="Q" THEN GO TO 9000
 220 IF k$="r" OR k$="R" THEN GO TO 100
 230 GO SUB 3000
 240 IF k$="o" OR k$="O" THEN LET h=h+1
 250 IF k$="p" OR k$="P" THEN LET h=h-1
 260 IF h=9 THEN LET h=1
 270 IF h=0 THEN LET h=8
 340 LET steps=steps+1
 350 GO SUB 3000
 370 GO TO 200
1000 BORDER 0: PAPER 0: INK 7: CLS
1010 PRINT AT 0,2; INK 5;"DRIFT";AT 0,19; INK 7;"HEADING"
1020 PRINT AT 1,2;"Point somewhere. Stay here."
1030 PLOT INK 1;15,24: DRAW INK 1;0,128: DRAW INK 1;225,0: DRAW INK 1;0,-128: DRAW INK 1;-225,0
1060 PRINT AT 19,2; INK 5;"O/P turn"
1070 PRINT AT 20,2; INK 7;"Eight headings. One position."
1080 PRINT AT 21,2; INK 5;"R retry              Q quit";
1090 RETURN
3000 LET px=INT (x+.5): LET py=INT (y+.5)
3010 PLOT INK 7; OVER 1;px+c(h),py+d(h)
3020 DRAW INK 7; OVER 1;e(h)-c(h),f(h)-d(h)
3030 DRAW INK 7; OVER 1;g(h)-e(h),j(h)-f(h)
3040 DRAW INK 7; OVER 1;c(h)-g(h),d(h)-j(h): RETURN
7000 DIM a(8): DIM b(8): DIM c(8): DIM d(8): DIM e(8): DIM f(8): DIM g(8): DIM j(8)
7010 FOR i=1 TO 8: LET angle=(i-1)*PI/4
7020 LET a(i)=COS angle: LET b(i)=SIN angle
7030 LET c(i)=INT (5 *a(i)+.5): LET d(i)=INT (5 *b(i)+.5)
7040 LET e(i)=INT (-3 *a(i)-3 *b(i)+.5): LET f(i)=INT (-3 *b(i)+3 *a(i)+.5)
7050 LET g(i)=INT (-3 *a(i)+3 *b(i)+.5): LET j(i)=INT (-3 *b(i)-3 *a(i)+.5)
7060 NEXT i: RETURN
9000 PAPER 0: INK 7: PRINT AT 21,0;"Finished. RUN to try again.     ";: STOP

The ship starts facing northeast. Hold O or P long enough to see a change, then release it. This program polls INKEY$: each pass reads the key held at that moment. A short tap can fall between reads, while a long hold can turn several times. SPACE does nothing yet. R restores the starting heading; Q returns to BASIC.

Keyboard notes: the new functions

Press CAPS SHIFT and SYMBOL SHIFT together for extended mode, then Q for SIN, W for COS, M for PI or R for INT. In keyword mode, D enters DIM. Array names and the ordinary variables following it are typed as letters. The spaces shown around tokens in the listing are for readability.

Follow the loop

k$ is a string variable: the dollar sign marks it as text. INKEY$ supplies a pressed character, or an empty string when no key is held. IF tests a condition; when it is true, the statements after THEN on that line run. OR lets either of two comparisons match, so both lower- and upper-case letters work. GO TO 200 returns to the next key read.

The steps variable increases on each pass. It is an update counter we will retain during flight, even though this checkpoint does not display it.

Erase before turning

Line 230 draws the old triangle again to erase it. Lines 240–270 change h and wrap it: 9 becomes 1, and 0 becomes 8. Line 350 draws with the new offsets. The ship’s x/y never changes.

The drawing routine still joins three corners, but now subtracts the coordinates of one corner from the next to obtain each DRAW movement. If turning leaves fragments, check those subtractions and make sure erasure happens before h changes.

Why is table entry 4 northwest rather than west?

Show the explanation

The first entry is already east at angle zero. Entry 4 has made three eighth-turns: 3*PI/4, or 135 degrees. West is the next entry, at PI.

Sources

Steven Vickers, edited by Robin Bradbeer, ZX Spectrum BASIC Programming, second edition (Sinclair Research, 1983), chapter 10, chapter 12, chapter 17. Chapter 10 explains radians and SIN/COS; chapter 12 describes DIM and array subscripts. The forward-and-sideways construction above defines our ship.

Run it here

This unit's program, built for you.

Program
drift.tap
Machine
ZX Spectrum 48K (PAL)

Download drift.tap