I recently got lured in to downloading some casual game for my phone. (Thank you, gas station rewards program, for telling me I could earn a bonus from you if I downloaded this stupid game.)
I gave up these time wasters years ago when I realized how much time they wasted.
But with this addictive substance back on my phone, I was thrown back in and realized… nothing has changed. It’s even worse. These games are not games. They are ad platforms. Every few minutes you get an ad. Or, you have to watch an ad to get something you need.
I will repeat… These games are not games. They are ad platforms.
But, once new trend is how many ads for other games I see that offer “real cash” for playing them. They show folks who are broke pulling out their phone and then playing to get the money they need. “It’s just that easy!”
Obviously, if you could easily make hundreds of dollars playing a game, you wouldn’t need to pay to advertise that game. People would all know about it from word-of-mouth and we’d all be doing that.
What is even more amusing (at least to me) is how many ads warn you about “scam games” … while basically being the same game they are warning you to avoid.
But one thing caught my attention… These ads will claim to be “skill based” games. Many of them are card games like Solitaire.
While it is true there is skill to know how to play a certain set of random cards you have been dealt, there is zero skill that can prevent you from losing if you get enough bad hands. Likewise, any “skill” game that uses a roll of the dice you can 100% be guaranteed to lose if you had enough random bad rolls of the dice and your opponent had enough random good rolls.
And that gave me a (dumb) idea…
The house always wins.
As a kid, I remember playing Battleship with friends. In case you are unfamiliar, here is the wiki page for this game:
Basically, you place your ships on a grid, aligned vertically or horizontally, and your opponent does the same. You then call out the coordinate you want to “bomb” on your opponent’s grid, and they tell you if you had a “hit” or a “miss.”
Some of us cheated.
Since there was no way to verify where the ships were, if your opponent called a shot that was a “hit,” you could easily move your piece out of the way to a new spot and report “miss.” Evil. But fun. This caused LONG games, playing until you basically got down to where the ships had no other place to be.
Because of this, I never trusted the Battleship clone I played on my Radio Shack Color Computer. How could I ever trust that computer wouldn’t “cheat” the same way?
But hey, Battleship is a game of skill — after all, it does not use any random roll of the dice or deal of a card.
But the card and dice games are completely “random” and no matter how skilled you are, you can have a hard time winning against bad randomness ;-)
I thought it would be fun to write a simple BASIC Blackjack (or 21) card game, except the computer would cheat. It would have a list of all un-dealt cards, and ensure it always gives the best card to the dealer, and the worst to the opponent. If things were truly random, this is a possibility with a truly random outcome.
The same cheating computer could be done for any random game — just as Monopoly. Imagine ALWAYS getting a roll that makes you pay rent or Go To Jail, while the computer always got a roll that got them to a safe property, or one they could buy.
Shall we play a game?
I will not present code for this yet. I have not written it. But, perhaps one of you will beat me to it.
My idea:
The cards are in a random array of 52.
The computer dealer will always get the best cards. Initially a face card and an ace, then as those cards are depleted, two cards totaling ten. At that point, there is no way the player can ever get two cards at 21. And, if the player got 20, the “house always wins” so even if that happened, the computer wins.
After the player receives its cards, any request to “hit” would be a card just enough to make them bust.
This process would continue through the rest of the deck.
I am unsure if, at some point, it would ever turn into a “fair” game. And this is why I want to write this dumb idea.
Over on the CoCo Facebook group, in a discussion about “why is I always used for loops”, Rob R. left a comment that caught my interest:
“… I have a vague memory that (at least on the Mod I) there were one or two letters that weren’t usable since they could be tokenized to a command. At least some two letter variables would be verboten, like ‘TO.'”
– Rob R. on Facebook
This made me wonder: how many are there? Here are the ones that I think would be problematic:
AS – in Disk BASIC, there is an “AS” keyword used for the file system. “FIELD #1, 10 AS A$”
FN – as in “DEF FNA(B)=B*42”
IF – as in “IF A=1”
ON – as in “ON X GOTO/GOSUB” or on the CoCo 3, “ON BRK GOTO” or “ON ERR GOTO”
OR – as in “IF A=1 OR A=2”
TO – as in “FOR I=1 TO 10”
Using any of these such as “TO=1” or “FN=3” will create a ?SN ERROR.
Are there others?
Also, some of these will work in Color BASIC that will not work if you have Extended or Disk BASIC:
“DEF FN” was added in Extended, so on a Color BASIC machine you should be able to use FN as a variable.
“AS” was added in Disk BASIC, so you should be able to use AS on Color and Extended BASIC.
I wonder how many folks wrote BASIC programs using variables that were not allowed when they later added the Extended BASIC ROM and/or Disk BASIC, and wondered why they stopped working?
There is something wonderful about having a poor memory. Whether I knew something before or not, I do not remember it now, so I get the pleasure of “learning something new” even if I previously knew the new thing.
In a reply to my Color BASIC and octal post, Juan Castro mentioned another quirk about how BASIC sees numbers represented as decimals versus hex or octal.
AND, OR, AND NOT demand signed 16-bit integers as input, whereas the &H/&O expressions (and consequantly my &B extension) return UNsigneded integers. That is VERY annoying:
PRINT &H8001 AND &H0007 ?FC ERROR OK
Come the %#&*£ ON!
– Juan Castro
I am unsure about this, since after the parser reads through the code, it turns any hex or octal or decimal value intothe internal 5-byte floating point representation of the value. “A=65535” turns into exactly the same type of variable as “A=&HFFFF”.
AND and OR should deal with them exactly the same way. In Juan’s example, decimal values will generate the same ?FC ERROR. But there is a workaround…
I stumbled upon this when playing around with using AND and OR to set or test bits. My Color BASIC and 16-bits and AND and OR and NOT article explains what I was trying to do, and how I ultimately ended up achieving it.
In Juan’s example, &H8001 is 32769 in decimal. &H0007 is 7. Having the “high bit” (bit 15) set of the first value effectively makes it a negative number:
1000000000000001 = &H8001 (32769) ^ ^ bit15 bit0
Any value with that bit set will not work with AND or OR, regardless if you represented it in decimal, hex or octal. You can go up to &H7FFF (32767) using AND or OR, but past that, you get the ?FC ERROR:
Juan has been working on some BASIC enhancements, adding new features (like a method of representing values in binary — which I would find quite useful). But seeing numbers in binary not work would be even more frustrating:
PRINT &B100000000000000 AND &H1 ?FC ERROR
…at least, that is my assumption about what is “&B” binary representation would do.
Here is how to make Juan’s code work:
PRINT (&H8007-65536) AND &H0007 1
Any value that has that high bit set needs to have 65536 subtracted from it, then you get the results you would expect.
But unfortunately, you cannot use AND to test for that high bit to be set ;-) so instead you have to do silly things like this:
V=&H8007 IF V>&H7FFF THEN V=V-65536 PRINT V AND &H0001
And it’s just that easy!
Though, if you print V, you will see it is a negative number, as you might have guessed. If you don’t need to PRINT V, doing something as simple as this will let you AND and/or OR on values that have the high bit set.
Back in the early 1980s, a friend of mine had TRS-80 Model III in their house. His mother was a writer, and had gotten it to use for writing books.
I believe it was this TRS-80 where I saw a version of Monopoly that taught me a strategy I had never learned as a kid:
The computer bought EVERYTHING it landed on, even if it had to mortgage properties to do so.
This simple strategy was a gamble, since you could end up with too many mortgaged properties and no source of income from rent. But. by doing this, the computer would end up owning so many random pieces it made it difficult to own a monopoly yourself.
And since then, that is how I played Monopoly. When it worked, it created some awfully long games (if no one had a monopoly to quickly drive other players bankrupt when they landed on their hotels).
In more modern times, I have watched YouTube videos concerning Monopoly strategies. They will break down the statistical odds of landing on any particular piece of property. For example, you know a pair of dice can produce values from 2 to 12. If truly random, the odds of getting a “1 and 1” should be the same as getting a “3 and 5.” Rather than focus on dice rolls, the strategies take into consideration things that alter locations: Go To Jail, Advance to Go, Take a Ride on Reading, etc.
These cards existing means there are more chances for a player to end up on Go, Reading Railroad, Jail, etc. This means the property squares after those spots have more chances of being landed on.
Board with board games. Move along…
But I digress… As Internet Rabbit Holes do, this led me to watch other videos about statistics in things like card games. In a randomly shuffled deck, there should be as much chance for the first card to be Ace of Spaces as there is for it to be Three of Clubs. It is random, after all.
For that first card drawn, that is a 1 out of 52 chance to be any card in the deck. (52 cards in the deck.)
But as a game plays on, there are fewer cards, so the odds of getting any of the remaining cards increases. For the second card drawn, you now know there is a 0% chance of getting whatever the first card is, and a 1 in 51 chance of getting any of the rest.
And so it continues…
For games like Blackjack or 21, you do not really care if it is a Ten of Diamonds or a King of Hearts or a Queen of Clubs or a Jack of Spades. They all have the value of 10 in the game. Thus, the likelihood of drawing a ten card is much higher than any other card in the deck.
You have four suits (clubs, spades, hearts, diamonds) so there are four of each card – Aces, Two through Ten, Jacks, Queens, and Kings. This means there are 16 cards in the deck that could be a value of 10 in the game. When you draw the first card, you should have a 16 in 52 chance of it being a ten card. That is about a 33% chance!
If you pay attention to what cards have been seen (either by you having it, or seeing it face up with another player), you can eliminate those cards from the possibilities — changing the odds of what you will get.
This is basically what I understand card counting to be. If you play a game, and you know you’ve seen three Kings so far (either in your hand, or played by others), you now know instead of four chances to draw a King, you only have one.
Math is hard. Make the computer do it.
I know this has been done before, and quite possible even on a Radio Shack Color Computer, but I thought it might be fun to create a program that displays the percentage likelihood of drawing a particular card from a deck. I suppose it could have the Blackjack/21 rule, where it treads 10, Jack, Queen and King the same, versus a “whole deck” rule where each card is unique (where you really need an 8 of Clubs to complete some run in poker or whatever game that does that; I barely know blackjack, and have never played poker).
I plan to play with this when I get time, but I decided to post this now in case others might want to work on it as well.
I envision a program that displays all the cards on the screen with a percentage below it. As cards are randomly drawn, that card’s percentage goes to 0% and all the others are adjusted.
I want to visualize some playing card statistics, and the easiest way I could think of to do this was to write a program in BASIC. Of course.
There are many approaches I can take, so as I put figure it out I will “show my work” and experiments that help me figure out which one will work best.
For my task, I will greatly simplify a deck of cards by ignoring suits (I just care if it’s an Ace, a Five, a King or whatever). I figure I can store it as a 52-character string representing each card:
I could “shuffle” this “deck” and end up with a string in “random” order. I believe I have touched on randomizing a list in the past, and had some great suggestions in comments on how to do it better. Forgetting all of that, let’s just say I end up with as string that gets randomized.
But I digress…
The next question will be: “How many Kings are left in the deck?”
Some of you will already see I am heading down a poor path for doing this, but let’s start there anyway.
One way of counting a specific character in a string is to loop through it and use MID$ to pull out an individual character. Something like this:
FOR A=1 TO LEN(A$) IF MID$(A$,A,1)="S" THEN C=C+1 NEXT
That would count all the “S” characters that appear in the string. Since every time MID$ is used it has to build a new string representing that portion of the original string, this should be our slowest way to do this. On a system with tons of strings in use, string manipulation gets real slow. For such a simple program, this might be fast enough.
Another approach, which was originally shown to me during a word wrap article as a submission, would be to use VARPTR to get the memory location of the 5-byte string ID memory, and then go to the memory where the string bytes are stored and use PEEK to look for them. You can find details in my earlier article on VARTPR.
The memory location that VARPTR returns will have the length of string as the first byte (byte 0), then an empty byte (byte 1, always 0), then the next two bytes will be the address where the string is stored (bytes 2 and 3) followed by a zero (byte 4). Knowing this, something like this would do the same thing as MID$:
A=VARPTR(A$) SL=PEEK(A) SS=PEEK(A+2)*256+PEEK(A+3) FOR A=SS TO SS+SL-1 IF PEEK(A)=ASC("S") THEN C=C+1 NEXT
And do it faster.
VARPTR is a legal BASIC function, but it still seems nasty to reach in to string memory to do this. Thus, I came up with the idea of using INSTR. This function returns the start location of a matching string in another string, or 0 if not found:
PRINT INSTR("ABCDEFG","D")
That should print 4, since a “D” is located at the 4th position in the string.
You can also add an additional parameter which is where in the string to start searching. Doing this:
PRINT INSTR(5,"ABCDEFG","D")
…would print 0, because it starts scanning at the 5th character (just past the D) of the string, and then won’t find anymore.
I could start using INSTR with a position of 1 (first character), and if it comes back with a value other than 0, I found one. That value will be the position of the found character. I could then loop back and use that position + 1 to scan again at the character after the match. Repeat until a 0 (no more found) is returned. That lets the scan for characters be done by the assembly code of the BASIC ROM and is even faster. It looks like this:
F=0 xxx F=INSTR(F+1,A$,T$):IF F>0 THEN C=C+1:GOTO xxx
And we put them all together in a benchmark test program…
10 ' COUNTSTR.BAS
20 A$="THIS IS A STRING I AM GOING TO USE FOR TESTING. I WANT IT TO BE VERY LONG SO IT TAKES A LONG TIME TO PARSE."
30 T$="S":T=ASC(T$)
40 '
50 ' MID$
60 '
70 PRINT "MID$:";TAB(9);
80 TIMER=0:C=0
90 FOR A=1 TO LEN(A$)
100 IF MID$(A$,A,1)=T$ THEN C=C+1
110 NEXT
120 PRINT C,TIMER
130 '
140 ' VARPTR
150 '
160 PRINT "VARPTR:";TAB(9);
170 TIMER=0:C=0
180 A=VARPTR(A$)
190 SL=PEEK(A)
200 SS=PEEK(A+2)*256+PEEK(A+3)
210 FOR A=SS TO SS+SL-1
220 IF PEEK(A)=T THEN C=C+1
230 NEXT
240 PRINT C,TIMER
250 '
260 ' INSTR
270 '
280 PRINT "INSTR:";TAB(9);
290 TIMER=0:C=0:F=0
300 F=INSTR(F+1,A$,T$):IF F>0 THEN C=C+1:GOTO 300
310 PRINT C,TIMER
And running prints:
Wow, using INSTR is six times faster than MID$? And four times faster than VARPTR. Nice.
Now you know a bit about what I need to do. I need to represent cards in a deck (and be able to “draw” cards from that deck) and calculate how many of a specific card remain in the deck.
Since I do not need to track or show the suits (hearts, spaced, clubs, diamonds), I figure I could use one byte in a string.
To be continued … but in the meantime, do you have a better approach? Comment away!
Earlier this year, Jason Pittman shared a BASIC program with me that drew representations of all the letters of the alphabet. It was a cute program, and did some fancy drawing.
I thought it might be fun to ask you — in the comments — to tell me how YOU would have done this. I can think of one way, that uses an Extended BASIC keyword, and another way, that would work on Color BASIC.
For a series of options that are sequential (like “A to Z”) there are certainly some options.
As a part two … what if they were not sequential? What if it was for a menu that had options like “A, B, C, D, Q, Z” or whatever? That let me think of a third way to do it to work in Color BASIC.
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:
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…
At this point, we have a nicely functional BASIC version of Lights Out. Some of its features include:
5×5 grid of lights.
Random games.
Allows replaying a specific game.
Counts moves for scoring.
Ability to quite a game in progress.
Slightly less sucky user interface than the original version.
But we still have more to do!
Bigger grid, please
One of the enhancements I mentioned in the previous installment was the ability to specify larger (or smaller, I suppose), grid sizes. We know that the official Lights Out games from Tiger Electronics in the 1990s used a 5×5 and 6×6 grid. Others have since created variations, such as one that operates around a cube (maybe we write that version some day). Let’s start there…
For this simple version, we’ll just arbitrarily pick a maximum grid size of 10×10. This should still fit easily on the CoCo’s 32×16 text screen. For fun, we could also allow a grid size smaller than 5×5 to be chosen. Since the spiritual predecessor of Lights Out was a 1970s game that featured a 3×3 grid, I’ll use that size as the minimum.
The changes needed should be fairly minor. When starting a game, the user will be prompted for the grid size.
It seems we could allow rectangular grids (5×3, 10×5, etc.), but for now we’ll just stick to square grids where both sides are the same size. The array for the grid just needs to be large enough to hold the largest sized grid.
Wrong DIMension
An oversight I made when starting this program was not adding a DIM statement to specify the maximum size of the two-dimensional grid array! The current version really needs a line that contains this at the very top:
DIM L(4,4)
Reminder: DIM is base-0, so it starts counting entries at 0. A DIM X(4) gives entries X(0), X(1), X(2), X(3) and X(4).
The program only works because Color BASIC allows array entries 0-10 before the DIM is needed. You can do A(10)=42 without needing to DIM A(10) first. (I find it weird that Microsoft chose to default to 11 array entries. I bet whoever coded that was thinking “1-10” rather than “0-10”.)
To enhance the program to support up to a 10×10 grid, we’ll allocate an array that large.
4 DIM L(9,9)
Next we need to ask the user what size grid they want. This can be added to the “initialize grid” subroutine:
4000 REM INITIALIZE GRID 4005 INPUT "GRID SIZE (3-10)";GS 4006 IF GS<3 OR GS>10 THEN 4005 ...
After that, any place that is currently hard coded to 4 will need to be changed to use the grid size variable. If the user types in 10 (for a 10×10), the array will be using 0-9. We must pay attention to that and know that a 10 grid is actually 0-9.
This is also a good time to clean up the printing of the grid a bit. Here is the full program with the latest changes in bold:
4 DIM L(9,9) 5 REM SELECT GAME 6 GOSUB 6000 10 REM INITIALIZE GRID 15 MV=0 20 GOSUB 4000 30 REM SHOW GRID 40 GOSUB 1000 47 IF LO=0 THEN 200 50 REM INPUT SQUARE 60 GOSUB 2000 70 REM TOGGLE SQUARES 80 GOSUB 3000 90 REM REPEAT 95 MV=MV+1 100 GOTO 30
200 REM GAME WON 220 PRINT "YOU WON IN";MV;"MOVES." 230 INPUT "PLAY AGAIN (Y/N)";Q$ 240 IF Q$="Y" THEN 5 250 PRINT "GAME OVER" 260 END
1000 REM SHOW GRID 1005 PRINT "GAME NUMBER:";GN 1006 PRINT " "; 1007 FOR A=1 TO GS 1008 PRINT RIGHT$(STR$(A),2); 1009 NEXT:PRINT 1010 FOR Y=0 TO GS-1 1015 PRINT RIGHT$(STR$(Y+1),2);" "; 1020 FOR X=0 TO GS-1 1030 IF L(X,Y) THEN PRINT "X ";:GOTO 1050 1040 PRINT ". "; 1050 NEXT 1060 PRINT 1070 NEXT 1080 PRINT "MOVES:";MV;"LIGHTS ON:";LO 1090 RETURN
2000 REM INPUT SQUARE 2010 PRINT "X,Y (1-";GS;",1-";GS;" OR 0,0)"; 2011 INPUT X,Y 2015 IF X=0 THEN IF Y=0 THEN 230 2020 IF X<1 OR X>GS OR Y<1 OR Y>GS THEN 2010 2025 X=X-1:Y=Y-1 2030 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 4005 INPUT "GRID SIZE (3-10)";GS 4006 IF GS<3 OR GS>10 THEN 4005 4010 PRINT "INITIALIZING..." 4020 FOR A=1 TO 10 4030 Y=RND(GS)-1 4040 X=RND(GS)-1 4050 GOSUB 3000 4060 NEXT 4070 RETURN
6000 REM SELECT GAME 6010 PRINT "PLAY SPECIFIC GAME # (Y/N)?" 6020 S=S+1:A$=INKEY$:IF A$="" THEN 6020 6030 IF A$="Y" THEN 6060 6040 IF A$="N" THEN A=RND(-S) 6045 GN=RND(65535):A=RND(-GN) 6046 GOTO 6090 6050 GOTO 6020 6060 INPUT "PLAY GAME (1-65535)";GN 6070 IF GN<1 OR GN>65535 THEN 6060 6080 A=RND(-GN) 6090 RETURN
This version will display the grid with nicely (?) formatted headers for the columns and rows, and less nicely formatted prompts for what values are allowed.
There is still so much work to do to make the user interface nicer to look at and interact with.
As we begin this part, let’s look at the BASIC Lights Out code as it stands currently:
5 REM SELECT GAME 6 GOSUB 6000 10 REM INITIALIZE GRID 20 GOSUB 4000 30 REM SHOW GRID 40 GOSUB 1000 47 IF LO=0 THEN PRINT "YOU WON!":END 48 PRINT "LIGHTS ON:";LO 50 REM INPUT SQUARE 60 GOSUB 2000 70 REM TOGGLE SQUARES 80 GOSUB 3000 90 REM REPEAT 100 GOTO 30
1000 REM SHOW GRID 1005 PRINT "GAME NUMBER:";GN 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 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):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
4000 REM INITIALIZE GRID 4010 PRINT "INITIALIZING..." 4020 FOR A=1 TO 10 4030 Y=RND(5)-1 4040 X=RND(5)-1 4050 GOSUB 3000 4060 NEXT 4070 RETURN
6000 REM SELECT GAME 6010 PRINT "PLAY SPECIFIC GAME # (Y/N)?" 6020 S=S+1:A$=INKEY$:IF A$="" THEN 6020 6030 IF A$="Y" THEN 6060 6040 IF A$="N" THEN A=RND(-S) 6045 GN=RND(65535):A=RND(-GN) 6046 GOTO 6090 6050 GOTO 6020 6060 INPUT "PLAY GAME (1-65535)";GN 6070 IF GN<1 OR GN>65535 THEN 6060 6080 A=RND(-GN) 6090 RETURN
There are quite a few more things that could (and probably need to) be done:
The number of moves taken should be counted and displayed.
If the game is won, it just ENDs in line 47. It could ask the player if they want to play again.
There is no way to quit a game in progress other than hitting the break key.
The user interface (typing in coordinates such as “0,2”) is user-hostile. At the very least, we could label the rows/columns to show which values to type. Perhaps even labeling them “A B C D E …” across, and “1 2 3 4 5” vertically, similar to how chess boards are done.
Options for grids larger than 5×5 could be implemented with very little change in the code.
Once the game is “code complete“, there is also be some cleanup and renumbering that should be done. (Having those odd line numbers like 47 bugs me.)
Score (Counting Moves)
The current code only knows that you won the game (the game ends) or you are still playing. If one person plays game number 16809 and solves it in 80 moves, and another person plays the same game and solves it in 10 moves, they are both treated to the same ending – the game exits.
Let’s add a move counter. We’ll reset it at the start of a game…
10 REM INITIALIZE GRID 15 MV=0 20 GOSUB 4000
…and increment it after every move:
90 REM REPEAT 95 MV=MV+1 100 GOTO 30
We should also display the move count each time the grid is display. This is a good time to also move the “number of lights on” in to the display grid routine, too.
48 PRINT "LIGHTS ON:";LO (removed)
1000 REM SHOW GRID 1005 PRINT "GAME NUMBER:";GN 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 "MOVES:";MV;"LIGHTS ON:";LO 1090 RETURN
This now gives us a display of our game number (which was already there), the number of moves made so far (new), and the current count of how many lights are on (already there).
DONE: The number of moves taken should be counted and displayed.
Game Over
The next thing I want to add is a game over screen. When the game is on, this screen should display how many moves it took. It could then prompt the user to see if they want to play agan.
It could be as simple as this:
47 IF LO=0 THEN 200
200 REM GAME WON 220 PRINT "YOU WON IN";MV;"MOVES." 230 INPUT "PLAY AGAIN (Y/N)";Q$ 240 IF Q$="Y" THEN 5 250 PRINT "GAME OVER" 260 END
DONE: If the game is won, it just ENDs in line 47. It could ask the player if they want to play again.
I give up!
The user should also be able to quit. Currently, the awful “type in an X,Y coordinate” thing is yucky, but we could make typing “-1,-1” end the game. (We will fix the user interface later.)
2000 REM INPUT SQUARE 2010 INPUT "X,Y (0-4,0-4 OR -1,-1)";X,Y 2015 IF X=-1 THEN IF Y=-1 THEN 230 2020 IF X<0 OR X>4 OR Y<0 OR Y>4 THEN 2010 2030 RETURN
This is so yucky, but the goal here is to get the code fully functional. We can make it nice later.
DONE: There is no way to quit a game in progress other than hitting the break key.
The user interface sucks!
Okay, this one will take more work, but a quick enhancement might be to just print the columns and rows so the player knows what to type. Also, humans like counting from one instead of zero (base-1 humans) so typing in things to match the base-0 arrays is not very human friendly. We can fix both things here.
1000 REM SHOW GRID 1005 PRINT "GAME NUMBER:";GN 1006 PRINT " 12345" 1010 FOR Y=0 TO 4 1015 PRINT Y+1; 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 "MOVES:";MV;"LIGHTS ON:";LO 1090 RETURN
…and…
2000 REM INPUT SQUARE 2010 INPUT "X,Y (1-5,1-5 OR 0,0)";X,Y 2015 IF X=0 THEN IF Y=0 THEN 230 2020 IF X<1 OR X>5 OR Y<1 OR Y>5 THEN 2010 2025 X=X-1:Y=Y-1 2030 RETURN
Now the user will see numbers above the grid, and to the left of the grid, and be able to enter them using 1-5 rather than 0-4. I made 0,0 exit as well to save the use from having to type the minus signs.
But, this interface still sucks. After we get the “text and typing” version done, we can do one that is more modern and uses the arrow keys to cursor around and select squares.
DONE: The user interface (typing in coordinates such as “0,2”) is user-hostile.
Bigger grid, please
Hey, let’s just get the basic game working first, then we can worry about “Deluxe Lights Out.”
DEFERRED: Options for grids larger than 5×5 could be implemented with very little change in the code.