Prompt
Answer
Code Review and Solution for Zombie Game in C++
The provided code implements a simple console-based game where a player (represented by 'P') moves around a 20x10 grid and tries to avoid moving zombies (represented by 'Z'). The player can move using the WASD keys, and the game ends if the player collides with a zombie.
Issues Identified
Input Handling: The code allows for only lowercase input ('w', 'a', 's', 'd'). Any other input, even if it is uppercase, will not allow the player to move.
Zombie Movement Logic: The zombies move randomly but have the potential to overlap with the player. The code could benefit from ensuring that zombies do not move onto the same space occupied by the player.
Game State Management: Lack of a clean exit strategy or game restart prompt after a collision.
Code Duplication: The
srand
function could be called elsewhere to avoid repetitive initialization.
Proposed Solutions
The following enhancements will be implemented:
- Case-Insensitive Input Handling: Convert input to lowercase to accept both upper and lower cases.
- Enhanced Zombie Movement: Ensure zombies do not spawn or move onto the player's location.
- Game Over Handling: Provide an option to restart the game without having to relaunch.
- Refactoring: Optimize
srand
to ensure it's only called once at the beginning of the game.
Updated Code
Here is the revised version with the suggested improvements:
#include
#include
#include
#include
using namespace std;
const int WIDTH = 20;
const int HEIGHT = 10;
const char PLAYER_CHAR = 'P';
const char ZOMBIE_CHAR = 'Z';
const char EMPTY_CHAR = '.';
struct Entity {
int x, y;
};
class Game {
public:
Game() {
player.x = WIDTH / 2;
player.y = HEIGHT / 2;
srand(static_cast(time(0))); // Seed random number generator
spawnZombies(3);
}
void run() {
char input;
while (true) {
clearScreen();
printBoard();
cout << "Move (WASD): ";
cin >> input;
input = tolower(input); // Convert to lowercase for uniformity
if (!handleInput(input)) {
cout << "Invalid input. Please use 'W', 'A', 'S', or 'D'." << endl;
continue;
}
updateZombies();
if (checkCollision()) {
cout << "Game Over! Do you want to restart? (Y/N): ";
cin >> input;
if (tolower(input) == 'y') {
Game newGame; // Create a new game instance
newGame.run(); // Restart game loop
}
break;
}
}
}
private:
Entity player;
vector zombies;
void spawnZombies(int count) {
zombies.clear(); // Clear existing zombies
for (int i = 0; i < count; i++) {
Entity zombie;
// Ensure zombies do not spawn at the player's location
do {
zombie.x = rand() % WIDTH;
zombie.y = rand() % HEIGHT;
} while ((zombie.x == player.x && zombie.y == player.y) || (isZombieAt(zombie)));
zombies.push_back(zombie);
}
}
bool isZombieAt(const Entity& position) {
for (const auto& zombie : zombies) {
if (zombie.x == position.x && zombie.y == position.y) {
return true;
}
}
return false;
}
void printBoard() {
char board[HEIGHT][WIDTH];
for (int i = 0; i < HEIGHT; i++)
for (int j = 0; j < WIDTH; j++)
board[i][j] = EMPTY_CHAR;
board[player.y][player.x] = PLAYER_CHAR;
for (const auto& zombie : zombies) {
board[zombie.y][zombie.x] = ZOMBIE_CHAR;
}
for (int i = 0; i < HEIGHT; i++) {
for (int j = 0; j < WIDTH; j++) {
cout << board[i][j] << ' ';
}
cout << endl;
}
}
bool handleInput(char input) {
switch (input) {
case 'w': if (player.y > 0) --player.y; break;
case 's': if (player.y < HEIGHT - 1) ++player.y; break;
case 'a': if (player.x > 0) --player.x; break;
case 'd': if (player.x < WIDTH - 1) ++player.x; break;
default: return false; // Invalid input
}
return true; // Valid input
}
void updateZombies() {
for (auto& zombie : zombies) {
// Random movement logic for more unpredictability, ensure no overlap with player
int direction = rand() % 4;
Entity newPosition = zombie;
switch (direction) {
case 0: if (zombie.y > 0) --newPosition.y; break; // Move up
case 1: if (zombie.y < HEIGHT - 1) ++newPosition.y; break; // Move down
case 2: if (zombie.x > 0) --newPosition.x; break; // Move left
case 3: if (zombie.x < WIDTH - 1) ++newPosition.x; break; // Move right
}
// Only update if position is not occupied
if (!isZombieAt(newPosition) && !(newPosition.x == player.x && newPosition.y == player.y)) {
zombie = newPosition;
}
}
}
bool checkCollision() {
for (const auto& zombie : zombies) {
if (zombie.x == player.x && zombie.y == player.y) {
return true;
}
}
return false;
}
void clearScreen() {
#ifdef _WIN32
system("cls");
#else
system("clear");
#endif
}
};
int main() {
Game game;
game.run();
return 0;
}
Key Changes Explained
Input Handling: The input is converted to lowercase using
tolower()
, allowing 'W', 'A', 'S', 'D' in addition to 'w', 'a', 's', 'd'.Zombie Movement: The
updateZombies
function now checks if moving a zombie would cause it to overlap with the player or existing zombies.Game Restart Feature: After a game over, the user is prompted to restart the game, creating a new instance of
Game
.Helper Function:
isZombieAt()
is created to check if a position is currently occupied by any zombie, aiding in both spawning and updating.
Conclusion
The revised code resolves the issues with the original implementation and enhances the gaming experience. The changes ensure better handling of inputs, zombie movements, and game state management. This version of the game allows for straightforward play and demonstrates improved design with potential for future expansions.
Description
This document details improvements to a console-based zombie game in C++, addressing input handling, zombie movement logic, game restart functionality, and code duplication. The revised code enhances player experience while maintaining simplicity.