diff --git a/.github/workflows/building.yml b/.github/workflows/building.yml index 93b6f57..2bf818b 100644 --- a/.github/workflows/building.yml +++ b/.github/workflows/building.yml @@ -22,6 +22,10 @@ jobs: os: ubuntu-latest compiler: clang++ + - name: MacOS GCC + os: macos-latest + compiler: g++ + #- name: MacOS Clang # os: macos-latest # compiler: clang++ @@ -30,6 +34,10 @@ jobs: os: windows-latest compiler: g++ + - name: Windows Clang + os: windows-latest + compiler: clang++ + steps: - name: Checkout uses: actions/checkout@v4 diff --git a/CMakeLists.txt b/CMakeLists.txt index 9e07497..97d7afd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,7 +7,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) set(PROJECT_COPYRIGHT "Copyright (c) 2022-2024 r3w0p") -set(PROJECT_DESCRIPTION "A TUI version of the Caravan card game from Fallout: New Vegas.") +set(PROJECT_DESCRIPTION "A command-line version of the Caravan card game from Fallout: New Vegas.") set(PROJECT_URL "https://github.com/r3w0p/caravan") set(CMAKE_EXE_LINKER_FLAGS ${CMAKE_EXE_LINKER_FLAGS} "-static") diff --git a/README b/README index e5fe49e..870d915 100644 --- a/README +++ b/README @@ -5,7 +5,7 @@ | v1.0.0 | GPL-3.0 | (c) 2022-2024 r3w0p | -A CLI version of the Caravan card game from Fallout: New Vegas. +A command-line version of the Caravan card game from Fallout: New Vegas. =========== diff --git a/include/caravan/controller/controller_tui.h b/include/caravan/controller/controller_tui.h index f98eef9..0a50dce 100644 --- a/include/caravan/controller/controller_tui.h +++ b/include/caravan/controller/controller_tui.h @@ -11,7 +11,7 @@ class ControllerTUI : public Controller { public: explicit ControllerTUI() = default; - void on_view_user_input(std::string input) override; // from ViewSubscriber + void on_view_user_input(std::string input, bool confirmed) override; // from ViewSubscriber }; #endif //CARAVAN_CONTROLLER_TUI_H diff --git a/include/caravan/view/view.h b/include/caravan/view/view.h index 737b4f2..e68ae86 100644 --- a/include/caravan/view/view.h +++ b/include/caravan/view/view.h @@ -12,7 +12,7 @@ class ViewSubscriber { public: - virtual void on_view_user_input(std::string input) = 0; + virtual void on_view_user_input(std::string input, bool confirmed) = 0; }; class View : public Publisher { diff --git a/src/caravan/controller/controller_tui.cpp b/src/caravan/controller/controller_tui.cpp index 5d4a72a..46f23dd 100644 --- a/src/caravan/controller/controller_tui.cpp +++ b/src/caravan/controller/controller_tui.cpp @@ -4,26 +4,230 @@ #include "caravan/controller/controller_tui.h" -void ControllerTUI::on_view_user_input(std::string input) { +void ControllerTUI::on_view_user_input(std::string input, bool confirmed) { if(closed) return; + // TODO A future feature that highlights parts of the board as a command is typed, + // so that the user has a better idea of what their command will do. + if(!confirmed) return; + // Decipher input GameCommand command; + /* + * EXIT + */ if( - input.size() >= 4 and - (input.at(0) == 'E' or input.at(0) == 'e') and - (input.at(1) == 'X' or input.at(1) == 'x') and - (input.at(2) == 'I' or input.at(2) == 'i') and - (input.at(3) == 'T' or input.at(3) == 't') + input.size() == 4 and + (input.at(0) == 'E' or input.at(0) == 'e') and + (input.at(1) == 'X' or input.at(1) == 'x') and + (input.at(2) == 'I' or input.at(2) == 'i') and + (input.at(3) == 'T' or input.at(3) == 't') ) { command.type = OPTION_EXIT; + } + + /* + * FIRST + * - OPTION TYPE + */ + + if (input.size() < 1) + throw CaravanFatalException("A command type has not been entered."); + + switch (input.at(0)) { + case 'P': + case 'p': + command.type = OPTION_PLAY; + /* + * P2F + * "Play numeral card at hand pos 2 onto caravan F" + * + * P4F8 + * "Play face card at hand pos 4 onto caravan F, slot 8" + */ + break; + case 'D': + case 'd': + command.type = OPTION_DISCARD; + /* + * D3 + * "Discard card at hand pos 3" + */ + break; + case 'C': + case 'c': + /* + * CE + * "Clear caravan E" + */ + command.type = OPTION_CLEAR; + break; + default: + throw CaravanGameException("Invalid option '" + std::to_string(input.at(0)) + "', must be one of: (P)lay, (D)iscard, (C)lear."); + } + + /* + * SECOND + * - HAND POSITION or + * - CARAVAN NAME + */ + + if (command.type == OPTION_PLAY or command.type == OPTION_DISCARD) { + + if (input.size() < 2) + throw CaravanFatalException("A hand position has not been entered."); + + switch (input.at(1)) { + case '1': + command.pos_hand = 1; + break; + case '2': + command.pos_hand = 2; + break; + case '3': + command.pos_hand = 3; + break; + case '4': + command.pos_hand = 4; + break; + case '5': + command.pos_hand = 5; + break; + case '6': + command.pos_hand = 6; + break; + case '7': + command.pos_hand = 7; + break; + case '8': + command.pos_hand = 8; + break; + default: + throw CaravanGameException("Invalid hand position '" + std::to_string(input.at(1)) + "'."); + } + + } else if (command.type == OPTION_CLEAR) { - } else if(input.size() >= 2) { + if (input.size() < 2) + throw CaravanFatalException("A caravan name has not been entered."); - } else return; + switch (input.at(1)) { + case 'A': + case 'a': + command.caravan_name = CARAVAN_A; + break; + case 'B': + case 'b': + command.caravan_name = CARAVAN_B; + break; + case 'C': + case 'c': + command.caravan_name = CARAVAN_C; + break; + case 'D': + case 'd': + command.caravan_name = CARAVAN_D; + break; + case 'E': + case 'e': + command.caravan_name = CARAVAN_E; + break; + case 'F': + case 'f': + command.caravan_name = CARAVAN_F; + break; + default: + throw CaravanGameException("Invalid caravan name '" + std::to_string(input.at(1)) + "', must be between: A-F."); + } + } + + // Discard/clear does not require caravan name/pos + if (command.type == OPTION_DISCARD or command.type == OPTION_CLEAR) + return command; // TODO fix + + /* + * THIRD + * - CARAVAN NAME + */ + + if (input.size() < 3) + throw CaravanFatalException("A caravan name has not been entered."); + + switch (input.at(2)) { + case 'A': + case 'a': + command.caravan_name = CARAVAN_A; + break; + case 'B': + case 'b': + command.caravan_name = CARAVAN_B; + break; + case 'C': + case 'c': + command.caravan_name = CARAVAN_C; + break; + case 'D': + case 'd': + command.caravan_name = CARAVAN_D; + break; + case 'E': + case 'e': + command.caravan_name = CARAVAN_E; + break; + case 'F': + case 'f': + command.caravan_name = CARAVAN_F; + break; + default: + throw CaravanGameException( + "Invalid caravan name '" + std::to_string(input.at(2)) + "', must be between: A-F."); + } + + /* + * FOURTH + * - CARAVAN POSITION (used when selecting Face card only) + */ + + // Caravan position only specified here when playing a face card + if ((char) input.at(3) == '\0') { + command.pos_caravan = 0; + return command; // TODO fix + } + + switch (input.at(3)) { + case '1': + command.pos_caravan = 1; + break; + case '2': + command.pos_caravan = 2; + break; + case '3': + command.pos_caravan = 3; + break; + case '4': + command.pos_caravan = 4; + break; + case '5': + command.pos_caravan = 5; + break; + case '6': + command.pos_caravan = 6; + break; + case '7': + command.pos_caravan = 7; + break; + case '8': + command.pos_caravan = 8; + break; + case '9': + command.pos_caravan = 9; + break; + default: + throw CaravanGameException("Invalid caravan position '" + std::to_string(input.at(3)) + "'."); + } - // Pass to subscribers + // Pass to subscribers (i.e., Model) for(ControllerSubscriber *s : subscribers) { s->on_controller_command(command); } diff --git a/src/caravan/mainOLD.cpp b/src/caravan/mainOLD.cpp deleted file mode 100644 index 8546503..0000000 --- a/src/caravan/mainOLD.cpp +++ /dev/null @@ -1,211 +0,0 @@ -#include // for text, center, separator, operator|, flex, Element, vbox, Fit, hbox, border -#include // for Full, Screen -#include -#include // for allocator - -#include "ftxui/dom/node.hpp" // for Render -#include "ftxui/screen/color.hpp" // for ftxui - -#include -#include - - -int main() { - using namespace ftxui; - - auto make_position = [](std::string position) { - return text(position) | borderEmpty | size(WIDTH, EQUAL, 4) | size(HEIGHT, EQUAL, 2); - }; - - auto make_card = [](std::wstring card) { - return text(card) | borderDouble | size(WIDTH, EQUAL, 5) | size(HEIGHT, EQUAL, 2); - }; - - auto make_faces = []() { - // TODO - return vbox({ - text(L"QQK"), - text(L"♠♠♠") - }) | borderEmpty | size(WIDTH, EQUAL, 5) | size(HEIGHT, EQUAL, 2); - }; - - auto make_caravan_card = [&](std::string position, std::wstring card) { - return hbox({ - make_position(position), - make_card(card), - make_faces(), - }) | hcenter | size(HEIGHT, EQUAL, 4); - }; - - auto make_player_card = [&](std::string position, std::wstring card) { - return hbox({ - make_position(position), - make_card(card), - separatorEmpty() | borderEmpty | size(WIDTH, EQUAL, 4) - }) | hcenter | size(HEIGHT, EQUAL, 4); - }; - - auto make_caravan_p1 = [&](std::string title) { - return window( - text(title) | hcenter | bold, - vbox({ - make_caravan_card("1", L" K♠"), - make_caravan_card("2", L" K♠"), - make_caravan_card("3", L" K♠"), - make_caravan_card("4", L" K♠"), - make_caravan_card("5", L" K♠"), - make_caravan_card("6", L" K♠"), - make_caravan_card("7", L" K♠"), - make_caravan_card("8", L" K♠"), - }) - ) | size(HEIGHT, EQUAL, 32); - }; - - auto make_caravan_p2 = [&](std::string title) { - return window( - text(title) | hcenter | bold, - vbox({ - make_caravan_card("8", L" K♠"), - make_caravan_card("7", L" K♠"), - make_caravan_card("6", L" K♠"), - make_caravan_card("5", L" K♠"), - make_caravan_card("4", L" K♠"), - make_caravan_card("3", L" K♠"), - make_caravan_card("2", L" K♠"), - make_caravan_card("1", L" K♠"), - }) - ) | size(HEIGHT, EQUAL, 32); - }; - - auto make_player_p1 = [&](std::string title) { - return window( - text(title) | hcenter | bold, - vbox({ - make_player_card("1", L" K♠"), - make_player_card("2", L" K♠"), - make_player_card("3", L" K♠"), - make_player_card("4", L" K♠"), - make_player_card("5", L" K♠"), - make_player_card("6", L" K♠"), - make_player_card("7", L" K♠"), - make_player_card("8", L" K♠"), - }) - ) | size(WIDTH, EQUAL, 17) | size(HEIGHT, EQUAL, 32); - }; - - auto make_player_p2 = [&](std::string title) { - return window( - text(title) | hcenter | bold, - vbox({ - make_player_card(" ", L"###"), - make_player_card(" ", L"###"), - make_player_card(" ", L"###"), - make_player_card(" ", L"###"), - make_player_card(" ", L"###"), - make_player_card(" ", L"###"), - make_player_card(" ", L"###"), - make_player_card(" ", L"###"), - }) - ) | size(WIDTH, EQUAL, 17) | size(HEIGHT, EQUAL, 32); - }; - - auto document = hbox({ // outermost box - - vbox({ // game area - - hbox({ // opp game area - - separatorEmpty(), - make_caravan_p2(" D (100, ASC) "), - separatorEmpty(), - make_caravan_p2(" E "), - separatorEmpty(), - make_caravan_p2(" F "), - separatorEmpty(), - - }) | size(HEIGHT, EQUAL, 35), - - hbox({ // you game area - - separatorEmpty(), - make_caravan_p1(" A "), - separatorEmpty(), - make_caravan_p1(" B "), - separatorEmpty(), - make_caravan_p1(" C "), - separatorEmpty(), - - }) | size(HEIGHT, EQUAL, 35), - - }) | size(HEIGHT, EQUAL, 67), - - separatorEmpty(), - separatorEmpty(), - separatorEmpty(), - - vbox({ // deck area - - hbox({ // opp deck area - - separatorEmpty(), - make_player_p2(" OPP "), - separatorEmpty(), - - }) | size(HEIGHT, EQUAL, 35), - - hbox({ // you deck area - - separatorEmpty(), - make_player_p1(" YOU "), - separatorEmpty(), - - }) | size(HEIGHT, EQUAL, 35), - - }) | size(HEIGHT, EQUAL, 67), - /* - separatorEmpty(), - separatorEmpty(), - separatorEmpty(), - - window( // input area - text(" INPUT ") | hcenter | bold, - vbox({ - text("YOU played QC on F7, changing F's direction"), - text("OPP played JS on A5, removing 3D"), - - separatorEmpty(), - - text("YOU > "), - }) | borderEmpty - ) | size(WIDTH, EQUAL, 60) | vcenter, // add height - */ - - }) | border | hcenter | size(HEIGHT, EQUAL, 70); - - auto terminal_size = Terminal::Size(); - auto document_width = 76; - auto document_height = 70; - - if (terminal_size.dimx < document_width) { - printf("Terminal: x=%d y=%d\n", terminal_size.dimx, terminal_size.dimy); - printf("Document: x=%d y=%d\n", document_width, document_height); - printf("Terminal too small.\n"); - exit(1); - } - - auto screen = Screen::Create(Dimension::Fixed(document_width), Dimension::Fixed(document_height)); - screen.Clear(); - - Render(screen, document); - screen.Print(); - - std::this_thread::sleep_for(std::chrono::milliseconds(1000 * 3)); - - screen.Clear(); - - printf("Terminal: x=%d y=%d\n", terminal_size.dimx, terminal_size.dimy); - printf("Document: x=%d y=%d\n", document_width, document_height); - - return 0; -} - diff --git a/src/caravan/view/view_tui.cpp b/src/caravan/view/view_tui.cpp index 085240e..5422bae 100644 --- a/src/caravan/view/view_tui.cpp +++ b/src/caravan/view/view_tui.cpp @@ -246,6 +246,7 @@ void ViewTUI::run() { // Input data std::string user_input; + bool confirmed; std::string command; std::string message; Dimensions terminal_size {}; @@ -283,18 +284,27 @@ void ViewTUI::run() { } // Handle user input - if(user_input.ends_with('\n')) { - command = user_input; + command = user_input; + if(command.ends_with('\n')) { command.pop_back(); // removes '\n' user_input = ""; - message += command + " "; + confirmed = true; + } else { + confirmed = false; } - if(command == "EXIT") screen.Exit(); + // Send input to subscribers (i.e., Controller) + for (ViewSubscriber *vs: subscribers) { + vs->on_view_user_input(command, confirmed); + } + + // TODO replace this with an 'exit early' signal from the model + // (i.e. not exciting because game has finished, but because of an exit signal from user) + //if(command == "EXIT") screen.Exit(); - // TODO immediately wipe command once passed to controller return gen_game(comp_user_input, user_input, command, message); }); screen.Loop(renderer); + screen.Clear(); }