Skip to main content

第6回 荷物を押そう

第6回 荷物を押そう

前回は、コスコマンが床とゴールの上を歩き、壁や荷物の前で止まる処理を作りました。

今回は、コスコマンの前にある荷物を1マス押せるようにします。

今回できるようになること

この回を完成させると、次の動きができるようになります。

  • 荷物の先が床なら、荷物を1マス押す
  • 荷物の先がゴールなら、荷物をゴールへ押す
  • 荷物の先が壁・別の荷物・Mapの外なら、何も動かさない
  • コスコマンや荷物がゴールから移動しても、ゴールを消さない

荷物を押すときは、横に並んだ3マスを調べます。

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

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


1.押せる場合と押せない場合に分けよう

コスコマンの1マス先が荷物だったら、さらにその先を調べます。

荷物の先がFLOORまたはGOAL
    → 押せる

それ以外
    → 押せない

処理も、この2つに分けます。

押せる場合
    3マスを書き換える

押せない場合
    何も変更しない

次の図では、押せる状態と押せない状態を、押す前と押したあとで確認できます。

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

押せない場合は、Mapを変更せずにMOVE_NONEを返します。

押せる場合は、次の3マスを書き換えます。

押す前

[ current ][ next ][ nextNext ]
 コスコマン  荷物    荷物の移動先

押したあと

1.コスコマンが移動したあとのマスを、`FLOOR`または`GOAL`にする
2.荷物がいた場所へ、コスコマンを移す
3.荷物の移動先へ、荷物を移す

書き込む値は、それぞれのマスが普通の床だったか、ゴールだったかで決まります。

コスコマンがいた場所
    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

2.荷物を押した結果を追加しよう

第5回のMoveResultには、歩いた結果まで入っています。

enum MoveResult
{
    MOVE_NONE,
    MOVE_WALK
};

荷物を押した結果として、次の2つを追加します。

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

それぞれの意味は次の通りです。

名前 意味
MOVE_PUSH 荷物を普通の床へ押した
MOVE_PUSH_GOAL 荷物をゴールへ押した

今回は移動結果として返します。後で効果音を付けるときは、この結果を使って音を選べます。


3.荷物押しを追加する場所を確認しよう

荷物を押す処理は、MoveObject()の中へ追加します。

次のコードは、第5回までに作った歩く処理と、今回追加する荷物押しの位置を示しています。

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と中の処理を追加する
    if (next == LUGG || next == LUGG_ON_GOAL)
    {
        // 荷物を押す処理
    }

    return MOVE_NONE;
}

今回完成させるのは、if (next == LUGG || next == LUGG_ON_GOAL)の中です。

完成するまでに、次の処理を順番に作ります。

  1. 荷物のさらに先の位置を求める
  2. そのマスに何があるか調べる
  3. 押せない場合は、何も変更せずに終了する
  4. コスコマンが移動したあとのマスを、FLOORまたはGOALにする
  5. 荷物がいたマスへ、コスコマンを移す
  6. 荷物をその先へ移す
  7. 荷物を押した結果を返す

最初の状態は次の形です。この中身を、次の節から順番に完成させます。

if (next == LUGG || next == LUGG_ON_GOAL)
{
    // 1.荷物のさらに先の位置を求める

    // 2.そのマスに何があるか調べる

    // 3.押せない場合は何も変更せずに終了する

    // 4.コスコマンが移動したあとのマスを書き換える

    // 5.荷物がいたマスへコスコマンを移す

    // 6.荷物をその先へ移す

    // 7.荷物を押した結果を返す
}

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

作る処理

荷物はnextPosにあります。そこから移動方向へもう1マス進んだ場所が、荷物の移動先nextNextPosです。

nextPos + DIR_VECTOR[dir] → nextNextPos

nextNextPosを求めたら、GetObjectNum()でそのマスの状態を取得し、nextNextへ入れます。

完成条件

  • nextがLUGGまたはLUGG_ON_GOALのときだけ処理する
  • nextNextPosのxとyを求める
  • 荷物の移動先にある値をnextNextへ入れる

次の空欄を埋めてください。

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

        // 荷物の位置から、移動方向へもう1マス進んだy座標
        ________________________________
    };

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

ヒント

nextPos.xとnextPos.yへ、DIR_VECTOR[dir]のxとyをそれぞれ足します。マスの状態はGetObjectNum()で取得します。

模範解答を見る
// 移動先が、普通の荷物またはゴール上の荷物か調べる
if (next == LUGG || next == LUGG_ON_GOAL)
{
    Point nextNextPos = {
        // 荷物の位置から、移動方向へもう1マス進んだx座標
        nextPos.x + DIR_VECTOR[dir].x,

        // 荷物の位置から、移動方向へもう1マス進んだy座標
        nextPos.y + DIR_VECTOR[dir].y
    };

    // 荷物のさらに先に何があるか調べる
    OBJNAME nextNext = GetObjectNum(nextNextPos, map);
}

5.押せない場合の処理を作ろう

作る処理

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

nextNextがFLOORまたはGOAL
    → この後の3マス書き換えへ進む

それ以外
    → Mapを変更せずMOVE_NONEを返す

この判定は、nextNextを取得した直後に行います。先にMapを書き換えると、押せない場合にも盤面が変わってしまいます。

完成条件

  • nextNextがFLOORでもGOALでもなければ、MOVE_NONEを返す
  • 押せない場合は、SetObjtoMap()を一度も呼ばない
  • nextNextがFLOORまたはGOALなら、その後の処理へ進む

次の空欄を埋めてください。

// 荷物の先がFLOORではないか調べる
if (nextNext != ____________________ &&
    // 荷物の先がGOALでもないか調べる
    nextNext != ____________________)
{
    // どちらでもなければ、Mapを変更せず「押せない」を返す
    return ____________________;
}

ヒント

「床ではない」かつ「ゴールでもない」なら押せません。押せないときの移動結果はMOVE_NONEです。

模範解答を見る
// 荷物の先がFLOORでもGOALでもなければ押せない
if (nextNext != FLOOR && nextNext != GOAL)
{
    // Mapを変更せず「押せない」を返す
    return MOVE_NONE;
}

6.コスコマンが移動したあとのマスを書き換えよう

作る処理

荷物を押せることが分かったら、3マスの書き換えを始めます。

最初に、コスコマンが移動したあとのplayerPosを書き換えます。

currentがHUMAN
    → playerPosをFLOORにする

currentがHUMAN_ON_GOAL
    → playerPosをGOALにする

完成条件

  • currentがHUMAN_ON_GOALなら、playerPosへGOALを書く
  • それ以外なら、playerPosへFLOORを書く

次の空欄を埋めてください。

// コスコマンがゴール上にいたか調べる
if (current == ____________________)
{
    // コスコマンが移動したあとのplayerPosをGOALにする
    SetObjtoMap(____________________, playerPos, map);
}
else
{
    // コスコマンが移動したあとのplayerPosをFLOORにする
    SetObjtoMap(____________________, playerPos, map);
}

ヒント

コスコマンがゴール上にいた場合、その場所から移動するとゴールだけが残ります。

模範解答を見る
// コスコマンがゴール上にいた場合
if (current == HUMAN_ON_GOAL)
{
    // コスコマンが移動したあとのplayerPosをGOALにする
    SetObjtoMap(GOAL, playerPos, map);
}
else
{
    // コスコマンが移動したあとのplayerPosをFLOORにする
    SetObjtoMap(FLOOR, playerPos, map);
}

7.荷物がいたマスへコスコマンを移そう

作る処理

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

nextがLUGG
    → nextPosへHUMANを書く

nextがLUGG_ON_GOAL
    → nextPosへHUMAN_ON_GOALを書く

完成条件

  • nextがLUGG_ON_GOALなら、nextPosへHUMAN_ON_GOALを書く
  • それ以外なら、nextPosへHUMANを書く

次の空欄を埋めてください。

// 荷物がゴール上にあったか調べる
if (next == ____________________)
{
    // 荷物がいた場所へ、ゴール上のコスコマンを置く
    SetObjtoMap(____________________, nextPos, map);
}
else
{
    // 荷物がいた場所へ、普通のコスコマンを置く
    SetObjtoMap(____________________, nextPos, map);
}

ヒント

ゴール上にあった荷物が移動すると、そのマスにはゴール上のコスコマンが入ります。

模範解答を見る
// 荷物がゴール上にあった場合
if (next == LUGG_ON_GOAL)
{
    // 荷物がいた場所へ、ゴール上のコスコマンを置く
    SetObjtoMap(HUMAN_ON_GOAL, nextPos, map);
}
else
{
    // 荷物がいた場所へ、普通のコスコマンを置く
    SetObjtoMap(HUMAN, nextPos, map);
}

8.荷物をその先へ移そう

作る処理

最後に、荷物をnextNextPosへ移します。

nextNextがFLOOR
    → nextNextPosへLUGGを書く
    → MOVE_PUSHを返す

nextNextがGOAL
    → nextNextPosへLUGG_ON_GOALを書く
    → MOVE_PUSH_GOALを返す

完成条件

  • 移動先がゴールなら、ゴール上の荷物を書いてMOVE_PUSH_GOALを返す
  • 移動先が普通の床なら、普通の荷物を書いてMOVE_PUSHを返す

次の空欄を埋めてください。

// 荷物の移動先がゴールか調べる
if (nextNext == ____________________)
{
    // 荷物の移動先へ、ゴール上の荷物を置く
    SetObjtoMap(____________________, nextNextPos, map);

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

// 荷物の移動先へ、普通の荷物を置く
SetObjtoMap(____________________, nextNextPos, map);

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

ヒント

ゴールへ荷物を置くとLUGG_ON_GOALになります。普通の床へ置くとLUGGになります。

模範解答を見る
// 荷物の移動先がゴールの場合
if (nextNext == GOAL)
{
    // 荷物の移動先へ、ゴール上の荷物を置く
    SetObjtoMap(LUGG_ON_GOAL, nextNextPos, map);

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

// 荷物の移動先へ、普通の荷物を置く
SetObjtoMap(LUGG, nextNextPos, map);

// 普通の床へ荷物を押した結果を返す
return MOVE_PUSH;

9.完成した荷物押し処理を確認しよう

if (next == LUGG || next == LUGG_ON_GOAL)の中が、次の形になれば荷物押しの処理は完成です。

if (next == LUGG || next == LUGG_ON_GOAL)
{
    // 1.荷物のさらに先の位置を求める
    Point nextNextPos = {
        nextPos.x + DIR_VECTOR[dir].x,
        nextPos.y + DIR_VECTOR[dir].y
    };

    // 2.荷物のさらに先に何があるか調べる
    OBJNAME nextNext = GetObjectNum(nextNextPos, map);

    // 3.押せない場合は、Mapを変更せずに終了する
    if (nextNext != FLOOR && nextNext != GOAL)
    {
        return MOVE_NONE;
    }

    // 4.コスコマンが移動したあとのマスを書き換える
    if (current == HUMAN_ON_GOAL)
    {
        SetObjtoMap(GOAL, playerPos, map);
    }
    else
    {
        SetObjtoMap(FLOOR, playerPos, map);
    }

    // 5.荷物がいたマスへコスコマンを移す
    if (next == LUGG_ON_GOAL)
    {
        SetObjtoMap(HUMAN_ON_GOAL, nextPos, map);
    }
    else
    {
        SetObjtoMap(HUMAN, nextPos, map);
    }

    // 6・7.荷物をゴールへ移し、結果を返す
    if (nextNext == GOAL)
    {
        SetObjtoMap(LUGG_ON_GOAL, nextNextPos, map);
        return MOVE_PUSH_GOAL;
    }

    // 6・7.荷物を普通の床へ移し、結果を返す
    SetObjtoMap(LUGG, nextNextPos, map);
    return MOVE_PUSH;
}

処理の順番を確認しよう

1マス先が荷物か調べる
        ↓
荷物のさらに先を調べる
        ↓
押せないなら、何も変更せず終了
        ↓
押せるなら、3マスを書き換える
        ↓
荷物を押した結果を返す

押せるかどうかの判定を、3マスの書き換えより先に行うことが重要です。

実行して確認

次の動きをすべて確認してください。

  • 荷物を普通の床へ押せる
  • 荷物をゴールへ押すと*になる
  • 荷物の先が壁なら、コスコマンも荷物も動かない
  • 荷物の先が別の荷物なら、コスコマンも荷物も動かない
  • ゴール上の荷物を押すと、荷物がいたマスのコスコマンが+になる
  • ゴール上にいるコスコマンが荷物を押すと、移動したあとのマスが.になる

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

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


10.第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 = 0;
    if (current != 0 && previous[key] == 0)
    {
        pressed = 1;
    }

    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 = 0;
    if (current != 0 && previous[key] == 0)
    {
        pressed = 1;
    }

    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;

        // 【解説・書き換え1】currentがHUMAN_ON_GOALなら、コスコマンが移動したあとのplayerPosをGOALにする。
        if (current == HUMAN_ON_GOAL)
            SetObjtoMap(GOAL, playerPos, map);
        else
            // 【解説・書き換え1】currentがHUMANなら、コスコマンが移動したあとのplayerPosをFLOORにする。
            SetObjtoMap(FLOOR, playerPos, map);

        // 【解説・書き換え2】nextがLUGG_ON_GOALなら、荷物がいたnextPosへHUMAN_ON_GOALを書く。
        if (next == LUGG_ON_GOAL)
            SetObjtoMap(HUMAN_ON_GOAL, nextPos, map);
        else
            // 【解説・書き換え2】nextがLUGGなら、荷物がいたnextPosへHUMANを書く。
            SetObjtoMap(HUMAN, nextPos, map);

        // 【解説・書き換え3】nextNextがGOALなら、荷物の移動先へLUGG_ON_GOALを書く。
        if (nextNext == GOAL)
        {
            SetObjtoMap(LUGG_ON_GOAL, nextNextPos, map);
            return MOVE_PUSH_GOAL;
        }

        // 【解説・書き換え3】nextNextがFLOORなら、荷物の移動先へLUGGを書き、MOVE_PUSHを返す。
        SetObjtoMap(LUGG, nextNextPos, map);
        return MOVE_PUSH;
    }

    return MOVE_NONE;
}