#5 実装例@ 手続き・関数


 数少ないDelphiファンのみなさん。ご無沙汰です。(うう、読んでくれてる人居るんかな。)ご無沙汰です。じぁんけんを手続き・関数を用いて実装しましょう。書評ばっかりにかまけてDelphi系の人を無視してしまいました。巻き返しを図ります。余談交えて丁寧に語ります。

「手続き・関数によるジャンケンの実装の巻」の巻

 手続き・関数の定義ができたので今度は実装です。#3のスケルトンコードのUnit(JAN02)だけを書き換えます。順に説明します。

[intereface部]
 さてJankenという手続きをプロジェクトファイルのほうから呼び出すのでこれはinterface部に記述しておきます。その他、ユニットの内部だけで利用する手続きや関数は一切宣言しません。interface部は以下のようになります。  
interface
procedure Janken;
[implementation部/宣言]
 ユニットの内部だけで利用する型・定数・変数・手続き・関数などなどをここで宣言します。次の宣言をすることにします。

uses節
 Delphiにはライブラリファイルというものが多々ついてます。便利機能集ですね。Delphiお得意のGUIコンポーネントもライブラリで与えられてるものです。コンソールのアプリでは、SysUtilsユニットを良く使います。 SysUtilsって?余談1へ=>Go!


 
uses SysUtils;


型:TJankenと定数:Jan
 ジャンケンの「グー・チョキ・パー」を数字の1,2,3に対応させることにします。そこで数字の1,2,3だけからなる整数の集合という型TJankenを定義します。これはパスカルの独特の型です。SysUtilsって?は余談1へ=>Go!
以下の宣言を追加します。  
type TJanken=set of 1..3;
const Jan:TJanken=[1,2,3];
[implementation部/手続き実装]
手続きTe(t:integer;player:string)
 コードを書いていくとつまらんことを何回も繰り返すことがあります。繰り返される部分を何回も使えるように書き出して部品化します。(ここのようなのはサブルーチンといいます。)じゃんけんだと「あなたはグーをだしました」「comはパーをだしました」ってな感じのテキストを出力させる部分です。グー、チョキ、パーの手を整数tという変数で渡して、「あなた」とか「com」とかの対戦者情報を文字列playerで渡し、テキストを書き出すようにします。「手」を示すので手続きの名前をTeとします。
 コードは以下のようになります。なお、文字列変数「s」は手続きTeの中でだけ使います。以下のように宣言すれば「s」はここだけで利用できる文字列です。
 
procedure Te(t:integer;player:string);
var s:string;
begin
 if      t=1 then s:='グー'
 else if t=2 then s:='チョキ'
 else if t=3 then s:='パー' ;
 writeln(player+'の手は'+s+'です。');
end;
関数ComJan(man:integer):string;
 関数も一つ作ってみます。色んなことやらせた後に最後に何かデータを返すのが関数です。コンピュータのジャンケンの手を乱数で作り、人間の手と比べて勝敗の判定を文字列で返す関数をComJanとします。人間の手を整数で与え結果を文字列で返します。コードは以下のようになります。  
function ComJan(man:integer):string;
var com:integer;judge:string;
begin
 Randomize;
 com:= Round(Random(3))+1;
 te(com,'コンピュータ');
 case (man-com) of
  -1, 2:judge:='あなたの勝ち';
   0   :judge:='あいこです';
   1,-2:judge:='comの勝ち';
 end;
 result:=judge;
end;
 この関数のなかでcom(コンピュータの手:整数)とjudge(勝敗判定文:文字列)を使うのでvarで宣言します。
 manは人間の手です。com(コンピュータの手)が乱数できまるように処理してます。乱数っては余談3へ=>Go!

 comがきまればそれをコンピュータの手として画面に表示するために、手続きTeを呼び出します。
 次に判定です。comとmanが等しいとあいこですから引き算して、0なら「あいこ」という文字列を作成します。同様にグーチョキパーの順に大きい整数とすると-1はmanの勝ち、+1はcomの勝ちです。ただし+2,-2はグーとパーの組み合わせに対しての判定です。
ジャンケンのメイン手続きJanken;
 最後にメインの手続きを作ります。これまでの手続きや関数は部品で最後に書くのはDelphiが、「すでに定義した手続き・関数しか使えない(前方参照の禁止)」というルールを持っているからです。(JavaだったらOK)メインを先に書いてそれからTeとかComJanを定義したほうが見通しがいいと思う場合はinterface部に定義しておいてもいいです。しかし不要な関数は外から使えない方がいいのでこうしました。別のユニットにまとめておいてusesで利用宣言することもいいでしょう。今回は簡単にTe,ComJan,Jankenの順で一つのファイルに定義しておきます。なお、別の項で説明するクラスとかオブジェクトというものを使うとこの辺のことはもっとすっきりします。
 Jankenはユーザーに1,2,3の数字を入れさせて、そうでない入力はもう一度入力させて、ユーザーの選択を確実にします。そして、入力された手を表示して確認し(Teを使う)、ついでコンピュータにユーザーの手を渡し、勝負判定をします。判定の文章がComJanの結果なのでWriteln(ComJan(i));とし、結果を表示します。
 コードは以下のようになります。
 

procedure Janken;
var i:integer;
    s:string;
begin
 writeln('あなたの手? グー:1、チョキ:2、パー:3で入力。');
 readln(s);
 try
  i:= StrToInt(s);
  if (i in Jan) then
   begin
    te(i,'あなた');
    writeln(ComJan(i));
   end
  else
   begin
    writeln('グー:1、チョキ:2、パー:3で入力してください。');
   end;
 except
  on EConvertError do
   begin
    writeln('グー:1、チョキ:2、パー:3で入力してください。');
   end;
 end;  
end;
 ここでStrToIntは文字列を整数にする関数です。 入力の文字列にどうやっても数字にならないアルファベットなどを入れると落ちてしまうのでtry-except文という処理をしてますが、おまじないだと思ってください。(別の章で詳しく説明します。)とんでもないエラー(人間様が見ると大した事ないじゃんというようなことですが)が起こりそうなところをtryという予約語とexceptという予約語ではさみます。とんでもないエラーは「例外」と呼ばれ、DelphiではEで始まる名前で定義されてます。(自分でつくることもできます。)今回のStrToIntはアルファベットを入れるとEConvertErrorという「例外」(とんでもないエラー)になります。予約語exceptに続けて on <例外名> do 文;とするとその「例外」が発生したときにdoの後の文が実行されます。exceptの後はこういったエラー処理文が列挙されるのですが、際限なく続くと困るのでend;で締めくくります。
 Jankenの動作を順に説明すると、readln(s);でユーザーの入力を読んで、文字列sをtryの中で数字に変換しようとしています。例外(Sに数字じゃないものが含まれていたときはexceptのon EConvertError doに飛んで1,2,3のどれかを入力してくれと促します。もしも例外じゃないとき(sが数字になりうるとき)はifに行きます。なお(i in Jan)はsを変換した整数iがJan(1または2または3)に含まれているときtrue、Janのどの要素にもなってないときfalseを返す論理式です。例外を起こさずに数字になっても1,2,3意外だと処理できないのでelseでやはり1,2,3のどれかにしてくださいと要求させます。
 また、1,2,3であればユーザーがちゃんとジャンケンの手を決めてくれたことですから、それをTeに渡します。(確認表示「あなたの手はグーです。」など。)
 また、コンピュータにじゃんけんの手を選ばせ、ユーザーとの勝敗を判定する関数ComJanがありますのでそれを呼びます。

以上、まとめるとJAN02.pasは以下のようになります。  

unit Jan02;

interface

procedure Janken;

implementation
uses
 SysUtils;
type TJanken=set of 1..3;
const Jan:TJanken=[1,2,3];

procedure Te(t:integer;player:string);
var s:string;
begin
 if      t=1 then s:='グー'
 else if t=2 then s:='チョキ'
 else if t=3 then s:='パー' ;
 writeln(player+'の手は'+s+'です。');
end;

function ComJan(man:integer):string;
var com:integer;judge:string;
begin
 Randomize;
 com:= Round(Random(3))+1;
 te(com,'コンピュータ');
 case (man-com) of
  -1, 2:judge:='あなたの勝ち';
   0   :judge:='あいこです';
   1,-2:judge:='comの勝ち';
 end;
 result:=judge;
end;

procedure Janken;
var i:integer;
    s:string;
begin
 writeln('あなたの手? グー:1、チョキ:2、パー:3で入力。');
 readln(s);
 try
  i:= StrToInt(s);
  if (i in Jan) then
   begin
    te(i,'あなた');
    writeln(ComJan(i));
   end
  else
   begin
    writeln('グー:1、チョキ:2、パー:3で入力してください。');
   end;
 except
  on EConvertError do
   begin
    writeln('グー:1、チョキ:2、パー:3で入力してください。');
   end;
 end;  
end;

initialization
 writeln('じゃんけんしましょう。');

finalization

end.


(2004/May/30)


ページtopへ

リファレンス

 斜体は節の宣言を示す。例えばvar <変数名>:<型名>;とあれば、varのセクションで繰り返し宣言ができる。
ex.
var
  i,j:integer;
  s:string;
  ・・・・・・・


余談1 元へ 「Unitの使用宣言」
 SysUtilsユニットがないと文字列と数字の変換、乱数発生などができず不便です。usesという予約語の下に名前を書くと、そのソースにて利用するという宣言をしたことになります。usesの宣言はinterface/implementation部それぞれの最初でできます。

余談2 元へ 「集合型」
 この型を定義するとある値が範囲(この場合{1,2,3}のこと)に入っているかどうかの判定に「in」という演算子が利用できて便利なのでよく使ってます。ですが、JavaやC/C++に馴染んだ人は集合型は苦手かもしれませんね、その場合は整数型を利用し、大小比較をしてください。

余談3 元へ 「乱数処理」
 Randomizeというのを宣言してからRandom(n)とやると0以上、n未満の乱数を返します。Roundは実数の少数部を切り捨てて整数を返す関数です。(いずれも冒頭で使用宣言したSysUtilsに宣言されている関数です。Randomizeは必ずしも必要ありませんがやらないと手口が毎回同じになる可能性があります。擬似乱数に実行時の時刻によって不確定要素を与える命令です。)
 Round(Random(3))で0,1,2のいずれかの整数が得られますので1足して、comには1,2,3のいずれかの整数が入ります。


ページtopへ