アメリカの卸業者COSTCOは、アメリカではカタカナで書くと(コスコ)と発音する。
ちなみにIKEAはアメリカではアイキアと読むのだが、スウェーデンではイケアと読む笑
ということで、倉庫の中の荷物を、目的の位置まで運ぶゲームを作ろうと思う。
とても似たようなゲームに倉庫番というゲームがあるらしい。。。
Web 倉庫番1
Web版をやってみるとわかると思うが、ルールは単純明快で、(来る日も来る日も、倉庫を片付ける仕事をする)倉庫番の労働者を操作して、荷物をすべて目的地まで片付けるパズルゲームである。
これをコンソール版で作ってみよう。いよいよキャラクターを操作するタイプのゲームである。
登場人物を@$#. (最後は半角スペース)で表す。
このようなステージは、
このような表示で表す。グラフィックがまだ使えないので、想像力でカバーしてほしい。
また、全角文字を使ったり、いろいろ表現を変えることもできるので見やすい表示にしたければやってみるといいと思う。
(全角使うと一番上の画像のように面白い表現法ができるけど、違うPCで実行したときに文字化けしたりしてとても面倒です。。。)
ちなみにこのようなコンソールのゲームは、Unix(Linuxの親玉)のシステム上でたくさん作られ、その後Windows(MS-DOS時代から)やその他のOSに移植された。
有名なものでは、Rogue、net-hackなどがあり、ストーリーや登場するアイテムや人物、モンスターなどは、トールキンの指輪物語や、D&D(Dangeons and Dragons)などを題材にしたものが多い。
これらのゲームはコンソール画面に出たキャラクターを、キーボードのエディタ操作を模した操作法で操作するため、遊んでいるうちに基本的な操作を覚えられたりと、いろいろ面白い要素がたくさんある。
軟派な派生品として、不思議のダンジョンシリーズなどがコンソールゲームとして発売されているが、ほぼ原作の丸パクリであることは有名である。
ということで、今回はこの想像力を鍛える画面で、ゲームを動かしていくことにする。
まずは、簡単なステージから作っていこう。
ステージは、先ほどの半角文字を2次元配列としてステージ型に並べちゃいます。
1次元配列と2次元配列の変換ができる人は1次元配列で持ってたほうが精神衛生上いい効果があります(笑)
2次元配列を1次元で表現(人のとこの記事だけど)
これを、intの配列で並べてあつかいます。
対応付けとして登場オブジェクトすべての組み合わせを書けるように以下のように表現する
(ゴールの上の荷物と、ゴールの上の人は、表現する文字を変えないとオブジェクト同士が重なってるのがわからないよねぇ。。。どうしよう)
//0 床 //1 壁 //2 荷物 //3 ゴール //4 人 //5 人 on the ゴール //6 荷物 on the ゴール //7 壁の外のゲームに使わない領域
const int MAX_STAGE_HEIGHT = 10; const int MAX_STAGE_WIDTH = 10; //行、列の順に並んでるから、HEIGHT,WIDTHの順に書くよ int stageData[MAX_STAGE_HEIGHT][MAX_STAGE_WIDTH] = { {1, 1, 1, 1, 7, 7, 7, 7, 7, 7}, {1, 3, 0, 0, 1, 7, 7, 7, 7, 7}, {1, 0, 2, 0, 1, 7, 7, 7, 7, 7}, {1, 0, 4, 0, 1, 7, 7, 7, 7, 7}, {1, 1, 1, 1, 1, 7, 7, 7, 7, 7}, {7, 7, 7, 7, 7, 7, 7, 7, 7, 7}, {7, 7, 7, 7, 7, 7, 7, 7, 7, 7}, {7, 7, 7, 7, 7, 7, 7, 7, 7, 7}, {7, 7, 7, 7, 7, 7, 7, 7, 7, 7}, {7, 7, 7, 7, 7, 7, 7, 7, 7, 7} };
後はこれを、表示する関数
void DrawStage(ステージのデータ)
を作ればいいんだけども、幅と高さの情報もステージに含めちゃうのと、配列を関数に渡すやり方をみんな知らんので、構造体に丸っと納めちゃいます。(あんまよくない)
struct Map { int stage_width; int stage_height; int Dat[MAX_STAGE_HEIGHT][MAX_STAGE_WIDTH]; };
ステージの最大の大きさは、STAGE_HEIGHT、STAGE_WIDTHで表されるけど、毎回全部使うわけではないので、そのステージの最大幅と、最大高さを、構造体のメンバstage_width,stage_heightで表す。
これのメンバを書いておくことで、無駄に全部の配列を毎回ループせずに済む(しても大した手間ではないんだけどね)
ステージのデータは以下のように作成する。(10×10の適当なステージを作ってみる)
Map sampleSatge = { 10,10, { {1,1,1,1,1,1,1,1,1,1}, {1,0,0,2,3,3,0,0,0,1}, {1,0,4,2,0,0,0,0,0,1}, {1,0,0,0,0,1,1,0,0,1}, {1,0,0,0,0,2,0,0,0,1}, {1,0,0,3,0,0,0,0,0,1}, {1,0,0,0,0,0,1,1,1,1}, {1,0,0,0,0,0,0,0,0,1}, {1,0,1,0,0,0,0,0,0,1}, {1,1,1,1,1,1,1,1,1,1} } }
次に、ステージデータを受け取り、ステージを描画する関数DrawStage()を作成する。
戻り値は、表示するだけなのでvoidで、引数はMapの参照を受け取ることにする。
void DrawStage(Map& _map);
ここで、表示のことを考えなければいけないのでゴール上の人、ゴール上の荷物も、何の文字で表すかを決める。
と思ったけど、実は似たようなゲームの倉庫番は、データの標準形が決まっていてXsokoban形式というのがある。
これを使おうと思う。参考ここ
マップデータの番号に対応したインデックスで、ゲームオブジェクトを表す文字の配列を作っておく
ついでに、0が床で、5がゴールに乗ってる人で、とか毎回照合するのがめんどくさいので、文字に数字充てて使いやすくしたい。
const int FLOOR = 0; const int WALL = 1; ・・・ const int MANONTHEGOAL = 5
とかでもいいけど、もっと楽ながあったよね!enumとかいうやつ。
ってことでenumで、番号中に名前つけていきます。
//0 床 //1 壁 //2 荷物 //3 ゴール //4 人 //5 人 on the ゴール //6 荷物 on the ゴール //7 壁の外のゲームに使わない領域 enum OBJNAME { FLOOR, WALL, LUGG, GOAL, HUMAN, HUMAN_ON_GOAL, LUGG_ON_GOAL
これで、マップの数字との比較などが楽になる。
もしpos.xの右隣の座標が壁だったら?という比較は、
//enumなしの場合 if(pos.x + 1 == 1){処理} //とかになる //enumを使ったとき if(pos.x + 1 == WALL){処理}
こう書くと、隣が壁なのかどうか調べてるんだな、というのがわかりやすい
んじゃ、まとめると以下のようになるので
void DrawStage(Map& _map);を書いてみよう。
//0 床
//1 壁
//2 荷物
//3 ゴール
//4 人
//5 人 on the ゴール
//6 荷物 on the ゴール
//7 壁の外のゲームに使わない領域
enum OBJNAME {
FLOOR, WALL, LUGG, GOAL, HUMAN, HUMAN_ON_GOAL, LUGG_ON_GOAL
};
const string OBJS[] = const char OBJS[] = { " ", "#", "$", ".", "@", "+", "*", " "};
void DrawStage(Map& _map)
{
for (int j = 0; j < _map.stage_height; j++)
{
for (int i = 0; i < _map.stage_width; i++)
{
cout << OBJS[マップデータの値];
}
cout << endl;
}
}
#include <iostream>
#include <iomanip>
#include <string>
#include <conio.h>
using std::cout;
using std::cin;
using std::endl;
using std::string;
const int MAX_STAGE_WIDTH = 10;
const int MAX_STAGE_HEIGHT = 10;
struct Map
{
int stage_width;
int stage_height;
int Dat[MAX_STAGE_HEIGHT][MAX_STAGE_WIDTH];
};
enum OBJNAME {
FLOOR, WALL, LUGG, GOAL, HUMAN, HUMAN_ON_GOAL, LUGG_ON_GOAL
};
const string OBJS[] = { " ", "#", "$", ".", "@", "+", "*", " " };
Map sampleSatge = {
10,10,
{
{1,1,1,1,1,1,1,1,1,1},
{1,0,0,2,3,3,0,0,0,1},
{1,0,4,2,0,0,0,0,0,1},
{1,0,0,0,0,1,1,0,0,1},
{1,0,0,0,0,2,0,0,0,1},
{1,0,0,3,0,0,0,0,0,1},
{1,0,0,0,0,0,1,1,1,1},
{1,0,0,0,0,0,0,0,0,1},
{1,0,1,0,0,0,0,0,0,1},
{1,1,1,1,1,1,1,1,1,1}
}
};
int main()
{
DrawStage(sampleSatge);
}
こんな感じでmainを組んで、実行してみよう。
2次元配列のステージが、キャラクターを表す文字に置き換われば成功!