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

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