// for a given unit, get a list of all the possible fields in moving range of the unit
    List <MGPumField> getPossibleFields(MGPumUnit unit)
    {
        List <MGPumField> possibleFields = new List <MGPumField>();

        Vector2Int position = unit.field.coords;

        MGPumField field = new MGPumField(unit.coords.x, unit.coords.y);

        for (int horizontal = 0; horizontal <= unit.currentSpeed; horizontal++)
        {
            for (int vertical = 0; vertical <= unit.currentSpeed; vertical++)
            {
                // check every direction
                if (state.getField(position + Vector2Int.up * vertical + Vector2Int.left * horizontal) != null)
                {
                    field = state.getField(position + Vector2Int.up * vertical + Vector2Int.left * horizontal);
                    if (!possibleFields.Contains(field) && field.coords != position && state.fields.inBounds(field.coords) && state.getUnitForField(field) == null)
                    {
                        possibleFields.Add(field);
                    }
                }

                if (state.getField(position + Vector2Int.up * vertical + Vector2Int.right * horizontal) != null)
                {
                    field = state.getField(position + Vector2Int.up * vertical + Vector2Int.right * horizontal);
                    if (!possibleFields.Contains(field) && field.coords != position && state.fields.inBounds(field.coords) && state.getUnitForField(field) == null)
                    {
                        possibleFields.Add(field);
                    }
                }

                if (state.getField(position + Vector2Int.down * vertical + Vector2Int.left * horizontal) != null)
                {
                    field = state.getField(position + Vector2Int.down * vertical + Vector2Int.left * horizontal);
                    if (!possibleFields.Contains(field) && field.coords != position && state.fields.inBounds(field.coords) && state.getUnitForField(field) == null)
                    {
                        possibleFields.Add(field);
                    }
                }

                if (state.getField(position + Vector2Int.down * vertical + Vector2Int.right * horizontal) != null)
                {
                    field = state.getField(position + Vector2Int.down * vertical + Vector2Int.right * horizontal);
                    if (!possibleFields.Contains(field) && field.coords != position && state.fields.inBounds(field.coords) && state.getUnitForField(field) == null)
                    {
                        possibleFields.Add(field);
                    }
                }
            }
        }

        return(possibleFields);
    }
    // method to score a move command
    private int scoreMove(MGPumMoveCommand move)
    {
        int score = 0;

        MGPumGameState copiedState = state.deepCopy();

        MGPumAIPlayerDummyController dummy1 = new MGPumAIPlayerDummyController(playerID);
        MGPumAIPlayerDummyController dummy2 = new MGPumAIPlayerDummyController(1 - playerID);

        MGPumGameController copiedController = new MGPumGameController(dummy1, dummy2, copiedState);

        copiedController.acceptCommand(move);

        MGPumUnit copiedUnit = null;

        if (copiedController.state.getUnitForField(move.chain.getLast()) != null)
        {
            copiedUnit = copiedController.state.getUnitForField(move.chain.getLast());
        }
        else
        {
            // this should never happen, but just in case we give it a really bad score
            return(-1000);
        }

        // get fields for different vision ranges
        List <MGPumField> possibleEnemyFields               = getPossibleEnemyFields(copiedUnit, copiedState, 0);
        List <MGPumField> possibleEnemyFieldsExtraRange     = getPossibleEnemyFields(copiedUnit, copiedState, 2);
        List <MGPumField> possibleEnemyFieldsExtraRangeKing = getPossibleEnemyFields(copiedUnit, copiedState, 1);
        List <MGPumField> allyFields = getAllyFields(copiedUnit, copiedState);

        foreach (MGPumField allyField in allyFields)
        {
            // if Bummz is nearby, reduce score
            if (state.getUnitForField(allyField).unitID == "PUM007")
            {
                score -= 5;
            }
        }


        //Debug.Log("Move: " + move);
        //Debug.Log("Possible Enemy Fields Count: " + possibleEnemyFields.Count);

        // if buffy or haley find a move to reach a lot of allies, score will be increased
        if (state.getUnitForField(move.chain.getFirst()).unitID == "PUM013" || state.getUnitForField(move.chain.getFirst()).unitID == "PUM014")
        {
            score += (allyFields.Count * 3);
        }
        // if unit is bummz, get away from teammates and go towards enemies
        if (state.getUnitForField(move.chain.getFirst()).unitID == "PUM007")
        {
            score += (possibleEnemyFieldsExtraRange.Count * 5);
            score -= (allyFields.Count * 2);
        }
        else if (state.getUnitForField(move.chain.getFirst()).unitID == "PUM004")
        {
            // Wolli should tank, sending him towards enemies rewards points
            score += (possibleEnemyFieldsExtraRange.Count * 3);
        }
        else if (state.getUnitForField(move.chain.getFirst()).unitID == "PUM008")
        {
            // the more wounded our king is, the higher the penalty for stepping near enemies
            score -= (possibleEnemyFieldsExtraRangeKing.Count * (state.getUnitForField(move.chain.getFirst()).damage + 1));
        }
        else
        {
            // the more enemies in attack range of the unit, the higher the penalty
            score -= (possibleEnemyFields.Count * 3);
        }

        // method in case no prior weighting has been given
        if (score == 0)
        {
            Debug.Log("Score ist 0");
            List <MGPumField> allEnemyFields = getPossibleEnemyFields(copiedUnit, copiedState, 7);
            foreach (MGPumField enemyField in allEnemyFields)
            {
                // move towards enemies
                if (Vector2Int.Distance(move.chain.getFirst().coords, enemyField.coords) > Vector2Int.Distance(move.chain.getLast().coords, enemyField.coords))
                {
                    score += 1;
                    break;
                }
            }
        }

        //Debug.Log("Score sollte hier negativ sein: " + score);

        List <MGPumAttackCommand> attackCommands = new List <MGPumAttackCommand>();
        int bestAttackScore = 0;
        MGPumAttackCommand bestAttackCommand = null;

        foreach (MGPumField enemyField in possibleEnemyFields)
        {
            MGPumAttackCommand attackCommand = findAttack(enemyField, copiedUnit, copiedState);
            //Debug.Log(attackCommand);

            if (attackCommand != null)
            {
                attackCommands.Add(attackCommand);
            }
        }

        if (attackCommands.Count > 0)
        {
            foreach (MGPumAttackCommand ac in attackCommands)
            {
                int attackScore = scoreAttack(ac);
                if (attackScore >= bestAttackScore)
                {
                    bestAttackScore   = attackScore;
                    bestAttackCommand = ac;
                }
            }
        }

        // final score is based on movement score and attack score
        score += bestAttackScore;

        //Debug.Log("Best Move Score: " + bestAttackScore);
        //Debug.Log("Best Move: "+ bestAttackCommand);
        return(score);
    }
 // method for evaluating how strong a unit is
 private int scoreUnit(MGPumUnit unit)
 {
     return(unit.currentRange + unit.currentSpeed + unit.currentPower);
 }
    // find possible attack and return it
    MGPumAttackCommand findAttack(MGPumField field, MGPumUnit unit, MGPumGameState state)
    {
        // can we find this out in a better way?
        queued       = new bool[8, 8];
        tilesToVisit = new SortedSet <Vector2Int>(new AStarComparer(field.coords));
        predecessors = new Dictionary <Vector2Int, Vector2Int>();
        //distance = new Dictionary<Vector2Int, int>();

        tilesToVisit.Add(unit.field.coords);
        queued[unit.field.coords.x, unit.field.coords.y] = true;

        int recursion    = 0;
        int maxRecursion = 500;

        while (tilesToVisit.Count > 0)
        {
            recursion++;
            if (recursion > maxRecursion)
            {
                break;
            }

            Vector2Int position = tilesToVisit.Min;
            tilesToVisit.Remove(position);

            if (!state.fields.inBounds(position))
            {
                continue;
            }

            //touchedTiles.Add(position);

            if (position == field.coords)
            {
                List <Vector2Int> path = new List <Vector2Int>();
                path.Add(position);

                //reconstruct path in reverse
                while (predecessors.ContainsKey(path[0]))
                {
                    path.Insert(0, predecessors[path[0]]);
                }

                MGPumAttackChainMatcher matcher = unit.getAttackMatcher();

                MGPumFieldChain chain = new MGPumFieldChain(unit.ownerID, matcher);

                for (int i = 0; i < path.Count; i++)
                {
                    chain.add(state.getField(path[i]));
                }

                if (chain.isValidChain())
                {
                    MGPumAttackCommand ac = new MGPumAttackCommand(this.playerID, chain, unit);
                    return(ac);
                }

                continue;
            }

            foreach (Vector2Int direction in getDirections())
            {
                Vector2Int neighbor = position + direction;

                if (!state.fields.inBounds(neighbor))
                {
                    continue;
                }

                if (!queued[neighbor.x, neighbor.y])
                {
                    queued[neighbor.x, neighbor.y] = true;
                    tilesToVisit.Add(neighbor);
                    predecessors.Add(neighbor, position);
                    //distance.Add(neighbor, )
                }
            }
        }
        return(null);
    }
    // return a list of fields where allied units are standing
    List <MGPumField> getAllyFields(MGPumUnit unit, MGPumGameState state)
    {
        List <MGPumField> possibleFields = new List <MGPumField>();

        Vector2Int position = unit.field.coords;

        MGPumField field     = new MGPumField(unit.coords.x, unit.coords.y);
        MGPumUnit  foundUnit = null;

        for (int horizontal = 0; horizontal <= unit.currentSpeed; horizontal++)
        {
            for (int vertical = 0; vertical <= unit.currentSpeed; vertical++)
            {
                if (state.getField(position + Vector2Int.up * vertical + Vector2Int.left * horizontal) != null)
                {
                    field     = state.getField(position + Vector2Int.up * vertical + Vector2Int.left * horizontal);
                    foundUnit = state.getUnitForField(field);
                    if (foundUnit != null)
                    {
                        if (!possibleFields.Contains(field) && field.coords != position && state.fields.inBounds(field.coords) && foundUnit.ownerID == unit.ownerID)
                        {
                            possibleFields.Add(field);
                        }
                    }
                }

                if (state.getField(position + Vector2Int.up * vertical + Vector2Int.right * horizontal) != null)
                {
                    field     = state.getField(position + Vector2Int.up * vertical + Vector2Int.right * horizontal);
                    foundUnit = state.getUnitForField(field);
                    if (foundUnit != null)
                    {
                        if (!possibleFields.Contains(field) && field.coords != position && state.fields.inBounds(field.coords) && foundUnit.ownerID == unit.ownerID)
                        {
                            possibleFields.Add(field);
                        }
                    }
                }

                if (state.getField(position + Vector2Int.down * vertical + Vector2Int.left * horizontal) != null)
                {
                    field     = state.getField(position + Vector2Int.down * vertical + Vector2Int.left * horizontal);
                    foundUnit = state.getUnitForField(field);
                    if (foundUnit != null)
                    {
                        if (!possibleFields.Contains(field) && field.coords != position && state.fields.inBounds(field.coords) && foundUnit.ownerID == unit.ownerID)
                        {
                            possibleFields.Add(field);
                        }
                    }
                }

                if (state.getField(position + Vector2Int.down * vertical + Vector2Int.right * horizontal) != null)
                {
                    field     = state.getField(position + Vector2Int.down * vertical + Vector2Int.right * horizontal);
                    foundUnit = state.getUnitForField(field);
                    if (foundUnit != null)
                    {
                        if (!possibleFields.Contains(field) && field.coords != position && state.fields.inBounds(field.coords) && foundUnit.ownerID == unit.ownerID)
                        {
                            possibleFields.Add(field);
                        }
                    }
                }
            }
        }

        return(possibleFields);
    }