Robot Game
My wife and I wrote a game for the Commodore VIC-20 in 1982. I reimplemented it in 15 minutes on my iPhone
Here’s my input:
I'd like to work with you to develop a web based children's application. I want the application to be a pure client side browser based application that plays a game for first grade students. It will be used to practice addition and subtraction problems for single digit numbers. As the game is played I'd like all correct answers to add a part to a robot, so that over time as the child answers correctly there will eventually be a complete picture of the robot. There will be no winners or losers in the game. It's intended to keep the child engaged as they work to create the robot.
The AI responded:
“A client-side math practice game for first graders that builds a robot piece-by-piece as children correctly solve addition and subtraction problems
I’ll include the following features:
Generate random single-digit addition and subtraction problems
Interactive answer input with number buttons for easy selection
Visual robot that builds progressively with each correct answer (head, body, arms, legs, features)
Positive feedback animations and encouraging messages for correct answers
Gentle redirection for incorrect answers without negative messaging
Colorful, child-friendly interface with large buttons and clear text
Robot completion celebration animation when all parts are assembled
Reset button to start building a new robot after completion”
Here’s the architecture that the AI built:
Overview
A pure client-side browser-based math practice game for first-grade students. Children solve single-digit addition and subtraction problems, and each correct answer adds a part to an SVG robot. After 10 correct answers, the robot is complete with a celebration animation.
Recent Changes
2026-02-18: Initial implementation of the game with SVG robot, math problem generation, answer buttons, feedback animations, and completion celebration.
Project Architecture
Frontend-only game logic: All game state managed in React with useState hooks
No database needed: Pure client-side application
SVG Robot: 10 progressively revealed parts (body, head, arms, legs, eyes, mouth, antenna)
Framer Motion: Used for spring animations on robot part reveals and feedback messages
Key Files
client/src/pages/game.tsx- Main game page with math logic, answer handling, and UIclient/src/components/Robot.tsx- SVG robot component with 10 animated partsclient/src/App.tsx- App entry point routing to the gameclient/src/index.css- Theme colors (playful, child-friendly palette)
Game Flow
Child sees a math problem (addition or subtraction with single digits)
Five answer choices displayed as large buttons
Correct answer: robot part added, encouraging message shown
Incorrect answer: gentle “try again” message, no penalty
After 10 correct answers: robot complete with sparkle celebration
“Build Another Robot” button to restart
User Preferences
Child-friendly, colorful design
No winners/losers concept
Purely engaging practice tool
Here’s the game source code:
import { motion, AnimatePresence } from "framer-motion";
const ROBOT_COLORS = {
body: "#6C63FF",
bodyDark: "#5A52D5",
head: "#7B73FF",
headDark: "#6960E0",
arms: "#FF6B9D",
armsDark: "#E05585",
legs: "#4ECDC4",
legsDark: "#3BB5AD",
eyes: "#FFFFFF",
eyePupil: "#2D2B55",
mouth: "#FFD93D",
antenna: "#FF6B6B",
antennaBall: "#FFD93D",
bolt: "#FFD93D",
panel: "#8B83FF",
};
const partAnimation = {
initial: { scale: 0, opacity: 0 },
animate: { scale: 1, opacity: 1 },
transition: { type: "spring", stiffness: 300, damping: 15, duration: 0.5 },
};
const bounceAnimation = {
animate: {
y: [0, -6, 0],
transition: {
duration: 2,
repeat: Infinity,
ease: "easeInOut",
},
},
};
interface RobotProps {
partsRevealed: number;
isComplete: boolean;
}
export default function Robot({ partsRevealed, isComplete }: RobotProps) {
return (
<div className="flex items-center justify-center" data-testid="robot-container">
<motion.svg
viewBox="0 0 280 380"
className="w-full max-w-[280px] h-auto"
{...(isComplete ? bounceAnimation : {})}
>
<defs>
<filter id="glow">
<feGaussianBlur stdDeviation="3" result="coloredBlur" />
<feMerge>
<feMergeNode in="coloredBlur" />
<feMergeNode in="SourceGraphic" />
</feMerge>
</filter>
<linearGradient id="bodyGrad" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stopColor={ROBOT_COLORS.body} />
<stop offset="100%" stopColor={ROBOT_COLORS.bodyDark} />
</linearGradient>
<linearGradient id="headGrad" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stopColor={ROBOT_COLORS.head} />
<stop offset="100%" stopColor={ROBOT_COLORS.headDark} />
</linearGradient>
<linearGradient id="armGrad" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stopColor={ROBOT_COLORS.arms} />
<stop offset="100%" stopColor={ROBOT_COLORS.armsDark} />
</linearGradient>
<linearGradient id="legGrad" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stopColor={ROBOT_COLORS.legs} />
<stop offset="100%" stopColor={ROBOT_COLORS.legsDark} />
</linearGradient>
</defs>
{partsRevealed < 1 && (
<g opacity="0.08">
<rect x="80" y="120" width="120" height="130" rx="12" fill="#888" />
<rect x="95" y="50" width="90" height="80" rx="14" fill="#888" />
<rect x="25" y="140" width="45" height="90" rx="10" fill="#888" />
<rect x="210" y="140" width="45" height="90" rx="10" fill="#888" />
<rect x="90" y="260" width="35" height="90" rx="10" fill="#888" />
<rect x="155" y="260" width="35" height="90" rx="10" fill="#888" />
<circle cx="120" cy="80" r="12" fill="#888" />
<circle cx="160" cy="80" r="12" fill="#888" />
<rect x="115" y="105" width="50" height="8" rx="4" fill="#888" />
<line x1="140" y1="20" x2="140" y2="50" stroke="#888" strokeWidth="4" />
<circle cx="140" cy="16" r="8" fill="#888" />
</g>
)}
<AnimatePresence>
{partsRevealed >= 1 && (
<motion.g key="body" {...partAnimation}>
<rect x="80" y="120" width="120" height="130" rx="12" fill="url(#bodyGrad)" />
<rect x="110" y="145" width="60" height="40" rx="6" fill={ROBOT_COLORS.panel} opacity="0.5" />
<circle cx="125" cy="210" r="5" fill={ROBOT_COLORS.bolt} />
<circle cx="155" cy="210" r="5" fill={ROBOT_COLORS.bolt} />
<rect x="130" y="230" width="20" height="8" rx="4" fill={ROBOT_COLORS.bolt} opacity="0.6" />
</motion.g>
)}
{partsRevealed >= 2 && (
<motion.g key="head" {...partAnimation}>
<rect x="95" y="50" width="90" height="80" rx="14" fill="url(#headGrad)" />
<rect x="105" y="55" width="70" height="10" rx="5" fill={ROBOT_COLORS.panel} opacity="0.3" />
</motion.g>
)}
{partsRevealed >= 3 && (
<motion.g key="left-arm" {...partAnimation}>
<rect x="25" y="140" width="45" height="90" rx="10" fill="url(#armGrad)" />
<circle cx="47" cy="155" r="6" fill={ROBOT_COLORS.bolt} />
<rect x="35" y="200" width="25" height="20" rx="8" fill={ROBOT_COLORS.armsDark} />
</motion.g>
)}
{partsRevealed >= 4 && (
<motion.g key="right-arm" {...partAnimation}>
<rect x="210" y="140" width="45" height="90" rx="10" fill="url(#armGrad)" />
<circle cx="232" cy="155" r="6" fill={ROBOT_COLORS.bolt} />
<rect x="220" y="200" width="25" height="20" rx="8" fill={ROBOT_COLORS.armsDark} />
</motion.g>
)}
{partsRevealed >= 5 && (
<motion.g key="left-leg" {...partAnimation}>
<rect x="90" y="260" width="35" height="90" rx="10" fill="url(#legGrad)" />
<rect x="85" y="335" width="45" height="18" rx="9" fill={ROBOT_COLORS.legsDark} />
</motion.g>
)}
{partsRevealed >= 6 && (
<motion.g key="right-leg" {...partAnimation}>
<rect x="155" y="260" width="35" height="90" rx="10" fill="url(#legGrad)" />
<rect x="150" y="335" width="45" height="18" rx="9" fill={ROBOT_COLORS.legsDark} />
</motion.g>
)}
{partsRevealed >= 7 && (
<motion.g key="left-eye" {...partAnimation}>
<circle cx="120" cy="82" r="14" fill={ROBOT_COLORS.eyes} />
<circle cx="122" cy="83" r="7" fill={ROBOT_COLORS.eyePupil} />
<circle cx="125" cy="80" r="3" fill="white" />
</motion.g>
)}
{partsRevealed >= 8 && (
<motion.g key="right-eye" {...partAnimation}>
<circle cx="160" cy="82" r="14" fill={ROBOT_COLORS.eyes} />
<circle cx="162" cy="83" r="7" fill={ROBOT_COLORS.eyePupil} />
<circle cx="165" cy="80" r="3" fill="white" />
</motion.g>
)}
{partsRevealed >= 9 && (
<motion.g key="mouth" {...partAnimation}>
<path
d="M 118 108 Q 140 125 162 108"
stroke={ROBOT_COLORS.mouth}
strokeWidth="5"
strokeLinecap="round"
fill="none"
/>
</motion.g>
)}
{partsRevealed >= 10 && (
<motion.g key="antenna" {...partAnimation}>
<line
x1="140"
y1="22"
x2="140"
y2="50"
stroke={ROBOT_COLORS.antenna}
strokeWidth="5"
strokeLinecap="round"
/>
<motion.circle
cx="140"
cy="16"
r="9"
fill={ROBOT_COLORS.antennaBall}
filter={isComplete ? "url(#glow)" : undefined}
animate={isComplete ? { scale: [1, 1.3, 1], opacity: [1, 0.8, 1] } : {}}
transition={isComplete ? { duration: 1, repeat: Infinity } : {}}
/>
</motion.g>
)}
</AnimatePresence>
{isComplete && (
<motion.g
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.3 }}
>
{[...Array(8)].map((_, i) => {
const angle = (i * 45 * Math.PI) / 180;
const x = 140 + Math.cos(angle) * 160;
const y = 180 + Math.sin(angle) * 160;
const colors = ["#FFD93D", "#FF6B9D", "#6C63FF", "#4ECDC4", "#FF6B6B"];
return (
<motion.circle
key={`sparkle-${i}`}
cx={x}
cy={y}
r="5"
fill={colors[i % colors.length]}
animate={{
scale: [0, 1.5, 0],
opacity: [0, 1, 0],
}}
transition={{
duration: 1.5,
repeat: Infinity,
delay: i * 0.2,
ease: "easeInOut",
}}
/>
);
})}
</motion.g>
)}
</motion.svg>
</div>
);
}
export const TOTAL_PARTS = 10;
export const PART_NAMES = [
"Body",
"Head",
"Left Arm",
"Right Arm",
"Left Leg",
"Right Leg",
"Left Eye",
"Right Eye",
"Smile",
"Antenna",
];

