Django游戏模型设计:对局如何关联两位玩家或两支队伍参赛
优化后的Django游戏对战模型设计方案
核心设计思路
- 采用Django通用外键实现参赛方兼容,支持单人和团队两种对战模式
- 移除Player、Team中的冗余胜负字段,通过关联关系动态计算统计数据
- 优化积分模型关联逻辑,明确每笔积分的归属方
- 新增对战参与方校验逻辑,限制单局比赛仅能有2个同类型参赛方
完整修改后的模型代码
from django.db import models from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.models import ContentType from django.core.exceptions import ValidationError class Player(models.Model): slug = models.SlugField(unique=True) name = models.CharField(max_length=100) picture = models.ImageField(upload_to='images/player_pics') def __str__(self): return self.name # 动态计算胜负数据,无需存储冗余字段 @property def wins(self): return GameParticipant.objects.filter( content_type=ContentType.objects.get_for_model(Player), object_id=self.id, is_winner=True ).count() @property def losses(self): return GameParticipant.objects.filter( content_type=ContentType.objects.get_for_model(Player), object_id=self.id, is_winner=False ).count() @property def games_played(self): return self.wins + self.losses class Team(models.Model): slug = models.SlugField(unique=True) name = models.CharField(max_length=100) players = models.ManyToManyField(Player) picture = models.ImageField(upload_to='images/team_pics') def __str__(self): return self.name # 动态计算胜负数据 @property def wins(self): return GameParticipant.objects.filter( content_type=ContentType.objects.get_for_model(Team), object_id=self.id, is_winner=True ).count() @property def losses(self): return GameParticipant.objects.filter( content_type=ContentType.objects.get_for_model(Team), object_id=self.id, is_winner=False ).count() @property def games_played(self): return self.wins + self.losses class Game(models.Model): slug = models.SlugField(unique=True) timestamp = models.DateTimeField(auto_now_add=True) target_score = models.IntegerField(help_text="获胜需要达到的目标积分") def __str__(self): return f"游戏{self.slug} - {self.timestamp.strftime('%Y-%m-%d %H:%M')}" # 校验单局游戏仅能有2个同类型参赛方 def clean(self): super().clean() if self.pk: participants = self.participants.all() if participants.count() != 2: raise ValidationError("单局游戏必须有且仅有2个参赛方") participant_types = set(p.content_type for p in participants) if len(participant_types) > 1: raise ValidationError("单局游戏的参赛方类型必须一致,不能同时存在玩家和战队") class GameParticipant(models.Model): """游戏参赛方,可关联玩家或战队""" game = models.ForeignKey(Game, on_delete=models.CASCADE, related_name="participants") # 通用外键配置 content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) object_id = models.PositiveIntegerField() competitor = GenericForeignKey('content_type', 'object_id') is_winner = models.BooleanField(default=False, help_text="是否为获胜方") class Meta: unique_together = ('game', 'content_type', 'object_id') def __str__(self): return f"{self.game}参赛方:{self.competitor}" class Point(models.Model): """积分记录""" participant = models.ForeignKey(GameParticipant, on_delete=models.CASCADE, related_name="points") val = models.IntegerField(help_text="积分变动值") timestamp = models.DateTimeField(auto_now_add=True) def __str__(self): return f"{self.participant.competitor} 获得{self.val}积分 - {self.timestamp.strftime('%Y-%m-%d %H:%M')}"
关键特性说明
- 参赛方兼容实现:通过
GameParticipant的通用外键,既可以关联Player实例实现单人对战,也可以关联Team实例实现团队对战 - 关联限制校验:在
Game的clean方法中实现校验逻辑,保证单局游戏只有2个参赛方,且参赛方类型完全一致 - 冗余字段消除:胜负、参赛场次数据都通过属性方法动态计算,数据库中无需存储重复数据,保证数据一致性
- 积分归属明确:每笔积分记录都关联到对应参赛方,配合自带的时间戳,可完整回溯单局游戏的积分变化过程
基础使用示例
# 1. 创建单人对局 player1 = Player.objects.create(name="张三", slug="zhangsan") player2 = Player.objects.create(name="李四", slug="lisi") game = Game.objects.create(slug="game-20240520-001", target_score=10) # 添加参赛方 GameParticipant.objects.create(game=game, competitor=player1) GameParticipant.objects.create(game=game, competitor=player2) # 记录积分 Point.objects.create(participant=game.participants.first(), val=1) Point.objects.create(participant=game.participants.last(), val=2) # 判定胜负 winner = game.participants.last() winner.is_winner = True winner.save() # 统计玩家数据 print(player1.wins) # 输出0 print(player2.wins) # 输出1 # 2. 创建团队对局逻辑和上面完全一致,只需要把competitor替换为Team实例即可
内容的提问来源于stack exchange,提问作者HamYam
相关产品推荐
相关产品推荐

