Java random Prim algorithm and A * algorithm produce random mazes

Posted by bhawap on Mon, 27 Dec 2021 14:04:11 +0100

Procedure requirements:

1. Randomly generate maze map

The maze map needs to be randomly generated in the game and Java util. Random class uses the random number generation method provided by random class to randomly generate obstacles, paths or rewards. Maze map is represented and stored by two-dimensional array.

2. Judge whether the player has successfully entered the level

According to whether the player's current position is located at the exit point of the map, judge whether the player has successfully broken through the pass. If successful, modify the game points according to the number of game steps.

3. Game main control module

The main control module of the game is a key if/else control module based on user input.

Enter "←↑↓→" to update the game interface and current position according to whether it can move, provided that the game is not over.

Enter W/S to increase / decrease the size of the maze accordingly; Enter "Y" to display the maze path, and enter "X" to refresh the maze;

If the game ends, exit the game main control module and output the winning or losing status of the player; Otherwise, the main control module will constantly modify the map status and update the player points according to the direction keys entered by the user.

catalogue

1. Maze class (Prim method)

2. Astar class (A * algorithm)

3. MazePanel class:

4. Main class

Win or lose, output

Where: 1 represents obstacle, 0 represents access, 2 represents reward, and 8 represents current position.

By default, the maze is surrounded by obstacles. The entry point is located in the upper left corner of the map and the exit point is located in the lower right corner of the map.

The user inputs "awsd" characters on the console, representing the direction keys "←↑↓→"; Enter the "x" character to exit the game early.

Under a finite number of steps, judge

Household score.

Users are encouraged to complete the game with shorter paths and more rewards. Set the following integration principles:

At the end of the game, the points increase the difference between the limited step size of the game and the current step size of the game. If the limited step size is 30 and the player takes 20 steps to reach the end this time, the score will be increased by 10.

Program running results

 

 

 

 

1. Maze class (Prim method)

package com.company;

import java.util.Random;

class Maze {
    // Initialize a map. All roads are blocked by default
    //The size of the resulting two-dimensional array is actually (2width + 1) * (2weight + 1)
    private static int width;
    private static int height;
    public static int[][] map;// Array of mazes
    private static int r;
    private static int c;

    Maze(int r0, int c0) {
        width = r0;
        height = c0;
        r = 2 * width + 1;
        c = 2 * height + 1;
        map = new int[r][c];
    }

    public static int[][] Init() {
        for (int i = 0; i < r; i++) // Set all cells as walls
            for (int j = 0; j < c; j++)
                map[i][j] = 0;// 0 is the wall and 1 is the road
        // The middle grid is 1
        for (int i = 0; i < width; i++)
            for (int j = 0; j < height; j++)
                map[2 * i + 1][2 * j + 1] = 1;// 0 is the wall and 1 is the road
        // Prim algorithm
        accLabPrime();
        return map;
    }

    public static void accLabPrime() {
        // ok stores the accessed queue, not stores the non accessed queue
        int[] ok, not;
        int sum = width * height;
        int count = 0;// Record the number of visited points
        ok = new int[sum];
        not = new int[sum];
        // Offset in each direction of width offset in each direction of height 0 left 1 right 3 up 2 down
        int[] offR = {-1, 1, 0, 0};
        int[] offC = {0, 0, 1, -1};

        // The offset in four directions is left, right, up and down
        int[] offS = {-1, 1, width, -width}; // Moving up and down is a line of change
        // 0 in initialization ok means no access, and 0 in not means no access
        for (int i = 0; i < sum; i++) {
            ok[i] =0;
            not[i] = 0;
        }
        // starting point
        Random rd = new Random();
        ok[0] = rd.nextInt(sum);// starting point
        int pos = ok[0];
        // First point deposit
        not[pos] = 1;
        while (count < sum) {
            // Take out the current point
            int x = pos % width;
            int y = pos / width;// The coordinates of the point
            int offpos = -1;
            int w = 0;
            // Try it in all four directions until you dig through
            while (++w < 5) {
                // Random access to the nearest point
                int point = rd.nextInt(4); // 0-3
                int repos;
                int move_x, move_y;
                // Calculate the moving direction
                repos = pos + offS[point];// Subscript after move
                move_x = x + offR[point];// Orientation after movement
                move_y = y + offC[point];
                if (move_y >= 0 && move_x >= 0 && move_x < width && move_y < height && repos >= 0 && repos < sum
                        && not[repos] != 1) {
                    not[repos] = 1;// Mark the point as accessed
                    ok[++count] = repos;// ++count represents the number of accessed points, and repos represents the subscript of the point
                    pos = repos;// Take this point as the starting point
                    offpos = point;
                    // Place 1 in the middle of the adjacent grid
                    map[2 * x + 1 + offR[point]][2 * y + 1 + offC[point]] = 1;
                    break;
                } else {
                    if (count == sum - 1)
                        return;
                }
            }
            if (offpos < 0) {// There is no way to go around. Find a new starting point from the way you have walked
                pos = ok[rd.nextInt(count + 1)];
            }
        }
    }
}

2. Astar class (A * algorithm)

package com.company;

import java.util.ArrayList;
import java.util.List;

class AStar {
    public static int[][] NODES;//Define a maze cell array
    public  int STEP = 10;//Set the weight of each step to 10
    private ArrayList<Node> openList = new ArrayList<Node>();//Maintain an open list
    private ArrayList<Node> closeList = new ArrayList<Node>();//Maintain a closed list

    AStar(int[][] map) {
        NODES=map;//Initialize the Maze unit as the newly generated corresponding map, transfer the map generated in Maze class to NODES, and then find the path with A * algorithm on the basis of this map
        Node startNode = new Node(1, 1);//starting point
        Node endNode = new Node(map.length-2, map.length-2);//End
        Node parent = findPath(startNode, endNode); //Parent node
        ArrayList<Node> arrayList = new ArrayList<Node>();
        while (parent != null) {

            arrayList.add(new Node(parent.x, parent.y));
            parent = parent.parent;
        }

        //Print a map with a path and view it in the console output
        System.out.println("\n"+"Print map with path:");
        for (int i = 0; i < NODES.length; i++) {
            for (int j = 0; j < NODES.length; j++) {
                if (exists(arrayList, i, j)) {
                    NODES[i][j]=2;//Mark the grid in the closed list as 2. For the convenience of drawing the system pathfinding path on the interface later
                }
                System.out.print(NODES[i][j] + " ");
            }
            System.out.println();
        }
    }
    public static int[][] ans(){
        return NODES;
    }
    //Method for finding the node with the lowest F value in the open list
    public Node findMinFNodeInOpneList() {
        Node tempNode = openList.get(0);
        for (Node node : openList) {
            if (node.F < tempNode.F) {
                tempNode = node;
            }
        }
        return tempNode;
    }

    //The method of traversing the upper, lower, left and right neighbors of the current node,
    public ArrayList<Node> findNeighborNodes(Node currentNode) {
        ArrayList<Node> arrayList = new ArrayList<Node>();
        // Only up, down, left and right, not diagonal
        int topX = currentNode.x;
        int topY = currentNode.y - 1;
        if (canReach(topX, topY) && !exists(closeList, topX, topY)) {
            arrayList.add(new Node(topX, topY));
        }
        int bottomX = currentNode.x;
        int bottomY = currentNode.y + 1;
        if (canReach(bottomX, bottomY) && !exists(closeList, bottomX, bottomY)) {
            arrayList.add(new Node(bottomX, bottomY));
        }
        int leftX = currentNode.x - 1;
        int leftY = currentNode.y;
        if (canReach(leftX, leftY) && !exists(closeList, leftX, leftY)) {
            arrayList.add(new Node(leftX, leftY));
        }
        int rightX = currentNode.x + 1;
        int rightY = currentNode.y;
        if (canReach(rightX, rightY) && !exists(closeList, rightX, rightY)) {
            arrayList.add(new Node(rightX, rightY));
        }
        return arrayList;
    }

    //Judge whether the coordinates here are reachable. If they exceed the boundary or are walls, they are not reachable
    public boolean canReach(int x, int y) {
        if (x >=0 && x < NODES.length && y >=0 && y < NODES.length && NODES[x][y]==1) {
            return true;
        }
        return false;
    }

    //A * routing process
    public Node findPath(Node startNode, Node endNode) {
        openList.add(startNode);// Add the starting point to the open list
        while (openList.size() > 0) {
            Node currentNode = findMinFNodeInOpneList();// Traverse the open list, find the node with the smallest F value, and take it as the node to be processed at present
            openList.remove(currentNode);// Remove from open list
            closeList.add(currentNode);// Move this node to the close list
            ArrayList<Node> neighborNodes = findNeighborNodes(currentNode);
            for (Node node : neighborNodes) {//Traverse four neighbors
                if (exists(openList, node)) {
                    foundPoint(currentNode, node);
                } else {
                    notFoundPoint(currentNode, endNode, node);
                }
            }
            if (find(openList, endNode) != null) {
                return find(openList, endNode);//Find the end and return
            }
        }
        return find(openList, endNode);
    }

    //You can find the situation after the node in the list
    private void foundPoint(Node tempStart, Node node) {
        int G = calcG(tempStart, node);
        if (G < node.G) {
            node.parent = tempStart;
            node.G = G;
            node.calcF();
        }
    }

    //The node cannot be found in the node
    private void notFoundPoint(Node tempStart, Node end, Node node) {
        node.parent = tempStart;
        node.G = calcG(tempStart, node);
        node.H = calcH(end, node);
        node.calcF();
        openList.add(node);
    }


    //Method of calculating G value
    private int calcG(Node start, Node node) {
        int G = STEP;
        int parentG = node.parent != null ? node.parent.G : 0;
        return G + parentG;
    }

    //Method of calculating H value
    private int calcH(Node end, Node node) {
        int step = Math.abs(node.x - end.x) + Math.abs(node.y - end.y);
        return step * STEP;
    }

    //How to find the end point
    public static Node find(List<Node> nodes, Node point) {
        for (Node n : nodes)
            if ((n.x == point.x) && (n.y == point.y)) {
                return n;
            }
        return null;
    }

    //The following two are overloads of the exist method to determine whether the node is in the list under different parameters
    public static boolean exists(List<Node> nodes, Node node) {
        for (Node n : nodes) {
            if ((n.x == node.x) && (n.y == node.y)) {
                return true;
            }
        }
        return false;
    }

    public static boolean exists(List<Node> nodes, int x, int y) {
        for (Node n : nodes) {
            if ((n.x == x) && (n.y == y)) {
                return true;
            }
        }
        return false;
    }

    //Node class, which defines the attributes of each node
    public static class Node {
        public Node(int x, int y) {
            this.x = x;
            this.y = y;
        }
        public int x;
        public int y;
        public int F;
        public int G;
        public int H;

        public void calcF() {
            this.F = this.G + this.H;
        }
        public Node parent;
    }
}

3. MazePanel class:

The maze interface is generated by filling color to produce the maze, and the keyboard is used to monitor the movement

package com.company;

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;

class MazePanel extends JPanel implements KeyListener,ActionListener {
    public final static int WALL = 0;//obstacle
    public final static int ROAD = 1;//access
    public static int width; //Map scale
    public static int height;
    public final static int current = 2; //Legend identifying the current location
    public final static int MAXSTEPS = 200; //Maximum number of steps in the game
    static int mx = 1; //Current position (curX,curY)
    static int my = 1;
    static int score = 0; //Game score and steps
    static int steps = 0; // Number of steps
    static int level = 3;//difficulty
    static int[][] map;  //Storage maze 
    private JButton ans = new JButton("Display path"); //Keys and keyboard mode are not realized. They are directly operated by the keyboard
    private JButton remake = new JButton("Reset maze");
    private JPanel jp = new JPanel();
    private JButton hide = new JButton("Hide path");
    private JButton exit = new JButton("Exit the game");
    private JButton start = new JButton("Start the game");

    public static int Difficult(int level) {   //Game difficulty, initially 10, difficulty + 1, map + 5
        int number = 10;
        for (int i = 0; i < level; i++) {
            number += 2;
        }
        return number;
    }

    public static void MapRandom() {   //Random maze
        width = Difficult(level);
        height = Difficult(level);
        Maze maze = new Maze(width, height);  //Initialize maze
        map = Maze.Init();  //Inheritance maze
        map[1][1] = 2; //Set the entrance and exit of the labyrinth
        mx = 1;
        my = 1;
        //map[map.length - 2][map.length - 2] = 2;
    }
    //Game interface initialization and design
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        this.addKeyListener(this);
        //count = Difficult(level);
        g.setColor(new Color(0,139,139));//Add RGB background color
        g.fillRect(0, 0, 760, 807);
        //Date.location.paintIcon(this,g,0,50);
        //g.setColor(new Color(224, 238, 238));
        for (int i = 0; i < map.length; i++) {  //Fill the color method to draw the wall and load of the maze
            for (int j = 0; j < map.length; j++) {
                if(i==map.length-2&&j==map.length-2){
                    g.setColor(Color.RED);
                    g.fillRect(10 * i + 10, 10 * j + 50, 10, 10);
                }else if (map[i][j] == 0) {
                    g.setColor(Color.BLACK);
                    g.fillRect(10 * i + 10, 10 * j + 50, 10, 10);
                } else if (map[i][j] == 1) {
                    g.setColor(Color.GRAY);
                    g.fillRect(10 * i + 10, 10 * j + 50, 10, 10);
                } else if (map[i][j] == 2) {
                    g.setColor(Color.RED);
                    g.fillRect(10 * i + 10, 10 * j + 50, 10, 10);
                }
            }
            g.setColor(new Color(188, 250, 250));
            g.fillRect(0, 0, 1000, 40);
            g.setColor(Color.BLACK);
            g.setFont(new Font("Imitation Song Dynasty", Font.BOLD, 20));
            g.drawString("Reset game(X)", 10, 25);
            g.drawString("Pathfinding(Y)", 150, 25);
            g.drawString("Current steps:" + steps, 250, 25);
            g.drawString("Difficulty:"+level,400,25);
            g.drawString("W:difficulty+  S: difficulty-",500,25);
            if (MazePanel.isSuccess()) {
                g.setFont(new Font("Song typeface", Font.BOLD, 50));
                g.drawString("Congratulations on customs clearance", 500, 400);
            }
        }
    }
    //Return the path obtained by A * algorithm to map
    public void ans() {
        AStar aStart = new AStar(map); 
        map = AStar.ans();
    }

    public MazePanel() {
        MapRandom();
        this.setFocusable(true);
        this.addKeyListener(this);
    }

    //Judge whether to get out of the maze
    public static boolean isSuccess() {
        //score += MazePanel.MAXSTEPS - steps;
        return map.length - 2 == mx && map.length - 2 == my;
    }

    @Override
    public void actionPerformed(ActionEvent e) {

    }
    static int i=0;
    @Override
    public void keyPressed(KeyEvent e) {   
        int key = e.getKeyCode();
        int x = mx, y = my;
        //Game movement mode and restrictions. The next step cannot be a wall and single monitoring. If i=0 is not added, there will be a bug
        if(!isSuccess()&&steps<MAXSTEPS) {
            if (key == KeyEvent.VK_LEFT && map[x - 1][y] != MazePanel.WALL&&i==0) {//Shift left
                map[x - 1][y] = MazePanel.current;   //Change the current position to the moved position
                map[x][y] = 1;//Restore the original icon of the map
                mx--;i++;     //Change current position coordinates
                steps++;
            } else if (key == KeyEvent.VK_RIGHT&&map[x + 1][y] != MazePanel.WALL&&i==0) {
                map[x + 1][y] = MazePanel.current;  //Shift right
                map[x][y] = 1;
                mx++;i++;
                steps++;
            } else if (key == KeyEvent.VK_UP&&map[x][y - 1] != MazePanel.WALL&&i==0) {
                map[x][y - 1] = MazePanel.current;  //Move up
                map[x][y] = 1;
                my--;i++;
                steps++;
            } else if (key == KeyEvent.VK_DOWN&&map[x][y + 1] != MazePanel.WALL&&i==0) {
                map[x][y + 1] = 2;   //Move down
                map[x][y] = 1;
                my++;i++;
                steps++;
            }else if(key == KeyEvent.VK_Y&&i==0){   //Display path
                ans();
            }else if(key == KeyEvent.VK_X&&i==0){   //Reset maze
                MapRandom();
            }
            else if(key==KeyEvent.VK_W&&i==0&&level<13){  //Increase maze difficulty
                level++;i++;
                MapRandom();
            }else if(key==KeyEvent.VK_S&&i==0&&level>=3){  //Reduce maze difficulty
                level--;i++;
                MapRandom();
            }
        }
        repaint();   //Refresh game interface
    }

    @Override
    public void keyTyped(KeyEvent e) {

    }

    @Override
    public void keyReleased(KeyEvent e) {
        i=0;       //Keyboard monitoring is limited. If it is not limited, it will move several grids at a time
    }
}


4. Main class

package com.company;

import javax.swing.*;

public class Main {
  
    public static void main(String[] args) {
        JFrame frame = new JFrame();  Generate window
        frame.setSize(770,825);   //Set window size
        MazePanel maze = new MazePanel();       
        frame.add(maze);    //Add game panel
        frame.setLocationRelativeTo(null);//Center of display screen
        frame.setResizable(false);//Fixed screen
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//Close command
        frame.setVisible(true);  //Window visualization
    }
}

Topics: Java IntelliJ IDEA Spring Algorithm intellij-idea