先日、ChatGPTの開発元であるOpenAIが、オープンソースのライセンスでモデル「gpt-oss」を一般公開しました。本稿では「gpt-oss」を利用する方法を紹介し、バイブコーディングで落ちものゲームを作ってみましょう。
オープンモデル「gpt-oss」について
OpenAIは2025年8月5日にオープンウェイトモデル「gpt-oss」を一般公開しました。「OpenAI o4-mini」や「o3-mini」とほぼ同等の性能を備えています。その構造には、MoE(Mixture-of-Experts)を採用しており、計算資源を節約しながら高い精度の推論が可能となっています。
また、ライセンスには自由度の高い「Apache License 2.0」が採用されているので改変も再配布も可能です。OpenAIは、2019年にGPT-2を公開して以降、久しくオープンソースでモデルを公開していませんでしたので、待望のオープンモデルです。
「gpt-oss」では、用途に応じて200億パラメータの「gpt-oss-20b」と1200億パラメータの「gpt-oss-120b」の二つが提供されています。このうち、家庭用のパソコンで動かすことができるのは「gpt-oss-20b」の方です。16GBのメモリを搭載したエッジデバイスで実行できると言われています。
実際、筆者が今年購入したMacBook Pro M4(メモリ32GB)で試したところ、それなりの速度で動作させることができました。ただし、メモリの少ないマシンだと動かすのが難しいかもしれません。
Ollamaで「gpt-oss」を利用してみよう
「gpt-oss」はオープンなモデルですが、モデルだけをダウンロードしても利用することはできません。モデルを動かすための推論エンジンが必要となります。ここでは、Ollamaを利用してみましょう。
Ollamaの使い方については、姉妹連載(こちら)で紹介しています。基本的には、OllamaのWebサイト(こちら)からインストーラーをダウンロードしてきてインストールします。
その後、ターミナル(WindowsならPowerShell、macOSならターミナル.app)を起動して、次のコマンドを実行します。モデルは、14GBほどありますので、インターネット環境によっては、ダウンロードに時間がかかるでしょう。
ollama pull gpt-oss:20b
「gpt-oss」を実行するには、Windowsならタスクトレイから、macOSならメニューバーの右上のアイコンから「Ollama」を開きましょう。そして、テキストボックスの右下にあるモデル選択ボックから「got-oss:20b」を選んで、プロンプトを入力します。
落ちものゲームを作ってもらおう
それでは、今回は、この「gpt-oss:20b」を利用して、バイブコーディングを実践してみましょう。作成するのは、ブラウザで手軽に遊べる落ちものゲームです。
今回、次のような指示(プロンプト)を利用して、落ちものゲームを作ってみます。Ollamaの「gpt-oss:20b」に以下のテキストを貼り付けて送信します。すると、HTML/JavaScriptのコードを生成します。
### 目標:
- 落ちものゲームを作成して「game.html」に保存してください
### 利用技術:
- HTML/JavaScriptを利用してブラウザゲームを作ってください
- Canvasを利用して描画してください
### ゲームの仕様:
- 5色のL字型(3x2)のブロックが落ちてくるものにしてください
- 同じ色のブロックが縦か横に5個並んだら消えるものにしてください
- カーソルキーの左右でブロックを左右に動かせるようにしてください
- カーソルキーの上でブロックが90度回転するようにしてください
- 画面は10x15個のブロックが並ぶ領域を用意してください
- 最初から3つくらいブロックをランダムに配置して障害物にしてください
- ブロックが障害物にぶつかったらそこで固定してください
生成されたプログラムをコピーしてテキストエディタに貼り付けて、ファイル「game.html」に保存します。そして、HTMLファイルをブラウザにドラッグ&ドロップしましょう。するとゲームが実行されます。
ゲームの仕様を敢えて緩く指定しているために、実行するたびに似て非なるプログラムを生成してくれます。大抵の場合、エラーのない正確なプログラムを生成してくれます。
落ちものゲームのプログラムを確認してみよう
今回、生成されたプログラムを、こちらのGistにアップロードしました。プログラム全体は、そちらから確認してみてください。
それでは、プログラムを部分的に抜粋して確認してみましょう。以下の部分は、ブロックの画面(変数grid)を0で初期化し、画面内に障害物を3つランダムに配置します。
const cols = 10;
const rows = 15;
let grid = [];
// グリッド初期化
for (let y = 0; y < rows; y++) {
grid[y] = Array(cols).fill(0);
}
// 障害物を3つランダム配置
for (let i = 0; i < 3; i++) {
let x, y;
do {
x = Math.floor(Math.random() * cols);
y = Math.floor(Math.random() * (rows - 5)) + 5;
} while (grid[y][x] !== 0);
grid[y][x] = Math.floor(Math.random() * colors.length) + 1;
}
続いて、新規ブロックを生成する関数spawnPieceを見てみましょう。基本ブロックの形状を定数baseShapeとして定義しており、これをベースに複製して新しい移動可能なブロックを生成します。
// 基本ブロックの形状
const baseShape = [{x:0, y:0}, {x:0, y:1}, {x:1, y:1}, {x:2, y:1}];
let piece; // 移動可能ブロック
let interval;
// 新しいブロック生成
function spawnPiece() {
piece = {
coords: baseShape.map(c => ({ x: c.x, y: c.y })),
x: Math.floor((cols - 3) / 2),
y: 0,
color: Math.floor(Math.random() * colors.length) + 1
};
// ブロック生成時に衝突したらゲームオーバー
if (collides(piece.x, piece.y, piece.coords)) {
clearInterval(interval);
alert("ゲームオーバー");
}
}
そして、以下の部分が、移動可能ブロックの衝突判定とブロックを固定する処理を記述したものです。衝突判定では、画面をはみ出してないか、あるいは、画面管理変数gridの要素が0(空)になっていないかを確認します。
// 衝突判定
function collides(px, py, coords) {
return coords.some(c => {
const x = px + c.x;
const y = py + c.y;
return x < 0 || x >= cols || y < 0 || y >= rows || grid[y][x] !== 0;
});
}
以下は、移動可能ブロックをグリッドに配置する処理です。
// ブロックを固定&消去&次生成
function placePiece() {
piece.coords.forEach(c => {
grid[piece.y + c.y][piece.x + c.x] = piece.color;
});
clearLines();
spawnPiece();
}
そして、以下は同じ色が5色並んでいたら消去する処理です。左上から右下へと同じ色が連なっていないかを確認し、連なっていた場合に、配列変数toClearに連なっている部分を追加します。そして、最後にtoClearを確認して画面管理変数gridを0に設定します。
// 同じ色が5個並んでいたら消去
function clearLines() {
const offsets = [1,2,3,4];
let toClear = [];
// 横
for (let y = 0; y < rows; y++) {
for (let x = 0; x <= cols - 5; x++) {
const v = grid[y][x];
if (v !== 0 && offsets.every(o => grid[y][x + o] === v)) {
for (let o = 0; o < 5; o++) toClear.push({ x: x + o, y: y });
}
}
}
// 縦
for (let x = 0; x < cols; x++) { … }
// 消去
toClear.forEach(p => { grid[p.y][p.x] = 0; });
// … 省略 …
}
以下がゲーム画面を描画する関数drawを抜粋したものです。最初に左上から右下へゲーム画面管理変数gridの値を参照してブロックを描画します。次に、現在のブロックを描画します。
// 描画
function draw() {
const ctx = document.getElementById("gameCanvas").getContext("2d");
ctx.clearRect(0, 0, cols * cellSize, rows * cellSize);
// グリッドを描画
for (let y = 0; y < rows; y++) {
for (let x = 0; x < cols; x++) {
if (grid[y][x]) {
ctx.fillStyle = colors[grid[y][x] - 1];
ctx.fillRect(x * cellSize, y * cellSize, cellSize, cellSize);
ctx.strokeRect(x * cellSize, y * cellSize, cellSize, cellSize);
}
}
}
// 現在のブロックを描画
piece.coords.forEach(c => {
ctx.fillStyle = colors[piece.color - 1];
ctx.fillRect((piece.x + c.x) * cellSize, (piece.y + c.y) * cellSize, cellSize, cellSize);
ctx.strokeRect((piece.x + c.x) * cellSize, (piece.y + c.y) * cellSize, cellSize, cellSize);
});
}
そして、メイン処理を行っているのが以下の部分です。移動可能ブロックを1つ下に移動させてみて、関数collidesで衝突判定をします。ぶつかっていなければ、実際に移動可能ブロックのy座標を1つ下に移動します。もし、移動できなければ、ブロックを固定します。これを300ミリ秒に1回行います。それによって、ブロックの落下処理を実現します。
// ゲーム開始
// …省略…
interval = setInterval(() => { // 繰り返し実行
// 落下可能か調べる
if (!collides(piece.x, piece.y + 1, piece.coords)) {
piece.y++;
} else {
// 落下できないのでブロックを固定
placePiece();
}
draw(); // 画面描画
}, 300);
まとめ
以上、今回は、OpenAIのオープンモデル「gpt-oss」を利用して、落ちものゲームを作成してみました。落ちものゲームは、だいたいプログラムの構造が決まっていることに加えて、生成行数が200行未満で実現できます。そのため、大抵問題のないプログラムが生成できます。
なお、今回「gpt-oss」に与えるプロンプトの「ゲームの仕様」の部分を変更することで、異なったゲームになります。例えば、ブロックの色数や形状を増減させたり、ブロックが揃う個数を変更したり、画面サイズを変更するなどしてみましょう。ルールに沿ったプログラムを生成してくれます。
そのようにして、いろいろとゲームのルールをカスタマイズできるので、自分好みの落ちものゲームを作ってみると良いでしょう。
自由型プログラマー。くじらはんどにて、プログラミングの楽しさを伝える活動をしている。代表作に、日本語プログラミング言語「なでしこ」 、テキスト音楽「サクラ」など。2001年オンラインソフト大賞入賞、2004年度未踏ユース スーパークリエータ認定、2010年 OSS貢献者章受賞。これまで50冊以上の技術書を執筆した。直近では、「大規模言語モデルを使いこなすためのプロンプトエンジニアリングの教科書(マイナビ出版)」「Pythonでつくるデスクトップアプリ(ソシム)」「実践力を身につける Pythonの教科書 第2版」「シゴトがはかどる Python自動処理の教科書(マイナビ出版)」など。