Skip to main content

第2回 盤面の作成

盤面を作って表示しよう

まずは、スライドパズルの盤面をプログラムの中に作って、画面に表示してみよう!

ここでは、次の4つを作ります。

  • Board構造体
  • Position構造体
  • InitBoard()
  • PrintBoard()

Visual Studioで使う準備

Visual Studioでscanf()などを使うと、C4996の警告が出ることがあります。

授業でいつも使っているように、ファイルの一番上へ次の1行を書いておこう!

#define _CRT_SECURE_NO_WARNINGS

そのあとに、標準入出力を使うためのstdio.hを読み込みます。

#define _CRT_SECURE_NO_WARNINGS

#include <stdio.h>

_CRT_SECURE_NO_WARNINGSは、#include <stdio.h>より前に書きます。

この先も、この2行はソースファイルの先頭に置いたまま使います。


1.盤面を構造体で表そう

今回作るスライドパズルは、横4マス、縦4マスです。

完成状態は次のようになります。

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

全部で16マスあるので、タイルの並びは配列で保存できます。

int tile[16];

この配列を「盤面」という1つのデータとして扱えるように、構造体にまとめてみよう!

盤面で使う定数を用意しよう!

#define BOARD_WIDTH 4
#define BOARD_HEIGHT 4
#define BLANK_TILE (BOARD_WIDTH * BOARD_HEIGHT)

BLANK_TILEは盤面のマス数と同じ16になり、空白を表す番号として使います。1~15は数字のタイルです。

穴埋め

typedef struct
{
    // 横のマス数 × 縦のマス数ぶんの要素を用意する
    int tile[____________________________];
} Board;

2.位置も構造体にしよう

これから、

空白はどこにある?
11番のタイルはどこにある?

という処理が必要になります。

位置は、

x → 横の位置
y → 縦の位置

で表します。

x・y座標の考え方

穴埋め

typedef struct
{
    // 横方向の位置を表すメンバ
    int ______;

    // 縦方向の位置を表すメンバ
    int ______;
} Position;

完成形

模範解答を見る
typedef struct
{
    int tile[BOARD_WIDTH * BOARD_HEIGHT];
} Board;

typedef struct
{
    int x;
    int y;
} Position;

これで、

Board board;
Position pos;

のように、盤面と位置をそれぞれ1つの型として扱えます。


3.構造体を関数へ渡してみよう!

このあと作るInitBoard()では、Board構造体を関数へ渡します。

void InitBoard(Board* board)

ここに出てくる、

Board* board

は、Board構造体を指すポインタです。

構造体を関数へ渡すときは、構造体そのものを丸ごと渡すのではなく、ポインタで渡すと効率よく扱えます。

詳しい仕組みは、今は秘密にしておきます。

まずは、

構造体を関数へ渡すときはポインタで渡す

と覚えておこう!

そして、ポインタで渡された構造体のメンバへアクセスするときは、.ではなく->を使います。

board->tile

これは、

boardが指しているBoard構造体のtile

という意味です。

このあと、

board->tile[i]

という書き方が何度も出てきます。


4.完成状態の盤面を作ろう

次は、Boardの中に完成状態のデータを入れてみよう!

配列では次のようになります。

添字   0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15
値     1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16

よく見ると、

入れる数字 = 配列の添字 + 1

です。

この規則を使って、盤面を初期化するInitBoard()を作ってみよう!

穴埋め

void InitBoard(Board* board)
{
    // 横のマス数 × 縦のマス数だけ繰り返す
    for (int i = 0; i < ____________________________; i++)
    {
        // iが0なら1、1なら2…となるようにタイル番号を入れる
        board->tile[i] = __________;
    }
}

模範解答

模範解答を見る
void InitBoard(Board* board)
{
    for (int i = 0; i < BOARD_WIDTH * BOARD_HEIGHT; i++)
    {
        board->tile[i] = i + 1;
    }
}

5.盤面を表示しよう

盤面ができたら、Boardの内容を4×4に表示してみよう!

tileは1次元配列ですが、画面では2次元に並べたいので、二重ループを使います。

for (int y = 0; y < BOARD_HEIGHT; y++)
{
    for (int x = 0; x < BOARD_WIDTH; x++)
    {
        // 1マス表示
    }

    printf("\n");
}

1次元配列と盤面の対応

ここで必要になるのが、xとyから配列の添字を求める式です。

index = y * BOARD_WIDTH + x;

穴埋め

void PrintBoard(Board* board)
{
    for (int y = 0; y < BOARD_HEIGHT; y++)
    {
        for (int x = 0; x < BOARD_WIDTH; x++)
        {
            // xとyから1次元配列の添字を求める
            int index = ____________________________;

            // このマスが空白なら数字を表示しない
            if (board->tile[index] == __________________)
            {
                printf("    ");
            }
            else
            {
                printf("%3d ", board->tile[index]);
            }
        }

        printf("\n");
    }
}

模範解答

模範解答を見る
void PrintBoard(Board* board)
{
    for (int y = 0; y < BOARD_HEIGHT; y++)
    {
        for (int x = 0; x < BOARD_WIDTH; x++)
        {
            int index = y * BOARD_WIDTH + x;

            if (board->tile[index] == BLANK_TILE)
            {
                printf("    ");
            }
            else
            {
                printf("%3d ", board->tile[index]);
            }
        }

        printf("\n");
    }
}

6.動作確認

int main(void)
{
    Board board;

    InitBoard(&board);
    PrintBoard(&board);

    return 0;
}

次のように表示されれば成功です。

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

ここまでで、

盤面を作る
↓
盤面を表示する

ところまで完成しました。

盤面を表示できたね。次は、指定したタイルの位置を探して、動かせるか調べてみよう!