diff --git a/apps/2048.cpp b/apps/2048.cpp new file mode 100644 index 00000000..27090f95 --- /dev/null +++ b/apps/2048.cpp @@ -0,0 +1,366 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +static ui::Color colors[] = { + ui::Color::from_rgb(255, 255, 0), ui::Color::from_rgb(255, 230, 0), ui::Color::from_rgb(255, 210, 0), + ui::Color::from_rgb(255, 190, 0), ui::Color::from_rgb(255, 170, 0), ui::Color::from_rgb(255, 150, 0), + ui::Color::from_rgb(255, 130, 0), ui::Color::from_rgb(255, 110, 0), ui::Color::from_rgb(255, 90, 0), + ui::Color::from_rgb(255, 70, 0), ui::Color::from_rgb(255, 50, 0), +}; + +struct Tile +{ + int number { 0 }; + int color { 0 }; +}; + +class GameWidget final : public ui::Widget +{ + public: + static constexpr int MARGIN = 5; + + Result draw(ui::Canvas& canvas) override + { + int width = m_rect.width / 4; + int height = m_rect.height / 4; + + for (int i = 0; i < 4; i++) + { + for (int j = 0; j < 4; j++) + { + auto subcanvas = canvas.subcanvas( + ui::Rect { width * j + MARGIN, height * i + MARGIN, width - MARGIN, height - MARGIN }); + int index = i * 4 + j; + TRY(draw_tile(index, subcanvas)); + } + } + + return {}; + } + + Result handle_key_event(const ui::KeyEventRequest& request) override + { + if (!request.pressed) return ui::EventResult::DidNotHandle; + + bool should_add_tile = false; + + switch (request.key) + { + case 'w': { + bool changed; + changed = move_up(); + if (changed) should_add_tile = true; + join_up(); + changed = move_up(); + if (changed) should_add_tile = true; + } + break; + case 'a': { + bool changed; + changed = move_left(); + if (changed) should_add_tile = true; + join_left(); + changed = move_left(); + if (changed) should_add_tile = true; + } + break; + case 's': { + bool changed; + changed = move_down(); + if (changed) should_add_tile = true; + join_down(); + changed = move_down(); + if (changed) should_add_tile = true; + } + break; + case 'd': { + bool changed; + changed = move_right(); + if (changed) should_add_tile = true; + join_right(); + changed = move_right(); + if (changed) should_add_tile = true; + } + break; + case 'r': { + reset(); + return ui::EventResult::DidHandle; + } + break; + default: return ui::EventResult::DidNotHandle; + } + + if (should_add_tile) add_tile(); + + return ui::EventResult::DidHandle; + } + + bool move_left() + { + Tile new_tiles[16]; + + bool changed = false; + + for (int i = 0; i < 4; i++) + { + int pos = 0; + + for (int j = 0; j < 4; j++) + { + if (tiles[i * 4 + j].number != 0) + { + new_tiles[i * 4 + pos] = tiles[i * 4 + j]; + pos += 1; + changed = true; + } + } + } + + memcpy(tiles, new_tiles, sizeof(tiles)); + return changed; + } + + void join_left() + { + for (int i = 0; i < 4; i++) + { + for (int j = 0; j < 3; j++) + { + auto& from_tile = tiles[i * 4 + j]; + auto& to_tile = tiles[i * 4 + j + 1]; + if (from_tile.number != 0 && from_tile.number == to_tile.number) + { + from_tile.number *= 2; + from_tile.color += 1; + to_tile.number = 0; + } + } + } + } + + bool move_right() + { + Tile new_tiles[16]; + + bool changed = false; + + for (int i = 0; i < 4; i++) + { + int pos = 3; + + for (int j = 0; j < 4; j++) + { + if (tiles[i * 4 + j].number != 0) + { + new_tiles[i * 4 + pos] = tiles[i * 4 + j]; + pos -= 1; + changed = true; + } + } + } + + memcpy(tiles, new_tiles, sizeof(tiles)); + return changed; + } + + void join_right() + { + for (int i = 0; i < 4; i++) + { + for (int j = 1; j < 4; j++) + { + auto& from_tile = tiles[i * 4 + j]; + auto& to_tile = tiles[i * 4 + j - 1]; + if (from_tile.number != 0 && from_tile.number == to_tile.number) + { + from_tile.number *= 2; + from_tile.color += 1; + to_tile.number = 0; + } + } + } + } + + bool move_up() + { + Tile new_tiles[16]; + + bool changed = false; + + for (int j = 0; j < 4; j++) + { + int pos = 0; + + for (int i = 0; i < 4; i++) + { + if (tiles[i * 4 + j].number != 0) + { + new_tiles[pos * 4 + j] = tiles[i * 4 + j]; + pos += 1; + changed = true; + } + } + } + + memcpy(tiles, new_tiles, sizeof(tiles)); + return changed; + } + + void join_up() + { + for (int i = 0; i < 3; i++) + { + for (int j = 0; j < 4; j++) + { + auto& from_tile = tiles[i * 4 + j]; + auto& to_tile = tiles[i * 4 + j + 4]; + if (from_tile.number != 0 && from_tile.number == to_tile.number) + { + from_tile.number *= 2; + from_tile.color += 1; + to_tile.number = 0; + } + } + } + } + + bool move_down() + { + Tile new_tiles[16]; + + bool changed = false; + + for (int j = 0; j < 4; j++) + { + int pos = 3; + + for (int i = 0; i < 4; i++) + { + if (tiles[i * 4 + j].number != 0) + { + new_tiles[pos * 4 + j] = tiles[i * 4 + j]; + pos -= 1; + changed = true; + } + } + } + + memcpy(tiles, new_tiles, sizeof(tiles)); + return changed; + } + + void join_down() + { + for (int i = 1; i < 4; i++) + { + for (int j = 0; j < 4; j++) + { + auto& from_tile = tiles[i * 4 + j]; + auto& to_tile = tiles[i * 4 + j - 4]; + if (from_tile.number != 0 && from_tile.number == to_tile.number) + { + from_tile.number *= 2; + from_tile.color += 1; + to_tile.number = 0; + } + } + } + } + + void add_tile() + { + bool can_add_tile = false; + for (int i = 0; i < 16; i++) + { + if (tiles[i].number == 0) + { + can_add_tile = true; + break; + } + } + + if (!can_add_tile) + { + reset(); + return; + } + + int start; + do { + start = rand() % 16; + } while (tiles[start].number != 0); + tiles[start].number = 2; + tiles[start].color = 0; + } + + void reset() + { + for (int i = 0; i < 16; i++) + { + tiles[i].number = 0; + tiles[i].color = 0; + } + + add_tile(); + } + + Tile tiles[16]; + + private: + Result draw_tile(int index, ui::Canvas& canvas) + { + auto tile = tiles[index]; + + if (tile.number == 0) + { + canvas.fill(ui::GRAY); + return {}; + } + + canvas.fill(colors[tile.color]); + + auto fmt = TRY(String::format("%d"_sv, tile.number)); + + auto font = ui::Font::default_bold_font(); + auto rect = ui::align({ 0, 0, canvas.width, canvas.height }, + { 0, 0, (int)fmt.length() * font->width(), font->height() }, + ui::VerticalAlignment::Center, ui::HorizontalAlignment::Center); + auto subcanvas = canvas.subcanvas(rect); + + Utf8StringDecoder decoder(fmt.chars()); + wchar_t buf[4096]; + TRY(decoder.decode(buf, sizeof(buf))); + + font->render(buf, ui::BLACK, subcanvas); + + return {}; + } +}; + +Result luna_main(int argc, char** argv) +{ + srand((unsigned)time(NULL)); + + ui::App app; + TRY(app.init(argc, argv)); + + auto* window = TRY(ui::Window::create(ui::Rect { 300, 300, 400, 400 })); + app.set_main_window(window); + + window->set_background(ui::BLACK); + window->set_title("2048"); + + GameWidget game; + window->set_main_widget(game); + game.reset(); + + window->draw(); + + return app.run(); +} diff --git a/apps/CMakeLists.txt b/apps/CMakeLists.txt index 18faa4f2..1aa3f138 100644 --- a/apps/CMakeLists.txt +++ b/apps/CMakeLists.txt @@ -45,3 +45,5 @@ luna_app(about.cpp about) target_link_libraries(about PUBLIC ui) luna_app(taskbar.cpp taskbar) target_link_libraries(taskbar PUBLIC ui) +luna_app(2048.cpp 2048) +target_link_libraries(2048 PUBLIC ui)