我正在用javascript创建俄罗斯方块。我已经完成了基本的机械操作。我希望能得到一些关于我如何改进它的反馈。
向下,左,右箭头键移动该部分,而向上箭头键旋转它。它在全页模式下工作得最好。
"use strict";
const pieceData = [
{ // O
builds: [
[
[0, 0],
[1, 0],
[0, 1],
[1, 1],
],
],
type: "other",
},
{ // T
builds: [
[
[0, 1],
[1, 1],
[1, 2],
[2, 1],
],
[
[1, 0],
[1, 1],
[2, 1],
[1, 2],
],
[
[1, 0],
[0, 1],
[1, 1],
[2, 1],
],
[
[1, 0],
[1, 1],
[1, 2],
[0, 1],
],
],
type: "other",
},
{ // long bar
builds: [
[
[0, 0],
[1, 0],
[2, 0],
[3, 0],
],
[
[1, -1],
[1, 0],
[1, 1],
[1, 2],
],
],
type: "other",
},
{ // Z
builds: [
[
[0, 0],
[1, 0],
[1, 1],
[2, 1],
],
[
[1, -1],
[1, 0],
[0, 0],
[0, 1],
],
],
type: "left",
},
{ // S
builds: [
[
[1, 0],
[0, 0],
[0, 1],
[-1, 1],
],
[
[0, -1],
[0, 0],
[1, 0],
[1, 1],
],
],
type: "right",
},
{ // L
builds: [
[
[0, 0],
[0, 1],
[0, 2],
[1, 2],
],
[
[-1, 1],
[0, 1],
[1, 1],
[1, 0],
],
[
[-1, 0],
[0, 0],
[0, 1],
[0, 2],
],
[
[-1, 2],
[-1, 1],
[0, 1],
[1, 1],
],
],
type: "right",
},
{ // J
builds: [
[
[1, 0],
[1, 1],
[1, 2],
[0, 2],
],
[
[0, 0],
[0, 1],
[1, 1],
[2, 1],
],
[
[1, 0],
[0, 0],
[0, 1],
[0, 2],
],
[
[-1, 1],
[0, 1],
[1, 1],
[1, 2],
]
],
type: "left",
},
];
const canvas = document.getElementById("display");
const nextPieceDisplay = document.getElementById("next");
const npctx = nextPieceDisplay.getContext("2d");
const ctx = canvas.getContext("2d");
const width = canvas.width;
const height = canvas.height;
const npw = nextPieceDisplay.width;
const nph = nextPieceDisplay.height;
const colors = {
"left": "#118c8b",
"right": "#f14d49",
"other": "#bca18d",
}
class Block {
constructor(x, y, size, type) {
this.x = x;
this.y = y;
this.size = size;
this.type = type;
}
draw(loc) {
loc.fillStyle = colors[this.type];
loc.fillRect(this.x, this.y, this.size, this.size);
loc.strokeRect(this.x, this.y, this.size, this.size);
}
}
class Piece {
constructor(x, y, size, type, builds) {
this.x = x;
this.y = y;
this.size = size;
this.type = type;
this.builds = builds;
this.blocks = [];
this.current = randInt(0, this.builds.length);
this.swap(0);
}
draw(loc) {
this.blocks.forEach(block => {
block.draw(loc);
});
}
swap(dir) {
this.current += dir;
this.current = mod(this.current, this.builds.length);
let newBuild = [];
for (let i = 0; i < this.builds[this.current].length; ++i) {
let pos = this.builds[this.current][i];
newBuild.push(new Block(this.x + pos[0] * this.size, this.y + pos[1] * this.size, this.size, this.type));
}
this.blocks = newBuild;
}
move(x, y) {
this.x += x * this.size;
this.y += y * this.size;
this.blocks.forEach(block => {
block.x += x * this.size;
block.y += y * this.size;
});
}
}
class Board {
constructor(w, h, gridSize) {
this.active = undefined;
this.blocks = [];
this.grid = gridSize;
this.w = w;
this.h = h;
this.shouldLock = false;
this.score = 0;
this.level = 0;
this.nextPieceId = randInt(0, pieceData.length);
}
draw() {
ctx.clearRect(0, 0, this.w, this.h);
this.active.draw(ctx);
this.blocks.forEach(block => {
block.draw(ctx);
});
}
newPiece() {
let data = pieceData[this.nextPieceId];
this.active = new Piece(Math.round(this.w / 2) - this.grid, 0, this.grid, data.type, data.builds);
if (this.pieceBlocked(0, 0)) {
clearInterval(gameLoop);
alert(`Final score: ${this.score}`);
location.reload();
}
this.nextPieceId = randInt(0, pieceData.length);
let tempData = pieceData[this.nextPieceId];
let tempPiece = new Piece(50, 50, 25, tempData.type, tempData.builds);
npctx.clearRect(0, 0, npw, nph);
tempPiece.draw(npctx);
}
findXs(y) {
let xs = [];
for (let i = 0; i < this.blocks.length; ++i) {
if (this.blocks[i].y === y) {
xs.push(this.blocks[i].x);
}
}
return xs;
}
next() {
this.active.swap(1);
if (this.pieceBlocked(0, 0)) {
this.active.swap(-1);
}
else {
this.shouldLock = false;
}
}
findFulls() {
let fulls = [];
for (let y = 0; y < height; y += this.grid) {
let xs = this.findXs(y);
if (xs.length >= this.w / this.grid) {
fulls.push(y);
}
}
return fulls;
}
shift(ys) {
if (ys.length) {
let grid = this.grid;
this.blocks.forEach(block => {
let below = ys.filter(a => a > block.y);
block.y += below.length * grid;
});
}
}
update() {
let noFall = this.pieceBlocked(0, 1);
if (this.shouldLock) {
if (noFall) {
this.lock();
this.newPiece();
return;
}
this.shouldLock = false;
}
if (noFall) {
this.shouldLock = true;
}
else {
this.active.move(0, 1);
}
let fulls = this.findFulls();
for (let i = 0; i < this.blocks.length; ++i) {
if (fulls.includes(this.blocks[i].y)) {
this.blocks[i] = undefined;
}
}
let len = fulls.length;
this.blocks = this.blocks.filter(a => a);
this.shift(fulls);
if (len) {
this.score += len ** 2 * 100;
}
this.level = Math.floor(Math.sqrt(this.score / 1000));
document.getElementById("score").innerText = "Score: " + this.score.toString();
document.getElementById("level").innerText = "Level: " + this.level.toString();
this.draw();
}
handleMovement(x, y) {
if (!this.pieceBlocked(x, y)) {
this.active.move(x, y);
if (x === 0 && y === 1) {
this.score++;
this.shouldLock = true;
}
}
}
blocked(block, xVel, yVel) {
let x = block.x + xVel * this.grid;
let y = block.y + yVel * this.grid;
if (x < 0 || x >= this.w || y >= this.h) {
return true;
}
for (let i = 0; i < this.blocks.length; ++i) {
if (x == this.blocks[i].x && y == this.blocks[i].y) {
return true;
}
}
}
pieceBlocked(xVel, yVel) {
for (let i = 0; i < this.active.blocks.length; ++i) {
if (this.blocked(this.active.blocks[i], xVel, yVel)) {
return true;
}
}
return false;
}
lock() {
this.blocks = this.blocks.concat(this.active.blocks);
this.active = undefined;
this.score += 10;
}
move(x, y) {
if (!this.pieceBlocked(x, y)) {
this.active.move(x, y);
}
}
}
function mod(n, m) {
return ((n % m) + m) % m;
}
function randChoice(arr) {
let rand = randInt(0, arr.length);
return arr[rand];
}
function randInt(min, max) {
return Math.floor(Math.random() * (max - min)) + min;
}
const board = new Board(width, height, 25);
let lastLevel = 0;
board.newPiece();
function game() {
board.update();
if (board.level != lastLevel) {
clearInterval(gameLoop);
lastLevel = board.level;
gameLoop = setInterval(game, (1 / lastLevel) * 500)
}
}
let gameLoop = setInterval(game, 500);
addEventListener("keydown", e => {
switch (e.code) {
case "ArrowUp":
board.next();
break;
case "ArrowDown":
board.handleMovement(0, 1);
break;
case "ArrowLeft":
board.handleMovement(-1, 0);
break;
case "ArrowRight":
board.handleMovement(1, 0);
break;
}
board.draw();
});html {
height: 100%;
display: grid;
}
body {
margin: auto;
}
#display {
border: 3px solid black;
float: left;
}
#info {
float: right;
margin: 10px;
}<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>Tetris</title>
</head>
<body>
<canvas id="display" width="250" height="450"></canvas>
<div id="info">
<p id="score">Score: 0</p>
<p id="level">Level: 0</p>
<p>Next piece: </p>
<canvas id="next" width="200" height="200"></canvas>
</div>
</body>
</html>发布于 2019-05-25 13:31:01
例如
const COLORS = {
left: "#118c8b",
right: "#f14d49",
other: "#bca18d",
}this令牌的需求(代码中有大约120个this令牌)。示例几乎与Block类相同,但看不到this。
function Block(x, y, size, type = "other") {
const FILL_STYLE = COLORS[type];
return {
get x() { return x },
get y() { return y },
draw(ctx) {
ctx.fillStyle = FILL_STYLE;
ctx.fillRect(x, y, size, size);
ctx.strokeRect(x, y, size, size);
},
move(addX, addY) {
x += addX * size;
y += addY * size;
},
};
}
const b = new Block(x, y, size, type); // instantiate
// or you no longer need the new token
const b = Block(x, y, size, type);
b.move(1, 0); // move right
filter(a => a < b.y); // access getter例如,下面的代码片段从字符串构建片段
const O_PIECE = "####";
const T_PIECE = "### # ";
const BAR_PIECE = "####";
const Z_PIECE = " # ###";
const S_PIECE = " #### ";
const pieceData = [
createPiece(O_PIECE,2),
createPiece(T_PIECE,3),
createPiece(S_PIECE,3),
createPiece(Z_PIECE,2),
createPiece(BAR_PIECE,4),
];
function createPiece(layout, width, type = "other") {
const height = layout.length / width;
var i = layout.length;
const build = []
while(i--) {
if(layout[i] === "#") {
const x = i % width;
const y = i / width | 0;
build.push([x, y]);
}
}
return {
builds: [build],
type,
};
}将您的实用程序代码放在一起,这样您就可以在开发其他应用程序时构建一个库。
const gMath = {}; // g for game maths
gMath.mod = (n, m) => (n % m + m) % m;
gMath.randInt = (min, max = min + (min = 0)) => Math.floor(Math.random() * (max - min) + min);
gMath.randItem = arr => arr[gMath.randInt(arr.length)];虽然这个游戏不需要一个高的帧速率,但最好还是让你的渲染与显示同步。使用requestAnimationFrame,您可以创建一个以60 per (每秒帧)运行的呈现循环。
若要控制游戏渲染速率,请使用帧计数器和渲染速率,以便游戏呈现任意n个帧。
requestAnimationFrame(mainLoop); // will start the animation loop when execution is idle
var frameCounter = 0;
var gameSpeed = 30; // Render every 30 frame or 1/2 second
var updateRender = true; // when true will re-render the game
function mainLoop(time) { // time is a high resolution time supplied by the DOM
// call IO code here
if (frameCounter % gameSpeed === 0) {
updateGame(); // call the game update
updateRender = true;
}
if (updateRender) {
updateRender = false;
draw(); // draw the game
}
frameCounter ++;
requestAnimationFrame(mainLoop);
}尝试将IO代码与游戏逻辑分开。使用键盘和鼠标/触摸等IO事件来设置IO对象的状态。使用IO对象接口控制游戏
举例说明。键状态IO.keys.为true,而键已关闭,如果不是,则为false。他们移除自动重复。
const IO = {
keys: { // list only the keys you need
ArrowLeft: false,
ArrowRight: false,
ArrowDown: false,
Escape: false, // reset or end game
},
clear() {
for(const key of Object.keys(IO.keys) { IO.keys[key] = false }
},
keyboard(event) {
if (IO.keys[event.keyCode] !== undefined) {
IO.keys[event.keyCode] = event.type === "keydown";
event.preventDefault();
}
}
}
addEventListener("keydown", IO.keyboard);
addEventListener("keyup", IO.keyboard);将绑定IO状态用于游戏操作,以便无需进入游戏代码就可以重新配置IO。还允许您将其他IO类型(如点击或点击)绑定到游戏操作中。
const GAME_DOWN = "ArrowDown"; // bind key to action
const GAME_LEFT = "ArrowLeft"; // bind key to action
const GAME_RIGHT = "ArrowRight"; // bind key to action
const KEY_REPEAT_RATE = 20; // every 20 frames 5 times a second
var rightRepeat = 0;
function doInput() {
// Action while key is down
if (IO.keys[GAME_LEFT]) {
/* do stuff */
}
// Action only once per key down
if (IO.keys[GAME_DOWN]) {
IO.keys[GAME_DOWN] = false; // turn off key.
/* do stuff */
}
// Action repeats on key down
if (IO.keys[GAME_RIGHT]) {
if (rightRepeat === 0) {
rightRepeat = keyRepeatRate;
/* do stuff */
}
rightRepeat--; // count down to next repeat
} else {
rightRepeat = 0; // reset repeat
}
// when changing levels clear the input state so if the user
// is holding a key down it does not effect the new level or game
IO.clear();
}不要忘记,很多设备都是触控的,而且没有键盘。
当它的时间来炫耀你的游戏,你将需要它很容易被插入到第三方网页。
为了确保没有问题,封装整个游戏,使其在全球范围内没有内容。这可以作为一个调用其自身的函数来完成。IIF (立即调用函数)
;((container)=> {
"use strict";
/*
All game code in here
*/
})(document.body); // the container for your game封装的一部分也是DOM内容,您不能依赖托管游戏的站点来提供内容,所以请尝试使游戏独立运行(无需链接到您宿主的站点以提供内容)。
const gDOM = {}; // g for game DOM
gDOM.create = (tag, props = {}) => Object.assign(document.createElement(tag), props);
gDOM.append = (parent, ...children) => children.reduce((p, c) => (p.appendChild(c), p), parent);用法
const styles = `
tetris--score-display {
/* add style stuff
}
/* and so on */
`;
gDOM.append(container, gDOM.create("style", {innerHTML: styles}));
const canvas = gDOM.create("canvas", {width: gameWidth, height: gameHeight});
const block = gDOM.create("canvas", {width: blockWidth, height: blockHeight});
const score = gDOM.create("div", {textContent: "Score:", className: "tetris--score-display"});
const level = gDOM.create("div", {textContent: "Level:", className: "tetris--level-display"});
const ctx = canvas.getContext("2d");
const bctx = block.getContext("2d");
gDOM.append(container, canvas, block, score, level);
// To set score or level
score.innerText = "Score: " + this.score; // Note you had this.score.toString() the toString is autonomic
level.innerText = "Level: " + this.level;使您的游戏响应空间可用。尽可能多地填充空间,你不希望你的游戏成为4K屏幕上的一个小盒子,更糟糕的是,你不希望你的游戏超出可观看的区域。
不要担心高分辨率设备具有高端GPU的性能,但是始终将画布分辨率设置为与显示的大小相匹配,这样就不会对GPU进行过多的煮熟。
https://codereview.stackexchange.com/questions/220850
复制相似问题