C#で2048ゲームのクローンを作る[その3]

今回は、ゲームサイクルの作成を行いました。

以下の細々とした処理を追加し、1回分のゲーム開始->プレイ->ゲームオーバーまで、1周分のサイクルが作れました。

ゲーム状態の管理を追加(isGameOver変数)
KeyDown()に、ゲームオーバー判定を追加
KeyDown()に、1手動かすたびに新セルの生成処理を追加
initBoard()に、盤面の初期化処理(ランダムで2セル生成)を追加
addCell()として、新セルの生成のメソッドを追加
slideCell()に、スライドした結果セルの移動があったか否かを取得できるよう修正





一通り遊べるのですが、スライドさせたときのアニメーションが無いため非常に遊びづらいです。
よく見ながら操作しないと、特に、どのセルがどこに移動したのか非常に分かり辛いです。

ですので、次は表示の改善を行いたいところですが…
一方で、現状の1ファイルでの管理はそろそろ限界に近づいてきてます。
なので、次回は盤面を管理するBoardクラスの切り出しを行おうと考えています。


修正後のコードは以下のとおりです。
ちょっと長くなってきました…

using System;
using System.Collections.Generic;
using System.Data;
using System.Windows.Forms;
using System.Diagnostics;
 
namespace Mock2048 {
    enum DIR {
        UP,
        RIGHT,
        DOWN,
        LEFT,
    };
 
    public partial class Form1 : Form {
        const int BOARD_SIZE = 4;
 
        // ゲームの状態管理(ゲームオーバーしたか否か)
        bool isGameOver = false;
 
        // 乱数
        System.Random rand = new Random();
 
        // 盤面の情報
        private int[,] board = new int[BOARD_SIZE,BOARD_SIZE];      
 
        public Form1() {
            InitializeComponent();
        }
 
        //*********************************************************************
        /// <summary> 画面表示時のハンドラ
        /// </summary>
        //*********************************************************************
        private void Form1_Load( object sender, EventArgs e ) {
            // Formコントロール自身がキー入力を取得可能とする
            this.KeyPreview = true;
 
            //盤面の初期化
            initBoard();
 
            // 盤面を表示する
            displayBoard(); 
        }
 
 
        //*********************************************************************
        /// <summary> キー入力時のハンドラ
        /// </summary>
        //*********************************************************************
        private void Form1_KeyDown( object sender, KeyEventArgs e ) {
            DIR direction;
 
            // ゲームオーバーのときは何もしない
            if (isGameOver) {
                return;
            }
 
 
            // 指定された方向に移動させる
            switch (e.KeyCode) {
                case Keys.Up:    direction = DIR.UP;    break;
                case Keys.Right: direction = DIR.RIGHT; break;
                case Keys.Down:  direction = DIR.DOWN;  break;
                case Keys.Left:  direction = DIR.LEFT;  break;
                default:
                    // 矢印キー以外は無視
                    return; 
            }
            bool isMove = slideCell( direction );
            if ( !isMove ) {
                // スライドさせてみたが1セルも動かなかった -> 何もしなかったとみなす
                return;
            }
 
 
            // セルを追加する
            bool isSuccess = addCell();
            if (!isSuccess) {
                // セルが追加できない(=空セルがない)時は、ゲームオーバーとみなす
                isGameOver = true;
            }
 
            // 追加した結果、まだスライド可能かチェック
            if (!canSlide()) {
                // どの方向にもスライドできない場合は、詰みなのでゲームオーバー
                isGameOver = true;
            }
 
            // 盤面を表示する
            displayBoard();
        }
 
        //*********************************************************************
        /// <summary> 盤面の初期化を行う
        /// </summary>
        //*********************************************************************
        private void initBoard() {
            isGameOver = false;
 
            // ランダムに2マス埋める
            for (int loop = 0; loop < 2; loop++) {
                addCell();
            }
        }
 
        //*********************************************************************
        /// <summary> 空いているところに、1つセルを追加する
        /// </summary>
        /// <returns></returns>
        //*********************************************************************
        private bool addCell() {
 
            // あいているセルを1つ取得する
            Tuple<int,int> freePos = getEmptyCell_AtRandom();
            if (freePos == null) {
                return false; // 空セル無し
            }
 
            // 取得した空セルにセットする
            int val = rand.Next(1,3) * 2;
            board[freePos.Item1,freePos.Item2] = val;
 
            log( "  -> セットする値=" + val );
 
            return true;
        }
 
        //*********************************************************************
        /// <summary> 空セルをランダムで1つ取得する
        /// </summary>
        /// <returns></returns>
        //*********************************************************************
        private Tuple<int,int> getEmptyCell_AtRandom() {
            List<Tuple<int,int>> emptyCellList = new List<Tuple<int,int>>();
 
            // 空セルを全部Listに集める
            for (int y = 0; y < BOARD_SIZE; y++) {
                for (int x = 0; x < BOARD_SIZE; x++) {
                    if ( board[x,y] == 0 ) {
                        emptyCellList.Add( new Tuple<int,int>(x,y) );
                    }
                }
            }           
            if (emptyCellList.Count <= 0) {
                // 空セルが1つも無い! ->nullを返して終了。
                return null; 
            }
 
            // 空セルの中から1つをランダムで抽選する
            int offset = rand.Next(0, emptyCellList.Count-1);
            Tuple<int,int> emptyCell = emptyCellList[offset];
 
            log( "空セルを取得しました [" + emptyCell.Item1 + ", " + emptyCell.Item2 + "] 残りの空数=" + (emptyCellList.Count-1) );
            return emptyCell;
        }
 
        //*********************************************************************
        /// <summary> 盤面を画面に表示させる
        /// </summary>
        //*********************************************************************
        private void displayBoard() {
 
            // ゲームオーバーのときは描画しない
            if (isGameOver) {
                txtLog.Text = "GAME OVER";
                return;
            }
 
            string boardData = "";
 
            // すべての行のデータを出すまで繰り返し
            for (int y = 0; y < 4; y++) {
                // 1行分のデータを出力
                for (int x = 0; x < 4; x++) {
                    boardData += "[" + String.Format("{0, 2}", board[x,y]) + "] ";
                }
                boardData += Environment.NewLine;
                boardData += Environment.NewLine;
            }
 
            txtLog.Text = boardData;
 
            log( "** Boardを描画します**" );
            log( boardData );
 
            txtLog.Select(0,0);
            txtLog.ReadOnly = true;
        }
 
        //*********************************************************************
        /// <summary> セルのスライドが可能がチェックする
        /// </summary>
        /// <returns></returns>
        //*********************************************************************
        private bool canSlide() {
            // 上下に連続して同じ数字があるかチェック
            for (int x = 0; x < BOARD_SIZE; x++) {
                for (int y = 0; y < BOARD_SIZE; y++) {
                    if (board[x, y] == 0) {
                        // 空セルがある -> スライド可能
                        return true;
                    }
 
                    if (x != BOARD_SIZE-1 && board[x, y] == board[x+1, y]) {
                        // 右に同じ数字が続く場所がある -> スライド可能
                        return true;
                    }
                    if (y != BOARD_SIZE-1 && board[x, y] == board[x, y+1]) {
                        // 下に同じ数字が続く場所がある -> スライド可能
                        return true;
                    }
 
                }
            }
 
            // ここまできた -> どの方向にもスライドできない
            return false;
        }
 
        //*********************************************************************
        /// <summary> 指定された方向に移動させる
        /// </summary>
        /// <param name="keyCode"></param>
        //*********************************************************************
        private bool slideCell( DIR dir ) {
            bool isMove = false;
            bool retVal;
 
            // 入力されたキーを判定する
            switch ( dir ) {
                case DIR.UP:
                    // 2~4列目に対し上方向のスライドを試みる
                    for (int y = 1; y < BOARD_SIZE; y++) {
                        for (int x = 0; x < BOARD_SIZE; x++) {  
                            retVal = moveCell( x, y, DIR.UP );
                            isMove = (retVal==true) ? true : isMove;
                        }
                    }
                    break;
                case DIR.DOWN:
                    // 1~3列目に対し下方向のスライドを試みる
                    for (int y = BOARD_SIZE-2; y >= 0; y--) {
                        for (int x = 0; x < BOARD_SIZE; x++) {  
                            retVal = moveCell( x, y, DIR.DOWN );
                            isMove = (retVal==true) ? true : isMove;
                        }
                    }
                    break;
                case DIR.LEFT:
                    // 2~4行目に対し左方向のスライドを試みる
                    for (int y = 0; y < BOARD_SIZE; y++) {
                        for (int x = 1; x < 4; x++) {
                            retVal = moveCell( x, y, DIR.LEFT );
                            isMove = (retVal==true) ? true : isMove;
                        }
                    }
                    break;
                case DIR.RIGHT:
                    // 1~3行目に対し右方向のスライドを試みる
                    for (int y = 0; y < BOARD_SIZE; y++) {
                        for (int x = BOARD_SIZE-2; x >= 0; x--) {
                            retVal = moveCell( x, y, DIR.RIGHT );
                            isMove = (retVal==true) ? true : isMove;
                        }
                    }
                    break;
                default:
                    //txtLog.Text = "";
                    break;                        
            }
 
            log( "セルをスライドさせました 方向=" + dir.ToString() + " 移動あり?=" + isMove );
 
            return isMove;
        }
 
        //*********************************************************************
        /// <summary> 指定された方向にセルをスライドさせる
        /// </summary>
        /// <param name="x"></param>
        /// <param name="y"></param>
        /// <param name="dir"></param>
        //*********************************************************************
        private bool moveCell( int x, int y, DIR dir ) {
            int endX;
            int endY;
            bool isDuplicate;
 
            // 移動先のセル位置を求める
            getDestPos( x, y, dir, out endX, out endY, out isDuplicate );
 
            // セルを移動
            int targetVal = board[x,y];
            board[x,y] = 0;
            board[endX,endY] = targetVal * (isDuplicate ? 2 : 1 );
 
            // セルが動いたか否かを返す
            bool isMove;
            if (x == endX && y == endY) {
                isMove = false;
            } else {
                isMove = true;
            }
 
            return isMove;
        }
 
 
        //*********************************************************************
        /// <summary> 指定された方向にセルをスライドさせた時の移動先を求める
        /// </summary>
        /// <param name="x"></param>
        /// <param name="y"></param>
        /// <param name="dir"></param>
        /// <param name="endX"></param>
        /// <param name="endY"></param>
        /// <param name="isDuplicate"></param>
        //*********************************************************************
        private void getDestPos( int x, int y, DIR dir, out int endX, out int endY, out bool isDuplicate ) {
 
            // initialize
            endX = x;
            endY = y;
            isDuplicate = false;
 
            // 指定されたセルの値を取得
            int targetCellVal = board[x,y];
            if (targetCellVal == 0) {
                return;
            }
 
            // 移動方向を元に探索方向のオフセットを決める
            int dx = 1;
            int dy = 1;
            switch ( dir ) {
                case DIR.UP:    dx =  0; dy = -1; break;
                case DIR.RIGHT: dx =  1; dy =  0; break;
                case DIR.DOWN:  dx =  0; dy =  1; break;
                case DIR.LEFT:  dx = -1; dy =  0; break;
            }
 
 
            // 何かにぶつかるまで探索する
            while (true) {
                if (endX + dx < 0 || endY + dy < 0 || endX + dx >= BOARD_SIZE || endY + dy >= BOARD_SIZE) {
                    // 壁にぶつかった -> 1つ手前の場所でストップ
                    isDuplicate = false;
                    break;
                } else if (board[endX + dx, endY + dy] != 0) {
                    // 他のセルにぶつかった
                    if (targetCellVal == board[endX + dx, endY + dy]) {
                        // 衝突先が同じ値だった -> 重ねる
                        endX += dx;
                        endY += dy;
                        isDuplicate = true;
                        break;
                    } else {
                        // 衝突先が異なる値だった -> 1つ手前の場所でストップ
                        isDuplicate = false;
                        break;
                    }
                } else {
                    // 何にもぶつからなかった -> さらに次の位置をチェック
                    endX += dx;
                    endY += dy;
                    continue;
                }               
            }
 
            //log( "セルの移動を行います [" + x + ", " + y + "] -> [" + endX + ", " + endY + "] dup=" + isDuplicate );
        }
 
        //*********************************************************************
        /// <summary> デバッグログを出力する
        /// </summary>
        /// <param name="message"></param>
        //*********************************************************************
        private void log(string message) {
            Debug.WriteLine( message );
        }
    }
}




また、コンパイルしたexeも置いときます。
Mock2048_20160128.zip

関連記事

コメントを残す

メールアドレスが公開されることはありません。