プログラムの方はというと, 現在基本グラフィックシステム(見た目の部分)はできたものの本題の占いの方はまだ四柱しか出してません. 基本的に四柱推命は表引きの塊なのでプログラマには非常に単調で飽きやすい作業の連続です. じつはそれが進んでいない理由だったりする. (^^;
とりあえずこれから, プログラム経験の無い1年生を中心にテキストをばらまいて打ち込んでもらい, プログラム経験のある人にはひたすら表をバイナリにしてもらえばほぼ完成してしまうんですね. でもそれじゃつまらないのでせめて24節気の日付くらい軌道計算しようということで, 現在Naoさんに「去年の星占いの太陽の軌道計算をちょうだい」って言ってるところです. 多分私はそっちの処理と表示ルーチンしか書かないでしょう. あとは入力チームの能率次第で完成日時が決まります.
あとデータがあがって暇があれば企画書のホラ(大風呂敷)の1つであるグラフィックの方も手掛けようかな. でも四柱にグラフィックなんて殆ど無いしなあ.
では. あとはデータがいつまでに出来るか=実際のコーディングにいつ入れるかが問題でしょう. (他力本願にして責任を逃れてると言う話あり)
具体的に何をするのかというと, 母音の認識がどのようにして行われるかを示すものです. 音声をサンプリングしてFFTにかけて各周波数成分にばらしてスペクトルを得たのち, ピークの周波数(フォルマント)の位置を調べれば, どの母音が発音されたのか判定することができます.
ただ, 実際に音声の波形をFFTにかけてみると, きれいにフォルマントが現れることは意外と少なく, しかも発音されてから音が消えるまでのあいだにスペクトルが大きく変化するためで, 認識させるのは難しそうです. しかし, 音声のスペクトルから母音の認識に至る過程がどのようなものであるか示すことはできるでしょう.
子音のほうは母音に比べてかなり難しいので, 認識させることは全く考えていませんが, 子音カットの実験というものをやってみたいと思っています. 子音カットの実験というのはどういうものかというと, 例えば, 「ぞうさん」という音の先頭部分を少しずつカットしていくと, 「どうさん」と聞こえるようになります. 「z」の音が「d」の音に変化するわけです. 先頭部分をカットすると他の子音に聞こえるという例は他にもあり, それらを実際に体験できるようにするつもりです.
▼ フォルマントの例(この図は, 「ア」の音のスペクトル)
株式会社ハイパーウェア 東京都文京区本郷 5-25-13 電話 03-3812-7020 |
▼ 画面写真1 ▼ 画面写真2
僕が MS-Windows 用や X Window System 用の平安京エイリアンを作りはじめたのはもともとは, TSG の WWW のホームページがさびしいので何かホームページに載せるような情報はありませんか, と呼びかけたところから始まったのです. そこで平安京エイリアンの MS-Windows 用やら, X Window System 用のものが ftp できたらいいだろうな, という話しが出て なんとなく僕が作ることになってしまったのです. だから, 完成すれば WWW を通じて配布される予定です (1995 年度末までは多分).
ただ, 僕が平安京エイリアンをよく知らないこともあって色々問題がでてきたわけです. (括弧の中は引用元の人ですが, 引用には多少手を加えてあります)
○ ○人○ 人:検非違使 ○ ○:穴
○Θ←この向きに入った ↑ すかさずここに掘る
検非違使の速度 | エイリアンの速度 | |
---|---|---|
低速 中速 高速 |
1 1 1 |
1 1.5 2 |
ftp://ftp.microsoft.com/developr/drg/WinG
の下にあるんですが, 840K ありますねえ. (上野)
また, 「平安京にエイリアンゲーム version 0.35」などというものがあるのが見つかったりもしました. (石田) 次はそのドキュメントの冒頭部分です. (^^;
『平安京にエイリアンゲーム』とは, あの昔懐かしい平安京エイリアンのパクリです!平安京状に作られた迷路の中を貴方の分身である検??????(忘れた!!)が動き回り, 通路に落し穴を掘ってエイリアンを埋め殺していく, 皆様御存じの懐かしいゲームと言えばゲームファンには説明は不要の事でしょう. 当時, 東大生が作ったと自慢していたのを記憶してますが, 真偽の程は定かではありません. おそらくは, 硬物で通っている(当時の)東大生だって, こんなに柔軟な発想が出来るんだぞ, と言いたかったのでしょうか? 一説には京大生が作ったとも言われてましたが, なるほど「平安京」から来ている噂ですね(笑). 確か電気音響 が開発したと思いますが…… この辺の詳しい事情を覚えてらっしゃる方は御一報下されば幸いです. |
今度は「東大生が作ったと自慢していたのを記憶してますが, 真偽の程は定かではありません」などと言われないようにしなければなりません.
当時は色々な掘り方が研究されたようです.
○ ┌───┐ ■■■ ■■■ │御徒町│ ■■■ ■■■ └───┘ ■■■ ■■■ 山 ○ 人 ○ 手 ■■■ ■■■ 線 ■■■ ■■■ ┌────┐ ┌───┐ ┌───┐ ■■■ ■■■ │お茶の水│総武線│秋葉原│総武線│浅草橋│ ○ └────┘ └───┘ └───┘ 山 手 線 ┌───┐ │ 神田 │ └───┘
# 中央線の立場がないですが(^^;) (万世橋駅がなくてよかった?!)(綾塚)
■■■○■■■ ■■■人■■■ ■■■○■■■
○ ■■■ ■■■ ■■■人■■■ ■■■ ■■■ ○
(1) 下図のように掘る (2) エイリアンが落ちる (3) 交差点に掘る ■■ ■■■■ ■■ ■■ ■■■■ ■■ ■■ ■■■■ ■■ ■■○■■■■○■■ ■■○■■■■○■■ ■■○■■■■○■■ ○ ○ →Θ ○ Θ○人 ○ ■■ ■■■■ ■■ ■■ ■■■■ ■■ ■■ ■■■■ ■■ ■■ ■■■■ ■■ ■■ ■■■■ ■■ ■■ ■■■■ ■■ ■■ ■■■■ ■■ ■■ ■■■■ ■■ ■■ ■■■■ ■■ ■■ ■■■■ ■■ ■■ ■■■■ ■■ ■■ ■■■■ ■■ ○ 人 ○ ○ 人 ○ ○ ○ ■■○■■■■○■■ ■■○■■■■○■■ ■■○■■■■○■■ (4) エイリアン這いだす (5) 隣の穴に落ちる (6) すぐに埋めて300点! ■■ ■■■■ ■■ ■■ ■■■■ ■■ ■■ ■■■■ ■■ ■■○■■■■○■■ ■■○■■■■○■■ ■■○■■■■○■■ @○人 ○ →Θ人 ○ 300 ○ ■■ ■■■■ ■■ ■■ ■■■■ ■■ ■■ ■■■■ ■■ ■■ ■■■■ ■■ ■■ ■■■■ ■■ ■■ ■■■■ ■■ ■■ ■■■■ ■■ ■■ ■■■■ ■■ ■■ ■■■■ ■■ ■■ ■■■■ ■■ ■■ ■■■■ ■■ ■■ ■■■■ ■■ ○ ○ ○ ○ ○ ○ ■■○■■■■○■■ ■■○■■■■○■■ ■■○■■■■○■■
穴に落ちたエイリアンは, それまでの進行方向と同じ方向に這いだす習性を利用しています. また, 隠居掘りと異なり万一埋めるのに失敗しても自分が穴とエイリアン挟まれてしまうことがないのが強みです.
■■■○■■■ ■■■○■■■ ■■■○■■■ ○○○人○○○ ■■■○■■■ ■■■○■■■ ■■■○■■■メリットを思い出せない.... 他のエイリアンに助けに来られても 1 回なら平気というのだったかもしれません. 2 個同時に落ちると, 大あわてでどっちをうめにかかります.
表題の件につき, 平安京エイリアン作成グループ(以下, 甲)は, (株)工学社(以下, 丙)に対し, 貴ネット「ポケット通信Ver.3」における, 【芸夢職人】(以下, 乙)氏作「平安君」の削除を求めるものである. 「平安京エイリアンゲーム」は, 既にテーブルゲームとして一企業に, 甲の著作者人格権を留保したままであるが, 版権等を譲り渡しているため, 乙は, 商標権侵害, 不正競争防止法抵触の恐れがあり, また名称を「平安君」「平安京エイ○アン」とすることは甲の著作者人格権を大きく損ねるものであり, 大変遺憾と考える. また, これを知りつつも放置した丙も法的責任を免れない. 甲は, 乙ならびに丙に対し, 速やかなる削除を求め, 乙は甲に対して損なわれた著作者人格権の回復に努力することを求める. 理由なく処置が遅滞した場合は, 乙, 丙に対し不本意ではあるが, これ以上の著作権侵害を防止するため「ポケット通信Ver.3」の運営差止めの仮処分などの断固とした対応を取ることにする. なお, 「平安京エイリアンゲーム」は, 当方は既に版権を譲渡しているため, 移植などの許可は一切受け付けない. 以上 |
工学社のPocket通信Ver.3の;GUESTボードに書き込まれていたものです.
結局, この問題は一時
ご連絡いただき, 誠に恐縮しております. 指摘されたプログラムは早急に仮削除いたします. プログラム作成者と話し合い検討後, 正式な対処を決定させていただきます. (SysOp/F.) |
となりましたが,
先程, 件の BBS のシスオペに電話しました. 事情を話したところ, 私が我々の立場を説明した文章を作ってシスオペへ送り, 彼が GUEST ボードに載せるという様にしたいとの事でしたので, 以下のような文を送りました. また, ポケコンゲーム「平安君」についての資料を送ってくれるそうです.
こんにちは, 河上と申します. GUEST ボードに「東京大学 元TSG 平安京エイリアンゲーム作成 グループ」という名前で“「平安京エイリアンゲーム」削除依頼”という書き込みがされている事を知り, たいへん驚いています. 「東京大学 元TSG 平安京エイリアンゲーム作成 グループ」という団体がもし存在するとすれば, それは私を含む数名の事であり, 私達はこの書き込みにまったく覚えがないからです. ここ数日間, 仲間内でいろいろ調査しましたが, 平安京エイリアンの開発メンバーの中にこの書き込みをした者はいません. したがって, この書き込みは正統なものではありません. 以後の処置はすべて SYSOP にお任せします. |
ということになり(河上)... それからどうなったのかな?(忘れてしまった(^^;)
また, この記事を書くにあったって様々な人からのメールを引用させてもらいました. 引用してまずかったところがあれば御指摘ください. m(__)m
平安京エイリアン |
《遊び方》 ◇ 1人でも 2人でも遊べます。 ◇ 1人用………¥100(硬貨)……… 1ゲーム 2人用………¥200 ……… 1ゲーム ◇ 1人用・ 2人用どちらかのボタンを押して下さい。 [2人用の場合] ロータリースイッチで PART I 、 PART II の選別をした上でもう一度 2人用ボタンを 押して下さい。 PART I …交互にプレーをして下さい。 PART II… 2人で同時にプレーをして下さい。 ◇穴掘りボタンで穴を掘り、落ちたエイリアンが抜け 出す前に穴埋めボタンで埋めて下さい。又、検非違 使(人間)はエイリアンの攻撃から逃げる為にレバー 操作で左右上下に進めます。 ◇ 1パターンに 1回ボーナス得点 1,000点が得られる チャンスがあります。 ◇5,000点以上得点をすると検非違使が 1人増えます。 ◇検非違使が全滅するとゲームオーバーです。 ◇最高得点者は名前を記入できます。 |
この製品(平安京エイリアン)は,電気音響(株), 交亜産業(株),(株)ギャロップとの共同企画によ る開発ゲームです。無許諾の複製は厳禁します。 |
リスト1
10 A=3+5
20 PRINT A
30 END
御存知BASIC のプログラムリストです。簡単過ぎるにも程がある……。
では、このプログラムの動作を順に追ってみましょう。
#石を投げないでくださいね。
10行目で、"3+5" の計算を行います。値は当然8です。その値が変数Aに格納されます。
20行目で、変数Aの内容(値8)を取り出し、画面に表示します。
30行目で終了です。
ここで注目して欲しいのは、いつ計算が行われたか、ということです。実際に計算結果の値が必要になるのは20行目のPRINT 文ですが、計算自体はそれより前、10行目の時点で行われています。これが「値が計算可能になった時点で計算を行う」、eager な評価です。
そんなの当たり前じゃん、と思われるかもしれません。実にその通りで、普通に我々が使ってるプログラム言語は全てeager な評価を行います。
ではこれを、lazyな評価を行う戦略で実行するとどうなるか見てみましょう。10行目では式"3+5" が現れます。しかし式の値はまだ必要ないので、計算は行いません。変数Aには、「この変数の値が本当に必要になったときには、式"3+5"の値を計算し、その値を使いなさい」という内容が格納されます。
20行目のPRINT 文の実行のために、変数Aの値を取り出します。この時点で変数Aには式"3+5" を計算してそれを使えという内容が入っていますから、ここで実際に"3+5" の計算を行い、その値8を画面に表示します。
このように lazy な評価では、「値が必要になったときにはどういう式の計算を行えば良いか」という情報を、一階のデータ(言語が直接の処理対象として扱うデータ)としてあちこち引っ張り回すことになります。
言語Mは関数型言語で、Schemeとほぼ同等の意味を持ちます。
データとしては、算術型やboolean などの一般的な型の他、リストとλ式を扱います。リスト操作の命令として、 cons, car, cdr が準備されています。λ式に関しては後で取り上げます。
以下に簡単な例を示します。見ただけで理解できることでしょう。
リスト2
defun factorial(n) =
if n=1 then
1 // termination
else
n * factorial(n-1); // recursion
実行例
……てな感じです。プロンプト">" から始まる行が、人間が入力した行ですね。
リスト3
defun add _2list(a,b) =
if null?(a) or null?(b) then
[] // empty list
else
cons( car(a)+car(b),add _2list(cdr(a),cdr(b)) );
実行例
リスト操作命令 cons, car, cdr の意味は大体分かりますよね?
car(cons(A,B))==A, cdr(cons(A,B))==B, [A,B,C]==cons(A,cons(B,cons(C,[])))とかいう関係になってます。
さて、言語Mが大ざっぱにわかったところで、言語Mに lazy な評価を導入してみましょう。(元々の言語Mはやはりeager な評価を行います。)
ここでは、どの式を lazy に評価するかをユーザーが明示するために、delay,force の2命令を導入することにします。delay の後に書かれた式は、force が来るまで評価を遅延されます。
実行例
変数aには、普通に値8が格納されています。これに対し変数bには、「この変数の値が本当に必要になったときには、式"3+5" の値を計算し、その値を使いなさい」という内容が格納されます。force 命令によって、言語Mに「値が本当に必要である」ということを知らせます。force 命令を実行する時点で始めて、式"3+5" の計算が行われます。ついでに、delay されてないデータをforce してもよいことにしましょう。
delay された値は普通のデータとして扱うことができます。当然、関数の戻り値にも使えます。
実際に関数の戻り値としてdelay された値を使うような例を見てみましょう。
リスト4
// example for eager evaluation
defun eager _add(x,y) = x+y;
defun eager _mul(x,y) = x*y;
defun eager _test(a,b,c) = eager_add(eager_mul(a,b),c);
// example for lazy evaluation
defun lazy _add(x,y) = delay (force x)+(force y);
defun lazy _mul(x,y) = delay (force x)*(force y);
defun lazy _test(a,b,c) = lazy_add(lazy_mul(a,b),c);
// use example
a=eager _test(2,3,4);
a;
b=lazy _test(2,3,4);
force b;
なんのこたない、a*b+c を計算する関数を使って、2*3+4 を計算してるだけのプログラムです。少なくともeager な例のほうは、まったく説明は不要ですよね。
では動作を順に追ってみましょう。まずは eager_test から。
a=eager_test(2,3,4);を実行すると、関数 eager_test が呼ばれます。
eager_testはまず eager_mul(2,3) を実行します。
eager_mul は式2*3 を計算し、値6を返します。
eager_testは返り値6を使って、eager_add(6,4)を実行します。
eager_add は式6+4 を計算し、値10を返します。
eager_testは返り値10を受け取り、それをそのまま返します。
最終的に返された値10が、変数aに格納されます。
次の文 a; を実行すると、変数aの値10を表示します。
問題はlazy_test の方ですね。こっちを順に見てみます。
b=lazy_test(2,3,4); を実行すると、関数lazy_test が呼ばれます。
lazy_test はまず lazy_mul(2,3)を実行します。
lazy_mulは、式2*3 を計算したりはしません。返り値として、「force された場合には、式
(force 2)*(force 3)" を計算してその値を使え」という内容、すなわち式
delay (force 2)*(force 3)" を返します。
lazy_test はその返り値を使って、lazy_add(delay (force 2)*(force 3),4)を実行し
ます。
lazy_addは返り値として、「force された場合には、式
(force (delay (force 2)*(force 3)))+(force 4)" を計算してその値を使え」という内容、すなわち式 delay (force (delay (force 2)*(force 3)))+(force 4)"を返します。
lazy_test は返り値を受け取り、それをそのまま返します。
最終的に返された値 delay (force (delay (force 2)*(force 3)))+(force 4)"
が、変数bに格納されます。
次の文 force b; を実行すると、次のように計算が行われます。
最終的に、値10が表示される、という仕掛けです。
ぐちゃぐちゃしてややこしいですが、値が必要になった時点で計算が行われるということがこれを見てわかると思います。
まずはλ式の説明から始めましょう。
言語Mにおけるλ式とは、大ざっぱに言えば、名前の無い関数のことです。
記法は、 fn (引数並び) => 関数本体" のようになります。
実際に例を見てみましょうか。
実行例
最初に出てくる関数foo は見たまんまです。foo(3,5)とかやって使うわけですね。ここで、foo 自体の型は関数型です*2。
次に出てくる fn (x,y) => x+y" が、問題のλ式です。λ式は名前の無い関数ですから、この式の型もやはり関数型です。
実際にλ式を使って計算をさせる場合は、普通の関数呼び出しと同じように書きます。
foo(3,5)とか書いてある関数名の部分が、そっくりそのまま長ったらしいλ式に置き替わる感じですね。C言語でも、関数のポインタを用いる場合には似たような書き方をしますよね。
またλ式は、整数やリストと同じように、一階のデータとして扱うことができます。つまり変数に格納したり、関数の戻り値として使えたりするわけです。試しにこのλ式を変数bar に格納してやると、bar(3,5)とかやってこのλ式を使うことができるようになります。
さて、上の例で foo(3,5) ってやっても bar(3,5) ってやっても、同じように結果8が返ってきます。ではこの二つの違いって何なんでしょうか?
実はこの両者はまったく同じです。いちいち bar=fn(x,y)=>x+y とか書くのが面倒なので、代わりにdefun と書いてもいいよ、というだけの話だったりします*3。
では、λ式を使ってdelay&force と同様の動作をさせることを考えましょう。 delay は「値が欲しくなったら式〜を評価しろ」ということですから、これを「値が欲しくなったら引数無しのλ式〜を評価しろ」で表すことにします。
delay 3+5 | |
(実装): | fn () => 3+5 |
force A | |
(実装): | A() |
force (delay 3+5) | |
(実装): | (fn () => 3+5)() |
→ 3+5 | |
→ 8 |
まだλ式に慣れてないかもしれませんが、念の為に書くと、(fn () => 3+5)()というのは
と等価です。これなら見慣れた表現だからわかりますよね。
次に、delay された値を返す関数を定義する例を見てみましょう。
defun add_delay(x,y) = delay x+y; | |
(実装): | defun add_delay(x,y) = fn () => x+y; |
こうすると、add_delay が呼ばれる度に、どんどん新しく名前の無い関数を作ることになり
ます。例えば add_delay(1,2) を実行すると無名関数 fn() => 1+2 が新しく作られますし、
add_delay(3,5)を実行すると無名関数 fn() => 3+5 が新しく作られます。
次に、delay されている値もされていない値も同様に扱えるようなforce を作ってみましょう。無理矢理force しようと思ってdelay されてない値を関数呼び出しすると、エラーになっちゃいますからね。
ここでは、delay されているかどうかの判断に、「そのデータは関数型か?」を用いることにします。本当は「その式はdelay によって作られた式か?」を調べる組み込み手続きがないと、関数型自体をdelay した場合にうまくいかないのですが。扱うデータを整数やリストに限定すれば、これでも十分です。
リスト5
defun force(dat) =
if function?(dat) then
dat() // Force!
else
dat;
最後に、リスト4の lazy_test を書き直してみます。
リスト6
// example for lazy evaluation
defun lazy_add(x,y) = fn () => (force(x))+(force(y));
defun lazy_mul(x,y) = fn () => (force(x))*(force(y));
defun lazy_test(a,b,c) = lazy_add(lazy_mul(a,b),c);
// use example
b=lazy_test(2,3,4);
force(b);
ほとんどリスト4の場合と同じですね。
b=lazy_test(2,3,4); で、関数lazy_test は返り値として、式
"fn()=>(force(fn()=>(force(2))*(force(3))))+(force(4))"
を返します。force(b); によって実際の計算が行われ、値10が表示されます。
ここでとりあげたforce は、単に関数評価を行うだけのものでした。
実際には、例えば上の例で求めたbについて、force(b)を何度も行うことになる場合が多いと思われます。ここで毎回同じ計算を繰り返してはたまらん、ということで、実際には「一度計算した値は内部で覚えておいて、二度目以降の forceでは計算済みの値を使う」という最適化が考えられます。
この最適化は、delay のほうに少し細工を加えることで可能になります。ここでは詳しい方法については説明しません(Schemeに関するもっと細かい知識が必要になるので)。
というような訳で、lazyな評価のための特殊な支援がなくても、無名関数を動的に生成できるような能力さえあれば、lazyな評価を実現することができます。
C言語で同じ事をしようとすると、ちょっとトリッキーなことをする必要がありますが、できないことはありません。
Lazy list とは、要素をdelay することによって、無限の長さを持たせたリストです。ここでは整数を保持する1次元のlazy list を考えることにします。
では例を見てみましょう。1ずつ増えていく無限整数列を生成する関数と、lazy list を先頭から何要素分か表示する関数です。
リスト7
defun Car(lazylist) = car(lazylist);
defun Cdr(lazylist) = force cdr(lazylist);
defun from(k) = cons(k, delay from(k+1)); // [k,k+1,k+2,...]
defun takeq(n, lazylist) =
if n=0 or null?(lazylist) then
[]
else
cons(Car(lazylist), takeq(n-1, Cdr(lazylist)));
実行例
関数takeq のほうは、普通のリストを先頭何要素分か取り出す関数そのものなので、何も特殊なことはありません。問題は関数 from の方です。
from(1) を評価すると、返り値は[1 . delay from(2)] です。Car(from(1))の結果は1になります(ここまでは自明)。
Cdr(from(1))すなわち force cdr(from(1)) を評価すると、これはforce(delay from(2)) となって from(2) を評価するのと等価ですから、値は[2 . delay from(3)] になります。以下同様に、Cdr(Cdr(from(1)), Cdr(Cdr(Cdr... と無限にCdr を取ることが可能になります。 つまり、関数 from によって生成されたリストは、長さが無限であり、実際に値が必要になったときに始めて計算が行われる、という性質を持っているわけです。これが典型的なlazy list です。
次に、引数として受け取ったlazy list を加工して、新たなlazy list を返す例を見てみます。
リスト8
defun squares(a) =
if null?(a) then
[]
else
cons( Car(a)*Car(a),
delay squares(Cdr(a)) );
defun addq(a,b) =
if null?(a) or null?(b) then
[]
else
cons( Car(a)+Car(b),
delay addq(Cdr(a),Cdr(b)) );
この関数 addq は、リスト3の関数add_2list とほとんど同じものです。違いは、Car/Cdr を用いている点と、cons で delay している点だけです。
実行例
from(1) の値は[1 . delay from(2)] でしたから、squares(from(1))の値は
[1 . delay squares(Cdr(from(1)))]です。また、addq(from(1), squares(from(1)))の値は[1 . delay addq(Cdr(from1), Cdr(squares(from(1))))] になります。
最後にトドメとして、素数の無限リストを求めるプログラムを示しておきます。
リスト9
defun filterq(pred,x) =
if null?(x) then
[]
else
if pred(Car(x)) then
cons(Car(x), delay filterq(pred, Cdr(x)))
else
filterq(pred, Cdr(x));
defun sift(p,x) =
filterq(fn (n) => (n mod p != 0), x);
defun sieve(x) =
cons(Car(x), delay sieve(sift(Car(x), Cdr(x))));
primes = sieve(from(2));
>
実行例
このように lazy な評価を使うと無限の構造を持つデータを直感的に取り扱うことができますから、うまく利用すればプログラミングの戦略を決める際の強力な武器になることでしょう*4。
今回のなおの話は、そんな方のためにWWW上で実行するオリジナルプログラムの作り方を解説する話です。
WWW上で実行するプログラムとは、入力はHTMLが用意する機能を使い、出力はHTMLの文章をそのまま送って、それをbrowserに処理させるというものです。内部処理は自作のアルゴリズムでいくらでも処理できるのです。
これにより、入る度に変化するページが作れたり、見に来た人がメッセージを送ることができるページが作れるのです。
僕は夏合宿の写真の焼き増しの注文受け付けページやWWW草の根ネットなどを作りました。なかなか楽しいですよ。
このようなプログラムのことをCGI(Common Gateway Interface)と呼びます。または単に Gateway Script とも呼ばれます。CGIは単なる実行形式のファイルなので、何の言語でもCGIが作れます。
ホームページを作る人のレベルが初心者であることが多いためか、数少ないCGIの解説書は、たいてい perl でかかれています。perlはbasicのように文字列操作が極めて簡単な言語で、それでいてpipeやsocketも作れてしまうという、ファイル操作にはとても便利な言語です。
しかし、本格的なCGIを作るためには、やはりC言語で記述するのがよいでしょう。なので、すべてC言語(一部C++)で説明します。
HTMLの文法についての解説書はすでに多くあります。しかしCGIの作り方についてC言語で解説された本は見かけません。それに、ほとんどのCGI解説書は、一番肝心な入力データ処理システムの作りを解説せずに市販のソフトを使用するように勧めています。
今回のなおの話は、他書には全く載っていないCGIの根底からの解説です。イメージマップの処理の仕方やFORMデータの受け付け方など非常に重要なことを紹介し、さらにその処理CGIのC言語ソースも載せてあります。
なおが休み中に自力で得た知見を特別にみなさんに解説するという非常にありがたい話なのですよ。大事にとっておきましょう。
まずは自分の Home Page の最初の1ページを作りましょう。駒場と本郷では作り方が少し違うので気をつけましょう。まず駒場での話をします。
1.1 とにかくindex.htmlをつくります。
まず、Home root Directry のすぐ下に WWW という名の directory を作り、その permission を go=rx をします。そしてその WWW directoryの下に index.html という名前のファイルをつくります。その中身は例えば次のようにしてみて下さい。 僕の名前やアドレスはあなたの物に変えて下さい。
<!--index.htmlの始まり--> <title>Nao's Home Page</title> <h1>なおのHome Pageにようこそ</h1> <hr><h2> ここは、渡辺尚貴のHome Pageですがまだ作り始めたばかりです。<br> 今大急ぎで建設中なので今しばらくお待ちください。<P> 僕の好きなfractalの絵を下に載せます。<br> <img src="http://www.komaba.ecc.u-tokyo.ac.jp/~g440056/IMAG/mand00.gif"> <br> <li>TSGのcircle Home Pageに行けます。 <a href="http://www.komaba.ecc.u-tokyo.ac.jp/~g541119/TSGhome.html"> ここを</a> クリックしてください。<P> </h2> <hr> <address>g440056@komaba.ecc.u-tokyo.ac.jp</address> <!--index.htmlの終わり-->
これをセーブして permission を go=r としてください。これを一晩つけておいてください。次の日の朝にはあなたのホームページの1ページ目が「駒場のホームページ一覧」に加えられています。
明日まで待てないという人は、MOSAIC の左上の file ボタンの dialog の中のOpen URL で今作ったページの URL を指定してください。
URL( Uniform Resource Locator ) とはWWWの世界の中でページのアドレスを示すもので、つまり path です。絶対 URL と相対 URL がありますが、Open URL には絶対 URL を使います。
URL の形式を説明します。まず、
http://
と書きます。この http ( Hyper Text Transfer Protocol )とはHTML文章を高速に転送するための決まり文句です。続けて駒場のアドレスを書きます。
www.komaba.ecc.u-tokyo.ac.jp/
続けてあなたのログイン名を書きます。
~g440056/
この後ろにhtmlファイルのWWW directory から相対 path を書きます。これを省略すると index.htmlが default になります。
つまりまとめると、
http://www.komaba.ecc.u-tokyo.ac.jp/~g440056/
です。これを Open URL でとなえてください。
あなたのホームページが表示されましたか?
1.1' 本郷にindex.htmlをつくります。
ホームページが大きくなってきたら本郷に支店を設けましょう。駒場の学生も本郷の学生もどちらも、駒場と本郷にそれぞれ15Mbyteのディスクが与えられています。駒場がまんぱんになったら、本郷に移すことができるのです。
本郷でのホームページの作り方において駒場と違うことはただひとつです。Home Root Directory の下に WWW ではなくて public_html という directory をつくります。あとはその下で駒場と同じように展開してください。
聞いた話しによると、実はpublic_htmlの方が主流なのだそうです。WWWの方がわかりやすい と思うのですが、それもそのはず、駒場は under barが打てない人のことを考えてWWWにしたのです。何もそんな人のことまで考えなくてもと思うのですがね。
1.2 文法の説明
例に使った index.html にでてきたHTMLの文法について説明します。
指定ファイルの拡張子によって対応が自動的にかわります。
HTMLの基本文法はこれで大丈夫です。さらに詳しく知りたい人は、WWWのどこかに情報がたくさんあります。本屋にもあります。そちらを参照してください。
1.3 HTMLトラブル対策集
HTMLを作る過程で、陥り易いミスを幾つか例示します。
┌─Mail/ │ ├─Tex/──report.dvi │ ├─Wnn/ ┌ index.html │ │ /home/g440056/──┼ WWW/ ────┼ intro.html │ │ └─Bin/ └ IMAG/ ──image.gif
このとき index.htmlから見た intro.htmlの相対URLは単に intro.htmlです。
image.gifの方は IMAG/image.gifです。
WWW の外の report.dviは見ることはできません。WWWの下にコピーしてください。
WWWの下でなら ../が使えます。IMAG/の下から ../index.htmlとかのようにです。
絶対URLはすべてWWWを省略します。WWW以外にあるファイルはアクセスできません。
http://www.komaba.ecc.u-tokyo.ac.jp/~g440056/intro.html
http://www.komaba.ecc.u-tokyo.ac.jp/~g440056/IMAG/image.gif
のようにするのです。
showtime.cc というファイルをWWWの下の好きなところに作って下さい。以下にそのリストを載せます。
// showtime.cc
#include<stdio.h>
#include<time.h>
void main( void )
{
printf("Content-type: text/html\n\n");
printf("<title>Show current time</title>\n");
printf("<h2>\n");
long ltime;
time( <ime );
tm* ct = localtime( <ime );
(ct->tm_mon)++;
printf("今日は%02d年%02d月%02d日です。<br>\n",
ct->tm_year, ct->tm_mon, ct->tm_mday );
printf("ただ今の時刻は%02d時%02d分%02d秒です。<br>\n",
ct->tm_hour, ct->tm_min, ct->tm_sec );
printf("</h2><hr>\n");
printf("<address>g440056@komaba.ecc.u-tokyo.ac.jp</address>\n");
}
さてこの showtime.cgi を普通にコマンドラインから実行すると次のような出力が得られますね。
<title>Show current time</title>
<h2>
今日は95年10月21日です。
ただ今の時刻は19時25分32秒です。
</h2><hr>
<address>g440056@komaba.ecc.u-tokyo.ac.jp</address>
最初の一行だけ解説が要ります。この一行が出力の先頭にあることによって、browser はこの出力テキストがHTML形式であることを判断します。
プログラムの先頭に例の通りに書いてください。少しでも違うと server errorになります。あとの出力はHTML形式そのまんまですね。
この showtime.cgi の execute permission を他人に対して許可にしてください。そしてこのCGIにリンクを張ってください。例えば、
<a href="showtime.cgi">今の時刻は?</a>
これでWWW上からCGIが実行されるのです。CGIの標準出力から出てきたHTML文章を browser が処理するのです。
このように printf 関数の中にリンクや絵を指定する命令を書けるのです。例えば、
printf("今日の絵<br>\n");
printf("<img src=\"todaypicute%d.gif\"><br>\n",ct->tm_mday);
printf("<a href=\"page%d.html\">", rand()%10 );
printf("どこへいくかは入ってからのお楽しみ");
printf("</a>\n");
のようなことができるのです。printfなどで " を表示するときは \ で escapeするのを忘れずに。
CGIの実行時にコマンドライン引き数を与えることが出来ます。CGIファイル名の後ろに?マークを付けて引き数を+で区切って並べるのです。例えば
<a href="showtime.cgi?Tokyo+London+NewYork">今の時刻は?</a>妙な例ですが使い方はわかったでしょう。
秋葉原のマップのように、全体図があって、セクションの部分をクリックするとそのセクションの解説のページが出るようなことに使われます。
また本棚の絵を書いて背表紙をクリックするとその関連の所に行けるというおしゃれな仕組みもできるのです。
この章では、クリックした座標に応じて反応するCGIの作り方を説明します。
例えば mapdisposer.cgi が、その処理CGIとします。クリックされる絵を例えばimagemap.gifとしましょう。この絵を次のようにして貼ります。
<a href="mapdisposer.cgi"><img src="imagemap.gif" ISMAP ></a>
ISMAPというのがイメージマップであることを示す呪文です。
絵がクリックされると mapdisposer.cgiの第一引き数に座標情報が100,200のようにひとつづりの文字として渡され実行されます。
mapdisposer.ccの中身を説明する前に、ひとつ重要な機能を説明します。browserは出力テキストの先頭に
のような指定があると、それ以後の文章を表示せずに、ここで指定されたファイルを表示します。これを mapdisposer.cgiに出力させるのです。
些細かつ重要な注意ですが、この行は必ず出力の先頭にしてください。そしてこの行の次の行は空行にしてください。URLは絶対URLです。相対URLは使えません。
このイメージマップ処理CGIのC++による完全なソースをここに公開します。なおの作品ですが、どんどんコピーしていってください。市販のものよりずっとシンプルで使いやすいです。手書きコピーをするのは無駄なので僕のWWWに入れておきます。
cut and paste で持っていってください。
//========================================================================== // mapdisposer.cc // Getting coordinates and proceedind next pages. // Copyright (C) Naoki Watanabe. 1995. All rights reserved. //========================================================================== #include <stdio.h> #include <stdlib.h> //************************************************ // Checking argc // argv[0] is CGI filename, argv[1] is Coordinates. //************************************************ void Check_Argument( int argc, int right ) { if( argc != right ){ printf("Content-type: text/html\n\n"); printf("<h1>ERROR!!!!<P>\n"); printf("argc has something wrong.</h1><br>\n"); exit(1); } } //************************************************ // Convert coordinates infomation into two integers //************************************************ void Get_Coordinates( char* point, int& X, int& Y ) { // *point is for example such as "100,200". for( int i=0 ; point[i]!='\0' && i<20 ; i++ ){ if( point[i] == ',' ){ point[i] = '\0'; X = atoi( &point[ 0 ] ); Y = atoi( &point[i+1] ); return; } } printf("Content-type: text/html\n\n"); printf("<h1>ERROR!!!!<P>\n"); printf("The coordinate infomation has something wrong.</h1><br>\n"); exit(1); } //************************************************ // Reporting the clicked coordinates in HTML style // This function needs HTML page, so if you use // this function, you can't load the given URL page. //************************************************ void Print_Coordinates( int X, int Y) { printf("Content-type: text/html\n\n"); printf("<H1>You clicked at (%d,%d)</H1>\n",X,Y); exit(1); } //************************************************ // Laoding given URL page. // URL is absolute one, please. //************************************************ void Proceed_Page( char* URL ) { printf("Location: %s\n\n", URL ); } //************************************************ // Main function. //************************************************ void main( int argc, char* argv[] ) { int X,Y; Check_Argument( argc, 2 ); Get_Coordinates( argv[1], X, Y ); // If you use this functon, you can't load the given URL page. //Print_Coordinates( X, Y ); // Inspecting which regions the clicked point is included. if( 0 <= Y && Y < 50 ){ // Goto NAO's Home Page Proceed_Page("http://www.komaba.ecc.u-tokyo.ac.jp/~g440056/"); exit(1); } if( 50 <= Y && Y < 100 ){ // Goto GANA's Home Page Proceed_Page("http://www.komaba.ecc.u-tokyo.ac.jp/~g440604/"); exit(1); } if( 100 <= Y && Y < 150 ){ // Goto NISHI's Home Page Proceed_Page("http://www.komaba.ecc.u-tokyo.ac.jp/~g440611/"); exit(1); } if( 150 <= Y && Y < 200 ){ // Goto HANAWA's Home Page Proceed_Page("http://www.komaba.ecc.u-tokyo.ac.jp/~g440614/"); exit(1); } if( 200 <= Y && Y < 250 ){ // Goto SHIGE's Home Page Proceed_Page("http://www.komaba.ecc.u-tokyo.ac.jp/~g441232/"); exit(1); } }
以上です。
座標からどのページに行くかの分岐の仕組みには、やはりif文を乱立させるしかないでしょうかねえ。まだまだ工夫が要りますね。
でも市販の処理プログラムだって同じなのですよ。インタプリタ的に領域指定ファイルを読んで解析して、もっとぐちゃぐちゃなif文を通しているのです。あれを真似する気にはなれません。ちなみにその市販されているソースは
にあります。
Image Map を使用すると、ホームページの見栄えが格段に上がります。グラフィカルなナビゲートシステムは、人々を迷わず楽しませることでしょう。
FORMというのが、そのためにHTMLに加えられた新機能なのです。しかしこのFORMは扱いがちょっと厄介です。しっかり読んでください。
まずは入力受け付けページ(FORM page)の作り方について解説します。次ぎにその入力データの解析CGIの作り方についてソースを公開します。
4.1 FORM文法の詳細な解説
FORMもHTML文法の一種なので、使い方は非常に簡単です。まず FORM area を設定します。入力データの解析CGIの名前は例えば formdisposer.cgi とします。HTML文章中に次ぎの2行を入れてください。
<form method="POST" action="formdisposer.cgi"> </form>
これだけではなににもなりません。area内に入力用のfieldを作ります。例えば、閲覧者の名前を書いてもらう欄を作るとします。
先の2行の間に次の1行を入れてください。
<input name="username" type=text size="20"><br>
これで20文字分のフィールドができます。そしてここに書いた文字は変数 username に格納されます。
今の<input>タグは一行の文字列を受け付けるものでしたが他にもいろいろな入力のための仕組みが用意されています。すべて紹介します。
<input name="username" type=text size="20"><br>
の様にすると20文字分の field ができます。ただし受け付けられる文字はもっとたくさんです。つまり20文字以上入力するとスクロールします。
入力できる文字数を制限する場合は次のようにします。
<input name="username" type=text size="20" maxlength="50" ><br>
defaultで文字を表示させたい場合は次のようにします。
<input name="username" type=text size="20" value="NAOKI" ><br>
<input name="password" type=password size="20" ><br>
の様に type=password とします。
<input name="cb" type=checkbox >
の様に type=checkbox とします。
checkされるとcbの値は"on"になります。別な値にしたいというときは、
<input name="cb" type=checkbox value="marked">
のようにします。
checkされていないとその値はありません。つまり変数も無いのです。
defaultでcheckされている状態にしたいのならこうします。
<input name="cb" type=checkbox CHECKED >
<input name="computer" type=radio value="PC98">NEC-PC98 <input name="computer" type=radio value="IBMPC">IBM-PC <input name="computer" type=radio value="MAC">MAC <input name="computer" type=radio value="TOWNS">FM-TOWNS
の様に type=radio として同じ変数名にします。選択されたときの変数の値を value で設定します。
defaultでcheckされている状態にしたいのならこうします。
<input name="computer" type=radio value="PC98" CHECKED >NEC-PC9801
<select name="language"> <option>中国語 <option>ロシア語 <option>スペイン語 <option>ドイツ語 <option>フランス語 </select>
の様にすると<optioin>の後ろの言葉がメニューに並んで、マウスを合わせると変数の値がその言葉になります。
メニューに出ている言葉と変数に入れる値を変えたい時は次のようにします。
<option value="china">中国語
通常先頭に並んでいる言葉がdefaultで設定されていますがあえて別な言葉を defaultで設定させたいなら、
<option selected>ドイツ語
としておきます。
一度に表示されるメニューの数を制限させたいなら
<select name="language" size="5" >
とします。スクロールバーが付くことになります。
<textarea name="comment" cols=60 rows=6></textarea>
の様にすると、横60文字、縦6文字分の input field ができます。その文章すべての値が変数 comment に入ります。cols は60以上にしないほうが良いでしょう。
この field に default 文章を入れたいときは、次のようにします。
<textarea name="comment" cols=60 rows=6>何か書いてください</textarea>
<input name="mode" type=hidden value="mode1"><br>
の様にすると field を出さずに変数に値が設定できます。
この使い道は、form処理CGIにコマンドライン引き数からではない方法で決まった情報を流すときに使われます。また2ページに渡って連続するFORMページを通して、FORM変数を送りたい時にも使われます。
<input type=reset value="全部書き直す">
これで「全部書き直す」というボタンができます。これを押すと入力したすべてのデータが default の状態にもどります。
<input type=submit value="送信する">
これで「送信する」というボタンができます。これを押すと指定したCGIが起動して、入力したデータは標準入出力を通じて送られます。
例を示さなくてはいけませんね。次のFORMは Home Page の感想のアンケートを書き込む例です。
<title>Opinion Page</title> <H1>私のHomePageの御感想をお聞かせください。</H1> <hr><h2> <form method="POST" action="formdisposer.cgi"> あなたのお名前は:<input name="name" type=text size="20"><P> あなたの住所は:<input name="address" type=text size="50"><P> あなたの性別は: <input name="gender" type=radio value="male" CHECKED>男性 <input name="gender" type=radio value="female">女性<P> あなたの年齢は: <select name="age"> <option value="10">10歳から19歳 <option value="20" selected >20歳から29歳 <option value="30">30歳から39歳 </select><P> 御感想をお願いします。<br> <textarea name="comment" cols=60 rows=6></textarea><P> <input type=reset value="全部書き直す"> <input type=submit value="送信する"> <p>