Skip to content

Commit

Permalink
Wacky hidden ways to guess; ruleset created in yaml
Browse files Browse the repository at this point in the history
  • Loading branch information
h-anjru committed Aug 1, 2024
1 parent d6124d0 commit 397d3ca
Show file tree
Hide file tree
Showing 6 changed files with 167 additions and 39 deletions.
23 changes: 21 additions & 2 deletions crimsobot/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,17 @@
from crimsobot import db
from crimsobot.context import CrimsoContext
from crimsobot.data.img import CAPTION_RULES, IMAGE_RULES
from crimsobot.exceptions import (BadCaption, LocationNotFound, NoImageFound, NoMatchingTarotCard,
NotDirectMessage, StrictInputFailed, ZoomNotValid)
from crimsobot.exceptions import (
BadCaption,
LocationNotFound,
NoImageFound,
NoMatchingTarotCard,
NotAnInteger,
NotDirectMessage,
OutOfBounds,
StrictInputFailed,
ZoomNotValid
)
from crimsobot.help_command import PaginatedHelpCommand
from crimsobot.models.ban import Ban
from crimsobot.utils import markov as m, tools as c
Expand Down Expand Up @@ -154,6 +163,16 @@ async def on_command_error(self, ctx: commands.Context, error: Exception) -> Non
traceback_needed = False
msg_to_user = f'Zoom level **{error.original.zoom}** not good!'

if isinstance(error.original, NotAnInteger):
error_type = '**not good with math**'
traceback_needed = False
msg_to_user = f'**{error.original.guess}** is not an integer!'

if isinstance(error.original, OutOfBounds):
error_type = '**not good with math**'
traceback_needed = False
msg_to_user = f'**{error.original.guess}** is out of bounds!'

if isinstance(error.original, NoImageFound):
error_type = '**NO IMAGE**'
traceback_needed = False
Expand Down
6 changes: 1 addition & 5 deletions crimsobot/cogs/games.py
Original file line number Diff line number Diff line change
Expand Up @@ -564,16 +564,12 @@ async def glb_plays(self, ctx: commands.Context, page: int = 1) -> None:
await ctx.send(embed=embed)

@commands.command(brief='A daily guessing game.')
async def daily(self, ctx: commands.Context, lucky_number: int) -> None:
async def daily(self, ctx: commands.Context, *, lucky_number: str) -> None:
"""Guess a number 1-100 and get a daily award!
Good luck hitting the big jackpot!
"""

# exception handling
if not 1 <= lucky_number <= 100:
raise commands.BadArgument('Lucky number is out of bounds.')

# pass to helper and spit out result in an embed
embed = await crimsogames.daily(ctx.message.author, lucky_number)

Expand Down
1 change: 1 addition & 0 deletions crimsobot/data/games/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,5 +50,6 @@ def get_keys(self) -> dict:
# these are what should be imported by other scripts
STORIES = [MadlibsStory(story) for story in _madlibs_stories]
CRINGO_RULES = _ruleset['cringo']
DAILY_RULES = _ruleset['daily']
EMOJISTORY_RULES = _ruleset['emojistory']
MADLIBS_RULES = _ruleset['madlibs']
29 changes: 29 additions & 0 deletions crimsobot/data/games/rules.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,35 @@ cringo:
2: 7
4: 9
6: 9
daily:
award:
lose: 10
win: 500
not_yet:
- Patience…
- Calm down!
- 🫸 Please wait.
- Touch grass!
- Play something else!
- Nothing better to do?
- 🛑
- Access denied.
wrong_guess:
- heck!
- frick!
- Womp womp.
- 😩
- Aw shucks.
- Why even bother?
- Oh bother!
- What a bungle!
- Not good!
- Ay Dios.
- So close! (Or not; I didn't look.)
- 99 ways to lose, and you found one!
- oof.
- bruh moment.
- Congratulations! You lost.
emojistory:
join_timer: 90
minimum_length: 5
Expand Down
10 changes: 10 additions & 0 deletions crimsobot/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,13 @@ class NoEmojiFound(Exception):

class BadCaption(Exception):
pass


class NotAnInteger(Exception):
def __init__(self, guess: str) -> None:
self.guess = guess


class OutOfBounds(Exception):
def __init__(self, guess: str) -> None:
self.guess = guess
137 changes: 105 additions & 32 deletions crimsobot/utils/games.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import random
import re
import sys
from collections import Counter
from datetime import datetime
from typing import List, Tuple, Union
from datetime import datetime, timezone
from typing import Any, List, Tuple, Union

import discord
from discord import Embed
from discord.ext import commands

from crimsobot.data.games import DAILY_RULES
from crimsobot.exceptions import NotAnInteger, OutOfBounds
from crimsobot.models.currency_account import CurrencyAccount
from crimsobot.models.guess_statistic import GuessStatistic
from crimsobot.utils import tools as c
Expand Down Expand Up @@ -82,57 +86,125 @@ async def win(discord_user: DiscordUser, amount: float) -> None:
account.add_to_balance(amount)
await account.save()

# simple_eval() by MestreLion via StackOverflow with changes in exception handling
# https://stackoverflow.com/a/65945969

# Kept outside simple_eval() just for performance
_re_simple_eval = re.compile(rb'd([\x00-\xFF]+)S\x00')


def simple_eval(expr: str) -> Any:
try:
c = compile(expr, 'userinput', 'eval')
except SyntaxError:
raise SyntaxError(f'Malformed expression: {expr}')

m = _re_simple_eval.fullmatch(c.co_code)

if not m:
raise SyntaxError(f'Not a simple algebraic expression: {expr}')

try:
return c.co_consts[int.from_bytes(m.group(1), sys.byteorder)]
except IndexError:
raise SyntaxError(f'Expression not evaluated as constant: {expr}')


def integer_in_range(number: Any, low: int, high: int) -> Any:
# is 'number' a number?
try:
number = float(number)
except ValueError:
raise ValueError(f'{number} is not a number!')
except TypeError:
raise ValueError(f'{number} is not a number!')

# is 'number' (close enough to) an integer?
try:
delta = abs(number - round(number))
if delta > 1e-10: # arbitrary limit
raise NotAnInteger(str(number))
except OverflowError: # e.g. infinity will fail at delta
raise OutOfBounds(str(number))

# is 'number' in range?
if not low <= number <= high:
raise OutOfBounds(str(int(number)))

# enforce type
number = int(number)

return number


async def daily(discord_user: DiscordUser, guess: str) -> Embed:
# is the guess in range?
try:
lucky_number = integer_in_range(guess, 1, 100)
# if the guess is not already a positive integer [1 - 100]...
except ValueError:
# ...first check if it's a math expression...
try:
lucky_number = simple_eval(guess)

# but if the answer is not an integer 1-100...
lucky_number = integer_in_range(lucky_number, 1, 100)
# ...and if it's bounced from simple_eval(), try it as a string
except SyntaxError:
# find sum of remaining characters
lucky_number = 0

for char in guess.lower():
if char.isalpha():
# this will effectively bounce everything except lowercase a-z
lucky_number += (ord(char) - 96)
elif char.isnumeric():
lucky_number += int(char)
else:
pass

lucky_number = integer_in_range(lucky_number, 1, 100)
# final catchment for strings with sums outside of bounds
except ValueError: # the last bastion
raise OutOfBounds(str(lucky_number))

async def daily(discord_user: DiscordUser, lucky_number: int) -> Embed:
# fetch account
account = await CurrencyAccount.get_by_discord_user(discord_user) # type: CurrencyAccount

# get current time and time last run
now = datetime.utcnow()
now = datetime.now(timezone.utc)
last = account.ran_daily_at

# check if dates are same; if so, gotta wait
if last and last.strftime('%Y-%m-%d') == now.strftime('%Y-%m-%d'):
title = 'Patience...'
award_string = 'Daily award resets at midnight UTC (<t:0:t> local).'
title = random.choice(DAILY_RULES['not_yet'])
award_string = 'The Daily game resets at midnight UTC.'
thumb = 'clock'
color = 'orange'
footer = None

# if no wait, then check if winner or loser
else:
winning_number = random.randint(1, 100)

if winning_number == lucky_number:
daily_award = 500
daily_award = DAILY_RULES['award']['win']

title = 'JACKPOT!'
wrong = '' # they're not wrong!
if_wrong = '' # they're not wrong!
thumb = 'moneymouth'
color = 'green'
footer = f'Your guess: {lucky_number} · Congratulations!'

else:
daily_award = 10

title_choices = [
'*heck*',
'*frick*',
'*womp womp*',
'😩',
'Aw shucks.',
'Why even bother?',
'Oh, bother!',
'What a bungle!',
'Not good!',
'Ay Dios.',
"So close! (Or not; I didn't look.)",
'99 ways to lose, and you found one!',
'oof',
'bruh moment',
'Congratulations! You lost.',
]
title = random.choice(title_choices)
wrong = 'The winning number this time was **{}**, but no worries:'.format(winning_number)
daily_award = DAILY_RULES['award']['lose']

title = random.choice(DAILY_RULES['wrong_guess'])
if_wrong = f'The winning number this time was **{winning_number}**. '
thumb = 'crimsoCOIN'
color = 'yellow'
footer = f'Your guess: {lucky_number} · Thanks for playing!'

# update daily then save
account.ran_daily_at = now
Expand All @@ -141,17 +213,18 @@ async def daily(discord_user: DiscordUser, lucky_number: int) -> Embed:
# update their balance now (will repoen and reclose user)
await win(discord_user, daily_award)

award_string = '{} You have been awarded your daily **\u20A2{:.2f}**!'.format(wrong, daily_award)
thumb = thumb
color = color
# finish up the award string with amount won
award_string = f'{if_wrong}You have been awarded **\u20A2{daily_award:.2f}**!'

# the embed to return
# embed to return
embed = c.crimbed(
title=title,
descr=award_string,
thumb_name=thumb,
color_name=color,
footer=footer,
)

return embed


Expand Down

0 comments on commit 397d3ca

Please sign in to comment.