diff --git a/client/src/app.jsx b/client/src/app.jsx index 416cd5c..d835ff5 100644 --- a/client/src/app.jsx +++ b/client/src/app.jsx @@ -8,6 +8,8 @@ import authService from './services/api/authService'; // FIXED: Capital S import './styles/global.css'; import GameRoom from './pages/GameRoom'; import MatchLobby from './pages/MatchLobby'; +import TestRoomPage from "./pages/TestRoomPage"; +import { AlignVerticalDistributeStartIcon } from 'lucide-react'; // Create context FIRST export const AppContext = React.createContext(); @@ -16,6 +18,7 @@ const App = () => { const [currentPage, setCurrentPage] = useState('landing'); const [user, setUser] = useState(null); const [isLoading, setIsLoading] = useState(true); + const [roomData, setRoomData] = useState(null); // Initialize app with proper auth service useEffect(() => { @@ -25,13 +28,15 @@ const App = () => { // Use proper auth service const authResult = await authService.initialize(); - + //console.log('authResult.user in App.jsx:', authResult.user); + if (authResult.authenticated) { console.log('User is authenticated:', authResult.user); setUser(authResult.user); // Navigate to proper page based on setup status if (!authResult.user.setupComplete) { + console.log('user setup status', authResult.user.setupComplete); setCurrentPage('profile-setup'); } else { setCurrentPage('dashboard'); @@ -54,23 +59,36 @@ const App = () => { }, []); // Navigation function - const navigate = (page) => { - console.log('Navigating to:', page); + const navigate = (page,payload) => { + console.log('Navigating to:', page, payload); + if (page === 'game-room') { + setRoomData(payload); + } setCurrentPage(page); }; // Enhanced login function - const handleLogin = (userData) => { + const handleLogin = async (userData) => { console.log('Login successful in App.jsx:', userData); - setUser(userData); - - // Navigate based on setup completion - if (!userData.setupComplete) { - console.log('User needs setup, navigating to profile-setup'); - navigate('profile-setup'); + setUser(userData); // temporary, need to get database user info + + const result = await authService.getCurrentUserFromServer(); + if (result.success) { + const dbUser = result.user; + console.log('Database user fetched:', dbUser); + setUser(dbUser); + + // Navigate based on setup completion + if (!dbUser.setupComplete) { + console.log('User needs setup, navigating to profile-setup'); + navigate('profile-setup'); + } else { + console.log('user setup complete, navigating to dashboard'); + navigate('dashboard'); + } } else { - console.log('User setup complete, navigating to dashboard'); - navigate('dashboard'); + console.error('Failed to fetch user from server:', result.error); + navigate('landing'); } }; @@ -109,7 +127,9 @@ const App = () => { navigate, handleLogin, handleLogout, - currentPage + currentPage, + roomData, + setRoomData }; // Page routing @@ -166,7 +186,8 @@ const App = () => { ); @@ -179,6 +200,14 @@ const App = () => { /> ); + case 'test-room': + return ( + + ); + default: return ( { // Game state const [gameState, setGameState] = useState('waiting'); // waiting, active, completed @@ -17,6 +18,8 @@ const GameRoom = ({ navigate, user, roomData }) => { const [isSubmitting, setIsSubmitting] = useState(false); const [connectionStatus, setConnectionStatus] = useState('connecting'); const [gameResult, setGameResult] = useState(null); + const [submitTimestamp, setSubmitTimestamp] = useState(null); + // Opponent state const [opponent, setOpponent] = useState({ @@ -39,31 +42,7 @@ const GameRoom = ({ navigate, user, roomData }) => { const progressTimeoutRef = useRef(null); // Sample problem (will come from server) - const [problem, setProblem] = useState({ - title: "Two Sum", - difficulty: "Easy", - description: `Given an array of integers nums and an integer target, return indices of the two numbers such that they add up to target. - -You may assume that each input would have exactly one solution, and you may not use the same element twice. - -You can return the answer in any order.`, - examples: [ - { - input: "nums = [2,7,11,15], target = 9", - output: "[0,1]", - explanation: "Because nums[0] + nums[1] == 9, we return [0, 1]." - }, - { - input: "nums = [3,2,4], target = 6", - output: "[1,2]" - } - ], - constraints: [ - "2 ≤ nums.length ≤ 104", - "-109 ≤ nums[i] ≤ 109", - "-109 ≤ target ≤ 109" - ] - }); + const [problem, setProblem] = useState(null); // Starter code const starterCode = { @@ -93,24 +72,6 @@ You can return the answer in any order.`, gameSocket.connect(user); } - // Join match if we have roomData with matchCode (William's logic) - if (roomData?.matchCode) { - console.log('Joining match with code:', roomData.matchCode); - gameSocket.socket.emit("join_match", { - matchCode: roomData.matchCode, - username: user?.username || 'Anonymous' - }); - } - - // Join room if we have roomData with roomCode - if (roomData?.roomCode) { - console.log('Joining room with code:', roomData.roomCode); - gameSocket.joinRoom(roomData.roomCode, { - id: user?.id, - username: user?.username - }); - } - /** * Socket Event Handlers */ @@ -133,10 +94,12 @@ You can return the answer in any order.`, // William's start_game event const handleStartGame = (data) => { - console.log('Game starting with problem:', data); + console.log('🔥 [GameRoom] start_game payload:', data); setGameState('active'); if (data.problem) { + console.log('📦 [GameRoom] setting problem:', data.problem); setProblem(data.problem); + setCode(data.problem.functionSignature[language] || ''); } setTimeLeft(data.timeLimit || 600); setChatMessages([{ @@ -220,10 +183,35 @@ You can return the answer in any order.`, }; // Game end events - const handleGameEnded = (data) => { + const handleGameEnded = async (data) => { console.log('Game ended:', data); setGameState('completed'); setGameResult(data); + const isWin = data.winner === user.id; + // update user stats (wins/winstreak) + try { + const response = await fetch('/api/user/update-stats', { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + googleId: user.googleId, + isWin + }) + }); + + const result = await response.json(); + if (!response.ok) { + console.error('Failed to update user stats:', result.error); + return; + } + console.log('User stats updated successfully:', result); + // Update local user state + setUser(result.user); + + } catch (error) { + console.error('Error updating user stats:', error); + } + }; // Chat events @@ -265,11 +253,36 @@ You can return the answer in any order.`, gameSocket.on('chat_message', handleChatMessage); gameSocket.on('error', handleError); + const onSubmissionResult = ({ output, allPassed, winner }) => { + handleSubmissionResult({ output }); // still uses your existing parser + setIsSubmitting(false); // stop the spinner + if (allPassed) { + setGameState('completed'); + setGameResult({ + winner, + reason: 'solution_correct', + solveTime: Date.now() - submitTimestamp, + coinsEarned: 50, + xpEarned: 25 + }); + } +}; + +gameSocket.on('submission_result', onSubmissionResult); + + // William's direct socket events - if (gameSocket.socket) { - gameSocket.socket.on('start_game', handleStartGame); - gameSocket.socket.on('server_message', handleServerMessage); - } + const handleSubmissionResult = ({ passed, output }) => { + const lines = output.split(/\r?\n/); + const results = lines + .map(raw => { + const m = raw.match(/^(✅|❌)\s*Test\s*(\d+):\s*(.+)$/); + return m ? { id: Number(m[2]), passed: m[1] === '✅', text: raw } : null; + }) + .filter(r => r !== null); + setTestResults(results); + setIsSubmitting(false); // this line is OK to keep here too for fallback + }; /** * Cleanup function @@ -290,6 +303,10 @@ You can return the answer in any order.`, gameSocket.off('game_ended', handleGameEnded); gameSocket.off('chat_message', handleChatMessage); gameSocket.off('error', handleError); + gameSocket.off('submission_result', onSubmissionResult); + + + // Remove William's direct socket events if (gameSocket.socket) { @@ -307,6 +324,18 @@ You can return the answer in any order.`, }; }, [user, roomData]); + useEffect(() => { + if (roomData?.problem) { + console.log('💡 [GameRoom] init from navigation:', roomData.problem); + setProblem(roomData.problem); + setGameState('active'); + setCode(roomData.problem.functionSignature[language] || ''); + if (roomData.timeLimit) { + setTimeLeft(roomData.timeLimit); + } + } +}, [roomData, language]); + /** * Timer effect - handles countdown */ @@ -327,14 +356,15 @@ You can return the answer in any order.`, } }, [gameState, timeLeft]); - /** - * Initialize code when language changes - */ + //Restart de code every time you change language useEffect(() => { - setCode(starterCode[language] || ''); - }, [language]); + if (problem) { + setCode(problem.functionSignature[language] || ''); + } + }, [language, problem]); + - /** + /* * Handle typing indicators and progress updates */ const handleCodeChange = useCallback((newCode) => { @@ -386,39 +416,19 @@ You can return the answer in any order.`, */ const handleSubmit = async () => { if (!code.trim()) return; - + + setSubmitTimestamp(Date.now()); setIsSubmitting(true); // Send submission via socket gameSocket.submitSolution({ + roomCode: roomData.roomCode, code: code.trim(), language, submissionTime: Date.now() }); // Simulate test execution (real implementation would wait for server response) - setTimeout(() => { - const mockResults = [ - { id: 1, input: '[2,7,11,15], 9', expected: '[0,1]', actual: '[0,1]', passed: true }, - { id: 2, input: '[3,2,4], 6', expected: '[1,2]', actual: '[1,2]', passed: true }, - { id: 3, input: '[3,3], 6', expected: '[0,1]', actual: '[0,1]', passed: true } - ]; - - setTestResults(mockResults); - setIsSubmitting(false); - - // If all tests pass, game ends with victory - if (mockResults.every(r => r.passed)) { - setGameState('completed'); - setGameResult({ - winner: user.id, - reason: 'solution_correct', - solveTime: 600 - timeLeft, - coinsEarned: 50, - xpEarned: 25 - }); - } - }, 2000); }; /** @@ -494,7 +504,18 @@ You can return the answer in any order.`, /** * Render problem panel */ - const renderProblemPanel = () => ( +const renderProblemPanel = () => { + // show loading until we find problem + if (!problem) { + return ( +
+

Waiting for problem…

+
+ ); + } + + // once we get it, we renderize it + return (
@@ -508,7 +529,7 @@ You can return the answer in any order.`,

{problem.description}

- {problem.examples.map((example, index) => ( + {problem.examples?.map((example, index) => (
Example {index + 1}:
@@ -524,15 +545,16 @@ You can return the answer in any order.`,
Constraints:
-
    - {problem.constraints.map((constraint, index) => ( -
  • • {constraint}
  • - ))} -
+
    + {problem.constraints?.map((constraint, index) => ( +
  • {constraint}
  • + ))} +
+
-
); +}; /** * Render code editor panel @@ -599,31 +621,33 @@ You can return the answer in any order.`, />
- {/* Test Results */} - {testResults.length > 0 && ( -
-
Test Results:
-
- {testResults.map((result) => ( -
- {result.passed ? ( - - ) : ( - - )} - - Test {result.id}: {result.input} - - {!result.passed && ( - - Expected {result.expected}, got {result.actual} - - )} -
- ))} -
+{/* Test Results */} +{testResults.length > 0 && ( +
+
Test Results:
+
+ {testResults.map(r => ( +
+ {r.passed ? ( + + ) : ( + + )} + {/* Aquí mostramos la línea completa del servidor */} + {r.text}
- )} + ))} +
+ {/* Resumen igual al estilo que prefieres */} +
+ Result: {testResults.filter(r => r.passed).length}/{testResults.length} tests passed.{" "} + {testResults.every(r => r.passed) + ? All passed 🎉 + : Some failed ✗} +
+
+)} +
); diff --git a/client/src/pages/Login.jsx b/client/src/pages/Login.jsx index 9591c4e..a06bffc 100644 --- a/client/src/pages/Login.jsx +++ b/client/src/pages/Login.jsx @@ -10,13 +10,17 @@ const Login = ({ navigate, onLogin }) => { // here we are going to send the unique googleId to the backend to find or create a user try { + console.log('The name is this:', user.name); console.log("THE USER ID IS THIS:", user.googleId); const response = await fetch('/api/user/auth/google', { method: 'Post', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ googleId: user.googleId }) + body: JSON.stringify({ + googleId: user.googleId, + name: user.username + }) }); const dbUser = await response.json(); @@ -27,8 +31,6 @@ const Login = ({ navigate, onLogin }) => { console.error('Error logging in with Google:', error); } - // Call the parent's onLogin function which handles navigation - onLogin(user); // Don't navigate here - let App.jsx handle it via onLogin }; diff --git a/client/src/pages/MatchLobby.jsx b/client/src/pages/MatchLobby.jsx index e903462..8be9fe1 100644 --- a/client/src/pages/MatchLobby.jsx +++ b/client/src/pages/MatchLobby.jsx @@ -25,6 +25,10 @@ const MatchLobby = ({ navigate, user, mode = 'quick' }) => { const [success, setSuccess] = useState(''); const [connectionStatus, setConnectionStatus] = useState('disconnected'); const [playersInQueue, setPlayersInQueue] = useState(0); + + const [language, setLanguage] = useState('python'); // python by defect + const [category, setCategory] = useState(''); // empty means any + const topics = ['arrays','linked list','graphs','trees']; // Available difficulty levels for problems const difficulties = [ @@ -93,12 +97,7 @@ const MatchLobby = ({ navigate, user, mode = 'quick' }) => { const handleMatchFound = (data) => { console.log('Match found:', data); setIsSearching(false); - setSuccess('Match found! Starting game...'); - - // Navigate to game room after brief success message - setTimeout(() => { - navigate('game-room', { matchData: data }); - }, 1500); + setSuccess('Match found! Waiting for both players…'); }; const handleMatchCancelled = (data) => { @@ -122,14 +121,18 @@ const MatchLobby = ({ navigate, user, mode = 'quick' }) => { const handleRoomJoined = (data) => { console.log('Room joined:', data); + setRoomCode(data.roomCode); setSuccess('Room joined! Waiting for opponent...'); - // If room has 2 players, start the game - if (data.playerCount >= 2) { - setTimeout(() => { - navigate('game-room', { roomData: data }); - }, 1000); - } + }; + + const handleStartGame = ({ roomCode: rc, problem, timeLimit }) => { + console.log('🔥 [MatchLobby] start_game received:', { rc, problem, timeLimit }); + navigate('game-room', { + roomCode: rc, + problem, + timeLimit: timeLimit || 600 + }); }; const handleRoomError = (data) => { @@ -137,10 +140,6 @@ const MatchLobby = ({ navigate, user, mode = 'quick' }) => { setError(data.message || 'Room error occurred.'); }; - const handleGameStarted = (data) => { - console.log('Game started:', data); - navigate('game-room', { gameData: data }); - }; const handleError = (data) => { console.error('Socket error:', data); @@ -154,8 +153,9 @@ const MatchLobby = ({ navigate, user, mode = 'quick' }) => { gameSocket.on('queue_update', handleQueueUpdate); gameSocket.on('room_created', handleRoomCreated); gameSocket.on('room_joined', handleRoomJoined); + gameSocket.on('start_game', handleStartGame); gameSocket.on('room_error', handleRoomError); - gameSocket.on('game_started', handleGameStarted); + //gameSocket.on('game_started', handleGameStarted); gameSocket.on('error', handleError); // Cleanup function - removes event listeners when component unmounts @@ -168,8 +168,9 @@ const MatchLobby = ({ navigate, user, mode = 'quick' }) => { gameSocket.off('queue_update', handleQueueUpdate); gameSocket.off('room_created', handleRoomCreated); gameSocket.off('room_joined', handleRoomJoined); + gameSocket.off('start_game', handleStartGame); gameSocket.off('room_error', handleRoomError); - gameSocket.off('game_started', handleGameStarted); + //gameSocket.off('game_started', handleGameStarted); gameSocket.off('error', handleError); // Leave any active matchmaking when component unmounts @@ -200,23 +201,23 @@ const MatchLobby = ({ navigate, user, mode = 'quick' }) => { * Create a private room with selected difficulty */ const generateRoomCode = () => { - // Check connection before attempting to create room - if (!gameSocket.isSocketConnected()) { - setError('Please wait for connection to establish.'); - return; - } - - console.log('Creating room with difficulty:', difficulty); - - // Use GameSocket class method to create room - gameSocket.createRoom({ - difficulty: difficulty, - creator: { - id: user?.id, - username: user?.username + if (!gameSocket.isSocketConnected()) { + setError('Please wait for connection to establish.'); + return; } - }); - }; + + console.log('Creating room with:', { difficulty, language, category }); + + gameSocket.createRoom({ + difficulty, + language, + category, + creator: { + id: user.id, + username: user.username + } + }); + }; /** * Copy room code to clipboard for sharing @@ -434,6 +435,37 @@ const MatchLobby = ({ navigate, user, mode = 'quick' }) => {
+ {/* Topic Selection */} +
+

Choose Topic

+
+ {topics.map(t => ( + + ))} + {/* Botón para "Any" */} + +
+
+ + + {createdCode && ( +

+ Your room code: {createdCode} +

+ )} + +
+ setRoomCode(e.target.value.toUpperCase())} + className="px-2 py-1 text-black rounded" + /> + +
+ + ); +}; + +export default TestRoomPage; diff --git a/client/src/services/api/authService.js b/client/src/services/api/authService.js index 3f944a0..35cd90a 100644 --- a/client/src/services/api/authService.js +++ b/client/src/services/api/authService.js @@ -77,18 +77,23 @@ class AuthService { // Update profile async updateProfile(profileData) { try { - const response = await api.put('/api/auth/profile', profileData); - const { user } = response.data; - - // Update stored user data - localStorage.setItem('codeclash_user', JSON.stringify(user)); - - return { success: true, user }; - } catch (error) { - return { - success: false, - error: error.response?.data?.message || 'Profile update failed' - }; + const user = this.getCurrentUser(); + if (!user) { + throw new Error('User not found'); + } + const response = await api.put('/api/user/profile', { + ...profileData, + googleId: user.googleId, + }); + + const {user: updatedUser} = response.data; + localStorage.setItem('codeclash_user', JSON.stringify(updatedUser)); + return { success: true, user: updatedUser }; + } catch (error) { + return { + success: false, + error: error.response?.data?.message || 'Failed to update profile' + }; } } diff --git a/client/src/sockets/socket.js b/client/src/sockets/socket.js index 40883fd..7f16468 100644 --- a/client/src/sockets/socket.js +++ b/client/src/sockets/socket.js @@ -104,6 +104,11 @@ class GameSocket { this.emit('game_started', data); }); + this.socket.on('start_game', (data) => { + console.log('🔔 [GameSocket] raw start_game:', data); + this.emit('start_game', data); + }); + this.socket.on('opponent_joined', (data) => { console.log('Opponent joined:', data); this.emit('opponent_joined', data); @@ -190,6 +195,8 @@ class GameSocket { console.log('Creating room:', roomData); this.socket.emit('create_room', { difficulty: roomData.difficulty, + language: roomData.language, + category: roomData.category, isPrivate: true, creator: roomData.creator }); @@ -261,8 +268,9 @@ class GameSocket { return; } - console.log('Submitting solution'); + console.log('Submitting solution', solutionData); this.socket.emit('submit_solution', { + roomCode: solutionData.roomCode, code: solutionData.code, language: solutionData.language, submissionTime: Date.now() diff --git a/package-lock.json b/package-lock.json index 741e60e..e44b55e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5729,9 +5729,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001727", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001727.tgz", - "integrity": "sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q==", + "version": "1.0.30001731", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001731.tgz", + "integrity": "sha512-lDdp2/wrOmTRWuoB5DpfNkC0rJDU8DqRa6nYL6HK6sytw70QMopt/NIc/9SM7ylItlBWfACXk0tEn37UWM/+mg==", "funding": [ { "type": "opencollective", @@ -7156,9 +7156,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.191", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.191.tgz", - "integrity": "sha512-xcwe9ELcuxYLUFqZZxL19Z6HVKcvNkIwhbHUz7L3us6u12yR+7uY89dSl570f/IqNthx8dAw3tojG7i4Ni4tDA==", + "version": "1.5.192", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.192.tgz", + "integrity": "sha512-rP8Ez0w7UNw/9j5eSXCe10o1g/8B1P5SM90PCCMVkIRQn2R0LEHWz4Eh9RnxkniuDe1W0cTSOB3MLlkTGDcuCg==", "license": "ISC" }, "node_modules/emittery": { @@ -18609,6 +18609,7 @@ "version": "0.8.0-beta.0", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz", "integrity": "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==", + "deprecated": "The work that was done in this beta branch won't be included in future versions", "license": "BSD-3-Clause", "dependencies": { "whatwg-url": "^7.0.0" @@ -18979,6 +18980,7 @@ "name": "codeclash-server", "version": "1.0.0", "dependencies": { + "axios": "^1.11.0", "cors": "^2.8.5", "dotenv": "^16.3.1", "express": "^4.18.2", diff --git a/server/app.js b/server/app.js index 2d62fc4..c26add4 100644 --- a/server/app.js +++ b/server/app.js @@ -3,6 +3,7 @@ const express = require("express"); const http = require("http"); const cors = require("cors"); const { Server } = require("socket.io"); +const matchSocketHandler = require("./sockets/matchSocket"); require('dotenv').config(); @@ -14,9 +15,15 @@ const gameRoutes = require("./routes/gameRoutes"); const app = express(); const server = http.createServer(app); +const allowedOrigins = [ + 'https://code-clash-tan.vercel.app', + 'https://code-clash-89bb.onrender.com', + 'http://localhost:3000' +]; + // CORS app.use(cors({ - origin: "http://localhost:3000", + origin: allowedOrigins, credentials: true })); @@ -46,11 +53,16 @@ app.use((error, req, res, next) => { // Socket.IO const io = new Server(server, { - cors: { origin: "http://localhost:3000" } + cors: { origin: process.env.NODE_ENV === 'production' ? + "https://code-clash-tan.vercel.app/" : + "http://localhost:3000" } }); io.on("connection", (socket) => { console.log(`User connected: ${socket.id}`); + + matchSocketHandler(socket, io); + socket.on("disconnect", () => { console.log(`User disconnected: ${socket.id}`); }); diff --git a/server/controllers/userController.js b/server/controllers/userController.js index 2448b29..2f22553 100644 --- a/server/controllers/userController.js +++ b/server/controllers/userController.js @@ -13,7 +13,7 @@ const { User } = require('../models'); // Function to find or create a user based on Google ID // Here we use async to handle asynchronous DB operations -async function findOrCreateUser(googleId) { +async function findOrCreateUser(googleId, googleName) { // first, check if the user already exists try { const existingUser = await User.findOne({ where: { googleId: googleId } }); @@ -24,7 +24,10 @@ async function findOrCreateUser(googleId) { } // if user does not exist, create a new user console.log('User not found, creating new user'); - const newUser = await User.create({ googleId: googleId }); + const newUser = await User.create({ + googleId: googleId, + displayName: googleName + }); console.log('New user created'); return newUser; diff --git a/server/data/problems.json b/server/data/problems.json index c2a375e..2b5aa60 100644 --- a/server/data/problems.json +++ b/server/data/problems.json @@ -11,6 +11,23 @@ "java": "public int[] twoSum(int[] nums, int target) {\n // your code here\n return new int[2];\n}", "cpp": "vector twoSum(vector& nums, int target) {\n // your code here\n return {};\n}" }, + "examples": [ + { + "input": "[2,7,11,15], 9", + "output": "[0,1]", + "explanation": "The numbers at indices 0 and 1 add up to 9." + }, + { + "input": "[3,2,4], 6", + "output": "[1,2]", + "explanation": "Elements at index 1 and 2 sum to 6." + } + ], + "constraints": [ + "2 <= nums.length <= 10^4", + "-10^9 <= nums[i] <= 10^9", + "Exactly one valid answer exists" + ], "testCases": [ { "input": "[2,7,11,15]\n9", "expectedOutputs": ["[0,1]"] }, { "input": "[3,2,4]\n6", "expectedOutputs": ["[1,2]"] }, @@ -36,6 +53,23 @@ "java": "public ListNode addTwoNumbers(ListNode l1, ListNode l2) {\n // your code here\n return null;\n}", "cpp": "ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {\n // your code here\n return nullptr;\n}" }, + "examples": [ + { + "input": "[2,4,3], [5,6,4]", + "output": "[7,0,8]", + "explanation": "342 + 465 = 807, stored in reverse." + }, + { + "input": "[0], [0]", + "output": "[0]", + "explanation": "0 + 0 = 0." + } + ], + "constraints": [ + "Each list is non-empty", + "0 <= Node.val <= 9", + "Digits stored in reverse order" + ], "testCases": [ { "input": "[2,4,3]\n[5,6,4]", "expectedOutputs": ["[7,0,8]"] }, { "input": "[0]\n[0]", "expectedOutputs": ["[0]"] }, @@ -61,6 +95,22 @@ "java": "public List inorderTraversal(TreeNode root) {\n // your code here\n return new ArrayList<>();\n}", "cpp": "vector inorderTraversal(TreeNode* root) {\n // your code here\n return {};\n}" }, + "examples": [ + { + "input": "[1,null,2,3]", + "output": "[1,3,2]", + "explanation": "Inorder of [1,null,2,3] is [1,3,2]." + }, + { + "input": "[]", + "output": "[]", + "explanation": "Empty tree returns empty traversal." + } + ], + "constraints": [ + "Number of nodes ≤ 100", + "-100 <= Node.val <= 100" + ], "testCases": [ { "input": "[1,null,2,3]", "expectedOutputs": ["[1,3,2]"] }, { "input": "[]", "expectedOutputs": ["[]"] }, @@ -86,6 +136,24 @@ "java": "public int countComponents(int n, int[][] edges) {\n // your code here\n return 0;\n}", "cpp": "int countComponents(int n, vector>& edges) {\n // your code here\n return 0;\n}" }, + "examples": [ + { + "input": "n = 5, edges = [[0,1],[1,2],[3,4]]", + "output": "2", + "explanation": "There are 2 disconnected components." + }, + { + "input": "n = 5, edges = [[0,1],[1,2],[2,3],[3,4]]", + "output": "1", + "explanation": "All nodes are connected." + } + ], + "constraints": [ + "1 <= n <= 2000", + "1 <= edges.length <= 5000", + "0 <= edges[i][0], edges[i][1] < n", + "No duplicate edges" + ], "testCases": [ { "input": "5\n[[0,1],[1,2],[3,4]]", "expectedOutputs": ["2"] }, { "input": "5\n[[0,1],[1,2],[2,3],[3,4]]", "expectedOutputs": ["1"] }, @@ -99,31 +167,6 @@ { "input": "4\n[[0,1]]", "expectedOutputs": ["3"] } ] }, - { - "id": 6, - "title": "Best Time to Buy and Sell Stock", - "category": "arrays", - "difficulty": "easy", - "description": "You are given an array prices where prices[i] is the price of a given stock on the ith day. You want to maximize your profit by choosing a single day to buy one stock and choosing a different day in the future to sell that stock. Return the maximum profit you can achieve from this transaction. If you cannot achieve any profit, return 0.", - "functionSignature": { - "javascript": "function maxProfit(prices) {\n // your code here\n}", - "python": "def max_profit(prices):\n # your code here\n pass", - "java": "public int maxProfit(int[] prices) {\n // your code here\n return 0;\n}", - "cpp": "int maxProfit(vector& prices) {\n // your code here\n return 0;\n}" - }, - "testCases": [ - { "input": "[7,1,5,3,6,4]", "expectedOutputs": ["5"] }, - { "input": "[7,6,4,3,1]", "expectedOutputs": ["0"] }, - { "input": "[1,2]", "expectedOutputs": ["1"] }, - { "input": "[2,4,1]", "expectedOutputs": ["2"] }, - { "input": "[3,2,6,5,0,3]", "expectedOutputs": ["4"] }, - { "input": "[1]", "expectedOutputs": ["0"] }, - { "input": "[]", "expectedOutputs": ["0"] }, - { "input": "[2,1,2,1,0,1,2]", "expectedOutputs": ["2"] }, - { "input": "[1,4,2,11]", "expectedOutputs": ["10"] }, - { "input": "[5,4,3,2,1]", "expectedOutputs": ["0"] } - ] - }, { "id": 7, "title": "Reverse Linked List", @@ -136,6 +179,22 @@ "java": "public ListNode reverseList(ListNode head) {\n // your code here\n return null;\n}", "cpp": "ListNode* reverseList(ListNode* head) {\n // your code here\n return nullptr;\n}" }, + "examples": [ + { + "input": "[1,2,3,4,5]", + "output": "[5,4,3,2,1]", + "explanation": "The list is reversed." + }, + { + "input": "[1,2]", + "output": "[2,1]", + "explanation": "Only two elements; order is flipped." + } + ], + "constraints": [ + "The number of nodes in the list is in the range [0, 5000]", + "-5000 <= Node.val <= 5000" + ], "testCases": [ { "input": "[1,2,3,4,5]", "expectedOutputs": ["[5,4,3,2,1]"] }, { "input": "[1,2]", "expectedOutputs": ["[2,1]"] }, @@ -161,6 +220,22 @@ "java": "public boolean isSymmetric(TreeNode root) {\n // your code here\n return false;\n}", "cpp": "bool isSymmetric(TreeNode* root) {\n // your code here\n return false;\n}" }, + "examples": [ + { + "input": "[1,2,2,3,4,4,3]", + "output": "true", + "explanation": "Mirror image on both sides." + }, + { + "input": "[1,2,2,null,3,null,3]", + "output": "false", + "explanation": "Not symmetric." + } + ], + "constraints": [ + "The number of nodes in the tree is in the range [1, 1000]", + "-100 <= Node.val <= 100" + ], "testCases": [ { "input": "[1,2,2,3,4,4,3]", "expectedOutputs": ["true"] }, { "input": "[1,2,2,null,3,null,3]", "expectedOutputs": ["false"] }, @@ -174,31 +249,7 @@ { "input": "[1,2,2,null,3,3,null]", "expectedOutputs": ["true"] } ] }, -{ - "id": 9, - "title": "Contains Duplicate", - "category": "arrays", - "difficulty": "easy", - "description": "Given an integer array nums, return true if any value appears at least twice in the array, and return false if every element is distinct.", - "functionSignature": { - "javascript": "function containsDuplicate(nums) {\n // your code here\n}", - "python": "def contains_duplicate(nums):\n # your code here\n pass", - "java": "public boolean containsDuplicate(int[] nums) {\n // your code here\n return false;\n}", - "cpp": "bool containsDuplicate(vector& nums) {\n // your code here\n return false;\n}" - }, - "testCases": [ - { "input": "[1,2,3,1]", "expectedOutputs": ["true"] }, - { "input": "[1,2,3,4]", "expectedOutputs": ["false"] }, - { "input": "[1,1,1,3,3,4,3,2,4,2]", "expectedOutputs": ["true"] }, - { "input": "[]", "expectedOutputs": ["false"] }, - { "input": "[0]", "expectedOutputs": ["false"] }, - { "input": "[1,2,3,4,5,6,7,8,9,1]", "expectedOutputs": ["true"] }, - { "input": "[1,2,3,4,5]", "expectedOutputs": ["false"] }, - { "input": "[2,14,18,22,22]", "expectedOutputs": ["true"] }, - { "input": "[99]", "expectedOutputs": ["false"] }, - { "input": "[1,2,3,4,4]", "expectedOutputs": ["true"] } - ] -}, + { "id": 10, "title": "Merge Two Sorted Lists", @@ -211,6 +262,23 @@ "java": "public ListNode mergeTwoLists(ListNode list1, ListNode list2) {\n // your code here\n return null;\n}", "cpp": "ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {\n // your code here\n return nullptr;\n}" }, + "examples": [ + { + "input": "[1,2,4], [1,3,4]", + "output": "[1,1,2,3,4,4]", + "explanation": "Merging two sorted linked lists." + }, + { + "input": "[], [0]", + "output": "[0]", + "explanation": "One list is empty." + } + ], + "constraints": [ + "The number of nodes in both lists is in the range [0, 50]", + "-100 <= Node.val <= 100", + "Both lists are sorted" + ], "testCases": [ { "input": "[1,2,4]\n[1,3,4]", "expectedOutputs": ["[1,1,2,3,4,4]"] }, { "input": "[]\n[]", "expectedOutputs": ["[]"] }, @@ -236,6 +304,22 @@ "java": "public boolean isValidBST(TreeNode root) {\n // your code here\n return false;\n}", "cpp": "bool isValidBST(TreeNode* root) {\n // your code here\n return false;\n}" }, + "examples": [ + { + "input": "[2,1,3]", + "output": "true", + "explanation": "Valid in-order sequence." + }, + { + "input": "[5,1,4,null,null,3,6]", + "output": "false", + "explanation": "Node 3 is in wrong position." + } + ], + "constraints": [ + "The number of nodes in the tree is in the range [0, 10^4]", + "-2^31 <= Node.val <= 2^31 - 1" + ], "testCases": [ { "input": "[2,1,3]", "expectedOutputs": ["true"] }, { "input": "[5,1,4,null,null,3,6]", "expectedOutputs": ["false"] }, @@ -261,6 +345,24 @@ "java": "public Node cloneGraph(Node node) {\n // your code here\n return null;\n}", "cpp": "Node* cloneGraph(Node* node) {\n // your code here\n return nullptr;\n}" }, + "examples": [ + { + "input": "[[2,4],[1,3],[2,4],[1,3]]", + "output": "[[2,4],[1,3],[2,4],[1,3]]", + "explanation": "The cloned graph structure matches the original." + }, + { + "input": "[]", + "output": "[]", + "explanation": "Empty graph returns empty." + } + ], + "constraints": [ + "The number of nodes in the graph is in the range [0, 100]", + "1 <= Node.val <= 100", + "Node.val is unique", + "There are no repeated edges and no self-loops" + ], "testCases": [ { "input": "[[2,4],[1,3],[2,4],[1,3]]", "expectedOutputs": ["[[2,4],[1,3],[2,4],[1,3]]"] }, { "input": "[]", "expectedOutputs": ["[]"] }, @@ -286,6 +388,22 @@ "java": "public boolean hasCycle(ListNode head) {\n // your code here\n return false;\n}", "cpp": "bool hasCycle(ListNode* head) {\n // your code here\n return false;\n}" }, + "examples": [ + { + "input": "[3,2,0,-4]", + "output": "true", + "explanation": "There is a cycle linking back to index 1." + }, + { + "input": "[1]", + "output": "false", + "explanation": "Single node with no cycle." + } + ], + "constraints": [ + "The number of nodes in the list is in the range [0, 10^4]", + "-10^5 <= Node.val <= 10^5" + ], "testCases": [ { "input": "[3,2,0,-4]", "expectedOutputs": ["true"] }, { "input": "[1,2]", "expectedOutputs": ["true"] }, @@ -311,6 +429,23 @@ "java": "public int findCircleNum(int[][] isConnected) {\n // your code here\n return 0;\n}", "cpp": "int findCircleNum(vector>& isConnected) {\n // your code here\n return 0;\n}" }, + "examples": [ + { + "input": "[[1,1,0],[1,1,0],[0,0,1]]", + "output": "2", + "explanation": "There are 2 separate provinces." + }, + { + "input": "[[1,0,0],[0,1,0],[0,0,1]]", + "output": "3", + "explanation": "All nodes are isolated." + } + ], + "constraints": [ + "1 <= isConnected.length <= 200", + "isConnected[i][j] is 1 or 0", + "isConnected[i][i] == 1" + ], "testCases": [ { "input": "[[1,1,0],[1,1,0],[0,0,1]]", "expectedOutputs": ["2"] }, { "input": "[[1,0,0],[0,1,0],[0,0,1]]", "expectedOutputs": ["3"] }, @@ -336,6 +471,22 @@ "java": "public int lengthOfLIS(int[] nums) {\n // your code here\n return 0;\n}", "cpp": "int lengthOfLIS(vector& nums) {\n // your code here\n return 0;\n}" }, + "examples": [ + { + "input": "[10,9,2,5,3,7,101,18]", + "output": "4", + "explanation": "The LIS is [2,3,7,101]" + }, + { + "input": "[0,1,0,3,2,3]", + "output": "4", + "explanation": "The LIS is [0,1,2,3]" + } + ], + "constraints": [ + "1 <= nums.length <= 2500", + "-10^4 <= nums[i] <= 10^4" + ], "testCases": [ { "input": "[10,9,2,5,3,7,101,18]", "expectedOutputs": ["4"] }, { "input": "[0,1,0,3,2,3]", "expectedOutputs": ["4"] }, @@ -361,6 +512,23 @@ "java": "public ListNode removeNthFromEnd(ListNode head, int n) {\n // your code here\n return null;\n}", "cpp": "ListNode* removeNthFromEnd(ListNode* head, int n) {\n // your code here\n return nullptr;\n}" }, + "examples": [ + { + "input": "[1,2,3,4,5], 2", + "output": "[1,2,3,5]", + "explanation": "Node 4 is removed." + }, + { + "input": "[1], 1", + "output": "[]", + "explanation": "Only node is removed." + } + ], + "constraints": [ + "The number of nodes in the list is in the range [1, 10^4]", + "0 <= Node.val <= 100", + "1 <= n <= length of the list" + ], "testCases": [ { "input": "[1,2,3,4,5]\n2", "expectedOutputs": ["[1,2,3,5]"] }, { "input": "[1]\n1", "expectedOutputs": ["[]"] }, @@ -386,6 +554,22 @@ "java": "public int maxDepth(TreeNode root) {\n // your code here\n return 0;\n}", "cpp": "int maxDepth(TreeNode* root) {\n // your code here\n return 0;\n}" }, + "examples": [ + { + "input": "[3,9,20,null,null,15,7]", + "output": "3", + "explanation": "The maximum depth is 3: 3 → 20 → 7." + }, + { + "input": "[1,null,2]", + "output": "2", + "explanation": "Depth is root and right child." + } + ], + "constraints": [ + "The number of nodes in the tree is in the range [0, 10^4]", + "-100 <= Node.val <= 100" + ], "testCases": [ { "input": "[3,9,20,null,null,15,7]", "expectedOutputs": ["3"] }, { "input": "[1,null,2]", "expectedOutputs": ["2"] }, @@ -411,6 +595,24 @@ "java": "public boolean canFinish(int numCourses, int[][] prerequisites) {\n // your code here\n return false;\n}", "cpp": "bool canFinish(int numCourses, vector>& prerequisites) {\n // your code here\n return false;\n}" }, + "examples": [ + { + "input": "2, [[1,0]]", + "output": "true", + "explanation": "You can finish the courses in order 0 → 1." + }, + { + "input": "2, [[1,0],[0,1]]", + "output": "false", + "explanation": "Cycle detected: 0 → 1 → 0." + } + ], + "constraints": [ + "1 <= numCourses <= 2000", + "0 <= prerequisites.length <= 5000", + "prerequisites[i].length == 2", + "All courses labeled from 0 to numCourses - 1" + ], "testCases": [ { "input": "2\n[[1,0]]", "expectedOutputs": ["true"] }, { "input": "2\n[[1,0],[0,1]]", "expectedOutputs": ["false"] }, @@ -436,6 +638,23 @@ "java": "public int[] productExceptSelf(int[] nums) {\n // your code here\n return new int[]{};\n}", "cpp": "vector productExceptSelf(vector& nums) {\n // your code here\n return {};\n}" }, + "examples": [ + { + "input": "[1,2,3,4]", + "output": "[24,12,8,6]", + "explanation": "Exclude the current index when multiplying." + }, + { + "input": "[0,1,2,3]", + "output": "[6,0,0,0]", + "explanation": "Zero affects the result." + } + ], + "constraints": [ + "2 <= nums.length <= 10^5", + "-30 <= nums[i] <= 30", + "The product of any prefix or suffix won't overflow a 32-bit integer" + ], "testCases": [ { "input": "[1,2,3,4]", "expectedOutputs": ["[24,12,8,6]"] }, { "input": "[2,3,4,5]", "expectedOutputs": ["[60,40,30,24]"] }, @@ -461,6 +680,22 @@ "java": "public boolean isPalindrome(ListNode head) {\n // your code here\n return false;\n}", "cpp": "bool isPalindrome(ListNode* head) {\n // your code here\n return false;\n}" }, + "examples": [ + { + "input": "[1,2,2,1]", + "output": "true", + "explanation": "The list is a palindrome." + }, + { + "input": "[1,2]", + "output": "false", + "explanation": "1 and 2 are different." + } + ], + "constraints": [ + "The number of nodes in the list is in the range [1, 10^5]", + "0 <= Node.val <= 9" + ], "testCases": [ { "input": "[1,2]", "expectedOutputs": ["false"] }, { "input": "[1,2,2,1]", "expectedOutputs": ["true"] }, @@ -486,6 +721,22 @@ "java": "public boolean isSameTree(TreeNode p, TreeNode q) {\n // your code here\n return false;\n}", "cpp": "bool isSameTree(TreeNode* p, TreeNode* q) {\n // your code here\n return false;\n}" }, + "examples": [ + { + "input": "[1,2,3], [1,2,3]", + "output": "true", + "explanation": "The structure and values are the same." + }, + { + "input": "[1,2], [1,null,2]", + "output": "false", + "explanation": "The trees have different structures." + } + ], + "constraints": [ + "The number of nodes in both trees is in the range [0, 100]", + "-10^4 <= Node.val <= 10^4" + ], "testCases": [ { "input": "[1,2,3]\n[1,2,3]", "expectedOutputs": ["true"] }, { "input": "[1,2]\n[1,null,2]", "expectedOutputs": ["false"] }, @@ -499,31 +750,6 @@ { "input": "[5,6]\n[6,5]", "expectedOutputs": ["false"] } ] }, -{ - "id": 22, - "title": "Rotate Array", - "category": "arrays", - "difficulty": "easy", - "description": "Given an integer array nums, rotate the array to the right by k steps, where k is non-negative.", - "functionSignature": { - "javascript": "function rotate(nums, k) {\n // your code here\n}", - "python": "def rotate(nums, k):\n # your code here\n pass", - "java": "public void rotate(int[] nums, int k) {\n // your code here\n}", - "cpp": "void rotate(vector& nums, int k) {\n // your code here\n}" - }, - "testCases": [ - { "input": "[1,2,3,4,5,6,7]\n3", "expectedOutputs": ["[5,6,7,1,2,3,4]"] }, - { "input": "[-1,-100,3,99]\n2", "expectedOutputs": ["[3,99,-1,-100]"] }, - { "input": "[1,2]\n3", "expectedOutputs": ["[2,1]"] }, - { "input": "[1]\n0", "expectedOutputs": ["[1]"] }, - { "input": "[1,2,3]\n0", "expectedOutputs": ["[1,2,3]"] }, - { "input": "[1,2,3,4]\n4", "expectedOutputs": ["[1,2,3,4]"] }, - { "input": "[1,2,3,4,5]\n5", "expectedOutputs": ["[1,2,3,4,5]"] }, - { "input": "[1,2,3,4,5,6]\n2", "expectedOutputs": ["[5,6,1,2,3,4]"] }, - { "input": "[1,2,3,4,5]\n3", "expectedOutputs": ["[3,4,5,1,2]"] }, - { "input": "[1,2,3,4,5,6,7,8]\n4", "expectedOutputs": ["[5,6,7,8,1,2,3,4]"] } - ] -}, { "id": 23, "title": "Merge k Sorted Lists", @@ -536,6 +762,25 @@ "java": "public ListNode mergeKLists(ListNode[] lists) {\n // your code here\n return null;\n}", "cpp": "ListNode* mergeKLists(vector& lists) {\n // your code here\n return nullptr;\n}" }, + "examples": [ + { + "input": "[[1,4,5],[1,3,4],[2,6]]", + "output": "[1,1,2,3,4,4,5,6]", + "explanation": "Merge all lists into one sorted list." + }, + { + "input": "[]", + "output": "[]", + "explanation": "No lists to merge." + } + ], + "constraints": [ + "k == lists.length", + "0 <= k <= 10^4", + "0 <= lists[i].length <= 500", + "-10^4 <= Node.val <= 10^4", + "Lists are sorted in ascending order" + ], "testCases": [ { "input": "[[1,4,5],[1,3,4],[2,6]]", "expectedOutputs": ["[1,1,2,3,4,4,5,6]"] }, { "input": "[]", "expectedOutputs": ["[]"] }, @@ -561,6 +806,24 @@ "java": "public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {\n // your code here\n return null;\n}", "cpp": "TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {\n // your code here\n return nullptr;\n}" }, + "examples": [ + { + "input": "[3,5,1,6,2,0,8,null,null,7,4], 5, 1", + "output": "3", + "explanation": "Common ancestor of 5 and 1 is 3." + }, + { + "input": "[3,5,1,6,2,0,8,null,null,7,4], 5, 4", + "output": "5", + "explanation": "5 is ancestor of 4." + } + ], + "constraints": [ + "The number of nodes in the tree is in the range [2, 10^5]", + "-10^9 <= Node.val <= 10^9", + "All Node values are unique", + "p and q exist in the tree" + ], "testCases": [ { "input": "[3,5,1,6,2,0,8,null,null,7,4]\n5\n1", "expectedOutputs": ["3"] }, { "input": "[3,5,1,6,2,0,8,null,null,7,4]\n5\n4", "expectedOutputs": ["5"] }, @@ -586,6 +849,24 @@ "java": "public int findJudge(int n, int[][] trust) {\n // your code here\n return -1;\n}", "cpp": "int findJudge(int n, vector>& trust) {\n // your code here\n return -1;\n}" }, + "examples": [ + { + "input": "2, [[1,2]]", + "output": "2", + "explanation": "2 is trusted by 1 and trusts no one." + }, + { + "input": "3, [[1,3],[2,3]]", + "output": "3", + "explanation": "Everyone trusts 3 except 3 themselves." + } + ], + "constraints": [ + "1 <= n <= 1000", + "0 <= trust.length <= 10^4", + "trust[i].length == 2", + "All trust pairs are distinct" + ], "testCases": [ { "input": "2\n[[1,2]]", "expectedOutputs": ["2"] }, { "input": "3\n[[1,3],[2,3]]", "expectedOutputs": ["3"] }, @@ -611,6 +892,23 @@ "java": "public int[] topKFrequent(int[] nums, int k) {\n // your code here\n return new int[]{};\n}", "cpp": "vector topKFrequent(vector& nums, int k) {\n // your code here\n return {};\n}" }, + "examples": [ + { + "input": "[1,1,1,2,2,3], 2", + "output": "[1,2]", + "explanation": "1 and 2 are the most frequent." + }, + { + "input": "[1], 1", + "output": "[1]", + "explanation": "Only one element in the array." + } + ], + "constraints": [ + "1 <= nums.length <= 10^5", + "k is in the range [1, the number of unique elements]", + "It is guaranteed that the answer is unique" + ], "testCases": [ { "input": "[1,1,1,2,2,3]\n2", "expectedOutputs": ["[1,2]", "[2,1]"] }, { "input": "[1]\n1", "expectedOutputs": ["[1]"] }, @@ -636,6 +934,24 @@ "java": "public int search(int[] nums, int target) {\n // your code here\n return -1;\n}", "cpp": "int search(vector& nums, int target) {\n // your code here\n return -1;\n}" }, + "examples": [ + { + "input": "[4,5,6,7,0,1,2], 0", + "output": "4", + "explanation": "The pivot is at index 4 where the value is 0." + }, + { + "input": "[4,5,6,7,0,1,2], 3", + "output": "-1", + "explanation": "3 is not in the array." + } + ], + "constraints": [ + "1 <= nums.length <= 5000", + "-10^4 <= nums[i] <= 10^4", + "All values of nums are unique", + "nums is sorted and then rotated" + ], "testCases": [ { "input": "[4,5,6,7,0,1,2]\n0", "expectedOutputs": ["4"] }, { "input": "[4,5,6,7,0,1,2]\n3", "expectedOutputs": ["-1"] }, @@ -661,6 +977,23 @@ "java": "public double myPow(double x, int n) {\n // your code here\n return 0.0;\n}", "cpp": "double myPow(double x, int n) {\n // your code here\n return 0.0;\n}" }, + "examples": [ + { + "input": "2.00000, 10", + "output": "1024.0", + "explanation": "2^10 = 1024" + }, + { + "input": "2.00000, -2", + "output": "0.25", + "explanation": "2^-2 = 1 / 4 = 0.25" + } + ], + "constraints": [ + "-100.0 < x < 100.0", + "-2^31 <= n <= 2^31-1", + "n is an integer" + ], "testCases": [ { "input": "2.00000\n10", "expectedOutputs": ["1024.0"] }, { "input": "2.10000\n3", "expectedOutputs": ["9.261"] }, @@ -686,6 +1019,22 @@ "java": "public int minMeetingRooms(int[][] intervals) {\n // your code here\n return 0;\n}", "cpp": "int minMeetingRooms(vector>& intervals) {\n // your code here\n return 0;\n}" }, + "examples": [ + { + "input": "[[0,30],[5,10],[15,20]]", + "output": "2", + "explanation": "Two meetings overlap between [5,10] and [15,20]." + }, + { + "input": "[[7,10],[2,4]]", + "output": "1", + "explanation": "No overlap, so only 1 room is needed." + } + ], + "constraints": [ + "1 <= intervals.length <= 10^4", + "0 <= start_i < end_i <= 10^6" + ], "testCases": [ { "input": "[[0,30],[5,10],[15,20]]", "expectedOutputs": ["2"] }, { "input": "[[7,10],[2,4]]", "expectedOutputs": ["1"] }, @@ -712,6 +1061,23 @@ "java": "public List> subsets(int[] nums) {\n // your code here\n return new ArrayList<>();\n}", "cpp": "vector> subsets(vector& nums) {\n // your code here\n return {};\n}" }, + "examples": [ + { + "input": "[1,2,3]", + "output": "[[],[1],[2],[3],[1,2],[1,3],[2,3],[1,2,3]]", + "explanation": "All subsets are generated." + }, + { + "input": "[0]", + "output": "[[],[0]]", + "explanation": "Only 0 and empty set." + } + ], + "constraints": [ + "1 <= nums.length <= 10", + "-10 <= nums[i] <= 10", + "All elements are unique" + ], "testCases": [ { "input": "[1,2,3]", "expectedOutputs": ["[[],[1],[2],[3],[1,2],[1,3],[2,3],[1,2,3]]"] }, { "input": "[0]", "expectedOutputs": ["[[],[0]]"] }, @@ -737,6 +1103,22 @@ "java": "public void reorderList(ListNode head) {\n // your code here\n}", "cpp": "void reorderList(ListNode* head) {\n // your code here\n}" }, + "examples": [ + { + "input": "[1,2,3,4]", + "output": "[1,4,2,3]", + "explanation": "Last inserted after first, second after second last, etc." + }, + { + "input": "[1,2,3,4,5]", + "output": "[1,5,2,4,3]", + "explanation": "Reordered from outside to center." + } + ], + "constraints": [ + "The number of nodes in the list is in the range [1, 10^4]", + "0 <= Node.val <= 1000" + ], "testCases": [ { "input": "[1,2,3,4]", "expectedOutputs": ["[1,4,2,3]"] }, { "input": "[1,2,3,4,5]", "expectedOutputs": ["[1,5,2,4,3]"] }, @@ -762,6 +1144,22 @@ "java": "public TreeNode invertTree(TreeNode root) {\n // your code here\n return null;\n}", "cpp": "TreeNode* invertTree(TreeNode* root) {\n // your code here\n return nullptr;\n}" }, + "examples": [ + { + "input": "[4,2,7,1,3,6,9]", + "output": "[4,7,2,9,6,3,1]", + "explanation": "All left and right subtrees are swapped recursively." + }, + { + "input": "[2,1,3]", + "output": "[2,3,1]", + "explanation": "Simple mirror swap." + } + ], + "constraints": [ + "The number of nodes in the tree is in the range [0, 100]", + "-100 <= Node.val <= 100" + ], "testCases": [ { "input": "[4,2,7,1,3,6,9]", "expectedOutputs": ["[4,7,2,9,6,3,1]"] }, { "input": "[2,1,3]", "expectedOutputs": ["[2,3,1]"] }, @@ -787,6 +1185,24 @@ "java": "public boolean validTree(int n, int[][] edges) {\n // your code here\n return false;\n}", "cpp": "bool validTree(int n, vector>& edges) {\n // your code here\n return false;\n}" }, + "examples": [ + { + "input": "5, [[0,1],[0,2],[0,3],[1,4]]", + "output": "true", + "explanation": "No cycles and all nodes are connected." + }, + { + "input": "5, [[0,1],[1,2],[2,3],[1,3],[1,4]]", + "output": "false", + "explanation": "Cycle detected between 1,2,3." + } + ], + "constraints": [ + "1 <= n <= 2000", + "0 <= edges.length <= 5000", + "edges[i].length == 2", + "No duplicate edges" + ], "testCases": [ { "input": "5\n[[0,1],[0,2],[0,3],[1,4]]", "expectedOutputs": ["true"] }, { "input": "5\n[[0,1],[1,2],[2,3],[1,3],[1,4]]", "expectedOutputs": ["false"] }, @@ -812,6 +1228,22 @@ "java": "public ListNode mergeTwoLists(ListNode l1, ListNode l2) {\n // your code here\n return null;\n}", "cpp": "ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {\n // your code here\n return nullptr;\n}" }, + "examples": [ + { + "input": "[1,2,4], [1,3,4]", + "output": "[1,1,2,3,4,4]", + "explanation": "Merged and sorted properly." + }, + { + "input": "[], [0]", + "output": "[0]", + "explanation": "Empty list merged with non-empty one." + } + ], + "constraints": [ + "The number of nodes in both lists is in the range [0, 50]", + "-100 <= Node.val <= 100" + ], "testCases": [ { "input": "[1,2,4]\n[1,3,4]", "expectedOutputs": ["[1,1,2,3,4,4]"] }, { "input": "[]\n[]", "expectedOutputs": ["[]"] }, @@ -837,6 +1269,24 @@ "java": "public int findDuplicate(int[] nums) {\n // your code here\n return -1;\n}", "cpp": "int findDuplicate(vector& nums) {\n // your code here\n return -1;\n}" }, + "examples": [ + { + "input": "[1,3,4,2,2]", + "output": "2", + "explanation": "2 appears twice, rest are unique." + }, + { + "input": "[3,1,3,4,2]", + "output": "3", + "explanation": "3 appears more than once." + } + ], + "constraints": [ + "1 <= nums.length <= 10^5", + "1 <= nums[i] <= n (where n = nums.length - 1)", + "Exactly one duplicate number exists", + "The duplicate number can appear more than once" + ], "testCases": [ { "input": "[1,3,4,2,2]", "expectedOutputs": ["2"] }, { "input": "[3,1,3,4,2]", "expectedOutputs": ["3"] }, @@ -862,6 +1312,24 @@ "java": "public Node cloneGraph(Node node) {\n // your code here\n return null;\n}", "cpp": "Node* cloneGraph(Node* node) {\n // your code here\n return nullptr;\n}" }, + "examples": [ + { + "input": "[[2,4],[1,3],[2,4],[1,3]]", + "output": "[[2,4],[1,3],[2,4],[1,3]]", + "explanation": "Each node and its neighbors are cloned." + }, + { + "input": "[[2],[1]]", + "output": "[[2],[1]]", + "explanation": "Two connected nodes are cloned correctly." + } + ], + "constraints": [ + "The number of nodes is in the range [0, 100]", + "1 <= Node.val <= 100", + "Node.val is unique", + "No duplicate or self-edges" + ], "testCases": [ { "input": "[]", "expectedOutputs": ["[]"] }, { "input": "[[2,4],[1,3],[2,4],[1,3]]", "expectedOutputs": ["[[2,4],[1,3],[2,4],[1,3]]"] }, @@ -875,31 +1343,6 @@ { "input": "[[2,3],[1,3],[1,2]]", "expectedOutputs": ["[[2,3],[1,3],[1,2]]"] } ] }, -{ - "id": 37, - "title": "Missing Number", - "category": "arrays", - "difficulty": "easy", - "description": "Given an array nums containing n distinct numbers in the range [0, n], return the only number in the range that is missing from the array.", - "functionSignature": { - "javascript": "function missingNumber(nums) {\n // your code here\n}", - "python": "def missing_number(nums):\n # your code here\n pass", - "java": "public int missingNumber(int[] nums) {\n // your code here\n return -1;\n}", - "cpp": "int missingNumber(vector& nums) {\n // your code here\n return -1;\n}" - }, - "testCases": [ - { "input": "[3,0,1]", "expectedOutputs": ["2"] }, - { "input": "[0,1]", "expectedOutputs": ["2"] }, - { "input": "[9,6,4,2,3,5,7,0,1]", "expectedOutputs": ["8"] }, - { "input": "[0]", "expectedOutputs": ["1"] }, - { "input": "[1]", "expectedOutputs": ["0"] }, - { "input": "[4,2,1,0]", "expectedOutputs": ["3"] }, - { "input": "[2,0]", "expectedOutputs": ["1"] }, - { "input": "[1,2]", "expectedOutputs": ["0"] }, - { "input": "[0,2]", "expectedOutputs": ["1"] }, - { "input": "[0,1,3,4]", "expectedOutputs": ["2"] } - ] -}, { "id": 38, "title": "Intersection of Two Linked Lists", @@ -912,6 +1355,24 @@ "java": "public ListNode getIntersectionNode(ListNode headA, ListNode headB) {\n // your code here\n return null;\n}", "cpp": "ListNode* getIntersectionNode(ListNode* headA, ListNode* headB) {\n // your code here\n return nullptr;\n}" }, + "examples": [ + { + "input": "[4,1,8,4,5], [5,6,1,8,4,5]", + "output": "8", + "explanation": "Lists intersect at node with value 8." + }, + { + "input": "[2,6,4], [1,5]", + "output": "null", + "explanation": "No intersection." + } + ], + "constraints": [ + "The number of nodes in both lists is in the range [0, 10^4]", + "1 <= Node.val <= 10^5", + "No cycles in either list", + "Lists may or may not intersect" + ], "testCases": [ { "input": "[4,1,8,4,5]\n[5,6,1,8,4,5]", "expectedOutputs": ["8"] }, { "input": "[1,9,1,2,4]\n[3,2,4]", "expectedOutputs": ["2"] }, @@ -937,6 +1398,24 @@ "java": "public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {\n // your code here\n return null;\n}", "cpp": "TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {\n // your code here\n return nullptr;\n}" }, + "examples": [ + { + "input": "[3,5,1,6,2,0,8,null,null,7,4], 5, 1", + "output": "3", + "explanation": "LCA of 5 and 1 is 3." + }, + { + "input": "[3,5,1,6,2,0,8,null,null,7,4], 5, 4", + "output": "5", + "explanation": "Node 5 is an ancestor of node 4." + } + ], + "constraints": [ + "The number of nodes is in the range [2, 10^5]", + "-10^9 <= Node.val <= 10^9", + "All node values are unique", + "p and q exist in the tree" + ], "testCases": [ { "input": "[3,5,1,6,2,0,8,null,null,7,4]\n5\n1", "expectedOutputs": ["3"] }, { "input": "[3,5,1,6,2,0,8,null,null,7,4]\n5\n4", "expectedOutputs": ["5"] }, @@ -962,6 +1441,24 @@ "java": "public boolean canFinish(int numCourses, int[][] prerequisites) {\n // your code here\n return false;\n}", "cpp": "bool canFinish(int numCourses, vector>& prerequisites) {\n // your code here\n return false;\n}" }, + "examples": [ + { + "input": "2, [[1,0]]", + "output": "true", + "explanation": "Finish in order 0 → 1." + }, + { + "input": "2, [[1,0],[0,1]]", + "output": "false", + "explanation": "Cycle: 0 → 1 → 0." + } + ], + "constraints": [ + "1 <= numCourses <= 2000", + "0 <= prerequisites.length <= 5000", + "prerequisites[i].length == 2", + "Courses are labeled from 0 to numCourses - 1" + ], "testCases": [ { "input": "2\n[[1,0]]", "expectedOutputs": ["true"] }, { "input": "2\n[[1,0],[0,1]]", "expectedOutputs": ["false"] }, @@ -975,31 +1472,6 @@ { "input": "3\n[[0,2],[1,2]]", "expectedOutputs": ["true"] } ] }, -{ - "id": 41, - "title": "Majority Element", - "category": "arrays", - "difficulty": "easy", - "description": "Given an array nums of size n, return the majority element. The majority element is the element that appears more than ⌊n / 2⌋ times. You may assume that the majority element always exists in the array.", - "functionSignature": { - "javascript": "function majorityElement(nums) {\n // your code here\n}", - "python": "def majority_element(nums):\n # your code here\n pass", - "java": "public int majorityElement(int[] nums) {\n // your code here\n return -1;\n}", - "cpp": "int majorityElement(vector& nums) {\n // your code here\n return -1;\n}" - }, - "testCases": [ - { "input": "[3,2,3]", "expectedOutputs": ["3"] }, - { "input": "[2,2,1,1,1,2,2]", "expectedOutputs": ["2"] }, - { "input": "[1]", "expectedOutputs": ["1"] }, - { "input": "[1,1,1,2,3]", "expectedOutputs": ["1"] }, - { "input": "[4,4,5,4,6,4,4]", "expectedOutputs": ["4"] }, - { "input": "[9,9,9,1,2,3,9]", "expectedOutputs": ["9"] }, - { "input": "[2,2,2,2,2,2,2]", "expectedOutputs": ["2"] }, - { "input": "[7,7,5,7,5,5,7]", "expectedOutputs": ["7"] }, - { "input": "[0,0,0,1,1]", "expectedOutputs": ["0"] }, - { "input": "[10,10,10,20,30]", "expectedOutputs": ["10"] } - ] -}, { "id": 42, "title": "Linked List Cycle", @@ -1012,6 +1484,22 @@ "java": "public boolean hasCycle(ListNode head) {\n // your code here\n return false;\n}", "cpp": "bool hasCycle(ListNode *head) {\n // your code here\n return false;\n}" }, + "examples": [ + { + "input": "[3,2,0,-4] (pos = 1)", + "output": "true", + "explanation": "Cycle detected at node with value 2." + }, + { + "input": "[1] (pos = -1)", + "output": "false", + "explanation": "Single node with no cycle." + } + ], + "constraints": [ + "The number of nodes in the list is in the range [0, 10^4]", + "-10^5 <= Node.val <= 10^5" + ], "testCases": [ { "input": "[3,2,0,-4] (pos = 1)", "expectedOutputs": ["true"] }, { "input": "[1,2] (pos = 0)", "expectedOutputs": ["true"] }, @@ -1037,6 +1525,22 @@ "java": "public int maxDepth(TreeNode root) {\n // your code here\n return 0;\n}", "cpp": "int maxDepth(TreeNode* root) {\n // your code here\n return 0;\n}" }, + "examples": [ + { + "input": "[3,9,20,null,null,15,7]", + "output": "3", + "explanation": "Depth is root → 20 → 7." + }, + { + "input": "[1,null,2]", + "output": "2", + "explanation": "Only right-side nodes." + } + ], + "constraints": [ + "The number of nodes in the tree is in the range [0, 10^4]", + "-100 <= Node.val <= 100" + ], "testCases": [ { "input": "[3,9,20,null,null,15,7]", "expectedOutputs": ["3"] }, { "input": "[1,null,2]", "expectedOutputs": ["2"] }, @@ -1062,6 +1566,26 @@ "java": "public int[] findRedundantConnection(int[][] edges) {\n // your code here\n return new int[2];\n}", "cpp": "vector findRedundantConnection(vector>& edges) {\n // your code here\n return {};\n}" }, + "examples": [ + { + "input": "[[1,2],[1,3],[2,3]]", + "output": "[2,3]", + "explanation": "Edge [2,3] creates a cycle." + }, + { + "input": "[[1,2],[2,3],[3,4],[1,4],[1,5]]", + "output": "[1,4]", + "explanation": "Removing [1,4] breaks the cycle." + } + ], + "constraints": [ + "n == edges.length", + "3 <= n <= 1000", + "edges[i].length == 2", + "edges[i] is a valid undirected edge", + "There are no duplicate edges", + "The graph is connected except for the extra edge" + ], "testCases": [ { "input": "[[1,2],[1,3],[2,3]]", "expectedOutputs": ["[2,3]"] }, { "input": "[[1,2],[2,3],[3,4],[1,4],[1,5]]", "expectedOutputs": ["[1,4]"] }, @@ -1087,6 +1611,23 @@ "java": "public void rotate(int[] nums, int k) {\n // your code here\n}", "cpp": "void rotate(vector& nums, int k) {\n // your code here\n}" }, + "examples": [ + { + "input": "[1,2,3,4,5,6,7], 3", + "output": "[5,6,7,1,2,3,4]", + "explanation": "Array is rotated to the right by 3 steps." + }, + { + "input": "[-1,-100,3,99], 2", + "output": "[3,99,-1,-100]", + "explanation": "Rotated twice to the right." + } + ], + "constraints": [ + "1 <= nums.length <= 10^5", + "-2^31 <= nums[i] <= 2^31 - 1", + "0 <= k <= 10^5" + ], "testCases": [ { "input": "[1,2,3,4,5,6,7]\n3", "expectedOutputs": ["[5,6,7,1,2,3,4]"] }, { "input": "[-1,-100,3,99]\n2", "expectedOutputs": ["[3,99,-1,-100]"] }, diff --git a/server/models/index.js b/server/models/index.js index 79ebcda..5fc6d36 100644 --- a/server/models/index.js +++ b/server/models/index.js @@ -9,7 +9,7 @@ const sequelize = require('./database'); const defineUser = require('./user'); -const Problem = require('./problem'); // Import the problem model +const Problem = require('./Problem'); // Import the problem model // Initialize the User model and Problem model with the sequelize instance const User = defineUser(sequelize); diff --git a/server/models/user.js b/server/models/user.js index 58a7f3b..abac05c 100644 --- a/server/models/user.js +++ b/server/models/user.js @@ -53,6 +53,15 @@ module.exports = (sequelize) => { allowNull: true // This will be filled in during profile setup // Possible values = improve problem solving, learn new algorithms, practice coding interviews, etc. }, + // User Avatar + avatarTheme: { + type: DataTypes.STRING, + allowNull: true // This will be filled in during profile setup + }, + avatarColor: { + type: DataTypes.STRING, + allowNull: true // This will be filled in during profile setup + }, setupComplete: { type: DataTypes.BOOLEAN, // Indicates whether the user has completed their profile setup defaultValue: false // Default to false when the user first signs up @@ -75,7 +84,7 @@ module.exports = (sequelize) => { }, coins: { type: DataTypes.INTEGER, - defaultValue: 0 // Initialize coins to 0 + defaultValue: 100 // Initialize coins to 100 }, winStreak: { type: DataTypes.INTEGER, diff --git a/server/package.json b/server/package.json index a28f2c3..41c7801 100644 --- a/server/package.json +++ b/server/package.json @@ -8,13 +8,14 @@ "dev": "nodemon app.js" }, "dependencies": { + "axios": "^1.11.0", + "cors": "^2.8.5", + "dotenv": "^16.3.1", "express": "^4.18.2", - "socket.io": "^4.7.2", "google-auth-library": "^8.9.0", - "cors": "^2.8.5", - "dotenv": "^16.3.1" + "socket.io": "^4.7.2" }, "devDependencies": { "nodemon": "^3.0.1" } -} \ No newline at end of file +} diff --git a/server/routes/authRoutes.js b/server/routes/authRoutes.js index 5560f78..4c417f6 100644 --- a/server/routes/authRoutes.js +++ b/server/routes/authRoutes.js @@ -1,5 +1,6 @@ const express = require('express'); const { OAuth2Client } = require('google-auth-library'); +const { User } = require('../models'); // Import User model const router = express.Router(); @@ -154,7 +155,16 @@ router.get('/me', async (req, res) => { return res.status(401).json({ message: 'Invalid or expired session' }); } - res.json({ user: session.user }); + const { googleId } = session.user; + const dbUser = await User.findOne({ where: { googleId } }); + + if (!dbUser) { + return res.status(404).json({ message: 'User not found in database' }); + } + + // Optionally update session in memory too + session.user = dbUser; + res.json({ user: dbUser.toJSON() }); } catch (error) { console.error('Get user error:', error); diff --git a/server/routes/gameRoutes.js b/server/routes/gameRoutes.js index 2e5022c..312832e 100644 --- a/server/routes/gameRoutes.js +++ b/server/routes/gameRoutes.js @@ -47,15 +47,7 @@ router.get('/question', (req, res) => { const randomProblem = filteredProblems[Math.floor(Math.random() * filteredProblems.length)]; // Return the selected problem - res.status(200).json({ - id: randomProblem.id, - title: randomProblem.title, - description: randomProblem.description, - difficulty: randomProblem.difficulty, - language: language, - functionSignature: randomProblem.functionSignature[language], // this returns the starter code for requested language - testCases: randomProblem.testCases - }); + res.status(200).json(randomProblem); }); // Export the router diff --git a/server/routes/userRoutes.js b/server/routes/userRoutes.js index 1ced72f..cb8de66 100644 --- a/server/routes/userRoutes.js +++ b/server/routes/userRoutes.js @@ -13,23 +13,59 @@ const express = require("express"); const app = express(); const router = express.Router(); app.use(express.json()); +const { User } = require('../models'); const { findOrCreateUser, updateUserPreferences, updateMatchStats, getUserStats } = require('../controllers/userController'); // Route to handle Google OAuth login router.post('/auth/google', async (req, res) => { - const { googleId } = req.body; - console.log("Received Google ID:", googleId); // 👈 add this + const { googleId, name } = req.body; + console.log("Received Google ID:", googleId); try { - const user = await findOrCreateUser(googleId); + const user = await findOrCreateUser(googleId, name); res.json(user); } catch (err) { - console.error("🔥 Error in /auth/google:", err); // 👈 add this + console.error("🔥 Error in /auth/google:", err); res.status(500).json({ error: 'Failed to find or create user' }); } }); +// route to set profile up +router.put('/profile', async (req, res) => { + try { + const { + googleId, + avatarTheme, + avatarColor, + favoriteLanguages, + skillLevel, + goals, + setupComplete + } = req.body; + console.log('Updating user profile with data:', req.body); // debugging log + + const user = await User.findOne({ where: { googleId } }); + if (!user) { + return res.status(404).json({ error: 'User not found' }); + } + await user.update({ + avatarTheme, + avatarColor, + favoriteLanguages, + skillLevel, + goals, + setupComplete: true + }); + + console.log('User profile updated successfully'); + res.json({ user }); + } catch (error) { + console.error('Error updating user profile:', error); + res.status(500).json({ error: 'Failed to update profile' }); + } +}); + // Route to update user preferences router.put('/update-preferences/:id', async (req, res) => { @@ -58,16 +94,64 @@ router.get('/stats/:id', async (req, res) => { // Route to update match stats (wins, matches played) router.put('/update-stats', async (req, res) => { - const {userId, isWin} = req.body; + const {googleId, isWin} = req.body; + + // basic validation + if (!googleId || typeof isWin !== 'boolean') { + return res.status(400).json({ error: 'Invalid request data' }); + } - // Update match stats based on the outcome of a match try { - const updatedUser = await updateMatchStats(userId, isWin); - res.status(200).json(updatedUser); + //find user by googleId + const user = await User.findOne({ where: { googleId } }); + if (!user) { + return res.status(404).json({ error: 'User not found' }); + } + + // update match stats + if (isWin) { + user.wins += 1; + user.winStreak += 1; + } else { + user.winStreak = 0; + } + user.totalMatchesPlayed += 1; + + + // update rank + const coinChange = isWin ? 100 : -50; // 100 coins for win, -50 for loss + const newCoins = user.coins + coinChange; + + user.coins = Math.max(newCoins, 0); // Ensure coins don't go negative + const c = newCoins; + if (c >= 1350) user.rank = 'Legend'; + else if (c >= 1200) user.rank = 'Gold 3'; + else if (c >= 1050) user.rank = 'Gold 2'; + else if (c >= 900) user.rank = 'Gold 1'; + else if (c >= 750) user.rank = 'Silver 3'; + else if (c >= 600) user.rank = 'Silver 2'; + else if (c >= 450) user.rank = 'Silver 1'; + else if (c >= 300) user.rank = 'Bronze 3'; + else if (c >= 150) user.rank = 'Bronze 2'; + else user.rank = 'Bronze 1'; + + await user.save(); + res.status(200).json({ + message: 'User stats updated successfully', + user: { + googleId: user.googleId, + wins: user.wins, + totalMatchesPlayed: user.totalMatchesPlayed, + winStreak: user.winStreak, + rank: user.rank, + coins: user.coins + } + }); } catch (error) { - console.error('Error updating match stats:', error); + console.error('Error updating user stats:', error); res.status(500).json({ error: 'Internal server error' }); } }); +// Export the router module.exports = router; diff --git a/server/sockets/matchSocket.js b/server/sockets/matchSocket.js index 264f855..8bc3992 100644 --- a/server/sockets/matchSocket.js +++ b/server/sockets/matchSocket.js @@ -1,12 +1,24 @@ // Store matches in memory (matchCode -> [sockets]) +const axios = require('axios'); const { runJudge0 } = require("../utils/judge0Helper"); -const matchRooms = {}; -const matchData = {}; const { buildJsWrappedCode, - formatTestCasesAsStdin + formatTestCasesAsStdin, + buildPythonWrapper } = require("../utils/wrappers"); -const problems = require("../data/problems.json"); +const matchRooms = {}; +const matchData = {}; +const LANGUAGE_ID = { + javascript: 63, + python: 71, + java: 62, + cpp: 54 +}; + +//Utility: generate random room codes +function generateRoomCode() { + return Math.random().toString(36).substring(2, 8).toUpperCase(); +} async function runAllTestCases(code, languageId, testCases) { const results = []; @@ -39,8 +51,88 @@ function matchSocketHandler(socket, io) { //sends welcome message to new users socket.emit("server_message", "Welcome to Code Clash!"); + //Create Room (Private) + socket.on("create_room", ({ difficulty, language, category, creator }) => { + console.log("🔥 Received create_room from:", creator?.username); + + let roomCode; + + do { + roomCode = generateRoomCode(); + } while (matchRooms[roomCode]); + + matchRooms[roomCode] = [socket]; + matchData[roomCode] = { + difficulty, + language, + category, + players: [creator], + createdAt: Date.now() + }; + + socket.join(roomCode); + socket.data.roomCode = roomCode; + socket.data.username = creator.username; + socket.data.userId = creator.id; + + console.log(`${creator.username} created room ${roomCode} with`, { difficulty, language, category }); + socket.emit("room_created", { roomCode }); + }); + + // ✅ Join Room + socket.on("join_room", async ({ roomCode, player }) => { + const room = matchRooms[roomCode]; + + if (!room || room.length >= 2) { + socket.emit("room_error", { message: "Room not found or full" }); + return; + } + + matchRooms[roomCode].push(socket); + matchData[roomCode].players.push(player); + socket.join(roomCode); + socket.data.roomCode = roomCode; + socket.data.username = player.username; + socket.data.userId = player.id; + + console.log(`${player.username} joined room ${roomCode}`); + + io.to(roomCode).emit("room_joined", { + roomCode, + playerCount: matchRooms[roomCode].length + }); + + // Start game when 2 players are in + if (matchRooms[roomCode].length === 2) { + const { difficulty, language, category } = matchData[roomCode]; + const response = await axios.get( + "http://localhost:3001/api/game/question", + { params: { difficulty, language, category } } + ); + const problem = response.data; + + matchData[roomCode].problem = problem; + + console.log( + `🔥 [matchSocket] BOTH PLAYERS JOINED — starting game in room ${roomCode}`, + { difficulty, language, category, problemId: problem.id } + ); + + io.to(roomCode).emit("start_game", { + roomCode, + problem, + timeLimit: 600 + }); + } + }); + + // 🧪 Test messaging + socket.on("client_message", (msg) => { + console.log(`Message from client ${socket.id}:`, msg); + }); + //User wants to join a match - socket.on ("join_match",({ matchCode, username}) => { + socket.on ("join_match", async({ matchCode, username}) => { socket.data.username = username; // Store username on socket // Create match if it doesn't exist @@ -55,69 +147,187 @@ function matchSocketHandler(socket, io) { // If 2 players are in, start the game if (matchRooms[matchCode].length === 2) { - const problem = problems.find(p => p.id === 2); // Pick any for now — maybe random later - if (!problem) return; + const difficulty = "easy"; // You can later let this be dynamic + const response = await axios.get(`http://localhost:3001/api/game/question?difficulty=${difficulty}`); + const problem = response.data; - matchData[matchCode] = { problem }; + matchData[matchCode] = { problem }; - io.to(matchCode).emit("start_game", { - problem: problem.description - }); + console.log("🔥 [matchSocket] Quick match started:", matchCode); + console.log("📦 [matchSocket] Problem sent:", problem.id, problem.title); + + io.to(matchCode).emit("start_game", { + roomCode: matchCode, + problem, + timeLimit: 600 + }); } }); - // Listen for a test message from the client - socket.on("client_message", (msg) => { - console.log(`Message from client ${socket.id}:`, msg); - }); // Listen for a submission from the client - socket.on("submit_code", async ({ matchCode, code, languageId }) => { - try { - const problem = matchData[matchCode]?.problem; - if (!problem) throw new Error("Problem not found"); - - const wrappedCode = buildJsWrappedCode(code, problem.testCases); - const stdin = formatTestCasesAsStdin(problem.testCases); - - const result = await runJudge0(wrappedCode, languageId, "", stdin); - const output = (result.stdout || result.stderr || "").trim(); - - // Check if all tests passed by searching for ❌ - const passedAll = !output.includes("❌"); - - if (passedAll) { - io.to(matchCode).emit("match_over", { - winner: socket.data.username, - output - }); - socket.to(matchCode).emit("lock_editor"); - } else { - socket.emit("submission_result", { - passed: false, - output - }); - } +// Inside your matchSocketHandler(socket, io), replace your existing handler with this: +socket.on("submit_solution", async ({ roomCode: matchCode, code, language }) => { + try { + console.log(`🛰️ [Server] submit_solution received in room: ${matchCode}, language: ${language}`); -} catch (err) { - socket.emit("submission_result", { - passed: false, - error: err.message - }); + // 1. Look up the problem data for this room + const problem = matchData[matchCode]?.problem; + if (!problem) { + socket.emit("submission_result", { passed: false, error: "Problem not found" }); + return; } -}); - - // Disconnect cleanup - socket.on("disconnect", () => { - console.log(`User disconnected: ${socket.id}`); - - for (const [code, sockets] of Object.entries(matchRooms)) { - matchRooms[code] = sockets.filter(s => s.id !== socket.id); - if (matchRooms[code].length === 0) { - delete matchRooms[code]; - } + + // 2. Map languages to Judge0 IDs + const languageIds = { + javascript: 63, + python: 71, + java: 62, + cpp: 54 + }; + const languageId = languageIds[language] || 63; + + // ── Branch by language ── + if (language === "javascript") { + // JavaScript: use your bulk‐wrapper + const wrapperType = problem.category === "linked list" + ? "linkedlist" + : problem.category === "binary tree" + ? "binarytree" + : problem.category === "graph" + ? "graph" + : "array"; + + const wrappedCode = buildJsWrappedCode(code, problem.testCases, wrapperType); + const stdin = formatTestCasesAsStdin(problem.testCases, wrapperType); + console.log("=== DEBUG: generated stdin ===\n" + stdin + "\n=== END DEBUG ==="); + + const result = await runJudge0(wrappedCode, languageId, "", stdin); + const rawBase64 = result.stdout || result.stderr || ""; + const decoded = Buffer.from(rawBase64, "base64").toString("utf-8"); + console.log("✅ Decoded Output:\n", decoded); + + const lines = decoded.split("\n").filter(l => l.trim().length); + const allPassed = lines.every(line => line.startsWith("✅")); + + io.to(matchCode).emit("submission_result", { + output: decoded, + allPassed, + winner: allPassed ? socket.data.username : null + }); + + if (allPassed) { + socket.to(matchCode).emit("lock_editor"); + } + + } else if (language === "python") { + // 1) Build & run the Python wrapper + const wrappedCode = buildPythonWrapper(code, problem.testCases); + // Only send the actual input lines (ignore expectedOutputs) + const stdin = formatTestCasesAsStdin(problem.testCases, "array");; + console.log("=== DEBUG PYTHON stdin ===\n" + stdin + "\n=== END DEBUG ==="); + + const result = await runJudge0(wrappedCode, languageId, "", stdin); + const decoded = Buffer.from(result.stdout || result.stderr || "", "base64") + .toString("utf8") + .trim(); + console.log("✅ Python decoded:\n", decoded); + + // 2) Break into lines and compare + const lines = decoded.split("\n").filter(l => l.trim().length); + const results = problem.testCases.map((tc, i) => { + let actual; + try { + actual = JSON.parse(lines[i]); + } catch { + actual = lines[i]; + } + // see if any of the allowed expected outputs matches + const passed = tc.expectedOutputs.some(exp => { + try { + return exp === JSON.stringify(actual); + } catch { + return false; + } + }); + return { + idx: i + 1, + passed, + input: tc.input.trim(), + expected: tc.expectedOutputs, + actual + }; + }); + + const allPassed = results.every(r => r.passed); + + // 3) Emit exactly the same format you have for JS + const formatted = results.map((r, i) => { + if (r.passed) { + return `✅ Test ${i + 1}: Passed`; + } else { + return [ + `❌ Test ${i + 1}: Failed`, + ` Input: ${r.input}`, + ` Expected: ${JSON.stringify(r.expected)}`, + ` Got: ${JSON.stringify(r.actual)}` + ].join("\n"); + } + }).join("\n\n"); + + io.to(matchCode).emit("submission_result", { + output: formatted, + allPassed, + winner: allPassed ? socket.data.username : null + }); + + if (allPassed) { + socket.to(matchCode).emit("lock_editor"); + } +} + else if (language === "java") { + // fallback: run each test case one by one + const results = await runAllTestCases(code, languageId, problem.testCases); + const allPassed = results.every(r => r.passed); + + const formatted = results.map((r, i) => { + if (r.passed) { + return `✅ Test ${i + 1}: Passed`; + } else { + return [ + `❌ Test ${i + 1}: Failed`, + ` Input: ${r.input}`, + ` Expected: ${JSON.stringify(r.expectedOutput || r.expectedOutputs)}`, + ` Got: ${JSON.stringify(r.actual)}` + ].join("\n"); } + }).join("\n\n"); + + io.to(matchCode).emit("submission_result", { + output: formatted, + allPassed, + winner: allPassed ? socket.data.username : null + }); + + if (allPassed) { + socket.to(matchCode).emit("lock_editor"); + } + } + else { + socket.emit("submission_result", { + passed: false, + error: `Unsupported language: ${language}` + }); + } + } catch (err) { + console.error("❌ submit_solution error:", err); + socket.emit("submission_result", { + passed: false, + error: err.message }); + } +}); + } diff --git a/server/utils/wrappers.js b/server/utils/wrappers.js index ea3b95c..1429646 100644 --- a/server/utils/wrappers.js +++ b/server/utils/wrappers.js @@ -1,43 +1,85 @@ function _buildArrayWrapper(userCode, testCases) { - const testRunner = ` -function arraysEqual(a, b) { - return Array.isArray(a) && Array.isArray(b) && - a.length === b.length && - a.every((val, index) => val === b[index]); -} + // 1. gets name and number of params + const match = userCode.match(/function\s+([^(]+)\s*\(([^)]*)\)/); + const fnName = match ? match[1] : "solutionFunc"; + const params = match[2] + .split(',') + .map(p => p.trim()) + .filter(p => p); + // 2. decide runner based on params + let testRunner; + if (params.length === 1) { + // just a param + testRunner = ` function runTests() { - const input = require('fs').readFileSync('/dev/stdin', 'utf8'); - const lines = input.trim().split('\\n\\n'); - lines.forEach((block, index) => { - const [numsLine, targetLine, ...expectedLines] = block.trim().split('\\n'); - const nums = JSON.parse(numsLine); - const target = JSON.parse(targetLine); - const expectedList = expectedLines.map(line => JSON.parse(line)); - let result, passed = false; - + const input = require('fs').readFileSync('/dev/stdin','utf8').trim(); + const blocks = input.split('\\n\\n'); + blocks.forEach((block, idx) => { + const [inp, ...expLines] = block.split('\\n'); + let result; try { - result = twoSum(nums, target); - passed = expectedList.some(expected => arraysEqual(result, expected)); + const arg = JSON.parse(inp); + result = ${fnName}(arg); } catch (e) { - console.log(\`❌ Test \${index + 1}: Error - \${e.message}\`); + console.log(\`❌ Test \${idx+1}: Error - \${e.message}\`); + return; + } + const expected = expLines.map(l => JSON.parse(l)); + if (expected.includes(result)) { + console.log(\`✅ Test \${idx+1}: Passed\`); + } else { + console.log(\`❌ Test \${idx+1}: Failed\\n Input: \${inp}\\n Expected: \${JSON.stringify(expected)}\\n Got: \${JSON.stringify(result)}\`); + } + }); +} +runTests(); +`; + } else { + // two params (ej. twoSum(nums, target)) + testRunner = ` +function arraysEqual(a, b) { + return Array.isArray(a) && a.length === b.length && a.every((v,i) => v === b[i]); +} +function runTests() { + const input = require('fs').readFileSync('/dev/stdin','utf8').trim(); + const blocks = input.split('\\n\\n'); + blocks.forEach((block, idx) => { + const [aLine, bLine, ...expLines] = block.split('\\n'); + const arg1 = JSON.parse(aLine), arg2 = JSON.parse(bLine); + const expected = expLines.map(l => JSON.parse(l)); + let res, passed=false; + try { + res = ${fnName}(arg1, arg2); + passed = expected.some(e => arraysEqual(res, e)); + } catch(e) { + console.log(\`❌ Test \${idx+1}: Error - \${e.message}\`); return; } - if (passed) { - console.log(\`✅ Test \${index + 1}: Passed\`); + console.log(\`✅ Test \${idx+1}: Passed\`); } else { - console.log(\`❌ Test \${index + 1}: Failed\\n Input: \${numsLine}, \${targetLine}\\n Expected: \${JSON.stringify(expectedList)}\\n Got: \${JSON.stringify(result)}\`); + console.log(\`❌ Test \${idx+1}: Failed\\n Input: \${aLine}, \${bLine}\\n Expected: \${JSON.stringify(expected)}\\n Got: \${JSON.stringify(res)}\`); } }); } - runTests(); `; + } + + // 3. returns whole code return `${userCode}\n${testRunner}`; } + function _buildLinkedListWrapper(userCode, testCases) { + // 1. Extract the user’s function name dynamically + const match = userCode.match(/function\s+([^(]+)\s*\(/); + const fnName = match ? match[1] : "addTwoNumbers"; + + // 2. Build the test runner that transforms input arrays into linked lists, + // invokes the user’s function, converts the result back to an array, + // and compares it against expected output const testRunner = ` class ListNode { constructor(val = 0, next = null) { @@ -66,46 +108,50 @@ function linkedListToArray(head) { } function arraysEqual(a, b) { - return Array.isArray(a) && Array.isArray(b) && - a.length === b.length && - a.every((val, index) => val === b[index]); + return Array.isArray(a) && a.length === b.length && a.every((v,i) => v === b[i]); } function runTests() { - const input = require('fs').readFileSync('/dev/stdin', 'utf8'); - const blocks = input.trim().split('\\n\\n'); - - blocks.forEach((block, index) => { - const [inputLine, expectedLine] = block.trim().split('\\n'); - const inputArr = JSON.parse(inputLine); - const expected = JSON.parse(expectedLine); - - let resultArr = []; + const input = require('fs').readFileSync('/dev/stdin','utf8').trim(); + const blocks = input.split('\\n\\n'); + blocks.forEach((block, idx) => { + const [l1Line, l2Line, expectedLine] = block.split('\\n'); + let arr1, arr2, expected, resultArr; try { - const l1 = arrayToLinkedList(inputArr[0]); - const l2 = arrayToLinkedList(inputArr[1]); - const result = addTwoNumbers(l1, l2); - resultArr = linkedListToArray(result); + arr1 = JSON.parse(l1Line); + arr2 = JSON.parse(l2Line); + expected = JSON.parse(expectedLine); + const l1 = arrayToLinkedList(arr1); + const l2 = arrayToLinkedList(arr2); + const res = ${fnName}(l1, l2); + resultArr = linkedListToArray(res); } catch (e) { - console.log(\`❌ Test \${index + 1}: Error - \${e.message}\`); + console.log(\`❌ Test \${idx+1}: Error - \${e.message}\`); return; } - - const passed = arraysEqual(resultArr, expected); - if (passed) { - console.log(\`✅ Test \${index + 1}: Passed\`); + if (arraysEqual(resultArr, expected)) { + console.log(\`✅ Test \${idx+1}: Passed\`); } else { - console.log(\`❌ Test \${index + 1}: Failed\\n Input: \${JSON.stringify(inputArr)}\\n Expected: \${JSON.stringify(expected)}\\n Got: \${JSON.stringify(resultArr)}\`); + console.log(\`❌ Test \${idx+1}: Failed\\n Input: \${l1Line}, \${l2Line}\\n Expected: \${JSON.stringify(expected)}\\n Got: \${JSON.stringify(resultArr)}\`); } }); } - runTests(); `; + + // 3. Return the combined user code plus test runner return `${userCode}\n${testRunner}`; } function _buildBinaryTreeWrapper(userCode, testCases) { + // 1. Extract the user’s function name dynamically + const match = userCode.match(/function\s+([^(]+)\s*\(/); + const fnName = match ? match[1] : "inorderTraversal"; + + // 2. Build the test runner that: + // a) Parses the input array into a binary tree + // b) Calls the user’s function on the tree + // c) Compares the returned array against expected results const testRunner = ` class TreeNode { constructor(val, left = null, right = null) { @@ -137,84 +183,80 @@ function arrayToBinaryTree(arr) { } function arraysEqual(a, b) { - return JSON.stringify(a) === JSON.stringify(b); + return Array.isArray(a) && a.length === b.length && a.every((v,i) => v === b[i]); } function runTests() { - const input = require('fs').readFileSync('/dev/stdin', 'utf8'); - const blocks = input.trim().split('\\n\\n'); - - blocks.forEach((block, index) => { - const [inputLine, ...expectedLines] = block.trim().split('\\n'); - const inputArr = JSON.parse(inputLine); - const expectedList = expectedLines - .map(line => { - try { - return JSON.parse(line); - } catch { - return undefined; - } - }) - .filter(e => e !== undefined); - - let resultArr = []; + const input = require('fs').readFileSync('/dev/stdin','utf8').trim(); + const blocks = input.split('\\n\\n'); + blocks.forEach((block, idx) => { + const lines = block.split('\\n'); + let treeArr, expectedList, resultArr; try { - const root = arrayToBinaryTree(inputArr); - const result = inorderTraversal(root); - resultArr = Array.isArray(result) ? result : []; + treeArr = JSON.parse(lines[0]); + expectedList = lines.slice(1).map(l => JSON.parse(l)); + const root = arrayToBinaryTree(treeArr); + const res = ${fnName}(root); + resultArr = Array.isArray(res) ? res : []; } catch (e) { - console.log(\`❌ Test \${index + 1}: Error - \${e.message}\`); + console.log(\`❌ Test \${idx+1}: Error - \${e.message}\`); return; } - - const passed = expectedList.some(expected => JSON.stringify(resultArr) === JSON.stringify(expected)); - if (passed) { - console.log(\`✅ Test \${index + 1}: Passed\`); + if (expectedList.some(exp => arraysEqual(resultArr, exp))) { + console.log(\`✅ Test \${idx+1}: Passed\`); } else { - console.log(\`❌ Test \${index + 1}: Failed\\n Input: \${inputLine}\\n Expected: \${JSON.stringify(expectedList)}\\n Got: \${JSON.stringify(resultArr)}\`); + console.log(\`❌ Test \${idx+1}: Failed\\n Input: \${JSON.stringify(treeArr)}\\n Expected: \${JSON.stringify(expectedList)}\\n Got: \${JSON.stringify(resultArr)}\`); } }); } - runTests(); `; + + // 3. Return user code plus test runner return `${userCode}\n${testRunner}`; } + function _buildGraphWrapper(userCode, testCases) { + // 1. Dynamically extract the user’s function name + const match = userCode.match(/function\s+([^(]+)\s*\(/); + const fnName = match ? match[1] : "countComponents"; + + // 2. Build a test runner that: + // a) Reads n and edges from stdin + // b) Calls the user’s function with those arguments + // c) Compares the returned number against each expected output const testRunner = ` function runTests() { - const input = require('fs').readFileSync('/dev/stdin', 'utf8'); - const blocks = input.trim().split('\\n\\n'); - - blocks.forEach((block, index) => { - const [nLine, edgesLine, ...expectedLines] = block.trim().split('\\n'); - const n = JSON.parse(nLine); - const edges = JSON.parse(edgesLine); - const expectedList = expectedLines.map(line => JSON.parse(line)); - - let result; + const input = require('fs').readFileSync('/dev/stdin','utf8').trim(); + const blocks = input.split('\\n\\n'); + blocks.forEach((block, idx) => { + const [nLine, edgesLine, ...expLines] = block.split('\\n'); + let n, edges, expectedList, result; try { - result = countComponents(n, edges); // expects user-defined function + n = JSON.parse(nLine); + edges = JSON.parse(edgesLine); + expectedList = expLines.map(l => JSON.parse(l)); + result = ${fnName}(n, edges); } catch (e) { - console.log(\`❌ Test \${index + 1}: Error - \${e.message}\`); + console.log(\`❌ Test \${idx+1}: Error - \${e.message}\`); return; } - - const passed = expectedList.some(expected => result === expected); - if (passed) { - console.log(\`✅ Test \${index + 1}: Passed\`); + if (expectedList.includes(result)) { + console.log(\`✅ Test \${idx+1}: Passed\`); } else { - console.log(\`❌ Test \${index + 1}: Failed\\n Input: \${nLine}, \${edgesLine}\\n Expected: \${JSON.stringify(expectedList)}\\n Got: \${result}\`); + console.log(\`❌ Test \${idx+1}: Failed\\n Input: n=\${n}, edges=\${JSON.stringify(edges)}\\n Expected: \${JSON.stringify(expectedList)}\\n Got: \${result}\`); } }); } - runTests(); `; + + // 3. Return the combined user code plus the test runner return `${userCode}\n${testRunner}`; } + function buildJsWrappedCode(userCode, testCases, type = "array") { switch (type.toLowerCase()) { case "linkedlist": @@ -230,16 +272,72 @@ function buildJsWrappedCode(userCode, testCases, type = "array") { } function formatTestCasesAsStdin(testCases, type = "array") { - return testCases.map(({ input, expectedOutputs }) => { - const [part1, part2] = input.trim().split('\n'); - const inputLine = type === "linkedlist" ? `[${part1},${part2}]` : `${part1}\n${part2}`; - const expectedLines = expectedOutputs.map(out => `${out}`).join('\n'); - return `${inputLine}\n${expectedLines}`; - }).join('\n\n'); + return testCases + .map(({ input, expectedOutputs }) => { + const inputLines = input.trim().split('\n'); + const expectedLines = expectedOutputs || []; + return [...inputLines, ...expectedLines].join('\n'); + }) + .join('\n\n'); +} + + +function buildPythonWrapper(userCode /* string */, testCases /* array */) { + const match = userCode.match(/def\s+([^(]+)\s*\(/); + const fnName = match ? match[1] : "solution"; + + return ` +${userCode} + +import sys, json, inspect + +def run_tests(): + data = sys.stdin.read().strip() + if not data: + return + blocks = data.split("\\n\\n") + + for idx, block in enumerate(blocks, start=1): + lines = block.strip().split("\\n") + + try: + # Dynamically determine number of parameters + sig = inspect.signature(${fnName}) + num_args = len(sig.parameters) + + # First num_args lines are inputs + args = [json.loads(l) for l in lines[:num_args]] + expected_lines = lines[num_args:] + + res = ${fnName}(*args) + output = json.dumps(res) + + expected = [json.dumps(json.loads(l)) for l in expected_lines] + passed = output in expected + + if passed: + print(f"✅ Test {idx}: Passed") + else: + print(f"❌ Test {idx}: Failed") + print(f" Input: {' | '.join(lines[:num_args])}") + print(f" Expected: {expected}") + print(f" Got: {output}") + + except Exception as e: + print(f"❌ Test {idx}: Error") + print(f" Input: {' | '.join(lines[:num_args])}") + print(f" Error: {e}") + +if __name__ == "__main__": + run_tests() +`; } + + module.exports = { buildJsWrappedCode, - formatTestCasesAsStdin + formatTestCasesAsStdin, + buildPythonWrapper };