+46 -15src/main.rs
1 1
mod components;
2 2
mod physics;
3 3
mod animator;
4 4
mod keyboard;
5 5
mod renderer;
6 6

7 7
use sdl2::event::Event;
8 8
use sdl2::keyboard::Keycode;
9 9
use sdl2::pixels::Color;
10 10
use sdl2::rect::{Point, Rect};
11 11
// "self" imports the "image" module itself as well as everything else we listed
12 12
use sdl2::image::{self, LoadTexture, InitFlag};
13 13

14 14
use specs::prelude::*;
15 15

16 16
use std::time::Duration;
17 17

18 18
use crate::components::*;
19 19

20 20
pub enum MovementCommand {
21 21
    Stop,
22 22
    Move(Direction),
23 23
}
24 24

25 25
/// Returns the row of the spritesheet corresponding to the given direction
26 26
fn direction_spritesheet_row(direction: Direction) -> i32 {
27 27
    use self::Direction::*;
28 28
    match direction {
29 29
        Up => 3,
30 30
        Down => 0,
31 31
        Left => 1,
32 32
        Right => 2,
33 33
    }
34 34
}
35 35

36 36
/// Create animation frames for the standard character spritesheet
37 37
fn character_animation_frames(spritesheet: usize, top_left_frame: Rect, direction: Direction) -> Vec<Sprite> {
38 38
    // All assumptions about the spritesheets are now encapsulated in this function instead of in
39 39
    // the design of our entire system. We can always replace this function, but replacing the
40 40
    // entire system is harder.
41 41

42 42
    let (frame_width, frame_height) = top_left_frame.size();
43 43
    let y_offset = top_left_frame.y() + frame_height as i32 * direction_spritesheet_row(direction);
44 44

45 45
    let mut frames = Vec::new();
46 46
    for i in 0..3 {
47 47
        frames.push(Sprite {
48 48
            spritesheet,
49 49
            region: Rect::new(
50 50
                top_left_frame.x() + frame_width as i32 * i,
51 51
                y_offset,
52 52
                frame_width,
53 53
                frame_height,
54 54
            ),
55 55
        })
56 56
    }
57 57

58 58
    frames
59 59
}
60 60

  61
fn initialize_player(world: &mut World, player_spritesheet: usize) {
  62
    let player_top_left_frame = Rect::new(0, 0, 26, 36);
  63

  64
    let player_animation = MovementAnimation {
  65
        current_frame: 0,
  66
        up_frames: character_animation_frames(player_spritesheet, player_top_left_frame, Direction::Up),
  67
        down_frames: character_animation_frames(player_spritesheet, player_top_left_frame, Direction::Down),
  68
        left_frames: character_animation_frames(player_spritesheet, player_top_left_frame, Direction::Left),
  69
        right_frames: character_animation_frames(player_spritesheet, player_top_left_frame, Direction::Right),
  70
    };
  71

  72
    world.create_entity()
  73
        .with(KeyboardControlled)
  74
        .with(Position(Point::new(0, 0)))
  75
        .with(Velocity {speed: 0, direction: Direction::Right})
  76
        .with(player_animation.right_frames[0].clone())
  77
        .with(player_animation)
  78
        .build();
  79
}
  80

  81
fn initialize_enemy(world: &mut World, enemy_spritesheet: usize, position: Point) {
  82
    let enemy_top_left_frame = Rect::new(0, 0, 32, 36);
  83

  84
    let enemy_animation = MovementAnimation {
  85
        current_frame: 0,
  86
        up_frames: character_animation_frames(enemy_spritesheet, enemy_top_left_frame, Direction::Up),
  87
        down_frames: character_animation_frames(enemy_spritesheet, enemy_top_left_frame, Direction::Down),
  88
        left_frames: character_animation_frames(enemy_spritesheet, enemy_top_left_frame, Direction::Left),
  89
        right_frames: character_animation_frames(enemy_spritesheet, enemy_top_left_frame, Direction::Right),
  90
    };
  91

  92
    world.create_entity()
  93
        .with(Position(position))
  94
        .with(Velocity {speed: 0, direction: Direction::Right})
  95
        .with(enemy_animation.right_frames[0].clone())
  96
        .with(enemy_animation)
  97
        .build();
  98
}
  99

61 100
fn main() -> Result<(), String> {
62 101
    let sdl_context = sdl2::init()?;
63 102
    let video_subsystem = sdl_context.video()?;
64 103
    // Leading "_" tells Rust that this is an unused variable that we don't care about. It has to
65 104
    // stay unused because if we don't have any variable at all then Rust will treat it as a
66 105
    // temporary value and drop it right away!
67 106
    let _image_context = image::init(InitFlag::PNG | InitFlag::JPG)?;
68 107

69 108
    let window = video_subsystem.window("game tutorial", 800, 600)
70 109
        .position_centered()
71 110
        .build()
72 111
        .expect("could not initialize video subsystem");
73 112

74 113
    let mut canvas = window.into_canvas().build()
75 114
        .expect("could not make a canvas");
76 115
    let texture_creator = canvas.texture_creator();
77 116

78 117
    let mut dispatcher = DispatcherBuilder::new()
79 118
        .with(keyboard::Keyboard, "Keyboard", &[])
80 119
        .with(physics::Physics, "Physics", &["Keyboard"])
81 120
        .with(animator::Animator, "Animator", &["Keyboard"])
82 121
        .build();
83 122

84 123
    let mut world = World::new();
85 124
    dispatcher.setup(&mut world.res);
86 125
    renderer::SystemData::setup(&mut world.res);
87 126

88 127
    // Initialize resource
89 128
    let movement_command: Option<MovementCommand> = None;
90 129
    world.add_resource(movement_command);
91 130

92 131
    let textures = [
93 132
        texture_creator.load_texture("assets/bardo.png")?,
  133
        texture_creator.load_texture("assets/reaper.png")?,
94 134
    ];
95 135
    // First texture in textures array
96 136
    let player_spritesheet = 0;
97  
    let player_top_left_frame = Rect::new(0, 0, 26, 36);
  137
    // Second texture in the textures array
  138
    let enemy_spritesheet = 1;
98 139

99  
    let player_animation = MovementAnimation {
100  
        current_frame: 0,
101  
        up_frames: character_animation_frames(player_spritesheet, player_top_left_frame, Direction::Up),
102  
        down_frames: character_animation_frames(player_spritesheet, player_top_left_frame, Direction::Down),
103  
        left_frames: character_animation_frames(player_spritesheet, player_top_left_frame, Direction::Left),
104  
        right_frames: character_animation_frames(player_spritesheet, player_top_left_frame, Direction::Right),
105  
    };
  140
    initialize_player(&mut world, player_spritesheet);
106 141

107  
    world.create_entity()
108  
        .with(KeyboardControlled)
109  
        .with(Position(Point::new(0, 0)))
110  
        .with(Velocity {speed: 0, direction: Direction::Right})
111  
        .with(player_animation.right_frames[0].clone())
112  
        .with(player_animation)
113  
        .build();
  142
    initialize_enemy(&mut world, enemy_spritesheet, Point::new(-150, -150));
  143
    initialize_enemy(&mut world, enemy_spritesheet, Point::new(150, -190));
  144
    initialize_enemy(&mut world, enemy_spritesheet, Point::new(-150, 170));
114 145

115 146
    let mut event_pump = sdl_context.event_pump()?;
116 147
    let mut i = 0;
117 148
    'running: loop {
118 149
        // None - no change, Some(MovementCommand) - perform movement
119 150
        let mut movement_command = None;
120 151
        // Handle events
121 152
        for event in event_pump.poll_iter() {
122 153
            match event {
123 154
                Event::Quit {..} |
124 155
                Event::KeyDown { keycode: Some(Keycode::Escape), .. } => {
125 156
                    break 'running;
126 157
                },
127 158
                Event::KeyDown { keycode: Some(Keycode::Left), repeat: false, .. } => {
128 159
                    movement_command = Some(MovementCommand::Move(Direction::Left));
129 160
                },
130 161
                Event::KeyDown { keycode: Some(Keycode::Right), repeat: false, .. } => {
131 162
                    movement_command = Some(MovementCommand::Move(Direction::Right));
132 163
                },
133 164
                Event::KeyDown { keycode: Some(Keycode::Up), repeat: false, .. } => {
134 165
                    movement_command = Some(MovementCommand::Move(Direction::Up));
135 166
                },
136 167
                Event::KeyDown { keycode: Some(Keycode::Down), repeat: false, .. } => {
137 168
                    movement_command = Some(MovementCommand::Move(Direction::Down));
138 169
                },
139 170
                Event::KeyUp { keycode: Some(Keycode::Left), repeat: false, .. } |
140 171
                Event::KeyUp { keycode: Some(Keycode::Right), repeat: false, .. } |
141 172
                Event::KeyUp { keycode: Some(Keycode::Up), repeat: false, .. } |
142 173
                Event::KeyUp { keycode: Some(Keycode::Down), repeat: false, .. } => {
143 174
                    movement_command = Some(MovementCommand::Stop);
144 175
                },
145 176
                _ => {}
146 177
            }
147 178
        }
148 179

149 180
        *world.write_resource() = movement_command;
150 181

151 182
        // Update
152 183
        i = (i + 1) % 255;
153 184
        dispatcher.dispatch(&mut world.res);
154 185
        world.maintain();
155 186

156 187
        // Render
157 188
        renderer::render(&mut canvas, Color::RGB(i, 64, 255 - i), &textures, world.system_data())?;
158 189

159 190
        // Time management!
160 191
        ::std::thread::sleep(Duration::new(0, 1_000_000_000u32 / 20));
161 192
    }
162 193

163 194
    Ok(())
164 195
}
+46 -15src/main.rs
1
mod components;
1
mod components;
2
mod physics;
2
mod physics;
3
mod animator;
3
mod animator;
4
mod keyboard;
4
mod keyboard;
5
mod renderer;
5
mod renderer;
6

6

7
use sdl2::event::Event;
7
use sdl2::event::Event;
8
use sdl2::keyboard::Keycode;
8
use sdl2::keyboard::Keycode;
9
use sdl2::pixels::Color;
9
use sdl2::pixels::Color;
10
use sdl2::rect::{Point, Rect};
10
use sdl2::rect::{Point, Rect};
11
// "self" imports the "image" module itself as well as everything else we listed
11
// "self" imports the "image" module itself as well as everything else we listed
12
use sdl2::image::{self, LoadTexture, InitFlag};
12
use sdl2::image::{self, LoadTexture, InitFlag};
13

13

14
use specs::prelude::*;
14
use specs::prelude::*;
15

15

16
use std::time::Duration;
16
use std::time::Duration;
17

17

18
use crate::components::*;
18
use crate::components::*;
19

19

20
pub enum MovementCommand {
20
pub enum MovementCommand {
21
    Stop,
21
    Stop,
22
    Move(Direction),
22
    Move(Direction),
23
}
23
}
24

24

25
/// Returns the row of the spritesheet corresponding to the given direction
25
/// Returns the row of the spritesheet corresponding to the given direction
26
fn direction_spritesheet_row(direction: Direction) -> i32 {
26
fn direction_spritesheet_row(direction: Direction) -> i32 {
27
    use self::Direction::*;
27
    use self::Direction::*;
28
    match direction {
28
    match direction {
29
        Up => 3,
29
        Up => 3,
30
        Down => 0,
30
        Down => 0,
31
        Left => 1,
31
        Left => 1,
32
        Right => 2,
32
        Right => 2,
33
    }
33
    }
34
}
34
}
35

35

36
/// Create animation frames for the standard character spritesheet
36
/// Create animation frames for the standard character spritesheet
37
fn character_animation_frames(spritesheet: usize, top_left_frame: Rect, direction: Direction) -> Vec<Sprite> {
37
fn character_animation_frames(spritesheet: usize, top_left_frame: Rect, direction: Direction) -> Vec<Sprite> {
38
    // All assumptions about the spritesheets are now encapsulated in this function instead of in
38
    // All assumptions about the spritesheets are now encapsulated in this function instead of in
39
    // the design of our entire system. We can always replace this function, but replacing the
39
    // the design of our entire system. We can always replace this function, but replacing the
40
    // entire system is harder.
40
    // entire system is harder.
41

41

42
    let (frame_width, frame_height) = top_left_frame.size();
42
    let (frame_width, frame_height) = top_left_frame.size();
43
    let y_offset = top_left_frame.y() + frame_height as i32 * direction_spritesheet_row(direction);
43
    let y_offset = top_left_frame.y() + frame_height as i32 * direction_spritesheet_row(direction);
44

44

45
    let mut frames = Vec::new();
45
    let mut frames = Vec::new();
46
    for i in 0..3 {
46
    for i in 0..3 {
47
        frames.push(Sprite {
47
        frames.push(Sprite {
48
            spritesheet,
48
            spritesheet,
49
            region: Rect::new(
49
            region: Rect::new(
50
                top_left_frame.x() + frame_width as i32 * i,
50
                top_left_frame.x() + frame_width as i32 * i,
51
                y_offset,
51
                y_offset,
52
                frame_width,
52
                frame_width,
53
                frame_height,
53
                frame_height,
54
            ),
54
            ),
55
        })
55
        })
56
    }
56
    }
57

57

58
    frames
58
    frames
59
}
59
}
60

60

    61
fn initialize_player(world: &mut World, player_spritesheet: usize) {
    62
    let player_top_left_frame = Rect::new(0, 0, 26, 36);
    63

    64
    let player_animation = MovementAnimation {
    65
        current_frame: 0,
    66
        up_frames: character_animation_frames(player_spritesheet, player_top_left_frame, Direction::Up),
    67
        down_frames: character_animation_frames(player_spritesheet, player_top_left_frame, Direction::Down),
    68
        left_frames: character_animation_frames(player_spritesheet, player_top_left_frame, Direction::Left),
    69
        right_frames: character_animation_frames(player_spritesheet, player_top_left_frame, Direction::Right),
    70
    };
    71

    72
    world.create_entity()
    73
        .with(KeyboardControlled)
    74
        .with(Position(Point::new(0, 0)))
    75
        .with(Velocity {speed: 0, direction: Direction::Right})
    76
        .with(player_animation.right_frames[0].clone())
    77
        .with(player_animation)
    78
        .build();
    79
}
    80

    81
fn initialize_enemy(world: &mut World, enemy_spritesheet: usize, position: Point) {
    82
    let enemy_top_left_frame = Rect::new(0, 0, 32, 36);
    83

    84
    let enemy_animation = MovementAnimation {
    85
        current_frame: 0,
    86
        up_frames: character_animation_frames(enemy_spritesheet, enemy_top_left_frame, Direction::Up),
    87
        down_frames: character_animation_frames(enemy_spritesheet, enemy_top_left_frame, Direction::Down),
    88
        left_frames: character_animation_frames(enemy_spritesheet, enemy_top_left_frame, Direction::Left),
    89
        right_frames: character_animation_frames(enemy_spritesheet, enemy_top_left_frame, Direction::Right),
    90
    };
    91

    92
    world.create_entity()
    93
        .with(Position(position))
    94
        .with(Velocity {speed: 0, direction: Direction::Right})
    95
        .with(enemy_animation.right_frames[0].clone())
    96
        .with(enemy_animation)
    97
        .build();
    98
}
    99

61
fn main() -> Result<(), String> {
100
fn main() -> Result<(), String> {
62
    let sdl_context = sdl2::init()?;
101
    let sdl_context = sdl2::init()?;
63
    let video_subsystem = sdl_context.video()?;
102
    let video_subsystem = sdl_context.video()?;
64
    // Leading "_" tells Rust that this is an unused variable that we don't care about. It has to
103
    // Leading "_" tells Rust that this is an unused variable that we don't care about. It has to
65
    // stay unused because if we don't have any variable at all then Rust will treat it as a
104
    // stay unused because if we don't have any variable at all then Rust will treat it as a
66
    // temporary value and drop it right away!
105
    // temporary value and drop it right away!
67
    let _image_context = image::init(InitFlag::PNG | InitFlag::JPG)?;
106
    let _image_context = image::init(InitFlag::PNG | InitFlag::JPG)?;
68

107

69
    let window = video_subsystem.window("game tutorial", 800, 600)
108
    let window = video_subsystem.window("game tutorial", 800, 600)
70
        .position_centered()
109
        .position_centered()
71
        .build()
110
        .build()
72
        .expect("could not initialize video subsystem");
111
        .expect("could not initialize video subsystem");
73

112

74
    let mut canvas = window.into_canvas().build()
113
    let mut canvas = window.into_canvas().build()
75
        .expect("could not make a canvas");
114
        .expect("could not make a canvas");
76
    let texture_creator = canvas.texture_creator();
115
    let texture_creator = canvas.texture_creator();
77

116

78
    let mut dispatcher = DispatcherBuilder::new()
117
    let mut dispatcher = DispatcherBuilder::new()
79
        .with(keyboard::Keyboard, "Keyboard", &[])
118
        .with(keyboard::Keyboard, "Keyboard", &[])
80
        .with(physics::Physics, "Physics", &["Keyboard"])
119
        .with(physics::Physics, "Physics", &["Keyboard"])
81
        .with(animator::Animator, "Animator", &["Keyboard"])
120
        .with(animator::Animator, "Animator", &["Keyboard"])
82
        .build();
121
        .build();
83

122

84
    let mut world = World::new();
123
    let mut world = World::new();
85
    dispatcher.setup(&mut world.res);
124
    dispatcher.setup(&mut world.res);
86
    renderer::SystemData::setup(&mut world.res);
125
    renderer::SystemData::setup(&mut world.res);
87

126

88
    // Initialize resource
127
    // Initialize resource
89
    let movement_command: Option<MovementCommand> = None;
128
    let movement_command: Option<MovementCommand> = None;
90
    world.add_resource(movement_command);
129
    world.add_resource(movement_command);
91

130

92
    let textures = [
131
    let textures = [
93
        texture_creator.load_texture("assets/bardo.png")?,
132
        texture_creator.load_texture("assets/bardo.png")?,
    133
        texture_creator.load_texture("assets/reaper.png")?,
94
    ];
134
    ];
95
    // First texture in textures array
135
    // First texture in textures array
96
    let player_spritesheet = 0;
136
    let player_spritesheet = 0;
97
    let player_top_left_frame = Rect::new(0, 0, 26, 36);
137
    // Second texture in the textures array
    138
    let enemy_spritesheet = 1;
98

139

99
    let player_animation = MovementAnimation {
140
    initialize_player(&mut world, player_spritesheet);
100
        current_frame: 0,
   
101
        up_frames: character_animation_frames(player_spritesheet, player_top_left_frame, Direction::Up),
   
102
        down_frames: character_animation_frames(player_spritesheet, player_top_left_frame, Direction::Down),
   
103
        left_frames: character_animation_frames(player_spritesheet, player_top_left_frame, Direction::Left),
   
104
        right_frames: character_animation_frames(player_spritesheet, player_top_left_frame, Direction::Right),
   
105
    };
   
106

141

107
    world.create_entity()
142
    initialize_enemy(&mut world, enemy_spritesheet, Point::new(-150, -150));
108
        .with(KeyboardControlled)
143
    initialize_enemy(&mut world, enemy_spritesheet, Point::new(150, -190));
109
        .with(Position(Point::new(0, 0)))
144
    initialize_enemy(&mut world, enemy_spritesheet, Point::new(-150, 170));
110
        .with(Velocity {speed: 0, direction: Direction::Right})
   
111
        .with(player_animation.right_frames[0].clone())
   
112
        .with(player_animation)
   
113
        .build();
   
114

145

115
    let mut event_pump = sdl_context.event_pump()?;
146
    let mut event_pump = sdl_context.event_pump()?;
116
    let mut i = 0;
147
    let mut i = 0;
117
    'running: loop {
148
    'running: loop {
118
        // None - no change, Some(MovementCommand) - perform movement
149
        // None - no change, Some(MovementCommand) - perform movement
119
        let mut movement_command = None;
150
        let mut movement_command = None;
120
        // Handle events
151
        // Handle events
121
        for event in event_pump.poll_iter() {
152
        for event in event_pump.poll_iter() {
122
            match event {
153
            match event {
123
                Event::Quit {..} |
154
                Event::Quit {..} |
124
                Event::KeyDown { keycode: Some(Keycode::Escape), .. } => {
155
                Event::KeyDown { keycode: Some(Keycode::Escape), .. } => {
125
                    break 'running;
156
                    break 'running;
126
                },
157
                },
127
                Event::KeyDown { keycode: Some(Keycode::Left), repeat: false, .. } => {
158
                Event::KeyDown { keycode: Some(Keycode::Left), repeat: false, .. } => {
128
                    movement_command = Some(MovementCommand::Move(Direction::Left));
159
                    movement_command = Some(MovementCommand::Move(Direction::Left));
129
                },
160
                },
130
                Event::KeyDown { keycode: Some(Keycode::Right), repeat: false, .. } => {
161
                Event::KeyDown { keycode: Some(Keycode::Right), repeat: false, .. } => {
131
                    movement_command = Some(MovementCommand::Move(Direction::Right));
162
                    movement_command = Some(MovementCommand::Move(Direction::Right));
132
                },
163
                },
133
                Event::KeyDown { keycode: Some(Keycode::Up), repeat: false, .. } => {
164
                Event::KeyDown { keycode: Some(Keycode::Up), repeat: false, .. } => {
134
                    movement_command = Some(MovementCommand::Move(Direction::Up));
165
                    movement_command = Some(MovementCommand::Move(Direction::Up));
135
                },
166
                },
136
                Event::KeyDown { keycode: Some(Keycode::Down), repeat: false, .. } => {
167
                Event::KeyDown { keycode: Some(Keycode::Down), repeat: false, .. } => {
137
                    movement_command = Some(MovementCommand::Move(Direction::Down));
168
                    movement_command = Some(MovementCommand::Move(Direction::Down));
138
                },
169
                },
139
                Event::KeyUp { keycode: Some(Keycode::Left), repeat: false, .. } |
170
                Event::KeyUp { keycode: Some(Keycode::Left), repeat: false, .. } |
140
                Event::KeyUp { keycode: Some(Keycode::Right), repeat: false, .. } |
171
                Event::KeyUp { keycode: Some(Keycode::Right), repeat: false, .. } |
141
                Event::KeyUp { keycode: Some(Keycode::Up), repeat: false, .. } |
172
                Event::KeyUp { keycode: Some(Keycode::Up), repeat: false, .. } |
142
                Event::KeyUp { keycode: Some(Keycode::Down), repeat: false, .. } => {
173
                Event::KeyUp { keycode: Some(Keycode::Down), repeat: false, .. } => {
143
                    movement_command = Some(MovementCommand::Stop);
174
                    movement_command = Some(MovementCommand::Stop);
144
                },
175
                },
145
                _ => {}
176
                _ => {}
146
            }
177
            }
147
        }
178
        }
148

179

149
        *world.write_resource() = movement_command;
180
        *world.write_resource() = movement_command;
150

181

151
        // Update
182
        // Update
152
        i = (i + 1) % 255;
183
        i = (i + 1) % 255;
153
        dispatcher.dispatch(&mut world.res);
184
        dispatcher.dispatch(&mut world.res);
154
        world.maintain();
185
        world.maintain();
155

186

156
        // Render
187
        // Render
157
        renderer::render(&mut canvas, Color::RGB(i, 64, 255 - i), &textures, world.system_data())?;
188
        renderer::render(&mut canvas, Color::RGB(i, 64, 255 - i), &textures, world.system_data())?;
158

189

159
        // Time management!
190
        // Time management!
160
        ::std::thread::sleep(Duration::new(0, 1_000_000_000u32 / 20));
191
        ::std::thread::sleep(Duration::new(0, 1_000_000_000u32 / 20));
161
    }
192
    }
162

193

163
    Ok(())
194
    Ok(())
164
}
195
}
/target
**/*.rs.bk
[package]
name = "game-tutorial"
version = "0.1.0"
authors = ["Sunjay Varma <[email protected]>"]
edition = "2018"

[dependencies]
specs = "0.14"
specs-derive = "0.4"

[dependencies.sdl2]
version = "0.32.1"
default-features = false
features = ["image"]
use specs::prelude::*;

use crate::components::*;

pub struct Animator;

impl<'a> System<'a> for Animator {
    type SystemData = (
        WriteStorage<'a, MovementAnimation>,
        WriteStorage<'a, Sprite>,
        ReadStorage<'a, Velocity>,
    );

    fn run(&mut self, mut data: Self::SystemData) {
        use self::Direction::*;
        //TODO: This code can be made nicer and more idiomatic using more pattern matching.
        // Look up "rust irrefutable patterns" and use them here.
        for (anim, sprite, vel) in (&mut data.0, &mut data.1, &data.2).join() {
            if vel.speed == 0 {
                continue;
            }

            let frames = match vel.direction {
                Left => &anim.left_frames,
                Right => &anim.right_frames,
                Up => &anim.up_frames,
                Down => &anim.down_frames,
            };

            anim.current_frame = (anim.current_frame + 1) % frames.len();
            *sprite = frames[anim.current_frame].clone();
        }
    }
}
use specs::prelude::*;
use specs_derive::Component;
use sdl2::rect::{Point, Rect};

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Direction {
    Up,
    Down,
    Left,
    Right,
}

#[derive(Component, Debug, Default)]
#[storage(NullStorage)]
pub struct KeyboardControlled;

/// The current position of a given entity
#[derive(Component, Debug)]
#[storage(VecStorage)]
pub struct Position(pub Point);

/// The current speed and direction of a given entity
#[derive(Component, Debug)]
#[storage(VecStorage)]
pub struct Velocity {
    pub speed: i32,
    pub direction: Direction,
}

#[derive(Component, Debug, Clone)]
#[storage(VecStorage)]
pub struct Sprite {
    /// The specific spritesheet to render from
    pub spritesheet: usize,
    /// The current region of the spritesheet to be rendered
    pub region: Rect,
}

#[derive(Component, Debug)]
#[storage(VecStorage)]
pub struct MovementAnimation {
    // The current frame in the animation of the direction this entity is moving in
    pub current_frame: usize,
    pub up_frames: Vec<Sprite>,
    pub down_frames: Vec<Sprite>,
    pub left_frames: Vec<Sprite>,
    pub right_frames: Vec<Sprite>,
}
use specs::prelude::*;

use crate::components::*;

use super::MovementCommand;

const PLAYER_MOVEMENT_SPEED: i32 = 20;

pub struct Keyboard;

impl<'a> System<'a> for Keyboard {
    type SystemData = (
        ReadExpect<'a, Option<MovementCommand>>,
        ReadStorage<'a, KeyboardControlled>,
        WriteStorage<'a, Velocity>,
    );

    fn run(&mut self, mut data: Self::SystemData) {
        //TODO: This code can be made nicer and more idiomatic using more pattern matching.
        // Look up "rust irrefutable patterns" and use them here.
        let movement_command = match &*data.0 {
            Some(movement_command) => movement_command,
            None => return, // no change
        };

        for (_, vel) in (&data.1, &mut data.2).join() {
            match movement_command {
                &MovementCommand::Move(direction) => {
                    vel.speed = PLAYER_MOVEMENT_SPEED;
                    vel.direction = direction;
                },
                MovementCommand::Stop => vel.speed = 0,
            }
        }
    }
}
mod components;
mod physics;
mod animator;
mod keyboard;
mod renderer;

use sdl2::event::Event;
use sdl2::keyboard::Keycode;
use sdl2::pixels::Color;
use sdl2::rect::{Point, Rect};
// "self" imports the "image" module itself as well as everything else we listed
use sdl2::image::{self, LoadTexture, InitFlag};

use specs::prelude::*;

use std::time::Duration;

use crate::components::*;

pub enum MovementCommand {
    Stop,
    Move(Direction),
}

/// Returns the row of the spritesheet corresponding to the given direction
fn direction_spritesheet_row(direction: Direction) -> i32 {
    use self::Direction::*;
    match direction {
        Up => 3,
        Down => 0,
        Left => 1,
        Right => 2,
    }
}

/// Create animation frames for the standard character spritesheet
fn character_animation_frames(spritesheet: usize, top_left_frame: Rect, direction: Direction) -> Vec<Sprite> {
    // All assumptions about the spritesheets are now encapsulated in this function instead of in
    // the design of our entire system. We can always replace this function, but replacing the
    // entire system is harder.

    let (frame_width, frame_height) = top_left_frame.size();
    let y_offset = top_left_frame.y() + frame_height as i32 * direction_spritesheet_row(direction);

    let mut frames = Vec::new();
    for i in 0..3 {
        frames.push(Sprite {
            spritesheet,
            region: Rect::new(
                top_left_frame.x() + frame_width as i32 * i,
                y_offset,
                frame_width,
                frame_height,
            ),
        })
    }

    frames
}

fn initialize_player(world: &mut World, player_spritesheet: usize) {
    let player_top_left_frame = Rect::new(0, 0, 26, 36);

    let player_animation = MovementAnimation {
        current_frame: 0,
        up_frames: character_animation_frames(player_spritesheet, player_top_left_frame, Direction::Up),
        down_frames: character_animation_frames(player_spritesheet, player_top_left_frame, Direction::Down),
        left_frames: character_animation_frames(player_spritesheet, player_top_left_frame, Direction::Left),
        right_frames: character_animation_frames(player_spritesheet, player_top_left_frame, Direction::Right),
    };

    world.create_entity()
        .with(KeyboardControlled)
        .with(Position(Point::new(0, 0)))
        .with(Velocity {speed: 0, direction: Direction::Right})
        .with(player_animation.right_frames[0].clone())
        .with(player_animation)
        .build();
}

fn initialize_enemy(world: &mut World, enemy_spritesheet: usize, position: Point) {
    let enemy_top_left_frame = Rect::new(0, 0, 32, 36);

    let enemy_animation = MovementAnimation {
        current_frame: 0,
        up_frames: character_animation_frames(enemy_spritesheet, enemy_top_left_frame, Direction::Up),
        down_frames: character_animation_frames(enemy_spritesheet, enemy_top_left_frame, Direction::Down),
        left_frames: character_animation_frames(enemy_spritesheet, enemy_top_left_frame, Direction::Left),
        right_frames: character_animation_frames(enemy_spritesheet, enemy_top_left_frame, Direction::Right),
    };

    world.create_entity()
        .with(Position(position))
        .with(Velocity {speed: 0, direction: Direction::Right})
        .with(enemy_animation.right_frames[0].clone())
        .with(enemy_animation)
        .build();
}

fn main() -> Result<(), String> {
    let sdl_context = sdl2::init()?;
    let video_subsystem = sdl_context.video()?;
    // Leading "_" tells Rust that this is an unused variable that we don't care about. It has to
    // stay unused because if we don't have any variable at all then Rust will treat it as a
    // temporary value and drop it right away!
    let _image_context = image::init(InitFlag::PNG | InitFlag::JPG)?;

    let window = video_subsystem.window("game tutorial", 800, 600)
        .position_centered()
        .build()
        .expect("could not initialize video subsystem");

    let mut canvas = window.into_canvas().build()
        .expect("could not make a canvas");
    let texture_creator = canvas.texture_creator();

    let mut dispatcher = DispatcherBuilder::new()
        .with(keyboard::Keyboard, "Keyboard", &[])
        .with(physics::Physics, "Physics", &["Keyboard"])
        .with(animator::Animator, "Animator", &["Keyboard"])
        .build();

    let mut world = World::new();
    dispatcher.setup(&mut world.res);
    renderer::SystemData::setup(&mut world.res);

    // Initialize resource
    let movement_command: Option<MovementCommand> = None;
    world.add_resource(movement_command);

    let textures = [
        texture_creator.load_texture("assets/bardo.png")?,
        texture_creator.load_texture("assets/reaper.png")?,
    ];
    // First texture in textures array
    let player_spritesheet = 0;
    // Second texture in the textures array
    let enemy_spritesheet = 1;

    initialize_player(&mut world, player_spritesheet);

    initialize_enemy(&mut world, enemy_spritesheet, Point::new(-150, -150));
    initialize_enemy(&mut world, enemy_spritesheet, Point::new(150, -190));
    initialize_enemy(&mut world, enemy_spritesheet, Point::new(-150, 170));

    let mut event_pump = sdl_context.event_pump()?;
    let mut i = 0;
    'running: loop {
        // None - no change, Some(MovementCommand) - perform movement
        let mut movement_command = None;
        // Handle events
        for event in event_pump.poll_iter() {
            match event {
                Event::Quit {..} |
                Event::KeyDown { keycode: Some(Keycode::Escape), .. } => {
                    break 'running;
                },
                Event::KeyDown { keycode: Some(Keycode::Left), repeat: false, .. } => {
                    movement_command = Some(MovementCommand::Move(Direction::Left));
                },
                Event::KeyDown { keycode: Some(Keycode::Right), repeat: false, .. } => {
                    movement_command = Some(MovementCommand::Move(Direction::Right));
                },
                Event::KeyDown { keycode: Some(Keycode::Up), repeat: false, .. } => {
                    movement_command = Some(MovementCommand::Move(Direction::Up));
                },
                Event::KeyDown { keycode: Some(Keycode::Down), repeat: false, .. } => {
                    movement_command = Some(MovementCommand::Move(Direction::Down));
                },
                Event::KeyUp { keycode: Some(Keycode::Left), repeat: false, .. } |
                Event::KeyUp { keycode: Some(Keycode::Right), repeat: false, .. } |
                Event::KeyUp { keycode: Some(Keycode::Up), repeat: false, .. } |
                Event::KeyUp { keycode: Some(Keycode::Down), repeat: false, .. } => {
                    movement_command = Some(MovementCommand::Stop);
                },
                _ => {}
            }
        }

        *world.write_resource() = movement_command;

        // Update
        i = (i + 1) % 255;
        dispatcher.dispatch(&mut world.res);
        world.maintain();

        // Render
        renderer::render(&mut canvas, Color::RGB(i, 64, 255 - i), &textures, world.system_data())?;

        // Time management!
        ::std::thread::sleep(Duration::new(0, 1_000_000_000u32 / 20));
    }

    Ok(())
}
use specs::prelude::*;

use crate::components::*;

pub struct Physics;

impl<'a> System<'a> for Physics {
    type SystemData = (WriteStorage<'a, Position>, ReadStorage<'a, Velocity>);

    fn run(&mut self, mut data: Self::SystemData) {
        use self::Direction::*;
        //TODO: This code can be made nicer and more idiomatic using more pattern matching.
        // Look up "rust irrefutable patterns" and use them here.
        for (pos, vel) in (&mut data.0, &data.1).join() {
            match vel.direction {
                Left => {
                    pos.0 = pos.0.offset(-vel.speed, 0);
                },
                Right => {
                    pos.0 = pos.0.offset(vel.speed, 0);
                },
                Up => {
                    pos.0 = pos.0.offset(0, -vel.speed);
                },
                Down => {
                    pos.0 = pos.0.offset(0, vel.speed);
                },
            }
        }
    }
}
use specs::prelude::*;
use sdl2::rect::{Point, Rect};
use sdl2::pixels::Color;
use sdl2::render::{WindowCanvas, Texture};

use crate::components::*;

// Type alias for the data needed by the renderer
pub type SystemData<'a> = (
    ReadStorage<'a, Position>,
    ReadStorage<'a, Sprite>,
);

pub fn render(
    canvas: &mut WindowCanvas,
    background: Color,
    textures: &[Texture],
    data: SystemData,
) -> Result<(), String> {
    canvas.set_draw_color(background);
    canvas.clear();

    let (width, height) = canvas.output_size()?;

    for (pos, sprite) in (&data.0, &data.1).join() {
        let current_frame = sprite.region;

        // Treat the center of the screen as the (0, 0) coordinate
        let screen_position = pos.0 + Point::new(width as i32 / 2, height as i32 / 2);
        let screen_rect = Rect::from_center(screen_position, current_frame.width(), current_frame.height());
        canvas.copy(&textures[sprite.spritesheet], current_frame, screen_rect)?;
    }

    canvas.present();

    Ok(())
}