Skip to content
Snippets Groups Projects
Select Git revision
  • main default protected
1 result

programming-in-c-assignment

  • Clone with SSH
  • Clone with HTTPS
  • Enhanced 2048 Game by Jamal Enoime

    A terminal-based version of the popular 2048 puzzle game with multiple game modes and visual enhancements.

    Project Structure

    2048-game/
    ├── bin/                    # Compiled executable
    ├── include/                # Header files
    │   ├── 2048.hpp           # Game logic header
    │   ├── Tile.hpp           # Tile class header
    │   ├── GameMode.hpp       # Game modes header
    │   ├── UI.hpp             # User interface header
    │   ├── Animations.hpp     # Animation system header
    │   └── SoundSystem.hpp    # Sound system header
    ├── src/                    # Source files
    │   ├── 2048.cpp           # Game logic implementation
    │   ├── Tile.cpp           # Tile class implementation
    │   ├── GameMode.cpp       # Game modes implementation
    │   ├── UI.cpp             # User interface implementation
    │   ├── Animations.cpp     # Animation system implementation
    │   ├── SoundSystem.cpp    # Sound system implementation
    │   └── main.cpp           # Entry point
    ├── obj/                    # Object files (generated during build)
    ├── res/                    # Resource files
    │   ├── sounds/            # Sound effects
    │   ├── music/             # Background music
    │   └── images/            # Images and GIFs
    ├── Makefile               # Build configuration
    └── README.md              # Project documentation

    Features

    • Interactive 2048-terminal clone
    • Multiple Game Modes:
      • Classic: Traditional 2048 gameplay
      • Timed: Race against the clock
      • Power-Up: Use special abilities
      • Challenge: Complete specific objectives with constraints
      • Zen: Relaxed mode with no game over
    • Animations and Effects:
      • Particle effects for special events
      • Confetti celebration for victories
      • Text effects and transitions between screens
    • Sound System:
      • Sound effects for game actions
      • Background music for different game states
    • Customization:
      • Multiple color themes
      • High contrast mode for accessibility
      • Toggle for sounds, music, and animations
    • Tutorial: Interactive guide to teach new players
    • Game Statistics: Track high scores and performance

    Screenshots

    • the directories
    • Building the game
    • game ui
    • user friendly tutorial
    • actual gameplay

    Dependencies

    • NCurses: For terminal-based UI
    • SDL2 and SDL2_mixer: For sound and music

    Installing Dependencies

    For Ubuntu/Debian:

    # Install NCurses
    sudo apt-get install libncurses5-dev libncursesw5-dev
    
    # Install SDL2 and SDL2_mixer
    sudo apt-get install libsdl2-dev libsdl2-mixer-dev

    For macOS (with Homebrew):

    brew install ncurses sdl2 sdl2_mixer

    For Windows:

    Use WSL (Windows Subsystem for Linux) and follow the Ubuntu/Debian instructions.

    Setting Up Sound Files

    For the music and sound effects to work properly:

    1. Create directories for sounds and music:
    mkdir -p res/sounds res/music
    1. Place your sound effect files in the res/sounds directory
    2. Place your background music files in the res/music directory
    3. Supported audio formats include WAV, MP3, and OGG (depending on your SDL2_mixer build)

    How to Build and Run

    # Build the project
    make
    
    # Run the game
    ./bin/2048
    
    # Clean build files
    make clean

    Controls

    • Arrow keys: Move tiles / Navigate menus
    • Enter: Select menu options
    • P: Pause game
    • R: Restart game
    • Q/ESC: Quit/Return to menu

    Special Mode Controls

    • Power-Up Mode:

      • 1: Activate double score
      • 2: Clear a row
      • 3: Undo move
      • 4: Upgrade a tile
    • Zen Mode:

      • C: Toggle color shift effect

    Issues and Solutions

    Known Issues

    • Gameplay Fragmentation: The gameplay sometimes fragments due to a logical error in the code.
    • Sound Issues: Sound files might not play correctly if not properly set up.

    Solutions

    To get a fully functional version of the game with no bugs but fewer features, replace the following files with the code provided below:

    UI.cpp

    #include "UI.hpp"
    #include <cmath>
    #include <algorithm>
    #include <chrono>
    #include <thread>
    
    // Credit text with author name
    const std::string CREDIT_TEXT = "2048 Game";
    
    UI::UI()
        : currentState(UIState::MAIN_MENU), previousState(UIState::MAIN_MENU), selectedMenuOption(0),
          mainWindow(nullptr), gameWindow(nullptr), scoreWindow(nullptr), infoWindow(nullptr),
          currentGameMode(GameModeType::CLASSIC), backgroundEffectTimer(0.0f),
          animationsEnabled(true), currentTheme(0) {
        
        // Initialize menu options
        mainMenuOptions = {"Play Game", "Select Mode", "Settings", "Tutorial", "Quit"};
        modeSelectionOptions = {"Classic", "Back"};
        pauseMenuOptions = {"Resume", "Restart", "Main Menu", "Quit"};
        settingsOptions = {"Animations: ON", "Theme: Default", "Back"};
        
        // Initialize themes
        initializeThemes();
    }
    
    UI::~UI() {
        destroyWindows();
        endwin();
    }
    
    bool UI::initialize() {
        // Initialize ncurses
        initscr();
        start_color();
        cbreak();
        noecho();
        keypad(stdscr, TRUE);
        curs_set(0);  // Hide cursor
        timeout(50);  // Non-blocking input with ~20fps refresh rate
        
        if (!has_colors()) {
            endwin();
            return false;
        }
        
        // Initialize color pairs
        init_pair(1, COLOR_BLACK, COLOR_WHITE);      // Default
        init_pair(2, COLOR_BLACK, COLOR_GREEN);      // 2
        init_pair(3, COLOR_BLACK, COLOR_CYAN);       // 4
        init_pair(4, COLOR_BLACK, COLOR_BLUE);       // 8
        init_pair(5, COLOR_BLACK, COLOR_MAGENTA);    // 16
        init_pair(6, COLOR_BLACK, COLOR_RED);        // 32
        init_pair(7, COLOR_WHITE, COLOR_BLUE);       // 64
        init_pair(8, COLOR_WHITE, COLOR_MAGENTA);    // 128
        init_pair(9, COLOR_WHITE, COLOR_RED);        // 256
        init_pair(10, COLOR_WHITE, COLOR_YELLOW);    // 512
        init_pair(11, COLOR_BLACK, COLOR_YELLOW);    // 1024
        init_pair(12, COLOR_WHITE, COLOR_GREEN);     // 2048
        init_pair(13, COLOR_WHITE, COLOR_RED);       // Higher
        init_pair(14, COLOR_YELLOW, COLOR_BLACK);    // Gold text
        init_pair(15, COLOR_CYAN, COLOR_BLACK);      // Cyan text
        
        // Initialize game
        game = Game2048();
        
        // Create windows
        createWindows();
        
        // Set up game callbacks
        game.addObserver([this](GameEvent event, int row, int col, int value) {
            switch (event) {
                case GameEvent::TILE_MERGED:
                    onTileMerged(row, col, value);
                    break;
                case GameEvent::TILE_SPAWNED:
                    onTileSpawned(row, col, value);
                    break;
                case GameEvent::SCORE_CHANGED:
                    onScoreChanged(value, game.getScore() - value);
                    break;
                case GameEvent::GAME_WON:
                    onVictory();
                    break;
                case GameEvent::GAME_OVER:
                    onGameOver();
                    break;
                default:
                    break;
            }
        });
        
        // Set default game mode
        setGameMode(GameModeType::CLASSIC);
        
        return true;
    }
    
    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);
        
        // Score window for displaying score and other game info
        scoreWindow = newwin(3, 20, 2, maxX - 25);
        
        // Info window for instructions and status messages
        infoWindow = newwin(5, maxX - 4, maxY - 6, 2);
    }
    
    void UI::destroyWindows() {
        if (mainWindow) delwin(mainWindow);
        if (gameWindow) delwin(gameWindow);
        if (scoreWindow) delwin(scoreWindow);
        if (infoWindow) delwin(infoWindow);
    }
    
    void UI::initializeThemes() {
        // Default theme
        Theme defaultTheme;
        defaultTheme.name = "Default";
        
        // Dark theme
        Theme darkTheme;
        darkTheme.name = "Dark";
        
        // Add themes to the list
        themes.push_back(defaultTheme);
        themes.push_back(darkTheme);
    }
    
    void UI::applyTheme(int themeIndex) {
        if (themeIndex < 0 || themeIndex >= static_cast<int>(themes.size())) {
            return;
        }
        
        currentTheme = themeIndex;
        
        // Apply theme colors
        const Theme& theme = themes[currentTheme];
        
        // Update settings menu
        settingsOptions[1] = "Theme: " + theme.name;
    }
    
    void UI::run() {
        auto lastTime = std::chrono::high_resolution_clock::now();
        
        while (currentState != UIState::EXIT_CONFIRM) {
            // Calculate delta time
            auto currentTime = std::chrono::high_resolution_clock::now();
            std::chrono::duration<float> duration = currentTime - lastTime;
            float deltaTime = duration.count();
            lastTime = currentTime;
            
            // Update background effect timer
            backgroundEffectTimer += deltaTime;
            
            // Update game mode
            if (currentState == UIState::GAME_PLAYING && gameMode) {
                gameMode->update(game, deltaTime);
            }
            
            // Draw UI based on current state
            switch (currentState) {
                case UIState::MAIN_MENU:
                    drawMainMenu();
                    handleMainMenuInput();
                    break;
                    
                case UIState::MODE_SELECTION:
                    drawModeSelection();
                    handleModeSelectionInput();
                    break;
                    
                case UIState::GAME_PLAYING:
                    drawBoard();
                    drawScore();
                    drawInfo();
                    handleGameInput();
                    break;
                    
                case UIState::PAUSE_MENU:
                    drawBoard(); // Draw board in background
                    drawPauseMenu();
                    handlePauseMenuInput();
                    break;
                    
                case UIState::GAME_OVER:
                    drawBoard(); // Draw board in background
                    drawGameOver();
                    handlePauseMenuInput(); // Reusing pause menu input handler
                    break;
                    
                case UIState::VICTORY:
                    drawBoard(); // Draw board in background
                    drawVictory();
                    handlePauseMenuInput(); // Reusing pause menu input handler
                    break;
                    
                case UIState::TUTORIAL:
                    drawTutorial();
                    handleTutorialInput();
                    break;
                    
                case UIState::SETTINGS:
                    drawSettings();
                    handleSettingsInput();
                    break;
                    
                case UIState::EXIT_CONFIRM:
                    drawExitConfirm();
                    handleExitConfirmInput();
                    break;
            }
            
            // Limit frame rate to reduce CPU usage
            std::this_thread::sleep_for(std::chrono::milliseconds(50)); // ~20 FPS
        }
    }
    
    void UI::setState(UIState newState) {
        previousState = currentState;
        currentState = newState;
        selectedMenuOption = 0;
    }
    
    UIState UI::getState() const {
        return currentState;
    }
    
    void UI::setGameMode(GameModeType mode) {
        currentGameMode = mode;
        
        // Create appropriate game mode instance - C++11 compatible version
        switch (mode) {
            case GameModeType::CLASSIC:
                gameMode.reset(new ClassicMode());
                break;
            default:
                gameMode.reset(new ClassicMode());
                break;
        }
        
        // Initialize the game mode
        if (gameMode) {
            gameMode->initialize(game);
        }
    }
    
    GameModeType UI::getGameMode() const {
        return currentGameMode;
    }
    
    void UI::setTheme(int themeIndex) {
        applyTheme(themeIndex);
    }
    
    int UI::getThemeCount() const {
        return static_cast<int>(themes.size());
    }
    
    std::string UI::getThemeName(int index) const {
        if (index >= 0 && index < static_cast<int>(themes.size())) {
            return themes[index].name;
        }
        return "Unknown";
    }
    
    void UI::toggleAnimations() {
        animationsEnabled = !animationsEnabled;
        settingsOptions[0] = std::string("Animations: ") + (animationsEnabled ? "ON" : "OFF");
    }
    
    // Drawing helpers
    void UI::drawCenteredText(WINDOW* win, int y, const std::string& text, bool highlight) {
        int maxX = getmaxx(win);
        
        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::drawBorderedWindow(WINDOW* win, const std::string& title) {
        int maxX = getmaxx(win);
        
        // Draw the border
        box(win, 0, 0);
        
        // Draw the title if provided
        if (!title.empty()) {
            int titleX = (maxX - title.length()) / 2;
            wattron(win, A_BOLD);
            mvwprintw(win, 0, titleX, " %s ", title.c_str());
            wattroff(win, A_BOLD);
        }
    }
    
    void UI::drawTile(WINDOW* win, int row, int col, int value) {
        int colorPair;
        switch (value) {
            case 0: colorPair = 1; break;   // Empty tile
            case 2: colorPair = 2; break;
            case 4: colorPair = 3; break;
            case 8: colorPair = 4; break;
            case 16: colorPair = 5; break;
            case 32: colorPair = 6; break;
            case 64: colorPair = 7; break;
            case 128: colorPair = 8; break;
            case 256: colorPair = 9; break;
            case 512: colorPair = 10; break;
            case 1024: colorPair = 11; break;
            case 2048: colorPair = 12; break;
            default: colorPair = 13; break; // Higher values
        }
        
        // Draw the tile with color if not empty
        wattron(win, COLOR_PAIR(colorPair) | A_BOLD);
        mvwprintw(win, row + 1, col * 6 + 1, "%5d", value == 0 ? 0 : value);
        wattroff(win, COLOR_PAIR(colorPair) | A_BOLD);
    }
    
    // Menu drawing functions
    void UI::drawMainMenu() {
        werase(mainWindow);
        
        int maxY, maxX;
        getmaxyx(mainWindow, maxY, maxX);
        
        // Draw title
        wattron(mainWindow, COLOR_PAIR(14) | A_BOLD);
        drawCenteredText(mainWindow, maxY / 4, "2048 GAME", false);
        wattroff(mainWindow, COLOR_PAIR(14) | A_BOLD);
        
        // Menu options
        for (size_t i = 0; i < mainMenuOptions.size(); i++) {
            int y = maxY / 3 + i * 2 + 5;
            
            if (static_cast<int>(i) == selectedMenuOption) {
                wattron(mainWindow, A_REVERSE | A_BOLD);
                drawCenteredText(mainWindow, y, mainMenuOptions[i], true);
                wattroff(mainWindow, A_REVERSE | A_BOLD);
            } else {
                drawCenteredText(mainWindow, y, mainMenuOptions[i], false);
            }
        }
        
        // 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(14) | A_BOLD);
        drawCenteredText(mainWindow, maxY / 4, "SELECT GAME MODE", false);
        wattroff(mainWindow, COLOR_PAIR(14) | A_BOLD);
        
        // Mode options
        for (size_t i = 0; i < modeSelectionOptions.size(); i++) {
            int y = maxY / 3 + i * 2 + 2;
            
            if (static_cast<int>(i) == selectedMenuOption) {
                wattron(mainWindow, A_REVERSE | A_BOLD);
                drawCenteredText(mainWindow, y, modeSelectionOptions[i], true);
                wattroff(mainWindow, A_REVERSE | A_BOLD);
            } else {
                drawCenteredText(mainWindow, y, modeSelectionOptions[i], false);
            }
        }
        
        // 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() {
        // Draw a box in the center of the screen
        int maxY, maxX;
        getmaxyx(mainWindow, maxY, maxX);
        
        int boxHeight = 6 + static_cast<int>(pauseMenuOptions.size()) * 2;
        int boxWidth = 30;
        int boxY = (maxY - boxHeight) / 2;
        int boxX = (maxX - boxWidth) / 2;
        
        // Draw the box
        for (int y = boxY; y < boxY + boxHeight; y++) {
            for (int x = boxX; x < boxX + boxWidth; x++) {
                if (y == boxY || y == boxY + boxHeight - 1 || x == boxX || x == boxX + boxWidth - 1) {
                    mvwaddch(mainWindow, y, x, ACS_BLOCK);
                } else {
                    mvwaddch(mainWindow, y, x, ' ');
                }
            }
        }
        
        // Title
        wattron(mainWindow, A_BOLD);
        mvwprintw(mainWindow, boxY + 2, boxX + (boxWidth - 6) / 2, "PAUSED");
        wattroff(mainWindow, A_BOLD);
        
        // Menu options
        for (size_t i = 0; i < pauseMenuOptions.size(); i++) {
            if (static_cast<int>(i) == selectedMenuOption) {
                wattron(mainWindow, A_REVERSE | A_BOLD);
                mvwprintw(mainWindow, boxY + 4 + static_cast<int>(i) * 2, 
                         boxX + (boxWidth - static_cast<int>(pauseMenuOptions[i].length())) / 2, 
                         "%s", pauseMenuOptions[i].c_str());
                wattroff(mainWindow, A_REVERSE | A_BOLD);
            } else {
                mvwprintw(mainWindow, boxY + 4 + static_cast<int>(i) * 2, 
                         boxX + (boxWidth - static_cast<int>(pauseMenuOptions[i].length())) / 2, 
                         "%s", pauseMenuOptions[i].c_str());
            }
        }
        
        wrefresh(mainWindow);
    }
    
    void UI::drawSettings() {
        werase(mainWindow);
        
        int maxY, maxX;
        getmaxyx(mainWindow, maxY, maxX);
        
        // Title
        wattron(mainWindow, COLOR_PAIR(15) | A_BOLD);
        drawCenteredText(mainWindow, maxY / 4, "SETTINGS", false);
        wattroff(mainWindow, COLOR_PAIR(15) | A_BOLD);
        
        // Settings options
        for (size_t i = 0; i < settingsOptions.size(); i++) {
            int y = maxY / 3 + static_cast<int>(i) * 2 + 2;
            
            if (static_cast<int>(i) == selectedMenuOption) {
                wattron(mainWindow, A_REVERSE | A_BOLD);
                drawCenteredText(mainWindow, y, settingsOptions[i], true);
                wattroff(mainWindow, A_REVERSE | A_BOLD);
            } else {
                drawCenteredText(mainWindow, y, settingsOptions[i], false);
            }
        }
        
        // Navigation help
        wattron(mainWindow, A_DIM);
        drawCenteredText(mainWindow, maxY - 2, "Use UP/DOWN to navigate, ENTER to toggle, ESC to return", false);
        wattroff(mainWindow, A_DIM);
        
        wrefresh(mainWindow);
    }
    
    void UI::drawTutorial() {
        werase(mainWindow);
        
        int maxY, maxX;
        getmaxyx(mainWindow, maxY, maxX);
        
        // Title
        wattron(mainWindow, A_BOLD);
        drawCenteredText(mainWindow, 2, "TUTORIAL", false);
        wattroff(mainWindow, A_BOLD);
        
        // Tutorial content
        std::vector<std::string> tutorial = {
            "Welcome to 2048!",
            "",
            "How to play:",
            "1. Use arrow keys to move all tiles in one direction",
            "2. When two tiles with the same number touch, they merge into one!",
            "3. After each move, a new tile appears (either 2 or 4)",
            "4. The goal is to create a tile with the value 2048",
            "",
            "Tips:",
            "- Keep your highest value tile in a corner",
            "- Try to maintain a clear path to that corner",
            "- Plan ahead and think about what will happen after each move"
        };
        
        for (size_t i = 0; i < tutorial.size(); i++) {
            if (tutorial[i].empty()) {
                continue;
            } else if (i > 0 && tutorial[i-1].empty()) {
                // Section header
                wattron(mainWindow, A_BOLD);
                mvwprintw(mainWindow, 5 + static_cast<int>(i), 5, "%s", tutorial[i].c_str());
                wattroff(mainWindow, A_BOLD);
            } else {
                mvwprintw(mainWindow, 5 + static_cast<int>(i), 5, "%s", tutorial[i].c_str());
            }
        }
        
        // Navigation help
        wattron(mainWindow, A_DIM);
        drawCenteredText(mainWindow, maxY - 2, "Press any key to return to main menu", false);
        wattroff(mainWindow, A_DIM);
        
        wrefresh(mainWindow);
    }
    
    void UI::drawExitConfirm() {
        // Draw a box in the center of the screen
        int maxY, maxX;
        getmaxyx(mainWindow, maxY, maxX);
        
        int boxHeight = 7;
        int boxWidth = 40;
        int boxY = (maxY - boxHeight) / 2;
        int boxX = (maxX - boxWidth) / 2;
        
        // Draw the box
        for (int y = boxY; y < boxY + boxHeight; y++) {
            for (int x = boxX; x < boxX + boxWidth; x++) {
                if (y == boxY || y == boxY + boxHeight - 1 || x == boxX || x == boxX + boxWidth - 1) {
                    mvwaddch(mainWindow, y, x, ACS_BLOCK);
                } else {
                    mvwaddch(mainWindow, y, x, ' ');
                }
            }
        }
        
        // Question
        wattron(mainWindow, A_BOLD);
        mvwprintw(mainWindow, boxY + 2, boxX + (boxWidth - 29) / 2, "Are you sure you want to quit?");
        wattroff(mainWindow, A_BOLD);
        
        // Options
        std::string yes = "Yes";
        std::string no = "No";
        
        // Highlight selected option
        if (selectedMenuOption == 0) {
            wattron(mainWindow, A_REVERSE | A_BOLD);
            mvwprintw(mainWindow, boxY + 4, boxX + boxWidth / 3 - 2, "%s", yes.c_str());
            wattroff(mainWindow, A_REVERSE | A_BOLD);
            
            mvwprintw(mainWindow, boxY + 4, boxX + 2 * boxWidth / 3 - 2, "%s", no.c_str());
        } else {
            mvwprintw(mainWindow, boxY + 4, boxX + boxWidth / 3 - 2, "%s", yes.c_str());
            
            wattron(mainWindow, A_REVERSE | A_BOLD);
            mvwprintw(mainWindow, boxY + 4, boxX + 2 * boxWidth / 3 - 2, "%s", no.c_str());
            wattroff(mainWindow, A_REVERSE | A_BOLD);
        }
        
        wrefresh(mainWindow);
    }
    
    void UI::drawGameOver() {
        int maxY, maxX;
        getmaxyx(mainWindow, maxY, maxX);
        
        // Display game over overlay
        int boxHeight = 7;
        int boxWidth = 30;
        int boxY = (maxY - boxHeight) / 2;
        int boxX = (maxX - boxWidth) / 2;
        
        // Draw box
        for (int y = boxY; y < boxY + boxHeight; y++) {
            for (int x = boxX; x < boxX + boxWidth; x++) {
                if (y == boxY || y == boxY + boxHeight - 1 || 
                    x == boxX || x == boxX + boxWidth - 1) {
                    mvwaddch(mainWindow, y, x, ACS_BLOCK);
                } else {
                    mvwaddch(mainWindow, y, x, ' ');
                }
            }
        }
        
        // Draw game over message
        wattron(mainWindow, COLOR_PAIR(13) | A_BOLD);
        mvwprintw(mainWindow, boxY + 2, boxX + (boxWidth - 9) / 2, "GAME OVER");
        wattroff(mainWindow, COLOR_PAIR(13) | A_BOLD);
        
        // Draw final score
        wattron(mainWindow, A_BOLD);
        std::string scoreText = "Final Score: " + std::to_string(game.getScore());
        mvwprintw(mainWindow, boxY + 4, boxX + (boxWidth - static_cast<int>(scoreText.length())) / 2, 
                 "%s", scoreText.c_str());
        wattroff(mainWindow, A_BOLD);
        
        // Instruction to continue
        wattron(mainWindow, A_DIM);
        mvwprintw(mainWindow, boxY + 6, boxX + 2, "Press any key to continue");
        wattroff(mainWindow, A_DIM);
        
        wrefresh(mainWindow);
    }
    
    void UI::drawVictory() {
        int maxY, maxX;
        getmaxyx(mainWindow, maxY, maxX);
        
        // Display victory overlay
        int boxHeight = 9;
        int boxWidth = 40;
        int boxY = (maxY - boxHeight) / 2;
        int boxX = (maxX - boxWidth) / 2;
        
        // Draw box
        for (int y = boxY; y < boxY + boxHeight; y++) {
            for (int x = boxX; x < boxX + boxWidth; x++) {
                if (y == boxY || y == boxY + boxHeight - 1 || 
                    x == boxX || x == boxX + boxWidth - 1) {
                    mvwaddch(mainWindow, y, x, ACS_BLOCK);
                } else {
                    mvwaddch(mainWindow, y, x, ' ');
                }
            }
        }
        
        // Victory message
        wattron(mainWindow, COLOR_PAIR(12) | A_BOLD);
        mvwprintw(mainWindow, boxY + 2, boxX + (boxWidth - 18) / 2, "VICTORY! YOU WIN!");
        wattroff(mainWindow, COLOR_PAIR(12) | A_BOLD);
        
        // Congratulatory message
        wattron(mainWindow, A_NORMAL);
        mvwprintw(mainWindow, boxY + 4, boxX + 2, "Congratulations! You've reached 2048!");
        wattroff(mainWindow, A_NORMAL);
        
        // Draw final score
        wattron(mainWindow, A_BOLD);
        std::string scoreText = "Final Score: " + std::to_string(game.getScore());
        mvwprintw(mainWindow, boxY + 6, boxX + (boxWidth - static_cast<int>(scoreText.length())) / 2, 
                 "%s", scoreText.c_str());
        wattroff(mainWindow, A_BOLD);
        
        // Instruction to continue
        wattron(mainWindow, A_DIM);
        mvwprintw(mainWindow, boxY + 8, boxX + 2, "Press any key to continue");
        wattroff(mainWindow, A_DIM);
        
        wrefresh(mainWindow);
    }
    
    void UI::drawBoard() {
        werase(gameWindow);
        
        // Draw border
        drawBorderedWindow(gameWindow, "2048");
        
        // Display the board
        const auto& board = game.getBoard();
        for (int i = 0; i < SIZE; ++i) {
            for (int j = 0; j < SIZE; ++j) {
                int value = board[i][j].getValue();
                drawTile(gameWindow, i, j, value);
            }
        }
        
        wrefresh(gameWindow);
    }
    
    void UI::drawScore() {
        werase(scoreWindow);
        
        // Draw border
        drawBorderedWindow(scoreWindow, "Score");
        
        // Current score
        wattron(scoreWindow, A_BOLD);
        mvwprintw(scoreWindow, 1, 2, "%d", game.getScore());
        wattroff(scoreWindow, A_BOLD);
        
        // High score
        wattron(scoreWindow, A_NORMAL);
        mvwprintw(scoreWindow, 2, 2, "Best: %d", game.getHighScore());
        wattroff(scoreWindow, A_NORMAL);
        
        wrefresh(scoreWindow);
    }
    
    void UI::drawInfo() {
        werase(infoWindow);
        
        // Draw border
        drawBorderedWindow(infoWindow, "Info");
        
        // Add controls info
        wattron(infoWindow, A_BOLD);
        mvwprintw(infoWindow, 1, 2, "Controls:");
        wattroff(infoWindow, A_BOLD);
        mvwprintw(infoWindow, 1, 12, "Arrow keys to move");
        
        mvwprintw(infoWindow, 2, 2, "P: Pause  R: Restart  Q: Quit");
        
        // Game goal
        wattron(infoWindow, A_BOLD);
        mvwprintw(infoWindow, 3, 2, "Goal: Reach the 2048 tile!");
        wattroff(infoWindow, A_BOLD);
        
        wrefresh(infoWindow);
    }
    
    // Event handlers for menu input
    void UI::handleMainMenuInput() {
        int key = wgetch(mainWindow);
        if (key == ERR) return;
        
        switch (key) {
            case KEY_UP:
                if (selectedMenuOption > 0) {
                    selectedMenuOption--;
                }
                break;
                
            case KEY_DOWN:
                if (selectedMenuOption < static_cast<int>(mainMenuOptions.size()) - 1) {
                    selectedMenuOption++;
                }
                break;
                
            case '\n': // Enter key
            case ' ':  // Space key
                switch (selectedMenuOption) {
                    case 0: // Play Game
                        setState(UIState::GAME_PLAYING);
                        break;
                        
                    case 1: // Select Mode
                        setState(UIState::MODE_SELECTION);
                        break;
                        
                    case 2: // Settings
                        setState(UIState::SETTINGS);
                        break;
                        
                    case 3: // Tutorial
                        setState(UIState::TUTORIAL);
                        break;
                        
                    case 4: // Quit
                        setState(UIState::EXIT_CONFIRM);
                        break;
                }
                break;
                
            case 'q':
            case 'Q':
                setState(UIState::EXIT_CONFIRM);
                break;
        }
    }
    
    void UI::handleModeSelectionInput() {
        int key = wgetch(mainWindow);
        if (key == ERR) return;
        
        switch (key) {
            case KEY_UP:
                if (selectedMenuOption > 0) {
                    selectedMenuOption--;
                }
                break;
                
            case KEY_DOWN:
                if (selectedMenuOption < static_cast<int>(modeSelectionOptions.size()) - 1) {
                    selectedMenuOption++;
                }
                break;
                
            case '\n': // Enter key
            case ' ':  // Space key
                if (selectedMenuOption == static_cast<int>(modeSelectionOptions.size()) - 1) {
                    // Back option
                    setState(UIState::MAIN_MENU);
                } else {
                    // Set game mode and start game
                    GameModeType mode;
                    switch (selectedMenuOption) {
                        case 0: mode = GameModeType::CLASSIC; break;
                        default: mode = GameModeType::CLASSIC; break;
                    }
                    
                    setGameMode(mode);
                    setState(UIState::GAME_PLAYING);
                }
                break;
                
            case 27: // ESC key
                setState(UIState::MAIN_MENU);
                break;
        }
    }
    
    void UI::handleGameInput() {
        int key = wgetch(mainWindow);
        if (key == ERR) return;
        
        switch (key) {
            case KEY_LEFT:
            case KEY_RIGHT:
            case KEY_UP:
            case KEY_DOWN:
                // Pass to game mode for handling
                if (gameMode) {
                    gameMode->handleInput(game, key);
                }
                break;
                
            case 'p':
            case 'P':
                setState(UIState::PAUSE_MENU);
                break;
                
            case 'r':
            case 'R':
                if (gameMode) {
                    gameMode->initialize(game);
                }
                break;
                
            case 'q':
            case 'Q':
            case 27: // ESC key
                setState(UIState::PAUSE_MENU);
                break;
        }
    }
    
    void UI::handlePauseMenuInput() {
        int key = wgetch(mainWindow);
        if (key == ERR) return;
        
        switch (key) {
            case KEY_UP:
                if (selectedMenuOption > 0) {
                    selectedMenuOption--;
                }
                break;
                
            case KEY_DOWN:
                if (selectedMenuOption < static_cast<int>(pauseMenuOptions.size()) - 1) {
                    selectedMenuOption++;
                }
                break;
                
            case '\n': // Enter key
            case ' ':  // Space key
                switch (selectedMenuOption) {
                    case 0: // Resume
                        setState(UIState::GAME_PLAYING);
                        break;
                        
                    case 1: // Restart
                        if (gameMode) {
                            gameMode->initialize(game);
                        }
                        setState(UIState::GAME_PLAYING);
                        break;
                        
                    case 2: // Main Menu
                        setState(UIState::MAIN_MENU);
                        break;
                        
                    case 3: // Quit
                        setState(UIState::EXIT_CONFIRM);
                        break;
                }
                break;
                
            case 'p':
            case 'P':
            case 27: // ESC key
                // Resume game
                setState(UIState::GAME_PLAYING);
                break;
        }
    }
    
    void UI::handleSettingsInput() {
        int key = wgetch(mainWindow);
        if (key == ERR) return;
        
        switch (key) {
            case KEY_UP:
                if (selectedMenuOption > 0) {
                    selectedMenuOption--;
                }
                break;
                
            case KEY_DOWN:
                if (selectedMenuOption < static_cast<int>(settingsOptions.size()) - 1) {
                    selectedMenuOption++;
                }
                break;
                
            case '\n': // Enter key
            case ' ':  // Space key
                switch (selectedMenuOption) {
                    case 0: // Animations toggle
                        toggleAnimations();
                        break;
                        
                    case 1: // Theme selection
                        // Cycle through themes
                        setTheme((currentTheme + 1) % static_cast<int>(themes.size()));
                        break;
                        
                    case 2: // Back
                        setState(previousState);
                        break;
                }
                break;
                
            case 27: // ESC key
                setState(previousState);
                break;
        }
    }
    
    void UI::handleTutorialInput() {
        int key = wgetch(mainWindow);
        if (key == ERR) return;
        
        // Any key returns to main menu
        setState(UIState::MAIN_MENU);
    }
    
    void UI::handleExitConfirmInput() {
        int key = wgetch(mainWindow);
        if (key == ERR) return;
        
        switch (key) {
            case KEY_LEFT:
                selectedMenuOption = 0; // Yes
                break;
                
            case KEY_RIGHT:
                selectedMenuOption = 1; // No
                break;
                
            case '\n': // Enter key
            case ' ':  // Space key
                if (selectedMenuOption == 0) {
                    // Yes - exit the game
                    endwin();
                    exit(0);
                } else {
                    // No - return to previous state
                    setState(previousState);
                }
                break;
                
            case 27: // ESC key
                setState(previousState);
                break;
        }
    }
    
    // Game event callbacks
    void UI::onTileMerged(int row, int col, int value) {
        // Simplified implementation - no animation
    }
    
    void UI::onTileSpawned(int row, int col, int value) {
        // Simplified implementation - no animation
    }
    
    void UI::onScoreChanged(int newScore, int prevScore) {
        // Simplified implementation - no animation
    }
    
    void UI::onGameOver() {
        setState(UIState::GAME_OVER);
    }
    
    void UI::onVictory() {
        setState(UIState::VICTORY);
    }

    main.cpp

    #include "UI.hpp"
    #include "Extra.hpp"
    #include <iostream>
    
    int main() {
        try {
            // Initialize the game
            initializeGame();
            
            // Create and initialize UI
            UI ui;
            if (!ui.initialize()) {
                cleanupGame();
                std::cerr << "Failed to initialize UI" << std::endl;
                return 1;
            }
            
            // Run the game
            ui.run();
            
            // Clean up
            cleanupGame();
            
            return 0;
        } catch (const std::exception& e) {
            // Clean up ncurses in case of exception
            endwin();
            std::cerr << "Error: " << e.what() << std::endl;
            return 1;
        }
    }

    Extra.hpp

    #ifndef EXTRA_HPP
    #define EXTRA_HPP
    
    #include <ncurses.h>
    
    // Initialize the game
    void initializeGame();
    
    // Clean up the game
    void cleanupGame();
    
    // Initialize colors
    void initColors();
    
    // Get color pair for a tile value
    int getTileColorPair(int value);
    
    #endif // EXTRA_HPP

    Extra.cpp

    #include "Extra.hpp"
    #include <ncurses.h>
    
    void initializeGame() {
        initscr();
        noecho();
        keypad(stdscr, TRUE);
        curs_set(0);
        
        // Initialize colors
        if(has_colors()) {
            start_color();
            initColors();
        }
    }
    
    void initColors() {
        // Initialize color pairs
        init_pair(1, COLOR_BLACK, COLOR_WHITE);      // Default
        init_pair(2, COLOR_BLACK, COLOR_GREEN);      // 2
        init_pair(3, COLOR_BLACK, COLOR_CYAN);       // 4
        init_pair(4, COLOR_BLACK, COLOR_BLUE);       // 8
        init_pair(5, COLOR_BLACK, COLOR_MAGENTA);    // 16
        init_pair(6, COLOR_BLACK, COLOR_RED);        // 32
        init_pair(7, COLOR_WHITE, COLOR_BLUE);       // 64
        init_pair(8, COLOR_WHITE, COLOR_MAGENTA);    // 128
        init_pair(9, COLOR_WHITE, COLOR_RED);        // 256
        init_pair(10, COLOR_WHITE, COLOR_YELLOW);    // 512
        init_pair(11, COLOR_BLACK, COLOR_YELLOW);    // 1024
        init_pair(12, COLOR_WHITE, COLOR_GREEN);     // 2048
        init_pair(13, COLOR_WHITE, COLOR_RED);       // Higher
        init_pair(14, COLOR_YELLOW, COLOR_BLACK);    // Gold text
        init_pair(15, COLOR_CYAN, COLOR_BLACK);      // Cyan text
    }
    
    int getTileColorPair(int value) {
        switch(value) {
            case 0: return 1;     // Default
            case 2: return 2;     // 2
            case 4: return 3;     // 4
            case 8: return 4;     // 8
            case 16: return 5;    // 16
            case 32: return 6;    // 32
            case 64: return 7;    // 64
            case 128: return 8;   // 128
            case 256: return 9;   // 256
            case 512: return 10;  // 512
            case 1024: return 11; // 1024
            case 2048: return 12; // 2048
            default: return 13;   // Higher values
        }
    }
    
    void cleanupGame() {
        endwin();
    }

    Makefile

    CC = g++
    CFLAGS = -std=c++11 -Wall -I include
    LDFLAGS = -lncurses
    
    # Directories
    SRCDIR = src
    INCDIR = include
    OBJDIR = obj
    BINDIR = bin
    
    # Create directories if they don't exist
    $(shell mkdir -p $(OBJDIR) $(BINDIR))
    
    # Source files and object files
    SOURCES = $(wildcard $(SRCDIR)/*.cpp)
    OBJECTS = $(patsubst $(SRCDIR)/%.cpp,$(OBJDIR)/%.o,$(SOURCES))
    EXECUTABLE = $(BINDIR)/2048
    
    # Main target
    all: directories $(EXECUTABLE)
    
    # Create necessary directories
    directories:
    	mkdir -p $(OBJDIR) $(BINDIR)
    
    # Link the executable
    $(EXECUTABLE): $(OBJECTS)
    	$(CC) $(OBJECTS) -o $@ $(LDFLAGS)
    	@echo "Build successful! Run with: $(EXECUTABLE)"
    
    # Compile object files
    $(OBJDIR)/%.o: $(SRCDIR)/%.cpp
    	$(CC) $(CFLAGS) -c $< -o $@
    
    # Clean build files
    clean:
    	rm -rf $(OBJDIR)/*.o $(EXECUTABLE)
    
    # Run the executable
    run: all
    	$(EXECUTABLE)
    
    .PHONY: all directories clean run

    this version can be run with make run in terminal

    Credits

    Created by Jamal Enoime