# Let’s write Lights Out in BASIC – part 3

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

The story so far… In the beginning, we learned a bit about the game Lights Out and its history. We then saw some very basic BASIC that implements the basic functionality of a basic game of this type. We still have a bit more work to do, such as initializing some of the squares to an “on” state and adding code to detect when the game has been won.

## Game Initialization

Since variables in BASIC initialize as 0, and 0 means FALSE (the light is “off”), the initial grid would be shown with all lights off. Congratulations. You win!

To make it a game, we need to have some or all of those lights on when the game starts. My initial attempts of just randomly setting some number of lights to on seemed to often produce puzzles that could not be solved — even when using lookup tables (i.e., “cheating”) that show the winning patterns. Are there unsolvable patterns in Lights Out???

I consulted the chat section of the BING search engine, and asked it…

Yes, there are some configurations of the Lights Out game that cannot be solved. In fact, Marlow Anderson and Todd Feil proved in 1998 that not all configurations are solvable. However, it is important to note that these unsolvable configurations are not valid Lights Out puzzles.

– BING AI Chat

Well then. Since unsolvable patterns are not valid for Lights Out, tust turning on random lights is not an acceptable way to start a game.

Side Note: Card games like Solitaire certainly can be unwinnable depending on the order of cards in the deck. I am pretty sure games like Minesweeper are like that, too. But Lights Out patterns are always supposed to be winnable.

It seemed if you started with all lights off, you could simply work backwards by toggling lights on randomly until you got to a stopping point. Whatever pattern that is, the player was assured that there was a way to win. Instead of just turning on specific lights, I could just select a square and allow it to do the normal toggle (that light, plus the adjacent lights).

For my version, I will just make a very simple “initialize game” routine that does this. To do this, I will use a FOR/NEXT loop and generate random X/Y coordinates and call the “toggle” subroutine. This should end up with a random pattern that is 100% solvable.

`4000 REM INITIALIZE GRID4010 PRINT "INITIALIZING..."4020 FOR A=1 TO 104030 X=RND(5)-14040 Y=RND(5)-14050 GOSUB 30004060 NEXT4070 RETURN`

This is what it is doing:

• Line 4010 – Since the FOR/NEXT loop will cause a pause, I wanted to print out something to tell the player what was happening.
• Line 4020 – A FOR/NEXT loop is done so the random grid selections can be done 10 times. (We’ll improve this later.)
• Line 4030 – For X, RND is used to choose a number from 1 to 5. Since the DIM arrays are base-0 (0-4), we subtract 1 so Y will be a number form 0-4.
• Line 4040 – Same thing, but for Y.
• Line 4050 – We GOSUB to the routine that toggles the specific square (X,Y), and any adjacent squares.
• Line 4060 – Continue the FOR/NEXT loop…
• Line 4070 – We return back to wherever we were GOSUB’d from.

At the start of the game, we’d just “GOSUB 4000” to initialize the grid. Here is the complete code, so far:

`10 REM INITIALIZE GRID20 GOSUB 400030 REM SHOW GRID40 GOSUB 100050 REM INPUT SQUARE60 GOSUB 200070 REM TOGGLE SQUARES80 GOSUB 300090 REM REPEAT100 GOTO 301000 REM SHOW GRID1010 FOR Y=0 TO 41020 FOR X=0 TO 41030 IF L(X,Y) THEN PRINT "X";:GOTO 10501040 PRINT ".";1050 NEXT1060 PRINT1070 NEXT1080 PRINT1090 RETURN2000 REM INPUT SQUARE2010 INPUT "X,Y (0-4,0-4)";X,Y2020 IF X<0 OR X>4 OR Y<0 OR Y>4 THEN 20102030 RETURN3000 REM TOGGLE SQUARES3010 L(X,Y)=NOT L(X,Y)3020 IF X>0 THEN L(X-1,Y)=NOT L(X-1,Y)3030 IF X<4 THEN L(X+1,Y)=NOT L(X+1,Y)3040 IF Y>0 THEN L(X,Y-1)=NOT L(X,Y-1)3050 IF Y<4 THEN L(X,Y+1)=NOT L(X,Y+1)3060 RETURN4000 REM INITIALIZE GRID4010 PRINT "INITIALIZING..."4020 FOR A=1 TO 104030 Y=RND(5)-14040 X=RND(5)-14050 GOSUB 30004060 NEXT4070 RETURN`

And it works! Even on a 1980 4K TRS-80 Color Computer (shown here, emulated in XRoar):

But the game still cannot be won because there is no code to check for a win ;-)

## Defining a “Win”

Since the goal of Lights Out is to turn all the lights out, winning means that the number of lights that are on is zero. One simple (but slow) way to test for this condition would be to scan through the entire grid and add up how many lights are on.

`5000 REM COUNT LIGHTS ON5005 LO=05010 FOR Y=0 TO 45020 FOR X=0 TO 45030 IF L(X,Y) THEN LO=LO+15040 NEXT5050 NEXT5060 RETURN`

To check, that routine is called. If LO=0 then the game has been won. Since the routine counts the number of “on” lights,, the user could also be told how many lights are on. I would probably add this right after it draws the grid, so the final winning grid is shown before the game ends.

`10 REM INITIALIZE GRID20 GOSUB 400030 REM SHOW GRID40 GOSUB 100045 REM CHECK FOR WIN46 GOSUB 500047 IF LO=0 THEN PRINT "YOU WON!":END48 PRINT "LIGHTS ON:";LO50 REM INPUT SQUARE60 GOSUB 200070 REM TOGGLE SQUARES80 GOSUB 300090 REM REPEAT100 GOTO 30`

For a small 5×5 grid, the CoCo seems plenty fast enough to do this check without much of a delay in between moves. If the machine were very slow, or the grid was very large (and thus, took much longer to check), this might not be the best approach.

Another option might be to start with a count of all lights that are on, and decrement that value each time a light is turned off. If a light is turned on, increment it. If the value reaches zero, the game has been won.

But how do we know which lights are on? We’d could just call our “count lights that are on” function after the grid is initialized and get that value.

Or, maybe we don’t use that routine at all. We could put the count routine directly in the routine that toggles the squares on and off:

`3000 REM TOGGLE SQUARES3010 L(X,Y)=NOT L(X,Y)3020 IF X>0 THEN L(X-1,Y)=NOT L(X-1,Y)3030 IF X<4 THEN L(X+1,Y)=NOT L(X+1,Y)3040 IF Y>0 THEN L(X,Y-1)=NOT L(X,Y-1)3050 IF Y<4 THEN L(X,Y+1)=NOT L(X,Y+1)3060 RETURN`

We could increment some variable every time a light is turned on, and decrement it any time a light is turned off.

It looks like our shortcut of using NOT is going to cause us more work. Since we aren’t manually setting the value to something meaning ON, and something else meaning OFF, we have no place add code to increment or decrement our Lights On count. Had we used 1 for on, and -1 for off, we could just add the value of the square (1 or -1) and been done.

But we didn’t.

We know after the NOT toggle, the value will be -1 or 0. Had it been 1 and -1, counting would be very straightforward. But with -1 and 0, we need to somehow make the -1 (meaning TRUE, a light was turned on) be an increment (+1), and the 0 (meaning FALSE, a light was turned off) to be a decrement (-1).

Side Note: There are many ways to write a program. In this one, I chose an easy way to toggle the lights. At the time, I was not considering counting the lights that were on. This NOT approach adds a bit more work to do the count. It could be that adding extra work here might be larger/slower than just doing a count routine that scans the whole grid. Depending on if we are going for speed or memory usage, the design decisions may need to be different.

Mapping this out, I find a simple solution. I can multiply the value (-1 or 0) by 2, and then add 1.

• If the value is -1, multiplying by 2 will make it -2. Then adding 1 will make it -1.
• If the value is 0, multiplying by 2 will be 0. Then adding 1 will make it 1.
`ON: -1 * 2 = -2 + 1 = -1OFF: 0 * 2 = 0 + 1 = 1`

Well, that gets us a +/- 1, but it is backwards from what I would have preferred. To work around that, I can subtract the value. If you subtract -1, it is like adding one. And if you subtract 1, it is like adding a -1. Easy! :)

The updated routine looks like this:

`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<4 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<4 THEN L(X,Y+1)=NOT L(X,Y+1):LO=LO-(L(X,Y+1)*2+1)3060 RETURN`

Yuck. But hey, it works!

Now, instead of calling the “count all the squares” routine, we can just reset the LO “lights on” variable during the initialization routine, and it should be decremented/incremented as the game plays.

To test, let’s comment out the call that counts the squares:

`10 REM INITIALIZE GRID20 GOSUB 400030 REM SHOW GRID40 GOSUB 100045 REM CHECK FOR WIN46 REM GOSUB 500047 IF LO=0 THEN PRINT "YOU WON!":END48 PRINT "LIGHTS ON:";LO50 REM INPUT SQUARE60 GOSUB 200070 REM TOGGLE SQUARES80 GOSUB 300090 REM REPEAT100 GOTO 30`

I did a quick test, and this appears to work. It is also faster, since it bypasses the small delay that was happening each time it called the “count all 25 squares” routine. Nice.

Again, the overhead of adding the count in five locations (about 21 bytes each, so 100 bytes extra) does appear to be larger than the “count all the squares” routine. If code size were more important than speed, the count routine would make more sense.

But so far, it still fits on my 1980 4K CoCo with 1514 bytes left to spare (and more room once I remove the un-used “count all the squares” routine). We can add more code! :)

We now have a very primitive but functional version of Lights Out that should guarantee each “random” pattern can be won. The “random” patterns should keep someone from just learning the moves and repeating them to win the level if they play it over and over.

Except they may not be as random as we think.

To be continued…

## 7 thoughts on “Let’s write Lights Out in BASIC – part 3”

1. William Astle

As a side note, for your count lights routine you could have used a similar trick to what you did for the running tally variation. That is, you could have just subtracted the array entry from your tally and not needed an IF at all. I only mention it in case someone else wants to do a similar tally. However, I’d stick with the running count you have implemented instead. It’s going to be much better on average.

1. Allen Huffman Post author

Oh yeah, that makes sense. I think Rick just counted in the display routine, since you always have to display anyway. I am not sure if I ended up doing that in mine in later parts.

2. Jason Pittman

Wow, I’ve never played it. It’s maddening!!! I thought it might make it easier for me to know what X/Y I wanted if I printed a header for them. Also, I think you could eliminate the IF/GOTO on line 1030 by just printing a semigraphics character and letting the L value either make it print CHR\$(159) (a yellow square for “on”) or CHR\$(160) (a black square for “off”). I’ll definitely be playing around with this more later.

```1000 REM SHOW GRID 1010 PRINT " 01234" 1020 FOR Y=0 TO 4 1030 PRINT Y; 1040 FOR X=0 TO 4 1050 PRINT CHR\$(L(X,Y)+160); 1060 NEXT 1070 PRINT 1080 NEXT 1090 PRINT 1100 RETURN ```

1. Allen Huffman Post author

It will indeed evolve to printing headers, but no demographics since by part 7 it runs on VIC too. But, once the logic is done it needs much better than “0,3” for input.

1. Jason Pittman

Ahh…I forgot all about VIC 20 when I was playing around with it. And I’m pretty sure that I won’t need the routine to see if I’ve won, because I’ll never win this one, but it’s a fun little game!

1. Allen Huffman Post author

There are some basic patterns explained on the wiki page. You can “chase” the lights from top to bottom, and then based on the final pattern, you start over with some sequence and do it again and can win. But memorizing that, man, I don’t know.

2. Allen Huffman Post author

When the final engine is posted, do you want to take a stab at enhancing it for CoCo? Joystick or better keyboard controls, semi graphics, etc.?

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