import math as maths import logging import random import time import gameUtils import NoPELib import numpy as np import pygame # TODO: Prevent another animation from starting when the wheel's spinning # TODO: Convert fade in / out time to be random # TODO: Make random finer (to prevent a range of 4-5 only having two possible speeds) def translate(value, aMin, aMax, bMin, bMax): """ Translates `value` to go from `aMin`-`aMax` to `bMin`-`bMax` Stolen from StackOverflow because we don't have time to do shit properly here. """ aRange = aMax - aMin bRange = bMax - bMin return (((value - aMin) * bRange) / aRange) + bMin def rotateRad(surface: pygame.Surface, radians: float) -> pygame.Surface: """ Rotates a surface clockwise in radians and re-centres it. """ # Calculate the degrees deg = radians*(180/maths.pi) # Rotate the surface re-centre return rotateDeg(surface, deg) def rotateDeg(surface: pygame.Surface, degrees: float) -> pygame.Surface: """ Rotates a surface clockwise in degrees and re-centres it. """ # Convert to CCW becaus for some reason that's what Pygame uses? degrees *= -1 # Rotate surface which will expand it rotSurface = pygame.transform.rotate(surface, degrees) # re-centre it to the orinal surface bounding box rotSurface = gameUtils.centre(rotSurface, surface.size) return rotSurface class WCfg: """ Holds all the Wheel Configurations. """ # The background colour of the wheel bgColour = (25, 25, 25) # The colour of the dividing lines divColour = (75, 75, 75) # The colour of the text txtColour = (255, 255, 255) # How much space is between the wheel and the screen margin = 50 # The minimum and maximum time it takes (in seconds) # for the wheel to get to full speed. spinFadeInTimes = [0.25, 0.25] # The minimum and maximum time it takes (in seconds) # for the wheel to get to go back to being stationary. spinFadeOutTimes = [3, 4] # The minimum and maximum speeds of the wheel in deg/s spinSpeedRange = [360, 1000] # How many pixels away from the outer side of the # circle the text should be textMargin = 10 # The font to use for drawing the item text font = pygame.font.SysFont('', 32) class WheelItem: def __init__(self, text): self.text = text class Game(gameUtils.Game): def __init__(self, *args, **kwargs): """ Ran when the game starts. You have access to the following variables: self.surf (pygame.Surface): This is the surface you draw onto. self.pm (NoPELib.PlayerManager): This is where your players are stored. self.cfg (dict): Everything from the `game.toml` file. You can access it like this: self.details['title'] """ # Don't remove this. It does important things. :3 super().__init__(*args, **kwargs) self.wheelSurf = pygame.Surface([min(self.size)-WCfg.margin*2]*2) # Don't ask. self.wheelRotation = 0 # Holds the decided speed of the wheel self.wheelSpinSpeed = 0 # Holds the decided fade in time self.animFadeIn = 0 # Holds the decided fade out time self.animFadeOut = 0 # When the wheel started its spin animation self.wheelAnimStart = 0 # The ticker sound effect self.tickSfx = pygame.mixer.Sound("./assets/spinner-tick.mp3") # The last time (in time.perf_counter()) the ticker was played self.lastTickTime = 0 # The last index the ticker ticked on self.lastTickItem = -1 self.items = [] self.items.append(WheelItem('Drink 1 shot')) self.items.append(WheelItem('Drink 2 shots')) self.items.append(WheelItem('Get shocked at 50%')) self.items.append(WheelItem('Get shocked at 100%')) self.items.append(WheelItem('Get slapped by\neveryone in the room')) self.items.append(WheelItem('Respin, wheel hidden')) random.shuffle(self.items) self.font = pygame.font.SysFont('', 50) self.drawWheel() def drawWheel(self): # Calculate circle radius circleR = self.wheelSurf.size[0] / 2 # Draw the base circle pygame.draw.circle(self.wheelSurf, WCfg.bgColour, (circleR, circleR), circleR) # Calculate the gap, in radians, between each item gap = maths.pi * 2 / len(self.items) for idx, item in enumerate(self.items): # Calculate the radians of this item rad = (idx / len(self.items)) * maths.pi * 2 # Calculate the vector offset by half the item gap # This is because the text should be in the middle, # and the lines (which is what this is used by) on the edges vector = maths.cos(rad+gap/2), maths.sin(rad+gap/2) # Calculate the end position of a line based on the vector endPos = np.add(np.multiply(vector, circleR), circleR) # Draw the dividing line pygame.draw.line(self.wheelSurf, WCfg.divColour, (circleR, circleR), endPos) # Create a surface that our text will be rendered onto textSurf = pygame.Surface(self.wheelSurf.size, pygame.SRCALPHA) # Render the item text text = WCfg.font.render(item.text, True, WCfg.txtColour) # Draw the item text onto the text surface, centred to the right textSurf.blit(text, (textSurf.size[0]-text.get_width()-WCfg.textMargin, textSurf.size[1]//2 - text.get_height()//2)) # Draw debug lines #if idx < 1: # pygame.draw.rect(textSurf, (0, 128, 255), (0, 0, textSurf.size[0], textSurf.size[1]), 1) # pygame.draw.line(textSurf, (255, 0, 0), (circleR, circleR), (textSurf.size[0], circleR)) # pygame.draw.line(textSurf, (0, 255, 0), (circleR, circleR), (circleR, textSurf.size[1])) # Rotate the text surface textSurf = rotateRad(textSurf, -rad) # Blit the text surface to the wheel surface self.wheelSurf.blit(textSurf, (0, 0)) def spinnerTick(self): # If there's a tick already playing if time.perf_counter() - self.lastTickTime < self.tickSfx.get_length(): return self.tickSfx.play() self.lastTickTime = time.perf_counter() def onEvent(self, event): """ Ran when an event is fired. Args: event (pygame.Event): The event that was fired. """ if event.type == pygame.KEYDOWN: if event.key == pygame.K_SPACE: self.wheelSpinSpeed = random.randint(*WCfg.spinSpeedRange) self.wheelAnimStart = time.perf_counter() def update(self): """ Ran once per frame, put your drawing code and any game logic that should be ran once per frame in here. """ self.surf.fill((0, 0, 0)) self.surf.blit(rotateDeg(gameUtils.centre(self.wheelSurf, self.size), self.wheelRotation), (0, 0)) arrowX = (self.size[0] - self.wheelSurf.size[0]) // 2 + self.wheelSurf.size[0] arrowY = self.size[1] // 2 arrowSize = 20 pygame.draw.polygon(self.surf, (255, 255, 255), ((arrowX, arrowY), (arrowX+arrowSize, arrowY-arrowSize//2), (arrowX+arrowSize, arrowY+arrowSize//2))) animTime = time.perf_counter() - self.wheelAnimStart if animTime < WCfg.spinFadeInTimes[0] + WCfg.spinFadeOutTimes[0]: if animTime < WCfg.spinFadeInTimes[0]: speed = translate(animTime, 0, WCfg.spinFadeInTimes[0], 0, self.wheelSpinSpeed) else: speed = translate(animTime, WCfg.spinFadeInTimes[0], WCfg.spinFadeInTimes[0]+WCfg.spinFadeOutTimes[0], self.wheelSpinSpeed, 0) self.wheelRotation += speed * self.timeDelta self.wheelRotation %= 360 # Calculates the current item index with... I don't know, magic? currentItemIdx = round((self.wheelRotation / 360) * len(self.items)) % len(self.items) # If the last tick was on a different index, if currentItemIdx != self.lastTickItem: # Click again self.spinnerTick() # And set the last tick index to the current one self.lastTickItem = currentItemIdx self.surf.blit(self.font.render(self.items[currentItemIdx].text, True, (255, 255, 255)), (0, 0)) def close(self): """ Ran when the game closes. """ pass