Skip to main content

第6回 荷物を押そう

第6回 荷物を押そう

前回は、床とゴールなら歩けて、壁や荷物では止まるところまで作りました。

今回は荷物を押せるようにします。

荷物を押すときは、コスコマンの移動先だけでは足りません。

コスコマン → 荷物 → 荷物の移動先

まで見る必要があります。

荷物を押すときに見る位置

今回の流れ

コスコマンの1マス先を見る
        ↓
そこが荷物なら、さらに1マス先を見る
        ↓
荷物を押せるか判断する
        ↓
current・next・nextNextを見る
        ↓
押した後の3マスの状態を決める

1.まず押せる場合と押せない場合を整理しよう

荷物のさらに先が、

床     → 押せる
ゴール → 押せる
壁     → 押せない
荷物   → 押せない
外     → 押せない

ならよさそうです。

荷物を押せるパターン・押せないパターン

実際には、コスコマンや荷物がゴール上にいる場合もあります。次の図では、押せる8通りと押せない4通りを、押す前と押したあとで見比べられます。

荷物を押すときの全パターン

緑の欄では、current、next、nextNextの3マスが押したあとにどう変わるかを確認できます。赤の欄は、盤面を変更せずMOVE_NONEを返す場合です。

また、荷物自身にも、

LUGG          普通の床にある荷物
LUGG_ON_GOAL  ゴール上にある荷物

の2種類があります。

コスコマンも、

HUMAN          普通の床にいる
HUMAN_ON_GOAL  ゴール上にいる

の2種類です。

これは、第1回で決めた「重なった状態にも専用の値を用意する」という方針を、プログラム上の名前で表したものです。

荷物を押したあと、3つのマスに何があるかを考えます。

コスコマンが移動したあとの場所
    何もない場所なら FLOOR
    ゴールなら       GOAL

荷物がいた場所
    コスコマンが入る
    普通の場所なら   HUMAN
    ゴールなら       HUMAN_ON_GOAL

荷物の移動先
    普通の場所なら   LUGG
    ゴールなら       LUGG_ON_GOAL

これを図の「押せる」①~⑧と対応させると、次のようになります。

見る値 図の番号 移動後の状態
current == HUMAN ①②⑤⑥ コスコマンがいなくなった場所はFLOOR
current == HUMAN_ON_GOAL ③④⑦⑧ コスコマンがいなくなった場所はGOAL
next == LUGG ①②③④ 荷物がいた場所へHUMANが入る
next == LUGG_ON_GOAL ⑤⑥⑦⑧ 荷物がいた場所へHUMAN_ON_GOALが入る
nextNext == FLOOR ①③⑤⑦ 荷物の移動先はLUGG
nextNext == GOAL ②④⑥⑧ 荷物の移動先はLUGG_ON_GOAL

図には押せる8通り、押せない4通りがあります。

もちろん、図の12通りをそのまま12個のif文にしても処理は作れます。

しかし図の番号を見比べると、同じ条件で同じ値を書き込むパターンをまとめられます。

そこで、図の番号を手掛かりに、12通りを少ない条件分岐へまとめていきます。


2.MoveResultへ荷物押しを追加しよう

第5回では、

enum MoveResult
{
    MOVE_NONE,
    MOVE_WALK
};

でした。

荷物を押した結果も追加します。

enum MoveResult
{
    MOVE_NONE,
    MOVE_WALK,
    MOVE_PUSH,
    MOVE_PUSH_GOAL
};

MOVE_PUSH_GOALは、荷物をゴールへ押し込んだ場合です。

今は戻り値として使うだけですが、後で効果音を付けるときにもこの結果を使います。


3.荷物のさらに先を求めよう

MoveObject()には、すでに、

Point playerPos;
Point nextPos;
OBJNAME current;
OBJNAME next;

があります。

nextが荷物だったときだけ、そのさらに先を調べます。

MoveObject()は長くなるので、関数全体はここで繰り返しません。第5回で作った歩く処理と、最後のreturn MOVE_NONE;の間へ、荷物を押す処理を1つ追加します。

if (next == FLOOR || next == GOAL)
{
    // 第5回で完成した「歩く処理」は変更しない
}

// ↓ここから第6回で追加
// 移動先が荷物だった場合のifを作り、
// 第3節~第7節の処理をその中へ上から順に追加する

return MOVE_NONE;

以降の第3節~第7節では、この新しいifの中身だけを順番に作ります。毎回MoveObject()全体を載せないので、上の位置を目印にしてください。

// 移動先が普通の荷物またはゴール上の荷物か調べる
if (next == ____________________ || next == ____________________)
{
    Point nextNextPos = {
        // 荷物の位置から、同じ方向へもう1マス進んだxを求める
        ________________________________,

        // 荷物の位置から、同じ方向へもう1マス進んだyを求める
        ________________________________
    };

    // 荷物のさらに先に何があるか調べる
    OBJNAME nextNext = ________________________________;
}

ヒント

荷物はnextPosにあります。そこへ、コスコマンと同じDIR_VECTOR[dir]をもう一度足します。

模範解答を見る
if (next == LUGG || next == LUGG_ON_GOAL)
{
    Point nextNextPos = {
        nextPos.x + DIR_VECTOR[dir].x,
        nextPos.y + DIR_VECTOR[dir].y
    };

    OBJNAME nextNext = GetObjectNum(nextNextPos, map);
}

4.荷物の先が空いていなければ押さない

荷物の先へ荷物を置けるのは、床かゴールだけです。

同じifの中で、先ほど作ったnextNextの直後へ追加します。

// 図「押せない」①~④:nextNextがFLOORではないか調べる
if (nextNext != ____________________ &&
    // nextNextがGOALでもないか調べる
    nextNext != ____________________)
{
    // どちらでもなければ「押せない」を返す
    return ____________________;
}

ヒント

「押してよい2種類以外なら失敗」と考えると、壁、別の荷物、ステージ外をまとめて止められます。

模範解答を見る
// 図「押せない」①~④
if (nextNext != FLOOR && nextNext != GOAL)
{
    return MOVE_NONE;
}

実行確認の準備

この段階では、まだ荷物を動かす処理を書いていないので、ビルド確認だけで構いません。


5.コスコマンが移動したあとのマスを決めよう

まず、コスコマンが移動したあとのplayerPosを考えます。

コスコマンがいなくなったあと、**何もない場所はFLOOR**です。
ゴールの場所ならGOALです。

図では、

①②⑤⑥  current == HUMAN
③④⑦⑧  current == HUMAN_ON_GOAL

なので、currentを見れば移動後のマスを決められます。

この節から第7節までの処理も、引き続き第3節で追加した荷物判定のifの中へ、上から順に続けます。

// 図「押せる」③④⑦⑧:ゴール上のコスコマンが移動したあと
if (current == HUMAN_ON_GOAL)
{
    // この場所はGOALになる
    SetObjtoMap(____________________, playerPos, map);
}
else
{
    // 図「押せる」①②⑤⑥:コスコマンが移動したあと
    // 何もない場所はFLOORになる
    SetObjtoMap(____________________, playerPos, map);
}

ヒント

コスコマンが移動したあとの場所を考えます。
普通の場所ならFLOOR、ゴールならGOALです。

模範解答を見る
// 図「押せる」③④⑦⑧
if (current == HUMAN_ON_GOAL)
{
    SetObjtoMap(GOAL, playerPos, map);
}
else
{
    // 図「押せる」①②⑤⑥
    SetObjtoMap(FLOOR, playerPos, map);
}

6.荷物がいた場所へコスコマンを移そう

荷物を押すと、コスコマンは荷物がいたnextPosへ移動します。

普通の場所ならHUMAN、ゴールならHUMAN_ON_GOALです。

図では、

①②③④  next == LUGG
⑤⑥⑦⑧  next == LUGG_ON_GOAL

なので、nextを見れば移動後のコスコマンの状態を決められます。

// 図「押せる」⑤⑥⑦⑧:ゴール上の荷物を押したあと
if (next == LUGG_ON_GOAL)
{
    // この場所へHUMAN_ON_GOALが入る
    SetObjtoMap(____________________, nextPos, map);
}
else
{
    // 図「押せる」①②③④:普通の荷物を押したあと
    // この場所へHUMANが入る
    SetObjtoMap(____________________, nextPos, map);
}

ヒント

荷物が移動したあと、その場所にはコスコマンが入ります。
普通の場所ならHUMAN、ゴールならHUMAN_ON_GOALです。

模範解答を見る
// 図「押せる」⑤⑥⑦⑧
if (next == LUGG_ON_GOAL)
{
    SetObjtoMap(HUMAN_ON_GOAL, nextPos, map);
}
else
{
    // 図「押せる」①②③④
    SetObjtoMap(HUMAN, nextPos, map);
}

7.荷物の移動先を決めよう

最後に、荷物が移動するnextNextPosを考えます。

普通の場所へ押したならLUGG、ゴールへ押したならLUGG_ON_GOALです。

図では、

①③⑤⑦  nextNext == FLOOR
②④⑥⑧  nextNext == GOAL

なので、nextNextを見れば移動後の荷物の状態を決められます。

// 図「押せる」②④⑥⑧:荷物の移動先がGOAL
if (nextNext == GOAL)
{
    // ゴール上の荷物になる
    SetObjtoMap(____________________, nextNextPos, map);

    // ゴールへ荷物を押した結果を返す
    return ____________________;
}

// 図「押せる」①③⑤⑦:荷物の移動先がFLOOR
// 普通の荷物になる
SetObjtoMap(____________________, nextNextPos, map);

// 普通に荷物を押した結果を返す
return ____________________;

ヒント

荷物の移動先だけを考えます。
普通の場所ならLUGG、ゴールならLUGG_ON_GOALです。

模範解答を見る
// 図「押せる」②④⑥⑧
if (nextNext == GOAL)
{
    SetObjtoMap(LUGG_ON_GOAL, nextNextPos, map);
    return MOVE_PUSH_GOAL;
}

// 図「押せる」①③⑤⑦
SetObjtoMap(LUGG, nextNextPos, map);
return MOVE_PUSH;

8.荷物押し全体を確認しよう

MoveObject()の考え方は、次の順になりました。

移動先が床/ゴール?
        ↓ YES
      歩く

        NO
        ↓
移動先が荷物?
        ↓ YES
荷物のさらに先を調べる
        ↓
床/ゴールなら押す
        ↓
それ以外なら押さない

12通りを全部別々に書かず、

  • コスコマンが移動したあとの場所
  • 荷物がいた場所
  • 荷物の移動先

の3か所に分けて考えると、図の番号をまとめて条件分岐にできます。

実行して確認

次を試してください。

  • 荷物を普通の床へ押せる
  • 荷物の先が壁なら押せない
  • 荷物の先が別の荷物なら押せない
  • 荷物をゴールへ押すと*になる
  • LUGG_ON_GOALの荷物を押すと、そのマスが.になる

これで、倉庫番の中心となる「荷物を押す」処理ができました。

次回はクリア判定と複数ステージを追加して、文字版を完成させます。


9.第6回の完成コード

第6回の完成コードを見る
#include "DxLib.h"

#define SCREEN_WIDTH  720
#define SCREEN_HEIGHT 600

#define MAX_STAGE_WIDTH  10
#define MAX_STAGE_HEIGHT 10

#define TEXT_SIZE 32
#define TEXT_STAGE_X 40
#define TEXT_STAGE_Y 90

enum OBJNAME
{
    FLOOR = 0,
    WALL = 1,
    LUGG = 2,
    GOAL = 3,
    HUMAN = 4,
    HUMAN_ON_GOAL = 5,
    LUGG_ON_GOAL = 6,
    OUTSIDE = 7
};

struct Point
{
    int x;
    int y;
};

struct Map
{
    int stage_width;
    int stage_height;
    int Dat[MAX_STAGE_HEIGHT][MAX_STAGE_WIDTH];
};

const char OBJECT_CHAR[] = {
    ' ',
    '#',
    '$',
    '.',
    '@',
    '+',
    '*',
    ' '
};

enum Direction
{
    DIR_UP,
    DIR_LEFT,
    DIR_DOWN,
    DIR_RIGHT,
    DIR_NONE
};

enum MoveResult
{
    MOVE_NONE,
    MOVE_WALK,
    MOVE_PUSH,
    MOVE_PUSH_GOAL
};

const Point DIR_VECTOR[] = {
    { 0, -1 },
    {-1,  0 },
    { 0, +1 },
    {+1,  0 },
    { 0,  0 }
};

const Map START_STAGE = {
    10, 7,
    {
        { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
        { 1, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
        { 1, 0, 0, 0, 3, 0, 0, 0, 0, 1 },
        { 1, 0, 0, 0, 2, 0, 0, 0, 0, 1 },
        { 1, 0, 0, 0, 4, 0, 0, 0, 0, 1 },
        { 1, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
        { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }
    }
};

void LoadStage(Map* map);
void DrawStageText(Map* map, int fontHandle);
Direction GetDirection(void);
Point GetPlayerPos(Map* map);
OBJNAME GetObjectNum(Point pos, Map* map);
void SetObjtoMap(OBJNAME obj, Point pos, Map* map);
int IsKeyPressed(int key);
MoveResult MoveObject(Direction dir, Map* map);

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    Map currentMap;
    int fontHandle;

    ChangeWindowMode(TRUE);
    SetGraphMode(SCREEN_WIDTH, SCREEN_HEIGHT, 32);
    SetMainWindowText("Costco Man");

    if (DxLib_Init() != 0)
        return -1;

    SetDrawScreen(DX_SCREEN_BACK);

    fontHandle = CreateFontToHandle("MS ゴシック", TEXT_SIZE, 1);
    LoadStage(&currentMap);

    while (ProcessMessage() == 0)
    {
        ClearDrawScreen();

        Direction dir = GetDirection();

        if (dir != DIR_NONE)
            MoveObject(dir, &currentMap);

        DrawString(24, 24, "COSTCO MAN", GetColor(255, 255, 255));
        DrawStageText(&currentMap, fontHandle);

        ScreenFlip();
    }

    DeleteFontToHandle(fontHandle);
    DxLib_End();
    return 0;
}

void LoadStage(Map* map)
{
    map->stage_width = START_STAGE.stage_width;
    map->stage_height = START_STAGE.stage_height;

    for (int y = 0; y < map->stage_height; y++)
    {
        for (int x = 0; x < map->stage_width; x++)
        {
            map->Dat[y][x] = START_STAGE.Dat[y][x];
        }
    }
}

void DrawStageText(Map* map, int fontHandle)
{
    char line[MAX_STAGE_WIDTH + 1];

    for (int y = 0; y < map->stage_height; y++)
    {
        for (int x = 0; x < map->stage_width; x++)
        {
            int obj = map->Dat[y][x];
            line[x] = OBJECT_CHAR[obj];
        }

        line[map->stage_width] = '\0';

        DrawStringToHandle(
            TEXT_STAGE_X,
            TEXT_STAGE_Y + y * TEXT_SIZE,
            line,
            GetColor(255, 255, 255),
            fontHandle);
    }
}

Direction GetDirection(void)
{
    // 4方向のキーが押された瞬間を調べる
    int up = IsKeyPressed(KEY_INPUT_W);
    int left = IsKeyPressed(KEY_INPUT_A);
    int down = IsKeyPressed(KEY_INPUT_S);
    int right = IsKeyPressed(KEY_INPUT_D);

    if (up)
    {
        return DIR_UP;
    }
    if (left)
    {
        return DIR_LEFT;
    }
    if (down)
    {
        return DIR_DOWN;
    }
    if (right)
    {
        return DIR_RIGHT;
    }

    return DIR_NONE;
}

Point GetPlayerPos(Map* map)
{
    Point result = { -1, -1 };

    for (int y = 0; y < map->stage_height; y++)
    {
        for (int x = 0; x < map->stage_width; x++)
        {
            int obj = map->Dat[y][x];

            if (obj == HUMAN || obj == HUMAN_ON_GOAL)
            {
                result.x = x;
                result.y = y;
                return result;
            }
        }
    }

    return result;
}

OBJNAME GetObjectNum(Point pos, Map* map)
{
    if (pos.x < 0 || pos.x >= map->stage_width || pos.y < 0 || pos.y >= map->stage_height)
        return OUTSIDE;

    return (OBJNAME)map->Dat[pos.y][pos.x];
}

void SetObjtoMap(OBJNAME obj, Point pos, Map* map)
{
    map->Dat[pos.y][pos.x] = obj;
}

int IsKeyPressed(int key)
{
    static int previous[256] = { 0 };
    int current = CheckHitKey(key);
    int pressed = current != 0 && previous[key] == 0;

    previous[key] = current;
    return pressed;
}

MoveResult MoveObject(Direction dir, Map* map)
{
    Point playerPos = GetPlayerPos(map);

    Point nextPos = {
        playerPos.x + DIR_VECTOR[dir].x,
        playerPos.y + DIR_VECTOR[dir].y
    };

    OBJNAME current = GetObjectNum(playerPos, map);
    OBJNAME next = GetObjectNum(nextPos, map);

    if (next == FLOOR || next == GOAL)
    {
        if (current == HUMAN_ON_GOAL)
            SetObjtoMap(GOAL, playerPos, map);
        else
            SetObjtoMap(FLOOR, playerPos, map);

        if (next == GOAL)
            SetObjtoMap(HUMAN_ON_GOAL, nextPos, map);
        else
            SetObjtoMap(HUMAN, nextPos, map);

        return MOVE_WALK;
    }

    if (next == LUGG || next == LUGG_ON_GOAL)
    {
        Point nextNextPos = {
            nextPos.x + DIR_VECTOR[dir].x,
            nextPos.y + DIR_VECTOR[dir].y
        };

        OBJNAME nextNext = GetObjectNum(nextNextPos, map);

        if (nextNext != FLOOR && nextNext != GOAL)
            return MOVE_NONE;

        if (current == HUMAN_ON_GOAL)
            SetObjtoMap(GOAL, playerPos, map);
        else
            SetObjtoMap(FLOOR, playerPos, map);

        if (next == LUGG_ON_GOAL)
            SetObjtoMap(HUMAN_ON_GOAL, nextPos, map);
        else
            SetObjtoMap(HUMAN, nextPos, map);

        if (nextNext == GOAL)
        {
            SetObjtoMap(LUGG_ON_GOAL, nextNextPos, map);
            return MOVE_PUSH_GOAL;
        }

        SetObjtoMap(LUGG, nextNextPos, map);
        return MOVE_PUSH;
    }

    return MOVE_NONE;
}
第6回の変更点入り完成コードを見る

このコードは、上の説明なし完成コードと同じ処理です。今回追加・変更した場所を中心に、役割や理由もコメントで確認できます。

  • プレイヤー・荷物・荷物の先という3マスを調べる押し処理
  • ゴール上の荷物とプレイヤーを正しい複合状態へ書き換える順序
  • 歩行・通常押し・ゴールへの押しをMoveResultで区別する理由
#include "DxLib.h"

#define SCREEN_WIDTH  720
#define SCREEN_HEIGHT 600

#define MAX_STAGE_WIDTH  10
#define MAX_STAGE_HEIGHT 10

#define TEXT_SIZE 32
#define TEXT_STAGE_X 40
#define TEXT_STAGE_Y 90

enum OBJNAME
{
    FLOOR = 0,
    WALL = 1,
    LUGG = 2,
    GOAL = 3,
    HUMAN = 4,
    HUMAN_ON_GOAL = 5,
    LUGG_ON_GOAL = 6,
    OUTSIDE = 7
};

struct Point
{
    int x;
    int y;
};

struct Map
{
    int stage_width;
    int stage_height;
    int Dat[MAX_STAGE_HEIGHT][MAX_STAGE_WIDTH];
};

const char OBJECT_CHAR[] = {
    ' ',
    '#',
    '$',
    '.',
    '@',
    '+',
    '*',
    ' '
};

enum Direction
{
    DIR_UP,
    DIR_LEFT,
    DIR_DOWN,
    DIR_RIGHT,
    DIR_NONE
};

enum MoveResult
{
    MOVE_NONE,
    MOVE_WALK,
    // 【解説・今回追加】荷物を床へ押した場合と、ゴールへ押した場合を別の結果として返す。
    MOVE_PUSH,
    MOVE_PUSH_GOAL
};

const Point DIR_VECTOR[] = {
    { 0, -1 },
    {-1,  0 },
    { 0, +1 },
    {+1,  0 },
    { 0,  0 }
};

const Map START_STAGE = {
    10, 7,
    {
        { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
        { 1, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
        { 1, 0, 0, 0, 3, 0, 0, 0, 0, 1 },
        { 1, 0, 0, 0, 2, 0, 0, 0, 0, 1 },
        { 1, 0, 0, 0, 4, 0, 0, 0, 0, 1 },
        { 1, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
        { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }
    }
};

void LoadStage(Map* map);
void DrawStageText(Map* map, int fontHandle);
Direction GetDirection(void);
Point GetPlayerPos(Map* map);
OBJNAME GetObjectNum(Point pos, Map* map);
void SetObjtoMap(OBJNAME obj, Point pos, Map* map);
int IsKeyPressed(int key);
MoveResult MoveObject(Direction dir, Map* map);

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    Map currentMap;
    int fontHandle;

    ChangeWindowMode(TRUE);
    SetGraphMode(SCREEN_WIDTH, SCREEN_HEIGHT, 32);
    SetMainWindowText("Costco Man");

    if (DxLib_Init() != 0)
        return -1;

    SetDrawScreen(DX_SCREEN_BACK);

    fontHandle = CreateFontToHandle("MS ゴシック", TEXT_SIZE, 1);
    LoadStage(&currentMap);

    while (ProcessMessage() == 0)
    {
        ClearDrawScreen();

        Direction dir = GetDirection();

        if (dir != DIR_NONE)
            MoveObject(dir, &currentMap);

        DrawString(24, 24, "COSTCO MAN", GetColor(255, 255, 255));
        DrawStageText(&currentMap, fontHandle);

        ScreenFlip();
    }

    DeleteFontToHandle(fontHandle);
    DxLib_End();
    return 0;
}

void LoadStage(Map* map)
{
    map->stage_width = START_STAGE.stage_width;
    map->stage_height = START_STAGE.stage_height;

    for (int y = 0; y < map->stage_height; y++)
    {
        for (int x = 0; x < map->stage_width; x++)
        {
            map->Dat[y][x] = START_STAGE.Dat[y][x];
        }
    }
}

void DrawStageText(Map* map, int fontHandle)
{
    char line[MAX_STAGE_WIDTH + 1];

    for (int y = 0; y < map->stage_height; y++)
    {
        for (int x = 0; x < map->stage_width; x++)
        {
            int obj = map->Dat[y][x];
            line[x] = OBJECT_CHAR[obj];
        }

        line[map->stage_width] = '\0';

        DrawStringToHandle(
            TEXT_STAGE_X,
            TEXT_STAGE_Y + y * TEXT_SIZE,
            line,
            GetColor(255, 255, 255),
            fontHandle);
    }
}

Direction GetDirection(void)
{
    // 4方向のキーが押された瞬間を調べる
    int up = IsKeyPressed(KEY_INPUT_W);
    int left = IsKeyPressed(KEY_INPUT_A);
    int down = IsKeyPressed(KEY_INPUT_S);
    int right = IsKeyPressed(KEY_INPUT_D);

    if (up)
    {
        return DIR_UP;
    }
    if (left)
    {
        return DIR_LEFT;
    }
    if (down)
    {
        return DIR_DOWN;
    }
    if (right)
    {
        return DIR_RIGHT;
    }

    return DIR_NONE;
}

Point GetPlayerPos(Map* map)
{
    Point result = { -1, -1 };

    for (int y = 0; y < map->stage_height; y++)
    {
        for (int x = 0; x < map->stage_width; x++)
        {
            int obj = map->Dat[y][x];

            if (obj == HUMAN || obj == HUMAN_ON_GOAL)
            {
                result.x = x;
                result.y = y;
                return result;
            }
        }
    }

    return result;
}

OBJNAME GetObjectNum(Point pos, Map* map)
{
    if (pos.x < 0 || pos.x >= map->stage_width || pos.y < 0 || pos.y >= map->stage_height)
        return OUTSIDE;

    return (OBJNAME)map->Dat[pos.y][pos.x];
}

void SetObjtoMap(OBJNAME obj, Point pos, Map* map)
{
    map->Dat[pos.y][pos.x] = obj;
}

int IsKeyPressed(int key)
{
    static int previous[256] = { 0 };
    int current = CheckHitKey(key);
    int pressed = current != 0 && previous[key] == 0;

    previous[key] = current;
    return pressed;
}

MoveResult MoveObject(Direction dir, Map* map)
{
    Point playerPos = GetPlayerPos(map);

    Point nextPos = {
        playerPos.x + DIR_VECTOR[dir].x,
        playerPos.y + DIR_VECTOR[dir].y
    };

    OBJNAME current = GetObjectNum(playerPos, map);
    OBJNAME next = GetObjectNum(nextPos, map);

    if (next == FLOOR || next == GOAL)
    {
        if (current == HUMAN_ON_GOAL)
            SetObjtoMap(GOAL, playerPos, map);
        else
            SetObjtoMap(FLOOR, playerPos, map);

        if (next == GOAL)
            SetObjtoMap(HUMAN_ON_GOAL, nextPos, map);
        else
            SetObjtoMap(HUMAN, nextPos, map);

        return MOVE_WALK;
    }

    // 【解説・今回追加】1マス先が荷物なら、さらにその先が空いているかを調べる。
    if (next == LUGG || next == LUGG_ON_GOAL)
    {
        // 【解説・流れ】移動先へ同じDIR_VECTORをもう一度足し、荷物の移動先を求める。
        Point nextNextPos = {
            nextPos.x + DIR_VECTOR[dir].x,
            nextPos.y + DIR_VECTOR[dir].y
        };

        // 【解説・判定】荷物の先のマスもGetObjectNumで安全に取得する。
        OBJNAME nextNext = GetObjectNum(nextNextPos, map);

        // 【解説・図】押せない①~④。nextNextがFLOORでもGOALでもなければ、Mapを書き換えずMOVE_NONEを返す。
        if (nextNext != FLOOR && nextNext != GOAL)
            return MOVE_NONE;

        // 【解説・図】押せる③④⑦⑧ではcurrentがHUMAN_ON_GOALなので、押した後のplayerPosへGOALを書く。
        if (current == HUMAN_ON_GOAL)
            SetObjtoMap(GOAL, playerPos, map);
        else
            // 【解説・図】押せる①②⑤⑥ではcurrentがHUMANなので、押した後のplayerPosへFLOORを書く。
            SetObjtoMap(FLOOR, playerPos, map);

        // 【解説・図】押せる⑤⑥⑦⑧ではnextがLUGG_ON_GOALなので、押した後のnextPosへHUMAN_ON_GOALを書く。
        if (next == LUGG_ON_GOAL)
            SetObjtoMap(HUMAN_ON_GOAL, nextPos, map);
        else
            // 【解説・図】押せる①②③④ではnextがLUGGなので、押した後のnextPosへHUMANを書く。
            SetObjtoMap(HUMAN, nextPos, map);

        // 【解説・図】押せる②④⑥⑧ではnextNextがGOALなので、押した後のnextNextPosへLUGG_ON_GOALを書く。
        if (nextNext == GOAL)
        {
            SetObjtoMap(LUGG_ON_GOAL, nextNextPos, map);
            return MOVE_PUSH_GOAL;
        }

        // 【解説・図】押せる①③⑤⑦ではnextNextがFLOORなので、押した後のnextNextPosへLUGGを書き、MOVE_PUSHを返す。
        SetObjtoMap(LUGG, nextNextPos, map);
        return MOVE_PUSH;
    }

    return MOVE_NONE;
}