
【ゲーム】JavaScript:12 七並べ
「🃏 七並べ 🃏」は、トランプ1デッキ(4種×13枚)を4人で配り、ダイヤの7から始めて各スートごとに順に1~6、8~13 を場に出していくカード配置ゲームです。最後まで手札を使い切るか、他プレイヤーが残り手札を失格したときが勝敗の決着となります。
遊び方と操作方法
- 初期化ボタンまたはページ読み込み時にゲームが開始し、全員に13枚ずつ配牌。
- ダイヤ7を持つプレイヤーが最初に自動で7を場に出し手札から除去。ダイヤ7を持っていたプレイヤーが最初に場にカードを出す権利が与えられます。
- 順番に自分の番が来たら、自分の手札の中から場に出せるカード(現在場のそのスートの最小値の1つ前、または最大値の1つ後)をクリックして出します。
- 出せるカードがない場合は「🛑 パス」を押し、最大3回までパス可能。4回目を超えると失格となり以後手番をパスします。
- 全員が出し切るか、残り生存プレイヤーが1名になると勝利。
ルール
- 場は各スートごとに連続した数値の範囲(初期は7のみ)。スートごとに最小値と最大値を管理。
- カードは「スート7」から出発し、以降は
min-1
またはmax+1
の数値のみ合法手。 - パスは各プレイヤー3回まで有効。4回目以降は失格となりゲームから除外。
- 最初に手札を使い切ったプレイヤー、または最後まで生存したプレイヤーが勝利。
🎮ゲームプレイ
以下のリンク先から実際にプレイできます。
12 七並べ
素材のダウンロード
以下のリンクから使用する素材をダウンロードできます。
sevens_bg.png |
---|
![]() |
ゲーム画面イメージ

プログラム全文(sevens.html)
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>🃏 七並べ 🃏</title>
<style>
/* 全体スタイル */
body {
margin: 0;
padding: 0;
font-family: 'Arial', sans-serif;
text-align: center;
/* 背景画像 */
background: url('sevens_bg.png') no-repeat center center fixed;
background-size: cover;
color: white;
}
/* タイトル・見出し */
h1, h2 {
display: inline-block;
background: rgba(0,0,0,0.6);
padding: 10px 20px;
border-radius: 8px;
margin: 20px 0;
text-shadow: 2px 2px 4px rgba(0,0,0,0.7);
}
h1 { font-size: 2.5em; }
/* 「場」の見出しを大きく */
#fieldTitle { font-size: 2.2em; }
h2:not(#fieldTitle) { font-size: 1.8em; }
/* フィールドのテーブル */
#field {
margin: 10px auto;
border-collapse: collapse;
background: rgba(0,0,0,0.6);
}
#field td, #field th {
width: 40px;
height: 30px;
border: 1px solid #ccc;
color: white;
text-align: center;
font-size: 0.9em;
}
#field th {
background: rgba(255,255,255,0.2);
}
/* 手札 */
.cards {
display: flex;
justify-content: center;
flex-wrap: wrap;
margin: 10px;
}
.card {
width: 60px;
height: 90px;
margin: 5px;
border-radius: 6px;
background-color: white;
color: black;
font-size: 16px;
line-height: 90px;
text-align: center;
font-weight: bold;
box-shadow: 0 4px 6px rgba(0,0,0,0.3);
cursor: pointer;
transition: transform 0.2s;
}
.card.red { color: red; }
.card.disabled {
opacity: 0.4;
cursor: not-allowed;
transform: none;
}
.card:hover:not(.disabled) {
transform: scale(1.1);
}
/* ボタン */
button {
padding: 8px 16px;
margin: 5px;
font-size: 14px;
font-weight: bold;
color: white;
background-color: #ff5733;
border: none;
border-radius: 4px;
cursor: pointer;
box-shadow: 0 4px 6px rgba(0,0,0,0.3);
transition: background-color 0.2s, transform 0.2s;
}
button:hover:not(:disabled) {
background-color: #ff2e00;
transform: scale(1.05);
}
button:disabled {
background-color: grey !important;
cursor: not-allowed;
opacity: 0.6;
transform: none !important;
}
#compCounts {
display: inline-block;
background: rgba(0,0,0,0.8);
padding: 12px 16px;
border-radius: 6px;
font-size: 1.5em;
text-shadow: 2px 2px 4px rgba(0,0,0,0.7);
color: #ffd700;
margin: 10px 0;
}
/* メッセージ */
#message {
display: inline-block;
margin: 20px;
padding: 10px 20px;
background: rgba(0,0,0,0.6);
border-radius: 5px;
font-size: 1.2em;
text-shadow: 2px 2px 4px rgba(0,0,0,0.7);
min-width: 200px;
}
</style>
</head>
<body>
<!-- タイトル -->
<h1>🃏 七並べ 🃏</h1>
<!-- フィールド -->
<div>
<h2 id="fieldTitle">🗺️ 場 🗺️</h2><br>
<table id="field"></table>
</div>
<!-- プレイヤー手札 -->
<div>
<h2>🧑 あなたの手札 🧑</h2>
<div class="cards" id="playerHand"></div>
<button id="passBtn" onclick="playerPass()">🛑 パス</button>
</div>
<!-- コンピュータ手札枚数 -->
<div>
<h2>💻 コンピュータの手札 💻</h2>
<p>
<div id="compCounts">
コンピュータ1: <span id="comp0Count">13</span>枚
コンピュータ2: <span id="comp1Count">13</span>枚
コンピュータ3: <span id="comp2Count">13</span>枚
</div>
</p>
</div>
<!-- メッセージ -->
<div id="message">ゲーム初期化中...</div>
<!-- もう一度ボタン -->
<button id="restartBtn" onclick="initGame()" style="display:none;">🔄 もう一度</button>
<script>
// 定数・変数
const suits = ['♠','♥','♦','♣'];
const maxPass = 3;
const numPlayers = 4;
const players = []; // { hand:[], pass:0, eliminated:false }
let deck = [];
const fieldState = {}; // suit -> {min, max}
let currentPlayer = 0;
let gameOver = false;
// プレイヤー初期化
for (let i = 0; i < numPlayers; i++) {
players.push({ hand: [], pass: 0, eliminated: false });
}
// ゲーム開始
window.onload = initGame;
function initGame() {
gameOver = false;
document.getElementById('restartBtn').style.display = 'none';
// フィールド初期化
for (let s of suits) fieldState[s] = { min: 7, max: 7 };
// デッキ作成・シャッフル
deck = createDeck();
shuffle(deck);
// 配布
for (let i = 0; i < numPlayers; i++) {
players[i].hand = deck.splice(0, 13);
players[i].pass = 0;
players[i].eliminated = false;
}
// 開始プレイヤー判定(ダイヤ7を持っていた人)
for (let i = 0; i < numPlayers; i++) {
if (players[i].hand.some(c => c.suit === '♦' && c.value === 7)) {
currentPlayer = i;
break;
}
}
// 全7を場に出して手札から除去
for (let i = 0; i < numPlayers; i++) {
players[i].hand = players[i].hand.filter(c => c.value !== 7);
}
// 描画
renderField();
renderHands();
updateCompCounts();
updateUI();
showMessage(`ゲーム開始! ${currentPlayer === 0 ? 'あなた' : 'コンピュータ'+currentPlayer} の番です`);
// AIの場合、自動で次のターン開始
if (currentPlayer !== 0) setTimeout(computerTurn, 800);
}
// デッキ生成
function createDeck() {
const d = [];
for (let s of suits) {
for (let v = 1; v <= 13; v++) {
d.push({ suit: s, value: v });
}
}
return d;
}
// シャッフル
function shuffle(d) {
for (let i = d.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[d[i], d[j]] = [d[j], d[i]];
}
}
// フィールド描画
function renderField() {
const tbl = document.getElementById('field');
tbl.innerHTML = '';
// ヘッダー行
let header = '<tr><th></th>';
for (let v = 1; v <= 13; v++) header += `<th>${v}</th>`;
header += '</tr>';
tbl.insertAdjacentHTML('beforeend', header);
// 各スート行
for (let s of suits) {
const { min, max } = fieldState[s];
let row = `<tr><th>${s}</th>`;
for (let v = 1; v <= 13; v++) {
row += (v >= min && v <= max)
? `<td>${v}</td>`
: `<td></td>`;
}
row += '</tr>';
tbl.insertAdjacentHTML('beforeend', row);
}
}
// 手札描画(プレイヤーのみ)
function renderHands() {
const container = document.getElementById('playerHand');
container.innerHTML = '';
// ソート:スート→数字
players[0].hand.sort((a,b) => {
const si = suits.indexOf(a.suit), sj = suits.indexOf(b.suit);
return si !== sj ? si - sj : a.value - b.value;
});
players[0].hand.forEach((card, idx) => {
const div = document.createElement('div');
div.className = 'card';
div.textContent = `${card.value}${card.suit}`;
if (card.suit === '♥' || card.suit === '♦') div.classList.add('red');
div.onclick = () => playerPlay(idx);
container.appendChild(div);
});
}
// コンピュータ手札枚数更新
function updateCompCounts() {
document.getElementById('comp0Count').textContent = players[1].hand.length;
document.getElementById('comp1Count').textContent = players[2].hand.length;
document.getElementById('comp2Count').textContent = players[3].hand.length;
}
// 合法手札インデックス取得
function legalMoves(hand) {
const moves = [];
hand.forEach((c, i) => {
const f = fieldState[c.suit];
if ((c.value === f.min - 1 && f.min > 1) ||
(c.value === f.max + 1 && f.max < 13)) {
moves.push(i);
}
});
return moves;
}
// プレイヤー手出し
function playerPlay(idx) {
if (gameOver || currentPlayer !== 0) return;
const moves = legalMoves(players[0].hand);
if (!moves.includes(idx)) {
showMessage('そのカードは出せません');
return;
}
playCard(0, idx);
nextTurn();
}
// プレイヤーパス
function playerPass() {
if (gameOver || currentPlayer !== 0) return;
players[0].pass++;
showMessage(`あなたはパス (${players[0].pass}/${maxPass})`);
if (players[0].pass > maxPass) {
eliminate(0);
}
nextTurn();
}
// カードを場に出す
function playCard(p, idx) {
const card = players[p].hand.splice(idx,1)[0];
const f = fieldState[card.suit];
if (card.value === f.min - 1) f.min--;
else f.max++;
renderField();
renderHands();
updateCompCounts();
showMessage(`${p===0?'あなた':'コンピュータ'+p} が ${card.value}${card.suit} を出しました`);
checkGameEnd();
}
// コンピュータターン
function computerTurn() {
if (gameOver) return;
const p = currentPlayer;
const moves = legalMoves(players[p].hand);
if (moves.length > 0) {
playCard(p, moves[0]);
} else {
players[p].pass++;
showMessage(`コンピュータ${p} はパス (${players[p].pass}/${maxPass})`);
if (players[p].pass > maxPass) eliminate(p);
}
nextTurn();
}
// 次のターン
function nextTurn() {
if (gameOver) return;
for (let i = 1; i <= numPlayers; i++) {
const np = (currentPlayer + i) % numPlayers;
if (!players[np].eliminated) {
currentPlayer = np;
break;
}
}
updateUI();
if (!gameOver) {
if (currentPlayer === 0) {
showMessage('あなたの番です');
} else {
setTimeout(computerTurn, 800);
}
}
}
// パス上限超過で失格
function eliminate(p) {
players[p].eliminated = true;
showMessage(`${p===0?'あなた':'コンピュータ'+p} は失格しました`);
checkGameEnd();
}
// ゲーム終了判定
function checkGameEnd() {
// 手札0枚の勝利
for (let i = 0; i < numPlayers; i++) {
if (!players[i].eliminated && players[i].hand.length === 0) {
gameOver = true;
showMessage(`${i===0?'あなた':'コンピュータ'+i} の勝利!`);
endGame();
return;
}
}
// 生存者1名の勝利
const alive = players.filter(p => !p.eliminated);
if (alive.length === 1) {
gameOver = true;
const winner = players.findIndex(p => !p.eliminated);
showMessage(`${winner===0?'あなた':'コンピュータ'+winner} の勝利!`);
endGame();
}
}
// 終了処理
function endGame() {
updateUI();
document.getElementById('restartBtn').style.display = 'inline-block';
}
// UI更新
function updateUI() {
document.getElementById('passBtn').disabled = (currentPlayer !== 0 || gameOver);
document.querySelectorAll('#playerHand .card').forEach((div,idx) => {
const legal = legalMoves(players[0].hand);
if (currentPlayer!==0 || gameOver || !legal.includes(idx)) div.classList.add('disabled');
else div.classList.remove('disabled');
});
}
// メッセージ表示
function showMessage(msg) {
document.getElementById('message').textContent = msg;
}
</script>
</body>
</html>
アルゴリズムの流れ
ステップ | 関数/命令 | 内容 |
---|---|---|
初期化 | initGame() | フィールド初期化・デッキ生成・シャッフル・配牌・先攻決定・7自動配置・UI描画・最初のプレイヤー実行 |
フィールド描画 | renderField() | HTMLテーブルに対し、各スートの min ~max のマスに数値を表示 |
手札描画 | renderHands() | 手札をソートし、カード要素を生成。クリックでプレイヤープレイ |
合法手判定 | legalMoves(hand) | 手札中、自分のスート min-1 または max+1 のカードインデックスを抽出 |
カード出し/場更新 | playCard(player,idx) | 手札からカードを削除→fieldState 更新→UI再描画→メッセージ |
パス/失格判定 | playerPass() /eliminate(p) | パス回数をカウントし、上限超過で eliminated=true 。以降スキップ |
ターン管理 | nextTurn() | 次の有効プレイヤーを currentPlayer に設定→UI更新→AIなら自動実行 |
勝敗判定 | checkGameEnd() | 手札ゼロまたは生存1名で終了→勝者メッセージ→endGame() |
メッセージ表示 | showMessage(msg) | 画面下部のメッセージエリアに文字列を表示 |
関数の詳細
関数名 | 機能 |
---|---|
initGame() | ゲームをリセットし、場とプレイヤー状態を初期化。最初の7を出し先攻プレイヤーを決定。UIを描画。 |
createDeck() | 4スート×1~13 のカードオブジェクト配列を生成 |
shuffle(d) | Fisher–Yates アルゴリズムで配列 d をランダム化 |
renderField() | <table> 要素にフィールド状態を反映。各スート行をヘッダー+数値セル or 空セルで描画 |
renderHands() | プレイヤーの手札をスート・数字順にソートし、.card 要素として表示。クリックで playerPlay() 呼び出し |
legalMoves(hand) | 与えられた手札配列中、自分の場と連続可能なカードインデックスの配列を返却 |
playCard(p,idx) | プレイヤー p の手札からインデックス idx のカードを場に出し、fieldState を更新 |
playerPass() | プレイヤーのパス回数をインクリメント。上限超過なら eliminate() 呼び出し |
computerTurn() | AIプレイヤーのターン実装:合法手を最初の1枚でプレイ、なければパス |
nextTurn() | 次の有効プレイヤーに交代し、自分/AIの動作を切り替え |
checkGameEnd() | 勝利条件(手札0枚 or 生存者1名)をチェックし、終了なら endGame() 呼び出し |
endGame() | gameOver=true に設定し、「もう一度」ボタンを表示 |
updateUI() | ボタンと手札の .disabled 状態を制御 |
showMessage(msg) | 画面下部のメッセージ要素に msg を表示 |
nameOf(i) | プレイヤー番号を「あなた」 or 「コンピュータn」にマッピング |
このゲームの改造ポイント
- AIの強化:現状は最初の合法手を出すだけ。ミニマックス法やヒューリスティックを導入して相手を邪魔する戦略AIに。
- 可視化演出:カードの出るアニメーション、パス時のエフェクト、勝利時の花火表示などで臨場感アップ。
- オンライン対戦:WebSocket で遠隔プレイヤー間のリアルタイム対戦を実装。
- 手札公開オプション:上級者向けに対戦相手の手札を一部表示するモードを追加。
- スートと数のカスタマイズ:トランプ以外のテーマ(麻雀牌やドミノなど)に応じてスート・数値を拡張。
- 観戦モード:第5人目として他プレイヤーの対戦を観戦可能にし、チャット機能やログ再生も実装。
この基本構造をベースに、ぜひオリジナリティあふれる「七並べ」を作り込んでみてください!