Championship standings example

Posts: 785
Starting from version 1.15.8, ATVO provides some support for displaying championship points and standings.

The following is supported:
  • The ChampionshipResult property on IEntity includes four properties:
    • Position: current champ position (before end of this race).
    • LivePosition: live champ position (updated according to position in current race).
    • Points: current champ points (before end of this race).
    • LivePoints: live champ points (updated according to position in current race).
    These properties will all be 0 by default, you must set them to the desired value using a script.
  • New Data Bindings for each of these properties:
    championshipposition, championshippoints, livechampionshipposition, livechampionshippoints
  • New Data Orders for Championship Position and Live Championship Position, allowing you to sort/order drivers by their championship standings, once set via your script.

The reason for the distinction between "current" and "live" points/positions is so you can show both the current standings (e.g. before the race) and live standings, or potentially a delta ("how many positions is a driver gaining in the championship if he stays in this position").


Setting the championship standings
To use the championship properties you must set them via a script. Your script must be a converter script that takes an object as the value input. Depending on which object you must dig down to the Entity and there you can find the ChampionshipResult property.

Binding to "entity_object":
IEntity entity = (IEntity) value;
IChampionshipResult champ = entity.ChampionshipResult;


Binding to "entitysessionresult_object":
IEntitySessionResult result = (IEntitySessionResult) value;
IChampionshipResult champ = result.Entity.ChampionshipResult;


Once you have the IChampionshipResult object, you can set its properties to the desired value:
champ.Position = 3;
champ.LivePosition = 4;
champ.Points = 192;
champ.LivePoints = 185;



Where do I get the amount of points I want to award?
To show current and live standings you will need at least a source of:
  1. Current champ standings (as they were before this race).
  2. Amount of points to award for each position

You can store both in a Spreadsheet object and read it via your script. Please let us know if you need assistance with this part.
Edited (1 time)
Posts: 1
We have a points system in our RX series where you get 10 points for winning qualification, 8 points for winning your heat and 50 points for winning the final (feature) heat. Do you plan on supporting this sort of a points system?
Posts: 785
Setting the championship points and standings is fully manual and you have to do it via a script. We do not provide any automated way to award championship points (because it would quickly become way too complicated with each league having their own rules, like your example).

So yes we support this as long as you can figure out how to use a script to set the points.
Posts: 15
Hi,

some question:
* in VS Code, what using we need to use to have IChampionshipResult, for auto completion ? I try in new theme with new script generation on VS Code
* when we have more driver in spreadsheet championship standing before race that we have on server, how can add they only on championship result ?

Thanks for your work
Posts: 785
I need to upload a new version of the nuget package for that, sorry. I'll try to do it soon.

For the second question I don't know what you mean can you elaborate?
Posts: 15
Thx

For second question, the IChampionshipResult his part of IEntity. But we have one IEntity by car on server that we watch. So if i want to have full championship classification with driver not in this race ?

Posts: 15
Nick ?
you need more explain ? Because i don't know how to do if i thinks how to work.

i have 3 driver in championship (A, B , C)
i have a race with driver A and B.
another race with driver A and C.
another race with all driver...

for the second race how can i do to display all driver ( A B and C ) because the B driver is not in second race ?

Thx
Posts: 785
I have to think about this more. It is not an easy problem.

To show drivers that are not in the server, the only solution is to use a Spreadsheet and bind to that. If you bind to any of the sim data sets, you'll only get drivers in the session, and I don't see a good way to hack around that.

If you bind to a Spreadsheet however you have some more "hacking room". You can in theory even create or modify the Spreadsheet in memory. I think you were doing this before as well but never figured it out properly. This is very tricky to get right because of how our data binding works. I will have to try some things out.
Posts: 785
Perhaps you can make it work easier using the Custom Data Set items. A Custom Data Set is an object in your theme in which you can simply store some raw values (a list of data). Then you can use this as a dataset in a Widget.

You could use a script to dynamically fill the data in a Custom Data Set.

The downside is that a Custom Data Set has only one item per row (one value), there's no way to store for example driver name AND points separately. You can get around that with multiple Custom Data Sets, but then you'd have to create multiple Widgets to show this information. For example one Widget (ticker) that shows the driver names, then another Widget (ticker) for the points, maybe another for car numbers, etc.


I had some downtime at work, here is a quick and dirty example I came up with. Not sure if it compiles but it should be close. You'll need four Custom Data Set objects, a spreadsheet for the standings and a spreadsheet for the point system, see the script.

using System;
using System.Data;
using ATVO.ThemesSDK;
using ATVO.ThemesSDK.Data.Entity;
using ATVO.ThemeEditor.ThemeModels;
using ATVO.ThemeEditor.Scripting.DotNET;
using System.Collections.Generic;

namespace Scripts
{
public class ChampPointScript : IScript
{
private Dictionary<int, int> _pointSystem = new Dictionary<int, int>();
private Dictionary<int, ChampDriver> _standings = new Dictionary<int, ChampDriver>();

public object Execute(ThemeContentItem item, object value, string parameter, ISimulation sim)
{
// Make sure point system and standings are loaded
LoadPointSystem(item.Theme);
LoadStandings(item.Theme);

// Update the points
UpdateSessionStandings(sim);

// Sort by points and assign position
// This updates positions for drivers in the session AND drivers not in the session
UpdateChampPositions();

// Also update the champ results for drivers in the session
// in case you are showing these somewhere else (in another widget)
UpdateSessionChampResults();

// Finally update our Custom Data Sets so the widgets are showing the new results
UpdateDataSets(item.Theme);
}

private void UpdateSessionStandings(ISimulation sim)
{
// Loop through entity results in the session and update the standings
foreach (var result in sim.Session.Current.Results)
{
ChampDriver driver;

// Check if there is a driver in the session who was NOT in the standings csv
if (!_standings.ContainsKey(result.Entity.Id))
{
// This driver isn't in the standings yet, let's add him
driver = new ChampDriver();
driver.CustomerId = result.Entity.Id;
driver.Name = result.Entity.Name;
driver.CarNumber = result.Entity.Car.Number;
driver.ChampPoints = 0;
_standings.Add(driver.CustomerId, driver);
}
else
{
// Otherwise, grab him from memory
driver = _standings[result.Entity.Id];
}

// Link him to the session entity
driver.Entity = result.Entity;

// Add the champ points obtained during this race
driver.LiveChampPoints = driver.ChampPoints + _pointSystem[result.Position]; // Or result.LivePosition to update during a lap
}
}

private void UpdateChampPositions()
{
// Sort the standings by live champ points and assign positions in order
var sorted = _standings.Values.OrderByDescending(d => d.LiveChampPoints);

int position = 1;
foreach (var driver in sorted)
{
driver.LiveChampPosition = position;
position += 1;
}
}

private void UpdateSessionChampResults()
{
foreach (var driver in _standings.Values)
{
// If this driver is in the session, he has his Entity linked up
// If not, Entity is null
if (driver.Entity != null)
{
// update the ChampionshipResult object
var champ = driver.Entity.ChampionshipResult;

champ.Position = driver.ChampPosition; // previous pos
champ.LivePosition = driver.LiveChampPosition; // updated pos
champ.Points = driver.ChampPoints; // previous points
champ.LivePoints = driver.LiveChampPoints; // updated points
}
}
}

private void UpdateDataSets(ITheme theme)
{
// Assuming you have four Custom Data Set items:
var cds_pos = theme.CustomDataSets.Find("cds_pos"); // for champ position
var cds_name = theme.CustomDataSets.Find("cds_name"); // for driver name
var cds_carnum = theme.CustomDataSets.Find("cds_carnum"); // for car num
var cds_points = theme.CustomDataSets.Find("cds_points"); // for champ points

// Clear their data
cds_pos.Data.Clear();
cds_name.Data.Clear();
cds_carnum.Data.Clear();
cds_points.Data.Clear();

// Add the new data in order
foreach (var driver in _standings.Values.OrderBy(d => d.LiveChampPosition))
{
cds_pos.Data.Add(new CustomDataSetItem(driver.LiveChampPosition));
cds_name.Data.Add(new CustomDataSetItem(driver.Name));
cds_carnum.Data.Add(new CustomDataSetItem(driver.CarNumber));
cds_points.Data.Add(new CustomDataSetItem(driver.LiveChampPoints));
}
}

private void LoadPointSystem(ITheme theme)
{
if (_pointSystem.Count == 0)
{
// Read point system from an embedded spreadsheet
// Two columns: position, points
var sheet = theme.Spreadsheets.Find("pointsystem.csv");

foreach (var row in sheet.Table.Data.Rows.OfType<DataRow>())
{
var position = row[0];
var points = row[1];
_pointSystem.Add(position, points);
}
}
}

private void LoadStandings(ITheme theme)
{
if (_standings.Count == 0)
{
// Read current standings from embedded csv
// Columns: customer_id, position, carnumber, drivername, points
var sheet = theme.Spreadsheets.Find("standings.csv");

foreach (var row in sheet.Table.Data.Rows.OfType<DataRow>())
{
var custid = row[0];
var position = row[1];
var carnum = row[2];
var name = row[3];
var points = row[4];

var driver = new ChampDriver();
driver.CustomerId = custid;
driver.ChampPosition = position;
driver.CarNumber = carnum;
driver.Name = name;
driver.ChampPoints = points;

_standings.Add(custid, driver);
}
}
}

public class ChampDriver
{
public IEntity Entity {get;set;}
public int CustomerId {get;set;}
public string CarNumber {get;set;}
public string Name {get;set;}

public int ChampPosition {get;set;}
public int ChampPoints {get;set;}
public int LiveChampPosition {get;set;}
public int LiveChampPoints {get;set;}
}
}
}


This assumes a simple point system with just a relation between current race position and number of points obtained. In your RX example it will be more complicated, but maybe you can figure it out. You'd need to grab the results for each heat and check how many points to obtain.


Note I have no idea about performance for this code. It is constantly updating the data in the Custom Data Sets, if those are bound to a ticker or something it will cause a lot of UI updates. It may work fine, or it may cause lag, no idea, but best not to call this script too often. You could call it every few seconds using a Timer or something.
Posts: 15
Ok thx Nick.
I read this evening and i come back to you.

Yeah it is a hard question, sorry for that.
Before, i use only spreadsheet and it work fine, but with a build (not sure the number) i have one function of order the spreadsheet before displaying not work anymore.

I can use your script. i have more question :
* how can i declare an object in script ?
* can i do a class of "IFRN_DRIVER" with lot of information, use a LIST of this class in script, and after run, calculation, order, write a spreadsheet for display ?

Maybe a suggestion, in script, to have possibility to create custom class


I read all of that and i search the better solution

Thxs for your help and your work
Posts: 785
What you're suggesting seems to be exactly what I'm doing. See the end of the script, I declare a custom class ChampDriver with some properties to hold this information in a list (actually a Dictionary, which is like a hash table). This should work just fine, but like I said I did not test it.

Writing to an actual spreadsheet binding is problematic since a few builds ago yes. I had to change something to avoid a bug and in that process I seem to have disabled you from easily changing the spreadsheet. But I don't think I can reverse it without introducing back the bug.

Instead of writing the standings to a spreadsheet I proposed here to write it to Custom Data Set objects. That should work very well, the only issue is you'll need one Widget for each type of data (position, car number, driver name, etc). So instead of one parent Widget ticker with different SubWidgets/Labels for each data type, you need a separate Widget with a different Dataset. A bit more annoying to work with, but it should work in the end...
Posts: 8
Hi Nick!
I try to test your code, but ATVO shows an error saying that IChampionshipResult does not exists inside the Entity Interface.
But, My ATVO Theme Editor has the Dataset.
How can I update the metadata to solve this problem?

Thanks!
Posts: 785
Can you post your code? IChampionshipResult is the type of the object, but the property on the IEntity interface is called ChampionshipResult (without the I), maybe that's the issue?
Posts: 8
Hi Nick!
I'm a new ATVO user and I'm learning how to use scripts.
I used your code to test this feature:

using System;
using System.Data;
using ATVO.ThemesSDK;
using ATVO.ThemesSDK.Data.Entity;
using ATVO.ThemeEditor.ThemeModels;
using ATVO.ThemeEditor.Scripting.DotNET;
using System.Collections.Generic;

namespace Scripts
{
public class LiveChampionshipPoints : IScript
{
private Dictionary<int, int> _pointSystem = new Dictionary<int, int>();
        private Dictionary<int, DriverContender> _standings = new Dictionary<int, DriverContender>();

public object Execute(ThemeContentItem item, object value, string parameter, ISimulation sim)
{
            // Make sure point system and standings are loaded
            LoadPointSystem(item.Theme);
            LoadStandings(item.Theme);

            // Update the points
            UpdateSessionStandings(sim);

            // Sort by points and assign position
            // This updates positions for drivers in the session AND drivers not in the session
            UpdateChampPositions();

            // Also update the champ results for drivers in the session
            // in case you are showing these somewhere else (in another widget)
            UpdateSessionChampResults();

            // Finally update our Custom Data Sets so the widgets are showing the new results
            UpdateDataSets(item.Theme);
           
return value.ToString();
}

        private void UpdateSessionStandings(ISimulation sim)
        {
            // Loop through entity results in the session and update the standings
            foreach (var result in sim.Session.Current.Results)
            {
                DriverContender driver;

                // Check if there is a driver in the session who was NOT in the standings csv
                if (!_standings.ContainsKey(result.Entity.Id))
                {
                    // This driver isn't in the standings yet, let's add him
                    driver = new DriverContender();
                    driver.CustomerId = result.Entity.Id;
                    driver.Name = result.Entity.Name;
                    driver.CarNumber = result.Entity.Car.Number;
                    driver.ChampPoints = 0;
                    _standings.Add(driver.CustomerId, driver);
                }
                else
                {
                    // Otherwise, grab him from memory
                    driver = _standings[result.Entity.Id];
                }
               
                // Link him to the session entity
                driver.Entity = result.Entity;

                // Add the champ points obtained during this race
                driver.LiveChampPoints = driver.ChampPoints + _pointSystem[result.Position]; // Or result.LivePosition to update during a lap
            }
        }

        private void UpdateChampPositions()
        {
            // Sort the standings by live champ points and assign positions in order
            var sorted = _standings.Values.OrderByDescending(d => d.LiveChampPoints);

            int position = 1;
            foreach (var driver in sorted)
            {
                driver.LiveChampPosition = position;
                position += 1;
            }
        }

        private void UpdateSessionChampResults()
        {
            foreach (var driver in _standings.Values)
            {
                // If this driver is in the session, he has his Entity linked up
                // If not, Entity is null
                if (driver.Entity != null)
                {
                    // update the ChampionshipResult object
                    var champ = driver.Entity.ChampionshipResult;

                    champ.Position = driver.ChampPosition; // previous pos
                    champ.LivePosition = driver.LiveChampPosition; // updated pos
                    champ.Points = driver.ChampPoints; // previous points
                    champ.LivePoints = driver.LiveChampPoints; // updated points
                }
            }
        }

        private void UpdateDataSets(Theme theme)
        {
            // Assuming you have four Custom Data Set items:
            var cds_pos = theme.CustomDataSets.Find("cds_pos"); // for champ position
            var cds_name = theme.CustomDataSets.Find("cds_name"); // for driver name
            var cds_carnum = theme.CustomDataSets.Find("cds_carnum"); // for car num
            var cds_points = theme.CustomDataSets.Find("cds_points"); // for champ points

            // Clear their data
            cds_pos.Data.Clear();
            cds_name.Data.Clear();
            cds_carnum.Data.Clear();
            cds_points.Data.Clear();

            // Add the new data in order
            foreach (var driver in _standings.Values.OrderBy(d => d.LiveChampPosition))
            {
                cds_pos.Data.Add(new CustomDataSetItem(driver.LiveChampPosition));
                cds_name.Data.Add(new CustomDataSetItem(driver.Name));
                cds_carnum.Data.Add(new CustomDataSetItem(driver.CarNumber));
                cds_points.Data.Add(new CustomDataSetItem(driver.LiveChampPoints));
            }
        }

private void LoadPointSystem(Theme theme)
        {
            if (_pointSystem.Count == 0)
            {
                // Read point system from an embedded spreadsheet
                // Two columns: position, points
                var sheet = theme.Spreadsheets.Find("points_system.csv");

                foreach (var row in sheet.Table.Data.Rows.OfType<DataRow>())
                {
                    var position = row[0];
                    var points = row[1];
                    _pointSystem.Add(position, points);
                }
            }
        }

        private void LoadStandings(Theme theme)
        {
            if (_standings.Count == 0)
            {
                // Read current standings from embedded csv
                // Columns: customer_id, position, carnumber, lastname, firstname, points
                var sheet = theme.Spreadsheets.Find("/csv/standings.csv");

                foreach (var row in sheet.Table.Data.Rows.OfType<DataRow>())
                {
                    var custid = row[0];
                    var position = row[1];
                    var carnum = row[2];
                    var name = String.Concat(row[4], row[3]);
                    var points = row[5];
                   
                    var driver = new DriverContender();
                    driver.CustomerId = custid;
                    driver.ChampPosition = position;
                    driver.CarNumber = carnum;
                    driver.Name = name;
                    driver.ChampPoints = points;

                    _standings.Add(custid, driver);
                }
            }
        }
}

public class DriverContender
{
public IEntity Entity {get;set;}
        public int CustomerId {get;set;}
        public string CarNumber {get;set;}
        public string Name {get;set;}
        public int ChampPosition {get;set;}
        public int ChampPoints {get;set;}
        public int LiveChampPosition {get;set;}
public int LiveChampPoints {get;set;}
}
}


So, here are the errors in ATVO Theme Editor:

LiveChampionshipPoints.cs (Line 74) 'Dictionary<int, DriverContender>.ValueCollection' does not contain a definition for 'OrderByDescending' and no extension method 'OrderByDescending' accepting a first argument of type 'Dictionary<int, DriverContender>.ValueCollection' could be found (are you missing a using directive or an assembly reference?)
LiveChampionshipPoints.cs (Line 77) foreach statement cannot operate on variables of type '?' because '?' does not contain a public definition for 'GetEnumerator'
LiveChampionshipPoints.cs (Line 118) 'Dictionary<int, DriverContender>.ValueCollection' does not contain a definition for 'OrderBy' and no extension method 'OrderBy' accepting a first argument of type 'Dictionary<int, DriverContender>.ValueCollection' could be found (are you missing a using directive or an assembly reference?)
LiveChampionshipPoints.cs (Line 135) 'DataRowCollection' does not contain a definition for 'OfType' and no extension method 'OfType' accepting a first argument of type 'DataRowCollection' could be found (are you missing a using directive or an assembly reference?)
LiveChampionshipPoints.cs (Line 152) 'DataRowCollection' does not contain a definition for 'OfType' and no extension method 'OfType' accepting a first argument of type 'DataRowCollection' could be found (are you missing a using directive or an assembly reference?)

And here are the errors when I'm using Visual Studio Code:


'Dictionary <int, DriverContender> .ValueCollection' does not contain a definition for 'OrderByDescending' and could not find any 'OrderByDescending' extension method that accepts a first argument of type 'Dictionary <int, DriverContender> .ValueCollection' (you are forgetting to use a directive or assembly reference?) (CS1061) [project]

'IEntity' does not contain a definition for "ChampionshipResult" and could not find any "ChampionshipResult" extension method that accepts a first argument of type 'IEntity' (are you forgetting to use a directive or an assembly reference?) ( CS1061) [project]

'Dictionary <int, DriverContender> .ValueCollection' does not contain a definition for 'OrderBy' and could not find any 'OrderBy' extension method that accepts a first argument of type 'Dictionary <int, DriverContender> .ValueCollection' (you are forgetting to use a directive or assembly reference?) (CS1061) [project]

'DataRowCollection' does not contain a definition for "OfType" and could not find any "OfType" extension method that accepts a first argument of type 'DataRowCollection' (are you forgetting to use a directive or an assembly reference?) ( CS1061) [project]

‘DataRowCollection’ não contém uma definição para "OfType" e não foi possível encontrar nenhum método de extensão "OfType" que aceite um primeiro argumento do tipo ‘DataRowCollection’ (você está se esquecendo de usar uma diretiva ou uma referência de assembly?) (CS1061) [project]

Sorry for this long post here.
If you can help me with this problem, I'll appreciate.

Thanks!

Edited (1 time)
Posts: 785
For the error about IEntity not having ChampionshipResult: this is likely just because I did not yet include that new property in the Nuget package. You can ignore that error in VS Code, it should still compile in ATVO Editor with no issues.

The other errors are likely missing 'using' statements. In VS Code, it should give you a popup that suggests a fix. Can you try to do that and does the error go away?
Posts: 8
Hi Nick!
It worked! Thank you so much!
And please, update the Nuget Package! hehe

Regards,
Leandro
Posts: 1
hello Nick.....I would love to see if there is a script for Nascar style points and telling who is in the chase and all that on the ticker?....I would pay for somone to help me set this up or give help on how to set it up.....

Thanks
Joe
Posts: 1
Hi! Im currently trying to get this on my themes, but since am a code muggle, Ive struggle to get it done, is not that am a millionaire but, I can spare a couple of bucks to someone better than me can help me with this, and helíng me a little bit with the implementation in my themes, just a simple graphic to show the first 10 drivers in the live championship on a small graphic during the race. Thanks ATVO creator for the amazing software by the way :)
Posts: 287
Leandro Vieiras wrote:
Hi Nick!
It worked! Thank you so much!
And please, update the Nuget Package! hehe

Regards,
Leandro
Hi Leandro, you seems to find the solution to do it works nice. Is it possible for you to show your final script ?
Posts: 287
Nick, Just on the top of your script exemple you say that:
I had some downtime at work, here is a quick and dirty example I came up with. Not sure if it compiles but it should be close. You'll need four Custom Data Set objects, a spreadsheet for the standings and a spreadsheet for the point system, see the script.
I am not sure how to select a custom data set in data of a widget.

I make some test and I can make appears with your script (with some corrections) actual point and updated point from liveposition. I use a ticker widget and I use Standing for Data Set. Bur I think you say something different!
Posts: 785
The standings dataset will always only show drivers that are currently in your session. But for a championship results graphic, there may be people in the championship who are not currently in the session (e.g. they missed this race). You probably will want to show them, hence you need a spreadsheet as the source of drivers, and you need custom datasets as the data source for a ticker widget.

A custom dataset is just an empty list of data which you can fill manually or via a script. You can put anything you want in there just like my script is putting data for championship drivers.

More recently I added better support for custom datasets by using a script as a dataset. That is probably the better solution to use now, but it requires a different script.
Posts: 287
If I understand, for the script show here I just have to add custom dataset with this name (dataset can be empty)
var cds_pos = theme.CustomDataSets.Find("cds_pos"); // for champ position
var cds_name = theme.CustomDataSets.Find("cds_name"); // for driver name
var cds_carnum = theme.CustomDataSets.Find("cds_carnum"); // for car num
var cds_points = theme.CustomDataSets.Find("cds_points"); // for champ points
I test without this custom dataset with 2 csv and the thing I don't have is the driver in the csv that are not in session.
Posts: 785
It is assumed that you have widgets that have these custom datasets as their data set.

But to be honest: this code was a very rough example and should be done much better. Unfortunately I don't have much time today but I will see if I can add a better example soon.
Posts: 287
No problem, I do work this code, I do step by step, thanks.
Posts: 287
Make some test but can't do appears anything with custom data set.

I can show Drivers in session with their old points (come from csv) and new points added. But I use Standings data set.