この記事では、JavaのSwingライブラリを使って、1ファイルで動作するテトリスを作ります。
ブロックの描画・移動・回転から、行の削除判定やゲームオーバー処理まで、コード全体をコピペで動かしながら学べます。
Java初心者でも、記事どおりに進めれば30分で自分だけのテトリスが完成します。
Javaテトリス開発の全体像

まず、Javaでテトリスを作るための要素を整理してみましょう。
要素を並べてみると複雑そうですが、ひとつずつ分解すればそれほど難しくありません。
初心者の方でも、まずは「動くブロックが表示される」だけの状態からスタートすればOKです。
Javaテトリスの前提条件と実行方法
テトリスを動かすために必要なのは、JDK(Java Development Kit)だけです。Java 8以上であればどのバージョンでも動作します。2026年4月時点の最新はJava 26ですが、SwingはJavaの標準ライブラリなので追加インストールは不要です。
IDEがなくても、コマンドラインから以下の2行でコンパイル・実行できます。
javac MiniTetris.java
java MiniTetrisEclipseや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メソッドは、ブロックが指定方向に移動できるかどうかを判定します。
判定のロジックはシンプルです。移動後の座標が「画面の左右端を超えていないか」「画面の底を超えていないか」「既に固定されたブロックと重なっていないか」の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で作成するミニマムなテトリスの全体コードです。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. ハードドロップ(スペースキーで一気に落下)
スペースキーを押すとブロックが一気に底まで落下する機能です。keyPressedにVK_SPACEの分岐を追加し、canMove(0, 1)がfalseになるまでpy++をループすれば実装できます。
まとめ:まずは1ブロック描画から始めよう
Javaでテトリスを作成するには、GUI構築・ブロックの管理・操作処理・描画ロジック・ゲーム状態の判定など、複数の要素を組み合わせる必要があります。
本記事では1ファイル完結のシンプルな構成にしています。Javaの基礎力を伸ばしたい方や、自作ゲームで成長したい方に最適です。
【要点まとめ】
- JavaのSwingでウィンドウと描画処理を構築する
- テトリスのブロック形状は2次元配列で表現する
- paintComponentで盤面とブロックを描画する
- KeyListenerでプレイヤー操作を実装する
- ブロックの回転は配列の回転処理で行う
- canMoveメソッドで移動可能かどうかを判定する
- 一列が埋まれば削除してスコア加算する
- ゲームオーバーは初期位置で配置できないときに発生する
- Timerを使って自動的に落下を進行させる
- 最終的に1ファイルで構成された実用的なコードが完成する
Javaでゲーム開発を体験することで、GUI構築やイベント処理、状態管理の理解が深まります。
まずは完成コードをコピペして動かし、1つずつコードの中身を理解していきましょう。