games/tictactoe/game.js

598 lines
No EOL
20 KiB
JavaScript

// Educational Tic Tac Toe Game
// Game variables
let currentPlayer = 'X';
let gameBoard = ['', '', '', '', '', '', '', '', ''];
let gameActive = true;
let scores = { X: 0, O: 0 };
let gameSettings = {
questionType: 'multiplechoice',
language: 'arabic',
category: 'beginners', // Changed to beginners for new users
timerSeconds: 5 // 5 seconds timer
};
// Timer variables
let timerInterval = null;
let timeLeft = 0;
// DOM Elements
const cells = document.querySelectorAll('.cell');
const player1Element = document.getElementById('player1');
const player2Element = document.getElementById('player2');
const resetButton = document.getElementById('resetBtn');
const newGameButton = document.getElementById('newGameBtn');
const questionModal = document.getElementById('questionModal');
const questionText = document.getElementById('questionText');
const answerOptions = document.getElementById('answerOptions');
const feedback = document.getElementById('feedback');
const gameOverModal = document.getElementById('gameOverModal');
const gameResult = document.getElementById('gameResult');
const playAgainButton = document.getElementById('playAgainBtn');
const applySettingsButton = document.getElementById('applySettingsBtn');
// Timer elements
const timerContainer = document.getElementById('timerContainer');
const timerBar = document.getElementById('timerBar');
const timerText = document.getElementById('timerText');
// Settings elements
const questionTypeSelect = document.getElementById('questionType');
const questionLanguageSelect = document.getElementById('questionLanguage');
const questionCategorySelect = document.getElementById('questionCategory');
const questionTimerSelect = document.getElementById('questionTimer');
// Current question and selected cell
let currentQuestion = null;
let selectedCellIndex = null;
let questionPlayer = null; // Track which player clicked the cell
// Initialize the game
function initGame() {
// Add event listeners to cells
cells.forEach(cell => {
cell.addEventListener('click', () => handleCellClick(parseInt(cell.dataset.index)));
});
// Add event listeners to buttons
resetButton.addEventListener('click', resetGame);
newGameButton.addEventListener('click', newGame);
playAgainButton.addEventListener('click', newGame);
applySettingsButton.addEventListener('click', applySettings);
// Add event listener to language select to update categories
questionLanguageSelect.addEventListener('change', updateCategoryOptions);
// Initialize settings from selects
questionTypeSelect.value = gameSettings.questionType;
questionLanguageSelect.value = gameSettings.language;
questionCategorySelect.value = gameSettings.category;
questionTimerSelect.value = gameSettings.timerSeconds;
// Update category options based on selected language
updateCategoryOptions();
// Set up the initial game state
updateGameDisplay();
// Update UI language immediately based on default settings
updateUILanguage();
// Set document direction for Arabic
if (gameSettings.language === 'arabic') {
document.documentElement.dir = 'rtl';
}
}
// Update category options based on selected language
function updateCategoryOptions() {
const language = questionLanguageSelect.value;
// Clear existing options
questionCategorySelect.innerHTML = '';
if (language === 'english') {
// Add English categories
const mathOption = document.createElement('option');
mathOption.value = 'math';
mathOption.textContent = 'Mathematics';
questionCategorySelect.appendChild(mathOption);
// Set default for English
questionCategorySelect.value = 'math';
} else if (language === 'arabic') {
// Add Arabic categories
const islamicOption = document.createElement('option');
islamicOption.value = 'islamic';
islamicOption.textContent = 'Islamic';
questionCategorySelect.appendChild(islamicOption);
const beginnersOption = document.createElement('option');
beginnersOption.value = 'beginners';
beginnersOption.textContent = 'Beginners Arabic';
questionCategorySelect.appendChild(beginnersOption);
// Set default for Arabic
questionCategorySelect.value = 'beginners';
}
}
// Handle cell click
function handleCellClick(index) {
// Ignore clicks if game is not active or cell is already filled
if (!gameActive || gameBoard[index] !== '') return;
// Store the selected cell index and current player
selectedCellIndex = index;
questionPlayer = currentPlayer; // Store which player clicked the cell
// Get a random question based on current settings
currentQuestion = getRandomQuestion(
gameSettings.language,
gameSettings.category,
gameSettings.questionType
);
// Display the question
showQuestion(currentQuestion);
}
// Show question modal
function showQuestion(question) {
// Set the question text with appropriate language attribute
questionText.textContent = question.question;
if (gameSettings.language === 'arabic') {
questionText.setAttribute('lang', 'ar');
} else {
questionText.removeAttribute('lang');
}
// Clear previous answer options and feedback
answerOptions.innerHTML = '';
feedback.textContent = '';
feedback.className = 'feedback';
// Create answer options based on question type
if (gameSettings.questionType === 'truefalse') {
// True/False question
const trueOption = document.createElement('div');
trueOption.className = 'answer-option';
trueOption.textContent = gameSettings.language === 'arabic' ? 'صحيح' : 'True';
trueOption.addEventListener('click', () => checkAnswer(true));
const falseOption = document.createElement('div');
falseOption.className = 'answer-option';
falseOption.textContent = gameSettings.language === 'arabic' ? 'خطأ' : 'False';
falseOption.addEventListener('click', () => checkAnswer(false));
answerOptions.appendChild(trueOption);
answerOptions.appendChild(falseOption);
} else {
// Multiple choice question
// Create a shuffled array of indices
const originalAnswer = question.answer;
const indices = Array.from({ length: question.options.length }, (_, i) => i);
const shuffledIndices = shuffleArray([...indices]);
// Create a mapping from shuffled positions to original positions
const shuffleMap = {};
shuffledIndices.forEach((originalIndex, newIndex) => {
shuffleMap[newIndex] = originalIndex;
});
// Display options in shuffled order
shuffledIndices.forEach((originalIndex, newIndex) => {
const optionElement = document.createElement('div');
optionElement.className = 'answer-option';
optionElement.textContent = question.options[originalIndex];
if (gameSettings.language === 'arabic') {
optionElement.setAttribute('lang', 'ar');
}
// When clicked, map back to the original index for checking
optionElement.addEventListener('click', () => checkAnswer(originalIndex, shuffleMap));
answerOptions.appendChild(optionElement);
});
}
// Show the modal
questionModal.classList.add('show');
// Setup timer if enabled
if (gameSettings.timerSeconds > 0) {
// Show timer container
timerContainer.style.display = 'block';
// Reset timer
timeLeft = gameSettings.timerSeconds;
updateTimerDisplay();
// Clear any existing timer
if (timerInterval) {
clearInterval(timerInterval);
}
// Start the timer
timerInterval = setInterval(() => {
timeLeft--;
updateTimerDisplay();
if (timeLeft <= 0) {
// Time's up
clearInterval(timerInterval);
timeOut();
}
}, 1000);
} else {
// Hide timer if disabled
timerContainer.style.display = 'none';
}
}
// Update the timer display
function updateTimerDisplay() {
// Update text
timerText.textContent = `${timeLeft}s`;
// Update progress bar
const percentage = (timeLeft / gameSettings.timerSeconds) * 100;
timerBar.style.width = `${percentage}%`;
// Change color based on time left
timerBar.className = 'timer-bar';
if (timeLeft <= Math.floor(gameSettings.timerSeconds / 3)) {
timerBar.classList.add('danger');
} else if (timeLeft <= Math.floor(gameSettings.timerSeconds / 2)) {
timerBar.classList.add('warning');
}
}
// Handle time out
function timeOut() {
// Show feedback
feedback.textContent = gameSettings.language === 'arabic'
? 'انتهى الوقت!'
: 'Time\'s up!';
feedback.classList.add('incorrect');
// Switch players after a delay
setTimeout(() => {
hideQuestionModal();
switchPlayer();
}, 1500);
}
// Check the answer
function checkAnswer(userAnswer, shuffleMap = null) {
// Clear the timer if it's running
if (timerInterval) {
clearInterval(timerInterval);
timerInterval = null;
}
// Get all answer options
const options = document.querySelectorAll('.answer-option');
// Determine if the answer is correct
let isCorrect = false;
if (gameSettings.questionType === 'truefalse') {
isCorrect = userAnswer === currentQuestion.answer;
// Highlight the selected option
options[userAnswer ? 0 : 1].classList.add('selected');
// Highlight the correct option
options[currentQuestion.answer ? 0 : 1].classList.add('correct');
// If wrong, highlight as incorrect
if (!isCorrect) {
options[userAnswer ? 0 : 1].classList.add('incorrect');
}
} else {
isCorrect = userAnswer === currentQuestion.answer;
// Find the positions in the shuffled array
let selectedPosition = -1;
let correctPosition = -1;
if (shuffleMap) {
// Find the position of the selected answer in the shuffled array
for (let i = 0; i < options.length; i++) {
if (shuffleMap[i] === userAnswer) {
selectedPosition = i;
}
if (shuffleMap[i] === currentQuestion.answer) {
correctPosition = i;
}
}
} else {
// If no shuffle map (shouldn't happen), use original positions
selectedPosition = userAnswer;
correctPosition = currentQuestion.answer;
}
// Highlight the selected option
if (selectedPosition >= 0 && selectedPosition < options.length) {
options[selectedPosition].classList.add('selected');
}
// Highlight the correct option
if (correctPosition >= 0 && correctPosition < options.length) {
options[correctPosition].classList.add('correct');
}
// If wrong, highlight as incorrect
if (!isCorrect && selectedPosition >= 0 && selectedPosition < options.length) {
options[selectedPosition].classList.add('incorrect');
}
}
// Show feedback
feedback.textContent = isCorrect
? (gameSettings.language === 'arabic' ? 'إجابة صحيحة!' : 'Correct!')
: (gameSettings.language === 'arabic' ? 'إجابة خاطئة!' : 'Incorrect!');
feedback.classList.add(isCorrect ? 'correct' : 'incorrect');
// Show explanation if available
if (currentQuestion.explanation) {
setTimeout(() => {
feedback.textContent += ' ' + currentQuestion.explanation;
}, 500);
}
// If correct, make the move after a short delay
if (isCorrect) {
setTimeout(() => {
// Make sure we're using the player who clicked the cell
if (currentPlayer !== questionPlayer) {
// If the player has changed, switch back to the original player
currentPlayer = questionPlayer;
// Update the active player display
player1Element.classList.toggle('active', currentPlayer === 'X');
player2Element.classList.toggle('active', currentPlayer === 'O');
}
makeMove(selectedCellIndex);
hideQuestionModal();
}, 1500);
} else {
// If incorrect, close the modal after a delay and switch players
setTimeout(() => {
hideQuestionModal();
// Only switch if the current player is still the one who clicked
if (currentPlayer === questionPlayer) {
switchPlayer();
}
// Reset the question player
questionPlayer = null;
}, 2000);
}
}
// Hide the question modal
function hideQuestionModal() {
// Clear the timer if it's running
if (timerInterval) {
clearInterval(timerInterval);
timerInterval = null;
}
questionModal.classList.remove('show');
}
// Make a move on the board
function makeMove(index) {
// Update the game board
gameBoard[index] = currentPlayer;
// Update the cell display
cells[index].textContent = currentPlayer;
cells[index].classList.add(currentPlayer.toLowerCase());
// Check for win or draw
if (checkWin()) {
endGame(false);
} else if (checkDraw()) {
endGame(true);
} else {
// Switch players
switchPlayer();
}
}
// Switch the current player
function switchPlayer() {
currentPlayer = currentPlayer === 'X' ? 'O' : 'X';
// Update the active player display
player1Element.classList.toggle('active');
player2Element.classList.toggle('active');
}
// Check if the current player has won
function checkWin() {
const winPatterns = [
[0, 1, 2], [3, 4, 5], [6, 7, 8], // Rows
[0, 3, 6], [1, 4, 7], [2, 5, 8], // Columns
[0, 4, 8], [2, 4, 6] // Diagonals
];
for (const pattern of winPatterns) {
const [a, b, c] = pattern;
if (gameBoard[a] && gameBoard[a] === gameBoard[b] && gameBoard[a] === gameBoard[c]) {
// Highlight the winning cells
cells[a].classList.add('highlight');
cells[b].classList.add('highlight');
cells[c].classList.add('highlight');
return true;
}
}
return false;
}
// Check if the game is a draw
function checkDraw() {
return gameBoard.every(cell => cell !== '');
}
// End the game
function endGame(isDraw) {
gameActive = false;
if (!isDraw) {
// Update scores
scores[currentPlayer]++;
document.querySelector(`#player${currentPlayer === 'X' ? '1' : '2'} .player-score`).textContent = scores[currentPlayer];
// Show game over message
gameResult.textContent = gameSettings.language === 'arabic'
? `اللاعب ${currentPlayer} فاز!`
: `Player ${currentPlayer} wins!`;
} else {
// Show draw message
gameResult.textContent = gameSettings.language === 'arabic' ? 'تعادل!' : 'It\'s a draw!';
}
// Show game over modal
setTimeout(() => {
gameOverModal.classList.add('show');
}, 1000);
}
// Reset the current game
function resetGame() {
// Clear the board
gameBoard = ['', '', '', '', '', '', '', '', ''];
gameActive = true;
currentPlayer = 'X';
// Reset the UI
cells.forEach(cell => {
cell.textContent = '';
cell.className = 'cell';
});
player1Element.classList.add('active');
player2Element.classList.remove('active');
// Clear any active timers
if (timerInterval) {
clearInterval(timerInterval);
timerInterval = null;
}
// Hide modals
questionModal.classList.remove('show');
gameOverModal.classList.remove('show');
}
// Start a new game (reset game and scores)
function newGame() {
// Reset scores
scores = { X: 0, O: 0 };
document.querySelector('#player1 .player-score').textContent = '0';
document.querySelector('#player2 .player-score').textContent = '0';
// Reset the game
resetGame();
}
// Apply new settings
function applySettings() {
gameSettings.questionType = questionTypeSelect.value;
gameSettings.language = questionLanguageSelect.value;
gameSettings.category = questionCategorySelect.value;
gameSettings.timerSeconds = parseInt(questionTimerSelect.value);
// Validate language-category combination
// Make sure the selected category exists for the selected language
if (gameSettings.language === 'english' && gameSettings.category !== 'math') {
// For English, only math category is available
gameSettings.category = 'math';
questionCategorySelect.value = 'math';
} else if (gameSettings.language === 'arabic' &&
(gameSettings.category !== 'islamic' && gameSettings.category !== 'beginners')) {
// For Arabic, only islamic and beginners categories are available
gameSettings.category = 'beginners';
questionCategorySelect.value = 'beginners';
}
// Update UI language if needed
updateUILanguage();
// Start a new game with new settings
newGame();
}
// Update UI language based on selected language
function updateUILanguage() {
const isArabic = gameSettings.language === 'arabic';
// Update document direction
document.documentElement.dir = isArabic ? 'rtl' : 'ltr';
// Update button texts
resetButton.textContent = isArabic ? 'إعادة اللعبة' : 'Reset Game';
newGameButton.textContent = isArabic ? 'لعبة جديدة' : 'New Game';
playAgainButton.textContent = isArabic ? 'العب مرة أخرى' : 'Play Again';
applySettingsButton.textContent = isArabic ? 'تطبيق الإعدادات' : 'Apply Settings';
// Update headings
document.querySelector('h1').textContent = isArabic ? 'لعبة تيك تاك تو التعليمية' : 'Educational Tic Tac Toe';
document.querySelector('.settings h3').textContent = isArabic ? 'إعدادات اللعبة' : 'Game Settings';
document.querySelector('.used-words-container h3').textContent = isArabic ? 'الكلمات المستخدمة:' : 'Selected Words:';
// Update player names
document.querySelector('#player1 .player-name').textContent = isArabic ? 'اللاعب 1' : 'Player 1';
document.querySelector('#player2 .player-name').textContent = isArabic ? 'اللاعب 2' : 'Player 2';
// Update settings labels
const labels = document.querySelectorAll('.setting-group label');
if (isArabic) {
labels[0].textContent = 'نوع السؤال:';
labels[1].textContent = 'اللغة:';
labels[2].textContent = 'الفئة:';
labels[3].textContent = 'الوقت المحدد (ثواني):';
} else {
labels[0].textContent = 'Question Type:';
labels[1].textContent = 'Language:';
labels[2].textContent = 'Category:';
labels[3].textContent = 'Time Limit (seconds):';
}
// Update modal texts
document.querySelector('#questionModal h2').textContent = isArabic
? 'أجب على السؤال لإجراء حركتك'
: 'Answer the question to make your move';
}
// Update the game display
function updateGameDisplay() {
// Update UI language
updateUILanguage();
// Update player scores
document.querySelector('#player1 .player-score').textContent = scores.X;
document.querySelector('#player2 .player-score').textContent = scores.O;
// Update active player
player1Element.classList.toggle('active', currentPlayer === 'X');
player2Element.classList.toggle('active', currentPlayer === 'O');
}
// Utility function to shuffle an array (Fisher-Yates algorithm)
function shuffleArray(array) {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
return array;
}
// Initialize the game when the page loads
window.addEventListener('load', initGame);