mirror of https://github.com/fairyglade/ly.git
make gameoflife more configurable and fix pull reviews
This commit is contained in:
parent
98af3a98c8
commit
fa46155f72
|
@ -125,6 +125,32 @@ error_fg = 0x01FF0000
|
||||||
# Foreground color id
|
# Foreground color id
|
||||||
fg = 0x00FFFFFF
|
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
|
# Remove main box borders
|
||||||
hide_borders = false
|
hide_borders = false
|
||||||
|
|
||||||
|
|
|
@ -8,30 +8,9 @@ const Random = std.Random;
|
||||||
|
|
||||||
const GameOfLife = @This();
|
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
|
// Visual styles - using block characters like other animations
|
||||||
const ALIVE_CHAR: u21 = 0x2588; // Full block █
|
const ALIVE_CHAR: u21 = 0x2588; // Full block █
|
||||||
const DEAD_CHAR: u21 = ' ';
|
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{
|
const NEIGHBOR_DIRS = [_][2]i8{
|
||||||
.{ -1, -1 }, .{ -1, 0 }, .{ -1, 1 },
|
.{ -1, -1 }, .{ -1, 0 }, .{ -1, 1 },
|
||||||
.{ 0, -1 }, .{ 0, 1 }, .{ 1, -1 },
|
.{ 0, -1 }, .{ 0, 1 }, .{ 1, -1 },
|
||||||
|
@ -44,13 +23,16 @@ current_grid: []bool,
|
||||||
next_grid: []bool,
|
next_grid: []bool,
|
||||||
frame_counter: usize,
|
frame_counter: usize,
|
||||||
generation: u64,
|
generation: u64,
|
||||||
color_index: usize,
|
fg_color: u32,
|
||||||
color_counter: usize,
|
entropy_interval: usize,
|
||||||
|
frame_delay: usize,
|
||||||
|
initial_density: f32,
|
||||||
|
randomize_colors: bool,
|
||||||
dead_cell: Cell,
|
dead_cell: Cell,
|
||||||
width: usize,
|
width: usize,
|
||||||
height: 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 width = terminal_buffer.width;
|
||||||
const height = terminal_buffer.height;
|
const height = terminal_buffer.height;
|
||||||
const grid_size = width * height;
|
const grid_size = width * height;
|
||||||
|
@ -65,8 +47,11 @@ pub fn init(allocator: Allocator, terminal_buffer: *TerminalBuffer) !GameOfLife
|
||||||
.next_grid = next_grid,
|
.next_grid = next_grid,
|
||||||
.frame_counter = 0,
|
.frame_counter = 0,
|
||||||
.generation = 0,
|
.generation = 0,
|
||||||
.color_index = 0,
|
.fg_color = if (randomize_colors) generateRandomColor(terminal_buffer.random) else fg_color,
|
||||||
.color_counter = 0,
|
.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 },
|
.dead_cell = .{ .ch = DEAD_CHAR, .fg = @intCast(TerminalBuffer.Color.DEFAULT), .bg = terminal_buffer.bg },
|
||||||
.width = width,
|
.width = width,
|
||||||
.height = height,
|
.height = height,
|
||||||
|
@ -92,47 +77,35 @@ fn realloc(self: *GameOfLife) anyerror!void {
|
||||||
const new_height = self.terminal_buffer.height;
|
const new_height = self.terminal_buffer.height;
|
||||||
const new_size = new_width * new_height;
|
const new_size = new_width * new_height;
|
||||||
|
|
||||||
// Only reallocate if size changed significantly
|
// Always reallocate to be safe
|
||||||
if (new_size != self.width * self.height) {
|
const current_grid = try self.allocator.realloc(self.current_grid, new_size);
|
||||||
const current_grid = try self.allocator.realloc(self.current_grid, new_size);
|
const next_grid = try self.allocator.realloc(self.next_grid, new_size);
|
||||||
const next_grid = try self.allocator.realloc(self.next_grid, new_size);
|
|
||||||
|
|
||||||
self.current_grid = current_grid;
|
self.current_grid = current_grid;
|
||||||
self.next_grid = next_grid;
|
self.next_grid = next_grid;
|
||||||
self.width = new_width;
|
self.width = new_width;
|
||||||
self.height = new_height;
|
self.height = new_height;
|
||||||
|
|
||||||
self.initializeGrid();
|
self.initializeGrid();
|
||||||
self.generation = 0;
|
self.generation = 0;
|
||||||
self.color_index = 0;
|
|
||||||
self.color_counter = 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw(self: *GameOfLife) void {
|
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
|
// Update game state at controlled frame rate
|
||||||
self.frame_counter += 1;
|
self.frame_counter += 1;
|
||||||
if (self.frame_counter >= FRAME_DELAY) {
|
if (self.frame_counter >= self.frame_delay) {
|
||||||
self.frame_counter = 0;
|
self.frame_counter = 0;
|
||||||
self.updateGeneration();
|
self.updateGeneration();
|
||||||
self.generation += 1;
|
self.generation += 1;
|
||||||
|
|
||||||
// Add entropy less frequently to reduce computational overhead
|
// Add entropy based on configuration (0 = disabled, >0 = interval)
|
||||||
if (self.generation % 150 == 0) {
|
if (self.entropy_interval > 0 and self.generation % self.entropy_interval == 0) {
|
||||||
self.addEntropy();
|
self.addEntropy();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render with ANSI color cycling - use current color from the array (same method as Matrix/Doom)
|
// Render with the set color (either configured or randomly generated at startup)
|
||||||
const current_color = ANSI_COLORS[self.color_index];
|
const alive_cell = Cell{ .ch = ALIVE_CHAR, .fg = self.fg_color, .bg = self.terminal_buffer.bg };
|
||||||
const alive_cell = Cell{ .ch = ALIVE_CHAR, .fg = current_color, .bg = self.terminal_buffer.bg };
|
|
||||||
|
|
||||||
for (0..self.height) |y| {
|
for (0..self.height) |y| {
|
||||||
const row_offset = y * self.width;
|
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 {
|
fn updateGeneration(self: *GameOfLife) void {
|
||||||
// Conway's Game of Life rules with optimized neighbor counting
|
// Conway's Game of Life rules with optimized neighbor counting
|
||||||
for (0..self.height) |y| {
|
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
|
// Use cached dimensions and more efficient bounds checking
|
||||||
for (NEIGHBOR_DIRS) |dir| {
|
for (NEIGHBOR_DIRS) |dir| {
|
||||||
const nx = @as(i32, @intCast(x)) + dir[0];
|
const nx: i32 = @intCast(x);
|
||||||
const ny = @as(i32, @intCast(y)) + dir[1];
|
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
|
// Toroidal wrapping with modular arithmetic
|
||||||
const wx: usize = @intCast(@mod(nx + @as(i32, @intCast(self.width)), @as(i32, @intCast(self.width))));
|
const wx: usize = @intCast(@mod(neighbor_x + width_i32, width_i32));
|
||||||
const wy: usize = @intCast(@mod(ny + @as(i32, @intCast(self.height)), @as(i32, @intCast(self.height))));
|
const wy: usize = @intCast(@mod(neighbor_y + height_i32, height_i32));
|
||||||
|
|
||||||
if (self.current_grid[wy * self.width + wx]) {
|
if (self.current_grid[wy * self.width + wx]) {
|
||||||
count += 1;
|
count += 1;
|
||||||
|
@ -192,62 +178,9 @@ fn initializeGrid(self: *GameOfLife) void {
|
||||||
@memset(self.current_grid, false);
|
@memset(self.current_grid, false);
|
||||||
@memset(self.next_grid, false);
|
@memset(self.next_grid, false);
|
||||||
|
|
||||||
// Random initialization with better distribution
|
// Random initialization with configurable density
|
||||||
for (0..total_cells) |i| {
|
for (0..total_cells) |i| {
|
||||||
self.current_grid[i] = self.terminal_buffer.random.float(f32) < INITIAL_DENSITY;
|
self.current_grid[i] = self.terminal_buffer.random.float(f32) < self.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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -36,6 +36,11 @@ doom_bottom_color: u32 = 0x00FFFFFF,
|
||||||
error_bg: u32 = 0x00000000,
|
error_bg: u32 = 0x00000000,
|
||||||
error_fg: u32 = 0x01FF0000,
|
error_fg: u32 = 0x01FF0000,
|
||||||
fg: u32 = 0x00FFFFFF,
|
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_borders: bool = false,
|
||||||
hide_key_hints: bool = false,
|
hide_key_hints: bool = false,
|
||||||
initial_info_text: ?[]const u8 = null,
|
initial_info_text: ?[]const u8 = null,
|
||||||
|
|
|
@ -366,7 +366,7 @@ pub fn main() !void {
|
||||||
animation = color_mix.animation();
|
animation = color_mix.animation();
|
||||||
},
|
},
|
||||||
.gameoflife => {
|
.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();
|
animation = game_of_life.animation();
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue