-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsnake.js
412 lines (349 loc) · 8.32 KB
/
snake.js
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
// Snake config.
var SNAKE_INTERVAL = {
"easy": 250,
"medium": 150,
"hard": 100
}
/////////////////////////////////////////////////////////////////////
// Utils.
function do_nothing()
{
return false;
}
function rand_range(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1)) + min;
}
function tile_to_str(tile)
{
return "x" + tile[0].toString() + "y" + tile[1].toString();
}
function is_same_tile(tile_a, tile_b)
{
var x_con = tile_a[0] == tile_b[0];
var y_con = tile_a[1] == tile_b[1];
return (x_con && y_con);
}
function find_tile(tile, tiles)
{
for(var i = 0; i < tiles.length; i++)
{
if(is_same_tile(tiles[i], tile))
{
return i;
}
}
return -1;
}
///////////////////////////////////////////////////////////////
// Direction snake can move.
var SNAKE_UP = 0;
var SNAKE_DOWN = 1;
var SNAKE_LEFT = 2;
var SNAKE_RIGHT = 3;
// Hold state of snake game.
var snake = {}
var snake_high_score = {
"easy": 0,
"medium": 0,
"hard": 0
};
// Color a given square for the game.
function snake_color_tile(tile, color)
{
// Color game tile.
var btn_id = tile_to_str(tile);
var btn = this.getField(btn_id);
btn.fillColor = color;
return btn;
}
function snake_clear_scores()
{
this.getField("snake_score").value = "0";
this.getField("snake_high_score").value = "0";
}
function snake_update_scores()
{
// Score field value.
this.getField("snake_score").value = snake["score"].toString();
// Use score for the high score if we exceed it.
if(snake["score"] > snake_high_score[snake["difficulty"]])
{
this.getField("snake_high_score").value = snake["score"].toString();
}
else
{
this.getField("snake_high_score").value = snake_high_score[snake["difficulty"]].toString();
}
}
function snake_game_over()
{
// Stop the game loop.
app.clearInterval(snake["game_loop"]);
// Show their score if they have one.
snake_update_scores();
if(snake["score"])
{
// Update high score.
if(snake["score"] > snake_high_score[snake["difficulty"]])
{
snake_high_score[snake["difficulty"]] = snake["score"];
}
// Show score and high score.
var ys = snake["score"].toString();
var hs = snake_high_score[snake["difficulty"]].toString();
var msg = 'Ouch! Game over! Your score = ' + ys + '; high score = ' + hs;
app.alert(msg);
snake["playing"] = false;
}
}
// Stop the game from running and do cleanup.
function snake_reset()
{
// Disable field highlighting if it's on.
if(app.runtimeHighlight)
{
app.runtimeHighlight = false;
//app.runtimeHighlightColor = color.red;
}
// Stop game loop running.
if(snake["game_loop"] != null)
{
// Don't show an alert if there was already one.
if(snake["playing"])
{
snake_game_over();
}
snake["game_loop"] = null;
}
// Game loop update speed.
var difficulty = this.getField("snake_difficulty").value;
// Reset snake state.
snake = {
"score": 0,
"difficulty": difficulty,
"playing": true,
"move": null,
"input": false,
"game_loop": null,
"x": 0,
"y": 0,
"x_limit": 16,
"y_limit": 6,
"snake_tiles": [],
"empty_tiles": [],
"food_tiles": [],
}
// Reset game tile coloring.
for(var x = 0; x <= snake["x_limit"]; x++)
{
for(var y = 0; y <= snake["y_limit"]; y++)
{
var tile = [x, y];
// Reset the tile color to white.
var btn = snake_color_tile(tile, color.white);
// Make sure it has a border.
// This fixes a bug on firefox.
btn.lineWidth = 1;
btn.strokeColor = color.black;
}
}
// Initial starting square.
snake_color_tile([0, 0], color.black);
// Score field value.
snake_update_scores();
}
// Start accepting input for the snake game.
function snake_start()
{
// Ensure state is clean.
snake_reset();
// Navigate to hidden input form.
var snake_input = this.getField("snake_input");
snake_input.setFocus();
}
// Add a new item of food to the game.
function snake_add_food()
{
// Check list of empty tiles.
var tile_len = snake["empty_tiles"].length;
if(tile_len == 0)
{
throw new Error("No empty tiles remaining.");
}
// Select a random empty tile.
var rand_index = rand_range(0, tile_len - 1);
var tile = snake["empty_tiles"][rand_index];
// Remove it from list of empty tiles.
snake["empty_tiles"].splice(rand_index, 1);
// Record food_tile.
snake["food_tiles"].push(tile);
// Color game pixel red.
snake_color_tile(tile, color.red);
}
// Called once, prior to starting game loop running.
// Used to initialize snake state.
function snake_init()
{
// Initialize the list of empty tiles.
snake["snake_tiles"] = [[0, 0]];
for(var x = 0; x <= snake["x_limit"]; x++)
{
for(var y = 0; y <= snake["y_limit"]; y++)
{
var tile = [x, y];
snake["empty_tiles"].push(tile);
}
}
snake["empty_tiles"].shift();
// Add initial food item.
snake_add_food();
}
function snake_next_tile(move)
{
// Calculate next coordinates of snake.
var x = snake["x"];
var y = snake["y"];
if(move == SNAKE_UP)
{
y = (snake["y"] + 1) % (snake["y_limit"] + 1);
}
if(move == SNAKE_DOWN)
{
y = (snake["y"] - 1) % (snake["y_limit"] + 1);
}
if(move == SNAKE_RIGHT)
{
x = (snake["x"] + 1) % (snake["x_limit"] + 1);
}
if(move == SNAKE_LEFT)
{
x = (snake["x"] - 1) % (snake["x_limit"] + 1);
}
// Wrap around X and Y.
x = x < 0 ? snake["x_limit"] : x;
y = y < 0 ? snake["y_limit"] : y;
return [x, y];
}
// The main game loop of the snake game.
function snake_game_loop()
{
// Calculate next coordinates of snake.
var snake_head_tile = snake_next_tile(snake["move"]);
// If the next head is in an empty tile then consume it.
var empty_index = find_tile(snake_head_tile, snake["empty_tiles"]);
if(empty_index != -1)
{
snake["empty_tiles"].splice(empty_index, 1);
}
// If new tile is filled with snake = game over.
if(find_tile(snake_head_tile, snake["snake_tiles"]) != -1)
{
snake_game_over();
return;
}
// If new tile is not filled with food = prune snake.
var food_index = find_tile(snake_head_tile, snake["food_tiles"]);
if(food_index == -1)
{
// Reset the trailing snake tile position.
var snake_end_tile = snake["snake_tiles"][0];
snake_color_tile(snake_end_tile, color.white);
snake["snake_tiles"].splice(0, 1);
snake["empty_tiles"].push(snake_end_tile);
}
// New position of the head snake tile.
snake["snake_tiles"].push(snake_head_tile);
snake_color_tile(snake_head_tile, color.black);
// Add more food if needed.
if(food_index != -1)
{
// Remove old entry for food index.
snake["food_tiles"].splice(food_index, 1);
// Add new food to collect.
snake_add_food();
// Increase score.
snake["score"] += 100;
snake_update_scores();
}
// Update snakes x and y position.
snake["x"] = snake_head_tile[0];
snake["y"] = snake_head_tile[1];
}
// Ignore input if it's opposite of direction and previous is snake.
function snake_ignore_input(move)
{
// Not relevant.
if(move == null)
{
return true;
}
// Ignore input if game is over.
if(snake["playing"] == false)
{
return true;
}
// Block going back in opposite direction if snake > 1 long.
var snake_len = snake["snake_tiles"].length;
if(snake_len > 1)
{
var tile_a = snake_next_tile(move);
var tile_b = snake["snake_tiles"][snake_len - 2];
console.println(tile_a);
console.println(tile_b);
console.println("\r\n");
if(is_same_tile(tile_a, tile_b))
{
return true;
}
}
return false;
}
// Move the snake using a, s, d, w.
function handle_snake_input()
{
var key = event.change;
// Don't write anything to input.
event.change = "";
// Snake is moving left.
var move = null;
if(key == "a" || key == "A")
{
move = SNAKE_LEFT;
}
// Snake is moving right.
if(key == "d" || key == "D")
{
move = SNAKE_RIGHT;
}
// Snake is moving up.
if(key == "w" || key == "W")
{
move = SNAKE_UP;
}
// Snake is moving down.
if(key == "s" || key == "S")
{
move = SNAKE_DOWN;
}
// Ignore input or not?
// To prevent going back same direction (if full.)
if(snake_ignore_input(move))
{
return;
}
// Update snake direction.
snake["move"] = move;
// Run game loop if it's not started.
if(snake["game_loop"] == null)
{
console.println("starting game loop");
snake_init();
// Start game loop -- update speed based on difficulty.
var interval_speed = SNAKE_INTERVAL[snake["difficulty"]];
snake["game_loop"] = app.setInterval("snake_game_loop()", interval_speed);
}
}
snake_clear_scores();
snake_reset();