スネークゲームのコア機能として、動的な体節管理ロジックの実装を検討します。主な要件は以下の3点です:
スネーク本体の管理クラスを定義します。体節の追加・移動処理をカプセル化し、ゲームループから独立した動作を実現します。
方向変更は単純なプロパティ更新で実装します。体節追加処理では、新しい頭部をリスト先頭に挿入し、現在の頭部情報を保持します。
移動処理の実装で重要なのは、体節の連続更新と末尾処理です。以下のアプローチを採用します:
ゲームマネージャークラスでは、スネークの移動とマップ更新を連携させます。advance()が返す削除座標を基に、画面表示を更新します。
実装の検証には、5回の前進動作を1秒間隔で実行するテストケースを使用します。初期位置から南方向に5マス移動し、末尾が正しく更新されることを確認します。
実行結果では、スネークの頭部が垂直方向に5マス移動し、体節が連続して追従する様子が確認できます。この実装により、O(n)の計算量で体節管理が可能となり、リアルタイムゲーム動作に適した設計となっています。
- 進行方向の動的変更
- 体節の動的追加
- 座標の連続更新による移動処理
enum class MovementDirection {
NORTH,
SOUTH,
WEST,
EAST
};
struct Position {
int x;
int y;
};
struct BodySegment {
BodySegment* next;
char symbol;
Position current;
};
class SnakeHead {
public:
char symbol;
MovementDirection direction;
size_t length;
Position position;
BodySegment* head;
};class MovingSnake {
public:
MovingSnake();
~MovingSnake();
void initialize(char symbol, MovementDirection dir, size_t length, const Position& pos);
void setDirection(MovementDirection dir);
void extendBody(const Position& newHead, char symbol);
Position advance();
private:
SnakeHead m_head;
};void MovingSnake::setDirection(MovementDirection dir) {
m_head.direction = dir;
}
void MovingSnake::extendBody(const Position& newHead, char symbol) {
BodySegment* segment = new BodySegment{m_head.head, m_head.symbol, m_head.position};
m_head.head = segment;
m_head.position = newHead;
m_head.symbol = symbol;
m_head.length++;
}- 新規頭部を追加(extendBodyを呼び出し)
- 体長が1の場合:現在位置を保持して新規体節生成
- 体長が2以上の場合:末尾体節を削除し座標情報を返却
- 方向に応じて新規頭部座標を計算
Position MovingSnake::advance() {
Position removedPos;
if (m_head.length > 1) {
extendBody(m_head.position, m_head.symbol);
BodySegment* current = m_head.head;
while (current->next->next != nullptr) {
current = current->next;
}
BodySegment* tail = current->next;
removedPos = tail->current;
delete tail;
current->next = nullptr;
} else {
removedPos = m_head.position;
}
switch (m_head.direction) {
case MovementDirection::NORTH: m_head.position.x--; break;
case MovementDirection::SOUTH: m_head.position.x++; break;
case MovementDirection::WEST: m_head.position.y--; break;
case MovementDirection::EAST: m_head.position.y++; break;
}
return removedPos;
}void GameMaster::updateGame() {
Position removed = m_snake.advance();
m_gameMap.clearCell(removed.x, removed.y);
MapElement newHead;
newHead.symbol = m_snake.getHeadSymbol();
m_gameMap.setCell(m_snake.getHeadPosition(), newHead);
m_gameMap.render();
}int main() {
GameMaster game;
game.initialize();
for (int i = 0; i < 5; i++) {
game.updateGame();
std::this_thread::sleep_for(std::chrono::seconds(1));
}
return 0;
}