Randomise ink colours each game

This commit is contained in:
Andrio Celos 2023-10-21 18:33:06 +11:00
parent 4a2bd4d5f9
commit e430d7f3d8
6 changed files with 200 additions and 135 deletions

View File

@ -153,7 +153,7 @@
</div>
<div id="gameControls">
<div id="debugColour" style="display: none">
<div id="debugColour" hidden>
<table>
<tr>
<th>Colour</th>

View File

@ -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<number>();
const placements = data.placements;
board.clearHighlight();
board.cardPlaying = null;
board.autoHighlight = false;
board.specialAttack = false;
canPlay = false;
timeLabel.faded = true;
const inkPlaced = new Set<number>();
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;

View File

@ -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();
}

View File

@ -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;
}
}

View File

@ -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];

View File

@ -0,0 +1,2 @@
namespace TableturfBattleServer;
internal record struct PlayerColours(Colour InkColour, Colour SpecialColour, Colour SpecialAccentColour, bool UIBaseColourIsSpecialColour);