From 41e75a1740ca5486edf3e71d62fdbf837ab48600 Mon Sep 17 00:00:00 2001 From: Krishna Bhat <61185596+krishnabhat3383@users.noreply.github.com> Date: Fri, 19 Jul 2024 12:09:08 +0530 Subject: [PATCH 001/116] Initial commit --- .github/workflows/lint.yaml | 35 +++++++ .gitignore | 31 ++++++ .pre-commit-config.yaml | 18 ++++ LICENSE.txt | 7 ++ README.md | 184 ++++++++++++++++++++++++++++++++++++ pyproject.toml | 44 +++++++++ requirements-dev.txt | 6 ++ samples/Pipfile | 15 +++ samples/pyproject.toml | 19 ++++ 9 files changed, 359 insertions(+) create mode 100644 .github/workflows/lint.yaml create mode 100644 .gitignore create mode 100644 .pre-commit-config.yaml create mode 100644 LICENSE.txt create mode 100644 README.md create mode 100644 pyproject.toml create mode 100644 requirements-dev.txt create mode 100644 samples/Pipfile create mode 100644 samples/pyproject.toml diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml new file mode 100644 index 0000000..7f67e80 --- /dev/null +++ b/.github/workflows/lint.yaml @@ -0,0 +1,35 @@ +# GitHub Action workflow enforcing our code style. + +name: Lint + +# Trigger the workflow on both push (to the main repository, on the main branch) +# and pull requests (against the main repository, but from any repo, from any branch). +on: + push: + branches: + - main + pull_request: + +# Brand new concurrency setting! This ensures that not more than one run can be triggered for the same commit. +# It is useful for pull requests coming from the main repository since both triggers will match. +concurrency: lint-${{ github.sha }} + +jobs: + lint: + runs-on: ubuntu-latest + + env: + # The Python version your project uses. Feel free to change this if required. + PYTHON_VERSION: "3.12" + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Python ${{ env.PYTHON_VERSION }} + uses: actions/setup-python@v5 + with: + python-version: ${{ env.PYTHON_VERSION }} + + - name: Run pre-commit hooks + uses: pre-commit/action@v3.0.1 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..233eb87 --- /dev/null +++ b/.gitignore @@ -0,0 +1,31 @@ +# Files generated by the interpreter +__pycache__/ +*.py[cod] + +# Environment specific +.venv +venv +.env +env + +# Unittest reports +.coverage* + +# Logs +*.log + +# PyEnv version selector +.python-version + +# Built objects +*.so +dist/ +build/ + +# IDEs +# PyCharm +.idea/ +# VSCode +.vscode/ +# MacOS +.DS_Store diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..4bccb6f --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,18 @@ +# Pre-commit configuration. +# See https://github.com/python-discord/code-jam-template/tree/main#pre-commit-run-linting-before-committing + +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.6.0 + hooks: + - id: check-toml + - id: check-yaml + - id: end-of-file-fixer + - id: trailing-whitespace + args: [--markdown-linebreak-ext=md] + + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.5.0 + hooks: + - id: ruff + - id: ruff-format diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..5a04926 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,7 @@ +Copyright 2021 Python Discord + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..d50f7b7 --- /dev/null +++ b/README.md @@ -0,0 +1,184 @@ +# Python Discord Code Jam Repository Template + +## A primer + +Hello code jam participants! We've put together this repository template for you to use in [our code jams](https://pythondiscord.com/events/) or even other Python events! + +This document contains the following information: + +1. [What does this template contain?](#what-does-this-template-contain) +2. [How do I use this template?](#how-do-i-use-this-template) +3. [How do I adapt this template to my project?](#how-do-i-adapt-this-template-to-my-project) + +> [!TIP] +> You can also look at [our style guide](https://pythondiscord.com/events/code-jams/code-style-guide/) to get more information about what we consider a maintainable code style. + +## What does this template contain? + +Here is a quick rundown of what each file in this repository contains: + +- [`LICENSE.txt`](LICENSE.txt): [The MIT License](https://opensource.org/licenses/MIT), an OSS approved license which grants rights to everyone to use and modify your project, and limits your liability. We highly recommend you to read the license. +- [`.gitignore`](.gitignore): A list of files and directories that will be ignored by Git. Most of them are auto-generated or contain data that you wouldn't want to share publicly. +- [`requirements-dev.txt`](requirements-dev.txt): Every PyPI package used for the project's development, to ensure a common development environment. More on that [below](#using-the-default-pip-setup). +- [`pyproject.toml`](pyproject.toml): Configuration and metadata for the project, as well as the linting tool Ruff. If you're interested, you can read more about `pyproject.toml` in the [Python Packaging documentation](https://packaging.python.org/en/latest/guides/writing-pyproject-toml/). +- [`.pre-commit-config.yaml`](.pre-commit-config.yaml): The configuration of the [pre-commit](https://pre-commit.com/) tool. +- [`.github/workflows/lint.yaml`](.github/workflows/lint.yaml): A [GitHub Actions](https://github.com/features/actions) workflow, a set of actions run by GitHub on their server after each push, to ensure the style requirements are met. + +Each of these files have comments for you to understand easily, and modify to fit your needs. + +### Ruff: general style rules + +Our first tool is Ruff. It will check your codebase and warn you about any non-conforming lines. +It is run with the command `ruff check` in the project root. + +Here is a sample output: + +```shell +$ ruff check +app.py:1:5: N802 Function name `helloWorld` should be lowercase +app.py:1:5: ANN201 Missing return type annotation for public function `helloWorld` +app.py:2:5: D400 First line should end with a period +app.py:2:5: D403 First word of the first line should be capitalized: `docstring` -> `Docstring` +app.py:3:15: W292 No newline at end of file +Found 5 errors. +``` + +Each line corresponds to an error. The first part is the file path, then the line number, and the column index. +Then comes the error code, a unique identifier of the error, and then a human-readable message. + +If, for any reason, you do not wish to comply with this specific error on a specific line, you can add `# noqa: CODE` at the end of the line. +For example: + +```python +def helloWorld(): # noqa: N802 + ... + +``` + +This will ignore the function naming issue and pass linting. + +> [!WARNING] +> We do not recommend ignoring errors unless you have a good reason to do so. + +### Ruff: formatting + +Ruff also comes with a formatter, which can be run with the command `ruff format`. +It follows the same code style enforced by [Black](https://black.readthedocs.io/en/stable/index.html), so there's no need to pick between them. + +### Pre-commit: run linting before committing + +The second tool doesn't check your code, but rather makes sure that you actually *do* check it. + +It makes use of a feature called [Git hooks](https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks) which allow you to run a piece of code before running `git commit`. +The good thing about it is that it will cancel your commit if the lint doesn't pass. You won't have to wait for GitHub Actions to report issues and have a second fix commit. + +It is *installed* by running `pre-commit install` and can be run manually by calling only `pre-commit`. + +[Lint before you push!](https://soundcloud.com/lemonsaurusrex/lint-before-you-push) + +#### List of hooks + +- `check-toml`: Lints and corrects your TOML files. +- `check-yaml`: Lints and corrects your YAML files. +- `end-of-file-fixer`: Makes sure you always have an empty line at the end of your file. +- `trailing-whitespace`: Removes whitespaces at the end of each line. +- `ruff`: Runs the Ruff linter. +- `ruff-format`: Runs the Ruff formatter. + +## How do I use this template? + +### Creating your team repository + +One person in the team, preferably the leader, will have to create the repository and add other members as collaborators. + +1. In the top right corner of your screen, where **Clone** usually is, you have a **Use this template** button to click. + ![use-this-template-button](https://docs.github.com/assets/images/help/repository/use-this-template-button.png) +2. Give the repository a name and a description. + ![create-repository-name](https://docs.github.com/assets/images/help/repository/create-repository-name.png) +3. Click **Create repository from template**. +4. Click **Settings** in your newly created repository. + ![repo-actions-settings](https://docs.github.com/assets/images/help/repository/repo-actions-settings.png) +5. In the "Access" section of the sidebar, click **Collaborators**. + ![collaborators-settings](https://github.com/python-discord/code-jam-template/assets/63936253/c150110e-d1b5-4e4d-93e0-0a2cf1de352b) +6. Click **Add people**. +7. Insert the names of each of your teammates, and invite them. Once they have accepted the invitation in their email, they will have write access to the repository. + +You are now ready to go! Sit down, relax, and wait for the kickstart! + +> [!IMPORTANT] +> Don't forget to swap "Python Discord" in the [`LICENSE.txt`](LICENSE.txt) file for the name of each of your team members or the name of your team *after* the start of the code jam. + +### Using the default pip setup + +Our default setup includes a bare requirements file to be used with a [virtual environment](https://docs.python.org/3/library/venv.html). +We recommend this if you have never used any other dependency manager, although if you have, feel free to switch to it. More on that [below](#how-do-i-adapt-this-template-to-my-project). + +#### Creating the environment + +Create a virtual environment in the folder `.venv`. + +```shell +python -m venv .venv +``` + +#### Entering the environment + +It will change based on your operating system and shell. + +```shell +# Linux, Bash +$ source .venv/bin/activate +# Linux, Fish +$ source .venv/bin/activate.fish +# Linux, Csh +$ source .venv/bin/activate.csh +# Linux, PowerShell Core +$ .venv/bin/Activate.ps1 +# Windows, cmd.exe +> .venv\Scripts\activate.bat +# Windows, PowerShell +> .venv\Scripts\Activate.ps1 +``` + +#### Installing the dependencies + +Once the environment is created and activated, use this command to install the development dependencies. + +```shell +pip install -r requirements-dev.txt +``` + +#### Exiting the environment + +Interestingly enough, it is the same for every platform. + +```shell +deactivate +``` + +Once the environment is activated, all the commands listed previously should work. + +> [!IMPORTANT] +> We highly recommend that you run `pre-commit install` as soon as possible. + +## How do I adapt this template to my project? + +If you wish to use Pipenv or Poetry, you will have to move the dependencies in [`requirements-dev.txt`](requirements-dev.txt) to the development dependencies of your tool. + +We've included a porting of [`requirements-dev.txt`](requirements-dev.txt) to both [Poetry](samples/pyproject.toml) and [Pipenv](samples/Pipfile) in the [`samples` folder](samples). +If you use the Poetry setup, make sure to change the project name, description, and authors at the top of the file. +Also note that the Poetry [`pyproject.toml`](samples/pyproject.toml) file does not include the Ruff configuration, so if you simply replace the file then the Ruff configuration will be lost. + +When installing new dependencies, don't forget to [pin](https://pip.pypa.io/en/stable/topics/repeatable-installs/#pinning-the-package-versions) them by adding a version tag at the end. +For example, if I wish to install [Click](https://click.palletsprojects.com/en/8.1.x/), a quick look at [PyPI](https://pypi.org/project/click/) tells me that `8.1.7` is the latest version. +I will then add `click~=8.1`, without the last number, to my requirements file or dependency manager. + +> [!IMPORTANT] +> A code jam project is left unmaintained after the end of the event. If the dependencies aren't pinned, the project will break after any major change in an API. + +## Final words + +> [!IMPORTANT] +> Don't forget to replace this README with an actual description of your project! Images are also welcome! + +We hope this template will be helpful. Good luck in the jam! diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..0880be9 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,44 @@ +[tool.ruff] +# Increase the line length. This breaks PEP8 but it is way easier to work with. +# The original reason for this limit was a standard vim terminal is only 79 characters, +# but this doesn't really apply anymore. +line-length = 119 +# Target Python 3.12. If you decide to use a different version of Python +# you will need to update this value. +target-version = "py312" +# Automatically fix auto-fixable issues. +fix = true +# The directory containing the source code. If you choose a different project layout +# you will need to update this value. +src = ["src"] + +[tool.ruff.lint] +# Enable all linting rules. +select = ["ALL"] +# Ignore some of the most obnoxious linting errors. +ignore = [ + # Missing docstrings. + "D100", + "D104", + "D105", + "D106", + "D107", + # Docstring whitespace. + "D203", + "D213", + # Docstring punctuation. + "D415", + # Docstring quotes. + "D301", + # Builtins. + "A", + # Print statements. + "T20", + # TODOs. + "TD002", + "TD003", + "FIX", + # Annotations. + "ANN101", + "ANN102", +] diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 0000000..d529f2e --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1,6 @@ +# This file contains all the development requirements for our linting toolchain. +# Don't forget to pin your dependencies! +# This list will have to be migrated if you wish to use another dependency manager. + +ruff~=0.5.0 +pre-commit~=3.7.1 diff --git a/samples/Pipfile b/samples/Pipfile new file mode 100644 index 0000000..27673c0 --- /dev/null +++ b/samples/Pipfile @@ -0,0 +1,15 @@ +# Sample Pipfile. + +[[source]] +url = "https://pypi.org/simple" +verify_ssl = true +name = "pypi" + +[packages] + +[dev-packages] +ruff = "~=0.5.0" +pre-commit = "~=3.7.1" + +[requires] +python_version = "3.12" diff --git a/samples/pyproject.toml b/samples/pyproject.toml new file mode 100644 index 0000000..835045d --- /dev/null +++ b/samples/pyproject.toml @@ -0,0 +1,19 @@ +# Sample poetry configuration. + +[tool.poetry] +name = "Name" +version = "0.1.0" +description = "Description" +authors = ["Author 1 "] +license = "MIT" + +[tool.poetry.dependencies] +python = "3.12.*" + +[tool.poetry.dev-dependencies] +ruff = "~0.5.0" +pre-commit = "~3.7.1" + +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" From 084c58ed39c69a9a9e8c7a3918c649b1c0daea14 Mon Sep 17 00:00:00 2001 From: hazyfossa Date: Sat, 20 Jul 2024 14:40:11 +0500 Subject: [PATCH 002/116] Add interactions.py --- requirements.in | 42 ++++++++++++++++++++++++++++++++++++++++++ requirements.txt | 1 + 2 files changed, 43 insertions(+) create mode 100644 requirements.in create mode 100644 requirements.txt diff --git a/requirements.in b/requirements.in new file mode 100644 index 0000000..dc1e440 --- /dev/null +++ b/requirements.in @@ -0,0 +1,42 @@ +# This file was autogenerated by uv via the following command: +# uv pip compile requirements.txt +aiohttp==3.9.5 + # via discord-py-interactions +aiosignal==1.3.1 + # via aiohttp +attrs==23.2.0 + # via + # aiohttp + # discord-py-interactions +croniter==2.0.7 + # via discord-py-interactions +discord-py-interactions==5.13.1 + # via -r requirements.txt +discord-typings==0.9.0 + # via discord-py-interactions +emoji==2.12.1 + # via discord-py-interactions +frozenlist==1.4.1 + # via + # aiohttp + # aiosignal +idna==3.7 + # via yarl +multidict==6.0.5 + # via + # aiohttp + # yarl +python-dateutil==2.9.0.post0 + # via croniter +pytz==2024.1 + # via croniter +six==1.16.0 + # via python-dateutil +tomli==2.0.1 + # via discord-py-interactions +typing-extensions==4.12.2 + # via + # discord-typings + # emoji +yarl==1.9.4 + # via aiohttp diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..204833e --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +discord-py-interactions~=5.13.0 \ No newline at end of file From 62cf6f2706ccdeab6555591b9e5f5eecfffcdb92 Mon Sep 17 00:00:00 2001 From: Sapient44 <78420201+Sapient44@users.noreply.github.com> Date: Mon, 22 Jul 2024 19:53:06 +0530 Subject: [PATCH 003/116] Basic joining and leaving of the game (#1) * basic bot booting * leaving the game instance * Ping Player on Joining, Leaving, Starting the game * Convert UI to embeds * Removing testing code * Converting game_start to an extension of main.py * Changing lib to src * Removed useless double starting of the bot --- main.py | 21 ++++++++++++++++ requirements.txt | 2 +- src/__init__.py | 0 src/game_start.py | 61 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 main.py create mode 100644 src/__init__.py create mode 100644 src/game_start.py diff --git a/main.py b/main.py new file mode 100644 index 0000000..4d371ad --- /dev/null +++ b/main.py @@ -0,0 +1,21 @@ +from os import getenv + +from dotenv import load_dotenv +from interactions import ( + Client, + Intents, + listen, +) + +load_dotenv() +bot = Client(intents=Intents.DEFAULT) + + +@listen() +async def on_ready() -> None: + """Print "Ready", when the bot is accepting commands.""" + print("Ready") + + +bot.load_extension("src.game_start") +bot.start(getenv("BOT_TOKEN")) diff --git a/requirements.txt b/requirements.txt index 204833e..660b479 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1 @@ -discord-py-interactions~=5.13.0 \ No newline at end of file +discord-py-interactions~=5.13.0 diff --git a/src/__init__.py b/src/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/game_start.py b/src/game_start.py new file mode 100644 index 0000000..ef3e21f --- /dev/null +++ b/src/game_start.py @@ -0,0 +1,61 @@ +from interactions import ( + ActionRow, + Button, + ButtonStyle, + ComponentContext, + Embed, + Extension, + SlashContext, + component_callback, + slash_command, +) + +users_ids: list[int] = [] # Global Var for the players in the game + + +class GameInitiation(Extension): + """Control the extension entry point.""" + + @slash_command(name="start_defcord", description="Welcome to the simulation called DEFCORD") + async def my_command_function(self, ctx: SlashContext) -> None: + """Start the game of DEFCORD.""" + # Here would need to check are there any other instances of the game + components = ActionRow( + Button( + style=ButtonStyle.GREEN, + label="Join", + custom_id="join_game", + ), + Button( + style=ButtonStyle.RED, + label="Leave", + custom_id="leave_game", + ), + ) + embed = Embed( + title="Starting DEFCORD", + description=f"The tactical game of DEFCORD has been initiated by <@{ctx.user.id}>", + color=(255, 0, 0), + ) + await ctx.send(embed=embed, components=components) + + @component_callback("join_game") + async def join_callback(self, ctx: ComponentContext) -> None: + """Control player joining the game.""" + # Need to make this channel wise + user = ctx.user + if user.id not in users_ids: + await ctx.send(f"Player <@{user.id}> has joined") + users_ids.append(user.id) + else: + await ctx.send("You are already part of the game", ephemeral=True) + + @component_callback("leave_game") + async def leave_callback(self, ctx: ComponentContext) -> None: + """Control players leaving the game.""" + user = ctx.user + if user.id in users_ids: + await ctx.send(f"Player <@{user.id}> has left") + users_ids.remove(user.id) + else: + await ctx.send("You are not part of the game", ephemeral=True) From 78ccddae0637cbe52b48f081141dc68884e6dd14 Mon Sep 17 00:00:00 2001 From: hazyfossa <148675205+hazyfossa@users.noreply.github.com> Date: Mon, 22 Jul 2024 21:19:13 +0500 Subject: [PATCH 004/116] Improve how we're starting our app (#2) * Handle if token isn't provided * Add logging * make python-dotenv optional * Add developer mode * Add periods to docstrings --- main.py | 53 ++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 46 insertions(+), 7 deletions(-) diff --git a/main.py b/main.py index 4d371ad..639b083 100644 --- a/main.py +++ b/main.py @@ -1,21 +1,60 @@ +import logging from os import getenv +from sys import argv, exit -from dotenv import load_dotenv from interactions import ( Client, Intents, listen, ) -load_dotenv() -bot = Client(intents=Intents.DEFAULT) +log = logging.getLogger("defcon-internal") +bot = Client(intents=Intents.DEFAULT, logging_level=logging.WARNING) @listen() async def on_ready() -> None: - """Print "Ready", when the bot is accepting commands.""" - print("Ready") + """Notify that the bot is started.""" + log.info("Bot started.") -bot.load_extension("src.game_start") -bot.start(getenv("BOT_TOKEN")) +def get_token() -> str: + """Try to read bot's token from environment or .env.""" + + try: + from dotenv import load_dotenv + + load_dotenv() + no_dotenv = False + except ImportError: + no_dotenv = True + + token = getenv("DEFCON_BOT_TOKEN") + + if token is None: + log.error("Token not found.\nPlease specify discord bot token via environment variable 'DEFCON_BOT_TOKEN'.") + + if no_dotenv: + log.info("To read token from .env, install 'python-dotenv'") + + exit() + + return token + + +def get_developer_mode() -> bool: + """Get if the bot is running in dev mode.""" + try: + return argv[1] == "--dev" + except IndexError: + return False + + +DEV = get_developer_mode() + +if __name__ == "__main__": + if DEV: + bot.load_extension("interactions.ext.jurigged") + + bot.load_extension("src.game_start") + bot.start(token=get_token()) From ccb771dad3b92c77a541db08fe0c9582327ca5f3 Mon Sep 17 00:00:00 2001 From: hazyfossa <148675205+hazyfossa@users.noreply.github.com> Date: Mon, 22 Jul 2024 21:19:57 +0500 Subject: [PATCH 005/116] Templating (#3) * Handle if token isn't provided * Add logging * make python-dotenv optional * Add developer mode * Add periods to docstrings * Templating draft * fix typo * fix typo --- src/example.py | 22 +++++++++++++++ src/models.py | 75 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+) create mode 100644 src/example.py create mode 100644 src/models.py diff --git a/src/example.py b/src/example.py new file mode 100644 index 0000000..bd652df --- /dev/null +++ b/src/example.py @@ -0,0 +1,22 @@ +from models import Actor, StageGroup, Template + +# fmt: off +Actor("John the Farmer", "url_here",[ + StageGroup(1, [ + Template( + "Hello, leader of {nation_name}. Can you spare some money for our new farming equipment?", + choices={ + "Yeah, sure": {"money": -10, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + Template( + "Things are great, we managed to grow even more crops than expected. Thank you for your help, leader! \n Here, have this small gift from our community", + choices = {"Thanks!": {"money": +15}}, + condition = lambda state: state.loyalty > 70 and state.money < 300, + ), + ]), + StageGroup([2, 3], [ + Template("Etc, you get the idea"), + ]), +]) diff --git a/src/models.py b/src/models.py new file mode 100644 index 0000000..02eff7c --- /dev/null +++ b/src/models.py @@ -0,0 +1,75 @@ +from collections.abc import Callable +from typing import Any, Literal, get_args + +from attrs import asdict, define, field, frozen +from interactions.models.discord.embed import Embed + +Stage = Literal[1, 2, 3] # Adjustable + + +@define +class State: + nation_name: str + stage: Stage = 1 + + money: int = 100 + loyalty: int = 50 + some_other_thing: int = 0 + + def apply(self, consequence: dict) -> None: + for k, v in consequence.items(): + self.__dict__[k] += v + + +# Consequence = Callable[[State], None] +Consequence = dict[Any, Any] +Condition = Callable[[State], bool] | None + + +@frozen +class Template: + @staticmethod + def convert_condition(condition: Condition | None) -> Condition: + def always_true(_: State) -> True: + return True + + if condition is None: + return always_true + + return condition + + string: str + choices: dict[None, Consequence] + condition: Condition = field(converter=convert_condition) + + def format(self, state: State) -> str: + return self.string.format(asdict(state)) + + def to_embed(self, state: State) -> Embed: + raise NotImplementedError + + +# StageSpec = Stage | tuple[Stage] | Literal["all"] +TotalStages = get_args(Stage) + + +@frozen +class StageGroup: + # @staticmethod + # def convert_stage(stage: StageSpec) -> Stage | tuple[Stage]: + # if stage == "all": + # return TotalStages + + # return stage + + stage: Stage | tuple[Stage] | Literal["all"] = field( + converter=lambda stage: TotalStages if stage == "all" else stage, + ) + templates: list[Template] + + +@frozen +class Actor: + name: str + picture: str # we'll need to serve these as static content probably + templates: list[StageGroup] From a8f6e7b742bd6be7a2ba9208d1af8c03e4f53f56 Mon Sep 17 00:00:00 2001 From: hazyfossa Date: Tue, 23 Jul 2024 20:08:12 +0500 Subject: [PATCH 006/116] Basic game management draft --- src/game.py | 14 ++++++++ src/game_start.py | 88 +++++++++++++++++++++++++---------------------- 2 files changed, 60 insertions(+), 42 deletions(-) create mode 100644 src/game.py diff --git a/src/game.py b/src/game.py new file mode 100644 index 0000000..17d55e9 --- /dev/null +++ b/src/game.py @@ -0,0 +1,14 @@ +from interactions import User + +from models import State + +GameID = str + + +class Game: + def __init__(self, id: GameID) -> None: + self.id = id + self.players: dict[str, State] = {} + + async def add_player(self, user: User, nation_name: str) -> None: + self.players[user.id] = State(nation_name) diff --git a/src/game_start.py b/src/game_start.py index ef3e21f..51bbfe9 100644 --- a/src/game_start.py +++ b/src/game_start.py @@ -8,54 +8,58 @@ SlashContext, component_callback, slash_command, + slash_option, ) -users_ids: list[int] = [] # Global Var for the players in the game +from game import Game, GameID +# Game = dict[Member, State] -class GameInitiation(Extension): + +class GameManager: + def __init__(self) -> None: + self.store: dict[GameID, Game] = {} + + def generate_game_id() -> GameID: + return "alpha-bravo" # Generate randomly + + def create_game(self) -> Game: + game_id = self.generate_game_id() + game = Game(game_id) + self.store[game_id] = game + + return Game + + def query_game(self, game_id: GameID) -> Game: + return self.store.get(game_id, None) + + +class GameInitializon(Extension): """Control the extension entry point.""" - @slash_command(name="start_defcord", description="Welcome to the simulation called DEFCORD") - async def my_command_function(self, ctx: SlashContext) -> None: - """Start the game of DEFCORD.""" - # Here would need to check are there any other instances of the game - components = ActionRow( - Button( - style=ButtonStyle.GREEN, - label="Join", - custom_id="join_game", - ), - Button( - style=ButtonStyle.RED, - label="Leave", - custom_id="leave_game", - ), - ) + def __init__(self, _) -> None: + self.manager = GameManager() + + @slash_command(name="defcord_create_game", description="Welcome to the simulation called DEFCORD") + async def create(self, ctx: SlashContext) -> None: + game = self.manager.create_game() # Add the first player here + embed = Embed( - title="Starting DEFCORD", - description=f"The tactical game of DEFCORD has been initiated by <@{ctx.user.id}>", + title="New game started!", + description=f"Your invite: {game.id}", color=(255, 0, 0), ) - await ctx.send(embed=embed, components=components) - - @component_callback("join_game") - async def join_callback(self, ctx: ComponentContext) -> None: - """Control player joining the game.""" - # Need to make this channel wise - user = ctx.user - if user.id not in users_ids: - await ctx.send(f"Player <@{user.id}> has joined") - users_ids.append(user.id) - else: - await ctx.send("You are already part of the game", ephemeral=True) - - @component_callback("leave_game") - async def leave_callback(self, ctx: ComponentContext) -> None: - """Control players leaving the game.""" - user = ctx.user - if user.id in users_ids: - await ctx.send(f"Player <@{user.id}> has left") - users_ids.remove(user.id) - else: - await ctx.send("You are not part of the game", ephemeral=True) + + await ctx.send(embed=embed) + + @slash_command(name="start_defcord", description="Welcome to the simulation called DEFCORD") + @slash_option("invite", "The invite code for the game", required=True) + async def join(self, ctx: SlashContext, invite: str) -> None: + """Start the game of DEFCORD.""" + + game = self.manager.query_game(invite) + + if game is None: + raise NotImplementedError + + await game.add_player(ctx.user, "") # Ask nation name here From 8edfdd77c43cc3530db90a3eae9913211242fd60 Mon Sep 17 00:00:00 2001 From: hazyfossa Date: Tue, 23 Jul 2024 21:26:25 +0500 Subject: [PATCH 007/116] Implement random game id's --- src/game_start.py | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/src/game_start.py b/src/game_start.py index 51bbfe9..629da7a 100644 --- a/src/game_start.py +++ b/src/game_start.py @@ -1,3 +1,6 @@ +import random +from string import ascii_uppercase, digits + from interactions import ( ActionRow, Button, @@ -5,6 +8,7 @@ ComponentContext, Embed, Extension, + OptionType, SlashContext, component_callback, slash_command, @@ -16,21 +20,27 @@ # Game = dict[Member, State] -class GameManager: +class GameFactory: def __init__(self) -> None: self.store: dict[GameID, Game] = {} - def generate_game_id() -> GameID: - return "alpha-bravo" # Generate randomly + def generate_game_id(self) -> GameID: + while True: + game_id = "".join(random.choice(ascii_uppercase + digits) for i in range(12)) # noqa: S311 This isn't for crypto purposes + + if game_id in self.store: + continue + + return game_id def create_game(self) -> Game: game_id = self.generate_game_id() game = Game(game_id) self.store[game_id] = game - return Game + return game - def query_game(self, game_id: GameID) -> Game: + def query_game(self, game_id: GameID) -> Game | None: return self.store.get(game_id, None) @@ -38,11 +48,11 @@ class GameInitializon(Extension): """Control the extension entry point.""" def __init__(self, _) -> None: - self.manager = GameManager() + self.game_factory = GameFactory() @slash_command(name="defcord_create_game", description="Welcome to the simulation called DEFCORD") async def create(self, ctx: SlashContext) -> None: - game = self.manager.create_game() # Add the first player here + game = self.game_factory.create_game() # Add the first player here embed = Embed( title="New game started!", @@ -53,11 +63,11 @@ async def create(self, ctx: SlashContext) -> None: await ctx.send(embed=embed) @slash_command(name="start_defcord", description="Welcome to the simulation called DEFCORD") - @slash_option("invite", "The invite code for the game", required=True) + @slash_option("invite", "The invite code for the game", required=True, opt_type=OptionType.STRING) async def join(self, ctx: SlashContext, invite: str) -> None: """Start the game of DEFCORD.""" - game = self.manager.query_game(invite) + game = self.game_factory.query_game(invite) if game is None: raise NotImplementedError From 0d8e8961e66b8c36cb6e531f71867b92e04a1c5a Mon Sep 17 00:00:00 2001 From: hazyfossa Date: Tue, 23 Jul 2024 21:55:29 +0500 Subject: [PATCH 008/116] Player registration UI prototype --- src/game_start.py | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/src/game_start.py b/src/game_start.py index 629da7a..82fedc4 100644 --- a/src/game_start.py +++ b/src/game_start.py @@ -1,23 +1,20 @@ import random from string import ascii_uppercase, digits +from typing import TYPE_CHECKING, Annotated from interactions import ( - ActionRow, - Button, - ButtonStyle, - ComponentContext, Embed, Extension, OptionType, SlashContext, - component_callback, slash_command, slash_option, ) from game import Game, GameID -# Game = dict[Member, State] +if TYPE_CHECKING: + from interactions import Client class GameFactory: @@ -47,12 +44,16 @@ def query_game(self, game_id: GameID) -> Game | None: class GameInitializon(Extension): """Control the extension entry point.""" - def __init__(self, _) -> None: + def __init__(self, _: "Client") -> None: self.game_factory = GameFactory() - @slash_command(name="defcord_create_game", description="Welcome to the simulation called DEFCORD") + @slash_command(name="defcord_create", description="Create a new DEFCORD game.") async def create(self, ctx: SlashContext) -> None: + """Create a game of DEFCORD""" game = self.game_factory.create_game() # Add the first player here + nation_name = await self.register_player() + + await game.add_player(ctx.user, nation_name) embed = Embed( title="New game started!", @@ -62,14 +63,18 @@ async def create(self, ctx: SlashContext) -> None: await ctx.send(embed=embed) - @slash_command(name="start_defcord", description="Welcome to the simulation called DEFCORD") + @slash_command(name="defcord_join", description="Join a game of DEFCORD.") @slash_option("invite", "The invite code for the game", required=True, opt_type=OptionType.STRING) async def join(self, ctx: SlashContext, invite: str) -> None: - """Start the game of DEFCORD.""" - + """Join a game of DEFCORD.""" game = self.game_factory.query_game(invite) if game is None: raise NotImplementedError - await game.add_player(ctx.user, "") # Ask nation name here + nation_name = await self.register_player() + await game.add_player(ctx.user, nation_name) # Ask nation name here + + async def register_player(self) -> Annotated[str, "Nation name"]: + """Ask the player for information.""" + raise NotImplementedError From 9ae9f3cdeb6738b10b4bbc8750d98ba4bb452cd1 Mon Sep 17 00:00:00 2001 From: Sapient44 Date: Tue, 23 Jul 2024 23:07:10 +0530 Subject: [PATCH 009/116] Remove player --- src/game.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/game.py b/src/game.py index 17d55e9..f64e9e2 100644 --- a/src/game.py +++ b/src/game.py @@ -4,6 +4,9 @@ GameID = str +# Tasks to be achieved here +# 1) Create the game flow, and when the template objects would be called +# 2) Make an internal game, so we can start testing class Game: def __init__(self, id: GameID) -> None: @@ -12,3 +15,6 @@ def __init__(self, id: GameID) -> None: async def add_player(self, user: User, nation_name: str) -> None: self.players[user.id] = State(nation_name) + + async def remove_player(self, user:User) -> None: + del self.players[user.id] From fa80ba4efc144ce99a1639b066c08e8a823fbc0c Mon Sep 17 00:00:00 2001 From: Sapient44 <78420201+Sapient44@users.noreply.github.com> Date: Tue, 23 Jul 2024 23:21:47 +0530 Subject: [PATCH 010/116] Linting + Embedding of template Game UI (#4) * Add embedding of Template * Linting * Linting check 2 * Linting * Linting ;-; * Improve docstrings and simplify condition converter * linting --------- Co-authored-by: hazyfossa --- main.py | 1 - pyproject.toml | 3 +++ src/example.py | 5 ++-- src/models.py | 62 +++++++++++++++++++++++++++++++------------------- 4 files changed, 45 insertions(+), 26 deletions(-) diff --git a/main.py b/main.py index 639b083..2999b5c 100644 --- a/main.py +++ b/main.py @@ -20,7 +20,6 @@ async def on_ready() -> None: def get_token() -> str: """Try to read bot's token from environment or .env.""" - try: from dotenv import load_dotenv diff --git a/pyproject.toml b/pyproject.toml index 0880be9..6ac7430 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,3 +42,6 @@ ignore = [ "ANN101", "ANN102", ] + +[tool.ruff.lint.per-file-ignores] +"example.py" = ["PLR2004"] diff --git a/src/example.py b/src/example.py index bd652df..4dd9139 100644 --- a/src/example.py +++ b/src/example.py @@ -11,9 +11,10 @@ }, ), Template( - "Things are great, we managed to grow even more crops than expected. Thank you for your help, leader! \n Here, have this small gift from our community", + """Things are great, we managed to grow even more crops than expected. + Thank you for your help, leader! \n Here, have this small gift from our community""", choices = {"Thanks!": {"money": +15}}, - condition = lambda state: state.loyalty > 70 and state.money < 300, + condition = lambda state: state.loyalty>70 and state.money<300, ), ]), StageGroup([2, 3], [ diff --git a/src/models.py b/src/models.py index 02eff7c..50fe9ae 100644 --- a/src/models.py +++ b/src/models.py @@ -2,13 +2,15 @@ from typing import Any, Literal, get_args from attrs import asdict, define, field, frozen -from interactions.models.discord.embed import Embed +from interactions import ActionRow, Button, ButtonStyle, Embed Stage = Literal[1, 2, 3] # Adjustable @define class State: + """The current state of the player.""" + nation_name: str stage: Stage = 1 @@ -17,50 +19,62 @@ class State: some_other_thing: int = 0 def apply(self, consequence: dict) -> None: + """Apply the consequnces to current state.""" for k, v in consequence.items(): self.__dict__[k] += v -# Consequence = Callable[[State], None] Consequence = dict[Any, Any] Condition = Callable[[State], bool] | None +def always_true(_: State) -> Literal[True]: + """Return True.""" + return True + + @frozen class Template: - @staticmethod - def convert_condition(condition: Condition | None) -> Condition: - def always_true(_: State) -> True: - return True + """Make a template for the messages to be served.""" + + text: str + choices: dict[str, Consequence] # Specify button color here somehow. + condition: Condition = field(converter=lambda condition: always_true if condition is None else condition) - if condition is None: - return always_true + def format(self, state: State) -> str: + """Format the text.""" + return self.text.format(asdict(state)) - return condition + def to_embed(self, state: State) -> tuple[Embed, ActionRow]: + """Return embed and action row for UI.""" + buttons: list[Button] = [] - string: str - choices: dict[None, Consequence] - condition: Condition = field(converter=convert_condition) + for id, choice in enumerate(self.choices.items()): + button = Button( + label=f"{next(iter(choice.keys()))}", # Something isn't right here + style=ButtonStyle.BLURPLE, + custom_id=f"Choice {id}", + ) + buttons.append(button) - def format(self, state: State) -> str: - return self.string.format(asdict(state)) + action_row = ActionRow(*buttons) - def to_embed(self, state: State) -> Embed: - raise NotImplementedError + embed = Embed( + title=state.nation_name, + description=self.text, + color=(0, 0, 255), + # Can we access Actor here in this class? like this actor is saying this + # hazyfossa: good question + ) + return (embed, action_row) -# StageSpec = Stage | tuple[Stage] | Literal["all"] TotalStages = get_args(Stage) @frozen class StageGroup: - # @staticmethod - # def convert_stage(stage: StageSpec) -> Stage | tuple[Stage]: - # if stage == "all": - # return TotalStages - - # return stage + """A helper class to group templates based on their stage in game.""" stage: Stage | tuple[Stage] | Literal["all"] = field( converter=lambda stage: TotalStages if stage == "all" else stage, @@ -70,6 +84,8 @@ class StageGroup: @frozen class Actor: + """An in-game character.""" + name: str picture: str # we'll need to serve these as static content probably templates: list[StageGroup] From aa5f9a08dd60426d28886c7563f8ace4e327a7f5 Mon Sep 17 00:00:00 2001 From: Maheshkumar Date: Tue, 23 Jul 2024 23:21:49 +0530 Subject: [PATCH 011/116] Get nation name from user for registration --- src/game.py | 2 +- src/game_start.py | 32 ++++++++++++++++++++++++++------ 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/src/game.py b/src/game.py index 17d55e9..0005c52 100644 --- a/src/game.py +++ b/src/game.py @@ -1,6 +1,6 @@ from interactions import User -from models import State +from src.models import State GameID = str diff --git a/src/game_start.py b/src/game_start.py index 82fedc4..6d03aa7 100644 --- a/src/game_start.py +++ b/src/game_start.py @@ -5,13 +5,16 @@ from interactions import ( Embed, Extension, + Modal, + ModalContext, OptionType, + ShortText, SlashContext, slash_command, slash_option, ) -from game import Game, GameID +from src.game import Game, GameID if TYPE_CHECKING: from interactions import Client @@ -49,9 +52,9 @@ def __init__(self, _: "Client") -> None: @slash_command(name="defcord_create", description="Create a new DEFCORD game.") async def create(self, ctx: SlashContext) -> None: - """Create a game of DEFCORD""" + """Create a game of DEFCORD.""" game = self.game_factory.create_game() # Add the first player here - nation_name = await self.register_player() + nation_name = await self.register_player(ctx) await game.add_player(ctx.user, nation_name) @@ -73,8 +76,25 @@ async def join(self, ctx: SlashContext, invite: str) -> None: raise NotImplementedError nation_name = await self.register_player() - await game.add_player(ctx.user, nation_name) # Ask nation name here + await game.add_player(ctx.user, nation_name) - async def register_player(self) -> Annotated[str, "Nation name"]: + async def register_player(self, ctx: SlashContext) -> Annotated[str, "Nation name"]: """Ask the player for information.""" - raise NotImplementedError + nation_name_modal = Modal( + ShortText( + label="Provide your nation name", + custom_id="nation_name", + min_length=3, + max_length=50, + required=True, + ), + title="Player Information", + ) + await ctx.send_modal(modal=nation_name_modal) + + modal_ctx: ModalContext = await ctx.bot.wait_for_modal(nation_name_modal) + nation_name = modal_ctx.responses["nation_name"] + + await modal_ctx.send(f"<@{ctx.user.id}> You are playing as a leader of {nation_name}", ephemeral=True) + + return nation_name From 5fd44214d97c3bc78c8ab974f563c333a92ac315 Mon Sep 17 00:00:00 2001 From: hazyfossa Date: Tue, 23 Jul 2024 22:55:22 +0500 Subject: [PATCH 012/116] Refactor how players are added to a game instance --- src/game.py | 19 +++++++++++++++---- src/game_start.py | 7 ++++--- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/game.py b/src/game.py index 17d55e9..a845944 100644 --- a/src/game.py +++ b/src/game.py @@ -1,14 +1,25 @@ -from interactions import User +from typing import Annotated + +from attrs import define +from interactions import SlashContext from models import State GameID = str +@define +class Player: + ctx: SlashContext + state: State + + class Game: def __init__(self, id: GameID) -> None: self.id = id - self.players: dict[str, State] = {} + self.players: dict[Annotated[int, "discord id"], Player] = {} + + async def add_player(self, context: SlashContext, nation_name: str) -> None: + self.players[context.user.id] = Player(context, State(nation_name)) - async def add_player(self, user: User, nation_name: str) -> None: - self.players[user.id] = State(nation_name) + async def loop(self) -> None: ... diff --git a/src/game_start.py b/src/game_start.py index 82fedc4..57c5398 100644 --- a/src/game_start.py +++ b/src/game_start.py @@ -1,4 +1,5 @@ import random +from asyncio import create_task from string import ascii_uppercase, digits from typing import TYPE_CHECKING, Annotated @@ -50,10 +51,10 @@ def __init__(self, _: "Client") -> None: @slash_command(name="defcord_create", description="Create a new DEFCORD game.") async def create(self, ctx: SlashContext) -> None: """Create a game of DEFCORD""" - game = self.game_factory.create_game() # Add the first player here + game = self.game_factory.create_game() nation_name = await self.register_player() - await game.add_player(ctx.user, nation_name) + await game.add_player(ctx, nation_name) embed = Embed( title="New game started!", @@ -73,7 +74,7 @@ async def join(self, ctx: SlashContext, invite: str) -> None: raise NotImplementedError nation_name = await self.register_player() - await game.add_player(ctx.user, nation_name) # Ask nation name here + await game.add_player(ctx, nation_name) # Ask nation name here async def register_player(self) -> Annotated[str, "Nation name"]: """Ask the player for information.""" From 112521fba4f5bc3f93263e0a79d15b4f59d4b456 Mon Sep 17 00:00:00 2001 From: hazyfossa Date: Tue, 23 Jul 2024 23:03:43 +0500 Subject: [PATCH 013/116] Remove message when registering --- src/game_start.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/game_start.py b/src/game_start.py index e2bed25..49e6761 100644 --- a/src/game_start.py +++ b/src/game_start.py @@ -76,7 +76,7 @@ async def join(self, ctx: SlashContext, invite: str) -> None: if game is None: raise NotImplementedError - nation_name = await self.register_player() + nation_name = await self.register_player(ctx) await game.add_player(ctx, nation_name) # Ask nation name here async def register_player(self, ctx: SlashContext) -> Annotated[str, "Nation name"]: @@ -94,8 +94,7 @@ async def register_player(self, ctx: SlashContext) -> Annotated[str, "Nation nam await ctx.send_modal(modal=nation_name_modal) modal_ctx: ModalContext = await ctx.bot.wait_for_modal(nation_name_modal) - nation_name = modal_ctx.responses["nation_name"] - await modal_ctx.send(f"<@{ctx.user.id}> You are playing as a leader of {nation_name}", ephemeral=True) + # await modal_ctx.send(f"<@{ctx.user.id}> You are playing as a leader of {nation_name}", ephemeral=True) - return nation_name + return modal_ctx.responses["nation_name"] From dacc85245b5358994ed42ce3dc2b1646ddbf717d Mon Sep 17 00:00:00 2001 From: Maheshkumar Date: Wed, 24 Jul 2024 00:06:27 +0530 Subject: [PATCH 014/116] Revert to sending reply for modal response --- src/game_start.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/game_start.py b/src/game_start.py index 49e6761..364633b 100644 --- a/src/game_start.py +++ b/src/game_start.py @@ -94,7 +94,10 @@ async def register_player(self, ctx: SlashContext) -> Annotated[str, "Nation nam await ctx.send_modal(modal=nation_name_modal) modal_ctx: ModalContext = await ctx.bot.wait_for_modal(nation_name_modal) + nation_name = modal_ctx.responses["nation_name"] - # await modal_ctx.send(f"<@{ctx.user.id}> You are playing as a leader of {nation_name}", ephemeral=True) + # interaction needs a response or we need to defer it. + await modal_ctx.send(f"<@{ctx.user.id}> You are playing as a leader of {nation_name}", ephemeral=True) + # TODO: think about defer and its args. - return modal_ctx.responses["nation_name"] + return nation_name From 42a9d8debaaa3a758b6a7cb6da5bb8ee0d4d6245 Mon Sep 17 00:00:00 2001 From: DhanvantG Date: Wed, 24 Jul 2024 16:10:58 +0530 Subject: [PATCH 015/116] Sample --- src/example.py | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/src/example.py b/src/example.py index 4dd9139..fd04bfa 100644 --- a/src/example.py +++ b/src/example.py @@ -6,18 +6,29 @@ Template( "Hello, leader of {nation_name}. Can you spare some money for our new farming equipment?", choices={ - "Yeah, sure": {"money": -10, "loyalty": +5}, + "Sure": {"money": -10, "loyalty": +5}, "Nope": {"loyalty": -5}, - }, + } ), Template( - """Things are great, we managed to grow even more crops than expected. - Thank you for your help, leader! \n Here, have this small gift from our community""", + "Things are great, we managed to grow even more crops than expected.\ + Thank you for your help, leader! \n Here, have this small gift from our community", choices = {"Thanks!": {"money": +15}}, condition = lambda state: state.loyalty>70 and state.money<300, ), ]), - StageGroup([2, 3], [ - Template("Etc, you get the idea"), + StageGroup(2, [ + Template("O Great leader of {nation_name}! Please provide money to upgrade our equipment", + choices = { + "Sure": {"money": -20, "loyalty": +5}, + "Nope": {"loyalty": -5} + } + ), + Template( + "Things are great, we managed to grow even more crops than expected.\ + Thank you for your help, leader! \n Here, have this small gift from our community", + choices = {"Thanks!": {"money": +30}}, + condition = lambda state: state.loyalty>75 and state.money<800, + ), ]), ]) From f11a75cfe11dddef78038c58437e58253197971f Mon Sep 17 00:00:00 2001 From: DhanvantG Date: Wed, 24 Jul 2024 16:15:25 +0530 Subject: [PATCH 016/116] Sample --- src/example.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/example.py b/src/example.py index fd04bfa..aa6293d 100644 --- a/src/example.py +++ b/src/example.py @@ -8,7 +8,7 @@ choices={ "Sure": {"money": -10, "loyalty": +5}, "Nope": {"loyalty": -5}, - } + }, ), Template( "Things are great, we managed to grow even more crops than expected.\ @@ -21,8 +21,8 @@ Template("O Great leader of {nation_name}! Please provide money to upgrade our equipment", choices = { "Sure": {"money": -20, "loyalty": +5}, - "Nope": {"loyalty": -5} - } + "Nope": {"loyalty": -5}, + }, ), Template( "Things are great, we managed to grow even more crops than expected.\ From f77801fafe63195b5bab1b2b4f1f01e789b3d716 Mon Sep 17 00:00:00 2001 From: Sapient44 Date: Wed, 24 Jul 2024 19:33:10 +0530 Subject: [PATCH 017/116] Add more vairables as per the GDD --- src/models.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/models.py b/src/models.py index 02eff7c..1da0285 100644 --- a/src/models.py +++ b/src/models.py @@ -12,9 +12,17 @@ class State: nation_name: str stage: Stage = 1 - money: int = 100 - loyalty: int = 50 - some_other_thing: int = 0 + # Money with the government + money: float = 100 + + # How loyal people feel to the current government that you have created + loyalty: float = 50 + + # How vulnerable is the country from external threats + security: float = 50 + + # Lower means entity sabotage and vice versa (might add this as a later future) + world_opinion: float = 50 def apply(self, consequence: dict) -> None: for k, v in consequence.items(): From ae686f8d893c360f1f94c52015b07b71808e764b Mon Sep 17 00:00:00 2001 From: hazyfossa <148675205+hazyfossa@users.noreply.github.com> Date: Wed, 24 Jul 2024 20:02:45 +0500 Subject: [PATCH 018/116] Game management logic (#5) * Basic game management draft * Implement random game id's * Player registration UI prototype * Get nation name from user for registration * Refactor how players are added to a game instance * Remove message when registering * Revert to sending reply for modal response * Updating this models from main * Changing class State to the name PlayerState * Change the name to GameInteraction --------- Co-authored-by: Maheshkumar Co-authored-by: Sapient44 --- main.py | 2 +- src/game.py | 25 ++++++++++ src/game_interaction.py | 102 ++++++++++++++++++++++++++++++++++++++++ src/game_start.py | 61 ------------------------ src/models.py | 13 ++--- 5 files changed, 135 insertions(+), 68 deletions(-) create mode 100644 src/game.py create mode 100644 src/game_interaction.py delete mode 100644 src/game_start.py diff --git a/main.py b/main.py index 2999b5c..32eb288 100644 --- a/main.py +++ b/main.py @@ -55,5 +55,5 @@ def get_developer_mode() -> bool: if DEV: bot.load_extension("interactions.ext.jurigged") - bot.load_extension("src.game_start") + bot.load_extension("src.game_interaction") bot.start(token=get_token()) diff --git a/src/game.py b/src/game.py new file mode 100644 index 0000000..66d8187 --- /dev/null +++ b/src/game.py @@ -0,0 +1,25 @@ +from typing import Annotated + +from attrs import define +from interactions import SlashContext + +from src.models import PlayerState + +GameID = str + + +@define +class Player: + ctx: SlashContext + state: PlayerState + + +class Game: + def __init__(self, id: GameID) -> None: + self.id = id + self.players: dict[Annotated[int, "discord id"], Player] = {} + + async def add_player(self, context: SlashContext, nation_name: str) -> None: + self.players[context.user.id] = Player(context, PlayerState(nation_name)) + + async def loop(self) -> None: ... diff --git a/src/game_interaction.py b/src/game_interaction.py new file mode 100644 index 0000000..a3da00b --- /dev/null +++ b/src/game_interaction.py @@ -0,0 +1,102 @@ +import random +from string import ascii_uppercase, digits +from typing import TYPE_CHECKING, Annotated + +from interactions import ( + Embed, + Extension, + Modal, + ModalContext, + OptionType, + ShortText, + SlashContext, + slash_command, + slash_option, +) + +from src.game import Game, GameID + +if TYPE_CHECKING: + from interactions import Client + + +class GameFactory: + def __init__(self) -> None: + self.store: dict[GameID, Game] = {} + + def generate_game_id(self) -> GameID: + while True: + game_id = "".join(random.choice(ascii_uppercase + digits) for i in range(12)) # noqa: S311 This isn't for crypto purposes + + if game_id in self.store: + continue + + return game_id + + def create_game(self) -> Game: + game_id = self.generate_game_id() + game = Game(game_id) + self.store[game_id] = game + + return game + + def query_game(self, game_id: GameID) -> Game | None: + return self.store.get(game_id, None) + + +class GameInteraction(Extension): + """Control the extension entry point.""" + + def __init__(self, _: "Client") -> None: + self.game_factory = GameFactory() + + @slash_command(name="defcord_create", description="Create a new DEFCORD game.") + async def create(self, ctx: SlashContext) -> None: + """Create a game of DEFCORD.""" + game = self.game_factory.create_game() # Add the first player here + nation_name = await self.register_player(ctx) + + await game.add_player(ctx, nation_name) + + embed = Embed( + title="New game started!", + description=f"Your invite: {game.id}", + color=(255, 0, 0), + ) + + await ctx.send(embed=embed) + + @slash_command(name="defcord_join", description="Join a game of DEFCORD.") + @slash_option("invite", "The invite code for the game", required=True, opt_type=OptionType.STRING) + async def join(self, ctx: SlashContext, invite: str) -> None: + """Join a game of DEFCORD.""" + game = self.game_factory.query_game(invite) + + if game is None: + raise NotImplementedError + + nation_name = await self.register_player(ctx) + await game.add_player(ctx, nation_name) # Ask nation name here + + async def register_player(self, ctx: SlashContext) -> Annotated[str, "Nation name"]: + """Ask the player for information.""" + nation_name_modal = Modal( + ShortText( + label="Provide your nation name", + custom_id="nation_name", + min_length=3, + max_length=50, + required=True, + ), + title="Player Information", + ) + await ctx.send_modal(modal=nation_name_modal) + + modal_ctx: ModalContext = await ctx.bot.wait_for_modal(nation_name_modal) + nation_name = modal_ctx.responses["nation_name"] + + # interaction needs a response or we need to defer it. + await modal_ctx.send(f"<@{ctx.user.id}> You are playing as a leader of {nation_name}", ephemeral=True) + # TODO: think about defer and its args. + + return nation_name diff --git a/src/game_start.py b/src/game_start.py deleted file mode 100644 index ef3e21f..0000000 --- a/src/game_start.py +++ /dev/null @@ -1,61 +0,0 @@ -from interactions import ( - ActionRow, - Button, - ButtonStyle, - ComponentContext, - Embed, - Extension, - SlashContext, - component_callback, - slash_command, -) - -users_ids: list[int] = [] # Global Var for the players in the game - - -class GameInitiation(Extension): - """Control the extension entry point.""" - - @slash_command(name="start_defcord", description="Welcome to the simulation called DEFCORD") - async def my_command_function(self, ctx: SlashContext) -> None: - """Start the game of DEFCORD.""" - # Here would need to check are there any other instances of the game - components = ActionRow( - Button( - style=ButtonStyle.GREEN, - label="Join", - custom_id="join_game", - ), - Button( - style=ButtonStyle.RED, - label="Leave", - custom_id="leave_game", - ), - ) - embed = Embed( - title="Starting DEFCORD", - description=f"The tactical game of DEFCORD has been initiated by <@{ctx.user.id}>", - color=(255, 0, 0), - ) - await ctx.send(embed=embed, components=components) - - @component_callback("join_game") - async def join_callback(self, ctx: ComponentContext) -> None: - """Control player joining the game.""" - # Need to make this channel wise - user = ctx.user - if user.id not in users_ids: - await ctx.send(f"Player <@{user.id}> has joined") - users_ids.append(user.id) - else: - await ctx.send("You are already part of the game", ephemeral=True) - - @component_callback("leave_game") - async def leave_callback(self, ctx: ComponentContext) -> None: - """Control players leaving the game.""" - user = ctx.user - if user.id in users_ids: - await ctx.send(f"Player <@{user.id}> has left") - users_ids.remove(user.id) - else: - await ctx.send("You are not part of the game", ephemeral=True) diff --git a/src/models.py b/src/models.py index 50fe9ae..d29f85c 100644 --- a/src/models.py +++ b/src/models.py @@ -3,17 +3,18 @@ from attrs import asdict, define, field, frozen from interactions import ActionRow, Button, ButtonStyle, Embed +from interactions import ActionRow, Button, ButtonStyle, Embed Stage = Literal[1, 2, 3] # Adjustable @define -class State: +class PlayerState: """The current state of the player.""" nation_name: str - stage: Stage = 1 + # Player values that state the current position of player money: int = 100 loyalty: int = 50 some_other_thing: int = 0 @@ -25,10 +26,10 @@ def apply(self, consequence: dict) -> None: Consequence = dict[Any, Any] -Condition = Callable[[State], bool] | None +Condition = Callable[[PlayerState], bool] | None -def always_true(_: State) -> Literal[True]: +def always_true(_: PlayerState) -> Literal[True]: """Return True.""" return True @@ -41,11 +42,11 @@ class Template: choices: dict[str, Consequence] # Specify button color here somehow. condition: Condition = field(converter=lambda condition: always_true if condition is None else condition) - def format(self, state: State) -> str: + def format(self, state: PlayerState) -> str: """Format the text.""" return self.text.format(asdict(state)) - def to_embed(self, state: State) -> tuple[Embed, ActionRow]: + def to_embed(self, state: PlayerState) -> tuple[Embed, ActionRow]: """Return embed and action row for UI.""" buttons: list[Button] = [] From a51e14c76330812a78650b38727fc509aa70b31b Mon Sep 17 00:00:00 2001 From: hazyfossa Date: Wed, 24 Jul 2024 20:12:00 +0500 Subject: [PATCH 019/116] move player registration to Player class --- src/game.py | 49 ++++++++++++++++++++++++++++++++++------- src/game_interaction.py | 34 +++------------------------- 2 files changed, 44 insertions(+), 39 deletions(-) diff --git a/src/game.py b/src/game.py index f48dca1..fcabd2e 100644 --- a/src/game.py +++ b/src/game.py @@ -1,17 +1,40 @@ +import asyncio from typing import Annotated -from attrs import define -from interactions import SlashContext +from interactions import Modal, ModalContext, ShortText, SlashContext from src.models import State +from utils import error_embed GameID = str -@define class Player: - ctx: SlashContext - state: State + def __init__(self, ctx: SlashContext) -> None: + self.ctx = ctx + self.state = None + + async def register(self) -> None: + """Ask the player for information.""" + registration_modal = Modal( + ShortText( + label="Provide your nation name", + custom_id="nation_name", + min_length=3, + max_length=50, + required=True, + ), + title="Player Information", + ) + await self.ctx.send_modal(modal=registration_modal) + + modal_ctx: ModalContext = await self.ctx.bot.wait_for_modal(registration_modal) + + # await modal_ctx.send(f"<@{ctx.user.id}> You are playing as a leader of {nation_name}", ephemeral=True) + + nation_name = modal_ctx.responses["nation_name"] + + self.state = State(nation_name) class Game: @@ -19,7 +42,17 @@ def __init__(self, id: GameID) -> None: self.id = id self.players: dict[Annotated[int, "discord id"], Player] = {} - async def add_player(self, context: SlashContext, nation_name: str) -> None: - self.players[context.user.id] = Player(context, State(nation_name)) + async def add_player(self, ctx: SlashContext) -> None: + self.players[ctx.user.id] = Player(ctx) + + async def loop(self) -> None: + players = self.players.values() + + while True: + try: + await asyncio.gather(*[self.tick(player) for player in players], return_exceptions=True) + except Exception: # noqa: BLE001 + for player in players: + await player.ctx.send(embed=error_embed) - async def loop(self) -> None: ... + async def tick(self, player: Player) -> None: ... diff --git a/src/game_interaction.py b/src/game_interaction.py index a3da00b..a7fe685 100644 --- a/src/game_interaction.py +++ b/src/game_interaction.py @@ -1,14 +1,11 @@ import random from string import ascii_uppercase, digits -from typing import TYPE_CHECKING, Annotated +from typing import TYPE_CHECKING from interactions import ( Embed, Extension, - Modal, - ModalContext, OptionType, - ShortText, SlashContext, slash_command, slash_option, @@ -54,9 +51,8 @@ def __init__(self, _: "Client") -> None: async def create(self, ctx: SlashContext) -> None: """Create a game of DEFCORD.""" game = self.game_factory.create_game() # Add the first player here - nation_name = await self.register_player(ctx) - await game.add_player(ctx, nation_name) + await game.add_player(ctx) embed = Embed( title="New game started!", @@ -75,28 +71,4 @@ async def join(self, ctx: SlashContext, invite: str) -> None: if game is None: raise NotImplementedError - nation_name = await self.register_player(ctx) - await game.add_player(ctx, nation_name) # Ask nation name here - - async def register_player(self, ctx: SlashContext) -> Annotated[str, "Nation name"]: - """Ask the player for information.""" - nation_name_modal = Modal( - ShortText( - label="Provide your nation name", - custom_id="nation_name", - min_length=3, - max_length=50, - required=True, - ), - title="Player Information", - ) - await ctx.send_modal(modal=nation_name_modal) - - modal_ctx: ModalContext = await ctx.bot.wait_for_modal(nation_name_modal) - nation_name = modal_ctx.responses["nation_name"] - - # interaction needs a response or we need to defer it. - await modal_ctx.send(f"<@{ctx.user.id}> You are playing as a leader of {nation_name}", ephemeral=True) - # TODO: think about defer and its args. - - return nation_name + await game.add_player(ctx) From de090dc040a66782259de26837b735c76295a5d5 Mon Sep 17 00:00:00 2001 From: hazyfossa Date: Wed, 24 Jul 2024 20:16:46 +0500 Subject: [PATCH 020/116] hotfix: make condition optional --- src/models.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/models.py b/src/models.py index d29f85c..a772aa9 100644 --- a/src/models.py +++ b/src/models.py @@ -3,7 +3,6 @@ from attrs import asdict, define, field, frozen from interactions import ActionRow, Button, ButtonStyle, Embed -from interactions import ActionRow, Button, ButtonStyle, Embed Stage = Literal[1, 2, 3] # Adjustable @@ -40,7 +39,7 @@ class Template: text: str choices: dict[str, Consequence] # Specify button color here somehow. - condition: Condition = field(converter=lambda condition: always_true if condition is None else condition) + condition: Condition | None = field(converter=lambda condition: always_true if condition is None else condition) def format(self, state: PlayerState) -> str: """Format the text.""" From c7c8a22d8c0aee9dbf3c805f071e09e6553ceb33 Mon Sep 17 00:00:00 2001 From: hazyfossa Date: Wed, 24 Jul 2024 20:17:48 +0500 Subject: [PATCH 021/116] add a common error message prototype --- src/utils.py | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 src/utils.py diff --git a/src/utils.py b/src/utils.py new file mode 100644 index 0000000..e850259 --- /dev/null +++ b/src/utils.py @@ -0,0 +1,3 @@ +from interactions import Embed + +error_embed = Embed() From 817995f9d79706087a5f6e1c0740d3d5b99427bf Mon Sep 17 00:00:00 2001 From: hazyfossa Date: Wed, 24 Jul 2024 20:21:16 +0500 Subject: [PATCH 022/116] formatting --- src/models.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/models.py b/src/models.py index edb2911..82f1736 100644 --- a/src/models.py +++ b/src/models.py @@ -13,17 +13,17 @@ class PlayerState: nation_name: str - # Money with the government + # Money with the government money: float = 100 - + # How loyal people feel to the current government that you have created loyalty: float = 50 - - # How vulnerable is the country from external threats + + # How vulnerable is the country from external threats security: float = 50 - + # Lower means entity sabotage and vice versa (might add this as a later future) - world_opinion: float = 50 + world_opinion: float = 50 def apply(self, consequence: dict) -> None: """Apply the consequnces to current state.""" From bbda9df2499e090acb70dab972a26bd35bde9d69 Mon Sep 17 00:00:00 2001 From: hazyfossa Date: Wed, 24 Jul 2024 20:37:10 +0500 Subject: [PATCH 023/116] move PlayerState to game.py --- src/game.py | 30 +++++++++++++++++++++++++++--- src/game_interaction.py | 2 +- src/models.py | 28 +++------------------------- 3 files changed, 31 insertions(+), 29 deletions(-) diff --git a/src/game.py b/src/game.py index fcabd2e..a6a6973 100644 --- a/src/game.py +++ b/src/game.py @@ -1,18 +1,42 @@ import asyncio from typing import Annotated +from attrs import define from interactions import Modal, ModalContext, ShortText, SlashContext -from src.models import State from utils import error_embed GameID = str +@define +class PlayerState: + """The current state of the player.""" + + nation_name: str + + # Money with the government + money: float = 100 + + # How loyal people feel to the current government that you have created + loyalty: float = 50 + + # How vulnerable is the country from external threats + security: float = 50 + + # Lower means entity sabotage and vice versa (might add this as a later future) + world_opinion: float = 50 + + def apply(self, consequence: dict) -> None: + """Apply the consequnces to current state.""" + for k, v in consequence.items(): + self.__dict__[k] += v + + class Player: def __init__(self, ctx: SlashContext) -> None: self.ctx = ctx - self.state = None + self.state: PlayerState = None # type: ignore TODO: properly type that state isn't none after register async def register(self) -> None: """Ask the player for information.""" @@ -34,7 +58,7 @@ async def register(self) -> None: nation_name = modal_ctx.responses["nation_name"] - self.state = State(nation_name) + self.state = PlayerState(nation_name) class Game: diff --git a/src/game_interaction.py b/src/game_interaction.py index a7fe685..0e504b8 100644 --- a/src/game_interaction.py +++ b/src/game_interaction.py @@ -11,7 +11,7 @@ slash_option, ) -from src.game import Game, GameID +from game import Game, GameID if TYPE_CHECKING: from interactions import Client diff --git a/src/models.py b/src/models.py index 82f1736..cf972a1 100644 --- a/src/models.py +++ b/src/models.py @@ -1,34 +1,12 @@ from collections.abc import Callable from typing import Any, Literal, get_args -from attrs import asdict, define, field, frozen +from attrs import asdict, field, frozen from interactions import ActionRow, Button, ButtonStyle, Embed -Stage = Literal[1, 2, 3] # Adjustable - - -@define -class PlayerState: - """The current state of the player.""" - - nation_name: str - - # Money with the government - money: float = 100 +from game import PlayerState - # How loyal people feel to the current government that you have created - loyalty: float = 50 - - # How vulnerable is the country from external threats - security: float = 50 - - # Lower means entity sabotage and vice versa (might add this as a later future) - world_opinion: float = 50 - - def apply(self, consequence: dict) -> None: - """Apply the consequnces to current state.""" - for k, v in consequence.items(): - self.__dict__[k] += v +Stage = Literal[1, 2, 3] # Adjustable Consequence = dict[Any, Any] From b59bd6f05bde488ed48cf69b75d1cf661cd6ccc8 Mon Sep 17 00:00:00 2001 From: hazyfossa Date: Wed, 24 Jul 2024 20:58:19 +0500 Subject: [PATCH 024/116] Move Stage type to game.py --- src/game.py | 5 ++++- src/models.py | 5 +---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/game.py b/src/game.py index a6a6973..6ac213e 100644 --- a/src/game.py +++ b/src/game.py @@ -1,5 +1,5 @@ import asyncio -from typing import Annotated +from typing import Annotated, Literal from attrs import define from interactions import Modal, ModalContext, ShortText, SlashContext @@ -80,3 +80,6 @@ async def loop(self) -> None: await player.ctx.send(embed=error_embed) async def tick(self, player: Player) -> None: ... + + +Stage = Literal[1, 2, 3] # Adjustable diff --git a/src/models.py b/src/models.py index cf972a1..d12f591 100644 --- a/src/models.py +++ b/src/models.py @@ -4,10 +4,7 @@ from attrs import asdict, field, frozen from interactions import ActionRow, Button, ButtonStyle, Embed -from game import PlayerState - -Stage = Literal[1, 2, 3] # Adjustable - +from game import PlayerState, Stage Consequence = dict[Any, Any] Condition = Callable[[PlayerState], bool] | None From ce075aab002c8cc85c8669b9be0da85ea0e28856 Mon Sep 17 00:00:00 2001 From: hazyfossa Date: Wed, 24 Jul 2024 23:55:58 +0500 Subject: [PATCH 025/116] templating backend and different types of templates --- src/example.py | 11 ++-- src/game.py | 6 ++- src/models.py | 73 ------------------------- src/templating.py | 133 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 143 insertions(+), 80 deletions(-) delete mode 100644 src/models.py create mode 100644 src/templating.py diff --git a/src/example.py b/src/example.py index aa6293d..d31fbb0 100644 --- a/src/example.py +++ b/src/example.py @@ -1,16 +1,17 @@ -from models import Actor, StageGroup, Template +from templating import Actor, StageGroup +from templating import ChoiceTemplate as t # fmt: off Actor("John the Farmer", "url_here",[ StageGroup(1, [ - Template( + t( "Hello, leader of {nation_name}. Can you spare some money for our new farming equipment?", choices={ "Sure": {"money": -10, "loyalty": +5}, "Nope": {"loyalty": -5}, }, ), - Template( + t( "Things are great, we managed to grow even more crops than expected.\ Thank you for your help, leader! \n Here, have this small gift from our community", choices = {"Thanks!": {"money": +15}}, @@ -18,13 +19,13 @@ ), ]), StageGroup(2, [ - Template("O Great leader of {nation_name}! Please provide money to upgrade our equipment", + t("O Great leader of {nation_name}! Please provide money to upgrade our equipment", choices = { "Sure": {"money": -20, "loyalty": +5}, "Nope": {"loyalty": -5}, }, ), - Template( + t( "Things are great, we managed to grow even more crops than expected.\ Thank you for your help, leader! \n Here, have this small gift from our community", choices = {"Thanks!": {"money": +30}}, diff --git a/src/game.py b/src/game.py index 6ac213e..f6f0ce1 100644 --- a/src/game.py +++ b/src/game.py @@ -34,9 +34,10 @@ def apply(self, consequence: dict) -> None: class Player: - def __init__(self, ctx: SlashContext) -> None: + def __init__(self, ctx: SlashContext, game: "Game") -> None: self.ctx = ctx self.state: PlayerState = None # type: ignore TODO: properly type that state isn't none after register + self.game = game async def register(self) -> None: """Ask the player for information.""" @@ -65,9 +66,10 @@ class Game: def __init__(self, id: GameID) -> None: self.id = id self.players: dict[Annotated[int, "discord id"], Player] = {} + self.stage: Stage = 1 async def add_player(self, ctx: SlashContext) -> None: - self.players[ctx.user.id] = Player(ctx) + self.players[ctx.user.id] = Player(ctx, self) async def loop(self) -> None: players = self.players.values() diff --git a/src/models.py b/src/models.py deleted file mode 100644 index d12f591..0000000 --- a/src/models.py +++ /dev/null @@ -1,73 +0,0 @@ -from collections.abc import Callable -from typing import Any, Literal, get_args - -from attrs import asdict, field, frozen -from interactions import ActionRow, Button, ButtonStyle, Embed - -from game import PlayerState, Stage - -Consequence = dict[Any, Any] -Condition = Callable[[PlayerState], bool] | None - - -def always_true(_: PlayerState) -> Literal[True]: - """Return True.""" - return True - - -@frozen -class Template: - """Make a template for the messages to be served.""" - - text: str - choices: dict[str, Consequence] # Specify button color here somehow. - condition: Condition | None = field(converter=lambda condition: always_true if condition is None else condition) - - def format(self, state: PlayerState) -> str: - """Format the text.""" - return self.text.format(asdict(state)) - - def to_embed(self, state: PlayerState) -> tuple[Embed, ActionRow]: - """Return embed and action row for UI.""" - buttons: list[Button] = [] - - for id, choice in enumerate(self.choices.items()): - button = Button( - label=f"{next(iter(choice.keys()))}", # Something isn't right here - style=ButtonStyle.BLURPLE, - custom_id=f"Choice {id}", - ) - buttons.append(button) - - action_row = ActionRow(*buttons) - - embed = Embed( - title=state.nation_name, - description=self.text, - color=(0, 0, 255), - # Can we access Actor here in this class? like this actor is saying this - # hazyfossa: good question - ) - return (embed, action_row) - - -TotalStages = get_args(Stage) - - -@frozen -class StageGroup: - """A helper class to group templates based on their stage in game.""" - - stage: Stage | tuple[Stage] | Literal["all"] = field( - converter=lambda stage: TotalStages if stage == "all" else stage, - ) - templates: list[Template] - - -@frozen -class Actor: - """An in-game character.""" - - name: str - picture: str # we'll need to serve these as static content probably - templates: list[StageGroup] diff --git a/src/templating.py b/src/templating.py new file mode 100644 index 0000000..40f35dc --- /dev/null +++ b/src/templating.py @@ -0,0 +1,133 @@ +import random +from collections.abc import Callable +from typing import Any, Literal, get_args + +from attrs import asdict, field, frozen +from interactions import ActionRow, Button, ButtonStyle, Embed + +from game import Player, PlayerState, Stage + +Consequence = dict[Any, Any] +Condition = Callable[[PlayerState], bool] | None + + +def always_true(_: PlayerState) -> Literal[True]: + """Return True.""" + return True + + +@frozen +class Template: + """Make a template for the messages to be served.""" + + text: str = field() + weight: int = 100 + + def format(self, state: PlayerState) -> str: + """Format the text.""" + return self.text.format(asdict(state)) + + def to_embed(self, player: Player, actor: "Actor") -> Embed: + """Get an embed for UI.""" + # Now you can access actor here + return Embed( + title=player.state.nation_name, + description=self.format(player.state), + color=(0, 0, 255), + ) + + async def ui(self, player: Player, actor: "Actor") -> None: + await player.ctx.send(embed=self.to_embed(player, actor)) + + +def not_none(var: Any | None) -> Any: # noqa: ANN401 temporary workaround FIXME + if var is None: + raise AttributeError + + return var + + +@frozen +class ChoiceTemplate(Template): + choices: dict[str, Consequence] = field(default=None, converter=not_none) # Specify button color here somehow. + condition: Condition = always_true + + async def ui(self, player: Player, actor: "Actor") -> None: + """Send UI and apply consequences.""" + buttons: list[Button] = [] + + for id, choice in enumerate(self.choices.items()): + button = Button( + label=f"{next(iter(choice.keys()))}", # Something isn't right here + style=ButtonStyle.BLURPLE, + custom_id=f"Choice {id}", + ) + buttons.append(button) + + embed = self.to_embed(player, actor) + + await player.ctx.send(embed=embed, action_row=ActionRow(*buttons)) + + +total_stages = get_args(Stage) + + +@frozen +class StageGroup: + """A helper class to group templates based on their stage in game.""" + + stage: Stage | tuple[Stage] | Literal["all"] = field( + converter=lambda stage: total_stages if stage == "all" else stage, + ) + templates: list[Template] + + +@frozen +class ActorData: + """The data needed to make an embed for UI.""" + + name: str + picture: str # we'll need to serve these as static content probably + + +class StageData: + def __init__(self, templates: list[Template]) -> None: + self.templates = templates + self.weights = [template.weight for template in self.templates] + + def get_random(self) -> Template: + return random.choices(self.templates, weights=self.weights, k=1)[0] # noqa: S311 Not for cryptographic purposes + + +class Actor: + def __init__(self, name: str, picture: str, templates: list[StageGroup]) -> None: + self.actor = ActorData(name, picture) + self.stages = self.cast_stages(templates) + + def cast_stages(self, stage_groups: list[StageGroup]) -> dict[Stage, StageData]: # Not the best code TODO: improve + stages: dict[Stage, StageData] = {} + + for stage in total_stages: + stage_templates = [] + + for stage_group in stage_groups: + template_stage = stage_group.stage + + if isinstance(template_stage, int): + if template_stage != stage: + continue + + elif stage not in template_stage: + continue + + stage_templates += stage_group.templates + + stages[stage] = StageData(stage_templates) + + return stages + + async def send(self, target: Player) -> None: + stage = self.stages[target.game.stage] + template = stage.get_random() + + await template.ui(target, self) From 15ee62eb90c13229f55735a7a51450e1302b9781 Mon Sep 17 00:00:00 2001 From: Sapient44 Date: Thu, 25 Jul 2024 01:23:46 +0530 Subject: [PATCH 026/116] Adding actor name in embed --- src/templating.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/templating.py b/src/templating.py index 40f35dc..52573fb 100644 --- a/src/templating.py +++ b/src/templating.py @@ -31,7 +31,7 @@ def to_embed(self, player: Player, actor: "Actor") -> Embed: """Get an embed for UI.""" # Now you can access actor here return Embed( - title=player.state.nation_name, + title=f"{actor.actor.name} of {player.state.nation_name}", description=self.format(player.state), color=(0, 0, 255), ) From 903a1b6994ea8714a493d608c9e96d085ea2992e Mon Sep 17 00:00:00 2001 From: hazyfossa Date: Thu, 25 Jul 2024 13:34:10 +0500 Subject: [PATCH 027/116] simplify data layout --- src/templating.py | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/src/templating.py b/src/templating.py index 52573fb..6b4b7af 100644 --- a/src/templating.py +++ b/src/templating.py @@ -11,11 +11,6 @@ Condition = Callable[[PlayerState], bool] | None -def always_true(_: PlayerState) -> Literal[True]: - """Return True.""" - return True - - @frozen class Template: """Make a template for the messages to be served.""" @@ -31,7 +26,7 @@ def to_embed(self, player: Player, actor: "Actor") -> Embed: """Get an embed for UI.""" # Now you can access actor here return Embed( - title=f"{actor.actor.name} of {player.state.nation_name}", + title=f"{actor.name} of {player.state.nation_name}", description=self.format(player.state), color=(0, 0, 255), ) @@ -50,7 +45,13 @@ def not_none(var: Any | None) -> Any: # noqa: ANN401 temporary workaround FIXME @frozen class ChoiceTemplate(Template): choices: dict[str, Consequence] = field(default=None, converter=not_none) # Specify button color here somehow. - condition: Condition = always_true + condition: Condition | None = None + + def is_available(self, player: Player) -> bool: + if self.condition is not None: + return self.condition(player.state) + + return True async def ui(self, player: Player, actor: "Actor") -> None: """Send UI and apply consequences.""" @@ -82,14 +83,6 @@ class StageGroup: templates: list[Template] -@frozen -class ActorData: - """The data needed to make an embed for UI.""" - - name: str - picture: str # we'll need to serve these as static content probably - - class StageData: def __init__(self, templates: list[Template]) -> None: self.templates = templates @@ -101,7 +94,8 @@ def get_random(self) -> Template: class Actor: def __init__(self, name: str, picture: str, templates: list[StageGroup]) -> None: - self.actor = ActorData(name, picture) + self.name = name + self.picture = picture self.stages = self.cast_stages(templates) def cast_stages(self, stage_groups: list[StageGroup]) -> dict[Stage, StageData]: # Not the best code TODO: improve From 2cf4dabc93bfb93ae11e4c9dc645835257180398 Mon Sep 17 00:00:00 2001 From: hazyfossa Date: Thu, 25 Jul 2024 14:05:59 +0500 Subject: [PATCH 028/116] simplify StageGroup casting --- src/templating.py | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/src/templating.py b/src/templating.py index 6b4b7af..af88f8f 100644 --- a/src/templating.py +++ b/src/templating.py @@ -77,9 +77,16 @@ async def ui(self, player: Player, actor: "Actor") -> None: class StageGroup: """A helper class to group templates based on their stage in game.""" - stage: Stage | tuple[Stage] | Literal["all"] = field( - converter=lambda stage: total_stages if stage == "all" else stage, - ) + @staticmethod + def convert_stage(stage: Stage | list[Stage] | Literal["all"]) -> list[Stage]: + if stage == "all": + return list(total_stages) + if isinstance(stage, int): + return [stage] + + return stage + + stage: Stage | list[Stage] | Literal["all"] = field(converter=convert_stage) templates: list[Template] @@ -101,22 +108,14 @@ def __init__(self, name: str, picture: str, templates: list[StageGroup]) -> None def cast_stages(self, stage_groups: list[StageGroup]) -> dict[Stage, StageData]: # Not the best code TODO: improve stages: dict[Stage, StageData] = {} - for stage in total_stages: + for stage_slot in total_stages: stage_templates = [] for stage_group in stage_groups: - template_stage = stage_group.stage - - if isinstance(template_stage, int): - if template_stage != stage: - continue - - elif stage not in template_stage: - continue - - stage_templates += stage_group.templates + if stage_slot in stage_group.stage: + stage_templates += stage_group.templates - stages[stage] = StageData(stage_templates) + stages[stage_slot] = StageData(stage_templates) return stages From 023c4237ddc5120f567f4acb29f8d6e1fd5e1103 Mon Sep 17 00:00:00 2001 From: hazyfossa <148675205+hazyfossa@users.noreply.github.com> Date: Thu, 25 Jul 2024 19:55:31 +0500 Subject: [PATCH 029/116] Templating v2 (#7) * Basic game management draft * Implement random game id's * Player registration UI prototype * Remove player * Get nation name from user for registration * Refactor how players are added to a game instance * Remove message when registering * Revert to sending reply for modal response * Add more vairables as per the GDD * move player registration to Player class * hotfix: make condition optional * add a common error message prototype * formatting * move PlayerState to game.py * Move Stage type to game.py * templating backend and different types of templates * Adding actor name in embed * simplify data layout --------- Co-authored-by: Sapient44 Co-authored-by: Maheshkumar --- src/example.py | 11 ++-- src/game.py | 78 +++++++++++++++++++++--- src/game_interaction.py | 36 ++---------- src/models.py | 92 ----------------------------- src/templating.py | 127 ++++++++++++++++++++++++++++++++++++++++ src/utils.py | 3 + 6 files changed, 210 insertions(+), 137 deletions(-) delete mode 100644 src/models.py create mode 100644 src/templating.py create mode 100644 src/utils.py diff --git a/src/example.py b/src/example.py index aa6293d..d31fbb0 100644 --- a/src/example.py +++ b/src/example.py @@ -1,16 +1,17 @@ -from models import Actor, StageGroup, Template +from templating import Actor, StageGroup +from templating import ChoiceTemplate as t # fmt: off Actor("John the Farmer", "url_here",[ StageGroup(1, [ - Template( + t( "Hello, leader of {nation_name}. Can you spare some money for our new farming equipment?", choices={ "Sure": {"money": -10, "loyalty": +5}, "Nope": {"loyalty": -5}, }, ), - Template( + t( "Things are great, we managed to grow even more crops than expected.\ Thank you for your help, leader! \n Here, have this small gift from our community", choices = {"Thanks!": {"money": +15}}, @@ -18,13 +19,13 @@ ), ]), StageGroup(2, [ - Template("O Great leader of {nation_name}! Please provide money to upgrade our equipment", + t("O Great leader of {nation_name}! Please provide money to upgrade our equipment", choices = { "Sure": {"money": -20, "loyalty": +5}, "Nope": {"loyalty": -5}, }, ), - Template( + t( "Things are great, we managed to grow even more crops than expected.\ Thank you for your help, leader! \n Here, have this small gift from our community", choices = {"Thanks!": {"money": +30}}, diff --git a/src/game.py b/src/game.py index 66d8187..f6f0ce1 100644 --- a/src/game.py +++ b/src/game.py @@ -1,25 +1,87 @@ -from typing import Annotated +import asyncio +from typing import Annotated, Literal from attrs import define -from interactions import SlashContext +from interactions import Modal, ModalContext, ShortText, SlashContext -from src.models import PlayerState +from utils import error_embed GameID = str @define +class PlayerState: + """The current state of the player.""" + + nation_name: str + + # Money with the government + money: float = 100 + + # How loyal people feel to the current government that you have created + loyalty: float = 50 + + # How vulnerable is the country from external threats + security: float = 50 + + # Lower means entity sabotage and vice versa (might add this as a later future) + world_opinion: float = 50 + + def apply(self, consequence: dict) -> None: + """Apply the consequnces to current state.""" + for k, v in consequence.items(): + self.__dict__[k] += v + + class Player: - ctx: SlashContext - state: PlayerState + def __init__(self, ctx: SlashContext, game: "Game") -> None: + self.ctx = ctx + self.state: PlayerState = None # type: ignore TODO: properly type that state isn't none after register + self.game = game + + async def register(self) -> None: + """Ask the player for information.""" + registration_modal = Modal( + ShortText( + label="Provide your nation name", + custom_id="nation_name", + min_length=3, + max_length=50, + required=True, + ), + title="Player Information", + ) + await self.ctx.send_modal(modal=registration_modal) + + modal_ctx: ModalContext = await self.ctx.bot.wait_for_modal(registration_modal) + + # await modal_ctx.send(f"<@{ctx.user.id}> You are playing as a leader of {nation_name}", ephemeral=True) + + nation_name = modal_ctx.responses["nation_name"] + + self.state = PlayerState(nation_name) class Game: def __init__(self, id: GameID) -> None: self.id = id self.players: dict[Annotated[int, "discord id"], Player] = {} + self.stage: Stage = 1 + + async def add_player(self, ctx: SlashContext) -> None: + self.players[ctx.user.id] = Player(ctx, self) + + async def loop(self) -> None: + players = self.players.values() + + while True: + try: + await asyncio.gather(*[self.tick(player) for player in players], return_exceptions=True) + except Exception: # noqa: BLE001 + for player in players: + await player.ctx.send(embed=error_embed) + + async def tick(self, player: Player) -> None: ... - async def add_player(self, context: SlashContext, nation_name: str) -> None: - self.players[context.user.id] = Player(context, PlayerState(nation_name)) - async def loop(self) -> None: ... +Stage = Literal[1, 2, 3] # Adjustable diff --git a/src/game_interaction.py b/src/game_interaction.py index a3da00b..0e504b8 100644 --- a/src/game_interaction.py +++ b/src/game_interaction.py @@ -1,20 +1,17 @@ import random from string import ascii_uppercase, digits -from typing import TYPE_CHECKING, Annotated +from typing import TYPE_CHECKING from interactions import ( Embed, Extension, - Modal, - ModalContext, OptionType, - ShortText, SlashContext, slash_command, slash_option, ) -from src.game import Game, GameID +from game import Game, GameID if TYPE_CHECKING: from interactions import Client @@ -54,9 +51,8 @@ def __init__(self, _: "Client") -> None: async def create(self, ctx: SlashContext) -> None: """Create a game of DEFCORD.""" game = self.game_factory.create_game() # Add the first player here - nation_name = await self.register_player(ctx) - await game.add_player(ctx, nation_name) + await game.add_player(ctx) embed = Embed( title="New game started!", @@ -75,28 +71,4 @@ async def join(self, ctx: SlashContext, invite: str) -> None: if game is None: raise NotImplementedError - nation_name = await self.register_player(ctx) - await game.add_player(ctx, nation_name) # Ask nation name here - - async def register_player(self, ctx: SlashContext) -> Annotated[str, "Nation name"]: - """Ask the player for information.""" - nation_name_modal = Modal( - ShortText( - label="Provide your nation name", - custom_id="nation_name", - min_length=3, - max_length=50, - required=True, - ), - title="Player Information", - ) - await ctx.send_modal(modal=nation_name_modal) - - modal_ctx: ModalContext = await ctx.bot.wait_for_modal(nation_name_modal) - nation_name = modal_ctx.responses["nation_name"] - - # interaction needs a response or we need to defer it. - await modal_ctx.send(f"<@{ctx.user.id}> You are playing as a leader of {nation_name}", ephemeral=True) - # TODO: think about defer and its args. - - return nation_name + await game.add_player(ctx) diff --git a/src/models.py b/src/models.py deleted file mode 100644 index d29f85c..0000000 --- a/src/models.py +++ /dev/null @@ -1,92 +0,0 @@ -from collections.abc import Callable -from typing import Any, Literal, get_args - -from attrs import asdict, define, field, frozen -from interactions import ActionRow, Button, ButtonStyle, Embed -from interactions import ActionRow, Button, ButtonStyle, Embed - -Stage = Literal[1, 2, 3] # Adjustable - - -@define -class PlayerState: - """The current state of the player.""" - - nation_name: str - - # Player values that state the current position of player - money: int = 100 - loyalty: int = 50 - some_other_thing: int = 0 - - def apply(self, consequence: dict) -> None: - """Apply the consequnces to current state.""" - for k, v in consequence.items(): - self.__dict__[k] += v - - -Consequence = dict[Any, Any] -Condition = Callable[[PlayerState], bool] | None - - -def always_true(_: PlayerState) -> Literal[True]: - """Return True.""" - return True - - -@frozen -class Template: - """Make a template for the messages to be served.""" - - text: str - choices: dict[str, Consequence] # Specify button color here somehow. - condition: Condition = field(converter=lambda condition: always_true if condition is None else condition) - - def format(self, state: PlayerState) -> str: - """Format the text.""" - return self.text.format(asdict(state)) - - def to_embed(self, state: PlayerState) -> tuple[Embed, ActionRow]: - """Return embed and action row for UI.""" - buttons: list[Button] = [] - - for id, choice in enumerate(self.choices.items()): - button = Button( - label=f"{next(iter(choice.keys()))}", # Something isn't right here - style=ButtonStyle.BLURPLE, - custom_id=f"Choice {id}", - ) - buttons.append(button) - - action_row = ActionRow(*buttons) - - embed = Embed( - title=state.nation_name, - description=self.text, - color=(0, 0, 255), - # Can we access Actor here in this class? like this actor is saying this - # hazyfossa: good question - ) - return (embed, action_row) - - -TotalStages = get_args(Stage) - - -@frozen -class StageGroup: - """A helper class to group templates based on their stage in game.""" - - stage: Stage | tuple[Stage] | Literal["all"] = field( - converter=lambda stage: TotalStages if stage == "all" else stage, - ) - templates: list[Template] - - -@frozen -class Actor: - """An in-game character.""" - - name: str - picture: str # we'll need to serve these as static content probably - templates: list[StageGroup] diff --git a/src/templating.py b/src/templating.py new file mode 100644 index 0000000..6b4b7af --- /dev/null +++ b/src/templating.py @@ -0,0 +1,127 @@ +import random +from collections.abc import Callable +from typing import Any, Literal, get_args + +from attrs import asdict, field, frozen +from interactions import ActionRow, Button, ButtonStyle, Embed + +from game import Player, PlayerState, Stage + +Consequence = dict[Any, Any] +Condition = Callable[[PlayerState], bool] | None + + +@frozen +class Template: + """Make a template for the messages to be served.""" + + text: str = field() + weight: int = 100 + + def format(self, state: PlayerState) -> str: + """Format the text.""" + return self.text.format(asdict(state)) + + def to_embed(self, player: Player, actor: "Actor") -> Embed: + """Get an embed for UI.""" + # Now you can access actor here + return Embed( + title=f"{actor.name} of {player.state.nation_name}", + description=self.format(player.state), + color=(0, 0, 255), + ) + + async def ui(self, player: Player, actor: "Actor") -> None: + await player.ctx.send(embed=self.to_embed(player, actor)) + + +def not_none(var: Any | None) -> Any: # noqa: ANN401 temporary workaround FIXME + if var is None: + raise AttributeError + + return var + + +@frozen +class ChoiceTemplate(Template): + choices: dict[str, Consequence] = field(default=None, converter=not_none) # Specify button color here somehow. + condition: Condition | None = None + + def is_available(self, player: Player) -> bool: + if self.condition is not None: + return self.condition(player.state) + + return True + + async def ui(self, player: Player, actor: "Actor") -> None: + """Send UI and apply consequences.""" + buttons: list[Button] = [] + + for id, choice in enumerate(self.choices.items()): + button = Button( + label=f"{next(iter(choice.keys()))}", # Something isn't right here + style=ButtonStyle.BLURPLE, + custom_id=f"Choice {id}", + ) + buttons.append(button) + + embed = self.to_embed(player, actor) + + await player.ctx.send(embed=embed, action_row=ActionRow(*buttons)) + + +total_stages = get_args(Stage) + + +@frozen +class StageGroup: + """A helper class to group templates based on their stage in game.""" + + stage: Stage | tuple[Stage] | Literal["all"] = field( + converter=lambda stage: total_stages if stage == "all" else stage, + ) + templates: list[Template] + + +class StageData: + def __init__(self, templates: list[Template]) -> None: + self.templates = templates + self.weights = [template.weight for template in self.templates] + + def get_random(self) -> Template: + return random.choices(self.templates, weights=self.weights, k=1)[0] # noqa: S311 Not for cryptographic purposes + + +class Actor: + def __init__(self, name: str, picture: str, templates: list[StageGroup]) -> None: + self.name = name + self.picture = picture + self.stages = self.cast_stages(templates) + + def cast_stages(self, stage_groups: list[StageGroup]) -> dict[Stage, StageData]: # Not the best code TODO: improve + stages: dict[Stage, StageData] = {} + + for stage in total_stages: + stage_templates = [] + + for stage_group in stage_groups: + template_stage = stage_group.stage + + if isinstance(template_stage, int): + if template_stage != stage: + continue + + elif stage not in template_stage: + continue + + stage_templates += stage_group.templates + + stages[stage] = StageData(stage_templates) + + return stages + + async def send(self, target: Player) -> None: + stage = self.stages[target.game.stage] + template = stage.get_random() + + await template.ui(target, self) diff --git a/src/utils.py b/src/utils.py new file mode 100644 index 0000000..e850259 --- /dev/null +++ b/src/utils.py @@ -0,0 +1,3 @@ +from interactions import Embed + +error_embed = Embed() From f091beb011502b07693c85fe725e1f865b725e4b Mon Sep 17 00:00:00 2001 From: hazyfossa Date: Thu, 25 Jul 2024 20:16:33 +0500 Subject: [PATCH 030/116] move weighted random to a separate file and add proper typing --- src/templating.py | 7 ++++--- src/weighted_random.py | 19 +++++++++++++++++++ 2 files changed, 23 insertions(+), 3 deletions(-) create mode 100644 src/weighted_random.py diff --git a/src/templating.py b/src/templating.py index af88f8f..e98c5a9 100644 --- a/src/templating.py +++ b/src/templating.py @@ -6,6 +6,7 @@ from interactions import ActionRow, Button, ButtonStyle, Embed from game import Player, PlayerState, Stage +from weighted_random import WeightedList Consequence = dict[Any, Any] Condition = Callable[[PlayerState], bool] | None @@ -105,8 +106,8 @@ def __init__(self, name: str, picture: str, templates: list[StageGroup]) -> None self.picture = picture self.stages = self.cast_stages(templates) - def cast_stages(self, stage_groups: list[StageGroup]) -> dict[Stage, StageData]: # Not the best code TODO: improve - stages: dict[Stage, StageData] = {} + def cast_stages(self, stage_groups: list[StageGroup]) -> dict[Stage, WeightedList[Template]]: + stages: dict[Stage, WeightedList[Template]] = {} for stage_slot in total_stages: stage_templates = [] @@ -115,7 +116,7 @@ def cast_stages(self, stage_groups: list[StageGroup]) -> dict[Stage, StageData]: if stage_slot in stage_group.stage: stage_templates += stage_group.templates - stages[stage_slot] = StageData(stage_templates) + stages[stage_slot] = WeightedList(stage_templates) return stages diff --git a/src/weighted_random.py b/src/weighted_random.py new file mode 100644 index 0000000..a15bac1 --- /dev/null +++ b/src/weighted_random.py @@ -0,0 +1,19 @@ +import random +from typing import Any, Generic, Protocol, TypeVar + + +class SupportsWeight(Protocol): + @property + def weight(self) -> int: ... + + +T = TypeVar("T", bound=SupportsWeight) + + +class WeightedList(Generic[T]): + def __init__(self, values: list[T]) -> None: + self.values = values + self.weights = [value.weight for value in self.values] + + def get_random(self) -> T: + return random.choices(self.values, weights=self.weights, k=1)[0] # noqa: S311 Not for cryptographic purposes From edbcf9d66fcdb7da3422d03df4067b12250c0933 Mon Sep 17 00:00:00 2001 From: Maheshkumar Date: Thu, 25 Jul 2024 20:53:16 +0530 Subject: [PATCH 031/116] Add dynamic character import util function --- src/resources/characters/character_1.py | 1 + src/templating.py | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+) create mode 100644 src/resources/characters/character_1.py diff --git a/src/resources/characters/character_1.py b/src/resources/characters/character_1.py new file mode 100644 index 0000000..f05ead9 --- /dev/null +++ b/src/resources/characters/character_1.py @@ -0,0 +1 @@ +character = 'Farmer' diff --git a/src/templating.py b/src/templating.py index 6b4b7af..416d81f 100644 --- a/src/templating.py +++ b/src/templating.py @@ -1,5 +1,8 @@ +import importlib.util +import logging import random from collections.abc import Callable +from pathlib import Path from typing import Any, Literal, get_args from attrs import asdict, field, frozen @@ -7,6 +10,8 @@ from game import Player, PlayerState, Stage +logger = logging.getLogger("defcon-internal") + Consequence = dict[Any, Any] Condition = Callable[[PlayerState], bool] | None @@ -125,3 +130,20 @@ async def send(self, target: Player) -> None: template = stage.get_random() await template.ui(target, self) + + +def get_characters() -> list: + """Return a list of characters imported from all the character definition files.""" + characters = [] + + for path in Path("src/resources/characters").rglob("*.py"): + try: + spec = importlib.util.spec_from_file_location(path.name.strip(".py"), str(path)) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + + if character := getattr(module, "character", None): + characters.append(character) + except Exception: + logger.exception("Exception raised when trying to import characters. current_path %s", str(path)) + return characters From ca6e8439d1c648ed597ffcf38e14ff6594b9e908 Mon Sep 17 00:00:00 2001 From: hazyfossa Date: Thu, 25 Jul 2024 20:46:49 +0500 Subject: [PATCH 032/116] add append to WeightedList --- src/weighted_random.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/weighted_random.py b/src/weighted_random.py index a15bac1..d38726b 100644 --- a/src/weighted_random.py +++ b/src/weighted_random.py @@ -11,9 +11,16 @@ def weight(self) -> int: ... class WeightedList(Generic[T]): - def __init__(self, values: list[T]) -> None: - self.values = values - self.weights = [value.weight for value in self.values] + def __init__(self, values: list[T] | None = None) -> None: + if values is not None: + self.values = values + self.weights = [value.weight for value in values] + else: + self.values = [] + + def append(self, value: T) -> None: + self.values.append(value) + self.weights.append(value.weight) def get_random(self) -> T: return random.choices(self.values, weights=self.weights, k=1)[0] # noqa: S311 Not for cryptographic purposes From 2df864f54a8f79e75d89a567a283c3ae2d021395 Mon Sep 17 00:00:00 2001 From: hazyfossa Date: Thu, 25 Jul 2024 20:59:25 +0500 Subject: [PATCH 033/116] make Actor a dataclass --- src/templating.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/templating.py b/src/templating.py index e98c5a9..8698d0b 100644 --- a/src/templating.py +++ b/src/templating.py @@ -100,13 +100,10 @@ def get_random(self) -> Template: return random.choices(self.templates, weights=self.weights, k=1)[0] # noqa: S311 Not for cryptographic purposes +@frozen class Actor: - def __init__(self, name: str, picture: str, templates: list[StageGroup]) -> None: - self.name = name - self.picture = picture - self.stages = self.cast_stages(templates) - - def cast_stages(self, stage_groups: list[StageGroup]) -> dict[Stage, WeightedList[Template]]: + @staticmethod + def cast_stages(stage_groups: list[StageGroup]) -> dict[Stage, WeightedList[Template]]: stages: dict[Stage, WeightedList[Template]] = {} for stage_slot in total_stages: @@ -120,6 +117,11 @@ def cast_stages(self, stage_groups: list[StageGroup]) -> dict[Stage, WeightedLis return stages + name: str + picture: str + stages: dict[Stage, WeightedList[Template]] = field(converter=cast_stages) + weight: int = 100 + async def send(self, target: Player) -> None: stage = self.stages[target.game.stage] template = stage.get_random() From acd9b71b5a176e9e1809965649f79bde91210683 Mon Sep 17 00:00:00 2001 From: hazyfossa Date: Thu, 25 Jul 2024 21:05:50 +0500 Subject: [PATCH 034/116] move code to characters __init__.py --- src/characters/__init__.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 src/characters/__init__.py diff --git a/src/characters/__init__.py b/src/characters/__init__.py new file mode 100644 index 0000000..274fc47 --- /dev/null +++ b/src/characters/__init__.py @@ -0,0 +1,21 @@ +from importlib import import_module +from logging import getLogger +from pathlib import Path + +from templating import Actor +from weighted_random import WeightedList + +log = getLogger("character-init") + +characters: WeightedList[Actor] = WeightedList() + +for file in Path(__file__).parent.glob("*.chr.py"): + if not file.is_file() or file.name == __file__: + continue + + try: + characters.append(character := import_module(file.name).character) + log.debug("Loaded {character.name} character from {file}.") + except Exception: + log.exception("Character file {file} is invalid. Skipping.") + continue From facbde4c58f454e79b5d8fdbba8494f5cd280c37 Mon Sep 17 00:00:00 2001 From: hazyfossa Date: Thu, 25 Jul 2024 21:12:08 +0500 Subject: [PATCH 035/116] move example to characters/ --- src/{example.py => characters/example.chr.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/{example.py => characters/example.chr.py} (100%) diff --git a/src/example.py b/src/characters/example.chr.py similarity index 100% rename from src/example.py rename to src/characters/example.chr.py From a000752e7cd2c8b2b5376c10c39770fe294eb8b2 Mon Sep 17 00:00:00 2001 From: hazyfossa Date: Thu, 25 Jul 2024 21:12:39 +0500 Subject: [PATCH 036/116] disable false-positive linting for custom character files --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 6ac7430..0e9e454 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,4 +44,4 @@ ignore = [ ] [tool.ruff.lint.per-file-ignores] -"example.py" = ["PLR2004"] +"*.chr.py" = ["PLR2004"] From 1d7c1b0ccce0e87c3ef2d49a80760b1fb906807a Mon Sep 17 00:00:00 2001 From: hazyfossa Date: Thu, 25 Jul 2024 21:13:33 +0500 Subject: [PATCH 037/116] change imports from relative to parent-package --- src/characters/__init__.py | 4 ++-- src/game_interaction.py | 2 +- src/templating.py | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/characters/__init__.py b/src/characters/__init__.py index 274fc47..a9e56de 100644 --- a/src/characters/__init__.py +++ b/src/characters/__init__.py @@ -2,8 +2,8 @@ from logging import getLogger from pathlib import Path -from templating import Actor -from weighted_random import WeightedList +from src.templating import Actor +from src.weighted_random import WeightedList log = getLogger("character-init") diff --git a/src/game_interaction.py b/src/game_interaction.py index 0e504b8..a7fe685 100644 --- a/src/game_interaction.py +++ b/src/game_interaction.py @@ -11,7 +11,7 @@ slash_option, ) -from game import Game, GameID +from src.game import Game, GameID if TYPE_CHECKING: from interactions import Client diff --git a/src/templating.py b/src/templating.py index 8698d0b..6e24357 100644 --- a/src/templating.py +++ b/src/templating.py @@ -5,8 +5,8 @@ from attrs import asdict, field, frozen from interactions import ActionRow, Button, ButtonStyle, Embed -from game import Player, PlayerState, Stage -from weighted_random import WeightedList +from src.game import Player, PlayerState, Stage +from src.weighted_random import WeightedList Consequence = dict[Any, Any] Condition = Callable[[PlayerState], bool] | None From 8ac4d7151c6601fa3cd34fb46bc7b35a893ba38f Mon Sep 17 00:00:00 2001 From: hazyfossa Date: Thu, 25 Jul 2024 21:14:32 +0500 Subject: [PATCH 038/116] hotfix: imports --- src/game.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/game.py b/src/game.py index f6f0ce1..9322092 100644 --- a/src/game.py +++ b/src/game.py @@ -4,7 +4,7 @@ from attrs import define from interactions import Modal, ModalContext, ShortText, SlashContext -from utils import error_embed +from src.utils import error_embed GameID = str From 57dcdbcd7d57e520108639ea3a76cc2bbc41bfe7 Mon Sep 17 00:00:00 2001 From: hazyfossa Date: Thu, 25 Jul 2024 21:55:35 +0500 Subject: [PATCH 039/116] change character extension --- pyproject.toml | 2 +- src/characters/__init__.py | 14 ++++++++------ src/characters/{example.chr.py => example_chr.py} | 6 +++--- src/resources/characters/character_1.py | 1 - src/weighted_random.py | 2 +- 5 files changed, 13 insertions(+), 12 deletions(-) rename src/characters/{example.chr.py => example_chr.py} (89%) delete mode 100644 src/resources/characters/character_1.py diff --git a/pyproject.toml b/pyproject.toml index 0e9e454..fed20f8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,4 +44,4 @@ ignore = [ ] [tool.ruff.lint.per-file-ignores] -"*.chr.py" = ["PLR2004"] +"src/characters/**.py" = ["PLR2004"] diff --git a/src/characters/__init__.py b/src/characters/__init__.py index a9e56de..ccf80f9 100644 --- a/src/characters/__init__.py +++ b/src/characters/__init__.py @@ -7,15 +7,17 @@ log = getLogger("character-init") -characters: WeightedList[Actor] = WeightedList() +all_characters: WeightedList[Actor] = WeightedList() -for file in Path(__file__).parent.glob("*.chr.py"): - if not file.is_file() or file.name == __file__: +for file in Path(__file__).parent.glob("*.py"): + module = file.name.split(".")[0] + + if not file.is_file() or not module.endswith("_chr"): continue try: - characters.append(character := import_module(file.name).character) - log.debug("Loaded {character.name} character from {file}.") + all_characters.append(character := import_module(f"src.characters.{module}").character) + log.debug("Loaded %s character from %s.", character.name, file) except Exception: - log.exception("Character file {file} is invalid. Skipping.") + log.exception("Character file %s is invalid. Skipping.", file) continue diff --git a/src/characters/example.chr.py b/src/characters/example_chr.py similarity index 89% rename from src/characters/example.chr.py rename to src/characters/example_chr.py index d31fbb0..184d006 100644 --- a/src/characters/example.chr.py +++ b/src/characters/example_chr.py @@ -1,8 +1,8 @@ -from templating import Actor, StageGroup -from templating import ChoiceTemplate as t +from src.templating import Actor, StageGroup +from src.templating import ChoiceTemplate as t # fmt: off -Actor("John the Farmer", "url_here",[ +character = Actor("John the Farmer", "url_here",[ StageGroup(1, [ t( "Hello, leader of {nation_name}. Can you spare some money for our new farming equipment?", diff --git a/src/resources/characters/character_1.py b/src/resources/characters/character_1.py deleted file mode 100644 index f05ead9..0000000 --- a/src/resources/characters/character_1.py +++ /dev/null @@ -1 +0,0 @@ -character = 'Farmer' diff --git a/src/weighted_random.py b/src/weighted_random.py index d38726b..d0f6b58 100644 --- a/src/weighted_random.py +++ b/src/weighted_random.py @@ -1,5 +1,5 @@ import random -from typing import Any, Generic, Protocol, TypeVar +from typing import Generic, Protocol, TypeVar class SupportsWeight(Protocol): From fb199e8638847a8636c4420d37ccd079400458bf Mon Sep 17 00:00:00 2001 From: hazyfossa Date: Thu, 25 Jul 2024 22:00:47 +0500 Subject: [PATCH 040/116] basic tick prototype --- src/game.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/game.py b/src/game.py index 9322092..70b37f3 100644 --- a/src/game.py +++ b/src/game.py @@ -1,9 +1,11 @@ import asyncio +from re import template from typing import Annotated, Literal from attrs import define from interactions import Modal, ModalContext, ShortText, SlashContext +from src.characters import all_characters from src.utils import error_embed GameID = str @@ -81,7 +83,10 @@ async def loop(self) -> None: for player in players: await player.ctx.send(embed=error_embed) - async def tick(self, player: Player) -> None: ... + async def tick(self, player: Player) -> None: + character = all_characters.get_random() + + await character.send(player) Stage = Literal[1, 2, 3] # Adjustable From 8e8c0133abb244934a5a02cdf7671fac661133f9 Mon Sep 17 00:00:00 2001 From: Maheshkumar Date: Thu, 25 Jul 2024 23:46:05 +0530 Subject: [PATCH 041/116] Fix circular imports --- src/characters/__init__.py | 7 ++-- src/game.py | 67 ++++---------------------------------- src/player.py | 60 ++++++++++++++++++++++++++++++++++ src/templating.py | 3 +- src/weighted_random.py | 1 + 5 files changed, 75 insertions(+), 63 deletions(-) create mode 100644 src/player.py diff --git a/src/characters/__init__.py b/src/characters/__init__.py index ccf80f9..c908038 100644 --- a/src/characters/__init__.py +++ b/src/characters/__init__.py @@ -1,13 +1,16 @@ +import typing from importlib import import_module from logging import getLogger from pathlib import Path -from src.templating import Actor from src.weighted_random import WeightedList +if typing.TYPE_CHECKING: + from src.templating import Actor + log = getLogger("character-init") -all_characters: WeightedList[Actor] = WeightedList() +all_characters: WeightedList["Actor"] = WeightedList() for file in Path(__file__).parent.glob("*.py"): module = file.name.split(".")[0] diff --git a/src/game.py b/src/game.py index 70b37f3..6676d98 100644 --- a/src/game.py +++ b/src/game.py @@ -1,67 +1,17 @@ import asyncio -from re import template -from typing import Annotated, Literal +import typing +from typing import Annotated -from attrs import define -from interactions import Modal, ModalContext, ShortText, SlashContext +from interactions import SlashContext from src.characters import all_characters +from src.player import Player from src.utils import error_embed -GameID = str - - -@define -class PlayerState: - """The current state of the player.""" - - nation_name: str - - # Money with the government - money: float = 100 - - # How loyal people feel to the current government that you have created - loyalty: float = 50 - - # How vulnerable is the country from external threats - security: float = 50 - - # Lower means entity sabotage and vice versa (might add this as a later future) - world_opinion: float = 50 - - def apply(self, consequence: dict) -> None: - """Apply the consequnces to current state.""" - for k, v in consequence.items(): - self.__dict__[k] += v +if typing.TYPE_CHECKING: + from src.templating import Stage - -class Player: - def __init__(self, ctx: SlashContext, game: "Game") -> None: - self.ctx = ctx - self.state: PlayerState = None # type: ignore TODO: properly type that state isn't none after register - self.game = game - - async def register(self) -> None: - """Ask the player for information.""" - registration_modal = Modal( - ShortText( - label="Provide your nation name", - custom_id="nation_name", - min_length=3, - max_length=50, - required=True, - ), - title="Player Information", - ) - await self.ctx.send_modal(modal=registration_modal) - - modal_ctx: ModalContext = await self.ctx.bot.wait_for_modal(registration_modal) - - # await modal_ctx.send(f"<@{ctx.user.id}> You are playing as a leader of {nation_name}", ephemeral=True) - - nation_name = modal_ctx.responses["nation_name"] - - self.state = PlayerState(nation_name) +GameID = str class Game: @@ -87,6 +37,3 @@ async def tick(self, player: Player) -> None: character = all_characters.get_random() await character.send(player) - - -Stage = Literal[1, 2, 3] # Adjustable diff --git a/src/player.py b/src/player.py new file mode 100644 index 0000000..5161117 --- /dev/null +++ b/src/player.py @@ -0,0 +1,60 @@ +import typing + +from attrs import define +from interactions import Modal, ModalContext, ShortText, SlashContext + +if typing.TYPE_CHECKING: + from src.game import Game + + +@define +class PlayerState: + """The current state of the player.""" + + nation_name: str + + # Money with the government + money: float = 100 + + # How loyal people feel to the current government that you have created + loyalty: float = 50 + + # How vulnerable is the country from external threats + security: float = 50 + + # Lower means entity sabotage and vice versa (might add this as a later future) + world_opinion: float = 50 + + def apply(self, consequence: dict) -> None: + """Apply the consequnces to current state.""" + for k, v in consequence.items(): + self.__dict__[k] += v + + +class Player: + def __init__(self, ctx: SlashContext, game: "Game") -> None: + self.ctx = ctx + self.state: PlayerState = None # type: ignore TODO: properly type that state isn't none after register + self.game = game + + async def register(self) -> None: + """Ask the player for information.""" + registration_modal = Modal( + ShortText( + label="Provide your nation name", + custom_id="nation_name", + min_length=3, + max_length=50, + required=True, + ), + title="Player Information", + ) + await self.ctx.send_modal(modal=registration_modal) + + modal_ctx: ModalContext = await self.ctx.bot.wait_for_modal(registration_modal) + + # await modal_ctx.send(f"<@{ctx.user.id}> You are playing as a leader of {nation_name}", ephemeral=True) + + nation_name = modal_ctx.responses["nation_name"] + + self.state = PlayerState(nation_name) diff --git a/src/templating.py b/src/templating.py index 6e24357..4ea15ac 100644 --- a/src/templating.py +++ b/src/templating.py @@ -5,11 +5,12 @@ from attrs import asdict, field, frozen from interactions import ActionRow, Button, ButtonStyle, Embed -from src.game import Player, PlayerState, Stage +from src.player import Player, PlayerState from src.weighted_random import WeightedList Consequence = dict[Any, Any] Condition = Callable[[PlayerState], bool] | None +Stage = Literal[1, 2, 3] # Adjustable @frozen diff --git a/src/weighted_random.py b/src/weighted_random.py index d0f6b58..32254f9 100644 --- a/src/weighted_random.py +++ b/src/weighted_random.py @@ -17,6 +17,7 @@ def __init__(self, values: list[T] | None = None) -> None: self.weights = [value.weight for value in values] else: self.values = [] + self.weights = [] def append(self, value: T) -> None: self.values.append(value) From 7b9d2e8a8ef2ebcc074a99e0d21ad1ab92e7c030 Mon Sep 17 00:00:00 2001 From: hazyfossa <148675205+hazyfossa@users.noreply.github.com> Date: Thu, 25 Jul 2024 23:29:17 +0500 Subject: [PATCH 042/116] Game loop (#6) * Basic game management draft * Implement random game id's * Player registration UI prototype * Remove player * Get nation name from user for registration * Refactor how players are added to a game instance * Remove message when registering * Revert to sending reply for modal response * Add more vairables as per the GDD * move player registration to Player class * hotfix: make condition optional * add a common error message prototype * formatting * move PlayerState to game.py * Move Stage type to game.py * templating backend and different types of templates * Adding actor name in embed * simplify data layout * simplify StageGroup casting * Templating v2 (#7) * Basic game management draft * Implement random game id's * Player registration UI prototype * Remove player * Get nation name from user for registration * Refactor how players are added to a game instance * Remove message when registering * Revert to sending reply for modal response * Add more vairables as per the GDD * move player registration to Player class * hotfix: make condition optional * add a common error message prototype * formatting * move PlayerState to game.py * Move Stage type to game.py * templating backend and different types of templates * Adding actor name in embed * simplify data layout --------- Co-authored-by: Sapient44 Co-authored-by: Maheshkumar * move weighted random to a separate file and add proper typing * Add dynamic character import util function * add append to WeightedList * make Actor a dataclass * move code to characters __init__.py * move example to characters/ * disable false-positive linting for custom character files * change imports from relative to parent-package * hotfix: imports * change character extension * basic tick prototype --------- Co-authored-by: Sapient44 Co-authored-by: Maheshkumar --- pyproject.toml | 2 +- src/characters/__init__.py | 23 +++++++++ src/{example.py => characters/example_chr.py} | 6 +-- src/game.py | 9 +++- src/game_interaction.py | 2 +- src/templating.py | 48 ++++++++++--------- src/weighted_random.py | 26 ++++++++++ 7 files changed, 86 insertions(+), 30 deletions(-) create mode 100644 src/characters/__init__.py rename src/{example.py => characters/example_chr.py} (89%) create mode 100644 src/weighted_random.py diff --git a/pyproject.toml b/pyproject.toml index 6ac7430..fed20f8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,4 +44,4 @@ ignore = [ ] [tool.ruff.lint.per-file-ignores] -"example.py" = ["PLR2004"] +"src/characters/**.py" = ["PLR2004"] diff --git a/src/characters/__init__.py b/src/characters/__init__.py new file mode 100644 index 0000000..ccf80f9 --- /dev/null +++ b/src/characters/__init__.py @@ -0,0 +1,23 @@ +from importlib import import_module +from logging import getLogger +from pathlib import Path + +from src.templating import Actor +from src.weighted_random import WeightedList + +log = getLogger("character-init") + +all_characters: WeightedList[Actor] = WeightedList() + +for file in Path(__file__).parent.glob("*.py"): + module = file.name.split(".")[0] + + if not file.is_file() or not module.endswith("_chr"): + continue + + try: + all_characters.append(character := import_module(f"src.characters.{module}").character) + log.debug("Loaded %s character from %s.", character.name, file) + except Exception: + log.exception("Character file %s is invalid. Skipping.", file) + continue diff --git a/src/example.py b/src/characters/example_chr.py similarity index 89% rename from src/example.py rename to src/characters/example_chr.py index d31fbb0..184d006 100644 --- a/src/example.py +++ b/src/characters/example_chr.py @@ -1,8 +1,8 @@ -from templating import Actor, StageGroup -from templating import ChoiceTemplate as t +from src.templating import Actor, StageGroup +from src.templating import ChoiceTemplate as t # fmt: off -Actor("John the Farmer", "url_here",[ +character = Actor("John the Farmer", "url_here",[ StageGroup(1, [ t( "Hello, leader of {nation_name}. Can you spare some money for our new farming equipment?", diff --git a/src/game.py b/src/game.py index f6f0ce1..70b37f3 100644 --- a/src/game.py +++ b/src/game.py @@ -1,10 +1,12 @@ import asyncio +from re import template from typing import Annotated, Literal from attrs import define from interactions import Modal, ModalContext, ShortText, SlashContext -from utils import error_embed +from src.characters import all_characters +from src.utils import error_embed GameID = str @@ -81,7 +83,10 @@ async def loop(self) -> None: for player in players: await player.ctx.send(embed=error_embed) - async def tick(self, player: Player) -> None: ... + async def tick(self, player: Player) -> None: + character = all_characters.get_random() + + await character.send(player) Stage = Literal[1, 2, 3] # Adjustable diff --git a/src/game_interaction.py b/src/game_interaction.py index 0e504b8..a7fe685 100644 --- a/src/game_interaction.py +++ b/src/game_interaction.py @@ -11,7 +11,7 @@ slash_option, ) -from game import Game, GameID +from src.game import Game, GameID if TYPE_CHECKING: from interactions import Client diff --git a/src/templating.py b/src/templating.py index 6b4b7af..6e24357 100644 --- a/src/templating.py +++ b/src/templating.py @@ -5,7 +5,8 @@ from attrs import asdict, field, frozen from interactions import ActionRow, Button, ButtonStyle, Embed -from game import Player, PlayerState, Stage +from src.game import Player, PlayerState, Stage +from src.weighted_random import WeightedList Consequence = dict[Any, Any] Condition = Callable[[PlayerState], bool] | None @@ -77,9 +78,16 @@ async def ui(self, player: Player, actor: "Actor") -> None: class StageGroup: """A helper class to group templates based on their stage in game.""" - stage: Stage | tuple[Stage] | Literal["all"] = field( - converter=lambda stage: total_stages if stage == "all" else stage, - ) + @staticmethod + def convert_stage(stage: Stage | list[Stage] | Literal["all"]) -> list[Stage]: + if stage == "all": + return list(total_stages) + if isinstance(stage, int): + return [stage] + + return stage + + stage: Stage | list[Stage] | Literal["all"] = field(converter=convert_stage) templates: list[Template] @@ -92,34 +100,28 @@ def get_random(self) -> Template: return random.choices(self.templates, weights=self.weights, k=1)[0] # noqa: S311 Not for cryptographic purposes +@frozen class Actor: - def __init__(self, name: str, picture: str, templates: list[StageGroup]) -> None: - self.name = name - self.picture = picture - self.stages = self.cast_stages(templates) + @staticmethod + def cast_stages(stage_groups: list[StageGroup]) -> dict[Stage, WeightedList[Template]]: + stages: dict[Stage, WeightedList[Template]] = {} - def cast_stages(self, stage_groups: list[StageGroup]) -> dict[Stage, StageData]: # Not the best code TODO: improve - stages: dict[Stage, StageData] = {} - - for stage in total_stages: + for stage_slot in total_stages: stage_templates = [] for stage_group in stage_groups: - template_stage = stage_group.stage - - if isinstance(template_stage, int): - if template_stage != stage: - continue + if stage_slot in stage_group.stage: + stage_templates += stage_group.templates - elif stage not in template_stage: - continue - - stage_templates += stage_group.templates - - stages[stage] = StageData(stage_templates) + stages[stage_slot] = WeightedList(stage_templates) return stages + name: str + picture: str + stages: dict[Stage, WeightedList[Template]] = field(converter=cast_stages) + weight: int = 100 + async def send(self, target: Player) -> None: stage = self.stages[target.game.stage] template = stage.get_random() diff --git a/src/weighted_random.py b/src/weighted_random.py new file mode 100644 index 0000000..d0f6b58 --- /dev/null +++ b/src/weighted_random.py @@ -0,0 +1,26 @@ +import random +from typing import Generic, Protocol, TypeVar + + +class SupportsWeight(Protocol): + @property + def weight(self) -> int: ... + + +T = TypeVar("T", bound=SupportsWeight) + + +class WeightedList(Generic[T]): + def __init__(self, values: list[T] | None = None) -> None: + if values is not None: + self.values = values + self.weights = [value.weight for value in values] + else: + self.values = [] + + def append(self, value: T) -> None: + self.values.append(value) + self.weights.append(value.weight) + + def get_random(self) -> T: + return random.choices(self.values, weights=self.weights, k=1)[0] # noqa: S311 Not for cryptographic purposes From fac649a3bd50944e4f744a58559c9bde2323457c Mon Sep 17 00:00:00 2001 From: Maheshkumar P <67100964+Maheshkumar-novice@users.noreply.github.com> Date: Fri, 26 Jul 2024 00:02:03 +0530 Subject: [PATCH 043/116] Add dynamic character import util function (#9) * Add dynamic character import util function * move code to characters __init__.py * move example to characters/ * disable false-positive linting for custom character files * change imports from relative to parent-package * hotfix: imports * change character extension --------- Co-authored-by: hazyfossa Co-authored-by: Sapient44 Co-authored-by: hazyfossa <148675205+hazyfossa@users.noreply.github.com> --- src/game.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/game.py b/src/game.py index 70b37f3..a949d02 100644 --- a/src/game.py +++ b/src/game.py @@ -5,6 +5,7 @@ from attrs import define from interactions import Modal, ModalContext, ShortText, SlashContext + from src.characters import all_characters from src.utils import error_embed From e4ed78dbd8525a1dcd9f57e9c5e2a8f83f93cd0f Mon Sep 17 00:00:00 2001 From: Maheshkumar Date: Fri, 26 Jul 2024 01:03:04 +0530 Subject: [PATCH 044/116] Fix template sending issues --- src/game.py | 5 +++-- src/player.py | 4 ++-- src/templating.py | 9 ++++----- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/game.py b/src/game.py index 6676d98..2850bf7 100644 --- a/src/game.py +++ b/src/game.py @@ -21,7 +21,9 @@ def __init__(self, id: GameID) -> None: self.stage: Stage = 1 async def add_player(self, ctx: SlashContext) -> None: - self.players[ctx.user.id] = Player(ctx, self) + player = Player(ctx, self) + await player.register() + self.players[ctx.user.id] = player async def loop(self) -> None: players = self.players.values() @@ -35,5 +37,4 @@ async def loop(self) -> None: async def tick(self, player: Player) -> None: character = all_characters.get_random() - await character.send(player) diff --git a/src/player.py b/src/player.py index 5161117..a1bb370 100644 --- a/src/player.py +++ b/src/player.py @@ -53,8 +53,8 @@ async def register(self) -> None: modal_ctx: ModalContext = await self.ctx.bot.wait_for_modal(registration_modal) - # await modal_ctx.send(f"<@{ctx.user.id}> You are playing as a leader of {nation_name}", ephemeral=True) - nation_name = modal_ctx.responses["nation_name"] + await modal_ctx.send(f"<@{self.ctx.user.id}> You are playing as a leader of {nation_name}", ephemeral=True) + self.state = PlayerState(nation_name) diff --git a/src/templating.py b/src/templating.py index 4ea15ac..f8268bd 100644 --- a/src/templating.py +++ b/src/templating.py @@ -22,7 +22,7 @@ class Template: def format(self, state: PlayerState) -> str: """Format the text.""" - return self.text.format(asdict(state)) + return self.text.format(**asdict(state)) def to_embed(self, player: Player, actor: "Actor") -> Embed: """Get an embed for UI.""" @@ -34,7 +34,7 @@ def to_embed(self, player: Player, actor: "Actor") -> Embed: ) async def ui(self, player: Player, actor: "Actor") -> None: - await player.ctx.send(embed=self.to_embed(player, actor)) + await player.ctx.send(embed=self.to_embed(player, actor), ephemeral=True) def not_none(var: Any | None) -> Any: # noqa: ANN401 temporary workaround FIXME @@ -61,7 +61,7 @@ async def ui(self, player: Player, actor: "Actor") -> None: for id, choice in enumerate(self.choices.items()): button = Button( - label=f"{next(iter(choice.keys()))}", # Something isn't right here + label=f"{next(iter(choice))}", style=ButtonStyle.BLURPLE, custom_id=f"Choice {id}", ) @@ -69,7 +69,7 @@ async def ui(self, player: Player, actor: "Actor") -> None: embed = self.to_embed(player, actor) - await player.ctx.send(embed=embed, action_row=ActionRow(*buttons)) + await player.ctx.send(embed=embed, components=ActionRow(*buttons), ephemeral=True) total_stages = get_args(Stage) @@ -126,5 +126,4 @@ def cast_stages(stage_groups: list[StageGroup]) -> dict[Stage, WeightedList[Temp async def send(self, target: Player) -> None: stage = self.stages[target.game.stage] template = stage.get_random() - await template.ui(target, self) From cfcc24dd34d863892ec019c4c36417740267f6f4 Mon Sep 17 00:00:00 2001 From: Sapient44 Date: Fri, 26 Jul 2024 17:37:06 +0530 Subject: [PATCH 045/116] basic seperation of stages --- src/game.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/game.py b/src/game.py index 70b37f3..d04876e 100644 --- a/src/game.py +++ b/src/game.py @@ -1,5 +1,5 @@ import asyncio -from re import template +import random from typing import Annotated, Literal from attrs import define @@ -69,6 +69,7 @@ def __init__(self, id: GameID) -> None: self.id = id self.players: dict[Annotated[int, "discord id"], Player] = {} self.stage: Stage = 1 + self.max_time = random.randrange(12,16) async def add_player(self, ctx: SlashContext) -> None: self.players[ctx.user.id] = Player(ctx, self) @@ -85,8 +86,18 @@ async def loop(self) -> None: async def tick(self, player: Player) -> None: character = all_characters.get_random() - - await character.send(player) - + # The sleep times are subject to change, based on hwo the actual gameplay feels + match self.stage: + case 1: + asyncio.sleep(15) + await character.send(player) + + case 2: + asyncio.sleep(13) + await character.send(player) + + case 1: + asyncio.sleep(10) + await character.send(player) Stage = Literal[1, 2, 3] # Adjustable From e8316b5ccbcefb0f6ec7f14822c7adbf88713664 Mon Sep 17 00:00:00 2001 From: Sapient44 Date: Fri, 26 Jul 2024 17:43:47 +0530 Subject: [PATCH 046/116] Add some variablility in time of sending message --- src/game.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/game.py b/src/game.py index d04876e..cf52b47 100644 --- a/src/game.py +++ b/src/game.py @@ -86,18 +86,19 @@ async def loop(self) -> None: async def tick(self, player: Player) -> None: character = all_characters.get_random() - # The sleep times are subject to change, based on hwo the actual gameplay feels + # The sleep times are subject to change, based on how the actual gameplay feels + # The randomness gives a variability of mentioned in the brackets match self.stage: case 1: - asyncio.sleep(15) + asyncio.sleep(15+(random.uniform(-2,2))) await character.send(player) case 2: - asyncio.sleep(13) + asyncio.sleep(13+(random.uniform(-2,1.5))) await character.send(player) case 1: - asyncio.sleep(10) + asyncio.sleep(10+(random.uniform(-2,1))) await character.send(player) Stage = Literal[1, 2, 3] # Adjustable From 15d0597d2e71676a855e02cfe6c5da0711d083ef Mon Sep 17 00:00:00 2001 From: Sapient44 Date: Fri, 26 Jul 2024 18:23:14 +0530 Subject: [PATCH 047/116] Add way to move the stage forward --- src/game.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/game.py b/src/game.py index cf52b47..c9c85bd 100644 --- a/src/game.py +++ b/src/game.py @@ -1,7 +1,7 @@ import asyncio import random from typing import Annotated, Literal - +import datetime from attrs import define from interactions import Modal, ModalContext, ShortText, SlashContext @@ -69,13 +69,21 @@ def __init__(self, id: GameID) -> None: self.id = id self.players: dict[Annotated[int, "discord id"], Player] = {} self.stage: Stage = 1 - self.max_time = random.randrange(12,16) + self.max_time: float = random.uniform(12.5, 16) + + self.cumm_percent_time_per_stage : list[float]= [0.25, 0.6, 1] + # Percentage of the time spent in the game when the next stage of the time begins (max value 1 = 100%) + + self.start_time = datetime.datetime.now() async def add_player(self, ctx: SlashContext) -> None: self.players[ctx.user.id] = Player(ctx, self) async def loop(self) -> None: players = self.players.values() + game_time :float = (datetime.datetime.now() - self.start_time) / datetime.timedelta(minutes=1) + if (game_time > self.cumm_percent_time_per_stage[self.stage - 1] * self.max_time) and (game_time < self.max_time): + self.stage += 1 while True: try: @@ -87,7 +95,7 @@ async def loop(self) -> None: async def tick(self, player: Player) -> None: character = all_characters.get_random() # The sleep times are subject to change, based on how the actual gameplay feels - # The randomness gives a variability of mentioned in the brackets + # The randomness gives a variability between the values mentioned in the brackets match self.stage: case 1: asyncio.sleep(15+(random.uniform(-2,2))) From bd33337e09b868048615ef657c6fcb8e46b3b5b5 Mon Sep 17 00:00:00 2001 From: Sapient44 Date: Fri, 26 Jul 2024 18:32:33 +0530 Subject: [PATCH 048/116] Linting --- src/game.py | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/src/game.py b/src/game.py index c9c85bd..930726b 100644 --- a/src/game.py +++ b/src/game.py @@ -1,7 +1,8 @@ import asyncio import random +from datetime import datetime, timedelta from typing import Annotated, Literal -import datetime + from attrs import define from interactions import Modal, ModalContext, ShortText, SlashContext @@ -36,6 +37,8 @@ def apply(self, consequence: dict) -> None: class Player: + """Initialize a Player and it's behaviours.""" + def __init__(self, ctx: SlashContext, game: "Game") -> None: self.ctx = ctx self.state: PlayerState = None # type: ignore TODO: properly type that state isn't none after register @@ -65,25 +68,30 @@ async def register(self) -> None: class Game: + """Initialize a Game and it's behaviours.""" + def __init__(self, id: GameID) -> None: self.id = id self.players: dict[Annotated[int, "discord id"], Player] = {} self.stage: Stage = 1 self.max_time: float = random.uniform(12.5, 16) - - self.cumm_percent_time_per_stage : list[float]= [0.25, 0.6, 1] + + self.cumm_percent_time_per_stage : list[float]= [0.25, 0.6, 1] # Percentage of the time spent in the game when the next stage of the time begins (max value 1 = 100%) - - self.start_time = datetime.datetime.now() + + self.start_time = datetime.now() async def add_player(self, ctx: SlashContext) -> None: + """Add player to the game.""" self.players[ctx.user.id] = Player(ctx, self) async def loop(self) -> None: + """Define the main loop of the game.""" players = self.players.values() - game_time :float = (datetime.datetime.now() - self.start_time) / datetime.timedelta(minutes=1) - if (game_time > self.cumm_percent_time_per_stage[self.stage - 1] * self.max_time) and (game_time < self.max_time): - self.stage += 1 + game_time :float = (datetime.now() - self.start_time) / timedelta(minutes=1) + if ((game_time > self.cumm_percent_time_per_stage[self.stage - 1] * self.max_time) + and (game_time < self.max_time)): + self.stage += 1 while True: try: @@ -93,9 +101,10 @@ async def loop(self) -> None: await player.ctx.send(embed=error_embed) async def tick(self, player: Player) -> None: + """Define the activities done in every game tick.""" character = all_characters.get_random() # The sleep times are subject to change, based on how the actual gameplay feels - # The randomness gives a variability between the values mentioned in the brackets + # The randomness gives a variability between the values mentioned in the brackets match self.stage: case 1: asyncio.sleep(15+(random.uniform(-2,2))) From 23322d64fcbdb6d05df728b8da1515cbd320b288 Mon Sep 17 00:00:00 2001 From: Sapient44 Date: Fri, 26 Jul 2024 18:58:45 +0530 Subject: [PATCH 049/116] Fix error of wrong time declarations Start time and the game_time where defined in wrong areas, which would have let to several unintended effects --- src/game.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/game.py b/src/game.py index 930726b..1511aaf 100644 --- a/src/game.py +++ b/src/game.py @@ -79,7 +79,6 @@ def __init__(self, id: GameID) -> None: self.cumm_percent_time_per_stage : list[float]= [0.25, 0.6, 1] # Percentage of the time spent in the game when the next stage of the time begins (max value 1 = 100%) - self.start_time = datetime.now() async def add_player(self, ctx: SlashContext) -> None: """Add player to the game.""" @@ -87,13 +86,14 @@ async def add_player(self, ctx: SlashContext) -> None: async def loop(self) -> None: """Define the main loop of the game.""" + self.start_time = datetime.now() players = self.players.values() - game_time :float = (datetime.now() - self.start_time) / timedelta(minutes=1) - if ((game_time > self.cumm_percent_time_per_stage[self.stage - 1] * self.max_time) - and (game_time < self.max_time)): - self.stage += 1 while True: + game_time :float = (datetime.now() - self.start_time) / timedelta(minutes=1) + if ((game_time > self.cumm_percent_time_per_stage[self.stage - 1] * self.max_time) + and (game_time < self.max_time)): + self.stage += 1 try: await asyncio.gather(*[self.tick(player) for player in players], return_exceptions=True) except Exception: # noqa: BLE001 From 9f635b307342181aad14994de7c24d99af1d9eae Mon Sep 17 00:00:00 2001 From: Sapient44 Date: Fri, 26 Jul 2024 19:24:52 +0530 Subject: [PATCH 050/116] change variable names --- src/game.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/game.py b/src/game.py index 1511aaf..86ee2af 100644 --- a/src/game.py +++ b/src/game.py @@ -32,8 +32,8 @@ class PlayerState: def apply(self, consequence: dict) -> None: """Apply the consequnces to current state.""" - for k, v in consequence.items(): - self.__dict__[k] += v + for key, value in consequence.items(): + self.__dict__[key] += value class Player: From c45ad9d9b6c3a9c4cd2304fe7fccc995c201cc18 Mon Sep 17 00:00:00 2001 From: Sapient44 Date: Fri, 26 Jul 2024 21:17:29 +0530 Subject: [PATCH 051/116] check for negative values, before continuing with the game --- src/game.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/game.py b/src/game.py index 86ee2af..59e0f08 100644 --- a/src/game.py +++ b/src/game.py @@ -78,7 +78,7 @@ def __init__(self, id: GameID) -> None: self.cumm_percent_time_per_stage : list[float]= [0.25, 0.6, 1] # Percentage of the time spent in the game when the next stage of the time begins (max value 1 = 100%) - + self.values_to_check : list[str] = ['loyalty', 'money', 'security', 'world_opinion'] async def add_player(self, ctx: SlashContext) -> None: """Add player to the game.""" @@ -105,6 +105,11 @@ async def tick(self, player: Player) -> None: character = all_characters.get_random() # The sleep times are subject to change, based on how the actual gameplay feels # The randomness gives a variability between the values mentioned in the brackets + + if any(getattr(player.state, attr) < 0 for attr in self.values_to_check): + # Some value is negative hence need to send the losing message + raise NotImplementedError + match self.stage: case 1: asyncio.sleep(15+(random.uniform(-2,2))) From 5e99a3d5197468b6d7a5760c82f7804fe0a09568 Mon Sep 17 00:00:00 2001 From: hazyfossa Date: Fri, 26 Jul 2024 20:54:53 +0500 Subject: [PATCH 052/116] Disable false-positive warnings about random security --- pyproject.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index fed20f8..e345848 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,6 +41,8 @@ ignore = [ # Annotations. "ANN101", "ANN102", + # We never use random for cryptographic purposes + "S311", ] [tool.ruff.lint.per-file-ignores] From 65edf32569d6a2fe6207bfed077a7ad4546e8ef1 Mon Sep 17 00:00:00 2001 From: hazyfossa Date: Fri, 26 Jul 2024 20:55:21 +0500 Subject: [PATCH 053/116] fix matching stage to send delay --- src/game.py | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/game.py b/src/game.py index 86ee2af..af30878 100644 --- a/src/game.py +++ b/src/game.py @@ -76,10 +76,9 @@ def __init__(self, id: GameID) -> None: self.stage: Stage = 1 self.max_time: float = random.uniform(12.5, 16) - self.cumm_percent_time_per_stage : list[float]= [0.25, 0.6, 1] + self.cumm_percent_time_per_stage: list[float] = [0.25, 0.6, 1] # Percentage of the time spent in the game when the next stage of the time begins (max value 1 = 100%) - async def add_player(self, ctx: SlashContext) -> None: """Add player to the game.""" self.players[ctx.user.id] = Player(ctx, self) @@ -90,13 +89,15 @@ async def loop(self) -> None: players = self.players.values() while True: - game_time :float = (datetime.now() - self.start_time) / timedelta(minutes=1) - if ((game_time > self.cumm_percent_time_per_stage[self.stage - 1] * self.max_time) - and (game_time < self.max_time)): + game_time: float = (datetime.now() - self.start_time) / timedelta(minutes=1) + if (game_time > self.cumm_percent_time_per_stage[self.stage - 1] * self.max_time) and ( + game_time < self.max_time + ): self.stage += 1 + try: await asyncio.gather(*[self.tick(player) for player in players], return_exceptions=True) - except Exception: # noqa: BLE001 + except Exception: # noqa: BLE001 this is intentional for player in players: await player.ctx.send(embed=error_embed) @@ -107,15 +108,16 @@ async def tick(self, player: Player) -> None: # The randomness gives a variability between the values mentioned in the brackets match self.stage: case 1: - asyncio.sleep(15+(random.uniform(-2,2))) - await character.send(player) + sleep_time = 15 + (random.uniform(-2, 2)) case 2: - asyncio.sleep(13+(random.uniform(-2,1.5))) - await character.send(player) + sleep_time = 13 + (random.uniform(-2, 1.5)) + + case 3: + sleep_time = 10 + (random.uniform(-2, 1)) + + await asyncio.sleep(sleep_time) + await character.send(player) - case 1: - asyncio.sleep(10+(random.uniform(-2,1))) - await character.send(player) Stage = Literal[1, 2, 3] # Adjustable From 90bce52655e97ac31f8aab146c203577865cc39c Mon Sep 17 00:00:00 2001 From: hazyfossa Date: Fri, 26 Jul 2024 21:16:56 +0500 Subject: [PATCH 054/116] make weighted random support condition --- src/templating.py | 26 ++++++++------------------ src/weighted_random.py | 23 ++++++++++++++++++----- 2 files changed, 26 insertions(+), 23 deletions(-) diff --git a/src/templating.py b/src/templating.py index f8268bd..8b60446 100644 --- a/src/templating.py +++ b/src/templating.py @@ -1,4 +1,3 @@ -import random from collections.abc import Callable from typing import Any, Literal, get_args @@ -19,11 +18,18 @@ class Template: text: str = field() weight: int = 100 + condition: Condition | None = None def format(self, state: PlayerState) -> str: """Format the text.""" return self.text.format(**asdict(state)) + def is_available(self, state: PlayerState) -> bool: + if self.condition is not None: + return self.condition(state) + + return True + def to_embed(self, player: Player, actor: "Actor") -> Embed: """Get an embed for UI.""" # Now you can access actor here @@ -47,13 +53,6 @@ def not_none(var: Any | None) -> Any: # noqa: ANN401 temporary workaround FIXME @frozen class ChoiceTemplate(Template): choices: dict[str, Consequence] = field(default=None, converter=not_none) # Specify button color here somehow. - condition: Condition | None = None - - def is_available(self, player: Player) -> bool: - if self.condition is not None: - return self.condition(player.state) - - return True async def ui(self, player: Player, actor: "Actor") -> None: """Send UI and apply consequences.""" @@ -92,15 +91,6 @@ def convert_stage(stage: Stage | list[Stage] | Literal["all"]) -> list[Stage]: templates: list[Template] -class StageData: - def __init__(self, templates: list[Template]) -> None: - self.templates = templates - self.weights = [template.weight for template in self.templates] - - def get_random(self) -> Template: - return random.choices(self.templates, weights=self.weights, k=1)[0] # noqa: S311 Not for cryptographic purposes - - @frozen class Actor: @staticmethod @@ -125,5 +115,5 @@ def cast_stages(stage_groups: list[StageGroup]) -> dict[Stage, WeightedList[Temp async def send(self, target: Player) -> None: stage = self.stages[target.game.stage] - template = stage.get_random() + template = stage.get_random(target.state) await template.ui(target, self) diff --git a/src/weighted_random.py b/src/weighted_random.py index 32254f9..2a9f65d 100644 --- a/src/weighted_random.py +++ b/src/weighted_random.py @@ -1,13 +1,18 @@ import random -from typing import Generic, Protocol, TypeVar +from typing import TYPE_CHECKING, Generic, Protocol, TypeVar +if TYPE_CHECKING: + from src.player import PlayerState -class SupportsWeight(Protocol): + +class SupportedRandomValue(Protocol): @property def weight(self) -> int: ... + def is_available(self, state: "PlayerState") -> bool: ... + -T = TypeVar("T", bound=SupportsWeight) +T = TypeVar("T", bound=SupportedRandomValue) class WeightedList(Generic[T]): @@ -23,5 +28,13 @@ def append(self, value: T) -> None: self.values.append(value) self.weights.append(value.weight) - def get_random(self) -> T: - return random.choices(self.values, weights=self.weights, k=1)[0] # noqa: S311 Not for cryptographic purposes + def get_random(self, state: "PlayerState") -> T: + result = None + + while result is None: + possible_result = random.choices(self.values, weights=self.weights, k=1)[0] + + if possible_result.is_available(state): + result = possible_result + + return result From 46ad3c346b39da4c72c8ae6345f44611b27a9ada Mon Sep 17 00:00:00 2001 From: Maheshkumar P <67100964+Maheshkumar-novice@users.noreply.github.com> Date: Fri, 26 Jul 2024 22:04:15 +0530 Subject: [PATCH 055/116] Fixes (#11) * disable false-positive linting for custom character files * change imports from relative to parent-package * hotfix: imports * Add some variablility in time of sending message * Linting * Disable false-positive warnings about random security * fix matching stage to send delay * make weighted random support condition * fix importing TYPE_CHECKING --------- Co-authored-by: hazyfossa Co-authored-by: Sapient44 --- pyproject.toml | 2 + src/characters/__init__.py | 7 ++- src/game.py | 106 +++++++++++++++---------------------- src/player.py | 60 +++++++++++++++++++++ src/templating.py | 38 +++++-------- src/weighted_random.py | 24 +++++++-- 6 files changed, 144 insertions(+), 93 deletions(-) create mode 100644 src/player.py diff --git a/pyproject.toml b/pyproject.toml index fed20f8..e345848 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,6 +41,8 @@ ignore = [ # Annotations. "ANN101", "ANN102", + # We never use random for cryptographic purposes + "S311", ] [tool.ruff.lint.per-file-ignores] diff --git a/src/characters/__init__.py b/src/characters/__init__.py index ccf80f9..d7522a6 100644 --- a/src/characters/__init__.py +++ b/src/characters/__init__.py @@ -1,13 +1,16 @@ from importlib import import_module from logging import getLogger from pathlib import Path +from typing import TYPE_CHECKING -from src.templating import Actor from src.weighted_random import WeightedList +if TYPE_CHECKING: + from src.templating import Actor + log = getLogger("character-init") -all_characters: WeightedList[Actor] = WeightedList() +all_characters: WeightedList["Actor"] = WeightedList() for file in Path(__file__).parent.glob("*.py"): module = file.name.split(".")[0] diff --git a/src/game.py b/src/game.py index a949d02..e9f8568 100644 --- a/src/game.py +++ b/src/game.py @@ -1,93 +1,75 @@ import asyncio -from re import template -from typing import Annotated, Literal - -from attrs import define -from interactions import Modal, ModalContext, ShortText, SlashContext +import random +from datetime import datetime, timedelta +from typing import Annotated, TYPE_CHECKING +from interactions import SlashContext from src.characters import all_characters +from src.player import Player from src.utils import error_embed -GameID = str - - -@define -class PlayerState: - """The current state of the player.""" - - nation_name: str - - # Money with the government - money: float = 100 - - # How loyal people feel to the current government that you have created - loyalty: float = 50 - - # How vulnerable is the country from external threats - security: float = 50 - - # Lower means entity sabotage and vice versa (might add this as a later future) - world_opinion: float = 50 - - def apply(self, consequence: dict) -> None: - """Apply the consequnces to current state.""" - for k, v in consequence.items(): - self.__dict__[k] += v - - -class Player: - def __init__(self, ctx: SlashContext, game: "Game") -> None: - self.ctx = ctx - self.state: PlayerState = None # type: ignore TODO: properly type that state isn't none after register - self.game = game +if TYPE_CHECKING: + from src.templating import Stage - async def register(self) -> None: - """Ask the player for information.""" - registration_modal = Modal( - ShortText( - label="Provide your nation name", - custom_id="nation_name", - min_length=3, - max_length=50, - required=True, - ), - title="Player Information", - ) - await self.ctx.send_modal(modal=registration_modal) - - modal_ctx: ModalContext = await self.ctx.bot.wait_for_modal(registration_modal) - - # await modal_ctx.send(f"<@{ctx.user.id}> You are playing as a leader of {nation_name}", ephemeral=True) - - nation_name = modal_ctx.responses["nation_name"] - - self.state = PlayerState(nation_name) +GameID = str class Game: + """Initialize a Game and it's behaviours.""" + def __init__(self, id: GameID) -> None: self.id = id self.players: dict[Annotated[int, "discord id"], Player] = {} self.stage: Stage = 1 + self.max_time: float = random.uniform(12.5, 16) + + self.cumm_percent_time_per_stage: list[float] = [0.25, 0.6, 1] + # Percentage of the time spent in the game when the next stage of the time begins (max value 1 = 100%) + self.values_to_check: list[str] = ["loyalty", "money", "security", "world_opinion"] async def add_player(self, ctx: SlashContext) -> None: - self.players[ctx.user.id] = Player(ctx, self) + player = Player(ctx, self) + await player.register() + self.players[ctx.user.id] = player async def loop(self) -> None: + """Define the main loop of the game.""" + self.start_time = datetime.now() players = self.players.values() while True: + game_time: float = (datetime.now() - self.start_time) / timedelta(minutes=1) + if (game_time > self.cumm_percent_time_per_stage[self.stage - 1] * self.max_time) and ( + game_time < self.max_time + ): + self.stage += 1 + try: await asyncio.gather(*[self.tick(player) for player in players], return_exceptions=True) - except Exception: # noqa: BLE001 + except Exception: # noqa: BLE001 this is intentional for player in players: await player.ctx.send(embed=error_embed) async def tick(self, player: Player) -> None: + """Define the activities done in every game tick.""" character = all_characters.get_random() + # The sleep times are subject to change, based on how the actual gameplay feels + # The randomness gives a variability between the values mentioned in the brackets - await character.send(player) + if any(getattr(player.state, attr) < 0 for attr in self.values_to_check): + # Some value is negative hence need to send the losing message + raise NotImplementedError + match self.stage: + case 1: + sleep_time = 15 + (random.uniform(-2, 2)) -Stage = Literal[1, 2, 3] # Adjustable + case 2: + sleep_time = 13 + (random.uniform(-2, 1.5)) + + case 3: + sleep_time = 10 + (random.uniform(-2, 1)) + + await asyncio.sleep(sleep_time) + await character.send(player) diff --git a/src/player.py b/src/player.py new file mode 100644 index 0000000..4bd94d9 --- /dev/null +++ b/src/player.py @@ -0,0 +1,60 @@ +from typing import TYPE_CHECKING + +from attrs import define +from interactions import Modal, ModalContext, ShortText, SlashContext + +if TYPE_CHECKING: + from src.game import Game + + +@define +class PlayerState: + """The current state of the player.""" + + nation_name: str + + # Money with the government + money: float = 100 + + # How loyal people feel to the current government that you have created + loyalty: float = 50 + + # How vulnerable is the country from external threats + security: float = 50 + + # Lower means entity sabotage and vice versa (might add this as a later future) + world_opinion: float = 50 + + def apply(self, consequence: dict) -> None: + """Apply the consequnces to current state.""" + for k, v in consequence.items(): + self.__dict__[k] += v + + +class Player: + def __init__(self, ctx: SlashContext, game: "Game") -> None: + self.ctx = ctx + self.state: PlayerState = None # type: ignore TODO: properly type that state isn't none after register + self.game = game + + async def register(self) -> None: + """Ask the player for information.""" + registration_modal = Modal( + ShortText( + label="Provide your nation name", + custom_id="nation_name", + min_length=3, + max_length=50, + required=True, + ), + title="Player Information", + ) + await self.ctx.send_modal(modal=registration_modal) + + modal_ctx: ModalContext = await self.ctx.bot.wait_for_modal(registration_modal) + + nation_name = modal_ctx.responses["nation_name"] + + await modal_ctx.send(f"<@{self.ctx.user.id}> You are playing as a leader of {nation_name}", ephemeral=True) + + self.state = PlayerState(nation_name) diff --git a/src/templating.py b/src/templating.py index 6e24357..8b60446 100644 --- a/src/templating.py +++ b/src/templating.py @@ -1,15 +1,15 @@ -import random from collections.abc import Callable from typing import Any, Literal, get_args from attrs import asdict, field, frozen from interactions import ActionRow, Button, ButtonStyle, Embed -from src.game import Player, PlayerState, Stage +from src.player import Player, PlayerState from src.weighted_random import WeightedList Consequence = dict[Any, Any] Condition = Callable[[PlayerState], bool] | None +Stage = Literal[1, 2, 3] # Adjustable @frozen @@ -18,10 +18,17 @@ class Template: text: str = field() weight: int = 100 + condition: Condition | None = None def format(self, state: PlayerState) -> str: """Format the text.""" - return self.text.format(asdict(state)) + return self.text.format(**asdict(state)) + + def is_available(self, state: PlayerState) -> bool: + if self.condition is not None: + return self.condition(state) + + return True def to_embed(self, player: Player, actor: "Actor") -> Embed: """Get an embed for UI.""" @@ -33,7 +40,7 @@ def to_embed(self, player: Player, actor: "Actor") -> Embed: ) async def ui(self, player: Player, actor: "Actor") -> None: - await player.ctx.send(embed=self.to_embed(player, actor)) + await player.ctx.send(embed=self.to_embed(player, actor), ephemeral=True) def not_none(var: Any | None) -> Any: # noqa: ANN401 temporary workaround FIXME @@ -46,13 +53,6 @@ def not_none(var: Any | None) -> Any: # noqa: ANN401 temporary workaround FIXME @frozen class ChoiceTemplate(Template): choices: dict[str, Consequence] = field(default=None, converter=not_none) # Specify button color here somehow. - condition: Condition | None = None - - def is_available(self, player: Player) -> bool: - if self.condition is not None: - return self.condition(player.state) - - return True async def ui(self, player: Player, actor: "Actor") -> None: """Send UI and apply consequences.""" @@ -60,7 +60,7 @@ async def ui(self, player: Player, actor: "Actor") -> None: for id, choice in enumerate(self.choices.items()): button = Button( - label=f"{next(iter(choice.keys()))}", # Something isn't right here + label=f"{next(iter(choice))}", style=ButtonStyle.BLURPLE, custom_id=f"Choice {id}", ) @@ -68,7 +68,7 @@ async def ui(self, player: Player, actor: "Actor") -> None: embed = self.to_embed(player, actor) - await player.ctx.send(embed=embed, action_row=ActionRow(*buttons)) + await player.ctx.send(embed=embed, components=ActionRow(*buttons), ephemeral=True) total_stages = get_args(Stage) @@ -91,15 +91,6 @@ def convert_stage(stage: Stage | list[Stage] | Literal["all"]) -> list[Stage]: templates: list[Template] -class StageData: - def __init__(self, templates: list[Template]) -> None: - self.templates = templates - self.weights = [template.weight for template in self.templates] - - def get_random(self) -> Template: - return random.choices(self.templates, weights=self.weights, k=1)[0] # noqa: S311 Not for cryptographic purposes - - @frozen class Actor: @staticmethod @@ -124,6 +115,5 @@ def cast_stages(stage_groups: list[StageGroup]) -> dict[Stage, WeightedList[Temp async def send(self, target: Player) -> None: stage = self.stages[target.game.stage] - template = stage.get_random() - + template = stage.get_random(target.state) await template.ui(target, self) diff --git a/src/weighted_random.py b/src/weighted_random.py index d0f6b58..2a9f65d 100644 --- a/src/weighted_random.py +++ b/src/weighted_random.py @@ -1,13 +1,18 @@ import random -from typing import Generic, Protocol, TypeVar +from typing import TYPE_CHECKING, Generic, Protocol, TypeVar +if TYPE_CHECKING: + from src.player import PlayerState -class SupportsWeight(Protocol): + +class SupportedRandomValue(Protocol): @property def weight(self) -> int: ... + def is_available(self, state: "PlayerState") -> bool: ... + -T = TypeVar("T", bound=SupportsWeight) +T = TypeVar("T", bound=SupportedRandomValue) class WeightedList(Generic[T]): @@ -17,10 +22,19 @@ def __init__(self, values: list[T] | None = None) -> None: self.weights = [value.weight for value in values] else: self.values = [] + self.weights = [] def append(self, value: T) -> None: self.values.append(value) self.weights.append(value.weight) - def get_random(self) -> T: - return random.choices(self.values, weights=self.weights, k=1)[0] # noqa: S311 Not for cryptographic purposes + def get_random(self, state: "PlayerState") -> T: + result = None + + while result is None: + possible_result = random.choices(self.values, weights=self.weights, k=1)[0] + + if possible_result.is_available(state): + result = possible_result + + return result From 7999ede6bc33d2a90bcb6dc5b79ea2cf4e155898 Mon Sep 17 00:00:00 2001 From: hazyfossa Date: Fri, 26 Jul 2024 21:45:29 +0500 Subject: [PATCH 056/116] fix typing --- src/game.py | 9 ++++++--- src/templating.py | 25 ++++++++++++++++--------- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/src/game.py b/src/game.py index e9f8568..0abc105 100644 --- a/src/game.py +++ b/src/game.py @@ -1,12 +1,13 @@ import asyncio import random from datetime import datetime, timedelta -from typing import Annotated, TYPE_CHECKING +from typing import TYPE_CHECKING, Annotated from interactions import SlashContext from src.characters import all_characters from src.player import Player +from src.templating import total_stages from src.utils import error_embed if TYPE_CHECKING: @@ -43,7 +44,9 @@ async def loop(self) -> None: if (game_time > self.cumm_percent_time_per_stage[self.stage - 1] * self.max_time) and ( game_time < self.max_time ): - self.stage += 1 + self.stage = total_stages[ + total_stages.index(self.stage) + 1 + ] # This isn't the best, but it won't go out of bounds and doesn't break typing try: await asyncio.gather(*[self.tick(player) for player in players], return_exceptions=True) @@ -53,7 +56,7 @@ async def loop(self) -> None: async def tick(self, player: Player) -> None: """Define the activities done in every game tick.""" - character = all_characters.get_random() + character = all_characters.get_random(player.state) # The sleep times are subject to change, based on how the actual gameplay feels # The randomness gives a variability between the values mentioned in the brackets diff --git a/src/templating.py b/src/templating.py index 8b60446..7ecd10a 100644 --- a/src/templating.py +++ b/src/templating.py @@ -1,14 +1,16 @@ from collections.abc import Callable -from typing import Any, Literal, get_args +from typing import TYPE_CHECKING, Any, Literal, get_args from attrs import asdict, field, frozen from interactions import ActionRow, Button, ButtonStyle, Embed -from src.player import Player, PlayerState from src.weighted_random import WeightedList +if TYPE_CHECKING: + from src.player import Player, PlayerState + Consequence = dict[Any, Any] -Condition = Callable[[PlayerState], bool] | None +Condition = Callable[["PlayerState"], bool] | None Stage = Literal[1, 2, 3] # Adjustable @@ -20,17 +22,17 @@ class Template: weight: int = 100 condition: Condition | None = None - def format(self, state: PlayerState) -> str: + def format(self, state: "PlayerState") -> str: """Format the text.""" return self.text.format(**asdict(state)) - def is_available(self, state: PlayerState) -> bool: + def is_available(self, state: "PlayerState") -> bool: if self.condition is not None: return self.condition(state) return True - def to_embed(self, player: Player, actor: "Actor") -> Embed: + def to_embed(self, player: "Player", actor: "Actor") -> Embed: """Get an embed for UI.""" # Now you can access actor here return Embed( @@ -39,7 +41,7 @@ def to_embed(self, player: Player, actor: "Actor") -> Embed: color=(0, 0, 255), ) - async def ui(self, player: Player, actor: "Actor") -> None: + async def ui(self, player: "Player", actor: "Actor") -> None: await player.ctx.send(embed=self.to_embed(player, actor), ephemeral=True) @@ -54,7 +56,7 @@ def not_none(var: Any | None) -> Any: # noqa: ANN401 temporary workaround FIXME class ChoiceTemplate(Template): choices: dict[str, Consequence] = field(default=None, converter=not_none) # Specify button color here somehow. - async def ui(self, player: Player, actor: "Actor") -> None: + async def ui(self, player: "Player", actor: "Actor") -> None: """Send UI and apply consequences.""" buttons: list[Button] = [] @@ -113,7 +115,12 @@ def cast_stages(stage_groups: list[StageGroup]) -> dict[Stage, WeightedList[Temp stages: dict[Stage, WeightedList[Template]] = field(converter=cast_stages) weight: int = 100 - async def send(self, target: Player) -> None: + def is_available(self, state: "PlayerState") -> bool: + # Add stuff here if you want to add actors which appear on condition. + _ = state + return True + + async def send(self, target: "Player") -> None: stage = self.stages[target.game.stage] template = stage.get_random(target.state) await template.ui(target, self) From 7e78e958bae9b83dade0ccffc8af6ab0fdfcf405 Mon Sep 17 00:00:00 2001 From: hazyfossa Date: Fri, 26 Jul 2024 21:46:12 +0500 Subject: [PATCH 057/116] rename utils.py to const.py --- src/{utils.py => const.py} | 0 src/game.py | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename src/{utils.py => const.py} (100%) diff --git a/src/utils.py b/src/const.py similarity index 100% rename from src/utils.py rename to src/const.py diff --git a/src/game.py b/src/game.py index 0abc105..4ec0e0e 100644 --- a/src/game.py +++ b/src/game.py @@ -8,7 +8,7 @@ from src.characters import all_characters from src.player import Player from src.templating import total_stages -from src.utils import error_embed +from src.const import error_embed if TYPE_CHECKING: from src.templating import Stage From 2084726a01f63a597739f4af2563823a37165a03 Mon Sep 17 00:00:00 2001 From: hazyfossa Date: Fri, 26 Jul 2024 21:46:42 +0500 Subject: [PATCH 058/116] linting --- src/game.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/game.py b/src/game.py index 4ec0e0e..eb35d4a 100644 --- a/src/game.py +++ b/src/game.py @@ -6,9 +6,9 @@ from interactions import SlashContext from src.characters import all_characters +from src.const import error_embed from src.player import Player from src.templating import total_stages -from src.const import error_embed if TYPE_CHECKING: from src.templating import Stage From 776707beaec71dfbf87fa0b6e1322dc644d59577 Mon Sep 17 00:00:00 2001 From: Sapient44 <78420201+Sapient44@users.noreply.github.com> Date: Sat, 27 Jul 2024 12:23:03 +0530 Subject: [PATCH 059/116] The player can now leave the game with subcommands (#12) * Player can leave the game * Remove player from game * simplify query_game * Broadcasting player_leave * changing capital letter to small in command name * Make player leave an emphemeral --------- Co-authored-by: hazyfossa --- src/game.py | 24 ++++++++++++++++++++++-- src/game_interaction.py | 41 +++++++++++++++++++++++++++++++++-------- 2 files changed, 55 insertions(+), 10 deletions(-) diff --git a/src/game.py b/src/game.py index eb35d4a..adacfaf 100644 --- a/src/game.py +++ b/src/game.py @@ -1,10 +1,12 @@ import asyncio + + +from attrs import define import random from datetime import datetime, timedelta from typing import TYPE_CHECKING, Annotated from interactions import SlashContext - from src.characters import all_characters from src.const import error_embed from src.player import Player @@ -13,6 +15,14 @@ if TYPE_CHECKING: from src.templating import Stage +class Player: + """Define and register the data related to the player.""" + + def __init__(self, ctx: SlashContext, game: "Game") -> None: + self.ctx = ctx + self.state: PlayerState = None # type: ignore TODO: properly type that state isn't none after register + self.game = game + GameID = str @@ -34,9 +44,20 @@ async def add_player(self, ctx: SlashContext) -> None: await player.register() self.players[ctx.user.id] = player + + async def remove_player(self, ctx: SlashContext) -> None: + """Remove player from the game.""" + player_to_delete = ctx.user.id + try: + del self.players[player_to_delete] + except KeyError: + raise NotImplementedError + # Need to pass this error to the user, that you are in no game + async def loop(self) -> None: """Define the main loop of the game.""" self.start_time = datetime.now() + players = self.players.values() while True: @@ -59,7 +80,6 @@ async def tick(self, player: Player) -> None: character = all_characters.get_random(player.state) # The sleep times are subject to change, based on how the actual gameplay feels # The randomness gives a variability between the values mentioned in the brackets - if any(getattr(player.state, attr) < 0 for attr in self.values_to_check): # Some value is negative hence need to send the losing message raise NotImplementedError diff --git a/src/game_interaction.py b/src/game_interaction.py index a7fe685..7903b33 100644 --- a/src/game_interaction.py +++ b/src/game_interaction.py @@ -19,13 +19,14 @@ class GameFactory: def __init__(self) -> None: - self.store: dict[GameID, Game] = {} + self.games: dict[GameID, Game] = {} + self.players: dict[int, Game] = {} def generate_game_id(self) -> GameID: while True: game_id = "".join(random.choice(ascii_uppercase + digits) for i in range(12)) # noqa: S311 This isn't for crypto purposes - if game_id in self.store: + if game_id in self.games: continue return game_id @@ -33,12 +34,18 @@ def generate_game_id(self) -> GameID: def create_game(self) -> Game: game_id = self.generate_game_id() game = Game(game_id) - self.store[game_id] = game + self.games[game_id] = game return game - def query_game(self, game_id: GameID) -> Game | None: - return self.store.get(game_id, None) + def query_game(self, game_id: GameID | None = None, player_id: int | None = None) -> Game | None: + if game_id: + return self.games.get(game_id, None) + if player_id: + return self.players.get(player_id, None) + + err = "You need to specify either game_id or player_id" + raise AttributeError(err) class GameInteraction(Extension): @@ -47,7 +54,11 @@ class GameInteraction(Extension): def __init__(self, _: "Client") -> None: self.game_factory = GameFactory() - @slash_command(name="defcord_create", description="Create a new DEFCORD game.") + @slash_command(name="defcord", description="Interact with defcord.") + async def defcord(self, ctx: SlashContext) -> None: + """Make the subcommand work, since it requires a function to latch off of.""" + + @defcord.subcommand(sub_cmd_name="create", sub_cmd_description="Create a game of Defcord") async def create(self, ctx: SlashContext) -> None: """Create a game of DEFCORD.""" game = self.game_factory.create_game() # Add the first player here @@ -62,13 +73,27 @@ async def create(self, ctx: SlashContext) -> None: await ctx.send(embed=embed) - @slash_command(name="defcord_join", description="Join a game of DEFCORD.") + @defcord.subcommand(sub_cmd_name="join", sub_cmd_description="Join a game of Defcord") @slash_option("invite", "The invite code for the game", required=True, opt_type=OptionType.STRING) async def join(self, ctx: SlashContext, invite: str) -> None: """Join a game of DEFCORD.""" - game = self.game_factory.query_game(invite) + game = self.game_factory.query_game(game_id=invite) if game is None: raise NotImplementedError await game.add_player(ctx) + + @defcord.subcommand(sub_cmd_name="leave", sub_cmd_description="Leave a game of Defcord") + async def leave(self, ctx: SlashContext) -> None: + """Leave the current game of defcord""" + game = self.game_factory.query_game(player_id=ctx.user.id) + + if game is None: + raise NotImplementedError + else: + embed = Embed(title= "A Player left the game", + description=f"<@{ctx.user.id}> has left the game", color=(255, 0, 0)) + await game.remove_player(ctx) + for player in game.players.values(): + await player.ctx.send(embed=embed, ephemeral=True) From ec1925399b9f9003807327ed2c48361d5227dc05 Mon Sep 17 00:00:00 2001 From: Sapient44 Date: Sat, 27 Jul 2024 12:51:34 +0530 Subject: [PATCH 060/116] Decrease the time of the message sending, to make it more choatic --- src/game.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/game.py b/src/game.py index adacfaf..39331ad 100644 --- a/src/game.py +++ b/src/game.py @@ -86,13 +86,13 @@ async def tick(self, player: Player) -> None: match self.stage: case 1: - sleep_time = 15 + (random.uniform(-2, 2)) + sleep_time = 10 + (random.uniform(-2, 2)) case 2: - sleep_time = 13 + (random.uniform(-2, 1.5)) + sleep_time = 8 + (random.uniform(-2, 1.5)) case 3: - sleep_time = 10 + (random.uniform(-2, 1)) + sleep_time = 6 + (random.uniform(-1, 0.75)) await asyncio.sleep(sleep_time) await character.send(player) From f5e6e2aa9956825a55fe34fd0c274809e9d95d27 Mon Sep 17 00:00:00 2001 From: Sapient44 Date: Sat, 27 Jul 2024 14:25:01 +0530 Subject: [PATCH 061/116] Define some coloured accents --- src/const.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/const.py b/src/const.py index e850259..c952f1e 100644 --- a/src/const.py +++ b/src/const.py @@ -1,3 +1,5 @@ from interactions import Embed -error_embed = Embed() +error_embed = Embed(color=(255, 58, 51)) +message_embed = Embed(color=(124, 199, 242)) +system_message_embed = Embed(color=(88, 245, 149)) From 08eedb60d7fad9251f276b998fc7a4c6fb7af0c3 Mon Sep 17 00:00:00 2001 From: Sapient44 Date: Sat, 27 Jul 2024 14:27:04 +0530 Subject: [PATCH 062/116] On death remove player --- src/game.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/game.py b/src/game.py index 39331ad..dfc5e2e 100644 --- a/src/game.py +++ b/src/game.py @@ -1,14 +1,12 @@ import asyncio - -from attrs import define import random from datetime import datetime, timedelta from typing import TYPE_CHECKING, Annotated from interactions import SlashContext from src.characters import all_characters -from src.const import error_embed +from src.const import error_embed, system_message_embed from src.player import Player from src.templating import total_stages @@ -54,6 +52,15 @@ async def remove_player(self, ctx: SlashContext) -> None: raise NotImplementedError # Need to pass this error to the user, that you are in no game + async def death_player(self, dead_player: Player) -> None: + embed = system_message_embed(title = "We have lost a national leader in the turmoil", + description = f"{dead_player.nation_name} has lost their leadership which was done by \n <@{dead_player.id}>") + + for player in self.players.values(): + await player.ctx.send(embed) + + self.remove_player(dead_player.ctx) + async def loop(self) -> None: """Define the main loop of the game.""" self.start_time = datetime.now() @@ -82,7 +89,7 @@ async def tick(self, player: Player) -> None: # The randomness gives a variability between the values mentioned in the brackets if any(getattr(player.state, attr) < 0 for attr in self.values_to_check): # Some value is negative hence need to send the losing message - raise NotImplementedError + self.death_player(player) match self.stage: case 1: From e22db7e2718750d8ab419f13c48c1ffde3fc66d4 Mon Sep 17 00:00:00 2001 From: Sapient44 Date: Sat, 27 Jul 2024 14:38:07 +0530 Subject: [PATCH 063/116] Add const colours --- src/game.py | 6 ++++-- src/game_interaction.py | 12 +++++------- src/templating.py | 4 ++-- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/game.py b/src/game.py index dfc5e2e..e6f8d0a 100644 --- a/src/game.py +++ b/src/game.py @@ -78,9 +78,11 @@ async def loop(self) -> None: try: await asyncio.gather(*[self.tick(player) for player in players], return_exceptions=True) - except Exception: # noqa: BLE001 this is intentional + except Exception as e: # noqa: BLE001 this is intentional + embed = error_embed(title = "Some error has occured", + description = f"{e} has occured, contact the devs if you see this message") for player in players: - await player.ctx.send(embed=error_embed) + await player.ctx.send(embed=embed) async def tick(self, player: Player) -> None: """Define the activities done in every game tick.""" diff --git a/src/game_interaction.py b/src/game_interaction.py index 7903b33..14b02d5 100644 --- a/src/game_interaction.py +++ b/src/game_interaction.py @@ -3,7 +3,6 @@ from typing import TYPE_CHECKING from interactions import ( - Embed, Extension, OptionType, SlashContext, @@ -12,7 +11,7 @@ ) from src.game import Game, GameID - +from src.const import system_message_embed if TYPE_CHECKING: from interactions import Client @@ -65,10 +64,9 @@ async def create(self, ctx: SlashContext) -> None: await game.add_player(ctx) - embed = Embed( + embed = system_message_embed( title="New game started!", - description=f"Your invite: {game.id}", - color=(255, 0, 0), + description=f"Your invite: {game.id}" ) await ctx.send(embed=embed) @@ -92,8 +90,8 @@ async def leave(self, ctx: SlashContext) -> None: if game is None: raise NotImplementedError else: - embed = Embed(title= "A Player left the game", - description=f"<@{ctx.user.id}> has left the game", color=(255, 0, 0)) + embed = system_message_embed(title= "A Player left the game", + description=f"<@{ctx.user.id}> has left the game") await game.remove_player(ctx) for player in game.players.values(): await player.ctx.send(embed=embed, ephemeral=True) diff --git a/src/templating.py b/src/templating.py index 7ecd10a..bb9570d 100644 --- a/src/templating.py +++ b/src/templating.py @@ -5,6 +5,7 @@ from interactions import ActionRow, Button, ButtonStyle, Embed from src.weighted_random import WeightedList +from src.const import message_embed if TYPE_CHECKING: from src.player import Player, PlayerState @@ -35,10 +36,9 @@ def is_available(self, state: "PlayerState") -> bool: def to_embed(self, player: "Player", actor: "Actor") -> Embed: """Get an embed for UI.""" # Now you can access actor here - return Embed( + return message_embed( title=f"{actor.name} of {player.state.nation_name}", description=self.format(player.state), - color=(0, 0, 255), ) async def ui(self, player: "Player", actor: "Actor") -> None: From 71f5113e5703d628e8de52f49f3957fc5d51a321 Mon Sep 17 00:00:00 2001 From: Maheshkumar Date: Sat, 27 Jul 2024 13:35:23 +0530 Subject: [PATCH 064/116] Add start logic and fix few bugs --- main.py | 13 ++-- src/game.py | 47 +++++++++----- src/game_interaction.py | 139 +++++++++++++++++++++++++++++++++++----- 3 files changed, 160 insertions(+), 39 deletions(-) diff --git a/main.py b/main.py index 32eb288..57d4d14 100644 --- a/main.py +++ b/main.py @@ -8,14 +8,16 @@ listen, ) -log = logging.getLogger("defcon-internal") -bot = Client(intents=Intents.DEFAULT, logging_level=logging.WARNING) +logger = logging.getLogger("defcon-internal") +logging.basicConfig(level=logging.INFO) + +bot = Client(intents=Intents.DEFAULT, logging_level=logging.INFO) @listen() async def on_ready() -> None: """Notify that the bot is started.""" - log.info("Bot started.") + logger.info("Bot started.") def get_token() -> str: @@ -31,13 +33,12 @@ def get_token() -> str: token = getenv("DEFCON_BOT_TOKEN") if token is None: - log.error("Token not found.\nPlease specify discord bot token via environment variable 'DEFCON_BOT_TOKEN'.") + logger.error("Token not found.\nPlease specify discord bot token via environment variable 'DEFCON_BOT_TOKEN'.") if no_dotenv: - log.info("To read token from .env, install 'python-dotenv'") + logger.info("To read token from .env, install 'python-dotenv'") exit() - return token diff --git a/src/game.py b/src/game.py index adacfaf..537bd19 100644 --- a/src/game.py +++ b/src/game.py @@ -1,12 +1,11 @@ import asyncio - - -from attrs import define +import logging import random from datetime import datetime, timedelta from typing import TYPE_CHECKING, Annotated from interactions import SlashContext + from src.characters import all_characters from src.const import error_embed from src.player import Player @@ -15,45 +14,49 @@ if TYPE_CHECKING: from src.templating import Stage -class Player: - """Define and register the data related to the player.""" - - def __init__(self, ctx: SlashContext, game: "Game") -> None: - self.ctx = ctx - self.state: PlayerState = None # type: ignore TODO: properly type that state isn't none after register - self.game = game GameID = str +logger = logging.getLogger(__name__) class Game: """Initialize a Game and it's behaviours.""" - def __init__(self, id: GameID) -> None: + def __init__(self, id: GameID, required_no_of_players: int) -> None: self.id = id + self.required_no_of_players: int = required_no_of_players self.players: dict[Annotated[int, "discord id"], Player] = {} self.stage: Stage = 1 self.max_time: float = random.uniform(12.5, 16) + self.started: bool = False + self.creator: int | None = None + self.stop_flag: bool = False self.cumm_percent_time_per_stage: list[float] = [0.25, 0.6, 1] # Percentage of the time spent in the game when the next stage of the time begins (max value 1 = 100%) self.values_to_check: list[str] = ["loyalty", "money", "security", "world_opinion"] - async def add_player(self, ctx: SlashContext) -> None: + async def add_player(self, ctx: SlashContext, cmd: str = "create") -> None: + """Add a player to the game.""" + if cmd == "create": + self.creator = ctx.user.id player = Player(ctx, self) await player.register() self.players[ctx.user.id] = player - async def remove_player(self, ctx: SlashContext) -> None: """Remove player from the game.""" player_to_delete = ctx.user.id try: del self.players[player_to_delete] except KeyError: - raise NotImplementedError + raise NotImplementedError from KeyError # Need to pass this error to the user, that you are in no game + def stop(self) -> None: + """Set the stop flag.""" + self.stop_flag = True + async def loop(self) -> None: """Define the main loop of the game.""" self.start_time = datetime.now() @@ -61,6 +64,9 @@ async def loop(self) -> None: players = self.players.values() while True: + if self.stop_flag: + break + game_time: float = (datetime.now() - self.start_time) / timedelta(minutes=1) if (game_time > self.cumm_percent_time_per_stage[self.stage - 1] * self.max_time) and ( game_time < self.max_time @@ -70,13 +76,22 @@ async def loop(self) -> None: ] # This isn't the best, but it won't go out of bounds and doesn't break typing try: - await asyncio.gather(*[self.tick(player) for player in players], return_exceptions=True) - except Exception: # noqa: BLE001 this is intentional + response = await asyncio.gather(*[self.tick(player) for player in players], return_exceptions=True) + + for res in response: + if isinstance(res, Exception): + logger.error(res) + except Exception: + logger.exception("Error occurred in game loop") + for player in players: await player.ctx.send(embed=error_embed) async def tick(self, player: Player) -> None: """Define the activities done in every game tick.""" + if self.stop_flag: + return + character = all_characters.get_random(player.state) # The sleep times are subject to change, based on how the actual gameplay feels # The randomness gives a variability between the values mentioned in the brackets diff --git a/src/game_interaction.py b/src/game_interaction.py index 7903b33..c400391 100644 --- a/src/game_interaction.py +++ b/src/game_interaction.py @@ -18,27 +18,44 @@ class GameFactory: + """Game creator factory class.""" + def __init__(self) -> None: self.games: dict[GameID, Game] = {} self.players: dict[int, Game] = {} def generate_game_id(self) -> GameID: + """Generate a random id for the game.""" while True: - game_id = "".join(random.choice(ascii_uppercase + digits) for i in range(12)) # noqa: S311 This isn't for crypto purposes + game_id = "".join(random.choice(ascii_uppercase + digits) for i in range(6)) if game_id in self.games: continue return game_id - def create_game(self) -> Game: + def create_game(self, required_no_of_players: int) -> Game: + """Create a game with the required details.""" game_id = self.generate_game_id() - game = Game(game_id) + game = Game(game_id, required_no_of_players) self.games[game_id] = game return game + def add_player(self, player_id: int, game: Game) -> None: + """Map a player with a game.""" + self.players[player_id] = game + + def remove_player(self, player_id: int) -> None: + """Remove a player game mapping.""" + del self.players[player_id] + + def remove_game(self, game_id: int) -> None: + """Remove a game from game id mapping.""" + del self.games[game_id] + def query_game(self, game_id: GameID | None = None, player_id: int | None = None) -> Game | None: + """Return the game based on the query details.""" if game_id: return self.games.get(game_id, None) if player_id: @@ -54,16 +71,46 @@ class GameInteraction(Extension): def __init__(self, _: "Client") -> None: self.game_factory = GameFactory() + async def send_player_join_notification(self, game: Game, ctx: SlashContext) -> None: + """Send a notification to all the players that a player has joined the game.""" + for player in game.players.values(): + remaining_players_count = game.required_no_of_players - len(game.players) + if remaining_players_count: + count_message = f"{remaining_players_count} yet to join." + else: + count_message = "All aboard. The game creator can start the game now." + + await player.ctx.send( + f"<@{ctx.user.id}> joined the game.\n{count_message}", + ephemeral=True, + ) + @slash_command(name="defcord", description="Interact with defcord.") async def defcord(self, ctx: SlashContext) -> None: """Make the subcommand work, since it requires a function to latch off of.""" @defcord.subcommand(sub_cmd_name="create", sub_cmd_description="Create a game of Defcord") - async def create(self, ctx: SlashContext) -> None: + @slash_option( + "required_no_of_players", + "Number of players required for the game", + required=True, + opt_type=OptionType.INTEGER, + min_value=1, + max_value=10, + ) + async def create(self, ctx: SlashContext, required_no_of_players: int = 5) -> None: """Create a game of DEFCORD.""" - game = self.game_factory.create_game() # Add the first player here - - await game.add_player(ctx) + existing_game = self.game_factory.query_game(player_id=ctx.user.id) + if existing_game: + await ctx.send( + f"<@{ctx.user.id}> You are already part of the game with invite {existing_game.id}", + ephemeral=True, + ) + return + + game = self.game_factory.create_game(required_no_of_players) + self.game_factory.add_player(ctx.user.id, game) + await game.add_player(ctx, cmd="create") embed = Embed( title="New game started!", @@ -72,6 +119,7 @@ async def create(self, ctx: SlashContext) -> None: ) await ctx.send(embed=embed) + await self.send_player_join_notification(game, ctx) @defcord.subcommand(sub_cmd_name="join", sub_cmd_description="Join a game of Defcord") @slash_option("invite", "The invite code for the game", required=True, opt_type=OptionType.STRING) @@ -80,20 +128,77 @@ async def join(self, ctx: SlashContext, invite: str) -> None: game = self.game_factory.query_game(game_id=invite) if game is None: - raise NotImplementedError + await ctx.send(f"<@{ctx.user.id}> Invite({invite}) is invalid", ephemeral=True) + return - await game.add_player(ctx) + if ctx.user.id in game.players: + await ctx.send(f"<@{ctx.user.id}> You are already part of the game with {invite=}", ephemeral=True) + return + + if game.required_no_of_players == len(game.players): + await ctx.send(f"<@{ctx.user.id}> Game is already full {invite=}", ephemeral=True) + return + + if game.started: + await ctx.send(f"<@{ctx.user.id}> Game already started", ephemeral=True) + return + + self.game_factory.add_player(ctx.user.id, game) + await game.add_player(ctx, cmd="join") + await self.send_player_join_notification(game, ctx) @defcord.subcommand(sub_cmd_name="leave", sub_cmd_description="Leave a game of Defcord") async def leave(self, ctx: SlashContext) -> None: - """Leave the current game of defcord""" + """Leave the current game of defcord.""" + game = self.game_factory.query_game(player_id=ctx.user.id) + + if game is None: + await ctx.send(f"<@{ctx.user.id}> You are not part of any game", ephemeral=True) + return + + # if game.creator == ctx.user.id: # TODO: check this validity - needed of not + # await ctx.send(f"<@{ctx.user.id}> Game creator cannot leave the game", ephemeral=True) # noqa: ERA001 + # return # noqa: ERA001 + + self.game_factory.remove_player(ctx.user.id) + await game.remove_player(ctx) + + embed = Embed( + title="A Player left the game", + description=f"<@{ctx.user.id}> has left the game ({len(game.players)} players left).", + color=(255, 0, 0), + ) + for player in game.players.values(): + await player.ctx.send(embed=embed, ephemeral=True) + + await ctx.send(embed=embed, ephemeral=True) + + if len(game.players) == 0: + await ctx.send("Game Over! You are the only one survivor. Everyone quit!", ephemeral=True) + game.stop() + self.game_factory.remove_game(game.id) + + @defcord.subcommand(sub_cmd_name="start", sub_cmd_description="Start a game of DEFCORD.") + async def start(self, ctx: SlashContext) -> None: + """Start and runs the Defcord game loop.""" game = self.game_factory.query_game(player_id=ctx.user.id) if game is None: - raise NotImplementedError - else: - embed = Embed(title= "A Player left the game", - description=f"<@{ctx.user.id}> has left the game", color=(255, 0, 0)) - await game.remove_player(ctx) - for player in game.players.values(): - await player.ctx.send(embed=embed, ephemeral=True) + await ctx.send(f"<@{ctx.user.id}> You are not part of any game", ephemeral=True) + return + + if game.creator != ctx.user.id: + await ctx.send(f"<@{ctx.user.id}> Only game creator can start it", ephemeral=True) + return + + if game.started: + await ctx.send(f"<@{ctx.user.id}> Game already started", ephemeral=True) + return + + if game.required_no_of_players != len(game.players): + await ctx.send(f"<@{ctx.user.id}> Cannot start the game until all the players join", ephemeral=True) + return + + game.started = True + await ctx.send(f"<@{ctx.user.id}> Game started", ephemeral=True) + await game.loop() From c030a455104d310fdfae8e33128c4cf7610c83cc Mon Sep 17 00:00:00 2001 From: Sapient44 Date: Sat, 27 Jul 2024 16:32:17 +0530 Subject: [PATCH 065/116] Fix on mistake in merge conflict --- src/game.py | 5 +++-- src/game_interaction.py | 3 +-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/game.py b/src/game.py index 3e3a9d4..c3e5ad9 100644 --- a/src/game.py +++ b/src/game.py @@ -90,11 +90,12 @@ async def loop(self) -> None: for res in response: if isinstance(res, Exception): logger.error(res) - except Exception: + except Exception as e: logger.exception("Error occurred in game loop") for player in players: - await player.ctx.send(embed=embed) + await player.ctx.send(error_embed(title = "Some error occured", + description =f"{e} \n has occured, please contact the devs if you see this")) async def tick(self, player: Player) -> None: """Define the activities done in every game tick.""" diff --git a/src/game_interaction.py b/src/game_interaction.py index b612058..eb9a4ef 100644 --- a/src/game_interaction.py +++ b/src/game_interaction.py @@ -161,10 +161,9 @@ async def leave(self, ctx: SlashContext) -> None: self.game_factory.remove_player(ctx.user.id) await game.remove_player(ctx) - embed = Embed( + embed = system_message_embed( title="A Player left the game", description=f"<@{ctx.user.id}> has left the game ({len(game.players)} players left).", - color=(255, 0, 0), ) for player in game.players.values(): await player.ctx.send(embed=embed, ephemeral=True) From 6fa1ce4672925de8bc352846aa5c796323dc9c99 Mon Sep 17 00:00:00 2001 From: Sapient44 Date: Sat, 27 Jul 2024 16:51:36 +0530 Subject: [PATCH 066/116] Fix the issue with Embed object --- src/const.py | 8 ++++---- src/game.py | 14 ++++++++------ src/game_interaction.py | 11 +++++++---- src/templating.py | 5 +++-- 4 files changed, 22 insertions(+), 16 deletions(-) diff --git a/src/const.py b/src/const.py index c952f1e..3527eae 100644 --- a/src/const.py +++ b/src/const.py @@ -1,5 +1,5 @@ -from interactions import Embed +# from interactions import Embed -error_embed = Embed(color=(255, 58, 51)) -message_embed = Embed(color=(124, 199, 242)) -system_message_embed = Embed(color=(88, 245, 149)) +error_color = (255, 58, 51) +message_color = (124, 199, 242) +system_message_color = (88, 245, 149) diff --git a/src/game.py b/src/game.py index c3e5ad9..b1358ce 100644 --- a/src/game.py +++ b/src/game.py @@ -4,10 +4,10 @@ from datetime import datetime, timedelta from typing import TYPE_CHECKING, Annotated -from interactions import SlashContext +from interactions import SlashContext, Embed from src.characters import all_characters -from src.const import error_embed, system_message_embed +from src.const import error_color, system_message_color from src.player import Player from src.templating import total_stages @@ -54,8 +54,9 @@ async def remove_player(self, ctx: SlashContext) -> None: # Need to pass this error to the user, that you are in no game async def death_player(self, dead_player: Player) -> None: - embed = system_message_embed(title = "We have lost a national leader in the turmoil", - description = f"{dead_player.nation_name} has lost their leadership which was done by \n <@{dead_player.id}>") + embed = Embed(title = "We have lost a national leader in the turmoil", + description = f"{dead_player.nation_name} has lost their leadership which was done by \n <@{dead_player.id}>", + color=system_message_color) for player in self.players.values(): await player.ctx.send(embed) @@ -94,8 +95,9 @@ async def loop(self) -> None: logger.exception("Error occurred in game loop") for player in players: - await player.ctx.send(error_embed(title = "Some error occured", - description =f"{e} \n has occured, please contact the devs if you see this")) + await player.ctx.send(Embed(title = "Some error occured", + description =f"{e} \n has occured, please contact the devs if you see this", + color=error_color)) async def tick(self, player: Player) -> None: """Define the activities done in every game tick.""" diff --git a/src/game_interaction.py b/src/game_interaction.py index eb9a4ef..53453e6 100644 --- a/src/game_interaction.py +++ b/src/game_interaction.py @@ -3,6 +3,7 @@ from typing import TYPE_CHECKING from interactions import ( + Embed, Extension, OptionType, SlashContext, @@ -11,7 +12,7 @@ ) from src.game import Game, GameID -from src.const import system_message_embed +from src.const import system_message_color if TYPE_CHECKING: from interactions import Client @@ -111,9 +112,10 @@ async def create(self, ctx: SlashContext, required_no_of_players: int = 5) -> No self.game_factory.add_player(ctx.user.id, game) await game.add_player(ctx, cmd="create") - embed = system_message_embed( + embed = Embed( title="New game started!", - description=f"Your invite: {game.id}" + description=f"Your invite: {game.id}", + color=system_message_color ) await ctx.send(embed=embed) @@ -161,9 +163,10 @@ async def leave(self, ctx: SlashContext) -> None: self.game_factory.remove_player(ctx.user.id) await game.remove_player(ctx) - embed = system_message_embed( + embed = Embed( title="A Player left the game", description=f"<@{ctx.user.id}> has left the game ({len(game.players)} players left).", + color=system_message_color ) for player in game.players.values(): await player.ctx.send(embed=embed, ephemeral=True) diff --git a/src/templating.py b/src/templating.py index bb9570d..2239fb0 100644 --- a/src/templating.py +++ b/src/templating.py @@ -5,7 +5,7 @@ from interactions import ActionRow, Button, ButtonStyle, Embed from src.weighted_random import WeightedList -from src.const import message_embed +from src.const import message_color if TYPE_CHECKING: from src.player import Player, PlayerState @@ -36,9 +36,10 @@ def is_available(self, state: "PlayerState") -> bool: def to_embed(self, player: "Player", actor: "Actor") -> Embed: """Get an embed for UI.""" # Now you can access actor here - return message_embed( + return Embed( title=f"{actor.name} of {player.state.nation_name}", description=self.format(player.state), + color=message_color ) async def ui(self, player: "Player", actor: "Actor") -> None: From df3762854a4d128aea4f5c494515b8a947fd4351 Mon Sep 17 00:00:00 2001 From: Sapient44 Date: Sat, 27 Jul 2024 17:25:29 +0530 Subject: [PATCH 067/116] changing datetime to time --- src/game.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/game.py b/src/game.py index b1358ce..05b40d6 100644 --- a/src/game.py +++ b/src/game.py @@ -1,7 +1,7 @@ import asyncio import logging import random -from datetime import datetime, timedelta +import time from typing import TYPE_CHECKING, Annotated from interactions import SlashContext, Embed @@ -69,7 +69,7 @@ def stop(self) -> None: async def loop(self) -> None: """Define the main loop of the game.""" - self.start_time = datetime.now() + self.start_time = time.time() players = self.players.values() @@ -77,7 +77,7 @@ async def loop(self) -> None: if self.stop_flag: break - game_time: float = (datetime.now() - self.start_time) / timedelta(minutes=1) + game_time: float = (time.time() - self.start_time) / 60 if (game_time > self.cumm_percent_time_per_stage[self.stage - 1] * self.max_time) and ( game_time < self.max_time ): From 7e02860ce54edde4c4b70e011b415a4d4374c2c0 Mon Sep 17 00:00:00 2001 From: Sapient44 Date: Sat, 27 Jul 2024 19:11:29 +0530 Subject: [PATCH 068/116] Skeleton of anti_afk --- src/const.py | 1 + src/game.py | 16 ++++++++++------ src/player.py | 10 +++++++--- 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/src/const.py b/src/const.py index 3527eae..f7f8fba 100644 --- a/src/const.py +++ b/src/const.py @@ -3,3 +3,4 @@ error_color = (255, 58, 51) message_color = (124, 199, 242) system_message_color = (88, 245, 149) +AFK_TIME = 60 diff --git a/src/game.py b/src/game.py index 05b40d6..3c6dd00 100644 --- a/src/game.py +++ b/src/game.py @@ -4,10 +4,10 @@ import time from typing import TYPE_CHECKING, Annotated -from interactions import SlashContext, Embed +from interactions import Embed, SlashContext from src.characters import all_characters -from src.const import error_color, system_message_color +from src.const import error_color, system_message_color, AFK_TIME from src.player import Player from src.templating import total_stages @@ -57,12 +57,12 @@ async def death_player(self, dead_player: Player) -> None: embed = Embed(title = "We have lost a national leader in the turmoil", description = f"{dead_player.nation_name} has lost their leadership which was done by \n <@{dead_player.id}>", color=system_message_color) - + for player in self.players.values(): await player.ctx.send(embed) - + self.remove_player(dead_player.ctx) - + def stop(self) -> None: """Set the stop flag.""" self.stop_flag = True @@ -96,7 +96,8 @@ async def loop(self) -> None: for player in players: await player.ctx.send(Embed(title = "Some error occured", - description =f"{e} \n has occured, please contact the devs if you see this", + description =f"{e} \n has occured, please contact the \ + devs if you see this", color=error_color)) async def tick(self, player: Player) -> None: @@ -104,6 +105,9 @@ async def tick(self, player: Player) -> None: if self.stop_flag: return + if player.current_activity_time - player.last_activity_time > AFK_TIME: + self.remove_player(player.ctx) + character = all_characters.get_random(player.state) # The sleep times are subject to change, based on how the actual gameplay feels # The randomness gives a variability between the values mentioned in the brackets diff --git a/src/player.py b/src/player.py index 4bd94d9..b069c74 100644 --- a/src/player.py +++ b/src/player.py @@ -1,5 +1,5 @@ from typing import TYPE_CHECKING - +import time from attrs import define from interactions import Modal, ModalContext, ShortText, SlashContext @@ -33,9 +33,13 @@ def apply(self, consequence: dict) -> None: class Player: def __init__(self, ctx: SlashContext, game: "Game") -> None: - self.ctx = ctx + self.ctx: SlashContext = ctx self.state: PlayerState = None # type: ignore TODO: properly type that state isn't none after register - self.game = game + self.game: Game = game + self.last_activity_time: float = time.time() + self.current_activity_time: float = 0 + + # self.last_activity_time: float = time.time() async def register(self) -> None: """Ask the player for information.""" From 20ed0d67d26f581fa38890bc92b57daa18d62cd4 Mon Sep 17 00:00:00 2001 From: Sapient44 Date: Sat, 27 Jul 2024 19:36:21 +0530 Subject: [PATCH 069/116] Added reference of Game Factory in Game, for removing player --- src/game.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/game.py b/src/game.py index b1358ce..8ea9e8b 100644 --- a/src/game.py +++ b/src/game.py @@ -13,6 +13,7 @@ if TYPE_CHECKING: from src.templating import Stage + from src.game_interaction import GameFactory GameID = str @@ -49,6 +50,7 @@ async def remove_player(self, ctx: SlashContext) -> None: player_to_delete = ctx.user.id try: del self.players[player_to_delete] + GameFactory.remove_player(player_to_delete) except KeyError: raise NotImplementedError from KeyError # Need to pass this error to the user, that you are in no game From 78802fa57a7f595ad33193e200f45d65aeb98347 Mon Sep 17 00:00:00 2001 From: Maheshkumar Date: Sat, 27 Jul 2024 20:25:42 +0530 Subject: [PATCH 070/116] Add button interaction callback --- src/game.py | 1 + src/game_interaction.py | 23 +++++++++++++++-------- src/player.py | 11 +++++++++-- src/templating.py | 8 +++++--- 4 files changed, 30 insertions(+), 13 deletions(-) diff --git a/src/game.py b/src/game.py index 537bd19..608f9b2 100644 --- a/src/game.py +++ b/src/game.py @@ -31,6 +31,7 @@ def __init__(self, id: GameID, required_no_of_players: int) -> None: self.started: bool = False self.creator: int | None = None self.stop_flag: bool = False + self.player_component_choice_mapping: dict[str, dict] = {} self.cumm_percent_time_per_stage: list[float] = [0.25, 0.6, 1] # Percentage of the time spent in the game when the next stage of the time begins (max value 1 = 100%) diff --git a/src/game_interaction.py b/src/game_interaction.py index c400391..18a232f 100644 --- a/src/game_interaction.py +++ b/src/game_interaction.py @@ -1,15 +1,10 @@ import random +import time from string import ascii_uppercase, digits from typing import TYPE_CHECKING -from interactions import ( - Embed, - Extension, - OptionType, - SlashContext, - slash_command, - slash_option, -) +from interactions import Embed, Extension, OptionType, SlashContext, listen, slash_command, slash_option +from interactions.api.events import Component from src.game import Game, GameID @@ -202,3 +197,15 @@ async def start(self, ctx: SlashContext) -> None: game.started = True await ctx.send(f"<@{ctx.user.id}> Game started", ephemeral=True) await game.loop() + + @listen(Component) + async def on_component(self, event: Component) -> None: + """Listen to button clicks.""" + ctx = event.ctx + game = self.game_factory.query_game(player_id=ctx.user.id) + consequences = game.player_component_choice_mapping[ctx.custom_id] + player = game.players[ctx.user.id] + player.state.apply(consequences) + player.last_activity_time = time.time() + await ctx.edit_origin(content=f"Your response ({ctx.component.label}) saved.", components=[]) + del game.player_component_choice_mapping[ctx.custom_id] diff --git a/src/player.py b/src/player.py index 4bd94d9..d70e5e7 100644 --- a/src/player.py +++ b/src/player.py @@ -28,14 +28,21 @@ class PlayerState: def apply(self, consequence: dict) -> None: """Apply the consequnces to current state.""" for k, v in consequence.items(): - self.__dict__[k] += v + if attr := getattr(self, k, None): + setattr(self, k, attr + v) class Player: def __init__(self, ctx: SlashContext, game: "Game") -> None: self.ctx = ctx self.state: PlayerState = None # type: ignore TODO: properly type that state isn't none after register - self.game = game + self.game: Game = game + self.component_id: int = 0 + + def get_component_id(self) -> int: + """Return an id to be used in a component like button.""" + self.component_id += 1 + return self.component_id async def register(self) -> None: """Ask the player for information.""" diff --git a/src/templating.py b/src/templating.py index 7ecd10a..16563e8 100644 --- a/src/templating.py +++ b/src/templating.py @@ -60,11 +60,13 @@ async def ui(self, player: "Player", actor: "Actor") -> None: """Send UI and apply consequences.""" buttons: list[Button] = [] - for id, choice in enumerate(self.choices.items()): + for _, choice in enumerate(self.choices.items()): + button_custom_id = f"{player.ctx.user.id}_{player.get_component_id()}" + player.game.player_component_choice_mapping[button_custom_id] = choice[1] button = Button( - label=f"{next(iter(choice))}", + label=f"{choice[0]}", style=ButtonStyle.BLURPLE, - custom_id=f"Choice {id}", + custom_id=f"{button_custom_id}", ) buttons.append(button) From d6776893f081b6cab3051ab4944afc86e9a0c8f5 Mon Sep 17 00:00:00 2001 From: Maheshkumar Date: Sat, 27 Jul 2024 21:14:47 +0530 Subject: [PATCH 071/116] Fix death player issues --- src/const.py | 2 -- src/game.py | 55 ++++++++++++++++++++++++----------------- src/game_interaction.py | 21 ++++++++++------ src/player.py | 5 ++-- src/templating.py | 3 ++- 5 files changed, 51 insertions(+), 35 deletions(-) diff --git a/src/const.py b/src/const.py index 3527eae..1aae9b5 100644 --- a/src/const.py +++ b/src/const.py @@ -1,5 +1,3 @@ -# from interactions import Embed - error_color = (255, 58, 51) message_color = (124, 199, 242) system_message_color = (88, 245, 149) diff --git a/src/game.py b/src/game.py index 86b197c..8ae918e 100644 --- a/src/game.py +++ b/src/game.py @@ -4,7 +4,7 @@ from datetime import datetime, timedelta from typing import TYPE_CHECKING, Annotated -from interactions import SlashContext, Embed +from interactions import Embed, SlashContext from src.characters import all_characters from src.const import error_color, system_message_color @@ -12,8 +12,8 @@ from src.templating import total_stages if TYPE_CHECKING: - from src.templating import Stage from src.game_interaction import GameFactory + from src.templating import Stage GameID = str @@ -23,7 +23,7 @@ class Game: """Initialize a Game and it's behaviours.""" - def __init__(self, id: GameID, required_no_of_players: int) -> None: + def __init__(self, id: GameID, required_no_of_players: int, game_factory: "GameFactory") -> None: self.id = id self.required_no_of_players: int = required_no_of_players self.players: dict[Annotated[int, "discord id"], Player] = {} @@ -33,6 +33,7 @@ def __init__(self, id: GameID, required_no_of_players: int) -> None: self.creator: int | None = None self.stop_flag: bool = False self.player_component_choice_mapping: dict[str, dict] = {} + self.game_factory: GameFactory = game_factory self.cumm_percent_time_per_stage: list[float] = [0.25, 0.6, 1] # Percentage of the time spent in the game when the next stage of the time begins (max value 1 = 100%) @@ -49,23 +50,27 @@ async def add_player(self, ctx: SlashContext, cmd: str = "create") -> None: async def remove_player(self, ctx: SlashContext) -> None: """Remove player from the game.""" player_to_delete = ctx.user.id - try: + if player_to_delete in self.players: del self.players[player_to_delete] - GameFactory.remove_player(player_to_delete) - except KeyError: - raise NotImplementedError from KeyError - # Need to pass this error to the user, that you are in no game + self.game_factory.remove_player(player_to_delete) async def death_player(self, dead_player: Player) -> None: - embed = Embed(title = "We have lost a national leader in the turmoil", - description = f"{dead_player.nation_name} has lost their leadership which was done by \n <@{dead_player.id}>", - color=system_message_color) - + """Mark the player as dead.""" + embed = Embed( + title="We have lost a national leader in the turmoil", + description=f"{dead_player.state.nation_name} has lost their leadership which was done by \n <@{dead_player.ctx.user.id}>", # noqa: E501 + color=system_message_color, + ) + for player in self.players.values(): - await player.ctx.send(embed) - - self.remove_player(dead_player.ctx) - + await player.ctx.send(embed=embed) + + await self.remove_player(dead_player.ctx) + + if len(self.players) == 0 and self.started: + self.stop() + self.game_factory.remove_game(self.id) + def stop(self) -> None: """Set the stop flag.""" self.stop_flag = True @@ -98,9 +103,13 @@ async def loop(self) -> None: logger.exception("Error occurred in game loop") for player in players: - await player.ctx.send(Embed(title = "Some error occured", - description =f"{e} \n has occured, please contact the devs if you see this", - color=error_color)) + await player.ctx.send( + Embed( + title="Some error occured", + description=f"{e} \n has occured, please contact the devs if you see this", + color=error_color, + ), + ) async def tick(self, player: Player) -> None: """Define the activities done in every game tick.""" @@ -110,9 +119,11 @@ async def tick(self, player: Player) -> None: character = all_characters.get_random(player.state) # The sleep times are subject to change, based on how the actual gameplay feels # The randomness gives a variability between the values mentioned in the brackets - if any(getattr(player.state, attr) < 0 for attr in self.values_to_check): - # Some value is negative hence need to send the losing message - self.death_player(player) + for attr in self.values_to_check: + if getattr(player.state, attr) < 0: + # Some value is negative hence need to send the losing message + await self.death_player(player) + return match self.stage: case 1: diff --git a/src/game_interaction.py b/src/game_interaction.py index 4627ea7..ca0e773 100644 --- a/src/game_interaction.py +++ b/src/game_interaction.py @@ -6,8 +6,9 @@ from interactions import Embed, Extension, OptionType, SlashContext, listen, slash_command, slash_option from interactions.api.events import Component -from src.game import Game, GameID from src.const import system_message_color +from src.game import Game, GameID + if TYPE_CHECKING: from interactions import Client @@ -32,7 +33,7 @@ def generate_game_id(self) -> GameID: def create_game(self, required_no_of_players: int) -> Game: """Create a game with the required details.""" game_id = self.generate_game_id() - game = Game(game_id, required_no_of_players) + game = Game(game_id, required_no_of_players, self) self.games[game_id] = game return game @@ -43,7 +44,8 @@ def add_player(self, player_id: int, game: Game) -> None: def remove_player(self, player_id: int) -> None: """Remove a player game mapping.""" - del self.players[player_id] + if player_id in self. players: + del self.players[player_id] def remove_game(self, game_id: int) -> None: """Remove a game from game id mapping.""" @@ -110,7 +112,7 @@ async def create(self, ctx: SlashContext, required_no_of_players: int = 5) -> No embed = Embed( title="New game started!", description=f"Your invite: {game.id}", - color=system_message_color + color=system_message_color, ) await ctx.send(embed=embed) @@ -155,20 +157,19 @@ async def leave(self, ctx: SlashContext) -> None: # await ctx.send(f"<@{ctx.user.id}> Game creator cannot leave the game", ephemeral=True) # noqa: ERA001 # return # noqa: ERA001 - self.game_factory.remove_player(ctx.user.id) await game.remove_player(ctx) embed = Embed( title="A Player left the game", description=f"<@{ctx.user.id}> has left the game ({len(game.players)} players left).", - color=system_message_color + color=system_message_color, ) for player in game.players.values(): await player.ctx.send(embed=embed, ephemeral=True) await ctx.send(embed=embed, ephemeral=True) - if len(game.players) == 0: + if len(game.players) == 0 and game.started: await ctx.send("Game Over! You are the only one survivor. Everyone quit!", ephemeral=True) game.stop() self.game_factory.remove_game(game.id) @@ -203,9 +204,15 @@ async def on_component(self, event: Component) -> None: """Listen to button clicks.""" ctx = event.ctx game = self.game_factory.query_game(player_id=ctx.user.id) + + if not game and game.started: + await ctx.edit_origin(content="Your game already over.", components=[]) + return + consequences = game.player_component_choice_mapping[ctx.custom_id] player = game.players[ctx.user.id] player.state.apply(consequences) player.last_activity_time = time.time() + print(player.state) await ctx.edit_origin(content=f"Your response ({ctx.component.label}) saved.", components=[]) del game.player_component_choice_mapping[ctx.custom_id] diff --git a/src/player.py b/src/player.py index d70e5e7..901f702 100644 --- a/src/player.py +++ b/src/player.py @@ -17,7 +17,7 @@ class PlayerState: money: float = 100 # How loyal people feel to the current government that you have created - loyalty: float = 50 + loyalty: float = 10 # How vulnerable is the country from external threats security: float = 50 @@ -28,8 +28,7 @@ class PlayerState: def apply(self, consequence: dict) -> None: """Apply the consequnces to current state.""" for k, v in consequence.items(): - if attr := getattr(self, k, None): - setattr(self, k, attr + v) + setattr(self, k, getattr(self, k, None) + v) class Player: diff --git a/src/templating.py b/src/templating.py index 2a7ec2e..0952713 100644 --- a/src/templating.py +++ b/src/templating.py @@ -39,7 +39,7 @@ def to_embed(self, player: "Player", actor: "Actor") -> Embed: return Embed( title=f"{actor.name} of {player.state.nation_name}", description=self.format(player.state), - color=message_color + color=message_color, ) async def ui(self, player: "Player", actor: "Actor") -> None: @@ -119,6 +119,7 @@ def cast_stages(stage_groups: list[StageGroup]) -> dict[Stage, WeightedList[Temp weight: int = 100 def is_available(self, state: "PlayerState") -> bool: + """Send always available.""" # Add stuff here if you want to add actors which appear on condition. _ = state return True From 98129fbd87db7d03f8cd16a39efeedb5e6b3e677 Mon Sep 17 00:00:00 2001 From: Maheshkumar Date: Sat, 27 Jul 2024 21:18:41 +0530 Subject: [PATCH 072/116] Fix game none issue --- src/game_interaction.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/game_interaction.py b/src/game_interaction.py index ca0e773..550477e 100644 --- a/src/game_interaction.py +++ b/src/game_interaction.py @@ -205,7 +205,7 @@ async def on_component(self, event: Component) -> None: ctx = event.ctx game = self.game_factory.query_game(player_id=ctx.user.id) - if not game and game.started: + if not game: await ctx.edit_origin(content="Your game already over.", components=[]) return From 349293fcb6c286148b176f61e05c693e1ce5fbb6 Mon Sep 17 00:00:00 2001 From: Dhanvant <92245578+Dhanvantg@users.noreply.github.com> Date: Sat, 27 Jul 2024 21:26:30 +0530 Subject: [PATCH 073/116] Added more actors --- src/characters/example_chr.py | 70 +++++++++++++++++------------------ 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/src/characters/example_chr.py b/src/characters/example_chr.py index 184d006..caec647 100644 --- a/src/characters/example_chr.py +++ b/src/characters/example_chr.py @@ -1,35 +1,35 @@ -from src.templating import Actor, StageGroup -from src.templating import ChoiceTemplate as t - -# fmt: off -character = Actor("John the Farmer", "url_here",[ - StageGroup(1, [ - t( - "Hello, leader of {nation_name}. Can you spare some money for our new farming equipment?", - choices={ - "Sure": {"money": -10, "loyalty": +5}, - "Nope": {"loyalty": -5}, - }, - ), - t( - "Things are great, we managed to grow even more crops than expected.\ - Thank you for your help, leader! \n Here, have this small gift from our community", - choices = {"Thanks!": {"money": +15}}, - condition = lambda state: state.loyalty>70 and state.money<300, - ), - ]), - StageGroup(2, [ - t("O Great leader of {nation_name}! Please provide money to upgrade our equipment", - choices = { - "Sure": {"money": -20, "loyalty": +5}, - "Nope": {"loyalty": -5}, - }, - ), - t( - "Things are great, we managed to grow even more crops than expected.\ - Thank you for your help, leader! \n Here, have this small gift from our community", - choices = {"Thanks!": {"money": +30}}, - condition = lambda state: state.loyalty>75 and state.money<800, - ), - ]), -]) +from src.templating import Actor, StageGroup +from src.templating import ChoiceTemplate as t + +# fmt: off +character = Actor("John the Farmer", "url_here",[ + StageGroup(1, [ + t( + "Hello, leader of {nation_name}. Can you spare some money for our new farming equipment?", + choices={ + "Sure": {"money": -10, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + t( + "Things are great, we managed to grow even more crops than expected.\ + Thank you for your help, leader! \n Here, have this small gift from our community", + choices = {"Thanks!": {"money": +15}}, + condition = lambda state: state.loyalty>70 and state.money<300, + ), + ]), + StageGroup(2, [ + t("O Great leader of {nation_name}! Please provide money to upgrade our equipment", + choices = { + "Sure": {"money": -20, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + t( + "Things are great, we managed to grow even more crops than expected.\ + Thank you for your help, leader! \n Here, have this small gift from our community", + choices = {"Thanks!": {"money": +30}}, + condition = lambda state: state.loyalty>75 and state.money<800, + ), + ]), +]) From dedf804ac977837b448b8994e4c2f2e15a671947 Mon Sep 17 00:00:00 2001 From: Dhanvant <92245578+Dhanvantg@users.noreply.github.com> Date: Sun, 28 Jul 2024 01:20:01 +0530 Subject: [PATCH 074/116] Add more Actors --- src/characters/example_chr.py | 763 +++++++++++++++++++++++++++++++++- 1 file changed, 757 insertions(+), 6 deletions(-) diff --git a/src/characters/example_chr.py b/src/characters/example_chr.py index caec647..738777a 100644 --- a/src/characters/example_chr.py +++ b/src/characters/example_chr.py @@ -2,16 +2,19 @@ from src.templating import ChoiceTemplate as t # fmt: off -character = Actor("John the Farmer", "url_here",[ +from models import Actor, StageGroup, Template + +# fmt: off +Actor("Fred the Farmer", "url_here",[ StageGroup(1, [ - t( + Template( "Hello, leader of {nation_name}. Can you spare some money for our new farming equipment?", choices={ "Sure": {"money": -10, "loyalty": +5}, "Nope": {"loyalty": -5}, }, ), - t( + Template( "Things are great, we managed to grow even more crops than expected.\ Thank you for your help, leader! \n Here, have this small gift from our community", choices = {"Thanks!": {"money": +15}}, @@ -19,17 +22,765 @@ ), ]), StageGroup(2, [ - t("O Great leader of {nation_name}! Please provide money to upgrade our equipment", + Template("Hello, leader of {nation_name}. Can you spare some money for our new farming equipment?", choices = { "Sure": {"money": -20, "loyalty": +5}, "Nope": {"loyalty": -5}, }, ), - t( + Template( "Things are great, we managed to grow even more crops than expected.\ Thank you for your help, leader! \n Here, have this small gift from our community", choices = {"Thanks!": {"money": +30}}, - condition = lambda state: state.loyalty>75 and state.money<800, + condition = lambda state: state.loyalty>90 and state.money<600, + ), + ]), + StageGroup(3, [ + Template("Hello, leader of {nation_name}. Can you spare some money for our new farming equipment?", + choices = { + "Sure": {"money": -40, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + Template( + "Things are great, we managed to grow even more crops than expected.\ + Thank you for your help, leader! \n Here, have this small gift from our community", + choices = {"Thanks!": {"money": +60}}, + condition = lambda state: state.loyalty>110 and state.money<1200, + ), + ]), + StageGroup(4, [ + Template("Hello, leader of {nation_name}. Can you spare some money for our new farming equipment?", + choices = { + "Sure": {"money": -80, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + Template( + "Things are great, we managed to grow even more crops than expected.\ + Thank you for your help, leader! \n Here, have this small gift from our community", + choices = {"Thanks!": {"money": +120}}, + condition = lambda state: state.loyalty>130 and state.money<2400, + ), + ]), + StageGroup(5, [ + Template("Hello, leader of {nation_name}. Can you spare some money for our new farming equipment?", + choices = { + "Sure": {"money": -160, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + Template( + "Things are great, we managed to grow even more crops than expected.\ + Thank you for your help, leader! \n Here, have this small gift from our community", + choices = {"Thanks!": {"money": +240}}, + condition = lambda state: state.loyalty>150 and state.money<480, + ), + ]), +]) + +Actor("Wong the Worker", "url_here",[ + StageGroup(1, [ + Template( + "O great leader of {nation_name}. May thou be kind enough to raise our minimum wage", + choices={ + "Sure": {"money": -10, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + Template( + "Thank you! We have been extremely productive and produced so much more goods.", + choices = {"Great!": {"money": +15}}, + condition = lambda state: state.loyalty>70 and state.money<300, + ), + ]), + StageGroup(2, [ + Template("O great leader of {nation_name}. May thou be kind enough to raise our minimum wage", + choices = { + "Sure": {"money": -20, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + Template( + "Thank you! We have been extremely productive and produced so much more goods.", + choices = {"Great!": {"money": +30}}, + condition = lambda state: state.loyalty>90 and state.money<600, + ), + ]), + StageGroup(3, [ + Template("O great leader of {nation_name}. May thou be kind enough to raise our minimum wage", + choices = { + "Sure": {"money": -40, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + Template( + "Thank you! We have been extremely productive and produced so much more goods.", + choices = {"Great!": {"money": +60}}, + condition = lambda state: state.loyalty>110 and state.money<1200, + ), + ]), + StageGroup(4, [ + Template("O great leader of {nation_name}. May thou be kind enough to raise our minimum wage", + choices = { + "Sure": {"money": -80, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + Template( + "Thank you! We have been extremely productive and produced so much more goods.", + choices = {"Great!": {"money": +120}}, + condition = lambda state: state.loyalty>130 and state.money<2400, + ), + ]), + StageGroup(5, [ + Template("O great leader of {nation_name}. May thou be kind enough to raise our minimum wage", + choices = { + "Sure": {"money": -160, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + Template( + "Thank you! We have been extremely productive and produced so much more goods.", + choices = {"Great!": {"money": +240}}, + condition = lambda state: state.loyalty>150 and state.money<480, + ), + ]), +]) + + +Actor("Sam the Scientist", "url_here",[ + StageGroup(1, [ + Template( + "Greetings, esteemed leader of {nation_name}. May I request your consideration for a financial contribution\ + towards our research?", + choices={ + "Sure": {"money": -10, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + Template( + "Alzhemier's will be cured soon! Thanks for your contribution", + choices = {"Great!": {"money": +15}}, + condition = lambda state: state.loyalty>70 and state.money<300, + ), + ]), + StageGroup(2, [ + Template("Greetings, esteemed leader of {nation_name}. May I request your consideration for a financial contribution\ + towards our research?", + choices = { + "Sure": {"money": -20, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + Template( + "Parkinson's will be cured soon! Thanks for your contribution", + choices = {"Great!": {"money": +30}}, + condition = lambda state: state.loyalty>90 and state.money<600, + ), + ]), + StageGroup(3, [ + Template("Greetings, esteemed leader of {nation_name}. May I request your consideration for a financial contribution\ + towards our research?", + choices = { + "Sure": {"money": -40, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + Template( + "AIDS will be cured soon! Thanks for your contribution", + choices = {"Great!": {"money": +60}}, + condition = lambda state: state.loyalty>110 and state.money<1200, + ), + ]), + StageGroup(4, [ + Template("Greetings, esteemed leader of {nation_name}. May I request your consideration for a financial contribution\ + towards our research?", + choices = { + "Sure": {"money": -80, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + Template( + "Hepatitis will be cured soon! Thanks for your contribution", + choices = {"Great!": {"money": +120}}, + condition = lambda state: state.loyalty>130 and state.money<2400, + ), + ]), + StageGroup(5, [ + Template("Greetings, esteemed leader of {nation_name}. May I request your consideration for a financial contribution\ + towards our research?", + choices = { + "Sure": {"money": -160, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + Template( + "CANCER will be cured soon! Thanks for your contribution", + choices = {"Great!": {"money": +240}}, + condition = lambda state: state.loyalty>150 and state.money<480, + ), + ]), +]) + + +Actor("Andy the Athlete", "url_here",[ + StageGroup(1, [ + Template( + "Dear leader of {nation_name}, please help our climate change marathon's fundraiser.", + choices={ + "Sure": {"money": -10, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + Template( + "The marathon was phenomenal! Thanks for your support.", + choices = {"Great!": {"money": +15}}, + condition = lambda state: state.loyalty>70 and state.money<300, + ), + ]), + StageGroup(2, [ + Template("Dear leader of {nation_name}, please help our climate change marathon's fundraiser.", + choices = { + "Sure": {"money": -20, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + Template( + "The marathon was phenomenal! Thanks for your support.", + choices = {"Great!": {"money": +30}}, + condition = lambda state: state.loyalty>90 and state.money<600, + ), + ]), + StageGroup(3, [ + Template("Dear leader of {nation_name}, please help our climate change marathon's fundraiser.", + choices = { + "Sure": {"money": -40, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + Template( + "The marathon was phenomenal! Thanks for your support.", + choices = {"Great!": {"money": +60}}, + condition = lambda state: state.loyalty>110 and state.money<1200, + ), + ]), + StageGroup(4, [ + Template("Dear leader of {nation_name}, please help our climate change marathon's fundraiser.", + choices = { + "Sure": {"money": -80, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + Template( + "The marathon was phenomenal! Thanks for your support.", + choices = {"Great!": {"money": +120}}, + condition = lambda state: state.loyalty>130 and state.money<2400, + ), + ]), + StageGroup(5, [ + Template("Dear leader of {nation_name}, please help our climate change marathon's fundraiser.", + choices = { + "Sure": {"money": -160, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + Template( + "The marathon was phenomenal! Thanks for your support.", + choices = {"Great!": {"money": +240}}, + condition = lambda state: state.loyalty>150 and state.money<480, + ), + ]), +]) + + +Actor("Aura the Activist", "url_here",[ + StageGroup(1, [ + Template( + "Kind leader of {nation_name}! Please donate to our LGBTQ+ movement.", + choices={ + "Sure": {"money": -10, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + Template( + "Rights are Rights! You did the right thing.", + choices = {"Great!": {"money": +15}}, + condition = lambda state: state.loyalty>70 and state.money<300, + ), + ]), + StageGroup(2, [ + Template("Kind leader of {nation_name}! Please donate to our LGBTQ+ movement.", + choices = { + "Sure": {"money": -20, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + Template( + "Rights are Rights! You did the right thing.", + choices = {"Great!": {"money": +30}}, + condition = lambda state: state.loyalty>90 and state.money<600, + ), + ]), + StageGroup(3, [ + Template("Kind leader of {nation_name}! Please donate to our LGBTQ+ movement.", + choices = { + "Sure": {"money": -40, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + Template( + "Rights are Rights! You did the right thing.", + choices = {"Great!": {"money": +60}}, + condition = lambda state: state.loyalty>110 and state.money<1200, + ), + ]), + StageGroup(4, [ + Template("Kind leader of {nation_name}! Please donate to our LGBTQ+ movement.", + choices = { + "Sure": {"money": -80, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + Template( + "Rights are Rights! You did the right thing.", + choices = {"Great!": {"money": +120}}, + condition = lambda state: state.loyalty>130 and state.money<2400, + ), + ]), + StageGroup(5, [ + Template("Kind leader of {nation_name}! Please donate to our LGBTQ+ movement.", + choices = { + "Sure": {"money": -160, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + Template( + "Rights are Rights! You did the right thing.", + choices = {"Great!": {"money": +240}}, + condition = lambda state: state.loyalty>150 and state.money<480, ), ]), ]) + + +Actor("Sheela the Singer", "url_here",[ + StageGroup(1, [ + Template( + "Blessed leader of {nation_name}, please fund my public concert!", + choices={ + "Sure": {"money": -10, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + Template( + "The concert was on fire! Thank you so much.", + choices = {"Great!": {"money": +15}}, + condition = lambda state: state.loyalty>70 and state.money<300, + ), + ]), + StageGroup(2, [ + Template("Blessed leader of {nation_name}, please fund my public concert!", + choices = { + "Sure": {"money": -20, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + Template( + "The concert was on firee! Thank you so much.", + choices = {"Great!": {"money": +30}}, + condition = lambda state: state.loyalty>90 and state.money<600, + ), + ]), + StageGroup(3, [ + Template("Blessed leader of {nation_name}, please fund my public concert!", + choices = { + "Sure": {"money": -40, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + Template( + "The concert was on fireee! Thank you so much.", + choices = {"Great!": {"money": +60}}, + condition = lambda state: state.loyalty>110 and state.money<1200, + ), + ]), + StageGroup(4, [ + Template("Blessed leader of {nation_name}, please fund my public concert!", + choices = { + "Sure": {"money": -80, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + Template( + "The concert was on fireeee! Thank you so much.", + choices = {"Great!": {"money": +120}}, + condition = lambda state: state.loyalty>130 and state.money<2400, + ), + ]), + StageGroup(5, [ + Template("Blessed leader of {nation_name}, please fund my public concert!", + choices = { + "Sure": {"money": -160, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + Template( + "The concert was on fireeeee! Thank you so much.", + choices = {"Great!": {"money": +240}}, + condition = lambda state: state.loyalty>150 and state.money<480, + ), + ]), +]) + + +Actor("Craig the Contractor", "url_here",[ + StageGroup(1, [ + Template( + "Ruler of {nation_name}, the roads need mending following the earthquake.", + choices={ + "Sure": {"money": -10, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + Template( + "", + choices = {"Great!": {"money": +15}}, + condition = lambda state: state.loyalty>70 and state.money<300, + ), + ]), + StageGroup(2, [ + Template("Ruler of {nation_name}, the houses need patching following the tornado.", + choices = { + "Sure": {"money": -20, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + Template( + "Great! Looks like a tornado never hit this place.", + choices = {"Great!": {"money": +30}}, + condition = lambda state: state.loyalty>90 and state.money<600, + ), + ]), + StageGroup(3, [ + Template("Ruler of {nation_name}, the drains need to be declogged following the flood.", + choices = { + "Sure": {"money": -40, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + Template( + "Great! Looks like a flood never hit this place.", + choices = {"Great!": {"money": +60}}, + condition = lambda state: state.loyalty>110 and state.money<1200, + ), + ]), + StageGroup(4, [ + Template("Ruler of {nation_name}, the electric poles need fixing following the thunderstorm.", + choices = { + "Sure": {"money": -80, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + Template( + "Great! Looks like a thunderstorm never hit this place.", + choices = {"Great!": {"money": +120}}, + condition = lambda state: state.loyalty>130 and state.money<2400, + ), + ]), + StageGroup(5, [ + Template("Ruler of {nation_name}, the city needs to be restored following the tsunami.", + choices = { + "Sure": {"money": -160, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + Template( + "Great! Looks like a tsunami never hit this place.", + choices = {"Great!": {"money": +240}}, + condition = lambda state: state.loyalty>150 and state.money<480, + ), + ]), +]) + + +Actor("Gary the General", "url_here",[ + StageGroup(1, [ + Template( + "Sir, ruler of {nation_name}! The army needs vests Sir!", + choices={ + "Sure": {"money": -10, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + Template( + "Thanks Sir! Now our army is stronger than ever Sir!", + choices = {"Great!": {"money": +15}}, + condition = lambda state: state.loyalty>70 and state.money<300, + ), + ]), + StageGroup(2, [ + Template("Sir, ruler of {nation_name}! The army needs equipment Sir!", + choices = { + "Sure": {"money": -20, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + Template( + "Thanks Sir! Now our army is stronger than ever Sir!", + choices = {"Great!": {"money": +30}}, + condition = lambda state: state.loyalty>90 and state.money<600, + ), + ]), + StageGroup(3, [ + Template("Sir, ruler of {nation_name}! The army needs weapons Sir!", + choices = { + "Sure": {"money": -40, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + Template( + "Thanks Sir! Now our army is stronger than ever Sir!", + choices = {"Great!": {"money": +60}}, + condition = lambda state: state.loyalty>110 and state.money<1200, + ), + ]), + StageGroup(4, [ + Template("Sir, ruler of {nation_name}! The army needs tanks Sir!", + choices = { + "Sure": {"money": -80, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + Template( + "Thanks Sir! Now our army is stronger than ever Sir!", + choices = {"Great!": {"money": +120}}, + condition = lambda state: state.loyalty>130 and state.money<2400, + ), + ]), + StageGroup(5, [ + Template("Sir, ruler of {nation_name}! The army needs helicopters Sir!", + choices = { + "Sure": {"money": -160, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + Template( + "Thanks Sir! Now our army is stronger than ever Sir!", + choices = {"Great!": {"money": +240}}, + condition = lambda state: state.loyalty>150 and state.money<480, + ), + ]), +]) + + +Actor("Sandra the Spy", "url_here",[ + StageGroup(1, [ + Template( + "ruler of {nation_name}, spy squad needs supplies", + choices={ + "Sure": {"money": -10, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + Template( + "The supplies helped our mission", + choices = {"Great!": {"money": +15}}, + condition = lambda state: state.loyalty>70 and state.money<300, + ), + ]), + StageGroup(2, [ + Template("ruler of {nation_name}, spy squad needs supplies", + choices = { + "Sure": {"money": -20, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + Template( + "The supplies helped our mission", + choices = {"Great!": {"money": +30}}, + condition = lambda state: state.loyalty>90 and state.money<600, + ), + ]), + StageGroup(3, [ + Template("ruler of {nation_name}, spy squad needs supplies", + choices = { + "Sure": {"money": -40, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + Template( + "The supplies helped our mission", + choices = {"Great!": {"money": +60}}, + condition = lambda state: state.loyalty>110 and state.money<1200, + ), + ]), + StageGroup(4, [ + Template("ruler of {nation_name}, spy squad needs supplies", + choices = { + "Sure": {"money": -80, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + Template( + "The supplies helped our mission", + choices = {"Great!": {"money": +120}}, + condition = lambda state: state.loyalty>130 and state.money<2400, + ), + ]), + StageGroup(5, [ + Template("ruler of {nation_name}, spy squad needs supplies", + choices = { + "Sure": {"money": -160, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + Template( + "The supplies helped our mission", + choices = {"Great!": {"money": +240}}, + condition = lambda state: state.loyalty>150 and state.money<480, + ), + ]), +]) + + +Actor("Dave the Doctor", "url_here",[ + StageGroup(1, [ + Template( + "Great ruler of {nation_name}, we need money to cure those with the flu!", + choices={ + "Sure": {"money": -10, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + Template( + "", + choices = {"Great!": {"money": +15}}, + condition = lambda state: state.loyalty>70 and state.money<300, + ), + ]), + StageGroup(2, [ + Template("Great ruler of {nation_name}, we need money to cure those with Alzheimer's!", + choices = { + "Sure": {"money": -20, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + Template( + "", + choices = {"Great!": {"money": +30}}, + condition = lambda state: state.loyalty>90 and state.money<600, + ), + ]), + StageGroup(3, [ + Template("Great ruler of {nation_name}, we need money to cure those with Parkinson's!", + choices = { + "Sure": {"money": -40, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + Template( + "", + choices = {"Great!": {"money": +60}}, + condition = lambda state: state.loyalty>110 and state.money<1200, + ), + ]), + StageGroup(4, [ + Template("Great ruler of {nation_name}, we need money to cure those with AIDS!", + choices = { + "Sure": {"money": -80, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + Template( + "", + choices = {"Great!": {"money": +120}}, + condition = lambda state: state.loyalty>130 and state.money<2400, + ), + ]), + StageGroup(5, [ + Template("Great ruler of {nation_name}, we need money to cure those with Hepatitis!", + choices = { + "Sure": {"money": -160, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + Template( + "", + choices = {"Great!": {"money": +240}}, + condition = lambda state: state.loyalty>150 and state.money<480, + ), + ]), +]) + + +Actor("Elsa the Engineer", "url_here",[ + StageGroup(1, [ + Template( + "Ruler of {nation_name}, spare some money to design the army's vests!", + choices={ + "Sure": {"money": -10, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + Template( + "The vests are bulletproof! Thanks.", + choices = {"Great!": {"money": +15}}, + condition = lambda state: state.loyalty>70 and state.money<300, + ), + ]), + StageGroup(2, [ + Template("Ruler of {nation_name}, spare some money to design the army's equipment!", + choices = { + "Sure": {"money": -20, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + Template( + "The equipment is top-notch. Thanks.", + choices = {"Great!": {"money": +30}}, + condition = lambda state: state.loyalty>90 and state.money<600, + ), + ]), + StageGroup(3, [ + Template("Ruler of {nation_name}, spare some money to design the army's weapons!", + choices = { + "Sure": {"money": -40, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + Template( + "The weapons are deadly! Thanks.", + choices = {"Great!": {"money": +60}}, + condition = lambda state: state.loyalty>110 and state.money<1200, + ), + ]), + StageGroup(4, [ + Template("Ruler of {nation_name}, spare some money to design the army's tanks!", + choices = { + "Sure": {"money": -80, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + Template( + "The tanks are tanky! Thanks.", + choices = {"Great!": {"money": +120}}, + condition = lambda state: state.loyalty>130 and state.money<2400, + ), + ]), + StageGroup(5, [ + Template("Ruler of {nation_name}, spare some money to design the army's helicopters!", + choices = { + "Sure": {"money": -160, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + Template( + "The helicopters are fast! Thanks.", + choices = {"Great!": {"money": +240}}, + condition = lambda state: state.loyalty>150 and state.money<480, + ), + ]), +]) + + From d2bf55ef3acd060d8cb62461491679ec4c8cbfdf Mon Sep 17 00:00:00 2001 From: Maheshkumar Date: Sun, 28 Jul 2024 01:23:24 +0530 Subject: [PATCH 075/116] Fix spacing and default values --- src/game_interaction.py | 3 +-- src/player.py | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/game_interaction.py b/src/game_interaction.py index 550477e..ce502c7 100644 --- a/src/game_interaction.py +++ b/src/game_interaction.py @@ -44,7 +44,7 @@ def add_player(self, player_id: int, game: Game) -> None: def remove_player(self, player_id: int) -> None: """Remove a player game mapping.""" - if player_id in self. players: + if player_id in self.players: del self.players[player_id] def remove_game(self, game_id: int) -> None: @@ -213,6 +213,5 @@ async def on_component(self, event: Component) -> None: player = game.players[ctx.user.id] player.state.apply(consequences) player.last_activity_time = time.time() - print(player.state) await ctx.edit_origin(content=f"Your response ({ctx.component.label}) saved.", components=[]) del game.player_component_choice_mapping[ctx.custom_id] diff --git a/src/player.py b/src/player.py index 901f702..522c834 100644 --- a/src/player.py +++ b/src/player.py @@ -17,7 +17,7 @@ class PlayerState: money: float = 100 # How loyal people feel to the current government that you have created - loyalty: float = 10 + loyalty: float = 50 # How vulnerable is the country from external threats security: float = 50 From 3ed01581fe34d0df53b3e7c837d18f42544f5ffa Mon Sep 17 00:00:00 2001 From: Sapient44 Date: Sun, 28 Jul 2024 06:51:02 +0530 Subject: [PATCH 076/116] Changed the format of Actors --- src/characters/Andy_the_athelete_chr.py | 46 ++ src/characters/Aura_the_activist_chr.py | 46 ++ src/characters/Criag_the_contractor_chr.py | 46 ++ src/characters/Dave_the_doctor_chr.py | 46 ++ src/characters/Else_the_engineer_chr.py | 45 ++ src/characters/Fred_the_Farmer_chr.py | 51 ++ src/characters/Gary_the_general_chr.py | 46 ++ src/characters/Sam_the_scientist_chr.py | 49 ++ src/characters/Sandra_the_spy_chr.py | 33 + src/characters/Sheela_the_singer_chr.py | 46 ++ src/characters/Wong_the_worker_chr.py | 46 ++ src/characters/example_chr.py | 786 --------------------- 12 files changed, 500 insertions(+), 786 deletions(-) create mode 100644 src/characters/Andy_the_athelete_chr.py create mode 100644 src/characters/Aura_the_activist_chr.py create mode 100644 src/characters/Criag_the_contractor_chr.py create mode 100644 src/characters/Dave_the_doctor_chr.py create mode 100644 src/characters/Else_the_engineer_chr.py create mode 100644 src/characters/Fred_the_Farmer_chr.py create mode 100644 src/characters/Gary_the_general_chr.py create mode 100644 src/characters/Sam_the_scientist_chr.py create mode 100644 src/characters/Sandra_the_spy_chr.py create mode 100644 src/characters/Sheela_the_singer_chr.py create mode 100644 src/characters/Wong_the_worker_chr.py delete mode 100644 src/characters/example_chr.py diff --git a/src/characters/Andy_the_athelete_chr.py b/src/characters/Andy_the_athelete_chr.py new file mode 100644 index 0000000..b0e691d --- /dev/null +++ b/src/characters/Andy_the_athelete_chr.py @@ -0,0 +1,46 @@ +from src.templating import Actor, StageGroup +from src.templating import ChoiceTemplate as t + +Actor("Andy the Athlete", "url_here",[ + StageGroup(1, [ + t( + "Dear leader of {nation_name}, please help our climate change marathon's fundraiser.", + choices={ + "Sure": {"money": -10, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + t( + "The marathon was phenomenal! Thanks for your support.", + choices = {"Great!": {"money": +15}}, + condition = lambda state: state.loyalty>70 and state.money<300, + ), + ]), + StageGroup(2, [ + t("Dear leader of {nation_name}, please help our climate change marathon's fundraiser.", + choices = { + "Sure": {"money": -20, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + t( + "The marathon was phenomenal! Thanks for your support.", + choices = {"Great!": {"money": +30}}, + condition = lambda state: state.loyalty>90 and state.money<600, + ), + ]), + StageGroup(3, [ + t("Dear leader of {nation_name}, please help our climate change marathon's fundraiser.", + choices = { + "Sure": {"money": -40, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + t( + "The marathon was phenomenal! Thanks for your support.", + choices = {"Great!": {"money": +60}}, + condition = lambda state: state.loyalty>110 and state.money<1200, + ), + ]), +]) + diff --git a/src/characters/Aura_the_activist_chr.py b/src/characters/Aura_the_activist_chr.py new file mode 100644 index 0000000..23a227a --- /dev/null +++ b/src/characters/Aura_the_activist_chr.py @@ -0,0 +1,46 @@ +from src.templating import Actor, StageGroup +from src.templating import ChoiceTemplate as t + +Actor("Aura the Activist", "url_here",[ + StageGroup(1, [ + t( + "Kind leader of {nation_name}! Please donate to our LGBTQ+ movement.", + choices={ + "Sure": {"money": -10, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + t( + "Rights are Rights! You did the right thing.", + choices = {"Great!": {"money": +15}}, + condition = lambda state: state.loyalty>70 and state.money<300, + ), + ]), + StageGroup(2, [ + t("Kind leader of {nation_name}! Please donate to our LGBTQ+ movement.", + choices = { + "Sure": {"money": -20, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + t( + "Rights are Rights! You did the right thing.", + choices = {"Great!": {"money": +30}}, + condition = lambda state: state.loyalty>90 and state.money<600, + ), + ]), + StageGroup(3, [ + t("Kind leader of {nation_name}! Please donate to our LGBTQ+ movement.", + choices = { + "Sure": {"money": -40, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + t( + "Rights are Rights! You did the right thing.", + choices = {"Great!": {"money": +60}}, + condition = lambda state: state.loyalty>110 and state.money<1200, + ), + ]), +]) + diff --git a/src/characters/Criag_the_contractor_chr.py b/src/characters/Criag_the_contractor_chr.py new file mode 100644 index 0000000..86a5315 --- /dev/null +++ b/src/characters/Criag_the_contractor_chr.py @@ -0,0 +1,46 @@ +from src.templating import Actor, StageGroup +from src.templating import ChoiceTemplate as t + +Actor("Craig the Contractor", "url_here",[ + StageGroup(1, [ + t( + "Ruler of {nation_name}, the roads need mending following the earthquake.", + choices={ + "Sure": {"money": -10, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + t( + "", + choices = {"Great!": {"money": +15}}, + condition = lambda state: state.loyalty>70 and state.money<300, + ), + ]), + StageGroup(2, [ + t("Ruler of {nation_name}, the houses need patching following the tornado.", + choices = { + "Sure": {"money": -20, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + t( + "Great! Looks like a tornado never hit this place.", + choices = {"Great!": {"money": +30}}, + condition = lambda state: state.loyalty>90 and state.money<600, + ), + ]), + StageGroup(3, [ + t("Ruler of {nation_name}, the drains need to be declogged following the flood.", + choices = { + "Sure": {"money": -40, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + t( + "Great! Looks like a flood never hit this place.", + choices = {"Great!": {"money": +60}}, + condition = lambda state: state.loyalty>110 and state.money<1200, + ), + ]), +]) + diff --git a/src/characters/Dave_the_doctor_chr.py b/src/characters/Dave_the_doctor_chr.py new file mode 100644 index 0000000..0544dee --- /dev/null +++ b/src/characters/Dave_the_doctor_chr.py @@ -0,0 +1,46 @@ +from src.templating import Actor, StageGroup +from src.templating import ChoiceTemplate as t + +Actor("Dave the Doctor", "url_here",[ + StageGroup(1, [ + t( + "Great ruler of {nation_name}, we need money to cure those with the flu!", + choices={ + "Sure": {"money": -10, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + t( + "", + choices = {"Great!": {"money": +15}}, + condition = lambda state: state.loyalty>70 and state.money<300, + ), + ]), + StageGroup(2, [ + t("Great ruler of {nation_name}, we need money to cure those with Alzheimer's!", + choices = { + "Sure": {"money": -20, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + t( + "", + choices = {"Great!": {"money": +30}}, + condition = lambda state: state.loyalty>90 and state.money<600, + ), + ]), + StageGroup(3, [ + t("Great ruler of {nation_name}, we need money to cure those with Parkinson's!", + choices = { + "Sure": {"money": -40, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + t( + "", + choices = {"Great!": {"money": +60}}, + condition = lambda state: state.loyalty>110 and state.money<1200, + ), + ]), +]) + diff --git a/src/characters/Else_the_engineer_chr.py b/src/characters/Else_the_engineer_chr.py new file mode 100644 index 0000000..4c701d7 --- /dev/null +++ b/src/characters/Else_the_engineer_chr.py @@ -0,0 +1,45 @@ +from src.templating import Actor, StageGroup +from src.templating import ChoiceTemplate as t + +Actor("Elsa the Engineer", "url_here",[ + StageGroup(1, [ + t( + "Ruler of {nation_name}, spare some money to design the army's vests!", + choices={ + "Sure": {"money": -10, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + t( + "The vests are bulletproof! Thanks.", + choices = {"Great!": {"money": +15}}, + condition = lambda state: state.loyalty>70 and state.money<300, + ), + ]), + StageGroup(2, [ + t("Ruler of {nation_name}, spare some money to design the army's equipment!", + choices = { + "Sure": {"money": -20, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + t( + "The equipment is top-notch. Thanks.", + choices = {"Great!": {"money": +30}}, + condition = lambda state: state.loyalty>90 and state.money<600, + ), + ]), + StageGroup(3, [ + t("Ruler of {nation_name}, spare some money to design the army's weapons!", + choices = { + "Sure": {"money": -40, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + t( + "The weapons are deadly! Thanks.", + choices = {"Great!": {"money": +60}}, + condition = lambda state: state.loyalty>110 and state.money<1200, + ), + ]), +]) diff --git a/src/characters/Fred_the_Farmer_chr.py b/src/characters/Fred_the_Farmer_chr.py new file mode 100644 index 0000000..050590e --- /dev/null +++ b/src/characters/Fred_the_Farmer_chr.py @@ -0,0 +1,51 @@ +from src.templating import Actor, StageGroup +from src.templating import ChoiceTemplate as t + +# fmt: off + +# fmt: off +Actor("Fred the Farmer", "url_here",[ + StageGroup(1, [ + t( + "Hello, leader of {nation_name}. Can you spare some money for our new farming equipment?", + choices={ + "Sure": {"money": -10, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + t( + "Things are great, we managed to grow even more crops than expected.\ + Thank you for your help, leader! \n Here, have this small gift from our community", + choices = {"Thanks!": {"money": +15}}, + condition = lambda state: state.loyalty>70 and state.money<300, + ), + ]), + StageGroup(2, [ + t("Hello, leader of {nation_name}. Can you spare some money for our new farming equipment?", + choices = { + "Sure": {"money": -20, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + t( + "Things are great, we managed to grow even more crops than expected.\ + Thank you for your help, leader! \n Here, have this small gift from our community", + choices = {"Thanks!": {"money": +30}}, + condition = lambda state: state.loyalty>90 and state.money<600, + ), + ]), + StageGroup(3, [ + t("Hello, leader of {nation_name}. Can you spare some money for our new farming equipment?", + choices = { + "Sure": {"money": -40, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + t( + "Things are great, we managed to grow even more crops than expected.\ + Thank you for your help, leader! \n Here, have this small gift from our community", + choices = {"Thanks!": {"money": +60}}, + condition = lambda state: state.loyalty>110 and state.money<1200, + ), + ]), +]) diff --git a/src/characters/Gary_the_general_chr.py b/src/characters/Gary_the_general_chr.py new file mode 100644 index 0000000..45345e6 --- /dev/null +++ b/src/characters/Gary_the_general_chr.py @@ -0,0 +1,46 @@ +from src.templating import Actor, StageGroup +from src.templating import ChoiceTemplate as t + +Actor("Gary the General", "url_here",[ + StageGroup(1, [ + t( + "Sir, ruler of {nation_name}! The army needs vests Sir!", + choices={ + "Sure": {"money": -10, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + t( + "Thanks Sir! Now our army is stronger than ever Sir!", + choices = {"Great!": {"money": +15}}, + condition = lambda state: state.loyalty>70 and state.money<300, + ), + ]), + StageGroup(2, [ + t("Sir, ruler of {nation_name}! The army needs equipment Sir!", + choices = { + "Sure": {"money": -20, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + t( + "Thanks Sir! Now our army is stronger than ever Sir!", + choices = {"Great!": {"money": +30}}, + condition = lambda state: state.loyalty>90 and state.money<600, + ), + ]), + StageGroup(3, [ + t("Sir, ruler of {nation_name}! The army needs weapons Sir!", + choices = { + "Sure": {"money": -40, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + t( + "Thanks Sir! Now our army is stronger than ever Sir!", + choices = {"Great!": {"money": +60}}, + condition = lambda state: state.loyalty>110 and state.money<1200, + ), + ]), +]) + diff --git a/src/characters/Sam_the_scientist_chr.py b/src/characters/Sam_the_scientist_chr.py new file mode 100644 index 0000000..efd89cd --- /dev/null +++ b/src/characters/Sam_the_scientist_chr.py @@ -0,0 +1,49 @@ +from src.templating import Actor, StageGroup +from src.templating import ChoiceTemplate as t + +Actor("Sam the Scientist", "url_here",[ + StageGroup(1, [ + t( + "Greetings, esteemed leader of {nation_name}. May I request your consideration for a financial contribution\ + towards our research?", + choices={ + "Sure": {"money": -10, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + t( + "Alzhemier's will be cured soon! Thanks for your contribution", + choices = {"Great!": {"money": +15}}, + condition = lambda state: state.loyalty>70 and state.money<300, + ), + ]), + StageGroup(2, [ + t("Greetings, esteemed leader of {nation_name}. May I request your consideration for a financial contribution\ + towards our research?", + choices = { + "Sure": {"money": -20, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + t( + "Parkinson's will be cured soon! Thanks for your contribution", + choices = {"Great!": {"money": +30}}, + condition = lambda state: state.loyalty>90 and state.money<600, + ), + ]), + StageGroup(3, [ + t("Greetings, esteemed leader of {nation_name}. May I request your consideration for a financial contribution\ + towards our research?", + choices = { + "Sure": {"money": -40, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + t( + "AIDS will be cured soon! Thanks for your contribution", + choices = {"Great!": {"money": +60}}, + condition = lambda state: state.loyalty>110 and state.money<1200, + ), + ]), +]) + diff --git a/src/characters/Sandra_the_spy_chr.py b/src/characters/Sandra_the_spy_chr.py new file mode 100644 index 0000000..a0bd542 --- /dev/null +++ b/src/characters/Sandra_the_spy_chr.py @@ -0,0 +1,33 @@ +from src.templating import Actor, StageGroup +from src.templating import ChoiceTemplate as t + +Actor("Sandra the Spy", "url_here",[ + StageGroup(1, [ + t( + "ruler of {nation_name}, spy squad needs supplies", + choices={ + "Sure": {"money": -10, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + t( + "The supplies helped our mission", + choices = {"Great!": {"money": +15}}, + condition = lambda state: state.loyalty>70 and state.money<300, + ), + ]), + StageGroup(2, [ + t("ruler of {nation_name}, spy squad needs supplies", + choices = { + "Sure": {"money": -20, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + t( + "The supplies helped our mission", + choices = {"Great!": {"money": +30}}, + condition = lambda state: state.loyalty>90 and state.money<600, + ), + ]), +]) + diff --git a/src/characters/Sheela_the_singer_chr.py b/src/characters/Sheela_the_singer_chr.py new file mode 100644 index 0000000..a66e835 --- /dev/null +++ b/src/characters/Sheela_the_singer_chr.py @@ -0,0 +1,46 @@ +from src.templating import Actor, StageGroup +from src.templating import ChoiceTemplate as t + +Actor("Sheela the Singer", "url_here",[ + StageGroup(1, [ + t( + "Blessed leader of {nation_name}, please fund my public concert!", + choices={ + "Sure": {"money": -10, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + t( + "The concert was on fire! Thank you so much.", + choices = {"Great!": {"money": +15}}, + condition = lambda state: state.loyalty>70 and state.money<300, + ), + ]), + StageGroup(2, [ + t("Blessed leader of {nation_name}, please fund my public concert!", + choices = { + "Sure": {"money": -20, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + t( + "The concert was on firee! Thank you so much.", + choices = {"Great!": {"money": +30}}, + condition = lambda state: state.loyalty>90 and state.money<600, + ), + ]), + StageGroup(3, [ + t("Blessed leader of {nation_name}, please fund my public concert!", + choices = { + "Sure": {"money": -40, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + t( + "The concert was on fireee! Thank you so much.", + choices = {"Great!": {"money": +60}}, + condition = lambda state: state.loyalty>110 and state.money<1200, + ), + ]), +]) + diff --git a/src/characters/Wong_the_worker_chr.py b/src/characters/Wong_the_worker_chr.py new file mode 100644 index 0000000..ce474e9 --- /dev/null +++ b/src/characters/Wong_the_worker_chr.py @@ -0,0 +1,46 @@ +from src.templating import Actor, StageGroup +from src.templating import ChoiceTemplate as t + +Actor("Wong the Worker", "url_here",[ + StageGroup(1, [ + t( + "O great leader of {nation_name}. May thou be kind enough to raise our minimum wage", + choices={ + "Sure": {"money": -10, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + t( + "Thank you! We have been extremely productive and produced so much more goods.", + choices = {"Great!": {"money": +15}}, + condition = lambda state: state.loyalty>70 and state.money<300, + ), + ]), + StageGroup(2, [ + t("O great leader of {nation_name}. May thou be kind enough to raise our minimum wage", + choices = { + "Sure": {"money": -20, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + t( + "Thank you! We have been extremely productive and produced so much more goods.", + choices = {"Great!": {"money": +30}}, + condition = lambda state: state.loyalty>90 and state.money<600, + ), + ]), + StageGroup(3, [ + t("O great leader of {nation_name}. May thou be kind enough to raise our minimum wage", + choices = { + "Sure": {"money": -40, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + t( + "Thank you! We have been extremely productive and produced so much more goods.", + choices = {"Great!": {"money": +60}}, + condition = lambda state: state.loyalty>110 and state.money<1200, + ), + ]), +]) + diff --git a/src/characters/example_chr.py b/src/characters/example_chr.py deleted file mode 100644 index 738777a..0000000 --- a/src/characters/example_chr.py +++ /dev/null @@ -1,786 +0,0 @@ -from src.templating import Actor, StageGroup -from src.templating import ChoiceTemplate as t - -# fmt: off -from models import Actor, StageGroup, Template - -# fmt: off -Actor("Fred the Farmer", "url_here",[ - StageGroup(1, [ - Template( - "Hello, leader of {nation_name}. Can you spare some money for our new farming equipment?", - choices={ - "Sure": {"money": -10, "loyalty": +5}, - "Nope": {"loyalty": -5}, - }, - ), - Template( - "Things are great, we managed to grow even more crops than expected.\ - Thank you for your help, leader! \n Here, have this small gift from our community", - choices = {"Thanks!": {"money": +15}}, - condition = lambda state: state.loyalty>70 and state.money<300, - ), - ]), - StageGroup(2, [ - Template("Hello, leader of {nation_name}. Can you spare some money for our new farming equipment?", - choices = { - "Sure": {"money": -20, "loyalty": +5}, - "Nope": {"loyalty": -5}, - }, - ), - Template( - "Things are great, we managed to grow even more crops than expected.\ - Thank you for your help, leader! \n Here, have this small gift from our community", - choices = {"Thanks!": {"money": +30}}, - condition = lambda state: state.loyalty>90 and state.money<600, - ), - ]), - StageGroup(3, [ - Template("Hello, leader of {nation_name}. Can you spare some money for our new farming equipment?", - choices = { - "Sure": {"money": -40, "loyalty": +5}, - "Nope": {"loyalty": -5}, - }, - ), - Template( - "Things are great, we managed to grow even more crops than expected.\ - Thank you for your help, leader! \n Here, have this small gift from our community", - choices = {"Thanks!": {"money": +60}}, - condition = lambda state: state.loyalty>110 and state.money<1200, - ), - ]), - StageGroup(4, [ - Template("Hello, leader of {nation_name}. Can you spare some money for our new farming equipment?", - choices = { - "Sure": {"money": -80, "loyalty": +5}, - "Nope": {"loyalty": -5}, - }, - ), - Template( - "Things are great, we managed to grow even more crops than expected.\ - Thank you for your help, leader! \n Here, have this small gift from our community", - choices = {"Thanks!": {"money": +120}}, - condition = lambda state: state.loyalty>130 and state.money<2400, - ), - ]), - StageGroup(5, [ - Template("Hello, leader of {nation_name}. Can you spare some money for our new farming equipment?", - choices = { - "Sure": {"money": -160, "loyalty": +5}, - "Nope": {"loyalty": -5}, - }, - ), - Template( - "Things are great, we managed to grow even more crops than expected.\ - Thank you for your help, leader! \n Here, have this small gift from our community", - choices = {"Thanks!": {"money": +240}}, - condition = lambda state: state.loyalty>150 and state.money<480, - ), - ]), -]) - -Actor("Wong the Worker", "url_here",[ - StageGroup(1, [ - Template( - "O great leader of {nation_name}. May thou be kind enough to raise our minimum wage", - choices={ - "Sure": {"money": -10, "loyalty": +5}, - "Nope": {"loyalty": -5}, - }, - ), - Template( - "Thank you! We have been extremely productive and produced so much more goods.", - choices = {"Great!": {"money": +15}}, - condition = lambda state: state.loyalty>70 and state.money<300, - ), - ]), - StageGroup(2, [ - Template("O great leader of {nation_name}. May thou be kind enough to raise our minimum wage", - choices = { - "Sure": {"money": -20, "loyalty": +5}, - "Nope": {"loyalty": -5}, - }, - ), - Template( - "Thank you! We have been extremely productive and produced so much more goods.", - choices = {"Great!": {"money": +30}}, - condition = lambda state: state.loyalty>90 and state.money<600, - ), - ]), - StageGroup(3, [ - Template("O great leader of {nation_name}. May thou be kind enough to raise our minimum wage", - choices = { - "Sure": {"money": -40, "loyalty": +5}, - "Nope": {"loyalty": -5}, - }, - ), - Template( - "Thank you! We have been extremely productive and produced so much more goods.", - choices = {"Great!": {"money": +60}}, - condition = lambda state: state.loyalty>110 and state.money<1200, - ), - ]), - StageGroup(4, [ - Template("O great leader of {nation_name}. May thou be kind enough to raise our minimum wage", - choices = { - "Sure": {"money": -80, "loyalty": +5}, - "Nope": {"loyalty": -5}, - }, - ), - Template( - "Thank you! We have been extremely productive and produced so much more goods.", - choices = {"Great!": {"money": +120}}, - condition = lambda state: state.loyalty>130 and state.money<2400, - ), - ]), - StageGroup(5, [ - Template("O great leader of {nation_name}. May thou be kind enough to raise our minimum wage", - choices = { - "Sure": {"money": -160, "loyalty": +5}, - "Nope": {"loyalty": -5}, - }, - ), - Template( - "Thank you! We have been extremely productive and produced so much more goods.", - choices = {"Great!": {"money": +240}}, - condition = lambda state: state.loyalty>150 and state.money<480, - ), - ]), -]) - - -Actor("Sam the Scientist", "url_here",[ - StageGroup(1, [ - Template( - "Greetings, esteemed leader of {nation_name}. May I request your consideration for a financial contribution\ - towards our research?", - choices={ - "Sure": {"money": -10, "loyalty": +5}, - "Nope": {"loyalty": -5}, - }, - ), - Template( - "Alzhemier's will be cured soon! Thanks for your contribution", - choices = {"Great!": {"money": +15}}, - condition = lambda state: state.loyalty>70 and state.money<300, - ), - ]), - StageGroup(2, [ - Template("Greetings, esteemed leader of {nation_name}. May I request your consideration for a financial contribution\ - towards our research?", - choices = { - "Sure": {"money": -20, "loyalty": +5}, - "Nope": {"loyalty": -5}, - }, - ), - Template( - "Parkinson's will be cured soon! Thanks for your contribution", - choices = {"Great!": {"money": +30}}, - condition = lambda state: state.loyalty>90 and state.money<600, - ), - ]), - StageGroup(3, [ - Template("Greetings, esteemed leader of {nation_name}. May I request your consideration for a financial contribution\ - towards our research?", - choices = { - "Sure": {"money": -40, "loyalty": +5}, - "Nope": {"loyalty": -5}, - }, - ), - Template( - "AIDS will be cured soon! Thanks for your contribution", - choices = {"Great!": {"money": +60}}, - condition = lambda state: state.loyalty>110 and state.money<1200, - ), - ]), - StageGroup(4, [ - Template("Greetings, esteemed leader of {nation_name}. May I request your consideration for a financial contribution\ - towards our research?", - choices = { - "Sure": {"money": -80, "loyalty": +5}, - "Nope": {"loyalty": -5}, - }, - ), - Template( - "Hepatitis will be cured soon! Thanks for your contribution", - choices = {"Great!": {"money": +120}}, - condition = lambda state: state.loyalty>130 and state.money<2400, - ), - ]), - StageGroup(5, [ - Template("Greetings, esteemed leader of {nation_name}. May I request your consideration for a financial contribution\ - towards our research?", - choices = { - "Sure": {"money": -160, "loyalty": +5}, - "Nope": {"loyalty": -5}, - }, - ), - Template( - "CANCER will be cured soon! Thanks for your contribution", - choices = {"Great!": {"money": +240}}, - condition = lambda state: state.loyalty>150 and state.money<480, - ), - ]), -]) - - -Actor("Andy the Athlete", "url_here",[ - StageGroup(1, [ - Template( - "Dear leader of {nation_name}, please help our climate change marathon's fundraiser.", - choices={ - "Sure": {"money": -10, "loyalty": +5}, - "Nope": {"loyalty": -5}, - }, - ), - Template( - "The marathon was phenomenal! Thanks for your support.", - choices = {"Great!": {"money": +15}}, - condition = lambda state: state.loyalty>70 and state.money<300, - ), - ]), - StageGroup(2, [ - Template("Dear leader of {nation_name}, please help our climate change marathon's fundraiser.", - choices = { - "Sure": {"money": -20, "loyalty": +5}, - "Nope": {"loyalty": -5}, - }, - ), - Template( - "The marathon was phenomenal! Thanks for your support.", - choices = {"Great!": {"money": +30}}, - condition = lambda state: state.loyalty>90 and state.money<600, - ), - ]), - StageGroup(3, [ - Template("Dear leader of {nation_name}, please help our climate change marathon's fundraiser.", - choices = { - "Sure": {"money": -40, "loyalty": +5}, - "Nope": {"loyalty": -5}, - }, - ), - Template( - "The marathon was phenomenal! Thanks for your support.", - choices = {"Great!": {"money": +60}}, - condition = lambda state: state.loyalty>110 and state.money<1200, - ), - ]), - StageGroup(4, [ - Template("Dear leader of {nation_name}, please help our climate change marathon's fundraiser.", - choices = { - "Sure": {"money": -80, "loyalty": +5}, - "Nope": {"loyalty": -5}, - }, - ), - Template( - "The marathon was phenomenal! Thanks for your support.", - choices = {"Great!": {"money": +120}}, - condition = lambda state: state.loyalty>130 and state.money<2400, - ), - ]), - StageGroup(5, [ - Template("Dear leader of {nation_name}, please help our climate change marathon's fundraiser.", - choices = { - "Sure": {"money": -160, "loyalty": +5}, - "Nope": {"loyalty": -5}, - }, - ), - Template( - "The marathon was phenomenal! Thanks for your support.", - choices = {"Great!": {"money": +240}}, - condition = lambda state: state.loyalty>150 and state.money<480, - ), - ]), -]) - - -Actor("Aura the Activist", "url_here",[ - StageGroup(1, [ - Template( - "Kind leader of {nation_name}! Please donate to our LGBTQ+ movement.", - choices={ - "Sure": {"money": -10, "loyalty": +5}, - "Nope": {"loyalty": -5}, - }, - ), - Template( - "Rights are Rights! You did the right thing.", - choices = {"Great!": {"money": +15}}, - condition = lambda state: state.loyalty>70 and state.money<300, - ), - ]), - StageGroup(2, [ - Template("Kind leader of {nation_name}! Please donate to our LGBTQ+ movement.", - choices = { - "Sure": {"money": -20, "loyalty": +5}, - "Nope": {"loyalty": -5}, - }, - ), - Template( - "Rights are Rights! You did the right thing.", - choices = {"Great!": {"money": +30}}, - condition = lambda state: state.loyalty>90 and state.money<600, - ), - ]), - StageGroup(3, [ - Template("Kind leader of {nation_name}! Please donate to our LGBTQ+ movement.", - choices = { - "Sure": {"money": -40, "loyalty": +5}, - "Nope": {"loyalty": -5}, - }, - ), - Template( - "Rights are Rights! You did the right thing.", - choices = {"Great!": {"money": +60}}, - condition = lambda state: state.loyalty>110 and state.money<1200, - ), - ]), - StageGroup(4, [ - Template("Kind leader of {nation_name}! Please donate to our LGBTQ+ movement.", - choices = { - "Sure": {"money": -80, "loyalty": +5}, - "Nope": {"loyalty": -5}, - }, - ), - Template( - "Rights are Rights! You did the right thing.", - choices = {"Great!": {"money": +120}}, - condition = lambda state: state.loyalty>130 and state.money<2400, - ), - ]), - StageGroup(5, [ - Template("Kind leader of {nation_name}! Please donate to our LGBTQ+ movement.", - choices = { - "Sure": {"money": -160, "loyalty": +5}, - "Nope": {"loyalty": -5}, - }, - ), - Template( - "Rights are Rights! You did the right thing.", - choices = {"Great!": {"money": +240}}, - condition = lambda state: state.loyalty>150 and state.money<480, - ), - ]), -]) - - -Actor("Sheela the Singer", "url_here",[ - StageGroup(1, [ - Template( - "Blessed leader of {nation_name}, please fund my public concert!", - choices={ - "Sure": {"money": -10, "loyalty": +5}, - "Nope": {"loyalty": -5}, - }, - ), - Template( - "The concert was on fire! Thank you so much.", - choices = {"Great!": {"money": +15}}, - condition = lambda state: state.loyalty>70 and state.money<300, - ), - ]), - StageGroup(2, [ - Template("Blessed leader of {nation_name}, please fund my public concert!", - choices = { - "Sure": {"money": -20, "loyalty": +5}, - "Nope": {"loyalty": -5}, - }, - ), - Template( - "The concert was on firee! Thank you so much.", - choices = {"Great!": {"money": +30}}, - condition = lambda state: state.loyalty>90 and state.money<600, - ), - ]), - StageGroup(3, [ - Template("Blessed leader of {nation_name}, please fund my public concert!", - choices = { - "Sure": {"money": -40, "loyalty": +5}, - "Nope": {"loyalty": -5}, - }, - ), - Template( - "The concert was on fireee! Thank you so much.", - choices = {"Great!": {"money": +60}}, - condition = lambda state: state.loyalty>110 and state.money<1200, - ), - ]), - StageGroup(4, [ - Template("Blessed leader of {nation_name}, please fund my public concert!", - choices = { - "Sure": {"money": -80, "loyalty": +5}, - "Nope": {"loyalty": -5}, - }, - ), - Template( - "The concert was on fireeee! Thank you so much.", - choices = {"Great!": {"money": +120}}, - condition = lambda state: state.loyalty>130 and state.money<2400, - ), - ]), - StageGroup(5, [ - Template("Blessed leader of {nation_name}, please fund my public concert!", - choices = { - "Sure": {"money": -160, "loyalty": +5}, - "Nope": {"loyalty": -5}, - }, - ), - Template( - "The concert was on fireeeee! Thank you so much.", - choices = {"Great!": {"money": +240}}, - condition = lambda state: state.loyalty>150 and state.money<480, - ), - ]), -]) - - -Actor("Craig the Contractor", "url_here",[ - StageGroup(1, [ - Template( - "Ruler of {nation_name}, the roads need mending following the earthquake.", - choices={ - "Sure": {"money": -10, "loyalty": +5}, - "Nope": {"loyalty": -5}, - }, - ), - Template( - "", - choices = {"Great!": {"money": +15}}, - condition = lambda state: state.loyalty>70 and state.money<300, - ), - ]), - StageGroup(2, [ - Template("Ruler of {nation_name}, the houses need patching following the tornado.", - choices = { - "Sure": {"money": -20, "loyalty": +5}, - "Nope": {"loyalty": -5}, - }, - ), - Template( - "Great! Looks like a tornado never hit this place.", - choices = {"Great!": {"money": +30}}, - condition = lambda state: state.loyalty>90 and state.money<600, - ), - ]), - StageGroup(3, [ - Template("Ruler of {nation_name}, the drains need to be declogged following the flood.", - choices = { - "Sure": {"money": -40, "loyalty": +5}, - "Nope": {"loyalty": -5}, - }, - ), - Template( - "Great! Looks like a flood never hit this place.", - choices = {"Great!": {"money": +60}}, - condition = lambda state: state.loyalty>110 and state.money<1200, - ), - ]), - StageGroup(4, [ - Template("Ruler of {nation_name}, the electric poles need fixing following the thunderstorm.", - choices = { - "Sure": {"money": -80, "loyalty": +5}, - "Nope": {"loyalty": -5}, - }, - ), - Template( - "Great! Looks like a thunderstorm never hit this place.", - choices = {"Great!": {"money": +120}}, - condition = lambda state: state.loyalty>130 and state.money<2400, - ), - ]), - StageGroup(5, [ - Template("Ruler of {nation_name}, the city needs to be restored following the tsunami.", - choices = { - "Sure": {"money": -160, "loyalty": +5}, - "Nope": {"loyalty": -5}, - }, - ), - Template( - "Great! Looks like a tsunami never hit this place.", - choices = {"Great!": {"money": +240}}, - condition = lambda state: state.loyalty>150 and state.money<480, - ), - ]), -]) - - -Actor("Gary the General", "url_here",[ - StageGroup(1, [ - Template( - "Sir, ruler of {nation_name}! The army needs vests Sir!", - choices={ - "Sure": {"money": -10, "loyalty": +5}, - "Nope": {"loyalty": -5}, - }, - ), - Template( - "Thanks Sir! Now our army is stronger than ever Sir!", - choices = {"Great!": {"money": +15}}, - condition = lambda state: state.loyalty>70 and state.money<300, - ), - ]), - StageGroup(2, [ - Template("Sir, ruler of {nation_name}! The army needs equipment Sir!", - choices = { - "Sure": {"money": -20, "loyalty": +5}, - "Nope": {"loyalty": -5}, - }, - ), - Template( - "Thanks Sir! Now our army is stronger than ever Sir!", - choices = {"Great!": {"money": +30}}, - condition = lambda state: state.loyalty>90 and state.money<600, - ), - ]), - StageGroup(3, [ - Template("Sir, ruler of {nation_name}! The army needs weapons Sir!", - choices = { - "Sure": {"money": -40, "loyalty": +5}, - "Nope": {"loyalty": -5}, - }, - ), - Template( - "Thanks Sir! Now our army is stronger than ever Sir!", - choices = {"Great!": {"money": +60}}, - condition = lambda state: state.loyalty>110 and state.money<1200, - ), - ]), - StageGroup(4, [ - Template("Sir, ruler of {nation_name}! The army needs tanks Sir!", - choices = { - "Sure": {"money": -80, "loyalty": +5}, - "Nope": {"loyalty": -5}, - }, - ), - Template( - "Thanks Sir! Now our army is stronger than ever Sir!", - choices = {"Great!": {"money": +120}}, - condition = lambda state: state.loyalty>130 and state.money<2400, - ), - ]), - StageGroup(5, [ - Template("Sir, ruler of {nation_name}! The army needs helicopters Sir!", - choices = { - "Sure": {"money": -160, "loyalty": +5}, - "Nope": {"loyalty": -5}, - }, - ), - Template( - "Thanks Sir! Now our army is stronger than ever Sir!", - choices = {"Great!": {"money": +240}}, - condition = lambda state: state.loyalty>150 and state.money<480, - ), - ]), -]) - - -Actor("Sandra the Spy", "url_here",[ - StageGroup(1, [ - Template( - "ruler of {nation_name}, spy squad needs supplies", - choices={ - "Sure": {"money": -10, "loyalty": +5}, - "Nope": {"loyalty": -5}, - }, - ), - Template( - "The supplies helped our mission", - choices = {"Great!": {"money": +15}}, - condition = lambda state: state.loyalty>70 and state.money<300, - ), - ]), - StageGroup(2, [ - Template("ruler of {nation_name}, spy squad needs supplies", - choices = { - "Sure": {"money": -20, "loyalty": +5}, - "Nope": {"loyalty": -5}, - }, - ), - Template( - "The supplies helped our mission", - choices = {"Great!": {"money": +30}}, - condition = lambda state: state.loyalty>90 and state.money<600, - ), - ]), - StageGroup(3, [ - Template("ruler of {nation_name}, spy squad needs supplies", - choices = { - "Sure": {"money": -40, "loyalty": +5}, - "Nope": {"loyalty": -5}, - }, - ), - Template( - "The supplies helped our mission", - choices = {"Great!": {"money": +60}}, - condition = lambda state: state.loyalty>110 and state.money<1200, - ), - ]), - StageGroup(4, [ - Template("ruler of {nation_name}, spy squad needs supplies", - choices = { - "Sure": {"money": -80, "loyalty": +5}, - "Nope": {"loyalty": -5}, - }, - ), - Template( - "The supplies helped our mission", - choices = {"Great!": {"money": +120}}, - condition = lambda state: state.loyalty>130 and state.money<2400, - ), - ]), - StageGroup(5, [ - Template("ruler of {nation_name}, spy squad needs supplies", - choices = { - "Sure": {"money": -160, "loyalty": +5}, - "Nope": {"loyalty": -5}, - }, - ), - Template( - "The supplies helped our mission", - choices = {"Great!": {"money": +240}}, - condition = lambda state: state.loyalty>150 and state.money<480, - ), - ]), -]) - - -Actor("Dave the Doctor", "url_here",[ - StageGroup(1, [ - Template( - "Great ruler of {nation_name}, we need money to cure those with the flu!", - choices={ - "Sure": {"money": -10, "loyalty": +5}, - "Nope": {"loyalty": -5}, - }, - ), - Template( - "", - choices = {"Great!": {"money": +15}}, - condition = lambda state: state.loyalty>70 and state.money<300, - ), - ]), - StageGroup(2, [ - Template("Great ruler of {nation_name}, we need money to cure those with Alzheimer's!", - choices = { - "Sure": {"money": -20, "loyalty": +5}, - "Nope": {"loyalty": -5}, - }, - ), - Template( - "", - choices = {"Great!": {"money": +30}}, - condition = lambda state: state.loyalty>90 and state.money<600, - ), - ]), - StageGroup(3, [ - Template("Great ruler of {nation_name}, we need money to cure those with Parkinson's!", - choices = { - "Sure": {"money": -40, "loyalty": +5}, - "Nope": {"loyalty": -5}, - }, - ), - Template( - "", - choices = {"Great!": {"money": +60}}, - condition = lambda state: state.loyalty>110 and state.money<1200, - ), - ]), - StageGroup(4, [ - Template("Great ruler of {nation_name}, we need money to cure those with AIDS!", - choices = { - "Sure": {"money": -80, "loyalty": +5}, - "Nope": {"loyalty": -5}, - }, - ), - Template( - "", - choices = {"Great!": {"money": +120}}, - condition = lambda state: state.loyalty>130 and state.money<2400, - ), - ]), - StageGroup(5, [ - Template("Great ruler of {nation_name}, we need money to cure those with Hepatitis!", - choices = { - "Sure": {"money": -160, "loyalty": +5}, - "Nope": {"loyalty": -5}, - }, - ), - Template( - "", - choices = {"Great!": {"money": +240}}, - condition = lambda state: state.loyalty>150 and state.money<480, - ), - ]), -]) - - -Actor("Elsa the Engineer", "url_here",[ - StageGroup(1, [ - Template( - "Ruler of {nation_name}, spare some money to design the army's vests!", - choices={ - "Sure": {"money": -10, "loyalty": +5}, - "Nope": {"loyalty": -5}, - }, - ), - Template( - "The vests are bulletproof! Thanks.", - choices = {"Great!": {"money": +15}}, - condition = lambda state: state.loyalty>70 and state.money<300, - ), - ]), - StageGroup(2, [ - Template("Ruler of {nation_name}, spare some money to design the army's equipment!", - choices = { - "Sure": {"money": -20, "loyalty": +5}, - "Nope": {"loyalty": -5}, - }, - ), - Template( - "The equipment is top-notch. Thanks.", - choices = {"Great!": {"money": +30}}, - condition = lambda state: state.loyalty>90 and state.money<600, - ), - ]), - StageGroup(3, [ - Template("Ruler of {nation_name}, spare some money to design the army's weapons!", - choices = { - "Sure": {"money": -40, "loyalty": +5}, - "Nope": {"loyalty": -5}, - }, - ), - Template( - "The weapons are deadly! Thanks.", - choices = {"Great!": {"money": +60}}, - condition = lambda state: state.loyalty>110 and state.money<1200, - ), - ]), - StageGroup(4, [ - Template("Ruler of {nation_name}, spare some money to design the army's tanks!", - choices = { - "Sure": {"money": -80, "loyalty": +5}, - "Nope": {"loyalty": -5}, - }, - ), - Template( - "The tanks are tanky! Thanks.", - choices = {"Great!": {"money": +120}}, - condition = lambda state: state.loyalty>130 and state.money<2400, - ), - ]), - StageGroup(5, [ - Template("Ruler of {nation_name}, spare some money to design the army's helicopters!", - choices = { - "Sure": {"money": -160, "loyalty": +5}, - "Nope": {"loyalty": -5}, - }, - ), - Template( - "The helicopters are fast! Thanks.", - choices = {"Great!": {"money": +240}}, - condition = lambda state: state.loyalty>150 and state.money<480, - ), - ]), -]) - - From 00355cbb4cdded510540039c5773f35b369d9302 Mon Sep 17 00:00:00 2001 From: Maheshkumar Date: Sun, 28 Jul 2024 13:03:42 +0530 Subject: [PATCH 077/116] Some enhancements --- pyproject.toml | 28 +++++----------------------- src/game.py | 35 ++++++++++++++++++++++------------- src/game_interaction.py | 6 ++++-- 3 files changed, 31 insertions(+), 38 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index e345848..6d45047 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,30 +17,12 @@ src = ["src"] select = ["ALL"] # Ignore some of the most obnoxious linting errors. ignore = [ - # Missing docstrings. - "D100", - "D104", - "D105", - "D106", + # `id` as arg + "A002", + # Init doc strings "D107", - # Docstring whitespace. - "D203", - "D213", - # Docstring punctuation. - "D415", - # Docstring quotes. - "D301", - # Builtins. - "A", - # Print statements. - "T20", - # TODOs. - "TD002", - "TD003", - "FIX", - # Annotations. - "ANN101", - "ANN102", + # We are using f-strings in logger + "G004", # We never use random for cryptographic purposes "S311", ] diff --git a/src/game.py b/src/game.py index 8ae918e..8a28e12 100644 --- a/src/game.py +++ b/src/game.py @@ -1,7 +1,9 @@ +"""Module responsible for game actions.""" + import asyncio import logging import random -from datetime import datetime, timedelta +import time from typing import TYPE_CHECKING, Annotated from interactions import Embed, SlashContext @@ -28,21 +30,23 @@ def __init__(self, id: GameID, required_no_of_players: int, game_factory: "GameF self.required_no_of_players: int = required_no_of_players self.players: dict[Annotated[int, "discord id"], Player] = {} self.stage: Stage = 1 - self.max_time: float = random.uniform(12.5, 16) + # self.max_time: float = random.uniform(12.5, 16) + self.max_time: float = random.uniform(2, 4) self.started: bool = False - self.creator: int | None = None + self.creator_id: int | None = None self.stop_flag: bool = False self.player_component_choice_mapping: dict[str, dict] = {} self.game_factory: GameFactory = game_factory + self.values_to_check: list[str] = ["loyalty", "money", "security", "world_opinion"] - self.cumm_percent_time_per_stage: list[float] = [0.25, 0.6, 1] # Percentage of the time spent in the game when the next stage of the time begins (max value 1 = 100%) - self.values_to_check: list[str] = ["loyalty", "money", "security", "world_opinion"] + self.cumm_percent_time_per_stage: list[float] = [0.25, 0.6, 1] async def add_player(self, ctx: SlashContext, cmd: str = "create") -> None: """Add a player to the game.""" + logger.info(f"Adding player {ctx.user.id} to the game {self.id}") if cmd == "create": - self.creator = ctx.user.id + self.creator_id = ctx.user.id player = Player(ctx, self) await player.register() self.players[ctx.user.id] = player @@ -50,8 +54,10 @@ async def add_player(self, ctx: SlashContext, cmd: str = "create") -> None: async def remove_player(self, ctx: SlashContext) -> None: """Remove player from the game.""" player_to_delete = ctx.user.id + if player_to_delete in self.players: del self.players[player_to_delete] + self.game_factory.remove_player(player_to_delete) async def death_player(self, dead_player: Player) -> None: @@ -77,7 +83,8 @@ def stop(self) -> None: async def loop(self) -> None: """Define the main loop of the game.""" - self.start_time = datetime.now() + # self.start_time = datetime.now(UTC) + self.start_time = time.time() players = self.players.values() @@ -85,13 +92,15 @@ async def loop(self) -> None: if self.stop_flag: break - game_time: float = (datetime.now() - self.start_time) / timedelta(minutes=1) + # game_time: float = (datetime.now(UTC) - self.start_time) / timedelta(minutes=1) + game_time: float = (time.time() - self.start_time) / 60 + if (game_time > self.cumm_percent_time_per_stage[self.stage - 1] * self.max_time) and ( game_time < self.max_time ): - self.stage = total_stages[ - total_stages.index(self.stage) + 1 - ] # This isn't the best, but it won't go out of bounds and doesn't break typing + self.stage = total_stages[total_stages.index(self.stage) + 1] + + logger.info(f"{game_time=} {self.stage=} {self.max_time=}") try: response = await asyncio.gather(*[self.tick(player) for player in players], return_exceptions=True) @@ -117,14 +126,14 @@ async def tick(self, player: Player) -> None: return character = all_characters.get_random(player.state) - # The sleep times are subject to change, based on how the actual gameplay feels - # The randomness gives a variability between the values mentioned in the brackets for attr in self.values_to_check: if getattr(player.state, attr) < 0: # Some value is negative hence need to send the losing message await self.death_player(player) return + # The sleep times are subject to change, based on how the actual gameplay feels + # The randomness gives a variability between the values mentioned in the brackets match self.stage: case 1: sleep_time = 10 + (random.uniform(-2, 2)) diff --git a/src/game_interaction.py b/src/game_interaction.py index ce502c7..71dc4d1 100644 --- a/src/game_interaction.py +++ b/src/game_interaction.py @@ -1,3 +1,5 @@ +"""Module responsible for game interaction with discord.""" + import random import time from string import ascii_uppercase, digits @@ -153,7 +155,7 @@ async def leave(self, ctx: SlashContext) -> None: await ctx.send(f"<@{ctx.user.id}> You are not part of any game", ephemeral=True) return - # if game.creator == ctx.user.id: # TODO: check this validity - needed of not + # if game.creator_id == ctx.user.id: # TODO: check this validity - needed of not # await ctx.send(f"<@{ctx.user.id}> Game creator cannot leave the game", ephemeral=True) # noqa: ERA001 # return # noqa: ERA001 @@ -183,7 +185,7 @@ async def start(self, ctx: SlashContext) -> None: await ctx.send(f"<@{ctx.user.id}> You are not part of any game", ephemeral=True) return - if game.creator != ctx.user.id: + if game.creator_id != ctx.user.id: await ctx.send(f"<@{ctx.user.id}> Only game creator can start it", ephemeral=True) return From 6d83574b552fe395a1b8a69cf179b28f4d37993d Mon Sep 17 00:00:00 2001 From: Sapient44 Date: Sun, 28 Jul 2024 14:59:22 +0530 Subject: [PATCH 078/116] Fix the false start of AFK timer --- src/game_interaction.py | 6 +++++- src/player.py | 4 +--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/game_interaction.py b/src/game_interaction.py index 550477e..872d778 100644 --- a/src/game_interaction.py +++ b/src/game_interaction.py @@ -197,6 +197,10 @@ async def start(self, ctx: SlashContext) -> None: game.started = True await ctx.send(f"<@{ctx.user.id}> Game started", ephemeral=True) + + for player in game.players.values(): + player.last_activity_time = time.time() + await game.loop() @listen(Component) @@ -212,7 +216,7 @@ async def on_component(self, event: Component) -> None: consequences = game.player_component_choice_mapping[ctx.custom_id] player = game.players[ctx.user.id] player.state.apply(consequences) - player.last_activity_time = time.time() + player.last_activity_time = player.current_activity_time print(player.state) await ctx.edit_origin(content=f"Your response ({ctx.component.label}) saved.", components=[]) del game.player_component_choice_mapping[ctx.custom_id] diff --git a/src/player.py b/src/player.py index 8620f99..454e166 100644 --- a/src/player.py +++ b/src/player.py @@ -36,10 +36,8 @@ def __init__(self, ctx: SlashContext, game: "Game") -> None: self.ctx: SlashContext = ctx self.state: PlayerState = None # type: ignore TODO: properly type that state isn't none after register self.game: Game= game - self.last_activity_time: float = time.time() + self.last_activity_time: float = 0 self.current_activity_time: float = 0 - - # self.last_activity_time: float = time.time() self.component_id: int = 0 def get_component_id(self) -> int: From 0fee8704deb59c528155428113881ae9d5071fc4 Mon Sep 17 00:00:00 2001 From: Sapient44 Date: Sun, 28 Jul 2024 15:04:31 +0530 Subject: [PATCH 079/116] Updated afk timer, on every button interaction --- src/game_interaction.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/game_interaction.py b/src/game_interaction.py index 872d778..e6b9d8c 100644 --- a/src/game_interaction.py +++ b/src/game_interaction.py @@ -216,6 +216,7 @@ async def on_component(self, event: Component) -> None: consequences = game.player_component_choice_mapping[ctx.custom_id] player = game.players[ctx.user.id] player.state.apply(consequences) + player.current_activity_time = time.time() player.last_activity_time = player.current_activity_time print(player.state) await ctx.edit_origin(content=f"Your response ({ctx.component.label}) saved.", components=[]) From 7190ad8c5863ae52a8c3d28752d176b4774d525a Mon Sep 17 00:00:00 2001 From: Maheshkumar Date: Sun, 28 Jul 2024 15:14:55 +0530 Subject: [PATCH 080/116] Fix ruff issue and game ending scenarios --- src/characters/__init__.py | 3 ++- src/characters/example_chr.py | 4 +++- src/game.py | 40 ++++++++++++++++++++++++++++------- src/templating.py | 27 +++++++++++++++++++++-- src/weighted_random.py | 15 +++++++++++-- 5 files changed, 75 insertions(+), 14 deletions(-) diff --git a/src/characters/__init__.py b/src/characters/__init__.py index 5b6d88f..b5aba77 100644 --- a/src/characters/__init__.py +++ b/src/characters/__init__.py @@ -1,4 +1,5 @@ -import typing +"""Import and store all the characters and templates for use.""" + from importlib import import_module from logging import getLogger from pathlib import Path diff --git a/src/characters/example_chr.py b/src/characters/example_chr.py index 184d006..74ddbcc 100644 --- a/src/characters/example_chr.py +++ b/src/characters/example_chr.py @@ -1,5 +1,7 @@ +"""Example character template.""" + from src.templating import Actor, StageGroup -from src.templating import ChoiceTemplate as t +from src.templating import ChoiceTemplate as t # noqa: N813 # fmt: off character = Actor("John the Farmer", "url_here",[ diff --git a/src/game.py b/src/game.py index 8a28e12..f59afa1 100644 --- a/src/game.py +++ b/src/game.py @@ -30,8 +30,7 @@ def __init__(self, id: GameID, required_no_of_players: int, game_factory: "GameF self.required_no_of_players: int = required_no_of_players self.players: dict[Annotated[int, "discord id"], Player] = {} self.stage: Stage = 1 - # self.max_time: float = random.uniform(12.5, 16) - self.max_time: float = random.uniform(2, 4) + self.max_time: float = random.uniform(12.5, 16) self.started: bool = False self.creator_id: int | None = None self.stop_flag: bool = False @@ -69,7 +68,7 @@ async def death_player(self, dead_player: Player) -> None: ) for player in self.players.values(): - await player.ctx.send(embed=embed) + await player.ctx.send(embed=embed, ephemeral=True) await self.remove_player(dead_player.ctx) @@ -77,13 +76,26 @@ async def death_player(self, dead_player: Player) -> None: self.stop() self.game_factory.remove_game(self.id) + async def stop_game_by_time(self) -> None: + """End game because the time is up.""" + embed = Embed( + title="Time Up! Game Over!", + description=f"Game is over! Because time is up! We have {len(self.players)} survivors! You are one of them!", # noqa: E501 + color=system_message_color, + ) + + for player in list(self.players.values()): + await player.ctx.send(embed=embed, ephemeral=True) + await self.remove_player(player.ctx) + + self.game_factory.remove_game(self.id) + def stop(self) -> None: """Set the stop flag.""" self.stop_flag = True async def loop(self) -> None: """Define the main loop of the game.""" - # self.start_time = datetime.now(UTC) self.start_time = time.time() players = self.players.values() @@ -92,7 +104,6 @@ async def loop(self) -> None: if self.stop_flag: break - # game_time: float = (datetime.now(UTC) - self.start_time) / timedelta(minutes=1) game_time: float = (time.time() - self.start_time) / 60 if (game_time > self.cumm_percent_time_per_stage[self.stage - 1] * self.max_time) and ( @@ -100,6 +111,12 @@ async def loop(self) -> None: ): self.stage = total_stages[total_stages.index(self.stage) + 1] + if game_time >= self.max_time: + logger.info(f"Time is Up! Game {self.id} is over!") + self.stop() + await self.stop_game_by_time() + break + logger.info(f"{game_time=} {self.stage=} {self.max_time=}") try: @@ -125,7 +142,6 @@ async def tick(self, player: Player) -> None: if self.stop_flag: return - character = all_characters.get_random(player.state) for attr in self.values_to_check: if getattr(player.state, attr) < 0: # Some value is negative hence need to send the losing message @@ -144,5 +160,13 @@ async def tick(self, player: Player) -> None: case 3: sleep_time = 6 + (random.uniform(-1, 0.75)) - await asyncio.sleep(sleep_time) - await character.send(player) + character = all_characters.get_random(player.state) + while self.stage not in character.stages: + character = all_characters.get_random(player.state) + + result = await character.send(player) + + if result: + await asyncio.sleep(sleep_time) + else: + await asyncio.sleep(0.2) diff --git a/src/templating.py b/src/templating.py index 0952713..9d28674 100644 --- a/src/templating.py +++ b/src/templating.py @@ -1,11 +1,14 @@ +"""Utils for creating and using templates.""" + +import logging from collections.abc import Callable from typing import TYPE_CHECKING, Any, Literal, get_args from attrs import asdict, field, frozen from interactions import ActionRow, Button, ButtonStyle, Embed -from src.weighted_random import WeightedList from src.const import message_color +from src.weighted_random import WeightedList if TYPE_CHECKING: from src.player import Player, PlayerState @@ -14,6 +17,8 @@ Condition = Callable[["PlayerState"], bool] | None Stage = Literal[1, 2, 3] # Adjustable +logger = logging.getLogger(__name__) + @frozen class Template: @@ -28,6 +33,7 @@ def format(self, state: "PlayerState") -> str: return self.text.format(**asdict(state)) def is_available(self, state: "PlayerState") -> bool: + """Check whether the template is available to serve.""" if self.condition is not None: return self.condition(state) @@ -43,10 +49,12 @@ def to_embed(self, player: "Player", actor: "Actor") -> Embed: ) async def ui(self, player: "Player", actor: "Actor") -> None: + """Send template data to ui.""" await player.ctx.send(embed=self.to_embed(player, actor), ephemeral=True) def not_none(var: Any | None) -> Any: # noqa: ANN401 temporary workaround FIXME + """Workaround for none check.""" if var is None: raise AttributeError @@ -55,6 +63,8 @@ def not_none(var: Any | None) -> Any: # noqa: ANN401 temporary workaround FIXME @frozen class ChoiceTemplate(Template): + """Make a template for the messages to be served.""" + choices: dict[str, Consequence] = field(default=None, converter=not_none) # Specify button color here somehow. async def ui(self, player: "Player", actor: "Actor") -> None: @@ -85,6 +95,7 @@ class StageGroup: @staticmethod def convert_stage(stage: Stage | list[Stage] | Literal["all"]) -> list[Stage]: + """Conver stage into a required a data type.""" if stage == "all": return list(total_stages) if isinstance(stage, int): @@ -98,8 +109,11 @@ def convert_stage(stage: Stage | list[Stage] | Literal["all"]) -> list[Stage]: @frozen class Actor: + """Respresents an actor who asks/gives information to the leader.""" + @staticmethod def cast_stages(stage_groups: list[StageGroup]) -> dict[Stage, WeightedList[Template]]: + """Cast stages into a weighted list.""" stages: dict[Stage, WeightedList[Template]] = {} for stage_slot in total_stages: @@ -125,6 +139,15 @@ def is_available(self, state: "PlayerState") -> bool: return True async def send(self, target: "Player") -> None: + """Send template to discord ui.""" stage = self.stages[target.game.stage] template = stage.get_random(target.state) - await template.ui(target, self) + sent = False + + if template: + sent = True + await template.ui(target, self) + else: + logger.info(f"Template not available for {stage=} in {self.name}") + + return sent diff --git a/src/weighted_random.py b/src/weighted_random.py index 2a9f65d..c35387a 100644 --- a/src/weighted_random.py +++ b/src/weighted_random.py @@ -1,3 +1,5 @@ +"""Weight random list implementation.""" + import random from typing import TYPE_CHECKING, Generic, Protocol, TypeVar @@ -6,16 +8,20 @@ class SupportedRandomValue(Protocol): + """Generic type for supported classes.""" + @property - def weight(self) -> int: ... + def weight(self) -> int: ... # noqa: D102 - def is_available(self, state: "PlayerState") -> bool: ... + def is_available(self, state: "PlayerState") -> bool: ... # noqa: D102 T = TypeVar("T", bound=SupportedRandomValue) class WeightedList(Generic[T]): + """Responsible for storing and retrieving values based on weights.""" + def __init__(self, values: list[T] | None = None) -> None: if values is not None: self.values = values @@ -25,12 +31,17 @@ def __init__(self, values: list[T] | None = None) -> None: self.weights = [] def append(self, value: T) -> None: + """Add a value to the weighted list.""" self.values.append(value) self.weights.append(value.weight) def get_random(self, state: "PlayerState") -> T: + """Get a random value based on the weights.""" result = None + if not self.values: + return result + while result is None: possible_result = random.choices(self.values, weights=self.weights, k=1)[0] From 3e7917e9de2640017b8cfb1d0e4bdcb45a86d7b6 Mon Sep 17 00:00:00 2001 From: Sapient44 Date: Sun, 28 Jul 2024 16:39:06 +0530 Subject: [PATCH 081/116] Added Character variable --- src/characters/Andy_the_athelete_chr.py | 4 ++-- src/characters/Aura_the_activist_chr.py | 2 +- src/characters/Criag_the_contractor_chr.py | 2 +- src/characters/Dave_the_doctor_chr.py | 2 +- src/characters/Else_the_engineer_chr.py | 2 +- src/characters/Fred_the_Farmer_chr.py | 2 +- src/characters/Gary_the_general_chr.py | 2 +- src/characters/Sam_the_scientist_chr.py | 2 +- src/characters/Sandra_the_spy_chr.py | 2 +- src/characters/Sheela_the_singer_chr.py | 2 +- src/characters/Wong_the_worker_chr.py | 2 +- 11 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/characters/Andy_the_athelete_chr.py b/src/characters/Andy_the_athelete_chr.py index b0e691d..2c80c70 100644 --- a/src/characters/Andy_the_athelete_chr.py +++ b/src/characters/Andy_the_athelete_chr.py @@ -1,12 +1,12 @@ from src.templating import Actor, StageGroup from src.templating import ChoiceTemplate as t -Actor("Andy the Athlete", "url_here",[ +character = Actor("Andy the Athlete", "url_here",[ StageGroup(1, [ t( "Dear leader of {nation_name}, please help our climate change marathon's fundraiser.", choices={ - "Sure": {"money": -10, "loyalty": +5}, + "Sure": {"money": -10, "loyalty": +5, "world_opinion" : +10}, "Nope": {"loyalty": -5}, }, ), diff --git a/src/characters/Aura_the_activist_chr.py b/src/characters/Aura_the_activist_chr.py index 23a227a..74d3ba8 100644 --- a/src/characters/Aura_the_activist_chr.py +++ b/src/characters/Aura_the_activist_chr.py @@ -1,7 +1,7 @@ from src.templating import Actor, StageGroup from src.templating import ChoiceTemplate as t -Actor("Aura the Activist", "url_here",[ +character = Actor("Aura the Activist", "url_here",[ StageGroup(1, [ t( "Kind leader of {nation_name}! Please donate to our LGBTQ+ movement.", diff --git a/src/characters/Criag_the_contractor_chr.py b/src/characters/Criag_the_contractor_chr.py index 86a5315..88a0540 100644 --- a/src/characters/Criag_the_contractor_chr.py +++ b/src/characters/Criag_the_contractor_chr.py @@ -1,7 +1,7 @@ from src.templating import Actor, StageGroup from src.templating import ChoiceTemplate as t -Actor("Craig the Contractor", "url_here",[ +character = Actor("Craig the Contractor", "url_here",[ StageGroup(1, [ t( "Ruler of {nation_name}, the roads need mending following the earthquake.", diff --git a/src/characters/Dave_the_doctor_chr.py b/src/characters/Dave_the_doctor_chr.py index 0544dee..c960e0b 100644 --- a/src/characters/Dave_the_doctor_chr.py +++ b/src/characters/Dave_the_doctor_chr.py @@ -1,7 +1,7 @@ from src.templating import Actor, StageGroup from src.templating import ChoiceTemplate as t -Actor("Dave the Doctor", "url_here",[ +character = Actor("Dave the Doctor", "url_here",[ StageGroup(1, [ t( "Great ruler of {nation_name}, we need money to cure those with the flu!", diff --git a/src/characters/Else_the_engineer_chr.py b/src/characters/Else_the_engineer_chr.py index 4c701d7..2a4bdce 100644 --- a/src/characters/Else_the_engineer_chr.py +++ b/src/characters/Else_the_engineer_chr.py @@ -1,7 +1,7 @@ from src.templating import Actor, StageGroup from src.templating import ChoiceTemplate as t -Actor("Elsa the Engineer", "url_here",[ +character = Actor("Elsa the Engineer", "url_here",[ StageGroup(1, [ t( "Ruler of {nation_name}, spare some money to design the army's vests!", diff --git a/src/characters/Fred_the_Farmer_chr.py b/src/characters/Fred_the_Farmer_chr.py index 050590e..f32c1a8 100644 --- a/src/characters/Fred_the_Farmer_chr.py +++ b/src/characters/Fred_the_Farmer_chr.py @@ -4,7 +4,7 @@ # fmt: off # fmt: off -Actor("Fred the Farmer", "url_here",[ +character = Actor("Fred the Farmer", "url_here",[ StageGroup(1, [ t( "Hello, leader of {nation_name}. Can you spare some money for our new farming equipment?", diff --git a/src/characters/Gary_the_general_chr.py b/src/characters/Gary_the_general_chr.py index 45345e6..6c1c482 100644 --- a/src/characters/Gary_the_general_chr.py +++ b/src/characters/Gary_the_general_chr.py @@ -1,7 +1,7 @@ from src.templating import Actor, StageGroup from src.templating import ChoiceTemplate as t -Actor("Gary the General", "url_here",[ +character = Actor("Gary the General", "url_here",[ StageGroup(1, [ t( "Sir, ruler of {nation_name}! The army needs vests Sir!", diff --git a/src/characters/Sam_the_scientist_chr.py b/src/characters/Sam_the_scientist_chr.py index efd89cd..7a26916 100644 --- a/src/characters/Sam_the_scientist_chr.py +++ b/src/characters/Sam_the_scientist_chr.py @@ -1,7 +1,7 @@ from src.templating import Actor, StageGroup from src.templating import ChoiceTemplate as t -Actor("Sam the Scientist", "url_here",[ +character = Actor("Sam the Scientist", "url_here",[ StageGroup(1, [ t( "Greetings, esteemed leader of {nation_name}. May I request your consideration for a financial contribution\ diff --git a/src/characters/Sandra_the_spy_chr.py b/src/characters/Sandra_the_spy_chr.py index a0bd542..392bc9c 100644 --- a/src/characters/Sandra_the_spy_chr.py +++ b/src/characters/Sandra_the_spy_chr.py @@ -1,7 +1,7 @@ from src.templating import Actor, StageGroup from src.templating import ChoiceTemplate as t -Actor("Sandra the Spy", "url_here",[ +character = Actor("Sandra the Spy", "url_here",[ StageGroup(1, [ t( "ruler of {nation_name}, spy squad needs supplies", diff --git a/src/characters/Sheela_the_singer_chr.py b/src/characters/Sheela_the_singer_chr.py index a66e835..51f5dbe 100644 --- a/src/characters/Sheela_the_singer_chr.py +++ b/src/characters/Sheela_the_singer_chr.py @@ -1,7 +1,7 @@ from src.templating import Actor, StageGroup from src.templating import ChoiceTemplate as t -Actor("Sheela the Singer", "url_here",[ +character = Actor("Sheela the Singer", "url_here",[ StageGroup(1, [ t( "Blessed leader of {nation_name}, please fund my public concert!", diff --git a/src/characters/Wong_the_worker_chr.py b/src/characters/Wong_the_worker_chr.py index ce474e9..594a1ab 100644 --- a/src/characters/Wong_the_worker_chr.py +++ b/src/characters/Wong_the_worker_chr.py @@ -1,7 +1,7 @@ from src.templating import Actor, StageGroup from src.templating import ChoiceTemplate as t -Actor("Wong the Worker", "url_here",[ +character = Actor("Wong the Worker", "url_here",[ StageGroup(1, [ t( "O great leader of {nation_name}. May thou be kind enough to raise our minimum wage", From fb39337ef81f6376204f10108574350bcc00462b Mon Sep 17 00:00:00 2001 From: Sapient44 Date: Sun, 28 Jul 2024 17:09:04 +0530 Subject: [PATCH 082/116] Fixed the afk timer --- src/game.py | 18 ++++++++++-------- src/game_interaction.py | 3 +-- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/game.py b/src/game.py index 69fb246..6d21cc6 100644 --- a/src/game.py +++ b/src/game.py @@ -34,7 +34,7 @@ def __init__(self, id: GameID, required_no_of_players: int, game_factory: "GameF self.max_time: float = random.uniform(12.5, 16) self.started: bool = False self.creator_id: int | None = None - self.stop_flag: bool = False + self.game_stop_flag: bool = False self.player_component_choice_mapping: dict[str, dict] = {} self.game_factory: GameFactory = game_factory self.values_to_check: list[str] = ["loyalty", "money", "security", "world_opinion"] @@ -93,7 +93,7 @@ async def stop_game_by_time(self) -> None: def stop(self) -> None: """Set the stop flag.""" - self.stop_flag = True + self.game_stop_flag = True async def loop(self) -> None: """Define the main loop of the game.""" @@ -102,7 +102,7 @@ async def loop(self) -> None: players = self.players.values() while True: - if self.stop_flag: + if self.game_stop_flag: break game_time: float = (time.time() - self.start_time) / 60 @@ -139,15 +139,17 @@ async def loop(self) -> None: async def tick(self, player: Player) -> None: """Define the activities done in every game tick.""" - if self.stop_flag: + if self.game_stop_flag: return - if player.current_activity_time - player.last_activity_time > AFK_TIME: - self.remove_player(player.ctx) + print(player.last_activity_time, time.time()) + print(player.last_activity_time - time.time()) + + + if time.time() - player.last_activity_time > AFK_TIME: + await self.remove_player(player.ctx) character = all_characters.get_random(player.state) - # The sleep times are subject to change, based on how the actual gameplay feels - # The randomness gives a variability between the values mentioned in the brackets for attr in self.values_to_check: if getattr(player.state, attr) < 0: # Some value is negative hence need to send the losing message diff --git a/src/game_interaction.py b/src/game_interaction.py index 1f16130..8b4baf7 100644 --- a/src/game_interaction.py +++ b/src/game_interaction.py @@ -218,7 +218,6 @@ async def on_component(self, event: Component) -> None: consequences = game.player_component_choice_mapping[ctx.custom_id] player = game.players[ctx.user.id] player.state.apply(consequences) - player.current_activity_time = time.time() - player.last_activity_time = player.current_activity_time + player.last_activity_time = time.time() await ctx.edit_origin(content=f"Your response ({ctx.component.label}) saved.", components=[]) del game.player_component_choice_mapping[ctx.custom_id] From 285c2f686bca21e033e88d7ec00d46080bebd224 Mon Sep 17 00:00:00 2001 From: Maheshkumar Date: Sun, 28 Jul 2024 17:24:54 +0530 Subject: [PATCH 083/116] Fix infinite loop afk issue --- src/game.py | 21 +++++++++++---------- src/game_interaction.py | 9 +++------ 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/src/game.py b/src/game.py index 6d21cc6..e984cf4 100644 --- a/src/game.py +++ b/src/game.py @@ -6,11 +6,10 @@ import time from typing import TYPE_CHECKING, Annotated -from interactions import Embed, SlashContext from interactions import Embed, SlashContext from src.characters import all_characters -from src.const import error_color, system_message_color, AFK_TIME +from src.const import AFK_TIME, error_color, system_message_color from src.player import Player from src.templating import total_stages @@ -98,10 +97,16 @@ def stop(self) -> None: async def loop(self) -> None: """Define the main loop of the game.""" self.start_time = time.time() - players = self.players.values() while True: + logger.info(f"{len(self.players)} left in game {self.id}") + + if len(self.players) == 0 and self.started: + self.stop() + self.game_factory.remove_game(self.id) + break + if self.game_stop_flag: break @@ -142,12 +147,10 @@ async def tick(self, player: Player) -> None: if self.game_stop_flag: return - print(player.last_activity_time, time.time()) - print(player.last_activity_time - time.time()) - - - if time.time() - player.last_activity_time > AFK_TIME: + if (time.time() - player.last_activity_time) > AFK_TIME: + await player.ctx.send(content="AFK detected. You are disqualified. Sorry!") await self.remove_player(player.ctx) + return character = all_characters.get_random(player.state) for attr in self.values_to_check: @@ -161,10 +164,8 @@ async def tick(self, player: Player) -> None: match self.stage: case 1: sleep_time = 10 + (random.uniform(-2, 2)) - case 2: sleep_time = 8 + (random.uniform(-2, 1.5)) - case 3: sleep_time = 6 + (random.uniform(-1, 0.75)) diff --git a/src/game_interaction.py b/src/game_interaction.py index 8b4baf7..8f7b28c 100644 --- a/src/game_interaction.py +++ b/src/game_interaction.py @@ -51,7 +51,8 @@ def remove_player(self, player_id: int) -> None: def remove_game(self, game_id: int) -> None: """Remove a game from game id mapping.""" - del self.games[game_id] + if game_id in self.games: + del self.games[game_id] def query_game(self, game_id: GameID | None = None, player_id: int | None = None) -> Game | None: """Return the game based on the query details.""" @@ -155,10 +156,6 @@ async def leave(self, ctx: SlashContext) -> None: await ctx.send(f"<@{ctx.user.id}> You are not part of any game", ephemeral=True) return - # if game.creator_id == ctx.user.id: # TODO: check this validity - needed of not - # await ctx.send(f"<@{ctx.user.id}> Game creator cannot leave the game", ephemeral=True) # noqa: ERA001 - # return # noqa: ERA001 - await game.remove_player(ctx) embed = Embed( @@ -199,7 +196,7 @@ async def start(self, ctx: SlashContext) -> None: game.started = True await ctx.send(f"<@{ctx.user.id}> Game started", ephemeral=True) - + for player in game.players.values(): player.last_activity_time = time.time() From c870836e3615f3c06c096cdbcc823fb42adfa3dc Mon Sep 17 00:00:00 2001 From: Maheshkumar Date: Sun, 28 Jul 2024 17:35:00 +0530 Subject: [PATCH 084/116] Add afk broadcast and embed --- src/const.py | 2 ++ src/game.py | 24 +++++++++++++++++------- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/const.py b/src/const.py index 7d8b530..24c44ca 100644 --- a/src/const.py +++ b/src/const.py @@ -1,3 +1,5 @@ +"""Application constants.""" + error_color = (255, 58, 51) message_color = (124, 199, 242) system_message_color = (88, 245, 149) diff --git a/src/game.py b/src/game.py index e984cf4..42eaa06 100644 --- a/src/game.py +++ b/src/game.py @@ -76,6 +76,22 @@ async def death_player(self, dead_player: Player) -> None: self.stop() self.game_factory.remove_game(self.id) + async def disqualify_player(self, player: Player) -> None: + """Disqualify inactive player.""" + embed = Embed( + title="We have lost a national leader due to inactivity.", + description=f"{player.state.nation_name} has lost their leadership which was done by \n <@{player.ctx.user.id}>", # noqa: E501 + color=system_message_color, + ) + for _player in self.players.values(): + await _player.ctx.send(embed=embed, ephemeral=True) + + await self.remove_player(player.ctx) + + if len(self.players) == 0 and self.started: + self.stop() + self.game_factory.remove_game(self.id) + async def stop_game_by_time(self) -> None: """End game because the time is up.""" embed = Embed( @@ -102,11 +118,6 @@ async def loop(self) -> None: while True: logger.info(f"{len(self.players)} left in game {self.id}") - if len(self.players) == 0 and self.started: - self.stop() - self.game_factory.remove_game(self.id) - break - if self.game_stop_flag: break @@ -148,8 +159,7 @@ async def tick(self, player: Player) -> None: return if (time.time() - player.last_activity_time) > AFK_TIME: - await player.ctx.send(content="AFK detected. You are disqualified. Sorry!") - await self.remove_player(player.ctx) + await self.disqualify_player(player) return character = all_characters.get_random(player.state) From e1e80d074da072ba5b4538f4a47015cd76156253 Mon Sep 17 00:00:00 2001 From: Sapient44 Date: Sun, 28 Jul 2024 17:48:25 +0530 Subject: [PATCH 085/116] Remove the Current activity time, is outdated --- src/player.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/player.py b/src/player.py index 582eee7..d4da3d1 100644 --- a/src/player.py +++ b/src/player.py @@ -37,7 +37,6 @@ def __init__(self, ctx: SlashContext, game: "Game") -> None: self.state: PlayerState = None # type: ignore TODO: properly type that state isn't none after register self.game: Game= game self.last_activity_time: float = 0 - self.current_activity_time: float = 0 self.component_id: int = 0 def get_component_id(self) -> int: From 63dd10316eb20cc690e4f50ef238da910e40ed88 Mon Sep 17 00:00:00 2001 From: Sapient44 Date: Sun, 28 Jul 2024 17:55:32 +0530 Subject: [PATCH 086/116] Increase min number of players --- src/game_interaction.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/game_interaction.py b/src/game_interaction.py index 8f7b28c..a5bf59a 100644 --- a/src/game_interaction.py +++ b/src/game_interaction.py @@ -95,7 +95,7 @@ async def defcord(self, ctx: SlashContext) -> None: "Number of players required for the game", required=True, opt_type=OptionType.INTEGER, - min_value=1, + min_value=2, max_value=10, ) async def create(self, ctx: SlashContext, required_no_of_players: int = 5) -> None: From 87d46a1a76e93b9310b29ae7539c7127da0910d7 Mon Sep 17 00:00:00 2001 From: Maheshkumar Date: Sun, 28 Jul 2024 18:40:09 +0530 Subject: [PATCH 087/116] Update gary the general --- src/characters/Gary_the_general_chr.py | 46 --------- src/characters/gary_the_general_chr.py | 128 +++++++++++++++++++++++++ 2 files changed, 128 insertions(+), 46 deletions(-) delete mode 100644 src/characters/Gary_the_general_chr.py create mode 100644 src/characters/gary_the_general_chr.py diff --git a/src/characters/Gary_the_general_chr.py b/src/characters/Gary_the_general_chr.py deleted file mode 100644 index 6c1c482..0000000 --- a/src/characters/Gary_the_general_chr.py +++ /dev/null @@ -1,46 +0,0 @@ -from src.templating import Actor, StageGroup -from src.templating import ChoiceTemplate as t - -character = Actor("Gary the General", "url_here",[ - StageGroup(1, [ - t( - "Sir, ruler of {nation_name}! The army needs vests Sir!", - choices={ - "Sure": {"money": -10, "loyalty": +5}, - "Nope": {"loyalty": -5}, - }, - ), - t( - "Thanks Sir! Now our army is stronger than ever Sir!", - choices = {"Great!": {"money": +15}}, - condition = lambda state: state.loyalty>70 and state.money<300, - ), - ]), - StageGroup(2, [ - t("Sir, ruler of {nation_name}! The army needs equipment Sir!", - choices = { - "Sure": {"money": -20, "loyalty": +5}, - "Nope": {"loyalty": -5}, - }, - ), - t( - "Thanks Sir! Now our army is stronger than ever Sir!", - choices = {"Great!": {"money": +30}}, - condition = lambda state: state.loyalty>90 and state.money<600, - ), - ]), - StageGroup(3, [ - t("Sir, ruler of {nation_name}! The army needs weapons Sir!", - choices = { - "Sure": {"money": -40, "loyalty": +5}, - "Nope": {"loyalty": -5}, - }, - ), - t( - "Thanks Sir! Now our army is stronger than ever Sir!", - choices = {"Great!": {"money": +60}}, - condition = lambda state: state.loyalty>110 and state.money<1200, - ), - ]), -]) - diff --git a/src/characters/gary_the_general_chr.py b/src/characters/gary_the_general_chr.py new file mode 100644 index 0000000..480b6c2 --- /dev/null +++ b/src/characters/gary_the_general_chr.py @@ -0,0 +1,128 @@ +"""General template and information.""" + +from src.templating import Actor, StageGroup +from src.templating import ChoiceTemplate as t # noqa: N813 + +# fmt: off +character = Actor("Gary the General", "url_here",[ + StageGroup(1, [ + t( + "Sir, ruler of {nation_name}! The army needs new helmets to improve safety!", + choices={ + "Sure": {"money": -10, "loyalty": +5, "security": +3}, + "Nope": {"loyalty": -5, "security": -5}, + }, + weight=90, + ), + t( + "Sir, ruler of {nation_name}! The army needs new vests to improve safety!", + choices={ + "Sure": {"money": -5, "loyalty": +3, "security": +2}, + "Nope": {"loyalty": -3, "security": -2}, + }, + weight=110, + ), + t( + "Sir, we have received intelligence about potential threats from neighboring countries.", + choices={ + "Increase border security": {"money": -20, "security": +10, "world_opinion": -5, "loyalty": +3}, + "Ignore the threats": {"security": -10, "world_opinion": +5, "loyalty": -5}, + }, + weight=80, + ), + t( + "Good morning, {nation_name}! Our brave soldiers are out training hard today. 💪 #MilitaryStrength #ProudNation", # noqa: E501 + choices={ + "Like": {"loyalty": +2}, + "Share": {"world_opinion": +1}, + "Ignore": {"loyalty": -2}, + }, + weight=80, + ), + t( + "Just received new supplies for the troops. Thanks to everyone for your support! 🛡️ #SupportOurTroops #NationalSecurity", # noqa: E501 + choices={ + "Comment 'Great job!'": {"loyalty": +3}, + "Ignore": {"loyalty": -3}, + }, + weight=120, + ), + ]), + StageGroup(2, [ + t( + "Sir, ruler of {nation_name}! The army needs equipment Sir!", + choices = { + "Sure": {"money": -20, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + weight=150, + ), + t( + "Thanks Sir! Now our army is stronger than ever Sir!", + choices = {"Great!": {"money": +30}}, + condition = lambda state: state.security>80 and state.money<200 and state.loyalty>70, + weight=150, + ), + t( + "Sir, we've been invited to a joint military exercise with allied nations.", + choices={ + "Participate in the exercise": {"money": -25, "security": +10, "world_opinion": +7}, + "Decline the invitation": {"world_opinion": -5, "security": -3}, + }, + condition = lambda state: state.security>60 and state.money>120, + weight=80, + ), + t( + "Our intelligence team has thwarted a potential threat. Kudos to them! 🕵️‍♂️ #SecurityFirst #StaySafe", + choices={ + "Like": {"security": +2}, + "Share": {"world_opinion": +2}, + "Ignore": {"loyalty": -5}, + }, + ), + ]), + StageGroup(3, [ + t( + "Sir, our intelligence agency needs more resources to counter espionage.", + choices={ + "Allocate resources": {"money": -40, "security": +12, "loyalty": +5}, + "Deny the request": {"loyalty": -10, "security": -5}, + }, + weight=90, + ), + t( + "Sir, ruler of {nation_name}! The army needs weapons Sir!", + choices = { + "Sure": {"money": -40, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + weight=150, + ), + t( + "Sir, a major international summit on global security is approaching. Should we attend?", + choices={ + "Attend the summit": {"money": -35, "world_opinion": +15, "security": +5}, + "Skip the summit": {"world_opinion": -10, "security": -2}, + }, + weight=105, + ), + t( + "Proud to announce new advancements in our military technology. Future-ready and strong! 🚀 #TechInDefense #Innovation", # noqa: E501 + choices={ + "Like": {"security": +3}, + "Comment 'Impressive!'": {"world_opinion": +3}, + "Ignore": {"loyalty": -5, "world_opinion": -10}, + }, + weight=120, + ), + t( + "Remembering our fallen heroes today. Their sacrifice keeps us free. #RemembranceDay #NeverForget", + choices={ + "Like": {"loyalty": +3}, + "Share": {"world_opinion": +2}, + "Ignore": {"loyalty": -5, "world_opinion": -10}, + }, + weight=150, + ), + ]), +]) From 34abc0919e02eab38ec3fa54d5db269dcef6900d Mon Sep 17 00:00:00 2001 From: Maheshkumar Date: Sun, 28 Jul 2024 18:51:31 +0530 Subject: [PATCH 088/116] Update sam the scientist --- src/characters/Sam_the_scientist_chr.py | 49 --------- src/characters/sam_the_scientist_chr.py | 133 ++++++++++++++++++++++++ 2 files changed, 133 insertions(+), 49 deletions(-) delete mode 100644 src/characters/Sam_the_scientist_chr.py create mode 100644 src/characters/sam_the_scientist_chr.py diff --git a/src/characters/Sam_the_scientist_chr.py b/src/characters/Sam_the_scientist_chr.py deleted file mode 100644 index 7a26916..0000000 --- a/src/characters/Sam_the_scientist_chr.py +++ /dev/null @@ -1,49 +0,0 @@ -from src.templating import Actor, StageGroup -from src.templating import ChoiceTemplate as t - -character = Actor("Sam the Scientist", "url_here",[ - StageGroup(1, [ - t( - "Greetings, esteemed leader of {nation_name}. May I request your consideration for a financial contribution\ - towards our research?", - choices={ - "Sure": {"money": -10, "loyalty": +5}, - "Nope": {"loyalty": -5}, - }, - ), - t( - "Alzhemier's will be cured soon! Thanks for your contribution", - choices = {"Great!": {"money": +15}}, - condition = lambda state: state.loyalty>70 and state.money<300, - ), - ]), - StageGroup(2, [ - t("Greetings, esteemed leader of {nation_name}. May I request your consideration for a financial contribution\ - towards our research?", - choices = { - "Sure": {"money": -20, "loyalty": +5}, - "Nope": {"loyalty": -5}, - }, - ), - t( - "Parkinson's will be cured soon! Thanks for your contribution", - choices = {"Great!": {"money": +30}}, - condition = lambda state: state.loyalty>90 and state.money<600, - ), - ]), - StageGroup(3, [ - t("Greetings, esteemed leader of {nation_name}. May I request your consideration for a financial contribution\ - towards our research?", - choices = { - "Sure": {"money": -40, "loyalty": +5}, - "Nope": {"loyalty": -5}, - }, - ), - t( - "AIDS will be cured soon! Thanks for your contribution", - choices = {"Great!": {"money": +60}}, - condition = lambda state: state.loyalty>110 and state.money<1200, - ), - ]), -]) - diff --git a/src/characters/sam_the_scientist_chr.py b/src/characters/sam_the_scientist_chr.py new file mode 100644 index 0000000..fa9a929 --- /dev/null +++ b/src/characters/sam_the_scientist_chr.py @@ -0,0 +1,133 @@ +"""Scientist template information.""" + +from src.templating import Actor, StageGroup +from src.templating import ChoiceTemplate as t # noqa: N813 + +# fmt: off +character = Actor("Sam the Scientist", "url_here",[ + StageGroup(1, [ + t( + "Greetings, esteemed leader of {nation_name}. May I request your consideration for a financial contribution towards our research?", # noqa: E501 + choices={ + "Sure": {"money": -10, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + t( + "Exciting news! Our team has just published a paper on renewable energy solutions. 🌱📄 #GreenEnergy #ScientificResearch", # noqa: E501 + choices={ + "Like": {"world_opinion": +2}, + "Share": {"world_opinion": +3}, + "Ignore": {"loyalty": -5}, + }, + ), + t( + "Hello, {nation_name}! Our research team needs funding to continue our groundbreaking work on renewable energy.", # noqa: E501 + choices={ + "Approve funding": {"money": -20, "world_opinion": +5, "security": +3}, + "Deny funding": {"loyalty": -5}, + }, + ), + t( + "We've developed a new technique to increase crop yields. Should we implement it?", + choices={ + "Yes, implement it": {"money": -10, "world_opinion": +3, "loyalty": +5}, + "No, not now": {"loyalty": -3}, + }, + condition=lambda state: state.money > 50, + ), + t( + "We've been invited to collaborate on a climate change project with international scientists.", + choices={ + "Join the project": {"money": -15, "world_opinion": +7}, + "Decline": {"loyalty": -2}, + }, + weight=110, + ), + t( + "Here's a fun fact: Did you know that our new crop yield technique could increase food production by 30%? 🍅 #Agriculture #FoodSecurity", # noqa: E501 + choices={ + "Like": {"loyalty": +2}, + "Comment 'Amazing!'": {"loyalty": +3}, + "Ignore": {"loyalty": -1}, + }, + condition=lambda state: state.loyalty > 30, + weight=150, + ), + ]), + StageGroup(2, [ + t( + "Our lab needs new equipment to stay competitive in biotech research.", + choices={ + "Buy the equipment": {"money": -30, "security": +5, "loyalty": +3}, + "Delay the purchase": {"loyalty": -4}, + }, + ), + t( + "We've been invited to present our findings at an international conference.", + choices={ + "Attend the conference": {"money": -25, "world_opinion": +10}, + "Decline the invitation": {"world_opinion": -5}, + }, + condition=lambda state: state.world_opinion > 40, + ), + t( + "A major corporation wants to fund our research in exchange for some control over our findings.", + choices={ + "Accept the funding": {"money": +40, "loyalty": -5, "world_opinion": -3}, + "Reject the funding": {"loyalty": +5, "world_opinion": +3}, + }, + ), + ]), + StageGroup(3, [ + t( + "We've discovered a potential cure for a common disease. Should we proceed with human trials?", + choices={ + "Approve the trials": {"money": -40, "world_opinion": +15, "security": +10}, + "Delay the trials": {"loyalty": -5}, + }, + condition=lambda state: state.security > 60, + ), + t( + "A foreign government has offered to fund our research in exchange for shared knowledge.", + choices={ + "Accept the offer": {"money": +50, "world_opinion": +5, "security": -10}, + "Decline the offer": {"world_opinion": -3}, + }, + ), + t( + "We've made a breakthrough in AI technology that could revolutionize various industries.", + choices={ + "Invest in AI": {"money": -50, "world_opinion": +20, "security": +10}, + "Hold off for now": {"loyalty": -7}, + }, + ), + t( + "Today's science joke: Why did the biologist look forward to casual Fridays? Because they're allowed to wear genes! 🤣 #ScienceJokes #FunFact", # noqa: E501 + choices={ + "Like": {"loyalty": +1}, + "Share": {"loyalty": +2}, + "Ignore": {"loyalty": -5}, + }, + weight=200, + ), + t( + "We've entered a new international collaboration. Excited for the future of science! 🌏 #GlobalResearch #ScienceTogether", # noqa: E501 + choices={ + "Retweet": {"world_opinion": +5}, + "Comment 'Fantastic!'": {"loyalty": +3}, + "Ignore": {"world_opinion": -5}, + }, + weight=150, + ), + t( + "Science trivia: What's the most abundant gas in Earth's atmosphere? Nitrogen! 🧠🌍 #ScienceFacts #Trivia", + choices={ + "Like": {"loyalty": +1}, + "Share": {"world_opinion": +2}, + "Ignore": {"loyalty": -1}, + }, + weight=150, + ), + ]), +]) From 3e882a72024039df91ad52c53f3ca1b794f2e94d Mon Sep 17 00:00:00 2001 From: Maheshkumar Date: Sun, 28 Jul 2024 19:04:33 +0530 Subject: [PATCH 089/116] Update sandra the spy --- src/characters/Sandra_the_spy_chr.py | 33 ----- src/characters/sandra_the_spy_chr.py | 192 +++++++++++++++++++++++++++ 2 files changed, 192 insertions(+), 33 deletions(-) delete mode 100644 src/characters/Sandra_the_spy_chr.py create mode 100644 src/characters/sandra_the_spy_chr.py diff --git a/src/characters/Sandra_the_spy_chr.py b/src/characters/Sandra_the_spy_chr.py deleted file mode 100644 index 392bc9c..0000000 --- a/src/characters/Sandra_the_spy_chr.py +++ /dev/null @@ -1,33 +0,0 @@ -from src.templating import Actor, StageGroup -from src.templating import ChoiceTemplate as t - -character = Actor("Sandra the Spy", "url_here",[ - StageGroup(1, [ - t( - "ruler of {nation_name}, spy squad needs supplies", - choices={ - "Sure": {"money": -10, "loyalty": +5}, - "Nope": {"loyalty": -5}, - }, - ), - t( - "The supplies helped our mission", - choices = {"Great!": {"money": +15}}, - condition = lambda state: state.loyalty>70 and state.money<300, - ), - ]), - StageGroup(2, [ - t("ruler of {nation_name}, spy squad needs supplies", - choices = { - "Sure": {"money": -20, "loyalty": +5}, - "Nope": {"loyalty": -5}, - }, - ), - t( - "The supplies helped our mission", - choices = {"Great!": {"money": +30}}, - condition = lambda state: state.loyalty>90 and state.money<600, - ), - ]), -]) - diff --git a/src/characters/sandra_the_spy_chr.py b/src/characters/sandra_the_spy_chr.py new file mode 100644 index 0000000..ee244f8 --- /dev/null +++ b/src/characters/sandra_the_spy_chr.py @@ -0,0 +1,192 @@ +"""Sandra the spy template information.""" + +from src.templating import Actor, StageGroup +from src.templating import ChoiceTemplate as t # noqa: N813 + +# fmt: off +character = Actor("Sandra the Spy", "url_here",[ + StageGroup(1, [ + t( + "Leader of {nation_name}, we need additional funds to enhance our espionage network.", + choices={ + "Approve funds": {"money": -15, "security": +5}, + "Deny funds": {"security": -5}, + }, + ), + t( + "A rival nation is developing new technology. Should we attempt to steal their plans?", + choices={ + "Yes, authorize the mission": {"money": -10, "security": +7}, + "No, too risky": {"security": -3}, + }, + condition=lambda state: state.security > 30, + ), + t( + "We have intercepted communications from a potential threat. Should we investigate?", + choices={ + "Yes, investigate": {"money": -5, "security": +5}, + "No, ignore for now": {"security": -2}, + }, + ), + t( + "New security measures in place to protect our nation. Stay vigilant! 🔒 #NationalSecurity #Vigilance", + choices={ + "Like": {"security": +2}, + "Share": {"security": +3}, + "Ignore": {"loyalty": -1}, + }, + weight=80, + ), + t( + "Top secret mission success! Our spies are the best in the world. 🕵️‍♀️✨ #Espionage #MissionAccomplished", + choices={ + "Like": {"security": +2}, + "Comment 'Well done!'": {"loyalty": +2}, + "Ignore": {}, + }, + weight=70, + ), + t( + "Daily reminder: Report any suspicious activity to keep our nation safe. 🚨 #SecurityAlert #StaySafe", + choices={ + "Like": {"security": +1}, + "Share": {"security": +2}, + "Ignore": {}, + }, + weight=120, + ), + ]), + StageGroup(2, [ + t( + "Our informants have provided intel on an underground organization plotting against us.", + choices={ + "Act on the intel": {"money": -20, "security": +10}, + "Ignore it": {"security": -5}, + }, + weight=120, + ), + t( + "Spotted: A suspicious activity report that led to a major breakthrough. 📈 #Security #PublicSafety", + choices={ + "Like": {"security": +2}, + "Share": {"security": +3}, + "Ignore": {}, + }, + ), + t( + "A leak has been discovered within our ranks. Should we launch an internal investigation?", + choices={ + "Yes, investigate": {"money": -10, "security": +5, "loyalty": -3}, + "No, let it be": {"security": -5, "loyalty": +3}, + }, + weight=110, + ), + t( + "Public service announcement: Always stay alert and report anything unusual. 🔔 #Alertness #Security", + choices={ + "Like": {"security": +1}, + "Share": {"security": +2}, + "Ignore": {}, + }, + weight=130, + ), + t( + "We've identified a mole within our government. Should we apprehend them?", + choices={ + "Yes, apprehend": {"money": -15, "security": +10}, + "No, watch them": {"security": -3, "loyalty": +2}, + }, + condition=lambda state: state.security > 60, + weight=130, + ), + t( + "We can sabotage a key project of our rival nation. Should we proceed?", + choices={ + "Yes, proceed": {"money": -30, "security": +15, "world_opinion": -5}, + "No, hold back": {"security": -5}, + }, + weight=80, + ), + t( + "Our operations have been compromised. Should we relocate our base of operations?", + choices={ + "Yes, relocate": {"money": -40, "security": +20}, + "No, stay put": {"security": -10}, + }, + weight=80, + ), + ]), + StageGroup(3, [ + t( + "Our informants have provided intel on an underground organization plotting against us.", + choices={ + "Act on the intel": {"money": -20, "security": +10}, + "Ignore it": {"security": -5}, + }, + ), + t( + "Breaking: We have successfully identified and neutralized a mole. 🕵️‍♀️✅ #Espionage #NationalSecurity", + choices={ + "Like": {"security": +3}, + "Share": {"security": +4}, + "Ignore": {}, + }, + condition=lambda state: state.security > 60, + ), + t( + "We have a chance to recruit a high-level asset from a rival nation.", + choices={ + "Recruit the asset": {"money": -25, "security": +15}, + "Pass on the opportunity": {"security": -5}, + }, + condition=lambda state: state.money > 50, + ), + t( + "Here's a spy-themed joke to lighten your day: Why did the spy stay in bed? Because he was undercover! 🤣 #SpyJokes #Humor", # noqa: E501 + choices={ + "Like": {"loyalty": +1}, + "Share": {"loyalty": +2}, + "Ignore": {}, + }, + weight=120, + ), + t( + "Security tip of the day: How to recognize and report suspicious activities. 🕵️‍♂️🔍 #SecurityTips #PublicSafety", # noqa: E501 + choices={ + "Like": {"security": +1}, + "Share": {"security": +2}, + "Ignore": {}, + }, + weight=110, + ), + t( + "Our informants have provided intel on an underground organization plotting against us.", + choices={ + "Act on the intel": {"money": -20, "security": +10}, + "Ignore it": {"security": -5}, + }, + ), + t( + "We've identified a mole within our government. Should we apprehend them?", + choices={ + "Yes, apprehend": {"money": -15, "security": +10}, + "No, watch them": {"security": -3, "loyalty": +2}, + }, + condition=lambda state: state.security > 60, + ), + t( + "We can bribe a key official in a rival nation to gain access to classified information.", + choices={ + "Yes, bribe them": {"money": -30, "security": +25, "world_opinion": -10}, + "No, too dangerous": {"security": -5}, + }, + ), + t( + "A rogue agent has been identified. Should we neutralize them?", + choices={ + "Yes, neutralize": {"money": -15, "security": +20}, + "No, monitor them": {"security": -5, "loyalty": +3}, + }, + ), + ]), +]) From d3c6425aca53f39cc6d379674d129063841ec923 Mon Sep 17 00:00:00 2001 From: Maheshkumar Date: Sun, 28 Jul 2024 19:10:49 +0530 Subject: [PATCH 090/116] Update sheela the singer --- src/characters/Sheela_the_singer_chr.py | 46 ------------------- src/characters/sheela_the_singer_chr.py | 60 +++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 46 deletions(-) delete mode 100644 src/characters/Sheela_the_singer_chr.py create mode 100644 src/characters/sheela_the_singer_chr.py diff --git a/src/characters/Sheela_the_singer_chr.py b/src/characters/Sheela_the_singer_chr.py deleted file mode 100644 index 51f5dbe..0000000 --- a/src/characters/Sheela_the_singer_chr.py +++ /dev/null @@ -1,46 +0,0 @@ -from src.templating import Actor, StageGroup -from src.templating import ChoiceTemplate as t - -character = Actor("Sheela the Singer", "url_here",[ - StageGroup(1, [ - t( - "Blessed leader of {nation_name}, please fund my public concert!", - choices={ - "Sure": {"money": -10, "loyalty": +5}, - "Nope": {"loyalty": -5}, - }, - ), - t( - "The concert was on fire! Thank you so much.", - choices = {"Great!": {"money": +15}}, - condition = lambda state: state.loyalty>70 and state.money<300, - ), - ]), - StageGroup(2, [ - t("Blessed leader of {nation_name}, please fund my public concert!", - choices = { - "Sure": {"money": -20, "loyalty": +5}, - "Nope": {"loyalty": -5}, - }, - ), - t( - "The concert was on firee! Thank you so much.", - choices = {"Great!": {"money": +30}}, - condition = lambda state: state.loyalty>90 and state.money<600, - ), - ]), - StageGroup(3, [ - t("Blessed leader of {nation_name}, please fund my public concert!", - choices = { - "Sure": {"money": -40, "loyalty": +5}, - "Nope": {"loyalty": -5}, - }, - ), - t( - "The concert was on fireee! Thank you so much.", - choices = {"Great!": {"money": +60}}, - condition = lambda state: state.loyalty>110 and state.money<1200, - ), - ]), -]) - diff --git a/src/characters/sheela_the_singer_chr.py b/src/characters/sheela_the_singer_chr.py new file mode 100644 index 0000000..643ba3c --- /dev/null +++ b/src/characters/sheela_the_singer_chr.py @@ -0,0 +1,60 @@ +"""Sheel template information.""" + +from src.templating import Actor, StageGroup +from src.templating import ChoiceTemplate as t # noqa: N813 + +# fmt: off +character = Actor("Sheela the Singer", "url_here",[ + StageGroup(1, [ + t( + "Blessed leader of {nation_name}, please fund my public concert!", + choices={ + "Sure": {"money": -10, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + t( + "Leader of {nation_name}, I need funding to produce a new national anthem that will boost our citizens' morale.", # noqa: E501 + choices={ + "Approve funding": {"money": -10, "loyalty": +5}, + "Deny funding": {"loyalty": -5}, + }, + ), + ]), + StageGroup(2, [ + t( + "I want to start a music school to nurture local talent. It will need substantial investment.", + choices={ + "Approve the school": {"money": -25, "loyalty": +15, "world_opinion": +5}, + "Deny the project": {"loyalty": -10}, + }, + ), + t( + "Excited to be a guest on the popular TV show this week. Tune in! 📺🎤 #TVAppearance #Excited", + choices={ + "Like": {"world_opinion": +2}, + "Share": {"world_opinion": +3}, + "Ignore": {}, + }, + ), + ]), + StageGroup(3, [ + t( + "I've been nominated for an international music award! Thank you for all your support. 🌟🎶 #MusicAward #FeelingBlessed", # noqa: E501 + choices={ + "Like": {"loyalty": +3}, + "Share": {"world_opinion": +3}, + "Ignore": {}, + }, + condition=lambda state: state.world_opinion > 50, + ), + t( + "Dreaming big! Planning to start a music school to nurture young talent. 🎼🎓 #MusicSchool #FutureTalent", + choices={ + "Like": {"loyalty": +3}, + "Share": {"world_opinion": +3}, + "Ignore": {}, + }, + ), + ]), +]) From 070147a62a6f01142e92c8ea2c59279376998b23 Mon Sep 17 00:00:00 2001 From: Maheshkumar Date: Sun, 28 Jul 2024 19:15:52 +0530 Subject: [PATCH 091/116] Update wong the worker --- src/characters/Wong_the_worker_chr.py | 46 ----------- src/characters/wong_the_worker_chr.py | 109 ++++++++++++++++++++++++++ 2 files changed, 109 insertions(+), 46 deletions(-) delete mode 100644 src/characters/Wong_the_worker_chr.py create mode 100644 src/characters/wong_the_worker_chr.py diff --git a/src/characters/Wong_the_worker_chr.py b/src/characters/Wong_the_worker_chr.py deleted file mode 100644 index 594a1ab..0000000 --- a/src/characters/Wong_the_worker_chr.py +++ /dev/null @@ -1,46 +0,0 @@ -from src.templating import Actor, StageGroup -from src.templating import ChoiceTemplate as t - -character = Actor("Wong the Worker", "url_here",[ - StageGroup(1, [ - t( - "O great leader of {nation_name}. May thou be kind enough to raise our minimum wage", - choices={ - "Sure": {"money": -10, "loyalty": +5}, - "Nope": {"loyalty": -5}, - }, - ), - t( - "Thank you! We have been extremely productive and produced so much more goods.", - choices = {"Great!": {"money": +15}}, - condition = lambda state: state.loyalty>70 and state.money<300, - ), - ]), - StageGroup(2, [ - t("O great leader of {nation_name}. May thou be kind enough to raise our minimum wage", - choices = { - "Sure": {"money": -20, "loyalty": +5}, - "Nope": {"loyalty": -5}, - }, - ), - t( - "Thank you! We have been extremely productive and produced so much more goods.", - choices = {"Great!": {"money": +30}}, - condition = lambda state: state.loyalty>90 and state.money<600, - ), - ]), - StageGroup(3, [ - t("O great leader of {nation_name}. May thou be kind enough to raise our minimum wage", - choices = { - "Sure": {"money": -40, "loyalty": +5}, - "Nope": {"loyalty": -5}, - }, - ), - t( - "Thank you! We have been extremely productive and produced so much more goods.", - choices = {"Great!": {"money": +60}}, - condition = lambda state: state.loyalty>110 and state.money<1200, - ), - ]), -]) - diff --git a/src/characters/wong_the_worker_chr.py b/src/characters/wong_the_worker_chr.py new file mode 100644 index 0000000..56097a7 --- /dev/null +++ b/src/characters/wong_the_worker_chr.py @@ -0,0 +1,109 @@ +"""Wong the worker template information.""" + +from src.templating import Actor, StageGroup +from src.templating import ChoiceTemplate as t # noqa: N813 + +# fmt: off +character = Actor("Wong the Worker", "url_here",[ + StageGroup(1, [ + t( + "O great leader of {nation_name}. May thou be kind enough to raise our minimum wage", + choices={ + "Sure": {"money": -10, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + t( + "Leader of {nation_name}, our factory needs new machinery to increase production.", + choices={ + "Approve funding": {"money": -15, "loyalty": +5}, + "Deny funding": {"loyalty": -5}, + }, + ), + t( + "We need better safety equipment for our workers. Can you help?", + choices={ + "Provide equipment": {"money": -10, "security": +5, "loyalty": +5}, + "Deny request": {"loyalty": -5, "security": -5}, + }, + ), + t( + "Excited to announce we'll be at the local job fair. Come and join our team! 👷‍♀️📈 #JobFair #HiringNow", + choices={ + "Like": {"loyalty": +2}, + "Share": {"loyalty": +3}, + "Ignore": {}, + }, + ), + ]), + StageGroup(2, [ + t( + "A nearby country is interested in a trade agreement. Should we pursue it?", + choices={ + "Yes, pursue agreement": {"money": +10, "world_opinion": +5}, + "No, it's not worth it": {"world_opinion": -5}, + }, + condition=lambda state: state.world_opinion > 30, + ), + t( + "We've had a surge in production. Should we give bonuses to the workers?", + choices={ + "Yes, give bonuses": {"money": -15, "loyalty": +10}, + "No, save the money": {"loyalty": -5}, + }, + ), + t( + "Welcoming foreign investment to boost our production capabilities. 🌍💼 #Investment #Growth", + choices={ + "Like": {"world_opinion": +2}, + "Share": {"world_opinion": +3}, + "Ignore": {}, + }, + ), + ]), + StageGroup(3, [ + t( + "Our competitors are starting to outpace us. Should we invest in cutting-edge technology?", + choices={ + "Yes, invest": {"money": -30, "security": +10, "loyalty": +5}, + "No, stick with what we have": {"loyalty": -5}, + }, + ), + t( + "We've been approached by a foreign investor. Should we accept their investment?", + choices={ + "Yes, accept": {"money": +25, "world_opinion": +10}, + "No, decline": {"world_opinion": -5}, + }, + ), + t( + "There's a growing unrest among workers due to long hours. Should we reduce working hours?", + choices={ + "Yes, reduce hours": {"money": -10, "loyalty": +10, "security": +5}, + "No, keep the hours": {"loyalty": -10, "security": -5}, + }, + ), + t( + "We've developed a new product. Should we launch it internationally?", + choices={ + "Yes, launch internationally": {"money": -20, "world_opinion": +15}, + "No, focus locally": {"world_opinion": -5}, + }, + ), + t( + "A new labor union is forming. Should we support its formation?", + choices={ + "Yes, support it": {"loyalty": +15, "security": +5}, + "No, oppose it": {"loyalty": -10, "security": -5}, + }, + ), + t( + "Launching our new product internationally! Exciting times ahead. 🌍🚀 #NewProduct #GlobalLaunch", + choices={ + "Like": {"world_opinion": +3}, + "Share": {"world_opinion": +4}, + "Ignore": {}, + }, + ), + ]), +]) From 4ba2df1bd41a6c7668aeb6cf293246aa7a508886 Mon Sep 17 00:00:00 2001 From: Sapient44 Date: Sun, 28 Jul 2024 19:24:36 +0530 Subject: [PATCH 092/116] update characters --- src/characters/Andy_the_athelete_chr.py | 29 +++++++------ src/characters/Aura_the_activist_chr.py | 28 +++++++++---- src/characters/Craig_the_contractor_chr.py | 48 ++++++++++++++++++++++ src/characters/Criag_the_contractor_chr.py | 46 --------------------- src/characters/Dave_the_doctor_chr.py | 39 +++++++++--------- src/characters/Else_the_engineer_chr.py | 37 +++++++++-------- src/characters/Fred_the_Farmer_chr.py | 32 +++++++-------- 7 files changed, 137 insertions(+), 122 deletions(-) create mode 100644 src/characters/Craig_the_contractor_chr.py delete mode 100644 src/characters/Criag_the_contractor_chr.py diff --git a/src/characters/Andy_the_athelete_chr.py b/src/characters/Andy_the_athelete_chr.py index 2c80c70..83d3496 100644 --- a/src/characters/Andy_the_athelete_chr.py +++ b/src/characters/Andy_the_athelete_chr.py @@ -7,39 +7,44 @@ "Dear leader of {nation_name}, please help our climate change marathon's fundraiser.", choices={ "Sure": {"money": -10, "loyalty": +5, "world_opinion" : +10}, - "Nope": {"loyalty": -5}, + "Nope": {"loyalty": -5, "world_opinion":-5}, }, ), t( "The marathon was phenomenal! Thanks for your support.", - choices = {"Great!": {"money": +15}}, - condition = lambda state: state.loyalty>70 and state.money<300, + choices = {"Great!": {"money": +15, "loyalty":+10}}, + condition = lambda state: state.money<150, ), ]), StageGroup(2, [ t("Dear leader of {nation_name}, please help our climate change marathon's fundraiser.", choices = { - "Sure": {"money": -20, "loyalty": +5}, - "Nope": {"loyalty": -5}, + "Sure": {"money": -15, "loyalty": +10, "world_opinion" : +10}, + "Nope": {"loyalty": -5, "world_opinion":-10}, }, ), t( "The marathon was phenomenal! Thanks for your support.", - choices = {"Great!": {"money": +30}}, - condition = lambda state: state.loyalty>90 and state.money<600, + choices = {"Great!": {"money": +20}}, + condition = lambda state: state.loyalty>20 and state.money<250, ), ]), StageGroup(3, [ t("Dear leader of {nation_name}, please help our climate change marathon's fundraiser.", choices = { - "Sure": {"money": -40, "loyalty": +5}, - "Nope": {"loyalty": -5}, + "Sure": {"money": -45, "loyalty": +5, "world_opinion" : +10}, + "Nope": {"loyalty": -5, "world_opinion":-5}, }, ), t( - "The marathon was phenomenal! Thanks for your support.", - choices = {"Great!": {"money": +60}}, - condition = lambda state: state.loyalty>110 and state.money<1200, + "The marathon was good, but several people were hospitalised due to heatstroke.", + choices = {"Oh no!": {"money": +30, "world_opinion" : -5}}, + condition = lambda state: state.loyalty>30 and state.money<300, + ), + t( + "The marathon went viral, and we were able to raise a lot of funds.", + choices = {"Awesome!": {"money": +70, "world_opinion" : +10}}, + condition = lambda state: state.loyalty>50 and state.money<200, ), ]), ]) diff --git a/src/characters/Aura_the_activist_chr.py b/src/characters/Aura_the_activist_chr.py index 74d3ba8..0e13d65 100644 --- a/src/characters/Aura_the_activist_chr.py +++ b/src/characters/Aura_the_activist_chr.py @@ -6,27 +6,32 @@ t( "Kind leader of {nation_name}! Please donate to our LGBTQ+ movement.", choices={ - "Sure": {"money": -10, "loyalty": +5}, - "Nope": {"loyalty": -5}, + "Sure": {"money": -10, "loyalty": +5, "world_opinion" : +10}, + "Nope": {"loyalty": -5, "world_opinion" : -15}, }, ), t( "Rights are Rights! You did the right thing.", - choices = {"Great!": {"money": +15}}, - condition = lambda state: state.loyalty>70 and state.money<300, + choices = {"Great!": {"money": +5}}, + condition = lambda state: state.loyalty>30, ), ]), StageGroup(2, [ t("Kind leader of {nation_name}! Please donate to our LGBTQ+ movement.", choices = { - "Sure": {"money": -20, "loyalty": +5}, - "Nope": {"loyalty": -5}, + "Sure": {"money": -20, "loyalty": +5, "world_opinion" : +10}, + "Nope": {"loyalty": -5, "world_opinion" : -10}, }, ), t( "Rights are Rights! You did the right thing.", - choices = {"Great!": {"money": +30}}, - condition = lambda state: state.loyalty>90 and state.money<600, + choices = {"Great!": {"money": +5}}, + condition = lambda state: state.loyalty>40, + ), + t( + "News in someone gassed the rally.", + choices = {"Great!": {"money": -10, "world_opinion" : -10, "security" : -5}}, + condition = lambda state: state.loyalty<50 and state.security<75, ), ]), StageGroup(3, [ @@ -39,7 +44,12 @@ t( "Rights are Rights! You did the right thing.", choices = {"Great!": {"money": +60}}, - condition = lambda state: state.loyalty>110 and state.money<1200, + condition = lambda state: state.loyalty>20 and state.money<100, + ), + t( + "News in someone gassed the rally.", + choices = {"Great!": {"money": -10, "world_opinion" : -10, "security" : -5}}, + condition = lambda state: state.loyalty<90 and state.security<100 ), ]), ]) diff --git a/src/characters/Craig_the_contractor_chr.py b/src/characters/Craig_the_contractor_chr.py new file mode 100644 index 0000000..c3d424e --- /dev/null +++ b/src/characters/Craig_the_contractor_chr.py @@ -0,0 +1,48 @@ +from src.templating import Actor, StageGroup +from src.templating import ChoiceTemplate as t + +character = Actor("Craig the Contractor", "url_here",[ + StageGroup(1, [ + t( + "Leader of {nation_name}, the roads need mending following the heavy use.", + choices={ + "Sure": {"money": -30, "loyalty": +5, "world_opinion" : +10, "security" : +10}, + "Nope": {"loyalty": -5, "world_opinion" : -5}, + }, + ), + t( + "The roads are being repaired thanks to the funds provided", + choices = {"Take more funds": {"money": -10, "loyalty" : +10}, + "Great": {"world_opinion" : +10} + }, + ), + ]), + StageGroup(2, [ + t("Leader of {nation_name}, we must upgrade our old government buildings.", + choices = { + "Sure": {"money": -20, "loyalty": +5}, + "Nope": {"loyalty": -5, "world_opinion" : -10, "security" : -5}, + }, + ), + t( + "We have shown great strides in the redevelopment plan, would you hold the inaguration ceremony.", + choices = {"Yes!": {"money": +5, "loyalty":+10, "world_opinion" : +10}, + "No!" : { "loyalty" : -5}}, + condition = lambda state: state.loyalty>40, + ), + ]), + StageGroup(3, [ + t("Leader of {nation_name}, the drains need to be declogged following the flood.", + choices = { + "Sure": {"money": -40, "loyalty": +5}, + "Nope": {"loyalty": -5}, + }, + ), + t( + "Great! Now the likelihood of floods would be decreased.", + choices = {"Great!": {"money": +20, "world_opinion" : +10, "loyalty" : +10}}, + condition = lambda state: state.loyalty>10, + ), + ]), +]) + diff --git a/src/characters/Criag_the_contractor_chr.py b/src/characters/Criag_the_contractor_chr.py deleted file mode 100644 index 88a0540..0000000 --- a/src/characters/Criag_the_contractor_chr.py +++ /dev/null @@ -1,46 +0,0 @@ -from src.templating import Actor, StageGroup -from src.templating import ChoiceTemplate as t - -character = Actor("Craig the Contractor", "url_here",[ - StageGroup(1, [ - t( - "Ruler of {nation_name}, the roads need mending following the earthquake.", - choices={ - "Sure": {"money": -10, "loyalty": +5}, - "Nope": {"loyalty": -5}, - }, - ), - t( - "", - choices = {"Great!": {"money": +15}}, - condition = lambda state: state.loyalty>70 and state.money<300, - ), - ]), - StageGroup(2, [ - t("Ruler of {nation_name}, the houses need patching following the tornado.", - choices = { - "Sure": {"money": -20, "loyalty": +5}, - "Nope": {"loyalty": -5}, - }, - ), - t( - "Great! Looks like a tornado never hit this place.", - choices = {"Great!": {"money": +30}}, - condition = lambda state: state.loyalty>90 and state.money<600, - ), - ]), - StageGroup(3, [ - t("Ruler of {nation_name}, the drains need to be declogged following the flood.", - choices = { - "Sure": {"money": -40, "loyalty": +5}, - "Nope": {"loyalty": -5}, - }, - ), - t( - "Great! Looks like a flood never hit this place.", - choices = {"Great!": {"money": +60}}, - condition = lambda state: state.loyalty>110 and state.money<1200, - ), - ]), -]) - diff --git a/src/characters/Dave_the_doctor_chr.py b/src/characters/Dave_the_doctor_chr.py index c960e0b..a32644a 100644 --- a/src/characters/Dave_the_doctor_chr.py +++ b/src/characters/Dave_the_doctor_chr.py @@ -4,43 +4,42 @@ character = Actor("Dave the Doctor", "url_here",[ StageGroup(1, [ t( - "Great ruler of {nation_name}, we need money to cure those with the flu!", + "Great Leader of {nation_name}, we need funding to create vaccines for this round of seasonal flu!", choices={ - "Sure": {"money": -10, "loyalty": +5}, - "Nope": {"loyalty": -5}, + "Sure": {"money": -10, "loyalty": +5, "world_opinion" : +5, "security" : +5}, + "Flu is easy to ovecome": {"loyalty": -5, "security" : -10, "world_opinion" : -5}, }, ), t( - "", - choices = {"Great!": {"money": +15}}, - condition = lambda state: state.loyalty>70 and state.money<300, + "We have manufactured the vaccines, but lack the funding to distribute, we require your assistance\ + on this", + choices = {"Sure": {"money": -15, "Nope":{ "world_opinion" : -20, "security" : -10, "loyalty" : -10}}} ), ]), StageGroup(2, [ - t("Great ruler of {nation_name}, we need money to cure those with Alzheimer's!", + t("Great Leader of {nation_name}, we need money to cure an blood borne epidemic !", choices = { - "Sure": {"money": -20, "loyalty": +5}, - "Nope": {"loyalty": -5}, + "Sure": {"money": -20, "loyalty": +5, "world_opinion" : +10}, + "Figure it yourself": {"loyalty": -5, "world_opinion" : -25, "security" : -20, "money" : -5}, }, ), t( - "", - choices = {"Great!": {"money": +30}}, - condition = lambda state: state.loyalty>90 and state.money<600, + "We have cured the epidemic", + choices = {"Great!": {"money": +30, "world_opinion" : +10, "loyalty" : +10}}, + condition = lambda state: state.loyalty>50 and state.money>500, ), ]), StageGroup(3, [ - t("Great ruler of {nation_name}, we need money to cure those with Parkinson's!", + t("Great Leader of {nation_name}, we need money to cure an blood borne epidemic !", choices = { - "Sure": {"money": -40, "loyalty": +5}, - "Nope": {"loyalty": -5}, + "Sure": {"money": -20, "loyalty": +5, "world_opinion" : +10}, + "Figure it yourself": {"loyalty": -5, "world_opinion" : -25, "security" : -20, "money" : -5}, }, ), t( - "", - choices = {"Great!": {"money": +60}}, - condition = lambda state: state.loyalty>110 and state.money<1200, + "We have cured the epidemic", + choices = {"Great!": {"money": +30, "world_opinion" : +10, "loyalty" : +10}}, + condition = lambda state: state.loyalty>50 and state.money>500, ), ]), -]) - +]) \ No newline at end of file diff --git a/src/characters/Else_the_engineer_chr.py b/src/characters/Else_the_engineer_chr.py index 2a4bdce..f5df141 100644 --- a/src/characters/Else_the_engineer_chr.py +++ b/src/characters/Else_the_engineer_chr.py @@ -4,42 +4,45 @@ character = Actor("Elsa the Engineer", "url_here",[ StageGroup(1, [ t( - "Ruler of {nation_name}, spare some money to design the army's vests!", + "Leader of {nation_name},it is my humble request allocate more budget towards research for\ + technology used in military field", choices={ - "Sure": {"money": -10, "loyalty": +5}, - "Nope": {"loyalty": -5}, + "Sure": {"money": -30, "loyalty": +5, "security" : +15}, + "Nope": {"loyalty": -5, "world_opinion" : -10}, }, ), t( - "The vests are bulletproof! Thanks.", - choices = {"Great!": {"money": +15}}, - condition = lambda state: state.loyalty>70 and state.money<300, + "We are competiting the cutting edge military tech now.", + choices = {"Great!": {"security": +10}}, + condition = lambda state: state.security<50, ), ]), StageGroup(2, [ - t("Ruler of {nation_name}, spare some money to design the army's equipment!", + t("Leader of {nation_name}, it is my humble request allocate some of the budget\ + in the manufacturing sector", choices = { - "Sure": {"money": -20, "loyalty": +5}, - "Nope": {"loyalty": -5}, + "Sure": {"money": -20, "loyalty": +5, "world_opinion" : +10}, + "Nope": {"loyalty": -5, "money" : -10}, }, ), t( - "The equipment is top-notch. Thanks.", + "Now we won't be requiring to be import certain important materials.", choices = {"Great!": {"money": +30}}, - condition = lambda state: state.loyalty>90 and state.money<600, + condition = lambda state: state.loyalty>40 and state.money>500, ), ]), StageGroup(3, [ - t("Ruler of {nation_name}, spare some money to design the army's weapons!", + t("Leader of {nation_name}, it is my humble request to give subsidies for \ + research companies in our nation", choices = { - "Sure": {"money": -40, "loyalty": +5}, - "Nope": {"loyalty": -5}, + "Sure": {"money": -30, "loyalty": +5 , "world_opinion" : +10}, + "Nope": {"loyalty": -5 , "world_opinion" : -10}, }, ), t( - "The weapons are deadly! Thanks.", - choices = {"Great!": {"money": +60}}, - condition = lambda state: state.loyalty>110 and state.money<1200, + "Now the nation would be filing more patents.", + choices = {"Great!": {"money": +50}}, + condition = lambda state: state.security>50 and state.money>500, ), ]), ]) diff --git a/src/characters/Fred_the_Farmer_chr.py b/src/characters/Fred_the_Farmer_chr.py index 7bec255..79373d3 100644 --- a/src/characters/Fred_the_Farmer_chr.py +++ b/src/characters/Fred_the_Farmer_chr.py @@ -9,45 +9,41 @@ character = Actor("Fred the Farmer", "url_here",[ StageGroup(1, [ t( - "Hello, leader of {nation_name}. Can you spare some money for our new farming equipment?", + "Hello, leader of {nation_name}. We request you to give subsidies for seeds", choices={ "Sure": {"money": -10, "loyalty": +5}, "Nope": {"loyalty": -5}, }, ), t( - "Things are great, we managed to grow even more crops than expected.\ - Thank you for your help, leader! \n Here, have this small gift from our community", - choices = {"Thanks!": {"money": +15}}, - condition = lambda state: state.loyalty>70 and state.money<300, + "Thank you for your help, leader!", + choices = {"It's my duty": {"money": +15}}, + condition = lambda state: state.loyalty>50, ), ]), StageGroup(2, [ - t("Hello, leader of {nation_name}. Can you spare some money for our new farming equipment?", + t("Hello, leader of {nation_name}. Can you raise the Minimum Support Price for crops", choices = { - "Sure": {"money": -20, "loyalty": +5}, - "Nope": {"loyalty": -5}, + "Sure": {"money": -20, "loyalty": +15}, + "Nope": {"loyalty": -10}, }, ), t( - "Things are great, we managed to grow even more crops than expected.\ - Thank you for your help, leader! \n Here, have this small gift from our community", - choices = {"Thanks!": {"money": +30}}, - condition = lambda state: state.loyalty>90 and state.money<600, + "Our votes will stay with you", + choices = {"Thanks!": {"loyalty": +20}}, + condition = lambda state: state.loyalty>70, ), ]), StageGroup(3, [ - t("Hello, leader of {nation_name}. Can you spare some money for our new farming equipment?", + t("Hello, leader of {nation_name}. Can you give subsidies for farming equipment?", choices = { - "Sure": {"money": -40, "loyalty": +5}, + "Sure": {"money": -20, "loyalty": +15}, "Nope": {"loyalty": -5}, }, ), t( - "Things are great, we managed to grow even more crops than expected.\ - Thank you for your help, leader! \n Here, have this small gift from our community", - choices = {"Thanks!": {"money": +60}}, - condition = lambda state: state.loyalty>110 and state.money<1200, + "We are able to do our work more effectively now", + choices = {"Great!": {"money": +20}}, ), ]), ]) From 87cd8e1c65845441ced5c88941b9d1b23fe9946f Mon Sep 17 00:00:00 2001 From: Maheshkumar Date: Sun, 28 Jul 2024 19:25:18 +0530 Subject: [PATCH 093/116] Add alex the analyst --- src/characters/alex_the_analyst_chr.py | 116 +++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 src/characters/alex_the_analyst_chr.py diff --git a/src/characters/alex_the_analyst_chr.py b/src/characters/alex_the_analyst_chr.py new file mode 100644 index 0000000..12d3c4d --- /dev/null +++ b/src/characters/alex_the_analyst_chr.py @@ -0,0 +1,116 @@ +"""Alex the analyst media templates and information.""" + +from src.templating import Actor, StageGroup +from src.templating import ChoiceTemplate as t # noqa: N813 + +# fmt: off +character = Actor("Alex the Analyst", "url_here",[ + StageGroup(1, [ + t( + "When the factory gets new machinery and the production line still breaks down. 🤦‍♂️🏭 #FactoryProblems #MondayBlues", # noqa: E501 + choices={ + "Like": {"loyalty": +1}, + "Share": {"loyalty": +2}, + "Ignore": {}, + }, + ), + t( + "Employee of the month: The coffee machine. ☕️🏆 #UnsungHero #OfficeLife", + choices={ + "Like": {"loyalty": +1}, + "Share": {"loyalty": +2}, + "Ignore": {}, + }, + ), + t( + "Joke: Why don't scientists trust atoms? Because they make up everything! 😂🔬 #ScienceJokes #Humor", + choices={ + "Like": {"loyalty": +1}, + "Share": {"loyalty": +2}, + "Ignore": {}, + }, + ), + t( + "Criticism: Our new safety equipment rollout has been slower than anticipated. Workers are concerned. 🛡️🕒 #SafetyFirst #WorkplaceIssues", # noqa: E501 + choices={ + "Acknowledge": {"loyalty": +2, "security": +2}, + "Ignore": {"loyalty": -1, "security": -1}, + }, + ), + ]), + StageGroup(2, [ + t( + "Meme: When you finally finish a huge project and your boss says, 'Great, now here's another one.' 😅📈 #WorkLife #NeverEndingStory", # noqa: E501 + choices={ + "Like": {"loyalty": +1}, + "Share": {"loyalty": +2}, + "Ignore": {}, + }, + ), + t( + "Criticism: There's been talk about inadequate training programs. Employees feel unprepared. 📚🛠️ #TrainingIssues #WorkplaceConcerns", # noqa: E501 + choices={ + "Address": {"loyalty": +2}, + "Ignore": {"loyalty": -2}, + }, + ), + t( + "Joke: I told my boss three companies were after me and I needed a raise. We laughed. Then I said, 'Seriously, I am going to Amazon, Google, or Netflix if you don not give me a raise.' 😂💼 #JobHumor #SalaryTalks", # noqa: E501 + choices={ + "Like": {"loyalty": +1}, + "Share": {"loyalty": +2}, + "Ignore": {}, + }, + ), + t( + "Positive Feedback: Great job team on hitting our production goals! 🎉📊 #TeamWork #Success", + choices={ + "Like": {"loyalty": +2}, + "Share": {"loyalty": +3}, + "Ignore": {}, + }, + ), + t( + "Meme: When the coffee machine is broken and you have a full day of meetings ahead. ☕️💀 #OfficeStruggles #NeedCoffee", # noqa: E501 + choices={ + "Like": {"loyalty": +1}, + "Share": {"loyalty": +2}, + "Ignore": {}, + }, + ), + ]), + StageGroup(3, [ + t( + "Meme: When you realize the 'urgent' email you sent an hour ago still has not been read. 📧😩 #EmailProblems #WorkFrustrations", # noqa: E501 + choices={ + "Like": {"loyalty": +1}, + "Share": {"loyalty": +2}, + "Ignore": {}, + }, + ), + t( + "Criticism: Recent changes in work hours are causing stress among workers. Something needs to be done. ⏰😟 #WorkHours #EmployeeWellbeing", # noqa: E501 + choices={ + "Address": {"loyalty": +3, "security": +2}, + "Ignore": {"loyalty": -3, "security": -2}, + }, + ), + t( + "Joke: Why did the scarecrow get a promotion? Because he was outstanding in his field! 😂🌾 #WorkJokes #Promotion", # noqa: E501 + choices={ + "Like": {"loyalty": +1}, + "Share": {"loyalty": +2}, + "Ignore": {}, + }, + ), + t( + "Meme: When you realize it is Friday and you have made it through another week. 🎉🙌 #TGIF #WeekendVibes", + choices={ + "Like": {"loyalty": +1}, + "Share": {"loyalty": +2}, + "Ignore": {}, + }, + ), + ]), +], weight=200, +) From 5f0b4a42ca20aba9cb10a79735206783657acb7a Mon Sep 17 00:00:00 2001 From: Maheshkumar Date: Sun, 28 Jul 2024 19:31:38 +0530 Subject: [PATCH 094/116] Update formatting --- ...Andy_the_athelete_chr.py => andy_the_athelete_chr.py} | 6 ++++-- ...Aura_the_activist_chr.py => aura_the_activist_chr.py} | 8 +++++--- ...the_contractor_chr.py => craig_the_contractor_chr.py} | 8 +++++--- .../{Dave_the_doctor_chr.py => dave_the_doctor_chr.py} | 9 ++++++--- ...Else_the_engineer_chr.py => else_the_engineer_chr.py} | 5 ++++- .../{Fred_the_Farmer_chr.py => fred_the_farmer_chr.py} | 6 ++---- 6 files changed, 26 insertions(+), 16 deletions(-) rename src/characters/{Andy_the_athelete_chr.py => andy_the_athelete_chr.py} (95%) rename src/characters/{Aura_the_activist_chr.py => aura_the_activist_chr.py} (94%) rename src/characters/{Craig_the_contractor_chr.py => craig_the_contractor_chr.py} (91%) rename src/characters/{Dave_the_doctor_chr.py => dave_the_doctor_chr.py} (91%) rename src/characters/{Else_the_engineer_chr.py => else_the_engineer_chr.py} (94%) rename src/characters/{Fred_the_Farmer_chr.py => fred_the_farmer_chr.py} (96%) diff --git a/src/characters/Andy_the_athelete_chr.py b/src/characters/andy_the_athelete_chr.py similarity index 95% rename from src/characters/Andy_the_athelete_chr.py rename to src/characters/andy_the_athelete_chr.py index 83d3496..f71a258 100644 --- a/src/characters/Andy_the_athelete_chr.py +++ b/src/characters/andy_the_athelete_chr.py @@ -1,6 +1,9 @@ +"""Andy character template.""" + from src.templating import Actor, StageGroup -from src.templating import ChoiceTemplate as t +from src.templating import ChoiceTemplate as t # noqa: N813 +# fmt: off character = Actor("Andy the Athlete", "url_here",[ StageGroup(1, [ t( @@ -48,4 +51,3 @@ ), ]), ]) - diff --git a/src/characters/Aura_the_activist_chr.py b/src/characters/aura_the_activist_chr.py similarity index 94% rename from src/characters/Aura_the_activist_chr.py rename to src/characters/aura_the_activist_chr.py index 0e13d65..7e75551 100644 --- a/src/characters/Aura_the_activist_chr.py +++ b/src/characters/aura_the_activist_chr.py @@ -1,6 +1,9 @@ +"""Aura character template.""" + from src.templating import Actor, StageGroup -from src.templating import ChoiceTemplate as t +from src.templating import ChoiceTemplate as t # noqa: N813 +# fmt: off character = Actor("Aura the Activist", "url_here",[ StageGroup(1, [ t( @@ -49,8 +52,7 @@ t( "News in someone gassed the rally.", choices = {"Great!": {"money": -10, "world_opinion" : -10, "security" : -5}}, - condition = lambda state: state.loyalty<90 and state.security<100 + condition = lambda state: state.loyalty<90 and state.security<100, ), ]), ]) - diff --git a/src/characters/Craig_the_contractor_chr.py b/src/characters/craig_the_contractor_chr.py similarity index 91% rename from src/characters/Craig_the_contractor_chr.py rename to src/characters/craig_the_contractor_chr.py index c3d424e..30422ec 100644 --- a/src/characters/Craig_the_contractor_chr.py +++ b/src/characters/craig_the_contractor_chr.py @@ -1,6 +1,9 @@ +"""Craig character template.""" + from src.templating import Actor, StageGroup -from src.templating import ChoiceTemplate as t +from src.templating import ChoiceTemplate as t # noqa: N813 +# fmt: off character = Actor("Craig the Contractor", "url_here",[ StageGroup(1, [ t( @@ -13,7 +16,7 @@ t( "The roads are being repaired thanks to the funds provided", choices = {"Take more funds": {"money": -10, "loyalty" : +10}, - "Great": {"world_opinion" : +10} + "Great": {"world_opinion" : +10}, }, ), ]), @@ -45,4 +48,3 @@ ), ]), ]) - diff --git a/src/characters/Dave_the_doctor_chr.py b/src/characters/dave_the_doctor_chr.py similarity index 91% rename from src/characters/Dave_the_doctor_chr.py rename to src/characters/dave_the_doctor_chr.py index a32644a..44492c1 100644 --- a/src/characters/Dave_the_doctor_chr.py +++ b/src/characters/dave_the_doctor_chr.py @@ -1,6 +1,9 @@ +"""Dave the doctor character template.""" + from src.templating import Actor, StageGroup -from src.templating import ChoiceTemplate as t +from src.templating import ChoiceTemplate as t # noqa: N813 +# fmt: off character = Actor("Dave the Doctor", "url_here",[ StageGroup(1, [ t( @@ -13,7 +16,7 @@ t( "We have manufactured the vaccines, but lack the funding to distribute, we require your assistance\ on this", - choices = {"Sure": {"money": -15, "Nope":{ "world_opinion" : -20, "security" : -10, "loyalty" : -10}}} + choices = {"Sure": {"money": -15, "Nope":{ "world_opinion" : -20, "security" : -10, "loyalty" : -10}}}, ), ]), StageGroup(2, [ @@ -42,4 +45,4 @@ condition = lambda state: state.loyalty>50 and state.money>500, ), ]), -]) \ No newline at end of file +]) diff --git a/src/characters/Else_the_engineer_chr.py b/src/characters/else_the_engineer_chr.py similarity index 94% rename from src/characters/Else_the_engineer_chr.py rename to src/characters/else_the_engineer_chr.py index f5df141..20ece19 100644 --- a/src/characters/Else_the_engineer_chr.py +++ b/src/characters/else_the_engineer_chr.py @@ -1,6 +1,9 @@ +"""Else character template.""" + from src.templating import Actor, StageGroup -from src.templating import ChoiceTemplate as t +from src.templating import ChoiceTemplate as t # noqa: N813 +# fmt: off character = Actor("Elsa the Engineer", "url_here",[ StageGroup(1, [ t( diff --git a/src/characters/Fred_the_Farmer_chr.py b/src/characters/fred_the_farmer_chr.py similarity index 96% rename from src/characters/Fred_the_Farmer_chr.py rename to src/characters/fred_the_farmer_chr.py index 79373d3..00d8719 100644 --- a/src/characters/Fred_the_Farmer_chr.py +++ b/src/characters/fred_the_farmer_chr.py @@ -1,10 +1,8 @@ -"""Example character template.""" +"""Fred character template.""" from src.templating import Actor, StageGroup from src.templating import ChoiceTemplate as t # noqa: N813 -# fmt: off - # fmt: off character = Actor("Fred the Farmer", "url_here",[ StageGroup(1, [ @@ -45,5 +43,5 @@ "We are able to do our work more effectively now", choices = {"Great!": {"money": +20}}, ), - ]), + ]), ]) From 840bc56a5565b6270237e17afd378dc357338ceb Mon Sep 17 00:00:00 2001 From: Maheshkumar Date: Sun, 28 Jul 2024 19:40:34 +0530 Subject: [PATCH 095/116] Update andy the athlete --- src/characters/andy_the_athelete_chr.py | 68 ++++++++++++++++++++----- 1 file changed, 56 insertions(+), 12 deletions(-) diff --git a/src/characters/andy_the_athelete_chr.py b/src/characters/andy_the_athelete_chr.py index f71a258..955bbf7 100644 --- a/src/characters/andy_the_athelete_chr.py +++ b/src/characters/andy_the_athelete_chr.py @@ -14,22 +14,50 @@ }, ), t( - "The marathon was phenomenal! Thanks for your support.", - choices = {"Great!": {"money": +15, "loyalty":+10}}, - condition = lambda state: state.money<150, + "Leader of {nation_name}, our national team needs better training facilities.", + choices={ + "Approve funding": {"money": -15, "loyalty": +5}, + "Deny funding": {"loyalty": -5}, + }, + ), + t( + "Our athletes need new uniforms. Can we get them?", + choices={ + "Provide new uniforms": {"money": -5, "loyalty": +2}, + "No, use the old ones": {"loyalty": -2}, + }, + ), + t( + "Some athletes are requesting personal trainers. Should we approve this?", + choices={ + "Approve personal trainers": {"money": -8, "loyalty": +3}, + "Deny the request": {"loyalty": -2}, + }, + condition=lambda state: state.money > 50, ), ]), StageGroup(2, [ - t("Dear leader of {nation_name}, please help our climate change marathon's fundraiser.", - choices = { - "Sure": {"money": -15, "loyalty": +10, "world_opinion" : +10}, - "Nope": {"loyalty": -5, "world_opinion":-10}, - }, - ), t( - "The marathon was phenomenal! Thanks for your support.", - choices = {"Great!": {"money": +20}}, - condition = lambda state: state.loyalty>20 and state.money<250, + "We've been invited to a major international sports event. Should we participate?", + choices={ + "Yes, participate": {"money": -20, "world_opinion": +10, "loyalty": +5}, + "No, it's too expensive": {"loyalty": -5, "world_opinion": -5}, + }, + ), + t( + "The national team's performance is dropping. Should we invest in a performance coach?", + choices={ + "Invest in performance coach": {"money": -12, "loyalty": +5, "world_opinion": +3}, + "No, it's unnecessary": {"loyalty": -3, "world_opinion": -2}, + }, + condition=lambda state: state.loyalty < 60, + ), + t( + "One of our top athletes has a serious injury. Should we fund their medical treatment?", + choices={ + "Yes, fund treatment": {"money": -15, "loyalty": +10}, + "No, it's too costly": {"loyalty": -10}, + }, ), ]), StageGroup(3, [ @@ -49,5 +77,21 @@ choices = {"Awesome!": {"money": +70, "world_opinion" : +10}}, condition = lambda state: state.loyalty>50 and state.money<200, ), + t( + "We have a chance to sign a sponsorship deal with a major brand. Should we proceed?", + choices={ + "Yes, sign the deal": {"money": +20, "world_opinion": +10}, + "No, it conflicts with our values": {"loyalty": +5, "world_opinion": -5}, + }, + condition=lambda state: state.world_opinion > 50, + ), + t( + "The sports facilities need renovations to meet international standards. Should we invest?", + choices={ + "Invest in renovations": {"money": -25, "loyalty": +10, "world_opinion": +5}, + "No, they are fine as they are": {"loyalty": -5, "world_opinion": -3}, + }, + condition=lambda state: state.money > 200, + ), ]), ]) From 7e42235d5208792f3177e07703d76afe860beedd Mon Sep 17 00:00:00 2001 From: Maheshkumar Date: Sun, 28 Jul 2024 19:43:57 +0530 Subject: [PATCH 096/116] Updat aura the activist --- src/characters/aura_the_activist_chr.py | 60 ++++++++++++++++--------- 1 file changed, 39 insertions(+), 21 deletions(-) diff --git a/src/characters/aura_the_activist_chr.py b/src/characters/aura_the_activist_chr.py index 7e75551..239b050 100644 --- a/src/characters/aura_the_activist_chr.py +++ b/src/characters/aura_the_activist_chr.py @@ -18,18 +18,29 @@ choices = {"Great!": {"money": +5}}, condition = lambda state: state.loyalty>30, ), + t( + "There's an opportunity to host a human rights seminar in our nation. Should we do it?", + choices={ + "Yes, host the seminar": {"money": -8, "world_opinion": +5, "loyalty": +3}, + "No, it's too costly": {"loyalty": -3, "world_opinion": -2}, + }, + ), ]), StageGroup(2, [ - t("Kind leader of {nation_name}! Please donate to our LGBTQ+ movement.", - choices = { - "Sure": {"money": -20, "loyalty": +5, "world_opinion" : +10}, - "Nope": {"loyalty": -5, "world_opinion" : -10}, - }, - ), t( - "Rights are Rights! You did the right thing.", - choices = {"Great!": {"money": +5}}, - condition = lambda state: state.loyalty>40, + "One of our leading activists has been unjustly detained. Should we fund their legal defense?", + choices={ + "Yes, fund legal defense": {"money": -10, "loyalty": +10}, + "No, it's too costly": {"loyalty": -10}, + }, + ), + t( + "The environmental group is requesting funds to plant trees across the city. Should we approve this?", + choices={ + "Approve funds": {"money": -8, "loyalty": +5, "world_opinion": +3}, + "Deny the request": {"loyalty": -4}, + }, + condition=lambda state: state.loyalty > 60, ), t( "News in someone gassed the rally.", @@ -38,21 +49,28 @@ ), ]), StageGroup(3, [ - t("Kind leader of {nation_name}! Please donate to our LGBTQ+ movement.", - choices = { - "Sure": {"money": -40, "loyalty": +5}, - "Nope": {"loyalty": -5}, - }, - ), t( - "Rights are Rights! You did the right thing.", - choices = {"Great!": {"money": +60}}, - condition = lambda state: state.loyalty>20 and state.money<100, + "The international community is considering us for a prestigious human rights award. Should we lobby for it?", # noqa: E501 + choices={ + "Yes, lobby for the award": {"money": -10, "world_opinion": +15, "loyalty": +5}, + "No, it's not worth it": {"world_opinion": -5}, + }, ), t( - "News in someone gassed the rally.", - choices = {"Great!": {"money": -10, "world_opinion" : -10, "security" : -5}}, - condition = lambda state: state.loyalty<90 and state.security<100, + "Several community leaders are asking for funds to improve local social programs. Should we approve it?", + choices={ + "Approve the funds": {"money": -20, "loyalty": +10}, + "No, we can't afford it": {"loyalty": -5}, + }, + condition=lambda state: state.money > 100, + ), + t( + "There's a push to create stricter environmental regulations. Should we support it?", + choices={ + "Yes, support the regulations": {"security": +5, "world_opinion": +10}, + "No, it's too restrictive": {"loyalty": -5, "world_opinion": -5}, + }, + condition=lambda state: state.security > 50, ), ]), ]) From 93cb9020cc2f2ab0c7996d46cad7b5977f10859d Mon Sep 17 00:00:00 2001 From: Maheshkumar Date: Sun, 28 Jul 2024 19:46:58 +0530 Subject: [PATCH 097/116] Update craig the contractor --- src/characters/craig_the_contractor_chr.py | 41 +++++++++++++++++++--- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/src/characters/craig_the_contractor_chr.py b/src/characters/craig_the_contractor_chr.py index 30422ec..8677279 100644 --- a/src/characters/craig_the_contractor_chr.py +++ b/src/characters/craig_the_contractor_chr.py @@ -19,6 +19,14 @@ "Great": {"world_opinion" : +10}, }, ), + t( + "Some contractors are requesting bonuses for their hard work. Should we approve this?", + choices={ + "Approve bonuses": {"money": -8, "loyalty": +4}, + "Deny the request": {"loyalty": -3}, + }, + condition=lambda state: state.money > 50, + ), ]), StageGroup(2, [ t("Leader of {nation_name}, we must upgrade our old government buildings.", @@ -28,11 +36,26 @@ }, ), t( - "We have shown great strides in the redevelopment plan, would you hold the inaguration ceremony.", + "We have shown great strides in the redevelopment plan, would you hold the inaguration ceremony?", choices = {"Yes!": {"money": +5, "loyalty":+10, "world_opinion" : +10}, "No!" : { "loyalty" : -5}}, condition = lambda state: state.loyalty>40, ), + t( + "We've been asked to build a new hospital in a rural area. Should we take on this project?", + choices={ + "Yes, build the hospital": {"money": -20, "security": +10, "loyalty": +5}, + "No, it's too expensive": {"security": -5, "loyalty": -3}, + }, + ), + t( + "The construction team suggests a new eco-friendly building method. Should we adopt it?", + choices={ + "Adopt the new method": {"money": -10, "world_opinion": +10}, + "Stick to old methods": {"world_opinion": -5}, + }, + condition=lambda state: state.world_opinion > 50, + ), ]), StageGroup(3, [ t("Leader of {nation_name}, the drains need to be declogged following the flood.", @@ -42,9 +65,19 @@ }, ), t( - "Great! Now the likelihood of floods would be decreased.", - choices = {"Great!": {"money": +20, "world_opinion" : +10, "loyalty" : +10}}, - condition = lambda state: state.loyalty>10, + "The international community is considering us for a prestigious construction award. Should we lobby for it?", # noqa: E501 + choices={ + "Yes, lobby for the award": {"money": -10, "world_opinion": +15, "loyalty": +5}, + "No, it's not worth it": {"world_opinion": -5}, + }, + ), + t( + "Several community leaders are asking for funds to improve local infrastructure. Should we approve it?", + choices={ + "Approve the funds": {"money": -20, "security": +10}, + "No, we can't afford it": {"security": -5}, + }, + condition=lambda state: state.money > 100, ), ]), ]) From d661d9fa64279b91453a0f2f44e23387bf1db2de Mon Sep 17 00:00:00 2001 From: Maheshkumar Date: Sun, 28 Jul 2024 19:50:46 +0530 Subject: [PATCH 098/116] Update dave the doctor --- src/characters/dave_the_doctor_chr.py | 84 +++++++++++++++++++++------ 1 file changed, 65 insertions(+), 19 deletions(-) diff --git a/src/characters/dave_the_doctor_chr.py b/src/characters/dave_the_doctor_chr.py index 44492c1..f1a3c83 100644 --- a/src/characters/dave_the_doctor_chr.py +++ b/src/characters/dave_the_doctor_chr.py @@ -18,31 +18,77 @@ on this", choices = {"Sure": {"money": -15, "Nope":{ "world_opinion" : -20, "security" : -10, "loyalty" : -10}}}, ), + t( + "Leader of {nation_name}, our hospital is overwhelmed with patients. We need additional funding to hire more staff.", # noqa: E501 + choices={ + "Approve funding": {"money": -20, "security": +5, "loyalty": +3}, + "Deny funding": {"security": -5, "loyalty": -3}, + }, + ), + t( + "There's a flu outbreak in the neighboring regions. Should we prepare by stocking up on vaccines?", + choices={ + "Yes, stock up on vaccines": {"money": -10, "security": +10, "world_opinion": +5}, + "No, it's not necessary": {"security": -5, "world_opinion": -3}, + }, + ), + t( + "Some doctors are requesting additional training to improve their skills. Should we invest in their education?", # noqa: E501 + choices={ + "Invest in training": {"money": -10, "loyalty": +3, "security": +2}, + "No, they are skilled enough": {"loyalty": -2, "security": -1}, + }, + condition=lambda state: state.money > 30, + ), ]), StageGroup(2, [ - t("Great Leader of {nation_name}, we need money to cure an blood borne epidemic !", - choices = { - "Sure": {"money": -20, "loyalty": +5, "world_opinion" : +10}, - "Figure it yourself": {"loyalty": -5, "world_opinion" : -25, "security" : -20, "money" : -5}, - }, - ), t( - "We have cured the epidemic", - choices = {"Great!": {"money": +30, "world_opinion" : +10, "loyalty" : +10}}, - condition = lambda state: state.loyalty>50 and state.money>500, + "There's a proposal to build a new children's hospital in a underserved area. Should we support it?", + choices={ + "Yes, build the hospital": {"money": -25, "loyalty": +10, "world_opinion": +5}, + "No, we can't afford it": {"loyalty": -5, "world_opinion": -3}, + }, + ), + t( + "We have the opportunity to participate in a global health initiative. Should we join?", + choices={ + "Yes, join the initiative": {"money": -15, "world_opinion": +15, "security": +5}, + "No, it's too costly": {"world_opinion": -5, "security": -3}, + }, + condition=lambda state: state.world_opinion > 40, + ), + t( + "We've been offered a partnership with a leading medical research facility. Should we accept?", + choices={ + "Yes, accept the partnership": {"money": -20, "world_opinion": +10, "security": +5}, + "No, we can manage on our own": {"world_opinion": -5, "security": -3}, + }, ), ]), StageGroup(3, [ - t("Great Leader of {nation_name}, we need money to cure an blood borne epidemic !", - choices = { - "Sure": {"money": -20, "loyalty": +5, "world_opinion" : +10}, - "Figure it yourself": {"loyalty": -5, "world_opinion" : -25, "security" : -20, "money" : -5}, - }, - ), - t( - "We have cured the epidemic", - choices = {"Great!": {"money": +30, "world_opinion" : +10, "loyalty" : +10}}, - condition = lambda state: state.loyalty>50 and state.money>500, + t( + "We have the chance to lead a groundbreaking health research project. Should we take the lead?", + choices={ + "Lead the project": {"money": -30, "world_opinion": +15, "security": +10}, + "No, let others lead": {"world_opinion": -5, "security": -3}, + }, + condition=lambda state: state.security > 50, + ), + t( + "A leading pharmaceutical company wants to conduct trials in our nation. Should we allow it?", + choices={ + "Allow the trials": {"money": +20, "security": +5, "world_opinion": +5}, + "Deny the trials": {"security": -5, "world_opinion": -5}, + }, + condition=lambda state: state.money < 50, + ), + t( + "The community is requesting free health check-ups. Should we organize this?", + choices={ + "Yes, organize free check-ups": {"money": -15, "loyalty": +10, "world_opinion": +5}, + "No, it's too costly": {"loyalty": -5, "world_opinion": -3}, + }, + condition=lambda state: state.loyalty > 50, ), ]), ]) From b07588313b352d3e610aae41fd64b9b31066c86c Mon Sep 17 00:00:00 2001 From: Sapient44 Date: Sun, 28 Jul 2024 19:51:14 +0530 Subject: [PATCH 099/116] Add images --- src/characters/alex_the_analyst_chr.py | 2 +- src/characters/andy_the_athelete_chr.py | 2 +- src/characters/aura_the_activist_chr.py | 2 +- src/characters/craig_the_contractor_chr.py | 2 +- src/characters/dave_the_doctor_chr.py | 2 +- src/characters/else_the_engineer_chr.py | 2 +- src/characters/fred_the_farmer_chr.py | 2 +- src/characters/gary_the_general_chr.py | 2 +- src/characters/sam_the_scientist_chr.py | 2 +- src/characters/sandra_the_spy_chr.py | 2 +- src/characters/sheela_the_singer_chr.py | 2 +- src/characters/wong_the_worker_chr.py | 2 +- 12 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/characters/alex_the_analyst_chr.py b/src/characters/alex_the_analyst_chr.py index 12d3c4d..65346b3 100644 --- a/src/characters/alex_the_analyst_chr.py +++ b/src/characters/alex_the_analyst_chr.py @@ -4,7 +4,7 @@ from src.templating import ChoiceTemplate as t # noqa: N813 # fmt: off -character = Actor("Alex the Analyst", "url_here",[ +character = Actor("Alex the Analyst", "https://i.postimg.cc/D0mxzB3d/Alex.webp",[ StageGroup(1, [ t( "When the factory gets new machinery and the production line still breaks down. 🤦‍♂️🏭 #FactoryProblems #MondayBlues", # noqa: E501 diff --git a/src/characters/andy_the_athelete_chr.py b/src/characters/andy_the_athelete_chr.py index 955bbf7..050c6bc 100644 --- a/src/characters/andy_the_athelete_chr.py +++ b/src/characters/andy_the_athelete_chr.py @@ -4,7 +4,7 @@ from src.templating import ChoiceTemplate as t # noqa: N813 # fmt: off -character = Actor("Andy the Athlete", "url_here",[ +character = Actor("Andy the Athlete", "https://i.postimg.cc/50h92KZ8/Andy.webp",[ StageGroup(1, [ t( "Dear leader of {nation_name}, please help our climate change marathon's fundraiser.", diff --git a/src/characters/aura_the_activist_chr.py b/src/characters/aura_the_activist_chr.py index 239b050..6e2fbf1 100644 --- a/src/characters/aura_the_activist_chr.py +++ b/src/characters/aura_the_activist_chr.py @@ -4,7 +4,7 @@ from src.templating import ChoiceTemplate as t # noqa: N813 # fmt: off -character = Actor("Aura the Activist", "url_here",[ +character = Actor("Aura the Activist", "https://i.postimg.cc/qvw7xL3d/aura.jpg",[ StageGroup(1, [ t( "Kind leader of {nation_name}! Please donate to our LGBTQ+ movement.", diff --git a/src/characters/craig_the_contractor_chr.py b/src/characters/craig_the_contractor_chr.py index 8677279..ebe654a 100644 --- a/src/characters/craig_the_contractor_chr.py +++ b/src/characters/craig_the_contractor_chr.py @@ -4,7 +4,7 @@ from src.templating import ChoiceTemplate as t # noqa: N813 # fmt: off -character = Actor("Craig the Contractor", "url_here",[ +character = Actor("Craig the Contractor", "https://i.postimg.cc/fbbR29cS/Craig.webp",[ StageGroup(1, [ t( "Leader of {nation_name}, the roads need mending following the heavy use.", diff --git a/src/characters/dave_the_doctor_chr.py b/src/characters/dave_the_doctor_chr.py index 44492c1..4029780 100644 --- a/src/characters/dave_the_doctor_chr.py +++ b/src/characters/dave_the_doctor_chr.py @@ -4,7 +4,7 @@ from src.templating import ChoiceTemplate as t # noqa: N813 # fmt: off -character = Actor("Dave the Doctor", "url_here",[ +character = Actor("Dave the Doctor", "https://i.postimg.cc/Y9GqkByp/Dave.webp",[ StageGroup(1, [ t( "Great Leader of {nation_name}, we need funding to create vaccines for this round of seasonal flu!", diff --git a/src/characters/else_the_engineer_chr.py b/src/characters/else_the_engineer_chr.py index 20ece19..a6f1479 100644 --- a/src/characters/else_the_engineer_chr.py +++ b/src/characters/else_the_engineer_chr.py @@ -4,7 +4,7 @@ from src.templating import ChoiceTemplate as t # noqa: N813 # fmt: off -character = Actor("Elsa the Engineer", "url_here",[ +character = Actor("Elsa the Engineer", "https://i.postimg.cc/kGWJ6Fmz/elsa.webp",[ StageGroup(1, [ t( "Leader of {nation_name},it is my humble request allocate more budget towards research for\ diff --git a/src/characters/fred_the_farmer_chr.py b/src/characters/fred_the_farmer_chr.py index 00d8719..5e095a7 100644 --- a/src/characters/fred_the_farmer_chr.py +++ b/src/characters/fred_the_farmer_chr.py @@ -4,7 +4,7 @@ from src.templating import ChoiceTemplate as t # noqa: N813 # fmt: off -character = Actor("Fred the Farmer", "url_here",[ +character = Actor("Fred the Farmer", "https://i.postimg.cc/ZKcmGqK8/Fred.webp",[ StageGroup(1, [ t( "Hello, leader of {nation_name}. We request you to give subsidies for seeds", diff --git a/src/characters/gary_the_general_chr.py b/src/characters/gary_the_general_chr.py index 480b6c2..f709ab8 100644 --- a/src/characters/gary_the_general_chr.py +++ b/src/characters/gary_the_general_chr.py @@ -4,7 +4,7 @@ from src.templating import ChoiceTemplate as t # noqa: N813 # fmt: off -character = Actor("Gary the General", "url_here",[ +character = Actor("Gary the General", "https://i.postimg.cc/c134bZnh/gary.webp",[ StageGroup(1, [ t( "Sir, ruler of {nation_name}! The army needs new helmets to improve safety!", diff --git a/src/characters/sam_the_scientist_chr.py b/src/characters/sam_the_scientist_chr.py index fa9a929..44b3675 100644 --- a/src/characters/sam_the_scientist_chr.py +++ b/src/characters/sam_the_scientist_chr.py @@ -4,7 +4,7 @@ from src.templating import ChoiceTemplate as t # noqa: N813 # fmt: off -character = Actor("Sam the Scientist", "url_here",[ +character = Actor("Sam the Scientist", "https://i.postimg.cc/xTSTr7G2/sam.webp",[ StageGroup(1, [ t( "Greetings, esteemed leader of {nation_name}. May I request your consideration for a financial contribution towards our research?", # noqa: E501 diff --git a/src/characters/sandra_the_spy_chr.py b/src/characters/sandra_the_spy_chr.py index ee244f8..a1b3b6d 100644 --- a/src/characters/sandra_the_spy_chr.py +++ b/src/characters/sandra_the_spy_chr.py @@ -4,7 +4,7 @@ from src.templating import ChoiceTemplate as t # noqa: N813 # fmt: off -character = Actor("Sandra the Spy", "url_here",[ +character = Actor("Sandra the Spy", "https://i.postimg.cc/MKbZzDtj/sandra.webp",[ StageGroup(1, [ t( "Leader of {nation_name}, we need additional funds to enhance our espionage network.", diff --git a/src/characters/sheela_the_singer_chr.py b/src/characters/sheela_the_singer_chr.py index 643ba3c..b52ed94 100644 --- a/src/characters/sheela_the_singer_chr.py +++ b/src/characters/sheela_the_singer_chr.py @@ -4,7 +4,7 @@ from src.templating import ChoiceTemplate as t # noqa: N813 # fmt: off -character = Actor("Sheela the Singer", "url_here",[ +character = Actor("Sheela the Singer", "https://i.postimg.cc/RZSShHbp/sheela.webp",[ StageGroup(1, [ t( "Blessed leader of {nation_name}, please fund my public concert!", diff --git a/src/characters/wong_the_worker_chr.py b/src/characters/wong_the_worker_chr.py index 56097a7..ccf9235 100644 --- a/src/characters/wong_the_worker_chr.py +++ b/src/characters/wong_the_worker_chr.py @@ -4,7 +4,7 @@ from src.templating import ChoiceTemplate as t # noqa: N813 # fmt: off -character = Actor("Wong the Worker", "url_here",[ +character = Actor("Wong the Worker", "https://i.postimg.cc/ht2SdtkD/wong.webp",[ StageGroup(1, [ t( "O great leader of {nation_name}. May thou be kind enough to raise our minimum wage", From bc2e2dd40c840471be8be28624291cb0d5e0db8c Mon Sep 17 00:00:00 2001 From: Maheshkumar Date: Sun, 28 Jul 2024 19:55:25 +0530 Subject: [PATCH 100/116] Update fred the farmer --- src/characters/fred_the_farmer_chr.py | 83 ++++++++++++++++++++++----- 1 file changed, 69 insertions(+), 14 deletions(-) diff --git a/src/characters/fred_the_farmer_chr.py b/src/characters/fred_the_farmer_chr.py index 5e095a7..068b0a5 100644 --- a/src/characters/fred_the_farmer_chr.py +++ b/src/characters/fred_the_farmer_chr.py @@ -14,9 +14,26 @@ }, ), t( - "Thank you for your help, leader!", - choices = {"It's my duty": {"money": +15}}, - condition = lambda state: state.loyalty>50, + "Leader of {nation_name}, our farms need new irrigation systems to increase crop yield.", + choices={ + "Approve funding": {"money": -15, "security": +5, "loyalty": +3}, + "Deny funding": {"security": -5, "loyalty": -3}, + }, + ), + t( + "There's an opportunity to switch to organic farming. Should we support this transition?", + choices={ + "Yes, support organic farming": {"money": -10, "world_opinion": +10, "loyalty": +5}, + "No, it's not necessary": {"world_opinion": -5, "loyalty": -3}, + }, + ), + t( + "Farmers are requesting subsidies to cope with recent droughts. Should we grant them?", + choices={ + "Grant subsidies": {"money": -15, "loyalty": +5, "world_opinion": +3}, + "No, we can't afford it": {"loyalty": -5, "world_opinion": -3}, + }, + condition=lambda state: state.money > 30, ), ]), StageGroup(2, [ @@ -27,21 +44,59 @@ }, ), t( - "Our votes will stay with you", - choices = {"Thanks!": {"loyalty": +20}}, - condition = lambda state: state.loyalty>70, + "There's a proposal to introduce advanced farming technologies. Should we invest in this?", + choices={ + "Yes, invest in technology": {"money": -20, "security": +10, "loyalty": +5}, + "No, it's too expensive": {"security": -5, "loyalty": -3}, + }, + ), + t( + "A neighboring country wants to collaborate on a farming project. Should we join them?", + choices={ + "Yes, join the project": {"money": -15, "world_opinion": +10, "security": +5}, + "No, we can manage on our own": {"world_opinion": -5, "security": -3}, + }, + ), + t( + "Farmers are organizing a festival to celebrate the harvest. Should we sponsor it?", + choices={ + "Sponsor the festival": {"money": -10, "loyalty": +10, "world_opinion": +5}, + "No, it's not necessary": {"loyalty": -5, "world_opinion": -3}, + }, + condition=lambda state: state.world_opinion > 40, ), ]), StageGroup(3, [ - t("Hello, leader of {nation_name}. Can you give subsidies for farming equipment?", - choices = { - "Sure": {"money": -20, "loyalty": +15}, - "Nope": {"loyalty": -5}, - }, - ), t( - "We are able to do our work more effectively now", - choices = {"Great!": {"money": +20}}, + "Our nation is being considered for a prestigious agricultural award. Should we lobby for it?", + choices={ + "Yes, lobby for the award": {"money": -10, "world_opinion": +20, "loyalty": +5}, + "No, it's not worth it": {"world_opinion": -5}, + }, + ), + t( + "We have the chance to lead a global agricultural research project. Should we take the lead?", + choices={ + "Lead the project": {"money": -25, "world_opinion": +15, "security": +10}, + "No, let others lead": {"world_opinion": -5, "security": -3}, + }, + condition=lambda state: state.security > 50, + ), + t( + "A major agricultural firm wants to test new fertilizers in our fields. Should we allow it?", + choices={ + "Allow the tests": {"money": +20, "security": +5, "world_opinion": +5}, + "Deny the tests": {"security": -5, "world_opinion": -5}, + }, + condition=lambda state: state.money < 50, + ), + t( + "Farmers are requesting financial aid due to a recent pest infestation. Should we help them?", + choices={ + "Yes, provide aid": {"money": -15, "loyalty": +10, "world_opinion": +5}, + "No, it's too costly": {"loyalty": -5, "world_opinion": -3}, + }, + condition=lambda state: state.loyalty > 50, ), ]), ]) From 30c95194990792629a6e266eb3f28ee659a508ca Mon Sep 17 00:00:00 2001 From: Maheshkumar Date: Sun, 28 Jul 2024 20:03:28 +0530 Subject: [PATCH 101/116] Add national media --- src/characters/national_media_chr.py | 84 ++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 src/characters/national_media_chr.py diff --git a/src/characters/national_media_chr.py b/src/characters/national_media_chr.py new file mode 100644 index 0000000..c22db34 --- /dev/null +++ b/src/characters/national_media_chr.py @@ -0,0 +1,84 @@ +"""National media templates and information.""" + +from src.templating import Actor, StageGroup +from src.templating import ChoiceTemplate as t # noqa: N813 + +# fmt: off +character = Actor("Social Media", "url_here", [ + StageGroup(1, [ + t( + "Trending Now: A rumor is spreading that neighboring nations are stockpiling weapons. Should we respond?", + choices={ + "Yes, increase military spending": {"money": -20, "security": +5, "world_opinion": -10}, + "No, it's just a rumor": {"security": -5, "world_opinion": +5}, + }, + ), + t( + "Viral Post: A celebrity is urging people to boycott local produce due to pesticide use. Should we address this?", # noqa: E501 + choices={ + "Yes, ban the use of pesticides": {"money": -15, "loyalty": +5, "world_opinion": +5}, + "No, ignore the celebrity": {"loyalty": -5, "world_opinion": -5}, + }, + ), + t( + "Breaking News: A popular social media figure claims your administration is hiding a health crisis. Should we respond?", # noqa: E501 + choices={ + "Yes, launch a PR campaign": {"money": -10, "loyalty": +5, "security": +5}, + "No, it's not true": {"loyalty": -5, "world_opinion": -5}, + }, + ), + ]), + StageGroup(2, [ + t( + "Hashtag Alert: #NoMoreTaxes is trending. Citizens are protesting against the latest tax hike. Should we lower taxes?", # noqa: E501 + choices={ + "Yes, lower taxes": {"money": -20, "loyalty": +10, "world_opinion": +5}, + "No, maintain the current tax rate": {"loyalty": -5, "world_opinion": -5}, + }, + ), + t( + "Misinformation: False reports of a natural disaster in a key agricultural region are causing panic. Should we allocate emergency funds?", # noqa: E501 + choices={ + "Yes, allocate emergency funds": {"money": -15, "security": +5, "world_opinion": +5}, + "No, verify the reports first": {"security": -5, "world_opinion": -5}, + }, + ), + t( + "Influencer Post: A famous influencer is promoting a controversial new technology. Should we adopt this technology?", # noqa: E501 + choices={ + "Yes, adopt the technology": {"money": -25, "loyalty": +10, "world_opinion": +5}, + "No, it's too risky": {"loyalty": -5, "world_opinion": -5}, + }, + ), + ]), + StageGroup(3, [ + t( + "Trending Conspiracy: A conspiracy theory is circulating that the government is spying on citizens. Should we address this?", # noqa: E501 + choices={ + "Yes, deny the accusations": {"money": -10, "security": +5, "world_opinion": +5}, + "No, ignore it": {"security": -5, "world_opinion": -5}, + }, + ), + t( + "False Alarm: A fake news article claims that a neighboring nation is planning an attack. Should we prepare for war?", # noqa: E501 + choices={ + "Yes, prepare for war": {"money": -30, "security": +10, "world_opinion": -10}, + "No, investigate the claim first": {"security": -5, "world_opinion": +5}, + }, + ), + t( + "Viral Challenge: A dangerous social media challenge is encouraging people to vandalize public property. Should we address this?", # noqa: E501 + choices={ + "Yes, launch a public awareness campaign": {"money": -15, "loyalty": +5, "world_opinion": +5}, + "No, it's just a phase": {"loyalty": -5, "world_opinion": -5}, + }, + ), + t( + "Social Media Pressure: Users are demanding immediate action on climate change. Should we implement drastic measures?", # noqa: E501 + choices={ + "Yes, implement drastic measures": {"money": -20, "security": +5, "world_opinion": +10}, + "No, take a more measured approach": {"security": -5, "world_opinion": -5}, + }, + ), + ]), +], weight=200) From e6e8839e169abf89808acbdcd34b524346fa6f60 Mon Sep 17 00:00:00 2001 From: Maheshkumar Date: Sun, 28 Jul 2024 20:12:11 +0530 Subject: [PATCH 102/116] Update weights --- src/characters/andy_the_athelete_chr.py | 2 +- src/characters/aura_the_activist_chr.py | 2 +- src/characters/craig_the_contractor_chr.py | 2 +- src/characters/dave_the_doctor_chr.py | 2 +- src/characters/else_the_engineer_chr.py | 2 +- src/characters/fred_the_farmer_chr.py | 2 +- src/characters/gary_the_general_chr.py | 2 +- src/characters/national_media_chr.py | 2 +- src/characters/sam_the_scientist_chr.py | 2 +- src/characters/sandra_the_spy_chr.py | 2 +- src/characters/wong_the_worker_chr.py | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/characters/andy_the_athelete_chr.py b/src/characters/andy_the_athelete_chr.py index 050c6bc..26f563a 100644 --- a/src/characters/andy_the_athelete_chr.py +++ b/src/characters/andy_the_athelete_chr.py @@ -94,4 +94,4 @@ condition=lambda state: state.money > 200, ), ]), -]) +], weight=80) diff --git a/src/characters/aura_the_activist_chr.py b/src/characters/aura_the_activist_chr.py index 6e2fbf1..0fc69ca 100644 --- a/src/characters/aura_the_activist_chr.py +++ b/src/characters/aura_the_activist_chr.py @@ -73,4 +73,4 @@ condition=lambda state: state.security > 50, ), ]), -]) +], weight=90) diff --git a/src/characters/craig_the_contractor_chr.py b/src/characters/craig_the_contractor_chr.py index ebe654a..563a923 100644 --- a/src/characters/craig_the_contractor_chr.py +++ b/src/characters/craig_the_contractor_chr.py @@ -80,4 +80,4 @@ condition=lambda state: state.money > 100, ), ]), -]) +], weight=70) diff --git a/src/characters/dave_the_doctor_chr.py b/src/characters/dave_the_doctor_chr.py index 3ea2ca7..dc566d3 100644 --- a/src/characters/dave_the_doctor_chr.py +++ b/src/characters/dave_the_doctor_chr.py @@ -91,4 +91,4 @@ condition=lambda state: state.loyalty > 50, ), ]), -]) +], weight=110) diff --git a/src/characters/else_the_engineer_chr.py b/src/characters/else_the_engineer_chr.py index a6f1479..146dc1d 100644 --- a/src/characters/else_the_engineer_chr.py +++ b/src/characters/else_the_engineer_chr.py @@ -48,4 +48,4 @@ condition = lambda state: state.security>50 and state.money>500, ), ]), -]) +], weight=60) diff --git a/src/characters/fred_the_farmer_chr.py b/src/characters/fred_the_farmer_chr.py index 068b0a5..8c23c68 100644 --- a/src/characters/fred_the_farmer_chr.py +++ b/src/characters/fred_the_farmer_chr.py @@ -99,4 +99,4 @@ condition=lambda state: state.loyalty > 50, ), ]), -]) +], weight=120) diff --git a/src/characters/gary_the_general_chr.py b/src/characters/gary_the_general_chr.py index f709ab8..8fa205c 100644 --- a/src/characters/gary_the_general_chr.py +++ b/src/characters/gary_the_general_chr.py @@ -125,4 +125,4 @@ weight=150, ), ]), -]) +], weight=130) diff --git a/src/characters/national_media_chr.py b/src/characters/national_media_chr.py index c22db34..7a30eae 100644 --- a/src/characters/national_media_chr.py +++ b/src/characters/national_media_chr.py @@ -4,7 +4,7 @@ from src.templating import ChoiceTemplate as t # noqa: N813 # fmt: off -character = Actor("Social Media", "url_here", [ +character = Actor("National Media", "url_here", [ StageGroup(1, [ t( "Trending Now: A rumor is spreading that neighboring nations are stockpiling weapons. Should we respond?", diff --git a/src/characters/sam_the_scientist_chr.py b/src/characters/sam_the_scientist_chr.py index 44b3675..f735204 100644 --- a/src/characters/sam_the_scientist_chr.py +++ b/src/characters/sam_the_scientist_chr.py @@ -130,4 +130,4 @@ weight=150, ), ]), -]) +], weight=120) diff --git a/src/characters/sandra_the_spy_chr.py b/src/characters/sandra_the_spy_chr.py index a1b3b6d..87b41cd 100644 --- a/src/characters/sandra_the_spy_chr.py +++ b/src/characters/sandra_the_spy_chr.py @@ -189,4 +189,4 @@ }, ), ]), -]) +], weight=90) diff --git a/src/characters/wong_the_worker_chr.py b/src/characters/wong_the_worker_chr.py index ccf9235..2d60aa7 100644 --- a/src/characters/wong_the_worker_chr.py +++ b/src/characters/wong_the_worker_chr.py @@ -106,4 +106,4 @@ }, ), ]), -]) +], weight=110) From 1045fc505596342eb6fa69311d81690dfb76eeff Mon Sep 17 00:00:00 2001 From: Sapient44 Date: Sun, 28 Jul 2024 20:17:58 +0530 Subject: [PATCH 103/116] Picturess are here --- src/characters/national_media_chr.py | 2 +- src/templating.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/characters/national_media_chr.py b/src/characters/national_media_chr.py index c22db34..3358600 100644 --- a/src/characters/national_media_chr.py +++ b/src/characters/national_media_chr.py @@ -4,7 +4,7 @@ from src.templating import ChoiceTemplate as t # noqa: N813 # fmt: off -character = Actor("Social Media", "url_here", [ +character = Actor("Social Media", "https://i.postimg.cc/VsTY8Wpz/defcord.webp", [ StageGroup(1, [ t( "Trending Now: A rumor is spreading that neighboring nations are stockpiling weapons. Should we respond?", diff --git a/src/templating.py b/src/templating.py index 9d28674..27c3779 100644 --- a/src/templating.py +++ b/src/templating.py @@ -46,6 +46,7 @@ def to_embed(self, player: "Player", actor: "Actor") -> Embed: title=f"{actor.name} of {player.state.nation_name}", description=self.format(player.state), color=message_color, + thumbnail=actor.picture ) async def ui(self, player: "Player", actor: "Actor") -> None: From 4e87c76d848d94d4498f420e24175e49bb2c1291 Mon Sep 17 00:00:00 2001 From: Maheshkumar Date: Sun, 28 Jul 2024 20:20:29 +0530 Subject: [PATCH 104/116] Rename to national media --- src/characters/national_media_chr.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/characters/national_media_chr.py b/src/characters/national_media_chr.py index 3358600..4e1230d 100644 --- a/src/characters/national_media_chr.py +++ b/src/characters/national_media_chr.py @@ -4,7 +4,7 @@ from src.templating import ChoiceTemplate as t # noqa: N813 # fmt: off -character = Actor("Social Media", "https://i.postimg.cc/VsTY8Wpz/defcord.webp", [ +character = Actor("National Media", "https://i.postimg.cc/VsTY8Wpz/defcord.webp", [ StageGroup(1, [ t( "Trending Now: A rumor is spreading that neighboring nations are stockpiling weapons. Should we respond?", From 48b73a6924b7d29be956e85cd4d89d1647bf1dfe Mon Sep 17 00:00:00 2001 From: Maheshkumar Date: Sun, 28 Jul 2024 20:27:00 +0530 Subject: [PATCH 105/116] Add thumbnail fail safe --- src/templating.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/templating.py b/src/templating.py index 27c3779..c2098ee 100644 --- a/src/templating.py +++ b/src/templating.py @@ -1,6 +1,7 @@ """Utils for creating and using templates.""" import logging +import os from collections.abc import Callable from typing import TYPE_CHECKING, Any, Literal, get_args @@ -46,7 +47,7 @@ def to_embed(self, player: "Player", actor: "Actor") -> Embed: title=f"{actor.name} of {player.state.nation_name}", description=self.format(player.state), color=message_color, - thumbnail=actor.picture + thumbnail=None if os.environ.get("WITHOUT_ACTOR_THUMBNAIL") else actor.picture, ) async def ui(self, player: "Player", actor: "Actor") -> None: From 72fc545db6418279bf1387fffaf39bc480ce8e7f Mon Sep 17 00:00:00 2001 From: Sapient44 Date: Sun, 28 Jul 2024 21:20:50 +0530 Subject: [PATCH 106/116] Basic stats announce --- src/game.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/game.py b/src/game.py index 42eaa06..7e083f2 100644 --- a/src/game.py +++ b/src/game.py @@ -126,6 +126,15 @@ async def loop(self) -> None: game_time < self.max_time ): self.stage = total_stages[total_stages.index(self.stage) + 1] + for player in self.players.values(): + await player.ctx.send( + Embed( + title="Your current stats are as follows", + description=f"{player.state}", + color=system_message_color, + ) + ) + if game_time >= self.max_time: logger.info(f"Time is Up! Game {self.id} is over!") From bef58f5aadcc0117c2feee899dabfe1433045578 Mon Sep 17 00:00:00 2001 From: Maheshkumar P <67100964+Maheshkumar-novice@users.noreply.github.com> Date: Sun, 28 Jul 2024 22:09:12 +0530 Subject: [PATCH 107/116] Stats announce (#20) * Send stats during stage switches * Fix and format templates --- src/characters/craig_the_contractor_chr.py | 5 ++-- src/characters/dave_the_doctor_chr.py | 8 ++++-- src/game.py | 32 ++++++++++++++-------- 3 files changed, 29 insertions(+), 16 deletions(-) diff --git a/src/characters/craig_the_contractor_chr.py b/src/characters/craig_the_contractor_chr.py index 563a923..8b5c64a 100644 --- a/src/characters/craig_the_contractor_chr.py +++ b/src/characters/craig_the_contractor_chr.py @@ -15,8 +15,9 @@ ), t( "The roads are being repaired thanks to the funds provided", - choices = {"Take more funds": {"money": -10, "loyalty" : +10}, - "Great": {"world_opinion" : +10}, + choices = { + "Take more funds": {"money": -10, "loyalty" : +10}, + "Great": {"world_opinion" : +10}, }, ), t( diff --git a/src/characters/dave_the_doctor_chr.py b/src/characters/dave_the_doctor_chr.py index dc566d3..a6e42bc 100644 --- a/src/characters/dave_the_doctor_chr.py +++ b/src/characters/dave_the_doctor_chr.py @@ -14,9 +14,11 @@ }, ), t( - "We have manufactured the vaccines, but lack the funding to distribute, we require your assistance\ - on this", - choices = {"Sure": {"money": -15, "Nope":{ "world_opinion" : -20, "security" : -10, "loyalty" : -10}}}, + "We have manufactured the vaccines, but lack the funding to distribute, we require your assistance on this", # noqa: E501 + choices = { + "Sure": {"money": -15}, + "Nope":{ "world_opinion" : -20, "security" : -10, "loyalty" : -10}, + }, ), t( "Leader of {nation_name}, our hospital is overwhelmed with patients. We need additional funding to hire more staff.", # noqa: E501 diff --git a/src/game.py b/src/game.py index 7e083f2..28e6e73 100644 --- a/src/game.py +++ b/src/game.py @@ -6,6 +6,7 @@ import time from typing import TYPE_CHECKING, Annotated +from attrs import asdict from interactions import Embed, SlashContext from src.characters import all_characters @@ -63,10 +64,13 @@ async def death_player(self, dead_player: Player) -> None: """Mark the player as dead.""" embed = Embed( title="We have lost a national leader in the turmoil", - description=f"{dead_player.state.nation_name} has lost their leadership which was done by \n <@{dead_player.ctx.user.id}>", # noqa: E501 + description=f"{dead_player.state.nation_name} has lost their leadership which was done by <@{dead_player.ctx.user.id}>", # noqa: E501 color=system_message_color, ) + for key, value in asdict(dead_player.state).items(): + embed.add_field(name=key.capitalize(), value=value) + for player in self.players.values(): await player.ctx.send(embed=embed, ephemeral=True) @@ -106,6 +110,19 @@ async def stop_game_by_time(self) -> None: self.game_factory.remove_game(self.id) + async def send_stats(self) -> None: + """Send player stats.""" + for player in self.players.values(): + embed = Embed( + title="Stats", + description=f"<@{player.ctx.user.id}> current stats as follows,", + color=system_message_color, + ) + for key, value in asdict(player.state).items(): + embed.add_field(name=key.capitalize(), value=value) + + await player.ctx.send(embed=embed, ephemeral=True) + def stop(self) -> None: """Set the stop flag.""" self.game_stop_flag = True @@ -114,6 +131,7 @@ async def loop(self) -> None: """Define the main loop of the game.""" self.start_time = time.time() players = self.players.values() + await self.send_stats() while True: logger.info(f"{len(self.players)} left in game {self.id}") @@ -126,15 +144,7 @@ async def loop(self) -> None: game_time < self.max_time ): self.stage = total_stages[total_stages.index(self.stage) + 1] - for player in self.players.values(): - await player.ctx.send( - Embed( - title="Your current stats are as follows", - description=f"{player.state}", - color=system_message_color, - ) - ) - + await self.send_stats() if game_time >= self.max_time: logger.info(f"Time is Up! Game {self.id} is over!") @@ -173,7 +183,7 @@ async def tick(self, player: Player) -> None: character = all_characters.get_random(player.state) for attr in self.values_to_check: - if getattr(player.state, attr) < 0: + if getattr(player.state, attr) <= 0: # Some value is negative hence need to send the losing message await self.death_player(player) return From 4d93cb303a6c47eb5377994eec7c5df246cf108c Mon Sep 17 00:00:00 2001 From: Sapient44 <78420201+Sapient44@users.noreply.github.com> Date: Sun, 28 Jul 2024 22:15:46 +0530 Subject: [PATCH 108/116] Add embeds to interaction.py (#21) * Add embeds to interaction.py * Update game_interaction.py Changed similar error detecting ifs to elif * Apply formatting --------- Co-authored-by: Maheshkumar --- src/game_interaction.py | 108 +++++++++++++++++++++++++++------------- 1 file changed, 73 insertions(+), 35 deletions(-) diff --git a/src/game_interaction.py b/src/game_interaction.py index a5bf59a..9a7ba3a 100644 --- a/src/game_interaction.py +++ b/src/game_interaction.py @@ -81,8 +81,12 @@ async def send_player_join_notification(self, game: Game, ctx: SlashContext) -> count_message = "All aboard. The game creator can start the game now." await player.ctx.send( - f"<@{ctx.user.id}> joined the game.\n{count_message}", - ephemeral=True, + Embed( + title="A player joined the game", + description=f"<@{ctx.user.id}> joined the game.\n{count_message}", + color=system_message_color, + ephemeral=True, + ), ) @slash_command(name="defcord", description="Interact with defcord.") @@ -103,8 +107,11 @@ async def create(self, ctx: SlashContext, required_no_of_players: int = 5) -> No existing_game = self.game_factory.query_game(player_id=ctx.user.id) if existing_game: await ctx.send( - f"<@{ctx.user.id}> You are already part of the game with invite {existing_game.id}", - ephemeral=True, + Embed( + title="You have already joined a game", + description=f"<@{ctx.user.id}> You are already part of the game with invite {existing_game.id}", + ephemeral=True, + ), ) return @@ -125,22 +132,27 @@ async def create(self, ctx: SlashContext, required_no_of_players: int = 5) -> No @slash_option("invite", "The invite code for the game", required=True, opt_type=OptionType.STRING) async def join(self, ctx: SlashContext, invite: str) -> None: """Join a game of DEFCORD.""" - game = self.game_factory.query_game(game_id=invite) + game: Game = self.game_factory.query_game(game_id=invite) + description: str = "" if game is None: - await ctx.send(f"<@{ctx.user.id}> Invite({invite}) is invalid", ephemeral=True) - return - - if ctx.user.id in game.players: - await ctx.send(f"<@{ctx.user.id}> You are already part of the game with {invite=}", ephemeral=True) - return - - if game.required_no_of_players == len(game.players): - await ctx.send(f"<@{ctx.user.id}> Game is already full {invite=}", ephemeral=True) - return - - if game.started: - await ctx.send(f"<@{ctx.user.id}> Game already started", ephemeral=True) + description = f"<@{ctx.user.id}> Invite({invite}) is invalid" + elif ctx.user.id in game.players: + description = f"<@{ctx.user.id}> You are already part of the game with {invite=}" + elif game.required_no_of_players == len(game.players): + description = f"<@{ctx.user.id}> Game is already full {invite=}" + elif game.started: + description = f"<@{ctx.user.id}> Game already started" + + if description != "": + await ctx.send( + Embed( + title="Unable to join the game", + description=description, + color=system_message_color, + ephemeral=True, + ), + ) return self.game_factory.add_player(ctx.user.id, game) @@ -153,7 +165,14 @@ async def leave(self, ctx: SlashContext) -> None: game = self.game_factory.query_game(player_id=ctx.user.id) if game is None: - await ctx.send(f"<@{ctx.user.id}> You are not part of any game", ephemeral=True) + await ctx.send( + Embed( + title="You cannot leave any game since", + description=f"<@{ctx.user.id}> You are not part of any game", + color=system_message_color, + ephemeral=True, + ), + ) return await game.remove_player(ctx) @@ -169,7 +188,14 @@ async def leave(self, ctx: SlashContext) -> None: await ctx.send(embed=embed, ephemeral=True) if len(game.players) == 0 and game.started: - await ctx.send("Game Over! You are the only one survivor. Everyone quit!", ephemeral=True) + await ctx.send( + Embed( + title="Game Over", + description="Game Over! You are the only one survivor. Everyone quit!", + color=system_message_color, + ephemeral=True, + ), + ) game.stop() self.game_factory.remove_game(game.id) @@ -177,25 +203,37 @@ async def leave(self, ctx: SlashContext) -> None: async def start(self, ctx: SlashContext) -> None: """Start and runs the Defcord game loop.""" game = self.game_factory.query_game(player_id=ctx.user.id) + description: str = "" if game is None: - await ctx.send(f"<@{ctx.user.id}> You are not part of any game", ephemeral=True) - return - - if game.creator_id != ctx.user.id: - await ctx.send(f"<@{ctx.user.id}> Only game creator can start it", ephemeral=True) - return - - if game.started: - await ctx.send(f"<@{ctx.user.id}> Game already started", ephemeral=True) - return - - if game.required_no_of_players != len(game.players): - await ctx.send(f"<@{ctx.user.id}> Cannot start the game until all the players join", ephemeral=True) + description = f"<@{ctx.user.id}> You are not part of any game" + elif game.creator_id != ctx.user.id: + description = f"<@{ctx.user.id}> Only game creator can start it" + elif game.started: + description = f"<@{ctx.user.id}> Game already started" + elif game.required_no_of_players != len(game.players): + description = f"<@{ctx.user.id}> Cannot start the game until all the players join" + + if description != "": + ctx.send( + Embed( + title="Unable to start the game", + description=description, + color=system_message_color, + ephemeral=True, + ), + ) return game.started = True - await ctx.send(f"<@{ctx.user.id}> Game started", ephemeral=True) + await ctx.send( + Embed( + title="Game Start", + description=f"<@{ctx.user.id}> Game started", + color=system_message_color, + ephemeral=True, + ), + ) for player in game.players.values(): player.last_activity_time = time.time() @@ -209,7 +247,7 @@ async def on_component(self, event: Component) -> None: game = self.game_factory.query_game(player_id=ctx.user.id) if not game: - await ctx.edit_origin(content="Your game already over.", components=[]) + await ctx.edit_origin(content="Your game has already ended.", components=[]) return consequences = game.player_component_choice_mapping[ctx.custom_id] From cbac17a0cf5367afd35623d66f49e9b1cadebff1 Mon Sep 17 00:00:00 2001 From: Sapient44 Date: Sun, 28 Jul 2024 22:20:06 +0530 Subject: [PATCH 109/116] fixing ephemeral bug --- src/game_interaction.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/game_interaction.py b/src/game_interaction.py index 9a7ba3a..4ac6425 100644 --- a/src/game_interaction.py +++ b/src/game_interaction.py @@ -85,8 +85,8 @@ async def send_player_join_notification(self, game: Game, ctx: SlashContext) -> title="A player joined the game", description=f"<@{ctx.user.id}> joined the game.\n{count_message}", color=system_message_color, - ephemeral=True, ), + ephemeral=True, ) @slash_command(name="defcord", description="Interact with defcord.") @@ -110,8 +110,8 @@ async def create(self, ctx: SlashContext, required_no_of_players: int = 5) -> No Embed( title="You have already joined a game", description=f"<@{ctx.user.id}> You are already part of the game with invite {existing_game.id}", - ephemeral=True, ), + ephemeral=True, ) return @@ -150,8 +150,8 @@ async def join(self, ctx: SlashContext, invite: str) -> None: title="Unable to join the game", description=description, color=system_message_color, - ephemeral=True, ), + ephemeral=True, ) return @@ -170,8 +170,8 @@ async def leave(self, ctx: SlashContext) -> None: title="You cannot leave any game since", description=f"<@{ctx.user.id}> You are not part of any game", color=system_message_color, - ephemeral=True, ), + ephemeral=True, ) return @@ -193,8 +193,8 @@ async def leave(self, ctx: SlashContext) -> None: title="Game Over", description="Game Over! You are the only one survivor. Everyone quit!", color=system_message_color, - ephemeral=True, ), + ephemeral=True, ) game.stop() self.game_factory.remove_game(game.id) @@ -220,8 +220,8 @@ async def start(self, ctx: SlashContext) -> None: title="Unable to start the game", description=description, color=system_message_color, - ephemeral=True, ), + ephemeral=True, ) return @@ -231,8 +231,8 @@ async def start(self, ctx: SlashContext) -> None: title="Game Start", description=f"<@{ctx.user.id}> Game started", color=system_message_color, - ephemeral=True, ), + ephemeral=True, ) for player in game.players.values(): From 54302af59c0384ef06d95efd850829d8f4b7b2a4 Mon Sep 17 00:00:00 2001 From: Sapient44 Date: Sun, 28 Jul 2024 22:20:35 +0530 Subject: [PATCH 110/116] fixing ephemeral bug --- src/game_interaction.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/game_interaction.py b/src/game_interaction.py index 4ac6425..ab79d1e 100644 --- a/src/game_interaction.py +++ b/src/game_interaction.py @@ -110,6 +110,7 @@ async def create(self, ctx: SlashContext, required_no_of_players: int = 5) -> No Embed( title="You have already joined a game", description=f"<@{ctx.user.id}> You are already part of the game with invite {existing_game.id}", + color=system_message_color, ), ephemeral=True, ) From 62fd7ae1046ce3cc153be0c643c5aa48b03ef024 Mon Sep 17 00:00:00 2001 From: Maheshkumar Date: Sun, 28 Jul 2024 22:38:28 +0530 Subject: [PATCH 111/116] Update reqs and embed issue --- requirements.txt | 1 + src/game_interaction.py | 5 ++--- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/requirements.txt b/requirements.txt index 660b479..f8da89a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,2 @@ discord-py-interactions~=5.13.0 +python-dotenv~=1.0.1 diff --git a/src/game_interaction.py b/src/game_interaction.py index ab79d1e..3eb01ac 100644 --- a/src/game_interaction.py +++ b/src/game_interaction.py @@ -79,9 +79,8 @@ async def send_player_join_notification(self, game: Game, ctx: SlashContext) -> count_message = f"{remaining_players_count} yet to join." else: count_message = "All aboard. The game creator can start the game now." - await player.ctx.send( - Embed( + embed=Embed( title="A player joined the game", description=f"<@{ctx.user.id}> joined the game.\n{count_message}", color=system_message_color, @@ -112,7 +111,7 @@ async def create(self, ctx: SlashContext, required_no_of_players: int = 5) -> No description=f"<@{ctx.user.id}> You are already part of the game with invite {existing_game.id}", color=system_message_color, ), - ephemeral=True, + ephemeral=True, ) return From 084c7a7a0077a413f1edad995194f07555b29005 Mon Sep 17 00:00:00 2001 From: Maheshkumar Date: Sun, 28 Jul 2024 22:39:59 +0530 Subject: [PATCH 112/116] Fix embeds --- src/game.py | 2 +- src/game_interaction.py | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/game.py b/src/game.py index 28e6e73..4960852 100644 --- a/src/game.py +++ b/src/game.py @@ -165,7 +165,7 @@ async def loop(self) -> None: for player in players: await player.ctx.send( - Embed( + embed=Embed( title="Some error occured", description=f"{e} \n has occured, please contact the devs if you see this", color=error_color, diff --git a/src/game_interaction.py b/src/game_interaction.py index 3eb01ac..1856106 100644 --- a/src/game_interaction.py +++ b/src/game_interaction.py @@ -106,7 +106,7 @@ async def create(self, ctx: SlashContext, required_no_of_players: int = 5) -> No existing_game = self.game_factory.query_game(player_id=ctx.user.id) if existing_game: await ctx.send( - Embed( + embed=Embed( title="You have already joined a game", description=f"<@{ctx.user.id}> You are already part of the game with invite {existing_game.id}", color=system_message_color, @@ -146,7 +146,7 @@ async def join(self, ctx: SlashContext, invite: str) -> None: if description != "": await ctx.send( - Embed( + embed=Embed( title="Unable to join the game", description=description, color=system_message_color, @@ -166,7 +166,7 @@ async def leave(self, ctx: SlashContext) -> None: if game is None: await ctx.send( - Embed( + embed=Embed( title="You cannot leave any game since", description=f"<@{ctx.user.id}> You are not part of any game", color=system_message_color, @@ -189,7 +189,7 @@ async def leave(self, ctx: SlashContext) -> None: if len(game.players) == 0 and game.started: await ctx.send( - Embed( + embed=Embed( title="Game Over", description="Game Over! You are the only one survivor. Everyone quit!", color=system_message_color, @@ -216,7 +216,7 @@ async def start(self, ctx: SlashContext) -> None: if description != "": ctx.send( - Embed( + embed=Embed( title="Unable to start the game", description=description, color=system_message_color, @@ -227,7 +227,7 @@ async def start(self, ctx: SlashContext) -> None: game.started = True await ctx.send( - Embed( + embed=Embed( title="Game Start", description=f"<@{ctx.user.id}> Game started", color=system_message_color, From 743d3427876ab5c32c10694008758a44a5e7565d Mon Sep 17 00:00:00 2001 From: Maheshkumar P <67100964+Maheshkumar-novice@users.noreply.github.com> Date: Thu, 1 Aug 2024 22:25:38 +0530 Subject: [PATCH 113/116] Create Documentation (#22) * Init doc * Add first draft * Add install section * Changed a few mistakes 1) Capitalisation of about \n 2) Removal of present time info enchancement * Add basic demo for the game * Add contributions * Add link of contributions * Table of contents * Update README.md * Update README.md * Update README.md * Update README.md * Convert attributes to table * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md --------- Co-authored-by: Sapient44 Co-authored-by: Sapient44 <78420201+Sapient44@users.noreply.github.com> --- README.md | 315 +++++++++++++++++++++++++++++++++--------------------- 1 file changed, 191 insertions(+), 124 deletions(-) diff --git a/README.md b/README.md index d50f7b7..5dd1a77 100644 --- a/README.md +++ b/README.md @@ -1,184 +1,251 @@ -# Python Discord Code Jam Repository Template +

+ Game Logo +

-## A primer +

Defcord

-Hello code jam participants! We've put together this repository template for you to use in [our code jams](https://pythondiscord.com/events/) or even other Python events! +

Lead Nations. Compete. Outsmart.

-This document contains the following information: +
+ Table Of Contents +
    +
  1. + About +
  2. +
  3. + Vital Attributes +
  4. +
  5. + Game Model +
  6. +
  7. + Commands Available +
  8. +
  9. + How the bot relate to the theme `Information Overload` +
  10. +
  11. + Known Issues +
  12. +
  13. + Enhancements +
  14. +
  15. + Contributions +
  16. +
  17. + Running the App +
  18. +
  19. + Demo of the game +
  20. +
+
-1. [What does this template contain?](#what-does-this-template-contain) -2. [How do I use this template?](#how-do-i-use-this-template) -3. [How do I adapt this template to my project?](#how-do-i-adapt-this-template-to-my-project) +## About -> [!TIP] -> You can also look at [our style guide](https://pythondiscord.com/events/code-jams/code-style-guide/) to get more information about what we consider a maintainable code style. +Defcord is a multiplayer, discord application based game. -## What does this template contain? +You play as a leader of a nation and it is your responsibility to make good decisions based on the information given to you. -Here is a quick rundown of what each file in this repository contains: +You should make sure all of your vital attributes stay positive. -- [`LICENSE.txt`](LICENSE.txt): [The MIT License](https://opensource.org/licenses/MIT), an OSS approved license which grants rights to everyone to use and modify your project, and limits your liability. We highly recommend you to read the license. -- [`.gitignore`](.gitignore): A list of files and directories that will be ignored by Git. Most of them are auto-generated or contain data that you wouldn't want to share publicly. -- [`requirements-dev.txt`](requirements-dev.txt): Every PyPI package used for the project's development, to ensure a common development environment. More on that [below](#using-the-default-pip-setup). -- [`pyproject.toml`](pyproject.toml): Configuration and metadata for the project, as well as the linting tool Ruff. If you're interested, you can read more about `pyproject.toml` in the [Python Packaging documentation](https://packaging.python.org/en/latest/guides/writing-pyproject-toml/). -- [`.pre-commit-config.yaml`](.pre-commit-config.yaml): The configuration of the [pre-commit](https://pre-commit.com/) tool. -- [`.github/workflows/lint.yaml`](.github/workflows/lint.yaml): A [GitHub Actions](https://github.com/features/actions) workflow, a set of actions run by GitHub on their server after each push, to ensure the style requirements are met. +You can't know what vital attributes increase/decrease for the given information for the decision you make because you know, people are complex. -Each of these files have comments for you to understand easily, and modify to fit your needs. +Whoever stays alive till the end of the game is the survivor of the game. If multiple people survive they are also survivors. -### Ruff: general style rules +The main goal is to make sure you won't run out of your vital attributes in order to survive. -Our first tool is Ruff. It will check your codebase and warn you about any non-conforming lines. -It is run with the command `ruff check` in the project root. +## Vital Attributes -Here is a sample output: +| Attribute | Default Value | +|---------------|---------------| +| Money | 100 | +| Loyalty | 50 | +| Security | 50 | +| World Opinion | 50 | -```shell -$ ruff check -app.py:1:5: N802 Function name `helloWorld` should be lowercase -app.py:1:5: ANN201 Missing return type annotation for public function `helloWorld` -app.py:2:5: D400 First line should end with a period -app.py:2:5: D403 First word of the first line should be capitalized: `docstring` -> `Docstring` -app.py:3:15: W292 No newline at end of file -Found 5 errors. -``` +## Game Model -Each line corresponds to an error. The first part is the file path, then the line number, and the column index. -Then comes the error code, a unique identifier of the error, and then a human-readable message. +One player is mapped with only one game. So at a time, a player can game participate in only one game. -If, for any reason, you do not wish to comply with this specific error on a specific line, you can add `# noqa: CODE` at the end of the line. -For example: +The game time will be a random time between `12.5` and `16` minutes. -```python -def helloWorld(): # noqa: N802 - ... +We have `3` stages, each stage will make the information flow faster. + +If the player is AFK or non-responsive to the information for `60` seconds, they're disqualified automatically. + +## Commands available + + +### `/defcord create` + +We use this command to create a defcord game. Whoever creates will be part of the game by default and they'll be the only one who can start the game. + +User needs to enter the number of players they want in the game and the nation name they want to be the leader of. + +Once the game is created, a message will be posted with the invite code. Other players need to use the invite code to join the game. + +A player can join from a different channel or even a different server. Only requirement is in that server `defcord` should be installed. -``` -This will ignore the function naming issue and pass linting. +### `/defcord join` -> [!WARNING] -> We do not recommend ignoring errors unless you have a good reason to do so. +Other payers need to use this command with the given/taken invite code from the game creator in order to join a `defcord` game. -### Ruff: formatting +They also need to enter their nation name, via a modal prompt. -Ruff also comes with a formatter, which can be run with the command `ruff format`. -It follows the same code style enforced by [Black](https://black.readthedocs.io/en/stable/index.html), so there's no need to pick between them. -### Pre-commit: run linting before committing +### `/defcord start` -The second tool doesn't check your code, but rather makes sure that you actually *do* check it. +Once the required number of players join, the game creator can start the game. Once invoked, the command will automatically start the game that the current player created and part of. -It makes use of a feature called [Git hooks](https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks) which allow you to run a piece of code before running `git commit`. -The good thing about it is that it will cancel your commit if the lint doesn't pass. You won't have to wait for GitHub Actions to report issues and have a second fix commit. +After that, stage `1` begins. Players will receive information at a slow rate first, as the game progresses it'll get faster. -It is *installed* by running `pre-commit install` and can be run manually by calling only `pre-commit`. +Now the players should start respond to the information by making a good decision in order to survive. -[Lint before you push!](https://soundcloud.com/lemonsaurusrex/lint-before-you-push) -#### List of hooks +### `/defcord leave` -- `check-toml`: Lints and corrects your TOML files. -- `check-yaml`: Lints and corrects your YAML files. -- `end-of-file-fixer`: Makes sure you always have an empty line at the end of your file. -- `trailing-whitespace`: Removes whitespaces at the end of each line. -- `ruff`: Runs the Ruff linter. -- `ruff-format`: Runs the Ruff formatter. +If the player wants to leave in the middle of the game or before the game start, they can do. But if they leave in the middle of the game, they cannot rejoin. They can join another game. -## How do I use this template? +If you are the last member to quit, you are the survivor of the game as you have no one to compete. But you can play till the game time and see what the game brings you. If you run out of an attribute than you are done. -### Creating your team repository -One person in the team, preferably the leader, will have to create the repository and add other members as collaborators. +## Theme Relativity -1. In the top right corner of your screen, where **Clone** usually is, you have a **Use this template** button to click. - ![use-this-template-button](https://docs.github.com/assets/images/help/repository/use-this-template-button.png) -2. Give the repository a name and a description. - ![create-repository-name](https://docs.github.com/assets/images/help/repository/create-repository-name.png) -3. Click **Create repository from template**. -4. Click **Settings** in your newly created repository. - ![repo-actions-settings](https://docs.github.com/assets/images/help/repository/repo-actions-settings.png) -5. In the "Access" section of the sidebar, click **Collaborators**. - ![collaborators-settings](https://github.com/python-discord/code-jam-template/assets/63936253/c150110e-d1b5-4e4d-93e0-0a2cf1de352b) -6. Click **Add people**. -7. Insert the names of each of your teammates, and invite them. Once they have accepted the invitation in their email, they will have write access to the repository. +Given theme is `Information Overload`. Here we try to make the player overwhelmed by giving them information continuously. -You are now ready to go! Sit down, relax, and wait for the kickstart! +They need to keep making good decisions in order to survive. Because they can't give `yes` to all the requests i.e. army tool requests, it'll cost their money. This is also applicable to the reverse situation where the player denies all requests. -> [!IMPORTANT] -> Don't forget to swap "Python Discord" in the [`LICENSE.txt`](LICENSE.txt) file for the name of each of your team members or the name of your team *after* the start of the code jam. +After every stage we show the player's attributes so they need to make a quick play to proceed with the next stage. We have AFK mechanism in place so they can't sit idle, information flow till the game end. -### Using the default pip setup +## Known Issues -Our default setup includes a bare requirements file to be used with a [virtual environment](https://docs.python.org/3/library/venv.html). -We recommend this if you have never used any other dependency manager, although if you have, feel free to switch to it. More on that [below](#how-do-i-adapt-this-template-to-my-project). +- `/defcord start` will show as `Application did not respond` in the invalid use case scenarios instead of showing a relevant message to the user. This is due to a fact that we missed to `await` that message call during a code refactor. Invalid use case scenarios, + * Trying to start the game without being in one + * When in a game, trying to start it (only creator can) + * Trying to start a game is already running + * Trying to start the game before all the players join +- We have an image embedded in each message to represent the actor of the message. But sometimes the service that we used to host the images goes down. So sometimes the images won't appear. If you receive any error due to this in the console or bot crashes due to this, you can set this env variable `WITHOUT_ACTOR_THUMBNAIL` to `True` to disable thumbnail functionality. -#### Creating the environment +## Enhancements -Create a virtual environment in the folder `.venv`. +- Include attribute sabotage and request mechanism so that it'll be more PvP instead of PvE. +- Game time can be configurable by the creator. +- Include encryption like mechanism to make it harder for the user. +- Option to stop the game by the creator (current work around is to make all players leave / die). +- Enhance random information picking logic to make it more relevant to the players context and reduce repetitiveness of information. +- Option to pause the game. +- Option to start the game if everyone has not joined. -```shell -python -m venv .venv +## Contributions + +- **Clueless_conoisseur** (krishnabhat): Checking PRs, structuring the project +- **Automafun** (Dhanvantg): Basic game character formation, logo creation +- **Diverman** (hazyfossa): Coding game factory, player classes, implementing character templates, weighted randomness logic +- **Maheshkumar**: Coding button interactions, defcord start command, advanced UI components +- **Sapient**: Basic UI elements, PlayerState class default values, Game class creation, game flow, Anti-AFK mechanism, character images + +## Running The App + +We require you to have `python 3.12`. + +### Clone + +```sh +git clone https://github.com/krishnabhat3383/code-jam-24-luminous-lightyears.git ``` -#### Entering the environment - -It will change based on your operating system and shell. - -```shell -# Linux, Bash -$ source .venv/bin/activate -# Linux, Fish -$ source .venv/bin/activate.fish -# Linux, Csh -$ source .venv/bin/activate.csh -# Linux, PowerShell Core -$ .venv/bin/Activate.ps1 -# Windows, cmd.exe -> .venv\Scripts\activate.bat -# Windows, PowerShell -> .venv\Scripts\Activate.ps1 +if you prefer ssh way, + +```sh +git clone git@github.com:krishnabhat3383/code-jam-24-luminous-lightyears.git ``` -#### Installing the dependencies +### Move To Directory + +```sh +cd code-jam-24-luminous-lightyears +``` -Once the environment is created and activated, use this command to install the development dependencies. +### Create Virtual Environment -```shell -pip install -r requirements-dev.txt +```sh +python3 -m venv .venv ``` -#### Exiting the environment +### Activate Virtual Environment + +```sh +source .venv/bin/activate +``` -Interestingly enough, it is the same for every platform. +### Install Requirements -```shell -deactivate +```sh +pip install -r requirements.txt ``` -Once the environment is activated, all the commands listed previously should work. +### Setup Bot Token + +```sh +export DEFCON_BOT_TOKEN= +``` + +for a persistent way, + +```sh +touch .env +``` + +```sh +echo "DEFCON_BOT_TOKEN=" >> .env +``` + +### Run The Application + +```sh +python main.py +``` + +## Demo + +After the starting of the bot, following would need to be done to start the game. + +1) To initiate the game the player (further referred as 'creator' of the game) need to use `/defcord create` and add the max number of players in the game. + + Game + +2) The creator will receive a modal, which will ask for their nation name. + + Game + +3) After entering the nation name the creator will receive 3 messages. + + 1st message referring to them as a player and their nation name. + + 2nd message is a game code, for anyone joining the game (visible to everyone in chat) + + 3rd message is the standard joining message, indicating how many players are left to join and who has last joined (here the game created if of 2 players) + +
-> [!IMPORTANT] -> We highly recommend that you run `pre-commit install` as soon as possible. + Game -## How do I adapt this template to my project? +4) Other wannabe players would need to use `/defcord join` with the invite code to join the game -If you wish to use Pipenv or Poetry, you will have to move the dependencies in [`requirements-dev.txt`](requirements-dev.txt) to the development dependencies of your tool. + Game -We've included a porting of [`requirements-dev.txt`](requirements-dev.txt) to both [Poetry](samples/pyproject.toml) and [Pipenv](samples/Pipfile) in the [`samples` folder](samples). -If you use the Poetry setup, make sure to change the project name, description, and authors at the top of the file. -Also note that the Poetry [`pyproject.toml`](samples/pyproject.toml) file does not include the Ruff configuration, so if you simply replace the file then the Ruff configuration will be lost. +5) After everyone joining the game, everyone in the game will receive this message -When installing new dependencies, don't forget to [pin](https://pip.pypa.io/en/stable/topics/repeatable-installs/#pinning-the-package-versions) them by adding a version tag at the end. -For example, if I wish to install [Click](https://click.palletsprojects.com/en/8.1.x/), a quick look at [PyPI](https://pypi.org/project/click/) tells me that `8.1.7` is the latest version. -I will then add `click~=8.1`, without the last number, to my requirements file or dependency manager. + Game -> [!IMPORTANT] -> A code jam project is left unmaintained after the end of the event. If the dependencies aren't pinned, the project will break after any major change in an API. + (Here `Thonk` being the last player joined) -## Final words +6) After this the creator is able to use the command `/defcord start` to start the game, and then everyone will receive 3 messages (3rd one being part of the main game, hence covered below) -> [!IMPORTANT] -> Don't forget to replace this README with an actual description of your project! Images are also welcome! + Game -We hope this template will be helpful. Good luck in the jam! + +[Move To Top](#defcord) From f85f557874d9d5fd7211263113fa5ab343a7ca0c Mon Sep 17 00:00:00 2001 From: Maheshkumar P <67100964+Maheshkumar-novice@users.noreply.github.com> Date: Thu, 1 Aug 2024 22:34:49 +0530 Subject: [PATCH 114/116] Add game flow gif (#24) --- README.md | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 5dd1a77..b50a1cb 100644 --- a/README.md +++ b/README.md @@ -211,13 +211,13 @@ python main.py ## Demo -After the starting of the bot, following would need to be done to start the game. +After the starting of the bot, following would need to be done to start the game. 1) To initiate the game the player (further referred as 'creator' of the game) need to use `/defcord create` and add the max number of players in the game. - + Game -2) The creator will receive a modal, which will ask for their nation name. +2) The creator will receive a modal, which will ask for their nation name. Game @@ -226,7 +226,7 @@ After the starting of the bot, following would need to be done to start the game 1st message referring to them as a player and their nation name. 2nd message is a game code, for anyone joining the game (visible to everyone in chat) - + 3rd message is the standard joining message, indicating how many players are left to join and who has last joined (here the game created if of 2 players)
@@ -243,9 +243,12 @@ After the starting of the bot, following would need to be done to start the game (Here `Thonk` being the last player joined) -6) After this the creator is able to use the command `/defcord start` to start the game, and then everyone will receive 3 messages (3rd one being part of the main game, hence covered below) +6) After this the creator is able to use the command `/defcord start` to start the game, and then everyone will receive 3 messages (3rd one being part of the main game, hence covered below) Game - +7) Game flow - example + + ![flow](https://github.com/user-attachments/assets/df7e944a-a361-47c5-862a-34a4b44f0bf0) + [Move To Top](#defcord) From a02079fe395520fc784bbd10d5840536d96441ee Mon Sep 17 00:00:00 2001 From: Maheshkumar P <67100964+Maheshkumar-novice@users.noreply.github.com> Date: Thu, 1 Aug 2024 22:41:00 +0530 Subject: [PATCH 115/116] Add YT video (#25) --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index b50a1cb..7353863 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,9 @@
  • Demo of the game
  • +
  • + A video of the gameplay +
  • @@ -251,4 +254,9 @@ After the starting of the bot, following would need to be done to start the game ![flow](https://github.com/user-attachments/assets/df7e944a-a361-47c5-862a-34a4b44f0bf0) + +## Video Showcase +https://youtube.com/shorts/Aox39yoCAXY + + [Move To Top](#defcord) From 61d6412a8623e37500271355c80a8a1f156e1902 Mon Sep 17 00:00:00 2001 From: Maheshkumar P <67100964+Maheshkumar-novice@users.noreply.github.com> Date: Thu, 1 Aug 2024 22:45:24 +0530 Subject: [PATCH 116/116] Fix cases --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 7353863..913178d 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ Game Model
  • - Commands Available + Available Commands
  • How the bot relate to the theme `Information Overload` @@ -40,7 +40,7 @@ Demo of the game
  • - A video of the gameplay + A Video of the Gameplay
  • @@ -78,7 +78,7 @@ We have `3` stages, each stage will make the information flow faster. If the player is AFK or non-responsive to the information for `60` seconds, they're disqualified automatically. -## Commands available +## Available Commands ### `/defcord create`