CS61B Project2:关于生成地图算法的讨论(一)

这篇具有很好参考价值的文章主要介绍了CS61B Project2:关于生成地图算法的讨论(一)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

问题介绍

Project 2介绍了一个渲染引擎,可以根据传入的TETile[][]数组来在对应的位置生成砖块(TETile对象表示各种不同的砖块类型,包括地板、墙、花等元素),我们的任务是完成一个用于生成可探索世界的引擎,并设计和实现一个基于2D图块的游戏(就像塞尔达传说:织梦岛一样)。

CS61B Project2:关于生成地图算法的讨论(一),算法,java

在Phrase 1的部分,我们的目标是编写一个世界生成器,这个生成器有以下这些要求:

  1. 世界必须是一个2D网络,使用提供的TERenderer.java引擎渲染
  2. 世界必须是伪随机生成的,随机数种子应该由用户输入
  3. 生成的世界必须包括房间和走廊(走廊是单行地板,房间是多行地板),以及可能的室外空间
  4. 至少应该存在一些矩形房间
  5. 生成的走廊需要包含转弯或者相交的部分
  6. 房间和走廊的数量应该是随机的
  7. 房间和走廊的位置应该是随机的
  8. 房间的宽度和高度应该是随机的
  9. 走廊的长度应该是随机的
  10. 房间的走廊和墙壁必须在视觉上与地板不同,墙壁和地板在视觉上应与未使用的空间不同
  11. 房间和走廊应该相连,即相邻房间或走廊之间的地板不应该有间隙
  12. 世界每次都应该有很大不同,即不应该具有相同的基本布局和易于预测的功能

针对这些要求,给出了一个合格世界的示例,在该图中,#代表墙壁,点代表地板,一个金色的墙代表一扇锁着的门。所有的未使用空间都留空

CS61B Project2:关于生成地图算法的讨论(一),算法,java

Project 2还有一些关于游戏进入界面和游玩方式的要求,因为本文仅谈论地图生成算法,所以先暂时略过(可能会在之后的博客中展示实现)

实现思路

通过对问题的拆解,我发现在这些要求中,最应该首先把握住的是房间和走廊连通的问题,从这个角度出发我思考出了第一版算法,他包含三个部分:

  1. 初始化TETile[][]数组,将其所有位置填充为`Tileset.NOTHING`
  2. 随机指定一个开始点,确保其坐标在数组内部,并以这个开始点随机游走,每次随机往上下左右四个方向之一前进一格,直到前进的次数除以总的格子数达到一个随机指定的“空间使用率”,对于每个经过的坐标(x, y),在TETile[x][y]中填入`Tileset.FLOOR`(要限制不能走到地图边缘,因为FLOOR周边应该包裹一层WALL)
  3. 遍历数组中的每个元素,如果这个元素是`Tileset.FLOOR`,那么检查其周围九宫格范围的八个坐标位置,如果为`Tileset.NOTHING`,那么填充为`Tileset.WALL`

为了实现这个算法,我创造了两个类,分别是Position.java和World.java,前者表示一个坐标,同时可以控制四个方向的移动,后者负责初始化和生成这个世界,其中在World.java中还有一个Nested Class,用于提供第二步和第三步中需要使用的一些方法,代码展示如下

// Position.java
package byog.Core;

public class Position {
    private int xPos;
    private int yPos;

    public Position(int x, int y) {
        xPos = x;
        yPos = y;
    }

    public Position(Position p) {
        xPos = p.xPos;
        yPos = p.yPos;
    }

    public int getXPos() {
        return xPos;
    }

    public int getYPos() {
        return yPos;
    }

    public Position goUp() {
        return new Position(xPos, yPos + 1);
    }

    public Position goDown() {
        return new Position(xPos, yPos - 1);
    }

    public Position goLeft() {
        return new Position(xPos - 1, yPos);
    }

    public Position goRight() {
        return new Position(xPos + 1, yPos);
    }

    public String toString() {
        StringBuilder returnSB = new StringBuilder("(");
        returnSB.append(xPos);
        returnSB.append(", ");
        returnSB.append(yPos);
        returnSB.append(")");
        return returnSB.toString();
    }

}


// World.java

import byog.TileEngine.TETile;
import byog.TileEngine.Tileset;

import java.io.*;
import java.util.Random;

public class World implements Serializable {

    private static final long serialVersionUID = 123123123123123L;
    private final Random r;
    private final int WIDTH;
    private final int HEIGHT;
    private final TETile[][] world;

    /** Initialize a new world */
    public World(int w, int h, int seed) {
        world = new TETile[w][h];
        r = new Random(seed);
        WIDTH = w;
        HEIGHT = h;
    }

    /** Load the existing world, or create a new world */
    public static World loadWorld(int w, int h, int seed) {
        File f = new File("./RandomWorld/world.ser");
        if (f.exists()) {
            try {
                FileInputStream fs = new FileInputStream(f);
                ObjectInputStream os = new ObjectInputStream((fs));
                World loadWorld = (World) os.readObject();
                os.close();
                return loadWorld;
            } catch (FileNotFoundException e) {
                System.out.println("File not found!");
                System.exit(0);
            } catch (IOException e) {
                System.out.println(e);
                System.exit(0);
            } catch (ClassNotFoundException e) {
                System.out.println("Class not found!");
                System.exit(0);
            }
        }

        return new World(w, h, seed);

    }

    /** Save the word instance that have been generalized */
    public static void saveWorld(World w) {
        File f = new File("./RandomWorld/world.ser");
        try {
            if (!f.exists()) {
                f.createNewFile();
            }
            FileOutputStream fs = new FileOutputStream(f);
            ObjectOutputStream os = new ObjectOutputStream(fs);
            os.writeObject(w);
            os.close();
        } catch (FileNotFoundException e) {
            System.out.println("File not found");
            System.exit(0);
        } catch (IOException e) {
            System.out.println(e);
            System.exit(0);
        }
    }

    /** A Nested Toolkit Class for generalizing world */
    private class GeneralizeHelper {

        /** Returns true if the position is out of the limitation */
        private boolean isOut(Position p) {
            if (p.getXPos() < 1 || p.getXPos() > WIDTH - 2
                    || p.getYPos() < 1 || p.getYPos() > HEIGHT - 2) {
                return true;
            }
            return false;
        }

        /** Choose a position to start random walk and make sure there is enough space to
         *  generate the wall */
        private Position startPosition() {
            int x = 2 + r.nextInt(WIDTH - 1);
            int y = 2 + r.nextInt(HEIGHT - 1);
            return new Position(x, y);
        }

        /** GO up, down, left or right randomly and the "PATH" should
         *  leave enough space to generate the wall */
        private Position randomWalk(Position p) {
            int chooseDirection = r.nextInt(4);
            Position newPos;

            // Choose a direction randomly
            switch (chooseDirection) {
                case 0: newPos = new Position(p.goUp()); break;
                case 1: newPos = new Position(p.goDown()); break;
                case 2: newPos = new Position(p.goLeft()); break;
                default: newPos = new Position(p.goRight()); break;
            }

            // Leave enough space to generate the wall
            if (isOut(newPos)) {
                return randomWalk(p);
            }

            return newPos;
        }

        /** Generate walls around the path */
        private void generateWalls(Position p, TETile[][] t) {
            Position[] positions = new Position[8];
            int x = p.getXPos();
            int y = p.getYPos();
            positions[0] = new Position(x - 1, y + 1);
            positions[1] = new Position(x , y + 1);
            positions[2] = new Position(x + 1, y + 1);
            positions[3] = new Position(x - 1, y);
            positions[4] = new Position(x + 1, y);
            positions[5] = new Position(x - 1, y - 1);
            positions[6] = new Position(x , y - 1);
            positions[7] = new Position(x + 1, y - 1);

            for (int i = 0; i < 7; i++) {
                int xpos = positions[i].getXPos();
                int ypos = positions[i].getYPos();
                if (t[xpos][ypos].equals(Tileset.NOTHING)) {
                    t[xpos][ypos] = Tileset.WALL;
                }
            }
        }
    }

    /** Generalize a new world randomly (Version 1.0)*/
    public void generalizeWorld() {

        // Fill the TETile 2D Array with `Nothing`
        for (int i = 0; i < WIDTH; i++) {
            for (int j = 0; j < HEIGHT; j++) {
                world[i][j] = Tileset.NOTHING;
            }
        }

        // Generalize "Path" and "Room" randomly
        double roomRatio = 0.4 + 0.2 * r.nextDouble();
        int distance = 1;
        GeneralizeHelper gh = new GeneralizeHelper();
        Position ptr = gh.startPosition();
        world[ptr.getXPos()][ptr.getYPos()] = Tileset.FLOOR;
        while ((double) distance / (WIDTH * HEIGHT) < roomRatio) {
            ptr = gh.randomWalk(ptr);
            System.out.println(ptr.toString());
            world[ptr.getXPos()][ptr.getYPos()] = Tileset.FLOOR;
            distance++;
        }

        // Generalize "Walls" around the "Path"
        for (int i = 0; i < WIDTH; i++) {
            for (int j = 0; j < HEIGHT; j++) {
                if (world[i][j].equals(Tileset.FLOOR)) {
                    gh.generateWalls(new Position(i, j), world);
                }
            }
        }

    }

    public TETile[][] getWorld() {
        return world;
    }


}

算法效果与结果反思

创建了一个TestWorld.java,用于测试生成算法

package byog.Core;

import byog.TileEngine.TERenderer;

public class TestWorld {
    public static void testGeneralizeWorld() {
        World w = new World(80, 30, 217);
        TERenderer ter = new TERenderer();
        ter.initialize(80, 30);
        w.generalizeWorld();
        ter.renderFrame(w.getWorld());
    }

    public static void main(String[] args) {
        testGeneralizeWorld();
    }
}

运行代码,结果如下

CS61B Project2:关于生成地图算法的讨论(一),算法,java

发现算法确实做到了所有的“走廊”连通了,但是存在以下几个问题:

  1. 占用面积过小,地图中出现大面积留白
  2. “走廊”这一概念的实现不好,没有单层的地板
  3. 生成墙的算法有bug,有些九宫格位置并没有生成墙

通过输出行走路径的坐标我发现了占用面积过小的原因

CS61B Project2:关于生成地图算法的讨论(一),算法,java

我发现在随机等概率行走的状态下,指针会反复经过一个区域。另外,因为我是等概率选择方向,从数学上看,这样的随机游走会生成一个二维的高斯分布,和最终需要的形态存在出入,所以具体的行走逻辑需要更改,也许可以考虑随机生成“走廊”和“房间”的概念,或者递归生成。文章来源地址https://www.toymoban.com/news/detail-839237.html

到了这里,关于CS61B Project2:关于生成地图算法的讨论(一)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 关于几个结构型模式的讨论

    结构型模式的思路是组合,而根据组合侧重的不同方面,分为了不同的模式。 结构型模式的思路和行为型模式中的模板方法模式有一定相似性,尤其是在实现具体的函数时,不过不同之处在于模板方法模式采用的是继承,并且它们的目的也不一样,结构型模式的目的是扩展、

    2024年02月12日
    浏览(33)
  • 关于语言模型私有化部署的讨论 | AIGC实践

    上周在与TC同行关于AIGC实践的线上交流中,大家普遍比较关心的一个实践切入点是:语言模型的私有化部署——简单来说,就是在企业内部,部署一个属于自己的“ChatGPT”,对于本行业/专业知识,以及企业独有的产品和技术信息有充分的了解,并且提供用户接口,通过自然

    2024年02月11日
    浏览(63)
  • 关于实变函数中德摩根定律和集合列上下极限的一些讨论

    本文内容来自作者本人在学习《实变函数与泛函分析基础》一书过程中的一些思考。 文章目录 前言 一、德-摩根定律 1.概率论与逻辑代数 2.集合论 二、集合列的上极限与下极限 1.基本定义 2.个人理解 3.一个例子 4.集合形式的描述定理 结语        实变函数论是克服黎曼可积

    2023年04月08日
    浏览(38)
  • 想着和GTP讨论关于人的一些哲学问题(答案很有意思)

    我:我觉得人活着没啥意义 GPT:作为AI语言模型,我当然不能给你身为人类的绝对回答,但我可以提供一些哲学和心理学上的观点供你参考。 人类作为一种高智能的生物存在于地球上已经很久了,并对自然环境和社会文明做出了许多贡献。每个人有自己的一种“存在意义”,

    2023年04月20日
    浏览(48)
  • 想着和GTP讨论关于人或AI的一些问题(答案很有意思)

    我:我觉得人活着没啥意义 GPT:作为AI语言模型,我当然不能给你身为人类的绝对回答,但我可以提供一些哲学和心理学上的观点供你参考。 人类作为一种高智能的生物存在于地球上已经很久了,并对自然环境和社会文明做出了许多贡献。每个人有自己的一种“存在意义”,

    2023年04月23日
    浏览(79)
  • 关于讨论IDEA创建springboot项目后启动类不显示启动按钮的问题

    最近准备创建一个springboot项目来开发微信小程序后台,但是在创建项目后,发现启动类没有启动入口显示是CurrentFile, 文件样式也与之前显示的不一样,我理解应该是没识别为Java项目文件,而是一个普通文件 右键pom.xml文件,选择 Add as Maven Project 等待ing… 等待若干分钟后,等

    2024年02月12日
    浏览(50)
  • 关于微信小程序与Java后台交互数据中中文乱码问题的讨论

    如果小程序端发起的请求参数中含有中文,直接发送到后台会显示乱码,需要在header中设置UTF-8编码 这样后台接收到的中文就能解析正常了 为了便于测试,后台接口简化如下: 结果小程序端显示的用户名为“寮犱笁”。 起初怀疑后台返回的编码格式不对,网上说对于Spring

    2024年02月09日
    浏览(38)
  • 关于Goby反制上线CS中的各种问题

    Goby作为新一代网络安全技术,通过为目标建立完整的资产数据库,实现快速的安全应急,日常为广大师傅提供了便捷的渗透体验。最近有观察到有关于某些蜜罐出现了Goby反制的指纹,顿时就起了兴趣进行研究Goby的反制,期间也遇到了很多网上没有答案的坑点,这里把遇到的

    2024年02月07日
    浏览(31)
  • 关于用栈和队列分别解决走迷宫问题的方法讨论(参与者:陈卓,毛敏磊)

    对于生活中最常见的小游戏——走迷宫,相信大家都不陌生,人为走相信大家都会走,但能不能用代码实现,我们认为是可以的,以下是我们对如何走迷宫的一些看法和代码实现(cz负责队列解决,mml负责用栈解决) 先简单介绍一下 队列 :队列是一种操作受限的线性表,只

    2024年04月08日
    浏览(78)
  • 【LeetCode】1654:到家的最少跳跃次数的解题思路 & 关于力扣无法return的BUG的讨论

    有一只跳蚤的家在数轴上的位置 x 处。请你帮助它从位置 0 出发,到达它的家。 跳蚤跳跃的规则如下: 它可以 往前 跳恰好 a 个位置(即往右跳)。 它可以 往后 跳恰好 b 个位置(即往左跳)。 它不能 连续 往后跳 2 次。 它不能跳到任何 forbidden 数组中的位置。 跳蚤可以往

    2024年02月10日
    浏览(49)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包