Skip to content
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
5 changes: 3 additions & 2 deletions rustorio-engine/src/gamemodes.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
//! A game mode defines the starting resources and victory conditions for a game.

use crate::tick::Tick;
use crate::{resources::TokenOfCreation, tick::Tick};

/// The starting resources of a game mode. These are provided to the player at the beginning of the game.
pub trait StartingResources {
/// Called once at the start of the game before control is handed to the player to create the starting resources.
///
/// # Parameters
/// - `token`: Token that allows creating resources out of thin air for the duration of this function.
/// - `tick`: The current game tick. Since this method is called at the start of the game, this will always be tick 0.
fn init(tick: &Tick) -> Self;
fn init(token: &TokenOfCreation, tick: &Tick) -> Self;
}

/// A game mode defines the starting resources and victory conditions for a game.
Expand Down
3 changes: 2 additions & 1 deletion rustorio-engine/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ pub mod tick;

use std::{io::Write, net::TcpStream, sync::Once};

use resources::creation_token;
use rustorio_common::cli::{PORT_ENV_NAME, PlayOutput};

pub use crate::resources::{ResourceType, bundle, resource};
Expand All @@ -37,7 +38,7 @@ pub fn play<G: GameMode>(main: fn(Tick, G::StartingResources) -> (Tick, G::Victo
);
}
let tick = Tick::start(G::DEFAULT_MAX_TICK);
let start_resources = G::StartingResources::init(&tick);
let start_resources = G::StartingResources::init(creation_token(), &tick);
let (tick, _points) = main(tick, start_resources);
if let Ok(port) = std::env::var(PORT_ENV_NAME) {
let port = port.parse().unwrap_or_else(|error| panic!("Failed to pass env variable '{PORT_ENV_NAME}' as port. Env var value: '{port}' Error: {error:?}"));
Expand Down
38 changes: 24 additions & 14 deletions rustorio-engine/src/machine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
//! ```

use crate::{
recipe::{MultiBundleEx, Recipe, RecipeEx},
recipe::{MultiBundle, Recipe},
resources::{TokenOfCreation, creation_token},
tick::{Tick, TickSnapshot},
};

Expand Down Expand Up @@ -69,7 +70,7 @@ pub struct Machine<R: Recipe> {
crafting_time: u64,
}

impl<R: RecipeEx> Machine<R> {
impl<R: Recipe> Machine<R> {
fn new_inner(tick: TickSnapshot) -> Self {
Self {
inputs: Default::default(),
Expand All @@ -80,7 +81,8 @@ impl<R: RecipeEx> Machine<R> {
}

/// Build a new machine.
pub fn new(tick: &Tick) -> Self {
// Needs a token because this can be used to create resources by making a custom recipe.
pub fn new(_token: &TokenOfCreation, tick: &Tick) -> Self {
Self::new_inner(tick.snapshot())
}

Expand All @@ -96,20 +98,27 @@ impl<R: RecipeEx> Machine<R> {
&mut self.outputs
}

fn iter_inputs(&mut self) -> impl Iterator<Item = (&'static str, u32, &mut u32)> {
<R::InputBundle as MultiBundleEx>::iter(&mut self.inputs)
fn iter_inputs<'a>(
&'a mut self,
token: &'a TokenOfCreation,
) -> impl Iterator<Item = (&'static str, u32, &'a mut u32)> {
<R::InputBundle as MultiBundle>::iter_mut(token, &mut self.inputs)
}

fn iter_outputs(&mut self) -> impl Iterator<Item = (&'static str, u32, &mut u32)> {
<R::OutputBundle as MultiBundleEx>::iter(&mut self.outputs)
fn iter_outputs<'a>(
&'a mut self,
token: &'a TokenOfCreation,
) -> impl Iterator<Item = (&'static str, u32, &'a mut u32)> {
<R::OutputBundle as MultiBundle>::iter_mut(token, &mut self.outputs)
}

/// Changes the [`Recipe`](crate::recipe) of the machine.
/// Returns the original machine if the machine has any inputs or outputs.
pub fn change_recipe<R2: RecipeEx>(
pub fn change_recipe<R2: Recipe>(
mut self,
recipe: R2,
) -> Result<Machine<R2>, MachineNotEmptyError<Self>> {
let token = creation_token();
let _ = recipe;
fn find_nonempty<'a>(
mut iter: impl Iterator<Item = (&'static str, u32, &'a mut u32)>,
Expand All @@ -121,8 +130,8 @@ impl<R: RecipeEx> Machine<R> {
}

if let Some((resource_type, amount, location)) =
find_nonempty(self.iter_inputs(), BufferLocation::Input)
.or_else(|| find_nonempty(self.iter_outputs(), BufferLocation::Output))
find_nonempty(self.iter_inputs(token), BufferLocation::Input)
.or_else(|| find_nonempty(self.iter_outputs(token), BufferLocation::Output))
{
Err(MachineNotEmptyError {
machine: self,
Expand All @@ -137,26 +146,27 @@ impl<R: RecipeEx> Machine<R> {

fn tick(&mut self, tick: &Tick) {
let time_elapsed = self.tick.advance_to(tick).unwrap();
let token = creation_token();

self.crafting_time += time_elapsed;
let crafting_time = self.crafting_time;
let count = self
.iter_inputs()
.iter_inputs(token)
.map(|(_, needed, current)| *current / needed)
.chain((R::TIME > 0).then(|| (crafting_time / R::TIME).try_into().unwrap()))
.min()
.unwrap();

for (_, needed, current) in self.iter_inputs() {
for (_, needed, current) in self.iter_inputs(token) {
*current -= count * needed;
}
for (_, needed, current) in self.iter_outputs() {
for (_, needed, current) in self.iter_outputs(token) {
*current += count * needed;
}
self.crafting_time -= u64::from(count) * R::TIME;

if self
.iter_inputs()
.iter_inputs(token)
.any(|(_, needed, current)| *current < needed)
{
self.crafting_time = 0;
Expand Down
101 changes: 56 additions & 45 deletions rustorio-engine/src/recipe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ pub use rustorio_derive::{Recipe, recipe_doc};

use crate::{
ResourceType, Sealed,
resources::{Bundle, Resource},
resources::{Bundle, Resource, TokenOfCreation, creation_token},
tick::Tick,
};

Expand All @@ -19,19 +19,33 @@ pub trait MultiBundle: Sized + std::fmt::Debug {
const AMOUNTS: Self::AmountsType;

/// Count the number of bundle tuples available in the given resource tuple.
fn bundle_count(res: &Self::AsResources) -> u32;
fn bundle_count(res: &Self::AsResources) -> u32 {
Self::iter(res)
.map(|(_, expected, current)| current / expected)
.min()
.unwrap_or(u32::MAX)
}
/// Add the bundle tuple to the resource tuple.
fn add(res: &mut Self::AsResources, bundle: Self);
/// Pop a bundle tuple from a resource tuple, if there are enough resources.
fn bundle(res: &mut Self::AsResources) -> Option<Self>;
}

#[doc(hidden)]
pub trait MultiBundleEx: MultiBundle {
/// Factory function to create a new bundle tuple.
fn new_bundle() -> Self;
/// Create a new bundle tuple out of thin air.
///
/// For use in mods only, cannot be used from the game.
fn new_bundle(token: &TokenOfCreation) -> Self;

/// Iterate over the resources, returning for each the resource name, per-bundle expected
/// amount, and current amount.
fn iter(items: &Self::AsResources) -> impl Iterator<Item = (&'static str, u32, u32)>;

/// Iterate over the resources, giving direct mutable access to the amounts.
fn iter(items: &mut Self::AsResources) -> impl Iterator<Item = (&'static str, u32, &mut u32)>;
///
/// For use in mods only, cannot be used from the game.
fn iter_mut<'a>(
token: &'a TokenOfCreation,
items: &'a mut Self::AsResources,
) -> impl Iterator<Item = (&'static str, u32, &'a mut u32)>;
}

// Special untupled case, for e.g. tech recipes that don't return a tuple.
Expand All @@ -41,22 +55,23 @@ impl<R1: ResourceType, const N1: u32> MultiBundle for Bundle<R1, N1> {
type AmountsType = (u32,);
const AMOUNTS: Self::AmountsType = (N1,);

fn bundle_count(res: &Self::AsResources) -> u32 {
<(Self,) as MultiBundle>::bundle_count(res)
}
fn add(res: &mut Self::AsResources, bundle: Self) {
<(Self,) as MultiBundle>::add(res, (bundle,))
}
fn bundle(res: &mut Self::AsResources) -> Option<Self> {
<(Self,) as MultiBundle>::bundle(res).map(|(r,)| r)
}
}
impl<R1: ResourceType, const N1: u32> MultiBundleEx for Bundle<R1, N1> {
fn new_bundle() -> Self {
<(Self,) as MultiBundleEx>::new_bundle().0
fn new_bundle(token: &TokenOfCreation) -> Self {
<(Self,) as MultiBundle>::new_bundle(token).0
}
fn iter(items: &mut Self::AsResources) -> impl Iterator<Item = (&'static str, u32, &mut u32)> {
<(Self,) as MultiBundleEx>::iter(items)
fn iter(items: &Self::AsResources) -> impl Iterator<Item = (&'static str, u32, u32)> {
<(Self,) as MultiBundle>::iter(items)
}
fn iter_mut<'a>(
token: &'a TokenOfCreation,
items: &'a mut Self::AsResources,
) -> impl Iterator<Item = (&'static str, u32, &'a mut u32)> {
<(Self,) as MultiBundle>::iter_mut(token, items)
}
}

Expand Down Expand Up @@ -93,23 +108,13 @@ macro_rules! impl_multi_bundle {
$($amount,)*
);

fn bundle_count(res: &Self::AsResources) -> u32 {
[
$(
res.$n.amount() / $amount,
)*
].into_iter().min().unwrap_or(u32::MAX)
}
fn add(res: &mut Self::AsResources, bundle: Self) {
$(
res.$n += bundle.$n;
)*
}
fn bundle(res: &mut Self::AsResources) -> Option<Self> {
let enough_resources = true $(
&& res.$n.amount() >= $amount
)*;
if enough_resources {
if Self::bundle_count(res) >= 1 {
Some((
$(
res.$n.bundle().ok()?,
Expand All @@ -119,28 +124,38 @@ macro_rules! impl_multi_bundle {
None
}
}
}

#[allow(unused)]
impl<
$($ty: ResourceType, const $amount: u32),*
> MultiBundleEx for ($(Bundle<$ty, $amount>,)*)
{
#[allow(clippy::unused_unit)]
fn new_bundle() -> Self {
fn new_bundle(token: &TokenOfCreation) -> Self {
(
$(
replace_expr!($ty, crate::resources::bundle()),
replace_expr!($ty, crate::resources::bundle(token)),
)*
)
}
fn iter(items: &mut Self::AsResources) -> impl Iterator<Item = (&'static str, u32, &mut u32)> {
fn iter(
items: &Self::AsResources,
) -> impl Iterator<Item = (&'static str, u32, u32)> {
[
$(
(
<$ty as ResourceType>::NAME,
Self::AMOUNTS.$n,
items.$n.amount(),
),
)*
]
.into_iter()
}
fn iter_mut<'a>(
token: &'a TokenOfCreation,
items: &'a mut Self::AsResources,
) -> impl Iterator<Item = (&'static str, u32, &'a mut u32)> {
[
$(
(
<$ty as ResourceType>::NAME,
Self::AMOUNTS.$n,
crate::resources::resource_amount_mut(&mut items.$n),
items.$n.amount_mut(token),
),
)*
]
Expand Down Expand Up @@ -201,17 +216,13 @@ pub trait Recipe {
type OutputResources: std::fmt::Debug + Default;
}

#[doc(hidden)]
pub trait RecipeEx: Recipe<InputBundle: MultiBundleEx, OutputBundle: MultiBundleEx> {}
impl<R: Recipe<InputBundle: MultiBundleEx, OutputBundle: MultiBundleEx>> RecipeEx for R {}

/// A recipe that can be hand-crafted by the player.
pub trait HandRecipe: std::fmt::Debug + Sealed + RecipeEx {
pub trait HandRecipe: std::fmt::Debug + Sealed + Recipe {
/// Crafts the recipe by consuming the input bundle and producing the output bundle.
/// Advances the provided `Tick` by the recipe's time.
fn craft(tick: &mut Tick, inputs: Self::InputBundle) -> Self::OutputBundle {
let _ = inputs;
tick.advance_by(Self::TIME);
Self::OutputBundle::new_bundle()
Self::OutputBundle::new_bundle(creation_token())
}
}
4 changes: 2 additions & 2 deletions rustorio-engine/src/research.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ pub use rustorio_derive::{TechnologyEx, technology_doc};

use crate::{
ResourceType, Sealed,
recipe::{MultiBundle, MultiBundleEx, Recipe},
recipe::{MultiBundle, Recipe},
resources::{Bundle, Resource},
};

Expand All @@ -36,7 +36,7 @@ pub trait Technology: Sealed + Debug + Sized + TechnologyEx {
pub trait TechnologyEx {
/// A type guaranteed to contain exactly the input resources for one research point.
/// Used in hand crafting.
type InputBundle: MultiBundleEx;
type InputBundle: MultiBundle;
/// The amount of ticks it takes to create one research point for this technology.
const POINT_RECIPE_TIME: u64;
/// How many of this technology's research points (`ResearchPoint<T>`) are needed to complete the research.
Expand Down
Loading