sc2 0.2.0

organelle networks for StarCraft II Client API
Documentation

#[macro_use]
extern crate error_chain;
#[macro_use]
extern crate serde_derive;

extern crate organelle;
extern crate docopt;
extern crate glob;
extern crate serde;

extern crate sc2;

use std::path::PathBuf;
use std::rc::Rc;

use organelle::{ Organelle, Soma, Impulse, ResultExt, Dendrite };
use docopt::Docopt;
use sc2::{
    Result,
    Signal,
    Synapse,
    Axon,
    FrameData,
    PlayerSetup,
    Race,
    GameSettings,
    LauncherSettings,
    Map,
};

const VERSION: &'static str = env!("CARGO_PKG_VERSION");

pub const USAGE: &'static str = "
StarCraft II Rust API Example.

Usage:
  example (-h | --help)
  example [options]
  example --version

Options:
  -h --help                         Show this screen.
  --version                         Show version.
  --wine                            Use Wine to run StarCraft II (for Linux).
  -d <path> --dir=<path>            Path to the StarCraft II installation.
  -p <port> --port=<port>           Port to make StarCraft II listen on.
  -m <name> --map=<name>            Path to the StarCraft II map.
  -r --realtime                     Run StarCraft II in real time
  -s <count> --step-size=<count>    How many steps to take per call.
  --replay-dir=<path>               Path to a replay pack
";

#[derive(Debug, Deserialize)]
pub struct Args {
    pub flag_dir:                   Option<PathBuf>,
    pub flag_port:                  Option<u16>,
    pub flag_map:                   Option<PathBuf>,
    pub flag_replay_dir:            Option<PathBuf>,
    pub flag_wine:                  bool,
    pub flag_version:               bool,
    pub flag_realtime:              bool,
    pub flag_step_size:             Option<u32>,
}

pub fn get_launcher_settings(args: &Args) -> Result<LauncherSettings> {
    let default_settings = LauncherSettings::default();

    Ok(
        LauncherSettings {
            use_wine: args.flag_wine,
            dir: args.flag_dir.clone(),
            base_port: {
                if let Some(port) = args.flag_port {
                    port
                }
                else {
                    default_settings.base_port
                }
            }
        }
    )
}

pub fn get_game_settings(args: &Args) -> Result<GameSettings> {
    let map = match args.flag_map {
        Some(ref map) => Map::LocalMap(map.clone()),
        None => bail!("no map specified")
    };

    Ok(GameSettings { map: map })
}

pub enum MarineMicroSoma {
    Init(Init),
    Setup(Setup),

    InGame(InGame),
}

impl MarineMicroSoma {
    pub fn organelle(interval: u32) -> Result<Self> {
        Ok(
            MarineMicroSoma::Init(
                Init {
                    axon: Axon::new(
                        vec![
                            Dendrite::RequireOne(Synapse::Agent),
                        ],
                        vec![ ],
                    )?,
                    interval: interval,
                }
            )
        )
    }
}

impl Soma for MarineMicroSoma {
    type Signal = Signal;
    type Synapse = Synapse;

    fn update(self, msg: Impulse<Signal, Synapse>)
        -> organelle::Result<MarineMicroSoma>
    {
        match self {
            MarineMicroSoma::Init(state) => state.update(msg),
            MarineMicroSoma::Setup(state) => state.update(msg),

            MarineMicroSoma::InGame(state) => state.update(msg),
        }.chain_err(
            || organelle::ErrorKind::SomaError
        )
    }
}

pub struct Init {
    axon:           Axon,
    interval:       u32,
}

impl Init {
    fn update(mut self, msg: Impulse<Signal, Synapse>)
        -> Result<MarineMicroSoma>
    {
        if let Some(msg) = self.axon.update(msg)? {
            match msg {
                Impulse::Start => Setup::setup(self.axon, self.interval),

                _ => bail!("unexpected message")
            }
        }
        else {
            Ok(MarineMicroSoma::Init(self))
        }
    }
}

pub struct Setup {
    axon:           Axon,
    interval:       u32,
}

impl Setup {
    fn setup(axon: Axon, interval: u32) -> Result<MarineMicroSoma> {
        Ok(MarineMicroSoma::Setup(Setup { axon: axon, interval: interval }))
    }

    fn update(mut self, msg: Impulse<Signal, Synapse>)-> Result<MarineMicroSoma> {
        if let Some(msg) = self.axon.update(msg)? {
            match msg {
                Impulse::Signal(_, Signal::RequestPlayerSetup(_)) => {
                    self.axon.send_req_input(
                        Synapse::Agent,
                        Signal::PlayerSetup(
                            PlayerSetup::Player {
                                race: Race::Terran
                            }
                        )
                    )?;

                    Ok(MarineMicroSoma::Setup(self))
                },
                Impulse::Signal(_, Signal::RequestUpdateInterval) => {
                    self.axon.send_req_input(
                        Synapse::Agent, Signal::UpdateInterval(self.interval)
                    )?;

                    Ok(MarineMicroSoma::Setup(self))
                },
                Impulse::Signal(_, Signal::GameStarted) => {
                    InGame::start(self.axon)
                },

                _ => bail!("unexpected message"),
            }
        }
        else {
            Ok(MarineMicroSoma::Setup(self))
        }
    }
}

pub struct InGame {
    axon:           Axon,
}

impl InGame {
    fn start(axon: Axon) -> Result<MarineMicroSoma> {
        Ok(MarineMicroSoma::InGame(InGame { axon: axon }))
    }

    fn update(mut self, msg: Impulse<Signal, Synapse>)
        -> Result<MarineMicroSoma>
    {
        if let Some(msg) = self.axon.update(msg)? {
            match msg {
                Impulse::Signal(_, Signal::Observation(frame)) => {
                    self.on_frame(frame)
                },

                _ => bail!("unexpected message")
            }
        }
        else {
            Ok(MarineMicroSoma::InGame(self))
        }
    }

    fn on_frame(self, _: Rc<FrameData>) -> Result<MarineMicroSoma> {
        self.axon.send_req_input(Synapse::Agent, Signal::UpdateComplete)?;

        Ok(MarineMicroSoma::InGame(self))
    }
}

quick_main!(
    || -> Result<()> {
        let args: Args = Docopt::new(USAGE)
            .and_then(|d| d.deserialize())
            .unwrap_or_else(|e| e.exit())
        ;

        if args.flag_version {
            println!("bot-micro version {}", VERSION);
            return Ok(())
        }

        let mut organelle = Organelle::new(
            sc2::MeleeSoma::organelle(
                sc2::MeleeSettings {
                    launcher: get_launcher_settings(&args)?,
                    players: (
                        sc2::AgentSoma::organelle(
                            MarineMicroSoma::organelle(
                                args.flag_step_size.unwrap_or(1)
                            )?
                        )?,
                        sc2::ComputerSoma::sheath(
                            sc2::Race::Zerg, sc2::Difficulty::VeryEasy
                        )?
                    ),
                    suite: sc2::MeleeSuite::OneAndDone(
                        get_game_settings(&args)?
                    )
                }
            )?
        );

        organelle.add_soma(sc2::CtrlcBreakerSoma::sheath()?);

        organelle.run()?;

        Ok(())
    }
);