Let’s write Lights Out in BASIC – part 2

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

Updates:

  • 2023-02-06 – William Astle pointed out that Color BASIC doeshave ELSE. Not sure why I thought it didn’t — I never had a machine with plain Color BASIC. Striked out!

In the first installment, I shared a brief history of the game Lights Out using “extensive” research from the Wikipedia page and some YouTube videos.

This time, let’s look begin implementing the game in BASIC. My first goal is to write something generic enough that it could be easily ported to other systems. I specifically have VIC-20 in mind, since it was my first home computer.

After this is achieved, maybe we can see how much we could optimize and enhance it specifically for the CoCo (or VIC).

In Rick Adams‘ CoCo 3 version, he uses what appears to be the high-resolution 320×200 16-color graphics screen. It uses a joystick to move around and select squares.

Rick Adams’ Lights Out for CoCo 3.

It is quite nice, and reminds me of how his Shanghai game was controlled. (Rick wrote the official Color Computer version, sold on a ROM-Pak cartridge by Radio Shack.)

For my version, I’ll only use the text screen, and the keyboard to select squares.

Representing the Grid of Lights

Since the game is played on a 5×5 grid, a simple (and obvious) way to represent that grid of lights might be to use a two-dimensioned array. Something like this:

DIM L(4,4)

That may look wrong for a 5×5 grid, but on Color BASIC, dimensions start at 0. DIM X(4) gives entries X(0), X(1), X(2), X(3) and X(4). For DIM L(4,4) we get:

+---+---+---+---+---+---+
| . | 0 | 1 | 2 | 3 | 4 |
+---+---+---+---+---+---+
| 0 | . | . | . | . | . |
+---+---+---+---+---+---+
| 1 | . | . | . | . | . |
+---+---+---+---+---+---+
| 2 | . | . | . | . | . |
+---+---+---+---+---+---+
| 3 | . | . | . | . | . |
+---+---+---+---+---+---+
| 4 | . | . | . | . | . |
+---+---+---+---+---+---+

If I were to do L(0,0)=123, the top left square would be 123:

+---+---+---+---+---+---+
| . | 0 | 1 | 2 | 3 | 4 |
+---+---+---+---+---+---+
| 0 |123| . | . | . | . |
+---+---+---+---+---+---+
| 1 | . | . | . | . | . |
+---+---+---+---+---+---+
| 2 | . | . | . | . | . |
+---+---+---+---+---+---+
| 3 | . | . | . | . | . |
+---+---+---+---+---+---+
| 4 | . | . | . | . | . |
+---+---+---+---+---+---+

Then if I did L(3,2)=456:

+---+---+---+---+---+---+
| . | 0 | 1 | 2 | 3 | 4 |
+---+---+---+---+---+---+
| 0 |123| . | . | . | . |
+---+---+---+---+---+---+
| 1 | . | . | . | . | . |
+---+---+---+---+---+---+
| 2 | . | . | . |456| . |
+---+---+---+---+---+---+
| 3 | . | . | . | . | . |
+---+---+---+---+---+---+
| 4 | . | . | . | . | . |
+---+---+---+---+---+---+

Toggling a Grid Value

By having a two-dimensional 5×5 array like that, I can represent the 25 squares of the Lights Out game. If I made it so a value of 0 was “off” and a value of “1” was on, I could toggle the value of a square using some simple logic like this:

IF L(X,Y)=1 THEN L(X,Y)=0 ELSE IF L(X,Y)=0 THEN L(X,Y)=1

Or, since I know I am only going to be putting a 0 or 1 there, I could simplify:

IF L(X,Y)=1 THEN L(X,Y)=0 ELSE L(X,Y)=1

That’s a bit clunky, and requires a BASIC that has the ELSE keyword. While the CoCo got ELSE with Extended BASIC, it was not present on the original Color BASIC. (Thanks, William A.!) My Commodore VIC-20 does not have it. To avoid using ELSE, it could be written like this:

100 IF L(X,Y)=1 THEN L(X,Y)=0:GOTO 120
110 L(X,Y)=1
120 ...program continues...

That “GOTO 120” to skip the next line is very important, else the first line would set it to 0, then the next line would set it back to 1. See? Clunky.

Since our goal is simply to toggle a square from on to off, or from off to on, the value is not important. It could be any two values, such as -57 and +123, using that code.

BUT, we can do better. We can make use of the BASIC mathematical NOT keyword. NOT is available in 1980 Color BASIC and even on the VIC-20.

NOT can be used on a number to flip it back and forth between two values:

PRINT NOT 0
-1

PRINT NOT -1
0

If we made 0 mean OFF, and -1 mean ON, we could toggle them back and forth by using NOT.

100 IF L(X,Y)=NOT L(X,Y)

That looks much simpler. And, as an added bonus, when you compare things in BASIC, the result is either -1 for TRUE, or 0 for FALSE. This is what the IF keyword is expecting. For example:

IF -1 THEN PRINT "THIS IS TRUE"

IF 0 THEN PRINT "YOU WON'T SEE THIS"

That will print “THIS IS TRUE”.

This would allow printing something quite easily based on the variable. We could use a FOR/NEXT loop for each row of the grid, and a second FOR/NEXT loop for each column in that row. A simple check could be done to decide if an “X” or “Y” should be printed for that grid element.

1000 REM SHOW GRID
1010 FOR Y=0 TO 4
1020 FOR X=0 TO 4
1030 IF L(X,Y) THEN PRINT "X";:GOTO 1050
1040 PRINT ".";
1050 NEXT
1060 PRINT
1070 NEXT
1080 PRINT

Since 0 represents FALSE (light is on), and since BASIC variables are initialized to 0, running that program should present a grid of all lights off (represented by a dot):

.....
.....
.....
.....
.....

If we set some of those array elements to -1 (FALSE, off), like this:

10 L(0,0)=-1
20 L(1,1)=-1
30 L(2,2)=-1
40 L(3,3)=-1
50 L(4,4)=-1

…then running it would show something like this:

X....
.X...
..X..
...X.
....X

This gives us a very simple “show grid” subroutine.

Selecting a Square to Toggle

If we go really, really old school, we could use INPUT and ask the user to tell us which square to toggle by using X and Y coordinates.

2000 REM INPUT SQUARE
2010 INPUT "X (0-4)";X
2020 INPUT "Y (0-4)";Y

Or, both variables could be asked for at the same time using “INPUT X,Y” like this:

2000 REM INPUT SQUARE
2010 INPUT "X,Y (0-4,0-4)";X,Y

To make sure typing a number larger than the array size (like 5), or smaller (like -5), does not crash the program, we should add some error checking:

2000 REM INPUT SQUARE
2010 INPUT "X,Y (0-4,0-4)";X,Y
2020 IF X<0 OR X>4 OR Y<0 OR Y>4 THEN 2010

Do you remember when we were learning BASIC like this? I promise, we’ll make it less remedial later.

Toggling that Square

Once we know the X and Y for a square inside the array, we can toggle that square as shown earlier:

L(X,Y)=NOT L(X,Y)

If the square value was 0, it will become -1. If it was -1, it will become zero. NOT does the work for us.

But, the way Lights Out works is it not only toggles that square, but the ones above, below, left and right of it (if there is a square there). Our toggle routine should do all that for us — and it should also know when there is not a square above, below, left or right of it:

3000 REM TOGGLE SQUARES
3010 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)

Let’s walk through that…

  • Line 3010 – toggles the selected square.
  • Line 3020 – if the selected square’s column (X) is greater than 0 (1-4), there will be a square to the left. Toggle that square (X-1).
  • Line 3030 – if the selected square’s column (X) is less than 4 (0-3), there will be a square to the right. Toggle that square (X+1).
  • Line 3040 – if the selected square’s row (Y) is greater than 0 (1-4), there will be a square above it. Toggle that square (Y-1).
  • Line 3050 – if the selected square’s row (Y) is less than 4 (0-3), there will be a square below it. Toggle that square (Y+1).

Putting it all together…

By adding a “RETURN” at the end of those three blocks of code, we can make them subroutines and call them using GOSUB. We now have the basic framework for a Lights Out game!

10 REM SHOW GRID
20 GOSUB 1000
30 REM INPUT SQUARE
40 GOSUB 2000
50 REM TOGGLE SQUARES
60 GOSUB 3000
70 REM REPEAT
80 GOTO 10

1000 REM SHOW GRID
1010 FOR Y=0 TO 4
1020 FOR X=0 TO 4
1030 IF L(X,Y) THEN PRINT ".";:GOTO 1050
1040 PRINT "X";
1050 NEXT
1060 PRINT
1070 NEXT
1080 PRINT
1090 RETURN

2000 REM INPUT SQUARE
2010 INPUT "X,Y (0-4,0-4)";X,Y
2020 IF X<0 OR X>4 OR Y<0 OR Y>4 THEN 2010
2030 RETURN

3000 REM TOGGLE SQUARES
3010 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

BUT, since variables initialize as 0, and see means FALSE (off), there would be no lights on at the start of this game. The game would initialize to “already won” ;-) There would need to be some kind of initializing routine that sets some or all of the squares “on” at the start of the game.

But even with that, this would still be an un-win-able game – there is no code that checks to see if all the lights have been turned off.

Let’s add that in the next installment…

4 thoughts on “Let’s write Lights Out in BASIC – part 2

    1. Allen Huffman Post author

      Really? Maybe it was the MC-10 I was thinking about? I remember writing an adventure game in 1984, and not using ELSE because it needed to run on other levels of CoCo.

      I’ll update it and blame it on Commodore BASIC not having it. :)

      Reply
  1. Johann Klasek

    You could still use 1 and toggling is also easy nearly as fast with L(X,Y)=1-L(X,Y).
    With 0 and 1 the display routine could be simplified to PRINT D$(L(X,Y) where D$(0)=”.” and D$(1)=”X”. But you can still use -1 and add the ABS function around D$() in above PRINT.

    Reply
    1. Allen Huffman Post author

      Oh, man. I didn’t even think about something like that. 1-1 becomes 0. 1-0 becomes 1… That would feel much better, and I am guessing subtracting one is probably smaller and less COu time than doing the NOT.

      Reply

Leave a Reply

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