Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added playble tic tac toe game in Board game section #401

Closed
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 12 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -274,19 +274,19 @@ We extend our heartfelt gratitude to all the amazing contributors who have made
</a>
</td>
<td align="center">
<a href="https://github.com/alo7lika">
<img src="https://avatars.githubusercontent.com/u/152315710?v=4" width="100;" alt="alo7lika"/>
<a href="https://github.com/Ashwinib26">
<img src="https://avatars.githubusercontent.com/u/149402720?v=4" width="100;" alt="Ashwinib26"/>
<br />
<sub><b>alolika bhowmik</b></sub>
<sub><b>Ashwini_ab</b></sub>
Comment on lines +277 to +280
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix indentation using spaces instead of tabs.

The indentation in these sections uses hard tabs. For consistency with the rest of the file and to follow markdown best practices, replace tabs with spaces.

Apply this change to fix the indentation:

-                <a href="https://github.com/Ashwinib26">
-                    <img src="https://avatars.githubusercontent.com/u/149402720?v=4" width="100;" alt="Ashwinib26"/>
-                    <br />
-                    <sub><b>Ashwini_ab</b></sub>
+                <a href="https://github.com/Ashwinib26">
+                    <img src="https://avatars.githubusercontent.com/u/149402720?v=4" width="100;" alt="Ashwinib26"/>
+                    <br />
+                    <sub><b>Ashwini_ab</b></sub>

Also applies to: 286-289

</a>
</td>
</tr>
<tr>
<td align="center">
<a href="https://github.com/Ashwinib26">
<img src="https://avatars.githubusercontent.com/u/149402720?v=4" width="100;" alt="Ashwinib26"/>
<a href="https://github.com/alo7lika">
<img src="https://avatars.githubusercontent.com/u/152315710?v=4" width="100;" alt="alo7lika"/>
<br />
<sub><b>Ashwini_ab</b></sub>
<sub><b>alolika bhowmik</b></sub>
</a>
</td>
<td align="center">
Expand All @@ -311,10 +311,10 @@ We extend our heartfelt gratitude to all the amazing contributors who have made
</a>
</td>
<td align="center">
<a href="https://github.com/NilanchalaPanda">
<img src="https://avatars.githubusercontent.com/u/110488337?v=4" width="100;" alt="NilanchalaPanda"/>
<a href="https://github.com/VinayLodhi1712">
<img src="https://avatars.githubusercontent.com/u/135756009?v=4" width="100;" alt="VinayLodhi1712"/>
<br />
<sub><b>Nilanchal</b></sub>
<sub><b>Vinay Anand Lodhi</b></sub>
</a>
</td>
<td align="center">
Expand All @@ -327,10 +327,10 @@ We extend our heartfelt gratitude to all the amazing contributors who have made
</tr>
<tr>
<td align="center">
<a href="https://github.com/VinayLodhi1712">
<img src="https://avatars.githubusercontent.com/u/135756009?v=4" width="100;" alt="VinayLodhi1712"/>
<a href="https://github.com/NilanchalaPanda">
<img src="https://avatars.githubusercontent.com/u/110488337?v=4" width="100;" alt="NilanchalaPanda"/>
<br />
<sub><b>Vinay Anand Lodhi</b></sub>
<sub><b>Nilanchal</b></sub>
</a>
</td>
<td align="center">
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/components/Pages/Boardgame.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import bg from '../../assets/Boardgames/bg.jpg';
import { LazyLoadImage } from 'react-lazy-load-image-component';
import 'react-lazy-load-image-component/src/effects/blur.css';
import MainHOC from '../MainHOC';
import { Link } from 'react-router-dom';
export default MainHOC(Boardgame);
function Boardgame() {
const [selectedBoard, setSelectedBoard] = useState(null);
Expand Down Expand Up @@ -396,7 +397,7 @@ function Boardgame() {
onClick={() => handleInstantPlay(board)}
className="px-4 py-2 text-white bg-blue-500 rounded-lg opacity-0 transition-opacity duration-700 delay-300 group-hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-blue-500"
>
Instant Play
<Link to="/TicTacToe">Instant Play</Link>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Conditionally render the Instant Play button.

Currently, the "Instant Play" button appears for all board games, but based on the PR objectives, only Tic Tac Toe should be playable. This might confuse users who expect other games to be playable as well.

Consider conditionally rendering the button only for Tic Tac Toe:

<div className="flex space-x-2 mt-4">
  <button
    onClick={() => handleOpenInstructions(board)}
    className="px-4 py-2 text-white bg-green-500 rounded-lg opacity-0 transition-opacity duration-700 delay-300 group-hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-blue-500"
  >
    See Instructions
  </button>
+  {board.title === "Tic-Tac-Toe" && (
    <Link
      to="/TicTacToe"
      className="inline-block px-4 py-2 text-white bg-blue-500 rounded-lg opacity-0 transition-opacity duration-700 delay-300 group-hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-blue-500"
    >
      Instant Play
    </Link>
+  )}
</div>

Committable suggestion was skipped due to low confidence.


⚠️ Potential issue

Fix button accessibility and remove unused handler.

The current implementation has several issues:

  1. Nesting a Link inside a button is an accessibility anti-pattern as it creates nested interactive elements
  2. There's an unused onClick handler (handleInstantPlay)
  3. The styling might not properly propagate to the Link

Consider this implementation instead:

-<button
-  onClick={() => handleInstantPlay(board)}
-  className="px-4 py-2 text-white bg-blue-500 rounded-lg opacity-0 transition-opacity duration-700 delay-300 group-hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-blue-500"
->
-  <Link to="/TicTacToe">Instant Play</Link>
-</button>
+<Link
+  to="/TicTacToe"
+  className="inline-block px-4 py-2 text-white bg-blue-500 rounded-lg opacity-0 transition-opacity duration-700 delay-300 group-hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-blue-500"
+>
+  Instant Play
+</Link>

Committable suggestion was skipped due to low confidence.

</button>
</div>
</div>
Expand Down
79 changes: 79 additions & 0 deletions frontend/src/components/Pages/Games/TicTacToe.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import React, { useState } from 'react';

function TicTacToe() {
const [board, setBoard] = useState(Array(9).fill(null));
const [isXNext, setIsXNext] = useState(true);
const winner = calculateWinner(board);
Comment on lines +1 to +6
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider enhancing type safety and accessibility

While the basic setup is correct, consider these improvements:

  1. Add PropTypes or TypeScript for better type safety
  2. Consider extracting game logic into a custom hook for better separation of concerns

Example implementation of a custom hook:

function useGameState() {
  const [board, setBoard] = useState(Array(9).fill(null));
  const [isXNext, setIsXNext] = useState(true);
  const winner = calculateWinner(board);
  
  // ... game logic here
  
  return { board, isXNext, winner, handleClick, resetGame };
}


const handleClick = (index) => {
if (board[index] || winner) return;
const newBoard = board.slice();
newBoard[index] = isXNext ? 'X' : 'O';
setBoard(newBoard);
setIsXNext(!isXNext);
};
Comment on lines +8 to +14
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add input validation and state update safety

The handleClick function should validate the input index and use the functional update form of setState to handle potential race conditions.

  const handleClick = (index) => {
+   if (typeof index !== 'number' || index < 0 || index > 8) return;
    if (board[index] || winner) return;
-   const newBoard = board.slice();
-   newBoard[index] = isXNext ? 'X' : 'O';
-   setBoard(newBoard);
+   setBoard(currentBoard => {
+     if (currentBoard[index]) return currentBoard;
+     const newBoard = currentBoard.slice();
+     newBoard[index] = isXNext ? 'X' : 'O';
+     return newBoard;
+   });
    setIsXNext(!isXNext);
  };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const handleClick = (index) => {
if (board[index] || winner) return;
const newBoard = board.slice();
newBoard[index] = isXNext ? 'X' : 'O';
setBoard(newBoard);
setIsXNext(!isXNext);
};
const handleClick = (index) => {
if (typeof index !== 'number' || index < 0 || index > 8) return;
if (board[index] || winner) return;
setBoard(currentBoard => {
if (currentBoard[index]) return currentBoard;
const newBoard = currentBoard.slice();
newBoard[index] = isXNext ? 'X' : 'O';
return newBoard;
});
setIsXNext(!isXNext);
};


const resetGame = () => {
setBoard(Array(9).fill(null));
setIsXNext(true);
};

return (
<div className="flex flex-col items-center my-20">
<h1 className="text-3xl font-bold mb-5">Tic Tac Toe</h1>
<div className="grid grid-cols-3 gap-2 w-64">
{board.map((value, index) => (
<button
key={index}
onClick={() => handleClick(index)}
className="w-20 h-20 bg-amber-200 flex items-center justify-center text-3xl font-semibold text-black-800 border-2 border-amber-500 hover:bg-amber-300 transition-all duration-200 rounded-md"
>
{value}
</button>
))}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Improve accessibility and keyboard navigation

The game board buttons need accessibility enhancements:

  1. Missing aria-labels for screen readers
  2. No keyboard navigation support

Apply these improvements:

 <button
   key={index}
   onClick={() => handleClick(index)}
+  aria-label={`Cell ${index + 1}, ${value || 'empty'}`}
+  tabIndex={0}
+  disabled={board[index] || winner}
   className={`w-20 h-20 bg-amber-200 flex items-center justify-center text-3xl 
-    font-semibold text-black-800 border-2 border-amber-500 hover:bg-amber-300
+    font-semibold text-black-800 border-2 border-amber-500 
+    ${!board[index] && !winner ? 'hover:bg-amber-300' : 'cursor-not-allowed opacity-75'}
     transition-all duration-200 rounded-md`}
 >
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{board.map((value, index) => (
<button
key={index}
onClick={() => handleClick(index)}
className="w-20 h-20 bg-amber-200 flex items-center justify-center text-3xl font-semibold text-black-800 border-2 border-amber-500 hover:bg-amber-300 transition-all duration-200 rounded-md"
>
{value}
</button>
))}
{board.map((value, index) => (
<button
key={index}
onClick={() => handleClick(index)}
aria-label={`Cell ${index + 1}, ${value || 'empty'}`}
tabIndex={0}
disabled={board[index] || winner}
className={`w-20 h-20 bg-amber-200 flex items-center justify-center text-3xl font-semibold text-black-800 border-2 border-amber-500 ${!board[index] && !winner ? 'hover:bg-amber-300' : 'cursor-not-allowed opacity-75'} transition-all duration-200 rounded-md`}
>
{value}
</button>
))}

</div>
<div className="mt-5 content-center ">
{winner ? (
<h2 className="text-2xl font-semibold text-green-600 text-center">
{winner} Wins!
</h2>
) : board.every((cell) => cell) ? (
<h2 className="text-2xl font-semibold text-red-600 text-center">It's a Draw!</h2>
) : (
<h2 className="text-xl font-semibold text-gray-700 text-center">
Next Player: {isXNext ? 'X' : 'O'}
</h2>
)}
<button
onClick={resetGame}
className="ml-1 mt-4 px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 hover:text-white transition-all duration-200"
>
Reset Game
</button>
</div>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Enhance game status with score tracking

Consider adding a score tracking feature to make the game more engaging across multiple rounds.

Add score tracking:

+ const [scores, setScores] = useState({ X: 0, O: 0 });

  const resetGame = () => {
    setBoard(Array(9).fill(null));
    setIsXNext(true);
  };

+ const updateScore = (winner) => {
+   if (winner) {
+     setScores(prev => ({
+       ...prev,
+       [winner]: prev[winner] + 1
+     }));
+   }
+ };

+ // Add score display in the UI
+ <div className="mt-3 text-lg">
+   <span className="mr-4">X: {scores.X}</span>
+   <span>O: {scores.O}</span>
+ </div>

Committable suggestion was skipped due to low confidence.

Comment on lines +54 to +60
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add keyboard shortcut for game reset

Consider adding a keyboard shortcut (e.g., 'R' key) for resetting the game to improve user experience.

+ useEffect(() => {
+   const handleKeyPress = (e) => {
+     if (e.key.toLowerCase() === 'r') {
+       resetGame();
+     }
+   };
+   window.addEventListener('keydown', handleKeyPress);
+   return () => window.removeEventListener('keydown', handleKeyPress);
+ }, []);

  <button
    onClick={resetGame}
+   title="Press 'R' to reset game"
    className="ml-1 mt-4 px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 hover:text-white transition-all duration-200"
  >
    Reset Game
  </button>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<button
onClick={resetGame}
className="ml-1 mt-4 px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 hover:text-white transition-all duration-200"
>
Reset Game
</button>
</div>
useEffect(() => {
const handleKeyPress = (e) => {
if (e.key.toLowerCase() === 'r') {
resetGame();
}
};
window.addEventListener('keydown', handleKeyPress);
return () => window.removeEventListener('keydown', handleKeyPress);
}, []);
<button
onClick={resetGame}
title="Press 'R' to reset game"
className="ml-1 mt-4 px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 hover:text-white transition-all duration-200"
>
Reset Game
</button>
</div>

</div>
);
}

function calculateWinner(board) {
const lines = [
[0, 1, 2],
[3, 4, 5],
[6, 7, 8],
[0, 3, 6],
[1, 4, 7],
[2, 5, 8],
[0, 4, 8],
[2, 4, 6],
];

for (let i = 0; i < lines.length; i++) {
const [a, b, c] = lines[i];
if (board[a] && board[a] === board[b] && board[a] === board[c]) {
return board[a];
}
}
return null;
}
Comment on lines +65 to +84
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Optimize winner calculation with memoization

The winner calculation could be optimized to prevent unnecessary recalculations.

Consider using useMemo:

+ import React, { useState, useMemo } from 'react';

  function TicTacToe() {
-   const winner = calculateWinner(board);
+   const winner = useMemo(() => calculateWinner(board), [board]);

Committable suggestion was skipped due to low confidence.


export default TicTacToe;
2 changes: 2 additions & 0 deletions frontend/src/router/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import VerifyOtp from '../components/Pages/VerifyOtp';
import EmailVerify from '../components/Pages/EmailVerify';
import Membership from '../components/Membership';
import HelpAndSupport from '../components/Pages/HelpAndSupport';
import TicTacToe from '../components/Pages/Games/TicTacToe';
const router = createBrowserRouter(
createRoutesFromElements(
<Route path="/" element={<App />}>
Expand All @@ -41,6 +42,7 @@ const router = createBrowserRouter(
<Route path="/email-verify" element={<EmailVerify />} />
<Route path="/membership" element={<Membership />} />
<Route path="/help" element={<HelpAndSupport />} />
<Route path="/TicTacToe" element={<TicTacToe />} />
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider route naming consistency and organization.

The current implementation has a few areas for improvement:

  1. The route path uses PascalCase ("/TicTacToe") while other routes use kebab-case or lowercase. Consider using "/tic-tac-toe" for consistency.
  2. Since this is part of the Board game section, consider either:
    • Grouping it near the "/boardgame" route for better code organization
    • Using nested routing under "/boardgame" for better URL hierarchy

Consider this refactoring approach:

      <Route path="/boardgame" element={<Boardgame />} />
+     <Route path="/boardgame/tic-tac-toe" element={<TicTacToe />} />
      <Route path="/events" element={<Event />} />
      ...
-     <Route path="/TicTacToe" element={<TicTacToe />} />

Note: If you implement this change, remember to update the corresponding Link component in Boardgame.jsx to use the new path.

Committable suggestion was skipped due to low confidence.


</Route>
)
Expand Down
Loading