マップを自動生成するようです
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();
}
}