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
{
}
}
Related
I have this really weird bug where I cannot seem to delete an object from a list. I am using an Injector class to pass the same object(thus the same list) around into multiple forms. The Delete() function is just a generic delete, really nothing special. However in my unit test it keeps failing and I'm unsure why.
Injector class:
[Serializable]
public class InjectorClass
{
public OrderAdministration Administration { get; private set; }
public WareHouse WareHouse { get; private set; }
public InjectorClass()
{
Administration = new OrderAdministration(this);
WareHouse= new WareHouse();
}
}
Constructor of the WareHouse class:
public List<Part> Catalogus { get; private set; }
public List<Part> PartsInStock { get; private set; }
public WareHouse()
{
PartsInStock = new List<Part>();
AddStandardStockToCatalogus();
AddStockPartsToStock();
}
private void AddStandardStockToCatalogus()
{
Body body1 = new Body("Body7zk", 1, "Ebony", "Blue");
Body body2 = new Body("SuperXtrem368", 2, "Maple", "Red");
Neck neck1 = new Neck("Heavy-Slider", 3, "Juniper", true);
Neck neck2 = new Neck("SlickFingerBoard", 4, "Oak", false);
Fretboard fretboard1 = new Fretboard("PrettySlamFinger", 5, "Oak", false);
Fretboard fretboard2 = new Fretboard("GentleToucher", 6, "Ebony", true);
Pickups pickups1 = new Pickups("TubeScreamers", 7, Pickups.PickupType.P90);
Pickups pickups2 = new Pickups("BluesBrothers", 8, Pickups.PickupType.Humbucker);
Bridge bridge1 = new Bridge("MetalGearRipper", 9, false);
Bridge bridge2 = new Bridge("BridgeOfLove", 10, true);
Catalogus = new List<Part> { body1, body2, neck1, neck2, fretboard1, fretboard2, pickups1, pickups2, bridge1, bridge2};
}
private void AddStockPartsToStock()
{
foreach(Part p in Catalogus)
{
PartsInStock.Add(p);
}
}
The Delete() function in the same Warehouse class:
public int GetStock(Part neededPart)
{
if(neededPart == null)
{
throw new ArgumentNullException();
}
int numberInStock = 0;
foreach(Part p in PartsInStock)
{
if(p.Id == neededPart.Id)
{
numberInStock++;
}
}
return numberInStock;
}
public bool AddToStock(Part arrivedPart)
{
if(arrivedPart == null)
{
throw new ArgumentNullException();
}
if(FindByName(arrivedPart.Name).Name == arrivedPart.Name)
{
PartsInStock.Add(arrivedPart);
return true;
}
return false;
}
public bool RemovePart(Part usedPart)
{
if (GetStock(usedPart) >= 1)
{
PartsInStock.Remove(usedPart);
return true;
}
return false;
}
The unit test I run:
Neck neck1 = new Neck("Heavy-Slider", 3, "Juniper", true);
InjectorClass injector = new InjectorClass();
Assert.AreEqual(10, injector.WareHouse.PartsInStock.Count);
injector.WareHouse.RemovePart(neck1);
Assert.AreEqual(9, injector.WareHouse.PartsInStock.Count);
The output on both the assert functions is 10
Edit:
I have re-written my search and delete function to the following:
Search function:
public Part FindByNameStock(Part neededPart)
{
if (neededPart == null)
{
throw new ArgumentNullException();
}
foreach (Part p in PartsInStock)
{
if (p.Name == neededPart.Name)
{
return p;
}
}
return null;
}
Delete function:
public bool RemovePart(Part usedPart)
{
Part part = FindByNameStock(usedPart);
if (part != null)
{
PartsInStock.Remove(part);
return true;
}
return false;
}
The code now works and the unit test too.
This line PartsInStock.Remove(usedPart); will try to remove the following object
new Neck("Heavy-Slider", 3, "Juniper", true);
which is not in your list. The reason is that reference type equality is not based on the properties, but on the actual reference. Whenever you run new a new reference is created.
var neck1 = new Neck("Heavy-Slider", 3, "Juniper", true);
var neck2 = new Neck("Heavy-Slider", 3, "Juniper", true);
Console.WriteLine(neck1 == neck2); //This is false
Check the code here for an example: https://dotnetfiddle.net/7gAWID
Check here for the IEquatable implementation that will make your code work.
Two separate objects of Neck although having the same ID are different objects and hence the item is never removed.
Moreover, you can return the value of List.Remove as it also returns a bool if an item was actually removed. So if no item was removed, it returns false:
public bool RemovePart(Part usedPart)
=> GetStock(usedPart) >= 1 ?
PartsInStock.Remove(usedPart);
: false;
Use RemoveAll() to remove the item by ID. RemoveAll() returns an int representing the number of items removed:
public bool RemovePart(Part usedPart)
{
if (GetStock(usedPart) >= 1)
{
PartsInStock.RemoveAll(part => part.Id == usedPart.Id);
return true;
}
return false;
}
Also, since your RemovePart returns a bool. You can add a check to ensure if it was removed.
if (injector.WareHouse.RemovePart(neck1))
{
Assert.AreEqual(9, injector.WareHouse.PartsInStock.Count);
}
GetStock uses the Id of the part to find it, whereas PartsInStock.Remove tries to remove the part by reference - and the reference you have is different to the item in the collection you're trying to remove from.
public bool RemovePart(Part usedPart)
{
if (GetStock(usedPart) >= 1) // item is found here
{
PartsInStock.Remove(usedPart); // but usedPart is a different reference - nothing is removed
return true;
}
return false;
}
The main idea is to each member on team1 attack a random enemy on team2. The damage dealt is based on a spell casted at random from a Grimoire book.
My Code:
using System;
namespace simple_test
{
class Program
{
public class IceLance : Hechizo //hechizo == spell
{
public IceLance(uint d) : base(20)
{
danio = d; // danio == damage
return;
}
public override void Castear() // castear == cast
{
Console.WriteLine("Ice Lance");
}
protected uint danio = 5; // danio == damage
}
public abstract class Hechizo
{
protected Hechizo(uint c)
{
costo = c; // costo == cost
}
public abstract void Castear();
protected uint costo = 1;
public uint Costo
{
get { return costo; }
}
}
public class Personaje // personaje == character
{
public Personaje()
{ }
public Personaje(uint s)
{
salud = s;
}
public void RecibirDanio(uint d) // RecibirDanio == RecieveDamage
{
salud -= d; // salud = salud - d // salud == Health
}
public virtual void Ataque() // Ataque == Attack
{
Console.WriteLine("Ataque melee");
return;
}
public bool IsVivo() // IsVivo == IsAlive
{
return salud > 0;
}
protected uint salud = 100;
}
public class Humano : Personaje // Humano == Human
{
public Humano() : base()
{ }
public Humano(uint s, uint m) : base(s)
{
mana = m;
}
public void CastearGrimorio(int idx)
{
if ((grimorio == null) ||
(idx >= grimorio.Length) || (grimorio[idx] == null))
return;
uint c = grimorio[idx].Costo;
if (mana >= c)
{
mana -= c;
grimorio[idx].Castear();
}
}
public void AprenderHechizo(Hechizo h) // AprenderHechizo == LearnSpell
{
if (grimorio == null)
{
grimorio = new Hechizo[1];
}
else
{
Hechizo[] nuevoGrimorio = new Hechizo[grimorio.Length + 1];
for (uint i = 0; i < grimorio.Length; ++i)
{
nuevoGrimorio[i] = grimorio[i];
}
grimorio = nuevoGrimorio;
}
grimorio[grimorio.Length - 1] = h;
}
public override void Ataque()
{
if (grimorio == null)
Console.WriteLine("Sin mana :(");
else
CastearGrimorio(rnd.Next(0, grimorio.Length));//con esto lanza hechizos al azar
return;
}
private uint mana = 10;
private Hechizo[] grimorio;
private Random rnd = new Random();
}
static public bool TodosMuertos(Personaje[] team) // TodosMuertos == AllDead
{
for (int i = 0; i < team.Length; ++i)
{
if (team[i].IsVivo())
{
return false;
}
}
return true;
}
static void Main(string[] args)
{
Personaje[] team1 = new Personaje[41];
for (int i = 0; i < 3; ++i)
{
Humano mage = new Humano(80, 100);
mage.AprenderHechizo(new IceLance(20));
mage.AprenderHechizo(new IceLance(10));
mage.AprenderHechizo(new IceLance(0));
team1[i] = mage;
}
Personaje[] team2 = new Personaje[41];
for (int i = 0; i < 3; ++i)
{
Humano mage = new Humano(80, 100);
mage.AprenderHechizo(new IceLance(20));
mage.AprenderHechizo(new IceLance(10));
mage.AprenderHechizo(new IceLance(0));
team2[i] = mage;
}
while (!TodosMuertos(team1) && !TodosMuertos(team2)) // turnos
{
Console.WriteLine("team1");
for (int i = 0; i < team1.Length; ++i)
{
if (team1[i].IsVivo())
{
team1[i].Ataque();
// Here should go the RecieveDamage funtion
}
Console.ReadKey();
}
}
}
}
}
This one is a simplified version of the code, i.e. it only has one class and one spell.
I'm not sure how to write the part of how to select a member of the opposite team and in base of the spell casted receiveDamage according to its damage.
PS: I added some references to the spanish language used in the code
You can use the Random class to get a random number between 0 and the number of alive mages on the other team, and use that number to attack. Modify your attack function to take a target mage as a parameter and get them to take damage from there.
using System.Linq; // Add to top of page to use linq functions - .Where()
....
public class Humano : Personaje // Humano == Human
{
...
public override void Ataque(Personaje personaje)
{
if (grimorio == null)
Console.WriteLine("Sin mana :(");
else
{
// Modify CastearGrimoiro to return spell damage. Use person parameter to inflict damage on that person
uint damage = CastearGrimorio(rnd.Next(0, grimorio.Length));//con esto lanza hechizos al azar
personaje.RecibirDanio(damage);
}
}
...
static Random _random = new Random();
static void TakeTeamTurn(Personaje[] team, Personaje[] enemyTeam)
{
var aliveMages = team.Where(m => m.IsVivo()).ToList();
foreach(var mage in aliveMages)
{
// Get team to attack
var attackableMages = enemyTeam.Where(m => m.IsVivo()).ToList(); // Get a list of alive mages on the other team
var mageAttackIndex = _random.Next(attackableMages.Count); // Get a random number between 0 and the number of attackable mages
// (Not written here) Modify your Ataque method to take a mage parameter.
// In the Ataque method or some other method you can use this parameter to do damage to the target mage
var mageToAttack = attackableMages[mageAttackIndex];
mage.Ataque(mageToAttack);
}
}
static void Main(string[] args)
{
...
while (!TodosMuertos(team1) && !TodosMuertos(team2)) // turnos
{
Console.WriteLine("team1");
TakeTeamTurn(team1, team2);
Console.WriteLine("team2");
TakeTeamTurn(team2, team1);
Console.ReadKey();
}
....
Working example here - https://pastebin.com/kSregt24
First of all, here's my script:
using UnityEngine;
using System.Collections;
using Steamworks;
public class Achievements : MonoBehaviour {
public static int currentScore=0;
public static int score300 = 300;
public static int score1000 = 1000;
public static int score3600 = 3600;
public static int score18000 = 18000;
public static int score72000 = 72000;
public static int score180000 = 180000;
void Start() {
if(SteamManager.Initialized) {
string name = SteamFriends.GetPersonaName();
Steamworks.SteamUserStats.SetAchievement("NEW_ACHIEVEMENT_1_0");
Steamworks.SteamUserStats.StoreStats();
Debug.Log(name);
}
}
void Update()
{
currentScore = PlayerPrefs.GetInt("highscore");
if (currentScore == score300 && SteamManager.Initialized){
Steamworks.SteamUserStats.SetAchievement("NEW_ACHIEVEMENT_5_0");
Steamworks.SteamUserStats.StoreStats();
}
if (currentScore == score1000 && SteamManager.Initialized) {
Steamworks.SteamUserStats.SetAchievement("NEW_ACHIEVEMENT_6_0");
Steamworks.SteamUserStats.StoreStats();
}
if (currentScore == score3600 && SteamManager.Initialized) {
Steamworks.SteamUserStats.SetAchievement("NEW_ACHIEVEMENT_7_0");
Steamworks.SteamUserStats.StoreStats();
}
if (currentScore == score18000 && SteamManager.Initialized) {
Steamworks.SteamUserStats.SetAchievement("NEW_ACHIEVEMENT_8_0");
Steamworks.SteamUserStats.StoreStats();
}
}
}
As you can see, I have public integers that hold variety of numbers. I am also using current steamworks.net, and I'm trying to see if I can match both "highscore" (which is already set up and working properly) with scoreXXX. If that happens, I want script to drop an achievement.
Am I executing if(x=x) function wrong? Can someone please help?
The problem is you arent checking if the score is greater than the score benchmarks, only that its equal.
You could simplify your code a bit by putting these values into a Dictionary<int, string>:
private static Dictionary<int, string> highScoreDictionary = new Dictionary<int, string>()
{
{ 300, "NEW_ACHIEVEMENT_5_0" },
{ 1000, "NEW_ACHIEVEMENT_6_0" },
{ 3600, "NEW_ACHIEVEMENT_7_0" },
{ 18000, "NEW_ACHIEVEMENT_8_0" },
{ 72000, "NEW_ACHIEVEMENT_9_0" },
{ 180000, "NEW_ACHIEVEMENT_10_0" }
};
void Update()
{
currentScore = PlayerPrefs.GetInt("highscore");
if(SteamManager.Initialized)
{
//Order by high score, descending
foreach(var score in highScoreDictionary.OrderByDescending(x => x.Key))
{
//If the score is greater than or equal to the benchmark
//Then add the achievement
if(currentScore >= score.Key)
{
Steamworks.SteamUserStats.SetAchievement(score.Value);
Steamworks.SteamUserStats.StoreStats();
break;
}
}
}
}
I made a fiddle here. Its obviously modified a bit since I dont have access to Unity libraries there, but you can see the logic in action.
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.
Can someone give me a code sample of 2-opt algorithm for traveling salesman problem. For now im using nearest neighbour to find the path but this method is far from perfect, and after some research i found 2-opt algorithm that would correct that path to the acceptable level. I found some sample apps but without source code.
So I got bored and wrote it. It looks like it works, but I haven't tested it very thoroughly. It assumes triangle inequality, all edges exist, that sort of thing. It works largely like the answer I outlined. It prints each iteration; the last one is the 2-optimized one.
I'm sure it can be improved in a zillion ways.
using System;
using System.Collections.Generic;
using System.Linq;
namespace TSP
{
internal static class Program
{
private static void Main(string[] args)
{
//create an initial tour out of nearest neighbors
var stops = Enumerable.Range(1, 10)
.Select(i => new Stop(new City(i)))
.NearestNeighbors()
.ToList();
//create next pointers between them
stops.Connect(true);
//wrap in a tour object
Tour startingTour = new Tour(stops);
//the actual algorithm
while (true)
{
Console.WriteLine(startingTour);
var newTour = startingTour.GenerateMutations()
.MinBy(tour => tour.Cost());
if (newTour.Cost() < startingTour.Cost()) startingTour = newTour;
else break;
}
Console.ReadLine();
}
private class City
{
private static Random rand = new Random();
public City(int cityName)
{
X = rand.NextDouble() * 100;
Y = rand.NextDouble() * 100;
CityName = cityName;
}
public double X { get; private set; }
public double Y { get; private set; }
public int CityName { get; private set; }
}
private class Stop
{
public Stop(City city)
{
City = city;
}
public Stop Next { get; set; }
public City City { get; set; }
public Stop Clone()
{
return new Stop(City);
}
public static double Distance(Stop first, Stop other)
{
return Math.Sqrt(
Math.Pow(first.City.X - other.City.X, 2) +
Math.Pow(first.City.Y - other.City.Y, 2));
}
//list of nodes, including this one, that we can get to
public IEnumerable<Stop> CanGetTo()
{
var current = this;
while (true)
{
yield return current;
current = current.Next;
if (current == this) break;
}
}
public override bool Equals(object obj)
{
return City == ((Stop)obj).City;
}
public override int GetHashCode()
{
return City.GetHashCode();
}
public override string ToString()
{
return City.CityName.ToString();
}
}
private class Tour
{
public Tour(IEnumerable<Stop> stops)
{
Anchor = stops.First();
}
//the set of tours we can make with 2-opt out of this one
public IEnumerable<Tour> GenerateMutations()
{
for (Stop stop = Anchor; stop.Next != Anchor; stop = stop.Next)
{
//skip the next one, since you can't swap with that
Stop current = stop.Next.Next;
while (current != Anchor)
{
yield return CloneWithSwap(stop.City, current.City);
current = current.Next;
}
}
}
public Stop Anchor { get; set; }
public Tour CloneWithSwap(City firstCity, City secondCity)
{
Stop firstFrom = null, secondFrom = null;
var stops = UnconnectedClones();
stops.Connect(true);
foreach (Stop stop in stops)
{
if (stop.City == firstCity) firstFrom = stop;
if (stop.City == secondCity) secondFrom = stop;
}
//the swap part
var firstTo = firstFrom.Next;
var secondTo = secondFrom.Next;
//reverse all of the links between the swaps
firstTo.CanGetTo()
.TakeWhile(stop => stop != secondTo)
.Reverse()
.Connect(false);
firstTo.Next = secondTo;
firstFrom.Next = secondFrom;
var tour = new Tour(stops);
return tour;
}
public IList<Stop> UnconnectedClones()
{
return Cycle().Select(stop => stop.Clone()).ToList();
}
public double Cost()
{
return Cycle().Aggregate(
0.0,
(sum, stop) =>
sum + Stop.Distance(stop, stop.Next));
}
private IEnumerable<Stop> Cycle()
{
return Anchor.CanGetTo();
}
public override string ToString()
{
string path = String.Join(
"->",
Cycle().Select(stop => stop.ToString()).ToArray());
return String.Format("Cost: {0}, Path:{1}", Cost(), path);
}
}
//take an ordered list of nodes and set their next properties
private static void Connect(this IEnumerable<Stop> stops, bool loop)
{
Stop prev = null, first = null;
foreach (var stop in stops)
{
if (first == null) first = stop;
if (prev != null) prev.Next = stop;
prev = stop;
}
if (loop)
{
prev.Next = first;
}
}
//T with the smallest func(T)
private static T MinBy<T, TComparable>(
this IEnumerable<T> xs,
Func<T, TComparable> func)
where TComparable : IComparable<TComparable>
{
return xs.DefaultIfEmpty().Aggregate(
(maxSoFar, elem) =>
func(elem).CompareTo(func(maxSoFar)) > 0 ? maxSoFar : elem);
}
//return an ordered nearest neighbor set
private static IEnumerable<Stop> NearestNeighbors(this IEnumerable<Stop> stops)
{
var stopsLeft = stops.ToList();
for (var stop = stopsLeft.First();
stop != null;
stop = stopsLeft.MinBy(s => Stop.Distance(stop, s)))
{
stopsLeft.Remove(stop);
yield return stop;
}
}
}
}
Well, your solution to TSP is always going to be far from perfect. No code, but here's how to go about 2-Opt. It's not too bad:
You need a class called Stop that has a Next, Prev, and City property, and probably a Stops property that just returns the array containing Next and Prev.
When you link them together, we'll call that a Tour. Tour has a Stop property (any of the stops will do), and an AllStops property, whose getter just walks the stops and returns them
You need a method that takes a tour and returns its cost. Let's call that Tour.Cost().
You need Tour.Clone(), which just walks the stops and clones them individually
You need a method that generates the set of tours with two edges switched. Call this Tour.PossibleMutations()
Start with your NN solution
Call PossibleMutations() on it
Call Cost() on all of them and take the one with the lowest result
Repeat until the cost doesn't go down
If the problem is euclidian distance and you want the cost of the solution produced by the algorithm is within 3/2 of the optimum then you want the Christofides algorithm. ACO and GA don't have a guaranteed cost.