11/15の課題
問題
以下の要素を備えたプログラムを作成しなさい.
- イベント処理
- ファイルからのイメージのロード
- Vectorクラス
解答例(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つ
は要素をうまく扱っています.なお,おもしろそうな題材でしたが,画像ファ
イルが見えないため,アイデアは評価できずにプログラムだけで評価したもの
もあります.