initial commit
This commit is contained in:
commit
7f01b847d8
10
api/Dockerfile
Normal file
10
api/Dockerfile
Normal 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
74
docker-compose.yml
Normal 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
|
||||
11
message_receiver/Dockerfile
Normal file
11
message_receiver/Dockerfile
Normal 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
62
message_receiver/bot.js
Normal 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
195
message_receiver/package-lock.json
generated
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
16
message_receiver/package.json
Normal file
16
message_receiver/package.json
Normal 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
3
postgresql
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
POSTGRES_DB=bot
|
||||
POSTGRES_USER=user
|
||||
POSTGRES_PASSWORD=password
|
||||
2
rabbitmq
Normal file
2
rabbitmq
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
RABBITMQ_DEFAULT_USER=user
|
||||
RABBITMQ_DEFAULT_PASS=pass
|
||||
11
tetris/Dockerfile
Normal file
11
tetris/Dockerfile
Normal 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
4
tetris/requirements.txt
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
pika
|
||||
fastapi[all]
|
||||
uvicorn
|
||||
sse_starlette
|
||||
11
tetris/static/<
Normal file
11
tetris/static/<
Normal 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
51
tetris/static/game.css
Normal 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
58
tetris/static/game.js
Normal 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
31
tetris/static/index.html
Normal 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
65
tetris/tetris/__main__.py
Normal 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
224
tetris/tetris/game.py
Normal 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
80
tetris/tetris/web.py
Normal 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
3
twitch
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
TWITCH_ACCESSTOKEN=<insert>
|
||||
TWITCH_USERNAME=Ekouz
|
||||
TWITCH_CHANNEL=ekouz
|
||||
10
workers/Dockerfile
Normal file
10
workers/Dockerfile
Normal 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
1
workers/requirements.txt
Normal file
|
|
@ -0,0 +1 @@
|
|||
twitchio
|
||||
0
workers/workers/__init__.py
Normal file
0
workers/workers/__init__.py
Normal file
19
workers/workers/__main__.py
Normal file
19
workers/workers/__main__.py
Normal 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())
|
||||
Loading…
Reference in a new issue