I think your win checking algorithm can be improved by using prime numbers. I’ll post more on how to do this with your code later. If you want to look at the Naughts and Crosses Engine that I’m working on, it can be found here. Its a little hard to extract what is going on in my program due to me trying to work with limited memory for the framework I’m trying to make in basic. More information on my project is on the Menace Page
Okay, so, a bit more information on how you can use prime numbers to detect a win. Imagine if each of the spots on the gameboard had an id. Some people may number them 1 through 9, but if we take advantage of the properties of primes the math gets kind of interesting. Consider if we use the following assignments for ID's:
By using the first 9 primes, each row column and diagonal will multiply together to a unique product (30, 1001, 7429, 238, 627, 1495, 506, or 935). If you take the ID's of all the spots that player one has played and multiply them together, you will get some number. Now, because we used the prime numbers, we know that a number will be evenly divisible by the product of three primes if and only if all three primes of the product were used to create the output.
I know that is a bit confusing, so let me give a bit of an example. If Player 1 has played {2,3,11}, the product of those numbers is 66. If we divide 66 by each of the numbers in our list of unique products, there will not be any products which have a remainder of 0. Since there are none with a remainder of 0, there is not a win condition. On the next turn of Player 1 they have 2 choices, play in spot 5, or play in spot 23. Lets examine them more closely, as well as a poor choice:
Playing spot 5:
- Take all of Player 1's moves, and find the product: f({2,3,11,5}) = 330
- Find the remainder when dividing by each of the products: 330 mod {30, 1001, 7429, 238, 627, 1495, 506, 935 } ={0, 330, 330, 92, 330, 330, 330, 330}
- 330 has a remainder of 0 when divided by 30; The player has won in Row 1
Playing spot 23:
- Take all of Player 1's moves, and find the product: f({2,3,11,23}) = 1518
- Find the remainder when dividing by each of the products: 1518 mod {30, 1001, 7429, 238, 627, 1495, 506, 935 } ={18, 517, 1518, 90, 264, 23, 0, 583}
- 1518 has a remainder of 0 when divided by 506; The player has won in Diagonal Left
Playing spot 23:
- Take all of Player 1's moves, and find the product: f({2,3,11,17}) = 1122
- Find the remainder when dividing by each of the products: 1122 mod {30, 1001, 7429, 238, 627, 1495, 506, 935 } ={12, 121, 1122, 170, 495, 1122, 110, 187}
- 1122 is not evenly divisible by any of our products, there is a remainder on all of them; The player has not won
So how does this optimize your code? Well, it reduces the number of variables you need, and makes the "If" statements smaller. Its also important to note that you have a memory-leak on your statement If A=1.1: Goto A, even though it can never be reached. My optimized version of your code is below. I like your UI, but it can be a little difficult to understand how to use it without knowing to use the number keys. I would also suggest letting the user press "clear" (A=4.5) as a way to quickly exit the program without finishing the game, but I did not implement it into my optimization, as I wanted the UI to be the same as yours.
Lbl Y
ClrHome
AxesOff
ZStandard
ZSquare
Vertical 4
Vertical ⁻4
Line(⁻11,3.7,11,3.7
Line(⁻11,⁻3.7,11,⁻3.7
{1→L₁
{30,1001,7429,238,627,1495,506,935→L₂
[[2,3,5][7,11,13][17,19,23]]→[A]
Repeat 10=dim(L₁
DelVar A
Repeat A>7.1 and A<9.5
.1getKey→A
End
10fPart(A)-1→C
iPart(A)-6→D
If not(sum(L₁=[A](D,C:Then
7.5C-15→X
⁻7D+14→Y
[A](D,C→L₁(1+dim(L₁
1+dim(L₁→T
If fPart(T/2:Then
Line(X-2,Y-2,X+2,Y+2,18
Line(X-2,Y+2,X+2,Y-2,18
Else
Circle(X,Y,2
End
1→W
For(E,1+not(not(fPart(T/2))),dim(L₁),2
WL₁(E→W
End
sum(L₂not(fPart(W/L₂→W
If sum(W={1495,627,238:Line(X,8,X,⁻8,11
If sum(W={30,1001,7429:Line(⁻8.5,Y,8.5,Y,11
If W=506:Line(⁻7.5,7,7.5,⁻7,11
If W=935:Line(⁻7.5,⁻7,7.5,7,11
If W:Text(⁻1,10,10,sub("XO",(fPart(T/2)=0)+1,1)+" WINS!!!
If not(W) and dim(L₁)=10:Text(⁻1,10,10,"TIE GAME!
If W:10→dim(L₁
If W+(dim(L₁)=10:Pause
End
End
Menu("PLAY AGAIN?","YES",Y,"NO",N
Lbl N
ClrDraw
ClrHome
"
Lbl Y
ClrHome
AxesOff
ZStandard
ZSquare
Vertical 4
Vertical ⁻4
Line(⁻11,3.7,11,3.7
Line(⁻11,⁻3.7,11,⁻3.7
//This is where the changes start
//Put a 1 into L1 so we don't get DIM errors later on; This is the only value that wont effect our calculations
{1→L₁
//Store our list of win conditions into L2
{30,1001,7429,238,627,1495,506,935→L₂
//
[[2,3,5][7,11,13][17,19,23]]→[A]
//Since we are using L1 to store data, we don't need to rely on the turn counter.
Repeat 10=dim(L₁
//Get the Key, this is the same as you had it
DelVar A
Repeat A>7.1 and A<9.5
.1getKey→A
End
10fPart(A)-1→C
iPart(A)-6→D
//This is the next area with differences
//We check if L1 contains the ID of the square the user tried before doing anything
If not(sum(L₁=[A](D,C:Then
7.5C-15→X
⁻7D+14→Y
//We put the ID of the square that was played onto the end of L1 so that we log the turn
[A](D,C→L₁(1+dim(L₁
//We store the size of the list +1 into T so that we save space doing math with T
//This is effectively the same thing as the turn counter, but is dependent on the number of spaces that are actually played
1+dim(L₁→T
//Your code for displaying the icons here; I did remove the color code from circle as I was testing on monochrome, but you should be able to add it back in
If fPart(T/2:Then
Line(X-2,Y-2,X+2,Y+2,18
Line(X-2,Y+2,X+2,Y-2,18
Else
Circle(X,Y,2
End
//Now lets check our win conditions
//We don't need to wait until the 4th turn, because our checking algorithm will tell us there is no win, and the processing is fast enough anyways
//Store 1 into W to reset our win condition
1→W
//This For loop looks a bit confusing, but its just determining whether to start at position 1 or position 2 of the list, based on how many spots have been played
//It multiplies together the ID's of all of the spots the player who just took a turn has played
For(E,1+not(not(fPart(T/2))),dim(L₁),2
WL₁(E→W
End
//This is what actually returns the result of our win conditions. I'll break it down piece by piece here
/*
Recall that L2 is our list of "Magic Numbers";
W/L2 returns a list of the answers when W is divided by all the values in L2; and so we do that, but then take the fPart of all the list values
not( changes 0's into 1's, and all other values into 0's in our list. We should now have a list that looks like {0,0,0,0,0,0,0,0} -or- {0,0,0,1,0,0,0,0}
We multiply this by L2 again to turn the 1's into the magic numbers they correspond to; e.g {0,0,0,238,0,0,0}
Taking the sum( of this list will return the exact magic number that is the win condition; which we store back into W
If there is no win, then W will be 0
*/
sum(L₂not(fPart(W/L₂→W
//Similar logic to above. We check if W is one of the three values, and if it is, we display the line
If sum(W={1495,627,238:Line(X,8,X,⁻8,11
If sum(W={30,1001,7429:Line(⁻8.5,Y,8.5,Y,11
//Check the win conditions for the diagonals
If W=506:Line(⁻7.5,7,7.5,⁻7,11
If W=935:Line(⁻7.5,⁻7,7.5,7,11
//Display who won, or if it was a tie
If W:Text(⁻1,10,10,sub("XO",(fPart(T/2)=0)+1,1)+" WINS!!!
If not(W) and dim(L₁)=10:Text(⁻1,10,10,"TIE GAME!
//If someone won, set the condition so we exit the loop; This is where you had 9->T
If W:10→dim(L₁
//If we have won, or if the game is over due to a tie, then Pause. Otherwise, continue
If W+(dim(L₁)=10:Pause
End
End
//Your code for the menu
Menu("PLAY AGAIN?","YES",Y,"NO",N
Lbl N
ClrDraw
ClrHome
"