Merge pull request #50 from gnieark/dev

Dev
This commit is contained in:
Gnieark 2016-05-31 12:52:37 +02:00
commit 0254556bbf
11 changed files with 516 additions and 61 deletions

View File

@ -3,9 +3,9 @@
* stupid IA for battle ship
* choose by random a free column
*/
$grid=json_decode($_POST['grid']);
$in=file_get_contents('php://input');
$params=json_decode($in, TRUE);
$grid=$params['grid'];
$colAvailable=array();
for($i=0;$i<7;$i++){

253
html/testBot.html Normal file
View File

@ -0,0 +1,253 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="author" content="Gnieark" />
<title>Test ton bot</title>
<style type="text/css">
body{width:100%; font-size:100%; line-height:140%; word-wrap:break-word; text-rendering:optimizelegibility;
margin:0 auto; font-family : "lucida grande", "gill sans", arial, sans-serif; left:auto;}
header{ background-color:#A60800; width: 100%; overflow: hidden; height: auto;}
header h1{display: block; font-size:300%; color:#FFF;float: left; margin-left: 5%;}
header nav{display: block; width: 45%; color:#FFF; float: right;}
#menus{ margin-left: 50px; width:100%; display: table;}
#menus a{color: #fff; display: table-cell; text-decoration: none;text-align: center;border-radius: 15px 15px 0px 0px;}
#menus a.selected{color:#202020; background-color:#fff;}
footer{height: 70px; display: block; color: #343638; font-size: 11pt; line-height: 15pt; margin: 0; padding-top: 15pt;
overflow-x: hidden; box-sizing: border-box; background-image: -webkit-linear-gradient(top, #f5f5f5,#e9e9e9);
border-top: 1px solid #bebebe; color: #999; font-size: 12px; line-height: 1.5em; text-align: center;width: 100%;}
footer a {margin:0px 15px 0px 15px; color: #666;text-decoration: none; font-weight: normal;}
#languages{float: right; text-align: right;}
section{margin: 0 auto; width: 90%;}
article{float: right; width:70%;}
aside{float:left; width: 28%; border-right: 1px dashed green;}
aside table {width: 90%;}
aside table tr td{width: 33%;}
aside table tr td input{width: 100%;}
.center{text-align: center;}
aside p img{ width: 100%; max-width:342px;}
form textarea, form input, form select {width:40%;}
form input[type=checkbox], form input[type=radio] { width:15px; }
form label {float:left; width:40%; margin-right:0.5em;
padding-top:0.2em; text-align:right;}
pre{ font-style: normal;font-size: 16px; margin-left: 32px;font-family: Consolas, "Times New Roman", Verdana;
border-left: 4px solid #CCC; padding-left: 8px;}
.battleGrid{display:table-cell; padding-left:10px; border-collapse:collapse; margin: 20px 20px 20px 20px;}
.battleGrid tr{}
.battleGrid tr td{border: 1px dashed green; text-align: center; font-weight: bold;min-width:20px; height:20px;}
.winCase{background-color:red;}
.hidden{display: none;}
#logs{font-size: 70%;}
</style>
<script>
var grid=[["","","","","","",""],
["","","","","","",""],
["","","","","","",""],
["","","","","","",""],
["","","","","","",""],
["","","","","","",""]];
var currentPlayer=1;
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 p=createElem('p');
p.innerHTML=message;
document.getElementById('logs').appendChild(p);
}
function changePlayerType(player,newValue){
if(newValue == "bot"){
document.getElementById('url' + player).disabled="";
}else{
document.getElementById('url' + player).disabled="disabled";
}
}
function playingAT(col){
//hide buttons
document.getElementById("playerButtons").setAttribute("class", "hidden");
if(currentPlayer == 1){
var symbol= "X";
}else{
var symbol="O";
}
//find the first line empty
var i=0;
for(i = 0; i < 6 , grid[i][col] !== ""; i++){
//nothing juste a counter
}
grid[i][col]=symbol;
document.getElementById('td' + col + '_' + i).innerHTML = symbol;
addLog('player ' + currentPlayer + ', symbol ' + symbol + ' played col ' + col);
//does he win?
var x=col;
var y=i;
var searchValue="";
for var k=0; k < 4; k++){
searchValue +=symbol;
}
//horizontaly
var line="";
for (var k=0; k < 7; k++){
if(grid[y][k] == ""){
line += " ";
}else{
line += grid[y][k];
}
}
if (line.indexOf(searchValue) > -1){
wins(currentPlayer);
return;
}
//verticaly
var line="";
for (var k=0; k < 6; k++){
if(grid[k][x] == ""){
line += " ";
}else{
line += grid[k][x];
}
}
if (line.indexOf(searchValue) > -1){
wins(currentPlayer);
return;
}
//diagonals
//\
for (var kx=x, var ky=y; kx < 7, ky
//change player
if(currentPlayer == 1){
play(2);
}else{
play(1);
}
}
function play(player){
currentPlayer=player;
if(document.getElementById("player" + player + "Type").value == "bot"){
//call bot url
if(currentPlayer == 1){
var symbol= "X";
}else{
var symbol="O";
}
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function(){if(xhr.readyState == 4){
if(xhr.status == 200) {
var response=xhr.responseText;
var t = new RegExp('^[0-6]$');
if (t.test(response)){
playingAT(response);
}else{
alert ('la reponse du bot doit etre un digit compris entre 0 et 7, correspondant à la colonne. Voici sa réponse: "' + response + '"' );
}
}else{
alert (xhr.status);
die;
}
}};
xhr.open("POST", document.getElementById('url' + player).value, true);
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xhr.send('game=connectFour&match_id=666-' + player + '&you=' + symbol + '&grid=' + JSON.stringify(grid) );
}else{
//wait for human response, show buttons
document.getElementById("playerButtons").setAttribute("class", "");
}
}
function startGame(){
//empty div
document.getElementById("fightResult").innerHTML="";
//create grid
var table=createElem('table',{'class':'battleGrid', 'id': 'grid'});
for (var i=6; i > -1; i--){
var tr=createElem('tr');
for (var j=0;j<7; j++){
var td=createElem('td',{'id': 'td' + j + '_' + i});
tr.appendChild (td);
}
table.appendChild(tr);
}
document.getElementById('fightResult').appendChild(table);
var divLogs=createElem("div",{"id":"logs"});
document.getElementById('fightResult').appendChild(divLogs);
var newTr=createElem('tr',{'id':'playerButtons','class':'hidden'});
for(var i = 0; i < 7; i++){
var newTd=createElem('td');
var button=createElem('input',{'type':'button','value': i, 'onclick': "playingAT('" + i + "');"});
newTd.appendChild(button);
newTr.appendChild(newTd);
}
document.getElementById('grid').appendChild(newTr);
grid=[["","","","","","",""],
["","","","","","",""],
["","","","","","",""],
["","","","","","",""],
["","","","","","",""],
["","","","","","",""]];
play(1);
}
</script>
</head>
<body>
<header>
<h1>Debug and test your connectFour AI</h1>
</header>
<section>
<aside>
<h2>Configure the test</h2>
<table>
<tr>
<td>Player 1</td>
<td><select id="player1Type" name="player1Type" onchange="changePlayerType(1,this.value);">
<option value="bot">bot</option>
<option value="human">human</option>
</select>
</td>
<td>
<input id="url1" type="text" name="player1URL" placeholder="url du bot http://localhost" value="https://botsarena.tinad.fr/StupidIAconnectFour.php"/>
</td>
</tr>
<tr>
<td>Player 2</td>
<td>
<select id="player2Type" name="player2Type" onchange="changePlayerType(2,this.value);">
<option value="human">human</option>
<option value="bot">bot</option>
</select>
</td>
<td>
<input id="url2" type="text" name="player2URL" placeholder="url du bot http://localhost" disabled value="https://botsarena.tinad.fr/StupidIAconnectFour.php"/>
</td>
</tr>
</table>
<p><input type="button" onclick="startGame()" value="Fight"/></p>
</aside>
<article id="fightResult">
</article>
</section>
<footer>
<a href="/p/About">About</a><a href="https://github.com/gnieark/botsArena">Bots'Arena source code</a><a href="/p/legals">Mentions légales</a>
</footer>
</body>
</html>

View File

View File

@ -84,12 +84,13 @@ switch ($_POST['act']){
'match_id' => $_SESSION['matchId']."-".$_SESSION['currentPlayer'],
'opponent' => $opponentName,
'you' => $you,
'grid' => json_encode( $_SESSION['map'])
'grid' => $_SESSION['map']
);
//send query
$anwserPlayer=get_IA_Response($botUrl,$postDatas);
//for test ***************************
//echo $anwserPlayer; die;
//vérifier la validité de la réponse
if((isset($_SESSION['map'][5][$anwserPlayer])) && ($_SESSION['map'][5][$anwserPlayer] == "")){
//reponse conforme

View File

@ -1,5 +1,9 @@
<?php
function get_IA_Response($iaUrl,$postParams){
//send params JSON as body
$data_string = json_encode($postParams);
/*
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $iaUrl);
curl_setopt($ch, CURLOPT_POST, true);
@ -7,6 +11,20 @@ function get_IA_Response($iaUrl,$postParams){
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$output = curl_exec($ch);
curl_close($ch);
*/
$ch = curl_init($iaUrl);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
curl_setopt($ch, CURLOPT_POSTFIELDS, $data_string);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
'Content-Type: application/json',
'Content-Length: ' . strlen($data_string))
);
$output= curl_exec($ch);
curl_close($ch);
return htmlentities($output);
}
function get_Post_Params($botsCount){

View File

@ -46,7 +46,10 @@ function connectFour(bot1,bot2,xd_check, newGame){
//send request
var xhr = Ajx();
xhr.onreadystatechange = function(){if(xhr.readyState == 4){
if(xhr.status == 200) {
if(xhr.status == 200) {
//for test
// alert(xhr.responseText);
try{
var reponse = JSON.parse(xhr.responseText);
}catch(e){

View File

@ -21,25 +21,59 @@ switch ($_POST['act']){
'2-0' => '','2-1' => '','2-2' => '');
$end=false;
//send init message to bots
$game_id = "".get_unique_id();
for($player = 0; $player < 2; $player++){
$params[$player]=array(
'game-id' => $game_id,
'action' => 'init',
'game' => 'tictactoe',
'players' => 2,
'board' => '',
'player-index' => $player
);
}
get_IA_Response($bots[$bot1]['url'],$params[0]); //don't care about result
get_IA_Response($bots[$bot2]['url'],$params[1]); //don't care about result
//"game-id":"gameid","action":"init","game":"tictactoe","players":2,"board":null,"you":null,"player-index":0}
//[0000] body> {"name":"moul-tictactoe","play":null,"error":null}
$playerPlayingNow=1;
while($end==false){
switch($playerPlayingNow){
case 1:
$playerURL=$bots[$bot1]['url'];
$playerCHAR='X';
$playerName=$bots[$bot1]['name'];
$playerIndex=0;
break;
case 2:
$playerURL=$bots[$bot2]['url'];
$playerCHAR='O';
$playerName=$bots[$bot2]['name'];
$playerIndex=1;
break;
default:
error(500,"oups");
die;
}
$playerResponse=get_IA_Response($playerCHAR,$playerURL,$map);
$paramsToSend=array(
'game-id' => $game_id,
'action' => 'play-turn',
'game' => 'tictactoe',
'players' => 2,
'board' => $map,
'you' => $playerCHAR,
'player-index' =>$playerIndex
);
$playerResponse=get_IA_Response($playerURL,$paramsToSend)['play'];
//tester la validité de la réponse
if((isset($map[$playerResponse])) && ($map[$playerResponse]=="")){
//reponse conforme

View File

@ -1,42 +1,181 @@
<h1>Fonctionnement des duels de morpion</h1>
<p>
Ne vous fiez pas au GIF animé de la page d'accueil du site.<br/>
Il est là pour illustrer le fonctionnement global de l'arène, mais il est faux : le tictactoe n'a besoin d'envoyer que 10 variables, de longueur maîtrisée, aux bots.<br/>
Le choix a donc été fait de les passer en paramètres GET et non POST comme indiqué sur l'animation.
Il est là pour illustrer le fonctionnement global de l'arène, mais il est faux.
</p>
<h2>Spécifications : variables GET et retour HTML</h2>
<p>
Votre programme n'a pas à gérer une partie entière de morpion, juste un tour.
</p>
<p>
Le programme arbitre (cette arène) fait une requête construite de la manière suivante :
</p>
<pre>
https://votreUrl/?you=O&amp;0-0=&amp;0-1=O&amp;0-2=X&amp;1-0=X&amp;1-1=X&amp;1-2=O&amp;2-0=O&amp;2-1=&amp;2-2=X
<h2>Communications entre l'arène et votre bot</h2>
<p>Pour communiquer, l'arène (le serveur hébergeant botsarena) fait des requetes http(s) de type POST vers les bots. Le message est dans le corps de la requête au format JSON.</p>
<p>Votre bot répond par un array au format JSON.</p>
<h3>Message d'initialisation de votre partie</h3>
l'arène envoie le message suivant:
<pre>{"game-id":"1126","action":"init","game":"tictactoe","players":2,"board":"","player-index":0}</pre>
<p>Si on décompose le tableau pour l'expliquer:</p>
<ul>
<li><em>game-id</em> string identifiant la partie.</li>
<li><em>action</em> string identifiant la phase, <em>init</em> tout de suite, sera <em>play-turn</em> dans le châpitre suivant.</li>
<li><em>game</em> string identifiant le jeu. Ici, ce sera forcément tictactoe. ça peut servir si vous donnez une seule URL pour plusieurs bots.</li>
<li><em>players</em> Int indiquant le nombre de joueurs dans la partie, toujours 2 au morpion.</li>
<li><em>board</em> Vide à cette étape, voir chapitre suivant.</li>
<li><em>player-index</em> int, L'ordre de votre bot dans les tours de jeu. Le premier joueur a la valeur 0, le deuxième 1.</li>
</ul>
<p>Votre bot doit retourner le nom de votre bot format JSON:</p>
<pre>
{"name":"botName"}
</pre>
<ul>
<li><em>name</em> le nom de votre bot (actuellment non pris en compte par cette arène).</li>
</ul>
<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>Message vous demandant de jouer (seulement un tour)</h3>
<p>L'arène vous envoie par exemple le message suivant:</p>
<pre>{"game-id":"1126","action":"play-turn","game":"tictactoe","players":2,"board":{"0-0":"","0-1":"","0-2":"","1-0":"","1-1":"","1-2":"","2-0":"","2-1":"","2-2":""},"you":"X","player-index":0}</pre>
<ul>
<li><em>game-id</em> String identifiant la partie.</li>
<li><em>action</em> String identifiant la phase, <em>play-turn</em> tout de suite, qui vous indique que vous devez jouer.</li>
<li><em>game</em> String identifiant le jeu. Ici, ce sera forcément tictactoe. ça peut servir si vous donnez une seulle URL pour plusieurs bots.</li>
<li><em>players</em> Int indiquant le nombre de joueurs dans la partie, toujours 2 au morpion</li>
<li><em>board</em> La carte je donne le détail ci dessous</li>
<li><em>you</em> String, Votre caractère dans la grille</li>
<li><em>player-index</em> Int, L'ordre de votre bot dans les tours de jeu. Le premier joueur a la valeur 0, le deuxième 1.</li>
</ul>
<h4>la map</h4>
<p>Elle est représentée par un sous-tableau. Les index correspondent aux coordonnées x-y de la case. x et y sont compris entre 0 et 2. Les valeurs peuvent &ecirc;tre:</p>
<ul>
<li>Vide, personne n'a joué à cette case</li>
<li>"X" ou "O", le joueur ayant ce symbole a déjà joué cette case.</li>
</ul>
<h2>Exemple de partie</h2>
<pre class="arrenaMessage">
{"game-id":"1126","action":"init","game":"tictactoe","players":2,"board":"","player-index":0}
</pre>
<p>
Le paramètre GET "you" vous indique quel est votre symbole dans la grille de morpion.<br/>
Classiquement : "X" ou "O"
</p>
<p>
Pour chaque case de la grille de morpion, un paramètre GET correspondant à ses coordonnées ("0-0", "0-1", etc ...) vous est indiqué.<br/>
Il peut être :
</p>
<ul>
<li>Votre symbole (ce qui signifie que vous avez déjà joué là)</li>
<li>Le symbole de votre adversaire. Il a déjà joué dans cette case</li>
<li>Rien, la case est disponible</li>
</ul>
<p>
Votre programme choisit la case sur laquelle il souhaite jouer compte tenu de la grille et retourne ses coordonnées.
</p>
<p>
Le résultat de la requête HTTP(s) ne peut qu'être : "0-0" ou "0-1" ou "0-2" ou "1-0" etc.
</p>
<p>
Un joueur (une IA) qui répond autre chose (au caractère près), ou qui répond avec les coordonnées d'une case déjà jouée, perd la partie.
</p>
<h2>Publier votre programme pour le tester puis le lâcher dans l'arène</h2>
<p>
Le formulaire est sur la <a href="/">page d'accueil du site</a> !
</p>
<pre class="botResponse">
{"name":"moul-tictactoe"}
</pre>
<pre class="arrenaMessage">
{"game-id":"1126","action":"init","game":"tictactoe","players":2,"board":"","player-index":1}
</pre>
<pre class="botResponse">
{"name":"moul-tictactoe"}
</pre>
<pre class="arrenaMessage">
{"game-id":"1126","action":"play-turn","game":"tictactoe","players":2,"board":{"0-0":"","0-1":"","0-2":"","1-0":"","1-1":"","1-2":"","2-0":"","2-1":"","2-2":""},"you":"X","player-index":0}
</pre>
<pre class="botResponse">
{"play":"0-1"}
</pre>
<pre>
Player 1 joue en 0-1 la nouvelle grille est
X
</pre>
<pre class="arrenaMessage">
{"game-id":"1126","action":"play-turn","game":"tictactoe","players":2,"board":{"0-0":"","0-1":"X","0-2":"","1-0":"","1-1":"","1-2":"","2-0":"","2-1":"","2-2":""},"you":"O","player-index":1}
</pre>
<pre class="botResponse">
{"play":"1-1"}
</pre>
<pre>
Player 2 joue en 1-1 la nouvelle grille est
X
O
</pre>
<pre class="arrenaMessage">
{"game-id":"1126","action":"play-turn","game":"tictactoe","players":2,"board":{"0-0":"","0-1":"X","0-2":"","1-0":"","1-1":"O","1-2":"","2-0":"","2-1":"","2-2":""},"you":"X","player-index":0}
</pre>
<pre class="botResponse">
{"play":"0-0"}
</pre>
<pre>
Player 1 joue en 0-0 la nouvelle grille est
X X
O
</pre>
<pre class="arrenaMessage">
{"game-id":"1126","action":"play-turn","game":"tictactoe","players":2,"board":{"0-0":"X","0-1":"X","0-2":"","1-0":"","1-1":"O","1-2":"","2-0":"","2-1":"","2-2":""},"you":"O","player-index":1}
</pre>
<pre class="botResponse">
{"play":"0-2"}
</pre>
<pre>
Player 2 joue en 0-2 la nouvelle grille est
X X O
O
</pre>
<pre class="arrenaMessage">
{"game-id":"1126","action":"play-turn","game":"tictactoe","players":2,"board":{"0-0":"X","0-1":"X","0-2":"O","1-0":"","1-1":"O","1-2":"","2-0":"","2-1":"","2-2":""},"you":"X","player-index":0}
</pre>
<pre class="botResponse">
{"play":"2-0"}
</pre>
<pre>
Player 1 joue en 2-0 la nouvelle grille est
X X O
O
X
</pre>
<pre class="arrenaMessage">
{"game-id":"1126","action":"play-turn","game":"tictactoe","players":2,"board":{"0-0":"X","0-1":"X","0-2":"O","1-0":"","1-1":"O","1-2":"","2-0":"X","2-1":"","2-2":""},"you":"O","player-index":1}
</pre>
<pre class="botResponse">
{"play":"1-0"}
</pre>
<pre>
Player 2 joue en 1-0 la nouvelle grille est
X X O
O O
X
</pre>
<pre class="arrenaMessage">
{"game-id":"1126","action":"play-turn","game":"tictactoe","players":2,"board":{"0-0":"X","0-1":"X","0-2":"O","1-0":"O","1-1":"O","1-2":"","2-0":"X","2-1":"","2-2":""},"you":"X","player-index":0}
</pre>
<pre class="botResponse">
{"play":"1-2"}
</pre>
<pre>
Player 1 joue en 1-2 la nouvelle grille est
X X O
O O X
X
</pre>
<pre class="arrenaMessage">
{"game-id":"1126","action":"play-turn","game":"tictactoe","players":2,"board":{"0-0":"X","0-1":"X","0-2":"O","1-0":"O","1-1":"O","1-2":"X","2-0":"X","2-1":"","2-2":""},"you":"O","player-index":1}
</pre>
<pre class="botResponse">
{"play":"2-1"}
</pre>
<pre>
Player 2 joue en 2-1 la nouvelle grille est
X X O
O O X
X O
</pre>
<pre class="arrenaMessage">
{"game-id":"1126","action":"play-turn","game":"tictactoe","players":2,"board":{"0-0":"X","0-1":"X","0-2":"O","1-0":"O","1-1":"O","1-2":"X","2-0":"X","2-1":"O","2-2":""},"you":"X","player-index":0}
</pre>
<pre class="botResponse">
{"play":"2-2"}
</pre>
<pre>
Player 1 joue en 2-2 la nouvelle grille est
X X O
O O X
X O X
Match nul
</pre>
<h2>Outils pour développer et tester votre bot</h2>
<h3>Script à télécharger</h3>
<p>En cours de développement</p>
<h3>Bolosseum</h3>
<p>Vous trouverez des outils en ligne de commande pour tester et déboguer votre bot sur <a href="https://github.com/moul/bolosseum">le projet github Bolosseum de @moul</a></p>
<h2>Faire entrer votre bot dans cette arène</h2>

View File

@ -16,6 +16,9 @@ function get_Post_Params($botsCount){
return array('bot1' => $_POST['bot1'],'bot2' => $_POST['bot2']);
}
/*
function get_Bots_Array(){
//Recupérer la liste des Bots
@ -31,20 +34,20 @@ function get_Bots_Array(){
return $bots;
}
*/
function get_IA_Response($youChar,$iaBaseUrl,$grille){
/*transforme la grille en parametres http GET
* et fait la requete vers $iaBaseUrl
* Retourne la réponse de l'IA
*/
$paramsGrille="";
foreach($grille as $key => $case){
$paramsGrille.="&".$key."=".$case;
}
$url=$iaBaseUrl."?you=".$youChar.$paramsGrille;
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$output = curl_exec($ch);
curl_close($ch);
return htmlentities($output);
function get_IA_Response($iaUrl,$postParams){
//send params JSON as body
$data_string = json_encode($postParams);
$ch = curl_init($iaUrl);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
curl_setopt($ch, CURLOPT_POSTFIELDS, $data_string);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
'Content-Type: application/json',
'Content-Length: ' . strlen($data_string))
);
$output= curl_exec($ch);
curl_close($ch);
return json_decode($output,TRUE);
}

View File

@ -2,4 +2,8 @@ td {min-width: 10px; height: 10px;}
.cellj0{border-bottom: 1px dotted black;}
.cellj2{border-top:1px dotted black;}
.celli0{border-right:1px dotted black;}
.celli2{border-left:1px dotted black;}
.celli2{border-left:1px dotted black;}
.arrenaMessage{color: green;}
.arrenaMessage::before{content: "arena message:"; color: grey; font-size: 70%; test-transform:italic;}
.botResponse{color: red;}
.botResponse::before{content: "bot response:";color: grey; font-size: 70%; test-transform:italic;}

View File