├── .gitignore ├── CMakeLists.txt ├── README.md ├── LICENSE └── src ├── fifteen.cpp └── 2048.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | .vscode 3 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.11) 2 | project(tiles 3 | VERSION 0.1 4 | LANGUAGES CXX) 5 | 6 | # --- Fetch FTXUI -------------------------------------------------------------- 7 | include(FetchContent) 8 | 9 | set(FETCHCONTENT_UPDATES_DISCONNECTED TRUE) 10 | FetchContent_Declare(ftxui 11 | GIT_REPOSITORY https://github.com/ArthurSonzogni/ftxui 12 | GIT_TAG 05fc866d7434566fd6cd0f39f62ba4bd8af855d7 13 | ) 14 | 15 | FetchContent_GetProperties(ftxui) 16 | if(NOT ftxui_POPULATED) 17 | FetchContent_Populate(ftxui) 18 | add_subdirectory(${ftxui_SOURCE_DIR} ${ftxui_BINARY_DIR} EXCLUDE_FROM_ALL) 19 | endif() 20 | 21 | add_executable(2048 src/2048.cpp) 22 | target_link_libraries(2048 PRIVATE ftxui::component) 23 | 24 | add_executable(fifteen src/fifteen.cpp) 25 | target_link_libraries(fifteen PRIVATE ftxui::component) 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tiles 2 | 3 | ![C++](https://img.shields.io/badge/c++-%2300599C.svg?style=flat&logo=c%2B%2B&logoColor=white) 4 | [![MIT license](https://img.shields.io/github/license/tusharpm/tiles)](http://opensource.org/licenses/MIT) 5 | ![size](https://img.shields.io/github/repo-size/tusharpm/tiles) 6 | [![issues](https://img.shields.io/github/issues/tusharpm/tiles)](https://github.com/tusharpm/tiles/issues) 7 | [![contributors](https://img.shields.io/github/contributors/tusharpm/tiles?color=blue)](https://github.com/tusharpm/tiles/graphs/contributors) 8 | 9 | A collection of terminal-based tiled games using [FTXUI](https://github.com/ArthurSonzogni/ftxui). 10 | - 2048 11 | ![ezgif com-gif-maker](https://user-images.githubusercontent.com/4759106/152344137-42e991d1-f840-447c-a690-cd0347bedc3b.gif) 12 | - fifteen (in-progress) 13 | 14 | ## Build instructions: 15 | ``` 16 | cmake -S . -B build 17 | cmake --build build 18 | ./build/2048 19 | # or ./build/fifteen 20 | ``` 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2021 Tushar Maheshwari. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /src/fifteen.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Tushar Maheshwari. All rights reserved. 2 | // Use of this source code is governed by the MIT license that can be found in 3 | // the LICENSE file. 4 | 5 | #include // for find, shuffle 6 | #include // for array 7 | #include // for size_t 8 | #include // for shared_ptr 9 | #include // for iota 10 | #include // for random_device, mt19937 11 | #include // for to_string, string 12 | #include // for move, swap 13 | 14 | #include // for CatchEvent, Renderer 15 | #include // for Event 16 | #include // for ScreenInteractive 17 | #include // for text, hbox, vbox, window, Element, Elements 18 | 19 | constexpr static size_t DigitsInNumber(size_t number) { 20 | size_t ret = 1; 21 | while (number /= 10) 22 | ++ret; 23 | return ret; 24 | } 25 | 26 | template struct Grid { 27 | constexpr static auto height = Height; 28 | constexpr static auto width = Width; 29 | constexpr static auto MaximumNumber = height * width; 30 | std::array elements; 31 | 32 | Grid() { 33 | std::iota(elements.begin(), elements.end(), 1); 34 | std::random_device rd; 35 | std::shuffle(elements.begin(), elements.end(), std::mt19937{rd()}); 36 | 37 | // TODO: ensure solvability. 38 | } 39 | 40 | void move(int x, int y) { 41 | auto blankPos = std::find(elements.begin(), elements.end(), MaximumNumber) - 42 | elements.begin(); 43 | auto swapWithRow = blankPos / width + y; 44 | auto swapWithCol = blankPos % width + x; 45 | if (swapWithRow >= 0 && swapWithRow < height && swapWithCol >= 0 && 46 | swapWithCol < width) { 47 | auto swapWithPos = swapWithRow * width + swapWithCol; 48 | std::swap(elements[blankPos], elements[swapWithPos]); 49 | } 50 | } 51 | }; 52 | 53 | using namespace ftxui; 54 | 55 | int main(int argc, const char *argv[]) { 56 | auto screen = ScreenInteractive::FitComponent(); 57 | using State = Grid; 58 | State state; 59 | constexpr auto DigitsInMaximumVisibleNumber = 60 | DigitsInNumber(State::MaximumNumber - 1); 61 | 62 | auto component = Renderer([&state] { 63 | Elements children; 64 | Elements row; 65 | for (auto cell : state.elements) { 66 | if (cell == State::MaximumNumber) { 67 | row.push_back(text("") | 68 | size(WIDTH, EQUAL, DigitsInMaximumVisibleNumber) | 69 | inverted | border); 70 | } else { 71 | row.push_back(text(std::to_string(cell)) | align_right | 72 | size(WIDTH, EQUAL, DigitsInMaximumVisibleNumber) | 73 | border); 74 | } 75 | if (row.size() == State::width) { 76 | children.push_back(hbox(std::move(row))); 77 | row.clear(); 78 | } 79 | } 80 | return window(text("Tiles"), vbox(std::move(children))); 81 | }); 82 | 83 | component = CatchEvent(component, [&](Event event) { 84 | if (event == Event::Escape || event.input() == "q") { 85 | screen.ExitLoopClosure()(); 86 | } else if (event == Event::Return || event.input() == "r") { 87 | state = {}; 88 | } else if (event == Event::ArrowUp) { 89 | state.move(0, 1); 90 | } else if (event == Event::ArrowDown) { 91 | state.move(0, -1); 92 | } else if (event == Event::ArrowLeft) { 93 | state.move(1, 0); 94 | } else if (event == Event::ArrowRight) { 95 | state.move(-1, 0); 96 | } 97 | return false; 98 | }); 99 | 100 | screen.Loop(component); 101 | } 102 | -------------------------------------------------------------------------------- /src/2048.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Tushar Maheshwari. All rights reserved. 2 | // Use of this source code is governed by the MIT license that can be found in 3 | // the LICENSE file. 4 | 5 | #include // for count 6 | #include // for array 7 | #include // for assert 8 | #include // for abs 9 | #include // for size_t 10 | #include // for shared_ptr 11 | #include // for random_device, mt19937 12 | #include // for to_string, string 13 | #include // for move, swap 14 | 15 | #include // for CatchEvent, Renderer 16 | #include // for Event 17 | #include // for ScreenInteractive 18 | #include // for text, hbox, vbox, window, Element, Elements 19 | 20 | template class Grid { 21 | public: 22 | constexpr static auto height = Height; 23 | constexpr static auto width = Width; 24 | constexpr static auto MaximumNumber = height * width; 25 | std::array elements{}; 26 | size_t score{}, score_increase{}; 27 | bool game_over = false; 28 | 29 | Grid() { insertOne(); } 30 | 31 | static std::string Stringify(Element elem) { 32 | return elem ? std::to_string(1 << elem) : ""; 33 | } 34 | 35 | static size_t DigitsInMaximumNumber() { 36 | return Stringify(MaximumNumber).size(); 37 | } 38 | 39 | void move(int x, int y) { 40 | assert(std::abs(x) + std::abs(y) == 1); 41 | bool valid = false; 42 | size_t move_increase = 0; 43 | const bool transpose = std::abs(y) == 1; 44 | const auto outer_end = transpose ? width : height; 45 | const auto direction = transpose ? y : x; 46 | const auto inner_begin = 47 | direction == 1 ? 0 : (height + width - outer_end) - 1; 48 | const auto inner_end = 49 | inner_begin + direction * (height + width - outer_end); 50 | for (size_t outer = 0; outer != outer_end; ++outer) { 51 | bool mergeable = false; 52 | auto write = inner_begin - direction; 53 | for (auto inner = inner_begin; inner != inner_end; inner += direction) { 54 | if (at(outer, inner, transpose) != 0) { 55 | if (mergeable && 56 | at(outer, write, transpose) == at(outer, inner, transpose)) { 57 | mergeable = false; 58 | move_increase += 1 << ++at(outer, write, transpose); 59 | at(outer, inner, transpose) = 0; 60 | valid = true; 61 | } else { 62 | mergeable = true; 63 | write += direction; 64 | if (write != inner) { 65 | std::swap(at(outer, write, transpose), 66 | at(outer, inner, transpose)); 67 | valid = true; 68 | } 69 | } 70 | } 71 | } 72 | } 73 | if (valid) { 74 | insertOne(); 75 | score += score_increase = move_increase; 76 | } 77 | } 78 | 79 | private: 80 | std::mt19937 prng{std::random_device{}()}; 81 | 82 | Element &at(int row, int col, bool transpose = false) { 83 | return transpose ? elements[col * width + row] 84 | : elements[row * width + col]; 85 | } 86 | 87 | void insertOne() { 88 | if (auto zeros = std::count(elements.begin(), elements.end(), 0)) { 89 | auto index = prng() % zeros; 90 | int counter = 0; 91 | for (auto &elem : elements) { 92 | if (elem == 0 && counter++ == index) { 93 | elem = prng() % 6 ? 1 : 2; // 16% chance for 4 tiles. 94 | break; 95 | } 96 | } 97 | if (zeros == 1) { 98 | for (size_t row = 0; row != height; ++row) { 99 | for (size_t col = 0; col != width; ++col) { 100 | if ((row && at(row, col) == at(row - 1, col)) || 101 | (col && at(row, col) == at(row, col - 1))) { 102 | return; 103 | } 104 | } 105 | } 106 | game_over = true; 107 | } 108 | } else { 109 | game_over = true; 110 | } 111 | } 112 | }; 113 | 114 | using namespace ftxui; 115 | 116 | int main(int /* argc */, const char * /* argv */[]) { 117 | auto screen = ScreenInteractive::FitComponent(); 118 | Grid state; 119 | 120 | auto component = Renderer([&state] { 121 | Elements children; 122 | Elements row; 123 | const auto DigitsInMaximumNumber = state.DigitsInMaximumNumber(); 124 | for (auto cell : state.elements) { 125 | row.push_back(text(state.Stringify(cell)) | bold | center | 126 | size(WIDTH, EQUAL, DigitsInMaximumNumber) | border | 127 | color(Color{static_cast(cell)})); 128 | if (row.size() == state.width) { 129 | children.push_back(hbox(std::move(row))); 130 | row.clear(); 131 | } 132 | } 133 | if (state.game_over) { 134 | children.push_back(text("Game Over") | bold | center | border | 135 | color(Color::Red)); 136 | } 137 | children.push_back(separator()); 138 | children.push_back( 139 | hbox(text("Score: "), text(std::to_string(state.score)) | bold, 140 | text("(+" + std::to_string(state.score_increase) + ")") | 141 | color(Color::Green))); 142 | return window(text(" 1 << 11 "), vbox(std::move(children))); 143 | }); 144 | 145 | component = CatchEvent(component, [&](Event event) { 146 | if (event == Event::Escape || event.input() == "q") { 147 | screen.ExitLoopClosure()(); 148 | } else if (event == Event::Return || event.input() == "r") { 149 | state = {}; 150 | } else if (event == Event::ArrowUp || event.input() == "w") { 151 | state.move(0, 1); 152 | } else if (event == Event::ArrowDown || event.input() == "s") { 153 | state.move(0, -1); 154 | } else if (event == Event::ArrowLeft || event.input() == "a") { 155 | state.move(1, 0); 156 | } else if (event == Event::ArrowRight || event.input() == "d") { 157 | state.move(-1, 0); 158 | } 159 | return false; 160 | }); 161 | 162 | screen.Loop(component); 163 | } 164 | --------------------------------------------------------------------------------