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;
}
