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 jakotruemoże być efekt zadający obrażenia, afalseznajdziemy 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 naCardInfolevel- klucz obcy naCardLevelnext_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 naCardcard_effect- klucz obcy naCardEffecttarget- 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
Cardpowinien być powiązany zCardInfo - Model
ProposedCardbyć 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
CardLevelEffectspowinien być powiązany zCard - Model
ProposedCardLevelEffectsbyć 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.