From 5cead1b20e10e0b986fd5a62a017c1e1fade0787 Mon Sep 17 00:00:00 2001
From: William Waites <william.waites@strath.ac.uk>
Date: Sat, 20 Jan 2024 17:48:20 +0000
Subject: [PATCH] initial implementation of imitation game

---
 imitation/imitation/__init__.py |   0
 imitation/imitation/client.py   |   0
 imitation/imitation/server.py   | 160 ++++++++++++++++++++++++++++++++
 imitation/setup.py              |  39 ++++++++
 4 files changed, 199 insertions(+)
 create mode 100644 imitation/imitation/__init__.py
 create mode 100644 imitation/imitation/client.py
 create mode 100644 imitation/imitation/server.py
 create mode 100644 imitation/setup.py

diff --git a/imitation/imitation/__init__.py b/imitation/imitation/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/imitation/imitation/client.py b/imitation/imitation/client.py
new file mode 100644
index 0000000..e69de29
diff --git a/imitation/imitation/server.py b/imitation/imitation/server.py
new file mode 100644
index 0000000..2405f70
--- /dev/null
+++ b/imitation/imitation/server.py
@@ -0,0 +1,160 @@
+import socketserver
+import argparse
+import logging
+import hashlib
+import time
+import random
+from queue import Queue
+import threading
+
+class Interlocutor(object):
+    queue = Queue()
+    def __init__(self, parent, sid):
+        self.request = parent.request
+        self.rfile = parent.rfile
+        self.wfile = parent.wfile
+        self.sid = sid
+        self.log = logging.getLogger(str(self))
+        self.n = 2
+        self.qs = Queue()
+        self.rs = Queue()
+        self.serv_colour = 95
+        self.peer_colour = 93
+
+    def ask(self, question):
+        self.qs.put(question)
+        return self.rs.get()
+
+    def read(self):
+        while True:
+            line = self.rfile.readline().strip().decode("utf-8")
+            if len(line) > 0:
+                return line
+
+    def write(self, *strings, colour=None):
+        if colour:
+            strings = [f"\033[31;{colour}m"] + list(strings) + ["\033[0m"]
+        for s in strings:
+            self.wfile.write(s.encode("utf-8"))
+
+class Interrogator(Interlocutor):
+    def __str__(self):
+        return f"I({self.sid})"
+
+    def connect(self, peer):
+        self.peer = peer
+        peer.connect(self)
+        self.log.info(f"connected to {self.peer}")
+
+    def handle(self):
+        self.log.info("connecting...")
+        self.write(f"""
+Welcome to the Imitation Game. Your role is "interrogator". You
+get to ask {self.n} questions of your interlocutor to determine if 
+they are a human or a machine. At the end of the session you
+will be asked which you think they are and why. Good luck!
+
+Please wait to be connected to an interlocutor...""", colour=self.serv_colour)
+
+        interlocutor = self.queue.get()
+        self.connect(interlocutor)
+
+        self.write(f" connected.\n\nYou may begin. Please ask a question.\n\n", colour=self.serv_colour)
+
+        for i in range(self.n):
+            question = self.read()
+            self.log.info(f"Q{i}: {question}")
+            response = self.peer.ask(question)
+            self.write("\n", response, "\n\n", colour=self.peer_colour)
+
+        self.write(f"""
+Thank you. Based on this interaction, do you believe that your
+interlocutor is a human? Please answer Yes or No.\n\n""", colour=self.serv_colour)
+
+        judgement = self.read()
+        self.log.info(f"Is a human? {judgement}")
+
+        self.write(f"""
+Why do you believe this?\n\n""", colour=self.serv_colour)
+
+        reason = self.read()
+        self.log.info(f"Why? {reason}")
+
+        self.write(f"""
+Thank you. Goodbye.
+""", colour=self.serv_colour)
+
+class Human(Interlocutor):
+    def __init__(self, *av, **kw):
+        super(Human, self).__init__(*av, **kw)
+        self.barrier = threading.Barrier(2)
+
+    def __str__(self):
+        return f"H({self.sid})"
+
+    def connect(self, peer):
+        self.barrier.wait()
+        self.peer = peer
+        self.log.info(f"connected to {self.peer}")
+
+    def handle(self):
+        self.log.info("connecting...")
+        self.write(f"""
+Welcome to the Imitation Game. Your role is HUMAN. You will be
+asked {self.n} questions by an interlocutor who is attempting 
+to find out if you are a human or a machine. Your task is to
+convince them that you are human. Good luck!
+
+Please wait to be connected to an interlocutor...""", colour=self.serv_colour)
+
+        self.queue.put(self)
+        self.barrier.wait()
+ 
+        self.write(f"connected.\n\nIt begins. Please wait for a question.\n", colour=self.serv_colour)
+
+        for i in range(self.n):
+            question = self.qs.get()
+            self.write("\n", question, "\n\n", colour=self.peer_colour)
+
+            response = self.read()
+            self.log.info(f"R{i}: {response}")
+            self.rs.put(response)
+
+        self.write("""
+That is all. Thank you for playing the Imitation Game.
+""", colour=self.serv_colour)
+
+class Handler(socketserver.StreamRequestHandler):
+    """
+    The request handler class for our server.
+
+    It is instantiated once per connection to the server, and must
+    override the handle() method to implement communication to the
+    client.
+    """
+
+    def handle(self):
+        raddr = self.request.getpeername()
+        sid = hashlib.sha3_224("{}:{}:{}".format(time.time(), raddr[0], raddr[1]).encode("utf-8")).hexdigest()[:8]
+        log = logging.getLogger(sid)
+
+        role = random.choice([Interrogator, Human])
+        h = role(self, sid)
+        h.handle()
+
+class ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
+    pass
+
+def cli():
+    parser = argparse.ArgumentParser("imitation_server")
+    parser.add_argument("-b", "--bind", default="0.0.0.0", help="hostname to bind (default localhost)")
+    parser.add_argument("-p", "--port", default=1950, type=int, help="port to bind (default 1950)")
+
+    args = parser.parse_args()
+
+    logging.basicConfig(level=logging.DEBUG, format='%(asctime)s %(name)s %(levelname)s: %(message)s')
+
+    random.seed(time.time())
+
+    with ThreadedTCPServer((args.bind, args.port), Handler) as server:
+        server.serve_forever()
diff --git a/imitation/setup.py b/imitation/setup.py
new file mode 100644
index 0000000..d866f5e
--- /dev/null
+++ b/imitation/setup.py
@@ -0,0 +1,39 @@
+#!/usr/bin/env python
+
+from setuptools import setup, find_packages
+
+setup(name='imitation',
+      version='0.1',
+      description='Turing Test',
+      author=['William Waites'],
+      author_email='william.waites@strath.ac.uk',
+      keywords=['AI'],
+      classifiers=[
+          # How mature is this project? Common values are
+          #   3 - Alpha
+          #   4 - Beta
+          #   5 - Production/Stable
+          'Development Status :: 3 - Alpha',
+
+          # Intended audience
+          'Intended Audience :: Education',
+          'Intended Audience :: Science/Research',
+
+          # License
+          #'License :: OSI Approved :: GNU General Public License (GPL)',
+          # Specify the Python versions you support here. In particular,
+          # ensure that you indicate whether you support Python 2, Python 3
+          # or both.
+          'Programming Language :: Python :: 3',
+      ],
+      license='GPLv3',
+      packages=find_packages(),
+      install_requires=[
+      ],
+      entry_points={
+         'console_scripts': [
+              'imitation = imitation.client:cli',
+              'imitation_server = imitation.server:cli',
+          ],
+      },
+)
-- 
GitLab