Merge pull request #8 from JoshHeaps/tech-debt/chess-cleanup
Tech debt/chess cleanup
This commit is contained in:
@@ -1,7 +1,8 @@
|
|||||||
{
|
{
|
||||||
"permissions": {
|
"permissions": {
|
||||||
"allow": [
|
"allow": [
|
||||||
"Bash(dotnet test:*)"
|
"Bash(dotnet test:*)",
|
||||||
|
"Bash(dotnet build)"
|
||||||
],
|
],
|
||||||
"deny": [],
|
"deny": [],
|
||||||
"ask": []
|
"ask": []
|
||||||
|
|||||||
@@ -59,7 +59,14 @@
|
|||||||
|
|
||||||
@section Scripts {
|
@section Scripts {
|
||||||
<script src="~/js/signalr/signalr.min.js"></script>
|
<script src="~/js/signalr/signalr.min.js"></script>
|
||||||
<script src="~/js/ChessScripts/chessLogic.js"></script>
|
<script src="~/js/ChessScripts/GameState.js"></script>
|
||||||
|
<script src="~/js/ChessScripts/ChessUtils.js"></script>
|
||||||
|
<script src="~/js/ChessScripts/ChessAPI.js"></script>
|
||||||
|
<script src="~/js/ChessScripts/ChessSignalR.js"></script>
|
||||||
|
<script src="~/js/ChessScripts/ChessInteractions.js"></script>
|
||||||
|
<script src="~/js/ChessScripts/ChessBoard.js"></script>
|
||||||
|
<script src="~/js/ChessScripts/ChessModals.js"></script>
|
||||||
|
<script src="~/js/ChessScripts/chessMain.js"></script>
|
||||||
}
|
}
|
||||||
|
|
||||||
@section Styles {
|
@section Styles {
|
||||||
|
|||||||
@@ -1,89 +1,269 @@
|
|||||||
@page
|
@page
|
||||||
@model JoshHeaps.Net.Pages.IndexModel
|
@model JoshHeaps.Net.Pages.IndexModel
|
||||||
@{
|
@{
|
||||||
ViewData["Title"] = "JoshHeaps.Net";
|
ViewData["Title"] = "Happy 5th Anniversary, Morgan!";
|
||||||
Layout = "_Layout";
|
Layout = "_Layout";
|
||||||
}
|
}
|
||||||
|
|
||||||
<header id="Header">
|
<style>
|
||||||
<a class="headerOption" onclick="showClickedContents('projects')">./ Projects</a>
|
body {
|
||||||
<a class="headerOption" onclick="showClickedContents('demos')">./ Demos</a>
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
<a class="headerOption" onclick="showClickedContents('contact')">./ Contact me</a>
|
min-height: 100vh;
|
||||||
</header>
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.anniversary-container {
|
||||||
|
max-width: 1400px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 3rem 2rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.anniversary-header {
|
||||||
|
color: white;
|
||||||
|
font-size: 3.5rem;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
|
||||||
|
animation: fadeInDown 1s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
#WelcomeMessage {
|
||||||
|
background: rgba(255, 255, 255, 0.95);
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 2rem 2.5rem;
|
||||||
|
margin: 0 auto 2rem;
|
||||||
|
max-width: 900px;
|
||||||
|
font-size: 1.3rem;
|
||||||
|
line-height: 1.8;
|
||||||
|
color: #333;
|
||||||
|
box-shadow: 0 10px 30px rgba(0,0,0,0.2);
|
||||||
|
animation: fadeIn 2s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hearts {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
margin: 1.5rem 0;
|
||||||
|
animation: heartbeat 1.5s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.photo-gallery {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
|
||||||
|
gap: 2rem;
|
||||||
|
margin: 10% auto;
|
||||||
|
max-width: 1200px;
|
||||||
|
padding: 0 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.photo-frame {
|
||||||
|
background: white;
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 15px;
|
||||||
|
box-shadow: 0 15px 40px rgba(0,0,0,0.3);
|
||||||
|
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
||||||
|
animation: fadeInUp 1.5s ease-out;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.photo-frame:hover {
|
||||||
|
transform: translateY(-10px);
|
||||||
|
box-shadow: 0 20px 50px rgba(0,0,0,0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.photo-wrapper {
|
||||||
|
width: 100%;
|
||||||
|
height: 500px;
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: 10px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.photo-frame img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
object-position: center;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.photo-frame:nth-child(2) img {
|
||||||
|
object-position: center 30%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.photo-caption {
|
||||||
|
margin-top: 1rem;
|
||||||
|
font-style: italic;
|
||||||
|
color: #666;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.love-message {
|
||||||
|
background: rgba(255, 255, 255, 0.95);
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 2.5rem 3rem;
|
||||||
|
margin: 2rem auto;
|
||||||
|
max-width: 1000px;
|
||||||
|
color: #444;
|
||||||
|
box-shadow: 0 10px 30px rgba(0,0,0,0.2);
|
||||||
|
animation: fadeIn 2.5s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.love-message h2 {
|
||||||
|
color: #764ba2;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
font-size: 2.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.love-message p {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
line-height: 1.9;
|
||||||
|
margin-bottom: 1.2rem;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.love-message p:last-of-type {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
@@keyframes fadeIn {
|
||||||
|
from { opacity: 0; }
|
||||||
|
to { opacity: 1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@@keyframes fadeInDown {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-50px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@@keyframes fadeInUp {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(50px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@@keyframes heartbeat {
|
||||||
|
0%, 100% { transform: scale(1); }
|
||||||
|
50% { transform: scale(1.1); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@@media (max-width: 900px) {
|
||||||
|
.photo-gallery {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
max-width: 600px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.photo-wrapper {
|
||||||
|
height: 450px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@@media (max-width: 768px) {
|
||||||
|
.anniversary-container {
|
||||||
|
padding: 2rem 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.anniversary-header {
|
||||||
|
font-size: 2.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#WelcomeMessage {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
padding: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.photo-wrapper {
|
||||||
|
height: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.love-message {
|
||||||
|
padding: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.love-message h2 {
|
||||||
|
font-size: 1.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.love-message p {
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hearts {
|
||||||
|
font-size: 2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="anniversary-container">
|
||||||
|
<h1 class="anniversary-header">Happy 5th Anniversary, Morgan!</h1>
|
||||||
|
|
||||||
<div id="WelcomeMessage"></div>
|
<div id="WelcomeMessage"></div>
|
||||||
|
|
||||||
<div id="ProjectsBox">
|
<div class="hearts">💖 💕 💗</div>
|
||||||
<div id="ChessProject" class="projectContainer">
|
|
||||||
<div class="displayBox diagonal-section-left">
|
<div class="photo-gallery">
|
||||||
<a target="_blank" href="https://github.com/JoshHeaps/OnlineChess">
|
<div class="photo-frame">
|
||||||
<img id="ChessImage" class="projectImage projectImageLeft" src="/images/Chess.jpg" title="Chessboard"/>
|
<div class="photo-wrapper">
|
||||||
</a>
|
<img src="/images/Wife Images/45053.jpeg" alt="Morgan with our beautiful baby">
|
||||||
</div>
|
</div>
|
||||||
<div id="ChessText" class="projectTextRight">
|
<div class="photo-caption">The most amazing mother to our precious child</div>
|
||||||
<h2 id="ChessTitle" class="projectHeaderRight projectHeader">Chess</h2>
|
</div>
|
||||||
<p id="ChessDescription" class="projectDescriptionRight projectDescription">
|
|
||||||
As someone who loves chess, I wanted to create a chess game that I could play with my friends and family. This project is a work in progress, as currently it can only be played locally. It was built in C# on .NET 8.0, using winforms, because I like a challenge. I plan to add online multiplayer functionality in the future.
|
<div class="photo-frame">
|
||||||
|
<div class="photo-wrapper">
|
||||||
|
<img src="/images/Wife Images/45054.jpeg" alt="Josh and Morgan together">
|
||||||
|
</div>
|
||||||
|
<div class="photo-caption">My best friend and eternal companion</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="love-message">
|
||||||
|
<h2>Five Years of Forever</h2>
|
||||||
|
<p>
|
||||||
|
Five years ago today, on October 2nd, 2020, I knelt across the altar from you in the temple
|
||||||
|
and we were sealed for time and all eternity. It was the best decision I ever made.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Morgan, you are everything I could have ever hoped for and so much more. You're not just
|
||||||
|
my wife—you're my best friend, my partner in every adventure, and the love of my life.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
You make me laugh every single day. Your kindness touches everyone around you. Your
|
||||||
|
strength amazes me. Your beauty, inside and out, takes my breath away. Watching you
|
||||||
|
become a mother has shown me depths of love and dedication I never knew existed.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Thank you for five incredible years of love, laughter, growth, and joy. Thank you for
|
||||||
|
choosing me, for believing in me, and for building this beautiful life with me.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Here's to five years down and an eternity to go. I love you more today than yesterday,
|
||||||
|
and I'll love you even more tomorrow.
|
||||||
|
</p>
|
||||||
|
<p style="font-size: 1.5rem; margin-top: 2rem; color: #764ba2;">
|
||||||
|
<strong>I love you, Morgan. Forever and always. 💖</strong>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="CompilerProject" class="projectContainer">
|
<div class="hearts">💝 💞 💓</div>
|
||||||
<div id="CompilerText" class="projectTextLeft">
|
|
||||||
<h2 id="CompilerTitle" class="projectHeaderLeft projectHeader">Compiler</h2>
|
|
||||||
<p id="CompilerDescription" class="projectDescriptionLeft projectDescription">
|
|
||||||
I love solving problems, and figuring out how things work. What better way to do both than writing a compiler myself? This project is a compiler that I wrote in C# on .NET 8.0. It takes in a simple language that I created, outputs the corresponding common intermediate language (CIL), and executes it. I plan to add more features in the future.
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="displayBox diagonal-section-right">
|
|
||||||
<a target="_blank" href="https://github.com/JoshHeaps/CILCompiler">
|
|
||||||
<img id="CompilerImage" class="projectImage projectImageRight" src="/images/CompilerDemo.png" title="Compiler Input and Output"/>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="DemosBox" class="displayBox">
|
|
||||||
<h2 id="DemoHeader">Demos</h2>
|
|
||||||
<div id="buttonWrapper">
|
|
||||||
<button id="ChessDemo" class="demoButton" onclick="window.location.href='/chess'">
|
|
||||||
Play Chess
|
|
||||||
</button>
|
|
||||||
<button id="ChessDemo" class="demoButton" onclick="window.location.href='/particles'">
|
|
||||||
Particle Simulator
|
|
||||||
</button>
|
|
||||||
<button id="MoreFiller" class="demoButton">
|
|
||||||
More coming soon...
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<footer id="Contacts">
|
|
||||||
<svg viewBox="0 0 16 16" class="social-icon">
|
|
||||||
<a id="GithubLink" class="social-link" href="https://github.com/JoshHeaps" target="_blank">
|
|
||||||
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"></path>
|
|
||||||
<rect width="100%" height="100%" fill="transparent" />
|
|
||||||
</a>
|
|
||||||
</svg>
|
|
||||||
|
|
||||||
<svg viewBox="0 0 24 24" class="social-icon">
|
|
||||||
<a id="LinkedinLink" class="social-link" href="https://www.linkedin.com/in/josh-heaps/" target="_blank">
|
|
||||||
<path d="M20.5 2h-17A1.5 1.5 0 002 3.5v17A1.5 1.5 0 003.5 22h17a1.5 1.5 0 001.5-1.5v-17A1.5 1.5 0 0020.5 2zM8 19H5v-9h3zM6.5 8.25A1.75 1.75 0 118.3 6.5a1.78 1.78 0 01-1.8 1.75zM19 19h-3v-4.74c0-1.42-.6-1.93-1.38-1.93A1.74 1.74 0 0013 14.19a.66.66 0 000 .14V19h-3v-9h2.9v1.3a3.11 3.11 0 012.7-1.4c1.55 0 3.36.86 3.36 3.66z"></path>
|
|
||||||
<rect width="100%" height="100%" fill="transparent" />
|
|
||||||
</a>
|
|
||||||
</svg>
|
|
||||||
|
|
||||||
<svg viewBox="0 -4 31 31" class="social-icon" preserveAspectRatio="none">
|
|
||||||
<a id="GmailLink" class="social-link" href="mailto:[email protected]">
|
|
||||||
<path d="m0 3.23863636v2.72727273l3.12784091 3.02727273 3.69034091 2.08636368.68181818-4.59034095-.68181818-4.27329546-1.90909091-1.43181818c-2.02329546-1.51704546-4.90909091-.07329545-4.90909091 2.45454545"/>
|
|
||||||
<path d="m23.1818182 2.21590909-.6818182 4.32954546.6818182 4.53409095 3.3494318-1.65852277 3.46875-3.45511364v-2.72727273c0-2.5278409-2.8857955-3.97159091-4.9090909-2.45454545z"/>
|
|
||||||
<path d="m2.04545455 22.6704545h4.77272727v-11.590909l-6.81818182-5.11363641v14.65909091c0 1.1301136.91534091 2.0454545 2.04545455 2.0454545"/>
|
|
||||||
<path d="m23.1818182 22.6704545h4.7727273c1.1301136 0 2.0454545-.9153409 2.0454545-2.0454545v-14.65909091l-6.8181818 5.11363641z"/>
|
|
||||||
<path d="m15 8.35227273-8.18181818-6.13636364v8.86363641l8.18181818 6.1363636 8.1818182-6.1363636v-8.86363641z"/>
|
|
||||||
<rect width="100%" height="100%" fill="transparent" />
|
|
||||||
</a>
|
|
||||||
</svg>
|
|
||||||
</footer>
|
|
||||||
|
|
||||||
@section Styles {
|
@section Styles {
|
||||||
<link href="/css/home/site.css?v=@ViewData["cssVersion"]" rel="stylesheet" type="text/css" />
|
<link href="/css/home/site.css?v=@ViewData["cssVersion"]" rel="stylesheet" type="text/css" />
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
transition: color 0.5s ease, font 0.5s ease;
|
transition: color 0.5s ease, font 0.5s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
#WelcomeMessage {
|
/*#WelcomeMessage {
|
||||||
font: 3vw arial;
|
font: 3vw arial;
|
||||||
width: auto;
|
width: auto;
|
||||||
height: 40vw;
|
height: 40vw;
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
text-align: left;
|
text-align: left;
|
||||||
padding-left: 10vw;
|
padding-left: 10vw;
|
||||||
padding-right: 10vw;
|
padding-right: 10vw;
|
||||||
}
|
}*/
|
||||||
|
|
||||||
#DemosBox {
|
#DemosBox {
|
||||||
margin: 10vw;
|
margin: 10vw;
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 51 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 98 KiB |
@@ -0,0 +1,108 @@
|
|||||||
|
const ChessAPI = {
|
||||||
|
async joinGame() {
|
||||||
|
const response = await fetch('/api/chess/JoinGame');
|
||||||
|
return await response.json();
|
||||||
|
},
|
||||||
|
|
||||||
|
async createCPUGame(difficulty) {
|
||||||
|
const response = await fetch(`/api/chess/new/${difficulty}`);
|
||||||
|
return await response.json();
|
||||||
|
},
|
||||||
|
|
||||||
|
async getGameState(gameId) {
|
||||||
|
const response = await fetch(`/api/chess/${gameId}`);
|
||||||
|
return await response.json();
|
||||||
|
},
|
||||||
|
|
||||||
|
async getLegalMoves(pieceId) {
|
||||||
|
const response = await fetch(`/api/chess/${GameState.currentGameId}/legalMoves/${pieceId}`);
|
||||||
|
if (!response.ok) throw new Error("API failed");
|
||||||
|
return await response.json();
|
||||||
|
},
|
||||||
|
|
||||||
|
async makeMove(moveDto) {
|
||||||
|
const response = await fetch("/api/chess/move", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify(moveDto)
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (!response.ok || !result.success) {
|
||||||
|
throw new Error(result.message || "Invalid move or not your turn.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
|
||||||
|
async handleMove(targetRow, targetCol) {
|
||||||
|
if (!GameState.selectedPiece) return;
|
||||||
|
|
||||||
|
const isPawn = GameState.selectedPiece.type === 0;
|
||||||
|
const reachedEnd = (Number(GameState.selectedPiece.color) === 0 && Number(targetRow) === 0) ||
|
||||||
|
(Number(GameState.selectedPiece.color) === 1 && Number(targetRow) === 0);
|
||||||
|
|
||||||
|
let choice = null;
|
||||||
|
|
||||||
|
if (isPawn && reachedEnd) {
|
||||||
|
choice = await ChessModals.promptPromotion();
|
||||||
|
if (!choice) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
[targetRow, targetCol] = ChessUtils.flipCoordinates(targetRow, targetCol);
|
||||||
|
|
||||||
|
const moveDto = {
|
||||||
|
GameId: GameState.currentGameId,
|
||||||
|
PlayerId: GameState.currentPlayerId,
|
||||||
|
PieceId: GameState.selectedPiece.id,
|
||||||
|
SourceRow: GameState.selectedPiece.row,
|
||||||
|
SourceCol: GameState.selectedPiece.col,
|
||||||
|
TargetRow: targetRow,
|
||||||
|
TargetCol: targetCol,
|
||||||
|
PromotionChoice: choice
|
||||||
|
};
|
||||||
|
|
||||||
|
GameState.setPreviousMove([GameState.selectedPiece.row, GameState.selectedPiece.col], [targetRow, targetCol]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const moveResult = await this.makeMove(moveDto);
|
||||||
|
|
||||||
|
const updatedGame = await this.getGameState(GameState.currentGameId);
|
||||||
|
ChessBoard.renderPieces(updatedGame.pieces);
|
||||||
|
|
||||||
|
GameState.clearSelection();
|
||||||
|
ChessInteractions.clearHighlights();
|
||||||
|
|
||||||
|
await ChessSignalR.notifyMoveMade(moveDto, moveResult);
|
||||||
|
this.alertGameStatusChange(moveResult);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
alert("❌ " + error.message);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
alertGameStatusChange(moveResult) {
|
||||||
|
setTimeout(async () => {
|
||||||
|
let gameOver = false;
|
||||||
|
|
||||||
|
if (moveResult.isCheck) {
|
||||||
|
console.log("🛑 Check!");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (moveResult.isCheckmate) {
|
||||||
|
alert("♟️ Checkmate!");
|
||||||
|
gameOver = true;
|
||||||
|
} else if (moveResult.isStalemate) {
|
||||||
|
alert("🤝 Stalemate!");
|
||||||
|
gameOver = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gameOver) {
|
||||||
|
await ChessSignalR.leaveGame();
|
||||||
|
}
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log("ChessAPI.js loaded");
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
const ChessBoard = {
|
||||||
|
renderPieces(pieces) {
|
||||||
|
this.clearAllSquares();
|
||||||
|
this.placePieces(pieces);
|
||||||
|
this.setupSquareEventHandlers();
|
||||||
|
this.highlightPreviousMove();
|
||||||
|
},
|
||||||
|
|
||||||
|
clearAllSquares() {
|
||||||
|
for (let i = 0; i < 64; i++) {
|
||||||
|
const square = document.getElementById(`square-${i}`);
|
||||||
|
square.innerHTML = "";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
placePieces(pieces) {
|
||||||
|
pieces.forEach(piece => {
|
||||||
|
const [r, c] = ChessUtils.flipCoordinates(piece.row, piece.col);
|
||||||
|
const index = r * 8 + c;
|
||||||
|
const square = document.getElementById(`square-${index}`);
|
||||||
|
if (!square) return;
|
||||||
|
|
||||||
|
const img = document.createElement("img");
|
||||||
|
img.src = ChessUtils.getPieceImageUrl(piece);
|
||||||
|
img.alt = piece.type;
|
||||||
|
img.classList.add("chessPiece");
|
||||||
|
|
||||||
|
this.setupPieceDragEvents(img, piece);
|
||||||
|
this.setupPieceClickEvent(img, piece);
|
||||||
|
|
||||||
|
square.appendChild(img);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
setupPieceDragEvents(img, piece) {
|
||||||
|
img.draggable = true;
|
||||||
|
img.ondragstart = async (e) => {
|
||||||
|
GameState.setSelectedPiece(piece);
|
||||||
|
try {
|
||||||
|
const moves = await ChessAPI.getLegalMoves(piece.id);
|
||||||
|
GameState.setLegalMoves(moves);
|
||||||
|
ChessInteractions.highlightSelected(piece.row, piece.col);
|
||||||
|
ChessInteractions.highlightLegalMoves(moves);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
e.dataTransfer.setData("text/plain", JSON.stringify({
|
||||||
|
srcRow: piece.Row,
|
||||||
|
srcCol: piece.Col
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
img.ondragend = () => ChessInteractions.clearHighlights();
|
||||||
|
},
|
||||||
|
|
||||||
|
setupPieceClickEvent(img, piece) {
|
||||||
|
img.onclick = (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
ChessInteractions.handlePieceClick(piece);
|
||||||
|
console.log("Clicked on:", piece);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
setupSquareEventHandlers() {
|
||||||
|
for (let i = 0; i < 64; i++) {
|
||||||
|
const square = document.getElementById(`square-${i}`);
|
||||||
|
square.classList.remove("previous-start", "previous-end");
|
||||||
|
|
||||||
|
if (!square.classList.contains("legal")) {
|
||||||
|
square.onclick = () => ChessInteractions.clearHighlights();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
highlightPreviousMove() {
|
||||||
|
if (GameState.previousMoveStart && GameState.previousMoveEnd) {
|
||||||
|
const [startRow, startCol] = ChessUtils.flipCoordinates(...GameState.previousMoveStart);
|
||||||
|
const [endRow, endCol] = ChessUtils.flipCoordinates(...GameState.previousMoveEnd);
|
||||||
|
|
||||||
|
const startIndex = startRow * 8 + startCol;
|
||||||
|
const endIndex = endRow * 8 + endCol;
|
||||||
|
|
||||||
|
document.getElementById(`square-${startIndex}`)?.classList.add("previous-start");
|
||||||
|
document.getElementById(`square-${endIndex}`)?.classList.add("previous-end");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
setupDragAndDrop() {
|
||||||
|
for (let i = 0; i < 64; i++) {
|
||||||
|
const square = document.getElementById(`square-${i}`);
|
||||||
|
|
||||||
|
square.ondragover = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
};
|
||||||
|
|
||||||
|
square.ondrop = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
if (!GameState.selectedPiece) return;
|
||||||
|
|
||||||
|
const index = parseInt(square.id.split('-')[1], 10);
|
||||||
|
const targetRow = Math.floor(index / 8);
|
||||||
|
const targetCol = index % 8;
|
||||||
|
|
||||||
|
ChessAPI.handleMove(targetRow, targetCol);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log("ChessBoard.js loaded");
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
const ChessInteractions = {
|
||||||
|
async handlePieceClick(piece) {
|
||||||
|
this.clearHighlights();
|
||||||
|
GameState.setSelectedPiece(piece);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const moves = await ChessAPI.getLegalMoves(piece.id);
|
||||||
|
GameState.setLegalMoves(moves);
|
||||||
|
this.highlightSelected(piece.row, piece.col);
|
||||||
|
this.highlightLegalMoves(moves);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Error in handlePieceClick:", err);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
highlightSelected(row, col) {
|
||||||
|
const [r, c] = ChessUtils.flipCoordinates(row, col);
|
||||||
|
const index = r * 8 + c;
|
||||||
|
document.getElementById(`square-${index}`).classList.add("selected");
|
||||||
|
},
|
||||||
|
|
||||||
|
highlightLegalMoves(moves) {
|
||||||
|
moves.forEach(move => {
|
||||||
|
const [r, c] = ChessUtils.flipCoordinates(move.row, move.col);
|
||||||
|
const index = r * 8 + c;
|
||||||
|
const square = document.getElementById(`square-${index}`);
|
||||||
|
square.classList.add("legal");
|
||||||
|
|
||||||
|
const img = square.querySelector("img");
|
||||||
|
if (img) img.onclick = null;
|
||||||
|
|
||||||
|
let overlay = document.createElement("div");
|
||||||
|
overlay.className = "legalOverlay";
|
||||||
|
square.appendChild(overlay);
|
||||||
|
|
||||||
|
square.onclick = () => ChessAPI.handleMove(r, c);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
clearHighlights() {
|
||||||
|
for (let i = 0; i < 64; i++) {
|
||||||
|
const square = document.getElementById(`square-${i}`);
|
||||||
|
|
||||||
|
const overlay = square.querySelector(".legalOverlay");
|
||||||
|
if (overlay) square.removeChild(overlay);
|
||||||
|
|
||||||
|
if (square.classList.contains("legal")) {
|
||||||
|
square.classList.remove("legal");
|
||||||
|
square.onclick = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
square.classList.remove("selected");
|
||||||
|
}
|
||||||
|
|
||||||
|
GameState.clearSelection();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log("ChessInteractions.js loaded");
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
const ChessModals = {
|
||||||
|
promptPromotion() {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
let color = 1;
|
||||||
|
if (GameState.currentPlayerIsWhite)
|
||||||
|
color = 0;
|
||||||
|
|
||||||
|
ChessUtils.updatePromotionModalImages(color);
|
||||||
|
|
||||||
|
document.getElementById("promotionModal").style.display = "block";
|
||||||
|
window.selectPromotion = (piece) => {
|
||||||
|
document.getElementById("promotionModal").style.display = "none";
|
||||||
|
resolve(piece);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
promptDifficulty() {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
document.getElementById("difficultyModal").style.display = "block";
|
||||||
|
window.selectDifficulty = (difficulty) => {
|
||||||
|
document.getElementById("difficultyModal").style.display = "none";
|
||||||
|
resolve(difficulty);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log("ChessModals.js loaded");
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
const ChessSignalR = {
|
||||||
|
connection: null,
|
||||||
|
|
||||||
|
async setupConnection() {
|
||||||
|
this.connection = new signalR.HubConnectionBuilder()
|
||||||
|
.withUrl("/chessHub")
|
||||||
|
.configureLogging(signalR.LogLevel.Information)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
this.connection.onclose(err => {
|
||||||
|
console.error("❌ SignalR connection closed:", err?.message);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.connection.on("ReceiveMoveUpdate", async (gameId, moveDto, moveResultDto) => {
|
||||||
|
await this.handleMoveUpdate(gameId, moveDto, moveResultDto);
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.connection.start();
|
||||||
|
console.log("✅ SignalR connected");
|
||||||
|
await this.connection.invoke("JoinWebsocketGroup", GameState.currentGameId);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("❌ SignalR failed to start or join:", err);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async handleMoveUpdate(gameId, moveDto, moveResultDto) {
|
||||||
|
if (gameId !== GameState.currentGameId) return;
|
||||||
|
|
||||||
|
const gameState = await ChessAPI.getGameState(gameId);
|
||||||
|
GameState.setPreviousMove([moveDto.sourceRow, moveDto.sourceCol], [moveDto.targetRow, moveDto.targetCol]);
|
||||||
|
ChessBoard.renderPieces(gameState.pieces);
|
||||||
|
|
||||||
|
ChessAPI.alertGameStatusChange(moveResultDto);
|
||||||
|
},
|
||||||
|
|
||||||
|
async notifyMoveMade(moveDto, moveResult) {
|
||||||
|
if (this.connection) {
|
||||||
|
await this.connection.invoke("MoveMade", GameState.currentGameId, moveDto, moveResult);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async leaveGame() {
|
||||||
|
if (this.connection) {
|
||||||
|
await this.connection.invoke("LeaveWebsocketGroup", GameState.currentGameId);
|
||||||
|
await this.connection.stop();
|
||||||
|
this.connection = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async stopConnection() {
|
||||||
|
if (this.connection) {
|
||||||
|
await this.connection.stop();
|
||||||
|
this.connection = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log("ChessSignalR.js loaded");
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
const ChessUtils = {
|
||||||
|
flipCoordinates(row, col) {
|
||||||
|
if (!GameState.currentPlayerIsWhite) {
|
||||||
|
return [7 - row, 7 - col];
|
||||||
|
}
|
||||||
|
return [row, col];
|
||||||
|
},
|
||||||
|
|
||||||
|
getCookie(name) {
|
||||||
|
const value = document.cookie.split('; ')
|
||||||
|
.find(row => row.startsWith(name + '='));
|
||||||
|
return value ? value.split('=')[1] : null;
|
||||||
|
},
|
||||||
|
|
||||||
|
setCookie(name, value, maxAge = 86400) {
|
||||||
|
document.cookie = `${name}=${value}; path=/; max-age=${maxAge}`;
|
||||||
|
},
|
||||||
|
|
||||||
|
getPieceImageUrl(piece) {
|
||||||
|
const basePath = "/images/Chess Images/";
|
||||||
|
const color = piece.color === 0 ? "White" : "Black";
|
||||||
|
const typeMap = {
|
||||||
|
0: "Pawn",
|
||||||
|
1: "Rook",
|
||||||
|
2: "Knight",
|
||||||
|
3: "Bishop",
|
||||||
|
4: "Queen",
|
||||||
|
5: "King"
|
||||||
|
};
|
||||||
|
|
||||||
|
return basePath + color + typeMap[piece.type] + ".svg";
|
||||||
|
},
|
||||||
|
|
||||||
|
updatePromotionModalImages(color) {
|
||||||
|
const pieceNames = ["Queen", "Rook", "Bishop", "Knight"];
|
||||||
|
const buttons = document.querySelectorAll("#promotionModal button img");
|
||||||
|
|
||||||
|
buttons.forEach((img, index) => {
|
||||||
|
img.src = `/images/Chess Images/${color === 0 ? "White" : "Black"}${pieceNames[index]}.svg`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log("ChessUtils.js loaded");
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
const GameState = {
|
||||||
|
currentGameId: null,
|
||||||
|
currentPlayerId: null,
|
||||||
|
currentPlayerIsWhite: null,
|
||||||
|
selectedPiece: null,
|
||||||
|
legalMoves: [],
|
||||||
|
previousMoveStart: null,
|
||||||
|
previousMoveEnd: null,
|
||||||
|
|
||||||
|
reset() {
|
||||||
|
this.currentGameId = null;
|
||||||
|
this.currentPlayerId = null;
|
||||||
|
this.currentPlayerIsWhite = null;
|
||||||
|
this.selectedPiece = null;
|
||||||
|
this.legalMoves = [];
|
||||||
|
this.previousMoveStart = null;
|
||||||
|
this.previousMoveEnd = null;
|
||||||
|
},
|
||||||
|
|
||||||
|
setGameInfo(gameId, playerId, isWhite) {
|
||||||
|
this.currentGameId = gameId;
|
||||||
|
this.currentPlayerId = playerId;
|
||||||
|
this.currentPlayerIsWhite = isWhite;
|
||||||
|
},
|
||||||
|
|
||||||
|
setSelectedPiece(piece) {
|
||||||
|
this.selectedPiece = piece;
|
||||||
|
},
|
||||||
|
|
||||||
|
clearSelection() {
|
||||||
|
this.selectedPiece = null;
|
||||||
|
this.legalMoves = [];
|
||||||
|
},
|
||||||
|
|
||||||
|
setLegalMoves(moves) {
|
||||||
|
this.legalMoves = moves;
|
||||||
|
},
|
||||||
|
|
||||||
|
setPreviousMove(startPos, endPos) {
|
||||||
|
this.previousMoveStart = startPos;
|
||||||
|
this.previousMoveEnd = endPos;
|
||||||
|
},
|
||||||
|
|
||||||
|
clearPreviousMove() {
|
||||||
|
this.previousMoveStart = null;
|
||||||
|
this.previousMoveEnd = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log("GameState.js loaded");
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
async function startNewGame() {
|
||||||
|
await ChessSignalR.stopConnection();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const gameData = await ChessAPI.joinGame();
|
||||||
|
|
||||||
|
GameState.setGameInfo(gameData.gameId, gameData.id, gameData.isWhite);
|
||||||
|
GameState.clearPreviousMove();
|
||||||
|
|
||||||
|
ChessUtils.setCookie('chessGameId', gameData.gameId);
|
||||||
|
ChessUtils.setCookie('chessPlayerId', gameData.id);
|
||||||
|
ChessUtils.setCookie('chessPlayerIsWhite', gameData.isWhite);
|
||||||
|
|
||||||
|
console.log("🆕 Game started:", gameData.gameId);
|
||||||
|
|
||||||
|
await ChessSignalR.setupConnection();
|
||||||
|
|
||||||
|
const gameState = await ChessAPI.getGameState(gameData.gameId);
|
||||||
|
ChessBoard.renderPieces(gameState.pieces);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to start new game:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function startCPUGame() {
|
||||||
|
await ChessSignalR.stopConnection();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const difficulty = await ChessModals.promptDifficulty();
|
||||||
|
const gameData = await ChessAPI.createCPUGame(difficulty);
|
||||||
|
|
||||||
|
GameState.setGameInfo(gameData.gameId, gameData.id, gameData.isWhite);
|
||||||
|
GameState.clearPreviousMove();
|
||||||
|
|
||||||
|
ChessUtils.setCookie('chessGameId', gameData.gameId);
|
||||||
|
ChessUtils.setCookie('chessPlayerId', gameData.id);
|
||||||
|
ChessUtils.setCookie('chessPlayerIsWhite', gameData.isWhite);
|
||||||
|
|
||||||
|
console.log("🆕 CPU Game started:", gameData.gameId);
|
||||||
|
|
||||||
|
await ChessSignalR.setupConnection();
|
||||||
|
|
||||||
|
const gameState = await ChessAPI.getGameState(gameData.gameId);
|
||||||
|
ChessBoard.renderPieces(gameState.pieces);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to start CPU game:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function resumeSavedGame() {
|
||||||
|
const savedGameId = ChessUtils.getCookie("chessGameId");
|
||||||
|
const savedPlayerId = ChessUtils.getCookie("chessPlayerId");
|
||||||
|
const savedPlayerIsWhite = ChessUtils.getCookie("chessPlayerIsWhite");
|
||||||
|
|
||||||
|
if (savedGameId && savedPlayerId) {
|
||||||
|
try {
|
||||||
|
const gameState = await ChessAPI.getGameState(savedGameId);
|
||||||
|
|
||||||
|
console.log("🧠 Rejoining saved game...");
|
||||||
|
GameState.setGameInfo(savedGameId, savedPlayerId, savedPlayerIsWhite === "true");
|
||||||
|
|
||||||
|
await ChessSignalR.setupConnection();
|
||||||
|
ChessBoard.renderPieces(gameState.pieces);
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
console.warn("Saved game not found or expired.", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.startNewGame = startNewGame;
|
||||||
|
window.startCPUGame = startCPUGame;
|
||||||
|
|
||||||
|
window.addEventListener('load', resumeSavedGame);
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
ChessBoard.setupDragAndDrop();
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("chessMain.js loaded");
|
||||||
@@ -3,5 +3,5 @@
|
|||||||
element.style.display = "none"; // Trigger reflow
|
element.style.display = "none"; // Trigger reflow
|
||||||
element.offsetHeight; // Force reflow
|
element.offsetHeight; // Force reflow
|
||||||
element.style.display = ""; // Restore the original display
|
element.style.display = ""; // Restore the original display
|
||||||
typeText("#WelcomeMessage", "Hey! You found me! Since I have your attention, why don't I tell you about myself?");
|
typeText("#WelcomeMessage", "Today, October 2nd, marks the 5th anniversary of the day I was sealed for time and all eternity to my wonderful wife. She's the kindest, sweetest, funniest, most beautiful woman I've ever met. I love you Morgan 💖");
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user