Three Prompts: Rubiks Cube

AI Assistant

Three Prompts: Rubiks Cube

Project demo

Introduction

The Rubik's Cube has fascinated puzzle enthusiasts since its invention in 1974. This iconic 3×3×3 mechanical puzzle has sold over 450 million units worldwide, making it the best-selling puzzle game and toy in history. In this Three Prompts project, we'll create an interactive, browser-based Rubik's Cube that users can manipulate with both button controls and mouse/touch interactions.

Using React, Three.js, and a specialized Rubik's Cube library, we'll build a fully functional 3D cube visualization that responds to user input. The project demonstrates how quickly we can implement complex 3D interactions in the browser by leveraging existing libraries and React's component model.

The Three Prompts

Here are the exact prompts that were used to create this project:

Prompt 1

In @projects/rubiks-cube make a demo project showing a rubiks cube in the browser than works as expected

Prompt 2

Now make it so the cube can be rotated so you can see the other sides

Prompt 3

Uncaught TypeError: Cannot read properties of null (reading 'removeEventListener')

Building a 3D Rubik's Cube

Setting Up the Project

Our Rubik's Cube demo is built on a Vite + React foundation, which provides fast development and optimized production builds. The project structure is minimal but effective:

projects/rubiks-cube/
├─ package.json – dependencies and scripts
├─ src/
│  ├─ App.jsx – renders our cube component
│  ├─ RubiksCubeDemo.jsx – core implementation
│  └─ main.jsx – React entry point
└─ public/ – static assets

The key dependencies for this project are:

  • React 19 for the UI framework
  • Three.js for 3D rendering capabilities
  • @curtishughes/rubiks-cube package which provides the cube implementation

The Core Component

The heart of our implementation is the RubiksCubeDemo component. Let's examine how it works:

import { useEffect, useRef, useState } from 'react'
import RubiksCube, { materials } from '@curtishughes/rubiks-cube'

export default function RubiksCubeDemo() {
  const canvasRef = useRef(null)
  const [cube, setCube] = useState(null)

  useEffect(() => {
    const canvasEl = canvasRef.current
    if (!canvasEl) return undefined

    // Create cube instance once
    const cubeInstance = new RubiksCube(canvasEl, materials.classic, 150)
    setCube(cubeInstance)

    // Pointer event handlers for rotation...
  }, [])

  // Button handlers and rendering...
}

The component uses React's useRef to access the canvas element and useState to store a reference to the cube instance. The useEffect hook initializes the cube when the component mounts.

Face Rotation Controls

The first set of controls allows users to rotate individual faces of the cube using standard Rubik's Cube notation:

  • F/F' - Front face clockwise/counterclockwise
  • R/R' - Right face clockwise/counterclockwise
  • U/U' - Upper face clockwise/counterclockwise

These controls are implemented through simple button clicks that call the corresponding methods on the cube instance:

function handleMove(move) {
  if (!cube) return
  switch (move) {
    case 'F':
      cube.F()
      break
    case "F'":
      cube.F(false)
      break
    // Other cases...
  }
}

Whole Cube Rotation

The second set of controls enables rotating the entire cube to view it from different angles:

  1. Button Controls: Using the x, y, and z axis rotations (and their counterclockwise variants)
  2. Pointer Drag: Horizontal and vertical dragging to rotate around the y and x axes respectively

The drag implementation is particularly interesting:

const onPointerMove = (e) => {
  if (!isDragging) return
  const dx = e.clientX - startX
  const dy = e.clientY - startY

  // Horizontal drag rotates around Y axis
  if (Math.abs(dx) > 30) {
    if (dx > 0) cubeInstance.y()
    else cubeInstance.y(false)
    startX = e.clientX
  }

  // Vertical drag rotates around X axis
  if (Math.abs(dy) > 30) {
    if (dy > 0) cubeInstance.x()
    else cubeInstance.x(false)
    startY = e.clientY
  }
}

This code tracks pointer movement and triggers rotations when the movement exceeds a threshold (30 pixels), creating a natural and intuitive interaction.

Debugging and Optimization

Fixing the Event Listener Bug

Our third prompt addressed a critical bug in the event listener cleanup:

Uncaught TypeError: Cannot read properties of null (reading 'removeEventListener')

This error occurred because the cleanup function in our useEffect hook was trying to remove event listeners from a canvas element that might no longer exist. The solution was to capture the canvas element reference inside the effect and check for its existence during cleanup:

useEffect(() => {
  const canvasEl = canvasRef.current
  if (!canvasEl) return undefined

  // Setup code...

  return () => {
    if (canvasEl) canvasEl.removeEventListener('pointerdown', onPointerDown)
    window.removeEventListener('pointermove', onPointerMove)
    window.removeEventListener('pointerup', onPointerUp)
  }
}, [])

This pattern is a common solution for React's strict mode, which mounts, unmounts, and remounts components during development to help identify side effects.

Conclusion

This Rubik's Cube project demonstrates how quickly we can create interactive 3D experiences in the browser by leveraging specialized libraries. With just three prompts, we built a fully functional Rubik's Cube that users can manipulate through both button controls and direct pointer interaction.

The project showcases several important web development concepts:

  1. Component-based architecture with React
  2. 3D rendering with Three.js
  3. Event handling for pointer/touch interactions
  4. Proper cleanup of event listeners to prevent memory leaks
  5. Library integration to avoid reinventing complex functionality

While this implementation focuses on core functionality, it could be extended with features like:

  • Cube state persistence
  • Scramble and solve algorithms
  • Custom color schemes
  • Animation speed controls
  • Mobile-optimized controls

Try the live demo and explore the possibilities of 3D web applications!