<!DOCTYPE html>
<html>
<body>
<canvas id="myCanvas" width="700" height="600" style="border:1px solid #c3c3c3;">
Your browser does not support the HTML5 canvas tag.
</canvas>
<script>
// AI Array, put in at least two constructor functions as described in the
// data file, and it will do round robin on them.
// Also, you can adjust some variables for testing purposes only.
var interval = 1; // in milliseconds per turn
var matches = 10; // number of matches for each pairing
var aiArray = [];
myAI = function(player1) {
this.player1 = player1;
this.yourMove = function(b) {
var me;
if (this.player1) {
me = b.player1;
} else {
me = b.player2;
}
var d = 0;
var tokenP;
while (tokenP == undefined) {
var arr = this.findTokensAtDistance(me.pos, d)
tokenP = this.findBestToken(arr, b.tokens, me);
d += 1;
}
return this.startAndGoalToCommand(me.pos, tokenP);
}
this.findTokensAtDistance = function(p, d) {
if (d == 0) {
return [
[p[0], p[1]]
];
}
var myArr = [];
for (i = 0; i <= d; i++) {
myArr[i] = [i, d - i];
}
var mySecArr = [];
for (i = 0; i <= d; i++) {
mySecArr[i] = [myArr[i][0] + p[0], myArr[i][1] + p[1]];
}
mySecArr[mySecArr.length] = [myArr[0][0] + p[0], -myArr[0][1] + p[1]];
for (i = 1; i < myArr.length - 1; i++) {
mySecArr[mySecArr.length] = [-myArr[i][0] + p[0], myArr[i][1] + p[1]]
mySecArr[mySecArr.length] = [myArr[i][0] + p[0], -myArr[i][1] + p[1]]
mySecArr[mySecArr.length] = [-myArr[i][0] + p[0], -myArr[i][1] + p[1]]
}
mySecArr[mySecArr.length] = [-myArr[myArr.length - 1][0] + p[0], myArr[myArr.length - 1][1] + p[1]];
return mySecArr;
}
this.findBestToken = function(arr, t, player) {
var tokenPos;
for (i = 0; i < arr.length; i++) {
if (arr[i][0] >= 0 && arr[i][0] < t.length && arr[i][1] >= 0 && arr[i][1] < t.length) {
if (t[arr[i][0]][arr[i][1]] != false && ((tokenPos == undefined) || (this.tokenScore(player, t[arr[i][0]][arr[i][1]]) > this.tokenScore(player, t[tokenPos[0]][tokenPos[1]])))) {
tokenPos = [arr[i][0],
[arr[i][1]]
];
}
}
}
return tokenPos;
}
this.tokenScore = function(player, token) {
if (player.lastColor == token.color) {
return player.colorBonus + 1 + token.points;
} else {
return token.points;
}
}
this.startAndGoalToCommand = function(start, goal) {
var diff = [goal[0] - start[0], goal[1] - start[1]];
if (diff[0] > 0) {
return "RIGHT";
} else if (diff[1] > 0) {
return "DOWN";
} else if (diff[1] < 0) {
return "UP";
} else if (diff[0] < 0) {
return "LEFT";
} else {
return "EAT";
}
}
}
aiArray[0] = myAI;
// This is a really stupid AI, used to test that the round-robin selected
// it is the worst by far
myStupidAI = function(player1) {
this.player1 = player1;
this.yourMove = function(b) {
var me;
if (this.player1) {
me = b.player1;
} else {
me = b.player2;
}
if (b.tokens[me.pos[0]][me.pos[1]] != false) {
return "EAT";
} else {
var dirs = this.getViableDirections(b, me.pos);
var rand = Math.floor(Math.random() * dirs.length);
return dirs[rand];
}
}
this.getViableDirections = function(b, p) {
var dirs = [];
if (p[0] > 0) {
dirs.push("LEFT");
}
if (p[1] > 0) {
dirs.push("UP");
}
if (p[1] < b.tokens.length - 1) {
dirs.push("DOWN");
}
if (p[0] < b.tokens.length - 1) {
dirs.push("RIGHT");
}
return dirs;
}
}
aiArray[1] = myStupidAI;
mirrorBot = function(player1){
this.hasStarted=0;
this.player1 = player1;
this.yourMove = function(b){
this.op = this.player1 ? b.player2 : b.player1;
var out = "EAT";
if(this.hasStarted){
if(this.opl.pos[0] < this.op.pos[0]) out = "LEFT";
if(this.opl.pos[0] > this.op.pos[0]) out = "RIGHT";
if(this.opl.pos[1] < this.op.pos[1]) out = "UP";
if(this.opl.pos[1] > this.op.pos[1]) out = "DOWN";
}
this.opl = this.op;
this.hasStarted = 1;
return out;
}
}
aiArray[2] = mirrorBot;
hungryBot = function(first) {
// Set up "self"
var self = this;
// Determine player order
this.player = -(first - 2);
this.enemy = first + 1;
// Action associative array
this.actions = ['EAT', 'LEFT', 'RIGHT', 'UP', 'DOWN'];
//Logic handler
this.yourMove = function(board) {
// Determine player object
var player = board['player' + self.player];
var enemy = board['player' + self.enemy];
// Point value action grid
var actions = [0, 0, 0, 0, 0]; // Associative with "this.actions"
// Board dimensions
var size = board.tokens.length;
var maxDist = size * 2;
// Colors remaining
var colors = {
'#FF0000': 0,
'#00FF00': 0,
'#0000FF': 0
};
// Averaged value weight
var average = [0, 0];
// Total points
var points = 0;
// Token holder
var tokens = [];
// Token parser
for (var i = 0, x = 0, y = 0; i < size * size; i += 1, x = i % size, y = i / size | 0) {
if (!board.tokens[x][y]) {
continue;
} else {
var token = {};
token.points = board.tokens[x][y].points;
token.color = board.tokens[x][y].color;
token.x = x - player.pos[0];
token.y = y - player.pos[1];
token.distX = Math.abs(token.x);
token.distY = Math.abs(token.y);
token.dist = token.distX + token.distY;
token.distE = Math.abs(x - enemy.pos[0]) + Math.abs(y - enemy.pos[1]);
token.value = -token.points - (player.colorBonus + 1) * (token.color == player.lastColor) * ((token.dist == 0) + 1) * 1.618 - (enemy.colorBonus + 1) * (token.color == enemy.lastColor);
tokens.push(token);
colors[token.color] += 1;
points += token.points;
average[0] += x * token.points;
average[1] += y * token.points;
}
}
// Determine actual average
average[0] = average[0] / points | 0;
average[1] = average[1] / points | 0;
// Pick best token
var best = 0;
// Calculate point values of tokens
for (i = 0; i < tokens.length; i++) {
var token = tokens[i];
// Add remaining numbers of tokens of color as factor
token.value -= (colors[token.color] / tokens.length) * 1.618;
// Subtract distance as a factor
token.value += token.dist;
// Add distance to average to value
token.value += (Math.abs(average[0] - (token.x + player.pos[0])) + Math.abs(average[1] - (token.y + player.pos[1]))) / Math.sqrt(2);
// Consider them higher value if we are closer, and lower if they are
token.value += ((token.dist - token.distE) / (token.dist + token.distE + 0.001)) * token.dist;
// Don't go for it if enemy is already there
token.value += (token.distE == 0 && token.dist > 0) * 100;
if (tokens[best].value > tokens[i].value || (tokens[best].value === tokens[i].value && Math.round(Math.random()))) {
best = i;
}
}
// Set token to best token
var token = tokens[best];
// What to respond with
var response = 'PASS';
// Find best action to get token
if (token.dist == 0) {
response = 'EAT'; // We're on the token
} else if (token.distX >= token.distY) { // Token is more horizontal
if (token.x < 0) { // Token is left
response = 'LEFT';
} else if (token.x > 0) { // Token is right
response = 'RIGHT';
}
} else if (token.distX < token.distY) { // Token is more vertical
if (token.y < 0) { // Token is above
response = 'UP';
} else if (token.y > 0) { // Token is below
response = 'DOWN';
}
}
// Return response
return response;
}
};
aiArray[3]=hungryBot;
for (i = 0; i < aiArray.length; i += 1) {
Object.freeze(aiArray[i]);
}
// This is the actual code to run the program. You shouldn't mess with it,
// or your AI might not work during the actual competition.
// Also note that you can't access this code at all in your AI.
// Feel free to make suggestions and we'll try to incorporate them,
// and let us know if something isn't working correctly.
Object.freeze(aiArray);
setInterval((function() {
function token(color, points) {
this.color = color;
this.points = points;
}
function player(pos, score, colorBonus, lastColor) {
this.pos = pos;
this.score = score;
this.colorBonus = colorBonus;
this.lastColor = lastColor;
}
function board(player1, player2, tokens) {
this.player1 = player1;
this.player2 = player2;
this.tokens = tokens;
}
function copyBoard(b) {
return new board(copyPlayer(b.player1), copyPlayer(b.player2), copyTokens(b.tokens));
}
function copyTokens(t) {
var tokens = [];
for (i = 0; i < t.length; i++) {
tokens[i] = [];
for (j = 0; j < t[i].length; j++) {
tokens[i][j] = t[i][j];
}
}
return tokens;
}
function copyPlayer(p) {
return new player([p.pos[0], p.pos[1]], p.score, p.colorBonus, p.lastColor);
}
function endGame(b) {
if (b.player1.score > b.player2.score) {
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.font = 20 + "px Arial";
ctx.textAlign = "left";
ctx.fillStyle = "#000000";
ctx.fillText("Player 1", 601, 160);
ctx.fillText("wins!", 601, 180);
} else if (b.player1.score < b.player2.score) {
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.font = 20 + "px Arial";
ctx.textAlign = "left";
ctx.fillStyle = "#000000";
ctx.fillText("Player 2", 601, 160);
ctx.fillText("wins!", 601, 180);
} else {
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.font = 20 + "px Arial";
ctx.textAlign = "left";
ctx.fillStyle = "#000000";
ctx.fillText("It's a", 601, 160);
ctx.fillText("draw!", 601, 180);
}
}
function genTokenArray(size) {
var temp = [];
for (i = 0; i < size; i++) {
temp[i] = [];
for (j = 0; j < size; j++) {
temp[i][j] = false;
}
}
return temp;
}
function genRandomTokenArray(size) {
var temp = genTokenArray(size);
for (i = 0; i < size * 2; i++) {
var rand = Math.floor(Math.random() * temp.length);
var points = Math.floor(Math.random() * 3 + 1);
var rand2 = Math.floor(Math.random() * 3);
var color;
var rand3 = Math.floor(Math.random() * temp[rand].length);
if (rand2 == 0) {
color = "#FF0000"
} else if (rand2 == 1) {
color = "#00FF00"
} else {
color = "#0000FF"
}
temp[rand][rand3] = new token(color, points);
}
return temp;
}
function countTokens(tokenArr) {
var x = 0;
for (i = 0; i < tokenArr.length; i++) {
for (j = 0; j < tokenArr[i].length; j++) {
if (tokenArr[i][j] != false) {
x += 1
}
}
}
return x;
}
function actPlayer(b, p, s) {
if (s == "UP" && p.pos[1] > 0) {
p.pos[1] -= 1;
} else if (s == "RIGHT" && p.pos[0] < b.tokens.length - 1) {
p.pos[0] += 1;
} else if (s == "DOWN" && p.pos[1] < b.tokens.length - 1) {
p.pos[1] += 1;
} else if (s == "LEFT" && p.pos[0] > 0) {
p.pos[0] -= 1;
} else if (s == "EAT" && b.tokens[p.pos[0]][p.pos[1]] != false) {
if (p.lastColor == b.tokens[p.pos[0]][p.pos[1]].color) {
p.colorBonus += 1;
p.score += p.colorBonus;
} else {
p.lastColor = b.tokens[p.pos[0]][p.pos[1]].color;
p.colorBonus = 0;
}
p.score += b.tokens[p.pos[0]][p.pos[1]].points;
b.tokens[p.pos[0]][p.pos[1]] = false;
}
}
function drawEmptyRect() {
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.clearRect(0, 0, 700, 600);
}
function drawToken(t, x, y, g) {
drawEntity(x, y, t.points, t.color, "#000000", 300 / g, g);
}
function drawLittleEntity(x, y, label, fillColor, labelColor, size, cX, cY, g) {
var delta = 600 / g;
var x1 = x * delta + cX * delta / 4;
var y1 = y * delta + cY * delta / 4;
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.beginPath();
ctx.arc(x1, y1, size / 2, 0, 2 * Math.PI);
ctx.stroke();
ctx.fillStyle = fillColor;
ctx.fill();
ctx.font = delta / 2 + "px Arial";
ctx.textAlign = "center";
ctx.fillStyle = labelColor;
ctx.fillText(label, x1, y1 + delta * 3 / 16);
}
function drawEntity(x, y, label, fillColor, labelColor, size, g) {
var delta = 600 / g;
var x1 = x * delta + delta / 2;
var y1 = y * delta + delta / 2;
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.beginPath();
ctx.arc(x1, y1, size, 0, 2 * Math.PI);
ctx.stroke();
ctx.fillStyle = fillColor;
ctx.fill();
ctx.font = delta + "px Arial";
ctx.textAlign = "center";
ctx.fillStyle = labelColor;
ctx.fillText(label, x1, y1 + delta * 3 / 8);
}
function drawAllTokens(arrToken) {
for (i = 0; i < arrToken.length; i++) {
for (j = 0; j < arrToken[i].length; j++) {
if (arrToken[i][j] != false) {
drawToken(arrToken[i][j], i, j, arrToken.length);
}
}
}
}
function drawGrid(g) {
var delta = 600 / g;
for (i = 0; i <= g; i++) {
var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
ctx.beginPath();
ctx.moveTo(i * delta, 0);
ctx.lineTo(i * delta, 600);
ctx.stroke();
}
for (j = 0; j <= g; j++) {
var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
ctx.beginPath();
ctx.moveTo(0, j * delta);
ctx.lineTo(600, j * delta);
ctx.stroke();
}
}
function drawPlayer1(b, g) {
drawLittleEntity(b.player1.pos[0], b.player1.pos[1], "F", "#000000", "#FFFFFF", 300 / g, 1, 1, g);
}
function drawPlayer2(b, g) {
drawLittleEntity(b.player2.pos[0], b.player2.pos[1], "S", "#000000", "#FFFFFF", 300 / g, 3, 3, g);
}
function drawScore(b) {
var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
ctx.font = "20px Arial";
ctx.fillStyle = "#000000";
ctx.textAlign = "left";
ctx.fillText("P" + indices[0] + " Score:", 601, 50);
ctx.fillText(b.player1.score, 601, 70);
ctx.fillText("P" + indices[1] + " Score:", 601, 100);
ctx.fillText(b.player2.score, 601, 120);
}
function drawScoreNum(s, i) {
var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
ctx.font = "20px Arial";
ctx.fillStyle = "#000000";
ctx.textAlign = "left";
ctx.fillText("P" + i + " Score:", 601, i * 50 + 50);
ctx.fillText(s, 601, i * 50 + 70);
}
function redraw(b, g) {
drawEmptyRect();
drawGrid(g);
drawScore(b);
drawAllTokens(b.tokens);
drawPlayer1(b, g);
drawPlayer2(b, g);
drawScoreNum()
}
function nextPair(pair, total) {
var pairRes = [pair[0], (pair[1] + 1) % total];
if (pairRes[1] == 0) {
pairRes[0] = pairRes[0] + 1;
}
return pairRes;
}
function nextGameUnfair() {
scores[indices[0]] = scores[indices[0]] + currBoard.player1.score;
scores[indices[1]] = scores[indices[1]] + currBoard.player2.score;
if (matchCount >= matches) {
matchCount = 0;
indices = nextPair(indices, aiArray.length);
if (indices[0] == indices[1]) {
indices = nextPair(indices, aiArray.length);
}
if (indices[0] >= aiArray.length) {
return false;
}
player1Turn = true;
timeWithoutEating = 0;
firstAI = new aiArray[indices[0]](true);
secondAI = new aiArray[indices[1]](false);
currBoard = genBoard();
} else {
matchCount++;
player1Turn = true;
timeWithoutEating = 0;
firstAI = new aiArray[indices[0]](true);
secondAI = new aiArray[indices[1]](false);
currBoard = genBoard();
}
return true;
}
function nextGameFair() {
scores[indices[0]] = scores[indices[0]] + currBoard.player1.score;
scores[indices[1]] = scores[indices[1]] + currBoard.player2.score;
indices = nextPair(indices, aiArray.length);
if (indices[0] == indices[1]) {
indices = nextPair(indices, aiArray.length);
}
if (indices[0] >= aiArray.length) {
indices = [0, 1];
matchCount++;
if (matchCount >= matches) {
return false;
}
player1Turn = true;
timeWithoutEating = 0;
currBoard = genBoard();
} else {
player1Turn = true;
timeWithoutEating = 0;
currBoard = savedBoard;
}
firstAI = new aiArray[indices[0]](true);
secondAI = new aiArray[indices[1]](false);
savedBoard = copyBoard(currBoard);
return true;
}
function genBoard() {
var rand = Math.floor(Math.random() * 11 + 5);
var randX = Math.floor(Math.random() * rand);
var randY = Math.floor(Math.random() * rand);
return new board(new player([randX, randY], 0, 0, "#000000"),
new player([randX, randY], 0, 0, "#000000"),
genRandomTokenArray(rand));
}
var running = true;
var indices = [0, 1];
var scores = []
for (i = 0; i < aiArray.length; i++) {
scores.push(0);
}
var matchCount = 0;
var firstAI = new aiArray[0](true);
var secondAI = new aiArray[1](false);
var player1Turn = true;
var currBoard = genBoard();
var savedBoard = copyBoard(currBoard);
var timeWithoutEating = 0;
return function() {
if (running) {
var numTokens = countTokens(currBoard.tokens);
if (numTokens <= 0) {} else if (player1Turn) {
actPlayer(currBoard, currBoard.player1, firstAI.yourMove(copyBoard(currBoard)));
} else {
actPlayer(currBoard, currBoard.player2, secondAI.yourMove(copyBoard(currBoard)));
}
player1Turn = !player1Turn;
redraw(currBoard, currBoard.tokens.length);
if (numTokens > countTokens(currBoard.tokens)) {
timeWithoutEating = 0;
} else {
timeWithoutEating += 1;
}
if (numTokens <= 0 || timeWithoutEating > 4 * currBoard.tokens.length) {
running = nextGameFair();
}
} else {
drawEmptyRect();
for (i = 0; i < scores.length; i++) {
drawScoreNum(scores[i], i);
}
}
}
})(), interval);
</script>
</body>
</html>
player1
booleano é o construtor da sua IA, que terá umayourMove
função que recebe a placa atual como entrada, comob
.Respostas:
HungryBot
Usa um sistema de pontos para adicionar peso ao valor de buscar cada token. Usa uma variedade de fatores diferentes em sua consideração e os reavalia a cada turno para garantir que segue a melhor estratégia.
fonte
self
:)self
? Não éthis
suficiente?PATH bot
Sigla significa Pathfinding And Tree Heuristics Bot
EDIT: A partir de agora, aqui estão as classificações para as IAs, com os pontos
Link para o controlador completo no github
Descrição: como o NaiveAI, este bot encontra o token mais próximo que lhe dará mais pontos. No entanto, também simula os resultados de cada um de seus movimentos, até 6 vezes.
Justificativa: Como o NaiveAI já é muito bom, eu acho que o tornaria melhor. Sem olhar para o código primeiro (grande erro).
Batimentos: Todos, exceto o HungryBot,
perde para: Nenhum, exceto o HungryBot
Problemas:
Pode se teletransportarAinda não sei por que estava se teletransportando, mas consertei. Vídeo antigo aqui: https://youtu.be/BIhSKycF9iA
Código completo:
fonte
NaiveAI
Começando com
r=0
, observe todos os tokens com distância do táxir
da sua posição. Se houver, escolha uma que daria a maior pontuação se você a obtivesse agora. Caso contrário, aumenter
em 1 e tente novamente.fonte
KindaRandomAI
A cada turno, faça o seguinte: Se houver um token em sua posição, "COMA". Caso contrário, mova-se em uma direção viável aleatória, ou seja, se você estiver na borda esquerda, não diga "ESQUERDA".
fonte
LazyBot
Só come alguma coisa se ele aparecer nela. Isso não tem chance de ganhar, mas o desafio não tinha um desses, então por que não.
fonte
MirrorBot
Deve ser chamado de "forragem de canhão"
Descrição: Move exatamente o oposto do que o outro jogador fez
Justificativa: eu queria ter uma programação confortável em JS novamente. Isso não deve ganhar
Vai bater: Ninguém
Perderá para: Todos
fonte
var out = "EAT";
vez deout = "EAT";
, pois o último define uma variável global. Se clicar um pouco, a terceira e a quarta linhas não fazem nada e também podem ser removidas eop
podem ser uma variável local, como emout
vez de uma propriedade.OneTarget
Encontra o token que fornecerá mais pontos no menor tempo e seguirá para esse. Classifica as fichas da mesma cor um pouco mais por causa do efeito cumulativo.
fonte
Quantidade
Tudo o que o QuantityPlayer se importa é com a quantidade de pontos que ele come, não com o valor ou a cor dos pontos. Ele sabe que, embora todos os pontos sejam diferentes, eles devem ser tratados da mesma forma.
fonte