看到網路上有人用一條指令就讓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>
沒有留言:
張貼留言