1use baba::prelude::*;
2
3fn main() -> baba::Result {
4    baba::game("SOKOBAN", Soko::update)
5        .viewport(Viewport::new(32 * 7, 32 * 7))
6        .run_with(Soko::new)
7}
8
9const MAP: &str = "\
10####..
11#.O#..
12#..###
13#@P..#
14#..*.#
15#..###
16####..";
17
18struct Entity {
19    texture: TextureSlice,
20    position: Vec2,
21}
22
23impl Entity {
24    fn new(texture: TextureSlice, position: Vec2) -> Self {
25        Self { texture, position }
26    }
27}
28
29struct Soko {
30    player: Entity,
31    objects: Vec<Entity>,
32    targets: Vec<Entity>,
33    walls: Vec<Entity>,
34
35    won: bool,
36}
37
38impl Soko {
39    fn new() -> Self {
40        let tiles = Texture::load("examples/tiles.png");
41
42        let object_slice = tiles.slice(Rect::new(0, 9, 8, 8));
43        let target_slice = tiles.slice(Rect::new(9, 9, 8, 8));
44        let player_slice = tiles.slice(Rect::new(27, 9, 8, 8));
45        let mut wall_slices = Vec::new();
46        for x in 0..4 {
47            wall_slices.push(tiles.slice(Rect::new(x * 9, 0, 8, 8)));
48        }
49
50        let mut player = Entity::new(player_slice, vec2(0., 0.));
51        let mut objects = Vec::new();
52        let mut targets = Vec::new();
53        let mut walls = Vec::new();
54        let map = Vec::from_iter(MAP.split('\n').map(|line| Vec::from_iter(line.chars())));
55
56        for (y, line) in map.iter().enumerate() {
57            for (x, tile) in line.iter().enumerate() {
58                let position = vec2(x as f32, y as f32);
59                match tile {
60                    'O' => targets.push(Entity::new(target_slice.clone(), position)),
61                    '*' => objects.push(Entity::new(object_slice.clone(), position)),
62                    '@' => {
63                        targets.push(Entity::new(target_slice.clone(), position));
64                        objects.push(Entity::new(object_slice.clone(), position));
65                    }
66                    'P' => player.position = position,
67                    '#' => {
68                        let left = map[y].get(x.wrapping_sub(1)) == Some(&'#');
69                        let right = map[y].get(x + 1) == Some(&'#');
70                        let i = left as usize * 2 + right as usize;
71
72                        walls.push(Entity::new(wall_slices[i].clone(), position));
73                    }
74                    _ => continue,
75                }
76            }
77        }
78
79        Self {
80            player,
81            objects,
82            targets,
83            walls,
84            won: false,
85        }
86    }
87
88    fn update(&mut self) {
89        if is_key_pressed(KeyCode::R) {
90            *self = Self::new();
91            return;
92        }
93
94        let mut movement = Vec2::ZERO;
95        if is_key_pressed(KeyCode::A) || is_key_pressed(KeyCode::Left) {
96            movement.x -= 1.;
97        }
98        if is_key_pressed(KeyCode::D) || is_key_pressed(KeyCode::Right) {
99            movement.x += 1.;
100        }
101        if is_key_pressed(KeyCode::W) || is_key_pressed(KeyCode::Up) {
102            movement.y -= 1.;
103        }
104        if is_key_pressed(KeyCode::S) || is_key_pressed(KeyCode::Down) {
105            movement.y += 1.;
106        }
107
108        let mut collided = false;
109        let position = self.player.position + movement;
110        if self.walls.iter().any(|e| e.position == position) {
111            collided = true;
112        } else if let Some(obj) = self.objects.iter().position(|e| e.position == position) {
113            let position = position + movement;
114            if self.solids().any(|e| e.position == position) {
115                collided = true;
116            } else {
117                self.objects[obj].position = position;
118            }
119        }
120
121        if !collided {
122            self.player.position = position;
123        }
124
125        if !self.won
126            && self
127                .objects
128                .iter()
129                .all(|object| self.targets.iter().any(|e| e.position == object.position))
130        {
131            self.won = true;
132            self.walls.clear();
134        }
135
136        self.draw();
137    }
138
139    fn draw(&self) {
140        gfx::clear(Color::from_rgb(0x2f, 0x28, 0x43));
141
142        for entity in self
143            .targets
144            .iter()
145            .chain(self.objects.iter())
146            .chain(self.walls.iter())
147            .chain(std::iter::once(&self.player))
148        {
149            gfx::draw(&entity.texture, (entity.position * 32.0, (4., 4.)));
150        }
151    }
152
153    fn solids(&self) -> impl Iterator<Item = &Entity> {
154        self.walls.iter().chain(self.objects.iter())
155    }
156}