本文详细解析使用JavaScript实现简易版消灭星星游戏的完整流程。内容涵盖游戏初始化、基于递归的相邻方块匹配算法、点击消除与重力下落逻辑,以及分数计算机制。通过HTML结构、CSS布局及核心JS代码的逐步拆解,帮助开发者理解基于二维数组的网格游戏状态管理与DOM操作技巧。
项目概述与开发背景
在观看相关视频教程后,经过思路整理,完成了简易版消灭星星游戏的开发。
核心算法与逻辑实现
整体开发思路如下:
模块1:初始化阶段
- 主要任务包括初始化总分数、当前分数、背景图片以及初始选中的星星分数。
- 具体实现为生成二维数组,遍历该数组中的每个对象并设置其样式(包括长、宽、背景图),随后生成对应的div元素节点并插入到游戏面板中。
模块2:预判与交互
交互判断逻辑如下:
当鼠标移动到某个方块时,利用递归方法判断其上下左右是否存在相连的同色小方块,并将这些相连方块的索引存储到数组choose[]中;若鼠标移开至其他方块,则清空choose数组。
视觉反馈(闪烁):
对已选中的小方块应用特定样式,例如通过缩放效果进行高亮显示。
分数预览:
设定初始分数与递增分数规则,根据当前选中块的数量动态计算并显示预估得分。
模块3:点击处理
消除逻辑:
当用户点击已选中的小方块时,将二维数组中对应所有相连小方块的位置标记为空,并清空choose数组。
位置移动(重力下落):
下移处理:设置一个指针指向当前列的最底行。遍历每一行,若遇到非空方块,则指针递增;若遇到某列为空,则将指针指向当前行号i。
左移处理:若最底部一行存在空列,则将右侧所有方块的列索引减1,实现紧凑排列。
状态判断:
每次点击操作完成后,检查游戏是否结束。
代码结构与优化建议
代码实现部分
HTML结构
HTML结构相对简单,主要包含游戏面板及分数显示区域。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="./index.js"></script>
<link rel="stylesheet" href="index.css" >
</head>
<body>
<div id="pop_star">
<div id="target_score">目标分数:2000</div>
<div id="now_score">当前分数:0</div>
<div id="select_score">0块 0分</div>
</div>
</body>
</html>
CSS样式
CSS布局较为直观,主要涉及游戏面板的尺寸、背景及文本样式设置。
* {
margin: 0px;
padding: 0px;
}
html, body {
height: 100%;
width: 100%;
}
#pop_star {
width: 500px;
height: 100%;
background: url("./pic/background.png");
margin-left: auto;
margin-right: auto;
position: relative;
background-size: cover;
font-size: 0px;
}
#target_score {
width: 100%;
height: 50px;
line-height: 50px;
text-align: center;
color: white;
font-size: 20px;
position: relative;
}
#now_score {
width: 100%;
height: 50px;
line-height: 50px;
text-align: center;
color: white;
font-size: 20px;
position: relative;
}
#select_score {
width: 100%;
height: 50px;
line-height: 50px;
text-align: center;
color: white;
font-size: 20px;
position: relative;
opacity: 0;
}
JavaScript核心逻辑
/*
创建二维数组
*
*
* */
var table ;
var suqareWidth = 50 ; //一个星星/方块边长
var boardWidth = 10 ; //横竖方块个数
var squareSet = [];//小方块的集合,二维数组
var choose = [];//有相邻的小方块,将其放到这个数组
var timer = null ;
var baseScore = 5 ;
var stepScore = 10 ;
var totalScore = 0 ;
var targetScore = 1500;
var flag = true ;
var tempSquare = null;//在处理鼠标动作过程中,动作被屏蔽,导致事件处理完成,有不连贯现象
function createSquare(value , row , col){
//创建小方块节点
var blocks = document.createElement('div');
//设置样式
blocks.style.width = suqareWidth + 'px';
blocks.style.height = suqareWidth + 'px';
blocks.style.display = 'inline-block';
blocks.style.boxSizing = 'border-box';
blocks.style.position = 'absolute';
blocks.style.borderRadius = "12px";
//小方块的行和列,小方块的num.jpg
blocks.num = value ;
blocks.row = row ;
blocks.col = col ;
return blocks;
}
function refresh(){
for(var i = 0 ; i < squareSet.length ; i ++){
for (var j = 0 ; j < squareSet[i].length ; j++) {
//严谨判断
if (squareSet[i][j] == null) {
continue;
}
//将二维数组里面的小方块对应面板的行和列显示
squareSet[i][j].row = i;
squareSet[i][j].col = j;
//列*方块长度
squareSet[i][j].style.transition = "left 0.3s, bottom 0.3s";
squareSet[i][j].style.left = squareSet[i][j].col * suqareWidth + 'px';
squareSet[i][j].style.bottom = squareSet[i][j].row * suqareWidth + 'px';
//背景图
squareSet[i][j].style.backgroundImage = "url('img/" + squareSet[i][j].num + ".png')";
squareSet[i][j].style.backgroundSize = 'cover';
squareSet[i][j].style.transform = 'scale(0.95)';//是图片缩小至原来的0.95倍
}
}
}
function checkLinked(square , arr){
//严谨判断
if(square == null){
return;
}
//添加小方块到arr
arr.push(square);
/*
判断位于该小方块左边的小方格是否能被收录进选择数组
1.小方格不能是最左边的
2.小方格左边必须有小方块
3.小方块左边的要和该小方块颜色相同
4.该小方块左边没有被收录到数组中去
5.递归
*
* */
//向左
if(square.col > 0 && squareSet[square.row][square.col - 1]
&& squareSet[square.row][square.col - 1].num == square.num
&& arr.indexOf(squareSet[square.row][square.col - 1]) == -1){
checkLinked(squareSet[square.row][square.col - 1] , arr);
}
//向右
if(square.col < boardWidth - 1 && squareSet[square.row][square.col + 1]
&& squareSet[square.row][square.col + 1].num == square.num
&& arr.indexOf(squareSet[square.row][square.col + 1]) == -1){
checkLinked(squareSet[square.row][square.col + 1] , arr);
}
//向上
if(square.row < boardWidth - 1 && squareSet[square.row + 1][square.col ]
&& squareSet[square.row + 1][square.col].num == square.num
&& arr.indexOf(squareSet[square.row + 1][square.col ]) == -1){
checkLinked(squareSet[square.row + 1][square.col] , arr);
}
//向上
if(square.row > 0 && squareSet[square.row - 1][square.col]
&& squareSet[square.row - 1][square.col].num == square.num
&& arr.indexOf(squareSet[square.row - 1][square.col]) == -1){
checkLinked(squareSet[square.row - 1][square.col] , arr);
}
}
//让选中的小方块闪烁
function flicker(arr){
var num = 0 ;
//设置计时器,让其一之闪烁
timer = setInterval(function(){
for (var i = 0 ; i < arr.length ; i++) {
//设置缩放样式
arr[i].style.border = "3px solid #BFEFFF";
arr[i].style.transform = "scale("+(0.9 + 0.05 *Math.pow(-1 , num))+")";
}
//小方块闪烁完成之后num++,使其再次缩放
num++;
},300);
}
function back(){
//若计时器还存在,清楚计数器
if(timer != null){
clearInterval(timer);
}
//返回原样式
for(var i = 0 ; i < squareSet.length ; i++){
for(var j = 0 ; j < squareSet[i].length ; j++){
//严谨判断
if (squareSet[i][j] == null) {
continue;
}
squareSet[i][j].style.border = "0px solid #BFEFFF";
squareSet[i][j].style.transform = "scale(0.95)";
}
}
}
//选中分数
function selectScore(){
var socre = 0 ;
//遍历choose
for(var i = 0 ; i < choose.length ; i++){
socre += baseScore + stepScore * i ;
}
//严谨判断
if (socre <= 0) {
return ;
}
//设置select_score的样式
var select_score = document.getElementById('select_score');
select_score.innerHTML = choose.length + "块" + socre + "分";
select_score.style.transition = null ;
//设置透明度,让其突然显示
select_score.style.opacity = 1 ;
//让其逐渐消失
setTimeout(function(){
select_score.style.transition = 'opacity 1s';
select_score.style.opacity = 0;
},1000);
}
//鼠标移动到该小方块时,闪烁
function mouseOver(obj){
//当鼠标在移动到该方块突然移动到其他位置时
if(!flag){
tempSquare = obj;
return ;
}
//当鼠标移开选中的方块之后,让其回到原来的样式
back();
//选择相邻相同的小方格
//传一个数组
choose = [];
checkLinked(obj , choose);//obj是当前鼠标移到的小方块,choose是存储响铃小方块的数组
if (choose.length <= 1) {
choose = [] ;
return;
}
//将选中的设置样式,让其闪烁
flicker(choose);
//显示所选中的小方块的分数
selectScore();
}
function move(){
/*
1.设置一个指针,开始的时候指针指向最下面一行
2.此时指针和j是否一样,一样都++。
3.若改行该列该列有小方块,均++,反之j++,pointer不变,循环判断该条件
3.当j移动到该列某行的小方块,该小方块存在,则将j指向的小方块的位置设置为指针指向的那一个小方块的位置
* */
//向下移动
for (var i = 0 ; i < boardWidth ; i ++) {
var pointer = 0;//pointer指向小方块,当遇到null的时候停止,等待上面的小方块落到这里来
for (var j = 0 ; j < boardWidth ; j ++) {
if (squareSet[j][i] != null) {
if (j != pointer) {
squareSet[pointer][i] = squareSet[j][i];
squareSet[j][i].row = pointer;
squareSet[j][i] = null;
}
pointer ++;
}
}
}
//横向移动
for (var i = 0 ; i < squareSet[0].length ; ) {
if (squareSet[0][i] == null) {
for (var j = 0 ; j < boardWidth ; j ++) {
squareSet[j].splice(i, 1);
}
continue;
}
i ++;
}
refresh();
}
function isFinish(){
for (var i = 0 ; i < squareSet.length ; i++) {
for (var j = 0 ; j < squareSet[i].length ; j++) {
//判断周围是否还有可消除的方块
var temp = [];
checkLinked(squareSet[i][j] , temp);
if(temp.length > 1){
return false ;
}
}
}
return true;
}
function init(){
//获取面板
table = document.getElementById('pop_star');
//创建二维数组
for(var i = 0 ; i < boardWidth ; i++){
squareSet[i] = new Array();
for(var j = 0 ; j < boardWidth; j++){
//创建小方块
var square = createSquare(Math.floor(Math.random() * 5), i, j);
//鼠标移动到该方块
square.onmouseover = function(){
mouseOver(this);
}
//点击小方块时的操作
square.onclick = function(){
//小方块在被点击的时候其他操作不能影响他的执行
if(choose.length == 0 || !flag ){
return ;
}
flag = false;
tempSquare = null ;
/*
1.增加当前分数
2.小方块消失
3.向下或想做移动
4.判断游戏是否结束
*/
var socre = 0 ;
//遍历choose
for(var i = 0 ; i < choose.length ; i++){
socre += baseScore + stepScore * i ;
}
totalScore += socre ;//总分数
//改变样式
document.getElementById('now_score').innerHTML = '当前分数:' + totalScore;
//小方块消失
/*
1.从二维数组里面移除选择了的小方块
2.在面板上移除div,不然div会一直占着格子
* */
for(var i = 0 ; i < choose.length ; i++){
//立即函数,立即出发该函数,否则的话,不会执行
(function(i){
setTimeout(function(){
//将二维数组的某一值设置为空,后面的会向前移
squareSet[choose[i].row][choose[i].col] = null ;
//移除div
table.removeChild(choose[i]);
},i * 100);
})(i);
}
//移动
setTimeout(function(){
move();
setTimeout(function(){
var finished = isFinish();
if(finished){
if (totalScore >= targetScore) {
alert('闯关成功');
} else{
alert('闯关失败');
}
}else{//还可以继续
choose = [] ;
flag = true;
mouseOver(tempSquare);
}
} , 300 + choose.length * 150);
},choose.length * 100);
}
//将小方块放进二维数组
squareSet[i][j] = square;
//将创建好的小方块插入到面板中
table.appendChild(square);
}
}
//显示小星星,刷新整个面板
refresh();
}
//页面加载完成之后,初始化所有操作
window.onload = function(){
init();
}
此处提及的优化点在于闯关模式:大致思路为每通过一关增加目标分数,若游戏结束时未达标,则闯关失败并将目标分数重置为初始值。
如有任何疑问,欢迎在评论区留言讨论。
jQuery进阶版的完整源码可访问我的GitHub仓库,具体网址见原文。
以上就是本文的全部内容,希望对各位的学习有所帮助,也欢迎大家多多支持本站。