forked from c0de/BaseballBot
Adding points check and fixing some minor other issues
This commit is contained in:
parent
caa0819d91
commit
f592bb8234
1
.gitignore
vendored
1
.gitignore
vendored
@ -1 +1,2 @@
|
||||
.idea/*
|
||||
__pycache__/
|
||||
|
@ -1,11 +1,8 @@
|
||||
from sqlalchemy import Column, String, Integer, ForeignKey, Date
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.ext.hybrid import hybrid_property
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
from src.main.db_session import DatabaseSession
|
||||
|
||||
Base = declarative_base()
|
||||
|
||||
class Play(Base):
|
||||
@ -14,6 +11,7 @@ class Play(Base):
|
||||
play_id = Column(String, nullable=False, primary_key=True)
|
||||
pitch_value = Column(Integer, nullable=True)
|
||||
creation_date = Column(Date, nullable=False)
|
||||
server_id = Column(String, nullable=False)
|
||||
|
||||
guesses = relationship(lambda : Guess)
|
||||
|
||||
|
@ -3,6 +3,7 @@ from copy import deepcopy
|
||||
from src.main.database_module.database_classes.db_classes import Guess
|
||||
from src.main.db_session import DatabaseSession
|
||||
|
||||
|
||||
MEMBER_ID = 'member_id'
|
||||
PLAY_ID = 'play_id'
|
||||
GUESSED_NUMBER = 'guessed_number'
|
||||
@ -15,11 +16,13 @@ class GuessDAO():
|
||||
Session = None
|
||||
engine = None
|
||||
|
||||
_database_session = None
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
self._database_session = DatabaseSession()
|
||||
|
||||
def insert(self, guess_info):
|
||||
session = DatabaseSession.session
|
||||
session = self._database_session.get_or_create_session()
|
||||
|
||||
guess = Guess(
|
||||
member_id=guess_info[MEMBER_ID],
|
||||
@ -55,7 +58,7 @@ class GuessDAO():
|
||||
return converted_games
|
||||
|
||||
def set_differences(self, pitch_value, play_id):
|
||||
session = DatabaseSession.session
|
||||
session = self._database_session.get_or_create_session()
|
||||
games_to_update = self.__convert_all__(session.query(Guess).filter(Guess.play_id == play_id))
|
||||
|
||||
for game in games_to_update:
|
||||
@ -74,7 +77,7 @@ class GuessDAO():
|
||||
return possible_value
|
||||
|
||||
def fetch_closest(self, num_to_fetch):
|
||||
session = DatabaseSession.session
|
||||
session = self._database_session.get_or_create_session()
|
||||
|
||||
return self.__convert_all__(
|
||||
session\
|
||||
@ -83,8 +86,19 @@ class GuessDAO():
|
||||
.limit(num_to_fetch)
|
||||
)
|
||||
|
||||
def refresh(self):
|
||||
self._database_session.__create_new_session__() # I know, I know. It's fine.
|
||||
|
||||
def get_all_guesses_for_plays(self, play_ids):
|
||||
session = self._database_session.get_or_create_session()
|
||||
return self.__convert_all__(
|
||||
session
|
||||
.query(Guess)
|
||||
.filter(Guess.play_id.in_(play_ids))
|
||||
)
|
||||
|
||||
def get_closest_on_play(self, play):
|
||||
session = DatabaseSession.session
|
||||
session = self._database_session.get_or_create_session()
|
||||
|
||||
# TODO: Make this a MAX query for ties
|
||||
converted_guesses = self.__convert_all__(
|
||||
|
@ -1,46 +1,62 @@
|
||||
from copy import deepcopy
|
||||
from sqlalchemy.sql.expression import and_
|
||||
|
||||
from src.main.db_session import DatabaseSession
|
||||
from src.main.database_module.database_classes.db_classes import Play
|
||||
|
||||
import datetime
|
||||
|
||||
PLAY_ID = 'play_id'
|
||||
PITCH_VALUE = 'pitch_value'
|
||||
CREATION_DATE = 'creation_date'
|
||||
SERVER_ID = 'server_id'
|
||||
|
||||
class PlayDAO():
|
||||
db_string = None
|
||||
session = None
|
||||
Session = None
|
||||
engine = None
|
||||
_database_session = None
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
self._database_session = DatabaseSession()
|
||||
|
||||
def insert(self, play_info):
|
||||
session = DatabaseSession.session
|
||||
session = self._database_session.get_or_create_session()
|
||||
|
||||
play = Play(
|
||||
play_id = play_info[PLAY_ID],
|
||||
pitch_value = play_info[PITCH_VALUE] if PITCH_VALUE in play_info else None,
|
||||
creation_date = play_info[CREATION_DATE]
|
||||
creation_date = play_info[CREATION_DATE],
|
||||
server_id = play_info[SERVER_ID]
|
||||
)
|
||||
|
||||
session.add(play)
|
||||
session.commit()
|
||||
|
||||
def get_play_by_id(self, input_id):
|
||||
session = DatabaseSession.session
|
||||
session = self._database_session.get_or_create_session()
|
||||
return self.__convert_all__(session.query(Play).filter(Play.play_id == input_id))
|
||||
|
||||
def get_all_plays_after(self, timestamp, input_server_id):
|
||||
session = self._database_session.get_or_create_session()
|
||||
return self.__convert_all__(session.query(Play).filter(and_(Play.server_id == str(input_server_id), Play.creation_date > timestamp)))
|
||||
|
||||
def get_all_plays_on_server(self, input_server_id, earliest_timestamp):
|
||||
session = self._database_session.get_or_create_session()
|
||||
converted_datetime = datetime.datetime.fromtimestamp(earliest_timestamp / 1000.0)
|
||||
|
||||
return self.__convert_all__(session.query(Play).filter(and_(Play.server_id == str(input_server_id), Play.creation_date > converted_datetime)))
|
||||
|
||||
'''
|
||||
Checks to see if there is a play that is currently active or not
|
||||
'''
|
||||
def is_active_play(self):
|
||||
return self.get_active_play() != None
|
||||
def is_active_play(self, server_id):
|
||||
return self.get_active_play(server_id) != None
|
||||
|
||||
def get_active_play(self):
|
||||
session = DatabaseSession.session
|
||||
plays = self.__convert_all__(session.query(Play).filter(Play.pitch_value == None))
|
||||
def get_active_play(self, input_server_id):
|
||||
session = self._database_session.get_or_create_session()
|
||||
plays = self.__convert_all__(session.query(Play).filter(and_(Play.pitch_value == None, Play.server_id == str(input_server_id))))
|
||||
|
||||
if len(plays) > 1:
|
||||
raise AssertionError("More than one active play! Can't continue!")
|
||||
@ -49,18 +65,21 @@ class PlayDAO():
|
||||
else:
|
||||
return plays[0]
|
||||
|
||||
def resolve_play(self, input_pitch):
|
||||
session = DatabaseSession.session
|
||||
active_id = self.get_active_play()
|
||||
def resolve_play(self, input_pitch, input_server_id):
|
||||
session = self._database_session.get_or_create_session()
|
||||
active_id = self.get_active_play(input_server_id)
|
||||
|
||||
session\
|
||||
.query(Play)\
|
||||
.filter(Play.pitch_value == None)\
|
||||
.filter(and_(Play.pitch_value == None, Play.server_id == str(input_server_id)))\
|
||||
.update({Play.pitch_value: input_pitch})
|
||||
session.commit()
|
||||
|
||||
return active_id
|
||||
|
||||
def refresh(self):
|
||||
self._database_session.__create_new_session__() # I know, I know. It's fine.
|
||||
|
||||
'''
|
||||
Converts the database object into a Dictionary, so that the database object is not passed out of the
|
||||
datastore layer.
|
||||
|
@ -17,13 +17,28 @@ and then use it to handle the necessary CRUD operations.
|
||||
You should NOT instantiate this in any method except the main application runner
|
||||
'''
|
||||
class DatabaseSession():
|
||||
session = None
|
||||
_session = None
|
||||
|
||||
def __init__(self):
|
||||
self.__create_new_session__()
|
||||
|
||||
def __create_new_session__(self):
|
||||
if self._session is not None:
|
||||
self._session.close()
|
||||
|
||||
config_map = Configs.configs
|
||||
db_string = self._pgsql_conn_string_(config_map)
|
||||
Session = sessionmaker(create_engine(db_string))
|
||||
DatabaseSession.session = Session()
|
||||
self._session = Session()
|
||||
|
||||
return self._session
|
||||
|
||||
def get_or_create_session(self):
|
||||
try:
|
||||
self._session.connection()
|
||||
return self._session
|
||||
except: # The linter can scream all it wants, this makes sense. If it's this broke, we want a new one anyway.
|
||||
return self.__create_new_session__()
|
||||
|
||||
# Look, this kinda sucks. But it's for fun and friends and I'm doing it quick and dirty.
|
||||
def _pgsql_conn_string_(self, config_map):
|
||||
|
@ -1,17 +1,22 @@
|
||||
import sys
|
||||
|
||||
import discord
|
||||
from discord.utils import get
|
||||
|
||||
import uuid
|
||||
import datetime
|
||||
import dateparser
|
||||
|
||||
from src.main.configs import Configs
|
||||
from src.main.database_module.guess_dao import GuessDAO, GUESSED_NUMBER, MEMBER_ID, MEMBER_NAME, DIFFERENCE
|
||||
from src.main.database_module.play_dao import PlayDAO, PLAY_ID, CREATION_DATE
|
||||
from src.main.services.points_service import PointsService
|
||||
from src.main.database_module.play_dao import PlayDAO, PLAY_ID, CREATION_DATE, SERVER_ID
|
||||
from src.main.db_session import DatabaseSession
|
||||
from src.main.discord_module.leaderboard_config import LeaderboardConfig
|
||||
|
||||
play_dao = None
|
||||
guess_dao = None
|
||||
points_service = PointsService()
|
||||
bot = discord.Client()
|
||||
|
||||
|
||||
@ -29,26 +34,28 @@ async def on_message(message):
|
||||
return
|
||||
|
||||
content = message.content
|
||||
server_id = message.guild.id
|
||||
|
||||
'''
|
||||
Sets up the next set of guesses.
|
||||
'''
|
||||
if content.startswith('!ghostball'):
|
||||
if play_dao.is_active_play():
|
||||
if play_dao.is_active_play(server_id):
|
||||
await message.channel.send("There's already an active play. Could you close that one first, please?")
|
||||
else:
|
||||
play_object = {PLAY_ID: uuid.uuid4(), CREATION_DATE: datetime.datetime.now()}
|
||||
generated_play_id = uuid.uuid4()
|
||||
play_object = {PLAY_ID: generated_play_id, CREATION_DATE: datetime.datetime.now(), SERVER_ID: server_id}
|
||||
play_dao.insert(play_object)
|
||||
|
||||
await message.channel.send("@flappyball, pitch is in! Send me your guesses with a !guess command.")
|
||||
await message.channel.send("@flappy ball, pitch is in! Send me your guesses with a !guess command.")
|
||||
|
||||
if content.startswith("!guess"):
|
||||
guess_value = __parse_guess__(content)
|
||||
|
||||
if not play_dao.is_active_play():
|
||||
if not play_dao.is_active_play(server_id):
|
||||
await message.channel.send("Hey, there's no active play! Start one up first with !ghostball.")
|
||||
else:
|
||||
play = play_dao.get_active_play()
|
||||
play = play_dao.get_active_play(server_id)
|
||||
guess_object = {PLAY_ID: play['play_id'],
|
||||
MEMBER_ID: str(message.author.id),
|
||||
GUESSED_NUMBER: guess_value,
|
||||
@ -60,52 +67,62 @@ async def on_message(message):
|
||||
# Closes off the active play to be ready for the next set
|
||||
if content.startswith('!resolve'):
|
||||
# try:
|
||||
pitch_value = __parse_resolve_play__(content)
|
||||
if pitch_value is None:
|
||||
args, has_batter = __parse_resolve_play__(content)
|
||||
if args is None:
|
||||
await message.channel.send("Hey " + "<@" + str(message.author.id) + ">, I'm not sure what you meant. "
|
||||
"You need real, numeric, values for this command to work. "
|
||||
"Use !resolve <pitch number> and try again.")
|
||||
"Use !resolve <pitch number> <optional batter> <optional swing number>"
|
||||
" and try again.")
|
||||
|
||||
# Check if we have an active play
|
||||
if not play_dao.is_active_play():
|
||||
if not play_dao.is_active_play(server_id):
|
||||
await message.channel.send("You confused me. There's no active play so I have nothing to close!")
|
||||
else:
|
||||
play = play_dao.resolve_play(pitch_value)
|
||||
if has_batter:
|
||||
referenced_member_id = args[1][3:-1]
|
||||
play = play_dao.get_active_play(server_id)
|
||||
guess_object = {PLAY_ID: play['play_id'],
|
||||
MEMBER_ID: str(referenced_member_id),
|
||||
GUESSED_NUMBER: args[2],
|
||||
MEMBER_NAME: bot.get_user(int(referenced_member_id)).name}
|
||||
|
||||
guess_dao.insert(guess_object)
|
||||
|
||||
pitch_value = args[0]
|
||||
play = play_dao.resolve_play(pitch_value, server_id)
|
||||
guess_dao.set_differences(pitch_value, play['play_id'])
|
||||
closest_guess = guess_dao.get_closest_on_play(play['play_id'])
|
||||
guesses = points_service.fetch_sorted_guesses_by_play(guess_dao, play['play_id'])
|
||||
|
||||
await message.channel.send(
|
||||
"Closed this play! " + "<@" + str(closest_guess[MEMBER_ID]) +
|
||||
"> was the closest with a guess of " + closest_guess[GUESSED_NUMBER] +
|
||||
" resulting in a difference of " + closest_guess[DIFFERENCE] + ".")
|
||||
response_message = "Closed this play! Here are the results:\n"
|
||||
response_message += "PLAYER --- DIFFERENCE --- POINTS GAINED\n"
|
||||
for guess in guesses:
|
||||
response_message += guess[1] + " --- " + str(guess[2]) + " --- " + str(guess[3]) + "\n"
|
||||
|
||||
# Likely due to too few parameters but could be any number of things
|
||||
# except :
|
||||
# await message.channel.send( "Hey " + "<@" + str(message.author.id) + ">, you confused me with that message. "
|
||||
# "To close an active pitch, the proper command is !resolve <pitch number> <swing_number>. "
|
||||
# "Use that format and try again, ok?")
|
||||
response_message += "\nCongrats to <@" + str(guesses[0][0]) + "> for being the closest! \n"
|
||||
response_message += "And tell <@" + str(guesses[-1][0]) + "> they suck."
|
||||
|
||||
if content.startswith('!leaderboard'):
|
||||
leaderboard_config = __parse_leaderboard_message__(content)
|
||||
|
||||
if leaderboard_config.should_sort_by_pure_closest():
|
||||
values = guess_dao.fetch_closest(10)
|
||||
|
||||
string_to_send = ''
|
||||
for i, value in enumerate(values):
|
||||
string_to_send += str(i + 1) + ': ' + value['member_name'] + ', ' + value['difference'] + '\n'
|
||||
|
||||
await message.channel.send(string_to_send)
|
||||
|
||||
elif leaderboard_config.should_sort_by_best_average():
|
||||
pass
|
||||
else:
|
||||
await message.channel.send(
|
||||
"I don't understand that leaderboard command, sorry! I know it's a little confusing, so send me"
|
||||
" a !help message if you want the full rundown for how to make this work!")
|
||||
await message.channel.send(response_message)
|
||||
|
||||
if content.startswith("!points"):
|
||||
pass #TODO
|
||||
try:
|
||||
timestamp = __parse_points_message__(content)
|
||||
except:
|
||||
await message.channel.send("You gave me a timestamp that was so bad, the best date handling library in the"
|
||||
" world of software couldn't figure out what you meant. That's...impressive. Now"
|
||||
" fix your shit and try again.")
|
||||
return
|
||||
|
||||
points_by_user = points_service.fetch_points(timestamp, server_id, play_dao, guess_dao)
|
||||
response = "Here are the top guessers by points as per your request..."
|
||||
for user in points_by_user:
|
||||
response += "\n" + str(user[1]) + " : " + str(user[2])
|
||||
|
||||
await message.channel.send(response)
|
||||
|
||||
# Refresh Postgres connection
|
||||
if content.startswith('!restart'):
|
||||
play_dao.refresh()
|
||||
guess_dao.refresh()
|
||||
|
||||
if content.startswith('!help'):
|
||||
help_message = __get_help_message__()
|
||||
@ -120,9 +137,14 @@ def __get_help_message__():
|
||||
"I will give you a thumbs up if everything worked!\n" \
|
||||
"!ghostball --- Starts a new play. I'll let you know if this didn't work for some reason!\n" \
|
||||
"!help --- You just asked for this. If you ask for it again, I'll repeat myself.\n" \
|
||||
"!resolve <PITCH_NUMBER> --- Uses the pitch number and real swing number " \
|
||||
"to figure out who was closest and ends the active play.\n" \
|
||||
"<HELP MESSAGE NEEDS DOCUMENTATION FOR LEADERBOARD COMMAND! PING KALI IF YOU'RE ANGRY!>\n"
|
||||
"!resolve <PITCH_NUMBER> <OPTIONAL --- BATTER by @-mention> <OPTIONAL - ACTUAL SWING NUMBER> --- " \
|
||||
"Uses the pitch number and real swing number to figure out who was closest and ends the active play." \
|
||||
"If you include the batter and their swing number, they will get credit for how well they did!\n" \
|
||||
"!points <OPTIONAL --- TIMESTAMP> Fetches all plays since your requested time, or the beginning of the unvierse " \
|
||||
"if none given. Will currently always dump all players - top X coming soon...\n" \
|
||||
"!restart --- If the bot looks broken, this will take a shot at fixing it. It won't answer your commands " \
|
||||
"for about 3 seconds after you do this! BE CAREFUL! ONLY USE IN AN EMERGENCY!\n" \
|
||||
"<PING KALI IF YOU'RE CONFUSED, ANGRY, OR WANT TO GEEK OUT ABOUT BRAVELY DEFAULT!>\n"
|
||||
|
||||
return help_message
|
||||
|
||||
@ -131,6 +153,20 @@ def __parse_leaderboard_message__(message_content):
|
||||
return LeaderboardConfig(message_content)
|
||||
|
||||
|
||||
def __parse_points_message__(message_content):
|
||||
pieces = message_content.split(' ')
|
||||
|
||||
if len(pieces) > 1:
|
||||
try:
|
||||
timestamp = dateparser.parse(pieces[1])
|
||||
except:
|
||||
raise RuntimeError("Unable to parse timestamp!")
|
||||
else:
|
||||
timestamp = dateparser.parse("1970-01-01")
|
||||
|
||||
return timestamp
|
||||
|
||||
|
||||
def __parse_guess__(message_content):
|
||||
pieces = message_content.split(' ')
|
||||
try:
|
||||
@ -140,11 +176,17 @@ def __parse_guess__(message_content):
|
||||
|
||||
|
||||
def __parse_resolve_play__(message_content):
|
||||
pieces = message_content.split(' ')
|
||||
pieces = message_content.split()
|
||||
try:
|
||||
return pieces[1]
|
||||
if len(pieces) == 2:
|
||||
return [pieces[1]], False
|
||||
elif len(pieces) == 4:
|
||||
return [pieces[1], pieces[2], pieces[3]], True
|
||||
else:
|
||||
print("Illegal resolution command")
|
||||
return None, None
|
||||
except TypeError:
|
||||
return None
|
||||
return None, None
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
0
src/main/services/__init__.py
Normal file
0
src/main/services/__init__.py
Normal file
45
src/main/services/points_service.py
Normal file
45
src/main/services/points_service.py
Normal file
@ -0,0 +1,45 @@
|
||||
|
||||
|
||||
class PointsService():
|
||||
_point_table = [(5,25),(25, 75), (75, 50), (150, 25)]
|
||||
|
||||
def fetch_points(self, timestamp, server_id, play_dao, guess_dao):
|
||||
plays = play_dao.get_all_plays_after(timestamp, server_id)
|
||||
all_guesses = guess_dao.get_all_guesses_for_plays(x['play_id'] for x in plays)
|
||||
|
||||
# Build a dictionary of each member and their total points
|
||||
totals_by_player = {}
|
||||
for guess in all_guesses:
|
||||
if guess['member_id'] in totals_by_player:
|
||||
totals_by_player[guess['member_id']]['points'] += self.__get_points_for_diff__(guess['difference'])
|
||||
else:
|
||||
totals_by_player[guess['member_id']] = {}
|
||||
totals_by_player[guess['member_id']]['points'] = self.__get_points_for_diff__(guess['difference'])
|
||||
totals_by_player[guess['member_id']]['member_name'] = guess['member_name']
|
||||
|
||||
# And now pull those numbers out into a list and sort them
|
||||
sorted_players = []
|
||||
for player in totals_by_player:
|
||||
sorted_players.append([player,
|
||||
totals_by_player[player]['member_name'],
|
||||
totals_by_player[player]['points']])
|
||||
|
||||
sorted_players.sort(key=lambda x: x[2], reverse=True)
|
||||
return sorted_players
|
||||
|
||||
def fetch_sorted_guesses_by_play(self, guess_dao, play_id):
|
||||
all_guesses = guess_dao.get_all_guesses_for_plays([play_id])
|
||||
player_list = []
|
||||
for guess in all_guesses:
|
||||
player_list.append([guess['member_id'], guess['member_name'], int(guess['difference']), self.__get_points_for_diff__(guess['difference'])])
|
||||
|
||||
player_list.sort(key=lambda x: x[2])
|
||||
return player_list
|
||||
|
||||
# Iterates through the point table, which we assume is sorted, and gets the points
|
||||
def __get_points_for_diff__(self, diff):
|
||||
for i in range(0, len(self._point_table)):
|
||||
if int(diff) < self._point_table[i][0]:
|
||||
return self._point_table[i][1]
|
||||
|
||||
return 0
|
Loading…
Reference in New Issue
Block a user