feat(toolbar): Parameters toolbar + click interaction
parent
b9efb79fd9
commit
1af96098e3
|
@ -704,6 +704,12 @@ dependencies = [
|
|||
"gl_generator",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"
|
||||
|
||||
[[package]]
|
||||
name = "ident_case"
|
||||
version = "1.0.1"
|
||||
|
@ -1193,6 +1199,12 @@ dependencies = [
|
|||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5583e89e108996506031660fe09baa5011b9dd0341b89029313006d1fb508d70"
|
||||
|
||||
[[package]]
|
||||
name = "safe_arch"
|
||||
version = "0.5.2"
|
||||
|
@ -1338,6 +1350,25 @@ version = "0.10.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||
|
||||
[[package]]
|
||||
name = "strum"
|
||||
version = "0.24.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f"
|
||||
|
||||
[[package]]
|
||||
name = "strum_macros"
|
||||
version = "0.24.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustversion",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.107"
|
||||
|
@ -1644,6 +1675,8 @@ dependencies = [
|
|||
"egui",
|
||||
"getrandom",
|
||||
"rand",
|
||||
"strum",
|
||||
"strum_macros",
|
||||
"tracing-subscriber",
|
||||
"tracing-wasm",
|
||||
"wasm-bindgen-futures",
|
||||
|
|
10
Cargo.toml
10
Cargo.toml
|
@ -13,6 +13,12 @@ eframe = { version = "0.20.0", default-features = false, features = [
|
|||
] }
|
||||
|
||||
rand = "0.8.5"
|
||||
strum = "0.24.1"
|
||||
strum_macros = "0.24.3"
|
||||
|
||||
[lib]
|
||||
name = "gol"
|
||||
path = "src/lib.rs"
|
||||
|
||||
# native:
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
|
@ -24,3 +30,7 @@ console_error_panic_hook = "0.1.6"
|
|||
tracing-wasm = "0.2"
|
||||
wasm-bindgen-futures = "0.4"
|
||||
getrandom = { version = "0.2", features = ["js"] }
|
||||
|
||||
[profile.release]
|
||||
opt-level = 's'
|
||||
lto = true
|
||||
|
|
|
@ -29,15 +29,12 @@ You can now open the `index.html` file built in the `dist` folder in your browse
|
|||
|
||||
## TODO
|
||||
### Game
|
||||
toolbar UI with:
|
||||
- pause/start/restart buttons
|
||||
- speed/size change selectors
|
||||
toolbar UI with:
|
||||
- choose between known interesting seed or totally random one
|
||||
- only live/dead colors mode or colored birth/death mode
|
||||
|
||||
interactions:
|
||||
- click to kill or create a cell
|
||||
interactions:
|
||||
...
|
||||
|
||||
### Embedding
|
||||
- embed in cyberduck.blog
|
||||
- embed in cyberduck.blog
|
||||
|
|
|
@ -2,6 +2,6 @@
|
|||
<head>
|
||||
</head>
|
||||
<body>
|
||||
<canvas id="canvas" style="position: absolute; top: 0%; left: 0%; width: 1600px; height: 900px;"></canvas>
|
||||
<canvas id="canvas" style="position: absolute; top: 0%; left: 0%; width: 1280px; height: 720px;"></canvas>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,201 @@
|
|||
use std::fmt::{Display, Formatter, Result};
|
||||
|
||||
use eframe::App;
|
||||
use egui::{
|
||||
vec2, CentralPanel, ComboBox, Context, Frame, Pos2, Rect, Sense, Shape, TextStyle,
|
||||
TopBottomPanel,
|
||||
};
|
||||
use strum::IntoEnumIterator;
|
||||
use strum_macros::EnumIter;
|
||||
|
||||
use crate::game::Universe;
|
||||
|
||||
const CANVAS_WIDTH: f32 = 1280.0;
|
||||
const CANVAS_HEIGHT: f32 = 720.0;
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, EnumIter)]
|
||||
enum Speed {
|
||||
Slow = 8,
|
||||
Normal = 4,
|
||||
Fast = 2,
|
||||
Fastest = 1,
|
||||
}
|
||||
|
||||
impl Default for Speed {
|
||||
fn default() -> Self {
|
||||
Speed::Fast
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Speed {
|
||||
fn fmt(&self, f: &mut Formatter) -> Result {
|
||||
match self {
|
||||
Speed::Slow => write!(f, "Slow"),
|
||||
Speed::Normal => write!(f, "Normal"),
|
||||
Speed::Fast => write!(f, "Fast"),
|
||||
Speed::Fastest => write!(f, "Fastest"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(u32)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, EnumIter)]
|
||||
enum Size {
|
||||
Small = 8,
|
||||
Medium = 4,
|
||||
Large = 2,
|
||||
Giant = 1,
|
||||
}
|
||||
|
||||
impl Default for Size {
|
||||
fn default() -> Self {
|
||||
Size::Large
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Size {
|
||||
fn fmt(&self, f: &mut Formatter) -> Result {
|
||||
match self {
|
||||
Size::Small => write!(f, "Small"),
|
||||
Size::Medium => write!(f, "Medium"),
|
||||
Size::Large => write!(f, "Large"),
|
||||
Size::Giant => write!(f, "Giant"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MyEguiApp {
|
||||
game: Universe,
|
||||
frame_counter: u8,
|
||||
speed: Speed,
|
||||
size: Size,
|
||||
cache: Vec<Shape>,
|
||||
paused: bool,
|
||||
click: Option<Pos2>,
|
||||
}
|
||||
|
||||
impl MyEguiApp {
|
||||
pub fn new(_cc: &eframe::CreationContext<'_>) -> Self {
|
||||
let size = Size::default();
|
||||
Self {
|
||||
game: Universe::new(
|
||||
CANVAS_WIDTH as u32 / size as u32,
|
||||
CANVAS_HEIGHT as u32 / size as u32,
|
||||
),
|
||||
frame_counter: 0,
|
||||
speed: Speed::default(),
|
||||
size,
|
||||
cache: Vec::new(),
|
||||
paused: false,
|
||||
click: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl App for MyEguiApp {
|
||||
fn update(&mut self, ctx: &Context, frame: &mut eframe::Frame) {
|
||||
let toolbar = self.render_toolbar(ctx);
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
frame.set_window_size(vec2(CANVAS_WIDTH, CANVAS_HEIGHT + toolbar.height()));
|
||||
self.render_canvas(ctx);
|
||||
if !self.paused {
|
||||
self.frame_counter += 1;
|
||||
self.frame_counter %= self.speed as u8;
|
||||
}
|
||||
ctx.request_repaint();
|
||||
}
|
||||
}
|
||||
|
||||
impl MyEguiApp {
|
||||
fn reset(&mut self) {
|
||||
self.game = Universe::new(
|
||||
CANVAS_WIDTH as u32 / self.size as u32,
|
||||
CANVAS_HEIGHT as u32 / self.size as u32,
|
||||
);
|
||||
self.cache = Vec::new();
|
||||
self.frame_counter = 0;
|
||||
}
|
||||
|
||||
fn render_toolbar(&mut self, ctx: &Context) -> Rect {
|
||||
let mut frame = egui::Frame::side_top_panel(ctx.style().as_ref());
|
||||
frame.inner_margin.bottom += 1.5;
|
||||
let toolbar = TopBottomPanel::top("my_panel")
|
||||
.frame(frame)
|
||||
.show(ctx, |ui| {
|
||||
ui.horizontal(|ui| {
|
||||
ui.style_mut().spacing.interact_size.y *= 1.25;
|
||||
ui.style_mut()
|
||||
.text_styles
|
||||
.get_mut(&TextStyle::Button)
|
||||
.unwrap()
|
||||
.size *= 1.25;
|
||||
if ui.button("Reset").clicked() {
|
||||
self.reset();
|
||||
}
|
||||
let paused = self.paused;
|
||||
ui.toggle_value(&mut self.paused, if paused { "▶" } else { "⏸" });
|
||||
ComboBox::from_label("Speed")
|
||||
.selected_text(format!("{:?}", self.speed))
|
||||
.show_ui(ui, |ui| {
|
||||
for speed in Speed::iter() {
|
||||
if ui
|
||||
.selectable_value(&mut self.speed, speed, speed.to_string())
|
||||
.clicked()
|
||||
{
|
||||
self.frame_counter = 0;
|
||||
}
|
||||
}
|
||||
});
|
||||
ComboBox::from_label("Size")
|
||||
.selected_text(format!("{:?}", self.size))
|
||||
.show_ui(ui, |ui| {
|
||||
for size in Size::iter() {
|
||||
if ui
|
||||
.selectable_value(&mut self.size, size, size.to_string())
|
||||
.clicked()
|
||||
{
|
||||
self.reset();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
toolbar.response.rect
|
||||
}
|
||||
|
||||
fn render_canvas(&mut self, ctx: &Context) -> Rect {
|
||||
let frame = Frame::central_panel(ctx.style().as_ref()).inner_margin(0.0);
|
||||
let canvas = CentralPanel::default().frame(frame).show(ctx, |ui| {
|
||||
let (res, painter) =
|
||||
ui.allocate_painter(vec2(CANVAS_WIDTH, CANVAS_HEIGHT), Sense::click());
|
||||
let canvas_origin = res.rect.left_top().to_vec2();
|
||||
let interaction = match (res.interact_pointer_pos(), self.click) {
|
||||
(Some(pointer_pos), None) => {
|
||||
let canvas_pos = pointer_pos - canvas_origin;
|
||||
self.click = Some(canvas_pos);
|
||||
let x = canvas_pos.x as u32 / self.size as u32;
|
||||
let y = canvas_pos.y as u32 / self.size as u32;
|
||||
self.game.toggle_cell(y, x);
|
||||
true
|
||||
}
|
||||
(None, Some(_)) => {
|
||||
self.click = None;
|
||||
false
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
let tick = !self.paused && self.frame_counter % self.speed as u8 == 0;
|
||||
if tick {
|
||||
self.game.tick();
|
||||
}
|
||||
if tick || interaction || self.cache.is_empty() {
|
||||
self.cache = self
|
||||
.game
|
||||
.render(canvas_origin, vec2(CANVAS_WIDTH, CANVAS_HEIGHT));
|
||||
}
|
||||
painter.extend(self.cache.iter().cloned());
|
||||
});
|
||||
canvas.response.rect
|
||||
}
|
||||
}
|
|
@ -0,0 +1,153 @@
|
|||
use eframe::epaint::RectShape;
|
||||
use egui::{pos2, vec2, Color32, Rect, Rounding, Shape, Stroke, Vec2};
|
||||
use rand::Rng;
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum Cell {
|
||||
Dead = 0,
|
||||
Alive = 1,
|
||||
}
|
||||
|
||||
pub struct Universe {
|
||||
width: u32,
|
||||
height: u32,
|
||||
age: u32,
|
||||
cells: (Vec<Cell>, Vec<Cell>),
|
||||
}
|
||||
|
||||
impl Universe {
|
||||
pub fn new(width: u32, height: u32) -> Universe {
|
||||
let mut rng = rand::thread_rng();
|
||||
let cells = (0..width * height)
|
||||
.map(|_| {
|
||||
if rng.gen_range(0..10) > 5 {
|
||||
Cell::Alive
|
||||
} else {
|
||||
Cell::Dead
|
||||
}
|
||||
})
|
||||
.collect::<Vec<Cell>>();
|
||||
|
||||
Universe {
|
||||
width,
|
||||
height,
|
||||
age: 0,
|
||||
cells: (cells.clone(), cells),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render(&self, canvas_origin: Vec2, canvas_size: Vec2) -> Vec<Shape> {
|
||||
let fill = Color32::from_gray(128);
|
||||
let rounding = Rounding::none();
|
||||
let cell_side = canvas_size.x / self.width as f32;
|
||||
let stroke = Stroke::NONE;
|
||||
let mut rectangles_cache = Vec::new();
|
||||
|
||||
let current = match self.age % 2 {
|
||||
0 => &self.cells.1,
|
||||
_ => &self.cells.0,
|
||||
};
|
||||
let mut width_counter = 0;
|
||||
let mut width_origin = 0.0;
|
||||
for row in 0..self.height {
|
||||
for col in 0..self.width {
|
||||
let idx = Universe::get_index(self.width, row, col);
|
||||
if let Cell::Alive = current[idx] {
|
||||
if width_counter == 0 {
|
||||
width_origin = col as f32 * cell_side;
|
||||
}
|
||||
width_counter += 1;
|
||||
} else if width_counter != 0 {
|
||||
let y = row as f32 * cell_side;
|
||||
let rect = Rect::from_min_size(
|
||||
pos2(width_origin, y) + canvas_origin,
|
||||
vec2(cell_side * width_counter as f32, cell_side),
|
||||
);
|
||||
rectangles_cache.push(Shape::Rect(RectShape {
|
||||
rect,
|
||||
rounding,
|
||||
fill,
|
||||
stroke,
|
||||
}));
|
||||
width_counter = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
rectangles_cache
|
||||
}
|
||||
|
||||
fn get_index(width: u32, row: u32, column: u32) -> usize {
|
||||
(row * width + column) as usize
|
||||
}
|
||||
|
||||
fn live_neighbor_count(
|
||||
universe: &[Cell],
|
||||
height: u32,
|
||||
width: u32,
|
||||
row: u32,
|
||||
column: u32,
|
||||
) -> u8 {
|
||||
let mut count = 0;
|
||||
for delta_row in [height - 1, 0, 1] {
|
||||
for delta_col in [width - 1, 0, 1] {
|
||||
if delta_row == 0 && delta_col == 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let neighbor_row = (row + delta_row) % height;
|
||||
let neighbor_col = (column + delta_col) % width;
|
||||
let idx = Universe::get_index(width, neighbor_row, neighbor_col);
|
||||
count += universe[idx] as u8;
|
||||
}
|
||||
}
|
||||
count
|
||||
}
|
||||
|
||||
pub fn tick(&mut self) {
|
||||
let (next, current) = match self.age % 2 {
|
||||
0 => (&mut self.cells.0, &self.cells.1),
|
||||
_ => (&mut self.cells.1, &self.cells.0),
|
||||
};
|
||||
for row in 0..self.height {
|
||||
for col in 0..self.width {
|
||||
let idx = Universe::get_index(self.width, row, col);
|
||||
let cell = current[idx];
|
||||
let live_neighbors =
|
||||
Universe::live_neighbor_count(current, self.height, self.width, row, col);
|
||||
|
||||
let next_cell = match (cell, live_neighbors) {
|
||||
// Rule 1: Any live cell with fewer than two live neighbours
|
||||
// dies, as if caused by underpopulation.
|
||||
(Cell::Alive, x) if x < 2 => Cell::Dead,
|
||||
// Rule 2: Any live cell with two or three live neighbours
|
||||
// lives on to the next generation.
|
||||
(Cell::Alive, 2) | (Cell::Alive, 3) => Cell::Alive,
|
||||
// Rule 3: Any live cell with more than three live
|
||||
// neighbours dies, as if by overpopulation.
|
||||
(Cell::Alive, x) if x > 3 => Cell::Dead,
|
||||
// Rule 4: Any dead cell with exactly three live neighbours
|
||||
// becomes a live cell, as if by reproduction.
|
||||
(Cell::Dead, 3) => Cell::Alive,
|
||||
// All other cells remain in the same state.
|
||||
(otherwise, _) => otherwise,
|
||||
};
|
||||
|
||||
next[idx] = next_cell;
|
||||
}
|
||||
}
|
||||
self.age += 1;
|
||||
}
|
||||
|
||||
pub fn toggle_cell(&mut self, row: u32, column: u32) {
|
||||
let idx = Universe::get_index(self.width, row, column);
|
||||
let current = match self.age % 2 {
|
||||
0 => &mut self.cells.1,
|
||||
_ => &mut self.cells.0,
|
||||
};
|
||||
current[idx] = match current[idx] {
|
||||
Cell::Dead => Cell::Alive,
|
||||
Cell::Alive => Cell::Dead,
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
pub mod app;
|
||||
pub mod game;
|
248
src/main.rs
248
src/main.rs
|
@ -1,169 +1,4 @@
|
|||
use eframe::epaint::RectShape;
|
||||
use egui::{pos2, Color32, Rect, Rounding, Sense, Shape, Stroke, Vec2};
|
||||
use rand::Rng;
|
||||
use std::fmt;
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum Cell {
|
||||
Dead = 0,
|
||||
Alive = 1,
|
||||
}
|
||||
|
||||
pub struct Universe {
|
||||
width: u32,
|
||||
height: u32,
|
||||
age: u32,
|
||||
cells: (Vec<Cell>, Vec<Cell>),
|
||||
}
|
||||
|
||||
impl Universe {
|
||||
pub fn new(width: u32, height: u32) -> Universe {
|
||||
let mut rng = rand::thread_rng();
|
||||
let cells = (0..width * height)
|
||||
.map(|_| {
|
||||
if rng.gen_range(0..10) > 5 {
|
||||
Cell::Alive
|
||||
} else {
|
||||
Cell::Dead
|
||||
}
|
||||
})
|
||||
.collect::<Vec<Cell>>();
|
||||
|
||||
Universe {
|
||||
width,
|
||||
height,
|
||||
age: 0,
|
||||
cells: (cells.clone(), cells),
|
||||
}
|
||||
}
|
||||
|
||||
// pub fn render(&self) -> String {
|
||||
// self.to_string()
|
||||
// }
|
||||
|
||||
pub fn render(&self, canvas_origin: Vec2, canvas_size: Vec2) -> Vec<Shape> {
|
||||
let fill = Color32::from_gray(128);
|
||||
let rounding = Rounding::none();
|
||||
let cell_side = canvas_size.x / self.width as f32;
|
||||
let stroke = Stroke::NONE;
|
||||
let mut rectangles_cache = Vec::new();
|
||||
|
||||
let current = match self.age % 2 {
|
||||
0 => &self.cells.0,
|
||||
_ => &self.cells.1,
|
||||
};
|
||||
let mut width_counter = 0;
|
||||
let mut width_origin = 0.0;
|
||||
for row in 0..self.height {
|
||||
for col in 0..self.width {
|
||||
let idx = Universe::get_index(self.width, row, col);
|
||||
if let Cell::Alive = current[idx] {
|
||||
if width_counter == 0 {
|
||||
width_origin = col as f32 * cell_side;
|
||||
}
|
||||
width_counter += 1;
|
||||
} else if width_counter != 0 {
|
||||
let y = row as f32 * cell_side;
|
||||
let rect = Rect::from_min_size(
|
||||
pos2(width_origin, y) + canvas_origin,
|
||||
Vec2::new(cell_side * width_counter as f32, cell_side),
|
||||
);
|
||||
rectangles_cache.push(Shape::Rect(RectShape {
|
||||
rect,
|
||||
rounding,
|
||||
fill,
|
||||
stroke,
|
||||
}));
|
||||
width_counter = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
rectangles_cache
|
||||
}
|
||||
|
||||
fn get_index(width: u32, row: u32, column: u32) -> usize {
|
||||
(row * width + column) as usize
|
||||
}
|
||||
|
||||
fn live_neighbor_count(
|
||||
universe: &[Cell],
|
||||
height: u32,
|
||||
width: u32,
|
||||
row: u32,
|
||||
column: u32,
|
||||
) -> u8 {
|
||||
let mut count = 0;
|
||||
for delta_row in [height - 1, 0, 1] {
|
||||
for delta_col in [width - 1, 0, 1] {
|
||||
if delta_row == 0 && delta_col == 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let neighbor_row = (row + delta_row) % height;
|
||||
let neighbor_col = (column + delta_col) % width;
|
||||
let idx = Universe::get_index(width, neighbor_row, neighbor_col);
|
||||
count += universe[idx] as u8;
|
||||
}
|
||||
}
|
||||
count
|
||||
}
|
||||
|
||||
pub fn tick(&mut self) {
|
||||
let (next, current) = match self.age % 2 {
|
||||
0 => (&mut self.cells.0, &self.cells.1),
|
||||
_ => (&mut self.cells.1, &self.cells.0),
|
||||
};
|
||||
for row in 0..self.height {
|
||||
for col in 0..self.width {
|
||||
let idx = Universe::get_index(self.width, row, col);
|
||||
let cell = current[idx];
|
||||
let live_neighbors =
|
||||
Universe::live_neighbor_count(current, self.height, self.width, row, col);
|
||||
|
||||
let next_cell = match (cell, live_neighbors) {
|
||||
// Rule 1: Any live cell with fewer than two live neighbours
|
||||
// dies, as if caused by underpopulation.
|
||||
(Cell::Alive, x) if x < 2 => Cell::Dead,
|
||||
// Rule 2: Any live cell with two or three live neighbours
|
||||
// lives on to the next generation.
|
||||
(Cell::Alive, 2) | (Cell::Alive, 3) => Cell::Alive,
|
||||
// Rule 3: Any live cell with more than three live
|
||||
// neighbours dies, as if by overpopulation.
|
||||
(Cell::Alive, x) if x > 3 => Cell::Dead,
|
||||
// Rule 4: Any dead cell with exactly three live neighbours
|
||||
// becomes a live cell, as if by reproduction.
|
||||
(Cell::Dead, 3) => Cell::Alive,
|
||||
// All other cells remain in the same state.
|
||||
(otherwise, _) => otherwise,
|
||||
};
|
||||
|
||||
next[idx] = next_cell;
|
||||
}
|
||||
}
|
||||
self.age += 1;
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Universe {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let current = match self.age % 2 {
|
||||
0 => &self.cells.0,
|
||||
_ => &self.cells.1,
|
||||
};
|
||||
for line in current.as_slice().chunks(self.width as usize) {
|
||||
for &cell in line {
|
||||
let symbol = if cell == Cell::Dead { '◻' } else { '◼' };
|
||||
write!(f, "{}", symbol)?;
|
||||
}
|
||||
writeln!(f)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
const CANVAS_WIDTH: f32 = 1600.0;
|
||||
const CANVAS_HEIGHT: f32 = 900.0;
|
||||
use gol::app::MyEguiApp;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
fn main() {
|
||||
|
@ -188,92 +23,13 @@ fn main() {
|
|||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn main() {
|
||||
use egui::vec2;
|
||||
|
||||
let options = eframe::NativeOptions {
|
||||
initial_window_size: Some(vec2(CANVAS_WIDTH, CANVAS_HEIGHT)),
|
||||
resizable: false,
|
||||
..Default::default()
|
||||
};
|
||||
eframe::run_native(
|
||||
"My egui App",
|
||||
"Game of Life",
|
||||
options,
|
||||
Box::new(|cc| Box::new(MyEguiApp::new(cc))),
|
||||
);
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
enum Speed {
|
||||
//Slow = 16,
|
||||
//Normal = 8,
|
||||
//Fast = 4,
|
||||
Fastest = 2,
|
||||
}
|
||||
|
||||
impl Default for Speed {
|
||||
fn default() -> Self {
|
||||
Speed::Fastest
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(u32)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
enum Size {
|
||||
//Small = 8,
|
||||
//Medium = 4,
|
||||
Large = 2,
|
||||
}
|
||||
|
||||
impl Default for Size {
|
||||
fn default() -> Self {
|
||||
Size::Large
|
||||
}
|
||||
}
|
||||
|
||||
struct MyEguiApp {
|
||||
game: Universe,
|
||||
frame_counter: u8,
|
||||
speed: Speed,
|
||||
//size: Size,
|
||||
cache: Vec<Shape>,
|
||||
}
|
||||
|
||||
impl MyEguiApp {
|
||||
fn new(_cc: &eframe::CreationContext<'_>) -> Self {
|
||||
// Customize egui here with cc.egui_ctx.set_fonts and cc.egui_ctx.set_visuals.
|
||||
// Restore app state using cc.storage (requires the "persistence" feature).
|
||||
// Use the cc.gl (a glow::Context) to create graphics shaders and buffers that you can use
|
||||
// for e.g. egui::PaintCallback.
|
||||
let size = Size::default();
|
||||
Self {
|
||||
game: Universe::new(
|
||||
CANVAS_WIDTH as u32 / size as u32,
|
||||
CANVAS_HEIGHT as u32 / size as u32,
|
||||
),
|
||||
frame_counter: 0,
|
||||
speed: Speed::default(),
|
||||
//size,
|
||||
cache: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl eframe::App for MyEguiApp {
|
||||
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||
const PAINTER_SIZE: Vec2 = egui::vec2(CANVAS_WIDTH, CANVAS_HEIGHT);
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
// add click sense later for interaction
|
||||
let (response, painter) = ui.allocate_painter(PAINTER_SIZE, Sense::hover());
|
||||
let origin = response.rect.left_top().to_vec2();
|
||||
if self.frame_counter % self.speed as u8 == 0 {
|
||||
self.game.tick();
|
||||
self.cache = self.game.render(origin, PAINTER_SIZE);
|
||||
}
|
||||
painter.extend(self.cache.iter().cloned());
|
||||
});
|
||||
self.frame_counter += 1;
|
||||
self.frame_counter %= self.speed as u8;
|
||||
ctx.request_repaint();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue