Compare commits

..

No commits in common. "2b0d198b059c7376b784bba0cc9984a6f4d63b91" and "9ff9d679e96598b42e9ccc63788245d73cefc6ea" have entirely different histories.

17 changed files with 93 additions and 350 deletions

View File

@ -5,7 +5,7 @@ VERSION='git'
DOWNLOAD_URL="https://github.com/jewelcodes/tinygb.git#57fdaff675a6b5b963b2b6624868d9698eabe375"
configure() {
make -f Makefile.banan_os clean
:
}
build() {

View File

@ -1,14 +1,16 @@
From 7f7c6402e384591bca63021aa97d60a8107de88d Mon Sep 17 00:00:00 2001
From 3e565f7d35e842e246db3371776adae74d02ae62 Mon Sep 17 00:00:00 2001
From: Bananymous <bananymousosq@gmail.com>
Date: Mon, 5 May 2025 00:59:03 +0300
Date: Wed, 23 Apr 2025 13:10:38 +0300
Subject: [PATCH] Add support for banan-os
---
Makefile.banan_os | 28 +++
src/platform/banan-os/main.cpp | 362 +++++++++++++++++++++++++++++++++
2 files changed, 390 insertions(+)
src/platform/banan-os/main.cpp | 365 +++++++++++++++++++++++++++++++
src/platform/banan-os/main.cpp.o | Bin 0 -> 23536 bytes
3 files changed, 393 insertions(+)
create mode 100644 Makefile.banan_os
create mode 100644 src/platform/banan-os/main.cpp
create mode 100644 src/platform/banan-os/main.cpp.o
diff --git a/Makefile.banan_os b/Makefile.banan_os
new file mode 100644
@ -46,10 +48,10 @@ index 0000000..22e191e
+ $(LD) $(OBJ) -o tinygb $(LDFLAGS)
diff --git a/src/platform/banan-os/main.cpp b/src/platform/banan-os/main.cpp
new file mode 100644
index 0000000..94f249e
index 0000000..e9c6a02
--- /dev/null
+++ b/src/platform/banan-os/main.cpp
@@ -0,0 +1,362 @@
@@ -0,0 +1,365 @@
+
+/* tinygb - a tiny gameboy emulator
+ (c) 2022 by jewel */
@ -244,8 +246,10 @@ index 0000000..94f249e
+ ASSERT(s_window->height() == static_cast<uint32_t>(SGB_HEIGHT * scaling));
+}
+
+int main(int argc, char **argv) {
+ if(argc != 2) {
+int main(int argc, char **argv)
+{
+ if(argc != 2)
+ {
+ fprintf(stdout, "usage: %s rom_name\n", argv[0]);
+ return 1;
+ }
@ -258,7 +262,8 @@ index 0000000..94f249e
+
+ // open the rom
+ FILE* rom_file = fopen(rom_filename, "r");
+ if(!rom_file) {
+ if (rom_file == nullptr)
+ {
+ write_log("unable to open %s for reading: %s\n", rom_filename, strerror(errno));
+ return 1;
+ }
@ -270,7 +275,8 @@ index 0000000..94f249e
+ write_log("loading rom from file %s, %d KiB\n", rom_filename, rom_size / 1024);
+
+ rom = malloc(rom_size);
+ if(!rom) {
+ if (rom == nullptr)
+ {
+ write_log("unable to allocate memory\n");
+ fclose(rom_file);
+ return 1;
@ -334,29 +340,28 @@ index 0000000..94f249e
+ time_t rawtime;
+ struct tm *timeinfo;
+ int sec = 500; // any invalid number
+ char new_title[256];
+ int percentage;
+ int throttle_underflow = 0;
+ int throttle_target = throttle_lo + SPEED_ALLOWANCE;
+
+ while(1) {
+ for (;;)
+ {
+ s_window->poll_events();
+
+ for(timing.current_cycles = 0; timing.current_cycles < timing.main_cycles; ) {
+ for (timing.current_cycles = 0; timing.current_cycles < timing.main_cycles;)
+ {
+ cpu_cycle();
+ display_cycle();
+ timer_cycle();
+ }
+
+
+ time(&rawtime);
+ timeinfo = localtime(&rawtime);
+
+ if(sec != timeinfo->tm_sec) {
+ if (sec != timeinfo->tm_sec)
+ {
+ sec = timeinfo->tm_sec;
+ percentage = (drawn_frames * 1000) / 597;
+ sprintf(new_title, "tinygb (%d fps - %d%%)", drawn_frames, percentage);
+ s_window->set_title(new_title);
+
+ // adjust cpu throttle according to acceptable fps (98%-102%)
+ if (throttle_enabled){
@ -412,7 +417,6 @@ index 0000000..94f249e
+ die(0, "");
+ return 0;
+}
\ No newline at end of file
--
2.49.0

View File

@ -1,5 +1,3 @@
// jmp_buf: esp, eip, ebx, ebp, edi, esi
// int setjmp(jmp_buf env)
.global setjmp
setjmp:
@ -11,11 +9,6 @@ setjmp:
movl (%esp), %eax
movl %eax, 4(%edx)
movl %ebx, 8(%edx)
movl %ebp, 12(%edx)
movl %edi, 16(%edx)
movl %esi, 20(%edx)
xorl %eax, %eax
ret
@ -34,10 +27,6 @@ longjmp:
movl 0(%edx), %esp
movl 4(%edx), %ecx
movl 8(%edx), %ebx
movl 12(%edx), %ebp
movl 16(%edx), %edi
movl 20(%edx), %esi
jmp *%ecx
.size longjmp, . - longjmp

View File

@ -1,5 +1,3 @@
// jmp_buf: rsp, rip, rbx, rbp, r12-r15
// int setjmp(jmp_buf env)
.global setjmp
setjmp:
@ -9,13 +7,6 @@ setjmp:
movq (%rsp), %rax
movq %rax, 8(%rdi)
movq %rbx, 16(%rdi)
movq %rbp, 24(%rdi)
movq %r12, 32(%rdi)
movq %r13, 40(%rdi)
movq %r14, 48(%rdi)
movq %r15, 56(%rdi)
xorq %rax, %rax
ret
@ -30,11 +21,5 @@ longjmp:
movq 0(%rdi), %rsp
movq 8(%rdi), %rcx
movq 16(%rdi), %rbx
movq 24(%rdi), %rbp
movq 32(%rdi), %r12
movq 40(%rdi), %r13
movq 48(%rdi), %r14
movq 56(%rdi), %r15
jmp *%rcx
.size longjmp, . - longjmp

View File

@ -7,14 +7,8 @@
__BEGIN_DECLS
#if defined(__x86_64__)
#define _JMP_BUF_REGS 8 // rsp, rip, rbx, rbp, r12-r15
#elif defined(__i686__)
#define _JMP_BUF_REGS 6 // esp, eip, ebx, ebp, edi, esi
#endif
typedef long jmp_buf[_JMP_BUF_REGS];
typedef long sigjmp_buf[_JMP_BUF_REGS + 1 + (sizeof(long long) / sizeof(long))];
typedef long jmp_buf[2];
typedef long sigjmp_buf[2 + 1 + (sizeof(long long) / sizeof(long))];
#define _longjmp longjmp
void longjmp(jmp_buf env, int val);

View File

@ -240,9 +240,9 @@ static void floating_point_to_exponent_string(char* buffer, T value, bool upper,
// Calculate which number to put as exponent
int exponent = 0;
if (value != static_cast<T>(0.0))
if (value != (T)0.0)
{
exponent = BAN::Math::floor<T>(BAN::Math::log10<T>(value));
exponent = (int)BAN::Math::log10<T>(value);
value /= BAN::Math::pow<T>(10.0, exponent);
}
@ -259,19 +259,6 @@ static void floating_point_to_exponent_string(char* buffer, T value, bool upper,
integer_to_string<int>(buffer + offset, exponent, 10, upper, exponent_options);
}
template<BAN::floating_point T>
static void floating_point_to_maybe_exponent_string(char* buffer, T value, bool upper, const format_options_t options)
{
int percision = 6;
if (options.percision != -1)
percision = options.percision;
const int exponent = (value != static_cast<T>(0.0)) ? BAN::Math::floor<T>(BAN::Math::log10(value)) : 0;
if (exponent < -4 || exponent >= percision)
return floating_point_to_exponent_string(buffer, value, upper, options);
return floating_point_to_string(buffer, value, upper, options);
}
extern "C" int printf_impl(const char* format, va_list arguments, int (*putc_fun)(int, void*), void* data)
{
int written = 0;
@ -533,16 +520,8 @@ extern "C" int printf_impl(const char* format, va_list arguments, int (*putc_fun
}
case 'g':
case 'G':
{
switch (options.length)
{
case length_t::L: floating_point_to_maybe_exponent_string<long double>(conversion, va_arg(arguments, long double), *format == 'G', options); break;
default: floating_point_to_maybe_exponent_string<double> (conversion, va_arg(arguments, double), *format == 'G', options); break;
}
string = conversion;
format++;
// TODO
break;
}
case 'a':
case 'A':
// TODO

View File

@ -1,19 +1,19 @@
#include <setjmp.h>
#include <signal.h>
static_assert(sizeof(sigjmp_buf) == (_JMP_BUF_REGS + 1) * sizeof(long) + sizeof(sigset_t));
static_assert(sizeof(sigjmp_buf) == sizeof(jmp_buf) + sizeof(long) + sizeof(sigset_t));
void siglongjmp(sigjmp_buf env, int val)
{
if (env[_JMP_BUF_REGS])
pthread_sigmask(SIG_SETMASK, reinterpret_cast<sigset_t*>(&env[_JMP_BUF_REGS + 1]), nullptr);
if (env[2])
pthread_sigmask(SIG_SETMASK, reinterpret_cast<sigset_t*>(&env[3]), nullptr);
return longjmp(env, val);
}
int sigsetjmp(sigjmp_buf env, int savemask)
{
env[_JMP_BUF_REGS] = savemask;
env[2] = savemask;
if (savemask)
pthread_sigmask(0, nullptr, reinterpret_cast<sigset_t*>(&env[_JMP_BUF_REGS + 1]));
pthread_sigmask(0, nullptr, reinterpret_cast<sigset_t*>(&env[3]));
return setjmp(env);
}

View File

@ -38,11 +38,6 @@ time_t time(time_t* tloc)
return tp.tv_sec;
}
double difftime(time_t time1, time_t time0)
{
return time1 - time0;
}
// sample implementation from https://pubs.opengroup.org/onlinepubs/9699919799/functions/asctime.html
char* asctime_r(const struct tm* __restrict tm, char* __restrict buf)
{

View File

@ -287,15 +287,6 @@ namespace LibGUI
return on_socket_error(__FUNCTION__);
}
void Window::set_title(BAN::StringView title)
{
WindowPacket::WindowSetTitle packet;
MUST(packet.title.append(title));
if (auto ret = packet.send_serialized(m_server_fd); ret.is_error())
return on_socket_error(__FUNCTION__);
}
void Window::set_position(int32_t x, int32_t y)
{
WindowPacket::WindowSetPosition packet;
@ -355,7 +346,7 @@ namespace LibGUI
m_framebuffer_smo = nullptr;
BAN::Vector<uint32_t> framebuffer;
TRY(framebuffer.resize(event.width * event.height, m_bg_color));
TRY(framebuffer.resize(event.width * event.height, 0xFFFFFFFF));
void* framebuffer_addr = smo_map(event.smo_key);
if (framebuffer_addr == nullptr)

View File

@ -165,7 +165,6 @@ namespace LibGUI
WindowSetMouseCapture,
WindowSetSize,
WindowSetFullscreen,
WindowSetTitle,
DestroyWindowEvent,
CloseWindowEvent,
@ -186,7 +185,6 @@ namespace LibGUI
bool focusable;
bool rounded_corners;
bool alpha_channel;
bool resizable;
};
DEFINE_PACKET(
@ -232,11 +230,6 @@ namespace LibGUI
bool, fullscreen
);
DEFINE_PACKET(
WindowSetTitle,
BAN::String, title
);
}
namespace EventPacket

View File

@ -22,7 +22,6 @@ namespace LibGUI
.focusable = true,
.rounded_corners = true,
.alpha_channel = false,
.resizable = false,
};
public:
@ -69,7 +68,6 @@ namespace LibGUI
void set_mouse_capture(bool captured);
void set_fullscreen(bool fullscreen);
void set_title(BAN::StringView title);
void set_position(int32_t x, int32_t y);
@ -83,9 +81,6 @@ namespace LibGUI
uint32_t width() const { return m_width; }
uint32_t height() const { return m_height; }
// used on resize to fill empty space
void set_bg_color(uint32_t bg_color) { m_bg_color = bg_color; }
void poll_events();
void set_socket_error_callback(BAN::Function<void()> callback) { m_socket_error_callback = callback; }
void set_close_window_event_callback(BAN::Function<void()> callback) { m_close_window_event_callback = callback; }
@ -122,8 +117,6 @@ namespace LibGUI
uint32_t m_width { 0 };
uint32_t m_height { 0 };
uint32_t m_bg_color { 0xFFFFFFFF };
BAN::Function<void()> m_socket_error_callback;
BAN::Function<void()> m_close_window_event_callback;
BAN::Function<void()> m_resize_window_event_callback;

View File

@ -116,12 +116,10 @@ void Terminal::run()
auto attributes = LibGUI::Window::default_attributes;
attributes.alpha_channel = true;
attributes.resizable = true;
m_window = MUST(LibGUI::Window::create(600, 400, "Terminal"_sv, attributes));
m_window->fill(m_bg_color);
m_window->invalidate();
m_window->set_bg_color(m_bg_color);
m_font = MUST(LibFont::Font::load("/usr/share/fonts/lat0-16.psfu"_sv));
@ -144,27 +142,6 @@ void Terminal::run()
show_cursor();
m_window->set_key_event_callback([&](LibGUI::EventPacket::KeyEvent::event_t event) { on_key_event(event); });
m_window->set_resize_window_event_callback([&] {
if (const auto rem = m_window->height() % m_font.height())
{
m_window->fill_rect(0, m_window->height() - rem, m_window->width(), rem, m_bg_color);
m_window->invalidate(0, m_window->height() - rem, m_window->width(), rem);
}
if (const auto rem = m_window->width() % m_font.width())
{
m_window->fill_rect(m_window->width() - rem, 0, rem, m_window->height(), m_bg_color);
m_window->invalidate(m_window->width() - rem, 0, rem, m_window->height());
}
if (m_cursor.x < cols() && m_cursor.y < rows())
return;
m_cursor.x = BAN::Math::min(m_cursor.x, cols() - 1);
m_cursor.y = BAN::Math::min(m_cursor.y, rows() - 1);
for (auto& pixel : m_cursor_buffer)
pixel = m_bg_color;
});
const int max_fd = BAN::Math::max(m_shell_info.pts_master, m_window->server_fd());
while (!s_shell_exited)

View File

@ -52,8 +52,6 @@ public:
LibGUI::Window::Attributes get_attributes() const { return m_attributes; };
void set_attributes(LibGUI::Window::Attributes attributes) { m_attributes = attributes; };
BAN::ErrorOr<void> set_title(BAN::StringView title) { m_title.clear(); TRY(m_title.append(title)); TRY(prepare_title_bar()); return {}; }
const uint32_t* framebuffer() const { return m_fb_addr; }
uint32_t title_bar_pixel(int32_t abs_x, int32_t abs_y, Position cursor) const

View File

@ -123,11 +123,19 @@ void WindowServer::on_window_invalidate(int fd, const LibGUI::WindowPacket::Wind
return;
}
const int32_t br_x = packet.x + packet.width - 1;
const int32_t br_y = packet.y + packet.height - 1;
if (!target_window->client_size().contains({ br_x, br_y }))
{
dwarnln("invalid Invalidate packet parameters");
return;
}
invalidate({
target_window->client_x() + static_cast<int32_t>(packet.x),
target_window->client_y() + static_cast<int32_t>(packet.y),
BAN::Math::min<int32_t>(packet.width, target_window->client_width()),
BAN::Math::min<int32_t>(packet.height, target_window->client_height())
static_cast<int32_t>(packet.width),
static_cast<int32_t>(packet.height),
});
}
@ -299,7 +307,7 @@ void WindowServer::on_window_set_fullscreen(int fd, const LibGUI::WindowPacket::
if (!target_window)
{
dwarnln("client tried to set window fullscreen while not owning a window");
dwarnln("client tried to set window size while not owning a window");
return;
}
@ -308,32 +316,6 @@ void WindowServer::on_window_set_fullscreen(int fd, const LibGUI::WindowPacket::
invalidate(m_framebuffer.area());
}
void WindowServer::on_window_set_title(int fd, const LibGUI::WindowPacket::WindowSetTitle& packet)
{
BAN::RefPtr<Window> target_window;
for (auto& window : m_client_windows)
{
if (window->client_fd() != fd)
continue;
target_window = window;
break;
}
if (!target_window)
{
dwarnln("client tried to set window title while not owning a window");
return;
}
if (auto ret = target_window->set_title(packet.title); ret.is_error())
{
dwarnln("failed to set window title: {}", ret.error());
return;
}
invalidate(target_window->title_bar_area());
}
void WindowServer::on_key_event(LibInput::KeyEvent event)
{
// Mod key is not passed to clients
@ -380,11 +362,6 @@ void WindowServer::on_key_event(LibInput::KeyEvent event)
if (!m_focused_window)
return;
m_is_fullscreen_window = !m_is_fullscreen_window;
if (m_is_fullscreen_window)
{
m_is_moving_window = false;
m_is_resizing_window = false;
}
invalidate(m_framebuffer.area());
return;
}
@ -428,40 +405,6 @@ void WindowServer::on_mouse_button(LibInput::MouseButtonEvent event)
}
}
if (m_is_moving_window && event.button == LibInput::MouseButton::Left && !event.pressed)
{
m_is_moving_window = false;
return;
}
if (m_is_resizing_window && event.button == LibInput::MouseButton::Right && !event.pressed)
{
const auto resize_area = this->resize_area(m_cursor);
m_is_resizing_window = false;
invalidate(resize_area.get_bounding_box(m_focused_window->full_area()));
const auto old_area = m_focused_window->full_area();
if (auto ret = m_focused_window->resize(resize_area.width, resize_area.height - m_focused_window->title_bar_height()); ret.is_error())
{
dwarnln("could not resize client window {}", ret.error());
return;
}
LibGUI::EventPacket::ResizeWindowEvent event;
event.width = m_focused_window->client_width();
event.height = m_focused_window->client_height();
event.smo_key = m_focused_window->smo_key();
if (auto ret = event.send_serialized(m_focused_window->client_fd()); ret.is_error())
{
dwarnln("could not respond to window resize request: {}", ret.error());
return;
}
invalidate(m_focused_window->full_area().get_bounding_box(old_area));
return;
}
// Ignore mouse button events which are not on top of a window
if (!target_window)
return;
@ -469,29 +412,13 @@ void WindowServer::on_mouse_button(LibInput::MouseButtonEvent event)
if (target_window->get_attributes().focusable)
set_focused_window(target_window);
if (!m_is_fullscreen_window && event.button == LibInput::MouseButton::Left)
{
// Handle window moving when mod key is held or mouse press on title bar
const bool can_start_move = m_is_mod_key_held || target_window->title_text_area().contains(m_cursor);
if (can_start_move && !m_is_moving_window && event.pressed)
{
if (event.pressed && event.button == LibInput::MouseButton::Left && !m_is_moving_window && can_start_move)
m_is_moving_window = target_window->get_attributes().movable;
return;
}
}
if (!m_is_fullscreen_window && event.button == LibInput::MouseButton::Right)
{
const bool can_start_resize = m_is_mod_key_held;
if (can_start_resize && !m_is_resizing_window && event.pressed)
{
m_is_resizing_window = target_window->get_attributes().resizable;
if (m_is_resizing_window)
m_resize_start = m_cursor;
return;
}
}
if (!event.pressed && event.button == LibInput::MouseButton::Left && target_window->close_button_area().contains(m_cursor))
else if (m_is_moving_window && !event.pressed)
m_is_moving_window = false;
else if (!event.pressed && event.button == LibInput::MouseButton::Left && target_window->close_button_area().contains(m_cursor))
{
// NOTE: we always have target window if code reaches here
LibGUI::EventPacket::CloseWindowEvent packet;
@ -515,6 +442,9 @@ void WindowServer::on_mouse_button(LibInput::MouseButtonEvent event)
return;
}
}
if (m_is_fullscreen_window)
m_is_moving_window = false;
}
void WindowServer::on_mouse_move(LibInput::MouseMoveEvent event)
@ -581,16 +511,6 @@ void WindowServer::on_mouse_move(LibInput::MouseMoveEvent event)
return;
}
if (m_is_resizing_window)
{
const auto max_cursor = Position {
.x = BAN::Math::max(old_cursor.x, new_cursor.x),
.y = BAN::Math::max(old_cursor.y, new_cursor.y),
};
invalidate(resize_area(max_cursor).get_bounding_box(m_focused_window->full_area()));
return;
}
if (m_focused_window)
{
LibGUI::EventPacket::MouseMoveEvent packet;
@ -970,24 +890,10 @@ void WindowServer::invalidate(Rectangle area)
}
}
if (m_is_resizing_window)
{
if (const auto overlap = resize_area(m_cursor).get_overlap(area); overlap.has_value())
{
for (int32_t y_off = 0; y_off < overlap->height; y_off++)
{
for (int32_t x_off = 0; x_off < overlap->width; x_off++)
{
auto& pixel = m_framebuffer.mmap[(overlap->y + y_off) * m_framebuffer.width + (overlap->x + x_off)];
pixel = alpha_blend(0x80000000, pixel);
}
}
}
}
if (!m_is_mouse_captured)
{
if (const auto overlap = cursor_area().get_overlap(area); overlap.has_value())
const auto cursor = cursor_area();
if (auto overlap = cursor.get_overlap(area); overlap.has_value())
{
for (int32_t y_off = 0; y_off < overlap->height; y_off++)
{
@ -1139,16 +1045,6 @@ Rectangle WindowServer::cursor_area() const
return { m_cursor.x, m_cursor.y, s_cursor_width, s_cursor_height };
}
Rectangle WindowServer::resize_area(Position cursor) const
{
ASSERT(m_is_resizing_window);
return {
.x = m_focused_window->full_x(),
.y = m_focused_window->full_y(),
.width = BAN::Math::max<int32_t>(20, m_focused_window->full_width() + cursor.x - m_resize_start.x),
.height = BAN::Math::max<int32_t>(20, m_focused_window->full_height() + cursor.y - m_resize_start.y),
};
}
void WindowServer::add_client_fd(int fd)
{

View File

@ -38,7 +38,6 @@ public:
void on_window_set_mouse_capture(int fd, const LibGUI::WindowPacket::WindowSetMouseCapture&);
void on_window_set_size(int fd, const LibGUI::WindowPacket::WindowSetSize&);
void on_window_set_fullscreen(int fd, const LibGUI::WindowPacket::WindowSetFullscreen&);
void on_window_set_title(int fd, const LibGUI::WindowPacket::WindowSetTitle&);
void on_key_event(LibInput::KeyEvent event);
void on_mouse_button(LibInput::MouseButtonEvent event);
@ -50,7 +49,6 @@ public:
void sync();
Rectangle cursor_area() const;
Rectangle resize_area(Position cursor) const;
void add_client_fd(int fd);
void remove_client_fd(int fd);
@ -88,9 +86,6 @@ private:
BAN::RefPtr<Window> m_focused_window;
Position m_cursor;
bool m_is_resizing_window { false };
Position m_resize_start;
bool m_is_mouse_captured { false };
bool m_deleted_window { false };

View File

@ -379,10 +379,6 @@ int main()
if (auto ret = LibGUI::WindowPacket::WindowSetFullscreen::deserialize(client_data.packet_buffer.span()); !ret.is_error())
window_server.on_window_set_fullscreen(fd, ret.release_value());
break;
case LibGUI::PacketType::WindowSetTitle:
if (auto ret = LibGUI::WindowPacket::WindowSetTitle::deserialize(client_data.packet_buffer.span()); !ret.is_error())
window_server.on_window_set_title(fd, ret.release_value());
break;
default:
dprintln("unhandled packet type: {}", *reinterpret_cast<uint32_t*>(client_data.packet_buffer.data()));
}

View File

@ -1,7 +1,6 @@
#include <BAN/Vector.h>
#include <fcntl.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@ -27,7 +26,7 @@ struct Point
bool g_running = true;
Point g_grid_size = { 21, 21 };
Direction g_direction = Direction::Up;
Point g_head = { g_grid_size.x / 2, g_grid_size.y / 2 };
Point g_head = { 10, 10 };
size_t g_tail_target = 3;
int g_score = 0;
BAN::Vector<Point> g_tail;
@ -54,78 +53,53 @@ Direction query_input()
}
}
const char* get_tail_char(Direction old_dir, Direction new_dir)
void set_grid_tile(Point point, const char* str)
{
const size_t old_idx = static_cast<size_t>(old_dir) - 2;
const size_t new_idx = static_cast<size_t>(new_dir) - 2;
// left, right, up, down
constexpr const char* tail_char_map[4][4] {
{ "", "", "", "" },
{ "", "", "", "" },
{ "", "", "", "" },
{ "", "", "", "" },
};
return tail_char_map[old_idx][new_idx];
printf("\e[%d;%dH%s", (point.y + 1) + 1, (point.x + 1) * 2 + 1, str);
}
void set_grid_tile(Point point, const char* str, int off_x = 0)
Point get_random_point()
{
printf("\e[%d;%dH%s", (point.y + 1) + 1, (point.x + 1) * 2 + 1 + off_x, str);
}
__attribute__((format(printf, 1, 2)))
void print_score_line(const char* format, ...)
{
printf("\e[%dH\e[m", g_grid_size.y + 3);
va_list args;
va_start(args, format);
vprintf(format, args);
va_end(args);
return { .x = rand() % g_grid_size.x, .y = rand() % g_grid_size.y };
}
void update_apple()
{
BAN::Vector<Point> free_tiles;
for (int y = 0; y < g_grid_size.y; y++)
for (int x = 0; x < g_grid_size.x; x++)
if (const Point point { x, y }; g_head != point && !g_tail.contains(point))
MUST(free_tiles.push_back(point));
if (free_tiles.empty())
for (;;)
{
print_score_line("You won!\n");
exit(0);
g_apple = get_random_point();
if (g_head == g_apple)
continue;
for (auto point : g_tail)
if (point == g_apple)
continue;
break;
}
g_apple = free_tiles[rand() % free_tiles.size()];
set_grid_tile(g_apple, "\e[31mO");
}
void setup_grid()
{
// Move cursor to beginning and clear screen
printf("\e[H\e[2J");
printf("\e[H\e[J");
// Render top line
printf("╔═");
for (int x = 0; x < g_grid_size.x; x++)
printf("══");
printf("\n");
putchar('#');
for (int x = 1; x < g_grid_size.x + 2; x++)
printf(" #");
putchar('\n');
// Render side lines
for (int y = 0; y < g_grid_size.y; y++)
printf("\e[%dC║\n", g_grid_size.x * 2 + 1);
printf("#\e[%dC#\n", g_grid_size.x * 2 + 1);
// Render Bottom line
printf("╚═");
for (int x = 0; x < g_grid_size.x; x++)
printf("══");
printf("");
putchar('#');
for (int x = 1; x < g_grid_size.x + 2; x++)
printf(" #");
putchar('\n');
// Render snake head
printf("\e[32m");
set_grid_tile(g_head, "O");
// Generate and render apple
@ -133,7 +107,7 @@ void setup_grid()
update_apple();
// Render score
print_score_line("Score: %d", g_score);
printf("\e[%dH\e[mScore: %d", g_grid_size.y + 3, g_score);
fflush(stdout);
}
@ -167,7 +141,6 @@ void update()
}
}
const auto old_direction = g_direction;
if (new_direction != g_direction && new_direction != Direction::None)
g_direction = new_direction;
@ -208,18 +181,7 @@ void update()
MUST(g_tail.insert(0, old_head));
if (g_tail.size() > g_tail_target)
{
const auto comp = g_tail.size() >= 2 ? g_tail[g_tail.size() - 2] : g_head;
const auto back = g_tail.back();
if (comp.y == back.y)
{
if (comp.x == back.x + 1)
set_grid_tile(back, " ", +1);
if (comp.x == back.x - 1)
set_grid_tile(back, " ", -1);
}
set_grid_tile(back, " ");
set_grid_tile(g_tail.back(), " ");
g_tail.pop_back();
}
@ -228,16 +190,12 @@ void update()
g_tail_target++;
g_score++;
update_apple();
print_score_line("Score: %d", g_score);
printf("\e[%dH\e[mScore: %d", g_grid_size.y + 3, g_score);
}
printf("\e[32m");
if (g_direction == Direction::Left)
set_grid_tile(g_head, "", +1);
if (g_direction == Direction::Right)
set_grid_tile(g_head, "", -1);
set_grid_tile(old_head, get_tail_char(old_direction, g_direction));
set_grid_tile(g_head, "O");
set_grid_tile(old_head, "\e[32mo");
set_grid_tile(g_head, "\e[32mO");
fflush(stdout);
}