...
 
Commits (3)
  • Terence Martin's avatar
    Include extra balls for testing purposes · 17284159
    Terence Martin authored
    In order to make debugging easier, we add a few extra balls to the
    pool instead of the exact number that we need. That way there are some
    left in reserve for adding to a level.
    17284159
  • Terence Martin's avatar
    Enhance Teleport; select non-blocked destination · 336736d5
    Terence Martin authored
    This change adds to the Teleport entity the ability to ensure that the
    position that it returns in response to ballTouch() is a location to
    which the ball can be transported without blocking.
    
    This is done by conducting a pre-check to ensure that there is at
    least one valid destination to select, so that it won't loop forever
    if all of the destinations are blocked.
    336736d5
  • Terence Martin's avatar
    Unplug blocked exits to Teleport entities · 437fa4d8
    Terence Martin authored
    It is possible for a ball to exit a Teleport and then not be able to
    drop further. When this happens, the ball movement stops and the ball
    gets put back into the maze at the location of the Teleport exit.
    
    This clobbers the Teleport out of the maze at this location, although
    it still visually renders at this location because the render is based
    on destinations (they all share the same entity). This results in that
    being an "exit only" teleport; the ball can't enter the teleport there
    because it's not in the maze any longer.
    
    Now when we determine that we are about to drop the ball down, we
    check to see if the position that we're currently occupying is empty
    and also a destination in the single teleport entity.
    
    If this is the case, we can re-insert the teleport, which allows this
    location to be the source of a teleport operation again.
    437fa4d8
......@@ -3422,6 +3422,18 @@ var nurdz;
}
return -1;
};
/**
* Perform a check to see if this teleport instance contains the
* destination point provided.
*
* @param {Point} destination the destination to check
*
* @returns {boolean} true if this teleport entity includes
* the given point in it's destination list.
*/
Teleport.prototype.hasDestination = function (destination) {
return this.indexOfDestination(destination) != -1;
};
/**
* Add a potential destination to this teleport instance. This can be
* invoked more than once, in which case when activated the teleport
......@@ -3456,6 +3468,54 @@ var nurdz;
if (index != -1)
this._destinations.splice(index, 1);
};
/**
* Given a (possibly null) entity, determine if we could teleport to its
* location or not. This is basically a helper to make the code easier
* to read, even though the test is quite simple.
*
* @param {MazeCell} entity the destination entity to check
*
* @returns {boolean} true if it would be OK to jump the ball
* here or false otherwise.
*/
Teleport.prototype.canTeleportToEntity = function (entity) {
// It's always OK to jump to an empty cell and it's usually OK to
// jump to an entity that is of the same type as us (although other
// code will determine if the location is not the source of the
// jump).
return (entity == null || entity.name == this.name);
};
/**
* Checks to see if this entity has any unblocked destinations or not.
* This will use the maze given to scan and see if any of the positions
* that it contains are a valid, unblocked jump destination.
*
* The position provided is the "sending" end of this black hole, which
* cannot be a possible destination even if it's not blocked.
*
* @param {Maze} maze the maze to check
* @param {Point} sourcePosition the position where the teleport
* will start
*
* @returns {boolean} true if it is possible for this
* teleport to jump a ball to a non-blocked location, false otherwise
*/
Teleport.prototype.hasUnblockedDestination = function (maze, sourcePosition) {
// Iterate over all of the destinations in our list.
for (var index = 0; index < this._destinations.length; index++) {
// Get this destination and the content of the cell at that
// position.
var thisDest = this._destinations[index];
var destCell = maze.contents.getCellAt(thisDest.x, thisDest.y);
// If the destination entity is a valid teleport location and
// it's not at the source position, we could teleport here.
if (this.canTeleportToEntity(destCell) &&
sourcePosition.equals(thisDest) == false)
return true;
}
// If we get here, none of the positions are valid.
return false;
};
/**
* We don't block the ball because we change its position when it gets
* on top of us instead of when it touches us.
......@@ -3482,22 +3542,24 @@ var nurdz;
* find one that is not blocked
*/
Teleport.prototype.ballTouch = function (maze, ball, location) {
// If there are no destinations stored, we can't teleport, so do
// nothing.
if (this.length == 0)
// If there are no destinations stored or we have no unblocked
// destinations, we can't do anything.
if (this.length == 0 || this.hasUnblockedDestination(maze, location) == false) {
console.log("Teleport has no unblocked destinations.");
return null;
// There are some destinations registered; get one out randomly.
}
// There are some destinations registered; get one out randomly and
// get the maze cell at that position.
var newPos = this.destination;
// As long as the new position is the same as the position that was
// given to us, select a new position (if possible), so that we
// don't try to teleport the ball to where it already is.
while (newPos.equals(location)) {
// If there is only a single destination, leave; we can't
// teleport because the ball is already there.
if (this.length == 1)
return null;
// Try again.
var destCell = maze.contents.getCellAt(newPos.x, newPos.y);
// As long as the cell given is a not a valid destination or the
// position is the position we were given, keep generating.
while (this.canTeleportToEntity(destCell) == false ||
newPos.equals(location)) {
// Try again. We know this won't infinitely loop because we did
// the test above to verify that we could find a location.
newPos = this.destination;
destCell = maze.contents.getCellAt(newPos.x, newPos.y);
}
// Indicate the new position
return newPos;
......@@ -3934,7 +3996,7 @@ var nurdz;
// Fill the actor pool for balls with a complete set of balls; this
// only ever happens once and is the one case where we always know
// exactly how many entities of a type we need.
for (var i = 0; i < (game.MAZE_WIDTH - 2) * 2; i++)
for (var i = 0; i < ((game.MAZE_WIDTH - 2) * 2) + 5; i++)
_this._balls.addEntity(new game.Ball(stage), false);
return _this;
}
......@@ -4254,6 +4316,26 @@ var nurdz;
// ball into it and we're done.
var below = this._contents.getBlockingCellAt(position.x, position.y + 1, isSimulation);
if (below == null) {
// It's possible for a ball to "block" a teleport exit if it
// exits and then can't drop any farther. When this happens the
// ball overwrites the teleport with itself. Although the
// teleport is still visually there and this exit can be
// selected, it cannot be entered.
//
// If we get here, we will drop the ball down, so if the cell at
// this location appears to be empty and it is also a teleport
// destination, put the teleport back.
//
// This has to be done here because the firs thing this method
// does is check and see if the tile beneath the ball is a
// teleport, so if we do this check and fix the maze too soon,
// we will teleport when we don't want to.
if (this._contents.getCellAt(ball.mapPosition.x, ball.mapPosition.y) == null &&
this._blackHole.hasDestination(ball.mapPosition)) {
console.log("Fixing broken Teleport");
this._contents.setCellAt(ball.mapPosition.x, ball.mapPosition.y, this._blackHole);
}
// Say we dropped, then update the position and leave.
ball.moveType = game.BallMoveType.BALL_MOVE_DROP;
position.y++;
return true;
......
......@@ -360,7 +360,7 @@ module nurdz.game
// Fill the actor pool for balls with a complete set of balls; this
// only ever happens once and is the one case where we always know
// exactly how many entities of a type we need.
for (let i = 0 ; i < (MAZE_WIDTH - 2) * 2 ; i++)
for (let i = 0 ; i < ((MAZE_WIDTH - 2) * 2) + 5 ; i++)
this._balls.addEntity (new Ball (stage), false);
}
......@@ -696,6 +696,28 @@ module nurdz.game
let below = this._contents.getBlockingCellAt (position.x, position.y + 1, isSimulation);
if (below == null)
{
// It's possible for a ball to "block" a teleport exit if it
// exits and then can't drop any farther. When this happens the
// ball overwrites the teleport with itself. Although the
// teleport is still visually there and this exit can be
// selected, it cannot be entered.
//
// If we get here, we will drop the ball down, so if the cell at
// this location appears to be empty and it is also a teleport
// destination, put the teleport back.
//
// This has to be done here because the firs thing this method
// does is check and see if the tile beneath the ball is a
// teleport, so if we do this check and fix the maze too soon,
// we will teleport when we don't want to.
if (this._contents.getCellAt (ball.mapPosition.x, ball.mapPosition.y) == null &&
this._blackHole.hasDestination (ball.mapPosition))
{
this._contents.setCellAt (ball.mapPosition.x, ball.mapPosition.y,
this._blackHole);
}
// Say we dropped, then update the position and leave.
ball.moveType = BallMoveType.BALL_MOVE_DROP;
position.y++;
return true;
......
......@@ -110,6 +110,20 @@ module nurdz.game
return -1;
}
/**
* Perform a check to see if this teleport instance contains the
* destination point provided.
*
* @param {Point} destination the destination to check
*
* @returns {boolean} true if this teleport entity includes
* the given point in it's destination list.
*/
hasDestination (destination : Point) : boolean
{
return this.indexOfDestination (destination) != -1;
}
/**
* Add a potential destination to this teleport instance. This can be
* invoked more than once, in which case when activated the teleport
......@@ -150,6 +164,61 @@ module nurdz.game
this._destinations.splice (index, 1);
}
/**
* Given a (possibly null) entity, determine if we could teleport to its
* location or not. This is basically a helper to make the code easier
* to read, even though the test is quite simple.
*
* @param {MazeCell} entity the destination entity to check
*
* @returns {boolean} true if it would be OK to jump the ball
* here or false otherwise.
*/
private canTeleportToEntity (entity : MazeCell) : boolean
{
// It's always OK to jump to an empty cell and it's usually OK to
// jump to an entity that is of the same type as us (although other
// code will determine if the location is not the source of the
// jump).
return (entity == null || entity.name == this.name);
}
/**
* Checks to see if this entity has any unblocked destinations or not.
* This will use the maze given to scan and see if any of the positions
* that it contains are a valid, unblocked jump destination.
*
* The position provided is the "sending" end of this black hole, which
* cannot be a possible destination even if it's not blocked.
*
* @param {Maze} maze the maze to check
* @param {Point} sourcePosition the position where the teleport
* will start
*
* @returns {boolean} true if it is possible for this
* teleport to jump a ball to a non-blocked location, false otherwise
*/
private hasUnblockedDestination (maze : Maze, sourcePosition : Point) : boolean
{
// Iterate over all of the destinations in our list.
for (let index = 0 ; index < this._destinations.length ; index++)
{
// Get this destination and the content of the cell at that
// position.
let thisDest = this._destinations[index];
let destCell = maze.contents.getCellAt (thisDest.x, thisDest.y);
// If the destination entity is a valid teleport location and
// it's not at the source position, we could teleport here.
if (this.canTeleportToEntity (destCell) &&
sourcePosition.equals (thisDest) == false)
return true;
}
// If we get here, none of the positions are valid.
return false;
}
/**
* We don't block the ball because we change its position when it gets
* on top of us instead of when it touches us.
......@@ -179,26 +248,28 @@ module nurdz.game
*/
ballTouch (maze : Maze, ball : Ball, location : Point) : Point
{
// If there are no destinations stored, we can't teleport, so do
// nothing.
if (this.length == 0)
// If there are no destinations stored or we have no unblocked
// destinations, we can't do anything.
if (this.length == 0 || this.hasUnblockedDestination (maze, location) == false)
{
console.log("Teleport has no unblocked destinations.");
return null;
}
// There are some destinations registered; get one out randomly.
// There are some destinations registered; get one out randomly and
// get the maze cell at that position.
let newPos = this.destination;
let destCell = maze.contents.getCellAt (newPos.x, newPos.y);
// As long as the new position is the same as the position that was
// given to us, select a new position (if possible), so that we
// don't try to teleport the ball to where it already is.
while (newPos.equals (location))
// As long as the cell given is a not a valid destination or the
// position is the position we were given, keep generating.
while (this.canTeleportToEntity (destCell) == false ||
newPos.equals (location))
{
// If there is only a single destination, leave; we can't
// teleport because the ball is already there.
if (this.length == 1)
return null;
// Try again.
// Try again. We know this won't infinitely loop because we did
// the test above to verify that we could find a location.
newPos = this.destination;
destCell = maze.contents.getCellAt (newPos.x, newPos.y);
}
// Indicate the new position
......