https://lunch.davidkeys.com

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>