Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lock players from seeding when in a game. #311

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
25 changes: 24 additions & 1 deletion website/api/manager/ManagerAPI.php
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,13 @@ private function getTrueskillMatchQuality($rankingValues) {
return floatval($lines[0]);
}

private function clearPairing() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not pass $paringID as a parameter to clearParing instead of relying on its being a POST parameter?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that makes sense. It just got factored out into a separate function just because the game api function can exit (return) from two code paths, each of which need to clear the pairing.

if(isset($_POST['pairingID'])) {
$pairingID = $this->mysqli->real_escape_string($_POST['pairingID']);
$this->insert("DELETE FROM PairingUser WHERE pairingID={$pairingID}");
$this->insert("DELETE FROM Pairing WHERE pairingID={$pairingID}");
}
}

/////////////////////////API ENDPOINTS\\\\\\\\\\\\\\\\\\\\\\\\\\\\

Expand All @@ -88,7 +95,7 @@ protected function task() {
$seedPlayer = null;
$randValue = mt_rand() / mt_getrandmax();
if($randValue > 0.5) {
$seedPlayer = $this->select("SELECT * FROM User WHERE isRunning = 1 order by rand()*-pow(sigma, 2) LIMIT 1");
$seedPlayer = $this->select("SELECT u.* FROM (SELECT MAX(p.timestamp) as maxTime, pu.userID as userID from PairingUser pu INNER JOIN Pairing p ON p.pairingID = pu.pairingID GROUP BY pu.userID) temptable RIGHT JOIN User u on u.userID = temptable.userID WHERE (maxTime IS NULL OR maxTime < DATE_SUB(NOW(), INTERVAL 10 MINUTE)) AND isRunning = 1 order by rand()*-pow(sigma, 2) LIMIT 1");
}
if ($randValue > 0.25 && $randValue <= 0.5) {
$seedPlayer = $this->select("SELECT * FROM (SELECT u.* FROM (SELECT MAX(g.timestamp) as maxTime, gu.userID as userID FROM GameUser gu INNER JOIN Game g ON g.gameID=gu.gameID GROUP BY gu.userID) temptable INNER JOIN User u on u.userID = temptable.userID where numGames < 400 and isRunning = 1 order by maxTime ASC limit 15) orderedTable order by rand() limit 1;");
Expand All @@ -105,10 +112,23 @@ protected function task() {
$sizes = array(20, 25, 25, 30, 30, 30, 35, 35, 35, 35, 40, 40, 40, 45, 45, 50);
$size = $sizes[array_rand($sizes)];

// Record pairing
$worker = $this->select("SELECT * FROM Worker WHERE apiKey=".$this->mysqli->real_escape_string($this->apiKey)." LIMIT 1");
$this->insert("INSERT INTO Pairing (workerID) VALUES (".$worker["workerID"].")");
$pairing = $this->select("SELECT * FROM Pairing WHERE workerID=".$worker["workerID"]." ORDER BY pairingID DESC LIMIT 1");
$playerValues = array();
foreach($players as $player) {
$playerValues[] = "(".$pairing["pairingID"].", ".$player["userID"].")";
}
$playerValues = implode(",", $playerValues);
$playerInsert = "INSERT INTO PairingUser (pairingID, userID) VALUES ".$playerValues;
$this->insert($playerInsert);

// Send game task
if(count($players) == $numPlayers) {
return array(
"type" => "game",
"pairingID" => $pairing["pairingID"],
"width" => $size,
"height" => $size,
"users" => $players
Expand Down Expand Up @@ -162,6 +182,7 @@ protected function game() {
$storedUser = $this->select("SELECT * FROM User WHERE userID=".$this->mysqli->real_escape_string($user->userID));
array_push($storedUsers, $storedUser);
if(intval($storedUser['numSubmissions']) != intval($user->numSubmissions)) {
$this->clearPairing();
return null;
}
}
Expand Down Expand Up @@ -261,6 +282,8 @@ protected function game() {
$rank = $userIndex+1;
$this->insert("UPDATE User SET rank={$rank} WHERE userID={$allUsers[$userIndex]['userID']}");
}

$this->clearPairing();
}
}

Expand Down
28 changes: 28 additions & 0 deletions website/sql/schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,34 @@ CREATE TABLE `GameUser` (
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Table structure for table `Pairing`
--

DROP TABLE IF EXISTS `Pairing`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `Pairing` (
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Paring is a bit vague. Might want to change this to GameParing.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A Pairing row doesn't really relate to a Game row in the database so that may be confusing. But it could be changed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Discord discussion settled on GameTask. Not sure on ID fields, going with gametaskID for the moment.

`pairingID` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
`workerID` mediumint(8) unsigned NOT NULL,
`timestamp` datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`pairingID`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Table structure for table `PairingUser`
--

DROP TABLE IF EXISTS `PairingUser`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `PairingUser` (
`pairingID` mediumint(8) unsigned NOT NULL,
`userID` mediumint(8) unsigned NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Table structure for table `User`
--
Expand Down
4 changes: 2 additions & 2 deletions worker/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,10 @@ def compileResult(userID, didCompile, language, errors=None):
r = requests.post(MANAGER_URL+"compile", data={"apiKey": API_KEY, "userID": userID, "didCompile": int(didCompile), "language": language, "errors": errors})
print("Posting compile result %s\n" % r.text)

def gameResult(width, height, users, replayPath, errorPaths):
def gameResult(pairID, width, height, users, replayPath, errorPaths):
"""Posts the result of a game task"""
files = {os.path.basename(replayPath): open(replayPath, "rb").read()}
for path in errorPaths:
files[os.path.basename(path)] = open(path, "rb").read()
r = requests.post(MANAGER_URL+"game", data={"apiKey": API_KEY, "mapWidth": str(width), "mapHeight": str(height), "users": json.dumps(users)}, files=files)
r = requests.post(MANAGER_URL+"game", data={"apiKey": API_KEY, "pairingID": pairID, "mapWidth": str(width), "mapHeight": str(height), "users": json.dumps(users)}, files=files)
print("Posting game result %s\n" % r.text)
13 changes: 9 additions & 4 deletions worker/worker.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,8 +141,11 @@ def parseGameOutput(output, users):

return users, replayPath, errorPaths

def executeGameTask(width, height, users, backend):
def executeGameTask(gameTask, backend):
"""Downloads compiled bots, runs a game, and posts the results of the game"""
width = int(gameTask["width"])
height = int(gameTask["height"])
users = gameTask["users"]
print("Running game with width %d, height %d\n" % (width, height))
print("Users objects %s\n" % (str(users)))

Expand All @@ -156,15 +159,15 @@ def executeGameTask(width, height, users, backend):
fIn.close()
fOut.close()

backend.gameResult(width, height, users, replayArchivePath, errorPaths)
backend.gameResult(gameTask["pairingID"], width, height, users, replayArchivePath, errorPaths)
filelist = glob.glob("*.log")
for f in filelist:
os.remove(f)

os.remove(replayPath)
os.remove(replayArchivePath)

if __name__ == "__main__":
def main():
print("\n\n\n\nStarting up worker...\n\n\n")
while True:
try:
Expand All @@ -178,7 +181,7 @@ def executeGameTask(width, height, users, backend):
executeCompileTask(task["user"], backend)
else:
print("Running a game task...\n")
executeGameTask(int(task["width"]), int(task["height"]), task["users"], backend)
executeGameTask(task, backend)
else:
print("No task available at time %s (GMT). Sleeping...\n" % str(strftime("%Y-%m-%d %H:%M:%S", gmtime())))
sleep(2)
Expand All @@ -187,3 +190,5 @@ def executeGameTask(width, height, users, backend):
print("Sleeping...\n")
sleep(2)

if __name__ == "__main__":
main()