Skip to content

Commit

Permalink
Implement bulk move of tiles (fix #12)
Browse files Browse the repository at this point in the history
  • Loading branch information
jcreedcmu committed Oct 21, 2023
1 parent fcc012f commit f401664
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 21 deletions.
12 changes: 12 additions & 0 deletions src/core/layer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,15 @@ export function overlayForEach<T>(layer: Overlay<T>, kont: (p: Point) => void):
kont(parseCoord(k));
});
}

export function overlayPoints<T>(layer: Overlay<T>): Point[] {
return Object.keys(layer.cells).map(k => parseCoord(k));
}

export function mkOverlayFrom(points: Point[]): Overlay<boolean> {
const layer: Overlay<boolean> = mkOverlay();
points.forEach(p => {
setOverlay(layer, p, true);
});
return layer;
}
79 changes: 61 additions & 18 deletions src/core/reduce.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,17 @@ import { canvas_from_drag_tile, pan_canvas_from_canvas_of_mouse_state } from '..
import { WidgetPoint, canvas_from_hand, getWidgetPoint } from '../ui/widget-helpers';
import { debugTiles } from '../util/debug';
import { produce } from '../util/produce';
import { compose, composen, inverse, scale, translate } from '../util/se2';
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, pointInRect } from '../util/util';
import { vadd, vequal, vm, vscale, vsub } from '../util/vutil';
import { Action, Effect, GameAction } from './action';
import { getPanicFraction } from './clock';
import { Overlay, mkOverlay, setOverlay } from './layer';
import { GameState, SceneState, SelectionState, mkGameSceneState } from './state';
import { addWorldTiles, checkValid, drawOfState, isOccupied, killTileOfState } from './state-helpers';
import { get_hand_tiles, get_main_tiles, putTileInHand, putTileInWorld, removeAllTiles } from "./tile-helpers";
import { Overlay, mkOverlay, mkOverlayFrom, overlayPoints, setOverlay } from './layer';
import { GameState, Location, SceneState, SelectionState, mkGameSceneState } from './state';
import { addWorldTiles, checkValid, drawOfState, isCollision, isOccupied, killTileOfState } from './state-helpers';
import { getTileId, get_hand_tiles, get_main_tiles, get_tiles, putTileInHand, putTileInWorld, removeAllTiles, setTileLoc } from "./tile-helpers";

function resolveMouseup(state: GameState): GameState {
// FIXME: Setting the mouse state to up *before* calling
Expand Down Expand Up @@ -63,16 +63,59 @@ function resolveMouseupInner(state: GameState): GameState {
const wp = getWidgetPoint(state, ms.p_in_canvas);
if (wp.t == 'world') {

const new_tile_in_world_int: Point = vm(compose(
inverse(state.canvas_from_world),
canvas_from_drag_tile(state, state.mouseState)).translate,
Math.round);
const selected = state.selected;
if (selected) {

// FIXME: ensure the dragged tile is in the selection
const remainingTiles = get_tiles(state).filter(tile => !selected.selectedIds.includes(tile.id));

const new_tile_in_world_int: Point = vm(compose(
inverse(state.canvas_from_world),
canvas_from_drag_tile(state, ms)).translate,
Math.round);
const old_tile_loc: Location = ms.orig_loc;
if (old_tile_loc.t != 'world') {
console.error(`Unexpected non-world tile`);
return state;
}
const old_tile_in_world_t = old_tile_loc.p_in_world_int;
const new_tile_from_old_tile: SE2 = translate(vsub(new_tile_in_world_int, old_tile_in_world_t));

const moves: { id: string, p_in_world_int: Point }[] = selected.selectedIds.flatMap(id => {
const tile = getTileId(state, id);
const loc = tile.loc;
if (loc.t == 'world') {
return [{ id, p_in_world_int: apply(new_tile_from_old_tile, loc.p_in_world_int) }];
}
else return [];
});

const tgts = moves.map(x => x.p_in_world_int);
if (isCollision(remainingTiles, tgts)) {
return state;
}

const afterDrop = produce(state, s => {
moves.forEach(({ id, p_in_world_int }) => {
setTileLoc(s, id, { t: 'world', p_in_world_int });
});
s.selected = { overlay: mkOverlayFrom(tgts), selectedIds: selected.selectedIds };
});
return checkValid(afterDrop);
}
else {
const new_tile_in_world_int: Point = vm(compose(
inverse(state.canvas_from_world),
canvas_from_drag_tile(state, ms)).translate,
Math.round);

const afterDrop = !isOccupied(state, new_tile_in_world_int)
? putTileInWorld(state, ms.id, new_tile_in_world_int)
: state;
return checkValid(afterDrop);
}

const afterDrop = !isOccupied(state, new_tile_in_world_int)
? putTileInWorld(state, ms.id, new_tile_in_world_int)
: state;

return checkValid(afterDrop);
}
else {
// Can't drag multiple things into hand
Expand All @@ -81,7 +124,7 @@ function resolveMouseupInner(state: GameState): GameState {

const new_tile_in_hand_int: Point = vm(compose(
inverse(canvas_from_hand()),
canvas_from_drag_tile(state, state.mouseState)).translate,
canvas_from_drag_tile(state, ms)).translate,
Math.round);

return ms.orig_loc.t == 'world'
Expand All @@ -97,6 +140,10 @@ function resolveMouseupInner(state: GameState): GameState {
}
}

function deselect(state: GameState): GameState {
return produce(state, s => { s.selected = undefined; });
}

export function reduceMouseDown(state: GameState, wp: WidgetPoint, button: number, mods: Set<string>): GameState {

function drag_world(): GameState {
Expand All @@ -109,10 +156,6 @@ export function reduceMouseDown(state: GameState, wp: WidgetPoint, button: numbe
});
}

function deselect(state: GameState): GameState {
return produce(state, s => { s.selected = undefined; });
}

function vacuous_down(): GameState {
return produce(state, s => { s.mouseState = { t: 'down', p_in_canvas: wp.p_in_canvas }; });
}
Expand Down
14 changes: 11 additions & 3 deletions src/core/state-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import { getAssets } from "./assets";
import { getLetterSample } from "./distribution";
import { checkConnected, checkGridWords, mkGridOfMainTiles } from "./grid";
import { getOverlayLayer, setOverlay } from "./layer";
import { GameState, Tile } from "./state";
import { addHandTile, addWorldTile, ensureTileId, get_hand_tiles, get_main_tiles, removeTile } from "./tile-helpers";
import { GameState, Tile, TileEntity } from "./state";
import { addHandTile, addWorldTile, ensureTileId, get_hand_tiles, get_main_tiles, get_tiles, removeTile } from "./tile-helpers";

export function addWorldTiles(state: GameState, tiles: Tile[]): GameState {
return produce(state, s => {
Expand All @@ -26,8 +26,16 @@ export function addHandTiles(state: GameState, tiles: Tile[]): GameState {
});
}

export function isCollision(tiles: TileEntity[], points: Point[]) {
return points.some(point => isOccupiedTiles(tiles, point));
}

export function isOccupied(state: GameState, p: Point): boolean {
return get_main_tiles(state).some(tile => vequal(tile.loc.p_in_world_int, p));
return isOccupiedTiles(get_tiles(state), p);
}

export function isOccupiedTiles(tiles: TileEntity[], p: Point): boolean {
return tiles.some(tile => tile.loc.t == 'world' && vequal(tile.loc.p_in_world_int, p));
}

export function drawOfState(state: GameState): GameState {
Expand Down

0 comments on commit f401664

Please sign in to comment.