Josh Bruce Online

SpinClick - Russian Roulette Simulation Game Documentation

Overview

“SpinClick” is an interactive Russian Roulette simulation game that recreates the tension and mechanics of the classic game through precise physics modeling and engaging visual design. The application features a six-chamber revolver system with scroll-controlled spinning mechanics, realistic momentum physics, and explosive visual effects, providing an authentic gaming experience while maintaining educational value about probability and risk.

Technical Architecture

Core Technologies

File Structure

spinclick/
└── index.html    # Complete Russian Roulette simulation (844 lines)

Game Mechanics System

Chamber Management

let chambers = [];          /* Array to hold bullet assignments (true = bullet) */
let firedChambers = [];     /* Array to track fired chambers (true = fired) */

function initializeChambers() {
  chambers = [false, false, false, false, false, false];
  firedChambers = [false, false, false, false, false, false];
  
  // Place one bullet in random chamber
  const bulletChamber = Math.floor(Math.random() * 6);
  chambers[bulletChamber] = true;
}

Chamber Alignment System

function getAlignedChamber() {
  const normalizedRotation = ((currentRotation % 360) + 360) % 360;
  
  // When rotation = 0°, circle 0 is at top
  // When rotation = 60°, circle 5 moves to top (rotating clockwise)
  const alignedChamber = Math.round((6 - normalizedRotation / 60)) % 6;
  
  return alignedChamber;
}

Probability and Randomization

Physics Engine Implementation

Scroll-Controlled Spinning

document.addEventListener('wheel', function(event) {
  event.preventDefault();
  
  const scrollDelta = event.deltaY;
  const scrollSpeed = Math.abs(scrollDelta) / 10;
  const direction = scrollDelta > 0 ? 1 : -1;
  
  // Add to rotation velocity (proportional to scroll speed)
  rotationVelocity += scrollSpeed * direction * 2;
  
  // Cap maximum velocity
  rotationVelocity = Math.max(-50, Math.min(50, rotationVelocity));
});

Momentum and Friction System

function animateRotation() {
  const timeSinceLastScroll = Date.now() - lastScrollTime;
  
  // Apply friction/momentum decay
  if (timeSinceLastScroll > 50) {
    const decayFactor = 0.98;
    rotationVelocity *= decayFactor;
    
    // Magnetic alignment for settling
    const currentAngle = ((currentRotation % 360) + 360) % 360;
    let targetPosition = findNearestUnfiredPosition(currentAngle);
    let angleDifference = targetPosition - currentAngle;
    
    // Handle wraparound
    if (angleDifference > 180) angleDifference -= 360;
    if (angleDifference < -180) angleDifference += 360;
    
    // Apply gentle magnetic force when settling
    if (Math.abs(rotationVelocity) < 3 && Math.abs(angleDifference) < 15) {
      const magneticForce = angleDifference * 0.05;
      rotationVelocity += magneticForce;
    }
  }
}

Alignment Snapping Algorithm

function findNearestUnfiredPosition(currentAngle) {
  const unfiredPositions = [];
  
  // Get angles for all unfired chambers
  for (let i = 0; i < 6; i++) {
    if (!firedChambers[i]) {
      const chamberPosition = ((6 - i) * 60) % 360;
      unfiredPositions.push(chamberPosition);
    }
  }
  
  // Find closest unfired position
  let bestPosition = unfiredPositions[0];
  let bestDistance = getAngleDistance(currentAngle, bestPosition);
  
  for (const position of unfiredPositions) {
    const distance = getAngleDistance(currentAngle, position);
    if (distance < bestDistance) {
      bestDistance = distance;
      bestPosition = position;
    }
  }
  
  return bestPosition;
}

Visual Design System

Hexagonal Chamber Layout

/* Circle positions in perfect hexagon around rotation center */
/* Radius = 12vh (distance from rotation center to stationary barrel) */

/* Circle 1: -90° (270°) - top position, aligns with stationary barrel */
.moving-circle:nth-child(1) {
  top: calc(50% - 12vh - 30px);
  left: calc(50% - 30px);
}

/* Circle 2: -30° (330°) - top-right */
.moving-circle:nth-child(2) {
  top: calc(50% - 6vh - 30px);
  left: calc(50% + 10.39vh - 30px);
}

/* Continues for all 6 chambers at 60° intervals */

Game State Visual Indicators

.moving-circle.fired {
  background-color: #cccccc;
  opacity: 0.6;
}

.moving-circle.clicked {
  background-color: #4CAF50;
}

.click-zone.active {
  opacity: 0.5;
  border-color: #00ff00;
}

Responsive Layout System

.game-container {
  position: relative;
  width: 100vh;
  height: 100vh;
}

body {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100vh;
  overflow: hidden;
}

Explosion Visual Effects System

Main Blast Animation

.explosion::before {
  content: '';
  background: radial-gradient(circle, #ffffff, #ffff00, #ff6600, #ff0000, #800000);
  border-radius: 50%;
  animation: mainBlast 0.6s ease-out forwards;
}

@keyframes mainBlast {
  0% {
    width: 0;
    height: 0;
    opacity: 1;
  }
  30% {
    width: 200px;
    height: 200px;
    opacity: 1;
  }
  100% {
    width: 800px;
    height: 800px;
    opacity: 0;
  }
}

Particle System Implementation

function createExplosion() {
  // Create random particles
  for (let i = 0; i < 20; i++) {
    const particle = document.createElement('div');
    particle.className = 'explosion-particle';
    
    // Random position around explosion center
    const angle = (Math.PI * 2 * i) / 20;
    const distance = 50 + Math.random() * 150;
    const x = Math.cos(angle) * distance;
    const y = Math.sin(angle) * distance;
    
    particle.style.left = `calc(50% + ${x}px)`;
    particle.style.top = `calc(50% + ${y}px)`;
    particle.style.animationDelay = `${Math.random() * 0.2}s`;
    
    document.querySelector('.game-container').appendChild(particle);
    particle.classList.add('animate');
  }
}

Fragment Effects

// Create fragments
for (let i = 0; i < 8; i++) {
  const fragment = document.createElement('div');
  fragment.className = 'explosion-fragment';
  
  const angle = (Math.PI * 2 * i) / 8;
  const distance = 100 + Math.random() * 200;
  const x = Math.cos(angle) * distance;
  const y = Math.sin(angle) * distance;
  
  fragment.style.setProperty('--fragment-x', `${x}px`);
  fragment.style.setProperty('--fragment-y', `${y}px`);
}

Game State Management

State Machine Implementation

let gameState = 'spinning'; // 'spinning', 'waiting', 'survived', 'gameOver'

function updateGameStatus() {
  switch(gameState) {
    case 'spinning':
      gameStatus.textContent = 'Spinning chamber...';
      break;
    case 'waiting':
      gameStatus.textContent = '';
      clickZone.classList.add('active');
      break;
    case 'survived':
      // Keep the survival message
      break;
    case 'gameOver':
      gameStatus.textContent = '';
      break;
  }
}

Trigger Pull Mechanics

function pullTrigger() {
  if (gameState !== 'waiting') return;
  
  roundsPlayed++;
  
  const alignedChamber = getAlignedChamber();
  const isLoaded = chambers[alignedChamber];
  
  // Mark this chamber as fired and update visuals
  firedChambers[alignedChamber] = true;
  updateChamberVisuals();
  
  if (isLoaded) {
    // Game over - explosion!
    gameState = 'gameOver';
    createExplosion();
    
    setTimeout(() => {
      gameOver.classList.add('visible');
    }, 400);
  } else {
    // Safe chamber - continue playing
    luckyMessage.classList.add('animate');
    gameStatus.textContent = `Safe! Round ${roundsPlayed} survived. Scroll to spin again...`;
    gameState = 'survived';
  }
}

Input Control System

Multi-Input Support

// Scroll wheel control
document.addEventListener('wheel', function(event) {
  event.preventDefault();
  const scrollDelta = event.deltaY;
  rotationVelocity += scrollSpeed * direction * 2;
});

// Mouse click trigger
document.addEventListener('click', function(event) {
  event.preventDefault();
  pullTrigger();
});

// Keyboard controls
document.addEventListener('keydown', function(event) {
  if (event.code === 'Space') {
    event.preventDefault();
    pullTrigger();
  }
});

Accessibility Features

Advanced Animation System

Smooth Rotation Physics

let currentRotation = 0;
let rotationVelocity = 0;
let lastScrollTime = 0;

function animateRotation() {
  // Update rotation only if there's velocity
  if (rotationVelocity !== 0) {
    currentRotation += rotationVelocity;
    container.style.transform = `translate(-50%, -50%) rotate(${currentRotation}deg)`;
  }
  
  requestAnimationFrame(animateRotation);
}

Lucky Message Animation

.lucky-message.animate {
  animation: luckyFlash 3s ease-out forwards;
}

@keyframes luckyFlash {
  0% {
    opacity: 0;
    transform: translateX(-50%) scale(0.8);
  }
  10% {
    opacity: 1;
    transform: translateX(-50%) scale(1.2);
  }
  20% {
    opacity: 1;
    transform: translateX(-50%) scale(1);
  }
  80% {
    opacity: 1;
    transform: translateX(-50%) scale(1);
  }
  100% {
    opacity: 0;
    transform: translateX(-50%) scale(0.9);
  }
}

Performance Optimization

Efficient Animation Loop

DOM Optimization

// Clean up any remaining particles/fragments
document.querySelectorAll('.explosion-particle, .explosion-fragment').forEach(el => {
  if (el.parentNode) {
    el.parentNode.removeChild(el);
  }
});

Educational Value and Psychology

Risk Assessment Learning

  1. Probability Demonstration: Visual representation of decreasing risk
  2. Decision Making: Players must choose when to continue or stop
  3. Consequence Visualization: Immediate feedback for risky choices
  4. Statistical Understanding: Experience with randomness and probability

Psychological Elements

  1. Tension Building: Scroll mechanics create anticipation
  2. Risk vs. Reward: Each round survived increases achievement
  3. Visual Feedback: Color changes and animations provide immediate response
  4. Momentum Physics: Realistic spinning creates authentic experience

Game Design Principles

User Experience Design

Accessibility Considerations

Future Enhancement Opportunities

Gameplay Extensions

  1. Multiple Bullets: Increase difficulty with multiple loaded chambers
  2. Timer Mode: Add time pressure for increased tension
  3. Multiplayer: Turn-based gameplay with friends
  4. Difficulty Levels: Adjustable chamber count and bullet placement
  5. Statistics Tracking: Personal best streaks and survival rates

Visual Enhancements

  1. 3D Graphics: WebGL implementation for realistic revolver model
  2. Sound Effects: Audio feedback for spinning, clicking, and explosions
  3. Particle Improvements: More realistic explosion physics
  4. Theme Variations: Different visual themes and environments
  5. Mobile Optimization: Touch-friendly controls for mobile devices

Technical Improvements

  1. Save System: Persistent game statistics and achievements
  2. Online Leaderboards: Global high score tracking
  3. Replay System: Record and playback game sessions
  4. Performance Analytics: FPS monitoring and optimization
  5. Progressive Web App: Offline functionality and installation

Code Quality Assessment

Strengths

Areas for Enhancement

Conclusion

SpinClick successfully recreates the psychological tension and mechanical precision of Russian Roulette through sophisticated physics modeling and engaging visual design. The scroll-controlled spinning mechanism provides an intuitive and realistic user experience while the probability-based gameplay offers educational value about risk assessment and statistical thinking.

Technical Rating: 9.1/10

The application demonstrates how classic concepts can be transformed into engaging digital experiences while maintaining both entertainment value and educational merit through precise technical implementation and thoughtful user experience design.