Skip to content

Commit

Permalink
Implement mouse gesture for swapping tiles (fix #13)
Browse files Browse the repository at this point in the history
  • Loading branch information
jcreedcmu committed Nov 22, 2023
1 parent f18ca53 commit 8c0d477
Show file tree
Hide file tree
Showing 6 changed files with 90 additions and 27 deletions.
34 changes: 27 additions & 7 deletions src/core/intent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,15 @@ import { SelectionOperation, selectionOperationOfMods } from './selection';
import { vacuous_down, deselect } from './reduce';

export type KillIntent =
| { t: 'kill'; radius: number; cost: number; }
| { t: 'bomb'; };
| { t: 'kill', radius: number, cost: number }
| { t: 'bomb' };

export type Intent =
| { t: 'dragTile'; id: string; }
| { t: 'vacuous'; }
| { t: 'panWorld'; }
| { t: 'startSelection'; opn: SelectionOperation; }
| { t: 'dragTile', id: string }
| { t: 'vacuous' }
| { t: 'panWorld' }
| { t: 'exchangeTiles', id: string }
| { t: 'startSelection', opn: SelectionOperation }
| KillIntent
;

Expand All @@ -28,7 +29,12 @@ export function getIntentOfMouseDown(tool: Tool, wp: WidgetPoint, button: number
if (hoverTile) {
if (pinned)
return { t: 'panWorld' };
return { t: 'dragTile', id: hoverTile.id };
if (mods.has('meta')) {
return { t: 'exchangeTiles', id: hoverTile.id };
}
else {
return { t: 'dragTile', id: hoverTile.id };
}
}
return { t: 'startSelection', opn: selectionOperationOfMods(mods) };
case 'hand': return { t: 'panWorld' };
Expand Down Expand Up @@ -67,6 +73,20 @@ export function reduceIntent(state: GameState, intent: Intent, wp: WidgetPoint):
p_in_canvas: wp.p_in_canvas,
};
});
case 'exchangeTiles': {
// FIXME: only works for world tiles right now
if (wp.t != 'world') return vacuous_down(state, wp);
const p_in_world_int = vm(wp.p_in_local, Math.floor);
return produce(state, s => {
s.mouseState = {
t: 'exchange_tiles',
orig_loc: { t: 'world', p_in_world_int },
id: intent.id,
orig_p_in_canvas: wp.p_in_canvas,
p_in_canvas: wp.p_in_canvas,
};
});
}
case 'vacuous': return vacuous_down(state, wp);
case 'panWorld':
if (wp.t != 'world') return vacuous_down(state, wp);
Expand Down
42 changes: 26 additions & 16 deletions src/core/reduce.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,20 @@ import { WidgetPoint, canvas_from_hand, getWidgetPoint } from '../ui/widget-help
import { debugTiles } from '../util/debug';
import { produce } from '../util/produce';
import { SE2, apply, compose, composen, inverse, scale, translate } from '../util/se2';
import { apply_to_rect } from '../util/se2-extra';
import { Point } from '../util/types';
import { boundRect, getRandomOrder, pointInRect } from '../util/util';
import { vadd, vequal, vm, vscale, vsub } from '../util/vutil';
import { getRandomOrder } from '../util/util';
import { vm, vscale, vsub } from '../util/vutil';
import { Action, Effect, GameAction } from './action';
import { getBonusLayer } from './bonus';
import { getPanicFraction, now_in_game } from './clock';
import { mkOverlay, mkOverlayFrom, setOverlay } from './layer';
import { GameState, HAND_TILE_LIMIT, Location, SceneState, TileEntity, mkGameSceneState } from './state';
import { MoveTile, addWorldTiles, bonusOfStatePoint, checkValid, drawOfState, filterExpiredAnimations, isCollision, isOccupied, isTilePinned, unpauseState } from './state-helpers';
import { getIntentOfMouseDown, reduceIntent } from './intent';
import { tryKillTileOfState } from './kill-helpers';
import { getTileId, get_hand_tiles, get_main_tiles, get_tiles, putTileInHand, putTileInWorld, putTilesInHand, removeAllTiles, setTileLoc } from "./tile-helpers";
import { bombIntent, dynamiteIntent, getCurrentTool, reduceToolSelect } from './tools';
import { mkOverlayFrom } from './layer';
import { resolveSelection } from './selection';
import { getIntentOfMouseDown, reduceIntent } from './intent';
import { GameState, HAND_TILE_LIMIT, Location, SceneState, mkGameSceneState } from './state';
import { MoveTile, addWorldTiles, bonusOfStatePoint, checkValid, drawOfState, filterExpiredAnimations, isCollision, isOccupied, isTilePinned, unpauseState } from './state-helpers';
import { getTileId, get_hand_tiles, get_tiles, putTileInHand, putTileInWorld, putTilesInHand, removeAllTiles, setTileLoc, tileAtPoint } from "./tile-helpers";
import { bombIntent, dynamiteIntent, getCurrentTool, reduceToolSelect } from './tools';

function resolveMouseup(state: GameState): GameState {
// FIXME: Setting the mouse state to up *before* calling
Expand Down Expand Up @@ -141,6 +140,23 @@ function resolveMouseupInner(state: GameState): GameState {
return state;
}
}
case 'exchange_tiles': {
const wp = getWidgetPoint(state, ms.p_in_canvas);
if (wp.t != 'world')
return state;
const id0 = ms.id;
const loc0 = getTileId(state.coreState, id0).loc;
const tile = tileAtPoint(state, wp.p_in_local);
if (tile == undefined)
return state;
const id1 = tile.id;
const loc1 = getTileId(state.coreState, id1).loc;
return checkValid(produce(state, s => {
setTileLoc(s, id0, loc1);
setTileLoc(s, id1, loc0);
}));
}

case 'up': return state; // no drag to resolve
case 'down': return state;
}
Expand All @@ -156,14 +172,8 @@ export function vacuous_down(state: GameState, wp: WidgetPoint): GameState {
}

function reduceMouseDownInWorld(state: GameState, wp: WidgetPoint & { t: 'world' }, button: number, mods: Set<string>): GameState {
let hoverTile: TileEntity | undefined = undefined;
const hoverTile = tileAtPoint(state, wp.p_in_local);
const p_in_world_int = vm(wp.p_in_local, Math.floor);
for (const tile of get_main_tiles(state.coreState)) {
if (vequal(p_in_world_int, tile.loc.p_in_world_int)) {
hoverTile = tile;
break;
}
}
const hoverBlock = bonusOfStatePoint(state.coreState, p_in_world_int).t == 'block';
let pinned =
(hoverTile && hoverTile.loc.t == 'world') ? isTilePinned(state, hoverTile.id, hoverTile.loc) : false;
Expand Down
1 change: 1 addition & 0 deletions src/core/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export type MouseState =
| { t: 'down', p_in_canvas: Point }
| { t: 'drag_world', orig_p: Point, p_in_canvas: Point }
| { t: 'drag_selection', orig_p: Point, p_in_canvas: Point, opn: SelectionOperation }
| { t: 'exchange_tiles', orig_loc: Location, orig_p_in_canvas: Point, p_in_canvas: Point, id: string }
| { t: 'drag_tile', orig_loc: Location, orig_p_in_canvas: Point, p_in_canvas: Point, id: string }
;

Expand Down
13 changes: 12 additions & 1 deletion src/core/tile-helpers.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Draft } from "immer";
import { produce } from "../util/produce";
import { Point } from "../util/types";
import { vequal, vm } from "../util/vutil";
import { CoreState, GameState, HandTile, Location, MainTile, Tile, TileEntity, TileEntityOptionalId, TileOptionalId } from "./state";
import { getOverlay } from "./layer";

// FIXME: global counter
let tileIdCounter = 1000;
Expand Down Expand Up @@ -166,3 +166,14 @@ export function isSelectedForDrag(state: GameState, tile: TileEntity): boolean {
return state.mouseState.id == tile.id || tile.loc.t == 'world' && state.coreState.selected.selectedIds.includes(tile.id);
}
}

export function tileAtPoint(state: GameState, p_in_world: Point): TileEntity | undefined {
let hoverTile: TileEntity | undefined = undefined;
const p_in_world_int = vm(p_in_world, Math.floor);
for (const tile of get_main_tiles(state.coreState)) {
if (vequal(p_in_world_int, tile.loc.p_in_world_int)) {
return tile;
}
}
return undefined;
}
19 changes: 16 additions & 3 deletions src/ui/render.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { GameState, TileEntity } from "../core/state";
import { bonusOfStatePoint } from "../core/state-helpers";
import { getTileId, get_hand_tiles, get_main_tiles, isSelectedForDrag } from "../core/tile-helpers";
import { getCurrentTool, getCurrentTools, rectOfTool } from "../core/tools";
import { drawImage, fillRect, fillText, pathRectCircle, strokeRect } from "../util/dutil";
import { drawImage, fillRect, fillText, lineTo, moveTo, pathRectCircle, strokeRect } from "../util/dutil";
import { SE2, apply, compose, inverse, translate } from '../util/se2';
import { apply_to_rect } from "../util/se2-extra";
import { Point, Rect } from "../util/types";
Expand All @@ -18,6 +18,8 @@ import { CanvasInfo } from "./use-canvas";
import { canvas_from_drag_tile, pan_canvas_from_world_of_state } from "./view-helpers";
import { canvas_bds_in_canvas, canvas_from_hand, canvas_from_toolbar, hand_bds_in_canvas, pause_button_bds_in_canvas, shuffle_button_bds_in_canvas, toolbar_bds_in_canvas, world_bds_in_canvas } from "./widget-helpers";

const interfaceCyan = 'rgb(0,255,255,0.5)';

export function paintWithScale(ci: CanvasInfo, state: GameState) {
const { d } = ci;
d.save();
Expand Down Expand Up @@ -218,7 +220,18 @@ export function rawPaint(ci: CanvasInfo, state: GameState) {
}

function drawOtherUi() {
// draw dragged tile on top

// draw exchange guide
if (ms.t == 'exchange_tiles') {
d.strokeStyle = interfaceCyan;
d.lineWidth = 2;
d.beginPath();
moveTo(d, ms.orig_p_in_canvas);
lineTo(d, ms.p_in_canvas);
d.stroke();
}

// draw dragged tile
if (ms.t == 'drag_tile') {
if (cs.selected) {
const tile0 = getTileId(state.coreState, ms.id);
Expand Down Expand Up @@ -275,7 +288,7 @@ export function rawPaint(ci: CanvasInfo, state: GameState) {
// draw selection
if (ms.t == 'drag_selection') {
const sel_rect_in_canvas: Rect = boundRect([ms.orig_p, ms.p_in_canvas]);
d.strokeStyle = "rgb(0,255,255,0.5)";
d.strokeStyle = interfaceCyan;
d.lineWidth = 2;
d.strokeRect(
sel_rect_in_canvas.p.x, sel_rect_in_canvas.p.y,
Expand Down
8 changes: 8 additions & 0 deletions src/util/dutil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,11 @@ export function pathRectCircle(d: CanvasRenderingContext2D, rect: Rect) {
0, 360,
);
}

export function moveTo(d: CanvasRenderingContext2D, p: Point) {
d.moveTo(p.x, p.y);
}

export function lineTo(d: CanvasRenderingContext2D, p: Point) {
d.lineTo(p.x, p.y);
}

0 comments on commit 8c0d477

Please sign in to comment.