Add more hints to the game page

This commit is contained in:
Andrio Celos
2023-06-23 10:04:24 +10:00
parent d5dde76a34
commit e46de3b775
6 changed files with 117 additions and 16 deletions

View File

@@ -241,6 +241,7 @@
</tr>
</table>
</div>
<div id="passHint" hidden>Select a card to discard.</div>
<div id="handContainer"></div>
<div id="playRow">
@@ -372,6 +373,8 @@
<div class="playContainer" data-index="2"></div>
<div class="playContainer" data-index="0"></div>
<div id="playHint" hidden></div>
<div id="testPlacementList"></div>
</div>
<div id="deckListPage" hidden>

View File

@@ -124,14 +124,14 @@ class Board {
var x2 = x + dx;
var y2 = y + dy;
if (x2 < 0 || x2 >= this.grid.length || y2 < 0 || y2 >= this.grid[x2].length)
return "It cannot overlap a wall or out of bounds."; // Out of bounds.
return 'It cannot be over a wall.'; // Out of bounds.
const space = this.grid[x2][y2];
if (space == Space.Wall || space == Space.OutOfBounds)
return "It cannot overlap a wall or out of bounds.";
return 'It cannot be over a wall.';
if (space >= Space.SpecialInactive1)
return "It cannot overlap a special space."
return `It cannot be over${' <div class="playHintSpecial" aria-label="Special space">&nbsp;</div>'.repeat(Math.max(currentGame?.players?.length ?? 0, 2))}.`;
if (space != Space.Empty && !isSpecialAttack)
return "It cannot overlap existing ink without a special attack.";
return 'It cannot be over inked spaces.';
if (!isAnchored) {
// A normal play must be adjacent to ink of the player's colour.
// A special attack must be adjacent to a special space of theirs.
@@ -152,7 +152,7 @@ class Board {
}
}
}
return isAnchored ? null : (isSpecialAttack ? "It must be next to one of your special spaces." : "It must be next to your turf.");
return isAnchored ? null : (isSpecialAttack ? 'It\'s not next to <div class="playHintSpecial">&nbsp;</div>.' : 'It\'s not touching your colour of ink.');
}
refreshHighlight() {

View File

@@ -6,6 +6,7 @@ const handButtons: CardButton[] = [ ];
const passButton = CheckButton.fromId('passButton');
const specialButton = CheckButton.fromId('specialButton');
const passHint = document.getElementById('passHint')!;
const gameButtonsContainer = document.getElementById('gameButtonsContainer')!;
const rotateLeftButton = document.getElementById('rotateLeftButton') as HTMLButtonElement;
const rotateRightButton = document.getElementById('rotateRightButton') as HTMLButtonElement;
@@ -22,6 +23,9 @@ const replayPreviousButton = document.getElementById('replayPreviousButton')!;
const flipButton = document.getElementById('flipButton') as HTMLButtonElement;
let replayAnimationAbortController: AbortController | null = null;
const playHint = document.getElementById('playHint')!;
let playHintTimeout: number | null = null;
const shareReplayLinkButton = document.getElementById('shareReplayLinkButton') as HTMLButtonElement;
let canShareReplay = false;
@@ -449,6 +453,30 @@ function updateStats(playerIndex: number, scores: number[]) {
playerBars[playerIndex].statPassesElement.innerText = currentGame.players[playerIndex].passes.toString();
}
function clearPlayHint() {
playHint.hidden = true;
if (playHintTimeout) {
clearTimeout(playHintTimeout);
playHintTimeout = null;
}
}
function showPlayHint(html: string) {
clearPlayHint();
playHint.innerHTML = html;
playHint.className = '';
playHint.hidden = false;
}
function showPlayHintError(html: string) {
playHint.className = !playHint.hidden && html == playHint.innerHTML ? 'playError repeated' : 'playError';
if (playHintTimeout) {
clearTimeout(playHintTimeout!);
resetAnimation(playHint);
}
playHint.innerHTML = html;
playHint.hidden = false;
playHintTimeout = setTimeout(() => playHint.hidden = true, 5000);
}
/** Shows the ready indication for the specified player. */
function showReady(playerIndex: number) {
const el = document.createElement('div');
@@ -499,6 +527,8 @@ function lockGamePage() {
for (const el of handButtons) el.enabled = false;
passButton.enabled = false;
specialButton.enabled = false;
passHint.hidden = true;
clearPlayHint();
}
async function playInkAnimations(data: {
@@ -756,7 +786,9 @@ function redrawButton_click(e: MouseEvent) {
function passButton_input() {
board.autoHighlight = !passButton.checked;
passHint.hidden = !passButton.checked;
if (passButton.checked) {
clearPlayHint();
specialButton.checked = false;
board.cardPlaying = null;
board.specialAttack = false;
@@ -776,6 +808,8 @@ passButton.input.addEventListener('input', passButton_input);
function specialButton_input() {
board.specialAttack = specialButton.checked;
if (specialButton.checked) {
passHint.hidden = true;
showPlayHint('Unleash it next to <div class="playHintSpecial">&nbsp;</div>!');
passButton.checked = false;
board.autoHighlight = true;
for (let i = 0; i < 4; i++) {
@@ -784,6 +818,7 @@ function specialButton_input() {
handButtons[i].checked = false;
}
} else {
clearPlayHint();
for (let i = 0; i < 4; i++) {
handButtons[i].enabled = canPlayCard[i];
if (!canPlayCard[i])
@@ -817,7 +852,7 @@ board.onsubmit = (x, y) => {
return;
const message = board.checkMoveLegality(currentGame.me.playerIndex, board.cardPlaying, x, y, board.cardRotation, board.specialAttack);
if (message != null) {
alert(message);
showPlayHintError(message);
return;
}
if (testMode) {

View File

@@ -76,14 +76,8 @@ class TimeLabel {
private updateWarningState() {
if (!this.faded && this.timeLeft != null && this.timeLeft <= 10) {
this.contentElement.classList.add('warning');
this.resetAnimation();
resetAnimation(this.contentElement);
} else
this.contentElement.classList.remove('warning');
}
private resetAnimation() {
this.contentElement.style.animation = 'none';
this.contentElement.offsetHeight; // Trigger a reflow.
this.contentElement.style.animation = '';
}
}

View File

@@ -628,6 +628,12 @@ function clearChildren(el: Element) {
while (el2 = el.firstChild)
el.removeChild(el2);
}
function resetAnimation(el: HTMLElement) {
el.style.animation = 'none';
el.offsetHeight; // Trigger a reflow.
el.style.animation = '';
}
document.getElementById('noJSPage')!.innerText = 'Loading client...';

View File

@@ -353,14 +353,25 @@ dialog::backdrop {
row-gap: 4px;
}
.cardSpecialPoint {
.cardSpecialPoint, .playHintSpecial {
display: inline-block;
color: transparent;
background: var(--player-special-colour);
width: 1ch;
height: 1ch;
vertical-align: middle;
}
.cardSpecialPoint {
margin-right: 0.3em;
}
.playHintSpecial {
width: 1.5ch;
height: 1.5ch;
}
.playHintSpecial:nth-of-type(1):not(:last-of-type) { background: var(--special-colour-1); }
.playHintSpecial:nth-of-type(2) { background: var(--special-colour-2); }
.playHintSpecial:nth-of-type(3) { background: var(--special-colour-3); }
.playHintSpecial:nth-of-type(4) { background: var(--special-colour-4); }
.card::before {
/* If it exists */
@@ -553,6 +564,14 @@ dialog::backdrop {
text-align: center;
}
#passHint:not([hidden]) {
position: absolute;
margin: 0 0 35em 1em;
padding: 0.5em 1em;
background: #80808080;
border-radius: 0.5em;
}
#playRow:not([hidden]) {
display: grid;
grid-template-columns: 1fr 1fr;
@@ -706,7 +725,7 @@ label[for="flipBox"] {
bottom: 0;
}
.playContainer { text-align: center; }
.playContainer { text-align: center; margin: 8em 0; }
.playContainer[data-index="2"] { grid-column: play-column-2; grid-row: 2 / -1; align-self: end; }
.playContainer[data-index="1"] { grid-column: play-column-2; grid-row: 1 / 4; align-self: start; }
@@ -722,6 +741,40 @@ label[for="flipBox"] {
margin: 0;
}
#playHint {
grid-column: -3 / -1; grid-row: -2 / -1; align-self: end;
justify-self: end;
margin: 0 3em 1.5em 0;
font-size: larger;
padding: 0 0.5em;
background: #00000080;
border-radius: 0.5em;
}
#playHint.playError {
animation: 1s infinite playHintError;
}
#playHint.playError.repeated {
animation: 1s infinite playHintError, 0.5s linear 1 playHintShake;
}
@keyframes playHintError {
from { background: #ff0000ff; }
50% { background: #ff000080; }
to { background: #ff0000ff; }
}
@keyframes playHintShake {
0% { transform: translate(1px, 1px); }
10% { transform: translate(-1px, -2px); }
20% { transform: translate(-3px, 0px); }
30% { transform: translate(3px, 2px); }
40% { transform: translate(1px, -1px); }
50% { transform: translate(-1px, 2px); }
60% { transform: translate(-3px, 1px); }
70% { transform: translate(3px, 1px); }
80% { transform: translate(-1px, -1px); }
90% { transform: translate(1px, 2px); }
100% { transform: translate(0px, 0px); }
}
#testPlacementList {
display: none;
}
@@ -1331,6 +1384,10 @@ label[for="flipBox"] {
min-height: 12em;
}
#passHint:not([hidden]) {
margin: 0;
}
#scoreSection:not([hidden]) {
grid-column: 1;
grid-row: 2;
@@ -1338,7 +1395,7 @@ label[for="flipBox"] {
grid-template-columns: 1fr;
}
#turnNumberContainer {
#timeSection {
grid-column: 1;
grid-row: 2;
margin: 0;
@@ -1404,6 +1461,12 @@ label[for="flipBox"] {
margin-top: 5em;
}
#playHint {
grid-column: 1 / -1;
grid-row: 2;
margin: 0 1em 0 0;
}
/* Deck editor - Mobile layout */
:is(#deckListPage, #deckEditPage):not([hidden]) {