Skip to content

Instantly share code, notes, and snippets.

@dcabines
Last active March 7, 2021 05:05
Show Gist options
  • Save dcabines/c49bd98ed55d6f156658fd8795c5655b to your computer and use it in GitHub Desktop.
Save dcabines/c49bd98ed55d6f156658fd8795c5655b to your computer and use it in GitHub Desktop.
const root = 'https://api.spacetraders.io';
// utils
const timeout = (seconds) => new Promise(resolve => setTimeout(resolve, seconds * 1000));
const sleeper = (seconds) => (x) => new Promise(resolve => setTimeout(() => resolve(x), seconds * 1000));
const userName = () => `user-${new Date().getTime()}-${Math.random()}`;
const querySeparator = (path) => path.indexOf('?') === -1 ? '?' : '&';
const get = (path) => fetch(`${root}${path}`).then(x => x.json()).then(saveState).then(saveArchive).then(sleeper(1));
const post = (path, body) => fetch(`${root}${path}`, postOptions(body)).then(x => x.json()).then(saveState).then(saveArchive).then(sleeper(1));
const put = (path, body) => fetch(`${root}${path}`, putOptions(body)).then(x => x.json()).then(saveState).then(saveArchive).then(sleeper(1));
const authGet = (path) => get(`${path}${querySeparator(path)}token=${state.token}`);
const authPost = (path, body) => post(`${path}${querySeparator(path)}token=${state.token}`, body);
const authPut = (path, body) => put(`${path}${querySeparator(path)}token=${state.token}`, body);
const postOptions = (body) => ({
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body)
});
const putOptions = (body) => ({
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body)
});
const upsert = (findKey) => (destination, incoming) => {
const existing = destination.find(x => findKey(x, incoming));
const others = destination.filter(x => x !== existing);
const updated = {...existing, ...incoming };
return [...others, updated];
};
// state
const state = {};
const saveState = (data) => Object.assign(state, data);
const system = () => state.systems[0];
const locations = () => system().locations;
const systemLocation = (symbol) => locations().find(x => x.symbol === symbol);
const marketItem = (symbol) => state.planet.marketplace.find(x => x.symbol === symbol);
const ship = (shipId) => state.user.ships.find(x => x.id === shipId);
const cargo = (shipId, good) => ship(shipId).cargo.find(x => x.good === good);
const cargoTotal = (shipId) => ship(shipId).cargo.reduce((p, c) => p + c.quantity, 0);
const remainingCargo = (shipId) => ship(shipId).maxCargo - cargoTotal(shipId);
const goodQuantity = (shipId, good) => cargo(shipId, good).quantity;
const maxAfford = (good, creditReserve) => {
const credits = state.user.credits - creditReserve;
const price = marketItem(good).pricePerUnit;
return credits / price;
};
// archive
const archive = {};
const planet = (symbol) => archive.planets.find(x => x.symbol === symbol);
const unexploredLocations = () => locations().filter(location => !planet(location));
const symbolMatch = (existing, incoming) => existing.symbol === incoming.symbol;
const fillHold = (shipId, good, creditReserve) => {
const space = remainingCargo(shipId);
const price = maxAfford(good, creditReserve);
return Math.min(space, price);
};
const beginTrip = () => {
archive.startCredits = state.user.credits;
};
const endTrip = () => {
archive.lastProfit = state.user.credits - archive.startCredits;
archive.trips = (archive.trips || 0) + 1;
archive.totalProfit = (archive.totalProfit || 0) + archive.lastProfit;
};
const archiveLocations = () => {
if (!state.locations) return;
archive.locations = state.locations.reduce(
upsert(symbolMatch),
archive.locations || []
);
};
const archivePlanet = () => {
if (!state.planet) return;
archive.planets = [state.planet].reduce(
upsert(symbolMatch),
archive.planets || []
);
};
const saveArchive = () => {
archiveLocations();
archivePlanet();
};
// report
const reportHeader = '/*\\';
const linePrefix = '|*|';
const reportFooter = '\\*/';
const takeLongest = (p, c) => c.length > p ? c.length : p;
const longestKey = (array, keyPicker) => array.map(keyPicker).reduce(takeLongest, 0);
const sortLocations = (a, b) => a.name < b.name ? -1 : 1;
const sortMarket = (a, b) => a.symbol < b.symbol ? -1 : 1;
const marketLine = (item) => `${item.symbol} ${item.pricePerUnit} ${item.available}`;
const marketReport = (marketplace) => {
const header = {
symbol: 'name',
pricePerUnit: 'price',
available: 'available'
};
const marketplaceWithHeader = [header, ...marketplace];
const longestSymbol = longestKey(marketplaceWithHeader, x => x.symbol);
const longestPrice = longestKey(marketplaceWithHeader, x => x.pricePerUnit.toString());
const longestAvailable = longestKey(marketplaceWithHeader, x => x.available.toString());
const lineData = (item) => ({
symbol: item.symbol.padEnd(longestSymbol, ' '),
pricePerUnit: item.pricePerUnit.toString().padStart(longestPrice, ' '),
available: item.available.toString().padStart(longestAvailable, ' ')
});
return [header, ...marketplace.sort(sortMarket)].map(lineData).map(marketLine);
};
const prefixLines = (data) => data.map(x => `${linePrefix} ${x}`);
const report = (data) => `${reportHeader}\n${prefixLines(data).join(`\n`)}\n${reportFooter}`;
const planetReport = (planet) => [`${planet.name} ${planet.symbol}`, ...marketReport(planet.marketplace)];
const systemReport = () => [].concat(...archive.planets.sort(sortLocations).map(x => [...planetReport(x), '']));
const buyReport = (order) => `Bought ${order.quantity} ${order.good} for ${order.total} at ${order.pricePerUnit} each.`;
const sellReport = (order) => `Sold ${order.quantity} ${order.good} for ${order.total}.`;
const creditReport = () => `${state.user.credits} credits remaining.`;
const flightReport = () => `Flying to ${systemLocation(state.flightPlan.destination).name}.`;
const tripReport = () => `Completed trip number ${archive.trips}.`;
const profitReport = () => `${archive.lastProfit} profit.`
const totalProfitReport = () => `${archive.totalProfit} total profit.`;
const availableReport = (good) => `${good} is not available here.`;
const poorReport = (good, quantity) => `Cannot afford to buy ${quantity} of ${good}`;
// path
const user = (path) => `/users/${state.user.username}/${path}`;
const game = (path) => `/game/${path}`;
// api
const getStatus = () => get(game('status'));
const createToken = (username) => post(`/users/${username}/token`);
const getUser = (username) => authGet(`/users/${username}`);
const getLoans = () => authGet(game('loans'));
const takeLoan = (type) => authPost(user('loans'), { type });
const payLoan = (loanId) => authPut(`${user('loans')}/${loanId}`);
const getShips = () => authGet(game('ships'));
const buyShip = (location, type) => authPost(user('ships'), { location, type });
const getSystems = () => authGet(game('systems'));
const getLocations = (type) => authGet(game(`systems/OE/locations?type=${type}`));
const getMarket = (location) => authGet(game(`locations/${location}/marketplace`));
const createFlightPlan = (shipId, destination) => authPost(user('flight-plans'), { shipId, destination });
const getFlightPlan = (flightPlanId) => authGet(user(`flight-plans/${flightPlanId}`));
const buyGood = (shipId, good, quantity) => authPost(user('purchase-orders'), { shipId, good, quantity: Math.trunc(quantity) });
const sellGood = (shipId, good, quantity) => authPost(user('sell-orders'), { shipId, good, quantity: Math.trunc(quantity) });
// actions
const getAccount = () => getUser(state.user.username);
const getMarkets = async() => {
for (const ship of state.user.ships) {
await getMarket(ship.location);
}
};
const fly = async(shipId, destination) => {
const myShip = ship(shipId);
if(myShip.location === destination) return;
await createFlightPlan(shipId, destination);
await getAccount();
console.log(flightReport());
await timeout(state.flightPlan.timeRemainingInSeconds);
await timeout(5); // docking
await getMarket(destination);
console.log(report(planetReport(state.planet)));
};
const buy = async(shipId, good, quantity) => {
const item = marketItem(good);
if(!item) {
console.log(availableReport(good));
return;
}
const canAfford = state.user.credits / item.pricePerUnit;
const toBuy = Math.min(canAfford, item.available, quantity);
if(isNaN(toBuy)) debugger;
if(toBuy <= 0) return;
await buyGood(shipId, good, toBuy);
await getAccount();
console.log(report([buyReport(state.order[0]), creditReport()]));
};
const sell = async(shipId, good, quantity) => {
if(quantity <= 0) return;
await sellGood(shipId, good, quantity);
await getAccount();
console.log(report([sellReport(state.order[0]), creditReport()]));
};
const refuel = async(shipId, wantedFuel) => {
const cargoFuel = cargo(shipId, 'FUEL') || { quantity: 0 };
const neededFuel = wantedFuel - cargoFuel.quantity;
if(isNaN(neededFuel)) debugger;
if(neededFuel <= 0) return;
await buy(shipId, 'FUEL', neededFuel);
};
const logIn = async(options) => {
if(options) {
state.token = options.token;
state.user = { username: options.username };
}
else {
await createToken(userName());
}
await getAccount();
await getSystems();
console.log('Username', state.user.username);
};
const startup = async() => {
await takeLoan('STARTUP');
await buyShip('OE-G4', 'JW-MK-I');
await getMarket('OE-G4');
await refuel(state.user.ships[0].id, 30);
console.log(report(planetReport(state.planet)));
};
const trade = async (options) => {
while(true) {
const location = ship(options.shipId).location;
if(!location) {
await getAccount();
continue;
}
beginTrip();
const inCargo = cargo(options.shipId, options.good);
if(!inCargo) {
await fly(options.shipId, options.source);
await refuel(options.shipId, options.fuel);
const toBuy = fillHold(options.shipId, options.good, options.creditReserve);
await buy(options.shipId, options.good, toBuy);
}
await fly(options.shipId, options.destination);
const toSell = goodQuantity(options.shipId, options.good);
await sell(options.shipId, options.good, toSell);
await refuel(options.shipId, options.fuel);
endTrip();
const reports = [
tripReport(),
profitReport(),
totalProfitReport()
];
console.log(report(reports));
if(state.user.credits < 1000) {
console.log('You\'re broke! Go home.');
return;
}
}
};
const explore = async(options) => {
const symbols = unexploredLocations().map(x => x.symbol);
for(let i = 0; i < symbols.length; i++) {
await refuel(options.shipId, 100);
await fly(options.shipId, symbols[i]);
}
console.log(report(systemReport()));
console.log('Exploration complete.');
};
const richMan = async() => {
await logIn({
token: 'a1e93722-98d0-4f1f-b747-d1983aef0723',
username: 'user-1615001801402-0.5537007097229811'
});
const myShip = ship('cklx7v1zc363076t889qtvaa555');
if(myShip.location) {
await getMarket(myShip.location);
}
await trade({
good: 'MACHINERY',
source: 'OE-D2',
destination: 'OE-G4',
fuel: 30,
creditReserve: 10000,
shipId: 'cklx7v1zc363076t889qtvaa555'
});
};
const richExplorer = async() => {
await logIn({
token: 'a1e93722-98d0-4f1f-b747-d1983aef0723',
username: 'user-1615001801402-0.5537007097229811'
});
const myShip = ship('cklygo8wk466961k089bwiotx6u');
if(myShip.location) {
await getMarket(myShip.location);
}
await explore({
shipId: myShip.id
});
};
const poorMan = async() => {
await logIn();
await startup();
await trade({
good: 'MACHINERY',
source: 'OE-G4',
destination: 'OE-D2',
fuel: 30,
creditReserve: 0,
shipId: state.user.ships[0].id
});
await poorMan();
};
const explorer = async() => {
await logIn();
await startup();
await explore({
shipId: state.user.ships[0].id
});
};
console.clear();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment