11/15の課題


問題

以下の要素を備えたプログラムを作成しなさい.

解答例(1)

/* XXXXXX XXXXX
 オセロをつくってみました。対人戦専用です。64個のマスをそのマスの状態と位置の
 フィールドを持つクラスとして定義し,ベクトル型の配列にしました。駒はgimpで描き
 ました、かなり単純な絵ですが。マウスをクリックした場所に駒が表示されます。rで
 再描画,cで色のチェンジ,qで終了です。細かい説明は以下のプログラム中に書きまし
 た。*/


 //AWTを使うため
import java.awt.*;      
 //イベント駆動関係のクラスを用いるため
import java.awt.event.*;
//Vectorクラスを用いるため
import java.util.*;      



//各マスを表すクラス
class Cell{              
  public int state;      //色と駒の有無(0は空,1は白,2は黒)
  public int sx, sy;     //各マスの左上のx,y
  //コンストラクタ
  public Cell(int x, int y, int s){
    sx = x;
    sy = y;    
    state = s;
  }
}


public class Kadai1115 extends Frame implements KeyListener,MouseListener{
  Image image;              //オフスクリーンイメージ
  static Vector cellArray;  //Cell型のオブジェクトの配列
  static int color = 1;     //最初は白

  //コンストラクタ
  public Kadai1115(){          
    super("Kadai1115");
    //GUI部品とEvent Listenerを関連づける
    addKeyListener(this);   
    addMouseListener(this);     
  }

  public static void main(String[] args){
    Kadai1115 frame = new Kadai1115(); //Kadai1115のインスタンス
    cellArray = new Vector();
    frame.setSize(800,800);
    int i;
    //マスの作成。左上から順に0,1,2,...,63
    for(i=0;i< 64;i++){
      int x = i%8*70+60;
      int y = (int)(i/8)*70+60;
      int s = 0;                       //空の状態
      if(i==27 || i==36)
        s=1;                           //色を白に(初期配置の2つ)
      if(i==28 || i==35)
        s=2;                           //色を黒に(初期配置の2つ)
      cellArray.addElement(new Cell(x,y,s)); //配列の要素を増やす
    }
    frame.setVisible(true);      //表示
  }

  public void update(Graphics g){
    int i;
    //オフスクリーンイメージの作成
    if(image==null){    
      image=createImage(800,800);        
      Graphics off = image.getGraphics();
      //背景の作成
      off.setColor(new Color(100,100,30));
      off.fillRect(0,0,800,800);
      //盤の作成
      off.setColor(new Color(20,200,20));
      off.fillRect(40,40,600,600);
      off.setColor(Color.black);
      off.drawRect(60,60,560,560);
      for(i=1;i< 8;i++){
        off.drawLine(i*70+60,60,i*70+60,620);
        off.drawLine(60,i*70+60,620,i*70+60);
      }
      //操作の説明を表示
      off.setColor(Color.yellow);
      off.setFont(new Font("Monospaced", Font.BOLD, 15));
      off.drawString("r:repaint", 660, 620);
      off.drawString("c:change color", 660, 640);
      off.drawString("q:end game", 660, 660);
    }
    g.drawImage(image,0,0,this);  //Imageの内容を描画
    //現在の色を表示
    g.setColor(Color.yellow);
    g.setFont(new Font("Monospaced", Font.BOLD, 20));
    String str = "";
    if(color==1) str = "color = white";
    if(color==2) str = "color = black";
    g.drawString(str, 660, 60);

    Image black = Toolkit.getDefaultToolkit().getImage("/home/g040236/java/black.gif");
    Image white = Toolkit.getDefaultToolkit().getImage("/home/g040236/java/white.gif"); 
    int whiteNum=0, blackNum=0; //黒,白の駒の個数
    boolean space = false;      //空のマスの有無
    //駒の表示
    for(i=0;i< 64;i++){
      Cell c = (Cell)cellArray.elementAt(i);
      switch(c.state){
      case 1:
        g.drawImage(white, c.sx+3, c.sy+3, this);
        whiteNum++;
        break;
      case 2:   
	g.drawImage(black, c.sx+3, c.sy+3, this);
        blackNum++;
      	break;
      default:
        space = true;
        break;
      }  
    }
    //空のマスが無いなら勝敗判定
    if(space==false){
      g.setFont(new Font("Monospaced", Font.ITALIC, 100));
      g.setColor(Color.orange);
      if(whiteNum < blackNum){
        g.drawString("Black won.", 90,290);
      }else if(whiteNum > blackNum){
        g.drawString("White won.", 90, 290);
      }else{
        g.drawString("Draw.", 90, 290);
      }
    }
  } 

  public void keyPressed(KeyEvent e){
    int key = e.getKeyChar();
    if(key=='q') System.exit(0);  //終了
    if(key=='r') repaint();       //再描画
    if(key=='c'){                 //色のチェンジ
       color = color%2 + 1;
       repaint();
    }    
  }
  public void keyReleased(KeyEvent e){}
  public void keyTyped(KeyEvent e){}
  public void mouseClicked(MouseEvent e){
    //押された場所の座標を得る
    int mx=e.getX();
    int my=e.getY();
    if(mx>=60 && mx<=620 && my>=60 && my<=620){ //盤外なら無視
      int tmp,tmpX,tmpY;      
      tmpX = (int)((mx-60)/70);   //左からtmpX(0-7)番目のマス
      tmpY = (int)((my-60)/70);   //上からtmpY(0-7)番目のマス
      tmp = tmpX+tmpY*8;          //押されたマスの番号(0-63)
      Cell b = (Cell)cellArray.elementAt(tmp);
      if(b.state==0){     //押された場所が空でないなら無視
	b.state= color;
                 
	//ここから引っくり返す作業
	turn(tmp, Math.min(tmpX, tmpY), -9);    //左上
	turn(tmp, tmpY, -8);                    //上
	turn(tmp, Math.min(7-tmpX, tmpY), -7);  //右上
	turn(tmp, tmpX, -1);                    //左
	turn(tmp, 7-tmpX, 1);                   //右
	turn(tmp, Math.min(tmpX, 7-tmpY), 7);   //左下
	turn(tmp, 7-tmpY, 8);                   //下
	turn(tmp, Math.min(7-tmpX, 7-tmpY), 9); //右下
	color = color%2 + 1;   //次の人の番だから色チェンジ
	repaint();
      }
    }
  }
  /*引っくり返すためのメソッド。引数は駒が置かれたマスの番号,置か
    れた駒から各8方向へ行ったときのマスの数,次のマスとの番号の差。
    置かれた駒に近い方のマスから順にstateを判断。 */
  public static void turn(int cellNum, int m, int n){
    Cell c = (Cell)cellArray.elementAt(cellNum);
    int k,l;
    for(k=1; k<=m; k++){
      Cell d = (Cell)cellArray.elementAt(cellNum+k*n);
      if(d.state == 0){
        break;             //空なら判断終了
      }else if(d.state != c.state){
        continue;          //色が違う限り続ける
      }else if(d.state == c.state){  //同じ色なら間の駒を裏返す
        for(l=1; l< k; l++){
          Cell e = (Cell)cellArray.elementAt(cellNum+l*n);
          e.state = color;
	}
	break;
      }     
    }
  }
  public void mouseEntered(MouseEvent e){}
  public void mouseExited(MouseEvent e){}
  public void mousePressed(MouseEvent e){}
  public void mouseReleased(MouseEvent e){}
}

解答例(2)

//XXXXX XXXXXX
//スライドパズルのプログラム

import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.io.*;


class Kadai1115 extends Frame implements KeyListener, MouseListener{ 
  Image MainImage;
  Vector ImageArray;  //セルに分割されたイメージの配列
  int[] CellArray;//セルの位置記憶用の配列
  int CellCount=4;//分割数
  boolean CellPlus=false;
  boolean Completed=true;
  boolean CellMove=false;
  int NullCell;//欠けているセルの初期位置(右下隅)
  int Counts=0;//|クリックした回数
  Image offScreenImage;
  Graphics offScreenGraphics;

  //コンストラクタ
  public Kadai1115(String title,String FileName){
    super(title);
    ImageArray=new Vector();
    //画像ファイルの読み込み
    if(FileName.equals("")){
      //デフォルト画像は田中先生の顔
      MainImage = Toolkit.getDefaultToolkit().getImage("/home/g040252/java/tanaka2.jpg");
    }else{
      MainImage = Toolkit.getDefaultToolkit().getImage(FileName);
      if(MainImage==null){System.out.println("null");}
    }
    addKeyListener(this);
    addMouseListener(this);
    repaint();
  }

  public static void main(String args[]) throws IOException{
    BufferedReader d=new BufferedReader(new InputStreamReader(System.in));
    String FileName;
    System.out.println("***スライドパズル***");
    System.out.println("画像ファイル名を入力してください(相対パス化。入力しなくても構いません。)>");
    FileName=d.readLine();    
    System.out.println("しばらくお待ち下さい.....");
    Kadai1115 frame = new Kadai1115("Kadai1115",FileName);
    frame.setSize(420,420);
    frame.setVisible(true);
  }

  //絵を分割するメソッド
  void bunkatu(){
    Image image;//一時的保存用のイメージ
    Graphics gi;
    int w=420/CellCount;//セルの幅
    int n,m;
    NullCell=CellCount*CellCount-1;//欠けているセルの位置
    CellArray = new int[CellCount*CellCount];
    ImageArray.clear();
    for(n=0;n< CellCount*CellCount;n++){
      CellArray[n]=n;
      image=createImage(w,w);
      gi=image.getGraphics();
      if(n!=NullCell){
	m=(int)(n/CellCount);
	//メインイメージの切り取り
	gi.drawImage(MainImage,0,0,w,w,(n-m*CellCount)*w,m*w,(n-m*CellCount+1)*w,(m+1)*w,this);
      }else{
	gi.clearRect(0,0,w,w);
      }
      gi.setColor(Color.red);
      gi.drawRect(0,0,w,w);
      ImageArray.addElement(image);
    }
    Completed=false;
    image=null;
  }


  public void update(Graphics g){
    if(offScreenImage==null){
      offScreenImage=createImage(600,600);
      offScreenGraphics=offScreenImage.getGraphics();
    }
    paint(offScreenGraphics);
    g.drawImage(offScreenImage,0,0,this);
  }


  public void paint(Graphics g){
    g.setColor(Color.black);
    g.fillRect(0,0,600,600);
    g.setColor(Color.white);
    g.setFont(new Font("Courier",Font.BOLD,15));
    g.drawString("Click Right To Shuffle:  Click Left To Move:  Press Q To Quit",10,40); 

    if(Completed==true){
      //完成時は一枚画像を表示
      g.drawImage(MainImage,50,50,this);
      g.setColor(Color.black);
      g.fillRect(470,50,130,550);
      g.fillRect(50,470,550,130);
      g.drawRect(50,50,420,420);
      g.setColor(Color.red);
      g.drawRect(50,50,420,420);
      g.setFont(new Font("Courier",Font.BOLD,60));
      g.setColor(Color.white);
      g.drawString("Completed",100,400);       
      g.setColor(Color.black);
      g.drawString("Completed",103,403);       

    }else{
      //未完成時
      int n,m,l;
      int w=420/CellCount;
      for(n=0;n< CellCount*CellCount;n++){
	m=(int)(n/CellCount);
	g.drawImage((Image)ImageArray.elementAt(CellArray[n]),(n-m*CellCount)*w+50,m*w+50,w,w,this);	
      }
      g.drawString("Counts:"+Counts,10,500);
    }

  }


  public void mouseClicked(MouseEvent e){
    int n,m,w;
    int MobileCell[] = new int[4];
    int ClickedCell;

    if(e.getModifiers()==e.BUTTON1_MASK){
      //左クリックでセル移動

      //クリックしたセルを取得
      w=420/CellCount;
      ClickedCell=(int)((e.getY()-50)/w)*CellCount+(int)((e.getX()-50)/w);
      //回りに空いているセルがあれば移動
      MobileCell=GetAroundCell(ClickedCell);
      for(m=0;m<4;m++){
	if(MobileCell[m]>=0){
	  if(CellArray[MobileCell[m]]==NullCell){
	    CellArray[MobileCell[m]]=CellArray[ClickedCell];
	    CellArray[ClickedCell]=NullCell;
	    Counts++;
	    break;
	  }
	}
      }
      repaint();

      //完成かどうか判断
      for(n=0;n< CellCount*CellCount;n++){
      if(CellArray[n]!=n){
	  break;
	}
      }
      if(n==CellCount*CellCount){
	Completed=true;
	repaint();
      }
      
    }else if(e.getModifiers()==e.BUTTON2_MASK){
      System.out.println("BUTTON2");

    }else if(e.getModifiers()==e.BUTTON3_MASK){
      //右クリックで分割,シャッフル
/
      //分割数を変えて分割
      if(CellCount==5 || CellCount==3){
	CellPlus=!CellPlus;
      }
      if(CellPlus==true){
	CellCount++;
      }else{
	CellCount--;
      }
      bunkatu();
      Counts=0;
      //シャッフル
      int BrankCell=NullCell;
      for(n=0;n< CellCount*CellCount*10;n++){
	MobileCell=GetAroundCell(BrankCell);
	m=(int)(Math.random()*4);
	while(MobileCell[m]<0){
	  m=(int)(Math.random()*4);
	}	
	CellArray[BrankCell]=CellArray[MobileCell[m]];
	CellArray[MobileCell[m]]=NullCell;
	BrankCell=MobileCell[m];
      }

      repaint();
    }
  }

  //Cellのまわりのセル番号を配列として返すメソッド
  int[] GetAroundCell(int Cell){
    int AroundCell[]=new int[4];
    int m;
    for(m=0;m<4;m++){
      AroundCell[m]=-1;
    }
    //上
    if(Cell>=CellCount){
      AroundCell[0]=Cell-CellCount;
    }
    //右
    if(Cell%CellCount!=CellCount-1){
      AroundCell[1]=Cell+1;
    }	  
    //下
    if(Cell< CellCount*(CellCount-1)){
      AroundCell[2]=Cell+CellCount;
    }
    //左
    if(Cell%CellCount!=0){
      AroundCell[3]=Cell-1;
    }
    return AroundCell;
  } 

  public void mouseEntered(MouseEvent e){}
  public void mouseExited(MouseEvent e){}
  public void mousePressed(MouseEvent e){}
  public void mouseReleased(MouseEvent e){}

  public void keyPressed(KeyEvent e){
    int key=e.getKeyChar();
    if(key=='q') System.exit(0);
  }
  public void keyReleased(KeyEvent e){}
  public void keyTyped(KeyEvent e){}
}

解答例(3)

XXXXXX XXXXXです。

11月15日の課題

起動方法
 % java Kadai1115 &

画面をクリックするとりんごの絵が現れます。絵をドラッグして移動できます。
画面の何もないところをドラッグすると好きな大きさ(と縦横比)のりんごがか
けます。bのキーを押すと、りんごの代わりにバナナが出るようになります。a
を押すと再びりんごに戻ります。qを押すと終了します。

反応が鈍いのはどうしたものか。

時間があるときにやってみたいこととしては、
 - 絵のサイズを後から変更できるようにする(隅をドラッグして)
 - 絵の重なりの順番を変更できるようにする
など。

===以下、コード (Kadai1115.java)

import java.util.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;

class ClipArt extends Rectangle
{
  int imageInd; // 表示するイメージは Kadai1115.images[this.imageInd]

  ClipArt(int x, int y, int w, int h, int ind)
  {
    super(x, y, w, h);
    imageInd = ind;
  }

  ClipArt(Rectangle r, int ind)
  {
    super(r);
    imageInd = ind;
  }

  int getImageInd()
  {
    return imageInd;
  }
}

class Kadai1115 extends Frame
{
  Image offScreenImage; // 表示用オフスクリーンイメージ

  public Vector objects;
  // 現在表示中のクリップアートオブジェクトを管理。背後から手前の順

  public boolean moving; // マウスでオブジェクトを移動中
  public boolean creating; // マウスでオブジェクトを作成中

  public Point dragStart; // ドラッグ開始点 (マウスをプレスした位置)
  public Point movingOffset;
  // 移動時の、オブジェクトの左上点からのマウスポインタの相対座標
  public int curObj; // 移動中もしくは作成中のオブジェクトの番号
  public boolean dragged;
  // オブジェクト作成時、マウスがプレスした点から移動したか

  Image[] images; // ファイルから読み込むクリップアート用イメージ
  int curImage; // 現在選ばれているクリップアート

  public static void main(String args[])
  {
    Kadai1115 frame = new Kadai1115("Don't drive me bananas!");
    frame.setSize(640, 480);
    frame.setVisible(true);
  }

  Kadai1115(String title)
  {
    super(title);

    objects = new Vector();

    moving = creating = false;

    images = new Image[2]; // イメージを読み込んでおく
    Toolkit tk = Toolkit.getDefaultToolkit();
    images[0] = tk.getImage("/home/g040240/java/Kadai1115/apple.gif");
    images[1] = tk.getImage("/home/g040240/java/Kadai1115/bananas.gif");

    curImage = 0; // デフォルトはりんご

    addKeyListener(new KeyAdapter()
      {
	public void keyPressed(KeyEvent evt)
	  {
	    char key = evt.getKeyChar();
	    switch (key)
	      {
	      case 'A': case 'a': // りんごを指定
		curImage = 0;
		break;
	      case 'B': case 'b': // バナナを指定
		curImage = 1;
		break;
	      case 'Q': case 'q': // 終了
		System.exit(0);
		break;
	      default: // コマンドでないときはビープ音をならす
		Toolkit.getDefaultToolkit().beep();
		break;
	      }
	  }
      });

    addMouseListener(new MouseAdapter()
      {
	public void mousePressed(MouseEvent evt)
	  {
	    dragStart = evt.getPoint();

	    moving  = creating = false; // 不要なはずだが念のため

	    for (int i = objects.size() - 1; i >= 0; i--)
	      // 表示中の全オブジェクトを調べ、移動か新規作成かを決める
	      // 逆順に調べるのは、手前のクリップアートを優先するため
	      {
		ClipArt obj = (ClipArt)objects.elementAt(i);
		if (obj.contains(dragStart))
		  // マウスプレスの点が既存のクリップアート内なら
		  {
		    moving = true; // 移動操作開始
		    curObj = i;

		    movingOffset = (Point)dragStart.clone();
		    movingOffset.translate(-obj.x, -obj.y);
		    // クリップアートの左上隅からのマウスの相対座標を保存

		    break;
		  }
	      }

	    if (!moving) // 移動ではない、すなわち新規作成のときは
	      {
		creating = true; // 作成操作開始
		dragged = false;
		// 実際のオブジェクト作成はマウスを動かすか、離すまでしない
	      }

	    repaint();
	  }

	public void mouseReleased(MouseEvent evt)
	  {
	    Point pt = evt.getPoint();

	    if (moving) // 移動中だった
	      {
		Point loc = (Point)pt.clone();
		loc.translate(-movingOffset.x, -movingOffset.y);
		// オブジェクトの左上座標を求める

		((ClipArt)objects.elementAt(curObj)).setLocation(loc);
		// 位置を設定

		moving = false;
	      }

	    if (creating) // 作成中だった
	      {
		if (dragged)
		  // マウスをドラッグして離した。オブジェクトは作成済み
		  {
		    Rectangle r = pointsToRectangle(dragStart, pt);
		    ((ClipArt)objects.elementAt(curObj)).setBounds(r);
		    // 新しい大きさに設定
		  }
		else // ドラッグではなくクリックだった
		  {
		    Rectangle r = centeredRectangle
		      (dragStart, images[curImage].getWidth(Kadai1115.this),
		       images[curImage].getHeight(Kadai1115.this));
		    objects.addElement(new ClipArt(r, curImage));
		    // マウスの位置を中心としてデフォルトの大きさでオ
		    // ブジェクトを作成
		  }

		creating = false;
	      }

	    repaint();
	  }
      });

    addMouseMotionListener(new MouseMotionAdapter()
      {
	public void mouseDragged(MouseEvent evt)
	  {
	    Point pt = evt.getPoint();

	    if (moving) // 移動中
	      {
		Point loc = (Point)pt.clone();
		loc.translate(-movingOffset.x, -movingOffset.y);
		// オブジェクトの左上座標を求める

		((ClipArt)objects.elementAt(curObj)).setLocation(loc);
		// 位置を設定
	      }

	    if (creating) // 作成中
	      {
		if (dragged) // すでにドラッグ中なら
		  {
		    Rectangle r = pointsToRectangle(dragStart, pt);
		    ((ClipArt)objects.elementAt(curObj)).setBounds(r);
		    // 新しい大きさに設定
		  }
		else
		  // これ以前にはマウスがプレスした点からずらされてい
		  // ない、すなわち今、初めてマウスが動かされた
		  {
		    Rectangle r = pointsToRectangle(dragStart, pt);
		    objects.addElement(new ClipArt(r, curImage));
		    // オブジェクトを作成する

		    dragged = true;
		    curObj = objects.size() - 1;
		  }
	      }

	    repaint();
	  }
      });
  }

  public void update(Graphics g)
  {
    if (offScreenImage == null) // オフスクリーンイメージが未作成なら作成
      {
	offScreenImage = createImage(640, 480);
      }

    paint(offScreenImage.getGraphics()); // 表示用イメージを作成
    g.drawImage(offScreenImage, 0, 0, this);
    // 左上隅に合わせてイメージを表示。ImageObserverはthis
  }

  public void paint(Graphics g)
  {
    g.setColor(Color.green);
    g.fillRect(0, 0, 640, 480);
    // 背景を塗り潰す

    for (int i = 0; i < objects.size(); i++)
      // 背後のクリップアートから順に描く
      {
	ClipArt c = (ClipArt)objects.elementAt(i);
	g.drawImage(images[c.getImageInd()], (int)c.getX(), (int)c.getY(),
		    (int)c.getWidth(), (int)c.getHeight(), this);
      }
  }

  public Rectangle pointsToRectangle(Point p1, Point p2)
  // 対角線の両端を与えて矩形を求める
  {
    int x, y, w, h;

    x = Math.min(p1.x, p2.x);
    y = Math.min(p1.y, p2.y);
    w = Math.abs(p2.x - p1.x);
    h = Math.abs(p2.y - p1.y);
    return new Rectangle(x, y, w, h);
  }

  public Rectangle centeredRectangle(Point c, int w, int h)
  // 点を中心とした矩形を得る
  {
    Point topLeft = (Point)c.clone();
    topLeft.translate(-w/2, -h/2);
    return new Rectangle(topLeft.x, topLeft.y, w, h);
  }
}


講評

Vector, 画像をどう有効に使うか扱いかねている解答が多いなかで,上の3つ は要素をうまく扱っています.なお,おもしろそうな題材でしたが,画像ファ イルが見えないため,アイデアは評価できずにプログラムだけで評価したもの もあります.