diff --git a/src/app.tsx b/src/app.tsx
index 119a18e..7c2ecfe 100644
--- a/src/app.tsx
+++ b/src/app.tsx
@@ -6,13 +6,11 @@ import { GameState, MouseState, SceneState, mkSceneState } from './core/state';
import { Instructions } from './ui/instructions';
import { key } from './ui/key';
import { paint } from './ui/render';
+import { resizeView } from './ui/ui-helpers';
import { CanvasInfo, useCanvas } from './ui/use-canvas';
import { useEffectfulReducer } from './ui/use-effectful-reducer';
-import { canvas_bds_in_canvas } from './ui/widget-helpers';
import { DEBUG } from './util/debug';
import { relpos } from './util/dutil';
-import { Point } from './util/types';
-import { vint } from './util/vutil';
export type GameProps = {
state: GameState,
@@ -146,33 +144,7 @@ function render(ci: CanvasInfo, props: CanvasProps) {
paint(ci, props.main);
}
-export type ViewData = {
- wsize: Point,
-};
-
-export function resizeView(c: HTMLCanvasElement): ViewData {
- const ratio = devicePixelRatio;
-
- const parent = c.parentElement?.getBoundingClientRect();
- const w = canvas_bds_in_canvas.sz.x;
- const h = canvas_bds_in_canvas.sz.y;
-
- c.width = w;
- c.height = h;
-
- const ow = w;
- const oh = h;
- c.width = ow * ratio;
- c.height = oh * ratio;
-
- c.style.width = ow + 'px';
- c.style.height = oh + 'px';
-
- const wsize = vint({ x: c.width / ratio, y: c.height / ratio });
-
- return { wsize };
-}
export function doEffect(state: SceneState, dispatch: (action: Action) => void, effect: Effect): void {
return;
diff --git a/src/core/action.ts b/src/core/action.ts
index cf42c17..8c83d1a 100644
--- a/src/core/action.ts
+++ b/src/core/action.ts
@@ -1,4 +1,4 @@
-import { ViewData } from '../app';
+import { ViewData } from '../ui/ui-helpers';
import { Point } from '../util/types';
import { SceneState } from './state';
@@ -6,22 +6,27 @@ import { SceneState } from './state';
// on view state, and other GameActions, which should be treated
// uniformly.
-export type GameAction = { t: 'none'; } |
- UiAction;
-// I think I want to migrate some of these up to GameAction
+export type GameAction =
+ | { t: 'none' }
+ | UiAction;
-export type UiAction = { t: 'key'; code: string; } |
-{ t: 'mouseDown'; button: number; p: Point; } |
-{ t: 'mouseUp'; p: Point; } |
-{ t: 'mouseMove'; p: Point; } |
-{ t: 'wheel'; p: Point; delta: number; } |
-{ t: 'repaint'; };
+// I think I want to migrate some of these up to GameAction
+export type UiAction =
+ | { t: 'key', code: string }
+ | { t: 'mouseDown', button: number, p: Point }
+ | { t: 'mouseUp', p: Point }
+ | { t: 'mouseMove', p: Point }
+ | { t: 'wheel', p: Point, delta: number }
+ | { t: 'repaint' }
+ ;
-export type Action = { t: 'resize'; vd: ViewData; } |
-{ t: 'newGame'; } |
-{ t: 'setSceneState'; state: SceneState; } |
- GameAction;
+export type Action =
+ | { t: 'resize', vd: ViewData }
+ | { t: 'newGame' }
+ | { t: 'setSceneState', state: SceneState }
+ | GameAction
+ ;
-export type Effect = { t: 'none'; };
+export type Effect = { t: 'none' };
export type Dispatch = (action: Action) => void;
diff --git a/src/ui/instructions.tsx b/src/ui/instructions.tsx
index 837fec9..1d43f75 100644
--- a/src/ui/instructions.tsx
+++ b/src/ui/instructions.tsx
@@ -2,9 +2,20 @@ import * as React from 'react';
import { useEffect } from 'react';
import { Dispatch } from '../core/action';
import { bonusGenerator } from '../core/bonus';
-import { mkGridOf } from '../core/grid';
+import { checkConnected, mkGridOf } from '../core/grid';
import { mkLayer } from '../core/layer';
-import { SceneState } from '../core/state';
+import { GameState, SceneState } from '../core/state';
+import { paint } from './render';
+import { resizeView } from './ui-helpers';
+import { CanvasInfo, useCanvas } from './use-canvas';
+import { checkValid } from '../core/state-helpers';
+import { PANIC_INTERVAL_MS } from '../core/clock';
+import { Point } from '../util/types';
+
+export type ForRenderState = GameState;
+type CanvasProps = {
+ main: ForRenderState,
+};
export function Instructions(props: { dispatch: Dispatch }): JSX.Element {
const { dispatch } = props;
@@ -20,12 +31,30 @@ export function Instructions(props: { dispatch: Dispatch }): JSX.Element {
document.removeEventListener('mousedown', mouseDownListener);
}
});
- return Instructions go here;
+
+
+ const state = exampleState();
+ const [cref, mc] = useCanvas(
+ { main: state }, render, [state], ci => {
+ dispatch({ t: 'resize', vd: resizeView(ci.c) });
+ });
+
+ return
+
+
;
+}
+
+function render(ci: CanvasInfo, props: CanvasProps) {
+ paint(ci, props.main);
+
+ drawBubble(ci, `This is the origin.\nAll tiles must connect here, and\nthe tile cannot be moved once placed.`,
+ { x: 150, y: 100 }, { x: 70, y: 230 });
}
-const state: SceneState = {
- t: "game",
- gameState: {
+function exampleState(): GameState {
+ const state: GameState = {
invalidWords: [],
connectedSet: mkGridOf([]),
energies: [
@@ -104,7 +133,11 @@ const state: SceneState = {
{ letter: "j", p_in_world_int: { x: 6, y: 6 }, used: true },
{ letter: "o", p_in_world_int: { x: 7, y: 6 }, used: true }
],
- hand_tiles: [],
+ hand_tiles: [
+ { letter: "e", p_in_world_int: { x: 0, y: 0 }, used: true },
+ { letter: "t", p_in_world_int: { x: 0, y: 1 }, used: true },
+ { letter: "a", p_in_world_int: { x: 0, y: 2 }, used: true },
+ ],
canvas_from_world: {
scale: {
x: 39.6694214876033,
@@ -135,7 +168,48 @@ const state: SceneState = {
}
},
score: 7,
- panic: undefined,
- },
- revision: 0,
-};
+ panic: { currentTime: Date.now(), lastClear: Date.now() - PANIC_INTERVAL_MS / 3 },
+ };
+
+ return checkValid(state);
+}
+
+function drawBubble(ci: CanvasInfo, text: string, textCenter: Point, coneApex: Point): void {
+ const { d } = ci;
+ const fontSize = 12;
+ const lines = text.split('\n');
+ d.font = `${fontSize}px sans-serif`;
+ const maxWidth = Math.max(...lines.map(line => d.measureText(line).width));
+ const MARGIN = 8;
+ const RADIUS = 5;
+
+ function bubble(color: string, thick: number): void {
+ d.fillStyle = color;
+ d.strokeStyle = color;
+ d.lineWidth = thick;
+ d.beginPath();
+
+ d.roundRect(textCenter.x - maxWidth / 2 - MARGIN, textCenter.y - fontSize / 2 - MARGIN,
+ maxWidth + MARGIN * 2, fontSize * lines.length + MARGIN * 2, RADIUS);
+
+ d.moveTo(textCenter.x - 10, textCenter.y);
+ d.lineTo(textCenter.x + 10, textCenter.y);
+ d.lineTo(coneApex.x, coneApex.y);
+ d.lineTo(textCenter.x - 10, textCenter.y);
+
+ d.fill();
+ if (thick != 0)
+ d.stroke();
+ }
+ bubble('black', 2);
+ bubble('white', 0);
+
+ d.fillStyle = 'black';
+ d.textAlign = 'center';
+ d.textBaseline = 'middle';
+
+ for (let i = 0; i < lines.length; i++) {
+ d.fillText(lines[i], textCenter.x, textCenter.y + i * fontSize);
+ }
+
+}
diff --git a/src/ui/render.ts b/src/ui/render.ts
index 363b199..465e6ab 100644
--- a/src/ui/render.ts
+++ b/src/ui/render.ts
@@ -150,6 +150,7 @@ export function paint(ci: CanvasInfo, state: GameState) {
}
d.restore();
}
+
export class RenderPane {
d: CanvasRenderingContext2D;
constructor(public c: HTMLCanvasElement) {
diff --git a/src/ui/ui-helpers.ts b/src/ui/ui-helpers.ts
new file mode 100644
index 0000000..49afbd2
--- /dev/null
+++ b/src/ui/ui-helpers.ts
@@ -0,0 +1,31 @@
+import { canvas_bds_in_canvas } from '../ui/widget-helpers';
+import { Point } from '../util/types';
+import { vint } from '../util/vutil';
+
+export type ViewData = {
+ wsize: Point,
+};
+
+export function resizeView(c: HTMLCanvasElement): ViewData {
+ const ratio = devicePixelRatio;
+
+ const parent = c.parentElement?.getBoundingClientRect();
+ const w = canvas_bds_in_canvas.sz.x;
+ const h = canvas_bds_in_canvas.sz.y;
+
+ c.width = w;
+ c.height = h;
+
+ const ow = w;
+ const oh = h;
+
+ c.width = ow * ratio;
+ c.height = oh * ratio;
+
+ c.style.width = ow + 'px';
+ c.style.height = oh + 'px';
+
+ const wsize = vint({ x: c.width / ratio, y: c.height / ratio });
+
+ return { wsize };
+}