#include <iostream>
#include <cstdlib>
#include <ctime>
#include <vector>
#include <conio.h>
#include <utility>
#include <cstring>
#ifdef _WIN32
#include <windows.h>
#define CLEAR_SCREEN "cls"
#define SLEEP_MS(x) Sleep(x)
#else
#define CLEAR_SCREEN "clear"
#define SLEEP_MS(x) usleep(x * 1000)
#endif
using namespace std;
enum Direction { UP, DOWN, LEFT, RIGHT };
struct Bullet {
int x, y;
Direction dir;
bool alive;
Bullet(int x_, int y_, Direction dir_) : x(x_), y(y_), dir(dir_), alive(true) {}
};
class Tank {
public:
int x, y;
Direction dir;
bool alive;
char symbol;
Tank(int x_, int y_, char sym) : x(x_), y(y_), dir(UP), alive(true), symbol(sym) {}
bool move(Direction newDir, int mapWidth, int mapHeight, const vector<pair<int, int> >& obstacles);
};
const int MAP_WIDTH = 60;
const int MAP_HEIGHT = 26;
const int AUTO_REFRESH_MS = 600;
const int DIR[4][2] = {{-1,0}, {1,0}, {0,-1}, {0,1}};
const int ENEMY_COUNT = 5;
const int ENEMY_DETECT_RANGE = 8;
Tank player(1, 1, 'P');
vector<Tank> enemies;
vector<Bullet> bullets;
vector<pair<int, int> > obstacles;
bool gameOver = false;
string winner = "";
bool visited[MAP_HEIGHT][MAP_WIDTH];
bool isObstacle(int x, int y) {
if (x <= 0 || x >= MAP_HEIGHT-1 || y <= 0 || y >= MAP_WIDTH-1)
return true;
for (vector<pair<int, int> >::const_iterator it = obstacles.begin(); it != obstacles.end(); ++it) {
if (it->first == x && it->second == y) {
return true;
}
}
return false;
}
bool hasTankAt(int x, int y) {
if (player.alive && player.x == x && player.y == y)
return true;
for (size_t i = 0; i < enemies.size(); ++i) {
if (enemies[i].alive && enemies[i].x == x && enemies[i].y == y)
return true;
}
return false;
}
bool Tank::move(Direction newDir, int mapWidth, int mapHeight, const vector<pair<int, int> >& obstacles) {
dir = newDir;
int newX = x, newY = y;
switch (newDir) {
case UP: newX--; break;
case DOWN: newX++; break;
case LEFT: newY--; break;
case RIGHT: newY++; break;
}
if (newX >= 1 && newX <= mapHeight-2
&& newY >= 1 && newY <= mapWidth-2
&& !isObstacle(newX, newY)
&& !hasTankAt(newX, newY)) {
x = newX;
y = newY;
return true;
}
return false;
}
void dfs(int x, int y, int endX, int endY, bool& reachable) {
if (x == endX && y == endY) {
reachable = true;
return;
}
if (x < 1 || x >= MAP_HEIGHT-1 || y < 1 || y >= MAP_WIDTH-1 || visited[x][y] || isObstacle(x, y)) {
return;
}
visited[x][y] = true;
for (int i = 0; i < 4 && !reachable; i++) {
dfs(x + DIR[i][0], y + DIR[i][1], endX, endY, reachable);
}
}
bool isMapConnected(int playerX, int playerY, const vector<Tank>& enemies) {
for (size_t i = 0; i < enemies.size(); ++i) {
if (!enemies[i].alive) continue;
memset(visited, false, sizeof(visited));
bool reachable = false;
dfs(playerX, playerY, enemies[i].x, enemies[i].y, reachable);
if (!reachable) return false;
}
return true;
}
pair<int, int> generateSuperSafePos() {
int x, y;
bool safe = false;
while (!safe) {
x = rand() % (MAP_HEIGHT - 2) + 1;
y = rand() % (MAP_WIDTH - 2) + 1;
if (isObstacle(x, y)) continue;
int freeDir = 0;
for (int i = 0; i < 4; i++) {
int nx = x + DIR[i][0];
int ny = y + DIR[i][1];
if (nx >= 1 && nx <= MAP_HEIGHT-2 && ny >= 1 && ny <= MAP_WIDTH-2 && !isObstacle(nx, ny)) {
freeDir++;
}
}
if (freeDir >= 3) safe = true;
}
return make_pair(x, y);
}
void generateOpenMap() {
obstacles.clear();
bool connected = false;
while (!connected) {
obstacles.clear();
const int WALL_SEGMENTS = 25;
const int MIN_WALL_LEN = 2;
const int MAX_WALL_LEN = 4;
for (int i = 0; i < WALL_SEGMENTS; i++) {
Direction dir = (Direction)(rand() % 2);
int len = MIN_WALL_LEN + rand() % (MAX_WALL_LEN - MIN_WALL_LEN + 1);
int x, y;
do {
x = rand() % (MAP_HEIGHT - 2) + 1;
y = rand() % (MAP_WIDTH - 2) + 1;
} while (isObstacle(x, y));
for (int j = 0; j < len; j++) {
int nx = x, ny = y;
if (dir == 0) ny += j;
else nx += j;
if (nx < 1 || nx >= MAP_HEIGHT-1 || ny < 1 || ny >= MAP_WIDTH-1 || isObstacle(nx, ny)) break;
obstacles.push_back(make_pair(nx, ny));
}
}
pair<int, int> playerPos = generateSuperSafePos();
player.x = playerPos.first;
player.y = playerPos.second;
player.alive = true;
enemies.clear();
for (int i = 0; i < ENEMY_COUNT; ++i) {
pair<int, int> enemyPos = generateSuperSafePos();
enemies.push_back(Tank(enemyPos.first, enemyPos.second, 'E'));
}
connected = isMapConnected(player.x, player.y, enemies);
}
}
bool hasLineOfSight(int enemyX, int enemyY, int playerX, int playerY) {
int dx = abs(playerX - enemyX);
int dy = abs(playerY - enemyY);
if (dx > 0 && dy > 0) return false;
if (dx == 0) {
int startY = min(enemyY, playerY) + 1;
int endY = max(enemyY, playerY);
for (int y = startY; y < endY; y++) {
if (isObstacle(enemyX, y)) return false;
}
} else if (dy == 0) {
int startX = min(enemyX, playerX) + 1;
int endX = max(enemyX, playerX);
for (int x = startX; x < endX; x++) {
if (isObstacle(x, enemyY)) return false;
}
}
return true;
}
bool canAttackPlayer(int enemyX, int enemyY) {
if (!player.alive) return false;
int dx = abs(player.x - enemyX);
int dy = abs(player.y - enemyY);
int distance = dx + dy;
if (distance > ENEMY_DETECT_RANGE) return false;
return hasLineOfSight(enemyX, enemyY, player.x, player.y);
}
void clearScreen() {
system(CLEAR_SCREEN);
}
void setColor(int color) {
#ifdef _WIN32
HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
SetConsoleTextAttribute(hConsole, BACKGROUND_GREEN | color);
#else
(void)color;
#endif
}
void drawMap() {
clearScreen();
for (int i = 0; i < MAP_HEIGHT; i++) {
for (int j = 0; j < MAP_WIDTH; j++) {
if (i == 0 || i == MAP_HEIGHT-1 || j == 0 || j == MAP_WIDTH-1) {
setColor(6); cout << "■";
} else if (isObstacle(i, j)) {
setColor(6); cout << "■";
} else if (player.alive && i == player.x && j == player.y) {
setColor(1); cout << player.symbol << " ";
} else {
bool isEnemy = false;
for (size_t k = 0; k < enemies.size(); ++k) {
if (enemies[k].alive && i == enemies[k].x && j == enemies[k].y) {
setColor(4); cout << enemies[k].symbol << " ";
isEnemy = true; break;
}
}
if (isEnemy) continue;
bool isBullet = false;
for (vector<Bullet>::iterator it = bullets.begin(); it != bullets.end(); ++it) {
if (it->alive && it->x == i && it->y == j) {
setColor(14); cout << "* ";
isBullet = true; break;
}
}
if (!isBullet) {
setColor(7); cout << " ";
}
}
}
cout << endl;
}
setColor(7);
cout << "===== 坦克对战 v1.0 =====" << endl;
cout << "操作:W(上) A(左) S(下) D(右) 空格(发射) Q(退出) R(重开)" << endl;
cout << "玩家:P 敌人:E 子弹:* 障碍物:■" << endl;
if (gameOver) {
cout << "★ 游戏结束!" << winner << " ★ 3秒后自动重开..." << endl;
}
}
void checkCollision() {
for (vector<Bullet>::iterator it = bullets.begin(); it != bullets.end(); ) {
if (!it->alive) { it = bullets.erase(it); continue; }
if (player.alive && it->x == player.x && it->y == player.y) {
player.alive = false;
gameOver = true;
winner = "玩家被击败,电脑获胜";
it->alive = false;
}
bool hitEnemy = false;
for (size_t k = 0; k < enemies.size(); ++k) {
if (enemies[k].alive && it->x == enemies[k].x && it->y == enemies[k].y) {
enemies[k].alive = false;
it->alive = false;
hitEnemy = true; break;
}
}
if (hitEnemy) {
bool allDead = true;
for (size_t k = 0; k < enemies.size(); ++k) {
if (enemies[k].alive) { allDead = false; break; }
}
if (allDead) {
gameOver = true;
winner = "所有敌人被消灭,玩家获胜";
}
it = bullets.erase(it);
continue;
}
if (isObstacle(it->x, it->y)) {
it->alive = false;
it = bullets.erase(it);
} else {
++it;
}
}
}
void moveBullets() {
for (vector<Bullet>::iterator it = bullets.begin(); it != bullets.end(); ++it) {
if (!it->alive) continue;
switch (it->dir) {
case UP: it->x--; break;
case DOWN: it->x++; break;
case LEFT: it->y--; break;
case RIGHT: it->y++; break;
}
}
}
// ==============================================
// 这里已经把 所有概率 提高到 75% 左右!
// ==============================================
void enemyAI() {
if (gameOver || !player.alive) return;
for (size_t k = 0; k < enemies.size(); ++k) {
Tank& enemy = enemies[k];
if (!enemy.alive) continue;
bool playerInRange = canAttackPlayer(enemy.x, enemy.y);
if (playerInRange) {
if (player.x < enemy.x) enemy.dir = UP;
else if (player.x > enemy.x) enemy.dir = DOWN;
else if (player.y < enemy.y) enemy.dir = LEFT;
else if (player.y > enemy.y) enemy.dir = RIGHT;
// 75% 移动概率
if (rand() % 4 != 0) {
enemy.move(enemy.dir, MAP_WIDTH, MAP_HEIGHT, obstacles);
}
// 62.5% 开火概率
if (rand() % 8 != 0) {
int bx = enemy.x, by = enemy.y;
switch (enemy.dir) {
case UP: bx--; break; case DOWN: bx++; break;
case LEFT: by--; break; case RIGHT: by++; break;
}
bullets.push_back(Bullet(bx, by, enemy.dir));
}
} else {
// 70% 随机移动
if (rand() % 10 != 0) {
Direction nd = (Direction)(rand() % 4);
enemy.move(nd, MAP_WIDTH, MAP_HEIGHT, obstacles);
}
// 52% 随机开火
if (rand() % 100 < 52) {
int bx = enemy.x, by = enemy.y;
switch (enemy.dir) {
case UP: bx--; break; case DOWN: bx++; break;
case LEFT: by--; break; case RIGHT: by++; break;
}
bullets.push_back(Bullet(bx, by, enemy.dir));
}
}
}
}
bool handleInput() {
if (!player.alive || gameOver) return false;
if (!kbhit()) return false;
char key = getch();
switch (key) {
case 'w': case 'W': player.move(UP, MAP_WIDTH, MAP_HEIGHT, obstacles); break;
case 's': case 'S': player.move(DOWN, MAP_WIDTH, MAP_HEIGHT, obstacles); break;
case 'a': case 'A': player.move(LEFT, MAP_WIDTH, MAP_HEIGHT, obstacles); break;
case 'd': case 'D': player.move(RIGHT, MAP_WIDTH, MAP_HEIGHT, obstacles); break;
case ' ': {
int bx = player.x, by = player.y;
switch (player.dir) {
case UP: bx--; break; case DOWN: bx++; break;
case LEFT: by--; break; case RIGHT: by++; break;
}
bullets.push_back(Bullet(bx, by, player.dir));
break;
}
case 'r': case 'R':
gameOver = true; winner = "手动重新开始"; break;
case 'q': case 'Q':
cout << "\n退出游戏!" << endl; exit(0);
default: return false;
}
return true;
}
void resetGame() {
player.alive = true;
bullets.clear();
gameOver = false;
winner = "";
generateOpenMap();
}
int main() {
cin.tie(NULL);
cout.tie(NULL);
srand((unsigned)time(NULL));
generateOpenMap();
drawMap();
DWORD last = GetTickCount();
while (true) {
if (gameOver) {
drawMap(); SLEEP_MS(3000); resetGame(); last = GetTickCount(); drawMap(); continue;
}
bool key = handleInput();
if (key) {
enemyAI(); moveBullets(); checkCollision();
drawMap(); last = GetTickCount();
continue;
}
DWORD now = GetTickCount();
if (now - last >= AUTO_REFRESH_MS) {
enemyAI(); moveBullets(); checkCollision();
drawMap(); last = now;
}
Sleep(10);
}
return 0;
}