Después de más de un año sin publicar vuelvo a la carga con un montaje para jugar al tres en raya con arduino. Para ello usaré una pantalla LCD y un joystick. El esquema es el siguiente:
En él hemos usado una pantalla Nokia LCD 5110, un módulo joystick, 6 resistencias de 560 Ω y 6 de 1KΩ. Las resistencias bajan el voltaje de 5V a 3.3V, que es el que usa el módulo LCD. Las conexiones del módulo LCD, que no se aprecian en el esquema son las siguientes:
El joystick tiene los siguientes pines:
- Cables rojos. Pines VCC y Led
- Cable amarillo. Pin RST
- Cable verde. Pin D/C
- Cable azul. Pin DN(MOSI)
- Cable naranja. Pin SCLK
- Cables negros. Pines GND y SCE
El joystick tiene los siguientes pines:
- GND. Conectado a tierra
- SW. Nombrado como SEL en el esquema. Es un pin digital. Detecta cuándo se ha pulsado el botón del joystick. En el que he usado devuelve LOW cuando se pulsa y HIGH en posición normal.
- VRx. Nombrado como Xout en el esquema. Es un pin analógico que devuelve un valor entre 0 y 1023 dependiendo de la posición del eje X del joystick.
- VRy. Nombrado Yout en el esquema. Es un pin analógico que devuelve un valor entre 0 y 1023 dependiendo de la posición del eje Y del joystick.
- +5V. Nombrado Vcc en el esquema. Es la alimentación del joystick.
Aquí está montado en la placa de prototipo:
Y el montaje en funcionamiento:
El código fuente del sketch se divide en dos ficheros:
- types.h. Este fichero es necesario porque el compilador de arduino obliga a que los tipos de datos definidos por el usuario y las funciones que tienen como valor de retorno alguno de estos tipos estén en un fichero separado del principal.
enum cellState{empty,black,white}; //Estados de cada casilla del juego
struct boardGame{
byte xCursor; //Coordenada x del cursor byte yCursor; //Coordenada y del cursor
cellState board[3][3]{{empty,empty,empty},{empty,empty,empty},{empty,empty,empty}}; //Tablero de juego
byte emptyCells=9; //Numero de casillas sin fichas
bool stopPlay=false; //Parar el juego
};
/*****************************************************************************/
/* Funcion winner. Devuelve el color de la jugada ganadora en el */
/* estado actual del juego. Empty si nadie gana */
/* */
/* Parametros: */
/* boardGame b-->Tablero de juego */
/*****************************************************************************/
cellState winner(boardGame b){
for(byte i=0;i<3;++i){
for(byte j=0;j<3;++j){
if(b.board[i][j]!=empty){
//horizontal
bool tmpWin=true;
for(byte x=1;x<3;++x){
tmpWin=tmpWin && (b.board[x][j]==b.board[x-1][j]);
}
if(tmpWin){
return b.board[i][j];
}
//vertical
if(!tmpWin){
tmpWin=true;
for(byte y=1;y<3;++y){
tmpWin=tmpWin && (b.board[i][y]==b.board[i][y-1]);
}
}
if(tmpWin){
return b.board[i][j];
}
//diagonalVID_20151226_131816
if(!tmpWin && i==0 && j==0){
tmpWin=true;
for(byte x=1,y=1;x<3 && y<3;++x,++y){
tmpWin=tmpWin && (b.board[x][y]==b.board[x-1][y-1]);
}
}
if(tmpWin){
return b.board[i][j];
}
if(!tmpWin && i==0 && j==2){
tmpWin=true;
for(byte x=1,y=1;x>0 && y<3;++x,--y){
tmpWin=tmpWin && (b.board[x][y]==b.board[x-1][y+1]);
}
}
if(tmpWin){
return b.board[i][j];
}
}
}
}
return empty;
}
struct boardGame{
byte xCursor; //Coordenada x del cursor byte yCursor; //Coordenada y del cursor
cellState board[3][3]{{empty,empty,empty},{empty,empty,empty},{empty,empty,empty}}; //Tablero de juego
byte emptyCells=9; //Numero de casillas sin fichas
bool stopPlay=false; //Parar el juego
};
/*****************************************************************************/
/* Funcion winner. Devuelve el color de la jugada ganadora en el */
/* estado actual del juego. Empty si nadie gana */
/* */
/* Parametros: */
/* boardGame b-->Tablero de juego */
/*****************************************************************************/
cellState winner(boardGame b){
for(byte i=0;i<3;++i){
for(byte j=0;j<3;++j){
if(b.board[i][j]!=empty){
//horizontal
bool tmpWin=true;
for(byte x=1;x<3;++x){
tmpWin=tmpWin && (b.board[x][j]==b.board[x-1][j]);
}
if(tmpWin){
return b.board[i][j];
}
//vertical
if(!tmpWin){
tmpWin=true;
for(byte y=1;y<3;++y){
tmpWin=tmpWin && (b.board[i][y]==b.board[i][y-1]);
}
}
if(tmpWin){
return b.board[i][j];
}
//diagonalVID_20151226_131816
if(!tmpWin && i==0 && j==0){
tmpWin=true;
for(byte x=1,y=1;x<3 && y<3;++x,++y){
tmpWin=tmpWin && (b.board[x][y]==b.board[x-1][y-1]);
}
}
if(tmpWin){
return b.board[i][j];
}
if(!tmpWin && i==0 && j==2){
tmpWin=true;
for(byte x=1,y=1;x>0 && y<3;++x,--y){
tmpWin=tmpWin && (b.board[x][y]==b.board[x-1][y+1]);
}
}
if(tmpWin){
return b.board[i][j];
}
}
}
}
return empty;
}
- 3EnRaya.ino
#include <SPI.h>
#include <Adafruit_GFX.h>
#include <Adafruit_PCD8544.h>
#include "types.h"
//LCD
const byte SCLKpin = 13;
const byte DNpin = 11;
const byte DCpin = 12;
const byte RESETpin = 10;
//Joystick
const int xAxis=A1;
const int yAxis=A0;
const int button=2;
const char contrast=0;
int fontWidth=6;
int fontHeigth=10;
int textSize=2;
boardGame board;
Adafruit_PCD8544 lcd(SCLKpin,DNpin,DCpin,RESETpin);
/******************************************************************/
/* Funcion evaluatePlay. Devuelve el numero de jugadas ganadoras */
/* si se juega una ficha en una casilla determinada */
/* */
/* Parametros: */
/* boardGame b-->Tablero de juego */
/* short x-->Coordenada x de la casilla que vamos a evaluar */
/* short y-->Coordenada y de la casilla que vamos a evaluar */
/* cellState c-->Color de la ficha con la que se pretende jugar */
/******************************************************************/
short evaluatePlay(boardGame b,short x,short y,cellState c){
short winCount=0;
short cellCount=0;
//Horizontal
for(byte j=0;j<3;++j){
if(b.board[x][j]==c || b.board[x][j]==empty){
++cellCount;
}
}
if(cellCount==3){
++winCount;
}
cellCount=0;
//Vertical
for(byte i=0;i<3;++i){
if(b.board[i][y]==c || b.board[i][y]==empty){
++cellCount;
}
}
if(cellCount==3){
++winCount;
}
cellCount=0;
// diagonal
if(x==y){
for(byte i=0,j=0;i<3 && j<3;++i,++j){
if(b.board[i][j]==c || b.board[i][j]==empty){
++cellCount;
}
}
if(cellCount==3){
++winCount;
}
}
cellCount=0;
if(x==y || abs(x-y)==2){
for(byte i=0,j=0;i>0 && j<3;++i,--j){
if(b.board[i][j]==c || b.board[i][j]==empty){
++cellCount;
}
}
if(cellCount==3){
++winCount;
}
}
return winCount;
}
/******************************************************************/
/* Funcion drawWinnerPlay. Dibuja una linea sobre la jugada */
/* ganadora */
/* */
/* Parametros: */
/* boardGame b-->Tablero de juego */
/******************************************************************/
void drawWinnerPlay(boardGame b){
for(byte i=0;i<3;++i){
for(byte j=0;j<3;++j){
if(b.board[i][j]!=empty){
//horizontal
bool tmpWin=true;
for(byte x=1;x<3;++x){
tmpWin=tmpWin && (b.board[x][j]==b.board[x-1][j]);
}
if(tmpWin){
lcd.drawLine(0, ((j+1)*16)-8, 84, ((j+1)*16)-8, BLACK);
}
//vertical
if(!tmpWin){
tmpWin=true;
for(byte y=1;y<3;++y){
tmpWin=tmpWin && (b.board[i][y]==b.board[i][y-1]);
}
if(tmpWin){
lcd.drawLine(((i+1)*28)-14, 0, ((i+1)*28)-14, 84, BLACK);
}
}
//diagonal
if(!tmpWin && i==0 && j==0){
tmpWin=true;
for(byte x=1,y=1;x<3 && y<3;++x,++y){
tmpWin=tmpWin && (b.board[x][y]==b.board[x-1][y-1]);
}
if(tmpWin){
lcd.drawLine(0, 0, 84, 84, BLACK);
}
}
if(!tmpWin && i==0 && j==2){
tmpWin=true;
for(byte x=1,y=1;x>0 && y<3;++x,--y){
tmpWin=tmpWin && (b.board[x][y]==b.board[x-1][y+1]);
}
if(tmpWin){
lcd.drawLine(0, 47, 84, 0, BLACK);
}
}
}
}
}
}
/******************************************************************/
/* Funcion play. Ejecuta la jugada de la maquina */
/* */
/******************************************************************/
void play(){
byte x;
byte y;
short maxEvaluation=-32768;
short currentEvaluation=0;
bool stopIteration=false;
//Compruebo si alguna jugada me da la victoria
for(byte i=1;i<4 && !stopIteration;++i){
cellState *statesArray=board.board[i-1];
for(byte j=1;j<4 && !stopIteration;++j){
if(board.board[i-1][j-1]==empty){
statesArray[j-1]=white;
if(winner(board)==white){
stopIteration=true;
board.stopPlay=true;
}
else{
statesArray[j-1]=empty;
}
}
}
}
//Compruebo si alguna jugada da la victoria al oponente
for(byte i=1;i<4 && !stopIteration;++i){
cellState *statesArray=board.board[i-1];
for(byte j=1;j<4 && !stopIteration;++j){
if(board.board[i-1][j-1]==empty){
statesArray[j-1]=black;
if(winner(board)==black){
statesArray[j-1]=white;
stopIteration=true;
}
else{
statesArray[j-1]=empty;
}
}
}
}
if(!stopIteration){
for(byte i=1;i<4;++i){
for(byte j=1;j<4;++j){
if(board.board[i-1][j-1]==empty){
currentEvaluation=evaluatePlay(board,i-1,j-1,white);
if(currentEvaluation>maxEvaluation){
maxEvaluation=currentEvaluation;
x=i;
y=j;
}
}
}
}
cellState *statesArray=board.board[x-1];
statesArray[y-1]=white;
}
}
/******************************************************************/
/* Funcion drawTile. Dibuja una ficha en una posicion */
/* */
/* Parametros: */
/* byte x-->Coordenada x de la casilla donde vamos a jugar */
/* byte y-->Coordenada y de la casilla donde vamos a jugar */
/* int color-->Color de la ficha con la que se pretende jugar */
/******************************************************************/
void drawTile(byte x,byte y,int color){
lcd.drawCircle((x*28)-14, (y*16)-8, 5,BLACK);
if(color==BLACK){
lcd.fillCircle((x*28)-14, (y*16)-8,5,color);
}
}
/******************************************************************/
/* Funcion drawCross. Dibuja el cursor en una posicion */
/* */
/* Parametros: */
/* byte x-->Coordenada x de la casilla donde vamos a jugar */
/* byte y-->Coordenada y de la casilla donde vamos a jugar */
/* int color-->Color de la ficha con la que se pretende jugar */
/******************************************************************/
void drawCross(byte x,byte y,int color){
lcd.drawFastVLine((x*28)-14,(y*16)-13, 10, color);
lcd.drawFastHLine((x*28)-19, (y*16)-8, 10, color);
}
/******************************************************************/
/* Funcion drawGameScreen. Dibuja el tablero de juego */
/* */
/******************************************************************/
void drawGameScreen(){
lcd.drawRoundRect(0, 0, 84, 48,10,BLACK);
lcd.drawLine(28, 0, 28, 84, BLACK);
lcd.drawLine(56, 0, 56, 84, BLACK);
lcd.drawLine(0, 16, 84, 16, BLACK);
lcd.drawLine(0, 32, 84, 32, BLACK);
lcd.drawLine(0, 32, 84, 32, BLACK);
for(byte i=1;i<4;++i){
cellState *statesArray=board.board[i-1];
for(byte j=1;j<4;++j){
if(statesArray[j-1]!=empty){
drawTile(i,j,(statesArray[j-1]==black)?BLACK:WHITE);
}
}
}
cellState *statesArray=board.board[board.xCursor-1];
drawCross(board.xCursor,board.yCursor,(statesArray[board.yCursor-1]==empty || statesArray[board.yCursor-1]==white)?BLACK:WHITE);
drawWinnerPlay(board);
}
void setup(){
Serial.begin(115200) ;
lcd.begin();
lcd.clearDisplay();
lcd.setTextColor(BLACK);
lcd.setTextSize(textSize);
board.xCursor=2;
board.yCursor=2;
drawGameScreen();
lcd.display();
pinMode(button,INPUT_PULLUP);
}
void loop(){
if(!board.stopPlay){
bool redrawScreen=false;
if(analogRead(xAxis)==1023 && board.xCursor>1){
//Se ha movido el joystick a la izquierda
board.xCursor--;
redrawScreen=true;
}
else if(analogRead(xAxis)==0 && board.xCursor<3){
//Se ha movido el joystick a la derecha
board.xCursor++;
redrawScreen=true;
}
else if(analogRead(yAxis)==1023 && board.yCursor<3){
//Se ha movido el joystick hacia arriba
board.yCursor++;
redrawScreen=true;
}
else if(analogRead(yAxis)==0 && board.yCursor>1){
//Se ha movido el joystick hacia abajo
board.yCursor--;
redrawScreen=true;
}
else if(!digitalRead(button)){
//Se ha pulsado el boton
cellState *statesArray=board.board[board.xCursor-1];
if(statesArray[board.yCursor-1]==empty){
statesArray[board.yCursor-1]=black;
board.emptyCells--;
if(board.emptyCells>0){
play();
}
else{
board.stopPlay=true;
}
redrawScreen=true;
}
}
if(redrawScreen){
lcd.clearDisplay();
drawGameScreen();
lcd.display();
delay(500);
}
}
}
#include <Adafruit_GFX.h>
#include <Adafruit_PCD8544.h>
#include "types.h"
//LCD
const byte SCLKpin = 13;
const byte DNpin = 11;
const byte DCpin = 12;
const byte RESETpin = 10;
//Joystick
const int xAxis=A1;
const int yAxis=A0;
const int button=2;
const char contrast=0;
int fontWidth=6;
int fontHeigth=10;
int textSize=2;
boardGame board;
Adafruit_PCD8544 lcd(SCLKpin,DNpin,DCpin,RESETpin);
/******************************************************************/
/* Funcion evaluatePlay. Devuelve el numero de jugadas ganadoras */
/* si se juega una ficha en una casilla determinada */
/* */
/* Parametros: */
/* boardGame b-->Tablero de juego */
/* short x-->Coordenada x de la casilla que vamos a evaluar */
/* short y-->Coordenada y de la casilla que vamos a evaluar */
/* cellState c-->Color de la ficha con la que se pretende jugar */
/******************************************************************/
short evaluatePlay(boardGame b,short x,short y,cellState c){
short winCount=0;
short cellCount=0;
//Horizontal
for(byte j=0;j<3;++j){
if(b.board[x][j]==c || b.board[x][j]==empty){
++cellCount;
}
}
if(cellCount==3){
++winCount;
}
cellCount=0;
//Vertical
for(byte i=0;i<3;++i){
if(b.board[i][y]==c || b.board[i][y]==empty){
++cellCount;
}
}
if(cellCount==3){
++winCount;
}
cellCount=0;
// diagonal
if(x==y){
for(byte i=0,j=0;i<3 && j<3;++i,++j){
if(b.board[i][j]==c || b.board[i][j]==empty){
++cellCount;
}
}
if(cellCount==3){
++winCount;
}
}
cellCount=0;
if(x==y || abs(x-y)==2){
for(byte i=0,j=0;i>0 && j<3;++i,--j){
if(b.board[i][j]==c || b.board[i][j]==empty){
++cellCount;
}
}
if(cellCount==3){
++winCount;
}
}
return winCount;
}
/******************************************************************/
/* Funcion drawWinnerPlay. Dibuja una linea sobre la jugada */
/* ganadora */
/* */
/* Parametros: */
/* boardGame b-->Tablero de juego */
/******************************************************************/
void drawWinnerPlay(boardGame b){
for(byte i=0;i<3;++i){
for(byte j=0;j<3;++j){
if(b.board[i][j]!=empty){
//horizontal
bool tmpWin=true;
for(byte x=1;x<3;++x){
tmpWin=tmpWin && (b.board[x][j]==b.board[x-1][j]);
}
if(tmpWin){
lcd.drawLine(0, ((j+1)*16)-8, 84, ((j+1)*16)-8, BLACK);
}
//vertical
if(!tmpWin){
tmpWin=true;
for(byte y=1;y<3;++y){
tmpWin=tmpWin && (b.board[i][y]==b.board[i][y-1]);
}
if(tmpWin){
lcd.drawLine(((i+1)*28)-14, 0, ((i+1)*28)-14, 84, BLACK);
}
}
//diagonal
if(!tmpWin && i==0 && j==0){
tmpWin=true;
for(byte x=1,y=1;x<3 && y<3;++x,++y){
tmpWin=tmpWin && (b.board[x][y]==b.board[x-1][y-1]);
}
if(tmpWin){
lcd.drawLine(0, 0, 84, 84, BLACK);
}
}
if(!tmpWin && i==0 && j==2){
tmpWin=true;
for(byte x=1,y=1;x>0 && y<3;++x,--y){
tmpWin=tmpWin && (b.board[x][y]==b.board[x-1][y+1]);
}
if(tmpWin){
lcd.drawLine(0, 47, 84, 0, BLACK);
}
}
}
}
}
}
/******************************************************************/
/* Funcion play. Ejecuta la jugada de la maquina */
/* */
/******************************************************************/
void play(){
byte x;
byte y;
short maxEvaluation=-32768;
short currentEvaluation=0;
bool stopIteration=false;
//Compruebo si alguna jugada me da la victoria
for(byte i=1;i<4 && !stopIteration;++i){
cellState *statesArray=board.board[i-1];
for(byte j=1;j<4 && !stopIteration;++j){
if(board.board[i-1][j-1]==empty){
statesArray[j-1]=white;
if(winner(board)==white){
stopIteration=true;
board.stopPlay=true;
}
else{
statesArray[j-1]=empty;
}
}
}
}
//Compruebo si alguna jugada da la victoria al oponente
for(byte i=1;i<4 && !stopIteration;++i){
cellState *statesArray=board.board[i-1];
for(byte j=1;j<4 && !stopIteration;++j){
if(board.board[i-1][j-1]==empty){
statesArray[j-1]=black;
if(winner(board)==black){
statesArray[j-1]=white;
stopIteration=true;
}
else{
statesArray[j-1]=empty;
}
}
}
}
if(!stopIteration){
for(byte i=1;i<4;++i){
for(byte j=1;j<4;++j){
if(board.board[i-1][j-1]==empty){
currentEvaluation=evaluatePlay(board,i-1,j-1,white);
if(currentEvaluation>maxEvaluation){
maxEvaluation=currentEvaluation;
x=i;
y=j;
}
}
}
}
cellState *statesArray=board.board[x-1];
statesArray[y-1]=white;
}
}
/******************************************************************/
/* Funcion drawTile. Dibuja una ficha en una posicion */
/* */
/* Parametros: */
/* byte x-->Coordenada x de la casilla donde vamos a jugar */
/* byte y-->Coordenada y de la casilla donde vamos a jugar */
/* int color-->Color de la ficha con la que se pretende jugar */
/******************************************************************/
void drawTile(byte x,byte y,int color){
lcd.drawCircle((x*28)-14, (y*16)-8, 5,BLACK);
if(color==BLACK){
lcd.fillCircle((x*28)-14, (y*16)-8,5,color);
}
}
/******************************************************************/
/* Funcion drawCross. Dibuja el cursor en una posicion */
/* */
/* Parametros: */
/* byte x-->Coordenada x de la casilla donde vamos a jugar */
/* byte y-->Coordenada y de la casilla donde vamos a jugar */
/* int color-->Color de la ficha con la que se pretende jugar */
/******************************************************************/
void drawCross(byte x,byte y,int color){
lcd.drawFastVLine((x*28)-14,(y*16)-13, 10, color);
lcd.drawFastHLine((x*28)-19, (y*16)-8, 10, color);
}
/******************************************************************/
/* Funcion drawGameScreen. Dibuja el tablero de juego */
/* */
/******************************************************************/
void drawGameScreen(){
lcd.drawRoundRect(0, 0, 84, 48,10,BLACK);
lcd.drawLine(28, 0, 28, 84, BLACK);
lcd.drawLine(56, 0, 56, 84, BLACK);
lcd.drawLine(0, 16, 84, 16, BLACK);
lcd.drawLine(0, 32, 84, 32, BLACK);
lcd.drawLine(0, 32, 84, 32, BLACK);
for(byte i=1;i<4;++i){
cellState *statesArray=board.board[i-1];
for(byte j=1;j<4;++j){
if(statesArray[j-1]!=empty){
drawTile(i,j,(statesArray[j-1]==black)?BLACK:WHITE);
}
}
}
cellState *statesArray=board.board[board.xCursor-1];
drawCross(board.xCursor,board.yCursor,(statesArray[board.yCursor-1]==empty || statesArray[board.yCursor-1]==white)?BLACK:WHITE);
drawWinnerPlay(board);
}
void setup(){
Serial.begin(115200) ;
lcd.begin();
lcd.clearDisplay();
lcd.setTextColor(BLACK);
lcd.setTextSize(textSize);
board.xCursor=2;
board.yCursor=2;
drawGameScreen();
lcd.display();
pinMode(button,INPUT_PULLUP);
}
void loop(){
if(!board.stopPlay){
bool redrawScreen=false;
if(analogRead(xAxis)==1023 && board.xCursor>1){
//Se ha movido el joystick a la izquierda
board.xCursor--;
redrawScreen=true;
}
else if(analogRead(xAxis)==0 && board.xCursor<3){
//Se ha movido el joystick a la derecha
board.xCursor++;
redrawScreen=true;
}
else if(analogRead(yAxis)==1023 && board.yCursor<3){
//Se ha movido el joystick hacia arriba
board.yCursor++;
redrawScreen=true;
}
else if(analogRead(yAxis)==0 && board.yCursor>1){
//Se ha movido el joystick hacia abajo
board.yCursor--;
redrawScreen=true;
}
else if(!digitalRead(button)){
//Se ha pulsado el boton
cellState *statesArray=board.board[board.xCursor-1];
if(statesArray[board.yCursor-1]==empty){
statesArray[board.yCursor-1]=black;
board.emptyCells--;
if(board.emptyCells>0){
play();
}
else{
board.stopPlay=true;
}
redrawScreen=true;
}
}
if(redrawScreen){
lcd.clearDisplay();
drawGameScreen();
lcd.display();
delay(500);
}
}
}