init: initialize project - 2 games
This commit is contained in:
commit
4740d0003b
7 changed files with 1784 additions and 0 deletions
598
tictactoe/game.js
Normal file
598
tictactoe/game.js
Normal file
|
|
@ -0,0 +1,598 @@
|
|||
// 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);
|
||||
Loading…
Add table
Add a link
Reference in a new issue