Fully async dice rolling. Also remove more unnecessary stuff.

This commit is contained in:
projectmoon 2020-10-17 20:24:24 +00:00 committed by ProjectMoon
parent 3c2a37c0f7
commit d5aae3ebb1
3 changed files with 68 additions and 79 deletions

View File

@ -1,7 +1,8 @@
use crate::context::Context; use crate::context::Context;
use crate::db::DataError::KeyDoesNotExist; use crate::db::DataError::KeyDoesNotExist;
use crate::error::BotError; use crate::error::BotError;
use crate::roll::{Roll, Rolled}; use crate::roll::Rolled;
use futures::stream::{self, StreamExt, TryStreamExt};
use itertools::Itertools; use itertools::Itertools;
use std::convert::TryFrom; use std::convert::TryFrom;
use std::fmt; use std::fmt;
@ -119,17 +120,18 @@ pub struct DicePool {
pub(crate) modifiers: DicePoolModifiers, pub(crate) modifiers: DicePoolModifiers,
} }
fn calculate_dice_amount(pool: &DicePoolWithContext) -> Result<i32, BotError> { async fn calculate_dice_amount<'a>(pool: &'a DicePoolWithContext<'a>) -> Result<i32, BotError> {
let dice_amount: Result<i32, BotError> = pool let stream = stream::iter(&pool.0.amounts);
.0
.amounts let dice_amount: Result<i32, BotError> = stream
.iter() .then(|amount| async move {
.map(|amount| match &amount.element { match &amount.element {
Element::Number(num_dice) => Ok(*num_dice * amount.operator.mult()), Element::Number(num_dice) => Ok(*num_dice * amount.operator.mult()),
Element::Variable(variable) => handle_variable(&pool.1, &variable), Element::Variable(variable) => handle_variable(&pool.1, &variable).await,
}
}) })
.collect::<Result<Vec<i32>, _>>() .try_fold(0, |total, num_dice| async move { Ok(total + num_dice) })
.map(|numbers| numbers.iter().sum()); .await;
dice_amount dice_amount
} }
@ -257,14 +259,6 @@ impl DicePoolRoll {
/// Attach a Context to a dice pool. Needed for database access. /// Attach a Context to a dice pool. Needed for database access.
pub struct DicePoolWithContext<'a>(pub &'a DicePool, pub &'a Context); pub struct DicePoolWithContext<'a>(pub &'a DicePool, pub &'a Context);
impl Roll for DicePoolWithContext<'_> {
type Output = Result<RolledDicePool, BotError>;
fn roll(&self) -> Result<RolledDicePool, BotError> {
roll_dice(self, &mut RngDieRoller(rand::thread_rng()))
}
}
impl Rolled for DicePoolRoll { impl Rolled for DicePoolRoll {
fn rolled_value(&self) -> i32 { fn rolled_value(&self) -> i32 {
self.successes() self.successes()
@ -358,29 +352,32 @@ fn roll_die<R: DieRoller>(roller: &mut R, pool: &DicePool) -> Vec<i32> {
results results
} }
fn handle_variable(ctx: &Context, variable: &str) -> Result<i32, BotError> { async fn handle_variable(ctx: &Context, variable: &str) -> Result<i32, BotError> {
ctx.db ctx.db
.get_user_variable(&ctx.room_id, &ctx.username, variable) .get_user_variable(&ctx.room_id, &ctx.username, variable)
.await
.map_err(|e| match e { .map_err(|e| match e {
KeyDoesNotExist(_) => DiceRollingError::VariableNotFound(variable.to_owned()).into(), KeyDoesNotExist(_) => DiceRollingError::VariableNotFound(variable.to_owned()).into(),
_ => e.into(), _ => e.into(),
}) })
} }
fn roll_dice<'a, R: DieRoller>(pool: &DicePool, num_dice: i32, roller: &mut R) -> Vec<i32> {
(0..num_dice)
.flat_map(|_| roll_die(roller, &pool))
.collect()
}
///Roll the dice in a dice pool, according to behavior documented in the various rolling ///Roll the dice in a dice pool, according to behavior documented in the various rolling
///methods. ///methods.
fn roll_dice<R: DieRoller>( pub async fn roll_pool(pool: &DicePoolWithContext<'_>) -> Result<RolledDicePool, BotError> {
pool: &DicePoolWithContext,
roller: &mut R,
) -> Result<RolledDicePool, BotError> {
if pool.0.amounts.len() > 100 { if pool.0.amounts.len() > 100 {
return Err(DiceRollingError::ExpressionTooLarge.into()); return Err(DiceRollingError::ExpressionTooLarge.into());
} }
let num_dice = calculate_dice_amount(&pool)?; let num_dice = calculate_dice_amount(&pool).await?;
let rolls: Vec<i32> = (0..num_dice) let mut roller = RngDieRoller(rand::thread_rng());
.flat_map(|_| roll_die(roller, &pool.0)) let rolls = roll_dice(&pool.0, num_dice, &mut roller);
.collect();
Ok(RolledDicePool::from(&pool.0, num_dice, rolls)) Ok(RolledDicePool::from(&pool.0, num_dice, rolls))
} }
@ -510,36 +507,23 @@ mod tests {
#[test] #[test]
pub fn no_explode_roll_test() { pub fn no_explode_roll_test() {
let db = Database::new(&sled::open(tempdir().unwrap()).unwrap());
let ctx = Context::new(&db, "roomid", "username", "message");
let pool = DicePool::easy_pool(1, DicePoolQuality::NoExplode); let pool = DicePool::easy_pool(1, DicePoolQuality::NoExplode);
let pool_with_ctx = DicePoolWithContext(&pool, &ctx);
let mut roller = SequentialDieRoller::new(vec![10, 8]); let mut roller = SequentialDieRoller::new(vec![10, 8]);
let result = roll_dice(&pool_with_ctx, &mut roller); let roll = roll_dice(&pool, 1, &mut roller);
assert!(result.is_ok()); assert_eq!(vec![10], roll);
let roll = result.unwrap().roll;
assert_eq!(vec![10], roll.rolls());
} }
#[test] #[tokio::test]
pub fn number_of_dice_equality_test() { async fn number_of_dice_equality_test() {
let db = Database::new(&sled::open(tempdir().unwrap()).unwrap()); let num_dice = 5;
let ctx = Context::new(&db, "roomid", "username", "message"); let rolls = vec![1, 2, 3, 4, 5];
let pool = DicePool::easy_pool(5, DicePoolQuality::NoExplode); let pool = DicePool::easy_pool(5, DicePoolQuality::TenAgain);
let pool_with_ctx = DicePoolWithContext(&pool, &ctx); let rolled_pool = RolledDicePool::from(&pool, num_dice, rolls);
assert_eq!(5, rolled_pool.num_dice);
let mut roller = SequentialDieRoller::new(vec![1, 2, 3, 4, 5]);
let result = roll_dice(&pool_with_ctx, &mut roller);
assert!(result.is_ok());
let roll = result.unwrap();
assert_eq!(5, roll.num_dice);
} }
#[test] #[tokio::test]
fn rejects_large_expression_test() { async fn rejects_large_expression_test() {
let db = Database::new(&sled::open(tempdir().unwrap()).unwrap()); let db = Database::new(&sled::open(tempdir().unwrap()).unwrap());
let ctx = Context::new(&db, "roomid", "username", "message"); let ctx = Context::new(&db, "roomid", "username", "message");
@ -554,9 +538,7 @@ mod tests {
let pool = DicePool::new(amounts, DicePoolModifiers::default()); let pool = DicePool::new(amounts, DicePoolModifiers::default());
let pool_with_ctx = DicePoolWithContext(&pool, &ctx); let pool_with_ctx = DicePoolWithContext(&pool, &ctx);
let result = roll_pool(&pool_with_ctx).await;
let mut roller = SequentialDieRoller::new(vec![1, 2, 3, 4, 5]);
let result = roll_dice(&pool_with_ctx, &mut roller);
assert!(matches!( assert!(matches!(
result, result,
Err(BotError::DiceRollingError( Err(BotError::DiceRollingError(
@ -565,12 +547,13 @@ mod tests {
)); ));
} }
#[test] #[tokio::test]
fn can_resolve_variables_test() { async fn can_resolve_variables_test() {
let db = Database::new(&sled::open(tempdir().unwrap()).unwrap()); let db = Database::new(&sled::open(tempdir().unwrap()).unwrap());
let ctx = Context::new(&db, "roomid", "username", "message"); let ctx = Context::new(&db, "roomid", "username", "message");
db.set_user_variable(&ctx.room_id, &ctx.username, "myvariable", 10) db.set_user_variable(&ctx.room_id, &ctx.username, "myvariable", 10)
.await
.expect("could not set myvariable to 10"); .expect("could not set myvariable to 10");
let amounts = vec![Amount { let amounts = vec![Amount {
@ -581,7 +564,7 @@ mod tests {
let pool = DicePool::new(amounts, DicePoolModifiers::default()); let pool = DicePool::new(amounts, DicePoolModifiers::default());
let pool_with_ctx = DicePoolWithContext(&pool, &ctx); let pool_with_ctx = DicePoolWithContext(&pool, &ctx);
assert_eq!(calculate_dice_amount(&pool_with_ctx).unwrap(), 10); assert_eq!(calculate_dice_amount(&pool_with_ctx).await.unwrap(), 10);
} }
//DicePool tests //DicePool tests

View File

@ -1,4 +1,4 @@
use crate::cofd::dice::{DicePool, DicePoolWithContext}; use crate::cofd::dice::{roll_pool, DicePool, DicePoolWithContext};
use crate::context::Context; use crate::context::Context;
use crate::db::DataError; use crate::db::DataError;
use crate::dice::ElementExpression; use crate::dice::ElementExpression;
@ -69,7 +69,7 @@ impl Command for PoolRollCommand {
async fn execute(&self, ctx: &Context) -> Execution { async fn execute(&self, ctx: &Context) -> Execution {
let pool_with_ctx = DicePoolWithContext(&self.0, ctx); let pool_with_ctx = DicePoolWithContext(&self.0, ctx);
let roll_result = pool_with_ctx.roll(); let roll_result = roll_pool(&pool_with_ctx).await;
let (plain, html) = match roll_result { let (plain, html) = match roll_result {
Ok(rolled_pool) => { Ok(rolled_pool) => {
@ -121,7 +121,11 @@ impl Command for GetVariableCommand {
async fn execute(&self, ctx: &Context) -> Execution { async fn execute(&self, ctx: &Context) -> Execution {
let name = &self.0; let name = &self.0;
let value = match ctx.db.get_user_variable(&ctx.room_id, &ctx.username, name) { let value = match ctx
.db
.get_user_variable(&ctx.room_id, &ctx.username, name)
.await
{
Ok(num) => format!("{} = {}", name, num), Ok(num) => format!("{} = {}", name, num),
Err(DataError::KeyDoesNotExist(_)) => format!("{} is not set", name), Err(DataError::KeyDoesNotExist(_)) => format!("{} is not set", name),
Err(e) => format!("error getting {}: {}", name, e), Err(e) => format!("error getting {}: {}", name, e),
@ -146,7 +150,8 @@ impl Command for SetVariableCommand {
let value = self.1; let value = self.1;
let result = ctx let result = ctx
.db .db
.set_user_variable(&ctx.room_id, &ctx.username, name, value); .set_user_variable(&ctx.room_id, &ctx.username, name, value)
.await;
let content = match result { let content = match result {
Ok(_) => format!("{} = {}", name, value), Ok(_) => format!("{} = {}", name, value),
@ -172,6 +177,7 @@ impl Command for DeleteVariableCommand {
let value = match ctx let value = match ctx
.db .db
.delete_user_variable(&ctx.room_id, &ctx.username, name) .delete_user_variable(&ctx.room_id, &ctx.username, name)
.await
{ {
Ok(()) => format!("{} now unset", name), Ok(()) => format!("{} now unset", name),
Err(DataError::KeyDoesNotExist(_)) => format!("{} is not currently set", name), Err(DataError::KeyDoesNotExist(_)) => format!("{} is not currently set", name),
@ -235,39 +241,39 @@ mod tests {
#[test] #[test]
fn chance_die_is_not_malformed() { fn chance_die_is_not_malformed() {
assert!(Command::parse("!chance").is_ok()); assert!(parse("!chance").is_ok());
} }
#[test] #[test]
fn roll_malformed_expression_test() { fn roll_malformed_expression_test() {
assert!(Command::parse("!roll 1d20asdlfkj").is_err()); assert!(parse("!roll 1d20asdlfkj").is_err());
assert!(Command::parse("!roll 1d20asdlfkj ").is_err()); assert!(parse("!roll 1d20asdlfkj ").is_err());
} }
#[test] #[test]
fn roll_dice_pool_malformed_expression_test() { fn roll_dice_pool_malformed_expression_test() {
assert!(Command::parse("!pool 8abc").is_err()); assert!(parse("!pool 8abc").is_err());
assert!(Command::parse("!pool 8abc ").is_err()); assert!(parse("!pool 8abc ").is_err());
} }
#[test] #[test]
fn pool_whitespace_test() { fn pool_whitespace_test() {
Command::parse("!pool ns3:8 ").expect("was error"); parse("!pool ns3:8 ").expect("was error");
Command::parse(" !pool ns3:8").expect("was error"); parse(" !pool ns3:8").expect("was error");
Command::parse(" !pool ns3:8 ").expect("was error"); parse(" !pool ns3:8 ").expect("was error");
} }
#[test] #[test]
fn help_whitespace_test() { fn help_whitespace_test() {
Command::parse("!help stuff ").expect("was error"); parse("!help stuff ").expect("was error");
Command::parse(" !help stuff").expect("was error"); parse(" !help stuff").expect("was error");
Command::parse(" !help stuff ").expect("was error"); parse(" !help stuff ").expect("was error");
} }
#[test] #[test]
fn roll_whitespace_test() { fn roll_whitespace_test() {
Command::parse("!roll 1d4 + 5d6 -3 ").expect("was error"); parse("!roll 1d4 + 5d6 -3 ").expect("was error");
Command::parse("!roll 1d4 + 5d6 -3 ").expect("was error"); parse("!roll 1d4 + 5d6 -3 ").expect("was error");
Command::parse(" !roll 1d4 + 5d6 -3 ").expect("was error"); parse(" !roll 1d4 + 5d6 -3 ").expect("was error");
} }
} }

View File

@ -39,7 +39,7 @@ impl Database {
Database { db: db.clone() } Database { db: db.clone() }
} }
pub fn get_user_variable( pub async fn get_user_variable(
&self, &self,
room_id: &str, room_id: &str,
username: &str, username: &str,
@ -61,7 +61,7 @@ impl Database {
} }
} }
pub fn set_user_variable( pub async fn set_user_variable(
&self, &self,
room_id: &str, room_id: &str,
username: &str, username: &str,
@ -74,7 +74,7 @@ impl Database {
Ok(()) Ok(())
} }
pub fn delete_user_variable( pub async fn delete_user_variable(
&self, &self,
room_id: &str, room_id: &str,
username: &str, username: &str,