forms

Fruit Slice Ninja: Ultimate Edition (V2)

Fruit Slice Ninja: Ultimate Edition (V2) is a polished, high-performance arcade experience that takes the core slicing loop to a professional level. This version focuses on visceral feedback, Newtonian physics, and a clean, modern aesthetic.

What's New in Version 2:
Newtonian Slicing Physics: Targets no longer just disappear; they split into two distinct halves at the exact point of impact. Each half inherits realistic momentum and rotational velocity, causing them to tumble and fall according to the direction of your slice.

Dynamic Juice Splatter: Every successful hit paints a "splash" onto the background arena. These splatters are semi-persistent, gradually fading over time to give the battlefield a messy, reactive feel as the intensity increases.

Enhanced Combat Feedback: * Impact Flashes: A high-intensity radial light flash triggers at the point of contact to provide instant visual satisfaction.

High-Velocity Particles: Exploding fruit debris now features gravity-affected physics and air friction for a more chaotic "burst" effect.

Ultimate Edition Visuals: * The UI has been overhauled with a premium dark-themed aesthetic, utilizing golden accents and bold typography.

The "Neon Blade" trail now features enhanced shadow blurring and a smoother decay, making every movement feel like a high-speed katana strike.

Refined Difficulty Engine: The spawn logic has been retuned. The "Difficulty Multiplier" now scales faster, progressively increasing the number of objects launched simultaneously and tightening the windows between waves to push your reflexes to the limit.

How to Play:

Slice: Click/drag or swipe to destroy the flying fruits.

Avoid: Do not touch the black bombs, or it's game over!

Perfect Run: Don't let fruits fall unsliced, or you'll lose one of your three lives.

Code Snippet

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
    <title>Fruit Slice Ninja - Ultimate Edition</title>
    <script src="https://cdn.tailwindcss.com"></script>
    <style>
        body {
            margin: 0;
            overflow: hidden;
            background: #0f172a;
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            touch-action: none;
        }
        canvas {
            display: block;
            cursor: crosshair;
        }
        #ui-layer {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            pointer-events: none;
            color: white;
            padding: 20px;
            user-select: none;
        }
        .stats {
            font-size: 28px;
            font-weight: 900;
            letter-spacing: 1px;
            text-shadow: 0 0 10px rgba(0,0,0,0.8);
            color: #fbbf24;
        }
        #game-over {
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: rgba(15, 23, 42, 0.95);
            padding: 50px;
            border-radius: 30px;
            text-align: center;
            display: none;
            pointer-events: auto;
            border: 4px solid #f59e0b;
            box-shadow: 0 0 40px rgba(245, 158, 11, 0.4);
        }
        .btn {
            background: linear-gradient(to bottom, #f59e0b, #d97706);
            color: #fff;
            padding: 15px 40px;
            border-radius: 50px;
            font-size: 20px;
            font-weight: bold;
            cursor: pointer;
            margin-top: 25px;
            display: inline-block;
            transition: all 0.2s;
            pointer-events: auto;
            border: none;
            box-shadow: 0 4px 0 #92400e;
        }
        .btn:active {
            transform: translateY(4px);
            box-shadow: 0 0 0 #92400e;
        }
    </style>
</head>
<body>

    <div id="ui-layer">
        <div class="flex justify-between items-start">
            <div class="stats">SCORE: <span id="score-val">0</span></div>
            <div class="stats">LIVES: <span id="lives-val">❤❤❤</span></div>
        </div>
        
        <div id="game-over">
            <h1 class="text-6xl font-black mb-4 text-white uppercase italic">Game Over</h1>
            <p class="text-3xl mb-8 opacity-90">SCORE: <span id="final-score">0</span></p>
            <div class="btn" onclick="resetGame()">PLAY AGAIN</div>
        </div>
    </div>

    <canvas id="gameCanvas"></canvas>

    <script>
        const canvas = document.getElementById('gameCanvas');
        const ctx = canvas.getContext('2d');
        const scoreEl = document.getElementById('score-val');
        const livesEl = document.getElementById('lives-val');
        const gameOverEl = document.getElementById('game-over');
        const finalScoreEl = document.getElementById('final-score');

        let score = 0;
        let lives = 3;
        let isGameOver = false;
        let objects = [];
        let particles = [];
        let splashes = [];
        let slicePath = [];
        let lastTime = 0;
        let spawnTimer = 0;
        let spawnRate = 1400;
        let difficultyMultiplier = 1;

        const TYPES = [
            { name: 'watermelon', color: '#10b981', inner: '#ef4444', r: 48, pts: 10 },
            { name: 'orange', color: '#f59e0b', inner: '#fbbf24', r: 38, pts: 15 },
            { name: 'plum', color: '#8b5cf6', inner: '#d8b4fe', r: 34, pts: 20 },
            { name: 'lemon', color: '#facc15', inner: '#fef08a', r: 32, pts: 15 },
            { name: 'bomb', color: '#1e293b', inner: '#ef4444', r: 30, pts: -50, isBomb: true }
        ];

        function resize() {
            canvas.width = window.innerWidth;
            canvas.height = window.innerHeight;
        }
        window.addEventListener('resize', resize);
        resize();

        class Splash {
            constructor(x, y, color) {
                this.x = x; this.y = y; this.color = color;
                this.opacity = 0.5;
                this.size = Math.random() * 50 + 50;
                this.r = Math.random() * Math.PI * 2;
            }
            update() { this.opacity -= 0.003; }
            draw() {
                ctx.save();
                ctx.globalAlpha = this.opacity;
                ctx.fillStyle = this.color;
                ctx.translate(this.x, this.y);
                ctx.rotate(this.r);
                // Create messy juice splat shape
                ctx.beginPath();
                for(let i=0; i<8; i++) {
                    let ang = (i / 8) * Math.PI * 2;
                    let rad = this.size * (0.6 + Math.random() * 0.4);
                    ctx.lineTo(Math.cos(ang)*rad, Math.sin(ang)*rad);
                }
                ctx.closePath();
                ctx.fill();
                ctx.restore();
            }
        }

        class Particle {
            constructor(x, y, color, vx, vy) {
                this.x = x; this.y = y; this.color = color;
                this.r = Math.random() * 4 + 2;
                this.vx = vx || (Math.random() - 0.5) * 15;
                this.vy = vy || (Math.random() - 0.5) * 15;
                this.life = 1.0;
            }
            update() {
                this.x += this.vx; this.y += this.vy;
                this.vy += 0.3; this.life -= 0.025;
            }
            draw() {
                ctx.globalAlpha = this.life;
                ctx.fillStyle = this.color;
                ctx.beginPath(); ctx.arc(this.x, this.y, this.r, 0, Math.PI*2); ctx.fill();
                ctx.globalAlpha = 1.0;
            }
        }

        class Sliceable {
            constructor() {
                const t = TYPES[Math.floor(Math.random() * TYPES.length)];
                this.t = t;
                this.r = t.r;
                this.x = Math.random() * (canvas.width - 120) + 60;
                this.y = canvas.height + this.r;
                this.vx = (canvas.width / 2 - this.x) * 0.012 + (Math.random() - 0.5) * 5;
                this.vy = -(Math.random() * 7 + 14);
                this.grav = 0.24;
                this.rot = 0;
                this.rotV = (Math.random() - 0.5) * 0.15;
                this.sliced = false;
                this.opacity = 1.0;
                this.h1 = { x:0, y:0, vx:0, vy:0, r:0, rv:0 };
                this.h2 = { x:0, y:0, vx:0, vy:0, r:0, rv:0 };
            }

            update() {
                if (!this.sliced) {
                    this.x += this.vx; this.y += this.vy;
                    this.vy += this.grav; this.rot += this.rotV;
                    if (this.y > canvas.height + 100 && !this.t.isBomb) {
                        loseLife();
                        this.sliced = true; this.opacity = 0;
                    }
                } else {
                    this.h1.x += this.h1.vx; this.h1.y += this.h1.vy; this.h1.vy += this.grav; this.h1.r += this.h1.rv;
                    this.h2.x += this.h2.vx; this.h2.y += this.h2.vy; this.h2.vy += this.grav; this.h2.r += this.h2.rv;
                    this.opacity -= 0.03;
                }
            }

            draw() {
                if (this.opacity <= 0) return;
                ctx.save();
                ctx.globalAlpha = this.opacity;
                if (!this.sliced) {
                    ctx.translate(this.x, this.y);
                    ctx.rotate(this.rot);
                    this.drawBody(0, 0, this.r, this.t.color);
                    if (this.t.isBomb) this.drawBombExtras();
                } else {
                    ctx.save();
                    ctx.translate(this.x + this.h1.x, this.y + this.h1.y);
                    ctx.rotate(this.h1.r);
                    this.drawHalf(this.r, this.t.color, this.t.inner, true);
                    ctx.restore();
                    ctx.save();
                    ctx.translate(this.x + this.h2.x, this.y + this.h2.y);
                    ctx.rotate(this.h2.r);
                    this.drawHalf(this.r, this.t.color, this.t.inner, false);
                    ctx.restore();
                }
                ctx.restore();
            }

            drawBody(x, y, r, c) {
                ctx.shadowBlur = 15; ctx.shadowColor = 'rgba(0,0,0,0.6)';
                ctx.fillStyle = c;
                ctx.beginPath(); ctx.arc(x, y, r, 0, Math.PI*2); ctx.fill();
                ctx.shadowBlur = 0;
                // Gloss
                ctx.fillStyle = 'rgba(255,255,255,0.25)';
                ctx.beginPath(); ctx.arc(x - r/3, y - r/3, r/3, 0, Math.PI*2); ctx.fill();
            }

            drawBombExtras() {
                ctx.strokeStyle = '#94a3b8'; ctx.lineWidth = 4;
                ctx.beginPath(); ctx.moveTo(0, -this.r);
                ctx.quadraticCurveTo(10, -this.r-15, 5, -this.r-25); ctx.stroke();
                // Spark
                ctx.fillStyle = '#fbbf24';
                ctx.beginPath(); ctx.arc(5, -this.r-25, 6 + Math.random()*3, 0, Math.PI*2); ctx.fill();
            }

            drawHalf(r, c, ic, left) {
                ctx.fillStyle = c;
                ctx.beginPath();
                if (left) ctx.arc(0, 0, r, Math.PI*0.5, Math.PI*1.5);
                else ctx.arc(0, 0, r, Math.PI*1.5, Math.PI*0.5);
                ctx.closePath(); ctx.fill();
                ctx.fillStyle = ic;
                ctx.beginPath();
                if (left) ctx.ellipse(-2, 0, r*0.75, r*0.85, 0, 0, Math.PI*2);
                else ctx.ellipse(2, 0, r*0.75, r*0.85, 0, 0, Math.PI*2);
                ctx.fill();
            }

            slice() {
                if (this.sliced) return;
                this.sliced = true;
                this.h1 = { x:-8, y:0, vx:-5, vy:-4, r:this.rot, rv:-0.15 };
                this.h2 = { x:8, y:0, vx:5, vy:-2, r:this.rot, rv:0.15 };

                if (this.t.isBomb) {
                    score = Math.max(0, score + this.t.pts);
                    loseLife(true);
                    explode(this.x, this.y, '#ef4444', 40);
                } else {
                    score += this.t.pts;
                    explode(this.x, this.y, this.t.color, 15);
                    splashes.push(new Splash(this.x, this.y, this.t.color));
                    // Hit Flash
                    ctx.save();
                    ctx.fillStyle = 'rgba(255,255,255,0.9)';
                    ctx.beginPath(); ctx.arc(this.x, this.y, this.r*2.5, 0, Math.PI*2); ctx.fill();
                    ctx.restore();
                }
                scoreEl.innerText = score;
            }
        }

        function explode(x, y, c, n) {
            for(let i=0; i<n; i++) particles.push(new Particle(x, y, c));
        }

        function loseLife(bomb = false) {
            if (bomb) lives = 0; else lives--;
            livesEl.innerText = "❤".repeat(Math.max(0, lives)) + "🤍".repeat(Math.max(0, 3 - lives));
            if (lives <= 0) { isGameOver = true; gameOverEl.style.display = 'block'; finalScoreEl.innerText = score; }
        }

        function resetGame() {
            score = 0; lives = 3; isGameOver = false;
            objects = []; particles = []; splashes = [];
            difficultyMultiplier = 1; spawnRate = 1400;
            scoreEl.innerText = "0"; livesEl.innerText = "❤❤❤";
            gameOverEl.style.display = 'none';
        }

        let slicing = false;
        function getPos(e) {
            const rect = canvas.getBoundingClientRect();
            return {
                x: (e.touches ? e.touches[0].clientX : e.clientX) - rect.left,
                y: (e.touches ? e.touches[0].clientY : e.clientY) - rect.top
            };
        }
        canvas.addEventListener('mousedown', (e) => { slicing = true; slicePath = [getPos(e)]; });
        window.addEventListener('mousemove', (e) => {
            if (!slicing) return;
            const p = getPos(e); slicePath.push(p);
            if (slicePath.length > 10) slicePath.shift();
            // Check collisions
            if (slicePath.length > 1) {
                const head = slicePath[slicePath.length-1];
                const prev = slicePath[slicePath.length-2];
                objects.forEach(o => {
                    if (!o.sliced && distToSeg(o, prev, head) < o.r) o.slice();
                });
            }
        });
        window.addEventListener('mouseup', () => { slicing = false; slicePath = []; });
        canvas.addEventListener('touchstart', (e) => { slicing = true; slicePath = [getPos(e)]; });
        window.addEventListener('touchmove', (e) => {
            e.preventDefault();
            if (!slicing) return;
            const p = getPos(e); slicePath.push(p);
            if (slicePath.length > 10) slicePath.shift();
            const head = slicePath[slicePath.length-1];
            const prev = slicePath[slicePath.length-2];
            objects.forEach(o => { if (!o.sliced && distToSeg(o, prev, head) < o.r) o.slice(); });
        }, { passive: false });

        function distToSeg(p, v, w) {
            const l2 = (v.x - w.x)**2 + (v.y - w.y)**2;
            if (l2 == 0) return Math.sqrt((p.x - v.x)**2 + (p.y - v.y)**2);
            let t = Math.max(0, Math.min(1, ((p.x - v.x) * (w.x - v.x) + (p.y - v.y) * (w.y - v.y)) / l2));
            return Math.sqrt((p.x - (v.x + t * (w.x - v.x)))**2 + (p.y - (v.y + t * (w.y - v.y)))**2);
        }

        function loop(t) {
            const dt = t - lastTime; lastTime = t;
            ctx.fillStyle = '#0f172a'; ctx.fillRect(0, 0, canvas.width, canvas.height);

            // BG Splashes
            splashes.forEach(s => { s.update(); s.draw(); });
            splashes = splashes.filter(s => s.opacity > 0);

            if (!isGameOver) {
                spawnTimer += dt;
                if (spawnTimer > spawnRate) {
                    const n = Math.floor(Math.random() * difficultyMultiplier) + 1;
                    for(let i=0; i<n; i++) objects.push(new Sliceable());
                    spawnTimer = 0;
                    difficultyMultiplier = Math.min(6, difficultyMultiplier + 0.015);
                    spawnRate = Math.max(500, 1400 - (difficultyMultiplier * 130));
                }

                objects.forEach(o => { o.update(); o.draw(); });
                objects = objects.filter(o => o.y < canvas.height + 200 && o.opacity > 0);

                particles.forEach(p => { p.update(); p.draw(); });
                particles = particles.filter(p => p.life > 0);

                // Blade Trail
                if (slicePath.length > 1) {
                    ctx.save();
                    ctx.shadowBlur = 20; ctx.shadowColor = '#60a5fa';
                    ctx.strokeStyle = 'white'; ctx.lineWidth = 6; ctx.lineCap = 'round';
                    ctx.beginPath(); ctx.moveTo(slicePath[0].x, slicePath[0].y);
                    slicePath.forEach(p => ctx.lineTo(p.x, p.y));
                    ctx.stroke();
                    ctx.restore();
                }
            }
            requestAnimationFrame(loop);
        }
        requestAnimationFrame(loop);
    </script>
</body>
</html>

Media & Assets

Fruit Slice Ninja: Ultimate Edition (V2)
Resource ID: #00037
Posted: December 25, 2025
© 2025 Your Code Library.