From 8c4591b4cc17fcfdd0832bd8b3e5cee054b0d4c7 Mon Sep 17 00:00:00 2001 From: Johannes Baum Date: Mon, 11 Oct 2021 20:15:31 +0200 Subject: [PATCH 1/4] #204 add isInRange --- src/GridTilemap/GridTilemap.test.ts | 34 +++++++++++++++++++++++++++++ src/GridTilemap/GridTilemap.ts | 17 +++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/src/GridTilemap/GridTilemap.test.ts b/src/GridTilemap/GridTilemap.test.ts index 43062d18..72dc7860 100644 --- a/src/GridTilemap/GridTilemap.test.ts +++ b/src/GridTilemap/GridTilemap.test.ts @@ -108,6 +108,8 @@ describe("GridTilemap", () => { ], tileWidth: 16, tileHeight: 16, + width: 20, + height: 30, getTileAt: jest.fn(), hasTileAt: jest.fn(), createBlankLayer: jest.fn().mockReturnValue(blankLayerMock), @@ -685,6 +687,38 @@ describe("GridTilemap", () => { expect(gridTilemap.getTileHeight()).toEqual(48); }); + it("should get width", () => { + expect(gridTilemap.getWidth()).toEqual(tilemapMock.width); + }); + + it("should get height", () => { + expect(gridTilemap.getHeight()).toEqual(tilemapMock.height); + }); + + it("should get positions in range", () => { + const inRange = { x: 10, y: 10 }; + const xTooSmall = { x: -1, y: 10 }; + const xZero = { x: 0, y: 10 }; + const xMaxSize = { x: tilemapMock.width - 1, y: 10 }; + const xTooLarge = { x: tilemapMock.width, y: 10 }; + const yTooSmall = { x: 10, y: -1 }; + const yZero = { x: 10, y: 0 }; + const yMaxSize = { x: 10, y: tilemapMock.height - 1 }; + const yTooLarge = { x: 10, y: tilemapMock.height }; + + expect(gridTilemap.isInRange(new Vector2(inRange))).toEqual(true); + + expect(gridTilemap.isInRange(new Vector2(xTooSmall))).toEqual(false); + expect(gridTilemap.isInRange(new Vector2(xZero))).toEqual(true); + expect(gridTilemap.isInRange(new Vector2(xMaxSize))).toEqual(true); + expect(gridTilemap.isInRange(new Vector2(xTooLarge))).toEqual(false); + + expect(gridTilemap.isInRange(new Vector2(yTooSmall))).toEqual(false); + expect(gridTilemap.isInRange(new Vector2(yZero))).toEqual(true); + expect(gridTilemap.isInRange(new Vector2(yMaxSize))).toEqual(true); + expect(gridTilemap.isInRange(new Vector2(yTooLarge))).toEqual(false); + }); + describe("transitions", () => { it("should set transitions", () => { gridTilemap = new GridTilemap(tilemapMock); diff --git a/src/GridTilemap/GridTilemap.ts b/src/GridTilemap/GridTilemap.ts index e3666276..b3e25bef 100644 --- a/src/GridTilemap/GridTilemap.ts +++ b/src/GridTilemap/GridTilemap.ts @@ -103,6 +103,23 @@ export class GridTilemap { return this.visLayerDepths.get(layerName) || 0; } + getWidth(): number { + return this.tilemap.width; + } + + getHeight(): number { + return this.tilemap.height; + } + + isInRange(pos: Vector2): boolean { + return ( + pos.x >= 0 && + pos.x < this.tilemap.width && + pos.y >= 0 && + pos.y < this.tilemap.height + ); + } + private isLayerBlockingAt( layer: Phaser.Tilemaps.LayerData, pos: Vector2, From 73efdf2dded66a75771fbee246f69bbf7fed2392 Mon Sep 17 00:00:00 2001 From: Johannes Baum Date: Mon, 11 Oct 2021 20:20:17 +0200 Subject: [PATCH 2/4] #204 add out of range check --- .../TargetMovement/TargetMovement.test.ts | 95 +++++++++++++------ src/Movement/TargetMovement/TargetMovement.ts | 4 +- 2 files changed, 68 insertions(+), 31 deletions(-) diff --git a/src/Movement/TargetMovement/TargetMovement.test.ts b/src/Movement/TargetMovement/TargetMovement.test.ts index 1549a1b8..5d4f552d 100644 --- a/src/Movement/TargetMovement/TargetMovement.test.ts +++ b/src/Movement/TargetMovement/TargetMovement.test.ts @@ -50,6 +50,7 @@ describe("TargetMovement", () => { hasBlockingChar: jest.fn().mockReturnValue(false), isBlocking: jest.fn(), getTransition: jest.fn(), + isInRange: jest.fn().mockReturnValue(true), }; mockBfs.getShortestPath = jest.fn(); }); @@ -658,40 +659,74 @@ describe("TargetMovement", () => { }); }); - it("should ignore blocked tiles with non-colliding char", () => { - const charPos = layerPos(new Vector2(3, 1)); - const targetPos = layerPos(new Vector2(1, 1)); - targetMovement = new TargetMovement(gridTilemapMock, targetPos); - const mockChar = createMockChar("char1", charPos.position); - mockChar.getCollides.mockReturnValue(false); + describe("non-colliding char", () => { + beforeEach(() => { + mockBfs.getShortestPath = jest.fn().mockReturnValue({ + path: [], + closestToTarget: undefined, + }); + }); - mockBfs.getShortestPath = jest.fn().mockReturnValue({ - path: [], - closestToTarget: undefined, + it("should ignore blocked tiles with non-colliding char", () => { + const charPos = layerPos(new Vector2(3, 1)); + const targetPos = layerPos(new Vector2(1, 1)); + targetMovement = new TargetMovement(gridTilemapMock, targetPos); + const mockChar = createMockChar("char1", charPos.position); + mockChar.getCollides.mockReturnValue(false); + + targetMovement.setCharacter(mockChar); + gridTilemapMock.isBlocking.mockReturnValue(true); + const getNeighbours = targetMovement.getNeighbours(charPos); + + expect(getNeighbours).toEqual([ + { + position: new Vector2(charPos.position.x, charPos.position.y + 1), + layer: "layer1", + }, + { + position: new Vector2(charPos.position.x + 1, charPos.position.y), + layer: "layer1", + }, + { + position: new Vector2(charPos.position.x - 1, charPos.position.y), + layer: "layer1", + }, + { + position: new Vector2(charPos.position.x, charPos.position.y - 1), + layer: "layer1", + }, + ]); }); - targetMovement.setCharacter(mockChar); - gridTilemapMock.isBlocking.mockReturnValue(true); - const getNeighbours = targetMovement.getNeighbours(charPos); + it("should not list tiles out of range", () => { + gridTilemapMock.isInRange.mockReturnValue(false); + const charPos = layerPos(new Vector2(3, 1)); + const targetPos = layerPos(new Vector2(1, 1)); + targetMovement = new TargetMovement(gridTilemapMock, targetPos); + const mockChar = createMockChar("char1", charPos.position); + mockChar.getCollides.mockReturnValue(false); - expect(getNeighbours).toEqual([ - { - position: new Vector2(charPos.position.x, charPos.position.y + 1), - layer: "layer1", - }, - { - position: new Vector2(charPos.position.x + 1, charPos.position.y), - layer: "layer1", - }, - { - position: new Vector2(charPos.position.x - 1, charPos.position.y), - layer: "layer1", - }, - { - position: new Vector2(charPos.position.x, charPos.position.y - 1), - layer: "layer1", - }, - ]); + targetMovement.setCharacter(mockChar); + const getNeighbours = targetMovement.getNeighbours(charPos); + + expect(getNeighbours).toEqual([]); + }); + + it("should not move if no path exists", () => { + targetMovement = new TargetMovement( + gridTilemapMock, + layerPos(new Vector2(3, 2)) + ); + const charPos = layerPos(new Vector2(3, 1)); + mockBfs.getShortestPath = jest + .fn() + .mockReturnValue({ path: [], closestToDistance: charPos }); + const mockChar = createMockChar("char", charPos.position); + mockChar.getCollides.mockReturnValue(false); + targetMovement.setCharacter(mockChar); + targetMovement.update(100); + expect(mockChar.move).not.toHaveBeenCalled(); + }); }); it("should delegate getNeighbours to gridTilemap", () => { diff --git a/src/Movement/TargetMovement/TargetMovement.ts b/src/Movement/TargetMovement/TargetMovement.ts index 430286eb..9adae9d3 100644 --- a/src/Movement/TargetMovement/TargetMovement.ts +++ b/src/Movement/TargetMovement/TargetMovement.ts @@ -278,7 +278,9 @@ export class TargetMovement implements Movement { } private isBlocking = (pos?: Vector2, charLayer?: string): boolean => { - if (!this.character?.getCollides()) return false; + if (!this.character?.getCollides()) { + return !pos || !this.tilemap.isInRange(pos); + } return !pos || this.tilemap.isBlocking(charLayer, pos); }; From 52469969a5a33921e108691acf53402fa0c1ef81 Mon Sep 17 00:00:00 2001 From: Johannes Baum Date: Tue, 12 Oct 2021 17:58:31 +0200 Subject: [PATCH 3/4] #204 refactor --- src/GridTilemap/GridTilemap.test.ts | 47 ++++++++++++++++------------- src/GridTilemap/GridTilemap.ts | 9 ++---- src/Utils/Rect/Rect.test.ts | 46 ++++++++++++++++++++++++++++ src/Utils/Rect/Rect.ts | 35 +++++++++++++++++++++ 4 files changed, 110 insertions(+), 27 deletions(-) create mode 100644 src/Utils/Rect/Rect.test.ts create mode 100644 src/Utils/Rect/Rect.ts diff --git a/src/GridTilemap/GridTilemap.test.ts b/src/GridTilemap/GridTilemap.test.ts index 72dc7860..41ae6d9f 100644 --- a/src/GridTilemap/GridTilemap.test.ts +++ b/src/GridTilemap/GridTilemap.test.ts @@ -5,6 +5,7 @@ import { of } from "rxjs"; import { Vector2 } from "../Utils/Vector2/Vector2"; import { Direction, NumberOfDirections } from "./../Direction/Direction"; import { GridTilemap } from "./GridTilemap"; +import { Rect } from "../Utils/Rect/Rect"; const mockCharBlockCache = { addCharacter: jest.fn(), @@ -66,6 +67,10 @@ const mockCharLayers = [ }, ]; +const mockRect = { + isInRange: jest.fn(), +}; + jest.mock("./CharBlockCache/CharBlockCache", function () { return { CharBlockCache: jest.fn().mockImplementation(function () { @@ -74,6 +79,14 @@ jest.mock("./CharBlockCache/CharBlockCache", function () { }; }); +jest.mock("../Utils/Rect/Rect", function () { + return { + Rect: jest.fn().mockImplementation(function () { + return mockRect; + }), + }; +}); + describe("GridTilemap", () => { let gridTilemap: GridTilemap; let tilemapMock; @@ -696,27 +709,19 @@ describe("GridTilemap", () => { }); it("should get positions in range", () => { - const inRange = { x: 10, y: 10 }; - const xTooSmall = { x: -1, y: 10 }; - const xZero = { x: 0, y: 10 }; - const xMaxSize = { x: tilemapMock.width - 1, y: 10 }; - const xTooLarge = { x: tilemapMock.width, y: 10 }; - const yTooSmall = { x: 10, y: -1 }; - const yZero = { x: 10, y: 0 }; - const yMaxSize = { x: 10, y: tilemapMock.height - 1 }; - const yTooLarge = { x: 10, y: tilemapMock.height }; - - expect(gridTilemap.isInRange(new Vector2(inRange))).toEqual(true); - - expect(gridTilemap.isInRange(new Vector2(xTooSmall))).toEqual(false); - expect(gridTilemap.isInRange(new Vector2(xZero))).toEqual(true); - expect(gridTilemap.isInRange(new Vector2(xMaxSize))).toEqual(true); - expect(gridTilemap.isInRange(new Vector2(xTooLarge))).toEqual(false); - - expect(gridTilemap.isInRange(new Vector2(yTooSmall))).toEqual(false); - expect(gridTilemap.isInRange(new Vector2(yZero))).toEqual(true); - expect(gridTilemap.isInRange(new Vector2(yMaxSize))).toEqual(true); - expect(gridTilemap.isInRange(new Vector2(yTooLarge))).toEqual(false); + const pos = new Vector2({ x: 10, y: 20 }); + mockRect.isInRange.mockReturnValue(false); + const res = gridTilemap.isInRange(pos); + + expect(Rect).toHaveBeenCalledWith( + 0, + 0, + tilemapMock.width, + tilemapMock.height + ); + + expect(mockRect.isInRange).toHaveBeenCalledWith(pos); + expect(res).toEqual(false); }); describe("transitions", () => { diff --git a/src/GridTilemap/GridTilemap.ts b/src/GridTilemap/GridTilemap.ts index b3e25bef..cd09387d 100644 --- a/src/GridTilemap/GridTilemap.ts +++ b/src/GridTilemap/GridTilemap.ts @@ -3,6 +3,7 @@ import { Direction } from "./../Direction/Direction"; import { GridCharacter } from "../GridCharacter/GridCharacter"; import { Vector2 } from "../Utils/Vector2/Vector2"; import { CharBlockCache } from "./CharBlockCache/CharBlockCache"; +import { Rect } from "../Utils/Rect/Rect"; export class GridTilemap { private static readonly MAX_PLAYER_LAYERS = 1000; @@ -112,12 +113,8 @@ export class GridTilemap { } isInRange(pos: Vector2): boolean { - return ( - pos.x >= 0 && - pos.x < this.tilemap.width && - pos.y >= 0 && - pos.y < this.tilemap.height - ); + const rect = new Rect(0, 0, this.tilemap.width, this.tilemap.height); + return rect.isInRange(pos); } private isLayerBlockingAt( diff --git a/src/Utils/Rect/Rect.test.ts b/src/Utils/Rect/Rect.test.ts new file mode 100644 index 00000000..ef940777 --- /dev/null +++ b/src/Utils/Rect/Rect.test.ts @@ -0,0 +1,46 @@ +import { Vector2 } from "../Vector2/Vector2"; +import { Rect } from "./Rect"; + +describe("Rect", () => { + it("should create a rect", () => { + const x = 10; + const y = 20; + const width = 30; + const height = 40; + const rect = new Rect(x, y, width, height); + + expect(rect).toBeTruthy(); + expect(rect.getX()).toEqual(x); + expect(rect.getY()).toEqual(y); + expect(rect.getWidth()).toEqual(width); + expect(rect.getHeight()).toEqual(height); + }); + + it("should check positions in range", () => { + const x = 10; + const y = 20; + const width = 30; + const height = 40; + const rect = new Rect(x, y, width, height); + + const inRange = { x: x + 10, y: y + 10 }; + const xTooSmall = { x: x - 1, y: y + 10 }; + const xMin = { x, y: y + 10 }; + const xMaxSize = { x: x + width - 1, y: y + 10 }; + const xTooLarge = { x: x + width, y: y + 10 }; + const yTooSmall = { x: x + 10, y: y - 1 }; + const yMin = { x: x + 10, y }; + const yMaxSize = { x: x + 10, y: y + height - 1 }; + const yTooLarge = { x: x + 10, y: y + height }; + + expect(rect.isInRange(new Vector2(inRange))).toEqual(true); + expect(rect.isInRange(new Vector2(xTooSmall))).toEqual(false); + expect(rect.isInRange(new Vector2(xMin))).toEqual(true); + expect(rect.isInRange(new Vector2(xMaxSize))).toEqual(true); + expect(rect.isInRange(new Vector2(xTooLarge))).toEqual(false); + expect(rect.isInRange(new Vector2(yTooSmall))).toEqual(false); + expect(rect.isInRange(new Vector2(yMin))).toEqual(true); + expect(rect.isInRange(new Vector2(yMaxSize))).toEqual(true); + expect(rect.isInRange(new Vector2(yTooLarge))).toEqual(false); + }); +}); diff --git a/src/Utils/Rect/Rect.ts b/src/Utils/Rect/Rect.ts new file mode 100644 index 00000000..c0f7655e --- /dev/null +++ b/src/Utils/Rect/Rect.ts @@ -0,0 +1,35 @@ +import { Vector2 } from "../Vector2/Vector2"; + +export class Rect { + constructor( + private x: number, + private y: number, + private width: number, + private height: number + ) {} + + getX(): number { + return this.x; + } + + getY(): number { + return this.y; + } + + getWidth(): number { + return this.width; + } + + getHeight(): number { + return this.height; + } + + isInRange(pos: Vector2): boolean { + return ( + pos.x >= this.x && + pos.x < this.x + this.width && + pos.y >= this.y && + pos.y < this.y + this.height + ); + } +} From 782921b2e663f4e2b52d206a6d04b0cfcdf68b31 Mon Sep 17 00:00:00 2001 From: Johannes Baum Date: Tue, 12 Oct 2021 18:02:17 +0200 Subject: [PATCH 4/4] #204 refactor --- src/GridTilemap/GridTilemap.test.ts | 8 -------- src/GridTilemap/GridTilemap.ts | 8 -------- 2 files changed, 16 deletions(-) diff --git a/src/GridTilemap/GridTilemap.test.ts b/src/GridTilemap/GridTilemap.test.ts index 41ae6d9f..f74283fa 100644 --- a/src/GridTilemap/GridTilemap.test.ts +++ b/src/GridTilemap/GridTilemap.test.ts @@ -700,14 +700,6 @@ describe("GridTilemap", () => { expect(gridTilemap.getTileHeight()).toEqual(48); }); - it("should get width", () => { - expect(gridTilemap.getWidth()).toEqual(tilemapMock.width); - }); - - it("should get height", () => { - expect(gridTilemap.getHeight()).toEqual(tilemapMock.height); - }); - it("should get positions in range", () => { const pos = new Vector2({ x: 10, y: 20 }); mockRect.isInRange.mockReturnValue(false); diff --git a/src/GridTilemap/GridTilemap.ts b/src/GridTilemap/GridTilemap.ts index cd09387d..55212f3c 100644 --- a/src/GridTilemap/GridTilemap.ts +++ b/src/GridTilemap/GridTilemap.ts @@ -104,14 +104,6 @@ export class GridTilemap { return this.visLayerDepths.get(layerName) || 0; } - getWidth(): number { - return this.tilemap.width; - } - - getHeight(): number { - return this.tilemap.height; - } - isInRange(pos: Vector2): boolean { const rect = new Rect(0, 0, this.tilemap.width, this.tilemap.height); return rect.isInRange(pos);