diff --git a/TableturfBattleClient/index.html b/TableturfBattleClient/index.html
index 8338099..2921470 100644
--- a/TableturfBattleClient/index.html
+++ b/TableturfBattleClient/index.html
@@ -153,7 +153,7 @@
-
+
| Colour |
diff --git a/TableturfBattleClient/src/Pages/GamePage.ts b/TableturfBattleClient/src/Pages/GamePage.ts
index 3528fb0..b0922db 100644
--- a/TableturfBattleClient/src/Pages/GamePage.ts
+++ b/TableturfBattleClient/src/Pages/GamePage.ts
@@ -139,7 +139,8 @@ function initReplay() {
flipButton.hidden = false;
gameButtonsContainer.hidden = false;
gamePage.dataset.myPlayerIndex = '0';
- gamePage.dataset.uiBaseColourIsSpecialColour = 'true';
+ updateColours();
+ gamePage.dataset.uiBaseColourIsSpecialColour = currentGame!.game.players[0].uiBaseColourIsSpecialColour?.toString();
canPlay = false;
showPage('game');
clearPlayContainers();
@@ -169,7 +170,7 @@ function initTest(stage: Stage) {
testUndoButton.enabled = false;
clearChildren(testPlacementList);
gamePage.dataset.myPlayerIndex = '0';
- gamePage.dataset.uiBaseColourIsSpecialColour = 'true';
+ gamePage.dataset.uiBaseColourIsSpecialColour = uiBaseColourIsSpecialColourOutOfGame.toString();
gameButtonsContainer.hidden = false;
testControls.hidden = false;
clearPlayContainers();
@@ -497,16 +498,28 @@ function loadPlayers(players: Player[]) {
playerBars[i].name = player.name;
playerBars[i].winCounter.wins = players[i].gamesWon;
updateStats(i, scores);
- if (player.colour.r || player.colour.g || player.colour.b) {
- document.body.style.setProperty(`--primary-colour-${i + 1}`, `rgb(${player.colour.r}, ${player.colour.g}, ${player.colour.b})`);
- document.body.style.setProperty(`--special-colour-${i + 1}`, `rgb(${player.specialColour.r}, ${player.specialColour.g}, ${player.specialColour.b})`);
- document.body.style.setProperty(`--special-accent-colour-${i + 1}`, `rgb(${player.specialAccentColour.r}, ${player.specialAccentColour.g}, ${player.specialAccentColour.b})`);
- }
}
for (let i = 0; i < playerBars.length; i++) {
playerBars[i].visible = i < players.length;
playContainers[i].hidden = i >= players.length;
}
+ updateColours();
+}
+
+function updateColours() {
+ if (currentGame == null) return;
+ for (let i = 0; i < currentGame.game.players.length; i++) {
+ if (currentGame.game.players[i].colour.r > 0 || currentGame.game.players[i].colour.g > 0 || currentGame.game.players[i].colour.b > 0) {
+ setColour(i, 0, currentGame.game.players[i].colour);
+ setColour(i, 1, currentGame.game.players[i].specialColour);
+ setColour(i, 2, currentGame.game.players[i].specialAccentColour);
+ for (let j = 0; j < 3; j++) {
+ updateHSL(i, j);
+ updateRGB(i, j);
+ }
+ }
+ }
+ uiBaseColourIsSpecialColourOutOfGame = currentGame.game.players[0].uiBaseColourIsSpecialColour ?? true;
}
function updateStats(playerIndex: number, scores: number[]) {
@@ -587,99 +600,103 @@ async function playInkAnimations(data: {
placements: Placement[],
specialSpacesActivated: Point[]
}, abortSignal?: AbortSignal) {
- if (!currentGame) return;
+ try {
+ if (!currentGame) return;
- const inkPlaced = new Set();
- const placements = data.placements;
- board.clearHighlight();
- board.cardPlaying = null;
- board.autoHighlight = false;
- board.specialAttack = false;
- canPlay = false;
- timeLabel.faded = true;
+ const inkPlaced = new Set();
+ const placements = data.placements;
+ board.clearHighlight();
+ board.cardPlaying = null;
+ board.autoHighlight = false;
+ board.specialAttack = false;
+ canPlay = false;
+ timeLabel.faded = true;
- // Show the cards that were played.
- let anySpecialAttacks = false;
- for (let i = 0; i < currentGame.game.players.length; i++) {
- const player = currentGame.game.players[i];
- const move = data.moves[i];
- if ((move as PlayMove).isSpecialAttack)
- anySpecialAttacks = true;
+ // Show the cards that were played.
+ let anySpecialAttacks = false;
+ for (let i = 0; i < currentGame.game.players.length; i++) {
+ const player = currentGame.game.players[i];
+ const move = data.moves[i];
+ if ((move as PlayMove).isSpecialAttack)
+ anySpecialAttacks = true;
- function addCardDisplay() {
- clearChildren(playContainers[i]);
- const display = new CardDisplay(move.card, 1);
- playContainers[i].append(display.element);
- if ((move as PlayMove).isSpecialAttack) {
- if (currentReplay) player.specialPoints -= (move as PlayMove).card.specialCost;
- const el = document.createElement('div');
- el.className = 'specialAttackLabel';
- el.innerText = 'Special Attack!';
- playContainers[i].appendChild(el);
- display.element.classList.add('specialAttack');
- } else if (move.isPass) {
- if (currentReplay) {
- player.passes++;
- player.specialPoints++;
+ function addCardDisplay() {
+ clearChildren(playContainers[i]);
+ const display = new CardDisplay(move.card, 1);
+ playContainers[i].append(display.element);
+ if ((move as PlayMove).isSpecialAttack) {
+ if (currentReplay) player.specialPoints -= (move as PlayMove).card.specialCost;
+ const el = document.createElement('div');
+ el.className = 'specialAttackLabel';
+ el.innerText = 'Special Attack!';
+ playContainers[i].appendChild(el);
+ display.element.classList.add('specialAttack');
+ } else if (move.isPass) {
+ if (currentReplay) {
+ player.passes++;
+ player.specialPoints++;
+ }
+ const el = document.createElement('div');
+ el.className = 'passLabel';
+ el.innerText = 'Pass';
+ playContainers[i].appendChild(el);
}
- const el = document.createElement('div');
- el.className = 'passLabel';
- el.innerText = 'Pass';
- playContainers[i].appendChild(el);
+ }
+
+ const back = playContainers[i].firstElementChild as HTMLElement;
+ if (back) {
+ back.style.setProperty('animation', '0.1s ease-in forwards flipCardOut');
+ back.addEventListener('animationend', addCardDisplay);
+ } else
+ addCardDisplay();
+ }
+
+ await delay(anySpecialAttacks ? 3000 : 1000, abortSignal);
+ for (let i = 0; i < data.game.players.length; i++) {
+ if ((data.moves[i] as PlayMove).isSpecialAttack)
+ playerBars[i].specialPoints -= data.moves[i].card.specialCost;
+ playerBars[i].highlightSpecialPoints = 0;
+ }
+ board.enableInkAnimations();
+ for (const placement of placements) {
+ // Skip the delay when cards don't overlap.
+ if (placement.spacesAffected.find(p => inkPlaced.has(p.space.y * 37 + p.space.x))) {
+ inkPlaced.clear();
+ await delay(500, abortSignal);
+ board.clearInkAnimations();
+ await delay(500, abortSignal);
+ board.enableInkAnimations();
+ }
+
+ for (const p of placement.spacesAffected) {
+ inkPlaced.add(p.space.y * 37 + p.space.x);
+ board.setDisplayedSpace(p.space.x, p.space.y, p.newState);
+ board.showInkAnimation(p.space.x, p.space.y);
}
}
+ await delay(500, abortSignal);
+ board.clearInkAnimations();
+ await delay(500, abortSignal);
- const back = playContainers[i].firstElementChild as HTMLElement;
- if (back) {
- back.style.setProperty('animation', '0.1s ease-in forwards flipCardOut');
- back.addEventListener('animationend', addCardDisplay);
- } else
- addCardDisplay();
- }
-
- await delay(anySpecialAttacks ? 3000 : 1000, abortSignal);
- for (let i = 0; i < data.game.players.length; i++) {
- if ((data.moves[i] as PlayMove).isSpecialAttack)
- playerBars[i].specialPoints -= data.moves[i].card.specialCost;
- playerBars[i].highlightSpecialPoints = 0;
- }
- board.enableInkAnimations();
- for (const placement of placements) {
- // Skip the delay when cards don't overlap.
- if (placement.spacesAffected.find(p => inkPlaced.has(p.space.y * 37 + p.space.x))) {
- inkPlaced.clear();
- await delay(500, abortSignal);
- board.clearInkAnimations();
- await delay(500, abortSignal);
- board.enableInkAnimations();
+ // Show special spaces.
+ if (data.game.board)
+ board.grid = data.game.board;
+ board.refresh();
+ if (data.specialSpacesActivated.length > 0)
+ await delay(1000, abortSignal); // Delay if we expect that this changed the board.
+ const scores = board.getScores();
+ for (let i = 0; i < data.game.players.length; i++) {
+ playerBars[i].specialPoints = data.game.players[i].specialPoints;
+ playerBars[i].pointsDelta = scores[i] - playerBars[i].points;
}
-
- for (const p of placement.spacesAffected) {
- inkPlaced.add(p.space.y * 37 + p.space.x);
- board.setDisplayedSpace(p.space.x, p.space.y, p.newState);
- board.showInkAnimation(p.space.x, p.space.y);
+ await delay(1000, abortSignal);
+ for (let i = 0; i < data.game.players.length; i++) {
+ updateStats(i, scores);
}
+ await delay(1000, abortSignal);
+ } catch (ex) {
+ if (!(ex instanceof DOMException) || ex.name != 'AbortError') console.error(ex);
}
- await delay(500, abortSignal);
- board.clearInkAnimations();
- await delay(500, abortSignal);
-
- // Show special spaces.
- if (data.game.board)
- board.grid = data.game.board;
- board.refresh();
- if (data.specialSpacesActivated.length > 0)
- await delay(1000, abortSignal); // Delay if we expect that this changed the board.
- const scores = board.getScores();
- for (let i = 0; i < data.game.players.length; i++) {
- playerBars[i].specialPoints = data.game.players[i].specialPoints;
- playerBars[i].pointsDelta = scores[i] - playerBars[i].points;
- }
- await delay(1000, abortSignal);
- for (let i = 0; i < data.game.players.length; i++) {
- updateStats(i, scores);
- }
- await delay(1000, abortSignal);
}
function showResult() {
@@ -1206,6 +1223,10 @@ function leaveButton_click(e: MouseEvent) {
leaveButton.addEventListener('click', leaveButton_click);
+function showColourDebug() {
+ document.getElementById('debugColour')!.hidden = false;
+}
+
const colBoxes = [
[
[ document.getElementById("colH0I") as HTMLInputElement, document.getElementById("colS0I") as HTMLInputElement, document.getElementById("colL0I") as HTMLInputElement, document.getElementById("colRGB0I") as HTMLInputElement ],
@@ -1229,14 +1250,40 @@ const colBoxes = [
]
];
function colHSL_change(e: Event) {
- var p = parseInt((e.target as HTMLInputElement).dataset.player!);
- var c = parseInt((e.target as HTMLInputElement).dataset.index!);
- updateRGB(p, c);
+ const box = e.target as HTMLInputElement;
+ const playerIndex = parseInt(box.dataset.player!);
+ const colourIndex = parseInt(box.dataset.index!);
+
+ const h = (parseInt(colBoxes[playerIndex][colourIndex][0].value) % 360 + 360) % 360; // degrees
+ const s = Math.min(100, Math.max(0, parseInt(colBoxes[playerIndex][colourIndex][1].value))); // %
+ const l = Math.min(100, Math.max(0, parseInt(colBoxes[playerIndex][colourIndex][2].value))); // %
+
+ const c = (100 - Math.abs(2 * l - 100)) * s; // × 1/10000
+ const x = c * (60 - Math.abs(h % 120 - 60)); // × 1/600000
+ const min = (l * 100 - c / 2) * 60; // × 1/600000
+ const rgb1 =
+ h < 60 ? { r: c * 60, g: x, b: 0 } :
+ h < 120 ? { r: x, g: c * 60, b: 0 } :
+ h < 180 ? { r: 0, g: c * 60, b: x } :
+ h < 240 ? { r: 0, g: x, b: c * 60 } :
+ h < 300 ? { r: x, g: 0, b: c * 60 } :
+ { r: c * 60, g: 0, b: x }; // × 1/600000
+ const rgb = { r: Math.round((rgb1.r + min) * 255 / 600000), g: Math.round((rgb1.g + min) * 255 / 600000), b: Math.round((rgb1.b + min) * 255 / 600000) };
+
+ setColour(playerIndex, colourIndex, rgb);
+ updateRGB(playerIndex, colourIndex);
}
function colRGB_change(e: Event) {
- var p = parseInt((e.target as HTMLInputElement).dataset.player!);
- var c = parseInt((e.target as HTMLInputElement).dataset.index!);
- updateHSL(p, c);
+ const box = e.target as HTMLInputElement;
+ const playerIndex = parseInt(box.dataset.player!);
+ const colourIndex = parseInt(box.dataset.index!);
+
+ let colourStr = colBoxes[playerIndex][colourIndex][3].value;
+ if (colourStr.startsWith('#')) colourStr = colourStr.substring(1);
+ if (colourStr.length >= 8) colourStr = colourStr.substring(colourStr.length - 6);
+ const rgb = { r: parseInt(colourStr.substring(0, 2), 16), g: parseInt(colourStr.substring(2, 4), 16), b: parseInt(colourStr.substring(4, 6), 16) };
+ setColour(playerIndex, colourIndex, rgb);
+ updateHSL(playerIndex, colourIndex);
}
for (const a of colBoxes) {
for (const a2 of a) {
@@ -1255,8 +1302,11 @@ function updateHSL(playerIndex: number, colourIndex: number) {
let colourStr = colBoxes[playerIndex][colourIndex][3].value;
if (colourStr.startsWith('#')) colourStr = colourStr.substring(1);
if (colourStr.length == 8) colourStr = colourStr.substring(2);
- const colour = { r: parseInt(colourStr.substring(0, 2), 16), g: parseInt(colourStr.substring(2, 4), 16), b: parseInt(colourStr.substring(4, 6), 16) };
- setColour(playerIndex, colourIndex, colour);
+ const colour = currentGame != null
+ ? (colourIndex == 0 ? currentGame!.game.players[playerIndex].colour :
+ colourIndex == 1 ? currentGame!.game.players[playerIndex].specialColour :
+ currentGame!.game.players[playerIndex].specialAccentColour)
+ : defaultColours[playerIndex][colourIndex];
const max = Math.max(colour.r, colour.g, colour.b); // × 1/255
const min = Math.min(colour.r, colour.g, colour.b); // × 1/255
const c = max - min; // × 1/255
@@ -1273,22 +1323,12 @@ function updateHSL(playerIndex: number, colourIndex: number) {
colBoxes[playerIndex][colourIndex][2].value = l.toString();
}
function updateRGB(playerIndex: number, colourIndex: number) {
- const h = (parseInt(colBoxes[playerIndex][colourIndex][0].value) % 360 + 360) % 360; // degrees
- const s = Math.min(100, Math.max(0, parseInt(colBoxes[playerIndex][colourIndex][1].value))); // %
- const l = Math.min(100, Math.max(0, parseInt(colBoxes[playerIndex][colourIndex][2].value))); // %
- const c = (100 - Math.abs(2 * l - 100)) * s; // × 1/10000
- const x = c * (60 - Math.abs(h % 120 - 60)); // × 1/600000
- const min = (l * 100 - c / 2) * 60; // × 1/600000
- const rgb1 =
- h < 60 ? { r: c * 60, g: x, b: 0 } :
- h < 120 ? { r: x, g: c * 60, b: 0 } :
- h < 180 ? { r: 0, g: c * 60, b: x } :
- h < 240 ? { r: 0, g: x, b: c * 60 } :
- h < 300 ? { r: x, g: 0, b: c * 60 } :
- { r: c * 60, g: 0, b: x }; // × 1/600000
- const rgb = { r: Math.round((rgb1.r + min) * 255 / 600000), g: Math.round((rgb1.g + min) * 255 / 600000), b: Math.round((rgb1.b + min) * 255 / 600000) };
- colBoxes[playerIndex][colourIndex][3].value = `#${rgb.r.toString(16).padStart(2, '0')}${rgb.g.toString(16).padStart(2, '0')}${rgb.b.toString(16).padStart(2, '0')}`;
- setColour(playerIndex, colourIndex, rgb);
+ const colour = currentGame != null
+ ? (colourIndex == 0 ? currentGame!.game.players[playerIndex].colour :
+ colourIndex == 1 ? currentGame!.game.players[playerIndex].specialColour :
+ currentGame!.game.players[playerIndex].specialAccentColour)
+ : defaultColours[playerIndex][colourIndex];
+ colBoxes[playerIndex][colourIndex][3].value = `#${colour.r.toString(16).padStart(2, '0')}${colour.g.toString(16).padStart(2, '0')}${colour.b.toString(16).padStart(2, '0')}`;
}
function setColour(playerIndex: number, colourIndex: number, colour: Colour) {
if (!currentGame || playerIndex >= currentGame.game.players.length) return;
diff --git a/TableturfBattleClient/src/app.ts b/TableturfBattleClient/src/app.ts
index 2bf5f69..4621c06 100644
--- a/TableturfBattleClient/src/app.ts
+++ b/TableturfBattleClient/src/app.ts
@@ -1,5 +1,13 @@
declare var baseUrl: string;
+const defaultColours = [
+ [ { r: 236, g: 249, b: 1 }, { r: 250, g: 158, b: 0 }, { r: 249, g: 249, b: 31 } ],
+ [ { r: 74, g: 92, b: 252 }, { r: 1, g: 237, b: 254 }, { r: 213, g: 225, b: 225 } ],
+ [ { r: 249, g: 6, b: 224 }, { r: 128, g: 6, b: 249 }, { r: 235, g: 180, b: 253 } ],
+ [ { r: 6, g: 249, b: 148 }, { r: 6, g: 249, b: 6 }, { r: 180, g: 253, b: 199 } ]
+];
+let uiBaseColourIsSpecialColourOutOfGame = true;
+
const errorDialog = document.getElementById('errorDialog') as HTMLDialogElement;
const errorMessage = document.getElementById('errorMessage')!;
const errorDialogForm = document.getElementById('errorDialogForm') as HTMLFormElement;
@@ -259,6 +267,7 @@ function setupWebSocket(gameID: string) {
me: payload.playerData,
webSocket: webSocket
};
+ updateColours();
lobbyResetSlots();
for (let i = 0; i < currentGame.game.players.length; i++)
@@ -393,6 +402,7 @@ function setupWebSocket(gameID: string) {
});
webSocket.addEventListener('close', webSocket_close);
}
+
function webSocket_close() {
communicationError();
}
diff --git a/TableturfBattleServer/Colour.cs b/TableturfBattleServer/Colour.cs
index 905f2fd..35fcb79 100644
--- a/TableturfBattleServer/Colour.cs
+++ b/TableturfBattleServer/Colour.cs
@@ -9,4 +9,10 @@ public struct Colour {
this.G = g;
this.B = b;
}
+
+ public Colour(int packed) {
+ this.R = packed >> 16 & byte.MaxValue;
+ this.G = packed >> 8 & byte.MaxValue;
+ this.B = packed & byte.MaxValue;
+ }
}
diff --git a/TableturfBattleServer/Game.cs b/TableturfBattleServer/Game.cs
index 98dcb77..f968c23 100644
--- a/TableturfBattleServer/Game.cs
+++ b/TableturfBattleServer/Game.cs
@@ -32,6 +32,18 @@ public class Game {
public Game(int maxPlayers) => this.MaxPlayers = maxPlayers;
+ private static readonly PlayerColours[] Colours = new PlayerColours[] {
+ new(new(0xf2200d), new(0xff8c1a), new(0xffd5cc), false), // Red
+ new(new(0xf2740d), new(0xff4000), new(0xffcc99), true), // Orange
+ new(new(0xecf901), new(0xfa9e00), new(0xf9f91f), true), // Yellow
+ new(new(0xc0f915), new(0x6aff00), new(0xe6ff99), true), // LimeGreen
+ new(new(0x06e006), new(0x33ffcc), new(0xb3ffd9), false), // Green
+ new(new(0x00ffea), new(0x00a8e0), new(0x99ffff), true), // Turquoise
+ new(new(0x4a5cfc), new(0x01edfe), new(0xd5e1e1), false), // Blue
+ new(new(0xa106ef), new(0xff00ff), new(0xffb3ff), false), // Purple
+ new(new(0xf906e0), new(0x8006f9), new(0xebb4fd), true), // Magenta
+ };
+
public bool TryAddPlayer(Player player, out int playerIndex, out Error error) {
lock (this.Players) {
if (this.State != GameState.WaitingForPlayers) {
@@ -134,30 +146,25 @@ public class Game {
internal void Tick() {
if (this.State is GameState.WaitingForPlayers or GameState.ChoosingStage && this.Players.Count >= 2 && this.Players.All(p => p.selectedStageIndex != null)) {
// Choose colours.
- for (int i = 0; i < this.Players.Count; i++) {
- this.Players[i].Colour = i switch {
- 0 => new(236, 249, 1),
- 1 => new( 74, 92, 252),
- 2 => new(249, 6, 224),
- _ => new( 6, 249, 148),
+ var random = new Random();
+ if (this.State == GameState.WaitingForPlayers) {
+ var index = random.Next(Colours.Length);
+ var increment = this.Players.Count switch {
+ 2 => random.Next(3, 7),
+ 3 => random.Next(2, 4),
+ _ => 2
};
- this.Players[i].SpecialColour = i switch {
- 0 => new(250, 158, 0),
- 1 => new( 1, 237, 254),
- 2 => new(128, 6, 249),
- _ => new( 6, 249, 6),
- };
- this.Players[i].SpecialAccentColour = i switch {
- 0 => new(249, 249, 31),
- 1 => new(213, 225, 225),
- 2 => new(235, 180, 253),
- _ => new(180, 253, 199),
- };
- this.Players[i].UIBaseColourIsSpecialColour = i != 1;
+ for (int i = 0; i < this.Players.Count; i++) {
+ var colours = Colours[index];
+ index = (index + increment) % Colours.Length;
+ this.Players[i].Colour = colours.InkColour;
+ this.Players[i].SpecialColour = colours.SpecialColour;
+ this.Players[i].SpecialAccentColour = colours.SpecialAccentColour;
+ this.Players[i].UIBaseColourIsSpecialColour = colours.UIBaseColourIsSpecialColour;
+ }
}
// Choose the stage.
- var random = new Random();
var stageIndex = this.Players[random.Next(this.Players.Count)].selectedStageIndex!.Value;
if (stageIndex < 0) stageIndex = random.Next(StageDatabase.Stages.Count);
var stage = StageDatabase.Stages[stageIndex];
diff --git a/TableturfBattleServer/PlayerColours.cs b/TableturfBattleServer/PlayerColours.cs
new file mode 100644
index 0000000..0f90488
--- /dev/null
+++ b/TableturfBattleServer/PlayerColours.cs
@@ -0,0 +1,2 @@
+namespace TableturfBattleServer;
+internal record struct PlayerColours(Colour InkColour, Colour SpecialColour, Colour SpecialAccentColour, bool UIBaseColourIsSpecialColour);