tasks class

This commit is contained in:
Kolby Nottingham 2024-12-10 15:39:57 -08:00
parent 760a178f74
commit d0140aa542
5 changed files with 204 additions and 273 deletions

View file

@ -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"

View file

@ -25,7 +25,6 @@ function parseArguments() {
.parse(); .parse();
} }
//todo: modify for multiple agents
function getProfiles(args) { function getProfiles(args) {
return args.profiles || settings.profiles; return args.profiles || settings.profiles;
} }
@ -35,7 +34,7 @@ async function main() {
const mindServer = createMindServer(); const mindServer = createMindServer();
} }
mainProxy.connect(); mainProxy.connect();
const args = parseArguments(); const args = parseArguments();
const profiles = getProfiles(args); const profiles = getProfiles(args);
console.log(profiles); console.log(profiles);

View file

@ -13,7 +13,7 @@ import { handleTranslation, handleEnglishTranslation } from '../utils/translator
import { addViewer } from './viewer.js'; import { addViewer } from './viewer.js';
import settings from '../../settings.js'; import settings from '../../settings.js';
import { serverProxy } from './agent_proxy.js'; import { serverProxy } from './agent_proxy.js';
import { loadTask, initBotTask, TechTreeHarvestValidator } from '../utils/tasks.js'; import { Task } from './tasks.js';
export class Agent { export class Agent {
async start(profile_fp, load_mem=false, init_message=null, count_id=0, task_path=null, task_id=null) { 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); convoManager.initAgent(this);
console.log('Initializing examples...'); console.log('Initializing examples...');
await this.prompter.initExamples(); 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); serverProxy.connect(this);
@ -58,24 +61,6 @@ export class Agent {
save_data = this.history.load(); 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', () => { this.bot.on('login', () => {
console.log(this.name, 'logged in!'); console.log(this.name, 'logged in!');
@ -105,8 +90,7 @@ export class Agent {
this._setupEventHandlers(save_data, init_message); this._setupEventHandlers(save_data, init_message);
this.startEvents(); this.startEvents();
if (this.task) this.task.initBotTask();
initBotTask(this);
} catch (error) { } catch (error) {
console.error('Error in spawn event:', error); console.error('Error in spawn event:', error);
@ -376,6 +360,7 @@ export class Agent {
} }
startEvents() { startEvents() {
// Custom events
this.bot.on('time', () => { this.bot.on('time', () => {
if (this.bot.time.timeOfDay == 0) if (this.bot.time.timeOfDay == 0)
this.bot.emit('sunrise'); this.bot.emit('sunrise');
@ -454,46 +439,17 @@ export class Agent {
this.bot.emit('idle'); this.bot.emit('idle');
// Check for task completion // Check for task completion
if (this.task) { if (this.task.data) {
setInterval(() => { setInterval(() => {
if (this.validator && this.validator.validate()) let res = this.task.isDone();
this.killBots(); if (res) {
// TODO check for other terminal conditions // TODO kill other bots
// if (this.task.goal && !this.self_prompter.on) this.cleanKill(res.message, res.code);
// 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);
}
} }
}, 1000); }, 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) { async update(delta) {
await this.bot.modes.update(); await this.bot.modes.update();
this.self_prompter.update(delta); this.self_prompter.update(delta);

192
src/agent/tasks.js Normal file
View 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}")`);
}
}
}

View file

@ -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;
}
}
}