diff --git a/.gitignore b/.gitignore
index 60e1da6..8fe4e99 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
roms/
.venv/
.mypy_cache/
+states/
diff --git a/src/client.html b/src/client.html
new file mode 100644
index 0000000..ad3b84e
--- /dev/null
+++ b/src/client.html
@@ -0,0 +1,67 @@
+
+
+
+
+ Telecommande
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/emulator.py b/src/emulator.py
new file mode 100644
index 0000000..254fd11
--- /dev/null
+++ b/src/emulator.py
@@ -0,0 +1,67 @@
+import asyncio
+import logging
+
+import mgba.core
+import mgba.image
+import mgba.log
+import numpy as np
+import pyvirtualcam
+import websockets
+
+WIDTH = 240
+HEIGHT = 160
+URI: str = "ws://127.0.0.1:6789/"
+KEYMAP: dict[str, int] = {
+ "a": 0,
+ "b": 1,
+ "select": 2,
+ "start": 3,
+ "right": 4,
+ "left": 5,
+ "up": 6,
+ "down": 7,
+ "r": 8,
+ "l": 9,
+}
+
+core = mgba.core.load_path("roms/pokemon.gba")
+screen = mgba.image.Image(WIDTH, HEIGHT)
+core.set_video_buffer(screen)
+core.reset()
+mgba.log.silence()
+
+logging.basicConfig(level=logging.DEBUG)
+
+
+async def main():
+ with pyvirtualcam.Camera(width=WIDTH, height=HEIGHT, fps=60) as cam:
+ logging.debug(f"Using virtual camera: {cam.device}")
+ async with websockets.connect(URI) as websocket:
+ await websocket.send('{"auth":"password"}')
+ logging.debug(f"connected to: {websocket}")
+ test = core.save_raw_state()
+
+ while True:
+
+ if core.frame_counter % 30 == 0:
+ await websocket.send('{"action":"get"}')
+ response = await websocket.recv()
+ if response in KEYMAP:
+ key = KEYMAP[response]
+ core.set_keys(key)
+ logging.debug(f"pressing: {key}")
+
+ # with open("pokemon.state", "w") as f:
+ # f.write(test)
+ core.load_raw_state(test)
+ print(str(test))
+
+ core.run_frame()
+ core.clear_keys(*KEYMAP.values())
+
+ frame = np.array(screen.to_pil().convert("RGB"), np.uint8)
+ cam.send(frame)
+ cam.sleep_until_next_frame()
+
+
+asyncio.run(main())
diff --git a/src/server.py b/src/server.py
new file mode 100644
index 0000000..616a488
--- /dev/null
+++ b/src/server.py
@@ -0,0 +1,119 @@
+import asyncio
+import json
+import logging
+import time
+from typing import Any
+
+import websockets
+
+logging.basicConfig(level=logging.DEBUG)
+
+
+class User:
+ websocket: Any = None
+ last_message: float = time.time()
+ has_voted: bool = False
+
+ def register(self, websocket: Any):
+ self.websocket = websocket
+ USERS.add(self)
+ logging.debug(
+ f"user registered: {self}",
+ )
+
+ def unregister(self):
+ # self.websocket.close()
+ USERS.remove(self)
+ logging.debug(
+ f"user unregistered: {self}",
+ )
+
+ def __str__(self) -> str:
+ return f"{self.websocket.remote_address} ({self.websocket.id})"
+
+
+USERS: set[User] = set()
+EMULATOR: Any = None
+PASSWORD: str = "password"
+
+VOTES: dict[str, int] = {
+ "a": 0,
+ "b": 0,
+ "select": 0,
+ "start": 0,
+ "right": 0,
+ "left": 0,
+ "up": 0,
+ "down": 0,
+ "r": 0,
+ "l": 0,
+}
+
+
+def clear_votes():
+ for key in VOTES.keys():
+ VOTES[key] = 0
+ for user in USERS:
+ user.has_voted = False
+ logging.info(f"votes cleared: {VOTES}")
+
+
+def next_move():
+ if any(VOTES.values()):
+ return max(VOTES, key=VOTES.get)
+ else:
+ return "null"
+
+
+async def handler(websocket, path):
+ try:
+ global EMULATOR
+
+ # Register user
+ user = User()
+ user.register(websocket)
+
+ # Manage received messages
+ async for message in websocket:
+ data = json.loads(message)
+
+ if "auth" in data:
+ auth: str = data["auth"]
+ logging.debug(f"auth received: {auth}")
+ if not EMULATOR and auth == PASSWORD:
+ EMULATOR = websocket
+ user.unregister()
+ logging.debug(f"emulator authenticated: {EMULATOR}")
+
+ if "action" in data:
+ action: str = data["action"]
+
+ if action in VOTES:
+ VOTES[action] += 1
+ user.last_message = time.time()
+ user.has_voted = True
+ logging.debug(f"key received: {action} ({VOTES[action]}), from {user}")
+
+ elif action == "get":
+ if EMULATOR and websocket == EMULATOR:
+ move = next_move()
+ await websocket.send(move)
+ logging.info(f"vote sent: {move}")
+ clear_votes()
+ else:
+ logging.error(f"user is not EMULATOR: {user}")
+
+ else:
+ logging.error(f"unsupported action: {data}")
+ finally:
+ # Unregister user
+ user.unregister()
+
+
+async def main():
+ async with websockets.serve(handler, "localhost", 6789):
+ await asyncio.Future() # run forever
+
+
+if __name__ == "__main__":
+ asyncio.run(main())