首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >HTML5蛇游戏

HTML5蛇游戏
EN

Code Review用户
提问于 2016-07-19 19:32:01
回答 1查看 1.1K关注 0票数 3

这是我第一个HTML5游戏的第一个版本。除了标准功能之外,我还添加了一个高分板,它使用本地存储来跟踪分数。

你可以用箭头键移动蛇。不像一般的游戏,跑到墙上不会杀死蛇,只有当它撞到自己的时候。这将显示“游戏超过”屏幕,在这一点上,您可以输入您的名字或匿名的'Noname‘。你可以随时查看高分,按下“高分”按钮。从那里你也可以回到主菜单,开始一个新的游戏。

我希望反馈意见集中在JavaScript,特别是在清洁和可读性方面,以及总体最佳实践方面。

另外,我不确定由于使用本地存储,堆栈代码段是否会正常运行,但如果正常运行,它可以正常工作。

代码语言: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();
})
} ())
代码语言:javascript
复制
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;
}
代码语言:javascript
复制
<!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>
EN

回答 1

Code Review用户

回答已采纳

发布于 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和快乐的用户保持他们的简短和快速。如果这需要时间,用户会期望在一个“请等待”的指示。

我很乐意在你申请这些校长之后再看一遍。

票数 2
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/135319

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档