Add score recording to SuperTuxKart

This commit is contained in:
DeathByDenim 2023-09-16 15:28:56 -04:00
parent 9dc5e6e3e6
commit a4bc499cbc
Signed by: DeathByDenim
GPG Key ID: 89185F675E0AB7D5
6 changed files with 683 additions and 14 deletions

287
configs/stkranking.patch Normal file
View File

@ -0,0 +1,287 @@
diff --git a/src/network/protocols/server_lobby.cpp b/src/network/protocols/server_lobby.cpp
index 3516ffe03..aa3fd698b 100644
--- a/src/network/protocols/server_lobby.cpp
+++ b/src/network/protocols/server_lobby.cpp
@@ -249,6 +249,7 @@ ServerLobby::ServerLobby() : LobbyProtocol()
m_game_mode.store(ServerConfig::m_server_mode);
m_default_vote = new PeerVote();
m_player_reports_table_exists = false;
+ m_grand_prix_rowid = -1;
initDatabase();
} // ServerLobby
@@ -389,6 +390,51 @@ void ServerLobby::initServerStatsTable()
") WITHOUT ROWID;", country_table_name.c_str());
easySQLQuery(query);
+ // Extra default table _grandprixresults:
+ std::string grandprixresults_table_name = std::string("v") +
+ StringUtils::toString(ServerConfig::m_server_db_version) + "_" +
+ ServerConfig::m_server_uid + "_grandprixresults";
+ query = StringUtils::insertValues(
+ "CREATE TABLE IF NOT EXISTS %s (\n"
+ " num_races INTEGER NOT NULL, -- Number of races completed\n"
+ " total_races INTEGER NOT NULL -- Total number of races\n"
+ ");", grandprixresults_table_name.c_str());
+ if(easySQLQuery(query)) {
+ m_server_grandprixresults_table = grandprixresults_table_name;
+ }
+
+ // Extra default table _raceresults:
+ std::string raceresults_table_name = std::string("v") +
+ StringUtils::toString(ServerConfig::m_server_db_version) + "_" +
+ ServerConfig::m_server_uid + "_raceresults";
+ query = StringUtils::insertValues(
+ "CREATE TABLE IF NOT EXISTS %s (\n"
+ " race_finished TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, -- Time when race was completed\n"
+ " grandprix_rowid INTEGER, -- ROWID of the grand prix this race is part of (optional)\n"
+ " track_name TEXT NOT NULL, -- Number of laps completed\n"
+ " num_laps INTEGER NOT NULL, -- Number of laps completed\n"
+ " total_laps INTEGER NOT NULL, -- Total number of laps\n"
+ " fastest_lap_time FLOAT NOT NULL, -- Fastest lap so far\n"
+ " fastest_lap_player TEXT NOT NULL -- Fastest lap so far\n"
+ ");", raceresults_table_name.c_str());
+ if(easySQLQuery(query)) {
+ m_server_raceresults_table = raceresults_table_name;
+ }
+
+ // Extra default table _raceresults:
+ std::string playerresults_table_name = std::string("v") +
+ StringUtils::toString(ServerConfig::m_server_db_version) + "_" +
+ ServerConfig::m_server_uid + "_playerresults";
+ query = StringUtils::insertValues(
+ "CREATE TABLE IF NOT EXISTS %s (\n"
+ " race_rowid INTEGER NOT NULL, -- ROWID of the race\n"
+ " elapsed_time FLOAT NOT NULL, -- Total race time\n"
+ " player_name TEXT NOT NULL -- Player name\n"
+ ") WITHOUT ROWID;", playerresults_table_name.c_str());
+ if(easySQLQuery(query)) {
+ m_server_playerresults_table = playerresults_table_name;
+ }
+
// Default views:
// _full_stats
// Full stats with ip in human readable format and time played of each
@@ -2884,6 +2930,7 @@ void ServerLobby::checkRaceFinished()
RaceEventManager::get()->getProtocol()->requestTerminate();
GameProtocol::lock()->requestTerminate();
+
// Save race result before delete the world
m_result_ns->clear();
m_result_ns->addUInt8(LE_RACE_FINISHED);
@@ -2895,10 +2942,13 @@ void ServerLobby::checkRaceFinished()
m_result_ns->addUInt32(fastest_lap);
m_result_ns->encodeString(static_cast<LinearWorld*>(World::getWorld())
->getFastestLapKartName());
+ std::cout << static_cast<LinearWorld*>(World::getWorld())->getFastestLapKartName().c_str() << std::endl;
+ Log::info("RaceResults", "Fastest lap by %s", StringUtils::wideToUtf8(static_cast<LinearWorld*>(World::getWorld())->getFastestLapKartName()).c_str());
// all gp tracks
m_result_ns->addUInt8((uint8_t)m_game_setup->getTotalGrandPrixTracks())
.addUInt8((uint8_t)m_game_setup->getAllTracks().size());
+ Log::info("RaceResults", "Track number %d / %d", m_game_setup->getAllTracks().size(), m_game_setup->getTotalGrandPrixTracks());
for (const std::string& gp_track : m_game_setup->getAllTracks())
m_result_ns->encodeString(gp_track);
@@ -2917,9 +2967,13 @@ void ServerLobby::checkRaceFinished()
overall_time = overall_time + player->getOverallTime();
player->setScore(cur_score);
player->setOverallTime(overall_time);
+ Log::info("RaceResults", "Score for %s from %d to %d (%f)", StringUtils::wideToUtf8(player->getName()).c_str(), last_score, cur_score, overall_time);
+ }
+ else {
+ Log::info("RaceResults", "Score for player %d from %d to %d (%f)", i, last_score, cur_score, overall_time);
}
m_result_ns->addUInt32(last_score).addUInt32(cur_score)
- .addFloat(overall_time);
+ .addFloat(overall_time);
}
}
else if (RaceManager::get()->modeHasLaps())
@@ -2936,6 +2990,8 @@ void ServerLobby::checkRaceFinished()
ranking_changes_indication = 1;
m_result_ns->addUInt8(ranking_changes_indication);
+ writeRaceResults();
+
if (ServerConfig::m_ranked)
{
computeNewRankings();
@@ -2944,6 +3000,148 @@ void ServerLobby::checkRaceFinished()
m_state.store(WAIT_FOR_RACE_STOPPED);
} // checkRaceFinished
+int ServerLobby::getLastRowID(const std::string &table) {
+ int rowid = -1;
+ std::string query = StringUtils::insertValues("SELECT ROWID from %s ORDER BY ROWID DESC LIMIT 1;", table);
+
+ sqlite3_stmt* stmt = NULL;
+ int ret = sqlite3_prepare_v2(m_db, query.c_str(), -1, &stmt, 0);
+ if (ret == SQLITE_OK)
+ {
+ ret = sqlite3_step(stmt);
+ if (ret == SQLITE_ROW)
+ {
+ rowid = sqlite3_column_int(stmt, 0);
+ }
+ ret = sqlite3_finalize(stmt);
+ if (ret != SQLITE_OK)
+ {
+ Log::error("ServerLobby",
+ "Error rowid database for query %s: %s",
+ query.c_str(), sqlite3_errmsg(m_db));
+ }
+ }
+ else
+ {
+ Log::error("ServerLobby", "Error preparing database for query %s: %s",
+ query.c_str(), sqlite3_errmsg(m_db));
+ return -1;
+ }
+ return rowid;
+
+}
+
+//-----------------------------------------------------------------------------
+/** Write the results of the race
+ */
+void ServerLobby::writeRaceResults()
+{
+#ifdef ENABLE_SQLITE3
+ std::string track_name = RaceManager::get()->getTrackName();
+ bool track_reverse = RaceManager::get()->getReverseTrack();
+ int player_count = RaceManager::get()->getNumPlayers();
+ int laps_number = RaceManager::get()->getNumLaps();
+
+ if (RaceManager::get()->getMinorMode() == RaceManager::MINOR_MODE_NORMAL_RACE) {
+ std::string query;
+ if (m_game_setup->isGrandPrix()) {
+ int race_number = m_game_setup->getAllTracks().size();
+ int total_races = m_game_setup->getTotalGrandPrixTracks();
+ if(race_number == 1) {
+ query = StringUtils::insertValues(
+ "INSERT INTO %s "
+ "(num_races, total_races) "
+ "VALUES (%d, %d);",
+ m_server_grandprixresults_table,
+ race_number,
+ total_races
+ );
+ if(easySQLQuery(query)) {
+ m_grand_prix_rowid = getLastRowID(m_server_grandprixresults_table);
+ }
+ }
+ else {
+ query = StringUtils::insertValues(
+ "UPDATE %s "
+ "SET num_races = %d "
+ "WHERE ROWID = %d;",
+ m_server_grandprixresults_table,
+ race_number,
+ m_grand_prix_rowid
+ );
+ easySQLQuery(query);
+ }
+ }
+
+ if(m_grand_prix_rowid >= 0) {
+ query = StringUtils::insertValues(
+ "INSERT INTO %s "
+ "(grandprix_rowid, track_name, num_laps, total_laps, fastest_lap_time, fastest_lap_player) "
+ "VALUES (%d, \"%s\", %d, %d, %f, \"%s\");",
+ m_server_raceresults_table,
+ m_grand_prix_rowid,
+ track_name,
+ laps_number,
+ 0,
+ 0.,
+ ""
+ );
+ }
+ else {
+ query = StringUtils::insertValues(
+ "INSERT INTO %s "
+ "(track_name, num_laps, total_laps, fastest_lap_time, fastest_lap_player) "
+ "VALUES (\"%s\", %d, %d, %f, \"%s\");",
+ m_server_raceresults_table,
+ track_name,
+ laps_number,
+ 0,
+ 0.,
+ ""
+ );
+ }
+ if(easySQLQuery(query)) {
+ int race_rowid = getLastRowID(m_server_raceresults_table);
+ Log::info("RaceResults", "Row ID: %d", race_rowid);
+ if(race_rowid >= 0) {
+ for (int i = 0; i < player_count; i++) {
+ double elapsed_time = RaceManager::get()->getKartRaceTime(i);
+ std::string player_name = StringUtils::wideToUtf8(
+ RaceManager::get()->getKartInfo(i).getPlayerName());
+ //int grand_prix_rank = RaceManager::get()->getKartGPRank(i);
+ query = StringUtils::insertValues(
+ "INSERT INTO %s "
+ "(race_rowid, elapsed_time, player_name) "
+ "VALUES (%d, %f, ?);",
+ m_server_playerresults_table,
+ race_rowid,
+ elapsed_time
+ );
+ easySQLQuery(query, [player_name](sqlite3_stmt* stmt) {
+ if (sqlite3_bind_text(stmt, 1, player_name.c_str(),
+ -1, SQLITE_TRANSIENT) != SQLITE_OK)
+ {
+ Log::error("easySQLQuery", "Failed to bind %s.",
+ player_name.c_str());
+ }
+ });
+ }
+ }
+ }
+
+ if(m_game_setup->isGrandPrix()) {
+ int race_number = m_game_setup->getAllTracks().size();
+ int total_races = m_game_setup->getTotalGrandPrixTracks();
+
+ if(race_number == total_races) {
+ m_grand_prix_rowid = -1;
+ }
+ }
+ }
+#endif
+}
+
+
//-----------------------------------------------------------------------------
/** Compute the new player's rankings used in ranked servers
*/
diff --git a/src/network/protocols/server_lobby.hpp b/src/network/protocols/server_lobby.hpp
index 53d3aceda..780ef2d4e 100644
--- a/src/network/protocols/server_lobby.hpp
+++ b/src/network/protocols/server_lobby.hpp
@@ -85,6 +85,14 @@ private:
std::string m_server_stats_table;
+ std::string m_server_grandprixresults_table;
+
+ std::string m_server_raceresults_table;
+
+ std::string m_server_playerresults_table;
+
+ int m_grand_prix_rowid;
+
bool m_ip_ban_table_exists;
bool m_ipv6_ban_table_exists;
@@ -377,6 +385,8 @@ private:
void testBannedForOnlineId(STKPeer* peer, uint32_t online_id) const;
void writeDisconnectInfoTable(STKPeer* peer);
void writePlayerReport(Event* event);
+ int getLastRowID(const std::string &table);
+ void writeRaceResults();
bool supportsAI();
void updateAddons();
public:

View File

@ -19,6 +19,11 @@ set -e
echo "Installing SuperTuxKart ${stk_version}"
apt-get install --assume-yes build-essential cmake libbluetooth-dev libsdl2-dev \
libcurl4-openssl-dev libenet-dev libfreetype6-dev libharfbuzz-dev \
libjpeg-dev libogg-dev libopenal-dev libpng-dev \
libssl-dev libvorbis-dev libmbedtls-dev pkg-config zlib1g-dev subversion
if [ -e /etc/systemd/system/supertuxkart.service ]; then
systemctl stop supertuxkart
fi
@ -31,10 +36,28 @@ fi
stk_dir="/opt/SuperTuxKart-${stk_version}"
mkdir -p ${stk_dir}
curl --location "https://github.com/supertuxkart/stk-code/releases/download/${stk_version}/SuperTuxKart-${stk_version}-linux-x86_64.tar.xz" | tar --extract --xz --no-same-owner --strip-components=1 --directory=${stk_dir}
builddir=""${TMPDIR:-/tmp}/stk-build""
if [ -d "$builddir" ];
rm -rf "$builddir"
fi
mkdir -p "$builddir"
curl --location "https://github.com/supertuxkart/stk-code/archive/refs/tags/${stk_version}.tar.gz" | tar --extract --gz --no-same-owner --directory=$builddir
svn co https://svn.code.sf.net/p/supertuxkart/code/stk-assets ${builddir}/stk-assets
patch -p1 < ~jarno/stkranking.patch
mkdir -p "$builddir"/build
cd "$builddir"/build
cmake ../stk-code-${stk_version}/ -DCMAKE_INSTALL_PREFIX=/opt/SuperTuxKart-${stk_version}/ -DSERVER_ONLY=On
make -j 2
make install
ln -s ${stk_dir}/bin/supertuxkart /usr/games/supertuxkart
# Configuration
cp $(dirname $0)/../configs/supertuxkart.xml /etc/supertuxkart.xml
mkdir -p /etc/supertuxkart
cp "$(dirname $0)"/../configs/supertuxkart.xml /etc/supertuxkart/supertuxkart.xml
touch /etc/supertuxkart/stkservers.db
chown -R ${systemuser}: /etc/supertuxkart
# Create SystemD unit
cat > /etc/systemd/system/supertuxkart.service <<EOF
@ -43,7 +66,30 @@ Description=SuperTuxKart server
After=network.target
[Service]
ExecStart=${stk_dir}/run_game.sh --server-config=/etc/supertuxkart.xml --lan-server=onFOSS
ExecStart=${stk_dir}/bin/supertuxkart --server-config=/etc/supertuxkart/supertuxkart.xml --lan-server=onFOSS
Restart=on-failure
User=${systemuser}
[Install]
WantedBy=multi-user.target
EOF
# SuperTuxKart scoring
cp "$(dirname $0)"/../services/supertuxkartscores.py ${stk_dir}
cat > /etc/nginx/gameserver.d/supertuxkart.conf <<EOF
location /dynamic/supertuxkartscore.json {
proxy_pass http://localhost:9985/;
}
EOF
cat > /etc/systemd/system/supertuxkartscores.service <<EOF
[Unit]
Description=SuperTuxKart score service
After=network.target
[Service]
ExecStart=${stk_dir}/supertuxkartscores.py
Restart=on-failure
User=${systemuser}

39
services/supertuxkartscores.py Executable file
View File

@ -0,0 +1,39 @@
#!/usr/bin/env python3
import sqlite3
import json
from http.server import HTTPServer, BaseHTTPRequestHandler
CONFIGNAME="supertuxkart"
DATABASEFILE='/etc/supertuxkart/stkservers.db'
def results_as_json():
cx = sqlite3.connect(DATABASEFILE)
grandprixes = []
grandprix_cursor = cx.cursor()
for grandprix_row in grandprix_cursor.execute(f'SELECT ROWID, num_races, total_races FROM v1_{CONFIGNAME}_grandprixresults'):
races = []
race_cursor = cx.cursor()
for race_row in race_cursor.execute(f'SELECT ROWID, track_name FROM v1_{CONFIGNAME}_raceresults WHERE grandprix_rowid = {grandprix_row[0]}'):
race = {"track": race_row[1], "players": []}
player_cursor = cx.cursor()
for player_row in player_cursor.execute(f'SELECT elapsed_time, player_name FROM v1_your_config_playerresults WHERE race_rowid = {race_row[0]} ORDER BY elapsed_time ASC'):
race['players'].append({"name": player_row[1], "time": player_row[0]})
races.append(race)
grandprixes.append({"num_races": grandprix_row[1], "total_races": grandprix_row[2], "races": races})
cx.close()
return json.dumps(grandprixes, indent=None, separators=(',', ':'))
class STKStatServer(BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.send_header("Access-Control-Allow-Origin", "*")
self.end_headers()
self.wfile.write(results_as_json().encode('utf-8'))
if __name__ == "__main__":
server = HTTPServer(("127.0.0.1", 9985), STKStatServer)
server.serve_forever()

View File

@ -1,4 +1,26 @@
- date: 2024-05-06T12:00:00UTC
- date: 2023-09-23T12:00:00UTC
intro: "onFOSS-LAN hosted by DeathByDenim"
games:
- time: "12:00"
title: "Get and chill together"
- time: "13:00"
title: "FTEQW"
- time: "14:30"
title: "Hypersomnia"
- time: "15:30"
title: "Break"
- time: "16:00"
title: "SuperTuxKart"
tournament: true
- time: "17:00"
title: "Break"
- time: "17:30"
title: "Bzflag"
- time: "18:30"
title: "Lix"
- time: "19:30"
title: "Free Play!"
- date: 2023-05-06T12:00:00UTC
intro: "onFOSS-LAN hosted by hribhrib"
games:
- time: "12:00"
@ -92,8 +114,170 @@
<li>Anonymous: &euro;40</li>
</ul>
- date: 2022-02-12T15:00UTC
intro: "Session to test if it all works as intended"
intro: "Session to test if it all works as intended! hosted by DeathByDenim, first time hosted by someone else"
games:
- title: "OpenSpades"
- title: "OpenHV"
- title: "Xonotic"
- date: 2022-01-10T12:00:00UTC
intro: "onFOSS-LAN: powered by LibreGaming! hosted by LibreGaming, first group-effort event"
games:
- time: "12:00"
title: "Meetup on Mumble and chill gaming"
- time: "13:00"
title: "Break"
- time: "14:00"
title: "New to onFOSS-LAN (UFO:AI, Soldat)"
- time: "15:30"
title: "Break"
- time: "16:00"
title: "SuperTuxKart Tournament"
- time: "18:00"
title: "Break"
- time: "18:30"
title: "Good old classics (Lix, Hedgewars)"
- time: "20:00"
title: "Late night gaming"
extras: ["Soldat", "UFO:AI","Lix","Hedgewars","SuperTuxKart"]
- date: 2021-12-19T11:00:00UTC
intro: "onFOSS-LAN: Double the fun! hosted by hribhrib, first event over two days, livestream by opensource_gaming"
games:
- time: "13:00"
title: "Meetup on Mumble and chill gaming"
- time: "15:30"
title: "Break"
- time: "16:00"
title: "Teeworlds Tournament - Finale LIVESTREAM"
- date: 2021-12-18T11:00:00UTC
intro: "onFOSS-LAN: Double the fun! hosted by hribhrib, first event over two days, livestream by opensource_gaming"
games:
- time: "11:00"
title: "Meetup on Mumble and chill gaming"
- time: "14:00"
title: "Break"
- time: "15:00"
title: "Teeworlds Tournament - Playoffs LIVESTREAM"
- time: "18:00"
title: "Break"
- time: "19:00"
title: "Late night gaming"
extras: ["Teeworlds", "OpenHV","Hedgewars","SuperTuxKart","Mindustry","OpenSpades","Unvanquished"]
- date: 2021-11-13T10:00:00UTC
intro: "onFOSS-LAN: Casual Saturday! hosted by hribhrib"
games:
- time: "10:00"
title: "Meetup on Mumble and chill gaming"
- time: "13:00"
title: "Break"
- time: "14:00"
title: "Casual gaming"
- time: "17:00"
title: "Break"
- time: "18:00"
title: "Late night gaming"
extras: ["BZFlag", "Hedgewars","Mindustry","SuperTuxKart"]
- date: 2021-09-11T10:00:00UTC
intro: "onFOSS-LAN: spoRTSmanship! hosted by hribhrib, livestream by opensource_gaming"
games:
- time: "10:00"
title: "Meetup on Mumble and chill gaming"
- time: "13:00"
title: "Break"
- time: "14:00"
title: "OpenHV tournament (LIVESTREAM)"
- time: "17:00"
title: "Break"
- time: "17:30"
title: "OpenHV finals (LIVESTREAM)"
- time: "19:00"
title: "Break"
- time: "20:00"
title: "Late night gaming"
extras: ["0ad", "OpenHV","Widelands"]
- date: 2021-07-10T10:00:00UTC
intro: "onFOSS-LAN: Race for the loot! hosted by hribhrib"
games:
- time: "10:00"
title: "Meetup on Mumble and chill gaming"
- time: "13:00"
title: "Break"
- time: "14:00"
title: "Veloren Loot and Level"
- time: "17:00"
title: "Break"
- time: "18:00"
title: "Veloren final Dungeon (LIVESTREAM)"
- time: "20:00"
title: "Late night gaming"
extras: ["SuperTuxKart", "armagetron","Minetest"]
- date: 2021-05-22T10:00:00UTC
intro: "onFOSS-LAN: Live and Reloaded! hosted by hribhrib, first livestream by Murks"
games:
- time: "10:00"
title: "Meetup on Mumble and chill gaming"
- time: "13:00"
title: "Break"
- time: "14:00"
title: "Teeworlds tournament (LIVESTREAM)"
- time: "17:00"
title: "Break"
- time: "17:30"
title: "Teeworlds finals (LIVESTREAM)"
- time: "19:00"
title: "Break"
- time: "20:00"
title: "Late night gaming"
extras: ["OpenSpades", "Xonotic","Minetest"]
- date: 2021-04-17T10:30:00UTC
intro: "onFOSS-LAN hosted by hribhrib, first public onFOSS-LAN!"
games:
- time: "10:30"
title: "Meetup on Mumble and chill gaming"
- time: "13:00"
title: "OpenRA tournament"
- time: "16:00"
title: "Break"
- time: "17:00"
title: "Hedgewars tournament"
- time: "20:00"
title: "Break"
- time: "21:00"
title: "Late night gaming"
extras: ["Minetest", "SuperTuxKart (Race and Soccer modes)","Teeworlds"]
- date: 2021-03-06T10:30:00UTC
intro: "onFOSS-LAN hosted by hribhrib, private"
games:
- time: "10:30"
title: "Meetup on Mumble and chill gaming"
- time: "13:00"
title: "test warzone2100"
- time: "15:00"
title: "OpenRA - Free for all (12player max)"
- time: "17:00"
title: "OpenRA - 1v1/2v2/3v3 tournaments"
- time: "21:00"
title: "Late night gaming"
- date: 2021-01-23T10:30:00UTC
intro: "onFOSS-LAN hosted by hribhrib, private"
games:
- time: "10:30"
title: "Meetup on Mumble and chill gaming"
- time: "13:00"
title: "supertuxkart turnament (8player max)"
- time: "15:00"
title: "hedgewars turnament (8player max)"
- time: "17:00"
title: "teeworlds together"
- time: "19:00"
title: "0ad US vs BOTS (6player max or 8 without bots)"
- time: "21:00"
title: "Late night gaming"
- date: 2021-01-02T10:30:00UTC
intro: "onFOSS-LAN hosted by hribhrib, private"
games:
- time: "10:30"
title: "Doors open, Welcoming and troubleshooting games if not running, talk, play"
- time: "12:00"
title: "Lunch together (or alone)"
- time: "13:00"
title: "GAMES"

View File

@ -0,0 +1,111 @@
let grandprix_blocks;
let races;
function positionToScore(i) {
switch(i) {
case 0:
return 4;
case 1:
return 2;
case 2:
return 1;
default:
return 0;
}
}
function supertuxkartScoreUpdate() {
d3.json("/dynamic/supertuxkartscore.json").then((data) => {
grandprix_blocks = d3.select('#supertuxkart-results')
.selectAll('div.grandprix-div')
.data(data)
.join(
(enter) => {
const e = enter.append('div')
.classed('grandprix-div', true);
e.append('h3')
.text((d, i) => 'Grand prix ' + (i+1));
e.append('div').classed('races', true);
return e;
},
(update) => {
update.select('h3')
.text((d, i) => 'Grand prix ' + (i+1));
return update;
},
(exit) => {
exit.remove();
}
);
races = grandprix_blocks.select('div.races')
.selectAll('table')
.data((d) => {
return d.races.map((x) => {
x["total"] = d.total_races;
return x;
});
})
.join(
(enter) => {
const table = enter.append('table')
.classed('table', true);
let thead = table.append('thead');
thead.append('tr')
.append('th')
.attr('colspan', 4)
.text((d, i) => {
return "Race " + (i+1) + " out of " + d.total + " on " + d.track;
});
let headerrows = thead.append('tr');
["Rank", "Name", "Time", "Points"].forEach((col) => {
headerrows.append('th').text(col);
});
table.append('tbody')
.classed('grandprix-tbody', true);
return table;
},
(update) => {
const u = update;
update.select('th[colspan="4"]')
.text((d, i) => {
return "Race " + (i+1) + " out of " + d.total + " on " + d.track;
});
return u;
},
(exit) => {
exit.remove();
}
);
let tbodies = races.select('tbody.grandprix-tbody')
.selectAll('tr')
.data((d) => d.players)
.join(
(enter) => {
let e = enter.append('tr');
e.append('td')
.text((d,i) => (i+1) + '.');
e.append('td')
.text((d,i) => d.name);
e.append('td')
.text((d,i) => d.time);
e.append('td')
.text((d,i) => positionToScore(i));
return e;
},
(update) => {
update.select('td:nth-child(1)')
.text((d,i) => {console.log(d); return (i+1) + '.'});
update.select('td:nth-child(2)')
.text((d,i) => d.name);
update.select('td:nth-child(3)')
.text((d,i) => d.time);
update.select('td:nth-child(4)')
.text((d,i) => positionToScore(i));
return update;
},
(exit) => exit.remove()
)
});
}

View File

@ -3,6 +3,7 @@ layout: default
nav_pill: tournament
---
<script src="js/xonscore.js"></script>
<script src="js/supertuxkartscore.js"></script>
<h1>Tournament</h1>
{% assign sitetime = site.time | date: "%FT%T" %}
{% assign nextevent = site.data.events | where_exp: "item", "item.date >= sitetime" | last %}
@ -25,19 +26,20 @@ nav_pill: tournament
{% endfor %}
</ul>
<h2>Ranking</h2>
<div id="supertuxkart-results"></div>
<script>
if(typeof d3 === 'undefined') {
document.write('<p><a href="supertuxkartscore.json">Results</a> (allow access to d3js.org for dynamic updates)</p>');
}
else {
supertuxkartScoreUpdate();
//setInterval(supertuxkartScoreUpdate, 10000);
}
</script>
<noscript><p><a href="supertuxkartscore.json">Results</a> (Enable JavaScript for dynamic updates)</p></noscript>
<ol id="xonotic-ranking"></ol>
<h2>Rounds</h2>
<div id="xonotic-results"></div>
<script>
if(typeof d3 === 'undefined') {
document.write('<p><a href="xonscore.txt">Results</a> (allow access to d3js.org for dynamic updates)</p>');
}
else {
xonoticScoreUpdate();
setInterval(xonoticScoreUpdate, 10000);
}
</script>
<noscript><p><a href="xonscore.txt">Results</a> (Enable JavaScript for dynamic updates)</p></noscript>
{% else %}
<p>No tournaments have been planned for the next event.</p>
{% endif %}