-
-
Notifications
You must be signed in to change notification settings - Fork 16
Team 4 #5
base: master
Are you sure you want to change the base?
Team 4 #5
Changes from all commits
bc07777
5b36849
dcaebf4
eb4e486
aa40d32
1f221ea
fd7a468
6191f2f
922e65a
a9b8d9f
62f3494
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -102,3 +102,4 @@ ENV/ | |
|
||
# PyCharm | ||
.idea/ | ||
.vscode/settings.json |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,16 @@ | ||
# coding=utf-8 | ||
import logging | ||
import json | ||
from aiohttp import ClientSession | ||
from random import choice | ||
from time import time | ||
from typing import Any, Dict | ||
|
||
from discord import Embed, Color | ||
from discord.ext.commands import AutoShardedBot, Context, command | ||
|
||
from bot.snakegame import SnakeGame | ||
|
||
log = logging.getLogger(__name__) | ||
|
||
|
||
|
@@ -14,7 +21,15 @@ class Snakes: | |
|
||
def __init__(self, bot: AutoShardedBot): | ||
self.bot = bot | ||
self.game = SnakeGame((5, 5)) | ||
self.debug = True | ||
# changed this to (User.id: int) in order to make it easier down the line to call. >(PC) | ||
self.mods = [255254195505070081, 98694745760481280] | ||
self.last_movement = time() | ||
self.movement_command = {"left": 0, "right": 0, "up": 0, "down": 0} | ||
self.wait_time = 2 | ||
|
||
# docstring for get_snek needs to be cleaned up >(PC) | ||
async def get_snek(self, name: str = None) -> Dict[str, Any]: | ||
""" | ||
Go online and fetch information about a snake | ||
|
@@ -29,6 +44,16 @@ async def get_snek(self, name: str = None) -> Dict[str, Any]: | |
:return: A dict containing information on a snake | ||
""" | ||
|
||
url = f'https://en.wikipedia.org/w/api.php?action=query&titles={name}' \ | ||
f'&prop=extracts&exlimit=1&explaintext&format=json&formatversion=2' | ||
|
||
# account for snakes without a page somewhere. >(PC) | ||
async with ClientSession() as session: | ||
async with session.get(url) as response: | ||
resp = json.loads(str(await response.read(), 'utf-8')) | ||
return resp | ||
|
||
# docstring for get needs to be cleaned up. >(PC) | ||
@command() | ||
async def get(self, ctx: Context, name: str = None): | ||
""" | ||
|
@@ -40,8 +65,84 @@ async def get(self, ctx: Context, name: str = None): | |
:param ctx: Context object passed from discord.py | ||
:param name: Optional, the name of the snake to get information for - omit for a random snake | ||
""" | ||
# Everything with snek_list should be cached >(PC) | ||
# SELF.BOT.SNEK_LIST OMG OMG OMG >(PC) | ||
# Since, on restart, the bot will forget this, it will be re-cached every time. Problem Solved in theory. >(PC) | ||
possible_names = 'https://en.wikipedia.org/w/api.php?action=query&titles=List_of_snakes_by_common_name' \ | ||
'&prop=extracts&exlimit=1&explaintext&format=json&formatversion=2' | ||
|
||
async with ClientSession() as session: | ||
async with session.get(possible_names) as all_sneks: | ||
resp = str(await all_sneks.read(), 'utf-8') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What's the reason for specifying the encoding? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem with encoding when trying to change the read response to a string. Specification solved that. |
||
|
||
# can we find a better way to do this? Doesn't seem too reliable, even though MW won't change their api. >(PC) | ||
snek_list = resp[409:].lower().split('\\n') | ||
|
||
# if name is None, choose a random snake. Need to clean up snek_list. >(PC) | ||
if name is None: | ||
name = choice(snek_list) | ||
|
||
# stops the command if the snek is not on the list >(PC) | ||
elif name.lower() not in snek_list: | ||
await ctx.send('This is not a valid snake. Please request one that exists.\n' | ||
'You can find a list of existing snakes here: ') | ||
return | ||
|
||
# accounting for the spaces in the names of some snakes. Allows for parsing of spaced names. >(PC) | ||
if name.split(' '): | ||
name = '%20'.join(name.split(' ')) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please use the urllib module for this operation There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you specify which function of URLLib I should be using for this? |
||
|
||
# leaving off here for the evening. Building the embed is easy. Pulling the information is hard. /s >(PC) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So, build the embed. :P There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
# snek_dict = await self.get_snek(name) | ||
|
||
# Any additional commands can be placed here. Be creative, but keep it to a reasonable amount! | ||
@command() | ||
async def play(self, ctx: Context, order): | ||
""" | ||
DiscordPlaysSnek | ||
|
||
Move the snek around the field and collect food. | ||
|
||
Valid use: `bot.play {direction}` | ||
|
||
With 'left', 'right', 'up' and 'down' as valid directions. | ||
""" | ||
|
||
# Maybe one game at a given time, and maybe editing the original message instead of constantly posting | ||
# new ones? Maybe we could also ask nicely for DMs to be allowed for this if they aren't. >(PC) | ||
if order in self.movement_command.keys(): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I may be wrong, but it would appear that only 1 person can play at once, you should save the message author ID to a dict with their last time? I havn't played this game, so I may be misinterpreting it. |
||
self.movement_command[order] += 1 | ||
|
||
# check if it's time to move the snek | ||
if time() - self.last_movement > self.wait_time: | ||
direction = max(self.movement_command, | ||
key=self.movement_command.get) | ||
percentage = 100*self.movement_command[direction]/sum(self.movement_command.values()) | ||
move_status = self.game.move(direction) | ||
|
||
# end game | ||
if move_status == "lost": | ||
await ctx.send("We made the snek cry! :snake: :cry:") | ||
|
||
# prepare snek message | ||
snekembed = Embed(color=Color.red()) | ||
snekembed.add_field(name="Score", value=self.game.score) | ||
snekembed.add_field(name="Winner movement", | ||
value="{dir}: {per:.2f}%".format(dir=direction, per=percentage)) | ||
|
||
snek_head = next(emoji for emoji in ctx.guild.emojis if emoji.name == 'python') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Very clever usage of next, I like that. However this creates issues if the name is changed. Might want to make this optional. |
||
|
||
game_string = str(self.game).replace(":python:", str(snek_head)) | ||
snekembed.add_field(name="Board", value=game_string, inline=False) | ||
if self.debug: | ||
snekembed.add_field( | ||
name="Debug", value="Debug - move_status: " + move_status, inline=False) | ||
|
||
# prepare next movement | ||
self.last_movement = time() | ||
self.movement_command = {"left": 0, | ||
"right": 0, "up": 0, "down": 0} | ||
await ctx.send(embed=snekembed) | ||
|
||
|
||
def setup(bot): | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
from random import randint | ||
|
||
|
||
class SnakeGame: | ||
""" | ||
Simple Snake game | ||
""" | ||
|
||
def __init__(self, board_dimensions): | ||
self.board_dimensions = board_dimensions | ||
self.restart() | ||
|
||
def restart(self): | ||
""" | ||
Restores game to default state | ||
""" | ||
|
||
self.snake = Snake(positions=[[2, 2], [2, 1]]) | ||
self.putFood() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This applies to all functions and variables, use self.put_food() Instead! |
||
self.score = 0 | ||
|
||
def __str__(self): | ||
""" | ||
Draw the board | ||
""" | ||
# create empty board | ||
# board_string = "+" + "-" * (self.board_dimensions[1]) + "+" + "\n" | ||
board_string = "" | ||
for i in range(self.board_dimensions[0]): | ||
# row_string = "|" | ||
row_string = "" | ||
# draw snake | ||
for j in range(self.board_dimensions[1]): | ||
if [i, j] == self.snake.positions[0]: | ||
row_string += ":python:" # head | ||
elif [i, j] in self.snake.positions: | ||
row_string += "🐍" | ||
elif [i, j] == self.food: | ||
row_string += "🍕" | ||
else: | ||
row_string += "◻️" | ||
# row_string += "|\n" | ||
board_string += row_string + "\n" | ||
# board_string += "+" + "-" * (self.board_dimensions[1]) + "+\n" | ||
|
||
return board_string | ||
|
||
def move(self, direction): | ||
""" | ||
Executes one movement. | ||
Returns information about the movement: | ||
"ok", "forbidden", "lost", "food". | ||
""" | ||
|
||
direction_dict = { | ||
"right": (0, 1), | ||
"left": (0, -1), | ||
"up": (-1, 0), | ||
"down": (1, 0) | ||
} | ||
move_status = self.snake.move(direction_dict[direction]) | ||
|
||
if self.isLost(): | ||
move_status = "lost" | ||
self.restart() | ||
|
||
if self.isEating(): | ||
self.snake.grow() | ||
move_status = "grow" | ||
self.putFood() | ||
self.score += 1 | ||
|
||
return move_status | ||
|
||
def isLost(self): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should use python property decorator! So it would look like @property
def lost(self): Applies to other |
||
head = self.snake.head | ||
|
||
if (head[0] == -1 or | ||
head[1] == -1 or | ||
head[0] == self.board_dimensions[0] or | ||
head[1] == self.board_dimensions[1] or | ||
head in self.snake.positions[1:]): | ||
return True | ||
|
||
return False | ||
|
||
def putFood(self): | ||
valid = False | ||
while not valid: | ||
i = randint(0, self.board_dimensions[0] - 1) | ||
j = randint(0, self.board_dimensions[1] - 1) | ||
|
||
if [i, j] not in self.snake.positions: | ||
valid = True | ||
|
||
self.food = [i, j] | ||
|
||
def isEating(self): | ||
if self.snake.head == self.food: | ||
return True | ||
return False | ||
|
||
|
||
class Snake: | ||
""" | ||
Actual snake in the game. | ||
""" | ||
|
||
def __init__(self, positions): | ||
self.positions = positions | ||
self.head = positions[0] | ||
|
||
def move(self, velocity): | ||
""" | ||
Executes one movement. | ||
|
||
Returns information about the movement: | ||
"ok", "forbidden" | ||
""" | ||
if not self.isPossible(velocity): | ||
print("Movement not allowed") | ||
return "forbidden" | ||
|
||
# delete tail but store it, as we might want the snake to grow | ||
self.deletedTail = self.positions[-1] | ||
self.positions = self.positions[:-1] | ||
|
||
# move head | ||
self.head = [self.head[0] + velocity[0], | ||
self.head[1] + velocity[1]] | ||
self.positions.insert(0, self.head) | ||
|
||
return "ok" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would be much nicer, if it returned a bool, or enum instead, this leads to less mistakes when developing. |
||
|
||
def isPossible(self, velocity): | ||
""" | ||
Check Snake is trying to do an 180º turn. | ||
""" | ||
newHead = [self.head[0] + velocity[0], | ||
self.head[1] + velocity[1]] | ||
if newHead == self.positions[1]: | ||
return False | ||
return True | ||
|
||
def grow(self): | ||
""" | ||
Makes the snake grow one square. | ||
""" | ||
self.positions.append(self.deletedTail) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can't you just
return await response.json()
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I keep getting a TypeError when trying that, so I decided to build it using "json.load()". I still works exactly as I would like it to, without all the TypeError stuff.
"usable_data = web_data['query']['pages'][0]['extract']
TypeError: 'generator' object is not subscriptable"
I also completely rewrote it, so while this is still there, it's not returning anything until it's actually done doing what it's supposed to do. You'll see it in the next commit in a little while, or below if you're invested enough.