forked from: Schelling Segregation Model (Weighted values)
Since not every country may have equal race distribution (eg. minority vs majority races),, it makes sense to add weighting to this model. Can Schelling Segregation be used to determine city planning layouts?
/**
* Copyright Glidias ( http://wonderfl.net/user/Glidias )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/iTAu
*/
package {
import com.bit101.components.InputText;
import com.bit101.components.Label;
import com.bit101.components.NumericStepper;
import com.bit101.components.PushButton;
import com.bit101.components.VBox;
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Sprite;
import flash.events.Event;
import flash.geom.Rectangle;
public class FlashTest extends Sprite {
private var weightVBox:VBox;
public static var WEIGHTS_DEFAULTED:Array = [ 1, .6, .8, .4, .9, 1.3]; // use this for weighting.
public static const ORANGE:uint = 0xea4b35; // retail
public static const GREEN:uint = 0x1aaf5d; // recreational
public static const BLUE:uint = 0x217fbc; // service
public static const YELLOW:uint = 0xf3c500; // creative
public static const PURPLE:uint = 0x8f3fb0; // food
public static const WHITE:uint = 0xffffff; //residential
public var colors:Array = [ORANGE, GREEN, BLUE, YELLOW, PURPLE, WHITE]; //0x1aaf5d];
public var WEIGHTS:Array;
public var WEIGHTS_TOTAL:Number = 1;
public var mainBitmap:Bitmap;
public var mainBitmapData:BitmapData;
public var cells:Vector.<Vector.<int>>;
public var tile_width:int = 8;
public var tile_height:int = 8;
public var tileNum_X:int = 58;
public var tileNum_Y:int = 50;
public var gridSprite:Sprite;
public var PRNG_gen:PM_PRNG;
public var seed:int = Math.random() * int.MAX_VALUE;
public var eraseCellNum:int = 50;
public var raceNum:int = 6;
private function recalculateWeights():void {
var weight:Number;
var i:int;
var numUndeclaredWeights:Number = 0;
var totalDeclaredWeight:Number = 0;
for (i = 0; i < raceNum; i++) {
weight = WEIGHTS[i];
totalDeclaredWeight += weight;
}
WEIGHTS_TOTAL = totalDeclaredWeight;
}
private function getRaceID(floatRatio:Number):int {
// return floatRatio * raceNum + 1;
floatRatio *= WEIGHTS_TOTAL;
var accum:Number = 0;
var result:int = 0;
for (var i:int = 0; i < raceNum; i++) {
if (floatRatio < accum) { // did not meet requirement
break;
}
accum += WEIGHTS[i];
result = i;
}
return result + 1;
}
public var emptyArr:Array = [0, tileNum_X - 1, (tileNum_Y - 1) * tileNum_X, tileNum_X * tileNum_Y - 1];
public var dir:Array = [[ -1, -1], [ -1, 0], [ -1, 1], [0, 1], [1, 1], [1, 0], [1, -1], [0, -1]];
public var happyNeighborNum:Array = [0, 1, 1, 2, 2, 2, 3, 3, 3];
public var escapeTryNum:int = 6;
public var _randomSeedInputText:InputText;
public var happyLabel:Label;
public function FlashTest() {
// write as3 code here..
PRNG_gen = new PM_PRNG();
PRNG_gen.seed = seed;
mainBitmapData = new BitmapData(465, 465, false, 0x292929);
mainBitmap = new Bitmap(mainBitmapData);
addChild(mainBitmap);
var i:int, j:int;
gridSprite = new Sprite(); //draw Line
gridSprite.graphics.lineStyle(0, 0x7f7f7f);
for (i = 0; i < tileNum_X + 1; i += 1)
{
gridSprite.graphics.moveTo(i * tile_width, 0);
gridSprite.graphics.lineTo(i * tile_width, tileNum_Y * tile_height);
}
for (i = 0; i < tileNum_Y + 1; i += 1)
{
gridSprite.graphics.moveTo(0, i * tile_height);
gridSprite.graphics.lineTo(tileNum_X * tile_width, i * tile_height);
}
addChild(gridSprite);
cells = new Vector.<Vector.<int>>(tileNum_Y);
for (i = 0; i < tileNum_Y; i += 1)
{
cells[i] = new Vector.<int>(tileNum_X);
for (j = 0; j < tileNum_X; j += 1)
{
//cells[i][j] = (i % 2 == j % 2) ? 1 : 2;
cells[i][j] = int(PRNG_gen.nextDouble() * raceNum) + 1;
}
}
for (i = 0; i < emptyArr.length; i += 1)
{
var tx:int = emptyArr[i] % tileNum_X;
var ty:int = emptyArr[i] / tileNum_X;
cells[ty][tx] = 0;
}
//cells[0][0] = 0;
//cells[0][tileNum_X - 1] = 0;
//cells[tileNum_Y - 1][0] = 0;
//cells[tileNum_Y - 1][tileNum_X - 1] = 0;
var _randomSeedLabel:Label = new Label(this, 10, tile_height * tileNum_Y + 10, "Random Seed");
_randomSeedInputText = new InputText(this, _randomSeedLabel.x + _randomSeedLabel.width + 5, _randomSeedLabel.y, String(seed));
_randomSeedInputText.restrict = "0-9";
var _randomSeedButton:PushButton = new PushButton(this, _randomSeedInputText.x + _randomSeedInputText.width + 5, _randomSeedInputText.y, "Another Seed",
function(e:Event):void
{
_randomSeedInputText.text = String(int(Math.random() * int.MAX_VALUE));
}
);
var _resetButton:PushButton = new PushButton(this, _randomSeedButton.x + _randomSeedButton.width + 5, _randomSeedButton.y, "Reset", onReset);
var showHeightWeights:PushButton = new PushButton(this, _randomSeedButton.x + _randomSeedButton.width + 5 + _resetButton.width, _randomSeedButton.y, "Weights", showWeightsHandler);
var _eraseRandomCellButton:PushButton = new PushButton(this, _randomSeedLabel.x, _randomSeedLabel.y + _randomSeedLabel.height + 10, "Erase Cell", eraseRandomCells);
var _segregateButton:PushButton = new PushButton(this, _eraseRandomCellButton.x + _eraseRandomCellButton.width + 5, _eraseRandomCellButton.y, "Do One Cycle", doSegregateOnce);
happyLabel = new Label(this, _segregateButton.x + _segregateButton.width + 5, _segregateButton.y, "");
var _raceLabel:Label = new Label(this, happyLabel.x + 80 + 10, happyLabel.y, "Race Num : ");
var _raceNumNumericStepper:NumericStepper = new NumericStepper(this, _raceLabel.x + 50 + 5, _raceLabel.y,
function(e:Event):void
{
raceNum = e.target.value;
}
);
_raceNumNumericStepper.minimum = 2;
_raceNumNumericStepper.maximum = 6;
_raceNumNumericStepper.value = raceNum;
drawCell(cells);
calcHappyPercent(cells);
weightVBox = new VBox(this);
for (i = 0; i < 6; i++) {
getWeightStepper(i, weightVBox)
}
onReset();
}
private function showWeightsHandler(e:Event):void
{
weightVBox.visible = !weightVBox.visible;
}
private function getWeightStepper(index:int, parent:Sprite):NumericStepper {
var stepper:NumericStepper = new NumericStepper(parent, 0,0, onWeightStepChange);
stepper.name = ""+index;
stepper.step = .1;
stepper.value = WEIGHTS_DEFAULTED[index];
return stepper;
}
private function onWeightStepChange(e:Event):void
{
WEIGHTS_DEFAULTED[int(e.currentTarget.name)] = e.currentTarget.value;
}
private function isHappy(i:int, j:int, color:int):Boolean
{
var neighborNum:int = 0;
var sameNeighbor:int = 0;
var k:int;
var tx:int, ty:int;
for (k = 0; k < dir.length; k += 1)
{
ty = i + dir[k][0];
tx = j + dir[k][1];
if (tx >= 0 &&
tx <= tileNum_X - 1 &&
ty >= 0 &&
ty <= tileNum_Y - 1)
{
if (cells[ty][tx] != 0)
{
neighborNum += 1;
if (color == cells[ty][tx])
{
sameNeighbor += 1;
}
}
}
}
if (happyNeighborNum[neighborNum] > sameNeighbor)
{
return false;
}
else
{
return true;
}
}
private function doSegregateOnce(e:Event = null):void
{
var i:int, j:int;
var temp:int;
var k:int;
var isMoved:Boolean;
for (i = 0; i < tileNum_Y; i += 1)
{
for (j = 0; j < tileNum_X; j += 1)
{
if (isHappy(i, j, cells[i][j]) == false)
{
isMoved = false;
for (k = 0; k < escapeTryNum; k += 1)
{
temp = emptyArr[int(PRNG_gen.nextDouble() * emptyArr.length)];
if (isHappy(int(temp / tileNum_X), temp % tileNum_X, cells[i][j]) == true)
{
cells[int(temp / tileNum_X)][temp % tileNum_X] = cells[i][j];
cells[i][j] = 0;
emptyArr.splice(temp, 1);
emptyArr.push(i * tileNum_X + j);
isMoved = true;
break;
}
}
if (isMoved == false)
{
cells[i][j] = 0; //dead
}
}
}
}
drawCell(cells);
calcHappyPercent(cells);
}
private function calcHappyPercent(cells:Vector.<Vector.<int>>):void
{
var happyNum:int = 0;
var allNum:int = 0;
var i:int, j:int;
for (i = 0; i < tileNum_Y; i += 1)
{
for (j = 0; j < tileNum_X; j += 1)
{
if (cells[i][j] > 0)
{
allNum += 1;
if (isHappy(i, j, cells[i][j]) == true)
{
happyNum += 1;
}
}
}
}
happyLabel.text = "Happy : " + String(int(happyNum / allNum * 10000) / 100) + "%";
}
private function onReset(e:Event = null):void
{
WEIGHTS = WEIGHTS_DEFAULTED.slice(0, raceNum);
recalculateWeights();
var i:int, j:int;
PRNG_gen.seed = parseInt(_randomSeedInputText.text);
emptyArr = [0, tileNum_X - 1, (tileNum_Y - 1) * tileNum_X, tileNum_X * tileNum_Y - 1];
cells = new Vector.<Vector.<int>>(tileNum_Y);
for (i = 0; i < tileNum_Y; i += 1)
{
cells[i] = new Vector.<int>(tileNum_X);
for (j = 0; j < tileNum_X; j += 1)
{
//cells[i][j] = (i % 2 == j % 2) ? 1 : 2;
cells[i][j] = getRaceID( PRNG_gen.nextDouble() ); // int(PRNG_gen.nextDouble() * raceNum) + 1;
}
}
for (i = 0; i < emptyArr.length; i += 1)
{
var tx:int = emptyArr[i] % tileNum_X;
var ty:int = emptyArr[i] / tileNum_X;
cells[ty][tx] = 0;
}
drawCell(cells);
calcHappyPercent(cells);
}
private function eraseRandomCells(e:Event = null):void
{
var i:int;
emptyArr = emptyArr.concat(printRandomNumber(tileNum_X * tileNum_Y, eraseCellNum));
var tempX:int, tempY:int;
for (i = 0; i < emptyArr.length; i += 1)
{
tempX = emptyArr[i] % tileNum_X;
tempY = emptyArr[i] / tileNum_X;
cells[tempY][tempX] = 0;
}
drawCell(cells);
calcHappyPercent(cells);
//trace(emptyArr);
}
private function printRandomNumber(n:int, k:int) : Array
{
var original:Array=[];
var result:Array=[];
var i:int;
var randInt:int;
var temp:Object;
for(i=0;i<n;i+=1)
{
original.push(i);
}
for(i=0;i<k;i+=1)
{
randInt = PRNG_gen.nextDouble() * (n - i) + i;
temp = original[i];
original[i] = original[randInt];
original[randInt] = temp;
result.push(original[i]);
}
return result;
}
private function drawCell(cells:Vector.<Vector.<int>>):void
{
mainBitmapData.fillRect(mainBitmapData.rect, 0x292929);
var i:int, j:int;
for (i = 0; i < tileNum_Y; i += 1)
{
for (j = 0; j < tileNum_X; j += 1)
{
mainBitmapData.fillRect(new Rectangle(j * tile_width +1, i * tile_height + 1, tile_width - 2, tile_height - 2), colors[cells[i][j] - 1]);
}
}
}
}
}
/*
* Copyright (c) 2009 Michael Baczynski, http://www.polygonal.de
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/**
* Implementation of the Park Miller (1988) "minimal standard" linear
* congruential pseudo-random number generator.
*
* For a full explanation visit: http://www.firstpr.com.au/dsp/rand31/
*
* The generator uses a modulus constant (m) of 2^31 - 1 which is a
* Mersenne Prime number and a full-period-multiplier of 16807.
* Output is a 31 bit unsigned integer. The range of values output is
* 1 to 2,147,483,646 (2^31-1) and the seed must be in this range too.
*
* David G. Carta's optimisation which needs only 32 bit integer math,
* and no division is actually *slower* in flash (both AS2 & AS3) so
* it's better to use the double-precision floating point version.
*
* @author Michael Baczynski, www.polygonal.de
*/
Class
{
class PM_PRNG
{
/**
* set seed with a 31 bit unsigned integer
* between 1 and 0X7FFFFFFE inclusive. don't use 0!
*/
public var seed:uint;
public function PM_PRNG()
{
seed = 1;
}
/**
* provides the next pseudorandom number
* as an unsigned integer (31 bits)
*/
public function nextInt():uint
{
return gen();
}
/**
* provides the next pseudorandom number
* as a float between nearly 0 and nearly 1.0.
*/
public function nextDouble():Number
{
return (gen() / 2147483647);
}
/**
* provides the next pseudorandom number
* as an unsigned integer (31 bits) betweeen
* a given range.
*/
public function nextIntRange(min:Number, max:Number):uint
{
min -= .4999;
max += .4999;
return Math.round(min + ((max - min) * nextDouble()));
}
/**
* provides the next pseudorandom number
* as a float between a given range.
*/
public function nextDoubleRange(min:Number, max:Number):Number
{
return min + ((max - min) * nextDouble());
}
/**
* generator:
* new-value = (old-value * 16807) mod (2^31 - 1)
*/
private function gen():uint
{
//integer version 1, for max int 2^46 - 1 or larger.
return seed = (seed * 16807) % 2147483647;
/**
* integer version 2, for max int 2^31 - 1 (slowest)
*/
//var test:int = 16807 * (seed % 127773 >> 0) - 2836 * (seed / 127773 >> 0);
//return seed = (test > 0 ? test : test + 2147483647);
/**
* david g. carta's optimisation is 15% slower than integer version 1
*/
//var hi:uint = 16807 * (seed >> 16);
//var lo:uint = 16807 * (seed & 0xFFFF) + ((hi & 0x7FFF) << 16) + (hi >> 15);
//return seed = (lo > 0x7FFFFFFF ? lo - 0x7FFFFFFF : lo);
}
}
}