I'm trying to activate collision detection while dragging an object. The behaviour I'm looking for can be seen with the second example (circles) seen here with another library:
library example
https://codepen.io/osublake/pen/bb6983d03e1c3582f9aac486ab9069f8
From my understanding, Phaser's collision engine will only work when object position is automatically updated through its velocity. Is it really the case ? If so, what would be a clean solution ?
My best solution right now is to change velocity in the update() method and use the pointer movement along with the delta time to calculate velocity. With constant deltas, the object movement would match the pointer movement. However, the result is a bit sketchy and has problems when the mouse stops to move (with button still down).
if (obj.membre.isDragging){
let prevPos = {x: obj.membre.x, y: obj.membre.y};
let pointer = scene.input.activePointer;
let velocityX = 0;
let velocityY = 0;
if (pointer.position.x !== status.pointerX && pointer.position.y !== status.pointerY){
status.pointerX = pointer.position.x;
status.pointerY = pointer.position.y;
velocityX = (pointer.position.x-pointer.prevPosition.x)/status.deltaS;
velocityY = (pointer.position.y-pointer.prevPosition.y)/status.deltaS;
}
obj.membre.setVelocity(velocityX, velocityY);
}
Matter engine would make it work, but here is an acceptable solution with arcade engine. The trick is to add LOTS of drag (as in friction). It also seams to work fine in the drag callback.
scene.input.on('drag', function (pointer, gameObject, dragX, dragY) {
gameObject.body.setAllowGravity(false);
gameobject.setDrag(1000,1000);
//set velocity to match pointer movement - status.deltaS is frame delta in seconds
velocityX = (pointer.position.x-pointer.prevPosition.x)/status.deltaS;
velocityY = (pointer.position.y-pointer.prevPosition.y)/status.deltaS;
gameObject.setVelocity(velocityX, velocityY);
});
Here is the solution a finally retained. It uses a custom speration function, based on the example http://labs.phaser.io/edit.html?src=src%5Cphysics%5Carcade%5Ccustom%20separate.js
collider in create()
this.physics.add.collider(obj.dynamicGroup,obj.dynamicGroup,customSeparate);
adjust body properties for all objects (necessary? based on example)
object.body.customSeparateX = true;
object.body.customSeparateY = true;
regular drag
scene.input.on('drag', function (pointer, gameObject, dragX, dragY) {
gameObject.x = dragX;
gameObject.y = dragY;
}
and here is the custom seperation function. I didn't know how to make it cleaner.
function customSeparate(s1, s2) {
var b1 = s1.body;
var b2 = s2.body;
//for dragged object, we have no velocity, so we take pointer direction
let pointFacingX = "left";
let pointFacingY = "top";
if (scene.input.activePointer.position.x > scene.input.activePointer.prevPosition.x) pointFacingX = "right";
if (scene.input.activePointer.position.y > scene.input.activePointer.prevPosition.y) pointFacingY = "bottom";
//if we have velocity we use that - could add priority to fastest object
if (b1.velocity.x > 0) pointFacingX = "right";
if (b2.velocity.x > 0) pointFacingX = "right";
if (b1.velocity.y > 0) pointFacingY = "bottom";
if (b2.velocity.y > 0) pointFacingY = "bottom";
let overlapX = 0;
let overlapY = 0;
if(b1.x > b2.x) {
overlapX = b2.right - b1.left;
}
else {
overlapX = b1.right - b2.left;
}
if(b1.y > b2.y) {
overlapY = b2.bottom - b1.top;
}
else {
overlapY = b1.bottom - b2.top;
}
//we move according to smallest overlap **no overlap is coded at 10000
if (overlapX <= 0) overlapX = 10000;
if (overlapY <= 0) overlapY = 10000;
if(overlapX < overlapY){
if (pointFacingX === "left"){
if (b1.x > b2.x) {
b2.x -= overlapX;
b2.stop();
}
else {
b1.x -= overlapX;
b1.stop();
}
}
else{
if (b1.x < b2.x) {
b2.x += overlapX;
b2.stop();
}
else {
b1.x += overlapX;
b1.stop();
}
}
}
else{
if (pointFacingY === "top"){
if (b1.y > b2.y) {
b2.y -= overlapY;
b2.stop();
}
else {
b1.y -= overlapY;
b1.stop();
}
}
else{
if (b1.y < b2.y) {
b2.y += overlapY;
b2.stop();
}
else {
b1.y += overlapY;
b1.stop();
}
}
}
}
Related
I'm working on a game. At a certain point, I would like to create special GameObject. The special GameObject is called Projectile and is the same as GameObject but has a dx and dy as well as some other functions that only a projectile will have. I am trying to make Projectile extend the GameObject class, but I run into issues when I try to create an instance of Projectile. I've tried different ways of declaring Projectile and shuffling declaration order but can't seem to figure out why I'm getting the error mentioned in the title. Thank you!
The following works just fine:
table.insert(self.dungeon.currentRoom.objects, GameObject(
GAME_OBJECT_DEFS['pot'],
self.player.x,
self.player.y
))
But when I change "GameObject" to "Projectile" it does not.
table.insert(self.dungeon.currentRoom.objects, Projectile(
GAME_OBJECT_DEFS['pot'],
self.player.x,
self.player.y
))
The rest is supporting code. I'm using Class code from Matthias Richter
require 'src/Projectile'
require 'src/GameObject'
require 'src/game_objects'
GameObject = Class{}
function GameObject:init(def, x, y)
-- string identifying this object type
self.type = def.type
self.texture = def.texture
self.frame = def.frame or 1
-- whether it acts as an obstacle or not
self.solid = def.solid
self.defaultState = def.defaultState
self.state = self.defaultState
self.states = def.states
-- dimensions
self.x = x
self.y = y
self.width = def.width
self.height = def.height
-- default empty collision callback
self.onCollide = def.onCollide
end
Projectile = Class{__includes = GameObject}
function Projectile:init()
GameObject.init(self, def)
self.dx = 0
self.dy = 0
end
GAME_OBJECT_DEFS = {
['pot'] = {
type = 'pot',
texture = 'tiles',
frame = 14,
width = 16,
height = 16,
solid = true,
defaultState = 'idle',
states = {
['idle'] = {
frame = 14,
}
},
onCollide = function()
end
}
}
function Projectile:init()
GameObject.init(self, def)
self.dx = 0
self.dy = 0
end
def is nil
so in
function GameObject:init(def, x, y)
-- string identifying this object type
self.type = def.type
you index a nil value.
Same will happen for x and y
I am using Google Mobile Vision to detect face landmarks and the distances between them. The weird thing about this error is that it is not consistent and does not show up all the time I run the same function.
Here is the error message...
TERMINATION
ert_Error ebs_ObjectStack::~ebs_ObjectStack():
Stack is not empty at destruction.
This can be an indiaction for a stack leak.
Please check the code where this instance was used.
libc++abi.dylib: terminating with uncaught exception of type ert_Error
(lldb)
Usually the first time I run the app in 24 hours everything goes smoothly and occasionally afterwards. For the most part though it appears. As I said earlier I am using Google Mobile Vision to detect and store certain values.
Here is the function that is causing the error...
func train2(image2: UIImage) {
print("TRAIN2 has been called")
// let options1 = [GMVDetectorFaceLandmarkType: GMVDetectorFaceLandmark.all.rawValue, GMVDetectorFaceClassificationType: GMVDetectorFaceClassification.all.rawValue, GMVDetectorFaceTrackingEnabled: true, GMVDetectorFaceMinSize: 0.09] as [String : Any]
var deviceOrientation = UIDevice.current.orientation
var devicePostion = AVCaptureDevice.Position.back //.Position(rawValue: (currentCameraPosition?.hashValue)!)
if CameraController().currentCameraPosition == .rear {
devicePostion = AVCaptureDevice.Position.back
}else if CameraController().currentCameraPosition == .front {
devicePostion = AVCaptureDevice.Position.front
}
var lastKnownDeviceOrientation = UIDeviceOrientation(rawValue: 0)
var orientation = GMVUtility.imageOrientation(from: deviceOrientation, with: devicePostion, defaultDeviceOrientation: lastKnownDeviceOrientation!)
var options3 = [GMVDetectorImageOrientation:orientation.rawValue]
// var sampbuff = cvPixelBufferRef(from: image2)
// var AImage = GMVUtility.sampleBufferTo32RGBA(sampbuff as! CMSampleBuffer)
var happyFaces = GfaceDetector.features(in: image2, options: options3) as! [GMVFaceFeature] // THE ERROR SEEMS TO ORIGINATE FROM THIS LINE
print("The Amount Of Happy Faces is: \(happyFaces.count)")
for face:GMVFaceFeature in happyFaces {
print(face.smilingProbability)
print("Go Google")
var YAngle = Float(face.headEulerAngleY)
var ZAngle = Float(face.headEulerAngleZ)
print("Y Angle \(YAngle), Z Angle \(ZAngle)")
if YAngle > -2.0 && YAngle < 4.0 && ZAngle > -2.0 && ZAngle < 2.0 {
var proportion = ((face.bounds.width * face.bounds.width) + (face.bounds.height * face.bounds.height))
var ratio = sqrtf(Float(proportion))
DBetweenEyes = (Float(distanceBetween(face.leftEyePosition, face.rightEyePosition)) * 1000) / ratio
DBetweenLEyeAndLEar = (Float(distanceBetween(face.leftEyePosition, face.leftEarPosition)) * 1000) / ratio
DBetweenREyeAndREar = (Float(distanceBetween(face.rightEyePosition, face.rightEarPosition)) * 1000) / ratio
DBetweenLEarAndREar = (Float(distanceBetween(face.leftEarPosition, face.rightEarPosition)) * 1000) / ratio
DBetweenLEyeAndNose = (Float(distanceBetween(face.leftEyePosition, face.noseBasePosition)) * 1000) / ratio
DBetweenREyeAndNose = (Float(distanceBetween(face.rightEyePosition, face.noseBasePosition)) * 1000) / ratio
DBetweenLEyeAndMouth = (Float(distanceBetween(face.leftEyePosition, face.bottomMouthPosition)) * 1000) / ratio
DBetweenREyeAndMouth = (Float(distanceBetween(face.rightEyePosition, face.bottomMouthPosition)) * 1000) / ratio
DBetweenLEarAndMouth = (Float(distanceBetween(face.leftEarPosition, face.bottomMouthPosition)) * 1000) / ratio
DBetweenREarAndMouth = (Float(distanceBetween(face.rightEarPosition, face.bottomMouthPosition)) * 1000) / ratio
print("Distances Established")
}
}
}
I have just discovered that the error is very rare when called when a button is pressed after the view loads. The error occurs far more frequently when called in the viewDidLoad method. Could this indicate some sort of pattern? How would I fix this error?
Any help or suggestions are helpful.
Thanks in advance!
I am pretty Newbie to programming. And I am trying to pile up the random blocks dynamically till it hits the upper frame. But it seems that Swift doesn't let me to do so. Did I miss anything please? Any input are appreciated.
let blocks =[block1,block2,block3,block4,block5,block6,block7,block8,block9,block10,block11,block12]
var block:SKSpriteNode!
let blockX:Double = 0.0
var blockY:Double = -(self.size.height/2)
repeat{
block = blocks.randomBlock()
block.zPosition = 2
block.position = CGPoint(x:blockX, y:blockY)
block.size.height = 50
block.size.width = 50
self.addChild(block)
blockY += 50
} while( block.position.y < self.size.height)
extension Array {
func randomBlock()-> Element {
let randint = Int(arc4random_uniform(UInt32(self.count)))
return self[randint]
}
}
you need to have someway of tracking which blocks have been selected and ensure that they don't get selected again. The method below uses an array to store the indexes of selected blocks and then uses recursion to find a cycle through until an unused match is found.
private var usedBlocks = [Int]()
func randomBlock() -> Int {
guard usedBlocks.count != blocks.count else { return -1 }
let random = Int(arc4random_uniform(UInt32(blocks.count)))
if usedBlocks.contains(random) {
return randomBlock()
}
usedBlocks.append(random)
return random
}
in your loop change your initializer to
let index = randomBlock()
if index > -1 {
block = blocks[index]
block.zPosition = 2
block.position = CGPoint(x:blockX, y:blockY)
}
remember that if you restart the game or start a new level, etc. you must clear all of the objects from usedBlocks
usedBlocks.removeAll()
I have a trail on a map that the user can "follow" by mousing over a graph (time and speed). If the user zooms in a lot, part of the trail may not be visible. When the user wants to see the part of the trail that is not showing I use the panTo method...
The panTo method of leaflet is currently also centering. I don't want to center, I want the map to move just enough to show a point. (The problem with panTo is it causes excessive map scrolling and a harsh user experience.)
I have tried changing the bounds, but that has an (unwanted) side affect of sometimes zooming out.
Any way I can do a "minimal" panTo?
This is a (working but unpolished) solution; map is our own map wrapper utility class, lmap is a leaflet map object in typescript, and toxy() is a method to convert lat/longs to x/y values.
if (!this.lmap.getBounds().contains(latlng)) {
let target = this.map.toxy(latlng);
let nw = this.map.toxy(this.lmap.getBounds().getNorthWest());
let se = this.map.toxy(this.lmap.getBounds().getSouthEast());
let x = 0, y = 0;
let margin = 75;
if (target.y < nw.y)
y = (-1 * (nw.y - target.y)) - margin;
else if (target.y > se.y)
y = (target.y - se.y) + margin;
if (target.x < nw.x)
x = (-1 * (nw.x - target.x)) - margin;
else if (target.x > se.x)
x = (target.x - se.x) + margin;
this.lmap.panBy(new L.Point(x, y));
}
First, fetch the bounds of the map (measured in pixels from the CRS origin) with map.getPixelBounds(). Then, use map.project(latlng, map.getZoom()) to get the coordinates (in pixels from the CRS origin) of the point you're interested.
If you're confused about this "pixels from the CRS origin" thing, read the "Pixel Origin" section at http://leafletjs.com/examples/extending/extending-2-layers.html .
Once you have these pixel coordinates, it should be a simple matter of checking whether the point is inside the viewport, and if not, how far away on each direction it is.
http://jsfiddle.net/jcollin6/b131tobj/2/
Should give you what you want
function clamp(n,lower,upper) {
return Math.max(lower, Math.min(upper, n));
}
//Shamelessly stolen from https://gist.github.com/dwtkns/d5b9b60285b8b0067c53
function getNearestPointInPerimeter(l,t,w,h,x,y) {
var r = l+w,
b = t+h;
var x = clamp(x,l,r),
y = clamp(y,t,b);
var dl = Math.abs(x-l),
dr = Math.abs(x-r),
dt = Math.abs(y-t),
db = Math.abs(y-b);
var m = Math.min(dl,dr,dt,db);
return (m===dt) ? {x: x, y: t} :
(m===db) ? {x: x, y: b} :
(m===dl) ? {x: l, y: y} : {x: r, y: y};
}
L.ExtendedMap = L.Map.extend({
panInside: function (latlng, pad, options) {
var padding = pad ? pad : 0;
var pixelBounds = this.getPixelBounds();
var center = this.getCenter();
var pixelCenter = this.project(center);
var pixelPoint = this.project(latlng);
var sw = pixelBounds.getBottomLeft();
var ne = pixelBounds.getTopRight();
var topLeftPoint = L.point(sw.x + padding, ne.y + padding);
var bottomRightPoint = L.point( ne.x - padding, sw.y - padding);
var paddedBounds = L.bounds(topLeftPoint, bottomRightPoint);
if (!paddedBounds.contains(pixelPoint)) {
this._enforcingBounds = true;
var nearestPoint = getNearestPointInPerimeter(
sw.x + padding,
ne.y + padding,
ne.x - sw.x - padding * 2,
sw.y - ne.y - padding * 2,
pixelPoint.x,
pixelPoint.y
);
var nearestPixelPoint = L.point(nearestPoint.x,nearestPoint.y)
var diffPixelPoint = nearestPixelPoint.subtract(pixelPoint);
var newPixelCenter = pixelCenter.subtract(diffPixelPoint);
var newCenter = this.unproject(newPixelCenter);
if (!center.equals(newCenter)) {
this.panTo(newCenter, options);
}
this._enforcingBounds = false;
}
return this;
}
});
Use this way
map.panTo([lat, lng]);
map.setZoom(Zoom);
(Sorry for the cross-post, but I put this up on GameDev.Stackexchange and it's not getting much traffic, so I thought I'd try here as well.)
I'm programming a simple tile-based puzzle game, and I've gotten stuck trying to work out a pathfinding algorithm.
Here's how the game is set out:
The game board is (arbitrarily) 8 tiles wide by 8 tiles tall.
Each tile can be one of four types (shown below as red, green, blue and yellow)
Additionally, a tile could be a reactor (the starting point of a path - this will become clear later on)
The board would look something like this:
(The reactors are the circles; the other tiles have no special properties.)
What I need to do is: starting from a reactor, trace the longest path along adjoining tiles of the same color as the reactor. Something like this:
The blue reactor is simple(ish) as its path doesn't branch. However, as you can see from the green reactor's start position, its path can branch two ways at the start (up or down), and take a detour midway through.
The path I'm looking for is the longest one, so that's the one that's highlighted in the screengrab (the first path only crosses two tiles, and the detour midway results in a sorter path).
When certain conditions have been fulfilled, the reactor will cause all the tiles in the longest path (where the arrows cross in the diagram) to disappear and be replaced with new ones. All other tiles will remain in place, including the extraneous green tiles adjacent to the green reactor's path.
The tiles are stored in an approximation of a 2D array (Swift doesn't have a robust native implementation of that yet, so I'm using the one described in this tutorial). They're retrieved using tile[column, row].
With some help from a friend, I've written a recursive function that should return the longest path. It's looping through correctly, but it's not pruning shorter branches from the longestPath array (for example, the longest path would include the 2-tile branch below the reactor, as well as the single-tile detour at the top of the arch).
Can anyone see where I'm going wrong in this code?
Here's the recursive function:
func pathfinder(startingTile: Tile, pathToThisPoint: Chain, var iteration: Int? = 1) -> Chain
{
var longestPath: Chain? = nil
var availableTiles = getNeighbouringTiles(startingTile)
for var nextTile = 0; nextTile < availableTiles.count; nextTile++
{
let column = availableTiles[nextTile].column
let row = availableTiles[nextTile].row
if tiles[column, row]!.tileType == startingTile.tileType && (tiles[column, row]!.isReactor == false || startingTile.isReactor)
{
// if we haven't been here before
if !pathToThisPoint.tiles.contains(tiles[column, row]!)
{
print(iteration)
iteration = iteration! + 1
// add this tile to the pathtothispoint
// go to the next unexplored tile (recurse this function)
pathToThisPointaddTile(tiles[column, row]!)
let tempPath = pathfinder(tiles[column, row]!, pathToThisPoint: pathToThisPoint)
// if the resulting path is longer...
if tempPath.length > longestPath.length
{
// then tempPath is now the longest path
for var i:Int = 0; i < tempPath.length; i++
{
let tile = Tile(column: pathToThisPoint.tiles[i].column, row: pathToThisPoint.tiles[i].row, tileType: pathToThisPoint.tiles[i].tileType)
longestPath?.addTile(tile)
}
}
}
}
if longestPath != nil
{
return longestPath!
}
else
{
return pathToThisPoint
}
}
It's dependent on the getNeighboringTiles function (shown below) that returns an array of valid tiles of the same type, excluding reactors:
func getNeighbouringTiles(tile: Tile, previousTile: Tile? = nil) -> Array<Tile>
{
var validNeighbouringTiles = Array<Tile>()
var neighbourTile: Tile
// check top, right, bottom, left
if tile.row < NumRows - 1
{
neighbourTile = tiles[tile.column, tile.row + 1]!
if neighbourTile.tileType == tile.tileType && !neighbourTile.isReactor && (previousTile == nil || previousTile != neighbourTile)
{
validNeighbouringTiles.append(neighbourTile)
}
}
if tile.column < NumColumns - 1
{
neighbourTile = tiles[tile.column + 1, tile.row]!
if neighbourTile.tileType == tile.tileType && !neighbourTile.isReactor && (previousTile == nil || previousTile != neighbourTile)
{
validNeighbouringTiles.append(neighbourTile)
}
}
if tile.row > 0
{
neighbourTile = tiles[tile.column, tile.row - 1]!
if neighbourTile.tileType == tile.tileType && !neighbourTile.isReactor && (previousTile == nil || previousTile != neighbourTile)
{
validNeighbouringTiles.append(neighbourTile)
}
}
if tile.column > 0
{
neighbourTile = tiles[tile.column - 1, tile.row]!
if neighbourTile.tileType == tile.tileType && !neighbourTile.isReactor && (previousTile == nil || previousTile != neighbourTile)
{
validNeighbouringTiles.append(neighbourTile)
}
}
// if we get this far, they have no neighbour
return validNeighbouringTiles
}
The Tile class looks like this (methods omitted for brevity):
class Tile: CustomStringConvertible, Hashable
{
var column:Int
var row:Int
var tileType: TileType // enum, 1 - 4, mapping to colors
var isReactor: Bool = false
// if the tile is a reactor, we can store is longest available path here
var reactorPath: Chain! = Chain()
}
And finally, the chain class looks like this (again, methods omitted for brevity):
class Chain {
// The tiles that are part of this chain.
var tiles = [Tile]()
func addTile(tile: Tile) {
tiles.append(tile)
}
func firstTile() -> Tile {
return tiles[0]
}
func lastTile() -> Tile {
return tiles[tiles.count - 1]
}
var length: Int {
return tiles.count
}
}
----------------------- EDIT : REPLACEMENT PATHFINDER ------------------------
I've attempted to convert User2464424's code to Swift. Here's what I've got:
func calculatePathsFromReactor(reactor: Tile) -> Chain?
{
func countDirections(neighbours: [Bool]) -> Int
{
var count: Int = 0
for var i:Int = 0; i < neighbours.count; i++
{
if neighbours[i] == true
{
count++
}
}
return count
}
var longestChain: Chain? = nil
longestChain = Chain()
var temp: Chain = Chain()
var lastBranch: Tile = reactor
var lastMove: Int? = reactor.neighbours.indexOf(true)
func looper(var currentTile: Tile)
{
if currentTile != reactor
{
if countDirections(currentTile.neighbours) > 2 //is branch
{
lastBranch = currentTile
}
if countDirections(currentTile.neighbours) == 1 //is endpoint
{
lastBranch.neighbours[lastMove!] = false // block move out of the last branch found
if longestChain.length < temp.length
{
longestChain = temp
}
currentTile = reactor // return to reactor and redo
lastVisitedTile = reactor
temp = Chain() //reset to empty array
lastBranch = reactor
lastMove = reactor.neighbours.indexOf(true)
looper(currentTile)
}
}
//let tempTile: Tile = Tile(column: currentTile.column, row: currentTile.row, tileType: currentTile.tileType, isReactor: currentTile.isReactor, movesRemaining: currentTile.movesRemaining)
//tempTile.neighbours = currentTile.neighbours
if currentTile.neighbours[0] == true
{
if !temp.tiles.contains(currentTile)
{
temp.addTile(currentTile)
}
if countDirections(currentTile.neighbours) > 2
{
lastMove = 0
}
lastVisitedTile = currentTile
currentTile = tiles[currentTile.column, currentTile.row + 1]! //must avoid going backwards
if !temp.tiles.contains(currentTile)
{
looper(currentTile)
}
}
if currentTile.neighbours[1] == true
{
if !temp.tiles.contains(currentTile)
{
temp.addTile(currentTile)
}
if countDirections(currentTile.neighbours) > 2
{
lastMove = 1
}
lastVisitedTile = currentTile
currentTile = tiles[currentTile.column + 1, currentTile.row]! //must avoid going backwards
if !temp.tiles.contains(currentTile)
{
looper(currentTile)
}
}
if currentTile.neighbours[2] == true
{
if !temp.tiles.contains(currentTile)
{
temp.addTile(currentTile)
}
if countDirections(currentTile.neighbours) > 2
{
lastMove = 2
}
lastVisitedTile = currentTile
currentTile = tiles[currentTile.column, currentTile.row - 1]! //must avoid going backwards
if !temp.tiles.contains(currentTile)
{
looper(currentTile)
}
}
if currentTile.neighbours[3] == true
{
if !temp.tiles.contains(currentTile)
{
temp.addTile(currentTile)
}
if countDirections(currentTile.neighbours) > 2
{
lastMove = 3
}
lastVisitedTile = currentTile
currentTile = tiles[currentTile.column - 1, currentTile.row]! //must avoid going backwards
if !temp.tiles.contains(currentTile)
{
looper(currentTile)
}
}
}
// trigger the function for the reactor tile
looper(reactor)
return longestChain
}
(The neighbours property is a struct containing four named variables: above, right, below and left, each initialised to false and then set to true by a function that runs immediately before the pathfinder.)
I'm finding a couple of issues now. The code loops as it should, but stops at the top of the arch, under the single-tile detour - the path that's returned is only 4 tiles long (including the reactor).
The other problem I'm having - which I'll worry about when the correct paths are being returned - is that I'm getting a memory access error when shifting the tiles in the third column down by one. I think it's getting confused when there's a block of tiles (2x2 or higher) rather than a path that's only ever a single tile wide.
I can't fix your code, but i have an idea for a system that doesn't require recursion.
You can try doing all possible paths from a reactor and block paths that you already traversed by being aware of the moves you have done when encountering a branch.
In the tile class, add another array of 4 integers initialized to 0 (called "dir" for example).
Pseudocode.
Do a preprocess loop first:
foreach tiles:
if tileHasNORTHNeighbor: tile.dir[0] = 1;
if tileHasEASTNeighbor: tile.dir[1] = 1;
if tileHasSOUTHNeighbor: tile.dir[2] = 1;
if tileHasWESTNeighbor: tile.dir[3] = 1;
Then do:
tile currentTile = reactor;
array longest;
array temp;
tile lastBranch = reactor;
int lastMove = any key of "reactor.dir" with "1" as value;
function int countOnes(array dir):
int count = 0;
int t;
for (t=0;t<4;t++):
if dir[t]==1:
count++;
return count;
:start
if currentTile != reactor:
if countOnes(currentTile.dir) > 2: //is branch
lastBranch = currentTile;
if countOnes(currentTile.dir) == 1: //is endpoint
lastBranch.dir[lastMove] = 0; // block move out of the last branch found
if longest.length < temp.length:
longest = temp;
currentTile = reactor; // return to reactor and redo
array temp = []; //reset to empty array
lastBranch = reactor;
lastMove = next "reactor.dir" key with "1" as value;
goto start;
if currentTile.dir[0] == 1:
temp.append(currentTile);
if countOnes(currentTile.dir) > 2:
lastMove = 0;
currentTile = getTileAtNORTH; //must avoid going backwards
goto start;
if currentTile.dir[1] == 1:
temp.append(currentTile);
if countOnes(currentTile.dir) > 2:
lastMove = 1;
currentTile = getTileAtEAST; //must avoid going backwards
goto start;
if currentTile.dir[2] == 1:
temp.append(currentTile);
if countOnes(currentTile.dir) > 2:
lastMove = 2;
currentTile = getTileAtSOUTH; //must avoid going backwards
goto start;
if currentTile.dir[3] == 1:
temp.append(currentTile);
if countOnes(currentTile.dir) > 2:
lastMove = 3;
currentTile = getTileAtWEST; //must avoid going backwards
goto start;
You could use the BFS Algorithm and easily modify it to give you the longest path.
You've got a implementation example here. Or you've got at least SwiftStructures and SwiftGraph github repositories with graph and search algorithms already implemented in swift.