Add basic player movement on a ZZT-style board
Implements Glyph (per-cell visual), Element (shared behavior palette), and Board (60x25 grid of (Glyph, usize) cells). Player walks around with arrow keys and is blocked by wall elements. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+111
@@ -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<Element>,
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+53
-7
@@ -1,19 +1,40 @@
|
|||||||
use eframe::egui;
|
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<()> {
|
fn main() -> eframe::Result<()> {
|
||||||
let options = eframe::NativeOptions::default();
|
let options = eframe::NativeOptions::default();
|
||||||
eframe::run_native(
|
eframe::run_native(
|
||||||
"Viberogue",
|
"Viberogue",
|
||||||
options,
|
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 {
|
impl eframe::App for App {
|
||||||
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
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::TopBottomPanel::top("menu_bar").show(ctx, |ui| {
|
||||||
egui::MenuBar::new().ui(ui, |ui| {
|
egui::MenuBar::new().ui(ui, |ui| {
|
||||||
ui.menu_button("File", |ui| {
|
ui.menu_button("File", |ui| {
|
||||||
@@ -25,10 +46,35 @@ impl eframe::App for App {
|
|||||||
});
|
});
|
||||||
|
|
||||||
egui::CentralPanel::default().show(ctx, |ui| {
|
egui::CentralPanel::default().show(ctx, |ui| {
|
||||||
let rect = ui.available_rect_before_wrap();
|
let board = &self.state.board;
|
||||||
let center = rect.center();
|
let origin = ui.available_rect_before_wrap().min;
|
||||||
let square = egui::Rect::from_center_size(center, egui::vec2(200.0, 200.0));
|
let painter = ui.painter();
|
||||||
ui.painter().rect_filled(square, 0.0, egui::Color32::BLUE);
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user