diff --git a/2048 b/2048 deleted file mode 100755 index c7c546fcb6a8996c9f964d9987173c168731fa75..0000000000000000000000000000000000000000 Binary files a/2048 and /dev/null differ diff --git a/Makefile b/Makefile index 30620e687d1412a80c9f4ef2aa656d574bc48c9c..8ccb1dea4b9b576b2ad19bcef74b14c310ffd1d0 100644 --- a/Makefile +++ b/Makefile @@ -1,28 +1,28 @@ -# Compiler and flags -CXX = g++ -CXXFLAGS = -std=c++11 -Wall -Wextra -Iinclude -LDFLAGS = -lncurses # Add this for linking ncurses +CC = g++ +CFLAGS = -std=c++11 -Wall -I include +LDFLAGS = -lncurses -# Source files -SRC = src/2048.cpp src/UI.cpp src/Extra.cpp src/Tile.cpp src/main.cpp +SRCDIR = src +OBJDIR = obj +BINDIR = bin -# Object files -OBJ = $(SRC:.cpp=.o) +# Source files +SOURCES = $(wildcard $(SRCDIR)/*.cpp) +OBJECTS = $(patsubst $(SRCDIR)/%.cpp,$(OBJDIR)/%.o,$(SOURCES)) +EXECUTABLE = $(BINDIR)/2048 -# Target executable -TARGET = 2048 +# Create directories if they don't exist +$(shell mkdir -p $(OBJDIR) $(BINDIR)) -# Default target -all: $(TARGET) +all: $(EXECUTABLE) -# Link object files to create the executable -$(TARGET): $(OBJ) - $(CXX) $(CXXFLAGS) -o $(TARGET) $(OBJ) $(LDFLAGS) # <-- Add $(LDFLAGS) here +$(EXECUTABLE): $(OBJECTS) + $(CC) $(OBJECTS) -o $@ $(LDFLAGS) -# Compile source files into object files -%.o: %.cpp - $(CXX) $(CXXFLAGS) -c $< -o $@ # <-- TAB indentation +$(OBJDIR)/%.o: $(SRCDIR)/%.cpp + $(CC) $(CFLAGS) -c $< -o $@ -# Clean up build artifacts clean: - rm -f $(OBJ) $(TARGET) # <-- TAB indentation \ No newline at end of file + rm -rf $(OBJDIR)/*.o $(EXECUTABLE) + +.PHONY: all clean \ No newline at end of file diff --git a/bin/2048 b/bin/2048 new file mode 100755 index 0000000000000000000000000000000000000000..646381373d323dc17565e3794d0c5796fde902f5 Binary files /dev/null and b/bin/2048 differ diff --git a/include/GameMode.hpp b/include/GameMode.hpp new file mode 100644 index 0000000000000000000000000000000000000000..a815dde893bad728557d71e6dd424dcc142c1ef5 --- /dev/null +++ b/include/GameMode.hpp @@ -0,0 +1,104 @@ +#ifndef GAMEMODE_HPP +#define GAMEMODE_HPP + +#include <string> + +enum class GameModeType { + CLASSIC, + TIMED, + POWERUP, + CHALLENGE, + TUTORIAL +}; + +class GameMode { +protected: + GameModeType type; + std::string name; + std::string description; + +public: + GameMode(GameModeType type, const std::string& name, const std::string& description); + virtual ~GameMode() = default; + + virtual void initialize() = 0; + virtual bool update(float deltaTime) = 0; + virtual void render() = 0; + + GameModeType getType() const; + const std::string& getName() const; + const std::string& getDescription() const; +}; + +// Classic mode - original 2048 gameplay +class ClassicMode : public GameMode { +public: + ClassicMode(); + void initialize() override; + bool update(float deltaTime) override; + void render() override; +}; + +// Timed mode - play against the clock +class TimedMode : public GameMode { +private: + float timeLimit; + float timeRemaining; + +public: + TimedMode(); + void initialize() override; + bool update(float deltaTime) override; + void render() override; + + float getTimeRemaining() const; +}; + +// PowerUp mode - special abilities and boosters +class PowerUpMode : public GameMode { +private: + int powerUpCount; + bool doubleScoreActive; + bool removeRowActive; + bool freezeTimeActive; + +public: + PowerUpMode(); + void initialize() override; + bool update(float deltaTime) override; + void render() override; + + void activatePowerUp(int type); +}; + +// Challenge mode - reach specific goals +class ChallengeMode : public GameMode { +private: + int targetScore; + int targetTile; + int movesLimit; + int movesUsed; + +public: + ChallengeMode(); + void initialize() override; + bool update(float deltaTime) override; + void render() override; +}; + +// Tutorial mode - learn to play +class TutorialMode : public GameMode { +private: + int tutorialStep; + bool waitingForInput; + +public: + TutorialMode(); + void initialize() override; + bool update(float deltaTime) override; + void render() override; + + void advanceTutorialStep(); +}; + +#endif \ No newline at end of file diff --git a/include/UI.hpp b/include/UI.hpp index 1e52a83003bb997c5b3254fdd82dbf87843a44f5..6f583b0e559456cf98f7c6f4f48648da2531801b 100644 --- a/include/UI.hpp +++ b/include/UI.hpp @@ -2,15 +2,80 @@ #define UI_HPP #include <ncurses.h> +#include <vector> +#include <string> #include "2048.hpp" +#include "GameMode.hpp" + +enum class UIState { + MAIN_MENU, + MODE_SELECTION, + GAME_PLAYING, + PAUSE_MENU, + GAME_OVER, + TUTORIAL +}; class UI { +private: + UIState currentState; + int selectedMenuOption; + std::vector<std::string> mainMenuOptions; + std::vector<std::string> modeSelectionOptions; + std::vector<std::string> pauseMenuOptions; + GameModeType currentGameMode; + + // Window management + WINDOW* mainWindow; + WINDOW* gameWindow; + WINDOW* scoreWindow; + WINDOW* infoWindow; + + // For animations and transitions + float transitionTimer; + bool isTransitioning; + public: + UI(); + ~UI(); + + void initialize(); void drawBoard(const Game2048& game); void handleInput(Game2048& game); - void showGameOver(Game2048& game); // Modified to allow restart + void showGameOver(Game2048& game); void displayScore(const Game2048& game); - void displayInstructions(); // New method to show game instructions + void displayInstructions(); + + // New menu-related methods + void drawMainMenu(); + void drawModeSelection(); + void drawPauseMenu(); + void handleMenuInput(); + + // Game mode management + void setGameMode(GameModeType mode); + GameModeType getGameMode() const; + + // Tutorial-specific rendering + void drawTutorial(int step); + + // PowerUp mode-specific UI + void drawPowerUpInterface(const PowerUpMode& powerUpMode); + + // Timed mode-specific UI + void drawTimerInterface(float timeRemaining); + + // Challenge mode-specific UI + void drawChallengeInterface(int targetScore, int targetTile, int movesRemaining); + + // State management + void setState(UIState newState); + UIState getState() const; + + // Utility methods + void drawCenteredText(WINDOW* win, int y, const std::string& text, bool highlight = false); + void createWindows(); + void destroyWindows(); }; #endif \ No newline at end of file diff --git a/obj/2048.o b/obj/2048.o new file mode 100644 index 0000000000000000000000000000000000000000..4cf51d0ff32df9b8ccd4253c4c7450419797ce66 Binary files /dev/null and b/obj/2048.o differ diff --git a/src/Extra.o b/obj/Extra.o similarity index 100% rename from src/Extra.o rename to obj/Extra.o diff --git a/obj/GameMode.o b/obj/GameMode.o new file mode 100644 index 0000000000000000000000000000000000000000..89923a74ad3a2e02561691fad69613a1ba3df67b Binary files /dev/null and b/obj/GameMode.o differ diff --git a/src/Tile.o b/obj/Tile.o similarity index 100% rename from src/Tile.o rename to obj/Tile.o diff --git a/obj/UI.o b/obj/UI.o new file mode 100644 index 0000000000000000000000000000000000000000..9d33e9f70128cb9f24de4a552ec79a45c72dd76e Binary files /dev/null and b/obj/UI.o differ diff --git a/obj/main.o b/obj/main.o new file mode 100644 index 0000000000000000000000000000000000000000..cbacafd64565d02669a72545504e1172c2d17029 Binary files /dev/null and b/obj/main.o differ diff --git a/src/2048.cpp b/src/2048.cpp index dd59286d8271c2b77d812ad18cef2eee55370d2f..c775f6dd3c52f0391f4c4f9e89e94b66fb38ba70 100644 --- a/src/2048.cpp +++ b/src/2048.cpp @@ -36,16 +36,6 @@ void Game2048::addToScore(int value) { score += value; } -void Game2048::drawBoard() { - clear(); - for (int i = 0; i < SIZE; ++i) { - for (int j = 0; j < SIZE; ++j) { - mvprintw(i * 2, j * 5, "%4d", board[i][j].getValue()); - } - } - refresh(); -} - void Game2048::moveLeft() { moved = false; for (int i = 0; i < SIZE; ++i) { @@ -163,4 +153,4 @@ int Game2048::getScore() const { const Tile (*Game2048::getBoard() const)[SIZE] { return board; -} \ No newline at end of file +} diff --git a/src/2048.o b/src/2048.o deleted file mode 100644 index 8980af415a3af453f1c2406f739659a30b323ac5..0000000000000000000000000000000000000000 Binary files a/src/2048.o and /dev/null differ diff --git a/src/GameMode.cpp b/src/GameMode.cpp new file mode 100644 index 0000000000000000000000000000000000000000..d5e605b899ad14246ae04cbbd264d71b1fc35f03 --- /dev/null +++ b/src/GameMode.cpp @@ -0,0 +1,150 @@ +#include "GameMode.hpp" +#include <ncurses.h> + +GameMode::GameMode(GameModeType type, const std::string& name, const std::string& description) + : type(type), name(name), description(description) { +} + +GameModeType GameMode::getType() const { + return type; +} + +const std::string& GameMode::getName() const { + return name; +} + +const std::string& GameMode::getDescription() const { + return description; +} + +// Classic Mode Implementation +ClassicMode::ClassicMode() + : GameMode(GameModeType::CLASSIC, "Classic", "The original 2048 game. Combine tiles to reach 2048!") { +} + +void ClassicMode::initialize() { + // Nothing special needed for classic mode +} + +bool ClassicMode::update(float deltaTime) { + // Classic mode has no time-based updates + return true; +} + +void ClassicMode::render() { + // Rendering handled by the main UI class +} + +// Timed Mode Implementation +TimedMode::TimedMode() + : GameMode(GameModeType::TIMED, "Timed", "Race against the clock! Score as high as possible before time runs out."), + timeLimit(180.0f), // 3 minutes + timeRemaining(180.0f) { +} + +void TimedMode::initialize() { + timeRemaining = timeLimit; +} + +bool TimedMode::update(float deltaTime) { + timeRemaining -= deltaTime; + return timeRemaining > 0.0f; +} + +void TimedMode::render() { + // Rendering handled by the UI class +} + +float TimedMode::getTimeRemaining() const { + return timeRemaining; +} + +// PowerUp Mode Implementation +PowerUpMode::PowerUpMode() + : GameMode(GameModeType::POWERUP, "Power-Up", "Use special powers to boost your score!"), + powerUpCount(3), + doubleScoreActive(false), + removeRowActive(false), + freezeTimeActive(false) { +} + +void PowerUpMode::initialize() { + powerUpCount = 3; + doubleScoreActive = false; + removeRowActive = false; + freezeTimeActive = false; +} + +bool PowerUpMode::update(float deltaTime) { + // Power-up specific logic + return true; +} + +void PowerUpMode::render() { + // Rendering handled by the UI class +} + +void PowerUpMode::activatePowerUp(int type) { + if (powerUpCount <= 0) return; + + powerUpCount--; + + switch (type) { + case 1: // Double score + doubleScoreActive = true; + break; + case 2: // Remove row + removeRowActive = true; + break; + case 3: // Freeze time + freezeTimeActive = true; + break; + } +} + +// Challenge Mode Implementation +ChallengeMode::ChallengeMode() + : GameMode(GameModeType::CHALLENGE, "Challenge", "Complete specific objectives within constraints."), + targetScore(5000), + targetTile(512), + movesLimit(100), + movesUsed(0) { +} + +void ChallengeMode::initialize() { + movesUsed = 0; +} + +bool ChallengeMode::update(float deltaTime) { + return movesUsed < movesLimit; +} + +void ChallengeMode::render() { + // Rendering handled by the UI class +} + +// Tutorial Mode Implementation +TutorialMode::TutorialMode() + : GameMode(GameModeType::TUTORIAL, "Tutorial", "Learn how to play 2048."), + tutorialStep(0), + waitingForInput(true) { +} + +void TutorialMode::initialize() { + tutorialStep = 0; + waitingForInput = true; +} + +bool TutorialMode::update(float deltaTime) { + // Tutorial is not time-based + return true; +} + +void TutorialMode::render() { + // Rendering handled by the UI class +} + +void TutorialMode::advanceTutorialStep() { + tutorialStep++; + waitingForInput = true; +} \ No newline at end of file diff --git a/src/UI.cpp b/src/UI.cpp index 630870ca801c9bfe897577fda39d6e8a7c25c13e..3486cb957467fe2b8726820ea6568291f560e777 100644 --- a/src/UI.cpp +++ b/src/UI.cpp @@ -1,16 +1,182 @@ #include "UI.hpp" #include "2048.hpp" #include "Extra.hpp" +#include "GameMode.hpp" #include <ncurses.h> #include <cstdlib> +#include <chrono> + +UI::UI() : currentState(UIState::MAIN_MENU), selectedMenuOption(0), + mainWindow(nullptr), gameWindow(nullptr), scoreWindow(nullptr), infoWindow(nullptr), + transitionTimer(0.0f), isTransitioning(false), currentGameMode(GameModeType::CLASSIC) { + + // Initialize menu options + mainMenuOptions = {"Play Game", "Select Mode", "Tutorial", "Quit"}; + modeSelectionOptions = {"Classic", "Timed", "Power-Up", "Challenge", "Back"}; + pauseMenuOptions = {"Resume", "Restart", "Main Menu", "Quit"}; +} + +UI::~UI() { + destroyWindows(); +} + +void UI::initialize() { + createWindows(); +} + +void UI::createWindows() { + int maxY, maxX; + getmaxyx(stdscr, maxY, maxX); + + // Main window covers the entire screen + mainWindow = newwin(maxY, maxX, 0, 0); + keypad(mainWindow, TRUE); + + // Game window for the actual 2048 board + gameWindow = newwin(SIZE * 2 + 2, SIZE * 6 + 2, 2, (maxX - (SIZE * 6 + 2)) / 2); + box(gameWindow, 0, 0); + + // Score window for displaying score and other game info + scoreWindow = newwin(3, 20, 2, maxX - 25); + box(scoreWindow, 0, 0); + + // Info window for instructions and status messages + infoWindow = newwin(5, maxX - 4, maxY - 6, 2); + box(infoWindow, 0, 0); +} + +void UI::destroyWindows() { + if (mainWindow) delwin(mainWindow); + if (gameWindow) delwin(gameWindow); + if (scoreWindow) delwin(scoreWindow); + if (infoWindow) delwin(infoWindow); +} + +void UI::setState(UIState newState) { + currentState = newState; + selectedMenuOption = 0; + isTransitioning = true; + transitionTimer = 0.5f; +} + +UIState UI::getState() const { + return currentState; +} + +void UI::drawCenteredText(WINDOW* win, int y, const std::string& text, bool highlight) { + int maxX, maxY; + getmaxyx(win, maxY, maxX); + int x = (maxX - text.length()) / 2; + + if (highlight) wattron(win, A_REVERSE); + mvwprintw(win, y, x, "%s", text.c_str()); + if (highlight) wattroff(win, A_REVERSE); +} + +void UI::drawMainMenu() { + werase(mainWindow); + + int maxY, maxX; + getmaxyx(mainWindow, maxY, maxX); + + // Title with color + wattron(mainWindow, COLOR_PAIR(COLOR_PAIR_2048) | A_BOLD); + drawCenteredText(mainWindow, maxY / 4, "2048 GAME", false); + wattroff(mainWindow, COLOR_PAIR(COLOR_PAIR_2048) | A_BOLD); + + // Menu options + for (size_t i = 0; i < mainMenuOptions.size(); i++) { + drawCenteredText(mainWindow, maxY / 3 + i * 2, mainMenuOptions[i], i == selectedMenuOption); + } + + // Footer + wattron(mainWindow, A_DIM); + drawCenteredText(mainWindow, maxY - 2, "Use UP/DOWN to navigate, ENTER to select", false); + wattroff(mainWindow, A_DIM); + + wrefresh(mainWindow); +} + +void UI::drawModeSelection() { + werase(mainWindow); + + int maxY, maxX; + getmaxyx(mainWindow, maxY, maxX); + + // Title + wattron(mainWindow, COLOR_PAIR(COLOR_PAIR_1024) | A_BOLD); + drawCenteredText(mainWindow, maxY / 4, "SELECT GAME MODE", false); + wattroff(mainWindow, COLOR_PAIR(COLOR_PAIR_1024) | A_BOLD); + + // Mode options + for (size_t i = 0; i < modeSelectionOptions.size(); i++) { + drawCenteredText(mainWindow, maxY / 3 + i * 2, modeSelectionOptions[i], i == selectedMenuOption); + } + + // Mode description (if one is selected) + if (selectedMenuOption < 4) { + std::string description; + switch (selectedMenuOption) { + case 0: description = "Classic 2048 - Combine tiles to reach 2048!"; break; + case 1: description = "Timed Mode - Race against the clock!"; break; + case 2: description = "Power-Up Mode - Use special abilities!"; break; + case 3: description = "Challenge Mode - Complete specific objectives!"; break; + } + + wattron(mainWindow, A_DIM); + drawCenteredText(mainWindow, maxY / 3 + modeSelectionOptions.size() * 2 + 2, description, false); + wattroff(mainWindow, A_DIM); + } + + // Navigation help + wattron(mainWindow, A_DIM); + drawCenteredText(mainWindow, maxY - 2, "Use UP/DOWN to navigate, ENTER to select", false); + wattroff(mainWindow, A_DIM); + + wrefresh(mainWindow); +} + +void UI::drawPauseMenu() { + // Dim the background + wattron(mainWindow, A_DIM); + for (int y = 0; y < getmaxy(mainWindow); y++) { + for (int x = 0; x < getmaxx(mainWindow); x++) { + mvwaddch(mainWindow, y, x, ' '); + } + } + wattroff(mainWindow, A_DIM); + + // Draw pause menu box + int maxY, maxX; + getmaxyx(mainWindow, maxY, maxX); + + int boxHeight = pauseMenuOptions.size() * 2 + 4; + int boxWidth = 20; + int boxY = (maxY - boxHeight) / 2; + int boxX = (maxX - boxWidth) / 2; + + WINDOW* pauseWin = subwin(mainWindow, boxHeight, boxWidth, boxY, boxX); + box(pauseWin, 0, 0); + + // Title + wattron(pauseWin, A_BOLD); + drawCenteredText(pauseWin, 1, "PAUSED", false); + wattroff(pauseWin, A_BOLD); + + // Menu options + for (size_t i = 0; i < pauseMenuOptions.size(); i++) { + drawCenteredText(pauseWin, 3 + i * 2, pauseMenuOptions[i], i == selectedMenuOption); + } + + wrefresh(pauseWin); + delwin(pauseWin); +} void UI::drawBoard(const Game2048& game) { - clear(); + if (currentState != UIState::GAME_PLAYING) return; - // Display title - attron(A_BOLD); - mvprintw(0, 0, "2048 Game"); - attroff(A_BOLD); + werase(gameWindow); + box(gameWindow, 0, 0); // Display the board for (int i = 0; i < SIZE; ++i) { @@ -19,69 +185,372 @@ void UI::drawBoard(const Game2048& game) { int colorPair = getTileColorPair(value); // Calculate position for the tile - int yPos = i * 2 + 2; - int xPos = j * 6; + int yPos = i * 2 + 1; + int xPos = j * 6 + 1; // Draw the tile with color - attron(COLOR_PAIR(colorPair) | A_BOLD); + wattron(gameWindow, COLOR_PAIR(colorPair) | A_BOLD); if (value != 0) { - mvprintw(yPos, xPos, "%5d", value); + mvwprintw(gameWindow, yPos, xPos, "%5d", value); } else { - mvprintw(yPos, xPos, " "); // Empty tile + mvwprintw(gameWindow, yPos, xPos, " "); // Empty tile } - attroff(COLOR_PAIR(colorPair) | A_BOLD); + wattroff(gameWindow, COLOR_PAIR(colorPair) | A_BOLD); } } - // Display the score with highlight - displayScore(game); + wrefresh(gameWindow); + + // Update score window + werase(scoreWindow); + box(scoreWindow, 0, 0); + + wattron(scoreWindow, COLOR_PAIR(COLOR_PAIR_HIGHER) | A_BOLD); + mvwprintw(scoreWindow, 1, 2, "Score: %d", game.getScore()); + wattroff(scoreWindow, COLOR_PAIR(COLOR_PAIR_HIGHER) | A_BOLD); + + wrefresh(scoreWindow); + + // Update info window + werase(infoWindow); + box(infoWindow, 0, 0); + + mvwprintw(infoWindow, 1, 2, "Controls: Arrow keys to move"); + mvwprintw(infoWindow, 2, 2, "P: Pause, R: Restart, Q: Quit"); - // Display instructions - displayInstructions(); + if (currentGameMode == GameModeType::TIMED) { + mvwprintw(infoWindow, 3, 2, "Time left: %.1f", 180.0f); // Placeholder value + } else if (currentGameMode == GameModeType::POWERUP) { + mvwprintw(infoWindow, 3, 2, "Power-ups: 3 (1-3 to use)"); + } else if (currentGameMode == GameModeType::CHALLENGE) { + mvwprintw(infoWindow, 3, 2, "Target: 512 Moves: 50/100"); + } - refresh(); + wrefresh(infoWindow); } -void UI::displayScore(const Game2048& game) { - attron(COLOR_PAIR(COLOR_PAIR_HIGHER) | A_BOLD); - mvprintw(0, SIZE * 6 + 2, "Score: %d", game.getScore()); - attroff(COLOR_PAIR(COLOR_PAIR_HIGHER) | A_BOLD); - - mvprintw(SIZE * 2 + 3, 0, "Score: %d", game.getScore()); +void UI::drawTimerInterface(float timeRemaining) { + mvwprintw(infoWindow, 3, 2, "Time left: %.1f", timeRemaining); + wrefresh(infoWindow); } -void UI::displayInstructions() { - mvprintw(SIZE * 2 + 4, 0, "Controls: Arrow keys to move, 'r' to restart, 'q' to quit"); +void UI::drawPowerUpInterface(const PowerUpMode& powerUpMode) { + mvwprintw(infoWindow, 3, 2, "Power-ups available: %d", 3); // Placeholder + mvwprintw(infoWindow, 4, 2, "1: Double Score, 2: Clear Row, 3: Undo"); + wrefresh(infoWindow); +} + +void UI::drawChallengeInterface(int targetScore, int targetTile, int movesRemaining) { + mvwprintw(infoWindow, 3, 2, "Target: %d tile Score: %d", targetTile, targetScore); + mvwprintw(infoWindow, 4, 2, "Moves remaining: %d", movesRemaining); + wrefresh(infoWindow); +} + +void UI::drawTutorial(int step) { + werase(mainWindow); + + int maxY, maxX; + getmaxyx(mainWindow, maxY, maxX); + + // Title + wattron(mainWindow, COLOR_PAIR(COLOR_PAIR_16) | A_BOLD); + drawCenteredText(mainWindow, 2, "TUTORIAL", false); + wattroff(mainWindow, COLOR_PAIR(COLOR_PAIR_16) | A_BOLD); + + // Tutorial content + std::string content; + switch (step) { + case 0: + content = "Welcome to 2048!"; + mvwprintw(mainWindow, 5, 5, "In this game, you combine tiles with the same number"); + mvwprintw(mainWindow, 6, 5, "to create a tile with the sum of those numbers."); + mvwprintw(mainWindow, 8, 5, "The goal is to create a tile with the value 2048."); + break; + case 1: + content = "Controls"; + mvwprintw(mainWindow, 5, 5, "Use the arrow keys to move all tiles in that direction."); + mvwprintw(mainWindow, 6, 5, "When two tiles with the same number touch, they merge!"); + mvwprintw(mainWindow, 8, 5, "Press 'r' to restart and 'q' to quit."); + break; + case 2: + content = "Game Modes"; + mvwprintw(mainWindow, 5, 5, "Classic: Traditional 2048 gameplay"); + mvwprintw(mainWindow, 6, 5, "Timed: Race against the clock"); + mvwprintw(mainWindow, 7, 5, "Power-Up: Use special abilities"); + mvwprintw(mainWindow, 8, 5, "Challenge: Meet specific objectives"); + break; + case 3: + content = "Strategy"; + mvwprintw(mainWindow, 5, 5, "Try to keep your highest tile in a corner."); + mvwprintw(mainWindow, 6, 5, "Plan your moves ahead. Don't make hasty decisions."); + mvwprintw(mainWindow, 7, 5, "Sometimes it's better to not merge tiles immediately."); + mvwprintw(mainWindow, 8, 5, "Focus on building up one corner of the board."); + break; + case 4: + content = "Ready to Play!"; + mvwprintw(mainWindow, 5, 5, "You've completed the tutorial!"); + mvwprintw(mainWindow, 6, 5, "Now it's time to play the game and reach 2048!"); + mvwprintw(mainWindow, 8, 5, "Good luck!"); + break; + default: + content = "End of Tutorial"; + mvwprintw(mainWindow, 5, 5, "You're ready to play 2048!"); + break; + } + + wattron(mainWindow, A_BOLD); + drawCenteredText(mainWindow, 3, content, false); + wattroff(mainWindow, A_BOLD); + + // Navigation + wattron(mainWindow, A_DIM); + if (step < 4) { + drawCenteredText(mainWindow, maxY - 2, "Press SPACE to continue or ESC to exit", false); + } else { + drawCenteredText(mainWindow, maxY - 2, "Press ESC to return to main menu", false); + } + wattroff(mainWindow, A_DIM); + + wrefresh(mainWindow); } void UI::handleInput(Game2048& game) { - int ch = getch(); - switch (ch) { - case KEY_LEFT: game.moveLeft(); break; - case KEY_RIGHT: game.moveRight(); break; - case KEY_UP: game.moveUp(); break; - case KEY_DOWN: game.moveDown(); break; - case 'r': case 'R': game.resetBoard(); break; // Restart on 'r' key - case 'q': case 'Q': endwin(); exit(0); break; // Quit on 'q' key + switch (currentState) { + case UIState::MAIN_MENU: + handleMenuInput(); + break; + + case UIState::MODE_SELECTION: + handleMenuInput(); + break; + + case UIState::GAME_PLAYING: { + int ch = wgetch(mainWindow); + switch (ch) { + case KEY_LEFT: game.moveLeft(); break; + case KEY_RIGHT: game.moveRight(); break; + case KEY_UP: game.moveUp(); break; + case KEY_DOWN: game.moveDown(); break; + case 'r': case 'R': game.resetBoard(); break; + case 'p': case 'P': setState(UIState::PAUSE_MENU); break; + case 'q': case 'Q': setState(UIState::MAIN_MENU); break; + + // PowerUp mode specific keys + case '1': + if (currentGameMode == GameModeType::POWERUP) { + // Activate power-up 1 (would need PowerUpMode instance) + } + break; + case '2': + if (currentGameMode == GameModeType::POWERUP) { + // Activate power-up 2 + } + break; + case '3': + if (currentGameMode == GameModeType::POWERUP) { + // Activate power-up 3 + } + break; + } + break; + } + + case UIState::PAUSE_MENU: + handleMenuInput(); + break; + + case UIState::GAME_OVER: { + int ch = wgetch(mainWindow); + if (ch == 'r' || ch == 'R') { + game.resetBoard(); + setState(UIState::GAME_PLAYING); + } else if (ch == 'q' || ch == 'Q' || ch == 27) { // 27 is ESC + setState(UIState::MAIN_MENU); + } + break; + } + + case UIState::TUTORIAL: { + int ch = wgetch(mainWindow); + static int tutorialStep = 0; + + if (ch == ' ') { + tutorialStep++; + if (tutorialStep > 4) { + setState(UIState::MAIN_MENU); + tutorialStep = 0; + } else { + drawTutorial(tutorialStep); + } + } else if (ch == 27) { // ESC + setState(UIState::MAIN_MENU); + tutorialStep = 0; + } + break; + } + } +} + +void UI::handleMenuInput() { + int ch = wgetch(mainWindow); + + switch (currentState) { + case UIState::MAIN_MENU: + if (ch == KEY_UP) { + selectedMenuOption = (selectedMenuOption - 1 + mainMenuOptions.size()) % mainMenuOptions.size(); + drawMainMenu(); + } else if (ch == KEY_DOWN) { + selectedMenuOption = (selectedMenuOption + 1) % mainMenuOptions.size(); + drawMainMenu(); + } else if (ch == 10 || ch == KEY_ENTER) { // Enter key + switch (selectedMenuOption) { + case 0: // Play Game + setState(UIState::GAME_PLAYING); + break; + case 1: // Select Mode + setState(UIState::MODE_SELECTION); + break; + case 2: // Tutorial + setState(UIState::TUTORIAL); + drawTutorial(0); + break; + case 3: // Quit + endwin(); + exit(0); + break; + } + } + break; + + case UIState::MODE_SELECTION: + if (ch == KEY_UP) { + selectedMenuOption = (selectedMenuOption - 1 + modeSelectionOptions.size()) % modeSelectionOptions.size(); + drawModeSelection(); + } else if (ch == KEY_DOWN) { + selectedMenuOption = (selectedMenuOption + 1) % modeSelectionOptions.size(); + drawModeSelection(); + } else if (ch == 10 || ch == KEY_ENTER) { // Enter key + switch (selectedMenuOption) { + case 0: // Classic + setGameMode(GameModeType::CLASSIC); + setState(UIState::GAME_PLAYING); + break; + case 1: // Timed + setGameMode(GameModeType::TIMED); + setState(UIState::GAME_PLAYING); + break; + case 2: // Power-Up + setGameMode(GameModeType::POWERUP); + setState(UIState::GAME_PLAYING); + break; + case 3: // Challenge + setGameMode(GameModeType::CHALLENGE); + setState(UIState::GAME_PLAYING); + break; + case 4: // Back + setState(UIState::MAIN_MENU); + break; + } + } else if (ch == 27) { // ESC + setState(UIState::MAIN_MENU); + } + break; + + case UIState::PAUSE_MENU: + if (ch == KEY_UP) { + selectedMenuOption = (selectedMenuOption - 1 + pauseMenuOptions.size()) % pauseMenuOptions.size(); + drawPauseMenu(); + } else if (ch == KEY_DOWN) { + selectedMenuOption = (selectedMenuOption + 1) % pauseMenuOptions.size(); + drawPauseMenu(); + } else if (ch == 10 || ch == KEY_ENTER) { // Enter key + switch (selectedMenuOption) { + case 0: // Resume + setState(UIState::GAME_PLAYING); + break; + case 1: // Restart + // Need to reset the game + setState(UIState::GAME_PLAYING); + break; + case 2: // Main Menu + setState(UIState::MAIN_MENU); + break; + case 3: // Quit + endwin(); + exit(0); + break; + } + } else if (ch == 27 || ch == 'p' || ch == 'P') { // ESC or P to resume + setState(UIState::GAME_PLAYING); + } + break; } } void UI::showGameOver(Game2048& game) { - attron(COLOR_PAIR(COLOR_PAIR_HIGHER) | A_BOLD); - mvprintw(SIZE * 2 + 6, 0, "GAME OVER!"); - attroff(COLOR_PAIR(COLOR_PAIR_HIGHER) | A_BOLD); + // Dim the background + wattron(mainWindow, A_DIM); + for (int y = 0; y < getmaxy(mainWindow); y++) { + for (int x = 0; x < getmaxx(mainWindow); x++) { + mvwaddch(mainWindow, y, x, ' '); + } + } + wattroff(mainWindow, A_DIM); + + // Create a popup window + int maxY, maxX; + getmaxyx(mainWindow, maxY, maxX); + + int boxHeight = 7; + int boxWidth = 30; + int boxY = (maxY - boxHeight) / 2; + int boxX = (maxX - boxWidth) / 2; + + WINDOW* gameOverWin = subwin(mainWindow, boxHeight, boxWidth, boxY, boxX); + box(gameOverWin, 0, 0); - mvprintw(SIZE * 2 + 7, 0, "Press 'r' to restart or 'q' to quit"); - refresh(); + // Title + wattron(gameOverWin, COLOR_PAIR(COLOR_PAIR_HIGHER) | A_BOLD); + drawCenteredText(gameOverWin, 1, "GAME OVER!", false); + wattroff(gameOverWin, COLOR_PAIR(COLOR_PAIR_HIGHER) | A_BOLD); + // Score + std::string scoreText = "Final Score: " + std::to_string(game.getScore()); + drawCenteredText(gameOverWin, 3, scoreText, false); + + // Options + drawCenteredText(gameOverWin, 5, "R: Restart Q: Quit", false); + + wrefresh(gameOverWin); + + // Wait for input while (true) { - int ch = getch(); + int ch = wgetch(gameOverWin); if (ch == 'r' || ch == 'R') { game.resetBoard(); + setState(UIState::GAME_PLAYING); + break; + } else if (ch == 'q' || ch == 'Q' || ch == 27) { // Q or ESC + setState(UIState::MAIN_MENU); break; - } else if (ch == 'q' || ch == 'Q') { - endwin(); - exit(0); } } -} \ No newline at end of file + + delwin(gameOverWin); +} + +void UI::setGameMode(GameModeType mode) { + currentGameMode = mode; +} + +GameModeType UI::getGameMode() const { + return currentGameMode; +} + +void UI::displayScore(const Game2048& game) { + // This is now handled in drawBoard +} + +void UI::displayInstructions() { + // This is now handled in drawBoard with the info window +} diff --git a/src/UI.o b/src/UI.o deleted file mode 100644 index 30433495b505e870cdf9860ba7f717ff9de76884..0000000000000000000000000000000000000000 Binary files a/src/UI.o and /dev/null differ diff --git a/src/main.cpp b/src/main.cpp index 8708fb433038bf557f1965c71437a41a2a6c18e5..934ac4ed69c018f3dcb34f24f595f52e2b151c73 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,7 +1,19 @@ #include "2048.hpp" #include "UI.hpp" #include "Extra.hpp" +#include "GameMode.hpp" #include <ncurses.h> +#include <chrono> +#include <thread> + +// Helper function for tracking time +float getDeltaTime() { + static auto lastTime = std::chrono::high_resolution_clock::now(); + auto currentTime = std::chrono::high_resolution_clock::now(); + std::chrono::duration<float> duration = currentTime - lastTime; + lastTime = currentTime; + return duration.count(); +} int main() { // Initialize the game and colors @@ -9,18 +21,71 @@ int main() { Game2048 game; UI ui; - - ui.drawBoard(game); - + ui.initialize(); + + // Create game mode instances + ClassicMode classicMode; + TimedMode timedMode; + PowerUpMode powerUpMode; + ChallengeMode challengeMode; + TutorialMode tutorialMode; + + // Show the main menu initially + ui.drawMainMenu(); + + float timeRemaining = 180.0f; // For timed mode + while (true) { + float deltaTime = getDeltaTime(); + + // Handle timing for timed mode + if (ui.getState() == UIState::GAME_PLAYING && ui.getGameMode() == GameModeType::TIMED) { + timeRemaining -= deltaTime; + ui.drawTimerInterface(timeRemaining); + + if (timeRemaining <= 0) { + ui.showGameOver(game); + } + } + + // Handle user input based on current state ui.handleInput(game); - ui.drawBoard(game); - - if (!game.canMove()) { - ui.showGameOver(game); - // After restart, redraw the board - ui.drawBoard(game); + + // Update display based on current state + switch (ui.getState()) { + case UIState::MAIN_MENU: + ui.drawMainMenu(); + break; + + case UIState::MODE_SELECTION: + ui.drawModeSelection(); + break; + + case UIState::GAME_PLAYING: + ui.drawBoard(game); + + // Check for game over + if (!game.canMove()) { + ui.showGameOver(game); + ui.drawBoard(game); + } + break; + + case UIState::PAUSE_MENU: + ui.drawPauseMenu(); + break; + + case UIState::GAME_OVER: + // Handled in showGameOver + break; + + case UIState::TUTORIAL: + // Handled in handleInput and drawTutorial + break; } + + // Sleep a bit to limit CPU usage + std::this_thread::sleep_for(std::chrono::milliseconds(50)); } cleanupGame(); diff --git a/src/main.o b/src/main.o deleted file mode 100644 index 618be4a5c4dc1eefa3ddc87ac01405fe3e818fbd..0000000000000000000000000000000000000000 Binary files a/src/main.o and /dev/null differ