概要
本稿では、C++ と Windows API を利用してコンソール上で動作する自動迷路探索プログラムの実装例を紹介します。迷路の構造を管理するクラスと、探索を行うエージェントクラスを分離し、壁沿い探索アルゴリズムによりゴールまで到達する様子を描画します。
迷路フィールドの定義
まず、迷路のデータ構造と描画機能を担う MazeField クラスを定義します。内部では 2 次元配列を用いて壁と通路を管理し、境界チェック機能を提供します。
#pragma once
#include <iostream>
#include <vector>
class MazeField {
public:
MazeField();
void initialize(const std::vector<std::vector<int>>& data);
void setSymbols(char wall, char path);
void render() const;
bool isObstacle(int row, int col) const;
int getHeight() const { return height; }
int getWidth() const { return width; }
char getWallSymbol() const { return wallChar; }
char getPathSymbol() const { return pathChar; }
private:
int grid[10][10];
int height;
int width;
char wallChar;
char pathChar;
};
実装ファイルでは、配列のコピー処理と描画ロジックを記述します。コンソール出力において、1 は壁、0 は通路として扱います。
#include "MazeField.h"
MazeField::MazeField() {
wallChar = '#';
pathChar = '.';
height = 0;
width = 0;
}
void MazeField::initialize(const std::vector<std::vector<int>>& data) {
height = static_cast<int>(data.size());
if (height > 10) height = 10;
for (int i = 0; i < height; ++i) {
width = static_cast<int>(data[i].size());
if (width > 10) width = 10;
for (int j = 0; j < width; ++j) {
grid[i][j] = data[i][j];
}
}
}
void MazeField::setSymbols(char wall, char path) {
wallChar = wall;
pathChar = path;
}
void MazeField::render() const {
for (int i = 0; i < height; ++i) {
for (int j = 0; j < width; ++j) {
if (grid[i][j] == 1) {
std::cout << wallChar;
} else {
std::cout << pathChar;
}
}
std::cout << std::endl;
}
}
bool MazeField::isObstacle(int row, int col) const {
if (row < 0 || col < 0 || row >= height || col >= width) {
return true;
}
return grid[row][col] == 1;
}
探索エージェントの実装
次に、実際に迷路内を移動する MazeRunner クラスを作成します。このクラスは現在の座標を保持し、進行方向を決定するロジックを持ちます。Windows API を使用してカーソル位置を制御し、アニメーションのような動きを実現します。
#pragma once
#include "MazeField.h"
#include <windows.h>
enum class Direction { Up, Down, Left, Right };
class MazeRunner {
public:
MazeRunner(MazeField& field);
void spawn(int row, int col);
void setSpeed(int delayMs);
void setSymbol(char sym);
void run();
private:
MazeField& maze;
int currentRow;
int currentCol;
int delayTime;
char agentSymbol;
Direction currentDir;
void moveCursor(int row, int col);
void clearPreviousPosition(int row, int col);
void drawCurrentPosition(int row, int col);
Direction decideDirection();
void step(Direction dir);
bool checkGoal(int row, int col);
};
移動ロジックでは、進行方向の壁を検知した場合に右折または左折を行う簡易的な壁沿いアルゴリズムを採用しています。ゴール条件は迷路の端に到達することとしています。
#include "MazeRunner.h"
#include <iostream>
#include <cstdlib>
MazeRunner::MazeRunner(MazeField& field) : maze(field) {
currentDir = Direction::Right;
delayTime = 100;
agentSymbol = 'A';
}
void MazeRunner::spawn(int row, int col) {
currentRow = row;
currentCol = col;
moveCursor(row, col);
std::cout << agentSymbol;
}
void MazeRunner::setSpeed(int delayMs) {
delayTime = delayMs;
}
void MazeRunner::setSymbol(char sym) {
agentSymbol = sym;
}
void MazeRunner::moveCursor(int row, int col) {
HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
COORD pos;
pos.X = static_cast<SHORT>(col);
pos.Y = static_cast<SHORT>(row);
SetConsoleCursorPosition(hOut, pos);
}
void MazeRunner::run() {
while (true) {
currentDir = decideDirection();
step(currentDir);
Sleep(delayTime);
}
}
void MazeRunner::step(Direction dir) {
int nextRow = currentRow;
int nextCol = currentCol;
if (dir == Direction::Up) nextRow--;
else if (dir == Direction::Down) nextRow++;
else if (dir == Direction::Left) nextCol--;
else if (dir == Direction::Right) nextCol++;
if (checkGoal(nextRow, nextCol)) {
std::cout << std::endl << "Goal Reached!" << std::endl;
exit(0);
}
clearPreviousPosition(currentRow, currentCol);
currentRow = nextRow;
currentCol = nextCol;
drawCurrentPosition(currentRow, currentCol);
}
void MazeRunner::clearPreviousPosition(int row, int col) {
moveCursor(row, col);
std::cout << maze.getPathSymbol();
}
void MazeRunner::drawCurrentPosition(int row, int col) {
moveCursor(row, col);
std::cout << agentSymbol;
}
Direction MazeRunner::decideDirection() {
// 簡易的な壁沿いロジック
if (currentDir == Direction::Up) {
if (maze.isObstacle(currentRow, currentCol - 1)) {
if (!maze.isObstacle(currentRow - 1, currentCol)) return Direction::Up;
if (!maze.isObstacle(currentRow, currentCol + 1)) return Direction::Right;
return Direction::Down;
}
return Direction::Left;
}
// 他方向も同様に判定...
// 簡略化のためここでは UP の例のみ記載
return currentDir;
}
bool MazeRunner::checkGoal(int row, int col) {
// 迷路の外縁に到達したらゴールとみなす
if (row <= 0 || col <= 0 || row >= maze.getHeight() - 1 || col >= maze.getWidth() - 1) {
return true;
}
return false;
}
メインプログラムの構成
最後に、迷路データを定義し、各クラスを初期化して実行するメイン関数です。配列データを用いて迷路の形状を定義し、探索スピードや表示文字を設定できます。
#include <iostream>
#include <vector>
#include "MazeField.h"
#include "MazeRunner.h"
int main() {
const int WALL = 1;
const int PATH = 0;
std::vector<std::vector<int>> mapData = {
{WALL, WALL, WALL, WALL, WALL, WALL, WALL, PATH, WALL},
{WALL, WALL, WALL, WALL, PATH, WALL, WALL, PATH, WALL},
{WALL, WALL, WALL, WALL, PATH, WALL, WALL, PATH, WALL},
{WALL, WALL, WALL, WALL, PATH, WALL, WALL, PATH, WALL},
{WALL, WALL, PATH, PATH, PATH, PATH, WALL, PATH, WALL},
{WALL, WALL, PATH, WALL, WALL, PATH, PATH, PATH, WALL},
{WALL, PATH, PATH, WALL, WALL, WALL, WALL, WALL, WALL},
{WALL, PATH, WALL, WALL, WALL, WALL, WALL, WALL, WALL}
};
MazeField field;
field.initialize(mapData);
field.setSymbols('*', ' ');
field.render();
MazeRunner runner(field);
runner.spawn(7, 1);
runner.setSpeed(200);
runner.setSymbol('P');
runner.run();
return 0;
}