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)) } })