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

Java入門

初心者向けJavaプログラミングでテトリスを作る方法

トム

・都内自社開発企業勤務/Javaバックエンドエンジニア
/Java歴10年以上 ・首都圏在住30代
・資格:基本情報技術者/応用情報技術者/Java Silver/Python3エンジニア認定基礎 詳細なプロフィール

この記事では、JavaのSwingライブラリを使って、1ファイルで動作するテトリスを作ります。

ブロックの描画・移動・回転から、行の削除判定やゲームオーバー処理まで、コード全体をコピペで動かしながら学べます。

Java初心者でも、記事どおりに進めれば30分で自分だけのテトリスが完成します。

Javaテトリス開発の全体像

Javaテトリス開発の全体像を示す図

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

ポイント

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

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

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

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

テトリスを動かすために必要なのは、JDK(Java Development Kit)だけです。Java 8以上であればどのバージョンでも動作します。2026年4月時点の最新はJava 26ですが、SwingはJavaの標準ライブラリなので追加インストールは不要です。

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

javac MiniTetris.java
java MiniTetris

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

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

JavaでGUIを作る方法の概要図

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メソッドは、ブロックが指定方向に移動できるかどうかを判定します。

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

衝突判定でよくあるバグはArrayIndexOutOfBoundsException(配列の範囲外アクセス)です。ブロックが画面上端より上にあるとき(pyが負の値のとき)にboard[ny][nx]を参照するとこのエラーが発生します。コード内の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++; // 同じ行をもう一度確認(複数ライン消し対応)
            }
        }
    }

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

テトリスのゲームオーバー画面

ブロックを固定した際に、すでに最上段に他のブロックが存在している場合はゲームオーバーとします。

        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.*;

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プログラミング
  • オブジェクト指向設計(クラスの分割や再利用)
  • イベントドリブン設計(ユーザー入力・タイマー処理)
  • 状態管理やロジックの分岐処理

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

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

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

1. テトリミノの色分け

現在のコードでは固定ブロックがシアン、操作中がグリーンの2色です。テトリミノの種類ごとに色を変えるには、board配列の値を0/1ではなく0〜7にし、値に応じてColorを切り替えます。

2. ネクストテトリミノの表示

次に落ちてくるテトリミノを画面右側に表示する機能です。spawnPieceで次のブロックを先に決めておき、paintComponentでフィールド外の座標に描画します。

3. ハードドロップ(スペースキーで一気に落下)

スペースキーを押すとブロックが一気に底まで落下する機能です。keyPressedVK_SPACEの分岐を追加し、canMove(0, 1)がfalseになるまでpy++をループすれば実装できます。

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

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

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

【要点まとめ】

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

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

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

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

トム

・都内自社開発企業勤務/Javaバックエンドエンジニア
/Java歴10年以上 ・首都圏在住30代
・資格:基本情報技術者/応用情報技術者/Java Silver/Python3エンジニア認定基礎 詳細なプロフィール

-Java入門