Fork Copy #include #include #include #include #include #include #include #include using namespace std; const int WIDTH = 30; const int HEIGHT = 20; const int CELL_SIZE = 20; const int UI_HEIGHT = 100; const sf::Vector2u WINDOW_SIZE(WIDTH * CELL_SIZE, HEIGHT * CELL_SIZE + UI_HEIGHT); enum eDirection { STOP = 0, LEFT, RIGHT, UP, DOWN }; enum eGameMode { CLASSIC, NO_WALLS, OBSTACLES, SPEEDRUN }; enum eState { MENU, SETTINGS, INPUT_NAME, GAME, GAME_OVER }; struct GameSettings { bool wallCollision; bool obstaclesEnabled; int gameSpeed; eGameMode mode; }; class SnakeGame { private: bool gameOver; int score; int bestScore; int headX, headY; vector> tail; vector> obstacles; int foodX, foodY; int specialFoodX, specialFoodY; bool specialFoodActive; time_t specialFoodTimer; eDirection dir; GameSettings settings; string playerName; string inputBuffer; sf::RenderWindow window; sf::Font font; eState currentState; void SetupObstacles() { obstacles.clear(); int obstacleCount = (WIDTH * HEIGHT) / 50; for (int i = 0; i < obstacleCount; i++) { int x, y; bool valid; do { valid = true; x = rand() % WIDTH; y = rand() % HEIGHT; if (x == headX && y == headY) valid = false; if (x == foodX && y == foodY) valid = false; for (const auto& obs : obstacles) if (obs.first == x && obs.second == y) valid = false; } while (!valid); obstacles.push_back(make_pair(x, y)); } } void GenerateFood() { bool valid; do { valid = true; foodX = rand() % WIDTH; foodY = rand() % HEIGHT; if (CheckCollision(foodX, foodY)) valid = false; } while (!valid); } void GenerateSpecialFood() { bool valid; do { valid = true; specialFoodX = rand() % WIDTH; specialFoodY = rand() % HEIGHT; if (specialFoodX == foodX && specialFoodY == foodY) valid = false; if (CheckCollision(specialFoodX, specialFoodY)) valid = false; } while (!valid); specialFoodActive = true; specialFoodTimer = time(nullptr); } bool CheckCollision(int x, int y) { if (settings.wallCollision && (x < 0 || x >= WIDTH || y < 0 || y >= HEIGHT)) return true; for (const auto& segment : tail) if (segment.first == x && segment.second == y) return true; if (settings.obstaclesEnabled) for (const auto& obs : obstacles) if (obs.first == x && obs.second == y) return true; return false; } void LoadBestScore() { ifstream file("snake_scores.dat"); if (file.is_open()) { file >> bestScore; file.close(); } else { bestScore = 0; } } void SaveBestScore() { ofstream file("snake_scores.dat"); if (file.is_open()) { file << bestScore; file.close(); } } void drawCell(int x, int y, sf::Color color) { sf::RectangleShape cell(sf::Vector2f(CELL_SIZE - 1, CELL_SIZE - 1)); cell.setPosition(x * CELL_SIZE, y * CELL_SIZE); cell.setFillColor(color); window.draw(cell); } void drawText(const string& text, float x, float y, sf::Color color = sf::Color::White, int size = 14) { sf::Text displayText; displayText.setFont(font); displayText.setCharacterSize(size); displayText.setFillColor(color); displayText.setPosition(x, y); displayText.setString(text); window.draw(displayText); } void Setup(eGameMode mode) { gameOver = false; score = 0; dir = RIGHT; headX = WIDTH / 2; headY = HEIGHT / 2; tail.clear(); specialFoodActive = false; settings.mode = mode; switch (mode) { case CLASSIC: settings.wallCollision = true; settings.obstaclesEnabled = false; settings.gameSpeed = 100; break; case NO_WALLS: settings.wallCollision = false; settings.obstaclesEnabled = false; settings.gameSpeed = 100; break; case OBSTACLES: settings.wallCollision = true; settings.obstaclesEnabled = true; settings.gameSpeed = 120; SetupObstacles(); break; case SPEEDRUN: settings.wallCollision = true; settings.obstaclesEnabled = false; settings.gameSpeed = 70; break; } srand(static_cast(time(nullptr))); GenerateFood(); if (playerName.empty()) { currentState = INPUT_NAME; inputBuffer.clear(); } } void Logic() { pair prev = make_pair(headX, headY); switch (dir) { case LEFT: headX--; break; case RIGHT: headX++; break; case UP: headY--; break; case DOWN: headY++; break; } if (!settings.wallCollision) { if (headX < 0) headX = WIDTH - 1; else if (headX >= WIDTH) headX = 0; if (headY < 0) headY = HEIGHT - 1; else if (headY >= HEIGHT) headY = 0; } if (settings.wallCollision && CheckCollision(headX, headY)) gameOver = true; if (!tail.empty()) { tail.insert(tail.begin(), prev); if (!(headX == foodX && headY == foodY) && !(specialFoodActive && headX == specialFoodX && headY == specialFoodY)) tail.pop_back(); } if (headX == foodX && headY == foodY) { score += 10; if (tail.empty()) tail.push_back(prev); else tail.push_back(tail.back()); GenerateFood(); if (rand() % 4 == 0 && !specialFoodActive) GenerateSpecialFood(); } if (specialFoodActive && headX == specialFoodX && headY == specialFoodY) { score += 30; specialFoodActive = false; if (settings.obstaclesEnabled && !obstacles.empty()) obstacles.erase(obstacles.begin() + rand() % obstacles.size()); } if (specialFoodActive && time(nullptr) - specialFoodTimer > 10) specialFoodActive = false; if (score > bestScore) { bestScore = score; SaveBestScore(); } } public: SnakeGame() : window(sf::VideoMode(WINDOW_SIZE.x, WINDOW_SIZE.y), "Snake Game") { settings.wallCollision = true; settings.obstaclesEnabled = false; settings.gameSpeed = 100; settings.mode = CLASSIC; currentState = MENU; LoadBestScore(); if (!font.loadFromFile("arial.ttf")) { cout << "Error loading font. Please ensure arial.ttf is in the project directory." << endl; } window.setFramerateLimit(60); } void DrawMenu() { window.clear(sf::Color::Black); drawText("==== SNAKE GAME ====", 50, 50, sf::Color::White, 24); drawText("1. Start Classic Mode", 50, 100); drawText("2. No Walls Mode", 50, 130); drawText("3. Obstacles Mode", 50, 160); drawText("4. Speedrun Mode", 50, 190); drawText("5. Settings", 50, 220); drawText("6. Exit", 50, 250); window.display(); } void DrawSettings() { window.clear(sf::Color::Black); drawText("==== SETTINGS ====", 50, 50, sf::Color::White, 24); drawText("1. Player Name: " + playerName, 50, 100); drawText("2. Game Speed: " + to_string(settings.gameSpeed) + "ms", 50, 130); drawText("3. Wall Collision: " + string(settings.wallCollision ? "ON" : "OFF"), 50, 160); drawText("4. Back to Main Menu", 50, 190); window.display(); } void DrawInputName() { window.clear(sf::Color::Black); drawText("Enter your name:", 50, 50, sf::Color::White, 24); drawText(inputBuffer + "_", 50, 100); // Hiển thị con trỏ nhấp nháy window.display(); } void DrawGame() { window.clear(sf::Color::Black); // Vẽ lưới for (int y = 0; y < HEIGHT; y++) { for (int x = 0; x < WIDTH; x++) { sf::RectangleShape cell(sf::Vector2f(CELL_SIZE, CELL_SIZE)); cell.setPosition(x * CELL_SIZE, y * CELL_SIZE); cell.setFillColor(sf::Color(20, 20, 20)); cell.setOutlineThickness(1); cell.setOutlineColor(sf::Color(50, 50, 50)); window.draw(cell); } } // Vẽ tường (nếu bật) if (settings.wallCollision) { for (int x = 0; x < WIDTH; x++) { drawCell(x, 0, sf::Color::Green); drawCell(x, HEIGHT - 1, sf::Color::Green); } for (int y = 0; y < HEIGHT; y++) { drawCell(0, y, sf::Color::Green); drawCell(WIDTH - 1, y, sf::Color::Green); } } // Vẽ đầu rắn drawCell(headX, headY, sf::Color::Red); // Vẽ thân rắn for (const auto& segment : tail) drawCell(segment.first, segment.second, sf::Color::Yellow); // Vẽ thức ăn drawCell(foodX, foodY, sf::Color::Blue); // Vẽ thức ăn đặc biệt if (specialFoodActive) drawCell(specialFoodX, specialFoodY, sf::Color::Magenta); // Vẽ chướng ngại vật if (settings.obstaclesEnabled) for (const auto& obs : obstacles) drawCell(obs.first, obs.second, sf::Color(128, 128, 128)); // Vẽ UI stringstream ss; ss << "Player: " << playerName << " | Score: " << score << " | Best: " << bestScore; drawText(ss.str(), 10, HEIGHT * CELL_SIZE + 10); ss.str(""); ss << "Mode: "; switch (settings.mode) { case CLASSIC: ss << "Classic"; break; case NO_WALLS: ss << "No Walls"; break; case OBSTACLES: ss << "Obstacles"; break; case SPEEDRUN: ss << "Speedrun"; break; } ss << " | Speed: " << settings.gameSpeed << "ms"; drawText(ss.str(), 10, HEIGHT * CELL_SIZE + 30); if (specialFoodActive) { int remaining = 10 - (time(nullptr) - specialFoodTimer); if (remaining > 0) { ss.str(""); ss << "Special Food: " << remaining << "s remaining"; drawText(ss.str(), 10, HEIGHT * CELL_SIZE + 50); } } drawText("Controls: WASD (Move) | P (Pause) | X (Exit)", 10, HEIGHT * CELL_SIZE + 70); window.display(); } void DrawGameOver() { window.clear(sf::Color::Black); drawText("GAME OVER!", 50, 50, sf::Color::Red, 24); drawText("Final Score: " + to_string(score), 50, 100); if (score == bestScore) drawText("NEW HIGH SCORE!", 50, 130, sf::Color::Yellow); drawText("Press any key to continue...", 50, 160); window.display(); } void HandleInput(sf::Event& event) { if (event.type == sf::Event::Closed) window.close(); if (event.type == sf::Event::KeyPressed) { switch (currentState) { case MENU: switch (event.key.code) { case sf::Keyboard::Num1: Setup(CLASSIC); currentState = GAME; break; case sf::Keyboard::Num2: Setup(NO_WALLS); currentState = GAME; break; case sf::Keyboard::Num3: Setup(OBSTACLES); currentState = GAME; break; case sf::Keyboard::Num4: Setup(SPEEDRUN); currentState = GAME; break; case sf::Keyboard::Num5: currentState = SETTINGS; break; case sf::Keyboard::Num6: window.close(); break; } break; case SETTINGS: switch (event.key.code) { case sf::Keyboard::Num1: currentState = INPUT_NAME; inputBuffer = playerName; break; case sf::Keyboard::Num2: settings.gameSpeed = (settings.gameSpeed == 100 ? 120 : 100); break; // Toggle tốc độ case sf::Keyboard::Num3: settings.wallCollision = !settings.wallCollision; break; case sf::Keyboard::Num4: currentState = MENU; break; } break; case INPUT_NAME: if (event.key.code == sf::Keyboard::Return) { playerName = inputBuffer; currentState = SETTINGS; inputBuffer.clear(); } else if (event.key.code == sf::Keyboard::BackSpace && !inputBuffer.empty()) { inputBuffer.pop_back(); } break; case GAME: switch (event.key.code) { case sf::Keyboard::A: if (dir != RIGHT) dir = LEFT; break; case sf::Keyboard::D: if (dir != LEFT) dir = RIGHT; break; case sf::Keyboard::W: if (dir != DOWN) dir = UP; break; case sf::Keyboard::S: if (dir != UP) dir = DOWN; break; case sf::Keyboard::P: while (true) { sf::Event pauseEvent; window.pollEvent(pauseEvent); if (pauseEvent.type == sf::Event::KeyPressed && pauseEvent.key.code == sf::Keyboard::P) break; sf::sleep(sf::milliseconds(100)); } break; case sf::Keyboard::X: gameOver = true; currentState = GAME_OVER; break; } break; case GAME_OVER: currentState = MENU; // Nhấn bất kỳ để quay menu break; } } else if (event.type == sf::Event::TextEntered && currentState == INPUT_NAME) { if (event.text.unicode < 128) { inputBuffer += static_cast(event.text.unicode); } } } void Run() { while (window.isOpen()) { sf::Event event; while (window.pollEvent(event)) { HandleInput(event); } switch (currentState) { case MENU: DrawMenu(); break; case SETTINGS: DrawSettings(); break; case INPUT_NAME: DrawInputName(); break; case GAME: Logic(); DrawGame(); if (gameOver) currentState = GAME_OVER; sf::sleep(sf::milliseconds(settings.gameSpeed)); break; case GAME_OVER: DrawGameOver(); break; } } } }; int main() { SnakeGame game; game.Run(); return 0; }