Full-Stack Chess App with MERN
Full-Stack Chess App with MERN
No:
Capstone Project
AIM:
React, [Link], [Link], MongoDB
Develop a full-stack web application using React for the front-end, [Link] and
[Link] for the back-end, and MongoDB for data storage.
DESCRIPTION:
1. A web application named ChessMaster made using MERN.
2. Platform used for React is NodeJS
Command: npx create-react-app application_name
By using vite, we can make our application more optimized.
Command: npm create vite@latest
3. Open cmd in ‘project’ folder and type the command
press y
write the project name: frontend folder name
Select framework: React
Select variant: JavaScript
clear the codes in assets>[Link], [Link], [Link].
4. All the folders and files must be in src in frontend folder
5. To run the [Link] file, open terminal:
npm i
npm run dev
6. For react, install axios package (as express for node)
Command in terminal: npm i axios
7. For navigation:
npm i react-router-dom
PROGRAM:
FrontEnd:
Home/[Link]
function Home() {
const navigate = useNavigate();
const [showProfileMenu, setShowProfileMenu] = useState(false);
const [playerColor, setPlayerColor] = useState(null);
const [isWaiting, setIsWaiting] = useState(false);
const [roomId, setRoomId] = useState(null);
const [error, setError] = useState(null);
useEffect(() => {
// Set username when component mounts
if (username) {
[Link]('username', username);
}
[Link]("matchFound", (res) => {
const{opponent, roomId, color} = res;
[Link](`Match found: ${opponent} in room ${roomId}, you are playing as
${color}`);
setRoomId(roomId);
navigate(`/game/${roomId}`,{state:{opponent:opponent, color:color}});
});
};
}, [username, roomId, navigate]);
[Link]('token');
[Link]('user');
navigate('/login');
};
return (
<div className="relative min-h-screen bg-[#1a1a1a] overflow-hidden">
{/* Chess-themed background */}
<div
className="absolute inset-0 opacity-20"
style={{
backgroundImage: "url('[Link]
1bad197a6461?q=80&w=2958&auto=format&fit=crop')",
backgroundSize: "cover",
backgroundPosition: "center",
backgroundBlendMode: "overlay"
}}
/>
<[Link]
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
onClick={() => navigate('/play/ai')}
className="bg-transparent border-2 border-white text-white px-8 py-4 rounded-
lg font-semibold text-lg hover:bg-white/10 transition-colors"
>
Play vs AI
</[Link]>
</div>
{error && (
<[Link]
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
className="mt-4 text-red-500"
>
{error}
</[Link]>
)}
const fadeIn = {
hidden: { opacity: 0, y: 20 },
visible: { opacity: 1, y: 0 }
};
const inputAnimation = {
focus: { scale: 1.02, transition: { duration: 0.2 } },
blur: { scale: 1, transition: { duration: 0.2 } }
};
function Login() {
const navigate = useNavigate();
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState("");
try {
const response = await [Link]("http:localhost:5000/api/auth/login", {
email,
password,
});
return (
<div
className="min-h-screen bg-[#1a1a1a] flex items-center justify-center p-4 sm:p-6
md:p-8"
style={{
backgroundImage: "url('[Link]
1bad197a6461?q=80&w=2958&auto=format&fit=crop')",
backgroundSize: "cover",
backgroundPosition: "center",
backgroundBlendMode: "overlay"
}}
>
<[Link]
initial="hidden"
animate="visible"
variants={fadeIn}
className="w-full max-w-md"
>
<div className="bg-black/80 backdrop-blur-lg rounded-2xl p-8 shadow-2xl border
border-white/10">
<[Link]
initial={{ scale: 0, rotate: -180 }}
animate={{ scale: 1, rotate: 0 }}
transition={{ type: "spring", stiffness: 200, damping: 20 }}
className="w-20 h-20 bg-white rounded-full mx-auto mb-8 flex items-center
justify-center shadow-lg"
>
<ChessKing className="w-12 h-12 text-black" />
</[Link]>
<div className="relative">
<[Link]
whileFocus="focus"
whileBlur="blur"
variants={inputAnimation}
>
<input
type="password"
value={password}
onChange={(e) => setPassword([Link])}
className="w-full px-4 py-3 bg-white/10 rounded-lg pl-12 text-white
placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-white/30 transition-all
border border-white/10"
placeholder="Password"
required
/>
<Lock className="absolute left-4 top-3.5 w-5 h-5 text-gray-400" />
</[Link]>
</div>
{error && (
<motion.p
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
className="text-red-400 text-sm text-center"
>
{error}
</motion.p>
)}
<[Link]
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
type="submit"
disabled={isLoading}
className="w-full bg-white text-black py-3 rounded-lg font-semibold hover:bg-
gray-100 transition-all disabled:opacity-70 flex items-center justify-center"
>
{isLoading ? (
<Loader2 className="w-5 h-5 animate-spin" />
):(
"Sign In"
)}
</[Link]>
<div className="flex items-center gap-4 my-6">
<div className="flex-1 h-px bg-white/10"></div>
<span className="text-gray-400 text-sm">or</span>
<div className="flex-1 h-px bg-white/10"></div>
</div>
<motion.p
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.2 }}
className="text-center text-gray-400 text-sm"
>
New to ChessMaster?{" "}
<button
onClick={() => navigate("/signup")}
className="text-white font-semibold hover:text-gray-200 transition-colors"
>
Create an account
</button>
</motion.p>
</form>
</div>
</[Link]>
</div>
);
}
// Connect to socket
useEffect(() => {
[Link]('connect', () => {
setIsConnected(true);
});
[Link]('disconnect', () => {
setIsConnected(false);
setGameState({
status: 'idle',
opponent: null,
roomId: roomId,
message: 'Disconnected from server'
});
setPlayers([]);
});
if ([Link]()) {
setOver(`${[Link]} wins!`);
alert("Game over!");
navigate("/home")
}
[Link](over)
});
if ([Link]()) {
if ([Link]()) {
setOver(
`Checkmate! ${[Link]() === "w" ? "black" : "white"} wins!`
);
} else if ([Link]()) {
setOver("Draw");
} else {
setOver("Game over");
}
}
return result;
} catch (e) {
return null;
}
},
[chess]
);
const moveData = {
from: sourceSquare,
to: targetSquare,
color: [Link](),
promotion: "q",
};
[Link]("move", {
move,
room: [Link],
});
if ([Link]()) {
setOver(`you wins!`);
alert("Game over! you win");
navigate("/home")
}
return true;
}
// Set username
const handleSetUsername = () => {
if ([Link]()) {
[Link]('username', username);
if ([Link]) {
[Link]();
}
}
};
// Find match
const handleFindMatch = () => {
if (![Link]()) {
alert('Please set a username first');
return;
}
setGameState({
status: 'waiting',
opponent: null,
roomId: null,
message: 'Looking for an opponent...'
});
// Cancel matchmaking
const handleCancelMatchmaking = () => {
[Link]('cancelMatchmaking');
setGameState({
status: 'idle',
opponent: null,
roomId: null,
message: 'Matchmaking cancelled'
});
};
// Leave game
const handleLeaveGame = () => {
[Link]('leaveGame');
handleCleanup();
};
return (
<div className="game-container p-4 max-w-4xl mx-auto bg-gray-100 rounded-
lg shadow-md">
<h1 className="text-2xl font-bold mb-4">Chess Game</h1>
</div>
);
};
export default Game;
Register/[Link]
const fadeIn = {
hidden: { opacity: 0, y: 20 },
visible: { opacity: 1, y: 0 }
};
const inputAnimation = {
focus: { scale: 1.02, transition: { duration: 0.2 } },
blur: { scale: 1, transition: { duration: 0.2 } }
};
function Signup() {
const navigate = useNavigate();
const [username, setUsername] = useState("");
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [mobile, setMobile] = useState("");
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState("");
const user = {
username,
email,
password,
mobile,
rating: 0,
wins: 0,
losses: 0,
draws: 0,
matches: 0
};
try {
const response = await [Link]("http:localhost:5000/api/auth/signup", user);
if ([Link] === 201) {
navigate("/login");
}
} catch (err) {
setError([Link]?.data?.message || "Signup failed. Please try again.");
} finally {
setIsLoading(false);
}
};
return (
<div
className="min-h-screen bg-[#1a1a1a] flex items-center justify-center p-4 sm:p-6
md:p-8"
style={{
backgroundImage: "url('[Link]
1bad197a6461?q=80&w=2958&auto=format&fit=crop')",
backgroundSize: "cover",
backgroundPosition: "center",
backgroundBlendMode: "overlay"
}}
>
<[Link]
initial="hidden"
animate="visible"
variants={fadeIn}
className="w-full max-w-md"
>
<div className="bg-black/80 backdrop-blur-lg rounded-2xl p-8 shadow-2xl border
border-white/10">
<[Link]
initial={{ scale: 0, rotate: -180 }}
animate={{ scale: 1, rotate: 0 }}
transition={{ type: "spring", stiffness: 200, damping: 20 }}
className="w-20 h-20 bg-white rounded-full mx-auto mb-8 flex items-center
justify-center shadow-lg"
>
<ChessKing className="w-12 h-12 text-black" />
</[Link]>
<div className="relative">
<[Link]
whileFocus="focus"
whileBlur="blur"
variants={inputAnimation}
>
<input
type="email"
value={email}
onChange={(e) => setEmail([Link])}
className="w-full px-4 py-3 bg-white/10 rounded-lg pl-12 text-white
placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-white/30 transition-all
border border-white/10"
placeholder="Email"
required
/>
<Mail className="absolute left-4 top-3.5 w-5 h-5 text-gray-400" />
</[Link]>
</div>
<div className="relative">
<[Link]
whileFocus="focus"
whileBlur="blur"
variants={inputAnimation}
>
<input
type="tel"
value={mobile}
onChange={(e) => setMobile([Link])}
className="w-full px-4 py-3 bg-white/10 rounded-lg pl-12 text-white
placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-white/30 transition-all
border border-white/10"
placeholder="Mobile Number"
required
/>
<Phone className="absolute left-4 top-3.5 w-5 h-5 text-gray-400" />
</[Link]>
</div>
<div className="relative">
<[Link]
whileFocus="focus"
whileBlur="blur"
variants={inputAnimation}
>
<input
type="password"
value={password}
onChange={(e) => setPassword([Link])}
className="w-full px-4 py-3 bg-white/10 rounded-lg pl-12 text-white
placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-white/30 transition-all
border border-white/10"
placeholder="Password"
required
/>
<Lock className="absolute left-4 top-3.5 w-5 h-5 text-gray-400" />
</[Link]>
</div>
{error && (
<motion.p
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
className="text-red-400 text-sm text-center"
>
{error}
</motion.p>
)}
<[Link]
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
type="submit"
disabled={isLoading}
className="w-full bg-white text-black py-3 rounded-lg font-semibold hover:bg-
gray-100 transition-all disabled:opacity-70 flex items-center justify-center"
>
{isLoading ? (
<Loader2 className="w-5 h-5 animate-spin" />
):(
"Create Account"
)}
</[Link]>
<motion.p
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.2 }}
className="text-center text-gray-400 text-sm"
>
Already have an account?{" "}
<button
onClick={() => navigate("/login")}
className="text-white font-semibold hover:text-gray-200 transition-colors"
>
Sign In
</button>
</motion.p>
</form>
</div>
</[Link]>
</div>
);
}
<Route
path="/game/:roomId"
element={
<PrivateRoute>
<Game />
</PrivateRoute>
}
/>
<Route
path="/play/ai"
element={
<PrivateRoute>
{/* AI Game component would go here */}
<div>AI Game - To be implemented</div>
</PrivateRoute>
}
/>
createRoot([Link]('root')).render(
<StrictMode>
<App />
</StrictMode>,
)
Backend:
Models/[Link]
const mongoose = require("mongoose")
[Link]=[Link]("User",userSchema)
routes/[Link]
// [Link]
const { v4: uuidv4 } = require('uuid');
[Link] = function(io) {
// Store for active users and their socket connections
const users = {};
// Set username
[Link]('joinRoom', ({ roomId, username }, callback) => {
const user = users[[Link]];
if (!user) {
callback({ success: false, message: 'Please set a username first' });
return;
}
if (rooms[roomId]) {
// Room exists, join it
[Link](roomId);
rooms[roomId].[Link]([Link]);
// Find opponent
const opponentId = rooms[roomId].[Link](id => id !== [Link]);
const opponent = users[opponentId] ? { id: opponentId, username:
users[opponentId].username } : null;
// Find match
[Link]('findMatch', (callback) => {
const user = users[[Link]];
if (!user) {
callback({ waiting: false, message: 'Please set a username first' });
return;
}
if ([Link]) {
callback({ waiting: false, message: 'You are already in a game' });
return;
}
[Link](player2Id).emit('matchFound', {
opponent: player1,
roomId: roomId,
color: 'black'
});
// Cancel matchmaking
[Link]('cancelMatchmaking', () => {
const index = [Link]([Link]);
if (index !== -1) {
[Link](index, 1);
[Link](`User ${users[[Link]]?.username || [Link]} canceled matchmaking`);
}
});
// Leave game
[Link]('leaveGame', () => {
const user = users[[Link]];
if (!user || ![Link] || ![Link]) return;
if (room) {
// Notify other player
const otherPlayerId = [Link](id => id !== [Link]);
if (otherPlayerId && users[otherPlayerId]) {
[Link](otherPlayerId).emit('opponentLeft', user);
// Clean up room
delete rooms[roomId];
}
[Link](roomId);
[Link](`User ${[Link]} left game in room ${roomId}`);
});
// Close room
[Link]('closeRoom', ({ roomId }) => {
[Link]([Link])
if (!roomId || !rooms[roomId]) return;
// Clean up room
delete rooms[roomId];
[Link](roomId).emit('closeRoom', { roomId });
// Disconnect
[Link]('disconnect', () => {
const user = users[[Link]];
if (room) {
const otherPlayerId = [Link](id => id !== [Link]);
if (otherPlayerId && users[otherPlayerId]) {
[Link](otherPlayerId).emit('playerDisconnected', user);
// Clean up room
delete rooms[roomId];
}
}
// Default role to "user" unless specified as "admin" (only for manually added
admins)
const newUser = await [Link]({
username,
email,
password:hashedPassword,
rating,
matches,
wins,
losses,
mobile
});
[Link] = router;
[Link]
// Initialize [Link]
const io = new Server(server, {
cors: {
origin: "*",
methods: ["GET", "POST"],
},
});
[Link]([Link]());
[Link](cors());
[Link]("/api/auth", authRoutes);
[Link](port, () => {
[Link](`listening on *:${port}`);
});
Output:
Result:
Hence, I have successfully created a full-stack web application for Sharing the
Stories.