From 7f01b847d8b4d96996113d0bd651eed2e9642063 Mon Sep 17 00:00:00 2001 From: ekouz Date: Fri, 5 Jul 2024 09:22:41 +0200 Subject: [PATCH] initial commit --- api/Dockerfile | 10 ++ docker-compose.yml | 74 ++++++++++ message_receiver/Dockerfile | 11 ++ message_receiver/bot.js | 62 ++++++++ message_receiver/package-lock.json | 195 +++++++++++++++++++++++++ message_receiver/package.json | 16 +++ postgresql | 3 + rabbitmq | 2 + tetris/Dockerfile | 11 ++ tetris/requirements.txt | 4 + tetris/static/< | 11 ++ tetris/static/game.css | 51 +++++++ tetris/static/game.js | 58 ++++++++ tetris/static/index.html | 31 ++++ tetris/tetris/__main__.py | 65 +++++++++ tetris/tetris/game.py | 224 +++++++++++++++++++++++++++++ tetris/tetris/web.py | 80 +++++++++++ twitch | 3 + workers/Dockerfile | 10 ++ workers/requirements.txt | 1 + workers/workers/__init__.py | 0 workers/workers/__main__.py | 19 +++ 22 files changed, 941 insertions(+) create mode 100644 api/Dockerfile create mode 100644 docker-compose.yml create mode 100644 message_receiver/Dockerfile create mode 100644 message_receiver/bot.js create mode 100644 message_receiver/package-lock.json create mode 100644 message_receiver/package.json create mode 100644 postgresql create mode 100644 rabbitmq create mode 100644 tetris/Dockerfile create mode 100644 tetris/requirements.txt create mode 100644 tetris/static/< create mode 100644 tetris/static/game.css create mode 100644 tetris/static/game.js create mode 100644 tetris/static/index.html create mode 100644 tetris/tetris/__main__.py create mode 100644 tetris/tetris/game.py create mode 100644 tetris/tetris/web.py create mode 100644 twitch create mode 100644 workers/Dockerfile create mode 100644 workers/requirements.txt create mode 100644 workers/workers/__init__.py create mode 100644 workers/workers/__main__.py diff --git a/api/Dockerfile b/api/Dockerfile new file mode 100644 index 0000000..98fb459 --- /dev/null +++ b/api/Dockerfile @@ -0,0 +1,10 @@ +FROM python:3 + +COPY requirements.txt /tmp/ + +RUN pip install -r /tmp/requirements.txt + +COPY . /code +WORKDIR /code + +CMD python -m workers diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..86d37b6 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,74 @@ +version: "3" + + +networks: + proxy: + external: + name: proxy + + +volumes: + db: + + +services: + postgresql: + image: postgres + env_file: + - postgresql + volumes: + - db:/var/lib/postgresql/data + + rabbitmq: + image: rabbitmq:3.11.5 + networks: + - default + - proxy + env_file: + - rabbitmq + + message_receiver: + image: twitchbot/message_receveiver + build: + context: message_receiver + environment: + RABBITMQ_HOST: rabbitmq + RABBITMQ_QUEUE: raw_messages + env_file: + - rabbitmq + - twitch + depends_on: + - rabbitmq + + tetris: &tetris + image: twichbot/tetris + build: + context: tetris + volumes: + - ./tetris:/app + environment: + RABBITMQ_HOST: rabbitmq + RABBITMQ_QUEUE_MESSAGES: raw_messages + RABBITMQ_EXCHANGE_TETRIS: tetris + env_file: + - rabbitmq + depends_on: + - rabbitmq + - message_receiver + + tetris-api: + <<: *tetris + command: uvicorn tetris.web:app --reload --host=0.0.0.0 + environment: + VIRTUAL_HOST: tetris.de-codeur.com + LETSENCRYPT_HOST: tetris.de-codeur.com + VIRTUAL_PORT: 8000 + RABBITMQ_HOST: rabbitmq + RABBITMQ_QUEUE_MESSAGES: raw_messages + RABBITMQ_EXCHANGE_TETRIS: tetris + RABBITMQ_QUEUE_TETRIS_UI: tetris-ui + ports: + - 8000:8000 + networks: + - default + - proxy diff --git a/message_receiver/Dockerfile b/message_receiver/Dockerfile new file mode 100644 index 0000000..79ab362 --- /dev/null +++ b/message_receiver/Dockerfile @@ -0,0 +1,11 @@ +FROM node + + +WORKDIR /app + +COPY package.json /app +RUN npm install + +COPY . /app + +CMD npm run bot diff --git a/message_receiver/bot.js b/message_receiver/bot.js new file mode 100644 index 0000000..e54ee72 --- /dev/null +++ b/message_receiver/bot.js @@ -0,0 +1,62 @@ +const tmi = require('tmi.js'); +var amqp = require('amqplib/callback_api'); + +twitch_token = process.env.TWITCH_ACCESSTOKEN +twitch_username = process.env.TWITCH_USERNAME +twitch_channel = process.env.TWITCH_CHANNEL + +rabbitmq_user = process.env.RABBITMQ_DEFAULT_USER +rabbitmq_pass = process.env.RABBITMQ_DEFAULT_PASS +rabbitmq_host = process.env.RABBITMQ_HOST +rabbitmq_queue = process.env.RABBITMQ_QUEUE + +// Define twitch configuration options +const opts = { + identity: { + username: twitch_username, + password: twitch_token, + }, + channels: [twitch_channel,] +}; +console.log(opts); + + +amqp_url = `amqp://${rabbitmq_user}:${rabbitmq_pass}@${rabbitmq_host}`; +console.log(amqp_url); +amqp.connect(amqp_url, function(error0, connection) { + if (error0) { + throw error0; + } + connection.createChannel(function(error1, channel) { + if (error1) { + throw error1; + } + + var queue = rabbitmq_queue; + + channel.assertQueue(queue, { + durable: false + }); + + // Create a clieng with our options + const client = new tmi.client(opts); + // Connect to Twitch: + client.connect(); + client.on('message', onMessageHandler); + client.on('connected', onConnectedHandler); + + function onMessageHandler (target, context, msg, self) { + console.log('msg:', msg) + channel.sendToQueue(queue, Buffer.from(JSON.stringify({ + "msg": msg, + "context": context, + "target": target, + }))); + } + + }); +}); +// Called every time the bot connects to Twitch chat +function onConnectedHandler (addr, port) { + console.log(`* Connected to ${addr}:${port}`); +} diff --git a/message_receiver/package-lock.json b/message_receiver/package-lock.json new file mode 100644 index 0000000..f0ef7d5 --- /dev/null +++ b/message_receiver/package-lock.json @@ -0,0 +1,195 @@ +{ + "name": "dice", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "dice", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "amqplib": "^0.10.3", + "tmi.js": "^1.8.5" + } + }, + "node_modules/@acuminous/bitsyntax": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@acuminous/bitsyntax/-/bitsyntax-0.1.2.tgz", + "integrity": "sha512-29lUK80d1muEQqiUsSo+3A0yP6CdspgC95EnKBMi22Xlwt79i/En4Vr67+cXhU+cZjbti3TgGGC5wy1stIywVQ==", + "dependencies": { + "buffer-more-ints": "~1.0.0", + "debug": "^4.3.4", + "safe-buffer": "~5.1.2" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/amqplib": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/amqplib/-/amqplib-0.10.3.tgz", + "integrity": "sha512-UHmuSa7n8vVW/a5HGh2nFPqAEr8+cD4dEZ6u9GjP91nHfr1a54RyAKyra7Sb5NH7NBKOUlyQSMXIp0qAixKexw==", + "dependencies": { + "@acuminous/bitsyntax": "^0.1.2", + "buffer-more-ints": "~1.0.0", + "readable-stream": "1.x >=1.1.9", + "url-parse": "~1.5.10" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/buffer-more-ints": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-more-ints/-/buffer-more-ints-1.0.0.tgz", + "integrity": "sha512-EMetuGFz5SLsT0QTnXzINh4Ksr+oo4i+UGTXEshiGCQWnsgSs7ZhJ8fzlwQ+OzEMs0MpDAMr1hxnblp5a4vcHg==" + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==" + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" + }, + "node_modules/readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==" + }, + "node_modules/tmi.js": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/tmi.js/-/tmi.js-1.8.5.tgz", + "integrity": "sha512-A9qrydfe1e0VWM9MViVhhxVgvLpnk7pFShVUWePsSTtoi+A1X+Zjdoa7OJd7/YsgHXGj3GkNEvnWop/1WwZuew==", + "dependencies": { + "node-fetch": "^2.6.1", + "ws": "^8.2.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/ws": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.12.0.tgz", + "integrity": "sha512-kU62emKIdKVeEIOIKVegvqpXMSTAMLJozpHZaJNDYqBjzlSYXQGviYwN1osDLJ9av68qHd4a2oSjd7yD4pacig==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + } + } +} diff --git a/message_receiver/package.json b/message_receiver/package.json new file mode 100644 index 0000000..d7d98bd --- /dev/null +++ b/message_receiver/package.json @@ -0,0 +1,16 @@ +{ + "name": "twitch_chat_injector", + "version": "1.0.0", + "description": "", + "main": "bot.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "bot": "node bot.js" + }, + "author": "", + "license": "ISC", + "dependencies": { + "amqplib": "^0.10.3", + "tmi.js": "^1.8.5" + } +} diff --git a/postgresql b/postgresql new file mode 100644 index 0000000..c88de83 --- /dev/null +++ b/postgresql @@ -0,0 +1,3 @@ +POSTGRES_DB=bot +POSTGRES_USER=user +POSTGRES_PASSWORD=password diff --git a/rabbitmq b/rabbitmq new file mode 100644 index 0000000..e5a7268 --- /dev/null +++ b/rabbitmq @@ -0,0 +1,2 @@ +RABBITMQ_DEFAULT_USER=user +RABBITMQ_DEFAULT_PASS=pass diff --git a/tetris/Dockerfile b/tetris/Dockerfile new file mode 100644 index 0000000..c9aab9c --- /dev/null +++ b/tetris/Dockerfile @@ -0,0 +1,11 @@ +FROM python:3 + +WORKDIR /app +EXPOSE 8001 + +COPY requirements.txt /app +RUN pip install -U -r requirements.txt + +COPY . /app + +CMD python -m tetris diff --git a/tetris/requirements.txt b/tetris/requirements.txt new file mode 100644 index 0000000..15f251b --- /dev/null +++ b/tetris/requirements.txt @@ -0,0 +1,4 @@ +pika +fastapi[all] +uvicorn +sse_starlette diff --git a/tetris/static/< b/tetris/static/< new file mode 100644 index 0000000..9920030 --- /dev/null +++ b/tetris/static/< @@ -0,0 +1,11 @@ + diff --git a/tetris/static/game.css b/tetris/static/game.css new file mode 100644 index 0000000..1c1904a --- /dev/null +++ b/tetris/static/game.css @@ -0,0 +1,51 @@ +table { + border-collpase: collpase; + border: 2px solid; +} + +.cell { + width: 20px; + height: 20px; + border: 2px solid; + border-color: black; + box-sizing: border-box; +} + +td :not(.empty_cell) { + box-shadow: 3px 3px 0px #2c2e38, 5px 5px 0px #5c5f72; + margin: 20px + padding: 20px +} + +#container { + border-color: black + border: 1px solid; +} + +.empty_cell { + background-color: rgba(0, 0, 0, 0); + border: 2px; +} + +.I_tetrimino { + background-color: FireBrick; +} +.O_tetrimino { + background-color: blue; +} +.T_tetrimino { + background-color: aquamarine; +} +.L_tetrimino { + background-color: cadetBlue; +} +.J_tetrimino { + background-color: BlueViolet; +} +.Z_tetrimino { + background-color: darkkhaki +} +.S_tetrimino { + background-color: Coral; +} + diff --git a/tetris/static/game.js b/tetris/static/game.js new file mode 100644 index 0000000..1afdd9b --- /dev/null +++ b/tetris/static/game.js @@ -0,0 +1,58 @@ +const eventSource = new EventSource("/stream"); + + +color_mapping = [ + "empty_cell", + "I_tetrimino", + "O_tetrimino", + "T_tetrimino", + "L_tetrimino", + "J_tetrimino", + "Z_tetrimino", + "S_tetrimino", +] + + +function draw_grid(data) { + const table = document.getElementById("grid"); + table.innerHTML = ""; + for (row in data.grid) { + let tr = document.createElement("tr"); + for (cell in data.grid[row]) { + let td = document.createElement("td"); + td.id = `${cell}-${row}`; + td.classList.add("cell"); + td.classList.add(color_mapping[data.grid[row][cell]]); + tr.appendChild(td); + } + table.appendChild(tr); + } + let x = parseInt(data.current_tetrimino.position[0]); + let y = parseInt(data.current_tetrimino.position[1]); + console.log(data.current_tetrimino.shape); + for (i in data.current_tetrimino.shape) { + console.log("x", x,i); + for (j in data.current_tetrimino.shape[i]) { + console.log("y", y, j); + if (data.current_tetrimino.shape[i][j] == 0) { + continue; + } + let current_x = x + parseInt(j); + let current_y = data.size.height - y + parseInt(i); + console.log(`${current_x}-${current_y}`); + let cell = document.getElementById(`${current_x}-${current_y}`) + cell.classList.remove("empty_cell"); + cell.classList.add(color_mapping[data.current_tetrimino.value]); + } + } +} + +eventSource.onmessage = (event) => { + draw_grid(JSON.parse(event.data)); +}; + +eventSource.onerror = (error) => { + console.error('Erreur SSE:', error); +}; + + diff --git a/tetris/static/index.html b/tetris/static/index.html new file mode 100644 index 0000000..86c2044 --- /dev/null +++ b/tetris/static/index.html @@ -0,0 +1,31 @@ + + + + + + + My Website + + + + +
+ +
+ + +
+ + + + + +
+ +
+
+ + + + diff --git a/tetris/tetris/__main__.py b/tetris/tetris/__main__.py new file mode 100644 index 0000000..481a6fd --- /dev/null +++ b/tetris/tetris/__main__.py @@ -0,0 +1,65 @@ +import json +import os +import pika + +from tetris.game import Game + + +rabbitmq_queue_message = os.environ['RABBITMQ_QUEUE_MESSAGES'] +rabbitmq_exchange_tetris = os.environ['RABBITMQ_EXCHANGE_TETRIS'] + + +def get_channel(): + connection = pika.BlockingConnection( + pika.ConnectionParameters( + host=os.environ['RABBITMQ_HOST'], + credentials=pika.PlainCredentials( + os.environ['RABBITMQ_DEFAULT_USER'], + os.environ['RABBITMQ_DEFAULT_PASS'], + ) + ) + ) + channel = connection.channel() + channel.exchange_declare(rabbitmq_exchange_tetris, exchange_type="direct") + channel.queue_declare(queue=rabbitmq_queue_message) + #channel.queue_declare(queue=rabbitmq_queue_tetris) + return channel + + +GAME = Game() + + +def dispatch_message(channel, method, properties, body): + body = json.loads(body) + message = body["msg"] + if message == '!start': + GAME.restart() + elif message == "!mleft": + GAME.move_left() + elif message == "!mdown": + GAME.move_down() + elif message == "!mright": + GAME.move_right() + elif message == "!rleft": + GAME.rotate_left() + elif message == "!rright": + GAME.rotate_right() + else: + print("not a command") + GAME.display() + GAME.move() + publish(channel, "donno", GAME.serialize()) + + +def publish(channel, routing, msg): + channel.basic_publish(rabbitmq_exchange_tetris, routing, json.dumps(msg)) + + +def main(): + channel = get_channel() + channel.basic_consume(on_message_callback=dispatch_message, queue=rabbitmq_queue_message, auto_ack=True) + channel.start_consuming() + + +if __name__ == '__main__': + main() diff --git a/tetris/tetris/game.py b/tetris/tetris/game.py new file mode 100644 index 0000000..e0cf11b --- /dev/null +++ b/tetris/tetris/game.py @@ -0,0 +1,224 @@ +import random +from copy import deepcopy + + +class Tetrimino: + value = 0 + rotation = 0 + shapes = [] + + def rotate_left(self): + self.rotate(-1) + + def rotate_right(self): + self.rotate(1) + + def rotate(self, sense): + old_width = self.size[0] + new_index = (self.rotation + sense) % len(self.shapes) + self.rotation = new_index + return self.size[0] > old_width + + @classmethod + def get_random_tetrimino(cls): + return random.choice(cls.__subclasses__())() + + def __repr__(self): + return str(self.shape) + + @property + def size(self): + return (len(self.shape[0]), len(self.shape)) + + @property + def shape(self): + return self.shapes[self.rotation] + + + +class I_Tetrimino(Tetrimino): + value = 1 + shapes = [ + [[1, 1, 1, 1]], + [[1], + [1], + [1], + [1], + [1]] + ] + + +class O_Tetrimino(Tetrimino): + value = 2 + shapes = [[[2, 2], + [2, 2]]] + +class T_Tetrimino(Tetrimino): + value = 3 + shapes = [ + [[3, 3, 3], + [0, 3, 0]], + [[0, 3], + [3, 3], + [0, 3]], + [[0, 3, 0], + [3, 3, 3]], + [[3, 0], + [3, 3], + [3, 0]] + ] + +class L_Tetriminos(Tetrimino): + value = 4 + shapes = [ + [[4, 4, 4], + [4, 0, 0]], + [[4, 4], + [0, 4], + [0, 4]], + [[0, 0, 4], + [4, 4, 4]], + [[4, 0], + [4, 0], + [4, 4]] + ] + +class J_Tetriminos(Tetrimino): + value = 5 + shapes = [ + [[5, 5, 5], + [0, 0, 5]], + [[0, 5], + [0, 5], + [5, 5]], + [[5, 0, 0], + [5, 5, 5]], + [[5, 5], + [5, 0], + [5, 0]] + ] + + +class Z_Tetriminos(Tetrimino): + value = 6 + shapes = [ + [[6, 6, 0], + [0, 6, 6]], + [[0, 6], + [6, 6], + [6, 0]], + ] + + + +class S_Tetriminos(Tetrimino): + value = 7 + shapes = [ + [[0, 7, 7], + [7, 7, 0]], + [[7, 0], + [7, 7], + [0, 7]], + ] + + +class Game: + def __init__(self): + self._width = 0 + self._heighth = 0 + self.grid = self.init_grid() + self.current_tetrimino = Tetrimino.get_random_tetrimino() + self.current_position = [self.width // 2, self.height - 1] + + + def restart(self): + self.grid = self.init_grid() + self.current_tetrimino = Tetrimino.get_random_tetrimino() + self.current_position = [self.width // 2, self.height - 1] + + def init_grid(self, width=10, height=20): + self._width = width + self._height = height + array = [] + for _ in range(height): + array.append([0] * width) + return array + + def display(self): + print(self.current_tetrimino, self.current_tetrimino.size) + print(self.current_position) + grid = deepcopy(self.grid) + for i, part in enumerate(self.current_tetrimino.shape): + for j, value in enumerate(part): + grid[self.current_position[1] - i][self.current_position[0] + j] = value + for line in reversed(grid): + print(line) + print(self.is_blocked()) + + def rotate_left(self): + self.current_tetrimino.rotate_left() + self.current_position[0] = min(self.current_position[0], self.width - self.current_tetrimino.size[0]) + + def rotate_right(self): + self.current_tetrimino.rotate_right() + self.current_position[0] = min(self.current_position[0], self.width - self.current_tetrimino.size[0]) + + def move_left(self): + self.current_position[0] -= 1 + self.current_position[0] = max(self.current_position[0], 0) + + def move_right(self): + self.current_position[0] += 1 + self.current_position[0] = min(self.current_position[0], self.width - self.current_tetrimino.size[0]) + + def move_down(self): + while not self.is_blocked(): + self.current_position[1] -= 1 + self.add_tetrimino_to_grid() + self.new_tetrimino() + + + def move(self): + self.current_position[1] -= 1 + if self.is_blocked(): + self.add_tetrimino_to_grid() + self.new_tetrimino() + + def add_tetrimino_to_grid(self): + for i, part in enumerate(self.current_tetrimino.shape): + for j, value in enumerate(part): + if value: + self.grid[self.current_position[1] - i][self.current_position[0] + j] = value + + def new_tetrimino(self): + self.current_tetrimino = Tetrimino.get_random_tetrimino() + self.current_position = [self.width // 2, self.height - 1] + + def is_blocked(self): + for i, part in enumerate(self.current_tetrimino.shape): + for j, value in enumerate(part): + if value != 0 and self.grid[self.current_position[1] - i - 1][self.current_position[0] + j] != 0: + return True + return self.current_position[1] - self.current_tetrimino.size[1] < 0 + + @property + def width(self): + return self._width + + @property + def height(self): + return self._height + + def serialize(self): + return { + "grid": self.grid[::-1], + "size": { + "width": self.width, + "height": self.height, + }, + "current_tetrimino": { + "position": self.current_position, + "shape": self.current_tetrimino.shape, + "value": self.current_tetrimino.value + } + } diff --git a/tetris/tetris/web.py b/tetris/tetris/web.py new file mode 100644 index 0000000..be499de --- /dev/null +++ b/tetris/tetris/web.py @@ -0,0 +1,80 @@ +import os +import json +import pika +import asyncio + +from sse_starlette.sse import EventSourceResponse +from fastapi import FastAPI, Request +from fastapi.staticfiles import StaticFiles +from fastapi.middleware.cors import CORSMiddleware + + +app = FastAPI() +app.add_middleware( + CORSMiddleware, + allow_origins=["*"], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + +app.mount("/static", StaticFiles(directory="static"), name="static") + +#pika +connection = pika.BlockingConnection( + pika.ConnectionParameters( + host=os.environ['RABBITMQ_HOST'], + credentials=pika.PlainCredentials( + os.environ['RABBITMQ_DEFAULT_USER'], + os.environ['RABBITMQ_DEFAULT_PASS'], + ) + ) +) + + +@app.post('/play') +async def play(request: Request): + action = await request.body() + action = action.decode('utf-8').split("=")[1] + print(action) + body = {"msg": ""} + if action == "left": + body["msg"] = "!mleft" + elif action == "down": + body["msg"] = "!mdown" + elif action == "right": + body["msg"] = "!mright" + elif action == "r+left": + body["msg"] = "!rleft" + elif action == "r+right": + body["msg"] = "!rright" + elif action == "restart": + body["msg"] = "!start" + channel = connection.channel() + channel.basic_publish("", "raw_messages", json.dumps(body)) + + + + +@app.get("/stream") +async def stream(request: Request): + rabbitmq_queue = os.environ['RABBITMQ_QUEUE_TETRIS_UI'] + channel = connection.channel() + result = channel.queue_declare(queue=rabbitmq_queue) + channel.queue_bind(exchange=os.environ["RABBITMQ_EXCHANGE_TETRIS"], queue=rabbitmq_queue, routing_key="donno") + channel.basic_qos(prefetch_count=1) + + async def event_generator(): + while True: + if await request.is_disconnected(): + break + + method_frame, header_frame, body = channel.basic_get(rabbitmq_queue) + if method_frame: + print("message sent") + yield { + "data": body.decode('utf-8') + } + channel.basic_ack(method_frame.delivery_tag) + await asyncio.sleep(0.1) + return EventSourceResponse(event_generator(), media_type='text/event-stream') diff --git a/twitch b/twitch new file mode 100644 index 0000000..cc97ce9 --- /dev/null +++ b/twitch @@ -0,0 +1,3 @@ +TWITCH_ACCESSTOKEN= +TWITCH_USERNAME=Ekouz +TWITCH_CHANNEL=ekouz diff --git a/workers/Dockerfile b/workers/Dockerfile new file mode 100644 index 0000000..98fb459 --- /dev/null +++ b/workers/Dockerfile @@ -0,0 +1,10 @@ +FROM python:3 + +COPY requirements.txt /tmp/ + +RUN pip install -r /tmp/requirements.txt + +COPY . /code +WORKDIR /code + +CMD python -m workers diff --git a/workers/requirements.txt b/workers/requirements.txt new file mode 100644 index 0000000..ecb911a --- /dev/null +++ b/workers/requirements.txt @@ -0,0 +1 @@ +twitchio diff --git a/workers/workers/__init__.py b/workers/workers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/workers/workers/__main__.py b/workers/workers/__main__.py new file mode 100644 index 0000000..a1a7281 --- /dev/null +++ b/workers/workers/__main__.py @@ -0,0 +1,19 @@ +import twitchio +from twitchio.ext import pubsub + +BOT_NICK = 'Ekouz' +OAUTH_TOKEN = 'oauth:poc2uc91z5b49kowag0kjbn5zxxto7' +CHANNEL = 'ekouz' + + +client = twitchio.Client(token=OAUTH_TOKEN) +client.pubsub = pubsub.PubSubPool(client) + + +async def main(): + topics = [ + pubsub. + + +if __name__ == '__main__': + client.loop.run_until_complete(main())