I am participating in the Mid-Autumn Festival Creative Submission contest, please see: Mid-Autumn Festival Creative Submission Contest for details

Without output for a long time, this Mid-Autumn festival activity was also in high spirits to participate in. But looking back, it didn’t seem like there was any interesting technology accumulation. When I was ready to give up, I suddenly remembered a maze robot I had done for my friend. It’s not fancy but it’s fun. And since I couldn’t find the right rabbit material, I used the previous image material. Map is to find a moon cake material to draw and become. Here’s how to share the process.

mapping

Firstly, the width and height of the map are defined, and a two-dimensional array is used to store the topography of the map. SpawnMarker Is used to store the spawnpoint location of the robot. Robots are used to store the current state of robots in a maze. Controller is used to receive keyboard/mouse events and pass them to the World. Renderer is used to render 2D character maps into graphics.

public class World {

    private int width, height;
    private char[][] terrain;
    private final char FIRST_TERRAIN = ' ', LAST_TERRAIN = '~'; // ASCII can be displayed on the map for robot speech
    private Position[] spawnMarker = new Position[10]; //from digits 0 to 9

    private List<Robot> robots = new ArrayList<>();
    private Controller controller = new Controller(this);
    private Renderer renderer;

    private int targetFrametime = 16;
    private boolean pause = true;
    private long currentFrame = 0;
    private long pauseAtFrame = -1; //will pause if current frame equals this, set to <0 to not use

    private Method work, memoryToString;
}
Copy the code

With that done, let’s look at how to render the following 2D characters into patterns.

String mazeMap =

        "###################\n" +
        "# 0H # ## # $\n" +
        "# #a ## ## ## # #\n" +
        "####p # # # ##\n" +
        "# yp # ## ## #\n" +
        "# # Mid # ##Automn#\n" +
        "# # # # # # # # # # # # # # # # # # # # #";

World world = new World(mazeMap);
Robot robot = makeMazeRunner();
robot.spawnInWorld(world, '0');
world.run();
Copy the code

First build the map data through the constructor of World, place the robot birth point, and render the map data by default.

//constructs the world from the "2D" String
public World(String mapData) {
    this(mapData, true);
}

//you should probably leave shouldRender to true, otherwise you will not see anything
public World(String mapData, boolean shouldRender) {
    if (shouldRender)
        renderer = new Renderer();

    width = mapData.lines().mapToInt(String::length).max().orElseThrow();
    height = (int) mapData.lines().count();

    terrain = new char[width][height];
    String[] rows = mapData.lines().map(s -> s + "".repeat(width - s.length())).toArray(String[]::new);
    for (int y = 0; y < height; y++) {
        for (int x = 0; x < width; x++) {
            char c = rows[y].charAt(x);
            if ('0' <= c && c <= '9')
                spawnMarker[c - '0'] = new Position(x + 0.5, y + 0.5); terrain[x][y] = c; }}try {
        work = Robot.class.getMethod("work");
        memoryToString = Robot.class.getMethod("memoryToString");
    } catch (NoSuchMethodException e) {
        /* student has not implemented these methods yet */}}Copy the code

The map data is then rendered using the World’s run() method. The Renderer class details will not be described, but the overall source code link will be posted at the end of this article. The rendered map should look something like this:

The top view looks like this:

public void run() { if(renderer ! = null) renderer.setup(); try { while (true) { if(renderer ! = null) { // wait until unpaused while (pause || currentFrame == pauseAtFrame) Thread.sleep(50); long before = System.currentTimeMillis(); simulateFrame(); renderer.render(); long frametime = System.currentTimeMillis() - before; long sleepTime = targetFrametime - frametime; if (sleepTime > 0) Thread.sleep(sleepTime); } else simulateFrame(); } } catch (InterruptedException e) { /* Intentionally left blank */ } }Copy the code

Maze robot

Next, let’s look at the Robot class. The variables of the robot are relatively simple. Name and size are used to describe the name size of the robot. Position, direction, world Describes the current status of the robot. Memory and sensors are used to store the robot’s memories and perceptions. Todo and program are used to store a series of instructions that the robot will do.

public class Robot {
 
    private final String name;
    private final double size;

    private Position position = new Position();
    private double direction;
    private World world;

    // memory expand
    privateList<Memory<? >> memory =new ArrayList<>();

    // sensor expand
    privateList<Sensor<? >> sensors =new ArrayList<>();
    
    private Queue<Command> todo = new ArrayDeque<>();
    private Function<Robot, List<Command>> program = new Function<Robot, List<Command>>() {
        @Override
        public List<Command> apply(Robot robot) {
            List<Command> commands = new ArrayList<>();
            commands.addAll(todo);
            returncommands; }}; }Copy the code

Since the robot has to navigate mazes, ways of walking and reorienting must be essential. Here, turnBy means how many angles are extended above the current direction, and turnTo means to go directly to the new direction.

/// Pre-programmed Commands
public boolean go(double distance) {
    //step can be negative if the penguin walks backwards
    double sign = Math.signum(distance);
    distance = Math.abs(distance);
    // Penguin walks, each step being 0.2m
    while (distance > 0) {
        position.moveBy(sign * Math.min(distance, 0.2), direction);
        world.resolveCollision(this, position);
        distance -= 0.2;
    }
    return true;
}

public boolean turnBy(double deltaDirection) {
    direction += deltaDirection;
    return true;
}

public boolean turnTo(double newDirection) {
    direction = newDirection;
    return true;
}
Copy the code

There is also a “say” method added to the robot, because some characters in the map can be directly printed and not rendered in the World. When the robot walks up to the character, I want to be able to make the robot say the character. The say method in World is called to render the character because the final print is a GUI.

public boolean say(String text) {
    world.say(this, text);
    return true;
}
Copy the code

I adjusted the ASCII print range so that the map would render happy Mid-Autumn Festival text smoothly. Let’s see how the robot says happy Mid-Autumn Festival:

Now it’s time for our most important part of the maze. In the case of not knowing the complexity of the maze, the route cannot be preset artificially. So we need to think about unknown routes, how to get the robot to make the right instructions.

I think I used this picture, but I think the algorithm is a little bit different. How he did not do notes, now do not understand how to think at that time. So as a coder, comments are really important!

This is my code at that time, more fun is, as long as the map can lead to the end, even if the map is not inside, but an extra port from the outside can also connect to the end. Robots can also find an end.

public static Robot makeMazeRunner(a) {

    Robot panicPenguin = new Robot("Maze!".0.0.5);

    // create memory
    Memory<Character> terrain = panicPenguin.createMemory(new Memory<>("terrain".'0'));
    Memory<Character> end = panicPenguin.createMemory(new Memory<>("end".'$'));
    // create and attach sensors
    panicPenguin.attachSensor(new TerrainSensor().setProcessor(terrain::setData));
    panicPenguin.attachSensor(new TerrainSensor().setProcessor(end::setData));

    // program the robot
    panicPenguin.setProgram(robot -> {
        Position position = robot.getPosition();
        List<Command> commands = new ArrayList<>();

        if (Objects.equals(end.getData().toString(), "$")) {
            return commands;
        }
        switch (dir) {
            case 0:
                if (The '#'! = robot.getWorld().getTerrain(position.x +1, position.y)) {
                    commands.add(r -> r.turnTo(0));
                    commands.add(r -> r.say(terrain.getData().toString()));
                    commands.add(r -> r.go(1));
                    dir = 1;
                    return commands;
                } else {
                    if (The '#'! = robot.getWorld().getTerrain(position.x, position.y -1)) {
                        commands.add(r -> r.turnTo(1.5 * Math.PI));
                        commands.add(r -> r.say(terrain.getData().toString()));
                        commands.add(r -> r.go(1));
                        return commands;
                    } else {
                        commands.add(r -> r.turnTo(Math.PI));
                        commands.add(r -> r.say(terrain.getData().toString()));
                        commands.add(r -> r.go(1));
                        dir = 3;
                        returncommands; }}case 1:
                if (The '#'! = robot.getWorld().getTerrain(position.x, position.y +1)) {
                    commands.add(r -> r.turnTo(Math.PI * 0.5));
                    commands.add(r -> r.say(terrain.getData().toString()));
                    commands.add(r -> r.go(1));
                    dir = 2;
                    return commands;
                } else {
                    if (The '#'! = robot.getWorld().getTerrain(position.x +1, position.y)) {
                        commands.add(r -> r.turnTo(0));
                        commands.add(r -> r.say(terrain.getData().toString()));
                        commands.add(r -> r.go(1));
                        return commands;
                    } else {
                        commands.add(r -> r.turnTo(1.5 * Math.PI));
                        commands.add(r -> r.say(terrain.getData().toString()));
                        commands.add(r -> r.go(1));
                        dir = 0;
                        returncommands; }}case 2:
                if (The '#'! = robot.getWorld().getTerrain(position.x -1, position.y)) {
                    commands.add(r -> r.turnTo(Math.PI));
                    commands.add(r -> r.say(terrain.getData().toString()));
                    commands.add(r -> r.go(1));
                    dir = 3;
                    return commands;
                } else {
                    if (The '#'! = robot.getWorld().getTerrain(position.x, position.y +1)) {
                        commands.add(r -> r.turnTo(Math.PI * 0.5));
                        commands.add(r -> r.say(terrain.getData().toString()));
                        commands.add(r -> r.go(1));
                        return commands;
                    } else {
                        commands.add(r -> r.turnTo(0));
                        commands.add(r -> r.say(terrain.getData().toString()));
                        commands.add(r -> r.go(1));
                        dir = 1;
                        returncommands; }}case 3:
                if (The '#'! = robot.getWorld().getTerrain(position.x, position.y -1)) {
                    commands.add(r -> r.turnTo(1.5 * Math.PI));
                    commands.add(r -> r.say(terrain.getData().toString()));
                    commands.add(r -> r.go(1));
                    dir = 0;
                    return commands;
                } else {
                    if (The '#'! = robot.getWorld().getTerrain(position.x -1, position.y)) {
                        commands.add(r -> r.turnTo(Math.PI));
                        commands.add(r -> r.say(terrain.getData().toString()));
                        commands.add(r -> r.go(1));
                        return commands;
                    } else {
                        commands.add(r -> r.turnTo(Math.PI * 0.5));
                        commands.add(r -> r.say(terrain.getData().toString()));
                        commands.add(r -> r.go(1));
                        dir = 2;
                        returncommands; }}}return commands;
    });
    return panicPenguin;
}
Copy the code

Let’s take a look at all kinds of fun effect pictures.

Source link

Source code link, feel fun point like 🧡, by the way I wish you all 🥮 happy ~