diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..88328ed
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,978 @@
+# flyctl launch added from .gitignore
+# Created by https://www.toptal.com/developers/gitignore/api/vim,node,data,emacs,python,pycharm,executable,sublimetext,visualstudio,visualstudiocode
+# Edit at https://www.toptal.com/developers/gitignore?templates=vim,node,data,emacs,python,pycharm,executable,sublimetext,visualstudio,visualstudiocode
+
+### Data ###
+**/*.csv
+**/*.dat
+**/*.efx
+**/*.gbr
+**/*.key
+**/*.pps
+**/*.ppt
+**/*.pptx
+**/*.sdf
+**/*.tax2010
+**/*.vcf
+**/*.xml
+
+### Emacs ###
+# -*- mode: gitignore; -*-
+**/*~
+**/\#*\#
+.emacs.desktop
+.emacs.desktop.lock
+**/*.elc
+**/auto-save-list
+**/tramp
+**/.\#*
+
+# Org-mode
+**/.org-id-locations
+**/*_archive
+
+# flymake-mode
+**/*_flymake.*
+
+# eshell files
+eshell/history
+eshell/lastdir
+
+# elpa packages
+elpa
+
+# reftex files
+**/*.rel
+
+# AUCTeX auto folder
+auto
+
+# cask packages
+**/.cask
+**/dist
+
+# Flycheck
+**/flycheck_*.el
+
+# server auth directory
+server
+
+# projectiles files
+**/.projectile
+
+# directory configuration
+**/.dir-locals.el
+
+# network security
+network-security.data
+
+
+### Executable ###
+**/*.app
+**/*.bat
+**/*.cgi
+**/*.com
+**/*.exe
+**/*.gadget
+**/*.jar
+**/*.pif
+**/*.vb
+**/*.wsf
+
+### Node ###
+# Logs
+**/logs
+**/*.log
+**/npm-debug.log*
+**/yarn-debug.log*
+**/yarn-error.log*
+**/lerna-debug.log*
+**/.pnpm-debug.log*
+
+# Diagnostic reports (https://nodejs.org/api/report.html)
+**/report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
+
+# Runtime data
+**/pids
+**/*.pid
+**/*.seed
+**/*.pid.lock
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+**/lib-cov
+
+# Coverage directory used by tools like istanbul
+**/coverage
+**/*.lcov
+
+# nyc test coverage
+**/.nyc_output
+
+# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
+**/.grunt
+
+# Bower dependency directory (https://bower.io/)
+**/bower_components
+
+# node-waf configuration
+**/.lock-wscript
+
+# Compiled binary addons (https://nodejs.org/api/addons.html)
+**/build/Release
+
+# Dependency directories
+**/node_modules
+**/jspm_packages
+
+# Snowpack dependency directory (https://snowpack.dev/)
+**/web_modules
+
+# TypeScript cache
+**/*.tsbuildinfo
+
+# Optional npm cache directory
+**/.npm
+
+# Optional eslint cache
+**/.eslintcache
+
+# Optional stylelint cache
+**/.stylelintcache
+
+# Microbundle cache
+**/.rpt2_cache
+**/.rts2_cache_cjs
+**/.rts2_cache_es
+**/.rts2_cache_umd
+
+# Optional REPL history
+**/.node_repl_history
+
+# Output of 'npm pack'
+**/*.tgz
+
+# Yarn Integrity file
+**/.yarn-integrity
+
+# dotenv environment variable files
+**/.env
+**/.env.development.local
+**/.env.test.local
+**/.env.production.local
+**/.env.local
+
+# parcel-bundler cache (https://parceljs.org/)
+**/.cache
+**/.parcel-cache
+
+# Next.js build output
+**/.next
+**/out
+
+# Nuxt.js build / generate output
+**/.nuxt
+**/dist
+
+# Gatsby files
+**/.cache
+# Comment in the public line in if your project uses Gatsby and not Next.js
+# https://nextjs.org/blog/next-9-1#public-directory-support
+# public
+
+# vuepress build output
+**/.vuepress/dist
+
+# vuepress v2.x temp and cache directory
+**/.temp
+
+# Docusaurus cache and generated files
+**/.docusaurus
+
+# Serverless directories
+**/.serverless
+
+# FuseBox cache
+**/.fusebox
+
+# DynamoDB Local files
+**/.dynamodb
+
+# TernJS port file
+**/.tern-port
+
+# Stores VSCode versions used for testing VSCode extensions
+**/.vscode-test
+
+# yarn v2
+**/.yarn/cache
+**/.yarn/unplugged
+**/.yarn/build-state.yml
+**/.yarn/install-state.gz
+**/.pnp.*
+
+### Node Patch ###
+# Serverless Webpack directories
+**/.webpack
+
+# Optional stylelint cache
+
+# SvelteKit build / generate output
+**/.svelte-kit
+
+### PyCharm ###
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+
+# User-specific stuff
+**/.idea/**/workspace.xml
+**/.idea/**/tasks.xml
+**/.idea/**/usage.statistics.xml
+**/.idea/**/dictionaries
+**/.idea/**/shelf
+
+# AWS User-specific
+**/.idea/**/aws.xml
+
+# Generated files
+**/.idea/**/contentModel.xml
+
+# Sensitive or high-churn files
+**/.idea/**/dataSources
+**/.idea/**/dataSources.ids
+**/.idea/**/dataSources.local.xml
+**/.idea/**/sqlDataSources.xml
+**/.idea/**/dynamic.xml
+**/.idea/**/uiDesigner.xml
+**/.idea/**/dbnavigator.xml
+
+# Gradle
+**/.idea/**/gradle.xml
+**/.idea/**/libraries
+
+# Gradle and Maven with auto-import
+# When using Gradle or Maven with auto-import, you should exclude module files,
+# since they will be recreated, and may cause churn. Uncomment if using
+# auto-import.
+# .idea/artifacts
+# .idea/compiler.xml
+# .idea/jarRepositories.xml
+# .idea/modules.xml
+# .idea/*.iml
+# .idea/modules
+# *.iml
+# *.ipr
+
+# CMake
+**/cmake-build-*
+
+# Mongo Explorer plugin
+**/.idea/**/mongoSettings.xml
+
+# File-based project format
+**/*.iws
+
+# IntelliJ
+**/out
+
+# mpeltonen/sbt-idea plugin
+**/.idea_modules
+
+# JIRA plugin
+**/atlassian-ide-plugin.xml
+
+# Cursive Clojure plugin
+**/.idea/replstate.xml
+
+# SonarLint plugin
+**/.idea/sonarlint
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+**/com_crashlytics_export_strings.xml
+**/crashlytics.properties
+**/crashlytics-build.properties
+**/fabric.properties
+
+# Editor-based Rest Client
+**/.idea/httpRequests
+
+# Android studio 3.1+ serialized cache file
+**/.idea/caches/build_file_checksums.ser
+
+### PyCharm Patch ###
+# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
+
+# *.iml
+# modules.xml
+# .idea/misc.xml
+# *.ipr
+
+# Sonarlint plugin
+# https://plugins.jetbrains.com/plugin/7973-sonarlint
+**/.idea/**/sonarlint
+
+# SonarQube Plugin
+# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin
+**/.idea/**/sonarIssues.xml
+
+# Markdown Navigator plugin
+# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced
+**/.idea/**/markdown-navigator.xml
+**/.idea/**/markdown-navigator-enh.xml
+**/.idea/**/markdown-navigator
+
+# Cache file creation bug
+# See https://youtrack.jetbrains.com/issue/JBR-2257
+**/.idea/$CACHE_FILE$
+
+# CodeStream plugin
+# https://plugins.jetbrains.com/plugin/12206-codestream
+**/.idea/codestream.xml
+
+# Azure Toolkit for IntelliJ plugin
+# https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij
+**/.idea/**/azureSettings.xml
+
+### Python ###
+# Byte-compiled / optimized / DLL files
+**/__pycache__
+**/*.py[cod]
+**/*$py.class
+
+# C extensions
+**/*.so
+
+# Distribution / packaging
+**/.Python
+**/build
+**/develop-eggs
+**/downloads
+**/eggs
+**/.eggs
+**/lib
+**/lib64
+**/parts
+**/sdist
+**/var
+**/wheels
+**/share/python-wheels
+**/*.egg-info
+**/.installed.cfg
+**/*.egg
+**/MANIFEST
+
+# PyInstaller
+# Usually these files are written by a python script from a template
+# before PyInstaller builds the exe, so as to inject date/other infos into it.
+**/*.manifest
+**/*.spec
+
+# Installer logs
+**/pip-log.txt
+**/pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+**/htmlcov
+**/.tox
+**/.nox
+**/.coverage
+**/.coverage.*
+**/nosetests.xml
+**/coverage.xml
+**/*.cover
+**/*.py,cover
+**/.hypothesis
+**/.pytest_cache
+**/cover
+
+# Translations
+**/*.mo
+**/*.pot
+
+# Django stuff:
+**/local_settings.py
+**/db.sqlite3
+**/db.sqlite3-journal
+
+# Flask stuff:
+**/instance
+**/.webassets-cache
+
+# Scrapy stuff:
+**/.scrapy
+
+# Sphinx documentation
+**/docs/_build
+
+# PyBuilder
+**/.pybuilder
+**/target
+
+# Jupyter Notebook
+**/.ipynb_checkpoints
+
+# IPython
+**/profile_default
+**/ipython_config.py
+
+# pyenv
+# For a library or package, you might want to ignore these files since the code is
+# intended to run in multiple environments; otherwise, check them in:
+# .python-version
+
+# pipenv
+# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
+# However, in case of collaboration, if having platform-specific dependencies or dependencies
+# having no cross-platform support, pipenv may install dependencies that don't work, or not
+# install all needed dependencies.
+#Pipfile.lock
+
+# poetry
+# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
+# This is especially recommended for binary packages to ensure reproducibility, and is more
+# commonly ignored for libraries.
+# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
+#poetry.lock
+
+# pdm
+# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
+#pdm.lock
+# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
+# in version control.
+# https://pdm.fming.dev/#use-with-ide
+**/.pdm.toml
+
+# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
+**/__pypackages__
+
+# Celery stuff
+**/celerybeat-schedule
+**/celerybeat.pid
+
+# SageMath parsed files
+**/*.sage.py
+
+# Environments
+**/.venv
+**/env
+**/venv
+**/ENV
+**/env.bak
+**/venv.bak
+
+# Spyder project settings
+**/.spyderproject
+**/.spyproject
+
+# Rope project settings
+**/.ropeproject
+
+# mkdocs documentation
+site
+
+# mypy
+**/.mypy_cache
+**/.dmypy.json
+**/dmypy.json
+
+# Pyre type checker
+**/.pyre
+
+# pytype static type analyzer
+**/.pytype
+
+# Cython debug symbols
+**/cython_debug
+
+# PyCharm
+# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
+# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
+# and can be added to the global gitignore or merged into this file. For a more nuclear
+# option (not recommended) you can uncomment the following to ignore the entire idea folder.
+#.idea/
+
+### Python Patch ###
+# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration
+**/poetry.toml
+
+# ruff
+**/.ruff_cache
+
+# LSP config files
+**/pyrightconfig.json
+
+### SublimeText ###
+# Cache files for Sublime Text
+**/*.tmlanguage.cache
+**/*.tmPreferences.cache
+**/*.stTheme.cache
+
+# Workspace files are user-specific
+**/*.sublime-workspace
+
+# Project files should be checked into the repository, unless a significant
+# proportion of contributors will probably not be using Sublime Text
+# *.sublime-project
+
+# SFTP configuration file
+**/sftp-config.json
+**/sftp-config-alt*.json
+
+# Package control specific files
+**/Package Control.last-run
+**/Package Control.ca-list
+**/Package Control.ca-bundle
+**/Package Control.system-ca-bundle
+**/Package Control.cache
+**/Package Control.ca-certs
+**/Package Control.merged-ca-bundle
+**/Package Control.user-ca-bundle
+**/oscrypto-ca-bundle.crt
+**/bh_unicode_properties.cache
+
+# Sublime-github package stores a github token in this file
+# https://packagecontrol.io/packages/sublime-github
+**/GitHub.sublime-settings
+
+### Vim ###
+# Swap
+**/[._]*.s[a-v][a-z]
+!**/*.svg # comment out if you don't need vector files
+**/[._]*.sw[a-p]
+**/[._]s[a-rt-v][a-z]
+**/[._]ss[a-gi-z]
+**/[._]sw[a-p]
+
+# Session
+**/Session.vim
+**/Sessionx.vim
+
+# Temporary
+**/.netrwhist
+# Auto-generated tag files
+**/tags
+# Persistent undo
+**/[._]*.un~
+
+### VisualStudioCode ###
+**/.vscode/*
+!**/.vscode/settings.json
+!**/.vscode/tasks.json
+!**/.vscode/launch.json
+!**/.vscode/extensions.json
+!**/.vscode/*.code-snippets
+
+# Local History for Visual Studio Code
+**/.history
+
+# Built Visual Studio Code Extensions
+**/*.vsix
+
+### VisualStudioCode Patch ###
+# Ignore all local history of files
+**/.history
+**/.ionide
+
+### VisualStudio ###
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+##
+## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
+
+# User-specific files
+**/*.rsuser
+**/*.suo
+**/*.user
+**/*.userosscache
+**/*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+**/*.userprefs
+
+# Mono auto generated files
+**/mono_crash.*
+
+# Build results
+**/[Dd]ebug
+**/[Dd]ebugPublic
+**/[Rr]elease
+**/[Rr]eleases
+**/x64
+**/x86
+**/[Ww][Ii][Nn]32
+**/[Aa][Rr][Mm]
+**/[Aa][Rr][Mm]64
+**/bld
+**/[Bb]in
+**/[Oo]bj
+**/[Ll]og
+**/[Ll]ogs
+
+# Visual Studio 2015/2017 cache/options directory
+**/.vs
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# Visual Studio 2017 auto generated files
+**/Generated\ Files
+
+# MSTest test Results
+**/[Tt]est[Rr]esult*
+**/[Bb]uild[Ll]og.*
+
+# NUnit
+**/*.VisualState.xml
+**/TestResult.xml
+**/nunit-*.xml
+
+# Build Results of an ATL Project
+**/[Dd]ebugPS
+**/[Rr]eleasePS
+**/dlldata.c
+
+# Benchmark Results
+**/BenchmarkDotNet.Artifacts
+
+# .NET Core
+**/project.lock.json
+**/project.fragment.lock.json
+**/artifacts
+
+# ASP.NET Scaffolding
+**/ScaffoldingReadMe.txt
+
+# StyleCop
+**/StyleCopReport.xml
+
+# Files built by Visual Studio
+**/*_i.c
+**/*_p.c
+**/*_h.h
+**/*.ilk
+**/*.meta
+**/*.obj
+**/*.iobj
+**/*.pch
+**/*.pdb
+**/*.ipdb
+**/*.pgc
+**/*.pgd
+**/*.rsp
+**/*.sbr
+**/*.tlb
+**/*.tli
+**/*.tlh
+**/*.tmp
+**/*.tmp_proj
+**/*_wpftmp.csproj
+**/*.tlog
+**/*.vspscc
+**/*.vssscc
+**/.builds
+**/*.pidb
+**/*.svclog
+**/*.scc
+
+# Chutzpah Test files
+**/_Chutzpah*
+
+# Visual C++ cache files
+**/ipch
+**/*.aps
+**/*.ncb
+**/*.opendb
+**/*.opensdf
+**/*.cachefile
+**/*.VC.db
+**/*.VC.VC.opendb
+
+# Visual Studio profiler
+**/*.psess
+**/*.vsp
+**/*.vspx
+**/*.sap
+
+# Visual Studio Trace Files
+**/*.e2e
+
+# TFS 2012 Local Workspace
+**/$tf
+
+# Guidance Automation Toolkit
+**/*.gpState
+
+# ReSharper is a .NET coding add-in
+**/_ReSharper*
+**/*.[Rr]e[Ss]harper
+**/*.DotSettings.user
+
+# TeamCity is a build add-in
+**/_TeamCity*
+
+# DotCover is a Code Coverage Tool
+**/*.dotCover
+
+# AxoCover is a Code Coverage Tool
+**/.axoCover/*
+!**/.axoCover/settings.json
+
+# Coverlet is a free, cross platform Code Coverage Tool
+**/coverage*.json
+**/coverage*.xml
+**/coverage*.info
+
+# Visual Studio code coverage results
+**/*.coverage
+**/*.coveragexml
+
+# NCrunch
+**/_NCrunch_*
+**/.*crunch*.local.xml
+**/nCrunchTemp_*
+
+# MightyMoose
+**/*.mm.*
+**/AutoTest.Net
+
+# Web workbench (sass)
+**/.sass-cache
+
+# Installshield output folder
+**/[Ee]xpress
+
+# DocProject is a documentation generator add-in
+**/DocProject/buildhelp
+**/DocProject/Help/*.HxT
+**/DocProject/Help/*.HxC
+**/DocProject/Help/*.hhc
+**/DocProject/Help/*.hhk
+**/DocProject/Help/*.hhp
+**/DocProject/Help/Html2
+**/DocProject/Help/html
+
+# Click-Once directory
+**/publish
+
+# Publish Web Output
+**/*.[Pp]ublish.xml
+**/*.azurePubxml
+# Note: Comment the next line if you want to checkin your web deploy settings,
+# but database connection strings (with potential passwords) will be unencrypted
+**/*.pubxml
+**/*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+**/PublishScripts
+
+# NuGet Packages
+**/*.nupkg
+# NuGet Symbol Packages
+**/*.snupkg
+# The packages folder can be ignored because of Package Restore
+**/**/[Pp]ackages/*
+# except build/, which is used as an MSBuild target.
+!**/**/[Pp]ackages/build
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/[Pp]ackages/repositories.config
+# NuGet v3's project.json files produces more ignorable files
+**/*.nuget.props
+**/*.nuget.targets
+
+# Microsoft Azure Build Output
+**/csx
+**/*.build.csdef
+
+# Microsoft Azure Emulator
+**/ecf
+**/rcf
+
+# Windows Store app package directories and files
+**/AppPackages
+**/BundleArtifacts
+**/Package.StoreAssociation.xml
+**/_pkginfo.txt
+**/*.appx
+**/*.appxbundle
+**/*.appxupload
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+**/*.[Cc]ache
+# but keep track of directories ending in .cache
+!**/?*.[Cc]ache
+
+# Others
+**/ClientBin
+**/~$*
+**/*.dbmdl
+**/*.dbproj.schemaview
+**/*.jfm
+**/*.pfx
+**/*.publishsettings
+**/orleans.codegen.cs
+
+# Including strong name files can present a security risk
+# (https://github.com/github/gitignore/pull/2483#issue-259490424)
+#*.snk
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+**/Generated_Code
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+**/_UpgradeReport_Files
+**/Backup*
+**/UpgradeLog*.XML
+**/UpgradeLog*.htm
+**/ServiceFabricBackup
+**/*.rptproj.bak
+
+# SQL Server files
+**/*.mdf
+**/*.ldf
+**/*.ndf
+
+# Business Intelligence projects
+**/*.rdl.data
+**/*.bim.layout
+**/*.bim_*.settings
+**/*.rptproj.rsuser
+**/*- [Bb]ackup.rdl
+**/*- [Bb]ackup ([0-9]).rdl
+**/*- [Bb]ackup ([0-9][0-9]).rdl
+
+# Microsoft Fakes
+**/FakesAssemblies
+
+# GhostDoc plugin setting file
+**/*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+**/.ntvs_analysis.dat
+
+# Visual Studio 6 build log
+**/*.plg
+
+# Visual Studio 6 workspace options file
+**/*.opt
+
+# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
+**/*.vbw
+
+# Visual Studio 6 auto-generated project file (contains which files were open etc.)
+**/*.vbp
+
+# Visual Studio 6 workspace and project file (working project files containing files to include in project)
+**/*.dsw
+**/*.dsp
+
+# Visual Studio 6 technical files
+
+# Visual Studio LightSwitch build output
+**/**/*.HTMLClient/GeneratedArtifacts
+**/**/*.DesktopClient/GeneratedArtifacts
+**/**/*.DesktopClient/ModelManifest.xml
+**/**/*.Server/GeneratedArtifacts
+**/**/*.Server/ModelManifest.xml
+**/_Pvt_Extensions
+
+# Paket dependency manager
+**/.paket/paket.exe
+**/paket-files
+
+# FAKE - F# Make
+**/.fake
+
+# CodeRush personal settings
+**/.cr/personal
+
+# Python Tools for Visual Studio (PTVS)
+**/*.pyc
+
+# Cake - Uncomment if you are using it
+# tools/**
+# !tools/packages.config
+
+# Tabs Studio
+**/*.tss
+
+# Telerik's JustMock configuration file
+**/*.jmconfig
+
+# BizTalk build output
+**/*.btp.cs
+**/*.btm.cs
+**/*.odx.cs
+**/*.xsd.cs
+
+# OpenCover UI analysis results
+**/OpenCover
+
+# Azure Stream Analytics local run output
+**/ASALocalRun
+
+# MSBuild Binary and Structured Log
+**/*.binlog
+
+# NVidia Nsight GPU debugger configuration file
+**/*.nvuser
+
+# MFractors (Xamarin productivity tool) working folder
+**/.mfractor
+
+# Local History for Visual Studio
+**/.localhistory
+
+# Visual Studio History (VSHistory) files
+**/.vshistory
+
+# BeatPulse healthcheck temp database
+**/healthchecksdb
+
+# Backup folder for Package Reference Convert tool in Visual Studio 2017
+**/MigrationBackup
+
+# Ionide (cross platform F# VS Code tools) working folder
+**/.ionide
+
+# Fody - auto-generated XML schema
+**/FodyWeavers.xsd
+
+# VS Code files for those working on multiple tools
+**/*.code-workspace
+
+# Local History for Visual Studio Code
+
+# Windows Installer files from build outputs
+**/*.cab
+**/*.msi
+**/*.msix
+**/*.msm
+**/*.msp
+
+# JetBrains Rider
+**/*.sln.iml
+
+### VisualStudio Patch ###
+# Additional files built by Visual Studio
+
+# End of https://www.toptal.com/developers/gitignore/api/vim,node,data,emacs,python,pycharm,executable,sublimetext,visualstudio,visualstudiocode
+**/database.db
+**/database.db
+**/database.db
+**/.markata.cache
+**/database.sqlite
+
+# flyctl launch added from .pytest_cache/.gitignore
+# Created by pytest automatically.
+.pytest_cache/**/*
+
+# flyctl launch added from .ruff_cache/.gitignore
+.ruff_cache/**/*
+fly.toml
diff --git a/.gitignore b/.gitignore
index 9c4e139..1683c90 100644
--- a/.gitignore
+++ b/.gitignore
@@ -967,3 +967,5 @@ database.db
database.db
.markata.cache
database.sqlite
+.env.dev
+.env.dev.docker
diff --git a/d3.py b/d3.py
new file mode 100644
index 0000000..3b41786
--- /dev/null
+++ b/d3.py
@@ -0,0 +1,70 @@
+import sqlite3
+
+from jinja2 import Environment, FileSystemLoader
+
+
+def get_tables_and_columns(conn):
+ cursor = conn.cursor()
+ cursor.execute("SELECT name FROM sqlite_master WHERE type='table';")
+ tables = [
+ {
+ "name": table[0],
+ "columns": get_columns(conn, table[0]),
+ "foreign_keys": get_foreign_keys(conn, table[0]),
+ }
+ for table in cursor.fetchall()
+ ]
+ return tables
+
+
+def get_columns(conn, table_name):
+ cursor = conn.cursor()
+ cursor.execute(f"PRAGMA table_info({table_name});")
+ columns = [row[1] for row in cursor.fetchall()]
+ return columns
+
+
+def get_foreign_keys(conn, table_name):
+ cursor = conn.cursor()
+ cursor.execute(f"PRAGMA foreign_key_list({table_name});")
+ foreign_keys = [
+ {"id": row[0], "from": row[3], "to_table": row[2], "to": row[4]}
+ for row in cursor.fetchall()
+ ]
+ return foreign_keys
+
+
+def generate_links(tables):
+ links = []
+ for t_index, table in enumerate(tables):
+ for fk in table["foreign_keys"]:
+ target_index = next(
+ i for i, target in enumerate(tables) if target["name"] == fk["to_table"]
+ )
+ source_y = 40 + table["columns"].index(fk["from"]) * 20
+ target_y = 40 + tables[target_index]["columns"].index(fk["to"]) * 20
+ links.append(
+ {
+ "source": {"x": 50 + t_index * 150 + 120, "y": 50 + source_y},
+ "target": {"x": 50 + target_index * 150, "y": 50 + target_y},
+ }
+ )
+ return links
+
+
+def generate_er_diagram(database_path):
+ conn = sqlite3.connect(database_path)
+ tables = get_tables_and_columns(conn)
+ links = [] # Currently, we won't extract relationships
+ links = generate_links(tables)
+
+ env = Environment(loader=FileSystemLoader("templates"))
+ template = env.get_template("er_diagram.html")
+
+ with open("index.html", "w") as f:
+ f.write(template.render(tables=tables, links=links))
+
+
+if __name__ == "__main__":
+ db_path = "database.db"
+ generate_er_diagram(db_path)
diff --git a/database.md b/database.md
new file mode 100644
index 0000000..481c4a0
--- /dev/null
+++ b/database.md
@@ -0,0 +1,72 @@
+
+
+---
+
+## Table: learn_sql_model_alembic_version
+
+### First 5 rows
+
+| version_num |
+|-------------|
+| f48730a783a5 |
+
+### Columns
+
+| Column Name | Type | Foreign Key | Example Value |
+|-------------|------|-------------|---------------|
+| version_num | VARCHAR(32) | | | |
+
+### Records Count
+
+The table learn_sql_model_alembic_version contains 1 records.
+
+---
+
+## Table: pet
+
+### First 5 rows
+
+| name | birthday | id |
+|------|----------|----|
+
+### Columns
+
+| Column Name | Type | Foreign Key | Example Value |
+|-------------|------|-------------|---------------|
+| name | VARCHAR | | | |
+| birthday | DATETIME | | | |
+| id | INTEGER | | | |
+
+### Records Count
+
+The table pet contains 0 records.
+
+---
+
+## Table: hero
+
+### First 5 rows
+
+| name | secret_name | x | y | size | age | shoe_size | pet_id | id |
+|------|-------------|---|---|------|-----|-----------|--------|----|
+
+### Columns
+
+| Column Name | Type | Foreign Key | Example Value |
+|-------------|------|-------------|---------------|
+| name | VARCHAR | | | |
+| secret_name | VARCHAR | | | |
+| x | INTEGER | | | |
+| y | INTEGER | | | |
+| size | INTEGER | | | |
+| age | INTEGER | | | |
+| shoe_size | INTEGER | | | |
+| pet_id | INTEGER | pet.id | | |
+| id | INTEGER | | | |
+
+### Records Count
+
+The table hero contains 0 records.
+
+---
+
diff --git a/er_diagram.png b/er_diagram.png
new file mode 100644
index 0000000..afcf6e0
Binary files /dev/null and b/er_diagram.png differ
diff --git a/im.png b/im.png
new file mode 100644
index 0000000..9196f21
Binary files /dev/null and b/im.png differ
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..bb40a04
--- /dev/null
+++ b/index.html
@@ -0,0 +1,129 @@
+
+
+
+
+
+ ER Diagram
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/learn_sql_model/cli/model.py b/learn_sql_model/cli/model.py
index d622821..786daff 100644
--- a/learn_sql_model/cli/model.py
+++ b/learn_sql_model/cli/model.py
@@ -1,15 +1,15 @@
from pathlib import Path
from typing import Annotated
-import copier
+# import copier
import typer
from learn_sql_model.cli.common import verbose_callback
from learn_sql_model.config import get_config
from learn_sql_model.optional import _optional_import_
-alembic = _optional_import_('alembic', group='manage')
-Config = _optional_import_('alembic.config', 'Config', group='manage')
+alembic = _optional_import_("alembic", group="manage")
+Config = _optional_import_("alembic.config", "Config", group="manage")
model_app = typer.Typer()
@@ -94,5 +94,4 @@ def populate(
callback=verbose_callback,
help="show the log messages",
),
-):
- ...
+): ...
diff --git a/learn_sql_model/game/debug.py b/learn_sql_model/game/debug.py
new file mode 100644
index 0000000..8e01bf3
--- /dev/null
+++ b/learn_sql_model/game/debug.py
@@ -0,0 +1,25 @@
+import pygame
+
+
+class Debug:
+ def __init__(self, game):
+ self.game = game
+ self.is_open = False
+ self.debounce = False
+
+ def handle_events(self, events):
+ for event in events:
+ if event.type == pygame.KEYDOWN:
+ if event.key == pygame.K_F3 and not self.debounce:
+ self.is_open = not self.is_open
+ self.debounce = True
+ if event.type == pygame.KEYUP:
+ if event.key == pygame.K_F3:
+ self.debounce = False
+
+ def render(self):
+ if self.is_open:
+ text = self.game.font.render(
+ str(int(self.game.clock.get_fps())) + " fps", True, (255, 255, 255)
+ )
+ self.game.screen.blit(text, (20, 20))
diff --git a/learn_sql_model/game/game.py b/learn_sql_model/game/game.py
index ffb0a0b..742d8f5 100644
--- a/learn_sql_model/game/game.py
+++ b/learn_sql_model/game/game.py
@@ -5,6 +5,7 @@ from websocket import create_connection
from learn_sql_model.config import get_config
from learn_sql_model.console import console
+from learn_sql_model.game.debug import Debug
from learn_sql_model.game.light import Light
from learn_sql_model.game.map import Map
from learn_sql_model.game.menu import Menu
@@ -20,7 +21,8 @@ config = get_config()
class Client:
def __init__(self):
- self.screen = pygame.display.set_mode((0, 0), pygame.FULLSCREEN)
+ # self.screen = pygame.display.set_mode((0, 0), pygame.FULLSCREEN)
+ self.screen = pygame.display.set_mode((1280, 720))
pygame.display.set_caption("Learn SQL Model")
self.clock = pygame.time.Clock()
self.running = True
@@ -38,8 +40,11 @@ class Client:
self.font = pygame.font.SysFont("", 25)
self.joysticks = {}
self.darkness = pygame.Surface(
- (self.screen.get_width(), self.screen.get_height())
+ (self.screen.get_width(), self.screen.get_height()),
+ pygame.SRCALPHA,
+ 32,
)
+ self.debug = Debug(self)
atexit.register(self.quit)
@@ -75,11 +80,11 @@ class Client:
console.print("update")
self.update()
console.print("render")
+
self.render()
time = self.clock.tick(60)
self.elapsed = time / 100
self.ticks += 1
- # Console().print(self.clock.get_fps())
console.print(f"time: {time}")
console.print(f"ticks: {self.ticks}")
if profiler:
@@ -98,9 +103,11 @@ class Client:
self.screen.fill((0, 0, 0))
self.map.render()
self.player.render()
- light_level = 0
- self.darkness.fill((light_level, light_level, light_level))
- self.light.render()
+
+ if self.ticks % 1 == 0 or self.ticks == 0:
+ light_level = 0
+ self.darkness.fill((light_level, light_level, light_level))
+ self.light.render()
self.screen.blit(
self.darkness,
(0, 0),
@@ -109,11 +116,13 @@ class Client:
# update the screen
self.menu.render()
+ self.debug.render()
pygame.display.flip()
def handle_events(self):
self.events = pygame.event.get()
self.menu.handle_events(self.events)
+ self.debug.handle_events(self.events)
self.player.handle_events()
for event in self.events:
if event.type == pygame.QUIT:
diff --git a/learn_sql_model/game/light.py b/learn_sql_model/game/light.py
index 9000bd5..abaa861 100644
--- a/learn_sql_model/game/light.py
+++ b/learn_sql_model/game/light.py
@@ -1,34 +1,133 @@
+import bisect
+
+from PIL import Image, ImageFilter
+
from learn_sql_model.optional import _optional_import_
pygame = _optional_import_("pygame", group="game")
+def rot_center(image, angle):
+ """rotate an image while keeping its center and size"""
+ orig_rect = image.get_rect()
+ rot_image = pygame.transform.rotate(image, angle)
+ rot_rect = orig_rect.copy()
+ rot_rect.center = rot_image.get_rect().center
+ rot_image = rot_image.subsurface(rot_rect).copy()
+ return rot_image
+
+
class Light:
def __init__(self, game):
self.game = game
self.surf = pygame.Surface(
- (self.game.screen.get_width(), self.game.screen.get_height())
+ (self.game.screen.get_width(), self.game.screen.get_height()),
+ pygame.SRCALPHA,
+ 32,
)
- # pil_image = Image.new("RGBA", (1000, 500))
- # pil_draw = ImageDraw.Draw(pil_image)
- # pil_draw.pieslice((-1500, -100, 1000, 600), 340, 20, fill=(255, 250, 205))
- # pil_image = pil_image.filter(ImageFilter.GaussianBlur(radius=5))
+ self.surf.set_colorkey((0, 0, 0))
+ self.pre_render()
- # mode = pil_image.mode
- # size = pil_image.size
- # data = pil_image.tobytes()
+ def pre_render(self):
- # self.image = pygame.image.fromstring(data, size, mode)
+ # self.lights = {}
+ # for deg in range(-360, 360, 20):
+ # print("loading light", deg)
+ # self.lights[deg] = pygame.image.load(
+ # f"lights/light-{deg}.png"
+ # ).convert_alpha()
+ # return
- # for r in range(-25, 25):
- # _v = v.rotate(r)
- # pygame.draw.line(
- # self.game.screen,
- # (255, 250, 205),
- # (0, 50),
- # (0 + _v.x, self.game.player.hero.y + _v.y),
- # 50,
- # )
+ light_surf = pygame.Surface(
+ (
+ self.game.player.hero.flashlight_strength * 3,
+ self.game.player.hero.flashlight_strength * 3,
+ ),
+ pygame.SRCALPHA,
+ 32,
+ )
+
+ v = pygame.math.Vector2(0, 1)
+ v.scale_to_length(self.game.player.hero.flashlight_strength)
+ for r in range(-90 - 25, -90 + 25):
+ _v = v.rotate(r)
+ pygame.draw.line(
+ light_surf,
+ (255, 250, 205),
+ (light_surf.get_width() / 2, light_surf.get_height() / 2),
+ (
+ light_surf.get_width() / 2 + _v.x,
+ light_surf.get_height() / 2 + _v.y,
+ ),
+ 50,
+ )
+ pygame.draw.circle(
+ light_surf,
+ (255, 250, 205),
+ (light_surf.get_width() / 2, light_surf.get_height() / 2),
+ self.game.player.hero.lanturn_strength,
+ )
+
+ light_surf_pil = Image.frombytes(
+ "RGBA",
+ (light_surf.get_width(), light_surf.get_height()),
+ pygame.image.tostring(light_surf, "RGBA", False),
+ )
+ light_surf_blur = light_surf_pil.filter(ImageFilter.GaussianBlur(radius=100))
+ light_surf = pygame.image.fromstring(
+ light_surf_blur.tobytes(),
+ (light_surf.get_width(), light_surf.get_height()),
+ "RGBA",
+ ).convert_alpha()
+
+ pygame.draw.circle(
+ light_surf,
+ (255, 250, 205),
+ (light_surf.get_width() / 2, light_surf.get_height() / 2),
+ self.game.player.hero.lanturn_strength,
+ )
+
+ light_surf_pil = Image.frombytes(
+ "RGBA",
+ (light_surf.get_width(), light_surf.get_height()),
+ pygame.image.tostring(light_surf, "RGBA", False),
+ )
+ light_surf_blur = light_surf_pil.filter(ImageFilter.GaussianBlur(radius=50))
+ light_surf = pygame.image.fromstring(
+ light_surf_blur.tobytes(),
+ (light_surf.get_width(), light_surf.get_height()),
+ "RGBA",
+ ).convert_alpha()
+
+ pygame.draw.circle(
+ light_surf,
+ (255, 250, 205),
+ (light_surf.get_width() / 2, light_surf.get_height() / 2),
+ self.game.player.hero.lanturn_strength,
+ )
+
+ light_surf_pil = Image.frombytes(
+ "RGBA",
+ (light_surf.get_width(), light_surf.get_height()),
+ pygame.image.tostring(light_surf, "RGBA", False),
+ )
+ light_surf_blur = light_surf_pil.filter(ImageFilter.GaussianBlur(radius=20))
+ light_surf = pygame.image.fromstring(
+ light_surf_blur.tobytes(),
+ (light_surf.get_width(), light_surf.get_height()),
+ "RGBA",
+ ).convert_alpha()
+
+ self.light_surf = light_surf
+ self.light_surf.set_colorkey((0, 0, 0))
+
+ self.lights = {
+ deg: pygame.transform.rotate(self.light_surf, deg - 90)
+ for deg in range(-360, 360, 20)
+ }
+
+ for deg, light in self.lights.items():
+ pygame.image.save(light, f"lights/light-{deg}.png")
def render(self):
self.surf.fill((0, 0, 0))
@@ -37,68 +136,84 @@ class Light:
mx - self.game.player.hero.x, my - self.game.player.hero.y
)
v.scale_to_length(self.game.player.hero.flashlight_strength)
- self.game.player.hero.flashlight_angle = v.angle_to(pygame.math.Vector2(0, 1))
-
- for r in range(-25, 25):
- _v = v.rotate(r)
- pygame.draw.line(
- self.surf,
- (255, 250, 205),
- (self.game.player.hero.x, self.game.player.hero.y),
- (self.game.player.hero.x + _v.x, self.game.player.hero.y + _v.y),
- 50,
- )
+ self.game.player.hero.flashlight_angle = v.angle_to(pygame.math.Vector2(1, 0))
for other in self.game.player.others.__root__:
if other.id == self.game.player.hero.id:
continue
- v = pygame.math.Vector2(0, 1)
- v = v.rotate(-other.flashlight_angle)
- v.scale_to_length(other.flashlight_strength)
- for r in range(-25, 25):
- _v = v.rotate(r)
- pygame.draw.line(
- self.surf,
- (255, 250, 205),
- (other.x, other.y),
- (other.x + _v.x, other.y + _v.y),
- 50,
+
+ light_index = list(self.lights.keys())[
+ bisect.bisect_left(
+ list(self.lights.keys()),
+ other.flashlight_angle + 90,
)
- pygame.draw.circle(
- self.surf,
- (255, 250, 205),
- (other.x, other.y),
- other.lanturn_strength,
+ ]
+
+ my_light = self.lights[light_index]
+ self.surf.blit(
+ my_light,
+ (
+ other.x - my_light.get_width() / 2,
+ other.y - my_light.get_height() / 2,
+ ),
)
- # draw a circle
- pygame.draw.circle(
- self.surf,
- (255, 250, 205),
- (self.game.player.hero.x, self.game.player.hero.y),
- self.game.player.hero.lanturn_strength,
+ light_index = list(self.lights.keys())[
+ bisect.bisect_left(
+ list(self.lights.keys()),
+ self.game.player.hero.flashlight_angle + 90,
+ )
+ ]
+
+ my_light = self.lights[light_index]
+ self.surf.blit(
+ my_light,
+ (
+ self.game.player.hero.x - my_light.get_width() / 2,
+ self.game.player.hero.y - my_light.get_height() / 2,
+ ),
)
+ # for r in range(-25, 25):
+ # _v = v.rotate(r)
+ # pygame.draw.line(
+ # self.surf,
+ # (255, 250, 205),
+ # (self.game.player.hero.x, self.game.player.hero.y),
+ # (self.game.player.hero.x + _v.x, self.game.player.hero.y + _v.y),
+ # 50,
+ # )
+ # # draw a circle
+ # pygame.draw.circle(
+ # self.surf,
+ # (255, 250, 205),
+ # (self.game.player.hero.x, self.game.player.hero.y),
+ # self.game.player.hero.lanturn_strength,
+ # )
+
+ # for other in self.game.player.others.__root__:
+ # if other.id == self.game.player.hero.id:
+ # continue
+ # v = pygame.math.Vector2(0, 1)
+ # v = v.rotate(-other.flashlight_angle)
+ # v.scale_to_length(other.flashlight_strength)
+ # for r in range(-25, 25):
+ # _v = v.rotate(r)
+ # pygame.draw.line(
+ # self.surf,
+ # (255, 250, 205),
+ # (other.x, other.y),
+ # (other.x + _v.x, other.y + _v.y),
+ # 50,
+ # )
+ # pygame.draw.circle(
+ # self.surf,
+ # (255, 250, 205),
+ # (other.x, other.y),
+ # other.lanturn_strength,
+ # )
+
self.game.darkness.blit(
self.surf,
(0, 0),
)
-
-
-def render_flashlight(light, strength, angle):
-
- # self.darkness.blit(
- # pygame.transform.smoothscale(
- # self.spot, [self.light_power, self.light_power]
- # ),
- # (self.x - self.light_power / 2, self.y - self.light_power / 2),
- # )
- for r in range(-25, 25):
- _v = v.rotate(r)
- pygame.draw.line(
- light,
- (255, 250, 205),
- (self.game.player.hero.x, self.game.player.hero.y),
- (self.game.player.hero.x + _v.x, self.game.player.hero.y + _v.y),
- 50,
- )
diff --git a/learn_sql_model/game/map.py b/learn_sql_model/game/map.py
index 7bb04c9..e792056 100644
--- a/learn_sql_model/game/map.py
+++ b/learn_sql_model/game/map.py
@@ -39,6 +39,33 @@ class Map:
# try to load the map from map.png
try:
self.surf = pygame.image.load("map.png").convert_alpha()
+
+ # self.surf_pil = Image.frombytes(
+ # "RGBA",
+ # (self.surf.get_width(), self.surf.get_height()),
+ # pygame.image.tostring(self.surf, "RGBA", False),
+ # )
+ # self.surf_blur = (
+ # self.surf_pil.filter(
+ # ImageFilter.SMOOTH_MORE(),
+ # )
+ # .filter(ImageFilter.SMOOTH_MORE())
+ # .filter(ImageFilter.SMOOTH_MORE())
+ # .filter(ImageFilter.SMOOTH_MORE())
+ # .filter(ImageFilter.SMOOTH_MORE())
+ # .filter(ImageFilter.SMOOTH_MORE())
+ # # sharpen
+ # .filter(ImageFilter.UnsharpMask(radius=3, percent=100, threshold=3))
+ # .filter(ImageFilter.UnsharpMask(radius=3, percent=100, threshold=3))
+ # .filter(ImageFilter.UnsharpMask(radius=3, percent=100, threshold=3))
+ # )
+
+ # self.surf = pygame.image.fromstring(
+ # self.surf_blur.tobytes(),
+ # (self.surf.get_width(), self.surf.get_height()),
+ # "RGBA",
+ # ).convert_alpha()
+
except FileNotFoundError:
self.pre_draw()
diff --git a/learn_sql_model/game/menu.py b/learn_sql_model/game/menu.py
index 565c305..c3c67f5 100644
--- a/learn_sql_model/game/menu.py
+++ b/learn_sql_model/game/menu.py
@@ -1,6 +1,7 @@
from typing import Callable, Tuple
from pydantic import BaseModel
+
from learn_sql_model.optional import _optional_import_
pygame = _optional_import_("pygame", group="game")
@@ -113,7 +114,7 @@ class Menu:
def handle_events(self, events):
self.hamburger.handle_events(self, events)
for event in events:
- if event.type == pygame.MOUSEBUTTONDOWN:
+ if event.type == pygame.MOUSEBUTTONDOWN and self.is_menu_open:
if event.button == 1: # Left mouse button
self.handle_click()
diff --git a/learn_sql_model/game/player.py b/learn_sql_model/game/player.py
index 508c75a..c7e1a79 100644
--- a/learn_sql_model/game/player.py
+++ b/learn_sql_model/game/player.py
@@ -33,9 +33,13 @@ class Player:
self.speed = 10
self.max_speed = 10
self.image = pygame.image.load("creeper.png").convert_alpha()
+ self.pet_image = pygame.image.load("pet.png").convert_alpha()
self.image = pygame.transform.scale(
self.image, (self.hero.size, self.hero.size)
)
+ self.pet_image = pygame.transform.scale(
+ self.pet_image, (self.hero.size/1.5, self.hero.size/2)
+ )
self.x_last = self.x
self.y_last = self.y
self.hitbox_surface = pygame.Surface((self.width, self.height))
@@ -204,16 +208,16 @@ class Player:
self.x_last = self.hero.x
self.y_last = self.hero.y
- # if self.game.ticks % 1 == 0 or self.game.ticks == 0:
- console.print("updating")
- update = HeroUpdate(**self.hero.dict(exclude_unset=True))
- console.print(update)
- self.game.ws.send(update.json())
- console.print("sent")
+ if self.game.ticks % 60 == 0 or self.game.ticks == 0:
+ console.print("updating")
+ update = HeroUpdate(**self.hero.dict(exclude_unset=True))
+ console.print(update)
+ self.game.ws.send(update.json())
+ console.print("sent")
- raw_heros = self.game.ws.recv()
- console.print(raw_heros)
- self.others = Heros.parse_raw(raw_heros)
+ raw_heros = self.game.ws.recv()
+ console.print(raw_heros)
+ self.others = Heros.parse_raw(raw_heros)
def draw(self):
self.move()
@@ -242,6 +246,10 @@ class Player:
self.image,
(self.hero.x - self.hero.size / 2, self.hero.y - self.hero.size / 2),
)
+ self.game.screen.blit(
+ self.pet_image,
+ (self.hero.x + self.hero.size / 2, self.hero.y - self.hero.size / 2),
+ )
# pygame.draw.circle(
# self.game.screen, (0, 0, 255), (self.hero.x, self.hero.y), self.hero.size
diff --git a/lights/light--100.png b/lights/light--100.png
new file mode 100644
index 0000000..de46434
Binary files /dev/null and b/lights/light--100.png differ
diff --git a/lights/light--120.png b/lights/light--120.png
new file mode 100644
index 0000000..963152f
Binary files /dev/null and b/lights/light--120.png differ
diff --git a/lights/light--140.png b/lights/light--140.png
new file mode 100644
index 0000000..cda76ae
Binary files /dev/null and b/lights/light--140.png differ
diff --git a/lights/light--160.png b/lights/light--160.png
new file mode 100644
index 0000000..3643c9e
Binary files /dev/null and b/lights/light--160.png differ
diff --git a/lights/light--180.png b/lights/light--180.png
new file mode 100644
index 0000000..82f9e33
Binary files /dev/null and b/lights/light--180.png differ
diff --git a/lights/light--20.png b/lights/light--20.png
new file mode 100644
index 0000000..3e14e6b
Binary files /dev/null and b/lights/light--20.png differ
diff --git a/lights/light--200.png b/lights/light--200.png
new file mode 100644
index 0000000..6490af9
Binary files /dev/null and b/lights/light--200.png differ
diff --git a/lights/light--220.png b/lights/light--220.png
new file mode 100644
index 0000000..a59b646
Binary files /dev/null and b/lights/light--220.png differ
diff --git a/lights/light--240.png b/lights/light--240.png
new file mode 100644
index 0000000..0f036b7
Binary files /dev/null and b/lights/light--240.png differ
diff --git a/lights/light--260.png b/lights/light--260.png
new file mode 100644
index 0000000..7033ac0
Binary files /dev/null and b/lights/light--260.png differ
diff --git a/lights/light--280.png b/lights/light--280.png
new file mode 100644
index 0000000..42509bf
Binary files /dev/null and b/lights/light--280.png differ
diff --git a/lights/light--300.png b/lights/light--300.png
new file mode 100644
index 0000000..0c63757
Binary files /dev/null and b/lights/light--300.png differ
diff --git a/lights/light--320.png b/lights/light--320.png
new file mode 100644
index 0000000..4112187
Binary files /dev/null and b/lights/light--320.png differ
diff --git a/lights/light--340.png b/lights/light--340.png
new file mode 100644
index 0000000..9a9bb4d
Binary files /dev/null and b/lights/light--340.png differ
diff --git a/lights/light--360.png b/lights/light--360.png
new file mode 100644
index 0000000..553fc60
Binary files /dev/null and b/lights/light--360.png differ
diff --git a/lights/light--40.png b/lights/light--40.png
new file mode 100644
index 0000000..c142c08
Binary files /dev/null and b/lights/light--40.png differ
diff --git a/lights/light--60.png b/lights/light--60.png
new file mode 100644
index 0000000..8965054
Binary files /dev/null and b/lights/light--60.png differ
diff --git a/lights/light--80.png b/lights/light--80.png
new file mode 100644
index 0000000..9ed81e6
Binary files /dev/null and b/lights/light--80.png differ
diff --git a/lights/light-0.png b/lights/light-0.png
new file mode 100644
index 0000000..553fc60
Binary files /dev/null and b/lights/light-0.png differ
diff --git a/lights/light-100.png b/lights/light-100.png
new file mode 100644
index 0000000..7033ac0
Binary files /dev/null and b/lights/light-100.png differ
diff --git a/lights/light-120.png b/lights/light-120.png
new file mode 100644
index 0000000..0367763
Binary files /dev/null and b/lights/light-120.png differ
diff --git a/lights/light-140.png b/lights/light-140.png
new file mode 100644
index 0000000..a59b646
Binary files /dev/null and b/lights/light-140.png differ
diff --git a/lights/light-160.png b/lights/light-160.png
new file mode 100644
index 0000000..6490af9
Binary files /dev/null and b/lights/light-160.png differ
diff --git a/lights/light-180.png b/lights/light-180.png
new file mode 100644
index 0000000..82f9e33
Binary files /dev/null and b/lights/light-180.png differ
diff --git a/lights/light-20.png b/lights/light-20.png
new file mode 100644
index 0000000..9a9bb4d
Binary files /dev/null and b/lights/light-20.png differ
diff --git a/lights/light-200.png b/lights/light-200.png
new file mode 100644
index 0000000..3643c9e
Binary files /dev/null and b/lights/light-200.png differ
diff --git a/lights/light-220.png b/lights/light-220.png
new file mode 100644
index 0000000..cda76ae
Binary files /dev/null and b/lights/light-220.png differ
diff --git a/lights/light-240.png b/lights/light-240.png
new file mode 100644
index 0000000..7c35056
Binary files /dev/null and b/lights/light-240.png differ
diff --git a/lights/light-260.png b/lights/light-260.png
new file mode 100644
index 0000000..de46434
Binary files /dev/null and b/lights/light-260.png differ
diff --git a/lights/light-280.png b/lights/light-280.png
new file mode 100644
index 0000000..9ed81e6
Binary files /dev/null and b/lights/light-280.png differ
diff --git a/lights/light-300.png b/lights/light-300.png
new file mode 100644
index 0000000..4c35301
Binary files /dev/null and b/lights/light-300.png differ
diff --git a/lights/light-320.png b/lights/light-320.png
new file mode 100644
index 0000000..c142c08
Binary files /dev/null and b/lights/light-320.png differ
diff --git a/lights/light-340.png b/lights/light-340.png
new file mode 100644
index 0000000..3e14e6b
Binary files /dev/null and b/lights/light-340.png differ
diff --git a/lights/light-40.png b/lights/light-40.png
new file mode 100644
index 0000000..4112187
Binary files /dev/null and b/lights/light-40.png differ
diff --git a/lights/light-60.png b/lights/light-60.png
new file mode 100644
index 0000000..0c63757
Binary files /dev/null and b/lights/light-60.png differ
diff --git a/lights/light-80.png b/lights/light-80.png
new file mode 100644
index 0000000..42509bf
Binary files /dev/null and b/lights/light-80.png differ
diff --git a/load_test.py b/load_test.py
new file mode 100644
index 0000000..461db8f
--- /dev/null
+++ b/load_test.py
@@ -0,0 +1,18 @@
+from locust import HttpUser, between, task
+
+from learn_sql_model.factories.hero import HeroFactory
+from learn_sql_model.models.hero import HeroCreate
+
+
+class QuickstartUser(HttpUser):
+ wait_time = between(1, 2)
+
+ @task
+ def hello_world(self):
+ self.client.get("/hero/1")
+ self.client.get("/heros/")
+
+ @task(3)
+ def create_hero(self):
+ hero = HeroFactory().build()
+ HeroCreate(**hero.dict()).post()
diff --git a/locustfile.py b/locustfile.py
new file mode 100644
index 0000000..4f4f551
--- /dev/null
+++ b/locustfile.py
@@ -0,0 +1,58 @@
+import random
+
+from locust import FastHttpUser, task
+
+from learn_sql_model.config import get_config
+from learn_sql_model.factories.hero import HeroFactory
+from learn_sql_model.models.hero import HeroCreate, HeroUpdate, Heros
+
+config = get_config()
+
+
+class QuickstartUser(FastHttpUser):
+ # wait_time = between(1, 2)
+ host = "http://localhost:5000"
+ # host = "https://waylonwalker.com"
+
+ def on_start(self):
+ self.client.verify = False
+
+ @task(6)
+ def get_a_hero(self):
+ # heros = Heros.list()
+ id = 1
+ # id = random.choice(heros.__root__).id
+
+ self.client.get(f"/hero/{id}")
+
+ # @task(2)
+ # def get_all_hero(self):
+ # self.client.get("/heros/")
+
+ @task
+ def create_hero(self):
+ hero = HeroFactory().build()
+ hero_create = HeroCreate(**hero.dict()).post()
+
+ self.client.post(
+ f"{config.api_client.url}/hero/",
+ json=hero_create.dict(),
+ )
+
+ @task(3)
+ def update_hero(self):
+ hero = HeroFactory().build()
+ hero_update = HeroUpdate(id=1, name=hero.name)
+
+ self.client.patch(
+ "/hero/",
+ json=hero_update.dict(exclude_none=True),
+ )
+
+ @task
+ def delete_hero(self):
+ heros = Heros.list()
+ id = random.choice(heros.__root__).id
+ self.client.delete(
+ f"/hero/{id}",
+ )
diff --git a/map.pkl b/map.pkl
new file mode 100644
index 0000000..e69de29
diff --git a/map.png b/map.png
new file mode 100644
index 0000000..04809ee
Binary files /dev/null and b/map.png differ
diff --git a/micro b/micro
new file mode 100644
index 0000000..a06df4a
Binary files /dev/null and b/micro differ
diff --git a/migrations/env.py b/migrations/env.py
index 4863934..27494c8 100644
--- a/migrations/env.py
+++ b/migrations/env.py
@@ -79,7 +79,7 @@ def run_migrations_online() -> None:
context.configure(
connection=connection,
target_metadata=target_metadata,
- render_as_batch=False,
+ render_as_batch=True,
version_table=f'{config.get_main_option("project")}_alembic_version',
)
diff --git a/notify.py b/notify.py
new file mode 100644
index 0000000..8c1178d
--- /dev/null
+++ b/notify.py
@@ -0,0 +1,37 @@
+# # Import smtplib for the actual sending function
+# import smtplib
+# # Import the email modules we'll need
+# from email.mime.text import MIMEText
+
+# # Open a plain text file for reading. For this example, assume that
+# # the text file contains only ASCII characters.
+# # with open(textfile, 'rb') as fp:
+# # # Create a text/plain message
+# # msg = MIMEText(fp.read())
+# msg = MIMEText("hello there", "plain", "utf-8")
+
+# # me == the sender's email address
+# # you == the recipient's email address
+# me = "waylon@waylonwalker.com"
+# you = "3195728809@msg.fi.google.com"
+# msg["Subject"] = "Python SMTP test"
+# msg["From"] = me
+# msg["To"] = you
+
+# # Send the message via our own SMTP server, but don't include the
+# # envelope header.
+# s = smtplib.SMTP("localhost")
+# s.sendmail(me, [you], msg.as_string())
+# s.quit()
+import requests
+
+requests.post(
+ "https://api.mailgun.net/v3/YOUR_DOMAIN_NAME/messages",
+ auth=("api", "YOUR_API_KEY"),
+ data={
+ "from": "Excited User ",
+ "to": ["bar@example.com", "YOU@YOUR_DOMAIN_NAME"],
+ "subject": "Hello",
+ "text": "Testing some Mailgun awesomness!",
+ },
+)
diff --git a/pet.png b/pet.png
new file mode 100644
index 0000000..b22dd02
Binary files /dev/null and b/pet.png differ
diff --git a/pyproject.toml b/pyproject.toml
index aba902f..53dbe66 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -31,7 +31,7 @@ dependencies = [
"engorgio",
"fastapi",
"httpx",
- "pydantic[dotenv]",
+ "pydantic<2.0.0",
"pyflyby",
"pyinstaller",
"rich",
diff --git a/rect.py b/rect.py
new file mode 100644
index 0000000..29def88
--- /dev/null
+++ b/rect.py
@@ -0,0 +1,23 @@
+import pygame
+
+pygame.init()
+
+screen = pygame.display.set_mode((500, 500))
+pygame.display.set_caption("draw a square")
+
+running = True
+
+while running:
+ for event in pygame.event.get():
+ if event.type == pygame.QUIT:
+ running = False
+
+ surface = pygame.Surface((500, 500))
+ surface.fill((255, 0, 0))
+
+ color = (0, 0, 255)
+ rect = (200, 200, 100, 100)
+ pygame.draw.rect(surface, color, rect)
+
+ screen.blit(surface, (0, 0))
+ pygame.display.flip()
diff --git a/templates/er_diagram.html b/templates/er_diagram.html
new file mode 100644
index 0000000..5dc1fed
--- /dev/null
+++ b/templates/er_diagram.html
@@ -0,0 +1,129 @@
+
+
+
+
+
+ ER Diagram
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tmp.sh b/tmp.sh
new file mode 100644
index 0000000..7f36aa4
--- /dev/null
+++ b/tmp.sh
@@ -0,0 +1,20 @@
+max="$1"
+date
+echo "url: $2
+rate: $max calls / second"
+START=$(date +%s);
+
+get () {
+ curl -s -v "$1" 2>&1 | tr '\r\n' '\\n' | awk -v date="$(date +'%r')" '{print $0"\n-----", date}' >> /tmp/perf-test.log
+}
+
+while true
+do
+ echo $(($(date +%s) - START)) | awk '{print int($1/60)":"int($1%60)}'
+ sleep 1
+
+ for i in `seq 1 $max`
+ do
+ get $2 &
+ done
+done
diff --git a/wyatt.py b/wyatt.py
new file mode 100644
index 0000000..d84db3e
--- /dev/null
+++ b/wyatt.py
@@ -0,0 +1,84 @@
+import random
+import sys
+
+# Initialize player attributes
+player = {
+ "name": input("Enter your character's name: "),
+ "health": 100,
+ "food": 100,
+ "x": 5,
+ "y": 5,
+ "day": 1,
+}
+
+# Define game resources
+resources = {
+ "food": 50,
+ "water": 50,
+}
+
+# Define game constants
+MAP_WIDTH, MAP_HEIGHT = 20, 10
+PLAYER_CHAR = "(o)"
+ENEMY_CHAR = "(?)"
+
+# Game loop
+while player["health"] > 0:
+ # Create the game map
+ game_map = [[" " for _ in range(MAP_WIDTH)] for _ in range(MAP_HEIGHT)]
+ game_map[player["y"]][player["x"]] = PLAYER_CHAR
+
+ # Place enemies randomly on the map
+ for _ in range(random.randint(1, 3)):
+ enemy_x = random.randint(0, MAP_WIDTH - 1)
+ enemy_y = random.randint(0, MAP_HEIGHT - 1)
+ game_map[enemy_y][enemy_x] = ENEMY_CHAR
+
+ # Print the game map
+ for row in game_map:
+ print("".join(row))
+
+ print(f"\nDay {player['day']}")
+ print(f"Name: {player['name']}")
+ print(f"Health: {player['health']} HP {'*' * player['health']}")
+ print(f"Food: {player['food']} Hunger {'*' * player['food']}")
+ print(f"Coordinates: ({player['x']}, {player['y']})")
+
+ # Player input for movement
+ move = input("Move (W/A/S/D): ").upper()
+
+ # Update player position based on input
+ if move == "W" and player["y"] > 0:
+ player["y"] -= 1
+ elif move == "S" and player["y"] < MAP_HEIGHT - 1:
+ player["y"] += 1
+ elif move == "A" and player["x"] > 0:
+ player["x"] -= 1
+ elif move == "D" and player["x"] < MAP_WIDTH - 1:
+ player["x"] += 1
+
+ # Consume resources
+ player["food"] -= random.randint(5, 15)
+
+ # Check if the player has enough resources
+ if player["food"] < 0:
+ player["food"] = 0
+ player["health"] -= 10
+
+ # Check if the player encounters an enemy
+ if game_map[player["y"]][player["x"]] == ENEMY_CHAR:
+ enemy_damage = random.randint(10, 30)
+ player["health"] -= enemy_damage
+ print(f"You encountered an enemy and took {enemy_damage} damage!")
+
+ # Rest for the day
+ player["day"] += 1
+
+ # Exit the game if health reaches zero
+ if player["health"] <= 0:
+ print("Game Over. You did not survive.")
+ break
+
+ input("Press Enter to continue to the next day...")
+
+sys.exit()