Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Upgrade player's fighters between battles #498

Merged
merged 1 commit into from
Jun 18, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
148 changes: 117 additions & 31 deletions src/core/campaign.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
use std::collections::HashMap;

use rand::{seq::SliceRandom, thread_rng};
use serde::{Deserialize, Serialize};

use crate::core::battle::{component::ObjType, scenario::Scenario, state::BattleResult, PlayerId};
Expand All @@ -23,6 +26,12 @@ pub struct Award {
pub recruits: Vec<ObjType>,
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub enum Action {
Recruit(ObjType),
Upgrade { from: ObjType, to: ObjType },
}

#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct CampaignNode {
pub scenario: Scenario,
Expand Down Expand Up @@ -50,23 +59,23 @@ pub struct State {
scenarios: Vec<CampaignNode>,
current_scenario_index: i32,
mode: Mode,

agents: Vec<ObjType>,

last_battle_casualties: Vec<ObjType>,
recruits: Vec<ObjType>,
upgrades: HashMap<ObjType, Vec<ObjType>>,
actions: Vec<Action>,
}

impl State {
pub fn from_plan(plan: Plan) -> Self {
pub fn new(plan: Plan, upgrades: HashMap<ObjType, Vec<ObjType>>) -> Self {
assert!(!plan.nodes.is_empty(), "No scenarios");
Self {
current_scenario_index: 0,
scenarios: plan.nodes,
mode: Mode::ReadyForBattle,
agents: plan.initial_agents,
last_battle_casualties: vec![],
recruits: vec![],
actions: vec![],
upgrades,
}
}

Expand Down Expand Up @@ -96,19 +105,26 @@ impl State {
&self.agents
}

pub fn recruit(&mut self, typename: ObjType) {
assert_eq!(self.mode(), Mode::PreparingForBattle);
assert!(self.recruits.contains(&typename));
self.agents.push(typename);
self.recruits = Vec::new();
self.mode = Mode::ReadyForBattle;
pub fn available_actions(&self) -> &[Action] {
&self.actions
}

pub fn available_recruits(&self) -> &[ObjType] {
if self.mode != Mode::PreparingForBattle {
assert!(self.recruits.is_empty());
pub fn exectute_action(&mut self, action: Action) {
assert_eq!(self.mode(), Mode::PreparingForBattle);
assert!(self.actions.contains(&action));
match action {
Action::Recruit(typename) => self.agents.push(typename),
Action::Upgrade { from, to } => {
self.agents
.iter()
.position(|a| a == &from)
.map(|e| self.agents.remove(e))
.unwrap_or_else(|| panic!("Can't find agent {:?}", from));
self.agents.push(to);
}
}
&self.recruits
self.actions = Vec::new();
self.mode = Mode::ReadyForBattle;
}

pub fn report_battle_results(&mut self, result: &BattleResult) -> Result<(), ()> {
Expand Down Expand Up @@ -140,12 +156,34 @@ impl State {
self.mode = Mode::Won;
} else {
let i = self.current_scenario_index as usize;
self.recruits = self.scenarios[i].award.recruits.clone();

for recruit in &self.scenarios[i].award.recruits {
self.actions.push(Action::Recruit(recruit.clone()));
}

// Add one random upgrade (if available).
{
let mut upgrade_candidates = Vec::new();
for agent in &self.agents {
for (from, upgrades) in &self.upgrades {
if from == agent {
for upgrade in upgrades {
let from = agent.clone();
let to = upgrade.clone();
upgrade_candidates.push(Action::Upgrade { from, to });
}
}
}
}
if let Some(final_action) = upgrade_candidates.choose(&mut thread_rng()) {
self.actions.push(final_action.clone());
}
}

self.current_scenario_index += 1;

self.mode = Mode::PreparingForBattle;
if self.available_recruits().is_empty() {
if self.available_actions().is_empty() {
// Skip the preparation step.
self.mode = Mode::ReadyForBattle;
}
Expand All @@ -157,14 +195,16 @@ impl State {

#[cfg(test)]
mod tests {
use std::collections::HashMap;

use crate::core::{
battle::{
component::ObjType,
scenario::{self, Line, ObjectsGroup, Scenario},
state::BattleResult,
PlayerId,
},
campaign::{Award, CampaignNode, Mode, Plan, State},
campaign::{Action, Award, CampaignNode, Mode, Plan, State},
};

type GroupTuple<'a> = (Option<PlayerId>, &'a str, Option<Line>, i32);
Expand All @@ -186,6 +226,10 @@ mod tests {
vec!["swordsman".into(), "alchemist".into()]
}

fn upgrades() -> HashMap<ObjType, Vec<ObjType>> {
HashMap::new()
}

fn campaign_plan_short() -> Plan {
let initial_agents = initial_agents();
let nodes = {
Expand Down Expand Up @@ -251,13 +295,13 @@ mod tests {
nodes: Vec::new(),
initial_agents: Vec::new(),
};
let _state = State::from_plan(empty_plan);
let _state = State::new(empty_plan, upgrades());
}

#[test]
fn short_happy_path() {
let mut state = State::from_plan(campaign_plan_short());
assert!(state.available_recruits().is_empty());
let mut state = State::new(campaign_plan_short(), upgrades());
assert!(state.available_actions().is_empty());
assert_eq!(state.mode(), Mode::ReadyForBattle);
let battle_result = BattleResult {
winner_id: PlayerId(0),
Expand All @@ -269,8 +313,8 @@ mod tests {

#[test]
fn short_fail_path() {
let mut state = State::from_plan(campaign_plan_short());
assert!(state.available_recruits().is_empty());
let mut state = State::new(campaign_plan_short(), upgrades());
assert!(state.available_actions().is_empty());
assert_eq!(state.mode(), Mode::ReadyForBattle);
let battle_result = BattleResult {
winner_id: PlayerId(1),
Expand All @@ -283,7 +327,7 @@ mod tests {

#[test]
fn bad_survivors() {
let mut state = State::from_plan(campaign_plan_short());
let mut state = State::new(campaign_plan_short(), upgrades());
let battle_result = BattleResult {
winner_id: PlayerId(1),
survivor_types: vec!["imp".into()],
Expand All @@ -293,7 +337,7 @@ mod tests {

#[test]
fn bad_battle_win_no_survivors() {
let mut state = State::from_plan(campaign_plan_short());
let mut state = State::new(campaign_plan_short(), upgrades());
let battle_result = BattleResult {
winner_id: PlayerId(0),
survivor_types: vec![],
Expand All @@ -302,9 +346,9 @@ mod tests {
}

#[test]
fn upgrade() {
let mut state = State::from_plan(campaign_plan_two_battles());
assert!(state.available_recruits().is_empty());
fn recruit_and_casualty() {
let mut state = State::new(campaign_plan_two_battles(), upgrades());
assert!(state.available_actions().is_empty());
assert_eq!(state.mode(), Mode::ReadyForBattle);
{
let battle_result = BattleResult {
Expand All @@ -313,11 +357,14 @@ mod tests {
};
state.report_battle_results(&battle_result).unwrap();
}
assert_eq!(state.available_recruits(), &["spearman".into()]);
assert_eq!(
state.available_actions(),
&[Action::Recruit("spearman".into())]
);
assert!(state.last_battle_casualties().is_empty());
assert_eq!(state.mode(), Mode::PreparingForBattle);
state.recruit("spearman".into());
assert!(state.available_recruits().is_empty());
state.exectute_action(Action::Recruit("spearman".into()));
assert!(state.available_actions().is_empty());
assert_eq!(state.mode(), Mode::ReadyForBattle);
{
let battle_result = BattleResult {
Expand All @@ -329,4 +376,43 @@ mod tests {
assert_eq!(state.mode(), Mode::Won);
assert_eq!(state.last_battle_casualties(), &["spearman".into()]);
}

#[test]
fn upgrade_and_casualty() {
let mut upgrades = HashMap::new();
upgrades.insert("swordsman".into(), vec!["heavy_swordsman".into()]);
let mut state = State::new(campaign_plan_two_battles(), upgrades);
assert!(state.available_actions().is_empty());
assert_eq!(state.mode(), Mode::ReadyForBattle);
{
let battle_result = BattleResult {
winner_id: PlayerId(0),
survivor_types: initial_agents(),
};
state.report_battle_results(&battle_result).unwrap();
}
let action_upgrade = Action::Upgrade {
from: "swordsman".into(),
to: "heavy_swordsman".into(),
};
let action_reqruit = Action::Recruit("spearman".into());
assert_eq!(
state.available_actions(),
&[action_reqruit, action_upgrade.clone()]
);
assert!(state.last_battle_casualties().is_empty());
assert_eq!(state.mode(), Mode::PreparingForBattle);
state.exectute_action(action_upgrade);
assert!(state.available_actions().is_empty());
assert_eq!(state.mode(), Mode::ReadyForBattle);
{
let battle_result = BattleResult {
winner_id: PlayerId(0),
survivor_types: vec!["alchemist".into()],
};
state.report_battle_results(&battle_result).unwrap();
}
assert_eq!(state.mode(), Mode::Won);
assert_eq!(state.last_battle_casualties(), &["heavy_swordsman".into()]);
}
}
2 changes: 1 addition & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ fn main() -> ZResult {
const APP_ID: &str = "zemeroth";
const APP_AUTHOR: &str = "ozkriff";
const ASSETS_DIR_NAME: &str = "assets";
const ASSETS_HASHSUM: &str = "2b90cce9970532b3a2e5678e650e38e8";
const ASSETS_HASHSUM: &str = "95a1bd6209b1b2b33cead60f40873c58";

fn enable_backtrace() {
if std::env::var("RUST_BACKTRACE").is_err() {
Expand Down
27 changes: 18 additions & 9 deletions src/screen/campaign.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use crate::{
state::BattleResult,
PlayerId,
},
campaign::{Mode, State},
campaign::{Action, Mode, State},
},
screen::{self, Screen, Transition},
utils, ZResult,
Expand All @@ -30,7 +30,7 @@ enum Message {
Menu,
StartBattle,
AgentInfo(ObjType),
Recruit(ObjType),
Action(Action),
}

const FONT_SIZE: f32 = utils::font_size();
Expand Down Expand Up @@ -119,7 +119,8 @@ pub struct Campaign {
impl Campaign {
pub fn new(context: &mut Context) -> ZResult<Self> {
let plan = utils::deserialize_from_file(context, "/campaign_01.ron")?;
let state = State::from_plan(plan);
let upgrades = utils::deserialize_from_file(context, "/agent_upgrades.ron")?;
let state = State::new(plan, upgrades);
let font = utils::default_font(context);
let gui = basic_gui(context, font)?;
let mut this = Self {
Expand Down Expand Up @@ -175,13 +176,18 @@ impl Campaign {
let text = Box::new(Text::new(("Choose:", self.font, FONT_SIZE)));
layout.add(Box::new(ui::Label::new(context, text, h)?));
}
for agent_type in self.state.available_recruits() {
for action in self.state.available_actions() {
let mut line = ui::HLayout::new();
{
let text = format!("- [Recruit {}]", agent_type.0);
let text = match action {
Action::Recruit(agent_type) => format!("- [Recruit {}]", agent_type.0),
Action::Upgrade { from, to } => {
format!("- [Upgrade {} to {}]", from.0, to.0)
}
};
let text = Box::new(Text::new((text.as_str(), self.font, FONT_SIZE)));
let sender = self.gui.sender();
let message = Message::Recruit(agent_type.clone());
let message = Message::Action(action.clone());
let button = ui::Button::new(context, text, h, sender, message)?;
line.add(Box::new(button));
}
Expand All @@ -194,7 +200,10 @@ impl Campaign {
}
{
let text = Box::new(Text::new(("[i]", self.font, FONT_SIZE)));
let message = Message::AgentInfo(agent_type.clone());
let message = match action {
Action::Recruit(agent_type) => Message::AgentInfo(agent_type.clone()),
Action::Upgrade { to, .. } => Message::AgentInfo(to.clone()),
};
let sender = self.gui.sender();
let button = ui::Button::new(context, text, h, sender, message)?;
line.add(Box::new(button));
Expand Down Expand Up @@ -328,8 +337,8 @@ impl Screen for Campaign {
let screen = self.start_battle(context)?;
Ok(Transition::Push(screen))
}
Some(Message::Recruit(typename)) => {
self.state.recruit(typename);
Some(Message::Action(action)) => {
self.state.exectute_action(action);
let new_mode = self.state.mode();
self.set_mode(context, new_mode)?;
Ok(Transition::None)
Expand Down