今天写一写2048小游戏的实现思路.先看效果图
<html lang="en">
<head>
<meta charset="UTF-8">
<title>2048title>
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>2048h1>
<a href="javascript:newGame()" id="newGameButton">新游戏a>
<p>score:<span id="score">0span>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控制他的位置.
.header{
margin: 0 auto;
display: block;
text-align: center;
width: 500px;
}
.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;
margin: 40px auto;
background-color: #bbada0;
border-radius: 10px;
position: relative;
}
.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);
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);
}
自定义的动画函数,渲染数字的动画和盒子平移的函数写在这里,更新分数的函数也写在这里,其实在这里可以添加一些动画特效
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;
}
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中再详细
说明.
var board = [];
var score = 0;
var hasConflicted = [];
$(function () {
newGame();
})
function newGame() {
init();
renderOneNumber();
renderOneNumber();
}
function init() {
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(''-' + j + '">
')
var theNumberCell = $('#number-cell-' + i + '-' + j);
if (board[i][j] == 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));
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;
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);
}
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;
}
for (var i = 0; i < 4; i++) {
for (var j = 1; j < 4; j++) {
if (board[i][j] != 0) {
for (var k = 0; k < j; k++) {
if (board[i][k] == 0 && noBlockHorizontal(i, k, j, board)) {
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]) {
showMoveAnimation(i, j, i, k);
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;
}
for (var i = 0; i < 4; i++) {
for (var j = 2; j >= 0; j--) {
if (board[i][j] != 0) {
for (var k = 3; k > j; k--) {
if (board[i][k] == 0 && noBlockHorizontal(i, j, k, board)) {
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]) {
showMoveAnimation(i, j, i, k);
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;
}
for (var j = 0; j < 4; j++) {
for (var i = 1; i < 4; i++) {
if (board[i][j] != 0) {
for (var k = 0; k < i; k++) {
if (board[k][j] == 0 && noBlockVertical(j, k, i, board)) {
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]) {
showMoveAnimation(i, j, k, j);
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;
}
for (var j = 0; j < 4; j++) {
for (var i = 2; i >= 0; i--) {
if (board[i][j] != 0) {
for (var k = 3; k > i; k--) {
if (board[k][j] == 0 && noBlockVertical(j, i, k, board)) {
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]) {
showMoveAnimation(i, j, k, j);
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,那么判断语句的条件就不会成立了.
该文只是提供一些思路,可能考虑的不是很全面,希望有大牛看到文章的时候时,可以指点一下我.
该文就已经结束了,后面附上一张有判定碰撞还有如果设置碰撞条件的时候,该游戏会怎么走的示意图.