Compare commits

...

6 commits

Author SHA1 Message Date
2be2b357ad Add difficulties
All checks were successful
continuous-integration/drone/push Build is passing
2022-01-30 03:56:34 -05:00
1628abea0e Remove unnecessary blink field from TileData 2022-01-30 03:55:59 -05:00
3da6f63b8e Randomize gem color 2022-01-30 01:01:51 -05:00
576bd76bd6 Render letter tiles as uppercase 2022-01-29 21:55:15 -05:00
62905dc330 Implement randomized levels 2022-01-29 21:53:01 -05:00
29a1a2bc7f Don't render player spawn tile 2022-01-29 21:51:12 -05:00
7 changed files with 285 additions and 155 deletions

45
src/difficulty.rs Normal file
View file

@ -0,0 +1,45 @@
#[derive(Debug, Eq, PartialEq, Copy, Clone, Hash)]
pub struct Difficulty {
pub value: u32,
pub starting_gems: u32,
pub starting_whips: u32,
pub starting_teleports: u32,
pub starting_keys: u32,
pub starting_whip_power: u32,
}
pub const SECRET: Difficulty = Difficulty {
value: 9,
starting_gems: 250,
starting_whips: 100,
starting_teleports: 50,
starting_keys: 1,
starting_whip_power: 3,
};
pub const NOVICE: Difficulty = Difficulty {
value: 8,
starting_gems: 20,
starting_whips: 10,
starting_teleports: 0,
starting_keys: 0,
starting_whip_power: 0,
};
pub const EXPERIENCED: Difficulty = Difficulty {
value: 5,
starting_gems: 15,
starting_whips: 0,
starting_teleports: 0,
starting_keys: 0,
starting_whip_power: 0,
};
pub const ADVANCED: Difficulty = Difficulty {
value: 2,
starting_gems: 10,
starting_whips: 0,
starting_teleports: 0,
starting_keys: 0,
starting_whip_power: 0,
};

56
src/levels.rs Normal file
View file

@ -0,0 +1,56 @@
use std::collections::HashMap;
use crate::{constants::*, tile_data::TileType};
#[allow(clippy::large_enum_variant)]
pub enum Level {
Normal([&'static str; MAP_HEIGHT]),
Randomized(HashMap<TileType, u32>),
End,
}
pub fn get_level(index: u32) -> Level {
match index {
0 => Level::Randomized(HashMap::from([
(TileType::Slow, 15),
(TileType::Whip, 1),
(TileType::Stairs, 3),
(TileType::Gem, 8),
])),
1 => Level::Normal([
"LXXX2 2 2 2 2 2 2 2 2 2 2 2 2 +",
"+XXX 2 2 2 2 2 2 2 2 XXX ",
" XXX2 2 2 2 2 2 2 2 2 2 2 XTX ",
"+XXX 2 2 2 2 2 2 2 XXX ",
" XXX 2 2 2 2 2 2 2 2 2 2 ",
"+XXX 2 ",
" XXX 2 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX 2 2 ",
"+XXX2 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX 2 ",
" XXX 2 XXX2 W 2XXX 2 ",
"+XXX 2 XXX 2 FFFFF 2 XXX 2 2 ",
" XXX2 2 XXX F111F XXX ",
"+XXX2 XXX+ 3 3 W F1P1F W 3 3 +XXX 2 ",
"XXXX 2 XXX F111F XXX 2 2",
"WXXX 2 XXX 2 FFFFF 2 XXX 2 ",
"WXXX2 XXX2 W 2XXX 2 ",
"WXXX 2 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX 2 ",
"WXXX 2 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX 2 ",
"WXXX2 2 ",
"WXXX 2 2 2 2 2 2 2 2 2 2 2 ",
"XXXX 2 2 2 2 2 2 2 2 2 2 2 XXX ",
"XXXX2 2 2 2 2 2 2 2 2 2 2 2 XTX ",
"XXXX 2 2 2 2 2 2 2 2 2 2 2 2 XXX ",
"CXXX 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +",
]),
2 => Level::Randomized(HashMap::from([
(TileType::Slow, 60),
(TileType::Block, 25),
(TileType::Whip, 5),
(TileType::Stairs, 2),
(TileType::Gem, 10),
(TileType::Teleport, 3),
])),
40 => Level::End,
_ => Level::Randomized(HashMap::new()),
}
}

View file

@ -2,6 +2,8 @@
pub mod components;
pub mod constants;
pub mod difficulty;
pub mod levels;
pub mod resources;
mod sidebar;
mod state;
@ -10,33 +12,27 @@ pub mod tile_data;
pub mod vga_color;
use bracket_lib::prelude::*;
use components::{
monster::{color_for_kind, glyphs_for_kind, ticks_for_kind, MonsterKind},
Monster, Player, Position, Renderable,
};
use components::{Monster, Player, Position, Renderable};
use resources::{Clock, LevelNumber, Map, ShowDebugInfo, SoundEffects, SoundOutput, Stats};
use specs::prelude::*;
use state::State;
use std::time::Instant;
use tile_data::TileType;
use vga_color as vga;
fn main() -> BError {
let context = BTermBuilder::simple(80, 25)?
let context = BTermBuilder::vga(80, 25)
.with_fps_cap(60.0)
.with_title("Kroz")
.with_tile_dimensions(8, 16)
.build()?;
let mut ss = SoundOutput::new();
let sound_effects = SoundEffects::new(&ss);
ss.play_sound(sound_effects.startup.clone());
let mut sound_system = SoundOutput::new();
let sound_effects = SoundEffects::new(&sound_system);
sound_system.play_sound(sound_effects.startup.clone());
let mut ecs = World::new();
ecs.insert(ss);
ecs.insert(LevelNumber(0));
let starting_level = 0;
ecs.insert(sound_system);
ecs.insert(LevelNumber(starting_level));
ecs.insert(ShowDebugInfo(false));
ecs.insert(Clock {
last_ticked: Instant::now(),
@ -44,12 +40,16 @@ fn main() -> BError {
ticks: 0,
});
ecs.insert(sound_effects);
let selected_difficulty = difficulty::SECRET;
ecs.insert(selected_difficulty);
ecs.insert(Stats {
score: 1290,
gems: 14,
whips: 7,
teleports: 0,
keys: 0,
score: 0,
gems: selected_difficulty.starting_gems,
whips: selected_difficulty.starting_whips,
whip_power: selected_difficulty.starting_whip_power,
teleports: selected_difficulty.starting_teleports,
keys: selected_difficulty.starting_keys,
});
ecs.register::<Position>();
@ -57,69 +57,10 @@ fn main() -> BError {
ecs.register::<Monster>();
ecs.register::<Player>();
let map = Map::new();
let mut rng = RandomNumberGenerator::new();
for (i, tile) in &mut map.get_tiles().iter().enumerate() {
if rng.roll_dice(1, 16) < 2 && *tile == TileType::Floor {
let position = map.index_to_point2d(i);
let kind = match rng.range(0, 3) {
0 => MonsterKind::Slow,
1 => MonsterKind::Medium,
2 => MonsterKind::Fast,
_ => MonsterKind::Slow,
};
ecs.create_entity()
.with(Position {
x: position.x,
y: position.y,
})
.with(Renderable {
glyph: *rng.random_slice_entry(&glyphs_for_kind(kind)).unwrap(),
fg: color_for_kind(kind),
bg: RGB::named(vga::BLACK),
})
.with(Monster {
kind,
ticks_until_move: ticks_for_kind(kind),
})
.build();
}
}
let player_start_pos = Point { x: 40, y: 22 };
ecs.create_entity()
.with(Position {
x: player_start_pos.x,
y: player_start_pos.y,
})
.with(Renderable {
glyph: to_cp437('☻'),
fg: RGB::named(vga::YELLOW_BRIGHT),
bg: RGB::named(vga::BLACK),
})
.with(Player {
last_moved: Instant::now(),
})
.build();
let map = Map::from_level(levels::get_level(starting_level));
map.spawn_entities(&mut ecs);
ecs.insert(map);
ecs.insert(Point::new(player_start_pos.x, player_start_pos.y));
// for i in 0..10 {
// gs.ecs
// .create_entity()
// .with(Position { x: i * 7, y: 20 })
// .with(Renderable {
// glyph: to_cp437('Ä'),
// fg: RGB::named(vga::RED_BRIGHT),
// bg: RGB::named(vga::BLACK),
// })
// .with(LeftMover {})
// .build();
// }
// let descent_sounds: Vec<Sound> = (20..100)
// .rev()
// .flat_map(|x| {
@ -138,14 +79,5 @@ fn main() -> BError {
// let _ = gs.sound_system.play_sound(descent_effect);
// let effect = gs.sound_system.render_sound_effect(SoundEffect {
// sounds: vec![Sound {
// sound_type: SoundType::Tone(3500),
// duration: Duration::from_millis(4000),
// }],
// });
// let _ = gs.sound_system.play_sound(effect);
main_loop(context, State::new(ecs))
}

View file

@ -1,16 +1,21 @@
use std::time::Instant;
use crate::{
constants::{MAP_HEIGHT, MAP_SIZE, MAP_WIDTH, MAP_X, MAP_Y},
components::{monster::*, Monster, Player, Position, Renderable},
constants::*,
levels::Level,
tile_data::{self, TileType},
vga_color as vga,
};
use bracket_lib::{prelude::*, random::RandomNumberGenerator};
use specs::Entity;
use specs::{Builder, Entity, World, WorldExt};
pub struct Map {
tiles: Vec<TileType>,
tile_content: Vec<Option<Entity>>,
border_fg: RGB,
border_bg: RGB,
gem_color: RGB,
}
impl BaseMap for Map {}
@ -29,20 +34,135 @@ impl Default for Map {
impl Map {
pub fn new() -> Self {
let mut tiles = vec![TileType::Floor; MAP_SIZE];
let mut rng = RandomNumberGenerator::new();
for tile in &mut tiles {
if rng.roll_dice(1, 16) < 2 {
*tile = TileType::Wall;
} else if rng.roll_dice(1, 16) < 2 {
*tile = TileType::Block;
}
}
Self {
tiles,
tiles: vec![TileType::Floor; MAP_SIZE],
tile_content: vec![None; MAP_SIZE],
border_fg: RGB::named(vga::get_by_index(rng.range(8, 15))),
border_bg: RGB::named(vga::get_by_index(rng.range(1, 7) as usize)),
gem_color: RGB::named(vga::get_by_index(rng.range(1, 15))),
}
}
pub fn from_level(level: Level) -> Self {
match level {
Level::Normal(_data) => todo!(),
Level::Randomized(data) => {
let mut rng = RandomNumberGenerator::new();
let mut map = Self::new();
for (tile, count) in data {
for _ in 0..count {
loop {
let point = Point {
x: rng.range(0, MAP_WIDTH as i32),
y: rng.range(0, MAP_HEIGHT as i32),
};
if map.get_tile_at(point) == TileType::Floor {
map.set_tile_at(point, tile);
break;
}
}
}
}
loop {
let point = Point {
x: rng.range(0, MAP_WIDTH as i32),
y: rng.range(0, MAP_HEIGHT as i32),
};
if map.get_tile_at(point) == TileType::Floor {
map.set_tile_at(point, TileType::Player);
break;
}
}
map
}
Level::End => todo!(),
}
}
pub fn spawn_entities(&self, ecs: &mut World) {
for (index, tile) in self.tiles.iter().enumerate() {
let point = self.index_to_point2d(index);
match tile {
TileType::Player => {
ecs.create_entity()
.with(Position {
x: point.x,
y: point.y,
})
.with(Renderable {
glyph: to_cp437('☻'),
fg: RGB::named(vga::YELLOW_BRIGHT),
bg: RGB::named(vga::BLACK),
})
.with(Player {
last_moved: Instant::now(),
})
.build();
ecs.insert(point);
}
TileType::Slow => {
let mut rng = RandomNumberGenerator::new();
ecs.create_entity()
.with(Position {
x: point.x,
y: point.y,
})
.with(Renderable {
glyph: *rng
.random_slice_entry(&glyphs_for_kind(MonsterKind::Slow))
.unwrap(),
fg: color_for_kind(MonsterKind::Slow),
bg: RGB::named(vga::BLACK),
})
.with(Monster {
kind: MonsterKind::Slow,
ticks_until_move: ticks_for_kind(MonsterKind::Slow),
})
.build();
}
TileType::Medium => {
let mut rng = RandomNumberGenerator::new();
ecs.create_entity()
.with(Position {
x: point.x,
y: point.y,
})
.with(Renderable {
glyph: *rng
.random_slice_entry(&glyphs_for_kind(MonsterKind::Medium))
.unwrap(),
fg: color_for_kind(MonsterKind::Medium),
bg: RGB::named(vga::BLACK),
})
.with(Monster {
kind: MonsterKind::Medium,
ticks_until_move: ticks_for_kind(MonsterKind::Medium),
})
.build();
}
TileType::Fast => {
let mut rng = RandomNumberGenerator::new();
ecs.create_entity()
.with(Position {
x: point.x,
y: point.y,
})
.with(Renderable {
glyph: *rng
.random_slice_entry(&glyphs_for_kind(MonsterKind::Fast))
.unwrap(),
fg: color_for_kind(MonsterKind::Fast),
bg: RGB::named(vga::BLACK),
})
.with(Monster {
kind: MonsterKind::Fast,
ticks_until_move: ticks_for_kind(MonsterKind::Fast),
})
.build();
}
_ => {}
}
}
}
@ -86,7 +206,11 @@ impl Map {
ctx.set(
x + MAP_X,
y + MAP_Y,
data.color_fg,
if *tile == TileType::Gem {
self.gem_color
} else {
RGB::named(data.color_fg)
},
data.color_bg,
data.glyph,
);

View file

@ -45,6 +45,7 @@ pub struct Stats {
pub score: u32,
pub gems: u32,
pub whips: u32,
pub whip_power: u32,
pub teleports: u32,
pub keys: u32,
}

View file

@ -39,7 +39,7 @@ pub fn draw(ecs: &World, ctx: &mut BTerm) {
ctx.print_centered_at(
SIDEBAR_POS_X + 6,
SIDEBAR_POS_Y + 4,
ecs.read_resource::<LevelNumber>().0,
ecs.read_resource::<LevelNumber>().0 + 1,
);
ctx.print(SIDEBAR_POS_X + 4, SIDEBAR_POS_Y + 6, "Gems");
ctx.print(SIDEBAR_POS_X + 4, SIDEBAR_POS_Y + 9, "Whips");

View file

@ -8,12 +8,14 @@ pub struct TileData {
pub color_fg: (u8, u8, u8),
pub color_bg: (u8, u8, u8),
pub serialized_char: char,
pub blink: bool,
}
#[derive(PartialEq, Copy, Clone)]
#[derive(Eq, PartialEq, Copy, Clone, Hash)]
pub enum TileType {
Floor, // 0
Slow, // 1
Medium, // 2
Fast, // 3
Block, // 4
Whip, // 5
Stairs, // 6
@ -70,336 +72,306 @@ pub fn tile_data(tile: TileType) -> TileData {
serialized_char: ' ',
color_fg: vga::BLACK,
color_bg: vga::BLACK,
blink: false,
},
TileType::Slow => TileData {
glyph: 0,
serialized_char: '1',
color_fg: vga::BLACK,
color_bg: vga::BLACK,
},
TileType::Medium => TileData {
glyph: 0,
serialized_char: '2',
color_fg: vga::BLACK,
color_bg: vga::BLACK,
},
TileType::Fast => TileData {
glyph: 0,
serialized_char: '3',
color_fg: vga::BLACK,
color_bg: vga::BLACK,
},
TileType::Block => TileData {
glyph: to_cp437('▓'),
serialized_char: 'X',
color_fg: vga::YELLOW,
color_bg: vga::BLACK,
blink: false,
},
TileType::Whip => TileData {
glyph: to_cp437('⌠'),
serialized_char: 'W',
color_fg: vga::WHITE_BRIGHT,
color_bg: vga::BLACK,
blink: false,
},
TileType::Stairs => TileData {
glyph: to_cp437('≡'),
serialized_char: 'L',
color_fg: vga::BLACK,
color_bg: vga::WHITE,
blink: true,
},
TileType::Chest => TileData {
glyph: to_cp437('C'),
serialized_char: 'C',
color_fg: vga::YELLOW_BRIGHT,
color_bg: vga::RED,
blink: false,
},
TileType::SlowTime => TileData {
glyph: to_cp437('Φ'),
serialized_char: 'S',
color_fg: vga::CYAN_BRIGHT,
color_bg: vga::BLACK,
blink: false,
},
TileType::Gem => TileData {
glyph: to_cp437('♦'),
serialized_char: '+',
color_fg: vga::WHITE,
color_bg: vga::BLACK,
blink: false,
},
TileType::Invisible => TileData {
glyph: to_cp437('¡'),
serialized_char: 'I',
color_fg: vga::GREEN,
color_bg: vga::BLACK,
blink: false,
},
TileType::Teleport => TileData {
glyph: to_cp437('↑'),
serialized_char: 'T',
color_fg: vga::MAGENTA_BRIGHT,
color_bg: vga::BLACK,
blink: false,
},
TileType::Key => TileData {
glyph: to_cp437('î'),
serialized_char: 'K',
color_fg: vga::RED_BRIGHT,
color_bg: vga::BLACK,
blink: false,
},
TileType::Door => TileData {
glyph: to_cp437('∞'),
serialized_char: 'D',
color_fg: vga::CYAN,
color_bg: vga::MAGENTA,
blink: false,
},
TileType::Wall => TileData {
glyph: to_cp437('█'),
serialized_char: '#',
color_fg: vga::YELLOW,
color_bg: vga::BLACK,
blink: false,
},
TileType::SpeedTime => TileData {
glyph: to_cp437('Θ'),
serialized_char: 'F',
color_fg: vga::CYAN_BRIGHT,
color_bg: vga::BLACK,
blink: false,
},
TileType::Trap => TileData {
glyph: to_cp437('∙'),
serialized_char: '.',
color_fg: vga::WHITE,
color_bg: vga::BLACK,
blink: false,
},
TileType::River => TileData {
glyph: to_cp437('≈'),
serialized_char: 'R',
color_fg: vga::BLUE_BRIGHT,
color_bg: vga::BLUE,
blink: false,
},
TileType::Power => TileData {
glyph: to_cp437('○'),
serialized_char: 'Q',
color_fg: vga::WHITE_BRIGHT,
color_bg: vga::BLACK,
blink: false,
},
TileType::Forest => TileData {
glyph: to_cp437('█'),
serialized_char: '/',
color_fg: vga::GREEN,
color_bg: vga::BLACK,
blink: false,
},
TileType::Tree => TileData {
glyph: to_cp437('♣'),
serialized_char: '\\',
color_fg: vga::YELLOW,
color_bg: vga::GREEN,
blink: false,
},
TileType::Bomb => TileData {
glyph: to_cp437('¥'),
serialized_char: 'B',
color_fg: vga::WHITE_BRIGHT,
color_bg: vga::BLACK,
blink: false,
},
TileType::Lava => TileData {
glyph: to_cp437('▓'),
serialized_char: 'V',
color_fg: vga::RED_BRIGHT,
color_bg: vga::RED,
blink: false,
},
TileType::Pit => TileData {
glyph: to_cp437('░'),
serialized_char: '=',
color_fg: vga::WHITE,
color_bg: vga::BLACK,
blink: false,
},
TileType::Tome => TileData {
glyph: to_cp437('♀'),
serialized_char: 'A',
color_fg: vga::WHITE_BRIGHT,
color_bg: vga::BLACK,
blink: true,
},
TileType::Tunnel => TileData {
glyph: to_cp437('∩'),
serialized_char: 'U',
color_fg: vga::WHITE_BRIGHT,
color_bg: vga::BLACK,
blink: false,
},
TileType::Freeze => TileData {
glyph: to_cp437('ƒ'),
serialized_char: 'Z',
color_fg: vga::CYAN_BRIGHT,
color_bg: vga::BLACK,
blink: false,
},
TileType::Nugget => TileData {
glyph: to_cp437('☼'),
serialized_char: '*',
color_fg: vga::YELLOW_BRIGHT,
color_bg: vga::BLACK,
blink: false,
},
TileType::Quake => TileData {
glyph: 0,
serialized_char: 'E',
color_fg: vga::BLACK,
color_bg: vga::BLACK,
blink: false,
},
TileType::InvisibleBlock => TileData {
glyph: 0,
serialized_char: ';',
color_fg: vga::BLACK,
color_bg: vga::BLACK,
blink: false,
},
TileType::InvisibleWall => TileData {
glyph: 0,
serialized_char: ':',
color_fg: vga::BLACK,
color_bg: vga::BLACK,
blink: false,
},
TileType::InvisibleDoor => TileData {
glyph: 0,
serialized_char: '`',
color_fg: vga::BLACK,
color_bg: vga::BLACK,
blink: false,
},
TileType::Stop => TileData {
glyph: 0,
serialized_char: '-',
color_fg: vga::BLACK,
color_bg: vga::BLACK,
blink: false,
},
TileType::Zap => TileData {
glyph: to_cp437('▲'),
serialized_char: '%',
color_fg: vga::RED_BRIGHT,
color_bg: vga::BLACK,
blink: false,
},
TileType::Create => TileData {
glyph: to_cp437('▼'),
serialized_char: ']',
color_fg: vga::WHITE_BRIGHT,
color_bg: vga::BLACK,
blink: false,
},
TileType::Generator => TileData {
glyph: to_cp437('♠'),
serialized_char: 'G',
color_fg: vga::BLACK,
color_bg: vga::BLACK,
blink: false,
},
TileType::Trap2 => TileData {
glyph: 0,
serialized_char: '@',
color_fg: vga::BLACK,
color_bg: vga::BLACK,
blink: false,
},
TileType::Trap3 => TileData {
glyph: 0,
serialized_char: ')',
color_fg: vga::BLACK,
color_bg: vga::BLACK,
blink: false,
},
TileType::Trap4 => TileData {
glyph: 0,
serialized_char: '(',
color_fg: vga::BLACK,
color_bg: vga::BLACK,
blink: false,
},
TileType::Trap5 => TileData {
glyph: 0,
serialized_char: '$', // Unsure about this
color_fg: vga::BLACK,
color_bg: vga::BLACK,
blink: false,
},
TileType::Trap6 => TileData {
glyph: 0,
serialized_char: 'α',
color_fg: vga::BLACK,
color_bg: vga::BLACK,
blink: false,
},
TileType::Trap7 => TileData {
glyph: 0,
serialized_char: 'ß',
color_fg: vga::BLACK,
color_bg: vga::BLACK,
blink: false,
},
TileType::Trap8 => TileData {
glyph: 0,
serialized_char: 'Γ',
color_fg: vga::BLACK,
color_bg: vga::BLACK,
blink: false,
},
TileType::Trap9 => TileData {
glyph: 0,
serialized_char: 'π',
color_fg: vga::BLACK,
color_bg: vga::BLACK,
blink: false,
},
TileType::Trap10 => TileData {
glyph: 0,
serialized_char: 'Σ',
color_fg: vga::BLACK,
color_bg: vga::BLACK,
blink: false,
},
TileType::Trap11 => TileData {
glyph: 0,
serialized_char: 'σ',
color_fg: vga::BLACK,
color_bg: vga::BLACK,
blink: false,
},
TileType::Trap12 => TileData {
glyph: 0,
serialized_char: 'µ',
color_fg: vga::BLACK,
color_bg: vga::BLACK,
blink: false,
},
TileType::Trap13 => TileData {
glyph: 0,
serialized_char: 'τ',
color_fg: vga::BLACK,
color_bg: vga::BLACK,
blink: false,
},
TileType::Player => TileData {
glyph: to_cp437('≡'),
glyph: 0,
serialized_char: 'P',
color_fg: vga::BLACK,
color_bg: vga::WHITE,
blink: true,
color_bg: vga::BLACK,
},
TileType::Punctuation => TileData {
glyph: to_cp437('!'),
serialized_char: '!',
color_fg: vga::WHITE_BRIGHT,
color_bg: vga::YELLOW,
blink: false,
},
TileType::Letter(c) => TileData {
glyph: to_cp437(c),
glyph: to_cp437(c.to_ascii_uppercase()),
serialized_char: c,
color_fg: vga::WHITE_BRIGHT,
color_bg: vga::YELLOW,
blink: false,
},
}
}