├── README.md
├── 贪吃蛇.html
└── 贪吃蛇.js
/README.md:
--------------------------------------------------------------------------------
1 | # 贪吃蛇实现(难度可调节)
2 | 在实现过程中需要考虑的一些问题以及解决方式以及游戏的可扩展性
3 | [效果展示,空格键(开始/暂停)](https://htmlpreview.github.io/?https://github.com/L-WJ1995/Snake/blob/master/%E8%B4%AA%E5%90%83%E8%9B%87.html)
4 | 1. Snake的移动方式
5 | 移动方向的判定,在移动过程中,只响应移动方向的left和right,需要注意的是,Snake的长度为1时,此时可以响应所有方向。
6 | * 移动过程中需要注意一个问题,按键的响应间隔,如果不加以限制,则会出现极短时间相反方向按键生效的问题。本例通过设置一个全局变量,执行完一次移动该变量值为ture,执行过程中为false,通过这个值的真假来决定是否响应按键。
7 | 2. 地图食物随机生成
8 | 通过random函数实现随机生成,需要注意的就是生成的食物坐标需要在地图内(不含地图边线),同时不能在Snake的身体上。
9 | 3. 移动中的Snake身体构建和食物
10 | * 身体的构建,本例通过数组的形式储存Snake的每一个部分,再执行完一次移动后,数组自后向前遍历,后一个部分的坐标赋值前一个部分的坐标,Snake_head的坐标则根据方向重新计算。
11 | * 如果移动过程吃到了食物,则Snake的坐标即食物坐标,数组末尾再添加一个原数组的最后一项坐标,同时再次随机生成食物。
12 | 4. 各个游戏结束机制判断
13 | 游戏结束机制有二, Snake_head吃到蛇身/碰撞边界
14 | * Snake_head吃到蛇身,本例采用移动完成后加一次判断,如果Snake_head的坐标在身体部分的坐标集合中已经存在,即可判定Snake_head吃到蛇身,游戏结束。
15 | * 碰撞边界,同样通过Snake_head的坐标判定
16 | 5. 游戏暂停以及重开
17 | 6. 流畅性问题
18 | 移动的流畅性,暂时想到可以通过requestAnimationFrame实现帧动画
19 | 7. 游戏扩展
20 | * 自定义是否可以碰撞边界
21 | * 地图障碍物生成
22 | * 移动过程中同方向的按键响应,加快移动速度
23 |
--------------------------------------------------------------------------------
/贪吃蛇.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
102 | 贪吃蛇
103 |
104 |
105 | 开始游戏
106 | 当前得分: 游戏未开始
107 | 指令状态: 游戏未开始
108 | 游戏难度
109 |
115 |
116 |
119 |
120 |
121 |
--------------------------------------------------------------------------------
/贪吃蛇.js:
--------------------------------------------------------------------------------
1 |
2 | let game_status //进程ID
3 | let status = "off" //开始/暂停
4 | let game_over_val = 0 //游戏状态 1:进行中 0:ganmeover
5 | let key_status = true //按键状态
6 |
7 | addEventListener("keydown",(e)=>{ //监听按键事件
8 | if (e.keyCode === 32) {
9 | game_mode() //执行游戏状态
10 | }
11 | if(!key_status){
12 | return
13 | }
14 | if (e.keyCode == 37 && game_over_val === 1) {
15 | if (coordinate_X.length === 1) {
16 | direction = "after"
17 | } else {
18 | direction = direction === "before" ? "before" : "after"
19 | }
20 | key_status = false
21 | if (status === "on" ) dir.children[1].textContent = direction === "before" ? "\u2192" : "\u2190"
22 | } else if (e.keyCode == 38 && game_over_val === 1) {
23 | if (coordinate_X.length === 1) {
24 | direction = "up"
25 | } else {
26 | direction = direction === "down" ? "down" : "up"
27 | }
28 | key_status = false
29 | if (status === "on") dir.children[1].textContent = direction === "down" ? "\u2193" : "\u2191"
30 | } else if (e.keyCode == 39 && game_over_val === 1) {
31 | if (coordinate_X.length === 1) {
32 | direction = "before"
33 | } else {
34 | direction = direction === "after" ? "after" : "before"
35 | }
36 | key_status = false
37 | if (status === "on") dir.children[1].textContent = direction === "after" ? "\u2190" : "\u2192"
38 | } else if (e.keyCode == 40 && game_over_val === 1) {
39 | if (coordinate_X.length === 1) {
40 | direction = "down"
41 | } else {
42 | direction = direction === "up" ? "up" : "down"
43 | }
44 | key_status = false
45 | if (status === "on") dir.children[1].textContent = direction === "up" ? "\u2191" : "\u2193"
46 | }
47 | })
48 |
49 | //执行游戏状态
50 | function game_mode() {
51 | if (status === "off") {
52 | Start_game.style.visibility = "hidden"
53 | map.style.visibility = "visible"
54 | status = "on"
55 | if (game_over_val === 0) {
56 | init() //初始化
57 | game_over_val = 1
58 | }
59 | if (!foodNode) append_food() //投放食物
60 | game_status = setInterval(move_direction, time) //开始游戏
61 | } else {
62 | key_status = true
63 | Start_game.style.visibility = "visible"
64 | map.style.visibility = "hidden"
65 | status = "off"
66 | Start_game.children[0].textContent = "游戏暂停中"
67 | Start_game.children[0].dataset.text = "..."
68 | clearInterval(game_status) //暂停游戏
69 | }
70 | }
71 |
72 |
73 |
74 | let
75 | direction, //前进方向
76 | coordinate_X,
77 | coordinate_Y,
78 | temp_X,temp_Y, //蛇身构造坐标(交换用)
79 | food_coordinate = [], //食物坐标数组
80 | foodNode, //食物节点
81 | time
82 |
83 |
84 |
85 |
86 | function init(){ //初始化
87 | score.children[1].textContent = "0" //得分
88 | dir.children[1].textContent = "\u2192" //前进方向文本
89 | time = diff.children[1].value - 0 //游戏难度(时间间隔)
90 | direction = "before" //默认前进方向
91 | coordinate_X = [0] //snak的X坐标数组[0] snak_head坐标
92 | coordinate_Y = [320] //snak的Y坐标数组[0] snak_head坐标
93 | Snak_head.style.top = coordinate_Y[0] - 0 + "px"
94 | Snak_head.style.left = coordinate_X[0] + 0 + "px"
95 | let node = map.children
96 | for (let i = node.length - 1; i >= 0; i--) {
97 | if (node[i].className === "Snak_body") map.removeChild(node[i])
98 | }
99 | }
100 |
101 |
102 | function move_direction(){ //移动以及移动过程中的事件判断
103 | let last_body_X = coordinate_X[coordinate_X.length - 1],
104 | last_body_Y = coordinate_Y[coordinate_Y.length - 1]
105 | temp_X = coordinate_X[0]
106 | temp_Y = coordinate_Y[0]
107 | if (direction === "up" && coordinate_Y[0] - 20 >= 0) {
108 | Snak_head.style.top = coordinate_Y[0] - 20 + "px"
109 | coordinate_Y[0] -= 20
110 | } else if (direction === "down" && coordinate_Y[0] + 20 <= 640) {
111 | Snak_head.style.top = coordinate_Y[0] + 20 + "px"
112 | coordinate_Y[0] += 20
113 | } else if (direction === "before" && coordinate_X[0] + 20 <= 640) {
114 | Snak_head.style.left = coordinate_X[0] + 20 + "px"
115 | coordinate_X[0] += 20
116 | } else if (direction === "after" && coordinate_X[0] - 20 >= 0) {
117 | Snak_head.style.left = coordinate_X[0] - 20 + "px"
118 | coordinate_X[0] -= 20
119 | } else {
120 | game_over()
121 | return
122 | }
123 | if (coordinate_X[0] === food_coordinate[0] && coordinate_Y[0] === food_coordinate[1]) {
124 | score.children[1].textContent = score.children[1].textContent - 0 + 1
125 | food_coordinate = []
126 | let Snak_body = document.createElement("div")
127 | Snak_body.className = "Snak_body"
128 | Snak_body.style.left = last_body_X + "px"
129 | Snak_body.style.top = last_body_Y + "px"
130 | map.appendChild(Snak_body)
131 | coordinate_X.push(last_body_X)
132 | coordinate_Y.push(last_body_Y)
133 | map.removeChild(foodNode)
134 | append_food()
135 | }
136 | key_status = true
137 | snake()
138 | if (!game_over_judge()) {
139 | game_over()
140 | }
141 | }
142 |
143 |
144 | function append_food(){ //添加食物
145 | foodNode = document.createElement("span")
146 | foodNode.className = "food_span"
147 | let count = 0, val
148 | while (count != 2) {
149 | val = parseInt(Math.round(Math.random() * 1000) / 20) * 20
150 | if (val > 640) continue
151 | if (coordinate_X.indexOf(val) === -1) {
152 | if (count === 0) {
153 | foodNode.style.left = val + "px"
154 | } else {
155 | foodNode.style.top = val + "px"
156 | }
157 | food_coordinate.push(val)
158 | count++
159 | }
160 | }
161 | map.appendChild(foodNode)
162 | }
163 |
164 | function snake(){ //重构蛇身
165 | let snak = map.getElementsByTagName("div")
166 | for (let i = snak.length - 1; i >= 1; i--) {
167 | if (i === 1) {
168 | snak[1].style.left = temp_X + "px"
169 | snak[1].style.top = temp_Y + "px"
170 | coordinate_X[1] = temp_X
171 | coordinate_Y[1] = temp_Y
172 | break
173 | }
174 | snak[i].style.left = coordinate_X[i - 1] + "px"
175 | snak[i].style.top = coordinate_Y[i - 1] + "px"
176 | coordinate_X[i] = coordinate_X[i - 1]
177 | coordinate_Y[i] = coordinate_Y[i - 1]
178 | }
179 | }
180 |
181 |
182 | function game_over_judge() { //游戏结束判断
183 | let judge_X = coordinate_X.slice(1),judge_Y = coordinate_Y.slice(1)
184 | for (let i = 0; i < judge_X.length; i++) {
185 | if (judge_X[i] === coordinate_X[0]) {
186 | if (judge_Y[i] === coordinate_Y[0]) return false
187 | }
188 | }
189 | return true
190 | }
191 |
192 | function game_over(){ //游戏结束
193 | Start_game.style.visibility = "visible"
194 | map.style.visibility = "hidden"
195 | status = "off"
196 | key_status = true
197 | game_over_val = 0
198 | Start_game.children[0].textContent = "Game over !!!"
199 | Start_game.children[0].dataset.text = ""
200 | clearInterval(game_status)
201 | }
--------------------------------------------------------------------------------