diff --git a/website/api/manager/ManagerAPI.php b/website/api/manager/ManagerAPI.php index 06ede14a3..3994d667f 100644 --- a/website/api/manager/ManagerAPI.php +++ b/website/api/manager/ManagerAPI.php @@ -131,11 +131,12 @@ protected function compile() { if($didCompile == 1) { // Did succeed $this->sendNotification($user, "Compilation Success", "

Your bot was sucessfully compiled on our servers as a program written in {$language}. Within a few minutes, your bot will begin playing games against other contestant's programs. Replays of these games will show up on your homepage.

", 1); - $this->insert("UPDATE User SET numSubmissions=numSubmissions+1, numGames=0, mu = 25.000, sigma = 8.333, compileStatus = 0, isRunning = 1, language = '".$this->mysqli->real_escape_string($language)."' WHERE userID = ".$this->mysqli->real_escape_string($userID)); + $this->insert("UPDATE User SET numSubmissions=numSubmissions+1, numGames=0, mu = 25.000, sigma = 8.333, compileStatus = 0, isRunning = 1, language = '".$this->mysqli->real_escape_string($language)."', compileTime = NOW() WHERE userID = ".$this->mysqli->real_escape_string($userID)); if(intval($user['numSubmissions']) != 0) { $numActiveUsers = mysqli_query($this->mysqli, "SELECT userID FROM User WHERE isRunning=1")->num_rows; - $this->insert("INSERT INTO UserHistory (userID, versionNumber, lastRank, lastNumPlayers, lastNumGames) VALUES ({$user['userID']}, {$user['numSubmissions']}, {$user['rank']}, $numActiveUsers, {$user['numGames']})"); + $compileTime = $user['compileTime'] == NULL ? 'NULL' : "'{$user['compileTime']}'"; + $this->insert("INSERT INTO UserHistory (userID, versionNumber, compileTime, lastRank, lastNumPlayers, lastNumGames) VALUES ({$user['userID']}, {$user['numSubmissions']}, $compileTime, {$user['rank']}, $numActiveUsers, {$user['numGames']})"); } } else { // Didnt succeed $this->sendNotification($user, "Compilation Failure", "

The bot that you recently submitted to the Halite competition would not compile on our servers.

Our autocompile script thought that your bot was written in \"{$language}\".

Here is a description of the compilation error:

{$_POST['errors']}
", -1); diff --git a/website/api/web/WebsiteAPI.php b/website/api/web/WebsiteAPI.php index 064e449cd..fd2820e0a 100644 --- a/website/api/web/WebsiteAPI.php +++ b/website/api/web/WebsiteAPI.php @@ -134,7 +134,12 @@ protected function user() { else if(isset($_GET['fields']) && isset($_GET['values'])) { $limit = isset($_GET['limit']) ? intval($_GET['limit']) : 10; $whereClauses = array_map(function($a) { - return $this->mysqli->real_escape_string($_GET['fields'][$a])." = '".$this->mysqli->real_escape_string($_GET['values'][$a])."'"; + $allowedOps = array("=", "!=", ">", "<", ">=", "<=", "IS", "IS NOT"); + $compOp = "="; + if(isset($_GET['comparison']) && in_array($_GET['comparison'][$a], $allowedOps)) { + $compOp = $_GET['comparison'][$a]; + } + return $this->mysqli->real_escape_string($_GET['fields'][$a])." ".$compOp." '".$this->mysqli->real_escape_string($_GET['values'][$a])."'"; }, range(0, count($_GET['fields'])-1)); $orderBy = isset($_GET['orderBy']) ? $this->mysqli->real_escape_string($_GET['orderBy']) : 'rank'; $page = isset($_GET['page']) ? intval($_GET['page']) : 0; diff --git a/website/recent_submissions.php b/website/recent_submissions.php new file mode 100644 index 000000000..585faa0ed --- /dev/null +++ b/website/recent_submissions.php @@ -0,0 +1,50 @@ + + + + + + + Recent submissions + + + + + + +
+ +
+
+
+ +
+
+

Bot Submission Feed

+
+ + + + + + + + +
TimePlayerLast RankLanguageVersion
+
+
+
+
+
+ + + + + + + + + + diff --git a/website/script/backend.js b/website/script/backend.js index 183e1e66f..b7e2d9b1a 100644 --- a/website/script/backend.js +++ b/website/script/backend.js @@ -262,9 +262,35 @@ function newEmail(email) { function getGames(previousID) { return $.ajax({ - url: "api/web/game", + url: url+"game", async: false, method: "GET", data: {previousID: previousID} }).responseJSON; } + +function getRecentSubmissions(cutoffTime) { + var requestData = { + fields: ["isRunning"], + comparison: ["="], + values: ["1"], + orderBy: "compileTime DESC", + limit: 20 + }; + if(typeof(cutoffTime) !== "undefined") { + requestData["fields"].push("compileTime"); + requestData["comparison"].push(">"); + requestData["values"].push(cutoffTime); + } + var result = $.ajax({ + url: url+"user", + async: false, + method: "GET", + data: requestData + }); + var resp = result.responseJSON; + if(typeof(resp.users) === "undefined") { + return []; + } + return resp.users; +} diff --git a/website/script/recent_submissions.js b/website/script/recent_submissions.js new file mode 100644 index 000000000..ed30d241e --- /dev/null +++ b/website/script/recent_submissions.js @@ -0,0 +1,121 @@ +$(function() { + var submissionTable = { + $alternateMessage: $("#noBotsMessage"), + $panel: $("#submissionPanel"), + $table: $("#submissionTable"), + $tableBody: $("#submissionTableBody"), + lastSubmissionTime: null, + init: function() { + var submissions = getRecentSubmissions(); + submissions = this.prepSubmissions(submissions); + this.lastSubmissionTime = submissions[0].compileTime; + + var schedCutoff = submissions.length > 1 ? 1 : 0; + var lastTime = submissions[0].date.valueOf(); + while (schedCutoff < 15 && + schedCutoff < submissions.length - 2 && + lastTime - submissions[schedCutoff].date.valueOf() < 60000) { + schedCutoff += 1; + } + + this.render(submissions.slice(schedCutoff)); + var delay = 45000; + if(schedCutoff > 0) { + delay = this.scheduleSubmissions(submissions.slice(0, schedCutoff)); + } + window.setTimeout(this.loadMore.bind(this), delay); + }, + prepSubmissions: function(submissions) { + submissions.forEach(function(sub) { + if(sub.compileTime) { + var dateComponents = sub.compileTime.split(/[- :]/); + sub.date = new Date(Date.UTC(dateComponents[0], dateComponents[1]-1, dateComponents[2], dateComponents[3], dateComponents[4], dateComponents[5])); + } else { + sub.date = new Date(0); + } + if(sub.numSubmissions > 1) { + var history = getHistories(sub.userID); + if(history.length > 0) { + sub.lastRank = history[0].lastRank; + } else { + // This should only occur if there was a server error + // during the compilation posting. + sub.lastRank = 'Missing'; + } + } else { + sub.lastRank = 'None'; + } + }); + return submissions; + }, + render: function(submissions) { + if(submissions.length == 0) { + this.$alternateMessage.css("display", "block"); + this.$panel.css("display", "none"); + } else { + this.$tableBody.empty(); + for(var a = 0; a < submissions.length; a++) { + this.$tableBody.append(this.getTableRow(submissions[a])); + } + } + }, + getTableRow: function(user) { + var $row = $(""+user.date.toLocaleTimeString()+""+user.username+""+user.lastRank+""+user.language+""+user.numSubmissions+""); + return $row; + }, + insertSubmission: function(submission) { + var $newrow = this.getTableRow(submission); + $newrow.children('td').css({"padding": "0"}).wrapInner('
'); + $newrow.find("td > div").css({"padding": "8px"}).hide(); + $newrow.prependTo(this.$tableBody) + $newrow.find("td > div").slideDown(800); + + // Check for and remove submissions if there are more than 50 + var $subrows = this.$tableBody.children("tr"); + for (i = $subrows.length-1; i > 50; i--) { + $subrows[i].remove(); + } + }, + scheduleSubmissions: function(submissions) { + var last = submissions[submissions.length-1].date; + var nextSlot = 0; + var timeMultiple = 1; + var span = submissions[0].date.valueOf() - last.valueOf(); + if(span < 55000) { + timeMultiple = 55000 / Math.max(span, 1); + } + for (i=submissions.length-1; i>=0; i--) { + var iSFunc = this.insertSubmission.bind(this); + function iSFactory(submission) { + return function() { + iSFunc(submission); + } + } + var callback = iSFactory(submissions[i]); + var delay = (submissions[i].date.valueOf() - last.valueOf()) * timeMultiple; + delay = Math.max(delay, nextSlot); + nextSlot = delay + 2000; + window.setTimeout(callback, delay); + } + console.log("Scheduled "+submissions.length+" subs for display in "+delay/1000); + return delay; + }, + loadMore: function() { + var newsubs = getRecentSubmissions(this.lastSubmissionTime); + if(newsubs.length == 0) { + console.log("No more submissions available."); + window.setTimeout(this.loadMore.bind(this), 60000); + return + } + newsubs = this.prepSubmissions(newsubs); + var nextGet = this.scheduleSubmissions(newsubs); + nextGet = Math.max(nextGet, 60000); + this.lastSubmissionTime = newsubs[0].compileTime; + + console.log("Next get in "+nextGet/1000); + window.setTimeout(this.loadMore.bind(this), nextGet); + } + } + + submissionTable.init(); +}) diff --git a/website/script/user.js b/website/script/user.js index 28b0f12e0..b096ae38c 100644 --- a/website/script/user.js +++ b/website/script/user.js @@ -27,7 +27,18 @@ $(function() { if(this.user['organization'] != 'Other') { this.$secondaryInfo.append($("Member of "+this.user['organization']+ "")); this.$secondaryInfo.append($("
")); - } + } + if(this.user['compileTime']) { + var dateComponents = this.user['compileTime'].split(/[- :]/); + var date = new Date(Date.UTC(dateComponents[0], dateComponents[1]-1, dateComponents[2], dateComponents[3], dateComponents[4], dateComponents[5])); + var uploadDateStr = date.toLocaleDateString(); + if(date.getTime() > Date.now() - (24 * 60 * 60 * 1000)) { + uploadDateStr = date.toLocaleTimeString(); + } + this.$secondaryInfo.append($("Uploaded at "+uploadDateStr+"")); + this.$secondaryInfo.append($("
")); + } + this.$secondaryInfo.append($(""+this.user['numSubmissions']+" "+(parseInt(this.user['numSubmissions']) == 1 ? "bot" : "bots")+" submitted")); this.$secondaryInfo.append($("
")); this.$secondaryInfo.append($(""+this.user['numGames']+" games played")); @@ -54,7 +65,17 @@ $(function() { } }, getTableRow: function(history) { - return ""+this.name+" v"+history.versionNumber+""+history.lastRank+" of "+history.lastNumPlayers+""+(history.lastNumGames != null ? history.lastNumGames : "Not Recorded")+""; + var uploadDateStr = "Unknown"; + if(history.compileTime) { + var dateComponents = history.compileTime.split(/[- :]/); + var date = new Date(Date.UTC(dateComponents[0], dateComponents[1]-1, dateComponents[2], dateComponents[3], dateComponents[4], dateComponents[5])); + if(date.getTime() > Date.now() - (24 * 60 * 60 * 1000)) { + uploadDateStr = date.toLocaleTimeString(); + } else { + uploadDateStr = date.toLocaleDateString(); + } + } + return ""+this.name+" v"+history.versionNumber+""+uploadDateStr+""+history.lastRank+" of "+history.lastNumPlayers+""+(history.lastNumGames != null ? history.lastNumGames : "Not Recorded")+""; } } diff --git a/website/sql/schema.sql b/website/sql/schema.sql index 1af52bb00..4359414e6 100644 --- a/website/sql/schema.sql +++ b/website/sql/schema.sql @@ -81,6 +81,7 @@ CREATE TABLE `User` ( `numSubmissions` smallint(5) NOT NULL DEFAULT 0, `numGames` smallint(5) NOT NULL DEFAULT 0, `creationTime` datetime DEFAULT CURRENT_TIMESTAMP, + `compileTime` datetime, `updateTime` datetime ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`userID`) ) ENGINE=InnoDB AUTO_INCREMENT=1000 DEFAULT CHARSET=latin1; @@ -96,6 +97,7 @@ DROP TABLE IF EXISTS `UserHistory`; CREATE TABLE `UserHistory` ( `userID` mediumint(8) unsigned NOT NULL, `versionNumber` smallint(5) NOT NULL, + `compileTime` datetime, `lastRank` smallint(5) NOT NULL, `lastNumPlayers` smallint(5) NOT NULL, `lastNumGames` smallint(5) DEFAULT NULL, diff --git a/website/user.php b/website/user.php index b16064b4d..92a306cfa 100644 --- a/website/user.php +++ b/website/user.php @@ -55,6 +55,7 @@ Version + Uploaded Final Ranking Games Played