From c840cd2e95b0b6fdfc0590fd0b0a37bc98bef9f1 Mon Sep 17 00:00:00 2001 From: Max Value Date: Sun, 14 Sep 2025 17:44:36 +0100 Subject: [PATCH] Init --- .gitignore | 2 + commissioner.py | 93 ++++++++++++++++++++++++++++++++++++++++++++ main.py | 63 ++++++++++++++++++++++++++++++ static/style.css | 12 ++++++ templates/index.html | 85 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 255 insertions(+) create mode 100644 .gitignore create mode 100644 commissioner.py create mode 100755 main.py create mode 100644 static/style.css create mode 100644 templates/index.html diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a230a78 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.venv/ +__pycache__/ diff --git a/commissioner.py b/commissioner.py new file mode 100644 index 0000000..19611a0 --- /dev/null +++ b/commissioner.py @@ -0,0 +1,93 @@ +import xml.etree.ElementTree as et +from ollama import Client +import re + +class commissioner(): + def __init__(self): + self.instability = 0 + self.log = [] + self.dream = "" + + self.client = Client(host='http://127.0.0.1:11434') + + self.gamespace = """ + + + 2 + 6 + playing + + + playing + + + playing + + This rule has no effect + If all players have the state 'stuck' or 'bust': the player with the largest hand with the state 'stuck' should have the state 'winner' + If a player has 0 'card' elements, deal them 2 random cards. + Initialized the gamespace + + """ + + def ratify(self, rule, lifespan="permanent"): + + root = et.fromstring(self.gamespace) + + new_rule = et.Element('rule') + new_rule.text = rule + new_rule.attrib = {"lifespan": lifespan} + + root.insert(-1, new_rule) + + self.gamespace = et.tostring(root, encoding="unicode") + + def observe(self): + root = et.fromstring(self.gamespace) + rules = root.findall("rule") + + self.instability = 0 + self.log = [] + + response = self.client.chat(model='deepseek-r1:14b', messages=[{ + 'role': 'user', + 'content': f""" + Below is some XML describing a card game.\n + The outer element is the "cardGame" element. Inside the game there are the "player" elements (denoting players), the "message" element and "rule" elements (denoting the rules of the game that should be applied). "rule" elements have the attribute "lifespan" describing when they should be removed from "cardGame". "player" elements contain cards but also the "state" element.\n + The state can either be "playing", "stuck", "bust" or "winner".\n + The "message" element contains a short description of the action taken. This should be replaced with a new message after each alteration you make to the gamespace.\n + Apply all rules to the XML. Return the resultant XML first, followed by your explaination of your actions.\n\n + Card game XML follows:\n + {self.gamespace} + """ + }]) + + think = re.search(r"[\n\D\d]*<\/think>", response.message.content) # get bounds of + body = response.message.content[think.span()[1]:] # get body by removing + think = re.sub(r"<\/*think>", "", think.group()) # get think by extracting and removing + xml = re.findall(r"(?<=```xml)[\d\D]*(?=```)", body) # get the xml portion + if len(xml) == 0: + self.instability = 999 + self.log.append("failed! no vaild XML produced") + return + + elif len(xml) > 1: + self.instability += 1 + self.log.append("multiple valid gamespaces... collapsing") + + # remove everying before and after and remove \n and \t + xml = re.sub(r"\\n|\\t", "", xml[-1]) + + try: + root = et.fromstring(xml) + if len(root.findall("rule")) == 0: # if there are no rules... + for rule in rules: + root.insert(-1, rule) # add old rules + self.instability += 2 # increce instability by 2 for each fixed rule (will be at least one) + self.log.append("rules dissapeared... attempting fix") + + self.gamespace = et.tostring(root, encoding="unicode") + + except et.ParseError: + self.instability = 999 # increce instability by 2 for each fixed rule (will be at least one) + self.log.append("failed! parser error") diff --git a/main.py b/main.py new file mode 100755 index 0000000..43d171a --- /dev/null +++ b/main.py @@ -0,0 +1,63 @@ +#!.venv/bin/python + +from commissioner import commissioner +import xml.etree.ElementTree as et +from flask import Flask, Response, render_template, request +from threading import Lock + +app = Flask(__name__) +c = commissioner() +lock = Lock() + +@app.route("/") +def home(): + return render_template("index.html", title = 'Test') + +@app.route("/api") +def get_gamespace(): + with lock: + return Response(c.gamespace, mimetype="text/xml") + +@app.route("/api/info") +def get_info(): + with lock: + return Response(f""" + {{ + 'instability':{c.instability}, + 'log':{c.log}, + 'think':{c.think} + }} + """, mimetype="text/json") + +@app.route("/api/ratify") +def ratify_rule(): + with lock: + rule = request.args.get("r") + if rule != "": + c.ratify(rule) + return "", 200 + else: + return "", 403 + +@app.route("/api/deal") +def deal_rule(): + with lock: + player = request.args.get("p") + if player != "": + c.ratify(f""" + Deal 'player' with name '{player}' one random card. + """, lifespan="deleteAfter") + c.observe() + + return "", 200 + else: + return "", 403 + +@app.route("/api/observe") +def observe_rule(): + with lock: + c.observe() + return "", 200 + +if __name__ == "__main__": + app.run(host='127.0.0.1', port=5000, debug=True) diff --git a/static/style.css b/static/style.css new file mode 100644 index 0000000..a2bbfad --- /dev/null +++ b/static/style.css @@ -0,0 +1,12 @@ +.card { + display: inline-block; + width: 64px; + height: 89px; + border: 1px solid black; + border-radius: 3.5px; + text-align: center; +} + +.suit { + font-weight: bold; +} diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..8b0e8a9 --- /dev/null +++ b/templates/index.html @@ -0,0 +1,85 @@ + + + Blackjack 2 + + + + +

Blackjack 2 test monitor

+

Current gamespace

+
+ + + +
+ + + -- 2.39.2