aboutsummaryrefslogtreecommitdiff
path: root/modules/TUI
diff options
context:
space:
mode:
authordam <dam@gudinoff>2024-05-18 01:30:01 +0100
committerdam <dam@gudinoff>2024-05-18 01:30:01 +0100
commitaf1317ed93f7d5fc16baceaadb0da17aa17591be (patch)
tree17b0878b430688da9f3481ac621f581d18cbf076 /modules/TUI
parentbdea73c8349c2c918befad4120f49f9826d270dc (diff)
downloadtask-time-tracker-af1317ed93f7d5fc16baceaadb0da17aa17591be.tar.zst
task-time-tracker-af1317ed93f7d5fc16baceaadb0da17aa17591be.zip
Updated modules.
Diffstat (limited to 'modules/TUI')
-rw-r--r--modules/TUI/module.jai17
-rw-r--r--modules/TUI/snake.jai165
2 files changed, 180 insertions, 2 deletions
diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai
index fb742da..d0373f9 100644
--- a/modules/TUI/module.jai
+++ b/modules/TUI/module.jai
@@ -1,3 +1,16 @@
+/*
+ A simple terminal user interface module that provides basic functionalities similar to the [ncurses library](https://en.wikipedia.org/wiki/Ncurses).
+ Usefull for creating simple terminal-based apps that require user input.
+ View `snake.jai` for an example.
+ It has been tested on the following terminal emulators:
+ - [GNOME Terminal](https://en.wikipedia.org/wiki/GNOME_Terminal)
+ - [kitty](https://en.wikipedia.org/wiki/Kitty_(terminal_emulator))
+ - [Konsole](https://en.wikipedia.org/wiki/Konsole)
+ - [Linux console](https://en.wikipedia.org/wiki/Linux_console)
+ - [xterm](https://en.wikipedia.org/wiki/Xterm)
+ - [Windows Terminal](https://en.wikipedia.org/wiki/Windows_Terminal)
+*/
+
#module_parameters(COLOR_MODE_BITS := 24);
@@ -32,11 +45,11 @@
#import "UTF8";
#load "key_map.jai";
-#add_context tui_style : Style; // This contains the last style applied by the module.
+#add_context tui_style : Style; // This contains the last style applied by the module.
#add_context tui_output_builder : *String_Builder; // If set, this will serve as an output buffer for this module procedures.
KEY_SIZE :: #run type_info(Key).runtime_size;
-#assert(input_buffer.count >= KEY_SIZE); // The input buffer size must be capable to hold an entire Key.
+#assert(input_buffer.count >= KEY_SIZE); // The input buffer size must be capable to hold an entire Key.
active := false;
input_override : Key;
diff --git a/modules/TUI/snake.jai b/modules/TUI/snake.jai
new file mode 100644
index 0000000..b35c0fb
--- /dev/null
+++ b/modules/TUI/snake.jai
@@ -0,0 +1,165 @@
+#import "Basic";
+#import "Random";
+TUI :: #import "TUI"(COLOR_MODE_BITS = 8);
+
+Vec2D :: struct {
+ x: int;
+ y: int;
+}
+
+operator == :: (a: Vec2D, b: Vec2D) -> bool {
+ return a.x == b.x && a.y == b.y;
+}
+
+screen_size_x: int = ---;
+screen_size_y: int = ---;
+player_name: string = ---;
+
+main :: () {
+
+ game_loop :: () {
+
+ LOOP_PERIOD_MS :: 30;
+
+ score := 0;
+ dir := Vec2D.{1, 0};
+ food := Vec2D.{5, 5};
+
+ snake_parts: [..] Vec2D;
+ for 0..13 array_add(*snake_parts, Vec2D.{3, 3});
+ snake_parts[0].x += 1;
+
+ TUI.flush_input();
+ TUI.set_next_key(TUI.Keys.Resize);
+ timer := current_time_monotonic();
+ while main_loop := true {
+
+ timestamp := current_time_monotonic();
+ key := TUI.get_key(LOOP_PERIOD_MS);
+
+ if key == {
+ case TUI.Keys.Resize;
+ TUI.clear_terminal();
+ screen_size_x, screen_size_y = TUI.get_terminal_size();
+ TUI.draw_box(1, 1, screen_size_x, screen_size_y);
+ TUI.set_cursor_position(3, screen_size_y);
+ write_strings(" ", player_name, " ");
+
+ case #char "q"; #through;
+ case #char "Q"; #through;
+ case TUI.Keys.Escape;
+ break main_loop;
+
+ case TUI.Keys.Up;
+ if dir != Vec2D.{0, 1} then dir = Vec2D.{0, -1};
+
+ case TUI.Keys.Down;
+ if dir != Vec2D.{0, -1} then dir = Vec2D.{0, 1};
+
+ case TUI.Keys.Left;
+ if dir != Vec2D.{1, 0} then dir = Vec2D.{-1, 0};
+
+ case TUI.Keys.Right;
+ if dir != Vec2D.{-1, 0} then dir = Vec2D.{1, 0};
+ }
+
+ if screen_size_x < 15 || screen_size_y < 15 {
+ TUI.clear_terminal();
+ TUI.set_cursor_position(1,1);
+ write_string("~ paused : increase window size ~");
+ continue;
+ }
+
+ last_pos := snake_parts[snake_parts.count-1];
+
+ // Update position.
+ for < snake_parts.count-1..1 {
+ if snake_parts[it] != snake_parts[it-1] {
+ snake_parts[it] = snake_parts[it-1];
+ }
+ }
+ snake_parts[0].x += dir.x;
+ snake_parts[0].y += dir.y;
+
+ // Teleport on borders.
+ if snake_parts[0].x < 2 then snake_parts[0].x = screen_size_x - 1;
+ if snake_parts[0].x >= screen_size_x then snake_parts[0].x = 2;
+ if snake_parts[0].y < 2 then snake_parts[0].y = screen_size_y - 1;
+ if snake_parts[0].y >= screen_size_y then snake_parts[0].y = 2;
+ food.x = clamp(food.x, 2, screen_size_x-1);
+ food.y = clamp(food.y, 2, screen_size_y-1);
+
+ // Check for game-over.
+ for 1..snake_parts.count-1 {
+ if snake_parts[it] == snake_parts[0] {
+ break main_loop;
+ }
+ }
+
+ // Check for food.
+ if snake_parts[0] == food {
+ score += 1;
+ array_add(*snake_parts, snake_parts[snake_parts.count-1]);
+ food = Vec2D.{
+ cast(int)(random_get_zero_to_one_open() * (screen_size_x-3) + 2),
+ cast(int)(random_get_zero_to_one_open() * (screen_size_y-3) + 2)
+ };
+ }
+
+ // Wait to match game loop time.
+ delta := to_milliseconds(current_time_monotonic() - timestamp);
+ if delta < LOOP_PERIOD_MS {
+ sleep_milliseconds(xx (LOOP_PERIOD_MS - delta));
+ }
+
+ // Draw snake.
+ write_string(TUI.Commands.DrawingMode);
+ TUI.set_cursor_position(last_pos.x, last_pos.y);
+ write_string(TUI.Drawings.Blank);
+ for snake_parts {
+ TUI.set_cursor_position(it.x, it.y);
+ write_string(TUI.Drawings.Checkerboard);
+ }
+ // Draw food.
+ {
+ TUI.using_style(TUI.Style.{ foreground = TUI.Palette.RED, bold = true, });
+ TUI.set_cursor_position(food.x, food.y);
+ write_string(TUI.Drawings.Diamond);
+ }
+ write_string(TUI.Commands.TextMode);
+
+ // Set score
+ TUI.set_cursor_position(3, 1);
+ print(" % ", score);
+ }
+ }
+
+ GAME_OVER_TEXT :: "~ game over ~";
+ INSTRUCTIONS_TEXT :: "(esc to exit)";
+
+ seed: u64 = xx to_milliseconds(current_time_monotonic()) | 0x01; // Seed must be odd.
+ random_seed(seed);
+
+ assert(TUI.setup_terminal(), "Failed to setup TUI.");
+ TUI.set_cursor_position(1, 1);
+
+ write_string("Please enter player name: ");
+ player_name = TUI.read_input_line(64);
+
+ while true {
+ game_loop();
+
+ // Game over screen.
+ box_size := Vec2D.{19, 4};
+ TUI.draw_box((screen_size_x-box_size.x)/2, (screen_size_y-box_size.y)/2, box_size.x, box_size.y);
+ TUI.set_cursor_position((screen_size_x-GAME_OVER_TEXT.count)/2, (screen_size_y-box_size.y)/2 + 1);
+ write_string(GAME_OVER_TEXT);
+ TUI.set_cursor_position((screen_size_x-GAME_OVER_TEXT.count)/2, (screen_size_y-box_size.y)/2 + 2);
+ write_string(INSTRUCTIONS_TEXT);
+ sleep_milliseconds(100);
+ TUI.flush_input();
+ if TUI.get_key() == TUI.Keys.Escape then break;
+ }
+
+ assert(TUI.reset_terminal(), "Failed to reset TUI.");
+}