k3kaimu
1/18/2014 - 8:27 AM

マップを自動生成するようです

マップを自動生成するようです

import std.range;
import std.variant;
import std.exception;
import std.typecons;
import std.format;
import std.string;
import std.math;
import std.conv;
import std.random;
import std.algorithm;
import std.numeric;
import std.complex;

import gigue.core;

//debug(HexMap)
    import std.stdio;

enum Angle
{
    right = 0,
    rightUp,
    leftUp,
    left,
    leftDown,
    rightDown,
}


/**
i * PI/3だけ反時計回りに回転させる
*/
Angle rot(Angle a, ptrdiff_t i) pure nothrow @safe
{
    immutable n = (cast(ptrdiff_t)a) + (i % 6);
    if(n < 0)
        return cast(Angle)(n + 6);
    else if(n >= 6)
        return cast(Angle)(n - 6);
    else
        return cast(Angle)n;
}


/**
方向ベクトルから、重みを計算
*/
real[] genWeight(V)(V v, real bias = 0)/* pure nothrow @safe*/
{
    // 0 ~ 2PI までの角度を 0 ~ 2 として計算
    immutable angle = {
        auto t = atan2(v[1], v[0]) / PI;
        if(t < 0)
            return t + 2;
        else
            return t;
    }();

    real[] w = new real[6];
    foreach(i, ref e; w){
        immutable a = i / 3.0;
        e = 1 - abs(a - angle)/2;    // 重みを計算
    }

    w[] += bias;
    normalize(w[]);     // 正規化
    return w;
}


struct MapInfo
{
    @property
    size_t height() const pure nothrow @safe
    {
        return _map.length;
    }


    @property
    size_t width() const pure nothrow @safe
    {
        if(!height)
            return 0;
        else
            return _map[0].length;
    }


    @property
    auto ref opIndex(size_t w, size_t h) pure nothrow @safe inout
    {
        return _map[h][w];
    }


    @property
    auto rooms() pure nothrow @safe inout
    {
        return _rooms;
    }


    /**
    画面の端まで来た場合に、どのようにイテレーションするか
    */
    enum WalkOption
    {
        loop,       /// 反対側に行く
        stay,       /// 現在地から動かない
        //refrect,    /// 反射
    }


    auto walker(size_t x, size_t y, WalkOption opt = WalkOption.stay) pure nothrow @safe
    {
        static struct Result()
        {
            auto ref here() pure nothrow @safe @property
            {
                return _map[_x, _y];
            }


            void walkTo(Angle a) pure nothrow @safe
            {
                final switch(a)
                {
                  case Angle.right:
                    ++_x;
                    break;

                  case Angle.rightUp:
                    _x += 1 - _y&1;
                    ++_y;
                    break;

                  case Angle.leftUp:
                    _x -= _y&1;
                    ++_y;
                    break;

                  case Angle.left:
                    --_x;
                    break;

                  case Angle.leftDown:
                    _x -= _y&1;
                    --_y;
                    break;

                  case Angle.rightDown:
                    _x += 1 - _y&1;
                    --_y;
                    break;
                }


                void op(string low, string over)()
                {
                    enum str = `if(%1$s < 0){` ~ low ~ "}else if(%1$s >= %2$s){" ~ over ~ "}";
                    mixin(str.format("_x", "_map.width"));
                    mixin(str.format("_y", "_map.height"));
                }


                final switch(_opt)
                {
                  case WalkOption.loop:
                    op!("%1$s += %1$s;", "%1$s -= %1$s;");
                    break;

                  case WalkOption.stay:
                    op!("%1$s += 1;", "%1$s -= 1;");
                    break;

                  //case WalkOption.refrect:
                  //  break;
                }
            }


            ptrdiff_t[2] pos() pure nothrow @safe @property const
            {
                return [_x, _y];
            }


          private:
            MapInfo _map;
            ptrdiff_t _x, _y;
            WalkOption _opt;
        }


        return Result!()(this, x, y, opt);
    }


  private:
    Variant[][] _map;
    RoomInfo*[] _rooms;
}


struct RoomInfo
{
    size_t[2] iniPos;       // 部屋の初期位置
    size_t floorSpace;      // 床面積
    real[6] weight;         // 部屋固有の重み


    @property
    auto byField() @safe @property
    {
        static struct Result(Room)
        {
            @property
            auto front() pure @safe
            {
                enforce(!empty);
                return _byIndex(_x, _y);
            }


            @property
            bool empty() pure nothrow @safe const
            {
                return _y == _room._map.height;
            }


            void popFront() @safe
            {
                enforce(!empty);
                findNext();
            }


            auto save() pure nothrow @safe inout
            {
                return this;
            }


          private:
            Room _room;
            size_t _x, _y;


            auto _byIndex(size_t x, size_t y) pure nothrow @safe
            {
                return Tuple!(size_t, "x",
                              size_t, "y",
                              typeof(_room._map[0,0]), "info")
                        (x, y, _room._map[x, y]);
            }


            bool checkNowPos() @trusted const
            {
                enforce(_x < _room._map.width);
                enforce(_y < _room._map.height);
                if(auto p = _room._map[_x, _y].peek!(RoomInfo*))
                    return *p is _room;
                else
                    return false;
            }


            void findNext() @safe
            {
                do
                {
                    ++_x;
                    if(_x >= _room._map.width){
                        _x -= _room._map.width;
                        ++_y;
                    }

                    if(this.empty)
                        return;
                }while(!checkNowPos());
            }
        }


        auto dst = Result!(typeof(&this))(&this, 0, 0);

        if(!dst.checkNowPos())
            dst.findNext();

        return dst;
    }


    @property
    auto extendFloor(Angle a) @safe
    {
        static struct Dummy{}

        foreach(e; this.byField)
        {
            immutable x = e.x,
                      y = e.y;

            auto walker = _map.walker(x, y);    // iterator
            walker.walkTo(a);
            if(!walker.here.hasValue)
                walker.here = Dummy();
        }

        foreach(x; 0 .. _map.width)
            foreach(y; 0 .. _map.height)
                if(_map[x, y].peek!(Dummy)){
                    _map[x, y] = &this;         // replace
                    ++floorSpace;
                }
    }


  private:
    MapInfo _map;
}


struct Road{}



MapInfo genMap(size_t width, size_t height,
                 ubyte maxRoom, real maxSRatio = 0.3)
{
    /**
    Hexにおいて、
    0 1 2 3 4 5 6 7
     0 1 2 3 4 5 6 7
    0 1 2 3 4 5 6 7

    と並んでいるから、移動方向は2を中心とすれば

     2 3
    1 2 3
     2 3

     である
    */

    //MapInfo* map = new MapInfo(width, height);
    //MapInfo map = new Variant[][](height, width);
    MapInfo map;
    map._map = new Variant[][](height, width);

    // 土地面積
    immutable totalSpace = height * width;

    //size_t cnt;

    //// 一部屋あたりの土地面積
    //immutable landSpacePerRoom = (cast(real)totalSpace) / maxRoom;

    // 一区画あたりの土地面積
    immutable landSpacePerDiv = (cast(real)totalSpace) / (maxRoom * 16); // 区画数は部屋数の4倍くらい

    // 区画の大きさを計算する
    // 区画は正方形だと考える
    immutable divSize = sqrt(landSpacePerDiv).to!size_t();

    immutable divNW = width / divSize;  // width方向の区画の数
    immutable divNH = height / divSize; // heigth方向の区画の数
    immutable divN = divNW * divNH;     // 区画の数

    RoomInfo*[] rooms;

    // 区画のうち、部屋として割り当てる区画を決める
    foreach(e; randomSample(iota(divN), maxRoom)){
        RoomInfo* room = new RoomInfo;
        room._map = map;
        //room.id = rooms.length + 1; // idは1, 2, 3, ...
        room.floorSpace = 7;        // 初期は7マス
        uniformDistribution(6, room.weight[]);  // 重み付け


        // 初期位置を決めて、そこを塗りつぶす
        {
            // 区画id(e)から、区画の位置を計算
            immutable divX = e % divNW,
                      divY = e / divNW;

            // 区画内で、部屋の中心の位置を決める
            immutable x = uniform!"()"(divX * divSize, (divX + 1) * divSize),
                      y = uniform!"()"(divY * divSize, (divY + 1) * divSize);

            room.iniPos = [x, y];
            map[x, y] = room;   // マップ上に書き込む
            
            foreach(i; 0 .. 6){
                auto walker = map.walker(room.iniPos[0], room.iniPos[1], MapInfo.WalkOption.stay);
                walker.walkTo(Angle.right.rot(i));
                walker.here = room;
            }
        }

        rooms ~= room;
    }
    map._rooms = rooms;


    // 現在の各部屋の大きさを計算
    size_t sum;
    foreach(room; map.rooms)
        sum += room.floorSpace;

    // 各部屋を育てる
    Lwhile:
    while(1)
        foreach(room; map.rooms){
            auto angle = Angle.right.rot(dice(room.weight));
            //writeln(angle);

            sum -= room.floorSpace;
            room.extendFloor(angle);
            sum += room.floorSpace;

            if((sum + 0.0) / totalSpace > maxSRatio)        // 目標の面積に達したら終了
                break Lwhile;
        }

    // 部屋から部屋への道を作る
    {
        auto remain = map.rooms[];

        // ランダムに部屋を一つ選んで、グループに加える
        auto group = [map.rooms[uniform!"[)"(0, map.rooms.length)]];
        remain = remain.remove(countUntil(remain, group[0])); // 現在の部屋をリストから削除

      LtoNextPath:
        foreach(e; remain){

            RoomInfo* near;
            real maxNorm = 0;
            foreach(g; group){
                immutable r = (e.iniPos[].toMatrix!(2, 1) - g.iniPos[].toMatrix!(2, 1)).norm2;
                if(maxNorm < r){
                    near = g;
                    maxNorm = r;
                }
            }

            // nearからeまで結ぶ
            {
                auto walker = map.walker(near.iniPos[0], near.iniPos[1]);
                auto end = e.iniPos;

                //size_t maxLen = 10;
                while(1){
                    // 方向ベクトル
                    auto dv =  end[].map!"cast(real)a"().array().toMatrix!(2, 1) - walker.pos[].map!"cast(real)a"().array.toMatrix!(2, 1);

                    // 方向ベクトルから方向の重み付け
                    auto weight = genWeight(dv);

                    // 重みから、移動方向を計算
                    auto angle = Angle.right.rot(weight.zip(iota(6)).minPos!"a[0]>b[0]"().front[1]);

                    // 移動
                    walker.walkTo(angle);

                    // 移動先を確認
                    auto now = &(walker.here());
                    if(auto p = (*now).peek!(RoomInfo*)){
                        if(*p == e) // 目的地に到達
                            continue LtoNextPath;   // 次の道を作る
                    }else if(!now.hasValue)
                        *now = Road();              // 道にする
                }
            }

            group ~= e; // 仲間に追加
        }
    }

    return map;
}



void main()
{
    import std.stdio;

    auto m = genMap(32, 32, cast(ubyte)uniform!"[)"(4, 8), 0.15);

    // 表示する
    writeToConsole(m);
}



void writeToConsole(MapInfo map)
{
    // 表示する
    foreach(y; 0 .. map.height){
        writef("%2d|", y);
        if(!(y&1))
            write(' ');
        foreach(x; 0 .. map.width){
            if(map[x, y].peek!(RoomInfo*))
                writef("%s ", map[x, y].hasValue ? "@" : " ");
            else if(map[x, y].peek!(Road))
                writef("%s ", map[x, y].hasValue ? "X" : " ");
            else
                writef("%s ", map[x, y].hasValue ? "." : " ");
        }
        writeln();
    }
}