Let’s write Lights Out in BASIC – part 7

See also: part 1, part 2, part 3, part 4, part 5, part 6 and part 7.

Code cleanup in aisle 4.

Let me begin this installment by presenting the current Lights Out code with some renumbering done and a few minor improvements.

0 REM LITESOUT-P7.BAS

10 REM SETUP
20 DIM L(9,9)

100 REM START NEW GAME
110 GOSUB 5000
120 REM INITIALIZE GRID
130 MV=0
140 GOSUB 4000
150 REM SHOW GRID
160 GOSUB 1000
170 REM CHECK FOR WIN
180 IF LO=0 THEN 300
190 REM INPUT SQUARE
200 GOSUB 2000
210 REM TOGGLE SQUARES
220 GOSUB 3000
230 REM REPEAT
240 MV=MV+1
250 GOTO 150

300 REM GAME WON
310 PRINT "YOU WON IN";MV;"MOVES."
320 INPUT "PLAY AGAIN (Y/N)";Q$
330 IF Q$="Y" THEN 100
340 PRINT "GAME OVER"
350 END

1000 REM SHOW GRID
1010 PRINT "GAME NUMBER:";GN
1020 PRINT " ";
1030 FOR A=1 TO GS
1040 PRINT RIGHT$(STR$(A),2);
1050 NEXT:PRINT
1060 FOR Y=0 TO GS-1
1070 PRINT RIGHT$(STR$(Y+1),2);" ";
1080 FOR X=0 TO GS-1
1090 IF L(X,Y) THEN PRINT "X ";:GOTO 1110
1100 PRINT ". ";
1110 NEXT
1120 PRINT
1130 NEXT
1140 PRINT "MOVES:";MV;"LIGHTS ON:";LO
1150 RETURN

2000 REM INPUT SQUARE
2010 S$=MID$(STR$(GS),2):PRINT "X,Y (1-";S$;",1-";S$;" OR 0,0)";
2020 INPUT X,Y
2030 IF X=0 THEN IF Y=0 THEN 320
2040 IF X<1 OR X>GS OR Y<1 OR Y>GS THEN 2010
2050 X=X-1:Y=Y-1
2060 RETURN

3000 REM TOGGLE SQUARES
3010 L(X,Y)=NOT L(X,Y):LO=LO-(L(X,Y)*2+1)
3020 IF X>0 THEN L(X-1,Y)=NOT L(X-1,Y):LO=LO-(L(X-1,Y)*2+1)
3030 IF X<GS-1 THEN L(X+1,Y)=NOT L(X+1,Y):LO=LO-(L(X+1,Y)*2+1)
3040 IF Y>0 THEN L(X,Y-1)=NOT L(X,Y-1):LO=LO-(L(X,Y-1)*2+1)
3050 IF Y<GS-1 THEN L(X,Y+1)=NOT L(X,Y+1):LO=LO-(L(X,Y+1)*2+1)
3060 RETURN

4000 REM INITIALIZE GRID
4010 INPUT "GRID SIZE (3-10, ENTER=5)";GS
4020 IF GS=0 THEN GS=5
4030 IF GS<3 OR GS>10 THEN 4010
4040 PRINT "INITIALIZING..."
4050 FOR A=1 TO 10*GS
4060 Y=RND(GS)-1
4070 X=RND(GS)-1
4080 GOSUB 3000
4090 NEXT
4100 RETURN

5000 REM SELECT GAME
5010 PRINT "PLAY SPECIFIC GAME # (Y/N)?"
5020 S=S+1:A$=INKEY$:IF A$="" THEN 5020
5030 IF A$="Y" THEN 5070
5040 IF A$="N" THEN A=RND(-S)
5050 GN=RND(65535):A=RND(-GN)
5060 GOTO 5100
5070 INPUT "PLAY GAME (1-65535)";GN
5080 IF GN<1 OR GN>65535 THEN 5060
5090 A=RND(-GN)
5100 RETURN

As I tested this version, I noticed my random grid generator was not very good–at least on a large 10×10 grid. It really needed to do more random light toggling for larger grids. This change would go in this line – maybe just doing a “grid size * X”, like “10*GS” or something. I am still trying things, but for now I made it do more random toggles the larger the grid size is (line 4050).

I also made it so the default grid size was 5 (line 4010).

And I fixed some output where it printed a numeric variable. In Color BASIC, there is an extra space printed before and after printing a number:

PRINT "*";42;"*"
* 42 *

And, technically, it’s not a space before a number — it’s a place for the sign (none if positive, “-” if negative):

PRINT "*";-42;"*"
*-42 *

Since I knew I was only printing a positive number, I just got rid of that leading space, and the one after it. I did this by converting the number to a string using STR$. That will convert it to a string with the first space (a place for the sign) but no space after it:

PRINT "*";STR$(42);"*"
* 42*

Then all I needed to do was trim off that leading space. In the old days, I would have done this by getting the length of the string using LEN$ and then using RIGHT$ to get just the character(s) after that leading space:

N=42
N$=STR$(N)
L=LEN(N$)
PRINT RIGHT$(N$,L-1)

But in recent years, someone commented on this site (I believe) about how you could use MID$ without a size and just specify the starting position in a string, and it would return everything from that position to the end:

N=42
PRINT MID$(STR$(N),2)

Nice! “Wish I knew then what I know now…”

But I digress…

We now have a fairly “feature complete” version of Lights Out that runs in very basic BASIC — it doesn’t even make use of ELSE.

Before I move on to making this look a lot nicer on a CoCo, I thought it might be fun to port it to a VIC-20. I made use of the wonderful CBM prg Studio Windows-based tool that can build BASIC or assembly programs for all kinds of Commodore machines. I just pasted in my source, and then found a few minor things I would need to change to run on the VIC-20’s CBM BASIC.

The first thing I did after pasting the code in to the editor was to convert it all to lowercase. That seems to be how that BASIC works, as SHIFT characters turn in to PETSCII graphics characters.

Now I would be able to build the program.

It would build right away, but not run on the VIC yet until I fix two things that are different between Color BASIC and CBM BASIC:

  • A$=INKEY$ must be changed to GET A$
  • RND(x) must be changed to INT(RND(1)*X)-1 (Doing the random seed using RND(-X) works the same way on the VIC, and could be left alone.)
CoCo:
5020 S=S+1:A$=INKEY$:IF A$="" THEN 5020

VIC:
5020 S=S+1:GET A$:IF A$="" THEN 5020

CoCo:
4060 Y=RND(GS)-1

VIC:
4060 Y=INT(RND(1)*GS)

CoCo:
4070 X=RND(GS)-1

VIC:
4070 X=INT(RND(1)*GS)

After that, I tried to RUN it and ran in to a problem with INPUT:

From testing, it seems that long prompts on INPUT cause issues, possibly related to how the VIC’s full screen editor works. I sent a tweet to 8-Bit Show and Tell to see what he knew, and did a simple workaround by breaking up the prompt and the input on separate screen lines:

4000 rem initialize grid
4010 print "grid size (3-10, enter=5)":input gs

I thought I might have the same issue with the “select square” input, but that prompt does not overlap to the next line so it worked… or so I thought. With the default grid size of 5×5, the prompt looked like:

X,Y (0-5,0-5 OR 0,0)?(space)
*(cursor here)

That seemed to work just fine, but when I did a grid of 10×10, it made the prompt longer, wrapping the “?” of INPUT to the next line:

X,Y (0-10,0-10 OR 0,0)
? *(cursor here)

So I needed to fix that, too. I had already had to break that one up in to a PRINT and an INPUT since I was printing variables in the prompt, so I just took the semicolon off from the end of the PRINT:

2000 rem input square
2010 s$=mid$(str$(gs),2):print "x,y (1-";s$;",1-";s$;" or 0,0)"
2020 input x,y

Now I had what appeared to be a working version of the game for VIC-20. BUT, it would need some work to reformat the prompts to fit on a 22-column screen, versus the 32- column CoCo screen. For anyone who wants to work on that, here is the VIC-20 version with the lines in bold that have changes from the CoCo version:

0 rem litesout-p7.bas

10 rem setup
20 dim l(9,9)

100 rem start new game
110 gosub 5000
120 rem initialize grid
130 mv=0
140 gosub 4000
150 rem show grid
160 gosub 1000
170 rem check for win
180 if lo=0 then 300
190 rem input square
200 gosub 2000
210 rem toggle squares
220 gosub 3000
230 rem repeat
240 mv=mv+1
250 goto 150

300 rem game won
310 print "you won in";mv;"moves."
320 input "play again (y/n)";q$
330 if q$="y" then 100
340 print "game over"
350 end

1000 rem show grid
1010 print "game number:";gn
1020 print " ";
1030 for a=1 to gs
1040 print right$(str$(a),2);
1050 next:print
1060 for y=0 to gs-1
1070 print right$(str$(y+1),2);" ";
1080 for x=0 to gs-1
1090 if l(x,y) then print "x ";:goto 1110
1100 print ". ";
1110 next
1120 print
1130 next
1140 print "moves:";mv;"lights on:";lo
1150 return

2000 rem input square
2010 s$=mid$(str$(gs),2):print "x,y (1-";s$;",1-";s$;" or 0,0)"
2020 input x,y
2030 if x=0 then if y=0 then 320
2040 if x<1 or x>gs or y<1 or y>gs then 2010
2050 x=x-1:y=y-1
2060 return

3000 rem toggle squares
3010 l(x,y)=not l(x,y):lo=lo-(l(x,y)*2+1)
3020 if x>0 then l(x-1,y)=not l(x-1,y):lo=lo-(l(x-1,y)*2+1)
3030 if x<gs-1 then l(x+1,y)=not l(x+1,y):lo=lo-(l(x+1,y)*2+1)
3040 if y>0 then l(x,y-1)=not l(x,y-1):lo=lo-(l(x,y-1)*2+1)
3050 if y<gs-1 then l(x,y+1)=not l(x,y+1):lo=lo-(l(x,y+1)*2+1)
3060 return

4000 rem initialize grid
4010 print "grid size (3-10, enter=5)":input gs
4020 if gs=0 then gs=5
4030 if gs<3 or gs>10 then 4010
4040 print "initializing..."
4050 for a=1 to 10*gs
4060 y=int(rnd(1)*gs)
4070 x=int(rnd(1)*gs)
4080 gosub 3000
4090 next
4100 return

5000 rem select game
5010 print "play specific game # (y/n)?"
5020 rem s=s+1:a$=inkey$:if a$="" then 5020
5021 s=s+1:get a$:if a$="" then 5020
5030 if a$="y" then 5070
5040 if a$="n" then a=rnd(-s)
5050 gn=rnd(65535):a=rnd(-gn)
5051 rem gn=int(rnd(1)*65535)-1:a=rnd(-gn)
5060 goto 5100
5070 input "play game (1-65535)";gn
5080 if gn<1 or gn>65535 then 5060
5090 a=rnd(-gn)
5100 return

The more I look at this code, the more optimizations I want to make to it.

But for now, that is a version for CoCo, and a version for Commodore. It should be easy to port to other flavors of BASIC. Let me know if you port it to something.

Now that I know how to write the game, the next goal will be to make it look a lot better on the CoCo (and maybe VIC-20 as well, if I feel ambitious).

But I think I need a brake from Lights Out for a bit…

Until then…

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.