今天写一写2048小游戏的实现思路.先看效果图

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>2048</title>
</head>
<link rel="stylesheet" href="./css/2048.css">
<script src="./js/jquery-1.12.4.js"></script>
<script src="./js/main.js"></script>
<script src="./js/support.js"></script>
<script src="./js/animation2048.js"></script>
<body>
<div class="header">
<h1>2048</h1>
<a href="javascript:newGame()" id="newGameButton">新游戏</a>
<p>score:<span id="score">0</span></p>
</div>
<div id="grid-container">
<div class="grid-cell" id="grid-cell-0-0"></div>
<div class="grid-cell" id="grid-cell-0-1"></div>
<div class="grid-cell" id="grid-cell-0-2"></div>
<div class="grid-cell" id="grid-cell-0-3"></div>
<div class="grid-cell" id="grid-cell-1-0"></div>
<div class="grid-cell" id="grid-cell-1-1"></div>
<div class="grid-cell" id="grid-cell-1-2"></div>
<div class="grid-cell" id="grid-cell-1-3"></div>
<div class="grid-cell" id="grid-cell-2-0"></div>
<div class="grid-cell" id="grid-cell-2-1"></div>
<div class="grid-cell" id="grid-cell-2-2"></div>
<div class="grid-cell" id="grid-cell-2-3"></div>
<div class="grid-cell" id="grid-cell-3-0"></div>
<div class="grid-cell" id="grid-cell-3-1"></div>
<div class="grid-cell" id="grid-cell-3-2"></div>
<div class="grid-cell" id="grid-cell-3-3"></div>
</div>
</body>
</html>
静态布局,我们先创建4*4个div,后面再用js控制他的位置.
/*2048.css部分*/
/* @aunthor: zengjiahao @date: 2018/08/18 */
/*{ margin: 0; padding: 0; }*/
.header{ margin: 0 auto; display: block; text-align: center; width: 500px; /*border: 1px solid black;*/ }
.header h1{ font-family: Arial; font-weight: bold; font-size: 50px; margin: 0 auto; }
.header #newGameButton{ text-decoration: none; display: block; font-family: Arial; margin: 0 auto; padding: 10px 10px; color: white; border-radius: 10px; background-color: #8f7a66; width: 100px; }
.header #newGameButton:hover{ background-color: #9f9e35; }
.header p{ font-family: Arial; font-size: 25px; margin: 0 auto; }
#grid-container{ width: 460px; height: 460px; padding: 20px; /*border: 1px solid black;*/ margin: 40px auto; background-color: #bbada0; border-radius: 10px; position: relative; /*display: none;*/ }
.grid-cell{ width: 100px; height: 100px; border-radius: 6px; background-color: #ccc0b3; position: absolute; }
.number-cell{ font-family: Arial; font-size: 60px; font-weight: bold; line-height: 100px; text-align: center; border-radius: 6px; position: absolute; }
这里是我们棋盘及其盒子的样式结构.这里就不赘述了.
/** * * @authors zengjiahao * @date 2018-08-16 */
function showNumber(i,j,randomNumber) {
var numberCell = $('#number-cell-' + i + '-' + j);
numberCell.css('background-color',getBackgroundColor(randomNumber));
numberCell.css('color',getColor(randomNumber));
numberCell.text(randomNumber);
// console.log(numberCell);
//生成动画
numberCell.animate({
width: "100px",
height: "100px",
top:getPosTop(i),
left:getPosLeft(j)
},50);
}
/** * * @param fromX 起始行数 * @param fromY 起始列数 * @param toX 目标行数 * @param toY 目标列数 */
function showMoveAnimation(fromX,fromY,toX,toY) {
var numberCell = $('#number-cell-' + fromX + '-' + fromY);
numberCell.animate({
top:getPosTop(toX),
left:getPosLeft(toY)
},200)
}
function updateScore(score){
$('#score').text(score);
}
自定义的动画函数,渲染数字的动画和盒子平移的函数写在这里,更新分数的函数也写在这里,其实在这里可以添加一些动画特效
/** * * @authors zengjiahao * @date 2018-08-16 */
//获取上坐标
function getPosTop(i) {
return 20 + i*120;
}
//获取左坐标
function getPosLeft(j) {
return 20 + j*120;
}
function getBackgroundColor(number) {
switch (number){
case 2:return '#eee4da';break;
case 4:return '#ede0c8';break;
case 8:return '#f2b179';break;
case 16:return '#f59563';break;
case 32:return '#f67c5f';break;
case 64:return '#f65e3b';break;
case 128:return '#edcf72';break;
case 256:return '#edcc61';break;
case 512:return '#9c0';break;
case 1024:return '#33b5e5';break;
case 2048:return '#09c';break;
case 4096:return '#a6c';break;
case 8192:return '#93c';break;
}
return 'black';
}
function getColor(number) {
if(number <= 4){
return '#776e65';
}
return 'white';
}
function nospace(board) {
for(var i = 0; i < 4;i++){
for(var j = 0;j<4;j++){
if(board[i][j] == 0){
return false;
}
}
}
return true
}
//判断是否可以向左移动
function canMoveLeft( board ) {
for(var i = 0; i<4; i++){
for(var j = 1; j<4; j++){
if(board[i][j] != 0){
if(board[i][j-1] == 0 || board[i][j-1] == board[i][j]){
return true;
}
}
}
}
return false;
}
//判断是否可以向右移动
function canMoveRight( board ) {
for(var i = 0; i<4; i++){
for(var j = 2; j>=0; j--){
if(board[i][j] != 0){
if(board[i][j+1] == 0 || board[i][j+1] == board[i][j]){
return true;
}
}
}
}
return false;
}
function canMoveUp(board) {
for(var j = 0; j < 4 ; j++){
for(var i = 1 ; i<4;i++){
if(board[i][j] != 0){
if(board[i-1][j] == 0 || board[i-1][j] == board[i][j]){
return true;
}
}
}
}
return false;
}
function canMoveDown(board) {
for(var j = 0; j < 4 ; j++){
for(var i = 2 ; i>=0;i--){
if(board[i][j] != 0){
if(board[i+1][j] == 0 || board[i+1][j] == board[i][j]){
return true;
}
}
}
}
return false;
}
//判断水平方向有没有障碍物
/** * 作用:目标列到判断列之间有没有障碍物 * @param row 行 * @param col1 小列 * @param col2 大列 * @param board 二维数组 */
function noBlockHorizontal(row, col1 , col2 , board) {
for(var i = col1 +1 ; i < col2 ;i++){
if(board[row][i] != 0){
return false;
}
}
return true;
}
function noBlockVertical(col,row1,row2,board) {
for(var i = row1 + 1;i< row2;i++){
if(board[i][col] != 0){
return false;
}
}
return true;
}
//判断还能不能动
function nomove(board) {
if(canMoveLeft(board)||canMoveRight(board)||canMoveUp(board)||canMoveDown(board)){
return false;
}
return true;
}
这里是辅助类的js,比如我们获取定位的坐标函数,获取颜色,背景色的函数也写在这里,还有一些辅助函数,我们在主逻辑js中再详细
说明.
/** * * @authors zengjiahao * @date 2018-08-16 */
// 游戏主逻辑
var board = [];
var score = 0;
var hasConflicted = [];
//入口函数
$(function () {
newGame();
})
function newGame() {
//初始化
init();
//在随机两个格子生成数字
renderOneNumber();
renderOneNumber();
}
function init() {
//4 * 4 棋盘
for (var i = 0; i < 4; i++) {
for (var j = 0; j < 4; j++) {
//灰色的初始棋盘
var gridCell = $('#grid-cell-' + i + '-' + j);
gridCell.css('top', getPosTop(i));
gridCell.css('left', getPosLeft(j));
}
}
//生成数据格子
for (var i = 0; i < 4; i++) {
//初始化
board[i] = []; //二维数组
hasConflicted[i] = [];
for (var j = 0; j < 4; j++) {
board[i][j] = 0;
hasConflicted[i][j] = false;
}
}
//初始化分数
score = 0;
updateScore(score);
updateBoard();
}
function updateBoard() {
$('.number-cell').remove();
for (var i = 0; i < 4; i++) {
for (var j = 0; j < 4; j++) {
$('#grid-container').append('<div class="number-cell" id="number-cell-' + i + '-' + j + '"></div>')
// var str = $('#grid-container').append('<div class="number-cell" id="number-cell-' + i +'-" + j></div>');
// console.log(str);
var theNumberCell = $('#number-cell-' + i + '-' + j);
if (board[i][j] == 0) {
//等于0就是不显示
theNumberCell.css({
'width': '0px',
'height': '0px',
'top': getPosTop(i) + 50,
'left': getPosLeft(j) + 50
});
} else {
//有数值就显示咯
theNumberCell.css({
'width': '100px',
'height': '100px',
'top': getPosTop(i),
'left': getPosLeft(j),
'background-color': getBackgroundColor(board[i][j]),
'color': getColor(board[i][j])
});
theNumberCell.text(board[i][j]);
if (theNumberCell.text() == 1024 || theNumberCell.text() == 2048 || theNumberCell.text() == 4096 || theNumberCell.text() == 8192) {
theNumberCell.css('font-size', '45px');
}
}
hasConflicted[i][j] = false;
}
}
}
//生成数字
function renderOneNumber() {
//判断是否有空格子
if (nospace(board)) {
return false;
}
//随机一个位置
var randomX = parseInt(Math.floor(Math.random() * 4));
var randomY = parseInt(Math.floor(Math.random() * 4));
// console.log(randomX);
// console.log(randomY);
var times = 0;
while (times < 30) {
if (board[randomX][randomY] == 0) {
break;
}
randomX = parseInt(Math.floor(Math.random() * 4));
randomY = parseInt(Math.floor(Math.random() * 4));
times++;
}
//性能优化
if (times == 30) {
for (var i = 0; i < 4; i++) {
for (var j = 0; j < 4; j++) {
if (board[i][j] == 0) {
randomX = i;
randomY = j;
}
}
}
console.log('手动生成');
}
//随机一个数字
var randomNumber = Math.random() < 0.5 ? 2 : 4; //2或4 55开
//在随机位置显示随机数字
board[randomX][randomY] = randomNumber;
showNumber(randomX, randomY, randomNumber);
return true;
}
$(document).keydown(function (e) {
switch (e.keyCode) {
case 37: //左
if (moveLeft()) {
setTimeout('renderOneNumber()', 210);
setTimeout('isGameOver()', 300);
// renderOneNumber();
// isGameOver();
}
break;
case 38: //上
if (moveUp()) {
setTimeout('renderOneNumber()', 210);
setTimeout('isGameOver()', 300);
}
break;
case 39: //右
if (moveRight()) {
setTimeout('renderOneNumber()', 210);
setTimeout('isGameOver()', 300);
}
break;
case 40: //下
if (moveDown()) {
setTimeout('renderOneNumber()', 210);
setTimeout('isGameOver()', 300);
}
break;
default:
break;
}
})
//游戏是否结束
function isGameOver() {
if (nospace(board) && nomove(board)) {
gameover();
}
}
function gameover() {
alert('gameover!');
}
function moveLeft() {
//判断是否可以左移动
if (!canMoveLeft(board)) {
return false;
}
//moveLeft
for (var i = 0; i < 4; i++) {
for (var j = 1; j < 4; j++) {
//如果不为0,那么他是可能向左移动的
if (board[i][j] != 0) {
for (var k = 0; k < j; k++) {
if (board[i][k] == 0 && noBlockHorizontal(i, k, j, board)) {
//move
showMoveAnimation(i, j, i, k);
board[i][k] = board[i][j];
board[i][j] = 0;
continue;
} else if (board[i][k] == board[i][j] && noBlockHorizontal(i, k, j, board) && !hasConflicted[i][k]) {
//move
showMoveAnimation(i, j, i, k);
//add
board[i][k] += board[i][j];
board[i][j] = 0;
//加分
score += board[i][k];
updateScore(score);
//开关
hasConflicted[i][k] = true;
continue;
}
}
}
}
}
//根据数组更新位置
setTimeout("updateBoard()", 200);
return true;
}
function moveRight() {
//判断是否可以右移动
if (!canMoveRight(board)) {
return false;
}
// flag = false;
//moveRight
for (var i = 0; i < 4; i++) {
for (var j = 2; j >= 0; j--) {
//如果不为0,那么他是可能向右移动的
if (board[i][j] != 0) {
for (var k = 3; k > j; k--) {
if (board[i][k] == 0 && noBlockHorizontal(i, j, k, board)) {
//move
showMoveAnimation(i, j, i, k);
board[i][k] = board[i][j];
board[i][j] = 0;
continue;
} else if (board[i][k] == board[i][j] && noBlockHorizontal(i, j, k, board) && !hasConflicted[i][k]) {
//move
showMoveAnimation(i, j, i, k);
//add
board[i][k] *= 2;
board[i][j] = 0;
//加分
score += board[i][k];
updateScore(score);
hasConflicted[i][k] = true;
continue;
}
}
}
}
}
//根据数组更新位置
setTimeout("updateBoard()", 200);
return true;
}
function moveUp() {
//判断是否可以上移动
if (!canMoveUp(board)) {
return false;
}
// flag = false;
//moveUp
for (var j = 0; j < 4; j++) {
for (var i = 1; i < 4; i++) {
//如果不为0,那么他是可能向上移动的
if (board[i][j] != 0) {
for (var k = 0; k < i; k++) {
if (board[k][j] == 0 && noBlockVertical(j, k, i, board)) {
//move
showMoveAnimation(i, j, k, j);
board[k][j] = board[i][j];
board[i][j] = 0;
continue;
} else if (board[k][j] == board[i][j] && noBlockVertical(j, k, i, board) && !hasConflicted[k][j]) {
//move
showMoveAnimation(i, j, k, j);
//add
board[k][j] *= 2;
board[i][j] = 0;
//加分
score += board[k][j];
updateScore(score);
hasConflicted[k][j] = true;
continue;
}
}
}
}
}
//根据数组更新位置
setTimeout("updateBoard()", 200);
return true;
}
function moveDown() {
//判断是否可以上移动
if (!canMoveDown(board)) {
return false;
}
// flag = false;
//moveUp
for (var j = 0; j < 4; j++) {
for (var i = 2; i >= 0; i--) {
//如果不为0,那么他是可能向下移动的
if (board[i][j] != 0) {
for (var k = 3; k > i; k--) {
if (board[k][j] == 0 && noBlockVertical(j, i, k, board)) {
//move
showMoveAnimation(i, j, k, j);
board[k][j] = board[i][j];
board[i][j] = 0;
continue;
} else if (board[k][j] == board[i][j] && noBlockVertical(j, i, k, board) && !hasConflicted[k][j]) {
//move
showMoveAnimation(i, j, k, j);
//add
board[k][j] *= 2;
board[i][j] = 0;
//加分
score += board[k][j];
updateScore(score);
hasConflicted[k][j] = true;
continue;
}
}
}
}
}
//根据数组更新位置
setTimeout("updateBoard()", 200);
return true;
}
这是我们游戏的主逻辑js,我们先从游戏规则说起吧,如果游戏规则不熟悉,可能会造成阅读障碍.
游戏控制器:
X:上下左右
当按下对应按钮时,所有方块向X移动。
游戏规则:
1、开始游戏时有两个方块,方块数字随机生成2或者4.
2、移动时,若方块数字相等,那么两个方块会合二为一。
3、发生碰撞后的方块,在这一次移动中不会发生第二次碰撞。
4、记录分数:如果发生了碰撞,分数会加上两个方块碰撞后的分数。
5、当棋盘四个方向都不能移动时,游戏结束。
那么我们把焦点放在我们的入口函数开始吧,首先,我们的入口函数,我们需要调用一个初始化的函数,这个函数主要是赋予我们灰色
格子定位属性,还有初始化我们记录数据格子的数组以及记录碰撞的数组.之后,我们需要生成两个盒子,那么就调用两次生成盒子的
函数即可.
那么我们游戏其实最重要的部分,还是在判断他是否能移动已经碰撞发生后不判定的问题.
先从我们是否能移动说起吧
1.
我们以按下左键,即盒子是否能向左边移动,其实判断盒子能否向左边移动,只需要判定后面三列即可,第一种情况,只要这4行3列里,
对应的二维数组的数值为0,那么该位置为空,那么铁定是可以向左移动的;第二种情况,只要这4行3列里,我们的数组值等于其左边的
数组值的话且中间没有数组的盒子形成阻挡,那么也可以说,他是可以向左移动的,只要判定了他可以向左移动,那么,我们就可以
接着执行向左移动的平移函数.
在这个函数里,我们主要判断这4行3列里,有数值的盒子,如何向左移动?我们利用两个for循环,可以遍历我们所有有值得盒子,只要,
我们的盒子满足他的左边有空的位置,还有左边的值与他相等且中间没有遮挡且该位置没有发生过碰撞关系,那么他就可以向左移动,
如果不满足条件即该盒子不移动.
2.判定碰撞的方法:其实仔细看了代码的朋友,会发现我们定义了一个hasConflicted的数组来解决这个问题,我们其实已经在
初始化的时候把他也初始化了,当两个相同值得盒子发生碰撞后,该值就会被修改为true,那么遍历到这个位置的时候,只要读到
这个值为true,那么判断语句的条件就不会成立了.
该文只是提供一些思路,可能考虑的不是很全面,希望有大牛看到文章的时候时,可以指点一下我.
该文就已经结束了,后面附上一张有判定碰撞还有如果设置碰撞条件的时候,该游戏会怎么走的示意图.

还没有评论,来说两句吧...