[想玩的遊戲,自己開發]Gemini快速開發一個復古版柏青哥 網頁遊戲

更新 發佈閱讀 43 分鐘

準備就寢前,突然想玩玩小時候的彈珠台 (柏青哥)遊戲,上網查了一下,居然找不到可以馬上開玩的柏青哥Game。
那只好借助AI 的生成式大神的力量,快速產生一個。
至Gemini (思考型)+建立圖像,
1.prompt:
產生如一張柏青哥 機台的圖,這張圖是一台非常經典的昭和時期(約 1970 年代)的「三洋(Sanyo)」手打式柏青哥機台,主題是「Three Ocean」。

它的最大特色在於:

左側發射軌道:球不是直接出現在上面,而是從左下角被「打」上去的。

特定的入賞口結構:中間有一個大役物(像是臉譜或飛機的造型),下方有三個類似鬱金香(Tulip)的開闔式接球座(黑/紅/白)。

手感控制:需要控制發射力道,太弱球上不去,太強會直接打到右邊死區。

產生一個火焰和鬱金香形象的機台。
產生的圖如下:

raw-image
  1. 將圖上傳至 gemini,加上 prompt:
    撰寫以上面圖像的柏青哥 機台網頁開發的遊戲
    <!DOCTYPE html>
    <html lang="zh-TW">
    <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>霓虹火焰柏青哥 (Neon Fire Pachinko)</title>
    <style>
    body {
    margin: 0;
    padding: 0;
    background-color: #050505;
    color: white;
    font-family: 'Segoe UI', Arial, sans-serif;
    display: flex;
    flex-direction: column;
    align-items: center;
    height: 100vh;
    overflow: hidden;
    }

    /* 機台外殼 */
    #game-wrapper {
    position: relative;
    margin-top: 20px;
    /* 模擬圖中的黑色鋼琴烤漆外框與紅色光暈 */
    border: 20px solid #111;
    border-radius: 30px 30px 10px 10px;
    box-shadow:
    0 0 10px #000,
    inset 0 0 50px rgba(255, 0, 50, 0.5), /* 內部紅光 */
    0 0 40px rgba(255, 0, 50, 0.3); /* 外部紅光 */
    background: #000;
    }

    /* 遊戲主畫面 - 模擬圖中的藍紅漸層 */
    #game-container {
    background: radial-gradient(circle at 50% 40%,
    #001a33 0%, /* 中心深藍 */
    #003366 30%, /* 中層藍 */
    #000000 60%, /* 過渡黑 */
    #660000 80%, /* 外層暗紅 */
    #ff0000 100% /* 邊緣亮紅 */
    );
    border-radius: 10px;
    overflow: hidden;
    }

    /* 閃爍特效 (中獎時觸發) */
    @keyframes flashEffect {
    0% { filter: brightness(1); }
    50% { filter: brightness(1.5) hue-rotate(90deg); }
    100% { filter: brightness(1); }
    }
    .flash {
    animation: flashEffect 0.2s linear 2;
    }

    /* 抬頭顯示器 (HUD) */
    #ui-layer {
    position: absolute;
    top: 20px;
    right: 20px;
    text-align: right;
    pointer-events: none;
    z-index: 10;
    }
    .hud-box {
    background: rgba(0, 0, 0, 0.6);
    border: 1px solid #00ccff;
    padding: 5px 15px;
    border-radius: 4px;
    margin-bottom: 5px;
    box-shadow: 0 0 10px #00ccff;
    }
    .hud-label { font-size: 10px; color: #00ccff; letter-spacing: 1px; }
    .hud-value { font-size: 24px; font-weight: bold; color: #fff; text-shadow: 0 0 5px #fff; }

    /* 控制面板 */
    #control-panel {
    margin-top: 20px;
    padding: 15px 30px;
    background: linear-gradient(to bottom, #222, #111);
    border-radius: 15px;
    border-top: 2px solid #333;
    display: flex;
    align-items: center;
    gap: 30px;
    box-shadow: 0 10px 20px rgba(0,0,0,0.8);
    }

    /* 力道旋鈕 */
    .knob-container {
    display: flex;
    flex-direction: column;
    align-items: center;
    }
    input[type=range] {
    width: 200px;
    height: 8px;
    -webkit-appearance: none;
    background: #333;
    border-radius: 5px;
    outline: none;
    border: 1px solid #555;
    }
    input[type=range]::-webkit-slider-thumb {
    -webkit-appearance: none;
    width: 24px;
    height: 24px;
    border-radius: 50%;
    background: #ff0055; /* 霓虹紅 */
    cursor: pointer;
    box-shadow: 0 0 10px #ff0055;
    border: 2px solid #fff;
    }

    /* 發射按鈕 */
    .launch-btn {
    width: 70px;
    height: 70px;
    border-radius: 50%;
    border: 3px solid #fff;
    background: radial-gradient(circle, #ff0000, #990000);
    color: white;
    font-weight: bold;
    font-size: 16px;
    cursor: pointer;
    box-shadow: 0 0 15px #ff0000;
    transition: transform 0.1s;
    text-shadow: 0 2px 0 #000;
    }
    .launch-btn:active {
    transform: scale(0.92);
    box-shadow: 0 0 5px #ff0000;
    }

    /* 下方紅色接球盤 */
    .tray {
    margin-top: -15px;
    width: 300px;
    height: 40px;
    background: linear-gradient(to bottom, #cc0000, #660000);
    border-radius: 0 0 20px 20px;
    border: 2px solid #440000;
    box-shadow: 0 5px 10px rgba(0,0,0,0.5);
    z-index: 5;
    }

    /* 得分飄浮字 */
    .score-popup {
    position: absolute;
    font-weight: 900;
    font-size: 28px;
    color: #fff;
    -webkit-text-stroke: 1px #ff0055;
    pointer-events: none;
    animation: floatUp 0.8s ease-out forwards;
    z-index: 100;
    text-shadow: 0 0 10px #ff0055;
    }
    @keyframes floatUp {
    0% { transform: translateY(0) scale(0.5); opacity: 0; }
    20% { transform: translateY(-10px) scale(1.2); opacity: 1; }
    100% { transform: translateY(-50px) scale(1); opacity: 0; }
    }
    </style>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.19.0/matter.min.js"></script>
    </head>
    <body>

    <div id="game-wrapper">
    <div id="ui-layer">
    <div class="hud-box">
    <div class="hud-label">WIN</div>
    <div class="hud-value" id="win-display">0</div>
    </div>
    <div class="hud-box">
    <div class="hud-label">CREDITS</div>
    <div class="hud-value" id="balls-display">100</div>
    </div>
    </div>
    <div id="game-container"></div>
    </div>

    <div class="tray"></div>

    <div id="control-panel">
    <div class="knob-container">
    <label style="color:#aaa; font-size:12px; margin-bottom:8px; letter-spacing:1px;">POWER CONTROL</label>
    <input type="range" id="power-slider" min="20" max="65" value="42" step="1">
    <div style="display:flex; justify-content:space-between; width:100%; font-size:10px; color:#555; margin-top:5px;">
    <span>MIN</span>
    <span>MAX</span>
    </div>
    </div>

    <button class="launch-btn" id="fire-btn" onclick="launchBall()">PUSH</button>
    </div>

    <script>
    const Engine = Matter.Engine,
    Render = Matter.Render,
    Runner = Matter.Runner,
    Bodies = Matter.Bodies,
    Composite = Matter.Composite,
    Events = Matter.Events,
    Body = Matter.Body,
    Vector = Matter.Vector;

    // 提高物理計算迭代次數,防止球高速穿牆
    const engine = Engine.create({
    positionIterations: 12,
    velocityIterations: 10
    });
    const world = engine.world;
    engine.gravity.y = 1.1; // 稍微增加重力,讓球掉落更有感

    const width = 450;
    const height = 650;

    const render = Render.create({
    element: document.getElementById('game-container'),
    engine: engine,
    options: {
    width: width,
    height: height,
    wireframes: false,
    background: 'transparent' // 使用 CSS 背景
    }
    });

    const walls = [];

    // --- 1. 機台邊界與導流結構 ---

    // 邊框
    walls.push(Bodies.rectangle(width/2, -60, width, 100, { isStatic: true, render: {fillStyle: '#111'} })); // 頂
    walls.push(Bodies.rectangle(width+15, height/2, 30, height, { isStatic: true, render: {fillStyle: '#111'} })); // 右
    walls.push(Bodies.rectangle(-15, height/2, 30, height, { isStatic: true, render: {fillStyle: '#111'} })); // 左

    // 左側發射軌道內壁 (半透明紫色,配合霓虹風格)
    const trackInner = Bodies.rectangle(55, height/2 + 80, 8, height - 160, {
    isStatic: true,
    render: { fillStyle: 'rgba(200, 0, 255, 0.4)' }
    });

    // **關鍵修正**:頂部導流三角 (Kicker)
    // 確保球撞到頂部後一定往右彈
    const topKicker = Bodies.rectangle(15, 15, 120, 20, {
    isStatic: true,
    angle: 0.7,
    render: { fillStyle: '#ff0055' } // 亮紅色
    });

    // 頂部圓弧軌道 (裝飾與物理)
    const topCurve = [];
    for(let i=4; i<18; i++) {
    let angle = Math.PI + (i * 0.1);
    let px = 200 + Math.cos(angle) * 160;
    let py = 200 + Math.sin(angle) * 160;
    if (px > 60 && px < 350 && py < 200) {
    // 使用發光的藍色小圓點模擬 LED
    topCurve.push(Bodies.circle(px, py, 4, {
    isStatic: true,
    render: { fillStyle: '#00ccff', strokeStyle: '#fff', lineWidth: 1 }
    }));
    }
    }

    // 底部死區 (Drain)
    const drain = Bodies.rectangle(width/2, height + 40, width, 60, { isStatic: true, isSensor: true, label: 'drain' });

    Composite.add(world, [...walls, trackInner, topKicker, ...topCurve, drain]);

    // --- 2. 釘子 (Nails) ---
    // 模擬釘子排列
    const pins = [];
    function createPin(x, y) {
    return Bodies.circle(x, y, 2, {
    isStatic: true,
    restitution: 0.5,
    render: { fillStyle: '#e6e6e6' } // 銀色釘子
    });
    }
    for(let row=0; row<14; row++) {
    for(let col=0; col<16; col++) {
    let x = 70 + col * 22; // 避開左側軌道
    let y = 140 + row * 25;
    if(row % 2 === 0) x += 11; // 交錯排列

    // 避開中央大獎區
    if (x > 140 && x < 310 && y > 200 && y < 380) continue;
    // 避開軌道區
    if (x < 65) continue;

    // 隨機微調位置,增加物理隨機性
    x += (Math.random() - 0.5) * 4;
    pins.push(createPin(x, y));
    }
    }
    Composite.add(world, pins);

    // --- 3. 入賞口 (Pockets) ---

    // 通用入賞口產生器
    function createPocket(x, y, type, score) {
    const color = type === 'fire' ? '#ff3300' : (type === 'tulip' ? '#ff0055' : '#00ccff');

    // 兩側擋板 (模擬鬱金香花瓣)
    const w1 = Bodies.rectangle(x - 14, y - 8, 6, 25, { isStatic: true, angle: -0.3, render: { fillStyle: color } });
    const w2 = Bodies.rectangle(x + 14, y - 8, 6, 25, { isStatic: true, angle: 0.3, render: { fillStyle: color } });

    // 底部感應器
    const sensor = Bodies.rectangle(x, y + 5, 20, 10, {
    isStatic: true, isSensor: true, label: 'pocket',
    plugin: { score: score, type: type },
    render: { fillStyle: '#000' }
    });

    // 裝飾光圈
    const deco = Bodies.circle(x, y-5, 12, {
    isStatic: true, isSensor: true,
    render: {
    fillStyle: color,
    opacity: 0.5
    }
    });

    return [w1, w2, sensor, deco];
    }

    // A. 中央火焰大獎 (Fire Center) - 圖中上方那個圓形
    const centerFeature = Bodies.circle(width/2, 280, 45, {
    isStatic: true, isSensor: true, label: 'pocket',
    plugin: { score: 100, type: 'fire' },
    render: {
    fillStyle: '#ff2200', // 火焰紅
    strokeStyle: '#ffcc00', // 金邊
    lineWidth: 4
    }
    });
    // 中央大獎周圍的保護釘與裝飾
    const centerGuardL = Bodies.rectangle(width/2 - 55, 280, 8, 60, { isStatic: true, angle: -0.1, render: {fillStyle: '#003366'} });
    const centerGuardR = Bodies.rectangle(width/2 + 55, 280, 8, 60, { isStatic: true, angle: 0.1, render: {fillStyle: '#003366'} });

    Composite.add(world, [centerFeature, centerGuardL, centerGuardR]);

    // B. 下方鬱金香 (Tulips) - 紅色
    Composite.add(world, createPocket(width/2, 530, 'tulip', 50)); // 正下方

    // C. 兩側小獎 (Wings) - 藍色
    Composite.add(world, createPocket(110, 450, 'wing', 15)); // 左
    Composite.add(world, createPocket(width - 110, 450, 'wing', 15)); // 右

    // --- 4. 遊戲邏輯 ---

    let balls = 100;
    let win = 0;
    const ballsDisplay = document.getElementById('balls-display');
    const winDisplay = document.getElementById('win-display');
    const fireBtn = document.getElementById('fire-btn');
    const gameContainer = document.getElementById('game-container');

    function launchBall() {
    if (balls <= 0) {
    alert("CREDITS EMPTY! (沒球了)");
    return;
    }

    balls--;
    ballsDisplay.innerText = balls;

    // 按鈕動畫
    fireBtn.style.transform = "scale(0.9)";
    setTimeout(() => fireBtn.style.transform = "scale(1)", 100);

    // 球體生成:銀色金屬質感
    const startX = 25;
    const startY = height - 80;
    const ball = Bodies.circle(startX, startY, 5.5, {
    restitution: 0.6,
    friction: 0.001, // 低摩擦
    frictionAir: 0.00, // 發射瞬間無空氣阻力
    density: 0.08, // 重金屬感
    label: 'ball',
    render: {
    fillStyle: '#eee',
    strokeStyle: '#999',
    lineWidth: 1
    }
    });

    Composite.add(world, ball);

    // 發射力道計算
    const sliderVal = parseFloat(document.getElementById('power-slider').value);
    // 加上隨機誤差模擬真實彈簧
    const randomVar = (Math.random() - 0.5) * 2;
    const power = sliderVal + randomVar;

    // 物理向量:強大的垂直力道 + 適度的水平推力
    const forceY = -(power * 0.00075); // 係數微調
    const velocityX = 4.0; // 強制水平速度,配合頂部 Kicker

    // 設定初速與空氣阻力恢復
    Body.setVelocity(ball, { x: velocityX, y: -20 }); // 給予初速防止卡住
    Body.applyForce(ball, ball.position, { x: 0, y: forceY });

    // 0.6秒後恢復空氣阻力,讓球掉落時比較自然
    setTimeout(() => {
    if(ball) ball.frictionAir = 0.01;
    }, 600);
    }

    // --- 5. 碰撞與計分系統 ---
    Events.on(engine, 'collisionStart', (event) => {
    event.pairs.forEach((pair) => {
    const bodyA = pair.bodyA;
    const bodyB = pair.bodyB;

    checkScore(bodyA, bodyB);
    checkScore(bodyB, bodyA);
    checkDrain(bodyA, bodyB);
    checkDrain(bodyB, bodyA);
    });
    });

    function checkScore(ball, target) {
    if (ball.label === 'ball' && target.label === 'pocket') {
    const points = target.plugin.score;
    const type = target.plugin.type;

    // 更新分數
    win += points;
    balls += points;
    winDisplay.innerText = win;
    ballsDisplay.innerText = balls;

    // 視覺特效
    triggerFlash();
    showPopup(ball.position.x, ball.position.y, points, type);

    // 移除球
    Composite.remove(world, ball);
    }
    }

    function checkDrain(ball, target) {
    if (ball.label === 'ball' && target.label === 'drain') {
    Composite.remove(world, ball);
    }
    }

    // 背景閃爍特效
    function triggerFlash() {
    gameContainer.classList.remove('flash');
    void gameContainer.offsetWidth; // 強制重繪
    gameContainer.classList.add('flash');
    }

    // 得分飄浮數字
    function showPopup(x, y, points, type) {
    const el = document.createElement('div');
    el.className = 'score-popup';
    el.innerText = points;

    // 根據類型設定顏色
    let color = '#fff';
    if (type === 'fire') color = '#ffcc00'; // 金色
    else if (type === 'tulip') color = '#ff0055'; // 紅色
    else color = '#00ccff'; // 藍色

    el.style.left = (x + 20) + 'px';
    el.style.top = (y - 20) + 'px';
    el.style.color = color;
    el.style.textShadow = `0 0 10px ${color}`;

    document.getElementById('game-wrapper').appendChild(el);
    setTimeout(() => el.remove(), 800);
    }

    // 鍵盤控制 (空白鍵)
    document.addEventListener('keydown', (e) => {
    if (e.code === 'Space') launchBall();
    });

    // 啟動引擎
    Render.run(render);
    const runner = Runner.create();
    Runner.run(runner, engine);

    </script>
    </body>
    </html>


用notepad 將網頁語法存成: game.html,
畫面如下:

raw-image


另一版本如下圖:

raw-image


結語

*Gemini產生這類小遊戲,幾乎一次搞定,很少會有語法錯誤的情形,
同樣用ChatGPT 就有點花時間,多次Prompt 語句修正和平台console除錯,才生成類似的結果。
*其實以工程師的角度,AI協助生成程式,幫了不少大忙,減少了不少除錯和系統分析的時間。

留言
avatar-img
Hank吳的沙龍
5會員
125內容數
這不僅僅是一個 Blog,更是一個交流與分享的空間。 期待在這裡與你相遇,一起探索科技、體驗生活、夢想旅行!💖
Hank吳的沙龍的其他內容
2025/12/09
這是在 Kaggle 上舉辦的 Google DeepMind - Vibe Code with Gemini 3 Pro 黑客松詳細資訊。 目前時間(2025 年 12 月 9 日)正值比賽期間,如果你有興趣參加,現在正是最後衝刺的關鍵時刻。
Thumbnail
2025/12/09
這是在 Kaggle 上舉辦的 Google DeepMind - Vibe Code with Gemini 3 Pro 黑客松詳細資訊。 目前時間(2025 年 12 月 9 日)正值比賽期間,如果你有興趣參加,現在正是最後衝刺的關鍵時刻。
Thumbnail
2025/12/03
Alpamayo-R1 是 NVIDIA(輝達)於 2025 年 12 月(NeurIPS 大會期間)最新發布的一款開源 AI 模型。 簡單來說,它是業界首款專為「自動駕駛研究」設計的推理型視覺-語言-動作模型 (Vision-Language-Action Model, VLAM)。
Thumbnail
2025/12/03
Alpamayo-R1 是 NVIDIA(輝達)於 2025 年 12 月(NeurIPS 大會期間)最新發布的一款開源 AI 模型。 簡單來說,它是業界首款專為「自動駕駛研究」設計的推理型視覺-語言-動作模型 (Vision-Language-Action Model, VLAM)。
Thumbnail
2025/12/02
「濃湯」用人哲學(Thick Soup Philosophy) 是 輝達(NVIDIA)創辦人兼執行長黃仁勳(Jensen Huang) 所提出的一種獨特人才管理與組織文化觀點。 這套哲學的核心在於 「拒絕末位淘汰制」,比起追求整齊劃一的菁英(清湯),他更傾向於打造一個包容多元、允許試錯
Thumbnail
2025/12/02
「濃湯」用人哲學(Thick Soup Philosophy) 是 輝達(NVIDIA)創辦人兼執行長黃仁勳(Jensen Huang) 所提出的一種獨特人才管理與組織文化觀點。 這套哲學的核心在於 「拒絕末位淘汰制」,比起追求整齊劃一的菁英(清湯),他更傾向於打造一個包容多元、允許試錯
Thumbnail
看更多