Luna/apps/2048.cpp
apio 280b0c90af
Some checks failed
Build and test / build (push) Failing after 1m49s
apps+editor+libui+terminal: Rework the layout system
2024-05-19 14:21:30 +02:00

374 lines
9.1 KiB
C++

#include <luna/String.h>
#include <luna/Utf8.h>
#include <stdlib.h>
#include <time.h>
#include <ui/Alignment.h>
#include <ui/App.h>
#include <ui/Font.h>
#include <ui/Layout.h>
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:
GameWidget(ui::Window* window, ui::Widget* parent) : ui::Widget(window, parent)
{
}
void show_tree(int indent) override
{
os::println("%*s- 2048 GameWidget (%d,%d,%d,%d)", indent, "", m_rect.pos.x, m_rect.pos.y, m_rect.width,
m_rect.height);
}
static constexpr int MARGIN = 5;
Result<void> 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<ui::EventResult> handle_key_event(const ui::KeyEventRequest& request) override
{
if (!request.pressed) return ui::EventResult::DidNotHandle;
bool should_add_tile = false;
switch (request.code)
{
case moon::K_UpArrow: {
bool changed;
changed = move_up();
if (changed) should_add_tile = true;
join_up();
changed = move_up();
if (changed) should_add_tile = true;
}
break;
case moon::K_LeftArrow: {
bool changed;
changed = move_left();
if (changed) should_add_tile = true;
join_left();
changed = move_left();
if (changed) should_add_tile = true;
}
break;
case moon::K_DownArrow: {
bool changed;
changed = move_down();
if (changed) should_add_tile = true;
join_down();
changed = move_down();
if (changed) should_add_tile = true;
}
break;
case moon::K_RightArrow: {
bool changed;
changed = move_right();
if (changed) should_add_tile = true;
join_right();
changed = move_right();
if (changed) should_add_tile = true;
}
break;
case moon::K_Home: {
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<void> 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<int> luna_main(int, char**)
{
srand((unsigned)time(NULL));
ui::App app;
TRY(app.init());
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");
auto* game = TRY(window->create_main_widget<GameWidget>());
game->reset();
return app.run();
}