diff --git a/src/game.rs b/src/game.rs new file mode 100644 index 0000000..96e97f0 --- /dev/null +++ b/src/game.rs @@ -0,0 +1,111 @@ +use eframe::egui::Color32; + +pub struct Glyph { + pub ch: char, + pub fg: Color32, + pub bg: Color32, +} + +impl Glyph { + pub fn floor() -> Self { + Self { ch: ' ', fg: Color32::BLACK, bg: Color32::BLACK } + } + + pub fn wall() -> Self { + Self { ch: '#', fg: Color32::GRAY, bg: Color32::DARK_GRAY } + } + + pub fn player() -> Self { + Self { ch: '@', fg: Color32::LIGHT_BLUE, bg: Color32::BLACK } + } +} + +pub struct Element { + pub passable: bool, +} + +pub struct Board { + pub width: usize, + pub height: usize, + pub elements: Vec, + cells: Vec<(Glyph, usize)>, +} + +impl Board { + pub fn add_element(&mut self, element: Element) -> usize { + let idx = self.elements.len(); + self.elements.push(element); + idx + } + + pub fn get(&self, x: usize, y: usize) -> &(Glyph, usize) { + &self.cells[y * self.width + x] + } + + pub fn get_mut(&mut self, x: usize, y: usize) -> &mut (Glyph, usize) { + &mut self.cells[y * self.width + x] + } + + pub fn is_passable(&self, x: usize, y: usize) -> bool { + let (_, elem_idx) = self.get(x, y); + self.elements[*elem_idx].passable + } +} + +pub struct Player { + pub x: i32, + pub y: i32, +} + +pub struct GameState { + pub board: Board, + pub player: Player, +} + +impl GameState { + pub fn new() -> Self { + let w = 60; + let h = 25; + + let mut board = Board { + width: w, + height: h, + elements: Vec::new(), + cells: Vec::new(), + }; + + let floor = board.add_element(Element { passable: true }); + let wall = board.add_element(Element { passable: false }); + + board.cells = (0..w * h) + .map(|_| (Glyph::floor(), floor)) + .collect(); + + for x in 0..w { + *board.get_mut(x, 0) = (Glyph::wall(), wall); + *board.get_mut(x, h - 1) = (Glyph::wall(), wall); + } + for y in 0..h { + *board.get_mut(0, y) = (Glyph::wall(), wall); + *board.get_mut(w - 1, y) = (Glyph::wall(), wall); + } + + Self { + board, + player: Player { x: 30, y: 12 }, + } + } + + pub fn try_move(&mut self, dx: i32, dy: i32) { + let nx = self.player.x + dx; + let ny = self.player.y + dy; + if nx >= 0 && ny >= 0 { + let nx = nx as usize; + let ny = ny as usize; + if nx < self.board.width && ny < self.board.height && self.board.is_passable(nx, ny) { + self.player.x = nx as i32; + self.player.y = ny as i32; + } + } + } +} diff --git a/src/main.rs b/src/main.rs index 7c15860..ff38176 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,19 +1,40 @@ use eframe::egui; +use egui::{Align2, FontId, Key, Pos2, Rect, vec2}; + +mod game; +use game::{GameState, Glyph}; + +const CELL_W: f32 = 14.0; +const CELL_H: f32 = 20.0; fn main() -> eframe::Result<()> { let options = eframe::NativeOptions::default(); eframe::run_native( "Viberogue", options, - Box::new(|_cc| Ok(Box::new(App::default()))), + Box::new(|_cc| Ok(Box::new(App::new()))), ) } -#[derive(Default)] -struct App; +struct App { + state: GameState, +} + +impl App { + fn new() -> Self { + Self { state: GameState::new() } + } +} impl eframe::App for App { fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { + ctx.input(|i| { + if i.key_pressed(Key::ArrowUp) { self.state.try_move( 0, -1); } + if i.key_pressed(Key::ArrowDown) { self.state.try_move( 0, 1); } + if i.key_pressed(Key::ArrowLeft) { self.state.try_move(-1, 0); } + if i.key_pressed(Key::ArrowRight) { self.state.try_move( 1, 0); } + }); + egui::TopBottomPanel::top("menu_bar").show(ctx, |ui| { egui::MenuBar::new().ui(ui, |ui| { ui.menu_button("File", |ui| { @@ -25,10 +46,35 @@ impl eframe::App for App { }); egui::CentralPanel::default().show(ctx, |ui| { - let rect = ui.available_rect_before_wrap(); - let center = rect.center(); - let square = egui::Rect::from_center_size(center, egui::vec2(200.0, 200.0)); - ui.painter().rect_filled(square, 0.0, egui::Color32::BLUE); + let board = &self.state.board; + let origin = ui.available_rect_before_wrap().min; + let painter = ui.painter(); + + for y in 0..board.height { + for x in 0..board.width { + let (glyph, _) = board.get(x, y); + draw_glyph(painter, origin, x, y, glyph); + } + } + + let player_glyph = Glyph::player(); + draw_glyph( + painter, + origin, + self.state.player.x as usize, + self.state.player.y as usize, + &player_glyph, + ); }); + + ctx.request_repaint(); } } + +fn draw_glyph(painter: &egui::Painter, origin: Pos2, x: usize, y: usize, glyph: &Glyph) { + let tl = origin + vec2(x as f32 * CELL_W, y as f32 * CELL_H); + let rect = Rect::from_min_size(tl, vec2(CELL_W, CELL_H)); + let center = rect.center(); + painter.rect_filled(rect, 0.0, glyph.bg); + painter.text(center, Align2::CENTER_CENTER, glyph.ch, FontId::monospace(CELL_H), glyph.fg); +}