I want to make a carousel that's linear WITH wrap enabled (so you can scroll indefinitely in either direction and it'll just wrap around) They have a CarouselType.Linear, but it isn't wrap enabled. Anyone know of how to do this with either iCarousel or UICollectionViews? I can't find any good resources on how to make a Custom iCarousel layout... Any good references there?
Alliance Carousel
this.Carousel = new CarouselView(frame)
{
DataSource = new CylindericalDataSource(this),
Delegate = new CylindericalDelegate(this)
};
this.Carousel.CarouselType = CarouselType.Linear;
this.Carousel.ConfigureView();
this.View.AddSubview(this.Carousel);
------------EDIT--------------
To answer #Rahul's question
public override void DidScroll (CarouselView carouselView)
{
base.DidScroll (carouselView);
nint numOfItems = carouselView.NumberOfItems;
if (this._parentController != null &&
this._parentController._studioViewController != null &&
this._parentController._studioViewController.SuccessfullyReceivedProjectAndPageData () &&
this._parentController._studioViewController.HasFlowCardCarouselFinishedLoading () &&
numOfItems > 0)
{
// Enforce min and max values fro ScrollOffset so the user can't scroll the carousel off screen. This is required when wrapping is turned off.
nfloat maxScrollOffset = ((nfloat)numOfItems) - 1f;
nfloat minScrollOffset = 0f;
if (carouselView.ScrollOffset < minScrollOffset)
{
carouselView.ScrollOffset = minScrollOffset;
}
if (carouselView.ScrollOffset > maxScrollOffset)
{
carouselView.ScrollOffset = maxScrollOffset;
}
}
}
Figured it out. Override the ValueForOption method and force the CarouseOption.Wrap to be equal to 1.0f. See below
/*--------------------------------------------------------------------------------*/
// Class: CylindericalDelegate
/*--------------------------------------------------------------------------------*/
public class CylindericalDelegate : CarouselViewDelegate
{
/*--------------------------------------------------------------------------------*/
// Constructors
/*--------------------------------------------------------------------------------*/
public CylindericalDelegate()
{
}
/*--------------------------------------------------------------------------------*/
// CylindericalDelegate Implementation
/*--------------------------------------------------------------------------------*/
public override nfloat ValueForOption(CarouselView carousel, CarouselOption option, nfloat aValue)
{
if (option == CarouselOption.Spacing)
{
return aValue * 1.1f;
}
if (option == CarouselOption.Wrap)
{
return 1.0f;
}
return aValue;
}
/*--------------------------------------------------------------------------------*/
}
/*--------------------------------------------------------------------------------*/
Related
This question already has answers here:
Copy Arrays to Array
(7 answers)
Closed last year.
I'm trying to make a snake game that causes as few foreground updates in the console as possible, so I have two grids in one class. The first is the current grid and the second is used to check whether something has changed in the current grid.
The second grid should therefore always have the value of the current grid from the last game tick, but the problem is, if I change the current grid, the second grid also changes, even though the two variables have nothing to do with each other.
public class Canvas
{
public Size Bounds { get; set; }
public Position Location { get; set; }
public Table Grid { get; set; } // That is grid one
public Position? Apple { get; set; }
private Table oldState; // And that is the second one
public Canvas(Position location, Size bounds)
{
Bounds = bounds;
Location = location;
Grid = new(location, bounds);
oldState = new(location, bounds);
}
public Task DrawField(bool borders = false, bool ignoreIf = false)
{
if (borders) DrawBorders();
if (Grid.Cells == oldState.Cells && !ignoreIf) return Task.CompletedTask;
for (int y = 0; y < Bounds.Height - 1; y += 2)
{
for (int x = 0; x < Bounds.Width; x++)
{
// Here is the problem: Both grids are the same
if (Grid.Cells[x, y].Type != oldState.Cells[x, y].Type || Grid.Cells[x, y + 1].Type != oldState.Cells[x, y + 1].Type || ignoreIf)
{
Console.BackgroundColor = Grid.Cells[x, y].GetColor() ?? (IsEven(x) ? ConsoleColor.Green : ConsoleColor.DarkGreen);
Console.ForegroundColor = Grid.Cells[x, y + 1].GetColor() ?? (IsEven(x) ? ConsoleColor.DarkGreen : ConsoleColor.Green);
Console.SetCursorPosition(Location.Left + x, Location.Top + y / 2);
Console.Write("▄");
}
}
}
oldState.SetCells(Grid.Cells);
return Task.CompletedTask;
}
public Task ClearField()
{
Grid.ClearField();
oldState.ClearField();
return Task.CompletedTask;
}
public Task SetNewApple()
{
List<Position> availableFields = new();
for (int y = 0; y < Bounds.Height; y++)
{
for (int x = 0; x < Bounds.Width; x++)
{
if (Grid.Cells[x, y].Type is Cell.CellTypes.Empty) availableFields.Add(new(x, y));
}
}
if (availableFields.Count == 0) return Task.CompletedTask; // GAME WIN
Random r = new(DateTime.Now.Millisecond);
if (Apple != null) Grid.Cells[Apple.Value.Left, Apple.Value.Top] = new(Cell.CellTypes.Empty);
Apple = availableFields[r.Next(0, availableFields.Count)];
Cell[,] newField = Grid.Cells;
newField[Apple.Value.Left, Apple.Value.Top] = new(Cell.CellTypes.Apple);
Grid.SetCells(newField); // This is where the apple is added for the snake game and that ONLY for grid 1, but this is where the second grid gets the apple added as well.
return Task.CompletedTask;
}
}
public class Table
{
public Cell[,] Cells { get; private set; }
public Position Location { get; set; }
public Size Bounds { get { return new(Cells.GetLength(0), Cells.GetLength(1)); } }
public Table(Position pos, Size size)
{
Cells = new Cell[size.Width, size.Height];
Location = pos;
ClearField();
}
public Task SetCells(Cell[,] cells)
{
Cells = cells;
return Task.CompletedTask;
}
public Task ClearField()
{
for (int y = 0; y < Cells.GetLength(1); y++)
{
for (int x = 0; x < Cells.GetLength(0); x++)
{
Cells[x, y] = new(Cell.CellTypes.Empty);
}
}
return Task.CompletedTask;
}
}
public class Cell
{
public CellTypes Type { get; set; }
public Cell()
{
Type = CellTypes.Empty;
}
public Cell(CellTypes type) => Type = type;
public ConsoleColor? GetColor() => (Type is CellTypes.Snake) ? ConsoleColor.White : (Type is CellTypes.Apple) ? ConsoleColor.Red : null;
public enum CellTypes { Empty, Snake, Apple }
}
The Position struct has two integers: Top and Left.
If you need more information about the code and my problem, feel free to ask, thank you.
Cell[,] newField = Grid.Cells;
A struct is passed by value, and copied on assignment. But array of anything (struct or class) is not a struct, and is passed by reference. You don't get a copy of the array even if Cell were a struct, which it isn't. After the line above newField and Grid.Cells reference the same array of cells.
To copy an array you actually have to copy all elements of the array into another array of the same dimensions. Manually. In your case I think you can get away with the "shallow copy". Cell is a class, but think here it's fine to have two arrays referencing the same cells. One thing though, Why not just use CellType instead of a Cell? ConsoleColor could just be an extension method in a separate class, instead of a property, and Cell class would become unnecessary.
I am trying to do something similar as UserInteractionEnabled = false in iOS, but it seems that this property is not available in NSView. I also see that one approach is to implement the hittest method to return null, but the hittest for this lass already has a non-trivial implementation and I don't think I can set it to return null. I found a (deprecated) property called acceptsTouchEnabled, and I m wondering if it can achieve the same thing as UserInteractionEnabled.
The project is done in Xamarin, btw.
FreeHandView = new PaintBoardControl
{
BackgroundColor = UIColor.Clear,
TranslatesAutoresizingMaskIntoConstraints = false,
UserInteractionEnabled = false
};
this is the original declaration of the variable, where the UserInteractionEnabled is set to false.
and this is the implementation of the hittest method in my Mac app:
public override NSView HitTest(CGPoint aPoint)
{
aPoint = ContentContainer.ConvertPointFromView(aPoint, this);
aPoint = new CGPoint(aPoint.X + _freehandViewLeftConstraint.Constant,
aPoint.Y + _freehandViewTopConstraint.Constant);
if (_hitTestBitArray != null && aPoint.X >= 0 && aPoint.Y >= 0 &&
_hitTestBitArrayWidth > aPoint.X && _hitTestBitArrayHeight > aPoint.Y)
{
var index = aPoint.Y * _hitTestBitArrayWidth + aPoint.X;
return _hitTestBitArray.Get((int)index) ? this : null;
}
return null;
}
Sometimes hitTest don't work at all try using something like this maybe mousedown can help you
public class ExtendedNSView : NSView
{
public bool IsEnabled { get; set; }
public override void MouseDown(NSEvent theEvent)
{
if (IsEnabled)
{
base.MouseDown(theEvent);
}
}
}
I'm using TDD to develop an Android mobile game with Unity. I isolated the core logic inside normal classes. Currently, I'm intend to test the following scenario : Player or AI activates the "End turn" option and the game manager goes to the next character waiting for its turn.
I tried to use AreEqual, AreNotEqual, AreSame, AreNotSame methods from the framework. AreSame and AreNotSame returned with failed. AreEquals and AreNotEquals returned with the following exception: "AssertFailException". I verified by debbuging my test that both references are not null. I have overridden the Equals and GetHashCode from compare 2 objects and made sure that the class I'm trying to check in my test implemented the interface IEqualityComparer. I'm trying to figure out what's wrong but I can't.
Basically my test is doing the following :
Generating the game manager
Generating the characters of the game
Add two sample characters to the list of characters
Creating an EndTurnCommand object
Activate the first player
Make the first player call end turn and activate the next character (second sample character)
Compare previous active character and current and make sure that they're not the same
Below you can find the TestMethod
var gm = new Assets.GameCoreLogic.Managers.GameManager();
gm.GenerateCharacters();
var characGen = new CharacterGenerator();
var stats = new BaseCharacterStats();
stats.StatGeneration(500, 1, 1, 1, 1, 1, 1, 1);
var charac = characGen.Generate(new Health((int)stats.HealthPoints), stats, PlayerDirection.Down);
var secondCharac = characGen.Generate(new Health((int)stats.HealthPoints+1), stats, PlayerDirection.Up);
gm.EveryCharacters.Add(charac);
gm.EveryCharacters.Add(secondCharac);
var gcm = new GameCommandMenu();
var etc = new EndTurnCommand(gcm);
gm.ActivatePlayer(0);
var active = gm.ActivePlayer;
etc.Execute(active,gm);
Assert.AreNotSame(active, gm.ActivePlayer);
To make sure that the TestMethod can make sense for readers, I'm putting the source code for EndTurnCommand (Command Pattern object), BaseCharacter(with properties and the override methods), the Generate method from CharacterGenerator.
EndTurnCommand
public class EndTurnCommand: CharacterActions
{
public EndTurnCommand(IReceiver receiver) : base(receiver)
{
}
public void Execute(BaseCharacter caller, GameManager manager)
{
if (caller == null)
throw new ArgumentException();
if(manager == null)
throw new ArgumentException();
manager.GoToNextCharacter();
}
}
BaseCharacter
public class BaseCharacter: IEqualityComparer<BaseCharacter>
{
public BaseCharacterStats BaseStats { get; set; }
public Health Health { get; set; }
public PlayerDirection Direction;
public List<BaseEnemy> CurrentEnnemies;
public List<BaseCharacter> TeamMembers;
public int MovementPoints = 4;
public GameMap GameMap;
public Cell CurrentCoordinates;
public bool IsDead; //Testing
public BaseCharacter(Health health = null, BaseCharacterStats stats = null, PlayerDirection direction = default(PlayerDirection))
{
BaseStats = stats;
Health = health;
Direction = direction;
CurrentEnnemies = new List<BaseEnemy>();
TeamMembers = new List<BaseCharacter>();
}
public bool Equals(BaseCharacter x, BaseCharacter y)
{
if (ReferenceEquals(x, y)) return true;
if (ReferenceEquals(x, null) || ReferenceEquals(y, null)) return false;
return x.IsDead == y.IsDead &&
x.Health.CurrentHealth == y.Health.CurrentHealth &&
x.Health.StartingHealth == y.Health.StartingHealth &&
x.BaseStats.Power == y.BaseStats.Power &&
x.BaseStats.Defense == y.BaseStats.Defense &&
x.BaseStats.MagicPower == y.BaseStats.MagicPower &&
x.BaseStats.MagicResist == y.BaseStats.MagicResist &&
x.BaseStats.Luck == y.BaseStats.Luck &&
x.BaseStats.Speed == y.BaseStats.Speed &&
x.BaseStats.Agility == y.BaseStats.Agility &&
x.Direction == y.Direction;
}
public int GetHashCode(BaseCharacter obj)
{
var hashCodeStats = obj.BaseStats?.GetHashCode() ?? 0;
var hasCodeHealth = obj.Health?.GetHashCode() ?? 0;
var hasCodeDirection = obj.Direction.GetHashCode();
return hashCodeStats ^ hasCodeHealth ^ hasCodeDirection;
}
}
CharacterGenerator
public class CharacterGenerator
{
public BaseCharacter Generate(Health h, BaseCharacterStats bcs, PlayerDirection pd)
{
return new BaseCharacter(h,bcs,pd);
}
}
Can you try to change your Equals signature to this?
public override bool Equals(object obj)
{
BaseCharacter that = (BaseCharacter)obj;
return this.IsDead == that.IsDead
&& this.Health.CurrentHealth == that.Health.CurrentHealth;
}
== updated ==
I went ahead an implement a simple class and a test case. Maybe it's easier for you to spot the difference.
using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace TestProject1
{
[TestClass]
public class UnitTest1
{
[TestMethod]
public void TestMethod1()
{
XCharacter x = new XCharacter() { isDead = true, Health = 100 };
YCharacter y1 = new YCharacter() { isDead = true, Health = 100};
YCharacter y2 = new YCharacter() { isDead = true, Health = 0 };
Assert.AreEqual(x, y1); //ok
Assert.AreNotEqual(x, y2); //ok
Assert.AreEqual(x,y2); // not ok
}
}
public abstract class BaseCharacter
{
public bool isDead { get; set; }
public int Health { get; set; }
public override bool Equals(object obj)
{
BaseCharacter that = (BaseCharacter)obj;
return this.isDead == that.isDead && this.Health == that.Health;
}
}
public class XCharacter : BaseCharacter
{
}
public class YCharacter : BaseCharacter
{
}
}
While attempting to build an inventory system including stackables. The inventory system works great, except for the stackables part.
Right now, I've hit a full stop due to an infinite loop.
Here's the code, with comments, so hopefully you'll be able to understand what I was trying to accomplish.
void AddItem(int id, int count)
{
while(count > 0) // Continue running as long as there is an item to be added
{
for(int i = 0; i < inventory.Count; i++) // Search through the inventory
{
if(inventory[i].itemID == id) // We've found an item with the appropriate ID
{
int maxAdd = inventory[i].itemMaxStack - inventory[i].itemCurrStack; // Figure out how much we can add to this stack
if(count > maxAdd) // There's not enough room to fit the entire stack, so add what we can and continue the -for- loop
{
inventory[i].itemCurrStack = inventory[i].itemMaxStack;
count -= maxAdd;
continue;
}
if(count <= maxAdd) // There's enough room to fit the entire stack, so add it in.
{
inventory[i].itemCurrStack += count;
count = 0;
}
if(inventory[i].itemCurrStack == inventory[i].itemMaxStack) // We found a stack, but it's already full, so continue the -for- loop
{
continue;
}
} else if(inventory[i].itemName == null) // There were no items with the specified ID, so let's create one.
{
for(int j = 0; j < database.items.Count; j++)
{
if(database.items[j].itemID == id)
{
inventory[i] = database.items[j];
break; // Break out of the -for- loop, since we've found what we're looking for.
}
}
}
}
}
}
You don't really need to do this with a loop. You can just stack the difference between the source and the target's max, and if there's any left, just dump it in the inventory as a new stack.
Edit: Added a little more plumbing to clarify, based on your comment. This is significantly different than your original question, but it demonstrates my point.
public abstract class Loot
{
public int Count { get; set; }
public virtual int MaxCount { get; set; }
}
public class Inventory : ICollection<Loot>
{
public void Stack(Loot source, Loot target)
{
var availableOnTarget = target.MaxCount - target.Count;
var amountToStack = Math.Min(availableOnTarget, source.Count);
target.Count += amountToStack;
source.Count -= amountToStack;
if (target.Count == target.MaxCount && source.Count > 0)
{
this.Add(source);
}
}
// ICollection implementation...
}
// This could be in Inventory, or the Player, or a gameplay manager...
// Personally I'd implement it in the Inventory class, if there was only
// one player with only one inventory. I'm sticking to the semantics of
// my first version, though.
public class Caller
{
public void TryAddItemToInventory<TLoot>(Inventory inventory, TLoot itemToAdd) where TLoot:Loot
{
var sourceType = itemToAdd.GetType();
var stackTarget = inventory.OfType<TLoot>().First(i => i.Count < i.MaxCount);
if (stackTarget != null)
{
inventory.Stack(itemToAdd, stackTarget);
}
else
{
inventory.Add(itemToAdd);
}
// You need to check if the inventory exists, if it has enough room to accommodate
// the item, what happens to overflow, etc. Left all that out for brevity.
}
}
Background: I have a game that i've been primarily testing on a (lowish spec) laptop. It runs fine. When testing on the xbox there is one method that appears to be seriously effecting performance/fps when it gets called. On the PC you wouldn't notice any slowdown/skipped frames.
I've profiled on the xbox and on average i get a GC about 1-2 times a second, taking 20-40ms when the game is running.
I've noticed no change in GC rate or duration when my slow method is running.
Next i tried profiling on the PC to identify what in the method was taking the most time. Turns out it was doing List<T>.Contains(), so i created my own class that had a List<T> and a HashSet<T> internally, so i could use the HashSet<T> internally for Contains().
I've reached the point now, where i can't really think of what else to tune without changing the algorithm, and i think the algorithm is as simple as it's going to get.
I know i can't profile to get method times / percentages on the xbox, so i'm at a bit of a loss as to what to try next.
I've included the code below which is used to simulate flowing water in a tile-based system. It runs once per water tile, trying to move it downwards possibly through other water tiles (generally speaking).
Question: I want to know if i'm doing anything obviously wrong (i.e. would impact xbox performance badly) here. Is calling Funcs slow? Am i getting boxing anywhere with my Point objects? etc.
Appologies for the vast amount of code! The tiles themselves come from an object pool to minimise GC. The InternalThink() method is what's causing all the issues.
public abstract class TileFlowing : TileMovable
{
private FastList<Point> TeleportSwapLocations = new FastList<Point>();
private FastList<Point> PossibleMoveLocations = new FastList<Point>();
private FastQueue<Point> PositionsToCheck = new FastQueue<Point>();
private FastList<Point> PositionsChecked = new FastList<Point>();
private static Comparison<Point> _PossibleMoveComparer;
public bool Static = false;
protected abstract Func<Point, int> PossibleMoveLocationOrdering { get; }
protected abstract Func<Point, Point, bool, bool> MoveSidewaysFunc { get; }
protected abstract int MaxUnitsWithin { get; }
protected abstract int Weight { get; }
public int UnitsWithin;
public int AvailableToFlowThrough = 0;
protected virtual bool RecurseTilesUp { get { return true; } }
protected virtual bool RecurseTilesDown { get { return true; } }
protected virtual bool RecurseTilesLeft { get { return true; } }
protected virtual bool RecurseTilesRight { get { return true; } }
public TileFlowing()
: base()
{
}
public override void LoadContent(Components.TileGridManagement.GameGrid Owner)
{
base.LoadContent(Owner);
_PossibleMoveComparer = (Point P1, Point P2) =>
{
int Result = PossibleMoveLocationOrdering(P1) -
PossibleMoveLocationOrdering(P2);
if (Result == 0)
Result = (IsSameType(P1) ? (_Owner[P1] as TileFlowing).UnitsWithin : 0) -
(IsSameType(P2) ? (_Owner[P2] as TileFlowing).UnitsWithin : 0);
return Result;
};
}
public override void ResetProperties()
{
base.ResetProperties();
Static = false;
UnitsWithin = MaxUnitsWithin;
AvailableToFlowThrough = 0;
}
public override void CopyProperties(Tile SourceTile)
{
base.CopyProperties(SourceTile);
Static = (SourceTile as TileFlowing).Static;
UnitsWithin = (SourceTile as TileFlowing).UnitsWithin;
AvailableToFlowThrough = (SourceTile as TileFlowing).AvailableToFlowThrough;
}
public override void Think()
{
base.Think();
InternalThink(false, false);
}
public override void FactoryThink()
{
base.FactoryThink();
InternalThink(true, false);
}
public void FlowThink(bool CalledFromFactoryThink)
{
InternalThink(CalledFromFactoryThink, true);
}
private bool IsSameType(Point Position)
{
return IsSameType(Position.X, Position.Y);
}
private bool IsSameType(int X, int Y)
{
return _Owner[X, Y] != null && _Owner[X, Y].GetType() == GetType();
}
private bool IsDifferentFlowingTile(Point Position)
{
return IsDifferentFlowingTile(Position.X, Position.Y);
}
private bool IsDifferentFlowingTile(int X, int Y)
{
return !IsSameType(X, Y) && _Owner[X, Y] is TileFlowing;
}
protected void CheckPosition(Point PositionToCheck, Point TilePosition, bool CalledFromFactoryThink, bool CalledFromFlowThink,
ref FastList<Point> PossibleMoveLocations, ref FastList<Point> TeleportSwapLocations, ref FastQueue<Point> PositionsToCheck,
Func<Point, Point, bool, bool> ClearCheckFunc)
{
if (IsSameType(PositionToCheck))
{
if (!PositionsToCheck.Contains(PositionToCheck))
PositionsToCheck.Enqueue(PositionToCheck);
}
else if (_Owner[PositionToCheck] is TileFlowing && (ClearCheckFunc == null || ClearCheckFunc(PositionToCheck, TilePosition, CalledFromFactoryThink)))
{
// If we weigh more than the other tile, or we're called from the factory think (are under pressure)
if ((_Owner[PositionToCheck] as TileFlowing).Weight < Weight || CalledFromFactoryThink)
{
if (!(_Owner[PositionToCheck] as TileFlowing).Static || !CalledFromFlowThink)
PossibleMoveLocations.Add(PositionToCheck);
}
}
else if (_Owner.IsClear(PositionToCheck) && (ClearCheckFunc == null || ClearCheckFunc(PositionToCheck, TilePosition, CalledFromFactoryThink)))
{
PossibleMoveLocations.Add(PositionToCheck);
}
}
private int PossibleMoveLocationsComparer(Point P1, Point P2)
{
return (PossibleMoveLocationOrdering(P1) - PossibleMoveLocationOrdering(P2)) * 1000 +
((IsSameType(P1) ? (_Owner[P1] as TileFlowing).UnitsWithin : 0) - (IsSameType(P2) ? (_Owner[P2] as TileFlowing).UnitsWithin : 0)) * 100;
}
protected void InternalThink(bool CalledFromFactoryThink, bool CalledFromFlowThink)
{
AvailableToFlowThrough = 0;
TeleportSwapLocations.Clear();
PossibleMoveLocations.Clear();
PositionsToCheck.Clear();
PositionsChecked.Clear();
PositionsToCheck.Enqueue(Position);
while (PositionsToCheck.Count != 0)
{
Point PositionToCheck = PositionsToCheck.Dequeue();
if (!PositionsChecked.Contains(PositionToCheck) &&
((_Owner[PositionToCheck] as TileFlowing).AvailableToFlowThrough < MaxUnitsWithin || CalledFromFactoryThink))
{
if (((_Owner[PositionToCheck] as TileFlowing).Static && !CalledFromFactoryThink))
continue;
if (PositionToCheck != Position)
{
(_Owner[PositionToCheck] as TileFlowing).AvailableToFlowThrough++;
}
PositionsChecked.Add(PositionToCheck);
if ((_Owner[PositionToCheck] as TileFlowing).UnitsWithin < MaxUnitsWithin && PositionToCheck != Position)
{
PossibleMoveLocations.Add(PositionToCheck);
if (CalledFromFactoryThink && (_Owner[PositionToCheck] as TileFlowing).UnitsWithin + UnitsWithin <= MaxUnitsWithin)
continue;
}
// Check below
Point PosBelow = new Point(PositionToCheck.X + TileDirection.Down.X, PositionToCheck.Y + TileDirection.Down.Y);
CheckPosition(PosBelow, Position, CalledFromFactoryThink, CalledFromFlowThink, ref PossibleMoveLocations, ref TeleportSwapLocations, ref PositionsToCheck, null);
// Check one horizontal direction
Point RandHDir = Randomiser.GetHDirection();
Point RandHPos = new Point(RandHDir.X + PositionToCheck.X, RandHDir.Y + PositionToCheck.Y);
CheckPosition(RandHPos, Position, CalledFromFactoryThink, CalledFromFlowThink, ref PossibleMoveLocations, ref TeleportSwapLocations, ref PositionsToCheck, MoveSidewaysFunc);
// Check the other horizontal direction
Point OtherHDir = new Point(-RandHDir.X, RandHDir.Y);
Point OtherHPos = new Point(OtherHDir.X + PositionToCheck.X, OtherHDir.Y + PositionToCheck.Y);
CheckPosition(OtherHPos, Position, CalledFromFactoryThink, CalledFromFlowThink, ref PossibleMoveLocations, ref TeleportSwapLocations, ref PositionsToCheck, MoveSidewaysFunc);
// Check above if appropriate
Point AbovePos = new Point(PositionToCheck.X + TileDirection.Up.X, PositionToCheck.Y + TileDirection.Up.Y);
if (TileDirection.Below(AbovePos, Position) || CalledFromFactoryThink)
{
CheckPosition(AbovePos, Position, CalledFromFactoryThink, CalledFromFlowThink, ref PossibleMoveLocations, ref TeleportSwapLocations, ref PositionsToCheck, null);
}
}
}
PossibleMoveLocations.Sort(_PossibleMoveComparer);
bool Moved = false;
if (PossibleMoveLocations.Count != 0)
{
if (CalledFromFactoryThink)
{
while (UnitsWithin != 0 && PossibleMoveLocations.Count != 0)
{
int OldUnitsWithin = UnitsWithin;
Moved = IterateTeleport(CalledFromFactoryThink, ref PossibleMoveLocations, (P) => !IsDifferentFlowingTile(P), Moved);
if (UnitsWithin == OldUnitsWithin)
{
Moved = IterateTeleport(CalledFromFactoryThink, ref PossibleMoveLocations, (P) => IsDifferentFlowingTile(P), Moved);
}
PossibleMoveLocations.RemoveAll(P => IsSameType(P) && (_Owner[P] as TileFlowing).UnitsWithin == MaxUnitsWithin);
}
}
else
{
Moved = Moved || Teleport(PossibleMoveLocations[0]);
}
// If we did move and not because we were forced to then mark all mercury tiles above and left or right as not static.
if (!CalledFromFactoryThink)
{
_Owner.RecurseTiles(Position,
(P) => RecurseTilesUp && IsSameType(P.X + TileDirection.Up.X, P.Y + TileDirection.Up.Y),
(P) => RecurseTilesDown && IsSameType(P.X + TileDirection.Down.X, P.Y + TileDirection.Down.Y),
(P) => RecurseTilesLeft && IsSameType(P.X + TileDirection.Left.X, P.Y + TileDirection.Left.Y),
(P) => RecurseTilesRight && IsSameType(P.X + TileDirection.Right.X, P.Y + TileDirection.Right.Y),
(P) =>
{
if (IsSameType(P))
(_Owner[P] as TileFlowing).Static = false;
});
}
}
else
{
// Mark this tile as static if we didn't move, no water moved through this tile and we have all the units we can take.
Static = (AvailableToFlowThrough == 0) && (UnitsWithin == MaxUnitsWithin); // TODO: 9 Fix flowing tiles becoming static and getting stuck
}
if (!Moved)
{
// If we haven't moved
if (TeleportSwapLocations.Count != 0)
{
Moved = TeleportSwap(TeleportSwapLocations[0]);
}
if(!Moved)
{
// If we didn't move, undo checked tiles
foreach (var CheckedPosition in PositionsChecked)
{
(_Owner[CheckedPosition] as TileFlowing).AvailableToFlowThrough--;
}
}
}
}
private bool IterateTeleport(bool CalledFromFactoryThink, ref FastList<Point> SortedPossibleMoveLocations, Func<Point, bool> PossibleMoveLocationsFilter, bool Moved)
{
foreach (var PossibleMoveLocation in SortedPossibleMoveLocations)
{
if (PossibleMoveLocationsFilter(PossibleMoveLocation))
{
if (IsDifferentFlowingTile(PossibleMoveLocation))
{
bool OldStatic = Static;
Static = true;
(_Owner[PossibleMoveLocation] as TileFlowing).FlowThink(CalledFromFactoryThink);
Static = OldStatic;
}
bool TeleportResult = Teleport(PossibleMoveLocation);
Moved = Moved || TeleportResult;
if (TeleportResult)
break;
}
}
return Moved;
}
protected bool TeleportSwap(Point NewPosition)
{
TileFlowing OurNewTile = (TileFlowing)Tile.GetNewTileFromStore(this);
OurNewTile.CopyProperties(this);
OurNewTile.Position = NewPosition;
TileFlowing ReplacedTile = (TileFlowing)Tile.GetNewTileFromStore(_Owner[NewPosition]);
ReplacedTile.CopyProperties(_Owner[NewPosition]);
ReplacedTile.Position = Position;
_Owner.ClearTile(NewPosition);
_Owner.AddTileToGrid(OurNewTile);
_Owner.ClearTile(Position);
_Owner.AddTileToGrid(ReplacedTile);
UnitsWithin = 0;
return true;
}
protected bool Teleport(Point NewPosition)
{
if (IsDifferentFlowingTile(NewPosition))
{
return TeleportSwap(NewPosition);
}
else
{
TileFlowing NewTile;
bool RemovedAllUnits = false;
int NewPositionUnits = IsSameType(NewPosition) ? (_Owner[NewPosition] as TileFlowing).UnitsWithin : 0;
int UnitsToRemove = Math.Min(UnitsWithin,
Math.Max(1,
Math.Min(Math.Abs(UnitsWithin - NewPositionUnits) / 2,
MaxUnitsWithin - NewPositionUnits)));
UnitsWithin -= UnitsToRemove;
if (IsSameType(NewPosition))
{
(_Owner[NewPosition] as TileFlowing).UnitsWithin += UnitsToRemove;
}
else
{
NewTile = (TileFlowing)Tile.GetNewTileFromStore(this);
NewTile.Position = NewPosition;
NewTile.UnitsWithin = UnitsToRemove;
_Owner.AddTileToGrid(NewTile);
}
if (UnitsWithin == 0)
{
_Owner.ClearTile(Position);
RemovedAllUnits = true;
}
return RemovedAllUnits;
}
}
}
You can use a Stopwatch to do some basic profiling, I think. And you could consider setting a millisecond or iteration 'limit' on the think method - so it won't ever take more than 5ms / 200 iterations or whatever works.
Just a wild guess here, but...
This repeated lookup and cast in the while loop looks a bit dodgy to me: _Owner[PositionToCheck] as TileFlowing. I would be tempted to pull it out as a variable and see what happens.