From 598b05c91c60fd5d237ecbc593dfacd1e4ae527b Mon Sep 17 00:00:00 2001 From: Mudita Date: Fri, 22 May 2026 14:25:09 +0530 Subject: [PATCH 1/4] feat: adaptive computer move selection for Rock Paper Scissors (#526) - Web (js/projects/rock-paper-scissor.js): - Implement Markov-chain + frequency blend adaptive AI engine - AI observes player history (last 20 moves) to predict next move - Plays counter-move 75% of time, random 25% for balanced gameplay - Add live 'Computer Brain Analysis' glassmorphic dashboard panel showing: AI mode, player favourite, predicted move, counter-move, confidence %, and round-result history dots - Persist player history in localStorage across page reloads - Fix: remove duplicate getRockPaperScissorHTML/initRockPaperScissor from projects.js so modular file is correctly loaded - Preserve all existing features: stats grid, keyboard shortcuts, comp-card reveals, streak/best-score tracking - Python CLI (games/Rock-Paper-Scissor/Rock-Paper-Scissor.py): - Same blended Markov + frequency prediction engine - 70% adaptive counter / 30% random for fairness - Print 'Computer Brain' status block each round once MIN_ADAPTIVE moves are recorded (favourite move, prediction, confidence, mode) - Show most-played move in statistics summary --- .../Rock-Paper-Scissor/Rock-Paper-Scissor.py | 132 +++- web-app/js/projects.js | 226 +------ web-app/js/projects/rock-paper-scissor.js | 581 ++++++++++++++---- 3 files changed, 591 insertions(+), 348 deletions(-) diff --git a/games/Rock-Paper-Scissor/Rock-Paper-Scissor.py b/games/Rock-Paper-Scissor/Rock-Paper-Scissor.py index c21f797..4ef3148 100644 --- a/games/Rock-Paper-Scissor/Rock-Paper-Scissor.py +++ b/games/Rock-Paper-Scissor/Rock-Paper-Scissor.py @@ -5,27 +5,123 @@ def __init__(self): """Initializes the game state without blocking execution.""" self.user_score = 0 self.computer_score = 0 - self.rounds_played = 0 + self.rounds_played = 0 # Perfectly mirrors the JS choices array ['rock', 'paper', 'scissors'] self.choices = ["rock", "paper", "scissors"] + # Counter look-up: what beats each move + self.beaten_by = {"rock": "paper", "paper": "scissors", "scissors": "rock"} + # Player history for adaptive AI (capped at recent 20 moves) + self.player_history = [] + self.HISTORY_CAP = 20 + self.MIN_ADAPTIVE = 3 # rounds before adaptive mode activates + self.ADAPT_RATE = 0.70 # probability of playing counter vs random + # ── Adaptive AI logic ──────────────────────────────────────────────── + def _get_move_frequencies(self): + """Returns overall frequency dict for player history.""" + freq = {"rock": 0, "paper": 0, "scissors": 0} + for move in self.player_history: + freq[move] += 1 + return freq + + def _get_markov_transitions(self, last_move): + """Returns transition counts from last_move to the next move in history.""" + transitions = {"rock": 0, "paper": 0, "scissors": 0} + for i in range(len(self.player_history) - 1): + if self.player_history[i] == last_move: + transitions[self.player_history[i + 1]] += 1 + return transitions + + def _predict_player_move(self): + """ + Uses a blended Markov-chain + frequency model to predict the player's + next move. Returns (predicted_move, confidence_pct) or (None, None) + if there is not enough data. + """ + n = len(self.player_history) + if n < self.MIN_ADAPTIVE: + return None, None + + freq = self._get_move_frequencies() + last = self.player_history[-1] + trans = self._get_markov_transitions(last) + total = sum(trans.values()) + + if total > 0: + # Blend: 60 % Markov, 40 % frequency + blended = {} + for c in self.choices: + blended[c] = (0.6 * trans[c] / total) + (0.4 * freq[c] / n) + predicted = max(blended, key=blended.get) + confidence = round(blended[predicted] * 100) + else: + # Fallback to pure frequency + predicted = max(freq, key=freq.get) + confidence = round(freq[predicted] / n * 100) + + return predicted, confidence + + def get_adaptive_computer_choice(self): + """ + Returns (computer_choice, predicted_player_move, confidence_pct, mode). + Plays the counter-move 70 % of the time; random otherwise for balance. + """ + predicted, confidence = self._predict_player_move() + + if predicted is None: + return random.choice(self.choices), None, None, "learning" + + if random.random() < self.ADAPT_RATE: + computer_choice = self.beaten_by[predicted] + mode = "adaptive" + else: + computer_choice = random.choice(self.choices) + mode = "random" + + return computer_choice, predicted, confidence, mode + + # ── Stats helpers ──────────────────────────────────────────────────── + def _most_frequent_choice(self): + if not self.player_history: + return None + freq = self._get_move_frequencies() + return max(freq, key=freq.get) + + # ── Gameplay ───────────────────────────────────────────────────────── def users_play(self): - """Handles a single round of interaction.""" + """Handles a single round of interaction with adaptive computer logic.""" user_choice = "" while user_choice not in self.choices: user_choice = input("Enter your choice (rock, paper, or scissors): ").lower() if user_choice not in self.choices: print("Invalid choice. Please choose rock, paper, or scissors.") - computer_choice = random.choice(self.choices) - print(f"Computer chose: {computer_choice}") + # AI decides BEFORE we record the player's move (prediction is based on prior history) + computer_choice, predicted, confidence, mode = self.get_adaptive_computer_choice() + # Print AI brain info after first MIN_ADAPTIVE rounds + if len(self.player_history) >= self.MIN_ADAPTIVE and predicted is not None: + fav = self._most_frequent_choice() + print(f"\n 🧠 Computer Brain [{mode.upper()}]") + print(f" Your favourite move : {fav}") + print(f" Predicted your move : {predicted} ({confidence}% confidence)") + print(f" Computer chose : {computer_choice}") + else: + remaining = self.MIN_ADAPTIVE - len(self.player_history) + if remaining > 0: + print(f"\n 🧠 Computer Brain [LEARNING] — observing for {remaining} more move(s)...") + print(f" Computer chose: {computer_choice}") + + # Record player move after AI has decided + self.player_history.append(user_choice) + if len(self.player_history) > self.HISTORY_CAP: + self.player_history.pop(0) + + # Determine round winner if user_choice == computer_choice: print("It's a Tie! 🤝") return "tie" - elif (user_choice == "rock" and computer_choice == "scissors") or \ - (user_choice == "paper" and computer_choice == "rock") or \ - (user_choice == "scissors" and computer_choice == "paper"): + elif self.beaten_by[computer_choice] == user_choice: print("You Win this round! 🎉") return "user" else: @@ -35,16 +131,24 @@ def users_play(self): def statistics(self): """Displays performance statistics matching the web dashboard metrics.""" print("\n--- Game Statistics ---") - print(f"Rounds Played: {self.rounds_played}") - print(f"Your Score: {self.user_score}") - print(f"Computer Score: {self.computer_score}") + print(f"Rounds Played : {self.rounds_played}") + print(f"Your Score : {self.user_score}") + print(f"Computer Score : {self.computer_score}") + fav = self._most_frequent_choice() + if fav: + freq = self._get_move_frequencies() + pct = round(freq[fav] / len(self.player_history) * 100) + print(f"Your Favourite : {fav} ({pct}% of plays)") def save_game(self): """Appends the final game results to a local tracking log.""" name = input("Enter your name to save the results (optional): ") if not name: name = "Anonymous" - result_string = f"Player: {name}, Final Score: {self.user_score} - {self.computer_score} (User-Computer), Rounds: {self.rounds_played}\n" + result_string = ( + f"Player: {name}, Final Score: {self.user_score} - {self.computer_score} " + f"(User-Computer), Rounds: {self.rounds_played}\n" + ) try: with open("game_results.txt", "a") as f: f.write(result_string) @@ -52,13 +156,14 @@ def save_game(self): except IOError: print("Error: Could not save game results to file.") - def play_game(self): + def play_game(self): """Launches the primary interactive gameplay loop.""" print("Welcome to Rock, Paper, Scissors!") + print("The computer will learn your patterns and adapt — good luck! 🧠") while True: self.rounds_played += 1 print(f"\n--- Round {self.rounds_played} ---") - + round_winner = self.users_play() if round_winner == "user": @@ -74,6 +179,7 @@ def play_game(self): self.save_game() break + # Standard execution block ensuring clean instantiation if __name__ == "__main__": game = Rock_Paper_Scissors() diff --git a/web-app/js/projects.js b/web-app/js/projects.js index ac7de39..49b6232 100644 --- a/web-app/js/projects.js +++ b/web-app/js/projects.js @@ -79,233 +79,9 @@ function getProjectHTML(projectName) { // ============================================ -// ROCK PAPER SCISSORS +// ROCK PAPER SCISSORS (Moved to js/projects/rock-paper-scissor.js) // ============================================ -function getRockPaperScissorHTML() { - return ` -
-

🪨 Rock Paper Scissors

-
-
-
- You - 0 -
-
- Computer - 0 -
-
- -
-
-
-

You

-
-
-
VS
-
-

Computer

-
-
-
-
Make your choice!
-
- -
- - - -
- - -
-
- - - `; -} -function initRockPaperScissor() { - let playerScore = 0; - let computerScore = 0; - - const choices = ['rock', 'paper', 'scissors']; - const emojis = { rock: '🪨', paper: '📄', scissors: '✂️' }; - - const choiceBtns = document.querySelectorAll('.choice-btn'); - const resetBtn = document.getElementById('resetRPS'); - - choiceBtns.forEach(btn => { - btn.addEventListener('click', () => { - const playerChoice = btn.getAttribute('data-choice'); - playRound(playerChoice); - }); - }); - - resetBtn.addEventListener('click', () => { - playerScore = 0; - computerScore = 0; - updateScore(); - document.getElementById('resultMessage').textContent = 'Make your choice!'; - document.getElementById('playerChoice').textContent = '❓'; - document.getElementById('computerChoice').textContent = '❓'; - }); - - function playRound(playerChoice) { - const computerChoice = choices[Math.floor(Math.random() * 3)]; - - document.getElementById('playerChoice').textContent = emojis[playerChoice]; - document.getElementById('computerChoice').textContent = emojis[computerChoice]; - - let result = ''; - - if (playerChoice === computerChoice) { - result = "It's a tie! 🤝"; - } else if ( - (playerChoice === 'rock' && computerChoice === 'scissors') || - (playerChoice === 'paper' && computerChoice === 'rock') || - (playerChoice === 'scissors' && computerChoice === 'paper') - ) { - result = 'You win! 🎉'; - playerScore++; - } else { - result = 'Computer wins! 🤖'; - computerScore++; - } - - document.getElementById('resultMessage').textContent = result; - updateScore(); - } - - function updateScore() { - document.getElementById('playerScore').textContent = playerScore; - document.getElementById('computerScore').textContent = computerScore; - } -} // ============================================ // DICE ROLLING diff --git a/web-app/js/projects/rock-paper-scissor.js b/web-app/js/projects/rock-paper-scissor.js index 2fd0ced..93d7dc3 100644 --- a/web-app/js/projects/rock-paper-scissor.js +++ b/web-app/js/projects/rock-paper-scissor.js @@ -3,17 +3,24 @@ function getRockPaperScissorHTML() {

🪨 Rock Paper Scissors

+ +
You 0
+
+ Draws + 0 +
Computer 0
- + +
@@ -42,6 +49,7 @@ function getRockPaperScissorHTML() {
Make your choice!
+
Games Played @@ -69,61 +77,96 @@ function getRockPaperScissorHTML() {
+ +
+
+ 🧠 +
+ Computer Brain + Observing... +
+
+ + confidence +
+
+
+
+ Your Favorite + +
+
+
+ AI Predicts + +
+
+
+ AI Will Play + +
+
+
+ +
+
+ +
- - -

⌨️ Press R Rock · P Paper · S Scissors

- +
- + `; @@ -488,26 +400,30 @@ function getRockPaperScissorHTML() { function initRockPaperScissor() { let playerScore = 0; let computerScore = 0; - let drawScore = 0; + let isAnimating = false; const choices = ['rock', 'paper', 'scissors']; - const emojis = { rock: '🪨', paper: '📄', scissors: '✂️' }; - const keyMap = { r: 'rock', p: 'paper', s: 'scissors' }; + const emojis = { rock: '🪨', paper: '📄', scissors: '✂️' }; + const keyMap = { r: 'rock', p: 'paper', s: 'scissors' }; - // Counter look-up: what beats each move - const beatenBy = { rock: 'paper', paper: 'scissors', scissors: 'rock' }; + const counterMove = { + rock: 'paper', + paper: 'scissors', + scissors: 'rock' + }; - // ── Adaptive AI state ──────────────────────────────────────────────── - // Stores last N player moves (capped at 20 for recency) - const HISTORY_CAP = 20; - const MIN_ADAPTIVE = 3; // minimum moves before adaptive mode kicks in - const ADAPT_RATE = 0.75; // probability of playing counter vs random + const difficultyConfig = { + easy: { adaptiveWeight: 0 }, + medium: { adaptiveWeight: 0.35 }, + hard: { adaptiveWeight: 0.55 } + }; - let playerHistory = []; // recent player moves, newest at end + const HISTORY_WINDOW = 10; - // ── Persistent storage ─────────────────────────────────────────────── const storage = window.appStorage || { - saveToStorage(key, value) { localStorage.setItem(key, JSON.stringify(value)); }, + saveToStorage(key, value) { + localStorage.setItem(key, JSON.stringify(value)); + }, loadFromStorage(key, defaultValue = null) { const data = localStorage.getItem(key); if (!data) return defaultValue; @@ -515,61 +431,97 @@ function initRockPaperScissor() { }, }; - // ── DOM refs ───────────────────────────────────────────────────────── - const choiceBtns = document.querySelectorAll('.choice-btn'); - const resetBtn = document.getElementById('resetRPS'); - const gamesPlayedDisplay = document.getElementById('gamesPlayed'); - const winsDisplay = document.getElementById('wins'); - const lossesDisplay = document.getElementById('losses'); + // DOM Elements + const choiceBtns = document.querySelectorAll('.rps-choice-btn'); + const resetBtn = document.getElementById('resetRPS'); + const diffTabs = document.querySelectorAll('.rps-diff-tab'); + const resultEl = document.getElementById('resultMessage'); + const playerChoiceEl = document.getElementById('playerChoice'); + const computerChoiceEl = document.getElementById('computerChoice'); + const clashEffect = document.getElementById('clashEffect'); + const playerFighter = document.querySelector('.rps-fighter-player'); + const computerFighter = document.querySelector('.rps-fighter-computer'); + + const gamesPlayedDisplay = document.getElementById('gamesPlayed'); + const winsDisplay = document.getElementById('wins'); + const lossesDisplay = document.getElementById('losses'); const currentStreakDisplay = document.getElementById('currentStreak'); - const bestStreakDisplay = document.getElementById('bestStreak'); - const bestScoreDisplay = document.getElementById('bestScore'); - const drawScoreDisplay = document.getElementById('drawScore'); - - // AI Brain panel refs - const aiModeEl = document.getElementById('aiMode'); - const aiConfEl = document.getElementById('aiConfidenceValue'); - const aiFavEl = document.getElementById('aiPlayerFavorite'); - const aiPredEl = document.getElementById('aiPrediction'); - const aiWillEl = document.getElementById('aiWillPlay'); - const aiHistoryBar = document.getElementById('aiHistoryBar'); - - // ── Load persisted stats ───────────────────────────────────────────── + const bestStreakDisplay = document.getElementById('bestStreak'); + + // State const stats = storage.loadFromStorage('rpsStats', { gamesPlayed: 0, wins: 0, losses: 0, currentStreak: 0, bestStreak: 0, }); - let bestScore = storage.loadFromStorage('rpsBestScore', 0); - // Restore history if any - playerHistory = storage.loadFromStorage('rpsPlayerHistory', []); + let bestScore = storage.loadFromStorage('rpsBestScore', 0); + let playerHistory = storage.loadFromStorage('rpsPlayerHistory', []); + let difficulty = storage.loadFromStorage('rpsDifficulty', 'medium'); + + // Init difficulty tabs + diffTabs.forEach(tab => { + if (tab.dataset.diff === difficulty) tab.classList.add('active'); + else tab.classList.remove('active'); + + tab.addEventListener('click', () => { + diffTabs.forEach(t => t.classList.remove('active')); + tab.classList.add('active'); + difficulty = tab.dataset.diff; + storage.saveToStorage('rpsDifficulty', difficulty); + }); + }); updateStatsDisplay(); - updateBestScore(); - updateBrainPanel(null); // initial render + updateTendencyDisplay(); - // ── Event wiring ───────────────────────────────────────────────────── + // --- Choice button clicks --- choiceBtns.forEach(btn => { - btn.addEventListener('click', () => playRound(btn.getAttribute('data-choice'))); + btn.addEventListener('click', () => { + if (isAnimating) return; + playRound(btn.getAttribute('data-choice')); + }); }); - // Keyboard shortcuts + // --- Keyboard shortcuts --- function handleKeydown(e) { + if (isAnimating) return; const key = e.key.toLowerCase(); if (keyMap[key]) { const choice = keyMap[key]; - const btn = document.querySelector(`.choice-btn[data-choice="${choice}"]`); - if (btn) { - btn.classList.add('key-active'); - setTimeout(() => btn.classList.remove('key-active'), 200); + const matchingBtn = document.querySelector(`.rps-choice-btn[data-choice="${choice}"]`); + if (matchingBtn) { + matchingBtn.classList.add('rps-key-active'); + setTimeout(() => matchingBtn.classList.remove('rps-key-active'), 200); } playRound(choice); } } document.addEventListener('keydown', handleKeydown); - resetBtn.addEventListener('click', resetGame); + // --- Reset --- + resetBtn.addEventListener('click', () => { + playerScore = 0; + computerScore = 0; + stats.gamesPlayed = 0; + stats.wins = 0; + stats.losses = 0; + stats.currentStreak = 0; + playerHistory = []; + updateScore(); + updateStatsDisplay(); + updateTendencyDisplay(); + saveRpsStats(); + storage.saveToStorage('rpsPlayerHistory', playerHistory); + resultEl.textContent = 'Choose your weapon!'; + resultEl.className = 'rps-result'; + playerChoiceEl.textContent = '❓'; + computerChoiceEl.textContent = '❓'; + playerFighter.classList.remove('rps-win', 'rps-lose'); + computerFighter.classList.remove('rps-win', 'rps-lose'); + clashEffect.className = 'rps-clash-effect'; + clashEffect.textContent = ''; + }); - // Clean up listener when modal closes + // Clean up on modal close const observer = new MutationObserver(() => { if (!document.getElementById('resetRPS')) { document.removeEventListener('keydown', handleKeydown); @@ -578,237 +530,165 @@ function initRockPaperScissor() { }); observer.observe(document.body, { childList: true, subtree: true }); - // ── Adaptive AI logic ───────────────────────────────────────────────── - /** - * Returns { choice, predicted, confidence, mode } - * - choice: what the computer actually plays - * - predicted: what it thinks the player will play - * - confidence: string like "72%" - * - mode: 'random' | 'learning' | 'adaptive' - */ + // --- Adaptive AI --- function getAdaptiveComputerChoice() { - if (playerHistory.length < MIN_ADAPTIVE) { - // Not enough data — pure random - return { - choice: choices[Math.floor(Math.random() * 3)], - predicted: null, - confidence: null, - mode: 'learning', - }; + const config = difficultyConfig[difficulty]; + if (config.adaptiveWeight === 0 || playerHistory.length < 5) { + return choices[Math.floor(Math.random() * 3)]; } - - // ── Step 1: Overall frequency ── + const recentMoves = playerHistory.slice(-HISTORY_WINDOW); const freq = { rock: 0, paper: 0, scissors: 0 }; - playerHistory.forEach(m => freq[m]++); - - // ── Step 2: Markov transition from last move ── - const lastMove = playerHistory[playerHistory.length - 1]; - const transitions = { rock: 0, paper: 0, scissors: 0 }; - for (let i = 0; i < playerHistory.length - 1; i++) { - if (playerHistory[i] === lastMove) { - transitions[playerHistory[i + 1]]++; - } - } - const transitionTotal = Object.values(transitions).reduce((a, b) => a + b, 0); - - let predicted; - let confidence; - - if (transitionTotal > 0) { - // Blend: 60% Markov, 40% frequency - const blended = {}; - choices.forEach(c => { - blended[c] = 0.6 * (transitions[c] / transitionTotal) - + 0.4 * (freq[c] / playerHistory.length); - }); - predicted = choices.reduce((a, b) => blended[a] > blended[b] ? a : b); - confidence = Math.round(blended[predicted] * 100); - } else { - // Fall back to overall frequency - predicted = choices.reduce((a, b) => freq[a] > freq[b] ? a : b); - confidence = Math.round((freq[predicted] / playerHistory.length) * 100); - } + recentMoves.forEach(m => freq[m]++); - // ── Step 3: Adapt with randomness factor ── - let finalChoice; - let mode; - if (Math.random() < ADAPT_RATE) { - // Counter the predicted move - finalChoice = beatenBy[predicted]; - mode = 'adaptive'; - } else { - // Random — keeps game fair and surprising - finalChoice = choices[Math.floor(Math.random() * 3)]; - mode = 'adaptive'; + let dominant = 'rock'; + let maxCount = 0; + for (const m of choices) { + if (freq[m] > maxCount) { maxCount = freq[m]; dominant = m; } } - return { - choice: finalChoice, - predicted, - confidence: `${confidence}%`, - mode, - }; + return Math.random() < config.adaptiveWeight + ? counterMove[dominant] + : choices[Math.floor(Math.random() * 3)]; } - // ── Brain panel update ───────────────────────────────────────────── - function updateBrainPanel(lastResult) { - const n = playerHistory.length; - - // Mode label - let modeText, modeClass; - if (n < MIN_ADAPTIVE) { - modeText = `Observing (${n}/${MIN_ADAPTIVE} moves)`; - modeClass = 'mode-learning'; - } else { - modeText = `Adaptive · ${n} moves analysed`; - modeClass = 'mode-adaptive'; - } - aiModeEl.textContent = modeText; - aiModeEl.className = 'ai-brain-mode ' + modeClass; - - // Overall favourite - if (n === 0) { - aiFavEl.textContent = '—'; - aiPredEl.textContent = '—'; - aiWillEl.textContent = '—'; - aiConfEl.textContent = '—'; - } else { - const freq = { rock: 0, paper: 0, scissors: 0 }; - playerHistory.forEach(m => freq[m]++); - const fav = choices.reduce((a, b) => freq[a] >= freq[b] ? a : b); - aiFavEl.textContent = emojis[fav] + ' ' + capitalize(fav); - - if (n < MIN_ADAPTIVE) { - aiPredEl.textContent = '—'; - aiWillEl.textContent = '—'; - aiConfEl.textContent = '—'; - } else { - // Show what AI predicts player will do NEXT - const ai = getAdaptiveComputerChoice(); - if (ai.predicted) { - aiPredEl.textContent = emojis[ai.predicted] + ' ' + capitalize(ai.predicted); - aiWillEl.textContent = emojis[ai.choice] + ' ' + capitalize(ai.choice); - aiConfEl.textContent = ai.confidence; - } - } - } - - // History dots (last 15 results) - if (lastResult !== null) { - // We store the per-round outcome - const dot = document.createElement('span'); - dot.className = 'ai-history-dot ' + lastResult; - dot.title = lastResult; - aiHistoryBar.appendChild(dot); - // Keep at most 15 - while (aiHistoryBar.children.length > 15) { - aiHistoryBar.removeChild(aiHistoryBar.firstChild); - } - } - } - - // ── Core round logic ────────────────────────────────────────────── + // --- Play Round with Animation --- function playRound(playerChoice) { - // Get AI decision BEFORE pushing player move (prediction is based on previous history) - const ai = getAdaptiveComputerChoice(); - const computerChoice = ai.choice; + isAnimating = true; + choiceBtns.forEach(b => b.classList.add('rps-btn-disabled')); - // Record player move + // Record history playerHistory.push(playerChoice); - if (playerHistory.length > HISTORY_CAP) playerHistory.shift(); storage.saveToStorage('rpsPlayerHistory', playerHistory); - // Highlight computer card - document.querySelectorAll('.comp-card').forEach(c => c.classList.remove('selected')); - document.getElementById(`comp-${computerChoice}`).classList.add('selected'); - document.getElementById('playerChoice').textContent = emojis[playerChoice]; - - // Determine result - let result, outcomeClass; - stats.gamesPlayed++; - - if (playerChoice === computerChoice) { - result = "It's a tie! 🤝"; - outcomeClass = 'draw'; - drawScore++; - } else if (beatenBy[computerChoice] === playerChoice) { - // player wins - result = 'You win! 🎉'; - outcomeClass = 'win'; - playerScore++; - stats.wins++; - stats.currentStreak++; - if (stats.currentStreak > stats.bestStreak) stats.bestStreak = stats.currentStreak; - if (playerScore > bestScore) { - bestScore = playerScore; - storage.saveToStorage('rpsBestScore', bestScore); - } - } else { - result = 'Computer wins! 🤖'; - outcomeClass = 'loss'; - computerScore++; - stats.losses++; - stats.currentStreak = 0; - } - - document.getElementById('resultMessage').textContent = result; - updateScore(); - saveRpsStats(); - updateStatsDisplay(); - updateBestScore(); - updateBrainPanel(outcomeClass); - } - - // ── Helpers ─────────────────────────────────────────────────────── - function capitalize(str) { - return str.charAt(0).toUpperCase() + str.slice(1); + // Reset visuals + playerFighter.classList.remove('rps-win', 'rps-lose'); + computerFighter.classList.remove('rps-win', 'rps-lose'); + clashEffect.className = 'rps-clash-effect'; + clashEffect.textContent = ''; + resultEl.className = 'rps-result'; + resultEl.textContent = '...'; + + // Show player choice immediately + playerChoiceEl.textContent = emojis[playerChoice]; + + // Shake animation for computer + computerChoiceEl.textContent = '❓'; + computerFighter.classList.add('rps-shake'); + + // Cycle through random emojis during shake + let cycleCount = 0; + const cycleInterval = setInterval(() => { + computerChoiceEl.textContent = emojis[choices[cycleCount % 3]]; + cycleCount++; + }, 80); + + // Reveal after delay + setTimeout(() => { + clearInterval(cycleInterval); + computerFighter.classList.remove('rps-shake'); + + const computerChoice = getAdaptiveComputerChoice(); + computerChoiceEl.textContent = emojis[computerChoice]; + + // Determine result + let result = ''; + let resultClass = ''; + let clashEmoji = ''; + + if (playerChoice === computerChoice) { + result = "It's a tie! 🤝"; + resultClass = 'rps-result-tie'; + clashEmoji = '🤝'; + stats.gamesPlayed++; + } else if ( + (playerChoice === 'rock' && computerChoice === 'scissors') || + (playerChoice === 'paper' && computerChoice === 'rock') || + (playerChoice === 'scissors' && computerChoice === 'paper') + ) { + result = 'You win! 🎉'; + resultClass = 'rps-result-win'; + clashEmoji = '💥'; + playerScore++; + stats.gamesPlayed++; + stats.wins++; + stats.currentStreak++; + if (stats.currentStreak > stats.bestStreak) { + stats.bestStreak = stats.currentStreak; + } + if (playerScore > bestScore) { + bestScore = playerScore; + storage.saveToStorage('rpsBestScore', bestScore); + } + playerFighter.classList.add('rps-win'); + computerFighter.classList.add('rps-lose'); + } else { + result = 'Computer wins! 🤖'; + resultClass = 'rps-result-lose'; + clashEmoji = '💀'; + computerScore++; + stats.gamesPlayed++; + stats.losses++; + stats.currentStreak = 0; + computerFighter.classList.add('rps-win'); + playerFighter.classList.add('rps-lose'); + } + + // Show clash effect + clashEffect.textContent = clashEmoji; + clashEffect.classList.add('rps-clash-show'); + + // Show result + resultEl.textContent = result; + resultEl.classList.add(resultClass); + + updateScore(); + saveRpsStats(); + updateStatsDisplay(); + updateTendencyDisplay(); + + // Re-enable buttons + setTimeout(() => { + isAnimating = false; + choiceBtns.forEach(b => b.classList.remove('rps-btn-disabled')); + clashEffect.classList.remove('rps-clash-show'); + }, 400); + + }, 600); } + // --- UI Helpers --- function updateScore() { - document.getElementById('playerScore').textContent = playerScore; + document.getElementById('playerScore').textContent = playerScore; document.getElementById('computerScore').textContent = computerScore; - if (drawScoreDisplay) drawScoreDisplay.textContent = drawScore; } function updateStatsDisplay() { - gamesPlayedDisplay.textContent = stats.gamesPlayed; - winsDisplay.textContent = stats.wins; - lossesDisplay.textContent = stats.losses; + gamesPlayedDisplay.textContent = stats.gamesPlayed; + winsDisplay.textContent = stats.wins; + lossesDisplay.textContent = stats.losses; currentStreakDisplay.textContent = stats.currentStreak; - bestStreakDisplay.textContent = stats.bestStreak; - } - - function updateBestScore() { - bestScoreDisplay.textContent = bestScore || 0; + bestStreakDisplay.textContent = stats.bestStreak; } function saveRpsStats() { storage.saveToStorage('rpsStats', stats); } - function resetGame() { - playerScore = 0; - computerScore = 0; - drawScore = 0; - - stats.gamesPlayed = 0; - stats.wins = 0; - stats.losses = 0; - stats.currentStreak = 0; - - playerHistory = []; - storage.saveToStorage('rpsPlayerHistory', []); + function updateTendencyDisplay() { + const total = playerHistory.length; + const freq = { rock: 0, paper: 0, scissors: 0 }; + playerHistory.forEach(m => freq[m]++); - updateScore(); - updateStatsDisplay(); - saveRpsStats(); + const rockPct = total > 0 ? Math.round((freq.rock / total) * 100) : 33; + const paperPct = total > 0 ? Math.round((freq.paper / total) * 100) : 33; + const scissorsPct = total > 0 ? Math.round((freq.scissors / total) * 100) : 34; - document.getElementById('resultMessage').textContent = 'Make your choice!'; - document.getElementById('playerChoice').textContent = '❓'; - document.querySelectorAll('.comp-card').forEach(c => c.classList.remove('selected')); + document.getElementById('tendRock').style.flex = Math.max(freq.rock, 0.3); + document.getElementById('tendPaper').style.flex = Math.max(freq.paper, 0.3); + document.getElementById('tendScissors').style.flex = Math.max(freq.scissors, 0.3); - // Reset brain panel - aiHistoryBar.innerHTML = ''; - updateBrainPanel(null); + document.getElementById('tendRockPct').textContent = rockPct + '%'; + document.getElementById('tendPaperPct').textContent = paperPct + '%'; + document.getElementById('tendScissorsPct').textContent = scissorsPct + '%'; } } \ No newline at end of file diff --git a/web-app/js/projects/snake.js b/web-app/js/projects/snake.js index fb4a23d..8946793 100644 --- a/web-app/js/projects/snake.js +++ b/web-app/js/projects/snake.js @@ -305,36 +305,8 @@ function restartGame() { function initSnakeGame() { window.requestAnimationFrame(main); - // Set initial historic rendering metrics + // Set initial rendering metrics updateBestScoreUI(); - function restartGame() { - // Hide game over overlay - document.getElementById('game-over-overlay').classList.add('hidden'); - - // Reset snake - snakeArr = [{ x: 13, y: 10 }]; - - // Reset score - score = 0; - document.getElementById('score').innerHTML = score; - - // Reset direction and start moving - direction = { x: 1, y: 0 }; - - // Generate new food - food = { - x: Math.round(2 + (16 - 2) * Math.random()), - y: Math.round(2 + (16 - 2) * Math.random()) - }; - - // Re-enable difficulty selection before start - if (selector) { - selector.disabled = true; - } - - // Reset frame timing - lastPaintTime = 0; -} // Map difficulty listener parameters const selector = document.getElementById('difficultySelect'); @@ -345,11 +317,7 @@ function initSnakeGame() { applyDifficultySettings(); document.getElementById('startGameBtn').addEventListener('click', () => { - applyDifficultySettings(); - // Prevent changing parameter matrices on an active engine run - if (selector) selector.disabled = true; - document.getElementById('game-over-overlay').classList.add('hidden'); - direction = { x: 1, y: 0 }; // Start moving right + restartGame(); }); document.getElementById('restartSnakeBtn').addEventListener('click', restartGame); @@ -357,7 +325,7 @@ function initSnakeGame() { document.getElementById('overlayRestartBtn').addEventListener('click', restartGame); window.addEventListener('keydown', e => { - // Change difficulty selection dynamic evaluations if arrow key registers + // Disable difficulty selection once arrow keys are pressed if (["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"].includes(e.key)) { if (selector && !selector.disabled) { selector.disabled = true; From 63e638b227c0a95f7b0f87359d081cbd324de9c3 Mon Sep 17 00:00:00 2001 From: Mudita Date: Fri, 22 May 2026 19:44:10 +0530 Subject: [PATCH 4/4] gitignore: ignore node_modules/ --- .gitignore | Bin 4771 -> 4785 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/.gitignore b/.gitignore index f85fe70be845a5abd8a0595584fa8711b8efcb4f..18d13dfff48db907c114e1c9a04ddeb701dd4451 100644 GIT binary patch delta 65 zcmZ3ix>0pQxR4MR7XuepUVchyd~SY9X-;af{^V#OH6@@ZLk2@8LpqQwW=LkpXDDJw OWzb_NVW$8XxDX337cax)Od&N!-pLb$WLbC_xEKIpR0WR!