Renderer, Oberfläche, Eingabe, Sound-Modul, Simulationstest und Docker-Setup (Port 18093)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Richard ZinkandClaude Opus 5.5 committed 2026-11-02 22:16:41 +01:00
1 parent 14e481cffb
commit db417b3af8
13 files changed
+1930 -19

No files matched your search

+275
View File
@@ -0,0 +1,275 @@
// Dungeoncraft – Dungeon-Aufbau & Verteidigung im Browser (Rust + macroquad, WASM).
mod game;
mod gfx;
mod map;
mod render;
mod sound;
mod ui;
use game::Game;
use gfx::Gfx;
use macroquad::prelude::*;
use map::{idx, inb, MH, MW};
use render::{draw_world, Overlay, View, ZOOMS};
use sound::SoundBank;
use ui::{hover_info, Tool, Ui, UiAction, TOOLS};
fn conf() -> Conf {
Conf { window_title: "Dungeoncraft".into(), window_width: 1280, window_height: 800, high_dpi: false, ..Default::default() }
}
struct Input {
ldown: Option<(i32, i32)>, // Start des Auswahlrechtecks
rdown: Option<(f32, f32)>, // Startpunkt rechte Maustaste
rlast: (f32, f32),
rdrag: bool,
minimap_drag: bool,
}
fn new_game(seed: u32) -> (Game, View) {
let g = Game::new(seed);
let (cx, cy) = g.map.core;
let zoom = 32.0;
let view = View { cam_x: cx as f32 + 1.5 - screen_width() / zoom * 0.5, cam_y: cy as f32 + 1.5 - screen_height() / zoom * 0.5, zoom };
(g, view)
}
fn clamp_cam(v: &mut View) {
let vw = screen_width() / v.zoom;
let vh = screen_height() / v.zoom;
v.cam_x = v.cam_x.clamp(-vw * 0.5, MW as f32 - vw * 0.5);
v.cam_y = v.cam_y.clamp(-vh * 0.5, MH as f32 - vh * 0.5);
}
fn center_on(v: &mut View, wx: f32, wy: f32) {
v.cam_x = wx - screen_width() / v.zoom * 0.5;
v.cam_y = wy - screen_height() / v.zoom * 0.5;
}
#[macroquad::main(conf)]
async fn main() {
let gfx = Gfx::new();
let mut sounds = SoundBank::new();
sounds.generate_all().await;
let mut seed = (miniquad::date::now() * 1000.0) as u32 | 1;
let (mut game, mut view) = new_game(seed);
let mut ui = Ui::new();
let mut inp = Input { ldown: None, rdown: None, rlast: (0.0, 0.0), rdrag: false, minimap_drag: false };
loop {
let dt = get_frame_time();
let now = get_time();
let (mx, my) = mouse_position();
let over_ui = ui.over_ui(mx, my);
let (wx, wy) = view.world(mx, my);
let hover_tile = (wx.floor() as i32, wy.floor() as i32);
// ---------------- Tastatur
for (i, k) in [
KeyCode::Key1,
KeyCode::Key2,
KeyCode::Key3,
KeyCode::Key4,
KeyCode::Key5,
KeyCode::Key6,
KeyCode::Key7,
KeyCode::Key8,
KeyCode::Key9,
KeyCode::Key0,
KeyCode::Q,
KeyCode::E,
]
.iter()
.enumerate()
{
if is_key_pressed(*k) {
ui.tool = TOOLS[i];
inp.ldown = None;
}
}
if is_key_pressed(KeyCode::H) || (is_key_pressed(KeyCode::Escape) && ui.show_help) {
ui.show_help = !ui.show_help;
}
if is_key_pressed(KeyCode::Escape) {
inp.ldown = None;
}
if (is_key_pressed(KeyCode::P) || is_key_pressed(KeyCode::Space)) && game.state == 0 {
game.paused = !game.paused;
}
if is_key_pressed(KeyCode::M) {
sounds.muted = !sounds.muted;
}
if is_key_pressed(KeyCode::Equal) || is_key_pressed(KeyCode::KpAdd) {
sounds.change_volume(0.1);
}
if is_key_pressed(KeyCode::Minus) || is_key_pressed(KeyCode::KpSubtract) {
sounds.change_volume(-0.1);
}
if is_key_pressed(KeyCode::N) && game.state != 0 {
seed = seed.wrapping_mul(1103515245).wrapping_add(12345) | 1;
(game, view) = new_game(seed);
ui.show_help = false;
}
// ---------------- Kamera
let pan = 900.0 * dt / view.zoom;
if is_key_down(KeyCode::W) || is_key_down(KeyCode::Up) {
view.cam_y -= pan;
}
if is_key_down(KeyCode::S) || is_key_down(KeyCode::Down) {
view.cam_y += pan;
}
if is_key_down(KeyCode::A) || is_key_down(KeyCode::Left) {
view.cam_x -= pan;
}
if is_key_down(KeyCode::D) || is_key_down(KeyCode::Right) {
view.cam_x += pan;
}
// Bildschirmrand
let edge = 6.0;
if !ui.show_help && mx >= 0.0 && my >= 0.0 && mx < screen_width() && my < screen_height() {
if mx < edge {
view.cam_x -= pan;
} else if mx > screen_width() - edge {
view.cam_x += pan;
}
if my < edge {
view.cam_y -= pan;
} else if my > screen_height() - edge {
view.cam_y += pan;
}
}
// Zoom
let (_, wheel) = mouse_wheel();
if wheel != 0.0 && !over_ui {
let zi = ZOOMS.iter().position(|&z| z >= view.zoom).unwrap_or(2) as i32;
let ni = (zi + if wheel > 0.0 { 1 } else { -1 }).clamp(0, ZOOMS.len() as i32 - 1) as usize;
if ZOOMS[ni] != view.zoom {
view.zoom = ZOOMS[ni];
view.cam_x = wx - mx / view.zoom;
view.cam_y = wy - my / view.zoom;
}
}
// Rechte Maustaste: ziehen = verschieben, klicken = absetzen
if is_mouse_button_pressed(MouseButton::Right) {
inp.rdown = Some((mx, my));
inp.rlast = (mx, my);
inp.rdrag = false;
}
if let Some((sx, sy)) = inp.rdown {
if is_mouse_button_down(MouseButton::Right) {
if (mx - sx).abs() + (my - sy).abs() > 5.0 {
inp.rdrag = true;
}
if inp.rdrag {
view.cam_x -= (mx - inp.rlast.0) / view.zoom;
view.cam_y -= (my - inp.rlast.1) / view.zoom;
}
inp.rlast = (mx, my);
}
}
if is_mouse_button_released(MouseButton::Right) {
if !inp.rdrag && !over_ui && game.state == 0 {
if inp.ldown.is_some() {
inp.ldown = None;
} else {
game.drop(wx, wy);
}
}
inp.rdown = None;
}
// ---------------- Linke Maustaste
if is_mouse_button_pressed(MouseButton::Left) {
match ui.click(mx, my, &game) {
UiAction::SelectTool(t) => {
ui.tool = t;
game.sfx_ui(game::Sfx::Click);
}
UiAction::MinimapJump(x, y) => {
center_on(&mut view, x, y);
inp.minimap_drag = true;
}
UiAction::CloseHelp => ui.show_help = false,
UiAction::NewGame => {
seed = seed.wrapping_mul(1103515245).wrapping_add(12345) | 1;
(game, view) = new_game(seed);
ui.show_help = false;
}
UiAction::Consumed => {}
UiAction::None => {
if game.state == 0 && inb(hover_tile.0, hover_tile.1) {
match ui.tool {
Tool::Dig | Tool::Room(_) | Tool::Sell => inp.ldown = Some(hover_tile),
Tool::Hand => {
game.pickup(wx, wy);
}
Tool::Flag => game.set_flag(hover_tile.0, hover_tile.1),
Tool::Lightning => game.lightning(wx, wy),
Tool::Heal => game.heal(wx, wy),
Tool::Summon => game.summon(wx, wy),
}
}
}
}
}
if inp.minimap_drag {
if is_mouse_button_down(MouseButton::Left) && ui.minimap_contains(mx, my) {
let (x, y) = ui.minimap_world(mx, my);
center_on(&mut view, x, y);
} else if !is_mouse_button_down(MouseButton::Left) {
inp.minimap_drag = false;
}
}
if is_mouse_button_released(MouseButton::Left) {
if let Some((sx, sy)) = inp.ldown.take() {
let (ex, ey) = (hover_tile.0.clamp(0, MW as i32 - 1), hover_tile.1.clamp(0, MH as i32 - 1));
match ui.tool {
Tool::Dig => {
let on = !game.map.t[idx(sx, sy)].marked;
game.mark_rect(sx, sy, ex, ey, on);
}
Tool::Room(r) => game.build_rect(r, sx, sy, ex, ey),
Tool::Sell => game.sell_rect(sx, sy, ex, ey),
_ => {}
}
}
}
clamp_cam(&mut view);
// ---------------- Update
game.update(dt);
for ev in game.events.drain(..) {
sounds.play(ev, &view, now);
}
for m in game.msgs.drain(..) {
ui.log.push((m, now));
}
// ---------------- Zeichnen
clear_background(Color::from_rgba(8, 6, 8, 255));
let (hover_color, radius) = match ui.tool {
Tool::Dig => (Color::new(1.0, 0.85, 0.3, 1.0), None),
Tool::Hand => (Color::new(0.9, 0.75, 0.6, 1.0), None),
Tool::Room(_) => (Color::new(0.4, 0.85, 1.0, 1.0), None),
Tool::Sell => (Color::new(1.0, 0.5, 0.3, 1.0), None),
Tool::Flag => (Color::new(1.0, 0.3, 0.3, 1.0), None),
Tool::Lightning => (Color::new(1.0, 1.0, 0.6, 1.0), Some((wx, wy, 1.6))),
Tool::Heal => (Color::new(0.4, 1.0, 0.5, 1.0), Some((wx, wy, 2.55))),
Tool::Summon => (Color::new(0.8, 0.5, 1.0, 1.0), None),
};
let ov = Overlay {
hover: if over_ui || !inb(hover_tile.0, hover_tile.1) { None } else { Some(hover_tile) },
hover_color,
rect: inp.ldown.map(|(sx, sy)| (sx, sy, hover_tile.0.clamp(0, MW as i32 - 1), hover_tile.1.clamp(0, MH as i32 - 1))),
rect_color: hover_color,
radius: if over_ui { None } else { radius },
};
draw_world(&game, &gfx, &view, now, &ov);
let info = if over_ui { Vec::new() } else { hover_info(&game, wx, wy) };
ui.draw(&game, &gfx, &view, &sounds, &info, now);
next_frame().await
}
}