Advertisement
caLLowCreation

Canvas Snake Game

Sep 7th, 2017
270
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
HTML 5 15.07 KB | None | 0 0
  1.  
  2. <!doctype html>
  3. <html lang="en">
  4.     <head>
  5.         <meta charset="UTF-8">
  6.         <title><Stream Snake Game></title>
  7.         <style></style>  
  8.         <script src="/socket.io/socket.io.js"></script>
  9.         <script src="http://<%= host %>:<%= port %>/js/source.js"></script>
  10.     </head>
  11.     <body>
  12.     </body>
  13. <script>
  14.  
  15. "use strict";
  16. // classes
  17. function vec2(x, y) {
  18.     this.x = x;
  19.     this.y = y;
  20. }
  21.  
  22. function element(position, color) {
  23.     this.position = position;
  24.     this.color = color;
  25. }
  26.  
  27. function player(user, entity) {
  28.     this.user = user;
  29.     this.head = entity;
  30.     this.velocity = new vec2(0, 0);
  31.     this.links = [];
  32.     this.targetId = -1;
  33.     this.speed = 1.0;
  34.     this.trailSpeed = 1.0;
  35.     this.interval = null;
  36. }
  37.  
  38. function collectable(id, entity) {
  39.     this.id = id;
  40.     this.position = entity.position;
  41.     this.color = entity.color;
  42. }
  43.  
  44. function engine(tileSize, pickupAmount, canvasPadding) {
  45.     this.tileSize = tileSize;
  46.     this.padding = canvasPadding;
  47.     this.pickupAmount = pickupAmount;
  48.  
  49.     this.font = {
  50.         size: 22,
  51.         color: 'pink'
  52.     };
  53.     // Set in on load
  54.     this.gridSize = new vec2(0, 0);
  55.     this.canvas = null;
  56.     this.context = null;
  57.     this.interval = -1;
  58.  
  59.     // set in on load
  60.     this.snakes = [];
  61.     this.pickups = [];
  62.  
  63.     // player stats
  64.     this.minCollisionLinks = 4;
  65.     this.maxBotTail = 7;
  66.     this.speedBoost = 1.0;
  67.     this.highPlayer = null;
  68.     //this.highestEver = null;
  69. }
  70.  
  71. // init vars
  72. const debug = {
  73.     info: {},
  74.     position: new vec2(10, 100),
  75.     index: 0,
  76.     spacing: 17,
  77.     font: {
  78.         size: 16,
  79.         color: 'white'
  80.     }
  81. }
  82.  
  83. const game = new engine(new vec2(20, 20), 5, 20);
  84.  
  85. const colors = ["lime", "yellow", "purple", "cyan", "orange", "red"]; //game.context.fillStyle = "lime";
  86.  
  87.  
  88. // game methods
  89. function addPlayer(data) {
  90.     const user = data.user;
  91.     const username = data.user.username;
  92.     const color = data.user.color === null ? colors[Math.floor(Math.random() * colors.length)] : data.user.color;
  93.     const hasPlayer = game.snakes.hasOwnProperty(username);
  94.  
  95.     if(hasPlayer === false) {
  96.         const snake = new player(user, new element(randomGridPosition(), color));
  97.         addTail(snake);
  98.         game.snakes[username] = snake;
  99.     }
  100. }
  101.  
  102. function addTail(snake) {
  103.     // add to tail
  104.     snake.links = [];
  105.     for(let i = 0; i < game.minCollisionLinks; i++) {
  106.        snake.links.push(new element(new vec2(snake.head.position.x, snake.head.position.y), snake.head.color));
  107.    }
  108. }
  109.  
  110. function targetPickup(data) {
  111.    const targetId = parseInt(data.command, 10);
  112.    if(isNaN(targetId)) return;
  113.  
  114.    const username = data.user.username;
  115.    
  116.    if(game.snakes.hasOwnProperty(username)) {
  117.        const snake = game.snakes[username];
  118.        snake.targetId = targetId;
  119.    }
  120. }
  121.  
  122. function createAndSetupGameObjects() {
  123.    // create initial players
  124.    for (let index = 0; index < 1; index++) {
  125.        addPlayer({user: {username: 'bot', color: 'red'}});
  126.        game.snakes['bot'].targetId = Math.floor(Math.random() * (game.pickupAmount - 1));
  127.        //game.snakes['bot'].speed = 3;
  128.        //game.snakes['bot'].trailSpeed = 3;
  129.        setIdleTimeout(game.snakes['bot']);
  130.    }
  131.    //game.highPlayer = game.snakes['bot'];
  132.  
  133.    // create initial collectables
  134.    for (let index = 0; index < game.pickupAmount; index++) {
  135.        game.pickups.push(new collectable(index, new element(randomGridPosition(), colors[index % colors.length])));
  136.    }
  137. }
  138.  
  139. function resetPlayer(snake) {  
  140.    snake.head.position = randomGridPosition();
  141.    snake.targetId = -1;
  142.    snake.velocity.x = 0;
  143.    snake.velocity.y = 0;
  144.    addTail(snake);
  145. }
  146.  
  147. function setIdleTimeout(snake) {
  148.    if(snake.interval !== null) {
  149.        clearInterval(snake.interval);
  150.    }
  151.    snake.interval = setTimeout(function() {
  152.        if (game.snakes.hasOwnProperty(snake.user.username)) {
  153.            delete game.snakes[snake.user.username];
  154.        }
  155.    }, 60 * 1000 * 5);
  156. }
  157.  
  158. function debugInfo() {
  159.    //debug.info.players = game.snakes;
  160.  
  161.    /*debug.info.canvasWidth = game.canvas.width;
  162.    debug.info.canvasHeight = game.canvas.height;
  163.    debug.info.game.snakes = game.snakes;
  164.    debug.info.rndGX = Math.floor(Math.random() * game.gridSize.x);*/
  165. }
  166.  
  167. function update(deltaTime) {
  168.  
  169.    for (let key in game.snakes) {
  170.        if (!game.snakes.hasOwnProperty(key)) continue;
  171.  
  172.        const snake = game.snakes[key];
  173.  
  174.        if(snake.targetId !== -1 && snake.targetId < game.pickups.length) {
  175.            let target = game.pickups[snake.targetId];
  176.            if(Math.floor(snake.head.position.x) < Math.floor(target.position.x)) {
  177.                moveRight(snake);
  178.            } else if(Math.floor(snake.head.position.x) > Math.floor(target.position.x)) {
  179.                 moveLeft(snake);
  180.             } else if(Math.floor(snake.head.position.y) < Math.floor(target.position.y)) {
  181.                moveDown(snake);
  182.            } else if(Math.floor(snake.head.position.y) > Math.floor(target.position.y)) {
  183.                 moveUp(snake);
  184.             }
  185.         }
  186.  
  187.         // update snake position
  188.         snake.head.position.x += snake.velocity.x * snake.speed * deltaTime;
  189.         snake.head.position.y += snake.velocity.y * snake.speed * deltaTime;
  190.  
  191.         // wrap snake position for high level game.snakes
  192.         if(snake.head.position.x < 0) {
  193.            //snake.head.position.x = game.gridSize.x - 1;
  194.            resetPlayer(snake);
  195.        }
  196.        if(snake.head.position.x > game.gridSize.x - 1) {
  197.             //snake.head.position.x = 0;
  198.             resetPlayer(snake);
  199.         }
  200.         if(snake.head.position.y < 0) {
  201.            //snake.head.position.y = game.gridSize.y - 1;
  202.            resetPlayer(snake);
  203.        }
  204.        if(snake.head.position.y > game.gridSize.y - 1) {
  205.             //snake.head.position.y = 0;
  206.             resetPlayer(snake);
  207.         }
  208.  
  209.         // update snake links
  210.         for(let i = 0; i < snake.links.length; i++) {
  211.            const link = snake.links[i];
  212.  
  213.            let targetPos = new vec2(0, 0);
  214.            if(i == 0) {
  215.                targetPos = snake.head.position;
  216.            }
  217.            else {
  218.                targetPos = snake.links[i - 1].position;
  219.            }
  220.            
  221.            const dir = new vec2(targetPos.x - link.position.x, targetPos.y - link.position.y);
  222.            const moveDir = new vec2(Math.min(1, dir.x), Math.min(1, dir.y));
  223.  
  224.            link.position.x += moveDir.x * snake.trailSpeed * deltaTime;
  225.            link.position.y += moveDir.y * snake.trailSpeed * deltaTime;
  226.        }
  227.  
  228.        // check game.pickups collision
  229.        for (let j = 0; j < game.pickups.length; j++) {
  230.            const pickup = game.pickups[j];
  231.            if(checkCollision(snake.head.position, pickup.position, new vec2(1, 1))) {
  232.                snake.targetId = -1;
  233.                pickup.position = randomGridPosition();                    
  234.                // add to tail
  235.                snake.links.unshift(new element(new vec2(snake.head.position.x, snake.head.position.y), pickup.color));
  236.                
  237.                if(key === 'bot') {
  238.                    game.snakes['bot'].targetId = Math.floor(Math.random() * (game.pickups.length - 1));
  239.                    if(snake.links.length > game.maxBotTail) {
  240.                         while(snake.links.length > game.minCollisionLinks) {
  241.                             snake.links.pop();
  242.                         }
  243.                     }
  244.                 }
  245.                
  246.                 if(game.highPlayer === null || snake.links.length > game.highPlayer.links.length) {
  247.                     game.highPlayer = new player(snake.user, snake.head);
  248.                     game.highPlayer.links = [];
  249.                     for (let i = 0; i < snake.links.length; i++) {
  250.                        let link = snake.links[i];
  251.                        game.highPlayer.links.push(link);
  252.                    }
  253.                    sendHighPlayerToServer(game.highPlayer);
  254.                }
  255.            }
  256.        }
  257.    }
  258.  
  259.    // update game.pickups
  260.    for (let index = 0; index < game.pickups.length; index++) {
  261.        const pickup = game.pickups[index];
  262.        pickup.id = index;
  263.    }
  264. }
  265.  
  266. function render() {
  267.  
  268.    // clear background
  269.    game.context.fillStyle = "black";
  270.    game.context.fillRect(0, 0, game.canvas.width, game.canvas.height);
  271.  
  272.    // render game.snakes    
  273.    for (let key in game.snakes) {
  274.        if (!game.snakes.hasOwnProperty(key)) continue;
  275.  
  276.        const snake = game.snakes[key];
  277.  
  278.        game.context.fillStyle = snake.head.color;
  279.        game.context.fillRect(snake.head.position.x * game.tileSize.x, snake.head.position.y * game.tileSize.y, game.tileSize.x - 2, game.tileSize.y - 2);
  280.        
  281.        for(let i = 0; i < snake.links.length; i++) {
  282.            const link = snake.links[i];
  283.            game.context.fillStyle = link.color;
  284.            game.context.fillRect(link.position.x * game.tileSize.x, link.position.y * game.tileSize.y, game.tileSize.x - 2, game.tileSize.y - 2);
  285.        }
  286.        const userText = "t" + snake.targetId + " s" + snake.speed + " b" + (snake.links.length - game.minCollisionLinks) + ":" + snake.user.username;
  287.        const textWidth = game.context.measureText(userText).width; /// width in pixels
  288.        textWithBackground(userText, 'pink', snake.head.position.x * game.tileSize.x, snake.head.position.y * game.tileSize.y - 10, "rgba(0, 0, 255, 0.3)");
  289.    }
  290.  
  291.    // render game.pickups
  292.    for (let index = 0; index < game.pickups.length; index++) {
  293.        const pickup = game.pickups[index];
  294.        game.context.fillStyle = pickup.color;
  295.        game.context.fillRect(pickup.position.x * game.tileSize.x, pickup.position.y * game.tileSize.y, game.tileSize.x - 2, game.tileSize.y - 2);
  296.        const textPosX = pickup.position.x * game.tileSize.x + game.tileSize.x - 4;
  297.        let textPosY = pickup.position.y * game.tileSize.y - 10;
  298.        if(textPosY < game.canvas.height * 0.5) {
  299.            textPosY += game.tileSize.y * 2.0 + 4;
  300.        }
  301.        textWithBackground(pickup.id, 'pink', textPosX, textPosY, "rgba(0, 0, 255, 0.3)");
  302.    }
  303.  
  304.    // render high score
  305.    if(game.highPlayer !== null) {
  306.        const highText = "HIGH TAIL " + game.highPlayer.user.username + " with " + game.highPlayer.links.length + " tail segments";
  307.        const textWidth = game.context.measureText(highText).width; /// width in pixels
  308.        textWithBackground(highText, 'pink', game.canvas.width - textWidth - 10, 20, "rgba(0, 0, 255, 0.3)");
  309.    }
  310.  
  311.    // render debug text
  312.    game.context.font = `${debug.font.size}px Arial`;
  313.    game.context.fillStyle = debug.font.color;
  314.  
  315.    debug.index = 0;
  316.    showDebugInfo('', debug.info);    
  317. }
  318.  
  319. function textWithBackground(text, color, x, y, bgColor) {
  320.  
  321.    game.context.font = `${game.font.size}px Arial`;
  322.  
  323.    const textWidth = game.context.measureText(text).width; /// width in pixels
  324.    const textHeight = 20;
  325.  
  326.    game.context.fillStyle = bgColor;
  327.    game.context.fillRect(x - 10 - 4, y - 15 - 4, textWidth + 6, textHeight + 2);
  328.  
  329.    game.context.fillStyle = color;
  330.    game.context.fillText(text, x - 10, y);
  331. }
  332.  
  333. function showDebugInfo(name, obj) {  
  334.    Object.keys(obj).forEach((key) => {
  335.         if(typeof obj[key] === 'object') {
  336.             showDebugInfo(name + "." + key, obj[key]);
  337.         }
  338.         else {
  339.             game.context.fillText(name + "." + key + " > " + obj[key], debug.position.x, debug.position.y + ((debug.index++) * debug.spacing));
  340.         }
  341.     });
  342. }
  343.  
  344. function checkCollision(position1, position2, size) {
  345.     return (position1.x > position2.x - size.x && position1.x < position2.x + size.x) &&
  346.        (position1.y > position2.y - size.y && position1.y < position2.y + size.y);
  347. }
  348.  
  349. function randomGridPosition() {
  350.     const x = Math.floor(Math.random() * game.canvas.width / game.tileSize.x);
  351.     const y = Math.floor(Math.random() * game.canvas.height / game.tileSize.y);
  352.     return new vec2(x, y);
  353. }
  354.  
  355. function resizeCanvasAndGrid() {
  356.     if(game.canvas !== null) {
  357.         // Set canvas size
  358.         game.canvas.width = window.innerWidth - game.padding;
  359.         game.canvas.height = window.innerHeight - game.padding;
  360.  
  361.         //Set grid size
  362.         game.gridSize.x = game.canvas.width / game.tileSize.x;
  363.         game.gridSize.y = game.canvas.height / game.tileSize.y;
  364.     }
  365. }
  366.  
  367. function processCommandData(data) {
  368.     if(data.user.username === 'callowcreation') {
  369.         data.user.username = 'bot';
  370.     }
  371.     let username = data.user.username;
  372.     if(data.command === 'join') {  // not a player yet, may want to join
  373.         addPlayer(data);
  374.     } else if(data.command === 'boost') {
  375.         if(game.snakes.hasOwnProperty(username)) {
  376.             const snake = game.snakes[username];
  377.             if(snake.links.length > game.minCollisionLinks) {
  378.                 snake.speed += game.speedBoost;
  379.                 snake.trailSpeed = snake.speed;
  380.                 snake.links.pop();
  381.                 setTimeout(function() {
  382.                     if(snake.speed > 0) {
  383.                         snake.speed -= game.speedBoost;
  384.                         snake.trailSpeed = snake.speed;
  385.                     }
  386.                 }, 5000);
  387.             }  
  388.         }    
  389.     } else {
  390.         const targetId = parseInt(data.command, 10);
  391.         if(isNaN(targetId) === false) { // might be a target
  392.             targetPickup(data);
  393.         }
  394.     }
  395.     if(game.snakes.hasOwnProperty(username)) {
  396.         const snake = game.snakes[username];
  397.         setIdleTimeout(snake);
  398.     }
  399. }
  400.  
  401. function moveUp(snake) {
  402.     snake.velocity.x = 0;
  403.     snake.velocity.y = -1;
  404. }
  405.  
  406. function moveDown(snake) {
  407.     snake.velocity.x = 0;
  408.     snake.velocity.y = 1;
  409. }
  410.  
  411. function moveLeft(snake) {
  412.     snake.velocity.x = -1;
  413.     snake.velocity.y = 0;
  414. }
  415.  
  416. function moveRight(snake) {
  417.     snake.velocity.x = 1;
  418.     snake.velocity.y = 0;
  419. }
  420.  
  421. // Cross-browser support for requestAnimationFrame
  422. const w = window;
  423. const requestAnimationFrame = w.requestAnimationFrame || w.webkitRequestAnimationFrame || w.msRequestAnimationFrame || w.mozRequestAnimationFrame;
  424.  
  425. // for delta time
  426. let then = Date.now();
  427. // The main game loop
  428. const main = function () {
  429.     const now = Date.now();
  430.     const delta = now - then;
  431.  
  432.     update(delta / 1000);
  433.    
  434.     debugInfo();
  435.  
  436.     render();
  437.  
  438.     then = now;
  439.  
  440.     // Request to do this again ASAP
  441.     requestAnimationFrame(main);
  442. };
  443.  
  444. // window set up
  445. window.onresize = function(event) {
  446.     resizeCanvasAndGrid();
  447. };
  448.  
  449. window.onload=function() {
  450.     // Create canvas
  451.     game.canvas = document.createElement("canvas");
  452.     // Get drawing buffer
  453.     game.context = game.canvas.getContext("2d");
  454.  
  455.     // Add canvas to dom
  456.     document.body.appendChild(game.canvas);
  457.  
  458.     resizeCanvasAndGrid();
  459.  
  460.     // Setup Game Objects
  461.     createAndSetupGameObjects();
  462.  
  463.     // Server listeners
  464.     socket.on('snake-control', processCommandData);
  465.  
  466.     // Start game loop
  467.     main();
  468.    
  469. }
  470.  
  471. function sendHighPlayerToServer(highPlayer) {
  472.     socket.emit('high-player', { data: highPlayer });
  473. }
  474.  
  475. </script>
  476.  
  477. </html>
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement