In celebration of Rust's 1.0 release, here's my first rust program.
I'm not up to a full rust tutorial, so instead I'll just dump this and
hope it's instructive for someone.
There's really not much to say about the program. There's a
Box<>
wrapping type that's not necessary, except a
co-worker dared me to heap-allocate some things. He wanted to make a
point that dealing with a lot of dynamic, heap-allocated objects is
where Rust's lifetime system becomes difficult and unwieldy. I didn't
find that, but this program is admittedly quite trivial.
A curses program that uses sleep()
for its game loop
requires a second thread for taking input, so there's a channel setup.
I'm a little ashamed of the unwrap()
calls, since they
represent unhandled null checks, and negate some benefits of using a
language that claims memory safety as a priority. That's on me,
though, since I could check my Option<>
return values
instead of taking the easy way out.
src/main.rs
#![feature(collections)]
extern crate collections;
extern crate ncurses;
extern crate rand;
use std::char;
use std::thread;
use std::sync::mpsc::channel;
use std::sync::mpsc::Receiver;
use std::sync::mpsc::TryRecvError;
use collections::vec_deque::VecDeque;
use ncurses::*;
use rand::Rng;
const FRAMERATE: u32 = 2;
const MAP_VISIBLE_WIDTH: usize = 10;
const ROAD_WIDTH: usize = 3;
const HILL_CHANCE_ONE_IN_X: u32 = 7;
fn get_road_char(terrain: &Terrain) -> u64 {
match terrain {
&Terrain::None => '_' as u64,
&Terrain::Hill => 'A' as u64,
}
}
fn is_crash(player: &PlayerState, map: &VecDeque<Box<MapSlice>>) -> bool {
match map.get(player.x as usize).unwrap().cells[player.y as usize] {
Terrain::None => false,
_ => true
}
}
fn draw(player: &PlayerState, map: &VecDeque<Box<MapSlice>>) {
clear();
for i in 0..map.0 {
let &slice = &map.get(i).unwrap();
for j in 0..slice.cells.0 {
mvaddch(3+j as i32,3+i as i32,get_road_char(&slice.cells[j]));
}
}
mvaddch( 3+player.y, 3+player.x, match is_crash(player,map) {
true => '*' as u64,
_ => '>' as u64
});
mvaddstr( 8, 3, "a and d to move, q to quit");
}
struct PlayerState {
x: i32,
y: i32,
}
enum Terrain {
None,
Hill
}
struct MapSlice {
cells: [Terrain; ROAD_WIDTH]
}
fn gameloop(rx: Receiver<char>) {
let mut player = PlayerState { x: 0, y: 1 };
let mut map = VecDeque::<Box<MapSlice>>::new(); let mut rng = rand::thread_rng();
loop {
while map.0 < MAP_VISIBLE_WIDTH {
let cells = [match rng.gen_weighted_bool(HILL_CHANCE_ONE_IN_X) {
true => Terrain::Hill,
_ => Terrain::None,
},match rng.gen_weighted_bool(HILL_CHANCE_ONE_IN_X) {
true => Terrain::Hill,
_ => Terrain::None,
},match rng.gen_weighted_bool(HILL_CHANCE_ONE_IN_X) {
true => Terrain::Hill,
_ => Terrain::None,
}];
let slice = Box::new(MapSlice { cells: cells });
map.push_back(slice);
}
let mut quit = false;
while !quit {
let res = rx.try_recv();
match res {
Ok(val) => {
printw(format!("Got {}\n",val).as_ref());
match val {
'q' => quit = true,
'a' => {
player.y -= 1;
if player.y < 0 { player.y = 0 }
},
'd' => {
player.y += 1;
if player.y > 2 { player.y = 2 }
},
_ => ()
}
},
Err(TryRecvError::Empty) => break,
Err(TryRecvError::Disconnected) => panic!("Channel disconnected")
};
}
draw(&player,&map);
refresh();
if quit || is_crash(&player,&map) { break }
map.pop_front();
thread::sleep_ms(1000/FRAMERATE);
}
}
fn main() {
initscr();
clear();
noecho();
curs_set(CURSOR_VISIBILITY::CURSOR_INVISIBLE);
let (tx,rx) = channel();
{
let tx = tx.clone();
thread::spawn(move || {
loop {
let c: i32 = getch();
tx.send(char::from_u32(c as u32).unwrap()).unwrap();
}
});
}
refresh();
gameloop(rx);
thread::sleep_ms(2000);
endwin();
}
Cargo.toml
[package]
name = "rustcurse"
version = "0.1.0"
authors = ["Erik Mackdanz "]
[dependencies]
ncurses="5.73.0"
rand="0.3.8"