Skip to content

Commit

Permalink
Implement word bubble drawing (#36)
Browse files Browse the repository at this point in the history
  • Loading branch information
jcreedcmu committed Oct 15, 2023
1 parent e3a4dd3 commit ac50ff2
Show file tree
Hide file tree
Showing 5 changed files with 138 additions and 55 deletions.
30 changes: 1 addition & 29 deletions src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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;
Expand Down
35 changes: 20 additions & 15 deletions src/core/action.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,32 @@
import { ViewData } from '../app';
import { ViewData } from '../ui/ui-helpers';
import { Point } from '../util/types';
import { SceneState } from './state';

// There are UiActions, which might have different behavior depending
// 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;
96 changes: 85 additions & 11 deletions src/ui/instructions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -20,12 +31,30 @@ export function Instructions(props: { dispatch: Dispatch }): JSX.Element {
document.removeEventListener('mousedown', mouseDownListener);
}
});
return <span>Instructions go here</span>;


const state = exampleState();
const [cref, mc] = useCanvas<CanvasProps>(
{ main: state }, render, [state], ci => {
dispatch({ t: 'resize', vd: resizeView(ci.c) });
});

return <div>
<canvas
style={{ cursor: 'pointer' }}
ref={cref} />
</div>;
}

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: [
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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);
}

}
1 change: 1 addition & 0 deletions src/ui/render.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ export function paint(ci: CanvasInfo, state: GameState) {
}
d.restore();
}

export class RenderPane {
d: CanvasRenderingContext2D;
constructor(public c: HTMLCanvasElement) {
Expand Down
31 changes: 31 additions & 0 deletions src/ui/ui-helpers.ts
Original file line number Diff line number Diff line change
@@ -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 };
}

0 comments on commit ac50ff2

Please sign in to comment.