mirror of
https://github.com/evgen-app/chess_rpg_backend.git
synced 2024-11-25 11:04:03 +03:00
Merge remote-tracking branch 'origin/dev' into stable
This commit is contained in:
commit
470645e460
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -1,6 +1,6 @@
|
||||||
.idea
|
.idea
|
||||||
static/
|
static/
|
||||||
media/
|
media/uploads/
|
||||||
|
|
||||||
# Byte-compiled / optimized / DLL files
|
# Byte-compiled / optimized / DLL files
|
||||||
__pycache__/
|
__pycache__/
|
||||||
|
|
19
README.md
19
README.md
|
@ -1,15 +1,28 @@
|
||||||
# chess_rpg_backend
|
# chess_rpg_backend
|
||||||
Backend for chess rpg game
|
DEV branch for backend for chess rpg game
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
##### dev server for up to date endpoints(web socket not provided)
|
||||||
|
|
||||||
|
- https://dev.akarpov.ru
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
### installation
|
### installation
|
||||||
```shell
|
```shell
|
||||||
$ python3 manage.py makemigrations & python3 manage.py migrate
|
$ python3 manage.py makemigrations & python3 manage.py migrate
|
||||||
|
$ python3 manage.py loaddata media/dump_data/hero_model_fixture.json
|
||||||
$ docker run -p 6379:6379 -d redis:5
|
$ docker run -p 6379:6379 -d redis:5
|
||||||
```
|
```
|
||||||
|
|
||||||
### run
|
### dev run
|
||||||
```shell
|
```shell
|
||||||
$ python3 manage.py runserver 0.0.0.0:8000
|
$ python3 manage.py runserver 0.0.0.0:8000
|
||||||
|
```
|
||||||
|
|
||||||
|
### prod run
|
||||||
|
```shell
|
||||||
|
$ daphne -b 0.0.0.0 -p 8000 chess_backend.asgi:application
|
||||||
```
|
```
|
||||||
|
|
||||||
### Описание команд сокетов
|
### Описание команд сокетов
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
# This will make sure the app is always imported when
|
||||||
|
# Django starts so that shared_task will use this app.
|
||||||
|
from .celery import app as celery_app
|
||||||
|
|
||||||
|
__all__ = ("celery_app",)
|
21
chess_backend/celery.py
Normal file
21
chess_backend/celery.py
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import os
|
||||||
|
|
||||||
|
from celery import Celery
|
||||||
|
|
||||||
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "chess_backend.settings")
|
||||||
|
|
||||||
|
app = Celery("chess_backend")
|
||||||
|
|
||||||
|
# Using a string here means the worker doesn't have to serialize
|
||||||
|
# the configuration object to child processes.
|
||||||
|
# - namespace='CELERY' means all celery-related configuration keys
|
||||||
|
# should have a `CELERY_` prefix.
|
||||||
|
app.config_from_object("django.conf:settings", namespace="CELERY")
|
||||||
|
|
||||||
|
# Load task modules from all registered Django apps.
|
||||||
|
app.autodiscover_tasks()
|
||||||
|
|
||||||
|
|
||||||
|
@app.task(bind=True, ignore_result=True)
|
||||||
|
def debug_task(self):
|
||||||
|
print(f"Request: {self.request!r}")
|
|
@ -6,15 +6,24 @@ BASE_DIR = Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
# SECURITY WARNING: keep the secret key used in production secret!
|
# SECURITY WARNING: keep the secret key used in production secret!
|
||||||
SECRET_KEY = "django-insecure-%_8sy196w4hzo9^cp9(@r=i+amh47r4mxfhq_(ok&=c(@%bhmk"
|
SECRET_KEY = "django-insecure-%_8sy196w4hzo9^cp9(@r=i+amh47r4mxfhq_(ok&=c(@%bhmk"
|
||||||
TOKEN_EXP = 2678400 # 31 day
|
|
||||||
DEBUG = True
|
DEBUG = True
|
||||||
|
if DEBUG:
|
||||||
|
TOKEN_EXP = 31536000 # 1 year
|
||||||
|
AUTH_EXP = 31536000 # 1 year
|
||||||
|
else:
|
||||||
|
TOKEN_EXP = 2678400 # 1 month
|
||||||
|
AUTH_EXP = 3600 # 1 hour
|
||||||
|
|
||||||
ALLOWED_HOSTS = []
|
ALLOWED_HOSTS = []
|
||||||
|
|
||||||
|
if DEBUG:
|
||||||
|
ALLOWED_HOSTS = ["*"]
|
||||||
|
|
||||||
INSTALLED_APPS = [
|
INSTALLED_APPS = [
|
||||||
"django.contrib.sessions",
|
"django.contrib.sessions",
|
||||||
"django.contrib.auth",
|
"django.contrib.auth",
|
||||||
"django.contrib.contenttypes",
|
"django.contrib.contenttypes",
|
||||||
|
"django.contrib.staticfiles",
|
||||||
"django.contrib.messages",
|
"django.contrib.messages",
|
||||||
# Packages
|
# Packages
|
||||||
"rest_framework",
|
"rest_framework",
|
||||||
|
@ -24,9 +33,22 @@ INSTALLED_APPS = [
|
||||||
"room",
|
"room",
|
||||||
]
|
]
|
||||||
|
|
||||||
if DEBUG:
|
|
||||||
INSTALLED_APPS.append("django.contrib.staticfiles")
|
TEMPLATES = [
|
||||||
INSTALLED_APPS.append("drf_yasg")
|
{
|
||||||
|
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
||||||
|
"DIRS": [],
|
||||||
|
"APP_DIRS": True,
|
||||||
|
"OPTIONS": {
|
||||||
|
"context_processors": [
|
||||||
|
"django.template.context_processors.debug",
|
||||||
|
"django.template.context_processors.request",
|
||||||
|
"django.contrib.auth.context_processors.auth",
|
||||||
|
"django.contrib.messages.context_processors.messages",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
|
@ -102,3 +124,9 @@ LOGGING = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Celery Configuration Options
|
||||||
|
CELERY_TIMEZONE = "Europe/Moscow"
|
||||||
|
CELERY_TASK_TRACK_STARTED = True
|
||||||
|
CELERY_TASK_TIME_LIMIT = 30 * 60
|
||||||
|
CELERY_BROKER_URL = 'redis://localhost:6379/0'
|
||||||
|
|
|
@ -1,46 +1,9 @@
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.conf.urls.static import static
|
from django.conf.urls.static import static
|
||||||
from django.template.defaulttags import url
|
|
||||||
from django.urls import path, include, re_path
|
from django.urls import path, include, re_path
|
||||||
|
|
||||||
# openapi schema
|
urlpatterns = (
|
||||||
from rest_framework import permissions
|
[path("api/", include("game.urls"))]
|
||||||
from drf_yasg.views import get_schema_view
|
+ static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||||
from drf_yasg import openapi
|
+ static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
||||||
|
|
||||||
|
|
||||||
schema_view = get_schema_view(
|
|
||||||
openapi.Info(
|
|
||||||
title="Snippets API",
|
|
||||||
default_version="v1",
|
|
||||||
description="Test description",
|
|
||||||
terms_of_service="https://www.google.com/policies/terms/",
|
|
||||||
contact=openapi.Contact(email="contact@snippets.local"),
|
|
||||||
license=openapi.License(name="BSD License"),
|
|
||||||
),
|
|
||||||
public=True,
|
|
||||||
permission_classes=(permissions.AllowAny,),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
urlpatterns = [path("api/", include("game.urls"))] + static(
|
|
||||||
settings.MEDIA_URL, document_root=settings.MEDIA_ROOT
|
|
||||||
)
|
|
||||||
|
|
||||||
if settings.DEBUG:
|
|
||||||
urlpatterns += [
|
|
||||||
re_path(
|
|
||||||
r"^swagger(?P<format>\.json|\.yaml)$",
|
|
||||||
schema_view.without_ui(cache_timeout=0),
|
|
||||||
name="schema-json",
|
|
||||||
),
|
|
||||||
re_path(
|
|
||||||
r"^swagger/$",
|
|
||||||
schema_view.with_ui("swagger", cache_timeout=0),
|
|
||||||
name="schema-swagger-ui",
|
|
||||||
),
|
|
||||||
re_path(
|
|
||||||
r"^redoc/$",
|
|
||||||
schema_view.with_ui("redoc", cache_timeout=0),
|
|
||||||
name="schema-redoc",
|
|
||||||
),
|
|
||||||
] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
|
||||||
|
|
48
common/debug.py
Normal file
48
common/debug.py
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
from common.generators import gen_ton
|
||||||
|
from game.models import Player, Deck, Hero
|
||||||
|
from room.services.room_create import sync_create_room
|
||||||
|
|
||||||
|
|
||||||
|
def _check_players_score(players):
|
||||||
|
for player in players:
|
||||||
|
for player2 in players:
|
||||||
|
if player != player2:
|
||||||
|
s_min = players[player] * 0.95
|
||||||
|
s_max = players[player] * 1.05
|
||||||
|
if s_min <= players[player2] <= s_max:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def generate_room():
|
||||||
|
players = {}
|
||||||
|
|
||||||
|
for _ in range(2):
|
||||||
|
player = Player.objects.create(ton_wallet=gen_ton())
|
||||||
|
players[player] = player.get_last_deck().score()
|
||||||
|
|
||||||
|
while _check_players_score(players):
|
||||||
|
player = Player.objects.create(ton_wallet=gen_ton())
|
||||||
|
players[player] = player.get_last_deck().score()
|
||||||
|
|
||||||
|
for player in players:
|
||||||
|
for player2 in players:
|
||||||
|
if player != player2:
|
||||||
|
s_min = players[player] * 0.95
|
||||||
|
s_max = players[player] * 1.05
|
||||||
|
if s_min <= players[player2] <= s_max:
|
||||||
|
p1 = player
|
||||||
|
p2 = player2
|
||||||
|
|
||||||
|
room_slug = sync_create_room(
|
||||||
|
p1.get_last_deck().id,
|
||||||
|
p1.id,
|
||||||
|
players[p1],
|
||||||
|
p2.get_last_deck().id,
|
||||||
|
p2.id,
|
||||||
|
players[p2],
|
||||||
|
)
|
||||||
|
print(f"ws://127.0.0.1:8000/room/{room_slug}")
|
||||||
|
print(f"Authorization: {p1.get_access_token()}")
|
||||||
|
print(f"Authorization: {p2.get_access_token()}")
|
||||||
|
return None
|
31
docker-compose.yaml
Normal file
31
docker-compose.yaml
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
services:
|
||||||
|
daphne:
|
||||||
|
build:
|
||||||
|
dockerfile: ./build/Dockerfile
|
||||||
|
context: .
|
||||||
|
image: daphne
|
||||||
|
volumes:
|
||||||
|
- type: bind
|
||||||
|
source: ${ROOT_DIR}/logs
|
||||||
|
target: /app/logs
|
||||||
|
ports:
|
||||||
|
- "8080:8080"
|
||||||
|
- "8000:8000"
|
||||||
|
depends_on:
|
||||||
|
- postgres
|
||||||
|
tty: true
|
||||||
|
container_name: daphne_server
|
||||||
|
postgres:
|
||||||
|
image: postgres:latest
|
||||||
|
ports:
|
||||||
|
- "5432:5432"
|
||||||
|
environment:
|
||||||
|
- POSTGRES_USER=daphne
|
||||||
|
- POSTGRES_PASSWORD=daphne
|
||||||
|
- POSTGRES_DB=daphne
|
||||||
|
container_name: daphne_database
|
||||||
|
redis:
|
||||||
|
image: redis:latest
|
||||||
|
ports:
|
||||||
|
- "6379:6379"
|
||||||
|
container_name: daphne_redis
|
|
@ -22,9 +22,7 @@ class GetHeroSerializer(serializers.ModelSerializer):
|
||||||
fields = (
|
fields = (
|
||||||
"added",
|
"added",
|
||||||
"type",
|
"type",
|
||||||
"idle_img",
|
"model",
|
||||||
"attack_img",
|
|
||||||
"die_img",
|
|
||||||
"health",
|
"health",
|
||||||
"attack",
|
"attack",
|
||||||
"speed",
|
"speed",
|
||||||
|
@ -37,15 +35,21 @@ class ListHeroSerializer(serializers.ModelSerializer):
|
||||||
fields = (
|
fields = (
|
||||||
"uuid",
|
"uuid",
|
||||||
"type",
|
"type",
|
||||||
"idle_img",
|
"model",
|
||||||
"attack_img",
|
|
||||||
"die_img",
|
|
||||||
"health",
|
"health",
|
||||||
"attack",
|
"attack",
|
||||||
"speed",
|
"speed",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ListHeroInDeckSerializer(serializers.ModelSerializer):
|
||||||
|
hero = ListHeroSerializer()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = HeroInDeck
|
||||||
|
fields = ("hero", "x", "y")
|
||||||
|
|
||||||
|
|
||||||
class CreatePlayerSerializer(serializers.ModelSerializer):
|
class CreatePlayerSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Player
|
model = Player
|
||||||
|
@ -106,7 +110,7 @@ class GetPlayerSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
class GetDeckSerializer(serializers.ModelSerializer):
|
class GetDeckSerializer(serializers.ModelSerializer):
|
||||||
player = GetPlayerSerializer()
|
player = GetPlayerSerializer()
|
||||||
heroes = ListHeroSerializer(many=True)
|
heroes = ListHeroInDeckSerializer(many=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Deck
|
model = Deck
|
||||||
|
|
|
@ -2,5 +2,8 @@ from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
class GameConfig(AppConfig):
|
class GameConfig(AppConfig):
|
||||||
default_auto_field = 'django.db.models.BigAutoField'
|
default_auto_field = "django.db.models.BigAutoField"
|
||||||
name = 'game'
|
name = "game"
|
||||||
|
|
||||||
|
def ready(self):
|
||||||
|
import game.signals
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
# Generated by Django 4.0.5 on 2022-06-04 14:16
|
# Generated by Django 4.0.5 on 2022-06-04 14:16
|
||||||
|
|
||||||
import django.core.validators
|
from django.db import migrations
|
||||||
from django.db import migrations, models
|
|
||||||
import uuid
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
@ -13,45 +11,4 @@ class Migration(migrations.Migration):
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
|
||||||
name='Hero',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, unique=True)),
|
|
||||||
('added', models.DateTimeField(auto_now_add=True)),
|
|
||||||
('type', models.CharField(choices=[('WIZARD', 'wizard'), ('ARCHER', 'archer'), ('WARRIOR', 'warrior')], max_length=7)),
|
|
||||||
('idle_img', models.ImageField(upload_to='uploads/idle')),
|
|
||||||
('attack_img', models.ImageField(upload_to='uploads/attack')),
|
|
||||||
('die_img', models.ImageField(upload_to='uploads/die')),
|
|
||||||
('health', models.IntegerField(validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(10)])),
|
|
||||||
('speed', models.IntegerField(validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(10)])),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'hero',
|
|
||||||
'verbose_name_plural': 'heroes',
|
|
||||||
'ordering': ['-added'],
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Player',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('ton_wallet', models.CharField(max_length=50, verbose_name='TON wallet')),
|
|
||||||
('name', models.CharField(blank=True, max_length=100)),
|
|
||||||
('added', models.DateTimeField(auto_now_add=True)),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'player',
|
|
||||||
'verbose_name_plural': 'players',
|
|
||||||
'ordering': ['-added'],
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.AddIndex(
|
|
||||||
model_name='player',
|
|
||||||
index=models.Index(fields=['ton_wallet'], name='game_player_ton_wal_47dd93_idx'),
|
|
||||||
),
|
|
||||||
migrations.AddIndex(
|
|
||||||
model_name='hero',
|
|
||||||
index=models.Index(fields=['uuid'], name='game_hero_uuid_ada5d9_idx'),
|
|
||||||
),
|
|
||||||
]
|
]
|
||||||
|
|
|
@ -9,14 +9,15 @@ from django.core.validators import (
|
||||||
)
|
)
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
|
from chess_backend import settings
|
||||||
from common.generators import generate_charset
|
from common.generators import generate_charset
|
||||||
from game.services.jwt import sign_jwt
|
from game.services.jwt import sign_jwt
|
||||||
|
|
||||||
|
|
||||||
class HeroTypes(models.TextChoices):
|
class HeroTypes(models.TextChoices):
|
||||||
wizard = "WIZARD", "wizard"
|
|
||||||
archer = "ARCHER", "archer"
|
archer = "ARCHER", "archer"
|
||||||
warrior = "WARRIOR", "warrior"
|
warrior = "WARRIOR", "warrior"
|
||||||
|
wizard = "WIZARD", "wizard"
|
||||||
king = "KING", "king"
|
king = "KING", "king"
|
||||||
|
|
||||||
|
|
||||||
|
@ -32,32 +33,6 @@ class Player(models.Model):
|
||||||
name = models.CharField(max_length=100, blank=True)
|
name = models.CharField(max_length=100, blank=True)
|
||||||
created = models.DateTimeField(auto_now_add=True)
|
created = models.DateTimeField(auto_now_add=True)
|
||||||
|
|
||||||
def save(
|
|
||||||
self, force_insert=False, force_update=False, using=None, update_fields=None
|
|
||||||
):
|
|
||||||
"""saves user and creates deck for him with 16 heroes"""
|
|
||||||
super(Player, self).save()
|
|
||||||
PlayerAuthSession.objects.create(player=self)
|
|
||||||
deck = Deck.objects.create(player=self)
|
|
||||||
types = (
|
|
||||||
["KING"]
|
|
||||||
+ ["ARCHER" for _ in range(4)]
|
|
||||||
+ ["WARRIOR" for _ in range(6)]
|
|
||||||
+ ["WIZARD" for _ in range(2)]
|
|
||||||
+ [random.choice(HeroTypes.choices[:3])[0] for _ in range(3)]
|
|
||||||
)
|
|
||||||
for t in types:
|
|
||||||
hero = Hero()
|
|
||||||
hero.player = self
|
|
||||||
hero.type = t
|
|
||||||
|
|
||||||
hero.health = random.randint(0, 10)
|
|
||||||
hero.attack = random.randint(0, 10)
|
|
||||||
hero.speed = random.randint(0, 10)
|
|
||||||
|
|
||||||
hero.save()
|
|
||||||
HeroInDeck.objects.create(deck=deck, hero=hero)
|
|
||||||
|
|
||||||
def get_last_deck(self):
|
def get_last_deck(self):
|
||||||
return Deck.objects.filter(player=self).last()
|
return Deck.objects.filter(player=self).last()
|
||||||
|
|
||||||
|
@ -65,10 +40,13 @@ class Player(models.Model):
|
||||||
return PlayerAuthSession.objects.get(player=self).jit
|
return PlayerAuthSession.objects.get(player=self).jit
|
||||||
|
|
||||||
def get_refresh_token(self):
|
def get_refresh_token(self):
|
||||||
return sign_jwt({"jit": self.get_auth_session(), "type": "refresh"})
|
return sign_jwt(
|
||||||
|
{"jit": self.get_auth_session(), "type": "refresh"},
|
||||||
|
t_life=settings.TOKEN_EXP,
|
||||||
|
)
|
||||||
|
|
||||||
def get_access_token(self):
|
def get_access_token(self):
|
||||||
return sign_jwt({"id": self.id, "type": "access"}, t_life=3600)
|
return sign_jwt({"id": self.id, "type": "access"}, t_life=settings.AUTH_EXP)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
@ -97,7 +75,7 @@ class Hero(models.Model):
|
||||||
added = models.DateTimeField(auto_now_add=True)
|
added = models.DateTimeField(auto_now_add=True)
|
||||||
|
|
||||||
type = models.CharField(blank=False, choices=HeroTypes.choices, max_length=7)
|
type = models.CharField(blank=False, choices=HeroTypes.choices, max_length=7)
|
||||||
model = models.ForeignKey("HeroModelSet", on_delete=models.CASCADE)
|
model_f = models.ForeignKey("HeroModelSet", on_delete=models.CASCADE)
|
||||||
health = models.IntegerField(
|
health = models.IntegerField(
|
||||||
validators=[MinValueValidator(1), MaxValueValidator(10)], blank=False
|
validators=[MinValueValidator(1), MaxValueValidator(10)], blank=False
|
||||||
)
|
)
|
||||||
|
@ -108,14 +86,8 @@ class Hero(models.Model):
|
||||||
validators=[MinValueValidator(1), MaxValueValidator(10)], blank=False
|
validators=[MinValueValidator(1), MaxValueValidator(10)], blank=False
|
||||||
)
|
)
|
||||||
|
|
||||||
def idle_img(self):
|
def model(self):
|
||||||
return self.idle_img_f.image.url
|
return self.model_f.model.url
|
||||||
|
|
||||||
def attack_img(self):
|
|
||||||
return self.attack_img_f.image.url
|
|
||||||
|
|
||||||
def die_img(self):
|
|
||||||
return self.die_img_f.image.url
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.type} {self.player.name}"
|
return f"{self.type} {self.player.name}"
|
||||||
|
@ -123,16 +95,8 @@ class Hero(models.Model):
|
||||||
def save(
|
def save(
|
||||||
self, force_insert=False, force_update=False, using=None, update_fields=None
|
self, force_insert=False, force_update=False, using=None, update_fields=None
|
||||||
):
|
):
|
||||||
self.idle_img_f = random.choice(
|
self.model_f = random.choice(HeroModelSet.objects.filter(hero_type=self.type))
|
||||||
[x for x in HeroModelSet.objects.filter(hero_type=self.type)]
|
super(Hero, self).save(force_insert, force_update, using, update_fields)
|
||||||
)
|
|
||||||
self.attack_img_f = random.choice(
|
|
||||||
[x for x in HeroModelSet.objects.filter(hero_type=self.type)]
|
|
||||||
)
|
|
||||||
self.die_img_f = random.choice(
|
|
||||||
[x for x in HeroModelSet.objects.filter(hero_type=self.type)]
|
|
||||||
)
|
|
||||||
super(Hero, self).save()
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
indexes = [models.Index(fields=["uuid"])]
|
indexes = [models.Index(fields=["uuid"])]
|
||||||
|
@ -145,7 +109,7 @@ class Hero(models.Model):
|
||||||
|
|
||||||
class HeroModelSet(models.Model):
|
class HeroModelSet(models.Model):
|
||||||
hero_type = models.CharField(blank=False, choices=HeroTypes.choices, max_length=7)
|
hero_type = models.CharField(blank=False, choices=HeroTypes.choices, max_length=7)
|
||||||
model = models.ImageField(upload_to="uploads/")
|
model = models.FileField(upload_to="uploads/")
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.hero_type} model file"
|
return f"{self.hero_type} model file"
|
||||||
|
@ -163,14 +127,15 @@ class Deck(models.Model):
|
||||||
return f"{self.player.name}'s deck"
|
return f"{self.player.name}'s deck"
|
||||||
|
|
||||||
def get_heroes(self):
|
def get_heroes(self):
|
||||||
return [x.hero for x in HeroInDeck.objects.filter(deck=self)]
|
return HeroInDeck.objects.filter(deck=self)
|
||||||
|
|
||||||
def heroes(self):
|
def heroes(self):
|
||||||
# added for better DRF view
|
|
||||||
return self.get_heroes()
|
return self.get_heroes()
|
||||||
|
|
||||||
def score(self):
|
def score(self):
|
||||||
return sum([x.attack + x.health + x.speed for x in self.get_heroes()])
|
return sum(
|
||||||
|
[x.hero.attack + x.hero.health + x.hero.speed for x in self.get_heroes()]
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
db_table = "deck"
|
db_table = "deck"
|
||||||
|
@ -191,11 +156,19 @@ class HeroInDeck(models.Model):
|
||||||
related_name="hero_in_deck",
|
related_name="hero_in_deck",
|
||||||
related_query_name="decks",
|
related_query_name="decks",
|
||||||
)
|
)
|
||||||
|
x = models.IntegerField(
|
||||||
|
blank=False, validators=[MinValueValidator(1), MaxValueValidator(8)]
|
||||||
|
)
|
||||||
|
y = models.IntegerField(
|
||||||
|
blank=False, validators=[MinValueValidator(1), MaxValueValidator(2)]
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
db_table = "hero_in_deck"
|
db_table = "hero_in_deck"
|
||||||
verbose_name = "Hero in deck"
|
verbose_name = "Hero in deck"
|
||||||
verbose_name_plural = "Heroes in decks"
|
verbose_name_plural = "Heroes in decks"
|
||||||
|
ordering = ["y", "x"]
|
||||||
|
unique_together = ["hero", "x", "y"]
|
||||||
|
|
||||||
|
|
||||||
class PlayerAuthSession(models.Model):
|
class PlayerAuthSession(models.Model):
|
||||||
|
|
49
game/services/deck_handler.py
Normal file
49
game/services/deck_handler.py
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
import random
|
||||||
|
|
||||||
|
from game.models import Deck, Player, HeroTypes, Hero, HeroInDeck
|
||||||
|
|
||||||
|
|
||||||
|
def create_first_deck(player: Player):
|
||||||
|
deck = Deck.objects.create(player=player)
|
||||||
|
positions = []
|
||||||
|
|
||||||
|
for x in range(1, 9):
|
||||||
|
for y in range(1, 3):
|
||||||
|
positions.append((x, y))
|
||||||
|
positions.remove((4, 1))
|
||||||
|
positions.remove((5, 1))
|
||||||
|
random.shuffle(positions)
|
||||||
|
|
||||||
|
types = ["KING", "WIZARD"] + ["ARCHER" for _ in range(4)] + ["WARRIOR" for _ in range(6)]
|
||||||
|
|
||||||
|
for _ in range(4):
|
||||||
|
t = random.choice(HeroTypes.choices[:3])[0]
|
||||||
|
if t == "WIZARD" and types.count("WIZARD") > 1:
|
||||||
|
t = random.choice(HeroTypes.choices[:2])[0]
|
||||||
|
types.append(t)
|
||||||
|
|
||||||
|
counter = 0
|
||||||
|
for t in types:
|
||||||
|
hero = Hero()
|
||||||
|
hero.player = player
|
||||||
|
hero.type = t
|
||||||
|
|
||||||
|
# set random position on deck for heroes
|
||||||
|
if t == "KING":
|
||||||
|
pos_x = 5
|
||||||
|
pos_y = 1
|
||||||
|
elif t == "WIZARD":
|
||||||
|
pos_x = 4
|
||||||
|
pos_y = 1
|
||||||
|
else:
|
||||||
|
pos_x = positions[counter][0]
|
||||||
|
pos_y = positions[counter][1]
|
||||||
|
|
||||||
|
counter += 1
|
||||||
|
|
||||||
|
hero.health = random.randint(1, 10)
|
||||||
|
hero.attack = random.randint(1, 10)
|
||||||
|
hero.speed = random.randint(1, 10)
|
||||||
|
|
||||||
|
hero.save()
|
||||||
|
HeroInDeck.objects.create(deck=deck, hero=hero, x=pos_x, y=pos_y)
|
11
game/signals.py
Normal file
11
game/signals.py
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
from django.db.models.signals import post_save
|
||||||
|
from django.dispatch import receiver
|
||||||
|
from .models import Player, PlayerAuthSession
|
||||||
|
from .services.deck_handler import create_first_deck
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(post_save, sender=Player)
|
||||||
|
def create_player(sender, instance, created, **kwargs):
|
||||||
|
if created:
|
||||||
|
PlayerAuthSession.objects.create(player=instance)
|
||||||
|
create_first_deck(instance)
|
BIN
media/dump_data/dump.jpg
Normal file
BIN
media/dump_data/dump.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 145 KiB |
BIN
media/dump_data/dump_1P13Svy.jpg
Normal file
BIN
media/dump_data/dump_1P13Svy.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 145 KiB |
BIN
media/dump_data/dump_d6lH7IJ.jpg
Normal file
BIN
media/dump_data/dump_d6lH7IJ.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 145 KiB |
BIN
media/dump_data/dump_mx4zxHq.jpg
Normal file
BIN
media/dump_data/dump_mx4zxHq.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 145 KiB |
34
media/dump_data/hero_model_fixture.json
Normal file
34
media/dump_data/hero_model_fixture.json
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"model": "game.heromodelset",
|
||||||
|
"pk": 1,
|
||||||
|
"fields": {
|
||||||
|
"hero_type": "KING",
|
||||||
|
"model": "dump_data/dump.jpg"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "game.heromodelset",
|
||||||
|
"pk": 2,
|
||||||
|
"fields": {
|
||||||
|
"hero_type": "WARRIOR",
|
||||||
|
"model": "dump_data/dump_mx4zxHq.jpg"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "game.heromodelset",
|
||||||
|
"pk": 3,
|
||||||
|
"fields": {
|
||||||
|
"hero_type": "ARCHER",
|
||||||
|
"model": "dump_data/dump_d6lH7IJ.jpg"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "game.heromodelset",
|
||||||
|
"pk": 4,
|
||||||
|
"fields": {
|
||||||
|
"hero_type": "WIZARD",
|
||||||
|
"model": "dump_data/dump_1P13Svy.jpg"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
44
nginx.conf
Normal file
44
nginx.conf
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
server {
|
||||||
|
# Base settings for client body size
|
||||||
|
client_max_body_size 64M;
|
||||||
|
client_body_timeout 10;
|
||||||
|
send_timeout 10;
|
||||||
|
keepalive_timeout 10;
|
||||||
|
client_header_timeout 10;
|
||||||
|
client_body_buffer_size 64M;
|
||||||
|
client_header_buffer_size 64M;
|
||||||
|
client_max_header_size 64M;
|
||||||
|
|
||||||
|
location /static {
|
||||||
|
allow all;
|
||||||
|
autoindex off;
|
||||||
|
root /var/www;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /media {
|
||||||
|
allow all;
|
||||||
|
autoindex off;
|
||||||
|
root /var/www;
|
||||||
|
}
|
||||||
|
|
||||||
|
location / {
|
||||||
|
allow all;
|
||||||
|
proxy_pass http://127.0.0.1:8080;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
add_header P3P 'CP="ALL DSP COR PSAa PSDa OUR NOR ONL UNI COM NAV"';
|
||||||
|
|
||||||
|
# WebSocket support
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection "upgrade";
|
||||||
|
}
|
||||||
|
|
||||||
|
listen 443 ssl;
|
||||||
|
|
||||||
|
# ssl settings
|
||||||
|
# ssl_certificate /etc/nginx/ssl/nginx.crt;
|
||||||
|
# ssl_certificate_key /etc/nginx/ssl/nginx.key;
|
||||||
|
# ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
|
||||||
|
}
|
|
@ -1,46 +0,0 @@
|
||||||
asgiref==3.5.2
|
|
||||||
asttokens==2.0.5
|
|
||||||
attrs==21.4.0
|
|
||||||
autobahn==22.5.1
|
|
||||||
Automat==20.2.0
|
|
||||||
backcall==0.2.0
|
|
||||||
cffi==1.15.0
|
|
||||||
channels==3.0.4
|
|
||||||
constantly==15.1.0
|
|
||||||
cryptography==37.0.2
|
|
||||||
daphne==3.0.2
|
|
||||||
decorator==5.1.1
|
|
||||||
Django==4.0.5
|
|
||||||
djangorestframework==3.13.1
|
|
||||||
executing==0.8.3
|
|
||||||
hyperlink==21.0.0
|
|
||||||
idna==3.3
|
|
||||||
incremental==21.3.0
|
|
||||||
ipython==8.4.0
|
|
||||||
jedi==0.18.1
|
|
||||||
matplotlib-inline==0.1.3
|
|
||||||
parso==0.8.3
|
|
||||||
pexpect==4.8.0
|
|
||||||
pickleshare==0.7.5
|
|
||||||
Pillow==9.1.1
|
|
||||||
prompt-toolkit==3.0.29
|
|
||||||
ptyprocess==0.7.0
|
|
||||||
pure-eval==0.2.2
|
|
||||||
pyasn1==0.4.8
|
|
||||||
pyasn1-modules==0.2.8
|
|
||||||
pycparser==2.21
|
|
||||||
Pygments==2.12.0
|
|
||||||
PyJWT==2.4.0
|
|
||||||
pyOpenSSL==22.0.0
|
|
||||||
pytz==2022.1
|
|
||||||
service-identity==21.1.0
|
|
||||||
six==1.16.0
|
|
||||||
sqlparse==0.4.2
|
|
||||||
stack-data==0.2.0
|
|
||||||
traitlets==5.2.2.post1
|
|
||||||
Twisted==22.4.0
|
|
||||||
txaio==22.2.1
|
|
||||||
typing_extensions==4.2.0
|
|
||||||
wcwidth==0.2.5
|
|
||||||
zope.interface==5.4.0
|
|
||||||
channels_redis
|
|
8
requirements/base.txt
Normal file
8
requirements/base.txt
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
Django==4.0.5
|
||||||
|
channels==3.0.4
|
||||||
|
djangorestframework==3.13.1
|
||||||
|
Pillow==9.1.1
|
||||||
|
PyJWT==2.4.0
|
||||||
|
channels-redis==3.4.1
|
||||||
|
celery==5.2.7
|
||||||
|
redis==4.3.4
|
4
requirements/dev.txt
Normal file
4
requirements/dev.txt
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
-r base.txt
|
||||||
|
|
||||||
|
ipython==8.4.0
|
||||||
|
termcolor==1.1.0
|
3
requirements/prod.txt
Normal file
3
requirements/prod.txt
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
-r base.txt
|
||||||
|
|
||||||
|
daphne==3.0.2
|
|
@ -1,17 +1,29 @@
|
||||||
import json
|
import json
|
||||||
|
import os
|
||||||
|
import django
|
||||||
|
|
||||||
from asgiref.sync import sync_to_async
|
from asgiref.sync import sync_to_async
|
||||||
from channels.generic.websocket import AsyncWebsocketConsumer
|
from channels.generic.websocket import AsyncWebsocketConsumer
|
||||||
from channels.layers import get_channel_layer
|
|
||||||
|
from room.services.game_logic import move_handler
|
||||||
|
|
||||||
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "chess_backend.settings")
|
||||||
|
django.setup()
|
||||||
|
|
||||||
from game.models import Deck
|
from game.models import Deck
|
||||||
from room.models import PlayerInQueue, Room, PlayerInRoom, GameState
|
from room.models import PlayerInQueue, Room, PlayerInRoom, GameState
|
||||||
from room.services.room_create import create_room
|
from room.services.room_create import create_room
|
||||||
|
|
||||||
channel_layer = get_channel_layer()
|
|
||||||
|
class BaseConsumer(AsyncWebsocketConsumer):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(args, kwargs)
|
||||||
|
|
||||||
|
async def send_message(self, message_type: str, **data):
|
||||||
|
await self.send(text_data=json.dumps({"type": message_type, **data}))
|
||||||
|
|
||||||
|
|
||||||
class QueueConsumer(AsyncWebsocketConsumer):
|
class QueueConsumer(BaseConsumer):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(args, kwargs)
|
super().__init__(args, kwargs)
|
||||||
self.room_group_name = None
|
self.room_group_name = None
|
||||||
|
@ -22,6 +34,14 @@ class QueueConsumer(AsyncWebsocketConsumer):
|
||||||
await self.accept()
|
await self.accept()
|
||||||
await self.check_origin()
|
await self.check_origin()
|
||||||
|
|
||||||
|
if await self.check_user_already_in_room():
|
||||||
|
await self.send_message(
|
||||||
|
"INFO",
|
||||||
|
message=f"user already in room {self.scope['room_id']}",
|
||||||
|
room=self.scope["room"],
|
||||||
|
)
|
||||||
|
await self.close()
|
||||||
|
|
||||||
await self.channel_layer.group_add(self.room_group_name, self.channel_name)
|
await self.channel_layer.group_add(self.room_group_name, self.channel_name)
|
||||||
|
|
||||||
async def disconnect(self, close_code):
|
async def disconnect(self, close_code):
|
||||||
|
@ -35,27 +55,17 @@ class QueueConsumer(AsyncWebsocketConsumer):
|
||||||
try:
|
try:
|
||||||
data = json.loads(text_data)
|
data = json.loads(text_data)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
await self.send(
|
await self.send_message("ERROR", message="data is not JSON serializable")
|
||||||
text_data=json.dumps(
|
|
||||||
{"type": "ERROR", "message": "data is not JSON serializable"}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if data:
|
if data:
|
||||||
# TODO move to external function/class
|
# TODO move to external function/class
|
||||||
if "type" not in data:
|
if "type" not in data:
|
||||||
await self.send(
|
await self.send_message("ERROR", message="incorrect data typing")
|
||||||
text_data=json.dumps(
|
|
||||||
{"type": "ERROR", "message": "incorrect data typing"}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
if data["type"] == "connect":
|
if data["type"] == "connect":
|
||||||
if "deck_id" not in data:
|
if "deck_id" not in data:
|
||||||
await self.send(
|
await self.send_message(
|
||||||
text_data=json.dumps(
|
"ERROR", message="deck id is not provided"
|
||||||
{"type": "ERROR", "message": "deck id is not provided"}
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
deck = None
|
deck = None
|
||||||
|
@ -64,32 +74,22 @@ class QueueConsumer(AsyncWebsocketConsumer):
|
||||||
deck_id = int(data["deck_id"])
|
deck_id = int(data["deck_id"])
|
||||||
deck = await self.check_user_deck(deck_id)
|
deck = await self.check_user_deck(deck_id)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
await self.send(
|
await self.send_message(
|
||||||
text_data=json.dumps(
|
"ERROR", message="deck id is incorrect"
|
||||||
{"type": "ERROR", "message": "deck id is incorrect"}
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if deck:
|
if deck:
|
||||||
# add to que, start finding players
|
# add to que, start finding players
|
||||||
await self.queue_connector(deck)
|
await self.queue_connector(deck)
|
||||||
await self.send(
|
await self.send_message(
|
||||||
text_data=json.dumps(
|
"INFO",
|
||||||
{
|
message=f"added to queue deck with score {self.scope['score']}",
|
||||||
"type": "INFO",
|
|
||||||
"message": f"added to queue deck with score {self.scope['score']}",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
opponent = await self.find_user_by_score()
|
opponent = await self.find_user_by_score()
|
||||||
|
|
||||||
if not opponent:
|
if not opponent:
|
||||||
await self.send(
|
await self.send_message(
|
||||||
text_data=json.dumps(
|
"INFO", message="no user found, awaiting in queue"
|
||||||
{
|
|
||||||
"type": "INFO",
|
|
||||||
"message": "no user found, awaiting in queue",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# add to group and send message that opponent found to players
|
# add to group and send message that opponent found to players
|
||||||
|
@ -111,32 +111,35 @@ class QueueConsumer(AsyncWebsocketConsumer):
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
await self.send(
|
await self.send_message(
|
||||||
text_data=json.dumps(
|
"INFO",
|
||||||
{
|
message=f"user found, with score {opponent[1]}",
|
||||||
"type": "INFO",
|
room=room,
|
||||||
"message": f"user found, with score {opponent[1]}",
|
|
||||||
"room": room,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
await self.send(
|
await self.send_message(
|
||||||
text_data=json.dumps(
|
"ERROR", message="such deck doesn't exist"
|
||||||
{
|
|
||||||
"type": "ERROR",
|
|
||||||
"message": "such deck doesn't exist",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@sync_to_async
|
@sync_to_async
|
||||||
def delete_user_in_queue(self):
|
def delete_user_in_queue(self):
|
||||||
try:
|
try:
|
||||||
PlayerInQueue.objects.get(player_id=self.scope["player"]).delete()
|
PlayerInQueue.objects.get(player_id=self.scope["player"])
|
||||||
except PlayerInQueue.DoesNotExist:
|
except PlayerInQueue.DoesNotExist:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@sync_to_async
|
||||||
|
def check_user_already_in_room(self):
|
||||||
|
try:
|
||||||
|
p = PlayerInRoom.objects.get(player_id=self.scope["player"])
|
||||||
|
|
||||||
|
self.scope["room"] = p.room.slug
|
||||||
|
self.scope["room_id"] = p.room.id
|
||||||
|
|
||||||
|
return True
|
||||||
|
except PlayerInRoom.DoesNotExist:
|
||||||
|
return False
|
||||||
|
|
||||||
@sync_to_async
|
@sync_to_async
|
||||||
def find_user_by_score(self):
|
def find_user_by_score(self):
|
||||||
s_min = self.scope["score"] * 0.95
|
s_min = self.scope["score"] * 0.95
|
||||||
|
@ -178,24 +181,20 @@ class QueueConsumer(AsyncWebsocketConsumer):
|
||||||
self.scope["score"] = queue.score
|
self.scope["score"] = queue.score
|
||||||
|
|
||||||
async def info(self, event):
|
async def info(self, event):
|
||||||
message = event["message"]
|
|
||||||
msg = {"type": "INFO", "message": message}
|
|
||||||
if "room" in event:
|
if "room" in event:
|
||||||
msg["room"] = event["room"]
|
await self.send_message(
|
||||||
|
"INFO", message=event["message"], room=event["room"]
|
||||||
await self.send(text_data=json.dumps(msg))
|
)
|
||||||
|
else:
|
||||||
|
await self.send_message("INFO", message=event["message"])
|
||||||
|
|
||||||
async def check_origin(self):
|
async def check_origin(self):
|
||||||
if not self.scope["player"]:
|
if not self.scope["player"]:
|
||||||
await self.send(
|
await self.send_message("ERROR", message="token is incorrect or expired")
|
||||||
text_data=json.dumps(
|
|
||||||
{"type": "ERROR", "message": "token is incorrect or expired"}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
await self.close()
|
await self.close()
|
||||||
|
|
||||||
|
|
||||||
class RoomConsumer(AsyncWebsocketConsumer):
|
class RoomConsumer(BaseConsumer):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.room_group_name = None
|
self.room_group_name = None
|
||||||
|
@ -210,18 +209,14 @@ class RoomConsumer(AsyncWebsocketConsumer):
|
||||||
else:
|
else:
|
||||||
message, round = await self.get_state()
|
message, round = await self.get_state()
|
||||||
|
|
||||||
await self.send(
|
await self.send_message(
|
||||||
json.dumps(
|
"INFO",
|
||||||
{
|
opponent_score=self.scope["opponent_score"],
|
||||||
"type": "INFO",
|
opponent_deck=self.scope["opponent_deck"],
|
||||||
"opponent_score": self.scope["opponent_score"],
|
opponent_online=self.scope["opponent_online"],
|
||||||
"opponent_deck": self.scope["opponent_deck"],
|
first=self.scope["first"],
|
||||||
"opponent_online": self.scope["opponent_online"],
|
state=message,
|
||||||
"first": self.scope["first"],
|
round=round,
|
||||||
"state": message,
|
|
||||||
"round": round,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
if "opponent_channel" in self.scope and self.scope["opponent_channel"]:
|
if "opponent_channel" in self.scope and self.scope["opponent_channel"]:
|
||||||
await self.channel_layer.send(
|
await self.channel_layer.send(
|
||||||
|
@ -239,6 +234,10 @@ class RoomConsumer(AsyncWebsocketConsumer):
|
||||||
# Join room group
|
# Join room group
|
||||||
await self.channel_layer.group_add(self.room_group_name, self.channel_name)
|
await self.channel_layer.group_add(self.room_group_name, self.channel_name)
|
||||||
|
|
||||||
|
# load and send board
|
||||||
|
await self.load_board()
|
||||||
|
await self.send_board()
|
||||||
|
|
||||||
@sync_to_async
|
@sync_to_async
|
||||||
def get_state(self):
|
def get_state(self):
|
||||||
state = self.scope["player_in_room"].get_state()
|
state = self.scope["player_in_room"].get_state()
|
||||||
|
@ -255,7 +254,7 @@ class RoomConsumer(AsyncWebsocketConsumer):
|
||||||
if not room:
|
if not room:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
self.scope["room"] = room
|
self.scope["room"] = room.first()
|
||||||
|
|
||||||
# check if player can be in a room
|
# check if player can be in a room
|
||||||
p_ids = [x.player.id for x in room.first().players.all()]
|
p_ids = [x.player.id for x in room.first().players.all()]
|
||||||
|
@ -269,6 +268,7 @@ class RoomConsumer(AsyncWebsocketConsumer):
|
||||||
self.scope["first"] = player.first
|
self.scope["first"] = player.first
|
||||||
self.scope["score"] = player.score
|
self.scope["score"] = player.score
|
||||||
self.scope["deck"] = player.deck.id
|
self.scope["deck"] = player.deck.id
|
||||||
|
self.scope["state"] = room.first().states.last().round
|
||||||
|
|
||||||
p_ids.remove(player.player.id)
|
p_ids.remove(player.player.id)
|
||||||
opponent = PlayerInRoom.objects.get(player_id=p_ids[0])
|
opponent = PlayerInRoom.objects.get(player_id=p_ids[0])
|
||||||
|
@ -310,20 +310,19 @@ class RoomConsumer(AsyncWebsocketConsumer):
|
||||||
try:
|
try:
|
||||||
data = json.loads(text_data)
|
data = json.loads(text_data)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
await self.send(
|
await self.send_message("ERROR", message="data is not JSON serializable")
|
||||||
text_data=json.dumps(
|
|
||||||
{"type": "ERROR", "message": "data is not JSON serializable"}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if data:
|
if data:
|
||||||
if data["type"] == "start":
|
if "type" not in data:
|
||||||
|
await self.send_message("ERROR", message="incorrect data typing")
|
||||||
|
elif data["type"] == "start":
|
||||||
if not await self.start(data):
|
if not await self.start(data):
|
||||||
await self.send(
|
await self.send_message("ERROR", message="opponent is offline")
|
||||||
text_data=json.dumps(
|
elif data["type"] == "move":
|
||||||
{"type": "ERROR", "message": "opponent is offline"}
|
if all(x in data for x in ["x", "y", "px", "py"]):
|
||||||
)
|
await self.perform_move(data)
|
||||||
)
|
else:
|
||||||
|
await self.send_message("ERROR", message="incorrect data typing")
|
||||||
|
|
||||||
async def start(self, data):
|
async def start(self, data):
|
||||||
if self.scope["opponent_channel"] and self.scope["opponent_online"]:
|
if self.scope["opponent_channel"] and self.scope["opponent_online"]:
|
||||||
|
@ -337,6 +336,57 @@ class RoomConsumer(AsyncWebsocketConsumer):
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
async def perform_move(self, data):
|
||||||
|
if await move_handler(
|
||||||
|
data["px"],
|
||||||
|
data["py"],
|
||||||
|
data["x"],
|
||||||
|
data["y"],
|
||||||
|
self.room_name,
|
||||||
|
self.scope["player_in_room"],
|
||||||
|
):
|
||||||
|
await self.send_board()
|
||||||
|
if self.scope["opponent_channel"] and self.scope["opponent_online"]:
|
||||||
|
await self.channel_layer.send(
|
||||||
|
self.scope["opponent_channel"],
|
||||||
|
{
|
||||||
|
"type": "move",
|
||||||
|
"x": data["x"],
|
||||||
|
"y": data["y"],
|
||||||
|
"px": data["px"],
|
||||||
|
"py": data["py"],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
@sync_to_async
|
||||||
|
def load_board(self):
|
||||||
|
# loads bord from db to scope
|
||||||
|
room = self.scope["room"]
|
||||||
|
board = [
|
||||||
|
[None, None, None, None, None, None, None, None],
|
||||||
|
[None, None, None, None, None, None, None, None],
|
||||||
|
[None, None, None, None, None, None, None, None],
|
||||||
|
[None, None, None, None, None, None, None, None],
|
||||||
|
[None, None, None, None, None, None, None, None],
|
||||||
|
[None, None, None, None, None, None, None, None],
|
||||||
|
[None, None, None, None, None, None, None, None],
|
||||||
|
[None, None, None, None, None, None, None, None],
|
||||||
|
]
|
||||||
|
for el in room.heroes.all():
|
||||||
|
board[el.y - 1][el.x - 1] = [el.hero.type, el.health]
|
||||||
|
|
||||||
|
self.scope["board"] = board
|
||||||
|
|
||||||
|
async def send_board(self):
|
||||||
|
# sends board to client
|
||||||
|
await self.send_message(
|
||||||
|
"INFO",
|
||||||
|
message=f"game's board for round {self.scope['state']}",
|
||||||
|
board=self.scope["board"],
|
||||||
|
)
|
||||||
|
|
||||||
# info type group message handler
|
# info type group message handler
|
||||||
async def info(self, event):
|
async def info(self, event):
|
||||||
message = event["message"]
|
message = event["message"]
|
||||||
|
@ -362,13 +412,6 @@ class RoomConsumer(AsyncWebsocketConsumer):
|
||||||
|
|
||||||
await self.send(text_data=json.dumps(msg))
|
await self.send(text_data=json.dumps(msg))
|
||||||
|
|
||||||
# Receive message from room group
|
|
||||||
async def chat_message(self, event):
|
|
||||||
message = event["message"]
|
|
||||||
|
|
||||||
# Send message to WebSocket
|
|
||||||
await self.send(text_data=json.dumps({"lot": message}))
|
|
||||||
|
|
||||||
async def channel(self, event):
|
async def channel(self, event):
|
||||||
channel = event["channel"]
|
channel = event["channel"]
|
||||||
self.scope["opponent_channel"] = channel
|
self.scope["opponent_channel"] = channel
|
||||||
|
@ -387,6 +430,19 @@ class RoomConsumer(AsyncWebsocketConsumer):
|
||||||
)
|
)
|
||||||
self.scope["opponent_online"] = status
|
self.scope["opponent_online"] = status
|
||||||
|
|
||||||
|
async def move(self, event):
|
||||||
|
await self.send(
|
||||||
|
text_data=json.dumps(
|
||||||
|
{
|
||||||
|
"type": "MOVE",
|
||||||
|
"x": event["x"],
|
||||||
|
"y": event["y"],
|
||||||
|
"px": event["px"],
|
||||||
|
"py": event["py"],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
async def check_origin(self):
|
async def check_origin(self):
|
||||||
if not self.scope["player"]:
|
if not self.scope["player"]:
|
||||||
await self.send(
|
await self.send(
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
|
from django.core.validators import MinValueValidator, MaxValueValidator
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
# Create your models here.
|
# Create your models here.
|
||||||
from game.models import Player, Deck
|
from game.models import Player, Deck, Hero
|
||||||
|
|
||||||
|
|
||||||
class PlayerInQueue(models.Model):
|
class PlayerInQueue(models.Model):
|
||||||
|
@ -41,10 +42,40 @@ class PlayerInRoom(models.Model):
|
||||||
|
|
||||||
|
|
||||||
class GameState(models.Model):
|
class GameState(models.Model):
|
||||||
room = models.ForeignKey(Room, on_delete=models.CASCADE)
|
room = models.ForeignKey(Room, related_name="states", on_delete=models.CASCADE)
|
||||||
player = models.ForeignKey(Player, on_delete=models.CASCADE)
|
player = models.ForeignKey(Player, on_delete=models.CASCADE)
|
||||||
round = models.IntegerField(blank=False)
|
round = models.IntegerField(blank=False)
|
||||||
message = models.CharField(max_length=100, blank=False)
|
message = models.CharField(max_length=100, blank=False)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
unique_together = ["room", "player", "round"]
|
unique_together = ["room", "player", "round"]
|
||||||
|
|
||||||
|
|
||||||
|
class HeroInGame(models.Model):
|
||||||
|
hero = models.ForeignKey(Hero, on_delete=models.CASCADE)
|
||||||
|
player = models.ForeignKey(PlayerInRoom, on_delete=models.CASCADE)
|
||||||
|
room = models.ForeignKey(Room, related_name="heroes", on_delete=models.CASCADE)
|
||||||
|
|
||||||
|
# state on board
|
||||||
|
x = models.IntegerField(
|
||||||
|
blank=False, validators=[MinValueValidator(1), MaxValueValidator(8)]
|
||||||
|
)
|
||||||
|
y = models.IntegerField(
|
||||||
|
blank=False, validators=[MinValueValidator(1), MaxValueValidator(8)]
|
||||||
|
)
|
||||||
|
health = models.IntegerField(blank=False)
|
||||||
|
dead = models.BooleanField(default=False)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.hero.type} in room {self.room.slug}"
|
||||||
|
|
||||||
|
def save(
|
||||||
|
self, force_insert=False, force_update=False, using=None, update_fields=None
|
||||||
|
):
|
||||||
|
if not self.health and not self.dead:
|
||||||
|
self.health = self.hero.health
|
||||||
|
|
||||||
|
super().save(force_insert, force_update, using, update_fields)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
unique_together = ["x", "y", "hero"]
|
||||||
|
|
110
room/services/game_logic.py
Normal file
110
room/services/game_logic.py
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
from asgiref.sync import sync_to_async
|
||||||
|
from termcolor import colored
|
||||||
|
|
||||||
|
from room.models import HeroInGame, Room, PlayerInRoom
|
||||||
|
|
||||||
|
|
||||||
|
def _check_path(f_x: int, f_y: int, x: int, y: int, room: Room, move_type: str):
|
||||||
|
if move_type == "DIAGONAL":
|
||||||
|
return (
|
||||||
|
HeroInGame.objects.filter(
|
||||||
|
room=room, x__range=(f_x, x), y__range=(f_y, y)
|
||||||
|
).count()
|
||||||
|
== 0
|
||||||
|
)
|
||||||
|
elif move_type == "HORIZONTAL":
|
||||||
|
return HeroInGame.objects.filter(room=room, x=x, y__range=(f_y, y)).count() == 0
|
||||||
|
elif move_type == "VERTICAL":
|
||||||
|
return HeroInGame.objects.filter(room=room, x__range=(f_x, x), y=y).count() == 0
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def _validate_hero_movement(
|
||||||
|
hero_type: str,
|
||||||
|
prev_x: int,
|
||||||
|
prev_y: int,
|
||||||
|
x: int,
|
||||||
|
y: int,
|
||||||
|
room: Room,
|
||||||
|
first: bool = False, # needed for warrior
|
||||||
|
):
|
||||||
|
if hero_type == "KING":
|
||||||
|
if abs(x - prev_x) > 1 or abs(y - prev_y) > 1:
|
||||||
|
return False
|
||||||
|
elif hero_type == "WIZARD":
|
||||||
|
if abs(x - prev_x) == abs(y - prev_y):
|
||||||
|
return _check_path(prev_x, prev_y, x, y, room, "DIAGONAL")
|
||||||
|
elif x == prev_x and y != prev_y:
|
||||||
|
return _check_path(prev_x, prev_y, x, y, room, "HORIZONTAL")
|
||||||
|
elif x != prev_x and y == prev_y:
|
||||||
|
return _check_path(prev_x, prev_y, x, y, room, "VERTICAL")
|
||||||
|
return False
|
||||||
|
elif hero_type == "ARCHER":
|
||||||
|
if abs(x - prev_x) == abs(y - prev_y):
|
||||||
|
return _check_path(prev_x, prev_y, x, y, room, "DIAGONAL")
|
||||||
|
return False
|
||||||
|
elif hero_type == "WARRIOR":
|
||||||
|
if first:
|
||||||
|
if x == prev_x and y - prev_y == 1:
|
||||||
|
return True
|
||||||
|
elif abs(x - prev_x) == 1 and y - prev_y == 1:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
if x == prev_x and prev_y - y == 1:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def _print_board(room: Room):
|
||||||
|
for y in range(1, 9):
|
||||||
|
for x in range(1, 9):
|
||||||
|
try:
|
||||||
|
hero = HeroInGame.objects.get(x=x, y=y, room=room)
|
||||||
|
if hero.hero.type == "KING":
|
||||||
|
if hero.player.first:
|
||||||
|
print(colored("♔", 'green', attrs=['bold']), end="")
|
||||||
|
else:
|
||||||
|
print(colored("♚", 'red', attrs=['bold']), end="")
|
||||||
|
elif hero.hero.type == "WIZARD":
|
||||||
|
if hero.player.first:
|
||||||
|
print(colored("♕", 'green', attrs=['bold']), end="")
|
||||||
|
else:
|
||||||
|
print(colored("♛", 'red', attrs=['bold']), end="")
|
||||||
|
elif hero.hero.type == "ARCHER":
|
||||||
|
if hero.player.first:
|
||||||
|
print(colored("♗", 'green', attrs=['bold']), end="")
|
||||||
|
else:
|
||||||
|
print(colored("♝", 'red', attrs=['bold']), end="")
|
||||||
|
else:
|
||||||
|
if hero.player.first:
|
||||||
|
print(colored("♙", 'green', attrs=['bold']), end="")
|
||||||
|
else:
|
||||||
|
print(colored("♟", 'red', attrs=['bold']), end="")
|
||||||
|
except HeroInGame.DoesNotExist:
|
||||||
|
print("*", end="")
|
||||||
|
print()
|
||||||
|
|
||||||
|
|
||||||
|
@sync_to_async
|
||||||
|
def move_handler(
|
||||||
|
prev_x: int, prev_y: int, x: int, y: int, room_slug: str, player: PlayerInRoom
|
||||||
|
):
|
||||||
|
room = Room.objects.get(slug=room_slug)
|
||||||
|
_print_board(room) # TODO: Remove in production
|
||||||
|
try:
|
||||||
|
hero = HeroInGame.objects.get(x=prev_x, y=prev_y, room=room, player=player)
|
||||||
|
except HeroInGame.DoesNotExist:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if x == prev_x and y == prev_y:
|
||||||
|
return False
|
||||||
|
|
||||||
|
h_t = hero.hero.type
|
||||||
|
|
||||||
|
if _validate_hero_movement(h_t, prev_x, prev_y, x, y, room, first=player.first):
|
||||||
|
hero.x = x
|
||||||
|
hero.y = y
|
||||||
|
hero.save(update_fields=["x", "y"])
|
||||||
|
return True
|
||||||
|
|
||||||
|
_print_board(room) # TODO: Remove in production
|
0
room/services/read_message.py
Normal file
0
room/services/read_message.py
Normal file
|
@ -2,26 +2,26 @@ from asgiref.sync import sync_to_async
|
||||||
from random import randint
|
from random import randint
|
||||||
|
|
||||||
from common.generators import generate_charset
|
from common.generators import generate_charset
|
||||||
from game.models import Player
|
from game.models import Player, Deck
|
||||||
from room.models import Room, PlayerInRoom, GameState
|
from room.models import Room, PlayerInRoom, GameState, HeroInGame
|
||||||
|
|
||||||
|
|
||||||
@sync_to_async
|
def sync_create_room(
|
||||||
def create_room(
|
|
||||||
deck_id_1: int,
|
deck_id_1: int,
|
||||||
player_id_1: int,
|
player_id_1: int,
|
||||||
player_score_1: int,
|
player_score_1: int,
|
||||||
deck_id_2: int,
|
deck_id_2: int,
|
||||||
player_id_2: int,
|
player_id_2: int,
|
||||||
player_score_2: int,
|
player_score_2: int,
|
||||||
) -> str:
|
):
|
||||||
|
|
||||||
room = Room.objects.create(slug=generate_charset(16))
|
room = Room.objects.create(slug=generate_charset(16))
|
||||||
player_1 = Player.objects.get(id=player_id_1)
|
player_1 = Player.objects.get(id=player_id_1)
|
||||||
player_2 = Player.objects.get(id=player_id_2)
|
player_2 = Player.objects.get(id=player_id_2)
|
||||||
|
|
||||||
first_player = randint(1, 2)
|
first_player = randint(1, 2)
|
||||||
|
|
||||||
PlayerInRoom.objects.create(
|
p1 = PlayerInRoom.objects.create(
|
||||||
player=player_1,
|
player=player_1,
|
||||||
room=room,
|
room=room,
|
||||||
score=player_score_1,
|
score=player_score_1,
|
||||||
|
@ -29,17 +29,37 @@ def create_room(
|
||||||
first=first_player == 1,
|
first=first_player == 1,
|
||||||
)
|
)
|
||||||
|
|
||||||
PlayerInRoom.objects.create(
|
p2 = PlayerInRoom.objects.create(
|
||||||
player=player_2,
|
player=player_2,
|
||||||
room=room,
|
room=room,
|
||||||
score=player_score_2,
|
score=player_score_2,
|
||||||
deck_id=deck_id_2,
|
deck_id=deck_id_2,
|
||||||
first=first_player == 2,
|
first=first_player == 2,
|
||||||
)
|
)
|
||||||
GameState.objects.create(
|
for p, d_id in [(p1, deck_id_1), (p2, deck_id_2)]:
|
||||||
room=room, player=player_1, round=0, message="Game started"
|
GameState.objects.create(
|
||||||
)
|
room=room, player=p.player, round=0, message="Game started"
|
||||||
GameState.objects.create(
|
)
|
||||||
room=room, player=player_2, round=0, message="Game started"
|
for hero_in_deck in Deck.objects.get(id=d_id).heroes():
|
||||||
)
|
if p.first:
|
||||||
|
HeroInGame.objects.create(
|
||||||
|
hero=hero_in_deck.hero,
|
||||||
|
player=p,
|
||||||
|
room=room,
|
||||||
|
x=hero_in_deck.x,
|
||||||
|
y=hero_in_deck.y,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
HeroInGame.objects.create(
|
||||||
|
hero=hero_in_deck.hero,
|
||||||
|
player=p,
|
||||||
|
room=room,
|
||||||
|
x=hero_in_deck.x,
|
||||||
|
y=8 if hero_in_deck.y == 1 else 7,
|
||||||
|
)
|
||||||
return room.slug
|
return room.slug
|
||||||
|
|
||||||
|
|
||||||
|
@sync_to_async
|
||||||
|
def create_room(**kwargs):
|
||||||
|
return sync_create_room(**kwargs)
|
||||||
|
|
3
room/tasks.py
Normal file
3
room/tasks.py
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
from celery import shared_task
|
||||||
|
|
||||||
|
# TODO: add timeout for state
|
Loading…
Reference in New Issue
Block a user