C++
リファクタリング
ゲーム制作
迷路
DungeonGeneration

C++で簡単に2次元配列を扱うクラスを実装する


はじめに

今回は二次元配列を扱う便利なクラスを作っていきたいと思います。


まずはC++風の二次元配列(std::vector)


C++

#include <vector>

#include <iostream>

//配列内の中身を見せる関数
void array2D(const std::vector<std::vector<size_t>>& array_2d) {
for (size_t i = 0; i < array_2d.size(); ++i) {
for (size_t j = 0; j < array_2d[i].size(); ++j) {
//配列の中身を出力
std::cout << array_2d[i][j] << ',';
}
//改行
std::cout << std::endl;
}
}

//メイン関数
int main() {

//2次元配列を宣言
//X:6, Y:10, 初期要素:255
std::vector<std::vector<size_t>> a(10, std::vector<size_t>(6, 255));
//配列の中身を見せる
array2D(a);

return 0;
}



出力結果

255,255,255,255,255,255,

255,255,255,255,255,255,
255,255,255,255,255,255,
255,255,255,255,255,255,
255,255,255,255,255,255,
255,255,255,255,255,255,
255,255,255,255,255,255,
255,255,255,255,255,255,
255,255,255,255,255,255,

二重std::vectorで二次元配列を作ってみました。

"生の配列で二次元配列を作らないの?"と思う人もいるかもしれません。

それは、以下の記事で解説されているので説明は省きますが、

>> C++ では C言語由来 の多次元配列を使うべき理由がない

生配列の二次元配列を関数の引数にする場合、引数は

(int data[][SIZE_X])のように書かなければいけません。

つまり配列の(横の)要素数を提示する必要があります。

これでは、非常に扱いづらいのです。おのれ生配列!!!

そこでstd::vectorを使い、二次元配列を作りました。

宣言はstd::vector<std::vector<型>>ですね。

コンストラクタは(10, std::vector<size_t>(6, 255))

とわかりずらいし書きづらいです。

ここで、これをもう少し使いやすくできないかなと考えました。

array_2d<型>のような感じで宣言して使える、

コンストラクタは単純に(10, 6, 255)のように書ける、

そのようなクラスを作っていきます。


基本事項

固定長(ここでは途中で大きさが変化しないという意味)の配列を扱う。

unique_ptrで管理。

STL(array, vector等)と同程度の使いやすさを目指す。


ソースコード


UniqueArray.hpp

//------------------------------------------------------------

//
// Unique Array Library
//
//------------------------------------------------------------

#pragma once
#include <memory>

namespace asc {

template<typename T>
class unique_array_2d_XY {
private:
const size_t id;
const std::unique_ptr<T[]>& t;
public:
constexpr unique_array_2d_XY<T>(const size_t id_, const std::unique_ptr<T[]>& t_)
: id(id_), t(t_) {}
constexpr T operator+() const { return +t[id]; }
constexpr T operator-() const { return -t[id]; }
constexpr T operator+(const T& a) const { return t[id] + a; }
constexpr T operator-(const T& a) const { return t[id] - a; }
constexpr T operator*(const T& a) const { return t[id] * a; }
constexpr T operator/(const T& a) const { return t[id] / a; }
constexpr T operator%(const T& a) const { return t[id] % a; }
constexpr T operator&(const T& a) const { return t[id] & a; }
constexpr T operator|(const T& a) const { return t[id] | a; }
constexpr T operator^(const T& a) const { return t[id] ^ a; }
constexpr T operator=(const T& a) const { return t[id] = a; }
constexpr T operator+=(const T& a) const { return t[id] += a; }
constexpr T operator-=(const T& a) const { return t[id] -= a; }
constexpr T operator*=(const T& a) const { return t[id] *= a; }
constexpr T operator/=(const T& a) const { return t[id] /= a; }
constexpr T operator%=(const T& a) const { return t[id] %= a; }
constexpr T operator^=(const T& a) const { return t[id] ^= a; }
constexpr T operator|=(const T& a) const { return t[id] |= a; }
constexpr T operator&=(const T& a) const { return t[id] &= a; }
constexpr T operator++() const { return ++t[id]; }
constexpr T operator++(int) const { return t[id]++; }
constexpr T operator--() const { return --t[id]; }
constexpr T operator--(int) const { return t[id]--; }
constexpr const bool operator!() const { return !t[id]; }
constexpr const bool operator&&(const T& a) const { return (t[id] && a); }
constexpr const bool operator||(const T& a) const { return (t[id] || a); }
constexpr const bool operator<(const T& a) const { return (t[id] < a); }
constexpr const bool operator>(const T& a) const { return (t[id] > a); }
constexpr const bool operator<=(const T& a) const { return (t[id] <= a); }
constexpr const bool operator>=(const T& a) const { return (t[id] >= a); }
constexpr const bool operator!=(const T& a) const { return (t[id] != a); }
constexpr const bool operator==(const T& a) const { return (t[id] == a); }

operator T() const { return t[id]; }

};

template<typename T>
class unique_array_2d_X {
private:
const size_t id;
const std::unique_ptr<T[]>& t;
public:
constexpr unique_array_2d_X<T>(const size_t id_, const std::unique_ptr<T[]>& t_) : id(id_), t(t_) {}
constexpr const unique_array_2d_XY<T> operator[](const size_t x_) const {
const unique_array_2d_XY<T> uptr_xy(id + x_, t);
return uptr_xy;
}
constexpr const unique_array_2d_XY<T> at(const size_t x_) const {
const unique_array_2d_XY<T> uptr_xy(id + x_, t);
return uptr_xy;
}
constexpr const unique_array_2d_XY<T> front() const {
const unique_array_2d_XY<T> uptr_xy(id, t);
return uptr_xy;
}
};

template<typename T>
class unique_array_2d {
private:
const std::unique_ptr<T[]> t;
const size_t size_x, size_y;
public:
unique_array_2d<T>(const size_t y_, const size_t x_, const T value_)
: t(new T[x_ * y_]), size_x((x_ == 0) ? 1 : x_), size_y((y_ == 0) ? 1 : y_) {
const size_t xy = x_ * y_;
for (T i = 0; i < xy; ++i) t[i] = value_;
}
constexpr unique_array_2d<T>(const size_t y_, const size_t x_)
: t(new T[x_ * y_]), size_x((x_ == 0) ? 1 : x_), size_y((y_ == 0) ? 1 : y_) {}
constexpr const unique_array_2d_X<T> operator[](const size_t y_) const {
unique_array_2d_X<T> uptr_x(y_*size_x, t);
return uptr_x;
}
constexpr const unique_array_2d_X<T> at(const size_t y_) const {
unique_array_2d_X<T> uptr_x(y_*size_x, t);
return uptr_x;
}
constexpr const unique_array_2d_X<T> front() const {
unique_array_2d_X<T> uptr_x(0, t);
return uptr_x;
}
std::unique_ptr<T[]> data() const { return t; }
constexpr size_t size() const { return size_x * size_y; }
constexpr size_t sizeX() const { return size_x; }
constexpr size_t sizeY() const { return size_y; }

constexpr T* operator&() const { return t.get(); }
constexpr T* const get() const { return t.get(); }
constexpr T* const begin() const { return t.get(); }
constexpr T* const end() const { return t.get() + size_x * size_y; }

};

}



仕様

二次元配列を生成するときは

asc::unique_array_2d<変数型> name(縦の要素数, 横の要素数);

または

asc::unique_array_2d<変数型> name(縦の要素数, 横の要素数, 初期値);

と宣言します。

name.begin();, name.end();, name.size();

一次元配列のstd::vectorとほぼ同じように使用できます。

範囲for文(The range-based for statement)も使えます。

for (const auto& i : v) std::cout << i << ',';


乱数をソートして使用する(サンプル)


Source.cpp

#include <UniqueArray.hpp>

#include <iostream>
#include <algorithm>
#include <random>

int main() {
//乱数を生成
std::random_device rand_d;
std::mt19937 rand_cpp(rand_d());
std::uniform_int_distribution<> uid(0, 99);

//2次元配列を生成
const asc::unique_array_2d<int> ptr(8, 5);

//ランダムな数(0~99)を代入
for (size_t i = 0; i < ptr.sizeY(); ++i) {
for (size_t j = 0; j < ptr.sizeX(); ++j) {
ptr[i][j] = uid(rand_cpp);
}
}
//配列内をソート
std::sort(ptr.begin(), ptr.end());

//値を出力
for (size_t i = 0; i < ptr.sizeY(); ++i) {
for (size_t j = 0; j < ptr.sizeX(); ++j) {
std::cout << ptr[i][j] << ',';
}
std::cout << std::endl;
}

return 0;
}



出力結果

1,3,4,10,11,

12,16,17,20,28,
33,38,39,44,45,
50,51,52,53,53,
58,58,60,60,60,
61,62,64,67,71,
77,77,80,83,88,
90,90,95,96,98,


最初に書いたコードを変えてみた(サンプル)


Source.cpp

#include <UniqueArray.hpp>

#include <iostream>

//配列内の中身を見せる関数
template<typename T>
void array2D(const asc::unique_array_2d<T>& array_2d) {
for (size_t i = 0; i < array_2d.sizeY(); ++i) {
for (size_t j = 0; j < array_2d.sizeX(); ++j) {
//配列の中身を出力
std::cout << array_2d[i][j] << ',';
}
//改行
std::cout << std::endl;
}
}

//メイン関数
int main() {

//2次元配列を宣言
//X:6, Y:10, 初期要素:255
asc::unique_array_2d a(10, 6, 255);
//配列の中身を見せる
array2D(a);

return 0;
}



出力結果

255,255,255,255,255,255,

255,255,255,255,255,255,
255,255,255,255,255,255,
255,255,255,255,255,255,
255,255,255,255,255,255,
255,255,255,255,255,255,
255,255,255,255,255,255,
255,255,255,255,255,255,
255,255,255,255,255,255,

だいぶ見やすくなりました!


迷路生成(穴掘り法)に使用してみる

>> 【C++】穴掘り法の迷路自動生成アルゴリズム

上記の記事の迷路はstd::vectorを使用して作られていますが、

これを今回作ったasc::unique_array_2dに変えて実行してみましょう。


実装コード


Maze.hpp

#pragma once

#include <UniqueArray.hpp>
#include <ctime>
#include <iostream>
#include <memory>

namespace maze {

//生成チェック
const bool mapCheck(const asc::unique_array_2d<size_t>& map_) {
if (map_.sizeY() <= 2 || map_.sizeX() <= 2) return false;
if ((map_.sizeY() & 1) == 0 || (map_.sizeX() & 1) == 0) return false;
return true;
}
//穴掘り
void mazeDig(asc::unique_array_2d<size_t>& map_, size_t x_, size_t y_, const size_t id_wall_, const size_t id_empty_)
{
if (!mapCheck(map_)) return;
int32_t dx, dy;
size_t random = size_t(rand()), counter = 0;
while (counter < 4) {
switch ((random + counter) & 3)
{
case 0:dx = 0; dy = -2; break;
case 1:dx = -2; dy = 0; break;
case 2:dx = 0; dy = 2; break;
case 3:dx = 2; dy = 0; break;
default:dx = 0; dy = 0; break;
}
if (x_ + dx <= 0 || y_ + dy <= 0 || x_ + dx >= map_.sizeY() - 1 || y_ + dy >= map_.sizeX() - 1 || map_[x_ + dx][y_ + dy] == id_empty_) {
++counter;
}
else if (map_[x_ + dx][y_ + dy] == id_wall_) {
map_[x_ + (dx >> 1)][y_ + (dy >> 1)] = id_empty_;
map_[x_ + dx][y_ + dy] = id_empty_;
x_ += dx;
y_ += dy;
counter = 0;
random = size_t(rand());
}
}
return;
}
//経路探索
void mazeRootLoop(asc::unique_array_2d<size_t>& map_, const size_t x_, const size_t y_, const size_t id_empty_, const size_t id_root_)
{
//経路探索状況
static bool b = true;

map_[x_][y_] = id_root_;
if (x_ == map_.sizeY() - 2 && y_ == map_.sizeX() - 2) b = false;
//上
if (b && map_[x_][y_ - 1] == id_empty_) mazeRootLoop(map_, x_, y_ - 1, id_empty_, id_root_);
//下
if (b && map_[x_][y_ + 1] == id_empty_) mazeRootLoop(map_, x_, y_ + 1, id_empty_, id_root_);
//左
if (b && map_[x_ - 1][y_] == id_empty_) mazeRootLoop(map_, x_ - 1, y_, id_empty_, id_root_);
//右
if (b && map_[x_ + 1][y_] == id_empty_) mazeRootLoop(map_, x_ + 1, y_, id_empty_, id_root_);

if (b) map_[x_][y_] = id_empty_;
return;
}
void mazeRoot(asc::unique_array_2d<size_t>& map_, const size_t id_empty_, const size_t id_root_)
{
if (!mapCheck(map_)) return;
mazeRootLoop(map_, 1, 1, id_empty_, id_root_);
}
//出力
void mazeOutput(const asc::unique_array_2d<size_t>& map_, const size_t id_wall_, const size_t id_empty_)
{
if (!mapCheck(map_)) return;
const size_t i_max = map_.sizeY();
const size_t j_max = map_.sizeX();
for (size_t i = 0; i < i_max; ++i) {
for (size_t j = 0; j < j_max; ++j) {
if (map_[i][j] == id_empty_) std::cout << " ";
else if (map_[i][j] == id_wall_) std::cout << "■";
else std::cout << "・";
}
std::cout << std::endl;
}
}
//迷路生成
const size_t mazeMakeLoop(asc::unique_array_2d<size_t>& map_, const size_t id_wall_, const size_t id_empty_, std::unique_ptr<size_t[]>& select_x, std::unique_ptr<size_t[]>& select_y)
{
size_t ii = 0;
const size_t i_max = map_.sizeY() - 1;
const size_t j_max = map_.sizeX() - 1;

for (size_t i = 1; i < i_max; i += 2)
for (size_t j = 1; j < j_max; j += 2) {
if (map_[i][j] != id_empty_) continue;
if ((i >= 2 && map_[i - 2][j] == id_wall_) || (j >= 2 && map_[i][j - 2] == id_wall_)) {
select_x[ii] = i;
select_y[ii] = j;
++ii;
}
else if ((j == map_.sizeX() - 2) && (i == map_.sizeY() - 2)) break;
else if ((i + 2 < map_.sizeY() && map_[i + 2][j] == id_wall_) || (j + 2 < map_.sizeX() && map_[i][j + 2] == id_wall_)) {
select_x[ii] = i;
select_y[ii] = j;
++ii;
}
}
return ii;
}
void mazeMake(asc::unique_array_2d<size_t>& map_, const size_t id_wall_, const size_t id_empty_)
{
if (map_.sizeY() <= 2 || map_.sizeX() <= 2) return;
if ((map_.sizeY() & 1) == 0 || (map_.sizeX() & 1) == 0) return;

map_[1][1] = id_empty_;

size_t a, ii;
std::unique_ptr<size_t[]> select_x(new size_t[map_.size()]);
std::unique_ptr<size_t[]> select_y(new size_t[map_.size()]);

//座標を選ぶ
while (true)
{
ii = mazeMakeLoop(map_, id_wall_, id_empty_, select_x, select_y);
if (ii == 0) break;
srand((unsigned int)time(nullptr));
a = size_t(rand()) % ii;
mazeDig(map_, select_x[a], select_y[a], id_wall_, id_empty_);
}
return;
}
//迷路生成
int createMaze(const size_t x_, const size_t y_, const size_t id_empty_, const size_t id_wall_, const size_t id_root_) {
if (x_ <= 2 || y_ <= 2) return 1;
if ((x_ & 1) == 0 || (y_ & 1) == 0) return 2;

//迷路マップ(全ての位置を壁にする)
asc::unique_array_2d<size_t> map_(y_, x_, id_wall_);
//迷路を生成
mazeMake(map_, id_wall_, id_empty_);
//経路探索
mazeRoot(map_, id_empty_, id_root_);
//出力
mazeOutput(map_, id_wall_, id_empty_);
return 0;
}
}



メイン関数


Source.cpp

#include <Maze.hpp>


//迷路の横幅
constexpr size_t size_x = 41;
//迷路の縦幅
constexpr size_t size_y = 21;

//迷路に設置する物体のID
enum :size_t {
id_empty_,//通路
id_wall_,//壁
id_root_,//ゴールまでの道
};

int main()
{
//迷路生成
return maze::createMaze(size_x, size_y, id_empty_, id_wall_, id_root_);
}



出力結果

maze_uni.PNG


ソースコードのライセンス

These codes are licensed under CC0.

CC0