<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Taco Truck: Perfect Competition Simulator</title>
<script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
const { useState } = React;
// Lucide icons as SVG components
const TrendingUp = () => (
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<polyline points="23 6 13.5 15.5 8.5 10.5 1 18"></polyline>
<polyline points="17 6 23 6 23 12"></polyline>
</svg>
);
const AlertCircle = () => (
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<circle cx="12" cy="12" r="10"></circle>
<line x1="12" y1="8" x2="12" y2="12"></line>
<line x1="12" y1="16" x2="12.01" y2="16"></line>
</svg>
);
const TacoTruckGame = () => {
const [stage, setStage] = useState(0);
const [answers, setAnswers] = useState({});
const [cumulativeProfit, setCumulativeProfit] = useState(0);
const [feedback, setFeedback] = useState(null);
const [gameHistory, setGameHistory] = useState([]);
const VC = 3;
const FC = 5000;
const CAPACITY = 1500;
const stages = [
{
id: 'intro',
title: '📰 Breaking News: The Taco Truck Boom!',
content: (
<div className="space-y-4">
<div className="bg-yellow-50 border-l-4 border-yellow-400 p-4">
<p className="font-semibold text-lg mb-2">"Local taco truck owner reports making $4,000+ profit per month!"</p>
<p className="text-gray-700">"It's the easiest business I've ever run," says Maria Rodriguez. "There's huge demand from office workers and late-night crowds, but not enough trucks to serve everyone. Customers are willing to pay premium prices!"</p>
</div>
<p className="text-lg">You decide to jump into the taco truck business!</p>
</div>
)
},
{
id: 'q1',
title: 'Month 1: Setting Your Price',
content: (
<div className="space-y-4">
<div className="bg-blue-50 p-4 rounded">
<h3 className="font-bold mb-2">Market Information:</h3>
<ul className="space-y-1">
<li>• <strong>Current market price:</strong> $10 per taco</li>
<li>• <strong>Your Variable Cost:</strong> $3 per taco (ingredients, packaging, fuel)</li>
<li>• <strong>Your Fixed Costs:</strong> $5,000/month</li>
<li className="ml-4 text-sm">- Food truck rental: $2,000</li>
<li className="ml-4 text-sm">- Permits & licenses: $500</li>
<li className="ml-4 text-sm">- Insurance: $800</li>
<li className="ml-4 text-sm">- Parking spot rental: $700</li>
<li className="ml-4 text-sm">- Your time/labor (opportunity cost): $1,000</li>
<li>• <strong>Capacity:</strong> Up to 1,500 tacos/month</li>
<li>• <strong>Expected demand at $10:</strong> 1,000 tacos/month</li>
</ul>
<p className="mt-3 text-sm bg-blue-100 p-2 rounded"><strong>Note:</strong> There's high demand but not enough taco trucks in the city! Customers are willing to pay $10 per taco. The maximum number of customers you can capture in your neighborhood is 1,000 tacos per month.</p>
</div>
<div>
<label className="block font-semibold mb-2">What price will you charge per taco?</label>
<input
type="number"
step="0.01"
className="border-2 border-gray-300 rounded px-4 py-2 w-full"
placeholder="Enter price (e.g., 10.00)"
onChange={(e) => setAnswers({...answers, q1Price: parseFloat(e.target.value)})}
/>
</div>
</div>
)
},
{
id: 'q2',
title: 'Month 2: Market Changes',
content: (
<div className="space-y-4">
<div className="bg-orange-50 border-l-4 border-orange-400 p-4">
<p className="font-semibold">News Update:</p>
<p>Dozens of new taco trucks have appeared across the city! It seems many people had the same idea after seeing the news about profitable taco trucks.</p>
</div>
<div>
<label className="block font-semibold mb-2">What happens to the market?</label>
<div className="space-y-2">
{[
{ value: 'A', text: 'Demand increases; Price increases' },
{ value: 'B', text: 'Supply increases; Price increases' },
{ value: 'C', text: 'Supply increases; Price decreases' },
{ value: 'D', text: 'Demand decreases; Price decreases' }
].map(option => (
<label key={option.value} className="flex items-center space-x-2 p-3 border-2 border-gray-200 rounded hover:bg-gray-50 cursor-pointer">
<input
type="radio"
name="q2"
value={option.value}
onChange={(e) => setAnswers({...answers, q2: e.target.value})}
className="w-4 h-4"
/>
<span>{option.value}) {option.text}</span>
</label>
))}
</div>
</div>
</div>
)
},
{
id: 'q3',
title: 'Month 3: Price War',
content: (
<div className="space-y-4">
<div className="bg-red-50 border-l-4 border-red-400 p-4">
<p className="font-semibold mb-2">Market Update:</p>
<p>With so many taco trucks competing, the market price has crashed to <strong>$5.50 per taco</strong>.</p>
<p className="mt-2 text-sm text-gray-700">Remember: Your ATC at 1,000 tacos = $8.00 per taco | Your AVC = $3.00 per taco</p>
</div>
<div>
<label className="block font-semibold mb-2">What will you do?</label>
<div className="space-y-2">
{[
{ value: 'exit', text: 'Exit the market immediately' },
{ value: 'stay', text: 'Continue operating' }
].map(option => (
<label key={option.value} className="flex items-center space-x-2 p-3 border-2 border-gray-200 rounded hover:bg-gray-50 cursor-pointer">
<input
type="radio"
name="q3decision"
value={option.value}
onChange={(e) => setAnswers({...answers, q3Decision: e.target.value})}
className="w-4 h-4"
/>
<span>{option.text}</span>
</label>
))}
</div>
{answers.q3Decision === 'stay' && (
<div className="mt-4 space-y-2">
<div>
<label className="block font-semibold mb-2">What price will you charge?</label>
<input
type="number"
step="0.01"
className="border-2 border-gray-300 rounded px-4 py-2 w-full"
placeholder="Enter price"
onChange={(e) => setAnswers({...answers, q3Price: parseFloat(e.target.value)})}
/>
</div>
<div>
<label className="block font-semibold mb-2">How many tacos will you produce?</label>
<input
type="number"
className="border-2 border-gray-300 rounded px-4 py-2 w-full"
placeholder="Enter quantity (max 1500)"
onChange={(e) => setAnswers({...answers, q3Quantity: parseInt(e.target.value)})}
/>
</div>
</div>
)}
</div>
</div>
)
},
{
id: 'q4',
title: 'Month 4: Shakeout Begins',
content: (
<div className="space-y-4">
<div className="bg-yellow-50 border-l-4 border-yellow-400 p-4">
<p className="font-semibold mb-2">Market Update:</p>
<p>You notice several taco trucks that were nearby are no longer in business. The heated competition has forced some operators to exit the market.</p>
<p className="mt-2">With fewer competitors, the market price has recovered to <strong>$7.00 per taco</strong>.</p>
<p className="mt-2 text-sm text-gray-700">Remember: Your ATC at 1,000 tacos = $8.00 per taco | Your AVC = $3.00 per taco</p>
</div>
<div>
<label className="block font-semibold mb-2">What will you do?</label>
<div className="space-y-2">
{[
{ value: 'exit', text: 'Exit the market' },
{ value: 'stay', text: 'Continue operating' }
].map(option => (
<label key={option.value} className="flex items-center space-x-2 p-3 border-2 border-gray-200 rounded hover:bg-gray-50 cursor-pointer">
<input
type="radio"
name="q4decision"
value={option.value}
onChange={(e) => setAnswers({...answers, q4Decision: e.target.value})}
className="w-4 h-4"
/>
<span>{option.text}</span>
</label>
))}
</div>
</div>
</div>
)
},
{
id: 'q5',
title: 'Month 5: Long-Run Equilibrium',
content: (
<div className="space-y-4">
<div className="bg-green-50 border-l-4 border-green-400 p-4">
<p className="font-semibold mb-2">Market Reaches Equilibrium:</p>
<p>More trucks have exited, and the market has stabilized. The price has returned to <strong>$8.00 per taco</strong>.</p>
<p className="mt-2">At this price, you're covering all your costs including opportunity costs, but making zero economic profit.</p>
</div>
<div className="bg-blue-50 p-4 rounded">
<h3 className="font-bold mb-2">Month 5 Financial Breakdown (at equilibrium price $8):</h3>
<div className="space-y-1">
<p>• Quantity sold: 1,000 tacos</p>
<p>• Revenue: $8 × 1,000 = <strong>$8,000</strong></p>
<p>• Variable Costs: $3 × 1,000 = $3,000</p>
<p>• Fixed Costs: $5,000</p>
<p>• Total Costs: $8,000</p>
<p className="font-bold text-lg mt-2">• Economic Profit: $0</p>
</div>
<p className="mt-2 text-sm text-gray-600">Notice: Price has fallen from the initial $10 to $8, which exactly equals your Average Total Cost (ATC). This is the long-run equilibrium!</p>
</div>
<div className="bg-purple-50 p-4 rounded">
<h3 className="font-bold mb-2">🎓 What You Learned:</h3>
<ul className="space-y-2 text-sm">
<li>• In <strong>perfect competition</strong>, firms are price-takers and must accept the market price</li>
<li>• <strong>Positive economic profits</strong> (like the $2,000 in Month 1 at P=$10) attract new firms to enter the market</li>
<li>• <strong>Easy entry</strong> increases supply and drives prices down (from $10 → $5.50)</li>
<li>• Firms should continue operating in the short run as long as <strong>P ≥ AVC</strong> (covering variable costs)</li>
<li>• Firms exit when they can't cover costs, causing supply to decrease and prices to rise (from $5.50 → $7 → $8)</li>
<li>• In the <strong>long run</strong>, economic profits approach zero as the market reaches equilibrium at <strong>P = ATC = $8</strong></li>
</ul>
</div>
</div>
)
}
];
const calculateProfit = (price, quantity, includeFC = true) => {
const revenue = price * quantity;
const variableCost = VC * quantity;
const totalCost = includeFC ? variableCost + FC : variableCost;
return revenue - totalCost;
};
const handleNext = () => {
let newFeedback = null;
if (stage === 0) {
setStage(1);
return;
}
if (stage === 1) {
const price = answers.q1Price || 0;
const marketPrice = 10;
let quantity;
let explanation;
if (Math.abs(price - marketPrice) < 0.01) {
quantity = 1000;
explanation = `Perfect! You charged the market price of $${marketPrice}. Customers are willing to pay this premium price because there aren't enough trucks to serve everyone. You sold 1,000 tacos. In perfect competition, you're a price-taker - you must accept the market price.`;
} else if (price > marketPrice) {
quantity = 0;
explanation = `You priced at $${price.toFixed(2)}, which is above the market price of $${marketPrice}. Even though demand is high, customers can still buy identical tacos from other trucks for $${marketPrice}, so nobody bought from you! You sold 0 tacos. In perfect competition, if you price above the market, you lose all your customers.`;
} else {
quantity = 1000;
explanation = `You priced at $${price.toFixed(2)}, below the market price of $${marketPrice}. You sold your maximum capacity in this neighborhood of 1,000 tacos, but you left money on the table! Customers were willing to pay $${marketPrice}, so you lost $${((marketPrice - price) * quantity).toFixed(0)} in potential revenue. In perfect competition, there's no reason to charge less than the market price.`;
}
const profit = calculateProfit(price, quantity);
setCumulativeProfit(profit);
const isOptimal = Math.abs(price - marketPrice) < 0.01;
newFeedback = {
optimal: isOptimal,
title: 'Month 1 Results',
explanation,
breakdown: {
price,
quantity,
revenue: price * quantity,
variableCost: VC * quantity,
fixedCost: FC,
totalCost: VC * quantity + FC,
profit
}
};
}
if (stage === 2) {
const isCorrect = answers.q2 === 'C';
newFeedback = {
optimal: isCorrect,
title: 'Market Analysis',
explanation: isCorrect
? "Correct! When many new firms enter the market, supply increases. With demand unchanged, the increased supply causes the market price to decrease. This is a key feature of perfect competition."
: "Not quite. When many new taco trucks enter (new firms), the supply curve shifts right. With demand unchanged, this causes price to fall. The correct answer was: Supply increases; Price decreases."
};
}
if (stage === 3) {
const decision = answers.q3Decision;
if (decision === 'exit') {
const loss = -FC;
setCumulativeProfit(prev => prev + loss);
newFeedback = {
optimal: false,
title: 'Month 3 Results',
explanation: "You exited too early! While you're losing money at $5.50, you should continue operating because P ($5.50) > AVC ($3.00). By operating, you cover your variable costs and contribute $2.50 per taco toward your fixed costs.",
breakdown: {
decision: 'Exited',
revenue: 0,
variableCost: 0,
fixedCost: FC,
totalCost: FC,
profit: loss,
note: "You still must pay fixed costs even when shut down. Loss: $5,000"
}
};
} else {
const price = answers.q3Price || 0;
const quantity = Math.min(answers.q3Quantity || 0, CAPACITY);
const isOptimal = Math.abs(price - 5.5) < 0.1 && quantity > 0;
const profit = calculateProfit(price, quantity);
setCumulativeProfit(prev => prev + profit);
const contributionMargin = (price - VC) * quantity;
newFeedback = {
optimal: isOptimal && price <= 5.5,
title: 'Month 3 Results',
explanation: isOptimal && price <= 5.5
? `Good decision! You're covering your variable costs ($3) and each taco contributes $${(price - VC).toFixed(2)} toward your fixed costs. You're losing money overall (${profit < 0 ? `-$${Math.abs(profit).toFixed(0)}` : `$${profit.toFixed(0)}`}), but you're losing LESS than if you shut down (which would cost you $5,000 in fixed costs). You paid off all your ingredients AND $${contributionMargin.toFixed(0)} of your $5,000 in monthly rent, permits, and opportunity costs.`
: price > 5.5
? "You priced above the market rate of $5.50. Customers went to competitors, so you sold fewer tacos than expected."
: "Operating is correct, but you should charge the market price of $5.50. Pricing below loses you money unnecessarily.",
breakdown: {
price,
quantity,
revenue: price * quantity,
variableCost: VC * quantity,
contributionToFC: contributionMargin,
fixedCost: FC,
totalCost: VC * quantity + FC,
profit,
note: profit < 0 ? `Loss of $${Math.abs(profit).toFixed(0)} is better than losing all $5,000 in FC if you shut down` : ''
}
};
}
}
if (stage === 4) {
const decision = answers.q4Decision;
if (decision === 'exit') {
const loss = -FC;
setCumulativeProfit(prev => prev + loss);
newFeedback = {
optimal: false,
title: 'Month 4 Results',
explanation: "You exited, but you should have stayed! At $7.00, you still cover your variable costs ($3) and contribute $4 per taco toward fixed costs. While still losing money, operating reduces your losses.",
breakdown: {
decision: 'Exited',
revenue: 0,
variableCost: 0,
fixedCost: FC,
totalCost: FC,
profit: loss
}
};
} else {
const price = 7;
const quantity = 1000;
const profit = calculateProfit(price, quantity);
setCumulativeProfit(prev => prev + profit);
newFeedback = {
optimal: true,
title: 'Month 4 Results',
explanation: `Correct! At $7.00, you're still below ATC ($8) but well above AVC ($3). You should continue operating. Each taco contributes $4 toward your fixed costs. Your loss is only $${Math.abs(profit).toFixed(0)}, much better than the $5,000 loss from shutting down.`,
breakdown: {
price,
quantity,
revenue: price * quantity,
variableCost: VC * quantity,
contributionToFC: (price - VC) * quantity,
fixedCost: FC,
totalCost: VC * quantity + FC,
profit
}
};
}
}
setFeedback(newFeedback);
if (newFeedback) {
setGameHistory(prev => [...prev, {
stage: stage,
decision: newFeedback.title,
wasOptimal: newFeedback.optimal,
profit: newFeedback.breakdown?.profit || 0
}]);
}
};
const canProceed = () => {
if (stage === 0) return true;
if (stage === 1) return answers.q1Price > 0;
if (stage === 2) return answers.q2;
if (stage === 3) {
if (answers.q3Decision === 'exit') return true;
return answers.q3Decision === 'stay' && answers.q3Price > 0 && answers.q3Quantity > 0;
}
if (stage === 4) return answers.q4Decision;
return true;
};
const downloadResults = () => {
const date = new Date().toLocaleDateString();
const time = new Date().toLocaleTimeString();
let resultsText = `TACO TRUCK PERFECT COMPETITION SIMULATOR - RESULTS\n`;
resultsText += `Date: ${date} at ${time}\n`;
resultsText += `Student Name: ___________________________\n\n`;
resultsText += `FINAL CUMULATIVE PROFIT/LOSS: ${cumulativeProfit.toFixed(0)}\n\n`;
resultsText += `DECISION HISTORY:\n`;
resultsText += `=================================================\n\n`;
gameHistory.forEach((entry, idx) => {
resultsText += `${entry.decision}\n`;
resultsText += `Decision: ${entry.wasOptimal ? '✓ OPTIMAL' : '✗ Suboptimal'}\n`;
resultsText += `Profit/Loss: ${entry.profit.toFixed(0)}\n\n`;
});
resultsText += `=================================================\n`;
resultsText += `SUMMARY:\n`;
resultsText += `Total Decisions Made: ${gameHistory.length}\n`;
resultsText += `Optimal Decisions: ${gameHistory.filter(h => h.wasOptimal).length}\n`;
resultsText += `Final Profit/Loss: ${cumulativeProfit.toFixed(0)}\n\n`;
resultsText += `This demonstrates understanding of:\n`;
resultsText += `- Perfect competition and price-taking behavior\n`;
resultsText += `- Market entry/exit effects on supply and price\n`;
resultsText += `- Shutdown rule (P vs AVC vs ATC)\n`;
resultsText += `- Long-run equilibrium with zero economic profit\n`;
const blob = new Blob([resultsText], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = `taco-truck-results-${date.replace(/\//g, '-')}.txt`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
};
return (
<div className="max-w-4xl mx-auto p-6 bg-gray-50 min-h-screen">
<div className="bg-white rounded-lg shadow-lg p-6">
<div className="flex items-center justify-between mb-6">
<h1 className="text-3xl font-bold text-gray-800">🌮 Taco Truck Simulator</h1>
<div className="text-right">
<div className="text-sm text-gray-600">Cumulative Profit/Loss</div>
<div className={`text-2xl font-bold ${cumulativeProfit >= 0 ? 'text-green-600' : 'text-red-600'}`}>
{cumulativeProfit >= 0 ? '+' : ''}{cumulativeProfit < 0 ? '-' : ''}${Math.abs(cumulativeProfit).toFixed(0)}
</div>
</div>
</div>
<div className="mb-6">
<div className="flex space-x-2 mb-4">
{stages.map((s, idx) => (
<div
key={s.id}
className={`flex-1 h-2 rounded ${
idx < stage ? 'bg-green-500' : idx === stage ? 'bg-blue-500' : 'bg-gray-300'
}`}
/>
))}
</div>
</div>
{feedback && (
<div className={`mb-6 p-4 rounded-lg border-l-4 ${
feedback.optimal ? 'bg-green-50 border-green-500' : 'bg-orange-50 border-orange-500'
}`}>
<div className="flex items-start space-x-2">
{feedback.optimal ? (
<div className="text-green-600 mt-1"><TrendingUp /></div>
) : (
<div className="text-orange-600 mt-1"><AlertCircle /></div>
)}
<div className="flex-1">
<h3 className="font-bold text-lg mb-2">{feedback.title}</h3>
<p className="mb-3">{feedback.explanation}</p>
{feedback.breakdown && (
<div className="bg-white p-3 rounded border">
<h4 className="font-semibold mb-2">Financial Breakdown:</h4>
<div className="text-sm space-y-1">
{feedback.breakdown.decision && (
<p>Decision: <strong>{feedback.breakdown.decision}</strong></p>
)}
{feedback.breakdown.price !== undefined && (
<p>Price: ${feedback.breakdown.price.toFixed(2)} per taco</p>
)}
{feedback.breakdown.quantity !== undefined && (
<p>Quantity: {feedback.breakdown.quantity} tacos</p>
)}
{feedback.breakdown.revenue !== undefined && (
<p>Revenue: ${feedback.breakdown.revenue.toFixed(0)}</p>
)}
{feedback.breakdown.variableCost !== undefined && (
<p>Variable Costs: ${feedback.breakdown.variableCost.toFixed(0)}</p>
)}
{feedback.breakdown.contributionToFC !== undefined && (
<p className="text-blue-600">Contribution to Fixed Costs: ${feedback.breakdown.contributionToFC.toFixed(0)}</p>
)}
{feedback.breakdown.fixedCost !== undefined && (
<p>Fixed Costs: ${feedback.breakdown.fixedCost.toFixed(0)}</p>
)}
{feedback.breakdown.totalCost !== undefined && (
<p>Total Costs: ${feedback.breakdown.totalCost.toFixed(0)}</p>
)}
{feedback.breakdown.profit !== undefined && (
<p className={`font-bold text-lg ${feedback.breakdown.profit >= 0 ? 'text-green-600' : 'text-red-600'}`}>
Profit/Loss: {feedback.breakdown.profit >= 0 ? '+' : ''}{feedback.breakdown.profit < 0 ? '-' : ''}${Math.abs(feedback.breakdown.profit).toFixed(0)}
</p>
)}
{feedback.breakdown.note && (
<p className="text-gray-600 italic mt-2">{feedback.breakdown.note}</p>
)}
</div>
</div>
)}
</div>
</div>
</div>
)}
<div className="bg-gray-50 p-6 rounded-lg">
<h2 className="text-2xl font-bold mb-4">{stages[stage].title}</h2>
{stages[stage].content}
</div>
<div className="mt-6 flex justify-between">
{stage > 0 && stage < stages.length - 1 && !feedback && (
<button
onClick={() => setStage(stage - 1)}
className="px-6 py-2 bg-gray-300 text-gray-700 rounded hover:bg-gray-400"
>
Back
</button>
)}
{feedback && stage === 1 && (
<div className="w-full flex justify-center space-x-4">
{!feedback.optimal && (
<button
onClick={() => {
setFeedback(null);
setAnswers({...answers, q1Price: null});
}}
className="px-6 py-2 bg-orange-600 text-white rounded hover:bg-orange-700 font-semibold"
>
⏮️ Go Back in Time and Change Price
</button>
)}
{feedback.optimal && (
<button
onClick={() => {
setStage(stage + 1);
setFeedback(null);
}}
className="px-6 py-2 bg-green-600 text-white rounded hover:bg-green-700 font-semibold"
>
Sounds Good! Continue to Month 2 →
</button>
)}
</div>
)}
{feedback && stage > 1 && (
<button
onClick={() => {
setStage(stage + 1);
setFeedback(null);
}}
className="ml-auto px-6 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 font-semibold"
>
Continue
</button>
)}
{stage < stages.length - 1 && !feedback && (
<button
onClick={handleNext}
disabled={!canProceed()}
className={`ml-auto px-6 py-2 rounded font-semibold ${
canProceed()
? 'bg-blue-600 text-white hover:bg-blue-700'
: 'bg-gray-300 text-gray-500 cursor-not-allowed'
}`}
>
{stage === 0 ? "Start Business" : "Submit Answer"}
</button>
)}
{stage === stages.length - 1 && (
<div className="ml-auto flex space-x-3">
<button
onClick={downloadResults}
className="px-6 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 font-semibold"
>
📥 Download Results
</button>
<button
onClick={() => {
setStage(0);
setAnswers({});
setCumulativeProfit(0);
setFeedback(null);
setGameHistory([]);
}}
className="px-6 py-2 bg-green-600 text-white rounded hover:bg-green-700 font-semibold"
>
Play Again
</button>
</div>
)}
</div>
</div>
</div>
);
};
ReactDOM.render(<TacoTruckGame />, document.getElementById('root'));
</script>
</body>
</html>