JS贪吃蛇小游戏
- 一、实现功能
- 二、结果展示
- 三、开始制作
- 1、配置并计算数据
- 2、搭建页面
- 3、开始游戏
- 4、移动小蛇并判断吃到食物,判断游戏结束
- 4.1 判断吃到食物
- 4.2 判断游戏结束
- 4.3 代码实现
- 5、操作栏监听以及键盘快捷键监听
- 三、完整源码
- 四、结语
一、实现功能
配置化制定,小蛇移动,小蛇死亡,分数统计,暂停游戏,继续游戏。
二、结果展示
三、开始制作
1、配置并计算数据
初始化配置数据,如游戏界面宽高,小蛇初始长度,游戏移动时间间隔,并按照界面宽高计算出小蛇上下左右移动以及穿墙步进值。
配置数据
//蛇蛇初始长度
let snake_initial_length = 3;
//游戏界面宽度
let view_width = 40;
//游戏界面高度
let view_height = 30;
// 移动时间毫秒
let move_time = 500;
按照配置数据计算各个步进值
//上下行动每列距离
let top_size = view_width;
//左右行动每列距离
let left_size = 1;
//上下穿墙距离
let top_through_size = view_width * view_height - view_width;
//左右穿墙距离
let left_through_size = view_width - 1;
2、搭建页面
按照配置页面宽高生成小蛇移动网格。
//界面初始化
function game_view_init() {
let str = '';
for (var i = 1; i <= view_height; i++) {
str += "<div class='div_line'>"
for (var j = 1; j <= view_width; j++) {
str += "<div class='view_list'></div>"
}
str += "</div>"
}
$('#game_view_box').html(str)
}
3、开始游戏
随机获取小蛇移动方向(1上 2右 3下 4左),并初始化游戏分数,按照移动方向个配置小蛇长度初始化小蛇数据,开启定时器开始移动小蛇和生成食物。
//开始游戏
function start_game() {
//初始定时器
if (stop_obj) {
stop_game()
}
//初始分数
game_score = 0;
$('#game_score').text(game_score);
//游戏状态
game_status = true;
//随机获取移动方向
move_direction = Math.floor(Math.random() * 4 + 1)
//获取初始小蛇数据
let base_number = Math.floor(Math.random() * 600 + 1)
snake_data = [];
snake_data.push(base_number)
if (move_direction == 1) {
for (let i = 1; i <= snake_initial_length; i++) {
snake_data.push(base_number + (top_size * i))
}
}
if (move_direction == 2) {
for (let i = 1; i <= snake_initial_length; i++) {
snake_data.push(base_number - (left_size * i))
}
}
if (move_direction == 3) {
for (let i = 1; i <= snake_initial_length; i++) {
snake_data.push(base_number - (top_size * i))
}
}
if (move_direction == 4) {
for (let i = 1; i <= snake_initial_length; i++) {
snake_data.push(base_number + (left_size * i))
}
}
//修改颜色
edit_color();
//移动小蛇
move_snack();
//创建食物
create_food();
}
//修改小蛇颜色
function edit_color() {
$('.view_list').each(function (k, v) {
let key = k + 1
if (snake_data.indexOf(key) != -1) {
$(this).addClass('snack_sel')
} else {
if ($(this).hasClass('snack_sel')) {
$(this).removeClass('snack_sel')
}
}
})
}
//创建食物
function create_food() {
if (!food_number) {
flag = true;
while (flag) {
food_number = Math.floor(Math.random() * 900 + 1)
if (snake_data.indexOf(food_number) == -1) {
flag = false;
}
}
$('.view_list').each(function (k, v) {
let key = k + 1
if (food_number == key) {
$(this).addClass('food_sel')
} else {
if ($(this).hasClass('food_sel')) {
$(this).removeClass('food_sel')
}
}
})
}
}
4、移动小蛇并判断吃到食物,判断游戏结束
4.1 判断吃到食物
当小蛇所在数组包含食物所在数时判断吃到食物,并销毁当前食物数据,重新生成食物数据,并且小蛇最后加一位(加的一位为异动前小蛇数据的最后的一位)(这里逻辑有点绕)
判断
4.2 判断游戏结束
当小蛇咬到自己的时候游戏结束,判断逻辑为将小蛇数组排序,判断数组内有相同数的时候游戏结束。
4.3 代码实现
//移动蛇蛇
function move_snack() {
stop_obj = setInterval(function () {
let old_snack_data = JSON.parse(JSON.stringify(snake_data))
let next_number = 0
switch (move_direction) {
case 1:
if (snake_data[0] <= top_size) {
next_number = snake_data[0] + top_through_size
} else {
next_number = snake_data[0] - top_size
}
break;
case 2:
if (snake_data[0] % view_width == 0) {
next_number = snake_data[0] - left_through_size
} else {
next_number = snake_data[0] + left_size
}
break;
case 3:
if (snake_data[0] >= top_through_size) {
next_number = snake_data[0] - top_through_size
} else {
next_number = snake_data[0] + top_size
}
break;
case 4:
if (snake_data[0] % view_width == 1) {
next_number = snake_data[0] + left_through_size
} else {
next_number = snake_data[0] - left_size
}
break;
}
snake_data = snake_data.reverse();
$.each(snake_data, function (k, v) {
if (k == snake_data.length - 1) {
snake_data[k] = next_number
} else {
snake_data[k] = snake_data[k + 1]
}
})
snake_data = snake_data.reverse();
if (snake_data.indexOf(food_number) != -1) {
food_number = ''
snake_data.push(old_snack_data[old_snack_data.length - 1])
game_score += 1
$('#game_score').text(game_score);
create_food()
}
let order_snake_data = JSON.parse(JSON.stringify(snake_data))
order_snake_data = positive_sort(order_snake_data)
$.each(order_snake_data, function (k, v) {
if (k != order_snake_data.length - 1 && order_snake_data[k] == order_snake_data[k + 1]) {
alert('游戏结束!最终分数为' + game_score + '!')
//清除定时器
clearInterval(stop_obj)
stop_obj = null
//修改游戏状态
game_status = false
//保持小蛇上一个状态
snake_data = old_snack_data
}
})
edit_color()
}, move_time)
}
//一维数组正序排序
function positive_sort(data) {
data.sort((x, y) => {
return x - y
})
return data;
}
//一维数组倒序排序
function reverse_sort(data) {
data.sort((x, y) => {
return y - x
})
return data;
}
5、操作栏监听以及键盘快捷键监听
暂停游戏,继续游戏,页面方向监听,上下左右键监听以及WASD键监听
//暂停游戏
function stop_game() {
clearInterval(stop_obj)
stop_obj = null
}
//继续游戏
function continue_game() {
if (!game_status) {
alert('游戏已结束!请重新开始!')
return false;
}
move_snack()
}
//修改方向
function edit_direction(direction) {
if (move_direction == 1 && direction == 3) {
return false;
}
if (move_direction == 2 && direction == 4) {
return false;
}
if (move_direction == 3 && direction == 1) {
return false;
}
if (move_direction == 4 && direction == 2) {
return false;
}
move_direction = direction
}
//监听键盘
$(document).keydown(function (event) {
console.log(event.keyCode)
if ((event.keyCode == 38 || event.keyCode == 87) && move_direction != 3) {
//上
move_direction = 1;
} else if ((event.keyCode == 39 || event.keyCode == 68) && move_direction != 4) {
//右
move_direction = 2;
} else if ((event.keyCode == 40 || event.keyCode == 83) && move_direction != 1) {
//下
move_direction = 3;
} else if ((event.keyCode == 37 || event.keyCode == 65) && move_direction != 2) {
//左
move_direction = 4;
}
});
三、完整源码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>贪吃蛇--超级小胖子的博客</title>
<style>
body {
display: flex;
flex-wrap: wrap;
justify-content: left;
padding-top: 25px;
min-width: 1400px;
}
.game_view_box {
width: 1000px;
display: flex;
flex-wrap: wrap;
justify-content: center;
}
.operation_view_box {
width: 380px;
margin-left: 20px;
}
.view_list {
width: 18px;
border: 1px solid black;
height: 18px;
font-size: 10px;
}
.snack_sel {
background-color: #00ac78;
}
.food_sel {
background-color: #ff4100;
}
.div_line {
width: 100%;
display: flex;
flex-wrap: nowrap;
justify-content: end;
}
.game_btn {
width: 100px;
height: 30px;
text-align: center;
line-height: 30px;
border-radius: 5px;
-moz-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
-khtml-user-select: none;
user-select: none;
}
.game_score {
width: 80%;
margin-top: 20px;
}
.game_score span {
font-weight: bold;
font-size: 20px;
color: #ff273f;
}
.direction_box {
width: 50%;
text-align: center;
margin-top: 100px;
}
.direction_box_list {
width: 100%;
display: flex;
flex-wrap: wrap;
justify-content: center;
margin-top: 15px;
-moz-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
-khtml-user-select: none;
user-select: none;
}
</style>
</head>
<body>
<div class="game_view_box" id="game_view_box">
</div>
<div class="operation_view_box" id="operation_view_box">
<div class="direction_box">
<div class="direction_box_list">
<div style="width: 50%;">
<div onclick="edit_direction(1)"
style="line-height:50px;width: 50px;height: 50px;background-color: #00ac78">
上
</div>
</div>
</div>
<div class="direction_box_list">
<div style="width: 50%">
<div onclick="edit_direction(4)"
style="line-height:50px;width: 50px;height: 50px;background-color: #00ac78">
左
</div>
</div>
<div style="width: 50%">
<div onclick="edit_direction(2)"
style="line-height:50px;width: 50px;height: 50px;background-color: #00ac78">
右
</div>
</div>
</div>
<div class="direction_box_list">
<div style="width: 50%">
<div onclick="edit_direction(3)"
style="line-height:50px;width: 50px;height: 50px;background-color: #00ac78">
下
</div>
</div>
</div>
</div>
<div class="game_score">游戏分数:<span id="game_score">0</span></div>
<div class="game_btn" style="background-color: #00ac78;margin-top: 20px;" onclick="start_game()">开始游戏</div>
<div class="game_btn" style="background-color: #ff4100;margin-top: 20px;" onclick="stop_game()">停止游戏</div>
<div class="game_btn" style="background-color: #1806ff;margin-top: 20px;" onclick="continue_game()">继续游戏</div>
</div>
</body>
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
<script>
//1 上 2 右 3 下 4 左
let move_direction = 1;
//蛇蛇数据
let snake_data = [];
//定时器对象
let stop_obj = '';
//食物数据
let food_number = '';
//获得分数
let game_score = 0;
//蛇蛇初始长度
let snake_initial_length = 3;
//游戏界面宽度
let view_width = 40;
//游戏界面高度
let view_height = 30;
// 移动时间毫秒
let move_time = 500;
//上下行动每列距离
let top_size = view_width;
//左右行动每列距离
let left_size = 1;
//上下穿墙距离
let top_through_size = view_width * view_height - view_width;
//左右穿墙距离
let left_through_size = view_width - 1;
//游戏状态
let game_status = false;
//界面初始化
function game_view_init() {
let str = '';
for (var i = 1; i <= view_height; i++) {
str += "<div class='div_line'>"
for (var j = 1; j <= view_width; j++) {
str += "<div class='view_list'></div>"
}
str += "</div>"
}
$('#game_view_box').html(str)
}
game_view_init();
//开始游戏
function start_game() {
//初始定时器
if (stop_obj) {
stop_game()
}
//初始分数
game_score = 0;
$('#game_score').text(game_score);
//游戏状态
game_status = true;
//随机获取移动方向
move_direction = Math.floor(Math.random() * 4 + 1)
//获取初始小蛇数据
let base_number = Math.floor(Math.random() * 600 + 1)
snake_data = [];
snake_data.push(base_number)
if (move_direction == 1) {
for (let i = 1; i <= snake_initial_length; i++) {
snake_data.push(base_number + (top_size * i))
}
}
if (move_direction == 2) {
for (let i = 1; i <= snake_initial_length; i++) {
snake_data.push(base_number - (left_size * i))
}
}
if (move_direction == 3) {
for (let i = 1; i <= snake_initial_length; i++) {
snake_data.push(base_number - (top_size * i))
}
}
if (move_direction == 4) {
for (let i = 1; i <= snake_initial_length; i++) {
snake_data.push(base_number + (left_size * i))
}
}
//修改颜色
edit_color();
//移动小蛇
move_snack();
//创建食物
create_food();
}
//暂停游戏
function stop_game() {
clearInterval(stop_obj)
stop_obj = null
}
//继续游戏
function continue_game() {
if (!game_status) {
alert('游戏已结束!请重新开始!')
return false;
}
move_snack()
}
//修改方向
function edit_direction(direction) {
if (move_direction == 1 && direction == 3) {
return false;
}
if (move_direction == 2 && direction == 4) {
return false;
}
if (move_direction == 3 && direction == 1) {
return false;
}
if (move_direction == 4 && direction == 2) {
return false;
}
move_direction = direction
}
//移动蛇蛇
function move_snack() {
stop_obj = setInterval(function () {
let old_snack_data = JSON.parse(JSON.stringify(snake_data))
let next_number = 0
switch (move_direction) {
case 1:
if (snake_data[0] <= top_size) {
next_number = snake_data[0] + top_through_size
} else {
next_number = snake_data[0] - top_size
}
break;
case 2:
if (snake_data[0] % view_width == 0) {
next_number = snake_data[0] - left_through_size
} else {
next_number = snake_data[0] + left_size
}
break;
case 3:
if (snake_data[0] >= top_through_size) {
next_number = snake_data[0] - top_through_size
} else {
next_number = snake_data[0] + top_size
}
break;
case 4:
if (snake_data[0] % view_width == 1) {
next_number = snake_data[0] + left_through_size
} else {
next_number = snake_data[0] - left_size
}
break;
}
snake_data = snake_data.reverse();
$.each(snake_data, function (k, v) {
if (k == snake_data.length - 1) {
snake_data[k] = next_number
} else {
snake_data[k] = snake_data[k + 1]
}
})
snake_data = snake_data.reverse();
if (snake_data.indexOf(food_number) != -1) {
food_number = ''
snake_data.push(old_snack_data[old_snack_data.length - 1])
game_score += 1
$('#game_score').text(game_score);
create_food()
}
let order_snake_data = JSON.parse(JSON.stringify(snake_data))
order_snake_data = positive_sort(order_snake_data)
$.each(order_snake_data, function (k, v) {
if (k != order_snake_data.length - 1 && order_snake_data[k] == order_snake_data[k + 1]) {
alert('游戏结束!最终分数为' + game_score + '!')
//清除定时器
clearInterval(stop_obj)
stop_obj = null
//修改游戏状态
game_status = false
//保持小蛇上一个状态
snake_data = old_snack_data
}
})
edit_color()
}, move_time)
}
//修改小蛇颜色
function edit_color() {
$('.view_list').each(function (k, v) {
let key = k + 1
if (snake_data.indexOf(key) != -1) {
$(this).addClass('snack_sel')
} else {
if ($(this).hasClass('snack_sel')) {
$(this).removeClass('snack_sel')
}
}
})
}
//创建食物
function create_food() {
if (!food_number) {
flag = true;
while (flag) {
food_number = Math.floor(Math.random() * 900 + 1)
if (snake_data.indexOf(food_number) == -1) {
flag = false;
}
}
$('.view_list').each(function (k, v) {
let key = k + 1
if (food_number == key) {
$(this).addClass('food_sel')
} else {
if ($(this).hasClass('food_sel')) {
$(this).removeClass('food_sel')
}
}
})
}
}
//监听键盘
$(document).keydown(function (event) {
console.log(event.keyCode)
if ((event.keyCode == 38 || event.keyCode == 87) && move_direction != 3) {
//上
move_direction = 1;
} else if ((event.keyCode == 39 || event.keyCode == 68) && move_direction != 4) {
//右
move_direction = 2;
} else if ((event.keyCode == 40 || event.keyCode == 83) && move_direction != 1) {
//下
move_direction = 3;
} else if ((event.keyCode == 37 || event.keyCode == 65) && move_direction != 2) {
//左
move_direction = 4;
}
});
//一维数组正序排序
function positive_sort(data) {
data.sort((x, y) => {
return x - y
})
return data;
}
//一维数组倒序排序
function reverse_sort(data) {
data.sort((x, y) => {
return y - x
})
return data;
}
</script>
</html>
四、结语
一个比较简单但也比较有趣的贪吃蛇DEMO,大家有兴趣可以按照这个DEMO进行延伸,如不同关卡难度设置,难度设置如不可穿墙,设置障碍物,撞到障碍物即游戏结束,移动时间加快等等,有什么问题欢迎大家讨论和留言,喜欢的同学们点赞关注收藏哦!