193 lines
5.0 KiB
Plaintext
193 lines
5.0 KiB
Plaintext
var core = use('core')
|
|
var camera = use('camera')
|
|
var compositor = use('compositor')
|
|
var input = use('input')
|
|
var shape = use('shape2d')
|
|
var text2d = use('text2d')
|
|
var tw = use('tween')
|
|
var ease = use('ease')
|
|
var random = use('random')
|
|
|
|
var GW = 600, GH = 600
|
|
var cellSize = 20
|
|
var gridW = GW / cellSize
|
|
var gridH = GH / cellSize
|
|
|
|
var game_cam = camera.make({width: GW, height: GH, pos: {x: GW / 2, y: GH / 2}})
|
|
var hud_cam = camera.make({width: GW, height: GH, pos: {x: GW / 2, y: GH / 2}})
|
|
|
|
// Action mapping with possession
|
|
input.configure({
|
|
action_map: {
|
|
up: ['w', 'up'],
|
|
down: ['s', 'down'],
|
|
left: ['a', 'left'],
|
|
right: ['d', 'right'],
|
|
restart: ['space']
|
|
}
|
|
})
|
|
|
|
// Game state
|
|
var snake_shapes = []
|
|
var snake_pos = []
|
|
var dir = {x: 1, y: 0}
|
|
var next_dir = {x: 1, y: 0}
|
|
var move_timer = 0
|
|
var move_interval = 0.12
|
|
var state = 'playing'
|
|
var gameover_label = null
|
|
var score = 0
|
|
var score_label = null
|
|
|
|
// Input handler entity — possessed by player1
|
|
var game_input = {
|
|
on_input: function(action, data) {
|
|
if (!data.pressed) return
|
|
if (state == 'playing') {
|
|
if (action == 'up' && dir.y != -1) next_dir = {x: 0, y: 1}
|
|
if (action == 'down' && dir.y != 1) next_dir = {x: 0, y: -1}
|
|
if (action == 'left' && dir.x != 1) next_dir = {x: -1, y: 0}
|
|
if (action == 'right' && dir.x != -1) next_dir = {x: 1, y: 0}
|
|
}
|
|
if (action == 'restart' && state == 'gameover') reset_game()
|
|
}
|
|
}
|
|
input.player1().possess(game_input)
|
|
|
|
function grid_to_world(gx, gy) {
|
|
return {x: gx * cellSize + cellSize / 2, y: gy * cellSize + cellSize / 2}
|
|
}
|
|
|
|
// Apple
|
|
var apple_gx = 0, apple_gy = 0
|
|
var apple_shape = shape.rect({
|
|
pos: {x: 0, y: 0}, width: cellSize - 2, height: cellSize - 2,
|
|
fill: {r: 1, g: 0.2, b: 0.2, a: 1}, plane: 'game'
|
|
})
|
|
|
|
function spawn_apple() {
|
|
apple_gx = floor(random.random() * gridW)
|
|
apple_gy = floor(random.random() * gridH)
|
|
var i = 0
|
|
for (i = 0; i < length(snake_pos); i++) {
|
|
if (snake_pos[i].x == apple_gx && snake_pos[i].y == apple_gy) {
|
|
spawn_apple()
|
|
return
|
|
}
|
|
}
|
|
var wp = grid_to_world(apple_gx, apple_gy)
|
|
apple_shape.pos.x = wp.x
|
|
apple_shape.pos.y = wp.y
|
|
}
|
|
|
|
function reset_game() {
|
|
var i = 0
|
|
for (i = 0; i < length(snake_shapes); i++)
|
|
snake_shapes[i].destroy()
|
|
snake_shapes = []
|
|
snake_pos = []
|
|
|
|
var cx = floor(gridW / 2)
|
|
var cy = floor(gridH / 2)
|
|
var wp = null
|
|
for (i = 0; i < 3; i++) {
|
|
push(snake_pos, {x: cx - i, y: cy})
|
|
wp = grid_to_world(cx - i, cy)
|
|
push(snake_shapes, shape.rect({
|
|
pos: {x: wp.x, y: wp.y}, width: cellSize - 2, height: cellSize - 2,
|
|
fill: {r: 0, g: 1, b: 0.3, a: 1}, plane: 'game'
|
|
}))
|
|
}
|
|
|
|
dir = {x: 1, y: 0}
|
|
next_dir = {x: 1, y: 0}
|
|
state = 'playing'
|
|
move_timer = 0
|
|
score = 0
|
|
if (score_label) score_label.text = "Score: 0"
|
|
if (gameover_label) { gameover_label.destroy(); gameover_label = null }
|
|
spawn_apple()
|
|
}
|
|
|
|
// HUD
|
|
score_label = text2d({
|
|
text: "Score: 0", pos: {x: 10, y: GH - 30},
|
|
plane: 'hud', size: 16, color: {r: 1, g: 1, b: 1, a: 1}
|
|
})
|
|
|
|
reset_game()
|
|
|
|
var comp_config = {
|
|
clear: {r: 0.08, g: 0.08, b: 0.1, a: 1},
|
|
planes: [
|
|
{name: 'game', camera: game_cam, resolution: {width: GW, height: GH}, presentation: 'letterbox'},
|
|
{name: 'hud', camera: hud_cam, resolution: {width: GW, height: GH}, presentation: 'stretch'}
|
|
]
|
|
}
|
|
|
|
core.start({
|
|
width: 600, height: 600, title: "Snake",
|
|
|
|
update: function(dt) {
|
|
if (state != 'playing') return
|
|
move_timer += dt
|
|
if (move_timer < move_interval) return
|
|
move_timer -= move_interval
|
|
|
|
dir.x = next_dir.x
|
|
dir.y = next_dir.y
|
|
|
|
// New head position
|
|
var hx = snake_pos[0].x + dir.x
|
|
var hy = snake_pos[0].y + dir.y
|
|
|
|
// Wrap
|
|
if (hx < 0) hx = gridW - 1
|
|
if (hx >= gridW) hx = 0
|
|
if (hy < 0) hy = gridH - 1
|
|
if (hy >= gridH) hy = 0
|
|
|
|
// Self collision
|
|
var i = 0
|
|
for (i = 0; i < length(snake_pos); i++) {
|
|
if (snake_pos[i].x == hx && snake_pos[i].y == hy) {
|
|
state = 'gameover'
|
|
gameover_label = text2d({
|
|
text: "GAME OVER — SPACE to restart",
|
|
pos: {x: GW / 2 - 170, y: GH / 2},
|
|
plane: 'hud', size: 20, color: {r: 1, g: 0.3, b: 0.3, a: 1}
|
|
})
|
|
return
|
|
}
|
|
}
|
|
|
|
// Add head with tween from old head position
|
|
var old_wp = grid_to_world(snake_pos[0].x, snake_pos[0].y)
|
|
var new_wp = grid_to_world(hx, hy)
|
|
snake_pos.unshift({x: hx, y: hy})
|
|
var head = shape.rect({
|
|
pos: {x: old_wp.x, y: old_wp.y}, width: cellSize - 2, height: cellSize - 2,
|
|
fill: {r: 0, g: 1, b: 0.3, a: 1}, plane: 'game'
|
|
})
|
|
// Smooth tween from old position to new position
|
|
tw.tween(head.pos).to({x: new_wp.x, y: new_wp.y}, move_interval).ease(ease.linear)
|
|
snake_shapes.unshift(head)
|
|
|
|
// Eat apple?
|
|
if (hx == apple_gx && hy == apple_gy) {
|
|
score++
|
|
score_label.text = "Score: " + score
|
|
spawn_apple()
|
|
} else {
|
|
// Remove tail
|
|
var tail = pop(snake_shapes)
|
|
tail.destroy()
|
|
pop(snake_pos)
|
|
}
|
|
},
|
|
|
|
render: function() {
|
|
return compositor.execute(compositor.compile(comp_config))
|
|
}
|
|
})
|