#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(); }