d-d-dice / index.html
dricampbell's picture
Add 1 files
b12dd46 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Epic D&D Dice Roller</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/cannon-es@0.19.0/dist/cannon-es.min.js"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Cinzel:wght@400;700&family=MedievalSharp&display=swap');
body {
background-color: #0a0a0a;
color: #e0d4b0;
font-family: 'Cinzel', serif;
overflow-x: hidden;
}
.dice-container {
perspective: 1000px;
}
.roll-history-item {
border-left: 3px solid #8b5a2b;
transition: all 0.3s ease;
}
.roll-history-item:hover {
background-color: rgba(139, 90, 43, 0.2);
}
.dice-selector {
transition: all 0.2s ease;
min-height: 80px;
}
.dice-selector:hover {
transform: translateY(-5px);
box-shadow: 0 10px 15px rgba(0, 0, 0, 0.3);
}
.dice-selector.active {
border-color: #d4af37;
box-shadow: 0 0 15px rgba(212, 175, 55, 0.5);
}
.glow-text {
text-shadow: 0 0 5px rgba(212, 175, 55, 0.7);
}
.roll-button {
background: linear-gradient(135deg, #8b5a2b 0%, #5d3a1a 100%);
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3);
transition: all 0.3s ease;
}
.roll-button:hover {
transform: translateY(-3px);
box-shadow: 0 7px 20px rgba(139, 90, 43, 0.6);
}
.roll-button:active {
transform: translateY(1px);
}
#canvas-container {
position: relative;
width: 100%;
height: 400px;
background: radial-gradient(ellipse at center, #1a1a1a 0%, #0a0a0a 100%);
border-radius: 10px;
overflow: hidden;
}
#canvas-container::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><rect width="100" height="100" fill="none" stroke="%23222222" stroke-width="1"/></svg>');
opacity: 0.3;
pointer-events: none;
}
.critical {
animation: pulse 1s infinite;
}
@keyframes pulse {
0% { color: #e0d4b0; }
50% { color: #d4af37; }
100% { color: #e0d4b0; }
}
.shape-name {
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
font-size: 0.7rem;
line-height: 1.1;
}
.dice-preview {
width: 40px;
height: 40px;
margin: 0 auto 5px;
position: relative;
}
.dice-preview-container {
width: 100%;
height: 100%;
transform-style: preserve-3d;
transition: transform 1s;
}
.dice-face {
position: absolute;
width: 100%;
height: 100%;
border: 1px solid rgba(255,255,255,0.2);
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
font-weight: bold;
color: white;
}
</style>
</head>
<body class="min-h-screen">
<div class="container mx-auto px-4 py-8">
<!-- Header -->
<header class="text-center mb-12">
<h1 class="text-4xl md:text-6xl font-bold mb-4 glow-text">Dragon's Fortune</h1>
<p class="text-xl md:text-2xl font-medium text-amber-200">Epic Dice Roller for Adventurers</p>
<div class="w-32 h-1 bg-amber-700 mx-auto mt-4 rounded-full"></div>
</header>
<div class="flex flex-col lg:flex-row gap-8">
<!-- Left Panel - Controls -->
<div class="w-full lg:w-1/3">
<div class="bg-gray-900 bg-opacity-70 rounded-lg p-6 shadow-xl border border-gray-800">
<!-- Dice Selection -->
<h2 class="text-2xl font-bold mb-4 text-amber-200 border-b border-amber-900 pb-2">Choose Your Dice</h2>
<div class="grid grid-cols-3 gap-4 mb-6">
<div class="dice-selector p-3 bg-gray-800 rounded-lg cursor-pointer text-center border border-gray-700" data-sides="4">
<div class="dice-preview">
<div class="dice-preview-container" id="d4-preview">
<!-- Faces will be added by JS -->
</div>
</div>
<div class="text-amber-200 text-lg font-bold">D4</div>
<div class="shape-name text-gray-400">Tetrahedron</div>
</div>
<div class="dice-selector p-3 bg-gray-800 rounded-lg cursor-pointer text-center border border-gray-700" data-sides="6">
<div class="dice-preview">
<div class="dice-preview-container" id="d6-preview">
<!-- Faces will be added by JS -->
</div>
</div>
<div class="text-amber-200 text-lg font-bold">D6</div>
<div class="shape-name text-gray-400">Cube</div>
</div>
<div class="dice-selector p-3 bg-gray-800 rounded-lg cursor-pointer text-center border border-gray-700" data-sides="8">
<div class="dice-preview">
<div class="dice-preview-container" id="d8-preview">
<!-- Faces will be added by JS -->
</div>
</div>
<div class="text-amber-200 text-lg font-bold">D8</div>
<div class="shape-name text-gray-400">Octahedron</div>
</div>
<div class="dice-selector p-3 bg-gray-800 rounded-lg cursor-pointer text-center border border-gray-700" data-sides="10">
<div class="dice-preview">
<div class="dice-preview-container" id="d10-preview">
<!-- Faces will be added by JS -->
</div>
</div>
<div class="text-amber-200 text-lg font-bold">D10</div>
<div class="shape-name text-gray-400">Pentagonal Trapezohedron</div>
</div>
<div class="dice-selector p-3 bg-gray-800 rounded-lg cursor-pointer text-center border border-gray-700" data-sides="12">
<div class="dice-preview">
<div class="dice-preview-container" id="d12-preview">
<!-- Faces will be added by JS -->
</div>
</div>
<div class="text-amber-200 text-lg font-bold">D12</div>
<div class="shape-name text-gray-400">Dodecahedron</div>
</div>
<div class="dice-selector p-3 bg-gray-800 rounded-lg cursor-pointer text-center border border-gray-700" data-sides="20">
<div class="dice-preview">
<div class="dice-preview-container" id="d20-preview">
<!-- Faces will be added by JS -->
</div>
</div>
<div class="text-amber-200 text-lg font-bold">D20</div>
<div class="shape-name text-gray-400">Icosahedron</div>
</div>
</div>
<!-- Quantity and Modifiers -->
<div class="mb-6">
<div class="flex items-center justify-between mb-2">
<label class="text-amber-200">Quantity:</label>
<input type="number" id="diceQuantity" min="1" max="10" value="1" class="w-16 bg-gray-800 border border-gray-700 text-center text-amber-200 rounded px-2 py-1">
</div>
<div class="flex items-center justify-between mb-2">
<label class="text-amber-200">Modifier:</label>
<input type="number" id="diceModifier" min="-20" max="20" value="0" class="w-16 bg-gray-800 border border-gray-700 text-center text-amber-200 rounded px-2 py-1">
</div>
</div>
<!-- Roll Button -->
<button id="rollButton" class="roll-button w-full py-4 rounded-lg text-xl font-bold text-amber-200 uppercase tracking-wider">
Roll the Dice
</button>
<!-- Result Display -->
<div id="resultDisplay" class="mt-6 p-4 bg-gray-800 rounded-lg text-center hidden">
<div class="text-sm text-gray-400 mb-1">Result</div>
<div id="totalResult" class="text-4xl font-bold text-amber-200"></div>
<div id="individualResults" class="mt-2 text-gray-300"></div>
<div id="criticalText" class="mt-2 text-xl font-bold text-red-400 hidden">CRITICAL HIT!</div>
</div>
</div>
<!-- Roll History -->
<div class="bg-gray-900 bg-opacity-70 rounded-lg p-6 shadow-xl border border-gray-800 mt-6">
<h2 class="text-2xl font-bold mb-4 text-amber-200 border-b border-amber-900 pb-2">Roll History</h2>
<div id="rollHistory" class="space-y-2 max-h-64 overflow-y-auto pr-2">
<!-- History items will be added here -->
</div>
</div>
</div>
<!-- Right Panel - 3D Dice -->
<div class="w-full lg:w-2/3">
<div id="canvas-container" class="dice-container">
<!-- Three.js canvas will be inserted here -->
</div>
<!-- Dice Table -->
<div class="bg-gray-900 bg-opacity-70 rounded-lg p-6 shadow-xl border border-gray-800 mt-6">
<h2 class="text-2xl font-bold mb-4 text-amber-200 border-b border-amber-900 pb-2">Dice Reference</h2>
<div class="grid grid-cols-2 md:grid-cols-3 gap-4">
<div class="p-3 bg-gray-800 rounded-lg">
<div class="flex items-center">
<div class="w-8 h-8 bg-amber-700 rounded flex items-center justify-center mr-2">4</div>
<div>
<div>D4</div>
<div class="text-xs text-gray-400">Tetrahedron</div>
</div>
</div>
</div>
<div class="p-3 bg-gray-800 rounded-lg">
<div class="flex items-center">
<div class="w-8 h-8 bg-amber-700 rounded flex items-center justify-center mr-2">6</div>
<div>
<div>D6</div>
<div class="text-xs text-gray-400">Cube</div>
</div>
</div>
</div>
<div class="p-3 bg-gray-800 rounded-lg">
<div class="flex items-center">
<div class="w-8 h-8 bg-amber-700 rounded flex items-center justify-center mr-2">8</div>
<div>
<div>D8</div>
<div class="text-xs text-gray-400">Octahedron</div>
</div>
</div>
</div>
<div class="p-3 bg-gray-800 rounded-lg">
<div class="flex items-center">
<div class="w-8 h-8 bg-amber-700 rounded flex items-center justify-center mr-2">10</div>
<div>
<div>D10</div>
<div class="text-xs text-gray-400">Pentagonal Trapezohedron</div>
</div>
</div>
</div>
<div class="p-3 bg-gray-800 rounded-lg">
<div class="flex items-center">
<div class="w-8 h-8 bg-amber-700 rounded flex items-center justify-center mr-2">12</div>
<div>
<div>D12</div>
<div class="text-xs text-gray-400">Dodecahedron</div>
</div>
</div>
</div>
<div class="p-3 bg-gray-800 rounded-lg">
<div class="flex items-center">
<div class="w-8 h-8 bg-amber-700 rounded flex items-center justify-center mr-2">20</div>
<div>
<div>D20</div>
<div class="text-xs text-gray-400">Icosahedron</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
// Initialize variables
let selectedSides = 20;
let scene, camera, renderer, world;
let diceObjects = [];
let diceMeshes = [];
let diceValues = {};
let isRolling = false;
// Initialize Three.js and Cannon.js
function init() {
// Set up Three.js scene
scene = new THREE.Scene();
scene.background = new THREE.Color(0x0a0a0a);
// Add ambient light
const ambientLight = new THREE.AmbientLight(0x404040, 1.5);
scene.add(ambientLight);
// Add directional light
const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
directionalLight.position.set(1, 1, 1).normalize();
directionalLight.castShadow = true;
directionalLight.shadow.mapSize.width = 1024;
directionalLight.shadow.mapSize.height = 1024;
scene.add(directionalLight);
// Add point light
const pointLight = new THREE.PointLight(0xd4af37, 1, 10);
pointLight.position.set(0, 3, 2);
scene.add(pointLight);
// Set up camera
camera = new THREE.PerspectiveCamera(45, document.getElementById('canvas-container').clientWidth / document.getElementById('canvas-container').clientHeight, 0.1, 1000);
camera.position.set(0, 5, 10);
camera.lookAt(0, 0, 0);
// Set up renderer
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(document.getElementById('canvas-container').clientWidth, document.getElementById('canvas-container').clientHeight);
renderer.shadowMap.enabled = true;
document.getElementById('canvas-container').appendChild(renderer.domElement);
// Set up Cannon.js world
world = new CANNON.World();
world.gravity.set(0, -9.82, 0);
world.broadphase = new CANNON.NaiveBroadphase();
world.solver.iterations = 10;
// Create a ground plane
const groundShape = new CANNON.Plane();
const groundBody = new CANNON.Body({ mass: 0 });
groundBody.addShape(groundShape);
groundBody.quaternion.setFromAxisAngle(new CANNON.Vec3(1, 0, 0), -Math.PI / 2);
groundBody.position.y = -2;
world.addBody(groundBody);
// Add a ground plane visual
const groundGeometry = new THREE.PlaneGeometry(20, 20, 1, 1);
const groundMaterial = new THREE.MeshStandardMaterial({
color: 0x333333,
roughness: 0.8,
metalness: 0.2
});
const groundMesh = new THREE.Mesh(groundGeometry, groundMaterial);
groundMesh.rotation.x = -Math.PI / 2;
groundMesh.receiveShadow = true;
scene.add(groundMesh);
// Add decorative elements
addDecorativeElements();
// Handle window resize
window.addEventListener('resize', onWindowResize);
// Set up dice selection
setupDiceSelection();
// Set up dice previews
setupDicePreviews();
// Set up roll button
document.getElementById('rollButton').addEventListener('click', rollDice);
// Start animation loop
animate();
}
// Set up dice previews
function setupDicePreviews() {
// D4 Preview
createDicePreview('d4-preview', 4, '#8b5a2b');
// D6 Preview
createDicePreview('d6-preview', 6, '#9b111e');
// D8 Preview
createDicePreview('d8-preview', 8, '#005b96');
// D10 Preview
createDicePreview('d10-preview', 10, '#5e8c31');
// D12 Preview
createDicePreview('d12-preview', 12, '#7d26cd');
// D20 Preview
createDicePreview('d20-preview', 20, '#d4af37');
}
// Create a dice preview
function createDicePreview(containerId, sides, color) {
const container = document.getElementById(containerId);
container.innerHTML = '';
// Create faces based on dice type
if (sides === 4) {
// Tetrahedron (4 triangular faces)
for (let i = 0; i < 4; i++) {
const face = document.createElement('div');
face.className = 'dice-face';
face.style.backgroundColor = color;
face.style.clipPath = 'polygon(50% 0%, 0% 100%, 100% 100%)';
face.style.transform = `rotateX(${i * 120}deg) rotateY(0deg) translateZ(20px)`;
face.textContent = i+1;
container.appendChild(face);
}
container.style.transform = 'rotateX(20deg) rotateY(20deg)';
}
else if (sides === 6) {
// Cube (6 square faces)
const positions = [
{ transform: 'rotateY(0deg) translateZ(20px)', color: color }, // front
{ transform: 'rotateY(180deg) translateZ(20px)', color: color }, // back
{ transform: 'rotateY(90deg) translateZ(20px)', color: color }, // right
{ transform: 'rotateY(-90deg) translateZ(20px)', color: color }, // left
{ transform: 'rotateX(90deg) translateZ(20px)', color: color }, // top
{ transform: 'rotateX(-90deg) translateZ(20px)', color: color } // bottom
];
for (let i = 0; i < 6; i++) {
const face = document.createElement('div');
face.className = 'dice-face';
face.style.backgroundColor = positions[i].color;
face.style.transform = positions[i].transform;
face.textContent = i+1;
container.appendChild(face);
}
container.style.transform = 'rotateX(20deg) rotateY(20deg)';
}
else if (sides === 8) {
// Octahedron (8 triangular faces)
for (let i = 0; i < 8; i++) {
const face = document.createElement('div');
face.className = 'dice-face';
face.style.backgroundColor = color;
face.style.clipPath = 'polygon(50% 0%, 0% 100%, 100% 100%)';
face.style.transform = `rotateX(${i * 90}deg) rotateY(${i % 2 === 0 ? 0 : 180}deg) translateZ(15px)`;
face.textContent = i+1;
container.appendChild(face);
}
container.style.transform = 'rotateX(20deg) rotateY(20deg)';
}
else if (sides === 10) {
// Pentagonal Trapezohedron (10 kite-shaped faces)
for (let i = 0; i < 10; i++) {
const face = document.createElement('div');
face.className = 'dice-face';
face.style.backgroundColor = color;
face.style.clipPath = 'polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%)';
face.style.transform = `rotateX(${i * 36}deg) rotateY(${i % 2 === 0 ? 0 : 180}deg) translateZ(15px)`;
face.textContent = i+1;
container.appendChild(face);
}
container.style.transform = 'rotateX(20deg) rotateY(20deg)';
}
else if (sides === 12) {
// Dodecahedron (12 pentagonal faces)
for (let i = 0; i < 12; i++) {
const face = document.createElement('div');
face.className = 'dice-face';
face.style.backgroundColor = color;
face.style.clipPath = 'polygon(50% 0%, 100% 38%, 82% 100%, 18% 100%, 0% 38%)';
face.style.transform = `rotateX(${i * 30}deg) rotateY(${i % 2 === 0 ? 0 : 180}deg) translateZ(15px)`;
face.textContent = i+1;
container.appendChild(face);
}
container.style.transform = 'rotateX(20deg) rotateY(20deg)';
}
else if (sides === 20) {
// Icosahedron (20 triangular faces)
for (let i = 0; i < 20; i++) {
const face = document.createElement('div');
face.className = 'dice-face';
face.style.backgroundColor = color;
face.style.clipPath = 'polygon(50% 0%, 0% 100%, 100% 100%)';
face.style.transform = `rotateX(${i * 72}deg) rotateY(${i % 2 === 0 ? 0 : 180}deg) translateZ(15px)`;
face.textContent = i+1;
container.appendChild(face);
}
container.style.transform = 'rotateX(20deg) rotateY(20deg)';
}
// Add rotation animation
setInterval(() => {
const currentTransform = container.style.transform;
const matches = currentTransform.match(/rotateX\((\d+)deg\) rotateY\((\d+)deg\)/);
if (matches) {
const x = parseInt(matches[1]);
const y = parseInt(matches[2]);
container.style.transform = `rotateX(${x}deg) rotateY(${y + 2}deg)`;
}
}, 100);
}
// Set up dice selection
function setupDiceSelection() {
const diceSelectors = document.querySelectorAll('.dice-selector');
diceSelectors.forEach(selector => {
selector.addEventListener('click', function() {
// Remove active class from all selectors
diceSelectors.forEach(s => s.classList.remove('active'));
// Add active class to clicked selector
this.classList.add('active');
// Update selected sides
selectedSides = parseInt(this.getAttribute('data-sides'));
});
});
// Activate D20 by default
document.querySelector('.dice-selector[data-sides="20"]').classList.add('active');
}
// Add decorative elements to the scene
function addDecorativeElements() {
// Add some floating runes
const runeGeometry = new THREE.TetrahedronGeometry(0.3);
const runeMaterial = new THREE.MeshBasicMaterial({
color: 0xd4af37,
transparent: true,
opacity: 0.3
});
for (let i = 0; i < 10; i++) {
const rune = new THREE.Mesh(runeGeometry, runeMaterial);
rune.position.set(
Math.random() * 10 - 5,
Math.random() * 5,
Math.random() * 10 - 5
);
scene.add(rune);
}
}
// Handle window resize
function onWindowResize() {
camera.aspect = document.getElementById('canvas-container').clientWidth / document.getElementById('canvas-container').clientHeight;
camera.updateProjectionMatrix();
renderer.setSize(document.getElementById('canvas-container').clientWidth, document.getElementById('canvas-container').clientHeight);
}
// Animation loop
function animate() {
requestAnimationFrame(animate);
// Update physics
if (world) {
world.step(1/60);
}
// Update dice meshes to match physics bodies
for (let i = 0; i < diceObjects.length; i++) {
if (diceObjects[i] && diceMeshes[i]) {
diceMeshes[i].position.copy(diceObjects[i].position);
diceMeshes[i].quaternion.copy(diceObjects[i].quaternion);
}
}
if (renderer && scene && camera) {
renderer.render(scene, camera);
}
}
// Create dice geometry based on number of sides
function createDiceGeometry(sides) {
let geometry;
switch(sides) {
case 4:
geometry = new THREE.TetrahedronGeometry(1);
break;
case 6:
geometry = new THREE.BoxGeometry(1.5, 1.5, 1.5);
break;
case 8:
geometry = new THREE.OctahedronGeometry(1);
break;
case 10:
geometry = new THREE.DodecahedronGeometry(1);
break;
case 12:
geometry = new THREE.DodecahedronGeometry(1);
break;
case 20:
geometry = new THREE.IcosahedronGeometry(1);
break;
default:
geometry = new THREE.BoxGeometry(1.5, 1.5, 1.5);
}
return geometry;
}
// Create dice physics body based on number of sides
function createDiceBody(sides, position) {
let shape;
switch(sides) {
case 4:
shape = new CANNON.ConvexPolyhedron({
vertices: [
new CANNON.Vec3(1, 1, 1),
new CANNON.Vec3(-1, -1, 1),
new CANNON.Vec3(-1, 1, -1),
new CANNON.Vec3(1, -1, -1)
],
faces: [
[0, 1, 2],
[0, 1, 3],
[0, 2, 3],
[1, 2, 3]
]
});
break;
case 6:
shape = new CANNON.Box(new CANNON.Vec3(0.75, 0.75, 0.75));
break;
case 8:
shape = new CANNON.ConvexPolyhedron({
vertices: [
new CANNON.Vec3(1, 0, 0),
new CANNON.Vec3(-1, 0, 0),
new CANNON.Vec3(0, 1, 0),
new CANNON.Vec3(0, -1, 0),
new CANNON.Vec3(0, 0, 1),
new CANNON.Vec3(0, 0, -1)
],
faces: [
[0, 2, 4], [0, 4, 3], [0, 3, 5], [0, 5, 2],
[1, 2, 5], [1, 5, 3], [1, 3, 4], [1, 4, 2]
]
});
break;
case 10:
case 12:
case 20:
// For simplicity, we'll use a sphere for these
shape = new CANNON.Sphere(1);
break;
default:
shape = new CANNON.Box(new CANNON.Vec3(0.75, 0.75, 0.75));
}
const body = new CANNON.Body({
mass: 1,
shape: shape,
position: position,
linearDamping: 0.1,
angularDamping: 0.1
});
return body;
}
// Create dice material
function createDiceMaterial(sides) {
let color;
switch(sides) {
case 4:
color = 0x8b5a2b; // Brown
break;
case 6:
color = 0x9b111e; // Red
break;
case 8:
color = 0x005b96; // Blue
break;
case 10:
color = 0x5e8c31; // Green
break;
case 12:
color = 0x7d26cd; // Purple
break;
case 20:
color = 0xd4af37; // Gold
break;
default:
color = 0xffffff; // White
}
return new THREE.MeshStandardMaterial({
color: color,
roughness: 0.3,
metalness: 0.5,
emissive: color,
emissiveIntensity: 0.1
});
}
// Roll the dice
function rollDice() {
if (isRolling) return;
isRolling = true;
// Clear previous dice
clearDice();
// Get quantity
const quantity = parseInt(document.getElementById('diceQuantity').value) || 1;
const modifier = parseInt(document.getElementById('diceModifier').value) || 0;
// Hide result display
document.getElementById('resultDisplay').classList.add('hidden');
document.getElementById('criticalText').classList.add('hidden');
// Create new dice
for (let i = 0; i < quantity; i++) {
// Position the dice in a line above the ground
const x = (i - (quantity - 1) / 2) * 2;
const position = new CANNON.Vec3(x, 5, 0);
// Create physics body
const body = createDiceBody(selectedSides, position);
// Add some random force to make it roll
body.velocity.set(
Math.random() * 5 - 2.5,
Math.random() * 2,
Math.random() * 5 - 2.5
);
body.angularVelocity.set(
Math.random() * 5,
Math.random() * 5,
Math.random() * 5
);
world.addBody(body);
diceObjects.push(body);
// Create visual mesh
const geometry = createDiceGeometry(selectedSides);
const material = createDiceMaterial(selectedSides);
const mesh = new THREE.Mesh(geometry, material);
mesh.castShadow = true;
mesh.receiveShadow = true;
scene.add(mesh);
diceMeshes.push(mesh);
}
// Wait for dice to settle
setTimeout(() => {
checkDiceResults(quantity, modifier);
}, 2000);
}
// Clear all dice from scene
function clearDice() {
// Remove physics bodies
diceObjects.forEach(body => {
world.removeBody(body);
});
// Remove meshes
diceMeshes.forEach(mesh => {
scene.remove(mesh);
if (mesh.geometry) mesh.geometry.dispose();
if (mesh.material) mesh.material.dispose();
});
diceObjects = [];
diceMeshes = [];
diceValues = {};
}
// Check dice results after they've settled
function checkDiceResults(quantity, modifier) {
const results = [];
let total = 0;
let isCritical = false;
// Generate random results (since we can't actually detect dice faces in this simplified version)
for (let i = 0; i < quantity; i++) {
const value = Math.floor(Math.random() * selectedSides) + 1;
results.push(value);
total += value;
// Check for critical (20 on d20)
if (selectedSides === 20 && value === 20) {
isCritical = true;
}
}
// Add modifier
total += modifier;
// Display results
displayResults(results, total, modifier, isCritical);
// Add to history
addToHistory(results, total, modifier);
isRolling = false;
}
// Display the results
function displayResults(results, total, modifier, isCritical) {
const resultDisplay = document.getElementById('resultDisplay');
const totalResult = document.getElementById('totalResult');
const individualResults = document.getElementById('individualResults');
const criticalText = document.getElementById('criticalText');
// Show total
totalResult.textContent = total;
// Show individual results
let individualText = `Rolls: ${results.join(', ')}`;
if (modifier !== 0) {
individualText += ` + ${modifier}`;
}
individualResults.textContent = individualText;
// Show critical if applicable
if (isCritical) {
criticalText.classList.remove('hidden');
}
// Show the display
resultDisplay.classList.remove('hidden');
}
// Add roll to history
function addToHistory(results, total, modifier) {
const historyContainer = document.getElementById('rollHistory');
const historyItem = document.createElement('div');
historyItem.className = 'roll-history-item p-3 bg-gray-800 rounded';
let historyText = `D${selectedSides}: ${results.join(' + ')}`;
if (modifier !== 0) {
historyText += ` + ${modifier}`;
}
historyText += ` = ${total}`;
historyItem.textContent = historyText;
historyContainer.insertBefore(historyItem, historyContainer.firstChild);
// Limit history to 10 items
if (historyContainer.children.length > 10) {
historyContainer.removeChild(historyContainer.lastChild);
}
}
// Initialize when DOM is loaded
document.addEventListener('DOMContentLoaded', init);
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=dricampbell/d-d-dice" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>