Skip to content

Instantly share code, notes, and snippets.

@mpolyak
Created September 18, 2014 12:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mpolyak/5bdf56379177755f6868 to your computer and use it in GitHub Desktop.
Save mpolyak/5bdf56379177755f6868 to your computer and use it in GitHub Desktop.
1st place in the CodeCombat Criss-Cross tournament on the Ogres side http://blog.codecombat.com/a-good-new-fashioned-programming-throwdown
/*
Copyright (c) 2014 Michael Polyak. All Rights Reserved.
mpolyak@gmail.com
CodeCombat Criss-Cross, HighSea "Ogres"
*/
var GOLD = 128;
var SIZE = 6;
var VECTOR =
{
humans:
{
x: 1, y: 0
},
ogres:
{
x: 0, y: 1
}
};
var DETAILED = false;
var DEBUG = false;
function tileInList(tile, list)
{
for (var i = 0; i < list.length; i ++)
{
if (list[i].id === tile.id)
return true;
}
return false;
}
function traceRoutes(team, origin, vector, free, grid, ranks)
{
var higher = function (a, b)
{
if (a.score == b.score)
{
if (a.team === b.team)
return (a.rank < b.rank) - (a.rank > b.rank);
return (a.team < b.team) - (a.team > b.team);
}
return (a.score < b.score) - (a.score > b.score);
};
var lower = function (a, b)
{
if (a.score === b.score)
return (a.rank > b.rank) - (a.rank < b.rank);
return (a.score > b.score) - (a.score < b.score);
};
var tiles = [{tile: origin}]; var visited = {};
visited[origin.id] = origin;
var i;
var tile;
while (tiles.length)
{
tile = tiles.shift().tile;
if ((vector.x && tile.x === SIZE) || (vector.y && tile.y === SIZE))
{
// Slower but more accurate routes generation.
if (DETAILED)
{
var list = [];
for (i = 0; i < tiles.length; i ++)
{
tile = tiles[i];
if (tile.team && ((vector.x && tile.tile.y !== SIZE) || (vector.y && tile.tile.x !== SIZE)))
list.push(tile);
}
tiles = list.sort(lower);
continue;
}
else
break;
}
var next;
for (i = 0; i < tile.neighbors.length; i ++)
{
next = tile.neighbors[i];
if ((next.owner && next.owner !== team) || visited[next.id])
continue;
visited[next.id] = tile;
tiles.push({tile: next, team: next.owner === team ? 1 : 0, rank: ranks[next.x][next.y],
score: (vector.x ? next.x : 0) + (vector.y ? next.y : 0)});
}
tiles = tiles.sort(higher);
}
var routes = [];
for (i = 0; i <= SIZE; i ++)
{
var target = grid[vector.x ? SIZE : i][vector.y ? SIZE : i]; tile = target;
if (!visited[tile.id])
continue;
var owned = []; var need = []; var used = null; var distance = 0; tiles = [];
while ((vector.x && tile.x) || (vector.y && tile.y))
{
tiles.push(tile);
if (tile.id === free.id) {
need.push(tile); used = tile;
}
else
{
if (tile.owner === team) {
owned.push(tile);
}
else {
need.push(tile); distance ++;
}
}
tile = visited[tile.id];
}
if (!tileInList(tile, tiles))
{
tiles.push(tile);
if (tile.id === free.id) {
need.push(tile); used = tile;
}
else
{
if (tile.owner === team) {
owned.push(tile);
}
else {
need.push(tile); distance ++;
}
}
}
routes.push({origin: tile, target: target, tiles: tiles, owned: owned, need: need, free: used, distance: distance});
}
return routes;
}
function rankGrid(team, free, grid)
{
var vector = VECTOR[team];
var table = [];
for (var x = 0; x <= SIZE; x ++)
{
var column = [];
for (var y = 0; y <= SIZE; y ++)
{
var tile = grid[x][y];
if (tile.owner && tile.owner !== team) {
column.push(0);
}
else
{
var rank = tile.owner === team || tile.id === free.id ? 2 : 0;
for (var i = 0; i < tile.neighbors.length; i ++)
{
var next = tile.neighbors[i];
if (next.id === free.id) {
rank += 1;
}
else if (next.owner)
{
if (next.owner === team) {
rank += 1;
}
else if ((vector.x && tile.x === next.x) || (vector.y && tile.y === next.y))
rank += 1;
}
}
column.push(rank);
}
}
table.push(column);
}
return table;
}
function teamRoutes(team, free, grid)
{
var vector = VECTOR[team];
var i;
var ranks = [];
for (i = 0; i < free.length; i ++)
ranks.push(rankGrid(team, free[i], grid));
var j;
var k;
var list;
var routes = []; var distance = -1;
for (i = 0; i <= SIZE; i ++)
{
var origin = grid[vector.y ? i : 0][vector.x ? i : 0];
if (origin.owner && origin.owner !== team)
continue;
for (j = 0; j < free.length; j ++)
{
list = traceRoutes(team, origin, vector, free[j], grid, ranks[j]);
for (k = 0; k < list.length; k ++)
{
if (distance !== -1)
{
if (list[k].distance <= distance) {
distance = list[k].distance;
}
else
continue;
}
else
distance = list[k].distance;
routes.push(list[k]);
}
}
}
list = [];
for (i = 0; i < routes.length; i ++)
{
if (routes[i].distance > distance)
continue;
if (!routes[i].free)
{
for (j = 0; j < list.length; j ++)
{
if (list[j].origin.id === routes[i].origin.id && list[j].target.id === routes[i].target.id && list[j].owned.length === routes[i].owned.length)
{
for (k = 0; k < list[j].owned.length; k ++)
{
if (!tileInList(list[j].owned[k], routes[i].owned))
break;
}
if (k === list[j].owned.length)
break;
}
}
if (j !== list.length)
continue;
}
list.push(routes[i]);
}
return list.sort(function (a, b)
{
if (a.distance !== b.distance)
return (a.distance > b.distance) - (a.distance < b.distance);
if (a.owned.length !== b.owned.length)
return (a.owned.length < b.owned.length) - (a.owned.length > b.owned.length);
if (!a.free || !b.free)
return a.free ? -1 : (b.free ? 1 : 0);
var i;
var tile;
var foesA = 0;
var foesB = 0;
for (i = 0; i < a.free.neighbors.length; i ++)
{
tile = a.free.neighbors[i];
if (tile.owner && tile.owner !== team && ((vector.x && a.free.x === tile.x) || (vector.y && a.free.y === tile.y)))
foesA ++;
}
for (i = 0; i < b.free.neighbors.length; i ++)
{
tile = b.free.neighbors[i];
if (tile.owner && tile.owner !== team && ((vector.x && b.free.x === tile.x) || (vector.y && b.free.y === tile.y)))
foesB ++;
}
if (foesA === foesB)
{
var centerA = Math.pow(a.free.x - (SIZE / 2), 2) + Math.pow(a.free.y - (SIZE / 2), 2);
var centerB = Math.pow(b.free.x - (SIZE / 2), 2) + Math.pow(b.free.y - (SIZE / 2), 2);
if (centerA === centerB)
{
centerA = Math.abs((vector.x ? a.origin.y : a.origin.x) - (SIZE / 2));
centerB = Math.abs((vector.x ? b.origin.y : b.origin.x) - (SIZE / 2));
if (centerA === centerB)
{
centerA = Math.abs((vector.x ? a.target.y : a.target.x) - (SIZE / 2));
centerB = Math.abs((vector.x ? b.target.y : b.target.x) - (SIZE / 2));
}
}
return (centerA > centerB) - (centerA < centerB);
}
return (foesA < foesB) - (foesA > foesB);
});
}
function adjacentFoes(team, tile)
{
var vector = VECTOR[team];
var foes = 0;
for (var i = 0; i < tile.neighbors.length; i ++)
{
var next = tile.neighbors[i];
if (next.owner && next.owner !== team && ((vector.x && next.x === tile.x) || (vector.y && next.y === tile.y)))
foes ++;
}
return foes;
}
function firstRoute(team, free, grid)
{
var vector = VECTOR[team];
var tiles = [];
var i;
for (i = 0; i < free.length; i ++)
{
tiles.push({tile: free[i], foes: adjacentFoes(team, free[i]),
center: Math.pow(free[i].x - (SIZE / 2), 2) + Math.pow(free[i].y - (SIZE / 2), 2)});
}
var list = tiles.sort(function (a, b)
{
if (a.foes === b.foes)
return (a.center > b.center) - (a.center < b.center);
return (a.foes < b.foes) - (a.foes > b.foes);
});
tiles = [];
for (i = 0; i <= SIZE; i ++)
tiles.push(grid[vector.x ? i : list[0].tile.x][vector.y ? i : list[0].tile.y]);
return {origin: tiles[0], target: tiles[SIZE], tiles: tiles, owned: [], need: [list[0].tile], free: list[0].tile, distance: SIZE};
}
function calculateBid(team, ourGold, foeGold, ourRoutes, forRoutes, ourShortest, foeShortest, gold, limit)
{
if (!ourGold)
return {debug: DEBUG ? " NO GOLD" : "", highlight: [], gold: 0, tile: null};
var debug = ""; var highlight = [];
var tile = null;
var i;
var ourRoute;
var foeRoute;
// We have routes.
if (ourRoutes.length)
{
// We have a tile to bid on.
if (ourRoutes[0].free)
{
// We are not on the last tile.
if (ourRoutes[0].distance)
{
if (foeGold && foeRoutes.length)
{
if (forRoutes[0].distance)
{
var foes = adjacentFoes(team, ourRoutes[0].free);
// Cross-reference opponent's routes with our and pick a common tile to bid on.
for (i = 0; i < foeRoutes.length; i ++)
{
foeRoute = foeRoutes[i];
if (!foeRoute.free)
continue;
for (var j = 0; j < ourRoutes.length; j ++)
{
ourRoute = ourRoutes[j];
if (!ourRoute.free)
continue;
if ((foeRoute.distance === 1 && (ourRoute.free.id === foeRoute.free.id || tileInList(foeRoute.free, ourRoute.need))) ||
(ourRoute.free.id === foeRoute.free.id && adjacentFoes(team, ourRoute.free) >= foes))
{
if (DEBUG) {
debug += " SHARED"; highlight = ourRoute.tiles;
}
tile = foeRoute.free;
if (foeRoute.distance === 1)
{
if (DEBUG)
debug += " STEAL";
// Try to steal opponent's tile.
if (foeRoute.free.id === ourRoute.free.id) {
gold = Math.min(Math.floor(ourGold / Math.max(ourRoute.distance, 2)), foeGold + 1);
}
else
gold = Math.min(Math.floor(ourGold / (ourRoute.distance + 1)), foeGold + 1);
}
else
{
if (DEBUG)
debug += " SAME";
// We're both going after the same tile.
if (foes) {
gold = Math.min(limit, Math.min(Math.floor(ourGold / (ourRoutes[0].distance + 1)), foeGold + 1));
}
else
gold = Math.min(Math.min(gold, Math.floor(ourGold / (ourRoutes[0].distance + 1))), foeGold + 1);
}
break;
}
}
if (tile)
break;
}
if (!tile)
{
if (DEBUG) {
debug += " OPTIMAL"; highlight = ourRoutes[0].tiles;
}
if (ourShortest > 1 && foeRoutes[0].distance === 1 && foeRoutes[0].free)
{
if (DEBUG)
debug += " STEAL";
// Try to steal opponent's tile.
gold = Math.min(limit, Math.min(Math.floor(ourGold / (ourShortest + 1)), foeGold + 1)); tile = foeRoutes[0].free;
}
else
{
if (ourShortest > 1 && ourGold >= foeGold && foeRoutes[0].distance === 1 && !foeRoutes[0].free)
{
if (DEBUG)
debug += " SKIP";
}
else
{
tile = ourRoutes[0].free;
// Bid on our most optimal tile.
if (foes) {
gold = Math.min(limit, Math.min(Math.floor(ourGold / (ourRoutes[0].distance + 1)), foeGold + 1));
}
else
gold = Math.min(Math.min(gold, Math.floor(ourGold / (ourRoutes[0].distance + 1))), foeGold + 1);
}
}
}
}
else
{
if (DEBUG)
debug += " BLOCK"; highlight = foeRoutes[0].tiles;
// Try to block opponent's lat tile bid.
gold = Math.min(ourGold, foeGold + 1); tile = foeRoutes[0].free;
}
}
else
{
// Our opponent has no tile to bid on.
if (DEBUG) {
debug += " OPTIMAL"; highlight = ourRoutes[0].tiles;
}
// Bid on our most optimal tile.
gold = Math.min(gold, Math.floor(ourGold / (ourRoutes[0].distance + 1))); tile = ourRoutes[0].free;
}
}
else
{
if (DEBUG)
debug += " FINAL"; highlightRoutes = ourRoutes[0].tiles;
// Bid everything on our last tile.
gold = ourGold; tile = ourRoutes[0].free;
}
}
else if (foeGold && foeRoutes.length && foeRoutes[0].free)
{
// We have no tile to bid on, bid on opponent's tile.
foeRoute = foeRoutes[0]; tile = foeRoute.free;
if (DEBUG) {
debug += " NO ROUTE"; highlight = foeRoute.tiles;
}
if (foeRoute.distance)
{
var distance;
if (foeRoute.distance === 1)
{
// Our opponent is two tiles away from winning, attempt to steal this tile.
if (DEBUG)
debug += " STEAL";
if (ourRoutes[0].distance === 1)
{
// We are two tiles away from winning as well.
gold = Math.min(Math.floor(ourGold / 2), Math.floor(foeGold / 2) + 1);
}
else
{
distance = ourRoutes[0].distance + 1;
// See if opponent's tile is of use in any of our routes.
for (i = 0; i < ourRoutes.length; i ++)
{
ourRoute = ourRoutes[i];
if (tileInList(tile, ourRoute.need))
{
if (DEBUG)
debug += " SHARED";
distance = ourRoute.distance;
break;
}
}
gold = Math.min(Math.floor(ourGold / distance), foeGold + 1);
}
}
else
{
distance = ourRoutes[0].distance + foeRoute.distance + 1;
// See if opponent's tile is of use in any of our routes.
for (i = 0; i < ourRoutes.length; i ++)
{
ourRoute = ourRoutes[i];
if (tileInList(tile, ourRoute.need))
{
if (DEBUG)
debug += " SHARED";
distance = ourRoute.distance;
break;
}
}
gold = Math.min(gold, Math.floor(ourGold / distance));
// We can't afford to bid on opponent's tile.
if (ourGold - gold <= foeGold)
{
if (DEBUG)
debug += " EXPENSIVE";
gold = Math.max(0, (ourGold - foeGold) - 1);
}
}
}
else
{
if (DEBUG)
debug += " BLOCK";
// Bid enough to try to win opponent's last tile.
if (ourGold > foeGold) {
gold = Math.min(ourGold - 1, foeGold + 1);
}
else
gold = ourGold;
}
}
}
else
{
// We are blocked.
if (DEBUG)
debug += " BLOCKED";
if (foeGold && foeRoutes.length)
{
// Bid on any free opponent tile.
for (i = 0; i < foeRoutes.length; i ++)
{
foeRoute = foeRoutes[i];
if (DEBUG)
highlight = foeRoute.tiles;
tile = foeRoute.tile;
// Bid enough to try to win opponent tile.
if (foeRoute.distance) {
gold = Math.min(ourGold, Math.floor(foeGold / (foeRoute.distance + 1)) + 1);
}
else
gold = Math.min(ourGold, foeGold + 1);
break;
}
}
}
return {debug: debug, highlight: highlight, gold: gold, tile: tile};
}
function firstBid(gold, route)
{
var fraction = 0.5 * (1 - ((Math.sqrt(Math.pow(route.free.x - (SIZE / 2), 2) + Math.pow(route.free.y - (SIZE / 2), 2))) / Math.sqrt(SIZE * 3)));
return {debug: DEBUG ? " FIRST" : "", highlight: DEBUG ? route.tiles : [],
gold: Math.floor((gold / (SIZE + 1)) * fraction) + 1, tile: route.free};
}
function maximumBid(team, gold, turns)
{
var bids = [];
var average = 0;
var stddev = 0;
var i;
var bid;
for (i = 0; i < turns.length; i ++)
{
bid = team === "humans" ? turns[i].ogreBid.bid : turns[i].humanBid.bid;
if (!isNaN(bid) && bid) {
bids.push(bid); average += bid;
}
}
if (!bids.length)
return turns.length ? turns[0][team === "humans" ? "humanBid" : "ogreBid"].bid * 2 : gold;
average /= bids.length;
for (i = 0; i < bids.length; i ++) {
bid = bids[i]; stddev += Math.pow(bid - average, 2);
}
stddev = Math.sqrt(stddev / bids.length);
if (stddev < average)
return Math.min(gold, Math.max(Math.floor(stddev), bid) + 1);
return gold;
}
function freeTiles(tiles)
{
var free = [];
for (var i = 0; i < tiles.length; i ++)
{
if (!tiles[i].owner)
free.push(tiles[i]);
}
return free;
}
this.printRoute = function (route, debug)
{
var tiles = "";
for (var i = 0; i < route.tiles.length; i ++)
tiles += "[" + route.tiles[i].id + "]";
var text = route.origin.id + " -> " + route.target.id + ": " + route.distance +
" (" + route.owned.length + ")" + (route.free ? " (" + route.free.id + ")" : "") + (tiles ? " " + tiles : "");
if (debug || debug === undefined)
this.debug(text);
return text;
};
var foeGold = this.turns.length ?
this.turns[this.turns.length - 1][this.team === "humans" ? "ogreGold" : "humanGold"] : GOLD;
var free = freeTiles(this.tileGroups[tileGroupLetter]);
var ourRoutes = this.myTiles.length ?
teamRoutes(this.team, free, this.tileGrid) : [firstRoute(this.team, free, this.tileGrid)];
var they = this.team === "humans" ? "ogres" : "humans";
var foeRoutes = this.opponentTiles.length ?
teamRoutes(they, free, this.tileGrid) : [];
if (this.turns.length === 0)
{
this.ourShortest = SIZE + 1;
this.foeShortest = SIZE + 1;
}
else
{
if (ourRoutes.length && ourRoutes[0].distance < this.ourShortest)
this.ourShortest = ourRoutes[0].distance;
if (foeRoutes.length && foeRoutes[0].distance < this.foeShortest)
this.foeShortest = foeRoutes[0].distance;
}
var foeBid = calculateBid(they, foeGold, this.gold, foeRoutes, ourRoutes,
this.foeShortest, this.ourShortest, foeGold, foeGold);
var maxBid = maximumBid(this.team, this.gold, this.turns, this.debug);
var ourBid;
if (!this.myTiles.length && !this.opponentTiles.length && !this.highBidder) {
ourBid = firstBid(this.gold, ourRoutes[0]);
}
else
{
ourBid = calculateBid(this.team, this.gold, foeGold, ourRoutes, foeRoutes, this.ourShortest, this.foeShortest,
Math.min(maxBid, !this.highBidder && foeBid.tile ? foeBid.gold + 1 : this.gold), foeBid.tile ? foeBid.gold + 1 : this.gold);
}
if (this.turns.length === 1)
{
var our = this.turns[0][this.team == "humans" ? "humanBid" : "ogreBid"].bid;
var foe = this.turns[0][this.team == "humans" ? "ogreBid" : "humanBid"].bid;
if (isNaN(our))
our = 0;
if (isNaN(foe))
foe = 0;
this.highBidder = our !== Math.floor(GOLD / (SIZE + 1)) && our < foe;
}
if (DEBUG)
{
var debug = this.round + "/" + this.turns.length + ": OUR GOLD " + this.gold + " FOE GOLD " + foeGold;
debug += " OUR SHORTEST " + this.ourShortest;
debug += " FOE SHORTEST " + this.foeShortest;
if (ourRoutes.length)
debug += " OUR ROUTE " + this.printRoute(ourRoutes[0], false);
if (foeRoutes.length)
debug += " FOE ROUTE " + this.printRoute(foeRoutes[0], false);
if (foeBid.tile)
debug += " FOE BID " + (foeBid.gold + 1);
debug += " MAX BID " + maxBid + ourBid.debug;
if (ourBid.tile) {
debug += " OUR BID " + ourBid.tile.id + " @ " + ourBid.gold;
}
else
debug += " NO BID";
this.debug(debug);
for (var i = 0; i < ourBid.highlight.length; i ++)
this.highlightTile(ourBid.highlight[i]);
}
else if (!ourBid.gold || !ourBid.tile)
return null;
return {gold: ourBid.tile ? ourBid.gold : 0, desiredTile: ourBid.tile};
@mpolyak
Copy link
Author

mpolyak commented Sep 18, 2014

Congratulations on the win Matt! It was a great competition.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment