From 67bed45ba8f6ae887ea2cc10b671bf415f879330 Mon Sep 17 00:00:00 2001 From: David Wylie Date: Mon, 8 Jan 2024 15:06:56 +0000 Subject: [PATCH] =?UTF-8?q?Refactored=20to=20introduce=20operation=20resul?= =?UTF-8?q?t=20and=20allow=20ui=20improvement=20and=E2=80=A6=20(#17)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Refactored to introduce operation results and allow ui improvement and web output formatter. * linting * linting and extract function for removing prefix * fix import issue. --- roll_witch/dice_bot/event_listener.py | 5 +- roll_witch/main.py | 7 +- roll_witch/rolling/command/__init__.py | 27 ++++-- roll_witch/rolling/command/basic.py | 55 +++++++---- roll_witch/rolling/command/regex.py | 80 +++++++--------- roll_witch/rolling/command/shadow_run.py | 42 +++++---- roll_witch/rolling/command/token.py | 36 +++---- roll_witch/rolling/output/__init__.py | 2 - roll_witch/rolling/output/base.py | 27 ++---- roll_witch/rolling/output/operation.py | 27 +++--- roll_witch/rolling/output/standard.py | 4 +- roll_witch/rolling/output/target.py | 6 +- roll_witch/rolling/output/web_operation.py | 93 +++++++++++++++++++ roll_witch/rolling/protocols/__init__.py | 5 +- roll_witch/rolling/protocols/operation.py | 8 +- .../rolling/protocols/operation_factory.py | 9 -- roll_witch/rolling/protocols/result.py | 14 +++ roll_witch/rolling/roller/__init__.py | 4 +- roll_witch/rolling/roller/operation_result.py | 17 ++-- roll_witch/rolling/roller/result.py | 3 + roll_witch/web_app/roller.py | 22 +++-- roll_witch/web_app/static/styles.css | 26 ++++++ roll_witch/web_app/templates/roller.jinja2 | 41 ++++++-- tests/test_basic_operation.py | 14 ++- tests/test_operation_shadowrun.py | 8 +- tests/test_operation_standard.py | 8 +- tests/test_output_parser.py | 46 +++++++-- tests/test_token_output_writer.py | 24 ++--- 28 files changed, 440 insertions(+), 220 deletions(-) create mode 100644 roll_witch/rolling/output/web_operation.py delete mode 100644 roll_witch/rolling/protocols/operation_factory.py diff --git a/roll_witch/dice_bot/event_listener.py b/roll_witch/dice_bot/event_listener.py index f64df19..5508ed8 100644 --- a/roll_witch/dice_bot/event_listener.py +++ b/roll_witch/dice_bot/event_listener.py @@ -28,7 +28,10 @@ async def on_message(self, message): roll_string=roll_string, user=message.author.display_name, ) - await message.channel.send(response) + message_to_send = command.format_output( + roll_result=response, user=message.author.display_name + ) + await message.channel.send(message_to_send) except ValueError: await message.channel.send( f" {message.author.display_name}: Invalid Command" diff --git a/roll_witch/main.py b/roll_witch/main.py index 70914e1..20bd39b 100644 --- a/roll_witch/main.py +++ b/roll_witch/main.py @@ -1,3 +1,5 @@ +import os + from roll_witch.dice_bot import bot from roll_witch.web_app import router import asyncio @@ -6,7 +8,10 @@ loop = asyncio.get_event_loop() loop.run_until_complete(router.start_app()) - loop.run_until_complete(bot.start_bot()) + if os.getenv("DISABLE_BOT") == "true": + print("Bot is disabled") + else: + loop.run_until_complete(bot.start_bot()) try: loop.run_forever() finally: diff --git a/roll_witch/rolling/command/__init__.py b/roll_witch/rolling/command/__init__.py index ef231d2..6c503ba 100644 --- a/roll_witch/rolling/command/__init__.py +++ b/roll_witch/rolling/command/__init__.py @@ -1,13 +1,16 @@ -from . import basic, token, regex, shadow_run +from .basic import BasicOperation +from .regex import RegexOperation +from .shadow_run import ShadowRunOperation +from .token import TokenOperation operations = { - "!r-t": token, - "!r-r": regex, - "!rb": basic, - "!r-b": basic, - "!roll": token, - "!r": token, - "!sr": shadow_run, + "!r-t": TokenOperation(), + "!r-r": RegexOperation(), + "!rb": BasicOperation(), + "!r-b": BasicOperation(), + "!roll": TokenOperation(), + "!r": TokenOperation(), + "!sr": ShadowRunOperation(), } @@ -15,10 +18,16 @@ def clean_command(command_string): return command_string.lower().replace("! ", "!").lstrip() +def remove_prefix(command_string, prefix): + if command_string.startswith(prefix): + return command_string[len(prefix):] + return command_string.lstrip() + + def get_command(message_content: str): clean_command_string = clean_command(message_content) for prefix, op_getter in operations.items(): if clean_command_string.startswith(prefix): - operation_input = clean_command_string[len(prefix):].lstrip() + operation_input = remove_prefix(clean_command_string, prefix) return op_getter, operation_input return None, message_content diff --git a/roll_witch/rolling/command/basic.py b/roll_witch/rolling/command/basic.py index 061de0f..08c9d2a 100644 --- a/roll_witch/rolling/command/basic.py +++ b/roll_witch/rolling/command/basic.py @@ -1,12 +1,12 @@ from roll_witch.rolling.input import get_basic_rpg_parser from roll_witch.rolling.output import TargetedOutputWriter -from roll_witch.rolling.roller import TargetedRoller +from roll_witch.rolling.roller import TargetedRoller, OperationRollResults from math import ceil, floor -from roll_witch.rolling.protocols import Result +from roll_witch.rolling.protocols import Operation, OperationResult class BasicOutputWriter(TargetedOutputWriter): - def build_success_string(self, roll_result: Result): + def build_success_string(self, roll_result: OperationResult): target_number = abs(roll_result.spec.target_number) if roll_result.total <= ceil(target_number * 0.05): @@ -27,20 +27,35 @@ def build_success_string(self, roll_result: Result): return "Failed" -def execute(roll_string: str, user: str): - try: - spec = get_spec(roll_string) - roll_result = TargetedRoller().roll(spec) - output = BasicOutputWriter().write_output(roll_result, user) - if len(output) > 2000: - raise ValueError() - return output - except ValueError: - raise Exception("Your answer is just too big to give you") - except Exception as e: - raise Exception(f"{e}") - - -def get_spec(roll_string): - parser = get_basic_rpg_parser() - return parser.parse(roll_string) +class BasicOperation(Operation): + def __init__(self): + super().__init__() + self.roller = TargetedRoller() + self.output_writer = BasicOutputWriter() + self.parser = get_basic_rpg_parser() + + def execute(self, roll_string: str, user: str) -> OperationResult: + try: + spec = self.get_spec(roll_string) + result = OperationRollResults(spec) + roll_result = self.roller.roll(spec) + result.append_roll_result(roll_result) + return result + except ValueError: + raise Exception("Your answer is just too big to give you") + except Exception as e: + raise Exception(f"{e}") + + def format_output(self, result: OperationResult, user): + try: + output = self.output_writer.write_output(result, user) + if len(output) > 2000: + raise ValueError() + return output + except ValueError: + raise Exception("Your answer is just too big to give you") + except Exception as e: + raise Exception(f"{e}") + + def get_spec(self, roll_string): + return self.parser.parse(roll_string) diff --git a/roll_witch/rolling/command/regex.py b/roll_witch/rolling/command/regex.py index 85cc5da..29c644a 100644 --- a/roll_witch/rolling/command/regex.py +++ b/roll_witch/rolling/command/regex.py @@ -1,47 +1,39 @@ from roll_witch.rolling.input import get_regex_parser from roll_witch.rolling.output import TargetedOutputWriter, StandardOutputWriter from roll_witch.rolling.roller import TargetedRoller, StandardRoller - - -def execute(roll_string: str, user: str): - roll_spec = get_spec(roll_string) - if roll_spec.has_target(): - return do_targeted_roll(roll_spec, user) - else: - return do_standard_roll(roll_spec, user) - - -def get_spec(roll_string): - parser = get_regex_parser() - roll_spec = parser.parse(roll_string) - return roll_spec - - -def do_targeted_roll(spec, user): - try: - roller = TargetedRoller() - output = TargetedOutputWriter() - roll_result = roller.roll(spec) - output = output.write_output(roll_result, user) - if len(output) > 2000: - raise ValueError() - return output - except ValueError: - raise Exception("Your answer is just too big to give you") - except Exception as e: - raise Exception(f"{e}") - - -def do_standard_roll(spec, user): - try: - roller = StandardRoller() - output = StandardOutputWriter() - roll_result = roller.roll(spec) - output = output.write_output(roll_result, user) - if len(output) > 2000: - raise ValueError() - return output - except ValueError: - raise Exception("Your answer is just too big to give you") - except Exception as e: - raise Exception(f"{e}") +from roll_witch.rolling.protocols import Operation, OperationResult +from roll_witch.rolling.roller import OperationRollResults + + +class RegexOperation(Operation): + def __init__(self): + self.name = "Regular Expression Roll" + self.targeted_roller = TargetedRoller() + self.standard_roller = StandardRoller() + self.targeted_output_writer = TargetedOutputWriter() + self.standard_output_writer = StandardOutputWriter() + self.parser = get_regex_parser() + + def execute(self, roll_string: str, user: str) -> OperationResult: + try: + roll_spec = self.parser.parse(roll_string) + result = OperationRollResults(roll_spec) + if roll_spec.has_target(): + result.append_roll_result(self.targeted_roller.roll(roll_spec)) + result.met_target = self.targeted_roller.met_target(roll_spec, result.total) + else: + result.append_roll_result(self.standard_roller.roll(roll_spec)) + return result + except Exception as e: + raise Exception(f"{e}") + + def format_output(self, result: OperationResult, user): + try: + if result.had_target(): + return self.targeted_output_writer.write_output(result, user) + else: + return self.standard_output_writer.write_output(result, user) + except ValueError: + raise Exception("Your answer is just too big to give you") + except Exception as e: + raise Exception(f"{e}") diff --git a/roll_witch/rolling/command/shadow_run.py b/roll_witch/rolling/command/shadow_run.py index ee08f72..ed87706 100644 --- a/roll_witch/rolling/command/shadow_run.py +++ b/roll_witch/rolling/command/shadow_run.py @@ -2,10 +2,11 @@ from roll_witch.rolling.output import OperationOutputWriter from roll_witch.rolling.roller import RollResult, RollSpec from roll_witch.rolling.roller import StandardRoller -from roll_witch.rolling.roller.operation_result import OperationResult +from roll_witch.rolling.roller.operation_result import OperationRollResults +from roll_witch.rolling.protocols import Operation, OperationResult -class ShadowRunResult(OperationResult): +class ShadowRunRollResults(OperationRollResults): def had_target(self) -> bool: return False @@ -16,21 +17,28 @@ def _apply_roll_to_total(self, result: RollResult): self.roll_total += result.roll_total -def execute(roll_string: str, user: str): - spec = get_spec(roll_string) - roller = StandardRoller() - output_parser = OperationOutputWriter() - result = ShadowRunResult(spec) - for part in spec.parts: - part.target_number = spec.target_number - result.append_roll_result(roller.roll(part)) +class ShadowRunOperation(Operation): + def __init__(self): + super().__init__() + self.name = "Shadow Run Roll" + self.parser = get_token_parser() + self.roller = StandardRoller() + self.output_parser = OperationOutputWriter() - return output_parser.write_output(result, user) + def execute(self, roll_string: str, user: str): + spec = self.get_spec(roll_string) + result = ShadowRunRollResults(spec) + for part in spec.parts: + part.target_number = spec.target_number + result.append_roll_result(self.roller.roll(part)) + return result -def get_spec(roll_string): - parser = get_token_parser() - roll_spec = parser.parse(roll_string) - if not roll_spec.target_number: - roll_spec.add_part(RollSpec(target_number=5, operation=None)) - return roll_spec + def format_output(self, result: OperationResult, user) -> str: + return self.output_parser.write_output(result, user) + + def get_spec(self, roll_string): + roll_spec = self.parser.parse(roll_string) + if not roll_spec.target_number: + roll_spec.add_part(RollSpec(target_number=5, operation=None)) + return roll_spec diff --git a/roll_witch/rolling/command/token.py b/roll_witch/rolling/command/token.py index 31c315f..14299e2 100644 --- a/roll_witch/rolling/command/token.py +++ b/roll_witch/rolling/command/token.py @@ -4,25 +4,29 @@ StandardRoller, TargetedRoller, ) -from roll_witch.rolling.roller.operation_result import OperationResult +from roll_witch.rolling.roller.operation_result import OperationRollResults +from roll_witch.rolling.protocols import Operation -def execute(roll_string: str, user: str): - spec = get_spec(roll_string) - roller = StandardRoller() - target_roller = TargetedRoller() - output_parser = OperationOutputWriter() - result = OperationResult(spec) - for part in spec.parts: - result.append_roll_result(roller.roll(part)) +class TokenOperation(Operation): + def __init__(self): + super().__init__() + self.name = "Standard Dice Roll" + self.parser = get_token_parser() + self.roller = StandardRoller() + self.target_roller = TargetedRoller() + self.output_parser = OperationOutputWriter() - if spec.has_target(): - result.met_target = target_roller.met_target(spec, result.total) + def execute(self, roll_string: str, user: str): + spec = self.parser.parse(roll_string) + result = OperationRollResults(spec) + for part in spec.parts: + result.append_roll_result(self.roller.roll(part)) - return output_parser.write_output(result, user) + if spec.has_target(): + result.met_target = self.target_roller.met_target(spec, result.total) + return result -def get_spec(roll_string): - parser = get_token_parser() - roll_spec = parser.parse(roll_string) - return roll_spec + def format_output(self, roll_result, user) -> str: + return self.output_parser.write_output(roll_result, user) diff --git a/roll_witch/rolling/output/__init__.py b/roll_witch/rolling/output/__init__.py index 8927581..2dd0bba 100644 --- a/roll_witch/rolling/output/__init__.py +++ b/roll_witch/rolling/output/__init__.py @@ -1,11 +1,9 @@ from .standard import StandardOutputWriter from .target import TargetedOutputWriter from .operation import OperationOutputWriter -from .base import OutputParser __all__ = [ "StandardOutputWriter", "TargetedOutputWriter", "OperationOutputWriter", - "OutputParser", ] diff --git a/roll_witch/rolling/output/base.py b/roll_witch/rolling/output/base.py index 0dd537a..dd66f31 100644 --- a/roll_witch/rolling/output/base.py +++ b/roll_witch/rolling/output/base.py @@ -1,26 +1,17 @@ -from roll_witch.rolling.roller import RollResult -from roll_witch.rolling.protocols import Result from abc import ABC, abstractmethod -from typing import Protocol - - -class OutputParser(Protocol): - def write_output(self, roll_result: Result, user: str): - return "Unknown" +from ..protocols.result import OperationResult class BaseOutputWriter(ABC): - def write_output(self, roll_result: Result, user): - total_string = self.build_total_string(roll_result) - return self.build_result_string(roll_result, total_string, user) + def write_output(self, result: OperationResult, user): + total_string = self.build_total_string(result) + return self.build_result_string(result, total_string, user) - def build_total_string(self, roll_result: Result): - if isinstance(roll_result, RollResult): - modifier_string = roll_result.formatted_modifier() - return f"{roll_result.rolls} = {roll_result.roll_total}{modifier_string}" - else: - raise Exception("Invalid Output parser for given data") + def build_total_string(self, result: OperationResult): + roll_result = result.rolls[0] + modifier_string = roll_result.formatted_modifier() + return f"{roll_result.rolls} = {roll_result.roll_total}{modifier_string}" @abstractmethod - def build_result_string(self, roll_result: Result, total_string, user): + def build_result_string(self, roll_result: OperationResult, total_string, user): return "Unsupported" diff --git a/roll_witch/rolling/output/operation.py b/roll_witch/rolling/output/operation.py index 950a807..af376d4 100644 --- a/roll_witch/rolling/output/operation.py +++ b/roll_witch/rolling/output/operation.py @@ -1,23 +1,18 @@ -from .base import OutputParser from roll_witch.rolling.roller import RollResult -from roll_witch.rolling.protocols import Result -from roll_witch.rolling.roller.operation_result import OperationResult +from ..protocols.result import OperationResult -class OperationOutputWriter(OutputParser): - def write_output(self, result: Result, user: str) -> str: - if isinstance(result, OperationResult): - parts = [] - for index, roll_result in enumerate(result.rolls): - operator_string = self.get_operator(index, roll_result) - value_string = self.get_value(roll_result) - parts.append(f"{operator_string}{value_string}") +class OperationOutputWriter: + def write_output(self, result: OperationResult, user: str) -> str: + parts = [] + for index, roll_result in enumerate(result.rolls): + operator_string = self.get_operator(index, roll_result) + value_string = self.get_value(roll_result) + parts.append(f"{operator_string}{value_string}") - target_string = self.get_target_string(result) - roll_string = "".join(parts) - return f"{user} Roll: {roll_string} Result: {result.total}{target_string}" - else: - raise Exception("Invalid Output parser for given data") + target_string = self.get_target_string(result) + roll_string = "".join(parts) + return f"{user} Roll: {roll_string} Result: {result.total}{target_string}" def get_target_string(self, result): if result.had_target(): diff --git a/roll_witch/rolling/output/standard.py b/roll_witch/rolling/output/standard.py index 548d403..3ed25d6 100644 --- a/roll_witch/rolling/output/standard.py +++ b/roll_witch/rolling/output/standard.py @@ -1,7 +1,7 @@ -from roll_witch.rolling.protocols import Result from .base import BaseOutputWriter +from ..protocols.result import OperationResult class StandardOutputWriter(BaseOutputWriter): - def build_result_string(self, roll_result: Result, total_string, user): + def build_result_string(self, roll_result: OperationResult, total_string, user): return f"{user} Roll: {total_string} Result: {roll_result.total}" diff --git a/roll_witch/rolling/output/target.py b/roll_witch/rolling/output/target.py index 713afe4..1dce761 100644 --- a/roll_witch/rolling/output/target.py +++ b/roll_witch/rolling/output/target.py @@ -1,12 +1,12 @@ -from roll_witch.rolling.protocols import Result from .base import BaseOutputWriter +from ..protocols.result import OperationResult class TargetedOutputWriter(BaseOutputWriter): - def build_success_string(self, roll_result: Result): + def build_success_string(self, roll_result: OperationResult): return "Success" if roll_result.met_target else "Failed" - def build_result_string(self, roll_result: Result, total_string, user): + def build_result_string(self, roll_result: OperationResult, total_string, user): success_string = self.build_success_string(roll_result) return ( f"{user} " diff --git a/roll_witch/rolling/output/web_operation.py b/roll_witch/rolling/output/web_operation.py new file mode 100644 index 0000000..88cb8b4 --- /dev/null +++ b/roll_witch/rolling/output/web_operation.py @@ -0,0 +1,93 @@ +from dataclasses import dataclass +from roll_witch.rolling.roller import RollResult +from ..protocols.result import OperationResult +from ..roller import RollSpec + + +@dataclass +class WebRollResult: + result: str + rolled: str + met_target: str + target: str + total: str + + +@dataclass +class WebResult: + request: str + roller: str + rolls: [WebRollResult] + target: str + met_target: str + total: str + + +class WebOperationOutputWriter: + def write_output( + self, operation_request: str, result: OperationResult, roller: str + ) -> WebResult: + roll_results = [] + for index, roll_result in enumerate(result.rolls): + operator_string = self.get_operator(index, roll_result) + value_string = self.get_value(roll_result) + if value_string: + roll_results.append( + WebRollResult( + rolled=self.get_roll_string(roll_result.roll_spec), + result=f"{operator_string}{value_string}", + met_target=self.get_met_target_string(roll_result), + target=self.get_target_string(roll_result), + total=f"{roll_result.total}", + ) + ) + + return WebResult( + request=operation_request, + roller=roller, + rolls=roll_results, + total=f"{result.total}", + target=self.get_target_string(result), + met_target=self.get_met_target_string(result), + ) + + def get_roll_string(self, roll_spec: RollSpec): + dice_count = roll_spec.dice_count if roll_spec.dice_count else "" + dice_sides = f"d{roll_spec.dice_sides}" if roll_spec.dice_sides else "" + modifier = ( + f"{roll_spec.operator}{roll_spec.dice_modifier}" + if roll_spec.dice_modifier > 0 + else "" + ) + return f"{dice_count}{dice_sides} {modifier}" + + def get_met_target_string(self, result): + if result.had_target(): + if result.met_target: + met_target_string = "Success" + else: + met_target_string = "Failure" + else: + met_target_string = "" + return met_target_string + + def get_target_string(self, result): + if result.had_target(): + target_string = f" (Target: {result.spec.target_number}) " + else: + target_string = "" + return target_string + + def get_value(self, roll_result: RollResult): + if roll_result.rolls: + return f"{roll_result.rolls}" + elif roll_result.roll_spec.has_modifier(): + return roll_result.roll_spec.dice_modifier + else: + return "" + + def get_operator(self, index, roll_result): + if index > 0 and roll_result.operator: + return f" {roll_result.operator} " + else: + return "" diff --git a/roll_witch/rolling/protocols/__init__.py b/roll_witch/rolling/protocols/__init__.py index e3ae0c8..5e7e216 100644 --- a/roll_witch/rolling/protocols/__init__.py +++ b/roll_witch/rolling/protocols/__init__.py @@ -1,9 +1,8 @@ from .dice_modifier import DiceModifier from .dice_set import DiceSet -from .result import Result +from .result import Result, OperationResult from .targetable import Targetable from .operation import Operation -from .operation_factory import OperationFactory __all__ = [ "DiceSet", @@ -11,5 +10,5 @@ "Result", "Targetable", "Operation", - "OperationFactory", + "OperationResult", ] diff --git a/roll_witch/rolling/protocols/operation.py b/roll_witch/rolling/protocols/operation.py index a4838e4..8e78714 100644 --- a/roll_witch/rolling/protocols/operation.py +++ b/roll_witch/rolling/protocols/operation.py @@ -1,6 +1,12 @@ from typing import Protocol +from .result import OperationResult class Operation(Protocol): - def execute(self) -> str: + name: str + + def execute(self, roll_string: str, user: str) -> OperationResult: + raise Exception("Not implemented yet") + + def format_output(self, roll_result, user) -> str: raise Exception("Not implemented yet") diff --git a/roll_witch/rolling/protocols/operation_factory.py b/roll_witch/rolling/protocols/operation_factory.py deleted file mode 100644 index 671b768..0000000 --- a/roll_witch/rolling/protocols/operation_factory.py +++ /dev/null @@ -1,9 +0,0 @@ -from typing import Protocol -from .operation import Operation - - -class OperationFactory(Protocol): - def get_roll_operation( - self, roll_type: str, roll_string: str, user: str - ) -> Operation: - raise Exception("Not implemented yet") diff --git a/roll_witch/rolling/protocols/result.py b/roll_witch/rolling/protocols/result.py index 42af06e..e2468c7 100644 --- a/roll_witch/rolling/protocols/result.py +++ b/roll_witch/rolling/protocols/result.py @@ -7,3 +7,17 @@ class Result(Protocol): roll_total: int spec: Targetable met_target: bool + + +class OperationResult(Protocol): + rolls: [Result] + total: int + roll_total: int + spec: Targetable + met_target: bool + + def append_roll_result(self, result: Result) -> None: + raise Exception("Not implemented yet") + + def had_target(self) -> bool: + return False diff --git a/roll_witch/rolling/roller/__init__.py b/roll_witch/rolling/roller/__init__.py index 73eb400..7ee8563 100644 --- a/roll_witch/rolling/roller/__init__.py +++ b/roll_witch/rolling/roller/__init__.py @@ -2,12 +2,12 @@ from .target import TargetedRoller from .result import RollResult from .spec import RollSpec -from .operation_result import OperationResult +from .operation_result import OperationRollResults __all__ = [ "StandardRoller", "TargetedRoller", "RollResult", - "OperationResult", + "OperationRollResults", "RollSpec", ] diff --git a/roll_witch/rolling/roller/operation_result.py b/roll_witch/rolling/roller/operation_result.py index 1438d99..cec582f 100644 --- a/roll_witch/rolling/roller/operation_result.py +++ b/roll_witch/rolling/roller/operation_result.py @@ -1,9 +1,10 @@ -from roll_witch.rolling.input.spec.operation import OperationSpec -from roll_witch.rolling.roller import RollResult +from ..input.spec.operation import OperationSpec +from . import RollResult +from ..protocols import Result, OperationResult -class OperationResult: - rolls: [RollResult] +class OperationRollResults(OperationResult): + rolls: [Result] def __init__(self, spec: OperationSpec) -> None: super().__init__() @@ -13,11 +14,15 @@ def __init__(self, spec: OperationSpec) -> None: self.spec = spec self.met_target = False - def append_roll_result(self, result: RollResult): + def append_roll_result(self, result: Result): self._apply_roll_to_total(result) self.rolls.append(result) - def _apply_roll_to_total(self, result: RollResult): + def _apply_roll_to_total(self, result: Result): + if isinstance(result, RollResult): + self._apply_roll_result_to_total(result) + + def _apply_roll_result_to_total(self, result): if result.roll_spec.operator == "+": self.total += result.total self.roll_total += result.roll_total diff --git a/roll_witch/rolling/roller/result.py b/roll_witch/rolling/roller/result.py index 7013279..605d137 100644 --- a/roll_witch/rolling/roller/result.py +++ b/roll_witch/rolling/roller/result.py @@ -39,3 +39,6 @@ def formatted_modifier(self) -> str: @property def operator(self): return self.roll_spec.operator + + def had_target(self) -> bool: + return self.spec.has_target() diff --git a/roll_witch/web_app/roller.py b/roll_witch/web_app/roller.py index 85df4f3..038ee7e 100644 --- a/roll_witch/web_app/roller.py +++ b/roll_witch/web_app/roller.py @@ -2,29 +2,33 @@ from aiohttp.web import Request from roll_witch.rolling import command +from rolling.output.web_operation import WebOperationOutputWriter @aiohttp_jinja2.template("roller.jinja2") async def roll(request: Request): data = await request.post() try: - roll_operation = data["roll_operation"] - if not (roll_operation.startswith("!r")): - roll_operation = f"!r {roll_operation}" - + roll_type = data["roll_type"] + roll_target = f"t{data["roll_target"]}" if data["roll_target"] else "" + roll_operation = f"{roll_type} {data["roll_operation"]} {roll_target}" bot_operation, roll_string = command.get_command(message_content=roll_operation) + print(f"Roll Request: {roll_string}") if bot_operation: operation_output = bot_operation.execute( roll_string=roll_string, user="", ) - print(f"Output: {operation_output}") + output_formatter = WebOperationOutputWriter() + output = output_formatter.write_output( + operation_request=roll_string, + result=operation_output, + roller=bot_operation.name, + ) + print(f"Output: {output}") return { - "output": { - "roll_request": roll_string, - "roll_result": operation_output, - } + "output": output, } except ValueError: return { diff --git a/roll_witch/web_app/static/styles.css b/roll_witch/web_app/static/styles.css index 79a4b3c..5a0dd5a 100644 --- a/roll_witch/web_app/static/styles.css +++ b/roll_witch/web_app/static/styles.css @@ -29,6 +29,7 @@ form { label { margin-bottom: 5px; /* Adds spacing between label and input */ + margin-right: 5px; /* Adds spacing between label and input */ } input[type="text"] { @@ -36,6 +37,20 @@ input[type="text"] { border: 1px solid #ccc; /* Creates a visible border */ border-radius: 5px; /* Softens corners */ margin-bottom: 10px; /* Adds spacing below input */ + min-width: 200px; /* Ensures input field is wide enough */ +} + +select { + padding: 10px; /* Adds padding to input field */ + border: 1px solid #ccc; /* Creates a visible border */ + border-radius: 5px; /* Softens corners */ + margin-bottom: 10px; /* Adds spacing below input */ + min-width: 200px; +} + +fieldset { + margin-bottom: 10px; /* Adds spacing below fieldset */ + display: grid; } button[type="submit"] { @@ -45,6 +60,7 @@ button[type="submit"] { border: none; /* Removes default border */ border-radius: 5px; /* Softens corners */ cursor: pointer; /* Indicates clickability */ + width: 100% } main section { @@ -170,4 +186,14 @@ a:hover { .article { width: 60%; } +} + +.Success { + color: green; + font-weight: bold; +} + +.Failure { + color: red; + font-weight: bold; } \ No newline at end of file diff --git a/roll_witch/web_app/templates/roller.jinja2 b/roll_witch/web_app/templates/roller.jinja2 index 2f21a0f..e40dc9a 100644 --- a/roll_witch/web_app/templates/roller.jinja2 +++ b/roll_witch/web_app/templates/roller.jinja2 @@ -19,18 +19,43 @@

Error

{{ output.error }}

{% endif %} -

Roll Request

-

{{ output.roll_request }}

-

Roll Result

-

{{ output.roll_result }}

+

{{ output.roller }}

{{ output.request }}
+

Result

{{ output.total }} {{ output.target }}{{output.met_target}}
+

Breakdown

+ + + + + + + {% for roll in output.rolls %} + + + + + + {% endfor %} +
TotalDice RolledResult
{{ roll.total }}{{ roll.rolled }}{{ roll.result }}
{% endif %}
-

Roll

- - - +
+ What would you like to Roll now? + + +
+ + +
+ + +
+ +
diff --git a/tests/test_basic_operation.py b/tests/test_basic_operation.py index e088b73..396ae76 100644 --- a/tests/test_basic_operation.py +++ b/tests/test_basic_operation.py @@ -1,27 +1,31 @@ from unittest import TestCase from unittest.mock import patch -from roll_witch.rolling import command +from roll_witch.rolling.command import BasicOperation class TestStandardOperation(TestCase): @patch("random.randint") def test_easy_dice_roll(self, mock_roll): mock_roll.side_effect = [10] - response = command.basic.execute("t33 easy", "Another TestUser") + operation = BasicOperation() + response = operation.execute("t33 easy", "Another TestUser") + actual_result = operation.format_output(response, "Another TestUser") expected_response = "Another TestUser " \ "Roll: [10] = 10 " \ "Total: 10 " \ "Target: 66 " \ "Result: Special" - self.assertEqual(expected_response, response) + self.assertEqual(expected_response, actual_result) @patch("random.randint") def test_hard_dice_roll(self, mock_roll): mock_roll.side_effect = [10] - response = command.basic.execute("t40 hard", "Another TestUser") + operation = BasicOperation() + response = operation.execute("t40 hard", "Another TestUser") + actual_result = operation.format_output(response, "Another TestUser") expected_response = "Another TestUser " \ "Roll: [10] = 10 " \ "Total: 10 " \ "Target: 20 " \ "Result: Success" - self.assertEqual(expected_response, response) + self.assertEqual(expected_response, actual_result) diff --git a/tests/test_operation_shadowrun.py b/tests/test_operation_shadowrun.py index ef0b9cf..671ad9c 100644 --- a/tests/test_operation_shadowrun.py +++ b/tests/test_operation_shadowrun.py @@ -1,12 +1,14 @@ from unittest import TestCase from unittest.mock import patch -from roll_witch.rolling import command +from roll_witch.rolling.command import ShadowRunOperation class TestShadowrunOperation(TestCase): @patch("random.randint") def test_shadowrun_dice_roll(self, mock_roll): mock_roll.side_effect = [1, 2, 3, 4, 5, 6] - response = command.shadow_run.execute("6d6", "Another TestUser") + operation = ShadowRunOperation() + response = operation.execute("6d6", "Another TestUser") + actual_response = operation.format_output(response, "Another TestUser") expected_response = "Another TestUser Roll: [1, 2, 3, 4, 5, 6] Result: 2" - self.assertEqual(expected_response, response) + self.assertEqual(expected_response, actual_response) diff --git a/tests/test_operation_standard.py b/tests/test_operation_standard.py index 0a31e86..066eb5e 100644 --- a/tests/test_operation_standard.py +++ b/tests/test_operation_standard.py @@ -1,12 +1,14 @@ from unittest import TestCase from unittest.mock import patch -from roll_witch.rolling import command +from roll_witch.rolling.command.regex import RegexOperation class TestStandardOperation(TestCase): @patch("random.randint") def test_simple_dice_roll(self, mock_roll): mock_roll.side_effect = [10] - response = command.regex.execute("d10", "Another TestUser") + operation = RegexOperation() + response = operation.execute("d10", "Another TestUser") + actual_response = operation.format_output(response, "Another TestUser") expected_response = "Another TestUser Roll: [10] = 10 Result: 10" - self.assertEqual(expected_response, response) + self.assertEqual(expected_response, actual_response) diff --git a/tests/test_output_parser.py b/tests/test_output_parser.py index 0bc3f87..68b89f0 100644 --- a/tests/test_output_parser.py +++ b/tests/test_output_parser.py @@ -1,19 +1,23 @@ from unittest import TestCase from roll_witch.rolling.output import StandardOutputWriter, TargetedOutputWriter -from roll_witch.rolling.input.spec.operation import RollSpec +from roll_witch.rolling.input.spec.operation import RollSpec, OperationSpec from roll_witch.rolling.roller import RollResult +from roll_witch.rolling.roller import OperationRollResults class TestStandardOutputWriter(TestCase): def test_build_result_string(self): writer = StandardOutputWriter() + spec = OperationSpec() roll_spec = RollSpec(dice_sides=10, dice_count=2) + spec.add_part(roll_spec) roll_result = RollResult(spec=roll_spec) roll_result.append_roll(3) roll_result.append_roll(4) - + operation_result = OperationRollResults(spec=spec) + operation_result.append_roll_result(roll_result) result_string = writer.build_result_string( - roll_result=roll_result, total_string="totalString", user="tester" + roll_result=operation_result, total_string="totalString", user="tester" ) expected_result_string = "tester Roll: totalString Result: 7" self.assertEqual(expected_result_string, result_string) @@ -22,14 +26,20 @@ def test_build_result_string(self): class TestTargetedOutputWriter(TestCase): def test_build_result_string_met_target(self): writer = TargetedOutputWriter() + operation_spec = OperationSpec() roll_spec = RollSpec(dice_sides=10, dice_count=2, target_number=5) + operation_spec.add_part(roll_spec) + roll_result = RollResult(spec=roll_spec) roll_result.append_roll(3) roll_result.append_roll(4) roll_result.met_target = True + operation_result = OperationRollResults(spec=operation_spec) + operation_result.append_roll_result(roll_result) + operation_result.met_target = True result_string = writer.build_result_string( - roll_result=roll_result, total_string="totalString", user="tester" + roll_result=operation_result, total_string="totalString", user="tester" ) expected_result_string = "tester Roll: totalString Total: 7 Target: 5 Result: Success" self.assertEqual(expected_result_string, result_string) @@ -37,13 +47,19 @@ def test_build_result_string_met_target(self): def test_build_result_string_missed_target(self): writer = TargetedOutputWriter() roll_spec = RollSpec(dice_sides=10, dice_count=2, target_number=5) + operation_spec = OperationSpec() + operation_spec.add_part(roll_spec) + roll_result = RollResult(spec=roll_spec) roll_result.append_roll(3) roll_result.append_roll(4) roll_result.met_target = False + operation_result = OperationRollResults(spec=operation_spec) + operation_result.append_roll_result(roll_result) + result_string = writer.build_result_string( - roll_result=roll_result, total_string="totalString", user="tester" + roll_result=operation_result, total_string="totalString", user="tester" ) expected_result_string = "tester Roll: totalString Total: 7 Target: 5 Result: Failed" self.assertEqual(expected_result_string, result_string) @@ -56,30 +72,40 @@ def test_write_output(self): roll_result = RollResult(spec=roll_spec) roll_result.append_roll(3) roll_result.append_roll(4) + op_spec = OperationSpec() + op_spec.add_part(roll_spec) + result = OperationRollResults(op_spec) + result.append_roll_result(roll_result) - result_string = writer.write_output(roll_result=roll_result, user="tester") + result_string = writer.write_output(result=result, user="tester") expected_result_string = "tester Roll: [3, 4] = 7 Result: 7" self.assertEqual(expected_result_string, result_string) def test_build_total_string(self): writer = StandardOutputWriter() roll_spec = RollSpec(dice_sides=10, dice_count=2) + op_spec = OperationSpec() + op_spec.add_part(roll_spec) roll_result = RollResult(spec=roll_spec) roll_result.append_roll(3) roll_result.append_roll(4) - - result_string = writer.build_total_string(roll_result=roll_result) + result = OperationRollResults(op_spec) + result.append_roll_result(roll_result) + result_string = writer.build_total_string(result=result) expected_result_string = "[3, 4] = 7" self.assertEqual(expected_result_string, result_string) def test_build_total_string_with_modifier(self): writer = StandardOutputWriter() roll_spec = RollSpec(dice_sides=10, dice_count=2, modifier=7) + op_spec = OperationSpec() + op_spec.add_part(roll_spec) roll_result = RollResult(spec=roll_spec) roll_result.append_roll(5) roll_result.append_roll(4) roll_result.apply_modifier(7) - - result_string = writer.build_total_string(roll_result=roll_result) + result = OperationRollResults(op_spec) + result.append_roll_result(roll_result) + result_string = writer.build_total_string(result=result) expected_result_string = "[5, 4] = 9 + 7" self.assertEqual(expected_result_string, result_string) diff --git a/tests/test_token_output_writer.py b/tests/test_token_output_writer.py index 315243b..e30f672 100644 --- a/tests/test_token_output_writer.py +++ b/tests/test_token_output_writer.py @@ -1,7 +1,7 @@ from unittest import TestCase from roll_witch.rolling.output import OperationOutputWriter from roll_witch.rolling.input.spec.operation import OperationSpec, RollSpec -from roll_witch.rolling.roller import RollResult, OperationResult +from roll_witch.rolling.roller import RollResult, OperationRollResults class TestTokenOutputWriter(TestCase): @@ -10,7 +10,7 @@ def test_single_operation(self): spec = OperationSpec() roll_spec = RollSpec(dice_count=3, dice_sides=6, operation="+") spec.add_part(roll_spec) - operation_result = OperationResult(spec) + operation_result = OperationRollResults(spec) roll_result = RollResult(roll_spec) roll_result.append_roll(1) roll_result.append_roll(2) @@ -27,7 +27,7 @@ def test_multiple_dice_operations(self): spec = OperationSpec() roll_spec = RollSpec(dice_count=3, dice_sides=6, operation="+") spec.add_part(roll_spec) - operation_result = OperationResult(spec) + operation_result = OperationRollResults(spec) roll_result = RollResult(roll_spec) roll_result.append_roll(1) roll_result.append_roll(2) @@ -52,7 +52,7 @@ def test_single_modifier_operations(self): spec = OperationSpec() roll_spec = RollSpec(modifier=5, operation="+") spec.add_part(roll_spec) - operation_result = OperationResult(spec) + operation_result = OperationRollResults(spec) roll_result = RollResult(roll_spec) roll_result.apply_modifier(5) @@ -66,7 +66,7 @@ def test_single_modifier_operations(self): def test_multiple_modifier_operations(self): user = "Test" spec = OperationSpec() - operation_result = OperationResult(spec) + operation_result = OperationRollResults(spec) roll_spec = RollSpec(modifier=5, operation="+") spec.add_part(roll_spec) @@ -94,7 +94,7 @@ def test_multiple_modifier_operations(self): def test_mixed_operation(self): user = "Test" spec = OperationSpec() - operation_result = OperationResult(spec) + operation_result = OperationRollResults(spec) roll_spec = RollSpec(modifier=5, operation="+") spec.add_part(roll_spec) @@ -127,7 +127,7 @@ def test_targeted_multiple_operation(self): roll_spec = RollSpec(dice_count=3, dice_sides=6, operation="+") spec.add_part(roll_spec) spec.target_number = 5 - operation_result = OperationResult(spec) + operation_result = OperationRollResults(spec) roll_result = RollResult(roll_spec) roll_result.append_roll(1) roll_result.append_roll(2) @@ -153,7 +153,7 @@ def test_targetted_single_operation(self): roll_spec = RollSpec(dice_count=3, dice_sides=6, operation="+") spec.add_part(roll_spec) spec.target_number = 5 - operation_result = OperationResult(spec) + operation_result = OperationRollResults(spec) roll_result = RollResult(roll_spec) roll_result.append_roll(1) roll_result.append_roll(2) @@ -171,7 +171,7 @@ def test_multiply_dice_operations(self): spec = OperationSpec() roll_spec = RollSpec(dice_count=3, dice_sides=6, operation="+") spec.add_part(roll_spec) - operation_result = OperationResult(spec) + operation_result = OperationRollResults(spec) roll_result = RollResult(roll_spec) roll_result.append_roll(3) roll_result.append_roll(2) @@ -196,7 +196,7 @@ def test_multiply_with_addition_dice_operations(self): spec = OperationSpec() roll_spec = RollSpec(dice_count=3, dice_sides=6, operation="+") spec.add_part(roll_spec) - operation_result = OperationResult(spec) + operation_result = OperationRollResults(spec) roll_result = RollResult(roll_spec) roll_result.append_roll(3) roll_result.append_roll(2) @@ -228,7 +228,7 @@ def test_multiply_operator_operations(self): spec = OperationSpec() roll_spec = RollSpec(dice_count=3, dice_sides=6, operation="+") spec.add_part(roll_spec) - operation_result = OperationResult(spec) + operation_result = OperationRollResults(spec) roll_result = RollResult(roll_spec) roll_result.append_roll(3) roll_result.append_roll(2) @@ -252,7 +252,7 @@ def test_divide_operator_operations(self): spec = OperationSpec() roll_spec = RollSpec(dice_count=3, dice_sides=6, operation="+") spec.add_part(roll_spec) - operation_result = OperationResult(spec) + operation_result = OperationRollResults(spec) roll_result = RollResult(roll_spec) roll_result.append_roll(3) roll_result.append_roll(2)