Skip to main content

blademaster/systems/
term.rs

1use std::{
2    io::{stdout, Write},
3    ops::Deref,
4    process,
5};
6
7use legion::prelude::*;
8
9use crossterm::{
10    event::{self, Event, KeyCode},
11    execute,
12    terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
13};
14
15use tui::{
16    backend::CrosstermBackend,
17    layout::{Constraint, Corner, Direction, Layout},
18    style::Color,
19    widgets::{canvas::Canvas, Block, Borders, List, Widget},
20    Terminal,
21};
22
23use crate::{CellAccess, CellKind, CellVisibility, GameCell, GameEvents, Inventory, Player};
24
25pub struct TuiSystem;
26
27impl TuiSystem {
28    pub fn run(world: &mut World) {
29        let read_query = <(Read<GameCell>,)>::query();
30        let write_query = <(legion::query::Write<GameCell>,)>::query();
31
32        enable_raw_mode().unwrap();
33
34        let mut stdout = stdout();
35        execute!(stdout, EnterAlternateScreen).unwrap();
36
37        let mut terminal = Terminal::new(CrosstermBackend::new(stdout)).unwrap();
38        terminal.hide_cursor().unwrap();
39        terminal.clear().unwrap();
40
41        let term_width = terminal.size().unwrap().width;
42        let term_height = terminal.size().unwrap().height;
43        let canvas_width = term_width - 25;
44        let canvas_height = term_height - 8;
45
46        let player = Player::new(
47            (canvas_width as f64 / 2.0).round(),
48            (canvas_height as f64 / 2.0).round(),
49        );
50
51        let mut game_events = GameEvents::new();
52
53        let mut inventory = Inventory::new();
54
55        loop {
56            if let Event::Key(key) = event::read().unwrap() {
57                match key.code {
58                    KeyCode::Up => {
59                        let mut collided = false;
60                        for (gamecell,) in read_query.iter_immutable(world) {
61                            if gamecell.access() == CellAccess::Impassable
62                                && (player.x() - gamecell.x() as f64).abs() < 1.0
63                                && (player.y() - (gamecell.y() - 1) as f64).abs() < 1.0
64                            {
65                                game_events.post_event(
66                                    format!(
67                                        "You ran into the {}.{space:>width$}",
68                                        gamecell.name(),
69                                        space = " ",
70                                        width = canvas_width as usize / 2,
71                                    ),
72                                    Color::Blue,
73                                );
74                                collided = true;
75                                break;
76                            }
77                        }
78                        if !collided {
79                            write_query.par_for_each(world, {
80                                |(mut gamecell,)| {
81                                    if gamecell.inside(
82                                        player.x() as u16 - 5,
83                                        player.y() as u16 - 5,
84                                        player.x() as u16 + 5,
85                                        player.y() as u16 + 5,
86                                    ) {
87                                        gamecell.set_visible(CellVisibility::Visible);
88                                    } else if gamecell.visible() == CellVisibility::Visible {
89                                        gamecell.set_visible(CellVisibility::Dark);
90                                    }
91                                    gamecell.move_up();
92                                }
93                            });
94                        }
95                    }
96                    KeyCode::Down => {
97                        let mut collided = false;
98                        for (gamecell,) in read_query.iter_immutable(world) {
99                            if gamecell.access() == CellAccess::Impassable
100                                && (player.x() - gamecell.x() as f64).abs() < 1.0
101                                && (player.y() - (gamecell.y() + 1) as f64).abs() < 1.0
102                            {
103                                game_events.post_event(
104                                    format!(
105                                        "You ran into the {}.{space:>width$}",
106                                        gamecell.name(),
107                                        space = " ",
108                                        width = canvas_width as usize / 2,
109                                    ),
110                                    Color::Blue,
111                                );
112                                collided = true;
113                                break;
114                            }
115                        }
116                        if !collided {
117                            write_query.par_for_each(world, {
118                                |(mut gamecell,)| {
119                                    if gamecell.inside(
120                                        player.x() as u16 - 5,
121                                        player.y() as u16 - 5,
122                                        player.x() as u16 + 5,
123                                        player.y() as u16 + 5,
124                                    ) {
125                                        gamecell.set_visible(CellVisibility::Visible);
126                                    } else if gamecell.visible() == CellVisibility::Visible {
127                                        gamecell.set_visible(CellVisibility::Dark);
128                                    }
129                                    gamecell.move_down();
130                                }
131                            });
132                        }
133                    }
134                    KeyCode::Left => {
135                        let mut collided = false;
136                        for (gamecell,) in read_query.iter_immutable(world) {
137                            if gamecell.access() == CellAccess::Impassable
138                                && (player.x() - (gamecell.x() + 1) as f64).abs() < 1.0
139                                && (player.y() - gamecell.y() as f64).abs() < 1.0
140                            {
141                                game_events.post_event(
142                                    format!(
143                                        "You ran into the {}.{space:>width$}",
144                                        gamecell.name(),
145                                        space = " ",
146                                        width = canvas_width as usize / 2,
147                                    ),
148                                    Color::Blue,
149                                );
150                                collided = true;
151                                break;
152                            }
153                        }
154                        if !collided {
155                            write_query.par_for_each(world, {
156                                |(mut gamecell,)| {
157                                    if gamecell.inside(
158                                        player.x() as u16 - 5,
159                                        player.y() as u16 - 5,
160                                        player.x() as u16 + 5,
161                                        player.y() as u16 + 5,
162                                    ) {
163                                        gamecell.set_visible(CellVisibility::Visible);
164                                    } else if gamecell.visible() == CellVisibility::Visible {
165                                        gamecell.set_visible(CellVisibility::Dark);
166                                    }
167                                    gamecell.move_right();
168                                }
169                            });
170                        }
171                    }
172                    KeyCode::Right => {
173                        let mut collided = false;
174                        for (gamecell,) in read_query.iter_immutable(world) {
175                            if gamecell.access() == CellAccess::Impassable
176                                && (player.x() - (gamecell.x() - 1) as f64).abs() < 1.0
177                                && (player.y() - gamecell.y() as f64).abs() < 1.0
178                            {
179                                game_events.post_event(
180                                    format!(
181                                        "You ran into the {}.{space:>width$}",
182                                        gamecell.name(),
183                                        space = " ",
184                                        width = canvas_width as usize / 2,
185                                    ),
186                                    Color::Blue,
187                                );
188                                collided = true;
189                                break;
190                            }
191                        }
192                        if !collided {
193                            write_query.par_for_each(world, {
194                                |(mut gamecell,)| {
195                                    if gamecell.inside(
196                                        player.x() as u16 - 5,
197                                        player.y() as u16 - 5,
198                                        player.x() as u16 + 5,
199                                        player.y() as u16 + 5,
200                                    ) {
201                                        gamecell.set_visible(CellVisibility::Visible);
202                                    } else if gamecell.visible() == CellVisibility::Visible {
203                                        gamecell.set_visible(CellVisibility::Dark);
204                                    }
205                                    gamecell.move_left();
206                                }
207                            });
208                        }
209                    }
210                    KeyCode::Char('q') => {
211                        terminal.clear().unwrap();
212                        terminal.show_cursor().unwrap();
213                        disable_raw_mode().unwrap();
214                        execute!(terminal.backend_mut(), LeaveAlternateScreen).unwrap();
215                        process::exit(1);
216                    }
217                    _ => (),
218                }
219            }
220
221            let read_query = <(Read<GameCell>,)>::query();
222
223            let mut taken = None;
224            for (entity, (gamecell,)) in read_query.iter_entities_immutable(world) {
225                if gamecell.access() == CellAccess::Takeable
226                    && gamecell.inside(1, 1, term_width, term_height)
227                    && (player.x() - gamecell.x() as f64).abs() < 1.0
228                    && (player.y() - gamecell.y() as f64).abs() < 1.0
229                {
230                    game_events.post_event(
231                        format!(
232                            "You now have the {}.{space:>width$}",
233                            gamecell.name(),
234                            space = " ",
235                            width = canvas_width as usize / 2,
236                        ),
237                        Color::Green,
238                    );
239                    inventory.take(gamecell.deref().clone());
240                    taken = Some(entity);
241                    break;
242                }
243            }
244            if let Some(entity) = taken {
245                world.delete(entity);
246            }
247
248            terminal
249                .draw(|mut f| {
250                    let chunks = Layout::default()
251                        .margin(0)
252                        .direction(Direction::Vertical)
253                        .constraints(
254                            [
255                                Constraint::Length(canvas_height + 1),
256                                Constraint::Length(term_height - canvas_height - 2),
257                            ]
258                            .as_ref(),
259                        )
260                        .split(f.size());
261                    let top_chunks = Layout::default()
262                        .margin(0)
263                        .direction(Direction::Horizontal)
264                        .constraints(
265                            [
266                                Constraint::Length(canvas_width + 1),
267                                Constraint::Length(term_width - canvas_width - 2),
268                            ]
269                            .as_ref(),
270                        )
271                        .split(chunks[0]);
272                    let bottom_chunks = Layout::default()
273                        .margin(0)
274                        .direction(Direction::Horizontal)
275                        .constraints(
276                            [Constraint::Percentage(70), Constraint::Percentage(30)].as_ref(),
277                        )
278                        .split(chunks[1]);
279                    Canvas::default()
280                        .block(Block::default().borders(Borders::ALL).title("Game"))
281                        .paint(|ctx| {
282                            for (gamecell,) in read_query.iter_immutable(world) {
283                                if gamecell.visible() != CellVisibility::Unvisited
284                                    && gamecell.inside(1, 1, term_width, term_height)
285                                {
286                                    let symbol = match gamecell.kind() {
287                                        CellKind::SoftArmor => "(",
288                                        CellKind::HardArmor => "[",
289                                        CellKind::BluntWeapon => "\\",
290                                        CellKind::EdgedWeapon => "|",
291                                        CellKind::PointedWeapon => "/",
292                                        CellKind::RangedWeapon => "}",
293                                        CellKind::ClosedDoor => "+",
294                                        CellKind::OpenedDoor => "'",
295                                        CellKind::Wall => "#",
296                                        CellKind::Floor => ".",
297                                    };
298                                    if gamecell.visible() == CellVisibility::Visible {
299                                        ctx.print(
300                                            gamecell.x() as f64,
301                                            gamecell.y() as f64,
302                                            symbol,
303                                            gamecell.color(),
304                                        );
305                                    } else {
306                                        ctx.print(
307                                            gamecell.x() as f64,
308                                            gamecell.y() as f64,
309                                            symbol,
310                                            Color::DarkGray,
311                                        );
312                                    }
313                                }
314                            }
315                            ctx.print(player.x(), player.y(), "@", Color::Rgb(0, 255, 0));
316                        })
317                        .x_bounds([2.0, canvas_width as f64])
318                        .y_bounds([2.0, canvas_height as f64])
319                        .render(&mut f, top_chunks[0]);
320                    List::new(inventory.list().into_iter())
321                        .block(Block::default().borders(Borders::ALL).title("Inventory"))
322                        .start_corner(Corner::TopLeft)
323                        .render(&mut f, top_chunks[1]);
324                    List::new(game_events.events().clone().into_iter())
325                        .block(Block::default().borders(Borders::ALL).title("Events"))
326                        .start_corner(Corner::TopLeft)
327                        .render(&mut f, bottom_chunks[0]);
328                    List::new(player.list().into_iter())
329                        .block(Block::default().borders(Borders::ALL).title("Player"))
330                        .start_corner(Corner::TopLeft)
331                        .render(&mut f, bottom_chunks[1]);
332                })
333                .unwrap();
334        }
335    }
336}