0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

samegame

Posted at

image.png

1985年ごろに登場したパズルゲームで、縦横に隣り合う同じ色のブロックをまとめて消すのが特徴。

基本ルール

・縦または横に 2つ以上隣接する同じ色 のブロックをクリックすると、そのグループがまとめて消える。
・消したブロックの上にあるブロックは下に落ちる。
・列が空になったら、右の列が左に詰まる。
・ブロックが消せなくなったら終了。
・消したブロック数をnとするとスコア加点はn^2

プログラム

// SameGame(さめがめ) Processing サンプル
// クリック:2個以上つながった同色グループを消去
// Rキー:リセット
// 得点 = n^2 / 盤クリアボーナスあり(+1000)

// ========= 設定 =========
int COLS = 12;           // 列数
int ROWS = 12;           // 行数
int COLORS = 5;          // 色の種類
int CELL  = 40;          // 1マスのピクセル
int MARGIN = 20;         // 盤表示の余白

color[] palette = {
  #e57373, #64b5f6, #81c784, #ffd54f, #ba68c8, #4db6ac, #ff8a65
};

// ========= ゲーム状態 =========
int[][] board;           // board[r][c] = 色インデックス (0..COLORS-1) / 空きは -1
int score = 0;           // スコア
boolean gameOver = false;

// ========= 初期化 =========
void setup() {
  //size(MARGIN*2 + COLS*CELL, MARGIN*2 + ROWS*CELL + 80);
  size(20*2 + 12*40, 20*2 + 12*40 + 80);//
  smooth(4);
  initGame();
}

void initGame() {
  board = new int[ROWS][COLS];
  for (int r = 0; r < ROWS; r++) {
    for (int c = 0; c < COLS; c++) {
      board[r][c] = int(random(COLORS));
    }
  }
  score = 0;
  gameOver = false;
}

// ========= メインループ =========
void draw() {
  background(245);

  // 盤の描画(ホバー中のグループを半透明でハイライト)
  ArrayList<PVector> hover = groupAtMouse();
  drawBoard(hover);

  // UI
  fill(20);
  textAlign(LEFT, TOP);
  textSize(16);
  int remaining = countMovableGroups();
  text("Score: " + score, MARGIN, height - 70);
  text("Moves: " + remaining, MARGIN, height - 48);
  text("R: Reset", MARGIN, height - 26);

  if (!gameOver && remaining == 0) {
    gameOver = true;
    // 全消しボーナス
    if (isBoardEmpty()) {
      score += 1000;
    }
  }

  if (gameOver) {
    fill(0, 180);
    rect(0, 0, width, height);
    fill(255);
    textAlign(CENTER, CENTER);
    textSize(28);
    String msg = isBoardEmpty()
      ? "Clear!!  Bonus +1000\nFinal Score: " + score + "\nPress R to Restart"
      : "No More Moves\nFinal Score: " + score + "\nPress R to Restart";
    text(msg, width/2, height/2);
  }
}

// ========= 入力 =========
void mousePressed() {
  if (gameOver) return;

  int[] rc = mouseToRC(mouseX, mouseY);
  if (rc == null) return;

  int r = rc[0], c = rc[1];
  int col = board[r][c];
  if (col < 0) return;

  ArrayList<PVector> group = floodGroup(r, c, col);
  if (group.size() >= 2) {
    // スコア計算
    int n = group.size();
    score += n*n;

    // 消去
    for (PVector p : group) {
      board[(int)p.x][(int)p.y] = -1;
    }

    // 重力&左詰め
    applyGravity();
    shiftColumnsLeft();
  }
}

void keyPressed() {
  if (key == 'r' || key == 'R') {
    initGame();
  }
}

// ========= 描画系 =========
void drawBoard(ArrayList<PVector> highlight) {
  // タイル
  noStroke();
  for (int r = 0; r < ROWS; r++) {
    for (int c = 0; c < COLS; c++) {
      int v = board[r][c];
      int x = MARGIN + c * CELL;
      int y = MARGIN + (ROWS-1 - r) * CELL; // 下がr=0になるように描く

      // マス背景
      fill(255);
      rect(x, y, CELL, CELL);

      if (v >= 0) {
        fill(palette[v % palette.length]);
        rect(x+2, y+2, CELL-4, CELL-4, 8);
      }
    }
  }

  // グリッド線
  stroke(220);
  for (int c = 0; c <= COLS; c++) {
    int x = MARGIN + c * CELL;
    line(x, MARGIN, x, MARGIN + ROWS * CELL);
  }
  for (int r = 0; r <= ROWS; r++) {
    int y = MARGIN + r * CELL;
    line(MARGIN, y, MARGIN + COLS * CELL, y);
  }

  // ホバー中のハイライト(半透明)
  if (highlight != null && highlight.size() >= 2) {
    noStroke();
    fill(255, 255, 255, 120);
    for (PVector p : highlight) {
      int rr = (int)p.x;
      int cc = (int)p.y;
      int x = MARGIN + cc * CELL;
      int y = MARGIN + (ROWS-1 - rr) * CELL;
      rect(x+2, y+2, CELL-4, CELL-4, 8);
    }

    // 枠線
    stroke(0, 180);
    noFill();
    for (PVector p : highlight) {
      int rr = (int)p.x;
      int cc = (int)p.y;
      int x = MARGIN + cc * CELL;
      int y = MARGIN + (ROWS-1 - rr) * CELL;
      rect(x+2, y+2, CELL-4, CELL-4, 8);
    }
  }
}

// ========= ロジック =========

// マウス座標 → (r,c) 変換。盤外なら null
int[] mouseToRC(int mx, int my) {
  int col = (mx - MARGIN)/CELL;
  int row = (my - MARGIN)/CELL;
  if (col < 0 || col >= COLS || row < 0 || row >= ROWS) return null;
  row = ROWS - 1 - row; // 画面下を r=0 として扱う
  return new int[]{row, col};
}

// 現在マウス位置のグループ(2個未満なら null)
ArrayList<PVector> groupAtMouse() {
  int[] rc = mouseToRC(mouseX, mouseY);
  if (rc == null) return null;
  int r = rc[0], c = rc[1];
  int col = board[r][c];
  if (col < 0) return null;
  ArrayList<PVector> g = floodGroup(r, c, col);
  if (g.size() < 2) return null;
  return g;
}

// BFSで同色連結成分を取得
ArrayList<PVector> floodGroup(int r0, int c0, int colorIndex) {
  ArrayList<PVector> out = new ArrayList<PVector>();
  if (!inBounds(r0, c0)) return out;
  if (board[r0][c0] != colorIndex) return out;

  boolean[][] vis = new boolean[ROWS][COLS];
  IntList rq = new IntList();
  IntList cq = new IntList();

  rq.append(r0);
  cq.append(c0);
  vis[r0][c0] = true;

  int[] dr = {1, -1, 0, 0};
  int[] dc = {0, 0, 1, -1};

  while (rq.size() > 0) {
    int r = rq.remove(0);
    int c = cq.remove(0);
    out.add(new PVector(r, c));
    for (int k = 0; k < 4; k++) {
      int nr = r + dr[k];
      int nc = c + dc[k];
      if (inBounds(nr, nc) && !vis[nr][nc] && board[nr][nc] == colorIndex) {
        vis[nr][nc] = true;
        rq.append(nr);
        cq.append(nc);
      }
    }
  }
  return out;
}

boolean inBounds(int r, int c) {
  return (0 <= r && r < ROWS && 0 <= c && c < COLS);
}

// 各列内で上から下へ詰める(重力)
void applyGravity() {
  for (int c = 0; c < COLS; c++) {
    int write = 0; // r=0 から順に埋める(下詰め)
    for (int r = 0; r < ROWS; r++) {
      if (board[r][c] >= 0) {
        if (r != write) {
          board[write][c] = board[r][c];
          board[r][c] = -1;
        }
        write++;
      }
    }
  }
}

// 空列を左へ詰める
void shiftColumnsLeft() {
  int write = 0;
  for (int c = 0; c < COLS; c++) {
    if (!isColumnEmpty(c)) {
      if (c != write) {
        // 列コピー
        for (int r = 0; r < ROWS; r++) {
          board[r][write] = board[r][c];
          board[r][c] = -1;
        }
      }
      write++;
    }
  }
}

boolean isColumnEmpty(int c) {
  for (int r = 0; r < ROWS; r++) {
    if (board[r][c] >= 0) return false;
  }
  return true;
}

boolean isBoardEmpty() {
  for (int r = 0; r < ROWS; r++) {
    for (int c = 0; c < COLS; c++) {
      if (board[r][c] >= 0) return false;
    }
  }
  return true;
}

// まだ消せるグループがあるか(2個以上連結があるか)
int countMovableGroups() {
  boolean[][] seen = new boolean[ROWS][COLS];
  int count = 0;

  for (int r = 0; r < ROWS; r++) {
    for (int c = 0; c < COLS; c++) {
      int v = board[r][c];
      if (v < 0 || seen[r][c]) continue;

      // 近傍だけ早期判定(同色が隣にいればOK)
      if ((inBounds(r+1, c) && board[r+1][c] == v) ||
          (inBounds(r-1, c) && board[r-1][c] == v) ||
          (inBounds(r, c+1) && board[r][c+1] == v) ||
          (inBounds(r, c-1) && board[r][c-1] == v)) {
        // 連結成分を一度だけマーキング
        ArrayList<PVector> comp = floodGroup(r, c, v);
        for (PVector p : comp) {
          seen[(int)p.x][(int)p.y] = true;
        }
        if (comp.size() >= 2) count++;
      } else {
        seen[r][c] = true;
      }
    }
  }
  return count;
}
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?