/*
LED_Matrix_TTT4.C - An AI version of Tic-Tac-Toe, played on a 4x4x4 LED matrix
powered by an Arduino
Copyright (C) 2011 Jeffrey R. Gilmour, Ph.D.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
#include
#include
#include // Uses pins 3 & 11, so DON'T USE those
const int loadPin = 4;
const int clockPin = 5;
const int dataPin = 6;
Matrix myMatrix = Matrix(dataPin, clockPin, loadPin);
#define FALSE 0
#define TRUE !FALSE
boolean OLedsOn = TRUE;
char cube0[4][4][4], cube1[4][4][4], cube2[4][4][4], cube3[4][4][4], human_symbol='X', comp_symbol='O';
const int period = 200;
const int numRows = 2;
const int rowPins[numRows] = { 7, 8 }; // Keypad pins 1 & 2, attached to Arduino pins 7 & 8
const int numCols = 3;
const int colPins[numCols] = { 9, 10, 12 }; // Keypad pins 5, 6 & 7
const int debounceTime = 200;
const char keymap[numRows][numCols] = { // USE PINS 1, 2, 5, 6, 7 on keypad
{ '1', '2', '3' },
{ '4', '5', '6' }
};
char key = 0;
int state = 0, state1x = 5, state1y = 5, state1z = 5;
int bestmovex, bestmovey, bestmovez;
char getKey() {
key = 0;
for (int column = 0; column < numCols; column++) {
digitalWrite(colPins[column], LOW);
for (int row = 0; row < numRows; row++) {
if(digitalRead(rowPins[row]) == LOW) {
delay(debounceTime);
while(digitalRead(rowPins[row]) == LOW) ;
key = keymap[row][column];
}
}
digitalWrite(colPins[column], HIGH);
}
return key;
}
char check(char board[4][4]) {
int i;
for (i=0;i<4;i++) /* check rows */
if (board[i][0]==board[i][1] &&
board[i][0]==board[i][2] &&
board[i][0]==board[i][3]) if (board[i][0]!=' ')
return board[i][1];
for (i=1;i<=4;i++) /* check columns */
if (board[0][i]==board[1][i] &&
board[0][i]==board[2][i] &&
board[0][i]==board[3][i]) if (board[0][i]!=' ')
return board[1][i];
/* test diagonals */
if (board[0][0]==board[1][1] && board[1][1]==board[2][2] &&
board[2][2]==board[3][3] && board[0][0]!=' ')
return board[1][1];
if (board[0][3]==board[1][2] && board[1][2]==board[2][1] &&
board[2][1]==board[3][0] && board[0][3]!=' ')
return board[1][2];
return ' ';
}
void disp_LED_cube(void) {
if (state == 0) { // Default display of all moves
for (int z=0;z<4;z++)
for (int y=0;y<4;y++)
for (int x=0;x<4;x++) {
if (OLedsOn) {
if (cube0[x][y][z] == 'O') {
if (z == 0) {
myMatrix.write(y, x, LOW);
} else if (z == 1) {
myMatrix.write(y, x+4, LOW);
} else if (z == 2) {
myMatrix.write(y+4, x+4, LOW);
} else if (z == 3) {
myMatrix.write(y+4, x, LOW);
}
}
} else {
if (cube0[x][y][z] != ' ') {
if (z == 0) {
myMatrix.write(y, x, HIGH);
} else if (z == 1) {
myMatrix.write(y, x+4, HIGH);
} else if (z == 2) {
myMatrix.write(y+4, x+4, HIGH);
} else if (z == 3) {
myMatrix.write(y+4, x, HIGH);
}
}
}
}
OLedsOn = !OLedsOn;
}
if (state == 1) { // setting up player's move
myMatrix.clear();
if (state1z < 5) {
if (state1z == 0) {
myMatrix.write(state1y, state1x, HIGH);
} else if (state1z == 1) {
myMatrix.write(state1y, state1x+4, HIGH);
} else if (state1z == 2) {
myMatrix.write(state1y+4, state1x+4, HIGH);
} else if (state1z == 3) {
myMatrix.write(state1y+4, state1x, HIGH);
}
} else
if (state1y < 5) {
for (int z=0;z<4;z++)
if (z == 0) {
myMatrix.write(state1y, state1x, HIGH);
} else if (z == 1) {
myMatrix.write(state1y, state1x+4, HIGH);
} else if (z == 2) {
myMatrix.write(state1y+4, state1x+4, HIGH);
} else if (z == 3) {
myMatrix.write(state1y+4, state1x, HIGH);
}
} else
if (state1x < 5) {
for (int z=0;z<4;z++)
for (int y=0;y<4;y++) {
if (z == 0) {
myMatrix.write(y, state1x, HIGH);
} else if (z == 1) {
myMatrix.write(y, state1x+4, HIGH);
} else if (z == 2) {
myMatrix.write(y+4, state1x+4, HIGH);
} else if (z == 3) {
myMatrix.write(y+4, state1x, HIGH);
}
}
}
}
if (state == 3) { // blinking last computer move
if (OLedsOn) {
if (bestmovez == 0) {
myMatrix.write(bestmovey, bestmovex, LOW);
} else if (bestmovez == 1) {
myMatrix.write(bestmovey, bestmovex+4, LOW);
} else if (bestmovez == 2) {
myMatrix.write(bestmovey+4, bestmovex+4, LOW);
} else if (bestmovez == 3) {
myMatrix.write(bestmovey+4, bestmovex, LOW);
}
} else {
if (bestmovez == 0) {
myMatrix.write(bestmovey, bestmovex, HIGH);
} else if (bestmovez == 1) {
myMatrix.write(bestmovey, bestmovex+4, HIGH);
} else if (bestmovez == 2) {
myMatrix.write(bestmovey+4, bestmovex+4, HIGH);
} else if (bestmovez == 3) {
myMatrix.write(bestmovey+4, bestmovex, HIGH);
}
}
OLedsOn = !OLedsOn;
}
if (state == 4) { // computer "thinking"
myMatrix.clear();
for (int z=0;z<4;z++)
for (int y=0;y<4;y++)
for (int x=0;x<4;x++) {
if (OLedsOn) {
if (cube3[x][y][z] == 'O') {
if (z == 0) {
myMatrix.write(y, x, LOW);
} else if (z == 1) {
myMatrix.write(y, x+4, LOW);
} else if (z == 2) {
myMatrix.write(y+4, x+4, LOW);
} else if (z == 3) {
myMatrix.write(y+4, x, LOW);
}
}
} else {
if (cube3[x][y][z] != ' ') {
if (z == 0) {
myMatrix.write(y, x, HIGH);
} else if (z == 1) {
myMatrix.write(y, x+4, HIGH);
} else if (z == 2) {
myMatrix.write(y+4, x+4, HIGH);
} else if (z == 3) {
myMatrix.write(y+4, x, HIGH);
}
}
}
}
OLedsOn = !OLedsOn;
}
}
char check_cube(char cube[4][4][4]) {
int x, y, z, n=0;
char done, board[4][4];
for (z=0;z<4;z++) { /* boards in Z direction */
for (y=0;y<4;y++)
for (x=0;x<4;x++)
board[x][y]=cube[x][y][z];
done=check(board);
if (done!=' ') return done;
}
for (z=0;z<4;z++) { /* boards in Y direction */
for (y=0;y<4;y++)
for (x=0;x<4;x++)
board[x][y]=cube[x][z][y];
done=check(board);
if (done!=' ') return done;
}
for (z=0;z<4;z++) { /* boards in X direction */
for (y=0;y<4;y++)
for (x=0;x<4;x++)
board[x][y]=cube[z][y][x];
done=check(board);
if (done!=' ') return done;
}
/* 3D diagonal 1 */
if (cube[0][0][0]==cube[1][1][1] && cube[0][0][0]==cube[2][2][2]
&& cube[0][0][0]==cube[3][3][3] && cube[0][0][0]!=' ')
return cube[0][0][0];
/* 3D diagonal 2 */
if (cube[0][0][3]==cube[1][1][2] && cube[0][0][3]==cube[2][2][1]
&& cube[0][0][3]==cube[3][3][0] && cube[0][0][3]!=' ')
return cube[0][0][3];
/* 3D diagonal 3 */
if (cube[3][0][0]==cube[2][1][1] && cube[3][0][0]==cube[1][2][2]
&& cube[3][0][0]==cube[0][3][3] && cube[3][0][0]!=' ')
return cube[3][0][0];
/* 3D diagonal 4 */
if (cube[0][3][0]==cube[1][2][1] && cube[0][3][0]==cube[2][1][2]
&& cube[0][3][0]==cube[3][0][3] && cube[0][3][0]!=' ')
return cube[0][3][0];
for (z=0;z<4;z++) /* if no spaces are empty, game is a draw */
for (y=0;y<4;y++)
for (x=0;x<4;x++)
if (cube[x][y][z]==' ') n++;
if (n==0) {
// Game is a draw - how to communicate to player?
loop();
}
return done;
}
void copy_cube(char b2[4][4][4], char b1[4][4][4]) {
int x,y,z;
for (z=0;z<4;z++)
for (y=0;y<4;y++)
for (x=0;x<4;x++) b2[x][y][z]=b1[x][y][z];
}
void get_computer_move(void) {
long int s0=-100000, s1, s2;
int test=0, x1, y1, z1, x2, y2, z2, x3, y3, z3, NoGood, NoGood2;
int score;
char done;
state = 4; // computer "thinking" LED display
for (z1=0;z1<4;z1++)
for (y1=0;y1<4;y1++)
for (x1=0;x1<4;x1++) {
s1=100000;
NoGood=FALSE;
if (cube0[x1][y1][z1]==' ') {
copy_cube(cube1,cube0);
cube1[x1][y1][z1]=comp_symbol;
done = check_cube(cube1); // Computer has WON!
if (done==comp_symbol) {
bestmovex = x1;
bestmovey = y1;
bestmovez = z1;
state = 3; // blinking last move of computer
while (getKey() == 0) ;
state = 0;
copy_cube(cube0,cube1);
while (getKey() == 0) ;
loop();
}
for (z2=0;z2<4 && !NoGood;z2++)
for (y2=0;y2<4 && !NoGood;y2++)
for (x2=0;x2<4 && !NoGood;x2++) {
s2=-100000;
NoGood2=FALSE;
if (cube1[x2][y2][z2]==' ') {
copy_cube(cube2,cube1);
cube2[x2][y2][z2]=human_symbol;
for (z3=0;z3<4 && !NoGood2;z3++)
for (y3=0;y3<4 && !NoGood2;y3++)
for (x3=0;x3<4 && !NoGood2;x3++) {
if (cube2[x3][y3][z3]==' '){
copy_cube(cube3,cube2);
cube3[x3][y3][z3]=comp_symbol;
score=score_cube(cube3);
if (s2s1) NoGood2=TRUE;
}
}
}
if (s1>s2) {
s1=s2;
if (s10 && tally_human>0) {
tally_comp=0;
tally_human=0;
}
comp[tally_comp]++;
human[tally_human]++;
tally_comp=0;
tally_human=0;
for (z=0;z<4;z++) { /* 3D diagonal 2 */
tally_comp+=(cube[3-z][z][z]==comp_symbol);
tally_human+=(cube[3-z][z][z]==human_symbol);
}
if (tally_comp>0 && tally_human>0) {
tally_comp=0;
tally_human=0;
}
comp[tally_comp]++;
human[tally_human]++;
tally_comp=0;
tally_human=0;
for (z=0;z<4;z++) { /* 3D diagonal 3 */
tally_comp+=(cube[z][3-z][z]==comp_symbol);
tally_human+=(cube[z][3-z][z]==human_symbol);
}
if (tally_comp>0 && tally_human>0) {
tally_comp=0;
tally_human=0;
}
comp[tally_comp]++;
human[tally_human]++;
tally_comp=0;
tally_human=0;
for (z=0;z<4;z++) { /* 3D diagonal 4 */
tally_comp+=(cube[z][z][3-z]==comp_symbol);
tally_human+=(cube[z][z][3-z]==human_symbol);
}
if (tally_comp>0 && tally_human>0) {
tally_comp=0;
tally_human=0;
}
comp[tally_comp]++;
human[tally_human]++;
scores+=comp[1]-3*human[1]+7*comp[2]-15*human[2]+31*comp[3]-63*human[3]+127*comp[4]-1000*human[4];
return scores;
}
int score(char board[4][4]) {
int x, y, tally_comp=0, tally_human=0, comp[5]={0,0,0,0,0}, human[5]={0,0,0,0,0};
for (y=0;y<4;y++) { /* Rows */
for (x=0;x<4;x++) {
tally_comp+=(board[x][y]==comp_symbol);
tally_human+=(board[x][y]==human_symbol);
}
if (tally_comp>0 && tally_human>0) {
tally_comp=0;
tally_human=0;
}
comp[tally_comp]++;
human[tally_human]++;
tally_comp=0;
tally_human=0;
}
for (x=0;x<4;x++) { /* Columns */
for (y=0;y<4;y++) {
tally_comp+=(board[x][y]==comp_symbol);
tally_human+=(board[x][y]==human_symbol);
}
if (tally_comp>0 && tally_human>0) {
tally_comp=0;
tally_human=0;
}
comp[tally_comp]++;
human[tally_human]++;
tally_comp=0;
tally_human=0;
}
for (y=0;y<4;y++) { /* Down diagonal */
tally_comp+=(board[y][y]==comp_symbol);
tally_human+=(board[y][y]==human_symbol);
}
if (tally_comp>0 && tally_human>0) {
tally_comp=0;
tally_human=0;
}
comp[tally_comp]++;
human[tally_human]++;
tally_comp=0;
tally_human=0;
for (y=0;y<4;y++) { /* Up diagonal */
tally_comp+=(board[3-y][y]==comp_symbol);
tally_human+=(board[3-y][y]==human_symbol);
}
if (tally_comp>0 && tally_human>0) {
tally_comp=0;
tally_human=0;
}
comp[tally_comp]++;
human[tally_human]++;
y=comp[1]-3*human[1]+7*comp[2]-15*human[2]+31*comp[3]-63*human[3]+127*comp[4]-1000*human[4];
/* Needs to be the same evaluation function as in 'score_cube' function */
return y;
}
void init_cube (void) {
int x,y,z;
for(z=0;z<4;z++)
for(y=0;y<4;y++)
for(x=0;x<4;x++) cube0[x][y][z]=' ';
}
void get_player_move(void) {
int x,y,z;
while (getKey() == 0) ;
state = 1;
state1x = 5;
state1y = 5;
state1z = 5;
do {
while (getKey() == 0) ;
if (key < '5')
state1x = (key - '0') - 1;
} while (key != '5' || state1x > 3 );
x = state1x;
do {
while (getKey() == 0) ;
if (key < '5')
state1y = (key - '0') - 1;
} while (key != '5' || state1y > 3 );
y = state1y;
do {
while (getKey() == 0) ;
if (key < '5')
state1z = (key - '0') - 1;
} while (key != '5' || state1z > 3 );
z = state1z;
if (cube0[x][y][z]!=' ') { // Invalid move - redo entry
get_player_move();
}
cube0[x][y][z]=human_symbol;
state = 0;
}
void setup() {
for (int row = 0; row < numRows; row++) {
pinMode(rowPins[row], INPUT);
digitalWrite(rowPins[row], HIGH);
}
for (int column = 0; column < numCols; column++) {
pinMode(colPins[column], OUTPUT);
digitalWrite(colPins[column], HIGH);
}
}
void loop() {
char done, Buf[12];
int x,y,z;
state = 1; // Pre-game cube LED flashing
done = ' ';
myMatrix.clear();
init_cube();
MsTimer2::set(period, disp_LED_cube);
MsTimer2::start();
while (getKey() == 0) ; // Press 1 to go first
state = 0; // Normal display of cube LED
if (key != '1') {
human_symbol='O';
comp_symbol='X';
get_computer_move();
}
do {
state = 0;
get_player_move();
done=check_cube(cube0);
if (done != ' ') break;
get_computer_move();
done=check_cube(cube0);
} while (done==' ');
if (done==human_symbol) ; // Player has WON! - how to communicate?
else ; // Compute has WON! - how to communicate?
}