Skip to content

10 Juegos Arcade de Arduino con el Display LCD

¿A quién no le gustan los videojuegos? Son un fantástico pasatiempo para matar el tiempo libre. Y qué mejor, si pudieras tener un videojuego en la palma de la mano, tan solo ocupando tu Arduino uno y un Shield LCD. Eso es lo que demostraremos en este artículo, 10 sorprendentes juegos con Arduino sin tanta complejidad de hardware. Vamos a ello.

https://youtu.be/GXnv0CWg-QU

Componentes Requeridos para la creación de los juegos con Arduino

Los componentes necesarios  para nuestro proyecto, son los siguientes.

Componentes Información del producto
1 x Arduino Uno
https://amzn.to/38YJpnM
LCD Keypad Shield
https://amzn.to/2vtZuUR
Buzzer
https://amzn.to/3b8hqnb

Armado del Circuito requerido para crear un juego con Arduino

En esta recopilación, me he centrado en crear la menor complejidad posible para recrear los proyectos, por lo que sólo necesitarás 2 componentes obligatorios, y uno opcional. En realidad el armado es sumamente sencillo gracias a los Shields de Arduino. Simplemente, al contar con nuestro Arduino y el Shield:

Juego Arduino

Procederemos a montar el Shield sobre el Arduino, cuidando que encajen correctamente las secciones de los pines entre ambos.

Crear juegos con Arduino

La bocina es opcional, y si deseas conectarla, deberás hacerlo al pin # 3 del Arduino, ya que es el pin preconfigurado en todos los sketch, para utilizarla.

¿Qué pasa si no quiero utilizar el Shield LCD para Arduino?

En el caso de que no quieras utilizar el Shield de Arduino, podrás conectar push buttons, de los cuales se deben leer los datos y modificar el sketch correspondiente, para que funcione con tu configuración personalizada.

Arduino Juego de la Serpiente con Display LCD

Autor: @boti12bot

¿Quién no se acuerda del clásico juego de la serpiente? Uno de los juegos que más horas de diversión pueden hacernos pasar. En esta versión para Arduino, hasta podremos seleccionar el nivel de dificultad al que nos queramos enfrentar.

https://youtu.be/wlDltOjGBDk

Sketch para crear el juego de la Serpiente con Arduino

//Written by Mason 2012
#include <LiquidCrystal.h>
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);
int adc_key_in  = 0;
#define btnRIGHT  0
#define btnUP     1
#define btnDOWN   2
#define btnLEFT   3
#define btnSELECT 4
#define btnNONE   5
int bpm = 30;
const int whole = (60000/bpm);
const int half = 30000/bpm;
const int quarter = 15000/bpm;
const int eight = 7500 / bpm;
const int sixteenth = 3750 / bpm;
const int thirty2 = 1875 / bpm;
int musicpin = 3;
float vballx = 1;
float vbally = 0.2;
float xball = 1;
float yball = 1;
int xmax = 80;
int delaytime = 60;
int score;
//define graphics
byte dot[8] = {
  0x0,
  0x0,
  0x0,
  0x0,
  0x0,
  0x0,
  0x0,
  0x0
};
byte paddle[8] = {
  B00000,
  B00000,
  B00000,
  B00000,
  B00000,
  B11000,
  B11000,
  B11000
};
byte wallFR [8] = {
  0x0,
  0x0,
  0x0,
  0x0,
  0x0,
  0x0,
  0x0,
  0x0
};
byte wallFL [8] = {
  0x0,
  0x0,
  0x0,
  0x0,
  0x0,
  0x0,
  0x0,
  0x0
};
byte wallBR [8] = {
  0x0,
  0x0,
  0x0,
  0x0,
  0x0,
  0x0,
  0x0,
  0x0
};
byte wallBL [8] = {
  0x0,
  0x0,
  0x0,
  0x0,
  0x0,
  0x0,
  0x0,
  0x0
};
//Define the wall tiles
boolean wallarray [16] = {
  1,1,1,1,
  1,1,1,1,
  1,1,1,1,
  1,1,1,1};
int lcd_key     = 0;
int paddle_pos = 1;
int read_LCD_buttons()
{
  adc_key_in = analogRead(0);      // read the value from the sensor
  // my buttons when read are centered at these values: 0, 144, 329, 504, 741
  // we add approx 50 to those values and check to see if we are close
  if (adc_key_in > 1000) return btnNONE; // We make this the 1st option for speed reasons since it will be the most likely result
  if (adc_key_in < 50)   return btnRIGHT;
  if (adc_key_in < 195)  return btnUP;
  if (adc_key_in < 380)  return btnDOWN;
  if (adc_key_in < 555)  return btnLEFT;
  if (adc_key_in < 790)  return btnSELECT;
  return btnNONE;  // when all others fail, return this...
}
void getPaddle() {
  lcd_key = read_LCD_buttons();  // read the buttons
  switch (lcd_key)               // depending on which button was pushed, we perform an action
  {
  case btnUP:
    {
      if (paddle_pos > 1) paddle_pos -= 1;
      break;
    }
  case btnDOWN:
    {
      if (paddle_pos < 14) paddle_pos += 1;
      break;
    }
  }
}
void drawwalls()
{
  for (int i=0; i < 8; i+=4)
  {
    for (int j = 0; j<4; j++)
    {
      wallFL[j+i]=wallarray[i/2]*16+wallarray[i/2]*8+wallarray[i/2+1]*2+wallarray[i/2+1]*1;
      wallFR[j+i]=wallarray[i/2+4]*16+wallarray[i/2+4]*8+wallarray[i/2+5]*2+wallarray[i/2+5]*1;
      wallBL[j+i]=wallarray[i/2+8]*16+wallarray[i/2+8]*8+wallarray[i/2+9]*2+wallarray[i/2+9]*1;
      wallBR[j+i]=wallarray[i/2+12]*16+wallarray[i/2+12]*8+wallarray[i/2+13]*2+wallarray[i/2+13]*1;
    }
  }
  lcd.createChar(2,wallFL);
  lcd.createChar(3,wallFR);
  lcd.createChar(4,wallBL);
  lcd.createChar(5,wallBR);
  lcd.setCursor(14,0);
  lcd.write((byte)2);
  lcd.write((byte)4);
  lcd.setCursor(14,1);
  lcd.write((byte)3);
  lcd.write((byte)5);
}
//place dot on screen (80x16)
void placedot(int x, int y) {
  createdot(x%5,y%8);
  lcd.setCursor(x/5,y/8);
  lcd.write((byte)0);
}
void placepaddle(int y) {
  for (int i=0;i<8;i++){
    paddle[i]=0x0;
  }
  if (y%8>0) paddle[y%8-1] = 0x10;
  paddle[y%8] = 0x10;
  if (y%8<7)paddle[y%8+1] = 0x10;
  lcd.createChar(1,paddle);
  lcd.setCursor(0,y/8);
  lcd.write((byte)1);
}
//draw a dot in the 5x8 space of the character at location x,y
void createdot(int x, int y)  {
  for (int i=0;i<8;i++){
    dot[i]=0x0;
  }
  if (y > 0) dot[y-1] = (B11000 >> x);
  dot[y] = (B11000 >> x);
  lcd.createChar(0,dot);
}
int blockcounter = 0;
void handlecollisions()
{
  xball = xball + vballx;
  yball = yball + vbally;
  //Handle collisions in y
  if ((yball > 15) || (yball < 1)) {
    vbally = -vbally;
    tone(musicpin,880,eight);
  }
  //Handle Collisions in x
  if (xball > 69) //is the ball in the correct area
  {
    for (int i=0;i < 16; i++)
    {
      if (xball>(70+2*(i%2)+5*(i/8))) {//x condition met
        if ((yball>(2*(i%8)))&&(yball<(2*(i%8)+4))){ // y condition met
          if (wallarray[i] == 1){ //wall exists
           tone(musicpin,1174,eight);
           delay(eight);
            wallarray[i]=0;
            vballx = -vballx;
            xball = 70;
          }
        }
      }
    }
  }
  // Check to see if at edge of screen.
  if (xball > xmax)   {
    vballx = -vballx;
    tone(musicpin,880,eight);
  }
  //check for ball and paddle
  if (xball < 1)   {
    if (paddle_pos > int(yball)-2 && paddle_pos < int(yball)+2){
      tone(musicpin,1397,sixteenth);
      delay(sixteenth);
      tone(musicpin,1567,eight);
      vballx = -vballx;
      vbally *= random(1,4) ;
      vbally /= 2;
      Serial.println(vbally);
      score += 1;
      //xmax -= 2;
      delaytime -= 2;
      vballx += 0.1;
      int left = 0;
      for (int i=0;i<16;i++)
      { left += wallarray[i];
      }
      if (left < 1)       {
        lcd.clear();
        lcd.print("You Win! ");
        arkanoidsong();
        lcd.print(score);
        delay(15000);
        xmax = 80;
        score = 0;
        delaytime = 60;
        for (int i=0;i< 16;i++)
        {wallarray[i]=1;}
      }
    }
    else {
      tone(musicpin,349,sixteenth);
      delay(sixteenth);
      tone(musicpin,329,eight);
      vballx = -vballx;
      vbally *= random(1,4) ;
      vbally /= 2;
      score -=1;
    }
  }
}
void arkanoidsong(){
    tone(musicpin, 1568, eight);//g6
  delay(eight);
  noTone(musicpin);
  delay(sixteenth);
  tone(musicpin, 1568, sixteenth);//g6
  delay(sixteenth);
  tone(musicpin, 1864, half);//a#6
  delay(half);
  noTone(musicpin);
  delay(thirty2);
  tone(musicpin, 1760, eight);//a6
  delay(eight);
  tone(musicpin, 1568, eight);//g6
  delay(eight);
  tone(musicpin, 1396, eight);//f6
  delay(eight);
  tone(musicpin, 1760, eight);//a6
  delay(eight);
  tone(musicpin, 1568, half);
  delay(half);
}
void setup() {
  lcd.begin(16, 2);
  delay(100);
  lcd.clear();
  lcd.setCursor(0,0);
  lcd.print("Arduinoid");
  lcd.setCursor(0,1);
  lcd.print("Get the Bricks");
  delay(500);
  arkanoidsong();
  delay(500);
  lcd.setCursor(0,1);
  lcd.print("Press to Start");
  while(analogRead(0)>1000)
  {
    delay(10);
  }
  Serial.begin(9600);
}
void loop() {
  lcd.clear();
  getPaddle();
  drawwalls();
  placepaddle(paddle_pos);
    handlecollisions();
  placedot(xball,yball);
  delay(delaytime);
} 

Arduino Juego de Tetris y Display LCD

Autor: Alex Zen

Otro de los clásicos, hablamos nada mas y nada menos que del clásico y poderoso Tetris. ¿Cuánto tiempo crees  durar ensamblando los famosos bloques?

https://youtu.be/YmZDL9-bCw0

Sketch para crear el juego de Tetris con Arduino

/*
 * Copyright (c) 2015, Alex Zen <me@alexzen.net>
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *     1. Redistributions of source code must retain the above copyright
 *        notice, this list of conditions and the following disclaimer.
 *     2. Redistributions in binary form must reproduce the above copyright
 *        notice, this list of conditions and the following disclaimer in the
 *        documentation and/or other materials provided with the distribution.
 *     3. Neither the name of poseur-blocks nor the names of its
 *        contributors may be used to endorse or promote products derived from
 *        this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ''AS IS''
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 * 
 */
#include <LiquidCrystal.h>
#define LEFT   1
#define RIGHT  2
#define DOWN   3
#define ROTATE 4
#define N_ROWS 32
#define N_COLS 6
#define N_LCD_CHAR 8
#define SOUND_NOTE 262
#define SOUND_DURATION 100
unsigned long curr_time;
unsigned long prev_time;
int score;
int base_pointer;
typedef char game_matrix[N_ROWS][N_COLS];
struct lcd_map
{
    int max;
    int lcd_char_map[N_LCD_CHAR];
};
/* Matrix of the game */
game_matrix matrix;
//LiquidCrystal lcd(12, 11, 5, 4, 3, 2);
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);
/*
 * Check if the char in input is "empty" ( == ' ' )
 */
int is_empty(char p)
{
    if (p == ' ')
        return 0;
    else
        return 1;
}
/* Point in 2-dimensional matrix */
struct point
{
    int x;
    int y;
};
/* Tetromino piece */
struct tetromino
{
    char type;
    struct point pos;
    int dir;
    struct point blocks[4];
} tetr;
int max_line = N_ROWS + 1;
struct tetromino start_pos[7];
/*
 * Draw the tetromino on the matrix of the game
 */
void draw_tetromino(struct tetromino *tetr, char val)
{
    struct point bl;
    int i;
    for (i=0; i<4; i++)
    {
        bl = tetr->blocks[i];
        matrix[bl.x][bl.y] = val;
    }
}
/*
 * Move the tetromino.
 * If collision occurs, return 1
 */
int move(int mov, struct tetromino *tetr)
{
    int i;
    struct point temp[4]; /* New position of blocks  */
    struct point pos; /* New position of tetromino */
    /* Compute new blocks' positions */
    switch(mov)
    {
        case LEFT:
            for (i=0; i<4; i++)
            {
                temp[i].x = tetr->blocks[i].x;
                temp[i].y = tetr->blocks[i].y - 1;
            }
            pos.x = tetr->pos.x;
            pos.y = tetr->pos.y - 1;
            break;
        case RIGHT:
            for (i=0; i<4; i++)
            {
                temp[i].x = tetr->blocks[i].x;
                temp[i].y = tetr->blocks[i].y + 1;
            }
            pos.x = tetr->pos.x;
            pos.y = tetr->pos.y + 1;
            break;
        case DOWN:
            for (i=0; i<4; i++)
            {
                temp[i].x = tetr->blocks[i].x + 1;
                temp[i].y = tetr->blocks[i].y;
            }
            pos.x = tetr->pos.x + 1;
            pos.y = tetr->pos.y;
            break;
        case ROTATE:
            for (i=0; i<4; i++)
            {
                temp[i].x = -(tetr->blocks[i].y - tetr->pos.y) + tetr->pos.x;
                temp[i].y = tetr->blocks[i].x - tetr->pos.x + tetr->pos.y;
            }
            pos.x = tetr->pos.x;
            pos.y = tetr->pos.y;
            break;
    }
    for (i=0; i<4; i++)
    {
        if (temp[i].x < 0  ||
            temp[i].x > base_pointer-1 ||
            temp[i].y < 0  ||
            temp[i].y > N_COLS-1)
                return 1;
    }
    /* Move the tetromino */
    draw_tetromino(tetr, ' ');
    /* Check for collisions */
    for (i=0; i<4; i++)
    {
        if (matrix[temp[i].x][temp[i].y] != ' ')
        {
            draw_tetromino(tetr, tetr->type);
            return 1;
        }
    }
    for (i=0; i<4; i++)
        tetr->blocks[i] = temp[i];
    tetr->pos = pos;
    if (mov == ROTATE)
        tetr->dir = (tetr->dir + 1) % 4;
    draw_tetromino(tetr, tetr->type);
    return 0;
}
/*
 * Search in the mapping table if the key is present
 */
int find_mapping(struct lcd_map *mapping, byte key)
{
    int i;
    for (i=0; i < (mapping->max); i++)
        if (mapping->lcd_char_map[i] == key)
            return i;
    return -1;
}
/*
 * Insert the key into the mapping table
 */
int insert_mapping(struct lcd_map *mapping, byte key)
{
    int pos = mapping->max;
    if (pos > N_LCD_CHAR-1) // Upper limit of array
        return -1;
    mapping->lcd_char_map[pos] = key;
    mapping->max = pos + 1;
    return pos;
}
/*
 * Compose the key for the mapping table
 */
byte compose_key(int i, int j)
{
    byte key = 0;
    key |= is_empty(matrix[i  ] [j+2]) << 5;
    key |= is_empty(matrix[i+1] [j+2]) << 4;
    key |= is_empty(matrix[i  ] [j+1]) << 3;
    key |= is_empty(matrix[i+1] [j+1]) << 2;
    key |= is_empty(matrix[i  ] [j]  ) << 1;
    key |= is_empty(matrix[i+1] [j]  );
    return key;
}
/*
 * Create a new char for the LCD screen
 */
void create_new_char(int i, int j, int index)
{
    byte bt_1 = B00000;
    byte bt_2 = B00000;
    byte bt_3 = B00000;
    bt_1 |= is_empty(matrix[i  ] [j+2]) << 4;
    bt_1 |= is_empty(matrix[i  ] [j+2]) << 3;
    bt_1 |= is_empty(matrix[i+1] [j+2]) << 1;
    bt_1 |= is_empty(matrix[i+1] [j+2]);
    bt_2 |= is_empty(matrix[i  ] [j+1]) << 4;
    bt_2 |= is_empty(matrix[i  ] [j+1]) << 3;
    bt_2 |= is_empty(matrix[i+1] [j+1]) << 1;
    bt_2 |= is_empty(matrix[i+1] [j+1]);
    bt_3 |= is_empty(matrix[i  ] [j]  ) << 4;
    bt_3 |= is_empty(matrix[i  ] [j]  ) << 3;
    bt_3 |= is_empty(matrix[i+1] [j]  ) << 1;
    bt_3 |= is_empty(matrix[i+1] [j]  );
    byte new_char[8] =
        {
            bt_1,
            bt_1,
            B00000,
            bt_2,
            bt_2,
            B00000,
            bt_3,
            bt_3
        };
    lcd.createChar(index, new_char);
    return;
}
/*
 * Draw the matrix on Arduino's LCD
 */
void print_to_lcd()
{
    struct lcd_map lcd_char_map;
    lcd_char_map.max = 0;
    byte key = 0;
    int index, i, j;
    //lcd.clear();
    // Create custom LCD's characters
    for (i=0; i<base_pointer; i+=2)
        for (j=0; j<N_COLS; j+=3)
        {
            key = compose_key(i, j);
            if (key == 0)
                continue;
            index = find_mapping(&lcd_char_map, key);
            if (index == -1)
            {
                index = insert_mapping(&lcd_char_map, key);
                create_new_char(i, j, index);
            }
        }
    lcd.begin(16, 2);
    // Print the characters on LCD screen
    for (j=N_COLS-3; j>=0; j-=3)
    {
        for (i=0; i<N_ROWS; i+=2)
        {
            if (i >= base_pointer)
            {
                lcd.write("$");
                continue;
            }
            key = compose_key(i, j);
            if (key == 0)
            {   
                lcd.write(" ");
                continue;
            }
            index = find_mapping(&lcd_char_map, key);
            lcd.write(byte(index));
        }
        lcd.setCursor(0, 1);
    }
}
/*
 * Add a new tetromino on the game
 */
int add_new_tetromino(int foo)
{
    tetr.blocks[0].x = start_pos[foo].blocks[0].x;
    tetr.blocks[0].y = start_pos[foo].blocks[0].y;
    tetr.blocks[1].x = start_pos[foo].blocks[1].x;
    tetr.blocks[1].y = start_pos[foo].blocks[1].y;
    tetr.blocks[2].x = start_pos[foo].blocks[2].x;
    tetr.blocks[2].y = start_pos[foo].blocks[2].y;
    tetr.blocks[3].x = start_pos[foo].blocks[3].x;
    tetr.blocks[3].y = start_pos[foo].blocks[3].y;
    tetr.dir = start_pos[foo].dir;
    tetr.type = start_pos[foo].type;
    tetr.pos.x = start_pos[foo].pos.x;
    tetr.pos.y = start_pos[foo].pos.y;
    // Check space
    if (matrix[tetr.blocks[0].x][tetr.blocks[0].y] != ' ' ||
        matrix[tetr.blocks[1].x][tetr.blocks[1].y] != ' ' ||
        matrix[tetr.blocks[2].x][tetr.blocks[2].y] != ' ' ||
        matrix[tetr.blocks[3].x][tetr.blocks[3].y] != ' ')
        return 1;
    else
        return 0;
}
/*
 * Arduino's setup() function. It is called when the sketch starts
 */
void setup()
{
    int i, j, mvm, foo;
    char cmd;
    score = 0;
    base_pointer = N_ROWS;
    randomSeed(millis());
    /* initial positions of the tetrominoes */
    pinMode(3, OUTPUT);
    /* I-block */
    start_pos[0].type = 'I';
    start_pos[0].pos.x = 1;
    start_pos[0].pos.y = 2;
    start_pos[0].dir = 0;
    start_pos[0].blocks[0].x = 1;
    start_pos[0].blocks[0].y = 1;
    start_pos[0].blocks[1].x = 1;
    start_pos[0].blocks[1].y = 2;
    start_pos[0].blocks[2].x = 1;
    start_pos[0].blocks[2].y = 3;
    start_pos[0].blocks[3].x = 1;
    start_pos[0].blocks[3].y = 4;
    /* J-block */
    start_pos[1].type = 'J';
    start_pos[1].pos.x = 1;
    start_pos[1].pos.y = 3;
    start_pos[1].dir = 0;
    start_pos[1].blocks[0].x = 0;
    start_pos[1].blocks[0].y = 2;
    start_pos[1].blocks[1].x = 1;
    start_pos[1].blocks[1].y = 2;
    start_pos[1].blocks[2].x = 1;
    start_pos[1].blocks[2].y = 3;
    start_pos[1].blocks[3].x = 1;
    start_pos[1].blocks[3].y = 4;
    /* L-block */
    start_pos[2].type = 'L';
    start_pos[2].pos.x = 1;
    start_pos[2].pos.y = 3;
    start_pos[2].dir = 0;
    start_pos[2].blocks[0].x = 1;
    start_pos[2].blocks[0].y = 2;
    start_pos[2].blocks[1].x = 1;
    start_pos[2].blocks[1].y = 3;
    start_pos[2].blocks[2].x = 1;
    start_pos[2].blocks[2].y = 4;
    start_pos[2].blocks[3].x = 0;
    start_pos[2].blocks[3].y = 4;
    /* O-block */
    start_pos[3].type = 'O';
    start_pos[3].pos.x = 1;
    start_pos[3].pos.y = 3;
    start_pos[3].dir = 0;
    start_pos[3].blocks[0].x = 0;
    start_pos[3].blocks[0].y = 2;
    start_pos[3].blocks[1].x = 0;
    start_pos[3].blocks[1].y = 3;
    start_pos[3].blocks[2].x = 1;
    start_pos[3].blocks[2].y = 2;
    start_pos[3].blocks[3].x = 1;
    start_pos[3].blocks[3].y = 3;
    /* S-block */
    start_pos[4].type = 'S';
    start_pos[4].pos.x = 1;
    start_pos[4].pos.y = 3;
    start_pos[4].dir = 0;
    start_pos[4].blocks[0].x = 1;
    start_pos[4].blocks[0].y = 2;
    start_pos[4].blocks[1].x = 1;
    start_pos[4].blocks[1].y = 3;
    start_pos[4].blocks[2].x = 0;
    start_pos[4].blocks[2].y = 3;
    start_pos[4].blocks[3].x = 0;
    start_pos[4].blocks[3].y = 4;
    /* T-block */
    start_pos[5].type = 'T';
    start_pos[5].pos.x = 1;
    start_pos[5].pos.y = 3;
    start_pos[5].dir = 0;
    start_pos[5].blocks[0].x = 0;
    start_pos[5].blocks[0].y = 3;
    start_pos[5].blocks[1].x = 1;
    start_pos[5].blocks[1].y = 2;
    start_pos[5].blocks[2].x = 1;
    start_pos[5].blocks[2].y = 3;
    start_pos[5].blocks[3].x = 1;
    start_pos[5].blocks[3].y = 4;
    /* Z-block */
    start_pos[6].type = 'Z';
    start_pos[6].pos.x = 1;
    start_pos[6].pos.y = 3;
    start_pos[6].dir = 0;
    start_pos[6].blocks[0].x = 0;
    start_pos[6].blocks[0].y = 2;
    start_pos[6].blocks[1].x = 0;
    start_pos[6].blocks[1].y = 3;
    start_pos[6].blocks[2].x = 1;
    start_pos[6].blocks[2].y = 3;
    start_pos[6].blocks[3].x = 1;
    start_pos[6].blocks[3].y = 4;
    for (i=0; i<N_ROWS; i++)
        for (j=0; j<N_COLS; j++)
            matrix[i][j] = ' ';
    prev_time = millis();
    add_new_tetromino(random(7));
    draw_tetromino(&tetr, tetr.type);
    print_to_lcd();
}
/*
 * Return the first full row found
 */
int find_full_rows()
{
    int i, j;
    int flag;
    for (i = base_pointer-1; i>=0; i--)
    {
        flag = 0;
        for (j=0; j<N_COLS; j++)
            if (matrix[i][j] == ' ')
                flag = 1;
        if (flag == 0)
            return i;
    } 
    return -1;
}
/*
 * Erase a row
 */
void mode_down_rows(int row)
{
    int i, j;
    for (i=row; i>0; i--)
        for (j=0; j<N_COLS; j++)
            matrix[i][j] = matrix[i-1][j];
    matrix[0][0] = ' ';
    matrix[0][1] = ' ';
    matrix[0][2] = ' ';
    matrix[0][3] = ' ';
}
/*
 * Arduino's loop() function
 */
void loop()
{
    int foo, ret, ret_mov;
    int mov = 0;
    int row_index;
    int i, j;
    int flag = 0;
    curr_time = millis();
    //int key_val = analogRead(A0);
    //Serial.println(key_val);
    // Check input
    /*if (key_val == 1023) // left
        mov = LEFT;
    else if (key_val >= 990 && key_val <= 1010) // right
        mov = RIGHT;
    else if (key_val >= 505 && key_val <= 515) // rotate
        mov = ROTATE;*/
    int b = analogRead(A0);
    if (b > 1000) {} 
    else if (b < 50) {}
    else if (b < 180) mov = RIGHT;
    else if (b < 330) mov = LEFT;
    else if (b < 520) mov = ROTATE;
    else if (b < 700) {}
    // Move/rotate the piece 
    if (mov != 0)
    {
        ret = move(mov, &tetr);
        if (ret == 0)
        {
            draw_tetromino(&tetr, tetr.type);
            print_to_lcd();
            tone(3, SOUND_NOTE, SOUND_DURATION);
            delay(100);
            return;
        }
    }
    // Move down the piece
    curr_time = millis();
    if (curr_time - prev_time > 500)
    {
        prev_time = curr_time;
        if ((ret_mov = move(DOWN, &tetr)) == 1) // piece has reached the bottom
        {
            while ((row_index = find_full_rows()) != -1)
            {
                mode_down_rows(row_index);
                score += 10;
            }
            // Apply the rule
            for (i=0; i<base_pointer;i++)
            {
                for (j=0; j<N_COLS; j++)
                    if (matrix[i][j] != ' ')
                    {
                        flag = 1;
                        if (base_pointer - i > 4)
                        {
                            base_pointer -= (base_pointer - i - 4);
                            if (base_pointer % 2 != 0)
                                base_pointer -= 1;
                        }
                        break;
                    }
                if (flag == 1)
                    break;
            }
            ret = add_new_tetromino(random(7));
            if (ret != 0)
            {
                // print score
                lcd.clear();
                lcd.print("Score: " );
                lcd.print(score);
                while(1)
                    ;
            }
            draw_tetromino(&tetr, tetr.type);
        }
        print_to_lcd();
        if (ret_mov == 1)
            tone(3, SOUND_NOTE, SOUND_DURATION);
    }
}
 

Arduino Juego tipo Runner y Display LCD

Autor: Joshua Brooks

¿Te imaginas un runnero tipo Mario Bros en tu Arduino? Así es, con este proyecto, podrás tener tu propia versión de un juego tipo runner corriendo desde la pantalla LCD controlada por Arduino.

https://youtu.be/VxZYTafPJ-Y

Sketch para crear con Arduino un juego tipo runner

/*  Slightly modified to work with the LCD Keypad Shield.
 *  Original - http://www.instructables.com/id/Arduino-LCD-Game/
 */
#include <LiquidCrystal.h>
#define SPRITE_RUN1 1
#define SPRITE_RUN2 2
#define SPRITE_JUMP 3
#define SPRITE_JUMP_UPPER '.'         // Use the '.' character for the head
#define SPRITE_JUMP_LOWER 4
#define SPRITE_TERRAIN_EMPTY ' '      // User the ' ' character
#define SPRITE_TERRAIN_SOLID 5
#define SPRITE_TERRAIN_SOLID_RIGHT 6
#define SPRITE_TERRAIN_SOLID_LEFT 7
#define HERO_HORIZONTAL_POSITION 1    // Horizontal position of hero on screen
#define TERRAIN_WIDTH 16
#define TERRAIN_EMPTY 0
#define TERRAIN_LOWER_BLOCK 1
#define TERRAIN_UPPER_BLOCK 2
#define HERO_POSITION_OFF 0          // Hero is invisible
#define HERO_POSITION_RUN_LOWER_1 1  // Hero is running on lower row (pose 1)
#define HERO_POSITION_RUN_LOWER_2 2  //                              (pose 2)
#define HERO_POSITION_JUMP_1 3       // Starting a jump
#define HERO_POSITION_JUMP_2 4       // Half-way up
#define HERO_POSITION_JUMP_3 5       // Jump is on upper row
#define HERO_POSITION_JUMP_4 6       // Jump is on upper row
#define HERO_POSITION_JUMP_5 7       // Jump is on upper row
#define HERO_POSITION_JUMP_6 8       // Jump is on upper row
#define HERO_POSITION_JUMP_7 9       // Half-way down
#define HERO_POSITION_JUMP_8 10      // About to land
#define HERO_POSITION_RUN_UPPER_1 11 // Hero is running on upper row (pose 1)
#define HERO_POSITION_RUN_UPPER_2 12 //                              (pose 2)
//LiquidCrystal lcd(11, 9, 6, 5, 4, 3);
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);
static char terrainUpper[TERRAIN_WIDTH + 1];
static char terrainLower[TERRAIN_WIDTH + 1];
static bool buttonPushed = false;
static int melodyPin = 3;
void initializeGraphics() {
  static byte graphics[] = {
    // Run position 1
    B01100,
    B01100,
    B00000,
    B01110,
    B11100,
    B01100,
    B11010,
    B10011,
    // Run position 2
    B01100,
    B01100,
    B00000,
    B01100,
    B01100,
    B01100,
    B01100,
    B01110,
    // Jump
    B01100,
    B01100,
    B00000,
    B11110,
    B01101,
    B11111,
    B10000,
    B00000,
    // Jump lower
    B11110,
    B01101,
    B11111,
    B10000,
    B00000,
    B00000,
    B00000,
    B00000,
    // Ground
    B11111,
    B11111,
    B11111,
    B11111,
    B11111,
    B11111,
    B11111,
    B11111,
    // Ground right
    B00011,
    B00011,
    B00011,
    B00011,
    B00011,
    B00011,
    B00011,
    B00011,
    // Ground left
    B11000,
    B11000,
    B11000,
    B11000,
    B11000,
    B11000,
    B11000,
    B11000,
  };
  int i;
  // Skip using character 0, this allows lcd.print() to be used to
  // quickly draw multiple characters
  for (i = 0; i < 7; ++i) {
    lcd.createChar(i + 1, &graphics[i * 8]);
  }
  for (i = 0; i < TERRAIN_WIDTH; ++i) {
    terrainUpper[i] = SPRITE_TERRAIN_EMPTY;
    terrainLower[i] = SPRITE_TERRAIN_EMPTY;
  }
}
// Slide the terrain to the left in half-character increments
//
void advanceTerrain(char* terrain, byte newTerrain) {
  for (int i = 0; i < TERRAIN_WIDTH; ++i) {
    char current = terrain[i];
    char next = (i == TERRAIN_WIDTH - 1) ? newTerrain : terrain[i + 1];
    switch (current) {
      case SPRITE_TERRAIN_EMPTY:
        terrain[i] = (next == SPRITE_TERRAIN_SOLID) ? SPRITE_TERRAIN_SOLID_RIGHT : SPRITE_TERRAIN_EMPTY;
        break;
      case SPRITE_TERRAIN_SOLID:
        terrain[i] = (next == SPRITE_TERRAIN_EMPTY) ? SPRITE_TERRAIN_SOLID_LEFT : SPRITE_TERRAIN_SOLID;
        break;
      case SPRITE_TERRAIN_SOLID_RIGHT:
        terrain[i] = SPRITE_TERRAIN_SOLID;
        break;
      case SPRITE_TERRAIN_SOLID_LEFT:
        terrain[i] = SPRITE_TERRAIN_EMPTY;
        break;
    }
  }
}
bool drawHero(byte position, char* terrainUpper, char* terrainLower, unsigned int score) {
  bool collide = false;
  char upperSave = terrainUpper[HERO_HORIZONTAL_POSITION];
  char lowerSave = terrainLower[HERO_HORIZONTAL_POSITION];
  byte upper, lower;
  switch (position) {
    case HERO_POSITION_OFF:
      upper = lower = SPRITE_TERRAIN_EMPTY;
      break;
    case HERO_POSITION_RUN_LOWER_1:
      upper = SPRITE_TERRAIN_EMPTY;
      lower = SPRITE_RUN1;
      break;
    case HERO_POSITION_RUN_LOWER_2:
      upper = SPRITE_TERRAIN_EMPTY;
      lower = SPRITE_RUN2;
      break;
    case HERO_POSITION_JUMP_1:
    case HERO_POSITION_JUMP_8:
      upper = SPRITE_TERRAIN_EMPTY;
      lower = SPRITE_JUMP;
      break;
    case HERO_POSITION_JUMP_2:
    case HERO_POSITION_JUMP_7:
      upper = SPRITE_JUMP_UPPER;
      lower = SPRITE_JUMP_LOWER;
      break;
    case HERO_POSITION_JUMP_3:
    case HERO_POSITION_JUMP_4:
    case HERO_POSITION_JUMP_5:
    case HERO_POSITION_JUMP_6:
      upper = SPRITE_JUMP;
      lower = SPRITE_TERRAIN_EMPTY;
      break;
    case HERO_POSITION_RUN_UPPER_1:
      upper = SPRITE_RUN1;
      lower = SPRITE_TERRAIN_EMPTY;
      break;
    case HERO_POSITION_RUN_UPPER_2:
      upper = SPRITE_RUN2;
      lower = SPRITE_TERRAIN_EMPTY;
      break;
  }
  if (upper != ' ') {
    terrainUpper[HERO_HORIZONTAL_POSITION] = upper;
    collide = (upperSave == SPRITE_TERRAIN_EMPTY) ? false : true;
  }
  if (lower != ' ') {
    terrainLower[HERO_HORIZONTAL_POSITION] = lower;
    collide |= (lowerSave == SPRITE_TERRAIN_EMPTY) ? false : true;
  }
  byte digits = (score > 9999) ? 5 : (score > 999) ? 4 : (score > 99) ? 3 : (score > 9) ? 2 : 1;
  // Draw the scene
  terrainUpper[TERRAIN_WIDTH] = '';
  terrainLower[TERRAIN_WIDTH] = '';
  char temp = terrainUpper[16 - digits];
  terrainUpper[16 - digits] = '';
  lcd.setCursor(0, 0);
  lcd.print(terrainUpper);
  terrainUpper[16 - digits] = temp;
  lcd.setCursor(0, 1);
  lcd.print(terrainLower);
  lcd.setCursor(16 - digits, 0);
  lcd.print(score);
  terrainUpper[HERO_HORIZONTAL_POSITION] = upperSave;
  terrainLower[HERO_HORIZONTAL_POSITION] = lowerSave;
  return collide;
}
// Handle the button push as an interrupt
void buttonPush() {
  buttonPushed = true;
}
void setup() {
  initializeGraphics();
  lcd.begin(16, 2);
}
void loop() {
  buttonCheck();
  static byte heroPos = HERO_POSITION_RUN_LOWER_1;
  static byte newTerrainType = TERRAIN_EMPTY;
  static byte newTerrainDuration = 1;
  static bool playing = false;
  static bool blink = false;
  static unsigned int distance = 0;
  if (!playing) {
    drawHero((blink) ? HERO_POSITION_OFF : heroPos, terrainUpper, terrainLower, distance >> 3);
    if (blink) {
      lcd.setCursor(0, 0);
      lcd.print("Press Start");
    }
    delay(250);
    blink = !blink;
    if (buttonPushed) {
      initializeGraphics();
      heroPos = HERO_POSITION_RUN_LOWER_1;
      playing = true;
      buttonPushed = false;
      distance = 0;
    }
    return;
  }
  // Shift the terrain to the left
  advanceTerrain(terrainLower, newTerrainType == TERRAIN_LOWER_BLOCK ? SPRITE_TERRAIN_SOLID : SPRITE_TERRAIN_EMPTY);
  advanceTerrain(terrainUpper, newTerrainType == TERRAIN_UPPER_BLOCK ? SPRITE_TERRAIN_SOLID : SPRITE_TERRAIN_EMPTY);
  // Make new terrain to enter on the right
  if (--newTerrainDuration == 0) {
    if (newTerrainType == TERRAIN_EMPTY) {
      newTerrainType = (random(3) == 0) ? TERRAIN_UPPER_BLOCK : TERRAIN_LOWER_BLOCK;
      newTerrainDuration = 2 + random(10);
    } else {
      newTerrainType = TERRAIN_EMPTY;
      newTerrainDuration = 10 + random(10);
    }
  }
  if (buttonPushed) {
    if (heroPos <= HERO_POSITION_RUN_LOWER_2) heroPos = HERO_POSITION_JUMP_1;
    buttonPushed = false;
  }
  if (drawHero(heroPos, terrainUpper, terrainLower, distance >> 3)) {
    playing = false; // The hero collided with something. Too bad.
  } else {
    if (heroPos == HERO_POSITION_RUN_LOWER_2 || heroPos == HERO_POSITION_JUMP_8) {
      heroPos = HERO_POSITION_RUN_LOWER_1;
    } else if ((heroPos >= HERO_POSITION_JUMP_3 && heroPos <= HERO_POSITION_JUMP_5) && terrainLower[HERO_HORIZONTAL_POSITION] != SPRITE_TERRAIN_EMPTY) {
      heroPos = HERO_POSITION_RUN_UPPER_1;
    } else if (heroPos >= HERO_POSITION_RUN_UPPER_1 && terrainLower[HERO_HORIZONTAL_POSITION] == SPRITE_TERRAIN_EMPTY) {
      heroPos = HERO_POSITION_JUMP_5;
    } else if (heroPos == HERO_POSITION_RUN_UPPER_2) {
      heroPos = HERO_POSITION_RUN_UPPER_1;
    } else {
      ++heroPos;
    }
    ++distance;
  }
  delay(100);
}
void buttonCheck() {
  int b = analogRead(A0);
  if (b < 850) {
    buttonPushed = true;
  }
}
 

Arduino Juego del Ahorcado y Display LCD

Autor: Dan Wagoner

Seguramente este juego del ahorcado, ni siquiera te había pasado por la cabeza, que podría realizarse con Arduino. Un divertido juego, que tiene la ventaja, que puedes cambiar el arreglo de las cadenas de entrada, con el propósito de crear un nuevo conjunto de palabras a adivinar

https://youtu.be/W9xMJqdIZXI

Sketch para crear el juego del Ahorcado con Arduino

// Hangmanduino
// =======================================================================================
// This sketch allows you to play hangman on your Arduino. There is a small list of words
// defined in a variable, which the program randomly selects from. A potentiometer is used
// for scrolling through the alphabet and a tactile switch for making selections. When you
// select the letter, the program displays a * instead of the letter and you aren't allowed
// to make that selection again. If you guess the word, you win and the board is reset. If
// you guess too many wrong, game over and the board is reset. If you hold the select button
// for 2 seconds, the board is reset.
//
// For more information, please visit my website: www.nerdybynature.com
//
// Written By: Dan Wagoner
// Date: 08/22/2009
//
//
// References
// ----------------------------------------------------------------------------------------
// Debounce code from David A. Mellis' / Limor Fried example in the Arduino examples
// String library written by Hernando Barragan - http://www.arduino.cc/en/Tutorial/TextString
//
#include <LiquidCrystal.h>
#define SPEAKERPIN 3
#define NUMWORDS 10
//define notes for buzzer
#define LOWNOTE 100
#define ALOW 440
#define CLOW 261
#define ELOW 329
#define FLOW 349
#define CHIGH 523
#define EHIGH 659
#define GHIGH 784
#define FSHIGH 740
#define AHIGH 880
#define btnRight     0
#define btnUp        1
#define btnDown      2
#define btnLeft      3
#define btnSelect    4
#define btnNone      5
void(* resetFunc) (void) = 0;             //declare reset function @ address 0
const char* words[] = {"atmosphere", "verbatim", "postscript", "deadline", "censorship", "shorthand", "monkey", "dickhead", "dilemma", "interface"};
const char letterVal[] = "abcdefghijklmnopqrstuvwxyz";
char guessLetter;
char guessLast;
char guessed[25];
char* secretWord;
int guessedCount = 1;
int wordSize;
int gotOne = 0;
int alreadyGuessed = 0;
int showAsterisk = 0;
int buttonState;
int hangman = 0;
int totalRight = 0;
int sel = 0;
int prevKey = btnNone;
unsigned long lastDebounceTime = 0;
//LiquidCrystal lcd(12, 11, 2, 7, 8, 9, 10);
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);
String guessWord = String(10);
// hangman graphic characters
byte topleft[] = { 0x1F, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10 };
byte topright[] = { 0x1C, 0x04, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00 };
byte bottomleft[] = { 0x10, 0x10, 0x10, 0x10, 0x10, 0x1F, 0x1F, 0x1F };
byte bottomright[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
byte head[] = { 0x1C, 0x04, 0x04, 0x0E, 0x0E, 0x00, 0x00, 0x00 };
byte topbody[] = { 0x1C, 0x04, 0x04, 0x0E, 0x0E, 0x04, 0x04, 0x04 };
byte bottombody[] = { 0x04, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
byte rightarm[] = { 0x1C, 0x04, 0x04, 0x0E, 0x0E, 0x05, 0x06, 0x04 };
byte leftarm[] = { 0x1C, 0x04, 0x04, 0x0E, 0x0E, 0x15, 0x0E, 0x04 };
byte rightleg[] = { 0x04, 0x04, 0x02, 0x02, 0x01, 0x00, 0x00, 0x00 };
byte leftleg[] = { 0x04, 0x04, 0x0A, 0x0A, 0x11, 0x00, 0x00, 0x00 };
byte leftarrow[] = { 0x10, 0x18, 0x1C, 0x1E, 0x1E, 0x1C, 0x18, 0x10 };
byte rightarrow[] = { 0x01, 0x03, 0x07, 0x0F, 0x0F, 0x07, 0x03, 0x01 };
void setup()
{
  Serial.begin(9600);
  pinMode (SPEAKERPIN, OUTPUT);
  lcd.begin(16, 2);
  lcd.clear();
  lcd.setCursor(2, 0);
  lcd.print("HANGMANDUINO");
  delay(2000);
  // pick a random word using analog 5 for random data
  randomSeed(analogRead(5));
  newWord();
  //draw the board
  draw_board();
}
void newWord() {
  //pick a random word from the list
  int pick = random(NUMWORDS);
  const char* pickWord = words[pick];
  guessWord = pickWord;
  //secretWord = guessWord.getChars();
  wordSize = guessWord.length();
  Serial.println(guessWord);          // print the word to serial for cheaters like me ;)
}
void draw_board() {
  // define the custom characters
  lcd.createChar(0, topleft);
  lcd.createChar(1, topright);
  lcd.createChar(2, bottomleft);
  lcd.createChar(3, bottomright);
  lcd.createChar(4, leftarrow);
  lcd.createChar(5, rightarrow);
  // draw blank hangman table
  lcd.clear();
  lcd.home();
  lcd.write(byte(0));
  lcd.write(1);
  lcd.setCursor(0, 1);
  lcd.write(2);
  lcd.write(3);
  // print underlines
  lcd.setCursor(3, 1);
  for (int x = 0; x < wordSize; x++) {
    lcd.print("_");
  }
}
void loop() {
  // letter selection via potentiometer
  //int potVal = analogRead(POTPIN) / 40;      // 1024 / 26 ~= 39
  int potVal = sel; //Hack to use lcd keypad shield buttons
  guessLetter = letterVal[potVal];
  // if letter is different from last, print to lcd
  // this prevents from printing the same char over and over
  if (guessLetter != guessLast) {
    guessLast = guessLetter;
    showAsterisk = 0;
    // cycle through all guessed letters and determine whether to show * or the letter
    for (int x = 0; x < guessedCount; x++) {
      if (guessLetter == guessed[x]) {
        showAsterisk = 1;
      }
    }
    // print letters to the left of selected letter
    lcd.setCursor(3, 0);
    for (int x = 5; x >= 1 ; x--) {
      if (potVal - x >= 0) {
        lcd.print(letterVal[potVal - x]);
      }
      else {
        lcd.print("|");
      }
    }
    // print left arrow
    lcd.write(4);
    // print the letter
    if (showAsterisk == 0) {
      lcd.setCursor(9, 0);
      lcd.print(guessLetter);
      alreadyGuessed = 0;
    }
    // print a *
    else {
      lcd.setCursor(9, 0);
      lcd.print("*");
      alreadyGuessed = 1;
    }
    // print right arrow
    lcd.write(5);
    // print letters to the right of selected letter
    lcd.setCursor(11, 0);
    for (int x = 1; x <= 5 ; x++) {
      if (potVal + x <= 25) {
        lcd.print(letterVal[potVal + x]);
      }
      else {
        lcd.print("|");
      }
    }
  }
  int k = inkeys();
  if ( k == btnSelect ) {
    gotOne = 0;
    if (alreadyGuessed == 0) {
      alreadyGuessed = 1;
      lcd.setCursor(9, 0);
      lcd.print("*");
      char buf[wordSize+1];
      guessWord.toCharArray(buf,wordSize+1);
      for (int i = 0; i < wordSize; i++) {
        if (buf[i] == guessLetter) {
          lcd.setCursor(i + 3, 1);
          lcd.print(guessLetter);
          gotOne = 1;
          totalRight = totalRight + 1;
        }
      }
      // add letter to guessed letter array
      guessed[guessedCount] = guessLetter;
      guessedCount++;
      // none of the letters match, draw the next body part on the hangman
      if (gotOne == 0) {
        buzz(LOWNOTE, 500);
        hangman++;
        draw_hangman(hangman);
      }
      else {
        // letter is in word, sound buzzer
        buzz(FSHIGH, 30);
        buzz(AHIGH, 50);
      }
      //all letters have been guessed...WIN!
      if (totalRight == wordSize) {
        gameOver(1);
      }
    }
    // this letter has already been guessed, sound buzzer
  } else if (k == btnRight) {
    if (sel > 24) {
      sel = 0;
    } else {
      sel++;
    }
  } else if (k == btnLeft) {
    if (sel <= 0) {
      sel = 25;
    } else {
      sel--;
    }
  }
}
void draw_hangman(int var) {
  switch (var) {
    case 1:
      lcd.createChar(1, head);           // head
      break;
    case 2:
      lcd.createChar(1, topbody);        // body
      lcd.createChar(3, bottombody);
      break;
    case 3:
      lcd.createChar(1, rightarm);       // right arm
      break;
    case 4:
      lcd.createChar(1, leftarm);        // left arm
      break;
    case 5:
      lcd.createChar(3, rightleg);       // right leg
      break;
    case 6:
      lcd.createChar(3, leftleg);        // left leg
      break;
    case 7:
      gameOver(0);
    default:
      break;
  }
}
void gameOver(int whatToDo) {
  // decide whether win, lose or restart game
  switch (whatToDo) {
    case 0:  // GAME OVER
      lcd.clear();
      lcd.setCursor(6, 0);
      lcd.print("GAME");
      lcd.setCursor(6, 1);
      lcd.print("OVER");
      //buzzer sound
      buzz(ELOW, 500);                  // GAME OVER!
      buzz(CLOW, 1000);                 // sound buzzer
      break;
    case 1:  // WINNER
      lcd.clear();
      lcd.setCursor(4, 0);
      lcd.print("HANGMAN");
      lcd.setCursor(4, 1);
      lcd.print("MASTER!");
      // buzzer sound
      buzz(ALOW, 150);
      buzz(CHIGH, 150);
      buzz(EHIGH, 150);
      buzz(AHIGH, 150);
      delay(150);
      buzz(GHIGH, 150);
      buzz(AHIGH, 500);
  }
  delay(2000);
  resetFunc();      // reset arduino for a new game
}
void buzz (int frequencyInHertz, long timeInMilliseconds) {
  Serial.println(frequencyInHertz);
  long delayAmount = (long)(1000000 / frequencyInHertz);
  long loopTime = (long)((timeInMilliseconds * 1000) / (delayAmount * 2));
  for (int x = 0; x < loopTime; x++) {
    digitalWrite(SPEAKERPIN, HIGH);
    delayMicroseconds(delayAmount);
    digitalWrite(SPEAKERPIN, LOW);
    delayMicroseconds(delayAmount);
  }
  delay(20);
}
int getKey() {
  int b = analogRead(A0);
  if (b > 1000) return btnNone;
  delay(8);
  if (b < 66) return btnRight;
  if (b < 221) return btnUp;
  if (b < 396) return btnDown;
  if (b < 602) return btnLeft;
  if (b < 902) return btnSelect;
}
int inkeys() {
  int k = getKey();
  while (k == btnNone) {
    k = getKey();
    delay(10);
  }
  delay(200); //debounce
  return k;
}
 

Arduino Juego de PacMan y Display LCD

Autor: Jean Malha

Otro juego clásico en tu Arduino, Pacman, y aunque no es una versión a tamaño completa como lo conocemos, seguro que tratar de escapar del fantasma te hará recordar viejos tiempos.

https://youtu.be/QSWBZ1kEbXM

Sketch para crear el juego con Arduino de Pacman

//Pacman
//Written By: Jean Malha
//http://forum.snootlab.com/viewtopic.php?f=34&t=207
//
//#include "Wire.h" // insertion de la librairie I2C (obligatoire)
//#include <Deuligne.h> // insertion de la librairie Deuligne (obligatoire)
#include <LiquidCrystal.h>
#define VITESSE_PAC 150
#define VITESSE_FANT 2000
#define MAXX 15
#define MAXY 1
#define btnRight     0
#define btnUp        1
#define btnDown      2
#define btnLeft      3
#define btnSelect    4
#define btnNone      5
void(* resetFunc) (void) = 0;
LiquidCrystal lcd(8, 9, 4, 5, 6, 7); 
//Deuligne lcd; // Déclaration de l'objet lcd
// Charactère spécifique pacman
byte pacman[8] = {
  B00000,
  B00000,
  B01110,
  B11011,
  B11100,
  B01110,
  B00000,
  B00000
};
// Charactère spécifique fantome
byte fantome[8] = {
  B00000,
  B00000,
  B01110,
  B10101,
  B11111,
  B11111,
  B10101,
  B00000
};
byte point[8] = {
  B00000,
  B00000,
  B00000,
  B01110,
  B01110,
  B00000,
  B00000,
  B00000
};
// Tableau des points à manger
byte points[MAXX+1][MAXY+1];
int xpac=2; // Position de pacman en X (colone)
int ypac=1; //position de pacmanen y (ligne)
int xfant=15;// Position du fantome en X (colone)
int yfant=0;// Position du fantome en Y (ligne)
byte light=true; //Eclairage
long keystruck=0; //dernier appui sur un bouton
long poursuite=0; //dernier movement du fantome
byte partieEnCours=true; // pour eviter de boucler sur la fin
byte vide=false; // pour tester si tout est manger
byte level=0; // niveau
int score=0; // niveau
void bouge(int x,int y) // fonction pour bouger pacman
{
  int oldx=xpac;
  int oldy=ypac;
  if (((xpac+x)>=0)&((xpac+x)<=MAXX)) xpac=xpac+x; //Si pas sorti d'ecran, on change x
  if (((ypac+y)>=0)&((ypac+y)<=MAXY)) ypac=ypac+y;//Si pas sorti d'ecran, on change y
  lcd.setCursor(xpac,ypac); // On se place en nouvelle position
  lcd.write(byte(0)); // et on pose le caractere 0 (Pacman)
  lcd.setCursor(oldx,oldy); // On se place en ancienne position
  if ((xpac!=oldx)||(ypac!=oldy)) lcd.print(" "); // et on efface Pacman (s'il a bougé)
  if(points[xpac][ypac]){
    points[xpac][ypac]=false; // mange le truc
    score++;
  }
  vide=true;
  for (int i=0; i<=MAXX; i=i+1)
    for (int j=0; j<=MAXY; j=j+1)
      if (points[i][j])  vide=false;
  if ((vide)&&(partieEnCours)) gagne();
}
void perdu(){
  lcd.setCursor(0, 0); // on se place au point 0,0 (1ere ligne, 1er caractere)
  lcd.print("***Game Over****"); // on écrit le début du texte de début
  lcd.setCursor(0, 1); // on se place au point 0,1 (2eme ligne, 1er caractere)
  lcd.print("***");
  lcd.print(score);
  lcd.print("***"); 
  delay(2000);
  resetFunc();
}
void gagne()
{
  level++;
  lcd.setCursor(0, 0); // on se place au point 0,0 (1ere ligne, 1er caractere)
  lcd.print("*** Next level ***"); // on écrit le début du texte de début
  lcd.setCursor(0, 1); // on se place au point 0,0 (1ere ligne, 1er caractere)
  lcd.print("*** ");
  lcd.print(level,DEC);
  lcd.print(" ***"); // on écrit le début du texte de début
  delay(2000); // 2 secondes de pause
  initLevel(); //reinitialisation du tableau
}
void poursuis() // fonction pour bouger fantome
{
  int oldx=xfant;
  int oldy=yfant;
  if (yfant<ypac) yfant=yfant+1;
  else if (yfant>ypac) yfant=yfant-1;
  else if (xfant<xpac) xfant=xfant+1;
  else if (xfant>xpac) xfant=xfant-1;
  lcd.setCursor(xfant,yfant); // On se place en nouvelle position
  lcd.write(1); // et on pose le caractere 0 (Fantome)
  lcd.setCursor(oldx,oldy); // On se place en ancienne position
  if ((oldx!=xfant)||(oldy!=yfant)) // et on efface Fantome (s'il a bougé)
  {
    if (points[oldx][oldy]) lcd.write(2); // remplacé par un point si pas mangé
    else lcd.print(" "); // remplacé par un espace si déja magé
  }
}
//initialisation du tableau 
void initLevel(){
  for (int i=0; i<=MAXX; i=i+1)
    for (int j=0; j<=MAXY; j=j+1){
      points[i][j]=true; //initialisation du tableau des trucs à manger
      lcd.setCursor(i-1, j-1); // on se place au point j,i 
      lcd.write(2); // on écrit les points
    }
  lcd.setCursor(xpac,ypac); // On se place en position de départ de pacman
  lcd.write(byte(0)); // et on pose le caractere 0 (Pacman)
  lcd.setCursor(xfant,yfant); // On se place en position de départ du fantome
  lcd.write(1); // et on pose le caractere 1 (fantome)
  poursuite=millis(); // On initialise le timer de poursuite (pour eviter un mouvement immédiat)
  vide=false;
}
void setup() {
  Serial.begin(9600);
  //Wire.begin(); // initialisation I2C (obligatoire)
  //lcd.init(); // initialisation LCD (obligatoire)
  lcd.begin(16, 2);
  lcd.createChar(0, pacman); // creation du caractere pacman et affectation au numéro 0
  lcd.createChar(1, fantome); // creation du caractere de fantome et affectation au numéro 1
  lcd.createChar(2, point); // creation du caractere de point et affectation au numéro 2
  //lcd.backLight(true); // on allume le retro eclairage
  lcd.setCursor(0, 0); // on se place au point 0,0 (1ere ligne, 1er caractere)
  lcd.print("Pacman!"); // on écrit le début du texte de début
  delay (5000); // Splash screen
  initLevel(); // initialisation du tableau
}
void loop() {
  int thisChar = Serial.read();
  switch (thisChar)
  {
  case 'r':
    lcd.scrollDisplayRight();
    break;
  case 'l':
    lcd.scrollDisplayLeft();
    break;
  }
  if ((thisChar>'a')&(thisChar<'z'))
  {
    lcd.setCursor(1,1);
    lcd.write(thisChar);
  }
  if (millis()-keystruck>VITESSE_PAC) // Si plus de 200ms depuis le dernier mouvement de joystick
  {
    int joy=getKey();
    switch (joy)
    {
    case btnNone:
      break;
    case btnLeft:
      Serial.print("Pacman bouge à gauche.n"); // envoi de controle sur liaison série
      Serial.print(keystruck);
      bouge(-1,0);// déplacement
      keystruck=millis(); // remise à zero du timer de mouvement
      break;
    case btnRight:
      Serial.print("Pacman bouge à droiten");// envoi de controle sur liaison série
      bouge(1,0);// déplacement
      keystruck=millis(); // remise à zero du timer de mouvement
      break;
    case btnUp:
      Serial.print("Pacman bouge en hautn");// envoi de controle sur liaison série
      bouge(0,-1);// déplacement
      keystruck=millis(); // remise à zero du timer de mouvement
      break;
    case btnDown:
      Serial.print("Pacman bouge en basn");
      bouge(0,1);// déplacement
      keystruck=millis(); // remise à zero du timer de mouvement
      break;
      /*case 4:
       Serial.print("centren");
       light=!light; //On inverse le statut d'allumage
       lcd.backLight(light); // on applique
       keystruck=millis(); // remise à zero du timer de mouvement
       break;*/
    default:
      Serial.print(joy); //au cas ou...
      keystruck=millis(); // remise à zero du timer de mouvement
    }; 
  };
  if (millis()-poursuite>VITESSE_FANT/(level+1)+10)
  {
    poursuis();
    poursuite=millis();
  }
  if ((xpac==xfant)&&(ypac==yfant)&&(partieEnCours)) 
  {
    perdu();
  }
}
int getKey() {
  int b = analogRead(A0);
  if (b > 1000) return btnNone;
  delay(8);
  if (b < 50) return btnRight;
  if (b < 180) return btnUp;
  if (b < 330) return btnDown;
  if (b < 520) return btnLeft;
  if (b < 700) return btnSelect;
}
 

Arduino Juego tipo Arkanoid y Display LCD

Autor: Mason

¿Alguien recuerda el juego de Arkanoid? Ese donde debías hacer lo imposible porque una pelota no cayera al suelo mientras destruías bloques. Bueno pues también es posible recrearlo con Arduino y una pantalla LCD.

https://youtu.be/cPdraCbdFoo

Sketch para recrear un juego Arkanoid con Arduino

//Written by Mason 2012
#include <LiquidCrystal.h>
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);
int adc_key_in  = 0;
#define btnRIGHT  0
#define btnUP     1
#define btnDOWN   2
#define btnLEFT   3
#define btnSELECT 4
#define btnNONE   5
int bpm = 30;
const int whole = (60000/bpm);
const int half = 30000/bpm;
const int quarter = 15000/bpm;
const int eight = 7500 / bpm;
const int sixteenth = 3750 / bpm;
const int thirty2 = 1875 / bpm;
int musicpin = 3;
float vballx = 1;
float vbally = 0.2;
float xball = 1;
float yball = 1;
int xmax = 80;
int delaytime = 60;
int score;
//define graphics
byte dot[8] = {
  0x0,
  0x0,
  0x0,
  0x0,
  0x0,
  0x0,
  0x0,
  0x0
};
byte paddle[8] = {
  B00000,
  B00000,
  B00000,
  B00000,
  B00000,
  B11000,
  B11000,
  B11000
};
byte wallFR [8] = {
  0x0,
  0x0,
  0x0,
  0x0,
  0x0,
  0x0,
  0x0,
  0x0
};
byte wallFL [8] = {
  0x0,
  0x0,
  0x0,
  0x0,
  0x0,
  0x0,
  0x0,
  0x0
};
byte wallBR [8] = {
  0x0,
  0x0,
  0x0,
  0x0,
  0x0,
  0x0,
  0x0,
  0x0
};
byte wallBL [8] = {
  0x0,
  0x0,
  0x0,
  0x0,
  0x0,
  0x0,
  0x0,
  0x0
};
//Define the wall tiles
boolean wallarray [16] = {
  1,1,1,1,
  1,1,1,1,
  1,1,1,1,
  1,1,1,1};
int lcd_key     = 0;
int paddle_pos = 1;
int read_LCD_buttons()
{
  adc_key_in = analogRead(0);      // read the value from the sensor
  // my buttons when read are centered at these values: 0, 144, 329, 504, 741
  // we add approx 50 to those values and check to see if we are close
  if (adc_key_in > 1000) return btnNONE; // We make this the 1st option for speed reasons since it will be the most likely result
  if (adc_key_in < 50)   return btnRIGHT;
  if (adc_key_in < 195)  return btnUP;
  if (adc_key_in < 380)  return btnDOWN;
  if (adc_key_in < 555)  return btnLEFT;
  if (adc_key_in < 790)  return btnSELECT;
  return btnNONE;  // when all others fail, return this...
}
void getPaddle() {
  lcd_key = read_LCD_buttons();  // read the buttons
  switch (lcd_key)               // depending on which button was pushed, we perform an action
  {
  case btnUP:
    {
      if (paddle_pos > 1) paddle_pos -= 1;
      break;
    }
  case btnDOWN:
    {
      if (paddle_pos < 14) paddle_pos += 1;
      break;
    }
  }
}
void drawwalls()
{
  for (int i=0; i < 8; i+=4)
  {
    for (int j = 0; j<4; j++)
    {
      wallFL[j+i]=wallarray[i/2]*16+wallarray[i/2]*8+wallarray[i/2+1]*2+wallarray[i/2+1]*1;
      wallFR[j+i]=wallarray[i/2+4]*16+wallarray[i/2+4]*8+wallarray[i/2+5]*2+wallarray[i/2+5]*1;
      wallBL[j+i]=wallarray[i/2+8]*16+wallarray[i/2+8]*8+wallarray[i/2+9]*2+wallarray[i/2+9]*1;
      wallBR[j+i]=wallarray[i/2+12]*16+wallarray[i/2+12]*8+wallarray[i/2+13]*2+wallarray[i/2+13]*1;
    }
  }
  lcd.createChar(2,wallFL);
  lcd.createChar(3,wallFR);
  lcd.createChar(4,wallBL);
  lcd.createChar(5,wallBR);
  lcd.setCursor(14,0);
  lcd.write((byte)2);
  lcd.write((byte)4);
  lcd.setCursor(14,1);
  lcd.write((byte)3);
  lcd.write((byte)5);
}
//place dot on screen (80x16)
void placedot(int x, int y) {
  createdot(x%5,y%8);
  lcd.setCursor(x/5,y/8);
  lcd.write((byte)0);
}
void placepaddle(int y) {
  for (int i=0;i<8;i++){
    paddle[i]=0x0;
  }
  if (y%8>0) paddle[y%8-1] = 0x10;
  paddle[y%8] = 0x10;
  if (y%8<7)paddle[y%8+1] = 0x10;
  lcd.createChar(1,paddle);
  lcd.setCursor(0,y/8);
  lcd.write((byte)1);
}
//draw a dot in the 5x8 space of the character at location x,y
void createdot(int x, int y)  {
  for (int i=0;i<8;i++){
    dot[i]=0x0;
  }
  if (y > 0) dot[y-1] = (B11000 >> x);
  dot[y] = (B11000 >> x);
  lcd.createChar(0,dot);
}
int blockcounter = 0;
void handlecollisions()
{
  xball = xball + vballx;
  yball = yball + vbally;
  //Handle collisions in y
  if ((yball > 15) || (yball < 1)) {
    vbally = -vbally;
    tone(musicpin,880,eight);
  }
  //Handle Collisions in x
  if (xball > 69) //is the ball in the correct area
  {
    for (int i=0;i < 16; i++)
    {
      if (xball>(70+2*(i%2)+5*(i/8))) {//x condition met
        if ((yball>(2*(i%8)))&&(yball<(2*(i%8)+4))){ // y condition met
          if (wallarray[i] == 1){ //wall exists
           tone(musicpin,1174,eight);
           delay(eight);
            wallarray[i]=0;
            vballx = -vballx;
            xball = 70;
          }
        }
      }
    }
  }
  // Check to see if at edge of screen.
  if (xball > xmax)   {
    vballx = -vballx;
    tone(musicpin,880,eight);
  }
  //check for ball and paddle
  if (xball < 1)   {
    if (paddle_pos > int(yball)-2 && paddle_pos < int(yball)+2){
      tone(musicpin,1397,sixteenth);
      delay(sixteenth);
      tone(musicpin,1567,eight);
      vballx = -vballx;
      vbally *= random(1,4) ;
      vbally /= 2;
      Serial.println(vbally);
      score += 1;
      //xmax -= 2;
      delaytime -= 2;
      vballx += 0.1;
      int left = 0;
      for (int i=0;i<16;i++)
      { left += wallarray[i];
      }
      if (left < 1)       {
        lcd.clear();
        lcd.print("You Win! ");
        arkanoidsong();
        lcd.print(score);
        delay(15000);
        xmax = 80;
        score = 0;
        delaytime = 60;
        for (int i=0;i< 16;i++)
        {wallarray[i]=1;}
      }
    }
    else {
      tone(musicpin,349,sixteenth);
      delay(sixteenth);
      tone(musicpin,329,eight);
      vballx = -vballx;
      vbally *= random(1,4) ;
      vbally /= 2;
      score -=1;
    }
  }
}
void arkanoidsong(){
    tone(musicpin, 1568, eight);//g6
  delay(eight);
  noTone(musicpin);
  delay(sixteenth);
  tone(musicpin, 1568, sixteenth);//g6
  delay(sixteenth);
  tone(musicpin, 1864, half);//a#6
  delay(half);
  noTone(musicpin);
  delay(thirty2);
  tone(musicpin, 1760, eight);//a6
  delay(eight);
  tone(musicpin, 1568, eight);//g6
  delay(eight);
  tone(musicpin, 1396, eight);//f6
  delay(eight);
  tone(musicpin, 1760, eight);//a6
  delay(eight);
  tone(musicpin, 1568, half);
  delay(half);
}
void setup() {
  lcd.begin(16, 2);
  delay(100);
  lcd.clear();
  lcd.setCursor(0,0);
  lcd.print("Arduinoid");
  lcd.setCursor(0,1);
  lcd.print("Get the Bricks");
  delay(500);
  arkanoidsong();
  delay(500);
  lcd.setCursor(0,1);
  lcd.print("Press to Start");
  while(analogRead(0)>1000)
  {
    delay(10);
  }
  Serial.begin(9600);
}
void loop() {
  lcd.clear();
  getPaddle();
  drawwalls();
  placepaddle(paddle_pos);
    handlecollisions();
  placedot(xball,yball);
  delay(delaytime);
} 

Arduino Juego de Helicóptero y Display LCD

Autor: Kevin Loney

Otro juego tipo Runner, pero esta vez, tendremos que controlar un bonito helicóptero. Un juego sencillo, pero la verdad es que las gráficas están muy bien hechas, y el hecho de que la dificultad aumente con el tiempo, le da un plus impresionante.

https://youtu.be/PMVz6QXlZjA

Sketch para crear el juego del Helicóptero con Arduino

// This work is licensed under the Creative Commons Attribution-ShareAlike 3.0 Unported License.
// To view a copy of this license, visit http://creativecommons.org/licenses/by-sa/3.0/.
#include <EEPROM.h>
#include <LiquidCrystal.h>
// Class for cycling through a sequence of animation frames
class Animation {
  private:
    int _current_frame;
    int _frame_count;
    int *_frames;
  public:
    Animation(int frame_count, int *frames) {
      _current_frame = 0;
      _frame_count = frame_count;
      _frames = frames;
    }
    int next() {
      _current_frame++;
      if (_current_frame == _frame_count) {
        _current_frame = 0;
      }
      return _frames[_current_frame];
    }
    int prev() {
      _current_frame++;
      if (_current_frame < 0) {
        _current_frame += _frame_count;
      }
      return _frames[_current_frame];
    }
    int current() {
      return _frames[_current_frame];
    }
};
// Constants for identifying each sprite
const byte SPRITE_TAIL0 = 0;
const byte SPRITE_TAIL1 = 1;
const byte SPRITE_BODY0 = 2;
const byte SPRITE_BODY1 = 3;
const byte SPRITE_WALLB = 5;
const byte SPRITE_WALLT = 6;
const byte SPRITE_EXPL = 7;
// Sprite custom character data
byte sprite_tail0[8] = {0b00000, 0b00000, 0b00000, 0b10001, 0b01111, 0b00101, 0b00000, 0b00001};
byte sprite_tail1[8] = {0b00000, 0b00000, 0b00000, 0b00101, 0b01111, 0b10001, 0b00000, 0b00001};
byte sprite_body0[8] = {0b00000, 0b00100, 0b00100, 0b11110, 0b10101, 0b11110, 0b10100, 0b11111};
byte sprite_body1[8] = {0b00000, 0b11111, 0b00100, 0b11110, 0b10101, 0b11110, 0b10100, 0b11111};
byte sprite_wallt[8] = {0b00000, 0b00100, 0b01001, 0b11110, 0b01001, 0b00100, 0b00000, 0b00000};
byte sprite_wallb[8] = {0b00000, 0b00100, 0b00100, 0b01010, 0b01110, 0b01010, 0b01110, 0b01010};
byte sprite_expl[8] = {0b00100, 0b10100, 0b01101, 0b01010, 0b01010, 0b10110, 0b00101, 0b00100};
// Animation sequences
int seq_tail[] = {SPRITE_TAIL0, SPRITE_TAIL1};
int seq_body[] = {SPRITE_BODY0, SPRITE_BODY1};
Animation anim_tail(2, seq_tail);
Animation anim_body(2, seq_body);
// LCD settings
//LiquidCrystal lcd(13, 12, 11, 10, 9, 8, 7, 6, 5, 4);
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);
//const int lcd_backlight = 3;
// Button pin constants
//const int button = 2;
//Speaker pin contstant
const int speaker = 3;
// Some game constants that can be adjusted to control difficulty
const unsigned long frame_rate = 125;
unsigned long frame_next = 0;
int game_mode = 0;
unsigned long score = 0;
boolean new_highscore = false;
boolean first = true;
boolean button_state = false;
const unsigned debounce_time = 20;
//volatile boolean button_toggled = true;
volatile unsigned long next_read = 0;
// Do "smart" spawning of walls so the game is never unplayable
unsigned int mask_wall = 0b1111000000000000;
// Helicopter collision detection mask
unsigned int mask_heli = 0b0000000000000011;
// Bitwise representations of obstacle locations
unsigned int walls_top = 0x0000;
unsigned int walls_bot = 0x0000;
// Maximum and minimum wall advance rates
unsigned long max_wall_advance_rate = 350;
unsigned long min_wall_advance_rate = 140;
unsigned long wall_advance_rate = max_wall_advance_rate;
unsigned long wall_advance_next = 0;
// Death animation and flash times
unsigned long death_rate = 150;
unsigned long death_hold = 1500;
void setup() {
  pinMode(speaker,OUTPUT);
  read_button();
  // Seed the random number generator using noise from analog 0
  unsigned long seed = 0;
  for (int n = 0; n < 32; ++n) {
    randomSeed(seed);
    delay(random() & 0xf);
    seed <<= 1;
    seed |= analogRead(1) & 1;
  }
  // Generate the custom characters for the games sprites
  lcd.createChar(SPRITE_TAIL0, sprite_tail0);
  lcd.createChar(SPRITE_TAIL1, sprite_tail1);
  lcd.createChar(SPRITE_BODY0, sprite_body0);
  lcd.createChar(SPRITE_BODY1, sprite_body1);
  lcd.createChar(SPRITE_WALLB, sprite_wallb);
  lcd.createChar(SPRITE_WALLT, sprite_wallt);
  lcd.createChar(SPRITE_EXPL, sprite_expl);
  // Start up the LCD
  lcd.begin(16, 2);
  lcd.noCursor();
  lcd.clear();
  lcd.home();
  // Start at the home screen
  set_game_mode(0);
}
void loop() {
  boolean update = false;
  // Update animation sequences
  unsigned long now = millis();
  if (frame_next < now) {
    anim_tail.next();
    anim_body.next();
    frame_next = now + frame_rate;
    update = true;
  }
  read_button();
  update = true;
  // Take appropriate action depending on the current game mode
  switch (game_mode) {
    case 0: game_home(update) ; break;
    case 1: game_play(update) ; break;
    case 2: game_over(update); break;
    default: game_mode = 0;
  }
}
// The home state
void game_home(boolean update) {
  if (first) {
    first = false;
    score = get_highscore();
    lcd.clear();
  }
   read_button();
   if (!button_state) {
    set_game_mode(1);
  }
  if (update) {
    lcd.setCursor(0, 0);
    lcd.print("Helicopter! ");
    lcd.write(anim_tail.current());
    lcd.write(anim_body.current());
    lcd.setCursor(0, 1);
    lcd.print("Best: ");
    lcd.print(score);
  }
}
// The game play state
void game_play(boolean update) {
  if (first) {
    first = false;
    score = 0;
    new_highscore = false;
    walls_bot = 0;
    walls_top = 0;
    wall_advance_rate = max_wall_advance_rate;
    lcd.clear();
  }
  unsigned long now = millis();
  // Is it time to advance the obstacles?
  if (now > wall_advance_next) {
    if (wall_advance_rate > min_wall_advance_rate) {
      wall_advance_rate--;
    }
    wall_advance_next = now + wall_advance_rate;
    walls_top >>= 1;
    walls_bot >>= 1;
    ++score;
    // Can we spawn a new obstacle for the player?
    if (((walls_top | walls_bot) & mask_wall) == 0) {
      if (random() & 1) {
        walls_top |= 0x8000;
      } else {
        walls_bot |= 0x8000;
      }
    }
    update = true;
  }
  // Render the next frame
  if (update) {
    lcd.setCursor(0, button_state ? 0 : 1);
    lcd.write(0x20);
    lcd.write(0x20);
    for (int n = 0; n < 16; ++n) {
      lcd.setCursor(n, 0);
      if (walls_top & (1 << n)) {
        lcd.write(SPRITE_WALLT);
        lcd.write(0x20);
      }
    }
    for (int n = 0; n < 16; ++n) {
      lcd.setCursor(n, 1);
      if (walls_bot & (1 << n)) {
        lcd.write(SPRITE_WALLB);
        lcd.write(0x20);
      }
    }
    // Handle a collision with an obstacle
    lcd.setCursor(0, button_state ? 1 : 0);
    if (mask_heli & (button_state ? walls_bot : walls_top)) {
      lcd.write(SPRITE_EXPL);
      lcd.write(SPRITE_EXPL);
      tone(speaker,329, 100);
      boolean ramp = false;
      unsigned long curr = millis(), prev = curr, next = curr + death_rate;
      unsigned long death_stop = millis() + death_hold;
      // Flash the LCDs backlight
      while (curr <= death_stop) {
        curr = millis();
        if (curr > next) {
          prev = curr;
          next = prev + death_rate;
        }
        // Fade the backlight on and off
        unsigned long v = map(curr, prev, next, 0, 255);
        if (!ramp) {
          v = 255 - v;
        }
        //analogWrite(lcd_backlight, v);
      }
      //analogWrite(lcd_backlight, 255);
      // Store the new highscore
      if (score > get_highscore()) {
        set_highscore(score);
        new_highscore = true;
      }
      set_game_mode(2);
    } else {
      lcd.write(anim_tail.current());
      lcd.write(anim_body.current());
    }
  }
}
// The game over state
// Displays the players score
void game_over(boolean update) {
  if (first) {
    first = false;
    lcd.clear();  
  }
  read_button();
  if (!button_state) {
    set_game_mode(0);
  }
  if (update) {
    lcd.setCursor(0, 0);
    lcd.print(new_highscore ? "New High Score!" : "Game Over");
    lcd.setCursor(0, 1);
    lcd.print("Score: ");
    lcd.print(score);
  }
}
// Change the game mode and reset some state information
void set_game_mode(int mode) {
  button_state = true;
  game_mode = mode;
  first = true;
  lcd.clear();
}
// Retrieve the high score from EEPROM
unsigned long get_highscore() {
  unsigned long b0 = EEPROM.read(0);
  unsigned long b1 = EEPROM.read(1);
  unsigned long b2 = EEPROM.read(2);
  unsigned long b3 = EEPROM.read(3);
  unsigned long cs = EEPROM.read(4);
  if (((b0 + b1 + b2 + b3) & 0xff) == cs) {
    return b0 | (b1 << 8) | (b2 << 16) | (b3 << 24);
  }
  return 0;
}
// Store the new highscore in EEPROM
void set_highscore(unsigned long score) {
  byte b0 = (byte)((score >> 0) & 0xff);
  byte b1 = (byte)((score >> 8) & 0xff);
  byte b2 = (byte)((score >> 16) & 0xff);
  byte b3 = (byte)((score >> 24) & 0xff);
  byte cs = b0 + b1 + b2 + b3;
  EEPROM.write(0, b0);
  EEPROM.write(1, b1);
  EEPROM.write(2, b2);
  EEPROM.write(3, b3);
  EEPROM.write(4, cs);
}
void read_button() {
  if (millis() > next_read) {
    next_read = millis() + 200;
    button_state = (analogRead(A0) > 1000);
  }
}
 

Arduino Juego de Carreras y Display LCD

Autor: Pierre Massat

¿Eres amante de los autos y de Arduino? Pues este es tu juego, ya que tendrás que correr a una velocidad considerable, esquivando vehículos sin chocar contra las paredes. El no saber dónde saldrá la próxima vuelta y los próximos carros, le dan un plus de adrenalina.

https://youtu.be/G12x4pmsfog

Sketch para crear un juego de carreras con Arduino

/* This is a simple car game written for a 16x2 LCD screen connected to a Arduino Uno.
It was written by Pierre Massat:
Welcome.
It uses pins 12, 11, 6, 5 and 4 to connect the LCD. 
It uses the LiquidCrystal library for Arduino :
http://arduino.cc/en/Reference/LiquidCrystal
Note that this codes was written for an ad-hoc pad of 10 buttons connected to the board. 
Only two simple push buttons are required to control the game. I don't change the code due to
lack of time, but you'll simply have to remove the "detect()" function, and adapt the code in
the "initial()" function and at the beginning of the main loop (using a simple digitalRead()).
Please refer to this page if you have trouble wiring your buttons :
http://www.arduino.cc/en/Tutorial/Pushbutton
The only magic there is to this game lies in the use of the extra memory for custom characters 
available in the LCD screen (a maximum of 8 characters can be created) :
http://arduino.cc/en/Reference/LiquidCrystalCreateChar
Have fun!
----------------------------------------------------------------------------------------------
Voici un jeu de voiture très simple écrit pour un écran LCD 16x2 connecté à un Arduino Uno.
Il a ét écrit par Pierre Massat:
Welcome.
Il utilise les entrées 12, 11, 6, 5 et 4 pour connecter le LCD.
Il utilise également la bibliothèque LiquidCrystal pour Arduino:
http://arduino.cc/en/Reference/LiquidCrystal
Veuillez noter que ce code a été écrit pour un pad ad-hoc de 10 boutons poussoirs connecté à l'Arduino.
Seuls 2 boutons poussoirs simples sont nécessaires pour contrôler le jeu. Je n'ai pas changé le code par 
manque de temps, mais vous pourrez simplement supprimer la fonction "detect()", et adapter le code dans 
la fonction "initial()" et au début de la boucle principale (en utilisant un simple digitalRead()).
Référez-vous à la page suivante si vous avez du mal à connecter vos boutons:
http://www.arduino.cc/en/Tutorial/Pushbutton
La seule magie tient à l'utilisation de la mémoire disponible pour des caractères sur mesure dans
l'écran LCD (un maximum de 8 caractères peuvent être créés):
http://arduino.cc/en/Reference/LiquidCrystalCreateChar
Amusez-vous bien!*/
//Setup the Arduino LCD Keypad Shield
#include <LiquidCrystal.h>
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);
#define btnRight     0
#define btnUp        1
#define btnDown      2
#define btnLeft      3
#define btnSelect    4
#define btnNone      5
int pos=0;
// We define all the graphical obects here as arrays of bytes. 
// We use the memory for custom characters featured in the LCD screen.
//------------------------------------------------------------------------
// On définit les objects graphiques ici sous forme de tableaux d'octets.
// On utilise la mémoire interne à l'écrant LCD dédiée aux caractères créés par l'utilisateur.
byte backslash[8] ={ // Définition du caractère backslash
  B00000,
  B10000,
  B01000,
  B00100,
  B00010,
  B00001,
  B00000,
};
// Car (straight up)
//-------------
// Voiture (droite)
byte voiture[8] = { 
  B00000,
  B00000,
  B01010,
  B00100,
  B00100,
  B01110,
  B00000,
};
// Car (truning to the left)
//---------------
// Voiture (tournant vers la gauche)
byte voitureG[8] = {
  B00000,
  B01000,
  B11000,
  B00101,
  B00010,
  B00100,
  B00000,
};
// Car (truning to the right)
//---------------
// Voiture (tournant vers la droite)
byte voitureD[8] = {
  B00000,
  B00010,
  B00011,
  B10100,
  B01000,
  B00100,
  B00000,
};
// Cow...
//---------
// Vache...
byte vache[8] = {
  B00000,
  B00000,
  B11000,
  B01111,
  B01010,
  B00000,
  B00000,
};
// Tree...
//---------
// Arbre...
byte arbre[8] = {
  B01110,
  B11111,
  B11111,
  B01110,
  B00100,
  B00100,
  B00100,
};
// Pine tree
//----------
// Sapin
byte sapin[8] = {
  B00000,
  B00000,
  B00100,
  B01110,
  B01110,
  B11111,
  B00100,
};
// House
//---------
// Maison
byte maison[8] = {
  B00000,
  B00000,
  B00000,
  B00000,
  B01110,
  B11111,
  B11011,
};
// Various variables used later on...
//-----------------------------------
// Diverses variables utilisées plus bas...
char haut[16] = "               ";
int posRoute;
int oldposRoute=5;
int actuDir=1;
int bouton;
unsigned long getTime;
unsigned long getTimeVoiture;
int collision=0;
unsigned long initTime;
int largRoute; // Largeur de la route
int limitDroite; // Limite à gauche de la route pour ne pas dépasser à droite.
int moveVoiture=1;
int posVoiture;
int posConc1;
int obsConc;
int compteurConc;
void setup() {
  int i;
  lcd.begin(16,2); // Initialize the LCD / Initialisation de l'écran LCD
  lcd.createChar(1,backslash); // Loading of custom characters into LCD memory / Chargement des caractères sur mesure dans la memoire du LCD
  lcd.createChar(2,voiture); 
  lcd.createChar(3,voitureG); 
  lcd.createChar(4,voitureD); 
  lcd.createChar(5,vache);
  lcd.createChar(6,arbre);
  lcd.createChar(7,sapin);
  lcd.createChar(8,maison);
  largRoute = 8; // Road width. / Largeur de la route.
  limitDroite = 6;
  initial();
  randomSeed(analogRead(A2));
}
void initial() {
  // Splash screen before the game starts.
  //----------------------------------------
  // Ecran d'accueil avant le début du jeu.
  long i=0;
  int k=btnNone;
  getTime=millis();
  // Loop until the player pushes a button
  //---------------------------------------
  // Tourne en boucle jusqu'à ce que le joueur appuie sur un bouton.
  while (k==btnNone) { 
    k=detect();
    i=i+1;
    if ((millis()-getTime)>1000) {
      if (i%2==0) {
        lcd.clear();
        lcd.setCursor(5,0);
        lcd.print("Vroum!");
        for (i=0;i<8;i++) {
          lcd.setCursor(i*2,1);
          lcd.write(4-(i%3));
        }
      }
      else {
        lcd.clear();  
        lcd.print("Press a key ... ");
      } 
      getTime=millis();
    }
  }
  // Initialize various variables when the player has pushed a button to start the game.
  //-----------------------------------------------------------------------------------
  // Initialise divers variables lorsque le joueur a appuyé sur un bouton pour commencer le jeu.
  for (i=0;i<16;i++) {
    haut[i]=' ';
  }
  getTime = millis();
  getTimeVoiture = getTime;
  initTime = getTime;
  posRoute=5;
  posVoiture = posRoute+3;
  posConc1=0;
  obsConc=0;
  compteurConc=7;
}
//\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
// This is the main loop. It controls the input from the buttons, 
// the animation of the road and the car, and the score if the car
// crashes out of the road.
//------------------------------------------------------------------
// C'est la boucle principale. Elle contrôle les boutons, 
// l'animation de la route et de la voiture, ainsi que le score si
// la voiture sort de la route.
void loop() {
  int i=0;
  int tmp;
  // Uses the "detect()" function to read the state of the buttons.
  // (This was made for an ad-hoc pad of 10 buttons. Only two simple
  // push buttons are required, but this code needs to be altered slightly).
  //-----------------------------------------------------------------------
  // Utilise la fonction "detect()" pour lire l'état des boutons.
  // (Cela a été créé pour un pad ad-hoc e 10 boutons. Seuls 2 boutons poussoirs
  // sont nécessaires en réalité, mais cette partie de code doit être modifiée légèrement).
  bouton = detect();
  if (bouton==btnLeft) tmp=0; // On bouge à gauche...
  else if (bouton==btnRight) tmp=2; // ...ou à droite...
  else tmp=1; // Ou on reste tout droit.
  moveVoiture=tmp;
  // The "Voit_animation()" function runs every 500 ms (car and road animation).
  // To increase the speed, replace 500 by a lower value.
  //---------------------------------------------------
  // La fonction "Voit_animation()" est lancée toutes les 500 ms (animation de la voiture et de la route).
  // Pour une plus grande vitesse, remplcer 500 par une valeur plus faible.
  if ((millis()-getTime)>500) { 
    Voit_animation();
    getTime=millis();
  }
  if (collision!=1) {
    if ((millis()-getTimeVoiture)>125) {
      Voit_animationVoiture();
      getTimeVoiture=millis();
    }
  }
  else Voit_score();
}
// Enof main loop / Fin de la boucle principale
//\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\_
// This function is here to convert the analog values read from the ad-hoc
// pad of 10 buttons into integers between 0 and 9. For the purpose of this game
// it can be completely replaced with 2 simple push buttons and 2 simple digitalRead().
// The code in the main loop must be changed accordingly.
//------------------------------------------------------------------------
// Cette fonction sert à convertir les valeurs analogiques lues depuis le pad ad-hoc de 10 boutons 
// en entiers entre 0 et 9. Pour ce jeu cela peut être entièrement remplacé par
// 2 boutons poussoirs et deux simples digitalRead().
// Le code dans la boucle principale doit être changé en conséquence.
/*int detect(int bouton1,int moitie) {
  int k;
  if (moitie==0) k=0;
  else if (moitie==1) k=1;
  if (bouton1>1000) return 1+k*5;
  else if (bouton1<530 && bouton1>500) return 2+k*5;
  else if (bouton1<370 && bouton1>310) return 3+k*5;
  else if (bouton1<290 && bouton1>240) return 4+k*5;
  else if (bouton1<230 && bouton1>180) {
    if (k==1) return 0;
    else return 5;
  }
  else return 10;
}*/
/* New detect() function reading the buttons
 * from the Arduino LCD Keypad shield.
 */
int detect() {
  int b = analogRead(A0);
  if (b > 1000) return btnNone;
  delay(8);
  if (b < 50) return btnRight;
  if (b < 180) return btnUp;
  if (b < 330) return btnDown;
  if (b < 520) return btnLeft;
  if (b < 700) return btnSelect;
}
// This function prints some kind of a "game over" splash screen. 
// It displays the time the player managed to drive the car without
// bumping into another one or crashing out of the road. After a few seconds it
// calls the "initial()" function to display the firt splash screen again.
//----------------------------------------------------------------------------
// Cette fontion affiche une sorte de "game over". Elle affiche le temps pendant lequel le joueur est
// parvenu à conduire sa voiture sans rentrer dans une autre ni sortir de la route.
// Après quelques secondes la fonction "initial()" est appelée pour afficher de nouveau le
// premier écran d'accueil.
void Voit_score() {
  int temps;
  temps = (millis() - initTime)/1000;
  String chaine = "";
  chaine = chaine + temps + " sec";
  delay(800);
  lcd.clear();
  lcd.print("Game Over");
  lcd.setCursor(0,1);
  lcd.print(chaine);
  /*chaine = ",";
  chaine = chaine;
  lcd.setCursor(6,1);
  lcd.print(chaine);*/
  delay(3000);
  collision=0;
  initial();
}
//=======================================================================================================================
// Actual animation (including random generation of obstacles and scenery).
//----------------------------------------------------------------
// Animation à proprement parler (dont génération aléatoire des obstacles et du décor).
void Voit_animation() {
  int i;
  int increment=0;
  int rand;
  int typeConc;
  int oldDir;
  int posDec1=1000;
  int posDec2=1000;
  int posDec3=1000;
  int posDec4=1000;
  int typeDec1;
  int typeDec2;
  int typeDec3;
  int typeDec4;
  oldDir = actuDir;
  oldposRoute = posRoute;
  // Generation of random scenery
  //-----------------------------------
  // Génération aléatoire du décor
  if (random(0,100)>60) { // Décor 1
    typeDec1=random(0,4);
    if (random(0,2)>0) {
      posDec1=posRoute-random(2,5);
    }
    else posDec1=posRoute+largRoute+random(0,4)+2;
  }
  else posDec1=1000;
  if (random(0,100)>50) { // Décor 2
    typeDec2=random(0,4);
    if (random(0,2)>0) {
      posDec2=posRoute-random(2,5);
    }
    else posDec2=posRoute+largRoute+random(0,4)+2;
  }
  else posDec2=1000;
  if (random(0,100)>50) { // Décor 3
    typeDec3=random(0,4);
    if (random(0,2)>0) {
      posDec3=posRoute-random(2,5);
    }
    else posDec3=posRoute+largRoute+random(0,4)+2;
  }
  else posDec3=1000;  
  if (random(0,100)>80) { // Décor 4
    typeDec4=random(0,4);
    if (random(0,2)>0) {
      posDec4=posRoute-random(2,5);
    }
    else posDec4=posRoute+largRoute+random(0,4)+2;
  }
  else posDec4=1000;
  // Random generation of new road direction (depending on constraints)
  //-----------------------------------------
  // Génération aléatoire de la direction de la route (en fonction des contraintes)
  if (posRoute==0) rand = random(1,3); // only staight or to the right / seulement devant ou à droite
  else if (posRoute==limitDroite) rand = random(0,2); // Only straight or to the left / seulement devant ou à gauche
  else rand = random(0,3); // no constraints / pas de contraintes
  // Moves the road sideways when necessary
  //--------------------------------
  // Décale la route wuand c'est nécessaire
  if (oldDir==0) {
    if (rand!=2 && posRoute!=0) posRoute = posRoute-1;
  }
  else if (oldDir==2) {
    if (rand!=0 && posRoute!=limitDroite) posRoute = posRoute+1;
  }
  actuDir = rand;
  // Copy top line to bottom line.
  //----------------------------
  // Copie la ligne du haut en bas.
  lcd.clear();
  lcd.setCursor(0,1);
  for (i=0;i<16;i++) { 
    lcd.setCursor(i,1);
    if (haut[i]=='*') lcd.write(1);
    else if (haut[i]=='V') lcd.write(5);
    else if (haut[i]=='A') lcd.write(6);
    else if (haut[i]=='S') lcd.write(7);
    else if (haut[i]=='M') lcd.write(8);
    else lcd.write(haut[i]);
  }
  // Create and print the top line based on the direction and the position of the road.
  //-------------------------------------------------------------------------------
  // Ecriture de la ligne du haut, générée en fonction de la direction et de la position de la route.
  for (i=0;i<16;i++) { // (we write the top line) / (On écrit en haut)
    lcd.setCursor(i,0);
    if (i==posRoute || i==posRoute+largRoute) { // If left or right border of the road / Si limite gauche ou droite de la route.
      if (actuDir==0) {
        lcd.write(1); // We use a custom character for the backlash when turning left / On met un '' si tourne à gauche 
        haut[i]='*';
      }
      else if (actuDir==1) {
        lcd.write('|');
        haut[i]='|';
      }
      else if (actuDir==2) {
        lcd.write('/');
        haut[i]='/';
      }
    }
    else if (i==posDec1) {
      lcd.write(typeDec1+5);
      if (typeDec1==0) haut[i]='V';
      else if (typeDec1==1) haut[i]='A';
      else if (typeDec1==2) haut[i]='S';
      else if (typeDec1==3) haut[i]='M';
    }
    else if (i==posDec2) {
      lcd.write(typeDec2+5);
      if (typeDec2==0) haut[i]='V';
      else if (typeDec2==1) haut[i]='A';
      else if (typeDec2==2) haut[i]='S';
      else if (typeDec2==3) haut[i]='M';
    }
    else if (i==posDec3) {
      lcd.write(typeDec3+5);
      if (typeDec3==0) haut[i]='V';
      else if (typeDec3==1) haut[i]='A';
      else if (typeDec3==2) haut[i]='S';
      else if (typeDec3==3) haut[i]='M';
    }
    else if (i==posDec4) {
      lcd.write(typeDec4+5);
      if (typeDec4==0) haut[i]='V';
      else if (typeDec4==1) haut[i]='A';
      else if (typeDec4==2) haut[i]='S';
      else if (typeDec4==3) haut[i]='M';
    }
    else { // Nothing special to write / Rien à écrire de spécial.
      lcd.write(' ');
      haut[i]=' ';
    }
  }
  // Other cars
  //-------------------------
  // Autres voitures
  if (compteurConc>6) { // Create new car when the last one has disapeared / Si voiture précédente disparue, on en cré une nouvelle
    obsConc=1000;
    if (random(0,100)>70) {
      posConc1 = random(1,largRoute); // Random position on top line / Position aléatoire sur la ligne du haut  
      compteurConc = 0;  
    }
  }
  else { // If the car is still there, update screen / Si voiture toujours là, on met à jour l'écriture sur l'écran
    if (compteurConc<3) { // The car stays a little in the top line... / elle reste un peu en haut...
      lcd.setCursor(posConc1 + posRoute,0);
      // We adapt the direction of the car to that of the road / On adapte la direction de la voiture à celle de la route.
      if (actuDir==0) typeConc=3;
      else if (actuDir==1) typeConc = 2;
      else typeConc = 4;
      lcd.write(typeConc);  
      compteurConc = compteurConc + 1;  
    }
    else { // ... then it moves to the bottom line / ... puis elle passe en bas.
      lcd.setCursor(posConc1 + oldposRoute,1);
      obsConc = posConc1 + oldposRoute;
      // We adapt the direction of the car to that of the road / On adapte la direction de la voiture à celle de la route.
      if (oldDir==0) typeConc=3;
      else if (oldDir==1) typeConc = 2;
      else typeConc = 4;
      lcd.write(typeConc);
      compteurConc = compteurConc + 1;       
    }
  }
}
// This function controls the animation of the car itself.
//------------------------------------------------------------
// Cette fonction contrôle l'animation de la voiture.
void Voit_animationVoiture() {
  int typeVoiture;  
  lcd.setCursor(posVoiture,1);
  lcd.print(' ');
  // Computes new position and direction of the car.
  //--------------------------------------------------
  // Calcule les nouvelles position et direction de la voiture.
  if (moveVoiture==0) {
    if (posVoiture!=0) {
      posVoiture = posVoiture - 1; // Move the car to the left / On décale la voiture vers la gauche
      typeVoiture = 3; // Custom character for the car turning to the left / Caractère de la voiture tournant vers la gauche.
    }
    else typeVoiture = 2;
  }
  else if (moveVoiture==2) {
    if (posVoiture!=15) {
      posVoiture = posVoiture + 1; // Move the car to the right / On décale la voiture vers la droite
      typeVoiture = 4;  // Custom character for the car turning to the right / Caractère de la voiture tournant vers la droite.
    }
    else typeVoiture = 2;
  }
  else {
    typeVoiture = 2;  // Custom character for the car moving straight ahead / Caractère de la voiture allant droit devant.
  }
  // If no collision, print the car on the screen...
  //------------------------------------------
  // Si pas de collision, on affiche la voiture sur l'écran...
  if (posVoiture!=posRoute && posVoiture!=(posRoute + largRoute) && posVoiture!=obsConc) { 
    lcd.setCursor(posVoiture,1);
    lcd.write(typeVoiture);
  }
  else {  // ... else : boom! / ... sinon : boom!
    lcd.setCursor(posVoiture-2,1);
    lcd.print("Boom!");
    collision = 1;
  }  
}
 

Arduino Juego de Pacman Rider y Display LCD

Autor: Micky Griffiths

Ya hemos hablado del clásico Pacman antes, en esta ocasión, se trata de una alternativa de Pacman, juego en el cual, tendremos que ir comiendo los items conforme vamos avanzando, mientras esquivamos a los enemigos que nos encontremos.

https://youtu.be/DU2Hl4GvLDU

Sketch para crear un Pacman Rider en Arduino

/*                                                                
 *
 *      ____  ___   ________  ______    _   __   ____  ________  __________  __
 *     / __ /   | / ____/  |/  /   |  / | / /  / __ /  _/ __ / ____/ __ / /
 *    / /_/ / /| |/ /   / /|_/ / /| | /  |/ /  / /_/ // // / / / __/ / /_/ / / 
 *   / ____/ ___ / /___/ /  / / ___ |/ /|  /  / _, _// // /_/ / /___/ _, _/_/  
 *  /_/   /_/  |_____/_/  /_/_/  |_/_/ |_/  /_/ |_/___/_____/_____/_/ |_(_)   
 *
 * Written by Micky Griffiths for the 16x2 LCD and RPi. -- 16 May 2013
 * Ported to Arduino for the Arduino LCD Keypad Shield by Johannes le Roux (@dadecoza) -- 4 Apr 2018
 * 
 * 
*/
#include <LiquidCrystal.h>
#include <EEPROM.h>
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);
static const int SPRITE_PACMAN_OPEN = 0;
static const int SPRITE_PACMAN_CLOSED = 1;
static const int SPRITE_GHOST = 2;
static const int SPRITE_HEART = 3;
static const int SPRITE_SMILEY = 4;
static const int TYPE_NONE = 0;
static const int TYPE_HEART = 1;
static const int TYPE_GHOST = 2;
static const int MAX_SPRITES = 10;
static const int STATE_INTRO = 0;
static const int STATE_PLAY = 1;
static const int STATE_GAMEOVER = 2;
static const int HIGHSCORE_ADDRESS = 0;
static const int TOP = 0;
static const int BOTTOM = 1;
struct sprite {
  int x;
  int y;
  int type;
};
struct sprite sprites[MAX_SPRITES];
long timeToMove, timeToAnimate, timeToDebounce;
int state, score, highScore, gameSpeed, pacmanX, pacmanY, ghostOdds;
boolean mouthState, smile;
static const byte spriteBitmaps[8][8] = {
  {0x7, 0xF, 0x1E, 0x1C, 0x1C, 0x1E, 0xF, 0x7},
  {0x0, 0xF, 0x1F, 0x1F, 0x1E, 0x1F, 0xF, 0x0},
  {0x19, 0x1F, 0x15, 0x1F, 0x11, 0x1F, 0x1D, 0xC},
  {0x0, 0x0, 0x0, 0xA, 0x15, 0x11, 0xA, 0x4},
  {0x0, 0xA, 0x0, 0x0, 0x11, 0xE, 0x0, 0x0},
  {0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x0, 0x0, 0x0},
  {0x0, 0x0, 0x0, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F},
  {0x1F, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}
};
void setup() {
  if (checkButton()) {
    EEPROM.write(HIGHSCORE_ADDRESS, 0);
  }
  for (int i = 0; i < (sizeof(spriteBitmaps) / 8); i++) {
    lcd.createChar(i, spriteBitmaps[i]);
  }
  delay(500);
  lcd.begin(16, 2);
  randomSeed(analogRead(1));
  state = STATE_INTRO;
  initVars();
}
void loop() {
  switch (state) {
    case STATE_INTRO: intro(); break;
    case STATE_PLAY: play(); break;
    case STATE_GAMEOVER: gameover(); break;
  }
}
void initVars() {
  for (int i = 0; i < MAX_SPRITES; i++) {
    sprites[i] = {0, 0, TYPE_NONE};
  }
  highScore = EEPROM.read(HIGHSCORE_ADDRESS);
  timeToMove = 0;
  timeToAnimate = 0;
  timeToDebounce = 0;
  score = 0;
  gameSpeed = 600;
  ghostOdds = 6;
  pacmanX = 1;
  pacmanY = 0;
  mouthState = false;
  smile = false;
}
void intro() {
  lcd.clear(); lcd.setCursor(3, 0); lcd.print("WELCOME TO"); lcd.setCursor(1, 1); lcd.print("MICKY'S ARCADE");
  waitButton();
  lcd.clear(); lcd.setCursor(2, 0); lcd.print("IT'S SIMPLE!");
  waitButton();
  lcd.clear(); lcd.setCursor(0, 0); lcd.print("Press the button"); lcd.setCursor(1, 1); lcd.print("to move pacman");
  waitButton();
  lcd.clear(); lcd.setCursor(6, 0); lcd.print("BUT!");
  lcd.setCursor(0, 1); lcd.print("Don't forget to");
  waitButton();
  lcd.clear(); lcd.setCursor(3, 0); lcd.print("COLLECT: "); lcd.write(byte(SPRITE_HEART)); lcd.setCursor(4, 1); lcd.print("AVOID: "); lcd.write(byte(SPRITE_GHOST));
  waitButton(); lcd.clear(); lcd.setCursor(1, 0); lcd.print("ARE YOU READY?"); lcd.setCursor(0, 1); lcd.print("Press the button");
  waitButton();
  animation(1);
  state = STATE_PLAY;
}
void play() {
  drawScreen();
  long now = millis();
  if (checkButton()) {
    hidePacman();
    pacmanY = !pacmanY;
  }
  if (now > timeToMove) {
    moveLeft();
    if (!random(0, ghostOdds)) {
      spawn(TYPE_GHOST);
    }
    if (!random(0, 3)) {
      spawn(TYPE_HEART);
    }
    timeToMove = now + gameSpeed;
  }
  int c = collision();
  if (c == TYPE_HEART) {
    eatHeart();
    increaseScore();
  } else if (c == TYPE_GHOST) {
    state = STATE_GAMEOVER;
  }
}
void gameover() {
  animation(0);
  lcd.setCursor(3, 0); lcd.print("GAME OVER"); lcd.setCursor(0, 1); lcd.print("How did you do?");
  waitButton();
  lcd.clear(); lcd.setCursor(2, 0); lcd.print("YOUR SCORE:"); lcd.setCursor(7, 1); lcd.print(score);
  waitButton();
  if (score > highScore) {
    lcd.clear(); lcd.setCursor(1, 0); lcd.print("NEW HIGHSCORE!");
    EEPROM.write(HIGHSCORE_ADDRESS, score);
    highScore = score;
    waitButton();
  }
  lcd.clear(); lcd.setCursor(3, 0); lcd.print("TRY AGAIN"); lcd.setCursor(2, 1); lcd.print("Highscore: "); lcd.print(highScore);
  waitButton();
  initVars();
  state = STATE_PLAY;
  lcd.clear();
}
void drawScreen() {
  for (int i = 0; i < MAX_SPRITES; i++) {
    drawSprite(i);
  }
  drawPacman();
}
void eatHeart() {
  for (int i = 0; i < MAX_SPRITES; i++) {
    if (sprites[i].x == pacmanX && sprites[i].y == pacmanY && sprites[i].type == TYPE_HEART) {
      smile = true;
      deleteSprite(i);
      return;
    }
  }
}
void increaseScore() {
  score++;
  if (!(score % 10)) {
    gameSpeed = gameSpeed - 30;
    if (ghostOdds > 1) {
      ghostOdds--;
    }
  }
}
void spawn(int type) {
  int x = 15;
  int y = random(0, 2);
  for (int i = 0; i < MAX_SPRITES; i++) {
    int spriteType = sprites[i].type;
    if (spriteType == TYPE_NONE) {
      if (((type == TYPE_GHOST) && okayToSpawnGhost(y)) || ((type == TYPE_HEART) && okayToSpawnHeart(y))) {
        createSprite(i, x, y, type);
      }
      return;
    }
  }
}
int at(int x, int y) {
  for (int i = 0; i < MAX_SPRITES; i++) {
    if (sprites[i].x == x && sprites[i].y == y && sprites[i].type != TYPE_NONE) {
      return sprites[i].type;
    }
  }
  return TYPE_NONE;
}
int okayToSpawnGhost(int pos) {
  if (at(15, pos) != TYPE_NONE) {
    return 0;
  } else if (at(15, !pos) == TYPE_GHOST) {
    return 0;
  } else if ((pos == TOP) && (at(14, BOTTOM) == TYPE_GHOST)) {
    return 0;
  } else if ((pos == BOTTOM) && (at(14, TOP) == TYPE_GHOST)) {
    return 0;
  }
  return 1;
}
int okayToSpawnHeart(int pos) {
  if (at(15, pos) != TYPE_NONE) {
    return 0;
  }
  return 1;
}
void moveLeft() {
  for (int i = 0; i < MAX_SPRITES; i++) {
    if (sprites[i].type != TYPE_NONE) {
      int x = sprites[i].x - 1;
      int y = sprites[i].y;
      moveSprite(i, x, y);
    }
  }
}
void createSprite(int s, int x, int y, int type) {
  sprites[s].x = x;
  sprites[s].y = y;
  sprites[s].type = type;
}
void drawSprite(int s) {
  int spriteType = sprites[s].type;
  if (spriteType == TYPE_NONE) {
    return;
  }
  int x = sprites[s].x;
  int y = sprites[s].y;
  if (!((x == pacmanX) && (y == pacmanY))) {
    lcd.setCursor(x, y);
    switch (spriteType) {
      case TYPE_GHOST: lcd.write(byte(SPRITE_GHOST)); break;
      case TYPE_HEART: lcd.write(byte(SPRITE_HEART)); break;
      default: lcd.write(byte(32)); break;
    }
  }
}
void hideSprite(int s) {
  lcd.setCursor(sprites[s].x, sprites[s].y);
  lcd.write(byte(32));
}
void drawPacman() {
  if ((millis() > timeToAnimate) || (smile)) {
    int wait = 350;
    lcd.setCursor(pacmanX, pacmanY);
    if (smile) {
      lcd.write(byte(SPRITE_SMILEY));
      wait = 600;
      smile = false;
    } else if (mouthState) {
      lcd.write(byte(SPRITE_PACMAN_OPEN));
    } else {
      lcd.write(byte(SPRITE_PACMAN_CLOSED));
    }
    mouthState = !mouthState;
    timeToAnimate = millis() + wait;
  }
}
void hidePacman() {
  lcd.setCursor(pacmanX, pacmanY);
  lcd.write(byte(32));
}
void deleteSprite(int s) {
  hideSprite(s);
  sprites[s].x = 0;
  sprites[s].y = 0;
  sprites[s].type = TYPE_NONE;
}
void moveSprite(int s, int x, int y) {
  if ((x < 0) || (x > 15)) {
    deleteSprite(s);
  } else {
    hideSprite(s);
    sprites[s].x = x;
    sprites[s].y = y;
  }
}
int collision() {
  return at(pacmanX, pacmanY);
}
void animation(int direction) {
  byte animationOpen[6] = {255, 255, 5, 6, 7, 95};
  byte animationClose[6] = {7, 95, 5, 6, 255, 255};
  byte animationChars[6];
  if (direction) {
    memcpy(animationChars, animationOpen, sizeof animationChars);
  } else {
    memcpy(animationChars, animationClose, sizeof animationChars);
  }
  lcd.clear();
  for (int frame = 0; frame < 3; frame++) {
    lcd.setCursor(0, 0);
    for (int i = 0; i < 16; i++) lcd.write(animationChars[frame * 2]);
    lcd.setCursor(0, 1);
    for (int i = 0; i < 16; i++) lcd.write(animationChars[(frame * 2) + 1]);
    delay(300);
    lcd.clear();
  }
}
int checkButton() {
  long now = millis();
  if (now > timeToDebounce) {
    int b = analogRead(A0);
    if (b < 850) {
      timeToDebounce = now + 300;
      return 1;
    }
  }
  return 0;
}
void waitButton() {
  while (!checkButton()) delay(50);
}
 

Arduino Juego Truck Lane y Display LCD

Autor: @TheRealDod

Este último proyecto, es un runner infinito, en el cual controlas un pequeño camión, que debe esquivar otros camiones para lograr la mayor puntuación posible.

https://youtu.be/mBW-0CmpGbA

Sketch del juego con Arduino Truck Lane

/* Simple Car game for a 16x2 LCD display
   You can use any Hitachi HD44780 compatible LCD.
   Wiring explained at http://www.arduino.cc/en/Tutorial/LiquidCrystal
   (I used theLCD Electronic Brick on bus 1:
     rs on pin 2, rw on pin 3, enable on pin 4,
     data on pins 5,6,7,8)
   There's also a "steering wheel" potentiometer on analog input 1,
   and a Piezo speaker on pin 9 (PWM).
   Enjoy,
   @TheRealDod, Nov 25, 2010
*/
#include <LiquidCrystal.h>
// LiquidCrystal display
// You can use any Hitachi HD44780 compatible. Wiring explained at
// http://www.arduino.cc/en/Tutorial/LiquidCrystal
// LiquidCrystal lcd(2, 3, 4, 5, 6, 7, 8);
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);
#define btnRight     0
#define btnUp        1
#define btnDown      2
#define btnLeft      3
#define btnSelect    4
#define btnNone      5
// Steering wheel potentiometer
// const int POTPIN = 1;
// const int MAXPOT = 800; // no need to turn the wheel all the way to 1023 :)
// Piezo speaker
const int SPEAKERPIN = 3;
const int RANDSEEDPIN = 0; // an analog pin that isn't connected to anything
const int MAXSTEPDURATION = 300; // Start slowly, each step is 1 millisec shorter.
const int MINSTEPDURATION = 150; // This is as fast as it gets
const int NGLYPHS = 6;
// the glyphs will be defined starting from 1 (not 0),
// to enable lcd.print() of null-terminated strings
byte glyphs[NGLYPHS][8] = {
  // 1: car up
  { B00000,
    B01110,
    B11111,
    B01010,
    B00000,
    B00000,
    B00000,
    B00000
  }
  // 2: car down
  , {
    B00000,
    B00000,
    B00000,
    B00000,
    B01110,
    B11111,
    B01010,
    B00000
  }
  // 3: truck up
  , {
    B00000,
    B11110,
    B11111,
    B01010,
    B00000,
    B00000,
    B00000,
    B00000
  }
  // 4: truck down
  , {
    B00000,
    B00000,
    B00000,
    B00000,
    B11110,
    B11111,
    B01010,
    B00000
  }
  // 5: crash up
  , {
    B10101,
    B01110,
    B01110,
    B10101,
    B00000,
    B00000,
    B00000,
    B00000
  }
  // 6: crash down
  , {
    B00000,
    B00000,
    B00000,
    B10101,
    B01110,
    B01110,
    B10101,
    B00000
  }
};
const int NCARPOSITIONS = 4;
// Each position is mapped to a column of 2 glyphs
// Used to make sense when I had a 5th position
// where car or crash was drawn as 2 glyphs
// (can't do that since 0 terminates strings),
// so it's kinda silly now, but it ain't broke :)
const char BLANK = 32;
char car2glyphs[NCARPOSITIONS][2] = {
  {1, BLANK}, {2, BLANK}, {BLANK, 1}, {BLANK, 2}
};
char truck2glyphs[NCARPOSITIONS][2] = {
  {3, BLANK}, {4, BLANK}, {BLANK, 3}, {BLANK, 4}
};
char crash2glyphs[NCARPOSITIONS][2] = {
  {5, BLANK}, {6, BLANK}, {BLANK, 5}, {BLANK, 6}
};
const int ROADLEN = 15; // LCD width (not counting our car)
int road[ROADLEN]; // positions of other cars
char line_buff[2 + ROADLEN]; // aux string for drawRoad()
int road_index;
int car_pos;
int old_key = btnNone;
// Off-the-grid position means empty column, so MAXROADPOS
// determines the probability of a car in a column
// e.g. 3*NCARPOSITIONS gives p=1/3
const int MAXROADPOS = 3 * NCARPOSITIONS;
int step_duration;
int crash; // true if crashed
unsigned int crashtime; // millis() when crashed
const int CRASHSOUNDDURATION = 250;
const char *INTRO1 = "Trucks ahead,";
const char *INTRO2 = "Drive carefully";
const int INTRODELAY = 2000;
void setup()
{
  crash = crashtime = road_index = 0;
  step_duration = MAXSTEPDURATION;
  line_buff[1 + ROADLEN] = ''; // null terminate it
  randomSeed(analogRead(RANDSEEDPIN));
  for (int i = 0; i < NGLYPHS; i++) {
    lcd.createChar(i + 1, glyphs[i]);
  }
  for (int i = 0; i < ROADLEN; i++) {
    road[i] = -1;
  }
  pinMode(SPEAKERPIN, OUTPUT);
  analogWrite(SPEAKERPIN, 0); // to be on the safe side
  lcd.begin(16, 2);
  getSteeringWheel();
  drawRoad();
  lcd.setCursor(1, 0);
  lcd.print(INTRO1);
  lcd.setCursor(1, 1);
  lcd.print(INTRO2);
  delay(INTRODELAY);
}
void loop() {
  unsigned long now = millis() - INTRODELAY;
  if (!crash) {
    //getSteeringWheel();
    crash = (car_pos == road[road_index]);
  }
  if (crash) {
    if (!crashtime) {
      crashtime = now;
      drawRoad();
      // Game over text
      // (keep first 2 "crash" columns intact)
      lcd.setCursor(2, 0);
      lcd.print("Crashed after");
      lcd.setCursor(2, 1);
      lcd.print(now / 1000);
      lcd.print(" seconds.");
    }
    if ((now - crashtime) < CRASHSOUNDDURATION) {
      analogWrite(SPEAKERPIN, random(255)); // white noise
    }
    else {
      analogWrite(SPEAKERPIN, 0); // dramatic post-crush silence :)
    }
    delay(10); // Wait a bit between writes
  }
  else {
    int prev_pos = road[(road_index - 1) % ROADLEN];
    int this_pos = random(MAXROADPOS);
    while (abs(this_pos - prev_pos) < 2) { // don't jam the road
      this_pos = random(MAXROADPOS);
    }
    road[road_index] = this_pos;
    road_index = (road_index + 1) % ROADLEN;
    drawRoad();
    //delay(step_duration);
    unsigned long wait = millis() + step_duration;
    while (millis() < wait) getSteeringWheel();
    if (step_duration > MINSTEPDURATION) {
      step_duration--; // go faster
    }
  }
}
void getSteeringWheel() {
  //car_pos = map(analogRead(POTPIN),0,1024,0,NCARPOSITIONS);
  int k = getKey();
  if (k != old_key) {
    switch (k) {
      case btnDown:
        if (car_pos < NCARPOSITIONS - 1) car_pos++;
        break;
      case btnUp:
        if (car_pos > 0) car_pos--;
        break;
    }
    old_key = k;
  }
}
void drawRoad() {
  for (int i = 0; i < 2; i++) {
    if (crash) {
      line_buff[0] = crash2glyphs[car_pos][i];
    }
    else {
      line_buff[0] = car2glyphs[car_pos][i];
    }
    for (int j = 0; j < ROADLEN; j++) {
      int pos = road[(j + road_index) % ROADLEN];
      line_buff[j + 1] = pos >= 0 && pos < NCARPOSITIONS ? truck2glyphs[pos][i] : BLANK;
    }
    lcd.setCursor(0, i);
    lcd.print(line_buff);
  }
}
int getKey() {
  int b = analogRead(A0);
  if (b > 1000) return btnNone;
  delay(8);
  if (b < 50) return btnRight;
  if (b < 180) return btnUp;
  if (b < 330) return btnDown;
  if (b < 520) return btnLeft;
  if (b < 700) return btnSelect;
}