Home >> Lecture 10


第10回 メソッドと複数のタートル(6/12)


10−1.戻り値を返すメソッド

■戻り値を返すメソッドの定義の仕方

引数は、呼出し側からメソッドへの引数の受渡しでした。メソッドの実行の終了時には、メソッド側から呼出し側へ、何らかの情報を返すことができます。

★メソッドの終了時における戻り値を返す文

メソッドの終了は、メソッドを定義しているブロックの最後まで実行が終わってしまえば、自動的に処理が呼出し側に戻ります。しかし、明示的にメソッドを終了したい場合は、return文を用います。これは、第8章で出てきました。メソッドをどの時点でも強制終了させて、呼出し側に制御を戻せます。加えて、値を返すメソッドを作成したい場合は、呼出し側に値を返すときにも、このreturn文を用います。このときは、次の書式のように、returnの後に式を記述することができます。
▼return文で値を返すときの書式
 return <式> ;
終了する前に、このreturn文の後に続く式が評価されます。評価が終わって一定の値になりましたら、呼出し側にその値が戻されます。ここで返される値のことを戻り値(Return Value)あるいは返り値と呼んでいます。それでは、戻り値を返すようなメソッドを定義してみましょう。まず、戻り値を返すので、その型を記述します。今までは、戻り値がありませんでしたので、voidと記述してきました。たとえば、整数を返すのであれば、int と記述します。簡単なメソッドを定義してみましょう。

		int    square(   int    x  )  {
			int   y = x * x;
			return    y;
		}
		
このsquareというメソッドは引数として受け取った値の2乗を計算して、計算結果を戻り値として返します。整数用になっていますので、仮引数も戻り値も型としてはint と記述されています。わかりやすくするために、ローカル変数yを用意して、それに計算結果を保持させるように記述しています。returnの後は、式を書くことができますので、このメソッドは直接的に次のように定義することもできます。

		int    square(   int    x  )  {
			return    x * x;
		}
		
もう1つの例を見てみましょう。2つの引数を受け取り、大きい方の数を返すメソッドを定義してみます。Lecture 7で出てきたif式を用いています。

		int 	greater(  int    x,   int   y ) {
			return   ( x > y ) ? x : y;
		}
		

図10-1 squareとgreaterの機能図

戻り値は1つしか記述できないのでしょうか?C言語系統のプログラミング言語は、メソッドは元々は関数的な取り扱いでしたので、戻り値は1つだけしか記述できません。もし、複数の値を戻したいのであれば、配列にしたり、オブジェクトの形に直す必要があります。 もし、戻り値を返す指定をした(たとえば、型をvoidではなくint と記述した)のに、return文がメソッドの定義の中に含まれていなかった場合はどうなるでしょうか?大丈夫です。Java言語のコンパイラがちゃんと見張っていて、「return文がないよ」というエラーメッセージを出してくれます。

★メソッドの呼出しと戻り値の評価

戻り値のあるメソッドを呼び出して使ってみましょう。次のように、代入文の式の中にメソッド呼出しを記述します。実引数の個数は、定義された仮引数の個数と同じ分だけ用意します。
▼戻り値のあるメソッドの呼出し
 変数 = メソッド呼出し( 実引数 );
もちろん、代入文など用意しないで、戻り値を受け取らなくても構いません。あるいは、メソッドの呼出しを複雑な式の中に埋没させても構いません。さて、それでは、先ほど定義したメソッドを、この書式に基づいて、呼び出して使うための記述をしてみましょう。変数を宣言しながら、代入を行なっています。

		int 	z = square( 30 );		// zには900が代入される
		int 	y = square( z + 20 );	// yには846400が代入される
		int 	w = greater( z,  y );	// wにも846400が代入される
		
他の自作のメソッドと同様に、アプリケーション(クラス)自身のメソッドであることを示すためには、次のようにthisをつけて呼び出すこともできます。

		int 	positive =  this.square(  -30 );
		
このような呼出しを行なった場合、実際には、どのような実行の順番になっているのでしょうか?簡単な例を用いて、少し、実行の様子を追ってみましょう。 メソッドの呼出しの記述:

		int 	result = square(  45 * 10 );	
			→45*10という式が評価されて、定数値450になり実引数としてsquareメソッドに渡されます。
		
メソッドの定義:

		int    square(   int    x  )  {   return    x * x ;   } 
			→squareが呼び出され、仮引数変数xに、実引数の定数値450が代入されます。
			→x * xと書かれた式の計算結果の定数値202500が戻り値として呼出し側に返送されます
		
メソッドの呼出し後の代入:

		int 	result = square(  45 * 10 );
			→square( 45 * 10 )の部分が、戻り値の定数値202500に置き替わります。
			→変数resultを宣言し、result = 202500;という代入文が実行されます。
		
このように、実行の制御が一度定義されたメソッドに移り、計算が行なわれた後、再び呼出し側に戻ってきました。呼出し側では、メソッドを呼び出した部分の記述が、戻り値に置き換えられます。

図10-2 squareメソッドの呼出しと戻り値の返送

★複雑なメソッドの呼出し

メソッドの呼出しは、式の中に記述できますので、実引数を示す式の中にも別のメソッドの呼出しを記述することができます。次のような複雑なメソッド呼出しでは、内側の実引数から順番に評価されていきます。従って、呼出しの順番もより内側に書かれているメソッドから行われることになります。以下の記述では、greater→square→printLineと実行されることになります。

		printLine(  "result:" +  square(  greater( 30,  40 ) - 10 ) )  );
		
実際に、この1文がどのように実行されるかを追ってみましょう。
1. greater( 30, 40 )が実行される→評価結果は40になる
2. square( 40 - 10 )が実行される→評価結果は900になる
3. printLine( "result: " + 900 );が実行される

★戻り値のあるメソッドの命名方法

メソッドの名前は、小文字から始まり、動詞が使われるという不文律がありました。また、動詞+名詞でも構わず、その場合は名詞の部分は大文字から始まりました。更に戻り値のあるメソッドでは、使われる動詞にも一般的な傾向があります。情報を返すメソッドでは、getという動詞が用いられます。また、論理値を返すメソッドでは、isという動詞が多く名前の中に使われています。タートル・グラフィックスにあるメソッドの名前を少しだけ列挙してみました。
情報を返す: getColor, getFont, getName, getKey, getX, getY, getImage
論理値を返す: isVisible, isTurtle, isMouseDown, isKeyDown, equals
自分で定義する際にも、メソッドに名前をつけるときにこのような慣習を守っておくと、後でプログラムを読み返したときにわかりやすいでしょう。

★論理値を返すメソッドの例

戻り値を返すメソッドの中でも、論理値を返すメソッドの場合は、if文やwhile文などの条件を記述する式に用いられます。たとえば、現在の時刻がお昼の12時から14時の間であることを判別するためのメソッドを記述してみます。プログラムの初心者ならば次のようにプログラムするでしょう。論理値を返すので、戻り値の型としてはbooleanを指定しています。名前は、慣習に従って、動詞のisをつけています。現在の時刻を24時間として時を変数hourに求めています。
		
		boolean  isLunchTime(  )   {
			int  hour = getHour( );
			if ( hour >= 12 && hour < 14 ) {
				return true ;
			} else {
				return false;
			}
		}
		

ところが、少し慣れてくると、論理型の変数に条件式が直接代入できることがわかります。 条件式は、trueかfalseに評価されますので、それを変数に代入して、その値を返してあげれば良いわけです。

		
		boolean  isLunchTime(  )   {
			int  hour = getHour( );
			boolean condition = ( hour >= 12 && hour < 14 );
			return condition;
		}
		

最後には、論理型の変数も使わずに、直接return文の戻り値を返す式のところに条件式を記述するようになります。


		boolean   isLunchTime(  )   {
			int  hour = getHour( );
			return hour >= 12 && hour < 14;
		}
		
return文の戻り値の式は、条件式になっています。このように記述すると、条件式が評価されて、論理値のtrueあるいはfalseが返されることになります。今度は、このメソッドをstartメソッドなどの他のメソッドの中から呼び出して記述するような場合を考えてみましょう。

		if  (  isLunchTime(  )  == true )  {
			printLine(  "It is lunch time!  Shall we have lunch?" );
		}
		
ところが、呼出し側でもこのように論理値と等しいかどうか比較しなくても、論理値そのものを返してくるのですから、if文の括弧の中に メソッドの呼出しだけを直接記述することができます。比較結果は、どうせtrueかfalseという論理値に評価されることを 思い出して下さい。

		if  (  isLunchTime(  )  )  {
			printLine(  "It is lunch time!  Shall we have lunch?" );
		}
		
このように論理値を戻り値として返すメソッドを用いる場合は、メソッドの呼出しを直接if文の条件式の中に記述して使います。

★実習10-1 greaterとlesser、averageを作る

大きい方の数を返すメソッド、小さい方の数を返すメソッド、2つの値の平均値を返すメソッドを それぞれ作成し、ユーザの入力に応じて表示させるようにしなさい。クラス名はTwoIntegersValidaterとします。

■戻り値のあるメソッドの有用例

★整数や実数を入力してもらう便利なメソッド

整数を入力してもらうのに、タートル・グラフィックスのライブラリでは、 displayInputDialogとconvertToIntegerなどのメソッドを組み合わせて、 整数用、長整数用と実数用にダイアログを出して入力してもらい、入力値を返すメソッドを定義しています。 デフォルトの入力値を0とするようなものを新たに作ってみましょう。


	int  displayIntegerDialog( String s ) {
		String  answer = displayInputDialog( s, "0" );
		return  convertToInteger( answer );
	}

	long displayLongIntegerDialog( String s ) {
		String  answer = displayInputDialog( s, "0" );  // Javaの長整数表記のLが要らないことに注意
		return  convertToLongInteger( answer );
	}

	double displayNumberDialog( String s ) {
		String answer = displayInputDialog( s, "0.0" );
		return  convertToNumber( answer );
	}
		

この3つのメソッドをstartメソッドから呼び出して使ってみます。


	public void start( ) {
		int apples = displayIntegerDialog( "リンゴの個数を入力して下さい" );
		long population = displayLongIntegerDialog( "地球の人口を入力して下さい" );
		double  radius = displayNumberDialog( "地球の赤道回りの円周を入力して下さい" );
	}
		

★カレンダー関係のメソッドの記述

カレンダー関係で必要なメソッドを記述してみましょう。 まず、引数に年を貰って閏年かどうかを論理値で返すメソッド(isLeapYear)です。 閏年ならtrueを、平年ならfalseを返します。

		
		boolean  isLeapYear( int  year ) {
			return   year % 400 == 0 || year % 4 == 0 && year % 100 != 0 ;
		}
		

次に、引数に年と月を貰って、その月の日数(整数)を返すメソッド(getMonthDays)です。 2月の場合は、isLeapYear( )を用いて閏年かどうかみています。 後は小の月の条件を論理式で記述しています。

		
		int   getMonthDays( int  year, int  month ) {
			if ( month == 2 ) {  if ( isLeapYear( year ) ) { return 29; } else { return 28; } }
			else if ( month%2==0 && month<8 || month%2==1 && month>8 ) { return 30; }
			else { return 31; }
		}
		

次のメソッドは、上記のgetMonthDaysメソッドを用いて、 指定された年(year)において、指定された月(month)の最初の日が始まるまでの 日数を求めています。


		int  countDays( int  year, int  month ) {
			int  countday = 0;
			for ( int  m=1; m < month; m++ ) { countday += getMonthDays( year, m ); }
			return countday;
		}
		

★実習10-2 ある年のある月のある日がその年の通算何日目かを求める

2008年は閏年ですね。2月は29日で終わります。 それを踏まえて、ある年のある月とその月の日の3つの値を 入力してもらうと、その年の1月1日から数えて何日目であるかを求めることができます。 大の月と小の月、しかも2月は閏年かどうかも計算にいれなければいけません。 CountTotalDaysというクラス名で作成し、コンパイルし、実行します。

以下のメソッドは、1900年から、指定された年(year)の最初の日が始まるまでの日数を求めています。 グレゴリオ暦は、1582年〜1700年ぐらいまででヨーロッパで導入されましたが、 アジア圏で導入されたのは、1873年の日本からです。 1900年ぐらいまでは、ユリウス暦からのいろいろな調整で、日付が結構変更されていますので、 1900年から日数を数えることに致しました。 isLeapYearメソッドを用いて、閏年なら366を、平年なら365を足し込んでいっています。

		
		int   countAllDays( int  year ) {
			int  countday = 0;
			for ( int  y=1900; y < year; y++ ) {
				countday += ( isLeapYear( y )  ) ? 366 : 365;
			}
			return  countday;
		}
		

最後は、引数に年と月を貰って、その月の第1日目が何曜日で始まるかを整数で返すメソッドです。 指定された年(year)・月(month)の始まる前の日が1900年1月1日から数えて何日目であるか求めて、 その月の始まる曜日を求めています。 0…日曜日〜6…土曜日という形で求まります。 countAllDaysとcountDaysメソッドを用いています。

		
		int  getStartWeekDay( int  year, int  month ) {
			int  everyday = countAllDays( year ) + countDays( year, month );
			return ( everyday + 1 ) % 7;  // 1を足しているのは、1900年1月1日が月曜だから
		}
		

★実習10-3 指定された年のある月、ある日の曜日を求める

1900年以降の年について、 入力された年、月、日について、その日の曜日を求めるようなプログラムを作成しなさい。 上記のgetStartWeekDayで入力された年と月が、1900年1月1日から、通算何日目であるかがわかります。 思考例として、2000年2000年1月1日から通算何日目を考えてみましょう。 たとえば、2003年の6月15日は、2000年が閏年なので366日、2001年と2002年が平年なので365日です。 2003年は平年なので、1月が31日、2月が28日、3月が31日、4月が30日、5月が31日あります。 そうすると、2003年6月15日は、2000年1月1日から数えて、366+365+365+31+28+31+30+31日と 加えて、6月の15日分になりますので、1262日目です。 2000年1月1日から数えていますので、何日経過したかといえば、-1して1261日経過しました。 これを1週間の7で割った余りを求めて下さい。1261%7→2になります。 それが曜日に対応する数値になります。 ちなみに2000年1月1日は、土曜日でしたから、この分も足さなければなりません。 日曜日=0,月曜日=1,火曜日=2,水曜日=3, 木曜日=4, 金曜日=5, 土曜日=6としています。 ですから、土曜日に対応する6を足します。(1+6)%7→0です。0は日曜日ということです。 CalculateWeekDayというクラス名で作成し、コンパイルし、実行します。 余裕のある人は指定された年の十二支も 表示するようにしてみなさい。ちなみに2000年は辰年でした。

☆課題10-1 カレンダーで必要なメソッドの記述

上記のメソッドを利用して、指定された年・月のカレンダーを表示するプログラムを作りなさい。 クラス名は、DisplayCompleteCalendarにて。


10−2.タートルと座標

■タートルの位置や角度の取得と設定

★タートルのgetAngle、getX、getYを使う

タートル・グラフィックスでは、現在タートルがどちらを向いているかを getAngleメソッドで、 現在タートルの中心がどこにいるかの位置を getXメソッドと getYメソッドで 求めることができます。これらは実数で返してくれます。 getIntegerXメソッドと getIntegerYメソッドは、 整数で返してくれます。 これらのメソッドは戻り値がありますので、次のように変数に代入したりして値を得ます。

		
		double  angle = turtle.getAngle( );
		double  x = turtle.getX( );
		double  y = turtle.getY( );
		int  x = turtle.getIntegerX( );
		int  y = turtle.getIntegerY( );
		

これらのメソッドは、タートルの状態を知るために非常に有用です。 なお、タートルの方向はマイナスで表されることもあります。以下の図を参照にして、現在タートルが どちらを向いているのかを把握してプログラムしてください。


図10-3 タートルの方向

以下のプログラムでは、タートルが現在どちらを向いているかを、それぞれの頂点にきたときに、 getAngleメソッドで求めて、それを文字端末(ターミナル)に表示するものです。 このようなタイミングで必要な情報を文字端末に表示させることをデバッグプリント(Debug Print)と 呼びます。 デバッガでいちいちプログラムを止めるよりも、必要な時点での変数やタートルの状態がわかり、 それが表示として残こっていきます。 そのため、多くのプログラマはデバッガを使うときは、よほど根拠が掴めない特殊な場合だけに限る ようにしています。 熟練プログラマは、デバッガを殆ど使わずに、デバッグプリント、つまりプログラム上のある地点に おける変数の値の移り変わりをトレースするだけで問題点を掴み、プログラムを修正していきます。


import sfc.turtle.TurtleFrame;
import sfc.turtle.Turtle;

public class SeaUrchin extends TurtleFrame{

	public static void main(String [ ]  args) { new SeaUrchin( ); }
	
	public void start( ) {
		Turtle  turtle = new Turtle( this );
		turtle.rotate( 6 );
		for ( int i=1; i <= 18 ; i++ ) {
			turtle.forward( 30 );
			printLine( i + "番目の外頂点での角度" + turtle.getAngle( ) );
			turtle.rotate( 168 );
			turtle.forward( 30 );
			printLine( i + "番目の内頂点での角度" + turtle.getAngle( ) );
			turtle.rotate( -168 + 360 / 18 );
		}
	}
}
		

☆課題10-2 うに型を描くプログラム

上記の「うに」のような図形は、棘が18本でしたが、これをユーザが入力できるようにしてみなさい。 値としては、3〜60までの整数値の範囲で受け取るように入力をガードしなさい。 クラス名はSeaUrchinDrawerとします。

★タートルの位置の設定

タートルは、次のような2つのメソッドで直接ウィンドウの特定の座標に置くことができます。

  setLocation( x座標, y座標 )
  moveTo( x座標, y座標 )

両者の違いは、setLocationがアニメーションを伴わないで、一瞬その位置に置かれるのに対して、 moveToは、タートルが目的地にまず回転して、それから歩いて移動するアニメーションを伴います。 ただし、ペンを上げていないと、初期の位置から、その座標まで軌跡が引かれることになります。 ウィンドウの座標は、左上の端がx座標0, y座標0で、x座標は右側にプラスになるのですが、 「y座標は下方向がプラスになる」という特性を持っています。 ほとんどのウィンドウのプログラミングができるライブラリにおいて、同じような座標系を 持っています。y座標の方向が違うことに注意して下さい。

★配列の値に応じて軌跡を残すタートル

次のプログラムは、配列の初期値を代入して、タートルを目的の座標に向かわせるものです。 x座標の配列とy座標の配列を持っています。ただし、x座標の配列の要素の値が -10ならばpendown、-20ならばpenupをさせるようにしています。 startメソッドの中だけを記述します。


	public void start(  ) {
		int  tx [ ] = { 100, -10, 300, 300, 220, 220, 180, 180, 100, 100, -20,
			320, -10, 360, 360, 440, 480, 390, 480, 440, 360, 360, 320, 320 };
		int  ty [ ]  = { 100, -10, 100, 140, 140, 300, 300, 140, 140, 100, -20,
			100, -10, 100, 180, 100, 100, 200, 300, 300, 220, 300, 300, 100 };
			
		Turtle  turtle = new Turtle( this );
		
		turtle.setPenColor( Blue );
		turtle.penup( );  // 最初はペンを上げた状態にしておきます
		turtle.moveTo( tx[ 0 ], ty[ 0 ] );  // 最初の位置に移動します。
		for ( int i=1; i < tx.length ; i++ ) {
			if ( tx[ i ] == -10 ) { turtle.pendown( ) ; }  //-10だったら、ペンを下ろす
			else if ( tx[ i ] == -20 ) { turtle.penup( ) ; }  //-20だったら、ペンを上げる
			else{  turtle.moveTo( tx[ i ],  ty[ i ] ); } // それ以外は要素の示す座標へ移動する
		}
	}
		

☆実習10-4 配列で軌跡を残すプログラム

上記のプログラムは、下記の図のようにTKと描くようにしました。他にも直線的なアルファベット (AEFHILMNVWXYZ)を描くように配列の初期値を修正してみてください。 クラス名はPractice1004とします。

★矩形を描く便利なメソッド

タートルグラフィックスでは、4角形や角の取れた4角形を描くのに非常に苦労します。そこで、 使い捨てのタートルを使って(軌跡を残すためだけにしか使われない)、矩形(4角形のこと)を描く 便利なメソッドを定義してみましょう。左上のx, y座標と、幅(width)と高さ(height)の4つの引数を 渡すようにします。 下記のメソッドの記述で、hideTurtle は、軌跡は残して、タートルだけは表示しないという機能を実行するメソッドです。


	void drawRectangle( int x, int y, int width, int height ) {
		Turtle  turtle = new Turtle( this );
		turtle.penup( );
		turtle.setLocation( x, y );
		turtle.pendown( );
		int i=1;
		while (  i <= 4  ) {
			turtle.rotate( 90 );
			if ( i % 2 == 1 ) { turtle.forward(  width ) ; }  // 奇数だったら幅分だけ移動
			else{  turtle.forward( height ); } // 偶数だったら高さ分だけ移動
			i = i + 1;
		}
		turtle.hideTurtle( );
	}
		

これでは、黒の矩形しか描きませんので、java.awt.Colorクラスを使って、色の指定ができるように します。そうすると、上記のメソッドは次のように記述することができます。Javaでは同じ名前のメソッドでも、 引数の数が違えば、シグネチャが異なりますので、違うメソッドとして認識してくれます。


	void drawRectangle( int x, int y, int width, int height, java.awt.Color c ) {
		Turtle  turtle = new Turtle( this );
		turtle.penup( );
		turtle.setLocation( x, y );
		turtle.setPenColor( c );
		turtle.pendown( );
		int i=1;
		while (  i <= 4  ) {
			turtle.rotate( 90 );
			if ( i % 2 == 1 ) { turtle.forward(  width ) ; }  // 奇数だったら幅分だけ移動
			else{  turtle.forward( height ); } // 偶数だったら高さ分だけ移動
			i = i + 1;
		}
		turtle.hideTurtle( );
	}
		

これまでは枠だけでしたが、色が指定できるなら、塗りつぶしたくなるのが、人の心情ってぇものです。 塗りつぶすメソッドを記述してみましょう。


	void fillRectangle( int x, int y, int width, int height, java.awt.Color c ) {
		Turtle  turtle = new Turtle( this );
		turtle.penup( );
		turtle.setLocation( x, y );
		turtle.setPenColor( c );
		turtle.rotate( 90 );
		turtle.pendown( );
		int i=1;
		while (  i <= height  ) { // 高さ分だけ繰返しを実行
			 turtle.forward( width ); // 横線で描画していきますので、幅分だけ移動
			 if ( i % 2 == 1 ) { turtle.rotate( 90 ); }  // 奇数回は時計回り、偶数回は半時計回り
			 else{ turtle.rotate( - 90 ); }  
			 if ( i < height ) {
			 	turtle.forward( 1 ); // 1ドット下に降ります
				if ( i % 2 == 1 ) { turtle.rotate( 90 ); } else{ turtle.rotate( - 90 ); }
			 }
			 i = i + 1;
		}
		turtle.hideTurtle( );
	}
		

さて、次はボタンなどに使われるちょっと角の取れた矩形を描くようにします。角の丸みをどれくらいに するかを併せて指定するようにします。丸みを帯びた角は、10度ずつ9回、90度分の円弧を描くようにしています。


	void drawRoundRectangle( int x, int y, int width, int height, int r, java.awt.Color c ) {
		Turtle  turtle = new Turtle( this );
		turtle.penup( );
		turtle.setLocation( x, y );
		turtle.setPenColor( c );
		turtle.rotate( 90 );
		turtle.forward( r );
		turtle.pendown( );
		int i=1;
		while (  i <= 4  ) {
			if ( i % 2 == 1 ) { turtle.forward( width - 2 * r ); }  // 左右の丸み分だけ移動距離は少なくなります
			else {  turtle.forward( height - 2 * r ); } // 上下の丸み分だけ移動距離は少なくなります
			int j=1;
			for (  j <= 9 ) { // 角の丸みを描いています
				turtle.rotate( 10 );
				turtle.forward( 0.5 * r * Math.PI / 9 );
				j = j + 1;
			}
			i = i + 1;
		}
		turtle.hideTurtle( );
	}
		

これらのメソッドをstartメソッドから呼び出して使ってみます。


	public void start( ) {
		drawRectangle( 10, 10, 80, 40 );
		drawRectangle( 100, 10, 80, 40, Red );
		fillRectangle( 190, 10, 80, 40, Magenta );
		drawRoundRectangle( 280, 10, 80, 40, 6, Blue );
	} 
		

☆課題10-3 グラデーションで矩形を塗りつぶすメソッド

矩形を塗り潰すときは、同じ色で塗りつぶしましたが、これを、白から赤へのグラデーション (Colorオブジェクトを作るときに赤色成分だけを変化させる)で塗りつぶすようなメソッドを作ってみなさい。 1つの横線を描くたびに色を変えていきます。余裕がある人だけがやってください。 クラス名はGradationDrawerとします。

☆課題10-4 カラーテーブルを作ってみる

矩形(四角形)領域を軌跡で塗りつぶすプログラムを利用して、緑色は0で構いませんから、青色、赤色をそれぞれ、 0から240まで16ずつ変化させながら、格子を描くプログラムを記述して見なさい。格子を描く開始位置を指定するのには、 setLocation( ) メソッドを使っても良いでしょう。クラス名は、ColorTableとします。 以下の記述を参照しなさい。


		turtle.setPenColor(  new java.awt.Color( red, 0, blue ) );   // 軌跡の色をred, 0, blueの3原色で混色される色にする。
		

★円を描くメソッド

次は中心のx, y座標と半径、および色を指定して、円(正確には正60角形)を描画するメソッドを 定義してみましょう。


	public void drawCircle( int x, int y, int r, java.awt.Color c ) {
		Turtle  turtle = new Turtle( this );
		turtle.penup( );
		turtle.setLocation( x, y );   // まず中心座標に行って
		turtle.forward( r );  // 半径分だけ上に登ります
		turtle.rotate( 90 );  // そこで右に向いて
		turtle.setPenColor( c ); // ペンの色を設定して
		turtle.pendown( ); // 描き始めです
		int i=1;
		while (  i <= 60 ) {
			turtle.forward( 2 * Math.PI * r / 60 );  // 1回分の進む距離を求めています
			turtle.rotate( 6 );
			i = i + 1;
		}
		turtle.hideTurtle( );
	}
		

円は、必ず中心を指定するだけはなく、矩形と同じように左上の座標を指定する場合があります。 正方形に内接する円(円弧がちょうど正方形の各辺のどこかに触れる円)は一意に指定できます。 このことを利用して、左上のx, y座標と直径、および色を指定して、円を描画するメソッドを定義して みましょう。半径が直径になった以外は、単に最初に行く場所の記述の式が違うだけです。はい。


	public void drawCircleSquare( int x, int y, int size, java.awt.Color c ) {
		Turtle  turtle = new Turtle( this );
		turtle.penup( );
		turtle.setLocation( x+size/2, y+size/2 );
		turtle.forward( size/2 );
		turtle.rotate( 90 );
		turtle.setPenColor( c );
		turtle.pendown( );
		int i=1;
		for (  i <= 60 ) {
			turtle.forward( Math.PI * size / 60 );
			turtle.rotate( 6 );
			i = i + 1;
		}
		turtle.hideTurtle( );
	}
		

円を塗りつぶしてみましょう。これは、中心から、1ドットずつ移動して、同心円を描いていきます。 うまく塗りつぶせるでしょうか。


	public void concentricCircle( int x, int y, int r, java.awt.Color c ) {
		Turtle  turtle = new Turtle( this );
		turtle.penup( );
		turtle.setLocation( x, y ); // まずは中心座標に行き
		turtle.setPenColor( c );
		turtle.pendown( );
		int i=1;
		while (  i <= r ) { // 1ドット登らせます
			turtle.forward( 1 );
			turtle.rotate( 90 );
			int j=1;
			while (  j <= 60  ) { // 円(同心円)を描かせます
				turtle.forward( 2 * Math.PI * i / 60 ); // 1回に進む距離は中心からの離れぐらいで異なります
				turtle.rotate( 6 );
				j = j + 1;
			}
			turtle.rotate( -90 );
			i = i + 1;
		}
		turtle.hideTurtle( );
	}
		

塗りつぶしをするものがいま一つな感じがあります。やはり、三角関数と逆三角関数を用いて横の長さを計算して、 矩形のときのように横に線を引いて塗りつぶしていきましょうか。そうしたものが以下のメソッドになっています。


	public void fillCircle( int x, int y, int r, java.awt.Color c ) {
		Turtle  turtle = new Turtle( this );
		turtle.penup( );
		turtle.setLocation( x, y );   // まずは中心座標に行き
		turtle.forward( r ); // 半径分だけ縦に登らせます
		turtle.setPenColor( c );
		turtle.rotate( 90 );
		int i=1; 
		while ( i <= 2 * r ) {
			double size = Math.sin( Math.acos( (r-i) / (double)r ) ) * r ;  // この式は横の長さを求めています
			turtle.forward( size );  // 右側に移動しておき
			turtle.rotate( 180 );  // くるりときびすを返します
			turtle.pendown( );
			turtle.forward( size * 2);  // この左側への移動で横線を描画します
			turtle.penup( );
			turtle.rotate( 180 );
			turtle.forward( size );  // 左端から、また真ん中に戻ってきます
			turtle.rotate( 90 );
			turtle.forward( 1 );  // 1ドット分下に降ります
			turtle.rotate( -90 );
			i = i + 1;
		}
		turtle.hideTurtle( );
	}

		

上記の4つの円を描くメソッドをstartメソッドから呼び出して使ってみます。


	public void start( ) {
		drawCircle( 60, 60, 40, Red );
		drawCircleSquare( 120, 20, 80, Magenta );
		concentricCircle( 260, 60, 40, Orange );
		fillCircle( 360, 60, 40, Blue );
	}
		

☆課題10-5 グラデーションで円を塗りつぶすメソッド

円を塗り潰すときは、同じ色で塗りつぶしましたが、これも、中心が白で外側に行くに従って赤へ変化するグラデーション (Colorオブジェクトを作るときに赤色成分だけを変化させる)で塗りつぶすようなメソッドを作ってみなさい。 1つの同心円を描くたびに、色を変えながら描画していきます。余裕がある人だけがやってください。 クラス名はGradationCircleとします。

☆課題10-6 サインカーブを円で描く

タートルを複数作ります。1つのタートルは、forwardとrotateで円を描かせるようにします。もう1つのタートルは、 円を描いているタートルのy座標をgetIntegerY( )メソッドで取り出して、moveToメソッドで、 向かうべきx座標とy座標を指定してサインカーブを描かせるようにします。 また、サインカーブを描くタートルは、そのx座標をgetIntegerX( )メソッドで取り出せますので、繰返しの際に それに一定の値を足していきます。クラス名はDrawSineとします。

■タートルの画面について

★タートルのあるウィンドウの幅と高さを調べたり、設定したりする

プログラム中から、ウィンドウの幅(Width)や高さを調べたり、設定するのには、つぎの メソッドが用意されています。すべて、整数で値を返したり、引数を指定したりします。 タートルの方は実数なので違います。注意して下さい。

	int  width = getWidth( );  // ウィンドウの幅を得る
	setWidth( width * 2 ); // 現在の幅の2倍の幅にする
	setHeight( 400 ); // ウィンドウの高さを400に固定する
		

★タートルのウィンドウの背景色を変える

タートルのウィンドウの背景色を変えるのには、 setBackgroundColorメソッドが使えます。

	setBackgroundColor( Red );  // 赤に設定する
	setBackgroundColor( new java.awt.Color( 244, 20, 128 ) );  // 3原色を指定して
		

★タートルの速さを設定する

プログラム中で、必要なときだけ、タートルの動きを待たないようにさせることができます。 これには、setWaitTurtleメソッドが使えます。引数として、論理値のtrueとfalseが使えます。 trueにすると、通常の速度(Normal)で実行します。falseにすると、描画時間を待たない(No Wait)で 実行することになります。

	setWaitTurtle( false );  // タートルを待たないようにする
	fillRectangle( 10, 100, 200, 40 ); // Lecture 8で作った矩形塗りつぶしメソッドを呼ぶ
	setWaitTurtle( true );  // この後は通常の速度で
		

★実習10-5 タートルウィンドウの背景をグラデーションで塗りつぶす

最後にこれらのメソッドを用いて、下半分を地面に、グラデーションで上半分を空にしてみるプログラムを書いてみましょう。

import sfc.turtle.TurtleFrame;
import sfc.turtle.Turtle;

public class SkyAndGround extends TurtleFrame {

	public static void main(String [ ]  args) { new SkyAndGround( ); }
	
	public void start( ) {
		setWidth( 800 );
		setHeight( 400 );
		setWaitTurtle( false );
		setBackgroundColor( new java.awt.Color( 200, 128, 80 ) );
		Turtle  turtle = new Turtle( this );
		int halfheight = getHeight( ) / 2;
		turtle.penup( );
		turtle.setLocation( 0, halfheight );
		turtle.rotate( 90 );
		turtle.pendown( );
		for ( int i=0; i <= halfheight ; i++ ) {
			turtle.setPenColor( new java.awt.Color( 0, 255-(255*i/halfheight), 255 ) );
			turtle.forward( getWidth( ) );
			turtle.rotate( (i % 2 == 0) ? -90 : 90 );
			turtle.forward( 1 );
			turtle.rotate( (i % 2 == 0) ? -90 : 90 );
		}
		turtle.hideTurtle( );
	}
}
		

10−3.オブジェクトの配列

■Java言語上のオブジェクトの配列

オブジェクトを使って関連する情報をまとめあげることは本当に風に役に立つのでしょうか?配列を使った場合を考えてみると、それが顕著になります。たとえば、点の配列を考えてみましょう。オブジェクトを使わなければ、各点のx座標やy座標を別々の配列として持たなければなりませんでした。しかし、Turtleクラスのオブジェクトを使えば、オブジェクトの配列を用意すれば、タートルの中心座標各点を直接表すことができます。x座標やy座標は、配列の要素であるオブジェクトのメソッドを呼び出せば求めることができます。

★オブジェクトの配列の宣言

オブジェクトの配列を宣言するときは、次のように型名の替わりにクラス名を用います。上の方の書式は、オブジェクトの配列を宣言するだけです。下の方の書式は、オブジェクトの配列の実体を用意するものです。
▼オブジェクト配列の宣言の書式
 クラス名 配列名 [ ];    あるいは
 クラス名 [ ] 配列名;
同じように配列の領域を確保するのは次のように記述します。
▼データ領域を確保する書式
 配列名 = new クラス名[ サイズ ];
配列のサイズとは、データをいれる要素の個数を示します。整数式で記述します。たとえば、先ほどの2つの配列変数aとbにそれぞれデータ領域を確保してみましょう。

	a = new  String[  10   ];		// 配列 aに、10個のStringのデータ領域を確保する
	b = new  Color[  5  ];		//  配列 bに、5個のColorのデータ領域を確保する
	
もちろん、通常の変数と同様に、データ領域を確保しながら宣言することもできます。
▼宣言と共にデータ領域を確保する書式
 型名 配列名 [ ] = new 型名[ サイズ ];   あるいは
 型名 [ ] 配列名 = new 型名[ サイズ ];
たとえば、次の2つの配列は、それぞれカラーとボタン用の配列となります。

	Color    colors  [ ]  =  new   Color[ 20 ];		//  20個のカラーオブジェクト用の配列
	Turtle  turtles [  ]  =  new   Turtle[ 10 ];	//  10個のタートルオブジェクト用の配列
		
後は、通常の配列のように用いることができます。配列の要素を指定する場合は、通常のオブジェクトを参照している変数のように用いることができます。

★オブジェクトの配列の要素は更に生成しないといけない!

ところがどっこい、オブジェクトの配列は、要素のオブジェクトまでは生成してくれません。 そのため、各要素を更に、new演算子を使って生成しなければなりません。 次のプログラムの断片は、各要素のオブジェクトを個別に生成したり、繰返しを使ってオブジェクトを生成する方法を示しています。

		colors[  3  ]  =  new  Color(  10, 30, 40 );		// 3番目の要素にカラーオブジェクトを生成
		for (  int  i = 0;  i < turtles.length; i++ ) {		// 配列のすべてのタートルに対して
			turtles[ i ]  =  new  Turtle( this);	// 個々の要素にタートルオブジェクトを生成
			turtles[ i ].penup( );
			turtles[ i ].setLocation( 20, i*40+20 ) 	//  アプレットの画面上に配置
		}
		turtles[  3 ].setTurtleColor(  colors[ 3 ] );  // 3番目のタートルのカラーを変える
		
上の例をみてもわかるように、オブジェクトの配列は、オブジェクトそのものを生成したのではありません。new文では配列を1つ生成にしただけに過ぎません。ですから、配列の要素となる個々のオブジェクトは、その都度繰返しなどを使ってnew文を使ってそれぞれを生成していく必要があります。これが、通常の整数などの配列と異なる点です。
<<Previous Lecture >>Next Lecture