Skip to content

Commit

Permalink
feat: show pieces to unlock next in castle menu
Browse files Browse the repository at this point in the history
  • Loading branch information
kacperwyczawski committed Aug 25, 2024
1 parent aab2772 commit 4ba4682
Show file tree
Hide file tree
Showing 6 changed files with 98 additions and 37 deletions.
16 changes: 14 additions & 2 deletions play.css
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,7 @@ a.button {
margin-top: .6rem;
}

& #castle-menu-pieces {
& #pieces {
display: grid;
grid-template-columns: repeat(6, 3rem);
gap: .6rem;
Expand All @@ -322,6 +322,18 @@ a.button {
align-items: center;
}
}

& #locked-pieces {
display: flex;
flex-direction: column;
gap: .6rem;

& li {
display: flex;
align-items: center;
gap: .6rem;
}
}
}

#game-over {
Expand All @@ -331,4 +343,4 @@ a.button {
& p {
margin-top: 0;
}
}
}
9 changes: 7 additions & 2 deletions play.html
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,14 @@
<button>close</button>
</form>
<p>
Pieces:
Pieces to buy:
</p>
<ul id="castle-menu-pieces">
<ul id="pieces">
</ul>
<p>
Pieces to unlock next:
</p>
<ul id="locked-pieces">
</ul>
</dialog>
<dialog id="game-over">
Expand Down
26 changes: 20 additions & 6 deletions src/game/game.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export default class Game {
#currentPlayerIndex = 0;
#selectedPoint: Point | null = null;

afterEndTurn: (winner: PlayerColor | null) => void = () => {};
afterEndTurn: (winner: PlayerColor | null) => void = () => { };

get currentPlayer() {
return this.#players[this.#currentPlayerIndex];
Expand All @@ -30,6 +30,23 @@ export default class Game {
return this.#selectedPoint !== null;
}

get nextAvailablePieces() {
let result: Piece[] = []
for (const locked of this.currentPlayer.lockedPieces) {
// TODO: use new set methods, when available
let canBeUnlocked = true;
for (const requirement of locked.requirements) {
if (!this.currentPlayer.unlockedPieces.map(p => p.name).includes(requirement.name)) {
canBeUnlocked = false
}
}
if (canBeUnlocked) {
result.push(locked)
}
}
return result
}

constructor(mapName: string) {
this.#board = new Board(mapName, this.#players);
}
Expand All @@ -43,16 +60,13 @@ export default class Game {
}

getPiecesToBuy(): { piece: Piece; isAvailable: boolean }[] {
const unlocked = getAllPieces(this.currentPlayer.color).filter((p) =>
this.currentPlayer.hasUnlocked(p),
);
if (!this.currentPlayer.canBuyPiece()) {
return unlocked.map((p) => ({
return this.currentPlayer.unlockedPieces.map((p) => ({
piece: p,
isAvailable: false,
}));
}
return unlocked.map((p) => ({
return this.currentPlayer.unlockedPieces.map((p) => ({
piece: p,
isAvailable: p.cost <= this.currentPlayer.gold,
}));
Expand Down
25 changes: 19 additions & 6 deletions src/game/player.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,29 @@
import type { Piece } from "../pieces/piece";
import getAllPieces from "./getAllPieces";

export class Player {
pieces = 1;
maxPieces = 2;
gold = 1;
#color;
boughtPieces = new Set<string>();
#color;

get color() {
return this.#color;
}

get unlockedPieces() {
return getAllPieces(this.color).filter((p) =>
this.#hasUnlocked(p),
);
}

get lockedPieces() {
return getAllPieces(this.color).filter((p) =>
!this.#hasUnlocked(p),
);
}

constructor(color: PlayerColor) {
this.#color = color;
}
Expand All @@ -19,16 +32,16 @@ export class Player {
return this.pieces < this.maxPieces;
}

hasUnlocked(piece: Piece) {
canBuyUpgrade() {
return this.gold >= 3;
}

#hasUnlocked(piece: Piece) {
for (const requirement of piece.requirements) {
if (!this.boughtPieces.has(requirement.name)) {
return false;
}
}
return true;
}

canBuyUpgrade() {
return this.gold >= 3;
}
}
57 changes: 36 additions & 21 deletions src/main.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Panzoom from "@panzoom/panzoom";
import Game from "./game/game.ts";
import { Point } from "./game/point.ts";

// TODO: warn when piece limit is hit

Expand Down Expand Up @@ -33,7 +34,8 @@ for (let y = 0; y < game.board.height; y++) {
}

const castleMenu = q("#castle-menu") as HTMLDialogElement;
const castleMenuPieces = q("#castle-menu-pieces") as HTMLUListElement;
const castleMenuPieces = q("#pieces") as HTMLUListElement;
const castleMenuLockedPieces = q("#locked-pieces") as HTMLUListElement;
const gameOverDialog = q("#game-over") as HTMLDialogElement;

q("#skip-turn").onclick = () => game.skipTurn();
Expand Down Expand Up @@ -146,26 +148,7 @@ function renderGame() {
game.moveTo(point);
}
else if (cell.building === "castle" && cell.owner === game.currentPlayer) {
castleMenu.showModal();
castleMenuPieces.innerHTML = "";
for (const { piece, isAvailable } of game.getPiecesToBuy()) {
const li = document.createElement("li");
li.classList.add("cell");
li.style.backgroundImage = `url('/${piece.name}-${piece.color}.png')`;
if (isAvailable) {
li.onclick = () => {
game.buyPiece(point, piece);
castleMenu.close();
};
} else {
li.classList.add("not-available");
}
const div = document.createElement("div");
div.classList.add("cell-annotation");
div.textContent = piece.cost.toString();
li.appendChild(div);
castleMenuPieces.appendChild(li);
}
openCastleMenu(point)
} else {
for (const c of allTableCells()) {
c.classList.remove("selected", "highlighted");
Expand All @@ -178,3 +161,35 @@ function renderGame() {

firstRender = false;
}

function openCastleMenu(point: Point) {
castleMenu.showModal();
castleMenuPieces.innerHTML = "";
for (const { piece, isAvailable } of game.getPiecesToBuy()) {
const li = document.createElement("li");
li.classList.add("cell");
li.style.backgroundImage = `url('/${piece.name}-${piece.color}.png')`;
if (isAvailable) {
li.onclick = () => {
game.buyPiece(point, piece);
castleMenu.close();
};
} else {
li.classList.add("not-available");
}
const div = document.createElement("div");
div.classList.add("cell-annotation");
div.textContent = piece.cost.toString();
li.appendChild(div);
castleMenuPieces.appendChild(li);
}
let lockedPiecesHTML = ""
for (const piece of game.nextAvailablePieces) {
let lockedPieceHTML = ""
for (const requirement of piece.requirements) {
lockedPieceHTML += `<div class="cell${game.currentPlayer.boughtPieces.has(requirement.name) ? "" : " not-available"}" style="background-image: url('/${requirement.name}-${requirement.color}.png')"></div>`
}
lockedPiecesHTML += `<li><div class="cell not-available" style="background-image: url('/${piece.name}-${piece.color}.png')"></div><span>requires:</span>${lockedPieceHTML}</li>`
}
castleMenuLockedPieces.innerHTML = lockedPiecesHTML
}
2 changes: 2 additions & 0 deletions src/pieces.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import getAllPieces from "./game/getAllPieces";

// TODO: inline this

const body = document.body;
for (const piece of getAllPieces("white")) {
if (piece.name === "pawn") {
Expand Down

0 comments on commit 4ba4682

Please sign in to comment.