TOCHORIS

by revetronique in Circuits > Arduino

893 Views, 2 Favorites, 0 Comments

TOCHORIS

IMG_20210602_013959.jpg
TOCHORIS (東京都庁でテ〇リス風ゲームしてみた)

Don't you think array of windows on a building as a dot display? Imagine that a skyscraper as an arcade game machine and playing PONG with it. TOCHORIS is a tetris-like puzzle game run on a miniature scale Tokyo Metropolitan Government called "Tocho". This device is created with Arduino (ATmega328P board) and full-color RGB LED matrix (array of WS2812-2020).

(Special Thanks)

Here's the websites I referred to create this artwork.
https://create.arduino.cc/projecthub/john-bradnam/...

https://electronoobs.com/eng_arduino_tut104.php

Supplies

  • Arduino (ATmega328P)
  • WS2812-2020 (8x8 matrix) x 2
  • WS2812B x 2
  • DFPlayer Mini (mp3 decoder & 3W speaker amp.)
  • TM1640
  • 7-segment LED x 2
  • Push button (tactile switch) x 5
  • Resistor (100Ω x 8, 1kΩ x 2, 10kΩ x 3, 2.2kΩ, 4.7kΩ, 5.6kΩ, 33kΩ)
  • Speaker (4Ω/8Ω)
  • AAA battery x 3

What Is It Inspired By?

「東京アラート」発動 赤色にライトアップされた東京都庁とレインボーブリッジ

This artwork is inspired by "Tokyo alert", which warns the epidemic risks and information of COVID-19 in Tokyo announced by Tokyo Metropolitan Government, and it's called "Gaming Tocho (Tokyo Metropolitan Government)" over social media because of its glowing color and amusing appearance.

When I found it, I remembered some creative projects using windows on a building as a dot-matrix display to enable people to play video games like Pong or Tetris, that is, making a building as an arcade game. Then I conceived an enjoyable creative work that reproducing them with a scale model of Tokyo Metropolitan Government building instead of remodeling itself.

Electronic Circuit

schematics.png

First, I'd like to show the schematics of the circuit.

Basically the circuit of what I developed is the same as the picture, but I used Arduino Pro Mini (5V, 16MHz) instead of Arduino UNO to make it smaller as possible. My circuit is divided into two 2.8" x 1.87" (7.2 x 4.5 cm) PCB boards, one is the main board with MCU (Arduino Pro Mini), DF player Mini, and buttons, and the other is implemented an LED driver TM1640 to control 7-segment LEDs.

I will explain the detail of each component in the several following steps. If you don't need any explanation about the specification and description of every component, skip to the step 7.

DF Player Mini (MP3 Player)

IMGP7930.JPG

DF player mini is a tiny MP3 decoder & 3W speaker amp. This module loads mp3 data from a SD card.

It has 3 control modes: IO control, A/D button control, and serial communication mode to play the music. In this project, I chose the 3rd one because microcontrollers can operate it only in this way.

To utilize this module from the microcontroller (Arduino), I also adopt a library "SoftwareSerial", which imitates serial communication (UART) with general IO pins to connect and communicate between them. I defined the pin 5 as receiver (RX) and pin 4 as transmitter (TX) connected to the peripheral module. Notice you do NOT connect them to the pins having the same name, but alternate with each other (pin 5 to TX, and pin 4 to RX of DF player mini).

Plus, 3 AAA batteries connected in series supplies its power (common with the main power source of the whole system), and a small piezoelectric speaker connected to the pin "SPK+" and "SPK-" outputs the sound.

Score Counter (7-segment LED Display)

IMGP7744.JPG
IMGP7929.JPG

I used two cathode common 7-segment LED for displaying the current score and high-score. What I chose is very tiny but already discontinued now, so you need to find an alternative.

You may need to utilize an LED driver IC when you implement a 7-segment LED because the circuit is required to drive many LEDs. In this project, we selected TM1640, which can control up to 16 LEDs and has reasonable price.

When you use it, you need to connect the pin 9 - 16 (SEG1 - 8) to the anode pins (A - G, DP) of the both 7-segment LEDs, and the pin 18 - 25 (GRID1 - 8) to each cathode pin of them. Notice the order of the GRID pins is descending, which means you have to connect the GRID 1 to the most significant digit of the LED and GRID 4 to the least one of the high-score display, and same as the GRID 5 - 8 and current score display.

TM1640 has a serial interface to receive operations to control LEDs from an MCU. I have connected DIN to digital pin 8 and SCLK to pin 7 of Arduino.

Push Buttons With a Single I/O Pin

IMGP7740.JPG

Then, I also soldered 5 small push buttons (tactile switches) on the board. All buttons are connected in parallel to the same analog input pin (A0 in this project). This system can utilize multiple buttons with a single input pin but detect only one is pressed even if you press multiple buttons simultaneously.

My system uses a voltage divider, a circuit of a series of resistors, to detect which button is pressed. This circuit divides the applied voltage to the input pin into 6 levels from 0V to the supply voltage (Vcc). You might have already found the value of all resistors are completely different in each other because it's convenient that the gap of each voltage level is equivalent and I had to calculate the proper resistance values. According to this article, I needed to prepare one 2.2k, 4.7k, 5.6k, and 33kΩ resistor suppose that treating a 10kΩ resistor as the pull-up resistor.

To implement this circuit, first you connect 10kΩ, 2.2kΩ, 4.7k, 5.6k, and 33kΩ serially in this order. Next, you attach one side of each button to the all of the middle of resistors or the edge of the 33kΩ. You also connect all the other side of the buttons to the GND and finally do A0 pin between the resistor 10kΩ and 2.2kΩ.

Function of each button is defined as SELECT, UP, DOWN, LEFT, and RIGHT in order near from the upper voltage side.

WS2812B (matrix Display & Illumination)

IMG_WS2812B.jpg

This is the most important component for my game device.

The main screen having 16 x 8 resolutions is composed of two WS2812-2020 matrix displays. This module has 8 x 8 resolutions and links whole LEDs serially inside the panel. Soldering a connector behind the both matrix LED modules and plugging them to link together and they work as one 16 x 8 resolution display. I connected the pin D9 (Arduino) to the display module.

I added 2 more WS2812B module to make the game and its player more excited with lighting effects like colors or animations. The input pins of the both LEDs are connected to the same pin D6 (Arduino).

These modules consume are supplied their electricity from the power source (battery) because of their highly consumption.

Install Necessary Libraries

Now you have understood all of the electronic components I used.

What you learn next is the required libraries to develop the firmware for Arduino. You may have already installed Arduino IDE, but you can download the installer or software package here if not.

After installing the IDE, you also have to install the following libraries.

  • Adafruit NeoPixel
  • TM16xx
  • Adafruit GFX
  • Adafruit BusIO

Except the 2nd item, you can install them from "library manager" accessing from the tab menu "sketch". But, don't worry, you can get the zip file from Github, and execute "Add .ZIP Library" in the menu Sketch > Include Library. TM16xx library uses Adafruit GFX and BusIO library internally, so that's why we need to install 2 extra libraries.

Uploading Software

Here's the main source of the firmware. Just copy and paste it on a new file of Arduino IDE. But wait a moment to upload it. Even if you press the upload button, you will see an error causes.

// https://create.arduino.cc/projecthub/john-bradnam/wii-chuck-neopixel-tetris-game-047fdc
// http://electronoobs.com/eng_arduino_tut104.php
#include <Adafruit_NeoPixel.h>
#ifdef __AVR__
 #include <avr/power.h> // Required for 16 MHz Adafruit Trinket
#endif
#include <EEPROM.h>
#include <TM1640.h> // requiring install Adafruit GFX and BusIO library
#include <DFRobotDFPlayerMini.h>
#include <SoftwareSerial.h>
#include "PROGMEM_readAnything.h"

// game FPS
#define FRAMERATE 30
// initial speed of falling (frames)
#define INIT_SPEED 20
// animation speed (frames)
#define FRAME_ANIME 5

// deal with that a pixel is blank
#define EMPTY 16

// number of columns
#define MATRIX_COLS 8
// number of rows
#define MATRIX_ROWS 16
// brightness of the display
#define MATRIX_BRIGHT 4 // 0 ~ 16
// color when removing line
#define MATRIX_REMOVE 0x888888

// number of buttons
#define NUM_BUTTON 5
// button pattern
#define BUTTON_SELECT 0
#define BUTTON_UP 1
#define BUTTON_DOWN 3
#define BUTTON_LEFT 2
#define BUTTON_RIGHT 4

// speaker volume
#define SPK_VOLUME 20 // 0 ~ 30
// number of the track
#define NUM_BGM_MAIN 1
#define NUM_SE_TITLE 3
#define NUM_SE_GAMEOVER 4

// EEPROM address for high score
#define ADDR_EEPROM_SCORE_UPPER 1
#define ADDR_EEPROM_SCORE_LOWER 0

/** 
 *  constants
 */
typedef struct {
  byte id;
  byte width;
  byte height;
  bool picture[6];
  byte turns; // number of possible turns
  byte cx;
  byte cy;
  uint32_t color;
} Piece;
// features of puzzle pieces
const uint32_t pieces_color[7] = {0xFF00FF, 0x009900, 0xFF8000, 0x00FFFF, 0x0000FF, 0xFF0000, 0xFFFF00};
const Piece pieces[7] PROGMEM = {
  {0, 3, 2, {0, 1, 0, 1, 1, 1}, 4, 1, 1, pieces_color[0]}, //T:purple
  {1, 3, 2, {0, 1, 1, 1, 1, 0}, 2, 1, 0, pieces_color[1]}, //S:green
  {2, 2, 3, {1, 0, 1, 0, 1, 1}, 4, 0, 1, pieces_color[2]}, //L:orange
  {3, 4, 1, {1, 1, 1, 1, 0, 0}, 2, 2, 0, pieces_color[3]}, //I:cyan
  {4, 2, 3, {0, 1, 0, 1, 1, 1}, 4, 1, 1, pieces_color[4]}, //J:blue
  {5, 3, 2, {1, 1, 0, 0, 1, 1}, 2, 1, 0, pieces_color[5]}, //Z:red
  {6, 2, 2, {1, 1, 1, 1, 0, 0}, 1, 0, 0, pieces_color[6]}  //O:yellow
};
const int num_pieces = sizeof(pieces_color) / sizeof(pieces_color[0]);

// configuration of button input pin
// 0, 182, 418, 569, 840, 1023
const int thres_step = (1 << 10) / NUM_BUTTON;  // step voltage of each button state
const int thres_bias = 50; // deviation from the average step

/**
 * globals
 */
// game state and condition
byte gamestate = 0; //0: title, 1: main game, 2: result
int speed = INIT_SPEED; //lower one every 10 frames (smaller = faster)
// game board
// buffer to store which block is filled in each pixel
byte board[MATRIX_COLS * MATRIX_ROWS];
// game score
unsigned int score = 0;
unsigned int highscore = 0;
int lines = 0;

// current block to use
Piece piece;
// rotation
byte rotation = 0;  //0, 1, 2, 3
// horizontal and vertical position
int xpos, ypos;

// LED Matrix
Adafruit_NeoPixel matrix = Adafruit_NeoPixel(MATRIX_COLS * MATRIX_ROWS, 6, NEO_GRB + NEO_KHZ800);
// illumination LED
Adafruit_NeoPixel deco = Adafruit_NeoPixel(1, 9, NEO_GRB + NEO_KHZ800);
// score counter
// TM1640 (Data in: 7, Clock: 8, 8 digits)
TM1640 module(7, 8, 8);

// MP3 player
DFRobotDFPlayerMini mp3;
// software serial for DF player. RX: 5, TX: 4
SoftwareSerial mp3_serial(5, 4);

void setup() {
  // put your setup code here, to run once:
  // These lines are specifically to support the Adafruit Trinket 5V 16 MHz.
  // Any other board, you can remove this part (but no harm leaving it):
#if defined(__AVR_ATtiny85__) && (F_CPU == 16000000)
  clock_prescale_set(clock_div_1);
#endif
  // serial communication setup
  Serial.begin(9600);
  mp3_serial.begin(9600);
  // random seed initialization
  randomSeed(micros()); //millis()

  // load the high score
  highscore = ((unsigned int)EEPROM.read(ADDR_EEPROM_SCORE_UPPER) << 8) + (EEPROM.read(ADDR_EEPROM_SCORE_LOWER));

  // speaker
  if(!mp3.begin(mp3_serial)){
    Serial.println(F("Unable to begin:"));
    Serial.println(F("1.Please recheck the connection!"));
    Serial.println(F("2.Please insert the SD card!"));
    while(true){
      delay(0); // Code to compatible with ESP8266 watch dog.
    }
  }
  // speaker configuration
  mp3.volume(SPK_VOLUME);
  // starting sound
  mp3.play(NUM_SE_TITLE);

  // illumination LED
  deco.begin();
  deco.show();
  // LED matrix
  matrix.begin();
  matrix.setBrightness(MATRIX_BRIGHT);
  // all pixels to 'off'
  matrix.show();
}

void loop() {
  // put your main code here, to run repeatedly:
  static unsigned long frameCount = 0;
  static bool pre_pressed[NUM_BUTTON] = {false, false, false, false, false};

  // detect pressed button
  // multiple buttons on single Analog Pin (A0)
  bool pressed[NUM_BUTTON] = {false, false, false, false, false};
  // applied voltage to the button pin
  int val = analogRead(A0);
  // detection process
  for(int i = 0; i < NUM_BUTTON; i++){
    // check pressed if the value of AD conversion
    bool state = (val > (thres_step * i - thres_bias)) && (val < (thres_step * i + thres_bias));
    // only the down button is acceptable for holding down
    if(i == BUTTON_DOWN){
      pressed[i] = state;
    }
    else{
      // reproduce pseudo rising detection
      pressed[i] = !pre_pressed[i] && state;
      // store the current state of the button
      pre_pressed[i] = state;
    }
  }

  // title
  if(gamestate == 0){
    // game logo
    module.setDisplayToString("TOCHORIS");
    // start if the select button pressed
    if(pressed[BUTTON_SELECT]){
      // initialize game
      resetGame();
      // main game BGM
      mp3.play(NUM_BGM_MAIN);
      // go to the main game
      gamestate = 1;
    }
    // title scene
    if(frameCount % FRAME_ANIME == 0){
      // rainbow illumination as the 
      uint16_t hue = (uint16_t)(millis() % 65536UL);
      deco.setPixelColor(0, deco.gamma32(deco.ColorHSV(hue)));
      deco.show();
      // LED matrix display
//      for(int y = 0; y < MATRIX_ROWS; y++){
//        for(int x = 0; x < MATRIX_COLS; x++){
//          int index = x + y * MATRIX_COLS;
//          uint16_t col = (uint16_t)((millis() + 4096UL * index / matrix.numPixels()) % 65536UL);
//          matrix.setPixelColor(index, matrix.gamma32(matrix.ColorHSV(col)));
//        }
//      }
      // pick-up random color of the pieces
      for(int i = 0; i < matrix.numPixels(); i++){
        matrix.setPixelColor(i, pieces_color[random(0, num_pieces)]);
      }
    }
  }
  // main game
  else if(gamestate == 1){
    // drawing LED matrix
    setBoardPixel();
    // control process
    if(pressed[BUTTON_LEFT]){
      if(checkMovable(piece, xpos - 1, ypos, rotation)){
        xpos--;
      }
    }
    if(pressed[BUTTON_RIGHT]){
      if(checkMovable(piece, xpos + 1, ypos, rotation)){
        xpos++;
      }
    }
    if(pressed[BUTTON_DOWN]){
      if(checkMovable(piece, xpos, ypos + 1, rotation)){
        ypos++;
      }
    }
    if(pressed[BUTTON_UP]){
      int nrot = (rotation + 1) % piece.turns;
      if(checkMovable(piece, xpos, ypos, nrot)){
        rotation = nrot;
      }
    }
    // add latest pose of the piece on board
    overwritePiece(piece, xpos, ypos, rotation);
    // lowing blocks
    if(frameCount % speed == 0){
      // fall down if there is a space
      if(checkMovable(piece, xpos, ypos + 1, rotation)){
        ypos++;
      }
      // piece on the ground
      else{
        // paint game board
        storePieceOnBoard(piece, xpos, ypos, rotation);
        // check lines
        checkBoardLines();
        // take out new piece
        generatePiece();
        // LED matrix
        matrix.show();
        // check whether game is over
        if(!checkMovable(piece, xpos, ypos, rotation)){
          // GAMEOVER
          gamestate = 2;
          storePieceOnBoard(piece, xpos, ypos, rotation);
          overwritePiece(piece, xpos, ypos, rotation);
          // Gameover sound
          mp3.play(NUM_SE_GAMEOVER);
        }
      }
    }
    // illumination LED indicates the piece color
    deco.setPixelColor(0, piece.color);
    deco.show();
    // indicate the score
    displayScore(score, 7, 4);
    // indicate the high score
    displayScore(highscore, 3, 4);
  }
  // result (game over)
  else if(gamestate == 2){
    // when select button pressed
    if(pressed[BUTTON_SELECT]){
      // update high score
      if(score > highscore){
        highscore = score;
        // save the high score
        EEPROM.write(ADDR_EEPROM_SCORE_LOWER, (byte)(highscore & 0xFF));
        EEPROM.write(ADDR_EEPROM_SCORE_UPPER, (byte)((highscore >> 8) & 0xFF));
      }
      // clean all matrix
      matrix.clear();
      // go to the title scene
      gamestate = 0;
      // title music
      mp3.play(NUM_SE_TITLE);
    }
    if(frameCount % FRAME_ANIME == 0){
      // new record
      if(score > highscore){
        // rainbow illumination
        deco.setPixelColor(0, pieces_color[random(0, num_pieces)]);
        deco.show();
      }
    }
  }

  // Matrix LED flash
  matrix.show();
  // update frame and waiting
  frameCount++;
  delay(1000 / FRAMERATE);
}

// find where each part of a block is in the board
int calculatePieceIndex(int x, int y, int dx, int dy, byte rot){
  // 4 rotational patterns
  // define the top and right as positive direction
  switch(rot){
    case 0: // deg 0
      return (y + dy) * MATRIX_COLS + x + dx;
    case 1: // deg 90
      return (y + dx) * MATRIX_COLS + x - dy;      
    case 2: // deg 180
      return (y - dy) * MATRIX_COLS + x - dx;
    case 3: // deg 270
      return (y - dx) * MATRIX_COLS + x + dy;
    default:
      return -1;
  }
}

// remapping the index of the board to that of the LED matrix
int remapBoardToLEDpixel(int pos){
  // mapping as inverted position
  return matrix.numPixels() - 1 - pos;
  // index of the board buffer is same as that of LED matrix
  // return pos;
}

// generate next piece
void generatePiece(){
  // select a piece
  PROGMEM_readAnything(&pieces[random(0, num_pieces)], piece);
  // initialize the position
  xpos = MATRIX_COLS / 2;
  ypos = piece.cy;
  rotation = 0;
}

//whether a block can move somewhere
bool checkMovable(Piece piece, int x, int y, byte rot){
  // return true if it can fall, false if not
  for(int i = 0; i < piece.width; i++){
    for(int j = 0; j < piece.height; j++){
      if(piece.picture[i + j * piece.width]){
        int xx, yy;
        switch(rot){
          case 0:
            xx = x + i - piece.cx;
            yy = y + j - piece.cy;
            break;
          case 1:
            xx = x - j + piece.cy;
            yy = y + i - piece.cx;
            break;
          case 2:
            xx = x - i + piece.cx;
            yy = y - j + piece.cy;
            break;
          case 3:
            xx = x + j - piece.cy;
            yy = y - i + piece.cx;
            break;
        }
        if(xx < 0 || xx >= MATRIX_COLS || yy < 0 || yy >= MATRIX_ROWS)  return false;
        if(board[xx + yy * MATRIX_COLS] != EMPTY) return false;
      }
    }
  }
  return true;
}

// clean full board line
void checkBoardLines(){
  // number of lines deleted at once.
  int account = 0;
  // scanning each rows up to down
  for(int y = 0; y < MATRIX_ROWS; y++){
    bool complete = true;
    // check all pixels in a single line are filled
    for(int x = 0; x < MATRIX_COLS; x++){
      if(board[x + y * MATRIX_COLS] == EMPTY) complete = false;
    }
    // if a line is filled with blocks
    if(complete){
      account++;
      lines++;
      // LED animation of removing line
      removeLine(y);
      // lowing blocks
      for(int yy = y; yy >= 0; yy--){
        for(int xx = 0; xx < MATRIX_COLS; xx++){
          // top line will always be empty
          if(yy == 0){
            board[xx] = EMPTY;
          }
          // otherwise block falls to the below pixel (substitute the above pixel)
          else{
            int i = xx + yy * MATRIX_COLS;
            board[i] = board[i - MATRIX_COLS];
          }
        }
      }
      // update and show display
      setBoardPixel();
      matrix.show();
    }
  }
  // there is a line deleted
  if(account > 0){
    // add score point
    // corresponding to the number of deleting line: 10 * x * (x + 1)
    score += 5 * account * (account + 1);  // 10, 30, 60, 100
    // update game speed (every 10 lines)
    speed = max(0, INIT_SPEED - floor(lines / 10));
  }
}

// paint the piece in the LED matrix
void overwritePiece(Piece piece, int x, int y, byte rot){
  for(int i = 0; i < piece.width; i++){
    for(int j = 0; j < piece.height; j++){
      if(piece.picture[i + j * piece.width]){
        int pos = calculatePieceIndex(x, y, i - piece.cx, j - piece.cy, rot);
        // set as position inverted
        matrix.setPixelColor(remapBoardToLEDpixel(pos), piece.color);
      }
    }
  }
}
// paint the piece on the board (in memory buffer)
void storePieceOnBoard(Piece piece, int x, int y, byte rot){
  for(int i = 0; i < piece.width; i++){
    for(int j = 0; j < piece.height; j++){
      if(piece.picture[i + j * piece.width]){
        int pos = calculatePieceIndex(x, y, i - piece.cx, j - piece.cy, rot);
        board[pos] = piece.id;
      }
    }
  }
}
// paint the board in the LED matrix
void setBoardPixel(){
  for(int i = 0; i < matrix.numPixels(); i++){
    // fill the pixel color with the chosen block color
    if(board[i] != EMPTY){
      matrix.setPixelColor(remapBoardToLEDpixel(i), pieces_color[board[i]]);
    }
    else{
      matrix.setPixelColor(remapBoardToLEDpixel(i), 0);
    }
  }
}
// animation deleting line
void removeLine(int y){
  for(int x = 0; x < MATRIX_COLS; x++){
    //flash LED
    int index = remapBoardToLEDpixel(x + y * MATRIX_COLS);
    matrix.setPixelColor(index, MATRIX_REMOVE);
    matrix.show();
    delay(50);
    matrix.setPixelColor(index, 0);
  }
}
// fill with EMPTY
void cleanBoard(){
  for(int i = 0; i < matrix.numPixels(); i++){
    board[i] = EMPTY;
  }
}
// initialize game condition
void resetGame(){
  // reset game score and speed
  lines = score = 0;
  speed = INIT_SPEED;
  // make all pixels empty
  cleanBoard();
  // select the first piece
  generatePiece();
}

// display a score with 7 segments LED
// [arg] score: score number, place: index of the ones place, digits: number of displays
void displayScore(int score, int place, int digits){
  // player score
  for(int i = 0; i < digits; i++){
    // if the digits of the score is more than the display size, score board displays 9999
    // otherwise, calculating the number in the ones place
    int val = score < pow(10, digits) ? score % 10 : 9;
    module.setDisplayDigit(val, place - i, false);
    // round down the ones place
    score /= 10;
  }
}  <br>

After you copy and paste the above code, once you add a new tab by pressing Ctrl + Shift + N or selecting "Add New Tab" in a small button with upside-down triangle, then copy and paste the following script, and finally save it with naming it "PROGMEM_readAnything.h".

//https://arduino.stackexchange.com/questions/13545/using-progmem-to-store-array-of-structs
#include <Arduino.h>

template <typename T> void PROGMEM_readAnything (const T* sce, T& dest){
  memcpy_P(&dest, sce, sizeof(T));
}
template <typename T> T PROGMEM_getAnything (const T* sce){
  static T temp;
  memcpy_P(&temp, sce, sizeof(T));
  return temp;
}<br>

Now you are ready to upload the firmware. Just pressing the upload button.

Creating Its Chassis

IMG_20210601_073050.jpg

Nothing to say about its housing. Basically you can use any materials you prefer like paper or cardboard, but I created it with 3D printer. I bought X-maker provided by QIDI TECH, but instead you can choose different models like that because what l bought is out of stock now.

There are 3 reasons to recommend you to use 3D printer, 1) it provides you higher flexibility of cases, 2) usually you will get a durable and good-looking one compared to paper crafts, 3) you can store and share it as 3D model data. If you start using a 3D printer, you should choose material to ABS or PLA because of their ease of handling.

In my case, I have printed out the center plate and 2 side steeples individually and combine them. The steeples are separated into the upper and lower side. The matrix LED displays are attached behind the center plate, and in each tower the pair of 7-segment LED and decoration full-color LED are installed. When you install them, you need to put the 7-segment LED in the rectangle hole and decoration LED on the top of the lower side of the tower.

These attached 4 STL files are the data I used to create its housing. Unfortunately, the current ones are not still the complete version because these don't have screw holes to be assembled into the miniature "Tocho". You need to make screw holes or use a glue to fix all of the components (I chose the former way). But feel free to utilize these STL files even if you can accept to process the housing parts.

Assemble

IMG_20210601_025300.jpg
IMG_20210602_013845.jpg

Now all components must be ready.

The left picture shows how the circuit is assembled and everything would consist of the device like the right picture. I use 3 AAA batteries for the power supply.

When you install 7-segment LED and decoration full-color LED into the steeples, you need to put the 7-segment LED in the rectangle hole and decoration LED on the top of the lower side of the tower.

How to Play

IMGP7756.JPG
IMG_20210602_014110.jpg
IMG_20210602_014003.jpg

Got it! After finishing all steps, you will see the complete one.

Now let's enjoy "TOCHORIS"! When turning on its power, you can see the opening effect of the LEDs.

You press the SELECT button to start the game, and its rule is almost the same as TETRIS, so you strategically rotate, move, and drop blocks falling into the rectangular field to clear as many lines as possible by completing horizontal rows of blocks without empty space. More lines you deletes, more scores you will get.

If the block reaches to the top, the game is over. You can see an extra effect (rainbow illumination) if you break the high score.