#include <iostream>
#include <conio.h>
#include <windows.h>
#include <cstdlib>
#include <ctime>
#include <vector>
#include <string>
#include <fstream>
using namespace std;
const int SCREEN_WIDTH = 60;
const int SCREEN_HEIGHT = 20;
const int GROUND_Y = 15;
const int PLAYER_START_X = 8;
const int FPS = 60;
const int BASE_SPEED = 80;
const char CHAR_PLAYER = '&';
const char CHAR_PLAYER_JUMP = '^';
const char CHAR_PLAYER_SLIDE = '~';
const char CHAR_GROUND = '_';
const char CHAR_COIN = '$';
const char CHAR_SPIKE = '^';
const char CHAR_BLOCK = '#';
const char CHAR_BIRD = 'V';
const char CHAR_STAR = '*';
const char CHAR_SHIELD = 'O';
const char CHAR_MAGNET = 'M';
const int COLOR_DEFAULT = 7;
const int COLOR_PLAYER = 11;
const int COLOR_GROUND = 10;
const int COLOR_COIN = 14;
const int COLOR_OBSTACLE = 12;
const int COLOR_SHIELD = 9;
const int COLOR_MAGNET = 13;
const int COLOR_STAR = 14;
const int COLOR_UI = 15;
enum PlayerState {
STATE_RUNNING,
STATE_JUMPING,
STATE_DOUBLE_JUMP,
STATE_SLIDING,
STATE_DEAD
};
enum ObstacleType {
OBSTACLE_SPIKE,
OBSTACLE_BLOCK,
OBSTACLE_TALL_BLOCK,
OBSTACLE_BIRD,
OBSTACLE_COUNT
};
struct Position {
int x;
int y;
Position() : x(0), y(0) {}
Position(int px, int py) : x(px), y(py) {}
};
struct Obstacle {
Position pos;
ObstacleType type;
int width;
int height;
bool passed;
};
struct Collectible {
Position pos;
int type;
int value;
bool active;
};
struct Particle {
Position pos;
char symbol;
int color;
int life;
int maxLife;
float vy;
};
class ParkourGame {
private:
HANDLE hConsole;
COORD bufferSize;
SMALL_RECT windowRect;
Position playerPos;
float playerVelocityY;
PlayerState playerState;
int playerJumps;
int playerSlideTimer;
bool gameRunning;
bool gamePaused;
int score;
int coins;
int distance;
int highScore;
int gameSpeed;
int frameCount;
int difficulty;
bool hasShield;
int shieldTimer;
bool hasMagnet;
int magnetTimer;
int invincibleTimer;
vector<Obstacle> obstacles;
vector<Collectible> collectibles;
vector<Particle> particles;
int bgOffset;
vector<Position> bgStars;
public:
ParkourGame() {
hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
setupConsole();
loadHighScore();
initGame();
}
~ParkourGame() {
saveHighScore();
resetConsole();
}
void setupConsole() {
SetConsoleTitle("Parkour Runner");
bufferSize.X = SCREEN_WIDTH + 2;
bufferSize.Y = SCREEN_HEIGHT + 10;
SetConsoleScreenBufferSize(hConsole, bufferSize);
windowRect.Left = 0;
windowRect.Top = 0;
windowRect.Right = SCREEN_WIDTH + 1;
windowRect.Bottom = SCREEN_HEIGHT + 5;
SetConsoleWindowInfo(hConsole, TRUE, &windowRect);
CONSOLE_CURSOR_INFO cursorInfo;
GetConsoleCursorInfo(hConsole, &cursorInfo);
cursorInfo.bVisible = FALSE;
SetConsoleCursorInfo(hConsole, &cursorInfo);
DWORD consoleMode;
GetConsoleMode(hConsole, &consoleMode);
consoleMode &= ~ENABLE_QUICK_EDIT_MODE;
SetConsoleMode(hConsole, consoleMode);
}
void resetConsole() {
CONSOLE_CURSOR_INFO cursorInfo;
GetConsoleCursorInfo(hConsole, &cursorInfo);
cursorInfo.bVisible = TRUE;
SetConsoleCursorInfo(hConsole, &cursorInfo);
setColor(COLOR_DEFAULT);
system("cls");
}
void initGame() {
playerPos = Position(PLAYER_START_X, GROUND_Y - 1);
playerVelocityY = 0;
playerState = STATE_RUNNING;
playerJumps = 0;
playerSlideTimer = 0;
gameRunning = true;
gamePaused = false;
score = 0;
coins = 0;
distance = 0;
gameSpeed = BASE_SPEED;
frameCount = 0;
difficulty = 1;
hasShield = false;
shieldTimer = 0;
hasMagnet = false;
magnetTimer = 0;
invincibleTimer = 0;
obstacles.clear();
collectibles.clear();
particles.clear();
bgOffset = 0;
initBackground();
}
void initBackground() {
bgStars.clear();
for (int i = 0; i < 20; i++) {
Position star;
star.x = rand() % SCREEN_WIDTH;
star.y = rand() % (GROUND_Y - 2);
bgStars.push_back(star);
}
}
void loadHighScore() {
ifstream file("parkour_highscore.dat");
if (file.is_open()) {
file >> highScore;
file.close();
} else {
highScore = 0;
}
}
void saveHighScore() {
if (score > highScore) {
ofstream file("parkour_highscore.dat");
if (file.is_open()) {
file << score;
file.close();
}
}
}
void gotoxy(int x, int y) {
COORD coord;
coord.X = x;
coord.Y = y;
SetConsoleCursorPosition(hConsole, coord);
}
void setColor(int color) {
SetConsoleTextAttribute(hConsole, color);
}
bool isKeyPressed(int keyCode) {
return (GetAsyncKeyState(keyCode) & 0x8000) != 0;
}
void run() {
showStartScreen();
while (gameRunning) {
handleInput();
if (!gamePaused) {
update();
checkCollisions();
spawnObjects();
render();
}
Sleep(gameSpeed / FPS);
}
showGameOverScreen();
}
void handleInput() {
if (_kbhit()) {
char key = _getch();
switch (key) {
case ' ':
case 'w':
case 'W':
jump();
break;
case 's':
case 'S':
slide();
break;
case 'p':
case 'P':
gamePaused = !gamePaused;
if (gamePaused) showPauseScreen();
break;
case 27:
gameRunning = false;
break;
}
}
if (isKeyPressed(VK_SPACE) && playerState == STATE_RUNNING) {
jump();
}
if (isKeyPressed('S') && playerState == STATE_RUNNING) {
slide();
}
}
void jump() {
if (playerState == STATE_RUNNING) {
playerVelocityY = -1.8f;
playerState = STATE_JUMPING;
playerJumps = 1;
createJumpParticles();
} else if (playerState == STATE_JUMPING && playerJumps == 1) {
playerVelocityY = -2.5f;
playerState = STATE_DOUBLE_JUMP;
playerJumps = 2;
createJumpParticles();
}
}
void slide() {
if (playerState == STATE_RUNNING) {
playerState = STATE_SLIDING;
playerSlideTimer = 15;
}
}
void createJumpParticles() {
for (int i = 0; i < 5; i++) {
Particle p;
p.pos = Position(playerPos.x, playerPos.y + 1);
p.symbol = '*';
p.color = COLOR_STAR;
p.life = 10;
p.maxLife = 10;
p.vy = -(rand() % 20) / 10.0f - 1;
particles.push_back(p);
}
}
void update() {
frameCount++;
distance++;
if (distance % 500 == 0 && gameSpeed > 30) {
difficulty++;
gameSpeed = max(30, BASE_SPEED - difficulty * 5);
}
if (frameCount % 10 == 0) {
score += difficulty;
if (score > highScore) highScore = score;
}
updatePlayer();
updateObstacles();
updateCollectibles();
updateParticles();
updatePowerups();
if (frameCount % 3 == 0) {
bgOffset = (bgOffset + 1) % SCREEN_WIDTH;
}
}
void updatePlayer() {
if (playerState == STATE_JUMPING || playerState == STATE_DOUBLE_JUMP) {
playerVelocityY += 0.15f;
playerPos.y += (int)playerVelocityY;
if (playerPos.y >= GROUND_Y - 1) {
playerPos.y = GROUND_Y - 1;
playerState = STATE_RUNNING;
playerVelocityY = 0;
playerJumps = 0;
createLandParticles();
}
}
if (playerState == STATE_SLIDING) {
playerSlideTimer--;
if (playerSlideTimer <= 0) {
playerState = STATE_RUNNING;
}
}
if (playerPos.y > GROUND_Y - 1) {
playerPos.y = GROUND_Y - 1;
}
if (playerPos.y < 0) {
playerPos.y = 0;
playerVelocityY = 0;
}
}
void updateObstacles() {
for (size_t i = 0; i < obstacles.size(); i++) {
obstacles[i].pos.x--;
if (obstacles[i].pos.x + obstacles[i].width < playerPos.x && !obstacles[i].passed) {
obstacles[i].passed = true;
score += 10 * difficulty;
}
}
while (!obstacles.empty() && obstacles[0].pos.x < -5) {
obstacles.erase(obstacles.begin());
}
}
void updateCollectibles() {
for (size_t i = 0; i < collectibles.size(); i++) {
if (hasMagnet && magnetTimer > 0) {
if (collectibles[i].pos.x > playerPos.x) {
collectibles[i].pos.x--;
}
if (collectibles[i].pos.y < playerPos.y) {
collectibles[i].pos.y++;
} else if (collectibles[i].pos.y > playerPos.y) {
collectibles[i].pos.y--;
}
} else {
collectibles[i].pos.x--;
}
}
while (!collectibles.empty() && collectibles[0].pos.x < 0) {
collectibles.erase(collectibles.begin());
}
}
void updateParticles() {
for (size_t i = 0; i < particles.size(); i++) {
particles[i].pos.y += (int)particles[i].vy;
particles[i].vy += 0.1f;
particles[i].life--;
}
while (!particles.empty() && particles[0].life <= 0) {
particles.erase(particles.begin());
}
}
void updatePowerups() {
if (hasShield) {
shieldTimer--;
if (shieldTimer <= 0) hasShield = false;
}
if (hasMagnet) {
magnetTimer--;
if (magnetTimer <= 0) hasMagnet = false;
}
if (invincibleTimer > 0) {
invincibleTimer--;
}
}
void createLandParticles() {
for (int i = 0; i < 3; i++) {
Particle p;
p.pos = Position(playerPos.x + rand() % 3 - 1, playerPos.y);
p.symbol = '.';
p.color = COLOR_GROUND;
p.life = 8;
p.maxLife = 8;
p.vy = -0.5f;
particles.push_back(p);
}
}
void checkCollisions() {
if (invincibleTimer > 0) return;
int playerWidth = (playerState == STATE_SLIDING) ? 3 : 1;
int playerHeight = (playerState == STATE_SLIDING) ? 1 : 2;
int playerTop = (playerState == STATE_SLIDING) ? playerPos.y : playerPos.y - 1;
for (size_t i = 0; i < obstacles.size(); i++) {
if (checkRectCollision(
playerPos.x, playerTop, playerWidth, playerHeight,
obstacles[i].pos.x, obstacles[i].pos.y - obstacles[i].height + 1,
obstacles[i].width, obstacles[i].height)) {
if (hasShield) {
hasShield = false;
shieldTimer = 0;
invincibleTimer = 30;
createShieldBreakParticles();
} else {
playerState = STATE_DEAD;
gameRunning = false;
return;
}
}
}
for (size_t i = 0; i < collectibles.size(); i++) {
if (collectibles[i].active &&
abs(collectibles[i].pos.x - playerPos.x) <= 1 &&
abs(collectibles[i].pos.y - playerPos.y) <= 1) {
collectItem(i);
}
}
}
bool checkRectCollision(int x1, int y1, int w1, int h1,
int x2, int y2, int w2, int h2) {
return !(x1 + w1 <= x2 || x2 + w2 <= x1 ||
y1 + h1 <= y2 || y2 + h2 <= y1);
}
void collectItem(int index) {
Collectible& item = collectibles[index];
switch (item.type) {
case 0:
coins += item.value;
score += item.value * 2;
break;
case 1:
hasShield = true;
shieldTimer = 300;
break;
case 2:
hasMagnet = true;
magnetTimer = 300;
break;
case 3:
score += 100;
invincibleTimer = 60;
break;
}
item.active = false;
collectibles.erase(collectibles.begin() + index);
}
void createShieldBreakParticles() {
for (int i = 0; i < 15; i++) {
Particle p;
p.pos = playerPos;
p.symbol = 'O';
p.color = COLOR_SHIELD;
p.life = 20;
p.maxLife = 20;
p.vy = -(rand() % 30) / 10.0f - 1;
particles.push_back(p);
}
}
void spawnObjects() {
int spawnChance = max(20, 100 - difficulty * 3);
if (frameCount % spawnChance == 0) {
Obstacle obs;
obs.passed = false;
obs.pos.x = SCREEN_WIDTH;
int typeRoll = rand() % 100;
if (typeRoll < 40) {
obs.type = OBSTACLE_SPIKE;
obs.width = 1;
obs.height = 1;
obs.pos.y = GROUND_Y;
} else if (typeRoll < 70) {
obs.type = OBSTACLE_BLOCK;
obs.width = 1;
obs.height = 2;
obs.pos.y = GROUND_Y;
} else if (typeRoll < 85) {
obs.type = OBSTACLE_TALL_BLOCK;
obs.width = 1;
obs.height = 4;
obs.pos.y = GROUND_Y;
} else {
obs.type = OBSTACLE_BIRD;
obs.width = 1;
obs.height = 2;
obs.pos.y = GROUND_Y - 3 - rand() % 3;
}
obstacles.push_back(obs);
}
if (frameCount % 40 == 0 && rand() % 100 < 40) {
Collectible coin;
coin.pos = Position(SCREEN_WIDTH, GROUND_Y - 1 - rand() % 3);
coin.type = 0;
coin.value = 1 + rand() % 3;
coin.active = true;
collectibles.push_back(coin);
}
if (frameCount % 300 == 0 && rand() % 100 < 30) {
Collectible powerup;
powerup.pos = Position(SCREEN_WIDTH, GROUND_Y - 2);
powerup.active = true;
int roll = rand() % 3;
if (roll == 0) {
powerup.type = 1;
powerup.value = 1;
} else if (roll == 1) {
powerup.type = 2;
powerup.value = 1;
} else {
powerup.type = 3;
powerup.value = 1;
}
collectibles.push_back(powerup);
}
}
void render() {
gotoxy(0, 0);
drawBackground();
drawGround();
drawParticles();
drawCollectibles();
drawObstacles();
drawPlayer();
drawUI();
}
void drawBackground() {
for (int y = 0; y < GROUND_Y; y++) {
gotoxy(0, y);
for (int x = 0; x < SCREEN_WIDTH; x++) {
bool isStar = false;
for (size_t i = 0; i < bgStars.size(); i++) {
if (bgStars[i].x == (x + bgOffset) % SCREEN_WIDTH &&
bgStars[i].y == y) {
isStar = true;
break;
}
}
if (isStar) {
setColor(8);
cout << '.';
} else {
cout << ' ';
}
}
}
}
void drawGround() {
setColor(COLOR_GROUND);
gotoxy(0, GROUND_Y);
for (int x = 0; x < SCREEN_WIDTH; x++) {
if (x % 4 == 0) {
cout << '|';
} else {
cout << CHAR_GROUND;
}
}
for (int y = GROUND_Y + 1; y < SCREEN_HEIGHT; y++) {
gotoxy(0, y);
setColor(8);
for (int x = 0; x < SCREEN_WIDTH; x++) {
cout << ':';
}
}
}
void drawPlayer() {
if (playerState == STATE_DEAD) return;
if (invincibleTimer > 0 && frameCount % 4 < 2) {
setColor(COLOR_PLAYER + 8);
} else {
setColor(COLOR_PLAYER);
}
char playerChar;
switch (playerState) {
case STATE_SLIDING:
playerChar = CHAR_PLAYER_SLIDE;
gotoxy(playerPos.x, playerPos.y);
cout << playerChar << playerChar << playerChar;
break;
default:
if (playerPos.y < GROUND_Y - 1) {
playerChar = CHAR_PLAYER_JUMP;
} else {
playerChar = CHAR_PLAYER;
}
gotoxy(playerPos.x, playerPos.y);
cout << playerChar;
gotoxy(playerPos.x, playerPos.y - 1);
cout << 'O';
break;
}
if (hasShield) {
setColor(COLOR_SHIELD);
gotoxy(playerPos.x - 1, playerPos.y - 1);
cout << '(';
gotoxy(playerPos.x + 1, playerPos.y - 1);
cout << ')';
}
}
void drawObstacles() {
for (size_t i = 0; i < obstacles.size(); i++) {
if (obstacles[i].pos.x < 0 || obstacles[i].pos.x >= SCREEN_WIDTH) continue;
switch (obstacles[i].type) {
case OBSTACLE_SPIKE:
setColor(COLOR_OBSTACLE);
gotoxy(obstacles[i].pos.x, obstacles[i].pos.y);
cout << CHAR_SPIKE;
break;
case OBSTACLE_BLOCK:
setColor(COLOR_OBSTACLE);
gotoxy(obstacles[i].pos.x, obstacles[i].pos.y);
cout << CHAR_BLOCK;
gotoxy(obstacles[i].pos.x, obstacles[i].pos.y - 1);
cout << CHAR_BLOCK;
break;
case OBSTACLE_TALL_BLOCK:
setColor(COLOR_OBSTACLE + 4);
gotoxy(obstacles[i].pos.x, obstacles[i].pos.y);
cout << CHAR_BLOCK;
for (int h = 1; h < obstacles[i].height; h++) {
gotoxy(obstacles[i].pos.x, obstacles[i].pos.y - h);
cout << CHAR_BLOCK;
}
break;
case OBSTACLE_BIRD:
setColor(COLOR_OBSTACLE + 2);
gotoxy(obstacles[i].pos.x, obstacles[i].pos.y);
cout << CHAR_BIRD;
gotoxy(obstacles[i].pos.x, obstacles[i].pos.y - 1);
cout << CHAR_BIRD;
break;
}
}
}
void drawCollectibles() {
for (size_t i = 0; i < collectibles.size(); i++) {
if (!collectibles[i].active ||
collectibles[i].pos.x < 0 ||
collectibles[i].pos.x >= SCREEN_WIDTH) continue;
if (collectibles[i].pos.y < 0 ||
collectibles[i].pos.y >= GROUND_Y) continue;
switch (collectibles[i].type) {
case 0:
setColor(COLOR_COIN);
gotoxy(collectibles[i].pos.x, collectibles[i].pos.y);
cout << CHAR_COIN;
break;
case 1:
setColor(COLOR_SHIELD);
gotoxy(collectibles[i].pos.x, collectibles[i].pos.y);
cout << CHAR_SHIELD;
break;
case 2:
setColor(COLOR_MAGNET);
gotoxy(collectibles[i].pos.x, collectibles[i].pos.y);
cout << CHAR_MAGNET;
break;
case 3:
setColor(frameCount % 6 < 3 ? COLOR_STAR : COLOR_STAR + 8);
gotoxy(collectibles[i].pos.x, collectibles[i].pos.y);
cout << CHAR_STAR;
break;
}
}
}
void drawParticles() {
for (size_t i = 0; i < particles.size(); i++) {
if (particles[i].pos.y < 0 || particles[i].pos.y >= GROUND_Y) continue;
if (particles[i].pos.x < 0 || particles[i].pos.x >= SCREEN_WIDTH) continue;
float alpha = (float)particles[i].life / particles[i].maxLife;
int color = particles[i].color;
if (alpha < 0.5) color += 8;
setColor(color);
gotoxy(particles[i].pos.x, particles[i].pos.y);
cout << particles[i].symbol;
}
}
void drawUI() {
setColor(COLOR_UI);
gotoxy(0, SCREEN_HEIGHT + 1);
cout << "══════════════════════════════════════════════════════════════";
gotoxy(2, SCREEN_HEIGHT + 2);
cout << "Score: " << score << " ";
cout << "High: " << highScore << " ";
cout << "Coins: " << coins << " ";
cout << "Distance: " << distance / 10 << "m ";
cout << "Level: " << difficulty;
gotoxy(2, SCREEN_HEIGHT + 3);
if (hasShield) {
setColor(COLOR_SHIELD);
cout << "[SHIELD " << shieldTimer / 60 << "s] ";
}
if (hasMagnet) {
setColor(COLOR_MAGNET);
cout << "[MAGNET " << magnetTimer / 60 << "s] ";
}
if (invincibleTimer > 0) {
setColor(COLOR_STAR);
cout << "[INVINCIBLE " << invincibleTimer / 10 << "]";
}
setColor(8);
gotoxy(2, SCREEN_HEIGHT + 4);
cout << "SPACE:Jump(2x) | S:Slide | P:Pause | ESC:Quit";
if (gamePaused) {
setColor(COLOR_STAR);
gotoxy(SCREEN_WIDTH / 2 - 5, SCREEN_HEIGHT / 2);
cout << "[ PAUSED ]";
}
}
void showStartScreen() {
system("cls");
setColor(COLOR_STAR);
string title[] = {
"╔═══════════════════════════════╗",
"║ PARKOUR RUNNER v2.0 ║",
"║ ___ ___ _ ║",
"║ | _ \\ / _ \\ | | ║ 恭喜你发现了彩蛋!",
"║ | _/ | (_) ||_| ║ 作者:Wink 真正作者:Deepseek",
"║ |_| \\___/ (_) ║",
"║ ║",
"╚═══════════════════════════════╝"
};
for (int i = 0; i < 8; i++) {
gotoxy(SCREEN_WIDTH / 2 - 16, 5 + i);
cout << title[i];
}
setColor(COLOR_UI);
gotoxy(SCREEN_WIDTH / 2 - 12, 14);
cout << "Press ENTER to Start";
gotoxy(SCREEN_WIDTH / 2 - 10, 15);
cout << "High Score: " << highScore;
while (true) {
if (_kbhit()) {
char key = _getch();
if (key == 13) break;
if (key == 27) exit(0);
}
}
}
void showPauseScreen() {
setColor(COLOR_STAR);
gotoxy(SCREEN_WIDTH / 2 - 5, SCREEN_HEIGHT / 2);
cout << "[ PAUSED ]";
}
void showGameOverScreen() {
system("cls");
if (score > highScore) {
highScore = score;
saveHighScore();
}
setColor(COLOR_OBSTACLE);
string gameOver[] = {
"╔═══════════════════════════════╗",
"║ GAME OVER! ║",
"╚═══════════════════════════════╝"
};
for (int i = 0; i < 3; i++) {
gotoxy(SCREEN_WIDTH / 2 - 16, 6 + i);
cout << gameOver[i];
}
setColor(COLOR_STAR);
gotoxy(SCREEN_WIDTH / 2 - 10, 10);
cout << "Final Score: " << score;
setColor(COLOR_COIN);
gotoxy(SCREEN_WIDTH / 2 - 10, 11);
cout << "Coins: " << coins;
setColor(COLOR_UI);
gotoxy(SCREEN_WIDTH / 2 - 10, 12);
cout << "Distance: " << distance / 10 << "m";
if (score >= highScore) {
setColor(COLOR_STAR);
gotoxy(SCREEN_WIDTH / 2 - 12, 14);
cout << "*** NEW HIGH SCORE! ***";
}
setColor(COLOR_UI);
gotoxy(SCREEN_WIDTH / 2 - 12, 16);
cout << "Press ENTER to Restart";
gotoxy(SCREEN_WIDTH / 2 - 10, 17);
cout << "Press ESC to Exit";
while (true) {
if (_kbhit()) {
char key = _getch();
if (key == 13) {
initGame();
run();
break;
}
if (key == 27) {
break;
}
}
}
}
};
int main() {
srand((unsigned int)time(NULL));
ParkourGame game;
game.run();
return 0;
}