Skip to content

Query and Change a Tile Map in Acorn Electron BASIC

Keep room data separate from its picture, with checked reads, edits, properties and world-to-cell conversion.

A wall is a rule as well as a picture. Keep that rule in a map and you can ask what is under a player without trying to recognise the picture on screen.

The caller prints an eight-by-six room, reads tile 2 at column 3, row 2, changes it to tile 3, then prints the changed room. The caller selects MODE 6. Lookup is ordinary BASIC arithmetic: it does not read pixels or assume the Electron has hardware tiles.

The maintained caller running through the machine’s stock BASIC ROM.
The maintained caller running through the machine’s stock BASIC ROM. Emu198x 0.44.1 capture.

Put it in a project

Copy routines.bas into unused line numbers, then add the initialisation from demo.bas before your game loop. If you renumber a routine, update every call to it. The arrays and variables belong to your program; none are local.

Complete room, query and edit example

demo.bas
5 MODE 6:VDU 23,1,0;0;0;0;
10 W=8:H=6:S=8:L=3
20 DIM M(W*H-1),P(L)
30 FOR I=0 TO 47:M(I)=0:NEXT I
40 FOR I=0 TO 7:M(I)=1:M(40+I)=1:NEXT I
50 FOR I=0 TO 5:M(I*8)=1:M(I*8+7)=1:NEXT I
60 M(19)=2:M(28)=3
70 P(0)=0:P(1)=1:P(2)=2:P(3)=4
80 Q=0:U=0:A=8:B=6
90 PRINT "BEFORE":GOSUB 1400
100 C=3:R=2:GOSUB 1000:GOSUB 1200
110 PRINT "TILE";T;" FLAGS";F
120 V=3:GOSUB 1100
130 PRINT "AFTER":GOSUB 1400
140 PRINT "DONE"
150 END
1000 E=0:T=-1
1010 IF C<0 THEN RETURN
1020 IF R<0 THEN RETURN
1030 IF C>=W THEN RETURN
1040 IF R>=H THEN RETURN
1050 IF C<>INT(C) THEN RETURN
1060 IF R<>INT(R) THEN RETURN
1070 D=R*W+C:T=M(D):E=1
1080 RETURN
1100 E=0
1110 IF V<0 THEN RETURN
1120 IF V>L THEN RETURN
1130 IF V<>INT(V) THEN RETURN
1140 GOSUB 1000
1150 IF E=0 THEN RETURN
1160 M(D)=V:T=V
1170 RETURN
1200 E=0:F=-1
1210 IF T<0 THEN RETURN
1220 IF T>L THEN RETURN
1230 IF T<>INT(T) THEN RETURN
1240 F=P(T):E=1
1250 RETURN
1300 E=0:C=-1:R=-1:T=-1
1310 IF S<=0 THEN RETURN
1320 IF X<0 THEN RETURN
1330 IF Y<0 THEN RETURN
1340 C=INT(X/S):R=INT(Y/S)
1350 GOSUB 1000
1360 RETURN
1400 E=0
1410 IF Q<0 THEN RETURN
1420 IF U<0 THEN RETURN
1430 IF A<1 THEN RETURN
1440 IF B<1 THEN RETURN
1450 IF Q+A>W THEN RETURN
1460 IF U+B>H THEN RETURN
1470 IF Q<>INT(Q) THEN RETURN
1480 IF U<>INT(U) THEN RETURN
1490 IF A<>INT(A) THEN RETURN
1500 IF B<>INT(B) THEN RETURN
1510 FOR K=U TO U+B-1
1520 FOR J=Q TO Q+A-1
1530 C=J:R=K:GOSUB 1000
1540 PRINT CHR$(48+T);
1550 NEXT J
1560 PRINT
1570 NEXT K
1580 E=1:RETURN

Set W/H to positive integer map dimensions, S to a positive integer tile size in world units and L to the highest permitted tile ID. Allocate and populate M()/P() before calling. Numeric array elements use interpreter storage, rather than one byte per tile. On this interpreter the array base is zero: the routine accesses M(D) for logical offset D.

The calls

Call Supply Result
1000: read C column, R row E=1, T=tile; invalid coordinates return E=0, T=-1
1100: change C/R, V tile ID Writes only if coordinates and ID are valid; E=1, T=V, otherwise E=0
1200: classify T tile ID E=1, F=property; invalid ID returns E=0, F=-1
1300: world position X/Y, S Floors X/S and Y/S to C/R, then performs the checked lookup
1400: show Q/U start, A/B size Prints a bounded text region at the current cursor

Use GOSUB (Spectrum: GO SUB) followed by the entry number. Read E immediately: every later call can replace it. Reads reject fractional cell coordinates; writes also reject fractional or out-of-range IDs. Non-negative fractional world positions are allowed. A negative position or non-positive S gives C=R=T=-1; a positive position beyond the map retains calculated C/R but returns E=0, T=-1.

Reusable map access and display routines

routines.bas
1000 E=0:T=-1
1010 IF C<0 THEN RETURN
1020 IF R<0 THEN RETURN
1030 IF C>=W THEN RETURN
1040 IF R>=H THEN RETURN
1050 IF C<>INT(C) THEN RETURN
1060 IF R<>INT(R) THEN RETURN
1070 D=R*W+C:T=M(D):E=1
1080 RETURN
1100 E=0
1110 IF V<0 THEN RETURN
1120 IF V>L THEN RETURN
1130 IF V<>INT(V) THEN RETURN
1140 GOSUB 1000
1150 IF E=0 THEN RETURN
1160 M(D)=V:T=V
1170 RETURN
1200 E=0:F=-1
1210 IF T<0 THEN RETURN
1220 IF T>L THEN RETURN
1230 IF T<>INT(T) THEN RETURN
1240 F=P(T):E=1
1250 RETURN
1300 E=0:C=-1:R=-1:T=-1
1310 IF S<=0 THEN RETURN
1320 IF X<0 THEN RETURN
1330 IF Y<0 THEN RETURN
1340 C=INT(X/S):R=INT(Y/S)
1350 GOSUB 1000
1360 RETURN
1400 E=0
1410 IF Q<0 THEN RETURN
1420 IF U<0 THEN RETURN
1430 IF A<1 THEN RETURN
1440 IF B<1 THEN RETURN
1450 IF Q+A>W THEN RETURN
1460 IF U+B>H THEN RETURN
1470 IF Q<>INT(Q) THEN RETURN
1480 IF U<>INT(U) THEN RETURN
1490 IF A<>INT(A) THEN RETURN
1500 IF B<>INT(B) THEN RETURN
1510 FOR K=U TO U+B-1
1520 FOR J=Q TO Q+A-1
1530 C=J:R=K:GOSUB 1000
1540 PRINT CHR$(48+T);
1550 NEXT J
1560 PRINT
1570 NEXT K
1580 E=1:RETURN

Reserve M()/P(), W/H/S/L, C/R/V/T/E/F/X/Y and scratch D/J/K/Q/U/A/B. Calls are synchronous and not re-entrant. Keep W/H consistent with the allocation; increasing them does not enlarge an array. Reading a stored tile does not certify its ID: property lookup checks that separately.

Why the arithmetic matters

Each complete row occupies W cells. That puts a cell at R*W+C. At column 3, row 2 in an eight-column map, the offset is 19. The array-base adjustment happens only after that calculation. Using H as the stride can look correct on a square map and fail when a level changes shape.

The reverse operation divides a world coordinate by the tile size and rounds down. World positions, tile positions and array subscripts are three different things. This is the sort of representation change explored in Maths for Games.

Change it and spot mistakes

Change the property table to make a new tile block movement, hurt the player or mark an exit. Enlarge P() and L together. Keep actors separate if their movement must leave the underlying floor intact. A map edit changes data; it does not redraw the screen.

A wrong stride shifts the room’s internal features. The retained wrong-stride caller demonstrates that symptom. A rejected write should leave every map cell unchanged; the checks read the whole map afterwards. Fix the index or bounds test instead of hiding the symptom by making the level square.

The text adapter uses IDs 0–3 as printable digits. For a separately includable renderer with a complete ID preflight, use bounded text regions. A text region is useful for debugging or a text-based room; graphical artwork needs a display adapter.

Deliberately wrong stride: the same logical request selects different source cells.
Deliberately wrong stride: the same logical request selects different source cells. Emu198x 0.44.1 capture.

Code and verification

Take the source and contract. Retained execution records identify the emulator, firmware, source and observers. Tests use a non-square map, bad coordinates, bad writes, world-to-cell boundaries and a deliberately wrong stride. Whole decoded captures are compared with independently specified outputs. This establishes those calls in the recorded configuration, not a complete game’s speed or original-hardware behaviour.

Array conventions: BBC BASIC II’s DIM and array conventions; compare the BBC Microcomputer User Guide, DIM entry.