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 }; +}