Compare commits
24 Commits
97dbe0ed3b
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
3046af13e5
|
|||
| ae8b0c77a5 | |||
| afd1fcd6be | |||
| 3cd2ddbd46 | |||
| f36bce66e2 | |||
| f625c80fd3 | |||
| db7faca3d9 | |||
| 11b1dad78b | |||
| 3471e41272 | |||
| ae6c23b6b7 | |||
| 6e70f74874 | |||
| 172ffc2abf | |||
| 31b863b49b | |||
| e549107fe1 | |||
| 220f5759e9 | |||
| bba51ebc8c | |||
| 904601c697 | |||
| ce771d26a3 | |||
| b61a7114ba | |||
| 1a34d7ca4a | |||
| 848ad2f8ff | |||
| 5101ec3eef | |||
| f5314d86e3 | |||
| 7fb52bce01 |
42
__init__.py
42
__init__.py
@ -1,42 +0,0 @@
|
|||||||
from PIL import Imgae
|
|
||||||
import logging
|
|
||||||
import pygame
|
|
||||||
import gameUtils
|
|
||||||
import NoPELib
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
def onEvent(self, event):
|
|
||||||
"""
|
|
||||||
Ran when an event is fired.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
event (pygame.Event): The event that was fired.
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def update(self):
|
|
||||||
"""
|
|
||||||
Ran once per frame, put your drawing code and any
|
|
||||||
game logic that should be ran once per frame in here.
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
"""
|
|
||||||
Ran when the game closes.
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
BIN
fonts/Chernobyl.otf
Normal file
BIN
fonts/Chernobyl.otf
Normal file
Binary file not shown.
BIN
fonts/CloisterBlack.ttf
Normal file
BIN
fonts/CloisterBlack.ttf
Normal file
Binary file not shown.
BIN
fonts/DeathMohawk_PERSONAL_USE_ONLY.otf
Normal file
BIN
fonts/DeathMohawk_PERSONAL_USE_ONLY.otf
Normal file
Binary file not shown.
BIN
fonts/Flame on Black.ttf
Normal file
BIN
fonts/Flame on Black.ttf
Normal file
Binary file not shown.
BIN
fonts/LEDLIGHT.otf
Normal file
BIN
fonts/LEDLIGHT.otf
Normal file
Binary file not shown.
BIN
fonts/Noxlock-Free.otf
Normal file
BIN
fonts/Noxlock-Free.otf
Normal file
Binary file not shown.
BIN
fonts/RoyalInitialen.ttf
Normal file
BIN
fonts/RoyalInitialen.ttf
Normal file
Binary file not shown.
BIN
fonts/VIDEO PIRATE.ttf
Normal file
BIN
fonts/VIDEO PIRATE.ttf
Normal file
Binary file not shown.
BIN
fonts/Vampire Wars.ttf
Normal file
BIN
fonts/Vampire Wars.ttf
Normal file
Binary file not shown.
15
fonts/fonts.json
Normal file
15
fonts/fonts.json
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
[
|
||||||
|
{"file": "Noxlock-Free.otf", "alphabet": "abcdefghijklmnopqrstuvwxyz", "difficulty": 0.0},
|
||||||
|
{"file": "Chernobyl.otf", "alphabet": "abcdefghijklmnopqrstuvwxyz0123456789", "difficulty": 0.1},
|
||||||
|
{"file": "VIDEO PIRATE.ttf", "alphabet": "abcdefghijklmnopqrstuvwxyz", "difficulty": 0.1},
|
||||||
|
{"file": "Vampire Wars.ttf", "alphabet": "abcdefghijklmnopqrstuvwxyz0123456789", "difficulty": 0.1},
|
||||||
|
{"file": "CloisterBlack.ttf", "alphabet": "0123456789", "difficulty": 0.1},
|
||||||
|
{"file": "Noxlock-Free.otf", "alphabet": "ABCDEFGHIJKLMNOPQRSTUVWXYZ", "difficulty": 0.2},
|
||||||
|
{"file": "CloisterBlack.ttf", "alphabet": "abcdefghijklmnopqrstuvwxyz", "difficulty": 0.5},
|
||||||
|
{"file": "LEDLIGHT.otf", "alphabet": "abcdefghijklmnopqrstuvwxyz", "difficulty": 0.4},
|
||||||
|
{"file": "Flame on Black.ttf", "alphabet": "0123456789!?/#<>", "difficulty": 0.5},
|
||||||
|
{"file": "CloisterBlack.ttf", "alphabet": "ABCDEFGHIJKLMNOPQRSTUVWXYZ", "difficulty": 0.6},
|
||||||
|
{"file": "CloisterBlack.ttf", "alphabet": "ABCDEFGHIJKLMNOPQRSTUVWXYZ", "difficulty": 0.8},
|
||||||
|
{"file": "RoyalInitialen.ttf", "alphabet": "abcdefghijklmnopqrstuvwxyz", "difficulty": 0.8},
|
||||||
|
{"file": "DeathMohawk_PERSONAL_USE_ONLY.otf", "alphabet": "ABCDEFGHIJKLMNOPQRSTUVWXYZ", "difficulty": 1.0}
|
||||||
|
]
|
||||||
230
game.py
Normal file
230
game.py
Normal file
@ -0,0 +1,230 @@
|
|||||||
|
from PIL import Image, ImageDraw, ImageFont
|
||||||
|
import math as maths
|
||||||
|
import numpy as np
|
||||||
|
import logging
|
||||||
|
import pygame
|
||||||
|
import gameUtils
|
||||||
|
import NoPELib
|
||||||
|
import random
|
||||||
|
import string
|
||||||
|
import json
|
||||||
|
import time
|
||||||
|
import os
|
||||||
|
|
||||||
|
# TODO: The current alphabets in the fonts.json file is kinda bullshit
|
||||||
|
# someone should probably play some test rounds and fix the diffuculty issue.
|
||||||
|
# TODO: Re-draw the captcha when a character is complete.
|
||||||
|
|
||||||
|
alphabet = string.ascii_letters + string.digits + string.punctuation
|
||||||
|
|
||||||
|
def normalDist(difficulty):
|
||||||
|
"""
|
||||||
|
This function generates a random number between 0-1
|
||||||
|
that should be used to get a font with a difficulty
|
||||||
|
equal to or less than the number returned.
|
||||||
|
"""
|
||||||
|
# Create a random number between 0-1
|
||||||
|
v = random.randint(-1000, 1000) / 1000
|
||||||
|
# Redistribute the number
|
||||||
|
v = v**3
|
||||||
|
# Shift the number based on game difficulty
|
||||||
|
difficulty = difficulty * 1.5 - 0.25
|
||||||
|
v += difficulty
|
||||||
|
# Cap it to be 0-1 again and return
|
||||||
|
return min(max(v, 0), 1)
|
||||||
|
|
||||||
|
def PIL2PG(pilImage: Image) -> pygame.Surface:
|
||||||
|
"""
|
||||||
|
Converts a PIL image to a Pygame surface.
|
||||||
|
"""
|
||||||
|
return pygame.image.fromstring(pilImage.tobytes(), pilImage.size, pilImage.mode)
|
||||||
|
|
||||||
|
class Font:
|
||||||
|
def __init__(self, data, fontSize):
|
||||||
|
self.path = data['file']
|
||||||
|
self.font = ImageFont.truetype(os.path.join('./fonts/', data['file']), fontSize)
|
||||||
|
self.alphabet = data['alphabet']
|
||||||
|
self.difficulty = data['difficulty']
|
||||||
|
|
||||||
|
class Captcha:
|
||||||
|
"""
|
||||||
|
Holds and manages the current captcha.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.chars = []
|
||||||
|
self.fonts = []
|
||||||
|
self.charIdx = 0 # Holds which character the player is on,
|
||||||
|
# anthing below this index should appear green.
|
||||||
|
|
||||||
|
def addChar(self, char, font):
|
||||||
|
self.chars.append(char)
|
||||||
|
self.fonts.append(font)
|
||||||
|
|
||||||
|
def match(self, char):
|
||||||
|
if self.charIdx == len(self.chars):
|
||||||
|
return True, True
|
||||||
|
|
||||||
|
matches = char.upper() == self.chars[self.charIdx].upper()
|
||||||
|
print(f'{char.upper()} == {self.chars[self.charIdx]}')
|
||||||
|
if matches:
|
||||||
|
self.charIdx += 1
|
||||||
|
return True, self.charIdx == len(self.chars)
|
||||||
|
else:
|
||||||
|
return False, self.charIdx == len(self.chars)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
# A value between 0-1 that defines which fonts the game will use
|
||||||
|
self.gameDifficulty = 0
|
||||||
|
# The actual text in the captcha, used to check what the user typed
|
||||||
|
self.currentCaptcha = None
|
||||||
|
# The image of the captcha
|
||||||
|
self.currentCaptchaImg = pygame.Surface((0, 0))
|
||||||
|
# Holds when the timer started
|
||||||
|
self.timerStart = 0
|
||||||
|
# Holds how much time the user has in total (not updated)
|
||||||
|
self.timerLength = 0
|
||||||
|
# The font used for the timer
|
||||||
|
self.timerFont = pygame.font.SysFont('', 100)
|
||||||
|
# Used for animating the red flash on the current character if the users enters it wrong.
|
||||||
|
self.lastWrong = 0
|
||||||
|
|
||||||
|
self.fonts = []
|
||||||
|
self.Drawsurface = Image.new("RGB",(800,220),(255,255,255))
|
||||||
|
|
||||||
|
# I haven't finished this yet, you might want to create
|
||||||
|
# an object or something, I don't know. Keep in mind you
|
||||||
|
# have to load all of the font files for use later.
|
||||||
|
with open(f'./fonts/fonts.json', 'r') as f:
|
||||||
|
for fontData in json.loads(f.read()):
|
||||||
|
self.fonts.append(Font(fontData, 100))
|
||||||
|
#self.fontList = json.loads(f.read())#loads the fonts from the json (Why the fuck was the variable called 'j' before????)
|
||||||
|
|
||||||
|
#for fontData in j:
|
||||||
|
# self.fonts.update()
|
||||||
|
|
||||||
|
def fontsByDifficulty(self, difficulty, width=0.3):
|
||||||
|
"""
|
||||||
|
Gets a list of fonts that fall within the specified difficulty.
|
||||||
|
"""
|
||||||
|
return [
|
||||||
|
font for font in self.fonts
|
||||||
|
if font.difficulty >= difficulty-width and font.difficulty <= difficulty+width
|
||||||
|
]
|
||||||
|
|
||||||
|
def createCaptcha(self):
|
||||||
|
"""
|
||||||
|
Use PIL (https://pillow.readthedocs.io/en/stable/handbook/tutorial.html)
|
||||||
|
to draw the fonts, add noise, random shapes, transforms etc. based on
|
||||||
|
self.gameDifficulty
|
||||||
|
"""
|
||||||
|
# Create a surface for that captcha
|
||||||
|
drawSurface = Image.new("RGB", (800,220), (255,255,255))
|
||||||
|
# Create a drawing object
|
||||||
|
draw = ImageDraw.Draw(drawSurface)
|
||||||
|
|
||||||
|
# Temporarily stores the current captcha text
|
||||||
|
captcha = Captcha()
|
||||||
|
# Get a random cpatcha length
|
||||||
|
capLength = random.randint(3, 6)
|
||||||
|
for i in range(capLength):
|
||||||
|
# Calculate the base X position of this character
|
||||||
|
# TODO: Make this better, I'm not too happy with my implementation here.
|
||||||
|
x = drawSurface.size[0] / capLength
|
||||||
|
x *= i
|
||||||
|
# Get the selected difficulty for this character
|
||||||
|
charDiff = normalDist(self.gameDifficulty)
|
||||||
|
# Pick a random font based on that difficulty
|
||||||
|
font = random.choice(self.fontsByDifficulty(charDiff))
|
||||||
|
# Pick a random character
|
||||||
|
char = random.choice(font.alphabet)
|
||||||
|
# Draw the selected character, with the selected font
|
||||||
|
draw.text((x, 10), char, font=font.font, fill=(0,0,0))
|
||||||
|
# Append the character to the captcha
|
||||||
|
captcha.addChar(char, font)
|
||||||
|
|
||||||
|
# Store the correct captcha text
|
||||||
|
self.currentCaptcha = captcha
|
||||||
|
|
||||||
|
# Store the created image
|
||||||
|
self.currentCaptchaImg = PIL2PG(drawSurface)
|
||||||
|
|
||||||
|
for char, font in zip(captcha.chars, captcha.fonts):
|
||||||
|
print(f'{char}: {font.path}')
|
||||||
|
|
||||||
|
# Start the timer
|
||||||
|
self.timerStart = time.perf_counter()
|
||||||
|
self.timerLength = random.randint(7, 15)
|
||||||
|
|
||||||
|
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_F1:
|
||||||
|
self.gameDifficulty -= 0.1
|
||||||
|
print(self.gameDifficulty)
|
||||||
|
elif event.key == pygame.K_F2:
|
||||||
|
self.gameDifficulty += 0.1
|
||||||
|
print(self.gameDifficulty)
|
||||||
|
elif event.key == pygame.K_F3:
|
||||||
|
self.createCaptcha()
|
||||||
|
|
||||||
|
elif event.unicode != '' and event.unicode in alphabet:
|
||||||
|
correct, finished = self.currentCaptcha.match(event.unicode)
|
||||||
|
if not correct:
|
||||||
|
self.lastWrong = time.perf_counter()
|
||||||
|
print(correct)
|
||||||
|
if finished:
|
||||||
|
self.createCaptcha()
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
"""
|
||||||
|
Ran once per frame, put your drawing code and any
|
||||||
|
game logic that should be ran once per frame in here.
|
||||||
|
"""
|
||||||
|
|
||||||
|
r = 1 - min(time.perf_counter() - self.lastWrong, 1)
|
||||||
|
|
||||||
|
self.surf.fill((r * 255, 0, 0)) #sets the background colour
|
||||||
|
self.surf.blit(gameUtils.centre(self.currentCaptchaImg, self.size)) #draws the text to center of the screen
|
||||||
|
#TODO: actually scale the text, however, more work needs to be done on the actual function of the game first
|
||||||
|
|
||||||
|
# Draw the captcha progerss if there is a captcha
|
||||||
|
if self.currentCaptcha is not None:
|
||||||
|
x1 = self.size[0] / 2 - self.currentCaptchaImg.size[0] / 2
|
||||||
|
y = self.size[1] / 2 + self.currentCaptchaImg.size[1] / 2
|
||||||
|
x2 = x1 + (self.currentCaptcha.charIdx / len(self.currentCaptcha.chars)) * self.currentCaptchaImg.size[0]
|
||||||
|
pygame.draw.line(self.surf, (0, 255, 0), (x1, y), (x2, y), 5)
|
||||||
|
|
||||||
|
# Draw the timer if there's any time left
|
||||||
|
timeLeft = self.timerLength - (time.perf_counter() - self.timerStart)
|
||||||
|
if timeLeft > 0:
|
||||||
|
normalisedTimeLeft = max(timeLeft / self.timerLength, 0)
|
||||||
|
x = round((1-normalisedTimeLeft) * (self.size[0] // 2))
|
||||||
|
w = normalisedTimeLeft * self.size[0]
|
||||||
|
c = np.round((255, 255*normalisedTimeLeft, 255*normalisedTimeLeft))
|
||||||
|
pygame.draw.rect(self.surf, c, (x, 0, w, 10))
|
||||||
|
self.surf.blit(self.timerFont.render(str(round(timeLeft, 1)), True, c), (0, 10))
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
"""
|
||||||
|
Ran when the game closes.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
17
launch.json
Normal file
17
launch.json
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
// Use IntelliSense to learn about possible attributes.
|
||||||
|
// Hover to view descriptions of existing attributes.
|
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Python Debugger",
|
||||||
|
"type": "debugpy",
|
||||||
|
"request": "launch",
|
||||||
|
"python": "../PainJamLauncher/.venv/Scripts/python.exe",
|
||||||
|
"program": "../PainJamLauncher/__main__.py",
|
||||||
|
"args": ["captchaGame"],
|
||||||
|
"console": "integratedTerminal"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -1,4 +1,5 @@
|
|||||||
../GameUtils/
|
../GameUtils/
|
||||||
../NoPELib/
|
../NoPELib/
|
||||||
../PDOLib/
|
../PDOLib/
|
||||||
pygame-ce
|
pygame-ce
|
||||||
|
Pillow
|
||||||
Reference in New Issue
Block a user