|
1 | | -use cell::Cellule; |
2 | 1 | use gloo::timers::callback::Interval; |
3 | | -use rand::Rng; |
4 | 2 | use yew::html::Scope; |
5 | 3 | use yew::{classes, html, Component, Context, Html}; |
6 | 4 |
|
7 | | -mod cell; |
| 5 | +mod conway; |
8 | 6 |
|
9 | 7 | pub enum Msg { |
10 | 8 | Random, |
11 | 9 | Start, |
12 | 10 | Step, |
13 | 11 | Reset, |
14 | 12 | Stop, |
15 | | - ToggleCellule(usize), |
| 13 | + ToggleCellule((usize, usize)), |
16 | 14 | Tick, |
17 | 15 | } |
18 | 16 |
|
19 | 17 | pub struct App { |
20 | 18 | active: bool, |
21 | | - cellules: Vec<Cellule>, |
22 | | - cellules_width: usize, |
23 | | - cellules_height: usize, |
| 19 | + conway: conway::Conway, |
24 | 20 | _interval: Interval, |
25 | 21 | } |
26 | 22 |
|
27 | 23 | impl App { |
28 | | - pub fn random_mutate(&mut self) { |
29 | | - for cellule in self.cellules.iter_mut() { |
30 | | - if rand::rng().random() { |
31 | | - cellule.set_alive(); |
32 | | - } else { |
33 | | - cellule.set_dead(); |
34 | | - } |
35 | | - } |
36 | | - } |
37 | | - |
38 | | - fn reset(&mut self) { |
39 | | - for cellule in self.cellules.iter_mut() { |
40 | | - cellule.set_dead(); |
41 | | - } |
42 | | - } |
43 | | - |
44 | | - fn step(&mut self) { |
45 | | - let mut to_dead = Vec::new(); |
46 | | - let mut to_live = Vec::new(); |
47 | | - for row in 0..self.cellules_height { |
48 | | - for col in 0..self.cellules_width { |
49 | | - let neighbors = self.neighbors(row as isize, col as isize); |
50 | | - |
51 | | - let current_idx = self.row_col_as_idx(row as isize, col as isize); |
52 | | - if self.cellules[current_idx].is_alive() { |
53 | | - if Cellule::alone(&neighbors) || Cellule::overpopulated(&neighbors) { |
54 | | - to_dead.push(current_idx); |
55 | | - } |
56 | | - } else if Cellule::can_be_revived(&neighbors) { |
57 | | - to_live.push(current_idx); |
58 | | - } |
59 | | - } |
60 | | - } |
61 | | - to_dead |
62 | | - .iter() |
63 | | - .for_each(|idx| self.cellules[*idx].set_dead()); |
64 | | - to_live |
65 | | - .iter() |
66 | | - .for_each(|idx| self.cellules[*idx].set_alive()); |
67 | | - } |
68 | | - |
69 | | - fn neighbors(&self, row: isize, col: isize) -> [Cellule; 8] { |
70 | | - [ |
71 | | - self.cellules[self.row_col_as_idx(row + 1, col)], |
72 | | - self.cellules[self.row_col_as_idx(row + 1, col + 1)], |
73 | | - self.cellules[self.row_col_as_idx(row + 1, col - 1)], |
74 | | - self.cellules[self.row_col_as_idx(row - 1, col)], |
75 | | - self.cellules[self.row_col_as_idx(row - 1, col + 1)], |
76 | | - self.cellules[self.row_col_as_idx(row - 1, col - 1)], |
77 | | - self.cellules[self.row_col_as_idx(row, col - 1)], |
78 | | - self.cellules[self.row_col_as_idx(row, col + 1)], |
79 | | - ] |
80 | | - } |
81 | | - |
82 | | - fn row_col_as_idx(&self, row: isize, col: isize) -> usize { |
83 | | - let row = wrap(row, self.cellules_height as isize); |
84 | | - let col = wrap(col, self.cellules_width as isize); |
85 | | - |
86 | | - row * self.cellules_width + col |
87 | | - } |
88 | | - |
89 | | - fn view_cellule(&self, idx: usize, cellule: &Cellule, link: &Scope<Self>) -> Html { |
90 | | - let cellule_status = { |
91 | | - if cellule.is_alive() { |
92 | | - "cellule-live" |
93 | | - } else { |
94 | | - "cellule-dead" |
95 | | - } |
| 24 | + fn view_cellule(&self, row: usize, col: usize, link: &Scope<Self>) -> Html { |
| 25 | + let status = if self.conway.alive(row, col) { |
| 26 | + "cellule-live" |
| 27 | + } else { |
| 28 | + "cellule-dead" |
96 | 29 | }; |
97 | 30 | html! { |
98 | | - <div key={idx} class={classes!("game-cellule", cellule_status)} |
99 | | - onclick={link.callback(move |_| Msg::ToggleCellule(idx))}> |
| 31 | + <div class={classes!("game-cellule", status)} |
| 32 | + onclick={link.callback(move |_| Msg::ToggleCellule((row,col)))}> |
100 | 33 | </div> |
101 | 34 | } |
102 | 35 | } |
103 | 36 | } |
| 37 | + |
104 | 38 | impl Component for App { |
105 | 39 | type Message = Msg; |
106 | 40 | type Properties = (); |
107 | 41 |
|
108 | 42 | fn create(ctx: &Context<Self>) -> Self { |
109 | 43 | let callback = ctx.link().callback(|_| Msg::Tick); |
110 | | - let interval = Interval::new(200, move || callback.emit(())); |
111 | | - |
112 | | - let (cellules_width, cellules_height) = (53, 40); |
113 | 44 |
|
114 | 45 | Self { |
115 | 46 | active: false, |
116 | | - cellules: vec![Cellule::new_dead(); cellules_width * cellules_height], |
117 | | - cellules_width, |
118 | | - cellules_height, |
119 | | - _interval: interval, |
| 47 | + conway: conway::Conway::new(53, 40), |
| 48 | + _interval: Interval::new(200, move || callback.emit(())), |
120 | 49 | } |
121 | 50 | } |
122 | 51 |
|
123 | 52 | fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool { |
| 53 | + let mut render = true; |
124 | 54 | match msg { |
125 | 55 | Msg::Random => { |
126 | | - self.random_mutate(); |
| 56 | + self.conway.random_mutate(); |
127 | 57 | log::info!("Random"); |
128 | | - true |
129 | 58 | } |
130 | 59 | Msg::Start => { |
131 | 60 | self.active = true; |
132 | 61 | log::info!("Start"); |
133 | | - false |
| 62 | + render = false; |
134 | 63 | } |
135 | 64 | Msg::Step => { |
136 | | - self.step(); |
137 | | - true |
| 65 | + self.conway.step(); |
138 | 66 | } |
139 | 67 | Msg::Reset => { |
140 | | - self.reset(); |
| 68 | + self.conway.reset(); |
141 | 69 | log::info!("Reset"); |
142 | | - true |
143 | 70 | } |
144 | 71 | Msg::Stop => { |
145 | 72 | self.active = false; |
146 | 73 | log::info!("Stop"); |
147 | | - false |
148 | | - } |
149 | | - Msg::ToggleCellule(idx) => { |
150 | | - let cellule = self.cellules.get_mut(idx).unwrap(); |
151 | | - cellule.toggle(); |
152 | | - true |
| 74 | + render = false; |
153 | 75 | } |
| 76 | + Msg::ToggleCellule((row, col)) => self.conway.toggle(row, col), |
154 | 77 | Msg::Tick => { |
155 | 78 | if self.active { |
156 | | - self.step(); |
157 | | - true |
| 79 | + self.conway.step(); |
158 | 80 | } else { |
159 | | - false |
| 81 | + render = false; |
160 | 82 | } |
161 | 83 | } |
162 | 84 | } |
| 85 | + render |
163 | 86 | } |
164 | 87 |
|
165 | 88 | fn view(&self, ctx: &Context<Self>) -> Html { |
166 | | - let cell_rows = |
167 | | - self.cellules |
168 | | - .chunks(self.cellules_width) |
169 | | - .enumerate() |
170 | | - .map(|(y, cellules)| { |
171 | | - let idx_offset = y * self.cellules_width; |
172 | | - |
173 | | - let cells = cellules |
174 | | - .iter() |
175 | | - .enumerate() |
176 | | - .map(|(x, cell)| self.view_cellule(idx_offset + x, cell, ctx.link())); |
177 | | - html! { |
178 | | - <div key={y} class="game-row"> |
179 | | - { for cells } |
180 | | - </div> |
181 | | - } |
182 | | - }); |
| 89 | + let cell_rows = self |
| 90 | + .conway |
| 91 | + .cellules |
| 92 | + .chunks(self.conway.width) |
| 93 | + .enumerate() |
| 94 | + .map(|(row, cellules)| { |
| 95 | + let cells = cellules |
| 96 | + .iter() |
| 97 | + .enumerate() |
| 98 | + .map(|(col, _)| self.view_cellule(row, col, ctx.link())); |
| 99 | + html! { |
| 100 | + <div class="game-row"> |
| 101 | + { for cells } |
| 102 | + </div> |
| 103 | + } |
| 104 | + }); |
183 | 105 |
|
184 | 106 | html! { |
185 | 107 | <div> |
@@ -212,17 +134,6 @@ impl Component for App { |
212 | 134 | } |
213 | 135 | } |
214 | 136 |
|
215 | | -fn wrap(coord: isize, range: isize) -> usize { |
216 | | - let result = if coord < 0 { |
217 | | - coord + range |
218 | | - } else if coord >= range { |
219 | | - coord - range |
220 | | - } else { |
221 | | - coord |
222 | | - }; |
223 | | - result as usize |
224 | | -} |
225 | | - |
226 | 137 | fn main() { |
227 | 138 | wasm_logger::init(wasm_logger::Config::default()); |
228 | 139 | log::trace!("Initializing yew..."); |
|
0 commit comments