Latest Posts

Topic: Rating system

trimard
Avatar
Topic Opener
Joined: 2009-03-05, 22:40
Posts: 230
Ranking
Widelands-Forum-Junkie
Location: Paris
Posted at: 2019-07-27, 17:07

Ok I'm having some trouble storing decimal value it seems in django. Anyone has experience in that?

Also, I've been rethinking about what Worlsavior said. Maybe we could already start a tread to call for games? People that post the result of their match in the post will have a ranking! And it would give us lots of data to play with face-smile.png I guess we would still some kind of rules. Like both players have to post that they want to join the ranking before playing the game otherwise it doesn't count?


Top Quote
einstein13
Avatar
Joined: 2013-07-29, 00:01
Posts: 1118
Ranking
One Elder of Players
Location: Poland
Posted at: 2019-07-27, 20:36

trimard wrote:

Ok I'm having some trouble storing decimal value it seems in django. Anyone has experience in that?

I don't have experience either. But as I remember: some database types don't support decimals, but all of them supports float. And float for our calculations should be enough to store all data. Python can easily show the values by "%.3f" % value (float value would be showed as 3-decimal digits number stored as string).
Another workaround is to use integer instead (as it is done in Widelands model for soldiers - their HP is stored like that).

But where you want to use decimals instead of floats?

Also, I've been rethinking about what Worlsavior said. Maybe we could already start a tread to call for games? People that post the result of their match in the post will have a ranking! And it would give us lots of data to play with face-smile.png I guess we would still some kind of rules. Like both players have to post that they want to join the ranking before playing the game otherwise it doesn't count?

That is a good idea!

Database models for ranking games and ladder

Recently I was thinking how to solve the models for games and players. The problem was split into two parts:

  1. Game models
  2. Ranking values models

For Game models we need lots of data, but the data can be also split into two parts: specific game data & players (and teams) data. Plus we want to support not only 2-players games, but every possible Widelands game too. That makes the system more complicated.

For that we need at least two models: game model and participant model. Game model will contain data like map, win conditions, timestamps, and so on. Participant model will create many-to-many relation between game model and registered user model, plus it should contain data like which team the player was playing with, how many interruptions were on that player side (future use?) and so on.

And for coding stuff, arbiter can have access to django admin page, where on a game model form can be several inlines of participants and it will be quite easy to add any records or edit them.

For rank models I was thinking about one that will contain join to the registered user, rank values (two integers and two floats for beginning), update time (when rank record was created) and if it is an active one. Why the last "active"? Because if we create that, we can easily store data about previous rank values and then we can see whole history of the player. And why I am thinking about integer values for ranking? Because then we can easily support simpliest win/lose rank too.

I understand that you may have another view on the problem, so I am open for your point of view face-smile.png .


einstein13
calculations & maps packages: http://wuatek.no-ip.org/~rak/widelands/
backup website files: http://kartezjusz.ddns.net/upload/widelands/

Top Quote
trimard
Avatar
Topic Opener
Joined: 2009-03-05, 22:40
Posts: 230
Ranking
Widelands-Forum-Junkie
Location: Paris
Posted at: 2019-07-28, 01:25

einstein13 wrote: I don't have experience either. But as I remember: some database types don't support decimals, but all of them supports float. And float for our calculations should be enough to store all data. Python can easily show the values by "%.3f" % value (float value would be showed as 3-decimal digits number stored as string).
Another workaround is to use integer instead (as it is done in Widelands model for soldiers - their HP is stored like that).

Django says it can, but actually you're right I should use float or int. Maybe int then? But then what do you do? You have a function for changing the value back to the original? Maybe float is easier

But where you want to use decimals instead of floats?

huhhhh, I have no idea what I'm doing on this. Never made a lot of "low-level" stuff before tbh. Neither a lot of math, at best some geometry stuff. It seems float will fit perfectly for that job, I'll use that

About the tread:

That is a good idea!

Ok, I'll do that tomorrow then face-smile.png

Database models for ranking games and ladder

Perfectly on topic on my current problems!

Recently I was thinking how to solve the models for games and players. The problem was split into two parts:

  1. Game models
  2. Ranking values models

Exactly.

Here's what I have for now for my first test. A lot of stuff are missing but I'm looking for functionality first

class Game(models.Model): #Same idea as you :P 
    user = models.ForeignKey(User, related_name='game')
    start_date = models.DateTimeField(default=0)
    game_type = models.CharField(max_length=255)
    game_map = models.CharField(max_length=255)
    players = models.CharField(max_length=255)
    result = models.CharField(max_length=255)
    submitter = models.CharField(max_length=255)

class Rating(models.Model):  #what you call participant model?
    player = models.CharField(max_length=255)
    rating = models.DecimalField(max_digits=5, decimal_places=2)
    standard_deviation = models.DecimalField(max_digits=4, decimal_places=2)
    volatility = models.DecimalField(max_digits=5, decimal_places=2)

For Game models we need lots of data, but the data can be also split into two parts: specific game data & players (and teams) data. Plus we want to support not only 2-players games, but every possible Widelands game too. That makes the system more complicated.

For that we need at least two models: game model and participant model. Game model will contain data like map, win conditions, timestamps, and so on. Participant model will create many-to-many relation between game model and registered user model, plus it should contain data like which team the player was playing with, how many interruptions were on that player side (future use?) and so on.

So if I understand correctly:

Specific game :

  • map,
  • win conditions,
  • timestamps
  • ...

participant

  • players1
  • players2
  • ==> rel to "registred user"
  • ...
  • rating
  • deviation
  • ...

registred user

  • name
  • rel to "participant"

Which data do you want to put in registered user?

And for coding stuff, arbiter can have access to django admin page, where on a game model form can be several inlines of participants and it will be quite easy to add any records or edit them.

Why didn't I think about that? The first thing I did was a page for the arbiter to enter data...

For rank models I was thinking about one that will contain join to the registered user

Huh, yes didn't realized I was gonna need another table for the score. Yes I guess we can't recalculate everything everytime someone looks at the scores. Even more so as the score won't be updated every minute.

rank values (two integers and two floats for beginning),

rank number. rating score. Standard deviation. Volatility???

update time (when rank record was created) and if it is an active one. Why the last "active"? Because if we create that, we can easily store data about previous rank values and then we can see whole history of the player.

Why not say that it's inactive after a certain standard deviation? Mhhh although yeah, we might need to store the user's history. Is that what you wanted to put inside regitred user?

And why I am thinking about integer values for ranking? Because then we can easily support simpliest win/lose rank too.

I'm not sure I understand that part face-tongue.png

I understand that you may have another view on the problem, so I am open for your point of view face-smile.png .

No no, all good ideas! Just not sure I understand exactly on all of them face-tongue.png


Top Quote
GunChleoc
Avatar
Joined: 2013-10-07, 15:56
Posts: 3324
Ranking
One Elder of Players
Location: RenderedRect
Posted at: 2019-07-28, 09:59

Also, I've been rethinking about what Worlsavior said. Maybe we could already start a tread to call for games? People that post the result of their match in the post will have a ranking! And it would give us lots of data to play with face-smile.png I guess we would still some kind of rules. Like both players have to post that they want to join the ranking before playing the game otherwise it doesn't count?

Definitely - players should always decide whether a match is ranked or not before they start playing.


Busy indexing nil values

Top Quote
kaputtnik
Avatar
Joined: 2013-02-18, 20:48
Posts: 2433
OS: Archlinux
Version: current master
Ranking
One Elder of Players
Location: Germany
Posted at: 2019-07-28, 10:52

I think using a models.DecimalField is good. One doesn't has to fiddle with roundings of values then, imho. See also python Decimal


Fight simulator for Widelands:
https://wide-fighter.netlify.app/

Top Quote
trimard
Avatar
Topic Opener
Joined: 2009-03-05, 22:40
Posts: 230
Ranking
Widelands-Forum-Junkie
Location: Paris
Posted at: 2019-07-28, 14:48

I think using a models.DecimalField is good. See also python Decimal

Yes, that's what I tried but I'm having trouble making it work. I always get the same bug: "[<class 'decimal.InvalidOperation'>]" When trying to store any number with the django model I showed in the upper messages. Even if I put Decimal(1500) or Decimal (0.1) or anything. Same bug. And I cannot find much documentation for that bug. Only the "max_digits should be higher than decimal_places"

Django's error message isn't really helpful...

One doesn't has to fiddle with roundings of values then, imho.

Ha, that's a good point in keeping this format then, yes. I'm not so used to math in code, so any simplification is beneficial. Well that is, if I can fix this bug face-tongue.png


Top Quote
WorldSavior
Avatar
Joined: 2016-10-15, 04:10
Posts: 2091
OS: Linux
Version: Recent tournament version
Ranking
One Elder of Players
Location: Germany
Posted at: 2019-07-28, 18:01

einstein13 wrote:

Plus we want to support not only 2-players games, but every possible Widelands game too.

Also on very imbalanced maps?

einstein13 wrote:

WorldSavior wrote:

2) Instead of handling the Glicko system, make first of all only a rating list for 1-player-collectors-matches, just sorted by the collectors points. (For example the challenge could be to play collectors alone on a chosen map and to gain as much points as possible)

Another economy tournament? face-smile.png

Kind of, but permanently. For example on Ice Wars, blue position.


Wanted to save the world, then I got widetracked

Top Quote
kaputtnik
Avatar
Joined: 2013-02-18, 20:48
Posts: 2433
OS: Archlinux
Version: current master
Ranking
One Elder of Players
Location: Germany
Posted at: 2019-07-28, 19:52

trimard wrote:

Yes, that's what I tried but I'm having trouble making it work. I always get the same bug: "[<class 'decimal.InvalidOperation'>]" When trying to store any number with the django model I showed in the upper messages. Even if I put Decimal(1500) or Decimal (0.1) or anything. Same bug. And I cannot find much documentation for that bug. Only the "max_digits should be higher than decimal_places"

max_digits is the overall amount of digits, that's the digits in the left of the decimal separator plus the digits on the right of the decimal separator.

volatility = models.DecimalField(max_digits=5, decimal_places=2)

= overall digits of 5, three on the left and two on the right of the separator. Trying to apply a value of 1500 does not work, because the left part (integers) can only have 3 digits. '0.1' should work though, maybe it should be '0,1' (note the comma instead of the point). Don't really know, but it may depends on your settings of locales. If you're on linux try this command:

locale -k LC_NUMERIC

The output from my machine:

decimal_point=","
thousands_sep="."
grouping=3;3
numeric-decimal-point-wc=44
numeric-thousands-sep-wc=46
numeric-codeset="UTF-8"

Fight simulator for Widelands:
https://wide-fighter.netlify.app/

Top Quote
trimard
Avatar
Topic Opener
Joined: 2009-03-05, 22:40
Posts: 230
Ranking
Widelands-Forum-Junkie
Location: Paris
Posted at: 2019-07-28, 21:09

overall digits of 5, three on the left and two on the right of the separator. Trying to apply a value of 1500 does not work, because the left part (integers) can only have 3 digits.

THANKSSSSS, that was it. Don't know how I didn't see that!


Top Quote
einstein13
Avatar
Joined: 2013-07-29, 00:01
Posts: 1118
Ranking
One Elder of Players
Location: Poland
Posted at: 2019-07-28, 23:06

I am sorry for technical language (for those who doesn't like it)

trimard wrote:

Database models for ranking games and ladder

Here's what I have for now for my first test. A lot of stuff are missing but I'm looking for functionality first (...)

class Game(models.Model):

    CHOICES_GAME_TYPE = ((1, 'Autocrat'), (2, 'Wood gnome'), ...)

    start_date = modelsDateTimeField()
    type = models.IntegerField(choices=CHOICES_GAME_TYPE)
    map = models.CharField(max_length=255)
    # here we can think about expanding to FK to uploaded maps too:
    # map_link = models.ForeignKey('?maps.map?', null=True, blank=True)
    win_team = models.IntegerField(null=True, blank=True) # blank when game not finished
    submitter = models.ForeignKey(User, related_name='submitted_games')

class Participant(models.Model):
    player = models.ForeignKey(User, related_name='played_games')
    game = models.ForeignKey(Game, related_name='participants')
    team = models.IntegerField()

class Rating(models.Model):

    CHOICES_TYPE = ((1, 'simple'), (2, 'Elo'), (3, 'Glicko'))
    CHOICES_SUBTYPE = ((1, 'updated instantly'), (2, 'updated weekly'))

    player = models.ForeignKey(User, related_name='ratings')
    type = models.IntegerField(choices=CHOICES_TYPE )
    subtype = models.IntegerField(choices=CHOICES_SUBTYPE )
    rating = models.DecimalField(max_digits=6, decimal_places=2)
    float1 = models.FloatField(null=True, blank=True) # rating_deviation for Glicko
    float2 = models.FloatField(null=True, blank=True) # volatility for Glicko
    integer1 = models.IntegerField(null=True, blank=True) # number of win games for simple ladder
    integer2 = models.IntegerField(null=True, blank=True) # number of lost games for simple ladder
    updated = models.DateTimeField(auto_now_add=True)
    active = models.BooleanField(default=True)

    def save(self):
        if self.id == None:
            # update previous records (if exist) of the same type & subtype to be inactive
            previous = Rating.objects.filter(active=True)
            previous = previous.filter(type=self.type)
            if self.subtype:
                previous = previous.filter(subtype=self.subtype)
            previous.update(active=False)
        super().save() # for Python 3.X
        super(Rating, self).save() # for Python 2.7
        return

And when you want to register new game, let's say between me and you (and you win), you will get:

Game records: 1

Game:
    id = 1
    start_date = 2019-07-28 22:00:00
    type = 1 # autocrat
    map = "Ice Wars"
    win_team = 2
    submitter = 253 # einstein13, example ID

Participants records: 2

Participant:
    id = 1
    player = 253 # einstein13
    game = 1 # record above
    team = 1

Participant:
    id = 2
    player = 355 # trimard
    game = 1 # record above
    team = 2

Rating records: 4

Rating:
    player = 253 # einstein13
    type = 1 # simple ladder
    subtype = 1 # updated instantly
    rating = 0.00
    # float1 & float2 are empty
    integer1 = 0
    integer2 = 1
    updated = 2019-07-28 23:13:31
    active = True

Rating:
    player = 355 # trimard
    type = 1 # simple ladder
    subtype = 1 # updated instantly
    rating = 1000.00 # max value
    # float1 & float2 are empty
    integer1 = 1
    integer2 = 0
    updated = 2019-07-28 23:13:31
    active = True

Rating:
    player = 253 # einstein13
    type = 3 # glicko
    subtype = 1 # updated instantly
    rating = 1467.79
    float1 = 327.58
    float2 = 0.059998
    # integer1 & integer2 are empty
    updated = 2019-07-28 23:13:31
    active = True

Rating:
    player = 355 # trimard
    type = 3 # glicko
    subtype = 1 # updated instantly
    rating = 1532.21
    float1 = 327.58
    float2 = 0.059998
    # integer1 & integer2 are empty
    updated = 2019-07-28 23:13:31
    active = True

Values are only for example purpose, the are not very strict

Is my idea clearer now? face-smile.png Hope so face-tongue.png
I know that it is completely different than yours. face-grin.png

I am not sure if you like it or not, but since Widelands is a doocracy and since you are developing the tools, you make a choice face-wink.png

Why didn't I think about that? The first thing I did was a page for the arbiter to enter data...

Nicely done! face-smile.png

And why I am thinking about integer values for ranking? Because then we can easily support simpliest win/lose rank too.

I'm not sure I understand that part face-tongue.png

The simpliest idea for rank I know is to find proportion of win/all games. And since "rating" is providing decimals, the (almost) most precise equation for rank value would be:

ranking = 1000.0 * number_of_win_games / (number_of_win_games + number_of_lost_games)
ranking = 1000.0 * self.integer1 / (self.integer1 + self.integer2)

And to be honest, my idea is covering more than one ranking at once, plus more than only one type of games (1 vs. 1) for them. But if you stick to your approach, that also will be very good! face-smile.png


einstein13
calculations & maps packages: http://wuatek.no-ip.org/~rak/widelands/
backup website files: http://kartezjusz.ddns.net/upload/widelands/

Top Quote