initial commit

This commit is contained in:
ekouz 2024-07-05 09:22:41 +02:00
commit 7f01b847d8
22 changed files with 941 additions and 0 deletions

10
api/Dockerfile Normal file
View file

@ -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

74
docker-compose.yml Normal file
View file

@ -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

View file

@ -0,0 +1,11 @@
FROM node
WORKDIR /app
COPY package.json /app
RUN npm install
COPY . /app
CMD npm run bot

62
message_receiver/bot.js Normal file
View file

@ -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}`);
}

195
message_receiver/package-lock.json generated Normal file
View file

@ -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
}
}
}
}
}

View file

@ -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"
}
}

3
postgresql Normal file
View file

@ -0,0 +1,3 @@
POSTGRES_DB=bot
POSTGRES_USER=user
POSTGRES_PASSWORD=password

2
rabbitmq Normal file
View file

@ -0,0 +1,2 @@
RABBITMQ_DEFAULT_USER=user
RABBITMQ_DEFAULT_PASS=pass

11
tetris/Dockerfile Normal file
View file

@ -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

4
tetris/requirements.txt Normal file
View file

@ -0,0 +1,4 @@
pika
fastapi[all]
uvicorn
sse_starlette

11
tetris/static/< Normal file
View file

@ -0,0 +1,11 @@
<script>
const eventSource = new EventSource('/stream');
eventSource.onmessage = (event) => {
console.log(`Message reçu: ${event.data}`);
};
eventSource.onerror = (error) => {
console.error('Erreur SSE:', error);
};
</script>

51
tetris/static/game.css Normal file
View file

@ -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;
}

58
tetris/static/game.js Normal file
View file

@ -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);
};

31
tetris/static/index.html Normal file
View file

@ -0,0 +1,31 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>My Website</title>
<link rel="stylesheet" href="./game.css">
<link rel="icon" href="./favicon.ico" type="image/x-icon">
</head>
<body>
<div id="container">
<table id="grid">
</table>
<script src=/static/game.js></script>
<iframe name="dummyframe" id="dummyframe" style="display: none;"></iframe>
<form action="/play" method="post" target="dummyframe">
<input type="submit" name="action" value="r left">
<input type="submit" name="action" value="left">
<input type="submit" name="action" value="down">
<input type="submit" name="action" value="right">
<input type="submit" name="action" value="r right">
</br>
<input type="submit" name="action" value="restart">
</form>
</div>
</body>
</html>
<script>
</script>

65
tetris/tetris/__main__.py Normal file
View file

@ -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()

224
tetris/tetris/game.py Normal file
View file

@ -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
}
}

80
tetris/tetris/web.py Normal file
View file

@ -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')

3
twitch Normal file
View file

@ -0,0 +1,3 @@
TWITCH_ACCESSTOKEN=<insert>
TWITCH_USERNAME=Ekouz
TWITCH_CHANNEL=ekouz

10
workers/Dockerfile Normal file
View file

@ -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

1
workers/requirements.txt Normal file
View file

@ -0,0 +1 @@
twitchio

View file

View file

@ -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())