diagram.json
{
"version": 1,
"author": "Anonymous maker",
"editor": "wokwi",
"parts": [
{ "type": "wokwi-arduino-nano", "id": "nano", "top": -283.2, "left": -163.7, "attrs": {} },
{
"type": "board-ssd1306",
"id": "oled1",
"top": -294.46,
"left": -278.17,
"attrs": { "i2cAddress": "0x3c" }
},
{
"type": "wokwi-pushbutton-6mm",
"id": "btn1",
"top": -184.6,
"left": -268.8,
"attrs": { "color": "green", "xray": "1", "key": "ArrowLeft" }
},
{
"type": "wokwi-pushbutton-6mm",
"id": "btn2",
"top": -184.6,
"left": -230.4,
"attrs": { "color": "green", "xray": "1", "key": "ArrowRight" }
},
{
"type": "wokwi-pushbutton-6mm",
"id": "btn3",
"top": -184.6,
"left": -182.4,
"attrs": { "color": "green", "xray": "1", "key": "ArrowDown" }
},
{
"type": "wokwi-pushbutton-6mm",
"id": "btn4",
"top": -184.6,
"left": -144,
"attrs": { "color": "green", "xray": "1", "key": "ArrowUp" }
}
],
"connections": [
[ "oled1:GND", "nano:GND.2", "black", [ "v-9.6", "h86.4" ] ],
[ "oled1:VCC", "nano:5V", "red", [ "v-19.2", "h172.95", "h19.2" ] ],
[ "oled1:SCL", "nano:A5", "green", [ "v-28.8", "h173.1", "h-19.2" ] ],
[ "oled1:SDA", "nano:A4", "green", [ "v-38.4", "h134.47" ] ],
[ "btn1:1.r", "nano:5", "green", [ "v-9.6", "h250.4", "v-96", "h-96" ] ],
[ "btn1:2.r", "nano:GND.1", "green", [ "h0" ] ],
[ "btn2:1.r", "nano:4", "green", [ "v0", "h135.2" ] ],
[ "btn2:2.r", "nano:GND.1", "green", [ "h0" ] ],
[ "btn3:1.r", "nano:3", "green", [ "v0", "h96.8" ] ],
[ "btn3:2.r", "nano:GND.1", "green", [ "h135.2", "v0.4" ] ],
[ "btn4:1.r", "nano:2", "green", [ "v0", "h68" ] ],
[ "btn4:2.r", "nano:GND.1", "green", [ "h96.8", "v-9.2" ] ]
],
"dependencies": {}
}
sketch.ino
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
// ディスプレイの設定
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
// ボタンのピン設定 (デジタルピン 2, 3, 4, 5 を使用)
const int BTN_LEFT = 5;
const int BTN_RIGHT = 4;
const int BTN_DOWN = 3;
const int BTN_ROT = 2;
// ゲームボードの設定
#define BOARD_WIDTH 10
#define BOARD_HEIGHT 20
#define BLOCK_SIZE 3 // 1ブロックを3x3ピクセルで描画
#define BOARD_X ((SCREEN_WIDTH - (BOARD_WIDTH * BLOCK_SIZE)) / 2)
#define BOARD_Y 2
// ボードの状態を保持する配列 (0: 空, 1: ブロックあり)
uint8_t board[BOARD_HEIGHT][BOARD_WIDTH] = {0};
// ゲーム状態の変数
int currentType, currentRot, currentX, currentY;
int nextType;
unsigned long lastDropTime = 0;
int dropInterval = 500; // 初期落下速度(ミリ秒)
int score = 0;
bool gameOver = false;
// 7種類のテトリミノ (I, J, L, O, S, T, Z) x 4方向の回転状態
// 4x4のグリッドを16ビット(0x0000〜0xFFFF)のビットマップで表現
const uint16_t TETROMINOS[7][4] PROGMEM = {
// I型
{0x0F00, 0x2222, 0x00F0, 0x4444},
// J型
{0x44C0, 0x8E00, 0x6440, 0x0E20},
// L型
{0x4460, 0x0E80, 0xC440, 0x2E00},
// O型 (回転しても形は同じ)
{0xCC00, 0xCC00, 0xCC00, 0xCC00},
// S型
{0x06C0, 0x8C40, 0x06C0, 0x8C40},
// T型
{0x0E40, 0x4C40, 0x4E00, 0x4640},
// Z型
{0x0C60, 0x4C80, 0x0C60, 0x4C80}
};
void setup() {
Serial.begin(9600);
// ボタンの初期化(内部プルアップ抵抗を使用)
pinMode(BTN_LEFT, INPUT_PULLUP);
pinMode(BTN_RIGHT, INPUT_PULLUP);
pinMode(BTN_ROT, INPUT_PULLUP);
pinMode(BTN_DOWN, INPUT_PULLUP);
// ディスプレイの初期化
if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println(F("SSD1306 allocation failed"));
for(;;); // 初期化失敗時は停止
}
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(47, 26);
display.println(F("TETRIS"));
display.display();
delay(1500);
// 乱数のシードを設定(未接続のアナログピンのノイズを利用)
randomSeed(analogRead(0));
// 最初のブロックを生成
nextType = random(7);
spawnBlock();
}
void loop() {
if (gameOver) {
drawGameOver();
// 任意のボタンを押してリスタート
if (digitalRead(BTN_ROT) == LOW || digitalRead(BTN_DOWN) == LOW) {
resetGame();
}
return;
}
unsigned long currentTime = millis();
// 入力処理
handleInput();
// ブロックの自然落下処理
if (currentTime - lastDropTime > dropInterval) {
if (!checkCollision(currentType, currentRot, currentX, currentY + 1)) {
currentY++; // 下へ移動可能なら移動
} else {
lockBlock(); // 衝突したら固定
}
lastDropTime = currentTime;
}
// 画面描画
drawGame();
}
void handleInput() {
// 左移動
if (digitalRead(BTN_LEFT) == LOW) {
if (!checkCollision(currentType, currentRot, currentX - 1, currentY)) {
currentX--;
}
delay(120); // チャタリング防止と移動速度調整
}
// 右移動
if (digitalRead(BTN_RIGHT) == LOW) {
if (!checkCollision(currentType, currentRot, currentX + 1, currentY)) {
currentX++;
}
delay(120);
}
// 回転
if (digitalRead(BTN_ROT) == LOW) {
int nextRot = (currentRot + 1) % 4;
if (!checkCollision(currentType, nextRot, currentX, currentY)) {
currentRot = nextRot;
}
delay(200); // 回転は少し長めにウェイトを入れる
}
// 下降(ソフトドロップ)
if (digitalRead(BTN_DOWN) == LOW) {
if (!checkCollision(currentType, currentRot, currentX, currentY + 1)) {
currentY++;
score += 1; // 落下させたボーナススコア
}
delay(50); // 高速落下
}
}
// 指定した位置・回転状態でブロックが壁や他のブロックに衝突しないか確認
bool checkCollision(int type, int rot, int x, int y) {
// PROGMEMからブロックの形状データを読み込む
uint16_t shape = pgm_read_word(&TETROMINOS[type][rot]);
for (int i = 0; i < 16; i++) {
// 16ビットのデータから各マスの状態を確認 (1番上の左端から順に確認)
if (shape & (0x8000 >> i)) {
int blockX = x + (i % 4);
int blockY = y + (i / 4);
// 左右の壁、または底に衝突
if (blockX < 0 || blockX >= BOARD_WIDTH || blockY >= BOARD_HEIGHT) {
return true;
}
// 既に固定されているブロックに衝突
if (blockY >= 0 && board[blockY][blockX] != 0) {
return true;
}
}
}
return false;
}
// ブロックをボードに固定し、新しいブロックを生成する
void lockBlock() {
uint16_t shape = pgm_read_word(&TETROMINOS[currentType][currentRot]);
for (int i = 0; i < 16; i++) {
if (shape & (0x8000 >> i)) {
int blockX = currentX + (i % 4);
int blockY = currentY + (i / 4);
if (blockY >= 0 && blockY < BOARD_HEIGHT && blockX >= 0 && blockX < BOARD_WIDTH) {
board[blockY][blockX] = 1; // ボードにブロックを書き込む
}
}
}
clearLines(); // 揃ったラインを消去
spawnBlock(); // 次のブロックを出現
}
void spawnBlock() {
currentType = nextType;
nextType = random(7);
currentRot = 0;
currentX = BOARD_WIDTH / 2 - 2; // 中央上部へ配置
currentY = -2; // 画面外(少し上)から出現させる
// 出現した瞬間に衝突する場合はゲームオーバー
if (checkCollision(currentType, currentRot, currentX, currentY)) {
gameOver = true;
}
}
// 横一列が揃っているか確認し、消去する
void clearLines() {
int linesCleared = 0;
for (int y = BOARD_HEIGHT - 1; y >= 0; y--) {
bool isFull = true;
for (int x = 0; x < BOARD_WIDTH; x++) {
if (board[y][x] == 0) {
isFull = false;
break;
}
}
if (isFull) {
linesCleared++;
// 消去した行より上の行をすべて1段下げる
for (int moveY = y; moveY > 0; moveY--) {
for (int x = 0; x < BOARD_WIDTH; x++) {
board[moveY][x] = board[moveY - 1][x];
}
}
// 1番上の行を空にする
for (int x = 0; x < BOARD_WIDTH; x++) {
board[0][x] = 0;
}
y++; // 下げた行も再度チェックするため y をインクリメント
}
}
// スコアの加算 (複数ライン同時消しでボーナス)
if (linesCleared == 1) score += 100;
else if (linesCleared == 2) score += 300;
else if (linesCleared == 3) score += 500;
else if (linesCleared >= 4) score += 800;
// スコアに応じて落下速度を上げる (MAX速度 150ms)
dropInterval = max(150, 500 - (score / 1000) * 50);
}
// 画面全体の描画
void drawGame() {
display.clearDisplay();
// ボードの枠を描画
display.drawRect(BOARD_X - 1, BOARD_Y - 1,
(BOARD_WIDTH * BLOCK_SIZE) + 2,
(BOARD_HEIGHT * BLOCK_SIZE) + 2,
SSD1306_WHITE);
// 固定されたブロックの描画
for (int y = 0; y < BOARD_HEIGHT; y++) {
for (int x = 0; x < BOARD_WIDTH; x++) {
if (board[y][x] != 0) {
display.fillRect(BOARD_X + x * BLOCK_SIZE, BOARD_Y + y * BLOCK_SIZE,
BLOCK_SIZE - 1, BLOCK_SIZE - 1, SSD1306_WHITE); // -1 でブロック間の隙間を作る
}
}
}
// 落下中のブロックの描画
uint16_t shape = pgm_read_word(&TETROMINOS[currentType][currentRot]);
for (int i = 0; i < 16; i++) {
if (shape & (0x8000 >> i)) {
int blockX = currentX + (i % 4);
int blockY = currentY + (i / 4);
if (blockY >= 0) { // 画面外(上部)のブロックは描画しない
display.fillRect(BOARD_X + blockX * BLOCK_SIZE, BOARD_Y + blockY * BLOCK_SIZE,
BLOCK_SIZE - 1, BLOCK_SIZE - 1, SSD1306_WHITE);
}
}
}
// UI (スコアと次のブロック) の描画
display.setCursor(BOARD_X + (BOARD_WIDTH * BLOCK_SIZE) + 10, BOARD_Y);
display.print(F("SCORE"));
display.setCursor(BOARD_X + (BOARD_WIDTH * BLOCK_SIZE) + 10, BOARD_Y + 10);
display.print(score);
display.setCursor(BOARD_X + (BOARD_WIDTH * BLOCK_SIZE) + 10, BOARD_Y + 30);
display.print(F("NEXT"));
drawNextPiece(BOARD_X + (BOARD_WIDTH * BLOCK_SIZE) + 10, BOARD_Y + 45);
display.display();
}
// 次のブロックを描画する関数
void drawNextPiece(int x, int y) {
uint16_t shape = pgm_read_word(&TETROMINOS[nextType][0]);
for (int i = 0; i < 16; i++) {
if (shape & (0x8000 >> i)) {
int blockX = (i % 4);
int blockY = (i / 4);
display.fillRect(x + blockX * BLOCK_SIZE, y + blockY * BLOCK_SIZE,
BLOCK_SIZE - 1, BLOCK_SIZE - 1, SSD1306_WHITE);
}
}
}
// ゲームオーバー画面
void drawGameOver() {
display.clearDisplay();
display.setCursor(30, 20);
display.println(F("GAME OVER"));
display.setCursor(20, 40);
display.print(F("Score: "));
display.println(score);
display.display();
}
// ゲームのリセット処理
void resetGame() {
// ボードのクリア
for (int y = 0; y < BOARD_HEIGHT; y++) {
for (int x = 0; x < BOARD_WIDTH; x++) {
board[y][x] = 0;
}
}
score = 0;
dropInterval = 500;
gameOver = false;
nextType = random(7);
spawnBlock();
delay(500); // 少し待機してから再開
}
