mirror of
https://github.com/morten-olsen/shipped.git
synced 2026-02-07 23:26:23 +01:00
init
This commit is contained in:
26
packages/website/src/pages/articles/full/main.mdx
Normal file
26
packages/website/src/pages/articles/full/main.mdx
Normal file
@@ -0,0 +1,26 @@
|
||||
import { Bottom } from '@/ui/base/bottom';
|
||||
import { createMap } from "@shipped/engine";
|
||||
import { Playground } from "@/ui/playground";
|
||||
import { VesselInfo } from "@/ui/playground/utils/vessel";
|
||||
|
||||
# Example
|
||||
|
||||
This is an example bot which will select one of the five nearest port at random and sail to it.
|
||||
|
||||
Once it reached the port it will do a refuel, and if the goods there are cheaper than the ones it has on board it will buy them. Otherwise it will sell its goods.
|
||||
|
||||
<Playground
|
||||
script={import('./script.ts?raw')}
|
||||
utils={(
|
||||
<>
|
||||
<VesselInfo />
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Bottom
|
||||
links={[
|
||||
{ title: 'Play', href: '/game' },
|
||||
{ title: 'Ideas', href: '/features' },
|
||||
]}
|
||||
/>
|
||||
92
packages/website/src/pages/articles/full/script.ts
Normal file
92
packages/website/src/pages/articles/full/script.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
import { MapTile, PortMapTile, CaptainAI, CaptainCommand, calculatePrice } from "@shipped/engine";
|
||||
import PF from 'pathfinding';
|
||||
|
||||
// We start by defining a function that will be called every turn
|
||||
const captain: CaptainAI = ({ vessel, map, currentPort }) => {
|
||||
// Then we create an array to hold any commands we want to send to the vessel
|
||||
const commands: CaptainCommand[] = [];
|
||||
|
||||
// If the vessel doesn't have a plan, give it one
|
||||
if (!vessel.plan || vessel.plan.length === 0) {
|
||||
// We arrived at a port, so we should fuel up
|
||||
commands.push({
|
||||
type: 'fuel-up',
|
||||
});
|
||||
|
||||
if (currentPort) {
|
||||
// Get the current price of goods at the port
|
||||
const goodsPrice = calculatePrice(currentPort);
|
||||
// And the amount we've paid for goods
|
||||
const paidPrice = parseFloat(vessel.data.paid || '0');
|
||||
|
||||
// If the price is higher than what we paid, and we have goods, sell them
|
||||
if (goodsPrice > paidPrice && vessel.goods > 0) {
|
||||
commands.push({
|
||||
type: 'sell',
|
||||
amount: vessel.goods,
|
||||
});
|
||||
// If the price is lower than what we paid, and we have space, buy them
|
||||
} else {
|
||||
commands.push({
|
||||
type: 'buy',
|
||||
amount: 10,
|
||||
});
|
||||
commands.push({
|
||||
type: 'record',
|
||||
name: 'paid',
|
||||
data: goodsPrice.toString(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// We get our list of port tiles
|
||||
const portTiles = findPorts(map);
|
||||
// And calculate the distance to each one
|
||||
const withDistance = portTiles.map((port) => ({
|
||||
...port,
|
||||
distance: Math.sqrt(
|
||||
Math.pow(port.x - vessel.position.x, 2) +
|
||||
Math.pow(port.y - vessel.position.y, 2)
|
||||
),
|
||||
}));
|
||||
// We select the 5 closest
|
||||
const closestPort = withDistance.sort((a, b) => a.distance - b.distance).slice(0, 5);
|
||||
// And pick a random one
|
||||
const randomPort = closestPort[Math.floor(Math.random() * closestPort.length)];
|
||||
const grid = new PF.Grid(
|
||||
map.map((row) => row.map((tile) => tile.type !== 'land' ? 0 : 1))
|
||||
);
|
||||
const finder = new PF.AStarFinder();
|
||||
|
||||
// We then use A* to find a path to it
|
||||
const path = PF.Util.compressPath(finder.findPath(
|
||||
Math.round(vessel.position.x),
|
||||
Math.round(vessel.position.y),
|
||||
randomPort.x,
|
||||
randomPort.y,
|
||||
grid,
|
||||
));
|
||||
|
||||
commands.push({
|
||||
type: 'update-plan',
|
||||
// And then update the plan to navigate to our random port
|
||||
plan: path.map(([x, y]) => ({ x, y })),
|
||||
});
|
||||
}
|
||||
|
||||
// Hand of the wheel
|
||||
return commands;
|
||||
}
|
||||
|
||||
// Since we only have a tile map, we want to convert it into a list
|
||||
// of all the port tiles
|
||||
const findPorts = (tiles: MapTile[][]) => {
|
||||
const portTiles = tiles.map((row, y) => row.map((tile, x) => ({
|
||||
x,
|
||||
y,
|
||||
tile: tile as PortMapTile,
|
||||
}))).flat().filter(({ tile }) => tile.type === 'port');
|
||||
return portTiles;
|
||||
};
|
||||
|
||||
export default captain;
|
||||
20
packages/website/src/pages/articles/intro/main.mdx
Normal file
20
packages/website/src/pages/articles/intro/main.mdx
Normal file
@@ -0,0 +1,20 @@
|
||||
import { Bottom } from '@/ui/base/bottom';
|
||||
|
||||
# Introduction
|
||||
|
||||
Congratulations, you just completed you Maritime 101 and is now ready to start your new **ocean empire**!
|
||||
|
||||
The first thing you do is spend your life savings buying a (_very_) used vessel. You then spend the next few months fixing it up and getting it ready for your first voyage. You are now ready to set sail!
|
||||
But the ship and the repair means that you are already out of money, so you can not afford to hire on a crew.
|
||||
|
||||
**Not to worry**; you once fixed you VCR with only minor electrical burns, so setting up a ship for remote control should be a piece of cake.
|
||||
|
||||
But remote controlling it requires a connection, and maritime connectivity is expensive. You have to find a way to control the ship from on board the ship it self, you need a **bot**!
|
||||
|
||||
Luckily you have extensive knowladge in Excel macros, so you are just the right person for the job - **Good luck!**
|
||||
|
||||
<Bottom
|
||||
links={[
|
||||
{ title: 'Next: The game', href: '/articles/rules/intro' },
|
||||
]}
|
||||
/>
|
||||
50
packages/website/src/pages/articles/rules/fuel/main.mdx
Normal file
50
packages/website/src/pages/articles/rules/fuel/main.mdx
Normal file
@@ -0,0 +1,50 @@
|
||||
import { Bottom } from '@/ui/base/bottom';
|
||||
import { createMap } from "@shipped/engine";
|
||||
import { Playground } from "@/ui/playground";
|
||||
import { VesselInfo } from "@/ui/playground/utils/vessel";
|
||||
|
||||
export const map = createMap(30, 10, {
|
||||
ports: {
|
||||
'25,3': {},
|
||||
'7,5': {},
|
||||
'5,7': {},
|
||||
}
|
||||
});
|
||||
|
||||
# Fuel
|
||||
|
||||
A ship needs fuel to move. Your ship will start with a full tank of fuel, which will depleted as you move around the map.
|
||||
|
||||
Once your fuel is depleted, you will be unable to move, and the ship will be stuck.
|
||||
|
||||
To refuel you need to visit a port. Port are black dots on the map. Once you are in a port, you can issue a `{ type: 'fuel-up', amount?: number }` command to refuel your ship.
|
||||
If you do not specify an amount, your ship will be refueled to full.
|
||||
|
||||
If you do not have enough cash to refuel the requested amount, the command will be ignored.
|
||||
|
||||
You can calulate the price of fuel as `desiredAmount * currentPort.fuelPrice`.
|
||||
|
||||
Different ports can have different fuel prices, and you can use the `ports` property to look at prices on different ports.
|
||||
|
||||
To find the location of a port, you can use the `map` property. which contains all the tiles of the map. here the tile will have a `type` of `port` and an `id` of the port.
|
||||
|
||||
### Visibility
|
||||
when you are at a port the `ports` property will contain all ports on the map, and their fuel prices.
|
||||
|
||||
When you are not at a port, the `ports` property will only contain the ports that are within your ships visibility range (`vessel.stats.visibility`).
|
||||
|
||||
<Playground
|
||||
map={map}
|
||||
script={import('./script.ts?raw')}
|
||||
utils={(
|
||||
<>
|
||||
<VesselInfo />
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Bottom
|
||||
links={[
|
||||
{ title: 'Next: Example', href: '/articles/full' },
|
||||
]}
|
||||
/>
|
||||
44
packages/website/src/pages/articles/rules/fuel/script.ts
Normal file
44
packages/website/src/pages/articles/rules/fuel/script.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { MapTile, PortMapTile, CaptainAI, CaptainCommand } from "@shipped/engine";
|
||||
|
||||
// We start by defining a function that will be called every turn
|
||||
const captain: CaptainAI = ({ vessel, map, currentPort }) => {
|
||||
// Then we create an array to hold any commands we want to send to the vessel
|
||||
const commands: CaptainCommand[] = [];
|
||||
|
||||
// If the vessel doesn't have a plan, give it one
|
||||
if (!vessel.plan || vessel.plan.length === 0) {
|
||||
// If we're at a port, we want to fuel up
|
||||
if (currentPort) {
|
||||
commands.push({
|
||||
type: 'fuel-up',
|
||||
});
|
||||
}
|
||||
|
||||
// We get our list of port tiles
|
||||
const portTiles = findPorts(map);
|
||||
// And pick a random one
|
||||
const randomPort = portTiles[Math.floor(Math.random() * portTiles.length)];
|
||||
|
||||
commands.push({
|
||||
type: 'update-plan',
|
||||
// And then update the plan to navigate to it
|
||||
plan: [randomPort],
|
||||
});
|
||||
}
|
||||
|
||||
// Hand of the wheel
|
||||
return commands;
|
||||
}
|
||||
|
||||
// Since we only have a tile map, we want to convert it into a list
|
||||
// of all the port tiles
|
||||
const findPorts = (tiles: MapTile[][]) => {
|
||||
const portTiles = tiles.map((row, y) => row.map((tile, x) => ({
|
||||
x,
|
||||
y,
|
||||
tile: tile as PortMapTile,
|
||||
}))).flat().filter(({ tile }) => tile.type === 'port');
|
||||
return portTiles;
|
||||
};
|
||||
|
||||
export default captain;
|
||||
30
packages/website/src/pages/articles/rules/intro/main.mdx
Normal file
30
packages/website/src/pages/articles/rules/intro/main.mdx
Normal file
@@ -0,0 +1,30 @@
|
||||
import { Bottom } from '@/ui/base/bottom';
|
||||
|
||||
# The game
|
||||
|
||||
_Shipped_ is a game where you build bots, which controls vessels on the sea, battling for supremacy. You win by making the bot which can earn the most money, in the shortest amount of time, emitting the least CO2.
|
||||
|
||||
## The rules
|
||||
|
||||
The game is played in a succession of rounds. In each round your bot is giving information about the vessel, and the world around it. Based on this information, your bot must decide what to do next.
|
||||
|
||||
Bots can perform a multitude of actions, for instance:
|
||||
|
||||
### Anywhere
|
||||
- Create a planned route for the vessel to follow
|
||||
- Change the speed of the vessel
|
||||
|
||||
### At ports
|
||||
- Fuel the vessel
|
||||
- Buy goods
|
||||
- Sell goods
|
||||
|
||||
## Scoring
|
||||
|
||||
The exact score calulation is TBD but will be a combination of profit and CO2 emissions.
|
||||
|
||||
<Bottom
|
||||
links={[
|
||||
{ title: 'Next: Planning a route', href: '/articles/rules/move' },
|
||||
]}
|
||||
/>
|
||||
27
packages/website/src/pages/articles/rules/land/main.mdx
Normal file
27
packages/website/src/pages/articles/rules/land/main.mdx
Normal file
@@ -0,0 +1,27 @@
|
||||
import { Bottom } from '@/ui/base/bottom';
|
||||
import { createMap } from "@shipped/engine";
|
||||
import { Playground } from "@/ui/playground";
|
||||
|
||||
export const map = {
|
||||
size: { width: 10, height: 10 },
|
||||
}
|
||||
|
||||
# Land
|
||||
|
||||
Unfortunatly the world is not only water. There are also land tiles. Land tiles are not passable by ships.
|
||||
|
||||
You can use the `map` property to check which tiles are land.
|
||||
|
||||
It is generally a good idea to avoid sailing unto land as your ship would get stuck.
|
||||
|
||||
The bot below find connecting square tiles, check if they are water tiles and sails to a random one.
|
||||
<Playground
|
||||
map={map}
|
||||
script={import('./script.ts?raw')}
|
||||
/>
|
||||
|
||||
<Bottom
|
||||
links={[
|
||||
{ title: 'Next: Fuel', href: '/articles/rules/fuel' },
|
||||
]}
|
||||
/>
|
||||
18
packages/website/src/pages/articles/rules/land/script.ts
Normal file
18
packages/website/src/pages/articles/rules/land/script.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { CaptainAI } from "@shipped/engine";
|
||||
|
||||
|
||||
const captain: CaptainAI = ({ vessel, map }) => {
|
||||
if (!vessel.plan || vessel.plan.length === 0) {
|
||||
const connectedPassableTiles = [[0, -1], [1, 0], [0, 1], [-1, 0]].map(([ x, y ]) => ({
|
||||
x: vessel.position.x + x,
|
||||
y: vessel.position.y + y,
|
||||
})).filter(({ x, y }) => map[y]?.[x] && map[y][x].type !== 'land')
|
||||
const randomTile = connectedPassableTiles[Math.floor(Math.random() * connectedPassableTiles.length)];
|
||||
return [{
|
||||
type: 'update-plan',
|
||||
plan: [randomTile],
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
export default captain;
|
||||
31
packages/website/src/pages/articles/rules/move/main.mdx
Normal file
31
packages/website/src/pages/articles/rules/move/main.mdx
Normal file
@@ -0,0 +1,31 @@
|
||||
import { Bottom } from '@/ui/base/bottom';
|
||||
import { createMap } from "@shipped/engine";
|
||||
import { Playground } from "@/ui/playground";
|
||||
|
||||
export const map = createMap(10, 3);
|
||||
export const vessel = {
|
||||
position: { x: 1, y: 1 },
|
||||
}
|
||||
|
||||
# Planning a route
|
||||
|
||||
So the first thing that you bot needs to learn is how to move the vessel around.
|
||||
|
||||
Everything the bot does, is done by returning a set of commands from its AI function.
|
||||
There are different commands, but they all take the form of <code>{`{ type: string, [prop: string]: any }`}</code>.
|
||||
|
||||
To move the ship the Captain needs to provide a plan in the form of a series of waypoints, which the ship will follow.
|
||||
|
||||
The captain can at anytime give a new plan, and the ship will then start to follow the updated plan
|
||||
|
||||
<Playground
|
||||
map={map}
|
||||
vessel={vessel}
|
||||
script={import('./script.ts?raw')}
|
||||
/>
|
||||
|
||||
<Bottom
|
||||
links={[
|
||||
{ title: 'Next: Land', href: '/articles/rules/land' },
|
||||
]}
|
||||
/>
|
||||
18
packages/website/src/pages/articles/rules/move/script.ts
Normal file
18
packages/website/src/pages/articles/rules/move/script.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { CaptainAI } from "@shipped/engine";
|
||||
|
||||
// We start by defining a function that will be called every turn
|
||||
const captain: CaptainAI = ({ vessel }) => {
|
||||
// If the vessel already have a plan we don't want to do anything
|
||||
if (vessel.plan && vessel.plan.length > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the vessel doesn't have a plan, give it one
|
||||
return [{
|
||||
type: 'update-plan',
|
||||
// This is a plan to move to the tile at (8, 1)
|
||||
plan: [{ x: 8, y: 1 }],
|
||||
}];
|
||||
}
|
||||
|
||||
export default captain;
|
||||
Reference in New Issue
Block a user