Random Map Test
/**
* Copyright chimad ( http://wonderfl.net/user/chimad )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/8TuO
*/
package {
import com.bit101.components.*;
import flash.display.Bitmap;
import flash.display.Sprite;
import flash.events.Event;
public class Main extends Sprite {
private const WIDTH:int = 64;
private const HEIGHT:int = 64;
private const NUM_ROOMS:int = 6;
private var floor:Floor;
private var monitor:Bitmap;
private var cmpNumRooms:NumericStepper;
private var cmpButton:PushButton;
private var cmpSplittingRate:HRangeSlider;
public function Main() {
monitor = new Bitmap();
monitor.scaleX = monitor.scaleY = 6;
addChild(monitor);
initComponents();
}
private function initComponents():void {
var panel:Panel = new Panel(this, 0, 0);
var height:Number = 0;
var margin:int = 20;
cmpNumRooms = new NumericStepper(panel, 0, 0);
cmpNumRooms.minimum = 0;
cmpNumRooms.maximum = 16;
cmpNumRooms.value = 4;
cmpNumRooms.width = panel.width;
height += (cmpNumRooms.height + margin);
cmpSplittingRate = new HRangeSlider(panel, 0, height);
cmpSplittingRate.minimum = 0;
cmpSplittingRate.lowValue = 0.5;
cmpSplittingRate.highValue = 0.5;
cmpSplittingRate.maximum = 1;
cmpSplittingRate.labelPosition = RangeSlider.BOTTOM;
cmpSplittingRate.labelPrecision = 1;
cmpSplittingRate.tick = 0.1;
cmpSplittingRate.width = panel.width;
height += (cmpSplittingRate.height + margin);
cmpButton = new PushButton(panel, 0, height, "Create", onClick);
height += cmpButton.height;
monitor.x = panel.width;
}
private function onClick(e:Event):void {
floor = new Floor(WIDTH, HEIGHT);
floor.makeRooms(cmpNumRooms.value, new Range(cmpSplittingRate.lowValue, cmpSplittingRate.highValue));
floor.makeTunnels();
monitor.bitmapData = floor.data;
}
}
}
import flash.display.BitmapData;
import flash.geom.Point;
import flash.geom.Rectangle;
class Floor {
public var width:int;
public var height:int;
public var rect:Rectangle;
public var data:BitmapData;
public var rooms:Vector.<Room> = new Vector.<Room>();
// 追加の通路をつなぐ確率
private var extraTunnelChance:Number = 0.3;
// 空間を分割する際の比率
private var defaultSplittingRate:Range = new Range(0.5, 0.5);
// 空間に対する部屋のサイズの割合
private var roomSizeRate:Range = new Range(0.5, 1.0);
public function Floor(width:int, height:int) {
this.width = width;
this.height = height;
this.rect = new Rectangle(0, 0, width, height);
this.data = new BitmapData(width, height, false, 0x0);
}
public function clear():void {
while (rooms.length > 0)
rooms.pop();
data.fillRect(data.rect, 0x0);
}
public function makeRooms(numRooms:int, splittingRate:Range = null):void {
if (numRooms < 1)
return;
if (splittingRate == null)
splittingRate = defaultSplittingRate;
// 部屋の元になる矩形を生成する
var rectangles /*Rectangle*/:Array = splitRectangle(this.rect, numRooms - 1, 0.5, splittingRate, Orientation.AUTO, SplittingMethod.AUTO);
for each (var rect:Rectangle in rectangles) {
fixRectangle(rect);
}
for each (rect in rectangles) {
var copy:Rectangle = rect.clone();
// 周囲1マスを空ける
copy.inflate( -1, -1);
// 部屋のサイズと位置を決める
var width:Number = copy.width * roomSizeRate.randomValue;
var height:Number = copy.height * roomSizeRate.randomValue;
var x:Number = rect.x + Math.random() * (copy.width - width);
var y:Number = rect.y + Math.random() * (copy.height - height);
var roomRect:Rectangle = new Rectangle(x, y, width, height);
fixRectangle(roomRect);
// 部屋が小さすぎる場合は破棄する(暫定対応)
if (roomRect.width < 2 || roomRect.height < 2)
continue;
var room:Room = new Room(roomRect, rect);
rooms.push(room);
}
// dataに反映する
data.fillRect(data.rect, Tile.GAP);
for each (room in rooms) {
data.fillRect(room.outerRect, Tile.ROOM_SPACE);
data.fillRect(room.rect, Tile.ROOM);
}
}
public function makeTunnels():void {
if (rooms.length < 2)
return;
// 隣接している部屋同士を検出する
for (var i:int = 0; i < rooms.length; i++) {
var from:Room = rooms[i];
for (var j:int = i + 1; j < rooms.length; j++) {
var to:Room = rooms[j];
if (from.isAdjacentTo(to)) {
from.adjacentRooms.push(to);
to.adjacentRooms.push(from);
}
}
}
// メインストリームをつなぐ
var start:Room = rooms[int(Math.random() * rooms.length)];
start.isAccessible = true;
makeTunnelRecursively(start);
// メインストリーム以外の通路をつなぐ
for each (from in rooms) {
for each (to in from.adjacentRooms) {
if (!from.isConnectedTo(to) && Math.random() < extraTunnelChance) {
makeTunnel(from, to);
}
}
}
// データに反映する
while (Ant.track.length > 0) {
var p:Point = Ant.track.pop();
data.setPixel(p.x, p.y, Tile.TRACK);
}
data.threshold(data, data.rect, new Point(), "==", 0xFF << 24 | Tile.TRACK, 0xFF << 24 | Tile.TUNNEL);
// data.threshold(data, data.rect, new Point(), "==", 0xFF << 24 | Tile.GAP, 0xFF << 24 | Tile.ROOM_SPACE);
}
private function makeTunnelRecursively(from:Room):void {
var nexts:Vector.<Room> = new Vector.<Room>();
for each (var to:Room in from.adjacentRooms) {
if (!to.isAccessible) {
makeTunnel(from, to);
to.isAccessible = true;
nexts.push(to);
}
}
for each (var next:Room in nexts) {
makeTunnelRecursively(next);
}
}
private function makeTunnel(from:Room, to:Room):void {
var direction:int = from.getIntersectingDirection(to);
if (direction == Direction.NONE)
return;
var pointFrom:Point = getRandomPoint(from.rect);
var pointTo:Point = getRandomPoint(to.rect);
var ant:Ant = new Ant(pointFrom, data);
var tile:uint;
// 空間と空間のすき間まで進む
do {
tile = ant.walk(direction, true);
} while (tile != Tile.GAP);
if (direction == Direction.UP || direction == Direction.DOWN) {
// X座標を合わせる
var dir:int = (pointTo.x > pointFrom.x) ? Direction.RIGHT : Direction.LEFT;
while (ant.x != pointTo.x)
ant.walk(dir, false);
}
if (direction == Direction.LEFT || direction == Direction.RIGHT) {
// Y座標を合わせる
dir = (pointTo.y > pointFrom.y) ? Direction.DOWN : Direction.UP;
while (ant.y != pointTo.y)
ant.walk(dir, false);
}
// 部屋に出るまで進む
do {
tile = ant.walk(direction, true);
} while (tile != Tile.ROOM);
from.connectedRooms.push(to);
to.connectedRooms.push(from);
}
}
class Ant extends Point {
private var data:BitmapData;
public static var track:Vector.<Point> = new Vector.<Point>();
public function Ant(point:Point, data:BitmapData) {
setTo(point.x, point.y);
this.data = data;
}
public function walk(direction:int, joiningExistingTrack:Boolean = true):uint {
if (direction == Direction.NONE)
return data.getPixel(x,y);
var v:Point = directionToPoint(direction);
x += v.x;
y += v.y;
var tile:uint = data.getPixel(x, y);
if (tile == Tile.ROOM_SPACE)
data.setPixel(x, y, Tile.TRACK);
if (tile == Tile.GAP)
track.push(new Point(x, y));
return tile;
}
private static function directionToPoint(direction:int):Point {
var r:int = direction % 3;
var x:int = (r == 1) ? -1 : (r == 2) ? 0 : 1;
var q:int = (direction - 1) / 3;
var y:int = (q == 0) ? 1 : (q == 1) ? 0 : -1;
return new Point(x, y);
}
}
class Tile {
public static const ROOM:uint = 0xFFFFFF;
public static const ROOM_SPACE:uint = 0x7F7F7F;
public static const TUNNEL:uint = 0xC0C0C0;
public static const GAP:uint = 0x0000FF;
public static const TRACK:uint = 0x00FF00;
}
class Room {
public var rect:Rectangle;
public var outerRect:Rectangle;
private var outerRectInflated:Rectangle;
public var adjacentRooms:Vector.<Room> = new Vector.<Room>();
public var connectedRooms:Vector.<Room> = new Vector.<Room>();
public var isAccessible:Boolean = false;
public function isConnectedTo(room:Room):Boolean {
return connectedRooms.indexOf(room) > -1;
}
public function Room(rect:Rectangle, outerRect:Rectangle) {
this.rect = rect;
this.outerRect = outerRect;
this.outerRectInflated = outerRect.clone();
this.outerRectInflated.inflate(1, 1);
}
public function isAdjacentTo(room:Room):Boolean {
var intersection:Rectangle = outerRectInflated.intersection(room.outerRectInflated);
// 面積2未満の交差は交差していると見なさない
return (intersection.width * intersection.height >= 2);
}
// 部屋と部屋の重なっている方向を取得する
public function getIntersectOrientation(room:Room):String {
var intersection:Rectangle = outerRectInflated.intersection(room.outerRectInflated);
if (intersection.width > intersection.height) {
return Orientation.HORIZONTAL;
} else {
return Orientation.VERTICAL;
}
}
public function getIntersectingDirection(room:Room):int {
var intersection:Rectangle = outerRectInflated.intersection(room.outerRectInflated);
if (intersection.width * intersection.height < 2) {
// 面積2未満の交差は交差していると見なさない
return Direction.NONE;
}
if (intersection.width > intersection.height) {
// 水平方向(上下)の交差
if (this.rect.y < intersection.y) {
return Direction.DOWN;
} else {
return Direction.UP;
}
} else if (intersection.width == intersection.height) {
// 斜め方向の交差?
return Direction.NONE;
} else {
// 垂直方向(左右)の交差
if (this.rect.x < intersection.x) {
return Direction.RIGHT;
} else {
return Direction.LEFT;
}
}
}
}
function splitRectangle(src:Rectangle, time:int = 1, ratio:Number = 0.5, randomRatio:Range = null, initialOrientation:String = Orientation.AUTO, splittingMethod:String = SplittingMethod.AUTO):Array {
var rect:Rectangle = src.clone();
var result:Array = [ rect ];
var orientation:String = initialOrientation;
var tinyNumber:Number = Math.pow(2, -8);
if (time < 1 || ratio >= 1)
return result;
for (var i:int = 0; i < time; i++) {
if (randomRatio != null)
ratio = randomRatio.randomValue;
if (orientation == Orientation.AUTO)
orientation = (rect.width >= rect.height) ? Orientation.VERTICAL : Orientation.HORIZONTAL;
var rectB:Rectangle = rect.clone();
if (orientation == Orientation.VERTICAL) {
rect.width *= ratio;
rectB.x += rect.width;
rectB.width *= (1 - ratio);
// 1マス空ける用の補正
if (rect.width % 1 == 0)
rect.width -= tinyNumber;
} else if (orientation == Orientation.HORIZONTAL) {
rect.height *= ratio;
rectB.y += rect.height;
rectB.height *= (1 - ratio);
// 1マス空ける用の補正
if (rect.height % 1 == 0)
rect.height -= tinyNumber;
}
// 二周目以降は長い方を分割していく
orientation = Orientation.AUTO;
if (splittingMethod == SplittingMethod.AUTO) {
// 面積順にソートし、一番大きいものを分割対象とする
var copy:Array = result.concat();
copy.sort(compareRectangle, Array.DESCENDING);
rect = copy[0];
} else {
rect = rectB;
}
result.push(rectB);
}
return result;
}
function compareRectangle(a:Rectangle, b:Rectangle):int {
return (a.width * a.height) - (b.width * b.height);
}
class SplittingMethod {
// 最も大きい矩形を優先的に分割する
public static const AUTO:String = "auto";
// 分割した片割れ(右側・下側)を連続的に分割する
public static const SEQUENTIAL:String = "sequential";
}
function fixRectangle(rect:Rectangle):void {
var diffX:Number, diffY:Number;
diffX = Math.ceil(rect.x) - rect.x;
diffY = Math.ceil(rect.y) - rect.y;
rect.x = Math.ceil(rect.x);
rect.width = Math.floor(rect.width - diffX);
rect.y = Math.ceil(rect.y);
rect.height = Math.floor(rect.height - diffY);
}
function getRandomPoint(rect:Rectangle, flooring:Boolean = true):Point {
var x:Number = Math.random() * rect.width + rect.x;
var y:Number = Math.random() * rect.height + rect.y;
if (flooring) {
x = Math.floor(x);
y = Math.floor(y);
}
return new Point(x, y);
}
class Orientation {
public static const AUTO:String = "auto";
public static const VERTICAL:String = "v";
public static const HORIZONTAL:String = "h";
}
class Direction {
public static const DOWN:int = 2;
public static const LEFT:int = 4;
public static const NONE:int = 5;
public static const RIGHT:int = 6;
public static const UP:int = 8;
}
class Range {
private var _min:Number = Number.MIN_VALUE;
public function get min():Number {
return _min;
}
public function set min(value:Number):void {
if (value > max)
value = max;
_min = value;
}
private var _max:Number = Number.MAX_VALUE;
public function get max():Number {
return _max;
}
public function set max(value:Number):void {
if (value < min)
value = min;
_max = value;
}
public function get randomValue():Number {
if (min == max)
return min;
else
return Math.random() * (max - min) + min;
}
public function Range(min:Number, max:Number) {
this.min = min;
this.max = max;
}
}