Skip to content

Instantly share code, notes, and snippets.

@Enigo
Created August 26, 2020 21:07
Show Gist options
  • Save Enigo/31bbbca0e1ceafa4d7d1c1ccd962a5c5 to your computer and use it in GitHub Desktop.
Save Enigo/31bbbca0e1ceafa4d7d1c1ccd962a5c5 to your computer and use it in GitHub Desktop.
BruteForceLevelsGenerator - click to expand!
public class BruteForceLevelsGenerator {
    private static final int MIN_NODE_COUNT_INCLUDED = 4;
    private static final int MAX_NODE_COUNT_EXCLUDED = 7;
    private static final int SIDE_SIZE = 4;  // 16 --> max 12 min 10
    
    private static final int SIDE_WITHOUT_CORNERS_SIZE = SIDE_SIZE - 2;
    private static final int MAX_COUNTER = 750_000_000;

    private final BruteForceLevelsChecker bruteForceLevelsChecker = new BruteForceLevelsChecker(SIDE_SIZE);
    private final Random random = new Random();
    private final List<Integer> cornerPositions = new ArrayList<>();
    private final List<Integer> sidePositions = new ArrayList<>();
    private List<Integer> innerPositions = new ArrayList<>();

    public void Execute(LevelsData levelsData) {
        SetupSidePositions();

        var generatedLevels = Stream.of(levelsData.levels).collect(Collectors.toCollection(ArrayList::new));
        var startLevel = generatedLevels.size() + 1;
        var gson = new Gson();
        for (var level = startLevel; level <= 4; level++) {
            GenerateLevels(generatedLevels, level);
        }
        levelsData.levels = generatedLevels.toArray(new LevelsData.LevelData[0]);
        System.out.println(gson.toJson(levelsData));
    }

    private void SetupSidePositions()
    {
        cornerPositions.add(0);
        cornerPositions.add(SIDE_SIZE - 1);
        cornerPositions.add(SIDE_SIZE * SIDE_SIZE - SIDE_SIZE);
        cornerPositions.add(SIDE_SIZE * SIDE_SIZE - 1);

        for (var i = 1; i < SIDE_SIZE - 1; i++)
        {
            sidePositions.add(i);
        }

        for (var i = SIDE_SIZE; i < cornerPositions.get(2); i += SIDE_SIZE)
        {
            sidePositions.add(i);
        }

        for (var i = SIDE_SIZE + SIDE_SIZE - 1; i < cornerPositions.get(3); i += SIDE_SIZE)
        {
            sidePositions.add(i);
        }

        for (var i = SIDE_SIZE * SIDE_SIZE - SIDE_SIZE + 1; i < cornerPositions.get(3); i++)
        {
            sidePositions.add(i);
        }

        for (var i = 0; i < SIDE_SIZE * SIDE_SIZE; i++)
        {
            innerPositions.add(i);
        }

        innerPositions.removeAll(cornerPositions);
        innerPositions.removeAll(sidePositions);
    }

    private void GenerateLevels(List<LevelsData.LevelData> generatedLevels, int level)
    {
        System.out.println("Starting for level " + level);
        var count = 0;
        List<Integer> occupiedPositions;
        List<Integer> counts;
        while (true)
        {
            var nextNodesCount = ThreadLocalRandom.current().nextInt(MIN_NODE_COUNT_INCLUDED, MAX_NODE_COUNT_EXCLUDED);
            occupiedPositions = GeneratePositions(nextNodesCount);
            counts = GenerateCounts(nextNodesCount, occupiedPositions);

            if (LevelUnique(generatedLevels, occupiedPositions, counts) && bruteForceLevelsChecker.isLevelPassable(occupiedPositions, counts))
            {
                System.out.println("*********************************");
                break;
            }

            if (count++ > 250)
            {
                throw new IllegalArgumentException("Reached LevelPassable counter state!");
            }
        }
        generatedLevels.add(new LevelsData.LevelData(level,
                SIDE_SIZE + "x" + SIDE_SIZE,
                occupiedPositions.toArray(new Integer[0]),
                counts.toArray(new Integer[0])));
    }

    private static boolean LevelUnique(List<LevelsData.LevelData> generatedLevels, List<Integer> occupiedPositions, List<Integer> counts)
    {
        return generatedLevels.stream().noneMatch(generatedLevel ->
                Arrays.equals(generatedLevel.nodes, occupiedPositions.toArray()) && Arrays.equals(generatedLevel.connectionsCount, counts.toArray()));
    }


    private List<Integer> GeneratePositions(int nextNodesCount)
    {
        var occupiedPositions = new ArrayList<Integer>();
        var counter = 0;
        var currentFreeCellsCount = SIDE_SIZE * SIDE_SIZE - nextNodesCount;
        for (var i = 0; i < nextNodesCount; i++)
        {
            while (true)
            {
                if (counter++ > MAX_COUNTER)
                {
                    throw new IllegalArgumentException("Reached positions counter state!");
                }

                var nextPosition = random.nextInt(SIDE_SIZE * SIDE_SIZE);
                if (occupiedPositions.contains(nextPosition) || IsNotAvailablePosition(nextPosition, occupiedPositions))
                {
                    continue;
                }

                var isFitForOtherPositions = true;
                for (var occupiedPosition : occupiedPositions)
                {
                    var tempOccupiedPositions = new ArrayList<>(occupiedPositions);
                    tempOccupiedPositions.remove(occupiedPosition);
                    tempOccupiedPositions.add(nextPosition);

                    if (IsNotAvailablePosition(occupiedPosition, tempOccupiedPositions))
                    {
                        isFitForOtherPositions = false;
                        break;
                    }
                }
                if (!isFitForOtherPositions) continue;

                occupiedPositions.add(nextPosition);
                break;
            }
        }

        var result = new ArrayList<Integer>(occupiedPositions.size());
        result.addAll(occupiedPositions.stream().filter(cornerPositions::contains).collect(Collectors.toList()));
        result.addAll(occupiedPositions.stream().filter(sidePositions::contains).collect(Collectors.toList()));
        result.addAll(occupiedPositions.stream().filter(innerPositions::contains).collect(Collectors.toList()));
        return result;
    }

    private List<Integer> GenerateCounts(int nextNodesCount, List<Integer> occupiedPositions)
    {
        var counter = 0;
        while (true)
        {
            var counts = new ArrayList<Integer>();
            if (counter++ > MAX_COUNTER)
            {
                throw new IllegalArgumentException("Reached counts counter state!");
            }

            for (var i = 0; i < nextNodesCount; i++)
            {
                var nextCount = ThreadLocalRandom.current().nextInt(1, 5);

                if (i == nextNodesCount - 1 && (counts.stream().mapToInt(Integer::intValue).sum() + nextCount) % 2 != 0)
                {
                    break;
                }

                var nextPosition = occupiedPositions.get(i);
                if (cornerPositions.contains(nextPosition) && nextCount > 2 - NodesCountOnCornerNextToGiven(nextPosition, occupiedPositions))
                {
                    break;
                }

                if (sidePositions.contains(nextPosition) && nextCount > 3 - NodesCountOnSideNextToGiven(nextPosition, occupiedPositions))
                {
                    break;
                }

                if (innerPositions.contains(nextPosition) && nextCount > 4 - NodesCountOnInnerNextToGiven(nextPosition, occupiedPositions))
                {
                    break;
                }

                counts.add(nextCount);
            }

            if (counts.size() == occupiedPositions.size())
            {
                return counts;
            }
        }
    }

    private boolean IsNotAvailablePosition(int nextPosition, List<Integer> occupiedPositions)
    {
        return NodesCountOnCornerNextToGiven(nextPosition, occupiedPositions) > 1 ||
                NodesCountOnSideNextToGiven(nextPosition, occupiedPositions) > 2 ||
                NodesCountOnInnerNextToGiven(nextPosition, occupiedPositions) > 3;
    }

    private int NodesCountOnCornerNextToGiven(int nextPosition, List<Integer> occupiedPositions)
    {
        var downLeftCorner = cornerPositions.get(0);
        var downRightCorner = cornerPositions.get(1);
        var upLeftCorner = cornerPositions.get(2);
        var upRightCorner = cornerPositions.get(3);

        if (nextPosition == downLeftCorner)
            return (int) occupiedPositions.stream().filter(index -> index == nextPosition + SIDE_SIZE || (index == nextPosition + 1))
                    .count();
        
        if (nextPosition == downRightCorner)
            return (int) occupiedPositions.stream().filter(index -> index == nextPosition + SIDE_SIZE || index == nextPosition - 1).count();

        if (nextPosition == upLeftCorner)
            return (int) occupiedPositions.stream().filter(index -> index == nextPosition - SIDE_SIZE || index == nextPosition + 1).count();

        if (nextPosition == upRightCorner)
            return (int) occupiedPositions.stream().filter(index ->index == nextPosition - SIDE_SIZE || index == nextPosition - 1).count();

        return 0;
    }

    private int NodesCountOnSideNextToGiven(int nextPosition, List<Integer> occupiedPositions)
    {
        var downLine = sidePositions.subList(0, SIDE_WITHOUT_CORNERS_SIZE);
        var leftLine = sidePositions.subList(SIDE_WITHOUT_CORNERS_SIZE, 2 * SIDE_WITHOUT_CORNERS_SIZE);
        var rightLine = sidePositions.subList(2 * SIDE_WITHOUT_CORNERS_SIZE, 3 * SIDE_WITHOUT_CORNERS_SIZE);
        var upLine = sidePositions.subList(3 * SIDE_WITHOUT_CORNERS_SIZE,  4 * SIDE_WITHOUT_CORNERS_SIZE);

        if (downLine.contains(nextPosition))
        {
            return (int) occupiedPositions.stream().filter(index -> index == nextPosition - 1 ||
                    index == nextPosition + 1 ||
                    index == nextPosition + SIDE_SIZE).count();
        }

        if (leftLine.contains(nextPosition))
        {
            return (int) occupiedPositions.stream().filter(index -> index == nextPosition + 1 ||
                    index == nextPosition - SIDE_SIZE ||
                    index == nextPosition + SIDE_SIZE).count();
        }


        if (rightLine.contains(nextPosition))
        {
            return (int) occupiedPositions.stream().filter(index -> index == nextPosition - 1 ||
                    index == nextPosition - SIDE_SIZE ||
                    index == nextPosition + SIDE_SIZE).count();
        }

        if (upLine.contains(nextPosition))
        {
            return (int) occupiedPositions.stream().filter(index -> index == nextPosition - 1 ||
                    index == nextPosition + 1 ||
                    index == nextPosition - SIDE_SIZE).count();
        }

        return 0;
    }

    private int NodesCountOnInnerNextToGiven(int nextPosition, List<Integer> occupiedPositions)
    {
        if (innerPositions.contains(nextPosition))
        {
            return (int) occupiedPositions.stream().filter(index -> index == nextPosition - 1 ||
                    index == nextPosition + 1 ||
                    index == nextPosition - SIDE_SIZE ||
                    index == nextPosition + SIDE_SIZE).count();
        }

        return 0;
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment