講義第8回:Pascal の基礎 --- グラフィックスと繰り返し、判断

今日はグラフィックスの機能を使い、画面にいろいろ絵(主に線画)を書いてみる。さらに、繰り返し処理などで複雑な図形を書くことを考える。

これにより、ある程度複雑なプログラムがどのように動くかということを視覚的に理解することにしたい。

2回めのレポートについて

提出状況が 課題2Aおよび 課題2B に出ているので、自分が出したものがある かどうか確認して、出したはずなのにないって人はとりあえず 白石さんに申し出て下さい。 なお、

グラフィックス

この講義で使っている PASCAL では、あらかじめ準備されている様々な手続き、関数を使うことで画面に絵を書くことができる。関数とは、普通の数学関数のように、数を返してくれるものである。手続きとは、例えば「指定した座標を中心として指定した半径で円を書く」という一連の仕事をやってくれるものである。

program graphics_sample(input, output);
#include <xgraph.h>
var gdriver, gmode : integer;         
    xcen, ycen : integer;
begin
    gdriver := detect;                グラフィックス使用の準備
    initgraph(gdriver, gmode, '');    グラフィックス使用の準備
    cleardevice;                      画面のクリア
    clrscr;                           文字画面のクリア
    xcen := getmaxx div 2;            画面中心の x 座標の計算
    ycen := getmaxy div 2;            画面中心の y 座標の計算
    circle(xcen,ycen, 100);           円の表示
    circle(xcen,ycen, 150);           円の表示
    line(xcen-150,ycen-150,xcen+150,ycen-150);      線分の表示
    line(xcen+150,ycen-150,xcen+150,ycen+150);      線分の表示
    line(xcen+150,ycen+150,xcen-150,ycen+150);      線分の表示
    line(xcen-150,ycen+150,xcen-150,ycen-150);      線分の表示
    readln;
    closegraph;                       グラフィックス使用の終了
end.
この例では、最初の #include 何とかというのは、グラフィックス関数を使うということを宣言している。この #include というのは、PASCAL 言語の機能ではなく、C という別の言語のコンパイラの機能であるが、とにかく指定したファイルの中身をそこに読み込んでくる。実際のファイルは /usr/include/xgraph.h のはずである。

実行部の先頭の2行は、実際に絵を書くための 準備をする手続きを呼んでいる。これを呼ぶと新しいウインドウができる。

このウインドウは、 800 x 800 の点からできている。座標系は、 慣習により画面の左上が (0,0), 右下が (799,799) となっている。

このグラフィックスの手続きは、 IBM のパソコンでつかわれていた Turbo PASCAL (商品名)というコンパイラと(大体)同じように使えるようになっている。言語が違ったりすると、グラフィックスの手続きの細かいところはいろいろちがってくるが、まあ、線を書く、円を書く、文字を書く、色を変えるといったものがあるというのはどれも同じようなものである。

グラフィックス手続きについては、簡 単なまとめをつくっておいた。

なお、日本語で説明を書いたところもそのままコピーしてはコンパイル出来ないので注意。そこは消すこと。

上のプログラムでは、circle と line が実際に円を書く、あるいは直線を引く手続きである。従って、これらを追加したり、指定する数(引数)を変えたりすれば画面に出る絵が変わる。なお、手続きや関数で引数を2個以上必要とする場合(例えば circle では x 座標、 y 座標、 半径の3個)3個の数(あるいは変数、式)をコンマで区切って並べる。並べたものの何番目がどれか(たとえばx、y 座標 と半径のうち)は、あらかじめ決まっている。このような方式を、位置パラメータといい、現在のほとんどの計算機言語で使われている。

このやり方の代わりに、例えば circle(x_pos=xcen, y_pos=ycen,radius=100) というように指定することも考えられる。この方がわかりやすいが、プログラムが繁雑になるのを嫌うためかこの形式が使える計算機言語は少ない。(アメリカ国防総省の指定言語 Ada は、この形が使える)

なお、このプログラムを打ち込んで、そのままコンパイルしようとしてもうま くできない。これは、グラフィックスを使う関数がどこにあるか指定されてい ないからである。これは以下のように指定する。

pc graph1.pas -o graph1 -lXtc -lX11
ここで、最後の2つの -l 何とかというのが、グラフィックスを使うためのおまじないである。コマンドやファイル名には大文字と小文字の区別がある。例えば -lxtc とか -lXTc とかではうまく動かないので注意して欲しい。

反復

さて、計算機のプログラムで絵を書くといっても、線を1本引くのにいちいち line ..... と書くのであれば tgif でも使うか、そもそも紙に手で書いた方が早い。プログラムを書くといいことは、例えば線を引く位置を変えながらたくさん引くとかいうのができることである。
program graphics_loop(input, output);
#include <xgraph.h>
var gdriver, gmode : integer;         
    xsize, ysize  : integer;
    n,         i  : integer;
    dx, dy        : real;
begin
    write('How many lines? ');
    readln(n);
    gdriver := detect;             
    initgraph(gdriver, gmode, ''); 
    cleardevice;                   
    clrscr;                        
    xsize := getmaxx ;            
    ysize := getmaxy ;
    dx := xsize/n;
    dy := ysize/n;
    for i := 0 to n do line(0,round(i*dy), round(i*dx),ysize);
    readln;
    closegraph;
end.
for 変数 := 最初の値 to 最後の値 do 文; は、まず変数に最初の値を入れて文を実行し、次に変数を1増やしてまた文を実行し、以下同様に繰り返して変数が最後の値になったらおしまいにするということになる(変数が最後の値になったときも文は実行される)。このような繰り返し処理をループ処理という。上の場合では始点、終点が違う線分を指定した本数だけ引いてくれる。なお、 round は実数型の値を四捨五入した整数に変換する関数である。

線の代わりに丸をたくさん書いてみる。

program graphics_loop2(input, output);
#include <xgraph.h>
var gdriver, gmode : integer;
    xcen, ycen  : integer;
    n,i,x,y  : integer;
    r1, r2, theta   : real;
begin
    write('How many circles ');
    readln(n);
    write('Enter r1, r2: ');
    readln(r1, r2);
    gdriver := detect;
    initgraph(gdriver, gmode, '');
    cleardevice;
    clrscr;
    xcen := getmaxx div 2;
    ycen := getmaxy div 2;
   theta := 3.142592*2/n;
    for i := 0 to n do begin
        x := round(r1*cos(i*theta))+xcen;
        y := round(r1*sin(i*theta))+ycen;
       circle(x,y, round(r2));       

    end;

    readln;
    closegraph;
end.
このプログラムでは、上のプログラムに比べて1つ新しいことをしている。 for で繰り返す中身が一文ではなく複数の文のつながりであることである。 ループの中でいくつかの文のつながりを実行するためには、

for 変数 := 最初の値 to 最後の値 do begin
文;
......
文;
end;
というふうに、いくつかの文を beginendで囲んでやる。

判断

program graphics_sample2(input, output);
#include <xgraph.h>
const rad = 30;
   
var gdriver, gmode		    : integer;
    x,y,vx,vy			    : real;
    i,j				    : integer;
   xmax, xmin,ymin,ymax		    : real;

begin
   write('Enter x, y, vx, vy:');
   readln(x, y, vx, vy);
   initgraph(gdriver, gmode, '');
   cleardevice;
   clrscr;
   xmin := rad;
   xmax := getmaxx-rad;
   ymin := rad;
   ymax := getmaxy-rad;
   for i := 0 to 10000 do begin
      setcolor(Black);
      circle(round(x), round(y), rad);
      if x+vx > xmax then  vx := -vx;
      if x+vx < xmin then  vx := -vx;
      if y+vy > ymax then  vy := -vy;
      if y+vy < ymin then  vy := -vy;
      x := x + vx;
      y := y + vy;
      setcolor(White);
      circle(round(x), round(y), rad);
      for j := 1 to 10000 do ;
    end;
    readln;
    closegraph;
end.

if 条件 do 文1
else 文2;

という構造は、条件が成り立っていれば文1を、そうでなければ文2を実行せよという意味になる。文2がない(条件が成り立っている時はなにかするがそうでなければ何もしない)ときには

if 条件 do 文1;

だけでいい。なお、 for の中に書いた、 begin 文; 文; ... end というまとまりのことを複文といい、一般に文が書けるところには複文も書ける。このため、 if ... then begin ..... end else begin .... end; というような風にすれば条件によって違ういくつかの処理をまとめてできる。

条件は、数値同士の比較式(大小、等しい)と、複数の比較式からできる論理式などが書ける。

画面のプリントアウト

グラフィックス画面は gcopy というコマンドを実行するとプリンターに送られる。これは、絵が出ているうちに行なう必要があるので、左上の小さいウインドウで入力する。

なお、xv で保存したものをプリントすることも出来る。これには、 xv で保存する時にファイル形式を postscript にする。これで出来たファイルはそのままプリンターに送ることが出来る。

画面をファイルに保存する

自分で作った絵を保存するには、 xv を使えばいい。

反復(その2)

お絵書きばかりでもなんなので、少し計算らしいこともしてみよう。

program loop_example2(input, output);
var interest,amount: real;
    year_end, year: integer;
begin
    write(errout,'Enter year to calculate the amount:');
    readln(year_end);
    write(errout,'Enter annual interest:');
    readln(interest);
    amount := 1;
    for year := 1 to year_end do begin
        amount := amount * (1 + interest);
        writeln('year: ', year:2, ', amount:', amount);
    end;
end.
\end{verbatim}
これは複利(等比数列)を計算し、出力する。等比数列なので、前の年の値に 1 + 利息 を掛けて、その答を書き出すという処理を繰り返している。このために

実行例

Enter year to calculate the amount:5
Enter annual interest:0.0525
year:  1, amount: 1.05250000000000e+00
year:  2, amount: 1.10775625000000e+00
year:  3, amount: 1.16591345312500e+00
year:  4, amount: 1.22712390941406e+00
year:  5, amount: 1.29154791465830e+00

条件付反復

for ループを使う時には、あらかじめ繰り返しの回数がわかっている必要がある。しかし、場合によってはそもそも計算機で計算してはじめて繰り返しの回数がわかることもある。そのような時はどうプログラムすればいいだろうか。 ローンを借りて、毎年一定額ずつ返済していくとして、毎年残金がいくらかを書きだし、残金が0(またはマイナス)になったらやめるという処理を考えて見よう。
program while_example(input, output);
var interest,amount,payment: real;
    year: integer;
begin
    write(errout,'Enter annual interest and your payment:');
    readln(interest, payment);
    amount := 1;
    year := 0;
    while amount > 0.0 do begin
        amount := amount * (1 + interest);
        amount := amount - payment;
        year := year + 1;
        writeln('year: ', year:2, ', amount:', amount);
    end;
end.
このプログラムで使っている、
while 条件 do  begin
    文;
    ......
    文;
end;
という構造は、 begin と end にかこまれた処理を条件が成り立っているあいだ繰り返せということになる。条件は、数値同士の比較式(大小、等しい)と、複数の比較式からできる論理式などが書ける。

次週予告

今週は、あらかじめグラフィックス用に準備された手続きを使ってみたが、次週は手続きを自分で作ってみることにする。