--- /dev/null
+.venv/
+__pycache__/
--- /dev/null
+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 = """
+ <cardGame>
+ <player name='max'>
+ <card suit='diamonds' color='red'>2</card>
+ <card suit='spades' color='black'>6</card>
+ <state>playing</state>
+ </player>
+ <player name='will'>
+ <state>playing</state>
+ </player>
+ <player name='rebecca'>
+ <state>playing</state>
+ </player>
+ <rule lifespan='permanent'>This rule has no effect</rule>
+ <rule lifespan='permanent'>If all players have the state 'stuck' or 'bust': the player with the largest hand with the state 'stuck' should have the state 'winner'</rule>
+ <rule lifespan='deleteAfter'>If a player has 0 'card' elements, deal them 2 random cards.</rule>
+ <message>Initialized the gamespace</message>
+ </cardGame>
+ """
+
+ 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"<think>[\n\D\d]*<\/think>", response.message.content) # get bounds of <think>
+ body = response.message.content[think.span()[1]:] # get body by removing <think>
+ think = re.sub(r"<\/*think>", "", think.group()) # get think by extracting and removing <think>
+ 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 <cardGame> 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")
--- /dev/null
+#!.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)
--- /dev/null
+.card {
+ display: inline-block;
+ width: 64px;
+ height: 89px;
+ border: 1px solid black;
+ border-radius: 3.5px;
+ text-align: center;
+}
+
+.suit {
+ font-weight: bold;
+}
--- /dev/null
+<html>
+ <head>
+ <title>Blackjack 2</title>
+ <link rel="stylesheet" href="static/style.css">
+ <script>
+var parser;
+parser = new DOMParser();
+
+function deal(playerName) {
+ console.log("dealing card...")
+ fetch(`/api/deal?p=${playerName}`);
+}
+
+function parseXML(data) {
+ gamespace = parser.parseFromString(data,"text/xml");
+
+ var players = "";
+ for (var e of gamespace.getElementsByTagName("player")) {
+ var cardList = "";
+ for (var c of e.getElementsByTagName("card")) {
+ cardList += `
+ <div class='card'>
+ <span class='value'>
+ ${c.childNodes[0].nodeValue}
+ </span>
+ <br>
+ of
+ <br>
+ <span class='suit' style='color:${c.getAttribute("color")};'>
+ ${c.getAttribute("suit")}
+ </span>
+ </div>`;
+ }
+ players += `
+ <h4>${e.getAttribute("name")}</h4>
+ <p><b>State:</b> ${e.getElementsByTagName("state")[0].childNodes[0].nodeValue}</p>
+ <div>${cardList}</div>
+ `;
+ }
+
+ var rulesList = "";
+ for (var e of gamespace.getElementsByTagName("rule")) {
+ rulesList += `<li>${e.childNodes[0].nodeValue}</li>`;
+ }
+
+ return `
+ <p><b>Message from the Ump:</b> ${gamespace.getElementsByTagName("message")[0].childNodes[0].nodeValue}</p>
+ <h3>Players</h3>
+ ${players}
+ <br>
+ <h3>List of rules</h3>
+ <ol>${rulesList}<ol>
+ `
+}
+
+function update() {
+ fetch("/api")
+ .then(response => response.text())
+ .then(text => parseXML(text))
+ .then(html => {
+ document.getElementById("gamespace").innerHTML = html;
+ });
+}
+
+function rule() {
+ var newRule = document.getElementById("rule").value;
+ fetch(`/api/ratify?r=${newRule}`);
+}
+
+function observe() {
+ fetch("/api/observe");
+}
+ </script>
+ </head>
+ <body onload="setInterval(update, 1000);">
+ <h1>Blackjack 2 test monitor</h1>
+ <h2>Current gamespace</h2>
+ <div id="gamespace"></div>
+ <input type="button" value="Deal Will" onclick="deal('will');"></input>
+ <input type="button" value="Deal Rebecca" onclick="deal('rebecca');"></input>
+ <input type="button" value="Observe gamespace" onclick="observe();"></input>
+ <br>
+ <input type="text" id="rule"></input><input type="button" value="Apply rule" onclick="rule();"></input>
+ </body>
+</html>