这是我第一个HTML5游戏的第一个版本。除了标准功能之外,我还添加了一个高分板,它使用本地存储来跟踪分数。
你可以用箭头键移动蛇。不像一般的游戏,跑到墙上不会杀死蛇,只有当它撞到自己的时候。这将显示“游戏超过”屏幕,在这一点上,您可以输入您的名字或匿名的'Noname‘。你可以随时查看高分,按下“高分”按钮。从那里你也可以回到主菜单,开始一个新的游戏。
我希望反馈意见集中在JavaScript,特别是在清洁和可读性方面,以及总体最佳实践方面。
另外,我不确定由于使用本地存储,堆栈代码段是否会正常运行,但如果正常运行,它可以正常工作。
"use strict";
(function (){
let canvas = document.getElementById('gameboard');
let scoreBoard = document.querySelector('.scoreboard');
let highscoreLabel = document.querySelector('.highscore');
let restartBtn = document.getElementById('restart');
let gameOverScreen = document.querySelector('.gameover-screen');
let finalScoreLabel = document.querySelector('.final-score');
let playerNameInput = document.querySelector('.player-name');
let highscoreBoardBtn = document.getElementById('highscores-btn');
let highscoresBoard = document.querySelector('.highscores-board');
let highscoresList = document.querySelector('.highscores-list');
let highscoresReturnBtn = document.getElementById('return-from-highscores');
canvas.width = 800;
canvas.height = 600;
let ctx = canvas.getContext('2d');
let isPlayerSaved,
state,
points,
playerName,
snakeBody,
direction,
blockSize,
snakeHead,
apple;
let init = function() {
canvas.style.display = "block";
gameOverScreen.style.display = "none";
isPlayerSaved = false;//this flag helps eliminate duplicates from highscore board;if it's value's true,same player can't be saved again
state = 'game';
points = 0;
playerName = '';
snakeBody = [{
x: 60,
y: 60,
color:'red',
lastPosX: null,
lastPosY: null
}];
for(let i = 0; i < 5; i++) {
snakeBody.push({color:'blue'});
}
direction = 'right';
blockSize = 20;
snakeHead = snakeBody[0];
apple = {
x: Math.floor(Math.random() * canvas.width),
y: Math.floor(Math.random() * canvas.height),
color: 'red',
};
}
init();
let draw = function(o) {
if(o.x != undefined || o.x != null) {
ctx.beginPath();
ctx.fillStyle = o.color;
ctx.fillRect(o.x, o.y, blockSize, blockSize);
}
}
let inputHandler = function(e) {
if(direction === 'right' && e.keyCode === 37) {
return;
}
else if(direction === 'left' && e.keyCode === 39) {
return;
}
else if(direction === 'up' && e.keyCode === 40) {
return;
}
else if(direction === 'down' && e.keyCode === 38) {
return;
}
switch (e.keyCode) {
case 38: /* Up arrow was pressed */
direction = 'up';
break;
case 40: /* Down arrow was pressed */
direction = 'down';
break;
case 37: /* Left arrow was pressed */
direction = 'left';
break;
case 39: /* Right arrow was pressed */
direction = 'right';
break;
}
}
let move = function() {
switch (direction) {
case 'left':
snakeHead.x -= blockSize;
break;
case 'right':
snakeHead.x += blockSize;
break;
case 'down':
snakeHead.y += blockSize;
break;
case 'up':
snakeHead.y -= blockSize;
break;
}
}
let update = function() {
scoreBoard.innerHTML = "Points: " + points;
//Calculate the highscore and display it
if(localStorage.getItem('highscores')) {
let storageHighscoresItemsArr = [];
let storageHighscoresItems = JSON.parse(localStorage.getItem('highscores'));
for(let i = 0; i < storageHighscoresItems.length; i++) {
storageHighscoresItemsArr.push(storageHighscoresItems[i].score);
}
let highscore = Math.max(...storageHighscoresItemsArr);
highscoreLabel.innerHTML = "Highscore: " + highscore;
}
//Snake's body movement
for(let i = 1; i < snakeBody.length; i++) {
snakeBody[i].x = snakeBody[i - 1].lastPosX;
snakeBody[i].y = snakeBody[i - 1].lastPosY;
snakeBody[i - 1].lastPosX = snakeBody[i - 1].x;
snakeBody[i - 1].lastPosY = snakeBody[i - 1].y;
}
}
let generateApple = function() {
apple = {
x: Math.floor(Math.random() * canvas.width),
y: Math.floor(Math.random() * canvas.height),
color: 'red',
};
let isAppleReachable = false;
let isAppleOutsideBody = function() {
for(let i = 0; i < snakeBody.length; i++ ) {
if((snakeBody[i].x === apple.x) && (snakeBody[i].y === apple.y)) {
return false;
}
}
return true;
}
while(isAppleReachable === false){
if(((apple.x % blockSize === 0) && (apple.y % blockSize === 0)) && isAppleOutsideBody()) {
isAppleReachable = true;
}
else {
apple.x = Math.floor(Math.random() * canvas.width);
apple.y = Math.floor(Math.random() * canvas.height);
}
}
}
let checkForColliions = function() {
if(snakeHead.x >= canvas.width) {
snakeHead.x = 0;
}
else if(snakeHead.x <= -10) {
snakeHead.x = canvas.width;
}
else if(snakeHead.y <= -10) {
snakeHead.y = canvas.height;
}
else if(snakeHead.y >= canvas.height) {
snakeHead.y = 0;
}
if((snakeHead.x === apple.x) && (snakeHead.y === apple.y)) {
points += 10;
generateApple();
snakeBody.push({color:'blue'});
}
for(let i = 1; i < snakeBody.length; i++) {
if((snakeHead.x === snakeBody[i].x) && (snakeHead.y === snakeBody[i].y)) {
state = 'gameOver';
}
}
}
let savePlayerToLocalStorage = function() {
if(isPlayerSaved === false) {
isPlayerSaved = true;
playerName = playerNameInput.value;
let highscores = [];
if(localStorage.getItem('highscores')) {
highscores = highscores.concat(JSON.parse(localStorage.getItem('highscores')));
}
highscores.push({name: playerName, score: points});
localStorage.setItem('highscores', JSON.stringify(highscores));
}
}
let loop = function() {
if(state === 'game') {
update();
ctx.clearRect(0, 0, canvas.width, canvas.height);
snakeBody.forEach( (s) => {
draw(s);
})
draw(apple);
for(let y = blockSize; y < 600; y += blockSize) {
ctx.beginPath();
ctx.moveTo(0,y);
ctx.lineTo(800, y);
ctx.stroke();
}
for(let x = blockSize; x < 800; x += blockSize) {
ctx.beginPath();
ctx.moveTo(x,0);
ctx.lineTo(x, 600);
ctx.stroke();
}
move();
checkForColliions();
setTimeout(loop,50);
}
else if(state === 'gameOver') {
canvas.style.display = "none";
gameOverScreen.style.display = "block";
highscoresBoard.style.display = "none";
finalScoreLabel.innerHTML = "Your score: " + points;
}
else if(state === 'highscores') {
gameOverScreen.style.display = 'none';
highscoresBoard.style.display = 'flex';
highscoresList.innerHTML = '';
let storageHighscoresItems = JSON.parse(localStorage.getItem('highscores'));
let storageHighscoresItemsArr = [];
for(let i = 0; i < storageHighscoresItems.length; i++) {
storageHighscoresItemsArr.push(storageHighscoresItems[i]);
}
storageHighscoresItemsArr.sort(function (a, b) {
if (a.score > b.score) {
return 1;
}
if (a.score < b.score) {
return -1;
}
return 0;
});
storageHighscoresItemsArr.reverse();
for(let i = 0; i < 10; i++) {
if(storageHighscoresItemsArr[i]) {
let li = document.createElement('li');
highscoresList.appendChild(li);
li.innerHTML = (storageHighscoresItemsArr[i].name || 'Noname') + " - " + storageHighscoresItemsArr[i].score;
}
}
}
}
loop();
generateApple()
addEventListener('keydown', e => inputHandler(e), true);
restartBtn.addEventListener('click', () => {
savePlayerToLocalStorage();
init();
generateApple();
loop();
})
highscoreBoardBtn.addEventListener('click', () => {
savePlayerToLocalStorage();
state = 'highscores';
loop();
})
highscoresReturnBtn.addEventListener('click', () => {
state = 'gameOver';
loop();
})
} ())html,
body {
margin: 0;
padding: 0;
overflow: hidden;
height: 100%;
}
.game-wrap {
height: 100%;
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.board {
background: white;
border: 2px black solid;
}
.label {
width: 800px;
display: flex;
flex-direction: row;
justify-content: space-between;
}
.gameover-screen {
height: 600px;
width: 800px;
background: red;
/*display: flex;
flex-direction: column;
justify-content: center;
align-items: center;*/
display: none;
}
.gameover-screen ul {
list-style: none;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.gameover-screen ul li {
margin-bottom: 50px;
}
.final-score {
font-size: 32px;
}
.highscores-board {
display: flex;
flex-direction: column;
align-items: center;
height: 600px;
width: 800px;
color: white;
background: black;
display: none;
}
.highscores-board ol {
margin-top: 35px;
}
.highscores-board ol li {
font-size: 36px;
margin-bottom: 10px;
}
#return-from-highscores {
width: 200px;
height: 100px;
}<!DOCTYPE html>
<html>
<head lang="en-Us">
<meta charset="utf-8">
<title>Snake</title>
<link rel="stylesheet" href="css/main.css">
</head>
<body>
</div>
<div class="game-wrap">
<div class="label">
<p class="scoreboard">Points: 0</p>
<p class="highscore">Highscore: 0</p>
</div>
<div class="gameover-screen">
<ul>
<li><p class="final-score">Your score: </p></li>
<li><label>Your name: </label>
<input class="player-name" type="text" maxlength="6"></li>
<li><button id="restart">Restart</button></li>
<li><button id="highscores-btn">Highscores</button></li>
</ul>
</div>
<div class="highscores-board">
<ol class="highscores-list"></ol>
<button id="return-from-highscores">Back</button>
</div>
<canvas id="gameboard" class="board"></canvas>
</div>
<script src="js/game.js"></script>
</body>
</html>发布于 2016-07-19 20:35:18

一般来说,这就是JS游戏架构的样子。当然,您可以让它与另一个体系结构一起工作,但是这个架构将提供良好的性能和响应能力。
您必须小心处理事件处理程序中的调用。您希望尽快释放事件处理程序-所以只调用快速的核心方法。
其他一切(比如保存到磁盘、网络io)都试图在核心线程/定时器中完成,例如,共享数据中的某个指示符应该触发进程。
关于特定时刻的评论:
1)全球功能
设generateApple =函数(){ ..}
当您将该代码放入全局范围时,它是相同的
函数generateApple() { ..}
在我看来后者的可读性更强。
2)全球职能2
你大部分时间都在使用全局函数--这让你可以从任何地方调用所有的东西,并使你的程序看起来像意大利面的弓。无意冒犯我喜欢它..。然而,从长远来看,您的代码很难阅读和维护。
我建议您至少在名称空间类中将代码分隔开。
变量Core={};Core.prototype.generateApple =函数(){ ..} Core.prototype.checkForCollisions =函数(){ ..} var UI = {};UI.prototype.draw = function() { ..}
这将使您能够分离关注点,并将帮助您更好地理解和维护自己的代码。
3)命名约定
函数循环(){ .} //预期内有一个循环。
尝试查找名称来表示内部代码所做的事情。如果代码正在处理用户输入,则将内容呈现到屏幕并更新高分表。就这么说吧。
handleUserInputRenderScreeContentAndUpdateHighScoreTable()
这应该会提示您在有目的的函数中中断由关注点分隔的代码。
4)事件处理程序
请注意-这些功能是在鼠标或按钮点击调用。如果您想要响应性的UI和快乐的用户保持他们的简短和快速。如果这需要时间,用户会期望在一个“请等待”的指示。
我很乐意在你申请这些校长之后再看一遍。
https://codereview.stackexchange.com/questions/135319
复制相似问题