【游戏系列】用js+html+css实现web端的2048小游戏
2021-04-29 本文已影响0人
凡繁烦
1.html代码块
overflow: hidden;此样式解决微信内置浏览器下拉问题
<body style="overflow: hidden;">
<div class="main">
<div id="c00"></div>
<div id="c01"></div>
<div id="c02"></div>
<div id="c03"></div>
<div id="c10"></div>
<div id="c11"></div>
<div id="c12"></div>
<div id="c13"></div>
<div id="c20"></div>
<div id="c21"></div>
<div id="c22"> </div>
<div id="c23"></div>
<div id="c30"></div>
<div id="c31"></div>
<div id="c32"></div>
<div id="c33"></div>
</div>
<div class="keymap">
<div class="score">分数:<span id="score">0</span></div>
<p><button onclick="game.reset()">重新开始</button></p>
</div>
</body>
2.css样式
使用flex弹性布局
@media all and (max-device-width: 400px)解决响应式布局
.main{
width: 400px;
height: 400px;
display: flex;
justify-content: space-between;
flex-wrap: wrap;
padding: 10px;
background:#bbada0;
border-radius: 6px;
position: absolute;
top: calc(50% - 200px);
left: calc(50% - 200px);
}
.main div{
width: 90px;
height: 90px;
border-radius: 6px;
background-color: #ccc0b3;
text-align: center;
line-height: 90px;
}
@media all and (max-device-width: 400px){
.main{
width: 340px;
height: 340px;
border-right: 1px solid #e0e0e0;
display: flex;
justify-content: space-between;
flex-wrap: wrap;
padding: 10px;
background:#bbada0;
border-radius: 6px;
position: absolute;
top: calc(50% - 170px);
left: calc(50% - 180px);
}
.main div{
width: 80px;
height: 80px;
}
}
.keymap{
margin: 0 auto;
width: 300px;
text-align: center;
}
.score{
height: 40px;
line-height: 40px;
font-size: 30px;
margin: 20px;
}
.btn-1,.btn-2,.btn-3,.btn-4{
display: flex;
flex-flow: row;
justify-content: center;
margin-top: 10px;
}
.btn-4{
margin-top: 40px;
}
button{
height: 50px;
width: 80px;
margin: 0 10px;
cursor: pointer;
border-width: 0;
outline: none;
background-color: #f59563;
font-family: KaiTi;
border-radius: 3px;
font-size: 20px;
}
button:hover{/*鼠标移动时的颜色变化*/
background-color: antiquewhite;
}
.n2{background-color:#eee3da !important}
.n4{background-color:#ede0c8 !important}
.n8{background-color:#f2b179 !important}
.n16{background-color:#f59563 !important}
.n32{background-color:#f67c5f !important}
.n64{background-color:#f65e3b !important}
.n128{background-color:#edcf72 !important}
.n256{background-color:#edcc61 !important}
.n512{background-color:#9c0 !important}
.n1024{background-color:#33b5e5 !important}
.n2048{background-color:#09c !important}
.n4096{background-color:#a6c !important}
.n8192{background-color:#93c !important }
.n2,.n4{color:#776e65 !important}
3.js逻辑处理
3.1声明一个game对象
let game = {
score: 0,
status: 0,
data: [
[],
[],
[],
[]
],
start: function(){
if(this.status == 1){
alert('游戏已开始');
return
}
this.status = 1;
let data = localStorage.getItem('data');
if(data){
let arr = data.split(',');
for(let i=0;i<arr.length;i++){
if(i<4){
this.data[0][i]=parseInt(arr[i]);
}else if(i<8){
this.data[1][i-4]=parseInt(arr[i]);
}else if(i<12){
this.data[2][i-8]=parseInt(arr[i]);
}else{
this.data[3][i-12]=parseInt(arr[i]);
}
}
}else{
this.data = [
[0,0,0,0],
[0,0,0,0],
[0,0,0,0],
[0,0,0,0]
];
this.randomData();
}
if(localStorage.getItem('score')){
this.score = parseInt(localStorage.getItem('score'))
}
this.renderView();
},
randomData: function(){
for(;;){
let a = Math.floor(Math.random()*4);
let b = Math.floor(Math.random()*4);
if(this.data[a][b] == 0){
var num = Math.random()>0.3 ? 2:4;
this.data[a][b]=num;
break;
}
}
},
//更新视图
renderView: function(){
for(let i = 0; i < this.data.length; i++){
let row = this.data[i];
for(let j = 0; j<row.length; j++){
let cell = row[j];
let dom = document.getElementById('c'+i+j);
if(cell != 0){
dom.innerText=cell;
dom.className='n'+cell;
}else{
dom.innerText='';
dom.className='';
}
}
}
localStorage.setItem('score',this.score);
document.getElementById('score').innerText=this.score;
},
reset(){
localStorage.clear();
this.data = [
[0,0,0,0],
[0,0,0,0],
[0,0,0,0],
[0,0,0,0]
]
this.score = 0;
this.randomData();
this.renderView();
},
checkGameOver: function(){
for(let i = 0; i < this.data.length; i++){
let row = this.data[i];
for(let j = 0; j<row.length; j++){
let cell = row[j];
if(cell == 0){
return false;
}
if(i < this.data.length-1 && this.data[i][j]== this.data[i+1][j]){
return false;
}
if(j < this.data.length-1 && this.data[i][j]== this.data[i][j+1]){
return false;
}
}
}
return true;
},
moveLeft: function(){
let before = String(this.data);
//遍历行
for(let a = 0; a < this.data.length; a++){
this.moveLeftInRow(a);
}
let after = String(this.data);
if(before != after){
this.randomData();
this.renderView();
if(this.checkGameOver()){
alert('游戏结束')
}
}
localStorage.setItem('data',this.data.join(','))
},
moveLeftInRow: function(a){
for(let b = 0; b < this.data.length - 1; b++){
let nextb = this.getNextInRow(a,b);
if(nextb != -1){
if(this.data[a][b] == 0){
this.data[a][b] = this.data[a][nextb];
this.data[a][nextb] = 0;
b--;
}else if(this.data[a][b] == this.data[a][nextb]){
this.data[a][b] *= 2;
this.score += this.data[a][b]
this.data[a][nextb] = 0
}
}else{
break;
}
}
},
getNextInRow: function(a,b){
for(let i = b+1; i<this.data.length; i++){
if(this.data[a][i] != 0){
return i;
}
}
return -1;
},
moveRight: function(){
let before = String(this.data);
for(let a = 0; a < this.data.length; a++){
this.moveRightInRow(a);
}
let after = String(this.data);
if(before != after){
this.randomData();
this.renderView();
if(this.checkGameOver()){
alert('游戏结束')
}
}
localStorage.setItem('data',this.data.join(','))
},
moveRightInRow: function(a){
for(let b=this.data.length-1; b > 0; b--){
let nextb = this.moveNextRight(a,b);
if(nextb != -1){
if(this.data[a][b] == 0){
this.data[a][b] = this.data[a][nextb];
this.data[a][nextb] = 0;
b++;
}else if(this.data[a][b] == this.data[a][nextb]){
this.data[a][b] *= 2;
this.score += this.data[a][b]
this.data[a][nextb] = 0
}
}else{
break;
}
}
},
moveNextRight(a,b){
for(var i = b-1; i >= 0; i--){
if(this.data[a][i] != 0){
return i;
}
}
return -1;
},
moveUp: function(){
let before = String(this.data);
for(let b=0; b < this.data.length; b++){
this.moveUpInRow(b);
}
let after = String(this.data);
if(before != after){
this.randomData();
this.renderView();
if(this.checkGameOver()){
alert('游戏结束')
}
}
localStorage.setItem('data',this.data.join(','))
},
moveUpInRow(b){
for(var a = 0; a < 3; a++){
let nexta = this.moveNextUp(a,b);
if(nexta != -1){
if(this.data[a][b] == 0){
this.data[a][b] = this.data[nexta][b];
this.data[nexta][b] = 0;
a--;
}else if(this.data[a][b] == this.data[nexta][b]){
this.data[a][b] *= 2;
this.score += this.data[a][b]
this.data[nexta][b] = 0
}
}else{
break;
}
}
},
moveNextUp(a,b){
for(var i = a+1; i<4; i++){
if(this.data[i][b] != 0){
return i;
}
}
return -1;
},
moveDown: function(){
let before = String(this.data);
for(let b=0; b< this.data.length; b++){
this.moveDownInRow(b);
}
let after = String(this.data);
if(before != after){
this.randomData();
this.renderView();
if(this.checkGameOver()){
alert('游戏结束')
}
}
localStorage.setItem('data',this.data.join(','))
},
moveDownInRow: function(b){
for(var a=3; a>0; a--){
var nexta = this.moveNextDown(a,b)
if(nexta != -1){
if(this.data[a][b]==0){
this.data[a][b] = this.data[nexta][b];
this.data[nexta][b] = 0;
a++;
}else if(this.data[a][b] == this.data[nexta][b]){
this.data[a][b] *= 2;
this.score += this.data[a][b]
this.data[nexta][b] = 0;
}
}else{
break;
}
}
},
moveNextDown(a,b){
for(var i = a-1;i >= 0;i--){
if(this.data[i][b] != 0){
return i;
}
}
return -1;
}
}
3.2监听键盘上下左右按键
document.addEventListener('keyup',function(e){
if(event.keyCode==37) //左
game.moveLeft();
if(event.keyCode==39) //右
game.moveRight();
if(event.keyCode==38) //上
game.moveUp();
if(event.keyCode==40) //下
game.moveDown();
})
3.3封装移动端滑动事件
let eventUtil = {
addHandler: function (element, type, handler) {
if (element.addEventListener)
element.addEventListener(type, handler, false);
else if (element.attachEvent)
element.attachEvent("on" + type, handler);
else
element["on" + type] = handler;
},
removeHandler: function (element, type, handler) {
if(element.removeEventListener)
element.removeEventListener(type, handler, false);
else if(element.detachEvent)
element.detachEvent("on" + type, handler);
else
element["on" + type] = handler;
},
/**
* 监听触摸的方向
* @param target 要绑定监听的目标元素
* @param isPreventDefault 是否屏蔽掉触摸滑动的默认行为(例如页面的上下滚动,缩放等)
* @param upCallback 向上滑动的监听回调(若不关心,可以不传,或传false)
* @param rightCallback 向右滑动的监听回调(若不关心,可以不传,或传false)
* @param downCallback 向下滑动的监听回调(若不关心,可以不传,或传false)
* @param leftCallback 向左滑动的监听回调(若不关心,可以不传,或传false)
*/
listenTouchDirection: function (target, isPreventDefault, upCallback, rightCallback, downCallback, leftCallback) {
this.addHandler(target, "touchstart", handleTouchEvent);
this.addHandler(target, "touchend", handleTouchEvent);
this.addHandler(target, "touchmove", handleTouchEvent);
var startX;
var startY;
function handleTouchEvent(event) {
switch (event.type){
case "touchstart":
startX = event.touches[0].pageX;
startY = event.touches[0].pageY;
break;
case "touchend":
var spanX = event.changedTouches[0].pageX - startX;
var spanY = event.changedTouches[0].pageY - startY;
if(Math.abs(spanX) > Math.abs(spanY)){ //认定为水平方向滑动
if(spanX > 30){ //向右
if(rightCallback)
rightCallback();
} else if(spanX < -30){ //向左
if(leftCallback)
leftCallback();
}
} else { //认定为垂直方向滑动
if(spanY > 30){ //向下
if(downCallback)
downCallback();
} else if (spanY < -30) {//向上
if(upCallback)
upCallback();
}
}
break;
case "touchmove":
//阻止默认行为
if(isPreventDefault)
event.preventDefault();
break;
}
}
}
}
3.4 监听滑动
eventUtil.listenTouchDirection(document.documentElement,true,
()=>{game.moveUp()},
()=>{game.moveRight()},
()=> {game.moveDown()},
()=>{game.moveLeft()})
image.png