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}