Skip to main content

第4回 そのタイル動くよ!

タイルを動かそう

スライドパズルでは、動かせるタイルを選んだら、そのタイルを空白の場所へ移動します。

このページでは、実際にタイルを動かすMoveTile()を作ってみよう!

タイルを動かすには、

① そのタイルが動かせるか調べる
② 空白の位置を探す
③ 動かすタイルの位置を探す
④ タイルと空白の値を入れ替える

という処理が必要です。

①では前のページで作ったCanMoveTile()、②と③ではSearchTile()を使えます。

新しく作るMoveTile()では、これらを使いながら最後にタイルと空白の値を入れ替える処理を追加します。

SearchTile()とCanMoveTile()を使って、実際にタイルを動かすMoveTile()を作ってみよう!


1.タイルを動かすには何が必要?

例えば、次の盤面を考えます。

  1   2   3   4
  5   6   7   8
  9  10      12
 13  14  11  15

11番のタイルを動かすと、

  1   2   3   4
  5   6   7   8
  9  10  11  12
 13  14      15

になります。

実際にやっていることは、

11番のタイル

と、

空白

の場所を入れ替えているだけです。

タイルを空白へずらすイメージ


2.処理を順番に考えよう

いきなりプログラムを書く前に、まずは何をすればよいか順番に考えてみよう!

① 指定したタイルが動かせるか調べる
② 空白の位置を探す
③ 指定したタイルの位置を探す
④ 2つの位置を配列の添字に変換する
⑤ 配列の中身を入れ替える

使える関数を当てはめると、

① → CanMoveTile()
② → SearchTile()
③ → SearchTile()

でできます。

つまり今回新しく考えるのは、

位置を配列の添字に変換する
値を入れ替える

という部分です。


3.動かせないなら何もしない

まずは、指定したタイルが動かせるか調べよう!

動かせないなら、その場で関数を終了すればよいです。

if (CanMoveTile(board, number) == 0)
{
    return;
}

CanMoveTile()は、

動かせる   → 1
動かせない → 0

を返すので、0だったらその場でreturnします。

☑何もしないときは先にreturnでメインルーチンに戻っちゃって、なにかする時(出来るとき)だけ先に進む。
ゲームプログラミングではこの手法結構使います。(早期リターン)


4.空白とタイルの位置を探す

位置を探す処理には、作っておいたSearchTile()を使おう!

どの関数を使えばよいか考えて、穴を埋めしてみよう!

穴埋め

// 空白の位置を探す
Position blank = ________________________________;

// 指定したタイルの位置を探す
Position tile = _________________________________;

ヒント

空白を探すときは、

BLANK_TILE

を使います。

指定したタイルの番号は、

number

に入っています。


5.位置を配列の添字に変換する

SearchTile()で分かるのは、

x
y

です。

しかし、実際に書き換えるのは、

board->tile[index]

という1次元配列です。

盤面を表示するときに使った式をもう一度使います。

index = y × BOARD_WIDTH + x

穴埋め

// 空白のx・y座標を1次元配列の添字へ変換する
int blankIndex = ________________________________________;

// タイルのx・y座標を1次元配列の添字へ変換する
int tileIndex = _________________________________________;

ここも、自分で式を組み立ててください。


6.2つの値を入れ替える

例えば、

A = 11
B = 16

を入れ替えたいとします。

いきなり、

A = B;
B = A;

とすると、

最初の行でAが16になってしまうので、

A = 16
B = 16

となってしまいます。

そこで、一時的に値を保存する変数を使います。

int temp = A;

A = B;
B = temp;

これで、

A = 16
B = 11

と正しく入れ替えられます。

同じことを、タイルと空白で行います。

移動と元の位置へ戻す考え方


7.MoveTile()を完成させよう

これまでに作った関数を使って、MoveTile()を完成させよう!

void MoveTile(Board* board, int number)
{
    if (CanMoveTile(board, number) == 0)
    {
        return;
    }

    // 空白の位置を探す
    Position blank = ________________________________;

    // 指定したタイルの位置を探す
    Position tile = _________________________________;

    // 空白の位置を1次元配列の添字へ変換する
    int blankIndex = ________________________________________;

    // タイルの位置を1次元配列の添字へ変換する
    int tileIndex = _________________________________________;

    int temp = board->tile[blankIndex];

    // 空白の場所へタイルの値を入れる
    _________________________________________________;

    // 元のタイル位置へ、保存しておいた空白の値を入れる
    _________________________________________________;
}

8.動作確認をしよう

例えば、完成状態では15番のタイルを動かせます。

int main(void)
{
    Board board;

    InitBoard(&board);

    PrintBoard(&board);

    printf("\n");

    MoveTile(&board, 15);

    PrintBoard(&board);

    return 0;
}

最初は、

  1   2   3   4
  5   6   7   8
  9  10  11  12
 13  14  15

です。

15番を動かしたあと、

  1   2   3   4
  5   6   7   8
  9  10  11  12
 13  14      15

のように空白と15番が入れ替われば成功です。


9.模範解答

模範解答を見る
void MoveTile(Board* board, int number)
{
    if (CanMoveTile(board, number) == 0)
    {
        return;
    }

    Position blank = SearchTile(board, BLANK_TILE);
    Position tile = SearchTile(board, number);

    int blankIndex = blank.y * BOARD_WIDTH + blank.x;
    int tileIndex = tile.y * BOARD_WIDTH + tile.x;

    int temp = board->tile[blankIndex];

    board->tile[blankIndex] = board->tile[tileIndex];
    board->tile[tileIndex] = temp;
}

今回作ったのはMoveTile()です。


10.関数を組み合わせて処理を作った

今回のMoveTile()では、

を使っています。

つまり、

タイルを探す
動かせるか調べる

という処理をもう一度自分で書いたわけではありません。

前に作った関数を使って、

タイルを実際に動かす

という、さらに大きな処理を作っています。

関数を作るときは、

その関数の中ですべてを自分でやる必要はありません。

すでに使える関数があるなら、それを使った方が短く、分かりやすく書けます。

タイルを動かせるようになった!

このページでは、

動かせるか調べる
↓
空白とタイルの位置を探す
↓
2つの値を入れ替える

という流れでMoveTile()を作りました。

これで、番号を指定すれば、そのタイルが空白の隣にあるときだけ実際に移動できるようになりました。

次は、タイルを動かしたあとに盤面が完成したかどうかを判定します。調べてみよう!