【androidアプリ開発 No.14】 スライドパズルを作ろう01

Pocket

間が開いてしまいましたが、スライドパズルを作成していきましょう。

前回はスライドパズルのレイアウトを作成しましたが、
今回からはスライドパズルのロジック部分を作成していきます。
※前回の記事はこちら

では早速ソースを書いていきましょう。

src/jp.ne.hatena.d.failure_engineer.SlidePuzzle/SlidePuzzleActivity.java

ピースの設定

まず最初にimageButtonのリソースIDと画像のリソースIDを配列に格納します。
画像のリソースIDを格納した配列は、パズルのピースが正解の位置に配置されているのか判定するために利用します。

    //===========================================================
    // パズルのピースを配列として設定する。(imageButtonのidを格納)
    //===========================================================
    static final int imgBtnArray[] = {
        R.id.imageButton1, R.id.imageButton2, R.id.imageButton3,
        R.id.imageButton4, R.id.imageButton5, R.id.imageButton6,
        R.id.imageButton7, R.id.imageButton8, R.id.imageButton9
    };
    //===========================================================
    // パズルのピースの画像を配列として設定する。(リソースidを格納)
    //===========================================================
    static final int pieceImageArray[] = {
        R.drawable.piece1, R.drawable.piece2, R.drawable.piece3,
        R.drawable.piece4, R.drawable.piece5, R.drawable.piece6,
        R.drawable.piece7, R.drawable.piece8, R.drawable.piece9
    };

配列が出来たら、imageButtonに画像を設定するメソッドを作成します。

    // SliderControllerインスタンス
    SliderController sliders[] = new SliderController[imgBtnArray.length];

    /**
     * パズルピースの設定
     *
     */
    private void createSliderController() {
        // ピースの生成を行う
        for(int i = 0; i < imgBtnArray.length; ++i) {
            ImageButton imgBtn = 
                    (ImageButton)findViewById(imgBtnArray&#91;i&#93;);
            sliders&#91;i&#93; = new SliderController(imgBtn, i, pieceImageArray&#91;i&#93;);
        }
    }

    /**
     * スライドコントロール
     */
    class SliderController implements View.OnClickListener{
        ImageButton imgBtn;
        int idx = 0;
        int curImageID = 0;
        
        public SliderController(ImageButton argImgBtn, int i, int resID) {
            imgBtn = argImgBtn;
            idx = i;
            setImgeRes(resID);
            imgBtn.setOnClickListener(this);
            
        }
        
        /**
         * 画像リソースセット
         *
         * @param リソースID resID(int)
         */
        private int setImgeRes(int resID) {

            int oldImgID = curImageID;
            curImageID = resID;
            imgBtn.setImageResource(resID);
            return oldImgID;
        }
    }
&#91;/java&#93;
SliderControllerはピースの画像交換などを行うクラスです。
詳細についてはまた後で記載します。

createSliderControllerメソッドではimageButton分SliderControllerを生成していきます。
SliderControllerのsetImgeResでimageButtonと画像の紐付けをします。
<h5>スタートボタン</h5>
次にスタートボタンを実装します。

    //===========================================================
    // 解法チェック用配列
    //===========================================================
    int tmp_sliders[] = new int[pieceImageArray.length];
    
    // ゲームがスタートされたか判定するフラグ
    private boolean isStart = false;

    /**
     * スタートボタン押下処理
     * (匿名クラス)
     */
    private void setStartBtnListener() {
        Button btn = (Button)findViewById(R.id.button1);
        btn.setOnClickListener(new View.OnClickListener() {
            
            @Override
            public void onClick(View v) {
                createSliderController();
                startGame();
                startChronometer();
            }
        });
    }

    
    /**
     * パズルのスタート準備
     *
     */
    private void startGame() {
        int size = pieceImageArray.length;
        
        // パズルを並び替える
        for(int i =0; i < size -2; ++i){
            int swap = (int)(Math.random() * (size - (i + 1)));
            sliders&#91;i&#93;.swapImage(sliders&#91;i+swap&#93;);
        }
        
        // 並び替えたパズルに解法が存在するかチェック
        if (isClearPossible()){
            // 解法が存在するため、フラグを立てる
            isStart = true;
        }else{
            // 再度パズルを並び替える
            startGame();
        }
    }
    
    /**
     * パズルの解法が存在するか判定する
     *
     * @return 解法有り(true) 解法無し(false)
     *
     */
    private boolean isClearPossible() {

        // ピースの総数設定
        int size = pieceImageArray.length;
        
        // ピース交換カウント
        int exchangeCnt = 0;
        
        // 解法判定用に並べ替えたピースIDをコピーする 
        for(int i = 0; i < (size - 1); ++i){
            tmp_sliders&#91;i&#93; = sliders&#91;i&#93;.curImageID;
        }
        
        for(int i = 0; i < (size - 2); ++i){
            
            // ピースが所定の場所に存在するか判定
            if(pieceImageArray&#91;i&#93; != tmp_sliders&#91;i&#93;){
                // カウントアップさせる
                exchangeCnt++;
                
                // ピースを所定の場所に交換
                exchangePiece(i);
            }
        }
        
        // 交換回数が偶数の場合は解法が存在しない
        if (1 == (exchangeCnt % 2)){
            return false;
        }
        
        // ここまできたら解法が存在する
        return true;
    }

    /**
     * パズルの解法チェック配列の要素の並べ替え
     *
     * @param 対象要素番号 startIndex(int)
     *
     */
    private void exchangePiece(int startIndex) {
        // 交換対象の要素を選択ソートを行う
        for(int i = startIndex; i < (tmp_sliders.length - 2); ++i){

            // 交換対象のピースか判定する。
            if(pieceImageArray&#91;startIndex&#93; == tmp_sliders&#91;i + 1&#93;){
                // ピースを交換する
                tmp_sliders&#91;i + 1&#93; = tmp_sliders&#91;startIndex&#93;;
                tmp_sliders&#91;startIndex&#93; = pieceImageArray&#91;startIndex&#93;;
                return;
            }
        }
        return;
    }

    /**
     * クロノメーター開始
     *
     */
    private void startChronometer() {
        Chronometer chrono = (Chronometer)findViewById(R.id.chronometer1);
        chrono.setBase(SystemClock.elapsedRealtime());
        chrono.start();
    }
&#91;/java&#93;
スタートボタンが押下されたら一番最初にcreateSliderControllerメソッドを呼び出し
ピースの配置状態を初期状態に戻します。
その後startGameメソッドを呼び、ゲームの準備を行います。
startGameメソッドではパズルをゲームを開始するため、ピースをバラバラにします。
ただし、ピースをランダムに並べるかだけでは正解が存在しない場合があります。
その為、ランダムで並び替えた後に正解があるかチェックを行い、
正解が存在しない場合は再度ランダムに並び替えるようにしています。
正解があるかの確認方法は下記のサイトを参考にさせていただきました。

<a href="http://homepage3.nifty.com/funahashi/game/game66.html" target="_blank">http://homepage3.nifty.com/funahashi/game/game66.html</a>

exchangePieceメソッドは上記サイトの方法をメソッドにしたものです。

startChronometerメソッドは、ゲーム開始からの経過時間を計るために使用します。
<h5>ピースのスライド</h5>
次はピースを選択したときにピースをスライドさせる処理を書いていきます。
手順としては押下したピースが移動できるか判定を行い、
移動できる場合は対象のピースの画像を入れ替え、最後にクリア後の配置になっているか確認します。

    // 縦軸横軸設定
    public final static int LAYOUT_X = 3;
    public final static int LAYOUT_Y = 3;

    /**
     * スライドコントロール
     */
    class SliderController implements View.OnClickListener{
        ImageButton imgBtn;
        int idx = 0;
        int curImageID = 0;
        
        public SliderController(ImageButton argImgBtn, int i, int resID) {
            imgBtn = argImgBtn;
            idx = i;
            setImgeRes(resID);
            imgBtn.setOnClickListener(this);
            
        }
        
        /**
         * 画像リソースセット
         *
         * @param リソースID resID(int)
         */
        private int setImgeRes(int resID) {

            int oldImgID = curImageID;
            curImageID = resID;
            imgBtn.setImageResource(resID);
            return oldImgID;
        }

        /**
         * 画像リソースID取得
         *
         */
        public int getImageRes() {
            return curImageID;
        }
        
        /**
         * 画像交換
         *
         * @param スライドコントロール other(SliderController)
         */
        public void swapImage(SliderController other) {
            int previous = other.setImgeRes(curImageID);
            setImgeRes(previous);
        }
        
        @Override
        public void onClick(View v) {

            if(curImageID == R.drawable.piece9) {
                return;
            }
            vectorDir(idx);
            
            // クリア判定
            if (isCompleted()){
                complete();
            }
        }
    }

    /**
     * スライド方向判定メソッド
     *
     * @param 要素番号 idx(int)
     */
    private void vectorDir(int idx){
        
        //============================
        // 上に移動できる可能性があるか判定
        // (一段目以外か判定)
        //============================
        if((LAYOUT_X - 1) < idx){
            if(vectorDirUP(idx)){
                return;
            }
        }
        
        //============================
        // 下に移動できる可能性があるか判定
        // (3段目以外か判定)
        //============================
        if(idx < (LAYOUT_X * (LAYOUT_Y - 1))){
            if(vectorDirDown(idx)){
                return;
            }
        }
        
        //============================
        // 左に移動できる可能性があるか判定
        // (左端以外か判定)
        //============================
        if((idx % LAYOUT_X) != 0){
            if(vectorDirLeft(idx)){
                return;
            }
        }
        
        //============================
        // 右に移動できる可能性があるか判定
        // (右端以外か判定)
        //============================
        if((idx % LAYOUT_X) != (LAYOUT_X - 1)){
            if(vectorDirRight(idx)){
                return;
            }
        }
    }

    /**
     * 上方向スライド判定メソッド
     *
     * @param 要素番号 idx(int)
     */
    private boolean vectorDirUP(int idx) {
        int scalar = 0;
        for(int i = idx - LAYOUT_X; i > -1; i -= LAYOUT_Y){
            scalar--;
            if(sliders[i].getImageRes() == R.drawable.piece9){
                swapUP(idx, scalar);
                return true;
            }
        }
        return false;
    }

    /**
     * 下方向スライド判定メソッド
     *
     * @param 要素番号 idx(int)
     */
    private boolean vectorDirDown(int idx) {
        int scalar = 0;
        for(int i = idx + LAYOUT_Y; i < LAYOUT_X * LAYOUT_Y; i += LAYOUT_Y){
            scalar++;
            if(sliders&#91;i&#93;.getImageRes() == R.drawable.piece9){
                swapDown(idx, scalar);
                return true;
            }
        }
        return false;
    }

    /**
     * 左方向スライド判定メソッド
     *
     * @param 要素番号 idx(int)
     */
    private boolean vectorDirLeft(int idx) {
        int scalar = 0;
        int min = idx - (idx % LAYOUT_X);
        
        for(int i = idx - 1; i >= min; i--){
            scalar--;
            if(sliders[i].getImageRes() == R.drawable.piece9){
                swapLeft(idx, scalar);
                return true;
            }
        }
        return false;
    }

    /**
     * 右方向スライド判定メソッド
     *
     * @param 要素番号 idx(int)
     */
    private boolean vectorDirRight(int idx) {
        int scalar = 0;
        int max = (idx + LAYOUT_X) - ((idx + LAYOUT_X) % LAYOUT_X);
        
        for(int i = idx + 1; i < max; i++){
            scalar++;
            if(sliders&#91;i&#93;.getImageRes() == R.drawable.piece9){
                swapRight(idx, scalar);
                return true;
            }
        }
        return false;
    }

    /**
     * 下方向へスライド
     *
     * @param 要素番号 idx(int), 移動量 scalar(int)
     */
    private void swapDown(int idx, int scalar) {

        for(int i = idx + (scalar * LAYOUT_Y); i > idx; i -= LAYOUT_X){
            sliders[i].swapImage(sliders[i - LAYOUT_X]);
        }
    }

    /**
     * 上方向へスライド
     *
     * @param 要素番号 idx(int), 移動量 scalar(int)
     */
    private void swapUP(int idx, int scalar) {

        for(int i = idx + (scalar * LAYOUT_Y); i < idx; i += LAYOUT_X){
            sliders&#91;i&#93;.swapImage(sliders&#91;i + LAYOUT_X&#93;);
        }
    }

    /**
     * 右方向へスライド
     *
     * @param 要素番号 idx(int), 移動量 scalar(int)
     */
    private void swapRight(int idx, int scalar) {

        for(int i = idx + scalar; i > idx; i--){
            sliders[i].swapImage(sliders[i - 1]);
        }
    }

    /**
     * 左方向へスライド
     *
     * @param 要素番号 idx(int), 移動量 scalar(int)
     */
    private void swapLeft(int idx, int scalar) {

        for(int i = idx + scalar; i < idx; i++){
            sliders&#91;i&#93;.swapImage(sliders&#91;i + 1&#93;);
        }
    }
&#91;/java&#93;
<h5>完了処理</h5>
最後にパズルが正解の位置に達したときにダイアログと経過時間を表示する部分を記述します。

    /**
     * クリア後の処理
     *
     */
    private void complete(){
        // 経過時間を取得
        long msec = stopChronometer();
        
        // ダイアログの生成
        AlertDialog.Builder alertDlgBld = new AlertDialog.Builder(this);
        alertDlgBld.setTitle("正解");
        alertDlgBld.setMessage(msec / 1000 + "秒");
        alertDlgBld.setPositiveButton(
                "OK", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        dialog.dismiss();
                    }
                });
        alertDlgBld.show();
        
        // スタートフラグを初期化する
        isStart = false;
    }

    /**
     * クロノメーター停止
     *
     */
    private long stopChronometer() {
        Chronometer chrono = 
                (Chronometer)findViewById(R.id.chronometer1);
        chrono.stop();
        return SystemClock.elapsedRealtime() - chrono.getBase();
    }

一応これでスライドパズルの完成です。

次回以降はアプリのタイトルや広告の入れ方、マーケットへのアップ方法を書いていきたいと思います。



Post Footer automatically generated by Add Post Footer Plugin for wordpress.

びのっち

関東圏で活動しているとてもマイペースなSEです。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

*