15パズルと違う所
いよいよjavaっぽいソースコードになってきています。オブジェクトクラスというものが明確に出てきて、ちょっと楽しいです。
パネルをバラバラにして1枚を1つのクラスとして登録しています。そのクラスを色々な使い方をしています。ソースコード上にコメントをなるべく載せておきました。タッチドラッグなどの処理もちがいます。そしてちょっとしたスレッド処理をしています。Viewクラスを使用しているので「バウンディングボックス」の時とはちょっと違う処理の仕方をしていますね。
あなたは
人目のプログラマーだよ。
Androidプログラミング日記 (仮).
以前やった「15パズル」を発展させたプログラムを紹介します。書籍では続きになっているのでわかりやすかったのですが、同じようなのを続けてやると飽きることがあるのでここでご紹介です。以前の絵をバラバラにしてタッチドラッグで正位置にもっていき、元の絵に戻していくゲームです。
いよいよjavaっぽいソースコードになってきています。オブジェクトクラスというものが明確に出てきて、ちょっと楽しいです。
パネルをバラバラにして1枚を1つのクラスとして登録しています。そのクラスを色々な使い方をしています。ソースコード上にコメントをなるべく載せておきました。タッチドラッグなどの処理もちがいます。そしてちょっとしたスレッド処理をしています。Viewクラスを使用しているので「バウンディングボックス」の時とはちょっと違う処理の仕方をしていますね。
今回作成使用したファイルは4つです。main.xmlとManifest.xmlファイルもちょっと改造しています。
ファイル名「main.xml」 <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res/and.roid.imgpuzzle" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" > <and.roid.imgpuzzle.PuzzleView android:id="@+id/PuzzleView01" android:layout_width="fill_parent" android:layout_height="fill_parent" /> </FrameLayout> |
||
ファイル名「AndroidManifest.xml」 <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="and.roid.imgpuzzle" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="4" /> <application android:icon="@drawable/ic_launcher" android:label="@string/app_name" > <activity android:name=".Puzzle" android:label="@string/app_name" android:screenOrientation="portrait"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest> |
||
ファイル名「Puzzle.java」 package and.roid.imgpuzzle; import android.app.*; import android.os.Bundle; import android.view.*; public class Puzzle extends Activity { public float disp_w,disp_h;//端末の画面の大きさを取得するための変数 @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //ここから下3行でフルスクリーン処理 requestWindowFeature(Window.FEATURE_NO_TITLE); Window window = getWindow(); window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); //端末の画面の大きさを取得するための処理 WindowManager manager = window.getWindowManager(); Display disp = manager.getDefaultDisplay(); disp_w = disp.getWidth();//端末の画面の横幅取得 disp_h = disp.getHeight();//端末の画面の縦幅取得 setContentView(R.layout.main); //15パズルの時と変わりはありません。 } } |
||
ファイル名「PuzzleView.java」 package and.roid.imgpuzzle; import java.util.*; import android.content.Context; import android.content.res.Resources; import android.graphics.*; import android.graphics.drawable.Drawable; import android.os.Handler; import android.util.AttributeSet; import android.view.*; import android.widget.Toast; //主に色々な初期化や表示のためのクラス //Puzzleクラスの次に呼び出される public class PuzzleView extends View { private int btn_x = 47; //ボタン横座標 private int btn_y = 770; //ボタン縦座標 private int btn_w = 395; //ボタンの縦幅 private int btn_h = 50; //ボタンの横幅 private int board_x = 40; //ボード横座標 private int board_y = 126; //ボード縦座標 private float board_w = 400;//ボード横幅 private float board_h = 600;//ボード縦幅 private int score_x = 60; //スコア表示場所横 private int score_y = 73; //スコア表示場所縦 private Puzzle puzzle; private PuzzleBoard board; private Drawable back,btn1,btn2; private boolean btn_down,isPlaying;//ボタン状態,プレイ状態 private int pressX,pressY;//タッチ場所 private String message = "please touch button."; private Timer timer; //コンストラクタ1 public PuzzleView(Context context) { super(context); init(context);//初期化呼び出し } //コンストラクタ2 public PuzzleView(Context context, AttributeSet attrs) { super(context, attrs); init(context);//初期化呼び出し } //初期化呼び出し private void init(Context context){ puzzle = (Puzzle)context; btn_down = false;//ボタン状態上がってる isPlaying = false;//プレイ中ではない setPuzzleSize();//変数設定呼び出し } //変数設定処理 private void setPuzzleSize(){ float w = puzzle.disp_w; float h = puzzle.disp_h; float dw = w / 480f; float dh = h / 854f; btn_x *= dw; btn_y *= dh; btn_w *= dw; btn_h *= dh; board_x *= dw; board_y *= dh; board_w *= dw; board_h *= dh; score_x *= dw; score_y *= dh; //ここまでで端末ごとの画面の大きさの違いを直す Resources resources = puzzle.getResources(); back = resources.getDrawable(R.drawable.back1); back.setBounds(0, 0, (int)w, (int)h); btn1 = resources.getDrawable(R.drawable.start1); btn1.setBounds(btn_x, btn_y, btn_x + btn_w, btn_y + btn_h); btn2 = resources.getDrawable(R.drawable.start2); btn2.setBounds(btn_x, btn_y, btn_x + btn_w, btn_y + btn_h); Bitmap img = BitmapFactory.decodeResource(resources,R.drawable.image1); board = new PuzzleBoard(board_x, board_y, board_w, board_h, img); //ここまでで画像設定 } //Viewを取り込んでいると自動で作成されるメソッド @Override protected void onDraw(Canvas c) { c.drawColor(Color.BLACK);//背景色表示 back.draw(c);//背景画像表示 board.draw(c);//ボード画像表示 //ボタンが押されているかどうかでボタン画像変化 if (btn_down){ btn2.draw(c); } else { btn1.draw(c); } //テキスト表示 Paint p = new Paint(); p.setColor(Color.WHITE);//テキストを白に p.setTextSize(30f);//テキストのサイズ c.drawText(message, score_x, score_y, p);//テキスト表示 } //タッチイベント処理 @Override public boolean onTouchEvent(MotionEvent event) { int action = event.getAction(); int x = (int)event.getX(); int y = (int)event.getY(); switch(action){ //タッチした時の処理 case MotionEvent.ACTION_DOWN: pressX = x; pressY = y; //プレイ中なら //ボードクラスのgetInにタッチ座標を渡して //ボードクラスのdragPieceに保管 //getInはタッチした場所にパネルがあるかどうか調べるメソッドで //この処理でdragPieceにそのパネルが保管されます。なければ保管されません //プレイ中でなければ //タッチ座標をisInメソッドに渡して、ボタン状態を調べます if (isPlaying){ board.dragPiece = board.getIn(pressX, pressY); } else { if (isIn(pressX,pressY,btn1.getBounds())){ btn_down = true; } } break; //タッチして離れた時の処理 //プレイ中なら //クリアかどうか判定。か、ボタン上でプレイ中じゃなければ開始 case MotionEvent.ACTION_UP: if (isPlaying){ board.checkPlace(); if (board.checkFinish()){ timer.cancel(); Toast toast = Toast.makeText(puzzle, "クリア!", Toast.LENGTH_LONG); toast.show(); isPlaying = false; } } else { if (isIn(pressX,pressY,btn1.getBounds())){ btn_down = false; isPlaying = true; board.init(); startTimer(); Toast toast = Toast.makeText(puzzle, "スタート!", Toast.LENGTH_LONG); toast.show(); } } btn_down = false;//何もなければボタンは常時上がっている board.dragPiece = null;//パネルは触っていない break; //タッチしながら移動中処理 //プレイ中なら //ボードクラスのdragPieceにタッチ座標を渡しながら移動 case MotionEvent.ACTION_MOVE: if (isPlaying){ board.dragPiece(x,y); } break; } invalidate();//再表示 return true; } //渡された座標が渡された短形範囲にあるかどうか public boolean isIn(int x, int y,Rect rect){ return x > rect.left && x < rect.right && y > rect.top && y < rect.bottom; } //タイマー処理 //スレッド処理と呼ばれるもので、Viewを使う場合のスレッド処理方法 //ここではcountを1秒に1だけ増やす public void startTimer(){ final Handler handler = new Handler(); TimerTask task = new TimerTask(){ private int count = 0; @Override public void run() { message = "Time: " + ++count + " sec."; handler.post(new Runnable(){ public void run() { invalidate(); } }); } }; timer = new Timer(); timer.schedule(task, 0, 1000); } } |
||
ファイル名「PuzzleBoard.java」 package and.roid.imgpuzzle; import java.util.*; import android.graphics.*; //ゲームの心臓部 public class PuzzleBoard { private static final int ROW = 6;//パネル縦の数 private static final int COL = 4;//パネル横の数 private float w = 400; //ボード横幅 private float h = 600; //ボード縦幅 private float pW = 100; //パネル1枚横幅 private float pH = 100; //パネル1枚縦幅 private float x, y; //パネル1枚座標 private PuzzlePiece[][] data; //パネル1枚づつ設定 public PuzzlePiece dragPiece; //パネル1枚づつ座標設定 public int count = 0; //カウント //コンストラクタ //変数設定 public PuzzleBoard(float x, float y, float w, float h, Bitmap image) { super(); this.x = x; this.y = y; this.w = w; this.h = h; pW = w / COL; pH = h / ROW; data = new PuzzlePiece[COL][ROW]; for (int i = 0; i < COL; i++) { for (int j = 0; j < ROW; j++) { Bitmap bitmap = Bitmap.createBitmap(image, (int) (pW * i), (int) (pH * j), (int) pW, (int) pH); data[i][j] = new PuzzlePiece(bitmap, i, j, pW, pH); data[i][j] .setLoc((int) (x + pW * i), (int) (y + pH * j), false); } } } //初期設定というよりここではパネルをランダムでバラバラにする //カウントを0に //クリア後などにここだけ呼び出せば大丈夫 public void init() { Random r = new Random(new Date().getTime()); for (int i = 0; i < COL; i++) { for (int j = 0; j < ROW; j++) { data[i][j].setLoc(r.nextInt((int) w), r.nextInt((int) h), true); data[i][j].placed = false; } } count = 0; } //パネル表示 public void draw(Canvas c) { for (int i = 0; i < COL; i++) { for (int j = 0; j < ROW; j++) { if (data[i][j] != null) data[i][j].draw(c); } } } //タッチ座標を受け取りその座標にパネルがあるかどうか public PuzzlePiece getIn(int x, int y) { PuzzlePiece result = null; for (int i = 0; i < COL; i++) { for (int j = 0; j < ROW; j++) { if (data[i][j].isIn(x, y)) result = data[i][j]; } } return result; } //ドラッグされているパネルを座標に移動 public void dragPiece(int x0, int y0) { if (dragPiece != null) { int px = dragPiece.getPlaceX(); int py = dragPiece.getPlaceY(); if (!data[px][py].placed) dragPiece.setLoc(x0, y0, true); } } //パネルの位置が正位置に近ければ正位置にセット public void checkPlace() { if (dragPiece != null) { int px = dragPiece.getPlaceX(); int py = dragPiece.getPlaceY(); Rect r = dragPiece.getRect(); if (Math.abs(x + px * pW - r.left) < (pW / 4) && Math.abs(y + py * pH - r.top) < (pH / 4)) { dragPiece.setLoc((int) (x + px * pW), (int) (y + py * pH), false); data[px][py].placed = true; } } } //クリア判定 public boolean checkFinish() { boolean flg = true; for (int i = 0; i < COL; i++) for (int j = 0; j < ROW; j++) if (data[i][j].placed == false) flg = false; return flg; } } |
||
ファイル名「PuzzlePiece.java」 package and.roid.imgpuzzle; import android.graphics.*; import android.graphics.drawable.BitmapDrawable; //パネル1枚のクラス public class PuzzlePiece { private BitmapDrawable image;//分割された画像 private float width,height,x,y,x1,y1,x2,y2; private int placeX,placeY;//パネルのマス目の番号 public boolean placed;//正位置かどうか //コンストラクタ //初期化 public PuzzlePiece(Bitmap image,int x, int y, float w, float h) { super(); this.image = new BitmapDrawable(image); placeX = x; placeY = y; width = w; height = h; placed = false; } //パネルを正位置に設置させる処理 public void setLoc(int x, int y,boolean center){ if (center){ this.x = x; this.y = y; x1 = this.x - (width / 2); y1 = this.y - (height / 2); } else { this.x = x + (width / 2); this.y = y + (height / 2); x1 = x; y1 = y; } x2 = x1 + width; y2 = y1 + height; } //plaseXを他のクラスから取り出すための処理 public int getPlaceX(){ return placeX; } //plaseYを他のクラスから取り出すための処理 public int getPlaceY(){ return placeY; } //短形情報を他のクラスから取り出すための処理 public Rect getRect(){ return new Rect((int)x1, (int)y1, (int)x2, (int)y2); } //渡された座標と1枚のパネル座標と合ってるかどうか public boolean isIn(int x, int y){ return x > x1 && x < x2 && y > y1 && y < y2; } //パネル表示 public void draw(Canvas c){ image.setBounds((int)x1, (int)y1, (int)x2, (int)y2); image.draw(c); } } |
||
スタートボタンをタッチすると元画像がバラバラになりついでにバラバラに置かれます。これをタッチ&ドラッグで元画像の位置にはめ込んでいきます。プレイするとわかるのですが、パネルを正位置の近くで離すと吸着します。正位置とパネル位置が四分の一の差であれば吸着するようになっています。
ソースコードを見てもらえばわかるとおもうのですが、これはもう数学の世界ですよね・・数学をもっと勉強していればよかった・・・
このプログラムに限ってのことではないのですが、数学を勉強やり直すことがすごく多いです。ついでに物理などもですが。バウンディングボックスなどは数学などを取り入れやすいプログラムですね。xyを整数で変化させるのではなく、角度と移動量で動かしたりできればおもしろいものがもっとできます。
とりあえず今回のプログラムが理解できて自分のものにできれば、ある程度の静的なものは色々できそうです。ボードゲーム的なものですね。オセロや麻雀とか将棋などなど。しかしどれもCPU相手にするためのアルゴリズムとか勉強しないと・・・
てことで今回はこれで終了です。