Add in hooks to update statistical events log for leaderboards.

Modify leaderboard PHP script for new updated leaderboard table formats.
This commit is contained in:
Ty Sayers 2015-02-19 18:53:46 -05:00
parent d29b43d645
commit def101cc8d
8 changed files with 306 additions and 87 deletions

View File

@ -0,0 +1,80 @@
package mineplex.core.leaderboard;
import mineplex.core.MiniPlugin;
import mineplex.core.account.CoreClient;
import mineplex.core.account.CoreClientManager;
import mineplex.core.spawn.command.SpawnCommand;
import org.bukkit.entity.Player;
import org.bukkit.plugin.java.JavaPlugin;
/**
* Manages dynamic Leaderboard statistics.
*
* Used for recording stat events, retrieving customized leaderboards, etc.
* @author MrTwiggy
*
*/
public class LeaderboardManager extends MiniPlugin
{
private static LeaderboardManager _instance; // Singleton instance of Leaderboard Manager
private StatEventsRepository _statEvents; // 'statEvents' table repository.
private CoreClientManager _clientManager;
private String _serverGroup;
/**
* Private class constructor to prevent non-singleton instances.
*/
public LeaderboardManager(JavaPlugin plugin, CoreClientManager clientManager)
{
super("Leaderboard Manager", plugin);
_instance = this;
_clientManager = clientManager;
_statEvents = new StatEventsRepository(plugin);
_serverGroup = _plugin.getConfig().getString("serverstatus.group");
}
/**
* Attempt to trigger a stat event.
* @param player - the player responsible for the statistic
* @param stat - the display name of the statistic to be added
* @param value - the counter value used to increment the statistic
* @return true, if a stat event was successfully triggered and logged, false otherwise.
*/
public boolean attemptStatEvent(Player player, String stat, int gamemode, int value)
{
StatType type = StatType.getType(stat);
return (type == null) ? false : onStatEvent(player, type, gamemode, value);
}
/**
* Trigger a stat event to be recorded.
* @param player - the player responsible for the statistic
* @param type - the unique type id designating the statistic being recorded
* @param gamemode - the unique gamemode id associated with the stat event
* @param value - the counter value used to increment the statistic
* @return true, if the stat event was successfully triggered and logged, false otherwise.
*/
public boolean onStatEvent(Player player, StatType type, int gamemode, int value)
{
_statEvents.insertStatEvent(player.getName(), gamemode, _serverGroup, type.getTypeId(), value);
return true;
}
@Override
public void AddCommands()
{
addCommand(new SetTournamentCommand(this));
}
/**
* @return the singleton instance for {@link LeaderboardManager}.
*/
public static LeaderboardManager getInstance()
{
return _instance;
}
}

View File

@ -0,0 +1,28 @@
package mineplex.core.leaderboard;
import mineplex.core.command.CommandBase;
import mineplex.core.common.Rank;
import org.bukkit.entity.Player;
public class SetTournamentCommand extends CommandBase<LeaderboardManager>
{
public SetTournamentCommand(LeaderboardManager plugin)
{
super(plugin, Rank.ADMIN, "settournament", "set-tournament");
}
@Override
public void Execute(Player caller, String[] args)
{
// TODO: Implement set tournament command.
/*if (args.length == 3)
{
String statType = args[0];
int gamemode = Integer.parseInt(args[1]);
int value = Integer.parseInt(args[2]);
LeaderboardManager.getInstance().attemptStatEvent(caller, statType, gamemode, value);
}
System.out.println("Received call with arg lengths: " + args.length);*/
}
}

View File

@ -0,0 +1,70 @@
package mineplex.core.leaderboard;
import mineplex.core.database.RepositoryBase;
import mineplex.core.database.column.ColumnInt;
import mineplex.core.database.column.ColumnVarChar;
import org.bukkit.plugin.java.JavaPlugin;
/**
* StatEventsRepository offers the ability to insert and log newly generated stat events.
*
* Intended for the purpose of statistical tracking of players.
* @author MrTwiggy
*
*/
public class StatEventsRepository extends RepositoryBase
{
// Insert or update stat events query
/*private static String INSERT_EVENT =
"INSERT INTO statEvents(accountId, gamemode, serverGroup, type, value, date) "
+ "VALUES (?, ?, ?, ?, ?, CURRENT_DATE()) "
+ "ON DUPLICATE KEY UPDATE value=value+";*/
private static String INSERT_EVENT =
"INSERT INTO statEvents(accountId, gamemode, serverGroup, type, value, date) "
+ "SELECT accounts.id, ?, ?, ?, ?, CURRENT_DATE() "
+ "FROM accounts WHERE name = ? "
+ "ON DUPLICATE KEY UPDATE value=value+";
/**
* Class constructor
* @param plugin - the plugin responsible for instantiating this repository.
*/
public StatEventsRepository(JavaPlugin plugin)
{
super(plugin, "jdbc:mysql://db.mineplex.com:3306/Account?autoReconnect=true&failOverReadOnly=false&maxReconnects=10", "root", "tAbechAk3wR7tuTh");
}
@Override
protected void initialize()
{
//executeUpdate(CREATE_EVENTS_TABLE);
//executeUpdate(CREATE_STAT_RELATION_TABLE);
}
@Override
protected void update()
{
}
/**
* Insert (or update) a new stat event record for today into the repository.
* @param accountId - the id of the account responsible for the stat event.
* @param gamemode - the id of the gamemode type at the time of the stat event.
* @param serverGroup - the server group id associated with the stat event.
* @param type - the type of stat event to be inserted (id of type).
* @param value - the integer based value denoting the actual statistic being logged.
*/
public void insertStatEvent(String playerName, int gamemode, String serverGroup, int type, int value)
{
System.out.println("------Attemping stat insert event on " + playerName + " -- " + value);
// Hacky string concatanation - Don't judge me!!
// TODO: How to handle outside value block parameters
executeUpdate(INSERT_EVENT + value + ";", new ColumnInt("gamemode", gamemode), new ColumnVarChar("serverGroup", 100, serverGroup),
new ColumnInt("type", type), new ColumnInt("value", value), new ColumnVarChar("name", 100, playerName));
}
}

View File

@ -0,0 +1,48 @@
package mineplex.core.leaderboard;
/**
* An enumeration delegating the various types of statistics to be dynamically tracked.
* @author MrTwiggy
*
*/
public enum StatType
{
WIN(1),
LOSS(2),
KILL(3),
DEATH(4);
private int _typeId; // Unique id for stat type
public int getTypeId() { return _typeId; }
/**
* Private class constructor
* @param typeId - the unique identifying id for this {@link StatType}
*/
private StatType(int typeId)
{
_typeId = typeId;
}
/**
* @param stat - the display name for the stat type
* @return the {@link StatType} corresponding to the passed in {@code stat}, if one exists,
* null otherwise.
*/
public static StatType getType(String stat)
{
switch(stat.toUpperCase().trim())
{
case "WINS":
return WIN;
case "LOSSES":
return LOSS;
case "KILLS":
return KILL;
case "DEATHS":
return DEATH;
default:
return null;
}
}
}

View File

@ -25,6 +25,7 @@ import mineplex.core.hologram.HologramManager;
import mineplex.core.ignore.IgnoreManager; import mineplex.core.ignore.IgnoreManager;
import mineplex.core.inventory.InventoryManager; import mineplex.core.inventory.InventoryManager;
import mineplex.core.itemstack.ItemStackFactory; import mineplex.core.itemstack.ItemStackFactory;
import mineplex.core.leaderboard.LeaderboardManager;
import mineplex.core.memory.MemoryFix; import mineplex.core.memory.MemoryFix;
import mineplex.core.message.MessageManager; import mineplex.core.message.MessageManager;
import mineplex.core.monitor.LagMeter; import mineplex.core.monitor.LagMeter;
@ -92,6 +93,7 @@ public class Arcade extends JavaPlugin
Creature creature = new Creature(this); Creature creature = new Creature(this);
ServerStatusManager serverStatusManager = new ServerStatusManager(this, _clientManager, new LagMeter(this, _clientManager)); ServerStatusManager serverStatusManager = new ServerStatusManager(this, _clientManager, new LagMeter(this, _clientManager));
LeaderboardManager leaderboardManager = new LeaderboardManager(this, _clientManager);
new Spawn(this, serverStatusManager.getCurrentServerName()); new Spawn(this, serverStatusManager.getCurrentServerName());
Teleport teleport = new Teleport(this); Teleport teleport = new Teleport(this);
Portal portal = new Portal(this, _clientManager, serverStatusManager.getCurrentServerName()); Portal portal = new Portal(this, _clientManager, serverStatusManager.getCurrentServerName());

View File

@ -5,78 +5,78 @@ import org.bukkit.Material;
public enum GameType public enum GameType
{ {
//Mini //Mini
BaconBrawl("Bacon Brawl", Material.PORK, (byte)0, GameCategory.ARCADE), BaconBrawl("Bacon Brawl", Material.PORK, (byte)0, GameCategory.ARCADE, 1),
Barbarians("A Barbarians Life", Material.WOOD_AXE, (byte)0, GameCategory.ARCADE), Barbarians("A Barbarians Life", Material.WOOD_AXE, (byte)0, GameCategory.ARCADE, 2),
Bridge("The Bridges", Material.IRON_PICKAXE, (byte)0, GameCategory.SURVIVAL), Bridge("The Bridges", Material.IRON_PICKAXE, (byte)0, GameCategory.SURVIVAL, 3),
CastleSiege("Castle Siege", Material.DIAMOND_CHESTPLATE, (byte)0, GameCategory.CLASSICS), CastleSiege("Castle Siege", Material.DIAMOND_CHESTPLATE, (byte)0, GameCategory.CLASSICS, 4),
ChampionsTDM("Champions TDM", "Champions", Material.GOLD_SWORD, (byte)0, GameCategory.CHAMPIONS), ChampionsTDM("Champions TDM", "Champions", Material.GOLD_SWORD, (byte)0, GameCategory.CHAMPIONS, 5),
ChampionsDominate("Champions Domination", "Champions", Material.BEACON, (byte)0, GameCategory.CHAMPIONS), ChampionsDominate("Champions Domination", "Champions", Material.BEACON, (byte)0, GameCategory.CHAMPIONS, 6),
ChampionsMOBA("Champions MOBA", "Champions", Material.SKULL_ITEM, (byte)0, GameCategory.CHAMPIONS), ChampionsMOBA("Champions MOBA", "Champions", Material.SKULL_ITEM, (byte)0, GameCategory.CHAMPIONS, 7),
Christmas("Christmas Chaos", Material.SNOW_BALL, (byte)0, GameCategory.CLASSICS), Christmas("Christmas Chaos", Material.SNOW_BALL, (byte)0, GameCategory.CLASSICS, 8),
DeathTag("Death Tag", Material.SKULL_ITEM, (byte)0, GameCategory.ARCADE), DeathTag("Death Tag", Material.SKULL_ITEM, (byte)0, GameCategory.ARCADE, 9),
DragonEscape("Dragon Escape", Material.DRAGON_EGG, (byte)0, GameCategory.ARCADE), DragonEscape("Dragon Escape", Material.DRAGON_EGG, (byte)0, GameCategory.ARCADE, 10),
DragonEscapeTeams("Dragon Escape Teams", Material.DRAGON_EGG, (byte)0, GameCategory.ARCADE), DragonEscapeTeams("Dragon Escape Teams", Material.DRAGON_EGG, (byte)0, GameCategory.ARCADE, 11),
DragonRiders("Dragon Riders", Material.DRAGON_EGG, (byte)0, GameCategory.ARCADE), DragonRiders("Dragon Riders", Material.DRAGON_EGG, (byte)0, GameCategory.ARCADE, 12),
Dragons("Dragons", Material.ENDER_STONE, (byte)0, GameCategory.ARCADE), Dragons("Dragons", Material.ENDER_STONE, (byte)0, GameCategory.ARCADE, 13),
DragonsTeams("Dragons Teams", Material.ENDER_STONE, (byte)0, GameCategory.ARCADE), DragonsTeams("Dragons Teams", Material.ENDER_STONE, (byte)0, GameCategory.ARCADE, 14),
Draw("Draw My Thing", Material.BOOK_AND_QUILL, (byte)0, GameCategory.CLASSICS), Draw("Draw My Thing", Material.BOOK_AND_QUILL, (byte)0, GameCategory.CLASSICS, 15),
Evolution("Evolution", Material.EMERALD, (byte)0, GameCategory.ARCADE), Evolution("Evolution", Material.EMERALD, (byte)0, GameCategory.ARCADE, 16),
FlappyBird("Flappy Bird", Material.FEATHER, (byte)0, GameCategory.ARCADE), FlappyBird("Flappy Bird", Material.FEATHER, (byte)0, GameCategory.ARCADE, 17),
Gravity("Gravity", Material.ENDER_PORTAL, (byte)0, GameCategory.ARCADE), Gravity("Gravity", Material.ENDER_PORTAL, (byte)0, GameCategory.ARCADE, 18),
Halloween("Halloween Horror", Material.PUMPKIN, (byte)0, GameCategory.CLASSICS), Halloween("Halloween Horror", Material.PUMPKIN, (byte)0, GameCategory.CLASSICS, 19),
HideSeek("Block Hunt", Material.GRASS, (byte)0, GameCategory.CLASSICS), HideSeek("Block Hunt", Material.GRASS, (byte)0, GameCategory.CLASSICS, 20),
Horse("Horseback", Material.IRON_BARDING, (byte)0, GameCategory.ARCADE), Horse("Horseback", Material.IRON_BARDING, (byte)0, GameCategory.ARCADE, 21),
SurvivalGames("Survival Games", Material.IRON_SWORD, (byte)0, GameCategory.SURVIVAL), SurvivalGames("Survival Games", Material.IRON_SWORD, (byte)0, GameCategory.SURVIVAL, 22),
SurvivalGamesTeams("Survival Games Teams", Material.IRON_SWORD, (byte)0, GameCategory.SURVIVAL), SurvivalGamesTeams("Survival Games Teams", Material.IRON_SWORD, (byte)0, GameCategory.SURVIVAL, 23),
Micro("Micro Battle", Material.LAVA_BUCKET, (byte)0, GameCategory.ARCADE), Micro("Micro Battle", Material.LAVA_BUCKET, (byte)0, GameCategory.ARCADE, 24),
MineStrike("MineStrike", Material.TNT, (byte)0, GameCategory.CLASSICS), MineStrike("MineStrike", Material.TNT, (byte)0, GameCategory.CLASSICS, 25),
MineWare("MineWare", Material.PAPER, (byte)0, GameCategory.ARCADE), MineWare("MineWare", Material.PAPER, (byte)0, GameCategory.ARCADE, 26),
MilkCow("Milk the Cow", Material.MILK_BUCKET, (byte)0, GameCategory.ARCADE), MilkCow("Milk the Cow", Material.MILK_BUCKET, (byte)0, GameCategory.ARCADE, 27),
Paintball("Super Paintball", Material.ENDER_PEARL, (byte)0, GameCategory.ARCADE), Paintball("Super Paintball", Material.ENDER_PEARL, (byte)0, GameCategory.ARCADE, 28),
Quiver("One in the Quiver", Material.ARROW, (byte)0, GameCategory.ARCADE), Quiver("One in the Quiver", Material.ARROW, (byte)0, GameCategory.ARCADE, 29),
QuiverTeams("One in the Quiver Teams", Material.ARROW, (byte)0, GameCategory.ARCADE), QuiverTeams("One in the Quiver Teams", Material.ARROW, (byte)0, GameCategory.ARCADE, 30),
Runner("Runner", Material.LEATHER_BOOTS, (byte)0, GameCategory.ARCADE), Runner("Runner", Material.LEATHER_BOOTS, (byte)0, GameCategory.ARCADE, 31),
SearchAndDestroy("Search and Destroy", Material.TNT, (byte)0, GameCategory.SURVIVAL), SearchAndDestroy("Search and Destroy", Material.TNT, (byte)0, GameCategory.SURVIVAL, 32),
Sheep("Sheep Quest", Material.WOOL, (byte)4, GameCategory.ARCADE), Sheep("Sheep Quest", Material.WOOL, (byte)4, GameCategory.ARCADE, 33),
Smash("Super Smash Mobs", Material.SKULL_ITEM, (byte)4, GameCategory.CLASSICS), Smash("Super Smash Mobs", Material.SKULL_ITEM, (byte)4, GameCategory.CLASSICS, 34),
SmashTeams("Super Smash Mobs Teams", "Super Smash Mobs", Material.SKULL_ITEM, (byte)4, GameCategory.CLASSICS), SmashTeams("Super Smash Mobs Teams", "Super Smash Mobs", Material.SKULL_ITEM, (byte)4, GameCategory.CLASSICS, 35),
SmashDomination("Super Smash Mobs Domination", "Super Smash Mobs", Material.SKULL_ITEM, (byte)4, GameCategory.CLASSICS), SmashDomination("Super Smash Mobs Domination", "Super Smash Mobs", Material.SKULL_ITEM, (byte)4, GameCategory.CLASSICS, 36),
Snake("Snake", Material.WOOL, (byte)0, GameCategory.ARCADE), Snake("Snake", Material.WOOL, (byte)0, GameCategory.ARCADE, 37),
SneakyAssassins("Sneaky Assassins", Material.INK_SACK, (byte)0, GameCategory.ARCADE), SneakyAssassins("Sneaky Assassins", Material.INK_SACK, (byte)0, GameCategory.ARCADE, 38),
SnowFight("Snow Fight", Material.SNOW_BALL, (byte)0, GameCategory.ARCADE), SnowFight("Snow Fight", Material.SNOW_BALL, (byte)0, GameCategory.ARCADE, 39),
Spleef("Super Spleef", Material.IRON_SPADE, (byte)0, GameCategory.ARCADE), Spleef("Super Spleef", Material.IRON_SPADE, (byte)0, GameCategory.ARCADE, 40),
SpleefTeams("Super Spleef Teams", Material.IRON_SPADE, (byte)0, GameCategory.ARCADE), SpleefTeams("Super Spleef Teams", Material.IRON_SPADE, (byte)0, GameCategory.ARCADE, 41),
Stacker("Super Stacker", Material.BOWL, (byte)0, GameCategory.ARCADE), Stacker("Super Stacker", Material.BOWL, (byte)0, GameCategory.ARCADE, 42),
SquidShooter("Squid Shooter", Material.FIREWORK_CHARGE, (byte)0, GameCategory.ARCADE), SquidShooter("Squid Shooter", Material.FIREWORK_CHARGE, (byte)0, GameCategory.ARCADE, 43),
Tug("Tug of Wool", Material.WHEAT, (byte)0, GameCategory.ARCADE), Tug("Tug of Wool", Material.WHEAT, (byte)0, GameCategory.ARCADE, 44),
TurfWars("Turf Wars", Material.STAINED_CLAY, (byte)14, GameCategory.ARCADE), TurfWars("Turf Wars", Material.STAINED_CLAY, (byte)14, GameCategory.ARCADE, 45),
UHC("Ultra Hardcore", Material.GOLDEN_APPLE, (byte)0, GameCategory.SURVIVAL), UHC("Ultra Hardcore", Material.GOLDEN_APPLE, (byte)0, GameCategory.SURVIVAL, 46),
WitherAssault("Wither Assault", Material.SKULL_ITEM, (byte)1, GameCategory.ARCADE), WitherAssault("Wither Assault", Material.SKULL_ITEM, (byte)1, GameCategory.ARCADE, 47),
Wizards("Wizards", Material.BLAZE_ROD, (byte)0, GameCategory.SURVIVAL), Wizards("Wizards", Material.BLAZE_ROD, (byte)0, GameCategory.SURVIVAL, 48),
ZombieSurvival("Zombie Survival", Material.SKULL_ITEM, (byte)2, GameCategory.SURVIVAL); ZombieSurvival("Zombie Survival", Material.SKULL_ITEM, (byte)2, GameCategory.SURVIVAL, 49);
String _name; String _name;
String _lobbyName; String _lobbyName;
Material _mat; Material _mat;
byte _data; byte _data;
GameCategory _gameCategory; GameCategory _gameCategory;
private int _gameId; // Unique identifying id for this gamemode (used for statistics)
public int getGameId() { return _gameId; }
GameType(String name, Material mat, byte data, GameCategory gameCategory) GameType(String name, Material mat, byte data, GameCategory gameCategory, int gameId)
{ {
_name = name; this(name, name, mat, data, gameCategory, gameId);
_lobbyName = name;
_mat = mat;
_data = data;
_gameCategory = gameCategory;
} }
GameType(String name, String lobbyName, Material mat, byte data, GameCategory gameCategory) GameType(String name, String lobbyName, Material mat, byte data, GameCategory gameCategory, int gameId)
{ {
_name = name; _name = name;
_lobbyName = lobbyName; _lobbyName = lobbyName;
_mat = mat; _mat = mat;
_data = data; _data = data;
_gameCategory = gameCategory; _gameCategory = gameCategory;
_gameId = gameId;
} }
public String GetName() public String GetName()

View File

@ -4,6 +4,7 @@ import java.util.HashMap;
import java.util.UUID; import java.util.UUID;
import mineplex.core.common.util.C; import mineplex.core.common.util.C;
import mineplex.core.leaderboard.LeaderboardManager;
import nautilus.game.arcade.ArcadeManager; import nautilus.game.arcade.ArcadeManager;
import nautilus.game.arcade.events.GameStateChangeEvent; import nautilus.game.arcade.events.GameStateChangeEvent;
import nautilus.game.arcade.game.Game.GameState; import nautilus.game.arcade.game.Game.GameState;
@ -60,7 +61,13 @@ public class GameStatManager implements Listener
{ {
for (String stat : event.GetGame().GetStats().get(player).keySet()) for (String stat : event.GetGame().GetStats().get(player).keySet())
{ {
Manager.GetStatsManager().incrementStat(player, stat, event.GetGame().GetStats().get(player).get(stat)); int value = event.GetGame().GetStats().get(player).get(stat);
Manager.GetStatsManager().incrementStat(player, stat, value);
// Leaderboard hook for logging appropriate stat events
// Note: Rejects stat events that are not of the appropriate types.
int gameId = event.GetGame().GetType().getGameId();
LeaderboardManager.getInstance().attemptStatEvent(player, stat.split("\\.")[1], gameId, value);
} }
} }
} }

View File

@ -27,8 +27,8 @@
<div class="jumbotron" style="text-align: center"> <div class="jumbotron" style="text-align: center">
<div class="container" style="text-align: center"> <div class="container" style="text-align: center">
<img src="mineplex.png" alt="Mineplex"/> <img src="mineplex.png" alt="Mineplex"/>
<h1>The Fall Invitational</h1> <h1>The Tournament</h1>
<h2>Tournament Leaderboard</h2> <h2>Leaderboard</h2>
</div> </div>
</div> </div>
@ -39,9 +39,10 @@
<?php <?php
$tournamentTypes = array('Super Smash Mobs', 'Survival Games', 'Mixed Arcade'); $tournamentTypes = array('Tournament');
$con = mysqli_connect('db.mineplex.com', 'root', 'tAbechAk3wR7tuTh', 'Account'); $con = mysqli_connect('db.mineplex.com', 'root', 'tAbechAk3wR7tuTh', 'Account');
//$con = mysqli_connect('localhost', 'root', 'ZXquwQyEdKMB', 'Development');
for ($i = 0; $i < count($tournamentTypes); $i++) for ($i = 0; $i < count($tournamentTypes); $i++)
{ {
@ -60,49 +61,32 @@ for ($i = 0; $i < count($tournamentTypes); $i++)
<th class="center">Rank</th> <th class="center">Rank</th>
<th>Player</th> <th>Player</th>
<th class="center">Wins</th> <th class="center">Wins</th>
<th class="center">Losses</th>
<th class="center">Score*</th>
</tr> </tr>
<?php <?php
$query = <<<QUERY $query = <<<QUERY
SELECT name, wins, total, score SELECT LB.rank, accounts.name, LB.value AS 'wins'
FROM tournamentLeaderboard FROM TournamentLB LB
JOIN accounts ON accounts.id = accountId INNER JOIN accounts ON accounts.id = accountId
WHERE tournamentId = 0 ORDER BY rank ASC
AND gameId = $i
AND score IS NOT NULL
ORDER BY score DESC, name ASC
LIMIT 30; LIMIT 30;
QUERY; QUERY;
$result = mysqli_query($con, $query); $result = mysqli_query($con, $query);
$index = 1; $index = 1;
$rank = 1;
$lastScore = null;
while($row = mysqli_fetch_array($result)) while($row = mysqli_fetch_array($result))
{ {
$score = $row['score'];
if ($score != $lastScore)
{
$rank = $index;
}
?> ?>
<tr> <tr>
<td class="center"><?php echo $rank ?></td> <td class="center"><?php echo $row['rank'] ?></td>
<td><?php echo $row['name'] ?></td> <td><?php echo $row['name'] ?></td>
<td class="center"><?php echo $row['wins'] ?></td> <td class="center"><?php echo $row['wins'] ?></td>
<td class="center"><?php echo ($row['total'] - $row['wins']) ?></td>
<td class="center"><?php echo round($score*100, 3) ?></td>
</tr> </tr>
<?php <?php
$index++; $index++;
$lastScore = $score;
} }
?> ?>