Frogolina
Published © MIT

Tetris on a LED board - controlled with uMyo EMG sensor

The idea of the project is to create Tetris on a LED field and control the game using the EMG muscle signal

IntermediateShowcase (no instructions)16 hours349
Tetris on a LED board - controlled with uMyo EMG sensor

Things used in this project

Hardware components

Addressable RGB ws2812 60 led/m black
×1
ESP32
×1
uMyo wearable EMG sensor with wet/dry electrodes
×1

Story

Read more

Code

Untitled file

C/C++
#include "tetris_eng.h"

sTet cur_tet;

int tet_rotate(sTet *tet, sTField *field, uint8_t cw_rot)
{
  uint8_t nrot;
  if(cw_rot) nrot = (tet->rot + 1)%4;
  else
  {
    if(tet->rot == 0) nrot = 3;
    else nrot = tet->rot - 1;
  }
  int nx[4], ny[4];
  if(tet->type == tet_sq)
  {
    for(int n = 0; n < 4; n++) nx[n] = n%2, ny[n] = n/2;
  }
  if(tet->type == tet_line)
  {
    if(nrot == 0 || nrot == 2) for(int n = 0; n < 4; n++) nx[n] = -1 + n, ny[n] = 0;
    if(nrot == 1 || nrot == 3) for(int n = 0; n < 4; n++) nx[n] = 0, ny[n] = -1 + n;
  }
  if(tet->type == tet_L || tet->type == tet_L2 || tet->type == tet_T)
  {
    if(nrot == 0 || nrot == 2)
    {
      for(int n = 0; n < 3; n++) nx[n] = -1 + n, ny[n] = 0;
      if(tet->type == tet_L) nx[3] = 1 - nrot, ny[3] = 1 - nrot;
      if(tet->type == tet_L2) nx[3] = nrot - 1, ny[3] = 1 - nrot;
      if(tet->type == tet_T) nx[3] = 0, ny[3] = 1 - nrot;
    }
    if(nrot == 1 || nrot == 3)
    {
      for(int n = 0; n < 3; n++) nx[n] = 0, ny[n] = -1 + n;
      if(tet->type == tet_L2) ny[3] = 2 - nrot, nx[3] = 2 - nrot;
      if(tet->type == tet_L) ny[3] = nrot - 2, nx[3] = 2 - nrot;
      if(tet->type == tet_T) ny[3] = 0, nx[3] = 2 - nrot;
    }
  }
  if(tet->type == tet_z || tet->type == tet_z2)
  {
    if(nrot == 0 || nrot == 2)
    {
      nx[0] = 0, ny[0] = 0;
      nx[1] = 1, ny[1] = 0;
      nx[2] = 1, nx[3] = 0;
      if(tet->type == tet_z) ny[2] = 1, ny[3] = -1;
      else ny[2] = -1, ny[3] = 1;
    }
    if(nrot == 1 || nrot == 3)
    {
      nx[0] = 0, ny[0] = 0;
      nx[1] = 0, ny[1] = 1;
      ny[2] = 1, ny[3] = 0;
      if(tet->type == tet_z) nx[2] = -1, nx[3] = 1;
      else nx[2] = 1, nx[3] = -1;
    }
  }
  int done = 0;
  int dx_try = 0;
  int dy_try = 0;
  int attempt = 0;
  int place_ok = 0;
  while(1)
  {
    if(attempt == 1) dx_try = 1;
    if(attempt == 2) dx_try = -1;
    if(attempt == 3) dx_try = 2;
    if(attempt == 4) dx_try = -2;
    if(attempt > 4)
    {
      break;
//      if(dy_try == 0) dy_try = 1, dx_try = 0, attempt = 0;
//      else break;
    }
    attempt++;
    place_ok = 1;
    for(int n = 0; n < 4; n++)
    {
      int bx = tet->cx + dx_try;
      int by = tet->cy + dy_try;
      bx += nx[n];
      by += ny[n];
      if(by >= field->SY) continue; //higher than top, no problem
      if(bx < 0 || bx >= field->SX || by < 0)
      {
        place_ok = 0;
        break;
      }
      if(field->field[by * field->SX + bx])
      {
        place_ok = 0;
        break;
      }
    }
    if(place_ok) break;
  }
  if(!place_ok) return 0;
  tet->rot = nrot;
  tet->cx += dx_try;
  tet->cy += dy_try;
  for(int n = 0; n < 4; n++) tet->bx[n] = nx[n], tet->by[n] = ny[n];
  return 1;
}

int tet_move(sTet *tet, sTField *field, int dir)
{
  int dx, dy;
  if(dir == move_down) dx = 0, dy = -1;
  else if(dir == move_left) dx = -1, dy = 0;
  else if(dir == move_right) dx = 1, dy = 0;
  else if(dir == move_up) dx = 0, dy = 1;
  else return 0; //unknown move -> failure

  int place_ok = 1;
  for(int n = 0; n < 4; n++)
  {
    int bx = tet->cx + tet->bx[n] + dx;
    int by = tet->cy + tet->by[n] + dy;
    if(by >= field->SY)
    {
      if(bx < 0 || bx >= field->SX)
      {
        place_ok = 0;
        break;
      }
      continue; //higher than top, no problem
    }
    if(bx < 0 || bx >= field->SX || by < 0)
    {
      place_ok = 0;
      break;
    }
    if(field->field[by * field->SX + bx])
    {
      place_ok = 0;
      break;
    }
  }
  if(place_ok)
  {
    tet->cx += dx;
    tet->cy += dy;
    return 1;
  }
  return 0;
}

int tet_place(sTet *tet, sTField *field)
{
  for(int n = 0; n < 4; n++)
  {
    int bx = tet->cx + tet->bx[n];
    int by = tet->cy + tet->by[n];
    if(by >= field->SY) continue; //higher than top - nothing to do
    if(bx < 0 || bx >= field->SX || by < 0) return 0; //error, shouldn't happen, random behavior is ok
    field->field[by * field->SX + bx] = tet->type;
  }
  return 1;
}

uint32_t tet_check_field(sTField *field)
{
  uint32_t res = 0;
  for(int y = 0; y < field->SY; y++)
  {
    int full_line = 1;
    for(int x = 0; x < field->SX; x++)
      if(!field->field[y * field->SX + x]) {full_line = 0; break;}
    if(full_line) res |= 1<<y;
  }
  return res;
}

void tet_remove_full(sTField *field)
{
  for(int y = 0; y < field->SY; y++)
  {
    int full_line = 1;
    for(int x = 0; x < field->SX; x++)
      if(!field->field[y * field->SX + x]) {full_line = 0; break;}
    if(full_line)
    {
      for(int y2 = y; y2 < field->SY-1; y2++)
      {
        for(int x = 0; x < field->SX; x++) 
          field->field[y2*field->SX + x] = field->field[(y2+1)*field->SX + x];
      }
      for(int x = 0; x < field->SX; x++) 
        field->field[(field->SY-1) * field->SX + x] = 0;
      y--;
    }
  }
}

void tet_init(sTField *field)
{
  for(int x = 0; x < field->SX*field->SY; x++) 
    field->field[x] = 0;
}

int tet_add_shape(sTField *field, uint8_t type)
{
  if(type == 0) //random
    type = 200 + rand()%7;
  cur_tet.type = type;
  cur_tet.cx = field->SX/2;
  cur_tet.cy = field->SY-1;
  cur_tet.rot = 3;
  return tet_rotate(&cur_tet, field, 1);
}

uint32_t prev_game_ms = 0;
uint8_t game_state = game_waitstart;
uint32_t game_prev_act = 0;

void tet_game_moveto(sTField *field, int tgt_x, int tgt_y, int tgt_rot)
{
  if(game_state != game_waitstep) return;
  int need_move_x = 1;
  int need_move_y = 1;
  int need_rot = 1;
  if(tgt_x == -1234) need_move_x = 0;
  if(tgt_y == -1234) need_move_y = 0;
  if(tgt_rot == -1234) need_rot = 0;
  tgt_x += field->SX/2;
  if(tgt_x < 0) tgt_x = 0;
  if(tgt_x >= field->SX) tgt_x = field->SX-1;
  int attempts = 0;
  if(need_rot)
    while(cur_tet.rot != tgt_rot && attempts++ < 5) tet_rotate(&cur_tet, field, 1);
  attempts = 0;
  if(need_move_x)
  {
    while(cur_tet.cx != tgt_x && attempts++ < field->SX)
    {
      if(cur_tet.cx > tgt_x)
        tet_move(&cur_tet, field, move_left);
      else
        tet_move(&cur_tet, field, move_right);
    }
  }
}

void tet_game_step(sTField *field, int req_action)
{
  uint32_t ms = millis();
  if(ms - prev_game_ms < 10) return;
  prev_game_ms = ms;
  if(game_state == game_waitstart)
  {
    game_prev_act = ms;
//    if(req_action != action_start) return;
    tet_init(field);
    game_state = game_needfigure;
    return;
  }
  if(game_state == game_needfigure)
  {
    int res = tet_add_shape(field, 0);
    if(res) game_state = game_waitstep;
    else game_state = game_over;
    game_prev_act = ms;
    return;
  }
  if(game_state == game_over)
  {
    if(ms < game_prev_act + field->over_time) return;
    tet_init(field);
    game_state = game_waitstart;
    game_prev_act = ms;
    return;
  }
  if(game_state == game_waitblow)
  {
    if(ms < game_prev_act + field->blow_time) return;
    tet_remove_full(field);
    game_state = game_needfigure;
    game_prev_act = ms;
    return;
  }
  if(game_state == game_dropend)
  {
    if(ms < game_prev_act + field->blow_time) return;
    game_state = game_figend;
    game_prev_act = ms;
    return;
  }
  if(game_state == game_figend)
  {
    if(tet_check_field(field)) game_state = game_waitblow;
    else game_state = game_needfigure;
    game_prev_act = ms;
    return;
  }
  if(game_state == game_waitstep)
  {
    if(req_action != action_none)
    {
      if(req_action == action_drop)
      {
        while(1)
        {
          int moved_free = tet_move(&cur_tet, field, move_down);
          if(!moved_free)
          {
            tet_place(&cur_tet, field);
            game_state = game_dropend;
            game_prev_act = ms;
            break;
          }
        }
      }
      if(req_action == action_left) tet_move(&cur_tet, field, move_left);
      if(req_action == action_right) tet_move(&cur_tet, field, move_right);
      if(req_action == action_cw) tet_rotate(&cur_tet, field, 1);
      if(req_action == action_ccw) tet_rotate(&cur_tet, field, 0);
    }
    if(ms < game_prev_act + field->step_time) return;
    int moved_free = tet_move(&cur_tet, field, move_down);
    if(!moved_free && ms > game_prev_act + field->place_time)
    {
      tet_place(&cur_tet, field);
      game_state = game_figend;
    }
    if(moved_free)
      game_prev_act = ms;
    return;
  }
}


void tet_draw_array(sTField *field, uint8_t *rgb_out)
{
  uint32_t ms = millis();
  if(game_state == game_over)
  {
    int phase = (ms - game_prev_act) * 1000 / field->over_time;
    phase = 500 - phase;
    if(phase < 0) phase = -phase;
    phase = 500 - phase;
    int full_lines = field->SY * phase / 500;
    for(int x = 0; x < field->SX; x++)
      for(int y = 0; y < field->SY; y++)
      {
        int r = 2, g = 0, b = 0;
        if(y <= full_lines) r = 30, g = 30, b = 30;
        int idx = 3*(y*field->SX + x);
        rgb_out[idx] = r;
        rgb_out[idx+1] = g;
        rgb_out[idx+2] = b;
      }
    return;
  }
  for(int x = 0; x < field->SX; x++)
    for(int y = 0; y < field->SY; y++)
    {
      int r = 2, g = 0, b = 0;
      int type = field->field[y*field->SX + x];
      if(type != 0) r = 15, g = 5, b = 20; 
      int idx = 3*(y*field->SX + x);
      rgb_out[idx] = r;
      rgb_out[idx+1] = g;
      rgb_out[idx+2] = b;
    }

  for(int n = 0; n < 4; n++)
  {
    int xx = cur_tet.cx + cur_tet.bx[n];
    int yy = cur_tet.cy + cur_tet.by[n];
    if(xx >= 0 && xx < field->SX && yy >= 0 && yy < field->SY)
    {
      int r = 10, g = 20, b = 5;
      int idx = 3*(yy*field->SX + xx);
      rgb_out[idx] = r;
      rgb_out[idx+1] = g;
      rgb_out[idx+2] = b;
      
    }
  }
  
  if(game_state == game_waitblow)
  {
    uint32_t mask = tet_check_field(field);
    int phase = (ms - game_prev_act) * 1000 / field->blow_time;
    for(int x = 0; x < field->SX; x++)
      for(int y = 0; y < field->SY; y++)
      {
        if(mask & 1<<y)
        {
          int r = 2, g = 0, b = 0;
          if(phase < 700)
            r = 5 + phase/10, g = 5 + phase/10, b = 5 + phase/10;
          else
            r = g = b = 76 - (phase-700)/4;
          int idx = 3*(y*field->SX + x);
          rgb_out[idx] = r;
          rgb_out[idx+1] = g;
          rgb_out[idx+2] = b;
        }
      }
  }
}

Untitled file

C/C++
#include <Arduino.h>

enum
{
  tet_line = 200,
  tet_L,
  tet_L2,
  tet_T,
  tet_sq,
  tet_z,
  tet_z2,
  tet_none
};

enum
{
  move_down = 0,
  move_left,
  move_right,
  move_up
};

enum
{
  game_waitstart = 0,
  game_needfigure,
  game_waitstep,
  game_figdrop,
  game_figend,
  game_dropend,
  game_waitblow,
  game_over
};

enum
{
  action_none = 0,
  action_start,
  action_left,
  action_right,
  action_cw,
  action_ccw,
  action_drop
};

typedef struct sTField
{
  uint8_t *field;
  int SX;
  int SY;
  int step_time;
  int drop_time;
  int place_time;
  int blow_time;
  int over_time;
};

typedef struct sTet
{
  uint8_t type;
  uint8_t rot;
  int bx[4];
  int by[4];
  uint8_t cx;
  uint8_t cy;
};

int tet_rotate(sTet *tet, sTField *field, uint8_t cw_rot);
int tet_move(sTet *tet, sTField *field, int dir);
int tet_place(sTet *tet, sTField *field);
uint32_t tet_check_field(sTField *field);
void tet_remove_full(sTField *field);
void tet_game_moveto(sTField *field, int tgt_x, int tgt_y, int tgt_rot);
void tet_game_step(sTField *field, int req_action);
void tet_draw_array(sTField *field, uint8_t *rgb_out);

Untitled file

C/C++
#include <Arduino.h>

#include <uMyo_BLE.h>

#include <FastLED.h>
#include "tetris_eng.h"

#define NUM_LEDS 122
#define DATA_PIN 13

#define TETR_SX 11
#define TETR_SY 11

CRGB leds[NUM_LEDS];
uint8_t tetr_field[TETR_SX*TETR_SY];
uint8_t rgb_states[TETR_SX*TETR_SY*3];

sTField field;

void setup() {
  field.field = tetr_field;
  field.SX = TETR_SX;
  field.SY = TETR_SY;
  field.step_time = 250;
  field.place_time = 800;
  field.drop_time = 200;
  field.blow_time = 600;
  field.over_time = 3000;
  Serial.begin(115200);
  uMyo.begin();
  
  FastLED.addLeds<NEOPIXEL, DATA_PIN>(leds, NUM_LEDS);  // GRB ordering is assumed
  for(int x = 0; x < NUM_LEDS; x++)
  {
    if(x%6 == 0) leds[x] = CRGB(5, 0, 0);
    if(x%6 == 1) leds[x] = CRGB(0, 5, 0);
    if(x%6 == 2) leds[x] = CRGB(0, 0, 5);
    if(x%6 == 3) leds[x] = CRGB(5, 5, 0);
    if(x%6 == 4) leds[x] = CRGB(0, 5, 5);
    if(x%6 == 5) leds[x] = CRGB(5, 0, 5);
  }
  FastLED.show();
  delay(1000);  
  tet_game_step(&field, action_start);
}

float angle_diff(float a1, float a2)
{
  float diff = a1 - a2;
  if(diff > 3.1415926) diff = -2*3.1415926 + diff;
  if(diff < -3.1415926) diff = 2*3.1415926 + diff;
  return diff;
}

uint32_t prev_ms = 0;
float avg_roll1 = 0;
float proc_roll1 = 0;
int rot_act_state = 0;
float loc_max = 100;
float zero_roll = 0;
float zero_yaw = 0;
int zero_inited = 0;

void loop() {
  float pitch1 = 0, pitch2 = 0;
  float level1 = 0, level2 = 0;
  float roll1 = 0, roll2 = 0;
  float yaw1 = 0, yaw2 = 0;
  int dev_cnt = uMyo.getDeviceCount();
  if(dev_cnt > 0)
  {
    level1 = uMyo.getAverageMuscleLevel(0);
    roll1 = uMyo.getRoll(0);
    pitch1 = uMyo.getPitch(0);
    yaw1 = uMyo.getYaw(0);
    if(!zero_inited)
    {
      zero_roll = roll1;
      zero_yaw = yaw1;
      zero_inited = 1;
    }
    zero_yaw *= 0.9995;
    zero_yaw += 0.0005*yaw1;
  }
  Serial.printf("Y %g P %g R %g\n", yaw1, pitch1, roll1);
  uint32_t ms = millis();
  static float last_roll_sw = 0;
  float roll_scale = 0.15;
  float droll = roll1 - last_roll_sw;
  static int roll_state = 0;
  int cur_action = action_none;
  static uint32_t prev_roll_tm = 0;
  
  if(droll > roll_scale && cur_action == action_none)
  {
    cur_action = action_ccw;
    roll_state--;
    last_roll_sw = roll1;
  }
  if(droll < -roll_scale && cur_action == action_none)
  {
    cur_action = action_cw;
    roll_state++;
    last_roll_sw = roll1;
  }

  static float last_yaw_sw = 0;
  float dyaw = angle_diff(yaw1, last_yaw_sw);
  float yaw_scale = 0.03;
  static int yaw_state = 0;
  if(dyaw > yaw_scale && cur_action == action_none)
  {
    cur_action = action_right;
    if(yaw_state < 7)
      yaw_state++;
    last_yaw_sw += yaw_scale;// yaw1;
  }
  if(dyaw < -yaw_scale && cur_action == action_none)
  {
    cur_action = action_left;
    if(yaw_state > -7)
      yaw_state--;
    last_yaw_sw -= yaw_scale;//yaw1;
  }


  int tgt_x = yaw_state;
  int tgt_y = -1234;
  int tgt_rot = roll_state;
  while(tgt_rot < 0) tgt_rot += 4;
  tgt_rot %= 4;
  tet_game_step(&field, cur_action);
  tet_draw_array(&field, rgb_states);
  leds[0] = 0;
  for(int l = 0; l < field.SX*field.SY; l++)
  {
    int x = 10 - l/11;
    int y = l%11;
    if(x%2) y = 10 - y;
    int r, g, b;
    int idx = y*11 + x;
    r = rgb_states[idx*3];
    g = rgb_states[idx*3+1];
    b = rgb_states[idx*3+2];
    leds[1+l] = CRGB(r, g, b);
  }
  FastLED.show();
  delay(5);
}

Simple Tetris implementation on LED matrix with control via uMyo

Credits

Frogolina

Frogolina

2 projects • 0 followers
I do a bunch of stuff in Ultimate Robotics
Thanks to Dmytro Dziuba.

Comments