|
61 | 61 | "```{admonition} Substep 1 Solution\n",
|
62 | 62 | ":class: dropdown\n",
|
63 | 63 | "```Julia\n",
|
64 |
| - "using Random\n", |
65 |
| - "\n", |
66 | 64 | "const N = 100 # grid size\n",
|
67 | 65 | "# Create a random N×N Matrix of Bool\n",
|
68 |
| - "grid = Matrix{Bool}(rand(Bool, N, N))\n", |
| 66 | + "grid = rand(Bool, N, N)\n", |
69 | 67 | "```\n",
|
70 | 68 | "```"
|
71 | 69 | ]
|
|
82 | 80 | },
|
83 | 81 | "source": [
|
84 | 82 | "### Substep 2: Neighbor Counting\n",
|
| 83 | + "\n", |
85 | 84 | "**Goal:** Count the number of live neighbors surrounding cell `(i, j)` in a finite `Matrix{Bool}` grid. \n",
|
86 | 85 | "```{admonition} Substep 2 Solution\n",
|
87 | 86 | ":class: dropdown\n",
|
88 | 87 | "```Julia\n",
|
89 |
| - "function count_neighbors_naive(grid::Matrix{Bool}, i::Int, j::Int)::Int\n", |
90 |
| - " cnt = 0\n", |
91 |
| - " for di in -1:1, dj in -1:1\n", |
92 |
| - " if di != 0 || dj != 0\n", |
93 |
| - " i2, j2 = i + di, j + dj\n", |
94 |
| - " if 1 ≤ i2 ≤ size(grid,1) && 1 ≤ j2 ≤ size(grid,2)\n", |
95 |
| - " cnt += grid[i2, j2]\n", |
96 |
| - " end\n", |
97 |
| - " end\n", |
98 |
| - " end\n", |
99 |
| - " return cnt\n", |
| 88 | + "function count_alive_neighbours(grid::Matrix{Bool}, i::Int, j::Int)::Int\n", |
| 89 | + " subset_i = max(1, i - 1):min(size(grid, 1), i + 1)\n", |
| 90 | + " subset_j = max(1, j - 1):min(size(grid, 2), j + 1)\n", |
| 91 | + " return sum(grid[subset_i, subset_j]) - grid[i, j] # don't count subject cell itself\n", |
100 | 92 | "end\n",
|
101 | 93 | "```\n",
|
| 94 | + "How do you think this could be made more efficient?\n", |
102 | 95 | "```"
|
103 | 96 | ]
|
104 | 97 | },
|
|
113 | 106 | "tags": []
|
114 | 107 | },
|
115 | 108 | "source": [
|
116 |
| - "### Substep 3: Compute Next State Naïvely\n", |
| 109 | + "### Substep 3: Compute Next State\n", |
| 110 | + "\n", |
117 | 111 | "**Goal:** Compute and return the next generation of the Game of Life grid by applying Conway’s rules, using a fresh `Matrix{Bool}` for the updated state. \n",
|
118 | 112 | "```{admonition} Substep 3 Solution\n",
|
119 | 113 | ":class: dropdown\n",
|
120 | 114 | "```Julia\n",
|
121 |
| - "function next_state_naive(grid::Matrix{Bool})::Matrix{Bool}\n", |
| 115 | + "function next_state(grid::Matrix{Bool})\n", |
| 116 | + " # Get grid dimensions: N1 rows, N2 columns\n", |
122 | 117 | " N1, N2 = size(grid)\n",
|
| 118 | + "\n", |
| 119 | + " # Pre-allocate a new grid of the same size, initially all false (dead)\n", |
123 | 120 | " newgrid = falses(N1, N2)\n",
|
| 121 | + "\n", |
| 122 | + " # Loop over every cell position (i, j)\n", |
124 | 123 | " for i in 1:N1, j in 1:N2\n",
|
125 |
| - " cnt = count_neighbors_naive(grid, i, j)\n", |
| 124 | + " # Count the live neighbors around (i, j)\n", |
| 125 | + " cnt = count_alive_neighbours(grid, i, j)\n", |
| 126 | + "\n", |
| 127 | + "\n", |
| 128 | + " # Apply the Game of Life rules:\n", |
| 129 | + " # - A live cell stays alive if it has 2 or 3 neighbors.\n", |
| 130 | + " # - A dead cell becomes alive if it has exactly 3 neighbors.\n", |
| 131 | + " # Otherwise it remains or becomes dead.\n", |
126 | 132 | " newgrid[i,j] = (grid[i,j] && (cnt == 2 || cnt == 3)) ||\n",
|
127 | 133 | " (!grid[i,j] && cnt == 3)\n",
|
128 | 134 | " end\n",
|
| 135 | + "\n", |
| 136 | + " # Return the updated grid for the next generation\n", |
129 | 137 | " return newgrid\n",
|
130 | 138 | "end\n",
|
131 | 139 | "```\n",
|
| 140 | + "Are there any opportunities for a boost in performance?\n", |
132 | 141 | "```"
|
133 | 142 | ]
|
134 | 143 | },
|
|
143 | 152 | "tags": []
|
144 | 153 | },
|
145 | 154 | "source": [
|
146 |
| - "### Substep 4: Run Simulation and Visualise\n", |
147 |
| - "**Goal:** Animate the evolution of the Game of Life grid over a given number of steps and save the result as a GIF using Plots. \n", |
| 155 | + "### Substep 4: Run Simulation\n", |
| 156 | + "\n", |
| 157 | + "**Goal:** Simulate the Game of Life for a given number of steps. \n", |
148 | 158 | "\n",
|
149 | 159 | "```{admonition} Substep 4 Solution\n",
|
150 | 160 | ":class: dropdown\n",
|
151 | 161 | "```Julia\n",
|
152 |
| - "using Plots\n", |
| 162 | + "function simulate(initial_grid::Matrix{Bool}, steps::Int)\n", |
| 163 | + " # Get grid dimensions\n", |
| 164 | + " N1, N2 = size(initial_grid)\n", |
153 | 165 | "\n",
|
154 |
| - "function run_naive_simulation(grid::Matrix{Bool}, steps::Int)\n", |
155 |
| - " anim = @animate for _ in 1:steps\n", |
156 |
| - " grid = next_state_naive(grid)\n", |
| 166 | + " # Make 3D array with time along the third axis.\n", |
| 167 | + " # Starts with only a single time index corresponding to initial\n", |
| 168 | + " # grid.\n", |
| 169 | + " simulations = reshape(initial_grid, N1, N2, 1)\n", |
| 170 | + " \n", |
| 171 | + " for step in 1:steps\n", |
| 172 | + " # Current latest generation\n", |
| 173 | + " current = simulations[:, :, end]\n", |
| 174 | + " \n", |
| 175 | + " # Update simulations with next generation\n", |
| 176 | + " next_gen = next_state(current)\n", |
| 177 | + " simulations = cat(simulations, next_gen; dims=3)\n", |
| 178 | + " end\n", |
| 179 | + " \n", |
| 180 | + " return simulations\n", |
| 181 | + "end\n", |
| 182 | + "```\n", |
| 183 | + "How do you think this could be made more performant?\n", |
| 184 | + "```" |
| 185 | + ] |
| 186 | + }, |
| 187 | + { |
| 188 | + "cell_type": "markdown", |
| 189 | + "id": "27592a70-12b5-4466-b832-1d70d94cf0c4", |
| 190 | + "metadata": {}, |
| 191 | + "source": [ |
| 192 | + "### Substep 5: Visualize\n", |
| 193 | + "\n", |
| 194 | + "**Goal:** Animate the evolution of the Game of Life grid and save the result as a GIF using `Plots`. \n", |
| 195 | + "\n", |
| 196 | + "```{admonition} Substep 5 Solution\n", |
| 197 | + ":class: dropdown\n", |
| 198 | + "```Julia\n", |
| 199 | + "function visualize_simulations(\n", |
| 200 | + " simulations::Array{Bool, 3};\n", |
| 201 | + " print_interval_percent::Int=10\n", |
| 202 | + ")\n", |
| 203 | + " # Compute how many steps correspond to one print interval:\n", |
| 204 | + " # at least 1, scaled by the given percentage of total steps\n", |
| 205 | + " n_steps = size(simulations, 3)\n", |
| 206 | + " step_interval = max(1, floor(Int, n_steps * print_interval_percent / 100))\n", |
| 207 | + "\n", |
| 208 | + " t_start = time()\n", |
| 209 | + " anim = @animate for step in axes(simulations, 3)\n", |
| 210 | + " # If we've reached a print interval, output progress stats\n", |
| 211 | + " if step % step_interval == 0\n", |
| 212 | + " pct = step / n_steps * 100\n", |
| 213 | + " elapsed = time() - t_start\n", |
| 214 | + " avg = elapsed / step\n", |
| 215 | + " remain = avg * (n_steps - step)\n", |
| 216 | + " @printf(\"Progress: %3.0f%% | Elapsed: %5.1fs | Remaining: %5.1fs\\n\",\n", |
| 217 | + " pct, elapsed, remain)\n", |
| 218 | + " end\n", |
| 219 | + "\n", |
| 220 | + " # Generate a heatmap frame for the current grid state\n", |
157 | 221 | " heatmap(\n",
|
158 |
| - " grid;\n", |
159 |
| - " color = :grays, \n", |
160 |
| - " axis = false, \n", |
| 222 | + " view(simulations, :, :, step);\n", |
| 223 | + " color = :grays,\n", |
| 224 | + " axis = false,\n", |
161 | 225 | " legend = false,\n",
|
162 | 226 | " framestyle = :none,\n",
|
163 | 227 | " aspect_ratio = 1\n",
|
164 | 228 | " )\n",
|
165 | 229 | " end\n",
|
166 |
| - " gif(anim, \"life_naive.gif\"; fps = 10)\n", |
167 |
| - " println(\"Saved naive animation to life_naive.gif\")\n", |
168 |
| - "end\n", |
| 230 | + " \n", |
| 231 | + " # Ensure the “data” directory exists\n", |
| 232 | + " mkpath(\"data\")\n", |
169 | 233 | "\n",
|
170 |
| - "# Example usage:\n", |
171 |
| - "run_naive_simulation(grid, 100)\n", |
| 234 | + " # Write the animation to GIF at 10 frames per second\n", |
| 235 | + " gif(anim, \"data/life_naive.gif\"; fps = 10)\n", |
| 236 | + " println(\"Saved animation to data/life_naive.gif\")\n", |
| 237 | + " \n", |
| 238 | + " # Detect if running inside a Jupyter notebook\n", |
| 239 | + " in_notebook = isdefined(Main, :IJulia) && IJulia.inited\n", |
| 240 | + " \n", |
| 241 | + " if in_notebook\n", |
| 242 | + " # Display the GIF inline in the notebook\n", |
| 243 | + " display(\"image/gif\", read(\"data/life_naive.gif\"))\n", |
| 244 | + " else\n", |
| 245 | + " # Otherwise, instruct the user to open the files manually\n", |
| 246 | + " println(\"Not running in a notebook.\")\n", |
| 247 | + " println(\"Open `data/life_naive.gif` in your OS image viewer to see the results.\")\n", |
| 248 | + " end\n", |
| 249 | + "end\n", |
172 | 250 | "```\n",
|
173 | 251 | "```"
|
174 | 252 | ]
|
|
206 | 284 | "### Formatted Output with `Printf`\n",
|
207 | 285 | "\n",
|
208 | 286 | "**Module**: `using Printf`\n",
|
| 287 | + "\n", |
209 | 288 | "**Macro**: `@printf` for C-style formatted printing to `STDOUT`.\n",
|
210 |
| - "`@printf(\"Progress: %3.0f%% | Elapsed: %5.1fs | Remaining: %5.1fs\\n\",\n", |
211 |
| - " pct, elapsed, remain)`\n", |
| 289 | + "\n", |
| 290 | + "````julia\n", |
| 291 | + "@printf(\"Progress: %3.0f%% | Elapsed: %5.1fs | Remaining: %5.1fs\\n\",\n", |
| 292 | + " pct, elapsed, remain)\n", |
| 293 | + "````\n", |
212 | 294 | "```\n",
|
213 | 295 | "\n",
|
214 | 296 | "```{admonition} Hint 2 \n",
|
215 | 297 | ":class: dropdown\n",
|
216 |
| - "### Animations with `Plots.jl` \n", |
| 298 | + "### Animations with `Plots.jl`\n", |
| 299 | + "\n", |
| 300 | + "**Module**: `using Plots`\n", |
| 301 | + "\n", |
217 | 302 | "**Macro**: `@animate` collects plot frames in a loop, which can then be exported with `gif(anim, filepath; fps=10)`.\n",
|
218 | 303 | "\n",
|
219 | 304 | "````julia \n",
|
|
223 | 308 | "````\n",
|
224 | 309 | "```\n"
|
225 | 310 | ]
|
226 |
| - }, |
227 |
| - { |
228 |
| - "cell_type": "code", |
229 |
| - "execution_count": null, |
230 |
| - "id": "c7ad76a0-fb5c-4a87-ade8-1690e2e1f61b", |
231 |
| - "metadata": {}, |
232 |
| - "outputs": [], |
233 |
| - "source": [] |
234 | 311 | }
|
235 | 312 | ],
|
236 | 313 | "metadata": {
|
237 | 314 | "kernelspec": {
|
238 |
| - "display_name": "Julia 1.11.5", |
| 315 | + "display_name": "Julia 1.10.9", |
239 | 316 | "language": "julia",
|
240 |
| - "name": "julia-1.11" |
| 317 | + "name": "julia-1.10" |
241 | 318 | },
|
242 | 319 | "language_info": {
|
243 | 320 | "file_extension": ".jl",
|
244 | 321 | "mimetype": "application/julia",
|
245 | 322 | "name": "julia",
|
246 |
| - "version": "1.11.5" |
| 323 | + "version": "1.10.9" |
247 | 324 | }
|
248 | 325 | },
|
249 | 326 | "nbformat": 4,
|
|
0 commit comments