desktop support
This commit is contained in:
parent
b08eab62cc
commit
68ef7c1591
6
.dockerignore
Normal file
6
.dockerignore
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
app/.desktop_mode
|
||||||
|
__pycache__/**
|
||||||
|
app/requirements-*.txt
|
||||||
|
|
||||||
|
seagull
|
||||||
|
.git*
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,3 +1,4 @@
|
|||||||
.vscode/**
|
.vscode/**
|
||||||
scratch.ipynb
|
scratch.ipynb
|
||||||
.docker-login-credentials
|
.docker-login-credentials
|
||||||
|
seagull
|
31
Dockerfile
31
Dockerfile
@ -1,33 +1,32 @@
|
|||||||
FROM python:3.11-alpine AS base
|
FROM python:3.13-alpine AS builder
|
||||||
|
|
||||||
# install rust environment (for rant)
|
# install rust environment (for rant)
|
||||||
ENV RUSTUP_HOME=/usr/local/rustup CARGO_HOME=/usr/local/cargo PATH=/usr/local/cargo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin RUST_VERSION=1.61.0
|
ENV RUSTUP_HOME=/usr/local/rustup CARGO_HOME=/usr/local/cargo PATH=/usr/local/cargo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin RUST_VERSION=1.61.0
|
||||||
RUN apk add --no-cache rustup gcc musl-dev
|
RUN --mount=type=cache,target=/var/cache/apk apk add --update-cache rustup gcc musl-dev
|
||||||
RUN rustup-init -y --profile minimal
|
RUN rustup-init -y --profile minimal
|
||||||
|
|
||||||
# install rant
|
# install rant
|
||||||
RUN cargo install --color=never rant --version 4.0.0-alpha.33 --root / --features cli
|
RUN --mount=type=cache,target=/root/.cargo \
|
||||||
|
--mount=type=cache,target=/usr/local/cargo/git/db \
|
||||||
|
--mount=type=cache,target=/usr/local/cargo/registry \
|
||||||
|
cargo install --color=never rant --version 4.0.0-alpha.33 --root /opt/rant --features cli
|
||||||
|
|
||||||
# python prereqs
|
FROM python:3.13-alpine AS app
|
||||||
|
ENV RUSTUP_HOME=/usr/local/rustup CARGO_HOME=/usr/local/cargo PATH=/opt/rant/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin RUST_VERSION=1.61.0
|
||||||
|
COPY --from=builder /opt/rant /opt/rant
|
||||||
|
|
||||||
|
# installing app
|
||||||
COPY app/requirements.txt /app/requirements.txt
|
COPY app/requirements.txt /app/requirements.txt
|
||||||
RUN pip install -r /app/requirements.txt
|
RUN --mount=type=cache,target=/root/.cache/pip pip install -r /app/requirements.txt
|
||||||
|
COPY app /app
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
# cleanup
|
# wordlist
|
||||||
RUN cargo install cargo-cache --root /
|
|
||||||
RUN cargo cache --remove-dir all
|
|
||||||
RUN pip cache purge
|
|
||||||
RUN apk del rustup gcc musl-dev
|
|
||||||
|
|
||||||
FROM base AS app
|
|
||||||
RUN mkdir -p /lib/wordlist
|
RUN mkdir -p /lib/wordlist
|
||||||
COPY ext/imsky/wordlists /lib/wordlist
|
COPY ext/imsky/wordlists /lib/wordlist
|
||||||
RUN mkdir -p /app/rant
|
RUN mkdir -p /app/rant
|
||||||
RUN python /lib/wordlist/render.py -o rant /app/rant/wordlist.rant
|
RUN python /lib/wordlist/render.py -o rant /app/rant/wordlist.rant
|
||||||
|
|
||||||
# installing app
|
|
||||||
COPY app /app
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
CMD [ "python", "index.wsgi" ]
|
CMD [ "python", "index.wsgi" ]
|
||||||
EXPOSE 80
|
EXPOSE 80
|
||||||
|
|
||||||
|
47
app/desktop.py
Executable file
47
app/desktop.py
Executable file
@ -0,0 +1,47 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import os
|
||||||
|
import pathlib
|
||||||
|
import sys
|
||||||
|
import threading
|
||||||
|
import webview
|
||||||
|
|
||||||
|
import flask
|
||||||
|
|
||||||
|
from pylocal import core, desktop, dev, tick
|
||||||
|
|
||||||
|
core.desktop_mode = True
|
||||||
|
sig_exit = threading.Event()
|
||||||
|
|
||||||
|
argp = argparse.ArgumentParser("seagull")
|
||||||
|
argp.add_argument("-d", "--debug", action="store_true")
|
||||||
|
argo = argp.parse_args()
|
||||||
|
|
||||||
|
@core.app.route("/")
|
||||||
|
def index():
|
||||||
|
if not core.base_context_live:
|
||||||
|
core.render_base_context()
|
||||||
|
core.base_context["scripts"].insert(0, core.app.url_for("static", filename="js/desktop-structuredclone.js"))
|
||||||
|
core.base_context["scripts"].insert(1, core.app.url_for("static", filename="js/seagull-desktop.js"))
|
||||||
|
return flask.render_template("main_page.j2", **core.base_context)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
try:
|
||||||
|
if sys.platform.startswith("win"):
|
||||||
|
storage_dir = pathlib.Path(os.environ["APPDATA"]) / "seagull"
|
||||||
|
elif sys.platform.startswith("darwin"): # macos
|
||||||
|
storage_dir = pathlib.Path(os.environ["HOME"]) / "Library/Application Support/seagull"
|
||||||
|
else:
|
||||||
|
storage_dir = pathlib.Path(os.environ["HOME"]) / ".local/share/seagull"
|
||||||
|
desktop.path_storagedir = pathlib.Path(storage_dir)
|
||||||
|
|
||||||
|
if argo.debug:
|
||||||
|
desktop.api.debug_mode = True
|
||||||
|
storage_dir.mkdir(exist_ok=True, parents=True)
|
||||||
|
webview.create_window("Seagull Game", core.app, js_api=desktop.api)
|
||||||
|
webview.start(private_mode=False, storage_path=storage_dir.as_posix(), debug=True if argo.debug else False)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
core.log.info("Goodnight, moon ...")
|
||||||
|
sig_exit.set()
|
||||||
|
sys.exit(0)
|
10
app/index.wsgi
Normal file → Executable file
10
app/index.wsgi
Normal file → Executable file
@ -3,17 +3,22 @@
|
|||||||
import gevent.monkey
|
import gevent.monkey
|
||||||
gevent.monkey.patch_all()
|
gevent.monkey.patch_all()
|
||||||
|
|
||||||
|
import pathlib
|
||||||
import sys
|
import sys
|
||||||
|
import threading
|
||||||
|
|
||||||
import flask
|
import flask
|
||||||
from gevent.pywsgi import WSGIServer
|
from gevent.pywsgi import WSGIServer
|
||||||
|
|
||||||
from pylocal import core, tick
|
from pylocal import core, dev, tick
|
||||||
|
|
||||||
|
sig_exit = threading.Event()
|
||||||
|
|
||||||
@core.app.route("/")
|
@core.app.route("/")
|
||||||
def index():
|
def index():
|
||||||
if not core.base_context_live:
|
if not core.base_context_live:
|
||||||
core.render_base_context()
|
core.render_base_context()
|
||||||
|
core.base_context["scripts"].append(core.app.url_for("static", filename="js/seagull-web.js"))
|
||||||
return flask.render_template("main_page.j2", **core.base_context)
|
return flask.render_template("main_page.j2", **core.base_context)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
@ -22,5 +27,6 @@ if __name__ == "__main__":
|
|||||||
http_server = WSGIServer(('', 80), core.app)
|
http_server = WSGIServer(('', 80), core.app)
|
||||||
http_server.serve_forever()
|
http_server.serve_forever()
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
print("Goodnight, moon ...")
|
core.log.info("Goodnight, moon ...")
|
||||||
|
sig_exit.set()
|
||||||
sys.exit(0)
|
sys.exit(0)
|
@ -1,10 +1,24 @@
|
|||||||
|
import logging
|
||||||
import os
|
import os
|
||||||
|
import pathlib
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
import flask
|
import flask
|
||||||
import redis
|
|
||||||
|
|
||||||
app = flask.Flask("seagull-game", root_path="/app")
|
log = logging.getLogger()
|
||||||
|
pipe_stderr = logging.StreamHandler(sys.stderr)
|
||||||
|
pipe_stderr.setLevel(logging.DEBUG)
|
||||||
|
log.addHandler(pipe_stderr)
|
||||||
|
|
||||||
|
if getattr(sys, "frozen", False):
|
||||||
|
path_appdir = pathlib.Path(sys._MEIPASS)
|
||||||
|
else:
|
||||||
|
path_appdir = pathlib.Path.cwd()
|
||||||
|
log.critical(path_appdir)
|
||||||
|
|
||||||
|
desktop_mode = False
|
||||||
|
|
||||||
|
app = flask.Flask("seagull-game", root_path=path_appdir)
|
||||||
orig_url_for = app.url_for
|
orig_url_for = app.url_for
|
||||||
|
|
||||||
#REDIS_HOST="stub-implementation.example.net"
|
#REDIS_HOST="stub-implementation.example.net"
|
||||||
@ -16,9 +30,8 @@ orig_url_for = app.url_for
|
|||||||
def url_for_override(endpoint, *posargs, _anchor=None, _method=None, _scheme=None, _external=None, self=app, **values):
|
def url_for_override(endpoint, *posargs, _anchor=None, _method=None, _scheme=None, _external=None, self=app, **values):
|
||||||
if endpoint == "static":
|
if endpoint == "static":
|
||||||
# bandaid for #1
|
# bandaid for #1
|
||||||
if not os.path.exists("/app/static/" + values["filename"]):
|
if not os.path.exists(path_appdir / "static" / values["filename"]):
|
||||||
sys.stderr.write("WARN:: requested {0} from local file, but it doesn't exist in this container. Redirecting to CDN...\n".format(values["filename"]))
|
log.warning("requested {0} from local file, but it doesn't exist in this container. Redirecting to CDN...\n".format(values["filename"]))
|
||||||
sys.stderr.flush()
|
|
||||||
return "https://cdn.otl-hga.net/seagull/" + values["filename"]
|
return "https://cdn.otl-hga.net/seagull/" + values["filename"]
|
||||||
|
|
||||||
return orig_url_for(endpoint, *posargs, _anchor=_anchor, _method=_method, _scheme=_scheme, _external=_external, **values)
|
return orig_url_for(endpoint, *posargs, _anchor=_anchor, _method=_method, _scheme=_scheme, _external=_external, **values)
|
||||||
@ -51,5 +64,5 @@ def render_base_context():
|
|||||||
base_context_live = True
|
base_context_live = True
|
||||||
|
|
||||||
@app.route("/core/ping")
|
@app.route("/core/ping")
|
||||||
def aws_healthcheck_ping():
|
def healthcheck_ping():
|
||||||
return flask.Response("OK", content_type="text/plain")
|
return flask.Response("OK", content_type="text/plain")
|
32
app/pylocal/desktop.py
Normal file
32
app/pylocal/desktop.py
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import pathlib
|
||||||
|
|
||||||
|
from . import core
|
||||||
|
|
||||||
|
path_storagedir = pathlib.Path()
|
||||||
|
|
||||||
|
class JS_API:
|
||||||
|
debug_mode = False
|
||||||
|
|
||||||
|
def load_data(self, key):
|
||||||
|
if not (path_storagedir / key).exists():
|
||||||
|
return None
|
||||||
|
|
||||||
|
with open(path_storagedir / key) as fd_datafile:
|
||||||
|
try:
|
||||||
|
return fd_datafile.read()
|
||||||
|
except Exception as exc:
|
||||||
|
core.log.error(f"problem loading {key} (from {path_storagedir}): {exc}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def save_data(self, key, data):
|
||||||
|
with open(path_storagedir / key, "w") as fd_datafile:
|
||||||
|
try:
|
||||||
|
fd_datafile.write(data)
|
||||||
|
except Exception as exc:
|
||||||
|
core.log.error(f"problem saving {key} (to {path_storagedir}): {exc}")
|
||||||
|
|
||||||
|
def delete_data(self, key):
|
||||||
|
if (path_storagedir / key).exists():
|
||||||
|
(path_storagedir / key).unlink()
|
||||||
|
|
||||||
|
api = JS_API()
|
7
app/pylocal/dev.py
Normal file
7
app/pylocal/dev.py
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import flask
|
||||||
|
|
||||||
|
from . import core
|
||||||
|
|
||||||
|
@core.app.route("/dev/get-toolbox")
|
||||||
|
def dev_toolbox():
|
||||||
|
return flask.render_template("dev_toolbox.j2", ipaddr=flask.request.remote_addr, desktop=core.desktop_mode)
|
@ -7,7 +7,11 @@ import flask
|
|||||||
from . import core
|
from . import core
|
||||||
|
|
||||||
def generate_flavor_text():
|
def generate_flavor_text():
|
||||||
proc_rant = subprocess.run(["rant", "/app/rant/flavor.rant"], capture_output=True)
|
if core.desktop_mode:
|
||||||
|
rant_path = core.path_appdir / "opt/rant/bin/rant"
|
||||||
|
else:
|
||||||
|
rant_path = "rant" # rely on OS PATH
|
||||||
|
proc_rant = subprocess.run([rant_path, (core.path_appdir / "rant/flavor.rant").as_posix()], capture_output=True)
|
||||||
return proc_rant.stdout.decode()
|
return proc_rant.stdout.decode()
|
||||||
|
|
||||||
class TickEvent(object):
|
class TickEvent(object):
|
||||||
@ -36,11 +40,13 @@ def tick():
|
|||||||
result["event_type"] = random.choices(ticktypes, weights=tickweights)[0]
|
result["event_type"] = random.choices(ticktypes, weights=tickweights)[0]
|
||||||
|
|
||||||
match result["event_type"]:
|
match result["event_type"]:
|
||||||
|
case 0:
|
||||||
|
pass
|
||||||
case 1: # FLAVOR
|
case 1: # FLAVOR
|
||||||
result["log"] = generate_flavor_text()
|
result["log"] = generate_flavor_text()
|
||||||
case 10: # ENCHUMAN
|
case 10: # ENCHUMAN
|
||||||
result["items"] = [] # TODO: implement items
|
result["items"] = {} # TODO: implement items
|
||||||
case _:
|
case _:
|
||||||
print("undefined tick: {0}".format(result["event_type"]))
|
core.log.warning("undefined tick: {0}".format(result["event_type"]))
|
||||||
|
|
||||||
return flask.Response(json.dumps(result), status=200, content_type="application/json")
|
return flask.Response(json.dumps(result), status=200, content_type="application/json")
|
@ -18,9 +18,7 @@
|
|||||||
You have a polite conversation about birdly affairs. |
|
You have a polite conversation about birdly affairs. |
|
||||||
It scoffs and flies away.
|
It scoffs and flies away.
|
||||||
}
|
}
|
||||||
|
|
} |
|
||||||
You encounter a human and attempt to steal their `{
|
{
|
||||||
[pick: <wordlist/nouns/food>] |
|
A nearby {`[pick: <wordlist/nouns/birds>]|colony of `[pick: <wordlist/nouns/birds>]s} seems to be harassing a human.
|
||||||
[pick: <wordlist/nouns/fast_food>]
|
|
||||||
}.
|
|
||||||
}
|
}
|
1
app/requirements-build-desktop.txt
Normal file
1
app/requirements-build-desktop.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
pyinstaller>=6.14.2
|
3
app/requirements-desktop-linux.txt
Normal file
3
app/requirements-desktop-linux.txt
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
pyqt6>=6.9.1
|
||||||
|
pyqtwebengine>=5.15.7
|
||||||
|
pywebview[qt]>=5.4
|
1
app/requirements-desktop.txt
Normal file
1
app/requirements-desktop.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
pywebview>=5.4
|
@ -1,4 +1,4 @@
|
|||||||
Flask==2.2.2
|
Flask==3.1.1
|
||||||
gevent==22.10.2
|
gevent==25.5.1
|
||||||
hiredis>=1.0.0
|
hiredis>=1.0.0
|
||||||
redis==4.5.1
|
redis==6.2.0
|
2
app/templates/dev_toolbox.j2
Normal file
2
app/templates/dev_toolbox.j2
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
{% if not desktop %}IP: {{ipaddr}}<br />{% endif %}
|
||||||
|
<button id="dev-reset" onClick="reset_game()">Reset Game</button>
|
@ -12,8 +12,10 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<noscript>
|
<noscript>
|
||||||
|
<div style="background:yellow;border:2px red;">
|
||||||
<h1>This doesn't work without JavaScript.</h1><br />
|
<h1>This doesn't work without JavaScript.</h1><br />
|
||||||
<h2>You're probably using a browser extension or privacy tool that disables it.</h2>
|
<h2>You're probably using a browser extension or privacy tool that disables it.</h2>
|
||||||
|
</div>
|
||||||
</noscript>
|
</noscript>
|
||||||
<div id="root">
|
<div id="root">
|
||||||
|
|
||||||
@ -22,12 +24,28 @@
|
|||||||
<div id="side-seagull-name"><span id="lbl-seagull-name">Nameless</span> <a href="javascript:change_seagull_name()">✏️</a></div>
|
<div id="side-seagull-name"><span id="lbl-seagull-name">Nameless</span> <a href="javascript:change_seagull_name()">✏️</a></div>
|
||||||
<div id="side-seagull-name-editor"><input type="text" id="edt-seagull-name"> <a href="javascript:confirm_seagull_name()">✅</a><a href="javascript:cancel_seagull_name()">❌</a></div>
|
<div id="side-seagull-name-editor"><input type="text" id="edt-seagull-name"> <a href="javascript:confirm_seagull_name()">✅</a><a href="javascript:cancel_seagull_name()">❌</a></div>
|
||||||
<div id="side-seagull-stats">
|
<div id="side-seagull-stats">
|
||||||
<p id="side-seagull-lvl">Lv 1 LoadError</p>
|
<p id="side-seagull-lvl">Lv <span id="lbl-seagull-lvl">1</span> <span id="lbl-seagull-class">LoadError</span></p>
|
||||||
|
<p id="side-seagull-xp">XP: <span id="lbl-seagull-xp-current">0</span>/<span id="lbl-seagull-xp-next">0</span></p>
|
||||||
<p id="side-seagull-misc">
|
<p id="side-seagull-misc">
|
||||||
Colony: <span id="lbl-seagull-colony">1337</span><br />
|
Colony: <span id="lbl-seagull-colony">1337</span><br />
|
||||||
Shinies: <span id="lbl-seagull-shinies">420</span><br />
|
Shinies: <span id="lbl-seagull-shinies">420</span><br />
|
||||||
|
Food: <span id="lbl-seagull-food">69</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
<hr />
|
||||||
|
<div id="side-action-bar">
|
||||||
|
<p>Human encounters: <select id="menu-enc-human">
|
||||||
|
<option value="pause">Stop and ask</option>
|
||||||
|
<option value="steal-food">Steal food</option>
|
||||||
|
<option value="steal-shinies">Steal shiny things</option>
|
||||||
|
</select></p>
|
||||||
|
<p>Seagull encounters: <select id="menu-enc-seagull">
|
||||||
|
<option value="pause">Stop and ask</option>
|
||||||
|
<option value="recruit">Attempt recruiting</option>
|
||||||
|
<option value="steal-food">Steal food</option>
|
||||||
|
<option value="steal-shinies">Steal shiny things</option>
|
||||||
|
</select></p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="main-content">
|
<div id="main-content">
|
||||||
|
43
build-desktop.sh
Executable file
43
build-desktop.sh
Executable file
@ -0,0 +1,43 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
BUILD_DIR=${BUILD_DIR:-./build}
|
||||||
|
|
||||||
|
die () {
|
||||||
|
echo "$@" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
findcmd () {
|
||||||
|
command -v $1 || die "missing command: $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
findcmd cargo
|
||||||
|
findcmd python
|
||||||
|
findcmd rsync
|
||||||
|
|
||||||
|
srcdir=$(pwd)
|
||||||
|
mkdir -p $BUILD_DIR && cd $BUILD_DIR
|
||||||
|
rsync -rv $srcdir/ $BUILD_DIR/
|
||||||
|
|
||||||
|
# rant
|
||||||
|
mkdir -p opt/rant
|
||||||
|
cargo install rant --version 4.0.0-alpha.33 --root $BUILD_DIR/opt/rant --features cli
|
||||||
|
python $srcdir/ext/imsky/wordlists/render.py -o rant $BUILD_DIR/app/rant/wordlist.rant
|
||||||
|
|
||||||
|
# python venv
|
||||||
|
python -m venv pyvenv
|
||||||
|
source pyvenv/bin/activate
|
||||||
|
pip install -r app/requirements.txt
|
||||||
|
pip install -r app/requirements-build-desktop.txt
|
||||||
|
pip install -r app/requirements-desktop.txt
|
||||||
|
[[ $(uname -s) == "Linux" ]] && pip install -r app/requirements-desktop-linux.txt
|
||||||
|
#[[ $(uname -s) == "Darwin" ]] && pip install -r app/requirements-desktop-macos.txt
|
||||||
|
|
||||||
|
pyinstaller seagull.spec
|
||||||
|
deactivate
|
||||||
|
|
||||||
|
cd $srcdir
|
||||||
|
cp -fv $BUILD_DIR/dist/seagull $srcdir
|
||||||
|
rm -fr $BUILD_DIR
|
||||||
|
du -sh seagull
|
||||||
|
echo "You should be able to run ./seagull now"
|
@ -1 +1 @@
|
|||||||
Subproject commit cdda0e81d62151349c3a17679b5a0433eec60327
|
Subproject commit 3aab59e6844fe899a3fcc3949859efbc74977510
|
43
seagull.spec
Normal file
43
seagull.spec
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
# -*- mode: python ; coding: utf-8 -*-
|
||||||
|
|
||||||
|
|
||||||
|
a = Analysis(
|
||||||
|
['app/desktop.py'],
|
||||||
|
pathex=[],
|
||||||
|
binaries=[],
|
||||||
|
datas=[
|
||||||
|
('app/templates', './templates'),
|
||||||
|
('static', './static'),
|
||||||
|
('app/rant', './rant'),
|
||||||
|
('opt', './opt')
|
||||||
|
],
|
||||||
|
hiddenimports=[],
|
||||||
|
hookspath=[],
|
||||||
|
hooksconfig={},
|
||||||
|
runtime_hooks=[],
|
||||||
|
excludes=[],
|
||||||
|
noarchive=False,
|
||||||
|
optimize=0,
|
||||||
|
)
|
||||||
|
pyz = PYZ(a.pure)
|
||||||
|
|
||||||
|
exe = EXE(
|
||||||
|
pyz,
|
||||||
|
a.scripts,
|
||||||
|
a.binaries,
|
||||||
|
a.datas,
|
||||||
|
[],
|
||||||
|
name='seagull',
|
||||||
|
debug=False,
|
||||||
|
bootloader_ignore_signals=False,
|
||||||
|
strip=False,
|
||||||
|
upx=True,
|
||||||
|
upx_exclude=[],
|
||||||
|
runtime_tmpdir=None,
|
||||||
|
console=True,
|
||||||
|
disable_windowed_traceback=False,
|
||||||
|
argv_emulation=False,
|
||||||
|
target_arch=None,
|
||||||
|
codesign_identity=None,
|
||||||
|
entitlements_file=None,
|
||||||
|
)
|
3
static/js/desktop-structuredclone.js
Normal file
3
static/js/desktop-structuredclone.js
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
function structuredClone(val) {
|
||||||
|
return JSON.parse(JSON.stringify(val));
|
||||||
|
}
|
34
static/js/seagull-desktop.js
Normal file
34
static/js/seagull-desktop.js
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
var desktop_mode = true;
|
||||||
|
|
||||||
|
async function prepare_gamestate() {
|
||||||
|
var gamestate_loaded = null;
|
||||||
|
try {
|
||||||
|
gamestate_loaded = await window.pywebview.api.load_data("gamestate");
|
||||||
|
} catch (exc) {
|
||||||
|
console.error("no gamestate");
|
||||||
|
gamestate_loaded = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gamestate_loaded == null) {
|
||||||
|
record_log("Welcome to Seagull Game! We haven't found a save in your app data, so we're starting a new game!");
|
||||||
|
gamestate = structuredClone(gamestate_default);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.log(gamestate_loaded);
|
||||||
|
gamestate = JSON.parse(gamestate_loaded);
|
||||||
|
record_log("Welcome back! Game loaded.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function save_game() {
|
||||||
|
window.pywebview.api.save_data("gamestate", JSON.stringify(gamestate));
|
||||||
|
record_log("Game saved.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var tick_meter_running = true;
|
||||||
|
|
||||||
|
function reset_game() {
|
||||||
|
tick_meter_running = false;
|
||||||
|
window.pywebview.api.delete_data("gamestate");
|
||||||
|
window.location.reload();
|
||||||
|
}
|
27
static/js/seagull-web.js
Normal file
27
static/js/seagull-web.js
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
var desktop_mode = false;
|
||||||
|
|
||||||
|
function prepare_gamestate() {
|
||||||
|
var gamestate_loaded = window.localStorage.getItem("gamestate");
|
||||||
|
|
||||||
|
if (gamestate_loaded == null) {
|
||||||
|
record_log("Welcome to Seagull Game! We haven't found a save in your browser storage, so we're starting a new game!");
|
||||||
|
gamestate = structuredClone(gamestate_default);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
gamestate = JSON.parse(gamestate_loaded);
|
||||||
|
record_log("Welcome back! Game loaded.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function save_game() {
|
||||||
|
window.localStorage.setItem("gamestate", JSON.stringify(gamestate));
|
||||||
|
record_log("Game saved.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var tick_meter_running = true;
|
||||||
|
|
||||||
|
function reset_game() {
|
||||||
|
tick_meter_running = false;
|
||||||
|
window.localStorage.removeItem("gamestate");
|
||||||
|
window.location.reload();
|
||||||
|
}
|
@ -14,22 +14,14 @@ const gamestate_default = {
|
|||||||
"name": "Nameless",
|
"name": "Nameless",
|
||||||
"level": 1,
|
"level": 1,
|
||||||
"shinies": 0,
|
"shinies": 0,
|
||||||
"autosave": 35
|
"colony": 1,
|
||||||
|
"food": 0,
|
||||||
|
"autosave": 35,
|
||||||
|
"story_beat": 0,
|
||||||
|
"xp": 0,
|
||||||
|
"xp_next": 50
|
||||||
};
|
};
|
||||||
|
|
||||||
function prepare_gamestate() {
|
|
||||||
var gamestate_loaded = window.localStorage.getItem("gamestate");
|
|
||||||
|
|
||||||
if (gamestate_loaded == null) {
|
|
||||||
record_log("Welcome to Seagull Game! We haven't found a save in your browser storage, so we're starting a new game!");
|
|
||||||
gamestate = structuredClone(gamestate_default);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
gamestate = JSON.parse(gamestate_loaded);
|
|
||||||
record_log("Welcome back! Game loaded.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var bool_log_alt = false
|
var bool_log_alt = false
|
||||||
function record_log(text) {
|
function record_log(text) {
|
||||||
const div_logrow = document.createElement("div");
|
const div_logrow = document.createElement("div");
|
||||||
@ -50,18 +42,40 @@ function record_log(text) {
|
|||||||
page_elements["div_log"].append(div_logrow);
|
page_elements["div_log"].append(div_logrow);
|
||||||
}
|
}
|
||||||
|
|
||||||
function save_game() {
|
function update_ui() {
|
||||||
window.localStorage.setItem("gamestate", JSON.stringify(gamestate));
|
page_elements["lbl_name"].innerHTML = gamestate["name"];
|
||||||
record_log("Game saved.");
|
page_elements["lbl_tick"].innerHTML = gamestate["tick"];
|
||||||
|
page_elements["lbl_colony"].innerHTML = gamestate["colony"];
|
||||||
|
page_elements["lbl_shinies"].innerHTML = gamestate["shinies"];
|
||||||
|
page_elements["lbl_food"].innerHTML = gamestate["food"];
|
||||||
}
|
}
|
||||||
|
|
||||||
var tick_meter_running = true;
|
var dev_toolbox_open = false;
|
||||||
|
function dev_toolbox(open) {
|
||||||
|
if (open != dev_toolbox_open) {
|
||||||
|
if (open) {
|
||||||
|
var div_toolbox = document.createElement("div");
|
||||||
|
page_elements["div_toolbox"] = div_toolbox;
|
||||||
|
div_toolbox.setAttribute("id", "dev_toolbox");
|
||||||
|
fetch("/dev/get-toolbox")
|
||||||
|
.then((response) => response.text())
|
||||||
|
.then((resp) => {div_toolbox.innerHTML = resp})
|
||||||
|
page_elements["div_sidebar"].appendChild(div_toolbox);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
var div_toolbox = page_elements["div_toolbox"];
|
||||||
|
page_elements["div_sidebar"].removeChild(div_toolbox);
|
||||||
|
div_toolbox.remove()
|
||||||
|
delete page_elements["div_toolbox"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dev_toolbox_open = open;
|
||||||
|
}
|
||||||
|
|
||||||
async function game_tick() {
|
async function game_tick() {
|
||||||
gamestate["tick"] += 1;
|
gamestate["tick"] += 1;
|
||||||
ticks_since_last_save += 1;
|
ticks_since_last_save += 1;
|
||||||
// temp
|
|
||||||
page_elements["lbl_colony"].innerHTML = ticks_since_last_save;
|
|
||||||
page_elements["lbl_tick"].innerHTML = gamestate["tick"];
|
page_elements["lbl_tick"].innerHTML = gamestate["tick"];
|
||||||
var tickdata = await fetch("/tick")
|
var tickdata = await fetch("/tick")
|
||||||
.then(res => {
|
.then(res => {
|
||||||
@ -95,22 +109,42 @@ async function game_tick() {
|
|||||||
save_game();
|
save_game();
|
||||||
ticks_since_last_save = 0;
|
ticks_since_last_save = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
update_ui();
|
||||||
}
|
}
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", function (ev) {
|
var start_event = "";
|
||||||
|
var target = null;
|
||||||
|
if (desktop_mode) {
|
||||||
|
// pywebview's native JS is nerfed in a few places and needs the additional python API
|
||||||
|
// which gets loaded after initial DOM via injections
|
||||||
|
start_event = "pywebviewready";
|
||||||
|
target = window;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// in web mode, browsers are expected to have working local storage
|
||||||
|
start_event = "DOMContentLoaded";
|
||||||
|
target = document;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
target.addEventListener(start_event, function (ev) {
|
||||||
page_elements["div_log"] = document.querySelector("#main-log");
|
page_elements["div_log"] = document.querySelector("#main-log");
|
||||||
|
page_elements["div_sidebar"] = document.querySelector("#main-sidebar");
|
||||||
page_elements["div_name"] = document.querySelector("#side-seagull-name");
|
page_elements["div_name"] = document.querySelector("#side-seagull-name");
|
||||||
page_elements["div_name_editor"] = document.querySelector("#side-seagull-name-editor");
|
page_elements["div_name_editor"] = document.querySelector("#side-seagull-name-editor");
|
||||||
page_elements["lbl_name"] = document.querySelector("#lbl-seagull-name");
|
page_elements["lbl_name"] = document.querySelector("#lbl-seagull-name");
|
||||||
page_elements["lbl_colony"] = document.querySelector("#lbl-seagull-colony");
|
page_elements["lbl_colony"] = document.querySelector("#lbl-seagull-colony");
|
||||||
|
page_elements["lbl_shinies"] = document.querySelector("#lbl-seagull-shinies");
|
||||||
|
page_elements["lbl_food"] = document.querySelector("#lbl-seagull-food");
|
||||||
page_elements["edt_name"] = document.querySelector("#edt-seagull-name");
|
page_elements["edt_name"] = document.querySelector("#edt-seagull-name");
|
||||||
page_elements["lbl_tick"] = document.querySelector("#main-day-counter");
|
page_elements["lbl_tick"] = document.querySelector("#main-day-counter");
|
||||||
|
page_elements["lbl_xp"] = document.querySelector("#lbl-seagull-xp-current");
|
||||||
|
page_elements["lbl_xp_next"] = document.querySelector("#lbl-seagull-xp-next");
|
||||||
|
|
||||||
prepare_gamestate();
|
prepare_gamestate().then(update_ui());
|
||||||
|
|
||||||
record_log("seagull game ver. " + ver_string);
|
record_log("seagull game ver. " + ver_string);
|
||||||
page_elements["lbl_name"].innerHTML = gamestate["name"];
|
|
||||||
page_elements["lbl_tick"].innerHTML = gamestate["tick"];
|
|
||||||
|
|
||||||
const interval = setInterval(() => {
|
const interval = setInterval(() => {
|
||||||
if (tick_meter_running) { game_tick(); }
|
if (tick_meter_running) { game_tick(); }
|
||||||
|
Loading…
x
Reference in New Issue
Block a user