2023年4月8日 星期六

一條指令就能讓chatGPT寫出俄羅斯方塊遊戲網頁嗎?

 看到網路上有人用一條指令就讓chatGPT寫出俄羅斯方塊遊戲網頁,就想模仿學習一下,但實際上沒有這麼的容易(電腦王阿達:相關介紹)(Twitter:一條指令寫出俄羅斯方塊)(reddit:一個半小時寫出俄羅斯方塊)(遊戲連結)

我花了大約5小時,才從簡單從
1.能運作的魔術方塊
2.增加GAMEOVER顯示
3.讓色塊更明顯變成有方格狀
4.增加計分與等級功能
5.增加下一個方塊顯示
6.改變分數呈現方式等,直到完成目前這個作品

其中最困難的還是版面的排版溝通,因為目前的chatGPT4還不能有圖片辨識的功能,所以很難讓他了解想要的設計到底是什麼,所以要設計出一個可玩而且有娛樂性的網頁,還是沒有新聞媒體上說得容易,但在實作的過程中,的確可以慢慢學習如何除蟲與簡單的網頁書寫規則,滿建議想要認識一個遊戲如何成形,每個函式function功能要如何書寫,還有如何來回跟chatGPT溝通才會比較有效能,寫一個遊戲的確是一個很棒的練習方式。

以下是我完成的遊戲

https://sites.google.com/view/tn-dcjhs-it/ai%E8%AA%B2%E7%A8%8B/%E8%80%81%E5%B8%AB%E4%BD%9C%E5%93%81


---以下是相關的HTML---

<!DOCTYPE html>

<html lang="en">

<head>

  <meta charset="UTF-8">

  <meta name="viewport" content="width=device-width, initial-scale=1.0">

  <link href="https://fonts.googleapis.com/css?family=Orbitron&display=swap" rel="stylesheet">

  <title>Tetris</title>

  <style>

    body {

      justify-content: center;

      align-items: center;

      display: flex;

      height: 70vh;

      margin: 0;

      background-color: #222;

    }


#gameContainer {

  display: flex;

  align-items: center;

}


#gameArea {

  margin-right: 20px;

  align-items: center;

}


#scoreArea {

  background-color: #444;

  width: 150px; /* 設置寬度 */

  padding: 80px;

  border-radius: 5px;

}


#scoreDisplay {

  font-size: 30px;

  -webkit-text-stroke: 0.5px orange;

}


    #levelDisplay {

  font-size: 30px;

      -webkit-text-stroke: 0.5px green;

}


    #nextPieceContainer {

      width: 80px; /* 添加固定寬度 */

    }

#startButton {

  font-size: 20px;

      -webkit-text-stroke: 0.5px white; /* 描邊*/

}

    #nextPiece {

      position: static; /* 修改為 static */

      border: 1px solid #fff;

    }


    #keyDisplay {

      color: white;

  font-size: 20px; /* 設置字體大小 */

    }


    canvas {

      border: 1px solid #fff;

    }


    .techDisplay {

      font-family: 'Orbitron', sans-serif; /* 使用 Orbitron 字體 */

      font-weight: bold; /* 添加粗體 */

      background-clip: text; /* 文字作為背景裁剪區域 */

      -webkit-background-clip: text; /* 為 Chrome 和 Safari 瀏覽器添加背景裁剪 */

      color: black; /* 將s文字顏色設置為透明,顯示底色 */

      text-shadow: none; /* 移除文字陰影 */

    }


  </style>

</head>

<body>

<div id="gameContainer">

  <div id="gameArea">

<canvas id="tetris" width="240" height="400"></canvas>

<div id="keyDisplay" class="techDisplay">Q W to turn</div>

  </div>

  <div id="scoreArea">

<div id="infoDisplay">

  <div id="scoreDisplay" class="techDisplay">

    <div class="scoreLabel">Score:</div>

    <div class="scoreNumber">0</div>

  </div>

  <div id="levelDisplay" class="techDisplay">Level: 1</div>

  <div id="nextPieceContainer">

<canvas id="nextPiece" width="80" height="80"></canvas>

  <button id="startButton" class="techDisplay">START</button>

  </div>

</div>

  </div>

</div>




  <script>

const startButton = document.getElementById('startButton');

startButton.addEventListener('click', startGame);


    const canvas = document.getElementById('tetris');

    const ctx = canvas.getContext('2d');

    ctx.scale(20, 20);


const colors = [

  null,

  'red',

  'blue',

  'green',

  'yellow',

  'purple',

  'orange',

  'cyan',

];



    let dropCounter = 0;

    let dropInterval = 1000;

    let lastTime = 0;

    let gameOver = false;


    const arena = createMatrix(12, 20);

const levelUpScore = 200;


//設定遊戲

const player = {

  pos: {x: 0, y: 0},

  matrix: null,

  score: 0,

  level: 1,

};

//開始遊戲

function startGame() {

  playerReset();

  player.score = 0;

  player.level = 1; 

  gameOver = false;

  updateScoreDisplay();

  arena.forEach(row => row.fill(0));

  clearScreen();

  update();

}


function clearScreen() {

  ctx.fillStyle = '#000';

  ctx.fillRect(0, 0, canvas.width, canvas.height);

}

//計算得分

function updateScore(rowCount) {

  const points = [0, 10, 30, 50, 80];

  player.score += points[rowCount];


  // 更新等級

  const newLevel = Math.floor(player.score / levelUpScore) + 1;

  if (newLevel !== player.level) {

player.level = newLevel;

dropInterval = 1000 * Math.pow(0.5, player.level - 1); // 更新下降速度

  }


  updateScoreDisplay();

}

//創建一個新的方塊

function randomPiece() {

  const pieces = 'TOLJSIZ';

  return createPiece(pieces[pieces.length * Math.random() | 0]);

}


//掃描並消除完整的行,並更新得分

function arenaSweep() {

  let rowCount = 0;

  outer: for (let y = arena.length - 1; y > 0; --y) {

for (let x = 0; x < arena[y].length; ++x) {

  if (arena[y][x] === 0) {

continue outer;

  }

}


const row = arena.splice(y, 1)[0].fill(0);

arena.unshift(row);

++y;

rowCount++;

  }


  if (rowCount > 0) {

updateScore(rowCount);

  }

}

//顯示得分和等級

function updateScoreDisplay() {

  const scoreDisplay = document.getElementById('scoreDisplay');

  const levelDisplay = document.getElementById('levelDisplay');

  scoreDisplay.textContent = `Score: ${player.score}`;

  levelDisplay.textContent = `Level: ${player.level}`;

}


//檢查碰撞

function collide(arena, player) {

  const [m, o] = [player.matrix, player.pos];

  for (let y = 0; y < m.length; ++y) {

for (let x = 0; x < m[y].length; ++x) {

  if (m[y][x] !== 0 &&

  (arena[y + o.y] &&

  arena[y + o.y][x + o.x]) !== 0) {

return true;

  }

}

  }

  return false;

}


//創建矩陣用0填充

    function createMatrix(w, h) {

      const matrix = [];

      while (h--) {

        matrix.push(new Array(w).fill(0));

      }

      return matrix;

    }

//繪製競技場和方塊,更新得分

function draw() {

  ctx.fillStyle = '#000';

  ctx.fillRect(0, 0, canvas.width, canvas.height);

  drawMatrix(arena, { x: 0, y: 0 }, ctx); // 添加 ctx 參數

  drawMatrix(player.matrix, player.pos, ctx); // 添加 ctx 參數

  updateScoreDisplay();

}


//畫出簍空方塊

function drawMatrix(matrix, offset, context) {

  const borderWidth = 0.05; // 設定格線寬度


  matrix.forEach((row, y) => {

row.forEach((value, x) => {

  if (value !== 0) {

context.fillStyle = colors[value];

context.fillRect(

  x + offset.x + borderWidth,

  y + offset.y + borderWidth,

  1 - 2 * borderWidth,

  1 - 2 * borderWidth

);

  }

});

  });

}


//將方塊合併到遊戲

    function merge(arena, player) {

      player.matrix.forEach((row, y) => {

        row.forEach((value, x) => {

          if (value !== 0) {

            arena[y + player.pos.y][x + player.pos.x] = value;

          }

        });

      });

    }


//方塊下降、碰撞、合併

    function playerDrop() {

      player.pos.y++;

      if (collide(arena, player)) {

        player.pos.y--;

        merge(arena, player);

        playerReset();

        arenaSweep();

      }

      dropCounter = 0;

    }


//方塊X軸移動

    function playerMove(dir) {

      player.pos.x += dir;

      if (collide(arena, player)) {

        player.pos.x -= dir;

      }

    }


//創建方塊

    function createPiece(type) {

      if (type === 'T') {

        return [

          [1, 1, 1],

          [0, 1, 0],

          [0, 0, 0],

        ];

      } else if (type === 'O') {

        return [

          [2, 2],

          [2, 2],

        ];

      } else if (type === 'L') {

        return [

          [0, 3, 0],

          [0, 3, 0],

          [0, 3, 3],

        ];

      } else if (type === 'J') {

        return [

          [0, 4, 0],

          [0, 4, 0],

          [4, 4, 0],

        ];

      } else if (type === 'I') {

        return [

          [0, 5, 0, 0],

          [0, 5, 0, 0],

          [0, 5, 0, 0],

          [0, 5, 0, 0],

        ];

      } else if (type === 'S') {

        return [

          [0, 6, 6],

          [6, 6, 0],

          [0, 0, 0],

        ];

      } else if (type === 'Z') {

        return [

          [7, 7, 0],

          [0, 7, 7],

          [0, 0, 0],

        ];

      }

    }


//繪製遊戲結束

function drawGameOver() {

  ctx.fillStyle = 'white';

  ctx.font = '2px Arial';

  ctx.fillText('Game Over', 1, 12);

}



//重置方塊狀態

function playerReset() {

  const pieces = 'TOLJSIZ';

  player.matrix = createPiece(pieces[pieces.length * Math.random() | 0]);

  player.pos.y = 0;

  player.pos.x = (arena[0].length / 2 | 0) - (player.matrix[0].length / 2 | 0);


  if (collide(arena, player)) {

arena.forEach(row => row.fill(0));

player.score = 0;

drawGameOver();

gameOver = true;

updateScoreDisplay();

  }

}


//旋轉方塊、碰撞

    function playerRotate(dir) {

      const pos = player.pos.x;

      let offset = 1;

      rotate(player.matrix, dir);

      while (collide(arena, player)) {

        player.pos.x += offset;

        offset = -(offset + (offset > 0 ? 1 : -1));

        if (offset > player.matrix[0].length) {

          rotate(player.matrix, -dir);

          player.pos.x = pos;

          return;

        }

      }

    }


//旋轉90度

    function rotate(matrix, dir) {

      for (let y = 0; y < matrix.length; ++y) {

        for (let x = 0; x < y; ++x) {

          [matrix[x][y], matrix[y][x]] = [matrix[y][x], matrix[x][y]];

        }

      }


      if (dir > 0) {

        matrix.forEach(row => row.reverse());

      } else {

        matrix.reverse();

      }

    }


function update(time = 0) {

  if (gameOver) {

drawGameOver();

return;

  }

  

  const deltaTime = time - lastTime;

  lastTime = time;


  dropCounter += deltaTime;

  if (dropCounter > dropInterval) {

playerDrop();

  }


  draw();

  drawNextPiece();

  requestAnimationFrame(update);

}


// 新增變量以獲取下一個方塊的canvas

const nextPieceCanvas = document.getElementById('nextPiece');

const nextPieceCtx = nextPieceCanvas.getContext('2d');

nextPieceCtx.scale(20, 20);


// 新增變量以保存下一個要出現的方塊

let nextPiece = randomPiece();


// 顯示區域繪製下一個要出現的方塊

function drawNextPiece() {

  // 計算下一個方塊的邊界

  let minX = nextPiece[0].length, minY = nextPiece.length;

  let maxX = 0, maxY = 0;


  for (let y = 0; y < nextPiece.length; y++) {

for (let x = 0; x < nextPiece[y].length; x++) {

  if (nextPiece[y][x] !== 0) {

minX = Math.min(minX, x);

maxX = Math.max(maxX, x);

minY = Math.min(minY, y);

maxY = Math.max(maxY, y);

  }

}

  }


  // 計算偏移

  const offsetX = Math.floor((nextPieceCanvas.width / 20 - (maxX - minX)) / 2) - minX;

  const offsetY = Math.floor((nextPieceCanvas.height / 20 - (maxY - minY + 1)) / 2) - minY;



  nextPieceCtx.fillStyle = '#000';

  nextPieceCtx.fillRect(0, 0, nextPieceCanvas.width, nextPieceCanvas.height);

  drawMatrix(nextPiece, { x: minX ? offsetX : offsetX - 1, y: offsetY }, nextPieceCtx);

}




// 更新以顯示下一個方塊

function playerReset() {

  player.matrix = nextPiece;

  nextPiece = randomPiece();

  drawNextPiece();


  player.pos.y = 0;

  player.pos.x = (arena[0].length / 2 | 0) - (player.matrix[0].length / 2 | 0);


  if (collide(arena, player)) {

arena.forEach(row => row.fill(0));

player.score = 0;

drawGameOver();

gameOver = true;

updateScoreDisplay();

  }

}


    document.addEventListener('keydown', event => {

      if (event.keyCode === 37) {

        playerMove(-1);

      } else if (event.keyCode === 39) {

        playerMove(1);

      } else if (event.keyCode === 40) {

        playerDrop();

      } else if (event.keyCode === 81) {

        playerRotate(-1);

      } else if (event.keyCode === 87) {

        playerRotate(1);

      }   

    });


    playerReset();

    update();

  </script>

</body>

</html>

沒有留言:

張貼留言