Minijong
A 10-line TurboBASIC XL program for the Atari 8-bit computer
Written for 2014 NOMAM programming competition
Bill Kendrick, February 19, 2014
An entry for the NOMAM 2014 10-line TurboBASIC XL
game competition.
Gameplay
This is a tiny version of the game
Mahjong
solitaire
(like Shanghai),
played on a board 3-levels deep and 7x7 in size.
Use a joystick in port 1 to play. Using the flashing cursor,
select pairs of matching tiles. Tiles on the lowest level are darkest,
tiles on the topmost level are highest. You may only select a tile
if it is "free" — there is no other tile adjacent to it on
either the right and/or left sides.
If you attempt select a tile that isn't free, or you've selected
one tile, and then try to select a non-matching tile, a buzzer will sound.
If you select two matching tiles, they'll both disappear. Any tiles
underneath (on a lower level than) the removed tiles will be revealed.
Development
...coming soon...
It was developed for the NOMAM 2014 competition.
Line-by-line Breakdown of the Source Code
Set-up
10 GRAPHICS 2:CH=PEEK(106)-16:POKE 756,CH:CH=CH*256:POKE 559,0:FOR I=0 TO 7:POKE CH+I,85+(85*(I MOD 2)):NEXT I:POKE 711,15:FOR I=8 TO 240
- Switch to large colored text mode (20x12; each character is one of 4 colors, on the background color), with a text window
- Locate some space relative to
MEMTOP
to store character set (font) data (CH
)
- Point
CHBAS
at this memory
- Convert
CH
from a page to a physical memory location
- Disable the playfield, by setting
SDMCTL
to 0.
(This has a side effect of running the program a little faster, because the ANTIC graphics chip is not interrupting us.)
- Convert the space character (ASCII 32) into a checkerboard (▩) pattern
- Set
COLOR3
to bright white (15)
- Begin a loop (
I
) that will set the character set bitmaps for ASCII characters 33 through 60 with...
20 POKE CH+I,127-PEEK(57600+I):NEXT I:SC=DPEEK(88):L=83:DIM B(196),C(L),SZ(2):FOR I=0 TO L-1:C(I)=INT(I/4)+1:NEXT I:X1=-1
- ...in a loop for ASCII characters 33 through 60...
- Set the byte to inverse (except for the leftmost pixel) of the byte from the ASCII symbol 32 characters ahead of us in the Atari OS ROM character set data (at page 224, or memory location 57344)
(e.g., ASCII 33 (!
), which is stored in screen memory as a "1" byte, will contain a mostly-inverse-video version of ASCII 65 (A
))
- Find the start of screen memory (
SAVMSC
) and store it (SC
)
- Set the length of how many symbols we'll need to place on our board. The board is 3 levels tall, and, from the bottom, 7x7, 5x5 and 3x3 tiles in size; that's 83. (
L
)
Yes, I realize that's an odd number, and Mah Jongg solitaire requires an even number of tiles to be completely winnable. Sorry!
- Set aside (via
DIM
) space in some arrays for:
- the board (
B()
) (which must be 7x7x3; whoops, I have 7x7x4 allocated)
- a pool of tiles to place on the board (
C()
) (we'll randomly pick from it)
- locations on the board for the 1st and 2nd picked tiles (in terms of screen memory locations) (
SZ()
)
- In a loop, fill the tile pool (
C()
) with (up to) 4 copies each of a tile
- Oops,
X1
never ended up being used, and I forgot to remove its initialization here
30 FOR Z=0 TO 2:FOR Y=Z TO 6-Z:FOR X=Z TO 6-Z:R=RAND(L):C=C(R):C(R)=C(L-1):L=L-1:B(Z*49+Y*7+X)=C:NEXT X:NEXT Y:NEXT Z
- For each level of the board (
Z
)...
- For each row of the level (
Y
)...
- For each column of the row (
X
)...
- Pick a random spot within the pool of available tiles (
R
)
- Grab the tile and store it (
C
; not to be confused with C()
)
- Take the last tile at the end of the pool (
C(L-1)
) and place it in the spot we just grabbed a tile from (C(R)
)
- Decrement the length of the pool (
L=L-1
)
- Assign the tile into the board at the location we're iterating over (
B()
)
(Note: This method of filling the board doesn't guarantee a winnable game.)
40 FOR Z=0 TO 2:FOR Y=Z TO 6-Z:FOR X=Z TO 6-Z:POKE SC+Y*20+X+42,B(Z*49+Y*7+X)+Z*64:NEXT X:NEXT Y:POKE 708+Z,(Z+1)*69:NEXT Z:POKE 559,34
- For each level of the board (
Z
)...
- For each row of the level (
Y
)...
- For each column of the row (
X
)...
- Draw the piece on the screen, activating bit 7 (64), bit 8 (128), or neither (0) (via
Z*64
), which causes GRAPHICS 2
to draw the same character in different colors
(In normal text mode (GRAPHICS 0
), you can show any of 128 symbols from the character set, in either normal or inverse-video. In this mode, you can only show 64 symbols, in normal (non-inverse-video) mode, in one of four foreground colors)
- Set the color palette for
COLOR0
, COLOR1
or COLOR2
(depending on Z
) to a particular color. The higher the Z
, brighter the luminence.
- Re-enable the playfield (by setting
SDMCTL
to 0)
(So at this point, the screen has been blank until all 3 levels of the board were drawn, so you don't get a peek at what's "underneath" any tiles)
Main loop
50 SZ=SC+(Y+2)*20+(X+2):CC=PEEK(SZ):POKE SZ,192:S=STICK(0):IF S=7:X=(X+1) MOD 7:ENDIF :IF S=11:X=(X+6) MOD 7:ENDIF
- Find the cursor's screen position (
SZ
)
- Grab the byte that's at the cursor (
CC
)
- Draw a white space character at the cursor
(White, via the color we put in COLOR3
, because we're setting both bits 7 and 8 (192
).
Visible at all because we put that checkerboard shape into the character set.)
- Grab the state of the joystick in controller port 1 (
S
)
- If the user is pushing right, move cursor to the right, wrapping to the left if we go past the rightmost position
- If the user is pushing left, move cursor to the left, wrapping to the right if we go past the leftmost position
(instead of subtracting 1, we add the maxmimum value minus 1, and let the MOD
(modulus) operator bring us back within our limits)
60 IF S=13:Y=(Y+1) MOD 7:ENDIF :IF S=14:Y=(Y+6) MOD 7:ENDIF :PAUSE 2:POKE SZ,CC:PAUSE 2:C(P)=CC:C2=CC&192:Z=C2/64
- If the user is pushing down, move the cursor down, etc...
- If the user is pushing up, move the cursor up, etc...
- Pause for a brief moment
- Re-draw the symbol that was under the cursor (put what's in
CC
back at screen memory location SZ
)
- Pause for a brief moment again
- Store what's under the cursor into position
C()
, based on how many tiles have been selected (P
)
- Determine which, if any, of the two highest bits (7 and 8) are set (
C2
)
- Use the high bits (in
C2
to which tells us what level the tile under the cursor is at (Z
)
70 IF STRIG(0)=0 AND CC:IF P=1 AND SZ=SZ(0):POKE SZ,C(0):P=0:ELSE :IF P=1:POKE SZ(0),C(0):ENDIF
- If the user is pressing fire on the joystick, and they're not above a blank spot on the screen (no more tiles)...
- If this is the second time they've selected a tile (
P=1
), and it's at the same place as before, that means they're un-selecting the tile...
- Put the un-highlighted tile symbol back on the screen
- Denote that no item has been selected yet (set
P
to 0)
- Otherwise, wasn't un-selecting a tile...
- If a tile has been selected, unhighlight it
(we won't want it highlighted, so we can tell what level it's at if we want to be able to select a tile adjacent to this one, at a different level)
80 Q=PEEK(SZ-1):R=PEEK(SZ+1):IF (Q=0 OR Q&192<C2 OR R=0 OR R&192<C2) AND (P=0 OR CC&63=PEEK(SZ(0))&63):SZ(P)=SZ:P=P+1
- ...pressing fire...
- ...wasn't un-selecting a tile...
- Grab what's on the screen at the left of the cursor (
Q
)
- Grab what's on the screen at the right of the cursor (
R
)
- If the tile we're selecting is:
- 'free' (nothing to the left and/or right of it at all (
Q=0
or R=0
) or at this level (by comparing the highest two bits of Q
and R
to the highest two bits of what's under the tile (C2
))
- and is either the first tile we're selecting (
P=0
), or it matches the first tile we selected (the byte at SZ(0)
), regardless of either's level (&63
)
then...
- Record the screen memory location of this selection (
SZ(P)
)
- Increment how many tiles have been selected (
P
)
90 IF P=2:FOR I=0 TO 1:IF C(I)&192=0:POKE SZ(I),0:ELSE :Z=(C(I)&192)/64-1:POKE SZ(I),B(Z*49+Y*7+X)+Z*64:ENDIF
- ...pressing fire...
- ...wasn't un-selecting a tile...
- ...the tile is free and is selectable...
- If we've selected two tiles...
(Based on logic that got us to this point, it's a selectable tile, and a match with the first)
- For each of the two spots (
I
)...
- If the tile at that spot was at the bottom-most level (it's highest two bits are off)...
- Clear the tile completely (put 0 in graphics memory)
- Otherwise (title wasn't at the bottom-most level)...
- Determine the level below the tile we're removing (
Z
)
- Place the tile underneath it (within
B()
) at that spot
100 NEXT I:P=0:ENDIF :ELSE :? "{bell}":ENDIF :PAUSE 4:IF P=1:POKE SZ(0),(C(0)&63)+192:ENDIF :ENDIF :ENDIF :GOTO 50
- ...pressing fire...
- ...wasn't un-selecting a tile...
- ...the tile is free and is selectable...
- If we've selected two tiles...
- Denote that no tiles are currently selected (set
P
to 0)
- Otherwise, the title was not free, or not a match to the first tile...
- Pause for a moment
- If we've selected the first tile, highlight it white (by setting both of the highest two bits)
- Repeat the main loop
Download
- MINIJONG.TBS - tokenized TurboBASIC XL file
- minijong-20140219.atr - ATR disk image containing:
- MyDOS 4.53/3 (
DOS.SYS
, DUP.SYS
)
- TurboBASIC XL (auto-run,
TBASIC.AR0
)
- The game (TBXL auto-run,
AUTORUN.BAS
;
except for the filename , same as MINIJONG.TBS
stand-alone file, above)
Bill Kendrick, 2014,
nbs@sonic.net,
New Breed Software
Other games I wrote for NOMAM 2014