-
-
69↪ 60+1
-
69↪ 60+1
-
69↪ 60+1
-
69↪ 60+1
-
+
69↪ 60+1
+
69↪ 60+1
+
69↪ 60+1
+
69↪ 60+1
diff --git a/TableturfBattleClient/src/Board.ts b/TableturfBattleClient/src/Board.ts
index 18cb6b7..9d16169 100644
--- a/TableturfBattleClient/src/Board.ts
+++ b/TableturfBattleClient/src/Board.ts
@@ -10,6 +10,7 @@ class Board {
autoHighlight = true;
touchscreenMode = false;
+ flip = false;
highlightX = NaN;
highlightY = NaN;
@@ -38,16 +39,16 @@ class Board {
this.moveHighlight((x, y, r) => [x, y, r - 1], false);
break;
case 'ArrowUp':
- this.moveHighlight((x, y, r) => [x, y - 1, r], true);
+ this.moveHighlight((x, y, r) => [x, y + (this.flip ? 1 : -1), r], true);
break;
case 'ArrowDown':
- this.moveHighlight((x, y, r) => [x, y + 1, r], true);
+ this.moveHighlight((x, y, r) => [x, y + (this.flip ? -1 : 1), r], true);
break;
case 'ArrowLeft':
- this.moveHighlight((x, y, r) => [x - 1, y, r], true);
+ this.moveHighlight((x, y, r) => [x + (this.flip ? 1 : -1), y, r], true);
break;
case 'ArrowRight':
- this.moveHighlight((x, y, r) => [x + 1, y, r], true);
+ this.moveHighlight((x, y, r) => [x + (this.flip ? -1 : 1), y, r], true);
break;
case 'Enter': case ' ':
if (this.onclick)
@@ -67,9 +68,9 @@ class Board {
const touch = e.changedTouches[0];
if (isNaN(this.highlightX) || isNaN(this.highlightY) || isNaN(this.cardRotation)) {
const startSpace = this.startSpaces[this.playerIndex];
- this.highlightX = startSpace.x - 3;
- this.highlightY = startSpace.y - 3;
- this.cardRotation = 0;
+ this.highlightX = startSpace.x - (this.flip ? 4 : 3);
+ this.highlightY = startSpace.y - (this.flip ? 4 : 3);
+ this.cardRotation = this.flip ? 2 : 0;
this.refreshHighlight();
}
this.touch = [touch.identifier, touch.pageX, touch.pageY, this.highlightX, this.highlightY];
@@ -105,7 +106,7 @@ class Board {
if (this.playerIndex == null) return;
if (this.highlightedCells.length == 0 || isNaN(this.highlightX) || isNaN(this.highlightY) || isNaN(this.cardRotation)) {
const startSpace = this.startSpaces[this.playerIndex];
- [this.highlightX, this.highlightY, this.cardRotation] = [startSpace.x - 3, startSpace.y - 3, 0];
+ [this.highlightX, this.highlightY, this.cardRotation] = [startSpace.x - (this.flip ? 4 : 3), startSpace.y - (this.flip ? 4 : 3), 0];
} else {
let [x, y, r] = move(this.highlightX, this.highlightY, this.cardRotation);
let clampedPosition = clamp
@@ -193,44 +194,33 @@ class Board {
this.highlightedCells.splice(0);
}
- resize(grid: Space[][]) {
- if (grid.length == 0)
- throw new Error('Grid must not be empty.');
-
- this.grid = grid || this.grid;
+ resize(grid?: Space[][]) {
+ if (grid) this.grid = grid;
clearChildren(this.table);
this.cells.splice(0);
this.highlightedCells.splice(0);
- const boardWidth = grid.length;
- const boardHeight = grid[0].length;
+ const boardWidth = this.grid.length;
+ const boardHeight = this.grid[0].length;
this.table.style.setProperty('--board-width', boardWidth.toString());
this.table.style.setProperty('--board-height', boardHeight.toString());
- const trs: HTMLTableRowElement[] = [ ];
- for (let y = 0; y < boardHeight; y++) {
- const tr = document.createElement('tr');
- trs.push(tr);
- this.table.appendChild(tr);
- }
-
for (let x = 0; x < boardWidth; x++) {
const col: HTMLTableCellElement[] = [ ];
- for (let y = 0; y < grid[x].length; y++) {
+ for (let y = 0; y < this.grid[x].length; y++) {
const td = document.createElement('td');
td.dataset.x = x.toString();
td.dataset.y = y.toString();
- trs[y].appendChild(td);
col.push(td);
td.addEventListener('mousemove', e => {
if (e.buttons == 0)
this.touchscreenMode = false;
if (!this.touchscreenMode) {
if (this.autoHighlight && this.cardPlaying != null) {
- const x = parseInt((e.target as HTMLTableCellElement).dataset.x!) - 3;
- const y = parseInt((e.target as HTMLTableCellElement).dataset.y!) - 3;
+ const x = parseInt((e.target as HTMLTableCellElement).dataset.x!) - (this.flip ? 4 : 3);
+ const y = parseInt((e.target as HTMLTableCellElement).dataset.y!) - (this.flip ? 4 : 3);
if (x != this.highlightX || y != this.highlightY) {
this.highlightX = x;
this.highlightY = y;
@@ -244,8 +234,8 @@ class Board {
if (this.touchscreenMode) {
this.onclick(this.highlightX, this.highlightY);
} else {
- const x = parseInt((e.target as HTMLTableCellElement).dataset.x!) - 3;
- const y = parseInt((e.target as HTMLTableCellElement).dataset.y!) - 3;
+ const x = parseInt((e.target as HTMLTableCellElement).dataset.x!) - (this.flip ? 4 : 3);
+ const y = parseInt((e.target as HTMLTableCellElement).dataset.y!) - (this.flip ? 4 : 3);
this.onclick(x, y);
}
}
@@ -262,6 +252,26 @@ class Board {
this.cells.push(col);
}
+ const trs: HTMLTableRowElement[] = [ ];
+ for (let y = 0; y < boardHeight; y++) {
+ const tr = document.createElement('tr');
+ trs.push(tr);
+ this.table.appendChild(tr);
+ }
+ if (this.flip) trs.reverse();
+
+ for (let y = 0; y < boardHeight; y++) {
+ if (this.flip) {
+ for (let x = boardWidth - 1; x >= 0; x--) {
+ trs[y].appendChild(this.cells[x][y]);
+ }
+ } else {
+ for (let x = 0; x < boardWidth; x++) {
+ trs[y].appendChild(this.cells[x][y]);
+ }
+ }
+ }
+
this.refresh();
}
diff --git a/TableturfBattleClient/src/Pages/GamePage.ts b/TableturfBattleClient/src/Pages/GamePage.ts
index 9024fa9..32d19a5 100644
--- a/TableturfBattleClient/src/Pages/GamePage.ts
+++ b/TableturfBattleClient/src/Pages/GamePage.ts
@@ -21,6 +21,7 @@ const resultElement = document.getElementById('result')!;
const replayControls = document.getElementById('replayControls')!;
const replayNextButton = document.getElementById('replayNextButton')!;
const replayPreviousButton = document.getElementById('replayPreviousButton')!;
+const replayFlipBox = document.getElementById('replayFlipBox') as HTMLInputElement;
let replayAnimationAbortController: AbortController | null = null;
const shareReplayLinkButton = document.getElementById('shareReplayLinkButton') as HTMLButtonElement;
@@ -185,6 +186,23 @@ replayPreviousButton.addEventListener('click', _ => {
}
});
+replayFlipBox.addEventListener('input', _ => {
+ if (currentGame == null || currentReplay == null) return;
+ if (replayAnimationAbortController) {
+ replayAnimationAbortController.abort();
+ replayAnimationAbortController = null;
+ clearPlayContainers();
+ turnNumberLabel.setTurnNumber(currentGame.turnNumber);
+ for (let i = 0; i < currentGame.players.length; i++) {
+ updateStats(i);
+ }
+ }
+ board.flip = replayFlipBox.checked;
+ if (board.flip) gamePage.classList.add('boardFlipped');
+ else gamePage.classList.remove('boardFlipped');
+ board.resize();
+});
+
function loadPlayers(players: Player[]) {
gamePage.dataset.players = players.length.toString();
for (let i = 0; i < players.length; i++) {
@@ -404,10 +422,10 @@ function updateHand(playerData: PlayerData) {
} else {
board.cardPlaying = card;
if (isNaN(board.highlightX) || isNaN(board.highlightY)) {
- board.highlightX = board.startSpaces[board.playerIndex!].x - 3;
- board.highlightY = board.startSpaces[board.playerIndex!].y - 3;
+ board.highlightX = board.startSpaces[board.playerIndex!].x - (board.flip ? 4 : 3);
+ board.highlightY = board.startSpaces[board.playerIndex!].y - (board.flip ? 4 : 3);
}
- board.cardRotation = 0;
+ board.cardRotation = board.flip ? 2 : 0;
board.refreshHighlight();
board.table.focus();
}
diff --git a/TableturfBattleClient/src/app.ts b/TableturfBattleClient/src/app.ts
index 6afef2a..44a2ec3 100644
--- a/TableturfBattleClient/src/app.ts
+++ b/TableturfBattleClient/src/app.ts
@@ -88,6 +88,9 @@ function onGameStateChange(game: any, playerData: PlayerData | null) {
throw new Error('currentGame is null');
clearPlayContainers();
if (game.board) {
+ board.flip = playerData != null && playerData.playerIndex % 2 != 0;
+ if (board.flip) gamePage.classList.add('boardFlipped');
+ else gamePage.classList.remove('boardFlipped');
board.resize(game.board);
board.startSpaces = game.startSpaces;
board.refresh();
diff --git a/TableturfBattleClient/tableturf.css b/TableturfBattleClient/tableturf.css
index e0bf5ec..ae8332a 100644
--- a/TableturfBattleClient/tableturf.css
+++ b/TableturfBattleClient/tableturf.css
@@ -450,14 +450,21 @@ dialog::backdrop {
grid-row: player-row-3;
}
+#gamePage.boardFlipped .playerBar[data-index="0"] { grid-row: player-row-1; }
+#gamePage.boardFlipped .playerBar[data-index="1"] { grid-row: player-row-0; }
+#gamePage.boardFlipped .playerBar[data-index="2"] { grid-row: player-row-3; }
+#gamePage.boardFlipped .playerBar[data-index="3"] { grid-row: player-row-2; }
+
.playerBar {
grid-column: 1 / span 2;
margin: 0 10px;
border-left: 8px solid var(--colour);
padding-left: 10px;
}
-#gamePage:not([data-players="2"]) .playerBar:is([data-index="1"], [data-index="2"]) {
+#gamePage:not(.boardFlipped):not([data-players="2"]) .playerBar:is([data-index="1"], [data-index="2"]),
+#gamePage.boardFlipped:not([data-players="2"]) .playerBar:is([data-index="0"], [data-index="3"]) {
text-align: right;
+ border-left: none;
border-right: 8px solid var(--colour);
padding-right: 10px;
}
@@ -466,10 +473,17 @@ dialog::backdrop {
grid-column: score-column;
grid-row: 2 / -2;
display: flex;
+ flex-flow: column;
justify-content: center;
align-items: center;
+ gap: 2em;
}
+#gamePage.boardFlipped .pointsContainer[data-index="2"] { order: 0; }
+#gamePage.boardFlipped .pointsContainer[data-index="0"] { order: 1; }
+#gamePage.boardFlipped .pointsContainer[data-index="3"] { order: 2; }
+#gamePage.boardFlipped .pointsContainer[data-index="1"] { order: 3; }
+
#playControls, #resultContainer, #replayControls {
grid-column: 1 / span 2;
grid-row: hand-row;
@@ -557,11 +571,16 @@ dialog::backdrop {
.playContainer { text-align: center; }
-.playContainer[data-index="0"] { grid-column: play-column-1; grid-row: 2 / -1; align-self: end; }
-.playContainer[data-index="1"] { grid-column: play-column-2; grid-row: 1 / 4; align-self: start; }
.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; }
+.playContainer[data-index="0"] { grid-column: play-column-1; grid-row: 2 / -1; align-self: end; }
.playContainer[data-index="3"] { grid-column: play-column-1; grid-row: 1 / 4; align-self: start; }
+#gamePage.boardFlipped .playContainer[data-index="3"] { grid-column: play-column-2; grid-row: 2 / -1; align-self: end; }
+#gamePage.boardFlipped .playContainer[data-index="0"] { grid-column: play-column-2; grid-row: 1 / 4; align-self: start; }
+#gamePage.boardFlipped .playContainer[data-index="1"] { grid-column: play-column-1; grid-row: 2 / -1; align-self: end; }
+#gamePage.boardFlipped .playContainer[data-index="2"] { grid-column: play-column-1; grid-row: 1 / 4; align-self: start; }
+
.playContainer .card {
margin: 0;
}
@@ -734,7 +753,6 @@ dialog::backdrop {
.pointsContainer {
color: var(--colour);
- margin: 2em 0;
}
.pointsDelta {
@@ -1040,6 +1058,11 @@ dialog::backdrop {
.playerBar[data-index="2"] { grid-column: 1 / span 4; grid-row: 3; }
.playerBar[data-index="3"] { grid-column: 1 / span 4; grid-row: 1; }
+ #gamePage.boardFlipped .playerBar[data-index="3"] { grid-column: 1 / span 4; grid-row: 3; }
+ #gamePage.boardFlipped .playerBar[data-index="2"] { grid-column: 1 / span 4; grid-row: 1; }
+ #gamePage.boardFlipped .playerBar[data-index="1"] { grid-column: 1 / span 4; grid-row: 3; }
+ #gamePage.boardFlipped .playerBar[data-index="0"] { grid-column: 1 / span 4; grid-row: 1; }
+
#boardSection {
grid-column: 2 / -1;
grid-row: 2;
@@ -1096,17 +1119,27 @@ dialog::backdrop {
width: 30vw;
}
- .playContainer:is([data-index="0"], [data-index="1"], [data-index="2"], [data-index="3"]) {
+ #gamePage:is(.boardFlipped, *) .playContainer:is([data-index="0"], [data-index="1"], [data-index="2"], [data-index="3"]) {
font-size: x-small;
grid-column: 1 / -1;
+ left: auto;
+ top: auto;
z-index: -1;
display: flex;
}
.playContainer[data-index="0"] { grid-row: 2 / span 2; justify-content: start; margin: 0 0 2em 1em; }
- .playContainer[data-index="1"] { grid-row: 1 / span 2; justify-content: end; margin: 2em 1em 0 0; }
- #gamePage[data-players="2"] .playContainer[data-index="0"],
+ .playContainer[data-index="1"] { grid-row: 1 / span 2; justify-content: end; margin: 4em 1em 0 0; }
.playContainer[data-index="2"] { grid-row: 2 / span 2; justify-content: end; margin: 0 1em 2em 0; }
- .playContainer[data-index="3"] { grid-row: 1 / span 2; justify-content: start; margin: 2em 0 0 1em; }
+ .playContainer[data-index="3"] { grid-row: 1 / span 2; justify-content: start; margin: 4em 0 0 1em; }
+ #gamePage[data-players="2"] .playContainer[data-index="0"] { justify-content: end; margin: 0 1em 0 0; }
+ #gamePage[data-players="2"] .playContainer[data-index="1"] { justify-content: end; margin: 0 1em 0 0; }
+
+ #gamePage.boardFlipped .playContainer[data-index="1"] { grid-row: 2 / span 2; justify-content: start; margin: 0 0 2em 1em; }
+ #gamePage.boardFlipped .playContainer[data-index="0"] { grid-row: 1 / span 2; justify-content: end; margin: 2em 1em 0 0; }
+ #gamePage.boardFlipped .playContainer[data-index="3"] { grid-row: 2 / span 2; justify-content: end; margin: 0 1em 2em 0; }
+ #gamePage.boardFlipped .playContainer[data-index="2"] { grid-row: 1 / span 2; justify-content: start; margin: 2em 0 0 1em; }
+ #gamePage.boardFlipped[data-players="2"] .playContainer[data-index="0"] { justify-content: end; margin: 0 1em 0 0; }
+ #gamePage.boardFlipped[data-players="2"] .playContainer[data-index="1"] { justify-content: end; margin: 0 1em 0 0; }
#gameButtonsContainer {
grid-column: 1;