« Genetic Algorythm Part3(JP) | Main | 企業が好む表現対策 Part2<吹出し>::Roundtrip Clip(JP) »

企業が好む表現対策 Part1<占い>::Circuit Maker(JP)

企業は占いが好きだ。TVラジオ新聞雑誌などメディア全般で日々頻繁に現れる占いは企業のウェブサイトでも当然使う機会が多い。今回制作したものは毎月一定のテキストを日々巡回して表示するもので、一般に占いなどで使われる仕組みである。通常のランダム生成していることが多くないだろうか。でもこれでは同じテキストが3日連続で出ることもある。しかし、固定のリストを埋め込む方法もあるが、常に同じものが同じ順番に必ず出てしまう。ここではあり得そうなオーダーを考えて2つの条件を用意した。
※ワードとはここでは画面に表示する文字列を表す。

必要な要素をまとめる
まず、絶対に必要とされる要素として、「一ヶ月単位でワードを繰り返したい」「毎日違うテキストを表示したい」と、「ワードは日数分は用意出来ない」ということはよくあるだろう。一ヶ月単位でワードを繰り返すということは、一ヶ月30日の時に20種類のワードが用意されていた場合、20日間は重なることなく20種類出現し、残りの10日間は再び20種類の中から10種類が現れる。どういう機能を載せて実現していくか並べてみると、

・XMLから一定数のワードリストを読み込む(修正のしやすさを考慮)
・毎日違うテキストを表示する(基本オーダーより)
・同じ日は同じテキストを表示する(基本オーダーより)
・月ごとにリストを再生成する(応用として、余裕があれば)

動作の概要
まず、xmlから読み込まれたドキュメントからワードの配列を生成する。次に、31日分の配列を作成、その中にワードをループで順に挿入する。ワードを入れた配列をシャッフルして順の一定さを無くす。今日の日付をindexとして配列からワードを取り出す。一回目だけ作成されたリストがローカル共有オブジェクトに保存されて、2回目以降の訪問では保存されたリストからワードが取り出されるので、毎回ランダムにはならなくなる。月ごとに違うリストを持たせることもできる。

実際に動作しているサンプルは記事の最後にある。このサンプルではXMLから取り出した2つの文字を使って表示する。このsourceも自由に利用出来るようにしてある。

var cm:CircuitMaker;
var cl:CircuitXML = new CircuitXML();
function onXMLLoad(){
	cm = new CircuitMaker(cl.getList(), "uranai", false);
	result.text = (cm.getString());
}
cl.addEventListener("onXMLLoad", this);
cl.load("norm.xml");

XMLデータの構造
これが読み込まれるXMLでとても単純な構造でワードリストが定義されている。wordタグが実際表示されるワードを持つ。

<?xml version="1.0" encoding="UTF-8" ?>
<circuit>
<word id="1">日は夕焼け</word>
<word id="2">日は夕立</word>
</circuit>
norm.xml

XMLローダ
CircuitXMLは中にXMLを持ち、上で定義したXMLドキュメントを読み込み、CircuitMakerで利用できるデータ(配列)に変換するところまでの役割を持つ。

public function load(url):Void{
	var xml:XML = new XML();
	var owner:CircuitXML = this;
	xml.ignoreWhite = true;
	xml.onLoad = function(success:Boolean):Void{
		if(success){
			owner._o.list = owner._xmlToArray(this);
			owner.dispatchEvent({type:"onXMLLoad"});
		}else{
			trace("Loader error.");
		}
	};
	xml.load(url);
}
xmlから配列を生成する。これにはこのXMLの構造がnorm.xmlのような形式であることが分かっていなければならない。
private function _xmlToArray(xml:XML):Array{
	var strList:Array = xml.firstChild.childNodes;
	var i:Number;
	var result:Array = new Array();
	for(i = 0; i < strList.length; i++){
		result.push(strList[i].firstChild.nodeValue);//<word>nodeValue</word>
	}
	return result;
}

本体の作成
テキストを日付毎に巡回して表示するために最も重要なCircuitMakerクラスではXMLローダにより作成された配列データを基にしてまずローカル共有オブジェクトにあるかを確認する。すでに作成されていた場合そのリスを使うが、無い場合31日分のワードリストを作成し、ローカルに保存する。ここでString型に一度変換してデータサイズを小さくする工夫をしている。updateEveryMonthフラグがtrueの場合、このリスト生成を毎月必ず行う。

public function CircuitMaker(strList:Array, soName:String, updateEveryMonth:Boolean) {

	var now:Date = new Date();
	var so:SharedObject = SharedObject.getLocal(soName, "/");
	if(so.data.list == undefined){
		so.data.list = _createList(strList, CIRCUIT_SIZE).join(DELEMITER);//String変換して保存
		so.data.month = now.getMonth();//リストを作成した月も保存
	}else{
		if(updateEveryMonth && so.data.month != now.getMonth()){//月が変わった時も作成
			so.data.list = _createList(strList, CIRCUIT_SIZE).join(DELEMITER);
			so.data.month = now.getMonth();
		}
	}
	_list = so.data.list.split(DELEMITER);
}
31日分の配列にワードリストからワードが割り当てられ、乱数でindex間を入れ替えることで配列の順番をバラバラに組み直す。ワードリストの数単位でシャッフルすることが重要である。31個まとめてシャッフルをするとワードが連続する可能性があるためである。
private function _createList(arr:Array, size:Number):Array{
	var i, count:Number = 0;
	var len:Number = arr.length;
	var result:Array = new Array();
	while(1){
		var unit:Array = new Array();
		for(i = 0; i < len; i++){//ワードリストを取り出す
			unit[i] = arr[i];
			if(++count == size){//31になったらやめる
				result.push(_shuffleList(unit));
				while(result.length > 1){
					result[0] = result[0].concat(result.pop());//配列同士の連結
				}
				return result[0];
			}
		}
		result.push(_shuffleList(unit));
	}
}
private function _shuffleList(arr:Array):Array{
	var i:Number = 0;
	var len:Number = arr.length;
	for(i = 0; i < len; i++){
		 var tmp:Object = arr[i];
		 var randomNum:Number = random(len);
		 arr[i] = arr[randomNum];
		 arr[randomNum] = tmp;
	 }
	return arr;
}

Post a comment