From 4b8e4f8f52c465636e21f2e56392b2ecbb29daf5 Mon Sep 17 00:00:00 2001 From: termite Date: Sat, 18 Jan 2025 11:01:47 -0800 Subject: [PATCH] Added a database, and a /top command --- .gitignore | 3 + bot.py | 508 ++++++++++++------ db.py | 124 +++++ game_logic/__init__.py | 0 .../__pycache__/__init__.cpython-313.pyc | Bin 155 -> 0 bytes .../__pycache__/connections.cpython-313.pyc | Bin 2581 -> 0 bytes game_logic/connections.py | 50 -- gamelogic/__init__.py | 13 + gamelogic/airport_guessr.py | 61 +++ gamelogic/connections.py | 126 +++++ gamelogic/flagle.py | 62 +++ gamelogic/genshindle.py | 62 +++ gamelogic/globle.py | 60 +++ gamelogic/planespottle.py | 62 +++ gamelogic/satle.py | 61 +++ gamelogic/wheretaken.py | 177 ++++++ gamelogic/wordle.py | 61 +++ 17 files changed, 1211 insertions(+), 219 deletions(-) create mode 100644 db.py delete mode 100644 game_logic/__init__.py delete mode 100644 game_logic/__pycache__/__init__.cpython-313.pyc delete mode 100644 game_logic/__pycache__/connections.cpython-313.pyc delete mode 100644 game_logic/connections.py create mode 100644 gamelogic/__init__.py create mode 100644 gamelogic/airport_guessr.py create mode 100644 gamelogic/connections.py create mode 100644 gamelogic/flagle.py create mode 100644 gamelogic/genshindle.py create mode 100644 gamelogic/globle.py create mode 100644 gamelogic/planespottle.py create mode 100644 gamelogic/satle.py create mode 100644 gamelogic/wheretaken.py create mode 100644 gamelogic/wordle.py diff --git a/.gitignore b/.gitignore index c9c6c28..493dfc0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ .env +**/__pycache__ tags .ycm_extra_conf.py +stats.db + diff --git a/bot.py b/bot.py index f324667..e5682df 100644 --- a/bot.py +++ b/bot.py @@ -1,11 +1,20 @@ -# bot.py import os +import datetime +import time + +import db + +import sqlite3 import discord -from game_logic import connections as con -import re -from collections import Counter from discord.ext import commands + +import gamelogic as gamlog + +import re + +from collections import Counter + from dotenv import load_dotenv WORDLE_CHANNEL = 1317916234863480832 @@ -14,54 +23,94 @@ MISC_GEOGRAPHY = 1317916442342981722 GLOBLE_CHANNEL = 1320505660701413511 +WHEN_WHERE_CHANNEL = 1320505906592612394 + +MISC_CHANNEL = 1317916469660618752 + +GENSHINDLE_CHANNEL = 1320505821112832010 + +CHANNELS = [ + WORDLE_CHANNEL, + MISC_GEOGRAPHY, + GLOBLE_CHANNEL, + WHEN_WHERE_CHANNEL, + MISC_CHANNEL, + GENSHINDLE_CHANNEL + ] + +possible_games = [ + "Wordle", + "Connections", + "Satle", + "Globle", + "Airport Guessr", + "WhereTaken", + "WhenTaken", + "Flagle", + "Genshindle", + "Planespottle" + ] + +chat_limit = 1000 + load_dotenv() TOKEN = os.getenv('DISCORD_TOKEN') +intents = discord.Intents.default() +intents.message_content = True -bot = commands.Bot() +bot = commands.Bot(intents=intents) + +db.create_table(); @bot.slash_command( name="stats", description="Prints out your stats in a daily game, examples include wordle", guild_ids=[261345598987436033] ) -async def stats(interaction: discord.Interaction, game, user: discord.User = None): +async def stats( + interaction: discord.Interaction, + game : discord.Option( + str, + choices=possible_games), + user: discord.User = None +): await interaction.response.defer() + + if user is None: + user = interaction.user + + conn = sqlite3.connect("stats.db") + cursor = conn.cursor() + + if not game in possible_games: + + print("SQL Injection???") + await interaction.followup.send( + "Sorry, something fucky wucky happened", + ephemeral=True + ) + return + + sql = 'SELECT * FROM ' + game.upper().replace(" ", "_") + ' WHERE NAME = ?' + cursor.execute(sql, (user.id,)) + data = cursor.fetchone() + conn.close() + + if not data: + await interaction.followup.send( + "No %s results found." % (game), + ephemeral=True + ) + return + match game.lower(): case "wordle": - channel = bot.get_channel(WORDLE_CHANNEL) - - if user is None: - user = interaction.user - - WORDLE_PATTERN = r"^Wordle (\d{1,3}(?:,\d{3})*) (X|\d+)/(\d+)" - - total_guesses = 0 - total_games = 0 - wins = 0 - - async for message in channel.history(limit=1000): # Limit can be adjusted - if message.author == user: - for line in message.content.splitlines(): - match = re.match(WORDLE_PATTERN, line.strip()) - if match: - total_games += 1 - guesses = match.group(2) - if guesses != 'X': - total_guesses += int(guesses) - wins += 1 - break - - - if total_games == 0: - await interaction.followup.send( - "No Wordle results found.", - ephemeral=True - ) - return - - average_guesses = total_guesses / wins - win_rate = (wins / total_games) * 100 + total_games = data[1] + wins = data[2] + win_rate = data[3] + total_guesses = data[4] + average_guesses = data[5] await interaction.followup.send( f"Wordle stats for {user.mention}:\n" @@ -71,47 +120,13 @@ async def stats(interaction: discord.Interaction, game, user: discord.User = Non ephemeral=False # Send message only to the user who called the command ) case "connections": - channel = bot.get_channel(WORDLE_CHANNEL) - - if user is None: - user = interaction.user - - CONNECTIONS_PATTERN = r"^Connections\nPuzzle #(\d+)\n((?:[\u2B1B\u2B1C\u2B1D\u2B1E\u2B20]{4}\n?)+)$" - - total_guesses = 0 - total_games = 0 - wins = 0 - perfects = 0 - - async for message in channel.history(limit=1000): # Limit can be adjusted - if message.author == user: - - color_grid = con.extract_connections_grid( message.content) - - if color_grid: - - guesses = con.check_connections_win(color_grid) - - total_games += 1 - - if guesses > 0: - total_guesses += guesses - wins += 1 - perfect_game = con.is_perfect_game(color_grid) - if perfect_game: - perfects += 1 - - - - if total_games == 0: - await interaction.followup.send( - "No Connections results found.", - ephemeral=True - ) - return - - average_guesses = total_guesses / wins - win_rate = (wins / total_games) * 100 + total_games = data[1] + wins = data[2] + perfects = data[3] + win_rate = data[4] + perfect_rate = data[5] + total_guesses = data[6] + average_guesses = data[7] await interaction.followup.send( f"Connections stats for {user.mention}:\n" @@ -123,40 +138,11 @@ async def stats(interaction: discord.Interaction, game, user: discord.User = Non ephemeral=False # Send message only to the user who called the command ) case "satle": - channel = bot.get_channel(MISC_GEOGRAPHY) - - if user is None: - user = interaction.user - - SATLE_PATTERN = r"🛰Satle #[0-9]+ (\d)/6" - - total_guesses = 0 - total_games = 0 - wins = 0 - - async for message in channel.history(limit=1000): # Limit can be adjusted - if message.author == user: - lines = message.content.splitlines() - for line in lines: - match = re.match(SATLE_PATTERN, line.strip()) - if match: - total_games += 1 - guesses = match.group(1) - if lines[lines.index(line)+1].__contains__("🟩"): - total_guesses += int(guesses) - wins += 1 - break - - - if total_games == 0: - await interaction.followup.send( - "No Satle results found.", - ephemeral=True - ) - return - - average_guesses = total_guesses / wins - win_rate = (wins / total_games) * 100 + total_games = data[1] + wins = data[2] + win_rate = data[3] + total_guesses = data[4] + average_guesses = data[5] await interaction.followup.send( f"Satle stats for {user.mention}:\n" @@ -166,61 +152,9 @@ async def stats(interaction: discord.Interaction, game, user: discord.User = Non ephemeral=False # Send message only to the user who called the command ) case "globle": - misc_channel = bot.get_channel(MISC_GEOGRAPHY) - globle_channel = bot.get_channel(GLOBLE_CHANNEL) - - if user is None: - user = interaction.user - - GLOBLE_PATTERN = r"I guessed today’s Globle in ([0-9]+) tries:" - BAD_GLOBLE_PATTERN = r".* = ([0-9]+)" - - total_guesses = 0 - total_games = 0 - - async for message in misc_channel.history(limit=1000): # Limit can be adjusted - if message.author == user: - lines = message.content.splitlines() - for line in lines: - match = re.match(GLOBLE_PATTERN, line.strip()) - if match: - total_games += 1 - guesses = match.group(1) - total_guesses += int(guesses) - break - match = re.match(BAD_GLOBLE_PATTERN, line.strip()) - if match: - total_games += 1 - guesses = match.group(1) - total_guesses += int(guesses) - break - - async for message in globle_channel.history(limit=1000): # Limit can be adjusted - if message.author == user: - lines = message.content.splitlines() - for line in lines: - match = re.match(GLOBLE_PATTERN, line.strip()) - if match: - total_games += 1 - guesses = match.group(1) - total_guesses += int(guesses) - break - match = re.match(BAD_GLOBLE_PATTERN, line.strip()) - if match: - total_games += 1 - guesses = match.group(1) - total_guesses += int(guesses) - break - - - if total_games == 0: - await interaction.followup.send( - "No Globle results found.", - ephemeral=True - ) - return - - average_guesses = total_guesses / total_games + total_games = data[1] + total_guesses = data[2] + average_guesses = data[3] await interaction.followup.send( f"Globle stats for {user.mention}:\n" @@ -228,15 +162,251 @@ async def stats(interaction: discord.Interaction, game, user: discord.User = Non f"Average Guesses per Game: {average_guesses:.2f}\n", ephemeral=False # Send message only to the user who called the command ) + case "airport guessr": + total_games = data[1] + wins = data[2] + win_rate = data[3] + total_guesses = data[4] + average_guesses = data[5] + + await interaction.followup.send( + f"Airport Guessr stats for {user.mention}:\n" + f"Total Games Played: {total_games}\n" + f"Average Guesses per Winning Game: {average_guesses:.2f}\n" + f"Win Rate: {win_rate:.2f}%\n", + ephemeral=False # Send message only to the user who called the command + ) + case "whentaken": + total_games = data[1] + total_distance = data[2] + average_distance = data[3] + total_points = data[4] + average_points = data[5] + total_years = data[6] + average_years = data[7] + + await interaction.followup.send( + f"Where Taken stats for {user.mention}:\n" + f"Total Games Played: {total_games}\n" + f"Average Distance: {average_distance:.2f} km\n" + f"Average Time Distance: {average_years:.2f} years\n" + f"Average Score: {average_points:.2f}\n", + ephemeral=False # Send message only to the user who called the command + ) + case "wheretaken": + total_games = data[1] + total_distance = data[2] + average_distance = data[3] + total_points = data[4] + average_points = data[5] + + await interaction.followup.send( + f"Where Taken stats for {user.mention}:\n" + f"Total Games Played: {total_games}\n" + f"Average Distance: {average_distance:.2f} km\n" + f"Average Score: {average_points:.2f}\n", + ephemeral=False # Send message only to the user who called the command + ) + case "flagle": + total_games = data[1] + wins = data[2] + win_rate = data[3] + total_guesses = data[4] + average_guesses = data[5] + + await interaction.followup.send( + f"Flagle stats for {user.mention}:\n" + f"Total Games Played: {total_games}\n" + f"Average Guesses per Winning Game: {average_guesses:.2f}\n" + f"Win Rate: {win_rate:.2f}%\n", + ephemeral=False # Send message only to the user who called the command + ) + case "genshindle": + total_games = data[1] + wins = data[2] + win_rate = data[3] + total_guesses = data[4] + average_guesses = data[5] + + await interaction.followup.send( + f"Genshindle stats for {user.mention}:\n" + f"Total Games Played: {total_games}\n" + f"Average Guesses per Winning Game: {average_guesses:.2f}\n" + f"Win Rate: {win_rate:.2f}%\n", + ephemeral=False # Send message only to the user who called the command + ) + case "planespottle": + total_games = data[1] + wins = data[2] + win_rate = data[3] + total_guesses = data[4] + average_guesses = data[5] + + await interaction.followup.send( + f"Planespottle stats for {user.mention}:\n" + f"Total Games Played: {total_games}\n" + f"Average Guesses per Winning Game: {average_guesses:.2f}\n" + f"Win Rate: {win_rate:.2f}%\n", + ephemeral=False # Send message only to the user who called the command + ) + case _: await interaction.followup.send( "Not a game.", ephemeral=True ) +table_dict = { + "games" : "GAMES", + "total wins" : "WINS", + "win rate" : "WIN_RATE", + "average guesses" : "GUESS_AVG", + "perfects" : "PERFECTS", + "perfect rate" : "PERFECT_RATE", + "distance" : "DISTANCE_AVG", + "score" : "POINTS_AVG", + "time distance" : "YEARS_AVG" + } + +async def get_table_options(ctx : discord.AutocompleteContext): + game = ctx.options['game'] + match game.lower(): + case "wordle": + return ["games","total wins","win rate","average guesses"] + case "connections": + return ["games","total wins","win rate","average guesses","perfects","perfect_rate"] + case "satle": + return ["games","total wins","win rate","average guesses"] + case "globle": + return ["games","average guesses"] + case "airport guessr": + return ["games","total wins","win rate","average guesses"] + case "whentaken": + return ["games", "distance", "score", "time distance"] + case "wheretaken": + return ["games", "distance", "score"] + case "flagle": + return ["games","total wins","win rate","average guesses"] + case "genshindle": + return ["games","total wins","win rate","average guesses"] + case "planespottle": + return ["games","total wins","win rate","average guesses"] + case _: + return ["HOW DID YOU GET HERE? SCREW YOU FOR MESSING WITH MY PROGRAM"] + + +@bot.slash_command( + name="top", + description="Prints out the top players of the given game.", + guild_ids=[261345598987436033], +) +async def top( + interaction: discord.Interaction, + game : discord.Option( + str, + choices=possible_games), + sort_by : discord.Option(str, autocomplete=discord.utils.basic_autocomplete(get_table_options)), + count : discord.Option(int) = 3 +): + await interaction.response.defer() + + table_column = table_dict[sort_by] + + conn = sqlite3.connect("stats.db") + cursor = conn.cursor() + + if not game in possible_games: + + print("SQL Injection???") + await interaction.followup.send( + "Sorry, something fucky wucky happened", + ephemeral=True + ) + return + + order = "DESC" + if table_column in ["GUESS_AVG", "DISTANCE_AVG", "YEARS_AVG"]: + order = "ASC" + + sql = 'SELECT NAME,' + table_column + ' FROM ' + game.upper().replace(" ", "_") + ' ORDER BY ' + table_column + '=0, ' + table_column + " " + order + cursor.execute(sql) + data = cursor.fetchmany(count) + conn.close() + + if not data: + await interaction.followup.send( + "No %s results found." % (game), + ephemeral=True + ) + return + + return_message = "Top " + str(len(data)) + " Players for " + game + " by " + sort_by + "\n" + + for i, stat in enumerate(data): + modifier = '' + match table_column: + case "WIN_RATE" | "PERFECT_RATE": + modifier = "%" + case "DISTANCE_AVG": + modifier = " km" + case "YEARS_AVG": + modifier = " years" + case "GAMES": + modifier = " games" + case "GUESS_AVG": + modifier = " guesses" + + line = f"#{i+1} <@{stat[0]}> Stats: {stat[1]:.2f}{modifier}\n" + + return_message += line + + + await interaction.followup.send( + return_message, + ephemeral=False # Send message only to the user who called the command + ) + +def process_message(message : discord.message, reverse : bool): + channel = bot.get_channel(WORDLE_CHANNEL) + + PATTERNS = { + gamlog.wordle : r"Wordle (\d{1,3}(?:,\d{3})*) (X|\d+)/(\d+)", + gamlog.connections : r"Connections\nPuzzle #", + gamlog.satle : r"🛰Satle #[0-9]+ (\d)/6", + gamlog.bad_globle: r"🌎 [a-zA-Z]{3} [0-9]+, [0-9]{4} 🌍", + gamlog.globle : r"I guessed today’s Globle in ([0-9]+) tries:", + gamlog.airport_guessr : r"I (got the|did not guess the) airport (in \d (guess|guesses) )?today:", + gamlog.wheretaken : r"#WhereTaken #[0-9]+ \([0-9]{2}\.[0-9]{2}\.[0-9]{4}\)", + gamlog.whentaken : r"#WhenTaken #[0-9]+ \([0-9]{2}\.[0-9]{2}\.[0-9]{4}\)", + gamlog.flagle : r"#Flagle #[0-9]+ \(.*\) ./6", + gamlog.genshindle : r"I (found|couldn't find) today's #Genshindle", + gamlog.planespottle : r"Planespottle #[0-9]+ (failed to guess|in \d/5 guesses)!" + } + + + for game, pattern in PATTERNS.items(): + match = re.search(pattern, message.content.strip()) + if match: + game(message, reverse) + @bot.event async def on_ready(): + + for channel in CHANNELS: + channel = bot.get_channel(channel) + async for message in channel.history(limit=chat_limit,oldest_first=True): + process_message(message, False) + print('Ready!') +@bot.event +async def on_message(message : discord.message): + process_message(message, False) + +@bot.event +async def on_message_delete(message : discord.message): + process_message(message, True) + bot.run(TOKEN) + diff --git a/db.py b/db.py new file mode 100644 index 0000000..a268b64 --- /dev/null +++ b/db.py @@ -0,0 +1,124 @@ +import sqlite3 +import pathlib + + +def create_table(): + cnt = None + cursor = None + + try: + # please remove this later, + # it just removes the database for testing + pathlib.Path.unlink("./stats.db") + + conn = sqlite3.connect('stats.db') + cursor = conn.cursor() + print('DB Init') + query = 'select sqlite_version();' + cursor.execute(query) + + result = cursor.fetchall() + print('SQLite Version is {}'.format(result)) + except sqlite3.Error as error: + print('Error occurred - ', error) + game_tables = [''' CREATE TABLE IF NOT EXISTS WORDLE( + NAME INTEGER PRIMARY KEY NOT NULL, + GAMES INTEGER NOT NULL, + WINS INTEGER NOT NULL, + WIN_RATE REAL NOT NULL, + GUESSES INTEGER NOT NULL, + GUESS_AVG REAL NOT NULL, + LAST_SYNC INTEGER NOT NULL + ); ''', + ''' CREATE TABLE IF NOT EXISTS CONNECTIONS( + NAME INTEGER PRIMARY KEY NOT NULL, + GAMES INTEGER NOT NULL, + WINS INTEGER NOT NULL, + PERFECTS INTEGER NOT NULL, + WIN_RATE REAL NOT NULL, + PERFECT_RATE REAL NOT NULL, + GUESSES INTEGER NOT NULL, + GUESS_AVG REAL NOT NULL, + LAST_SYNC INTEGER NOT NULL + ); ''', + ''' CREATE TABLE IF NOT EXISTS SATLE( + NAME INTEGER PRIMARY KEY NOT NULL, + GAMES INTEGER NOT NULL, + WINS INTEGER NOT NULL, + WIN_RATE REAL NOT NULL, + GUESSES INTEGER NOT NULL, + GUESS_AVG REAL NOT NULL, + LAST_SYNC INTEGER NOT NULL + ); ''', + ''' CREATE TABLE IF NOT EXISTS GLOBLE( + NAME INTEGER PRIMARY KEY NOT NULL, + GAMES INTEGER NOT NULL, + GUESSES INTEGER NOT NULL, + GUESS_AVG REAL NOT NULL, + LAST_SYNC INTEGER NOT NULL + ); ''', + ''' CREATE TABLE IF NOT EXISTS AIRPORT_GUESSR( + NAME INTEGER PRIMARY KEY NOT NULL, + GAMES INTEGER NOT NULL, + WINS INTEGER NOT NULL, + WIN_RATE REAL NOT NULL, + GUESSES INTEGER NOT NULL, + GUESS_AVG REAL NOT NULL, + LAST_SYNC INTEGER NOT NULL + ); ''', + ''' CREATE TABLE IF NOT EXISTS WHERETAKEN( + NAME INTEGER PRIMARY KEY NOT NULL, + GAMES INTEGER NOT NULL, + DISTANCE REAL NOT NULL, + DISTANCE_AVG REAL NOT NULL, + POINTS INTEGER NOT NULL, + POINTS_AVG REAL NOT NULL, + LAST_SYNC INTEGER NOT NULL + ); ''', + ''' CREATE TABLE IF NOT EXISTS WHENTAKEN( + NAME INTEGER PRIMARY KEY NOT NULL, + GAMES INTEGER NOT NULL, + DISTANCE REAL NOT NULL, + DISTANCE_AVG REAL NOT NULL, + POINTS INTEGER NOT NULL, + POINTS_AVG REAL NOT NULL, + YEARS INTEGER NOT NULL, + YEARS_AVG REAL NOT NULL, + LAST_SYNC INTEGER + ); ''', + ''' CREATE TABLE IF NOT EXISTS FLAGLE( + NAME INTEGER PRIMARY KEY NOT NULL, + GAMES INTEGER NOT NULL, + WINS INTEGER NOT NULL, + WIN_RATE REAL NOT NULL, + GUESSES INTEGER NOT NULL, + GUESS_AVG REAL NOT NULL, + LAST_SYNC INTEGER NOT NULL + ); ''', + ''' CREATE TABLE IF NOT EXISTS GENSHINDLE( + NAME INTEGER PRIMARY KEY NOT NULL, + GAMES INTEGER NOT NULL, + WINS INTEGER NOT NULL, + WIN_RATE REAL NOT NULL, + GUESSES INTEGER NOT NULL, + GUESS_AVG REAL NOT NULL, + LAST_SYNC INTEGER NOT NULL + ); ''', + ''' CREATE TABLE IF NOT EXISTS PLANESPOTTLE( + NAME INTEGER PRIMARY KEY NOT NULL, + GAMES INTEGER NOT NULL, + WINS INTEGER NOT NULL, + WIN_RATE REAL NOT NULL, + GUESSES INTEGER NOT NULL, + GUESS_AVG REAL NOT NULL, + LAST_SYNC INTEGER NOT NULL + ); ''' + ] + + for table in game_tables: + cursor.execute(table) + + conn.commit() + conn.close() + print("Tables Created") + diff --git a/game_logic/__init__.py b/game_logic/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/game_logic/__pycache__/__init__.cpython-313.pyc b/game_logic/__pycache__/__init__.cpython-313.pyc deleted file mode 100644 index 90f508d6780738bb9e8280ce41ce2f29e7690853..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 155 zcmey&%ge<81YAC?=^*+sh=2h`DC08=kTI1Zok5e)ZzV$!6Oi{ABz4P5KO;XkRlg*) zC^xetRX-)QOuwMABqKjhKRF{cEhkksDZfNNJux>mJ|{mtGg&`AJ~J<~BtBlRpz;=n eO>TZlX-=wL5i8IrkX6MX#z$sGM#ds$APWE|`y~GW diff --git a/game_logic/__pycache__/connections.cpython-313.pyc b/game_logic/__pycache__/connections.cpython-313.pyc deleted file mode 100644 index 7acabc8b2cf7a0e46525dc1b379e3a8c98f23e65..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2581 zcmb7F%Wo4$7@zg7A90d~;5asrgw2zfHns~yh>*H!ogx&-gRBh^sbnqo`T^@*cXpFN zT8XF?Qq*2RRYZEDEv?eWArc%=Pbuvm;O$nmwNfRd_L3VVT)6d{^*V{8(8owSGduG= z=KJRN`)0GT(T$*ed;V7DGbciSke_O^S(D8!m@Fd>anw09N@0pcob4dOHjaj#<{0Ri zj6LAkAPND>a7N`>NfmICA`4b?z(~WlS%B4L6h~7#cP*hoYm}%sHASR%HI5pzwEC?b z{x*Ws#HrKp&wq>%=-GEE!7pd4>dioumA1V$01Fb>*i(~^+Xk&nNc8mnIL-X?$(fhh z+1vis$*NvB#ypRG+RLGT@#Tm+rs}HkY>ymWjaG8Tojr)$&A3q?l_g0?sySIw7F?6F z3k#yab{mv?gQWCKz;4(T73T_uTTyweD)TutYcT1Ylrm^hkYKkU=G1`8aOMR?;WL8K zWK9xD8LnGW4Nt+enviDmR|K3e?HJ9qivrkdtXVSL8JtU5(hcUkYS5jOMHwf^n&N^} zvQ{(R2xaBG5CV|pbE*(Z33H*s4K*uEp=4G_i^9MaSq)|QypRy(OfDG$gVorBg&T%n zxUOP8sgjdd*G(_D6}%AODL+8Bh{~<)ck_4hpUR69WoK*2*{L}@?+uikVa*v{yS)CU zJ~;KHW1!S=OzSwdHl%k%e)g_1W* z9xM9COa7SVkLmu4ON~z*sI|A`4QSrLYR_6zDLkTuN7gUv;YrbRd5^3zx_1DCnht=ECrvGzaC%h3KaxI^e{B~3Jm?~& z*g{CLdC=LzzyNAyi;**1+>389u8Jv+8k$BZ1)LabCw!Zj+{~e&vk1+hWjl+;AYgJa8R{gRrUEv65ERD0K>?d# z3x*AQlGJI1R5zBzUKotJ;fx>&*9$me^aGQU*J>C7AqR&=ImwGkB)BEn3yMk$bQ?gJ zVsGr12)7nreedh5FnOVn(|_3Pp)he1aA0zC2CWFH!CaMd5+=bIG%t#VV~!UgZ2>zb z#ta(E^G3t0luOGvpMW35cqJ|JDpVRFWs=ossFa6F7z(NTW__rH;@$K7H~>;U_>>Qz zTSR{~pq`-a8Tn#pCHm0wkbC5LF#Vu?y;FNVdN=y{(Bk=`XXJ_3clXkrODpf{-lL1- z&mE}c&`L!299=vQb65S+uY5}{akBXhCy6*;^0A-?A zflMQdQ%$>E;vno1>MDKEEu!ac0-~XhZmx9d&O`TUt$X6fQN4Sj#7=1JM7haRuHT0V zWXIu+t_R4A)+=su$9%C&RGW4&Bo>*{6&ARL^Kxod6e1YPC?cm)h^ePEMNz+_!_Sy~ Sl=oSGGu61+=cJlV#lHbMp*#El diff --git a/game_logic/connections.py b/game_logic/connections.py deleted file mode 100644 index 494cbae..0000000 --- a/game_logic/connections.py +++ /dev/null @@ -1,50 +0,0 @@ -from collections import Counter - -def extract_connections_grid(message): - message_content = message.strip() - if message_content.startswith("Connections") and "Puzzle #" in message_content: - # Extract the puzzle number - puzzle_number_start = message_content.find("Puzzle #") + len("Puzzle #") - puzzle_number_end = message_content.find("\n", puzzle_number_start) - puzzle_number = message_content[puzzle_number_start:puzzle_number_end].strip() - - # Extract the grid (assume the grid starts after the first two lines) - grid_start = message_content.find("\n", message_content.find("Puzzle #")) + 1 - grid_content = message_content[grid_start:].strip() - - # Split the grid into lines - color_grid = grid_content.split("\n") - return color_grid - else: - return False - - - -def check_connections_win(color_grid): - """ - Check if the Connections game is a win and return the number of guesses. - A win is when there are exactly 4 groups of 4 colors each. - """ - # Flatten the color grid into a list of colors - uniform_rows = [row for row in color_grid if len(set(row)) == 1] - - flattened_grid = ''.join(uniform_rows) - - # Count the occurrences of each color in the grid - color_counts = Counter(flattened_grid) - - # Check if there are exactly 4 different colors with 4 squares each - if len(color_counts) == 4 and all(count == 4 for count in color_counts.values()): - # A win: return the number of guesses (lines in the grid) - return len(color_grid) - return 0 # Not a win - -def is_perfect_game(color_grid): - """ - Check if the Connections game is a perfect game (solved in 4 guesses, no mistakes). - """ - # A perfect game is one where the first 4 lines form perfect groups - if len(color_grid) == 4 and all(len(set(line)) == 1 for line in color_grid): - return True # It's a perfect game - return False - diff --git a/gamelogic/__init__.py b/gamelogic/__init__.py new file mode 100644 index 0000000..876da9f --- /dev/null +++ b/gamelogic/__init__.py @@ -0,0 +1,13 @@ +from gamelogic import wheretaken +from .wordle import wordle +from .connections import connections +from .satle import satle +from .globle import globle +from .globle import bad_globle +from .airport_guessr import airport_guessr +from .flagle import flagle +from .genshindle import genshindle +from .planespottle import planespottle +from .wheretaken import wheretaken +from .wheretaken import whentaken + diff --git a/gamelogic/airport_guessr.py b/gamelogic/airport_guessr.py new file mode 100644 index 0000000..d2918e3 --- /dev/null +++ b/gamelogic/airport_guessr.py @@ -0,0 +1,61 @@ +import sys +import time +import discord +import re + +import sqlite3 + + +def airport_guessr(message : discord.message, reverse): + if reverse == True: + reverse = -1 + else: + reverse = 1 + + conn = sqlite3.connect('stats.db') + cursor = conn.cursor() + + PATTERN = r"I got the airport in (\d) guess.*? today:" + FAIL_PATTERN = r"I did not guess the airport today:" + + total_guesses = 0 + total_games = 0 + wins = 0 + sync_time = 0 + new_sync_time = (int)(time.mktime(message.created_at.timetuple())) + + cursor.execute('''SELECT * FROM AIRPORT_GUESSR WHERE NAME = ?''', (message.author.id,)) + + data = cursor.fetchone() + + if data: + total_games = data[1] + wins = data[2] + total_guesses = data[4] + sync_time = data[6] + if sync_time > new_sync_time and reverse == 1: + return + + match = re.search(FAIL_PATTERN, message.content.strip()) + if not match: + match = re.search(PATTERN, message.content.strip()) + + guesses = match.group(1) + total_guesses += int(guesses) * reverse + wins += 1 * reverse + + total_games += 1 * reverse + + if wins != 0: + average_guesses = total_guesses / wins + else: + average_guesses = 0 + + + win_rate = (wins / total_games) * 100 + + cursor.execute('''INSERT OR REPLACE INTO AIRPORT_GUESSR VALUES(?, ?, ?, ?, ?, ?, ?)''', + (message.author.id,total_games,wins,win_rate,total_guesses,average_guesses,new_sync_time)) + + conn.commit() + conn.close() diff --git a/gamelogic/connections.py b/gamelogic/connections.py new file mode 100644 index 0000000..d81564b --- /dev/null +++ b/gamelogic/connections.py @@ -0,0 +1,126 @@ +from collections import Counter + +import discord +import sqlite3 +import sys +import time +import re + +def connections(message : discord.message, reverse): + if reverse == True: + reverse = -1 + else: + reverse = 1 + + conn = sqlite3.connect('stats.db') + cursor = conn.cursor() + + sync_time = 0 + new_sync_time = (int)(time.mktime(message.created_at.timetuple())) + + cursor.execute('''SELECT * FROM CONNECTIONS WHERE NAME = ?''', (message.author.id,)) + + data = cursor.fetchone() + + total_guesses = 0 + total_games = 0 + wins = 0 + perfects = 0 + + if data: + total_games = data[1] + wins = data[2] + perfects = data[3] + total_guesses = data[6] + sync_time = data[8] + if sync_time > new_sync_time and reverse == 1: + return + + color_grid = extract_connections_grid( message.content) + + if color_grid: + + guesses = check_connections_win(color_grid) + + total_games += 1 * reverse + + if guesses > 0: + total_guesses += guesses * reverse + wins += 1 * reverse + perfect_game = is_perfect_game(color_grid) + if perfect_game: + perfects += 1 * reverse + else: + print("Connections: Failed parsing color grid") + print(message.content) + return + + + if wins != 0: + average_guesses = total_guesses / wins + else: + average_guesses = 0 + win_rate = (wins / total_games) * 100 + perfect_rate = (perfects / total_games) * 100 + + + cursor.execute('''INSERT OR REPLACE INTO CONNECTIONS VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?)''', + (message.author.id, + total_games, + wins, + perfects, + win_rate, + perfect_rate, + total_guesses, + average_guesses, + new_sync_time, + )) + + conn.commit() + conn.close() + +def extract_connections_grid(message): + message_content = message.strip() + # Extract the puzzle number + puzzle_number_start = message_content.find("Puzzle #") + len("Puzzle #") + puzzle_number_end = message_content.find("\n", puzzle_number_start) + puzzle_number = message_content[puzzle_number_start:puzzle_number_end].strip() + + # Extract the grid (assume the grid starts after the first two lines) + grid_start = message_content.find("\n", message_content.find("Puzzle #")) + 1 + grid_content = message_content[grid_start:].strip() + + # Split the grid into lines + color_grid = grid_content.split("\n") + return color_grid + + + +def check_connections_win(color_grid): + """ + Check if the Connections game is a win and return the number of guesses. + A win is when there are exactly 4 groups of 4 colors each. + """ + # Flatten the color grid into a list of colors + uniform_rows = [row for row in color_grid if len(set(row)) == 1] + + flattened_grid = ''.join(uniform_rows) + + # Count the occurrences of each color in the grid + color_counts = Counter(flattened_grid) + + # Check if there are exactly 4 different colors with 4 squares each + if len(color_counts) == 4 and all(count == 4 for count in color_counts.values()): + # A win: return the number of guesses (lines in the grid) + return len(color_grid) + return 0 # Not a win + +def is_perfect_game(color_grid): + """ + Check if the Connections game is a perfect game (solved in 4 guesses, no mistakes). + """ + # A perfect game is one where the first 4 lines form perfect groups + if len(color_grid) == 4 and all(len(set(line)) == 1 for line in color_grid): + return True # It's a perfect game + return False + diff --git a/gamelogic/flagle.py b/gamelogic/flagle.py new file mode 100644 index 0000000..8ec83f4 --- /dev/null +++ b/gamelogic/flagle.py @@ -0,0 +1,62 @@ +import sys +import time +import discord +import re + +import sqlite3 + + +def flagle(message : discord.message, reverse): + if reverse == True: + reverse = -1 + else: + reverse = 1 + + conn = sqlite3.connect('stats.db') + cursor = conn.cursor() + + PATTERN = r"#Flagle #[0-9]+ \(.*\) (.)/6" + + + match = re.match(PATTERN, message.content.strip()) + + + total_guesses = 0 + total_games = 0 + wins = 0 + sync_time = 0 + new_sync_time = (int)(time.mktime(message.created_at.timetuple())) + + cursor.execute('''SELECT * FROM FLAGLE WHERE NAME = ?''', (message.author.id,)) + + data = cursor.fetchone() + + + if data: + total_games = data[1] + wins = data[2] + total_guesses = data[4] + sync_time = data[6] + if sync_time > new_sync_time and reverse == 1: + return + + guesses = match.group(1) + if guesses != 'X': + total_guesses += int(guesses) * reverse + wins += 1 * reverse + + total_games += 1 * reverse + + if wins != 0: + average_guesses = total_guesses / wins + else: + average_guesses = 0 + + + win_rate = (wins / total_games) * 100 + + cursor.execute('''INSERT OR REPLACE INTO FLAGLE VALUES(?, ?, ?, ?, ?, ?, ?)''', + (message.author.id,total_games,wins,win_rate,total_guesses,average_guesses,new_sync_time)) + + conn.commit() + conn.close() diff --git a/gamelogic/genshindle.py b/gamelogic/genshindle.py new file mode 100644 index 0000000..0e74abc --- /dev/null +++ b/gamelogic/genshindle.py @@ -0,0 +1,62 @@ +import sys +import time +import discord +import re + +import sqlite3 + + +def genshindle(message : discord.message, reverse): + if reverse == True: + reverse = -1 + else: + reverse = 1 + + conn = sqlite3.connect('stats.db') + cursor = conn.cursor() + + PATTERN = r"I found today's #Genshindle in (\d) tr.*?!" + FAIL_PATTERN = r"I couldn't find today's #Genshindle" + + + total_guesses = 0 + total_games = 0 + wins = 0 + sync_time = 0 + new_sync_time = (int)(time.mktime(message.created_at.timetuple())) + + cursor.execute('''SELECT * FROM GENSHINDLE WHERE NAME = ?''', (message.author.id,)) + + data = cursor.fetchone() + + if data: + total_games = data[1] + wins = data[2] + total_guesses = data[4] + sync_time = data[6] + if sync_time > new_sync_time and reverse == 1: + return + + match = re.search(FAIL_PATTERN, message.content.strip()) + if not match: + match = re.search(PATTERN, message.content.strip()) + + guesses = match.group(1) + total_guesses += int(guesses) * reverse + wins += 1 * reverse + + total_games += 1 * reverse + + if wins != 0: + average_guesses = total_guesses / wins + else: + average_guesses = 0 + + + win_rate = (wins / total_games) * 100 + + cursor.execute('''INSERT OR REPLACE INTO GENSHINDLE VALUES(?, ?, ?, ?, ?, ?, ?)''', + (message.author.id,total_games,wins,win_rate,total_guesses,average_guesses,new_sync_time)) + + conn.commit() + conn.close() diff --git a/gamelogic/globle.py b/gamelogic/globle.py new file mode 100644 index 0000000..96aceb1 --- /dev/null +++ b/gamelogic/globle.py @@ -0,0 +1,60 @@ +import sys +import time +import discord +import re + +import sqlite3 + + +def globle(message : discord.message, reverse, bad = False): + if reverse == True: + reverse = -1 + else: + reverse = 1 + + conn = sqlite3.connect('stats.db') + cursor = conn.cursor() + + if bad: + PATTERN = r".* = ([0-9]+)" + else: + PATTERN = r"I guessed today’s Globle in ([0-9]+) tries:" + + match = re.search(PATTERN, message.content.strip()) + + if not match: + print("AAAAA") + print(message.content) + return + + total_guesses = 0 + total_games = 0 + sync_time = 0 + new_sync_time = (int)(time.mktime(message.created_at.timetuple())) + + cursor.execute('''SELECT * FROM GLOBLE WHERE NAME = ?''', (message.author.id,)) + + data = cursor.fetchone() + + if data: + total_games = data[1] + total_guesses = data[2] + sync_time = data[4] + if sync_time > new_sync_time and reverse == 1: + return + + guesses = match.group(1) + total_guesses += int(guesses) * reverse + + total_games += 1 * reverse + + average_guesses = total_guesses / total_games + + cursor.execute('''INSERT OR REPLACE INTO GLOBLE VALUES(?, ?, ?, ?, ?)''', + (message.author.id,total_games,total_guesses,average_guesses,new_sync_time)) + + conn.commit() + conn.close() + +def bad_globle(message : discord.message, reverse): + globle(message, reverse, True) diff --git a/gamelogic/planespottle.py b/gamelogic/planespottle.py new file mode 100644 index 0000000..5e28e01 --- /dev/null +++ b/gamelogic/planespottle.py @@ -0,0 +1,62 @@ +import sys +import time +import discord +import re + +import sqlite3 + + +def planespottle(message : discord.message, reverse): + if reverse == True: + reverse = -1 + else: + reverse = 1 + + conn = sqlite3.connect('stats.db') + cursor = conn.cursor() + + PATTERN = r"Planespottle #[0-9]+ in (\d)/5" + FAIL_PATTERN = r"Planespottle #[0-9]+ failed to guess!" + + + total_guesses = 0 + total_games = 0 + wins = 0 + sync_time = 0 + new_sync_time = (int)(time.mktime(message.created_at.timetuple())) + + cursor.execute('''SELECT * FROM PLANESPOTTLE WHERE NAME = ?''', (message.author.id,)) + + data = cursor.fetchone() + + if data: + total_games = data[1] + wins = data[2] + total_guesses = data[4] + sync_time = data[6] + if sync_time > new_sync_time and reverse == 1: + return + + match = re.search(FAIL_PATTERN, message.content.strip()) + if not match: + match = re.search(PATTERN, message.content.strip()) + + guesses = match.group(1) + total_guesses += int(guesses) * reverse + wins += 1 * reverse + + total_games += 1 * reverse + + if wins != 0: + average_guesses = total_guesses / wins + else: + average_guesses = 0 + + + win_rate = (wins / total_games) * 100 + + cursor.execute('''INSERT OR REPLACE INTO PLANESPOTTLE VALUES(?, ?, ?, ?, ?, ?, ?)''', + (message.author.id,total_games,wins,win_rate,total_guesses,average_guesses,new_sync_time)) + + conn.commit() + conn.close() diff --git a/gamelogic/satle.py b/gamelogic/satle.py new file mode 100644 index 0000000..3bfa21a --- /dev/null +++ b/gamelogic/satle.py @@ -0,0 +1,61 @@ +import sys +import time +import discord +import re + +import sqlite3 + + +def satle(message : discord.message, reverse): + if reverse == True: + reverse = -1 + else: + reverse = 1 + + conn = sqlite3.connect('stats.db') + cursor = conn.cursor() + + PATTERN = r"🛰Satle #[0-9]+ (\d)/6" + + match = re.match(PATTERN, message.content.strip()) + + total_guesses = 0 + total_games = 0 + wins = 0 + sync_time = 0 + new_sync_time = (int)(time.mktime(message.created_at.timetuple())) + + cursor.execute('''SELECT * FROM SATLE WHERE NAME = ?''', (message.author.id,)) + + data = cursor.fetchone() + + if data: + total_games = data[1] + wins = data[2] + total_guesses = data[4] + sync_time = data[6] + if sync_time > new_sync_time and reverse == 1: + return + + lines = message.content.splitlines() + match = re.match(PATTERN, lines[0].strip()) + guesses = match.group(1) + if lines[1].__contains__("🟩"): + wins += 1 * reverse + total_guesses += int(guesses) * reverse + + total_games += 1 * reverse + + if wins != 0: + average_guesses = total_guesses / wins + else: + average_guesses = 0 + + + win_rate = (wins / total_games) * 100 + + cursor.execute('''INSERT OR REPLACE INTO SATLE VALUES(?, ?, ?, ?, ?, ?, ?)''', + (message.author.id,total_games,wins,win_rate,total_guesses,average_guesses,new_sync_time)) + + conn.commit() + conn.close() diff --git a/gamelogic/wheretaken.py b/gamelogic/wheretaken.py new file mode 100644 index 0000000..c798787 --- /dev/null +++ b/gamelogic/wheretaken.py @@ -0,0 +1,177 @@ +import sys +import time +import discord +import re + +import sqlite3 + +def wheretaken(message : discord.message, reverse): + if reverse == True: + reverse = -1 + else: + reverse = 1 + + total_distance = 0 + total_points = 0 + total_games = 0 + + sync_time = 0 + new_sync_time = (int)(time.mktime(message.created_at.timetuple())) + + conn = sqlite3.connect('stats.db') + cursor = conn.cursor() + + cursor.execute('''SELECT * FROM WHERETAKEN WHERE NAME = ?''', (message.author.id,)) + + data = cursor.fetchone() + + if data: + total_games = data[1] + total_distance = data[2] + total_points = data[4] + sync_time = data[6] + if sync_time > new_sync_time and reverse == 1: + return + + distances, points = parse_distances_and_points(message.content) + avg_distance = calculate_average(distances) + + total_distance += avg_distance * reverse + total_points += sum(points) * reverse + total_games += 1 * reverse + + + average_distance = total_distance / total_games + average_points = total_points / total_games + + cursor.execute('''INSERT OR REPLACE INTO WHERETAKEN VALUES(?, ?, ?, ?, ?, ?, ?)''', + (message.author.id, + total_games, + total_distance, + average_distance, + total_points, + average_points, + new_sync_time + )) + + conn.commit() + conn.close() + +def whentaken(message : discord.message, reverse): + if reverse == True: + reverse = -1 + else: + reverse = 1 + + total_distance = 0 + total_points = 0 + total_games = 0 + total_years = 0 + + sync_time = 0 + new_sync_time = (int)(time.mktime(message.created_at.timetuple())) + + conn = sqlite3.connect('stats.db') + cursor = conn.cursor() + + cursor.execute('''SELECT * FROM WHENTAKEN WHERE NAME = ?''', (message.author.id,)) + + data = cursor.fetchone() + + if data: + total_games = data[1] + total_distance = data[2] + total_points = data[4] + total_years = data[6] + sync_time = data[8] + if sync_time > new_sync_time and reverse == 1: + return + + distances, points, years = parse_distances_points_years(message.content) + avg_distance = calculate_average(distances) + avg_years = calculate_average(years) + + total_years += avg_years + total_distance += avg_distance * reverse + total_points += sum(points) * reverse + total_games += 1 * reverse + + + average_distance = total_distance / total_games + average_points = total_points / total_games + average_years = total_years / total_games + + cursor.execute('''INSERT OR REPLACE INTO WHENTAKEN VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?)''', + (message.author.id, + total_games, + total_distance, + average_distance, + total_points, + average_points, + total_years, + average_years, + new_sync_time + )) + + conn.commit() + conn.close() + + +def parse_distances_points_years(message): + pattern = r'.{4}([0-9]*\.?[0-9]+)([A-Za-z]?) ([A-Za-z]+) - ..([0-9]+) yrs - .([0-9]+)/200' + matches = re.findall(pattern, message) + + converted_distances = [] + points = [] + years = [] + + for distance, thousands, unit, year, point in matches: + # Convert distance to float + distance = float(distance) + + # Handle 'K' (thousands) notation + if thousands: + distance *= 1000 # Convert K to actual kilometers + + # If the unit is meters, convert to kilometers + if unit == 'm': + distance /= 1000 # Convert meters to kilometers + + # Store the distance and corresponding points + converted_distances.append(distance) + points.append(int(point)) + years.append(int(year)) + + return converted_distances, points, years + +def parse_distances_and_points(message): + # Find all the distances in the format of 'xxx km' or 'xxx m' and their corresponding points + pattern = r'(\d+(\.\d+)?)\s*(km|m|K)\s*(km)?\s*[^a-zA-Z0-9]*\s*(\d+)\s*/\s*\d+' + matches = re.findall(pattern, message) + + converted_distances = [] + points = [] + + for distance, _, unit, _, point in matches: + # Convert distance to float + distance = float(distance) + + # Handle 'K' (thousands) notation + if unit == 'K': + distance *= 1000 # Convert K to actual kilometers + + # If the unit is meters, convert to kilometers + elif unit == 'm': + distance /= 1000 # Convert meters to kilometers + + # Store the distance and corresponding points + converted_distances.append(distance) + points.append(int(point)) + + return converted_distances, points + +def calculate_average(distances): + # Calculate the average of the distances + if distances: + return sum(distances) / len(distances) + return 0 diff --git a/gamelogic/wordle.py b/gamelogic/wordle.py new file mode 100644 index 0000000..b846a1e --- /dev/null +++ b/gamelogic/wordle.py @@ -0,0 +1,61 @@ +import sys +import time +import discord +import re + +import sqlite3 + + +def wordle(message : discord.message, reverse): + if reverse == True: + reverse = -1 + else: + reverse = 1 + + conn = sqlite3.connect('stats.db') + cursor = conn.cursor() + + PATTERN = r"^Wordle (\d{1,3}(?:,\d{3})*) (X|\d+)/(\d+)" + + match = re.match(PATTERN, message.content.strip()) + + + total_guesses = 0 + total_games = 0 + wins = 0 + sync_time = 0 + new_sync_time = (int)(time.mktime(message.created_at.timetuple())) + + cursor.execute('''SELECT * FROM WORDLE WHERE NAME = ?''', (message.author.id,)) + + data = cursor.fetchone() + + + if data: + total_games = data[1] + wins = data[2] + total_guesses = data[4] + sync_time = data[6] + if sync_time > new_sync_time and reverse == 1: + return + + guesses = match.group(2) + if guesses != 'X': + total_guesses += int(guesses) * reverse + wins += 1 * reverse + + total_games += 1 * reverse + + if wins != 0: + average_guesses = total_guesses / wins + else: + average_guesses = 0 + + + win_rate = (wins / total_games) * 100 + + cursor.execute('''INSERT OR REPLACE INTO WORDLE VALUES(?, ?, ?, ?, ?, ?, ?)''', + (message.author.id,total_games,wins,win_rate,total_guesses,average_guesses,new_sync_time)) + + conn.commit() + conn.close()