This project began using Python as the language. Further down the page you will see that the code was transformed so that it could be displayed as a web page.
import tkinter as tk
import random
import math
import time
class SpinningWheel:
def __init__(self, root, items):
self.root = root
self.root.title("Spinning Wheel Picker")
self.items = items
self.angle = 0
self.spinning = False
self.canvas_size = 400
self.canvas = tk.Canvas(root, width=self.canvas_size, height=self.canvas_size)
self.canvas.pack()
self.result_label = tk.Label(root, text="", font=("Arial", 16))
self.result_label.pack(pady=10)
self.spin_button = tk.Button(root, text="Spin", command=self.start_spin)
self.spin_button.pack(pady=10)
self.draw_wheel()
def draw_wheel(self):
self.canvas.delete("all")
num_items = len(self.items)
angle_per_item = 360 / num_items
center = self.canvas_size // 2
radius = center - 10
for i, item in enumerate(self.items):
start_angle = self.angle + i * angle_per_item
color = f"#{random.randint(0, 0xFFFFFF):06x}"
self.canvas.create_arc(
10, 10, self.canvas_size-10, self.canvas_size-10,
start=start_angle, extent=angle_per_item,
fill=color
)
# Text placement
mid_angle = math.radians(start_angle + angle_per_item / 2)
x = center + (radius / 2) * math.cos(mid_angle)
y = center - (radius / 2) * math.sin(mid_angle)
self.canvas.create_text(x, y, text=item, angle=start_angle + angle_per_item / 2)
# Draw pointer
self.canvas.create_polygon(
center-10, 5,
center+10, 5,
center, 25,
fill="black"
)
def start_spin(self):
if not self.spinning:
self.spinning = True
self.result_label.config(text="")
self.spin_wheel()
def spin_wheel(self):
if self.spinning:
self.angle += random.randint(10, 30)
self.angle %= 360
self.draw_wheel()
# Slow down effect
if random.random() < 0.05:
self.spinning = False
self.show_result()
return
self.root.after(50, self.spin_wheel)
def show_result(self):
num_items = len(self.items)
angle_per_item = 360 / num_items
# Adjust for pointer at top (90 degrees)
adjusted_angle = (360 - self.angle + 90) % 360
index = int(adjusted_angle // angle_per_item)
result = self.items[index]
self.result_label.config(text=f"Selected: {result}")
# Example usage
if __name__ == "__main__":
root = tk.Tk()
items = [
"Chilis",
"Chinese",
"Bar",
"Poke",
"Village Inn",
"Burgatory"
]
app = SpinningWheel(root, items)
root.mainloop()
The code evolved into a combination of client-side JavaScript, working together with HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Lunch Picker Wheel</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
body {
font-family: Arial, sans-serif;
text-align: center;
background: #f4f6f8;
margin: 0;
padding: 15px;
}
h1 {
font-size: clamp(20px, 5vw, 32px);
}
#wheel {
width: 90vw;
max-width: 500px;
height: auto;
display: block;
margin: 20px auto;
}
button {
margin-top: 20px;
padding: 12px 25px;
font-size: clamp(14px, 3vw, 18px);
border: none;
border-radius: 6px;
background: #007BFF;
color: white;
cursor: pointer;
}
button:disabled {
background: #999;
}
#result {
margin-top: 20px;
font-size: clamp(18px, 4vw, 26px);
font-weight: bold;
}
</style>
</head>
<body>
<h1>🍽️ Where to go to lunch?</h1>
<canvas id="wheel"></canvas>
<button id="spinBtn">Spin the Wheel</button>
<div id="result"></div>
<script>
const items = [
"Chilis","Something New","Caliente","Zen Ramen and Poke",
"Italian Village","Burgatory","Chinese",
"Ritters Diner","Uncle Sams","Indian Buffet"
];
const weights = [1,1,1,1,1,1,1,1,1,1];
const canvas = document.getElementById("wheel");
const ctx = canvas.getContext("2d");
const resultDiv = document.getElementById("result");
const spinBtn = document.getElementById("spinBtn");
let size, center, radius;
let angle = 0;
let spinning = false;
let lastTickIndex = -1;
const arc = 2 * Math.PI / items.length;
const colors = items.map((_, i) =>
`hsl(${(i * 360) / items.length}, 70%, 60%)`
);
// Resize canvas responsively
function resizeCanvas() {
const displaySize = Math.min(window.innerWidth * 0.9, 500);
canvas.width = displaySize;
canvas.height = displaySize;
size = displaySize;
center = size / 2;
radius = size * 0.45;
drawWheel();
}
window.addEventListener("resize", resizeCanvas);
// Draw wheel
function drawWheel() {
ctx.clearRect(0, 0, size, size);
items.forEach((item, i) => {
const start = angle + i * arc;
ctx.beginPath();
ctx.moveTo(center, center);
ctx.arc(center, center, radius, start, start + arc);
ctx.fillStyle = colors[i];
ctx.fill();
ctx.save();
ctx.translate(center, center);
ctx.rotate(start + arc / 2);
ctx.textAlign = "right";
ctx.fillStyle = "#000";
ctx.font = `${Math.max(size * 0.035, 12)}px Arial`;
ctx.fillText(item, radius - 10, 5);
ctx.restore();
});
// Pointer
ctx.fillStyle = "#000";
ctx.beginPath();
ctx.moveTo(center - size * 0.025, size * 0.025);
ctx.lineTo(center + size * 0.025, size * 0.025);
ctx.lineTo(center, size * 0.1);
ctx.closePath();
ctx.fill();
}
// Sound
const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
function playTick() {
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
osc.frequency.value = 1000;
gain.gain.value = 0.05;
osc.connect(gain);
gain.connect(audioCtx.destination);
osc.start();
osc.stop(audioCtx.currentTime + 0.02);
}
function playWin() {
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
osc.frequency.value = 400;
gain.gain.value = 0.1;
osc.connect(gain);
gain.connect(audioCtx.destination);
osc.start();
osc.frequency.exponentialRampToValueAtTime(800, audioCtx.currentTime + 0.3);
osc.stop(audioCtx.currentTime + 0.3);
}
// Weighted pick
function pickWeightedIndex() {
const total = weights.reduce((a,b)=>a+b,0);
let r = Math.random() * total;
for (let i = 0; i < items.length; i++) {
if (r < weights[i]) return i;
r -= weights[i];
}
return 0;
}
function easeOut(t) {
return 1 - Math.pow(1 - t, 3);
}
// Spin
function spinWheel() {
if (spinning) return;
audioCtx.resume();
spinning = true;
spinBtn.disabled = true;
resultDiv.textContent = "";
lastTickIndex = -1;
const duration = 4000;
const start = performance.now();
const weightedIndex = pickWeightedIndex();
const bias = (weightedIndex / items.length) * 2 * Math.PI;
const spins = Math.random() * 3 + 5;
const finalAngle = angle + spins * 2 * Math.PI + bias;
function animate(now) {
const elapsed = now - start;
const t = Math.min(elapsed / duration, 1);
const eased = easeOut(t);
angle = finalAngle * eased;
const normalized =
((3 * Math.PI / 2 - angle) % (2 * Math.PI) + 2 * Math.PI) % (2 * Math.PI);
const currentIndex = Math.floor(normalized / arc);
if (currentIndex !== lastTickIndex) {
playTick();
lastTickIndex = currentIndex;
}
drawWheel();
if (t < 1) {
requestAnimationFrame(animate);
} else {
angle %= (2 * Math.PI);
spinning = false;
spinBtn.disabled = false;
showResult();
playWin();
}
}
requestAnimationFrame(animate);
}
function showResult() {
const normalized =
((3 * Math.PI / 2 - angle) % (2 * Math.PI) + 2 * Math.PI) % (2 * Math.PI);
const index = Math.floor(normalized / arc);
resultDiv.textContent = `You should go to: ${items[index]}!`;
}
// Init
resizeCanvas();
spinBtn.addEventListener("click", spinWheel);
</script>
</body>
</html>