← index
libmx2/libmx/knights_tour/knight.cpp
Source: libmx2/libmx/knights_tour/knight.cpp
#include "mx.hpp"

#include "argz.hpp"

#include <algorithm>

#include <array>

#include <cstdlib>

#include <ctime>

#include<functional>

#ifdef __EMSCRIPTEN__

#include <emscripten/emscripten.h>

#endif


class Tour {
public:
    Tour();
    void drawBoard(mx::mxWindow* win);
    void drawKnight(mx::mxWindow* win, mx::Texture& tex);
    void nextMove();
    void resetTour();
    int getMoves() const { return moves; }
private:
    struct Position {
        int row;
        int col;
        constexpr Position(int r = 0, int c = 0) : row(r), col(c) {}
    };
    
    void initializeBoard();
    void clearBoard();
    bool isValidMove(const Position& pos) const;
    int getDegree(const Position& pos) const;
    bool solveKnightsTour(Position pos, int moveCount);
    
    static constexpr int BOARD_SIZE = 8;
    static constexpr int TOTAL_MOVES = BOARD_SIZE * BOARD_SIZE + 1;
    static constexpr int START_X = 100;
    static constexpr int START_Y = 30;
    static constexpr int CELL_SIZE = 55;
    static constexpr int CELL_DRAW_SIZE = 50;
    static constexpr int KNIGHT_SIZE = 35;
    
    std::vector<std::vector<int>> board;
    std::vector<Position> moveSequence;
    Position knightPos;
    int moves;
    bool tourOver;
    
    static constexpr std::array<int, 8> horizontal = {2, 1, -1, -2, -2, -1, 1, 2};
    static constexpr std::array<int, 8> vertical = {-1, -2, -2, -1, 1, 2, 2, 1};
};


class KnightsTour : public obj::Object {
public:
    KnightsTour() {}
    ~KnightsTour() override {}
    
    virtual void load(mx::mxWindow *win) override {
        the_font.loadFont(win->util.getFilePath("data/font.ttf"), TEXT_SIZE);
        int w = 0, h = 0;
        knight.loadTexture(win, win->util.getFilePath("data/knight.png"), w, h, true, {255, 255, 255, 255});
        if(mx::Joystick::joysticks() > 0) {
            if(stick.open(0)) {
                mx::system_out << "Joystick opened: " << stick.name() << "\n";
            } else {
                mx::system_out << "Could not open joystick..\n";
            }
        }
    }
    
    virtual void draw(mx::mxWindow *win) override {
        tour.drawBoard(win);
        tour.drawKnight(win, knight);
        win->text.setColor({255, 255, 255, 255});
        if(tour.getMoves() < 65) {
            win->text.printText_Blended(the_font, TEXT_OFFSET_X, TEXT_OFFSET_Y, "Knights Tour - Tap Space, Press Return to Reset");
            win->text.printText_Blended(the_font, 400, TEXT_OFFSET_Y, "Moves: " + std::to_string(tour.getMoves()));
        } else {
            win->text.printText_Blended(the_font, TEXT_OFFSET_X, TEXT_OFFSET_Y, "-[ Tour Complete ]- Press Return to Reset");
        }
    }
    
    virtual void event(mx::mxWindow *win, SDL_Event &e) override {
        switch (e.type) {
            case SDL_KEYDOWN:
                switch (e.key.keysym.sym) {
                    case SDLK_SPACE:
                        tour.nextMove();
                        break;
                    case SDLK_RETURN:
                        tour.resetTour();
                        break;
                }
                break;
            case SDL_MOUSEBUTTONDOWN:
                switch(e.button.button) {
                    case SDL_BUTTON_LEFT:
                        tour.nextMove();
                    break;
                    case SDL_BUTTON_RIGHT:
                        tour.resetTour();
                    break;
                }
            break;
            case SDL_JOYBUTTONDOWN:
                switch(e.jbutton.button) {
                    case 1:
                        tour.nextMove();
                    break;
                    case 2:
                        tour.resetTour();
                    break;
                }
            break;
        }
    }
private:
    mx::Font the_font;
    mx::Texture knight;
    mx::Joystick stick;
    
    static constexpr int TEXT_OFFSET_X = 15;
    static constexpr int TEXT_OFFSET_Y = 5;
    static constexpr int TEXT_SIZE = 14;
    
    Tour tour;
};

class Intro : public obj::Object {
public:
    Intro() {}
    ~Intro() override {}

    virtual void load(mx::mxWindow *win) override {
        tex.loadTexture(win, win->util.getFilePath("data/logo.png"));
    }

    virtual void draw(mx::mxWindow *win) override {
        static Uint32 previous_time = SDL_GetTicks();
        Uint32 current_time = SDL_GetTicks();
        static int alpha = 255;
        static bool fading_out = true;
        static bool done = false;

        SDL_SetTextureAlphaMod(tex.wrapper().unwrap(), alpha);
        SDL_RenderCopy(win->renderer, tex.wrapper().unwrap(), nullptr, nullptr);

        if(done == true) {
            win->setObject(new KnightsTour());
            win->object->load(win);
            return;
        }
        
        if (current_time - previous_time >= 15) {
            previous_time = current_time;
            if (fading_out) {
                alpha -= 3;  
                if (alpha <= 0) {
                    alpha = 0;
                    fading_out = false;
                    done = true;
                }
            } else {
                alpha += 3;
                if (alpha >= 255) {
                    alpha = 255;
                    fading_out = true;
                    previous_time = SDL_GetTicks();
                }
            }
        }
    }

    virtual void event(mx::mxWindow *win, SDL_Event &e) override {

    }
    private:
        mx::Texture tex;
};

Tour::Tour() : moves(1), tourOver(false) {
    srand(static_cast<unsigned int>(time(0)));
    initializeBoard();
    resetTour();
}

void Tour::initializeBoard() {
    board.resize(BOARD_SIZE, std::vector<int>(BOARD_SIZE, 0));
}

void Tour::clearBoard() {
    for (auto& row : board) {
        std::fill(row.begin(), row.end(), 0);
    }
}

bool Tour::isValidMove(const Position& pos) const {
    return pos.row >= 0 && pos.row < BOARD_SIZE &&
    pos.col >= 0 && pos.col < BOARD_SIZE &&
    board[pos.row][pos.col] == 0;
}

int Tour::getDegree(const Position& pos) const {
    int count = 0;
    for (int i = 0; i < 8; ++i) {
        int newRow = pos.row + vertical[i];
        int newCol = pos.col + horizontal[i];
        if (isValidMove(Position(newRow, newCol))) {
            ++count;
        }
    }
    return count;
}

bool Tour::solveKnightsTour(Position pos, int moveCount) {
    if (moveCount == TOTAL_MOVES) {
        return true;
    }
    
    std::vector<std::pair<int, Position>> nextMoves;
    
    for (int i = 0; i < 8; ++i) {
        Position newPos(pos.row + vertical[i], pos.col + horizontal[i]);
        if (isValidMove(newPos)) {
            int degree = getDegree(newPos);
            nextMoves.emplace_back(degree, newPos);
        }
    }
    
    std::sort(nextMoves.begin(), nextMoves.end(),
              [](const std::pair<int, Position>& a, const std::pair<int, Position>& b) {
        return a.first < b.first;
    });
    
    for (const auto& [degree, nextPos] : nextMoves) {
        board[nextPos.row][nextPos.col] = moveCount;
        moveSequence.push_back(nextPos);
        
        if (solveKnightsTour(nextPos, moveCount + 1)) {
            return true;
        } else {
            board[nextPos.row][nextPos.col] = 0;
            moveSequence.pop_back();
        }
    }
    
    return false;
}

void Tour::resetTour() {
    clearBoard();
    knightPos = Position(rand() % BOARD_SIZE, rand() % BOARD_SIZE);
    board[knightPos.row][knightPos.col] = 1;
    moveSequence.clear();
    moveSequence.push_back(knightPos);
    solveKnightsTour(knightPos, 2);
    moves = 1;
    tourOver = false;
}

void Tour::nextMove() {
    if (moves == TOTAL_MOVES) {
        return;
    }
    
    if (!moveSequence.empty()) {
        Position nextPos = moveSequence[moves - 1];
        board[knightPos.row][knightPos.col] = -1;
        knightPos = nextPos;
        board[knightPos.row][knightPos.col] = moves;
        ++moves;
        
        if (moves == TOTAL_MOVES) {
            tourOver = true;
        }
    }
}

void Tour::drawBoard(mx::mxWindow* win) {
    SDL_SetRenderDrawColor(win->renderer, 0, 0, 0, 255);
    SDL_RenderClear(win->renderer);
    for (int i = 0; i < BOARD_SIZE; ++i) {
        for (int j = 0; j < BOARD_SIZE; ++j) {
            SDL_Rect rect = {START_X + j * CELL_SIZE, START_Y + i * CELL_SIZE, CELL_DRAW_SIZE, CELL_DRAW_SIZE};
            if (board[i][j] == -1) {
                SDL_SetRenderDrawColor(win->renderer, 0, 0, 0, 255);
            } else if ((i + j) % 2 == 0) {
                SDL_SetRenderDrawColor(win->renderer, 255, 255, 255, 255);
            } else {
                SDL_SetRenderDrawColor(win->renderer, 255, 0, 0, 255);
            }
            SDL_RenderFillRect(win->renderer, &rect);
        }
    }
}

void Tour::drawKnight(mx::mxWindow* win, mx::Texture& tex) {
    SDL_Rect dst = {START_X + knightPos.col * CELL_SIZE + 5, START_Y + knightPos.row * CELL_SIZE + 5, KNIGHT_SIZE, KNIGHT_SIZE};
    SDL_RenderCopy(win->renderer, tex.wrapper().unwrap(), nullptr, &dst);
}

class MainWindow : public mx::mxWindow {
public:
    mx::Texture bg_tex;

    MainWindow(const std::string &path, int tex_w, int tex_h)
    : mx::mxWindow("Knights Tour", tex_w, tex_h, false) {
        setPath(path);
        setIcon(util.getFilePath("data/knight.png"));
        bg_tex.createTexture(this, 640, 480);
        setObject(new Intro());
        object->load(this);
    }
    
    ~MainWindow() override {}
    
    virtual void event(SDL_Event &e) override {
        if(e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_s) {
            bg_tex.saveTexture(this,"screenshot.png");
            mx::system_out << "mx: Screenshot captured..\n";
        }
    }
    
    virtual void draw(SDL_Renderer *renderer) override {
        SDL_SetRenderTarget(renderer,bg_tex.wrapper().unwrap());
        SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
        SDL_RenderClear(renderer);
        object->draw(this);
        SDL_SetRenderTarget(renderer, nullptr);
        SDL_RenderCopy(renderer, bg_tex.wrapper().unwrap(), nullptr, nullptr);
        SDL_RenderPresent(renderer);
    }
};

MainWindow *main_win = nullptr;

void eventProc() {
    if(main_win) {
        main_win->proc();
    }
}

int main(int argc, char **argv) {
    
    Argz<std::string> parser(argc, argv);
    
    parser.addOptionSingle('h', "Display help message")
          .addOptionSingleValue('p', "assets path")
          .addOptionDoubleValue('P', "path", "assets path")
          .addOptionSingleValue('r',"Resolution WidthxHeight")
          .addOptionDoubleValue('R',"resolution", "Resolution WidthxHeight");
    
    // Parse arguments

    Argument<std::string> arg;
    std::string path;
    int value = 0;
    int tw = 960, th = 720;
    try {
        while((value = parser.proc(arg)) != -1) {
            switch(value) {
                case 'h':
                case 'v':
                    parser.help(std::cout);
                    exit(EXIT_SUCCESS);
                    break;
                case 'p':
                case 'P':
                    path = arg.arg_value;
                break;
                case 'r':
                case 'R': {
                    auto pos = arg.arg_value.find("x");
                    if(pos == std::string::npos)  {
                        mx::system_err << "Error invalid resolution use WidthxHeight\n";
                        mx::system_err.flush();
                        exit(EXIT_FAILURE);
                    }
                    std::string left, right;
                    left = arg.arg_value.substr(0, pos);
                    right = arg.arg_value.substr(pos+1);
                    tw = atoi(left.c_str());
                    th = atoi(right.c_str());
                }
                break;

            }
        }
    } catch (const ArgException<std::string>& e) {
        mx::system_err << e.text() << "\n";
    }
#ifndef __EMSCRIPTEN__

    if(path.empty()) {
        mx::system_out << "mx: No path provided trying default current directory.\n";
        path = ".";
    }
#endif

    try {
        MainWindow main_window(path, tw, th);
#ifdef __EMSCRIPTEN__

        main_win =  &main_window;
        emscripten_set_main_loop(eventProc, 0, 1);
#else

        main_window.loop();
#endif

    } catch(const mx::Exception &e) {
        mx::system_err << "mx: " << e.text() << "\n";
        mx::system_err.flush();
        exit(EXIT_FAILURE);
    }

    return 0;
}