sapbot

Sketch Hop

Feb 17th, 2026
50
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
HTML 28.27 KB | None | 0 0
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4.     <meta charset="UTF-8">
  5.     <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
  6.     <title>Sketch Hop</title>
  7.     <style>
  8.         * {
  9.             margin: 0;
  10.             padding: 0;
  11.             box-sizing: border-box;
  12.             touch-action: none;
  13.         }
  14.        
  15.         body {
  16.             overflow: hidden;
  17.             background: #f0f0f0;
  18.             font-family: 'Comic Sans MS', 'Chalkboard SE', sans-serif;
  19.             display: flex;
  20.             justify-content: center;
  21.             align-items: center;
  22.             height: 100vh;
  23.         }
  24.        
  25.         #gameContainer {
  26.             position: relative;
  27.             box-shadow: 0 0 20px rgba(0,0,0,0.3);
  28.         }
  29.        
  30.         canvas {
  31.             display: block;
  32.             background: #fff;
  33.             background-image:
  34.                 linear-gradient(#e0e0e0 1px, transparent 1px),
  35.                 linear-gradient(90deg, #e0e0e0 1px, transparent 1px);
  36.             background-size: 20px 20px;
  37.         }
  38.        
  39.         #ui {
  40.             position: absolute;
  41.             top: 10px;
  42.             left: 10px;
  43.             right: 10px;
  44.             display: flex;
  45.             justify-content: space-between;
  46.             pointer-events: none;
  47.             font-size: 20px;
  48.             font-weight: bold;
  49.             color: #333;
  50.             text-shadow: 2px 2px 0 #fff;
  51.         }
  52.        
  53.         #gameOver {
  54.             position: absolute;
  55.             top: 50%;
  56.             left: 50%;
  57.             transform: translate(-50%, -50%);
  58.             text-align: center;
  59.             display: none;
  60.             background: rgba(255,255,255,0.95);
  61.             padding: 30px;
  62.             border-radius: 20px;
  63.             border: 3px solid #333;
  64.             box-shadow: 5px 5px 0 #333;
  65.         }
  66.        
  67.         #gameOver h1 {
  68.             font-size: 36px;
  69.             margin-bottom: 10px;
  70.             color: #ff6b6b;
  71.         }
  72.        
  73.         #gameOver p {
  74.             font-size: 24px;
  75.             margin: 10px 0;
  76.         }
  77.        
  78.         button {
  79.             margin-top: 15px;
  80.             padding: 15px 30px;
  81.             font-size: 20px;
  82.             font-family: inherit;
  83.             font-weight: bold;
  84.             background: #4ecdc4;
  85.             border: 3px solid #333;
  86.             border-radius: 10px;
  87.             cursor: pointer;
  88.             box-shadow: 3px 3px 0 #333;
  89.             transition: transform 0.1s;
  90.         }
  91.        
  92.         button:active {
  93.             transform: translate(2px, 2px);
  94.             box-shadow: 1px 1px 0 #333;
  95.         }
  96.        
  97.         #instructions {
  98.             position: absolute;
  99.             bottom: 10px;
  100.             left: 50%;
  101.             transform: translateX(-50%);
  102.             text-align: center;
  103.             color: #666;
  104.             font-size: 14px;
  105.             pointer-events: none;
  106.         }
  107.        
  108.         @media (max-width: 600px) {
  109.             #ui { font-size: 16px; }
  110.             #gameOver h1 { font-size: 28px; }
  111.             #gameOver p { font-size: 18px; }
  112.             button { font-size: 16px; padding: 12px 24px; }
  113.         }
  114.     </style>
  115. </head>
  116. <body>
  117.     <div id="gameContainer">
  118.         <canvas id="gameCanvas" width="400" height="600"></canvas>
  119.         <div id="ui">
  120.             <span id="score">0</span>
  121.             <span id="highScore">High: 0</span>
  122.         </div>
  123.         <div id="gameOver">
  124.             <h1>Game Over!</h1>
  125.             <p id="finalScore">Score: 0</p>
  126.             <button onclick="game.restart()">Hop Again!</button>
  127.         </div>
  128.         <div id="instructions">← → Arrow keys or tilt to move • Tap/Click to shoot</div>
  129.     </div>
  130.  
  131.     <script>
  132.         const canvas = document.getElementById('gameCanvas');
  133.         const ctx = canvas.getContext('2d');
  134.        
  135.         function resizeCanvas() {
  136.             const maxWidth = Math.min(window.innerWidth - 20, 400);
  137.             const maxHeight = Math.min(window.innerHeight - 20, 600);
  138.             canvas.style.width = maxWidth + 'px';
  139.             canvas.style.height = maxHeight + 'px';
  140.         }
  141.         resizeCanvas();
  142.         window.addEventListener('resize', resizeCanvas);
  143.  
  144.         class SketchHop {
  145.             constructor() {
  146.                 this.width = 400;
  147.                 this.height = 600;
  148.                 this.reset();
  149.                 this.setupControls();
  150.                 this.loop();
  151.             }
  152.            
  153.             reset() {
  154.                 this.player = {
  155.                     x: 200,
  156.                     y: 400,
  157.                     vx: 0,
  158.                     vy: 0,
  159.                     width: 30,
  160.                     height: 40,
  161.                     facingRight: true,
  162.                     jetpack: 0,
  163.                     propeller: 0
  164.                 };
  165.                
  166.                 this.platforms = [];
  167.                 this.monsters = [];
  168.                 this.projectiles = [];
  169.                 this.particles = [];
  170.                
  171.                 this.score = 0;
  172.                 this.highScore = localStorage.getItem('sketchHopHighScore') || 0;
  173.                 this.camera = 0;
  174.                 this.gameOver = false;
  175.                 this.lastPlatformX = 200;
  176.                
  177.                 // Starting platform directly under player
  178.                 this.platforms.push({
  179.                     x: 170,
  180.                     y: 440,
  181.                     width: 60,
  182.                     height: 15,
  183.                     type: 'normal',
  184.                     broken: false,
  185.                     dx: 0,
  186.                     springOffset: 0
  187.                 });
  188.                
  189.                 // Generate initial platforms
  190.                 let currentY = 440;
  191.                 for (let i = 0; i < 15; i++) {
  192.                    currentY = this.addPlatformAbove(currentY, i);
  193.                }
  194.                
  195.                this.updateUI();
  196.            }
  197.            
  198.            addPlatformAbove(lastY, index) {
  199.                const heightProgress = Math.abs(this.camera) / 1000;
  200.                
  201.                // Vertical gap: 90-120px
  202.                const minGap = 90;
  203.                const maxGap = 120;
  204.                const newY = lastY - (minGap + Math.random() * (maxGap - minGap));
  205.                
  206.                // Horizontal reach: max 100px from last platform
  207.                const maxReach = 100;
  208.                let minX = Math.max(20, this.lastPlatformX - maxReach);
  209.                let maxX = Math.min(this.width - 80, this.lastPlatformX + maxReach);
  210.                
  211.                if (maxX - minX < 40) {
  212.                    if (this.lastPlatformX < this.width / 2) {
  213.                        maxX = Math.min(this.width - 80, this.lastPlatformX + maxReach);
  214.                        minX = Math.max(20, maxX - 100);
  215.                    } else {
  216.                        minX = Math.max(20, this.lastPlatformX - maxReach);
  217.                        maxX = Math.min(this.width - 80, minX + 100);
  218.                    }
  219.                }
  220.                
  221.                const newX = minX + Math.random() * (maxX - minX);
  222.                this.lastPlatformX = newX;
  223.                
  224.                // Platform types based on height
  225.                let type = 'normal';
  226.                const rand = Math.random();
  227.                if (heightProgress > 0.3 && rand > 0.7) type = 'moving';
  228.                 else if (heightProgress > 0.5 && rand > 0.8) type = 'breakable';
  229.                 else if (heightProgress > 0.8 && rand > 0.85) type = 'spring';
  230.                
  231.                 this.platforms.push({
  232.                     x: newX,
  233.                     y: newY,
  234.                     width: 60,
  235.                     height: 15,
  236.                     type: type,
  237.                     broken: false,
  238.                     dx: type === 'moving' ? (Math.random() > 0.5 ? 2 : -2) : 0,
  239.                     springOffset: type === 'spring' ? Math.random() * 40 + 10 : 0
  240.                 });
  241.                
  242.                 // Spawn monster occasionally (not on first few platforms)
  243.                 if (index > 3 && Math.random() > 0.7) {
  244.                    this.spawnMonster(newY);
  245.                 }
  246.                
  247.                 return newY;
  248.             }
  249.            
  250.             spawnMonster(y) {
  251.                 const heightProgress = Math.abs(this.camera) / 1000;
  252.                 const canSpawnUFO = heightProgress > 1.0;
  253.                 const ufoChance = canSpawnUFO ? 0.05 : 0;
  254.                
  255.                 const baseMonsterChance = 0.25 + (heightProgress * 0.1);
  256.                 const monsterChance = Math.min(baseMonsterChance, 0.4);
  257.                
  258.                 if (Math.random() < monsterChance) {
  259.                    const isUFO = Math.random() < ufoChance;
  260.                    
  261.                    this.monsters.push({
  262.                        x: Math.random() * (this.width - 40) + 20,
  263.                        y: y,
  264.                        width: 30,
  265.                        height: 30,
  266.                        vx: (Math.random() - 0.5) * (1.5 + heightProgress * 0.5),
  267.                        type: isUFO ? 'ufo' : 'normal',
  268.                        hp: 1
  269.                    });
  270.                }
  271.            }
  272.            
  273.            setupControls() {
  274.                this.keys = {};
  275.                
  276.                window.addEventListener('keydown', (e) => {
  277.                     this.keys[e.key] = true;
  278.                     if (e.key === ' ' || e.key === 'ArrowUp') {
  279.                         e.preventDefault();
  280.                         this.shoot();
  281.                     }
  282.                 });
  283.                
  284.                 window.addEventListener('keyup', (e) => {
  285.                     this.keys[e.key] = false;
  286.                 });
  287.                
  288.                 let touchStartX = 0;
  289.                 canvas.addEventListener('touchstart', (e) => {
  290.                     touchStartX = e.touches[0].clientX;
  291.                     this.shoot();
  292.                 });
  293.                
  294.                 canvas.addEventListener('touchmove', (e) => {
  295.                     e.preventDefault();
  296.                     const touch = e.touches[0];
  297.                     const diff = touch.clientX - touchStartX;
  298.                     this.player.vx = diff * 0.1;
  299.                     this.player.facingRight = diff > 0;
  300.                 });
  301.                
  302.                 canvas.addEventListener('touchend', () => {
  303.                     this.player.vx = 0;
  304.                 });
  305.                
  306.                 canvas.addEventListener('mousedown', () => this.shoot());
  307.             }
  308.            
  309.             shoot() {
  310.                 if (this.gameOver || this.player.jetpack > 0 || this.player.propeller > 0) return;
  311.                
  312.                 this.projectiles.push({
  313.                     x: this.player.x + (this.player.facingRight ? 20 : -20),
  314.                     y: this.player.y - 10,
  315.                     vx: this.player.facingRight ? 8 : -8,
  316.                     vy: -2,
  317.                     life: 60
  318.                 });
  319.             }
  320.            
  321.             update() {
  322.                 if (this.gameOver) return;
  323.                
  324.                 // Player movement
  325.                 if (this.keys['ArrowLeft'] || this.keys['a']) {
  326.                     this.player.vx = -5;
  327.                     this.player.facingRight = false;
  328.                 } else if (this.keys['ArrowRight'] || this.keys['d']) {
  329.                     this.player.vx = 5;
  330.                     this.player.facingRight = true;
  331.                 } else if (!this.keys['touch']) {
  332.                     this.player.vx *= 0.8;
  333.                 }
  334.                
  335.                 // Screen wrap
  336.                 if (this.player.x < -20) this.player.x = this.width + 20;
  337.                if (this.player.x > this.width + 20) this.player.x = -20;
  338.                
  339.                 // Powerups
  340.                 if (this.player.jetpack > 0) {
  341.                     this.player.jetpack--;
  342.                     this.player.vy = -8;
  343.                     this.addParticles(this.player.x, this.player.y + 20, 'fire');
  344.                 } else if (this.player.propeller > 0) {
  345.                     this.player.propeller--;
  346.                     this.player.vy = -4;
  347.                 } else {
  348.                     this.player.vy += 0.4;
  349.                 }
  350.                
  351.                 this.player.x += this.player.vx;
  352.                 this.player.y += this.player.vy;
  353.                
  354.                 // Camera follow
  355.                 if (this.player.y < 300 + this.camera) {
  356.                    const diff = (300 + this.camera) - this.player.y;
  357.                    this.camera -= diff;
  358.                    this.score += Math.floor(diff / 10);
  359.                    
  360.                    // Spawn new platforms AHEAD
  361.                    this.spawnPlatformsAhead();
  362.                }
  363.                
  364.                // Check death
  365.                if (this.player.y > this.height + this.camera + 100) {
  366.                     this.endGame();
  367.                 }
  368.                
  369.                 // Platform collisions
  370.                 this.platforms.forEach((plat, i) => {
  371.                     if (plat.type === 'moving') {
  372.                         plat.x += plat.dx;
  373.                         if (plat.x <= 0 || plat.x >= this.width - plat.width) plat.dx *= -1;
  374.                     }
  375.                    
  376.                     if (plat.broken) return;
  377.                    
  378.                     if (this.player.vy > 0 &&
  379.                        this.player.x + 15 > plat.x &&
  380.                        this.player.x - 15 < plat.x + plat.width &&
  381.                        this.player.y + 20 > plat.y &&
  382.                        this.player.y + 20 < plat.y + plat.height + 10) {
  383.                        
  384.                        if (plat.type === 'breakable') {
  385.                            plat.broken = true;
  386.                             this.addParticles(plat.x + plat.width/2, plat.y, 'brown');
  387.                         } else {
  388.                             this.player.vy = -12;
  389.                            
  390.                             if (plat.type === 'spring') {
  391.                                 this.player.vy = -20;
  392.                                 this.addParticles(plat.x + plat.springOffset, plat.y, 'spring');
  393.                             }
  394.                            
  395.                             if (Math.random() > 0.97) {
  396.                                 this.player.jetpack = 100;
  397.                             } else if (Math.random() > 0.97) {
  398.                                 this.player.propeller = 80;
  399.                             }
  400.                         }
  401.                     }
  402.                 });
  403.                
  404.                 // Monster collisions
  405.                 this.monsters.forEach((mon, i) => {
  406.                     mon.x += mon.vx;
  407.                     if (mon.x < 0 || mon.x > this.width) mon.vx *= -1;
  408.                    
  409.                     if (Math.abs(this.player.x - mon.x) < 25 &&
  410.                        Math.abs(this.player.y - mon.y) < 30) {
  411.                        
  412.                        if (this.player.vy > 0 && this.player.y < mon.y) {
  413.                            this.monsters.splice(i, 1);
  414.                             this.player.vy = -10;
  415.                             this.score += 50;
  416.                             this.addParticles(mon.x, mon.y, 'green');
  417.                         } else if (this.player.jetpack <= 0 && this.player.propeller <= 0) {
  418.                            this.endGame();
  419.                        }
  420.                    }
  421.                    
  422.                    if (mon.type === 'ufo' &&
  423.                        Math.abs(this.player.x - mon.x) < 50 &&
  424.                        this.player.y > mon.y &&
  425.                        this.player.y < mon.y + 120) {
  426.                        this.player.y += 1.5;
  427.                         this.player.x += (mon.x - this.player.x) * 0.03;
  428.                     }
  429.                 });
  430.                
  431.                 // Projectile collisions
  432.                 this.projectiles.forEach((proj, pi) => {
  433.                     proj.x += proj.vx;
  434.                     proj.y += proj.vy;
  435.                     proj.life--;
  436.                    
  437.                     this.monsters.forEach((mon, mi) => {
  438.                         if (Math.abs(proj.x - mon.x) < 20 &&
  439.                            Math.abs(proj.y - mon.y) < 20) {
  440.                            mon.hp--;
  441.                            if (mon.hp <= 0) {
  442.                                this.monsters.splice(mi, 1);
  443.                                this.score += 50;
  444.                                this.addParticles(mon.x, mon.y, 'green');
  445.                            }
  446.                            this.projectiles.splice(pi, 1);
  447.                        }
  448.                    });
  449.                });
  450.                
  451.                this.projectiles = this.projectiles.filter(p => p.life > 0);
  452.                
  453.                 // Cleanup off-screen
  454.                 this.platforms = this.platforms.filter(p => p.y < this.height + this.camera + 200);
  455.                this.monsters = this.monsters.filter(m => m.y < this.height + this.camera + 200);
  456.                
  457.                // Update particles
  458.                this.particles.forEach(p => {
  459.                     p.x += p.vx;
  460.                     p.y += p.vy;
  461.                     p.life--;
  462.                 });
  463.                 this.particles = this.particles.filter(p => p.life > 0);
  464.                
  465.                 this.updateUI();
  466.             }
  467.            
  468.             spawnPlatformsAhead() {
  469.                 const spawnBuffer = 300;
  470.                 const topOfWorld = this.camera - spawnBuffer;
  471.                
  472.                 // Find highest platform
  473.                 let highestY = Infinity;
  474.                 let highestX = 200;
  475.                 this.platforms.forEach(p => {
  476.                     if (p.y < highestY) {
  477.                        highestY = p.y;
  478.                        highestX = p.x + p.width/2;
  479.                    }
  480.                });
  481.                
  482.                // Spawn until we fill the buffer
  483.                let index = this.platforms.length;
  484.                while (highestY > topOfWorld) {
  485.                     const gap = highestY - topOfWorld;
  486.                     if (gap > 150) {
  487.                         // Emergency spawn
  488.                         const emergencyY = highestY - 110;
  489.                         const emergencyX = Math.max(30, Math.min(310, highestX + (Math.random() - 0.5) * 80));
  490.                        
  491.                         this.platforms.push({
  492.                             x: emergencyX,
  493.                             y: emergencyY,
  494.                             width: 60,
  495.                             height: 15,
  496.                             type: 'normal',
  497.                             broken: false,
  498.                             dx: 0,
  499.                             springOffset: 0
  500.                         });
  501.                        
  502.                         highestY = emergencyY;
  503.                         highestX = emergencyX;
  504.                     } else {
  505.                         // Normal spawn
  506.                         highestY = this.addPlatformAbove(highestY, index);
  507.                         highestX = this.lastPlatformX;
  508.                         index++;
  509.                     }
  510.                 }
  511.             }
  512.            
  513.             addParticles(x, y, type) {
  514.                 const colors = {
  515.                     fire: ['#ff6b35', '#f7931e', '#ffd23f'],
  516.                     brown: ['#8b4513', '#a0522d', '#cd853f'],
  517.                     green: ['#2ecc71', '#27ae60', '#229954'],
  518.                     spring: ['#f1c40f', '#f39c12', '#e67e22']
  519.                 };
  520.                
  521.                 for (let i = 0; i < 8; i++) {
  522.                    this.particles.push({
  523.                        x: x,
  524.                        y: y,
  525.                        vx: (Math.random() - 0.5) * 4,
  526.                        vy: (Math.random() - 0.5) * 4,
  527.                        life: 30,
  528.                        color: colors[type][Math.floor(Math.random() * colors[type].length)],
  529.                        size: Math.random() * 6 + 2
  530.                    });
  531.                }
  532.            }
  533.            
  534.            draw() {
  535.                ctx.clearRect(0, 0, this.width, this.height);
  536.                
  537.                ctx.save();
  538.                ctx.translate(0, -this.camera);
  539.                
  540.                // Draw platforms
  541.                this.platforms.forEach(plat => {
  542.                     if (plat.broken) return;
  543.                    
  544.                     ctx.strokeStyle = '#333';
  545.                     ctx.lineWidth = 2;
  546.                    
  547.                     if (plat.type === 'normal') {
  548.                         ctx.fillStyle = '#90EE90';
  549.                         ctx.fillRect(plat.x, plat.y, plat.width, plat.height);
  550.                         ctx.strokeRect(plat.x, plat.y, plat.width, plat.height);
  551.                         ctx.fillStyle = '#228B22';
  552.                         ctx.fillRect(plat.x, plat.y, plat.width, 4);
  553.                     } else if (plat.type === 'moving') {
  554.                         ctx.fillStyle = '#87CEEB';
  555.                         ctx.fillRect(plat.x, plat.y, plat.width, plat.height);
  556.                         ctx.strokeRect(plat.x, plat.y, plat.width, plat.height);
  557.                         ctx.beginPath();
  558.                         ctx.moveTo(plat.x + 10, plat.y + 7);
  559.                         ctx.lineTo(plat.x + 20, plat.y + 7);
  560.                         ctx.stroke();
  561.                     } else if (plat.type === 'breakable') {
  562.                         ctx.fillStyle = '#D2691E';
  563.                         ctx.fillRect(plat.x, plat.y, plat.width, plat.height);
  564.                         ctx.strokeRect(plat.x, plat.y, plat.width, plat.height);
  565.                         ctx.beginPath();
  566.                         ctx.moveTo(plat.x + 15, plat.y);
  567.                         ctx.lineTo(plat.x + 25, plat.y + 15);
  568.                         ctx.moveTo(plat.x + 35, plat.y);
  569.                         ctx.lineTo(plat.x + 25, plat.y + 15);
  570.                         ctx.stroke();
  571.                     } else if (plat.type === 'spring') {
  572.                         ctx.fillStyle = '#90EE90';
  573.                         ctx.fillRect(plat.x, plat.y, plat.width, plat.height);
  574.                         ctx.strokeRect(plat.x, plat.y, plat.width, plat.height);
  575.                         ctx.fillStyle = '#FFD700';
  576.                         ctx.fillRect(plat.x + plat.springOffset, plat.y - 8, 8, 8);
  577.                         ctx.strokeRect(plat.x + plat.springOffset, plat.y - 8, 8, 8);
  578.                     }
  579.                 });
  580.                
  581.                 // Draw monsters
  582.                 this.monsters.forEach(mon => {
  583.                     ctx.fillStyle = mon.type === 'ufo' ? '#9b59b6' : '#e74c3c';
  584.                     ctx.beginPath();
  585.                     if (mon.type === 'ufo') {
  586.                         ctx.ellipse(mon.x, mon.y, 20, 10, 0, 0, Math.PI * 2);
  587.                         ctx.fill();
  588.                         ctx.stroke();
  589.                         ctx.fillStyle = '#3498db';
  590.                         ctx.beginPath();
  591.                         ctx.arc(mon.x, mon.y - 5, 10, Math.PI, 0);
  592.                         ctx.fill();
  593.                         if (Math.abs(this.player.x - mon.x) < 50 && this.player.y > mon.y) {
  594.                             ctx.fillStyle = 'rgba(155, 89, 182, 0.3)';
  595.                             ctx.beginPath();
  596.                             ctx.moveTo(mon.x - 15, mon.y);
  597.                             ctx.lineTo(mon.x + 15, mon.y);
  598.                             ctx.lineTo(mon.x + 30, mon.y + 120);
  599.                             ctx.lineTo(mon.x - 30, mon.y + 120);
  600.                             ctx.fill();
  601.                         }
  602.                     } else {
  603.                         ctx.arc(mon.x, mon.y, 15, 0, Math.PI * 2);
  604.                         ctx.fill();
  605.                         ctx.stroke();
  606.                         ctx.fillStyle = '#fff';
  607.                         ctx.beginPath();
  608.                         ctx.arc(mon.x - 5, mon.y - 3, 4, 0, Math.PI * 2);
  609.                         ctx.arc(mon.x + 5, mon.y - 3, 4, 0, Math.PI * 2);
  610.                         ctx.fill();
  611.                         ctx.fillStyle = '#000';
  612.                         ctx.beginPath();
  613.                         ctx.arc(mon.x - 5, mon.y - 3, 2, 0, Math.PI * 2);
  614.                         ctx.arc(mon.x + 5, mon.y - 3, 2, 0, Math.PI * 2);
  615.                         ctx.fill();
  616.                     }
  617.                 });
  618.                
  619.                 // Draw projectiles
  620.                 ctx.fillStyle = '#2ecc71';
  621.                 this.projectiles.forEach(proj => {
  622.                     ctx.beginPath();
  623.                     ctx.arc(proj.x, proj.y, 5, 0, Math.PI * 2);
  624.                     ctx.fill();
  625.                     ctx.stroke();
  626.                 });
  627.                
  628.                 // Draw particles
  629.                 this.particles.forEach(p => {
  630.                     ctx.fillStyle = p.color;
  631.                     ctx.globalAlpha = p.life / 30;
  632.                     ctx.fillRect(p.x - p.size/2, p.y - p.size/2, p.size, p.size);
  633.                 });
  634.                 ctx.globalAlpha = 1;
  635.                
  636.                 this.drawPlayer();
  637.                
  638.                 ctx.restore();
  639.             }
  640.            
  641.             drawPlayer() {
  642.                 const p = this.player;
  643.                
  644.                 ctx.save();
  645.                 ctx.translate(p.x, p.y);
  646.                 if (!p.facingRight) ctx.scale(-1, 1);
  647.                
  648.                 if (p.jetpack > 0) {
  649.                     ctx.fillStyle = '#ff6b35';
  650.                     ctx.beginPath();
  651.                     ctx.moveTo(-10, 15);
  652.                     ctx.lineTo(0, 35 + Math.random() * 10);
  653.                     ctx.lineTo(10, 15);
  654.                     ctx.fill();
  655.                 }
  656.                
  657.                 if (p.propeller > 0) {
  658.                     ctx.fillStyle = '#f1c40f';
  659.                     ctx.fillRect(-2, -25, 4, 10);
  660.                     ctx.fillStyle = '#e74c3c';
  661.                     ctx.beginPath();
  662.                     ctx.ellipse(0, -30, 20, 3, 0, 0, Math.PI * 2);
  663.                     ctx.fill();
  664.                     ctx.stroke();
  665.                 }
  666.                
  667.                 ctx.fillStyle = '#2ecc71';
  668.                 ctx.beginPath();
  669.                 ctx.ellipse(0, 0, 15, 20, 0, 0, Math.PI * 2);
  670.                 ctx.fill();
  671.                 ctx.stroke();
  672.                
  673.                 ctx.fillStyle = '#fff';
  674.                 ctx.beginPath();
  675.                 ctx.arc(5, -5, 5, 0, Math.PI * 2);
  676.                 ctx.fill();
  677.                 ctx.fillStyle = '#000';
  678.                 ctx.beginPath();
  679.                 ctx.arc(7, -5, 2, 0, Math.PI * 2);
  680.                 ctx.fill();
  681.                
  682.                 ctx.fillStyle = '#27ae60';
  683.                 ctx.beginPath();
  684.                 ctx.ellipse(12, 0, 4, 3, 0, 0, Math.PI * 2);
  685.                 ctx.fill();
  686.                
  687.                 ctx.strokeStyle = '#333';
  688.                 ctx.lineWidth = 3;
  689.                 ctx.beginPath();
  690.                 ctx.moveTo(-8, 15);
  691.                 ctx.lineTo(-8, 25);
  692.                 ctx.moveTo(8, 15);
  693.                 ctx.lineTo(8, 25);
  694.                 ctx.stroke();
  695.                
  696.                 ctx.restore();
  697.             }
  698.            
  699.             updateUI() {
  700.                 document.getElementById('score').textContent = this.score;
  701.                 document.getElementById('highScore').textContent = 'High: ' + this.highScore;
  702.             }
  703.            
  704.             endGame() {
  705.                 this.gameOver = true;
  706.                 if (this.score > this.highScore) {
  707.                     this.highScore = this.score;
  708.                     localStorage.setItem('sketchHopHighScore', this.highScore);
  709.                 }
  710.                 document.getElementById('finalScore').textContent = 'Score: ' + this.score;
  711.                 document.getElementById('gameOver').style.display = 'block';
  712.             }
  713.            
  714.             restart() {
  715.                 document.getElementById('gameOver').style.display = 'none';
  716.                 this.reset();
  717.             }
  718.            
  719.             loop() {
  720.                 this.update();
  721.                 this.draw();
  722.                 requestAnimationFrame(() => this.loop());
  723.             }
  724.         }
  725.        
  726.         const game = new SketchHop();
  727.     </script>
  728. </body>
  729. </html>
Advertisement
Add Comment
Please, Sign In to add comment