1#![no_std]
2
3extern crate alloc;
4#[macro_use]
5extern crate playdate_rs;
6
7use core::ops::{Add, Mul};
8
9use alloc::format;
10use playdate_rs::display::{DISPLAY_HEIGHT, DISPLAY_WIDTH};
11use playdate_rs::graphics::Color;
12use playdate_rs::math::Vec2;
13use playdate_rs::system::Buttons;
14use playdate_rs::{app, println, App, PLAYDATE};
15
16#[derive(Debug, Clone, Copy, PartialEq)]
17struct Complex {
18 re: f32,
19 im: f32,
20}
21
22impl Complex {
23 fn new(re: f32, im: f32) -> Self {
24 Self { re, im }
25 }
26
27 fn from_point(point: Vec2<i32>, center: Complex, scale: f32) -> Self {
28 let bounds = (DISPLAY_WIDTH as f32, DISPLAY_HEIGHT as f32);
29 let c1 = Complex::new(
30 center.re - bounds.0 / 2.0 * scale,
31 center.im + bounds.1 / 2.0 * scale,
32 );
33 let c2 = Complex::new(
34 center.re + bounds.0 / 2.0 * scale,
35 center.im - bounds.1 / 2.0 * scale,
36 );
37 let upper_left = Complex::new(c1.re.min(c2.re), c1.im.max(c2.im));
38 let lower_right = Complex::new(c1.re.max(c2.re), c1.im.min(c2.im));
39 assert!(lower_right.re > upper_left.re);
40 assert!(upper_left.im > lower_right.im);
41 let (width, height) = (
42 lower_right.re - upper_left.re,
43 upper_left.im - lower_right.im,
44 );
45 Complex {
46 re: upper_left.re + point.x as f32 * width / bounds.0,
47 im: upper_left.im - point.y as f32 * height / bounds.1,
48 }
49 }
50
51 fn norm_sqr(&self) -> f32 {
52 self.re * self.re + self.im * self.im
53 }
54}
55
56impl Add<Complex> for Complex {
57 type Output = Self;
58 fn add(self, rhs: Self) -> Self::Output {
59 Self::new(self.re + rhs.re, self.im + rhs.im)
60 }
61}
62
63impl Mul<Complex> for Complex {
64 type Output = Self;
65 fn mul(self, rhs: Complex) -> Self::Output {
66 Self::new(
67 self.re * rhs.re - self.im * rhs.im,
68 self.re * rhs.im + self.im * rhs.re,
69 )
70 }
71}
72
73fn f(c: Complex, max_iter: i32) -> bool {
74 let mut z = Complex::new(0.0, 0.0);
75 for _ in 0..max_iter {
76 if z.norm_sqr() > 4.0 {
77 return true;
78 }
79 z = z * z + c;
80 }
81 false
82}
83
84#[app]
85pub struct Mandelbrot {
86 center: Complex,
87 scale: f32,
88}
89
90impl Mandelbrot {
91 fn get_iter(&self) -> i32 {
92 match self.scale {
93 s if s > 0.01 => 16,
94 s if s > 0.001 => 32,
95 s if s > 0.0001 => 64,
96 s if s > 0.00001 => 96,
97 _ => 128,
98 }
99 }
100
101 fn draw_frame(&self) {
102 PLAYDATE.graphics.clear(Color::White);
103 let iter = self.get_iter();
104 for y in 0..DISPLAY_HEIGHT {
105 for x in 0..DISPLAY_WIDTH {
106 let pos = vec2![x as i32, y as i32];
107 let v = f(Complex::from_point(pos, self.center, self.scale), iter);
108 PLAYDATE
109 .graphics
110 .draw_pixel(pos, if v { Color::Black } else { Color::White });
111 }
112 }
113 }
114
115 fn draw_meta(&self) {
116 let text_area_width = 130;
117 let row_height = 14;
118 let text_area_height = row_height * 3;
119 let top_left_x = DISPLAY_WIDTH as i32 - text_area_width;
120 PLAYDATE.graphics.draw_rect(
121 rect! {
122 x: top_left_x - 3, y: DISPLAY_HEIGHT as i32 - text_area_height - 3,
123 w: text_area_width + 3, h: text_area_height + 3,
124 },
125 Color::White,
126 );
127 PLAYDATE.graphics.fill_rect(
128 rect! {
129 x: top_left_x - 2, y: DISPLAY_HEIGHT as i32 - text_area_height - 2,
130 w: text_area_width + 2, h: text_area_height + 2,
131 },
132 Color::Black,
133 );
134 PLAYDATE.graphics.fill_rect(
135 rect! {
136 x: top_left_x, y: DISPLAY_HEIGHT as i32 - text_area_height,
137 w: text_area_width , h: text_area_height,
138 },
139 Color::White,
140 );
141 PLAYDATE.graphics.draw_text(
142 format!("<{:.4}, {:.4}i>", self.center.re, self.center.im,),
143 vec2![top_left_x + 2, DISPLAY_HEIGHT as i32 - row_height * 3],
144 );
145 PLAYDATE.graphics.draw_text(
146 format!("SCALE: {:.8}", 1.0 / (self.scale * 100.0)),
147 vec2![top_left_x + 2, DISPLAY_HEIGHT as i32 - row_height * 2],
148 );
149 PLAYDATE.graphics.draw_text(
150 format!("ITER: {:}", self.get_iter()),
151 vec2![top_left_x + 2, DISPLAY_HEIGHT as i32 - row_height],
152 );
153 }
154}
155
156impl App for Mandelbrot {
157 fn new() -> Self {
158 println!("Hello, Mandelbrot!");
159 Self {
160 center: Complex::new(-0.5, 0.0),
161 scale: 0.01,
162 }
163 }
164
165 fn init(&mut self) {
166 let font = PLAYDATE
167 .graphics
168 .load_font("/System/Fonts/Roobert-10-Bold.pft")
169 .unwrap();
170 PLAYDATE.graphics.set_font(&font);
171 self.draw_frame();
172 }
173
174 fn update(&mut self, _delta: f32) {
175 let button_state = PLAYDATE.system.get_button_state();
176 let prev_scale = self.scale;
177 let prev_center = self.center;
178 if button_state.current.contains(Buttons::A) {
180 self.scale /= 1.1;
181 } else if button_state.current.contains(Buttons::B) {
182 self.scale *= 1.1
183 }
184 if !PLAYDATE.system.is_crank_docked() {
185 let crank = PLAYDATE.system.get_crank_change();
186 if crank > 0.0 {
187 self.scale /= 1.05;
188 } else if crank < 0.0 {
189 self.scale *= 1.05;
190 }
191 }
192 if button_state.current.contains(Buttons::Up) {
194 self.center.im += 10.0 * self.scale
195 } else if button_state.current.contains(Buttons::Down) {
196 self.center.im -= 10.0 * self.scale
197 }
198 if button_state.current.contains(Buttons::Left) {
199 self.center.re -= 10.0 * self.scale
200 } else if button_state.current.contains(Buttons::Right) {
201 self.center.re += 10.0 * self.scale
202 }
203 if prev_center != self.center || prev_scale != self.scale {
204 self.draw_frame();
205 }
206 self.draw_meta();
208 PLAYDATE.system.draw_fps(vec2![0, 0]);
210 }
211}