Skip to content

Commit c43462b

Browse files
committed
new(apps): connect four
1 parent 320a5f1 commit c43462b

File tree

3 files changed

+224
-0
lines changed

3 files changed

+224
-0
lines changed

public/apps/apps.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,13 @@
8888
"title": "Tic Tac Toe",
8989
"description": "Play the classic game of Tic Tac Toe against another player or AI.",
9090
"icon": "XCircleIcon"
91+
},
92+
{
93+
"slug": "connect-four",
94+
"to": "/apps/connect-four",
95+
"title": "Connect Four",
96+
"description": "Play the classic game of Connect Four against another player or AI.",
97+
"icon": "AtomIcon"
9198
}
9299
]
93100
},

src/components/AnimatedRoutes.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ import SoccerPongPage from '../pages/apps/SoccerPongPage';
4949
import MemoryGamePage from '../pages/apps/MemoryGamePage'; // Import MemoryGamePage
5050
import RockPaperScissorsPage from '../pages/apps/RockPaperScissorsPage'; // Import RockPaperScissorsPage
5151
import TicTacToePage from '../pages/apps/TicTacToePage'; // Import TicTacToePage
52+
import ConnectFourPage from '../pages/apps/ConnectFourPage'; // Import ConnectFourPage
5253
import SettingsPage from '../pages/SettingsPage';
5354

5455
import UsefulLinksPage from '../pages/UsefulLinksPage';
@@ -409,6 +410,7 @@ function AnimatedRoutes() {
409410
<Route path="/apps::mg" element={<Navigate to="/apps/memory-game" replace />} />
410411
<Route path="/apps::rps" element={<Navigate to="/apps/rock-paper-scissors" replace />} />
411412
<Route path="/apps::ttt" element={<Navigate to="/apps/tic-tac-toe" replace />} />
413+
<Route path="/apps::c4" element={<Navigate to="/apps/connect-four" replace />} />
412414
{/* End of hardcoded redirects */}
413415
<Route
414416
path="/apps/ip"
@@ -424,6 +426,20 @@ function AnimatedRoutes() {
424426
</motion.div>
425427
}
426428
/>
429+
<Route
430+
path="/apps/connect-four"
431+
element={
432+
<motion.div
433+
initial="initial"
434+
animate="in"
435+
exit="out"
436+
variants={pageVariants}
437+
transition={pageTransition}
438+
>
439+
<ConnectFourPage />
440+
</motion.div>
441+
}
442+
/>
427443
<Route
428444
path="/apps/tic-tac-toe"
429445
element={

src/pages/apps/ConnectFourPage.js

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
import React, { useState, useEffect } from 'react';
2+
import { Link } from 'react-router-dom';
3+
import { ArrowLeft } from '@phosphor-icons/react';
4+
import { useToast } from '../../hooks/useToast';
5+
import useSeo from "../../hooks/useSeo";
6+
import colors from '../../config/colors';
7+
8+
const ConnectFourPage = () => {
9+
useSeo({
10+
title: 'Connect Four | Fezcodex',
11+
description: 'Play the classic game of Connect Four against another player or AI.',
12+
keywords: ['Fezcodex', 'connect four', 'game', 'fun app'],
13+
ogTitle: 'Connect Four | Fezcodex',
14+
ogDescription: 'Play the classic game of Connect Four against another player or AI.',
15+
ogImage: 'https://fezcode.github.io/logo512.png',
16+
twitterCard: 'summary_large_image',
17+
twitterTitle: 'Connect Four | Fezcodex',
18+
twitterDescription: 'Play the classic game of Connect Four against another player or AI.',
19+
twitterImage: 'https://fezcode.github.io/logo512.png'
20+
});
21+
22+
const { addToast } = useToast();
23+
24+
// Game state (to be implemented)
25+
const [board, setBoard] = useState([]); // 6 rows, 7 columns
26+
const [currentPlayer, setCurrentPlayer] = useState(1); // 1 for Player 1 (Red), 2 for Player 2 (Yellow)
27+
const [winner, setWinner] = useState(null); // null, 1, 2, or 'Draw'
28+
29+
useEffect(() => {
30+
initializeBoard();
31+
}, []);
32+
33+
const initializeBoard = () => {
34+
const newBoard = Array(6).fill(null).map(() => Array(7).fill(0)); // 0 for empty, 1 for Player 1, 2 for Player 2
35+
setBoard(newBoard);
36+
setCurrentPlayer(1);
37+
setWinner(null);
38+
};
39+
40+
const handleColumnClick = (colIndex) => {
41+
if (winner) return;
42+
43+
const newBoard = board.map(row => [...row]);
44+
let rowIndex = -1;
45+
46+
// Find the lowest empty spot in the column
47+
for (let r = 5; r >= 0; r--) {
48+
if (newBoard[r][colIndex] === 0) {
49+
newBoard[r][colIndex] = currentPlayer;
50+
rowIndex = r;
51+
break;
52+
}
53+
}
54+
55+
if (rowIndex === -1) {
56+
addToast({ title: 'Column Full', message: 'This column is full. Choose another one.', duration: 2000 });
57+
return;
58+
}
59+
60+
setBoard(newBoard);
61+
62+
if (checkWin(newBoard, rowIndex, colIndex, currentPlayer)) {
63+
setWinner(currentPlayer);
64+
addToast({ title: 'Game Over', message: `Player ${currentPlayer} wins!`, duration: 3000 });
65+
} else if (newBoard.every(row => row.every(cell => cell !== 0))) {
66+
setWinner('Draw');
67+
addToast({ title: 'Game Over', message: 'It\'s a draw!', duration: 3000 });
68+
} else {
69+
setCurrentPlayer(currentPlayer === 1 ? 2 : 1);
70+
}
71+
};
72+
73+
const checkWin = (currentBoard, row, col, player) => {
74+
// Check horizontal
75+
for (let c = 0; c <= 3; c++) {
76+
if (currentBoard[row][c] === player &&
77+
currentBoard[row][c+1] === player &&
78+
currentBoard[row][c+2] === player &&
79+
currentBoard[row][c+3] === player) {
80+
return true;
81+
}
82+
}
83+
84+
// Check vertical
85+
for (let r = 0; r <= 2; r++) {
86+
if (currentBoard[r][col] === player &&
87+
currentBoard[r+1][col] === player &&
88+
currentBoard[r+2][col] === player &&
89+
currentBoard[r+3][col] === player) {
90+
return true;
91+
}
92+
}
93+
94+
// Check diagonal (top-left to bottom-right)
95+
for (let r = 0; r <= 2; r++) {
96+
for (let c = 0; c <= 3; c++) {
97+
if (currentBoard[r][c] === player &&
98+
currentBoard[r+1][c+1] === player &&
99+
currentBoard[r+2][c+2] === player &&
100+
currentBoard[r+3][c+3] === player) {
101+
return true;
102+
}
103+
}
104+
}
105+
106+
// Check diagonal (bottom-left to top-right)
107+
for (let r = 3; r <= 5; r++) {
108+
for (let c = 0; c <= 3; c++) {
109+
if (currentBoard[r][c] === player &&
110+
currentBoard[r-1][c+1] === player &&
111+
currentBoard[r-2][c+2] === player &&
112+
currentBoard[r-3][c+3] === player) {
113+
return true;
114+
}
115+
}
116+
}
117+
118+
return false;
119+
};
120+
121+
const getCellColor = (cellValue) => {
122+
if (cellValue === 1) return 'bg-red-500';
123+
if (cellValue === 2) return 'bg-yellow-500';
124+
return 'bg-gray-700';
125+
};
126+
127+
const status = winner
128+
? winner === 'Draw' ? 'Draw!' : `Player ${winner} wins!`
129+
: `Current Player: ${currentPlayer === 1 ? 'Red' : 'Yellow'}`;
130+
131+
const cardStyle = {
132+
backgroundColor: colors['app-alpha-10'],
133+
borderColor: colors['app-alpha-50'],
134+
color: colors.app,
135+
};
136+
137+
return (
138+
<div className="py-16 sm:py-24">
139+
<div className="mx-auto max-w-7xl px-6 lg:px-8 text-gray-300">
140+
<Link
141+
to="/apps"
142+
className="text-article hover:underline flex items-center justify-center gap-2 text-lg mb-4"
143+
>
144+
<ArrowLeft size={24} /> Back to Apps
145+
</Link>
146+
<h1 className="text-4xl font-bold tracking-tight sm:text-6xl mb-4 flex items-center">
147+
<span className="codex-color">fc</span>
148+
<span className="separator-color">::</span>
149+
<span className="apps-color">apps</span>
150+
<span className="separator-color">::</span>
151+
<span className="single-app-color">c4</span>
152+
</h1>
153+
<hr className="border-gray-700" />
154+
<div className="flex justify-center items-center mt-16">
155+
<div
156+
className="group bg-transparent border rounded-lg shadow-2xl p-6 flex flex-col justify-between relative transform transition-all duration-300 ease-in-out scale-105 overflow-hidden h-full w-full max-w-4xl"
157+
style={cardStyle}
158+
>
159+
<div
160+
className="absolute top-0 left-0 w-full h-full opacity-10"
161+
style={{
162+
backgroundImage:
163+
'radial-gradient(circle, white 1px, transparent 1px)',
164+
backgroundSize: '10px 10px',
165+
}}
166+
></div>
167+
<div className="relative z-10 p-1">
168+
<h1 className="text-3xl font-arvo font-normal mb-4 text-app"> Connect Four </h1>
169+
<hr className="border-gray-700 mb-4" />
170+
<div className="flex flex-col items-center gap-8">
171+
<div className="text-2xl font-medium mb-4">{status}</div>
172+
<div className="grid grid-cols-7 gap-1 p-2 bg-gray-800 rounded-lg">
173+
{board.map((row, rowIndex) =>
174+
row.map((cell, colIndex) => (
175+
<div
176+
key={`${rowIndex}-${colIndex}`}
177+
className="w-12 h-12 rounded-full flex items-center justify-center cursor-pointer"
178+
onClick={() => handleColumnClick(colIndex)}
179+
>
180+
<div className={`w-10 h-10 rounded-full ${getCellColor(cell)}`}></div>
181+
</div>
182+
))
183+
)}
184+
</div>
185+
<button
186+
onClick={initializeBoard}
187+
className="flex items-center gap-2 text-lg font-arvo font-normal px-4 py-2 rounded-md border transition-colors duration-300 ease-in-out bg-app/50 text-white hover:bg-app/70"
188+
style={{ borderColor: colors['app-alpha-50'] }}
189+
>
190+
Reset Game
191+
</button>
192+
</div>
193+
</div>
194+
</div>
195+
</div>
196+
</div>
197+
</div>
198+
);
199+
};
200+
201+
export default ConnectFourPage;

0 commit comments

Comments
 (0)