2017-07-09 03:15:17 +00:00
|
|
|
extern crate sdl2;
|
|
|
|
|
|
|
|
|
|
#[cfg(feature = "unsafe_textures")]
|
2020-12-08 17:06:23 +01:00
|
|
|
use game_of_life::{PLAYGROUND_HEIGHT, PLAYGROUND_WIDTH, SQUARE_SIZE};
|
2017-07-09 03:15:17 +00:00
|
|
|
#[cfg(feature = "unsafe_textures")]
|
|
|
|
|
use sdl2::event::Event;
|
|
|
|
|
#[cfg(feature = "unsafe_textures")]
|
|
|
|
|
use sdl2::keyboard::Keycode;
|
|
|
|
|
#[cfg(feature = "unsafe_textures")]
|
2020-12-08 17:06:23 +01:00
|
|
|
use sdl2::mouse::MouseButton;
|
|
|
|
|
#[cfg(feature = "unsafe_textures")]
|
|
|
|
|
use sdl2::pixels::Color;
|
|
|
|
|
#[cfg(feature = "unsafe_textures")]
|
|
|
|
|
use sdl2::rect::{Point, Rect};
|
2017-07-09 03:15:17 +00:00
|
|
|
#[cfg(feature = "unsafe_textures")]
|
|
|
|
|
use sdl2::render::{Canvas, Texture};
|
|
|
|
|
#[cfg(feature = "unsafe_textures")]
|
2020-12-08 17:06:23 +01:00
|
|
|
use sdl2::video::Window;
|
2017-07-09 03:15:17 +00:00
|
|
|
|
|
|
|
|
#[cfg(feature = "unsafe_textures")]
|
|
|
|
|
mod game_of_life {
|
|
|
|
|
pub const SQUARE_SIZE: u32 = 16;
|
|
|
|
|
pub const PLAYGROUND_WIDTH: u32 = 49;
|
|
|
|
|
pub const PLAYGROUND_HEIGHT: u32 = 40;
|
|
|
|
|
|
|
|
|
|
#[derive(Copy, Clone)]
|
|
|
|
|
pub enum State {
|
|
|
|
|
Paused,
|
|
|
|
|
Playing,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub struct GameOfLife {
|
2020-12-08 17:06:23 +01:00
|
|
|
playground: [bool; (PLAYGROUND_WIDTH * PLAYGROUND_HEIGHT) as usize],
|
2017-07-09 03:15:17 +00:00
|
|
|
state: State,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl GameOfLife {
|
|
|
|
|
pub fn new() -> GameOfLife {
|
|
|
|
|
let mut playground = [false; (PLAYGROUND_WIDTH * PLAYGROUND_HEIGHT) as usize];
|
|
|
|
|
|
|
|
|
|
// let's make a nice default pattern !
|
2020-12-08 17:06:23 +01:00
|
|
|
for i in 1..(PLAYGROUND_HEIGHT - 1) {
|
|
|
|
|
playground[(1 + i * PLAYGROUND_WIDTH) as usize] = true;
|
|
|
|
|
playground[((PLAYGROUND_WIDTH - 2) + i * PLAYGROUND_WIDTH) as usize] = true;
|
2017-07-09 03:15:17 +00:00
|
|
|
}
|
2020-12-08 17:06:23 +01:00
|
|
|
for j in 2..(PLAYGROUND_WIDTH - 2) {
|
2017-07-09 03:15:17 +00:00
|
|
|
playground[(PLAYGROUND_WIDTH + j) as usize] = true;
|
2020-12-08 17:06:23 +01:00
|
|
|
playground[((PLAYGROUND_HEIGHT - 2) * PLAYGROUND_WIDTH + j) as usize] = true;
|
2017-07-09 03:15:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
GameOfLife {
|
|
|
|
|
playground: playground,
|
|
|
|
|
state: State::Paused,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn get(&self, x: i32, y: i32) -> Option<bool> {
|
2020-12-08 17:06:23 +01:00
|
|
|
if x >= 0 && y >= 0 && (x as u32) < PLAYGROUND_WIDTH && (y as u32) < PLAYGROUND_HEIGHT {
|
|
|
|
|
Some(self.playground[(x as u32 + (y as u32) * PLAYGROUND_WIDTH) as usize])
|
2017-07-09 03:15:17 +00:00
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn get_mut(&mut self, x: i32, y: i32) -> Option<&mut bool> {
|
2020-12-08 17:06:23 +01:00
|
|
|
if x >= 0 && y >= 0 && (x as u32) < PLAYGROUND_WIDTH && (y as u32) < PLAYGROUND_HEIGHT {
|
|
|
|
|
Some(&mut self.playground[(x as u32 + (y as u32) * PLAYGROUND_WIDTH) as usize])
|
2017-07-09 03:15:17 +00:00
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn toggle_state(&mut self) {
|
|
|
|
|
self.state = match self.state {
|
|
|
|
|
State::Paused => State::Playing,
|
|
|
|
|
State::Playing => State::Paused,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn state(&self) -> State {
|
|
|
|
|
self.state
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn update(&mut self) {
|
|
|
|
|
let mut new_playground = self.playground;
|
2017-09-23 21:06:08 +02:00
|
|
|
for (u, square) in new_playground.iter_mut().enumerate() {
|
2017-07-09 03:15:17 +00:00
|
|
|
let u = u as u32;
|
|
|
|
|
let x = u % PLAYGROUND_WIDTH;
|
|
|
|
|
let y = u / PLAYGROUND_WIDTH;
|
2020-12-08 17:06:23 +01:00
|
|
|
let mut count: u32 = 0;
|
2017-07-09 03:15:17 +00:00
|
|
|
for i in -1..2 {
|
|
|
|
|
for j in -1..2 {
|
|
|
|
|
if !(i == 0 && j == 0) {
|
2020-12-08 17:06:23 +01:00
|
|
|
let peek_x: i32 = (x as i32) + i;
|
|
|
|
|
let peek_y: i32 = (y as i32) + j;
|
2017-07-09 03:15:17 +00:00
|
|
|
if let Some(true) = self.get(peek_x, peek_y) {
|
|
|
|
|
count += 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if count > 3 || count < 2 {
|
|
|
|
|
*square = false;
|
|
|
|
|
} else if count == 3 {
|
|
|
|
|
*square = true;
|
|
|
|
|
} else if count == 2 {
|
|
|
|
|
*square = *square;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
self.playground = new_playground;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<'a> IntoIterator for &'a GameOfLife {
|
|
|
|
|
type Item = &'a bool;
|
|
|
|
|
type IntoIter = ::std::slice::Iter<'a, bool>;
|
|
|
|
|
fn into_iter(self) -> ::std::slice::Iter<'a, bool> {
|
|
|
|
|
self.playground.iter()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(feature = "unsafe_textures")]
|
2019-01-11 23:42:23 -05:00
|
|
|
fn dummy_texture<'a>(canvas: &mut Canvas<Window>) -> Result<(Texture, Texture), String> {
|
2017-07-09 03:15:17 +00:00
|
|
|
enum TextureColor {
|
|
|
|
|
Yellow,
|
|
|
|
|
White,
|
|
|
|
|
};
|
2020-12-08 17:06:23 +01:00
|
|
|
let mut square_texture1 = canvas
|
|
|
|
|
.create_texture_target(None, SQUARE_SIZE, SQUARE_SIZE)
|
|
|
|
|
.map_err(|e| e.to_string())?;
|
|
|
|
|
let mut square_texture2 = canvas
|
|
|
|
|
.create_texture_target(None, SQUARE_SIZE, SQUARE_SIZE)
|
|
|
|
|
.map_err(|e| e.to_string())?;
|
|
|
|
|
// let's change the textures we just created
|
2017-07-09 03:15:17 +00:00
|
|
|
{
|
|
|
|
|
let textures = vec![
|
|
|
|
|
(&mut square_texture1, TextureColor::Yellow),
|
2020-12-08 17:06:23 +01:00
|
|
|
(&mut square_texture2, TextureColor::White),
|
2017-07-09 03:15:17 +00:00
|
|
|
];
|
2020-12-08 17:06:23 +01:00
|
|
|
canvas
|
|
|
|
|
.with_multiple_texture_canvas(textures.iter(), |texture_canvas, user_context| {
|
|
|
|
|
texture_canvas.set_draw_color(Color::RGB(0, 0, 0));
|
|
|
|
|
texture_canvas.clear();
|
|
|
|
|
match *user_context {
|
|
|
|
|
TextureColor::Yellow => {
|
|
|
|
|
for i in 0..SQUARE_SIZE {
|
|
|
|
|
for j in 0..SQUARE_SIZE {
|
|
|
|
|
if (i + j) % 4 == 0 {
|
|
|
|
|
texture_canvas.set_draw_color(Color::RGB(255, 255, 0));
|
|
|
|
|
texture_canvas
|
|
|
|
|
.draw_point(Point::new(i as i32, j as i32))
|
|
|
|
|
.expect("could not draw point");
|
|
|
|
|
}
|
|
|
|
|
if (i + j * 2) % 9 == 0 {
|
|
|
|
|
texture_canvas.set_draw_color(Color::RGB(200, 200, 0));
|
|
|
|
|
texture_canvas
|
|
|
|
|
.draw_point(Point::new(i as i32, j as i32))
|
|
|
|
|
.expect("could not draw point");
|
|
|
|
|
}
|
2017-07-09 03:15:17 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-12-08 17:06:23 +01:00
|
|
|
TextureColor::White => {
|
|
|
|
|
for i in 0..SQUARE_SIZE {
|
|
|
|
|
for j in 0..SQUARE_SIZE {
|
|
|
|
|
// drawing pixel by pixel isn't very effective, but we only do it once and store
|
|
|
|
|
// the texture afterwards so it's still alright!
|
|
|
|
|
if (i + j) % 7 == 0 {
|
|
|
|
|
// this doesn't mean anything, there was some trial and error to find
|
|
|
|
|
// something that wasn't too ugly
|
|
|
|
|
texture_canvas.set_draw_color(Color::RGB(192, 192, 192));
|
|
|
|
|
texture_canvas
|
|
|
|
|
.draw_point(Point::new(i as i32, j as i32))
|
|
|
|
|
.expect("could not draw point");
|
|
|
|
|
}
|
|
|
|
|
if (i + j * 2) % 5 == 0 {
|
|
|
|
|
texture_canvas.set_draw_color(Color::RGB(64, 64, 64));
|
|
|
|
|
texture_canvas
|
|
|
|
|
.draw_point(Point::new(i as i32, j as i32))
|
|
|
|
|
.expect("could not draw point");
|
|
|
|
|
}
|
2017-07-09 03:15:17 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-12-08 17:06:23 +01:00
|
|
|
};
|
|
|
|
|
for i in 0..SQUARE_SIZE {
|
|
|
|
|
for j in 0..SQUARE_SIZE {
|
|
|
|
|
// drawing pixel by pixel isn't very effective, but we only do it once and store
|
|
|
|
|
// the texture afterwards so it's still alright!
|
|
|
|
|
if (i + j) % 7 == 0 {
|
|
|
|
|
// this doesn't mean anything, there was some trial and serror to find
|
|
|
|
|
// something that wasn't too ugly
|
|
|
|
|
texture_canvas.set_draw_color(Color::RGB(192, 192, 192));
|
|
|
|
|
texture_canvas
|
|
|
|
|
.draw_point(Point::new(i as i32, j as i32))
|
|
|
|
|
.expect("could not draw point");
|
|
|
|
|
}
|
|
|
|
|
if (i + j * 2) % 5 == 0 {
|
|
|
|
|
texture_canvas.set_draw_color(Color::RGB(64, 64, 64));
|
|
|
|
|
texture_canvas
|
|
|
|
|
.draw_point(Point::new(i as i32, j as i32))
|
|
|
|
|
.expect("could not draw point");
|
|
|
|
|
}
|
2017-07-09 03:15:17 +00:00
|
|
|
}
|
|
|
|
|
}
|
2020-12-08 17:06:23 +01:00
|
|
|
})
|
|
|
|
|
.map_err(|e| e.to_string())?;
|
2017-07-09 03:15:17 +00:00
|
|
|
}
|
2019-01-11 23:42:23 -05:00
|
|
|
Ok((square_texture1, square_texture2))
|
2017-07-09 03:15:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(feature = "unsafe_textures")]
|
2019-01-11 23:42:23 -05:00
|
|
|
pub fn main() -> Result<(), String> {
|
|
|
|
|
let sdl_context = sdl2::init()?;
|
|
|
|
|
let video_subsystem = sdl_context.video()?;
|
|
|
|
|
|
2017-07-09 03:15:17 +00:00
|
|
|
// the window is the representation of a window in your operating system,
|
|
|
|
|
// however you can only manipulate properties of that window, like its size, whether it's
|
|
|
|
|
// fullscreen, ... but you cannot change its content without using a Canvas or using the
|
|
|
|
|
// `surface()` method.
|
|
|
|
|
let window = video_subsystem
|
2020-12-08 17:06:23 +01:00
|
|
|
.window(
|
|
|
|
|
"rust-sdl2 demo: Game of Life",
|
|
|
|
|
SQUARE_SIZE * PLAYGROUND_WIDTH,
|
|
|
|
|
SQUARE_SIZE * PLAYGROUND_HEIGHT,
|
|
|
|
|
)
|
2017-07-09 03:15:17 +00:00
|
|
|
.position_centered()
|
|
|
|
|
.build()
|
2019-01-11 23:42:23 -05:00
|
|
|
.map_err(|e| e.to_string())?;
|
2017-07-09 03:15:17 +00:00
|
|
|
|
|
|
|
|
// the canvas allows us to both manipulate the property of the window and to change its content
|
|
|
|
|
// via hardware or software rendering. See CanvasBuilder for more info.
|
2020-12-08 17:06:23 +01:00
|
|
|
let mut canvas = window
|
|
|
|
|
.into_canvas()
|
2017-07-09 03:15:17 +00:00
|
|
|
.target_texture()
|
|
|
|
|
.present_vsync()
|
2020-12-08 17:06:23 +01:00
|
|
|
.build()
|
|
|
|
|
.map_err(|e| e.to_string())?;
|
2017-07-09 03:15:17 +00:00
|
|
|
|
|
|
|
|
println!("Using SDL_Renderer \"{}\"", canvas.info().name);
|
|
|
|
|
canvas.set_draw_color(Color::RGB(0, 0, 0));
|
|
|
|
|
// clears the canvas with the color we set in `set_draw_color`.
|
|
|
|
|
canvas.clear();
|
|
|
|
|
// However the canvas has not been updated to the window yet, everything has been processed to
|
|
|
|
|
// an internal buffer, but if we want our buffer to be displayed on the window, we need to call
|
|
|
|
|
// `present`. We need to call this everytime we want to render a new frame on the window.
|
|
|
|
|
canvas.present();
|
|
|
|
|
|
|
|
|
|
// Create a "target" texture so that we can use our Renderer with it later
|
2019-01-11 23:42:23 -05:00
|
|
|
let (square_texture1, square_texture2) = dummy_texture(&mut canvas)?;
|
2017-07-09 03:15:17 +00:00
|
|
|
let mut game = game_of_life::GameOfLife::new();
|
|
|
|
|
|
2019-01-11 23:42:23 -05:00
|
|
|
let mut event_pump = sdl_context.event_pump()?;
|
2020-12-08 17:06:23 +01:00
|
|
|
let mut frame: u32 = 0;
|
2017-07-09 03:15:17 +00:00
|
|
|
'running: loop {
|
|
|
|
|
// get the inputs here
|
|
|
|
|
for event in event_pump.poll_iter() {
|
|
|
|
|
match event {
|
2020-12-08 17:06:23 +01:00
|
|
|
Event::Quit { .. }
|
|
|
|
|
| Event::KeyDown {
|
|
|
|
|
keycode: Some(Keycode::Escape),
|
|
|
|
|
..
|
|
|
|
|
} => break 'running,
|
|
|
|
|
Event::KeyDown {
|
|
|
|
|
keycode: Some(Keycode::Space),
|
|
|
|
|
repeat: false,
|
|
|
|
|
..
|
|
|
|
|
} => {
|
2017-07-09 03:15:17 +00:00
|
|
|
game.toggle_state();
|
2020-12-08 17:06:23 +01:00
|
|
|
}
|
|
|
|
|
Event::MouseButtonDown {
|
|
|
|
|
x,
|
|
|
|
|
y,
|
|
|
|
|
mouse_btn: MouseButton::Left,
|
|
|
|
|
..
|
|
|
|
|
} => {
|
2017-07-09 03:15:17 +00:00
|
|
|
let x = (x as u32) / SQUARE_SIZE;
|
|
|
|
|
let y = (y as u32) / SQUARE_SIZE;
|
|
|
|
|
match game.get_mut(x as i32, y as i32) {
|
2020-12-08 17:06:23 +01:00
|
|
|
Some(square) => {
|
|
|
|
|
*square = !(*square);
|
|
|
|
|
}
|
2019-01-11 23:42:23 -05:00
|
|
|
None => unreachable!(),
|
2017-07-09 03:15:17 +00:00
|
|
|
};
|
2020-12-08 17:06:23 +01:00
|
|
|
}
|
2017-07-09 03:15:17 +00:00
|
|
|
_ => {}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// update the game loop here
|
|
|
|
|
if frame >= 30 {
|
|
|
|
|
game.update();
|
|
|
|
|
frame = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
canvas.set_draw_color(Color::RGB(0, 0, 0));
|
|
|
|
|
canvas.clear();
|
|
|
|
|
for (i, unit) in (&game).into_iter().enumerate() {
|
|
|
|
|
let i = i as u32;
|
|
|
|
|
let square_texture = if frame >= 15 {
|
|
|
|
|
&square_texture1
|
|
|
|
|
} else {
|
|
|
|
|
&square_texture2
|
|
|
|
|
};
|
|
|
|
|
if *unit {
|
2020-12-08 17:06:23 +01:00
|
|
|
canvas.copy(
|
|
|
|
|
&square_texture,
|
|
|
|
|
None,
|
|
|
|
|
Rect::new(
|
|
|
|
|
((i % PLAYGROUND_WIDTH) * SQUARE_SIZE) as i32,
|
|
|
|
|
((i / PLAYGROUND_WIDTH) * SQUARE_SIZE) as i32,
|
|
|
|
|
SQUARE_SIZE,
|
|
|
|
|
SQUARE_SIZE,
|
|
|
|
|
),
|
|
|
|
|
)?;
|
2017-07-09 03:15:17 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
canvas.present();
|
|
|
|
|
if let game_of_life::State::Playing = game.state() {
|
|
|
|
|
frame += 1;
|
|
|
|
|
};
|
|
|
|
|
}
|
2019-01-11 23:42:23 -05:00
|
|
|
|
|
|
|
|
Ok(())
|
2017-07-09 03:15:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(not(feature = "unsafe_textures"))]
|
2020-12-08 17:06:23 +01:00
|
|
|
pub fn main() {}
|