Dediggefedde

Canvas Game Exercise

Nov 18th, 2019
171
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. <!DOCTYPE html>
  2. <html lang="en">
  3.  
  4. <head>
  5.     <meta charset="UTF-8">
  6.     <meta name="viewport" content="width=device-width, initial-scale=1.0">
  7.     <meta http-equiv="X-UA-Compatible" content="ie=edge">
  8.     <title>Document</title>
  9.     <style>
  10.         html,
  11.         body {
  12.             height: 90%
  13.         }
  14.     </style>
  15. </head>
  16.  
  17. <body>
  18.     <div style="display:none">
  19.         <svg class="block" version="1.1" viewBox="0 0 10.26 10.26" preserveAspectRatio="none"
  20.             xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#"
  21.             xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
  22.             <g transform="translate(-3.4688 -2.8125)">
  23.                 <g stroke="#000" stroke-linecap="square">
  24.                     <rect x="3.9688" y="3.3125" width="9.2604" height="9.2604" fill="#b3b3b3"
  25.                         style="paint-order:stroke fill markers" />
  26.                     <rect x="5.2917" y="4.6354" width="6.6146" height="6.6146" fill="#333" stroke-width=".5"
  27.                         style="paint-order:stroke fill markers" />
  28.                     <rect x="6.6146" y="5.9583" width="3.9688" height="3.9688" fill="#f2f2f2" stroke-width=".5"
  29.                         style="paint-order:stroke fill markers" />
  30.                 </g>
  31.                 <path d="m13.229 3.3125h-9.2604v9.2604z" fill="#ffe6d5" fill-rule="evenodd" opacity=".515" />
  32.                 <path d="m13.229 12.573h-9.2604v-9.2604z" fill="#280b0b" fill-rule="evenodd" opacity=".515" />
  33.             </g>
  34.         </svg>
  35.         <img class="block" />
  36.         <img class="back"
  37.             src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAZAAAAGQBAMAAABykSv/AAAAFVBMVEX////6+vr9/f3+/v77+/v8/Pz5+fnUxEs+AAAD2klEQVR4Xu3cMW4UQRSE4ScbDrC7NvFgOMCCIAfsAxhxAu5/CYhcoJ7FTdbP+l5aqqCk1iTz66/vh/27bRbU6wvJY7Og6u1ucGwX1NVusrUL9jce+wX7G7d2wf7GY79gf+PWMhg3HnsG48ataVD14a/g1DMYN27tgtz7Q+5bz2DcuLUMxi/BsXVQ10/JuXWQL8Gpd5CN59ZBNp56B9l4bhvkPv0O7hsH2fj1cHNuHWTjfesg93Bh4fVDg8bdH/euLtzHu+FWa9Qh9/N8qXL95pBbs5Eh+Qbsvsfcmo0MyTdg/xORW7JRw/Rnx6/ZqGH6s+PXbGTIl/rnvcqAFRsZclPPXMYv2ahMr8nxazYq02fHr9moTJ8dv2ajMn12/IKNDPkxU/mcGWs1MuS2Zi4/ThdsVN7i7Htcs1F5i7Pvcb3G05DHyUb+Za/XqLzF2fe4ZqMyfXb8mo3K9NnxazYq02fHr9moTJ8dv2ajMn12/JqNyvTZ8Ws2avvfytWajXox9/IJOgQdgg5Bh6BD0CHoEHQIOgQdgg5Bh6BD0CHoEHQIOgRdT1BubDQF5cZGT1BubDQF5cZGT1BubDQF5cZGT1BubPQG5dJoDsql0RuUS6M5KJdGb1AujeagXBq9Qbk0moNyafQG5dJoDsql0RuUS6M5KJdGb1AujeagXBq9Qbk0moNyaXQH5dLoD8ql0R+U22rVQ9Ah6BB0CDoEHYIOQYegQ9Ah6BB0CDoEHYIOQYegQ9Ah6DjoOOg46DjoOOg46DjoOOg46DjoOOg46DjoOOg46DjoOOg46DjoOOg46BB0CDoEHYIOQYegQ9Ah6BB0CDoEHYIOQYegQ9Ah6BB0CDoOOg46DjoOOg46DjoOOg46DjoOOg46DjoOOg46DjoOOg46DjoOOg46DjoEHYIOQYegQ9Ah6BB0CDoEHYIOQYegQ9Ah6BB0CDoEHYKOg46DjoOOg46DjoOOg46DjoOOg46DjoOOg46DjoOOg46DjoOOg46DjoMOQYegQ9Ah6BB0CDoEHYIOQYegQ9Ah6BB0CDoEHYIOQYeg46DjoOOg46DjoOOg46DjoOOg46DjoOOg46DjoOOg46DjoOOg46DjoOOgQ9Ah6BB0CDoEHYIOQYegQ9Ah6BB0CDoEHYIOQYegQ9Ah6DjoOOg46DjoOOg46DjoOOg46DjoOOg46DjoOOg46DjoOOg46DjoOOg46BB0CDoEHYIOQYegQ9Ah6BB0CDoEHYIOQYegQ9Ah6BB0CDoOOg46DjoOOg46DjoOOg46DjoOOg46DjoOOg46DjoOOg46DjoOOg46DrpfMHoHl1WY49wAAAAASUVORK5CYII=" />
  38.         <!--background image 400*400px-->
  39.         <audio class='shuffle' src="shuffle2.wav" preload="auto" controls="none"></audio>
  40.         <!-- https://freesound.org/s/159355/ modified-->
  41.         <audio class='down' src="shuffle3.wav" preload="auto" controls="none"></audio> <!-- also, modified -->
  42.         <audio class='gameover' src="gameover.wav" preload="auto" controls="none"></audio>
  43.         <!-- https://freesound.org/s/382310/ -->
  44.         <audio class='clean' src="clean2.wav" preload="auto" controls="none"></audio>
  45.         <!-- https://freesound.org/s/447808/ -->
  46.         <audio class='background' src="NiGiD_-_Waiting_for_the_morning_bus.mp3" controls="none" autoplay loop></audio>
  47.         <!-- http://dig.ccmixter.org/files/NiGiD/47862 -->
  48.     </div>
  49.     <canvas id="canvas" width=600 height=750></canvas>
  50.     <div>
  51.         <h3>Music Credits</h3>
  52.         <ul>
  53.             <li> Background: Waiting for the morning bus by Martijn de Boer (NiGiD) (c) copyright 2014 Licensed under a
  54.                 Creative Commons Attribution Noncommercial (3.0) license. http://dig.ccmixter.org/files/NiGiD/47862 Ft:
  55.                 Pitx</li>
  56.             <li> sfx shuffle: https://freesound.org/s/159355/ </li>
  57.             <li> sfx down: https://freesound.org/s/263001/</li>
  58.             <li> sfx clean: https://freesound.org/s/447808/</li>
  59.         </ul>
  60.     </div>
  61.  
  62.     <script>
  63.         (function () { //prevent simple console access from browser... like "score=1234567"
  64.  
  65.             var whitePx =
  66.                 "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z/C/HgAGgwJ/lK3Q6wAAAABJRU5ErkJggg==";
  67.             var canvas; //canvas object
  68.             var ctx; //canvas 2d context
  69.             var lastsec; //drop 1 per sec
  70.             var size; //size of canvas in px and grids
  71.             var pixels; //dropping form
  72.             var grid; //dropped forms
  73.             var img_block, img_back; //image resources
  74.             var audio_shuffle, audio_down, audio_gameover, audio_background, audio_clean; //audio resources
  75.             var deleting, ending, paused, starting; //if false no dropping
  76.             var score; //your archieved score.
  77.             var rewards = [10, 30, 50, 70]; //bonus points for erased lines (4l = +70)
  78.             var pausebounds; //boundaries for pause button
  79.             var startbounds; //boundaries for start button
  80.             var mx, my; //mouse xy
  81.             var nextFormInd; //preview of next form
  82.             var playSFX = true,
  83.                 playMusic = true; //flag for playing sfx sound
  84.             var sidewidth; //width of sidebar
  85.             var gradients = []; //gradients used at rendering. prerendered to save cpu
  86.             var curForm = {
  87.                 x: [],
  88.                 y: []
  89.             }; //current form
  90.             var zwiForm = {
  91.                 x: [],
  92.                 y: []
  93.             }; //turned form used at turning pieces
  94.  
  95.             var forms = [{ //possivle forms
  96.                     x: [-1, 0, 1, 2], //I
  97.                     y: [0, 0, 0, 0],
  98.                     w: 4,
  99.                     h: 1
  100.                 },
  101.                 {
  102.                     x: [-1, 0, 0, 1], //T
  103.                     y: [0, 0, 1, 0],
  104.                     w: 3,
  105.                     h: 2
  106.                 },
  107.                 {
  108.                     x: [-1, -1, 0, 0], //O
  109.                     y: [0, 1, 0, 1],
  110.                     w: 2,
  111.                     h: 2
  112.                 },
  113.                 {
  114.                     x: [-1, 0, 1, 1], //L
  115.                     y: [0, 0, 0, 1],
  116.                     w: 3,
  117.                     h: 2
  118.                 },
  119.                 {
  120.                     x: [-1, -1, 0, 1], //L rev
  121.                     y: [1, 0, 0, 0],
  122.                     w: 3,
  123.                     h: 2
  124.                 },
  125.                 {
  126.                     x: [-1, 0, 0, 1], //Z
  127.                     y: [1, 1, 0, 0],
  128.                     w: 3,
  129.                     h: 2
  130.                 },
  131.                 {
  132.                     x: [-1, 0, 0, 1], //Z rev
  133.                     y: [0, 0, 1, 1],
  134.                     w: 3,
  135.                     h: 2
  136.                 }
  137.             ];
  138.             var turn = [ //rotation matrix 90deg left
  139.                 [0, -1],
  140.                 [1, 0]
  141.             ];
  142.  
  143.             function init() { //at start of each game.
  144.                 lastsec = 0;
  145.                 size = { //grid width/height in blocks
  146.                     w: 8,
  147.                     h: 14
  148.                 };
  149.  
  150.                 grid = new Array(size.h + 1); //create 0 filles grid
  151.                 for (var i = 0; i < size.h + 1; ++i) {
  152.                     grid[i] = new Array(size.w + 1);
  153.                     grid[i].fill(0);
  154.                 }
  155.  
  156.                 pixels = []; //moving blocks
  157.                 for (var i = 0; i < 4; ++i) //4 block pieces, initialies at top-center
  158.                     pixels.push({
  159.                         x: Math.ceil(size.w / 2.0),
  160.                         y: 0
  161.                     });
  162.  
  163.                 canvas = document.getElementById('canvas');
  164.                 ctx = canvas.getContext('2d'); //get context for rendering
  165.                 rescale(); //adapt screen size
  166.  
  167.                 //audio handles to play on demand
  168.                 audio_shuffle = document.querySelector("audio.shuffle");
  169.                 audio_down = document.querySelector("audio.down");
  170.                 audio_gameover = document.querySelector("audio.gameover");
  171.                 audio_clean = document.querySelector("audio.clean");
  172.                 audio_background = document.querySelector("audio.background");
  173.                 audio_background.volume = 0.2;
  174.                 if (playMusic) audio_background.play();
  175.  
  176.                 //initialize flags
  177.                 pausehover = false; //not hovering over pause button
  178.                 deleting = false; //no line deleting animation atm
  179.                 ending = false; //game is not ending (animation)
  180.                 paused = false; //game is not paused (pause text)
  181.                 starting = true; //game is starting (splash screen)
  182.  
  183.                 score = 0; //initial score 0
  184.                 nextFormInd = getRandomInt(forms.length); //next form coming down
  185.                 newPixel(false); //create form at top, no dropping yet
  186.             }
  187.  
  188.             function rescale() { //adapt to screen size
  189.                 var sBarWPerc = 0.2; //20% of width is sidebar
  190.  
  191.                 var scale = size.w / size.h / (1 - sBarWPerc); //block ratio rendered 1:1. Add sidebar
  192.                 canvas.height = canvas.parentElement.clientHeight; //canvas Height 100% of parent
  193.                 canvas.width = scale * canvas.height; //width rescale to keep aspect ratio
  194.  
  195.                 //calculate current metrics from canvas-height and grid-size
  196.                 size.bw = Math.floor(canvas.clientWidth * (1 - sBarWPerc) / (size.w)); //bw=block with in px
  197.                 size.bh = Math.floor(canvas.clientHeight / (size.h + 1));
  198.                 size.ph = Math.floor(size.bh * (size.h + 1)); //ph = total height in pixel
  199.                 size.pw = Math.floor(size.bw * (size.w));
  200.  
  201.                 sidewidth = canvas.clientWidth * (sBarWPerc);
  202.  
  203.                 pausebounds = { //pause button, click/hover region
  204.                     x: size.pw,
  205.                     y: size.ph * 0.44,
  206.                     w: sidewidth,
  207.                     h: size.ph * 0.05
  208.                 };
  209.                 startbounds = {
  210.                     x: 0,
  211.                     y: (size.ph / 2) * 0.8,
  212.                     w: size.pw + sidewidth,
  213.                     h: (size.ph / 2) * 0.4
  214.                 };
  215.                 muteSFXbounds = {
  216.                     x: size.pw,
  217.                     y: size.ph * 0.55,
  218.                     w: sidewidth,
  219.                     h: size.ph * 0.04
  220.                 };
  221.                 muteMusicbounds = {
  222.                     x: size.pw,
  223.                     y: size.ph * 0.65,
  224.                     w: sidewidth,
  225.                     h: size.ph * 0.04
  226.                 };
  227.  
  228.                 //render resource images
  229.                 //render scaling svg DOM: img_block can be used in ctx.drawImage at the end
  230.                 var svg_block = document.querySelector("svg.block");
  231.                 svg_block.setAttribute("width", size.bw);
  232.                 svg_block.setAttribute("height", size.bh);
  233.                 var xml = new XMLSerializer().serializeToString(svg_block);
  234.                 var img = 'data:image/svg+xml;base64,' + btoa(xml);
  235.                 img_block = document.querySelector("img.block");
  236.                 img_block.src = whitePx; //change of src to force rerender/override cache
  237.                 img_block.src = img;
  238.  
  239.                 //render raster image, no rescaling
  240.                 img_back = document.querySelector("img.back");
  241.  
  242.                 //gradient creation
  243.                 gradients=[];
  244.                 var grad = ctx.createLinearGradient(0, 0, 0, size.ph); //sidebar background
  245.                 grad.addColorStop(0, "rgba(255,255,255,0.1)");
  246.                 grad.addColorStop("0.5", "rgba(0,100,0,0.5)");
  247.                 grad.addColorStop(1, "rgba(255,255,255,0.1)");
  248.                 gradients.push(grad);
  249.                 grad = ctx.createLinearGradient(0, pausebounds.y, 0, pausebounds.y + pausebounds
  250.                     .h); //pause hover text color
  251.                 grad.addColorStop(0, "rgba(255,255,255,1)");
  252.                 grad.addColorStop("0.5", "rgba(0,100,0,1)");
  253.                 grad.addColorStop(1, "rgba(255,255,255,1)");
  254.                 gradients.push(grad);
  255.                 grad = ctx.createLinearGradient(0, 0, 0, size.ph); //splash screen background
  256.                 grad.addColorStop(0, "rgba(255,255,255,0.1)");
  257.                 grad.addColorStop("0.4", "rgba(0,100,0,1)");
  258.                 grad.addColorStop("0.6", "rgba(0,100,0,1)");
  259.                 grad.addColorStop(1, "rgba(255,255,255,0.1)");
  260.                 gradients.push(grad);
  261.                 grad = ctx.createLinearGradient(0, startbounds.y, 0, startbounds.y + startbounds
  262.                     .h); //START text color
  263.                 grad.addColorStop(0, "rgba(255,255,255,1)");
  264.                 grad.addColorStop("0.5", "rgba(0,100,0,1)");
  265.                 grad.addColorStop(1, "rgba(255,255,255,1)");
  266.                 gradients.push(grad);
  267.             }
  268.  
  269.             function tileBackground(bgw, bgh) { //background is not rescaled, so we tile it
  270.                 for (var y = 0; y < Math.ceil(size.ph / bgh); ++y) {
  271.                     for (var x = 0; x < Math.ceil((size.pw+sidewidth) / bgw); ++x) {
  272.                         ctx.drawImage(img_back, x * bgw, y * bgh);
  273.                     }
  274.                 }
  275.             }
  276.  
  277.             function getRandomInt(max) { //random integer between 0 and max
  278.                 return Math.floor(Math.random() * Math.floor(max));
  279.             }
  280.  
  281.             function mInBounds(bounds) { //mouse coordinates within bounds (x,y,w,h)
  282.                 if (mx > bounds.x && mx < bounds.x + bounds.w && my > bounds.y && my < bounds.y + bounds.h)
  283.                     return true;
  284.                 return false;
  285.             }
  286.  
  287.             function dropPixel(delay) {
  288.                 //move dropping pixels down.
  289.                 //dropspeed is 1 block /s using getSeconds!=lastsec
  290.                 //delay=false to drop multiple times without delay (e.g. drop to bottom)
  291.                 //returns 1 when form stuck and new form created
  292.  
  293.                 var dat = new Date();
  294.                 if (!delay || lastsec != dat.getSeconds()) {
  295.                     for (var i = 0; i < pixels.length; ++i) pixels[i].y++;
  296.                     lastsec = dat.getSeconds();
  297.                 } else {
  298.                     return 0;
  299.                 }
  300.  
  301.                 var dropped = false;
  302.                 for (var i = 0; i < pixels.length; ++i) { //check if stuck (bottom or overlaying other form)
  303.                     if (pixels[i].y < 0) continue;
  304.                     if (pixels[i].y == size.h + 1 || grid[pixels[i].y][pixels[i].x] == 1) dropped = true;
  305.                 }
  306.                 if (dropped) { //if stuck, new form
  307.                     newPixel(true);
  308.                     return 1;
  309.                 }
  310.                 return 0;
  311.             }
  312.  
  313.             function newPixel(drop) { //create new form
  314.                 //curform=forms[...] is reference, not copy.
  315.                 //Object.assign also only shallow copy with deep references
  316.                 //solution is JSON-copy to test changes without applying
  317.                 //otherwise rotating would also rotate base-forms
  318.  
  319.                 curForm = JSON.parse(JSON.stringify(forms[nextFormInd]));
  320.                 nextFormInd = getRandomInt(forms.length);
  321.                 for (var i = 0; i < pixels.length; ++i) {
  322.                     if (drop) { //current form overlays form on grid, so move it up again
  323.                         pixels[i].y--;
  324.                         grid[pixels[i].y][pixels[i].x] = 1; //store form on grid
  325.                     }
  326.  
  327.                     pixels[i].y = 0 + curForm.y[i]; //new form generation
  328.                     pixels[i].x = Math.floor(size.w / 2.0) - 1 + curForm.x[i];
  329.  
  330.                     if (grid[pixels[i].y][pixels[i].x] == 1)
  331.                         return endGame(); //newly created form blocked = game_over
  332.                 }
  333.             }
  334.  
  335.             //delete lines, index in ys-array. also awards scores.
  336.             function deleteFullLine(ys) {
  337.                 if (ending) return;
  338.                 var y;
  339.                 for (var yi = ys.length - 1; yi >= 0; --yi) {
  340.                     y = ys[yi];
  341.                     ny = new Array(size.w + 1);
  342.                     ny.fill(0);
  343.                     grid.splice(y, 1);
  344.                     grid.unshift(ny);
  345.                 }
  346.                 score += rewards[ys.length - 1];
  347.                 deleting = false;
  348.             }
  349.  
  350.             //checks for full lines, marks grids for highlight (=2) for 1s and triggers deleteFullLine
  351.             function clearFullLine() {
  352.                 var su = 0;
  353.                 var ny;
  354.                 var ys = [];
  355.  
  356.                 for (var y = size.h; y >= 0; --y) {
  357.                     su = 0;
  358.                     for (var x = 0; x < size.w; ++x)
  359.                         su += grid[y][x];
  360.                     if (su == size.w) {
  361.                         ys.push(y);
  362.                         for (var x = 0; x < size.w; ++x)
  363.                             grid[y][x] = 2;
  364.                     }
  365.                 }
  366.                 if (ys.length > 0) {
  367.                     setTimeout(deleteFullLine.bind(null, ys), 1000);
  368.                     if (playSFX) audio_clean.play();
  369.                     deleting = true;
  370.                 }
  371.             }
  372.  
  373.             //ending animation. recursive call to fill grid upwards line-per-line 50ms step.
  374.             function fillUpwards(line,
  375.                 resolve
  376.             ) { //called from a Promise, so "resolve" is returning the Promise and trigger its "then()" function
  377.                 for (var x = 0; x < size.w; ++x)
  378.                     grid[line][x] = 1;
  379.                 if (line > 0) setTimeout(fillUpwards.bind(null, line - 1, resolve), 50);
  380.                 else setTimeout(resolve, 1000);
  381.             }
  382.  
  383.             function animFillUpwards() { //triggers fillUpward-animation and returns a promise so you can wait for its end.
  384.                 return new Promise(function (resolve, reject) {
  385.                     fillUpwards(size.h, resolve);
  386.                 });
  387.             }
  388.  
  389.             function endGame() { //game ends. flag set, animFIllUpwards triggered. afterwards, starting-splashscreen flag set.
  390.                 ending = true;
  391.                 if (playSFX) audio_gameover.play();
  392.                 animFillUpwards()
  393.                     .then(function () {
  394.                         ending = false;
  395.                         starting = true;
  396.                     });
  397.             }
  398.  
  399.             function renderGrid() { //render grid and pixels to the playboard
  400.                 //ctx.fillStyle = 'rgb(255,225,225)'; //plain color background
  401.                 //ctx.fillRect(0, 0, size.pw, size.ph);
  402.                 tileBackground(400, 400); //tiled background, width/height of image
  403.  
  404.                 for (var cy = 0; cy < size.h + 1; ++
  405.                     cy) { //render set blocks (grid=1) or highlighted/deleting ones (grid=2)
  406.                     for (var cx = 0; cx < size.w + 1; ++cx) {
  407.                         if (grid[cy][cx] == 1) { //set blocks
  408.                             ctx.fillStyle = 'rgba(255,0,0,0.2)'; //reddish
  409.                             ctx.drawImage(img_block, cx * size.bw, cy * size
  410.                                 .bh); //img_block from svg canvas is already scaled correctly
  411.                             ctx.fillRect(cx * size.bw, cy * size.bh, size.bw, size
  412.                                 .bh); //change image by drawing transparent above
  413.                         } else if (grid[cy][cx] == 2) { //deleting blocks
  414.                             ctx.fillStyle = 'rgba(255,255,255,0.2)'; //whitish
  415.                             ctx.drawImage(img_block, cx * size.bw, cy * size.bh);
  416.                             ctx.fillRect(cx * size.bw, cy * size.bh, size.bw, size.bh);
  417.                         }
  418.                     }
  419.                 }
  420.                 ctx.fillStyle = 'rgba(0,0,0,0.2)'; //moving blocks, blackish
  421.                 for (var i = 0; i < pixels.length; ++i) {
  422.                     ctx.drawImage(img_block, pixels[i].x * size.bw, pixels[i].y * size.bh);
  423.                     ctx.fillRect(pixels[i].x * size.bw, pixels[i].y * size.bh, size.bw, size.bh);
  424.                 }
  425.                 if (paused) {
  426.  
  427.                     ctx.textAlign = "center";
  428.                     ctx.textBaseline = "middle";
  429.                     ctx.font = (size.pw / 4) + "px Georgia";
  430.                     ctx.fillStyle = gradients[1];
  431.                     ctx.fillText("PAUSE", size.pw / 2, size.ph / 2);
  432.                     ctx.strokeStyle = "rgb(0,155,0)";
  433.                     ctx.strokeText("PAUSE", size.pw / 2, size.ph / 2);
  434.                 }
  435.             }
  436.  
  437.             function renderSidebar() {
  438.                 ctx.fillStyle = gradients[0]; //background sidebar
  439.                 ctx.fillRect(size.pw, 0, sidewidth, size.ph);
  440.                 ctx.fillStyle = "rgba(255,255,255,0.5)"; //white highlight bg for numbers
  441.                 ctx.fillRect(size.pw, size.ph * 0.1, sidewidth, size.ph * 0.05); //score background
  442.                 ctx.fillRect(size.pw, size.ph * 0.25, sidewidth, size.ph * 0.15); //next preview background
  443.  
  444.                 canvas.style.cursor = "default";
  445.  
  446.                 //hover over pause button
  447.                 if (mInBounds(pausebounds)) {
  448.                     ctx.fillStyle = "rgba(150,255,150,0.8)";
  449.                     canvas.style.cursor = "pointer";
  450.                 } else { //default style
  451.                     ctx.fillStyle = "rgba(150,255,150,0.4)";
  452.                 }
  453.                 ctx.fillRect(pausebounds.x, pausebounds.y, pausebounds.w, pausebounds.h);
  454.  
  455.                 for (var i = 0; i < curForm.x.length; ++i) { //render next
  456.                     ctx.drawImage(img_block,
  457.                         size.pw + Math.floor((3 - forms[nextFormInd].w / 2) * size.bw / 2) +
  458.                         forms[nextFormInd].x[i] * size.bw / 2,
  459.                         size.ph * 0.29 + forms[nextFormInd].y[i] * size.bh / 2 + (1 - forms[nextFormInd].h /
  460.                             2) * size
  461.                         .bh / 2,
  462.                         size.bw / 2,
  463.                         size.bh / 2); //some math to make blocks 1/2 wide and center on field
  464.                 }
  465.  
  466.                 //rendered text
  467.                 ctx.textAlign = "center";
  468.                 ctx.textBaseline = "top";
  469.                 ctx.font = size.ph * 0.04 +
  470.                     "px Georgia"; //font.size in px, but rescale() will also scale it with parent Element-height
  471.                 ctx.fillStyle = "black";
  472.                 ctx.fillText(score, size.pw + sidewidth / 2, size.ph * 0.1);
  473.                 ctx.fillText("SCORE", size.pw + sidewidth / 2, size.ph * 0.06);
  474.                 ctx.fillText("NEXT", size.pw + sidewidth / 2, size.ph * 0.20);
  475.                 ctx.fillText("PAUSE", size.pw + sidewidth / 2, size.ph * 0.45);
  476.  
  477.                 //mutebutton
  478.                 if (playSFX)
  479.                     ctx.fillStyle = "green";
  480.                 else
  481.                     ctx.fillStyle = "red";
  482.                 ctx.fillRect(muteSFXbounds.x, muteSFXbounds.y, muteSFXbounds.w, muteSFXbounds.h);
  483.                 ctx.fillStyle = "black";
  484.                 ctx.fillText("SFX", size.pw + sidewidth / 2, muteSFXbounds.y);
  485.                 if (mInBounds(muteSFXbounds)) {
  486.                     canvas.style.cursor = "pointer";
  487.                 }
  488.  
  489.                 //mutebutton
  490.                 if (playMusic)
  491.                     ctx.fillStyle = "green";
  492.                 else
  493.                     ctx.fillStyle = "red";
  494.                 ctx.fillRect(muteMusicbounds.x, muteMusicbounds.y, muteMusicbounds.w, muteMusicbounds.h);
  495.                 ctx.fillStyle = "black";
  496.                 ctx.fillText("MUSIC", size.pw + sidewidth / 2, muteMusicbounds.y);
  497.                 if (mInBounds(muteMusicbounds)) {
  498.                     canvas.style.cursor = "pointer";
  499.                 }
  500.             }
  501.  
  502.             function renderSplash() { //start screen
  503.                 var dat = new Date(); //date for pulsing effect
  504.  
  505.                 ctx.fillStyle = gradients[2]; //background
  506.                 ctx.fillRect(0, 0, size.pw + sidewidth, size.ph);
  507.  
  508.                 //start text filled with gradient
  509.                 ctx.textAlign = "center";
  510.                 ctx.textBaseline = "middle";
  511.                 ctx.font = (size.pw / 4) + "px Georgia";
  512.                 ctx.fillStyle = gradients[3];
  513.                 ctx.fillText("START", (size.pw + sidewidth) / 2, size.ph / 2);
  514.  
  515.                 //pulsing outline effect
  516.                 if (dat.getMilliseconds() < 500) //brighter first 1/2s, darker last 1/2s, 1s period
  517.                     ctx.strokeStyle = "rgb(0," + dat.getMilliseconds() / 500 * 255 +
  518.                     ",0)"; //linear transition of green-part
  519.                 else
  520.                     ctx.strokeStyle = "rgb(0," + (255 - (dat.getMilliseconds() - 500) / 500 * 255) + ",0)";
  521.                 ctx.strokeText("START", (size.pw + sidewidth) / 2, size.ph / 2);
  522.  
  523.                 //hover Start text
  524.                 if (mInBounds(startbounds)) {
  525.                     canvas.style.cursor = "pointer";
  526.                 } else {
  527.                     canvas.style.cursor = "default";
  528.                 }
  529.             }
  530.  
  531.             //main render cycle.
  532.             //requestAnimationFrame renders as often as possible, but within screen rendering speed.
  533.             //typically around 60fps.
  534.             //modern browsers don't render when canvas not on screen/different tab.
  535.             function draw() {
  536.                 if (!deleting && !ending && !paused && !starting) { //flags where normal game is paused
  537.                     dropPixel(true); //dropPixel has an inbuilt 1s delay per drop
  538.                     clearFullLine(); //check for full lines and remove with animation
  539.                 }
  540.                 renderGrid
  541.                     (); //renders set, moving and highlighted blocks. Input(Key/mouse) are immediatelly reacted to.
  542.                 renderSidebar(); //renders sidebar
  543.                 if (starting) renderSplash(); //start of game
  544.                 window.requestAnimationFrame(draw);
  545.             }
  546.  
  547.             init(); //start game
  548.             window.requestAnimationFrame(draw); //render game
  549.  
  550.             //user input section
  551.             canvas.addEventListener("mouseup", function (evt) { //click
  552.                 if (mInBounds(pausebounds) && !ending && !starting && !deleting) { //pause button
  553.                     paused = !paused; //toggle pause flag
  554.                 } else
  555.                 if (mInBounds(startbounds) && starting) { //start button
  556.                     init(); //restart the game
  557.                     starting = false; //end starting screen
  558.                 } else
  559.                 if (mInBounds(muteSFXbounds)) { //mute sfx button
  560.                     playSFX = !playSFX;
  561.                 }
  562.                 if (mInBounds(muteMusicbounds)) { //mute music button
  563.                     playMusic = !playMusic;
  564.                     if (!playMusic) audio_background.pause();
  565.                     else audio_background.play();
  566.                 }
  567.             });
  568.             canvas.addEventListener("mousemove", function (evt) { //fetch mouse position
  569.                 var BB = canvas.getBoundingClientRect();
  570.                 mx = parseInt(evt.clientX - BB.left);
  571.                 my = parseInt(evt.clientY - BB.top);
  572.             });
  573.             window.addEventListener("resize", function () { //rescale on window-change
  574.                 rescale();
  575.                 draw();
  576.             });
  577.             window.addEventListener("keydown", function (e) { //prevent keyboard scrolling events on page
  578.                 // space and arrow keys
  579.                 if ([32, 37, 38, 39, 40].indexOf(e.keyCode) > -1) {
  580.                     e.preventDefault();
  581.                 }
  582.             }, false);
  583.             window.addEventListener("keyup", function (keyEv) { //key input
  584.                 var allow = true;
  585.                 var newX, newY;
  586.                 if ((starting && keyEv.which != 32) || deleting || ending || (paused && keyEv.which != 80))
  587.                     return;
  588.                 //disable input when splash, while animation (deleting/ending) and if paused, unless it's p to end pause
  589.  
  590.                 switch (keyEv.which) { //fetch pressed key
  591.                     case 80: //p
  592.                         paused = !paused;
  593.                         break;
  594.                     case 32: //space
  595.                         if (starting) {
  596.                             init();
  597.                             starting = false;
  598.                         } else {
  599.                             if (playSFX) audio_down.load();
  600.                             if (playSFX) audio_down.play();
  601.                             while (dropPixel(false) == 0) {}; //drop to bottom
  602.                         }
  603.                         break;
  604.                     case 38: //up turn
  605.                         //turn moving blocks by 90deg left
  606.  
  607.                         if (playSFX) audio_shuffle.load();
  608.                         if (playSFX) audio_shuffle.play();
  609.  
  610.                         zwiForm = JSON.parse(JSON.stringify(curForm)); //new rotated blocks in zwiForm
  611.                         for (var i = 0; i < pixels.length; ++i) {
  612.                             zwiForm.x[i] = turn[0][0] * curForm.x[i] + turn[1][0] * curForm.y[i];
  613.                             zwiForm.y[i] = turn[0][1] * curForm.x[i] + turn[1][1] * curForm.y[i];
  614.                         }
  615.                         //check of rotated form actually fits
  616.                         for (var i = 0; i < pixels.length; ++i) {
  617.                             newX = pixels[i].x - curForm.x[i] + zwiForm.x[i];
  618.                             newY = pixels[i].y - curForm.y[i] + zwiForm.y[i];
  619.  
  620.                             if (newX < 0 || newX > size.w - 1) allow = false;
  621.                             else if (newY < 0 || newY > size.h - 1) allow = false;
  622.                             else if (grid[newY][newX] == 1) allow = false;
  623.                         }
  624.                         if (allow) { //new form fits, override current form with rotated form
  625.                             for (var i = 0; i < pixels.length; ++i) { //remove current form
  626.                                 pixels[i].x -= curForm.x[i];
  627.                                 pixels[i].y -= curForm.y[i];
  628.                             }
  629.  
  630.                             curForm = JSON.parse(JSON.stringify(
  631.                                 zwiForm)); //copy rotated form to current
  632.  
  633.                             for (var i = 0; i < pixels.length; ++i) { //apply rotated form
  634.                                 pixels[i].x += curForm.x[i];
  635.                                 pixels[i].y += curForm.y[i];
  636.                             }
  637.                         }
  638.                         break;
  639.                     case 40: //down move
  640.                         for (var i = 0; i < pixels.length; ++i) //all moving pixels
  641.                             if (!(pixels[i].y < size.h - 1 && grid[pixels[i].y + 1][pixels[i].x] !=
  642.                                     1)) //check for grid-boundaries and existing blocks
  643.                                 allow = false;
  644.                         if (allow) //only move when allowed
  645.                             for (var i = 0; i < pixels.length; ++i) pixels[i].y++;
  646.                         break;
  647.                     case 37: //left move
  648.                         for (var i = 0; i < pixels.length; ++i)
  649.                             if (!(pixels[i].x > 0 && grid[pixels[i].y][pixels[i].x - 1] != 1))
  650.                                 allow = false;
  651.                         if (allow)
  652.                             for (var i = 0; i < pixels.length; ++i) pixels[i].x--;
  653.                         break;
  654.                     case 39: //right move
  655.                         for (var i = 0; i < pixels.length; ++i)
  656.                             if (!(pixels[i].x < size.w - 1 && grid[pixels[i].y][pixels[i].x + 1] !=
  657.                                     1))
  658.                                 allow =
  659.                                 false;
  660.                         if (allow)
  661.                             for (var i = 0; i < pixels.length; ++i) pixels[i].x++;
  662.                         break;
  663.                 };
  664.             });
  665.         })();
  666.     </script>
  667. </body>
  668.  
  669. </html>
Add Comment
Please, Sign In to add comment