CautiousBot, Node.js (ES5)
บอทนี้ดับและพยายามจุดไฟเผาดินแดนบอทอื่น มันอยู่บนกองไฟเป็นเวลา 3 เห็บเพื่อซ่อนมัน! อย่างไรก็ตามเราไม่ควรระแวดระวังเกินไปดังนั้นจึงทำให้แน่ใจว่าอยู่ใกล้พอที่จะดับไฟในที่ดินของตนเอง
หมายเหตุ:
- ใช้ไฟล์สถานะที่
state.json
เก็บไว้ในไดเรกทอรีการทำงานของมันใช้เพื่อเก็บข้อมูลเกี่ยวกับตำแหน่งเริ่มต้นของบ็อตอื่น ๆ และเพื่อกำหนดระยะเวลาที่จะซ่อนไฟที่เริ่มต้น สิ่งนี้จะต้องถูกลบเมื่อรอบจบลง (เช่นเมื่อบอทบางคนชนะ) ไม่เช่นนั้นบอทจะสับสนในรอบต่อไป (แจ้งให้เราทราบหากสิ่งนี้ขัดกับกฎ)
- ต้องใช้
split
โมดูล
#!/usr/bin/env node
// imports
var fs = require("fs");
var splitmod = require("split");
// variables
var startX, startY, currentX, currentY, board = [], state = {};
var DEBUG = false;
// utility functions
function debug(){
if(DEBUG) console.log.apply(console, arguments);
}
// calculates manhattan distance which is also the number of turns it will take to get somewhere
function manhattan(x1, y1, x2, y2){
return Math.abs(x2 - x1) + Math.abs(y2 - y1);
}
// calculates chebyshev distance (mostly used for determining if a bot is within someone's plot)
function chebyshev(x1, y1, x2, y2){
return Math.max(Math.abs(x2 - x1), Math.abs(y2 - y1));
}
// gets the board character at x, y
function get(x, y){
return board[y][x];
}
function readState(){
try {
state = JSON.parse(fs.readFileSync('state.json').toString());
debug("Opened state file");
} catch(e){
// it must be the first turn
createState();
}
}
function writeState(){
fs.writeFileSync('state.json', JSON.stringify(state));
debug("Wrote state file");
}
// finds out where all the other bots are
function getBotPositions(){
var positions = [];
for(var x = 0; x < 32; x++){
for(var y = 0; y < 32; y++){
if(get(x, y) == '@' && x != currentX && y != currentY){
positions.push({x: x, y: y});
}
}
}
return positions;
}
function createState(){
debug("Creating state");
// take a loot at where other bots are to record their land locations
var botLands = getBotPositions();
state['botLands'] = botLands;
state['turn'] = 0; // which turn is it?
state['lastFireTurn'] = -999; // which turn was the last one where this bot set a fire?
}
// finds whether a plot of land (defined by its center) has fire on it
function isLandBurning(x, y){
for(var dx = -4; dx < 5; dx++){
for(var dy = -4; dy < 5; dy++){
if(get(x + dx, y + dy).match(/[1-6]/) != null) return true;
}
}
return false;
}
// finds the fire with the highest number (and therefore the one to put out first)
function findFire(x, y){
var highestNum = 0;
var fire = {x: x, y: y};
for(var dx = -4; dx < 5; dx++){
for(var dy = -4; dy < 5; dy++){
if(get(x + dx, y + dy).match(/[1-6]/) != null){
var num = parseInt(get(x + dx, y + dy));
if(num > highestNum){
highestNum = num;
fire = {x: x + dx, y: y + dy};
}
}
}
}
return fire;
}
// figures out where to go to get somewhere
function getDirection(x1, y1, x2, y2){
var direction = 'S';
var cycx = Math.abs(y2 - y1) / Math.abs(x2 - x1);
if(cycx < 1){
if(x2 > x1) direction = 'R';
if(x2 < x1) direction = 'L';
} else {
if(y2 > y1) direction = 'D';
if(y2 < y1) direction = 'U';
}
debug("Getting direction", x1, y1, x2, y2, "result", direction);
return direction;
}
// read input
var dataCycle = 0;
process.stdin.pipe(splitmod()).on('data', function(line){
switch(dataCycle){
case 0:
startX = parseInt(line);
break;
case 1:
startY = parseInt(line);
break;
case 2:
currentX = parseInt(line);
break;
case 3:
currentY = parseInt(line);
break;
default:
board.push(line);
}
dataCycle++;
}).on('end', function(){
// main bot code
readState();
state['turn']++;
debug("It is turn", state['turn']);
// get bot positions
var botPositions = getBotPositions();
var action = {type:'X', direction:'S'};
var move = 'S';
var isMyLandBurning = isLandBurning(startX, startY);
if(isMyLandBurning){ // hurry over there ASAP!
debug("Bot land is burning!");
var pos = findFire(startX, startY);
debug("Fire found at", pos);
move = getDirection(currentX, currentY, pos.x, pos.y);
// simulate the move and figure out if/where to dump the water
var newX = currentX + (move == 'R') - (move == 'L');
var newY = currentY + (move == 'D') - (move == 'U');
if(chebyshev(newX, newY, pos.x, pos.y) < 5){
// on its own land, start dropping water like a madman
debug("Dropping water");
action.type = 'P';
// if it can put out the target fire, then do that
if(manhattan(newX, newY, pos.x, pos.y) == 1) action.direction = getDirection(newX, newY, pos.x, pos.y);
// it's not in range of the target fire, so use the time to put out other fires
// if it's moving on top of a fire, put that out
else if(get(newX, newY).match(/[1-6]/) != null) action.direction = 'S';
// if there's a fire around it then put that out
else if(get(newX + 1, newY).match(/[1-6]/) != null) action.direction = 'R';
else if(get(newX - 1, newY).match(/[1-6]/) != null) action.direction = 'L';
else if(get(newX, newY + 1).match(/[1-6]/) != null) action.direction = 'D';
else if(get(newX, newY - 1).match(/[1-6]/) != null) action.direction = 'U';
else action.direction = 'S';
}
} else {
// are there any bots that could start a fire when this bot is 6+ tiles away?
var headBack = false;
for(var i = 0; i < botPositions.length; i++){
var otherBot = botPositions[i];
var dist = manhattan(otherBot.x, otherBot.y, startX, startY) - 4;
var myDist = manhattan(currentX, currentY, startX, startY);
if(dist + 6 < myDist){
headBack = true;
break;
}
}
if(headBack){ // they're probably up to no good
debug("Bots are dangerously close, heading back");
move = getDirection(currentX, currentY, startX, startY);
} else if(state['turn'] - state['lastFireTurn'] < 3) { // no bots near own plot, time to consider other options
debug("Hiding fire");
// sneakily hide the fire if one was set :)
} else { // last option is to go find land to burn
debug("Finding land to burn");
var closestX = 999, closestY = 999;
for(var i = 0; i < state.botLands.length; i++){
var otherLand = state.botLands[i];
if(!isLandBurning(otherLand.x, otherLand.y) && chebyshev(currentX, currentY, otherLand.x, otherLand.y) > 4){ // find someone to burn
// use [-3, 3] here because on the first turn, the bots could have moved before this bot had a chance to see them
// meaning that the [-3, 3] region is the only one that is guaranteed to be on their land
for(var dx = -3; dx < 4; dx++){
for(var dy = -3; dy < 4; dy++){
var type = get(otherLand.x + dx, otherLand.y + dy);
var distThere = manhattan(currentX, currentY, otherLand.x + dx, otherLand.y + dy);
var distShortest = manhattan(currentX, currentY, closestX, closestY);
// find normal land, or wet land that will dry by the time the bot gets to it
if((type == '.' || type == '@' || (type == 'W' && distThere > 1)) && distThere < distShortest){
closestX = otherLand.x + dx;
closestY = otherLand.y + dy;
}
}
}
}
}
if(closestX != 999 && closestY != 999){ // land found; go there
debug("Target acquired", closestX, closestY);
debug("Burning land");
move = getDirection(currentX, currentY, closestX, closestY);
if(move == 'S'){ // is it on the land? If so, then burn it
action.type = 'B';
action.direction = 'S';
state['lastFireTurn'] = state['turn']; // record when the fire was set
}
} else { // everyone else's land already has a fire
debug("Default action");
// default to heading back; one can never be too safe!
move = getDirection(currentX, currentY, startX, startY);
}
}
}
// save the state file
writeState();
// output the action
console.log(move + action.type + action.direction);
});