);
}
diff --git a/src/components/BoardCells.tsx b/src/components/BoardCells.tsx
index 6f945c7..d87fc85 100644
--- a/src/components/BoardCells.tsx
+++ b/src/components/BoardCells.tsx
@@ -1,6 +1,14 @@
import * as React from "react";
import { BoardCell } from "../util/BoardCell";
-import { EMPTY } from "../setup";
+import {
+ BOARD_CELL_COLOR,
+ BOARD_CELL_TEXT_COLOR,
+ EMPTY,
+ EMPTY_CELL_COLOR,
+ MATCH_COLOR,
+ MATCH_TEXT_COLOR,
+ NORMAL_TEXT_SIZE,
+} from "../setup";
export const BoardCells = React.memo(
({ boardCellMatrix }: { boardCellMatrix: BoardCell[][] }) => {
@@ -8,26 +16,32 @@ export const BoardCells = React.memo(
row.map((cell, c) => {
const bg = () => {
if (cell.char === EMPTY) {
- return "none";
+ return EMPTY_CELL_COLOR;
} else if (cell.hasMatched) {
- return "lightgreen";
+ return MATCH_COLOR;
} else {
- return "red";
+ return BOARD_CELL_COLOR;
+ }
+ };
+ const textColor = () => {
+ if (cell.hasMatched) {
+ return MATCH_TEXT_COLOR;
+ } else {
+ return BOARD_CELL_TEXT_COLOR;
}
};
const divStyle = {
- width: "auto",
- text: cell.char === EMPTY ? "none" : "red",
- border: "2px solid",
gridRow: r + 1,
gridColumn: c + 1,
- textAlign: "center",
background: bg(),
+ color: textColor(),
+ fontSize: NORMAL_TEXT_SIZE,
} as const;
return (
{cell.char}
diff --git a/src/components/CountdownOverlay.tsx b/src/components/CountdownOverlay.tsx
index 61b519d..8168b04 100644
--- a/src/components/CountdownOverlay.tsx
+++ b/src/components/CountdownOverlay.tsx
@@ -1,4 +1,5 @@
import * as React from "react";
+import { BOARD_CELL_COLOR, LARGE_TEXT_SIZE, MENU_TEXT_COLOR } from "../setup";
export const CountdownOverlay = React.memo(
(
@@ -14,8 +15,10 @@ export const CountdownOverlay = React.memo(
left: "50%",
transform: "translate(-50%, -50%)",
zIndex: 2,
- color: "red",
- fontSize: "200%",
+ color: MENU_TEXT_COLOR,
+ fontSize: "13vmin",
+ WebkitTextStroke: "0.2vmin",
+ WebkitTextStrokeColor: BOARD_CELL_COLOR,
} as const;
return (
diff --git a/src/components/FallingBlock.tsx b/src/components/FallingBlock.tsx
new file mode 100644
index 0000000..284efa3
--- /dev/null
+++ b/src/components/FallingBlock.tsx
@@ -0,0 +1,77 @@
+import * as React from "react";
+import { animated, useSpring } from "react-spring";
+import { BoardCell } from "../util/BoardCell";
+import { BOARD_CELL_TEXT_COLOR, NORMAL_TEXT_SIZE } from "../setup";
+
+export const FallingBlock = React.memo(
+ (
+ { fallingLetters, durationRate, color }: {
+ fallingLetters: BoardCell[];
+ durationRate: number;
+ color: string;
+ },
+ ) => {
+ const fallenLetters = fallingLetters
+ .map((fallingLetterBeforeAndAfter) => (
+
+ ));
+ return <>{fallenLetters}>;
+ },
+);
+
+const FallingLetter = React.memo(
+ (
+ { fallingLetterBeforeAndAfter, durationRate, color }: {
+ fallingLetterBeforeAndAfter: BoardCell[];
+ durationRate: number;
+ color: string;
+ },
+ ) => {
+ console.assert(fallingLetterBeforeAndAfter.length == 2);
+ const [before, after] = fallingLetterBeforeAndAfter;
+ const margin = 100 * Math.abs(after.r - before.r);
+
+ const styles = useSpring({
+ from: {
+ gridRow: before.r + 1,
+ gridColumn: before.c + 1,
+ zIndex: 5,
+ marginTop: "0%",
+ marginBottom: "0%",
+ },
+ to: {
+ marginTop: `${margin}%`,
+ marginBottom: `-${margin}%`,
+ },
+ reset: true,
+ config: {
+ duration: durationRate * (after.r - before.r),
+ },
+ });
+
+ const innerStyle = {
+ height: "88%",
+ background: color,
+ color: BOARD_CELL_TEXT_COLOR,
+ fontSize: NORMAL_TEXT_SIZE,
+ } as const;
+
+ return (
+
+
+ {before.char}
+
+
+ );
+ },
+);
diff --git a/src/components/GameOverOverlay.tsx b/src/components/GameOverOverlay.tsx
index aa86664..61174b9 100644
--- a/src/components/GameOverOverlay.tsx
+++ b/src/components/GameOverOverlay.tsx
@@ -1,5 +1,11 @@
import * as React from "react";
import { ReactNode } from "react";
+import {
+ MENU_TEXT_COLOR,
+ NORMAL_TEXT_SIZE,
+ PLAYER_COLOR,
+ UNIVERSAL_BORDER_RADIUS,
+} from "../setup";
export const GameOverOverlay = React.memo(
(
@@ -11,11 +17,12 @@ export const GameOverOverlay = React.memo(
const divStyle = {
visibility: isVisible ? "visible" as const : "hidden" as const,
position: "absolute",
- top: "35%",
+ top: "50%",
left: "50%",
- transform: "translate(-25%, -25%)",
+ whiteSpace: "nowrap",
+ transform: "translate(-50%, -50%)",
zIndex: 2,
- color: "red",
+ color: MENU_TEXT_COLOR,
fontSize: "200%",
} as const;
return (
@@ -33,17 +40,24 @@ export const PlayAgainButton = React.memo(
const buttonStyle = {
cursor: "pointer",
border: "none",
- display: "inline-block",
+ background: PLAYER_COLOR,
+ color: MENU_TEXT_COLOR,
+ borderRadius: UNIVERSAL_BORDER_RADIUS,
+ padding: "0.4vmin",
+ textAlign: "center",
+ marginTop: "0.4vmin",
+ fontSize: NORMAL_TEXT_SIZE,
};
return (
-
+
);
},
);
diff --git a/src/components/Header.tsx b/src/components/Header.tsx
new file mode 100644
index 0000000..edaf830
--- /dev/null
+++ b/src/components/Header.tsx
@@ -0,0 +1,42 @@
+import * as React from "react";
+import { BOARD_CELL_COLOR } from "../setup";
+
+export const Header = React.memo(() => {
+ const style = {
+ zIndex: 20,
+ } as const;
+
+ return (
+
+
+
+ );
+});
+
+export const Title = React.memo(() => {
+ const containerStyle = {
+ marginTop: "3vmin",
+ marginLeft: "2vmin",
+ zIndex: 20,
+ } as const;
+
+ const textStyle = {
+ fontSize: "30px",
+ textTransform: "uppercase",
+ fontWeight: "bolder",
+ color: BOARD_CELL_COLOR,
+ padding: "10px",
+ fontFamily: `"Press Start 2P"`,
+ } as const;
+
+ return (
+
+ );
+});
diff --git a/src/components/PlayerBlock.tsx b/src/components/PlayerBlock.tsx
index 3b8e476..c5e73b0 100644
--- a/src/components/PlayerBlock.tsx
+++ b/src/components/PlayerBlock.tsx
@@ -1,5 +1,13 @@
import * as React from "react";
-import { _ENABLE_UP_KEY, ENABLE_SMOOTH_FALL, interp } from "../setup";
+import {
+ _ENABLE_UP_KEY,
+ BOARD_CELL_TEXT_COLOR,
+ ENABLE_SMOOTH_FALL,
+ interp,
+ NORMAL_TEXT_SIZE,
+ PLAYER_COLOR,
+ UNIVERSAL_BORDER_RADIUS,
+} from "../setup";
import { UserCell } from "../util/UserCell";
export const PlayerBlock = React.memo(
@@ -9,26 +17,28 @@ export const PlayerBlock = React.memo(
adjustedCells: UserCell[];
},
) => {
- // This function contains player information.
-
const adjustedCellsStyled = adjustedCells.map((cell) => {
- const margin = ENABLE_SMOOTH_FALL ? interp.val : 0;
const divStyle = {
- background: "lightblue",
- border: 2,
- borderStyle: "solid",
+ background: PLAYER_COLOR,
+ color: BOARD_CELL_TEXT_COLOR,
+ fontSize: NORMAL_TEXT_SIZE,
gridRow: cell.r + 1,
gridColumn: cell.c + 1,
- display: "flex",
- marginTop: `${margin}%`,
- marginBottom: `${-margin}%`,
- justifyContent: "center",
+ marginTop: ENABLE_SMOOTH_FALL
+ ? `${interp.val + UNIVERSAL_BORDER_RADIUS}%`
+ : "0.4vmin",
+ marginBottom: ENABLE_SMOOTH_FALL
+ ? `${-interp.val - UNIVERSAL_BORDER_RADIUS}%`
+ : "0.4vmin",
+ marginLeft: "0.4vmin",
+ marginRight: "0.4vmin",
visibility: isVisible ? "visible" as const : "hidden" as const,
zIndex: 1,
};
return (
{cell.char}
diff --git a/src/components/Prompt.tsx b/src/components/Prompt.tsx
new file mode 100644
index 0000000..6199b9e
--- /dev/null
+++ b/src/components/Prompt.tsx
@@ -0,0 +1,35 @@
+import * as React from "react";
+import { ReactNode } from "react";
+import { BOARD_CELL_COLOR, SMALL_TEXT_SIZE } from "../setup";
+
+export const Prompt = React.memo(({ children }: { children: ReactNode }) => {
+ // To align `
` above the game.
+ const promptContainerStyle = {
+ flexDirection: "column",
+ display: "flex",
+ } as const;
+
+ const promptSize = SMALL_TEXT_SIZE;
+ const paddingSize = SMALL_TEXT_SIZE;
+
+ const promptStyle = {
+ textAlign: "center",
+ fontSize: promptSize,
+ paddingBottom: paddingSize,
+ color: BOARD_CELL_COLOR,
+ } as const;
+
+ // This div allows the children to stay centered in `
`'s parent.
+ const counterBalanceStyle = {
+ height: promptSize,
+ paddingBottom: paddingSize,
+ } as const;
+
+ return (
+
+
Create words of 3+ letters
+ {children}
+
+
+ );
+});
diff --git a/src/components/WordList.tsx b/src/components/WordList.tsx
index 3dd1528..9cd489d 100644
--- a/src/components/WordList.tsx
+++ b/src/components/WordList.tsx
@@ -1,14 +1,31 @@
import * as React from "react";
+import {
+ BOARD_CELL_COLOR,
+ MENU_TEXT_COLOR,
+ NORMAL_TEXT_SIZE,
+ PLAYER_COLOR,
+ SMALL_TEXT_SIZE,
+ UNIVERSAL_BORDER_RADIUS,
+} from "../setup";
export const WordList = React.memo(
({ displayedWords }: { displayedWords: string[] }) => {
const wordStyle = {
- background: "yellow",
+ background: BOARD_CELL_COLOR,
+ padding: UNIVERSAL_BORDER_RADIUS,
+ margin: UNIVERSAL_BORDER_RADIUS,
+ borderRadius: UNIVERSAL_BORDER_RADIUS,
+ fontSize: SMALL_TEXT_SIZE,
+ fontStyle: "italic",
} as const;
const outerStyle = {
display: "flex",
flexDirection: "column",
+ color: MENU_TEXT_COLOR,
+ paddingLeft: UNIVERSAL_BORDER_RADIUS,
+ paddingRight: UNIVERSAL_BORDER_RADIUS,
+ marginBottom: UNIVERSAL_BORDER_RADIUS,
} as const;
const scrollBoxStyle = {
@@ -17,15 +34,32 @@ export const WordList = React.memo(
height: "0px",
} as const;
+ const titleStyle = {
+ color: BOARD_CELL_COLOR,
+ fontSize: NORMAL_TEXT_SIZE,
+ } as const;
+
+ const pointsStyle = {
+ color: PLAYER_COLOR,
+ fontSize: NORMAL_TEXT_SIZE,
+ } as const;
+
return (
- Matched Words ({displayedWords.length})
+
+ MATCHES [
+
+ {displayedWords.length}
+
+ ]
+
<>
{displayedWords.map((word, i) => (
// Invert the key to keep scroll bar at bottom if set to bottom.
{word}
diff --git a/src/fonts/PressStart2P-Regular.ttf b/src/fonts/PressStart2P-Regular.ttf
new file mode 100644
index 0000000..2442aff
Binary files /dev/null and b/src/fonts/PressStart2P-Regular.ttf differ
diff --git a/src/index.tsx b/src/index.tsx
index 16b65b0..6f95f57 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -1,4 +1,4 @@
-import 'react-app-polyfill/stable';
+import "react-app-polyfill/stable";
import * as React from "react";
import { Suspense } from "react";
import * as ReactDOM from "react-dom/client";
diff --git a/src/setup.ts b/src/setup.ts
index 9f53064..620d6de 100644
--- a/src/setup.ts
+++ b/src/setup.ts
@@ -26,3 +26,22 @@ export const lockMax = 1500;
export const matchAnimLength = 750;
export const groundExitPenaltyRate = 250;
export const countdownTotalSecs = 3;
+
+export const boardCellFallDurationMillisecondsRate = 75;
+export const playerCellFallDurationMillisecondsRate = 10;
+
+export const FONT_COLOR = "#FFFFFF";
+export const BOARD_COLOR = "#FDEDD8";
+export const EMPTY_CELL_COLOR = "#F4A261";
+export const BOARD_CELL_COLOR = "#2B5F8C";
+export const PLAYER_COLOR = "#499F68";
+export const MATCH_COLOR = "#FFEA00";
+export const MATCH_TEXT_COLOR = BOARD_CELL_COLOR;
+export const MENU_TEXT_COLOR = "#FFFFFF";
+export const BOARD_CELL_TEXT_COLOR = "#EBF5EE";
+
+export const UNIVERSAL_BORDER_RADIUS = "1vmin";
+export const CELL_SIZE = "7vmin";
+export const LARGE_TEXT_SIZE = "7vmin";
+export const NORMAL_TEXT_SIZE = "3.5vmin";
+export const SMALL_TEXT_SIZE = "3.0vmin";
diff --git a/src/util/playerUtil.tsx b/src/util/playerUtil.tsx
index c608a98..ce7cfa7 100644
--- a/src/util/playerUtil.tsx
+++ b/src/util/playerUtil.tsx
@@ -13,7 +13,7 @@ import { UserCell } from "./UserCell";
import { BoardCell } from "./BoardCell";
import { getGroundHeight } from "./boardUtil";
-export const spawnPos: readonly [number, number] = [1, 3];
+export const spawnPos: readonly [number, number] = [1, 4];
export const layout = [
[EMPTY, EMPTY, EMPTY, EMPTY, EMPTY],
[EMPTY, EMPTY, TBD, EMPTY, EMPTY],
@@ -139,14 +139,14 @@ export function isPlayerTouchingGround(
export function dropFloatingCells(
board: BoardCell[][],
-): [BoardCell[][], [number, number][], [number, number][]] {
- // Returns an array of 3 arrays:
- // Array 1: The resulting board with drops.
- // Array 2: The array for the coords of the floating cells, post-drop.
- // Array 3: Array for the old coords of the floating cells.
+): {
+ boardWithoutFallCells: BoardCell[][];
+ postFallCells: BoardCell[];
+ preFallCells: BoardCell[];
+} {
const newBoard = board.slice();
- const added: [number, number][] = [];
- const removed: [number, number][] = [];
+ const postFallCells: BoardCell[] = [];
+ const preFallCells: BoardCell[] = [];
for (let r = BOARD_ROWS - 2; r >= 0; --r) {
for (let c = BOARD_COLS - 1; c >= 0; --c) {
if (
@@ -154,13 +154,19 @@ export function dropFloatingCells(
newBoard[r + 1][c].char === EMPTY
) {
const g = getGroundHeight(c, r, newBoard);
- newBoard[g][c].char = newBoard[r][c].char;
+ const char = newBoard[r][c].char;
+ newBoard[g][c].char = char;
newBoard[r][c].char = EMPTY;
// Update cell in placedCells.
- added.push([g, c]);
- removed.push([r, c]);
+ postFallCells.push({ r: g, c, char, hasMatched: false });
+ preFallCells.push({ r, c, char, hasMatched: false });
}
}
}
- return [newBoard, added, removed];
+ // Remove chars here, since the iteration logic above depends on changes.
+ const boardWithoutFallCells = newBoard;
+ postFallCells.forEach((cell) =>
+ boardWithoutFallCells[cell.r][cell.c].char = EMPTY
+ );
+ return { boardWithoutFallCells, postFallCells, preFallCells };
}