0
.gitignore
vendored
Normal file → Executable file
0
.gitmodules
vendored
Normal file → Executable file
2
countBattles.txt
Normal file → Executable file
|
@ -1 +1 @@
|
|||
1448
|
||||
1836
|
0
html/.htaccess
Normal file → Executable file
0
html/imgs/Arenes-Nimes.jpg
Normal file → Executable file
Before Width: | Height: | Size: 550 KiB After Width: | Height: | Size: 550 KiB |
0
html/imgs/Bronze_Medal.svg
Normal file → Executable file
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
0
html/imgs/Emoji_u1f4a9.svg
Normal file → Executable file
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 4.6 KiB |
0
html/imgs/Gold_Medal.svg
Normal file → Executable file
Before Width: | Height: | Size: 114 KiB After Width: | Height: | Size: 114 KiB |
0
html/imgs/Silver_Medal.svg
Normal file → Executable file
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
0
html/index.php
Normal file → Executable file
0
html/log.txt
Normal file → Executable file
0
html/principe.gif
Normal file → Executable file
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
0
html/style.css
Normal file → Executable file
0
html/testBotScripts/connectfour.html
Normal file → Executable file
0
html/testBotScripts/index.html
Normal file → Executable file
0
html/testBotScripts/tictactoe.html
Normal file → Executable file
0
install.sql
Normal file → Executable file
0
lang/en.php
Normal file → Executable file
0
lang/fr.php
Normal file → Executable file
80
src/DUEL.php
Normal file
|
@ -0,0 +1,80 @@
|
|||
<?php
|
||||
class DUEL{
|
||||
public $rank1;
|
||||
public $rank2;
|
||||
|
||||
private $factor = 400;
|
||||
|
||||
public function __construct($r1,$r2){
|
||||
$this->rank1 = $r1;
|
||||
$this->rank2 = $r2;
|
||||
}
|
||||
private function get_k($rank){
|
||||
if ($rank < 1000) return 80;
|
||||
if ($rank < 2000) return 50;
|
||||
if ($rank <= 2400) return 30;
|
||||
return 20;
|
||||
}
|
||||
private function changeScores($score){
|
||||
$this->rank1 = $this->rank1 + $this->get_k($this->rank1) * ($score - (1/ (1 + pow(10,(($this->rank2 - $this->rank1) / $this->factor)))));
|
||||
$this->rank2 = $this->rank2 + $this->get_k($this->rank2) * (1 - $score - (1/ (1 + pow(10,(($this->rank1 - $this->rank2) / $this->factor)))));
|
||||
}
|
||||
|
||||
public function oneWinsAgainstTwo(){
|
||||
$this->changeScores(1);
|
||||
}
|
||||
public function twoWinsAgainstOne(){
|
||||
$this->changeScores(0);
|
||||
}
|
||||
public function drawGame(){
|
||||
$this->changeScores(0.5);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class ELO
|
||||
{
|
||||
public $rank = 1500; //default rank
|
||||
|
||||
public function __construct($v=1500) {
|
||||
$this->rank = $v;
|
||||
}
|
||||
|
||||
private function ELO_get_new_ranks($elo1,$elo2,$score){
|
||||
/*
|
||||
* return an array containing new ELO scores after a battle
|
||||
* $score : 0 player 2 won
|
||||
* 0.5 draws
|
||||
* 1 player 1 won
|
||||
*/
|
||||
|
||||
//good luck for understanding it
|
||||
//(see https://blog.antoine-augusti.fr/2012/06/maths-et-code-le-classement-elo/)
|
||||
return array(
|
||||
$elo1 + ELO_get_k($elo1) * ($score - (1/ (1 + pow(10,(($elo2 - $elo1) / 400))))),
|
||||
$elo2 + ELO_get_k($elo2) * (1 - $score - (1/ (1 + pow(10,(($elo1 - $elo2) / 400)))))
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function looseAgainst(ELO $winer){
|
||||
|
||||
}
|
||||
public function winAgainst(ELO $looser){
|
||||
|
||||
}
|
||||
public function drawAgainst(ELO $drawPlayer){
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -1 +1 @@
|
|||
Subproject commit 7c8b786228bb9e1561ff60a2d6f7f6ce91be6fee
|
||||
Subproject commit 1d85f9ef3ecfc42bbc4f3c70d5e37ca9a65f629a
|
0
src/about.html
Normal file → Executable file
0
src/aboutBot.php
Normal file → Executable file
0
src/act.php
Normal file → Executable file
0
src/addBot.php
Normal file → Executable file
0
src/arenas/Battleship/act.php
Normal file → Executable file
0
src/arenas/Battleship/doc-en.html
Normal file → Executable file
0
src/arenas/Battleship/doc-fr.html
Normal file → Executable file
0
src/arenas/Battleship/functions.php
Normal file → Executable file
0
src/arenas/Battleship/js.js
Normal file → Executable file
0
src/arenas/Battleship/public.php
Normal file → Executable file
0
src/arenas/Battleship/style.css
Normal file → Executable file
0
src/arenas/Battleship/testBot.html
Normal file → Executable file
0
src/arenas/connectFour/act.php
Normal file → Executable file
0
src/arenas/connectFour/doc-en.html
Normal file → Executable file
0
src/arenas/connectFour/doc-fr.html
Normal file → Executable file
0
src/arenas/connectFour/functions.php
Normal file → Executable file
0
src/arenas/connectFour/js.js
Normal file → Executable file
0
src/arenas/connectFour/public.php
Normal file → Executable file
0
src/arenas/connectFour/style.css
Normal file → Executable file
0
src/arenas/tictactoe/act.php
Normal file → Executable file
0
src/arenas/tictactoe/doc-en.html
Normal file → Executable file
0
src/arenas/tictactoe/doc-fr.html
Normal file → Executable file
0
src/arenas/tictactoe/doc.html
Normal file → Executable file
0
src/arenas/tictactoe/functions.php
Normal file → Executable file
0
src/arenas/tictactoe/js.js
Normal file → Executable file
0
src/arenas/tictactoe/public.php
Normal file → Executable file
0
src/arenas/tictactoe/style.css
Normal file → Executable file
0
src/arenas/tictactoe/testBot.html
Normal file → Executable file
33
src/arenas/tron/Coords.php
Executable file
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
class Coords{
|
||||
private static $min = 0;
|
||||
private static $max = 999;
|
||||
|
||||
public $x;
|
||||
public $y;
|
||||
|
||||
public function __construct(int $x = 0, int $y = 0) {
|
||||
if (($x < Coords::$min) || ($x > Coords::$max) || ($y < Coords::$min) || ($y > Coords::$max)){
|
||||
//out of limits
|
||||
error_log("a bot out of limits");
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->x = $x;
|
||||
$this->y = $y;
|
||||
|
||||
|
||||
}
|
||||
|
||||
public function __toString(){
|
||||
return $this->x.",".$this->y;
|
||||
|
||||
}
|
||||
|
||||
public function addDirection(Direction $dir){
|
||||
return new Coords(
|
||||
$this->x + $dir->deltaX,
|
||||
$this->y + $dir->deltaY
|
||||
);
|
||||
}
|
||||
}
|
95
src/arenas/tron/Direction.php
Executable file
|
@ -0,0 +1,95 @@
|
|||
<?php
|
||||
class InvalidDirectionException extends UnexpectedValueException{
|
||||
|
||||
}
|
||||
class Direction
|
||||
{
|
||||
private static $top = 0;
|
||||
private static $bottom = 1;
|
||||
private static $left = 2;
|
||||
private static $right = 3;
|
||||
|
||||
private $value;
|
||||
|
||||
public $deltaX;
|
||||
public $deltaY;
|
||||
|
||||
public function __construct(){
|
||||
$this->value = 0;
|
||||
}
|
||||
|
||||
private function setValue($value){
|
||||
$this->value = $value;
|
||||
switch ($value){
|
||||
case Direction::$bottom:
|
||||
$this->deltaY = -1;
|
||||
$this->deltaX = 0;
|
||||
break;
|
||||
case Direction::$top:
|
||||
$this->deltaY = 1;
|
||||
$this->deltaX = 0;
|
||||
break;
|
||||
case Direction::$left:
|
||||
$this->deltaY = 0;
|
||||
$this->deltaX = -1;
|
||||
break;
|
||||
case Direction::$right:
|
||||
$this->deltaY = 0;
|
||||
$this->deltaX = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public function __toString(){
|
||||
switch ($this->value){
|
||||
case Direction::$top:
|
||||
return "y+";
|
||||
break;
|
||||
case Direction::$bottom:
|
||||
return "y-";
|
||||
break;
|
||||
case Direction::$left:
|
||||
return "x-";
|
||||
break;
|
||||
case Direction::$right:
|
||||
return "x+";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static function make($str){
|
||||
$dir = new Direction();
|
||||
switch((string)$str){
|
||||
case "x+":
|
||||
$dir->setValue(Direction::$right);
|
||||
break;
|
||||
case "x-":
|
||||
$dir->setValue(Direction::$left);
|
||||
break;
|
||||
case "y+":
|
||||
$dir->setValue(Direction::$top);
|
||||
break;
|
||||
case "y-":
|
||||
$dir->setValue(Direction::$bottom);
|
||||
break;
|
||||
default:
|
||||
//error_log("expected 'x+', 'x-', 'y+' or 'y-'". (string)$str."received.");
|
||||
return false;
|
||||
//throw new InvalidDirectionException("expected 'x+', 'x-', 'y+' or 'y-'". (string)$str."received.");
|
||||
break;
|
||||
}
|
||||
return $dir;
|
||||
}
|
||||
public function opposite(){
|
||||
$opposites = array(
|
||||
Direction::$top => Direction::$bottom,
|
||||
Direction::$bottom => Direction::$top,
|
||||
Direction::$left => Direction::$right,
|
||||
Direction::$right => Direction::$left
|
||||
);
|
||||
|
||||
$opposite = new Direction();
|
||||
$opposite->setValue($opposites[$this->value]);
|
||||
return $opposite;
|
||||
}
|
||||
}
|
64
src/arenas/tron/ScoreLap.php
Normal file
|
@ -0,0 +1,64 @@
|
|||
<?php
|
||||
|
||||
class ScoreLap
|
||||
{
|
||||
|
||||
private $participants; //array containing bots objects
|
||||
private $loosers; //those who losed
|
||||
|
||||
|
||||
public function addBotOnLap(TronPlayer $bot){
|
||||
$this->participants[] = $bot;
|
||||
}
|
||||
|
||||
public function addLoser(TronPlayer $bot){
|
||||
$this->loosers[] = $bot;
|
||||
}
|
||||
public function __construct() {
|
||||
$this->participants= array();
|
||||
$this->loosers = array();
|
||||
}
|
||||
|
||||
public function getLoosersList(){
|
||||
//return losers as a digest array
|
||||
$arr = array();
|
||||
|
||||
foreach($this->loosers as $looser){
|
||||
$arr[] = array("id" => $looser->id,"order" => $looser->playerIndex);
|
||||
}
|
||||
return $arr;
|
||||
}
|
||||
|
||||
private function ApplyDraws(){
|
||||
|
||||
//apply draw match to all losers
|
||||
if(count($this->loosers) < 2) return;
|
||||
|
||||
foreach($this->loosers as $looser1){
|
||||
foreach($this->loosers as $looser2){
|
||||
if($looser1->playerIndex == $looser2->playerIndex) continue;
|
||||
save_battle('tron', $looser1->id, $looser2->id, 0, 'id' );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private function ApplyWins(){
|
||||
|
||||
//need to make losers List. simply array of orders
|
||||
$loosersIndexArr = array();
|
||||
foreach($this->loosers as $looser){
|
||||
$loosersIndexArr[] = $looser->playerIndex;
|
||||
}
|
||||
foreach($this->loosers as $looser){
|
||||
foreach($this->participants as $participant){
|
||||
if(in_array($participant->playerIndex,$loosersIndexArr)) continue;
|
||||
save_battle('tron', $looser->id, $participant->id, 2, 'id');
|
||||
}
|
||||
}
|
||||
}
|
||||
public function ApplyScores(){
|
||||
$this-> ApplyDraws();
|
||||
$this-> ApplyWins();
|
||||
}
|
||||
}
|
73
src/arenas/tron/Trail.php
Executable file
|
@ -0,0 +1,73 @@
|
|||
<?php
|
||||
class AlreadyBeenAddedException extends LogicException { }
|
||||
|
||||
class Trail {
|
||||
private $trail;
|
||||
|
||||
public function __construct() {
|
||||
$this->trail = new SplStack();
|
||||
}
|
||||
|
||||
public function last() {
|
||||
if($this->trail->isEmpty()){
|
||||
return false;
|
||||
}
|
||||
return $this->trail->top();
|
||||
}
|
||||
|
||||
public function emptyTrail(){
|
||||
$this->trail = new SplStack();
|
||||
}
|
||||
public function getTrail(){
|
||||
return $this->trail;
|
||||
}
|
||||
public function mergeWith($trailToMerge){
|
||||
if($trailToMerge->getTrail()->isEmpty()) return;
|
||||
|
||||
foreach($trailToMerge->getTrail() as $value) {
|
||||
$this->trail->push($value);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function add($value) {
|
||||
if(!$this->trail->isEmpty()) {
|
||||
if(Trail::kind($this->trail->bottom()) !== Trail::kind($value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if($this->contains($value)) {
|
||||
//throw new AlreadyBeenAddedException(
|
||||
// 'value has already been added to the trail'.$value.'|'.$this->__toString()
|
||||
//);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$this->trail->push($value);
|
||||
}
|
||||
public function __toString(){
|
||||
return json_encode($this->getTrailAsArray());
|
||||
}
|
||||
|
||||
public function getTrailAsArray(){
|
||||
$arr = array();
|
||||
foreach($this->trail as $coord) {
|
||||
$arr[] = array($coord->x,$coord->y);
|
||||
}
|
||||
return $arr;
|
||||
}
|
||||
public function contains($searchedValue) {
|
||||
foreach($this->trail as $value) {
|
||||
if($value == $searchedValue) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function kind($var) {
|
||||
$type = gettype($var);
|
||||
if($type == 'object') $type .= ' ' . get_class($var);
|
||||
return $type;
|
||||
}
|
||||
}
|
253
src/arenas/tron/TronGame.php
Executable file
|
@ -0,0 +1,253 @@
|
|||
<?php
|
||||
class TronGame
|
||||
{
|
||||
private $bots; //array of bots
|
||||
public $gameId;
|
||||
private $status; //false => Game ended or not initialised
|
||||
|
||||
private function getAliveBots(){
|
||||
$aliveBots = array();
|
||||
foreach($this->bots as $bot){
|
||||
if($bot->isAlive){
|
||||
$aliveBots[] = $bot;
|
||||
}
|
||||
}
|
||||
return $aliveBots;
|
||||
}
|
||||
|
||||
private function getBotByPlayerIndex($index){
|
||||
foreach($this->bots as $bot){
|
||||
if($bot->playerIndex == $index){
|
||||
return $bot;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
private function initScoring(){
|
||||
/*
|
||||
*Add all alive bots on a ScoreLap object and return it
|
||||
*/
|
||||
$scoring = new ScoreLap();
|
||||
foreach($this->getAliveBots()as $bot){
|
||||
$scoring->addBotOnLap($bot);
|
||||
}
|
||||
return $scoring;
|
||||
}
|
||||
public function get_continue(){
|
||||
//count bots alive. if less than 1, game is ended
|
||||
if(count($this->getAliveBots()) > 1){
|
||||
return true;
|
||||
}else{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function get_trails(){
|
||||
//return all trails for draw svg
|
||||
$trailsArr = array();
|
||||
foreach($this->bots as $bot){
|
||||
$trailsArr[] = $bot->trail->getTrailAsArray();
|
||||
}
|
||||
//error_log("*********".json_encode($trailsArr,true)."********");
|
||||
return $trailsArr;
|
||||
|
||||
}
|
||||
private function get_map_as_an_unique_trail(){
|
||||
$trail = new Trail;
|
||||
foreach($this->bots as $bot){
|
||||
$trail->mergeWith($bot->trail);
|
||||
}
|
||||
return $trail;
|
||||
|
||||
}
|
||||
public function get_lasts_trails(){
|
||||
//return only the lasts coords for each tail
|
||||
$trailsArr = array();
|
||||
foreach($this->bots as $bot){
|
||||
$trailsArr[] = $bot->trail->last();
|
||||
}
|
||||
return $trailsArr;
|
||||
}
|
||||
public function new_lap(){
|
||||
|
||||
if($this->get_continue() === false){
|
||||
return false;
|
||||
}
|
||||
|
||||
$scoreObj = $this->initScoring();
|
||||
$aliveBots = $this->getAliveBots();
|
||||
|
||||
//fixed Query parameters
|
||||
$nbeBots = count($this->bots);
|
||||
$board = $this->get_trails(); //same for each bot
|
||||
$initialMapAsATrail = $this->get_map_as_an_unique_trail();
|
||||
|
||||
//Open curl multi
|
||||
$cmh = curl_multi_init();
|
||||
$ch = array();
|
||||
|
||||
|
||||
|
||||
foreach($aliveBots as $bot){
|
||||
$i = $bot->playerIndex; //because $i is shorter
|
||||
|
||||
$bodyRequestArr[$i] = array(
|
||||
'game-id' => "".$this->gameId,
|
||||
'action' => 'play-turn',
|
||||
'game' => 'tron',
|
||||
'board' => $board,
|
||||
'player-index' => $i,
|
||||
'players' => $nbeBots
|
||||
);
|
||||
$data_string = json_encode($bodyRequestArr[$i]);
|
||||
$ch[$i] = curl_init($bot->url);
|
||||
curl_setopt($ch[$i], CURLOPT_CUSTOMREQUEST, "POST");
|
||||
curl_setopt($ch[$i], CURLOPT_SSL_VERIFYHOST, false);
|
||||
curl_setopt($ch[$i], CURLOPT_SSL_VERIFYPEER, false);
|
||||
curl_setopt($ch[$i], CURLOPT_POSTFIELDS, $data_string);
|
||||
curl_setopt($ch[$i], CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch[$i], CURLOPT_HTTPHEADER, array(
|
||||
'Content-Type: application/json',
|
||||
'Content-Length: ' . strlen($data_string))
|
||||
);
|
||||
curl_multi_add_handle($cmh,$ch[$i]);
|
||||
}
|
||||
|
||||
//send the requests
|
||||
do {
|
||||
$returnVal = curl_multi_exec($cmh, $runningHandles);
|
||||
}while($runningHandles > 0);
|
||||
|
||||
//get results
|
||||
foreach($ch as $playerIndex=>$cr){
|
||||
$currentBot = $this->getBotByPlayerIndex($playerIndex);
|
||||
|
||||
// Check for errors
|
||||
$curlError = curl_error($cr);
|
||||
$response = curl_multi_getcontent($cr);
|
||||
|
||||
if($curlError !== "") {
|
||||
|
||||
//erreur curl, he loses
|
||||
$scoreObj-> addLoser($currentBot);
|
||||
$currentBot->loose();
|
||||
error_log("no curl response".$playerIndex); //debug
|
||||
|
||||
}elseif(! $arr = json_decode($response,TRUE)){
|
||||
|
||||
//la reponse n'est pas un json, il a perdu
|
||||
$scoreObj-> addLoser($currentBot);
|
||||
$currentBot->loose();
|
||||
error_log("la reponse est pas JSON".$playerIndex); //debug
|
||||
|
||||
}elseif(Direction::make($arr['play']) === false){
|
||||
|
||||
//tester ici la réponse
|
||||
//he loose il utilise probablement une de ses propres cases
|
||||
$scoreObj-> addLoser($currentBot);
|
||||
$currentBot->loose();
|
||||
error_log("La reponse ne contient pas une direction".$playerIndex); //debug
|
||||
|
||||
}elseif($initialMapAsATrail->contains($currentBot->trail->last()->addDirection(Direction::make($arr['play'])))){ //ounch
|
||||
|
||||
//le bot tente d'aller sur une case qui était prise au début du round
|
||||
$scoreObj-> addLoser($currentBot);
|
||||
$currentBot->loose();
|
||||
error_log("Il joue sur une case deja prise".$playerIndex); //debug
|
||||
}else{
|
||||
//mettre de coté la direction du bot
|
||||
$currentBot->nextDir = Direction::make($arr['play']);
|
||||
}
|
||||
//close curl
|
||||
curl_multi_remove_handle($cmh, $cr);
|
||||
curl_close($cr);
|
||||
|
||||
}
|
||||
|
||||
$aliveBots = $this->getAliveBots();
|
||||
//pour tous les bots encore vivants, on teste si deux d'entre eux ne cibleraient pas la même case
|
||||
foreach ($aliveBots as $bot1){
|
||||
foreach ($aliveBots as $bot2){
|
||||
if($bot1->playerIndex == $bot2->playerIndex) continue;
|
||||
if($bot1->trail->last()->addDirection($bot1->nextDir) == $bot2->trail->last()->addDirection($bot2->nextDir)){
|
||||
//he loose
|
||||
$scoreObj-> addLoser($bot1);
|
||||
$bot1->loose();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//ok, faire grossir les bots qui restent
|
||||
foreach($this->getAliveBots() as $bot){
|
||||
$bot-> applyNextDir();
|
||||
}
|
||||
|
||||
//apply scores:
|
||||
$scoreObj-> ApplyScores();
|
||||
|
||||
return array(
|
||||
'last_points' => $this->get_lasts_trails(),
|
||||
'loosers' => $scoreObj->getLoosersList()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public function init_game(){
|
||||
//send init messages to bots
|
||||
$logs = "";
|
||||
$fullLogs = "";
|
||||
$nbeBots = count($this->bots);
|
||||
for ($botCount = 0; $botCount < $nbeBots; $botCount++){
|
||||
$messageArr = array(
|
||||
'game-id' => "".$this->gameId,
|
||||
'action' => 'init',
|
||||
'game' => 'tron',
|
||||
'board' => '',
|
||||
'players' => $nbeBots,
|
||||
'player-index' => $botCount
|
||||
);
|
||||
|
||||
$resp = get_IA_Response($this->bots[$botCount]->url,$messageArr);
|
||||
$fullLogs .= 'Arena send to '.$this->bots[$botCount]->name.'<em>'.htmlentities($resp['messageSend']).'</em><br/>
|
||||
HTTP status: <em>'.htmlentities($resp['httpStatus']).'</em><br/>
|
||||
Bot anwser: <em>'.htmlentities($resp['response']).'</em><br/>';
|
||||
$logs.="Init message send to ".$this->bots[$botCount]->name."<br/>";
|
||||
}
|
||||
return array($logs,$fullLogs);
|
||||
}
|
||||
|
||||
public function __construct($botsInfos){
|
||||
$this->gameId = get_unique_id();
|
||||
$this->bots = array();
|
||||
$positions = array();
|
||||
$botCount = 0;
|
||||
$err = "";
|
||||
|
||||
//print_r($botsInfos);
|
||||
|
||||
foreach($botsInfos as $bot){
|
||||
//find a random start position
|
||||
do{
|
||||
$x = rand(1,999);
|
||||
$y = rand(1,999);
|
||||
}while(in_array($x.",".$y,$positions));
|
||||
|
||||
$positions[] = $x.",".$y;
|
||||
$startCoord = new Coords($x,$y);
|
||||
|
||||
$this->bots[$botCount] = new TronPlayer();
|
||||
$this->bots[$botCount]->make($bot['id'],$startCoord,$bot['name'],$bot['url'],$botCount);
|
||||
|
||||
if ($this->bots[$botCount]->isAlive === false){
|
||||
$err .= "Something went wrong for ".$this->bots[$botCount]->getName()."<br/>";
|
||||
}else{
|
||||
$botCount++;
|
||||
}
|
||||
}
|
||||
return $err;
|
||||
}
|
||||
}
|
45
src/arenas/tron/TronPlayer.php
Executable file
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
class TronPlayer{
|
||||
public $url;
|
||||
public $id;
|
||||
public $name;
|
||||
public $trail;
|
||||
public $isAlive = true;
|
||||
public $playerIndex = -1; //if unset is -1
|
||||
public $looseCause = "";
|
||||
public $nextDir;
|
||||
|
||||
public function grow(Direction $dir){
|
||||
$dest = $this->trail->last()->addDirection($dir);
|
||||
if($dest === false){
|
||||
$this->loose();
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->trail->add($this->trail->last()->addDirection($dir));
|
||||
return $this->trail->last();
|
||||
}
|
||||
|
||||
public function applyNextDir(){
|
||||
$this-> grow($this->nextDir);
|
||||
}
|
||||
|
||||
public function loose(){
|
||||
$this->isAlive = false;
|
||||
$this->trail->emptyTrail();
|
||||
//error_log($this->name." a perdu");
|
||||
return false;
|
||||
}
|
||||
public function make($botId, Coords $initialsCoords,$name,$url,$playerIndex = -1){
|
||||
$this->id = $botId;
|
||||
$this->trail = new Trail;
|
||||
$this->trail->add($initialsCoords);
|
||||
$this->name = $name;
|
||||
$this->url = $url;
|
||||
$this->state = true;
|
||||
$this->playerIndex = $playerIndex;
|
||||
}
|
||||
public function __construct(){
|
||||
$this->state = false;
|
||||
}
|
||||
}
|
91
src/arenas/tron/act.php
Executable file
|
@ -0,0 +1,91 @@
|
|||
<?php
|
||||
#- BEGIN LICENSE BLOCK ---------------------------------------
|
||||
#
|
||||
# This file is part of botsArena.
|
||||
#
|
||||
# Copyright (C) Gnieark https://blog-du-grouik.tinad.fr et contributeurs
|
||||
# Licensed under the GPL version 3.0 license.
|
||||
# See LICENSE file or
|
||||
# http://www.gnu.org/licenses/gpl-3.0-standalone.html
|
||||
#
|
||||
# -- END LICENSE BLOCK -----------------------------------------
|
||||
|
||||
//error_log(json_encode($_SESSION,true)."\n\n");
|
||||
|
||||
require_once ("TronGame.php");
|
||||
require_once ("TronPlayer.php");
|
||||
require_once ("Direction.php");
|
||||
require_once ("Trail.php");
|
||||
require_once ("Coords.php");
|
||||
require_once ("ScoreLap.php");
|
||||
|
||||
switch ($_POST['act']){
|
||||
case "initGame":
|
||||
|
||||
$rs = mysqli_query($lnMysql,"SELECT id,name,url FROM bots WHERE game='tron';");
|
||||
while($r = mysqli_fetch_row($rs)){
|
||||
$botsFullArr[$r[0]] = array('id' => $r[0], 'name' => $r[1], 'url' => $r[2]);
|
||||
}
|
||||
|
||||
|
||||
$botsArrayTemp = json_decode($_POST['bots']);
|
||||
//error_log($_POST['bots']);
|
||||
$botsInfos = array();
|
||||
|
||||
foreach($botsArrayTemp as $id){
|
||||
//tester si le bot existe dans la bdd
|
||||
if((isset($botsFullArr[$id])) && ($id > 0)){
|
||||
$botsInfos[] = $botsFullArr[$id];
|
||||
}
|
||||
}
|
||||
|
||||
$game = new TronGame($botsInfos);
|
||||
|
||||
|
||||
$logs = $game->init_game();
|
||||
|
||||
echo json_encode(array(
|
||||
'status' => $game->get_continue(),
|
||||
'logs' => $logs,
|
||||
'gameId' => $game->gameId,
|
||||
'botsPosition' => $game->get_lasts_trails()
|
||||
));
|
||||
|
||||
$_SESSION['game'] = serialize($game);
|
||||
|
||||
|
||||
die;
|
||||
break;
|
||||
case "play":
|
||||
$logs = "";
|
||||
if(!isset($_SESSION['game'])){
|
||||
echo '{"status":"error"}';
|
||||
die;
|
||||
}
|
||||
|
||||
$game = unserialize($_SESSION['game']);
|
||||
|
||||
if($game->gameId <> $_POST['gameId']){
|
||||
//sometimes if an ajax callback is applied after init an other game
|
||||
echo '{"status":"error"}';
|
||||
die;
|
||||
}
|
||||
$lap = $game->new_lap();
|
||||
if($game->get_continue()){
|
||||
$continue = 1;
|
||||
}else{
|
||||
$continue = 0;
|
||||
}
|
||||
echo json_encode(array(
|
||||
'gameId' => $game->gameId,
|
||||
'continue' => $continue,
|
||||
'lap' => $lap
|
||||
));
|
||||
$_SESSION['game'] = serialize($game);
|
||||
die;
|
||||
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
||||
}
|
175
src/arenas/tron/doc-en.html
Executable file
|
@ -0,0 +1,175 @@
|
|||
<h2> How Tron Fights Works </h2>
|
||||
<h3> Game's rules (excluding technical specs) </h3>
|
||||
<p> Each bot starts from a point randomly selected by the arena. </p>
|
||||
<p> At each turn, bots are asked to grow one square. they can choose the direction.</p>
|
||||
<p> If a bot strikes a cell already taken by its trail or that of another, it loses. </p>
|
||||
<p> It is not turn-based, bots are playinig simultaneously, so they can lose by choosing the same destination cell as another snake.</p>
|
||||
<p> Example: </p>
|
||||
<table class="tabledoc">
|
||||
<tr><th>9</th><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
|
||||
<tr><th>8</th><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
|
||||
<tr><th>7</th><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
|
||||
<tr><th>6</th><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
|
||||
<tr><th>5</th><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
|
||||
<tr><th>4</th><td class="green"></td><td class="green"></td><td class="green">oO</td><td></td><td class="red">oO</td><td class="red"></td><td></td><td></td><td></td></tr>
|
||||
<tr><th>3</th><td class="green"></td><td></td><td></td><td></td><td></td><td class="red"></td><td class="red"></td><td class="red"></td><td></td></tr>
|
||||
<tr><th>2</th><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
|
||||
<tr><th>1</th><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
|
||||
<tr><th>0</th><th>1</th><th>2</th><th>3</th><th>4</th><th>5</th><th>6</th><th>7</th><th>8</th><th>9</th></tr>
|
||||
</table>
|
||||
<p>If in the same turn the green snake and the red snake decide to go respectively to the left and to the right, they will telescope and both will lose.</p>
|
||||
<table class="tabledoc">
|
||||
<tr><th>9</th><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
|
||||
<tr><th>8</th><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
|
||||
<tr><th>7</th><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
|
||||
<tr><th>6</th><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
|
||||
<tr><th>5</th><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
|
||||
<tr><th>4</th><td class="green"></td><td class="green"></td><td class="green"></td><td class="brown">): :(</td><td class="red"></td><td class="red"></td><td></td><td></td><td></td></tr>
|
||||
<tr><th>3</th><td class="green"></td><td></td><td></td><td></td><td></td><td class="red"></td><td class="red"></td><td class="red"></td><td></td></tr>
|
||||
<tr><th>2</th><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
|
||||
<tr><th>1</th><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
|
||||
<tr><th>0</th><th>1</th><th>2</th><th>3</th><th>4</th><th>5</th><th>6</th><th>7</th><th>8</th><th>9</th></tr>
|
||||
</table>
|
||||
<p>When a snake dies, (when a bot loses) its tail is erased and cells taken by it become free for snakes still alive.</p>
|
||||
<p>The game ends when there are less than two snakes left</p>
|
||||
<h3>Grid</h3>
|
||||
<ul>
|
||||
<li>width: 1000 cells</li>
|
||||
<li>Height: 1000 cells</li>
|
||||
</ul>
|
||||
|
||||
<h3>Communications between the arena and the bots</h3>
|
||||
<p>The arena does send http (s) requests to the bots.</p>
|
||||
<ul>
|
||||
<li>Type: POST</li>
|
||||
<li>in JSON format inside the query's body</li>
|
||||
</ul>
|
||||
|
||||
<h3>Initialization of the game</h3>
|
||||
<p>The arena (this site) sends the following exemple message to your bot:</p>
|
||||
<pre>
|
||||
{"game-id":"1679","action":"init","game":"tron","board":"","players":4,"player-index":2}
|
||||
</pre>
|
||||
<ul>
|
||||
<li><em>game-id</em> String The unique identifier of the game. Your bot can be led to play several parts simultaneously. If the functioning of your bot requires to keep data between two turns of the arena, this identifier will be useful.</li>
|
||||
<li><em>action</em> String, can be "init" or "play-turn". For the current step, it's "init"</li>
|
||||
<li><em>game</em> String, Always "tron" here,can be usefull if you use the same URL to serve many bots</li>
|
||||
<li><em>board</em> Is empty at this step./li>
|
||||
<li><em>players</em> Intreger indicating how many bots are playing this game.Your bot can "play" several players on the same game. What if you had them collaborate in that case?</li>
|
||||
<li><em>player-index</em> Entier Your bot number. The first bot is player-index: 0. You'll keep the same number throughout the game, even if some others players are already dead.</li>
|
||||
</ul>
|
||||
|
||||
<p>Your bot must respond with an array in json format, like this:</p>
|
||||
<pre>
|
||||
{"name":"botName"}
|
||||
</pre>
|
||||
<p>
|
||||
The arena does not currently verify this response, the init step has been inserted to ensure compatibility with Bolosseum. If your bot answers a blank page at this stage (action = init), it will work as part of botsarena.
|
||||
</p>
|
||||
|
||||
<h3>Game's laps</h3>
|
||||
<p>Exemple of arena's message send on the first lap:</p>
|
||||
|
||||
|
||||
<p>Exemple of arena's message send after some laps:</p>
|
||||
<pre>
|
||||
{"game-id":"1680","action":"play-turn","game":"tron","board":[[[687,110],[687,111],[686,111],[686,110],[686,109],[686,108],[686,107],[685,107],[685,108],[685,109],[684,109],[684,110],[684,111],[684,112],[684,113],[683,113],[682,113],[681,113],[681,114],[681,115],[681,116],[680,116],[680,117],[679,117],[679,116],[679,115],[679,114],[679,113],[679,112],[679,111],[679,110],[679,109],[678,109],[678,108],[677,108],[676,108],[676,107],[676,106],[676,105],[676,104],[676,103],[675,103],[674,103],[674,102],[673,102],[672,102],[672,101],[671,101],[670,101],[669,101],[669,102],[669,103],[669,104],[669,105],[669,106],[669,107],[668,107],[668,108],[668,109],[668,110],[668,111],[668,112],[667,112],[667,111],[667,110],[666,110],[666,109],[666,108],[665,108],[664,108],[664,107],[664,106],[663,106],[663,107],[663,108],[662,108],[661,108],[660,108],[660,107],[659,107],[659,106],[658,106],[657,106],[657,105],[657,104],[656,104],[656,103],[655,103],[655,102],[655,101],[654,101],[654,102],[654,103],[654,104],[653,104],[653,103],[653,102],[653,101],[653,100],[652,100],[651,100],[651,101],[651,102],[651,103],[651,104],[650,104],[650,105],[650,106],[649,106],[649,105],[648,105]],[[100,225],[100,226],[100,227],[100,228],[100,229],[100,230],[100,231],[99,231],[98,231],[98,232],[97,232],[96,232],[96,231],[96,230],[96,229],[96,228],[96,227],[96,226],[96,225],[96,224],[96,223],[95,223],[95,222],[94,222],[94,221],[94,220],[93,220],[92,220],[92,219],[92,218],[92,217],[92,216],[92,215],[91,215],[91,214],[90,214],[89,214],[89,213],[89,212],[88,212],[88,213],[88,214],[87,214],[87,213],[86,213],[86,214],[86,215],[85,215],[85,214],[84,214],[84,215],[83,215],[83,216],[83,217],[82,217],[82,216],[81,216],[81,215],[80,215],[80,216],[79,216],[79,215],[79,214],[78,214],[77,214],[77,213],[77,212],[76,212],[75,212],[75,211],[75,210],[75,209],[74,209],[74,210],[74,211],[73,211],[73,210],[72,210],[72,209],[71,209],[71,210],[70,210],[70,209],[69,209],[68,209],[68,210],[68,211],[67,211],[67,210],[67,209],[67,208],[66,208],[66,207],[66,206],[66,205],[66,204],[66,203],[65,203],[65,204],[64,204],[64,203],[63,203],[63,204],[63,205],[63,206],[62,206],[61,206],[61,207],[60,207],[60,208],[59,208]]],"player-index":0,"players":2}
|
||||
</pre>
|
||||
<p>Following fields are as in the previous paragraph:</p>
|
||||
<ul>
|
||||
<li><em>game-id</em></li>
|
||||
<li><em>game</em></li>
|
||||
<li><em>players</em></li>
|
||||
<li><em>player-index</em></li>
|
||||
</ul>
|
||||
<p>Not same fields are:</p>
|
||||
<ul>
|
||||
<li><em>action</em>String, always "play-turn" for this step.</li>
|
||||
<li><em>board</em>A full chapter is needed ti explain that.</li>
|
||||
</ul>
|
||||
<h4>The map / the board</h4>
|
||||
<p>It is contained on "board" field. All the cells taken by each player are writted in.</p>
|
||||
|
||||
It is composed of several subarrays / subobjects (depending on your programming language).
|
||||
the sub-tables haved the following form:
|
||||
<ul>
|
||||
<li>Board:
|
||||
<ul>
|
||||
<li>Player 1
|
||||
<ul>
|
||||
<li>Cell 1
|
||||
<ul>
|
||||
<li>Coordinate X</li>
|
||||
<li>Coordinate Y</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Cell2
|
||||
<ul>
|
||||
<li>Coordinate X</li>
|
||||
<li>Coordinate Y</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Player 2>
|
||||
<ul>
|
||||
<li>Cell 1
|
||||
<ul>
|
||||
<li>Coordinate X</li>
|
||||
<li>Coordinate Y</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Cell 2
|
||||
<ul>
|
||||
<li>Coordinate X</li>
|
||||
<li>Coordinate Y</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</li>
|
||||
<li>...</li>
|
||||
<ul>
|
||||
</li>
|
||||
</ul>
|
||||
<p> JSON form is as follows:</p>
|
||||
|
||||
<p>
|
||||
[<br />
|
||||
[<br />
|
||||
[x1,y1],[x2,y2],[x3,y3]<br />
|
||||
],<br />
|
||||
[<br />
|
||||
(...)<br />
|
||||
]<br />
|
||||
</p>
|
||||
<p>Trails'order on this table is the same as Player's order. So, your "snake" has the trail corresponding with player-index. (First player-index value is 0).</p>
|
||||
<p>coordinate's are from the head to the trail. Bots grow by moving their heads.</p>
|
||||
|
||||
<h2>Bot's awnser</h2>
|
||||
<p> It returns direction it wants on a JSON array.</p><p>4 possibilies:</p>
|
||||
|
||||
<pre>
|
||||
{"play":"x+"}
|
||||
</pre>
|
||||
<pre>
|
||||
{"play":"x-"}
|
||||
</pre>
|
||||
<pre>
|
||||
{"play":"y+"}
|
||||
</pre>
|
||||
<pre>
|
||||
{"play":"y-"}
|
||||
</pre>
|
||||
<p>The way to note the directions seems fairly explicit for me to not explain.</p>
|
||||
<h2>Scoring</h2>
|
||||
<p>The scoring (EHLO classification) remains on a logic of duels, although this game can contain more than two bots per game. The score is changed as the game happens each time a bot dies:</p>
|
||||
<ul>
|
||||
<li> During the defeat of a bot, the arena records a draw against all the other bots that also lost in the same round of play.</li>
|
||||
<li>It also records a defeat of the dead bots against all the bots still in the race at the end of the round.</li>
|
||||
</ul>
|
||||
|
||||
<h2>Tools to develop and test your bot</h2>
|
||||
<p>Unfortunately no tools yet. It will come in the weeks to come, maybe in the form of a swagger interface</p>
|
||||
<p>And I must also develop an AI less stupid than stupid IA, because currently as it commits suicide too fast, it does not allow to test the arena nor a more intelligent bot.</p>
|
||||
ent</p>
|
171
src/arenas/tron/doc-fr.html
Executable file
|
@ -0,0 +1,171 @@
|
|||
<h2>Fonctionnement des combats de Tron</h2>
|
||||
<h3>Règles du jeu (hors specs techniques)</h3>
|
||||
<p>Chaque bot démarre depuis une case choisie au hasard par l'arène.</p>
|
||||
<p>A chaque tour, il est demandé aux bots de grandir d'une case. Il peut choisir la direction. Sa queue s'allonge.</p>
|
||||
<p>Si un bot percute une case déjà prise par sa queue ou celle d'un autre, il perd.</p>
|
||||
<p>Ce n'est pas du tour par tour, les bots jouent simultanément, ils peuvent donc perdre en choisissant la même case de destination qu'un autre serpent</p>
|
||||
<p>Exemple:</p>
|
||||
<table class="tabledoc">
|
||||
<tr><th>9</th><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
|
||||
<tr><th>8</th><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
|
||||
<tr><th>7</th><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
|
||||
<tr><th>6</th><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
|
||||
<tr><th>5</th><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
|
||||
<tr><th>4</th><td class="green"></td><td class="green"></td><td class="green">oO</td><td></td><td class="red">oO</td><td class="red"></td><td></td><td></td><td></td></tr>
|
||||
<tr><th>3</th><td class="green"></td><td></td><td></td><td></td><td></td><td class="red"></td><td class="red"></td><td class="red"></td><td></td></tr>
|
||||
<tr><th>2</th><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
|
||||
<tr><th>1</th><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
|
||||
<tr><th>0</th><th>1</th><th>2</th><th>3</th><th>4</th><th>5</th><th>6</th><th>7</th><th>8</th><th>9</th></tr>
|
||||
</table>
|
||||
<p>Si dans le même tour le serpent vert et le serpent rouge décident d'aller respectivement vers la gauche et vers la droite, ils vont se télescoper et perdre tous les deux.</p>
|
||||
<table class="tabledoc">
|
||||
<tr><th>9</th><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
|
||||
<tr><th>8</th><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
|
||||
<tr><th>7</th><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
|
||||
<tr><th>6</th><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
|
||||
<tr><th>5</th><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
|
||||
<tr><th>4</th><td class="green"></td><td class="green"></td><td class="green"></td><td class="brown">): :(</td><td class="red"></td><td class="red"></td><td></td><td></td><td></td></tr>
|
||||
<tr><th>3</th><td class="green"></td><td></td><td></td><td></td><td></td><td class="red"></td><td class="red"></td><td class="red"></td><td></td></tr>
|
||||
<tr><th>2</th><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
|
||||
<tr><th>1</th><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
|
||||
<tr><th>0</th><th>1</th><th>2</th><th>3</th><th>4</th><th>5</th><th>6</th><th>7</th><th>8</th><th>9</th></tr>
|
||||
</table>
|
||||
<p>Lorsqu'un serpent meurt, (lorsqu'un bot perd) Sa queue est effacée et les cases prises par cette dernière deviennent libres pour les serpents encore en vie.</p>
|
||||
<p>Le jeu prend fin lorsqu'il reste moins de deux serpents.</p>
|
||||
<h3>La grille</h3>
|
||||
<ul>
|
||||
<li>largeur: 1000 cases</li>
|
||||
<li>hauteur: 1000 cases</li>
|
||||
</ul>
|
||||
|
||||
<h3>Communications entre l'arène et les bots</h3>
|
||||
<p>L'arène fait émet des requêtes http(s) à destination des bots.</p>
|
||||
<ul>
|
||||
<li>Type: POST</li>
|
||||
<li>informations: au format JSON dans le corps de la requetes</li>
|
||||
</ul>
|
||||
|
||||
<h3>Initialisation de la partie</h3>
|
||||
<p>L'arène (ce site) envoie le message (exemple) suivant à votre bot:</p>
|
||||
<pre>
|
||||
{"game-id":"1679","action":"init","game":"tron","board":"","players":4,"player-index":2}
|
||||
</pre>
|
||||
<ul>
|
||||
<li><em>game-id</em> String Identifiant unique de la partie. Votre bot peut être emmené à jouer plusieurs parties en simultané. Si le fonctionnement de votre bot nécessite de suivre / conserver des valeurs entre deux tours de l'arène, cet identifiant vous sera utile.</li>
|
||||
<li><em>action</em> String, peut prendre les valeurs init ou play-turn, mais à cette étape, c'est init</li>
|
||||
<li><em>game</em> String, sera toujours "tron" ici. Peut vous servir si vous utilisez la même URL pour plusieurs bots.</li>
|
||||
<li><em>board</em> Sera vide à cette étape</li>
|
||||
<li><em>players</em> Entier Vous indique le nombre de joueurs sur cette partie. Il est possible que votre bot "joue" plusieurs joueurs. Et si vous les faisiez collaborer en ce cas là?</li>
|
||||
<li><em>player-index</em> Entier Le numéro de votre bot. Le premier bot a le numéro 0. Vous gardez le même numéro durant toute la partie, même si des joueurs sont déjà morts</li>
|
||||
</ul>
|
||||
|
||||
<p>Votre bot doit répondre par un tableau au format json, comme ceci:</p>
|
||||
<pre>
|
||||
{"name":"botName"}
|
||||
</pre>
|
||||
<p>L'arène ne vérifie pas actuellement cette réponse, l'étape d'init a été insérée pour assurer la compatibilité avec <a href="https://github.com/moul/bolosseum">Bolosseum</a>.
|
||||
Si votre bot repond une page blanche à cette étape (action=init), ça marchera dans le cadre de botsarena.</p>
|
||||
|
||||
<h3>Tours de jeu</h3>
|
||||
<p>Exemple de message envoyé par l'arène au premier tour de jeu:</p>
|
||||
|
||||
<p>Exemple de message envoyé par l'arène après quelques tours de jeu:</p>
|
||||
<pre>
|
||||
{"game-id":"1680","action":"play-turn","game":"tron","board":[[[687,110],[687,111],[686,111],[686,110],[686,109],[686,108],[686,107],[685,107],[685,108],[685,109],[684,109],[684,110],[684,111],[684,112],[684,113],[683,113],[682,113],[681,113],[681,114],[681,115],[681,116],[680,116],[680,117],[679,117],[679,116],[679,115],[679,114],[679,113],[679,112],[679,111],[679,110],[679,109],[678,109],[678,108],[677,108],[676,108],[676,107],[676,106],[676,105],[676,104],[676,103],[675,103],[674,103],[674,102],[673,102],[672,102],[672,101],[671,101],[670,101],[669,101],[669,102],[669,103],[669,104],[669,105],[669,106],[669,107],[668,107],[668,108],[668,109],[668,110],[668,111],[668,112],[667,112],[667,111],[667,110],[666,110],[666,109],[666,108],[665,108],[664,108],[664,107],[664,106],[663,106],[663,107],[663,108],[662,108],[661,108],[660,108],[660,107],[659,107],[659,106],[658,106],[657,106],[657,105],[657,104],[656,104],[656,103],[655,103],[655,102],[655,101],[654,101],[654,102],[654,103],[654,104],[653,104],[653,103],[653,102],[653,101],[653,100],[652,100],[651,100],[651,101],[651,102],[651,103],[651,104],[650,104],[650,105],[650,106],[649,106],[649,105],[648,105]],[[100,225],[100,226],[100,227],[100,228],[100,229],[100,230],[100,231],[99,231],[98,231],[98,232],[97,232],[96,232],[96,231],[96,230],[96,229],[96,228],[96,227],[96,226],[96,225],[96,224],[96,223],[95,223],[95,222],[94,222],[94,221],[94,220],[93,220],[92,220],[92,219],[92,218],[92,217],[92,216],[92,215],[91,215],[91,214],[90,214],[89,214],[89,213],[89,212],[88,212],[88,213],[88,214],[87,214],[87,213],[86,213],[86,214],[86,215],[85,215],[85,214],[84,214],[84,215],[83,215],[83,216],[83,217],[82,217],[82,216],[81,216],[81,215],[80,215],[80,216],[79,216],[79,215],[79,214],[78,214],[77,214],[77,213],[77,212],[76,212],[75,212],[75,211],[75,210],[75,209],[74,209],[74,210],[74,211],[73,211],[73,210],[72,210],[72,209],[71,209],[71,210],[70,210],[70,209],[69,209],[68,209],[68,210],[68,211],[67,211],[67,210],[67,209],[67,208],[66,208],[66,207],[66,206],[66,205],[66,204],[66,203],[65,203],[65,204],[64,204],[64,203],[63,203],[63,204],[63,205],[63,206],[62,206],[61,206],[61,207],[60,207],[60,208],[59,208]]],"player-index":0,"players":2}
|
||||
</pre>
|
||||
<p>Les champs suivants sont les mêmes qu'au paragraphe précédent:</p>
|
||||
<ul>
|
||||
<li><em>game-id</em></li>
|
||||
<li><em>game</em></li>
|
||||
<li><em>players</em></li>
|
||||
<li><em>player-index</em></li>
|
||||
</ul>
|
||||
|
||||
Les champs qui différent sont:
|
||||
<ul>
|
||||
<li><em>action</em>String, est toujours "play-turn" à cette étape</li>
|
||||
<li><em>board</em>Je vous explique ça au chapitre suivant.</li>
|
||||
</ul>
|
||||
<h4>La Carte (board)</h4>
|
||||
<p>Elle est représentée dans le champs "board" du JSON envoyé par l'arène. Elle décrit les cases prises par chaque joueur.</p>
|
||||
<p> Elle est composée de plusieurs sous-arrays/sous-objets (en fonction de votre language de programmation). Elle se décompose en sous tableaux de la forme suivante:<p>
|
||||
<ul>
|
||||
<li>Board:
|
||||
<ul>
|
||||
<li>Joueur 1
|
||||
<ul>
|
||||
<li>Case1
|
||||
<ul>
|
||||
<li>Coordonnée X</li>
|
||||
<li>Coordonnée Y</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Case2
|
||||
<ul>
|
||||
<li>coordonnée X</li>
|
||||
<li>Coordonnée Y</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Joueur 2>
|
||||
<ul>
|
||||
<li>Case1
|
||||
<ul>
|
||||
<li>coordonnée X</li>
|
||||
<li>Coordonnée Y</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Case2
|
||||
<ul>
|
||||
<li>coordonnée X</li>
|
||||
<li>Coordonnée Y</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</li>
|
||||
<li>...</li>
|
||||
<ul>
|
||||
</li>
|
||||
</ul>
|
||||
<p> La représentation JSON est la suivante:</p>
|
||||
|
||||
<p>
|
||||
[<br />
|
||||
[<br />
|
||||
[x1,y1],[x2,y2],[x3,y3]<br />
|
||||
],<br />
|
||||
[<br />
|
||||
(...)<br />
|
||||
]<br />
|
||||
</p>
|
||||
<p>L'ordre des "queues" des serpents dans ce tableau correspond à l'ordre des joueurs. Donc votre serpent est representée par la queue correspondant à player-index (le décompte de player-index commence par 0).</p>
|
||||
<p>L'ordre des couples de coordonnées de chaque bot, est dans le sens tête vers queue. Les bots grandissent à chaque tour en déplaçant leur tête.</p>
|
||||
<h2>La réponse de votre bot</h2>
|
||||
<p>Il retourne la direction qu'il souhaite prendre sous la forme d'un array.</p><p>4 possibilités:</p>
|
||||
|
||||
<pre>
|
||||
{"play":"x+"}
|
||||
</pre>
|
||||
<pre>
|
||||
{"play":"x-"}
|
||||
</pre>
|
||||
<pre>
|
||||
{"play":"y+"}
|
||||
</pre>
|
||||
<pre>
|
||||
{"play":"y-"}
|
||||
</pre>
|
||||
<p>La façon de noter les directions me semble assez explicite pour que je ne détaille pas.</p>
|
||||
<h2>Scoring</h2>
|
||||
<p>Le scorring (classement EHLO) reste sur une logique de duels, bien que ce jeu puisse contenir plus de deux bots par match. Le score est modifié au fur et à mesure de la partie à chaque fois qu'un bot "décède":</p>
|
||||
<ul>
|
||||
<li>Lors de la défaite d'un bot, l'arène enregistre un match nul contre tous les autres bots qui ont aussi perdu au même tour de jeu.</li>
|
||||
<li>Elle enregistre aussi une défaite des bots morts contre tous les bots encore en course à la fin du tour.</li>
|
||||
</ul>
|
||||
<h2>
|
||||
|
||||
<h2>Outils pour développer et tester votre bot</h2>
|
||||
<p>Malheureusement aucun outil pour le moment. Ca viendra dans les semaines qui viennent, peut être sous la forme d'une interface swagger</p>
|
||||
<p>Et il faut aussi que je développe une AI moins stupide que stupid IA, car actuellement comme il se suicide trop vite, ça ne permet ni de tester l'arène ni un bot plus intelligent</p>
|
1
src/arenas/tron/functions.php
Executable file
|
@ -0,0 +1 @@
|
|||
<?php
|
183
src/arenas/tron/js.js
Executable file
|
@ -0,0 +1,183 @@
|
|||
function createElem(type,attributes){
|
||||
var elem=document.createElement(type);
|
||||
for (var i in attributes)
|
||||
{elem.setAttribute(i,attributes[i]);}
|
||||
return elem;
|
||||
}
|
||||
function addLog(message){
|
||||
var divLogs = document.getElementById("logs");
|
||||
var p=createElem('p',{});
|
||||
p.innerHTML=message;
|
||||
divLogs.appendChild(p);
|
||||
divLogs.scrollTop = divLogs.scrollHeight;
|
||||
|
||||
}
|
||||
|
||||
function createElemNS(type,attributes){
|
||||
//same as createElem but with ns for svg file
|
||||
var elem=document.createElementNS("http://www.w3.org/2000/svg",type);
|
||||
for (var i in attributes)
|
||||
{elem.setAttributeNS(null,i,attributes[i]);}
|
||||
return elem;
|
||||
}
|
||||
function changeSelect(number,botId){
|
||||
//show an other selector if bot is chosen
|
||||
var next = parseInt(number) + 1;
|
||||
if((botId > 0) && (number < 12)){
|
||||
if(document.getElementById('selectBot' + next)){
|
||||
return;
|
||||
}else{
|
||||
show_bot_panel(next);
|
||||
}
|
||||
if(number > 0){
|
||||
document.getElementById('fightButton').disabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
function show_bot_panel(number){
|
||||
//configurePlayers
|
||||
var fieldset = createElem('fieldset',{'class':'botFormulaire'});
|
||||
var legend = createElem('legend',{});
|
||||
legend.innerHTML = 'bot ' + number;
|
||||
fieldset.appendChild(legend);
|
||||
var p=createElem('p');
|
||||
var select = createElem('select',{'id':'selectBot' + number, 'onchange':'changeSelect(' + number + ',this.value);'});
|
||||
var option = createElem('option',{'value': '0', 'selected': 'selected','disabled':'disabled'});
|
||||
option.innerHTML = '';
|
||||
select.appendChild(option);
|
||||
for (var i = 0; i < botsAvailable.length; i++){
|
||||
var option = createElem('option',{'value': botsAvailable[i]['id']});
|
||||
option.innerHTML = botsAvailable[i]['name'];
|
||||
select.appendChild(option);
|
||||
}
|
||||
p.appendChild(select);
|
||||
fieldset.appendChild(p);
|
||||
|
||||
document.getElementById('configurePlayers').appendChild(fieldset);
|
||||
}
|
||||
|
||||
function applyInitMessage(req,xd_check){
|
||||
document.getElementById('fightButton').disabled=true;
|
||||
//callback function when init game request
|
||||
if(req.readyState == 4){
|
||||
if(req.status == 200) {
|
||||
//alert (req.responseText);
|
||||
try{
|
||||
var ret = JSON.parse(req.responseText);
|
||||
}catch(e){
|
||||
addLog('erreur' + req.responseText);
|
||||
return;
|
||||
}
|
||||
addLog(ret['logs']);
|
||||
if(ret['status'] == true){
|
||||
drawMap(ret['botsPosition']);
|
||||
play(ret['gameId'],xd_check);
|
||||
}
|
||||
|
||||
}else{
|
||||
alert ('error ' + req.status + req.responseText );
|
||||
document.getElementById('fightButton').disabled=false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function drawMap(map){
|
||||
//console.log(map);
|
||||
var botsColor = ['cyan','darkmagenta','darkred','darkslategrey','deeppink','dodgerblue','goldenrod','grey','indigo','lightgreen','mediumslateblue','midnightblue'];
|
||||
|
||||
for (var botId in map){
|
||||
if(typeof(map[botId]['x']) != 'undefined'){ //don't draw deads bots
|
||||
//draw the point
|
||||
var rect=createElemNS('rect',{'x':map[botId]['x'],'y':map[botId]['y'],'width':'2','height':'2','style':'fill:' + botsColor[botId] + ';'});
|
||||
document.getElementById('map').appendChild(rect);
|
||||
}
|
||||
}
|
||||
}
|
||||
function delTrail(order){
|
||||
var botsColor = ['cyan','darkmagenta','darkred','darkslategrey','deeppink','dodgerblue','goldenrod','grey','indigo','lightgreen','mediumslateblue','midnightblue'];
|
||||
//on supprime tous les elements ayant la couleur correspndante.
|
||||
|
||||
var container = document.getElementById('map');
|
||||
|
||||
var listNode = container.children;
|
||||
for (var i= 0; i < listNode.length; i++){
|
||||
if( listNode[i].style.fill == botsColor[order] ){
|
||||
container.removeChild(listNode[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
function play(gameId,xd_check){
|
||||
|
||||
var req = new XMLHttpRequest();
|
||||
req.onreadystatechange = function(){
|
||||
if(req.readyState == 4){
|
||||
if(req.status == 200) {
|
||||
//addLog(req.responseText);
|
||||
var reponse = JSON.parse(req.responseText);
|
||||
|
||||
//to do Effacer les bots perdants
|
||||
for(var i=0; i < reponse['lap']['loosers'].length; i++){
|
||||
//alert (req.responseText);
|
||||
//return;
|
||||
delTrail(reponse['lap']['loosers'][i]['order']);
|
||||
|
||||
//find the bot name
|
||||
for (var j = 0; j < botsAvailable.length; j ++){
|
||||
if(botsAvailable[j]['id'] == reponse['lap']['loosers'][i]['id']){
|
||||
var botName = botsAvailable[j]['name'];
|
||||
}
|
||||
}
|
||||
addLog("Bot " + reponse['lap']['loosers'][i]['order'] + " Name: " + botName + " loosed");
|
||||
}
|
||||
drawMap(reponse['lap']['last_points']);
|
||||
if(reponse['continue'] == '1'){
|
||||
|
||||
//setTimeout(function(){
|
||||
play(gameId,xd_check);
|
||||
//} ,500);
|
||||
|
||||
}else{
|
||||
addLog("Game ended");
|
||||
document.getElementById('fightButton').disabled=false;
|
||||
}
|
||||
|
||||
}else{
|
||||
alert('erreur' + req.status);
|
||||
|
||||
}
|
||||
}
|
||||
};
|
||||
req.open("POST", '/tron', true);
|
||||
req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
|
||||
req.send('act=play&xd_check=' + xd_check + '&gameId=' + gameId + '&fullLogs=' + document.getElementById("fullLogs").checked);
|
||||
}
|
||||
function tron(xd_check){
|
||||
//empty
|
||||
while (document.getElementById('fightResult').firstChild) {
|
||||
document.getElementById('fightResult').removeChild(document.getElementById('fightResult').firstChild);
|
||||
}
|
||||
// draw border;
|
||||
var svg = createElemNS('svg',{'id':'map','width':'500','height':'500','viewBox':'0 0 1000 1000'});
|
||||
var rect=createElemNS('rect',{'x':'0','y':'0','width':'1000','height':'1000','style':'stroke:#000000; fill:none;'});
|
||||
svg.appendChild(rect);
|
||||
document.getElementById("fightResult").appendChild(svg);
|
||||
|
||||
var plogs = createElem("p",{'id':'logs'});
|
||||
document.getElementById("fightResult").appendChild(plogs);
|
||||
//get bot list
|
||||
var botsList=[];
|
||||
var i=0;
|
||||
while(document.getElementById('selectBot' + i)){
|
||||
botsList.push(document.getElementById('selectBot' + i).value);
|
||||
i++;
|
||||
}
|
||||
|
||||
//ask arena to send bots init messages
|
||||
var request = new XMLHttpRequest();
|
||||
request.onreadystatechange = function(){applyInitMessage(request,xd_check)};
|
||||
request.open("POST", '/tron', true);
|
||||
request.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
|
||||
request.send('act=initGame&xd_check=' + xd_check + '&bots=' + JSON.stringify(botsList) + '&fullLogs=' + document.getElementById("fullLogs").checked);
|
||||
}
|
33
src/arenas/tron/public.php
Executable file
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
#- BEGIN LICENSE BLOCK ---------------------------------------
|
||||
#
|
||||
# This file is part of botsArena.
|
||||
#
|
||||
# Copyright (C) Gnieark et contributeurs
|
||||
# Licensed under the GPL version 3.0 license.
|
||||
# See LICENSE file or
|
||||
# http://www.gnu.org/licenses/gpl-3.0-standalone.html
|
||||
#
|
||||
# -- END LICENSE BLOCK -----------------------------------------
|
||||
|
||||
require_once(__DIR__."/functions.php");
|
||||
$bots=get_Bots_Array('tron');
|
||||
$botsArr=array();
|
||||
foreach($bots as $bot){
|
||||
$botsArr[]=array('id' => $bot['id'], 'name' => $bot['name']);
|
||||
}
|
||||
?>
|
||||
<article id="mainArticle">
|
||||
<p>Cette arène n'est qu'à moitié ouverte. Elle fonctionne et la documentation est écrite, mais les outils pour vous aider à débogguer et tester un bot n'ont pas été créés. Ca viendra. </p>
|
||||
<h2><?php echo $lang['MAKE_DUEL'];?></h2>
|
||||
<aside id="configurePlayers">
|
||||
|
||||
</aside>
|
||||
<script>
|
||||
var botsAvailable = <?php echo json_encode($botsArr); ?>;
|
||||
show_bot_panel(0);
|
||||
</script>
|
||||
<p><input type="checkbox" id="fullLogs"/><label for="fullLogs">view the full logs</label></p>
|
||||
<p><input id="fightButton" disabled="disabled" type="button" value="<?php echo $lang['FIGHT']; ?>" onclick="tron('<?php echo xd_check_input(2); ?>');"></p>
|
||||
<div id="fightResult"></div>
|
||||
</article>
|
10
src/arenas/tron/style.css
Executable file
|
@ -0,0 +1,10 @@
|
|||
.tabledoc{border-collapse:collapse;}
|
||||
.tabledoc tr td,.tabledoc tr th{border: 1px solid green; padding-left: 5px;width: 20px; height: 20px;}
|
||||
.green{background-color: green;}
|
||||
.red{background-color: red;}
|
||||
.brown{background-color: grey;}
|
||||
#configurePlayers{width: 100%;}
|
||||
#configurePlayers fieldset{display: block; width: 100px; float:left;}
|
||||
#logs{display:block;padding-left:10px; height: 200px; overflow-y: scroll;}
|
||||
#logs p em {color: grey; font-size: 70%; test-transform:italic;}
|
||||
code{font-family: monospace;}
|
27
src/arenas/tron/test/CoordsTest.php
Executable file
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
use PHPUnit\Framework\TestCase;
|
||||
require_once '../Coords.php';
|
||||
require_once '../Direction.php';
|
||||
|
||||
class CoordsTest extends TestCase {
|
||||
|
||||
|
||||
public function testCirculaire(){
|
||||
$startCoord = new Coords(15,3);
|
||||
$endCoord = $startCoord->addDirection(Direction::make('x+'))
|
||||
->addDirection(Direction::make('y-'))
|
||||
->addDirection(Direction::make('x-'))
|
||||
->addDirection(Direction::make('y+'));
|
||||
|
||||
|
||||
|
||||
$this->assertTrue($endCoord == $startCoord);
|
||||
}
|
||||
public function testIsDifferent(){
|
||||
$startCoord = new Coords(15,3);
|
||||
$endCoord = $startCoord->addDirection(Direction::make('x+'));
|
||||
fwrite(STDERR, $startCoord ."\n");
|
||||
fwrite(STDERR, $endCoord ."\n");
|
||||
$this->assertFalse($endCoord == $startCoord);
|
||||
}
|
||||
}
|
71
src/arenas/tron/test/DirectionTest.php
Executable file
|
@ -0,0 +1,71 @@
|
|||
<?php
|
||||
use PHPUnit\Framework\TestCase;
|
||||
require_once '../Direction.php';
|
||||
|
||||
class Directiontest extends TestCase {
|
||||
public function invalidStrings() {
|
||||
return array(
|
||||
array('jhgjhg'),
|
||||
array('X+'),
|
||||
array(4)
|
||||
);
|
||||
}
|
||||
/**
|
||||
* @dataProvider invalidStrings
|
||||
* @expectedException invalidDirectionException
|
||||
*/
|
||||
public function testRejectInvalidString($invalidString){
|
||||
Direction::make($invalidString);
|
||||
}
|
||||
public function validStrings(){
|
||||
return array(
|
||||
array('x+'),
|
||||
array('y+'),
|
||||
array('x-'),
|
||||
array('y-'),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider validStrings
|
||||
*/
|
||||
public function testAcceptValidString($validString){
|
||||
$this->assertInstanceOf(Direction::class,Direction::make($validString));
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider validStrings
|
||||
*/
|
||||
public function testDeltaXY($validString){
|
||||
$dir = Direction::make($validString);
|
||||
$this->assertTrue($dir->deltaX != 0 || $dir->deltaY != 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider validStrings
|
||||
*/
|
||||
public function testToString($validString){
|
||||
$this->assertTrue(Direction::make($validString) == $validString);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider validStrings
|
||||
*/
|
||||
public function testOpposite($validString){
|
||||
$dir = Direction::make($validString);
|
||||
$op = $dir->opposite();
|
||||
|
||||
$this->assertInstanceOf(Direction::class,$op);
|
||||
$this->assertFalse($dir == $op);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider validStrings
|
||||
*/
|
||||
public function testOppositeOpposite($validString){
|
||||
$dir = Direction::make($validString);
|
||||
$opop = $dir->opposite()->opposite();
|
||||
$this->assertTrue($dir == $opop);
|
||||
}
|
||||
|
||||
}
|
64
src/arenas/tron/test/TrailTest.php
Executable file
|
@ -0,0 +1,64 @@
|
|||
<?php
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
require_once '../Direction.php';
|
||||
require_once '../Coords.php';
|
||||
require_once '../TronPlayer.php';
|
||||
|
||||
class TronPlayerTest extends TestCase {
|
||||
public function validPlayer(Direction $direction) {
|
||||
return new TronPlayer(
|
||||
'http://127.0.0.1',
|
||||
'test',
|
||||
new Coords(0, 0),
|
||||
$direction
|
||||
);
|
||||
}
|
||||
|
||||
public function testTronPlayerCreation() {
|
||||
$this->assertInstanceOf(
|
||||
TronPlayer::class,
|
||||
$this->validPlayer(Direction::make('x+'))
|
||||
);
|
||||
}
|
||||
|
||||
public function directions() {
|
||||
return array(
|
||||
array(Direction::make('x+')),
|
||||
array(Direction::make('x-')),
|
||||
array(Direction::make('y+')),
|
||||
array(Direction::make('y-')),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider directions
|
||||
* @expectedException OppositeForbiddenException
|
||||
*/
|
||||
public function testOppositeForbidden(Direction $direction) {
|
||||
$player = $this->validPlayer($direction);
|
||||
$player->changeDirection($direction->opposite());
|
||||
}
|
||||
|
||||
public function testAlreadyLost() {
|
||||
$right = Direction::make('x+');
|
||||
$down = Direction::make('y-');
|
||||
$left = Direction::make('x-');
|
||||
$up = Direction::make('y+');
|
||||
|
||||
$player = $this->validPlayer($right);
|
||||
$player->nextMove($right);
|
||||
$player->nextMove($down);
|
||||
$player->nextMove($left);
|
||||
|
||||
try {
|
||||
$player->nextMove($up);
|
||||
throw new Exception('TronPlayer did not throw AlreadyPlayedException');
|
||||
} catch(AlreadyPlayedException $e) { }
|
||||
|
||||
try {
|
||||
$player->nextMove($up);
|
||||
throw new Exception('TronPlayer did not throw AlreadyLostException');
|
||||
} catch(AlreadyLostException $e) { }
|
||||
}
|
||||
}
|
8
src/arenas_lists.php
Normal file → Executable file
|
@ -36,6 +36,14 @@ $arenas=array(
|
|||
'jsFile'=> "js.js",
|
||||
'cssFile'=> "style.css",
|
||||
'ludusUrl' => "/testBotScripts/connectfour.html"
|
||||
),
|
||||
array(
|
||||
'id' => "tron",
|
||||
'url' => "/tron",
|
||||
'title' => "Tron",
|
||||
'metaDescription' => 'Affrontements de bots à Tron',
|
||||
'jsFile'=> "js.js",
|
||||
'cssFile'=> "style.css"
|
||||
)
|
||||
|
||||
);
|
0
src/config.php.empty
Normal file → Executable file
0
src/editBot.php
Normal file → Executable file
108
src/functions.php
Normal file → Executable file
|
@ -9,6 +9,7 @@
|
|||
# http://www.gnu.org/licenses/gpl-3.0-standalone.html
|
||||
#
|
||||
# -- END LICENSE BLOCK -----------------------------------------
|
||||
include(__DIR__."/DUEL.php");
|
||||
|
||||
function get_arenas_list(){
|
||||
include (__DIR__."/arenas_lists.php");
|
||||
|
@ -114,6 +115,7 @@ function get_language_array(){
|
|||
return $lang;
|
||||
}
|
||||
function error($code,$message){
|
||||
error_log($code." ".$message);
|
||||
switch($code){
|
||||
case 404:
|
||||
header("HTTP/1.0 404 Not Found");
|
||||
|
@ -151,7 +153,7 @@ function conn_bdd(){
|
|||
}
|
||||
mysqli_select_db($linkMysql,$mysqlParams['database']);
|
||||
mysqli_set_charset($linkMysql, 'utf8');
|
||||
return $linkMysql; //does PHP can do that?
|
||||
return $linkMysql;
|
||||
|
||||
}
|
||||
function get_battles_history($game){
|
||||
|
@ -202,93 +204,73 @@ function ELO_get_podium($arena){
|
|||
}
|
||||
return $podium;
|
||||
}
|
||||
function ELO_get_k($elo){
|
||||
if ($elo < 1000){
|
||||
return 80;
|
||||
}
|
||||
if ($elo < 2000){
|
||||
return 50;
|
||||
}
|
||||
if ($elo <= 2400){
|
||||
return 30;
|
||||
}
|
||||
return 20;
|
||||
}
|
||||
function ELO_get_new_ranks($elo1,$elo2,$score){
|
||||
|
||||
|
||||
function save_battle($game,$bot1,$bot2,$result,$nameOrIds = 'name'){
|
||||
/*
|
||||
* return an array containing new ELO scores after a battle
|
||||
* $score : 0 player 2 won
|
||||
* 0.5 draws
|
||||
* 1 player 1 won
|
||||
* Calculate new ELO ranks and save them on conn_bdd
|
||||
*/
|
||||
|
||||
//good luck for understanding it
|
||||
//(see https://blog.antoine-augusti.fr/2012/06/maths-et-code-le-classement-elo/)
|
||||
return array(
|
||||
$elo1 + ELO_get_k($elo1) * ($score - (1/ (1 + pow(10,(($elo2 - $elo1) / 400))))),
|
||||
$elo2 + ELO_get_k($elo2) * (1 - $score - (1/ (1 + pow(10,(($elo1 - $elo2) / 400)))))
|
||||
);
|
||||
}
|
||||
function save_battle($game,$bot1,$bot2,$resultat){
|
||||
//resultat: 0 match nul, 1 bot1 gagne 2 bot 2 gagne
|
||||
|
||||
global $lnMysql;
|
||||
|
||||
$game=substr($game,0,8); //limit 8 char for limitting mysql index size
|
||||
|
||||
|
||||
//chercher les id de bot 1 et bot2
|
||||
$rs=mysqli_query($lnMysql,"SELECT name,id,ELO FROM bots
|
||||
WHERE name='".mysqli_real_escape_string($lnMysql,$bot1)."'
|
||||
OR name='".mysqli_real_escape_string($lnMysql,$bot2)."'");
|
||||
while($r=mysqli_fetch_row($rs)){
|
||||
$bots[$r[0]]=$r[1];
|
||||
$actualELO[$r[0]]=$r[2];
|
||||
if($nameOrIds == "name"){
|
||||
for( $i=1; $i<3; $i++){
|
||||
$str = "bot".$i;
|
||||
$botName = $$str;
|
||||
$rs=mysqli_query($lnMysql,"SELECT id,ELO FROM bots WHERE name='".mysqli_real_escape_string($lnMysql,$botName)."'");
|
||||
$r = mysqli_fetch_row($rs);
|
||||
$bot[$i] = array(
|
||||
'id' => $r[0],
|
||||
'ELO' => $r[1]
|
||||
);
|
||||
}
|
||||
}else{
|
||||
//the same, but query by id
|
||||
for( $i=1; $i<3; $i++){
|
||||
$str = "bot".$i;
|
||||
$botName = $$str;
|
||||
$rs=mysqli_query($lnMysql,"SELECT id,ELO FROM bots WHERE id='".mysqli_real_escape_string($lnMysql,$botName)."'");
|
||||
$r = mysqli_fetch_row($rs);
|
||||
$bot[$i] = array(
|
||||
'id' => $r[0],
|
||||
'ELO' => $r[1]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if((!isset($bots[$bot1])) OR (!isset($bots[$bot2]))){
|
||||
error (500,"database corrupt");
|
||||
die;
|
||||
}
|
||||
//apply $result
|
||||
$duel = new DUEL( $bot[1]["ELO"] , $bot[2]["ELO"]);
|
||||
|
||||
switch($resultat){
|
||||
switch ($result){
|
||||
case 0:
|
||||
$duel->drawGame();
|
||||
$field="nulCount";
|
||||
$eloScore = 0.5;
|
||||
break;
|
||||
case 1:
|
||||
$duel->oneWinsAgainstTwo();
|
||||
$field="player1_winsCount";
|
||||
$eloScore = 1;
|
||||
break;
|
||||
case 2:
|
||||
$duel->twoWinsAgainstOne();
|
||||
$field="player2_winsCount";
|
||||
$eloScore = 0;
|
||||
break;
|
||||
default:
|
||||
error (500,"something impossible has happened");
|
||||
error (500,"Oups");
|
||||
break;
|
||||
}
|
||||
|
||||
$newRanks = ELO_get_new_ranks($actualELO[$bot1],$actualELO[$bot2],$eloScore);
|
||||
|
||||
mysqli_multi_query($lnMysql,
|
||||
"
|
||||
UPDATE bots
|
||||
SET ELO='".$newRanks[0]."'
|
||||
WHERE id='".$bots[$bot1]."';
|
||||
|
||||
UPDATE bots
|
||||
SET ELO='".$newRanks[1]."'
|
||||
WHERE id='".$bots[$bot2]."';
|
||||
|
||||
|
||||
//update ELO rank on database
|
||||
mysqli_multi_query($lnMysql,"
|
||||
UPDATE bots SET ELO = '".$duel->rank1."' WHERE id='".$bot[1]["id"]."';
|
||||
UPDATE bots SET ELO = '".$duel->rank2."' WHERE id='".$bot[2]["id"]."';
|
||||
INSERT INTO arena_history(game,player1_id,player2_id,".$field.") VALUES
|
||||
('".mysqli_real_escape_string($lnMysql,$game)."',
|
||||
'".$bots[$bot1]."',
|
||||
'".$bots[$bot2]."',
|
||||
'".$bot[1]["id"]."',
|
||||
'".$bot[2]["id"]."',
|
||||
'1')
|
||||
ON DUPLICATE KEY UPDATE ".$field." = ".$field." + 1;");
|
||||
|
||||
ON DUPLICATE KEY UPDATE ".$field." = ".$field." + 1;
|
||||
");
|
||||
}
|
||||
function get_unique_id(){
|
||||
//increment the number
|
||||
|
|