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
|
||||
static/
|
||||
media/
|
||||
media/uploads/
|
||||
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
|
|
17
README.md
17
README.md
|
@ -1,17 +1,30 @@
|
|||
# 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
|
||||
```shell
|
||||
$ 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
|
||||
```
|
||||
|
||||
### run
|
||||
### dev run
|
||||
```shell
|
||||
$ python3 manage.py runserver 0.0.0.0:8000
|
||||
```
|
||||
|
||||
### prod run
|
||||
```shell
|
||||
$ daphne -b 0.0.0.0 -p 8000 chess_backend.asgi:application
|
||||
```
|
||||
|
||||
### Описание команд сокетов
|
||||
```python
|
||||
# подключиние к очереди(ws://room/)
|
||||
|
|
|
@ -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!
|
||||
SECRET_KEY = "django-insecure-%_8sy196w4hzo9^cp9(@r=i+amh47r4mxfhq_(ok&=c(@%bhmk"
|
||||
TOKEN_EXP = 2678400 # 31 day
|
||||
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 = []
|
||||
|
||||
if DEBUG:
|
||||
ALLOWED_HOSTS = ["*"]
|
||||
|
||||
INSTALLED_APPS = [
|
||||
"django.contrib.sessions",
|
||||
"django.contrib.auth",
|
||||
"django.contrib.contenttypes",
|
||||
"django.contrib.staticfiles",
|
||||
"django.contrib.messages",
|
||||
# Packages
|
||||
"rest_framework",
|
||||
|
@ -24,9 +33,22 @@ INSTALLED_APPS = [
|
|||
"room",
|
||||
]
|
||||
|
||||
if DEBUG:
|
||||
INSTALLED_APPS.append("django.contrib.staticfiles")
|
||||
INSTALLED_APPS.append("drf_yasg")
|
||||
|
||||
TEMPLATES = [
|
||||
{
|
||||
"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 = [
|
||||
|
@ -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.urls.static import static
|
||||
from django.template.defaulttags import url
|
||||
from django.urls import path, include, re_path
|
||||
|
||||
# openapi schema
|
||||
from rest_framework import permissions
|
||||
from drf_yasg.views import get_schema_view
|
||||
from drf_yasg import openapi
|
||||
|
||||
|
||||
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)
|
||||
+ static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
||||
)
|
||||
|
||||
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 = (
|
||||
"added",
|
||||
"type",
|
||||
"idle_img",
|
||||
"attack_img",
|
||||
"die_img",
|
||||
"model",
|
||||
"health",
|
||||
"attack",
|
||||
"speed",
|
||||
|
@ -37,15 +35,21 @@ class ListHeroSerializer(serializers.ModelSerializer):
|
|||
fields = (
|
||||
"uuid",
|
||||
"type",
|
||||
"idle_img",
|
||||
"attack_img",
|
||||
"die_img",
|
||||
"model",
|
||||
"health",
|
||||
"attack",
|
||||
"speed",
|
||||
)
|
||||
|
||||
|
||||
class ListHeroInDeckSerializer(serializers.ModelSerializer):
|
||||
hero = ListHeroSerializer()
|
||||
|
||||
class Meta:
|
||||
model = HeroInDeck
|
||||
fields = ("hero", "x", "y")
|
||||
|
||||
|
||||
class CreatePlayerSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Player
|
||||
|
@ -106,7 +110,7 @@ class GetPlayerSerializer(serializers.ModelSerializer):
|
|||
|
||||
class GetDeckSerializer(serializers.ModelSerializer):
|
||||
player = GetPlayerSerializer()
|
||||
heroes = ListHeroSerializer(many=True)
|
||||
heroes = ListHeroInDeckSerializer(many=True)
|
||||
|
||||
class Meta:
|
||||
model = Deck
|
||||
|
|
|
@ -2,5 +2,8 @@ from django.apps import AppConfig
|
|||
|
||||
|
||||
class GameConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'game'
|
||||
default_auto_field = "django.db.models.BigAutoField"
|
||||
name = "game"
|
||||
|
||||
def ready(self):
|
||||
import game.signals
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
# Generated by Django 4.0.5 on 2022-06-04 14:16
|
||||
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
import uuid
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
@ -13,45 +11,4 @@ class Migration(migrations.Migration):
|
|||
]
|
||||
|
||||
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 chess_backend import settings
|
||||
from common.generators import generate_charset
|
||||
from game.services.jwt import sign_jwt
|
||||
|
||||
|
||||
class HeroTypes(models.TextChoices):
|
||||
wizard = "WIZARD", "wizard"
|
||||
archer = "ARCHER", "archer"
|
||||
warrior = "WARRIOR", "warrior"
|
||||
wizard = "WIZARD", "wizard"
|
||||
king = "KING", "king"
|
||||
|
||||
|
||||
|
@ -32,32 +33,6 @@ class Player(models.Model):
|
|||
name = models.CharField(max_length=100, blank=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):
|
||||
return Deck.objects.filter(player=self).last()
|
||||
|
||||
|
@ -65,10 +40,13 @@ class Player(models.Model):
|
|||
return PlayerAuthSession.objects.get(player=self).jit
|
||||
|
||||
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):
|
||||
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):
|
||||
return self.name
|
||||
|
@ -97,7 +75,7 @@ class Hero(models.Model):
|
|||
added = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
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(
|
||||
validators=[MinValueValidator(1), MaxValueValidator(10)], blank=False
|
||||
)
|
||||
|
@ -108,14 +86,8 @@ class Hero(models.Model):
|
|||
validators=[MinValueValidator(1), MaxValueValidator(10)], blank=False
|
||||
)
|
||||
|
||||
def idle_img(self):
|
||||
return self.idle_img_f.image.url
|
||||
|
||||
def attack_img(self):
|
||||
return self.attack_img_f.image.url
|
||||
|
||||
def die_img(self):
|
||||
return self.die_img_f.image.url
|
||||
def model(self):
|
||||
return self.model_f.model.url
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.type} {self.player.name}"
|
||||
|
@ -123,16 +95,8 @@ class Hero(models.Model):
|
|||
def save(
|
||||
self, force_insert=False, force_update=False, using=None, update_fields=None
|
||||
):
|
||||
self.idle_img_f = random.choice(
|
||||
[x for x in HeroModelSet.objects.filter(hero_type=self.type)]
|
||||
)
|
||||
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()
|
||||
self.model_f = random.choice(HeroModelSet.objects.filter(hero_type=self.type))
|
||||
super(Hero, self).save(force_insert, force_update, using, update_fields)
|
||||
|
||||
class Meta:
|
||||
indexes = [models.Index(fields=["uuid"])]
|
||||
|
@ -145,7 +109,7 @@ class Hero(models.Model):
|
|||
|
||||
class HeroModelSet(models.Model):
|
||||
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):
|
||||
return f"{self.hero_type} model file"
|
||||
|
@ -163,14 +127,15 @@ class Deck(models.Model):
|
|||
return f"{self.player.name}'s deck"
|
||||
|
||||
def get_heroes(self):
|
||||
return [x.hero for x in HeroInDeck.objects.filter(deck=self)]
|
||||
return HeroInDeck.objects.filter(deck=self)
|
||||
|
||||
def heroes(self):
|
||||
# added for better DRF view
|
||||
return self.get_heroes()
|
||||
|
||||
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:
|
||||
db_table = "deck"
|
||||
|
@ -191,11 +156,19 @@ class HeroInDeck(models.Model):
|
|||
related_name="hero_in_deck",
|
||||
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:
|
||||
db_table = "hero_in_deck"
|
||||
verbose_name = "Hero in deck"
|
||||
verbose_name_plural = "Heroes in decks"
|
||||
ordering = ["y", "x"]
|
||||
unique_together = ["hero", "x", "y"]
|
||||
|
||||
|
||||
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 os
|
||||
import django
|
||||
|
||||
from asgiref.sync import sync_to_async
|
||||
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 room.models import PlayerInQueue, Room, PlayerInRoom, GameState
|
||||
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):
|
||||
super().__init__(args, kwargs)
|
||||
self.room_group_name = None
|
||||
|
@ -22,6 +34,14 @@ class QueueConsumer(AsyncWebsocketConsumer):
|
|||
await self.accept()
|
||||
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)
|
||||
|
||||
async def disconnect(self, close_code):
|
||||
|
@ -35,27 +55,17 @@ class QueueConsumer(AsyncWebsocketConsumer):
|
|||
try:
|
||||
data = json.loads(text_data)
|
||||
except ValueError:
|
||||
await self.send(
|
||||
text_data=json.dumps(
|
||||
{"type": "ERROR", "message": "data is not JSON serializable"}
|
||||
)
|
||||
)
|
||||
await self.send_message("ERROR", message="data is not JSON serializable")
|
||||
|
||||
if data:
|
||||
# TODO move to external function/class
|
||||
if "type" not in data:
|
||||
await self.send(
|
||||
text_data=json.dumps(
|
||||
{"type": "ERROR", "message": "incorrect data typing"}
|
||||
)
|
||||
)
|
||||
await self.send_message("ERROR", message="incorrect data typing")
|
||||
else:
|
||||
if data["type"] == "connect":
|
||||
if "deck_id" not in data:
|
||||
await self.send(
|
||||
text_data=json.dumps(
|
||||
{"type": "ERROR", "message": "deck id is not provided"}
|
||||
)
|
||||
await self.send_message(
|
||||
"ERROR", message="deck id is not provided"
|
||||
)
|
||||
else:
|
||||
deck = None
|
||||
|
@ -64,32 +74,22 @@ class QueueConsumer(AsyncWebsocketConsumer):
|
|||
deck_id = int(data["deck_id"])
|
||||
deck = await self.check_user_deck(deck_id)
|
||||
except ValueError:
|
||||
await self.send(
|
||||
text_data=json.dumps(
|
||||
{"type": "ERROR", "message": "deck id is incorrect"}
|
||||
)
|
||||
await self.send_message(
|
||||
"ERROR", message="deck id is incorrect"
|
||||
)
|
||||
|
||||
if deck:
|
||||
# add to que, start finding players
|
||||
await self.queue_connector(deck)
|
||||
await self.send(
|
||||
text_data=json.dumps(
|
||||
{
|
||||
"type": "INFO",
|
||||
"message": f"added to queue deck with score {self.scope['score']}",
|
||||
}
|
||||
)
|
||||
await self.send_message(
|
||||
"INFO",
|
||||
message=f"added to queue deck with score {self.scope['score']}",
|
||||
)
|
||||
opponent = await self.find_user_by_score()
|
||||
|
||||
if not opponent:
|
||||
await self.send(
|
||||
text_data=json.dumps(
|
||||
{
|
||||
"type": "INFO",
|
||||
"message": "no user found, awaiting in queue",
|
||||
}
|
||||
)
|
||||
await self.send_message(
|
||||
"INFO", message="no user found, awaiting in queue"
|
||||
)
|
||||
else:
|
||||
# add to group and send message that opponent found to players
|
||||
|
@ -111,32 +111,35 @@ class QueueConsumer(AsyncWebsocketConsumer):
|
|||
},
|
||||
)
|
||||
|
||||
await self.send(
|
||||
text_data=json.dumps(
|
||||
{
|
||||
"type": "INFO",
|
||||
"message": f"user found, with score {opponent[1]}",
|
||||
"room": room,
|
||||
}
|
||||
)
|
||||
await self.send_message(
|
||||
"INFO",
|
||||
message=f"user found, with score {opponent[1]}",
|
||||
room=room,
|
||||
)
|
||||
else:
|
||||
await self.send(
|
||||
text_data=json.dumps(
|
||||
{
|
||||
"type": "ERROR",
|
||||
"message": "such deck doesn't exist",
|
||||
}
|
||||
)
|
||||
await self.send_message(
|
||||
"ERROR", message="such deck doesn't exist"
|
||||
)
|
||||
|
||||
@sync_to_async
|
||||
def delete_user_in_queue(self):
|
||||
try:
|
||||
PlayerInQueue.objects.get(player_id=self.scope["player"]).delete()
|
||||
PlayerInQueue.objects.get(player_id=self.scope["player"])
|
||||
except PlayerInQueue.DoesNotExist:
|
||||
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
|
||||
def find_user_by_score(self):
|
||||
s_min = self.scope["score"] * 0.95
|
||||
|
@ -178,24 +181,20 @@ class QueueConsumer(AsyncWebsocketConsumer):
|
|||
self.scope["score"] = queue.score
|
||||
|
||||
async def info(self, event):
|
||||
message = event["message"]
|
||||
msg = {"type": "INFO", "message": message}
|
||||
if "room" in event:
|
||||
msg["room"] = event["room"]
|
||||
|
||||
await self.send(text_data=json.dumps(msg))
|
||||
await self.send_message(
|
||||
"INFO", message=event["message"], room=event["room"]
|
||||
)
|
||||
else:
|
||||
await self.send_message("INFO", message=event["message"])
|
||||
|
||||
async def check_origin(self):
|
||||
if not self.scope["player"]:
|
||||
await self.send(
|
||||
text_data=json.dumps(
|
||||
{"type": "ERROR", "message": "token is incorrect or expired"}
|
||||
)
|
||||
)
|
||||
await self.send_message("ERROR", message="token is incorrect or expired")
|
||||
await self.close()
|
||||
|
||||
|
||||
class RoomConsumer(AsyncWebsocketConsumer):
|
||||
class RoomConsumer(BaseConsumer):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.room_group_name = None
|
||||
|
@ -210,18 +209,14 @@ class RoomConsumer(AsyncWebsocketConsumer):
|
|||
else:
|
||||
message, round = await self.get_state()
|
||||
|
||||
await self.send(
|
||||
json.dumps(
|
||||
{
|
||||
"type": "INFO",
|
||||
"opponent_score": self.scope["opponent_score"],
|
||||
"opponent_deck": self.scope["opponent_deck"],
|
||||
"opponent_online": self.scope["opponent_online"],
|
||||
"first": self.scope["first"],
|
||||
"state": message,
|
||||
"round": round,
|
||||
},
|
||||
)
|
||||
await self.send_message(
|
||||
"INFO",
|
||||
opponent_score=self.scope["opponent_score"],
|
||||
opponent_deck=self.scope["opponent_deck"],
|
||||
opponent_online=self.scope["opponent_online"],
|
||||
first=self.scope["first"],
|
||||
state=message,
|
||||
round=round,
|
||||
)
|
||||
if "opponent_channel" in self.scope and self.scope["opponent_channel"]:
|
||||
await self.channel_layer.send(
|
||||
|
@ -239,6 +234,10 @@ class RoomConsumer(AsyncWebsocketConsumer):
|
|||
# Join room group
|
||||
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
|
||||
def get_state(self):
|
||||
state = self.scope["player_in_room"].get_state()
|
||||
|
@ -255,7 +254,7 @@ class RoomConsumer(AsyncWebsocketConsumer):
|
|||
if not room:
|
||||
return False
|
||||
|
||||
self.scope["room"] = room
|
||||
self.scope["room"] = room.first()
|
||||
|
||||
# check if player can be in a room
|
||||
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["score"] = player.score
|
||||
self.scope["deck"] = player.deck.id
|
||||
self.scope["state"] = room.first().states.last().round
|
||||
|
||||
p_ids.remove(player.player.id)
|
||||
opponent = PlayerInRoom.objects.get(player_id=p_ids[0])
|
||||
|
@ -310,20 +310,19 @@ class RoomConsumer(AsyncWebsocketConsumer):
|
|||
try:
|
||||
data = json.loads(text_data)
|
||||
except ValueError:
|
||||
await self.send(
|
||||
text_data=json.dumps(
|
||||
{"type": "ERROR", "message": "data is not JSON serializable"}
|
||||
)
|
||||
)
|
||||
await self.send_message("ERROR", message="data is not JSON serializable")
|
||||
|
||||
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):
|
||||
await self.send(
|
||||
text_data=json.dumps(
|
||||
{"type": "ERROR", "message": "opponent is offline"}
|
||||
)
|
||||
)
|
||||
await self.send_message("ERROR", message="opponent is offline")
|
||||
elif data["type"] == "move":
|
||||
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):
|
||||
if self.scope["opponent_channel"] and self.scope["opponent_online"]:
|
||||
|
@ -337,6 +336,57 @@ class RoomConsumer(AsyncWebsocketConsumer):
|
|||
return True
|
||||
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
|
||||
async def info(self, event):
|
||||
message = event["message"]
|
||||
|
@ -362,13 +412,6 @@ class RoomConsumer(AsyncWebsocketConsumer):
|
|||
|
||||
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):
|
||||
channel = event["channel"]
|
||||
self.scope["opponent_channel"] = channel
|
||||
|
@ -387,6 +430,19 @@ class RoomConsumer(AsyncWebsocketConsumer):
|
|||
)
|
||||
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):
|
||||
if not self.scope["player"]:
|
||||
await self.send(
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
from django.core.validators import MinValueValidator, MaxValueValidator
|
||||
from django.db import models
|
||||
|
||||
# Create your models here.
|
||||
from game.models import Player, Deck
|
||||
from game.models import Player, Deck, Hero
|
||||
|
||||
|
||||
class PlayerInQueue(models.Model):
|
||||
|
@ -41,10 +42,40 @@ class PlayerInRoom(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)
|
||||
round = models.IntegerField(blank=False)
|
||||
message = models.CharField(max_length=100, blank=False)
|
||||
|
||||
class Meta:
|
||||
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 common.generators import generate_charset
|
||||
from game.models import Player
|
||||
from room.models import Room, PlayerInRoom, GameState
|
||||
from game.models import Player, Deck
|
||||
from room.models import Room, PlayerInRoom, GameState, HeroInGame
|
||||
|
||||
|
||||
@sync_to_async
|
||||
def create_room(
|
||||
def sync_create_room(
|
||||
deck_id_1: int,
|
||||
player_id_1: int,
|
||||
player_score_1: int,
|
||||
deck_id_2: int,
|
||||
player_id_2: int,
|
||||
player_score_2: int,
|
||||
) -> str:
|
||||
):
|
||||
|
||||
room = Room.objects.create(slug=generate_charset(16))
|
||||
player_1 = Player.objects.get(id=player_id_1)
|
||||
player_2 = Player.objects.get(id=player_id_2)
|
||||
|
||||
first_player = randint(1, 2)
|
||||
|
||||
PlayerInRoom.objects.create(
|
||||
p1 = PlayerInRoom.objects.create(
|
||||
player=player_1,
|
||||
room=room,
|
||||
score=player_score_1,
|
||||
|
@ -29,17 +29,37 @@ def create_room(
|
|||
first=first_player == 1,
|
||||
)
|
||||
|
||||
PlayerInRoom.objects.create(
|
||||
p2 = PlayerInRoom.objects.create(
|
||||
player=player_2,
|
||||
room=room,
|
||||
score=player_score_2,
|
||||
deck_id=deck_id_2,
|
||||
first=first_player == 2,
|
||||
)
|
||||
GameState.objects.create(
|
||||
room=room, player=player_1, round=0, message="Game started"
|
||||
)
|
||||
GameState.objects.create(
|
||||
room=room, player=player_2, round=0, message="Game started"
|
||||
)
|
||||
for p, d_id in [(p1, deck_id_1), (p2, deck_id_2)]:
|
||||
GameState.objects.create(
|
||||
room=room, player=p.player, 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
|
||||
|
||||
|
||||
@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