Cards - emkarcinos/WMIAdventure GitHub Wiki
Moduł przeznaczony na karty zlokalizowany w folderze cards
. Nie ma tutaj logiki biznesowej (na dzień dzisiejszy), jest tylko model bazy danych i ORM w Django.
Karty to "przedmioty", których gracze używają do pojedynków między sobą. Istnieją też karty kolekcjonerskie (TODO) służące jako achievementy w aplikacji. Tworzone są w specjalnym Edytorze Kart. Wszystkie requesty dotyczące kart trafiają do klas widoków właśnie w tym module.
Diagram bazy
CardInfo
Encja przechowująca podstawowe informacje o karcie w prostej formie.
name
- Wyświetlana nazwa kartytooltip
- Krótki opis kartysubject
- Przedmiot którego dotyczy karta - Na razie jest to String, w przyszłości powinien się tu pojawić klucz obcy na faktyczną encję z przedmiotami, które obecnie nie są zaimplementowaneimage
- obraz w karcie - na razie nie używane nigdzie w systemie.
CardEffect
Ogólne efekty kart. Każdy wpis w tabeli reprezentuje jeden typ efektu. Każda karta może mieć wiele takich efektów jednocześnie. Modyfikacja ID lub nazwy efektu nie powinna być wykonywana w bazie czy aktualizowana za pomocą requestów, w models.py/CardEffect
zdefiniowany jest specjalny enumerate przeznaczony na wiązanie nazwy efektów z jego identyfikatorami. Pomaga to przy pracach programistycznych.
name
- Nazwa efektutooltip
- Krótki opis efektuhas_modifier
- Pole boolowskie mówiące o tym, czy dany efekt może być modyfikowany przez mnożniki. Przykładem efektu, który będzie miał to pole jakotrue
może być efekt zadający obrażenia, afalse
znajdziemy przy efekcie pomijania następnej karty.
CardLevel
Lista dostępnych poziomów dla kart. Właściwie jest to po prostu tabela słownikowa - poziomy są konkretnie nazwane, jeżeli chcemy je modyfikować to właśnie tutaj. Podobnie jak w CardEffects, w models/CardLevel
zdefiniowany jest specjalny enumerate i najlepiej tam wprowadzać zmiany.
level
- numer poziomuname
- wyświetlana nazwa
Card
Zrozumienie idei tej encji może być nieco skomplikowane. Każda karta którą stworzymy będzie miała jeden opis, wiele poziomów i na każdym z tych poziomów wiele efektów. Widzimy, że Card
ma pola level
i effects
. Połączenie wielu poziomów z wieloma efektami jest skomplikowane i zostało rozwiązane w ten sposób, że dla każdego jednego poziomu w jednej karcie tworzymy nowy wpis w tej tabeli. Zatem tworząc kartę, która ma trzy poziomy, tworzymy jeden CardInfo
, i dla każdego poziomu tworzymy Card
z odnośnikiem do CardInfo
i numerem poziomu. Efekty zostaną dodane w innej encji - CardLevelEffects
info
- klucz obcy naCardInfo
level
- klucz obcy naCardLevel
next_level_cost
- koszt ulepszenia karty na kolejny poziom jako *int
CardLevelEffects
Ta encja może być rozumiana jako rozszerzone-wiele-do-wielu, tj. wiążemy efekty z encją Card
i nadajemy im jakieś parametry. Mówi nam zatem o tym jakie efekty występują na konkretnym poziomie danej karty i o ich parametrach.
card
- klucz obcy naCard
card_effect
- klucz obcy naCardEffect
target
- cel, na który ten efekt zadziała - baza jest ograniczona na dwie wartości - 1, jako cel na gracza i 2 jako cel na przeciwnika.power
- moc efekturange
- 'losowość' efektu - ostateczna wartość efektu będzie z przedziału(power - range), (power + range)
- więcej w Battle - Wyliczanie mocy efektu
Szczegóły implementacji niektórych modeli w Django
Niektóre modele zostały stworzone przy użyciu dość nietypowego mechanizmu tworzenia abstrakcyjnego modelu w osobnej funkcji. Motywacją do użycia tego mechanizmu jest ponowne użycie kodu, wykorzystane w w module proposed_content.
BaseCardInfo
def base_card_info_factory(upload_images_to: str):
class BaseCardInfo(models.Model):
class Meta:
abstract = True
name = models.CharField(max_length=50, help_text="Displayed card's name.")
tooltip = models.TextField(help_text="Card's description. Gets displayed together with the card as a tooltip.")
image = models.ImageField(upload_to=upload_images_to, null=True, blank=True,
help_text="An image. We don't really"
"know what should that be.")
subject = models.CharField(max_length=50, null=True,
help_text="Subject name. In the future this field will be an"
" id pointing to Subject object.")
return BaseCardInfo
Abstrakcyjny model BaseCardInfo
jest tworzony w funkcji. Przechowuje on podstawowe informacje o danej karcie, jak nazwa, opis, itp.
Dlaczego po prostu nie zrobić modelu BaseCardInfo
, bez żadnych funkcji? Tworzenie tego modelu w funkcji umożliwia przekazanie parametru upload_images_to
, który ustala gdzie przechowywać obrazy kart.
Przechowywanie obrazów dla kart proponowanych powinno być w innym miejscu niż kart, które są zaakceptowane i używane w systemie, dzięki przekazywaniu parametru upload_images_to
do funkcji jest to możliwe.
CardInfo
class CardInfo(base_card_info_factory('cards/images/'))
Model CardInfo
przechowuje podstawowe informacje o danej karcie.
Dziedziczy wszystkie pola z abstrakcyjnego modelu BaseCardInfo
tworzonego w funkcji base_card_info_factory
.
BaseCard
def base_card_factory(related_card_info_class: type):
class BaseCard(models.Model):
info = models.ForeignKey(related_card_info_class, related_name='levels', unique=False, on_delete=models.CASCADE)
level = models.ForeignKey(CardLevel, unique=False, on_delete=models.CASCADE)
next_level_cost = models.IntegerField(null=True, validators=[MinValueValidator(0),
MaxValueValidator(100)])
class Meta:
abstract = True
"""
This makes (info, level) unique
"""
constraints = [
models.UniqueConstraint(fields=['info', 'level'],
name=f'unique_{related_card_info_class.__name__}_level')
]
return BaseCard
Abstrakcyjny model BaseCard
przechowuje informacje o konkretnym poziomie danej karty.
Tworzony w funkcji base_card_factory
. Tworzenie go w funkcji umożliwia przekazanie parametru related_card_info_class
, który ustala z jakim typem card info powiązany jest dany model card
.
(typy card info: CardInfo, ProposedCardInfo)
Po co przekazywać related_card_info_class
?
- Model
Card
powinien być powiązany zCardInfo
- Model
ProposedCard
być powiązany z modelemProposedCardInfo
, nie zCardInfo
. Ustawianie odpowiednich powiązań jest możliwe dziękirelated_card_info_class
.
Card
class Card(base_card_factory(CardInfo))
Model Card
przechowuje informacje o konkretnym poziomie danej karty
Card
dziedziczy wszystkie pola z abstrakcyjnego modelu BaseCard
tworzonego w funkcji base_card_factory
.
BaseCardLevelEffects
def base_card_level_effects_factory(foreignkey_card_cls: type):
class BaseCardLevelEffects(models.Model):
class Meta:
abstract = True
class Target(models.IntegerChoices):
"""
Possible targets.
"""
PLAYER = 1
OPPONENT = 2
card = models.ForeignKey(foreignkey_card_cls, related_name='effects', unique=False, on_delete=models.CASCADE)
card_effect = models.ForeignKey(CardEffect, unique=False, on_delete=models.CASCADE)
# This isn't unique even as a pair with card, as a single card on a given level '
# may have multiple of the same effect.
target = models.IntegerField(choices=Target.choices, default=Target.OPPONENT)
power = models.IntegerField(null=True, validators=[MinValueValidator(0),
MaxValueValidator(100)])
# Range defines how the power attribute will vary in card logic.
# So an actual power will be randomized from range (power - range, power + range)
range = models.FloatField(null=True, validators=[MinValueValidator(0),
MaxValueValidator(100)])
return BaseCardLevelEffects
Abstrakcyjny model BaseCardLevelEffects
definiuje pola przechowujące informacje o efektach na danym poziomie karty.
Jest tworzony w funkcji base_card_level_effects_factory
. Tworzenie go w funkcji umożliwia przekazanie parametru foreignkey_card_cls
, który ustala z jakim typem card powiązany jest dany model card level effects.
(typy card: Card, ProposedCard)
Po co przekazywać foreignkey_card_cls
?
- Model
CardLevelEffects
powinien być powiązany zCard
- Model
ProposedCardLevelEffects
być powiązany z modelemProposedCard
. Ustawianie odpowiednich powiązań jest możliwe dziękirelated_card_info_class
.
CardLevelEffects
class CardLevelEffects(base_card_level_effects_factory(Card))
Model CardLevelEffects
przechowuje informacje o efektach na danym poziomie karty.
CardLevelEffects
dziedziczy wszystkie pola z abstrakcyjnego modelu BaseCardLevelEffects
tworzonego w funkcji base_card_level_effects_factory
.
Szczegóły implementacji niektórych serializerów
Analogicznie do niektórych modeli część serializerów dziedziczy z bazowych serializerów zagnieżdżonych w funkcji. Motywacją do użycia tego mechanizmu tworzenia bazowych serializerów jest ponowne użycie kodu, wykorzystane w w module proposed_content.
By lepiej zrozumieć ten mechanizm warto przeczytać tę sekcję.
BaseSimpleCardLevelEffectsSerializer
def base_simple_card_lvl_efcts_ser_factory(card_level_effects_model: type):
class BaseSimpleCardLevelEffectsSerializer(serializers.ModelSerializer):
class Meta:
model = card_level_effects_model
fields = ['card_effect', 'target', 'power', 'range']
return BaseSimpleCardLevelEffectsSerializer
Parametr card_level_effects_model
określa, jakiego typu będzie serializowany obiekt.
(Możliwe typy: CardLevelEffects, ProposedCardLevelEffects)
SimpleCardLevelEffectsSerializer
class SimpleCardLevelEffectsSerializer(base_simple_card_lvl_efcts_ser_factory(CardLevelEffects)):
Dziedziczy wszystkie reguły serializacji z BaseSimpleCardLevelEffectsSerializer.
BaseSimpleCardSerializer
def base_simple_card_serializer_factory(card_model: type, simple_card_level_effects_ser: type):
class BaseSimpleCardSerializer(serializers.ModelSerializer):
effects = simple_card_level_effects_ser(many=True)
class Meta:
model = card_model
fields = ['level', 'next_level_cost', 'effects']
return BaseSimpleCardSerializer
Parametr card_model
określa, jakiego typu będzie serializowany obiekt.
(Możliwe typy: Card, ProposedCard)
Parametr simple_card_level_effects_ser
podaje typ serializera serializującego konkretne obiekty card level effects.
(Możliwe typy serializerów: SimpleCardLevelEffectsSerializer, SimpleProposedCardLevelEffectsSerializer)
SimpleCardSerializer
class SimpleCardSerializer(base_simple_card_serializer_factory(Card, SimpleCardLevelEffectsSerializer)):
"""
Managing simple serialization of given card model.
See: BaseSimpleCardSerializer which is inner class in base_simple_card_serializer_factory function.
"""
Dziedziczy wszystkie reguły serializacji z BaseSimpleCardSerializer.
BaseWholeCardSerializer
def base_whole_card_serializer_factory(card_info_cls: type, simple_card_ser: type):
class BaseWholeCardSerializer(serializers.ModelSerializer):
"""
(De)Serializes Card as a whole, packs all the information scattered across many models in one serializer.
Information like:
- card name
- card image
- card tooltip
- all possible card's levels
- all effects on given card's level
etc.
"""
levels = simple_card_ser(many=True, required=False, help_text="An array of levels objects.")
subject = serializers.CharField(max_length=50, allow_null=True,
help_text="Subject name. In the future this field"
"will be an id pointing to Subject "
"object.")
class Meta:
model = card_info_cls
fields = ['id', 'name', 'subject', 'image', 'tooltip', 'levels']
def validate(self, attrs):
...
def create(self, validated_data):
...
def update(self, instance, validated_data):
...
def _validate_levels_provided(self, validated_data):
...
def _before_create_validation(self, validated_data):
...
return BaseWholeCardSerializer
Określa reguły (de)serializacji karty jako całości - agreguje wszystkie informacje rozsiane po wielu modelach.
Parametry funkcji base_whole_card_serializer_factory
:
-
card_info_cls
- określa jaki model card info będzie serializowany. Dostępne typy: CardInfo, ProposedCardInfo -
simple_card_ser
- określa jaki serializer będzie użyty do (de)serializowania modeli card. Dostępne serializery: SimpleCardSerializer, SimpleCardSerializer
WholeCardSerializer
class WholeCardSerializer(base_whole_card_serializer_factory(CardInfo, SimpleCardSerializer)):
"""
(De)Serializes Card as a whole, packs all the information scattered across many models in one serializer.
Information like:
- card name
- card image
- card tooltip
- all possible card's levels
- all effects on given card's level
etc.
See: base_whole_card_serializer_factory
"""
Dziedziczy wszystkie reguły serializacji z BaseWholeCardSerializer.