2023-04-14 使用纯JS实现一个2048小游戏
文章目录
- 一.实现思路
 - 1.2048的逻辑
 - 2.移动操作的过程中会有三种情况
 
- 二.代码部分:分为初始化部分和移动部分
 - 1.初始化部分
 - 1.1.生成第一个方块:
 - 1.2.生成第二个方块:
 
- 2.移动过程部分:
 
- 三.实现代码
 - 1.HTML部分
 - 2.CSS部分
 - 3.JS部分
 - 3.1.game对象的属性
 - 3.2.game对象的start方法
 - 3.3.game对象的randomNum方法
 - 3.4.game对象的dataView方法
 - 3.5.game对象的isGameOver方法
 - 3.6.game对象中设置移动的方法(以左移动为例)
 - 3.6.1.moveLeft()方法
 - 3.6.2.moveLeftinRow()方法
 - 3.6.3.moveLeftNum()方法
 
- 3.7.监听用户的键盘按下事件,用户按下"上下左右"键位时,触发相应的移动函数
 - 3.8.游戏开始时,调用game对象中的start方法
 - 3.9.游戏结束时弹出结算框,结算框中绑定一个try again按钮,功能是可以再来一次
 - 3.10.完整js代码
 
- 四.效果
 
一.实现思路
1.2048的逻辑
一共有16个格子,开局5个随机格子上生成带数字的方块,2或者4
 通过控制方向来使原有的方块移动并产生新的方块
2.移动操作的过程中会有三种情况
- 方块移动的方向没有其他方块
 - 方块移动的方向有其他方块,但数字不同
 - 方块移动的方向有其他方块,且数字相同
 
二.代码部分:分为初始化部分和移动部分
1.初始化部分
新建一个4X4二维数组,存储16个方块的值,
 分两个步骤生成第一个和第二个方块
1.1.生成第一个方块:
用0-15的随机数代表第几个数组元素上面生成第一个方块块的值(值为2或者4),其余的值为0
 具体生成2或者4,设置为随机,其中2的概率高一些,4的概率低一些,比如:
 0-10之间,若随机值为0,生成2,否则生成4,比例:10:1
1.2.生成第二个方块:
-  
为什么第二个方块(以及其他所有后续的方块)不能使用上述方法?
因为如果两次生成的随机数一样,那么就会出现bug:
同一位置产生两个值 -  
第二个方块如何生成?
先获取16个格子中,空格的个数,也就是二维数组中0的个数
以空格的个数作为循环的次数 
2.移动过程部分:
移动过程是根据输入的方向来控制方块的移动的
- 首先获取输入的状态,即玩家操作的上下左右键
 - 其次,移动的控制逻辑是:
 -  
- 遍历二维数组,找到每一个方块(以左移动为例)
由于第一列无需移动,只从每一行的第二个方块开始判断
(若第一个方块不存在:交换值,并让交换的原位置的值清0 
 - 遍历二维数组,找到每一个方块(以左移动为例)
 -  
- 若当前方块的紧邻左方块和当前方块,两者的值相同,那么左边的值变为原来的两倍,当前方块的值清0
 
 -  
- 若当前方块的紧邻左方块看和当前方块,两者的值不同,不作处理,不移动
 
 
三.实现代码
1.HTML部分
-  
结构:分数实时显示,4X4的2048游戏页面,如果游戏结束,则弹出结算框
 -  
快捷键:
div#all>div.cell#n0$@0*4+div.cell#n1$@0*4+div.cell#n2$@0*4+div.cell#n3$@0*4 -  
代码:2048.html
 
<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><title>2048小游戏</title><link rel="stylesheet" href="2048.css" />
</head>
<body><!--分数--><p class="header">SCORE: <span id="score01"></span></p><!--游戏部分--><div id="all"><!--第一行--><div class="cell"  id="n00"></div><div class="cell"  id="n01"></div><div class="cell"  id="n02"></div><div class="cell"  id="n03"></div><!--第二行--><div class="cell"  id="n10"></div><div class="cell"  id="n11"></div><div class="cell"  id="n12"></div><div class="cell"  id="n13"></div><!--第三行--><div class="cell"  id="n20"></div><div class="cell"  id="n21"></div><div class="cell"  id="n22"></div><div class="cell"  id="n23"></div><!--第四行--><div class="cell"  id="n30"></div><div class="cell"  id="n31"></div><div class="cell"  id="n32"></div><div class="cell"  id="n33"></div></div><!--弹框--><div id="gameover" ><p>GAME OVER<br />SCORE:<span id="score02"></span><br /><a onclick="typeonce()">TYPE AGAIN</a></p></div><script type="text/javascript" src="2048.js" ></script>
</body>
</html> 
2.CSS部分
2038.css:每一个数量级的格子会获得相应的颜色
*{font-family: arial;/*	user-select: none;*/font-weight: bold;}.header{width: 480px;font-size:40px ;margin: 60px auto 5px auto;}#score01{color: #F00;}#all{width: 480px;height: 480px;background-color: #bbada0;margin: 0 auto;border-radius: 20px;}.cell{width: 100px;height: 100px;background-color: #ccc0b3;;border-radius: 10px;text-align: center;line-height: 100px;font-size: 40px;color: #fff;float: left;margin: 16px 0px 0px 16px;}.n2{background-color:#eee3da;color:#776e65;}.n4{background-color:#ede0c8;color:#776e65;}.n8{background-color:#f2b179;}.n16{background-color:#f59563;}.n32{background-color:#f67c5f;}.n64{background-color:#f65e3b;}.n128{background-color:#edcf72;}.n256{background-color:#edcc61;}.n512{background-color:#9c0;}.n1024{background-color:#33b5e5;font-size:40px;}.n2048{background-color:#09c;font-size:40px;}.n4096{background-color:#a6c;font-size:40px;}.n8192{background-color:#93c;font-size:40px;}#gameover{position: absolute;background-color: rgba(0,0,0,0.2);left: 0;top: 0;right: 0;bottom: 0;font-size: 40px;display: none;}#gameover p{background-color: #fff;width: 300px;height: 200px;border-radius: 10px;line-height: 66.66px;text-align: center;position: absolute;left: 50%;top: 50%;margin-left: -150px;margin-top: -150px;}#gameover p a{padding: 10px;text-decoration: none;background-color: #9f8d77;color: #fff;border-radius: 10px;cursor: pointer;} 
3.JS部分
3.1.game对象的属性
- data属性:是一个二维数组,用于存放数据
 - score属性:用于存放分数
 - gameruning属性:游戏开始时的状态码
 - gameover属性:游戏结束时的状态码
 - status属性:当前状态码
 
  /****对象属性****/data: [],  //二维数组:用于存放4X4的2048格子中的数字显示score: 0,  //分数:用于计算当前得分gamerunning: 1,  //游戏开始时的状态码:1gameover: 0,  //游戏结束时的状态码:0status: 1,  //当前状态码:取值是0或1(默认)
 
3.2.game对象的start方法
初始化属性值
- data:二维数组的所有元素的值都为0
 - score:分数清零
 - status:状态码=游戏开始时的状态码
 
调用对象方法:
- randomNum():让一个二维数组元素获得一个数(2或4)
 - dataView():更新视图,即把数组元素的值赋给对应的HTML结构中
 
//1.start方法:游戏开始时调用的对象方法,用于初始化start: function () {//还原对象属性this.data = [[0, 0, 0, 0],[0, 0, 0, 0],[0, 0, 0, 0],[0, 0, 0, 0]];this.score = 0;this.status = this.gamerunning;///调用对象方法for(var i=0;i<5;i++){this.randomNum();//游戏开始时需要在几个格子上出现2或4,就调用几次}this.dataView(); //更新视图},
 
3.3.game对象的randomNum方法
用于让一个随机的数组元素获取到随机的数字
 步骤:
 - 设置随机行数
 - 设置随机列数
 - 设置随机获得2或4的参数num
 - 让随机行数+随机列数对应的二维数组元素获得num
 //2.randomNum方法:用于让一个随机的数组元素获取到随机的数字randomNum: function () {while (true) {//判断是否存在空白位置//随机行数var row = Math.floor(Math.random() * 4);//取值范围:0,1,2,3//随机列数var col = Math.floor(Math.random() * 4);//取值范围:0,1,2,3if (this.data[row][col] == 0) {//随机整数var num = Math.random() > 0.2 ? 2 : 4;//取值范围:2或者4,比例分别为80%和20%this.data[row][col] = num;//让一个随机的数组元素获取到随机的数字break;//停止循环}}},
 
3.4.game对象的dataView方法
用于更新视图,让数组中的值在对应的HTML元素中显示
思路是
- 遍历#n00`#n3312个div
 - 把对应的二维数组元素赋给这些div的innerHTML
 - 并让这些被赋值的地址获得新类名.n32,.n128等等,这样显示出来会有不同的颜色
 
//3.dataView方法:用于更新视图,让数组中的值在对应的HTML元素中显示dataView:function(){/*为html元素赋值*/for(var row=0;row<4;row++){for(var col=0;col<4;col++){	var div=document.getElementById("n"+row+col);//字符串拼接:目标是往<div id="n12"></div>里面写内容if(this.data[row][col]!=0){div.innerHTML=this.data[row][col];div.className="cell n"+this.data[row][col];//css中每个阶段都有其样式如".n64{}","n128{}"等}else{div.innerHTML="";div.className="cell";}}}/*更新分数*/document.getElementById("score01").innerHTML=this.score;/*判断游戏是否结束*/if(this.status==this.gameover){//游戏已结束document.getElementById("gameover").style.display="block";//弹出结算框document.getElementById("score02").innerHTML=this.score;}else{//游戏未结束document.getElementById("gameover").style.display="none";//隐藏结算框}},
 
3.5.game对象的isGameOver方法
用于判断游戏是否结束,该函数返回一个布尔值
 游戏未结束的三个情况:
- 1.存在空格子
 - 2.上下行的数字相同
 - 3.左右列的数字相同
 
isGameOver: function () {//游戏未结束的三种情况:1.存在空格子;2.左右格子数字一样;3.上下格子数字一样for(var row=0;row<4;row++){for(var col=0;col<4;col++){if(this.data[row][col]==0) return false;//1.存在空格子if(col<3){//2.左右格子数字一样if(this.data[row][col]==this.data[row][col+1]) return false;}if(row<3){//3.上下格子数字一样if(this.data[row][col]==this.data[row+1][col]) return false;}}}return true;},
 
3.6.game对象中设置移动的方法(以左移动为例)
3.6.1.moveLeft()方法
当用户有左移操作时,触发该函数
 该函数主要做这些事:
- 调用moveLeftinRow()方法,目的是处理移动时的数据合并
 - 判断二维数组在调用上述方法前后是否发生变化
 - 若有,说明移动过程中发生了数据合并,那么需要做这些事:
 -  
- 调用randomNum()方法,让一个随机的二维数组元素生成2或4
 
 -  
- 判断游戏是否结束,若结束则让状态码=gameover,即游戏结束的状态码
 
 -  
- 调用dataView()更新视图
 
 
//1.1.moveLeft方法:用户有左移操作时触发moveLeft:function(){// 左移前的data数组(为方便对比先进行字符化)var before=String(this.data);// 处理每一行的数据:调用moveLeftinRow方法for(var row=0;row<4;row++){this.moveLeftinRow(row);}// 左移后的data数组(为方便对比先进行字符化)var after=String(this.data);//若处理数据后data发生了变化if(before!=after){// 随机生成2或4this.randomNum();// 判断游戏是否结束if(this.isGameOver()) this.status==this.gameover;//更新状态码 // 更新视图this.dataView();}},
 
3.6.2.moveLeftinRow()方法
用于处理左移过程中的数据合并,分为两种情况:
- 当前元素为0时:让当前元素与moveLeftNum的值调换
 - 当前元素与moveLeftNum的值相同时,当前元素的值倍增,moveLeftNum的值清零,分数累计
 
//1.2.moveLeftinRow方法:处理每一行的数据moveLeftinRow:function(row){for(var col=0;col<3;col++){//此处的col<3要与下面moveLeftNum方法中的循环条件i=col+1相对应var nextCol=this.moveLeftNum(row,col);//nextCol是指:与当前元素的同一行中,后面非空的那一列元素if(nextCol!=-1){//i// 向左移动时的两种情况:1.当前元素为0;2.当前元素与nextCol的值一样if(this.data[row][col]==0){// 1.当前元素为0——让当前元素的值变为nextCol,以及nextCol的值变为0this.data[row][col]=this.data[row][nextCol];this.data[row][nextCol]=0;col--;//继续返回最前列再次循环}else if(this.data[row][col]==this.data[row][nextCol]){//2.当前元素与nextCol的值一样——让当前元素的值倍增,nextCol的值变为0,分数累计this.data[row][col]*=2;this.data[row][nextCol]=0;this.score+=this.data[row][col]}}else{//-1break;}}},
 
3.6.3.moveLeftNum()方法
用于返回左移过程中,与当前元素同行不同列的非空元素的下标
	//1.3.moveLeftNum方法:返回左移过程中符合条件的下标col(第几列)moveLeftNum:function(row,col){for(var i=col+1;i<4;i++){if(this.data[row][i]!=0) {//条件就是:后面的元素非空就返回其下标return i;}}return -1;},/**后面的右移,上移和下移,参考左移即可*难点主要是改变循环条件*其中,上移对应的应该是左移,因为是从高行数(列数)向低行数(列数)遍历*同理.下移对应的是右移,低==>高*/
 
3.7.监听用户的键盘按下事件,用户按下"上下左右"键位时,触发相应的移动函数
document.onkeydown = function (event) {if (event.keyCode == 65) game.moveLeft();//键盘左:Aif (event.keyCode == 87) game.moveUp();//键盘上:Wif (event.keyCode == 68) game.moveRight();//键盘右:Dif (event.keyCode == 83) game.moveDown();//键盘下:S
}
 
3.8.游戏开始时,调用game对象中的start方法
game.start();
 
3.9.游戏结束时弹出结算框,结算框中绑定一个try again按钮,功能是可以再来一次
function typeonce() {document.getElementById("gameover").style.display = "none";window.location.href = "2048.html";
}
 
3.10.完整js代码
/******思路*******//*一.游戏环节:创建game对象*/
//属性:data,score,gamerunning,gameover,status
//普通方法:start,randomNum,dataView,isGameOver
//移动的方法:moveLeft,moveRight,moveUp,moveDown
//移动方法中处理每一行数据的方法:
//moveLeftinRow,moveLeftNum,moveRightinRow,moveRightNum
//moveUpinRow,moveUpNum,moveDowninRow,moveDownNum// var game={};/*二.监听鼠标按下事件,并调用game对象中的移动方法*/
//如果监听到上下左右对应的ASCII码,就触发相应的上下左右game移动方法// document.οnkeydοwn=function(){}/*三.游戏开始时,调用game对象中的start方法*/// game.start();/*四.点击再来一次*/
//隐藏#gameover元素,并跳转回2048.html// function typeonce(){}
/**********************************************************************************/
/*一.游戏环节:创建game对象*/
//属性:data,score,gamerunning,gameover,status
//普通方法:start,randomNum,dataView,isGameOver
//移动的方法:moveLeft,moveRight,moveUp,moveDown
//移动方法中处理每一行数据的方法:
//moveLeftinRow,moveLeftNum,moveRightinRow,moveRightNum
//moveUpinRow,moveUpNum,moveDowninRow,moveDownNum
var game = {/****对象属性****/data: [],  //二维数组:用于存放4X4的2048格子中的数字显示score: 0,  //分数:用于计算当前得分gamerunning: 1,  //游戏开始时的状态码:1gameover: 0,  //游戏结束时的状态码:0status: 1,  //当前状态码:取值是0或1(默认)/****普通的对象方法****///1.start方法:游戏开始时调用的对象方法,用于初始化start: function () {//还原对象属性this.data = [[0, 0, 0, 0],[0, 0, 0, 0],[0, 0, 0, 0],[0, 0, 0, 0]];this.score = 0;this.status = this.gamerunning;///调用对象方法for(var i=0;i<15;i++){this.randomNum();//游戏开始时需要在几个格子上出现2或4,就调用几次}this.dataView(); //更新视图},//2.randomNum方法:用于让一个随机的数组元素获取到随机的数字randomNum: function () {while (true) {//判断是否存在空白位置//随机行数var row = Math.floor(Math.random() * 4);//取值范围:0,1,2,3//随机列数var col = Math.floor(Math.random() * 4);//取值范围:0,1,2,3if (this.data[row][col] == 0) {//随机整数var num = Math.random() > 0.2 ? 2 : 4;//取值范围:2或者4,比例分别为80%和20%this.data[row][col] = num;//让一个随机的数组元素获取到随机的数字break;//停止循环}}},//3.dataView方法:用于更新视图,让数组中的值在对应的HTML元素中显示dataView:function(){/*为html元素赋值*/for(var row=0;row<4;row++){for(var col=0;col<4;col++){	var div=document.getElementById("n"+row+col);//字符串拼接:目标是往<div id="n12"></div>里面写内容if(this.data[row][col]!=0){div.innerHTML=this.data[row][col];div.className="cell n"+this.data[row][col];//css中每个阶段都有其样式如".n64{}","n128{}"等}else{div.innerHTML="";div.className="cell";}}}/*更新分数*/document.getElementById("score01").innerHTML=this.score;/*判断游戏是否结束*/if(this.status==this.gameover){//游戏已结束document.getElementById("gameover").style.display="block";//弹出结算框document.getElementById("score02").innerHTML=this.score;}else{//游戏未结束document.getElementById("gameover").style.display="none";//隐藏结算框}},//4.isGameOver:用于判断游戏是否已经结束,返回一个布尔值isGameOver: function () {//游戏未结束的三种情况:1.存在空格子;2.左右格子数字一样;3.上下格子数字一样for(var row=0;row<4;row++){for(var col=0;col<4;col++){if(this.data[row][col]==0) return false;//1.存在空格子if(col<3){//2.左右格子数字一样if(this.data[row][col]==this.data[row][col+1]) return false;}if(row<3){//3.上下格子数字一样if(this.data[row][col]==this.data[row+1][col]) return false;}}}return true;},/****设置移动的对象方法****//**1.左移**///1.1.moveLeft方法:用户有左移操作时触发moveLeft:function(){// 左移前的data数据:为方便对比,先进行字符化var before=String(this.data);// 处理每一行的数据:调用moveLeftinRow方法for(var row=0;row<4;row++){this.moveLeftinRow(row);}// 右移前的data数据:为方便对比,先进行字符化var after=String(this.data);// 若处理数据后data发生了变化if(before!=after){// 随机生成2或者4this.randomNum();// 判断游戏是否结束if(this.isGameOver()){this.status=this.gameover;//更新状态码}// 更新视图this.dataView();}},//1.2.moveLeftinRow方法:处理每一行的数据moveLeftinRow:function(row){for(var col=0;col<3;col++){//此处的col<3要与下面moveLeftNum方法中的循环条件i=col+1相对应var nextCol=this.moveLeftNum(row,col);//nextCol是指:与当前元素的同一行中,后面非空的那一列元素if(nextCol!=-1){//i// 向左移动时的两种情况:1.当前元素为0;2.当前元素与nextCol的值一样if(this.data[row][col]==0){// 1.当前元素为0——让当前元素的值变为nextCol,以及nextCol的值变为0this.data[row][col]=this.data[row][nextCol];this.data[row][nextCol]=0;col--;//继续返回最前列再次循环}else if(this.data[row][col]==this.data[row][nextCol]){//2.当前元素与nextCol的值一样——让当前元素的值倍增,nextCol的值变为0,分数累计this.data[row][col]*=2;this.data[row][nextCol]=0;this.score+=this.data[row][col]}}else{//-1break;}}},//1.3.moveLeftNum方法:返回左移过程中符合条件的下标col(第几列)moveLeftNum:function(row,col){for(var i=col+1;i<4;i++){if(this.data[row][i]!=0) {//条件就是:后面的元素非空就返回其下标return i;}}return -1;},/**后面的右移,上移和下移,参考左移即可*难点主要是改变循环条件*其中,上移对应的应该是左移,因为是从高行数(列数)向低行数(列数)遍历*同理.下移对应的是右移,低==>高*//**2.右移**///2.1.moveRightmoveRight:function(){var before=String(this.data);for(var row=0;row<4;row++){//右移时,行数遍历不变,都是从第一行开始遍历this.moveRightinRow(row);}var after=String(this.data);if(before!=after){this.randomNum();if(this.isGameOver()) this.status==this.gameover;this.dataView();}},//2.2.moveRightinRow方法:处理每一行的数据moveRightinRow:function(row){for(var col=3;col>=0;col--){//右移时,要从右向左遍历,当前元素是右元素,lastCol是左元素var lastCol=this.moveRightNum(row,col);if(lastCol!=-1){//向右移动需要处理的两个情况:1.当前元素为0;2.当前元素和lastCol一样if(this.data[row][col]==0){//1.当前元素为0:当前元素的值变为lastCol,lastCol的值变为0this.data[row][col]=this.data[row][lastCol];this.data[row][lastCol]=0;col++;}else if(this.data[row][col]==this.data[row][lastCol]){//2.当前元素和lastCol一样:当前元素的值倍增,lastcol的值变为0,分数累计this.data[row][col]*=2;this.data[row][lastCol]=0;this.score+=this.data[row][col];}}else{break;}}},//2.3.moveRightNum方法:获取非空元素的下标moveRightNum:function(row,col){for(var i=col-1;i>=0;i--){//右移时,从右向左遍历,因此lastCol的值不能包含最后一列if(this.data[row][i]!=0) return i;}return -1;},/**3.上移**///3.1.moveUp方法:用户有上移操作时触发moveUp:function(){var before=String(this.data);for(var col=0;col<4;col++){//上移时,从第一列开始处理数据this.moveUpinCol(col);}var after=String(this.data);if(before!=after){this.randomNum();//获取2或4if(this.isGameOver()) this.status==this.gameover;//若游戏结束就更新状态码this.dataView();//更新视图}},//3.2.moveUpinColmoveUpinCol:function(col){// 上移过程中需要处理的两种情况:1.当前元素值为0;2.当前元素和nextRow一样for(var row=0;row<3;row++){var nextRow=this.moveUpNum(row,col);if(nextRow!=-1){if(this.data[row][col]==0){this.data[row][col]=this.data[nextRow][col];this.data[nextRow][col]=0;row--;}else if(this.data[row][col]==this.data[nextRow][col]){this.data[row][col]*=2;this.data[nextRow][col]=0;this.score+=this.data[row][col];}}else{break;}}},//3.3.moveUpNum方法:获取向上遍历时符合条件的非空元素moveUpNum:function(row,col){for(var i=row+1;i<4;i++){//让nextCol的值从第二行开始遍历if(this.data[i][col]!=0) return i;}return -1;},/**4.下移**///4.1.moveDown:用户有下移操作时触发moveDown: function () {var before = String(this.data);for (var col = 0; col < 4; col++) {//下移时,从第一列开始处理数据this.moveDowninCol(col);}var after = String(this.data);if (before != after) {this.randomNum();//获取2或4if (this.isGameOver()) { this.status = this.gameover;//更新状态码}this.dataView();//更新视图}},//4.2.moveDowninColmoveDowninCol: function (col) {// 下移过程中需要处理的两种情况:1.当前元素值为0;2.当前元素和lastRow一样for(var row=3;row>=0;row--){//下移时需要从最后一行开始遍历,当前元素是下元素,lastRow是下元素var lastRow=this.moveDownNum(row,col);if(lastRow!=-1){if(this.data[row][col]==0){// 1.当前元素的值为0this.data[row][col]=this.data[lastRow][col];this.data[lastRow][col]=0;row++;//重新回到下面一行进行循环}else if(this.data[row][col]==this.data[lastRow][col]){// 2.当前元素和lastRow一样this.data[row][col]*=2;this.data[lastRow][col]=0;this.score+=this.data[row][col];}}else{break;}}},//4.3.moveDownNum方法:获取向下遍历时符合条件的非空元素moveDownNum:function(row,col){for (var i = row - 1; i >= 0; i--) {//让nextCol的值从第二行开始遍历if (this.data[i][col] != 0) return i;}return -1;}
};/*二.监听鼠标按下事件,并调用game对象中的移动方法*/
//如果监听到上下左右对应的ASCII码,就触发相应的上下左右game移动方法
document.onkeydown = function (event) {if (event.keyCode == 65) game.moveLeft();//键盘左:Aif (event.keyCode == 87) game.moveUp();//键盘上:Wif (event.keyCode == 68) game.moveRight();//键盘右:Dif (event.keyCode == 83) game.moveDown();//键盘下:S
}/*三.游戏开始时,调用game对象中的start方法*/
game.start();
/*四.点击再来一次*/
//隐藏#gameover元素,并跳转回2048.html
function typeonce() {document.getElementById("gameover").style.display = "none";window.location.href = "2048.html";
}
 
四.效果

相关文章:
2023-04-14 使用纯JS实现一个2048小游戏
文章目录 一.实现思路1.2048的逻辑2.移动操作的过程中会有三种情况 二.代码部分:分为初始化部分和移动部分1.初始化部分1.1.生成第一个方块:1.2.生成第二个方块: 2.移动过程部分: 三.实现代码1.HTML部分2.CSS部分3.JS部分3.1.game对象的属性3.2.game对象的start方法3.3.game对象…...
C++入门(3)
C入门 1.auto关键字(C11)1.1. 类型别名的思考1.2. auto简介1.3. auto使用情景1.4. auto的使用细则1.5. auto不能推导的场景 1.auto关键字(C11) 1.1. 类型别名的思考 随着程序越来越复杂,程序中用到的类型也越来越复杂…...
【亲测有效】更新了WIN11之后 右键无 新建WORD,PPT,EXCEL 选项 问题 解决方案
原本正常的正版系统,在昨天4月自动更新安装之后,发现右键找 不到新建文档了,word,ppt,excel都不见了。 看了网上大神的方法 Win11安装了Office右键没有新建Excel选项怎么办? - 知乎 可以解决一部分 官方解决方案,亲…...
2023年4月北京/西安/郑州/深圳CDGA/CDGP数据治理认证考试报名
DAMA认证为数据管理专业人士提供职业目标晋升规划,彰显了职业发展里程碑及发展阶梯定义,帮助数据管理从业人士获得企业数字化转型战略下的必备职业能力,促进开展工作实践应用及实际问题解决,形成企业所需的新数字经济下的核心职业…...
Win10桌面我的电脑怎么调出来?最简单方法教学
Win10桌面我的电脑怎么调出来?有用户发现自己的电脑桌面没有我的电脑这个程序图标,每次要访问磁盘的时候,开启都非常的麻烦。那么怎么将这个图标设置到桌面显示呢?接下来我们一起来看看以下的解决方法吧。 方法一: 在开…...
开启单细胞及空间组学行业发展黄金时代!首届国际单细胞及空间组学大会在穗闭幕
2023年4月16日,首届TICSSO国际单细胞及空间组学大会圆满闭幕,本次大会吸引了2000余位来自产、学、研、资、医、政、媒等业界人士齐聚羊城,注册总人数5398人,网络播放总量达548245人次,网络观看覆盖美国、德国、日本、澳…...
YOLOv8 更换主干网络之 GhostNetV2
《GhostNetV2:Enhance Cheap Operation with Long-Range Attention》 轻量级卷积神经网络(CNN)是专门为在移动设备上具有更快推理速度的应用而设计的。卷积操作只能捕捉窗口区域内的局部信息,这防止了性能的进一步提高。将自注意力引入卷积可以很好地捕捉全局信息,但这将大…...
高级服务框架(黑马)
一、修改order-service服务 修改OrderService,让其监听Nacos中的sentinel规则配置。 具体步骤如下: 1.引入依赖 在order-service中引入sentinel监听nacos的依赖: <dependency><groupId>com.alibaba.csp</groupId><…...
Go语言面试题--基础语法(29)
文章目录 1.下面的代码有什么问题?2.下面代码最后一行输出什么?请说明原因3.下面代码有什么问题?4.下面的代码输出什么? 1.下面的代码有什么问题? func main() {data : []int{1,2,3}i : 0ifmt.Println(data[i]) }参考…...
毕业生招聘信息的发布与管理系统(论文+设计)
前 言 当今,人类社会已经进入信息全球化和全球信息化、网络化的高速发展阶段。丰富的网络信息已经成为人们工作、生活、学习中不可缺少的一部分。人们正在逐步适应和习惯于网上贸易、网上购物、网上支付、网上服务和网上娱乐等活动,人类的许多社会活动…...
mysql安全加固配置文档(完结)
4. MySQL 权限安全配置 4.1. 确保只有管理员账号有所有数据库的访问权限 建议理由 除了管理员账号,其他用户没必要有所有数据库的访问权限。过高的权限会导致安全问题。检查方法 SELECT user, host FROM MySQL.user WHERE (Select_priv Y) OR (Insert_priv Y) …...
CAPL函数在实现AES加密算法时遇到的各种问题(c++中符号的含义,AES算法中padding的问题等)
本来打算把AES算法的代码移植到CAPL中的,文章:https://blog.csdn.net/qq_28205153/article/details/55798628?spm=1001.2014.3001.5506里有非常详细的代码。但是一来太麻烦,二来没必要,因为CAPL提供了Security安全相关的函数: 这里面就提供了AES加密算法的接口函数,使用…...
二叉排序树(二叉查找树)基本操作_20230417
二叉排序树(二叉查找树)基本操作_20230417 前言 二叉排序树首先是一颗二叉树,它不同于常规二叉树的地方在于,如果左子树不为空,那么左子树上所有结点的值都不大于根节点的值,如果右子树不为空,…...
实现服务器版本的表白墙
目录 初始前端代码 网页初始效果 一、确定接口 二、编写代码 2.1 创建项目七步走 1、创建Maven项目 2、引入依赖 3、构建目录 4、编写代码 5、打包、部署 编辑 7、验证代码 三、具体的代码逻辑 3.1 服务器——两个服务接口 3.2 前端页面的代码 3.2.1 前端存档…...
TensorFlow 2 和 Keras 高级深度学习:6~10
原文:Advanced Deep Learning with TensorFlow 2 and Keras 协议:CC BY-NC-SA 4.0 译者:飞龙 本文来自【ApacheCN 深度学习 译文集】,采用译后编辑(MTPE)流程来尽可能提升效率。 不要担心自己的形象&#x…...
unity,制作一个环状滑动条
介绍 unity,制作一个环状滑动条 方法 1.导入png图片素材2.新建一个滑动条,两者图片都设置为图片3.调节slider的参数4.调节backgroud的参数5.fill area、fill的参数同上。 得到两个叠加的圆环。6.设置fill的背景颜色为红色7.设置fill填充方式࿰…...
2023-04-17 算法面试中常见的树和递归问题
二叉树和递归 0 LeetCode297 二叉树的序列化和反序列化 序列化是将一个数据结构或者对象转换为连续的比特位的操作,进而可以将转换后的数据存储在一个文件或者内存中,同时也可以通过网络传输到另一个计算机环境,采取相反方式重构得到原数据…...
3分钟通过日志定位bug,这个技能测试人必须会
♥ 前 言 软件开发中通过日志记录程序的运行情况是一个开发的好习惯,对于错误排查和系统运维都有很大帮助。 Python 标准库自带了强大的 logging 日志模块,在各种 python 模块中得到广泛应用。 一、简单使用 1. 入门小案例 import logging logging.ba…...
【论文总结】V-Shuttle:可扩展和语义感知的 Hypervisor 虚拟设备模糊测试
介绍 这是来自2021 CCS的一篇论文,作者有GaoningPan, Xingwei Lin, Xuhong Zhang, Yongkang Jia, Shouling Ji, Chunming Wu, Xinlei Ying, Jiashui Wang, Yanjun Wu。该论文提出V-shuttle的新框架来执行管控程序的模糊测试,该框架执行可扩展和语义感知…...
一篇文章让你搞懂TypeScript中的typeof()、keyof()是什么意思
TypeScript中的typeof()、keyof()是什么意思? 知识回调(不懂就看这儿!)场景复现核心干货👇👇👇举例引入字面量类型(literal types&…...
【kafka】Golang实现分布式Masscan任务调度系统
要求: 输出两个程序,一个命令行程序(命令行参数用flag)和一个服务端程序。 命令行程序支持通过命令行参数配置下发IP或IP段、端口、扫描带宽,然后将消息推送到kafka里面。 服务端程序: 从kafka消费者接收…...
基于距离变化能量开销动态调整的WSN低功耗拓扑控制开销算法matlab仿真
目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.算法仿真参数 5.算法理论概述 6.参考文献 7.完整程序 1.程序功能描述 通过动态调整节点通信的能量开销,平衡网络负载,延长WSN生命周期。具体通过建立基于距离的能量消耗模型&am…...
通过Wrangler CLI在worker中创建数据库和表
官方使用文档:Getting started Cloudflare D1 docs 创建数据库 在命令行中执行完成之后,会在本地和远程创建数据库: npx wranglerlatest d1 create prod-d1-tutorial 在cf中就可以看到数据库: 现在,您的Cloudfla…...
【SpringBoot】100、SpringBoot中使用自定义注解+AOP实现参数自动解密
在实际项目中,用户注册、登录、修改密码等操作,都涉及到参数传输安全问题。所以我们需要在前端对账户、密码等敏感信息加密传输,在后端接收到数据后能自动解密。 1、引入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId...
Qwen3-Embedding-0.6B深度解析:多语言语义检索的轻量级利器
第一章 引言:语义表示的新时代挑战与Qwen3的破局之路 1.1 文本嵌入的核心价值与技术演进 在人工智能领域,文本嵌入技术如同连接自然语言与机器理解的“神经突触”——它将人类语言转化为计算机可计算的语义向量,支撑着搜索引擎、推荐系统、…...
Python爬虫(一):爬虫伪装
一、网站防爬机制概述 在当今互联网环境中,具有一定规模或盈利性质的网站几乎都实施了各种防爬措施。这些措施主要分为两大类: 身份验证机制:直接将未经授权的爬虫阻挡在外反爬技术体系:通过各种技术手段增加爬虫获取数据的难度…...
Robots.txt 文件
什么是robots.txt? robots.txt 是一个位于网站根目录下的文本文件(如:https://example.com/robots.txt),它用于指导网络爬虫(如搜索引擎的蜘蛛程序)如何抓取该网站的内容。这个文件遵循 Robots…...
PostgreSQL——环境搭建
一、Linux # 安装 PostgreSQL 15 仓库 sudo dnf install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-$(rpm -E %{rhel})-x86_64/pgdg-redhat-repo-latest.noarch.rpm# 安装之前先确认是否已经存在PostgreSQL rpm -qa | grep postgres# 如果存在࿰…...
MySQL 索引底层结构揭秘:B-Tree 与 B+Tree 的区别与应用
文章目录 一、背景知识:什么是 B-Tree 和 BTree? B-Tree(平衡多路查找树) BTree(B-Tree 的变种) 二、结构对比:一张图看懂 三、为什么 MySQL InnoDB 选择 BTree? 1. 范围查询更快 2…...
消息队列系统设计与实践全解析
文章目录 🚀 消息队列系统设计与实践全解析🔍 一、消息队列选型1.1 业务场景匹配矩阵1.2 吞吐量/延迟/可靠性权衡💡 权衡决策框架 1.3 运维复杂度评估🔧 运维成本降低策略 🏗️ 二、典型架构设计2.1 分布式事务最终一致…...
