Now that Business agents are communicating with Customers, we need the Customers to send a reply back. To do this, let’s look at customer.js. Every time a Customer agent receives a batch of messages, it will:
The cost function will be as follows, where price is the Business’s item_price, position is the Business’s position, and D is the distance function (linear euclidean) :
Create the function calculate_cost() in customer.js.
const behavior = (state, context) => {
// Function to determine cost --> Business price * distance from Business
const calculate_cost = (position, price) => {
const state_position = state.position;
return (
price +
Math.sqrt(
Math.pow(state_position[0] - position[0], 2) +
Math.pow(state_position[1] - position[1], 2),
)
);
};
};
Next, we are going to collect and store all the messages sent by the Business agents into a dictionary. This will make for easy access and iteration when we compare. Place this code underneath calculate_cost().
const collect_business_data = (messages) => {
let shops = {};
messages
.filter((message) => message.type === "business_movement")
.forEach((message) => {
const agent_id = message.from;
if (agent_id in shops) {
shops[agent_id].data.push([
message.data.position,
message.data.price,
message.data.rgb,
]);
} else {
shops[agent_id] = {
data: [[message.data.position, message.data.price, message.data.rgb]],
};
}
});
return shops;
};
Now that all the Business data is stored successfully, we need to iterate through each key (or Business) and determine which position and item_price combination yields the lowest cost for the individual Business as well as the overall.
Define a find_min() function like the one below.
const find_min = (businesses) => {
let overall_min = {
cost: null,
agent_id: "",
position: [],
price: 0,
rgb: null,
};
Object.keys(businesses).forEach((shop) => {
let individual_min = {
cost: null,
agent_id: "",
position: [],
price: 0,
rgb: null,
};
// Find min cost for each business
businesses[shop].data.forEach((business_change) => {
const position = business_change[0];
const price = business_change[1];
const rgb = business_change[2];
const cost = calculate_cost(position, price);
});
});
};
Let’s add the cost comparisons for both the individual Business and overall. Place the following code right below your cost calculation (within thebusinesses[shop].data.forEach() call).
// Check min for individual business
if (cost < individual_min.cost || individual_min.cost === null) {
// TODO: Update individual_min
} else if (cost === individual_min.cost) {
if (Math.random() < 0.5) {
// TODO: Update individual_min
}
}
// Check min for all business
if (cost < overall_min.cost || overall_min.cost === null) {
// TODO: Update overall_min
} else if (cost === overall_min.cost) {
// 50% chance of picking another business if cost is the same or will pick the shop they shopped at last tick
if (
Math.random() < 0.5 ||
JSON.stringify(state.rgb) === JSON.stringify(rgb)
) {
// TODO: Update overall_min
}
}
Notice how many times you need to update overall_min and individual_min. Since both objects have the same structure and to save lines of code, let’s create another function in customer.js called update_min(). (Add it before find_min()).
// Update properties of 'type' object (individual_min or overall_min)
const update_min = (type, cost, id, position, price, rgb) => {
type.cost = cost;
type.agent_id = id;
type.position = position;
type.price = price;
type.rgb = rgb;
};
Now we can replace the ‘TODO’ comments with a call to update_min() with the proper parameters. We've inserted those calls in the snippet below.
// Check min for individual business
if (cost < individual_min.cost || individual_min.cost === null) {
update_min(individual_min, cost, shop, position, price, rgb);
} else if (cost === individual_min.cost) {
if (Math.random() < 0.5) {
update_min(individual_min, cost, shop, position, price, rgb);
}
}
// Check min for all business
if (cost < overall_min.cost || overall_min.cost === null) {
update_min(overall_min, cost, shop, position, price, rgb);
} else if (cost === overall_min.cost) {
// 50% chance of picking another business if cost is the same or will pick the shop they shopped at last tick
if (
Math.random() < 0.5 ||
JSON.stringify(state.rgb) === JSON.stringify(rgb)
) {
update_min(overall_min, cost, shop, position, price, rgb);
}
}
After all the comparisons have been made, we want to notify that particular Business where this Customer would prefer to shop. Let's send a message just below the businesses[shop].data.forEach() call within the key iterator.
state.addMessage(individual_min.agent_id, "customer_cost", {
cost: individual_min.cost,
position: individual_min.position,
price: individual_min.price,
});
All that’s left to do now for customer.js is to update the agent’s color to the overall_min’s color. This signifies that the Customer decided it would want to purchase from that particular business. Set the agent’s color after the closing of the object key iterator (on line 89).
// Only update color if min cost was determined during this time step
if (overall_min.rgb !== null) {
state.rgb = overall_min.rgb;
}
Finally, call find_min() below your call to collect_business_data() and pass in the const variable businesses.
const businesses = collect_business_data(context.messages());
find_min(businesses);
The customer.js behavior is finally complete!
To see customer.js in full, navigate to bottom of this section or click on ‘Phase 1 Final Code’ in the sidebar.
Reset and run!
If you followed all the steps above, run the simulation a couple times and you should see the customer agents change color based on their decision after a couple time steps.
const behavior = (state, context) => {
// Function to determine cost --> business price + distance from business
const calculate_cost = (position, price) => {
const state_position = state.position;
return (
price +
Math.sqrt(
Math.pow(state_position[0] - position[0], 2) +
Math.pow(state_position[1] - position[1], 2),
)
);
};
const collect_business_data = (messages) => {
let shops = {};
messages
.filter((message) => message.type === "business_movement")
.forEach((message) => {
const agent_id = message.from;
if (agent_id in shops) {
shops[agent_id].data.push([
message.data.position,
message.data.price,
message.data.rgb,
]);
} else {
shops[agent_id] = {
data: [
[message.data.position, message.data.price, message.data.rgb],
],
};
}
});
return shops;
};
// Update properties of 'type' object (individual_min or overall_min)
const update_min = (type, cost, id, position, price, rgb) => {
type.cost = cost;
type.agent_id = id;
type.position = position;
type.price = price;
type.rgb = rgb;
};
const find_min = (businesses) => {
let overall_min = {
cost: null,
agent_id: "",
position: [],
price: 0,
rgb: null,
};
Object.keys(businesses).forEach((shop) => {
let individual_min = {
cost: null,
agent_id: "",
position: [],
price: 0,
rgb: null,
};
// Find min cost for each business
businesses[shop].data.forEach((business_change) => {
const position = business_change[0];
const price = business_change[1];
const rgb = business_change[2];
const cost = calculate_cost(position, price);
// Check min for individual business
if (cost < individual_min.cost || individual_min.cost === null) {
update_min(individual_min, cost, shop, position, price, rgb);
} else if (cost === individual_min.cost) {
if (Math.random() < 0.5) {
update_min(individual_min, cost, shop, position, price, rgb);
}
}
// Check min for all business
if (cost < overall_min.cost || overall_min.cost === null) {
update_min(overall_min, cost, shop, position, price, rgb);
} else if (cost === overall_min.cost) {
// 50% chance of picking another business if cost is the same or will pick the shop they shopped at last tick
if (
Math.random() < 0.5 ||
JSON.stringify(state.rgb) === JSON.stringify(rgb)
) {
update_min(overall_min, cost, shop, position, price, rgb);
}
}
});
state.addMessage(individual_min.agent_id, "customer_cost", {
cost: individual_min.cost,
position: individual_min.position,
price: individual_min.price,
});
});
// Only update color if min cost was determined during this time step
if (overall_min.rgb !== null) {
state.rgb = overall_min.rgb;
}
};
const businesses = collect_business_data(context.messages());
find_min(businesses);
};
Previous