You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.05 15:15:03