Liveweave: Generative AI Web Editor & HTML/CSS/JS Playground
Initializing
Liveweave
Web
expand_more
home
Home
Palette
Color Explorer
arrow_outward
Polyline
Graphics Editor
arrow_outward
frame_source
Python Playground
arrow_outward
build
Tools
expand_more
restart_alt
Load "Hello Weaver!"
post_add
Generate Lorem ipsum...
code
Format HTML
code_blocks
Format CSS
data_object
Format JavaScript
library_add
Library
expand_more
A
Algolia JS
Animate CSS
Apex Charts JS
B
Bulma CSS
Bootstrap
C
Chart JS
Chartist
Create JS
D
D3
Dojo
F
Foundation
Fullpage JS
G
Granim JS
Google Charts
H
Halfmoon
J
jQuery
M
Materialize
Moment JS
Masonry JS
Milligram CSS
P
Pure CSS
Primer CSS
Popper JS
Pattern CSS
Picnic CSS
R
React JS
Raphael JS
Raisin CSS
S
Semantic UI
Skeleton CSS
Spectre CSS
Tachyons CSS
T
Tailwind
Three JS
U
UI Kit
Vis JS
W
Water CSS
download
Download
expand_more
developer_mode
Download as HTML
folder_zip
Download as .ZIP
cloud_upload
Fork
account_circle
Login
settings
Settings
expand_more
Editor font
14
px
A
A
Night mode
Line number
Mini map
Word wrap
sync_alt
Reset Settings
smart_display
Run
left_panel_close
AI Assistant
dashboard
All
HTML
CSS
JS
visibility
Preview
bolt
Live Preview
terminal
JS Console
<!DOCTYPE html> <html> <head> <title>3D Bowling game</title> </head> <body> <!-- Start your code here --> <div class="bowling-app"> <header class="club-header"> <div class="club-logo"> <span class="logo-ball">●</span> <div> <strong>LUCKY<span>STRIKE</span></strong> <small>THE BOWLING SOCIAL CLUB</small> </div> </div> <div class="header-center"> <span class="header-star">✳</span> GOOD TIMES ARE ROLLING <span class="header-star">✳</span> </div> <button id="newGameButton" class="new-game-button"> ↻ NEW GAME </button> </header> <div class="game-container"> <section class="game-left"> <div class="intro-section"> <div class="eyebrow"> <span class="eyebrow-dot"></span> WELCOME TO THE CLUB </div> <h1> Roll with<br> the <em>good</em> times. </h1> <p> Ten frames. One perfect game. Line up your shot and let the good times roll. </p> </div> <div class="score-card"> <div class="score-card-top"> <span>YOUR SCORECARD</span> <span class="score-decoration">✳ ✳ ✳</span> </div> <div class="score-table-wrap"> <div class="score-table" id="scoreTable"></div> </div> <div class="score-total"> <div> <span>TOTAL SCORE</span> <strong id="totalScore">0</strong> </div> <div class="frame-indicator"> FRAME <strong id="currentFrame">1</strong> / 10 </div> </div> </div> <div class="club-tip"> <span class="tip-icon">✦</span> <div> <strong>PRO TIP</strong> <p> Aim for the pocket between the head pin and the pins behind it for the best strike! </p> </div> </div> </section> <section class="game-right"> <div class="lane-header"> <div> <span class="lane-label">LANE 07 / OPEN PLAY</span> <h2>THE MAIN EVENT</h2> </div> <div class="lane-status"> <span class="status-light"></span> READY TO BOWL </div> </div> <div class="game-viewport" id="gameViewport"> <div class="viewport-decoration top-left">✳</div> <div class="viewport-decoration top-right">✳</div> <div id="threeContainer"></div> <div class="viewport-overlay"> <div class="lane-number"> <span>LANE</span> <strong>07</strong> </div> <div class="shot-message" id="shotMessage"> LINE UP YOUR SHOT! </div> <div class="aim-instruction"> MOVE YOUR MOUSE TO AIM </div> </div> <div class="power-indicator"> <span>POWER</span> <div class="power-bars" id="powerBars"></div> </div> </div> <div class="game-controls"> <div class="aim-control"> <div class="control-label"> <span>AIM</span> <strong id="aimValue">CENTER</strong> </div> <input type="range" id="aimSlider" min="-100" max="100" value="0" > <div class="range-labels"> <span>LEFT</span> <span>RIGHT</span> </div> </div> <div class="power-control"> <div class="control-label"> <span>POWER</span> <strong id="powerValue">70%</strong> </div> <input type="range" id="powerSlider" min="20" max="100" value="70" > <div class="range-labels"> <span>SOFT</span> <span>MAX</span> </div> </div> <button id="rollButton" class="roll-button"> <span class="roll-icon">●</span> ROLL THE BALL <span class="roll-arrow">↗</span> </button> </div> <div class="game-bottom"> <span>★ EST. 1978 — BOWLING IS BETTER TOGETHER</span> <span>PLAY NICE. ROLL BIG.</span> </div> </section> </div> <div id="gameModal" class="game-modal hidden"> <div class="modal-card"> <div class="modal-star">✳</div> <span class="modal-eyebrow">GAME COMPLETE</span> <h2>What a roll!</h2> <p>You finished your bowling adventure.</p> <div class="modal-score" id="finalScore">0</div> <button id="playAgainButton" class="roll-button"> PLAY AGAIN </button> </div> </div> </div> <!-- End your code here --> </body> </html>
@import url('https://fonts.googleapis.com/css2?family=Fredoka:wght@400;500;600;700&family=Outfit:wght@400;500;600;700;800&display=swap'); * { box-sizing: border-box; } body { margin: 0; background: #f6efdf; color: #38251f; font-family: 'Outfit', sans-serif; } button, input { font-family: inherit; } button { cursor: pointer; } .bowling-app { min-height: 100vh; background: #f6efdf; } /* HEADER */ .club-header { height: 95px; padding: 0 5%; display: flex; align-items: center; justify-content: space-between; background: #f7d768; border-bottom: 4px solid #38251f; } .club-logo { display: flex; align-items: center; gap: 13px; } .logo-ball { display: flex; align-items: center; justify-content: center; width: 52px; height: 52px; border-radius: 50%; background: #e65c47; border: 3px solid #38251f; color: #38251f; font-size: 32px; } .club-logo strong { display: block; font-family: 'Fredoka', sans-serif; font-size: 24px; letter-spacing: -1px; } .club-logo strong span { color: #df5945; } .club-logo small { display: block; margin-top: 2px; font-size: 9px; font-weight: 800; letter-spacing: 2px; } .header-center { display: flex; align-items: center; gap: 18px; font-size: 11px; font-weight: 800; letter-spacing: 2px; } .header-star { font-size: 22px; color: #df5945; } .new-game-button { padding: 13px 22px; border: 3px solid #38251f; border-radius: 30px; background: #fff8e8; color: #38251f; font-size: 11px; font-weight: 800; letter-spacing: 1px; box-shadow: 4px 4px 0 #38251f; transition: .2s; } .new-game-button:hover { transform: translate(2px, 2px); box-shadow: 2px 2px 0 #38251f; } /* LAYOUT */ .game-container { max-width: 1600px; margin: auto; display: grid; grid-template-columns: 380px minmax(0, 1fr); gap: 45px; padding: 50px 5%; } .game-left, .game-right { min-width: 0; } .eyebrow { display: flex; align-items: center; gap: 10px; font-size: 11px; font-weight: 800; letter-spacing: 2px; } .eyebrow-dot { width: 9px; height: 9px; border-radius: 50%; background: #e65c47; } .intro-section h1 { font-family: 'Fredoka', sans-serif; font-size: clamp(48px, 4.5vw, 70px); font-weight: 600; letter-spacing: -2px; line-height: 1.03; margin: 24px 0; } .intro-section h1 em { color: #e65c47; font-style: normal; } .intro-section p { max-width: 310px; color: #8a776b; font-size: 14px; line-height: 1.8; } /* SCORECARD */ .score-card { margin-top: 40px; border: 3px solid #38251f; border-radius: 18px; background: #fffaf0; overflow: hidden; box-shadow: 8px 8px 0 #38251f; } .score-card-top { display: flex; align-items: center; justify-content: space-between; padding: 18px; background: #a8d6c2; border-bottom: 3px solid #38251f; font-size: 11px; font-weight: 800; letter-spacing: 1px; } .score-decoration { color: #df5945; font-size: 18px; } .score-table-wrap { overflow-x: auto; } .score-table { display: grid; grid-template-columns: repeat(10, minmax(48px, 1fr)); min-width: 480px; } .frame-box { border-right: 1px solid #d8c8b8; text-align: center; } .frame-box:last-child { border-right: none; } .frame-number { padding: 8px; background: #f4ead9; border-bottom: 1px solid #d8c8b8; font-size: 10px; font-weight: 800; } .frame-rolls { min-height: 32px; padding: 7px 2px; font-size: 11px; font-weight: 700; } .frame-score { min-height: 32px; padding: 7px 2px; border-top: 1px solid #e6d9c8; font-size: 13px; font-weight: 800; } .frame-box.active { background: #fff0bd; } .score-total { display: flex; justify-content: space-between; align-items: center; padding: 20px; border-top: 3px solid #38251f; } .score-total span { display: block; font-size: 10px; font-weight: 800; letter-spacing: 2px; } .score-total strong { display: block; font-family: 'Fredoka', sans-serif; font-size: 45px; line-height: 1.2; color: #e65c47; } .frame-indicator { font-size: 11px; font-weight: 800; } .frame-indicator strong { display: inline; font-size: 24px; color: #38251f; } .club-tip { display: flex; gap: 15px; margin-top: 40px; padding: 22px; border-radius: 14px; background: #e9ddc8; } .tip-icon { color: #e65c47; font-size: 30px; } .club-tip strong { font-size: 11px; letter-spacing: 2px; } .club-tip p { margin-bottom: 0; color: #79685d; font-size: 12px; line-height: 1.8; } /* GAME */ .lane-header { display: flex; justify-content: space-between; align-items: center; gap: 20px; margin-bottom: 18px; } .lane-label { font-size: 10px; font-weight: 800; letter-spacing: 2px; color: #e65c47; } .lane-header h2 { font-family: 'Fredoka', sans-serif; font-size: 27px; margin: 7px 0 0; } .lane-status { display: flex; align-items: center; gap: 8px; font-size: 10px; font-weight: 800; letter-spacing: 1px; } .status-light { width: 8px; height: 8px; border-radius: 50%; background: #51ae85; } .game-viewport { position: relative; height: 560px; border: 5px solid #38251f; border-radius: 20px; overflow: hidden; background: #f2b58f; box-shadow: 10px 10px 0 #38251f; } #threeContainer { position: absolute; inset: 0; } #threeContainer canvas { display: block; width: 100%; height: 100%; } .viewport-decoration { position: absolute; z-index: 3; font-size: 45px; color: #f7d768; pointer-events: none; } .top-left { top: 15px; left: 20px; } .top-right { top: 15px; right: 20px; } .viewport-overlay { position: absolute; inset: 0; z-index: 4; pointer-events: none; } .lane-number { position: absolute; top: 25px; left: 75px; display: flex; flex-direction: column; color: #fff8e8; } .lane-number span { font-size: 10px; font-weight: 800; letter-spacing: 2px; } .lane-number strong { font-family: 'Fredoka', sans-serif; font-size: 37px; line-height: 1; } .shot-message { position: absolute; top: 30px; left: 50%; transform: translateX(-50%); padding: 12px 25px; border-radius: 30px; background: #fff8e8; border: 3px solid #38251f; color: #38251f; font-family: 'Fredoka', sans-serif; font-size: 15px; white-space: nowrap; box-shadow: 4px 4px 0 #38251f; } .aim-instruction { position: absolute; bottom: 25px; left: 50%; transform: translateX(-50%); padding: 10px 20px; border-radius: 20px; background: #38251fdd; color: #fff8e8; font-size: 10px; font-weight: 800; letter-spacing: 1px; white-space: nowrap; } .power-indicator { position: absolute; right: 20px; bottom: 65px; z-index: 5; display: flex; flex-direction: column; align-items: center; gap: 10px; } .power-indicator span { font-size: 10px; font-weight: 800; color: #fff8e8; } .power-bars { display: flex; flex-direction: column-reverse; gap: 4px; } .power-bar { width: 24px; height: 9px; border: 2px solid #38251f; border-radius: 3px; background: #fff8e8; } .power-bar.active { background: #f7d768; } /* CONTROLS */ .game-controls { display: grid; grid-template-columns: 1fr 1fr auto; align-items: center; gap: 25px; margin-top: 30px; padding: 25px; border: 3px solid #38251f; border-radius: 16px; background: #fffaf0; box-shadow: 6px 6px 0 #38251f; } .control-label { display: flex; justify-content: space-between; gap: 10px; font-size: 11px; font-weight: 800; letter-spacing: 1px; } .control-label strong { color: #e65c47; } .game-controls input[type="range"] { width: 100%; margin: 14px 0 8px; accent-color: #e65c47; cursor: pointer; } .range-labels { display: flex; justify-content: space-between; color: #a18b7c; font-size: 9px; font-weight: 800; letter-spacing: 1px; } .roll-button { display: flex; align-items: center; justify-content: center; gap: 13px; padding: 20px 24px; border: 3px solid #38251f; border-radius: 12px; background: #e65c47; color: #fffaf0; font-family: 'Fredoka', sans-serif; font-size: 16px; font-weight: 600; white-space: nowrap; box-shadow: 5px 5px 0 #38251f; transition: .2s; } .roll-button:hover { transform: translate(2px, 2px); box-shadow: 3px 3px 0 #38251f; } .roll-button:disabled { opacity: .5; cursor: not-allowed; } .roll-icon { font-size: 23px; } .roll-arrow { font-size: 20px; } .game-bottom { display: flex; justify-content: space-between; gap: 20px; margin-top: 30px; font-size: 10px; font-weight: 800; letter-spacing: 1px; color: #8b7566; } /* MODAL */ .game-modal { position: fixed; inset: 0; z-index: 100; display: flex; align-items: center; justify-content: center; padding: 20px; background: #38251fe8; } .game-modal.hidden { display: none; } .modal-card { width: 100%; max-width: 440px; padding: 45px; border: 4px solid #38251f; border-radius: 25px; background: #f7d768; text-align: center; box-shadow: 12px 12px 0 #e65c47; } .modal-star { font-size: 65px; color: #e65c47; } .modal-eyebrow { font-size: 11px; font-weight: 800; letter-spacing: 3px; } .modal-card h2 { font-family: 'Fredoka', sans-serif; font-size: 50px; margin: 15px 0; } .modal-card p { color: #796153; } .modal-score { margin: 25px 0; font-family: 'Fredoka', sans-serif; font-size: 85px; color: #e65c47; } .modal-card .roll-button { width: 100%; } /* RESPONSIVE */ @media (max-width: 1250px) { .game-container { grid-template-columns: 300px minmax(0, 1fr); gap: 30px; } .game-controls { grid-template-columns: 1fr 1fr; } .roll-button { grid-column: 1 / -1; } } @media (max-width: 900px) { .game-container { grid-template-columns: 1fr; } .game-left { display: grid; grid-template-columns: 1fr 1fr; gap: 25px; } .score-card { margin-top: 0; } .club-tip { grid-column: 1 / -1; margin-top: 0; } } @media (max-width: 650px) { .club-header { height: auto; padding: 18px; } .header-center { display: none; } .club-logo strong { font-size: 18px; } .game-container { padding: 30px 18px; } .game-left { display: block; } .score-card { margin-top: 30px; } .club-tip { margin-top: 30px; } .game-viewport { height: 430px; } .game-controls { grid-template-columns: 1fr; } .roll-button { grid-column: auto; } .game-bottom { flex-direction: column; gap: 8px; } }
(function () { var container = document.getElementById("threeContainer"); if (!container) { return; } // Load Three.js dynamically function loadThree(callback) { if (window.THREE) { callback(); return; } var script = document.createElement("script"); script.src = "https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"; script.onload = callback; script.onerror = function () { document.getElementById("shotMessage").innerHTML = "Unable to load 3D engine."; }; document.head.appendChild(script); } loadThree(initGame); function initGame() { var THREE = window.THREE; var scene = new THREE.Scene(); scene.background = new THREE.Color(0xf2b58f); scene.fog = new THREE.Fog(0xf2b58f, 18, 60); var camera = new THREE.PerspectiveCamera( 45, container.clientWidth / container.clientHeight, 0.1, 100 ); camera.position.set(0, 7, 18); camera.lookAt(0, 0, -5); var renderer = new THREE.WebGLRenderer({ antialias: true, alpha: false }); renderer.setSize( container.clientWidth, container.clientHeight ); renderer.setPixelRatio( Math.min(window.devicePixelRatio, 2) ); renderer.shadowMap.enabled = true; renderer.shadowMap.type = THREE.PCFSoftShadowMap; container.appendChild(renderer.domElement); // LIGHTING scene.add(new THREE.AmbientLight(0xffffff, 0.7)); var mainLight = new THREE.DirectionalLight( 0xffffff, 1.2 ); mainLight.position.set(-5, 12, 8); mainLight.castShadow = true; mainLight.shadow.mapSize.width = 1024; mainLight.shadow.mapSize.height = 1024; scene.add(mainLight); var warmLight = new THREE.PointLight( 0xffb36b, 1.2, 25 ); warmLight.position.set(4, 6, -8); scene.add(warmLight); // MATERIALS var woodMaterial = new THREE.MeshStandardMaterial({ color: 0xe4a969, roughness: 0.45, metalness: 0.05 }); var darkWood = new THREE.MeshStandardMaterial({ color: 0x8c4b39, roughness: 0.6 }); var creamMaterial = new THREE.MeshStandardMaterial({ color: 0xfff4d9, roughness: 0.35 }); var redMaterial = new THREE.MeshStandardMaterial({ color: 0xe65c47, roughness: 0.35 }); var mintMaterial = new THREE.MeshStandardMaterial({ color: 0x8acbb3, roughness: 0.5 }); // LANE var lane = new THREE.Mesh( new THREE.BoxGeometry(6, 0.3, 36), woodMaterial ); lane.position.set(0, -0.2, -7); lane.receiveShadow = true; scene.add(lane); // WOOD STRIPES for (var i = 0; i < 14; i++) { var stripe = new THREE.Mesh( new THREE.BoxGeometry(0.015, 0.01, 36), new THREE.MeshStandardMaterial({ color: i % 2 === 0 ? 0xf7c891 : 0xb97a4c }) ); stripe.position.set( -2.8 + i * 0.43, -0.04, -7 ); scene.add(stripe); } // GUTTERS [-3.3, 3.3].forEach(function (x) { var gutter = new THREE.Mesh( new THREE.BoxGeometry(0.5, 0.2, 36), darkWood ); gutter.position.set(x, -0.3, -7); scene.add(gutter); }); // SIDE WALLS [-5.5, 5.5].forEach(function (x) { var wall = new THREE.Mesh( new THREE.BoxGeometry(0.4, 7, 45), mintMaterial ); wall.position.set(x, 3.3, -8); scene.add(wall); }); // BACK WALL var backWall = new THREE.Mesh( new THREE.BoxGeometry(12, 8, 0.5), redMaterial ); backWall.position.set(0, 3.5, -25); scene.add(backWall); // DECORATIVE PANELS for (var p = 0; p < 6; p++) { var panel = new THREE.Mesh( new THREE.BoxGeometry(1.5, 1.5, 0.1), p % 2 === 0 ? creamMaterial : mintMaterial ); panel.position.set(-4.5 + p * 1.8, 5, -24.7); scene.add(panel); } // BALL var ballGroup = new THREE.Group(); var ball = new THREE.Mesh( new THREE.SphereGeometry(0.55, 32, 32), new THREE.MeshStandardMaterial({ color: 0xe65c47, roughness: 0.2, metalness: 0.1 }) ); ball.castShadow = true; ballGroup.add(ball); // BALL STRIPES var stripeGeometry = new THREE.TorusGeometry( 0.54, 0.035, 8, 64 ); var stripeMaterial = new THREE.MeshStandardMaterial({ color: 0xffd66e }); for (var s = 0; s < 3; s++) { var stripeRing = new THREE.Mesh( stripeGeometry, stripeMaterial ); stripeRing.rotation.x = s * 0.5; stripeRing.rotation.y = s * 0.6; ballGroup.add(stripeRing); } ballGroup.position.set(0, 0.55, 8); scene.add(ballGroup); // PINS var pins = []; function createPin(x, z) { var pinGroup = new THREE.Group(); var pinBody = new THREE.Mesh( new THREE.CylinderGeometry( 0.13, 0.22, 1.2, 16 ), creamMaterial ); pinBody.position.y = 0.6; pinBody.castShadow = true; pinGroup.add(pinBody); var pinHead = new THREE.Mesh( new THREE.SphereGeometry(0.19, 16, 16), creamMaterial ); pinHead.position.y = 1.25; pinHead.castShadow = true; pinGroup.add(pinHead); var neckStripe = new THREE.Mesh( new THREE.CylinderGeometry( 0.145, 0.145, 0.12, 16 ), redMaterial ); neckStripe.position.y = 1.02; pinGroup.add(neckStripe); pinGroup.position.set(x, 0, z); scene.add(pinGroup); pins.push({ mesh: pinGroup, x: x, z: z, standing: true, velocityX: 0, velocityZ: 0, falling: false }); } function resetPins() { pins.forEach(function (pin) { scene.remove(pin.mesh); }); pins = []; var positions = [ [0, -19], [-0.45, -20], [0.45, -20], [-0.9, -21], [0, -21], [0.9, -21], [-1.35, -22], [-0.45, -22], [0.45, -22], [1.35, -22] ]; positions.forEach(function (position) { createPin(position[0], position[1]); }); } resetPins(); // GAME STATE var frames = []; var currentFrame = 0; var currentRoll = 0; var standingPins = 10; var aim = 0; var power = 70; var rolling = false; var ballVelocity = 0; var ballDirection = 0; var knockedThisRoll = 0; var aimSlider = document.getElementById("aimSlider"); var powerSlider = document.getElementById("powerSlider"); var rollButton = document.getElementById("rollButton"); var shotMessage = document.getElementById("shotMessage"); // POWER BARS function updatePowerBars() { var container = document.getElementById("powerBars"); container.innerHTML = ""; for (var i = 0; i < 10; i++) { var bar = document.createElement("div"); bar.className = "power-bar"; if (i < Math.ceil(power / 10)) { bar.classList.add("active"); } container.appendChild(bar); } } updatePowerBars(); // SCORECARD function renderScorecard() { var table = document.getElementById("scoreTable"); table.innerHTML = ""; for (var i = 0; i < 10; i++) { var frame = frames[i] || []; var box = document.createElement("div"); box.className = "frame-box"; if (i === currentFrame) { box.classList.add("active"); } var rollsText = frame.map(function (value) { return value === 10 ? "X" : value; }).join(" "); box.innerHTML = '<div class="frame-number">' + (i + 1) + '</div>' + '<div class="frame-rolls">' + rollsText + '</div>' + '<div class="frame-score">' + (calculateScore(i) || "") + '</div>'; table.appendChild(box); } document.getElementById("totalScore").innerHTML = calculateScore(9); document.getElementById("currentFrame").innerHTML = Math.min(currentFrame + 1, 10); } function calculateScore(upToFrame) { var total = 0; for (var i = 0; i <= upToFrame; i++) { var frame = frames[i]; if (!frame) { continue; } total += frame.reduce(function (a, b) { return a + b; }, 0); } return total; } renderScorecard(); // CONTROLS aimSlider.addEventListener("input", function () { aim = parseFloat(this.value) / 100; document.getElementById("aimValue").innerHTML = aim < -0.1 ? "LEFT" : aim > 0.1 ? "RIGHT" : "CENTER"; if (!rolling) { ballGroup.position.x = aim * 2; } }); powerSlider.addEventListener("input", function () { power = parseFloat(this.value); document.getElementById("powerValue").innerHTML = power + "%"; updatePowerBars(); }); // ROLL function rollBall() { if (rolling || currentFrame >= 10) { return; } rolling = true; rollButton.disabled = true; ballVelocity = 0.18 + power * 0.004; ballDirection = aim * 0.015; knockedThisRoll = 0; shotMessage.innerHTML = "HERE WE GO!"; ballGroup.position.z = 8; } rollButton.addEventListener("click", rollBall); // COLLISIONS function checkCollisions() { pins.forEach(function (pin) { if (!pin.standing) { return; } var dx = ballGroup.position.x - pin.mesh.position.x; var dz = ballGroup.position.z - pin.mesh.position.z; var distance = Math.sqrt(dx * dx + dz * dz); if (distance < 0.7) { pin.standing = false; pin.falling = true; pin.velocityX = dx * -0.12 + (Math.random() - 0.5) * 0.1; pin.velocityZ = -0.1 - Math.random() * 0.1; knockedThisRoll++; } }); } function updatePins() { pins.forEach(function (pin) { if (!pin.falling) { return; } pin.mesh.rotation.z += 0.08; pin.mesh.position.x += pin.velocityX; pin.mesh.position.z += pin.velocityZ; pin.velocityX *= 0.97; pin.velocityZ *= 0.97; if (pin.mesh.rotation.z > 1.5) { pin.falling = false; } }); } // FRAME MANAGEMENT function finishRoll() { rolling = false; standingPins -= knockedThisRoll; if (!frames[currentFrame]) { frames[currentFrame] = []; } frames[currentFrame].push(knockedThisRoll); currentRoll++; if (standingPins <= 0 || currentRoll >= 2) { currentFrame++; currentRoll = 0; standingPins = 10; resetPins(); } if (currentFrame >= 10) { document.getElementById("finalScore").innerHTML = calculateScore(9); document.getElementById("gameModal").classList.remove("hidden"); shotMessage.innerHTML = "GAME COMPLETE!"; } else { shotMessage.innerHTML = knockedThisRoll === 10 ? "STRIKE!" : knockedThisRoll >= 7 ? "NICE SHOT!" : knockedThisRoll === 0 ? "GUTTER BALL!" : "KEEP ROLLING!"; } ballGroup.position.set(aim * 2, 0.55, 8); rollButton.disabled = currentFrame >= 10; renderScorecard(); } // ANIMATION var lastTime = 0; function animate(time) { requestAnimationFrame(animate); var delta = Math.min((time - lastTime) / 16.67, 2); lastTime = time; if (rolling) { ballGroup.position.z -= ballVelocity * delta; ballGroup.position.x += ballDirection * delta; ballGroup.rotation.x -= ballVelocity * delta; checkCollisions(); if (ballGroup.position.z < -24) { finishRoll(); } } updatePins(); renderer.render(scene, camera); } requestAnimationFrame(animate); // NEW GAME function newGame() { frames = []; currentFrame = 0; currentRoll = 0; standingPins = 10; rolling = false; resetPins(); ballGroup.position.set(0, 0.55, 8); aim = 0; power = 70; aimSlider.value = 0; powerSlider.value = 70; rollButton.disabled = false; shotMessage.innerHTML = "LINE UP YOUR SHOT!"; document.getElementById("gameModal").classList.add("hidden"); updatePowerBars(); renderScorecard(); } document.getElementById("newGameButton").addEventListener( "click", newGame ); document.getElementById("playAgainButton").addEventListener( "click", newGame ); // RESIZE window.addEventListener("resize", function () { camera.aspect = container.clientWidth / container.clientHeight; camera.updateProjectionMatrix(); renderer.setSize( container.clientWidth, container.clientHeight ); }); } })();
terminal
JavaScript Console
Preview
Clear
close
›
Run