viernes, 1 de enero de 2016

Tres en raya

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:

  • 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.
El funcionamiento es muy sencillo. Con el joystick moveremos un cursor por la pantalla LCD y al pulsar el botón del joystick pondremos nuestra ficha en la casilla seleccionada. Inmediatamente el microcontrolador hará su jugada y nos devolverá el control para la siguiente.
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;
}

     
  • 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);
     
     }
    }
   
  }

     

No hay comentarios:

Publicar un comentario