private bool punisherStep()
        {
            int i, j;
            if (punisherGoals.Count > 0)
                prevPunisherGoal = punisherGoals[0];

            for (i = 0; i < Environment.envW; i++) {
                for (j = 0; j < Environment.envH; j++) {
                    if (parentEnvironment.get(i, j) is Human && parentEnvironment.get(i, j).src.Contains("hunting")) {
                        if (prevPunisherGoal != parentEnvironment.get(i, j) && !parentEnvironment.isObstructedLos(x, y, i, j) &&
                            ((Human)parentEnvironment.get(i, j)).strategy == Strategies.Defector) {
                            //prevPunisherGoal = punisherGoals;
                                if (!punisherGoals.Contains((Human)parentEnvironment.get(i, j)))
                                    punisherGoals.Add((Human)parentEnvironment.get(i, j));
                        }
                    }
                }
            }

            if (punisherGoals == null || punisherGoals.Count == 0 || payoff < minInitialPayoff)
            {
                if (base.src.Contains("punishing")) base.setImg("caveman");
                return hunterStep();
            }
            else
            {
                if (parentEnvironment.getDistance(x, y, punisherGoals[0].x, punisherGoals[0].y) > 2)
                {
                    int dx = 0, dy = 0;
                    double dist = 1e9;
                    parentEnvironment.getBestDirection(this, punisherGoals[0], ref dx, ref dy, ref dist);
                    //return parentEnvironment.move(this, x + dx, y + dy);

                    bool success = true;
                    if (dist > 2)
                    {
                        if (parentEnvironment.get(x + dx, y + dy) == null || parentEnvironment.get(x + dx, y + dy) is Human)
                            success = parentEnvironment.move(this, x + dx * punisherSpeed, y + dy * punisherSpeed, true);
                        else
                            success = parentEnvironment.move(this, x + dx * punisherSpeed, y + dy * punisherSpeed);
                    }
                    if (dist <= 2 || !success)
                    {
                        if (parentEnvironment.get(x + dx, y + dy) == null || parentEnvironment.get(x + dx, y + dy) is Human)
                            return parentEnvironment.move(this, x + dx, y + dy, true);
                        else
                            return parentEnvironment.move(this, x + dx, y + dy);
                    }
                }
                else if (punisherGoals != null && punisherGoals.Count > 0) {
                    base.setImg("caveman_punishing");
                    parentEnvironment.lastCounts = ("Defector punished " + punisherGoals[0].payoff + "->" + (punisherGoals[0].payoff - defectorPunishment) + "\r\n") + parentEnvironment.lastCounts;
                    punisherGoals[0].setImg("caveman_punished");
                    punisherGoals[0].payoff -= defectorPunishment;
                    punisherGoals.RemoveAt(0);

                    payoff -= punisherCost;
                }
            }
            return true;
        }
        public override bool Step()
        {
            base.Step();
            payoff -= 1;

            bool d = true;
            if (age > maxAge && payoff > 0)
            {
                payoff = 0; //die
                diedAtStep = parentEnvironment.steps;
                if (!base.src.Contains("caveman_dead"))
                    parentEnvironment.lastCounts = ("Died from old age " + this.strategy.ToString() + "\r\n") + parentEnvironment.lastCounts;
                d = false;
            }

            if (payoff <= 0)
            {
                if (parentEnvironment.steps - diedAtStep <= 2)
                {
                    base.setImg("caveman_dead");
                    payoff--;
                }
                else
                {
                    if (d)
                        parentEnvironment.lastCounts = ("Died from malnourishment " + this.strategy.ToString() + "\r\n") + parentEnvironment.lastCounts;
                    parentEnvironment.remove(this);
                }
                return true;
            }
            else if (diedAtStep > 0) {
                parentEnvironment.remove(this);
                return true;
            }
            else
            {
                bool success;
                switch (strategy)
                {
                    case Strategies.Cooperator:
                        success = hunterStep();
                        break;
                    case Strategies.Defector:
                        success = hunterStep();
                        break;
                    case Strategies.Punisher:
                        success = punisherStep();
                        break;
                    default:
                        success = lonerStep();
                        break;
                }

                if (payoff > reproductionThreshold)
                {
                    //reproduce and reset payoff
                    Strategies strat = strategy;
                    payoff -= reproductionCost;
                    Human child = new Human(parentEnvironment);
                    child.init();
                    child.strategy = strat;
                    bool reproduced = false;
                    for (int i = -1; i <= 1 && !reproduced; i++)
                    {
                        for (int j = -1; j <= 1; j++)
                        {
                            if (parentEnvironment.get(x + i, y + j) == null)
                            {
                                success &= parentEnvironment.add(child, x + i, y + j);
                                reproduced = true;
                                break;
                            }
                        }
                    }
                }

                //update average observed payoffs of all humans
                List<Entity> humansInSight = parentEnvironment.getAllInLOS(x, y, this);
                if (humansInSight.Count > 0)
                    foreach (Human h in humansInSight)
                    {
                        if (h == null) continue;
                        int[] entry;
                        if (!observedPayoffs.ContainsKey(h))
                        {
                            entry = new int[4];
                            entry[0] = (int)h.payoff;
                            entry[1] = 1;
                            entry[2] = (int)h.strategy;
                            entry[3] = h.signaling ? 1 : 0;
                            observedPayoffs[h] = entry;
                        }
                        else
                        {
                            entry = (int[])observedPayoffs[h];
                            entry[3] = h.signaling ? 1 : 0;
                            if (entry[2] != (int)h.strategy)
                            { //human changed strategy
                                entry[2] = (int)h.strategy;
                                entry[1] = 1;
                                entry[0] = (int)h.payoff;
                            }
                            else
                            {
                                int n = entry[1];
                                int sum = entry[0];
                                entry[1] = n + 1;
                                entry[0] = (int)(sum * n + h.payoff) / (n + 1);
                            }
                            observedPayoffs[h] = entry;
                        }
                    }

                if (Utils.rnd.Next((int)(1.0 / strategyMutation)) == 0) {
                    Strategies strat = (Strategies)Utils.rnd.Next(4);
                    while (strat == strategy || strat == (Strategies)blockedStrategy) strat = (Strategies)Utils.rnd.Next(4);
                    strategy = strat;
                }

                return success;
            }
        }
        private void pnlContainer_MouseUp(object sender, MouseEventArgs e)
        {
            int x = (int)(e.X / (float)pnlContainer.Width * Environment.envW);
            int y = (int)(e.Y / (float)pnlContainer.Height * Environment.envH);

            if (e.Button == MouseButtons.Right)
            {
                environment.remove(x, y);

                txtHumans.Text = environment.count(new Human(environment)).ToString();
                txtMammoths.Text = environment.count(new Mammoth(environment)).ToString();
                txtPlants.Text = environment.count(new Plant(environment)).ToString();
                txtObstacles.Text = environment.count(new Stone(environment)).ToString();

                environment.draw();
            }
            else if (e.Button == MouseButtons.Middle)
            {
                Entity add;
                if (cmbAdd.Text == "Mammoth") add = new Mammoth(environment);
                else if (cmbAdd.Text == "Human") add = new Human(environment);
                else if (cmbAdd.Text == "Plant") add = new Plant(environment);
                else if (cmbAdd.Text == "Stone") add = new Stone(environment);
                else return;

                Entity ent = environment.getNearest(x, y, add);
                if (ent != null)
                    MessageBox.Show(environment.getDistance(x, y, ent.x, ent.y).ToString());
            }
            else {
                Entity ent = environment.get(x, y);
                if (ent != null)
                {
                    txtActions.Text = ("Info:\r\n" + ent.getInfo() + "\r\n(x, y):(" + x + "," + y + ")");
                }
            }
        }
        public override void init()
        {
            strategy = (Strategies)Utils.rnd.Next(4);
            while (strategy == (Strategies)blockedStrategy) strategy = (Strategies)Utils.rnd.Next(4);

            payoff = minInitialPayoff + Utils.rnd.Next(minInitialPayoff);
            prevDistance = 1e9;
            prevGoal = null;
            maxAge = maxHumanAge / 2 + Utils.rnd.Next(maxHumanAge / 2);

            punisherGoals = new List<Human>();
            prevPunisherGoal = null;

            prevHunterGoal = null;
            prevHunterDistance = 1e6;
            prevMHunters = -1;

            diedAtStep = -1;

            observedPayoffs = new Hashtable();
        }
        private void pnlContainer_MouseDoubleClick(object sender, MouseEventArgs e)
        {
            Entity add = null;
            if (cmbAdd.Text == "Mammoth") add = new Mammoth(environment);
            else if (cmbAdd.Text == "Human") add = new Human(environment);
            else if (cmbAdd.Text == "Plant") add = new Plant(environment);
            else if (cmbAdd.Text == "Stone") add = new Stone(environment);

            int x = (int)(e.X / (float)pnlContainer.Width * Environment.envW);
            int y = (int)(e.Y / (float)pnlContainer.Height * Environment.envH);

            if (add != null) add.init();
            if (environment.get(x, y) == null && add != null) environment.add(add, x, y);
            else if (environment.get(x, y) is Human)
            {
                Human h = (Human)environment.get(x, y);
                h.strategy = (Strategies)(((int)h.strategy + 1) % 4);
            }

            txtHumans.Text = environment.count(new Human(environment)).ToString();
            txtMammoths.Text = environment.count(new Mammoth(environment)).ToString();
            txtPlants.Text = environment.count(new Plant(environment)).ToString();
            txtObstacles.Text = environment.count(new Stone(environment)).ToString();

            environment.draw();
        }