Date: Thu, 25 Dec 2025 18:40:08 +0100
Subject: [PATCH 05/11] style: improve UI for "Play vs Computer" feature
---
.../features/engine/PlayComputer.module.scss | 193 ++++++++++++++----
1 file changed, 148 insertions(+), 45 deletions(-)
diff --git a/react-ystemandchess/src/features/engine/PlayComputer.module.scss b/react-ystemandchess/src/features/engine/PlayComputer.module.scss
index 8209e09f..6556e9e5 100644
--- a/react-ystemandchess/src/features/engine/PlayComputer.module.scss
+++ b/react-ystemandchess/src/features/engine/PlayComputer.module.scss
@@ -4,17 +4,19 @@
align-items: center;
padding: 2rem;
min-height: 100vh;
- background: #f5f5f5;
+ background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%);
}
.header {
text-align: center;
margin-bottom: 2rem;
-
+
h1 {
font-size: 2.5rem;
- color: #333;
+ color: #ffffff;
margin-bottom: 1rem;
+ text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
+ font-weight: 700;
}
}
@@ -23,43 +25,54 @@
justify-content: center;
gap: 2rem;
margin-top: 1rem;
-
+
.thinking {
- color: #ff9800;
+ color: #ffd54f;
font-weight: 600;
font-size: 1.1rem;
+ background: rgba(255, 152, 0, 0.15);
+ padding: 0.5rem 1rem;
+ border-radius: 20px;
+ border: 2px solid #ffd54f;
}
-
+
.gameStatus {
- color: #4caf50;
+ color: #81c784;
font-weight: 600;
font-size: 1.1rem;
+ background: rgba(76, 175, 80, 0.15);
+ padding: 0.5rem 1rem;
+ border-radius: 20px;
+ border: 2px solid #81c784;
}
}
.settingsPanel {
background: white;
padding: 3rem;
- border-radius: 12px;
- box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
+ border-radius: 16px;
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
max-width: 500px;
width: 100%;
-
+
h2 {
text-align: center;
margin-bottom: 2rem;
- color: #333;
+ color: #1e3c72;
+ font-size: 1.8rem;
+ font-weight: 700;
}
}
.setting {
margin-bottom: 2rem;
-
+
label {
display: block;
- margin-bottom: 0.5rem;
+ margin-bottom: 0.8rem;
font-weight: 600;
- color: #555;
+ color: #333;
+ font-size: 1.1rem;
}
}
@@ -67,33 +80,39 @@
.difficultyButtons {
display: flex;
gap: 1rem;
-
+
button {
flex: 1;
padding: 1rem;
- border: 2px solid #ddd;
+ border: 2px solid #e0e0e0;
background: white;
- border-radius: 8px;
+ border-radius: 10px;
cursor: pointer;
font-size: 1rem;
- transition: all 0.2s;
-
+ font-weight: 600;
+ color: #555;
+ transition: all 0.3s ease;
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
+
&:hover {
border-color: #7fcc26;
transform: translateY(-2px);
+ box-shadow: 0 4px 8px rgba(127, 204, 38, 0.2);
+ color: #333;
}
-
+
&.active {
- background: #7fcc26;
+ background: linear-gradient(135deg, #7fcc26 0%, #6fb31e 100%);
color: white;
border-color: #7fcc26;
+ box-shadow: 0 4px 12px rgba(127, 204, 38, 0.3);
}
}
}
.difficultyButtons {
flex-wrap: wrap;
-
+
button {
min-width: 100px;
}
@@ -102,22 +121,27 @@
.startButton {
width: 100%;
padding: 1.2rem;
- background: #7fcc26;
+ background: linear-gradient(135deg, #7fcc26 0%, #6fb31e 100%);
color: white;
border: none;
- border-radius: 8px;
+ border-radius: 10px;
font-size: 1.2rem;
- font-weight: 600;
+ font-weight: 700;
cursor: pointer;
- transition: background 0.2s;
-
+ transition: all 0.3s ease;
+ box-shadow: 0 4px 12px rgba(127, 204, 38, 0.3);
+ margin-top: 1rem;
+
&:hover:not(:disabled) {
- background: #6fb31e;
+ background: linear-gradient(135deg, #6fb31e 0%, #5fa015 100%);
+ transform: translateY(-2px);
+ box-shadow: 0 6px 16px rgba(127, 204, 38, 0.4);
}
-
+
&:disabled {
background: #ccc;
cursor: not-allowed;
+ box-shadow: none;
}
}
@@ -125,56 +149,135 @@
display: flex;
gap: 1rem;
margin-bottom: 2rem;
-
+ flex-wrap: wrap;
+ justify-content: center;
+
button {
padding: 0.8rem 1.5rem;
background: white;
- border: 2px solid #ddd;
- border-radius: 8px;
+ border: 2px solid #e0e0e0;
+ border-radius: 10px;
cursor: pointer;
font-size: 1rem;
- font-weight: 500;
- transition: all 0.2s;
-
+ font-weight: 600;
+ color: #555;
+ transition: all 0.3s ease;
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+
&:hover:not(:disabled) {
border-color: #7fcc26;
background: #f0f9e8;
+ transform: translateY(-2px);
+ box-shadow: 0 4px 8px rgba(127, 204, 38, 0.2);
+ color: #333;
}
-
+
&:disabled {
opacity: 0.5;
cursor: not-allowed;
+ transform: none;
}
}
}
.chessboardContainer {
margin-bottom: 2rem;
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
+ border-radius: 8px;
+ overflow: hidden;
}
.moveHistory {
background: white;
padding: 1.5rem;
- border-radius: 8px;
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+ border-radius: 12px;
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2);
max-width: 600px;
width: 100%;
-
+
h3 {
margin-bottom: 1rem;
- color: #333;
+ color: #1e3c72;
+ font-size: 1.3rem;
+ font-weight: 700;
+ border-bottom: 2px solid #e0e0e0;
+ padding-bottom: 0.5rem;
}
-
+
.moves {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
-
- .move {
- padding: 0.4rem 0.8rem;
- background: #f5f5f5;
+ max-height: 200px;
+ overflow-y: auto;
+ padding: 0.5rem 0;
+
+ &::-webkit-scrollbar {
+ width: 8px;
+ }
+
+ &::-webkit-scrollbar-track {
+ background: #f1f1f1;
+ border-radius: 4px;
+ }
+
+ &::-webkit-scrollbar-thumb {
+ background: #7fcc26;
border-radius: 4px;
+
+ &:hover {
+ background: #6fb31e;
+ }
+ }
+
+ .move {
+ padding: 0.5rem 1rem;
+ background: linear-gradient(135deg, #f5f5f5 0%, #e8e8e8 100%);
+ border-radius: 6px;
font-size: 0.9rem;
+ font-weight: 500;
+ color: #333;
+ border: 1px solid #ddd;
+ transition: all 0.2s ease;
+
+ &:hover {
+ background: linear-gradient(135deg, #e8f5e9 0%, #c8e6c9 100%);
+ border-color: #7fcc26;
+ transform: translateY(-1px);
+ }
+ }
+ }
+}
+
+// Responsive adjustments
+@media (max-width: 768px) {
+ .playComputerContainer {
+ padding: 1rem;
+ }
+
+ .header h1 {
+ font-size: 2rem;
+ }
+
+ .settingsPanel {
+ padding: 2rem;
+ }
+
+ .colorButtons,
+ .difficultyButtons {
+ flex-direction: column;
+
+ button {
+ width: 100%;
+ }
+ }
+
+ .controls {
+ width: 100%;
+
+ button {
+ flex: 1;
+ min-width: 120px;
}
}
}
From c0bc2315f36efd5de7987d0df6383a8e48eba90c Mon Sep 17 00:00:00 2001
From: F-Hejazi <60328249+F-Hejazi@users.noreply.github.com>
Date: Tue, 30 Dec 2025 15:52:49 +0100
Subject: [PATCH 06/11] fix: clean up environment URLs
---
react-ystemandchess/src/environments/environment.js | 7 +++----
react-ystemandchess/src/environments/environment.prod.js | 5 ++---
react-ystemandchess/src/features/engine/PlayComputer.tsx | 2 +-
3 files changed, 6 insertions(+), 8 deletions(-)
diff --git a/react-ystemandchess/src/environments/environment.js b/react-ystemandchess/src/environments/environment.js
index 2a84a330..5ecd0c4f 100644
--- a/react-ystemandchess/src/environments/environment.js
+++ b/react-ystemandchess/src/environments/environment.js
@@ -5,9 +5,8 @@ export const environment = {
},
urls: {
middlewareURL: 'http://localhost:8000',
- chessClientURL: 'http://localhost',
- stockFishURL: 'http://localhost:8080/stockfishserver/',
- chessServer: 'http://localhost:3001/',
+ stockfishServerURL: 'http://localhost:8080',
+ chessServerURL: 'http://localhost:3001',
},
- productionType: 'development', // development/production
+ productionType: 'development',
};
\ No newline at end of file
diff --git a/react-ystemandchess/src/environments/environment.prod.js b/react-ystemandchess/src/environments/environment.prod.js
index f86e300f..ed2ba30a 100644
--- a/react-ystemandchess/src/environments/environment.prod.js
+++ b/react-ystemandchess/src/environments/environment.prod.js
@@ -5,8 +5,7 @@ export const environment = {
},
urls: {
middlewareURL: 'http://localhost/middleware/',
- chessClientURL: 'http://localhost/chessclient/',
- stockFishURL: 'http://localhost/stockfishserver/',
- chessServer: 'http://localhost/chessserver/',
+ stockfishServerURL: 'http://localhost/stockfishserver/',
+ chessServerURL: 'http://localhost/chessserver/',
},
};
\ No newline at end of file
diff --git a/react-ystemandchess/src/features/engine/PlayComputer.tsx b/react-ystemandchess/src/features/engine/PlayComputer.tsx
index 7e23f3bc..3dc66576 100644
--- a/react-ystemandchess/src/features/engine/PlayComputer.tsx
+++ b/react-ystemandchess/src/features/engine/PlayComputer.tsx
@@ -55,7 +55,7 @@ const PlayComputer: React.FC = () => {
// Initialize socket connection
useEffect(() => {
- const socket = io(environment.urls.stockfishServerURL || 'http://localhost:8080', {
+ const socket = io(environment.urls.stockfishServerURL, {
transports: ['websocket'],
reconnection: true,
});
From 951527c96de4837000403bb00dfc7a33b13a992b Mon Sep 17 00:00:00 2001
From: F-Hejazi <60328249+F-Hejazi@users.noreply.github.com>
Date: Tue, 30 Dec 2025 16:23:16 +0100
Subject: [PATCH 07/11] fix: allow new game without socket reconnection
---
.../src/features/engine/PlayComputer.tsx | 3 +++
stockfishServer/src/managers/socket.js | 10 ++++++++++
2 files changed, 13 insertions(+)
diff --git a/react-ystemandchess/src/features/engine/PlayComputer.tsx b/react-ystemandchess/src/features/engine/PlayComputer.tsx
index 3dc66576..9946950d 100644
--- a/react-ystemandchess/src/features/engine/PlayComputer.tsx
+++ b/react-ystemandchess/src/features/engine/PlayComputer.tsx
@@ -247,6 +247,9 @@ const PlayComputer: React.FC = () => {
// Handle new game with settings
const newGame = useCallback(() => {
+ if (socketRef.current && sessionStartedRef.current) {
+ socketRef.current.emit('end-session');
+ }
resetGame();
setShowSettings(true);
setSessionStarted(false);
diff --git a/stockfishServer/src/managers/socket.js b/stockfishServer/src/managers/socket.js
index 4570b0ab..d7c325ce 100644
--- a/stockfishServer/src/managers/socket.js
+++ b/stockfishServer/src/managers/socket.js
@@ -35,6 +35,16 @@ const initializeSocket = (socket) => {
}
});
+ // End the current session without disconnecting the socket
+ socket.on("end-session", () => {
+ try {
+ stockfishManager.deleteSession(socket.id);
+ socket.emit("session-ended", { success: true });
+ } catch (err) {
+ console.error("Error ending session:", err);
+ }
+ });
+
// Clean up session when client disconnects
socket.on("disconnect", () => {
stockfishManager.deleteSession(socket.id);
From ee9f1c1b9b7223f1389571367674accc1e8f7e9a Mon Sep 17 00:00:00 2001
From: F-Hejazi <60328249+F-Hejazi@users.noreply.github.com>
Date: Wed, 31 Dec 2025 04:10:05 +0100
Subject: [PATCH 08/11] style: fix layout for settings and move history
---
.../features/engine/PlayComputer.module.scss | 453 +++++++++++++++---
.../src/features/engine/PlayComputer.tsx | 112 +++--
2 files changed, 453 insertions(+), 112 deletions(-)
diff --git a/react-ystemandchess/src/features/engine/PlayComputer.module.scss b/react-ystemandchess/src/features/engine/PlayComputer.module.scss
index 6556e9e5..8dc2537a 100644
--- a/react-ystemandchess/src/features/engine/PlayComputer.module.scss
+++ b/react-ystemandchess/src/features/engine/PlayComputer.module.scss
@@ -4,7 +4,7 @@
align-items: center;
padding: 2rem;
min-height: 100vh;
- background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%);
+ background: #3a7cca;
}
.header {
@@ -20,140 +20,227 @@
}
}
-.statusBar {
- display: flex;
- justify-content: center;
- gap: 2rem;
- margin-top: 1rem;
-
- .thinking {
- color: #ffd54f;
- font-weight: 600;
- font-size: 1.1rem;
- background: rgba(255, 152, 0, 0.15);
- padding: 0.5rem 1rem;
- border-radius: 20px;
- border: 2px solid #ffd54f;
- }
-
- .gameStatus {
- color: #81c784;
- font-weight: 600;
- font-size: 1.1rem;
- background: rgba(76, 175, 80, 0.15);
- padding: 0.5rem 1rem;
- border-radius: 20px;
- border: 2px solid #81c784;
- }
-}
-
.settingsPanel {
- background: white;
- padding: 3rem;
- border-radius: 16px;
- box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
- max-width: 500px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%);
+ padding: 3.5rem 3rem;
+ border-radius: 20px;
+ box-shadow: 0 12px 48px rgba(0, 0, 0, 0.25);
+ max-width: 550px;
width: 100%;
+ border: 3px solid rgba(58, 124, 202, 0.2);
+ box-sizing: border-box;
h2 {
text-align: center;
- margin-bottom: 2rem;
- color: #1e3c72;
- font-size: 1.8rem;
+ margin-bottom: 2.5rem;
+ color: #3a7cca;
+ font-size: 2rem;
font-weight: 700;
+ text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.05);
+ position: relative;
+ padding-bottom: 1rem;
+
+ &::after {
+ content: '';
+ position: absolute;
+ bottom: 0;
+ left: 50%;
+ transform: translateX(-50%);
+ width: 60px;
+ height: 4px;
+ background: linear-gradient(90deg, #7fcc26, #3a7cca);
+ border-radius: 2px;
+ }
}
}
.setting {
- margin-bottom: 2rem;
+ margin-bottom: 2.5rem;
label {
display: block;
- margin-bottom: 0.8rem;
- font-weight: 600;
- color: #333;
- font-size: 1.1rem;
+ margin-bottom: 1rem;
+ font-weight: 700;
+ color: #2c3e50;
+ font-size: 1.15rem;
+ letter-spacing: 0.3px;
+ }
+}
+
+.colorButtons {
+ display: grid;
+ width: 400px;
+ grid-template-columns: 1fr 1fr;
+ gap: 1.2rem;
+
+ button {
+ padding: 1.4rem;
+ border: 3px solid #e5e7eb;
+ border-radius: 12px;
+ cursor: pointer;
+ font-size: 1.2rem;
+ font-weight: 700;
+ transition: all 0.3s ease;
+ box-shadow: 0 3px 10px rgba(0, 0, 0, 0.1);
+ position: relative;
+ overflow: hidden;
+
+ &:first-child {
+ background: linear-gradient(135deg, #ffffff 0%, #f3f4f6 100%);
+ color: #1f2937;
+ border-color: #d1d5db;
+
+ &:hover {
+ background: linear-gradient(135deg, #f9fafb 0%, #e5e7eb 100%);
+ transform: translateY(-3px);
+ box-shadow: 0 6px 16px rgba(0, 0, 0, 0.15);
+ }
+
+ &.active {
+ background: linear-gradient(135deg, #ffffff 0%, #e5e7eb 100%);
+ color: #111827;
+ border-color: #7fcc26;
+ box-shadow: 0 6px 20px rgba(0, 0, 0, 0.25), inset 0 2px 4px rgba(0, 0, 0, 0.1);
+ transform: scale(1.02);
+ }
+ }
+
+ &:last-child {
+ background: linear-gradient(135deg, #1f2937 0%, #111827 100%);
+ color: #ffffff;
+ border-color: #374151;
+
+ &:hover {
+ background: linear-gradient(135deg, #374151 0%, #1f2937 100%);
+ transform: translateY(-3px);
+ box-shadow: 0 6px 16px rgba(0, 0, 0, 0.3);
+ }
+
+ &.active {
+ background: linear-gradient(135deg, #111827 0%, #000000 100%);
+ color: #ffffff;
+ border-color: #7fcc26;
+ box-shadow: 0 6px 20px rgba(0, 0, 0, 0.4), inset 0 2px 4px rgba(0, 0, 0, 0.3);
+ transform: scale(1.02);
+ }
+ }
}
}
-.colorButtons,
.difficultyButtons {
- display: flex;
- gap: 1rem;
+ display: grid;
+ grid-template-columns: repeat(6, 1fr);
+ gap: 1.5rem;
+ justify-items: center;
button {
- flex: 1;
- padding: 1rem;
- border: 2px solid #e0e0e0;
+ grid-column: span 2;
+ width: 100%;
+ padding: 1.1rem 1rem;
+ border: 3px solid #e5e7eb;
background: white;
border-radius: 10px;
cursor: pointer;
- font-size: 1rem;
- font-weight: 600;
- color: #555;
+ font-size: 0.95rem;
+ font-weight: 700;
+ color: #6b7280;
transition: all 0.3s ease;
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
+ position: relative;
&:hover {
border-color: #7fcc26;
transform: translateY(-2px);
- box-shadow: 0 4px 8px rgba(127, 204, 38, 0.2);
- color: #333;
+ box-shadow: 0 4px 12px rgba(127, 204, 38, 0.25);
+ color: #374151;
}
&.active {
- background: linear-gradient(135deg, #7fcc26 0%, #6fb31e 100%);
+ background: linear-gradient(135deg, #3a7cca 0%, #2563eb 100%);
color: white;
- border-color: #7fcc26;
- box-shadow: 0 4px 12px rgba(127, 204, 38, 0.3);
+ border-color: #2563eb;
+ box-shadow: 0 4px 16px rgba(58, 124, 202, 0.4);
+ transform: scale(1.05);
}
- }
-}
-.difficultyButtons {
- flex-wrap: wrap;
+ &:nth-child(4) {
+ grid-column: 2 / 4;
+ margin-left: 0;
+ }
- button {
- min-width: 100px;
+ &:nth-child(5) {
+ grid-column: 4 / 6;
+ margin-right: 0;
+ }
}
}
.startButton {
width: 100%;
- padding: 1.2rem;
+ padding: 1.4rem;
background: linear-gradient(135deg, #7fcc26 0%, #6fb31e 100%);
color: white;
border: none;
- border-radius: 10px;
- font-size: 1.2rem;
+ border-radius: 12px;
+ font-size: 1.3rem;
font-weight: 700;
cursor: pointer;
transition: all 0.3s ease;
- box-shadow: 0 4px 12px rgba(127, 204, 38, 0.3);
- margin-top: 1rem;
+ box-shadow: 0 6px 20px rgba(127, 204, 38, 0.35);
+ margin-top: 1.5rem;
+ text-transform: uppercase;
+ letter-spacing: 1px;
+ position: relative;
+ overflow: hidden;
+
+ &::before {
+ content: '';
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ width: 0;
+ height: 0;
+ border-radius: 50%;
+ background: rgba(255, 255, 255, 0.2);
+ transform: translate(-50%, -50%);
+ transition: width 0.6s, height 0.6s;
+ }
&:hover:not(:disabled) {
background: linear-gradient(135deg, #6fb31e 0%, #5fa015 100%);
- transform: translateY(-2px);
- box-shadow: 0 6px 16px rgba(127, 204, 38, 0.4);
+ transform: translateY(-3px);
+ box-shadow: 0 8px 24px rgba(127, 204, 38, 0.45);
+
+ &::before {
+ width: 300px;
+ height: 300px;
+ }
+ }
+
+ &:active:not(:disabled) {
+ transform: translateY(-1px);
}
&:disabled {
- background: #ccc;
+ background: linear-gradient(135deg, #d1d5db 0%, #9ca3af 100%);
cursor: not-allowed;
box-shadow: none;
+ transform: none;
}
}
.controls {
- display: flex;
gap: 1rem;
- margin-bottom: 2rem;
- flex-wrap: wrap;
+ margin-bottom: 1.5rem;
justify-content: center;
button {
- padding: 0.8rem 1.5rem;
+ flex: 1;
+ width: 120px;
+ padding: 0.8rem 1.2rem;
background: white;
border: 2px solid #e0e0e0;
border-radius: 10px;
@@ -163,6 +250,7 @@
color: #555;
transition: all 0.3s ease;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+ white-space: nowrap;
&:hover:not(:disabled) {
border-color: #7fcc26;
@@ -180,6 +268,52 @@
}
}
+.statusBarFixed {
+ width: 100%;
+ max-width: 600px;
+ height: 60px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ margin-bottom: 1rem;
+
+ .statusMessage {
+ padding: 0.75rem 1.5rem;
+ border-radius: 10px;
+ font-weight: 600;
+ font-size: 1.1rem;
+ transition: all 0.3s ease;
+
+ &.check {
+ color: #ff6b6b;
+ background: rgba(255, 107, 107, 0.15);
+ border: 2px solid #ff6b6b;
+ animation: shake 0.5s ease;
+ }
+
+ &.empty {
+ opacity: 0;
+ }
+ }
+}
+
+@keyframes pulse {
+ 0%, 100% {
+ transform: scale(1);
+ opacity: 1;
+ }
+ 50% {
+ transform: scale(1.05);
+ opacity: 0.9;
+ }
+}
+
+@keyframes shake {
+ 0%, 100% { transform: translateX(0); }
+ 25% { transform: translateX(-5px); }
+ 75% { transform: translateX(5px); }
+}
+
.chessboardContainer {
margin-bottom: 2rem;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
@@ -197,7 +331,7 @@
h3 {
margin-bottom: 1rem;
- color: #1e3c72;
+ color: #3a7cca;
font-size: 1.3rem;
font-weight: 700;
border-bottom: 2px solid #e0e0e0;
@@ -249,7 +383,167 @@
}
}
-// Responsive adjustments
+.movePair {
+ display: grid;
+ grid-template-columns: 40px 1fr 1fr;
+ gap: 0.5rem;
+ padding: 0.6rem 0.8rem;
+ background: linear-gradient(135deg, #f9fafb 0%, #f3f4f6 100%);
+ border-radius: 8px;
+ border: 1px solid #e5e7eb;
+ align-items: center;
+ transition: all 0.2s ease;
+
+ &:hover {
+ background: linear-gradient(135deg, #e8f5e9 0%, #d1f0d3 100%);
+ border-color: #7fcc26;
+ transform: translateX(2px);
+ box-shadow: 0 2px 8px rgba(127, 204, 38, 0.15);
+ }
+}
+
+.moveNumber {
+ font-weight: 700;
+ color: #3a7cca;
+ font-size: 0.95rem;
+ text-align: right;
+}
+
+.whiteMove,
+.blackMove {
+ padding: 0.4rem 0.8rem;
+ border-radius: 6px;
+ font-size: 0.9rem;
+ font-weight: 600;
+ font-family: 'Courier New', monospace;
+ transition: all 0.2s ease;
+}
+
+.whiteMove {
+ background: white;
+ color: #1f2937;
+ border: 1px solid #d1d5db;
+
+ &:hover {
+ background: #f9fafb;
+ border-color: #9ca3af;
+ }
+}
+
+.blackMove {
+ background: #1f2937;
+ color: white;
+ border: 1px solid #374151;
+
+ &:hover {
+ background: #374151;
+ border-color: #4b5563;
+ }
+}
+
+.modalOverlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background-color: rgba(0, 0, 0, 0.75);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: 1000;
+ animation: fadeIn 0.2s ease-out;
+}
+
+.modal {
+ background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%);
+ padding: 50px 40px;
+ border-radius: 20px;
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.4);
+ text-align: center;
+ max-width: 450px;
+ width: 90%;
+ border: 3px solid #7fcc26;
+ animation: slideUp 0.3s ease-out;
+
+ h2 {
+ font-size: 32px;
+ margin-bottom: 35px;
+ color: #2c3e50;
+ font-weight: 700;
+ text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.1);
+ }
+}
+
+.modalButtons {
+ display: flex;
+ gap: 15px;
+
+ button {
+ flex: 1;
+ padding: 14px 20px;
+ font-size: 16px;
+ font-weight: 600;
+ border: none;
+ border-radius: 10px;
+ cursor: pointer;
+ transition: all 0.2s ease;
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+
+ &:hover {
+ transform: translateY(-3px);
+ box-shadow: 0 6px 20px rgba(0, 0, 0, 0.2);
+ }
+
+ &:active {
+ transform: translateY(-1px);
+ }
+ }
+
+ .primaryButton {
+ background: linear-gradient(135deg, #7fcc26 0%, #6fb31e 100%);
+ color: white;
+ box-shadow: 0 4px 15px rgba(127, 204, 38, 0.3);
+
+ &:hover {
+ background: linear-gradient(135deg, #6fb31e 0%, #5fa015 100%);
+ box-shadow: 0 6px 20px rgba(127, 204, 38, 0.4);
+ }
+ }
+
+ .secondaryButton {
+ background: linear-gradient(135deg, #e2e8f0 0%, #cbd5e1 100%);
+ color: #475569;
+ box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
+
+ &:hover {
+ background: linear-gradient(135deg, #cbd5e1 0%, #94a3b8 100%);
+ color: #1e293b;
+ }
+ }
+}
+
+@keyframes fadeIn {
+ from {
+ opacity: 0;
+ }
+ to {
+ opacity: 1;
+ }
+}
+
+@keyframes slideUp {
+ from {
+ transform: translateY(50px);
+ opacity: 0;
+ }
+ to {
+ transform: translateY(0);
+ opacity: 1;
+ }
+}
+
@media (max-width: 768px) {
.playComputerContainer {
padding: 1rem;
@@ -273,11 +567,14 @@
}
.controls {
- width: 100%;
+ flex-wrap: wrap;
button {
- flex: 1;
min-width: 120px;
}
}
+
+ .modalButtons {
+ flex-direction: column;
+ }
}
diff --git a/react-ystemandchess/src/features/engine/PlayComputer.tsx b/react-ystemandchess/src/features/engine/PlayComputer.tsx
index 9946950d..cc2278e5 100644
--- a/react-ystemandchess/src/features/engine/PlayComputer.tsx
+++ b/react-ystemandchess/src/features/engine/PlayComputer.tsx
@@ -15,6 +15,7 @@ const PlayComputer: React.FC = () => {
const playerColorRef = useRef<'white' | 'black'>('white');
const sessionStartedRef = useRef
(false);
const difficultyRef = useRef(10);
+ const movesContainerRef = useRef(null);
const [fen, setFen] = useState("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1");
const [playerColor, setPlayerColor] = useState<'white' | 'black'>('white');
@@ -26,6 +27,8 @@ const PlayComputer: React.FC = () => {
const [gameStatus, setGameStatus] = useState("");
const [highlightSquares, setHighlightSquares] = useState([]);
const [showSettings, setShowSettings] = useState(true);
+ const [showGameEndModal, setShowGameEndModal] = useState(false);
+ const [gameEndMessage, setGameEndMessage] = useState('');
// Update refs whenever state changes
useEffect(() => {
@@ -40,7 +43,12 @@ const PlayComputer: React.FC = () => {
difficultyRef.current = difficulty;
}, [difficulty]);
- // Request computer move from Stockfish
+ useEffect(() => {
+ if (movesContainerRef.current) {
+ movesContainerRef.current.scrollTop = movesContainerRef.current.scrollHeight;
+ }
+ }, [moveHistory]);
+
const requestComputerMove = useCallback((currentFen: string) => {
if (!socketRef.current || !sessionStartedRef.current) return;
@@ -98,7 +106,7 @@ const PlayComputer: React.FC = () => {
const updatedFen = gameRef.current.fen();
setFen(updatedFen);
setHighlightSquares([moveResult.from, moveResult.to]);
- setMoveHistory(prev => [...prev, `${moveResult.from}-${moveResult.to}`]);
+ setMoveHistory(prev => [...prev, `${moveResult.from} -> ${moveResult.to}`]);
// Update chessboard
if (chessBoardRef.current) {
@@ -161,7 +169,7 @@ const PlayComputer: React.FC = () => {
const newFen = gameRef.current.fen();
setFen(newFen);
setHighlightSquares([move.from, move.to]);
- setMoveHistory(prev => [...prev, `${move.from}-${move.to}`]);
+ setMoveHistory(prev => [...prev, `${move.from} -> ${move.to}`]);
// Check if game ended
if (checkGameStatus()) {
@@ -179,39 +187,44 @@ const PlayComputer: React.FC = () => {
}
}, [requestComputerMove]);
- // Check game status (checkmate, stalemate, draw)
const checkGameStatus = useCallback((): boolean => {
const game = gameRef.current;
if (game.isCheckmate()) {
const winner = game.turn() === 'w' ? 'Black' : 'White';
- setGameStatus(`Checkmate! ${winner} wins!`);
+ setGameEndMessage(`Checkmate! ${winner} wins!`);
+ setShowGameEndModal(true);
return true;
}
if (game.isDraw()) {
- setGameStatus('Draw!');
+ setGameEndMessage('Game over: Draw!');
+ setShowGameEndModal(true);
return true;
}
if (game.isStalemate()) {
- setGameStatus('Stalemate! Draw!');
- return true;
- }
+ setGameEndMessage('Stalemate! Draw!');
+ setShowGameEndModal(true);
+ return true;
+ }
if (game.isThreefoldRepetition()) {
- setGameStatus('Draw by threefold repetition!');
+ setGameEndMessage('Draw by threefold repetition!');
+ setShowGameEndModal(true);
return true;
}
if (game.isInsufficientMaterial()) {
- setGameStatus('Draw by insufficient material!');
+ setGameEndMessage('Draw by insufficient material!');
+ setShowGameEndModal(true);
return true;
}
if (game.isCheck()) {
- setGameStatus('Check!');
- setTimeout(() => setGameStatus(''), 2000);
+ const sideInCheck = game.turn() === 'w' ? 'White' : 'Black';
+ setGameStatus(`${sideInCheck} is in Check!`);
+ setTimeout(() => setGameStatus(''), 5000);
} else {
setGameStatus('');
}
@@ -219,7 +232,6 @@ const PlayComputer: React.FC = () => {
return false;
}, []);
- // Reset game
const resetGame = useCallback(() => {
gameRef.current.reset();
const startFen = gameRef.current.fen();
@@ -245,7 +257,6 @@ const PlayComputer: React.FC = () => {
}
}, [requestComputerMove]);
- // Handle new game with settings
const newGame = useCallback(() => {
if (socketRef.current && sessionStartedRef.current) {
socketRef.current.emit('end-session');
@@ -285,12 +296,6 @@ const PlayComputer: React.FC = () => {
Play vs Computer
- {!showSettings && (
-
- {isThinking && Computer is thinking...}
- {gameStatus && {gameStatus}}
-
- )}
{showSettings ? (
@@ -298,25 +303,25 @@ const PlayComputer: React.FC = () => {
Game Settings
-
+
-
+