这是我第一次用画布制作的游戏,目前它运行良好,但我知道我的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);
});<canvas id="board"></canvas>查看码笔
发布于 2017-04-22 01:43:42
你的拼图看上去不错!下面是关于您的代码的一些建议:
您的getPosition函数访问非标准属性,并且无法工作,例如在火狐中。遵循这个建议并编写
let rect = canvas.getBoundingClientRect();
let x = event.clientX - rect.left;
let y = event.clientY - rect.top;从函数中删除对全局(如c或map )的引用,并通过参数传递它们。
将所有不直接依赖于DOMContentLoaded事件的代码从一个大型事件侦听器中移出。
将游戏状态与UI属性分开。例如,瓷砖的宽度和高度是它们在屏幕上的视觉表示的属性,不应该是板子模型的一部分。
另外,将处理游戏状态更新的逻辑与处理UI更新的逻辑分开。例如,介绍一个板模型和在该模型上运行的函数,如push、isSolved等,并使用观察者设计模式听取UI更新的状态更改。
你的董事会代表是多余的,而且比必要的更复杂。简单地将数字存储在二维数组中的特定列和行(类似于map和winMap数组)就足够了。
考虑通过连接行或列将您的二维数组扁平成一个大的一维数组。这简化了数组的创建、复制和比较,并提高了性能。例如,您的checkWin逻辑可以重写为
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位置移动或推开瓷砖时,可以使用以下有效函数:
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更新的已建立的库--我们得到以下代码:
// 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")
);.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;
}<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>发布于 2017-04-20 14:12:59
我没有足够的声誉来发表评论,所以这里有一个答案。
如果只给出0行,则第3列为空。
当我单击0,0时,第0行上的所有块都将向右移动,而板将以0,0为空结束。
..however..
当我点击3,3,只有一个街区会向上移动,而板将以1,3为空。
你可能想看看这个,对我来说好像是个小虫子!
https://codereview.stackexchange.com/questions/155130
复制相似问题