目次
「じゃんけん」したことありますか。
2人以上で面と向かってポンポンやるアレです。
最初はグーのやつですね。
ですです。
検索しました。
参考:日本じゃんけん協会
https://japan-rps.jimdofree.com/
「最初はグー、じゃんけんぽん!」などの掛け声を発しつつ、「石(グー)」、「鋏(チョキ)」、「紙(パー)」のうちから、各プレーヤーが、同時に好きな手を出す。 それぞれが出した手の種類によって、プレーヤー間の「勝ち」、「負け」、「あいこ」が決まる。
すごく単純で簡単なゲームですね。
今回、どうしてじゃんけん?
【経緯】なぜじゃんけん
じゃんけんが必要だったのです。そんな時ありませんか。
たとえば。。
- 誰が支払うかじゃんけんで決める
- バンジーする順番をじゃんけんで決める
- 好きなドーナッツを選ぶ順番を決める
などなど。
そういう時、じゃんけんは便利です。
でも、テレワークとか、ネット経由のイベントなど、面と向かってじゃんけんできない。
そんな時、ブラウザでできるじゃんけんゲームを作ろうと思いました。
【調査】参考ブログ探し
じゃんけんプログラムはどうやって作ればいいのでしょう。
検索していろんなブログを拝見しました。
一番近い内容が「複数人」「じゃんけん」でヒットした以下のブログです。
ChatGPTも教えてくれない!? じゃんけんプログラムの作り方!
https://zenn.dev/nodamushi/articles/b3c4c9802e7f5e
ぜひ見ていただきたい。私には難しすぎる。
読んでもわかりません。。
しかし今はAIがあります。
【共有】サンプルページ
リアルタイム多人数じゃんけん。複数人でプレイ可能です。

複数人じゃんけんオンラインゲーム(サンプルページ)
https://astrowave.jp/amnesia_record/janken.php
特徴
- 制限なしで何人でも同時プレイ可能
- 遠く離れた人ともオンラインで対戦
- サドンデス機能で決着まで自動進行
- 見学モードで敗者も観戦可能
簡単操作・自動進行
- ルーム作成はワンクリック
- 共有URLで即座に参加
- 全員選択完了で自動カウントダウン
- サドンデス・あいこも自動で次ラウンド
使い方
1.ルームに参加
- ホストから受け取った共有URLを開く
- 自動的にルームに参加
2.手を選択
- 自分のカード(「(あなた)」バッジ付き)で手を選ぶ
- グー・チョキ・パーのいずれかを選択
3.結果確認
- 全員選択完了まで待機
- カウントダウン後、結果を確認
基本ルール
じゃんけんと同じです。
めんどくさいのは、共有URLを連絡する必要があること。
使用したものはPHPとJavascriptになります。
【共有2】試そうマニュアル
「新しいルームを作成」ボタンを押します。

するとルームが作成されます。

「緑」のコピーボタンを押して共有URLをコピーします。
共有URLをプレイしたい人に教えます。

共有が確認できると、ホストと参加プレイヤーの内容は連動し始めます。

ゲーム開始でじゃんけんのモードに入ります。

何を出すか選ぼう。選んだら選択済みになります。
選んだ手は相手に見えません。

参加プレイヤーの手が出揃ったらカウントダウンが始まって勝敗が知らされます。

トロフィーのアイコンが付いた人の勝ち。

勝敗後もホストの「ゲーム開始」ボタンで何回もプレイ可能です。
人数が変わる時は、改めてルームを作成してください。
改める場合は、ホストのURLを再読み込みです。
【共有3】PHPファイルと構成
やりたい機能を追加していくと、とても長いコードになってしまいました。
ファイル構成
- janken.php ← メインのじゃんけんアプリ
- room_manager.php ← ルーム管理API
- rooms.json ← ルームデータ保存(自動生成)
興味のある人はページをダウンロードしてください。
janken.php
長すぎるので、掲載やめました。
ページごとダウンロードしてください。
636行
<!-- /////////////////////////////////////////////////////////////////////////
ここから
//////////////////////////////////////////////////////////////////////////////-->
この間にコードあります。
<!-- /////////////////////////////////////////////////////////////////////////
ここまで
//////////////////////////////////////////////////////////////////////////////-->
2006行まで以下はルーム管理APIのphpファイル。
自分だけではとてもじゃないですが作れません。
room_manager.php
<?php
header('Content-Type: application/json');
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type');
// ルームデータファイルのパス
$roomsFile = 'rooms.json';
// ルームデータを読み込み
function loadRooms() {
global $roomsFile;
if (file_exists($roomsFile)) {
$data = file_get_contents($roomsFile);
$decoded = json_decode($data, true);
return is_array($decoded) ? $decoded : [];
}
return [];
}
// ルームデータを保存
function saveRooms($rooms) {
global $roomsFile;
file_put_contents($roomsFile, json_encode($rooms, JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE), LOCK_EX);
}
// 古いルームをクリーンアップ(設定可能な時間で経過したルームを削除)
function cleanupOldRooms(&$rooms) {
$currentTime = time();
// クリーンアップ時間設定(秒単位)
// 1時間 = 3600秒, 6時間 = 21600秒, 24時間 = 86400秒
$cleanupThreshold = 3600; // 1時間(必要に応じて調整)
$timeThreshold = $currentTime - $cleanupThreshold;
$removedCount = 0;
foreach ($rooms as $roomId => $room) {
if (isset($room['created_at']) && $room['created_at'] < $timeThreshold) {
unset($rooms[$roomId]);
$removedCount++;
}
}
// クリーンアップ後にファイルを保存(古いルームを削除した場合のみ)
if ($removedCount > 0) {
saveRooms($rooms);
}
return $removedCount;
}
$method = $_SERVER['REQUEST_METHOD'];
if ($method === 'OPTIONS') {
exit(0);
}
$rooms = loadRooms();
$removedCount = cleanupOldRooms($rooms);
// デバッグ用(本番環境では削除可能)
if ($removedCount > 0) {
error_log("Cleaned up {$removedCount} old rooms");
}
switch ($method) {
case 'GET':
$action = $_GET['action'] ?? '';
if ($action === 'get_room') {
$roomId = $_GET['room_id'] ?? '';
if (isset($rooms[$roomId])) {
echo json_encode(['success' => true, 'room' => $rooms[$roomId]]);
} else {
echo json_encode(['success' => false, 'message' => 'Room not found']);
}
} elseif ($action === 'list_rooms') {
echo json_encode(['success' => true, 'rooms' => $rooms]);
}
break;
case 'POST':
$input = json_decode(file_get_contents('php://input'), true) ?: [];
$action = $input['action'] ?? '';
if ($action === 'create_room') {
$roomId = $input['room_id'] ?? '';
$hostOnly = isset($input['host_only']) ? (bool)$input['host_only'] : false;
$hostToken = bin2hex(random_bytes(16));
if ($hostOnly) {
$hostId = $input['host_id'] ?? '';
$hostName = $input['host_name'] ?? '';
$rooms[$roomId] = [
'id' => $roomId,
'host' => $hostId,
'host_name' => $hostName,
'host_token' => $hostToken,
'players' => [],
'gameState' => [
'phase' => 'waiting',
'selectedHands' => [],
'playerCount' => 0
],
'created_at' => time()
];
} else {
// 従来: ホストも参加
$playerId = $input['player_id'] ?? '';
$playerName = $input['player_name'] ?? '';
$rooms[$roomId] = [
'id' => $roomId,
'host' => $playerId,
'host_name' => $playerName,
'host_token' => $hostToken,
'players' => [[
'id' => $playerId,
'name' => $playerName,
'isHost' => true
]],
'gameState' => [
'phase' => 'waiting',
'selectedHands' => [],
'playerCount' => 0
],
'created_at' => time()
];
}
saveRooms($rooms);
echo json_encode(['success' => true, 'room' => $rooms[$roomId]]);
} elseif ($action === 'join_room') {
$roomId = $input['room_id'] ?? '';
$playerId = $input['player_id'] ?? '';
$playerName = $input['player_name'] ?? '';
if (isset($rooms[$roomId])) {
// 既存プレイヤー重複チェック(playerIdで判定)
$exists = false;
foreach ($rooms[$roomId]['players'] as $p) {
if (isset($p['id']) && $p['id'] === $playerId) {
$exists = true;
break;
}
}
if (!$exists) {
$rooms[$roomId]['players'][] = [
'id' => $playerId,
'name' => $playerName,
'isHost' => false
];
saveRooms($rooms);
}
echo json_encode(['success' => true, 'room' => $rooms[$roomId]]);
} else {
echo json_encode(['success' => false, 'message' => 'Room not found']);
}
} elseif ($action === 'update_game_state') {
$roomId = $input['room_id'] ?? '';
$gameState = $input['game_state'] ?? [];
$hostToken = $input['host_token'] ?? '';
if (!isset($rooms[$roomId])) { echo json_encode(['success'=>false,'message'=>'Room not found']); break; }
if ($hostToken !== ($rooms[$roomId]['host_token'] ?? '')) { echo json_encode(['success'=>false,'message'=>'forbidden']); break; }
$rooms[$roomId]['gameState'] = $gameState;
saveRooms($rooms);
echo json_encode(['success' => true, 'room' => $rooms[$roomId]]);
} elseif ($action === 'select_hand') {
$roomId = $input['room_id'] ?? '';
$playerId = $input['player_id'] ?? '';
$hand = $input['hand'] ?? '';
if (!isset($rooms[$roomId])) { echo json_encode(['success'=>false,'message'=>'Room not found']); break; }
// プレイヤーIDからインデックスを特定
$players = $rooms[$roomId]['players'] ?? [];
$playerIndex = -1;
foreach ($players as $i => $p) {
if (isset($p['id']) && $p['id'] === $playerId) { $playerIndex = $i; break; }
}
if ($playerIndex === -1) { echo json_encode(['success'=>false,'message'=>'player not in room']); break; }
if (!isset($rooms[$roomId]['gameState'])) { $rooms[$roomId]['gameState'] = []; }
if (!isset($rooms[$roomId]['gameState']['selectedHands'])) { $rooms[$roomId]['gameState']['selectedHands'] = []; }
$rooms[$roomId]['gameState']['selectedHands'][(string)$playerIndex] = $hand;
saveRooms($rooms);
echo json_encode(['success' => true, 'room' => $rooms[$roomId]]);
} elseif ($action === 'start_countdown') {
$roomId = $input['room_id'] ?? '';
$hostToken = $input['host_token'] ?? '';
if (!isset($rooms[$roomId])) { echo json_encode(['success'=>false,'message'=>'Room not found']); break; }
if ($hostToken !== ($rooms[$roomId]['host_token'] ?? '')) { echo json_encode(['success'=>false,'message'=>'forbidden']); break; }
if (!isset($rooms[$roomId]['gameState'])) { $rooms[$roomId]['gameState'] = []; }
$rooms[$roomId]['gameState']['phase'] = 'countdown';
$rooms[$roomId]['gameState']['countdownAt'] = time();
saveRooms($rooms);
echo json_encode(['success' => true, 'room' => $rooms[$roomId]]);
} elseif ($action === 'game_finished') {
// ゲーム終了時にルームを削除(オプション)
$roomId = $input['room_id'] ?? '';
$hostToken = $input['host_token'] ?? '';
if (!isset($rooms[$roomId])) { echo json_encode(['success'=>false,'message'=>'Room not found']); break; }
if ($hostToken !== ($rooms[$roomId]['host_token'] ?? '')) { echo json_encode(['success'=>false,'message'=>'forbidden']); break; }
// ルームを削除
unset($rooms[$roomId]);
saveRooms($rooms);
echo json_encode(['success' => true, 'message' => 'Room deleted']);
}
break;
}
?>
セッション管理は以下
- ブラウザのlocalStorageに依存
- プライベートブラウジングでは動作しない場合がある
【ご注意】設置する場合のセキュリティ面など
プログラムは初心者のようなもの。何度も修正して、ようやく今の形になりました。
まだ変なところあるかも。。
AIに現在の動作環境と、ほか今後の改善点を聞きました。
APIエンドポイントの保護
- room_manager.phpが直接アクセス可能になっている
- 必要に応じて認証やレート制限を追加することを推奨
ホストトークンの管理
- 現在は単純なハッシュ値を使用
- より強固な認証システムへの移行を検討
依存関係
- jQuery 3.6.3以上
- モダンブラウザ(ES6対応)
サーバー環境
PHPの要件
- PHP 7.0以上が必要
- json、file_get_contents、file_put_contents関数が使用可能である必要
ファイル権限
- rooms.jsonファイルの書き込み権限が必要
- 適切なディレクトリ権限の設定
同時接続数
- 現在はファイルベースの保存
- 大量の同時アクセスには不向き
- 必要に応じてデータベースへの移行を検討してください
【AI】イラストを描いてもらった
今回の記事のキャッチ画像で使わせてもらいます「Google ImageFX」で作成した画像です。誰でもgoogleアカウントでログインして使えます。
この記事にピッタリなイラストのための考えたリクエストは、「サイバー空間でグーチョキパーのじゃんけんゲーム。突き出された手を上からの構図で、白熱の状況。全員女性で日本のアニメ風。」です。

このアプリでこんな風に白熱しますかね。
無理じゃないでしょうか。
星間旅路のメロディ
「宇宙の静けさに包まれながら、漂流する過去の音楽を捜し求め、銀河の奥底でその旋律に耳を傾ける。」
「この電波はどこの星からきたのだろうか。」
どこかで聞いたことがあるような。
楽しげで、元気が出そうですね。



