本ページはプロモーションが含まれています

Java入門

Javaテトリスの作り方|30分で動くコード付き解説

CATEGORY
REV.
DRAWN BY
TOM
SHEETS
13枚
Tetris(30分で動くテトリス)

トム

SESで約8年、環境を変えられないもどかしさの中にいました。32歳で自社開発企業に転職し、今はClaude Codeと自動化で、仕事も時間も組み直している途中です。
・Javaバックエンドエンジニア(Java歴12年以上)
・資格:基本情報技術者/応用情報技術者/Java Silver/Python3エンジニア認定基礎
詳細なプロフィール

𝕏

Javaのテトリスは、Swingを使えば1ファイル・約250行のソースコードで作れます。JDKさえあれば、完成コードをコピペしてjavacとjavaの2コマンドを打つだけで遊べます。

この記事では、(1)ブロックの描画、(2)キー操作と回転、(3)衝突判定、(4)ライン消去、(5)ゲームオーバー判定の順に、完成コードを部品ごとに分解して解説します。

完成コードを動かし、各パーツの解説を読み終えるまで30分が目安です。Java初心者でも、自分で動かしたテトリスを改造しながら仕組みを学べます。

Javaテトリス開発の全体像

まず、Javaでテトリスを作るための要素を整理してみましょう。

ポイント

  • ブロックの表示(GUI)
  • ブロックの移動・回転
  • 衝突判定(壁・他のブロック)
  • 行が揃ったときの削除処理
  • スコア表示
  • ゲームオーバーの判定

要素を並べてみると複雑そうですが、ひとつずつ分解すればそれほど難しくありません。

初心者の方でも、まずは「動くブロックが表示される」だけの状態からスタートすればOKです。

Javaテトリスの前提条件と実行方法

テトリスを動かすために必要なのは、JDK(Java Development Kit)だけです。Java 8以上であればどのバージョンでも動作します。

2026年9月時点の最新はJava 27(長期サポート版はJava 25)ですが、SwingはJavaの標準ライブラリなので追加インストールは不要です。

IDEがなくても、コマンドラインから以下の2行でコンパイル・実行できます。

javac MiniTetris.java
java MiniTetris

EclipseやIntelliJ IDEAなどのIDEを使っている場合は、新しいJavaプロジェクトにコードを貼り付けて実行してください。

あわせて読む

JavaのSwingでテトリス画面を描画する

JavaでGUIを作る方法はSwing以外にもJavaFXがあります。

JavaFXの方がモダンですが、テトリスのような2Dゲームを学習目的で作るなら、Swingの方がセットアップが簡単で、追加ライブラリなしで動作するためおすすめです。

JFrame frame = new JFrame("Mini Tetris");
MiniTetris game = new MiniTetris();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(game);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);

上記のコードで、テトリスのベースとなるウィンドウを表示できます。

ブロックの描画は paintComponent メソッドをオーバーライドして Graphics オブジェクトを使って行います。

    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        for (int y = 0; y < ROWS; y++) {
            for (int x = 0; x < COLS; x++) {
                if (board[y][x] == 1) {
                    g.setColor(Color.CYAN);
                    g.fillRect(x * BLOCK, y * BLOCK, BLOCK, BLOCK);
                }
            }
        }
        g.setColor(Color.GREEN);
        for (int y = 0; y < currentPiece.length; y++) {
            for (int x = 0; x < currentPiece[y].length; x++) {
                if (currentPiece[y][x] == 1) {
                    g.fillRect((px + x) * BLOCK, (py + y) * BLOCK, BLOCK, BLOCK);
                }
            }
        }
    }

paintComponentメソッドで1マスずつ描画し、テトリスのフィールドを構成します。

あわせて読む

テトリミノを2次元配列で定義する

テトリスのブロックは、基本的に「4つのマスから成る図形」です。これを2次元配列で表現するのが定番です。

あわせて読む
    private final List<int[][]> pieces = Arrays.asList(
        new int[][] {
            {0, 0, 0, 0},
            {1, 1, 1, 1},
            {0, 0, 0, 0},
            {0, 0, 0, 0}
        },
        new int[][] {
            {1, 1},
            {1, 1}
        },
        new int[][] {
            {0, 1, 0},
            {1, 1, 1},
            {0, 0, 0}
        },
        new int[][] {
            {0, 1, 1},
            {1, 1, 0},
            {0, 0, 0}
        },
        new int[][] {
            {1, 1, 0},
            {0, 1, 1},
            {0, 0, 0}
        },
        new int[][] {
            {1, 0, 0},
            {1, 1, 1},
            {0, 0, 0}
        },
        new int[][] {
            {0, 0, 1},
            {1, 1, 1},
            {0, 0, 0}
        }
    );

配列でブロックの形状を定義します。回転は配列の行列変換で実現できます。

キー操作でテトリミノを移動・回転させる

プレイヤーの入力に応じてブロックを左右・下に移動、もしくは回転させる必要があります。これは KeyListener を使って実装します。

    public void keyPressed(KeyEvent e) {
        switch (e.getKeyCode()) {
            case KeyEvent.VK_LEFT:
                if (canMove(-1, 0)) px--;
                break;
            case KeyEvent.VK_RIGHT:
                if (canMove(1, 0)) px++;
                break;
            case KeyEvent.VK_DOWN:
                if (canMove(0, 1)) py++;
                break;
            case KeyEvent.VK_UP:
                rotatePiece();
                break;
        }
        repaint();
    }

keyPressedメソッドにより、ユーザーのキーボード操作がテトリスのプレイに反映されます。

回転処理でよくある失敗は、壁際で回転するとブロックが画面外にはみ出すバグです。

このコードではcanRotateメソッドで回転後の位置を事前チェックし、はみ出す場合は回転をキャンセルしています。

本格的なテトリスでは「壁蹴り(Wall Kick)」と呼ばれる補正処理を入れますが、このミニマム版では回転キャンセルでシンプルに対処しています。

衝突判定(canMove)の仕組み

テトリスの動作で最も重要なのが衝突判定です。canMoveメソッドは、ブロックが指定方向に移動できるかどうかを判定します。

完成コードのゲームループ(入力・落下・衝突判定・ライン消去・描画)の中で、canMoveがどこで呼ばれているかを図にすると次のようになります。

ゲームループの中で canMove が呼ばれる3か所
呼ばれる場所
判定
はい
いいえ
キー入力keyPressed
canMove / canRotate移動・回転できる?
px・py を更新、または回転を反映→ repaint
何もせず repaint
Timer(0.5秒ごと)actionPerformed
canMove(0, 1)1マス下に動ける?
py++(1マス落下)→ repaint
mergePiece で盤面に固定→ clearLines で揃った行を消去 → spawnPiece
次のブロックを出現spawnPiece
canMove(0, 0)出現位置に置ける?
そのまま続行→ repaint
timer.stop()Game Over
NOTEどの経路も最後は repaint(paintComponent で再描画)に行き着きます。Timer の「いいえ」から spawnPiece の行へつながります。

判定のロジックはシンプルです。

移動後の座標が「画面の左右端を超えていないか」「画面の底を超えていないか」「既に固定されたブロックと重なっていないか」の3つをチェックします。

衝突判定でよくあるバグはArrayIndexOutOfBoundsException(配列の範囲外アクセス)です。

ブロックが画面上端より上にあるとき(pyが負の値のとき)にboard[ny][nx]を参照するとこのエラーが発生します。

このコードのpyは0から始まって増えるだけなので実際には負になりませんが、出現位置を画面の上にずらす拡張をしたときのために、ny >= 0 &&のチェックで備えています。

canRotateメソッドも同じ原理で動作しますが、回転後の形状を引数として受け取り、回転後の全マスが有効範囲内かを判定する点が異なります。

行が揃ったら消すライン削除の実装

行が揃ったかどうかのチェックは「盤面の横一列がすべて埋まっているか?」を判定するだけです。

全て埋まっていればその行を削除し、上の行を1段ずつ下にずらす処理を行います。

private void clearLines() {
        for (int y = ROWS - 1; y >= 0; y--) {
            boolean full = true;
            for (int x = 0; x < COLS; x++) {
                if (board[y][x] == 0) full = false;
            }
            if (full) {
                // 上の行を下にずらす
                for (int row = y; row > 0; row--) {
                    board[row] = board[row - 1].clone();
                }
                board[0] = new int[COLS]; // 一番上は空に
                y++; // 同じ行をもう一度確認(複数ライン消し対応)
            }
        }
    }
clearLines() が揃った行を消す流れ(下2行が揃っている例)
1
y = 19一番下の行から上へ順に調べるy = ROWS − 1 から開始
2
1段ずらす上の行を1段ずつ下にずらし、一番上を空の行にする
3
y = 19 をもう一度同じ行をもう一度確認する → また揃っている複数ライン消し対応
4
2行消えたもう1段ずらす
5
ループ終了上の行へ進む残りの行も揃っていないので処理が終わる
NOTE盤面は説明用の例(5列×5行)。y++ で同じ行をもう一度確認するので、2行続けて揃っている場合も両方消えます。

ゲームオーバー判定とスコア管理

ブロックを固定したあと、新しいブロックを出現位置に置けない(canMove(0, 0)がfalseになる)場合はゲームオーバーとします。

        if (!canMove(0, 0)) {
            timer.stop();
            JOptionPane.showMessageDialog(this, "Game Over!");
        }

スコア管理は、削除した行数に応じて点数を加算していく形式が一般的です。行数によって倍率を変えると、ゲーム性が高まります。

Javaテトリスの完成ソースコード(コピペで動く)

Javaで作成したテトリスの実行画面

以下がJavaで作成するミニマムなテトリスの全体コードです。1ファイルで動作するように構成しています。

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.Arrays;
import java.util.List;
import java.util.Random;

public class MiniTetris extends JPanel implements ActionListener, KeyListener {

    // フィールドサイズ
    private final int ROWS = 20, COLS = 10;

    // 1ブロックのピクセルサイズ(30x30)
    private final int BLOCK = 30;

    // タイマー(定期的に actionPerformed を呼び出す)
    private Timer timer;

    // ゲームボードの状態を表す2次元配列(0: 空, 1: 固定ブロック)
    private int[][] board = new int[ROWS][COLS];

    // 現在操作中のブロック形状(2次元配列)
    private int[][] currentPiece;

    // 現在操作中ブロックの座標(左上基準)
    private int px = 3, py = 0;

    // ランダムブロック生成用
    private Random rand = new Random();

    // ブロックパターン(I, O, T, S, Z, J, L)
    private final List<int[][]> pieces = Arrays.asList(
        new int[][] { // I
            {0, 0, 0, 0},
            {1, 1, 1, 1},
            {0, 0, 0, 0},
            {0, 0, 0, 0}
        },
        new int[][] { // O
            {1, 1},
            {1, 1}
        },
        new int[][] { // T
            {0, 1, 0},
            {1, 1, 1},
            {0, 0, 0}
        },
        new int[][] { // S
            {0, 1, 1},
            {1, 1, 0},
            {0, 0, 0}
        },
        new int[][] { // Z
            {1, 1, 0},
            {0, 1, 1},
            {0, 0, 0}
        },
        new int[][] { // J
            {1, 0, 0},
            {1, 1, 1},
            {0, 0, 0}
        },
        new int[][] { // L
            {0, 0, 1},
            {1, 1, 1},
            {0, 0, 0}
        }
    );

    // コンストラクタ(初期化処理)
    public MiniTetris() {
        setPreferredSize(new Dimension(COLS * BLOCK, ROWS * BLOCK)); // パネルサイズ指定
        setBackground(Color.BLACK); // 背景黒
        setFocusable(true); // キー入力受け付け
        addKeyListener(this); // キーイベント登録
        spawnPiece(); // 最初のブロック出現
        timer = new Timer(500, this); // 0.5秒ごとに落下処理
        timer.start(); // タイマースタート
    }

    // ランダムにブロックを出現させる
    private void spawnPiece() {
        currentPiece = deepCopy(pieces.get(rand.nextInt(pieces.size()))); // パターンからランダム取得
        px = 3;
        py = 0;

        // 初期位置で置けない場合はゲームオーバー
        if (!canMove(0, 0)) {
            timer.stop();
            JOptionPane.showMessageDialog(this, "Game Over!");
        }
    }

    // 2次元配列のディープコピー
    private int[][] deepCopy(int[][] shape) {
        int[][] copy = new int[shape.length][];
        for (int i = 0; i < shape.length; i++) {
            copy[i] = Arrays.copyOf(shape[i], shape[i].length);
        }
        return copy;
    }

    // 指定方向に移動できるかどうかチェック
    private boolean canMove(int dx, int dy) {
        for (int y = 0; y < currentPiece.length; y++) {
            for (int x = 0; x < currentPiece[y].length; x++) {
                if (currentPiece[y][x] == 1) {
                    int nx = px + x + dx;
                    int ny = py + y + dy;
                    // 画面外または既存ブロックと衝突していないか
                    if (nx < 0 || nx >= COLS || ny >= ROWS || (ny >= 0 && board[ny][nx] == 1))
                        return false;
                }
            }
        }
        return true;
    }

    // 回転後の形状で配置可能かを判定
    private boolean canRotate(int[][] rotated) {
        for (int y = 0; y < rotated.length; y++) {
            for (int x = 0; x < rotated[y].length; x++) {
                if (rotated[y][x] == 1) {
                    int nx = px + x;
                    int ny = py + y;
                    if (nx < 0 || nx >= COLS || ny >= ROWS || (ny >= 0 && board[ny][nx] == 1))
                        return false;
                }
            }
        }
        return true;
    }

    // ブロックの回転処理(90度右回転)
    private void rotatePiece() {
        int size = currentPiece.length;
        int[][] rotated = new int[size][size];
        for (int y = 0; y < size; y++) {
            for (int x = 0; x < size; x++) {
                rotated[x][size - 1 - y] = currentPiece[y][x];
            }
        }
        if (canRotate(rotated)) {
            currentPiece = rotated;
        }
    }

    // ブロックを盤面に固定(マージ)
    private void mergePiece() {
        for (int y = 0; y < currentPiece.length; y++) {
            for (int x = 0; x < currentPiece[y].length; x++) {
                if (currentPiece[y][x] == 1) {
                    board[py + y][px + x] = 1;
                }
            }
        }
        clearLines();  // ライン消去
        spawnPiece();  // 次のブロックを出現
    }

    // ラインが揃っていたら消去
    private void clearLines() {
        for (int y = ROWS - 1; y >= 0; y--) {
            boolean full = true;
            for (int x = 0; x < COLS; x++) {
                if (board[y][x] == 0) full = false;
            }
            if (full) {
                // 上の行を下にずらす
                for (int row = y; row > 0; row--) {
                    board[row] = board[row - 1].clone();
                }
                board[0] = new int[COLS]; // 一番上は空に
                y++; // 同じ行をもう一度確認(複数ライン消し対応)
            }
        }
    }

    // 描画処理(盤面と現在のブロック)
    public void paintComponent(Graphics g) {
        super.paintComponent(g);

        // 固定されたブロックを描画
        for (int y = 0; y < ROWS; y++) {
            for (int x = 0; x < COLS; x++) {
                if (board[y][x] == 1) {
                    g.setColor(Color.CYAN);
                    g.fillRect(x * BLOCK, y * BLOCK, BLOCK, BLOCK);
                }
            }
        }

        // 現在の操作中ブロックを描画
        g.setColor(Color.GREEN);
        for (int y = 0; y < currentPiece.length; y++) {
            for (int x = 0; x < currentPiece[y].length; x++) {
                if (currentPiece[y][x] == 1) {
                    g.fillRect((px + x) * BLOCK, (py + y) * BLOCK, BLOCK, BLOCK);
                }
            }
        }
    }

    // タイマーから呼ばれる落下処理
    public void actionPerformed(ActionEvent e) {
        if (canMove(0, 1)) {
            py++; // 1マス下に移動
        } else {
            mergePiece(); // 着地 → 固定化
        }
        repaint(); // 画面更新
    }

    // キー入力処理
    public void keyPressed(KeyEvent e) {
        switch (e.getKeyCode()) {
            case KeyEvent.VK_LEFT:
                if (canMove(-1, 0)) px--;
                break;
            case KeyEvent.VK_RIGHT:
                if (canMove(1, 0)) px++;
                break;
            case KeyEvent.VK_DOWN:
                if (canMove(0, 1)) py++;
                break;
            case KeyEvent.VK_UP:
                rotatePiece(); // 回転
                break;
        }
        repaint();
    }

    // 他のキーイベントは使用しない
    public void keyReleased(KeyEvent e) {}
    public void keyTyped(KeyEvent e) {}

    // メインメソッド:ゲーム起動処理
    public static void main(String[] args) {
        JFrame frame = new JFrame("Mini Tetris");
        MiniTetris game = new MiniTetris();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add(game);
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }
}

このコードで、ブロックの回転・移動・行削除が動作するテトリスが完成します。

スコア表示やネクストテトリミノの表示は未実装ですが、テトリスの基本構造を学ぶには最適です。

Javaでテトリスを作る楽しさと学び

実際に手を動かしてテトリスを作っていくと、以下のようなスキルが身につきます。

  • JavaのGUIプログラミング
  • クラス設計の基礎(フィールドとメソッドによる処理の整理)
  • イベントドリブン設計(ユーザー入力・タイマー処理)
  • 状態管理やロジックの分岐処理

そして何より「自分で作ったゲームが動く」という喜びは、プログラミング学習のモチベーションを大きく引き上げてくれます。

Javaテトリスを拡張するアイデア3選

基本のテトリスが動いたら、以下の機能を追加してみましょう。

テトリミノを色分けする

現在のコードでは固定ブロックがシアン、操作中がグリーンの2色です。

テトリミノの種類ごとに色を変えるには、board配列の値を0/1ではなく0〜7にし、値に応じてColorを切り替えます。あわせてcanMove・canRotateのboard[ny][nx] == 1をboard[ny][nx] != 0に直してください(そのままだと値が2〜7の固定ブロックと衝突しなくなります)。

次のテトリミノを表示する

次に落ちてくるテトリミノを画面右側に表示する機能です。

spawnPieceで次のブロックを先に決めておき、paintComponentでフィールド外の座標に描画します。

ハードドロップで一気に落とす

スペースキーを押すとブロックが一気に底まで落下する機能です。

keyPressedにVK_SPACEの分岐を追加し、canMove(0, 1)がfalseになるまでpy++をループすれば実装できます。

Javaテトリスでよくある質問

Q1. なぜSwingを使うのですか?JavaFXではダメですか?

A1. Swingは標準ライブラリに含まれ追加設定なしで動くため、初心者が1ファイルで完結させるのに向いています。JavaFXはより現代的なUIが作れますが、モジュール設定が必要になるため、まずはSwingで基本構造を学ぶのがおすすめです。

Q2. テトリミノを2次元配列で表現するメリットは?

A2. 0と1の並びでブロックの形をそのまま表現でき、回転処理も配列の行列入れ替えだけで実装できます。座標計算を個別に書くよりもシンプルで、拡張(色分けなど)もしやすくなります。

Q3. IDEを使わずに実行できますか?

A3. はい。JDKさえインストールされていれば、コマンドラインでjavac MiniTetris.javaとjava MiniTetrisの2行だけで動作します。EclipseやIntelliJ IDEAを使う場合はプロジェクトにコードを貼り付けるだけです。

Q4. 動かないときは何を確認すればいい?

A4. まずはJDKのバージョン(Java 8以上)と、コードの貼り付け漏れがないかを確認してください。コンパイルエラーが出る場合は、クラス名とファイル名(MiniTetris.java)が一致しているかもよくある原因です。

Q5. 完成後、どう拡張すればいいですか?

A5. 「テトリミノの色分け」「ネクストテトリミノの表示」「ハードドロップ」の3つが取り組みやすい拡張です。いずれも既存の変数や配列を少し拡張するだけで実装できます。

まとめ:まずは1ブロック描画から始めよう

Javaでテトリスを作成するには、GUI構築・ブロックの管理・操作処理・描画ロジック・ゲーム状態の判定など、複数の要素を組み合わせる必要があります。

この記事では1ファイル完結のシンプルな構成にしています。Javaの基礎力を伸ばしたい方や、自作ゲームで成長したい方に最適です。

【要点まとめ】

  • JavaのSwingでウィンドウと描画処理を構築する
  • テトリスのブロック形状は2次元配列で表現する
  • paintComponentで盤面とブロックを描画する
  • KeyListenerでプレイヤー操作を実装する
  • ブロックの回転は配列の回転処理で行う
  • canMoveメソッドで移動可能かどうかを判定する
  • 一列が埋まれば削除して上の行を詰める
  • ゲームオーバーは初期位置で配置できないときに発生する
  • Timerを使って自動的に落下を進行させる
  • 最終的に1ファイルで構成された実用的なコードが完成する

Javaでゲーム開発を体験することで、GUI構築やイベント処理、状態管理の理解が深まります。

まずは完成コードをコピペして動かし、1つずつコードの中身を理解していきましょう。

もっと本格的にゲーム作りでJavaを学びたい方には以下の書籍もおすすめです。

この記事で扱ったテトリス実装の考え方をベースに、より体系的な演習を通じてJavaのゲームプログラミングを深められます。

REVISIONS / 改訂履歴
REV.DATE内容
62026-10-15図解を描き直し(図面のデザインにそろえた)
52026-10-14一人称と言い回しの表記をそろえた。雰囲気だけの挿絵を外した
42026-10-04記載内容の事実確認と修正
32026-09-042026年9月時点の情報で全体を見直し
… ほか1回の改訂
12025-04-19初版
NEXT READ本棚から1本

AIで開発を組み直すなら、まずこの5冊から

入門・実践・設計まで、Claude Codeの本をレベル別に5冊選んで比べました。

→ おすすめ5冊を見る

  • この記事を書いた人
  • 最新記事

トム

SESで約8年、環境を変えられないもどかしさの中にいました。32歳で自社開発企業に転職し、今はClaude Codeと自動化で、仕事も時間も組み直している途中です。
・Javaバックエンドエンジニア(Java歴12年以上)
・資格:基本情報技術者/応用情報技術者/Java Silver/Python3エンジニア認定基礎
詳細なプロフィール

𝕏

Java入門