2010年02月28日

limechat2でjavascript作成(アリアンロッドのランダムダンジョン簡易版スクリプト)

javascript好きの皆さんこんばんは!
検索でブログ見に来ていただいている大半が、
ライムチャットとスクリプト絡みであることに最近気が付いた、海月歩空です。

というわけで、あまり書いていないなぁ、と思ったので、
久しぶりにスクリプト作成を行います。

ざっくり適当に書いていくので、
もっといい書き方はあると思いますよ!

後、過去作っている関数も使っているので、なんだろうこれ、
みたいになったら、過去も参照してください。

というか、ですね。
これ、いったん9割書き終わっていたんですよ。
それも、スクリプト作成しながら、ノリノリで。
でも、なんか瞬間停電がおきたらしく、
ぶっつりとデータが消えちゃいました……。
絶望した!

そんな悲しみに暮れているので、
本気で適当に書いています。
(説明とか)
後で見直したら、っていうか今見ても、
ここ直したいなぁ、というところがあるので、
ちょっと使ってみたい、とか、
この人はこうやって書いているのかと参考にしたい人向けです。

さて、今回はなにを作ったかというと、
アリアンロッドTRPGのランダムダンジョン用スクリプトです。
ただし、私はランダムダンジョンのデータを持っていません!
というわけで、簡易版となります。
プレイヤーとして参加したときの感覚から作っているので、
マップデータや扉も選択できる機能があればもっと便利になるんでしょうが、
データがないことには始まらない。

そのうち買ったら修正版……とかになるかなぁ。

さてさて。
簡易版って言うのはどんな機能? と思われますか?
ざっくり説明すると、トランプを混ぜて引いて、表示。
これくらいの機能です。

わお、ざっくり!

実際、それくらいしか作っていないので、書き用もないんですが!
細かい内容としては、いつもの適当仕様があったんですが、
やっぱり無くなってしまったので、更に適当にいきます。
・「探索準備[9]」で全てを未使用に変更後、
エース以外を8枚分とエースを1枚分をその階層で使用するモノとしてチェックする。
・「次のエリアへ」で、一回前に表示したモノを使用済みとし、
その階層で使用するものから無作為に一枚表示。
・「次の階層へ[9]」で、その階層で使用した・するモノを使用済みにし、エースのみ未使用に戻し、
未使用のエース以外を8枚分とエースを1枚分をその階層で使用するモノとしてチェックする。
・探索準備と次の階層へは、[]が無い場合、デフォルトの数字になる。
位でしょうか。

後、重要な要素として、トランプなので、使用したかどうかを記憶しておく必要があります。
(未使用だとか使用済みだとかというところですね)
エース以外は、同じのがでないようにする仕組みだけなら、配列とかでもいいのでしょうが、
回線切断なども含めて考えると、外部ファイルを使用して記録するのがいいでしょう。

というわけで、作ってみた結果が以下の通りです。
1個目は、前作ったダイスの基本関数を拡張したもの。
ファイル名:diceRoll.js

/******************************************************
ダイス基本関数

基本説明
ダイス個数回分、ダイス面数で指定されたダイスを振り、
ダイスの振った結果をオブジェクトとして返却する。

in
diceNum:ダイス個数(正数)
diceFace:ダイス面数(正数)

out
ダイス結果object
[
array:ダイス結果配列(Array)
sum:ダイス結果合計値
]
*******************************************************/
function diceRoll(diceNum, diceFace)
{
//返却用ダイスobject
var dice = new Object();
//返却用ダイス結果合計値
dice.sum = 0;
//返却用ダイス結果配列
dice.array = new Array();

if (diceNum <= 0 || diceFace <= 0)
{
dice.array.push(0);
return(dice);
}

//ダイス個数分、ダイス面数で指定されたダイスを振る。
for (var i = 0; i < diceNum; i++)
{
//ダイスを振った結果をダイス結果配列に。
dice.array.push(rand(diceFace));

//ダイスを振った結果を合計値に足していく。
dice.sum += dice.array[i];
}

//結果返却
return(dice);
}

//乱数関数ファイル読み込み
var fileObj = openFile("mt.js");
if (fileObj) {

try
{
var fileStr = fileObj.readAll();
fileObj.close();
eval(fileStr);
var mt1 = new MersenneTwister();
var myRand = function () {return mt1.next();};
log("mt.js読み込み成功");
}
catch(e)
{
var myRand = function () {return Math.random();};
log("mt.jsファイル読み込み失敗");
}
} else {
var myRand = function () {return Math.random();};
log("mt.jsファイルオープン失敗");
}

/*
ランダム関数

引数
num : 乱数範囲1-numの最大値
返却値
int数値
*/
function rand(num)
{
return(Math.floor(myRand() * num) + 1);
}

ここで取り込んでいる乱数関数ファイルというのは、
メルセンヌツイスターです。
こちらのサイトのメルセンヌツイスターのjsファイルがあれば、
取り込んでメルセンヌ関数のランダムになります。
無くても標準のランダム関数がロードされます。
個人的趣味の仕様ですね。

次はランダムダンジョンスクリプトのファイル。
ファイル名:ランダムダンジョン.js

/*******************************************************
ランダムダンジョンスクリプト機能

一意のID
表示項目(ハート 1等)
トランプの状態(未使用:0、階層選択状態:1、使用中:2、使用済:3)
スートのエースかどうかの区分(エースで無い:0、ある:1)
以上4項目をカンマ区切りで記述したものが複数行存在するtrump.txtを操作し、
ランダムダンジョン処理を行う。
********************************************************/
//文字列入力処理
function event::onChannelText(prefix, channel, text)
{

var drowCount;
var area;

//関数呼び出しワード
if (text.match(/^探索準備(\[(\d+)\])?([  ].*)?$/i))
{

drowCount = RegExp.$2;

if (drowCount == "")
{
drowCount = 6;
}

//初期化関数、階層関数呼び出し
if (setUpDungeon() && drawDungeon(drowCount))
{
//メッセージ
send(channel, "OK!");
}
else
{
//メッセージ
send(channel, "BAD SETTING!");
}
}

//関数呼び出しワード
if (text.match(/^次の階層へ(\[(\d+)\])?([  ].*)?$/i))
{

drowCount = RegExp.$2;

log(drowCount);
if (drowCount == "")
{
drowCount = 6;
}

if (drawDungeon(drowCount))
{
//メッセージ
send(channel, "OK!");
}
else
{
//メッセージ
send(channel, "BAD SETTING!");
}
}

//関数呼び出しワード
if (text.match(/^次のエリアへ([  ].*)?$/i))
{

if (reverseDungeon())
{
if ((area = dispDungeon()) != null)
{
//メッセージ
send(channel, "next area :" + area);
}
else
{
//メッセージ
send(channel, "this floor is over.");
}
}
else
{
//メッセージ
send(channel, "BAD SETTING!");
}
}


}

/******************************************************
↓初期読み込み処理↓
******************************************************/
//ダイス関数ファイル読み込み
var fileObj = openFile("diceRoll.js");
if (fileObj) {
try
{
var fileStr = fileObj.readAll();
fileObj.close();
eval(fileStr);
}
catch(e)
{
log("diceRoll.jsファイル読み込み失敗");
}
} else {
log("diceRoll.jsファイルオープン失敗");
}

/******************************************************
↑初期読み込み処理↑
******************************************************/

/******************************************************
トランプテキスト読み込み関数

基本説明
trump.txtを読み込み、文字列を返却する。
in
なし

out
テキストデータ/null
*******************************************************/
function readTrumpTxt()
{
try
{
var fileObj = openFile("trump.txt", true);
var fileStr = fileObj.readAll();
fileObj.close();

//結果返却
return(fileStr);
}
catch(e)
{
return(null);
}
}


/******************************************************
トランプテキスト書き込み関数

基本説明
trump.txtに文字列を書き込む。
in
テキストデータ

out
true/false
*******************************************************/
function writeTrumpTxt(trumpTxt)
{
try
{
var fileObj = openFile("trump.txt", false);
fileObj.write(trumpTxt);
fileObj.close();

//結果返却
return(true);
}
catch(e)
{
return(false);
}
}

/******************************************************
初期化関数

基本説明
初期化
in
なし

out
true/false
*******************************************************/
function setUpDungeon()
{
try
{
//変数設定
var trumpData = "";

//ファイル読み込み
if ((trumpData = readTrumpTxt()) == null)
{
//結果返却
return(false);
}

//データ初期化
trumpData = trumpData.replace(/(\d+,[^,]+,)\d(,\d)/g, "$10$2");

//ファイル書き込み
if (writeTrumpTxt(trumpData))
{

//結果返却
return(true);
}
return(false);
}
catch(e)
{
return(true);
}
}

/******************************************************
ダンジョン階層設定関数

基本説明
現在、エース以外で未使用以外のモノを使用済みに変更し、
エースを未使用状態に戻し、
エース以外からdrawCount-1個、無作為に階層選択状態に変更し、
エースを1個無作為に階層選択状態に変更する。
in
drawCount : その階層に使用するトランプの枚数

out
true/false
*******************************************************/
function drawDungeon(drawCount)
{
try
{
//変数設定
var trumpData = "";

var reg;
var result;
var idArray;
var idData;
var idNum;

//ファイル読み込み
if ((trumpData = readTrumpTxt()) == null)
{
//結果返却
return(false);
}

//エース以外で未使用以外の状態のモノを、使用済に変更する
trumpData = trumpData.replace(/(\d+,[^,]+,)[12](,0)/g, "$13$2");

//エースを未使用に戻す
trumpData = trumpData.replace(/(\d+,[^,]+,)\d(,1)/g, "$10$2");

//エース以外の未使用を抽出し、配列へ
reg = new RegExp(/(\d+),[^,]+,0,0/g);
idArray = new Array();
while((result = reg.exec(trumpData)) != null)
{
idArray.push(RegExp.$1);
}

//未使用が設定件数より少ないのでエラー
if (idArray.length < drawCount - 1)
{
return(false);
}

//エース以外からdrawCount-1個、無作為に階層選択状態に変更
for (var i = 0; i < drawCount - 1; i++)
{
idData = rand(idArray.length) - 1;
idNum = idArray[idData];
rep = new RegExp("(" + idNum + ",[^,]+,)0(,0)");
trumpData = trumpData.replace(rep, "$11$2");
idArray.splice(idData, 1);
}

//エースの未使用を抽出し、配列へ
reg = new RegExp(/(\d+),[^,]+,0,1/g);
idArray = new Array();
while((result = reg.exec(trumpData)) != null)
{
idArray.push(RegExp.$1);
}

//エースから1個、無作為に階層選択状態に変更
idData = rand(idArray.length) - 1;
idNum = idArray[idData];
rep = new RegExp("(" + idNum + ",[^,]+,)0(,1)");
trumpData = trumpData.replace(rep, "$11$2");
idArray.splice(idData, 1);

//ファイル書き込み
if (writeTrumpTxt(trumpData))
{

//結果返却
return(true);
}
return(false);
}
catch(e)
{
return(true);
}
}

/******************************************************
ダンジョン進行関数

基本説明
現在、使用中のモノを使用済みに変更し、
階層選択状態のモノから無作為に使用中状態に変更する。
in
なし

out
true/false
*******************************************************/
function reverseDungeon()
{
try
{
//変数設定
var trumpData = "";

var reg;
var result;
var idArray;
var idData;
var idNum;

//ファイル読み込み
if ((trumpData = readTrumpTxt()) == null)
{
//結果返却
return(false);
}

//使用中状態のモノを、使用済に変更する
trumpData = trumpData.replace(/(\d+,[^,]+,)2(,\d)/g, "$13$2");

//階層選択状態を抽出し、配列へ
reg = new RegExp(/(\d+),[^,]+,1,\d/g);
idArray = new Array();
while((result = reg.exec(trumpData)) != null)
{
idArray.push(RegExp.$1);
}

//階層選択状態から1個、無作為に使用中状態に変更
idData = rand(idArray.length) - 1;
idNum = idArray[idData];
rep = new RegExp("(" + idNum + ",[^,]+,)1(,[01])");
trumpData = trumpData.replace(rep, "$12$2");
idArray.splice(idData, 1);

//ファイル書き込み
if (writeTrumpTxt(trumpData))
{
//結果返却
return(true);
}
return(false);
}
catch(e)
{
return(true);
}
}

/******************************************************
ダンジョン表示関数

基本説明
現在、使用中の表示項目を返却する。
in
なし

out
文字列/null
*******************************************************/
function dispDungeon()
{
try
{
//変数設定
var trumpData = "";

var reg;
var result;
var trumpStr;

//ファイル読み込み
if ((trumpData = readTrumpTxt()) == null)
{
//結果返却
return(null);
}

//使用中状態を抽出
reg = new RegExp(/\d+,([^,]+),2,\d/);
if ((result = reg.exec(trumpData)) != null)
{
trumpStr = RegExp.$1;
return(trumpStr);
}

return(null);
}
catch(e)
{
return(null);
}
}


そして読み込むトランプのファイル。
ファイル名:trump.txt

1,スペード 1,1,1
2,スペード 2,0,0
3,スペード 3,0,0
4,スペード 4,0,0
5,スペード 5,0,0
6,スペード 6,0,0
7,スペード 7,1,0
8,スペード 8,1,0
9,スペード 9,0,0
10,スペード 10,0,0
11,スペード 11,1,0
12,スペード 12,0,0
13,スペード 13,1,0
14,ハート 1,0,1
15,ハート 2,0,0
16,ハート 3,0,0
17,ハート 4,0,0
18,ハート 5,0,0
19,ハート 6,0,0
20,ハート 7,0,0
21,ハート 8,0,0
22,ハート 9,0,0
23,ハート 10,0,0
24,ハート 11,0,0
25,ハート 12,0,0
26,ハート 13,1,0
27,クローバー 1,0,1
28,クローバー 2,2,0
29,クローバー 3,0,0
30,クローバー 4,0,0
31,クローバー 5,0,0
32,クローバー 6,0,0
33,クローバー 7,0,0
34,クローバー 8,0,0
35,クローバー 9,0,0
36,クローバー 10,0,0
37,クローバー 11,0,0
38,クローバー 12,0,0
39,クローバー 13,0,0
40,ダイヤ 1,0,1
41,ダイヤ 2,1,0
42,ダイヤ 3,0,0
43,ダイヤ 4,0,0
44,ダイヤ 5,0,0
45,ダイヤ 6,0,0
46,ダイヤ 7,0,0
47,ダイヤ 8,1,0
48,ダイヤ 9,0,0
49,ダイヤ 10,0,0
50,ダイヤ 11,0,0
51,ダイヤ 12,0,0
52,ダイヤ 13,0,0

うん、これはただのCSVっぽいファイルですね。

※rand()や読み込んでいる説明なしにテキスト、読み込みの処理などは、
過去のブログ記事を参照してください。

今回のこだわりは、「CSV風味のテキストを正規表現で操作する仕組み」です。
たぶん普通なら配列などで操作するところを、
(例えばカンマ区切りを切ったりはったり、使用・未使用を別配列に入れたり)
置き換えやらマッチングやらで解決!
(配列も一部使っていますが)

地味に、ただの文字列変数でやれているあたりがセクシィです。
(自画自賛気味)

個人的には、もう少し改良の余地があるので
(2回3回同じようなことをしている部分があるのは、別にまとめられるはず)
そこがちょっと不満ですが、最初配列でやろうとしたときに時間がかかりすぎて、
気力がとぎれてしまったので今回はここまでにしておきます。

本当は地形データも組み込みたくなってきたのに手元に本が無いから、
完全に完成しないし、いいか! なんて思ってないんですよ!
(ぶっちゃけた!?)

では、今回のまとめと前回までのスクリプトを圧縮してここに上げておきます
必要があれば使ってみてください。
登録の仕方や使い方が解らないよ、とかの声があれば今度それ用の記事でも作ります。
他の場所でちゃんと説明しているサイトがいっぱいあるから、
それを参照した方が早いし正確なんですけどね!
(ぶっちゃけすぎです)

それではスクリプト大好き海月歩空でした!
posted by 海月歩空 at 11:37| Comment(0) | TrackBack(0) | プログラム

2009年04月01日

スクリプトのこと・その3

肩こりがひどいので、アンメルツが手放せられない、海月歩空です。こんばんは。

いつもいっているところでevalはいやだなぁ、という話を読んで、前回は、eval以外の方法でやってみよう、ということを考えてみました。
しかし結果は、直接ファイルを読み込むのと同じ意味合いになってしまいましたね、ということでした。
つまりは、再評価をしている時点であんまり変わらない、という感じでしょうか。

で。
ここ最近見てみると、その記事のコメントで、プリプロセッサ談義が花を咲かせている模様なので、私はlimechat2&javascriptで作るならと考えてみました。
考えた結果、コメントにするには思ったより長くなったのと、最近ダイスのネタをまとめられない(次回は六門か何かにする予定)ので、せっかくだからと記事にさせてもらいました。
かってしてます、ごめんなさい!

さてさて、プリプロセッサはざっくり言えば、ファイルを読み込んだり文字列を置き換えたり、条件によってソースを消したり出したりして、プログラムコンパイル用にいろいろやってくれるようなものですね。
つまり、通常のファイルに、”ここにこんなソース書いて欲しいなー”と言う印を付けておいて、置き換えるようにしておけば、インクルード風味のものができるわけです。
たぶん実際にも、インクルードって言うものはそんなものだと思わなくもないです(あやふやな表現ですね)
というわけで、プリプロセッサ風味に、実行すると置き換えるjavascriptをつくるのでしたら、単純な方法としてrepleceを使う方法があります。
例として、”/*test test*/”というコメントをソースに置き換えてくれるスクリプトを作成しましょう。


base.txt(ソース読み込み用元ファイル)

/*test インクルードコメントtext関数取り込み test*/
try
{
log(test());
}
catch(e)
{
log("プリプロセッサ風味の実行失敗");
}


test.txt(ソース置き換え用元ファイル)

function test()
{
return("プリプロセッサ風味の実行成功!");
}


preprocessor.js
//ソース読み込み用元ファイル
var fileObj = openFile("base.txt");
var baseStr = fileObj.readAll();
fileObj.close();

//ソース置き換え用元ファイル
var fileObj2 = openFile("test.txt");
var testStr = fileObj2.readAll();
fileObj2.close();

//置き換え(肯定先読み置換) "/*test〜test*/"を置き換え。
var overStr = baseStr.replace(/\/\*test.*(?=test\*\/)test\*\//g, testStr);
//書き込み
fileObj = openFile("output.js", false);
fileObj.truncate();
fileObj.write(overStr);
fileObj.close();


これで、preprocessor.jsを実行すると、output.jsに以下の感じで出力されます。

output.js
function test()
{
return("プリプロセッサ風味の実行成功!");
}

try
{
log(test());
}
catch(e)
{
log("プリプロセッサ風味の実行失敗");
}


書き換える度に、コンパイル的なことをしないといけないので、インタプリタの強みが無くなってしまうのけれど、別方向からのインクルードというならこういう形でしょうね。
ちなみに、なぜ肯定先読みしているかといわれれば、ただの好みですが、なにが入る予定、というコメントをかけるのがいいのでこういう形にしました。
それと、なんでコメント? 実行失敗したら結局変わらないのに、とかは、気持ちの問題というか、エディタでコメントの文字色が変わるような設定にしているからというか、そんな感じです(アバウトすぎです)
置き換えだと、ソース以外に、定数も定数風味に文字列を書いておいて、置き換えで数字や文字列にすることで使用できますね。
以上、プレコンパイル的javascript作成方法のひとつでした。


さてちなみに。
わざわざインクルード風味で行わなくても、くっつければいいじゃない、linuxならcatで、という風に言いましたが、windowsなら、typeコマンドでできることをご報告しておきます。
コマンドプロンプトで、”type a.txt b.txt >c.js”というようにすれば、楽ちん。
コピペコマンド風味ですけれど、これもまたひとつの方法になりますね。
posted by 海月歩空 at 22:49| Comment(0) | TrackBack(0) | プログラム

2009年03月27日

スクリプトのこと・その2

スクリプトのことでちょこちょことお話モードの海月歩空です。こんばんは。

時々行くところで、私の記事が取り上げられています! わほー。
記事の内容は、evalを使用して、インクルードする方法に心理抵抗が大きいとのこと。
気持ちはわからなくもないです。自分でごりごり書いているんではなくて、人のものだったら一応確認せずにはいられないです。
個人的には、インタプリタといえばeval、言語操作といえば正規表現というくらい、大好きな物なのですが、実際に悪いことしようと思えば、いくらでもできるのもevalなんですよね。
ファイル名:test.js
var fileObj = openFile("test2.js");
var str = fileObj.readAll();
eval(str);
fileObj.close();

ファイル名:test2.js
log("海月歩空大好きー");

こうすると、スクリプトコンソールに、自画自賛コメントが出てきます。ちょっぴり幸せ。
次は、すごく単純に悪いことする方法の簡易版。実際にはまねしちゃダメだぞ!
ファイル名:test2.js
fileObj = new Object();
fileObj.close = function()
{
log("海月歩空なんて大嫌いー");
};

例えば、こうすると、fileObjを上書きして、クローズしようとする度に非難メッセージが出てくる&ファイルクローズができない、なんてことが起こります。
これは危険。超危険。

というわけで、そんなあなたにはnew Function。
ファンクションオブジェクトを生成することができるものです。
使い方はいたって簡単。
基本的には、文字列を渡して実行すると、ファンクションオブジェクトができます。
使用例は以下の通り。
ファイル名:test.js
var testFunc = new Function("log('海月歩空大好きー');");
testFunc();

ということは、ファイルを読み込んで、文字列を渡してあげれば、関数になりそうです。
ファイル名:test.js
var fileObj = openFile("test2.js");
var str = fileObj.readAll();
testFunc = new Function(str);
testFunc();
fileObj.close();

ファイル名:test2.js
log("海月歩空大好きー");

おお、なんて簡単!
evalにもまけないぜー!続きを読む
posted by 海月歩空 at 02:33| Comment(0) | TrackBack(0) | プログラム

2009年03月25日

LimeChat2のjavaScriptでダイス作成(7)

ダブルクロスとお寿司大好き、海月歩空です。こんばんは。

ダブルクロスは、無駄に許容量が広いところが大好きです。
最近のリプレイの果てしなさがすごい……。
というわけで、対抗して、”寿司王子”をダブルクロスキャラクターとして作成しました。

米寿・司、ハヌマーン/サラマンダーの格闘家で、自然流琉球空手の伝承者として設定。

経験表−生まれ・名家の生まれ:祖父はスシ王。父はスシ伯爵。スシの名家に生まれた彼は、人呼んでスシ王子。
経験表−幼少・死別:祖父と父を、巨大カジキマグロ"ヌシ"によって殺されてしまう。
覚醒表・犠牲:祖父と父を殺され、魚に睨まれたことから、その魚と戦うための力に目覚めた。
衝動表・破壊:魚と戦う力――それは、無差別に破壊してまわるだけの、暴走であった。
神秘・達人の指導:親を亡くした彼は、師匠である武留守リリーによって育てられた。
経験表−死別:師匠は遺言を残し、彼の前から消え去った。
「自然流空手の神髄は、スシにある。スシを握れ」

ロイス
武留守リリー タイタス:自然流琉球空手の師匠であり、彼の育ての親。
一柳サヨリ 慕情○/疎外感:子供の頃に離ればなれになった、実の母。
Dロイス・伝承者(白兵):自然流琉球空手の拳の握りは、スシの握り。
自然流をマスターするため、そして母に会うために、彼はスシの世界に戻ることとなった。

さすがダブルクロス。ルールに対して違和感のない設定です。
”電光石火稲妻握り返し”をコンボとして作ることができたりと、懐の深さにわくわく。

さてさて、ダブルクロスダイス、完成しましたね。
使い方とか説明していないので、誰か使うんだろうか、的な考えも沸くんですが、そんなこと自己満足の前では無視です!
実際は、チャットでセッションする人がお友達にいるので、その人にあげたり自分で使ったりしていますけど。
では、前回の要約から。

・eval関数あいらぶゆー。
ajaxの一翼を担う関数。例えばlimechat2なら、
メッセージの入力で関数呼び出しまでできます。

・ソースは小分けに見通しよく。
どこを直せばいいのか、
何となくわかるようにしておくとなおよし。

ざっくりですね!
evalはいとおしいので、みんなも使ってみてください。
正規表現とevalが大好きな海月歩空よりのお願い!

そんな、変なお願いはいいのよ!
前回適当に進めすぎた分のフォローをするそうじゃない!(そ、そうなんですか)
さっさと、始めなさいよ!

・ダイス作成(7) 機能拡張・その2−ダブルクロスダイス作成(2)(ぱらららーん)

あんまり勢いで書いていてもあれなので、今回は、海月歩空のスクリプトの書き方というか考え方を、前回までのネタを使いながらやっていきたいと思います。
ちなみに、私が短めな文章書くときも、方向は違いますが似たような方法で書きます。
つまり、私にとってスクリプトの書き方=文章の書き方、ということに。
とてもとてもベーシックな方法なので、たぶん、他の人もやっていると思います。

というわけで、海月歩空のスクリプト作成方法、紹介!

その1。入出力をイメージする。
ダイス作成(1)とダイス作成(2)にかぶりますが、はじめに考えることは、入出力を決めるところからですね。
こういうデータがあって、こういう結果が欲しい、という考え方です。
使う側にとって、処理なんてものは、信頼できる処理が入っていれば、後はお任せです。お任せ。
なので、実は入出力をどれだけうまく人の思考にあわせられるかが、使われるシステムになるかの重要ポイント……というわけでもないところがプログラムの悲しいところ。
今回で言うと、前回仕様で書いていた入力と出力の処理を行うにはどうしよう、というところをまずイメージします。
この時点では、イメージだけで、ダイス作成(2)みたいなものを作っておきます。
テストできるように、準備をするという感じですね。

その2。最低限の処理項目を考える。
最低限、必要な処理をリストアップします。
実は、これが超重要!
というか、ダイス作成(6)で書いた、機能の仕様(大まかに)の部分が必要な処理のリストアップがそれにあたります。
機能の仕様(大まかに)
1.ダイスの面数は10面ダイス固定。
2.ダイスの個数は入力式。
3.クリティカルは入力式。
4.技能値等の達成値ボーナスは入力式。
5.ダイスの個数分、一度にダイスを振る。
6.ダイスの振った結果、一番高いダイス目を成功度として扱う。
7.ダイスを振った結果クリティカル値以下のダイスがある限り
クリティカルした個数分振り足しをする(クリティカル処理)
8.クリティカル処理が行われたときの成功度計算方法は、
ダイス目に関係なくクリティカル=10として、振り足しを行う。
9.ダイスの個数は0個を下回ることがある。
下回ったときクリティカルは絶対せず、ダイスは1個となる。
10.1回目に全てのダイスが1を出したとき、ファンブルとなる。
11.制限として、無限ループに陥る可能性があるため
、無限ループ用の制御をする。

これです。
実際にどれが入力で必要か、どれが結果として必要かーみたいなのを考えるのも、ここですね。

その3。コメントをおいていく。
ここから、プログラムを書いていくという作業に入ります。
上から、必要な処理がリストアップができたので、それを流れになおします。
本当は、ここでなんとか図を書いてみたり、いろいろするのが普通なんでしょうが、一人で書く場合とかは楽するためにそんな感じです。
いわゆる下書き! 感覚で書きます。
ちょっと、本来のソースとコメントが違う部分もありますけれど、そこはわかりやすくしたためで、本当は単語でざらっと書いていることが多いです。
関数のひな形
//ダブルクロスダイスの関数。入出力は外に任す。引数はダイスの要素の文字列

//返却に必要な項目。
//ここで出力する予定はないので、objectで複数項目返す。
//必要そうな項目
//達成値
//成功度
//表示項目
function ()
{
//判定用の値取得。○x△+□で分割。
//○=ダイスの個数
//△=クリティカル値
//□=達成値ボーナス

//分割した物を計算

//達成値ボーナスはなくてもいいので、無いときの処理。

//ダイスの個数が0以下のとき、
//クリティカル値を11にして、ダイスの個数は1個にする。

//計算結果、無限ループや
//すごい大きな処理になりそうなら終了
//どうやってエラーを返す?
//マイナス達成値? 表示だけ返す?

//ダイスロール。10面ダイスと、ダイスの個数でロール
//ロール結果を表示項目に追加

//1回目に全てのダイスが1を出したとき、ファンブル
//ファンブルを返却

//達成値判定
//ダイスロールした結果一番高い数値を成功度に

//クリティカル値以上の値をカウント
//クリティカルがでていたら、
//成功度はダイスの数値ではなく10に。

//クリティカルしている限り以下を繰り返し

//ダイスロール。10面ダイスと、
//クリティカルをカウントした結果のダイスの個数でロール。
//ロール結果を表示項目に追加

//達成値判定
//ダイスロールした結果一番高い数値を成功度に加算

//クリティカル値以上の値をカウント
//クリティカルがでていたら、
//成功度はダイスの数値ではなく10に。

//以上を繰り返し

//達成値計算。成功度+ボーナス
}

大体コメントはこんな感じでざっくりと書きます。
クエッションなんかかいていたり、ソース上ではdo〜while文にして一回で書いている所を2回で書いていたりしますが、これは実際になにをどうしようと考えながら書いていたためです。
今回は、元々他のダイスでイメージができていたのでそんなに違いはないですが、実際に関係ないコメントやら、入れ替えたり処理が消えたりすることもままあります。
プログラムを書くとき、ソースを書いてからコメントを書く人がいますが、コメントを書いてからプログラムを穴埋めしていくのが簡単かつ間違えづらいのです。
繰り返しますが、コメントは下書き!

その4。プログラムを書く。
後は、書いてください。
重複したりしたら、省略できないか考えたりするのはここです。
エラーの返却方法が決まっていなかったですが(複数の関数を使って作る場合は、エラーと結果の返し方は決めておいた方が無難)、オブジェクトを返すしそれならエラーフラグの方が楽だからとフラグ式にしました。
受け側がどう処理するかも考えていくと、大体、プログラムの大筋は人によってそこまでずれないです。
小技やらテクニック、後は知識、書き方によって、細かい部分は変わってきますけど。

こんな感じで私はプログラムを書いています。
普通にプログラムを書くときは、入力、返却、コメント、この三つを決めておくと、あとで細かい修正をするとき他に影響が少なくなるので、皆様も試してみてくださいね!
posted by 海月歩空 at 03:38| Comment(0) | TrackBack(0) | プログラム

2009年03月23日

LimeChat2のjavaScriptでダイス作成(6)

アメリカンヒーローではバットマンが大好き、海月歩空です。こんばんは。

スパイダーマはもちろんいろんな意味で好きですが、原作の方はちょっと苦手。
なんていうか、アメリカンな人がミュータントをテーマにするとごちゃっとしますよね。
ミュータントタートルズも何となく複雑だったような思い出が。

それはともかく、前回は、ダイスの機能拡張として、四則演算やらなにやらを使えるようにできました!
これで、ダイスを使用するある程度の機能がそろったわけですね。
では、前回の要約から。

・無名関数はちょっとしたときに便利。
配列ソートの並び替えあたりが一番わかりやすい例。
・try-catch文はエラー対策に。
本当はそういう使い方あまりお勧めじゃないけれど、
キャッチからのリターンで普通に処理ができるようにも。

ざっくりですね!
javascriptを書く人なら覚えておいても損はない二つのものです。
無名関数なんかはもう流行と書いてながれと読むくらいベターな技ですよ!

そんなよくある説明はいいのよ!
今回は、なんかゲーム用のダイスを作るらしいじゃないの!(え、そうなんですか?)
さあ、そのゲーム用ダイスの説明を始めなさい!

・ダイス作成(6) 機能拡張・その2−ダブルクロスダイス作成(ぱらららーん)

実は、ここ最近、ちょこちょことおじゃましているブログがありまして、そちらでダブルクロスのダイスをつくっていらっしゃいまして。
せっかくちょっかい出しているのなら、ダブルクロスのダイスを、私ならこう作ってますよーとアピールしてみようと思ったわけです。
ブログを書き始めているのはあちらの方がむちゃくちゃ早いんですが。
後出しな私。(パクリ! パクリ!)
そ、そんな、い、一応、chocoaからライムチャット2に今もまだ移行中ですが、ここに書いているのは移行時点で作ったものをブログ用に丁寧に書き直したものなので、スクリプト的にはパクリではないと!
言いたいわけです!(きわどくあらがってます)

……発表が早い者勝ちなので、ぱくりと言われればその通り!(あ、認めました)

それはともかく、私もオンラインでダブルクロスをやる身としては、ダブルクロスのダイスも作ってみよう、となるわけです。
というわけで、”ダブルクロスダイスの機能追加”を要望です。

さてさて、ダブルクロスのダイスといえば、どういう機能が必要でしょうか。
ざっと上げていきましょう。
機能の仕様(大まかに)
1.ダイスの面数は10面ダイス固定。
2.ダイスの個数は入力式。
3.クリティカルは入力式。
4.技能値等の達成値ボーナスは入力式。
5.ダイスの個数分、一度にダイスを振る。
6.ダイスの振った結果、一番高いダイス目を成功度として扱う。
7.ダイスを振った結果クリティカル値以下のダイスがある限り
クリティカルした個数分振り足しをする(クリティカル処理)
8.クリティカル処理が行われたときの成功度計算方法は、
ダイス目に関係なくクリティカル=10として、振り足しを行う。
9.ダイスの個数は0個を下回ることがある。
下回ったときクリティカルは絶対せず、ダイスは1個となる。
10.1回目に全てのダイスが1を出したとき、ファンブルとなる。
11.制限として、無限ループに陥る可能性があるため、
無限ループ用の制御をする。


はい、こんな感じです。
本当は、もうちょっと丁寧に説明しろ、っていう感じなんでしょうが、めんどう……もとい、ルールブックを買ってください! という感じで(本気で投げっぱなしです)
というか、私はプログラムを作成するときは感覚派なので説明が苦手だと言うことがわかってきたかも(大問題です)

さてさてさて、それを入出力の形にすると以下のように。
機能の仕様
1.メッセージにデータ入力
・入力の形式は、○x△+□で、
○=【ダイスの個数(実数かかっこを含む四則演算の結果)】、
△=【クリティカル値(実数かかっこを含む四則演算の結果)】、
□=【達成値(実数かかっこを四則演算を含む)】とする。
□はなくてもよい。
例:1*3+2+5x(10-3)+5+1
2.出力形式は× : ○x△+□=■ 成功度:▲ 達成値:●で、
■=【ダイスを振った結果(振り足し処理ごとに分ける)】、
▲=【成功度】、●=【達成値】とする。
例:× : 10x10+4=[.3.1.7.6.5.5.7.10.6.10.][.6.9.] 成功度:19 達成値:23

そして、内部的な仕様は、大まかな部分から抽出してください(本気でおおざっぱすぎます)

ではでは、これからイメージを高めていきましょう。
このダイス、つまるところ、クリティカル処理とファンブル処理とをうまく行えればいいというわけですよね。
それと、無限にループする可能性があるので、それの制御をする必要があります。

ファイル名:dice.js
//文字列入力処理
function event::onChannelText(prefix, channel, text)
{
//textの中に○d△があるとき、ダイス要素部分を取り出す
if (text.match(/^([0-9+*/()-]*?[1-9]\d*d[1-9]\d*[0-9+*/()-]*?)([  ].*)?$/i))
{

//通常ダイス関数呼び出し
var dice = normalDice(RegExp.$1);

//文字列出力処理
send(channel, prefix.nick + " : " + dice.words
+ " = " + dice.disp + " = " + dice.sum);
}

//ダイス要素っぽいものがあれば取り出し
if (text.match(/^([0-9()x/+*-]+)([  ].*)?$/i))
{

//通常ダイス関数呼び出し
if (dice = doubleCrossDice(RegExp.$1))
{
if (dice.errFlg)
{
//メッセージ
send(channel, prefix.nick + " : " + dice.words + "= " + dice.disp.join(""));
}
else
{
if (dice.fumbleFlg)
{
//メッセージ
send(channel, prefix.nick + " : " + dice.words
+ "= " + dice.disp + "= FUMBLE!!");
}
else
{
//メッセージ
send(channel, prefix.nick + " : " + dice.words
+ "=" + dice.disp.join("") + " 成功度:"
+ dice.succVal + " 達成値:" + dice.achiVal);
}
}
}
}
}

/******************************************************
前回分中略
******************************************************/

//ダイス個数上限(ダブルクロスダイス関数制御用グローバル変数)
var defDXDiceMaxLim = 40;

//クリティカル値下限(ダブルクロスダイス関数制御用グローバル変数)
var defDXClitMinLim = 5;

/******************************************************
ダブルクロスダイス関数

基本説明
○x△の要素を含む文字列を受け取り、○x△+□の形を抽出。
○個分10面ダイスを振り、△以下の結果をカウント、
カウントした個数分10面ダイスをふり直す行為を繰り返す。
ふり直した回数×10と、最後に振った10面ダイスの最大値を
成功度。成功度に□を足した結果を計算したものを達成値。
ダイスの振っている経過をdispに配列。
始めに振ったダイスが全て1であったときはファンブルフラグはtrue。
ダイスの個数かクリティカル値が制限をこえていたときはerrFlgはtrue。
これらの要素を含むオブジェクトを返却する。

in
diceWords:ダイス要素を含む文字列

out
ダイス結果object
[
disp:ダイス結果配列(Array)
fumbleFlg:ファンブルフラグ
errFlg:上限/下限エラーフラグ
achiVal:達成値
succVal:成功度
]
*******************************************************/
function doubleCrossDice(diceWords)
{
try
{
//返却用ダイスobject
var dice = new Object();
//ファンブルフラグ
dice.fumbleFlg = true;
//達成値
dice.achiVal = 0;
//成功度
dice.succVal = 0;
//表示項目
dice.disp = new Array();
//上限・下限エラーフラグ
dice.errFlg = false;

//子文字化
dice.words = diceWords.replace(/x/ig, "x");

//判定用の値取得。○x△+□で分割。
if (dice.words.
match(/([0-9()+*/-]+\)?)x(\d+|\([^)]+?\)|\([^(]*?\([^)]*?\)[^)]*?\))(.*)/i))
{
//数値計算
var diceCount = Math.floor(parseFloat(eval(RegExp.$1)));
var diceCritical = Math.floor(parseFloat(eval(RegExp.$2)));
var diceBonus = Math.floor(parseFloat(eval(RegExp.$3)));

//達成値上昇無し
if (!diceBonus)
{
diceBonus = 0;
}

//ダイス個数がマイナス時の処理
if (diceCount < 1)
{
diceCritical = 11;
diceCount = 1;
}

//上限値チェック
if (diceCount > defDXDiceMaxLim
|| diceCritical < defDXClitMinLim)
{
//エラー
dice.errFlg = true;
//メッセージ
dice.disp.push("ダイス個数かクリティカル値が上限を超えています。");
dice.disp.push("個数=" + diceCount+ "・個数上限="
+ defDXDiceMaxLim + "/クリティカル値="
+ diceCritical + "・クリティカル下限=" + defDXClitMinLim);

//エラーで終了
return(dice);
}

//判定
do
{
var xVal = 0;
var critFlg = false;
//ダイスロール
var diceData = diceRoll(diceCount, 10);

//ファンブルチェック
if (dice.fumbleFlg && diceData.sum != diceCount)
{
dice.fumbleFlg = false;
}

//ダイスの個数をリセット
diceCount = 0;

//ダイスループ
for (var i = 0; i < diceData.array.length && !dice.FumbleFlg; i++)
{
//達成値判定
if (diceData.array[i] > xVal)
{
xVal = diceData.array[i];
}

//クリティカル判定
if (diceCritical <= diceData.array[i])
{
xVal = 10;
critFlg = true;
++diceCount;
}
}

dice.succVal += xVal;
dice.disp.push("[." + diceData.array.join(".") + ".]");
}
while(critFlg) //クリティカルしていたら繰り返し

//達成度計算
dice.achiVal = dice.succVal + diceBonus;
}
return(dice);
}
catch(e)
{
return(null);
}
}

ざっくりと、作りましたね。
正規表現、いつもはこだわるんですが、今回はあんまりこだわらずに作っています。
ひとつは計算できないときはエラーに飛ぶから、というtry文にお任せなのと、もう一つはXと数字とかっこ位しか入力されないだろうという前提があるからです。

今回の注目として、グローバル変数で制限用の値を指定しています。
通常、関数の中のものは関数の中で処理して、グローバルを汚さないようにしましょうというのが基本(javascriptは適当にvarを使わずに作った変数はグローバル変数となってしまうのでなおさら)。
なのですが、プログラム動かしている間は固定だけれど、変えたいときにどこを変えればいいかすぐわかるようにする場合なんかは、わざとグローバルで宣言することもあります。

もっと単純に、関数同士で呼び出されたことを記憶して切り替わるスイッチとして使うこともありますけれどね!

さてさて。
そういえば、グローバル変数を取り出すときには、こうした方がいいかも、とかいっていたときがありましたよね。
そう、「スクリプトのこと」の回です。
ファイルを読み込んで、evalで再評価することによって、共通で使う関数とか定数ファイルとかを別に書いておくことができます。
というわけでその機能を含めて改めて書き直しましょう。
前回の分も含めて、三つのソースと1つの定数ファイルに分けます。
ファイル名:diceRoll.js
/******************************************************
ダイス基本関数

基本説明
ダイス個数回分、ダイス面数で指定されたダイスを振り、
ダイスの振った結果をオブジェクトとして返却する。

in
diceNum:ダイス個数(正数)
diceFace:ダイス面数(正数)

out
ダイス結果object
[
array:ダイス結果配列(Array)
sum:ダイス結果合計値
]
*******************************************************/
function diceRoll(diceNum, diceFace)
{
//返却用ダイスobject
var dice = new Object();
//返却用ダイス結果合計値
dice.sum = 0;
//返却用ダイス結果配列
dice.array = new Array();

//0以下のときには0を返す。
if (diceNum <= 0 || diceFace <= 0)
{
dice.array.push(0);
return(dice);
}

//ダイス個数分、ダイス面数で指定されたダイスを振る。
for (var i = 0; i < diceNum; i++)
{
//ダイスを振った結果をダイス結果配列に。
dice.array.push(Math.floor(Math.random() * diceFace) + 1);

//ダイスを振った結果を合計値に足していく。
dice.sum += dice.array[i];
}

//結果返却
return(dice);
}

ファイル名:normalDice.js
//文字列入力処理
function event::onChannelText(prefix, channel, text)
{
//ダイス要素っぽいものがあれば取り出し
if (text.match(/^([0-9()d./+*-]+)([  ].*)?$/i))
{

//通常ダイス関数呼び出し
if (dice = normalDice(RegExp.$1))
{

//文字列出力処理
send(channel, prefix.nick + " : " + dice.words
+ " = " + dice.disp + " = " + dice.sum);
}
}
}
/******************************************************
↓初期読み込み処理↓
******************************************************/
//ダイス関数ファイル読み込み
var fileObj = openFile("diceRoll.js");
if (fileObj) {
try
{
var fileStr = fileObj.readAll();
fileObj.close();
eval(fileStr);
}
catch(e)
{
log("diceRoll.jsファイル読み込み失敗");
}
} else {
log("diceRoll.jsファイルオープン失敗");
}
/******************************************************
↑初期読み込み処理↑
******************************************************/

/******************************************************
通常ダイス関数

基本説明
ダイスの要素を含む文字列を受け取り、○d△の形を抽出。
ダイスを振り、結果をダイス要素に置き換えたものと、
結果を計算したものをオブジェクトとして返却する。
通常の数式を入れた場合も計算をする。
in
words:入力ワード(ダイス要素を含む文字列)

out
ダイス結果object
[
disp:ダイス表示用(ダイスを振った結果で置き換えた文字列)
sum:ダイス結果合計値(計算した結果の数値)
cal:計算式(ダイスを振った後の計算過程の計算式)
diceWords:渡された入力文字列
]
*******************************************************/
function normalDice(diceWords)
{
try
{
//返却オブジェクト
var dice = new Object();

//入力文字列保存
dice.words = diceWords.replace(/d/ig, "D");

//かっこ計算処理
while (diceWords.match(/(\([^d(]+?\))/i))
{
var sum = eval(RegExp.$1);

//かっこ計算後置き換え
diceWords = diceWords.replace(/(\([^d(]+?\))/i, sum);
}

//
dice.disp = diceWords;
dice.cal = diceWords;

var diceMatch =
new RegExp(/((0|[1-9]\d*)(\.[0-9]+)?)d((0|[1-9]\d*)(\.[0-9]+)?)/i);

//ダイスの要素を取り出し(小数点を含む場合の正規表現の関係で、
//括弧対応の取り出しは1番目と4番目)
while (dice.disp.match(diceMatch))
{
//ダイス個数
var diceNum = Math.floor(parseFloat(RegExp.$1));
//ダイス面数
var diceFace = Math.floor(parseFloat(RegExp.$4));

//ダイス結果格納
var diceData = diceRoll(diceNum, diceFace);

//表示置き換え(ループしないようにDをMに一時的に置き換え)
dice.disp = dice.disp.replace(diceMatch,
"(" + diceNum + "M" + diceFace + "=[."
+ diceData.array.join(".") + ".]=" + diceData.sum + ")");

//計算式置き換え
dice.cal = dice.cal.replace(diceMatch, diceData.sum);
}

//表示置き換え
dice.disp = dice.disp.replace(/M/ig, "D");

//計算結果入れ
dice.sum = eval(dice.cal);

//結果返却
return(dice);
}
catch(e)
{
return(null);
}
}

ファイル名:doubleCrossDice.js

//文字列入力処理
function event::onChannelText(prefix, channel, text)
{
//ダイス要素っぽいものがあれば取り出し
if (text.match(/^([0-9()x/+*-]+)([  ].*)?$/i))
{

//通常ダイス関数呼び出し
if (dice = doubleCrossDice(RegExp.$1))
{
if (dice.errFlg)
{
//メッセージ
send(channel, prefix.nick + " : " + dice.words + "= " + dice.disp.join(""));
}
else
{
if (dice.fumbleFlg)
{
//メッセージ
send(channel, prefix.nick + " : " + dice.words + "= " + dice.disp + "= FUMBLE!!");
}
else
{
//メッセージ
send(channel, prefix.nick + " : " + dice.words + "=" + dice.disp.join("")
+ " 成功度:" + dice.succVal + " 達成値:" + dice.achiVal);
}
}
}
}
}

/******************************************************
↓初期読み込み処理↓
******************************************************/
//ダイス関数ファイル読み込み
var fileObj = openFile("diceRoll.js");
if (fileObj) {
try
{
var fileStr = fileObj.readAll();
fileObj.close();
eval(fileStr);
}
catch(e)
{
log("diceRoll.jsファイル読み込み失敗");
}
} else {
log("diceRoll.jsファイルオープン失敗");
}

//初期設定ファイル読み込み
var fileObj = openFile("diceStatus.txt");
if (fileObj) {
try
{
var fileStr = fileObj.readAll();
fileObj.close();
eval(fileStr);
}
catch(e)
{
log("diceStatus.txtファイル読み込み失敗");
}
} else {
log("diceStatus.txtファイルオープン失敗");
}

//ダイス個数上限宣言されているか判定
if (typeof defDXDiceMaxLim == "undefined")
{
//ダイス個数上限(ダブルクロスダイス関数制御用グローバル変数)
var defDXDiceMaxLim = 40;
}

//クリティカル値下限宣言されているか判定
if (typeof defDXDiceMaxLim == "undefined")
{
//クリティカル値下限(ダブルクロスダイス関数制御用グローバル変数)
var defDXClitMinLim = 5;
}
/******************************************************
↑初期読み込み処理↑
******************************************************/

/******************************************************
ダブルクロスダイス関数

基本説明
○x△の要素を含む文字列を受け取り、○x△+□の形を抽出。
○個分10面ダイスを振り、△以下の結果をカウント、
カウントした個数分10面ダイスをふり直す行為を繰り返す。
ふり直した回数×10と、最後に振った10面ダイスの最大値を
成功度。成功度に□を足した結果を計算したものを達成値。
ダイスの振っている経過をdispに配列。
始めに振ったダイスが全て1であったときはファンブルフラグはtrue。
ダイスの個数かクリティカル値が制限をこえていたときはerrFlgはtrue。
これらの要素を含むオブジェクトを返却する。
エラー及びマッチしない場合は、falseを返却する。

in
diceWords:ダイス要素を含む文字列

out
ダイス結果object
[
disp:ダイス結果配列(Array)
fumbleFlg:ファンブルフラグ
errFlg:上限/下限エラーフラグ
achiVal:達成値
succVal:成功度
words:渡された入力文字列
]
*******************************************************/
function doubleCrossDice(diceWords)
{
try
{
//返却用ダイスobject
var dice = new Object();
//ファンブルフラグ
dice.fumbleFlg = true;
//達成値
dice.achiVal = 0;
//成功度
dice.succVal = 0;
//表示項目
dice.disp = new Array();
//上限・下限エラーフラグ
dice.errFlg = false;

//子文字化
dice.words = diceWords.replace(/x/ig, "x");

//判定用の値取得。○x△+□で分割。
if (dice.words.match(/([0-9()+*/-]+\)?)x(\d+|\([^)]+?\)|\([^(]*?\([^)]*?\)[^)]*?\))(.*)/i))
{
//数値計算
var diceCount = Math.floor(parseFloat(eval(RegExp.$1)));
var diceCritical = Math.floor(parseFloat(eval(RegExp.$2)));
var diceBonus = Math.floor(parseFloat(eval(RegExp.$3)));

//達成値上昇無し
if (!diceBonus)
{
diceBonus = 0;
}

//ダイス個数がマイナス時の処理
if (diceCount < 1)
{
diceCritical = 11;
diceCount = 1;
}

//上限値チェック
if (diceCount > defDXDiceMaxLim
|| diceCritical < defDXClitMinLim)
{
//エラー
dice.errFlg = true;
//メッセージ
dice.disp.push("ダイス個数かクリティカル値が上限を超えています。");
dice.disp.push("個数=" + diceCount+ "・個数上限=" + defDXDiceMaxLim + "/クリティカル値="
+ diceCritical + "・クリティカル下限=" + defDXClitMinLim);

//エラーで終了
return(dice);
}

//判定
do
{
var xVal = 0;
var critFlg = false;
//ダイスロール
var diceData = diceRoll(diceCount, 10);

//ファンブルチェック
if (dice.fumbleFlg && diceData.sum != diceCount)
{
dice.fumbleFlg = false;
}

//ダイスの個数をリセット
diceCount = 0;

//ダイスループ
for (var i = 0; i < diceData.array.length && !dice.FumbleFlg; i++)
{
//達成値判定
if (diceData.array[i] > xVal)
{
xVal = diceData.array[i];
}

//クリティカル判定
if (diceCritical <= diceData.array[i])
{
xVal = 10;
critFlg = true;
++diceCount;
}
}

dice.succVal += xVal;
dice.disp.push("[." + diceData.array.join(".") + ".]");
}
while(critFlg) //クリティカルしていたら繰り返し

//達成度計算
dice.achiVal = dice.succVal + diceBonus;
}
else
{
return(false);
}
return(dice);
}
catch(e)
{
return(false);
}
}

ファイル名:diceStatus.txt
//ダイス個数上限(ダブルクロスダイス制御用)
defDXDiceMaxLim = 40;
//クリティカル値下限(ダブルクロスダイス制御用)
defDXClitMinLim = 5;


ざっくりざっくり。こんな感じです。
というか長すぎ!

ファイルの場所は、normalDice.jsとdoubleCrossDice.jsはスクリプトフォルダ、diceStatus.txtとdiceRoll.jsはスクリプトフォルダ内のfilesとなります。
openFile関数の参照場所のデフォルトがそこなので、変に設定を増やさなくていいので、そんな感じ!

こういう風に小分けにしておくと、「スクリプトのこと」で言っていたように、見通しがよくなるのとか、修正しやすくなったりとか、オンオフが機能ごとにできるようになるとか、いろいろ利点があります。
後、こういう記事ネタにするときに、後々見やすくなるというか。
なので、分割作業をこれ以上長くなる前の今したわけですね。

というわけで、今回分のソースです。
複数個なので圧縮してプレゼント。
前回と同じように、自主責任において使用してください、フリーなソースです。
使用方法とか説明するのが面倒なので、そのあたり含めてフリー。
でもおしえてっていわれれば教えなくもないわよ!
そして、ちょっぴり著作をアピール。

追伸。ソースを表示するために改行を増やすのが面倒になってきたので、縮小しています。実際のソースは、ファイルか何かで見てください。ごめんなさい!
(本当は長さもルールで決めるべきですよ)

2010.2.28追記
一部バグがあったため(半角かっこのみ等で入力されると反応してしまう)
修正いたしました。
posted by 海月歩空 at 22:22| Comment(0) | TrackBack(0) | プログラム

2009年03月14日

LimeChat2のjavaScriptでダイス作成(5)

漫画的インフレ大好き、海月歩空です。こんばんは。

前回でダイスの基礎基本が完成しました! やりましたね!
というわけで、今回からはダイスの機能拡張が始まります。
これからのパターンとしては、ある機能を追加したい→仕様に起こす(イメージを言語化)→機能開発、という流れになります。
無駄に多機能にしていく予定です。無駄、大好き!
では、前回の要約から。

・関数化は多機能化の一歩。
小さいものを組み合わせて積み木でお城を造ろう!
・見通しよくプログラム。
基本は100行も行かないように小分け小分け。

ざっくりですね!
関数化(javascriptだとクラス化も含む)なら、小さく小分けで書いていく方があとあと便利です。
同じことを繰り返し書くなら、それを関数化すること考えましょう。
2回で関数化できるか考え、更に出現すると関数化が必要と思う、位のスタンスで行けばオッケー。

そんなよくある説明はいいのよ!
今日で普通のダイスは完成なのよ!?(え、もう決まっていました?)
さあ、基本ダイスを作成しなさい!

・ダイス作成(5) 機能拡張・その1−基本ダイス作成(ぱらららーん)

さて、前回までで、基本的なダイスができたわけで、すごろくや麻雀そのほかのゲームするときには、これで十分ことたります。
ですが、さいころ振ってその結果を計算して……というような処理が必要になるなら、このままでは足りません。
というわけで、今回は、ネット上そこらにあるダイスでも対応されているものが多い”ダイスの結果に対して四則演算をしたい”という要望を追加したいと思います。
機能追加の仕様
1.メッセージへデータ入力。
・入力形式は、元の○d△に対して、
通常の四則演算(+-/*)を行えるようにする。
例:4+3d6+1-4
・かっこの計算も可とする。
例:4d6*(4+4)
・スペースで区切ることによって、
メッセージも追加できる(おまけ)。
例:3d6+3 高めでろー
2.メッセージへデータ出力。
・出力形式の、【ダイス結果の合計値】を
【ダイス結果と四則演算の計算結果】に訂正。
・出力形式に、ダイス結果の合計値を改めて追加。
×:○d△+▲=([.□.□.]=●)=■で、
入力者:【ダイスの個数】d【ダイスの面数】【四則演算等】=
([.【ダイス一個の結果】.【ダイス一個の結果】.]=
【ダイス結果の合計値】)【四則演算等】=
【ダイス結果と四則演算の合計値】(実際は1行)

はい、こんな感じで拡張します。
パワーアップ!(これでやっと他と同じレベルです)

イメージとしては、○d△を、ダイスの結果に置き換えるような仕組みにして、最後に計算させればいいわけですね。
というわけで、これを形にしてみましょう。
ファイル名:dice.js
//文字列入力処理
function event::onChannelText(prefix, channel, text)
{
var matchWords =
new RegExp(/^([0-9+*/()-]*?[1-9]\d*d[1-9]\d*[0-9+*/()-]*?)([  ].*)?$/i);
//textの中に○d△があるとき、
//ダイス要素部分を取り出す
if (text.match(matchWords))
{

//通常ダイス関数呼び出し
var dice = normalDice(RegExp.$1);

//文字列出力処理
send(channel, prefix.nick + " : " + dice.words
+ " = " + dice.disp + " = " + dice.sum);
}
}

/******************************************************
通常ダイス関数

基本説明
ダイスの要素を含む文字列を受け取り、○d△の形を抽出。
ダイスを振り、結果をダイス要素に置き換えたものと、
結果を計算したものをオブジェクトとして返却する。
in
diceWords:入力ワード(ダイス要素を含む文字列)

out
ダイス結果object
[
disp:ダイス表示用
(ダイスを振った結果で置き換えた文字列)
sum:ダイス結果合計値(計算した結果の数値)
]
*******************************************************/
function normalDice(diceWords)
{

//返却オブジェクト
var dice = new Object();

//入力文字列保存
dice.words = diceWords;

//ダイスマッチ用正規表現
var diceMatch =
new RegExp(/^[0-9+/()*-]*?([1-9]\d*)d([1-9]\d*)[0-9+/()*-]*?$/i);

if (diceWords.match(diceMatch))
{
//ダイス個数
var diceNum = parseInt(RegExp.$1);
//ダイス面数
var diceFace = parseInt(RegExp.$2);

//ダイス結果格納
var diceData = diceRoll(diceNum, diceFace);
}

//表示置き換え
dice.disp = diceWords.replace(/(\d+d\d+)/i,
"([." + diceData.array.join(".") + ".]="
+ diceData.sum + ")");

//計算式置き換え
var cal = diceWords.replace(/(\d+d\d+)/i, diceData.sum);

//計算結果入れ
dice.sum = eval(cal);

//結果返却
return(dice);
}

/******************************************************
ダイス基本関数

基本説明
ダイス個数回分、ダイス面数で指定されたダイスを振り、
ダイスの振った結果をオブジェクトとして返却する。

in
diceNum:ダイス個数(正数)
diceFace:ダイス面数(正数)

out
ダイス結果object
[
array:ダイス結果配列(Array)
sum:ダイス結果合計値
]
*******************************************************/
function diceRoll(diceNum, diceFace)
{
//返却用ダイスobject
var dice = new Object();
//返却用ダイス結果合計値
dice.sum = 0;
//返却用ダイス結果配列
dice.array = new Array();

if (diceNum <= 0 || diceFace <= 0)
{
dice.array.push(0);
return(dice);
}

//ダイス個数分、ダイス面数で指定されたダイスを振る。
for (var i = 0; i < diceNum; i++)
{
//ダイスを振った結果をダイス結果配列に。
dice.array.push(Math.floor(Math.random() * diceFace) + 1);

//ダイスを振った結果を合計値に足していく。
dice.sum += dice.array[i];
}

//結果返却
return(dice);
}

前回から変数名と関わっていますが、何となく気に入らなかったので変えちゃいました(適当すぎます)
実際は、もうちょっと名前とかをこだわっておくと、こんなことはあまりなくなります。
仕事でするならば、オブジェクトとかを返すときはもっとこだわる必要ありまくりです。
返す形さえ変えてなければ、バグ修正しても相手には迷惑かけずにすみます。要チェック。
私は適当なので、気にしない気にしない(気にしなさい)

というわけで、ダイスのロール部分と、ダイスの要素取り出し及び計算部分、テキストの入力受付部分というように分けました。
こうやってみると、同じような処理の積み重ねですね。
今回初めて入ったものは、replaceです。
一つめの引数にマッチしたものを、二つめの引数に置き換える処理です。

さてさて。
この形だと、更にひとつ機能が追加できそうですよね。
そう、ダイス要素を四則演算する機能です!
計算方法としては、(○+○)d△という方法と、○d△+○d△という方法があります。
おまけとして、ダイスの面数も計算できそうです。
機能追加の仕様
1.メッセージへデータ入力。
・(追加)入力形式は、(○+○)d△など、
ダイスの個数に四則演算(+-/*)を行えるようにする。
例:(4+3)d6+1-4
※ダイスの個数の計算結果が少数を含む場合、切り捨てとする。
※0以下になった場合は、ダイスを振った結果を0として扱う。
・(追加)入力形式は、○d△+○d△など、
ダイスの結果同士に四則計算を行えるようにする。
例:4d6+3d6

仕様として、このような形になりますね。
さて、さくっとこの部分も追加しましょう。
ファイル名:dice.js
//文字列入力処理
function event::onChannelText(prefix, channel, text)
{
//ダイス要素っぽいものがあれば取り出し
if (text.match(/^([0-9()d./+*-]+)([  ].*)?$/i))
{

//通常ダイス関数呼び出し
if (dice = normalDice(RegExp.$1))
{

//文字列出力処理
send(channel, prefix.nick + " : " + dice.words
+ " = " + dice.disp + " = " + dice.sum);
}
}
}

/******************************************************
通常ダイス関数

基本説明
ダイスの要素を含む文字列を受け取り、○d△の形を抽出。
ダイスを振り、結果をダイス要素に置き換えたものと、
結果を計算したものをオブジェクトとして返却する。
通常の数式を入れた場合も計算をする。
in
words:入力ワード(ダイス要素を含む文字列)

out
ダイス結果object
[
disp:ダイス表示用(ダイスを振った結果で置き換えた文字列)
sum:ダイス結果合計値(計算した結果の数値)
cal:計算式(ダイスを振った後の計算過程の計算式)
diceWords:渡された入力文字列
]
*******************************************************/
function normalDice(diceWords)
{
//try(今回の注目)
try

{
//返却オブジェクト
var dice = new Object();

//入力文字列保存
dice.words = diceWords.replace(/d/ig, "D");

//かっこ計算処理
while (diceWords.match(/(\([^d(]+?\))/i))
{
//かっこ計算後置き換え(今回の注目)
diceWords = diceWords.replace(/(\([^d(]+?\))/i,

(function (cal) {return eval(cal);}));

}

//置き換え用に、初期文字列を入れる
dice.disp = diceWords;
dice.cal = diceWords;

//正規表現定数化
var diceMatch =
new RegExp(/((0|[1-9]\d*)(\.[0-9]+)?)d((0|[1-9]\d*)(\.[0-9]+)?)/i);

//ダイスの要素を取り出し
//(小数点を含む場合の正規表現の関係で、
//括弧対応の取り出しは1番目と4番目)
while (dice.disp.match(diceMatch))
{
//ダイス個数(小数点を含むときは切り捨て)
var diceNum = Math.floor(parseFloat(RegExp.$1));
//ダイス面数(小数点を含むときは切り捨て)
var diceFace = Math.floor(parseFloat(RegExp.$4));

//ダイス結果格納
var diceData = diceRoll(diceNum, diceFace);

//表示置き換え
//(ループしないようにDをMに一時的に置き換え)
dice.disp = dice.disp.replace(diceMatch,
"(" + diceNum + "M" + diceFace +
"=[." + diceData.array.join(".") + ".]="
+ diceData.sum + ")");

//計算式置き換え
dice.cal = dice.cal.replace(diceMatch , diceData.sum);
}

//表示置き換え
dice.disp = dice.disp.replace(/M/ig, "D");

//計算結果入れ
dice.sum = eval(dice.cal);

//結果返却
return(dice);
}
//catch(今回の注目)
catch(e)

{
log(e);
//エラー時にはnullを返却。
return(null);
}
}

/******************************************************
ダイス基本関数

基本説明
ダイス個数回分、ダイス面数で指定されたダイスを振り、
ダイスの振った結果をオブジェクトとして返却する。

in
diceNum:ダイス個数(正数)
diceFace:ダイス面数(正数)

out
ダイス結果object
[
array:ダイス結果配列(Array)
sum:ダイス結果合計値
]
*******************************************************/
function diceRoll(diceNum, diceFace)
{
//返却用ダイスobject
var dice = new Object();
//返却用ダイス結果合計値
dice.sum = 0;
//返却用ダイス結果配列
dice.array = new Array();

//0以下のときには0を返す。
if (diceNum <= 0 || diceFace <= 0)
{
dice.array.push(0);
return(dice);
}

//ダイス個数分、ダイス面数で指定されたダイスを振る。
for (var i = 0; i < diceNum; i++)
{
//ダイスを振った結果をダイス結果配列に。
dice.array.push(Math.floor(Math.random() * diceFace) + 1);

//ダイスを振った結果を合計値に足していく。
dice.sum += dice.array[i];
}

//結果返却
return(dice);
}

いえーい、完成です! やんややんや!
というかすごい長!?
案外簡単……といいつつ、正規表現が一番たいへんだったりしました。
つまり逆説的に言えば、正規表現をマスターすれば、文字列編集もマスターしたも同然! やはー!

さて、今回の注目は2点です。
案外便利な二つのjavascriptらしい要素。
”try〜catch”と”無名関数”です!

”try〜catch”といえば、javaなどの最近のクラスを使うプログラム言語を使用していたら出てくるベーシックなものですが、try文でありえなーいってエラーがでたとき、catch文に飛ばされるという、おおざっぱにぶっちゃければ、goto文の亜種ですね(おおざっぱすぎです)
今回で言うと、文字入力処理で複数の関数を呼び出している場合、ちゃんとしないとエラーでぼこんと落ちてしまうのですが、これを助けてくれるんです。

”無名関数”は、関数を作るほどでもない、けれど、関数じゃないと処理できない、的なものを行うjavascriptらしい関数です。
そう、repleaceなんかは、マッチした要素を関数で加工することができないんですが、無名関数を引数に渡してあげることによって、加工することができるようになります。
……今回の例だと、例になっていないんですよね。

それに目をそらしつつ、基本ダイスは完成しました!
いえーい!

というわけで、今回分のダイス(dice.js)をアップしておきます。
ソースには保証がないけれどちょっぴり著作を小さく主張。
使い心地とか、バグとか教えていただければ、ありがたく思ったりします。
posted by 海月歩空 at 21:22| Comment(0) | TrackBack(0) | プログラム

2009年03月08日

LimeChat2のjavaScriptでダイス作成(4)

ランダム大好き、海月歩空です。こんばんは。

今日でダイスの基本形が完成!
いえーい!
そう、ここからダイスの多機能化開発の道が始まるわけです。
無駄に多機能、私が大好きな言葉のひとつ!
では、前回の要約から。

・入力制御は重要な処理(前回と重複)
チャット上では1行文の入力なため、この操作ができれば基本的な操作はできるようになる。
ダイスの個数とダイスの面数は取り出せるようになりました。

・macthと正規表現は文字列操作に強い関数。
はじめは目的に合う関数を探して、使い方を調べることが大切。後、正規表現は最高です!

ざっくりですね!
前回は、前々回とかぶっているので、似たようなことを言っていますが気にしない!

今回は処理をダイス作成まで行くのだわ!
そして、自作関数の形にしていくのよ! 命令だわ!(何様ですか)
さあ、処理のメインを作成しなさい!

・ダイス作成(4) 処理・関数化(ぱらららーん)

というわけで、前回ののソースを呼びましょう。
ファイル名:dice.js
//文字列入力処理
function event::onChannelText(prefix, channel, text)
{
//textの中に○d△があるとき、取り出す
if (text.match(/^(\d+)d(\d+)$/i))
{
//文字列出力処理
send(channel, prefix.nick + ":" + "ダイスの個数=" + RegExp.$1
+ "/ダイスの面数=" + RegExp.$2 );
}
}

必要なものを取り出す処理までができていますね。
さあ、後はさいころの機能を追加するだけです。
さいころとは、”ランダム”に、”1〜ダイスの面数”までの値を返すものです。
つまり、ランダム関数(Math.random())を使う、ということですね。

ただ、機能として、ランダム関数は0以上1未満の浮動小数で結果を返すので、ちょっと計算が必要ですね。
いわゆる、かけて端数切り下げて足して、という、ランダム関数で実数を取り出すベタなものです。
さあ、組み込んでダイスを作成してみましょう。
ファイル名:dice.js
//文字列入力処理
function event::onChannelText(prefix, channel, text)
{
//textの中に○d△があるとき、取り出す
if (text.match(/^([1-9]\d*)d([1-9]\d*)$/i))
{
//ダイス個数
var diceNum = parseInt(RegExp.$1);
//ダイス面数
var diceFace = parseInt(RegExp.$2);
//ダイス結果配列
var diceAns = new Array();
//ダイスの合計値
var diceSum = 0;

//ダイス個数分、ダイス面数で指定されたダイスを振る。
for (var i = 0; i < diceNum; i++)
{
//ダイスを振った結果をダイス結果配列に。
diceAns.push(Math.floor(Math.random() * diceFace) + 1);

//ダイスを振った結果を合計値に足していく。
diceSum += diceAns[i];
}

//文字列出力処理
send(channel, prefix.nick + ":" + diceNum + "D" + diceFace
+ "=[." + diceAns.join(".") + ".]=" + diceSum);

}
}

シンプルですが、これでダイスが完成しました!
いきなり、かなり多めに処理が入ってきてしまいましたね。
ついてきてますか? ついてこれなくてもついてきてください!(横暴です)

機能的には、コメントを見ればわかるようになっているはずですが、変更・追加があったところを上から解説していきましょう。

まずは、ダイスの正規表現が変更になりました。
うっかり0個でも0面でも振れるようになっていたのと、0123みたいな数でもオッケーになっていたので(うっかりすぎです)、正数を表す正規表現に直しました!
ちなみに、0と正数を表す正規表現は/0|[1-9]\d*/となり、小数点も含めば、/0|[1-9]\d*(.\d+)?/という感じ……だったと思います(え、適当?)
メジャーどころの正規表現は、どこかにあると思いますので、調べてみてください! というか、私は”好き”なだけでできる子じゃないので!

次は、変数宣言中に、parseIntですね。実数化する関数です。マッチで取り出したのは、一応文字列なので(javascriptは読替で勝手に数値化することもありますが)関数で数値化させます。

そして、forループで、ダイス個数分繰り返し、その結果を結果の配列と結果の合計値に入れていきます。
ランダム関数を含む処理の結果を、pushで配列に入れて、そこから取り出して結果に足していきます。

最後は、結果表示。結果配列の出力処理ですね。

これで、基本は完成ですね!
やりましたー!(ぱちぱちぱち)
たった30行以下で基礎基本がかけるものでした!

さて、これで基本ができたのですが、これからいろいろと拡張していく場合、どんどんとプログラムのソースが伸びていってしまいますね。
見づらくなってしまいます。
こういうものは、細かくばらばらに、1機能をひとまとまりにするのがマナーでルールです。
すると、後で拡張するとき便利です。

というわけで、関数化させましょう。
機能として更に拡張する予定なので、複数結果を返却できるようオブジェクトで返す関数です。
ファイル名:dice.js
//文字列入力処理
function event::onChannelText(prefix, channel, text)
{
//textの中に○d△があるとき、取り出す
if (text.match(/^([1-9]\d*)d([1-9]\d*)$/i))
{
//ダイス個数
var diceNum = parseInt(RegExp.$1);
//ダイス面数
var diceFace = parseInt(RegExp.$2);

//ダイス基本関数呼び出し
var dice = basicDice(diceNum, diceFace);

//文字列出力処理
send(channel, prefix.nick + ":" + diceNum + "D" + diceFace
+ "=[." + dice.diceAns.join(".") + ".]=" + dice.diceSum);

}
}


/******************************************************
ダイス基本関数

基本説明
ダイス個数回分、ダイス面数で指定されたダイスを振り、
ダイスの振った結果をオブジェクトとして返却する。

in
diceNum:ダイス個数(正数)
diceFace:ダイス面数(正数)

out
ダイス結果object
[
diceAns:ダイス結果配列(Array)
diceSum:ダイス結果合計値
]
*******************************************************/
function basicDice(diceNum, diceFace)
{
//返却用ダイスobject
var dice = new Object();
//返却用ダイス結果合計値
dice.diceSum = 0;
//返却用ダイス結果配列
dice.diceAns = new Array();

//ダイス個数分、ダイス面数で指定されたダイスを振る。
for (var i = 0; i < diceNum; i++)
{
//ダイスを振った結果をダイス結果配列に。
dice.diceAns.push(Math.floor(Math.random() * diceFace) + 1);

//ダイスを振った結果を合計値に足していく。
dice.diceSum += dice.diceAns[i];
}

//結果返却
return(dice);
}


そのまんま、関数化しただけですね。
今回は入力処理側に、文字処理を預けてしまっています。
これは人の好みとか開発のルールとか、そういうものです。今はそこ以外入力処理がないことと、入力→入力制御処理(→ダイス処理”関数部分”)→出力、というのが見通しがいい感じなのでそのままにしてあります。
でも、この後拡張していく上で、どうやっていくか、を考えつついきましょう。

ちなみに補足というか蛇足ですが、関数化させると見通しがよくなります。しかし、関数化にともなって汎用性を高めると逆に処理が遅くなってしまうときもあるので、べたーと書いている人が悪いというわけではありません。
ようは、目的がわかっていて、そのためにどうするか、を考えて答えられればよいのですね。
ただ、関数化するときは、関数の説明はしっかり書いておくこと。これ重要! 超重要!

さて、ダイスとしてはこれで完成で、これだけでダイスを使用するゲームもできるのですが、便利ではありません。
というわけで、次回から、拡張していきましょうっ!
わくわく、拡張でーす!
posted by 海月歩空 at 20:26| Comment(0) | TrackBack(0) | プログラム

2009年03月06日

LimeChat2のjavaScriptでダイス作成(3)

スクリプト大好き、海月歩空です。こんばんは。

前回も、前々回も、注目だのなんだの言いつつ、説明すらしていないという体たらく。
し、調べて学ぶ猶予を作っているだけですよ!
勢いで書いている行き当たりばったりとか言わないで!
というわけで、前回のおさらい(要約)から。

・プログラムとは、入力・処理・出力の三大要素でできている。
元々の入出力イメージの仕様ができていれば外枠はできる。後は処理だけ。
・入力後最初に行う処理の入力制御。
文字の入力制御は、文字列操作・判定は正規表現を使うと便利。
こちらの意味が何となくわかるようになるとエクセレント!

ざっくりですね!
というか、毎回、この要約を見てから、前回の話のソースを見ればいいのかも。
そうすると、何となくわかるかな……はっ、すごく問題?

そんなこと言っている暇はないわ!
勢いで書いているうちに、最後まで行くのだわ!
(どなたですか?)
今回は入力制御ですわっ!

・ダイス作成(3) 入力制御(ぱらららーん)

というわけで、前回の入力制御後のソースを呼びましょう。
ファイル名:dice.js
//文字列入力処理
function event::onChannelText(prefix, channel, text)
{
//textの中に○d△があるか
if (text.match(/^\d+d\d+$/i))
{
//文字列出力処理
send(channel, prefix.nick + ":" + text);
}
}

ここから、必要なものを取り出す機能を作ります。
さて、やっと前回やる予定だった、ダイスとはなんぞやを始めたいと思います。

Q.ダイスとはなんぞや?
A.さいころです。

お終い……あいたっ! ものなげないでっ!?
ということはジョークとして、さいころといえば、一般の方はわかりやすいですね。
メジャーなのは、1〜6の目がある6面の正方形です。
実際には、いろいろな面数のあるさいころがあるわけで、今回の仕様がダイスの個数とダイスの面数を入力するようになっていたわけですね。
……という説明は、こういう機能を作ろうとする人には、何となく嘘くさい教育番組的な感じがしているかも。

それはともかく。

今回必要なのは、○つまりダイスの個数と、△いわゆるダイスの面数ですね。
それを取り出せるように変更しましょう。
ファイル名:dice.js
//文字列入力処理
function event::onChannelText(prefix, channel, text)
{
//textの中に○d△があるとき、取り出す(今回の注目A)
if (text.match(/^(\d+)d(\d+)$/i))

{
//文字列出力処理(今回の注目B)
send(channel, prefix.nick + ":" + "ダイスの個数=" + RegExp.$1
+ "/ダイスの面数=" + RegExp.$2 );

}
}

ほんのちょっと変わっただけですね(手抜き手抜き!)
手抜きとか言わないの!

変わったところと言えば、今回の注目Aのmatchの丸かっこが増えたことと、今回の注目BのRegExp.$1、RegExp.$2ですね。
これは、お察しの通り関係あります。
matchで丸かっこと対応する部分を、何番目、という感じで取り出す技なのです!(ばばーん)
そこで、○と△が取り出せる様になりました。やったね、べいべ!

ちなみに、別の方法で書くパターンもあります。
ファイル名:dice.js
//文字列入力処理
function event::onChannelText(prefix, channel, text)
{
//textの中に○d△があるとき、取り出す(別パターン)
var matchData;
if (matchData = text.match(/^(\d+)d(\d+)$/i))

{
//文字列出力処理
send(channel, prefix.nick + ":" + "ダイスの個数=" + matchData[1]
+ "/ダイスの面数=" + matchData[2] );

}
}

どちらも同じです。ただ、うっかりいろいろやったあとに取りだそうとすると、RegExpの方では取り出せなくなってしまうかもしれません。
例えば、macth使っちゃうと上書きされてダメダメ。

ただ、個人的好みでは、RegExpなので、このまま行きます。
次回で、基本のダイス完成!
……基本? ということはまた別の方向が……?
お楽しみに!
posted by 海月歩空 at 02:14| Comment(0) | TrackBack(0) | プログラム

2009年03月01日

LimeChat2のjavaScriptでダイス作成(2)

大無反応連載中、海月歩空です。こんばんは。

すごい地味なことに対して、無駄に長く書いてしまっているため、読み取りづらいですが、ついてきていますでしょうか。
私は無理です(ならしないの!)
というわけで、前回のおさらい(要約)から。

・目的のプログラムを作るためには、なにが必要か。
どう動いて欲しいかの仕様が必要。
・そこで今回のダイススクリプトの仕様。

1.メッセージへデータ入力
・入力の形式は、○d△で、【ダイスの個数】d【ダイスの面数】とする。
2.メッセージにデータ出力
・出力の形式は、×:○d△=[.□.□.]=●で、入力者:【ダイスの個数】d【ダイスの面数】=[.【ダイス一個の結果】.【ダイス一個の結果】.]=【ダイス結果の合計値】とする。

ざっくりですね!
本当はココで、ダイスとはなんぞやとか始めないといけないんですが、出だしはプログラムの三大要素の入力、出力から行った方がわかりやすいのでこれで十分です。

ところで、このダイススクリプトのお話、誰に向けて話しているんでしょうか。
最低限、プログラムを書いたことがある人(JavaScriptをさわったことがある人)で、一番最初のハローワールド的なところは、LimeChat2のヘルプを見ている人、というかなりの隙間産業な方々です。
……それって、何人?(笑)

それはともかく。
ダイススクリプトを一気に作れる、なんて思わない方がいいわ!
プログラムは、試行錯誤の連続なのよ!(どなた様?)
というわけで、まず基本、ハローワールド的な状態のものを作りましょう。

・ダイス作成(2) 入出力の処理を書く(ぱらららーん)

つまり、ひな形ですね。これから拡張していきます。
ファイル名:dice.js
//文字列入力処理
function event::onChannelText(prefix, channel, text)
{
//文字列出力処理
send(channel, prefix.nick + ":" + text);
}

これを使用すれば、あなたの発言を返してくれるスクリプトができたと思います。
event::onChannelTextってなんだろう的な話は、やっぱりLimeChat2のヘルプを見て確認してください(投げっぱなし)
これで基本の基本、入出力ができたわけです。
やっほう!

……早すぎるかも。

というわけで、正しい入力だけ反応するようにしましょう。
入力→処理→出力、という形にする第一歩、正しい入力の判断です。
今回は、○d△の○と△は正数、そして、そういう文字のみで入力されたメッセージのみ返却、という形にします。
ファイル名:dice.js
//文字列入力処理
function event::onChannelText(prefix, channel, text)
{
//textの中に○d△があるか(今回の注目)
if (text.match(/^\d+d\d+$/i))
{

//文字列出力処理
send(channel, prefix.nick + ":" + text);
}
}

これで、3d8とか、2d6とか入力したときのみ、反応するようになったはずです。
これで、イメージ通りの入力制御ができるようになりましたね。

さて、今回の注目。
matchについてのお話。
matchは、正規表現を使って、その文字列があっているかどうかの結果を返します。
チャットは文字列加工が基本なので、この機能はとても重要!
この後もばんばん出てくるので、覚えておいてください。
正規表現覚えると入力制御がすごく便利!
日常じゃあまり使わないですけれど、便利!

ちなみに、私は正規表現がとても大好きですが、あんまり使い道がないです。
残念。
posted by 海月歩空 at 22:25| Comment(0) | TrackBack(0) | プログラム

LimeChat2のjavaScriptでダイス作成(1)

今回から大好きプログラムのコーナーを始める海月歩空です。こんばんは。

日記のリハビリを含めて、説明文章を書くことであたまをぐにぐにーと動かすために、はじめちゃいます!
いえ、最近妄想はかなりしているんですが、ここ最近の妄想を日記に書くと怪しいというか、ちょっとやばめなので封印しないといけないので、妄想ネタがないんです。
ネタがないのは、死んでいるのと同じよねー……。

というわけで、話のネタというかタネはたっぷりあるけれど、誰も求められていないLimeChat2のJavaScriptでダイス作成コーナーを始めたいと思います!

LimeChat2とはなんじゃらほい、みたいな人は速攻でおいていくのでそのつもりで(ひどい)
グーグル先生に聞きなさい!(スパルタ先生だ)

プログラム作成のお勉強といえば、”ハローワールド”に続いて行うのは、ランダムに結果を返すダイス(さいころ)プログラムです。
なぜなら、プログラムの三大要素、”入力”・”処理”・”出力”があって、更に処理の要素といえば、”判断”、”反復”、”計算”、この三つ。
これをまんべんなく組み込めて、バグが入り込む要素が多分にあり、プログラムの楽しさと難しさがえられるもの、これがダイスプログラムなのです!(私の持論)
というわけで、そんなダイスを作りたいと思います。

さて、プログラムを作る場合、なにを一番最初に始めますか?
あ、LimeChat2を用意するとか、そういうベタなことはおいといてくださいね!
ハイ、そこの人! 答えて!
まずは書いてみる? それも正しいです。
殺してでも奪い取る? 手に入れたものが正義ですからね。
ただ、ちょっと問題があります。法に触れるとかそういう感じの。
そう、一番の正解は妄想をする! うまく動いている姿を想像するです!
……ごほん、もとい、”仕様を決める”ですね。

・ダイス作成(1) 仕様を決める(ぱらららーん)

ものを作るときには、仕様を決めないといけません。
これ基本。要チェックや!(古い)
例えば、肉じゃがを作ろう、と思って料理の準備をします。
なれている人なら、準備しながら作ることができるかもしれません。
ただ、普通は料理のレシピを準備しないといけません。
レシピ=仕様なのです!(ばばーん)

というわけで、ダイススクリプトの仕様を決めましょう。
今回は、メジャーなダイスの仕様をいくつか引き継いで、以下のようにします。
正しい書き方わからないので、イメージだけ……というか通常個人で作る場合はイメージで十分ですね。
・ダイス仕様(基本のダイス)

1.メッセージへデータ入力
・入力の形式は、○d△で、【ダイスの個数】d【ダイスの面数】とする。
2.メッセージにデータ出力
・出力の形式は、×:○d△=[.□.□.]=●で、入力者:【ダイスの個数】d【ダイスの面数】=[.【ダイス一個の結果】.【ダイス一個の結果】.]=【ダイス結果の合計値】とする。

これは、基本の基本部分なので、拡張する機能とかもある予定……というよりも、実際はもうできているわけですが。

というわけで、おおざっぱにイメージができたところで、次は作成してみましょう。
posted by 海月歩空 at 15:15| Comment(0) | TrackBack(0) | プログラム

2009年02月23日

スクリプトのこと

書く書くと何度か言ってかいていない、そんな海月歩空です。こんばんは。

他の方のスクリプトを書いているブログを見て、コメント書いていたら、自分のところでなにもしていなかったことを思い出したわけです。
なので、LimeChat2のJavaScriptで、使える小技をひとつ。
(普通のJavaScriptでも使えるのですが、便利さの問題でLimeChat2向け)

LimeChat2では、”スクリプトの設定”で有効/無効が選べます。
つまり、機能ごとにファイルを区分けすることで、必要なものだけオンにすることができるのです。
これは便利!

しかし、ファイルごとを飛び越えて関数呼び出しとかはできません。
そのため、共通関数とか定数ファイルとかを別途作ることができないわけです。
これは不便!

というわけで、今回は共通関数とか定数ファイルとかを外から呼び出す方法です。


外部読み出し例
//読み込み処理
//外部ファイルオープン
var fileObj = openFile("共通関数の入ったjsファイル");
//読み込めた
if (fileObj)
{
try
{
//文字列読込
var fileStr = fileObj.readAll();
//ファイル閉じる
fileObj.close();
//関数読込(今回の注目点)
eval(fileStr);

//例えばココで読み込んだファイルからオブジェクト生成やらなにやら

//ロード成功
log("ロード成功");
}
catch(e)
{
//ファイルロードor関数読込失敗など。
//読み込む予定のファイルの関数の置き換えなどごちゃごちゃ
log("ロード失敗");
}
}
else
{
//読み込めない
//ファイルロードor関数読込失敗など。
//読み込む予定のファイルの関数の置き換えなどごちゃごちゃ
log("ファイルのっとふぁうんど");
}

(少し修正。onloadで囲んでしまうと、その中でしかできないため)
すごく単純に、”文字列を読み込んで、それをeval関数で再評価”という、よく使われる技法なんですけれどね。
ファイル間同士の行き来ができないという制限があるところでは、使い勝手がいいわけです。
というわけで、上の例をアレンジして使えば、他のファイルにある関数や定数は呼び出せるようになります。
後は、ファイル名を関数名にして、関数型プログラミング的な使用方法をするもよし。
設定ファイルを単純に取り込むのもよし。
フリーのJavaScriptファイルを取り込む形式にしておいて、取り込めば機能アップできるように編集するもよし。

eval関数が嫌いな人も多いと思いますけれど、私はスクリプトといえばevalなのです。
スクリプト好きとして愛する関数。
このevalを使ってみてください。

……というわけで、私に変な発言やらなにやらを期待しているひとは、すみません。
プログラムやらスクリプトのときはまじめな私なのです!
大好きだぁぁぁ!
posted by 海月歩空 at 00:26| Comment(0) | TrackBack(0) | プログラム