The whole-ass thing
5
current_games/3KJ5SVOn
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
points:7
|
||||||
|
i:10
|
||||||
|
game_length:10
|
||||||
|
images:WCQU,WD2r,WDcX,WDm+,WDwV,WDHM,WDSo,WE0V,WEb2,WElr
|
||||||
|
fonts:Akzidenz-Grotesk,Helvetica,Futura,Helvetica,Futura,Calibri,Akzidenz-Grotesk,Calibri,Helvetica,Roboto
|
5
current_games/3KJ63z2M
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
points:10
|
||||||
|
i:10
|
||||||
|
game_length:10
|
||||||
|
images:7g1k,7geK,7gps,7gzz,7gKk,7gTP,7h0A,7h9Y,7hkN,7hzd
|
||||||
|
fonts:Roboto,Roboto,Calibri,Helvetica,Roboto,Akzidenz-Grotesk,Futura,Calibri,Arial,Arial
|
5
current_games/3KJ69bto
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
points:4
|
||||||
|
i:6
|
||||||
|
game_length:10
|
||||||
|
images:cUCv,cUOb,cUYI,cV6T,cVgK,cVqA,cVBc,cVLa,cVUo,cW1_
|
||||||
|
fonts:Futura,Roboto,Arial,Akzidenz-Grotesk,Futura,Calibri,Helvetica,Akzidenz-Grotesk,Arial,Futura
|
29
font-list.txt
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
Akzidenz Grotesk
|
||||||
|
Arial
|
||||||
|
#Baskerville
|
||||||
|
#Bembo
|
||||||
|
#Bodoni
|
||||||
|
#Cambria
|
||||||
|
Calibri
|
||||||
|
#Clarendon
|
||||||
|
#DaxPro
|
||||||
|
#Didot
|
||||||
|
#Franklin Gothic
|
||||||
|
#Frutiger
|
||||||
|
Futura
|
||||||
|
#Garamond
|
||||||
|
#Georgia
|
||||||
|
#Gill Sans
|
||||||
|
#Gotham
|
||||||
|
Helvetica
|
||||||
|
#Minion
|
||||||
|
#Mrs Eaves
|
||||||
|
#Myriad
|
||||||
|
#News Gothic
|
||||||
|
Roboto
|
||||||
|
#Roboto Slab
|
||||||
|
#Rockwell
|
||||||
|
#Sabon
|
||||||
|
#Segoe UI
|
||||||
|
#Times New Roman
|
||||||
|
#Verdana
|
5
font_game/__init__.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
from .util import (base64, FONT_SIZE, FONT_COLOR, IMAGE_BACKGROUND, IMAGE_SIZE,
|
||||||
|
MARGINS, FONTS)
|
||||||
|
from . import images
|
||||||
|
from . import web
|
||||||
|
from . import game
|
BIN
font_game/__pycache__/__init__.cpython-310.pyc
Normal file
BIN
font_game/__pycache__/game.cpython-310.pyc
Normal file
BIN
font_game/__pycache__/images.cpython-310.pyc
Normal file
BIN
font_game/__pycache__/util.cpython-310.pyc
Normal file
BIN
font_game/__pycache__/web.cpython-310.pyc
Normal file
47
font_game/game.py
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import random
|
||||||
|
import time
|
||||||
|
import os
|
||||||
|
|
||||||
|
from font_game.images import gen_image
|
||||||
|
from font_game import base64, FONTS
|
||||||
|
|
||||||
|
def purge_games():
|
||||||
|
for root, _, files in os.walk("current_games"):
|
||||||
|
for file in files:
|
||||||
|
os.remove(os.path.join(root, file))
|
||||||
|
|
||||||
|
def gen_id():
|
||||||
|
return base64(int(time.time()*10000))
|
||||||
|
|
||||||
|
def pick_fonts(game_length: int):
|
||||||
|
weights = {key: 100 for key in FONTS}
|
||||||
|
font_list = []
|
||||||
|
for _ in range(game_length):
|
||||||
|
picked = random.choice([k for k in weights for _ in range(weights[k])])
|
||||||
|
font_list.append(picked)
|
||||||
|
weights[picked] = weights[picked] // 2
|
||||||
|
|
||||||
|
return [(i, FONTS[i]) for i in font_list]
|
||||||
|
|
||||||
|
def start_game(game_length: int = 10) -> str:
|
||||||
|
picked_fonts = pick_fonts(game_length)
|
||||||
|
images = []
|
||||||
|
for font in picked_fonts:
|
||||||
|
images.append(gen_image(font[1]))
|
||||||
|
|
||||||
|
game_id = gen_id()
|
||||||
|
|
||||||
|
|
||||||
|
game_dict = {
|
||||||
|
"points": "0",
|
||||||
|
"i": "0",
|
||||||
|
"game_length": str(game_length),
|
||||||
|
"images": ','.join(images),
|
||||||
|
"fonts": ','.join(i[0] for i in picked_fonts)
|
||||||
|
}
|
||||||
|
game = "\n".join([f"{key}:{value}" for key, value in game_dict.items()])
|
||||||
|
|
||||||
|
with open(f"current_games/{game_id}", "w", encoding="utf-8") as file:
|
||||||
|
file.write(game)
|
||||||
|
|
||||||
|
return game_id
|
45
font_game/images.py
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import os
|
||||||
|
import time
|
||||||
|
from PIL import Image, ImageFont, ImageDraw
|
||||||
|
import lorem
|
||||||
|
|
||||||
|
from font_game import base64, IMAGE_SIZE, IMAGE_BACKGROUND, MARGINS, FONT_COLOR
|
||||||
|
|
||||||
|
def gen_image(font: ImageFont.ImageFont) -> str:
|
||||||
|
img = Image.new("RGB", IMAGE_SIZE, IMAGE_BACKGROUND)
|
||||||
|
drawer = ImageDraw.Draw(img)
|
||||||
|
text_width = IMAGE_SIZE[0] - 2 * MARGINS
|
||||||
|
text = lorem.sentence()
|
||||||
|
text_list = []
|
||||||
|
line = ""
|
||||||
|
for word in text.split(" "):
|
||||||
|
if drawer.textsize(line + word, font)[0] > text_width:
|
||||||
|
text_list.append(line)
|
||||||
|
line = ""
|
||||||
|
else:
|
||||||
|
line += word + " "
|
||||||
|
|
||||||
|
text_list.append(line)
|
||||||
|
|
||||||
|
drawer.text(
|
||||||
|
(MARGINS, MARGINS),
|
||||||
|
'\n'.join(text_list),
|
||||||
|
FONT_COLOR,
|
||||||
|
font,
|
||||||
|
spacing=30
|
||||||
|
)
|
||||||
|
|
||||||
|
filename = base64(int(time.time() * 10000) % 0xffffff)
|
||||||
|
|
||||||
|
path = f"static/images/{filename}.png"
|
||||||
|
|
||||||
|
new_size = (IMAGE_SIZE[0] // 2, IMAGE_SIZE[1] // 2)
|
||||||
|
img = img.resize(new_size, resample=Image.ANTIALIAS)
|
||||||
|
|
||||||
|
img.save(path)
|
||||||
|
return filename
|
||||||
|
|
||||||
|
def purge_images():
|
||||||
|
for root, _, files in os.walk("static/images"):
|
||||||
|
for file in files:
|
||||||
|
os.remove(os.path.join(root, file))
|
49
font_game/util.py
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import string
|
||||||
|
import pathlib
|
||||||
|
from PIL import ImageFont
|
||||||
|
|
||||||
|
BASE64_DIGITS = string.digits + string.ascii_letters + "+_"
|
||||||
|
|
||||||
|
FONT_SIZE = 120
|
||||||
|
FONT_COLOR = (0,0,0)
|
||||||
|
IMAGE_BACKGROUND = (255,255,255)
|
||||||
|
IMAGE_SIZE = (1800, 1200)
|
||||||
|
|
||||||
|
MARGINS = 60
|
||||||
|
|
||||||
|
|
||||||
|
def base64(num: int) -> str:
|
||||||
|
temp = ""
|
||||||
|
while num > 0:
|
||||||
|
temp = BASE64_DIGITS[num % 64] + temp
|
||||||
|
num = num//64
|
||||||
|
|
||||||
|
return temp
|
||||||
|
|
||||||
|
|
||||||
|
def make_fonts():
|
||||||
|
with open("font-list.txt", "r", encoding="utf-8") as file_pointer:
|
||||||
|
fonts = file_pointer.read().split("\n")
|
||||||
|
|
||||||
|
image_fonts = {}
|
||||||
|
for font_name in fonts:
|
||||||
|
if font_name[0] == "#":
|
||||||
|
continue
|
||||||
|
font_name = font_name.replace(" ","-")
|
||||||
|
if pathlib.Path(f"./fonts/{font_name}.TTF").is_file():
|
||||||
|
file_type = "TTF"
|
||||||
|
elif pathlib.Path(f"./fonts/{font_name}.OTF").is_file():
|
||||||
|
file_type = "OTF"
|
||||||
|
else:
|
||||||
|
print(f"Could not locate font \033[0;31m{font_name}\033[0m")
|
||||||
|
continue
|
||||||
|
|
||||||
|
font_path = f"./fonts/{font_name}.{file_type}"
|
||||||
|
new_font = ImageFont.truetype(font_path, FONT_SIZE)
|
||||||
|
image_fonts[font_name] = new_font
|
||||||
|
print(f"Successfully loaded font \033[0;32m{font_name}\033[0m")
|
||||||
|
|
||||||
|
return image_fonts
|
||||||
|
|
||||||
|
|
||||||
|
FONTS = make_fonts()
|
89
font_game/web.py
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
import pathlib
|
||||||
|
import flask
|
||||||
|
from font_game.images import purge_images
|
||||||
|
from font_game.game import start_game, purge_games
|
||||||
|
from font_game import FONTS
|
||||||
|
|
||||||
|
app = flask.Flask(
|
||||||
|
__name__,
|
||||||
|
static_folder="../static",
|
||||||
|
template_folder="../templates"
|
||||||
|
)
|
||||||
|
|
||||||
|
@app.before_first_request
|
||||||
|
def activate():
|
||||||
|
purge_images()
|
||||||
|
purge_games()
|
||||||
|
|
||||||
|
@app.route("/fontgame", methods=["GET", "POST"])
|
||||||
|
def font_game():
|
||||||
|
if flask.request.method == "POST":
|
||||||
|
game_id = flask.request.form['id']
|
||||||
|
font_guess = flask.request.form['font']
|
||||||
|
|
||||||
|
if not pathlib.Path(f"current_games/{game_id}").is_file():
|
||||||
|
return flask.redirect("/")
|
||||||
|
|
||||||
|
with open(f"current_games/{game_id}", "r+", encoding="utf-8") as file:
|
||||||
|
game_dict = dict(
|
||||||
|
[tuple(line.split(":")) for line in file.read().split('\n')]
|
||||||
|
)
|
||||||
|
i = int(game_dict['i'])
|
||||||
|
font = game_dict['fonts'].split(',')[i]
|
||||||
|
if font_guess == font:
|
||||||
|
game_dict['points'] = str(int(game_dict['points']) + 1)
|
||||||
|
game_dict['i'] = str(i+1)
|
||||||
|
game = "\n".join(
|
||||||
|
[f"{key}:{value}" for key, value in game_dict.items()]
|
||||||
|
)
|
||||||
|
file.seek(0)
|
||||||
|
file.write(game)
|
||||||
|
|
||||||
|
args = flask.request.args
|
||||||
|
if 'id' in args:
|
||||||
|
if not pathlib.Path(f"current_games/{args['id']}").is_file():
|
||||||
|
return flask.redirect("/")
|
||||||
|
|
||||||
|
with open(f"current_games/{args['id']}", "r", encoding="utf-8") as file:
|
||||||
|
game = dict(
|
||||||
|
[tuple(line.split(":")) for line in file.read().split('\n')]
|
||||||
|
)
|
||||||
|
|
||||||
|
if int(game['i']) == int(game['game_length']):
|
||||||
|
return flask.render_template("final.html", points=game['points'],
|
||||||
|
game_length=game['game_length'])
|
||||||
|
|
||||||
|
images = game['images'].split(",")
|
||||||
|
image = images[int(game['i'])]
|
||||||
|
|
||||||
|
url = f"/static/images/{image}.png"
|
||||||
|
parameters = {
|
||||||
|
"url": url,
|
||||||
|
"i": str(int(game['i']) + 1),
|
||||||
|
"round_n": game['i'],
|
||||||
|
"game_length": game['game_length'],
|
||||||
|
"points":game['points'],
|
||||||
|
"fonts":FONTS,
|
||||||
|
"id":args['id']
|
||||||
|
}
|
||||||
|
return flask.render_template("fontgame.html", **parameters)
|
||||||
|
|
||||||
|
if 'n' in flask.request.args:
|
||||||
|
game_length = int(flask.request.args['n'])
|
||||||
|
else:
|
||||||
|
game_length = 10
|
||||||
|
|
||||||
|
game_id = start_game(game_length)
|
||||||
|
return flask.redirect(f"/fontgame?id={game_id}")
|
||||||
|
|
||||||
|
@app.route("/")
|
||||||
|
def root():
|
||||||
|
return flask.render_template("menu.html")
|
||||||
|
|
||||||
|
@app.route("/startgame", methods=["POST"])
|
||||||
|
def start():
|
||||||
|
if 'game_length' in flask.request.form:
|
||||||
|
game_length = flask.request.form['game_length']
|
||||||
|
else:
|
||||||
|
game_length = 10
|
||||||
|
return flask.redirect(f"/fontgame?n={game_length}")
|
BIN
fonts/Akzidenz-Grotesk.TTF
Normal file
BIN
fonts/Arial.TTF
Normal file
BIN
fonts/Baskerville.TTF
Normal file
BIN
fonts/Bodoni.OTF
Normal file
BIN
fonts/Calibri.TTF
Normal file
BIN
fonts/Futura.TTF
Normal file
BIN
fonts/Garamond.TTF
Normal file
BIN
fonts/Georgia.TTF
Normal file
BIN
fonts/Helvetica.TTF
Normal file
BIN
fonts/Roboto-Slab.TTF
Normal file
BIN
fonts/Roboto.TTF
Normal file
BIN
fonts/Segoe-UI.TTF
Normal file
BIN
fonts/Times-New-Roman.TTF
Normal file
BIN
fonts/Verdana.TTF
Normal file
4
main.py
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
from font_game.web import app
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app.run()
|
34
static/css/stylesheet.css
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
.mainpage, .stats {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mainpage img {
|
||||||
|
width: 600pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rounds, .points {
|
||||||
|
font-size: 15pt;
|
||||||
|
font-weight: bold;
|
||||||
|
margin: 10pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
.finalpage, .menupage {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-content: center;
|
||||||
|
align-items: center;
|
||||||
|
justify-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.finalpage .finalscore {
|
||||||
|
margin-bottom: 30pt;
|
||||||
|
margin-top: 60pt;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 20pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menupage form .number {
|
||||||
|
width: 50pt;
|
||||||
|
text-align: right;
|
||||||
|
}
|
BIN
static/images/7g1k.png
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
static/images/7gKk.png
Normal file
After Width: | Height: | Size: 34 KiB |
BIN
static/images/7gTP.png
Normal file
After Width: | Height: | Size: 19 KiB |
BIN
static/images/7geK.png
Normal file
After Width: | Height: | Size: 32 KiB |
BIN
static/images/7gps.png
Normal file
After Width: | Height: | Size: 24 KiB |
BIN
static/images/7gzz.png
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
static/images/7h0A.png
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
static/images/7h9Y.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
static/images/7hkN.png
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
static/images/7hzd.png
Normal file
After Width: | Height: | Size: 33 KiB |
BIN
static/images/WCQU.png
Normal file
After Width: | Height: | Size: 37 KiB |
BIN
static/images/WD2r.png
Normal file
After Width: | Height: | Size: 27 KiB |
BIN
static/images/WDHM.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
static/images/WDSo.png
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
static/images/WDcX.png
Normal file
After Width: | Height: | Size: 21 KiB |
BIN
static/images/WDm+.png
Normal file
After Width: | Height: | Size: 25 KiB |
BIN
static/images/WDwV.png
Normal file
After Width: | Height: | Size: 23 KiB |
BIN
static/images/WE0V.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
static/images/WEb2.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
static/images/WElr.png
Normal file
After Width: | Height: | Size: 20 KiB |
BIN
static/images/cUCv.png
Normal file
After Width: | Height: | Size: 19 KiB |
BIN
static/images/cUOb.png
Normal file
After Width: | Height: | Size: 31 KiB |
BIN
static/images/cUYI.png
Normal file
After Width: | Height: | Size: 29 KiB |
BIN
static/images/cV6T.png
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
static/images/cVBc.png
Normal file
After Width: | Height: | Size: 29 KiB |
BIN
static/images/cVLa.png
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
static/images/cVUo.png
Normal file
After Width: | Height: | Size: 31 KiB |
BIN
static/images/cVgK.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
static/images/cVqA.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
static/images/cW1_.png
Normal file
After Width: | Height: | Size: 27 KiB |
15
templates/final.html
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<link rel="stylesheet" href="/static/css/stylesheet.css">
|
||||||
|
</head>
|
||||||
|
<body class="finalpage">
|
||||||
|
<div class="finalscore">
|
||||||
|
Final Score:
|
||||||
|
{{ points }}/{{ game_length }}
|
||||||
|
</div>
|
||||||
|
<form action="/">
|
||||||
|
<input type="submit" value="Try again" />
|
||||||
|
</form>
|
||||||
|
</body>
|
||||||
|
</html>
|
28
templates/fontgame.html
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<link rel="stylesheet" href="/static/css/stylesheet.css">
|
||||||
|
</head>
|
||||||
|
<body class="mainpage">
|
||||||
|
<img src="{{ url }}" >
|
||||||
|
<div class="right">
|
||||||
|
<div class="stats">
|
||||||
|
<div class="rounds">
|
||||||
|
Round {{ i }} of {{ game_length }}
|
||||||
|
</div>
|
||||||
|
<div class="points">
|
||||||
|
{{ points }}/{{ round_n }} points.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<form method="post" action="/fontgame?id={{ id }}">
|
||||||
|
<select name="font">
|
||||||
|
{% for font in fonts %}
|
||||||
|
<option>{{ font }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
<input type="hidden" name="id" value="{{ id }}">
|
||||||
|
<input type="submit" value="Submit">
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
14
templates/menu.html
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<link rel="stylesheet" href="/static/css/stylesheet.css">
|
||||||
|
</head>
|
||||||
|
<body class="menupage">
|
||||||
|
<h1>Font Game</h1>
|
||||||
|
<form method="post" action="/startgame">
|
||||||
|
<label for="game_length">Game rounds:</label>
|
||||||
|
<input type="number" name="game_length" min=1 class="number" value=10>
|
||||||
|
<input type="submit" value="Start">
|
||||||
|
</form>
|
||||||
|
</body>
|
||||||
|
</html>
|