diff --git a/TableturfBattleClient/src/HintLabel.ts b/TableturfBattleClient/src/HintLabel.ts new file mode 100644 index 0000000..b061b7e --- /dev/null +++ b/TableturfBattleClient/src/HintLabel.ts @@ -0,0 +1,75 @@ +class HintLabel { + private static readonly CLASS_NAME_ERROR = 'hintError'; + private static readonly CLASS_NAME_SPECIAL = 'hintSpecial'; + private static readonly CLASS_NAME_REPEATED = 'repeated'; + + readonly element: HTMLElement; + + private _isSpecial = false; + private _errorTimeout: number | null = null; + private _baseHtml: string | null = null; + + constructor(element: HTMLElement) { + this.element = element; + } + + static fromId(id: string) { return new HintLabel(document.getElementById(id)!); } + + private clearError() { + if (this._errorTimeout) { + clearTimeout(this._errorTimeout); + this._errorTimeout = null; + } + } + + show(html: string, isSpecial: boolean) { + this._baseHtml = html; + this._isSpecial = isSpecial; + this.clearError(); + this.element.classList.remove(HintLabel.CLASS_NAME_ERROR); + this.element.classList.remove(HintLabel.CLASS_NAME_REPEATED); + if (isSpecial) + this.element.classList.add(HintLabel.CLASS_NAME_SPECIAL); + else + this.element.classList.remove(HintLabel.CLASS_NAME_SPECIAL); + this.element.innerHTML = html; + this.element.hidden = false; + } + + clear() { + this._baseHtml = null; + this._isSpecial = false; + clearChildren(this.element); + this.clearError(); + this.element.hidden = true; + this.element.classList.remove(HintLabel.CLASS_NAME_ERROR); + this.element.classList.remove(HintLabel.CLASS_NAME_REPEATED); + this.element.classList.remove(HintLabel.CLASS_NAME_SPECIAL); + } + + showError(html: string) { + if (this._errorTimeout != null && html == this.element.innerHTML) { + this.element.classList.add(HintLabel.CLASS_NAME_REPEATED); + resetAnimation(this.element); + } else + this.element.classList.remove(HintLabel.CLASS_NAME_REPEATED); + this.clearError(); + this.element.classList.remove(HintLabel.CLASS_NAME_SPECIAL); + this.element.classList.add(HintLabel.CLASS_NAME_ERROR); + this.element.innerHTML = html; + this.element.hidden = false; + this._errorTimeout = setTimeout((() => { + this._errorTimeout = null; + this.element.classList.remove(HintLabel.CLASS_NAME_ERROR); + this.element.classList.remove(HintLabel.CLASS_NAME_REPEATED); + if (this._baseHtml) { + if (this._isSpecial) + this.element.classList.add(HintLabel.CLASS_NAME_SPECIAL); + this.element.innerHTML = this._baseHtml; + } else { + this.element.hidden = true; + clearChildren(this.element); + } + }).bind(this), 5000); + } +} diff --git a/TableturfBattleClient/src/Pages/GamePage.ts b/TableturfBattleClient/src/Pages/GamePage.ts index 5ef03e4..b1cef6f 100644 --- a/TableturfBattleClient/src/Pages/GamePage.ts +++ b/TableturfBattleClient/src/Pages/GamePage.ts @@ -7,7 +7,6 @@ const timeLabel = new TimeLabel(document.getElementById('timeLabel')!); 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; @@ -25,8 +24,8 @@ 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 cardHint = HintLabel.fromId('cardHint'); +const playHint = HintLabel.fromId('playHint'); const shareReplayLinkButton = document.getElementById('shareReplayLinkButton') as HTMLButtonElement; let canShareReplay = false; @@ -57,6 +56,8 @@ const testDeckCardButtons: CardButton[] = [ ]; const testPlacements: { card: Card, placementResults: PlacementResults }[] = [ ]; const testCardListBackdrop = document.getElementById('testCardListBackdrop')!; +let playHintHtml: string | null = null; + let testMode = false; let canPlay = false; @@ -362,7 +363,7 @@ function addTestCard(card: Card) { function testCardButton_click(button: CardButton) { if (!button.enabled) { - showPlayHintError('This card has already been played.'); + cardHint.showError('This card has already been played.'); return; } board.autoHighlight = true; @@ -447,30 +448,6 @@ 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'); @@ -521,8 +498,8 @@ function lockGamePage() { for (const button of handButtons.buttons) button.enabled = false; passButton.enabled = false; specialButton.enabled = false; - passHint.hidden = true; - clearPlayHint(); + cardHint.clear(); + playHint.clear(); } async function playInkAnimations(data: { @@ -550,7 +527,10 @@ async function playInkAnimations(data: { // 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(1000, abortSignal); + await delay(500, abortSignal); + board.clearInkAnimations(); + await delay(500, abortSignal); + board.enableInkAnimations(); } for (const p of placement.spacesAffected) { @@ -676,12 +656,13 @@ function updateHandAndDeck(playerData: PlayerData) { button.buttonElement.addEventListener('click', e => { if (!button.enabled) { if (specialButton.checked && currentGame!.players[currentGame!.me!.playerIndex].specialPoints < card.specialCost) - showPlayHintError('Not enough special points.'); + cardHint.showError('Not enough special points.'); else if (!(specialButton.checked ? canPlayCardAsSpecialAttack : canPlayCard)[i]) - showPlayHintError('No place to play this card.'); + cardHint.showError('No place to play this card.'); e.preventDefault(); return; } + cardHint.clear(); if (passButton.checked) { if (canPlay) { timeLabel.faded = true; @@ -696,6 +677,8 @@ function updateHandAndDeck(playerData: PlayerData) { board.highlightX = board.startSpaces[board.playerIndex!].x - (board.flip ? 4 : 3); board.highlightY = board.startSpaces[board.playerIndex!].y - (board.flip ? 4 : 3); } + if (specialButton.checked) + playHint.show('Unleash it next to