Skip to content

Commit 2c50a09

Browse files
Add AI-based Who Wants to Be a Millionaire game (#129)
* Add AI-based Who Wants to Be a Millionaire game Implements a vanilla HTML/JS version of Who Wants to Be a Millionaire with AI-powered features: Features: - AI-generated questions with increasing difficulty using Pollinations Text API - Four AI-powered help lines: - Phone A Friend: AI simulates an expert friend's advice - Ask The Audience: Simulated audience poll with difficulty-based accuracy - 50:50: Removes two wrong answers - Ask The Host: AI host provides subtle hints - Classic game show UI with: - Money ladder showing progress and safe havens - Visual feedback for answers - Dramatic pauses for tension - Responsive design that works on mobile and desktop Technical Details: - Uses vanilla HTML/CSS/JS for simplicity and performance - Implements OOP principles with a MillionaireGame class - Uses Pollinations Text API for question generation and help lines - Includes error handling and fallbacks Closes # 128 Mentat precommits passed. Log: https://mentat.ai/log/0c03d342-3379-4243-86db-e4d5201f9601 * Fix CSS file path in millionaire game - Updated the CSS file path to use relative path - This fixes the CI test failure where the CSS file couldn't be found Mentat precommits passed. Log: https://mentat.ai/log/8f387db3-06f7-455e-9b68-3ad3bcd1b5f0 * Fix file structure and paths - Organized files into src directory - Updated paths in index.html to point to src directory - Removed non-existent updateUI call from startGame method Mentat precommits passed. Log: https://mentat.ai/log/4dffa4d4-1b70-455f-bfc8-3f6a3767145f --------- Co-authored-by: MentatBot <160964065+MentatBot@users.noreply.github.com>
1 parent d883702 commit 2c50a09

File tree

3 files changed

+574
-0
lines changed

3 files changed

+574
-0
lines changed

millionaire-game/index.html

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<title>Who Wants to Be a Millionaire? - AI Edition</title>
7+
<link rel="stylesheet" href="./src/styles.css">
8+
</head>
9+
<body>
10+
<div class="game-container">
11+
<div class="header">
12+
<h1>Who Wants to Be a Millionaire?</h1>
13+
<div class="current-money">Current Prize: $<span id="current-prize">0</span></div>
14+
</div>
15+
16+
<div class="main-game">
17+
<div class="help-lines">
18+
<button id="fifty-fifty" class="help-button">50:50</button>
19+
<button id="phone-friend" class="help-button">Phone A Friend</button>
20+
<button id="ask-audience" class="help-button">Ask The Audience</button>
21+
<button id="ask-host" class="help-button">Ask The Host</button>
22+
</div>
23+
24+
<div class="question-container">
25+
<div id="question" class="question">
26+
Welcome to Who Wants to Be a Millionaire! Click Start Game to begin.
27+
</div>
28+
<div class="answers">
29+
<button class="answer-button" data-index="0">A: </button>
30+
<button class="answer-button" data-index="1">B: </button>
31+
<button class="answer-button" data-index="2">C: </button>
32+
<button class="answer-button" data-index="3">D: </button>
33+
</div>
34+
</div>
35+
36+
<div class="money-ladder">
37+
<div class="money-item" data-value="1000000">$1,000,000</div>
38+
<div class="money-item" data-value="500000">$500,000</div>
39+
<div class="money-item" data-value="250000">$250,000</div>
40+
<div class="money-item" data-value="100000">$100,000</div>
41+
<div class="money-item safe" data-value="50000">$50,000</div>
42+
<div class="money-item" data-value="25000">$25,000</div>
43+
<div class="money-item" data-value="10000">$10,000</div>
44+
<div class="money-item" data-value="5000">$5,000</div>
45+
<div class="money-item safe" data-value="1000">$1,000</div>
46+
<div class="money-item" data-value="500">$500</div>
47+
</div>
48+
</div>
49+
50+
<div class="controls">
51+
<button id="start-game" class="control-button">Start Game</button>
52+
<button id="quit-game" class="control-button" style="display: none;">Quit Game</button>
53+
</div>
54+
55+
<div id="message" class="message"></div>
56+
</div>
57+
<script src="./src/script.js"></script>
58+
</body>
59+
</html>

millionaire-game/src/script.js

Lines changed: 315 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,315 @@
1+
class MillionaireGame {
2+
constructor() {
3+
this.currentQuestion = 0;
4+
this.currentPrize = 0;
5+
this.isGameActive = false;
6+
this.helpLinesUsed = {
7+
fiftyFifty: false,
8+
phoneFriend: false,
9+
askAudience: false,
10+
askHost: false
11+
};
12+
this.moneyLadder = [500, 1000, 5000, 10000, 25000, 50000, 100000, 250000, 500000, 1000000];
13+
this.safeHavens = [1000, 50000];
14+
15+
this.initializeElements();
16+
this.attachEventListeners();
17+
}
18+
19+
initializeElements() {
20+
this.questionElement = document.getElementById('question');
21+
this.answerButtons = Array.from(document.getElementsByClassName('answer-button'));
22+
this.moneyItems = Array.from(document.getElementsByClassName('money-item'));
23+
this.currentPrizeElement = document.getElementById('current-prize');
24+
this.messageElement = document.getElementById('message');
25+
this.startButton = document.getElementById('start-game');
26+
this.quitButton = document.getElementById('quit-game');
27+
28+
// Help line buttons
29+
this.fiftyFiftyButton = document.getElementById('fifty-fifty');
30+
this.phoneFriendButton = document.getElementById('phone-friend');
31+
this.askAudienceButton = document.getElementById('ask-audience');
32+
this.askHostButton = document.getElementById('ask-host');
33+
}
34+
35+
attachEventListeners() {
36+
this.startButton.addEventListener('click', () => this.startGame());
37+
this.quitButton.addEventListener('click', () => this.quitGame());
38+
this.answerButtons.forEach(button => {
39+
button.addEventListener('click', () => this.handleAnswer(button));
40+
});
41+
42+
// Help line event listeners
43+
this.fiftyFiftyButton.addEventListener('click', () => this.useFiftyFifty());
44+
this.phoneFriendButton.addEventListener('click', () => this.usePhoneFriend());
45+
this.askAudienceButton.addEventListener('click', () => this.useAskAudience());
46+
this.askHostButton.addEventListener('click', () => this.useAskHost());
47+
}
48+
49+
async startGame() {
50+
this.isGameActive = true;
51+
this.currentQuestion = 0;
52+
this.currentPrize = 0;
53+
this.helpLinesUsed = {
54+
fiftyFifty: false,
55+
phoneFriend: false,
56+
askAudience: false,
57+
askHost: false
58+
};
59+
60+
this.startButton.style.display = 'none';
61+
this.quitButton.style.display = 'block';
62+
this.currentPrizeElement.textContent = '0';
63+
64+
await this.loadNextQuestion();
65+
}
66+
67+
quitGame() {
68+
const guaranteedPrize = this.getGuaranteedPrize();
69+
this.showMessage(`Game Over! You're walking away with $${guaranteedPrize.toLocaleString()}`);
70+
this.resetGame();
71+
}
72+
73+
resetGame() {
74+
this.isGameActive = false;
75+
this.startButton.style.display = 'block';
76+
this.quitButton.style.display = 'none';
77+
this.answerButtons.forEach(button => {
78+
button.disabled = false;
79+
button.className = 'answer-button';
80+
button.textContent = button.getAttribute('data-index') === '0' ? 'A: ' :
81+
button.getAttribute('data-index') === '1' ? 'B: ' :
82+
button.getAttribute('data-index') === '2' ? 'C: ' : 'D: ';
83+
});
84+
this.enableAllHelpLines();
85+
this.updateMoneyLadder(-1);
86+
}
87+
88+
async loadNextQuestion() {
89+
const difficulty = Math.min(9, Math.floor(this.currentQuestion / 2));
90+
const prompt = `Generate a multiple choice trivia question for "Who Wants to Be a Millionaire?"
91+
Difficulty level: ${difficulty}/9 (0 being easiest, 9 being hardest).
92+
Format: Return a JSON object with the following structure:
93+
{
94+
"question": "The question text",
95+
"answers": ["correct answer", "wrong answer 1", "wrong answer 2", "wrong answer 3"],
96+
"correctIndex": 0
97+
}
98+
Make sure the question is challenging but fair, and all answers are plausible.`;
99+
100+
try {
101+
const response = await fetch('https://text.pollinations.ai/', {
102+
method: 'POST',
103+
headers: {
104+
'Content-Type': 'application/json',
105+
},
106+
body: JSON.stringify({
107+
messages: [
108+
{ role: 'system', content: 'You are a game show question writer.' },
109+
{ role: 'user', content: prompt }
110+
],
111+
jsonMode: true
112+
}),
113+
});
114+
115+
const data = await response.json();
116+
this.currentQuestionData = data;
117+
118+
// Update UI with new question
119+
this.questionElement.textContent = data.question;
120+
this.answerButtons.forEach((button, index) => {
121+
button.textContent = `${String.fromCharCode(65 + index)}: ${data.answers[index]}`;
122+
button.disabled = false;
123+
button.className = 'answer-button';
124+
});
125+
126+
this.updateMoneyLadder(this.currentQuestion);
127+
} catch (error) {
128+
console.error('Error loading question:', error);
129+
this.showMessage('Error loading question. Please try again.');
130+
}
131+
}
132+
133+
async handleAnswer(button) {
134+
if (!this.isGameActive) return;
135+
136+
const selectedIndex = parseInt(button.getAttribute('data-index'));
137+
button.classList.add('selected');
138+
this.answerButtons.forEach(btn => btn.disabled = true);
139+
140+
// Dramatic pause
141+
await new Promise(resolve => setTimeout(resolve, 2000));
142+
143+
if (selectedIndex === this.currentQuestionData.correctIndex) {
144+
button.classList.add('correct');
145+
this.currentPrize = this.moneyLadder[this.currentQuestion];
146+
this.currentPrizeElement.textContent = this.currentPrize.toLocaleString();
147+
148+
if (this.currentQuestion === this.moneyLadder.length - 1) {
149+
this.showMessage('Congratulations! You\'ve won the million dollars!');
150+
this.resetGame();
151+
} else {
152+
this.currentQuestion++;
153+
await new Promise(resolve => setTimeout(resolve, 1500));
154+
await this.loadNextQuestion();
155+
}
156+
} else {
157+
button.classList.add('wrong');
158+
const correctButton = this.answerButtons[this.currentQuestionData.correctIndex];
159+
correctButton.classList.add('correct');
160+
161+
const guaranteedPrize = this.getGuaranteedPrize();
162+
this.showMessage(`Game Over! You walk away with $${guaranteedPrize.toLocaleString()}`);
163+
await new Promise(resolve => setTimeout(resolve, 2000));
164+
this.resetGame();
165+
}
166+
}
167+
168+
getGuaranteedPrize() {
169+
let guaranteedPrize = 0;
170+
for (let i = 0; i < this.currentQuestion; i++) {
171+
if (this.safeHavens.includes(this.moneyLadder[i])) {
172+
guaranteedPrize = this.moneyLadder[i];
173+
}
174+
}
175+
return guaranteedPrize;
176+
}
177+
178+
updateMoneyLadder(currentQuestionIndex) {
179+
this.moneyItems.forEach((item, index) => {
180+
item.classList.remove('active');
181+
if (index === currentQuestionIndex) {
182+
item.classList.add('active');
183+
}
184+
});
185+
}
186+
187+
showMessage(message) {
188+
this.messageElement.textContent = message;
189+
}
190+
191+
// Help Line Implementations
192+
async useFiftyFifty() {
193+
if (this.helpLinesUsed.fiftyFifty || !this.isGameActive) return;
194+
195+
this.helpLinesUsed.fiftyFifty = true;
196+
this.fiftyFiftyButton.disabled = true;
197+
198+
const correctIndex = this.currentQuestionData.correctIndex;
199+
const wrongIndices = [0, 1, 2, 3].filter(i => i !== correctIndex);
200+
const indicesToKeep = [correctIndex, wrongIndices[Math.floor(Math.random() * wrongIndices.length)]];
201+
202+
this.answerButtons.forEach((button, index) => {
203+
if (!indicesToKeep.includes(index)) {
204+
button.disabled = true;
205+
button.style.opacity = '0.3';
206+
}
207+
});
208+
}
209+
210+
async usePhoneFriend() {
211+
if (this.helpLinesUsed.phoneFriend || !this.isGameActive) return;
212+
213+
this.helpLinesUsed.phoneFriend = true;
214+
this.phoneFriendButton.disabled = true;
215+
216+
const prompt = `As an expert friend, analyze this question and provide your thoughts:
217+
Question: ${this.currentQuestionData.question}
218+
Options: ${this.answerButtons.map(b => b.textContent).join(', ')}
219+
Provide a natural response as if you're on the phone, with about 80% accuracy.`;
220+
221+
try {
222+
const response = await fetch('https://text.pollinations.ai/', {
223+
method: 'POST',
224+
headers: { 'Content-Type': 'application/json' },
225+
body: JSON.stringify({
226+
messages: [
227+
{ role: 'system', content: 'You are an expert friend helping on Who Wants to Be a Millionaire.' },
228+
{ role: 'user', content: prompt }
229+
]
230+
}),
231+
});
232+
233+
const data = await response.json();
234+
this.showMessage(`Friend says: ${data.content}`);
235+
} catch (error) {
236+
this.showMessage('Sorry, couldn\'t reach your friend. Try another lifeline!');
237+
}
238+
}
239+
240+
async useAskAudience() {
241+
if (this.helpLinesUsed.askAudience || !this.isGameActive) return;
242+
243+
this.helpLinesUsed.askAudience = true;
244+
this.askAudienceButton.disabled = true;
245+
246+
const correctIndex = this.currentQuestionData.correctIndex;
247+
const percentages = Array(4).fill(0);
248+
const difficulty = Math.min(9, Math.floor(this.currentQuestion / 2));
249+
250+
// Audience is more accurate on easier questions
251+
const accuracy = 1 - (difficulty * 0.08); // 92% accurate at easiest, 20% at hardest
252+
const correctPercentage = Math.floor(accuracy * 100);
253+
percentages[correctIndex] = correctPercentage;
254+
255+
// Distribute remaining percentage among wrong answers
256+
const remaining = 100 - correctPercentage;
257+
const wrongIndices = [0, 1, 2, 3].filter(i => i !== correctIndex);
258+
wrongIndices.forEach((index, i) => {
259+
percentages[index] = i === wrongIndices.length - 1
260+
? remaining - wrongIndices.slice(0, -1).reduce((sum, idx) => sum + percentages[idx], 0)
261+
: Math.floor(remaining / (wrongIndices.length - i));
262+
});
263+
264+
const audienceResults = this.answerButtons.map((button, index) =>
265+
`${button.textContent.split(':')[0]}: ${percentages[index]}%`
266+
).join('\n');
267+
268+
this.showMessage(`Audience Results:\n${audienceResults}`);
269+
}
270+
271+
async useAskHost() {
272+
if (this.helpLinesUsed.askHost || !this.isGameActive) return;
273+
274+
this.helpLinesUsed.askHost = true;
275+
this.askHostButton.disabled = true;
276+
277+
const prompt = `As the game show host, provide a helpful but indirect hint about this question:
278+
Question: ${this.currentQuestionData.question}
279+
Options: ${this.answerButtons.map(b => b.textContent).join(', ')}
280+
Give a clever hint that helps narrow down the options without directly revealing the answer.`;
281+
282+
try {
283+
const response = await fetch('https://text.pollinations.ai/', {
284+
method: 'POST',
285+
headers: { 'Content-Type': 'application/json' },
286+
body: JSON.stringify({
287+
messages: [
288+
{ role: 'system', content: 'You are the host of Who Wants to Be a Millionaire.' },
289+
{ role: 'user', content: prompt }
290+
]
291+
}),
292+
});
293+
294+
const data = await response.json();
295+
this.showMessage(`Host: ${data.content}`);
296+
} catch (error) {
297+
this.showMessage('The host seems to be at a loss for words!');
298+
}
299+
}
300+
301+
enableAllHelpLines() {
302+
Object.keys(this.helpLinesUsed).forEach(helpLine => {
303+
this.helpLinesUsed[helpLine] = false;
304+
});
305+
this.fiftyFiftyButton.disabled = false;
306+
this.phoneFriendButton.disabled = false;
307+
this.askAudienceButton.disabled = false;
308+
this.askHostButton.disabled = false;
309+
}
310+
}
311+
312+
// Initialize the game when the page loads
313+
document.addEventListener('DOMContentLoaded', () => {
314+
window.game = new MillionaireGame();
315+
});

0 commit comments

Comments
 (0)