From 98af3a98c8e2cc05e1739f03becb325956b54994 Mon Sep 17 00:00:00 2001 From: thoxy Date: Tue, 27 May 2025 07:07:37 +0200 Subject: [PATCH 1/8] Add GameOfLife Animation --- src/animations/GameOfLife.zig | 272 ++++++++++++++++++++++++++++++++++ src/enums.zig | 1 + src/main.zig | 5 + 3 files changed, 278 insertions(+) create mode 100644 src/animations/GameOfLife.zig diff --git a/src/animations/GameOfLife.zig b/src/animations/GameOfLife.zig new file mode 100644 index 0000000..1130262 --- /dev/null +++ b/src/animations/GameOfLife.zig @@ -0,0 +1,272 @@ +const std = @import("std"); +const Animation = @import("../tui/Animation.zig"); +const Cell = @import("../tui/Cell.zig"); +const TerminalBuffer = @import("../tui/TerminalBuffer.zig"); + +const Allocator = std.mem.Allocator; +const Random = std.Random; + +const GameOfLife = @This(); + +pub const FRAME_DELAY: usize = 6; // Slightly faster for smoother animation +pub const INITIAL_DENSITY: f32 = 0.4; // Increased for more activity +pub const COLOR_CYCLE_DELAY: usize = 192; // Change color every N frames + +// Visual styles - using block characters like other animations +const ALIVE_CHAR: u21 = 0x2588; // Full block █ +const DEAD_CHAR: u21 = ' '; + +// ANSI basic colors using TerminalBuffer.Color like other animations +const ANSI_COLORS = [_]u32{ + @intCast(TerminalBuffer.Color.RED), + @intCast(TerminalBuffer.Color.GREEN), + @intCast(TerminalBuffer.Color.YELLOW), + @intCast(TerminalBuffer.Color.BLUE), + @intCast(TerminalBuffer.Color.MAGENTA), + @intCast(TerminalBuffer.Color.CYAN), + @intCast(TerminalBuffer.Color.RED | TerminalBuffer.Styling.BOLD), + @intCast(TerminalBuffer.Color.GREEN | TerminalBuffer.Styling.BOLD), + @intCast(TerminalBuffer.Color.YELLOW | TerminalBuffer.Styling.BOLD), + @intCast(TerminalBuffer.Color.BLUE | TerminalBuffer.Styling.BOLD), + @intCast(TerminalBuffer.Color.MAGENTA | TerminalBuffer.Styling.BOLD), + @intCast(TerminalBuffer.Color.CYAN | TerminalBuffer.Styling.BOLD), +}; +const NUM_COLORS = ANSI_COLORS.len; +const NEIGHBOR_DIRS = [_][2]i8{ + .{ -1, -1 }, .{ -1, 0 }, .{ -1, 1 }, + .{ 0, -1 }, .{ 0, 1 }, .{ 1, -1 }, + .{ 1, 0 }, .{ 1, 1 }, +}; + +allocator: Allocator, +terminal_buffer: *TerminalBuffer, +current_grid: []bool, +next_grid: []bool, +frame_counter: usize, +generation: u64, +color_index: usize, +color_counter: usize, +dead_cell: Cell, +width: usize, +height: usize, + +pub fn init(allocator: Allocator, terminal_buffer: *TerminalBuffer) !GameOfLife { + const width = terminal_buffer.width; + const height = terminal_buffer.height; + const grid_size = width * height; + + const current_grid = try allocator.alloc(bool, grid_size); + const next_grid = try allocator.alloc(bool, grid_size); + + var game = GameOfLife{ + .allocator = allocator, + .terminal_buffer = terminal_buffer, + .current_grid = current_grid, + .next_grid = next_grid, + .frame_counter = 0, + .generation = 0, + .color_index = 0, + .color_counter = 0, + .dead_cell = .{ .ch = DEAD_CHAR, .fg = @intCast(TerminalBuffer.Color.DEFAULT), .bg = terminal_buffer.bg }, + .width = width, + .height = height, + }; + + // Initialize grid + game.initializeGrid(); + + return game; +} + +pub fn animation(self: *GameOfLife) Animation { + return Animation.init(self, deinit, realloc, draw); +} + +fn deinit(self: *GameOfLife) void { + self.allocator.free(self.current_grid); + self.allocator.free(self.next_grid); +} + +fn realloc(self: *GameOfLife) anyerror!void { + const new_width = self.terminal_buffer.width; + const new_height = self.terminal_buffer.height; + const new_size = new_width * new_height; + + // Only reallocate if size changed significantly + if (new_size != self.width * self.height) { + const current_grid = try self.allocator.realloc(self.current_grid, new_size); + const next_grid = try self.allocator.realloc(self.next_grid, new_size); + + self.current_grid = current_grid; + self.next_grid = next_grid; + self.width = new_width; + self.height = new_height; + + self.initializeGrid(); + self.generation = 0; + self.color_index = 0; + self.color_counter = 0; + } +} + +fn draw(self: *GameOfLife) void { + // Update ANSI color cycling at controlled rate + self.color_counter += 1; + if (self.color_counter >= COLOR_CYCLE_DELAY) { + self.color_counter = 0; + self.color_index = (self.color_index + 1) % NUM_COLORS; + } + + // Update game state at controlled frame rate + self.frame_counter += 1; + if (self.frame_counter >= FRAME_DELAY) { + self.frame_counter = 0; + self.updateGeneration(); + self.generation += 1; + + // Add entropy less frequently to reduce computational overhead + if (self.generation % 150 == 0) { + self.addEntropy(); + } + } + + // Render with ANSI color cycling - use current color from the array (same method as Matrix/Doom) + const current_color = ANSI_COLORS[self.color_index]; + const alive_cell = Cell{ .ch = ALIVE_CHAR, .fg = current_color, .bg = self.terminal_buffer.bg }; + + for (0..self.height) |y| { + const row_offset = y * self.width; + for (0..self.width) |x| { + const cell = if (self.current_grid[row_offset + x]) alive_cell else self.dead_cell; + cell.put(x, y); + } + } +} + +fn updateGeneration(self: *GameOfLife) void { + // Conway's Game of Life rules with optimized neighbor counting + for (0..self.height) |y| { + const row_offset = y * self.width; + for (0..self.width) |x| { + const index = row_offset + x; + const neighbors = self.countNeighborsOptimized(x, y); + const is_alive = self.current_grid[index]; + + // Optimized rule application + self.next_grid[index] = switch (neighbors) { + 2 => is_alive, + 3 => true, + else => false, + }; + } + } + + // Efficient grid swap + std.mem.swap([]bool, &self.current_grid, &self.next_grid); +} + +fn countNeighborsOptimized(self: *GameOfLife, x: usize, y: usize) u8 { + var count: u8 = 0; + + // Use cached dimensions and more efficient bounds checking + for (NEIGHBOR_DIRS) |dir| { + const nx = @as(i32, @intCast(x)) + dir[0]; + const ny = @as(i32, @intCast(y)) + dir[1]; + + // Toroidal wrapping with modular arithmetic + const wx: usize = @intCast(@mod(nx + @as(i32, @intCast(self.width)), @as(i32, @intCast(self.width)))); + const wy: usize = @intCast(@mod(ny + @as(i32, @intCast(self.height)), @as(i32, @intCast(self.height)))); + + if (self.current_grid[wy * self.width + wx]) { + count += 1; + } + } + + return count; +} + +fn initializeGrid(self: *GameOfLife) void { + const total_cells = self.width * self.height; + + // Clear grid + @memset(self.current_grid, false); + @memset(self.next_grid, false); + + // Random initialization with better distribution + for (0..total_cells) |i| { + self.current_grid[i] = self.terminal_buffer.random.float(f32) < INITIAL_DENSITY; + } + + // Add interesting patterns with better positioning + self.addPatterns(); +} + +fn addPatterns(self: *GameOfLife) void { + if (self.width < 8 or self.height < 8) return; + + // Add multiple instances of each pattern for liveliness + for (0..3) |_| { + self.addGlider(); + if (self.width >= 10 and self.height >= 10) { + self.addBlock(); + self.addBlinker(); + } + } +} + +fn addGlider(self: *GameOfLife) void { + const x = self.terminal_buffer.random.intRangeAtMost(usize, 2, self.width - 4); + const y = self.terminal_buffer.random.intRangeAtMost(usize, 2, self.height - 4); + + // Classic glider pattern + const positions = [_][2]usize{ .{ 1, 0 }, .{ 2, 1 }, .{ 0, 2 }, .{ 1, 2 }, .{ 2, 2 } }; + + for (positions) |pos| { + const idx = (y + pos[1]) * self.width + (x + pos[0]); + self.current_grid[idx] = true; + } +} + +fn addBlock(self: *GameOfLife) void { + const x = self.terminal_buffer.random.intRangeAtMost(usize, 1, self.width - 3); + const y = self.terminal_buffer.random.intRangeAtMost(usize, 1, self.height - 3); + + // 2x2 block + const positions = [_][2]usize{ .{ 0, 0 }, .{ 1, 0 }, .{ 0, 1 }, .{ 1, 1 } }; + + for (positions) |pos| { + const idx = (y + pos[1]) * self.width + (x + pos[0]); + self.current_grid[idx] = true; + } +} + +fn addBlinker(self: *GameOfLife) void { + const x = self.terminal_buffer.random.intRangeAtMost(usize, 1, self.width - 4); + const y = self.terminal_buffer.random.intRangeAtMost(usize, 1, self.height - 2); + + // 3-cell horizontal line + for (0..3) |i| { + const idx = y * self.width + (x + i); + self.current_grid[idx] = true; + } +} + +fn addEntropy(self: *GameOfLife) void { + // Add fewer random cells but in clusters for more interesting patterns + const clusters = 2; + for (0..clusters) |_| { + const cx = self.terminal_buffer.random.intRangeAtMost(usize, 1, self.width - 2); + const cy = self.terminal_buffer.random.intRangeAtMost(usize, 1, self.height - 2); + + // Small cluster around center point + for (0..3) |dy| { + for (0..3) |dx| { + if (self.terminal_buffer.random.float(f32) < 0.4) { + const x = (cx + dx) % self.width; + const y = (cy + dy) % self.height; + self.current_grid[y * self.width + x] = true; + } + } + } + } +} diff --git a/src/enums.zig b/src/enums.zig index 6d1f1f3..a70f17c 100644 --- a/src/enums.zig +++ b/src/enums.zig @@ -3,6 +3,7 @@ pub const Animation = enum { doom, matrix, colormix, + gameoflife, }; pub const DisplayServer = enum { diff --git a/src/main.zig b/src/main.zig index bd89b15..259a5e3 100644 --- a/src/main.zig +++ b/src/main.zig @@ -12,6 +12,7 @@ const ColorMix = @import("animations/ColorMix.zig"); const Doom = @import("animations/Doom.zig"); const Dummy = @import("animations/Dummy.zig"); const Matrix = @import("animations/Matrix.zig"); +const GameOfLife = @import("animations/GameOfLife.zig"); const Animation = @import("tui/Animation.zig"); const TerminalBuffer = @import("tui/TerminalBuffer.zig"); const Session = @import("tui/components/Session.zig"); @@ -364,6 +365,10 @@ pub fn main() !void { var color_mix = ColorMix.init(&buffer, config.colormix_col1, config.colormix_col2, config.colormix_col3); animation = color_mix.animation(); }, + .gameoflife => { + var game_of_life = try GameOfLife.init(allocator, &buffer); + animation = game_of_life.animation(); + }, } defer animation.deinit(); From fa46155f725ce907f3bc7343bb53ccbcf583906a Mon Sep 17 00:00:00 2001 From: thoxy Date: Fri, 30 May 2025 20:04:52 +0200 Subject: [PATCH 2/8] make gameoflife more configurable and fix pull reviews --- res/config.ini | 26 ++++++ src/animations/GameOfLife.zig | 155 ++++++++++------------------------ src/config/Config.zig | 5 ++ src/main.zig | 2 +- 4 files changed, 76 insertions(+), 112 deletions(-) diff --git a/res/config.ini b/res/config.ini index 53050e4..c358c19 100644 --- a/res/config.ini +++ b/res/config.ini @@ -125,6 +125,32 @@ error_fg = 0x01FF0000 # Foreground color id fg = 0x00FFFFFF +# Game of Life entropy interval (0 = disabled, >0 = add entropy every N generations) +# 0 -> Pure Conway's Game of Life (will eventually stabilize) +# 10 -> Add entropy every 10 generations (recommended for continuous activity) +# 50+ -> Less frequent entropy for more natural evolution +gameoflife_entropy_interval = 10 + +# Game of Life animation foreground color id +gameoflife_fg = 0x0000FF00 + +# Game of Life frame delay (lower = faster animation, higher = slower) +# 1-3 -> Very fast animation +# 6 -> Default smooth animation speed +# 10+ -> Slower, more contemplative speed +gameoflife_frame_delay = 6 + +# Game of Life initial cell density (0.0 to 1.0) +# 0.1 -> Sparse, minimal activity +# 0.4 -> Balanced activity (recommended) +# 0.7+ -> Dense, chaotic patterns +gameoflife_initial_density = 0.4 + +# Game of Life randomize colors (true/false) +# false -> Use the fixed gameoflife_fg color +# true -> Generate one random color at startup and use it for the entire session +gameoflife_randomize_colors = false + # Remove main box borders hide_borders = false diff --git a/src/animations/GameOfLife.zig b/src/animations/GameOfLife.zig index 1130262..f564e52 100644 --- a/src/animations/GameOfLife.zig +++ b/src/animations/GameOfLife.zig @@ -8,30 +8,9 @@ const Random = std.Random; const GameOfLife = @This(); -pub const FRAME_DELAY: usize = 6; // Slightly faster for smoother animation -pub const INITIAL_DENSITY: f32 = 0.4; // Increased for more activity -pub const COLOR_CYCLE_DELAY: usize = 192; // Change color every N frames - // Visual styles - using block characters like other animations const ALIVE_CHAR: u21 = 0x2588; // Full block █ const DEAD_CHAR: u21 = ' '; - -// ANSI basic colors using TerminalBuffer.Color like other animations -const ANSI_COLORS = [_]u32{ - @intCast(TerminalBuffer.Color.RED), - @intCast(TerminalBuffer.Color.GREEN), - @intCast(TerminalBuffer.Color.YELLOW), - @intCast(TerminalBuffer.Color.BLUE), - @intCast(TerminalBuffer.Color.MAGENTA), - @intCast(TerminalBuffer.Color.CYAN), - @intCast(TerminalBuffer.Color.RED | TerminalBuffer.Styling.BOLD), - @intCast(TerminalBuffer.Color.GREEN | TerminalBuffer.Styling.BOLD), - @intCast(TerminalBuffer.Color.YELLOW | TerminalBuffer.Styling.BOLD), - @intCast(TerminalBuffer.Color.BLUE | TerminalBuffer.Styling.BOLD), - @intCast(TerminalBuffer.Color.MAGENTA | TerminalBuffer.Styling.BOLD), - @intCast(TerminalBuffer.Color.CYAN | TerminalBuffer.Styling.BOLD), -}; -const NUM_COLORS = ANSI_COLORS.len; const NEIGHBOR_DIRS = [_][2]i8{ .{ -1, -1 }, .{ -1, 0 }, .{ -1, 1 }, .{ 0, -1 }, .{ 0, 1 }, .{ 1, -1 }, @@ -44,13 +23,16 @@ current_grid: []bool, next_grid: []bool, frame_counter: usize, generation: u64, -color_index: usize, -color_counter: usize, +fg_color: u32, +entropy_interval: usize, +frame_delay: usize, +initial_density: f32, +randomize_colors: bool, dead_cell: Cell, width: usize, height: usize, -pub fn init(allocator: Allocator, terminal_buffer: *TerminalBuffer) !GameOfLife { +pub fn init(allocator: Allocator, terminal_buffer: *TerminalBuffer, fg_color: u32, entropy_interval: usize, frame_delay: usize, initial_density: f32, randomize_colors: bool) !GameOfLife { const width = terminal_buffer.width; const height = terminal_buffer.height; const grid_size = width * height; @@ -65,8 +47,11 @@ pub fn init(allocator: Allocator, terminal_buffer: *TerminalBuffer) !GameOfLife .next_grid = next_grid, .frame_counter = 0, .generation = 0, - .color_index = 0, - .color_counter = 0, + .fg_color = if (randomize_colors) generateRandomColor(terminal_buffer.random) else fg_color, + .entropy_interval = entropy_interval, + .frame_delay = frame_delay, + .initial_density = initial_density, + .randomize_colors = randomize_colors, .dead_cell = .{ .ch = DEAD_CHAR, .fg = @intCast(TerminalBuffer.Color.DEFAULT), .bg = terminal_buffer.bg }, .width = width, .height = height, @@ -92,47 +77,35 @@ fn realloc(self: *GameOfLife) anyerror!void { const new_height = self.terminal_buffer.height; const new_size = new_width * new_height; - // Only reallocate if size changed significantly - if (new_size != self.width * self.height) { - const current_grid = try self.allocator.realloc(self.current_grid, new_size); - const next_grid = try self.allocator.realloc(self.next_grid, new_size); + // Always reallocate to be safe + const current_grid = try self.allocator.realloc(self.current_grid, new_size); + const next_grid = try self.allocator.realloc(self.next_grid, new_size); - self.current_grid = current_grid; - self.next_grid = next_grid; - self.width = new_width; - self.height = new_height; + self.current_grid = current_grid; + self.next_grid = next_grid; + self.width = new_width; + self.height = new_height; - self.initializeGrid(); - self.generation = 0; - self.color_index = 0; - self.color_counter = 0; - } + self.initializeGrid(); + self.generation = 0; } fn draw(self: *GameOfLife) void { - // Update ANSI color cycling at controlled rate - self.color_counter += 1; - if (self.color_counter >= COLOR_CYCLE_DELAY) { - self.color_counter = 0; - self.color_index = (self.color_index + 1) % NUM_COLORS; - } - // Update game state at controlled frame rate self.frame_counter += 1; - if (self.frame_counter >= FRAME_DELAY) { + if (self.frame_counter >= self.frame_delay) { self.frame_counter = 0; self.updateGeneration(); self.generation += 1; - // Add entropy less frequently to reduce computational overhead - if (self.generation % 150 == 0) { + // Add entropy based on configuration (0 = disabled, >0 = interval) + if (self.entropy_interval > 0 and self.generation % self.entropy_interval == 0) { self.addEntropy(); } } - // Render with ANSI color cycling - use current color from the array (same method as Matrix/Doom) - const current_color = ANSI_COLORS[self.color_index]; - const alive_cell = Cell{ .ch = ALIVE_CHAR, .fg = current_color, .bg = self.terminal_buffer.bg }; + // Render with the set color (either configured or randomly generated at startup) + const alive_cell = Cell{ .ch = ALIVE_CHAR, .fg = self.fg_color, .bg = self.terminal_buffer.bg }; for (0..self.height) |y| { const row_offset = y * self.width; @@ -143,6 +116,15 @@ fn draw(self: *GameOfLife) void { } } +fn generateRandomColor(random: Random) u32 { + // Generate a random RGB color with good visibility + // Avoid very dark colors by using range 64-255 for each component + const r = random.intRangeAtMost(u8, 64, 255); + const g = random.intRangeAtMost(u8, 64, 255); + const b = random.intRangeAtMost(u8, 64, 255); + return (@as(u32, r) << 16) | (@as(u32, g) << 8) | @as(u32, b); +} + fn updateGeneration(self: *GameOfLife) void { // Conway's Game of Life rules with optimized neighbor counting for (0..self.height) |y| { @@ -170,12 +152,16 @@ fn countNeighborsOptimized(self: *GameOfLife, x: usize, y: usize) u8 { // Use cached dimensions and more efficient bounds checking for (NEIGHBOR_DIRS) |dir| { - const nx = @as(i32, @intCast(x)) + dir[0]; - const ny = @as(i32, @intCast(y)) + dir[1]; + const nx: i32 = @intCast(x); + const ny: i32 = @intCast(y); + const neighbor_x: i32 = nx + dir[0]; + const neighbor_y: i32 = ny + dir[1]; + const width_i32: i32 = @intCast(self.width); + const height_i32: i32 = @intCast(self.height); // Toroidal wrapping with modular arithmetic - const wx: usize = @intCast(@mod(nx + @as(i32, @intCast(self.width)), @as(i32, @intCast(self.width)))); - const wy: usize = @intCast(@mod(ny + @as(i32, @intCast(self.height)), @as(i32, @intCast(self.height)))); + const wx: usize = @intCast(@mod(neighbor_x + width_i32, width_i32)); + const wy: usize = @intCast(@mod(neighbor_y + height_i32, height_i32)); if (self.current_grid[wy * self.width + wx]) { count += 1; @@ -192,62 +178,9 @@ fn initializeGrid(self: *GameOfLife) void { @memset(self.current_grid, false); @memset(self.next_grid, false); - // Random initialization with better distribution + // Random initialization with configurable density for (0..total_cells) |i| { - self.current_grid[i] = self.terminal_buffer.random.float(f32) < INITIAL_DENSITY; - } - - // Add interesting patterns with better positioning - self.addPatterns(); -} - -fn addPatterns(self: *GameOfLife) void { - if (self.width < 8 or self.height < 8) return; - - // Add multiple instances of each pattern for liveliness - for (0..3) |_| { - self.addGlider(); - if (self.width >= 10 and self.height >= 10) { - self.addBlock(); - self.addBlinker(); - } - } -} - -fn addGlider(self: *GameOfLife) void { - const x = self.terminal_buffer.random.intRangeAtMost(usize, 2, self.width - 4); - const y = self.terminal_buffer.random.intRangeAtMost(usize, 2, self.height - 4); - - // Classic glider pattern - const positions = [_][2]usize{ .{ 1, 0 }, .{ 2, 1 }, .{ 0, 2 }, .{ 1, 2 }, .{ 2, 2 } }; - - for (positions) |pos| { - const idx = (y + pos[1]) * self.width + (x + pos[0]); - self.current_grid[idx] = true; - } -} - -fn addBlock(self: *GameOfLife) void { - const x = self.terminal_buffer.random.intRangeAtMost(usize, 1, self.width - 3); - const y = self.terminal_buffer.random.intRangeAtMost(usize, 1, self.height - 3); - - // 2x2 block - const positions = [_][2]usize{ .{ 0, 0 }, .{ 1, 0 }, .{ 0, 1 }, .{ 1, 1 } }; - - for (positions) |pos| { - const idx = (y + pos[1]) * self.width + (x + pos[0]); - self.current_grid[idx] = true; - } -} - -fn addBlinker(self: *GameOfLife) void { - const x = self.terminal_buffer.random.intRangeAtMost(usize, 1, self.width - 4); - const y = self.terminal_buffer.random.intRangeAtMost(usize, 1, self.height - 2); - - // 3-cell horizontal line - for (0..3) |i| { - const idx = y * self.width + (x + i); - self.current_grid[idx] = true; + self.current_grid[i] = self.terminal_buffer.random.float(f32) < self.initial_density; } } diff --git a/src/config/Config.zig b/src/config/Config.zig index c0f3411..60dad89 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -36,6 +36,11 @@ doom_bottom_color: u32 = 0x00FFFFFF, error_bg: u32 = 0x00000000, error_fg: u32 = 0x01FF0000, fg: u32 = 0x00FFFFFF, +gameoflife_fg: u32 = 0x0000FF00, +gameoflife_entropy_interval: usize = 10, +gameoflife_frame_delay: usize = 6, +gameoflife_initial_density: f32 = 0.4, +gameoflife_randomize_colors: bool = false, hide_borders: bool = false, hide_key_hints: bool = false, initial_info_text: ?[]const u8 = null, diff --git a/src/main.zig b/src/main.zig index 259a5e3..c70d5aa 100644 --- a/src/main.zig +++ b/src/main.zig @@ -366,7 +366,7 @@ pub fn main() !void { animation = color_mix.animation(); }, .gameoflife => { - var game_of_life = try GameOfLife.init(allocator, &buffer); + var game_of_life = try GameOfLife.init(allocator, &buffer, config.gameoflife_fg, config.gameoflife_entropy_interval, config.gameoflife_frame_delay, config.gameoflife_initial_density, config.gameoflife_randomize_colors); animation = game_of_life.animation(); }, } From 9d4c4a3a59d0790edfaa9943849c391e5b0d4a15 Mon Sep 17 00:00:00 2001 From: thoxy Date: Fri, 30 May 2025 20:11:53 +0200 Subject: [PATCH 3/8] add mention of the new animation in the config file --- res/config.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/res/config.ini b/res/config.ini index c358c19..b405f2f 100644 --- a/res/config.ini +++ b/res/config.ini @@ -23,6 +23,7 @@ allow_empty_password = true # doom -> PSX DOOM fire # matrix -> CMatrix # colormix -> Color mixing shader +# gameoflife -> John Conway's Game of Life animation = none # Stop the animation after some time From 5e8e0af59c111f6c48ac3ef2387c4fbdce924ffc Mon Sep 17 00:00:00 2001 From: thoxy Date: Sat, 31 May 2025 17:56:16 +0200 Subject: [PATCH 4/8] Remove redundant comments and type annotations --- src/animations/GameOfLife.zig | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/animations/GameOfLife.zig b/src/animations/GameOfLife.zig index f564e52..785c236 100644 --- a/src/animations/GameOfLife.zig +++ b/src/animations/GameOfLife.zig @@ -77,7 +77,6 @@ fn realloc(self: *GameOfLife) anyerror!void { const new_height = self.terminal_buffer.height; const new_size = new_width * new_height; - // Always reallocate to be safe const current_grid = try self.allocator.realloc(self.current_grid, new_size); const next_grid = try self.allocator.realloc(self.next_grid, new_size); @@ -150,12 +149,9 @@ fn updateGeneration(self: *GameOfLife) void { fn countNeighborsOptimized(self: *GameOfLife, x: usize, y: usize) u8 { var count: u8 = 0; - // Use cached dimensions and more efficient bounds checking for (NEIGHBOR_DIRS) |dir| { - const nx: i32 = @intCast(x); - const ny: i32 = @intCast(y); - const neighbor_x: i32 = nx + dir[0]; - const neighbor_y: i32 = ny + dir[1]; + const neighbor_x = @as(i32, @intCast(x)) + dir[0]; + const neighbor_y = @as(i32, @intCast(y)) + dir[1]; const width_i32: i32 = @intCast(self.width); const height_i32: i32 = @intCast(self.height); From a5e38e2ce5c830576431700c177707dcab3598ec Mon Sep 17 00:00:00 2001 From: thoxy Date: Tue, 17 Jun 2025 12:35:22 +0200 Subject: [PATCH 5/8] Remove color randomization from Game of Life animation --- res/config.ini | 5 ----- src/animations/GameOfLife.zig | 18 +++--------------- src/config/Config.zig | 1 - 3 files changed, 3 insertions(+), 21 deletions(-) diff --git a/res/config.ini b/res/config.ini index b405f2f..df6d721 100644 --- a/res/config.ini +++ b/res/config.ini @@ -147,11 +147,6 @@ gameoflife_frame_delay = 6 # 0.7+ -> Dense, chaotic patterns gameoflife_initial_density = 0.4 -# Game of Life randomize colors (true/false) -# false -> Use the fixed gameoflife_fg color -# true -> Generate one random color at startup and use it for the entire session -gameoflife_randomize_colors = false - # Remove main box borders hide_borders = false diff --git a/src/animations/GameOfLife.zig b/src/animations/GameOfLife.zig index 785c236..9cbefaa 100644 --- a/src/animations/GameOfLife.zig +++ b/src/animations/GameOfLife.zig @@ -4,7 +4,6 @@ const Cell = @import("../tui/Cell.zig"); const TerminalBuffer = @import("../tui/TerminalBuffer.zig"); const Allocator = std.mem.Allocator; -const Random = std.Random; const GameOfLife = @This(); @@ -27,12 +26,11 @@ fg_color: u32, entropy_interval: usize, frame_delay: usize, initial_density: f32, -randomize_colors: bool, dead_cell: Cell, width: usize, height: usize, -pub fn init(allocator: Allocator, terminal_buffer: *TerminalBuffer, fg_color: u32, entropy_interval: usize, frame_delay: usize, initial_density: f32, randomize_colors: bool) !GameOfLife { +pub fn init(allocator: Allocator, terminal_buffer: *TerminalBuffer, fg_color: u32, entropy_interval: usize, frame_delay: usize, initial_density: f32) !GameOfLife { const width = terminal_buffer.width; const height = terminal_buffer.height; const grid_size = width * height; @@ -47,11 +45,10 @@ pub fn init(allocator: Allocator, terminal_buffer: *TerminalBuffer, fg_color: u3 .next_grid = next_grid, .frame_counter = 0, .generation = 0, - .fg_color = if (randomize_colors) generateRandomColor(terminal_buffer.random) else fg_color, + .fg_color = fg_color, .entropy_interval = entropy_interval, .frame_delay = frame_delay, .initial_density = initial_density, - .randomize_colors = randomize_colors, .dead_cell = .{ .ch = DEAD_CHAR, .fg = @intCast(TerminalBuffer.Color.DEFAULT), .bg = terminal_buffer.bg }, .width = width, .height = height, @@ -103,7 +100,7 @@ fn draw(self: *GameOfLife) void { } } - // Render with the set color (either configured or randomly generated at startup) + // Render with the configured color const alive_cell = Cell{ .ch = ALIVE_CHAR, .fg = self.fg_color, .bg = self.terminal_buffer.bg }; for (0..self.height) |y| { @@ -115,15 +112,6 @@ fn draw(self: *GameOfLife) void { } } -fn generateRandomColor(random: Random) u32 { - // Generate a random RGB color with good visibility - // Avoid very dark colors by using range 64-255 for each component - const r = random.intRangeAtMost(u8, 64, 255); - const g = random.intRangeAtMost(u8, 64, 255); - const b = random.intRangeAtMost(u8, 64, 255); - return (@as(u32, r) << 16) | (@as(u32, g) << 8) | @as(u32, b); -} - fn updateGeneration(self: *GameOfLife) void { // Conway's Game of Life rules with optimized neighbor counting for (0..self.height) |y| { diff --git a/src/config/Config.zig b/src/config/Config.zig index 60dad89..e4030ce 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -40,7 +40,6 @@ gameoflife_fg: u32 = 0x0000FF00, gameoflife_entropy_interval: usize = 10, gameoflife_frame_delay: usize = 6, gameoflife_initial_density: f32 = 0.4, -gameoflife_randomize_colors: bool = false, hide_borders: bool = false, hide_key_hints: bool = false, initial_info_text: ?[]const u8 = null, From e7aad8de888d3d53c9870bd53992ec8e1b26f895 Mon Sep 17 00:00:00 2001 From: thoxy Date: Tue, 17 Jun 2025 12:40:49 +0200 Subject: [PATCH 6/8] remove gameoflife_randomize_colors argument in main for gameoflife --- src/main.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.zig b/src/main.zig index 6eed2a2..17309df 100644 --- a/src/main.zig +++ b/src/main.zig @@ -366,7 +366,7 @@ pub fn main() !void { animation = color_mix.animation(); }, .gameoflife => { - var game_of_life = try GameOfLife.init(allocator, &buffer, config.gameoflife_fg, config.gameoflife_entropy_interval, config.gameoflife_frame_delay, config.gameoflife_initial_density, config.gameoflife_randomize_colors); + var game_of_life = try GameOfLife.init(allocator, &buffer, config.gameoflife_fg, config.gameoflife_entropy_interval, config.gameoflife_frame_delay, config.gameoflife_initial_density); animation = game_of_life.animation(); }, } From de11ac8972edcf519ee83de144b2a218046b3fa6 Mon Sep 17 00:00:00 2001 From: Ireozar Date: Tue, 24 Jun 2025 12:34:41 +0200 Subject: [PATCH 7/8] improved/added German localization --- res/lang/de.ini | 96 ++++++++++++++++++++++++------------------------- 1 file changed, 48 insertions(+), 48 deletions(-) diff --git a/res/lang/de.ini b/res/lang/de.ini index af8090d..0859297 100644 --- a/res/lang/de.ini +++ b/res/lang/de.ini @@ -1,63 +1,63 @@ - - - +authenticating = authentifizierend... +brightness_down = Helligkeit- +brightness_up = Helligkeit+ capslock = Feststelltaste err_alloc = Speicherzuweisung fehlgeschlagen -err_bounds = Listenindex ist außerhalb des Bereichs - -err_chdir = Fehler beim oeffnen des home-ordners - +err_bounds = Index außerhalb des Bereichs +err_brightness_change = Helligkeitsänderung fehlgeschlagen +err_chdir = Fehler beim Oeffnen des Home-Ordners +err_config = Fehler beim Verarbeiten der Konfigurationsdatei err_console_dev = Zugriff auf die Konsole fehlgeschlagen -err_dgn_oob = Protokoll Nachricht -err_domain = Unzulaessige domain - - -err_hostname = Holen des Hostnames fehlgeschlagen -err_mlock = Abschließen des Passwortspeichers fehlgeschlagen -err_null = Null Zeiger - -err_pam = pam Transaktion fehlgeschlagen -err_pam_abort = pam Transaktion abgebrochen +err_dgn_oob = Diagnose-Nachricht +err_domain = Ungültige Domain +err_empty_password = Leeres Passwort nicht zugelassen +err_envlist = Fehler beim Abrufen der Umgebungs-Variablen +err_hostname = Abrufen des Hostnames fehlgeschlagen +err_mlock = Sperren des Passwortspeichers fehlgeschlagen +err_null = Null Pointer +err_numlock = Numlock konnte nicht aktiviert werden +err_pam = PAM-Transaktion fehlgeschlagen +err_pam_abort = PAM-Transaktion abgebrochen err_pam_acct_expired = Benutzerkonto abgelaufen -err_pam_auth = Authentifizierungs Fehler -err_pam_authinfo_unavail = holen der Benutzerinformationen fehlgeschlagen -err_pam_authok_reqd = Schluessel abgelaufen +err_pam_auth = Authentifizierungsfehler +err_pam_authinfo_unavail = Abrufen der Benutzerinformationen fehlgeschlagen +err_pam_authok_reqd = Passwort abgelaufen err_pam_buf = Speicherpufferfehler -err_pam_cred_err = Fehler beim setzen der Anmeldedaten +err_pam_cred_err = Fehler beim Setzen der Anmeldedaten err_pam_cred_expired = Anmeldedaten abgelaufen err_pam_cred_insufficient = Anmeldedaten unzureichend -err_pam_cred_unavail = Fehler beim holen der Anmeldedaten -err_pam_maxtries = Maximale Versuche erreicht -err_pam_perm_denied = Zugriff Verweigert +err_pam_cred_unavail = Fehler beim Abrufen der Anmeldedaten +err_pam_maxtries = Maximale Versuchsanzahl erreicht +err_pam_perm_denied = Zugriff verweigert err_pam_session = Sitzungsfehler err_pam_sys = Systemfehler err_pam_user_unknown = Unbekannter Nutzer -err_path = Fehler beim setzen des Pfades -err_perm_dir = Fehler beim wechseln des Ordners -err_perm_group = Fehler beim heruntersetzen der Gruppen Berechtigungen -err_perm_user = Fehler beim heruntersetzen der Nutzer Berechtigungen -err_pwnam = Holen der Benutzerinformationen fehlgeschlagen - - -err_user_gid = Fehler beim setzen der Gruppen Id des Nutzers -err_user_init = Initialisierung des Nutzers fehlgeschlagen -err_user_uid = Setzen der Benutzer Id fehlgeschlagen - - -err_xsessions_dir = Fehler beim finden des Sitzungsordners -err_xsessions_open = Fehler beim öffnen des Sitzungsordners - -login = Anmelden -logout = Abgemeldet - - -numlock = Numtaste - +err_path = Fehler beim Setzen des Pfades +err_perm_dir = Ordnerwechsel fehlgeschlagen +err_perm_group = Fehler beim Heruntersetzen der Gruppenberechtigungen +err_perm_user = Fehler beim Heruntersetzen der Nutzerberechtigungen +err_pwnam = Abrufen der Benutzerinformationen fehlgeschlagen +err_sleep = Sleep-Befehl fehlgeschlagen +err_tty_ctrl = Fehler bei der TTY-Übergabe +err_user_gid = Fehler beim Setzen der Gruppen-ID +err_user_init = Nutzer-Initialisierung fehlgeschlagen +err_user_uid = Setzen der Benutzer-ID fehlgeschlagen +err_xauth = Xauth-Befehl fehlgeschlagen +err_xcb_conn = xcb-Verbindung fehlgeschlagen +err_xsessions_dir = Fehler beim Finden des Sitzungsordners +err_xsessions_open = Fehler beim Öffnen des Sitzungsordners +insert = Einfügen +login = Nutzer +logout = Abmelden +no_x11_support = X11-Support bei Kompilierung deaktiviert +normal = Normal +numlock = Numlock +other = Andere password = Passwort restart = Neustarten -shell = shell +shell = Shell shutdown = Herunterfahren - +sleep = Sleep wayland = wayland - +x11 = X11 xinitrc = xinitrc From 7182d91b37f9b865dae2855f1476968dbbc7e237 Mon Sep 17 00:00:00 2001 From: Ireozar Date: Tue, 24 Jun 2025 13:25:45 +0200 Subject: [PATCH 8/8] removed some special characters --- res/lang/de.ini | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/res/lang/de.ini b/res/lang/de.ini index 0859297..47c44aa 100644 --- a/res/lang/de.ini +++ b/res/lang/de.ini @@ -1,15 +1,15 @@ -authenticating = authentifizierend... +authenticating = authentifizieren... brightness_down = Helligkeit- brightness_up = Helligkeit+ capslock = Feststelltaste err_alloc = Speicherzuweisung fehlgeschlagen -err_bounds = Index außerhalb des Bereichs +err_bounds = Index ausserhalb des Bereichs err_brightness_change = Helligkeitsänderung fehlgeschlagen err_chdir = Fehler beim Oeffnen des Home-Ordners err_config = Fehler beim Verarbeiten der Konfigurationsdatei err_console_dev = Zugriff auf die Konsole fehlgeschlagen err_dgn_oob = Diagnose-Nachricht -err_domain = Ungültige Domain +err_domain = Ungueltige Domain err_empty_password = Leeres Passwort nicht zugelassen err_envlist = Fehler beim Abrufen der Umgebungs-Variablen err_hostname = Abrufen des Hostnames fehlgeschlagen @@ -38,14 +38,14 @@ err_perm_group = Fehler beim Heruntersetzen der Gruppenberechtigungen err_perm_user = Fehler beim Heruntersetzen der Nutzerberechtigungen err_pwnam = Abrufen der Benutzerinformationen fehlgeschlagen err_sleep = Sleep-Befehl fehlgeschlagen -err_tty_ctrl = Fehler bei der TTY-Übergabe +err_tty_ctrl = Fehler bei der TTY-Uebergabe err_user_gid = Fehler beim Setzen der Gruppen-ID err_user_init = Nutzer-Initialisierung fehlgeschlagen err_user_uid = Setzen der Benutzer-ID fehlgeschlagen err_xauth = Xauth-Befehl fehlgeschlagen err_xcb_conn = xcb-Verbindung fehlgeschlagen err_xsessions_dir = Fehler beim Finden des Sitzungsordners -err_xsessions_open = Fehler beim Öffnen des Sitzungsordners +err_xsessions_open = Fehler beim Oeffnen des Sitzungsordners insert = Einfügen login = Nutzer logout = Abmelden