Merge pull request #8 from JoshHeaps/tech-debt/chess-cleanup
Tech debt/chess cleanup
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(dotnet test:*)"
|
||||
"Bash(dotnet test:*)",
|
||||
"Bash(dotnet build)"
|
||||
],
|
||||
"deny": [],
|
||||
"ask": []
|
||||
|
||||
@@ -59,7 +59,14 @@
|
||||
|
||||
@section Scripts {
|
||||
<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 {
|
||||
|
||||
@@ -1,89 +1,269 @@
|
||||
@page
|
||||
@model JoshHeaps.Net.Pages.IndexModel
|
||||
@{
|
||||
ViewData["Title"] = "JoshHeaps.Net";
|
||||
ViewData["Title"] = "Happy 5th Anniversary, Morgan!";
|
||||
Layout = "_Layout";
|
||||
}
|
||||
|
||||
<header id="Header">
|
||||
<a class="headerOption" onclick="showClickedContents('projects')">./ Projects</a>
|
||||
<a class="headerOption" onclick="showClickedContents('demos')">./ Demos</a>
|
||||
<a class="headerOption" onclick="showClickedContents('contact')">./ Contact me</a>
|
||||
</header>
|
||||
<style>
|
||||
body {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
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="ProjectsBox">
|
||||
<div id="ChessProject" class="projectContainer">
|
||||
<div class="displayBox diagonal-section-left">
|
||||
<a target="_blank" href="https://github.com/JoshHeaps/OnlineChess">
|
||||
<img id="ChessImage" class="projectImage projectImageLeft" src="/images/Chess.jpg" title="Chessboard"/>
|
||||
</a>
|
||||
<div class="hearts">💖 💕 💗</div>
|
||||
|
||||
<div class="photo-gallery">
|
||||
<div class="photo-frame">
|
||||
<div class="photo-wrapper">
|
||||
<img src="/images/Wife Images/45053.jpeg" alt="Morgan with our beautiful baby">
|
||||
</div>
|
||||
<div id="ChessText" class="projectTextRight">
|
||||
<h2 id="ChessTitle" class="projectHeaderRight projectHeader">Chess</h2>
|
||||
<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-caption">The most amazing mother to our precious child</div>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="CompilerProject" class="projectContainer">
|
||||
<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 class="hearts">💝 💞 💓</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 {
|
||||
<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;
|
||||
}
|
||||
|
||||
#WelcomeMessage {
|
||||
/*#WelcomeMessage {
|
||||
font: 3vw arial;
|
||||
width: auto;
|
||||
height: 40vw;
|
||||
@@ -31,7 +31,7 @@
|
||||
text-align: left;
|
||||
padding-left: 10vw;
|
||||
padding-right: 10vw;
|
||||
}
|
||||
}*/
|
||||
|
||||
#DemosBox {
|
||||
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.offsetHeight; // Force reflow
|
||||
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