Skip to content

Commit

Permalink
Initial commit.
Browse files Browse the repository at this point in the history
  • Loading branch information
dpc committed Feb 12, 2015
0 parents commit 38fd549
Show file tree
Hide file tree
Showing 17 changed files with 1,854 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
target/
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
language: rust
14 changes: 14 additions & 0 deletions Cargo.toml
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 = "*"
20 changes: 20 additions & 0 deletions Makefile
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
2 changes: 2 additions & 0 deletions Makefile.defs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
PKG_NAME=hex2d
DEFAULT_TARGET=test
65 changes: 65 additions & 0 deletions README.md
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.


162 changes: 162 additions & 0 deletions src/actor.rs
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
}

}
85 changes: 85 additions & 0 deletions src/ai.rs
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();
}
}
Loading

0 comments on commit 38fd549

Please sign in to comment.