-
Notifications
You must be signed in to change notification settings - Fork 18
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 38fd549
Showing
17 changed files
with
1,854 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
target/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
language: rust |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
[package] | ||
|
||
name = "rhex" | ||
version = "0.0.1" | ||
authors = ["Dawid Ciężarkiewicz <dpc@ucore.info>"] | ||
|
||
[dependencies.ncurses] | ||
git = "https://github.com/jeaye/ncurses-rs.git" | ||
|
||
[dependencies] | ||
num = "*" | ||
rand = "*" | ||
hex2d = "*" | ||
hex2d-dpcext = "*" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
include Makefile.defs | ||
|
||
default: $(DEFAULT_TARGET) | ||
|
||
.PHONY: run test build doc clean release rrun | ||
run test build doc clean: | ||
cargo $@ | ||
|
||
simple: | ||
cargo run | ||
|
||
release: | ||
cargo build --release | ||
|
||
rrun: | ||
cargo run --release | ||
|
||
.PHONY: docview | ||
docview: doc | ||
xdg-open target/doc/$(PKG_NAME)/index.html |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
PKG_NAME=hex2d | ||
DEFAULT_TARGET=test |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
[![Build Status](https://travis-ci.org/dpc/rhex.svg?branch=master)](https://travis-ci.org/dpc/rhex) | ||
|
||
# rhex | ||
|
||
## Introduction | ||
|
||
Simple roguelike prototype written in [Rust][rust-home]. | ||
|
||
You can try it out by pointing your **ssh client** to: rhex [at] rhex.dpc.pw (password is obvious). Note: **Make sure your terminal supports 256 colors and exports `TERM=xterm-256color`!** | ||
|
||
It's intendent to exercise my [Rust][rust-home] knowledge and let me play with | ||
certain mechanisms that I'd like to see in roguelike game: | ||
|
||
* hexagonal map | ||
* tactical positioning (strafing, face-direction) | ||
|
||
Previous iteration of this idea was/is: [Rustyhex][rustyhex] . This two project might merge into whole at some point. | ||
|
||
This project is based on more general and useful: [hex2d-rs - Hexagonal grid map utillity library][hex2d-rs]. | ||
|
||
[rust-home]: http://rust-lang.org | ||
[rustyhex]: //github.com/dpc/rustyhex | ||
[hex2d-rs]: //github.com/dpc/hex2d-rs | ||
|
||
## Overview | ||
|
||
![RustyHex screenshot][ss] | ||
|
||
[ss]: http://i.imgur.com/LI0FOPF.png | ||
|
||
## Building | ||
|
||
git clone https://github.com/dpc/rhex.git | ||
cd rhex | ||
cargo build | ||
|
||
|
||
## Status and goals | ||
|
||
ATM. the game is just a working prototype. There's not much of gameplay yet. | ||
|
||
Some core features that it already implements: simple AI, LoS, lighting, random map generation, areas, autoexplore command. | ||
|
||
Core features that I'd like to add soon: | ||
|
||
* sound (actions make noise that propagates) | ||
* combat | ||
* working stats | ||
* stairs | ||
* items/inventory | ||
|
||
[Report problems and ideas][issues] | ||
|
||
[issues]: https://github.com/dpc/rhex/issues | ||
|
||
# How to play | ||
|
||
## Basics | ||
|
||
* Use `hjkl` or arrow keys to move. | ||
* Press `o` to autoexplore | ||
* Hold `Shift` to strafe (with Left/Right move) | ||
* Press `.` to wait. | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,162 @@ | ||
use std::collections::HashSet; | ||
use hex2d::{Coordinate,Direction}; | ||
use game; | ||
use hex2dext::algo; | ||
|
||
type Visibility = HashSet<Coordinate>; | ||
|
||
#[derive(Copy, Clone, Eq, PartialEq, Debug)] | ||
pub enum Behavior { | ||
Player, | ||
Pony, | ||
Ai, | ||
} | ||
|
||
#[derive(Clone, Eq, PartialEq, Debug)] | ||
pub struct State { | ||
pub pos : Coordinate, | ||
pub dir : Direction, | ||
|
||
pub behavior : Behavior, | ||
|
||
/// Currently visible | ||
pub visible: Visibility, | ||
|
||
/// Known coordinates | ||
pub known: Visibility, | ||
/// Known areas | ||
pub known_areas: Visibility, | ||
|
||
/// Discovered in the last LoS | ||
pub discovered: Visibility, | ||
/// Just discovered areas | ||
pub discovered_areas: Visibility, | ||
|
||
pub light : u32, | ||
} | ||
|
||
fn calculate_los(pos : Coordinate, dir : Direction, gstate : &game::State) -> Visibility { | ||
let mut visibility = HashSet::new(); | ||
algo::los::los( | ||
&|coord| gstate.tile_at(coord).map_or(10000, |tile| tile.opaqueness()), | ||
&mut |coord| { | ||
if pos.distance(coord) < 2 || gstate.light_map.contains(&coord) { | ||
let _ = visibility.insert(coord); | ||
} | ||
}, | ||
10, pos, &[dir] | ||
); | ||
|
||
visibility | ||
} | ||
|
||
impl State { | ||
pub fn new(behavior : Behavior, pos : Coordinate, dir : Direction, gstate : &game::State) -> State { | ||
|
||
let visible = calculate_los(pos, dir, gstate); | ||
|
||
let mut state = State { | ||
behavior : behavior, | ||
pos: pos, dir: dir, | ||
visible: visible, | ||
known: HashSet::new(), | ||
known_areas: HashSet::new(), | ||
discovered: HashSet::new(), | ||
discovered_areas: HashSet::new(), | ||
light: 0, | ||
}; | ||
|
||
state.postprocess_visibile(gstate); | ||
|
||
state | ||
} | ||
|
||
pub fn add_light(&self, light : u32) -> State { | ||
State { | ||
behavior: self.behavior, | ||
pos: self.pos, | ||
dir: self.dir, | ||
visible: self.visible.clone(), | ||
known: self.known.clone(), | ||
known_areas: self.known_areas.clone(), | ||
discovered: self.discovered.clone(), | ||
discovered_areas: self.discovered_areas.clone(), | ||
light: light, | ||
} | ||
} | ||
|
||
pub fn sees(&self, pos : Coordinate) -> bool { | ||
self.visible.contains(&pos) | ||
} | ||
|
||
pub fn knows(&self, pos : Coordinate) -> bool { | ||
self.known.contains(&pos) | ||
} | ||
|
||
pub fn act(&self, gstate : &game::State, action : game::Action) -> State { | ||
let (pos, dir) = match action { | ||
game::Action::Wait => (self.pos, self.dir), | ||
game::Action::Turn(a) => (self.pos, self.dir + a), | ||
game::Action::Move(a) => (self.pos + (self.dir + a), self.dir), | ||
}; | ||
|
||
if let Some(tile) = gstate.tile_type_at(pos) { | ||
if self.pos == pos || (tile.is_passable() && !gstate.actors.contains_key(&pos)) { | ||
let visible = calculate_los(pos, dir, gstate); | ||
|
||
let mut state = State { | ||
behavior: self.behavior, | ||
pos: pos, | ||
dir: dir, | ||
visible: visible, | ||
known: self.known.clone(), | ||
known_areas: self.known_areas.clone(), | ||
discovered: HashSet::new(), | ||
discovered_areas: HashSet::new(), | ||
light: self.light, | ||
}; | ||
|
||
state.postprocess_visibile(gstate); | ||
|
||
state | ||
} else { | ||
self.clone() | ||
} | ||
} else { | ||
self.clone() | ||
} | ||
|
||
} | ||
|
||
pub fn postprocess_visibile(&mut self, gstate : &game::State) { | ||
|
||
let mut discovered = HashSet::new(); | ||
let mut discovered_areas = HashSet::new(); | ||
|
||
for i in self.visible.iter() { | ||
if !self.known.contains(i) { | ||
self.known.insert(*i); | ||
discovered.insert(*i); | ||
} | ||
} | ||
|
||
for &coord in discovered.iter() { | ||
if let Some(area) = gstate.tile_at(coord).and_then(|t| t.area) { | ||
let area_center = area.center; | ||
|
||
if !self.known_areas.contains(&area_center) { | ||
self.known_areas.insert(area_center); | ||
discovered_areas.insert(area_center); | ||
} | ||
} | ||
} | ||
|
||
self.discovered_areas = discovered_areas; | ||
self.discovered = discovered; | ||
} | ||
|
||
pub fn is_player(&self) -> bool { | ||
self.behavior == Behavior::Player | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
use rand; | ||
use rand::Rng; | ||
use std::sync::{Arc,mpsc}; | ||
|
||
use hex2dext::algo::bfs; | ||
|
||
use hex2d::{self, Coordinate}; | ||
use hex2d as h2d; | ||
use game; | ||
use actor; | ||
|
||
|
||
fn roam() -> game::Action { | ||
match rand::thread_rng().gen_range(0, 10) { | ||
0 => game::Action::Turn(h2d::Angle::Right), | ||
1 => game::Action::Turn(h2d::Angle::Left), | ||
2 => game::Action::Move(h2d::Angle::Forward), | ||
_ => game::Action::Wait, | ||
} | ||
} | ||
|
||
|
||
fn closest_reachable<F>(gstate : &game::State, start : Coordinate, max_distance : i32, cond : F) -> Option<(Coordinate, Coordinate)> | ||
where F : Fn(Coordinate) -> bool | ||
{ | ||
let mut bfs = bfs::Traverser::new( | ||
|pos| pos == start || (gstate.tile_map_or(pos, false, |t| t.is_passable()) | ||
&& pos.distance(start) < max_distance && !gstate.occupied(pos)), | ||
cond, | ||
start | ||
); | ||
bfs.find().map(|pos| (pos, bfs.backtrace_last(pos).unwrap())) | ||
} | ||
|
||
|
||
fn pony_follow(astate : &actor::State, gstate : &game::State) -> game::Action { | ||
|
||
let start = astate.pos; | ||
|
||
|
||
let player_pos = closest_reachable(gstate, start, 10, | ||
|pos| gstate.actor_map_or(pos, false, &|a| a.is_player()) | ||
); | ||
|
||
let player_pos = if let Some((dst, _)) = player_pos { | ||
if dst.distance(start) < 3 { | ||
closest_reachable(gstate, start, 10, |pos| pos.distance(dst) == 3 && gstate.passable(pos)) | ||
} else { | ||
player_pos | ||
} | ||
} else { | ||
player_pos | ||
}; | ||
|
||
if let Some((_, neigh)) = player_pos { | ||
let ndir = astate.pos.direction_to_cw(neigh).expect("bfs gave me trash"); | ||
|
||
if ndir == astate.dir { | ||
return game::Action::Move(hex2d::Angle::Forward) | ||
} else { | ||
return game::Action::Turn(ndir - astate.dir) | ||
} | ||
} else { | ||
roam() | ||
} | ||
} | ||
|
||
pub fn run( | ||
req : mpsc::Receiver<(Arc<actor::State>, Arc<game::State>)>, | ||
rep : mpsc::Sender<(Arc<actor::State>, game::Action)> | ||
) | ||
{ | ||
|
||
loop { | ||
let (astate, gstate) = req.recv().unwrap(); | ||
|
||
let action = match astate.behavior { | ||
actor::Behavior::Ai => roam(), | ||
actor::Behavior::Pony => pony_follow(&astate, &gstate), | ||
_ => panic!(), | ||
}; | ||
|
||
rep.send((astate, action)).unwrap(); | ||
} | ||
} |
Oops, something went wrong.