Newer
Older
OurSketch / Another.ino.txt
@JUNYA ONO JUNYA ONO 1 day ago 8 KB tuika
#include <M5Unified.h>
#include <esp_now.h>
#include <WiFi.h>
#include <cmath>

// --- 色定義 ---
#define BLACK   0x0000
#define WHITE   0xFFFF
#define GREEN   0x07E0
#define RED     0xF800
#define GRAY    0x8410 // おじゃまブロック用の色

#define SCREEN_W 240
#define SCREEN_H 135

// --- バー設定 ---
int barW = 40;
int barH = 5;
int barX = (SCREEN_W - barW) / 2;
int barY = SCREEN_H - 10;

// --- ボール設定 ---
float ballX = SCREEN_W / 2;
float ballY = SCREEN_H / 2;
float ballVX = 2.5;
float ballVY = -2.0;
int ballR = 3;

// --- ブロック設定 ---
const int BLOCK_ROWS = 4;
const int BLOCK_COLS = 8;
int blockW = SCREEN_W / BLOCK_COLS;
int blockH = 12;
// ブロックの状態: 0 = なし, 1 = 通常, 2 = おじゃま
int blocks[BLOCK_ROWS][BLOCK_COLS];

// --- ゲーム状態 ---
bool gameWon = false;
bool gameOver = false;
int life = 3;

// --- 通信とおじゃまブロック用のスタック ---
int sent_stack = 0; // 送信用のスタック
volatile int recv_stack = 0; // 受信したおじゃまブロックのスタック (ISRから変更されるためvolatile)

// --- ESP-NOW関連 ---
// ブロードキャストアドレス(すべてのESP-NOWデバイスに送信)
uint8_t broadcastAddress[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
esp_now_peer_info_t peerInfo;

// 送受信するデータの構造体
typedef struct struct_message {
    char type; // 'A' = Attack (おじゃまブロック送信)
} struct_message;

struct_message myData;


// --- 関数プロトタイプ ---
void Init_ESPNOW();
void onESPNOWSent(const uint8_t *mac_addr, esp_now_send_status_t status);
// ★★★ エラー修正箇所 ★★★
void onESPNOWReceive(const esp_now_recv_info_t *info, const uint8_t *incomingData, int len);
void sendAttack();
void addOjamaBlock();
void resetBlocks();
void resetBall();

// ESP-NOW データ送信後のコールバック
void ICACHE_RAM_ATTR onESPNOWSent(const uint8_t *mac_addr, esp_now_send_status_t status) {
  // 送信結果はここでは特に処理しない
}

// ESP-NOW データ受信時のコールバック
// ★★★ ESP32コアのバージョンアップに対応した引数に変更 ★★★
void ICACHE_RAM_ATTR onESPNOWReceive(const esp_now_recv_info_t *info, const uint8_t *incomingData, int len) {
  struct_message msg;
  memcpy(&msg, incomingData, sizeof(msg));
  if (msg.type == 'A') {
    recv_stack++; // おじゃまスタックを増やす
  }
}

// ESP-NOW初期化
void Init_ESPNOW() {
  WiFi.mode(WIFI_STA);
  WiFi.disconnect();
  if (esp_now_init() == ESP_OK) {
    M5.Display.println("ESP-Now Init Success");
    esp_now_register_send_cb(onESPNOWSent);
    // ★★★ ここで呼び出す関数のシグネチャがコンパイルエラーの原因だった ★★★
    esp_now_register_recv_cb(onESPNOWReceive);
  } else {
    M5.Display.println("ESP-Now Init Failed");
    return;
  }
  
  memcpy(peerInfo.peer_addr, broadcastAddress, 6);
  peerInfo.channel = 0;  
  peerInfo.encrypt = false;
  
  if (esp_now_add_peer(&peerInfo) != ESP_OK){
    M5.Display.println("Failed to add peer");
    return;
  }
  M5.Display.println("Peer added");
}

// 攻撃(おじゃまブロック)を送信
void sendAttack() {
    myData.type = 'A';
    esp_err_t result = esp_now_send(broadcastAddress, (uint8_t *) &myData, sizeof(myData));
}

// おじゃまブロックを追加
void addOjamaBlock() {
    // 下の行から空いている場所を探す
    for (int i = BLOCK_ROWS - 1; i >= 0; --i) {
        for (int j = 0; j < BLOCK_COLS; ++j) {
            if (blocks[i][j] == 0) { // 空のマスを見つけたら
                blocks[i][j] = 2; // おじゃまブロック(2)を設置
                return; // 1つ追加したら終了
            }
        }
    }
}


void resetBlocks() {
  for (int i = 0; i < BLOCK_ROWS; ++i)
    for (int j = 0; j < BLOCK_COLS; ++j)
      blocks[i][j] = 1; // 通常ブロック(1)で初期化
  gameWon = false;
  gameOver = false;
  life = 3;
  sent_stack = 0;
  recv_stack = 0;
}

bool allBlocksCleared() {
  for (int i = 0; i < BLOCK_ROWS; ++i)
    for (int j = 0; j < BLOCK_COLS; ++j)
      if (blocks[i][j] != 0) return false; // ブロックが1つでも残っていたらfalse
  return true;
}

void showMessage(const char* msg) {
  M5.Display.fillScreen(BLACK);
  M5.Display.setTextColor(WHITE);
  M5.Display.setCursor(60, SCREEN_H / 2 - 8);
  M5.Display.println(msg);
  delay(1500);
}

void resetBall() {
  ballX = SCREEN_W / 2;
  ballY = SCREEN_H / 2;
  ballVX = 2.5;
  ballVY = -2.0;
}

// 通信初期化用のタスク
void task0(void *pvParameters) {
    Init_ESPNOW();
    vTaskDelete(NULL); // 初期化が終わったらタスクを削除
}

void setup() {
  auto cfg = M5.config();
  M5.begin(cfg);
  M5.Display.setRotation(3);
  M5.Display.setTextFont(1);
  M5.Display.fillScreen(BLACK);
  
  // Core1でESP-NOWの初期化タスクを実行
  xTaskCreatePinnedToCore(task0, "Task0", 4096, NULL, 1, NULL, 1);
  
  delay(500); // 通信初期化のための待機
  M5.Display.clear();
  
  resetBlocks();
}

void loop() {
  M5.update();

  if (gameWon) {
    showMessage("You Win!");
    resetBlocks();
    resetBall();
    return;
  }

  if (gameOver) {
    showMessage("You Lose...");
    resetBlocks();
    resetBall();
    return;
  }

  // 受信したおじゃまブロックを処理
  if (recv_stack > 0) {
      addOjamaBlock();
      recv_stack--;
  }

  // 操作(傾き)
  if (M5.Imu.update()) {
    auto data = M5.Imu.getImuData();
    float tilt = -data.accel.y;
    float speed = tilt * 10.0;
    barX += (int)speed;
    if (barX < 0) barX = 0;
    if (barX > SCREEN_W - barW) barX = SCREEN_W - barW;
  }

  // ボール移動
  ballX += ballVX;
  ballY += ballVY;

  // 壁反射
  if (ballX <= ballR) {
    ballX = ballR + 1;
    ballVX *= -1;
  }
  if (ballX >= SCREEN_W - ballR) {
    ballX = SCREEN_W - ballR - 1;
    ballVX *= -1;
  }
  if (ballY <= ballR) {
    ballY = ballR + 1;
    ballVY *= -1;
  }

  // バー反射
  if (ballY + ballR >= barY && ballY + ballR <= barY + barH &&
      ballX >= barX && ballX <= barX + barW) {
    float offset = (ballX - (barX + barW / 2)) / (barW / 2);
    float angle_deg = 15 + 60 * fabs(offset);
    float angle_rad = angle_deg * M_PI / 180.0;
    float speed = sqrt(ballVX * ballVX + ballVY * ballVY);
    ballVX = speed * sin(offset >= 0 ? angle_rad : -angle_rad);
    ballVY = -speed * cos(angle_rad);
    ballY = barY - ballR - 1;

    if (ballX <= ballR) ballX = ballR + 1;
    if (ballX >= SCREEN_W - ballR) ballX = SCREEN_W - ballR - 1;
  }

  // ブロックとの衝突
  for (int i = 0; i < BLOCK_ROWS; ++i) {
    for (int j = 0; j < BLOCK_COLS; ++j) {
      if (blocks[i][j] > 0) { // ブロックが存在すれば
        int bx = j * blockW;
        int by = i * blockH;
        if (ballX + ballR > bx && ballX - ballR < bx + blockW &&
            ballY + ballR > by && ballY - ballR < by + blockH) {
          
          if(blocks[i][j] == 1) { // 通常ブロックの場合
            blocks[i][j] = 0; // ブロックを消す
            sent_stack++; // 送信用スタックを+1
            if (sent_stack >= 3) {
                sendAttack(); // 3つ溜まったら攻撃
                sent_stack = 0; // スタックリセット
            }
          } else if (blocks[i][j] == 2) { // おじゃまブロックの場合
            blocks[i][j] = 1; // 通常ブロックに変化させる
          }

          // 反射方向の計算
          float cx = bx + blockW / 2;
          float cy = by + blockH / 2;
          float dx = ballX - cx;
          float dy = ballY - cy;
          if (fabs(dx / blockW) > fabs(dy / blockH)) {
            ballVX *= -1;
          } else {
            ballVY *= -1;
          }

          goto block_hit_done;
        }
      }
    }
  }
block_hit_done:

  // ボールが下に落ちたらライフ減
  if (ballY > SCREEN_H) {
    life--;
    if (life <= 0) {
      gameOver = true;
      return;
    } else {
      resetBall();
    }
  }

  if (allBlocksCleared()) {
    gameWon = true;
    return;
  }

  // === 描画 ===
  M5.Display.fillScreen(BLACK);

  // ブロック
  for (int i = 0; i < BLOCK_ROWS; ++i) {
    for (int j = 0; j < BLOCK_COLS; ++j) {
      int bx = j * blockW;
      int by = i * blockH;
      if (blocks[i][j] == 1) { // 通常ブロック
        M5.Display.fillRect(bx + 1, by + 1, blockW - 2, blockH - 2, GREEN);
      } else if (blocks[i][j] == 2) { // おじゃまブロック
        M5.Display.fillRect(bx + 1, by + 1, blockW - 2, blockH - 2, GRAY);
      }
    }
  }

  // バー
  M5.Display.fillRect(barX, barY, barW, barH, WHITE);

  // ボール
  M5.Display.fillCircle((int)ballX, (int)ballY, ballR, RED);

  // ライフ表示
  M5.Display.setTextColor(WHITE);
  M5.Display.setCursor(5, 5);
  M5.Display.printf("Life: %d", life);

  delay(16);
}