首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >第一款HTML5帆布游戏攻略15

第一款HTML5帆布游戏攻略15
EN

Code Review用户
提问于 2017-02-12 04:14:42
回答 2查看 1.7K关注 0票数 6

这是我第一次用画布制作的游戏,目前它运行良好,但我知道我的JavaScript可能会更好,因为我还在学习。我可以对我的JavaScript的结构做什么改进?

代码语言:javascript
复制
document.addEventListener("DOMContentLoaded", function() {

  var c = document.getElementById("board");
  var ctx = c.getContext("2d");
  c.width = 400;
  c.height = 400;

  var map = [
    [4,9,12,8],
    [6,15,3,11],
    [7,0,1,10],
    [13,14,2,5]
  ];

  var winMap = [
    [1,2,3,4],
    [5,6,7,8],
    [9,10,11,12],
    [13,14,15,0]
  ];

  var tileMap = [];

  var tile = {
  width : 100,
  height : 100
  };

  var pos = {
    x : 0,
    y : 0,
    textx : 45,
    texty : 55
  };

  var drawTile = function(){
    ctx.fillStyle = '#EB5E55';
    ctx.shadowColor = '#000';
    ctx.shadowBlur = 4;
    ctx.shadowOffsetX = 0;
    ctx.shadowOffsetY = 2;
    ctx.fillRect(pos.x + 5,pos.y + 5,tile.width-10,tile.height-10);
    ctx.shadowColor = "transparent";
    ctx.fillStyle = '#FFFFFF';
    ctx.font = "20px Arial";
    //adjust center for larger numbers
    if(map[i][j] >= 10){
      ctx.fillText(map[i][j], pos.textx -2 , pos.texty);
    }else{
      ctx.fillText(map[i][j], pos.textx + 2 , pos.texty);
    }
  };

  var buildBoard = function(){
    for(i = 0; i < map.length; i++){

      tileMap[i] = [];

      for(j = 0; j < map[i].length; j++){
        var currentTile = {
            tileName : map[i][j],
            x : pos.x,
            y : pos.y,
            width : 100,
            height : 100,
            tileIndex : j
          };

        if(map[i][j] !== 0){
          //create our numbered box
          drawTile();
          //push box id and cords to tilemap array
          tileMap[i].push(currentTile);

        }else{
          //create our zero box
          tileMap[i].push(currentTile);
        }
        pos.textx += 100;
        pos.x += 100;
      }
      pos.x = 0;
      pos.textx = 43;
      pos.texty += 100; 
      pos.y += 100;
    }
  };

  //get mouse position
  function getPosition(event){

    var x = event.x;
    var y = event.y;
    x -= c.offsetLeft;
    y -= c.offsetTop;

    //Check to see which box we are in
    for(var i = 0; i < tileMap.length; i++){
      for(var j = 0; j < tileMap[i].length; j++){
        if(y > tileMap[i][j].y && y < tileMap[i][j].y + tileMap[i][j].height &&
          x > tileMap[i][j].x && x < tileMap[i][j].x + tileMap[i][j].width){
              checkMove(tileMap[i][j].tileName, tileMap[i][j].tileIndex);
           }
      }
    }
  }


  // detect if move is possible
  var checkMove = function(item, itemIndex){
    //check column for zero and clicked box
    var checkColumn = function(){
      var zeroIndex = null;
      //check for zero
      for(var x = 0; x < map.length; x++){
          zeroIndex = map[x].indexOf(0);
        if(zeroIndex > -1){
          zeroIndex = zeroIndex;
          break;
        }
      }
      if(zeroIndex === itemIndex){
        //create a new array with column values
        var tempArr = [];
        for(var i = 0; i < map.length; i++){
          tempArr.push(map[i][zeroIndex]);
        }
        //keep track of our clicked item and zero
        var zero = tempArr.indexOf(0);
        var selectedItem = tempArr.indexOf(item);

        //sort our tempArray
        if (selectedItem >= tempArr.length) {
          var k = selectedItem - tempArr.length;
          while ((k--) + 1) {
            map[i].push(undefined);
          }
        }
        tempArr.splice(selectedItem, 0, tempArr.splice(zero, 1)[0]);

        //update our map with the correct values for the column
        for(var l = 0; l < map.length; l++){
          map[l][zeroIndex] = tempArr[l];
        }
      }
    };


    //check row for zero and clicked box
    var checkRow = function(){
      for(var i = 0; i< map.length; i++){
        var itemIndex = map[i].indexOf(item);
        var zeroIndex = map[i].indexOf(0);
        //if zero and clicked box are present in same row
        if(itemIndex > -1 && zeroIndex > -1){
          //reorder row
          if (itemIndex >= map[i].length) {
            var k = itemIndex - map[i].length;
            while ((k--) + 1) {
              map[i].push(undefined);
            }
          }
          map[i].splice(itemIndex, 0, map[i].splice(zeroIndex, 1)[0]);
        }
      }
    };

    checkColumn();
    checkRow();

    clear();
  };

  var clear = function(){
    ctx.clearRect(0, 0, 400, 400);
    pos = {
      x : 0,
      y : 0,
      textx : 46,
      texty : 55
    };
    buildBoard();
    checkWin();
  };

  var checkWin = function(){
    var allMatch = true;
    for(var i = 0; i < winMap.length; i++){
      for(var j = 0; j < winMap[i].length; j++){
        if(map[i][j] !== winMap[i][j]){
          allMatch = false;
        }
      }    
    }
    if(allMatch){
      var winMessage = document.querySelector('.win');
      winMessage.classList.remove('hide');
      winMessage.classList.add('fall');
    }
  }

  buildBoard();
  c.addEventListener("mousedown", getPosition, false);
});
代码语言:javascript
复制
<canvas id="board"></canvas>

查看码笔

EN

回答 2

Code Review用户

回答已采纳

发布于 2017-04-22 01:43:42

你的拼图看上去不错!下面是关于您的代码的一些建议:

标准

您的getPosition函数访问非标准属性,并且无法工作,例如在火狐中。遵循这个建议并编写

代码语言:javascript
复制
let rect = canvas.getBoundingClientRect();
let x = event.clientX - rect.left;
let y = event.clientY - rect.top;

范围

从函数中删除对全局(如cmap )的引用,并通过参数传递它们。

将所有不直接依赖于DOMContentLoaded事件的代码从一个大型事件侦听器中移出。

State

将游戏状态与UI属性分开。例如,瓷砖的宽度和高度是它们在屏幕上的视觉表示的属性,不应该是板子模型的一部分。

另外,将处理游戏状态更新的逻辑与处理UI更新的逻辑分开。例如,介绍一个板模型和在该模型上运行的函数,如pushisSolved等,并使用观察者设计模式听取UI更新的状态更改。

你的董事会代表是多余的,而且比必要的更复杂。简单地将数字存储在二维数组中的特定列和行(类似于mapwinMap数组)就足够了。

考虑通过连接行或列将您的二维数组扁平成一个大的一维数组。这简化了数组的创建、复制和比较,并提高了性能。例如,您的checkWin逻辑可以重写为

代码语言:javascript
复制
map.length == winMap.length && map.every((tile, i) => tile == winMap[i])

还要考虑显式地跟踪和存储空瓷砖的坐标。这在一定程度上简化和概括了您的代码。

然后,示例性板状态将由一个具有宽度、高度、瓷砖和空瓷砖的x和y坐标的板组成,例如:

const board = {width: 2, height: 2, tiles: [3, 1, 0, 2], x: 0, y: 1}

这是有利的,使您的董事会状态不变。然后,通过推送瓷砖进行状态更改,需要用更新的瓷砖覆盖旧的瓷砖数组,而不是直接对其进行变异。在x,y位置移动或推开瓷砖时,可以使用以下有效函数:

代码语言:javascript
复制
function pushTile(board, x, y) {
  if ((x == board.x) == (y == board.y)) return board;

  let tiles = board.tiles.slice();
  let start = board.x + board.y * board.width;
  let stop  = x + y * board.width;
  let step  = y == board.y ? 1 : board.width;

  if (start > stop) step = -step;

  for (let i = start; i != stop; i += step) {
    [tiles[i + step], tiles[i]] = [tiles[i], tiles[i + step]];
  }
  return Object.assign({}, board, {tiles: tiles, x: x, y: y});
}

命名

选择描述性名称。将c转换为canvas。分开并清楚地指出关切的问题。例如,您的函数checkWin应该只检查谜题是否已经解决,而不是执行其他任务,例如显示消息。checkMove的情况也是如此。

可选

useCapture参数在addEventListener("mousedown", click, false)中是默认的false,您可以忽略它。

用户界面

您的用户界面可以通过用样式化的DOM元素替换<canvas>来简化每个单独的平铺。您的浏览器非常擅长事件处理和鼠标坐标映射,所以请利用这一点。

使用react.js --一个在(UI)状态更改时处理和最小化DOM更新的已建立的库--我们得到以下代码:

代码语言:javascript
复制
// Compute new board with tiles at x, y pushed towards the empty space:
function pushTile(board, x, y) {
  if ((x == board.x) == (y == board.y)) return board;
  
  let tiles = board.tiles.slice();
  let start = board.x + board.y * board.width;
  let stop  = x + y * board.width;
  let step  = y == board.y ? 1 : board.width;

  if (start > stop) step = -step;

  for (let i = start; i != stop; i += step) {
    [tiles[i + step], tiles[i]] = [tiles[i], tiles[i + step]];
  }
  return Object.assign({}, board, {tiles: tiles, x: x, y: y});
}

// Compare board tiles with a given solution:
function isSolved(board, solution) {
  return board.tiles.every((tile, i) => tile == solution[i]);
}

// Retrieve value of tile at x, y:
function tileAt(board, x, y) {
  return board.tiles[x + y * board.width];
}

// React component displaying a single tile:
function Tile(props) {
  const className = props.isSpace ? "tile tile-space" : "tile";
  return (
    <button className={className} onMouseDown={props.onMouseDown}>
      {props.value}
    </button>
  );
}

// React component displaying a grid of tiles: 
function Tiles(props) {
  let rows = [];
  for (let y = 0; y < props.board.height; y++) {
    let row = [];
    for (let x = 0; x < props.board.width; x++) {
      row.push(
        <Tile
          value={tileAt(props.board, x, y)}
          isSpace={props.board.x == x && props.board.y == y} 
          onMouseDown={() => props.onMouseDown(x, y)}
        />
      );
    }
    rows.push(<div className="tiles-row">{row}</div>);
  }
  return (<div className="tiles">{rows}</div>);
}

// React component displaying a complete puzzle:
class Puzzle extends React.Component {
  constructor(props) {
    super(props);
    
    const board = {
      tiles: props.tiles,
      width: props.width,
      height: props.height,
      x: props.x,
      y: props.y
    };
    this.solution = props.solution;
    this.state = {board: board, solved: isSolved(board, this.solution)}
  }
  handleMouseDown(x, y) {
    let board = pushTile(this.state.board, x, y);
    this.setState({board: board, solved: isSolved(board, this.solution)});
  }
  render() {
    return (
      <div className="puzzle">
        <Tiles
          board={this.state.board}
          onMouseDown={(x, y) => this.handleMouseDown(x, y)}
        />
        {this.state.solved && <span className="puzzle-solved">You won!</span>}
      </div>
    );
  }
}

// Initialise and render the puzzle:
ReactDOM.render(
  <Puzzle
    tiles={[
      4,  9,  12, 8,
      6,  15, 3,  11,
      7,  0,  1,  10,
      13, 14, 2,  5
    ]}
    solution={[
      1,  2,  3,  4,
      5,  6,  7,  8,
      9,  10, 11, 12,
      13, 14, 15, 0
    ]}
    width={4} height={4}
    x={1} y={2}
  />,
  document.getElementById("root")
);
代码语言:javascript
复制
.puzzle {
  position: relative;
  display: inline-block;
  text-align: center;
  background-color: #3A3335;
  border: 2px solid #3A3335;
  box-shadow: 0 10px 30px -10px #000;
}

.puzzle-solved {
  position: absolute;
  width: 100%;
  left: 50%;
  top: 50%;
  transform: translate(-50%,-50%) rotate(-15deg);
  font-size: 50px;
  color: #FDF0D5;
  text-shadow: 3px 3px 0 #000, -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000, 1px 1px 0 #000;
}

.tile {
  margin: 5px;
  width: 90px;
  height: 90px;
  font-size: 20px;
  color: #FDF0D5;
  background-color: #EB5E55;
  box-shadow: 0px 2px 4px 0px #000;
  border: none;
}

.tile-space {
  visibility: hidden;
}
代码语言:javascript
复制
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>

<div id="root"></div>
票数 2
EN

Code Review用户

发布于 2017-04-20 14:12:59

我没有足够的声誉来发表评论,所以这里有一个答案。

如果只给出0行,则第3列为空。

当我单击0,0时,第0行上的所有块都将向右移动,而板将以0,0为空结束。

..however..

当我点击3,3,只有一个街区会向上移动,而板将以1,3为空。

你可能想看看这个,对我来说好像是个小虫子!

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

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

复制
相关文章

相似问题

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