-
Notifications
You must be signed in to change notification settings - Fork 0
/
script.js (Snake)
446 lines (372 loc) · 12.8 KB
/
script.js (Snake)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
// global variables
// size of the board [width, length]
size = [10, 10]
// giant array with all of the cells of the field
var cells = []
// array with all of the [i][j] locations of the snake cells in the 'cells' array, as [i, j]
var snake = []
// the last useful key pressed
var lastKey = 97
// variables for the setIntervals
var m = undefined
var f = undefined
// highest score this runtime
var highScore = 0
// the game is not running right now
var game = false
// function that funs when the program starts
window.onload = function() {
// this is the pregame
document.getElementsByTagName('body')[0].className = 'preGame'
// make the board the pre-game size
setUpBoard()
// set up the cell array
makeCellArray()
// set the board to the pre-game configuration
setUpPreGame()
}
// when a key is pressed
window.onkeypress = function(event) {
// store the ascii code of the pressed key
newKey = event.charCode
// if it's a useful key (WASD during game, spacebar when waiting for game to start)
if ((game && (newKey === 97 || newKey === 65 || newKey === 100 || newKey === 68 ||
newKey === 119 || newKey === 87 || newKey === 115 || newKey === 83)) ||
(!game && newKey === 32)) {
// store it in the global variable
lastKey = newKey
}
// if the game isn't going and spacebar was pressed (this is starting the game)
if (!game && lastKey === 32) {
// this is the game phase
document.getElementsByTagName('body')[0].className = 'game'
// reset the board
resetSnake()
reset(intoArray(document.getElementsByClassName('fruit')), true)
// note that the game is happening
game = true
// start the game
startGame()
}
}
// function to run a function on each cell (just like forEach, but runs on each cell in an array of arrays)
function forEachCell(func) {
// check each row...
cells.forEach(function(row) {
// and each cell in that row
row.forEach(func)
})
}
// function to return a random integer in the accepted RGB range (0 - 255)
function randomRGB() {
return Math.floor(Math.random() * 255)
}
// function to write a word one letter at a time in a given row
function writeWord(word, row) {
// starting in the leftmost cell of the given row, place one letter of the word at a time
for (let i = 0, n = word.length; i < n && i < cells[row].length; i++) {
cells[row][i].innerHTML = word[i]
}
}
// function to figure out which row a cell is in
function findCoordinates(cell) {
// check each row
for (let i = 0, n = cells.length; i < n; i++) {
// and each cell in that row
for (let j = 0, n = cells[i].length; j < n; j++) {
// if this cell is the one we're looking for
if (cell === cells[i][j]) {
// return which row it's in
return [i, j]
}
}
}
}
// function to return an array containing every other cell in a row, starting with the first one
function everyOtherCell(row) {
// variable that will be returned
let arr = []
// add every other cell in this row to the return array
for (let i = 0, n = cells[row].length; i < n; i += 2) {
arr.push(cells[row][i])
}
// return the array
return arr
}
// function to find the cell (td pointer) that the snake would be moving into
function findNewSpot(front) {
// a and A handling
if (lastKey === 97 || lastKey === 65) {
newSpot = [front[0], front[1] - 1]
}
// d and D handling
else if (lastKey === 100 || lastKey === 68) {
newSpot = [front[0], front[1] + 1]
}
// w and W handling
else if (lastKey === 119 || lastKey === 87) {
newSpot = [front[0] - 1, front[1]]
}
// s and S handling
else if (lastKey === 115 || lastKey === 83) {
newSpot = [front[0] + 1, front[1]]
}
// return the pointer
return cells[newSpot[0]][newSpot[1]]
}
// function to make the weird DOM pointer thingies into proper arrays
function intoArray(dom) {
// return variable
arr = []
// add each item in the DOM to the return array
for (let i = 0, n = dom.length; i < n; i++) {
arr.push(dom[i])
}
// return the much more useful array
return arr
}
// function to add a new cell to the snake
function newSnake(cell) {
// add new cell to snake array
snake.push(findCoordinates(cell))
// make it look like a snake cell
cell.className = 'snake'
}
// function to determine the number of fruits already present
function numFruits() {
// counter variable
let fruits = 0
// check each cell
forEachCell(function(cell) {
// increment counter if it is a fruit
if (cell.className === 'fruit') {
fruits++
}
})
// return the coutner
return fruits
}
// function that runs when the user selects one of the sizes
var selectSize = function() {
// figure out which set this cell is in
let row = Math.floor(findCoordinates(this)[0] / 3)
// reset only this set of sizes
reset(cells[row * 3 + 1])
reset(cells[row * 3 + 2])
// the clicked cell looks selected, and the size array is update with its value
this.className = 'selected-size'
size[row] = this.innerHTML
}
// function that runs when the user selects of the colors
var selectColor = function() {
// figure out which row the cell is in
let row = findCoordinates(this)[0]
// reset all of the other color cells in that row
reset(everyOtherCell(row), false, 'unselected-color')
// make the color look selected, and set the snake-color CSS variable to this cell's background color
this.className = 'selected-color'
document.documentElement.style.setProperty('--snake-color', this.style.background)
}
// function to show the sizes to choose from
function showSizes(name, startRow) {
// write the name of this size
writeWord(name, startRow)
// for each cell in the two rows below the start row
for (let i = 0, n = cells[startRow + 1].length * 2; i < n; i++) {
// figure out which row we're in
let row = Math.floor(i / (n / 2))
// give the proper cell its size value and the right onclick
cells[startRow + 1 + row][i - n / 2 * row].innerHTML = i + 7
cells[startRow + 1 + row][i - n / 2 * row].onclick = selectSize
}
}
// function to show the colors to choose from
function showColors(startRow) {
// the name of this selecor
let name = 'COLORS'
// write the name of this selector
writeWord(name, startRow)
// for every other cell two rows down from the start row
for (let i = 0, n = Math.floor(cells[startRow + 1].length / 2); i < n; i++) {
// set the background to a random color
cells[startRow + 2][i * 2].style.background = 'rgb(' + randomRGB() + ', ' + randomRGB() + ', ' + randomRGB() + ')'
// set up the click ability, and note that this color is not selected
cells[startRow + 2][i * 2].className = 'unselected-color'
cells[startRow + 2][i * 2].onclick = selectColor
}
}
// function to create the desired number of rows and columns in the tbody tag
function setUpBoard() {
// reference variable for where the new HTML is going
table = document.getElementsByTagName('tbody')[0]
// reset it (in case there was anything in there before)
table.innerHTML = ''
// make as many rows as the first size value, height, says so
for (let i = 0; i < size[0]; i++) {
// start this row with an open-tr tag
let row = '<tr>'
// make as many columns in that row as the second size value, width, says so
for (let j = 0; j < size[1]; j++) {
// add a complete empty td tag to this row
row += '<td></td>'
}
// add the completed row plus a closing tag to the tbody
table.innerHTML += row + '</tr>'
}
}
// function to set up the giant array of cells (which is an array of arrays)
function makeCellArray() {
// clear the array
cells = []
for (let i = 0, rows = document.getElementsByTagName('TR'); i < rows.length; i++) {
// start the array for this row
let row = []
for (let j = 0, boxes = rows[i].getElementsByTagName('TD'); j < boxes.length; j++) {
// add the individual cell to this row's array
row.push(boxes[j])
}
// add this row's array to the master 'cells' array
cells.push(row)
}
}
// function to set up the pregame board by displaying all of the selectors
function setUpPreGame() {
// all cells are initally grass
forEachCell(function(cell) {
cell.className = 'grass'
})
// display the selectors
showSizes('HEIGHT', 0)
showSizes('WIDTH', 3)
showColors(6)
// make the lower right corner into a play button - clicking it starts the game
lrCorner = cells[cells.length - 1][cells[cells.length - 1].length - 1]
lrCorner.innerHTML = 'play'
lrCorner.style.background = 'lightcoral'
lrCorner.onclick = setUpGame
}
// function to set up the game's board by organizing all of the cells into catergories
var setUpGame = function() {
// make the board cells
setUpBoard()
// organize the board cells
makeCellArray()
// this is the game phase
document.getElementsByTagName('body')[0].className = 'game'
for (let i = 0, n = cells.length; i < n; i++) {
for (let j = 0, l = cells[i].length; j < l; j++) {
// if it's the middle cell, it starts as a snake cell
if (i === Math.floor(cells.length / 2) && j === Math.floor(cells[i].length / 2)) {
cells[i][j].className = 'snake'
snake.push([i, j])
}
// if it's on the edge, it starts as a border cell
else if (i === 0 || i === cells.length - 1 || j === 0 || j === cells[i].length - 1) {
cells[i][j].className = 'border'
}
// otherwise, it's just 'grass' or background
else {
cells[i][j].className = 'grass'
}
}
}
}
// fuction to start the game
function startGame() {
// at the beginning, the snake moves left
lastKey = 97
// start the two intervals and store their stop codes in the global variables
m = setInterval(move, 250 * 0.95)
f = setInterval(newFruit, 1000)
}
// function to make a new fruit (run in a setInterval)
var newFruit = function() {
// if there aren't too many fruits
if (numFruits() < 5) {
// reference variable
let openGrass = document.getElementsByClassName('grass')
// randomely pick a grass cell
let fruit = openGrass[Math.floor(Math.random() * openGrass.length)]
// put a fruit image in the cell and set its class to fruit
fruit.innerHTML = '<img src="apple.png">'
fruit.className = 'fruit'
}
}
// function to move the snake (run in a setInterval)
var move = function() {
// find out which cell the snake would be moving into
let check = findNewSpot(snake[snake.length - 1])
// if the snake ran into itself or the wall
if (check.className === 'border' || check.className === 'snake') {
// end the game
endGame()
}
// if the snake ran into a fruit
else if (check.className === 'fruit') {
// add the new cell to the snake
newSnake(check)
// stop the move clock, and then set it again but faster
clearInterval(m)
m = setInterval(move, 250 * Math.pow(0.95, snake.length))
// get rid of the fruit image
check.innerHTML = ''
}
// otherwise (it ran into grass)
else {
// add the new cell to the snake
newSnake(check)
// the tail end of the snake is no longer snake
cells[snake[0][0]][snake[0][1]].className = 'grass'
snake.splice(0, 1)
}
}
// function to end the game
function endGame() {
// stop both intervals (moving and fruit making)
clearInterval(m)
clearInterval(f)
// reference variable
let score = snake.length
// add score to the score h2
document.getElementById('score').innerHTML = 'Your score is ' + score
// if the user got the max score, recognize that
if (score === ((size[0] - 1) * (size[1] - 1))) {
document.getElementById('score').innerHTML += ', the max score!'
}
// if this is a high score
if (score > highScore) {
// celebrate in the highScore h2
document.getElementById('highScore').innerHTML = 'Congratulations, you beat the high score!'
// change the high score
highScore = score
}
// if it isn't, the highScore h2 has the high score
else {
document.getElementById('highScore').innerHTML = 'The high score is ' + highScore
}
// this is the post game
document.getElementsByTagName('body')[0].className = 'postGame'
// the game is not currently running
game = false
}
// function to reset an arr of cells
function reset(arr, clearInner=false, resetTo='grass') {
// for each cell
for (let i = 0, n = arr.length; i < n; i++) {
// delete what's in the cell if required
if (clearInner) {
arr[i].innerHTML = ''
}
// reset it to the desired class
arr[i].className = resetTo
}
}
// function to fully reset the snake
function resetSnake() {
// get rid of all the old snake cells
reset(intoArray(document.getElementsByClassName('snake')), true)
// make the middle cell a snake cell
cells[Math.floor(cells.length / 2)][Math.floor(cells[0].length / 2)].className = 'snake'
// add the middle cell to the snake array
snake = [[Math.floor(cells.length / 2), Math.floor(cells[0].length / 2)]]
}