15パズル
画像などを4×4などに区切り1マスだけ空欄にし、バラバラになった画像を空欄を使ってもとの画像に戻すゲームです。
最初に紹介ずるゲームにこれを選んだ理由は、書籍の最初がこれだったかr・・・えー今までやってきた基本のコードの組み合わせでできるからです。
ゲーム自体は、画像さえ変えたり、分割数を変えたりすれば無限に種類はできますので、お手軽でいいですね。
今回からは、コードを書いてからどういうことをやっているのか順番に簡単な説明をいれていきます。
プロジェクト名「Puzzle」
使用画像 |
|
|
|
|
|
|
背景画像 |
パズル画像 |
|
|
|
|
スタートボタン(通常) |
スタートボタン(押下) |
|
ファイル名「main.xml」
01 | <?xml version= "1.0" encoding= "utf-8" ?> |
05 | android:layout_width= "fill_parent" |
06 | android:layout_height= "fill_parent" |
07 | android:orientation= "vertical" > |
10 | android:id= "@+id/View01" |
11 | android:layout_width= "fill_parent" |
12 | android:layout_height= "fill_parent" /> |
|
|
|
|
|
|
レイアウトするオブジェクトの修正ですね。<FrameLayout></FrameLayout>に変更し、xmlns:app="http://schemas.android.com/apk/res/and,roid"のようにパッケージパスを追加。
android:id="@+id/View01"でオブジェクトidを設定しました。 |
|
|
ファイル名「PuzzleManifest.xml」
01 | <?xml version= "1.0" encoding= "utf-8" ?> |
04 | android:versionCode= "1" |
05 | android:versionName= "1.0" > |
07 | <uses-sdk android:minSdkVersion= "4" /> |
10 | android:icon= "@drawable/ic_launcher" |
11 | android:label= "@string/app_name" > |
13 | android:screenOrientation= "portrait" |
14 | android:name= ".PuzzleActivity" |
15 | android:label= "@string/app_name" > |
17 | <action android:name= "android.intent.action.MAIN" /> |
19 | <category android:name= "android.intent.category.LAUNCHER" /> |
|
|
|
|
|
|
ここでは早速おぼえたての、回転機能を縦で固定にしています。 |
|
|
ファイル名「PuzzleActivity.java」
03 | import android.app.Activity; |
04 | import android.os.Bundle; |
07 | public class PuzzleActivity extends Activity { |
10 | public void onCreate(Bundle savedInstanceState) { |
11 | super.onCreate(savedInstanceState); |
12 | requestWindowFeature(Window.FEATURE_NO_TITLE); |
13 | getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); |
15 | setContentView(R.layout.main); |
|
|
|
|
|
|
このファイルはプロジェクト作成時にすでにあるファイルです。赤字の所でフルスクリーンにするようにしています。このコードはそのままおぼえましょう。 |
|
|
ファイル名「PView.ja」このファイルはView クラスをスーパークラスに持たせて作成します。
003 | import android.content.Context; |
004 | import android.content.res.Resources; |
005 | import android.graphics.*; |
006 | import android.graphics.drawable.Drawable; |
007 | import android.util.AttributeSet; |
008 | import android.view.*; |
009 | import android.widget.Toast; |
011 | public class PView extends View { |
012 | private static final int btn_x = 50; |
013 | private static final int btn_y = 770; |
014 | private static final int btn_w = 390; |
015 | private static final int btn_h = 50; |
016 | private static final int board_x = 40; |
017 | private static final int board_y = 126; |
018 | private static final int score_x = 60; |
019 | private static final int score_y = 73; |
021 | private PuzzleActivity puzzle; |
023 | private Drawable back,btn1,btn2; |
024 | private boolean btn_down,isPlaying; |
025 | private int pressX,pressY,upX,upY; |
027 | public PView(Context context) { |
032 | public PView(Context context, AttributeSet attrs){ |
037 | private void init(Context context){ (2) |
038 | puzzle =(PuzzleActivity)context; |
039 | Resources resources = context.getResources(); |
040 | back = resources.getDrawable(R.drawable.back1); |
041 | btn1 = resources.getDrawable(R.drawable.start1); |
042 | btn1.setBounds(btn_x,btn_y,btn_x+btn_w,btn_y+btn_h); |
043 | btn2 = resources.getDrawable(R.drawable.start2); |
044 | btn2.setBounds(btn_x,btn_y,btn_x+btn_w,btn_y+btn_h); |
046 | Bitmap img = BitmapFactory.decodeResource(resources, R.drawable.image1); |
047 | board = new PBoard(board_x,board_y,img); |
052 | protected void onDraw(Canvas c){ (3) |
053 | c.drawColor(Color.BLACK); |
054 | int w = this .getWidth(); |
055 | int h = this .getHeight(); |
056 | back.setBounds(0, 0, w, h); |
064 | Paint p = new Paint(); |
066 | p.setColor(Color.WHITE); |
067 | c.drawText( "count:" +board.count, score_x, score_y, p); |
070 | public boolean onTouchEvent(MotionEvent event ){ (4) |
071 | int action = event .getAction(); |
072 | int x = ( int ) event .getX(); |
073 | int y = ( int ) event .getY(); |
075 | case MotionEvent.ACTION_DOWN: |
078 | if (isIn(x,y,btn1.getBounds())){ |
082 | Toast toast = Toast.makeText(puzzle, "スタート!" ,Toast.LENGTH_LONG); |
086 | case MotionEvent.ACTION_UP: |
090 | if (isPlaying) checkMove(); |
097 | public boolean isIn( int x, int y, Rect rect){ (5) |
098 | return x>rect.left && x<rect.right && y>rect.top && y<rect.bottom; |
101 | public void checkMove(){ (6) |
102 | int dx = upX - pressX; |
103 | int dy = upY - pressY; |
104 | if (dx < -100) board.move(PBoard.WEST); |
105 | if (dx > 100) board.move(PBoard.EAST); |
106 | if (dy < -100) board.move(PBoard.NORTH); |
107 | if (dy > 100) board.move(PBoard.SOUTH); |
108 | if (board.checkFinish()){ |
110 | Toast toast = Toast.makeText(puzzle, "完成!" , Toast.LENGTH_LONG); |
|
|
|
|
|
|
このファイルクラスでは主に全体の画面や初期設定など、アプリの心臓部となるクラスです。
(1) private static final int で 設定した変数に数値を代入すると、その数値の変更ができなくなり、固定値のようになります。 (2) private void init(Context context) の「init」は初期化を意味します。初期化するクラスをここで作り、画像の取り込みや初期位置情報などを設定しています。
(3) protected void onDraw(Canvas c)はいろいろ表示するクラスです。画面全体を黒で塗りつぶし、画面の横幅縦幅を知り、その座標を使い背景画像をセット。のちに作る別ファイルクラスにある表示クラスを呼び出し、ボタン画像を表示。ここでボタンが押されているかいないかで、ボタンの画像を変えて表示。ここからはテキスト表示のためにPaintオブジェクトを生成し、テキストサイズを設定。色を設定して変数countを設定座標に表示しています。
(4) ここはタッチパネルのイベント処理です。MotionEvent eventでタッチした時にアクションイベント値が自動的にeventにはいります。それをint action = event.getAction();で受け取り、次で座標も受け取ります。switchでさきほど受け取ったタッチイベントによって処理を変えます。
(5) public boolean isIn(int x, int y, Rect rectの説明を先に。座標と短形情報をうけとり、渡された座標(今回はタッチ座標)と短形座標(今回はボタン座標)をくらべて、短形エリアでタッチされたかいないか(boolean)をtrue、falseで返します。
MotionEvent.ACTION_DOWN:はタッチした時です。まずタッチした時の座標をpressX、Yに入れます。isInに値を渡し(上記(5)参照)それが真なら(短形エリアでタッチされたら)btn_down = true(押された)、isPlaying = true(スタート開始ok)にし、のちに設定するちがうファイルのボード(動かす写真)クラスの初期化を飛び出し、最後に下部にテキストトーストを表示します。
MotionEvent.ACTION_UP:はタッチしたあとにタッチが離れた時の処理です。ボタン上であればタッチは離れるわけですから元にもどします(btn_down = false)。つまり押した時だけボタン画像を変更させて、あとはfalseの状態にしておくということです。そのご、タッチを離した時の座標をupX、Yに入れ、if(isPlaying) checkMove();でゲーム中なら checkMove()クラスを呼び出しゲーム中でないならそのまま下に移動。
最後に画面を更新(invalidate();)してreturn trueでこのクラスを出ます。
(6) public void checkMove()はタッチを話した時に呼び出されるクラスです。ここではパネルを移動させるための条件が整っていれば、違うファイルのパネルを動かすクラスを呼び出す処理を最初にしています。タッチon、off時にそれぞれ座標を格納しているので、タッチした時の座標からタッチが離れた時の座標を差し引く事で、どれだけタッチしたまま移動して離れたかを出します。その差が100ドット以上の差があり、xならプラス100かマイナス100か、yも同じ感じで調べてそれぞれ違う数値を渡して呼び出しています。。
最後にこの処理が終わった瞬間に、ゲーム終了かどうかのチェックをして、終了ならテキストトーストを表示しています。 |
|
|
ファイル名「PBoard.java」
05 | import android.graphics.*; |
08 | public static final int CENTER = 0; |
09 | public static final int NORTH = 1; |
10 | public static final int SOUTH = 2; |
11 | public static final int EAST = 3; |
12 | public static final int WEST = 4; |
17 | public static final int row = 6; |
18 | public static final int col = 4; |
19 | public static final int pW = 100; |
20 | public static final int pH = 100; |
23 | public PBoard( int x, int y, Bitmap image){ (1) |
28 | data = new int []{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,-1}; |
32 | public void init(){ (2) |
34 | Random r = new Random( new Date().getTime()); |
35 | int a = r.nextInt(data.length); |
36 | int b = r.nextInt(data.length); |
41 | for ( int i=0;i<data.length;i++) |
42 | if (data[i] == -1) place = i; |
46 | public void draw(Canvas canvas){ (3) |
48 | for ( int i=0;i<row;i++){ |
49 | for ( int j=0;j<col;j++){ |
51 | int r = ( int )(data[n]/col); |
53 | canvas.drawBitmap(image, new Rect(x+c*pW, y+r*pH, x+c*pW+pW, y+r*pH+pH), |
54 | new Rect(x+j*pW, y+i*pH, x+j*pW+pW, y+i*pH+pH), new Paint()); |
61 | public void move( int move){ (4) |
63 | int r = ( int )(place / col); |
80 | int n = data[r * col + c]; |
81 | data[r * col + c] = data[r2 * col + c2]; |
82 | data[r2 * col + c2] = n; |
83 | for ( int i=0;i<data.length;i++) |
84 | if (data[i] == -1) place = i; |
88 | public boolean checkFinish(){ (5) |
90 | for ( int i=0;i<data.length-1;i++) |
91 | if (data[i] != i) flg = false ; |
|
|
|
|
|
|
このクラスは表示するパネルに関する処理が書かれています。
(1) PBoard(int x, int y, Bitmap image)はこのファイルの主クラスとなります。このクラスを使用する時には、座標とイメージをこのクラスに渡すようにしてます。このクラスの座標メンバxyに渡された座標を入れ、パネルを分割するための準備としてdataの配列を作成し、中にに24個の数値を襦袢に1から入れ、最後24個目を-1(空欄)と設定します。空欄パネル数の場所をplaceに入れます。
(2) public void init() でパネルの初期化処理をしています。簡単にいいますと、ランダムな数値0から24を2つ作り、3つの変数を使い(例えばA=B B=C C=A)としながら入れ替える処理を200回繰り返します。
Random r = new Random(new Date().getTime());はランダム数値を使うための準備です。
int a = r.nextInt(data.length);は下のコードbと同じで、ランダム値をdata.lengthのdataの大きさ、ここでは24で作成しています。そして3つの変数を使用して数値を入れ替えています。
最後にfor文でdataの中にある配列数分だけ中の数値をチェックし、その数値が-1であれば空欄を表すplaceに-1を入れます。そしてcountを0に設定。
(3) public void draw(Canvas canvas) ではパネル表示を画面に表示させる処理をしているクラスです。ここはほとんどが数学の応用を使用してますね。
(4) public void move(int move) ではパネルの移動処理をしています。渡された値によって処理を変えます。最後に空欄を探してplaceに-1を入れます。
(5) public boolean checkFinish() はパネルの絵が元の絵に戻ったかどうかの判定処理です。最初の設定で元の画像が0から22まで順番に並んでいます。for分を成分iも順番に0から増えていく処理をして、元絵の数値と比べておなじゅ数値に並んでいれば完成というわけです。最初のboolean flg = true;は完成したかどうかのフラグで、完成と一度設定しています。そのわけは逆の処理をするよりこちらのほうが簡単な処理ができるからです。(たぶん) |
|
|
|
|
|
 |
これで完成ですが、エミュレータを色々試したらわかると思いますがXperiaでしか表示がしっかりしません。 |
|
|
|
Xperiaでしかちゃんと表示されないのは問題です。ちょっと改造します。
|
ファイル名「PuzzleActivity.java」
03 | import android.app.Activity; |
04 | import android.os.Bundle; |
07 | public class PuzzleActivity extends Activity { |
08 | public float disp_w,disp_h; |
11 | public void onCreate(Bundle savedInstanceState) { |
12 | super.onCreate(savedInstanceState); |
13 | requestWindowFeature(Window.FEATURE_NO_TITLE); |
14 | Window window = getWindow(); |
15 | window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); |
16 | WindowManager manager = window.getWindowManager(); |
17 | Display disp = manager.getDefaultDisplay(); |
18 | disp_w = disp.getWidth(); |
19 | disp_h = disp.getHeight(); |
21 | setContentView(R.layout.main); |
|
|
|
|
|
|
|
|
|
ファイル名「PView.java」
003 | import android.content.Context; |
004 | import android.content.res.Resources; |
005 | import android.graphics.*; |
006 | import android.graphics.drawable.Drawable; |
007 | import android.util.AttributeSet; |
008 | import android.view.*; |
009 | import android.widget.Toast; |
011 | public class PView extends View { |
012 | private static final float XPERIA_W = 480; |
013 | private static final float XPERIA_H = 854; |
014 | private float btn_x = 50; |
015 | private float btn_y = 770; |
016 | private float btn_w = 390; |
017 | private float btn_h = 50; |
018 | private float board_x = 40; |
019 | private float board_y = 126; |
020 | private float score_x = 60; |
021 | private float score_y = 73; |
023 | private PuzzleActivity puzzle; |
025 | private Drawable back,btn1,btn2; |
026 | private boolean btn_down,isPlaying; |
027 | private int pressX,pressY,upX,upY; |
029 | public PView(Context context) { |
034 | public PView(Context context, AttributeSet attrs){ |
040 | private void init(Context context){ |
041 | puzzle =(PuzzleActivity)context; |
045 | private void setPuzzleSize(){ |
046 | float w = puzzle.disp_w; |
047 | float h = puzzle.disp_h; |
048 | float dw = w / XPERIA_W; |
049 | float dh = h / XPERIA_H; |
055 | board_x = board_x * dw; |
056 | board_y = board_y * dh; |
057 | score_x = score_x * dw; |
058 | score_y = score_y * dh; |
060 | Resources resources = puzzle.getResources(); |
061 | back = resources.getDrawable(R.drawable.back1); |
062 | back.setBounds(0, 0, ( int )w, ( int )h); |
063 | btn1 = resources.getDrawable(R.drawable.start1); |
064 | btn1.setBounds(( int )btn_x,( int )btn_y,( int )(btn_x+btn_w),( int )(btn_y+btn_h)); |
065 | btn2 = resources.getDrawable(R.drawable.start2); |
066 | btn2.setBounds(( int )btn_x,( int )btn_y,( int )(btn_x+btn_w),( int )(btn_y+btn_h)); |
068 | Bitmap img = BitmapFactory.decodeResource(resources, R.drawable.image1); |
069 | board = new PBoard(board_x,board_y,dw,dh,img); |
072 | protected void onDraw(Canvas c){ |
073 | c.drawColor(Color.BLACK); |
081 | Paint p = new Paint(); |
083 | p.setColor(Color.WHITE); |
084 | c.drawText( "count:" +board.count, score_x, score_y, p); |
087 | public boolean onTouchEvent(MotionEvent event ){ |
088 | int action = event .getAction(); |
089 | int x = ( int ) event .getX(); |
090 | int y = ( int ) event .getY(); |
092 | case MotionEvent.ACTION_DOWN: |
093 | pressX = ( int ) event .getX(); |
094 | pressY = ( int ) event .getY(); |
095 | if (isIn(x,y,btn1.getBounds())){ |
099 | Toast toast = Toast.makeText(puzzle, "スタート!" ,Toast.LENGTH_LONG); |
103 | case MotionEvent.ACTION_UP: |
105 | upX = ( int ) event .getX(); |
106 | upY = ( int ) event .getY(); |
107 | if (isPlaying) checkMove(); |
109 | case MotionEvent.ACTION_MOVE: |
116 | public boolean isIn( int x, int y, Rect rect){ |
117 | return x>rect.left && x<rect.right && y>rect.top && y<rect.bottom; |
120 | public void checkMove(){ |
121 | int dx = upX - pressX; |
122 | int dy = upY - pressY; |
123 | if (dx < -100) board.move(PBoard.WEST); |
124 | if (dx > 100) board.move(PBoard.EAST); |
125 | if (dy < -100) board.move(PBoard.NORTH); |
126 | if (dy > 100) board.move(PBoard.SOUTH); |
127 | if (board.checkFinish()){ |
129 | Toast toast = Toast.makeText(puzzle, "完成!" , Toast.LENGTH_LONG); |
|
|
|
|
|
|
|
|
|
ファイル名「PBoard.java」
05 | import android.graphics.*; |
08 | public static final int CENTER = 0; |
09 | public static final int NORTH = 1; |
10 | public static final int SOUTH = 2; |
11 | public static final int EAST = 3; |
12 | public static final int WEST = 4; |
17 | public static final int row = 6; |
18 | public static final int col = 4; |
19 | private float pW = 100f; |
20 | private float pH = 100f; |
23 | public PBoard( float x, float y, float dw, float dh, Bitmap image){ |
30 | data = new int []{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,-1}; |
36 | Random r = new Random( new Date().getTime()); |
37 | int a = r.nextInt(data.length); |
38 | int b = r.nextInt(data.length); |
43 | for ( int i=0;i<data.length;i++){ |
44 | if (data[i] == -1) place = i; |
49 | public void draw(Canvas canvas){ |
51 | for ( int i=0;i<row;i++){ |
52 | for ( int j=0;j<col;j++){ |
54 | int r = ( int )(data[n]/col); |
56 | canvas.drawBitmap(image, |
57 | new Rect(( int )(x+c*pW), ( int )(y+r*pH), ( int )(x+c*pW+pW), ( int )(y+r*pH+pH)), |
58 | new Rect(( int )(x+j*pW), ( int )(y+i*pH), ( int )(x+j*pW+pW), ( int )(y+i*pH+pH)), |
66 | public void move( int move){ |
68 | int r = ( int )(place / col); |
85 | int n = data[r * col + c]; |
86 | data[r * col + c] = data[r2 * col + c2]; |
87 | data[r2 * col + c2] = n; |
88 | for ( int i=0;i<data.length;i++) |
89 | if (data[i] == -1) place = i; |
93 | public boolean checkFinish(){ |
95 | for ( int i=0;i<data.length-1;i++) |
96 | if (data[i] != i) flg = false ; |
|
|
|
|
|
|
|
|
|
 |
|
|
|
|
|
|
|
変更場所は赤字にしました。
Xperiaの画面に合わせているので、実際の画面との縮尺を計算するための変更です。
初めて使うメソッドなど出てきても、日本語でゆっくり考えてみると理解しやすいとおもいます。
javaはオブジェクト思考プログラミングということで、処理をクラスごとに細かく分けてパーツを組み合わせる感じです。
最初に作成されるファイルはそれがないと動かないファイルという感じで覚えて、携帯端末機自体のオブジェクトに、ゲームの背景やボタン、テキストを設定、パネルの処理だけ別のオブジェクトとしてクラス作成をする。という感じでしょうか。
オブジェクト思考はパーツごとに細かく分かれていて、ゆっくり調べることができ、見やすいのがいいですよね。