XTaylorSpenceX

BeetleCraft

Sep 23rd, 2025
79
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
HTML 34.73 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">
  6.     <title>BeetleCraft</title>
  7.     <style>
  8.         * {
  9.             margin: 0;
  10.             padding: 0;
  11.             box-sizing: border-box;
  12.         }
  13.  
  14.         body {
  15.             font-family: 'Courier New', monospace;
  16.             background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  17.             display: flex;
  18.             justify-content: center;
  19.             align-items: center;
  20.             min-height: 100vh;
  21.             overflow: hidden;
  22.         }
  23.  
  24.         #gameContainer {
  25.             background: rgba(0, 0, 0, 0.9);
  26.             border-radius: 15px;
  27.             padding: 20px;
  28.             box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
  29.         }
  30.  
  31.         #gameCanvas {
  32.             border: 3px solid #4a5568;
  33.             border-radius: 10px;
  34.             cursor: crosshair;
  35.             image-rendering: pixelated;
  36.             image-rendering: -moz-crisp-edges;
  37.             image-rendering: crisp-edges;
  38.         }
  39.  
  40.         #ui {
  41.             display: flex;
  42.             justify-content: space-between;
  43.             align-items: center;
  44.             margin-top: 15px;
  45.             color: white;
  46.             font-size: 14px;
  47.         }
  48.  
  49.         .ui-section {
  50.             display: flex;
  51.             gap: 20px;
  52.             align-items: center;
  53.         }
  54.  
  55.         .hotbar {
  56.             display: flex;
  57.             gap: 5px;
  58.         }
  59.  
  60.         .hotbar-slot {
  61.             width: 40px;
  62.             height: 40px;
  63.             border: 2px solid #4a5568;
  64.             border-radius: 5px;
  65.             display: flex;
  66.             align-items: center;
  67.             justify-content: center;
  68.             color: white;
  69.             font-size: 10px;
  70.             background: rgba(0, 0, 0, 0.5);
  71.             cursor: pointer;
  72.             transition: all 0.2s;
  73.         }
  74.  
  75.         .hotbar-slot.active {
  76.             border-color: #fbbf24;
  77.             background: rgba(251, 191, 36, 0.2);
  78.             transform: scale(1.1);
  79.         }
  80.  
  81.         .hotbar-slot:hover {
  82.             border-color: #8b9dc3;
  83.         }
  84.  
  85.         #controls {
  86.             position: absolute;
  87.             top: 20px;
  88.             left: 20px;
  89.             color: white;
  90.             background: rgba(0, 0, 0, 0.8);
  91.             padding: 15px;
  92.             border-radius: 10px;
  93.             font-size: 12px;
  94.             line-height: 1.6;
  95.         }
  96.  
  97.         #startScreen {
  98.             position: absolute;
  99.             top: 0;
  100.             left: 0;
  101.             right: 0;
  102.             bottom: 0;
  103.             background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  104.             display: flex;
  105.             flex-direction: column;
  106.             justify-content: center;
  107.             align-items: center;
  108.             color: white;
  109.             z-index: 100;
  110.         }
  111.  
  112.         #startScreen h1 {
  113.             font-size: 48px;
  114.             margin-bottom: 10px;
  115.             text-shadow: 3px 3px 6px rgba(0, 0, 0, 0.5);
  116.             animation: pulse 2s infinite;
  117.         }
  118.  
  119.         #startScreen p {
  120.             font-size: 18px;
  121.             margin-bottom: 30px;
  122.             opacity: 0.9;
  123.         }
  124.  
  125.         #startButton {
  126.             padding: 15px 40px;
  127.             font-size: 20px;
  128.             background: rgba(255, 255, 255, 0.2);
  129.             border: 2px solid white;
  130.             border-radius: 10px;
  131.             color: white;
  132.             cursor: pointer;
  133.             transition: all 0.3s;
  134.         }
  135.  
  136.         #startButton:hover {
  137.             background: rgba(255, 255, 255, 0.3);
  138.             transform: scale(1.05);
  139.         }
  140.  
  141.         @keyframes pulse {
  142.             0%, 100% { transform: scale(1); }
  143.             50% { transform: scale(1.05); }
  144.         }
  145.  
  146.         .particle {
  147.             position: absolute;
  148.             pointer-events: none;
  149.             animation: particleFall 1s ease-out forwards;
  150.         }
  151.  
  152.         @keyframes particleFall {
  153.             0% {
  154.                 opacity: 1;
  155.                 transform: translateY(0) rotate(0deg);
  156.             }
  157.             100% {
  158.                 opacity: 0;
  159.                 transform: translateY(30px) rotate(180deg);
  160.             }
  161.         }
  162.     </style>
  163. </head>
  164. <body>
  165.     <div id="startScreen">
  166.         <h1>🪲 BeetleCraft 🪲</h1>
  167.         <p>A tiny beetle's big adventure!</p>
  168.         <button id="startButton">Start Adventure</button>
  169.     </div>
  170.  
  171.     <div id="controls">
  172.         <strong>Controls:</strong><br>
  173.         WASD/Arrows - Move<br>
  174.         Space - Jump<br>
  175.         Left Click - Break block<br>
  176.         Right Click - Place block<br>
  177.         1-9 - Select block<br>
  178.         R - Reset world
  179.     </div>
  180.  
  181.     <div id="gameContainer" style="display: none;">
  182.         <canvas id="gameCanvas"></canvas>
  183.         <div id="ui">
  184.             <div class="ui-section">
  185.                 <div>Health: <span id="health">♥♥♥♥♥</span></div>
  186.                 <div>Score: <span id="score">0</span></div>
  187.             </div>
  188.             <div class="hotbar" id="hotbar">
  189.                 <div class="hotbar-slot active" data-slot="0">Dirt</div>
  190.                 <div class="hotbar-slot" data-slot="1">Stone</div>
  191.                 <div class="hotbar-slot" data-slot="2">Wood</div>
  192.                 <div class="hotbar-slot" data-slot="3">Leaf</div>
  193.                 <div class="hotbar-slot" data-slot="4">Sand</div>
  194.                 <div class="hotbar-slot" data-slot="5">Glass</div>
  195.                 <div class="hotbar-slot" data-slot="6">Brick</div>
  196.                 <div class="hotbar-slot" data-slot="7">Coal</div>
  197.                 <div class="hotbar-slot" data-slot="8">Gold</div>
  198.             </div>
  199.         </div>
  200.     </div>
  201.  
  202.     <script>
  203.         // Game configuration
  204.         const BLOCK_SIZE = 20;
  205.         const WORLD_WIDTH = 50;
  206.         const WORLD_HEIGHT = 30;
  207.         const GRAVITY = 0.5;
  208.         const JUMP_POWER = 10;
  209.         const MOVE_SPEED = 3;
  210.  
  211.         // Block types
  212.         const BlockType = {
  213.             AIR: 0,
  214.             DIRT: 1,
  215.             STONE: 2,
  216.             WOOD: 3,
  217.             LEAF: 4,
  218.             SAND: 5,
  219.             GLASS: 6,
  220.             BRICK: 7,
  221.             COAL: 8,
  222.             GOLD: 9,
  223.             GRASS: 10,
  224.             BEDROCK: 11
  225.         };
  226.  
  227.         // Block colors
  228.         const blockColors = {
  229.             [BlockType.AIR]: null,
  230.             [BlockType.DIRT]: '#8B4513',
  231.             [BlockType.STONE]: '#808080',
  232.             [BlockType.WOOD]: '#8B5A00',
  233.             [BlockType.LEAF]: '#228B22',
  234.             [BlockType.SAND]: '#F4E4C1',
  235.             [BlockType.GLASS]: 'rgba(173, 216, 230, 0.3)',
  236.             [BlockType.BRICK]: '#B22222',
  237.             [BlockType.COAL]: '#2F4F4F',
  238.             [BlockType.GOLD]: '#FFD700',
  239.             [BlockType.GRASS]: '#3CB371',
  240.             [BlockType.BEDROCK]: '#1C1C1C'
  241.         };
  242.  
  243.         class Beetle {
  244.             constructor(x, y) {
  245.                 this.x = x;
  246.                 this.y = y;
  247.                 this.vx = 0;
  248.                 this.vy = 0;
  249.                 this.width = BLOCK_SIZE;
  250.                 this.height = BLOCK_SIZE;
  251.                 this.onGround = false;
  252.                 this.facing = 1; // 1 for right, -1 for left
  253.                 this.animFrame = 0;
  254.                 this.health = 5;
  255.                 // Anti-jitter smooth position tracking
  256.                 this.displayX = x;
  257.                 this.displayY = y;
  258.                 // Previous frame positions for smoothing
  259.                 this.prevX = x;
  260.                 this.prevY = y;
  261.                 // Collision box inset
  262.                 this.collisionInset = 3;
  263.             }
  264.  
  265.             update(world, keys) {
  266.                 // Store previous position
  267.                 this.prevX = this.x;
  268.                 this.prevY = this.y;
  269.  
  270.                 // Horizontal movement with smooth acceleration
  271.                 const accel = 0.4;
  272.                 if (keys['a'] || keys['ArrowLeft']) {
  273.                     this.vx = Math.max(this.vx - accel, -MOVE_SPEED);
  274.                     this.facing = -1;
  275.                 } else if (keys['d'] || keys['ArrowRight']) {
  276.                     this.vx = Math.min(this.vx + accel, MOVE_SPEED);
  277.                     this.facing = 1;
  278.                 } else {
  279.                     // Apply friction
  280.                     if (Math.abs(this.vx) > 0.1) {
  281.                         this.vx *= 0.88;
  282.                     } else {
  283.                         this.vx = 0;
  284.                     }
  285.                 }
  286.  
  287.                 // CRITICAL FIX: Always check if there's ground beneath us
  288.                 // This ensures gravity works when walking off ledges
  289.                 const groundBelowY = this.y + BLOCK_SIZE + 1;
  290.                 const hasGroundBelow = !this.canMoveTo(this.x, groundBelowY, world);
  291.                
  292.                 // Update ground state
  293.                 if (!hasGroundBelow && this.onGround) {
  294.                    // We just walked off a ledge!
  295.                    this.onGround = false;
  296.                 }
  297.  
  298.                 // Jumping with ground check
  299.                 if ((keys['w'] || keys['ArrowUp'] || keys[' ']) && this.onGround) {
  300.                    this.vy = -JUMP_POWER;
  301.                     this.onGround = false;
  302.                 }
  303.  
  304.                 // Apply gravity when not on ground
  305.                 if (!this.onGround) {
  306.                     this.vy = Math.min(this.vy + GRAVITY, 12);
  307.                 } else {
  308.                     // Even when on ground, check if we should fall
  309.                     if (!hasGroundBelow) {
  310.                         this.vy = 0.1; // Small initial fall velocity
  311.                         this.onGround = false;
  312.                     }
  313.                 }
  314.  
  315.                 // Update position with advanced collision detection
  316.                 this.moveWithSmoothCollision(world);
  317.  
  318.                 // Ultra-smooth display position (Enhanced Anti-JitterBugâ„¢)
  319.                 const smoothFactor = 0.25;
  320.                 this.displayX += (this.x - this.displayX) * smoothFactor;
  321.                 this.displayY += (this.y - this.displayY) * smoothFactor;
  322.                
  323.                 // Snap display to actual if very close (prevents micro-jitter)
  324.                 if (Math.abs(this.displayX - this.x) < 0.1) this.displayX = this.x;
  325.                if (Math.abs(this.displayY - this.y) < 0.1) this.displayY = this.y;
  326.  
  327.                // Smooth animation
  328.                if (Math.abs(this.vx) > 0.5) {
  329.                     this.animFrame = (this.animFrame + Math.abs(this.vx) * 0.05) % 4;
  330.                 }
  331.             }
  332.  
  333.             moveWithSmoothCollision(world) {
  334.                 const steps = 4; // Subdivide movement into smaller steps
  335.                 const dx = this.vx / steps;
  336.                 const dy = this.vy / steps;
  337.                
  338.                 let movedX = false;
  339.                 let movedY = false;
  340.  
  341.                 for (let step = 0; step < steps; step++) {
  342.                    // Try horizontal movement
  343.                    if (!movedX && Math.abs(dx) > 0.01) {
  344.                         const testX = this.x + dx;
  345.                         if (this.canMoveTo(testX, this.y, world)) {
  346.                             this.x = testX;
  347.                         } else {
  348.                             // Find the exact position we can move to
  349.                             const direction = Math.sign(dx);
  350.                             let bestX = this.x;
  351.                             for (let i = 0; i < Math.abs(dx); i += 0.5) {
  352.                                const checkX = this.x + (i * direction);
  353.                                if (this.canMoveTo(checkX, this.y, world)) {
  354.                                    bestX = checkX;
  355.                                } else {
  356.                                    break;
  357.                                }
  358.                            }
  359.                            this.x = bestX;
  360.                            this.vx = 0;
  361.                            movedX = true;
  362.                        }
  363.                    }
  364.  
  365.                    // Try vertical movement
  366.                    if (!movedY && Math.abs(dy) > 0.01) {
  367.                         const testY = this.y + dy;
  368.                         if (this.canMoveTo(this.x, testY, world)) {
  369.                             this.y = testY;
  370.                             this.onGround = false;
  371.                         } else {
  372.                             // Find the exact position we can move to
  373.                             const direction = Math.sign(dy);
  374.                             let bestY = this.y;
  375.                             for (let i = 0; i < Math.abs(dy); i += 0.5) {
  376.                                const checkY = this.y + (i * direction);
  377.                                if (this.canMoveTo(this.x, checkY, world)) {
  378.                                    bestY = checkY;
  379.                                } else {
  380.                                    break;
  381.                                }
  382.                            }
  383.                            this.y = bestY;
  384.                            
  385.                            if (direction > 0) {
  386.                                 // Hit ground - align to grid
  387.                                 this.onGround = true;
  388.                                 const gridY = Math.floor((this.y + BLOCK_SIZE) / BLOCK_SIZE);
  389.                                 this.y = gridY * BLOCK_SIZE - BLOCK_SIZE;
  390.                             }
  391.                             this.vy = 0;
  392.                             movedY = true;
  393.                         }
  394.                     }
  395.                 }
  396.  
  397.                 // Additional ground check for stability
  398.                 if (!this.onGround) {
  399.                     const groundCheckY = this.y + 1;
  400.                     if (!this.canMoveTo(this.x, groundCheckY, world)) {
  401.                         this.onGround = true;
  402.                     }
  403.                 }
  404.  
  405.                 // Smooth boundary clamping
  406.                 const padding = 2;
  407.                 this.x = Math.max(padding, Math.min(this.x, (WORLD_WIDTH - 1) * BLOCK_SIZE - padding));
  408.                 this.y = Math.max(padding, Math.min(this.y, (WORLD_HEIGHT - 1) * BLOCK_SIZE - padding));
  409.             }
  410.  
  411.             canMoveTo(x, y, world) {
  412.                 // Check collision with smaller inset box for smoother movement
  413.                 const inset = this.collisionInset;
  414.                 const points = [
  415.                     {x: x + inset, y: y + inset},
  416.                     {x: x + BLOCK_SIZE - inset, y: y + inset},
  417.                     {x: x + inset, y: y + BLOCK_SIZE - inset},
  418.                     {x: x + BLOCK_SIZE - inset, y: y + BLOCK_SIZE - inset},
  419.                     // Center points for better detection
  420.                     {x: x + BLOCK_SIZE/2, y: y + inset},
  421.                     {x: x + BLOCK_SIZE/2, y: y + BLOCK_SIZE - inset}
  422.                 ];
  423.                
  424.                 for (let point of points) {
  425.                     const gridX = Math.floor(point.x / BLOCK_SIZE);
  426.                     const gridY = Math.floor(point.y / BLOCK_SIZE);
  427.                    
  428.                     if (gridX < 0 || gridX >= WORLD_WIDTH ||
  429.                         gridY < 0 || gridY >= WORLD_HEIGHT) {
  430.                         return false;
  431.                     }
  432.                    
  433.                     if (world[gridY] && world[gridY][gridX] !== BlockType.AIR) {
  434.                        return false;
  435.                     }
  436.                 }
  437.                
  438.                 return true;
  439.             }
  440.  
  441.             draw(ctx, camera) {
  442.                 ctx.save();
  443.                 // Use smooth display position instead of raw position
  444.                 ctx.translate(this.displayX - camera.x + BLOCK_SIZE/2, this.displayY - camera.y + BLOCK_SIZE/2);
  445.                
  446.                 // Smooth rotation based on velocity
  447.                 const tilt = this.vx * 0.02;
  448.                 ctx.rotate(tilt);
  449.                
  450.                 // Flip beetle based on direction
  451.                 ctx.scale(this.facing, 1);
  452.                
  453.                 // Draw beetle body
  454.                 ctx.fillStyle = '#1a1a1a';
  455.                 ctx.beginPath();
  456.                 ctx.ellipse(0, 0, BLOCK_SIZE/2.5, BLOCK_SIZE/3, 0, 0, Math.PI * 2);
  457.                 ctx.fill();
  458.                
  459.                 // Draw beetle shell with gradient (Anti-JitterBugâ„¢ Neptune Variety)
  460.                 const gradient = ctx.createRadialGradient(0, 0, 0, 0, 0, BLOCK_SIZE/2);
  461.                 gradient.addColorStop(0, '#6a4c93');
  462.                 gradient.addColorStop(0.5, '#7b68a6');
  463.                 gradient.addColorStop(0.8, '#5e3c8f');
  464.                 gradient.addColorStop(1, '#432874');
  465.                 ctx.fillStyle = gradient;
  466.                 ctx.beginPath();
  467.                 ctx.ellipse(0, -2, BLOCK_SIZE/2.8, BLOCK_SIZE/3.5, 0, 0, Math.PI * 2);
  468.                 ctx.fill();
  469.                
  470.                 // Neptune shimmer effect
  471.                 ctx.fillStyle = 'rgba(147, 191, 207, 0.3)';
  472.                 ctx.beginPath();
  473.                 ctx.ellipse(-3, -4, BLOCK_SIZE/6, BLOCK_SIZE/8, -0.3, 0, Math.PI * 2);
  474.                 ctx.fill();
  475.                
  476.                 // Draw legs (smoothly animated)
  477.                 ctx.strokeStyle = '#000';
  478.                 ctx.lineWidth = 1;
  479.                 const legOffset = Math.sin(this.animFrame * 1.5) * 1.5;
  480.                 for (let i = -1; i <= 1; i++) {
  481.                    ctx.beginPath();
  482.                    ctx.moveTo(i * 4, 3);
  483.                    ctx.lineTo(i * 6, 7 + (i === 0 ? -legOffset : legOffset));
  484.                    ctx.stroke();
  485.                }
  486.                
  487.                // Draw antennae (more stable)
  488.                const antennaWave = Math.sin(Date.now() * 0.002) * 0.5;
  489.                ctx.beginPath();
  490.                ctx.moveTo(5, -5);
  491.                ctx.quadraticCurveTo(8, -8 + antennaWave, 7, -10);
  492.                ctx.moveTo(5, -5);
  493.                ctx.quadraticCurveTo(3, -8 - antennaWave, 2, -10);
  494.                ctx.stroke();
  495.                
  496.                // Draw eyes
  497.                ctx.fillStyle = '#fff';
  498.                ctx.beginPath();
  499.                ctx.arc(4, -2, 2, 0, Math.PI * 2);
  500.                ctx.arc(4, 2, 2, 0, Math.PI * 2);
  501.                ctx.fill();
  502.                ctx.fillStyle = '#000';
  503.                ctx.beginPath();
  504.                ctx.arc(4.5, -2, 1, 0, Math.PI * 2);
  505.                ctx.arc(4.5, 2, 1, 0, Math.PI * 2);
  506.                ctx.fill();
  507.                
  508.                ctx.restore();
  509.            }
  510.        }
  511.  
  512.        class Game {
  513.            constructor() {
  514.                this.canvas = document.getElementById('gameCanvas');
  515.                this.ctx = this.canvas.getContext('2d');
  516.                this.canvas.width = 800;
  517.                this.canvas.height = 600;
  518.                
  519.                this.world = [];
  520.                this.beetle = null;
  521.                this.camera = { x: 0, y: 0 };
  522.                this.keys = {};
  523.                this.selectedBlock = BlockType.DIRT;
  524.                this.score = 0;
  525.                this.particles = [];
  526.                this.sandUpdateTimer = 0;
  527.                this.sandUpdateInterval = 5; // Update sand every 5 frames for performance
  528.                
  529.                this.init();
  530.            }
  531.  
  532.            init() {
  533.                this.generateWorld();
  534.                this.beetle = new Beetle(WORLD_WIDTH * BLOCK_SIZE / 2, 100);
  535.                this.setupEventListeners();
  536.                this.gameLoop();
  537.            }
  538.  
  539.            generateWorld() {
  540.                // Initialize empty world
  541.                for (let y = 0; y < WORLD_HEIGHT; y++) {
  542.                    this.world[y] = [];
  543.                    for (let x = 0; x < WORLD_WIDTH; x++) {
  544.                        this.world[y][x] = BlockType.AIR;
  545.                    }
  546.                }
  547.  
  548.                // Generate terrain
  549.                const surfaceHeight = 15;
  550.                const noise = this.generateNoise();
  551.                
  552.                for (let x = 0; x < WORLD_WIDTH; x++) {
  553.                    const height = surfaceHeight + Math.floor(noise[x] * 5);
  554.                    
  555.                    // Place blocks
  556.                    for (let y = height; y < WORLD_HEIGHT; y++) {
  557.                        if (y === height) {
  558.                            this.world[y][x] = BlockType.GRASS;
  559.                        } else if (y < height + 3) {
  560.                            // Add some sand patches for testing gravity
  561.                            if (x > 10 && x < 20 && Math.random() < 0.7) {
  562.                                this.world[y][x] = BlockType.SAND;
  563.                             } else {
  564.                                 this.world[y][x] = BlockType.DIRT;
  565.                             }
  566.                         } else if (y < height + 7) {
  567.                            this.world[y][x] = BlockType.STONE;
  568.                        } else if (y === WORLD_HEIGHT - 1) {
  569.                            this.world[y][x] = BlockType.BEDROCK;
  570.                        } else {
  571.                            // Ore generation
  572.                            const rand = Math.random();
  573.                            if (rand < 0.02) {
  574.                                this.world[y][x] = BlockType.COAL;
  575.                            } else if (rand < 0.025) {
  576.                                this.world[y][x] = BlockType.GOLD;
  577.                            } else {
  578.                                this.world[y][x] = BlockType.STONE;
  579.                            }
  580.                        }
  581.                    }
  582.                    
  583.                    // Generate trees
  584.                    if (x > 3 && x < WORLD_WIDTH - 3 && Math.random() < 0.1) {
  585.                        this.generateTree(x, height - 1);
  586.                     }
  587.                 }
  588.                
  589.                 // Add a sand pit for testing gravity
  590.                 for (let x = 25; x < 35; x++) {
  591.                    for (let y = 10; y < 15; y++) {
  592.                        this.world[y][x] = BlockType.SAND;
  593.                    }
  594.                }
  595.            }
  596.  
  597.            generateNoise() {
  598.                const noise = [];
  599.                let seed = Math.random() * 100;
  600.                for (let x = 0; x < WORLD_WIDTH; x++) {
  601.                    noise[x] = Math.sin(seed + x * 0.1) * Math.cos(seed * 2 + x * 0.05);
  602.                }
  603.                return noise;
  604.            }
  605.  
  606.            generateTree(x, y) {
  607.                // Trunk
  608.                for (let i = 0; i < 4; i++) {
  609.                    if (y - i >= 0) {
  610.                         this.world[y - i][x] = BlockType.WOOD;
  611.                     }
  612.                 }
  613.                
  614.                 // Leaves
  615.                 for (let ly = -5; ly <= -3; ly++) {
  616.                    for (let lx = -2; lx <= 2; lx++) {
  617.                        if (y + ly >= 0 && x + lx >= 0 && x + lx < WORLD_WIDTH) {
  618.                            if (Math.abs(lx) + Math.abs(ly + 4) <= 2) {
  619.                                this.world[y + ly][x + lx] = BlockType.LEAF;
  620.                             }
  621.                         }
  622.                     }
  623.                 }
  624.             }
  625.  
  626.             setupEventListeners() {
  627.                 // Keyboard controls
  628.                 window.addEventListener('keydown', (e) => {
  629.                     this.keys[e.key] = true;
  630.                    
  631.                     // Block selection
  632.                     if (e.key >= '1' && e.key <= '9') {
  633.                        const slot = parseInt(e.key) - 1;
  634.                         this.selectBlock(slot);
  635.                     }
  636.                    
  637.                     // Reset world
  638.                     if (e.key === 'r' || e.key === 'R') {
  639.                         this.generateWorld();
  640.                         this.beetle = new Beetle(WORLD_WIDTH * BLOCK_SIZE / 2, 100);
  641.                         this.score = 0;
  642.                     }
  643.                 });
  644.  
  645.                 window.addEventListener('keyup', (e) => {
  646.                     this.keys[e.key] = false;
  647.                 });
  648.  
  649.                 // Mouse controls
  650.                 this.canvas.addEventListener('mousedown', (e) => {
  651.                     const rect = this.canvas.getBoundingClientRect();
  652.                     const mouseX = e.clientX - rect.left + this.camera.x;
  653.                     const mouseY = e.clientY - rect.top + this.camera.y;
  654.                     const gridX = Math.floor(mouseX / BLOCK_SIZE);
  655.                     const gridY = Math.floor(mouseY / BLOCK_SIZE);
  656.  
  657.                     if (gridX >= 0 && gridX < WORLD_WIDTH && gridY >= 0 && gridY < WORLD_HEIGHT) {
  658.                        if (e.button === 0) { // Left click - break
  659.                            if (this.world[gridY][gridX] !== BlockType.AIR &&
  660.                                this.world[gridY][gridX] !== BlockType.BEDROCK) {
  661.                                this.createParticles(gridX * BLOCK_SIZE, gridY * BLOCK_SIZE, this.world[gridY][gridX]);
  662.                                 this.world[gridY][gridX] = BlockType.AIR;
  663.                                 this.score += 10;
  664.                             }
  665.                         } else if (e.button === 2) { // Right click - place
  666.                             if (this.world[gridY][gridX] === BlockType.AIR) {
  667.                                 this.world[gridY][gridX] = this.selectedBlock;
  668.                             }
  669.                         }
  670.                     }
  671.                 });
  672.  
  673.                 this.canvas.addEventListener('contextmenu', (e) => {
  674.                     e.preventDefault();
  675.                 });
  676.  
  677.                 // Hotbar clicks
  678.                 document.querySelectorAll('.hotbar-slot').forEach(slot => {
  679.                     slot.addEventListener('click', () => {
  680.                         this.selectBlock(parseInt(slot.dataset.slot));
  681.                     });
  682.                 });
  683.             }
  684.  
  685.             selectBlock(slot) {
  686.                 document.querySelectorAll('.hotbar-slot').forEach(s => {
  687.                     s.classList.remove('active');
  688.                 });
  689.                 document.querySelectorAll('.hotbar-slot')[slot].classList.add('active');
  690.                 this.selectedBlock = slot + 1; // +1 because AIR is 0
  691.             }
  692.  
  693.             createParticles(x, y, blockType) {
  694.                 for (let i = 0; i < 5; i++) {
  695.                    this.particles.push({
  696.                        x: x + Math.random() * BLOCK_SIZE,
  697.                        y: y + Math.random() * BLOCK_SIZE,
  698.                        vx: (Math.random() - 0.5) * 5,
  699.                        vy: -Math.random() * 5,
  700.                        color: blockColors[blockType],
  701.                        life: 30
  702.                    });
  703.                }
  704.            }
  705.  
  706.            updateParticles() {
  707.                this.particles = this.particles.filter(p => {
  708.                     p.x += p.vx;
  709.                     p.y += p.vy;
  710.                     p.vy += 0.3;
  711.                     p.life--;
  712.                     return p.life > 0;
  713.                 });
  714.             }
  715.  
  716.             updateSand() {
  717.                 // Update sand less frequently for performance
  718.                 this.sandUpdateTimer++;
  719.                 if (this.sandUpdateTimer < this.sandUpdateInterval) return;
  720.                this.sandUpdateTimer = 0;
  721.                
  722.                // Process from bottom to top to prevent multiple sand blocks from falling through each other
  723.                for (let y = WORLD_HEIGHT - 2; y >= 0; y--) {
  724.                     for (let x = 0; x < WORLD_WIDTH; x++) {
  725.                        if (this.world[y][x] === BlockType.SAND) {
  726.                            // Check if the block below is air
  727.                            if (y + 1 < WORLD_HEIGHT && this.world[y + 1][x] === BlockType.AIR) {
  728.                                // Move sand down
  729.                                this.world[y + 1][x] = BlockType.SAND;
  730.                                this.world[y][x] = BlockType.AIR;
  731.                            }
  732.                            // Check if sand can fall diagonally
  733.                            else if (y + 1 < WORLD_HEIGHT) {
  734.                                // Try falling left
  735.                                if (x > 0 && this.world[y + 1][x - 1] === BlockType.AIR) {
  736.                                    this.world[y + 1][x - 1] = BlockType.SAND;
  737.                                     this.world[y][x] = BlockType.AIR;
  738.                                 }
  739.                                 // Try falling right
  740.                                 else if (x < WORLD_WIDTH - 1 && this.world[y + 1][x + 1] === BlockType.AIR) {
  741.                                    this.world[y + 1][x + 1] = BlockType.SAND;
  742.                                    this.world[y][x] = BlockType.AIR;
  743.                                }
  744.                            }
  745.                        }
  746.                    }
  747.                }
  748.            }
  749.  
  750.            updateCamera() {
  751.                // Ultra-smooth camera follow with deadzone
  752.                const targetX = this.beetle.x - this.canvas.width / 2;
  753.                const targetY = this.beetle.y - this.canvas.height / 2;
  754.                
  755.                // Slower camera lerp for smoother following
  756.                const cameraSpeed = 0.08;
  757.                this.camera.x += (targetX - this.camera.x) * cameraSpeed;
  758.                this.camera.y += (targetY - this.camera.y) * cameraSpeed;
  759.                
  760.                // Round camera position to prevent sub-pixel rendering issues
  761.                this.camera.x = Math.round(this.camera.x * 2) / 2;
  762.                this.camera.y = Math.round(this.camera.y * 2) / 2;
  763.                
  764.                // Camera bounds with smooth clamping
  765.                this.camera.x = Math.max(0, Math.min(this.camera.x, WORLD_WIDTH * BLOCK_SIZE - this.canvas.width));
  766.                this.camera.y = Math.max(0, Math.min(this.camera.y, WORLD_HEIGHT * BLOCK_SIZE - this.canvas.height));
  767.            }
  768.  
  769.            draw() {
  770.                // Clear canvas
  771.                this.ctx.fillStyle = '#87CEEB';
  772.                this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
  773.  
  774.                // Draw clouds
  775.                this.drawClouds();
  776.  
  777.                // Draw world
  778.                const startX = Math.floor(this.camera.x / BLOCK_SIZE);
  779.                const endX = Math.ceil((this.camera.x + this.canvas.width) / BLOCK_SIZE);
  780.                const startY = Math.floor(this.camera.y / BLOCK_SIZE);
  781.                const endY = Math.ceil((this.camera.y + this.canvas.height) / BLOCK_SIZE);
  782.  
  783.                for (let y = startY; y <= endY && y < WORLD_HEIGHT; y++) {
  784.                    for (let x = startX; x <= endX && x < WORLD_WIDTH; x++) {
  785.                        if (this.world[y] && this.world[y][x] !== BlockType.AIR) {
  786.                            const blockType = this.world[y][x];
  787.                            const drawX = x * BLOCK_SIZE - this.camera.x;
  788.                            const drawY = y * BLOCK_SIZE - this.camera.y;
  789.                            
  790.                            // Draw block
  791.                            this.ctx.fillStyle = blockColors[blockType];
  792.                            this.ctx.fillRect(drawX, drawY, BLOCK_SIZE, BLOCK_SIZE);
  793.                            
  794.                            // Draw block borders
  795.                            this.ctx.strokeStyle = 'rgba(0, 0, 0, 0.2)';
  796.                            this.ctx.strokeRect(drawX, drawY, BLOCK_SIZE, BLOCK_SIZE);
  797.                            
  798.                            // Add texture details
  799.                            if (blockType === BlockType.GRASS) {
  800.                                this.ctx.fillStyle = '#2E7D32';
  801.                                this.ctx.fillRect(drawX, drawY, BLOCK_SIZE, 4);
  802.                            } else if (blockType === BlockType.WOOD) {
  803.                                this.ctx.strokeStyle = 'rgba(0, 0, 0, 0.3)';
  804.                                this.ctx.beginPath();
  805.                                this.ctx.moveTo(drawX + 5, drawY);
  806.                                this.ctx.lineTo(drawX + 5, drawY + BLOCK_SIZE);
  807.                                this.ctx.moveTo(drawX + 15, drawY);
  808.                                this.ctx.lineTo(drawX + 15, drawY + BLOCK_SIZE);
  809.                                this.ctx.stroke();
  810.                            } else if (blockType === BlockType.SAND) {
  811.                                // Add sand texture
  812.                                this.ctx.fillStyle = 'rgba(255, 255, 255, 0.2)';
  813.                                for (let i = 0; i < 3; i++) {
  814.                                    const dotX = drawX + Math.random() * BLOCK_SIZE;
  815.                                    const dotY = drawY + Math.random() * BLOCK_SIZE;
  816.                                    this.ctx.fillRect(dotX, dotY, 1, 1);
  817.                                }
  818.                            }
  819.                        }
  820.                    }
  821.                }
  822.  
  823.                // Draw particles
  824.                this.particles.forEach(p => {
  825.                     this.ctx.fillStyle = p.color;
  826.                     this.ctx.globalAlpha = p.life / 30;
  827.                     this.ctx.fillRect(p.x - this.camera.x, p.y - this.camera.y, 4, 4);
  828.                 });
  829.                 this.ctx.globalAlpha = 1;
  830.  
  831.                 // Draw beetle
  832.                 this.beetle.draw(this.ctx, this.camera);
  833.  
  834.                 // Update UI
  835.                 document.getElementById('score').textContent = this.score;
  836.                 document.getElementById('health').textContent = '♥'.repeat(this.beetle.health);
  837.             }
  838.  
  839.             drawClouds() {
  840.                 this.ctx.fillStyle = 'rgba(255, 255, 255, 0.7)';
  841.                 const time = Date.now() * 0.00005;
  842.                
  843.                 for (let i = 0; i < 3; i++) {
  844.                    const x = ((time * 20 * (i + 1)) % (this.canvas.width + 100)) - 100;
  845.                    const y = 50 + i * 40;
  846.                    
  847.                    this.ctx.beginPath();
  848.                    this.ctx.arc(x, y, 25, 0, Math.PI * 2);
  849.                    this.ctx.arc(x + 25, y, 30, 0, Math.PI * 2);
  850.                    this.ctx.arc(x + 50, y, 25, 0, Math.PI * 2);
  851.                    this.ctx.fill();
  852.                }
  853.            }
  854.  
  855.            gameLoop() {
  856.                // Update
  857.                this.beetle.update(this.world, this.keys);
  858.                this.updateSand(); // Update sand physics
  859.                this.updateCamera();
  860.                this.updateParticles();
  861.  
  862.                // Draw
  863.                this.draw();
  864.  
  865.                // Continue loop
  866.                requestAnimationFrame(() => this.gameLoop());
  867.             }
  868.         }
  869.  
  870.         // Start game
  871.         document.getElementById('startButton').addEventListener('click', () => {
  872.             document.getElementById('startScreen').style.display = 'none';
  873.             document.getElementById('gameContainer').style.display = 'block';
  874.             const game = new Game();
  875.         });
  876.     </script>
  877. </body>
  878. </html>
  879.  
Advertisement
Add Comment
Please, Sign In to add comment