xbox performance gotchas - c#

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.

Related

Trouble with simple battle game in C#

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

c# strange classes behavior

so, im making small unity game and have some classes for working with big nubmer's
here is the code for class CurrLevel
public class CurrLevel {
public CurrLevel (int levelNum, string id) {
valuesLevel = "pow" + levelNum.ToString();
if(levelNum != 0){
numberFormatSci = "10^" + levelNum.ToString();
} else {
numberFormatSci = "";
}
identificator = id;
}
public string valuesLevel;
public string numberFormatSci;
public string identificator;
public int getValue(){
return PlayerPrefs.GetInt(identificator+"-"+valuesLevel);
}
public void setValue(int value){
PlayerPrefs.SetInt(identificator+"-"+valuesLevel, value);
}
public void add(int value){
PlayerPrefs.SetInt(identificator+"-"+valuesLevel, PlayerPrefs.GetInt(identificator+"-"+valuesLevel) + value);
}
public void substract(int value){
PlayerPrefs.SetInt(identificator+"-"+valuesLevel, PlayerPrefs.GetInt(identificator+"-"+valuesLevel) - value);
}
}
here is the code for class SomeCurrency
public class SomeCurrency {
public string identificator;
public CurrLevel[] levels = new CurrLevel[10];
public SomeCurrency(string id){
identificator = id;
for(int i = 0; i < 30; i=i+3){
levels[i/3] = new CurrLevel(i, identificator);
}
}
public void add(int power, double value){
int full = (int) value;
int leftover = (int) (value*1000 - full*1000);
if(power >= 3){
levels[power/3-1].add(leftover);
}
levels[power/3].add(full);
updateValues();
}
public SomeCurrency copy(SomeCurrency CurrToCopy){
SomeCurrency copy = new SomeCurrency(CurrToCopy.identificator);
for(int i = 0; i < 30; i++){
copy.levels[i/3] = CurrToCopy.levels[i/3];
}
return copy;
}
public void addAnotherCurrency(SomeCurrency anotherCurr){
for(int i = 0; i < 30; i=i+3){
this.add(i, anotherCurr.levels[i/3].getValue());
}
updateValues();
}
public bool substractAnotherCurrency(SomeCurrency anotherCurr){
SomeCurrency buffer = copy(anotherCurr);
Debug.Log(anotherCurr.levels[1].getValue());
if(canSubstract(buffer)){
Debug.Log(anotherCurr.levels[1].getValue());
// for(int i = 27; i >= 0; i-=3){
// levels[i/3].substract(anotherCurr.levels[i/3].getValue());
// }
return true;
} else {
return false;
}
}
public bool canSubstract(SomeCurrency fromWhereSubstract){
bool possible = false;
for(int i = 0; i < 30; i+=3){
fromWhereSubstract.levels[i/3].substract(levels[i/3].getValue());
if(i != 27){
if(fromWhereSubstract.levels[i/3].getValue() < 0){
fromWhereSubstract.levels[i/3+1].substract(1);
fromWhereSubstract.levels[i/3].add(1000);
}
}
}
if(fromWhereSubstract.levels[9].getValue() < 0){
possible = true;
}
return possible;
}
public void setValue(int power, double value){
int full = (int) value;
int leftover = (int) (value*1000 - full*1000);
if(power >= 3){
string beforeid = identificator+"-"+levels[power/3-1].valuesLevel;
PlayerPrefs.SetInt(beforeid,leftover);
}
string thisid = identificator+"-"+levels[power/3].valuesLevel;
PlayerPrefs.SetInt(thisid,full);
updateValues();
}
public string getStringValue(){
int maxlvl = 0;
for(int i = 27; i >= 0; i=i-3){
if(levels[i/3].getValue() > 0){
maxlvl = i/3;
break;
}
}
string result = levels[maxlvl].getValue().ToString();
if(maxlvl > 0){
string leftover = levels[maxlvl-1].getValue().ToString();
while(leftover.Length != 3){
leftover = "0"+leftover;
}
result += "." + leftover + "*" + levels[maxlvl].numberFormatSci;
}
return result;
}
public void resetValues(){
for(int i = 0; i < 30; i+=3){
levels[i/3].setValue(0);
}
}
private void updateValues(){
for(int i = 0; i < 27; i=i+3){
levels[i/3] = new CurrLevel(i, identificator);
if(levels[i/3].getValue() >= 1000){
levels[i/3].setValue(levels[i/3].getValue()-1000);
levels[i/3+1].setValue(levels[i/3+1].getValue()+1);
}
}
}
}
So basicly, in the code i create to new variables type of SomeCurrency
public NumberFormatting.SomeCurrency playerScore = new NumberFormatting.SomeCurrency("playerScore");
public NumberFormatting.SomeCurrency playerClickValue = new NumberFormatting.SomeCurrency("playerClickValue");
playerScore.resetValues();
playerScore.add(6, 1.32);
playerClickValue.resetValues();
playerClickValue.add(3, 103.831);
And later, when player clicks the button i try to substract one from another
Debug.Log(playerClickValue.levels[1].getValue());
Debug.Log(playerScore.substractAnotherCurrency(playerClickValue));
Debugger firstly print 103 (original value of playerClickValue.levels[1].getValue() from click function), then it prints 103 again (from the function substractAnotherCurrency before if(canSubstract(buffer)), but printing the same variable after this canSubstract shows the value of 783. So, my functions somehow change original value of playerClickValue every time i call substractAnotherCurrency.
What should i change to keep the playerClickValue same, but still checking can i suubstract it from another SomeCurrency, and after checking if i can - do so.
In C#, objects are passed by reference, meaning that if you modify an object in a fonction, it will be modified everywhere. You can read more about it, it will be important when coding. It seems you tried to do something like a copy somewhere, but you don't use the copy at all.
Also, are you sure you want to edit a variable in canSubstact?
The name suggest if will just return a bool and not change anything but you actually call substact in it
fromWhereSubstract.levels[i/3+1].substract(1);

c# Constructor not called

My code just prints:
welcome to battleship
but the ship coordinates or ship created
and even the end "Press any key to exit"
is not printed.
My suspicion goes to the Constructor, looks like its not called but why my program is counting and not crashing?
Im running the code on a Mac with Xamarin Studio.
Code:
using System;
using System.Collections.Generic;
namespace battleship
{
class MainClass
{
public static void Main(string[] args)
{
Console.WriteLine("welcome to battleship");
EnumShipClassification.ShipClassification cruiser = EnumShipClassification.ShipClassification.theCruiser;
Coordinate startCoordinate = new Coordinate(5, 5);
EnumDirection.Direction direction = EnumDirection.Direction.west;
Ship ship = new Ship(cruiser, startCoordinate, direction);
Console.WriteLine("ship created"); // #debug
Console.WriteLine("all coords: " + ship.coordinates);
//#end
Console.WriteLine("Press any key to exit.");
Console.ReadLine();
}
}
public class Ship
{
public int size; // values from wikipedia
public int amount; // values from wikipedia
public List<Coordinate> coordinates = new List<Coordinate>();
public EnumShipClassification.ShipClassification classification;
public Ship(EnumShipClassification.ShipClassification classification, Coordinate startCoordinate, EnumDirection.Direction toDirection)
{
Console.WriteLine("Ship Constructor called "); // #debug
this.classification = classification;
Console.WriteLine("classification added"); // #debug
switch (classification)
{
case EnumShipClassification.ShipClassification.theBattleship: size = 5; amount = 1; break;
case EnumShipClassification.ShipClassification.theCruiser: size = 4; amount = 2; break;
case EnumShipClassification.ShipClassification.theDestroyer: size = 3; amount = 3; break;
case EnumShipClassification.ShipClassification.theSubmarine: size = 2; amount = 4; break;
default: break;
}
Console.WriteLine("switch ended with size {0} and amout {1}", size, amount);
for (int i = 0; i < size; i++)
{
coordinates.Add(startCoordinate.advancedBy(i, toDirection));
Console.WriteLine("i is: " + i); // #debug
}
}
}
public class EnumDirection
{
public enum Direction
{
north, south, east, west // + all (Direction[] array with content [.n, .s, .e, .w]
}
public Direction[] all = { Direction.north, Direction.south, Direction.east, Direction.west };
}
public class EnumShipClassification
{
public enum ShipClassification // numbers: BoatSize
{
theBattleship = 5, theCruiser = 4, theDestroyer = 3, theSubmarine = 2
}
}
public class Coordinate
{
public int row;
public int column;
private int fieldSize = 10;
public void init()
{
row = 0;
column = 0;
}
public Coordinate invalid = new Coordinate(-1, -1);
public Coordinate(int row, int column)
{
this.row = row;
this.column = column;
}
public Coordinate getRandomCoordinate(int betweenX, int Y)
{
Random r = new Random(fieldSize);
return new Coordinate(r.Next(), r.Next());
}
public Coordinate neighbor(EnumDirection.Direction inDirection)
{
return advancedBy(1, inDirection: inDirection);
}
public Coordinate advancedBy(int displacement, EnumDirection.Direction inDirection)
{
switch (inDirection)
{
case EnumDirection.Direction.north: return new Coordinate(column: column, row: row + displacement);
case EnumDirection.Direction.south: return new Coordinate(column: column, row: row - displacement);
case EnumDirection.Direction.east: return new Coordinate(column: column + displacement, row: row);
case EnumDirection.Direction.west: return new Coordinate(column: column - displacement, row: row);
default: break; // never happens
}
return null; // never happens
}
public string description
{
get
{
return "column: " + column + ", row: " + row;
}
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
try
{
Coordinate _obj = (Coordinate)obj;
return _obj.row == this.row && _obj.column == this.column;
}
catch
{
return false;
}
}
public override int GetHashCode()
{
return 1; // TODO: return a real value
}
public static bool operator ==(Coordinate lhs, Coordinate rhs)
{
return lhs.row == rhs.row && lhs.column == rhs.column;
}
public static bool operator !=(Coordinate lhs, Coordinate rhs)
{
return lhs.row != rhs.row && lhs.column != rhs.column;
}
}
Change
public Coordinate invalid = new Coordinate(-1, -1);
To
public static Coordinate invalid = new Coordinate(-1, -1);
public Coordinate invalid = new Coordinate(-1, -1); causes stack overflow. that's because new Coordinate initialized inside Coordinate class so every time new coordinate must be created. Make the field static so that is created once and can be used for all instances.
You can also add readyonly keyword if the reference is not supposed to change.
public readonly static Coordinate invalid = new Coordinate(-1, -1);

Comparing two objects with Visual Studio Unit Testing Framework

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
{
}
}

traveling salesman problem, 2-opt algorithm c# implementation

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.

Categories