mirror of
https://github.com/kolbytn/mindcraft.git
synced 2025-03-28 14:56:24 +01:00
tasks class
This commit is contained in:
parent
760a178f74
commit
d0140aa542
5 changed files with 204 additions and 273 deletions
67
evaluate.sh
67
evaluate.sh
|
@ -1,67 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Initialize variables
|
||||
args=()
|
||||
num_experiments=0
|
||||
successful=0
|
||||
unsuccessful=0
|
||||
error=0
|
||||
|
||||
# Parse arguments
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--num_experiments)
|
||||
num_experiments="$2"
|
||||
shift 2
|
||||
;;
|
||||
*)
|
||||
args+=("$1")
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Validate num_experiments
|
||||
if ! [[ "$num_experiments" =~ ^[0-9]+$ ]] || [[ "$num_experiments" -eq 0 ]]; then
|
||||
echo "Error: num_experiments must be a positive integer"
|
||||
echo "Usage: $0 --num_experiments <number> [other arguments]"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Run experiments
|
||||
while (( successful + unsuccessful < num_experiments )); do
|
||||
node main.js "${args[@]}"
|
||||
exit_code=$?
|
||||
|
||||
case $exit_code in
|
||||
2) ((successful++));;
|
||||
3) ((unsuccessful++));;
|
||||
4) ((error++));;
|
||||
*) echo "Unknown exit code: $exit_code";;
|
||||
esac
|
||||
|
||||
# Calculate success percentage
|
||||
if [[ $successful -eq 0 && $unsuccessful -eq 0 ]]; then
|
||||
success_percentage=0
|
||||
else
|
||||
success_percentage=$(echo "scale=2; $successful / ($successful + $unsuccessful) * 100" | bc)
|
||||
fi
|
||||
|
||||
echo "Success percentage: $success_percentage%"
|
||||
echo "Total successful: $successful"
|
||||
echo "Total unsuccessful: $unsuccessful"
|
||||
echo "Total errors: $error"
|
||||
echo "Total experiments run: $((successful + unsuccessful))"
|
||||
done
|
||||
|
||||
# Generate output file with a cleaner name format
|
||||
date_time=$(date +'%Y-%m-%d_%H-%M-%S')
|
||||
output_file="${date_time}_results.txt"
|
||||
|
||||
echo "Total experiments: $num_experiments" > "$output_file"
|
||||
echo "Successful experiments: $successful" >> "$output_file"
|
||||
echo "Unsuccessful experiments: $unsuccessful" >> "$output_file"
|
||||
echo "Experiments with errors: $error" >> "$output_file"
|
||||
echo "Success percentage: $success_percentage%" >> "$output_file"
|
||||
|
||||
echo "Results saved in $output_file"
|
3
main.js
3
main.js
|
@ -25,7 +25,6 @@ function parseArguments() {
|
|||
.parse();
|
||||
}
|
||||
|
||||
//todo: modify for multiple agents
|
||||
function getProfiles(args) {
|
||||
return args.profiles || settings.profiles;
|
||||
}
|
||||
|
@ -35,7 +34,7 @@ async function main() {
|
|||
const mindServer = createMindServer();
|
||||
}
|
||||
mainProxy.connect();
|
||||
|
||||
|
||||
const args = parseArguments();
|
||||
const profiles = getProfiles(args);
|
||||
console.log(profiles);
|
||||
|
|
|
@ -13,7 +13,7 @@ import { handleTranslation, handleEnglishTranslation } from '../utils/translator
|
|||
import { addViewer } from './viewer.js';
|
||||
import settings from '../../settings.js';
|
||||
import { serverProxy } from './agent_proxy.js';
|
||||
import { loadTask, initBotTask, TechTreeHarvestValidator } from '../utils/tasks.js';
|
||||
import { Task } from './tasks.js';
|
||||
|
||||
export class Agent {
|
||||
async start(profile_fp, load_mem=false, init_message=null, count_id=0, task_path=null, task_id=null) {
|
||||
|
@ -45,6 +45,9 @@ export class Agent {
|
|||
convoManager.initAgent(this);
|
||||
console.log('Initializing examples...');
|
||||
await this.prompter.initExamples();
|
||||
console.log('Initializing task...');
|
||||
this.task = new Task(this, task_path, task_id);
|
||||
this.blocked_actions = this.task.blocked_actions || [];
|
||||
|
||||
serverProxy.connect(this);
|
||||
|
||||
|
@ -58,24 +61,6 @@ export class Agent {
|
|||
save_data = this.history.load();
|
||||
}
|
||||
|
||||
// Load task if provided
|
||||
if (task_path) {
|
||||
this.task = loadTask(task_path, task_id);
|
||||
this.taskTimeout = this.task.timeout || 300;
|
||||
this.taskStartTime = Date.now();
|
||||
this.validator = new TechTreeHarvestValidator(this.task, this.bot);
|
||||
this.blocked_actions = this.task.blocked_actions || [];
|
||||
if (this.task.goal)
|
||||
this.blocked_actions.push('!endGoal');
|
||||
if (this.task.conversation)
|
||||
this.blocked_actions.push('!endConversation');
|
||||
} else {
|
||||
this.task = null;
|
||||
this.taskTimeout = null;
|
||||
this.validator = null;
|
||||
this.blocked_actions = [];
|
||||
}
|
||||
|
||||
this.bot.on('login', () => {
|
||||
console.log(this.name, 'logged in!');
|
||||
|
||||
|
@ -105,8 +90,7 @@ export class Agent {
|
|||
this._setupEventHandlers(save_data, init_message);
|
||||
this.startEvents();
|
||||
|
||||
if (this.task)
|
||||
initBotTask(this);
|
||||
this.task.initBotTask();
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error in spawn event:', error);
|
||||
|
@ -376,6 +360,7 @@ export class Agent {
|
|||
}
|
||||
|
||||
startEvents() {
|
||||
// Custom events
|
||||
this.bot.on('time', () => {
|
||||
if (this.bot.time.timeOfDay == 0)
|
||||
this.bot.emit('sunrise');
|
||||
|
@ -454,46 +439,17 @@ export class Agent {
|
|||
this.bot.emit('idle');
|
||||
|
||||
// Check for task completion
|
||||
if (this.task) {
|
||||
if (this.task.data) {
|
||||
setInterval(() => {
|
||||
if (this.validator && this.validator.validate())
|
||||
this.killBots();
|
||||
// TODO check for other terminal conditions
|
||||
// if (this.task.goal && !this.self_prompter.on)
|
||||
// this.cleanKill('Agent ended goal', 3);
|
||||
// if (this.task.conversation && !inConversation())
|
||||
// this.cleanKill('Agent ended conversation', 3);
|
||||
if (this.taskTimeout) {
|
||||
const elapsedTime = (Date.now() - this.taskStartTime) / 1000;
|
||||
if (elapsedTime >= this.taskTimeout) {
|
||||
console.log('Task timeout reached. Task unsuccessful.');
|
||||
this.cleanKill('Task unsuccessful: Timeout reached', 3);
|
||||
}
|
||||
let res = this.task.isDone();
|
||||
if (res) {
|
||||
// TODO kill other bots
|
||||
this.cleanKill(res.message, res.code);
|
||||
}
|
||||
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
async killBots() {
|
||||
this.bot.chat('Task completed!');
|
||||
this.bot.chat(`/clear @p`);
|
||||
// Kick other bots
|
||||
if (this.task && this.task.agent_number) {
|
||||
const agent_names = this.task.agent_names;
|
||||
console.log('All agent names:', agent_names);
|
||||
console.log('My name:', this.name);
|
||||
const botNames = agent_names.filter(botName => botName !== this.name);
|
||||
console.log('Kicking bots:', botNames);
|
||||
botNames.forEach(botName => {
|
||||
this.bot.chat(`/kick ${botName}`);
|
||||
console.log(`/kick ${botName}`);
|
||||
|
||||
});
|
||||
}
|
||||
this.cleanKill('Task completed, exiting', 2);
|
||||
}
|
||||
|
||||
async update(delta) {
|
||||
await this.bot.modes.update();
|
||||
this.self_prompter.update(delta);
|
||||
|
|
192
src/agent/tasks.js
Normal file
192
src/agent/tasks.js
Normal file
|
@ -0,0 +1,192 @@
|
|||
import { readFileSync } from 'fs';
|
||||
import { executeCommand } from './commands/index.js';
|
||||
import { getPosition } from './library/world.js'
|
||||
import settings from '../../settings.js';
|
||||
|
||||
|
||||
export class TaskValidator {
|
||||
constructor(data, agent) {
|
||||
this.target = data.target;
|
||||
this.number_of_target = data.number_of_target;
|
||||
this.agent = agent;
|
||||
}
|
||||
|
||||
validate() {
|
||||
try{
|
||||
let valid = false;
|
||||
let total_targets = 0;
|
||||
this.agent.bot.inventory.slots.forEach((slot) => {
|
||||
if (slot && slot.name.toLowerCase() === this.target) {
|
||||
total_targets += slot.count;
|
||||
}
|
||||
if (slot && slot.name.toLowerCase() === this.target && slot.count >= this.number_of_target) {
|
||||
valid = true;
|
||||
console.log('Task is complete');
|
||||
}
|
||||
});
|
||||
if (total_targets >= this.number_of_target) {
|
||||
valid = true;
|
||||
console.log('Task is complete');
|
||||
}
|
||||
return valid;
|
||||
} catch (error) {
|
||||
console.error('Error validating task:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class Task {
|
||||
constructor(agent, task_path, task_id) {
|
||||
this.agent = agent;
|
||||
this.data = null;
|
||||
this.taskTimeout = 300;
|
||||
this.taskStartTime = Date.now();
|
||||
this.validator = null;
|
||||
this.blocked_actions = [];
|
||||
if (task_path && task_id) {
|
||||
this.data = this.loadTask(task_path, task_id);
|
||||
this.taskTimeout = this.data.timeout || 300;
|
||||
this.taskStartTime = Date.now();
|
||||
this.validator = new TaskValidator(this.data, this.agent);
|
||||
this.blocked_actions = this.data.blocked_actions || [];
|
||||
if (this.data.goal)
|
||||
this.blocked_actions.push('!endGoal');
|
||||
if (this.data.conversation)
|
||||
this.blocked_actions.push('!endConversation');
|
||||
}
|
||||
}
|
||||
|
||||
loadTask(task_path, task_id) {
|
||||
try {
|
||||
const tasksFile = readFileSync(task_path, 'utf8');
|
||||
const tasks = JSON.parse(tasksFile);
|
||||
const task = tasks[task_id];
|
||||
if (!task) {
|
||||
throw new Error(`Task ${task_id} not found`);
|
||||
}
|
||||
|
||||
return task;
|
||||
} catch (error) {
|
||||
console.error('Error loading task:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
isDone() {
|
||||
if (this.validator && this.validator.validate())
|
||||
return {"message": 'Task successful', "code": 2};
|
||||
// TODO check for other terminal conditions
|
||||
// if (this.task.goal && !this.self_prompter.on)
|
||||
// return {"message": 'Agent ended goal', "code": 3};
|
||||
// if (this.task.conversation && !inConversation())
|
||||
// return {"message": 'Agent ended conversation', "code": 3};
|
||||
if (this.taskTimeout) {
|
||||
const elapsedTime = (Date.now() - this.taskStartTime) / 1000;
|
||||
if (elapsedTime >= this.taskTimeout) {
|
||||
console.log('Task timeout reached. Task unsuccessful.');
|
||||
return {"message": 'Task timeout reached', "code": 4};
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
async initBotTask() {
|
||||
if (this.data === null)
|
||||
return;
|
||||
let bot = this.agent.bot;
|
||||
let name = this.agent.name;
|
||||
|
||||
bot.chat(`/clear ${name}`);
|
||||
console.log(`Cleared ${name}'s inventory.`);
|
||||
|
||||
//wait for a bit so inventory is cleared
|
||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||
|
||||
if (this.data.agent_number > 1) {
|
||||
var initial_inventory = this.data.initial_inventory[this.agent.count_id.toString()];
|
||||
console.log("Initial inventory:", initial_inventory);
|
||||
} else if (this.data) {
|
||||
console.log("Initial inventory:", this.data.initial_inventory);
|
||||
var initial_inventory = this.data.initial_inventory;
|
||||
}
|
||||
|
||||
if ("initial_inventory" in this.data) {
|
||||
console.log("Setting inventory...");
|
||||
console.log("Inventory to set:", initial_inventory);
|
||||
for (let key of Object.keys(initial_inventory)) {
|
||||
console.log('Giving item:', key);
|
||||
bot.chat(`/give ${name} ${key} ${initial_inventory[key]}`);
|
||||
};
|
||||
//wait for a bit so inventory is set
|
||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||
console.log("Done giving inventory items.");
|
||||
}
|
||||
// Function to generate random numbers
|
||||
|
||||
function getRandomOffset(range) {
|
||||
return Math.floor(Math.random() * (range * 2 + 1)) - range;
|
||||
}
|
||||
|
||||
let human_player_name = null;
|
||||
let available_agents = settings.profiles.map((p) => JSON.parse(readFileSync(p, 'utf8')).name); // TODO this does not work with command line args
|
||||
|
||||
// Finding if there is a human player on the server
|
||||
for (const playerName in bot.players) {
|
||||
const player = bot.players[playerName];
|
||||
if (!available_agents.some((n) => n === name)) {
|
||||
console.log('Found human player:', player.username);
|
||||
human_player_name = player.username
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If there are multiple human players, teleport to the first one
|
||||
|
||||
// teleport near a human player if found by default
|
||||
|
||||
if (human_player_name) {
|
||||
console.log(`Teleporting ${name} to human ${human_player_name}`)
|
||||
bot.chat(`/tp ${name} ${human_player_name}`) // teleport on top of the human player
|
||||
|
||||
}
|
||||
await new Promise((resolve) => setTimeout(resolve, 200));
|
||||
|
||||
// now all bots are teleport on top of each other (which kinda looks ugly)
|
||||
// Thus, we need to teleport them to random distances to make it look better
|
||||
|
||||
/*
|
||||
Note : We don't want randomness for construction task as the reference point matters a lot.
|
||||
Another reason for no randomness for construction task is because, often times the user would fly in the air,
|
||||
then set a random block to dirt and teleport the bot to stand on that block for starting the construction,
|
||||
This was done by MaxRobinson in one of the youtube videos.
|
||||
*/
|
||||
|
||||
if (this.data.type !== 'construction') {
|
||||
const pos = getPosition(bot);
|
||||
const xOffset = getRandomOffset(5);
|
||||
const zOffset = getRandomOffset(5);
|
||||
bot.chat(`/tp ${name} ${Math.floor(pos.x + xOffset)} ${pos.y + 3} ${Math.floor(pos.z + zOffset)}`);
|
||||
await new Promise((resolve) => setTimeout(resolve, 200));
|
||||
}
|
||||
|
||||
if (this.data.agent_count && this.data.agent_count > 1) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 10000));
|
||||
if (available_agents.length < this.data.agent_count) {
|
||||
console.log(`Missing ${this.data.agent_count - available_agents.length} bot(s).`);
|
||||
this.agent.cleanKill('Not all required players/bots are present in the world. Exiting.', 4);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (this.data.goal) {
|
||||
await executeCommand(this.agent, `!goal("${this.data.goal}")`);
|
||||
}
|
||||
|
||||
if (this.data.conversation && this.agent.count_id === 0) {
|
||||
let other_name = available_agents.filter(n => n !== name)[0];
|
||||
await executeCommand(this.agent, `!startConversation("${other_name}", "${this.data.conversation}")`);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,149 +0,0 @@
|
|||
import { readFileSync } from 'fs';
|
||||
import { executeCommand } from '../agent/commands/index.js';
|
||||
import { getPosition } from '../agent/library/world.js'
|
||||
import settings from '../../settings.js';
|
||||
|
||||
export function loadTask(task_path, task_id) {
|
||||
try {
|
||||
const tasksFile = readFileSync(task_path, 'utf8');
|
||||
const tasks = JSON.parse(tasksFile);
|
||||
const task = tasks[task_id];
|
||||
if (!task) {
|
||||
throw new Error(`Task ${task_id} not found`);
|
||||
}
|
||||
|
||||
return task;
|
||||
} catch (error) {
|
||||
console.error('Error loading task:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
export async function initBotTask(agent) {
|
||||
let bot = agent.bot;
|
||||
let task = agent.task;
|
||||
let name = bot.username;
|
||||
|
||||
bot.chat(`/clear ${name}`);
|
||||
console.log(`Cleared ${name}'s inventory.`);
|
||||
|
||||
//wait for a bit so inventory is cleared
|
||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||
|
||||
if (task.agent_number > 1) {
|
||||
var initial_inventory = task.initial_inventory[agent.count_id.toString()];
|
||||
console.log("Initial inventory:", initial_inventory);
|
||||
} else if (task) {
|
||||
console.log("Initial inventory:", task.initial_inventory);
|
||||
var initial_inventory = task.initial_inventory;
|
||||
}
|
||||
|
||||
if ("initial_inventory" in task) {
|
||||
console.log("Setting inventory...");
|
||||
console.log("Inventory to set:", initial_inventory);
|
||||
for (let key of Object.keys(initial_inventory)) {
|
||||
console.log('Giving item:', key);
|
||||
bot.chat(`/give ${name} ${key} ${initial_inventory[key]}`);
|
||||
};
|
||||
//wait for a bit so inventory is set
|
||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||
console.log("Done giving inventory items.");
|
||||
}
|
||||
// Function to generate random numbers
|
||||
|
||||
function getRandomOffset(range) {
|
||||
return Math.floor(Math.random() * (range * 2 + 1)) - range;
|
||||
}
|
||||
|
||||
let human_player_name = null;
|
||||
let available_agents = settings.profiles.map((p) => JSON.parse(readFileSync(p, 'utf8')).name);
|
||||
|
||||
// Finding if there is a human player on the server
|
||||
for (const playerName in bot.players) {
|
||||
const player = bot.players[playerName];
|
||||
if (!available_agents.some((n) => n === name)) {
|
||||
console.log('Found human player:', player.username);
|
||||
human_player_name = player.username
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If there are multiple human players, teleport to the first one
|
||||
|
||||
// teleport near a human player if found by default
|
||||
|
||||
if (human_player_name) {
|
||||
console.log(`Teleporting ${name} to human ${human_player_name}`)
|
||||
bot.chat(`/tp ${name} ${human_player_name}`) // teleport on top of the human player
|
||||
|
||||
}
|
||||
await new Promise((resolve) => setTimeout(resolve, 200));
|
||||
|
||||
// now all bots are teleport on top of each other (which kinda looks ugly)
|
||||
// Thus, we need to teleport them to random distances to make it look better
|
||||
|
||||
/*
|
||||
Note : We don't want randomness for construction task as the reference point matters a lot.
|
||||
Another reason for no randomness for construction task is because, often times the user would fly in the air,
|
||||
then set a random block to dirt and teleport the bot to stand on that block for starting the construction,
|
||||
This was done by MaxRobinson in one of the youtube videos.
|
||||
*/
|
||||
|
||||
if (task.type !== 'construction') {
|
||||
const pos = getPosition(bot);
|
||||
const xOffset = getRandomOffset(5);
|
||||
const zOffset = getRandomOffset(5);
|
||||
bot.chat(`/tp ${name} ${Math.floor(pos.x + xOffset)} ${pos.y + 3} ${Math.floor(pos.z + zOffset)}`);
|
||||
await new Promise((resolve) => setTimeout(resolve, 200));
|
||||
}
|
||||
|
||||
if (task.agent_count && task.agent_count > 1) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 10000));
|
||||
if (available_agents.length < task.agent_count) {
|
||||
console.log(`Missing ${task.agent_count - available_agents.length} bot(s).`);
|
||||
agent.cleanKill('Not all required players/bots are present in the world. Exiting.', 4);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (task.goal) {
|
||||
await executeCommand(agent, `!goal("${task.goal}")`);
|
||||
}
|
||||
|
||||
if (task.conversation && agent.count_id === 0) {
|
||||
let other_name = available_agents.filter(n => n !== name)[0];
|
||||
await executeCommand(agent, `!startConversation("${other_name}", "${task.conversation}")`);
|
||||
}
|
||||
}
|
||||
|
||||
export class TechTreeHarvestValidator {
|
||||
constructor(task, bot) {
|
||||
this.target = task.target;
|
||||
this.number_of_target = task.number_of_target;
|
||||
this.bot = bot;
|
||||
}
|
||||
|
||||
validate() {
|
||||
try{
|
||||
let valid = false;
|
||||
let total_targets = 0;
|
||||
this.bot.inventory.slots.forEach((slot) => {
|
||||
if (slot && slot.name.toLowerCase() === this.target) {
|
||||
total_targets += slot.count;
|
||||
}
|
||||
if (slot && slot.name.toLowerCase() === this.target && slot.count >= this.number_of_target) {
|
||||
valid = true;
|
||||
console.log('Task is complete');
|
||||
}
|
||||
});
|
||||
if (total_targets >= this.number_of_target) {
|
||||
valid = true;
|
||||
console.log('Task is complete');
|
||||
}
|
||||
return valid;
|
||||
} catch (error) {
|
||||
console.error('Error validating task:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue