001package squidpony.squidgrid.mapping;
002
003import squidpony.ArrayTools;
004import squidpony.squidmath.GWTRNG;
005import squidpony.squidmath.IRNG;
006
007/**
008 * Meant to produce the sort of narrow, looping, not-quite-maze-like passages found in a certain famous early arcade game.
009 * Created by Tommy Ettinger on 3/30/2016.
010 */
011public class PacMazeGenerator {
012    public IRNG rng;
013    public int width, height;
014    private boolean[][] map;
015    private int[][] env;
016    private char[][] maze;
017
018    public PacMazeGenerator() {
019        this(250, 250);
020    }
021
022    public PacMazeGenerator(int width, int height) {
023        this.height = height;
024        this.width = width;
025        rng = new GWTRNG();
026    }
027
028    public PacMazeGenerator(int width, int height, IRNG rng) {
029        this.height = height;
030        this.width = width;
031        this.rng = rng;
032    }
033
034    private static final byte[] //unbiased_connections = new byte[]{3, 5, 6, 7, 9, 10, 11, 12, 13, 14, 15},
035            connections = new byte[]{
036            3, 5, 6, 9, 10, 12,/*
037                    3, 5, 6, 9, 10, 12,
038                    3, 5, 6, 9, 10, 12,
039                    3, 5, 6, 9, 10, 12,
040                    7, 11, 13, 14,
041                    7, 11, 13, 14,
042                    15*/
043    };
044    private static final int connections_length = connections.length;
045
046    private boolean write(boolean[][] m, int x, int y, int xOffset, int yOffset, boolean value) {
047        int nx = x * 3 + xOffset + 1, ny = y * 3 + yOffset + 1;
048        if (nx >= 0 && nx < m.length && ny >= 0 && ny < m[nx].length) {
049            m[nx][ny] = value;
050            return true;
051        }
052        return false;
053    }
054
055    public boolean[][] create() {
056        map = new boolean[width][height];
057        byte[][] conns = new byte[(width + 2) / 3][(height + 2) / 3];
058        int xOff = (width % 3 == 1) ? -1 : 0, yOff = (height % 3 == 1) ? -1 : 0;
059        for (int x = 0; x < (width + 2) / 3; x++) {
060            for (int y = 0; y < (height + 2) / 3; y++) {
061                conns[x][y] = connections[rng.nextInt(connections_length)];
062            }
063        }
064        for (int x = 0; x < (width + 2) / 3; x++) {
065            for (int y = 0; y < (height + 2) / 3; y++) {
066                write(map, x, y, xOff, yOff, true);
067                if (x > 0 && ((conns[x - 1][y] & 1) != 0 || (conns[x][y] & 2) != 0)) {
068                    conns[x - 1][y] |= 1;
069                    conns[x][y] |= 2;
070                }
071                if (x < conns.length - 1 && ((conns[x + 1][y] & 2) != 0 || (conns[x][y] & 1) != 0)) {
072                    conns[x + 1][y] |= 2;
073                    conns[x][y] |= 1;
074                }
075                if (y > 0 && ((conns[x][y - 1] & 4) != 0 || (conns[x][y] & 8) != 0)) {
076                    conns[x][y - 1] |= 4;
077                    conns[x][y] |= 8;
078                }
079                if (y < conns[0].length - 1 && ((conns[x][y + 1] & 8) != 0 || (conns[x][y] & 4) != 0)) {
080                    conns[x][y + 1] |= 8;
081                    conns[x][y] |= 4;
082                }
083            }
084        }
085
086        for (int x = 1; x < (width - 1) / 3; x++) {
087            for (int y = 1; y < (height - 1) / 3; y++) {
088                if (Integer.bitCount(conns[x][y]) >= 4) {
089                    //byte temp = connections[rng.nextInt(connections_length)];
090                    int temp = 1 << rng.nextInt(4);
091                    conns[x][y] ^= temp;
092                    if ((temp & 2) != 0) conns[x - 1][y] ^= 1;
093                    else if ((temp & 1) != 0) conns[x + 1][y] ^= 2;
094                    else if ((temp & 8) != 0) conns[x][y - 1] ^= 4;
095                    else if ((temp & 4) != 0) conns[x][y + 1] ^= 8;
096                }
097            }
098        }
099        for (int x = 0; x < (width + 2) / 3; x++) {
100            for (int y = 0; y < (height + 2) / 3; y++) {
101                write(map, x, y, xOff, yOff, true);
102                if (x > 0 && (conns[x][y] & 2) != 0)
103                    write(map, x, y, xOff - 1, yOff, true);
104                if (x < conns.length - 1 && (conns[x][y] & 1) != 0)
105                    write(map, x, y, xOff + 1, yOff, true);
106                if (y > 0 && (conns[x][y] & 8) != 0)
107                    write(map, x, y, xOff, yOff - 1, true);
108                if (y < conns[0].length - 1 && (conns[x][y] & 4) != 0)
109                    write(map, x, y, xOff, yOff + 1, true);
110            }
111        }
112        int upperY = height - 1;
113        int upperX = width - 1;
114        for (int i = 0; i < width; i++) {
115            map[i][0] = false;
116            map[i][upperY] = false;
117        }
118        for (int i = 0; i < height; i++) {
119            map[0][i] = false;
120            map[upperX][i] = false;
121        }
122        return map;
123    }
124
125    public char[][] generate() {
126        create();
127        maze = new char[width][height];
128        env = new int[width][height];
129        for (int x = 0; x < width; x++) {
130            for (int y = 0; y < height; y++) {
131                maze[x][y] = map[x][y] ? '.' : '#';
132                env[x][y] = map[x][y] ? DungeonUtility.CORRIDOR_FLOOR : DungeonUtility.CORRIDOR_WALL;
133            }
134        }
135
136        return maze;
137    }
138
139    public int[][] getEnvironment() {
140        if (env == null)
141            return ArrayTools.fill(DungeonUtility.CORRIDOR_WALL, width, height);
142        return env;
143    }
144
145    /**
146     * Gets the maze as a 2D array of true for passable or false for blocked.
147     *
148     * @return a 2D boolean array; true is passable and false is not.
149     */
150    public boolean[][] getMap() {
151        if (map == null)
152            return new boolean[width][height];
153        return map;
154    }
155
156    /**
157     * Gets the maze as a 2D array of ',' for passable or '#' for blocked.
158     *
159     * @return a 2D char array; '.' is passable and '#' is not.
160     */
161    public char[][] getMaze() {
162        if (maze == null)
163            return ArrayTools.fill('#', width, height);
164        return maze;
165    }
166
167    /**
168     * Gets the maze as a 2D array of ',' for passable or '#' for blocked.
169     *
170     * @return a 2D char array; '.' is passable and '#' is not.
171     */
172    public char[][] getDungeon() {
173        return getMaze();
174    }
175}