curses_game_wrapper/
lib.rs

1//! This crate is wrapper of curses games like rogue and nethack, for AI making.
2//!
3//! What this crate provie is spawning CUI game as child process and emulation of vt100 control
4//! sequence(helped by vte crate).
5//!
6//! To run AI, You have to implement ```Reactor``` trait to your AI object.
7//! The result of vt100 emulation are stored as ```Vec<Vec<u8>>``` and AI recieves it as
8//! ```Changed(Vec<Vec<u8>>)```.
9//! # Examples
10//! ```no_run
11//! extern crate curses_game_wrapper as cgw;
12//! use cgw::{Reactor, ActionResult, AsciiChar, GameSetting, Severity};
13//! use std::time::Duration;
14//! fn main() {
15//!     struct EmptyAI {
16//!         loopnum: usize,
17//!     };
18//!     impl Reactor for EmptyAI {
19//!         fn action(&mut self, _screen: ActionResult, turn: usize) -> Option<Vec<u8>> {
20//!             let mut res = Vec::new();
21//!             match turn {
22//!                 val if val == self.loopnum - 1 => res.push(AsciiChar::CarriageReturn.as_byte()),
23//!                 val if val == self.loopnum - 2 => res.push(b'y'),
24//!                 val if val == self.loopnum - 3 => res.push(b'Q'),
25//!                 _ => {
26//!                     let c = match (turn % 4) as u8 {
27//!                         0u8 => b'h',
28//!                         1u8 => b'j',
29//!                         2u8 => b'k',
30//!                         _ => b'l',
31//!                     };
32//!                     res.push(c);
33//!                 }
34//!             };
35//!             Some(res)
36//!         }
37//!     }
38//!     let loopnum = 50;
39//!     let gs = GameSetting::new("rogue")
40//!         .env("ROGUEUSER", "EmptyAI")
41//!         .lines(24)
42//!         .columns(80)
43//!         .debug_file("debug.txt")
44//!         .max_loop(loopnum + 1)
45//!         .draw_on(Duration::from_millis(200));
46//!     let game = gs.build();
47//!     let mut ai = EmptyAI { loopnum: loopnum };
48//!     game.play(&mut ai);
49//! }
50//! ```
51
52#![cfg_attr(feature = "clippy", feature(plugin))]
53#![cfg_attr(feature = "clippy", plugin(clippy))]
54
55extern crate ascii;
56#[macro_use]
57extern crate bitflags;
58#[macro_use]
59extern crate slog;
60extern crate sloggers;
61extern crate termion;
62extern crate vte;
63
64mod term_data;
65
66/// It's imported from ```ascii``` crate for convinience.
67pub use ascii::AsciiChar;
68pub use sloggers::types::Severity;
69use termion::async_stdin;
70use termion::raw::IntoRawMode;
71use vte::Parser;
72
73use term_data::TermData;
74use std::error::Error;
75use std::fmt::{self, Debug, Formatter};
76use std::io;
77use std::process::{Child, Command, Stdio};
78use std::env;
79use std::io::{BufReader, Read, Write};
80use std::str;
81use std::sync::{Arc, Mutex};
82use std::sync::atomic::{AtomicBool, Ordering};
83use std::sync::mpsc::{self, Receiver, Sender};
84use std::thread::{self, JoinHandle};
85use std::time::Duration;
86
87#[derive(Clone, Debug)]
88struct LogInfo {
89    fname: String,
90    sev: Severity,
91}
92
93impl Default for LogInfo {
94    fn default() -> LogInfo {
95        LogInfo {
96            fname: String::new(),
97            sev: Severity::Debug,
98        }
99    }
100}
101
102#[derive(Copy, Clone, Debug)]
103enum DrawType {
104    Terminal(Duration),
105    Null,
106}
107
108/// Game process builder, providing control over how a new process
109/// should be spawned.
110///
111/// Like ```std::process::Command```, A default configuration can be
112/// generated using Gamesetting::new(command name) and other settings
113/// can be added by builder methods.
114/// # Example
115/// ```no_run
116/// extern crate curses_game_wrapper as cgw;
117/// use cgw::{Reactor, ActionResult, AsciiChar, GameSetting, Severity};
118/// use std::time::Duration;
119/// fn main() {
120///     let loopnum = 50;
121///     let gs = GameSetting::new("rogue")
122///         .env("ROGUEUSER", "EmptyAI")
123///         .lines(24)
124///         .columns(80)
125///         .debug_file("debug.txt")
126///         .max_loop(loopnum + 1)
127///         .draw_on(Duration::from_millis(200));
128///     let game = gs.build();
129/// }
130/// ```
131#[derive(Clone, Debug)]
132pub struct GameSetting<'a> {
133    cmdname: String,
134    lines: usize,
135    columns: usize,
136    envs: Vec<(&'a str, &'a str)>,
137    args: Vec<&'a str>,
138    log_info: LogInfo,
139    timeout: Duration,
140    draw_type: DrawType,
141    max_loop: usize,
142}
143impl<'a> GameSetting<'a> {
144    /// Build GameSetting object with command name(like ```rogue```).
145    pub fn new(command_name: &str) -> Self {
146        GameSetting {
147            cmdname: String::from(command_name),
148            lines: 24,
149            columns: 80,
150            envs: Vec::new(),
151            args: Vec::new(),
152            log_info: LogInfo::default(),
153            timeout: Duration::from_millis(100),
154            draw_type: DrawType::Null,
155            max_loop: 100,
156        }
157    }
158    /// Set screen width of curses widow
159    pub fn columns(mut self, u: usize) -> Self {
160        self.columns = u;
161        self
162    }
163    /// Set screen height of curses window
164    pub fn lines(mut self, u: usize) -> Self {
165        self.lines = u;
166        self
167    }
168    /// Add command line argument
169    pub fn arg(mut self, s: &'a str) -> Self {
170        self.args.push(s);
171        self
172    }
173    /// Set environmental variable
174    pub fn env(mut self, s: &'a str, t: &'a str) -> Self {
175        self.envs.push((s, t));
176        self
177    }
178    /// Set multiple command line arguments
179    pub fn args<I>(mut self, i: I) -> Self
180    where
181        I: IntoIterator<Item = &'a str>,
182    {
183        let v: Vec<_> = i.into_iter().collect();
184        self.args = v;
185        self
186    }
187    /// Set multiple environmental variables
188    pub fn envs<I>(mut self, i: I) -> Self
189    where
190        I: IntoIterator<Item = (&'a str, &'a str)>,
191    {
192        let v: Vec<_> = i.into_iter().collect();
193        self.envs = v;
194        self
195    }
196    /// Draw game on terminal(Default: off).
197    /// You hanve to set duration of drawing.
198    pub fn draw_on(mut self, d: Duration) -> Self {
199        self.draw_type = DrawType::Terminal(d);
200        self
201    }
202    /// You can set debug file of this crate.
203    /// This is mainly for developper of this crate:)
204    pub fn debug_file(mut self, s: &str) -> Self {
205        self.log_info.fname = s.to_owned();
206        self
207    }
208    /// You can set debug level of this crate.
209    /// This is mainly for developper of this crate:)
210    pub fn debug_level(mut self, s: Severity) -> Self {
211        self.log_info.sev = s;
212        self
213    }
214    /// You can set timeout to game output.
215    /// It's setted to 0.1s by default.
216    pub fn timeout(mut self, d: Duration) -> Self {
217        self.timeout = d;
218        self
219    }
220    /// You can set max_loop of game.
221    /// It's setted to 100 by default.
222    pub fn max_loop(mut self, t: usize) -> Self {
223        self.max_loop = t;
224        self
225    }
226    /// Consume game setting and build GameEnv
227    pub fn build(self) -> GameEnv {
228        let dat = TermData::from_setting(&self);
229        let t = self.timeout;
230        let m = self.max_loop;
231        let d = self.draw_type;
232        GameEnv {
233            process: ProcHandler::from_setting(self),
234            term_data: dat,
235            timeout: t,
236            max_loop: m,
237            draw_type: d,
238        }
239    }
240}
241
242/// Result of the game action.
243/// ```Changed(Vec<Vec<u8>>)``` contains virtual terminal as buffer.
244#[derive(Clone)]
245pub enum ActionResult {
246    Changed(Vec<Vec<u8>>),
247    NotChanged,
248    GameEnded,
249}
250impl Debug for ActionResult {
251    fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
252        match *self {
253            ActionResult::Changed(ref buf) => {
254                write!(f, "ActionResult::Changed\n")?;
255                write!(f, "--------------------\n")?;
256                for v in buf {
257                    let s = str::from_utf8(v).unwrap();
258                    write!(f, "{}\n", s)?;
259                }
260                write!(f, "--------------------")
261            }
262            ActionResult::NotChanged => write!(f, "ActionResult::NotChanged"),
263            ActionResult::GameEnded => write!(f, "ActionResult::GameEnded"),
264        }
265    }
266}
267
268/// You have to implement ```Reactor``` for your AI to work.
269pub trait Reactor {
270    fn action(&mut self, action_result: ActionResult, turn: usize) -> Option<Vec<u8>>;
271}
272
273/// This is for spawning curses game as child process.
274///
275/// It stores inputs from the game and sends result to AI when its input handler timeouts.
276///
277/// The only usage is
278/// # Example
279/// ```no_run
280/// extern crate curses_game_wrapper as cgw;
281/// use cgw::{Reactor, ActionResult, AsciiChar, GameSetting};
282/// use std::time::Duration;
283/// fn main() {
284///     struct EmptyAI;
285///     impl Reactor for EmptyAI {
286///         fn action(&mut self, _screen: ActionResult, _turn: usize) -> Option<Vec<u8>> {
287///              None
288///         }
289///     }
290///     let gs = GameSetting::new("rogue")
291///         .env("ROGUEUSER", "EmptyAI")
292///         .lines(24)
293///         .columns(80)
294///         .debug_file("debug.txt")
295///         .max_loop(10)
296///         .draw_on(Duration::from_millis(200));
297///     let game = gs.build();
298///     let mut ai = EmptyAI { };
299///     game.play(&mut ai);
300/// }
301/// ```
302pub struct GameEnv {
303    process: ProcHandler,
304    term_data: TermData,
305    timeout: Duration,
306    max_loop: usize,
307    draw_type: DrawType,
308}
309impl GameEnv {
310    /// Start process and run AI.
311    ///
312    pub fn play<R: Reactor>(mut self, ai: &mut R) {
313        use mpsc::RecvTimeoutError;
314        macro_rules! send_or {
315            ($to:expr, $handle:expr) => (
316                if let Err(why) = $to.send_bytes($handle) {
317                    debug!(
318                        self.term_data.logger,
319                        concat!("can't send to ", stringify!($to), ": {}"),
320                        why.description()
321                    );
322                }
323            )
324        }
325        let proc_handle = self.process.run();
326        let mut viewer: Box<GameViewer> = match self.draw_type {
327            DrawType::Terminal(d) => Box::new(TerminalViewer::new(d)),
328            DrawType::Null => Box::new(EmptyViewer {}),
329        };
330        let viewer_handle = viewer.run();
331        let mut stdin = async_stdin().bytes();
332        let mut ctrl_c = false;
333
334        let mut parser = Parser::new();
335        let mut proc_dead = false;
336        let mut stored_map = None;
337        let mut cnt = 0;
338        while cnt < self.max_loop {
339            macro_rules! do_action {
340                ($act:expr) => {{
341                    cnt += 1;
342                    if let Some(bytes) = ai.action($act, cnt) {
343                        send_or!(self.process, &bytes);
344                    }
345                }}
346            }
347            let action_res = match self.process.rx.recv_timeout(self.timeout) {
348                Ok(rec) => match rec {
349                    Handle::Panicked => {
350                        send_or!(viewer, Handle::Panicked);
351                        panic!("panicked in child thread")
352                    }
353                    Handle::Zero => {
354                        debug!(self.term_data.logger, "read zero bytes");
355                        send_or!(viewer, Handle::Zero);
356                        proc_dead = true;
357                        ActionResult::GameEnded
358                    }
359                    Handle::Valid(ref r) => {
360                        send_or!(viewer, Handle::Valid(r));
361                        for c in r {
362                            parser.advance(&mut self.term_data, *c);
363                        }
364                        ActionResult::Changed(self.term_data.ret_screen())
365                    }
366                },
367                Err(err) => match err {
368                    RecvTimeoutError::Timeout => ActionResult::NotChanged,
369                    RecvTimeoutError::Disconnected => panic!("disconnected"),
370                },
371            };
372            trace!(self.term_data.logger, "{:?}, turn: {}", action_res, cnt);
373            match action_res {
374                ActionResult::GameEnded => do_action!(ActionResult::GameEnded),
375                // store inputs until timeout occurs
376                ActionResult::Changed(map) => stored_map = Some(map),
377                ActionResult::NotChanged => if let Some(map) = stored_map {
378                    do_action!(ActionResult::Changed(map));
379                    stored_map = None;
380                } else {
381                    do_action!(ActionResult::NotChanged);
382                },
383            }
384            if proc_dead {
385                trace!(self.term_data.logger, "Game ended in turn {}", cnt);
386                break;
387            }
388            if let Some(Ok(3)) = stdin.next() {
389                ctrl_c = true;
390                break;
391            }
392        }
393        if !proc_dead {
394            debug!(
395                self.term_data.logger,
396                "Game not ended and killed process forcibly"
397            );
398            self.process.kill();
399            send_or!(viewer, Handle::Zero);
400            let _ = ai.action(ActionResult::GameEnded, self.max_loop);
401        }
402        if !ctrl_c {
403            proc_handle.join().unwrap();
404            viewer_handle.join().unwrap();
405        }
406    }
407}
408
409// handles Sender and Reciever
410enum Handle<T> {
411    Panicked, // thread panicked
412    Zero,     // read 0 bytes (probably game ended)
413    Valid(T), // read 1 or more bytes
414}
415
416trait GameViewer {
417    fn run(&mut self) -> JoinHandle<()>;
418    fn send_bytes(&mut self, bytes: Handle<&[u8]>) -> Result<(), ViewerError>;
419}
420
421#[derive(Debug)]
422struct ViewerError(String);
423impl fmt::Display for ViewerError {
424    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
425        write!(f, "{}", self.0)
426    }
427}
428impl Error for ViewerError {
429    fn description(&self) -> &str {
430        &self.0
431    }
432}
433impl From<mpsc::SendError<Handle<Vec<u8>>>> for ViewerError {
434    fn from(e: mpsc::SendError<Handle<Vec<u8>>>) -> Self {
435        ViewerError(e.description().to_owned())
436    }
437}
438
439struct EmptyViewer {}
440
441impl GameViewer for EmptyViewer {
442    fn run(&mut self) -> JoinHandle<()> {
443        thread::spawn(move || {})
444    }
445    fn send_bytes(&mut self, _bytes: Handle<&[u8]>) -> Result<(), ViewerError> {
446        Ok(())
447    }
448}
449
450#[derive(Debug)]
451struct TerminalViewer {
452    tx: mpsc::Sender<Handle<Vec<u8>>>,
453    rx: Arc<Mutex<Receiver<Handle<Vec<u8>>>>>,
454    sleep_time: Arc<Duration>,
455}
456
457impl TerminalViewer {
458    fn new(d: Duration) -> Self {
459        let (tx, rx) = mpsc::channel();
460        let wrapped_recv = Arc::new(Mutex::new(rx));
461        TerminalViewer {
462            tx: tx,
463            rx: wrapped_recv,
464            sleep_time: Arc::new(d),
465        }
466    }
467}
468impl GameViewer for TerminalViewer {
469    fn run(&mut self) -> JoinHandle<()> {
470        let rx = Arc::clone(&self.rx);
471        let sleep = Arc::clone(&self.sleep_time);
472        env::set_var("TERM", "vt100");
473        thread::spawn(move || {
474            let receiver = rx.lock().unwrap();
475            while let Ok(game_input) = (*receiver).recv() {
476                match game_input {
477                    Handle::Valid(ref bytes) => {
478                        let s = str::from_utf8(bytes).unwrap();
479                        let mut stdout = io::stdout()
480                            .into_raw_mode()
481                            .expect("Couldn't get raw stdin");
482                        write!(stdout, "{}", s).expect("Couldn't write to stdin");
483                        stdout.flush().expect("Could not flush stdout");
484                    }
485                    Handle::Zero => break,
486                    Handle::Panicked => panic!("main thread panicked"),
487                }
488                thread::sleep(*sleep);
489            }
490        })
491    }
492    fn send_bytes(&mut self, b: Handle<&[u8]>) -> Result<(), ViewerError> {
493        let txclone = self.tx.clone();
494        let res = match b {
495            Handle::Zero => Handle::Zero,
496            Handle::Panicked => Handle::Panicked,
497            Handle::Valid(b) => Handle::Valid(b.to_owned()),
498        };
499        txclone.send(res)?;
500        Ok(())
501    }
502}
503
504#[derive(Debug)]
505struct ProcessError(String);
506
507impl fmt::Display for ProcessError {
508    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
509        write!(f, "{}", self.0)
510    }
511}
512
513impl Error for ProcessError {
514    fn description(&self) -> &str {
515        &self.0
516    }
517}
518
519impl From<io::Error> for ProcessError {
520    fn from(why: io::Error) -> Self {
521        ProcessError(why.description().to_owned())
522    }
523}
524
525// exec process
526struct ProcHandler {
527    my_proc: Child,
528    tx: Sender<Handle<Vec<u8>>>,
529    // note : Reciever blocks until some bytes wrote
530    rx: Receiver<Handle<Vec<u8>>>,
531    killed: Arc<AtomicBool>,
532}
533
534impl ProcHandler {
535    fn from_setting(g: GameSetting) -> ProcHandler {
536        let mut cmd = Command::new(&g.cmdname);
537        let cmd = cmd.args(g.args);
538        let cmd = cmd.env("LINES", format!("{}", g.lines));
539        let cmd = cmd.env("COLUMNS", format!("{}", g.columns));
540        let cmd = cmd.env("TERM", "vt100"); // You can override it by env
541        let cmd = cmd.envs(g.envs);
542        let cmd = cmd.stdin(Stdio::piped()).stdout(Stdio::piped());
543        let process = match cmd.spawn() {
544            Ok(p) => p,
545            Err(why) => panic!("couldn't spawn game: {}", why.description()),
546        };
547        let (tx, rx) = mpsc::channel();
548        ProcHandler {
549            my_proc: process,
550            tx: tx,
551            rx: rx,
552            killed: Arc::new(AtomicBool::new(false)),
553        }
554    }
555
556    fn run(&mut self) -> JoinHandle<()> {
557        let proc_out = self.my_proc.stdout.take().unwrap();
558        let txclone = self.tx.clone();
559        let ac = Arc::clone(&self.killed);
560        thread::spawn(move || {
561            let mut proc_reader = BufReader::new(proc_out);
562            const BUFSIZE: usize = 4096;
563            let mut readbuf = vec![0u8; BUFSIZE];
564            while !ac.load(Ordering::Relaxed) {
565                match proc_reader.read(&mut readbuf) {
566                    Err(why) => {
567                        txclone.send(Handle::Panicked).ok();
568                        panic!("couldn't read child stdout: {}", why.description())
569                    }
570                    Ok(0) => {
571                        txclone.send(Handle::Zero).ok();
572                        break;
573                    }
574                    Ok(BUFSIZE) => {
575                        txclone.send(Handle::Panicked).ok();
576                        panic!("Buffer is too small.")
577                    }
578                    Ok(n) => {
579                        txclone.send(Handle::Valid(readbuf[0..n].to_owned())).ok();
580                    }
581                }
582            }
583        })
584    }
585
586    fn send_bytes(&mut self, buf: &[u8]) -> Result<(), ProcessError> {
587        let stdin = self.my_proc.stdin.as_mut().unwrap();
588        stdin.write_all(buf)?;
589        Ok(())
590    }
591
592    fn kill(&mut self) {
593        self.my_proc.kill().unwrap();
594        let ac = Arc::clone(&self.killed);
595        ac.store(true, Ordering::Relaxed)
596    }
597}
598
599// Destractor (kill proc)
600impl Drop for ProcHandler {
601    fn drop(&mut self) {
602        self.my_proc.kill().unwrap();
603    }
604}
605
606#[cfg(test)]
607mod tests {
608    #[test]
609    #[ignore]
610    fn test_gameplay() {
611        use super::*;
612        struct EmptyAI {
613            loopnum: usize,
614        };
615        impl Reactor for EmptyAI {
616            fn action(&mut self, _screen: ActionResult, turn: usize) -> Option<Vec<u8>> {
617                let mut res = Vec::new();
618                match turn {
619                    val if val == self.loopnum - 1 => res.push(AsciiChar::CarriageReturn.as_byte()),
620                    val if val == self.loopnum - 2 => res.push(b'y'),
621                    val if val == self.loopnum - 3 => res.push(b'Q'),
622                    _ => res.push(b'j'),
623                };
624                Some(res)
625            }
626        }
627        let loopnum = 10;
628        let gs = GameSetting::new("rogue")
629            .env("ROGUEUSER", "EmptyAI")
630            .lines(24)
631            .columns(80)
632            .debug_file("debug.txt")
633            .debug_level(Severity::Trace)
634            .max_loop(loopnum + 1)
635            .draw_on(Duration::from_millis(100));
636        let game = gs.build();
637        let mut ai = EmptyAI { loopnum: loopnum };
638        game.play(&mut ai);
639    }
640}