diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml
index 82f205fea..a2eb67c4a 100644
--- a/.github/workflows/build-and-deploy.yml
+++ b/.github/workflows/build-and-deploy.yml
@@ -20,7 +20,7 @@ jobs:
node-version: ${{ matrix.node-version }}
- uses: pnpm/action-setup@v4
with:
- version: 8
+ version: 9.1.2
- name: install & build
run: pnpm build
env:
diff --git a/LineDetection.html b/LineDetection.html
new file mode 100644
index 000000000..cfd6f345b
--- /dev/null
+++ b/LineDetection.html
@@ -0,0 +1,149 @@
+
+
+
+
+
+ Line Detection
+
+
+
+
+ Line Detection
+
+
+
+
+
+
+
+
diff --git a/extensions/src/common/extension/mixins/configurable/drawable.ts b/extensions/src/common/extension/mixins/configurable/drawable.ts
index 4882a869c..6c159111b 100644
--- a/extensions/src/common/extension/mixins/configurable/drawable.ts
+++ b/extensions/src/common/extension/mixins/configurable/drawable.ts
@@ -60,6 +60,8 @@ type Renderer = {
destroyDrawable(drawableID: Handle, group: string): void;
}
+type Drawable = ImageBitmap | Parameters[0];
+
/**
* Mixin the ability for extensions to draw images into the canvas
* @param Ctor
@@ -75,12 +77,13 @@ export default function (Ctor: T) {
* @param {ImageData | ImageBitmap} image
* @returns
*/
- protected createDrawable(image: T) {
+ protected createDrawable(image: T) {
this.renderer ??= this.runtime.renderer;
const { renderer } = this;
if (!renderer) return null;
+
const skin = renderer.createBitmapSkin(image as ImageData, 1);
const drawable = renderer.createDrawable(StageLayering.VideoLayer);
@@ -92,7 +95,7 @@ export default function (Ctor: T) {
const setVisible = (visible: boolean = true) =>
renderer.updateDrawableVisible(drawable, visible);
- const update = (image: ImageData | ImageBitmap) =>
+ const update = (image: Drawable) =>
renderer.updateBitmapSkin(skin, image as ImageData, 1);
const destroy = () => {
diff --git a/extensions/src/common/extension/mixins/configurable/indicators/index.ts b/extensions/src/common/extension/mixins/configurable/indicators/index.ts
index 4b284e317..9603da4df 100644
--- a/extensions/src/common/extension/mixins/configurable/indicators/index.ts
+++ b/extensions/src/common/extension/mixins/configurable/indicators/index.ts
@@ -2,7 +2,7 @@ import { untilTimePassed } from "$common/utils";
import { MinimalExtensionConstructor } from "../../base";
import { isSvgGroup, isSvgText, openAlert } from "./svgAlert";
-type IndicatorPayload = { position?: "category", msg: string, type?: "success" | "warning" | "error" };
+export type IndicatorPayload = { position?: "category", msg: string, type?: "success" | "warning" | "error", retry?: boolean };
/**
* Mixin the ability for extensions to add an indicator message to the workspace.
@@ -22,13 +22,20 @@ export default function (Ctor: T) {
* - `type`: The type of indicator to display. Currently "success", "warning" and "error", which effect the color of the indicator.
* @returns
*/
- async indicate({ position = "category", msg, type = "success" }: IndicatorPayload) {
-
+ async indicate({ position = "category", msg, type = "success", retry = false }: IndicatorPayload): ReturnType {
const elements = position === "category"
? getCategoryElements(this.name)
: { error: "Unsupported indicator position" };
- if ("error" in elements) throw new Error(elements.error);
+ if ("error" in elements) {
+ if (retry) {
+ await untilTimePassed(100);
+ const retried = await this.indicate({ position, msg, type, retry: false });
+ return retried;
+ }
+ else throw new Error(elements.error);
+ }
+
const { container } = elements;
const alert = await openAlert(container, msg, type);
return alert;
diff --git a/extensions/src/common/extension/mixins/configurable/indicators/svgAlert.ts b/extensions/src/common/extension/mixins/configurable/indicators/svgAlert.ts
index 7ee81aee0..e1ddd2104 100644
--- a/extensions/src/common/extension/mixins/configurable/indicators/svgAlert.ts
+++ b/extensions/src/common/extension/mixins/configurable/indicators/svgAlert.ts
@@ -1,9 +1,10 @@
+import { IndicatorPayload } from ".";
import { ExtensionInstanceWithFunctionality } from "../..";
export const isSvgGroup = (element: Element): element is SVGGElement => element.nodeName === "g";
export const isSvgText = (element: Element): element is SVGTextElement => element.nodeName === "text";
-type AlertType = Parameters["indicate"]>[0]["type"];
+type AlertType = IndicatorPayload["type"];
const fills = {
success: "#5ACA75",
diff --git a/extensions/src/common/types/utils.ts b/extensions/src/common/types/utils.ts
index 39f1d79d7..1ab68608e 100644
--- a/extensions/src/common/types/utils.ts
+++ b/extensions/src/common/types/utils.ts
@@ -47,4 +47,10 @@ export type ExpandRecursively = T extends (...args: infer A) => infer R
? T extends infer O
? { [K in keyof O]: ExpandRecursively }
: never
- : T;
\ No newline at end of file
+ : T;
+
+export type ReverseMap> = {
+ [P in T[keyof T]]: {
+ [K in keyof T]: T[K] extends P ? K : never
+ }[keyof T]
+}
\ No newline at end of file
diff --git a/extensions/src/doodlebot/Connect.svelte b/extensions/src/doodlebot/Connect.svelte
new file mode 100644
index 000000000..92ff1f8f8
--- /dev/null
+++ b/extensions/src/doodlebot/Connect.svelte
@@ -0,0 +1,171 @@
+
+
+
+ {#if error}
+
+ {error}
+
+ {/if}
+ {#if bluetooth}
+
How to connect to doodlebot
+
+
1. Set network credentials:
+
+ SSID (Network Name):
+
+
+
+ Password:
+
+
+
+
+
+
+ IP:
+ {#each ipParts as part, i}
+
+ {i < ipParts.length - 1 ? "." : ""}
+ {/each}
+
+
+
+
+
+
2. Select bluetooth device
+
+
+
+ {:else}
+ Uh oh! Your browser does not support bluetooth. Here's how to fix that...
+ TBD
+ {/if}
+
+
+
diff --git a/extensions/src/doodlebot/Doodlebot.ts b/extensions/src/doodlebot/Doodlebot.ts
new file mode 100644
index 000000000..c296b6529
--- /dev/null
+++ b/extensions/src/doodlebot/Doodlebot.ts
@@ -0,0 +1,992 @@
+import EventEmitter from "events";
+import { Service } from "./communication/ServiceHelper";
+import UartService from "./communication/UartService";
+import { followLine } from "./LineFollowing";
+import { Command, DisplayKey, NetworkStatus, ReceivedCommand, SensorKey, command, display, endpoint, keyBySensor, motorCommandReceived, networkStatus, port, sensor } from "./enums";
+import { base64ToInt32Array, makeWebsocket, Max32Int, testWebSocket } from "./utils";
+import { LineDetector } from "./LineDetection";
+import { calculateArcTime } from "./TimeHelper";
+
+export type Services = Awaited>;
+export type MotorStepRequest = {
+ /** Number of steps for the stepper (+ == forward, - == reverse) */
+ steps: number,
+ /** Number of steps/second (rate) of stepper */
+ stepsPerSecond: number
+};
+export type Bumper = { front: number, back: number };
+export type Vector3D = { x: number, y: number, z: number };
+export type Color = { red: number, green: number, blue: number, alpha: number };
+export type SensorReading = number | Vector3D | Bumper | Color;
+export type SensorData = Doodlebot["sensorData"];
+export type NetworkCredentials = { ssid: string, password: string, ipOverride?: string };
+export type NetworkConnection = { ip: string, hostname?: string };
+export type RequestBluetooth = (callback: (bluetooth: Bluetooth) => any) => void;
+export type SaveIP = (ip: string) => void;
+
+type MaybePromise = undefined | Promise;
+
+type Pending = Record<"motor" | "wifi" | "websocket" | "video" | "image", MaybePromise> & { ip: MaybePromise };
+
+type SubscriptionTarget = Pick;
+
+type Subscription = {
+ target: T,
+ event: Parameters[0],
+ listener: Parameters[1],
+}
+
+type MotorCommand = "steps" | "arc" | "stop";
+
+const trimNewtworkStatusMessage = (message: string, prefix: NetworkStatus) => message.replace(prefix, "").trim();
+
+const localIp = "127.0.0.1";
+
+const events = {
+ stop: "motor",
+ connect: "connect",
+ disconnect: "disconnect",
+} as const;
+
+type CreatePayload = {
+ credentials: NetworkCredentials,
+ requestBluetooth: RequestBluetooth,
+ saveIP: SaveIP,
+}
+
+const msg = (content: string, type: "success" | "warning" | "error") => {
+ switch (type) {
+ case "success":
+ console.log(content);
+ break;
+ case "warning":
+ console.warn(content);
+ break;
+ case "error":
+ console.error(content);
+ break;
+ }
+}
+
+type BLECommunication = {
+ onDisconnect: (...callbacks: (() => void)[]) => void,
+ onReceive: (callback: (text: CustomEvent) => void) => void,
+ send: (text: string) => Promise,
+}
+
+export default class Doodlebot {
+ /**
+ *
+ * @param services
+ * @param serviceClass
+ * @returns
+ */
+ static async tryCreateService any)>(
+ services: BluetoothRemoteGATTService[], serviceClass: T
+ ): Promise> {
+ const found = services.find((service) => service.uuid === serviceClass.uuid);
+ return found ? await serviceClass.create(found) : undefined;
+ }
+
+ /**
+ *
+ * @param bluetooth
+ * @param devicePrefix @todo unused
+ * @returns
+ */
+ static async requestRobot(bluetooth: Bluetooth, ...filters: BluetoothLEScanFilter[]) {
+ const device = await bluetooth.requestDevice({
+ filters: [
+ ...(filters ?? []),
+ {
+ services: [UartService.uuid]
+ },
+ ],
+ });
+
+ return device;
+ }
+
+ /**
+ * Get
+ * @param device
+ * @returns
+ */
+ static async getServices(device: BluetoothDevice) {
+ if (!device || !device.gatt) return null;
+ if (!device.gatt.connected) await device.gatt.connect();
+
+ const services = await device.gatt.getPrimaryServices();
+ const uartService = await Doodlebot.tryCreateService(services, UartService);
+
+ return { uartService, };
+ }
+
+ static async getBLE(ble: Bluetooth, ...filters: BluetoothLEScanFilter[]) {
+ const robot = await Doodlebot.requestRobot(ble, ...filters);
+ const services = await Doodlebot.getServices(robot);
+ if (!services) throw new Error("Unable to connect to doodlebot's UART service");
+ return { robot, services };
+ }
+
+ /**
+ *
+ * @param ble
+ * @param filters
+ * @throws
+ * @returns
+ */
+ static async tryCreate(
+ ble: Bluetooth,
+ { requestBluetooth, credentials, saveIP }: CreatePayload,
+ ...filters: BluetoothLEScanFilter[]) {
+ const { robot, services } = await Doodlebot.getBLE(ble, ...filters);
+ return new Doodlebot({
+ onReceive: (callback) => services.uartService.addEventListener("receiveText", callback),
+ onDisconnect: (callback) => ble.addEventListener("gattserverdisconnected", callback),
+ send: (text) => services.uartService.sendText(text),
+ }, requestBluetooth, credentials, saveIP);
+ }
+
+ private pending: Pending = { motor: undefined, wifi: undefined, websocket: undefined, ip: undefined };
+ private onMotor = new EventEmitter();
+ private onSensor = new EventEmitter();
+ private onNetwork = new EventEmitter();
+ private disconnectCallbacks = new Set<() => void>();
+ private subscriptions = new Array>();
+ private connection: NetworkConnection;
+ private websocket: WebSocket;
+ private encoder = new TextEncoder();
+
+ private isStopped = true; // should this be initializeed more intelligently?
+
+ private sensorData = ({
+ bumper: { front: 0, back: 0 },
+ altimeter: 0,
+ battery: 0,
+ distance: 0,
+ humidity: 0,
+ temperature: 0,
+ pressure: 0,
+ gyroscope: { x: 0, y: 0, z: 0 },
+ magnometer: { x: 0, y: 0, z: 0 },
+ accelerometer: { x: 0, y: 0, z: 0 },
+ light: { red: 0, green: 0, blue: 0, alpha: 0 }
+ } satisfies Record);
+
+ private sensorState: Record = {
+ bumper: false,
+ altimeter: false,
+ battery: false,
+ distance: false,
+ humidity: false,
+ temperature: false,
+ pressure: false,
+ gyroscope: false,
+ magnometer: false,
+ accelerometer: false,
+ light: false
+ };
+
+ private audioSocket: WebSocket;
+ private audioCallbacks = new Set<(chunk: Float32Array) => void>();
+
+ constructor(
+ private ble: BLECommunication,
+ private requestBluetooth: RequestBluetooth,
+ private credentials: NetworkCredentials,
+ private saveIP: SaveIP
+ ) {
+ this.ble.onReceive(this.receiveTextBLE.bind(this));
+ this.ble.onDisconnect(this.handleBleDisconnect.bind(this));
+ this.connectionWorkflow(credentials);
+ }
+
+ private formCommand(...args: (string | number)[]) {
+ return `(${args.join(",")})`;
+ }
+
+ private parseCommand(text: string) {
+ const lines = text.split("(").map((line) => line.replace(")", "")).splice(1);
+ return lines.map((line) => {
+ const [command, ...parameters] = line.split(",").map(s => s.trim()) as [ReceivedCommand, ...string[]];
+ return { command, parameters };
+ });
+ }
+
+ private updateSensor(type: T, value: SensorData[T]) {
+ this.onSensor.emit(type, value);
+ this.sensorData[type] = value;
+ this.sensorState[type] = true;
+ }
+
+ private updateNetworkStatus(ipComponent: string, hostnameComponent: string) {
+ const ip = trimNewtworkStatusMessage(ipComponent, networkStatus.ipPrefix);
+ const hostname = trimNewtworkStatusMessage(hostnameComponent, networkStatus.hostnamePrefix);
+ if (ip === localIp) {
+ return this.onNetwork.emit(events.disconnect);
+ }
+ this.connection = { ip, hostname };
+ this.onNetwork.emit(events.connect, this.connection);
+ }
+
+ private receiveTextBLE(event: CustomEvent) {
+ const { detail } = event;
+
+ console.log("received text", detail);
+
+ if (detail.startsWith(networkStatus.ipPrefix)) {
+ const parts = detail.split(",");
+ this.updateNetworkStatus(parts[0], parts[1]);
+ return;
+ }
+
+ for (const { command, parameters } of this.parseCommand(detail)) {
+ console.log({ command, parameters });
+ switch (command) {
+ case motorCommandReceived:
+ this.isStopped = true;
+ this.onMotor.emit(events.stop);
+ break;
+ case sensor.bumper: {
+ const [front, back] = parameters.map((parameter) => Number.parseFloat(parameter));
+ this.updateSensor(keyBySensor[command], { front, back });
+ break;
+ }
+ case sensor.distance:
+ case sensor.battery:
+ case sensor.altimeter:
+ case sensor.humidity:
+ case sensor.temperature:
+ case sensor.pressure: {
+ const value = Number.parseFloat(parameters[0]);
+ this.updateSensor(keyBySensor[command], value);
+ break;
+ }
+ case sensor.gyroscope:
+ case sensor.magnometer:
+ case sensor.accelerometer: {
+ const [x, y, z] = parameters.map((parameter) => Number.parseFloat(parameter));
+ this.updateSensor(keyBySensor[command], { x, y, z });
+ break;
+ }
+ case sensor.light: {
+ const [red, green, blue, alpha] = parameters.map((parameter) => Number.parseFloat(parameter));
+ this.updateSensor(keyBySensor[command], { red, green, blue, alpha });
+ break;
+ }
+ default:
+ throw new Error(`Not implemented: ${command}`);
+ }
+ }
+ }
+
+ private async onWebsocketMessage(event: MessageEvent) {
+ console.log("websocket message", { event });
+ if (event.data instanceof Blob) {
+ const text = await event.data.text();
+ console.log(text);
+ }
+ else if (event.data instanceof ArrayBuffer) {
+ const decoder = new TextDecoder('utf-8');
+ const decodedMessage = decoder.decode(event.data);
+ console.log('Received ArrayBuffer as text:', decodedMessage);
+ }
+
+ }
+
+ private invalidateWifiConnection() {
+ this.connection = undefined;
+ this.pending.wifi = undefined;
+ this.pending.websocket = undefined;
+ this.websocket?.close();
+ this.websocket = undefined;
+ }
+
+ private handleBleDisconnect() {
+ console.log("disconnected!!!");
+ for (const callback of this.disconnectCallbacks) callback();
+ for (const { target, event, listener } of this.subscriptions) target.removeEventListener(event, listener);
+ }
+
+ onDisconnect(...callbacks: (() => void)[]) {
+ for (const callback of callbacks) this.disconnectCallbacks.add(callback);
+ }
+
+ /**
+ *
+ * @param type
+ * @returns
+ */
+ async enableSensor(type: T) {
+ if (this.sensorState[type]) return;
+ await this.sendBLECommand(command.enable, sensor[type]);
+ await new Promise((resolve) => this.onSensor.once(type, resolve));
+ this.sensorState[type] = true;
+ }
+
+ /**
+ *
+ */
+ async disableSensor(type: T) {
+ if (!this.sensorState[type]) return;
+ await this.sendBLECommand(command.disable, sensor[type]);
+ this.sensorState[type] = false;
+ }
+
+ /**
+ *
+ * @param type
+ * @returns
+ */
+ async getSensorReading(type: T): Promise {
+ await this.enableSensor(type); // should this be automatic?
+ return this.sensorData[type];
+ }
+
+ /**
+ *
+ * @param type
+ * @returns
+ */
+ getSensorReadingImmediately(type: T): SensorData[T] {
+ this.enableSensor(type); // should this be automatic?
+ return this.sensorData[type];
+ }
+
+ /**
+ * @typedef {Object} MotorStepRequest
+ * @property {number} steps - The number of steps the motor should move.
+ * @property {number} stepsPerSecond - The speed of the motor in steps per second.
+ */
+
+ /**
+ *
+ * @param type
+ * @param left {MotorStepRequest}
+ * @param right
+ */
+ async motorCommand(type: "steps", left: MotorStepRequest, right: MotorStepRequest);
+ /**
+ *
+ * @param type
+ * @param radius
+ * @param degrees
+ */
+ async motorCommand(type: "arc", radius: number, degrees: number);
+ /**
+ *
+ * @param type
+ */
+ async motorCommand(type: "stop");
+ async motorCommand(type: MotorCommand, ...args: any[]) {
+
+ const { pending: { motor: pending } } = this;
+ switch (type) {
+ case "steps": {
+ if (pending) await pending;
+ const [left, right] = args as MotorStepRequest[];
+ return await this.untilFinishedPending("motor", new Promise(async (resolve) => {
+ this.isStopped = false;
+ await this.sendBLECommand(command.motor, left.steps, right.steps, left.stepsPerSecond, right.stepsPerSecond);
+ this.onMotor.once(events.stop, resolve);
+ }));
+ }
+ case "arc": {
+ if (pending) await pending;
+ const [radius, degrees] = args as number[];
+ return await this.untilFinishedPending("motor", new Promise(async (resolve) => {
+ this.isStopped = false;
+ await this.sendBLECommand(command.arc, radius, degrees);
+ this.onMotor.once(events.stop, resolve);
+ }));
+ }
+ case "stop":
+ if (this.isStopped) return;
+ return await this.untilFinishedPending("motor", new Promise(async (resolve) => {
+ await this.sendBLECommand(command.motor, "s");
+ this.onMotor.once(events.stop, resolve);
+ }));
+ }
+ }
+
+ async penCommand(direction: "up" | "down") {
+ await this.sendBLECommand(command.pen, direction === "up" ? 0 : 45);
+ }
+
+ /**
+ *
+ */
+ async lowPowerMode() {
+ await this.sendBLECommand(command.lowPower);
+ }
+
+ async getIPAddress() {
+ const self = this;
+ const interval = setTimeout(() => this.sendBLECommand(command.network), 1000);
+ const ip = await new Promise(async (resolve) => {
+ this.onNetwork.once(events.connect, () => resolve(self.connection.ip));
+ this.onNetwork.once(events.disconnect, () => resolve(null));
+ });
+ console.log(`Got ip: ${ip}`);
+ clearTimeout(interval);
+ return ip;
+ }
+
+ async testIP(ip: string) {
+ if (!ip) return false;
+ const controller = new AbortController();
+ const timeout = setTimeout(() => controller.abort(), 2000);
+ try {
+ const resp = await fetch(`http://${ip}:${port.camera}/${endpoint.video}`, { signal: controller.signal });
+ return resp.ok;
+ }
+ catch {
+ return false;
+ }
+ finally {
+ clearTimeout(timeout);
+ }
+ }
+
+ setIP(ip: string) {
+ this.connection ??= { ip };
+ this.saveIP(ip);
+ return this.connection.ip = ip;
+ }
+
+
+ /**
+ *
+ * @param ssid
+ * @param password
+ */
+ async connectToWifi(credentials: NetworkCredentials) {
+ if (credentials.ipOverride) {
+ msg("Testing stored IP address", "warning")
+ const validIP = await this.testIP(credentials.ipOverride);
+ msg(
+ validIP ? "Validated stored IP address" : "Stored IP address could not be reached",
+ validIP ? "success" : "warning"
+ )
+ if (validIP) return this.setIP(credentials.ipOverride);
+ }
+
+ msg("Asking doodlebot for it's IP", "warning");
+
+ let ip = await this.getIPAddress();
+
+ if (ip) {
+ if (ip === localIp) {
+ msg("Doodlebot IP is local, not valid", "warning");
+ }
+ else {
+ msg("Testing Doodlebot's reported IP address", "warning");
+ const validIP = await this.testIP(ip);
+ msg(
+ validIP ? "Validated Doodlebot's IP address" : "Doodlebot's IP address could not be reached",
+ validIP ? "success" : "warning"
+ )
+ if (validIP) return this.setIP(ip);
+ }
+ }
+ else {
+ msg("Could not retrieve IP address from doodlebot", "error")
+ }
+
+ // return new Promise(async (resolve) => {
+ // const self = this;
+ // const { device } = this;
+
+ // const reconnectToBluetooth = async () => {
+ // this.requestBluetooth(async (ble) => {
+ // msg("Reconnected to doodlebot", "success");
+ // const { robot, services } = await Doodlebot.getBLE(ble);
+ // self.attachToBLE(robot, services);
+ // device.removeEventListener("gattserverdisconnected", reconnectToBluetooth);
+ // msg("Waiting to issue connect command", "warning");
+ // await new Promise((resolve) => setTimeout(resolve, 5000));
+ // msg("Testing doodlebot's IP after reconnect", "warning");
+ // const ip = await self.getIPAddress();
+ // msg(
+ // ip === localIp ? "Doodlebot's IP is local, not valid" : "Doodlebot's IP is valid",
+ // ip === localIp ? "warning" : "success"
+ // )
+ // resolve(this.setIP(ip));
+ // });
+ // }
+
+ // device.addEventListener("gattserverdisconnected", reconnectToBluetooth);
+
+ // msg("Attempting to connect to wifi", "warning");
+
+ // await this.sendBLECommand(command.wifi, credentials.ssid, credentials.password);
+ // });
+ }
+
+ /**
+ *
+ * @param credentials
+ */
+ async connectToWebsocket(ip: string) {
+ this.websocket = makeWebsocket(ip, port.websocket);
+ await this.untilFinishedPending("websocket", new Promise((resolve) => {
+ const resolveAndRemove = () => {
+ console.log("Connected to websocket");
+ this.websocket.removeEventListener("open", resolveAndRemove);
+ resolve();
+ }
+ this.websocket.addEventListener("open", resolveAndRemove);
+ this.websocket.addEventListener("message", this.onWebsocketMessage.bind(this));
+ }));
+ }
+
+ async connectionWorkflow(credentials: NetworkCredentials) {
+ await this.connectToWifi(credentials);
+ await this.connectToWebsocket(this.connection.ip);
+ this.detector = new LineDetector(this.connection.ip);
+ //await this.connectToImageWebSocket(this.connection.ip);
+ }
+
+ async getImageStream() {
+ if (this.pending["websocket"]) await this.pending["websocket"];
+ if (!this.connection.ip) return;
+ const image = document.createElement("img");
+ image.src = `http://${this.connection.ip}:${port.camera}/${endpoint.video}`;
+ image.crossOrigin = "anonymous";
+ await new Promise((resolve) => image.addEventListener("load", resolve));
+ return image;
+ }
+
+ async connectToImageWebSocket(ip: string) {
+ // Create a WebSocket connection
+ this.websocket = new WebSocket(`ws://${ip}:${port.camera}`);
+
+ // Return a promise that resolves when the WebSocket is connected
+ await this.untilFinishedPending("image", new Promise((resolve, reject) => {
+ const onOpen = () => {
+ console.log("Connected to WebSocket for image stream");
+ this.websocket.removeEventListener("open", onOpen);
+ resolve();
+ };
+
+ const onError = (err: Event) => {
+ console.error("WebSocket error: ", err);
+ reject(err);
+ };
+
+ this.websocket.addEventListener("open", onOpen);
+ this.websocket.addEventListener("error", onError);
+
+ // Handle each message (which could be an image frame)
+ this.websocket.addEventListener("message", (event) => this.onWebSocketImageMessage(event));
+ }));
+ }
+
+ // Handle incoming image data from WebSocket
+ onWebSocketImageMessage(event: MessageEvent) {
+ // Assuming the message contains binary image data (e.g., Blob or ArrayBuffer)
+ const imageBlob = event.data;
+
+ // Create an image element to render the received frame
+ const image = document.createElement("img");
+ const url = URL.createObjectURL(imageBlob);
+ image.src = url;
+
+ // Wait until the image loads and then perform some action
+ image.addEventListener("load", () => {
+ console.log("Image frame received and rendered");
+ this.onImageReceived(image); // Process or display the image
+ URL.revokeObjectURL(url); // Clean up the URL object
+ });
+ }
+
+ // Example image processing logic
+ onImageReceived(image: HTMLImageElement) {
+ // Perform operations on the image, e.g., display or analyze the frame
+ console.log("Processing image from WebSocket stream");
+ document.body.appendChild(image); // Example: Display the image in the document
+ }
+
+ prependUntilTarget = (line) => {
+ const targetX = line[0][0];
+ const targetY = line[0][1];
+ const startX = line[0][0];
+ const startY = 0; // Start slightly below targetY
+
+
+ const incrementX = 0.01; // Small step for x
+ let x = startX;
+ let y = startY;
+
+ const newSegment = [];
+ while (y < targetY) {
+ newSegment.push([x, y]);
+ y += incrementX; // Increment y based on slope
+ }
+
+ // Prepend the new segment to the beginning of line
+ line.unshift(...newSegment);
+ return line;
+ }
+
+ printLine() {
+ console.log(this.wholeString);
+ }
+
+ wholeString = "export const allLines = [";
+ cumulativeLine = "export const cumulativeLines = [";
+
+ motorCommands;
+ bezierPoints;
+ line;
+ lineCounter = 0;
+ detector;
+ iterationNumber = 0;
+
+ deepEqual = (a, b) => {
+ if (a === b) return true;
+ if (Array.isArray(a) && Array.isArray(b)) {
+ return a.length === b.length && a.every((val, i) => this.deepEqual(val, b[i]));
+ }
+ if (typeof a === 'object' && typeof b === 'object') {
+ const keysA = Object.keys(a), keysB = Object.keys(b);
+ return keysA.length === keysB.length && keysA.every(key => this.deepEqual(a[key], b[key]));
+ }
+ return false;
+ };
+
+ async followLine() {
+ let first = true;
+ let iterations = 2;
+
+ let prevInterval;
+ let t = { aT: 0.3 };
+ let lastTime: number;
+
+ console.log(this.connection.ip);
+ console.log(this.detector);
+ this.detector = new LineDetector(this.connection.ip);
+ await this.detector.initialize(this);
+ let prevLine = [];
+ let add = 0;
+ while (true) {
+ try {
+ let lineData = this.detector.returnLine();
+ lineData = lineData.map(value => [640 - value[0], 480 - value[1]])
+ const firstQuadrant = lineData.filter(value => value[1] < 10);
+ lineData = firstQuadrant.length > 0 ? lineData.filter(value => value[1] < 350) : [];
+ console.log("FIRST QUADRANT", firstQuadrant.length);
+ // Process line data
+ lineData = lineData.sort((a, b) => a[1] - b[1]);
+
+ // Debugging statements
+ this.wholeString = this.wholeString + `${JSON.stringify(lineData)},`;
+ this.printLine();
+ this.lineCounter += 1;
+
+ let newMotorCommands;
+
+ if (first) {
+ ({ motorCommands: newMotorCommands, bezierPoints: this.bezierPoints, line: this.line } = followLine(
+ lineData,
+ lineData,
+ prevLine,
+ [],
+ [],
+ [],
+ add,
+ true
+ ));
+ } else {
+ ({ motorCommands: newMotorCommands, bezierPoints: this.bezierPoints, line: this.line } = followLine(
+ this.line,
+ lineData,
+ prevLine,
+ this.motorCommands,
+ [prevInterval/2],
+ [t.aT],
+ add,
+ false
+ ));
+ }
+
+ lastTime = Date.now();
+
+ // Debugging statement
+ this.cumulativeLine = this.cumulativeLine + `${JSON.stringify(this.line)},`;
+ console.log(this.cumulativeLine);
+
+ let waitTime = prevLine.length < 100 ? 190 : 200;
+ if (newMotorCommands[0].angle > 10) {
+ newMotorCommands[0].angle = 10;
+ } else if (newMotorCommands[0].angle < -10) {
+ newMotorCommands[0].angle = -10;
+ }
+
+
+ if (this.iterationNumber % iterations == 0) {
+ newMotorCommands[0].angle = this.limitArcLength(newMotorCommands[0].angle, newMotorCommands[0].radius, 2);
+ // newMotorCommands[0].angle = this.increaseArcLength(newMotorCommands[0].angle, newMotorCommands[0].radius, );
+ if (newMotorCommands[0].radius < 10) {
+ newMotorCommands[0].angle = this.limitArcLength(newMotorCommands[0].angle, newMotorCommands[0].radius, 1.5);
+ }
+
+ if (this.motorCommands && !(this.motorCommands[0].distance > 0)) {
+ if (this.motorCommands) {
+ t = calculateArcTime(this.motorCommands[0].radius, this.motorCommands[0].angle, newMotorCommands[0].radius, newMotorCommands[0].angle);
+ } else {
+ t = calculateArcTime(0, 0, newMotorCommands[0].radius, newMotorCommands[0].angle);
+ }
+ }
+
+ this.motorCommands = newMotorCommands;
+ for (const command of this.motorCommands) {
+ let { radius, angle } = command;
+
+ if ((lineData.length == 0 || !this.deepEqual(lineData, prevLine))) {
+ if (command.distance > 0) {
+ this.sendWebsocketCommand("m", Math.round(12335.6*command.distance), Math.round(12335.6*command.distance), 500, 500);
+ } else {
+ this.sendBLECommand("t", radius, angle);
+ }
+ }
+ if (this.deepEqual(lineData, prevLine) && lineData.length > 0) {
+ console.log("LAG");
+ }
+
+ }
+ if (prevLine.length < 100 && lineData.length < 100) {
+ add = add + 1;
+ } else {
+ add = 0;
+ }
+ }
+
+ await new Promise((resolve) => setTimeout(resolve, waitTime));
+ prevInterval = waitTime/1000;
+ first = false;
+ prevLine = lineData;
+ } catch (error) {
+ console.error("Error in followLine loop:", error);
+ break; // Optionally, break the loop on error
+ }
+ this.iterationNumber = this.iterationNumber + 1;
+ }
+ }
+
+
+ limitArcLength(angle: number, radius: number, maxArcLength: number = 2): number {
+ // Calculate the max allowable angle
+ let negative = true;
+ if (angle > 0) {
+ negative = false;
+ }
+ angle = Math.abs(angle);
+ const maxAngle = (maxArcLength * 180) / ((radius + 2.93) * Math.PI);
+
+ const returnAngle = Math.min(angle, maxAngle);
+ // Return the limited angle
+ return negative ? returnAngle * -1 : returnAngle;
+ }
+
+ increaseArcLength(angle: number, radius: number, maxArcLength: number = 2): number {
+ // Calculate the max allowable angle
+ let negative = true;
+ if (angle > 0) {
+ negative = false;
+ }
+ angle = Math.abs(angle);
+ const maxAngle = (maxArcLength * 180) / ((radius + 2.93) * Math.PI);
+
+ // Return the limited angle
+ return negative ? maxAngle*-1 : maxAngle;
+ }
+
+ private setupAudioStream() {
+ if (!this.connection.ip) return false;
+
+ if (this.audioSocket) return true;
+
+ const socket = new WebSocket(`ws://${this.connection.ip}:${port.audio}`);
+ const self = this;
+
+ socket.onopen = function (event) {
+ console.log('WebSocket connection established');
+ socket.send(self.encoder.encode('(1)'));
+ };
+
+ socket.onerror = function (error) {
+ console.error('WebSocket Error:', error);
+ // TODO: automatically restart socket
+ };
+
+ socket.onclose = function () {
+ console.log('WebSocket connection closed');
+ };
+
+ socket.onmessage = async function (event) {
+ if (self.audioCallbacks.size === 0) return;
+
+ const data = JSON.parse(event.data);
+
+ const interleaved = await base64ToInt32Array(data.audio_data);
+ const chunkSize = interleaved.length / 2;
+
+ const mono = new Float32Array(chunkSize);
+
+ for (let i = 0, j = 0; i < interleaved.length; i += 2, j++) {
+ mono[j] = interleaved[i] / Max32Int;
+ }
+
+ for (const callback of self.audioCallbacks) callback(mono);
+ };
+
+ return true;
+ }
+
+ parseWavHeader(uint8Array) {
+ const dataView = new DataView(uint8Array.buffer);
+ // Extract sample width, number of channels, and sample rate
+ const sampleWidth = dataView.getUint16(34, true) / 8; // Sample width in bytes (16-bit samples = 2 bytes, etc.)
+ const channels = dataView.getUint16(22, true); // Number of channels
+ const rate = dataView.getUint32(24, true); // Sample rate
+ const byteRate = dataView.getUint32(28, true); // Byte rate
+ const blockAlign = dataView.getUint16(32, true); // Block align
+ const dataSize = dataView.getUint32(40, true); // Size of the data chunk
+ const frameSize = blockAlign; // Size of each frame in bytes
+ return {
+ sampleWidth,
+ channels,
+ rate,
+ frameSize,
+ dataSize
+ };
+ }
+ splitIntoChunks(uint8Array, framesPerChunk) {
+ const headerInfo = this.parseWavHeader(uint8Array);
+ const { frameSize } = headerInfo;
+ const chunkSize = framesPerChunk * frameSize; // Number of bytes per chunk
+ const chunks = [];
+ // Skip the header (typically 44 bytes)
+ const dataStart = 44;
+ for (let i = dataStart; i < uint8Array.length; i += chunkSize) {
+ const chunk = uint8Array.slice(i, i + chunkSize);
+ chunks.push(chunk);
+ }
+ return chunks;
+ }
+ async sendAudioData(uint8Array: Uint8Array) {
+ let CHUNK_SIZE = 1024;
+ let ip = this.connection.ip;
+ const ws = makeWebsocket(ip, '8877');
+ ws.onopen = () => {
+ console.log('WebSocket connection opened');
+ let { sampleWidth, channels, rate } = this.parseWavHeader(uint8Array);
+ let first = "(1," + String(sampleWidth) + "," + String(channels) + "," + String(rate) + ")";
+ console.log(first);
+ ws.send(first);
+ let chunks = this.splitIntoChunks(uint8Array, CHUNK_SIZE);
+ let i = 0;
+ async function sendNextChunk() {
+ if (i >= chunks.length) {
+ console.log('All data sent');
+ ws.close();
+ return;
+ }
+ const chunk = chunks[i];
+ const binaryString = Array.from(chunk).map((byte: any) => String.fromCharCode(byte)).join('');;
+ const base64Data = btoa(binaryString);
+ const jsonData = JSON.stringify({ audio_data: base64Data });
+ ws.send(jsonData);
+ i = i + 1;
+ sendNextChunk();
+ }
+ sendNextChunk();
+ };
+ ws.onerror = (error) => {
+ console.error('WebSocket error:', error);
+ };
+ ws.onmessage = (message) => {
+ console.log(message);
+ }
+ ws.onclose = () => {
+ console.log('WebSocket connection closed');
+ };
+ }
+
+ recordAudio(numSeconds = 1) {
+ if (!this.setupAudioStream()) return;
+
+ const context: AudioContext = new (window.AudioContext || (window as any).webkitAudioContext)();
+ const sampleRate = 48000;
+ const totalSamples = sampleRate * numSeconds;
+ const buffer = context.createBuffer(1, totalSamples, sampleRate);
+
+ let index = 0;
+ let samples = 0;
+ const startTime = Date.now();
+ const callbacks = this.audioCallbacks;
+
+ return new Promise<{ context: AudioContext, buffer: AudioBuffer }>((resolve) => {
+ const accumulate = (chunk: Float32Array) => {
+ // Check if we've exceeded our time limit
+ if (Date.now() - startTime >= numSeconds * 1000) {
+ callbacks.delete(accumulate);
+ return resolve({ context, buffer });
+ }
+
+ if (samples + chunk.length > totalSamples) {
+ // Only copy what we need to fill the buffer
+ const remainingSamples = totalSamples - samples;
+ buffer.copyToChannel(chunk.slice(0, remainingSamples), 0, samples);
+ samples = totalSamples;
+ callbacks.delete(accumulate);
+ return resolve({ context, buffer });
+ }
+
+ buffer.copyToChannel(chunk, 0, samples);
+ samples += chunk.length;
+ index++;
+ }
+ callbacks.add(accumulate);
+ });
+ }
+
+ async display(type: DisplayKey) {
+ const value = display[type];
+ await this.sendWebsocketCommand(command.display, value);
+ }
+
+ async displayText(text: string) {
+ await this.sendWebsocketCommand(command.display, "t", text);
+ }
+
+ /**
+ * NOTE: Consider making private
+ * @param command
+ * @param args
+ * @returns
+ */
+ sendBLECommand(command: Command, ...args: (string | number)[]) {
+ return this.ble.send(this.formCommand(command, ...args));
+ }
+
+ /**
+ * NOTE: Consider making private
+ * @param command
+ * @param args
+ */
+ sendWebsocketCommand(command: Command, ...args: (string | number)[]) {
+ const msg = this.formCommand(command, ...args);
+ console.log("sending websocket message", msg);
+ this.websocket.send(this.encoder.encode(msg));
+ }
+
+ async untilFinishedPending(type: TKey, promise: Pending[TKey]) {
+ this.pending[type] = promise;
+ const value = await promise;
+ this.pending[type] = undefined;
+ return value;
+ }
+}
\ No newline at end of file
diff --git a/extensions/src/doodlebot/LineDetection.ts b/extensions/src/doodlebot/LineDetection.ts
new file mode 100644
index 000000000..ad8cfd131
--- /dev/null
+++ b/extensions/src/doodlebot/LineDetection.ts
@@ -0,0 +1,303 @@
+import { endpoint, port } from "./enums";
+import Doodlebot from "./Doodlebot";
+
+const debug = {
+ info: (msg: string, ...args: any[]) => console.log(`[LineDetector] ${msg}`, ...args),
+ warn: (msg: string, ...args: any[]) => console.warn(`[LineDetector] ${msg}`, ...args),
+ error: (msg: string, ...args: any[]) => console.error(`[LineDetector] ${msg}`, ...args),
+ time: (label: string) => console.time(`[LineDetector] ${label}`),
+ timeEnd: (label: string) => console.timeEnd(`[LineDetector] ${label}`)
+};
+
+export class LineDetector {
+ private width: number;
+ private height: number;
+ private canvas: HTMLCanvasElement;
+ private ctx: CanvasRenderingContext2D;
+ private lastDetectedLine: number[][];
+ private frameCount: number;
+ private allCoordinates: number[][][];
+ private threshold: number = 70;
+ private collectLine: boolean = false;
+ private isProcessing: boolean = false;
+ private lastProcessTime: number = 0;
+ private readonly MIN_PROCESS_INTERVAL = 100;
+ private imageStream: HTMLImageElement | null = null;
+
+ constructor(private raspberryPiIp: string, imageStream: HTMLImageElement, width = 640, height = 480) {
+ debug.info('Initializing LineDetector', { raspberryPiIp, width, height });
+
+ this.width = width;
+ this.height = height;
+ this.canvas = document.createElement('canvas');
+ this.canvas.width = width;
+ this.canvas.height = height;
+
+ const ctx = this.canvas.getContext('2d', { willReadFrequently: true });
+ if (!ctx) {
+ debug.error('Failed to get canvas context');
+ throw new Error('Failed to get canvas context');
+ }
+
+ this.ctx = ctx;
+ this.lastDetectedLine = [];
+ this.frameCount = 0;
+ this.allCoordinates = [];
+
+ debug.info('LineDetector initialized successfully');
+ this.imageStream = imageStream;
+ }
+
+ async initialize(doodlebot: Doodlebot): Promise {
+ debug.info('Initializing image stream');
+ try {
+ this.imageStream = await doodlebot.getImageStream();
+ if (!this.imageStream) {
+ throw new Error('Failed to get image stream from Doodlebot');
+ }
+ debug.info('Successfully initialized image stream');
+ } catch (error) {
+ debug.error('Error initializing image stream:', error);
+ throw error;
+ }
+ }
+
+ private processImageData(imageData: ImageData): number[][] {
+ debug.time('processImageData');
+
+ const lineCoordinates: number[][] = [];
+ const maxY = Math.max(this.height, 400);
+ const data = imageData.data;
+ const width = this.width;
+
+ for (let y = 100; y < maxY; y += 2) {
+ const rowOffset = y * width * 4;
+ for (let x = 0; x < width; x += 2) {
+ const index = rowOffset + (x * 4);
+ if (data[index] < this.threshold &&
+ data[index + 1] < this.threshold &&
+ data[index + 2] < this.threshold) {
+ lineCoordinates.push([x, y]);
+ }
+ }
+ }
+
+ debug.info(`Found ${lineCoordinates.length} line points`);
+ debug.timeEnd('processImageData');
+
+ return lineCoordinates.length > 0
+ ? lineCoordinates.sort((a, b) => a[1] - b[1])
+ : [];
+ }
+
+ returnLine() {
+ const now = Date.now();
+
+ if (this.isProcessing) {
+ debug.warn('Detection already in progress, returning last result');
+ return this.lastDetectedLine;
+ }
+
+ if ((now - this.lastProcessTime) < this.MIN_PROCESS_INTERVAL) {
+ debug.warn('Called too soon after last detection, returning last result');
+ return this.lastDetectedLine;
+ }
+
+
+ debug.time('detectLine');
+ this.isProcessing = true;
+ let attempt = 0;
+
+ try {
+
+ try {
+
+ debug.info('Clearing canvas and drawing new image');
+ this.ctx.clearRect(0, 0, this.width, this.height);
+ this.ctx.drawImage(this.imageStream!, 0, 0, this.width, this.height);
+
+ debug.info('Getting image data from canvas');
+ const imageData = this.ctx.getImageData(0, 0, this.width, this.height);
+
+ debug.info('Processing image data');
+ const lineCoordinates = this.processImageData(imageData);
+
+ if (lineCoordinates.length > 0) {
+ debug.info('Line detected successfully', {
+ points: lineCoordinates.length,
+ firstPoint: lineCoordinates[0],
+ lastPoint: lineCoordinates[lineCoordinates.length - 1]
+ });
+
+ this.lastDetectedLine = lineCoordinates;
+
+ if (this.collectLine) {
+ this.allCoordinates.push(lineCoordinates);
+ this.frameCount++;
+ this.collectLine = false;
+ debug.info('Line collected, frame count:', this.frameCount);
+ }
+
+ this.lastProcessTime = now;
+ this.isProcessing = false;
+ debug.timeEnd('detectLine');
+ return lineCoordinates;
+ } else {
+ this.lastDetectedLine = lineCoordinates;
+ }
+
+ debug.warn('No line detected in this attempt');
+ attempt++;
+ const backoffTime = 100 * Math.pow(2, attempt);
+ debug.info(`Waiting ${backoffTime}ms before next attempt`);
+
+ } catch (error) {
+ debug.error(`Processing attempt ${attempt + 1} failed:`, error);
+ attempt++;
+
+
+
+ const backoffTime = 100 * Math.pow(2, attempt);
+ debug.info(`Waiting ${backoffTime}ms before retry`);
+ }
+
+ } catch (error) {
+ debug.error('Error getting image stream:', error);
+ this.isProcessing = false;
+ debug.timeEnd('detectLine');
+ return this.lastDetectedLine;
+ }
+
+ this.isProcessing = false;
+ debug.timeEnd('detectLine');
+ return this.lastDetectedLine;
+ }
+
+ async detectLine(doodlebot: Doodlebot, retries: number = 3): Promise {
+ const now = Date.now();
+
+ if (this.isProcessing) {
+ debug.warn('Detection already in progress, returning last result');
+ return this.lastDetectedLine;
+ }
+
+ if ((now - this.lastProcessTime) < this.MIN_PROCESS_INTERVAL) {
+ debug.warn('Called too soon after last detection, returning last result');
+ return this.lastDetectedLine;
+ }
+
+ if (!this.imageStream) {
+ debug.warn('Image stream not initialized, initializing now');
+ await this.initialize(doodlebot);
+ }
+
+ debug.time('detectLine');
+ this.isProcessing = true;
+ let attempt = 0;
+
+ try {
+ while (attempt < retries) {
+ try {
+ debug.info(`Detection attempt ${attempt + 1}/${retries}`);
+
+ debug.info('Clearing canvas and drawing new image');
+ this.ctx.clearRect(0, 0, this.width, this.height);
+ this.ctx.drawImage(this.imageStream!, 0, 0, this.width, this.height);
+
+ debug.info('Getting image data from canvas');
+ const imageData = this.ctx.getImageData(0, 0, this.width, this.height);
+
+ debug.info('Processing image data');
+ const lineCoordinates = this.processImageData(imageData);
+
+ if (lineCoordinates.length > 0) {
+ debug.info('Line detected successfully', {
+ points: lineCoordinates.length,
+ firstPoint: lineCoordinates[0],
+ lastPoint: lineCoordinates[lineCoordinates.length - 1]
+ });
+
+ this.lastDetectedLine = lineCoordinates;
+
+ if (this.collectLine) {
+ this.allCoordinates.push(lineCoordinates);
+ this.frameCount++;
+ this.collectLine = false;
+ debug.info('Line collected, frame count:', this.frameCount);
+ }
+
+ this.lastProcessTime = now;
+ this.isProcessing = false;
+ debug.timeEnd('detectLine');
+ return lineCoordinates;
+ }
+
+ debug.warn('No line detected in this attempt');
+ attempt++;
+ const backoffTime = 100 * Math.pow(2, attempt);
+ debug.info(`Waiting ${backoffTime}ms before next attempt`);
+ await new Promise(resolve => setTimeout(resolve, backoffTime));
+
+ } catch (error) {
+ debug.error(`Processing attempt ${attempt + 1} failed:`, error);
+ attempt++;
+
+ if (attempt === retries) {
+ debug.warn('Max retries reached, returning last known good result');
+ this.isProcessing = false;
+ debug.timeEnd('detectLine');
+ return this.lastDetectedLine;
+ }
+
+ const backoffTime = 100 * Math.pow(2, attempt);
+ debug.info(`Waiting ${backoffTime}ms before retry`);
+ await new Promise(resolve => setTimeout(resolve, backoffTime));
+ }
+ }
+ } catch (error) {
+ debug.error('Error getting image stream:', error);
+ this.isProcessing = false;
+ debug.timeEnd('detectLine');
+ return this.lastDetectedLine;
+ }
+
+ this.isProcessing = false;
+ debug.timeEnd('detectLine');
+ return this.lastDetectedLine;
+ }
+
+ setThreshold(value: number) {
+ const oldThreshold = this.threshold;
+ this.threshold = Math.max(0, Math.min(255, value));
+ debug.info('Threshold updated:', { old: oldThreshold, new: this.threshold });
+ }
+
+ getLastDetectedLine(): number[][] {
+ debug.info('Returning last detected line', {
+ points: this.lastDetectedLine.length
+ });
+ return this.lastDetectedLine;
+ }
+
+ startCollecting() {
+ debug.info('Starting line collection');
+ this.collectLine = true;
+ }
+
+ stopCollecting() {
+ debug.info('Stopping line collection');
+ this.collectLine = false;
+ }
+
+ clearHistory() {
+ debug.info('Clearing detection history');
+ this.allCoordinates = [];
+ this.frameCount = 0;
+ }
+}
+
+export async function createLineDetector(raspberryPiIp: string, imageStream: HTMLImageElement): Promise<(doodlebot: Doodlebot) => Promise> {
+ debug.info('Creating new LineDetector instance');
+ const detector = new LineDetector(raspberryPiIp, imageStream);
+ return (doodlebot: Doodlebot) => detector.detectLine(doodlebot);
+}
\ No newline at end of file
diff --git a/extensions/src/doodlebot/LineFollowing.ts b/extensions/src/doodlebot/LineFollowing.ts
new file mode 100644
index 000000000..2b5a4dd14
--- /dev/null
+++ b/extensions/src/doodlebot/LineFollowing.ts
@@ -0,0 +1,728 @@
+import * as Spline from "cubic-spline";
+import * as Bezier from "bezier-js";
+import { rotateCurve } from "curve-matcher";
+import { procrustes } from "./Procrustes";
+import { type Point, type PointObject, type ProcrustesResult, type RobotPosition, type Command, calculateLineError, applyTranslation, cutOffLineAtOverlap, distanceBetweenPoints, approximateBezierWithArc } from './LineHelper';
+
+// CONSTANTS
+const maxDistance = 100;
+const epsilon = 1;
+const bezierSamples = 2;
+const controlLength = .01;
+let lookahead = .05;
+let start = 0.001;
+const previousIncrement = 0.005;
+
+const imageDimensions = [640, 480];
+const horizontalFOV = 53.4;
+const verticalFOV = 41.41;
+const cameraHeight = 0.098;
+const tiltAngle = 30;
+
+
+
+function cutOffLineOnDistance(line: Point[], maxDistance: number) {
+ let filteredLine = [line[0]]; // Start with the first point
+
+ for (let i = 1; i < line.length; i++) {
+ const point1 = line[i - 1];
+ const point2 = line[i];
+ const distance = distanceBetweenPoints(point1, point2);
+
+ // If the distance exceeds the threshold, stop adding points
+ if (distance > maxDistance) {
+ break;
+ }
+
+ filteredLine.push(point2); // Add the current point if within distance
+ }
+
+ return filteredLine;
+}
+
+function findPointAtDistanceWithIncrements(spline: Spline, increment: number, desiredDistance: number): number {
+ let totalDistance = 0;
+ const xValues = spline.xs;
+
+ // Iterate through each pair of xValues in the array
+ for (let i = 0; i < xValues.length - 1; i++) {
+ let currentX = xValues[i];
+ const nextX = xValues[i + 1];
+
+ // Step through each segment in increments, adjusting for direction
+ while (currentX < nextX) {
+ const nextXIncrement = currentX + increment; // Increment or decrement by step size
+ const currentY = spline.at(currentX);
+ const nextY = spline.at(nextXIncrement);
+
+ // Calculate distance between current and next increment
+ if (!isNaN(nextY)) {
+ const distance = distanceBetweenPoints([currentX, currentY], [nextXIncrement, nextY]);
+
+ totalDistance += distance;
+ if (totalDistance >= desiredDistance) {
+ return nextXIncrement;
+ }
+ currentX = nextXIncrement;
+
+ // Stop if we overshoot the next point
+ if (currentX > nextX) {
+ currentX = nextX;
+ }
+ } else {
+ break;
+ }
+
+ }
+ }
+
+ // If the desired distance is beyond all xValues, return the last point
+ return xValues[xValues.length - 1];
+}
+
+
+function medianPoint(points: number[][]): number[] {
+ if (points.length === 0) return [0, 0];
+
+ // Sort points by x-coordinate
+ const sortedPoints = points.sort((a, b) => a[0] - b[0]);
+
+ // Return the median point (middle point in sorted array)
+ const middle = Math.floor(sortedPoints.length / 2);
+ return sortedPoints[middle];
+}
+
+
+function groupByYInterval(points: Point[], intervalSize: number): Point[] {
+ const groupedPoints: Map = new Map();
+
+ for (const point of points) {
+ const [x, y] = point;
+
+ // Find the interval the current y-value falls into
+ const interval = Math.floor(y / intervalSize) * intervalSize;
+
+ // Add the point to the appropriate group
+ if (!groupedPoints.has(interval)) {
+ groupedPoints.set(interval, []);
+ }
+ groupedPoints.get(interval)?.push(point);
+ }
+
+ // For each interval, take the median point based on the x-coordinate
+ const medianPoints: number[][] = [];
+ groupedPoints.forEach((group) => {
+ const median = medianPoint(group);
+ medianPoints.push(median);
+ });
+
+ // Sort by y-value (ascending)
+ return medianPoints.sort((a, b) => a[1] - b[1]);
+}
+
+
+function rdpSimplify(points: Point[], epsilon: number): Point[] {
+ if (points.length < 3) {
+ return points;
+ }
+
+ let maxDistance = 0;
+ let index = 0;
+
+ // Find the point with the maximum distance from the line formed by the first and last points.
+ for (let i = 1; i < points.length - 1; i++) {
+ const distance = perpendicularDistance(points[i], points[0], points[points.length - 1]);
+ if (distance > maxDistance) {
+ maxDistance = distance;
+ index = i;
+ }
+ }
+
+ // If the maximum distance is greater than epsilon, recursively simplify.
+ if (maxDistance > epsilon) {
+ const leftSegment = rdpSimplify(points.slice(0, index + 1), epsilon);
+ const rightSegment = rdpSimplify(points.slice(index), epsilon);
+
+ // Combine the results, excluding the duplicate point at the intersection.
+ return [...leftSegment.slice(0, -1), ...rightSegment];
+ } else {
+ // If no point is far enough, return the endpoints.
+ return [points[0], points[points.length - 1]];
+ }
+}
+
+
+function simplifyLine(points: Point[], epsilon: number, intervalSize: number): number[][] {
+ // Group points by y-interval and pick the median x-coordinate in each group
+ const medianPoints = groupByYInterval(points, intervalSize);
+
+ // Simplify the remaining points using Ramer-Douglas-Peucker
+ return rdpSimplify(medianPoints, epsilon);
+}
+
+
+function perpendicularDistance(point: number[], lineStart: number[], lineEnd: number[]): number {
+ const [x, y] = point;
+ const [x1, y1] = lineStart;
+ const [x2, y2] = lineEnd;
+
+ const num = Math.abs((y2 - y1) * x - (x2 - x1) * y + x2 * y1 - y2 * x1);
+ const den = Math.sqrt(Math.pow(y2 - y1, 2) + Math.pow(x2 - x1, 2));
+
+ return den === 0 ? 0 : num / den;
+}
+
+
+function getRobotPositionAfterArc(
+ command: Command,
+ initialPosition: RobotPosition,
+ percent: number
+) {
+ // Extract radius and angle from command
+ let { radius, angle: angleDegrees } = command;
+ const { x: initialX, y: initialY, angle: initialAngle } = initialPosition;
+
+ // Clamp percent between 0 and 1 to avoid invalid inputs
+ percent = Math.max(0, Math.min(1, percent));
+
+ // Determine the direction
+ const direction = angleDegrees < 0 ? "left" : "right";
+ const angleRadians = Math.abs(angleDegrees * (Math.PI / 180)); // Target angle in radians
+
+ // Scale radius if needed
+ radius = radius * 0.0254;
+
+ // Calculate the center of the circular path
+ const centerX = direction === "left"
+ ? initialX + radius * Math.cos(initialAngle)
+ : initialX - radius * Math.cos(initialAngle);
+ const centerY = direction === "left"
+ ? initialY + radius * Math.sin(initialAngle)
+ : initialY - radius * Math.sin(initialAngle);
+
+ // Calculate the angle traveled based on the percentage of the arc
+ const angleTraveled = angleRadians * percent;
+ const finalAngleTraveled = initialAngle + angleTraveled * (direction === "left" ? -1 : 1);
+
+ // Calculate the new position along the arc
+ const newX = direction === "left"
+ ? centerX - radius * Math.cos(finalAngleTraveled)
+ : centerX + radius * Math.cos(finalAngleTraveled);
+ const newY = direction === "left"
+ ? centerY - radius * Math.sin(finalAngleTraveled)
+ : centerY + radius * Math.sin(finalAngleTraveled);
+
+ // Return the updated position and angle
+ return {
+ x: newX,
+ y: newY,
+ angle: finalAngleTraveled
+ };
+}
+
+function showLineAboveY(line: Point[], yLimit: number) {
+ const newLine: Point[] = [];
+
+ // Only add point if y is above limit
+ for (let i = 0; i < line.length; i++) {
+ const [x, y] = line[i];
+ if (y >= yLimit) {
+ newLine.push([x, y]);
+ }
+ }
+
+ return newLine;
+}
+
+
+
+function showLineBelowY(line: Point[], yLimit: number) {
+ const newLine: Point[] = [];
+
+ // Only add point is y is below limit
+ for (let i = 0; i < line.length; i++) {
+ const [x, y] = line[i];
+ if (y <= yLimit) {
+ newLine.push([x, y]);
+ }
+ }
+
+ return newLine;
+}
+
+
+function smoothLine(line: Point[], windowSize = 3) {
+ const smoothedLine: Point[] = [];
+
+ for (let i = 0; i < line.length; i++) {
+ // Define the range of indices for the smoothing window
+ let start = Math.max(0, i - Math.floor(windowSize / 2));
+ let end = Math.min(line.length, i + Math.floor(windowSize / 2) + 1);
+
+ // Sum the x and y values within the window
+ let sumX = 0;
+ let sumY = 0;
+ for (let j = start; j < end; j++) {
+ sumX += line[j][0];
+ sumY += line[j][1];
+ }
+
+ // Push the averaged point to the smoothed line
+ let count = end - start;
+ smoothedLine.push([sumX / count, sumY / count]);
+ }
+
+ return smoothedLine;
+}
+
+
+function blendLines(entireLine: Point[], worldPoints: Point[], transitionLength: number = 3) {
+ // Start with the non-overlapping portion of entireLine
+ const blendedLine = [...cutOffLineAtOverlap(entireLine, worldPoints).line];
+
+ // If either line is too short for blending, return them joined directly
+ if (entireLine.length < transitionLength || worldPoints.length < transitionLength) {
+ return [...entireLine, ...worldPoints];
+ }
+
+ // Blend the transition region between entireLine and worldPoints
+ for (let i = 0; i < transitionLength; i++) {
+ const t = i / (transitionLength - 1); // Interpolation factor (0 to 1)
+ const [x1, y1] = entireLine[entireLine.length - transitionLength + i];
+ const [x2, y2] = worldPoints[i];
+
+ // Linearly interpolate between corresponding points
+ const blendedX = (1 - t) * x1 + t * x2;
+ const blendedY = (1 - t) * y1 + t * y2;
+
+ blendedLine.push([blendedX, blendedY]);
+ }
+
+ // Append remaining points from worldPoints after the transition region
+ blendedLine.push(...worldPoints.slice(transitionLength));
+
+ return blendedLine;
+}
+
+
+function prependUntilTarget(line2) {
+ let line = [...line2];
+ const targetX = line[0][0];
+ const targetY = line[0][1];
+ const startX = line[0][0];
+ const startY = 0; // Start slightly below targetY
+
+
+ const incrementX = 0.01; // Small step for x
+ let x = startX;
+ let y = startY;
+
+ const newSegment = [];
+ while (y < targetY) {
+ newSegment.push([x, y]);
+ y += incrementX; // Increment y based on slope
+ }
+
+ // Prepend the new segment to the beginning of line
+ line.unshift(...newSegment);
+ return line;
+}
+
+export function followLine(previousLine: Point[], pixels: Point[], previousPixels: Point[], previousCommands: Command[], previousTime: number[], totalTime: number[], add: number, first = false) {
+
+ let errorMultiplier = 1;
+ let worldPoints = [];
+
+ if (pixels.length > 100 || first) {
+
+ worldPoints = simplifyLine(pixels, epsilon, 0.1);
+ worldPoints = smoothLine(worldPoints, 2);
+
+ if (worldPoints[0] == undefined) {
+ worldPoints = [];
+ }
+ worldPoints = cutOffLineOnDistance(worldPoints, 50);
+ worldPoints = worldPoints.length > 0 ? worldPoints.map(point => pixelToGroundCoordinates(point)) : [];
+
+ const height = worldPoints.length > 0 ? worldPoints[worldPoints.length - 1][1] - worldPoints[0][1] : 0;
+
+ // TUNING VARIABLE
+ // We have enough points to conduct frame matching
+ if (height > .01) {
+ if (first || (previousPixels.length == 0)) {
+ previousLine = worldPoints
+ }
+ } else {
+ worldPoints = [];
+ }
+ }
+
+ previousLine = showLineAboveY(previousLine, 0);
+ let guessLine = previousLine;
+ let robotPosition = { x: 0, y: 0, angle: 0 };
+
+ // Use previous information as current information
+ if (worldPoints.length == 0) {
+ for (let i = 0; i < previousCommands.length; i++) {
+ const command = previousCommands[i];
+ if (command.radius == Infinity) {
+ robotPosition.y = robotPosition.y + command.distance * Math.min((previousTime[i] / totalTime[i]), 1);
+ } else {
+ robotPosition = getRobotPositionAfterArc({ angle: command.angle * -1, radius: command.radius, distance: 0 }, robotPosition, Math.min((previousTime[i] / totalTime[i]), 1));
+ }
+ }
+ // Ideally, we would rotate and translate previous line by robot's movement
+ // guessLine = rotateAndTranslateLine(previousLine, -1 * robotPosition.angle, [-1 * robotPosition.x, -1 * robotPosition.y]);
+
+ // TUNING VARIABLES
+ const previousLineStart = 0.02;
+ const previousLineLookahead = 0.06;
+
+ if ((previousLineLookahead + previousIncrement * add) < guessLine[guessLine.length - 1][1]) {
+ start = previousLineStart + previousIncrement * add;
+ lookahead = previousLineLookahead + previousIncrement * add;
+ } else {
+ start = Math.max(guessLine[0][1], guessLine[guessLine.length - 1][1] - .04);
+ lookahead = guessLine[guessLine.length - 1][1];
+ }
+
+ guessLine = showLineAboveY(guessLine, 0);
+ guessLine = previousLine;
+ errorMultiplier = errorMultiplier * 0;
+ }
+
+ if (guessLine.length == 0) {
+ return;
+ }
+
+ let line = [];
+ if (worldPoints.length > 0) {
+ // Cutting off segments to the overlap portion
+ let segment1 = showLineAboveY(guessLine, Math.max(worldPoints.length > 0 ? worldPoints[0][1] : 0, guessLine[0][1]));
+ let segment2 = [];
+ if (worldPoints.length > 0) {
+ segment2 = showLineBelowY(worldPoints, Math.min(guessLine[guessLine.length - 1][1], worldPoints[worldPoints.length - 1][1]))
+ }
+
+ // Distance of the world line
+ let worldDistance = worldPoints.length > 0 ? worldPoints[worldPoints.length - 1][1] - worldPoints[0][1] : 0;
+
+ // Collect the error between guess and world
+ let procrustesResult: ProcrustesResult;
+ // TUNING VARIABLE
+ if (worldDistance > 0.01) {
+ const scaleValues = [];
+ // TUNING VARIABLES
+ const start = 0.7;
+ const end = 0.9;
+ const interval = 0.01;
+ for (let value = start; value <= end; value += interval) {
+ scaleValues.push(value); // Keeps precision at 1 decimal
+ }
+ let lowestError = Infinity;
+ let bestResult = null;
+ scaleValues.forEach(scale => {
+ try {
+ let result = procrustes(segment1, segment2, scale);
+ let guessLineTemp = rotateCurve(segment1.map(point => ({ x: point[0], y: point[1] })), result.rotation);
+ if (guessLineTemp) {
+ guessLineTemp = guessLineTemp.map((point: { x: number, y: number }) => [point.x, point.y]);
+ guessLineTemp = applyTranslation(guessLineTemp, result.translation);
+ guessLineTemp = showLineAboveY(guessLineTemp, 0);
+ let cumulativeError = calculateLineError(segment2, guessLineTemp);
+
+ if (cumulativeError < lowestError) {
+ lowestError = cumulativeError;
+ bestResult = result;
+ }
+ }
+
+ } catch (e) {
+
+ }
+ });
+ procrustesResult = bestResult;
+ } else {
+ // If the current frame doesn't contain that many points, just use previous guess
+ procrustesResult = { translation: [0, 0], rotation: 0, distance: 0 };
+ }
+
+ // Correct the guess of the previous line
+ if (!procrustesResult) {
+ procrustesResult = { translation: [0, 0], rotation: 0, distance: 0 };
+ }
+
+ line = rotateCurve(guessLine.map((point: Point) => ({ x: point[0], y: point[1] })), procrustesResult.rotation).map((point: { x: number, y: number }) => [point.x, point.y]);
+ line = applyTranslation(line, procrustesResult.translation);
+ line = showLineAboveY(line, 0);
+
+ if (line.length < 1) {
+ return;
+ }
+
+ if (worldDistance > 0.05 && line.length > 0) {
+ // If we have enough points to append, add the new portion of the current camera frame
+ let test2 = worldPoints.slice(Math.round(worldPoints.length/2), worldPoints.length)
+ if (test2.length > 0) {
+ let trimmedLine = cutOffLineAtOverlap(line, test2);
+ line = trimmedLine.overlap ? trimmedLine.line : blendLines(trimmedLine.line, test2);
+ }
+ }
+
+ line = smoothLine(line);
+ } else {
+ line = guessLine;
+ line = showLineAboveY(line, 0);
+ }
+
+ // Remove duplicate y values
+ const seenY = new Set();
+ line = line.filter((point: Point) => {
+ const y = point[1];
+ if (seenY.has(y)) {
+ return false;
+ }
+ seenY.add(y);
+ return true;
+ });
+
+ if (line.length == 0) {
+ return;
+ }
+
+ // Create the spline
+ let splineLine = line;
+ if (line.length > 0 && line[0][1] > 0 && pixels.length != 0) {
+ splineLine = prependUntilTarget(line);
+ } else if (pixels.length == 0) {
+ splineLine = [[0,0], ...line];
+ }
+
+ const xs = splineLine.map((point: Point) => point[0]);
+ const ys = splineLine.map((point: Point) => point[1]);
+ const spline = new Spline.default(ys, xs); // Switch x and y so we no overlapping 'x' values
+
+ // Find the end point for the Bezier curve
+ let distance = lookahead * 1;
+
+ const x1 = findPointAtDistanceWithIncrements(spline, 0.001, distance - .01);
+ const x2 = findPointAtDistanceWithIncrements(spline, 0.001, distance);
+ const point1 = { x: spline.at(x1), y: x1 }
+ const point2 = { x: spline.at(x2), y: x2 }
+
+ // Extend point1 in the direction of the unit vector to make the Bezier control point
+ const dx = point2.x - point1.x;
+ const dy = point2.y - point1.y;
+ const length = Math.sqrt(dx * dx + dy * dy);
+ const unitDx = dx / length;
+ const unitDy = dy / length;
+
+ const extendedPoint1 = {
+ x: point2.x - unitDx * (controlLength),
+ y: point2.y - unitDy * (controlLength)
+ };
+
+ // Find the start point for the Bezier curve -- account for camera latency
+ let x3 = findPointAtDistanceWithIncrements(spline, 0.001, start);
+
+ const splineValue = spline.at(x3);
+ if (typeof splineValue === 'undefined') {
+ // Handle the error case - either return early or use a default value
+ return {
+ motorCommands: [],
+ bezierPoints: [],
+ line: []
+ };
+ }
+
+ const point3 = { x: !isNaN(splineValue) ? splineValue : 0, y: x3 };
+
+ // Find the x offset to correct
+ const reference1 = [spline.at(spline.xs[0]), 0] // First point should be very close to 0
+ const reference2 = [0, 0]
+
+ let xOffset = ((reference1[0] - reference2[0]) * errorMultiplier);
+
+ // We want to correct the offset and direct the robot to a future point on the curve
+ // TODO: Add angle correction to the control points
+ const bezier = new Bezier.Bezier(
+ { x: point3.x + xOffset, y: point3.y },
+ { x: point3.x + xOffset, y: point3.y + .005 },
+ isNaN(extendedPoint1.x) ? point2 : extendedPoint1,
+ point2
+ );
+
+ const motorCommands: Command[] = [];
+
+ // // Split the Bezier curve into a series of arcs
+ const bezierPoints = bezierCurvePoints(bezier, bezierSamples);
+
+
+ let command = createArcFromPoints(bezier.points[0], bezier.get(0.5), bezier.get(1));
+
+ if (errorMultiplier == 0) {
+ let negative = command.angle < 0;
+ command.angle = Math.abs(command.angle);
+ command.angle = Math.max(command.angle * 1.5, 5);
+ command.angle = pixels.length > 0 && pixels.length <= 100 ? Math.min(command.angle, 5) : command.angle;
+ command.angle = negative ? -1*command.angle : command.angle;
+ command.radius = Math.max(1, Math.min(1.5, command.radius / 2));
+ command.distance = 0;
+ }
+
+ motorCommands.push(command);
+ return { motorCommands, bezierPoints, line };
+
+}
+
+
+export function pixelToGroundCoordinates(pixelCoords: Point): Point {
+ // Based on Franklin's algorithm
+ const verticalPixels = imageDimensions[1] / verticalFOV;
+ const angleP = pixelCoords[1] / verticalPixels;
+ const angleC = (180 - (tiltAngle + 90));
+ const angleY = 180 - angleC;
+ const sideB = solveForSide(90, angleC, cameraHeight);
+ const angleZ = 180 - angleP - angleY;
+ const sideA = solveForSide(tiltAngle, 90, sideB);
+ const distanceP = solveForSide(angleP, angleZ, sideB);
+ const verticalOffset = sideA + distanceP;
+
+ const horizontalPixels = imageDimensions[0] / horizontalFOV;
+ const diversion = Math.abs(pixelCoords[0] - imageDimensions[0] / 2);
+ const angleA = diversion / horizontalPixels;
+ const sideK = Math.sqrt(verticalOffset * verticalOffset + cameraHeight * cameraHeight);
+ const angleB = 90 - angleA;
+ const horizontalOffset = pixelCoords[0] < imageDimensions[0] / 2 ? solveForSide(angleA, angleB, sideK) * -1 : solveForSide(angleA, angleB, sideK);
+
+ return [horizontalOffset, verticalOffset];
+
+}
+
+function bezierCurvePoints(bezier: Bezier, n: number) {
+ const points = [];
+
+ for (let i = 0; i <= n; i++) {
+ const t = i / n;
+ const position = bezier.get(t);
+ let orientation: number;
+ if (i == 0) {
+ orientation = Math.PI / 2;
+ } else {
+ const position2 = bezier.get((i - 1) / n);
+ orientation = Math.atan2(position.y - position2.y, position.x - position2.x)
+ }
+
+ points.push({ x: position.x, y: position.y, angle: orientation });
+ }
+
+ return points;
+}
+
+function solveForSide(angleA: number, angleB: number, sideB: number) {
+ // Convert angles from degrees to radians
+ const angleARad = angleA * (Math.PI / 180);
+ const angleBRad = angleB * (Math.PI / 180);
+
+ // Calculate the side opposite angleA using the Law of Sines
+ const sideA = (sideB * Math.sin(angleARad)) / Math.sin(angleBRad);
+
+ return sideA;
+}
+
+function createArcFromPoints(P1: PointObject, P2: PointObject, P3: PointObject): Command {
+ // Midpoints of the segments
+ const mid1 = { x: (P1.x + P2.x) / 2, y: (P1.y + P2.y) / 2 };
+ const mid2 = { x: (P2.x + P3.x) / 2, y: (P2.y + P3.y) / 2 };
+
+ // Perpendicular directions
+ const dir1 = { x: P2.y - P1.y, y: P1.x - P2.x }; // Perpendicular to P1 -> P2
+ const dir2 = { x: P3.y - P2.y, y: P2.x - P3.x }; // Perpendicular to P2 -> P3
+
+ // Solve for intersection (center of the circle)
+ const det = dir1.x * dir2.y - dir1.y * dir2.x;
+ if (Math.abs(det) < 1e-9) {
+ // The points are collinear, no valid arc
+ return { radius: Infinity, angle: 0, distance: distanceBetweenPoints([P1.x, P1.y], [P3.x, P3.y]) };
+ }
+
+ const dx = mid2.x - mid1.x;
+ const dy = mid2.y - mid1.y;
+ const t = (dy * dir2.x - dx * dir2.y) / det;
+
+ const center = {
+ x: mid1.x + t * dir1.x,
+ y: mid1.y + t * dir1.y
+ };
+
+ // Radius of the circle
+ const radius = Math.sqrt((P1.x - center.x) ** 2 + (P1.y - center.y) ** 2) * 39.37;
+
+ // Angles of the points
+ const startAngle = Math.atan2(P1.y - center.y, P1.x - center.x) * (180 / Math.PI);
+ const middleAngle = Math.atan2(P2.y - center.y, P2.x - center.x) * (180 / Math.PI);
+ const endAngle = Math.atan2(P3.y - center.y, P3.x - center.x) * (180 / Math.PI);
+
+ // Determine sweep direction (left or right)
+ const crossProduct = (P2.x - P1.x) * (P3.y - P2.y) - (P2.y - P1.y) * (P3.x - P2.x);
+ let sweepAngle = endAngle - startAngle;
+
+ if (sweepAngle > 180) {
+ sweepAngle -= 360;
+ } else if (sweepAngle < -180) {
+ sweepAngle += 360;
+ }
+
+ if (crossProduct < 0) {
+ // Left turn: negative angle
+ if (sweepAngle > 0) sweepAngle = sweepAngle * -1;
+ } else {
+ // Right turn: positive angle
+ if (sweepAngle < 0) sweepAngle = sweepAngle * -1;
+ }
+
+ return {
+ radius,
+ angle: sweepAngle * -1,
+ distance: 0
+ };
+}
+
+
+function calculateCurveBetweenPoints(pointA: RobotPosition, pointB: RobotPosition) {
+ const { x: x1, y: y1, angle: theta1Rad } = pointA;
+ const { x: x2, y: y2, angle: theta2Rad } = pointB;
+
+ // Midpoint and distance between points A and B
+ const midX = (x1 + x2) / 2;
+ const midY = (y1 + y2) / 2;
+ const distanceAB = Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2);
+
+ // Calculate the bisector angle and tangent angle for radius calculation
+ const angleBisector = Math.atan2(y2 - y1, x2 - x1) + Math.PI / 2;
+ const tanAngle = Math.tan((theta2Rad - theta1Rad) / 2);
+
+ // If tanAngle is close to zero, it's a straight line
+ if (Math.abs(tanAngle) < 1e-6) {
+ // Return infinite radius and the straight-line distance
+ return { radius: Infinity, angle: 0, distance: distanceAB };
+ }
+
+ // Calculate the radius of the curve
+ const radius = distanceAB / (2 * tanAngle);
+
+ // Angle to travel on the circumference
+ const angleRad = 2 * Math.atan2(distanceAB, 2 * radius);
+
+ // Convert to inches assuming pixels to inches ratio (e.g., 39.37 pixels/inch)
+ const radiusInches = Math.abs(radius * 39.37);
+ let angleDegrees = angleRad * (180 / Math.PI);
+ if (angleDegrees > 180) {
+ angleDegrees = angleDegrees - 360;
+ }
+
+ // Calculate arc length for the curved path (angle in radians * radius)
+ const arcLength = Math.abs(angleRad * radiusInches);
+
+ return { radius: radiusInches, angle: angleDegrees, distance: arcLength };
+}
diff --git a/extensions/src/doodlebot/LineHelper.ts b/extensions/src/doodlebot/LineHelper.ts
new file mode 100644
index 000000000..9ec0584e8
--- /dev/null
+++ b/extensions/src/doodlebot/LineHelper.ts
@@ -0,0 +1,174 @@
+export type Command = { radius: number, angle: number, distance: number };
+export type Point = number[];
+export type PointObject = { x: number, y: number };
+export type RobotPosition = { x: number, y: number, angle: number };
+export type ProcrustesResult = { rotation: number, translation: number[], distance: number };
+
+export function distanceBetweenPoints(p1: Point, p2: Point): number {
+ return Math.sqrt((p1[0] - p2[0]) ** 2 + (p1[1] - p2[1]) ** 2);
+}
+
+function distance(p1: PointObject, p2: PointObject): number {
+ return Math.sqrt((p2.x - p1.x) ** 2 + (p2.y - p1.y) ** 2);
+}
+
+function findClosestPoint(line: Point[], targetPoint: Point): number {
+ let closestPointIndex = 0;
+ let minDistance = Infinity;
+
+ line.forEach((point, index) => {
+ const distance = distanceBetweenPoints(point, targetPoint);
+ if (distance < minDistance) {
+ minDistance = distance;
+ closestPointIndex = index;
+ }
+ });
+
+ return closestPointIndex; // Return the index of the closest point
+}
+
+export function cutOffLineAtOverlap(line1: Point[], line2: Point[]): { line: Point[], distance: number, overlap: Boolean } {
+ const line2StartPoint = line2[0];
+ let closestPointIndex: number;
+ const line1End = line1[line1.length - 1];
+ const line2End = line2[line2.length - 1];
+
+ let line: Point[];
+ let overlap = false;
+
+ closestPointIndex = findClosestPoint(line1, line2StartPoint);
+ if (line2End[1] > line1End[1]) {
+ line = line1.slice(0, closestPointIndex + 1);
+ } else {
+ line = line1;
+ overlap = true;
+ }
+
+ const trimmedLine = line1.slice(0, closestPointIndex + 1);
+
+ let totalDistance = 0;
+
+ for (let i = 0; i < trimmedLine.length - 1; i++) {
+ const point1 = trimmedLine[i];
+ const point2 = trimmedLine[i + 1];
+
+ const distance = Math.sqrt(
+ Math.pow(point2[0] - point1[0], 2) + Math.pow(point2[1] - point1[1], 2)
+ );
+ totalDistance += distance;
+ }
+
+ return { line, distance: totalDistance, overlap };
+}
+
+export function applyTranslation(line: Point[], translationVector: number[]) {
+ return line.map(point => [
+ point[0] + translationVector[0],
+ point[1] + translationVector[1]
+ ]);
+}
+
+export function calculateLineError(line1: Point[], line2: Point[]) {
+ // Filter line points based on the overlapping y-range
+ const yMin = Math.max(
+ Math.min(...line1.map(([x, y]) => y)),
+ Math.min(...line2.map(([x, y]) => y))
+ );
+ const yMax = Math.min(
+ Math.max(...line1.map(([x, y]) => y)),
+ Math.max(...line2.map(([x, y]) => y))
+ );
+
+ // Filter both lines to keep only points within the overlapping y range
+ const line1Filtered = line1.filter(([x, y]) => y >= yMin && y <= yMax);
+ const line2Filtered = line2.filter(([x, y]) => y >= yMin && y <= yMax);
+
+ // Calculate cumulative translation error by finding the closest point
+ let cumulativeError = 0;
+ let count = 0;
+
+ for (const [x1, y1] of line1Filtered) {
+ let minDistance = Infinity;
+
+ for (const [x2, y2] of line2Filtered) {
+ // Calculate Euclidean distance between points
+ const distance = Math.sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2);
+ if (distance < minDistance) {
+ minDistance = distance;
+ }
+ }
+
+ // Sum the minimum distance found for this point in line1
+ cumulativeError += minDistance;
+ count++;
+ }
+
+ // Calculate average translation error
+ const averageError = count > 0 ? cumulativeError / count : 0;
+ return averageError;
+}
+
+function lerp(a: PointObject, b: PointObject, t: number): PointObject {
+ return {
+ x: a.x + (b.x - a.x) * t,
+ y: a.y + (b.y - a.y) * t
+ };
+}
+
+function bezierMidpoint(P1: PointObject, C1: PointObject, C2: PointObject, P2: PointObject): PointObject {
+ const A = lerp(P1, C1, 0.5);
+ const B = lerp(C1, C2, 0.5);
+ const C = lerp(C2, P2, 0.5);
+ const D = lerp(A, B, 0.5);
+ const E = lerp(B, C, 0.5);
+ return lerp(D, E, 0.5);
+}
+
+function findSingleCircle(
+ P1: PointObject,
+ P2: PointObject,
+ midpoint: PointObject
+): { center: PointObject; radius: number; angle: number } | null {
+ const mid1 = { x: (P1.x + midpoint.x) / 2, y: (P1.y + midpoint.y) / 2 };
+ const mid2 = { x: (P2.x + midpoint.x) / 2, y: (P2.y + midpoint.y) / 2 };
+
+ const dir1 = { x: midpoint.y - P1.y, y: P1.x - midpoint.x };
+ const dir2 = { x: midpoint.y - P2.y, y: P2.x - midpoint.x };
+
+ const det = dir1.x * dir2.y - dir1.y * dir2.x;
+ if (Math.abs(det) < 1e-9) return null;
+
+ const dx = mid2.x - mid1.x;
+ const dy = mid2.y - mid1.y;
+ const u = (dy * dir2.x - dx * dir2.y) / det;
+
+ const center = {
+ x: mid1.x + u * dir1.x,
+ y: mid1.y + u * dir1.y
+ };
+
+ const radiusInPixels = distance(center, P1);
+ const radiusInInches = radiusInPixels * 39.3701; // Convert pixels to inches
+
+ // Calculate angles in radians
+ const angle1 = Math.atan2(P1.y - center.y, P1.x - center.x);
+ const angle2 = Math.atan2(P2.y - center.y, P2.x - center.x);
+
+ // Calculate the angle difference
+ let angleInDegrees = (angle2 - angle1) * (180 / Math.PI);
+
+ // Normalize the angle to range [-180, 180]
+ if (angleInDegrees > 180) {
+ angleInDegrees -= 360;
+ } else if (angleInDegrees < -180) {
+ angleInDegrees += 360;
+ }
+
+ return { center, radius: radiusInInches, angle: -1 * angleInDegrees };
+}
+
+
+export function approximateBezierWithArc(P1: PointObject, C1: PointObject, C2: PointObject, P2: PointObject): { center: PointObject; radius: number; angle: number } | null {
+ const midpoint = bezierMidpoint(P1, C1, C2, P2);
+ return findSingleCircle(P1, P2, midpoint);
+}
diff --git a/extensions/src/doodlebot/Points.ts b/extensions/src/doodlebot/Points.ts
new file mode 100644
index 000000000..748541906
--- /dev/null
+++ b/extensions/src/doodlebot/Points.ts
@@ -0,0 +1,28 @@
+export const line8 = [[450,0],[451,0],[452,0],[453,0],[454,0],[455,0],[456,0],[457,0],[458,0],[449,1],[450,1],[451,1],[452,1],[453,1],[454,1],[455,1],[456,1],[457,1],[449,2],[450,2],[451,2],[452,2],[453,2],[454,2],[455,2],[456,2],[457,2],[448,3],[449,3],[450,3],[451,3],[452,3],[453,3],[454,3],[455,3],[456,3],[448,4],[449,4],[450,4],[451,4],[452,4],[453,4],[454,4],[455,4],[447,5],[448,5],[449,5],[450,5],[451,5],[452,5],[453,5],[454,5],[455,5],[447,6],[448,6],[449,6],[450,6],[451,6],[452,6],[453,6],[454,6],[446,7],[447,7],[448,7],[449,7],[450,7],[451,7],[452,7],[453,7],[446,8],[447,8],[448,8],[449,8],[450,8],[451,8],[452,8],[453,8],[445,9],[446,9],[447,9],[448,9],[449,9],[450,9],[451,9],[452,9],[444,10],[445,10],[446,10],[447,10],[448,10],[449,10],[450,10],[451,10],[444,11],[445,11],[446,11],[447,11],[448,11],[449,11],[450,11],[443,12],[444,12],[445,12],[446,12],[447,12],[448,12],[449,12],[443,13],[444,13],[445,13],[446,13],[447,13],[448,13],[449,13],[442,14],[443,14],[444,14],[445,14],[446,14],[447,14],[448,14],[441,15],[442,15],[443,15],[444,15],[445,15],[446,15],[447,15],[440,16],[441,16],[442,16],[443,16],[444,16],[445,16],[446,16],[440,17],[441,17],[442,17],[443,17],[444,17],[445,17],[446,17],[439,18],[440,18],[441,18],[442,18],[443,18],[444,18],[445,18],[438,19],[439,19],[440,19],[441,19],[442,19],[443,19],[444,19],[437,20],[438,20],[439,20],[440,20],[441,20],[442,20],[443,20],[436,21],[437,21],[438,21],[439,21],[440,21],[441,21],[442,21],[436,22],[437,22],[438,22],[439,22],[440,22],[441,22],[442,22],[435,23],[436,23],[437,23],[438,23],[439,23],[440,23],[441,23],[434,24],[435,24],[436,24],[437,24],[438,24],[439,24],[440,24],[434,25],[435,25],[436,25],[437,25],[438,25],[439,25],[433,26],[434,26],[435,26],[436,26],[437,26],[438,26],[432,27],[433,27],[434,27],[435,27],[436,27],[437,27],[431,28],[432,28],[433,28],[434,28],[435,28],[436,28],[431,29],[432,29],[433,29],[434,29],[435,29],[436,29],[430,30],[431,30],[432,30],[433,30],[434,30],[435,30],[429,31],[430,31],[431,31],[432,31],[433,31],[434,31],[428,32],[429,32],[430,32],[431,32],[432,32],[433,32],[427,33],[428,33],[429,33],[430,33],[431,33],[432,33],[433,33],[427,34],[428,34],[429,34],[430,34],[431,34],[432,34],[426,35],[427,35],[428,35],[429,35],[430,35],[431,35],[425,36],[426,36],[427,36],[428,36],[429,36],[430,36],[424,37],[425,37],[426,37],[427,37],[428,37],[429,37],[430,37],[423,38],[424,38],[425,38],[426,38],[427,38],[428,38],[429,38],[423,39],[424,39],[425,39],[426,39],[427,39],[428,39],[422,40],[423,40],[424,40],[425,40],[426,40],[427,40],[421,41],[422,41],[423,41],[424,41],[425,41],[426,41],[420,42],[421,42],[422,42],[423,42],[424,42],[425,42],[426,42],[419,43],[420,43],[421,43],[422,43],[423,43],[424,43],[425,43],[418,44],[419,44],[420,44],[421,44],[422,44],[423,44],[424,44],[417,45],[418,45],[419,45],[420,45],[421,45],[422,45],[423,45],[417,46],[418,46],[419,46],[420,46],[421,46],[422,46],[423,46],[416,47],[417,47],[418,47],[419,47],[420,47],[421,47],[422,47],[415,48],[416,48],[417,48],[418,48],[419,48],[420,48],[421,48],[414,49],[415,49],[416,49],[417,49],[418,49],[419,49],[420,49],[421,49],[413,50],[414,50],[415,50],[416,50],[417,50],[418,50],[419,50],[420,50],[412,51],[413,51],[414,51],[415,51],[416,51],[417,51],[418,51],[419,51],[411,52],[412,52],[413,52],[414,52],[415,52],[416,52],[417,52],[418,52],[410,53],[411,53],[412,53],[413,53],[414,53],[415,53],[416,53],[417,53],[409,54],[410,54],[411,54],[412,54],[413,54],[414,54],[415,54],[416,54],[408,55],[409,55],[410,55],[411,55],[412,55],[413,55],[414,55],[415,55],[407,56],[408,56],[409,56],[410,56],[411,56],[412,56],[413,56],[414,56],[406,57],[407,57],[408,57],[409,57],[410,57],[411,57],[412,57],[413,57],[405,58],[406,58],[407,58],[408,58],[409,58],[410,58],[411,58],[412,58],[404,59],[405,59],[406,59],[407,59],[408,59],[409,59],[410,59],[411,59],[403,60],[404,60],[405,60],[406,60],[407,60],[408,60],[409,60],[410,60],[402,61],[403,61],[404,61],[405,61],[406,61],[407,61],[408,61],[409,61],[400,62],[401,62],[402,62],[403,62],[404,62],[405,62],[406,62],[407,62],[408,62],[399,63],[400,63],[401,63],[402,63],[403,63],[404,63],[405,63],[406,63],[407,63],[398,64],[399,64],[400,64],[401,64],[402,64],[403,64],[404,64],[405,64],[406,64],[397,65],[398,65],[399,65],[400,65],[401,65],[402,65],[403,65],[404,65],[405,65],[396,66],[397,66],[398,66],[399,66],[400,66],[401,66],[402,66],[403,66],[404,66],[395,67],[396,67],[397,67],[398,67],[399,67],[400,67],[401,67],[402,67],[403,67],[393,68],[394,68],[395,68],[396,68],[397,68],[398,68],[399,68],[400,68],[401,68],[402,68],[392,69],[393,69],[394,69],[395,69],[396,69],[397,69],[398,69],[399,69],[400,69],[401,69],[391,70],[392,70],[393,70],[394,70],[395,70],[396,70],[397,70],[398,70],[399,70],[390,71],[391,71],[392,71],[393,71],[394,71],[395,71],[396,71],[397,71],[398,71],[389,72],[390,72],[391,72],[392,72],[393,72],[394,72],[395,72],[396,72],[397,72],[387,73],[388,73],[389,73],[390,73],[391,73],[392,73],[393,73],[394,73],[395,73],[396,73],[386,74],[387,74],[388,74],[389,74],[390,74],[391,74],[392,74],[393,74],[394,74],[395,74],[385,75],[386,75],[387,75],[388,75],[389,75],[390,75],[391,75],[392,75],[393,75],[394,75],[383,76],[384,76],[385,76],[386,76],[387,76],[388,76],[389,76],[390,76],[391,76],[392,76],[393,76],[382,77],[383,77],[384,77],[385,77],[386,77],[387,77],[388,77],[389,77],[390,77],[391,77],[392,77],[380,78],[381,78],[382,78],[383,78],[384,78],[385,78],[386,78],[387,78],[388,78],[389,78],[390,78],[379,79],[380,79],[381,79],[382,79],[383,79],[384,79],[385,79],[386,79],[387,79],[388,79],[389,79],[377,80],[378,80],[379,80],[380,80],[381,80],[382,80],[383,80],[384,80],[385,80],[386,80],[387,80],[388,80],[376,81],[377,81],[378,81],[379,81],[380,81],[381,81],[382,81],[383,81],[384,81],[385,81],[386,81],[387,81],[375,82],[376,82],[377,82],[378,82],[379,82],[380,82],[381,82],[382,82],[383,82],[384,82],[385,82],[386,82],[373,83],[374,83],[375,83],[376,83],[377,83],[378,83],[379,83],[380,83],[381,83],[382,83],[383,83],[384,83],[371,84],[372,84],[373,84],[374,84],[375,84],[376,84],[377,84],[378,84],[379,84],[380,84],[381,84],[382,84],[383,84],[370,85],[371,85],[372,85],[373,85],[374,85],[375,85],[376,85],[377,85],[378,85],[379,85],[380,85],[381,85],[368,86],[369,86],[370,86],[371,86],[372,86],[373,86],[374,86],[375,86],[376,86],[377,86],[378,86],[379,86],[380,86],[367,87],[368,87],[369,87],[370,87],[371,87],[372,87],[373,87],[374,87],[375,87],[376,87],[377,87],[378,87],[365,88],[366,88],[367,88],[368,88],[369,88],[370,88],[371,88],[372,88],[373,88],[374,88],[375,88],[376,88],[377,88],[363,89],[364,89],[365,89],[366,89],[367,89],[368,89],[369,89],[370,89],[371,89],[372,89],[373,89],[374,89],[375,89],[362,90],[363,90],[364,90],[365,90],[366,90],[367,90],[368,90],[369,90],[370,90],[371,90],[372,90],[373,90],[374,90],[360,91],[361,91],[362,91],[363,91],[364,91],[365,91],[366,91],[367,91],[368,91],[369,91],[370,91],[371,91],[372,91],[358,92],[359,92],[360,92],[361,92],[362,92],[363,92],[364,92],[365,92],[366,92],[367,92],[368,92],[369,92],[370,92],[371,92],[356,93],[357,93],[358,93],[359,93],[360,93],[361,93],[362,93],[363,93],[364,93],[365,93],[366,93],[367,93],[368,93],[369,93],[354,94],[355,94],[356,94],[357,94],[358,94],[359,94],[360,94],[361,94],[362,94],[363,94],[364,94],[365,94],[366,94],[367,94],[351,95],[352,95],[353,95],[354,95],[355,95],[356,95],[357,95],[358,95],[359,95],[360,95],[361,95],[362,95],[363,95],[364,95],[365,95],[366,95],[349,96],[350,96],[351,96],[352,96],[353,96],[354,96],[355,96],[356,96],[357,96],[358,96],[359,96],[360,96],[361,96],[362,96],[363,96],[364,96],[346,97],[347,97],[348,97],[349,97],[350,97],[351,97],[352,97],[353,97],[354,97],[355,97],[356,97],[357,97],[358,97],[359,97],[360,97],[361,97],[362,97],[363,97],[343,98],[344,98],[345,98],[346,98],[347,98],[348,98],[349,98],[350,98],[351,98],[352,98],[353,98],[354,98],[355,98],[356,98],[357,98],[358,98],[359,98],[360,98],[361,98],[341,99],[342,99],[343,99],[344,99],[345,99],[346,99],[347,99],[348,99],[349,99],[350,99],[351,99],[352,99],[353,99],[354,99],[355,99],[356,99],[357,99],[358,99],[359,99],[360,99],[337,100],[338,100],[339,100],[340,100],[341,100],[342,100],[343,100],[344,100],[345,100],[346,100],[347,100],[348,100],[349,100],[350,100],[351,100],[352,100],[353,100],[354,100],[355,100],[356,100],[357,100],[335,101],[336,101],[337,101],[338,101],[339,101],[340,101],[341,101],[342,101],[343,101],[344,101],[345,101],[346,101],[347,101],[348,101],[349,101],[350,101],[351,101],[352,101],[353,101],[354,101],[355,101],[332,102],[333,102],[334,102],[335,102],[336,102],[337,102],[338,102],[339,102],[340,102],[341,102],[342,102],[343,102],[344,102],[345,102],[346,102],[347,102],[348,102],[349,102],[350,102],[351,102],[352,102],[353,102],[329,103],[330,103],[331,103],[332,103],[333,103],[334,103],[335,103],[336,103],[337,103],[338,103],[339,103],[340,103],[341,103],[342,103],[343,103],[344,103],[345,103],[346,103],[347,103],[348,103],[349,103],[350,103],[326,104],[327,104],[328,104],[329,104],[330,104],[331,104],[332,104],[333,104],[334,104],[335,104],[336,104],[337,104],[338,104],[339,104],[340,104],[341,104],[342,104],[343,104],[344,104],[345,104],[346,104],[347,104],[324,105],[325,105],[326,105],[327,105],[328,105],[329,105],[330,105],[331,105],[332,105],[333,105],[334,105],[335,105],[336,105],[337,105],[338,105],[339,105],[340,105],[341,105],[342,105],[343,105],[344,105],[321,106],[322,106],[323,106],[324,106],[325,106],[326,106],[327,106],[328,106],[329,106],[330,106],[331,106],[332,106],[333,106],[334,106],[335,106],[336,106],[337,106],[338,106],[339,106],[340,106],[341,106],[318,107],[319,107],[320,107],[321,107],[322,107],[323,107],[324,107],[325,107],[326,107],[327,107],[328,107],[329,107],[330,107],[331,107],[332,107],[333,107],[334,107],[335,107],[336,107],[337,107],[338,107],[315,108],[316,108],[317,108],[318,108],[319,108],[320,108],[321,108],[322,108],[323,108],[324,108],[325,108],[326,108],[327,108],[328,108],[329,108],[330,108],[331,108],[332,108],[333,108],[334,108],[335,108],[312,109],[313,109],[314,109],[315,109],[316,109],[317,109],[318,109],[319,109],[320,109],[321,109],[322,109],[323,109],[324,109],[325,109],[326,109],[327,109],[328,109],[329,109],[330,109],[331,109],[332,109],[310,110],[311,110],[312,110],[313,110],[314,110],[315,110],[316,110],[317,110],[318,110],[319,110],[320,110],[321,110],[322,110],[323,110],[324,110],[325,110],[326,110],[327,110],[328,110],[329,110],[307,111],[308,111],[309,111],[310,111],[311,111],[312,111],[313,111],[314,111],[315,111],[316,111],[317,111],[318,111],[319,111],[320,111],[321,111],[322,111],[323,111],[324,111],[325,111],[326,111],[304,112],[305,112],[306,112],[307,112],[308,112],[309,112],[310,112],[311,112],[312,112],[313,112],[314,112],[315,112],[316,112],[317,112],[318,112],[319,112],[320,112],[321,112],[322,112],[323,112],[302,113],[303,113],[304,113],[305,113],[306,113],[307,113],[308,113],[309,113],[310,113],[311,113],[312,113],[313,113],[314,113],[315,113],[316,113],[317,113],[318,113],[319,113],[320,113],[299,114],[300,114],[301,114],[302,114],[303,114],[304,114],[305,114],[306,114],[307,114],[308,114],[309,114],[310,114],[311,114],[312,114],[313,114],[314,114],[315,114],[316,114],[317,114],[296,115],[297,115],[298,115],[299,115],[300,115],[301,115],[302,115],[303,115],[304,115],[305,115],[306,115],[307,115],[308,115],[309,115],[310,115],[311,115],[312,115],[313,115],[314,115],[315,115],[293,116],[294,116],[295,116],[296,116],[297,116],[298,116],[299,116],[300,116],[301,116],[302,116],[303,116],[304,116],[305,116],[306,116],[307,116],[308,116],[309,116],[310,116],[311,116],[312,116],[290,117],[291,117],[292,117],[293,117],[294,117],[295,117],[296,117],[297,117],[298,117],[299,117],[300,117],[301,117],[302,117],[303,117],[304,117],[305,117],[306,117],[307,117],[308,117],[309,117],[310,117],[287,118],[288,118],[289,118],[290,118],[291,118],[292,118],[293,118],[294,118],[295,118],[296,118],[297,118],[298,118],[299,118],[300,118],[301,118],[302,118],[303,118],[304,118],[305,118],[306,118],[307,118],[284,119],[285,119],[286,119],[287,119],[288,119],[289,119],[290,119],[291,119],[292,119],[293,119],[294,119],[295,119],[296,119],[297,119],[298,119],[299,119],[300,119],[301,119],[302,119],[303,119],[304,119],[280,120],[281,120],[282,120],[283,120],[284,120],[285,120],[286,120],[287,120],[288,120],[289,120],[290,120],[291,120],[292,120],[293,120],[294,120],[295,120],[296,120],[297,120],[298,120],[299,120],[300,120],[301,120],[277,121],[278,121],[279,121],[280,121],[281,121],[282,121],[283,121],[284,121],[285,121],[286,121],[287,121],[288,121],[289,121],[290,121],[291,121],[292,121],[293,121],[294,121],[295,121],[296,121],[297,121],[298,121],[274,122],[275,122],[276,122],[277,122],[278,122],[279,122],[280,122],[281,122],[282,122],[283,122],[284,122],[285,122],[286,122],[287,122],[288,122],[289,122],[290,122],[291,122],[292,122],[293,122],[294,122],[295,122],[270,123],[271,123],[272,123],[273,123],[274,123],[275,123],[276,123],[277,123],[278,123],[279,123],[280,123],[281,123],[282,123],[283,123],[284,123],[285,123],[286,123],[287,123],[288,123],[289,123],[290,123],[291,123],[292,123],[266,124],[267,124],[268,124],[269,124],[270,124],[271,124],[272,124],[273,124],[274,124],[275,124],[276,124],[277,124],[278,124],[279,124],[280,124],[281,124],[282,124],[283,124],[284,124],[285,124],[286,124],[287,124],[288,124],[262,125],[263,125],[264,125],[265,125],[266,125],[267,125],[268,125],[269,125],[270,125],[271,125],[272,125],[273,125],[274,125],[275,125],[276,125],[277,125],[278,125],[279,125],[280,125],[281,125],[282,125],[283,125],[284,125],[285,125],[256,126],[257,126],[258,126],[259,126],[260,126],[261,126],[262,126],[263,126],[264,126],[265,126],[266,126],[267,126],[268,126],[269,126],[270,126],[271,126],[272,126],[273,126],[274,126],[275,126],[276,126],[277,126],[278,126],[279,126],[280,126],[281,126],[282,126],[248,127],[249,127],[250,127],[251,127],[252,127],[253,127],[254,127],[255,127],[256,127],[257,127],[258,127],[259,127],[260,127],[261,127],[262,127],[263,127],[264,127],[265,127],[266,127],[267,127],[268,127],[269,127],[270,127],[271,127],[272,127],[273,127],[274,127],[275,127],[276,127],[277,127],[278,127],[279,127],[247,128],[248,128],[249,128],[250,128],[251,128],[252,128],[253,128],[254,128],[255,128],[256,128],[257,128],[258,128],[259,128],[260,128],[261,128],[262,128],[263,128],[264,128],[265,128],[266,128],[267,128],[268,128],[269,128],[270,128],[271,128],[272,128],[273,128],[274,128],[275,128],[276,128],[247,129],[248,129],[249,129],[250,129],[251,129],[252,129],[253,129],[254,129],[255,129],[256,129],[257,129],[258,129],[259,129],[260,129],[261,129],[262,129],[263,129],[264,129],[265,129],[266,129],[267,129],[268,129],[269,129],[270,129],[271,129],[272,129],[273,129],[247,130],[248,130],[249,130],[250,130],[251,130],[252,130],[253,130],[254,130],[255,130],[256,130],[257,130],[258,130],[259,130],[260,130],[261,130],[262,130],[263,130],[264,130],[265,130],[266,130],[267,130],[268,130],[269,130],[270,130],[247,131],[248,131],[249,131],[250,131],[251,131],[252,131],[253,131],[254,131],[255,131],[256,131],[257,131],[258,131],[259,131],[260,131],[261,131],[262,131],[263,131],[264,131],[265,131],[266,131],[247,132],[248,132],[249,132],[250,132],[251,132],[252,132],[253,132],[254,132],[255,132],[256,132],[257,132],[258,132],[259,132],[247,133],[248,133],[249,133],[250,133],[251,133],[252,133],[253,133],[254,133],[255,133],[248,134],[249,134],[250,134]]
+
+
+export const line7 = [[608,0],[609,0],[610,0],[611,0],[612,0],[613,0],[614,0],[615,0],[616,0],[617,0],[618,0],[619,0],[620,0],[621,0],[622,0],[623,0],[624,0],[625,0],[626,0],[627,0],[628,0],[629,0],[630,0],[631,0],[632,0],[633,0],[634,0],[635,0],[636,0],[608,1],[609,1],[610,1],[611,1],[612,1],[613,1],[614,1],[615,1],[616,1],[617,1],[618,1],[619,1],[620,1],[621,1],[622,1],[623,1],[624,1],[625,1],[626,1],[627,1],[628,1],[629,1],[630,1],[631,1],[632,1],[633,1],[634,1],[635,1],[636,1],[608,2],[609,2],[610,2],[611,2],[612,2],[613,2],[614,2],[615,2],[616,2],[617,2],[618,2],[619,2],[620,2],[621,2],[622,2],[623,2],[624,2],[625,2],[626,2],[627,2],[628,2],[629,2],[630,2],[631,2],[632,2],[633,2],[634,2],[635,2],[636,2],[609,3],[610,3],[611,3],[612,3],[613,3],[614,3],[615,3],[616,3],[617,3],[618,3],[619,3],[620,3],[621,3],[622,3],[623,3],[624,3],[625,3],[626,3],[627,3],[628,3],[629,3],[630,3],[631,3],[632,3],[633,3],[634,3],[635,3],[636,3],[609,4],[610,4],[611,4],[612,4],[613,4],[614,4],[615,4],[616,4],[617,4],[618,4],[619,4],[620,4],[621,4],[622,4],[623,4],[624,4],[625,4],[626,4],[627,4],[628,4],[629,4],[630,4],[631,4],[632,4],[633,4],[634,4],[635,4],[636,4],[609,5],[610,5],[611,5],[612,5],[613,5],[614,5],[615,5],[616,5],[617,5],[618,5],[619,5],[620,5],[621,5],[622,5],[623,5],[624,5],[625,5],[626,5],[627,5],[628,5],[629,5],[630,5],[631,5],[632,5],[633,5],[634,5],[635,5],[636,5],[610,6],[611,6],[612,6],[613,6],[614,6],[615,6],[616,6],[617,6],[618,6],[619,6],[620,6],[621,6],[622,6],[623,6],[624,6],[625,6],[626,6],[627,6],[628,6],[629,6],[630,6],[631,6],[632,6],[633,6],[634,6],[635,6],[636,6],[610,7],[611,7],[612,7],[613,7],[614,7],[615,7],[616,7],[617,7],[618,7],[619,7],[620,7],[621,7],[622,7],[623,7],[624,7],[625,7],[626,7],[627,7],[628,7],[629,7],[630,7],[631,7],[632,7],[633,7],[634,7],[635,7],[636,7],[610,8],[611,8],[612,8],[613,8],[614,8],[615,8],[616,8],[617,8],[618,8],[619,8],[620,8],[621,8],[622,8],[623,8],[624,8],[625,8],[626,8],[627,8],[628,8],[629,8],[630,8],[631,8],[632,8],[633,8],[634,8],[635,8],[636,8],[637,8],[611,9],[612,9],[613,9],[614,9],[615,9],[616,9],[617,9],[618,9],[619,9],[620,9],[621,9],[622,9],[623,9],[624,9],[625,9],[626,9],[627,9],[628,9],[629,9],[630,9],[631,9],[632,9],[633,9],[634,9],[635,9],[636,9],[637,9],[611,10],[612,10],[613,10],[614,10],[615,10],[616,10],[617,10],[618,10],[619,10],[620,10],[621,10],[622,10],[623,10],[624,10],[625,10],[626,10],[627,10],[628,10],[629,10],[630,10],[631,10],[632,10],[633,10],[634,10],[635,10],[636,10],[637,10],[611,11],[612,11],[613,11],[614,11],[615,11],[616,11],[617,11],[618,11],[619,11],[620,11],[621,11],[622,11],[623,11],[624,11],[625,11],[626,11],[627,11],[628,11],[629,11],[630,11],[631,11],[632,11],[633,11],[634,11],[635,11],[636,11],[637,11],[612,12],[613,12],[614,12],[615,12],[616,12],[617,12],[618,12],[619,12],[620,12],[621,12],[622,12],[623,12],[624,12],[625,12],[626,12],[627,12],[628,12],[629,12],[630,12],[631,12],[632,12],[633,12],[634,12],[635,12],[636,12],[637,12],[612,13],[613,13],[614,13],[615,13],[616,13],[617,13],[618,13],[619,13],[620,13],[621,13],[622,13],[623,13],[624,13],[625,13],[626,13],[627,13],[628,13],[629,13],[630,13],[631,13],[632,13],[633,13],[634,13],[635,13],[636,13],[637,13],[638,13],[612,14],[613,14],[614,14],[615,14],[616,14],[617,14],[618,14],[619,14],[620,14],[621,14],[622,14],[623,14],[624,14],[625,14],[626,14],[627,14],[628,14],[629,14],[630,14],[631,14],[632,14],[633,14],[634,14],[635,14],[636,14],[637,14],[613,15],[614,15],[615,15],[616,15],[617,15],[618,15],[619,15],[620,15],[621,15],[622,15],[623,15],[624,15],[625,15],[626,15],[627,15],[628,15],[629,15],[630,15],[631,15],[632,15],[633,15],[634,15],[635,15],[636,15],[637,15],[638,15],[613,16],[614,16],[615,16],[616,16],[617,16],[618,16],[619,16],[620,16],[621,16],[622,16],[623,16],[624,16],[625,16],[626,16],[627,16],[628,16],[629,16],[630,16],[631,16],[632,16],[633,16],[634,16],[635,16],[636,16],[637,16],[638,16],[613,17],[614,17],[615,17],[616,17],[617,17],[618,17],[619,17],[620,17],[621,17],[622,17],[623,17],[624,17],[625,17],[626,17],[627,17],[628,17],[629,17],[630,17],[631,17],[632,17],[633,17],[634,17],[635,17],[636,17],[637,17],[638,17],[614,18],[615,18],[616,18],[617,18],[618,18],[619,18],[620,18],[621,18],[622,18],[623,18],[624,18],[625,18],[626,18],[627,18],[628,18],[629,18],[630,18],[631,18],[632,18],[633,18],[634,18],[635,18],[636,18],[637,18],[638,18],[614,19],[615,19],[616,19],[617,19],[618,19],[619,19],[620,19],[621,19],[622,19],[623,19],[624,19],[625,19],[626,19],[627,19],[628,19],[629,19],[630,19],[631,19],[632,19],[633,19],[634,19],[635,19],[636,19],[637,19],[638,19],[614,20],[615,20],[616,20],[617,20],[618,20],[619,20],[620,20],[621,20],[622,20],[623,20],[624,20],[625,20],[626,20],[627,20],[628,20],[629,20],[630,20],[631,20],[632,20],[633,20],[634,20],[635,20],[636,20],[637,20],[638,20],[615,21],[616,21],[617,21],[618,21],[619,21],[620,21],[621,21],[622,21],[623,21],[624,21],[625,21],[626,21],[627,21],[628,21],[629,21],[630,21],[631,21],[632,21],[633,21],[634,21],[635,21],[636,21],[637,21],[638,21],[615,22],[616,22],[617,22],[618,22],[619,22],[620,22],[621,22],[622,22],[623,22],[624,22],[625,22],[626,22],[627,22],[628,22],[629,22],[630,22],[631,22],[632,22],[633,22],[634,22],[635,22],[636,22],[637,22],[638,22],[615,23],[616,23],[617,23],[618,23],[619,23],[620,23],[621,23],[622,23],[623,23],[624,23],[625,23],[626,23],[627,23],[628,23],[629,23],[630,23],[631,23],[632,23],[633,23],[634,23],[635,23],[636,23],[637,23],[638,23],[615,24],[616,24],[617,24],[618,24],[619,24],[620,24],[621,24],[622,24],[623,24],[624,24],[625,24],[626,24],[627,24],[628,24],[629,24],[630,24],[631,24],[632,24],[633,24],[634,24],[635,24],[636,24],[637,24],[638,24],[616,25],[617,25],[618,25],[619,25],[620,25],[621,25],[622,25],[623,25],[624,25],[625,25],[626,25],[627,25],[628,25],[629,25],[630,25],[631,25],[632,25],[633,25],[634,25],[635,25],[636,25],[637,25],[638,25],[616,26],[617,26],[618,26],[619,26],[620,26],[621,26],[622,26],[623,26],[624,26],[625,26],[626,26],[627,26],[628,26],[629,26],[630,26],[631,26],[632,26],[633,26],[634,26],[635,26],[636,26],[637,26],[638,26],[616,27],[617,27],[618,27],[619,27],[620,27],[621,27],[622,27],[623,27],[624,27],[625,27],[626,27],[627,27],[628,27],[629,27],[630,27],[631,27],[632,27],[633,27],[634,27],[635,27],[636,27],[637,27],[638,27],[616,28],[617,28],[618,28],[619,28],[620,28],[621,28],[622,28],[623,28],[624,28],[625,28],[626,28],[627,28],[628,28],[629,28],[630,28],[631,28],[632,28],[633,28],[634,28],[635,28],[636,28],[637,28],[638,28],[639,28],[616,29],[617,29],[618,29],[619,29],[620,29],[621,29],[622,29],[623,29],[624,29],[625,29],[626,29],[627,29],[628,29],[629,29],[630,29],[631,29],[632,29],[633,29],[634,29],[635,29],[636,29],[637,29],[638,29],[639,29],[617,30],[618,30],[619,30],[620,30],[621,30],[622,30],[623,30],[624,30],[625,30],[626,30],[627,30],[628,30],[629,30],[630,30],[631,30],[632,30],[633,30],[634,30],[635,30],[636,30],[637,30],[638,30],[639,30],[617,31],[618,31],[619,31],[620,31],[621,31],[622,31],[623,31],[624,31],[625,31],[626,31],[627,31],[628,31],[629,31],[630,31],[631,31],[632,31],[633,31],[634,31],[635,31],[636,31],[637,31],[638,31],[639,31],[617,32],[618,32],[619,32],[620,32],[621,32],[622,32],[623,32],[624,32],[625,32],[626,32],[627,32],[628,32],[629,32],[630,32],[631,32],[632,32],[633,32],[634,32],[635,32],[636,32],[637,32],[638,32],[639,32],[617,33],[618,33],[619,33],[620,33],[621,33],[622,33],[623,33],[624,33],[625,33],[626,33],[627,33],[628,33],[629,33],[630,33],[631,33],[632,33],[633,33],[634,33],[635,33],[636,33],[637,33],[638,33],[639,33],[618,34],[619,34],[620,34],[621,34],[622,34],[623,34],[624,34],[625,34],[626,34],[627,34],[628,34],[629,34],[630,34],[631,34],[632,34],[633,34],[634,34],[635,34],[636,34],[637,34],[638,34],[639,34],[618,35],[619,35],[620,35],[621,35],[622,35],[623,35],[624,35],[625,35],[626,35],[627,35],[628,35],[629,35],[630,35],[631,35],[632,35],[633,35],[634,35],[635,35],[636,35],[637,35],[638,35],[639,35],[618,36],[619,36],[620,36],[621,36],[622,36],[623,36],[624,36],[625,36],[626,36],[627,36],[628,36],[629,36],[630,36],[631,36],[632,36],[633,36],[634,36],[635,36],[636,36],[637,36],[638,36],[639,36],[618,37],[619,37],[620,37],[621,37],[622,37],[623,37],[624,37],[625,37],[626,37],[627,37],[628,37],[629,37],[630,37],[631,37],[632,37],[633,37],[634,37],[635,37],[636,37],[637,37],[638,37],[639,37],[618,38],[619,38],[620,38],[621,38],[622,38],[623,38],[624,38],[625,38],[626,38],[627,38],[628,38],[629,38],[630,38],[631,38],[632,38],[633,38],[634,38],[635,38],[636,38],[637,38],[638,38],[639,38],[618,39],[619,39],[620,39],[621,39],[622,39],[623,39],[624,39],[625,39],[626,39],[627,39],[628,39],[629,39],[630,39],[631,39],[632,39],[633,39],[634,39],[635,39],[636,39],[637,39],[638,39],[639,39],[619,40],[620,40],[621,40],[622,40],[623,40],[624,40],[625,40],[626,40],[627,40],[628,40],[629,40],[630,40],[631,40],[632,40],[633,40],[634,40],[635,40],[636,40],[637,40],[638,40],[639,40],[619,41],[620,41],[621,41],[622,41],[623,41],[624,41],[625,41],[626,41],[627,41],[628,41],[629,41],[630,41],[631,41],[632,41],[633,41],[634,41],[635,41],[636,41],[637,41],[638,41],[619,42],[620,42],[621,42],[622,42],[623,42],[624,42],[625,42],[626,42],[627,42],[628,42],[629,42],[630,42],[631,42],[632,42],[633,42],[634,42],[635,42],[636,42],[637,42],[638,42],[619,43],[620,43],[621,43],[622,43],[623,43],[624,43],[625,43],[626,43],[627,43],[628,43],[629,43],[630,43],[631,43],[632,43],[633,43],[634,43],[635,43],[636,43],[637,43],[638,43],[619,44],[620,44],[621,44],[622,44],[623,44],[624,44],[625,44],[626,44],[627,44],[628,44],[629,44],[630,44],[631,44],[632,44],[633,44],[634,44],[635,44],[636,44],[637,44],[638,44],[619,45],[620,45],[621,45],[622,45],[623,45],[624,45],[625,45],[626,45],[627,45],[628,45],[629,45],[630,45],[631,45],[632,45],[633,45],[634,45],[635,45],[636,45],[637,45],[638,45],[619,46],[620,46],[621,46],[622,46],[623,46],[624,46],[625,46],[626,46],[627,46],[628,46],[629,46],[630,46],[631,46],[632,46],[633,46],[634,46],[635,46],[636,46],[637,46],[638,46],[619,47],[620,47],[621,47],[622,47],[623,47],[624,47],[625,47],[626,47],[627,47],[628,47],[629,47],[630,47],[631,47],[632,47],[633,47],[634,47],[635,47],[636,47],[637,47],[638,47],[620,48],[621,48],[622,48],[623,48],[624,48],[625,48],[626,48],[627,48],[628,48],[629,48],[630,48],[631,48],[632,48],[633,48],[634,48],[635,48],[636,48],[637,48],[638,48],[620,49],[621,49],[622,49],[623,49],[624,49],[625,49],[626,49],[627,49],[628,49],[629,49],[630,49],[631,49],[632,49],[633,49],[634,49],[635,49],[636,49],[637,49],[638,49],[620,50],[621,50],[622,50],[623,50],[624,50],[625,50],[626,50],[627,50],[628,50],[629,50],[630,50],[631,50],[632,50],[633,50],[634,50],[635,50],[636,50],[637,50],[638,50],[620,51],[621,51],[622,51],[623,51],[624,51],[625,51],[626,51],[627,51],[628,51],[629,51],[630,51],[631,51],[632,51],[633,51],[634,51],[635,51],[636,51],[637,51],[638,51],[620,52],[621,52],[622,52],[623,52],[624,52],[625,52],[626,52],[627,52],[628,52],[629,52],[630,52],[631,52],[632,52],[633,52],[634,52],[635,52],[636,52],[637,52],[638,52],[620,53],[621,53],[622,53],[623,53],[624,53],[625,53],[626,53],[627,53],[628,53],[629,53],[630,53],[631,53],[632,53],[633,53],[634,53],[635,53],[636,53],[637,53],[638,53],[620,54],[621,54],[622,54],[623,54],[624,54],[625,54],[626,54],[627,54],[628,54],[629,54],[630,54],[631,54],[632,54],[633,54],[634,54],[635,54],[636,54],[637,54],[620,55],[621,55],[622,55],[623,55],[624,55],[625,55],[626,55],[627,55],[628,55],[629,55],[630,55],[631,55],[632,55],[633,55],[634,55],[635,55],[636,55],[637,55],[620,56],[621,56],[622,56],[623,56],[624,56],[625,56],[626,56],[627,56],[628,56],[629,56],[630,56],[631,56],[632,56],[633,56],[634,56],[635,56],[636,56],[637,56],[620,57],[621,57],[622,57],[623,57],[624,57],[625,57],[626,57],[627,57],[628,57],[629,57],[630,57],[631,57],[632,57],[633,57],[634,57],[635,57],[636,57],[637,57],[620,58],[621,58],[622,58],[623,58],[624,58],[625,58],[626,58],[627,58],[628,58],[629,58],[630,58],[631,58],[632,58],[633,58],[634,58],[635,58],[636,58],[637,58],[620,59],[621,59],[622,59],[623,59],[624,59],[625,59],[626,59],[627,59],[628,59],[629,59],[630,59],[631,59],[632,59],[633,59],[634,59],[635,59],[636,59],[620,60],[621,60],[622,60],[623,60],[624,60],[625,60],[626,60],[627,60],[628,60],[629,60],[630,60],[631,60],[632,60],[633,60],[634,60],[635,60],[636,60],[621,61],[622,61],[623,61],[624,61],[625,61],[626,61],[627,61],[628,61],[629,61],[630,61],[631,61],[632,61],[633,61],[634,61],[635,61],[636,61],[621,62],[622,62],[623,62],[624,62],[625,62],[626,62],[627,62],[628,62],[629,62],[630,62],[631,62],[632,62],[633,62],[634,62],[635,62],[636,62],[621,63],[622,63],[623,63],[624,63],[625,63],[626,63],[627,63],[628,63],[629,63],[630,63],[631,63],[632,63],[633,63],[634,63],[635,63],[636,63],[621,64],[622,64],[623,64],[624,64],[625,64],[626,64],[627,64],[628,64],[629,64],[630,64],[631,64],[632,64],[633,64],[634,64],[635,64],[636,64],[621,65],[622,65],[623,65],[624,65],[625,65],[626,65],[627,65],[628,65],[629,65],[630,65],[631,65],[632,65],[633,65],[634,65],[635,65],[636,65],[620,66],[621,66],[622,66],[623,66],[624,66],[625,66],[626,66],[627,66],[628,66],[629,66],[630,66],[631,66],[632,66],[633,66],[634,66],[635,66],[620,67],[621,67],[622,67],[623,67],[624,67],[625,67],[626,67],[627,67],[628,67],[629,67],[630,67],[631,67],[632,67],[633,67],[634,67],[635,67],[620,68],[621,68],[622,68],[623,68],[624,68],[625,68],[626,68],[627,68],[628,68],[629,68],[630,68],[631,68],[632,68],[633,68],[634,68],[635,68],[620,69],[621,69],[622,69],[623,69],[624,69],[625,69],[626,69],[627,69],[628,69],[629,69],[630,69],[631,69],[632,69],[633,69],[634,69],[635,69],[620,70],[621,70],[622,70],[623,70],[624,70],[625,70],[626,70],[627,70],[628,70],[629,70],[630,70],[631,70],[632,70],[633,70],[634,70],[620,71],[621,71],[622,71],[623,71],[624,71],[625,71],[626,71],[627,71],[628,71],[629,71],[630,71],[631,71],[632,71],[633,71],[634,71],[620,72],[621,72],[622,72],[623,72],[624,72],[625,72],[626,72],[627,72],[628,72],[629,72],[630,72],[631,72],[632,72],[633,72],[634,72],[620,73],[621,73],[622,73],[623,73],[624,73],[625,73],[626,73],[627,73],[628,73],[629,73],[630,73],[631,73],[632,73],[633,73],[634,73],[620,74],[621,74],[622,74],[623,74],[624,74],[625,74],[626,74],[627,74],[628,74],[629,74],[630,74],[631,74],[632,74],[633,74],[634,74],[620,75],[621,75],[622,75],[623,75],[624,75],[625,75],[626,75],[627,75],[628,75],[629,75],[630,75],[631,75],[632,75],[633,75],[620,76],[621,76],[622,76],[623,76],[624,76],[625,76],[626,76],[627,76],[628,76],[629,76],[630,76],[631,76],[632,76],[633,76],[620,77],[621,77],[622,77],[623,77],[624,77],[625,77],[626,77],[627,77],[628,77],[629,77],[630,77],[631,77],[632,77],[619,78],[620,78],[621,78],[622,78],[623,78],[624,78],[625,78],[626,78],[627,78],[628,78],[629,78],[630,78],[631,78],[632,78],[619,79],[620,79],[621,79],[622,79],[623,79],[624,79],[625,79],[626,79],[627,79],[628,79],[629,79],[630,79],[631,79],[632,79],[619,80],[620,80],[621,80],[622,80],[623,80],[624,80],[625,80],[626,80],[627,80],[628,80],[629,80],[630,80],[631,80],[632,80],[619,81],[620,81],[621,81],[622,81],[623,81],[624,81],[625,81],[626,81],[627,81],[628,81],[629,81],[630,81],[631,81],[619,82],[620,82],[621,82],[622,82],[623,82],[624,82],[625,82],[626,82],[627,82],[628,82],[629,82],[630,82],[631,82],[619,83],[620,83],[621,83],[622,83],[623,83],[624,83],[625,83],[626,83],[627,83],[628,83],[629,83],[630,83],[631,83],[619,84],[620,84],[621,84],[622,84],[623,84],[624,84],[625,84],[626,84],[627,84],[628,84],[629,84],[630,84],[618,85],[619,85],[620,85],[621,85],[622,85],[623,85],[624,85],[625,85],[626,85],[627,85],[628,85],[629,85],[630,85],[618,86],[619,86],[620,86],[621,86],[622,86],[623,86],[624,86],[625,86],[626,86],[627,86],[628,86],[629,86],[630,86],[618,87],[619,87],[620,87],[621,87],[622,87],[623,87],[624,87],[625,87],[626,87],[627,87],[628,87],[629,87],[618,88],[619,88],[620,88],[621,88],[622,88],[623,88],[624,88],[625,88],[626,88],[627,88],[628,88],[629,88],[617,89],[618,89],[619,89],[620,89],[621,89],[622,89],[623,89],[624,89],[625,89],[626,89],[627,89],[628,89],[629,89],[617,90],[618,90],[619,90],[620,90],[621,90],[622,90],[623,90],[624,90],[625,90],[626,90],[627,90],[628,90],[617,91],[618,91],[619,91],[620,91],[621,91],[622,91],[623,91],[624,91],[625,91],[626,91],[627,91],[628,91],[617,92],[618,92],[619,92],[620,92],[621,92],[622,92],[623,92],[624,92],[625,92],[626,92],[627,92],[628,92],[617,93],[618,93],[619,93],[620,93],[621,93],[622,93],[623,93],[624,93],[625,93],[626,93],[627,93],[617,94],[618,94],[619,94],[620,94],[621,94],[622,94],[623,94],[624,94],[625,94],[626,94],[627,94],[616,95],[617,95],[618,95],[619,95],[620,95],[621,95],[622,95],[623,95],[624,95],[625,95],[626,95],[616,96],[617,96],[618,96],[619,96],[620,96],[621,96],[622,96],[623,96],[624,96],[625,96],[626,96],[616,97],[617,97],[618,97],[619,97],[620,97],[621,97],[622,97],[623,97],[624,97],[625,97],[626,97],[616,98],[617,98],[618,98],[619,98],[620,98],[621,98],[622,98],[623,98],[624,98],[625,98],[615,99],[616,99],[617,99],[618,99],[619,99],[620,99],[621,99],[622,99],[623,99],[624,99],[625,99],[615,100],[616,100],[617,100],[618,100],[619,100],[620,100],[621,100],[622,100],[623,100],[624,100],[615,101],[616,101],[617,101],[618,101],[619,101],[620,101],[621,101],[622,101],[623,101],[624,101],[615,102],[616,102],[617,102],[618,102],[619,102],[620,102],[621,102],[622,102],[623,102],[624,102],[614,103],[615,103],[616,103],[617,103],[618,103],[619,103],[620,103],[621,103],[622,103],[623,103],[614,104],[615,104],[616,104],[617,104],[618,104],[619,104],[620,104],[621,104],[622,104],[623,104],[614,105],[615,105],[616,105],[617,105],[618,105],[619,105],[620,105],[621,105],[622,105],[613,106],[614,106],[615,106],[616,106],[617,106],[618,106],[619,106],[620,106],[621,106],[622,106],[613,107],[614,107],[615,107],[616,107],[617,107],[618,107],[619,107],[620,107],[621,107],[613,108],[614,108],[615,108],[616,108],[617,108],[618,108],[619,108],[620,108],[621,108],[612,109],[613,109],[614,109],[615,109],[616,109],[617,109],[618,109],[619,109],[620,109],[621,109],[612,110],[613,110],[614,110],[615,110],[616,110],[617,110],[618,110],[619,110],[620,110],[612,111],[613,111],[614,111],[615,111],[616,111],[617,111],[618,111],[619,111],[620,111],[611,112],[612,112],[613,112],[614,112],[615,112],[616,112],[617,112],[618,112],[619,112],[611,113],[612,113],[613,113],[614,113],[615,113],[616,113],[617,113],[618,113],[619,113],[611,114],[612,114],[613,114],[614,114],[615,114],[616,114],[617,114],[618,114],[610,115],[611,115],[612,115],[613,115],[614,115],[615,115],[616,115],[617,115],[618,115],[610,116],[611,116],[612,116],[613,116],[614,116],[615,116],[616,116],[617,116],[609,117],[610,117],[611,117],[612,117],[613,117],[614,117],[615,117],[616,117],[609,118],[610,118],[611,118],[612,118],[613,118],[614,118],[615,118],[616,118],[609,119],[610,119],[611,119],[612,119],[613,119],[614,119],[615,119],[616,119],[608,120],[609,120],[610,120],[611,120],[612,120],[613,120],[614,120],[615,120],[608,121],[609,121],[610,121],[611,121],[612,121],[613,121],[614,121],[615,121],[608,122],[609,122],[610,122],[611,122],[612,122],[613,122],[614,122],[607,123],[608,123],[609,123],[610,123],[611,123],[612,123],[613,123],[614,123],[607,124],[608,124],[609,124],[610,124],[611,124],[612,124],[613,124],[606,125],[607,125],[608,125],[609,125],[610,125],[611,125],[612,125],[613,125],[606,126],[607,126],[608,126],[609,126],[610,126],[611,126],[612,126],[605,127],[606,127],[607,127],[608,127],[609,127],[610,127],[611,127],[612,127],[605,128],[606,128],[607,128],[608,128],[609,128],[610,128],[611,128],[604,129],[605,129],[606,129],[607,129],[608,129],[609,129],[610,129],[611,129],[604,130],[605,130],[606,130],[607,130],[608,130],[609,130],[610,130],[603,131],[604,131],[605,131],[606,131],[607,131],[608,131],[609,131],[603,132],[604,132],[605,132],[606,132],[607,132],[608,132],[609,132],[603,133],[604,133],[605,133],[606,133],[607,133],[608,133],[602,134],[603,134],[604,134],[605,134],[606,134],[607,134],[601,135],[602,135],[603,135],[604,135],[605,135],[606,135],[607,135],[601,136],[602,136],[603,136],[604,136],[605,136],[606,136],[601,137],[602,137],[603,137],[604,137],[605,137],[600,138],[601,138],[602,138],[603,138],[604,138],[605,138],[599,139],[600,139],[601,139],[602,139],[603,139],[604,139],[599,140],[600,140],[601,140],[602,140],[603,140],[598,141],[599,141],[600,141],[601,141],[602,141],[597,142],[598,142],[599,142],[600,142],[601,142],[602,142],[597,143],[598,143],[599,143],[600,143],[601,143],[596,144],[597,144],[598,144],[599,144],[600,144],[595,145],[596,145],[597,145],[598,145],[599,145],[595,146],[596,146],[597,146],[598,146],[594,147],[595,147],[596,147],[597,147],[598,147],[593,148],[594,148],[595,148],[596,148],[597,148],[592,149],[593,149],[594,149],[595,149],[596,149],[592,150],[593,150],[594,150],[595,150],[591,151],[592,151],[593,151],[594,151],[595,151],[590,152],[591,152],[592,152],[593,152],[594,152],[589,153],[590,153],[591,153],[592,153],[593,153],[589,154],[590,154],[591,154],[592,154],[588,155],[589,155],[590,155],[591,155],[587,156],[588,156],[589,156],[590,156],[591,156],[586,157],[587,157],[588,157],[589,157],[590,157],[586,158],[587,158],[588,158],[589,158],[585,159],[586,159],[587,159],[588,159],[589,159],[584,160],[585,160],[586,160],[587,160],[588,160],[583,161],[584,161],[585,161],[586,161],[587,161],[583,162],[584,162],[585,162],[586,162],[582,163],[583,163],[584,163],[585,163],[586,163],[581,164],[582,164],[583,164],[584,164],[585,164],[580,165],[581,165],[582,165],[583,165],[584,165],[579,166],[580,166],[581,166],[582,166],[583,166],[578,167],[579,167],[580,167],[581,167],[582,167],[583,167],[577,168],[578,168],[579,168],[580,168],[581,168],[582,168],[576,169],[577,169],[578,169],[579,169],[580,169],[581,169],[576,170],[577,170],[578,170],[579,170],[580,170],[574,171],[575,171],[576,171],[577,171],[578,171],[579,171],[574,172],[575,172],[576,172],[577,172],[578,172],[573,173],[574,173],[575,173],[576,173],[577,173],[572,174],[573,174],[574,174],[575,174],[576,174],[571,175],[572,175],[573,175],[574,175],[575,175],[570,176],[571,176],[572,176],[573,176],[574,176],[569,177],[570,177],[571,177],[572,177],[573,177],[568,178],[569,178],[570,178],[571,178],[572,178],[567,179],[568,179],[569,179],[570,179],[571,179],[565,180],[566,180],[567,180],[568,180],[569,180],[570,180],[564,181],[565,181],[566,181],[567,181],[568,181],[569,181],[563,182],[564,182],[565,182],[566,182],[567,182],[568,182],[562,183],[563,183],[564,183],[565,183],[566,183],[567,183],[561,184],[562,184],[563,184],[564,184],[565,184],[566,184],[560,185],[561,185],[562,185],[563,185],[564,185],[565,185],[559,186],[560,186],[561,186],[562,186],[563,186],[564,186],[558,187],[559,187],[560,187],[561,187],[562,187],[563,187],[556,188],[557,188],[558,188],[559,188],[560,188],[561,188],[562,188],[555,189],[556,189],[557,189],[558,189],[559,189],[560,189],[561,189],[554,190],[555,190],[556,190],[557,190],[558,190],[559,190],[552,191],[553,191],[554,191],[555,191],[556,191],[557,191],[558,191],[551,192],[552,192],[553,192],[554,192],[555,192],[556,192],[557,192],[550,193],[551,193],[552,193],[553,193],[554,193],[555,193],[556,193],[548,194],[549,194],[550,194],[551,194],[552,194],[553,194],[554,194],[555,194],[547,195],[548,195],[549,195],[550,195],[551,195],[552,195],[553,195],[554,195],[546,196],[547,196],[548,196],[549,196],[550,196],[551,196],[552,196],[544,197],[545,197],[546,197],[547,197],[548,197],[549,197],[550,197],[551,197],[543,198],[544,198],[545,198],[546,198],[547,198],[548,198],[549,198],[541,199],[542,199],[543,199],[544,199],[545,199],[546,199],[547,199],[548,199],[540,200],[541,200],[542,200],[543,200],[544,200],[545,200],[546,200],[538,201],[539,201],[540,201],[541,201],[542,201],[543,201],[544,201],[545,201],[537,202],[538,202],[539,202],[540,202],[541,202],[542,202],[543,202],[544,202],[535,203],[536,203],[537,203],[538,203],[539,203],[540,203],[541,203],[542,203],[534,204],[535,204],[536,204],[537,204],[538,204],[539,204],[540,204],[541,204],[532,205],[533,205],[534,205],[535,205],[536,205],[537,205],[538,205],[539,205],[531,206],[532,206],[533,206],[534,206],[535,206],[536,206],[537,206],[538,206],[529,207],[530,207],[531,207],[532,207],[533,207],[534,207],[535,207],[536,207],[527,208],[528,208],[529,208],[530,208],[531,208],[532,208],[533,208],[534,208],[535,208],[526,209],[527,209],[528,209],[529,209],[530,209],[531,209],[532,209],[533,209],[534,209],[524,210],[525,210],[526,210],[527,210],[528,210],[529,210],[530,210],[531,210],[532,210],[522,211],[523,211],[524,211],[525,211],[526,211],[527,211],[528,211],[529,211],[530,211],[520,212],[521,212],[522,212],[523,212],[524,212],[525,212],[526,212],[527,212],[528,212],[517,213],[518,213],[519,213],[520,213],[521,213],[522,213],[523,213],[524,213],[525,213],[526,213],[527,213],[515,214],[516,214],[517,214],[518,214],[519,214],[520,214],[521,214],[522,214],[523,214],[524,214],[525,214],[513,215],[514,215],[515,215],[516,215],[517,215],[518,215],[519,215],[520,215],[521,215],[522,215],[523,215],[511,216],[512,216],[513,216],[514,216],[515,216],[516,216],[517,216],[518,216],[519,216],[520,216],[509,217],[510,217],[511,217],[512,217],[513,217],[514,217],[515,217],[516,217],[517,217],[518,217],[506,218],[507,218],[508,218],[509,218],[510,218],[511,218],[512,218],[513,218],[514,218],[515,218],[516,218],[504,219],[505,219],[506,219],[507,219],[508,219],[509,219],[510,219],[511,219],[512,219],[513,219],[502,220],[503,220],[504,220],[505,220],[506,220],[507,220],[508,220],[509,220],[510,220],[511,220],[500,221],[501,221],[502,221],[503,221],[504,221],[505,221],[506,221],[507,221],[508,221],[509,221],[498,222],[499,222],[500,222],[501,222],[502,222],[503,222],[504,222],[505,222],[506,222],[507,222],[496,223],[497,223],[498,223],[499,223],[500,223],[501,223],[502,223],[503,223],[504,223],[494,224],[495,224],[496,224],[497,224],[498,224],[499,224],[500,224],[501,224],[502,224],[491,225],[492,225],[493,225],[494,225],[495,225],[496,225],[497,225],[498,225],[499,225],[500,225],[489,226],[490,226],[491,226],[492,226],[493,226],[494,226],[495,226],[496,226],[497,226],[498,226],[487,227],[488,227],[489,227],[490,227],[491,227],[492,227],[493,227],[494,227],[495,227],[485,228],[486,228],[487,228],[488,228],[489,228],[490,228],[491,228],[492,228],[493,228],[483,229],[484,229],[485,229],[486,229],[487,229],[488,229],[489,229],[490,229],[491,229],[481,230],[482,230],[483,230],[484,230],[485,230],[486,230],[487,230],[488,230],[489,230],[479,231],[480,231],[481,231],[482,231],[483,231],[484,231],[485,231],[486,231],[487,231],[477,232],[478,232],[479,232],[480,232],[481,232],[482,232],[483,232],[484,232],[485,232],[475,233],[476,233],[477,233],[478,233],[479,233],[480,233],[481,233],[482,233],[483,233],[472,234],[473,234],[474,234],[475,234],[476,234],[477,234],[478,234],[479,234],[480,234],[481,234],[470,235],[471,235],[472,235],[473,235],[474,235],[475,235],[476,235],[477,235],[478,235],[479,235],[468,236],[469,236],[470,236],[471,236],[472,236],[473,236],[474,236],[475,236],[476,236],[466,237],[467,237],[468,237],[469,237],[470,237],[471,237],[472,237],[473,237],[474,237],[464,238],[465,238],[466,238],[467,238],[468,238],[469,238],[470,238],[471,238],[472,238],[461,239],[462,239],[463,239],[464,239],[465,239],[466,239],[467,239],[468,239],[469,239],[459,240],[460,240],[461,240],[462,240],[463,240],[464,240],[465,240],[466,240],[467,240],[456,241],[457,241],[458,241],[459,241],[460,241],[461,241],[462,241],[463,241],[464,241],[465,241],[454,242],[455,242],[456,242],[457,242],[458,242],[459,242],[460,242],[461,242],[462,242],[451,243],[452,243],[453,243],[454,243],[455,243],[456,243],[457,243],[458,243],[459,243],[460,243],[449,244],[450,244],[451,244],[452,244],[453,244],[454,244],[455,244],[456,244],[457,244],[458,244],[446,245],[447,245],[448,245],[449,245],[450,245],[451,245],[452,245],[453,245],[454,245],[455,245],[456,245],[443,246],[444,246],[445,246],[446,246],[447,246],[448,246],[449,246],[450,246],[451,246],[452,246],[453,246],[454,246],[440,247],[441,247],[442,247],[443,247],[444,247],[445,247],[446,247],[447,247],[448,247],[449,247],[450,247],[451,247],[436,248],[437,248],[438,248],[439,248],[440,248],[441,248],[442,248],[443,248],[444,248],[445,248],[446,248],[447,248],[448,248],[435,249],[436,249],[437,249],[438,249],[439,249],[440,249],[441,249],[442,249],[443,249],[444,249],[445,249],[434,250],[435,250],[436,250],[437,250],[438,250],[439,250],[440,250],[441,250],[442,250],[434,251],[435,251],[436,251],[437,251],[438,251],[439,251],[435,252],[436,252],[437,252]]
+
+
+export const line6 = [[639,277],[637,278],[638,278],[639,278],[635,279],[636,279],[637,279],[638,279],[639,279],[633,280],[634,280],[635,280],[636,280],[637,280],[638,280],[639,280],[631,281],[632,281],[633,281],[634,281],[635,281],[636,281],[637,281],[638,281],[629,282],[630,282],[631,282],[632,282],[633,282],[634,282],[635,282],[636,282],[627,283],[628,283],[629,283],[630,283],[631,283],[632,283],[633,283],[634,283],[624,284],[625,284],[626,284],[627,284],[628,284],[629,284],[630,284],[631,284],[632,284],[622,285],[623,285],[624,285],[625,285],[626,285],[627,285],[628,285],[629,285],[620,286],[621,286],[622,286],[623,286],[624,286],[625,286],[626,286],[627,286],[617,287],[618,287],[619,287],[620,287],[621,287],[622,287],[623,287],[624,287],[625,287],[616,288],[617,288],[618,288],[619,288],[620,288],[621,288],[622,288],[615,289],[616,289],[617,289],[618,289],[619,289],[620,289],[616,290],[617,290],[618,290]];
+
+export const line5 = [[242,0],[243,0],[244,0],[245,0],[246,0],[247,0],[242,1],[243,1],[244,1],[245,1],[246,1],[247,1],[242,2],[243,2],[244,2],[245,2],[246,2],[247,2],[243,3],[244,3],[245,3],[246,3],[247,3],[248,3],[243,4],[244,4],[245,4],[246,4],[247,4],[248,4],[243,5],[244,5],[245,5],[246,5],[247,5],[248,5],[243,6],[244,6],[245,6],[246,6],[247,6],[248,6],[244,7],[245,7],[246,7],[247,7],[248,7],[244,8],[245,8],[246,8],[247,8],[248,8],[249,8],[244,9],[245,9],[246,9],[247,9],[248,9],[249,9],[244,10],[245,10],[246,10],[247,10],[248,10],[249,10],[245,11],[246,11],[247,11],[248,11],[249,11],[245,12],[246,12],[247,12],[248,12],[249,12],[245,13],[246,13],[247,13],[248,13],[249,13],[250,13],[245,14],[246,14],[247,14],[248,14],[249,14],[250,14],[246,15],[247,15],[248,15],[249,15],[250,15],[246,16],[247,16],[248,16],[249,16],[250,16],[246,17],[247,17],[248,17],[249,17],[250,17],[251,17],[246,18],[247,18],[248,18],[249,18],[250,18],[251,18],[247,19],[248,19],[249,19],[250,19],[251,19],[247,20],[248,20],[249,20],[250,20],[251,20],[247,21],[248,21],[249,21],[250,21],[251,21],[248,22],[249,22],[250,22],[251,22],[252,22],[248,23],[249,23],[250,23],[251,23],[252,23],[248,24],[249,24],[250,24],[251,24],[252,24],[248,25],[249,25],[250,25],[251,25],[252,25],[253,25],[248,26],[249,26],[250,26],[251,26],[252,26],[253,26],[249,27],[250,27],[251,27],[252,27],[253,27],[249,28],[250,28],[251,28],[252,28],[253,28],[249,29],[250,29],[251,29],[252,29],[253,29],[254,29],[250,30],[251,30],[252,30],[253,30],[254,30],[250,31],[251,31],[252,31],[253,31],[254,31],[250,32],[251,32],[252,32],[253,32],[254,32],[255,32],[251,33],[252,33],[253,33],[254,33],[255,33],[251,34],[252,34],[253,34],[254,34],[255,34],[251,35],[252,35],[253,35],[254,35],[255,35],[256,35],[251,36],[252,36],[253,36],[254,36],[255,36],[256,36],[252,37],[253,37],[254,37],[255,37],[256,37],[252,38],[253,38],[254,38],[255,38],[256,38],[252,39],[253,39],[254,39],[255,39],[256,39],[257,39],[253,40],[254,40],[255,40],[256,40],[257,40],[253,41],[254,41],[255,41],[256,41],[257,41],[253,42],[254,42],[255,42],[256,42],[257,42],[253,43],[254,43],[255,43],[256,43],[257,43],[258,43],[254,44],[255,44],[256,44],[257,44],[258,44],[254,45],[255,45],[256,45],[257,45],[258,45],[259,45],[254,46],[255,46],[256,46],[257,46],[258,46],[259,46],[255,47],[256,47],[257,47],[258,47],[259,47],[255,48],[256,48],[257,48],[258,48],[259,48],[256,49],[257,49],[258,49],[259,49],[260,49],[256,50],[257,50],[258,50],[259,50],[260,50],[256,51],[257,51],[258,51],[259,51],[260,51],[261,51],[256,52],[257,52],[258,52],[259,52],[260,52],[261,52],[257,53],[258,53],[259,53],[260,53],[261,53],[257,54],[258,54],[259,54],[260,54],[261,54],[262,54],[258,55],[259,55],[260,55],[261,55],[262,55],[258,56],[259,56],[260,56],[261,56],[262,56],[263,56],[259,57],[260,57],[261,57],[262,57],[263,57],[259,58],[260,58],[261,58],[262,58],[263,58],[260,59],[261,59],[262,59],[263,59],[264,59],[260,60],[261,60],[262,60],[263,60],[264,60],[260,61],[261,61],[262,61],[263,61],[264,61],[265,61],[261,62],[262,62],[263,62],[264,62],[265,62],[261,63],[262,63],[263,63],[264,63],[265,63],[262,64],[263,64],[264,64],[265,64],[266,64],[262,65],[263,65],[264,65],[265,65],[266,65],[263,66],[264,66],[265,66],[266,66],[267,66],[263,67],[264,67],[265,67],[266,67],[267,67],[264,68],[265,68],[266,68],[267,68],[268,68],[264,69],[265,69],[266,69],[267,69],[268,69],[269,69],[265,70],[266,70],[267,70],[268,70],[269,70],[270,70],[266,71],[267,71],[268,71],[269,71],[270,71],[271,71],[266,72],[267,72],[268,72],[269,72],[270,72],[271,72],[267,73],[268,73],[269,73],[270,73],[271,73],[272,73],[267,74],[268,74],[269,74],[270,74],[271,74],[272,74],[268,75],[269,75],[270,75],[271,75],[272,75],[273,75],[268,76],[269,76],[270,76],[271,76],[272,76],[273,76],[269,77],[270,77],[271,77],[272,77],[273,77],[274,77],[269,78],[270,78],[271,78],[272,78],[273,78],[274,78],[275,78],[270,79],[271,79],[272,79],[273,79],[274,79],[275,79],[271,80],[272,80],[273,80],[274,80],[275,80],[276,80],[271,81],[272,81],[273,81],[274,81],[275,81],[276,81],[277,81],[272,82],[273,82],[274,82],[275,82],[276,82],[277,82],[272,83],[273,83],[274,83],[275,83],[276,83],[277,83],[278,83],[273,84],[274,84],[275,84],[276,84],[277,84],[278,84],[279,84],[274,85],[275,85],[276,85],[277,85],[278,85],[279,85],[280,85],[274,86],[275,86],[276,86],[277,86],[278,86],[279,86],[280,86],[281,86],[275,87],[276,87],[277,87],[278,87],[279,87],[280,87],[281,87],[276,88],[277,88],[278,88],[279,88],[280,88],[281,88],[282,88],[276,89],[277,89],[278,89],[279,89],[280,89],[281,89],[282,89],[283,89],[277,90],[278,90],[279,90],[280,90],[281,90],[282,90],[283,90],[278,91],[279,91],[280,91],[281,91],[282,91],[283,91],[284,91],[279,92],[280,92],[281,92],[282,92],[283,92],[284,92],[285,92],[279,93],[280,93],[281,93],[282,93],[283,93],[284,93],[285,93],[286,93],[280,94],[281,94],[282,94],[283,94],[284,94],[285,94],[286,94],[281,95],[282,95],[283,95],[284,95],[285,95],[286,95],[287,95],[281,96],[282,96],[283,96],[284,96],[285,96],[286,96],[287,96],[288,96],[282,97],[283,97],[284,97],[285,97],[286,97],[287,97],[288,97],[289,97],[283,98],[284,98],[285,98],[286,98],[287,98],[288,98],[289,98],[290,98],[284,99],[285,99],[286,99],[287,99],[288,99],[289,99],[290,99],[291,99],[284,100],[285,100],[286,100],[287,100],[288,100],[289,100],[290,100],[291,100],[292,100],[285,101],[286,101],[287,101],[288,101],[289,101],[290,101],[291,101],[292,101],[293,101],[286,102],[287,102],[288,102],[289,102],[290,102],[291,102],[292,102],[293,102],[294,102],[287,103],[288,103],[289,103],[290,103],[291,103],[292,103],[293,103],[294,103],[287,104],[288,104],[289,104],[290,104],[291,104],[292,104],[293,104],[294,104],[295,104],[288,105],[289,105],[290,105],[291,105],[292,105],[293,105],[294,105],[295,105],[296,105],[289,106],[290,106],[291,106],[292,106],[293,106],[294,106],[295,106],[296,106],[297,106],[290,107],[291,107],[292,107],[293,107],[294,107],[295,107],[296,107],[297,107],[291,108],[292,108],[293,108],[294,108],[295,108],[296,108],[297,108],[298,108],[292,109],[293,109],[294,109],[295,109],[296,109],[297,109],[298,109],[299,109],[293,110],[294,110],[295,110],[296,110],[297,110],[298,110],[299,110],[300,110],[294,111],[295,111],[296,111],[297,111],[298,111],[299,111],[300,111],[301,111],[295,112],[296,112],[297,112],[298,112],[299,112],[300,112],[301,112],[302,112],[295,113],[296,113],[297,113],[298,113],[299,113],[300,113],[301,113],[302,113],[303,113],[296,114],[297,114],[298,114],[299,114],[300,114],[301,114],[302,114],[303,114],[304,114],[297,115],[298,115],[299,115],[300,115],[301,115],[302,115],[303,115],[304,115],[298,116],[299,116],[300,116],[301,116],[302,116],[303,116],[304,116],[305,116],[298,117],[299,117],[300,117],[301,117],[302,117],[303,117],[304,117],[305,117],[306,117],[299,118],[300,118],[301,118],[302,118],[303,118],[304,118],[305,118],[306,118],[307,118],[300,119],[301,119],[302,119],[303,119],[304,119],[305,119],[306,119],[307,119],[308,119],[301,120],[302,120],[303,120],[304,120],[305,120],[306,120],[307,120],[308,120],[309,120],[302,121],[303,121],[304,121],[305,121],[306,121],[307,121],[308,121],[309,121],[310,121],[302,122],[303,122],[304,122],[305,122],[306,122],[307,122],[308,122],[309,122],[310,122],[311,122],[303,123],[304,123],[305,123],[306,123],[307,123],[308,123],[309,123],[310,123],[311,123],[312,123],[304,124],[305,124],[306,124],[307,124],[308,124],[309,124],[310,124],[311,124],[312,124],[305,125],[306,125],[307,125],[308,125],[309,125],[310,125],[311,125],[312,125],[313,125],[306,126],[307,126],[308,126],[309,126],[310,126],[311,126],[312,126],[313,126],[314,126],[307,127],[308,127],[309,127],[310,127],[311,127],[312,127],[313,127],[314,127],[308,128],[309,128],[310,128],[311,128],[312,128],[313,128],[314,128],[315,128],[308,129],[309,129],[310,129],[311,129],[312,129],[313,129],[314,129],[315,129],[316,129],[309,130],[310,130],[311,130],[312,130],[313,130],[314,130],[315,130],[316,130],[317,130],[310,131],[311,131],[312,131],[313,131],[314,131],[315,131],[316,131],[317,131],[318,131],[311,132],[312,132],[313,132],[314,132],[315,132],[316,132],[317,132],[318,132],[319,132],[312,133],[313,133],[314,133],[315,133],[316,133],[317,133],[318,133],[319,133],[320,133],[313,134],[314,134],[315,134],[316,134],[317,134],[318,134],[319,134],[320,134],[321,134],[313,135],[314,135],[315,135],[316,135],[317,135],[318,135],[319,135],[320,135],[321,135],[322,135],[323,135],[314,136],[315,136],[316,136],[317,136],[318,136],[319,136],[320,136],[321,136],[322,136],[323,136],[324,136],[315,137],[316,137],[317,137],[318,137],[319,137],[320,137],[321,137],[322,137],[323,137],[324,137],[325,137],[316,138],[317,138],[318,138],[319,138],[320,138],[321,138],[322,138],[323,138],[324,138],[325,138],[326,138],[318,139],[319,139],[320,139],[321,139],[322,139],[323,139],[324,139],[325,139],[326,139],[327,139],[328,139],[319,140],[320,140],[321,140],[322,140],[323,140],[324,140],[325,140],[326,140],[327,140],[328,140],[329,140],[320,141],[321,141],[322,141],[323,141],[324,141],[325,141],[326,141],[327,141],[328,141],[329,141],[330,141],[321,142],[322,142],[323,142],[324,142],[325,142],[326,142],[327,142],[328,142],[329,142],[330,142],[331,142],[322,143],[323,143],[324,143],[325,143],[326,143],[327,143],[328,143],[329,143],[330,143],[331,143],[332,143],[323,144],[324,144],[325,144],[326,144],[327,144],[328,144],[329,144],[330,144],[331,144],[332,144],[333,144],[334,144],[324,145],[325,145],[326,145],[327,145],[328,145],[329,145],[330,145],[331,145],[332,145],[333,145],[334,145],[335,145],[325,146],[326,146],[327,146],[328,146],[329,146],[330,146],[331,146],[332,146],[333,146],[334,146],[335,146],[336,146],[326,147],[327,147],[328,147],[329,147],[330,147],[331,147],[332,147],[333,147],[334,147],[335,147],[336,147],[337,147],[328,148],[329,148],[330,148],[331,148],[332,148],[333,148],[334,148],[335,148],[336,148],[337,148],[338,148],[329,149],[330,149],[331,149],[332,149],[333,149],[334,149],[335,149],[336,149],[337,149],[338,149],[339,149],[340,149],[330,150],[331,150],[332,150],[333,150],[334,150],[335,150],[336,150],[337,150],[338,150],[339,150],[340,150],[341,150],[331,151],[332,151],[333,151],[334,151],[335,151],[336,151],[337,151],[338,151],[339,151],[340,151],[341,151],[342,151],[332,152],[333,152],[334,152],[335,152],[336,152],[337,152],[338,152],[339,152],[340,152],[341,152],[342,152],[343,152],[344,152],[334,153],[335,153],[336,153],[337,153],[338,153],[339,153],[340,153],[341,153],[342,153],[343,153],[344,153],[345,153],[335,154],[336,154],[337,154],[338,154],[339,154],[340,154],[341,154],[342,154],[343,154],[344,154],[345,154],[346,154],[336,155],[337,155],[338,155],[339,155],[340,155],[341,155],[342,155],[343,155],[344,155],[345,155],[346,155],[347,155],[348,155],[337,156],[338,156],[339,156],[340,156],[341,156],[342,156],[343,156],[344,156],[345,156],[346,156],[347,156],[348,156],[349,156],[339,157],[340,157],[341,157],[342,157],[343,157],[344,157],[345,157],[346,157],[347,157],[348,157],[349,157],[350,157],[351,157],[340,158],[341,158],[342,158],[343,158],[344,158],[345,158],[346,158],[347,158],[348,158],[349,158],[350,158],[351,158],[352,158],[341,159],[342,159],[343,159],[344,159],[345,159],[346,159],[347,159],[348,159],[349,159],[350,159],[351,159],[352,159],[353,159],[354,159],[343,160],[344,160],[345,160],[346,160],[347,160],[348,160],[349,160],[350,160],[351,160],[352,160],[353,160],[354,160],[355,160],[344,161],[345,161],[346,161],[347,161],[348,161],[349,161],[350,161],[351,161],[352,161],[353,161],[354,161],[355,161],[356,161],[357,161],[345,162],[346,162],[347,162],[348,162],[349,162],[350,162],[351,162],[352,162],[353,162],[354,162],[355,162],[356,162],[357,162],[358,162],[347,163],[348,163],[349,163],[350,163],[351,163],[352,163],[353,163],[354,163],[355,163],[356,163],[357,163],[358,163],[359,163],[360,163],[348,164],[349,164],[350,164],[351,164],[352,164],[353,164],[354,164],[355,164],[356,164],[357,164],[358,164],[359,164],[360,164],[361,164],[350,165],[351,165],[352,165],[353,165],[354,165],[355,165],[356,165],[357,165],[358,165],[359,165],[360,165],[361,165],[362,165],[363,165],[351,166],[352,166],[353,166],[354,166],[355,166],[356,166],[357,166],[358,166],[359,166],[360,166],[361,166],[362,166],[363,166],[364,166],[353,167],[354,167],[355,167],[356,167],[357,167],[358,167],[359,167],[360,167],[361,167],[362,167],[363,167],[364,167],[365,167],[366,167],[354,168],[355,168],[356,168],[357,168],[358,168],[359,168],[360,168],[361,168],[362,168],[363,168],[364,168],[365,168],[366,168],[367,168],[356,169],[357,169],[358,169],[359,169],[360,169],[361,169],[362,169],[363,169],[364,169],[365,169],[366,169],[367,169],[368,169],[369,169],[357,170],[358,170],[359,170],[360,170],[361,170],[362,170],[363,170],[364,170],[365,170],[366,170],[367,170],[368,170],[369,170],[370,170],[371,170],[359,171],[360,171],[361,171],[362,171],[363,171],[364,171],[365,171],[366,171],[367,171],[368,171],[369,171],[370,171],[371,171],[372,171],[360,172],[361,172],[362,172],[363,172],[364,172],[365,172],[366,172],[367,172],[368,172],[369,172],[370,172],[371,172],[372,172],[373,172],[362,173],[363,173],[364,173],[365,173],[366,173],[367,173],[368,173],[369,173],[370,173],[371,173],[372,173],[373,173],[374,173],[375,173],[363,174],[364,174],[365,174],[366,174],[367,174],[368,174],[369,174],[370,174],[371,174],[372,174],[373,174],[374,174],[375,174],[376,174],[365,175],[366,175],[367,175],[368,175],[369,175],[370,175],[371,175],[372,175],[373,175],[374,175],[375,175],[376,175],[377,175],[378,175],[367,176],[368,176],[369,176],[370,176],[371,176],[372,176],[373,176],[374,176],[375,176],[376,176],[377,176],[378,176],[379,176],[368,177],[369,177],[370,177],[371,177],[372,177],[373,177],[374,177],[375,177],[376,177],[377,177],[378,177],[379,177],[380,177],[381,177],[369,178],[370,178],[371,178],[372,178],[373,178],[374,178],[375,178],[376,178],[377,178],[378,178],[379,178],[380,178],[381,178],[382,178],[371,179],[372,179],[373,179],[374,179],[375,179],[376,179],[377,179],[378,179],[379,179],[380,179],[381,179],[382,179],[383,179],[384,179],[373,180],[374,180],[375,180],[376,180],[377,180],[378,180],[379,180],[380,180],[381,180],[382,180],[383,180],[384,180],[385,180],[374,181],[375,181],[376,181],[377,181],[378,181],[379,181],[380,181],[381,181],[382,181],[383,181],[384,181],[385,181],[386,181],[387,181],[376,182],[377,182],[378,182],[379,182],[380,182],[381,182],[382,182],[383,182],[384,182],[385,182],[386,182],[387,182],[388,182],[389,182],[377,183],[378,183],[379,183],[380,183],[381,183],[382,183],[383,183],[384,183],[385,183],[386,183],[387,183],[388,183],[389,183],[390,183],[379,184],[380,184],[381,184],[382,184],[383,184],[384,184],[385,184],[386,184],[387,184],[388,184],[389,184],[390,184],[391,184],[392,184],[381,185],[382,185],[383,185],[384,185],[385,185],[386,185],[387,185],[388,185],[389,185],[390,185],[391,185],[392,185],[393,185],[382,186],[383,186],[384,186],[385,186],[386,186],[387,186],[388,186],[389,186],[390,186],[391,186],[392,186],[393,186],[394,186],[395,186],[384,187],[385,187],[386,187],[387,187],[388,187],[389,187],[390,187],[391,187],[392,187],[393,187],[394,187],[395,187],[396,187],[397,187],[386,188],[387,188],[388,188],[389,188],[390,188],[391,188],[392,188],[393,188],[394,188],[395,188],[396,188],[397,188],[398,188],[387,189],[388,189],[389,189],[390,189],[391,189],[392,189],[393,189],[394,189],[395,189],[396,189],[397,189],[398,189],[399,189],[400,189],[389,190],[390,190],[391,190],[392,190],[393,190],[394,190],[395,190],[396,190],[397,190],[398,190],[399,190],[400,190],[401,190],[390,191],[391,191],[392,191],[393,191],[394,191],[395,191],[396,191],[397,191],[398,191],[399,191],[400,191],[401,191],[402,191],[403,191],[392,192],[393,192],[394,192],[395,192],[396,192],[397,192],[398,192],[399,192],[400,192],[401,192],[402,192],[403,192],[404,192],[405,192],[394,193],[395,193],[396,193],[397,193],[398,193],[399,193],[400,193],[401,193],[402,193],[403,193],[404,193],[405,193],[406,193],[395,194],[396,194],[397,194],[398,194],[399,194],[400,194],[401,194],[402,194],[403,194],[404,194],[405,194],[406,194],[407,194],[408,194],[397,195],[398,195],[399,195],[400,195],[401,195],[402,195],[403,195],[404,195],[405,195],[406,195],[407,195],[408,195],[409,195],[410,195],[398,196],[399,196],[400,196],[401,196],[402,196],[403,196],[404,196],[405,196],[406,196],[407,196],[408,196],[409,196],[410,196],[411,196],[412,196],[400,197],[401,197],[402,197],[403,197],[404,197],[405,197],[406,197],[407,197],[408,197],[409,197],[410,197],[411,197],[412,197],[413,197],[414,197],[402,198],[403,198],[404,198],[405,198],[406,198],[407,198],[408,198],[409,198],[410,198],[411,198],[412,198],[413,198],[414,198],[415,198],[416,198],[403,199],[404,199],[405,199],[406,199],[407,199],[408,199],[409,199],[410,199],[411,199],[412,199],[413,199],[414,199],[415,199],[416,199],[417,199],[418,199],[405,200],[406,200],[407,200],[408,200],[409,200],[410,200],[411,200],[412,200],[413,200],[414,200],[415,200],[416,200],[417,200],[418,200],[419,200],[420,200],[407,201],[408,201],[409,201],[410,201],[411,201],[412,201],[413,201],[414,201],[415,201],[416,201],[417,201],[418,201],[419,201],[420,201],[421,201],[422,201],[409,202],[410,202],[411,202],[412,202],[413,202],[414,202],[415,202],[416,202],[417,202],[418,202],[419,202],[420,202],[421,202],[422,202],[423,202],[410,203],[411,203],[412,203],[413,203],[414,203],[415,203],[416,203],[417,203],[418,203],[419,203],[420,203],[421,203],[422,203],[423,203],[424,203],[425,203],[412,204],[413,204],[414,204],[415,204],[416,204],[417,204],[418,204],[419,204],[420,204],[421,204],[422,204],[423,204],[424,204],[425,204],[426,204],[427,204],[414,205],[415,205],[416,205],[417,205],[418,205],[419,205],[420,205],[421,205],[422,205],[423,205],[424,205],[425,205],[426,205],[427,205],[428,205],[429,205],[416,206],[417,206],[418,206],[419,206],[420,206],[421,206],[422,206],[423,206],[424,206],[425,206],[426,206],[427,206],[428,206],[429,206],[430,206],[418,207],[419,207],[420,207],[421,207],[422,207],[423,207],[424,207],[425,207],[426,207],[427,207],[428,207],[429,207],[430,207],[431,207],[432,207],[419,208],[420,208],[421,208],[422,208],[423,208],[424,208],[425,208],[426,208],[427,208],[428,208],[429,208],[430,208],[431,208],[432,208],[433,208],[434,208],[421,209],[422,209],[423,209],[424,209],[425,209],[426,209],[427,209],[428,209],[429,209],[430,209],[431,209],[432,209],[433,209],[434,209],[435,209],[436,209],[423,210],[424,210],[425,210],[426,210],[427,210],[428,210],[429,210],[430,210],[431,210],[432,210],[433,210],[434,210],[435,210],[436,210],[437,210],[438,210],[425,211],[426,211],[427,211],[428,211],[429,211],[430,211],[431,211],[432,211],[433,211],[434,211],[435,211],[436,211],[437,211],[438,211],[439,211],[440,211],[426,212],[427,212],[428,212],[429,212],[430,212],[431,212],[432,212],[433,212],[434,212],[435,212],[436,212],[437,212],[438,212],[439,212],[440,212],[441,212],[442,212],[428,213],[429,213],[430,213],[431,213],[432,213],[433,213],[434,213],[435,213],[436,213],[437,213],[438,213],[439,213],[440,213],[441,213],[442,213],[443,213],[444,213],[430,214],[431,214],[432,214],[433,214],[434,214],[435,214],[436,214],[437,214],[438,214],[439,214],[440,214],[441,214],[442,214],[443,214],[444,214],[445,214],[446,214],[432,215],[433,215],[434,215],[435,215],[436,215],[437,215],[438,215],[439,215],[440,215],[441,215],[442,215],[443,215],[444,215],[445,215],[446,215],[447,215],[448,215],[434,216],[435,216],[436,216],[437,216],[438,216],[439,216],[440,216],[441,216],[442,216],[443,216],[444,216],[445,216],[446,216],[447,216],[448,216],[449,216],[436,217],[437,217],[438,217],[439,217],[440,217],[441,217],[442,217],[443,217],[444,217],[445,217],[446,217],[447,217],[448,217],[449,217],[450,217],[451,217],[439,218],[440,218],[441,218],[442,218],[443,218],[444,218],[445,218],[446,218],[447,218],[448,218],[449,218],[450,218],[451,218],[452,218],[453,218],[454,218],[441,219],[442,219],[443,219],[444,219],[445,219],[446,219],[447,219],[448,219],[449,219],[450,219],[451,219],[452,219],[453,219],[454,219],[455,219],[443,220],[444,220],[445,220],[446,220],[447,220],[448,220],[449,220],[450,220],[451,220],[452,220],[453,220],[454,220],[455,220],[456,220],[457,220],[445,221],[446,221],[447,221],[448,221],[449,221],[450,221],[451,221],[452,221],[453,221],[454,221],[455,221],[456,221],[457,221],[458,221],[459,221],[447,222],[448,222],[449,222],[450,222],[451,222],[452,222],[453,222],[454,222],[455,222],[456,222],[457,222],[458,222],[459,222],[460,222],[461,222],[449,223],[450,223],[451,223],[452,223],[453,223],[454,223],[455,223],[456,223],[457,223],[458,223],[459,223],[460,223],[461,223],[462,223],[463,223],[451,224],[452,224],[453,224],[454,224],[455,224],[456,224],[457,224],[458,224],[459,224],[460,224],[461,224],[462,224],[463,224],[464,224],[465,224],[453,225],[454,225],[455,225],[456,225],[457,225],[458,225],[459,225],[460,225],[461,225],[462,225],[463,225],[464,225],[465,225],[466,225],[455,226],[456,226],[457,226],[458,226],[459,226],[460,226],[461,226],[462,226],[463,226],[464,226],[465,226],[466,226],[467,226],[468,226],[456,227],[457,227],[458,227],[459,227],[460,227],[461,227],[462,227],[463,227],[464,227],[465,227],[466,227],[467,227],[468,227],[469,227],[470,227],[458,228],[459,228],[460,228],[461,228],[462,228],[463,228],[464,228],[465,228],[466,228],[467,228],[468,228],[469,228],[470,228],[471,228],[472,228],[473,228],[460,229],[461,229],[462,229],[463,229],[464,229],[465,229],[466,229],[467,229],[468,229],[469,229],[470,229],[471,229],[472,229],[473,229],[474,229],[475,229],[462,230],[463,230],[464,230],[465,230],[466,230],[467,230],[468,230],[469,230],[470,230],[471,230],[472,230],[473,230],[474,230],[475,230],[476,230],[477,230],[464,231],[465,231],[466,231],[467,231],[468,231],[469,231],[470,231],[471,231],[472,231],[473,231],[474,231],[475,231],[476,231],[477,231],[478,231],[479,231],[466,232],[467,232],[468,232],[469,232],[470,232],[471,232],[472,232],[473,232],[474,232],[475,232],[476,232],[477,232],[478,232],[479,232],[480,232],[481,232],[468,233],[469,233],[470,233],[471,233],[472,233],[473,233],[474,233],[475,233],[476,233],[477,233],[478,233],[479,233],[480,233],[481,233],[482,233],[471,234],[472,234],[473,234],[474,234],[475,234],[476,234],[477,234],[478,234],[479,234],[480,234],[481,234],[482,234],[483,234],[484,234],[485,234],[473,235],[474,235],[475,235],[476,235],[477,235],[478,235],[479,235],[480,235],[481,235],[482,235],[483,235],[484,235],[485,235],[486,235],[487,235],[475,236],[476,236],[477,236],[478,236],[479,236],[480,236],[481,236],[482,236],[483,236],[484,236],[485,236],[486,236],[487,236],[488,236],[477,237],[478,237],[479,237],[480,237],[481,237],[482,237],[483,237],[484,237],[485,237],[486,237],[487,237],[488,237],[489,237],[490,237],[479,238],[480,238],[481,238],[482,238],[483,238],[484,238],[485,238],[486,238],[487,238],[488,238],[489,238],[490,238],[491,238],[492,238],[481,239],[482,239],[483,239],[484,239],[485,239],[486,239],[487,239],[488,239],[489,239],[490,239],[491,239],[492,239],[493,239],[494,239],[482,240],[483,240],[484,240],[485,240],[486,240],[487,240],[488,240],[489,240],[490,240],[491,240],[492,240],[493,240],[494,240],[495,240],[496,240],[484,241],[485,241],[486,241],[487,241],[488,241],[489,241],[490,241],[491,241],[492,241],[493,241],[494,241],[495,241],[496,241],[497,241],[498,241],[486,242],[487,242],[488,242],[489,242],[490,242],[491,242],[492,242],[493,242],[494,242],[495,242],[496,242],[497,242],[498,242],[499,242],[500,242],[487,243],[488,243],[489,243],[490,243],[491,243],[492,243],[493,243],[494,243],[495,243],[496,243],[497,243],[498,243],[499,243],[500,243],[501,243],[489,244],[490,244],[491,244],[492,244],[493,244],[494,244],[495,244],[496,244],[497,244],[498,244],[499,244],[500,244],[501,244],[502,244],[490,245],[491,245],[492,245],[493,245],[494,245],[495,245],[496,245],[497,245],[498,245],[499,245],[500,245],[501,245],[502,245],[503,245],[504,245],[492,246],[493,246],[494,246],[495,246],[496,246],[497,246],[498,246],[499,246],[500,246],[501,246],[502,246],[503,246],[504,246],[505,246],[493,247],[494,247],[495,247],[496,247],[497,247],[498,247],[499,247],[500,247],[501,247],[502,247],[503,247],[504,247],[505,247],[506,247],[495,248],[496,248],[497,248],[498,248],[499,248],[500,248],[501,248],[502,248],[503,248],[504,248],[505,248],[506,248],[507,248],[508,248],[496,249],[497,249],[498,249],[499,249],[500,249],[501,249],[502,249],[503,249],[504,249],[505,249],[506,249],[507,249],[508,249],[509,249],[498,250],[499,250],[500,250],[501,250],[502,250],[503,250],[504,250],[505,250],[506,250],[507,250],[508,250],[509,250],[510,250],[499,251],[500,251],[501,251],[502,251],[503,251],[504,251],[505,251],[506,251],[507,251],[508,251],[509,251],[510,251],[511,251],[512,251],[500,252],[501,252],[502,252],[503,252],[504,252],[505,252],[506,252],[507,252],[508,252],[509,252],[510,252],[511,252],[512,252],[513,252],[502,253],[503,253],[504,253],[505,253],[506,253],[507,253],[508,253],[509,253],[510,253],[511,253],[512,253],[513,253],[514,253],[503,254],[504,254],[505,254],[506,254],[507,254],[508,254],[509,254],[510,254],[511,254],[512,254],[513,254],[514,254],[515,254],[516,254],[505,255],[506,255],[507,255],[508,255],[509,255],[510,255],[511,255],[512,255],[513,255],[514,255],[515,255],[516,255],[517,255],[518,255],[519,255],[506,256],[507,256],[508,256],[509,256],[510,256],[511,256],[512,256],[513,256],[514,256],[515,256],[516,256],[517,256],[518,256],[519,256],[520,256],[521,256],[507,257],[508,257],[509,257],[510,257],[511,257],[512,257],[513,257],[514,257],[515,257],[516,257],[517,257],[518,257],[519,257],[520,257],[521,257],[522,257],[508,258],[509,258],[510,258],[511,258],[512,258],[513,258],[514,258],[515,258],[516,258],[517,258],[518,258],[519,258],[520,258],[521,258],[522,258],[523,258],[524,258],[509,259],[510,259],[511,259],[512,259],[513,259],[514,259],[515,259],[516,259],[517,259],[518,259],[519,259],[520,259],[521,259],[522,259],[523,259],[524,259],[525,259],[511,260],[512,260],[513,260],[514,260],[515,260],[516,260],[517,260],[518,260],[519,260],[520,260],[521,260],[522,260],[523,260],[524,260],[525,260],[526,260],[527,260],[512,261],[513,261],[514,261],[515,261],[516,261],[517,261],[518,261],[519,261],[520,261],[521,261],[522,261],[523,261],[524,261],[525,261],[526,261],[527,261],[528,261],[513,262],[514,262],[515,262],[516,262],[517,262],[518,262],[519,262],[520,262],[521,262],[522,262],[523,262],[524,262],[525,262],[526,262],[527,262],[528,262],[529,262],[514,263],[515,263],[516,263],[517,263],[518,263],[519,263],[520,263],[521,263],[522,263],[523,263],[524,263],[525,263],[526,263],[527,263],[528,263],[529,263],[530,263],[516,264],[517,264],[518,264],[519,264],[520,264],[521,264],[522,264],[523,264],[524,264],[525,264],[526,264],[527,264],[528,264],[529,264],[530,264],[531,264],[517,265],[518,265],[519,265],[520,265],[521,265],[522,265],[523,265],[524,265],[525,265],[526,265],[527,265],[528,265],[529,265],[530,265],[531,265],[532,265],[519,266],[520,266],[521,266],[522,266],[523,266],[524,266],[525,266],[526,266],[527,266],[528,266],[529,266],[530,266],[531,266],[532,266],[533,266],[520,267],[521,267],[522,267],[523,267],[524,267],[525,267],[526,267],[527,267],[528,267],[529,267],[530,267],[531,267],[532,267],[533,267],[534,267],[521,268],[522,268],[523,268],[524,268],[525,268],[526,268],[527,268],[528,268],[529,268],[530,268],[531,268],[532,268],[533,268],[534,268],[535,268],[523,269],[524,269],[525,269],[526,269],[527,269],[528,269],[529,269],[530,269],[531,269],[532,269],[533,269],[534,269],[535,269],[536,269],[524,270],[525,270],[526,270],[527,270],[528,270],[529,270],[530,270],[531,270],[532,270],[533,270],[534,270],[535,270],[536,270],[537,270],[525,271],[526,271],[527,271],[528,271],[529,271],[530,271],[531,271],[532,271],[533,271],[534,271],[535,271],[536,271],[537,271],[538,271],[526,272],[527,272],[528,272],[529,272],[530,272],[531,272],[532,272],[533,272],[534,272],[535,272],[536,272],[537,272],[538,272],[527,273],[528,273],[529,273],[530,273],[531,273],[532,273],[533,273],[534,273],[535,273],[536,273],[537,273],[538,273],[539,273],[528,274],[529,274],[530,274],[531,274],[532,274],[533,274],[534,274],[535,274],[536,274],[537,274],[538,274],[539,274],[540,274],[529,275],[530,275],[531,275],[532,275],[533,275],[534,275],[535,275],[536,275],[537,275],[538,275],[539,275],[540,275],[530,276],[531,276],[532,276],[533,276],[534,276],[535,276],[536,276],[537,276],[538,276],[539,276],[540,276],[541,276],[531,277],[532,277],[533,277],[534,277],[535,277],[536,277],[537,277],[538,277],[539,277],[540,277],[541,277],[532,278],[533,278],[534,278],[535,278],[536,278],[537,278],[538,278],[539,278],[540,278],[541,278],[542,278],[532,279],[533,279],[534,279],[535,279],[536,279],[537,279],[538,279],[539,279],[540,279],[541,279],[542,279],[533,280],[534,280],[535,280],[536,280],[537,280],[538,280],[539,280],[540,280],[541,280],[542,280],[543,280],[534,281],[535,281],[536,281],[537,281],[538,281],[539,281],[540,281],[541,281],[542,281],[543,281],[535,282],[536,282],[537,282],[538,282],[539,282],[540,282],[541,282],[542,282],[543,282],[535,283],[536,283],[537,283],[538,283],[539,283],[540,283],[541,283],[542,283],[543,283],[544,283],[536,284],[537,284],[538,284],[539,284],[540,284],[541,284],[542,284],[543,284],[544,284],[536,285],[537,285],[538,285],[539,285],[540,285],[541,285],[542,285],[543,285],[544,285],[537,286],[538,286],[539,286],[540,286],[541,286],[542,286],[543,286],[544,286],[537,287],[538,287],[539,287],[540,287],[541,287],[542,287],[543,287],[544,287],[545,287],[538,288],[539,288],[540,288],[541,288],[542,288],[543,288],[544,288],[545,288],[538,289],[539,289],[540,289],[541,289],[542,289],[543,289],[544,289],[545,289],[538,290],[539,290],[540,290],[541,290],[542,290],[543,290],[544,290],[545,290],[539,291],[540,291],[541,291],[542,291],[543,291],[544,291],[545,291],[539,292],[540,292],[541,292],[542,292],[543,292],[544,292],[545,292],[539,293],[540,293],[541,293],[542,293],[543,293],[544,293],[545,293],[539,294],[540,294],[541,294],[542,294],[543,294],[544,294],[545,294],[540,295],[541,295],[542,295],[543,295],[544,295],[545,295],[540,296],[541,296],[542,296],[543,296],[544,296],[545,296],[540,297],[541,297],[542,297],[543,297],[544,297],[545,297],[540,298],[541,298],[542,298],[543,298],[544,298],[540,299],[541,299],[542,299],[543,299],[544,299],[540,300],[541,300],[542,300],[543,300],[544,300],[540,301],[541,301],[542,301],[543,301],[544,301],[540,302],[541,302],[542,302],[543,302],[544,302],[540,303],[541,303],[542,303],[543,303],[544,303],[540,304],[541,304],[542,304],[543,304],[540,305],[541,305],[542,305],[543,305],[539,306],[540,306],[541,306],[542,306],[543,306],[539,307],[540,307],[541,307],[542,307],[539,308],[540,308],[541,308],[542,308],[539,309],[540,309],[541,309],[538,310],[539,310],[540,310],[541,310],[538,311],[539,311],[540,311],[537,312],[538,312],[539,312],[537,313],[538,313],[539,313],[536,314],[537,314],[538,314],[535,315],[536,315],[537,315],[535,316],[536,316],[537,316],[534,317],[535,317],[536,317],[534,318],[535,318],[536,318],[533,319],[534,319],[535,319],[532,320],[533,320],[534,320],[535,320],[531,321],[532,321],[533,321],[534,321],[530,322],[531,322],[532,322],[533,322],[530,323],[531,323],[532,323],[529,324],[530,324],[531,324],[528,325],[529,325],[530,325],[527,326],[528,326],[529,326],[525,327],[526,327],[527,327],[528,327],[524,328],[525,328],[526,328],[527,328],[523,329],[524,329],[525,329],[526,329],[522,330],[523,330],[524,330],[525,330],[520,331],[521,331],[522,331],[523,331],[524,331],[519,332],[520,332],[521,332],[522,332],[523,332],[518,333],[519,333],[520,333],[521,333],[516,334],[517,334],[518,334],[519,334],[520,334],[514,335],[515,335],[516,335],[517,335],[518,335],[513,336],[514,336],[515,336],[516,336],[517,336],[511,337],[512,337],[513,337],[514,337],[515,337],[509,338],[510,338],[511,338],[512,338],[513,338],[507,339],[508,339],[509,339],[510,339],[511,339],[512,339],[505,340],[506,340],[507,340],[508,340],[509,340],[510,340],[502,341],[503,341],[504,341],[505,341],[506,341],[507,341],[500,342],[501,342],[502,342],[503,342],[504,342],[505,342],[497,343],[498,343],[499,343],[500,343],[501,343],[502,343],[495,344],[496,344],[497,344],[498,344],[499,344],[492,345],[493,345],[494,345],[495,345],[496,345],[497,345],[490,346],[491,346],[492,346],[493,346],[494,346],[488,347],[489,347],[490,347],[491,347],[492,347],[485,348],[486,348],[487,348],[488,348],[489,348],[490,348],[483,349],[484,349],[485,349],[486,349],[487,349],[481,350],[482,350],[483,350],[484,350],[485,350],[478,351],[479,351],[480,351],[481,351],[482,351],[475,352],[476,352],[477,352],[478,352],[479,352],[480,352],[473,353],[474,353],[475,353],[476,353],[477,353],[470,354],[471,354],[472,354],[473,354],[474,354],[475,354],[467,355],[468,355],[469,355],[470,355],[471,355],[472,355],[464,356],[465,356],[466,356],[467,356],[468,356],[469,356],[461,357],[462,357],[463,357],[464,357],[465,357],[466,357],[467,357],[457,358],[458,358],[459,358],[460,358],[461,358],[462,358],[463,358],[457,359],[458,359],[459,359],[460,359]]
+
+
+export const line4 = [[138,0],[139,0],[140,0],[141,0],[142,0],[143,0],[144,0],[145,0],[146,0],[147,0],[148,0],[149,0],[150,0],[151,0],[137,1],[138,1],[139,1],[140,1],[141,1],[142,1],[143,1],[144,1],[145,1],[146,1],[147,1],[148,1],[149,1],[150,1],[136,2],[137,2],[138,2],[139,2],[140,2],[141,2],[142,2],[143,2],[144,2],[145,2],[146,2],[147,2],[148,2],[149,2],[134,3],[135,3],[136,3],[137,3],[138,3],[139,3],[140,3],[141,3],[142,3],[143,3],[144,3],[145,3],[146,3],[147,3],[133,4],[134,4],[135,4],[136,4],[137,4],[138,4],[139,4],[140,4],[141,4],[142,4],[143,4],[144,4],[145,4],[146,4],[131,5],[132,5],[133,5],[134,5],[135,5],[136,5],[137,5],[138,5],[139,5],[140,5],[141,5],[142,5],[143,5],[144,5],[129,6],[130,6],[131,6],[132,6],[133,6],[134,6],[135,6],[136,6],[137,6],[138,6],[139,6],[140,6],[141,6],[142,6],[143,6],[128,7],[129,7],[130,7],[131,7],[132,7],[133,7],[134,7],[135,7],[136,7],[137,7],[138,7],[139,7],[140,7],[141,7],[142,7],[127,8],[128,8],[129,8],[130,8],[131,8],[132,8],[133,8],[134,8],[135,8],[136,8],[137,8],[138,8],[139,8],[140,8],[126,9],[127,9],[128,9],[129,9],[130,9],[131,9],[132,9],[133,9],[134,9],[135,9],[136,9],[137,9],[138,9],[125,10],[126,10],[127,10],[128,10],[129,10],[130,10],[131,10],[132,10],[133,10],[134,10],[135,10],[136,10],[137,10],[123,11],[124,11],[125,11],[126,11],[127,11],[128,11],[129,11],[130,11],[131,11],[132,11],[133,11],[134,11],[135,11],[122,12],[123,12],[124,12],[125,12],[126,12],[127,12],[128,12],[129,12],[130,12],[131,12],[132,12],[133,12],[134,12],[121,13],[122,13],[123,13],[124,13],[125,13],[126,13],[127,13],[128,13],[129,13],[130,13],[131,13],[132,13],[120,14],[121,14],[122,14],[123,14],[124,14],[125,14],[126,14],[127,14],[128,14],[129,14],[130,14],[131,14],[119,15],[120,15],[121,15],[122,15],[123,15],[124,15],[125,15],[126,15],[127,15],[128,15],[129,15],[130,15],[118,16],[119,16],[120,16],[121,16],[122,16],[123,16],[124,16],[125,16],[126,16],[127,16],[128,16],[117,17],[118,17],[119,17],[120,17],[121,17],[122,17],[123,17],[124,17],[125,17],[126,17],[127,17],[116,18],[117,18],[118,18],[119,18],[120,18],[121,18],[122,18],[123,18],[124,18],[125,18],[114,19],[115,19],[116,19],[117,19],[118,19],[119,19],[120,19],[121,19],[122,19],[123,19],[124,19],[113,20],[114,20],[115,20],[116,20],[117,20],[118,20],[119,20],[120,20],[121,20],[122,20],[123,20],[112,21],[113,21],[114,21],[115,21],[116,21],[117,21],[118,21],[119,21],[120,21],[121,21],[122,21],[123,21],[111,22],[112,22],[113,22],[114,22],[115,22],[116,22],[117,22],[118,22],[119,22],[120,22],[121,22],[122,22],[110,23],[111,23],[112,23],[113,23],[114,23],[115,23],[116,23],[117,23],[118,23],[119,23],[120,23],[109,24],[110,24],[111,24],[112,24],[113,24],[114,24],[115,24],[116,24],[117,24],[118,24],[119,24],[107,25],[108,25],[109,25],[110,25],[111,25],[112,25],[113,25],[114,25],[115,25],[116,25],[117,25],[106,26],[107,26],[108,26],[109,26],[110,26],[111,26],[112,26],[113,26],[114,26],[115,26],[116,26],[105,27],[106,27],[107,27],[108,27],[109,27],[110,27],[111,27],[112,27],[113,27],[114,27],[115,27],[104,28],[105,28],[106,28],[107,28],[108,28],[109,28],[110,28],[111,28],[112,28],[113,28],[114,28],[103,29],[104,29],[105,29],[106,29],[107,29],[108,29],[109,29],[110,29],[111,29],[112,29],[113,29],[101,30],[102,30],[103,30],[104,30],[105,30],[106,30],[107,30],[108,30],[109,30],[110,30],[111,30],[112,30],[100,31],[101,31],[102,31],[103,31],[104,31],[105,31],[106,31],[107,31],[108,31],[109,31],[110,31],[111,31],[99,32],[100,32],[101,32],[102,32],[103,32],[104,32],[105,32],[106,32],[107,32],[108,32],[109,32],[110,32],[98,33],[99,33],[100,33],[101,33],[102,33],[103,33],[104,33],[105,33],[106,33],[107,33],[108,33],[97,34],[98,34],[99,34],[100,34],[101,34],[102,34],[103,34],[104,34],[105,34],[106,34],[107,34],[96,35],[97,35],[98,35],[99,35],[100,35],[101,35],[102,35],[103,35],[104,35],[105,35],[106,35],[95,36],[96,36],[97,36],[98,36],[99,36],[100,36],[101,36],[102,36],[103,36],[104,36],[105,36],[94,37],[95,37],[96,37],[97,37],[98,37],[99,37],[100,37],[101,37],[102,37],[103,37],[93,38],[94,38],[95,38],[96,38],[97,38],[98,38],[99,38],[100,38],[101,38],[102,38],[91,39],[92,39],[93,39],[94,39],[95,39],[96,39],[97,39],[98,39],[99,39],[100,39],[101,39],[90,40],[91,40],[92,40],[93,40],[94,40],[95,40],[96,40],[97,40],[98,40],[99,40],[100,40],[89,41],[90,41],[91,41],[92,41],[93,41],[94,41],[95,41],[96,41],[97,41],[98,41],[99,41],[88,42],[89,42],[90,42],[91,42],[92,42],[93,42],[94,42],[95,42],[96,42],[97,42],[98,42],[87,43],[88,43],[89,43],[90,43],[91,43],[92,43],[93,43],[94,43],[95,43],[96,43],[97,43],[86,44],[87,44],[88,44],[89,44],[90,44],[91,44],[92,44],[93,44],[94,44],[95,44],[96,44],[85,45],[86,45],[87,45],[88,45],[89,45],[90,45],[91,45],[92,45],[93,45],[94,45],[95,45],[84,46],[85,46],[86,46],[87,46],[88,46],[89,46],[90,46],[91,46],[92,46],[93,46],[94,46],[83,47],[84,47],[85,47],[86,47],[87,47],[88,47],[89,47],[90,47],[91,47],[92,47],[81,48],[82,48],[83,48],[84,48],[85,48],[86,48],[87,48],[88,48],[89,48],[90,48],[91,48],[80,49],[81,49],[82,49],[83,49],[84,49],[85,49],[86,49],[87,49],[88,49],[89,49],[90,49],[79,50],[80,50],[81,50],[82,50],[83,50],[84,50],[85,50],[86,50],[87,50],[88,50],[89,50],[78,51],[79,51],[80,51],[81,51],[82,51],[83,51],[84,51],[85,51],[86,51],[87,51],[88,51],[77,52],[78,52],[79,52],[80,52],[81,52],[82,52],[83,52],[84,52],[85,52],[86,52],[87,52],[76,53],[77,53],[78,53],[79,53],[80,53],[81,53],[82,53],[83,53],[84,53],[85,53],[75,54],[76,54],[77,54],[78,54],[79,54],[80,54],[81,54],[82,54],[83,54],[84,54],[74,55],[75,55],[76,55],[77,55],[78,55],[79,55],[80,55],[81,55],[82,55],[83,55],[73,56],[74,56],[75,56],[76,56],[77,56],[78,56],[79,56],[80,56],[81,56],[82,56],[72,57],[73,57],[74,57],[75,57],[76,57],[77,57],[78,57],[79,57],[80,57],[81,57],[70,58],[71,58],[72,58],[73,58],[74,58],[75,58],[76,58],[77,58],[78,58],[79,58],[80,58],[69,59],[70,59],[71,59],[72,59],[73,59],[74,59],[75,59],[76,59],[77,59],[78,59],[79,59],[69,60],[70,60],[71,60],[72,60],[73,60],[74,60],[75,60],[76,60],[77,60],[78,60],[68,61],[69,61],[70,61],[71,61],[72,61],[73,61],[74,61],[75,61],[76,61],[77,61],[67,62],[68,62],[69,62],[70,62],[71,62],[72,62],[73,62],[74,62],[75,62],[76,62],[67,63],[68,63],[69,63],[70,63],[71,63],[72,63],[73,63],[74,63],[75,63],[76,63],[66,64],[67,64],[68,64],[69,64],[70,64],[71,64],[72,64],[73,64],[74,64],[75,64],[65,65],[66,65],[67,65],[68,65],[69,65],[70,65],[71,65],[72,65],[73,65],[74,65],[75,65],[64,66],[65,66],[66,66],[67,66],[68,66],[69,66],[70,66],[71,66],[72,66],[73,66],[74,66],[64,67],[65,67],[66,67],[67,67],[68,67],[69,67],[70,67],[71,67],[72,67],[73,67],[74,67],[63,68],[64,68],[65,68],[66,68],[67,68],[68,68],[69,68],[70,68],[71,68],[72,68],[73,68],[63,69],[64,69],[65,69],[66,69],[67,69],[68,69],[69,69],[70,69],[71,69],[72,69],[73,69],[62,70],[63,70],[64,70],[65,70],[66,70],[67,70],[68,70],[69,70],[70,70],[71,70],[72,70],[73,70],[62,71],[63,71],[64,71],[65,71],[66,71],[67,71],[68,71],[69,71],[70,71],[71,71],[72,71],[73,71],[61,72],[62,72],[63,72],[64,72],[65,72],[66,72],[67,72],[68,72],[69,72],[70,72],[71,72],[72,72],[58,73],[59,73],[60,73],[61,73],[62,73],[63,73],[64,73],[65,73],[66,73],[67,73],[68,73],[69,73],[70,73],[71,73],[72,73],[57,74],[58,74],[59,74],[60,74],[61,74],[62,74],[63,74],[64,74],[65,74],[66,74],[67,74],[68,74],[69,74],[70,74],[71,74],[72,74],[57,75],[58,75],[59,75],[60,75],[61,75],[62,75],[63,75],[64,75],[65,75],[66,75],[67,75],[68,75],[69,75],[70,75],[71,75],[58,76],[59,76],[60,76],[61,76],[62,76],[63,76],[64,76],[65,76],[66,76],[67,76],[68,76],[69,76],[70,76],[58,77],[59,77],[60,77],[61,77],[62,77],[63,77],[64,77],[65,77],[66,77],[67,77],[68,77],[69,77],[58,78],[59,78],[60,78],[61,78],[62,78],[63,78],[64,78],[65,78],[66,78],[67,78],[59,79],[60,79],[61,79],[62,79],[63,79],[64,79],[65,79],[66,79],[67,79],[59,80],[60,80],[61,80],[62,80],[63,80],[64,80],[65,80],[66,80],[59,81],[60,81],[61,81],[62,81],[63,81],[64,81],[65,81],[66,81],[59,82],[60,82],[61,82],[62,82],[63,82],[64,82],[65,82],[66,82],[59,83],[60,83],[61,83],[62,83],[63,83],[64,83],[65,83],[66,83],[59,84],[60,84],[61,84],[62,84],[63,84],[64,84],[65,84],[66,84],[59,85],[60,85],[61,85],[62,85],[63,85],[64,85],[65,85],[66,85],[59,86],[60,86],[61,86],[62,86],[63,86],[64,86],[65,86],[66,86],[59,87],[60,87],[61,87],[62,87],[63,87],[64,87],[65,87],[66,87],[59,88],[60,88],[61,88],[62,88],[63,88],[64,88],[65,88],[58,89],[59,89],[60,89],[61,89],[62,89],[63,89],[64,89],[65,89],[58,90],[59,90],[60,90],[61,90],[62,90],[63,90],[64,90],[65,90],[58,91],[59,91],[60,91],[61,91],[62,91],[63,91],[64,91],[58,92],[59,92],[60,92],[61,92],[62,92],[63,92],[57,93],[58,93],[59,93],[60,93],[61,93],[62,93],[63,93],[57,94],[58,94],[59,94],[60,94],[61,94],[62,94],[56,95],[57,95],[58,95],[59,95],[60,95],[61,95],[62,95],[56,96],[57,96],[58,96],[59,96],[60,96],[61,96],[55,97],[56,97],[57,97],[58,97],[59,97],[60,97],[61,97],[54,98],[55,98],[56,98],[57,98],[58,98],[59,98],[60,98],[61,98],[54,99],[55,99],[56,99],[57,99],[58,99],[59,99],[60,99],[53,100],[54,100],[55,100],[56,100],[57,100],[58,100],[59,100],[60,100],[53,101],[54,101],[55,101],[56,101],[57,101],[58,101],[59,101],[52,102],[53,102],[54,102],[55,102],[56,102],[57,102],[58,102],[52,103],[53,103],[54,103],[55,103],[56,103],[57,103],[58,103],[51,104],[52,104],[53,104],[54,104],[55,104],[56,104],[57,104],[51,105],[52,105],[53,105],[54,105],[55,105],[56,105],[57,105],[50,106],[51,106],[52,106],[53,106],[54,106],[55,106],[56,106],[50,107],[51,107],[52,107],[53,107],[54,107],[55,107],[56,107],[50,108],[51,108],[52,108],[53,108],[54,108],[55,108],[49,109],[50,109],[51,109],[52,109],[53,109],[54,109],[55,109],[49,110],[50,110],[51,110],[52,110],[53,110],[54,110],[49,111],[50,111],[51,111],[52,111],[53,111],[54,111],[49,112],[50,112],[51,112],[52,112],[53,112],[54,112],[48,113],[49,113],[50,113],[51,113],[52,113],[53,113],[54,113],[48,114],[49,114],[50,114],[51,114],[52,114],[53,114],[54,114],[48,115],[49,115],[50,115],[51,115],[52,115],[53,115],[48,116],[49,116],[50,116],[51,116],[52,116],[53,116],[48,117],[49,117],[50,117],[51,117],[52,117],[53,117],[48,118],[49,118],[50,118],[51,118],[52,118],[53,118],[48,119],[49,119],[50,119],[51,119],[52,119],[53,119],[48,120],[49,120],[50,120],[51,120],[52,120],[53,120],[47,121],[48,121],[49,121],[50,121],[51,121],[52,121],[47,122],[48,122],[49,122],[50,122],[51,122],[52,122],[47,123],[48,123],[49,123],[50,123],[51,123],[52,123],[47,124],[48,124],[49,124],[50,124],[51,124],[52,124],[47,125],[48,125],[49,125],[50,125],[51,125],[52,125],[47,126],[48,126],[49,126],[50,126],[51,126],[52,126],[47,127],[48,127],[49,127],[50,127],[51,127],[52,127],[47,128],[48,128],[49,128],[50,128],[51,128],[52,128],[47,129],[48,129],[49,129],[50,129],[51,129],[47,130],[48,130],[49,130],[50,130],[51,130],[46,131],[47,131],[48,131],[49,131],[50,131],[51,131],[46,132],[47,132],[48,132],[49,132],[50,132],[51,132],[46,133],[47,133],[48,133],[49,133],[50,133],[51,133],[46,134],[47,134],[48,134],[49,134],[50,134],[51,134],[46,135],[47,135],[48,135],[49,135],[50,135],[51,135],[46,136],[47,136],[48,136],[49,136],[50,136],[51,136],[46,137],[47,137],[48,137],[49,137],[50,137],[51,137],[46,138],[47,138],[48,138],[49,138],[50,138],[51,138],[46,139],[47,139],[48,139],[49,139],[50,139],[51,139],[46,140],[47,140],[48,140],[49,140],[50,140],[51,140],[46,141],[47,141],[48,141],[49,141],[50,141],[51,141],[46,142],[47,142],[48,142],[49,142],[50,142],[51,142],[46,143],[47,143],[48,143],[49,143],[50,143],[51,143],[47,144],[48,144],[49,144],[50,144],[51,144],[47,145],[48,145],[49,145],[50,145],[51,145],[47,146],[48,146],[49,146],[50,146],[51,146],[47,147],[48,147],[49,147],[50,147],[51,147],[47,148],[48,148],[49,148],[50,148],[51,148],[47,149],[48,149],[49,149],[50,149],[51,149],[47,150],[48,150],[49,150],[50,150],[51,150],[47,151],[48,151],[49,151],[50,151],[51,151],[47,152],[48,152],[49,152],[50,152],[51,152],[52,152],[47,153],[48,153],[49,153],[50,153],[51,153],[52,153],[48,154],[49,154],[50,154],[51,154],[52,154],[48,155],[49,155],[50,155],[51,155],[52,155],[48,156],[49,156],[50,156],[51,156],[52,156],[48,157],[49,157],[50,157],[51,157],[52,157],[48,158],[49,158],[50,158],[51,158],[52,158],[48,159],[49,159],[50,159],[51,159],[52,159],[53,159],[49,160],[50,160],[51,160],[52,160],[53,160],[49,161],[50,161],[51,161],[52,161],[53,161],[49,162],[50,162],[51,162],[52,162],[53,162],[49,163],[50,163],[51,163],[52,163],[53,163],[49,164],[50,164],[51,164],[52,164],[53,164],[54,164],[50,165],[51,165],[52,165],[53,165],[54,165],[50,166],[51,166],[52,166],[53,166],[54,166],[50,167],[51,167],[52,167],[53,167],[54,167],[51,168],[52,168],[53,168],[54,168],[55,168],[51,169],[52,169],[53,169],[54,169],[55,169],[51,170],[52,170],[53,170],[54,170],[55,170],[52,171],[53,171],[54,171],[55,171],[56,171],[52,172],[53,172],[54,172],[55,172],[56,172],[53,173],[54,173],[55,173],[56,173],[53,174],[54,174],[55,174],[56,174],[57,174],[54,175],[55,175],[56,175],[57,175],[58,175],[54,176],[55,176],[56,176],[57,176],[58,176],[54,177],[55,177],[56,177],[57,177],[58,177],[59,177],[55,178],[56,178],[57,178],[58,178],[59,178],[55,179],[56,179],[57,179],[58,179],[59,179],[60,179],[56,180],[57,180],[58,180],[59,180],[60,180],[57,181],[58,181],[59,181],[60,181],[61,181],[57,182],[58,182],[59,182],[60,182],[61,182],[62,182],[58,183],[59,183],[60,183],[61,183],[62,183],[58,184],[59,184],[60,184],[61,184],[62,184],[63,184],[59,185],[60,185],[61,185],[62,185],[63,185],[64,185],[59,186],[60,186],[61,186],[62,186],[63,186],[64,186],[60,187],[61,187],[62,187],[63,187],[64,187],[65,187],[61,188],[62,188],[63,188],[64,188],[65,188],[66,188],[61,189],[62,189],[63,189],[64,189],[65,189],[66,189],[62,190],[63,190],[64,190],[65,190],[66,190],[67,190],[63,191],[64,191],[65,191],[66,191],[67,191],[68,191],[63,192],[64,192],[65,192],[66,192],[67,192],[68,192],[64,193],[65,193],[66,193],[67,193],[68,193],[69,193],[65,194],[66,194],[67,194],[68,194],[69,194],[70,194],[65,195],[66,195],[67,195],[68,195],[69,195],[70,195],[71,195],[66,196],[67,196],[68,196],[69,196],[70,196],[71,196],[72,196],[67,197],[68,197],[69,197],[70,197],[71,197],[72,197],[73,197],[68,198],[69,198],[70,198],[71,198],[72,198],[73,198],[69,199],[70,199],[71,199],[72,199],[73,199],[74,199],[69,200],[70,200],[71,200],[72,200],[73,200],[74,200],[75,200],[70,201],[71,201],[72,201],[73,201],[74,201],[75,201],[71,202],[72,202],[73,202],[74,202],[75,202],[76,202],[72,203],[73,203],[74,203],[75,203],[76,203],[77,203],[72,204],[73,204],[74,204],[75,204],[76,204],[77,204],[78,204],[73,205],[74,205],[75,205],[76,205],[77,205],[78,205],[79,205],[74,206],[75,206],[76,206],[77,206],[78,206],[79,206],[74,207],[75,207],[76,207],[77,207],[78,207],[79,207],[80,207],[75,208],[76,208],[77,208],[78,208],[79,208],[80,208],[81,208],[76,209],[77,209],[78,209],[79,209],[80,209],[81,209],[82,209],[77,210],[78,210],[79,210],[80,210],[81,210],[82,210],[83,210],[78,211],[79,211],[80,211],[81,211],[82,211],[83,211],[84,211],[78,212],[79,212],[80,212],[81,212],[82,212],[83,212],[84,212],[79,213],[80,213],[81,213],[82,213],[83,213],[84,213],[85,213],[80,214],[81,214],[82,214],[83,214],[84,214],[85,214],[86,214],[81,215],[82,215],[83,215],[84,215],[85,215],[86,215],[81,216],[82,216],[83,216],[84,216],[85,216],[86,216],[87,216],[82,217],[83,217],[84,217],[85,217],[86,217],[87,217],[88,217],[83,218],[84,218],[85,218],[86,218],[87,218],[88,218],[89,218],[84,219],[85,219],[86,219],[87,219],[88,219],[89,219],[90,219],[85,220],[86,220],[87,220],[88,220],[89,220],[90,220],[91,220],[86,221],[87,221],[88,221],[89,221],[90,221],[91,221],[92,221],[87,222],[88,222],[89,222],[90,222],[91,222],[92,222],[93,222],[88,223],[89,223],[90,223],[91,223],[92,223],[93,223],[94,223],[89,224],[90,224],[91,224],[92,224],[93,224],[94,224],[95,224],[90,225],[91,225],[92,225],[93,225],[94,225],[95,225],[96,225],[97,225],[91,226],[92,226],[93,226],[94,226],[95,226],[96,226],[97,226],[98,226],[92,227],[93,227],[94,227],[95,227],[96,227],[97,227],[98,227],[99,227],[93,228],[94,228],[95,228],[96,228],[97,228],[98,228],[99,228],[100,228],[94,229],[95,229],[96,229],[97,229],[98,229],[99,229],[100,229],[101,229],[95,230],[96,230],[97,230],[98,230],[99,230],[100,230],[101,230],[102,230],[96,231],[97,231],[98,231],[99,231],[100,231],[101,231],[102,231],[103,231],[97,232],[98,232],[99,232],[100,232],[101,232],[102,232],[103,232],[104,232],[98,233],[99,233],[100,233],[101,233],[102,233],[103,233],[104,233],[105,233],[100,234],[101,234],[102,234],[103,234],[104,234],[105,234],[106,234],[101,235],[102,235],[103,235],[104,235],[105,235],[106,235],[107,235],[108,235],[102,236],[103,236],[104,236],[105,236],[106,236],[107,236],[108,236],[109,236],[103,237],[104,237],[105,237],[106,237],[107,237],[108,237],[109,237],[110,237],[104,238],[105,238],[106,238],[107,238],[108,238],[109,238],[110,238],[111,238],[105,239],[106,239],[107,239],[108,239],[109,239],[110,239],[111,239],[112,239],[113,239],[107,240],[108,240],[109,240],[110,240],[111,240],[112,240],[113,240],[114,240],[108,241],[109,241],[110,241],[111,241],[112,241],[113,241],[114,241],[115,241],[109,242],[110,242],[111,242],[112,242],[113,242],[114,242],[115,242],[116,242],[110,243],[111,243],[112,243],[113,243],[114,243],[115,243],[116,243],[117,243],[118,243],[112,244],[113,244],[114,244],[115,244],[116,244],[117,244],[118,244],[119,244],[113,245],[114,245],[115,245],[116,245],[117,245],[118,245],[119,245],[120,245],[114,246],[115,246],[116,246],[117,246],[118,246],[119,246],[120,246],[121,246],[122,246],[116,247],[117,247],[118,247],[119,247],[120,247],[121,247],[122,247],[123,247],[117,248],[118,248],[119,248],[120,248],[121,248],[122,248],[123,248],[124,248],[118,249],[119,249],[120,249],[121,249],[122,249],[123,249],[124,249],[125,249],[120,250],[121,250],[122,250],[123,250],[124,250],[125,250],[126,250],[127,250],[121,251],[122,251],[123,251],[124,251],[125,251],[126,251],[127,251],[128,251],[122,252],[123,252],[124,252],[125,252],[126,252],[127,252],[128,252],[129,252],[123,253],[124,253],[125,253],[126,253],[127,253],[128,253],[129,253],[130,253],[125,254],[126,254],[127,254],[128,254],[129,254],[130,254],[131,254],[132,254],[126,255],[127,255],[128,255],[129,255],[130,255],[131,255],[132,255],[133,255],[128,256],[129,256],[130,256],[131,256],[132,256],[133,256],[134,256],[129,257],[130,257],[131,257],[132,257],[133,257],[134,257],[135,257],[136,257],[130,258],[131,258],[132,258],[133,258],[134,258],[135,258],[136,258],[137,258],[132,259],[133,259],[134,259],[135,259],[136,259],[137,259],[138,259],[133,260],[134,260],[135,260],[136,260],[137,260],[138,260],[139,260],[134,261],[135,261],[136,261],[137,261],[138,261],[139,261],[140,261],[141,261],[136,262],[137,262],[138,262],[139,262],[140,262],[141,262],[142,262],[137,263],[138,263],[139,263],[140,263],[141,263],[142,263],[143,263],[144,263],[138,264],[139,264],[140,264],[141,264],[142,264],[143,264],[144,264],[145,264],[140,265],[141,265],[142,265],[143,265],[144,265],[145,265],[146,265],[141,266],[142,266],[143,266],[144,266],[145,266],[146,266],[147,266],[142,267],[143,267],[144,267],[145,267],[146,267],[147,267],[148,267],[149,267],[144,268],[145,268],[146,268],[147,268],[148,268],[149,268],[150,268],[145,269],[146,269],[147,269],[148,269],[149,269],[150,269],[151,269],[152,269],[146,270],[147,270],[148,270],[149,270],[150,270],[151,270],[152,270],[153,270],[148,271],[149,271],[150,271],[151,271],[152,271],[153,271],[154,271],[155,271],[149,272],[150,272],[151,272],[152,272],[153,272],[154,272],[155,272],[156,272],[151,273],[152,273],[153,273],[154,273],[155,273],[156,273],[157,273],[158,273],[152,274],[153,274],[154,274],[155,274],[156,274],[157,274],[158,274],[159,274],[154,275],[155,275],[156,275],[157,275],[158,275],[159,275],[160,275],[161,275],[155,276],[156,276],[157,276],[158,276],[159,276],[160,276],[161,276],[162,276],[157,277],[158,277],[159,277],[160,277],[161,277],[162,277],[163,277],[164,277],[158,278],[159,278],[160,278],[161,278],[162,278],[163,278],[164,278],[165,278],[160,279],[161,279],[162,279],[163,279],[164,279],[165,279],[166,279],[167,279],[161,280],[162,280],[163,280],[164,280],[165,280],[166,280],[167,280],[168,280],[162,281],[163,281],[164,281],[165,281],[166,281],[167,281],[168,281],[169,281],[164,282],[165,282],[166,282],[167,282],[168,282],[169,282],[170,282],[171,282],[165,283],[166,283],[167,283],[168,283],[169,283],[170,283],[171,283],[172,283],[173,283],[167,284],[168,284],[169,284],[170,284],[171,284],[172,284],[173,284],[174,284],[169,285],[170,285],[171,285],[172,285],[173,285],[174,285],[175,285],[176,285],[170,286],[171,286],[172,286],[173,286],[174,286],[175,286],[176,286],[177,286],[172,287],[173,287],[174,287],[175,287],[176,287],[177,287],[178,287],[179,287],[174,288],[175,288],[176,288],[177,288],[178,288],[179,288],[180,288],[175,289],[176,289],[177,289],[178,289],[179,289],[180,289],[181,289],[182,289],[177,290],[178,290],[179,290],[180,290],[181,290],[182,290],[183,290],[178,291],[179,291],[180,291],[181,291],[182,291],[183,291],[184,291],[185,291],[180,292],[181,292],[182,292],[183,292],[184,292],[185,292],[186,292],[181,293],[182,293],[183,293],[184,293],[185,293],[186,293],[187,293],[188,293],[183,294],[184,294],[185,294],[186,294],[187,294],[188,294],[189,294],[184,295],[185,295],[186,295],[187,295],[188,295],[189,295],[190,295],[191,295],[186,296],[187,296],[188,296],[189,296],[190,296],[191,296],[192,296],[187,297],[188,297],[189,297],[190,297],[191,297],[192,297],[193,297],[189,298],[190,298],[191,298],[192,298],[193,298],[194,298],[195,298],[190,299],[191,299],[192,299],[193,299],[194,299],[195,299],[196,299],[192,300],[193,300],[194,300],[195,300],[196,300],[197,300],[198,300],[194,301],[195,301],[196,301],[197,301],[198,301],[199,301],[200,301],[195,302],[196,302],[197,302],[198,302],[199,302],[200,302],[201,302],[197,303],[198,303],[199,303],[200,303],[201,303],[202,303],[203,303],[198,304],[199,304],[200,304],[201,304],[202,304],[203,304],[204,304],[200,305],[201,305],[202,305],[203,305],[204,305],[205,305],[206,305],[201,306],[202,306],[203,306],[204,306],[205,306],[206,306],[207,306],[203,307],[204,307],[205,307],[206,307],[207,307],[208,307],[204,308],[205,308],[206,308],[207,308],[208,308],[209,308],[210,308],[206,309],[207,309],[208,309],[209,309],[210,309],[211,309],[207,310],[208,310],[209,310],[210,310],[211,310],[212,310],[213,310],[208,311],[209,311],[210,311],[211,311],[212,311],[213,311],[214,311],[215,311],[210,312],[211,312],[212,312],[213,312],[214,312],[215,312],[216,312],[211,313],[212,313],[213,313],[214,313],[215,313],[216,313],[217,313],[212,314],[213,314],[214,314],[215,314],[216,314],[217,314],[218,314],[213,315],[214,315],[215,315],[216,315],[217,315],[218,315],[219,315],[214,316],[215,316],[216,316],[217,316],[218,316],[219,316],[220,316],[216,317],[217,317],[218,317],[219,317],[220,317],[221,317],[217,318],[218,318],[219,318],[220,318],[221,318],[222,318],[223,318],[218,319],[219,319],[220,319],[221,319],[222,319],[223,319],[224,319],[219,320],[220,320],[221,320],[222,320],[223,320],[224,320],[225,320],[220,321],[221,321],[222,321],[223,321],[224,321],[225,321],[226,321],[227,321],[221,322],[222,322],[223,322],[224,322],[225,322],[226,322],[227,322],[228,322],[222,323],[223,323],[224,323],[225,323],[226,323],[227,323],[228,323],[229,323],[230,323],[223,324],[224,324],[225,324],[226,324],[227,324],[228,324],[229,324],[230,324],[231,324],[224,325],[225,325],[226,325],[227,325],[228,325],[229,325],[230,325],[231,325],[232,325],[226,326],[227,326],[228,326],[229,326],[230,326],[231,326],[232,326],[233,326],[227,327],[228,327],[229,327],[230,327],[231,327],[232,327],[233,327],[234,327],[228,328],[229,328],[230,328],[231,328],[232,328],[233,328],[234,328],[235,328],[229,329],[230,329],[231,329],[232,329],[233,329],[234,329],[235,329],[236,329],[230,330],[231,330],[232,330],[233,330],[234,330],[235,330],[236,330],[237,330],[231,331],[232,331],[233,331],[234,331],[235,331],[236,331],[237,331],[238,331],[232,332],[233,332],[234,332],[235,332],[236,332],[237,332],[238,332],[233,333],[234,333],[235,333],[236,333],[237,333],[238,333],[239,333],[234,334],[235,334],[236,334],[237,334],[238,334],[239,334],[240,334],[234,335],[235,335],[236,335],[237,335],[238,335],[239,335],[240,335],[235,336],[236,336],[237,336],[238,336],[239,336],[240,336],[241,336],[236,337],[237,337],[238,337],[239,337],[240,337],[241,337],[236,338],[237,338],[238,338],[239,338],[240,338],[241,338],[237,339],[238,339],[239,339],[240,339],[241,339],[242,339],[237,340],[238,340],[239,340],[240,340],[241,340],[242,340],[238,341],[239,341],[240,341],[241,341],[242,341],[238,342],[239,342],[240,342],[241,342],[242,342],[238,343],[239,343],[240,343],[241,343],[242,343],[239,344],[240,344],[241,344],[242,344],[239,345],[240,345],[241,345],[242,345],[239,346],[240,346],[241,346],[242,346],[239,347],[240,347],[241,347],[242,347],[239,348],[240,348],[241,348],[242,348],[239,349],[240,349],[241,349],[242,349],[239,350],[240,350],[241,350],[239,351],[240,351],[241,351],[238,352],[239,352],[240,352],[241,352],[238,353],[239,353],[240,353],[238,354],[239,354],[240,354],[237,355],[238,355],[239,355],[236,356],[237,356],[238,356],[236,357],[237,357],[235,358],[236,358],[234,359],[235,359],[233,360],[234,360],[231,361],[232,361],[233,361],[230,362],[231,362],[232,362],[228,363],[229,363],[230,363],[231,363],[227,364],[228,364],[229,364],[225,365],[226,365],[227,365],[222,366],[223,366],[224,366],[225,366],[226,366],[220,367],[221,367],[222,367],[223,367],[217,368],[218,368],[219,368],[220,368],[221,368],[213,369],[214,369],[215,369],[216,369],[217,369],[218,369],[207,370],[208,370],[209,370],[210,370],[211,370],[212,370],[213,370],[214,370],[202,371],[203,371],[204,371],[205,371],[206,371],[207,371],[208,371],[196,372],[197,372],[198,372],[199,372],[200,372],[201,372],[202,372],[203,372],[191,373],[192,373],[193,373],[194,373],[195,373],[196,373],[197,373],[198,373],[185,374],[186,374],[187,374],[188,374],[189,374],[190,374],[191,374],[192,374],[193,374],[177,375],[178,375],[179,375],[180,375],[181,375],[182,375],[183,375],[184,375],[185,375],[186,375],[173,376],[174,376],[175,376],[176,376],[177,376],[178,376],[179,376],[180,376]]
+
+
+export const line3 = [[427,0],[428,0],[429,0],[430,0],[431,0],[432,0],[433,0],[434,0],[435,0],[436,0],[437,0],[438,0],[439,0],[427,1],[428,1],[429,1],[430,1],[431,1],[432,1],[433,1],[434,1],[435,1],[436,1],[437,1],[438,1],[439,1],[427,2],[428,2],[429,2],[430,2],[431,2],[432,2],[433,2],[434,2],[435,2],[436,2],[437,2],[438,2],[439,2],[427,3],[428,3],[429,3],[430,3],[431,3],[432,3],[433,3],[434,3],[435,3],[436,3],[437,3],[438,3],[439,3],[427,4],[428,4],[429,4],[430,4],[431,4],[432,4],[433,4],[434,4],[435,4],[436,4],[437,4],[438,4],[427,5],[428,5],[429,5],[430,5],[431,5],[432,5],[433,5],[434,5],[435,5],[436,5],[437,5],[438,5],[427,6],[428,6],[429,6],[430,6],[431,6],[432,6],[433,6],[434,6],[435,6],[436,6],[437,6],[438,6],[427,7],[428,7],[429,7],[430,7],[431,7],[432,7],[433,7],[434,7],[435,7],[436,7],[437,7],[438,7],[427,8],[428,8],[429,8],[430,8],[431,8],[432,8],[433,8],[434,8],[435,8],[436,8],[437,8],[438,8],[427,9],[428,9],[429,9],[430,9],[431,9],[432,9],[433,9],[434,9],[435,9],[436,9],[437,9],[438,9],[427,10],[428,10],[429,10],[430,10],[431,10],[432,10],[433,10],[434,10],[435,10],[436,10],[437,10],[426,11],[427,11],[428,11],[429,11],[430,11],[431,11],[432,11],[433,11],[434,11],[435,11],[436,11],[437,11],[426,12],[427,12],[428,12],[429,12],[430,12],[431,12],[432,12],[433,12],[434,12],[435,12],[436,12],[437,12],[426,13],[427,13],[428,13],[429,13],[430,13],[431,13],[432,13],[433,13],[434,13],[435,13],[436,13],[426,14],[427,14],[428,14],[429,14],[430,14],[431,14],[432,14],[433,14],[434,14],[435,14],[436,14],[426,15],[427,15],[428,15],[429,15],[430,15],[431,15],[432,15],[433,15],[434,15],[435,15],[436,15],[426,16],[427,16],[428,16],[429,16],[430,16],[431,16],[432,16],[433,16],[434,16],[435,16],[436,16],[426,17],[427,17],[428,17],[429,17],[430,17],[431,17],[432,17],[433,17],[434,17],[435,17],[436,17],[426,18],[427,18],[428,18],[429,18],[430,18],[431,18],[432,18],[433,18],[434,18],[435,18],[436,18],[426,19],[427,19],[428,19],[429,19],[430,19],[431,19],[432,19],[433,19],[434,19],[435,19],[436,19],[425,20],[426,20],[427,20],[428,20],[429,20],[430,20],[431,20],[432,20],[433,20],[434,20],[435,20],[436,20],[425,21],[426,21],[427,21],[428,21],[429,21],[430,21],[431,21],[432,21],[433,21],[434,21],[435,21],[436,21],[425,22],[426,22],[427,22],[428,22],[429,22],[430,22],[431,22],[432,22],[433,22],[434,22],[435,22],[436,22],[425,23],[426,23],[427,23],[428,23],[429,23],[430,23],[431,23],[432,23],[433,23],[434,23],[435,23],[436,23],[425,24],[426,24],[427,24],[428,24],[429,24],[430,24],[431,24],[432,24],[433,24],[434,24],[435,24],[436,24],[425,25],[426,25],[427,25],[428,25],[429,25],[430,25],[431,25],[432,25],[433,25],[434,25],[435,25],[436,25],[425,26],[426,26],[427,26],[428,26],[429,26],[430,26],[431,26],[432,26],[433,26],[434,26],[435,26],[436,26],[424,27],[425,27],[426,27],[427,27],[428,27],[429,27],[430,27],[431,27],[432,27],[433,27],[434,27],[435,27],[436,27],[424,28],[425,28],[426,28],[427,28],[428,28],[429,28],[430,28],[431,28],[432,28],[433,28],[434,28],[435,28],[424,29],[425,29],[426,29],[427,29],[428,29],[429,29],[430,29],[431,29],[432,29],[433,29],[434,29],[435,29],[424,30],[425,30],[426,30],[427,30],[428,30],[429,30],[430,30],[431,30],[432,30],[433,30],[434,30],[435,30],[424,31],[425,31],[426,31],[427,31],[428,31],[429,31],[430,31],[431,31],[432,31],[433,31],[434,31],[435,31],[424,32],[425,32],[426,32],[427,32],[428,32],[429,32],[430,32],[431,32],[432,32],[433,32],[434,32],[435,32],[424,33],[425,33],[426,33],[427,33],[428,33],[429,33],[430,33],[431,33],[432,33],[433,33],[434,33],[435,33],[424,34],[425,34],[426,34],[427,34],[428,34],[429,34],[430,34],[431,34],[432,34],[433,34],[434,34],[435,34],[424,35],[425,35],[426,35],[427,35],[428,35],[429,35],[430,35],[431,35],[432,35],[433,35],[434,35],[435,35],[424,36],[425,36],[426,36],[427,36],[428,36],[429,36],[430,36],[431,36],[432,36],[433,36],[434,36],[435,36],[424,37],[425,37],[426,37],[427,37],[428,37],[429,37],[430,37],[431,37],[432,37],[433,37],[434,37],[424,38],[425,38],[426,38],[427,38],[428,38],[429,38],[430,38],[431,38],[432,38],[433,38],[434,38],[423,39],[424,39],[425,39],[426,39],[427,39],[428,39],[429,39],[430,39],[431,39],[432,39],[433,39],[434,39],[423,40],[424,40],[425,40],[426,40],[427,40],[428,40],[429,40],[430,40],[431,40],[432,40],[433,40],[434,40],[423,41],[424,41],[425,41],[426,41],[427,41],[428,41],[429,41],[430,41],[431,41],[432,41],[433,41],[434,41],[423,42],[424,42],[425,42],[426,42],[427,42],[428,42],[429,42],[430,42],[431,42],[432,42],[433,42],[434,42],[423,43],[424,43],[425,43],[426,43],[427,43],[428,43],[429,43],[430,43],[431,43],[432,43],[433,43],[434,43],[423,44],[424,44],[425,44],[426,44],[427,44],[428,44],[429,44],[430,44],[431,44],[432,44],[433,44],[434,44],[423,45],[424,45],[425,45],[426,45],[427,45],[428,45],[429,45],[430,45],[431,45],[432,45],[433,45],[434,45],[423,46],[424,46],[425,46],[426,46],[427,46],[428,46],[429,46],[430,46],[431,46],[432,46],[433,46],[434,46],[422,47],[423,47],[424,47],[425,47],[426,47],[427,47],[428,47],[429,47],[430,47],[431,47],[432,47],[433,47],[434,47],[422,48],[423,48],[424,48],[425,48],[426,48],[427,48],[428,48],[429,48],[430,48],[431,48],[432,48],[433,48],[422,49],[423,49],[424,49],[425,49],[426,49],[427,49],[428,49],[429,49],[430,49],[431,49],[432,49],[433,49],[422,50],[423,50],[424,50],[425,50],[426,50],[427,50],[428,50],[429,50],[430,50],[431,50],[432,50],[433,50],[422,51],[423,51],[424,51],[425,51],[426,51],[427,51],[428,51],[429,51],[430,51],[431,51],[432,51],[433,51],[421,52],[422,52],[423,52],[424,52],[425,52],[426,52],[427,52],[428,52],[429,52],[430,52],[431,52],[432,52],[421,53],[422,53],[423,53],[424,53],[425,53],[426,53],[427,53],[428,53],[429,53],[430,53],[431,53],[432,53],[421,54],[422,54],[423,54],[424,54],[425,54],[426,54],[427,54],[428,54],[429,54],[430,54],[431,54],[432,54],[421,55],[422,55],[423,55],[424,55],[425,55],[426,55],[427,55],[428,55],[429,55],[430,55],[431,55],[420,56],[421,56],[422,56],[423,56],[424,56],[425,56],[426,56],[427,56],[428,56],[429,56],[430,56],[431,56],[420,57],[421,57],[422,57],[423,57],[424,57],[425,57],[426,57],[427,57],[428,57],[429,57],[430,57],[431,57],[420,58],[421,58],[422,58],[423,58],[424,58],[425,58],[426,58],[427,58],[428,58],[429,58],[430,58],[420,59],[421,59],[422,59],[423,59],[424,59],[425,59],[426,59],[427,59],[428,59],[429,59],[430,59],[419,60],[420,60],[421,60],[422,60],[423,60],[424,60],[425,60],[426,60],[427,60],[428,60],[429,60],[430,60],[419,61],[420,61],[421,61],[422,61],[423,61],[424,61],[425,61],[426,61],[427,61],[428,61],[429,61],[419,62],[420,62],[421,62],[422,62],[423,62],[424,62],[425,62],[426,62],[427,62],[428,62],[429,62],[419,63],[420,63],[421,63],[422,63],[423,63],[424,63],[425,63],[426,63],[427,63],[428,63],[429,63],[418,64],[419,64],[420,64],[421,64],[422,64],[423,64],[424,64],[425,64],[426,64],[427,64],[428,64],[429,64],[418,65],[419,65],[420,65],[421,65],[422,65],[423,65],[424,65],[425,65],[426,65],[427,65],[428,65],[418,66],[419,66],[420,66],[421,66],[422,66],[423,66],[424,66],[425,66],[426,66],[427,66],[428,66],[418,67],[419,67],[420,67],[421,67],[422,67],[423,67],[424,67],[425,67],[426,67],[427,67],[428,67],[417,68],[418,68],[419,68],[420,68],[421,68],[422,68],[423,68],[424,68],[425,68],[426,68],[427,68],[428,68],[417,69],[418,69],[419,69],[420,69],[421,69],[422,69],[423,69],[424,69],[425,69],[426,69],[427,69],[417,70],[418,70],[419,70],[420,70],[421,70],[422,70],[423,70],[424,70],[425,70],[426,70],[427,70],[417,71],[418,71],[419,71],[420,71],[421,71],[422,71],[423,71],[424,71],[425,71],[426,71],[427,71],[416,72],[417,72],[418,72],[419,72],[420,72],[421,72],[422,72],[423,72],[424,72],[425,72],[426,72],[427,72],[416,73],[417,73],[418,73],[419,73],[420,73],[421,73],[422,73],[423,73],[424,73],[425,73],[426,73],[416,74],[417,74],[418,74],[419,74],[420,74],[421,74],[422,74],[423,74],[424,74],[425,74],[426,74],[415,75],[416,75],[417,75],[418,75],[419,75],[420,75],[421,75],[422,75],[423,75],[424,75],[425,75],[426,75],[415,76],[416,76],[417,76],[418,76],[419,76],[420,76],[421,76],[422,76],[423,76],[424,76],[425,76],[415,77],[416,77],[417,77],[418,77],[419,77],[420,77],[421,77],[422,77],[423,77],[424,77],[425,77],[415,78],[416,78],[417,78],[418,78],[419,78],[420,78],[421,78],[422,78],[423,78],[424,78],[425,78],[414,79],[415,79],[416,79],[417,79],[418,79],[419,79],[420,79],[421,79],[422,79],[423,79],[424,79],[414,80],[415,80],[416,80],[417,80],[418,80],[419,80],[420,80],[421,80],[422,80],[423,80],[424,80],[414,81],[415,81],[416,81],[417,81],[418,81],[419,81],[420,81],[421,81],[422,81],[423,81],[424,81],[414,82],[415,82],[416,82],[417,82],[418,82],[419,82],[420,82],[421,82],[422,82],[423,82],[413,83],[414,83],[415,83],[416,83],[417,83],[418,83],[419,83],[420,83],[421,83],[422,83],[423,83],[413,84],[414,84],[415,84],[416,84],[417,84],[418,84],[419,84],[420,84],[421,84],[422,84],[412,85],[413,85],[414,85],[415,85],[416,85],[417,85],[418,85],[419,85],[420,85],[421,85],[422,85],[412,86],[413,86],[414,86],[415,86],[416,86],[417,86],[418,86],[419,86],[420,86],[421,86],[422,86],[412,87],[413,87],[414,87],[415,87],[416,87],[417,87],[418,87],[419,87],[420,87],[421,87],[411,88],[412,88],[413,88],[414,88],[415,88],[416,88],[417,88],[418,88],[419,88],[420,88],[421,88],[411,89],[412,89],[413,89],[414,89],[415,89],[416,89],[417,89],[418,89],[419,89],[420,89],[421,89],[411,90],[412,90],[413,90],[414,90],[415,90],[416,90],[417,90],[418,90],[419,90],[420,90],[410,91],[411,91],[412,91],[413,91],[414,91],[415,91],[416,91],[417,91],[418,91],[419,91],[420,91],[410,92],[411,92],[412,92],[413,92],[414,92],[415,92],[416,92],[417,92],[418,92],[419,92],[409,93],[410,93],[411,93],[412,93],[413,93],[414,93],[415,93],[416,93],[417,93],[418,93],[419,93],[409,94],[410,94],[411,94],[412,94],[413,94],[414,94],[415,94],[416,94],[417,94],[418,94],[409,95],[410,95],[411,95],[412,95],[413,95],[414,95],[415,95],[416,95],[417,95],[418,95],[408,96],[409,96],[410,96],[411,96],[412,96],[413,96],[414,96],[415,96],[416,96],[417,96],[408,97],[409,97],[410,97],[411,97],[412,97],[413,97],[414,97],[415,97],[416,97],[407,98],[408,98],[409,98],[410,98],[411,98],[412,98],[413,98],[414,98],[415,98],[416,98],[407,99],[408,99],[409,99],[410,99],[411,99],[412,99],[413,99],[414,99],[415,99],[406,100],[407,100],[408,100],[409,100],[410,100],[411,100],[412,100],[413,100],[414,100],[406,101],[407,101],[408,101],[409,101],[410,101],[411,101],[412,101],[413,101],[414,101],[405,102],[406,102],[407,102],[408,102],[409,102],[410,102],[411,102],[412,102],[413,102],[405,103],[406,103],[407,103],[408,103],[409,103],[410,103],[411,103],[412,103],[413,103],[404,104],[405,104],[406,104],[407,104],[408,104],[409,104],[410,104],[411,104],[412,104],[404,105],[405,105],[406,105],[407,105],[408,105],[409,105],[410,105],[411,105],[412,105],[403,106],[404,106],[405,106],[406,106],[407,106],[408,106],[409,106],[410,106],[411,106],[403,107],[404,107],[405,107],[406,107],[407,107],[408,107],[409,107],[410,107],[411,107],[402,108],[403,108],[404,108],[405,108],[406,108],[407,108],[408,108],[409,108],[410,108],[411,108],[402,109],[403,109],[404,109],[405,109],[406,109],[407,109],[408,109],[409,109],[410,109],[401,110],[402,110],[403,110],[404,110],[405,110],[406,110],[407,110],[408,110],[409,110],[410,110],[401,111],[402,111],[403,111],[404,111],[405,111],[406,111],[407,111],[408,111],[409,111],[400,112],[401,112],[402,112],[403,112],[404,112],[405,112],[406,112],[407,112],[408,112],[409,112],[400,113],[401,113],[402,113],[403,113],[404,113],[405,113],[406,113],[407,113],[408,113],[400,114],[401,114],[402,114],[403,114],[404,114],[405,114],[406,114],[407,114],[408,114],[399,115],[400,115],[401,115],[402,115],[403,115],[404,115],[405,115],[406,115],[407,115],[399,116],[400,116],[401,116],[402,116],[403,116],[404,116],[405,116],[406,116],[407,116],[398,117],[399,117],[400,117],[401,117],[402,117],[403,117],[404,117],[405,117],[406,117],[398,118],[399,118],[400,118],[401,118],[402,118],[403,118],[404,118],[405,118],[406,118],[397,119],[398,119],[399,119],[400,119],[401,119],[402,119],[403,119],[404,119],[405,119],[396,120],[397,120],[398,120],[399,120],[400,120],[401,120],[402,120],[403,120],[404,120],[396,121],[397,121],[398,121],[399,121],[400,121],[401,121],[402,121],[403,121],[404,121],[395,122],[396,122],[397,122],[398,122],[399,122],[400,122],[401,122],[402,122],[403,122],[394,123],[395,123],[396,123],[397,123],[398,123],[399,123],[400,123],[401,123],[402,123],[394,124],[395,124],[396,124],[397,124],[398,124],[399,124],[400,124],[401,124],[402,124],[393,125],[394,125],[395,125],[396,125],[397,125],[398,125],[399,125],[400,125],[401,125],[393,126],[394,126],[395,126],[396,126],[397,126],[398,126],[399,126],[400,126],[392,127],[393,127],[394,127],[395,127],[396,127],[397,127],[398,127],[399,127],[400,127],[392,128],[393,128],[394,128],[395,128],[396,128],[397,128],[398,128],[399,128],[391,129],[392,129],[393,129],[394,129],[395,129],[396,129],[397,129],[398,129],[399,129],[390,130],[391,130],[392,130],[393,130],[394,130],[395,130],[396,130],[397,130],[398,130],[390,131],[391,131],[392,131],[393,131],[394,131],[395,131],[396,131],[397,131],[398,131],[389,132],[390,132],[391,132],[392,132],[393,132],[394,132],[395,132],[396,132],[397,132],[388,133],[389,133],[390,133],[391,133],[392,133],[393,133],[394,133],[395,133],[396,133],[388,134],[389,134],[390,134],[391,134],[392,134],[393,134],[394,134],[395,134],[387,135],[388,135],[389,135],[390,135],[391,135],[392,135],[393,135],[394,135],[386,136],[387,136],[388,136],[389,136],[390,136],[391,136],[392,136],[393,136],[386,137],[387,137],[388,137],[389,137],[390,137],[391,137],[392,137],[385,138],[386,138],[387,138],[388,138],[389,138],[390,138],[391,138],[392,138],[384,139],[385,139],[386,139],[387,139],[388,139],[389,139],[390,139],[391,139],[383,140],[384,140],[385,140],[386,140],[387,140],[388,140],[389,140],[390,140],[382,141],[383,141],[384,141],[385,141],[386,141],[387,141],[388,141],[389,141],[381,142],[382,142],[383,142],[384,142],[385,142],[386,142],[387,142],[388,142],[380,143],[381,143],[382,143],[383,143],[384,143],[385,143],[386,143],[387,143],[379,144],[380,144],[381,144],[382,144],[383,144],[384,144],[385,144],[386,144],[379,145],[380,145],[381,145],[382,145],[383,145],[384,145],[385,145],[378,146],[379,146],[380,146],[381,146],[382,146],[383,146],[384,146],[377,147],[378,147],[379,147],[380,147],[381,147],[382,147],[383,147],[376,148],[377,148],[378,148],[379,148],[380,148],[381,148],[382,148],[375,149],[376,149],[377,149],[378,149],[379,149],[380,149],[381,149],[374,150],[375,150],[376,150],[377,150],[378,150],[379,150],[380,150],[373,151],[374,151],[375,151],[376,151],[377,151],[378,151],[379,151],[372,152],[373,152],[374,152],[375,152],[376,152],[377,152],[371,153],[372,153],[373,153],[374,153],[375,153],[376,153],[370,154],[371,154],[372,154],[373,154],[374,154],[375,154],[368,155],[369,155],[370,155],[371,155],[372,155],[373,155],[367,156],[368,156],[369,156],[370,156],[371,156],[372,156],[366,157],[367,157],[368,157],[369,157],[370,157],[364,158],[365,158],[366,158],[367,158],[368,158],[369,158],[363,159],[364,159],[365,159],[366,159],[367,159],[368,159],[361,160],[362,160],[363,160],[364,160],[365,160],[366,160],[360,161],[361,161],[362,161],[363,161],[364,161],[365,161],[358,162],[359,162],[360,162],[361,162],[362,162],[363,162],[364,162],[356,163],[357,163],[358,163],[359,163],[360,163],[361,163],[362,163],[363,163],[354,164],[355,164],[356,164],[357,164],[358,164],[359,164],[360,164],[361,164],[362,164],[352,165],[353,165],[354,165],[355,165],[356,165],[357,165],[358,165],[359,165],[360,165],[361,165],[350,166],[351,166],[352,166],[353,166],[354,166],[355,166],[356,166],[357,166],[358,166],[359,166],[360,166],[349,167],[350,167],[351,167],[352,167],[353,167],[354,167],[355,167],[356,167],[357,167],[358,167],[359,167],[360,167],[348,168],[349,168],[350,168],[351,168],[352,168],[353,168],[354,168],[355,168],[356,168],[357,168],[358,168],[347,169],[348,169],[349,169],[350,169],[351,169],[352,169],[353,169],[354,169],[355,169],[356,169],[357,169],[347,170],[348,170],[349,170],[350,170],[351,170],[352,170],[353,170],[354,170],[355,170],[347,171],[348,171],[349,171],[350,171],[351,171],[352,171],[353,171],[354,171],[347,172],[348,172],[349,172],[350,172],[351,172],[352,172],[346,173],[347,173],[348,173],[349,173],[350,173],[351,173],[345,174],[346,174],[347,174],[348,174],[349,174],[342,175],[343,175],[344,175],[345,175],[346,175],[347,175],[340,176],[341,176],[342,176],[343,176],[344,176],[345,176],[338,177],[339,177],[340,177],[341,177],[342,177],[343,177],[334,178],[335,178],[336,178],[337,178],[338,178],[339,178],[340,178],[341,178],[342,178],[331,179],[332,179],[333,179],[334,179],[335,179],[336,179],[337,179],[338,179],[339,179],[340,179],[341,179],[325,180],[326,180],[327,180],[328,180],[329,180],[330,180],[331,180],[332,180],[333,180],[334,180],[335,180],[336,180],[337,180],[338,180],[339,180],[319,181],[320,181],[321,181],[322,181],[323,181],[324,181],[325,181],[326,181],[327,181],[328,181],[329,181],[330,181],[331,181],[332,181],[333,181],[334,181],[335,181],[336,181],[316,182],[317,182],[318,182],[319,182],[320,182],[321,182],[322,182],[323,182],[324,182],[325,182],[326,182],[327,182],[328,182],[329,182],[330,182],[331,182],[332,182],[333,182],[314,183],[315,183],[316,183],[317,183],[318,183],[319,183],[320,183],[321,183],[322,183],[323,183],[324,183],[325,183],[326,183],[327,183],[328,183],[329,183],[330,183],[312,184],[313,184],[314,184],[315,184],[316,184],[317,184],[318,184],[319,184],[320,184],[321,184],[322,184],[323,184],[324,184],[325,184],[326,184],[327,184],[310,185],[311,185],[312,185],[313,185],[314,185],[315,185],[316,185],[317,185],[318,185],[319,185],[320,185],[321,185],[322,185],[323,185],[309,186],[310,186],[311,186],[312,186],[313,186],[314,186],[315,186],[316,186],[317,186],[308,187],[309,187],[310,187],[311,187],[312,187],[313,187],[307,188],[308,188],[309,188],[310,188],[311,188],[306,189],[307,189],[303,190],[304,190],[305,190],[306,190],[301,191],[302,191],[303,191],[304,191],[305,191],[298,192],[299,192],[300,192],[301,192],[302,192],[303,192],[304,192],[305,192],[295,193],[296,193],[297,193],[298,193],[299,193],[300,193],[301,193],[302,193],[303,193],[291,194],[292,194],[293,194],[294,194],[295,194],[296,194],[297,194],[298,194],[299,194],[300,194],[288,195],[289,195],[290,195],[291,195],[292,195],[293,195],[294,195],[295,195],[296,195],[297,195],[283,196],[284,196],[285,196],[286,196],[287,196],[288,196],[289,196],[290,196],[291,196],[292,196],[293,196],[294,196],[280,197],[281,197],[282,197],[283,197],[284,197],[285,197],[286,197],[287,197],[288,197],[289,197],[277,198],[278,198],[279,198],[280,198],[281,198],[282,198],[283,198],[275,199],[276,199],[277,199],[278,199],[279,199],[280,199],[281,199],[272,200],[273,200],[274,200],[275,200],[276,200],[277,200],[278,200],[270,201],[271,201],[272,201],[273,201],[274,201],[275,201],[266,202],[267,202],[268,202],[269,202],[270,202],[271,202],[272,202],[264,203],[265,203],[266,203],[267,203],[268,203],[269,203],[270,203],[262,204],[263,204],[264,204],[265,204],[266,204],[267,204],[268,204],[269,204],[260,205],[261,205],[262,205],[263,205],[264,205],[265,205],[266,205],[258,206],[259,206],[260,206],[261,206],[262,206],[256,207],[257,207],[258,207],[259,207],[260,207],[254,208],[255,208],[256,208],[257,208],[258,208],[251,209],[252,209],[253,209],[254,209],[255,209],[256,209],[246,210],[247,210],[248,210],[249,210],[250,210],[251,210],[252,210],[253,210],[254,210],[243,211],[244,211],[245,211],[246,211],[247,211],[248,211],[249,211],[250,211],[251,211],[240,212],[241,212],[242,212],[243,212],[244,212],[245,212],[246,212],[237,213],[238,213],[239,213],[240,213],[241,213],[242,213],[243,213],[235,214],[236,214],[237,214],[238,214],[239,214],[240,214],[241,214],[232,215],[233,215],[234,215],[235,215],[236,215],[237,215],[238,215],[230,216],[231,216],[232,216],[233,216],[234,216],[228,217],[229,217],[230,217],[231,217],[232,217],[225,218],[226,218],[227,218],[228,218],[229,218],[230,218],[223,219],[224,219],[225,219],[226,219],[227,219],[220,220],[221,220],[222,220],[223,220],[224,220],[225,220],[217,221],[218,221],[219,221],[220,221],[221,221],[222,221],[223,221],[215,222],[216,222],[217,222],[218,222],[219,222],[220,222],[212,223],[213,223],[214,223],[215,223],[216,223],[217,223],[218,223],[210,224],[211,224],[212,224],[213,224],[214,224],[215,224],[208,225],[209,225],[210,225],[211,225],[212,225],[213,225],[206,226],[207,226],[208,226],[209,226],[210,226],[204,227],[205,227],[206,227],[207,227],[208,227],[202,228],[203,228],[204,228],[205,228],[206,228],[199,229],[200,229],[201,229],[202,229],[203,229],[204,229],[197,230],[198,230],[199,230],[200,230],[201,230],[202,230],[195,231],[196,231],[197,231],[198,231],[199,231],[200,231],[193,232],[194,232],[195,232],[196,232],[197,232],[198,232],[191,233],[192,233],[193,233],[194,233],[195,233],[189,234],[190,234],[191,234],[192,234],[193,234],[187,235],[188,235],[189,235],[190,235],[191,235],[185,236],[186,236],[187,236],[188,236],[189,236],[183,237],[184,237],[185,237],[186,237],[187,237],[188,237],[181,238],[182,238],[183,238],[184,238],[185,238],[186,238],[179,239],[180,239],[181,239],[182,239],[183,239],[177,240],[178,240],[179,240],[180,240],[181,240],[182,240],[175,241],[176,241],[177,241],[178,241],[179,241],[173,242],[174,242],[175,242],[176,242],[177,242],[171,243],[172,243],[173,243],[174,243],[175,243],[169,244],[170,244],[171,244],[172,244],[173,244],[167,245],[168,245],[169,245],[170,245],[171,245],[172,245],[166,246],[167,246],[168,246],[169,246],[170,246],[171,246],[164,247],[165,247],[166,247],[167,247],[168,247],[169,247],[170,247],[163,248],[164,248],[165,248],[166,248],[167,248],[168,248],[169,248],[162,249],[163,249],[164,249],[165,249],[166,249],[167,249],[168,249],[161,250],[162,250],[163,250],[164,250],[165,250],[166,250],[167,250],[158,251],[159,251],[160,251],[161,251],[162,251],[163,251],[164,251],[165,251],[166,251],[167,251],[158,252],[159,252],[160,252],[161,252],[162,252],[163,252],[164,252],[165,252],[158,253],[159,253],[160,253],[161,253],[162,253],[163,253],[158,254],[159,254],[160,254],[161,254],[162,254],[163,254],[158,255],[159,255],[160,255],[161,255],[162,255],[158,256],[159,256],[160,256],[161,256],[162,256],[157,257],[158,257],[159,257],[160,257],[161,257],[157,258],[158,258],[159,258],[160,258],[156,259],[157,259],[158,259],[159,259],[155,260],[156,260],[157,260],[158,260],[155,261],[156,261],[157,261],[158,261],[153,262],[154,262],[155,262],[156,262],[157,262],[152,263],[153,263],[154,263],[155,263],[156,263],[151,264],[152,264],[153,264],[154,264],[155,264],[150,265],[151,265],[152,265],[153,265],[154,265],[149,266],[150,266],[151,266],[152,266],[153,266],[148,267],[149,267],[150,267],[151,267],[152,267],[148,268],[149,268],[150,268],[151,268],[147,269],[148,269],[149,269],[150,269],[147,270],[148,270],[149,270],[150,270],[146,271],[147,271],[148,271],[149,271],[146,272],[147,272],[148,272],[149,272],[146,273],[147,273],[148,273],[145,274],[146,274],[147,274],[148,274],[145,275],[146,275],[147,275],[148,275],[144,276],[145,276],[146,276],[147,276],[144,277],[145,277],[146,277],[147,277],[143,278],[144,278],[145,278],[146,278],[143,279],[144,279],[145,279],[146,279],[143,280],[144,280],[145,280],[146,280],[143,281],[144,281],[145,281],[143,282],[144,282],[145,282],[142,283],[143,283],[144,283],[145,283],[142,284],[143,284],[144,284],[145,284],[142,285],[143,285],[144,285],[145,285],[142,286],[143,286],[144,286],[142,287],[143,287],[144,287],[142,288],[143,288],[144,288],[142,289],[143,289],[144,289],[142,290],[143,290],[144,290],[142,291],[143,291],[144,291],[142,292],[143,292],[144,292],[142,293],[143,293],[144,293],[142,294],[143,294],[144,294],[142,295],[143,295],[144,295],[142,296],[143,296],[144,296],[145,296],[142,297],[143,297],[144,297],[145,297],[143,298],[144,298],[145,298],[143,299],[144,299],[145,299],[143,300],[144,300],[145,300],[146,300],[144,301],[145,301],[146,301],[147,301],[144,302],[145,302],[146,302],[147,302],[145,303],[146,303],[147,303],[148,303],[145,304],[146,304],[147,304],[148,304],[146,305],[147,305],[148,305],[149,305],[147,306],[148,306],[149,306],[150,306],[147,307],[148,307],[149,307],[150,307],[148,308],[149,308],[150,308],[151,308],[149,309],[150,309],[151,309],[152,309],[149,310],[150,310],[151,310],[152,310],[153,310],[150,311],[151,311],[152,311],[153,311],[154,311],[151,312],[152,312],[153,312],[154,312],[155,312],[152,313],[153,313],[154,313],[155,313],[153,314],[154,314],[155,314],[156,314],[154,315],[155,315],[156,315],[157,315],[154,316],[155,316],[156,316],[157,316],[158,316],[155,317],[156,317],[157,317],[158,317],[159,317],[156,318],[157,318],[158,318],[159,318],[160,318],[157,319],[158,319],[159,319],[160,319],[161,319],[158,320],[159,320],[160,320],[161,320],[162,320],[158,321],[159,321],[160,321],[161,321],[162,321],[159,322],[160,322],[161,322],[162,322],[163,322],[160,323],[161,323],[162,323],[163,323],[164,323],[161,324],[162,324],[163,324],[164,324],[165,324],[162,325],[163,325],[164,325],[165,325],[166,325],[167,325],[163,326],[164,326],[165,326],[166,326],[167,326],[168,326],[165,327],[166,327],[167,327],[168,327],[169,327],[166,328],[167,328],[168,328],[169,328],[170,328],[167,329],[168,329],[169,329],[170,329],[171,329],[172,329],[168,330],[169,330],[170,330],[171,330],[172,330],[173,330],[170,331],[171,331],[172,331],[173,331],[174,331],[171,332],[172,332],[173,332],[174,332],[175,332],[176,332],[172,333],[173,333],[174,333],[175,333],[176,333],[177,333],[174,334],[175,334],[176,334],[177,334],[178,334],[175,335],[176,335],[177,335],[178,335],[179,335],[180,335],[176,336],[177,336],[178,336],[179,336],[180,336],[181,336],[178,337],[179,337],[180,337],[181,337],[182,337],[183,337],[180,338],[181,338],[182,338],[183,338],[184,338],[181,339],[182,339],[183,339],[184,339],[185,339],[186,339],[183,340],[184,340],[185,340],[186,340],[187,340],[184,341],[185,341],[186,341],[187,341],[188,341],[189,341],[186,342],[187,342],[188,342],[189,342],[190,342],[187,343],[188,343],[189,343],[190,343],[191,343],[192,343],[189,344],[190,344],[191,344],[192,344],[193,344],[191,345],[192,345],[193,345],[194,345],[195,345],[192,346],[193,346],[194,346],[195,346],[196,346],[197,346],[194,347],[195,347],[196,347],[197,347],[198,347],[195,348],[196,348],[197,348],[198,348],[199,348],[200,348],[197,349],[198,349],[199,349],[200,349],[201,349],[199,350],[200,350],[201,350],[202,350],[203,350],[200,351],[201,351],[202,351],[203,351],[204,351],[202,352],[203,352],[204,352],[205,352],[206,352],[203,353],[204,353],[205,353],[206,353],[207,353],[208,353],[205,354],[206,354],[207,354],[208,354],[209,354],[210,354],[207,355],[208,355],[209,355],[210,355],[211,355],[212,355],[208,356],[209,356],[210,356],[211,356],[212,356],[213,356],[214,356],[210,357],[211,357],[212,357],[213,357],[214,357],[215,357],[212,358],[213,358],[214,358],[215,358],[216,358],[217,358],[214,359],[215,359],[216,359],[217,359],[218,359],[219,359],[216,360],[217,360],[218,360],[219,360],[220,360],[217,361],[218,361],[219,361],[220,361],[221,361],[222,361],[219,362],[220,362],[221,362],[222,362],[223,362],[224,362],[221,363],[222,363],[223,363],[224,363],[225,363],[226,363],[223,364],[224,364],[225,364],[226,364],[227,364],[228,364],[225,365],[226,365],[227,365],[228,365],[229,365],[227,366],[228,366],[229,366],[230,366],[231,366],[229,367],[230,367],[231,367],[232,367],[233,367],[231,368],[232,368],[233,368],[234,368],[235,368],[232,369],[233,369],[234,369],[235,369],[236,369],[234,370],[235,370],[236,370],[237,370],[238,370],[236,371],[237,371],[238,371],[239,371],[240,371],[238,372],[239,372],[240,372],[241,372],[242,372],[240,373],[241,373],[242,373],[243,373],[244,373],[241,374],[242,374],[243,374],[244,374],[245,374],[243,375],[244,375],[245,375],[246,375],[247,375],[245,376],[246,376],[247,376],[248,376],[249,376],[247,377],[248,377],[249,377],[250,377],[251,377],[248,378],[249,378],[250,378],[251,378],[252,378],[253,378],[249,379],[250,379],[251,379],[252,379],[253,379],[254,379],[251,380],[252,380],[253,380],[254,380],[255,380],[252,381],[253,381],[254,381],[255,381],[256,381],[257,381],[253,382],[254,382],[255,382],[256,382],[257,382],[258,382],[255,383],[256,383],[257,383],[258,383],[259,383],[256,384],[257,384],[258,384],[259,384],[260,384],[261,384],[257,385],[258,385],[259,385],[260,385],[261,385],[262,385],[263,385],[259,386],[260,386],[261,386],[262,386],[263,386],[264,386],[265,386],[260,387],[261,387],[262,387],[263,387],[264,387],[265,387],[266,387],[262,388],[263,388],[264,388],[265,388],[266,388],[267,388],[263,389],[264,389],[265,389],[266,389],[267,389],[268,389],[264,390],[265,390],[266,390],[267,390],[268,390],[269,390],[265,391],[266,391],[267,391],[268,391],[269,391],[270,391],[266,392],[267,392],[268,392],[269,392],[270,392],[271,392],[267,393],[268,393],[269,393],[270,393],[271,393],[272,393],[268,394],[269,394],[270,394],[271,394],[272,394],[269,395],[270,395],[271,395],[272,395],[273,395],[269,396],[270,396],[271,396],[272,396],[273,396],[270,397],[271,397],[272,397],[273,397],[270,398],[271,398],[272,398],[273,398],[274,398],[271,399],[272,399],[273,399],[274,399]]
+
+
+export const line2 = [[535,0],[536,0],[537,0],[538,0],[539,0],[540,0],[541,0],[542,0],[543,0],[544,0],[545,0],[546,0],[547,0],[548,0],[549,0],[550,0],[551,0],[552,0],[553,0],[554,0],[555,0],[556,0],[557,0],[536,1],[537,1],[538,1],[539,1],[540,1],[541,1],[542,1],[543,1],[544,1],[545,1],[546,1],[547,1],[548,1],[549,1],[550,1],[551,1],[552,1],[553,1],[554,1],[555,1],[556,1],[557,1],[537,2],[538,2],[539,2],[540,2],[541,2],[542,2],[543,2],[544,2],[545,2],[546,2],[547,2],[548,2],[549,2],[550,2],[551,2],[552,2],[553,2],[554,2],[555,2],[556,2],[557,2],[558,2],[538,3],[539,3],[540,3],[541,3],[542,3],[543,3],[544,3],[545,3],[546,3],[547,3],[548,3],[549,3],[550,3],[551,3],[552,3],[553,3],[554,3],[555,3],[556,3],[557,3],[558,3],[559,3],[539,4],[540,4],[541,4],[542,4],[543,4],[544,4],[545,4],[546,4],[547,4],[548,4],[549,4],[550,4],[551,4],[552,4],[553,4],[554,4],[555,4],[556,4],[557,4],[558,4],[559,4],[540,5],[541,5],[542,5],[543,5],[544,5],[545,5],[546,5],[547,5],[548,5],[549,5],[550,5],[551,5],[552,5],[553,5],[554,5],[555,5],[556,5],[557,5],[558,5],[559,5],[560,5],[540,6],[541,6],[542,6],[543,6],[544,6],[545,6],[546,6],[547,6],[548,6],[549,6],[550,6],[551,6],[552,6],[553,6],[554,6],[555,6],[556,6],[557,6],[558,6],[559,6],[560,6],[561,6],[541,7],[542,7],[543,7],[544,7],[545,7],[546,7],[547,7],[548,7],[549,7],[550,7],[551,7],[552,7],[553,7],[554,7],[555,7],[556,7],[557,7],[558,7],[559,7],[560,7],[561,7],[542,8],[543,8],[544,8],[545,8],[546,8],[547,8],[548,8],[549,8],[550,8],[551,8],[552,8],[553,8],[554,8],[555,8],[556,8],[557,8],[558,8],[559,8],[560,8],[561,8],[562,8],[543,9],[544,9],[545,9],[546,9],[547,9],[548,9],[549,9],[550,9],[551,9],[552,9],[553,9],[554,9],[555,9],[556,9],[557,9],[558,9],[559,9],[560,9],[561,9],[562,9],[563,9],[544,10],[545,10],[546,10],[547,10],[548,10],[549,10],[550,10],[551,10],[552,10],[553,10],[554,10],[555,10],[556,10],[557,10],[558,10],[559,10],[560,10],[561,10],[562,10],[563,10],[564,10],[545,11],[546,11],[547,11],[548,11],[549,11],[550,11],[551,11],[552,11],[553,11],[554,11],[555,11],[556,11],[557,11],[558,11],[559,11],[560,11],[561,11],[562,11],[563,11],[564,11],[565,11],[545,12],[546,12],[547,12],[548,12],[549,12],[550,12],[551,12],[552,12],[553,12],[554,12],[555,12],[556,12],[557,12],[558,12],[559,12],[560,12],[561,12],[562,12],[563,12],[564,12],[565,12],[546,13],[547,13],[548,13],[549,13],[550,13],[551,13],[552,13],[553,13],[554,13],[555,13],[556,13],[557,13],[558,13],[559,13],[560,13],[561,13],[562,13],[563,13],[564,13],[565,13],[566,13],[547,14],[548,14],[549,14],[550,14],[551,14],[552,14],[553,14],[554,14],[555,14],[556,14],[557,14],[558,14],[559,14],[560,14],[561,14],[562,14],[563,14],[564,14],[565,14],[566,14],[567,14],[548,15],[549,15],[550,15],[551,15],[552,15],[553,15],[554,15],[555,15],[556,15],[557,15],[558,15],[559,15],[560,15],[561,15],[562,15],[563,15],[564,15],[565,15],[566,15],[567,15],[568,15],[549,16],[550,16],[551,16],[552,16],[553,16],[554,16],[555,16],[556,16],[557,16],[558,16],[559,16],[560,16],[561,16],[562,16],[563,16],[564,16],[565,16],[566,16],[567,16],[568,16],[569,16],[550,17],[551,17],[552,17],[553,17],[554,17],[555,17],[556,17],[557,17],[558,17],[559,17],[560,17],[561,17],[562,17],[563,17],[564,17],[565,17],[566,17],[567,17],[568,17],[569,17],[570,17],[551,18],[552,18],[553,18],[554,18],[555,18],[556,18],[557,18],[558,18],[559,18],[560,18],[561,18],[562,18],[563,18],[564,18],[565,18],[566,18],[567,18],[568,18],[569,18],[570,18],[571,18],[551,19],[552,19],[553,19],[554,19],[555,19],[556,19],[557,19],[558,19],[559,19],[560,19],[561,19],[562,19],[563,19],[564,19],[565,19],[566,19],[567,19],[568,19],[569,19],[570,19],[571,19],[572,19],[552,20],[553,20],[554,20],[555,20],[556,20],[557,20],[558,20],[559,20],[560,20],[561,20],[562,20],[563,20],[564,20],[565,20],[566,20],[567,20],[568,20],[569,20],[570,20],[571,20],[572,20],[573,20],[553,21],[554,21],[555,21],[556,21],[557,21],[558,21],[559,21],[560,21],[561,21],[562,21],[563,21],[564,21],[565,21],[566,21],[567,21],[568,21],[569,21],[570,21],[571,21],[572,21],[573,21],[554,22],[555,22],[556,22],[557,22],[558,22],[559,22],[560,22],[561,22],[562,22],[563,22],[564,22],[565,22],[566,22],[567,22],[568,22],[569,22],[570,22],[571,22],[572,22],[573,22],[574,22],[555,23],[556,23],[557,23],[558,23],[559,23],[560,23],[561,23],[562,23],[563,23],[564,23],[565,23],[566,23],[567,23],[568,23],[569,23],[570,23],[571,23],[572,23],[573,23],[574,23],[556,24],[557,24],[558,24],[559,24],[560,24],[561,24],[562,24],[563,24],[564,24],[565,24],[566,24],[567,24],[568,24],[569,24],[570,24],[571,24],[572,24],[573,24],[574,24],[575,24],[557,25],[558,25],[559,25],[560,25],[561,25],[562,25],[563,25],[564,25],[565,25],[566,25],[567,25],[568,25],[569,25],[570,25],[571,25],[572,25],[573,25],[574,25],[575,25],[576,25],[558,26],[559,26],[560,26],[561,26],[562,26],[563,26],[564,26],[565,26],[566,26],[567,26],[568,26],[569,26],[570,26],[571,26],[572,26],[573,26],[574,26],[575,26],[576,26],[577,26],[558,27],[559,27],[560,27],[561,27],[562,27],[563,27],[564,27],[565,27],[566,27],[567,27],[568,27],[569,27],[570,27],[571,27],[572,27],[573,27],[574,27],[575,27],[576,27],[577,27],[578,27],[559,28],[560,28],[561,28],[562,28],[563,28],[564,28],[565,28],[566,28],[567,28],[568,28],[569,28],[570,28],[571,28],[572,28],[573,28],[574,28],[575,28],[576,28],[577,28],[578,28],[579,28],[560,29],[561,29],[562,29],[563,29],[564,29],[565,29],[566,29],[567,29],[568,29],[569,29],[570,29],[571,29],[572,29],[573,29],[574,29],[575,29],[576,29],[577,29],[578,29],[579,29],[580,29],[561,30],[562,30],[563,30],[564,30],[565,30],[566,30],[567,30],[568,30],[569,30],[570,30],[571,30],[572,30],[573,30],[574,30],[575,30],[576,30],[577,30],[578,30],[579,30],[580,30],[581,30],[562,31],[563,31],[564,31],[565,31],[566,31],[567,31],[568,31],[569,31],[570,31],[571,31],[572,31],[573,31],[574,31],[575,31],[576,31],[577,31],[578,31],[579,31],[580,31],[581,31],[582,31],[563,32],[564,32],[565,32],[566,32],[567,32],[568,32],[569,32],[570,32],[571,32],[572,32],[573,32],[574,32],[575,32],[576,32],[577,32],[578,32],[579,32],[580,32],[581,32],[582,32],[583,32],[564,33],[565,33],[566,33],[567,33],[568,33],[569,33],[570,33],[571,33],[572,33],[573,33],[574,33],[575,33],[576,33],[577,33],[578,33],[579,33],[580,33],[581,33],[582,33],[583,33],[584,33],[565,34],[566,34],[567,34],[568,34],[569,34],[570,34],[571,34],[572,34],[573,34],[574,34],[575,34],[576,34],[577,34],[578,34],[579,34],[580,34],[581,34],[582,34],[583,34],[584,34],[585,34],[566,35],[567,35],[568,35],[569,35],[570,35],[571,35],[572,35],[573,35],[574,35],[575,35],[576,35],[577,35],[578,35],[579,35],[580,35],[581,35],[582,35],[583,35],[584,35],[585,35],[586,35],[567,36],[568,36],[569,36],[570,36],[571,36],[572,36],[573,36],[574,36],[575,36],[576,36],[577,36],[578,36],[579,36],[580,36],[581,36],[582,36],[583,36],[584,36],[585,36],[586,36],[587,36],[568,37],[569,37],[570,37],[571,37],[572,37],[573,37],[574,37],[575,37],[576,37],[577,37],[578,37],[579,37],[580,37],[581,37],[582,37],[583,37],[584,37],[585,37],[586,37],[587,37],[568,38],[569,38],[570,38],[571,38],[572,38],[573,38],[574,38],[575,38],[576,38],[577,38],[578,38],[579,38],[580,38],[581,38],[582,38],[583,38],[584,38],[585,38],[586,38],[587,38],[588,38],[569,39],[570,39],[571,39],[572,39],[573,39],[574,39],[575,39],[576,39],[577,39],[578,39],[579,39],[580,39],[581,39],[582,39],[583,39],[584,39],[585,39],[586,39],[587,39],[588,39],[589,39],[570,40],[571,40],[572,40],[573,40],[574,40],[575,40],[576,40],[577,40],[578,40],[579,40],[580,40],[581,40],[582,40],[583,40],[584,40],[585,40],[586,40],[587,40],[588,40],[589,40],[590,40],[571,41],[572,41],[573,41],[574,41],[575,41],[576,41],[577,41],[578,41],[579,41],[580,41],[581,41],[582,41],[583,41],[584,41],[585,41],[586,41],[587,41],[588,41],[589,41],[590,41],[591,41],[572,42],[573,42],[574,42],[575,42],[576,42],[577,42],[578,42],[579,42],[580,42],[581,42],[582,42],[583,42],[584,42],[585,42],[586,42],[587,42],[588,42],[589,42],[590,42],[591,42],[592,42],[573,43],[574,43],[575,43],[576,43],[577,43],[578,43],[579,43],[580,43],[581,43],[582,43],[583,43],[584,43],[585,43],[586,43],[587,43],[588,43],[589,43],[590,43],[591,43],[592,43],[574,44],[575,44],[576,44],[577,44],[578,44],[579,44],[580,44],[581,44],[582,44],[583,44],[584,44],[585,44],[586,44],[587,44],[588,44],[589,44],[590,44],[591,44],[592,44],[593,44],[575,45],[576,45],[577,45],[578,45],[579,45],[580,45],[581,45],[582,45],[583,45],[584,45],[585,45],[586,45],[587,45],[588,45],[589,45],[590,45],[591,45],[592,45],[593,45],[594,45],[576,46],[577,46],[578,46],[579,46],[580,46],[581,46],[582,46],[583,46],[584,46],[585,46],[586,46],[587,46],[588,46],[589,46],[590,46],[591,46],[592,46],[593,46],[594,46],[595,46],[577,47],[578,47],[579,47],[580,47],[581,47],[582,47],[583,47],[584,47],[585,47],[586,47],[587,47],[588,47],[589,47],[590,47],[591,47],[592,47],[593,47],[594,47],[595,47],[578,48],[579,48],[580,48],[581,48],[582,48],[583,48],[584,48],[585,48],[586,48],[587,48],[588,48],[589,48],[590,48],[591,48],[592,48],[593,48],[594,48],[595,48],[596,48],[579,49],[580,49],[581,49],[582,49],[583,49],[584,49],[585,49],[586,49],[587,49],[588,49],[589,49],[590,49],[591,49],[592,49],[593,49],[594,49],[595,49],[596,49],[580,50],[581,50],[582,50],[583,50],[584,50],[585,50],[586,50],[587,50],[588,50],[589,50],[590,50],[591,50],[592,50],[593,50],[594,50],[595,50],[596,50],[597,50],[580,51],[581,51],[582,51],[583,51],[584,51],[585,51],[586,51],[587,51],[588,51],[589,51],[590,51],[591,51],[592,51],[593,51],[594,51],[595,51],[596,51],[597,51],[581,52],[582,52],[583,52],[584,52],[585,52],[586,52],[587,52],[588,52],[589,52],[590,52],[591,52],[592,52],[593,52],[594,52],[595,52],[596,52],[597,52],[598,52],[582,53],[583,53],[584,53],[585,53],[586,53],[587,53],[588,53],[589,53],[590,53],[591,53],[592,53],[593,53],[594,53],[595,53],[596,53],[597,53],[598,53],[599,53],[583,54],[584,54],[585,54],[586,54],[587,54],[588,54],[589,54],[590,54],[591,54],[592,54],[593,54],[594,54],[595,54],[596,54],[597,54],[598,54],[599,54],[600,54],[584,55],[585,55],[586,55],[587,55],[588,55],[589,55],[590,55],[591,55],[592,55],[593,55],[594,55],[595,55],[596,55],[597,55],[598,55],[599,55],[600,55],[601,55],[585,56],[586,56],[587,56],[588,56],[589,56],[590,56],[591,56],[592,56],[593,56],[594,56],[595,56],[596,56],[597,56],[598,56],[599,56],[600,56],[601,56],[602,56],[586,57],[587,57],[588,57],[589,57],[590,57],[591,57],[592,57],[593,57],[594,57],[595,57],[596,57],[597,57],[598,57],[599,57],[600,57],[601,57],[602,57],[603,57],[586,58],[587,58],[588,58],[589,58],[590,58],[591,58],[592,58],[593,58],[594,58],[595,58],[596,58],[597,58],[598,58],[599,58],[600,58],[601,58],[602,58],[603,58],[604,58],[587,59],[588,59],[589,59],[590,59],[591,59],[592,59],[593,59],[594,59],[595,59],[596,59],[597,59],[598,59],[599,59],[600,59],[601,59],[602,59],[603,59],[604,59],[605,59],[588,60],[589,60],[590,60],[591,60],[592,60],[593,60],[594,60],[595,60],[596,60],[597,60],[598,60],[599,60],[600,60],[601,60],[602,60],[603,60],[604,60],[605,60],[589,61],[590,61],[591,61],[592,61],[593,61],[594,61],[595,61],[596,61],[597,61],[598,61],[599,61],[600,61],[601,61],[602,61],[603,61],[604,61],[605,61],[606,61],[590,62],[591,62],[592,62],[593,62],[594,62],[595,62],[596,62],[597,62],[598,62],[599,62],[600,62],[601,62],[602,62],[603,62],[604,62],[605,62],[606,62],[607,62],[590,63],[591,63],[592,63],[593,63],[594,63],[595,63],[596,63],[597,63],[598,63],[599,63],[600,63],[601,63],[602,63],[603,63],[604,63],[605,63],[606,63],[607,63],[591,64],[592,64],[593,64],[594,64],[595,64],[596,64],[597,64],[598,64],[599,64],[600,64],[601,64],[602,64],[603,64],[604,64],[605,64],[606,64],[607,64],[608,64],[592,65],[593,65],[594,65],[595,65],[596,65],[597,65],[598,65],[599,65],[600,65],[601,65],[602,65],[603,65],[604,65],[605,65],[606,65],[607,65],[608,65],[609,65],[593,66],[594,66],[595,66],[596,66],[597,66],[598,66],[599,66],[600,66],[601,66],[602,66],[603,66],[604,66],[605,66],[606,66],[607,66],[608,66],[609,66],[594,67],[595,67],[596,67],[597,67],[598,67],[599,67],[600,67],[601,67],[602,67],[603,67],[604,67],[605,67],[606,67],[607,67],[608,67],[609,67],[610,67],[595,68],[596,68],[597,68],[598,68],[599,68],[600,68],[601,68],[602,68],[603,68],[604,68],[605,68],[606,68],[607,68],[608,68],[609,68],[610,68],[611,68],[595,69],[596,69],[597,69],[598,69],[599,69],[600,69],[601,69],[602,69],[603,69],[604,69],[605,69],[606,69],[607,69],[608,69],[609,69],[610,69],[611,69],[612,69],[596,70],[597,70],[598,70],[599,70],[600,70],[601,70],[602,70],[603,70],[604,70],[605,70],[606,70],[607,70],[608,70],[609,70],[610,70],[611,70],[612,70],[613,70],[597,71],[598,71],[599,71],[600,71],[601,71],[602,71],[603,71],[604,71],[605,71],[606,71],[607,71],[608,71],[609,71],[610,71],[611,71],[612,71],[613,71],[614,71],[598,72],[599,72],[600,72],[601,72],[602,72],[603,72],[604,72],[605,72],[606,72],[607,72],[608,72],[609,72],[610,72],[611,72],[612,72],[613,72],[614,72],[615,72],[599,73],[600,73],[601,73],[602,73],[603,73],[604,73],[605,73],[606,73],[607,73],[608,73],[609,73],[610,73],[611,73],[612,73],[613,73],[614,73],[615,73],[616,73],[599,74],[600,74],[601,74],[602,74],[603,74],[604,74],[605,74],[606,74],[607,74],[608,74],[609,74],[610,74],[611,74],[612,74],[613,74],[614,74],[615,74],[616,74],[617,74],[600,75],[601,75],[602,75],[603,75],[604,75],[605,75],[606,75],[607,75],[608,75],[609,75],[610,75],[611,75],[612,75],[613,75],[614,75],[615,75],[616,75],[617,75],[618,75],[601,76],[602,76],[603,76],[604,76],[605,76],[606,76],[607,76],[608,76],[609,76],[610,76],[611,76],[612,76],[613,76],[614,76],[615,76],[616,76],[617,76],[618,76],[602,77],[603,77],[604,77],[605,77],[606,77],[607,77],[608,77],[609,77],[610,77],[611,77],[612,77],[613,77],[614,77],[615,77],[616,77],[617,77],[618,77],[619,77],[602,78],[603,78],[604,78],[605,78],[606,78],[607,78],[608,78],[609,78],[610,78],[611,78],[612,78],[613,78],[614,78],[615,78],[616,78],[617,78],[618,78],[619,78],[620,78],[603,79],[604,79],[605,79],[606,79],[607,79],[608,79],[609,79],[610,79],[611,79],[612,79],[613,79],[614,79],[615,79],[616,79],[617,79],[618,79],[619,79],[620,79],[604,80],[605,80],[606,80],[607,80],[608,80],[609,80],[610,80],[611,80],[612,80],[613,80],[614,80],[615,80],[616,80],[617,80],[618,80],[619,80],[620,80],[621,80],[605,81],[606,81],[607,81],[608,81],[609,81],[610,81],[611,81],[612,81],[613,81],[614,81],[615,81],[616,81],[617,81],[618,81],[619,81],[620,81],[621,81],[605,82],[606,82],[607,82],[608,82],[609,82],[610,82],[611,82],[612,82],[613,82],[614,82],[615,82],[616,82],[617,82],[618,82],[619,82],[620,82],[621,82],[622,82],[606,83],[607,83],[608,83],[609,83],[610,83],[611,83],[612,83],[613,83],[614,83],[615,83],[616,83],[617,83],[618,83],[619,83],[620,83],[621,83],[622,83],[623,83],[607,84],[608,84],[609,84],[610,84],[611,84],[612,84],[613,84],[614,84],[615,84],[616,84],[617,84],[618,84],[619,84],[620,84],[621,84],[622,84],[623,84],[608,85],[609,85],[610,85],[611,85],[612,85],[613,85],[614,85],[615,85],[616,85],[617,85],[618,85],[619,85],[620,85],[621,85],[622,85],[623,85],[624,85],[609,86],[610,86],[611,86],[612,86],[613,86],[614,86],[615,86],[616,86],[617,86],[618,86],[619,86],[620,86],[621,86],[622,86],[623,86],[624,86],[625,86],[609,87],[610,87],[611,87],[612,87],[613,87],[614,87],[615,87],[616,87],[617,87],[618,87],[619,87],[620,87],[621,87],[622,87],[623,87],[624,87],[625,87],[610,88],[611,88],[612,88],[613,88],[614,88],[615,88],[616,88],[617,88],[618,88],[619,88],[620,88],[621,88],[622,88],[623,88],[624,88],[625,88],[626,88],[611,89],[612,89],[613,89],[614,89],[615,89],[616,89],[617,89],[618,89],[619,89],[620,89],[621,89],[622,89],[623,89],[624,89],[625,89],[626,89],[627,89],[612,90],[613,90],[614,90],[615,90],[616,90],[617,90],[618,90],[619,90],[620,90],[621,90],[622,90],[623,90],[624,90],[625,90],[626,90],[627,90],[612,91],[613,91],[614,91],[615,91],[616,91],[617,91],[618,91],[619,91],[620,91],[621,91],[622,91],[623,91],[624,91],[625,91],[626,91],[627,91],[628,91],[613,92],[614,92],[615,92],[616,92],[617,92],[618,92],[619,92],[620,92],[621,92],[622,92],[623,92],[624,92],[625,92],[626,92],[627,92],[628,92],[629,92],[614,93],[615,93],[616,93],[617,93],[618,93],[619,93],[620,93],[621,93],[622,93],[623,93],[624,93],[625,93],[626,93],[627,93],[628,93],[629,93],[630,93],[615,94],[616,94],[617,94],[618,94],[619,94],[620,94],[621,94],[622,94],[623,94],[624,94],[625,94],[626,94],[627,94],[628,94],[629,94],[630,94],[631,94],[615,95],[616,95],[617,95],[618,95],[619,95],[620,95],[621,95],[622,95],[623,95],[624,95],[625,95],[626,95],[627,95],[628,95],[629,95],[630,95],[631,95],[616,96],[617,96],[618,96],[619,96],[620,96],[621,96],[622,96],[623,96],[624,96],[625,96],[626,96],[627,96],[628,96],[629,96],[630,96],[631,96],[632,96],[617,97],[618,97],[619,97],[620,97],[621,97],[622,97],[623,97],[624,97],[625,97],[626,97],[627,97],[628,97],[629,97],[630,97],[631,97],[632,97],[633,97],[618,98],[619,98],[620,98],[621,98],[622,98],[623,98],[624,98],[625,98],[626,98],[627,98],[628,98],[629,98],[630,98],[631,98],[632,98],[633,98],[618,99],[619,99],[620,99],[621,99],[622,99],[623,99],[624,99],[625,99],[626,99],[627,99],[628,99],[629,99],[630,99],[631,99],[632,99],[633,99],[634,99],[619,100],[620,100],[621,100],[622,100],[623,100],[624,100],[625,100],[626,100],[627,100],[628,100],[629,100],[630,100],[631,100],[632,100],[633,100],[634,100],[635,100],[620,101],[621,101],[622,101],[623,101],[624,101],[625,101],[626,101],[627,101],[628,101],[629,101],[630,101],[631,101],[632,101],[633,101],[634,101],[635,101],[621,102],[622,102],[623,102],[624,102],[625,102],[626,102],[627,102],[628,102],[629,102],[630,102],[631,102],[632,102],[633,102],[634,102],[635,102],[636,102],[621,103],[622,103],[623,103],[624,103],[625,103],[626,103],[627,103],[628,103],[629,103],[630,103],[631,103],[632,103],[633,103],[634,103],[635,103],[636,103],[622,104],[623,104],[624,104],[625,104],[626,104],[627,104],[628,104],[629,104],[630,104],[631,104],[632,104],[633,104],[634,104],[635,104],[636,104],[637,104],[623,105],[624,105],[625,105],[626,105],[627,105],[628,105],[629,105],[630,105],[631,105],[632,105],[633,105],[634,105],[635,105],[636,105],[637,105],[638,105],[624,106],[625,106],[626,106],[627,106],[628,106],[629,106],[630,106],[631,106],[632,106],[633,106],[634,106],[635,106],[636,106],[637,106],[638,106],[639,106],[624,107],[625,107],[626,107],[627,107],[628,107],[629,107],[630,107],[631,107],[632,107],[633,107],[634,107],[635,107],[636,107],[637,107],[638,107],[639,107],[625,108],[626,108],[627,108],[628,108],[629,108],[630,108],[631,108],[632,108],[633,108],[634,108],[635,108],[636,108],[637,108],[638,108],[639,108],[626,109],[627,109],[628,109],[629,109],[630,109],[631,109],[632,109],[633,109],[634,109],[635,109],[636,109],[637,109],[638,109],[639,109],[626,110],[627,110],[628,110],[629,110],[630,110],[631,110],[632,110],[633,110],[634,110],[635,110],[636,110],[637,110],[638,110],[639,110],[627,111],[628,111],[629,111],[630,111],[631,111],[632,111],[633,111],[634,111],[635,111],[636,111],[637,111],[638,111],[639,111],[628,112],[629,112],[630,112],[631,112],[632,112],[633,112],[634,112],[635,112],[636,112],[637,112],[638,112],[639,112],[628,113],[629,113],[630,113],[631,113],[632,113],[633,113],[634,113],[635,113],[636,113],[637,113],[638,113],[639,113],[629,114],[630,114],[631,114],[632,114],[633,114],[634,114],[635,114],[636,114],[637,114],[638,114],[639,114],[630,115],[631,115],[632,115],[633,115],[634,115],[635,115],[636,115],[637,115],[638,115],[639,115],[630,116],[631,116],[632,116],[633,116],[634,116],[635,116],[636,116],[637,116],[638,116],[639,116],[631,117],[632,117],[633,117],[634,117],[635,117],[636,117],[637,117],[638,117],[639,117],[632,118],[633,118],[634,118],[635,118],[636,118],[637,118],[638,118],[639,118],[632,119],[633,119],[634,119],[635,119],[636,119],[637,119],[638,119],[639,119],[633,120],[634,120],[635,120],[636,120],[637,120],[638,120],[639,120],[633,121],[634,121],[635,121],[636,121],[637,121],[638,121],[639,121],[634,122],[635,122],[636,122],[637,122],[638,122],[639,122],[635,123],[636,123],[637,123],[638,123],[639,123],[635,124],[636,124],[637,124],[638,124],[639,124],[636,125],[637,125],[638,125],[639,125],[637,126],[638,126],[639,126],[637,127],[638,127],[639,127],[638,128],[639,128],[639,129],[639,130],[639,249],[638,250],[639,250],[637,251],[638,251],[639,251],[636,252],[637,252],[638,252],[639,252],[635,253],[636,253],[637,253],[634,254],[635,254],[636,254],[632,255],[633,255],[634,255],[635,255],[631,256],[632,256],[633,256],[634,256],[629,257],[630,257],[631,257],[632,257],[633,257],[628,258],[629,258],[630,258],[631,258],[632,258],[626,259],[627,259],[628,259],[629,259],[630,259],[631,259],[625,260],[626,260],[627,260],[628,260],[629,260],[630,260],[623,261],[624,261],[625,261],[626,261],[627,261],[628,261],[629,261],[630,261],[622,262],[623,262],[624,262],[625,262],[626,262],[627,262],[628,262],[620,263],[621,263],[622,263],[623,263],[624,263],[625,263],[626,263],[627,263],[619,264],[620,264],[621,264],[622,264],[623,264],[624,264],[625,264],[619,265],[620,265],[621,265],[622,265],[623,265],[624,265],[618,266],[619,266],[620,266],[621,266],[622,266],[623,266],[618,267],[619,267],[620,267],[621,267],[618,268],[619,268],[616,269],[617,269],[618,269],[614,270],[615,270],[616,270],[612,271],[613,271],[614,271],[615,271],[616,271],[611,272],[612,272],[613,272],[614,272],[608,273],[609,273],[610,273],[611,273],[612,273],[613,273],[606,274],[607,274],[608,274],[609,274],[610,274],[611,274],[604,275],[605,275],[606,275],[607,275],[608,275],[609,275],[601,276],[602,276],[603,276],[604,276],[605,276],[606,276],[607,276],[598,277],[599,277],[600,277],[601,277],[602,277],[603,277],[604,277],[605,277],[595,278],[596,278],[597,278],[598,278],[599,278],[600,278],[601,278],[602,278],[603,278],[593,279],[594,279],[595,279],[596,279],[597,279],[598,279],[599,279],[600,279],[601,279],[591,280],[592,280],[593,280],[594,280],[595,280],[596,280],[597,280],[598,280],[590,281],[591,281],[592,281],[593,281],[594,281],[595,281],[588,282],[589,282],[590,282],[591,282],[592,282],[587,283],[588,283],[589,283],[590,283],[591,283],[587,284],[588,284],[586,285],[584,286],[585,286],[582,287],[583,287],[584,287],[580,288],[581,288],[582,288],[583,288],[579,289],[580,289],[581,289],[576,290],[577,290],[578,290],[579,290],[574,291],[575,291],[576,291],[577,291],[572,292],[573,292],[574,292],[575,292],[570,293],[571,293],[572,293],[568,294],[569,294],[565,295],[566,295],[563,296],[564,296],[561,297],[562,297],[559,298],[560,298],[557,299],[558,299],[555,300],[556,300],[553,301],[554,301],[555,301],[551,302],[552,302],[553,302],[550,303],[551,303],[548,304],[546,305],[547,305],[544,306],[545,306],[542,307],[543,307],[541,308],[542,308],[539,309],[536,310],[537,310],[534,311],[531,312],[532,312],[533,312],[530,313],[531,313],[528,314],[525,315],[526,315],[527,315],[524,316],[525,316],[522,317],[523,317],[520,318],[521,318],[518,319],[519,319],[516,320],[517,320],[518,320],[515,321],[516,321],[513,322],[514,322],[511,323],[512,323],[509,324],[510,324],[507,325],[508,325],[505,326],[506,326],[507,326],[504,327],[505,327],[502,328],[503,328],[501,329],[499,330],[500,330],[497,331],[498,331],[495,332],[496,332],[497,332],[494,333],[495,333],[492,334],[493,334],[491,335],[492,335],[489,336],[490,336],[487,337],[488,337],[489,337],[486,338],[487,338],[484,339],[485,339],[486,339],[483,340],[484,340],[481,341],[482,341],[479,342],[480,342],[481,342],[478,343],[479,343],[476,344],[477,344],[478,344],[475,345],[476,345],[477,345],[474,346],[475,346],[476,346],[477,346],[473,347],[474,347],[475,347],[476,347],[472,348],[473,348],[474,348],[475,348],[470,349],[471,349],[472,349],[473,349],[474,349],[469,350],[470,350],[471,350],[472,350],[473,350],[470,351],[471,351],[472,351],[473,351],[470,352],[471,352],[472,352],[470,353],[471,353],[472,353],[469,354],[470,354],[471,354],[469,355],[470,355],[468,356],[469,356],[467,357],[468,357],[466,358],[467,358],[465,359],[466,359],[464,360],[465,360],[466,360],[464,361],[465,361],[463,362],[464,362],[465,362],[463,363],[464,363],[462,364],[463,364],[464,364],[462,365],[463,365],[464,365],[462,366],[463,366],[462,367],[463,367],[461,368],[462,368],[463,368],[461,369],[462,369],[463,369],[461,370],[462,370],[461,371],[462,371],[461,372],[462,372],[461,373],[462,373],[461,374],[462,374],[461,375],[462,375],[461,376],[462,376],[463,376],[461,377],[462,377],[463,377],[462,378],[463,378],[462,379],[463,379],[462,380],[463,380],[464,380],[463,381],[464,381],[463,382],[464,382],[465,382],[464,383],[465,383],[466,383],[465,384],[466,384],[467,384],[466,385],[467,385],[468,385],[469,385],[467,386],[468,386],[469,386],[470,386],[469,387],[470,387],[471,387],[470,388],[471,388],[472,388],[473,388],[471,389],[472,389],[473,389],[474,389],[473,390],[474,390],[475,390],[476,390],[474,391],[475,391],[476,391],[477,391],[476,392],[477,392],[478,392],[479,392],[477,393],[478,393],[479,393],[480,393],[481,393],[478,394],[479,394],[480,394],[481,394],[482,394],[480,395],[481,395],[482,395],[483,395],[484,395],[482,396],[483,396],[484,396],[485,396],[486,396],[487,396],[484,397],[485,397],[486,397],[487,397],[488,397],[489,397],[486,398],[487,398],[488,398],[489,398],[490,398],[491,398],[489,399],[490,399],[491,399],[492,399],[493,399],[494,399]]
+
+
+export const line1 = [[332,0],[333,0],[334,0],[335,0],[336,0],[337,0],[338,0],[339,0],[340,0],[341,0],[342,0],[343,0],[344,0],[333,1],[334,1],[335,1],[336,1],[337,1],[338,1],[339,1],[340,1],[341,1],[342,1],[343,1],[344,1],[345,1],[333,2],[334,2],[335,2],[336,2],[337,2],[338,2],[339,2],[340,2],[341,2],[342,2],[343,2],[344,2],[345,2],[333,3],[334,3],[335,3],[336,3],[337,3],[338,3],[339,3],[340,3],[341,3],[342,3],[343,3],[344,3],[345,3],[334,4],[335,4],[336,4],[337,4],[338,4],[339,4],[340,4],[341,4],[342,4],[343,4],[344,4],[345,4],[346,4],[334,5],[335,5],[336,5],[337,5],[338,5],[339,5],[340,5],[341,5],[342,5],[343,5],[344,5],[345,5],[346,5],[335,6],[336,6],[337,6],[338,6],[339,6],[340,6],[341,6],[342,6],[343,6],[344,6],[345,6],[346,6],[335,7],[336,7],[337,7],[338,7],[339,7],[340,7],[341,7],[342,7],[343,7],[344,7],[345,7],[346,7],[347,7],[335,8],[336,8],[337,8],[338,8],[339,8],[340,8],[341,8],[342,8],[343,8],[344,8],[345,8],[346,8],[347,8],[336,9],[337,9],[338,9],[339,9],[340,9],[341,9],[342,9],[343,9],[344,9],[345,9],[346,9],[347,9],[336,10],[337,10],[338,10],[339,10],[340,10],[341,10],[342,10],[343,10],[344,10],[345,10],[346,10],[347,10],[348,10],[337,11],[338,11],[339,11],[340,11],[341,11],[342,11],[343,11],[344,11],[345,11],[346,11],[347,11],[348,11],[337,12],[338,12],[339,12],[340,12],[341,12],[342,12],[343,12],[344,12],[345,12],[346,12],[347,12],[348,12],[349,12],[338,13],[339,13],[340,13],[341,13],[342,13],[343,13],[344,13],[345,13],[346,13],[347,13],[348,13],[349,13],[338,14],[339,14],[340,14],[341,14],[342,14],[343,14],[344,14],[345,14],[346,14],[347,14],[348,14],[349,14],[350,14],[339,15],[340,15],[341,15],[342,15],[343,15],[344,15],[345,15],[346,15],[347,15],[348,15],[349,15],[350,15],[339,16],[340,16],[341,16],[342,16],[343,16],[344,16],[345,16],[346,16],[347,16],[348,16],[349,16],[350,16],[351,16],[340,17],[341,17],[342,17],[343,17],[344,17],[345,17],[346,17],[347,17],[348,17],[349,17],[350,17],[351,17],[340,18],[341,18],[342,18],[343,18],[344,18],[345,18],[346,18],[347,18],[348,18],[349,18],[350,18],[351,18],[352,18],[341,19],[342,19],[343,19],[344,19],[345,19],[346,19],[347,19],[348,19],[349,19],[350,19],[351,19],[352,19],[342,20],[343,20],[344,20],[345,20],[346,20],[347,20],[348,20],[349,20],[350,20],[351,20],[352,20],[353,20],[342,21],[343,21],[344,21],[345,21],[346,21],[347,21],[348,21],[349,21],[350,21],[351,21],[352,21],[353,21],[342,22],[343,22],[344,22],[345,22],[346,22],[347,22],[348,22],[349,22],[350,22],[351,22],[352,22],[353,22],[354,22],[343,23],[344,23],[345,23],[346,23],[347,23],[348,23],[349,23],[350,23],[351,23],[352,23],[353,23],[354,23],[344,24],[345,24],[346,24],[347,24],[348,24],[349,24],[350,24],[351,24],[352,24],[353,24],[354,24],[355,24],[344,25],[345,25],[346,25],[347,25],[348,25],[349,25],[350,25],[351,25],[352,25],[353,25],[354,25],[355,25],[344,26],[345,26],[346,26],[347,26],[348,26],[349,26],[350,26],[351,26],[352,26],[353,26],[354,26],[355,26],[345,27],[346,27],[347,27],[348,27],[349,27],[350,27],[351,27],[352,27],[353,27],[354,27],[355,27],[356,27],[345,28],[346,28],[347,28],[348,28],[349,28],[350,28],[351,28],[352,28],[353,28],[354,28],[355,28],[356,28],[346,29],[347,29],[348,29],[349,29],[350,29],[351,29],[352,29],[353,29],[354,29],[355,29],[356,29],[357,29],[346,30],[347,30],[348,30],[349,30],[350,30],[351,30],[352,30],[353,30],[354,30],[355,30],[356,30],[357,30],[347,31],[348,31],[349,31],[350,31],[351,31],[352,31],[353,31],[354,31],[355,31],[356,31],[357,31],[347,32],[348,32],[349,32],[350,32],[351,32],[352,32],[353,32],[354,32],[355,32],[356,32],[357,32],[358,32],[348,33],[349,33],[350,33],[351,33],[352,33],[353,33],[354,33],[355,33],[356,33],[357,33],[358,33],[348,34],[349,34],[350,34],[351,34],[352,34],[353,34],[354,34],[355,34],[356,34],[357,34],[358,34],[359,34],[349,35],[350,35],[351,35],[352,35],[353,35],[354,35],[355,35],[356,35],[357,35],[358,35],[359,35],[349,36],[350,36],[351,36],[352,36],[353,36],[354,36],[355,36],[356,36],[357,36],[358,36],[359,36],[360,36],[349,37],[350,37],[351,37],[352,37],[353,37],[354,37],[355,37],[356,37],[357,37],[358,37],[359,37],[360,37],[361,37],[350,38],[351,38],[352,38],[353,38],[354,38],[355,38],[356,38],[357,38],[358,38],[359,38],[360,38],[361,38],[351,39],[352,39],[353,39],[354,39],[355,39],[356,39],[357,39],[358,39],[359,39],[360,39],[361,39],[362,39],[351,40],[352,40],[353,40],[354,40],[355,40],[356,40],[357,40],[358,40],[359,40],[360,40],[361,40],[362,40],[363,40],[352,41],[353,41],[354,41],[355,41],[356,41],[357,41],[358,41],[359,41],[360,41],[361,41],[362,41],[363,41],[352,42],[353,42],[354,42],[355,42],[356,42],[357,42],[358,42],[359,42],[360,42],[361,42],[362,42],[363,42],[353,43],[354,43],[355,43],[356,43],[357,43],[358,43],[359,43],[360,43],[361,43],[362,43],[363,43],[364,43],[353,44],[354,44],[355,44],[356,44],[357,44],[358,44],[359,44],[360,44],[361,44],[362,44],[363,44],[364,44],[354,45],[355,45],[356,45],[357,45],[358,45],[359,45],[360,45],[361,45],[362,45],[363,45],[364,45],[365,45],[355,46],[356,46],[357,46],[358,46],[359,46],[360,46],[361,46],[362,46],[363,46],[364,46],[365,46],[366,46],[355,47],[356,47],[357,47],[358,47],[359,47],[360,47],[361,47],[362,47],[363,47],[364,47],[365,47],[366,47],[356,48],[357,48],[358,48],[359,48],[360,48],[361,48],[362,48],[363,48],[364,48],[365,48],[366,48],[367,48],[356,49],[357,49],[358,49],[359,49],[360,49],[361,49],[362,49],[363,49],[364,49],[365,49],[366,49],[367,49],[357,50],[358,50],[359,50],[360,50],[361,50],[362,50],[363,50],[364,50],[365,50],[366,50],[367,50],[368,50],[358,51],[359,51],[360,51],[361,51],[362,51],[363,51],[364,51],[365,51],[366,51],[367,51],[368,51],[369,51],[358,52],[359,52],[360,52],[361,52],[362,52],[363,52],[364,52],[365,52],[366,52],[367,52],[368,52],[369,52],[359,53],[360,53],[361,53],[362,53],[363,53],[364,53],[365,53],[366,53],[367,53],[368,53],[369,53],[370,53],[360,54],[361,54],[362,54],[363,54],[364,54],[365,54],[366,54],[367,54],[368,54],[369,54],[370,54],[360,55],[361,55],[362,55],[363,55],[364,55],[365,55],[366,55],[367,55],[368,55],[369,55],[370,55],[371,55],[361,56],[362,56],[363,56],[364,56],[365,56],[366,56],[367,56],[368,56],[369,56],[370,56],[371,56],[372,56],[361,57],[362,57],[363,57],[364,57],[365,57],[366,57],[367,57],[368,57],[369,57],[370,57],[371,57],[372,57],[362,58],[363,58],[364,58],[365,58],[366,58],[367,58],[368,58],[369,58],[370,58],[371,58],[372,58],[373,58],[363,59],[364,59],[365,59],[366,59],[367,59],[368,59],[369,59],[370,59],[371,59],[372,59],[373,59],[374,59],[363,60],[364,60],[365,60],[366,60],[367,60],[368,60],[369,60],[370,60],[371,60],[372,60],[373,60],[374,60],[364,61],[365,61],[366,61],[367,61],[368,61],[369,61],[370,61],[371,61],[372,61],[373,61],[374,61],[375,61],[364,62],[365,62],[366,62],[367,62],[368,62],[369,62],[370,62],[371,62],[372,62],[373,62],[374,62],[375,62],[365,63],[366,63],[367,63],[368,63],[369,63],[370,63],[371,63],[372,63],[373,63],[374,63],[375,63],[376,63],[365,64],[366,64],[367,64],[368,64],[369,64],[370,64],[371,64],[372,64],[373,64],[374,64],[375,64],[376,64],[366,65],[367,65],[368,65],[369,65],[370,65],[371,65],[372,65],[373,65],[374,65],[375,65],[376,65],[366,66],[367,66],[368,66],[369,66],[370,66],[371,66],[372,66],[373,66],[374,66],[375,66],[376,66],[377,66],[367,67],[368,67],[369,67],[370,67],[371,67],[372,67],[373,67],[374,67],[375,67],[376,67],[377,67],[378,67],[368,68],[369,68],[370,68],[371,68],[372,68],[373,68],[374,68],[375,68],[376,68],[377,68],[378,68],[368,69],[369,69],[370,69],[371,69],[372,69],[373,69],[374,69],[375,69],[376,69],[377,69],[378,69],[379,69],[369,70],[370,70],[371,70],[372,70],[373,70],[374,70],[375,70],[376,70],[377,70],[378,70],[379,70],[369,71],[370,71],[371,71],[372,71],[373,71],[374,71],[375,71],[376,71],[377,71],[378,71],[379,71],[380,71],[370,72],[371,72],[372,72],[373,72],[374,72],[375,72],[376,72],[377,72],[378,72],[379,72],[380,72],[381,72],[371,73],[372,73],[373,73],[374,73],[375,73],[376,73],[377,73],[378,73],[379,73],[380,73],[381,73],[371,74],[372,74],[373,74],[374,74],[375,74],[376,74],[377,74],[378,74],[379,74],[380,74],[381,74],[382,74],[372,75],[373,75],[374,75],[375,75],[376,75],[377,75],[378,75],[379,75],[380,75],[381,75],[382,75],[383,75],[373,76],[374,76],[375,76],[376,76],[377,76],[378,76],[379,76],[380,76],[381,76],[382,76],[383,76],[384,76],[373,77],[374,77],[375,77],[376,77],[377,77],[378,77],[379,77],[380,77],[381,77],[382,77],[383,77],[384,77],[385,77],[374,78],[375,78],[376,78],[377,78],[378,78],[379,78],[380,78],[381,78],[382,78],[383,78],[384,78],[385,78],[375,79],[376,79],[377,79],[378,79],[379,79],[380,79],[381,79],[382,79],[383,79],[384,79],[385,79],[386,79],[375,80],[376,80],[377,80],[378,80],[379,80],[380,80],[381,80],[382,80],[383,80],[384,80],[385,80],[386,80],[387,80],[376,81],[377,81],[378,81],[379,81],[380,81],[381,81],[382,81],[383,81],[384,81],[385,81],[386,81],[387,81],[388,81],[377,82],[378,82],[379,82],[380,82],[381,82],[382,82],[383,82],[384,82],[385,82],[386,82],[387,82],[388,82],[389,82],[378,83],[379,83],[380,83],[381,83],[382,83],[383,83],[384,83],[385,83],[386,83],[387,83],[388,83],[389,83],[378,84],[379,84],[380,84],[381,84],[382,84],[383,84],[384,84],[385,84],[386,84],[387,84],[388,84],[389,84],[390,84],[379,85],[380,85],[381,85],[382,85],[383,85],[384,85],[385,85],[386,85],[387,85],[388,85],[389,85],[390,85],[391,85],[380,86],[381,86],[382,86],[383,86],[384,86],[385,86],[386,86],[387,86],[388,86],[389,86],[390,86],[391,86],[392,86],[381,87],[382,87],[383,87],[384,87],[385,87],[386,87],[387,87],[388,87],[389,87],[390,87],[391,87],[392,87],[393,87],[381,88],[382,88],[383,88],[384,88],[385,88],[386,88],[387,88],[388,88],[389,88],[390,88],[391,88],[392,88],[393,88],[394,88],[382,89],[383,89],[384,89],[385,89],[386,89],[387,89],[388,89],[389,89],[390,89],[391,89],[392,89],[393,89],[394,89],[383,90],[384,90],[385,90],[386,90],[387,90],[388,90],[389,90],[390,90],[391,90],[392,90],[393,90],[394,90],[395,90],[384,91],[385,91],[386,91],[387,91],[388,91],[389,91],[390,91],[391,91],[392,91],[393,91],[394,91],[395,91],[384,92],[385,92],[386,92],[387,92],[388,92],[389,92],[390,92],[391,92],[392,92],[393,92],[394,92],[395,92],[396,92],[385,93],[386,93],[387,93],[388,93],[389,93],[390,93],[391,93],[392,93],[393,93],[394,93],[395,93],[396,93],[397,93],[386,94],[387,94],[388,94],[389,94],[390,94],[391,94],[392,94],[393,94],[394,94],[395,94],[396,94],[397,94],[398,94],[387,95],[388,95],[389,95],[390,95],[391,95],[392,95],[393,95],[394,95],[395,95],[396,95],[397,95],[398,95],[387,96],[388,96],[389,96],[390,96],[391,96],[392,96],[393,96],[394,96],[395,96],[396,96],[397,96],[398,96],[399,96],[388,97],[389,97],[390,97],[391,97],[392,97],[393,97],[394,97],[395,97],[396,97],[397,97],[398,97],[399,97],[400,97],[389,98],[390,98],[391,98],[392,98],[393,98],[394,98],[395,98],[396,98],[397,98],[398,98],[399,98],[400,98],[401,98],[390,99],[391,99],[392,99],[393,99],[394,99],[395,99],[396,99],[397,99],[398,99],[399,99],[400,99],[401,99],[402,99],[391,100],[392,100],[393,100],[394,100],[395,100],[396,100],[397,100],[398,100],[399,100],[400,100],[401,100],[402,100],[403,100],[392,101],[393,101],[394,101],[395,101],[396,101],[397,101],[398,101],[399,101],[400,101],[401,101],[402,101],[403,101],[392,102],[393,102],[394,102],[395,102],[396,102],[397,102],[398,102],[399,102],[400,102],[401,102],[402,102],[403,102],[404,102],[393,103],[394,103],[395,103],[396,103],[397,103],[398,103],[399,103],[400,103],[401,103],[402,103],[403,103],[404,103],[394,104],[395,104],[396,104],[397,104],[398,104],[399,104],[400,104],[401,104],[402,104],[403,104],[404,104],[405,104],[395,105],[396,105],[397,105],[398,105],[399,105],[400,105],[401,105],[402,105],[403,105],[404,105],[405,105],[406,105],[395,106],[396,106],[397,106],[398,106],[399,106],[400,106],[401,106],[402,106],[403,106],[404,106],[405,106],[406,106],[407,106],[396,107],[397,107],[398,107],[399,107],[400,107],[401,107],[402,107],[403,107],[404,107],[405,107],[406,107],[407,107],[408,107],[397,108],[398,108],[399,108],[400,108],[401,108],[402,108],[403,108],[404,108],[405,108],[406,108],[407,108],[408,108],[409,108],[398,109],[399,109],[400,109],[401,109],[402,109],[403,109],[404,109],[405,109],[406,109],[407,109],[408,109],[409,109],[410,109],[399,110],[400,110],[401,110],[402,110],[403,110],[404,110],[405,110],[406,110],[407,110],[408,110],[409,110],[410,110],[411,110],[399,111],[400,111],[401,111],[402,111],[403,111],[404,111],[405,111],[406,111],[407,111],[408,111],[409,111],[410,111],[411,111],[412,111],[400,112],[401,112],[402,112],[403,112],[404,112],[405,112],[406,112],[407,112],[408,112],[409,112],[410,112],[411,112],[412,112],[413,112],[401,113],[402,113],[403,113],[404,113],[405,113],[406,113],[407,113],[408,113],[409,113],[410,113],[411,113],[412,113],[413,113],[414,113],[402,114],[403,114],[404,114],[405,114],[406,114],[407,114],[408,114],[409,114],[410,114],[411,114],[412,114],[413,114],[414,114],[415,114],[403,115],[404,115],[405,115],[406,115],[407,115],[408,115],[409,115],[410,115],[411,115],[412,115],[413,115],[414,115],[415,115],[416,115],[404,116],[405,116],[406,116],[407,116],[408,116],[409,116],[410,116],[411,116],[412,116],[413,116],[414,116],[415,116],[416,116],[417,116],[405,117],[406,117],[407,117],[408,117],[409,117],[410,117],[411,117],[412,117],[413,117],[414,117],[415,117],[416,117],[417,117],[418,117],[406,118],[407,118],[408,118],[409,118],[410,118],[411,118],[412,118],[413,118],[414,118],[415,118],[416,118],[417,118],[418,118],[419,118],[407,119],[408,119],[409,119],[410,119],[411,119],[412,119],[413,119],[414,119],[415,119],[416,119],[417,119],[418,119],[419,119],[420,119],[408,120],[409,120],[410,120],[411,120],[412,120],[413,120],[414,120],[415,120],[416,120],[417,120],[418,120],[419,120],[420,120],[421,120],[409,121],[410,121],[411,121],[412,121],[413,121],[414,121],[415,121],[416,121],[417,121],[418,121],[419,121],[420,121],[421,121],[422,121],[410,122],[411,122],[412,122],[413,122],[414,122],[415,122],[416,122],[417,122],[418,122],[419,122],[420,122],[421,122],[422,122],[423,122],[411,123],[412,123],[413,123],[414,123],[415,123],[416,123],[417,123],[418,123],[419,123],[420,123],[421,123],[422,123],[423,123],[424,123],[412,124],[413,124],[414,124],[415,124],[416,124],[417,124],[418,124],[419,124],[420,124],[421,124],[422,124],[423,124],[424,124],[425,124],[426,124],[413,125],[414,125],[415,125],[416,125],[417,125],[418,125],[419,125],[420,125],[421,125],[422,125],[423,125],[424,125],[425,125],[426,125],[427,125],[414,126],[415,126],[416,126],[417,126],[418,126],[419,126],[420,126],[421,126],[422,126],[423,126],[424,126],[425,126],[426,126],[427,126],[428,126],[415,127],[416,127],[417,127],[418,127],[419,127],[420,127],[421,127],[422,127],[423,127],[424,127],[425,127],[426,127],[427,127],[428,127],[429,127],[416,128],[417,128],[418,128],[419,128],[420,128],[421,128],[422,128],[423,128],[424,128],[425,128],[426,128],[427,128],[428,128],[429,128],[430,128],[417,129],[418,129],[419,129],[420,129],[421,129],[422,129],[423,129],[424,129],[425,129],[426,129],[427,129],[428,129],[429,129],[430,129],[431,129],[418,130],[419,130],[420,130],[421,130],[422,130],[423,130],[424,130],[425,130],[426,130],[427,130],[428,130],[429,130],[430,130],[431,130],[432,130],[419,131],[420,131],[421,131],[422,131],[423,131],[424,131],[425,131],[426,131],[427,131],[428,131],[429,131],[430,131],[431,131],[432,131],[433,131],[420,132],[421,132],[422,132],[423,132],[424,132],[425,132],[426,132],[427,132],[428,132],[429,132],[430,132],[431,132],[432,132],[433,132],[434,132],[421,133],[422,133],[423,133],[424,133],[425,133],[426,133],[427,133],[428,133],[429,133],[430,133],[431,133],[432,133],[433,133],[434,133],[435,133],[423,134],[424,134],[425,134],[426,134],[427,134],[428,134],[429,134],[430,134],[431,134],[432,134],[433,134],[434,134],[435,134],[436,134],[424,135],[425,135],[426,135],[427,135],[428,135],[429,135],[430,135],[431,135],[432,135],[433,135],[434,135],[435,135],[436,135],[437,135],[425,136],[426,136],[427,136],[428,136],[429,136],[430,136],[431,136],[432,136],[433,136],[434,136],[435,136],[436,136],[437,136],[438,136],[426,137],[427,137],[428,137],[429,137],[430,137],[431,137],[432,137],[433,137],[434,137],[435,137],[436,137],[437,137],[438,137],[439,137],[427,138],[428,138],[429,138],[430,138],[431,138],[432,138],[433,138],[434,138],[435,138],[436,138],[437,138],[438,138],[439,138],[440,138],[441,138],[428,139],[429,139],[430,139],[431,139],[432,139],[433,139],[434,139],[435,139],[436,139],[437,139],[438,139],[439,139],[440,139],[441,139],[442,139],[429,140],[430,140],[431,140],[432,140],[433,140],[434,140],[435,140],[436,140],[437,140],[438,140],[439,140],[440,140],[441,140],[442,140],[443,140],[444,140],[430,141],[431,141],[432,141],[433,141],[434,141],[435,141],[436,141],[437,141],[438,141],[439,141],[440,141],[441,141],[442,141],[443,141],[444,141],[445,141],[432,142],[433,142],[434,142],[435,142],[436,142],[437,142],[438,142],[439,142],[440,142],[441,142],[442,142],[443,142],[444,142],[445,142],[446,142],[433,143],[434,143],[435,143],[436,143],[437,143],[438,143],[439,143],[440,143],[441,143],[442,143],[443,143],[444,143],[445,143],[446,143],[447,143],[448,143],[434,144],[435,144],[436,144],[437,144],[438,144],[439,144],[440,144],[441,144],[442,144],[443,144],[444,144],[445,144],[446,144],[447,144],[448,144],[449,144],[435,145],[436,145],[437,145],[438,145],[439,145],[440,145],[441,145],[442,145],[443,145],[444,145],[445,145],[446,145],[447,145],[448,145],[449,145],[450,145],[437,146],[438,146],[439,146],[440,146],[441,146],[442,146],[443,146],[444,146],[445,146],[446,146],[447,146],[448,146],[449,146],[450,146],[451,146],[438,147],[439,147],[440,147],[441,147],[442,147],[443,147],[444,147],[445,147],[446,147],[447,147],[448,147],[449,147],[450,147],[451,147],[452,147],[439,148],[440,148],[441,148],[442,148],[443,148],[444,148],[445,148],[446,148],[447,148],[448,148],[449,148],[450,148],[451,148],[452,148],[453,148],[454,148],[441,149],[442,149],[443,149],[444,149],[445,149],[446,149],[447,149],[448,149],[449,149],[450,149],[451,149],[452,149],[453,149],[454,149],[455,149],[442,150],[443,150],[444,150],[445,150],[446,150],[447,150],[448,150],[449,150],[450,150],[451,150],[452,150],[453,150],[454,150],[455,150],[456,150],[443,151],[444,151],[445,151],[446,151],[447,151],[448,151],[449,151],[450,151],[451,151],[452,151],[453,151],[454,151],[455,151],[456,151],[457,151],[445,152],[446,152],[447,152],[448,152],[449,152],[450,152],[451,152],[452,152],[453,152],[454,152],[455,152],[456,152],[457,152],[458,152],[459,152],[446,153],[447,153],[448,153],[449,153],[450,153],[451,153],[452,153],[453,153],[454,153],[455,153],[456,153],[457,153],[458,153],[459,153],[460,153],[447,154],[448,154],[449,154],[450,154],[451,154],[452,154],[453,154],[454,154],[455,154],[456,154],[457,154],[458,154],[459,154],[460,154],[461,154],[462,154],[449,155],[450,155],[451,155],[452,155],[453,155],[454,155],[455,155],[456,155],[457,155],[458,155],[459,155],[460,155],[461,155],[462,155],[463,155],[450,156],[451,156],[452,156],[453,156],[454,156],[455,156],[456,156],[457,156],[458,156],[459,156],[460,156],[461,156],[462,156],[463,156],[464,156],[465,156],[451,157],[452,157],[453,157],[454,157],[455,157],[456,157],[457,157],[458,157],[459,157],[460,157],[461,157],[462,157],[463,157],[464,157],[465,157],[466,157],[453,158],[454,158],[455,158],[456,158],[457,158],[458,158],[459,158],[460,158],[461,158],[462,158],[463,158],[464,158],[465,158],[466,158],[467,158],[454,159],[455,159],[456,159],[457,159],[458,159],[459,159],[460,159],[461,159],[462,159],[463,159],[464,159],[465,159],[466,159],[467,159],[468,159],[455,160],[456,160],[457,160],[458,160],[459,160],[460,160],[461,160],[462,160],[463,160],[464,160],[465,160],[466,160],[467,160],[468,160],[469,160],[457,161],[458,161],[459,161],[460,161],[461,161],[462,161],[463,161],[464,161],[465,161],[466,161],[467,161],[468,161],[469,161],[470,161],[458,162],[459,162],[460,162],[461,162],[462,162],[463,162],[464,162],[465,162],[466,162],[467,162],[468,162],[469,162],[470,162],[471,162],[472,162],[460,163],[461,163],[462,163],[463,163],[464,163],[465,163],[466,163],[467,163],[468,163],[469,163],[470,163],[471,163],[472,163],[473,163],[461,164],[462,164],[463,164],[464,164],[465,164],[466,164],[467,164],[468,164],[469,164],[470,164],[471,164],[472,164],[473,164],[474,164],[462,165],[463,165],[464,165],[465,165],[466,165],[467,165],[468,165],[469,165],[470,165],[471,165],[472,165],[473,165],[474,165],[475,165],[464,166],[465,166],[466,166],[467,166],[468,166],[469,166],[470,166],[471,166],[472,166],[473,166],[474,166],[475,166],[476,166],[477,166],[465,167],[466,167],[467,167],[468,167],[469,167],[470,167],[471,167],[472,167],[473,167],[474,167],[475,167],[476,167],[477,167],[478,167],[467,168],[468,168],[469,168],[470,168],[471,168],[472,168],[473,168],[474,168],[475,168],[476,168],[477,168],[478,168],[479,168],[480,168],[468,169],[469,169],[470,169],[471,169],[472,169],[473,169],[474,169],[475,169],[476,169],[477,169],[478,169],[479,169],[480,169],[481,169],[482,169],[470,170],[471,170],[472,170],[473,170],[474,170],[475,170],[476,170],[477,170],[478,170],[479,170],[480,170],[481,170],[482,170],[483,170],[471,171],[472,171],[473,171],[474,171],[475,171],[476,171],[477,171],[478,171],[479,171],[480,171],[481,171],[482,171],[483,171],[484,171],[485,171],[472,172],[473,172],[474,172],[475,172],[476,172],[477,172],[478,172],[479,172],[480,172],[481,172],[482,172],[483,172],[484,172],[485,172],[486,172],[474,173],[475,173],[476,173],[477,173],[478,173],[479,173],[480,173],[481,173],[482,173],[483,173],[484,173],[485,173],[486,173],[487,173],[488,173],[475,174],[476,174],[477,174],[478,174],[479,174],[480,174],[481,174],[482,174],[483,174],[484,174],[485,174],[486,174],[487,174],[488,174],[489,174],[477,175],[478,175],[479,175],[480,175],[481,175],[482,175],[483,175],[484,175],[485,175],[486,175],[487,175],[488,175],[489,175],[490,175],[491,175],[478,176],[479,176],[480,176],[481,176],[482,176],[483,176],[484,176],[485,176],[486,176],[487,176],[488,176],[489,176],[490,176],[491,176],[492,176],[480,177],[481,177],[482,177],[483,177],[484,177],[485,177],[486,177],[487,177],[488,177],[489,177],[490,177],[491,177],[492,177],[493,177],[494,177],[481,178],[482,178],[483,178],[484,178],[485,178],[486,178],[487,178],[488,178],[489,178],[490,178],[491,178],[492,178],[493,178],[494,178],[495,178],[483,179],[484,179],[485,179],[486,179],[487,179],[488,179],[489,179],[490,179],[491,179],[492,179],[493,179],[494,179],[495,179],[496,179],[497,179],[484,180],[485,180],[486,180],[487,180],[488,180],[489,180],[490,180],[491,180],[492,180],[493,180],[494,180],[495,180],[496,180],[497,180],[498,180],[499,180],[486,181],[487,181],[488,181],[489,181],[490,181],[491,181],[492,181],[493,181],[494,181],[495,181],[496,181],[497,181],[498,181],[499,181],[500,181],[501,181],[488,182],[489,182],[490,182],[491,182],[492,182],[493,182],[494,182],[495,182],[496,182],[497,182],[498,182],[499,182],[500,182],[501,182],[502,182],[503,182],[489,183],[490,183],[491,183],[492,183],[493,183],[494,183],[495,183],[496,183],[497,183],[498,183],[499,183],[500,183],[501,183],[502,183],[503,183],[504,183],[505,183],[491,184],[492,184],[493,184],[494,184],[495,184],[496,184],[497,184],[498,184],[499,184],[500,184],[501,184],[502,184],[503,184],[504,184],[505,184],[506,184],[492,185],[493,185],[494,185],[495,185],[496,185],[497,185],[498,185],[499,185],[500,185],[501,185],[502,185],[503,185],[504,185],[505,185],[506,185],[507,185],[508,185],[494,186],[495,186],[496,186],[497,186],[498,186],[499,186],[500,186],[501,186],[502,186],[503,186],[504,186],[505,186],[506,186],[507,186],[508,186],[509,186],[510,186],[496,187],[497,187],[498,187],[499,187],[500,187],[501,187],[502,187],[503,187],[504,187],[505,187],[506,187],[507,187],[508,187],[509,187],[510,187],[511,187],[498,188],[499,188],[500,188],[501,188],[502,188],[503,188],[504,188],[505,188],[506,188],[507,188],[508,188],[509,188],[510,188],[511,188],[512,188],[513,188],[499,189],[500,189],[501,189],[502,189],[503,189],[504,189],[505,189],[506,189],[507,189],[508,189],[509,189],[510,189],[511,189],[512,189],[513,189],[514,189],[501,190],[502,190],[503,190],[504,190],[505,190],[506,190],[507,190],[508,190],[509,190],[510,190],[511,190],[512,190],[513,190],[514,190],[515,190],[516,190],[502,191],[503,191],[504,191],[505,191],[506,191],[507,191],[508,191],[509,191],[510,191],[511,191],[512,191],[513,191],[514,191],[515,191],[516,191],[517,191],[504,192],[505,192],[506,192],[507,192],[508,192],[509,192],[510,192],[511,192],[512,192],[513,192],[514,192],[515,192],[516,192],[517,192],[518,192],[519,192],[505,193],[506,193],[507,193],[508,193],[509,193],[510,193],[511,193],[512,193],[513,193],[514,193],[515,193],[516,193],[517,193],[518,193],[519,193],[520,193],[507,194],[508,194],[509,194],[510,194],[511,194],[512,194],[513,194],[514,194],[515,194],[516,194],[517,194],[518,194],[519,194],[520,194],[521,194],[522,194],[523,194],[509,195],[510,195],[511,195],[512,195],[513,195],[514,195],[515,195],[516,195],[517,195],[518,195],[519,195],[520,195],[521,195],[522,195],[523,195],[524,195],[511,196],[512,196],[513,196],[514,196],[515,196],[516,196],[517,196],[518,196],[519,196],[520,196],[521,196],[522,196],[523,196],[524,196],[525,196],[512,197],[513,197],[514,197],[515,197],[516,197],[517,197],[518,197],[519,197],[520,197],[521,197],[522,197],[523,197],[524,197],[525,197],[526,197],[514,198],[515,198],[516,198],[517,198],[518,198],[519,198],[520,198],[521,198],[522,198],[523,198],[524,198],[525,198],[526,198],[527,198],[528,198],[515,199],[516,199],[517,199],[518,199],[519,199],[520,199],[521,199],[522,199],[523,199],[524,199],[525,199],[526,199],[527,199],[528,199],[529,199],[530,199],[517,200],[518,200],[519,200],[520,200],[521,200],[522,200],[523,200],[524,200],[525,200],[526,200],[527,200],[528,200],[529,200],[530,200],[531,200],[532,200],[519,201],[520,201],[521,201],[522,201],[523,201],[524,201],[525,201],[526,201],[527,201],[528,201],[529,201],[530,201],[531,201],[532,201],[533,201],[534,201],[521,202],[522,202],[523,202],[524,202],[525,202],[526,202],[527,202],[528,202],[529,202],[530,202],[531,202],[532,202],[533,202],[534,202],[535,202],[522,203],[523,203],[524,203],[525,203],[526,203],[527,203],[528,203],[529,203],[530,203],[531,203],[532,203],[533,203],[534,203],[535,203],[536,203],[537,203],[524,204],[525,204],[526,204],[527,204],[528,204],[529,204],[530,204],[531,204],[532,204],[533,204],[534,204],[535,204],[536,204],[537,204],[538,204],[539,204],[526,205],[527,205],[528,205],[529,205],[530,205],[531,205],[532,205],[533,205],[534,205],[535,205],[536,205],[537,205],[538,205],[539,205],[540,205],[541,205],[527,206],[528,206],[529,206],[530,206],[531,206],[532,206],[533,206],[534,206],[535,206],[536,206],[537,206],[538,206],[539,206],[540,206],[541,206],[542,206],[529,207],[530,207],[531,207],[532,207],[533,207],[534,207],[535,207],[536,207],[537,207],[538,207],[539,207],[540,207],[541,207],[542,207],[543,207],[531,208],[532,208],[533,208],[534,208],[535,208],[536,208],[537,208],[538,208],[539,208],[540,208],[541,208],[542,208],[543,208],[544,208],[545,208],[533,209],[534,209],[535,209],[536,209],[537,209],[538,209],[539,209],[540,209],[541,209],[542,209],[543,209],[544,209],[545,209],[546,209],[534,210],[535,210],[536,210],[537,210],[538,210],[539,210],[540,210],[541,210],[542,210],[543,210],[544,210],[545,210],[546,210],[547,210],[536,211],[537,211],[538,211],[539,211],[540,211],[541,211],[542,211],[543,211],[544,211],[545,211],[546,211],[547,211],[548,211],[538,212],[539,212],[540,212],[541,212],[542,212],[543,212],[544,212],[545,212],[546,212],[547,212],[548,212],[549,212],[550,212],[539,213],[540,213],[541,213],[542,213],[543,213],[544,213],[545,213],[546,213],[547,213],[548,213],[549,213],[550,213],[551,213],[552,213],[541,214],[542,214],[543,214],[544,214],[545,214],[546,214],[547,214],[548,214],[549,214],[550,214],[551,214],[552,214],[553,214],[554,214],[542,215],[543,215],[544,215],[545,215],[546,215],[547,215],[548,215],[549,215],[550,215],[551,215],[552,215],[553,215],[554,215],[555,215],[544,216],[545,216],[546,216],[547,216],[548,216],[549,216],[550,216],[551,216],[552,216],[553,216],[554,216],[555,216],[556,216],[557,216],[545,217],[546,217],[547,217],[548,217],[549,217],[550,217],[551,217],[552,217],[553,217],[554,217],[555,217],[556,217],[557,217],[558,217],[547,218],[548,218],[549,218],[550,218],[551,218],[552,218],[553,218],[554,218],[555,218],[556,218],[557,218],[558,218],[559,218],[548,219],[549,219],[550,219],[551,219],[552,219],[553,219],[554,219],[555,219],[556,219],[557,219],[558,219],[559,219],[560,219],[561,219],[550,220],[551,220],[552,220],[553,220],[554,220],[555,220],[556,220],[557,220],[558,220],[559,220],[560,220],[561,220],[562,220],[563,220],[551,221],[552,221],[553,221],[554,221],[555,221],[556,221],[557,221],[558,221],[559,221],[560,221],[561,221],[562,221],[563,221],[564,221],[565,221],[553,222],[554,222],[555,222],[556,222],[557,222],[558,222],[559,222],[560,222],[561,222],[562,222],[563,222],[564,222],[565,222],[566,222],[555,223],[556,223],[557,223],[558,223],[559,223],[560,223],[561,223],[562,223],[563,223],[564,223],[565,223],[566,223],[567,223],[568,223],[556,224],[557,224],[558,224],[559,224],[560,224],[561,224],[562,224],[563,224],[564,224],[565,224],[566,224],[567,224],[568,224],[569,224],[557,225],[558,225],[559,225],[560,225],[561,225],[562,225],[563,225],[564,225],[565,225],[566,225],[567,225],[568,225],[569,225],[570,225],[559,226],[560,226],[561,226],[562,226],[563,226],[564,226],[565,226],[566,226],[567,226],[568,226],[569,226],[570,226],[571,226],[560,227],[561,227],[562,227],[563,227],[564,227],[565,227],[566,227],[567,227],[568,227],[569,227],[570,227],[571,227],[572,227],[573,227],[562,228],[563,228],[564,228],[565,228],[566,228],[567,228],[568,228],[569,228],[570,228],[571,228],[572,228],[573,228],[574,228],[564,229],[565,229],[566,229],[567,229],[568,229],[569,229],[570,229],[571,229],[572,229],[573,229],[574,229],[575,229],[565,230],[566,230],[567,230],[568,230],[569,230],[570,230],[571,230],[572,230],[573,230],[574,230],[575,230],[576,230],[577,230],[566,231],[567,231],[568,231],[569,231],[570,231],[571,231],[572,231],[573,231],[574,231],[575,231],[576,231],[577,231],[578,231],[568,232],[569,232],[570,232],[571,232],[572,232],[573,232],[574,232],[575,232],[576,232],[577,232],[578,232],[579,232],[580,232],[569,233],[570,233],[571,233],[572,233],[573,233],[574,233],[575,233],[576,233],[577,233],[578,233],[579,233],[580,233],[581,233],[571,234],[572,234],[573,234],[574,234],[575,234],[576,234],[577,234],[578,234],[579,234],[580,234],[581,234],[582,234],[572,235],[573,235],[574,235],[575,235],[576,235],[577,235],[578,235],[579,235],[580,235],[581,235],[582,235],[583,235],[584,235],[573,236],[574,236],[575,236],[576,236],[577,236],[578,236],[579,236],[580,236],[581,236],[582,236],[583,236],[584,236],[585,236],[575,237],[576,237],[577,237],[578,237],[579,237],[580,237],[581,237],[582,237],[583,237],[584,237],[585,237],[586,237],[576,238],[577,238],[578,238],[579,238],[580,238],[581,238],[582,238],[583,238],[584,238],[585,238],[586,238],[587,238],[588,238],[577,239],[578,239],[579,239],[580,239],[581,239],[582,239],[583,239],[584,239],[585,239],[586,239],[587,239],[588,239],[589,239],[579,240],[580,240],[581,240],[582,240],[583,240],[584,240],[585,240],[586,240],[587,240],[588,240],[589,240],[590,240],[580,241],[581,241],[582,241],[583,241],[584,241],[585,241],[586,241],[587,241],[588,241],[589,241],[590,241],[591,241],[592,241],[581,242],[582,242],[583,242],[584,242],[585,242],[586,242],[587,242],[588,242],[589,242],[590,242],[591,242],[592,242],[593,242],[583,243],[584,243],[585,243],[586,243],[587,243],[588,243],[589,243],[590,243],[591,243],[592,243],[593,243],[594,243],[595,243],[584,244],[585,244],[586,244],[587,244],[588,244],[589,244],[590,244],[591,244],[592,244],[593,244],[594,244],[595,244],[596,244],[585,245],[586,245],[587,245],[588,245],[589,245],[590,245],[591,245],[592,245],[593,245],[594,245],[595,245],[596,245],[597,245],[586,246],[587,246],[588,246],[589,246],[590,246],[591,246],[592,246],[593,246],[594,246],[595,246],[596,246],[597,246],[598,246],[588,247],[589,247],[590,247],[591,247],[592,247],[593,247],[594,247],[595,247],[596,247],[597,247],[598,247],[599,247],[589,248],[590,248],[591,248],[592,248],[593,248],[594,248],[595,248],[596,248],[597,248],[598,248],[599,248],[600,248],[590,249],[591,249],[592,249],[593,249],[594,249],[595,249],[596,249],[597,249],[598,249],[599,249],[600,249],[601,249],[591,250],[592,250],[593,250],[594,250],[595,250],[596,250],[597,250],[598,250],[599,250],[600,250],[601,250],[602,250],[592,251],[593,251],[594,251],[595,251],[596,251],[597,251],[598,251],[599,251],[600,251],[601,251],[602,251],[603,251],[594,252],[595,252],[596,252],[597,252],[598,252],[599,252],[600,252],[601,252],[602,252],[603,252],[604,252],[595,253],[596,253],[597,253],[598,253],[599,253],[600,253],[601,253],[602,253],[603,253],[604,253],[605,253],[596,254],[597,254],[598,254],[599,254],[600,254],[601,254],[602,254],[603,254],[604,254],[605,254],[606,254],[597,255],[598,255],[599,255],[600,255],[601,255],[602,255],[603,255],[604,255],[605,255],[606,255],[607,255],[598,256],[599,256],[600,256],[601,256],[602,256],[603,256],[604,256],[605,256],[606,256],[607,256],[599,257],[600,257],[601,257],[602,257],[603,257],[604,257],[605,257],[606,257],[607,257],[608,257],[600,258],[601,258],[602,258],[603,258],[604,258],[605,258],[606,258],[607,258],[608,258],[609,258],[610,258],[601,259],[602,259],[603,259],[604,259],[605,259],[606,259],[607,259],[608,259],[609,259],[610,259],[611,259],[602,260],[603,260],[604,260],[605,260],[606,260],[607,260],[608,260],[609,260],[610,260],[611,260],[612,260],[603,261],[604,261],[605,261],[606,261],[607,261],[608,261],[609,261],[610,261],[611,261],[612,261],[613,261],[604,262],[605,262],[606,262],[607,262],[608,262],[609,262],[610,262],[611,262],[612,262],[613,262],[614,262],[605,263],[606,263],[607,263],[608,263],[609,263],[610,263],[611,263],[612,263],[613,263],[614,263],[615,263],[606,264],[607,264],[608,264],[609,264],[610,264],[611,264],[612,264],[613,264],[614,264],[615,264],[606,265],[607,265],[608,265],[609,265],[610,265],[611,265],[612,265],[613,265],[614,265],[615,265],[616,265],[607,266],[608,266],[609,266],[610,266],[611,266],[612,266],[613,266],[614,266],[615,266],[616,266],[617,266],[608,267],[609,267],[610,267],[611,267],[612,267],[613,267],[614,267],[615,267],[616,267],[617,267],[618,267],[609,268],[610,268],[611,268],[612,268],[613,268],[614,268],[615,268],[616,268],[617,268],[618,268],[610,269],[611,269],[612,269],[613,269],[614,269],[615,269],[616,269],[617,269],[618,269],[619,269],[611,270],[612,270],[613,270],[614,270],[615,270],[616,270],[617,270],[618,270],[619,270],[612,271],[613,271],[614,271],[615,271],[616,271],[617,271],[618,271],[619,271],[620,271],[612,272],[613,272],[614,272],[615,272],[616,272],[617,272],[618,272],[619,272],[620,272],[613,273],[614,273],[615,273],[616,273],[617,273],[618,273],[619,273],[620,273],[621,273],[614,274],[615,274],[616,274],[617,274],[618,274],[619,274],[620,274],[621,274],[622,274],[614,275],[615,275],[616,275],[617,275],[618,275],[619,275],[620,275],[621,275],[622,275],[623,275],[615,276],[616,276],[617,276],[618,276],[619,276],[620,276],[621,276],[622,276],[623,276],[624,276],[615,277],[616,277],[617,277],[618,277],[619,277],[620,277],[621,277],[622,277],[623,277],[624,277],[616,278],[617,278],[618,278],[619,278],[620,278],[621,278],[622,278],[623,278],[624,278],[625,278],[617,279],[618,279],[619,279],[620,279],[621,279],[622,279],[623,279],[624,279],[625,279],[626,279],[617,280],[618,280],[619,280],[620,280],[621,280],[622,280],[623,280],[624,280],[625,280],[626,280],[618,281],[619,281],[620,281],[621,281],[622,281],[623,281],[624,281],[625,281],[626,281],[627,281],[619,282],[620,282],[621,282],[622,282],[623,282],[624,282],[625,282],[626,282],[627,282],[619,283],[620,283],[621,283],[622,283],[623,283],[624,283],[625,283],[626,283],[627,283],[628,283],[620,284],[621,284],[622,284],[623,284],[624,284],[625,284],[626,284],[627,284],[628,284],[620,285],[621,285],[622,285],[623,285],[624,285],[625,285],[626,285],[627,285],[628,285],[621,286],[622,286],[623,286],[624,286],[625,286],[626,286],[627,286],[628,286],[629,286],[621,287],[622,287],[623,287],[624,287],[625,287],[626,287],[627,287],[628,287],[629,287],[621,288],[622,288],[623,288],[624,288],[625,288],[626,288],[627,288],[628,288],[629,288],[622,289],[623,289],[624,289],[625,289],[626,289],[627,289],[628,289],[629,289],[630,289],[622,290],[623,290],[624,290],[625,290],[626,290],[627,290],[628,290],[629,290],[630,290],[622,291],[623,291],[624,291],[625,291],[626,291],[627,291],[628,291],[629,291],[630,291],[623,292],[624,292],[625,292],[626,292],[627,292],[628,292],[629,292],[630,292],[623,293],[624,293],[625,293],[626,293],[627,293],[628,293],[629,293],[630,293],[623,294],[624,294],[625,294],[626,294],[627,294],[628,294],[629,294],[630,294],[623,295],[624,295],[625,295],[626,295],[627,295],[628,295],[629,295],[630,295],[623,296],[624,296],[625,296],[626,296],[627,296],[628,296],[629,296],[630,296],[624,297],[625,297],[626,297],[627,297],[628,297],[629,297],[630,297],[624,298],[625,298],[626,298],[627,298],[628,298],[629,298],[630,298],[624,299],[625,299],[626,299],[627,299],[628,299],[629,299],[630,299],[624,300],[625,300],[626,300],[627,300],[628,300],[629,300],[624,301],[625,301],[626,301],[627,301],[628,301],[629,301],[623,302],[624,302],[625,302],[626,302],[627,302],[628,302],[629,302],[623,303],[624,303],[625,303],[626,303],[627,303],[628,303],[629,303],[623,304],[624,304],[625,304],[626,304],[627,304],[628,304],[629,304],[623,305],[624,305],[625,305],[626,305],[627,305],[628,305],[629,305],[623,306],[624,306],[625,306],[626,306],[627,306],[628,306],[629,306],[623,307],[624,307],[625,307],[626,307],[627,307],[628,307],[623,308],[624,308],[625,308],[626,308],[627,308],[628,308],[622,309],[623,309],[624,309],[625,309],[626,309],[627,309],[628,309],[622,310],[623,310],[624,310],[625,310],[626,310],[627,310],[622,311],[623,311],[624,311],[625,311],[626,311],[627,311],[621,312],[622,312],[623,312],[624,312],[625,312],[626,312],[621,313],[622,313],[623,313],[624,313],[625,313],[626,313],[620,314],[621,314],[622,314],[623,314],[624,314],[625,314],[620,315],[621,315],[622,315],[623,315],[624,315],[619,316],[620,316],[621,316],[622,316],[623,316],[619,317],[620,317],[621,317],[622,317],[623,317],[618,318],[619,318],[620,318],[621,318],[622,318],[617,319],[618,319],[619,319],[620,319],[621,319],[616,320],[617,320],[618,320],[619,320],[620,320],[616,321],[617,321],[618,321],[619,321],[615,322],[616,322],[617,322],[614,323],[615,323],[616,323],[613,324],[614,324],[615,324],[611,325],[612,325],[613,325],[610,326],[611,326],[612,326],[608,327],[609,327],[610,327],[611,327],[607,328],[608,328],[609,328],[610,328],[611,328],[605,329],[606,329],[607,329],[608,329],[609,329],[610,329],[603,330],[604,330],[605,330],[606,330],[607,330],[608,330],[609,330],[602,331],[603,331],[604,331],[605,331],[606,331],[607,331],[602,332],[603,332],[604,332],[605,332],[602,333],[603,333],[604,333],[601,334],[602,334],[599,335],[600,335],[601,335],[597,336],[598,336],[599,336],[600,336],[595,337],[596,337],[597,337],[598,337],[593,338],[594,338],[595,338],[596,338],[590,339],[591,339],[592,339],[593,339],[594,339],[587,340],[588,340],[589,340],[590,340],[591,340],[592,340],[584,341],[585,341],[586,341],[587,341],[588,341],[589,341],[590,341],[582,342],[583,342],[584,342],[585,342],[586,342],[587,342],[581,343],[582,343],[583,343],[584,343],[579,344],[580,344],[581,344],[582,344],[578,345],[579,345],[577,346],[578,346],[575,347],[576,347],[577,347],[574,348],[575,348],[572,349],[573,349],[569,350],[570,350],[571,350],[567,351],[568,351],[562,353],[559,354],[560,354],[558,355],[556,356],[553,357],[554,357],[552,358],[546,361],[545,362],[537,365],[533,367],[531,368],[529,369],[527,370],[525,371],[523,372],[524,372],[521,373],[519,374],[517,375],[518,375],[515,376],[516,376],[514,377],[512,378],[513,378],[510,379],[511,379],[509,380],[507,381],[505,382],[506,382],[503,383],[504,383],[502,384],[503,384],[500,385],[501,385],[498,386],[499,386],[497,387],[498,387],[495,388],[496,388],[497,388],[494,389],[495,389],[496,389],[493,390],[494,390],[495,390],[492,391],[493,391],[494,391],[490,392],[491,392],[492,392],[493,392],[490,393],[491,393],[492,393],[493,393],[490,394],[491,394],[492,394],[490,395],[491,395],[492,395],[490,396],[491,396],[489,397],[490,397],[488,398],[489,398],[487,399],[488,399]]
+
+
+export const line0 = [[175,0],[176,0],[177,0],[178,0],[179,0],[180,0],[181,0],[182,0],[183,0],[184,0],[185,0],[186,0],[175,1],[176,1],[177,1],[178,1],[179,1],[180,1],[181,1],[182,1],[183,1],[184,1],[185,1],[186,1],[175,2],[176,2],[177,2],[178,2],[179,2],[180,2],[181,2],[182,2],[183,2],[184,2],[185,2],[174,3],[175,3],[176,3],[177,3],[178,3],[179,3],[180,3],[181,3],[182,3],[183,3],[184,3],[185,3],[174,4],[175,4],[176,4],[177,4],[178,4],[179,4],[180,4],[181,4],[182,4],[183,4],[184,4],[174,5],[175,5],[176,5],[177,5],[178,5],[179,5],[180,5],[181,5],[182,5],[183,5],[184,5],[173,6],[174,6],[175,6],[176,6],[177,6],[178,6],[179,6],[180,6],[181,6],[182,6],[183,6],[173,7],[174,7],[175,7],[176,7],[177,7],[178,7],[179,7],[180,7],[181,7],[182,7],[183,7],[172,8],[173,8],[174,8],[175,8],[176,8],[177,8],[178,8],[179,8],[180,8],[181,8],[182,8],[183,8],[172,9],[173,9],[174,9],[175,9],[176,9],[177,9],[178,9],[179,9],[180,9],[181,9],[182,9],[172,10],[173,10],[174,10],[175,10],[176,10],[177,10],[178,10],[179,10],[180,10],[181,10],[182,10],[172,11],[173,11],[174,11],[175,11],[176,11],[177,11],[178,11],[179,11],[180,11],[181,11],[171,12],[172,12],[173,12],[174,12],[175,12],[176,12],[177,12],[178,12],[179,12],[180,12],[181,12],[171,13],[172,13],[173,13],[174,13],[175,13],[176,13],[177,13],[178,13],[179,13],[180,13],[181,13],[171,14],[172,14],[173,14],[174,14],[175,14],[176,14],[177,14],[178,14],[179,14],[180,14],[170,15],[171,15],[172,15],[173,15],[174,15],[175,15],[176,15],[177,15],[178,15],[179,15],[180,15],[170,16],[171,16],[172,16],[173,16],[174,16],[175,16],[176,16],[177,16],[178,16],[179,16],[180,16],[170,17],[171,17],[172,17],[173,17],[174,17],[175,17],[176,17],[177,17],[178,17],[179,17],[170,18],[171,18],[172,18],[173,18],[174,18],[175,18],[176,18],[177,18],[178,18],[179,18],[169,19],[170,19],[171,19],[172,19],[173,19],[174,19],[175,19],[176,19],[177,19],[178,19],[179,19],[169,20],[170,20],[171,20],[172,20],[173,20],[174,20],[175,20],[176,20],[177,20],[178,20],[179,20],[169,21],[170,21],[171,21],[172,21],[173,21],[174,21],[175,21],[176,21],[177,21],[178,21],[168,22],[169,22],[170,22],[171,22],[172,22],[173,22],[174,22],[175,22],[176,22],[177,22],[178,22],[168,23],[169,23],[170,23],[171,23],[172,23],[173,23],[174,23],[175,23],[176,23],[177,23],[178,23],[168,24],[169,24],[170,24],[171,24],[172,24],[173,24],[174,24],[175,24],[176,24],[177,24],[167,25],[168,25],[169,25],[170,25],[171,25],[172,25],[173,25],[174,25],[175,25],[176,25],[177,25],[167,26],[168,26],[169,26],[170,26],[171,26],[172,26],[173,26],[174,26],[175,26],[176,26],[177,26],[167,27],[168,27],[169,27],[170,27],[171,27],[172,27],[173,27],[174,27],[175,27],[176,27],[167,28],[168,28],[169,28],[170,28],[171,28],[172,28],[173,28],[174,28],[175,28],[176,28],[166,29],[167,29],[168,29],[169,29],[170,29],[171,29],[172,29],[173,29],[174,29],[175,29],[176,29],[166,30],[167,30],[168,30],[169,30],[170,30],[171,30],[172,30],[173,30],[174,30],[175,30],[166,31],[167,31],[168,31],[169,31],[170,31],[171,31],[172,31],[173,31],[174,31],[175,31],[165,32],[166,32],[167,32],[168,32],[169,32],[170,32],[171,32],[172,32],[173,32],[174,32],[175,32],[165,33],[166,33],[167,33],[168,33],[169,33],[170,33],[171,33],[172,33],[173,33],[174,33],[175,33],[165,34],[166,34],[167,34],[168,34],[169,34],[170,34],[171,34],[172,34],[173,34],[174,34],[165,35],[166,35],[167,35],[168,35],[169,35],[170,35],[171,35],[172,35],[173,35],[174,35],[164,36],[165,36],[166,36],[167,36],[168,36],[169,36],[170,36],[171,36],[172,36],[173,36],[174,36],[164,37],[165,37],[166,37],[167,37],[168,37],[169,37],[170,37],[171,37],[172,37],[173,37],[164,38],[165,38],[166,38],[167,38],[168,38],[169,38],[170,38],[171,38],[172,38],[173,38],[164,39],[165,39],[166,39],[167,39],[168,39],[169,39],[170,39],[171,39],[172,39],[173,39],[164,40],[165,40],[166,40],[167,40],[168,40],[169,40],[170,40],[171,40],[172,40],[163,41],[164,41],[165,41],[166,41],[167,41],[168,41],[169,41],[170,41],[171,41],[172,41],[163,42],[164,42],[165,42],[166,42],[167,42],[168,42],[169,42],[170,42],[171,42],[172,42],[163,43],[164,43],[165,43],[166,43],[167,43],[168,43],[169,43],[170,43],[171,43],[172,43],[163,44],[164,44],[165,44],[166,44],[167,44],[168,44],[169,44],[170,44],[171,44],[172,44],[163,45],[164,45],[165,45],[166,45],[167,45],[168,45],[169,45],[170,45],[171,45],[172,45],[163,46],[164,46],[165,46],[166,46],[167,46],[168,46],[169,46],[170,46],[171,46],[172,46],[162,47],[163,47],[164,47],[165,47],[166,47],[167,47],[168,47],[169,47],[170,47],[171,47],[162,48],[163,48],[164,48],[165,48],[166,48],[167,48],[168,48],[169,48],[170,48],[171,48],[162,49],[163,49],[164,49],[165,49],[166,49],[167,49],[168,49],[169,49],[170,49],[171,49],[162,50],[163,50],[164,50],[165,50],[166,50],[167,50],[168,50],[169,50],[170,50],[171,50],[162,51],[163,51],[164,51],[165,51],[166,51],[167,51],[168,51],[169,51],[170,51],[171,51],[162,52],[163,52],[164,52],[165,52],[166,52],[167,52],[168,52],[169,52],[170,52],[171,52],[162,53],[163,53],[164,53],[165,53],[166,53],[167,53],[168,53],[169,53],[170,53],[161,54],[162,54],[163,54],[164,54],[165,54],[166,54],[167,54],[168,54],[169,54],[170,54],[161,55],[162,55],[163,55],[164,55],[165,55],[166,55],[167,55],[168,55],[169,55],[170,55],[161,56],[162,56],[163,56],[164,56],[165,56],[166,56],[167,56],[168,56],[169,56],[170,56],[161,57],[162,57],[163,57],[164,57],[165,57],[166,57],[167,57],[168,57],[169,57],[170,57],[161,58],[162,58],[163,58],[164,58],[165,58],[166,58],[167,58],[168,58],[169,58],[170,58],[161,59],[162,59],[163,59],[164,59],[165,59],[166,59],[167,59],[168,59],[169,59],[170,59],[161,60],[162,60],[163,60],[164,60],[165,60],[166,60],[167,60],[168,60],[169,60],[170,60],[161,61],[162,61],[163,61],[164,61],[165,61],[166,61],[167,61],[168,61],[169,61],[170,61],[161,62],[162,62],[163,62],[164,62],[165,62],[166,62],[167,62],[168,62],[169,62],[170,62],[161,63],[162,63],[163,63],[164,63],[165,63],[166,63],[167,63],[168,63],[169,63],[170,63],[161,64],[162,64],[163,64],[164,64],[165,64],[166,64],[167,64],[168,64],[169,64],[170,64],[160,65],[161,65],[162,65],[163,65],[164,65],[165,65],[166,65],[167,65],[168,65],[169,65],[170,65],[160,66],[161,66],[162,66],[163,66],[164,66],[165,66],[166,66],[167,66],[168,66],[169,66],[160,67],[161,67],[162,67],[163,67],[164,67],[165,67],[166,67],[167,67],[168,67],[169,67],[160,68],[161,68],[162,68],[163,68],[164,68],[165,68],[166,68],[167,68],[168,68],[169,68],[160,69],[161,69],[162,69],[163,69],[164,69],[165,69],[166,69],[167,69],[168,69],[169,69],[160,70],[161,70],[162,70],[163,70],[164,70],[165,70],[166,70],[167,70],[168,70],[169,70],[160,71],[161,71],[162,71],[163,71],[164,71],[165,71],[166,71],[167,71],[168,71],[169,71],[160,72],[161,72],[162,72],[163,72],[164,72],[165,72],[166,72],[167,72],[168,72],[169,72],[160,73],[161,73],[162,73],[163,73],[164,73],[165,73],[166,73],[167,73],[168,73],[160,74],[161,74],[162,74],[163,74],[164,74],[165,74],[166,74],[167,74],[168,74],[160,75],[161,75],[162,75],[163,75],[164,75],[165,75],[166,75],[167,75],[168,75],[160,76],[161,76],[162,76],[163,76],[164,76],[165,76],[166,76],[167,76],[168,76],[160,77],[161,77],[162,77],[163,77],[164,77],[165,77],[166,77],[167,77],[168,77],[160,78],[161,78],[162,78],[163,78],[164,78],[165,78],[166,78],[167,78],[168,78],[160,79],[161,79],[162,79],[163,79],[164,79],[165,79],[166,79],[167,79],[168,79],[160,80],[161,80],[162,80],[163,80],[164,80],[165,80],[166,80],[167,80],[168,80],[160,81],[161,81],[162,81],[163,81],[164,81],[165,81],[166,81],[167,81],[168,81],[160,82],[161,82],[162,82],[163,82],[164,82],[165,82],[166,82],[167,82],[168,82],[160,83],[161,83],[162,83],[163,83],[164,83],[165,83],[166,83],[167,83],[168,83],[160,84],[161,84],[162,84],[163,84],[164,84],[165,84],[166,84],[167,84],[168,84],[160,85],[161,85],[162,85],[163,85],[164,85],[165,85],[166,85],[167,85],[168,85],[160,86],[161,86],[162,86],[163,86],[164,86],[165,86],[166,86],[167,86],[168,86],[160,87],[161,87],[162,87],[163,87],[164,87],[165,87],[166,87],[167,87],[168,87],[160,88],[161,88],[162,88],[163,88],[164,88],[165,88],[166,88],[167,88],[168,88],[160,89],[161,89],[162,89],[163,89],[164,89],[165,89],[166,89],[167,89],[168,89],[160,90],[161,90],[162,90],[163,90],[164,90],[165,90],[166,90],[167,90],[168,90],[160,91],[161,91],[162,91],[163,91],[164,91],[165,91],[166,91],[167,91],[168,91],[160,92],[161,92],[162,92],[163,92],[164,92],[165,92],[166,92],[167,92],[168,92],[160,93],[161,93],[162,93],[163,93],[164,93],[165,93],[166,93],[167,93],[168,93],[160,94],[161,94],[162,94],[163,94],[164,94],[165,94],[166,94],[167,94],[168,94],[160,95],[161,95],[162,95],[163,95],[164,95],[165,95],[166,95],[167,95],[168,95],[160,96],[161,96],[162,96],[163,96],[164,96],[165,96],[166,96],[167,96],[168,96],[160,97],[161,97],[162,97],[163,97],[164,97],[165,97],[166,97],[167,97],[168,97],[160,98],[161,98],[162,98],[163,98],[164,98],[165,98],[166,98],[167,98],[168,98],[160,99],[161,99],[162,99],[163,99],[164,99],[165,99],[166,99],[167,99],[168,99],[160,100],[161,100],[162,100],[163,100],[164,100],[165,100],[166,100],[167,100],[168,100],[160,101],[161,101],[162,101],[163,101],[164,101],[165,101],[166,101],[167,101],[168,101],[160,102],[161,102],[162,102],[163,102],[164,102],[165,102],[166,102],[167,102],[168,102],[160,103],[161,103],[162,103],[163,103],[164,103],[165,103],[166,103],[167,103],[168,103],[160,104],[161,104],[162,104],[163,104],[164,104],[165,104],[166,104],[167,104],[168,104],[160,105],[161,105],[162,105],[163,105],[164,105],[165,105],[166,105],[167,105],[168,105],[161,106],[162,106],[163,106],[164,106],[165,106],[166,106],[167,106],[168,106],[161,107],[162,107],[163,107],[164,107],[165,107],[166,107],[167,107],[168,107],[161,108],[162,108],[163,108],[164,108],[165,108],[166,108],[167,108],[168,108],[161,109],[162,109],[163,109],[164,109],[165,109],[166,109],[167,109],[168,109],[161,110],[162,110],[163,110],[164,110],[165,110],[166,110],[167,110],[168,110],[169,110],[161,111],[162,111],[163,111],[164,111],[165,111],[166,111],[167,111],[168,111],[169,111],[162,112],[163,112],[164,112],[165,112],[166,112],[167,112],[168,112],[169,112],[162,113],[163,113],[164,113],[165,113],[166,113],[167,113],[168,113],[169,113],[162,114],[163,114],[164,114],[165,114],[166,114],[167,114],[168,114],[169,114],[170,114],[162,115],[163,115],[164,115],[165,115],[166,115],[167,115],[168,115],[169,115],[170,115],[162,116],[163,116],[164,116],[165,116],[166,116],[167,116],[168,116],[169,116],[170,116],[162,117],[163,117],[164,117],[165,117],[166,117],[167,117],[168,117],[169,117],[170,117],[162,118],[163,118],[164,118],[165,118],[166,118],[167,118],[168,118],[169,118],[170,118],[162,119],[163,119],[164,119],[165,119],[166,119],[167,119],[168,119],[169,119],[170,119],[163,120],[164,120],[165,120],[166,120],[167,120],[168,120],[169,120],[170,120],[163,121],[164,121],[165,121],[166,121],[167,121],[168,121],[169,121],[170,121],[163,122],[164,122],[165,122],[166,122],[167,122],[168,122],[169,122],[170,122],[163,123],[164,123],[165,123],[166,123],[167,123],[168,123],[169,123],[170,123],[163,124],[164,124],[165,124],[166,124],[167,124],[168,124],[169,124],[170,124],[163,125],[164,125],[165,125],[166,125],[167,125],[168,125],[169,125],[170,125],[163,126],[164,126],[165,126],[166,126],[167,126],[168,126],[169,126],[170,126],[171,126],[164,127],[165,127],[166,127],[167,127],[168,127],[169,127],[170,127],[171,127],[164,128],[165,128],[166,128],[167,128],[168,128],[169,128],[170,128],[171,128],[164,129],[165,129],[166,129],[167,129],[168,129],[169,129],[170,129],[171,129],[164,130],[165,130],[166,130],[167,130],[168,130],[169,130],[170,130],[171,130],[164,131],[165,131],[166,131],[167,131],[168,131],[169,131],[170,131],[171,131],[165,132],[166,132],[167,132],[168,132],[169,132],[170,132],[171,132],[172,132],[165,133],[166,133],[167,133],[168,133],[169,133],[170,133],[171,133],[172,133],[165,134],[166,134],[167,134],[168,134],[169,134],[170,134],[171,134],[172,134],[165,135],[166,135],[167,135],[168,135],[169,135],[170,135],[171,135],[172,135],[166,136],[167,136],[168,136],[169,136],[170,136],[171,136],[172,136],[166,137],[167,137],[168,137],[169,137],[170,137],[171,137],[172,137],[173,137],[166,138],[167,138],[168,138],[169,138],[170,138],[171,138],[172,138],[173,138],[166,139],[167,139],[168,139],[169,139],[170,139],[171,139],[172,139],[173,139],[167,140],[168,140],[169,140],[170,140],[171,140],[172,140],[173,140],[174,140],[167,141],[168,141],[169,141],[170,141],[171,141],[172,141],[173,141],[174,141],[167,142],[168,142],[169,142],[170,142],[171,142],[172,142],[173,142],[174,142],[167,143],[168,143],[169,143],[170,143],[171,143],[172,143],[173,143],[174,143],[167,144],[168,144],[169,144],[170,144],[171,144],[172,144],[173,144],[174,144],[167,145],[168,145],[169,145],[170,145],[171,145],[172,145],[173,145],[174,145],[175,145],[168,146],[169,146],[170,146],[171,146],[172,146],[173,146],[174,146],[175,146],[168,147],[169,147],[170,147],[171,147],[172,147],[173,147],[174,147],[175,147],[168,148],[169,148],[170,148],[171,148],[172,148],[173,148],[174,148],[175,148],[168,149],[169,149],[170,149],[171,149],[172,149],[173,149],[174,149],[175,149],[169,150],[170,150],[171,150],[172,150],[173,150],[174,150],[175,150],[176,150],[169,151],[170,151],[171,151],[172,151],[173,151],[174,151],[175,151],[176,151],[169,152],[170,152],[171,152],[172,152],[173,152],[174,152],[175,152],[176,152],[170,153],[171,153],[172,153],[173,153],[174,153],[175,153],[176,153],[177,153],[170,154],[171,154],[172,154],[173,154],[174,154],[175,154],[176,154],[177,154],[170,155],[171,155],[172,155],[173,155],[174,155],[175,155],[176,155],[177,155],[171,156],[172,156],[173,156],[174,156],[175,156],[176,156],[177,156],[178,156],[171,157],[172,157],[173,157],[174,157],[175,157],[176,157],[177,157],[178,157],[172,158],[173,158],[174,158],[175,158],[176,158],[177,158],[178,158],[172,159],[173,159],[174,159],[175,159],[176,159],[177,159],[178,159],[179,159],[172,160],[173,160],[174,160],[175,160],[176,160],[177,160],[178,160],[179,160],[173,161],[174,161],[175,161],[176,161],[177,161],[178,161],[179,161],[173,162],[174,162],[175,162],[176,162],[177,162],[178,162],[179,162],[180,162],[173,163],[174,163],[175,163],[176,163],[177,163],[178,163],[179,163],[180,163],[174,164],[175,164],[176,164],[177,164],[178,164],[179,164],[180,164],[174,165],[175,165],[176,165],[177,165],[178,165],[179,165],[180,165],[181,165],[174,166],[175,166],[176,166],[177,166],[178,166],[179,166],[180,166],[181,166],[175,167],[176,167],[177,167],[178,167],[179,167],[180,167],[181,167],[182,167],[175,168],[176,168],[177,168],[178,168],[179,168],[180,168],[181,168],[182,168],[175,169],[176,169],[177,169],[178,169],[179,169],[180,169],[181,169],[182,169],[176,170],[177,170],[178,170],[179,170],[180,170],[181,170],[182,170],[176,171],[177,171],[178,171],[179,171],[180,171],[181,171],[182,171],[183,171],[176,172],[177,172],[178,172],[179,172],[180,172],[181,172],[182,172],[183,172],[177,173],[178,173],[179,173],[180,173],[181,173],[182,173],[183,173],[177,174],[178,174],[179,174],[180,174],[181,174],[182,174],[183,174],[184,174],[177,175],[178,175],[179,175],[180,175],[181,175],[182,175],[183,175],[184,175],[178,176],[179,176],[180,176],[181,176],[182,176],[183,176],[184,176],[185,176],[178,177],[179,177],[180,177],[181,177],[182,177],[183,177],[184,177],[185,177],[179,178],[180,178],[181,178],[182,178],[183,178],[184,178],[185,178],[179,179],[180,179],[181,179],[182,179],[183,179],[184,179],[185,179],[186,179],[180,180],[181,180],[182,180],[183,180],[184,180],[185,180],[186,180],[180,181],[181,181],[182,181],[183,181],[184,181],[185,181],[186,181],[187,181],[180,182],[181,182],[182,182],[183,182],[184,182],[185,182],[186,182],[187,182],[181,183],[182,183],[183,183],[184,183],[185,183],[186,183],[187,183],[188,183],[181,184],[182,184],[183,184],[184,184],[185,184],[186,184],[187,184],[188,184],[182,185],[183,185],[184,185],[185,185],[186,185],[187,185],[188,185],[182,186],[183,186],[184,186],[185,186],[186,186],[187,186],[188,186],[189,186],[183,187],[184,187],[185,187],[186,187],[187,187],[188,187],[189,187],[183,188],[184,188],[185,188],[186,188],[187,188],[188,188],[189,188],[190,188],[184,189],[185,189],[186,189],[187,189],[188,189],[189,189],[190,189],[184,190],[185,190],[186,190],[187,190],[188,190],[189,190],[190,190],[191,190],[185,191],[186,191],[187,191],[188,191],[189,191],[190,191],[191,191],[185,192],[186,192],[187,192],[188,192],[189,192],[190,192],[191,192],[192,192],[186,193],[187,193],[188,193],[189,193],[190,193],[191,193],[192,193],[186,194],[187,194],[188,194],[189,194],[190,194],[191,194],[192,194],[193,194],[187,195],[188,195],[189,195],[190,195],[191,195],[192,195],[193,195],[187,196],[188,196],[189,196],[190,196],[191,196],[192,196],[193,196],[188,197],[189,197],[190,197],[191,197],[192,197],[193,197],[194,197],[188,198],[189,198],[190,198],[191,198],[192,198],[193,198],[194,198],[189,199],[190,199],[191,199],[192,199],[193,199],[194,199],[195,199],[189,200],[190,200],[191,200],[192,200],[193,200],[194,200],[195,200],[190,201],[191,201],[192,201],[193,201],[194,201],[195,201],[196,201],[190,202],[191,202],[192,202],[193,202],[194,202],[195,202],[196,202],[197,202],[191,203],[192,203],[193,203],[194,203],[195,203],[196,203],[197,203],[192,204],[193,204],[194,204],[195,204],[196,204],[197,204],[198,204],[192,205],[193,205],[194,205],[195,205],[196,205],[197,205],[198,205],[193,206],[194,206],[195,206],[196,206],[197,206],[198,206],[199,206],[193,207],[194,207],[195,207],[196,207],[197,207],[198,207],[199,207],[194,208],[195,208],[196,208],[197,208],[198,208],[199,208],[200,208],[195,209],[196,209],[197,209],[198,209],[199,209],[200,209],[201,209],[195,210],[196,210],[197,210],[198,210],[199,210],[200,210],[201,210],[196,211],[197,211],[198,211],[199,211],[200,211],[201,211],[202,211],[196,212],[197,212],[198,212],[199,212],[200,212],[201,212],[202,212],[203,212],[197,213],[198,213],[199,213],[200,213],[201,213],[202,213],[203,213],[198,214],[199,214],[200,214],[201,214],[202,214],[203,214],[204,214],[198,215],[199,215],[200,215],[201,215],[202,215],[203,215],[204,215],[205,215],[199,216],[200,216],[201,216],[202,216],[203,216],[204,216],[205,216],[199,217],[200,217],[201,217],[202,217],[203,217],[204,217],[205,217],[206,217],[200,218],[201,218],[202,218],[203,218],[204,218],[205,218],[206,218],[201,219],[202,219],[203,219],[204,219],[205,219],[206,219],[207,219],[201,220],[202,220],[203,220],[204,220],[205,220],[206,220],[207,220],[202,221],[203,221],[204,221],[205,221],[206,221],[207,221],[208,221],[203,222],[204,222],[205,222],[206,222],[207,222],[208,222],[209,222],[203,223],[204,223],[205,223],[206,223],[207,223],[208,223],[209,223],[204,224],[205,224],[206,224],[207,224],[208,224],[209,224],[210,224],[205,225],[206,225],[207,225],[208,225],[209,225],[210,225],[211,225],[206,226],[207,226],[208,226],[209,226],[210,226],[211,226],[212,226],[206,227],[207,227],[208,227],[209,227],[210,227],[211,227],[212,227],[213,227],[207,228],[208,228],[209,228],[210,228],[211,228],[212,228],[213,228],[208,229],[209,229],[210,229],[211,229],[212,229],[213,229],[214,229],[209,230],[210,230],[211,230],[212,230],[213,230],[214,230],[215,230],[209,231],[210,231],[211,231],[212,231],[213,231],[214,231],[215,231],[216,231],[210,232],[211,232],[212,232],[213,232],[214,232],[215,232],[216,232],[217,232],[211,233],[212,233],[213,233],[214,233],[215,233],[216,233],[217,233],[212,234],[213,234],[214,234],[215,234],[216,234],[217,234],[218,234],[213,235],[214,235],[215,235],[216,235],[217,235],[218,235],[219,235],[213,236],[214,236],[215,236],[216,236],[217,236],[218,236],[219,236],[220,236],[214,237],[215,237],[216,237],[217,237],[218,237],[219,237],[220,237],[221,237],[215,238],[216,238],[217,238],[218,238],[219,238],[220,238],[221,238],[222,238],[216,239],[217,239],[218,239],[219,239],[220,239],[221,239],[222,239],[217,240],[218,240],[219,240],[220,240],[221,240],[222,240],[223,240],[217,241],[218,241],[219,241],[220,241],[221,241],[222,241],[223,241],[224,241],[218,242],[219,242],[220,242],[221,242],[222,242],[223,242],[224,242],[219,243],[220,243],[221,243],[222,243],[223,243],[224,243],[225,243],[220,244],[221,244],[222,244],[223,244],[224,244],[225,244],[226,244],[221,245],[222,245],[223,245],[224,245],[225,245],[226,245],[227,245],[222,246],[223,246],[224,246],[225,246],[226,246],[227,246],[228,246],[229,246],[223,247],[224,247],[225,247],[226,247],[227,247],[228,247],[229,247],[224,248],[225,248],[226,248],[227,248],[228,248],[229,248],[230,248],[231,248],[225,249],[226,249],[227,249],[228,249],[229,249],[230,249],[231,249],[226,250],[227,250],[228,250],[229,250],[230,250],[231,250],[232,250],[233,250],[227,251],[228,251],[229,251],[230,251],[231,251],[232,251],[233,251],[228,252],[229,252],[230,252],[231,252],[232,252],[233,252],[234,252],[229,253],[230,253],[231,253],[232,253],[233,253],[234,253],[235,253],[230,254],[231,254],[232,254],[233,254],[234,254],[235,254],[236,254],[231,255],[232,255],[233,255],[234,255],[235,255],[236,255],[237,255],[238,255],[232,256],[233,256],[234,256],[235,256],[236,256],[237,256],[238,256],[239,256],[233,257],[234,257],[235,257],[236,257],[237,257],[238,257],[239,257],[234,258],[235,258],[236,258],[237,258],[238,258],[239,258],[240,258],[235,259],[236,259],[237,259],[238,259],[239,259],[240,259],[241,259],[236,260],[237,260],[238,260],[239,260],[240,260],[241,260],[242,260],[243,260],[237,261],[238,261],[239,261],[240,261],[241,261],[242,261],[243,261],[244,261],[238,262],[239,262],[240,262],[241,262],[242,262],[243,262],[244,262],[245,262],[239,263],[240,263],[241,263],[242,263],[243,263],[244,263],[245,263],[246,263],[240,264],[241,264],[242,264],[243,264],[244,264],[245,264],[246,264],[247,264],[241,265],[242,265],[243,265],[244,265],[245,265],[246,265],[247,265],[248,265],[243,266],[244,266],[245,266],[246,266],[247,266],[248,266],[249,266],[244,267],[245,267],[246,267],[247,267],[248,267],[249,267],[250,267],[251,267],[245,268],[246,268],[247,268],[248,268],[249,268],[250,268],[251,268],[252,268],[246,269],[247,269],[248,269],[249,269],[250,269],[251,269],[252,269],[253,269],[248,270],[249,270],[250,270],[251,270],[252,270],[253,270],[254,270],[249,271],[250,271],[251,271],[252,271],[253,271],[254,271],[255,271],[250,272],[251,272],[252,272],[253,272],[254,272],[255,272],[256,272],[251,273],[252,273],[253,273],[254,273],[255,273],[256,273],[257,273],[252,274],[253,274],[254,274],[255,274],[256,274],[257,274],[258,274],[259,274],[254,275],[255,275],[256,275],[257,275],[258,275],[259,275],[260,275],[255,276],[256,276],[257,276],[258,276],[259,276],[260,276],[261,276],[256,277],[257,277],[258,277],[259,277],[260,277],[261,277],[262,277],[263,277],[257,278],[258,278],[259,278],[260,278],[261,278],[262,278],[263,278],[264,278],[259,279],[260,279],[261,279],[262,279],[263,279],[264,279],[265,279],[260,280],[261,280],[262,280],[263,280],[264,280],[265,280],[266,280],[261,281],[262,281],[263,281],[264,281],[265,281],[266,281],[267,281],[262,282],[263,282],[264,282],[265,282],[266,282],[267,282],[268,282],[264,283],[265,283],[266,283],[267,283],[268,283],[269,283],[265,284],[266,284],[267,284],[268,284],[269,284],[270,284],[266,285],[267,285],[268,285],[269,285],[270,285],[271,285],[272,285],[268,286],[269,286],[270,286],[271,286],[272,286],[273,286],[269,287],[270,287],[271,287],[272,287],[273,287],[274,287],[275,287],[270,288],[271,288],[272,288],[273,288],[274,288],[275,288],[276,288],[272,289],[273,289],[274,289],[275,289],[276,289],[277,289],[273,290],[274,290],[275,290],[276,290],[277,290],[278,290],[279,290],[274,291],[275,291],[276,291],[277,291],[278,291],[279,291],[280,291],[276,292],[277,292],[278,292],[279,292],[280,292],[281,292],[277,293],[278,293],[279,293],[280,293],[281,293],[282,293],[283,293],[278,294],[279,294],[280,294],[281,294],[282,294],[283,294],[284,294],[280,295],[281,295],[282,295],[283,295],[284,295],[285,295],[286,295],[281,296],[282,296],[283,296],[284,296],[285,296],[286,296],[287,296],[283,297],[284,297],[285,297],[286,297],[287,297],[288,297],[289,297],[284,298],[285,298],[286,298],[287,298],[288,298],[289,298],[290,298],[286,299],[287,299],[288,299],[289,299],[290,299],[291,299],[292,299],[287,300],[288,300],[289,300],[290,300],[291,300],[292,300],[293,300],[288,301],[289,301],[290,301],[291,301],[292,301],[293,301],[294,301],[290,302],[291,302],[292,302],[293,302],[294,302],[295,302],[296,302],[291,303],[292,303],[293,303],[294,303],[295,303],[296,303],[297,303],[293,304],[294,304],[295,304],[296,304],[297,304],[298,304],[294,305],[295,305],[296,305],[297,305],[298,305],[299,305],[300,305],[295,306],[296,306],[297,306],[298,306],[299,306],[300,306],[301,306],[297,307],[298,307],[299,307],[300,307],[301,307],[302,307],[298,308],[299,308],[300,308],[301,308],[302,308],[303,308],[304,308],[300,309],[301,309],[302,309],[303,309],[304,309],[305,309],[301,310],[302,310],[303,310],[304,310],[305,310],[306,310],[307,310],[303,311],[304,311],[305,311],[306,311],[307,311],[308,311],[304,312],[305,312],[306,312],[307,312],[308,312],[309,312],[310,312],[306,313],[307,313],[308,313],[309,313],[310,313],[311,313],[307,314],[308,314],[309,314],[310,314],[311,314],[312,314],[313,314],[309,315],[310,315],[311,315],[312,315],[313,315],[314,315],[310,316],[311,316],[312,316],[313,316],[314,316],[315,316],[311,317],[312,317],[313,317],[314,317],[315,317],[316,317],[313,318],[314,318],[315,318],[316,318],[317,318],[314,319],[315,319],[316,319],[317,319],[318,319],[319,319],[315,320],[316,320],[317,320],[318,320],[319,320],[320,320],[317,321],[318,321],[319,321],[320,321],[321,321],[322,321],[318,322],[319,322],[320,322],[321,322],[322,322],[323,322],[320,323],[321,323],[322,323],[323,323],[324,323],[321,324],[322,324],[323,324],[324,324],[325,324],[326,324],[322,325],[323,325],[324,325],[325,325],[326,325],[327,325],[323,326],[324,326],[325,326],[326,326],[327,326],[328,326],[329,326],[325,327],[326,327],[327,327],[328,327],[329,327],[330,327],[326,328],[327,328],[328,328],[329,328],[330,328],[331,328],[327,329],[328,329],[329,329],[330,329],[331,329],[332,329],[329,330],[330,330],[331,330],[332,330],[333,330],[330,331],[331,331],[332,331],[333,331],[334,331],[335,331],[331,332],[332,332],[333,332],[334,332],[335,332],[336,332],[332,333],[333,333],[334,333],[335,333],[336,333],[337,333],[334,334],[335,334],[336,334],[337,334],[338,334],[335,335],[336,335],[337,335],[338,335],[339,335],[340,335],[336,336],[337,336],[338,336],[339,336],[340,336],[341,336],[337,337],[338,337],[339,337],[340,337],[341,337],[342,337],[339,338],[340,338],[341,338],[342,338],[343,338],[340,339],[341,339],[342,339],[343,339],[344,339],[345,339],[341,340],[342,340],[343,340],[344,340],[345,340],[346,340],[342,341],[343,341],[344,341],[345,341],[346,341],[347,341],[343,342],[344,342],[345,342],[346,342],[347,342],[348,342],[344,343],[345,343],[346,343],[347,343],[348,343],[349,343],[346,344],[347,344],[348,344],[349,344],[350,344],[347,345],[348,345],[349,345],[350,345],[351,345],[348,346],[349,346],[350,346],[351,346],[352,346],[348,347],[349,347],[350,347],[351,347],[352,347],[353,347],[349,348],[350,348],[351,348],[352,348],[353,348],[354,348],[350,349],[351,349],[352,349],[353,349],[354,349],[355,349],[352,350],[353,350],[354,350],[355,350],[356,350],[353,351],[354,351],[355,351],[356,351],[357,351],[353,352],[354,352],[355,352],[356,352],[357,352],[358,352],[354,353],[355,353],[356,353],[357,353],[358,353],[359,353],[355,354],[356,354],[357,354],[358,354],[359,354],[360,354],[356,355],[357,355],[358,355],[359,355],[360,355],[357,356],[358,356],[359,356],[360,356],[361,356],[358,357],[359,357],[360,357],[361,357],[362,357],[358,358],[359,358],[360,358],[361,358],[362,358],[359,359],[360,359],[361,359],[362,359],[363,359],[360,360],[361,360],[362,360],[363,360],[364,360],[360,361],[361,361],[362,361],[363,361],[364,361],[361,362],[362,362],[363,362],[364,362],[365,362],[362,363],[363,363],[364,363],[365,363],[366,363],[362,364],[363,364],[364,364],[365,364],[366,364],[367,364],[363,365],[364,365],[365,365],[366,365],[367,365],[364,366],[365,366],[366,366],[367,366],[368,366],[364,367],[365,367],[366,367],[367,367],[368,367],[365,368],[366,368],[367,368],[368,368],[369,368],[365,369],[366,369],[367,369],[368,369],[369,369],[366,370],[367,370],[368,370],[369,370],[370,370],[366,371],[367,371],[368,371],[369,371],[370,371],[366,372],[367,372],[368,372],[369,372],[370,372],[367,373],[368,373],[369,373],[370,373],[367,374],[368,374],[369,374],[370,374],[371,374],[367,375],[368,375],[369,375],[370,375],[371,375],[367,376],[368,376],[369,376],[370,376],[367,377],[368,377],[369,377],[370,377],[367,378],[368,378],[369,378],[370,378],[367,379],[368,379],[369,379],[370,379],[367,380],[368,380],[369,380],[370,380],[366,381],[367,381],[368,381],[369,381],[370,381],[366,382],[367,382],[368,382],[369,382],[370,382],[366,383],[367,383],[368,383],[369,383],[365,384],[366,384],[367,384],[368,384],[365,385],[366,385],[367,385],[364,386],[365,386],[366,386],[363,387],[364,387],[365,387],[362,388],[363,388],[364,388],[360,389],[361,389],[362,389],[358,390],[359,390],[360,390],[356,391],[357,391],[358,391],[359,391],[354,392],[355,392],[356,392],[357,392],[358,392],[354,393],[355,393],[356,393],[352,394],[353,394],[354,394],[348,395],[349,395],[350,395],[351,395],[342,396],[343,396],[344,396],[345,396],[346,396],[347,396],[348,396],[340,397],[341,397],[342,397],[338,398],[336,399],[337,399]]
+
+
+
+export const baseLine = [[307,187],[308,187],[309,187],[310,187],[311,187],[312,187],[313,187],[314,187],[315,187],[316,187],[317,187],[319,187],[295,188],[296,188],[297,188],[298,188],[299,188],[300,188],[301,188],[302,188],[303,188],[304,188],[305,188],[306,188],[307,188],[308,188],[309,188],[310,188],[311,188],[312,188],[313,188],[334,188],[335,188],[289,189],[290,189],[291,189],[292,189],[293,189],[294,189],[295,189],[296,189],[297,189],[298,189],[299,189],[283,190],[284,190],[285,190],[286,190],[287,190],[288,190],[289,190],[290,190],[291,190],[292,190],[293,190],[279,191],[280,191],[281,191],[282,191],[283,191],[284,191],[285,191],[286,191],[287,191],[349,191],[350,191],[351,191],[352,191],[353,191],[354,191],[273,192],[274,192],[275,192],[276,192],[277,192],[278,192],[279,192],[280,192],[281,192],[282,192],[283,192],[351,192],[352,192],[353,192],[354,192],[355,192],[356,192],[357,192],[358,192],[359,192],[271,193],[272,193],[273,193],[274,193],[275,193],[276,193],[277,193],[278,193],[279,193],[280,193],[281,193],[354,193],[355,193],[356,193],[357,193],[358,193],[359,193],[360,193],[361,193],[362,193],[363,193],[269,194],[270,194],[271,194],[272,194],[273,194],[274,194],[275,194],[276,194],[277,194],[278,194],[358,194],[359,194],[360,194],[361,194],[362,194],[363,194],[364,194],[365,194],[366,194],[266,195],[267,195],[268,195],[269,195],[270,195],[271,195],[272,195],[273,195],[361,195],[362,195],[363,195],[364,195],[365,195],[366,195],[367,195],[368,195],[264,196],[265,196],[266,196],[267,196],[268,196],[269,196],[270,196],[365,196],[366,196],[367,196],[368,196],[369,196],[370,196],[371,196],[261,197],[262,197],[263,197],[264,197],[265,197],[266,197],[267,197],[368,197],[369,197],[370,197],[371,197],[372,197],[373,197],[374,197],[258,198],[259,198],[260,198],[261,198],[262,198],[263,198],[264,198],[265,198],[371,198],[372,198],[373,198],[374,198],[375,198],[376,198],[377,198],[256,199],[257,199],[258,199],[259,199],[260,199],[261,199],[262,199],[263,199],[373,199],[374,199],[375,199],[376,199],[377,199],[378,199],[379,199],[255,200],[256,200],[257,200],[258,200],[259,200],[260,200],[261,200],[376,200],[377,200],[378,200],[379,200],[380,200],[381,200],[382,200],[249,201],[250,201],[251,201],[252,201],[253,201],[254,201],[255,201],[256,201],[257,201],[258,201],[259,201],[378,201],[379,201],[380,201],[381,201],[382,201],[383,201],[384,201],[248,202],[249,202],[250,202],[251,202],[252,202],[253,202],[254,202],[255,202],[256,202],[257,202],[380,202],[381,202],[382,202],[383,202],[384,202],[385,202],[386,202],[237,203],[238,203],[246,203],[247,203],[248,203],[249,203],[250,203],[251,203],[252,203],[253,203],[254,203],[255,203],[383,203],[384,203],[385,203],[386,203],[387,203],[388,203],[389,203],[238,204],[239,204],[245,204],[246,204],[247,204],[248,204],[249,204],[250,204],[251,204],[252,204],[253,204],[385,204],[386,204],[387,204],[388,204],[389,204],[390,204],[391,204],[239,205],[240,205],[241,205],[242,205],[243,205],[244,205],[245,205],[246,205],[247,205],[248,205],[249,205],[250,205],[251,205],[252,205],[387,205],[388,205],[389,205],[390,205],[391,205],[392,205],[393,205],[240,206],[241,206],[242,206],[243,206],[244,206],[245,206],[246,206],[247,206],[248,206],[249,206],[250,206],[251,206],[388,206],[389,206],[390,206],[391,206],[392,206],[393,206],[394,206],[395,206],[396,206],[240,207],[241,207],[242,207],[243,207],[244,207],[245,207],[246,207],[247,207],[248,207],[249,207],[390,207],[391,207],[392,207],[393,207],[394,207],[395,207],[396,207],[397,207],[398,207],[240,208],[241,208],[242,208],[243,208],[244,208],[245,208],[246,208],[247,208],[248,208],[392,208],[393,208],[394,208],[395,208],[396,208],[397,208],[398,208],[399,208],[239,209],[240,209],[241,209],[242,209],[243,209],[244,209],[245,209],[246,209],[247,209],[394,209],[395,209],[396,209],[397,209],[398,209],[399,209],[400,209],[401,209],[238,210],[239,210],[240,210],[241,210],[242,210],[243,210],[244,210],[245,210],[395,210],[396,210],[397,210],[398,210],[399,210],[400,210],[401,210],[402,210],[403,210],[237,211],[238,211],[239,211],[240,211],[241,211],[242,211],[243,211],[397,211],[398,211],[399,211],[400,211],[401,211],[402,211],[403,211],[404,211],[236,212],[237,212],[238,212],[239,212],[240,212],[241,212],[242,212],[398,212],[399,212],[400,212],[401,212],[402,212],[403,212],[404,212],[405,212],[406,212],[235,213],[236,213],[237,213],[238,213],[239,213],[240,213],[241,213],[400,213],[401,213],[402,213],[403,213],[404,213],[405,213],[406,213],[407,213],[408,213],[234,214],[235,214],[236,214],[237,214],[238,214],[239,214],[240,214],[401,214],[402,214],[403,214],[404,214],[405,214],[406,214],[407,214],[408,214],[409,214],[234,215],[235,215],[236,215],[237,215],[238,215],[239,215],[402,215],[403,215],[404,215],[405,215],[406,215],[407,215],[408,215],[409,215],[410,215],[411,215],[233,216],[234,216],[235,216],[236,216],[237,216],[238,216],[404,216],[405,216],[406,216],[407,216],[408,216],[409,216],[410,216],[411,216],[412,216],[232,217],[233,217],[234,217],[235,217],[236,217],[237,217],[405,217],[406,217],[407,217],[408,217],[409,217],[410,217],[411,217],[412,217],[413,217],[232,218],[233,218],[234,218],[235,218],[236,218],[406,218],[407,218],[408,218],[409,218],[410,218],[411,218],[412,218],[413,218],[414,218],[231,219],[232,219],[233,219],[234,219],[235,219],[236,219],[407,219],[408,219],[409,219],[410,219],[411,219],[412,219],[413,219],[414,219],[415,219],[230,220],[231,220],[232,220],[233,220],[234,220],[235,220],[408,220],[409,220],[410,220],[411,220],[412,220],[413,220],[414,220],[415,220],[416,220],[230,221],[231,221],[232,221],[233,221],[234,221],[409,221],[410,221],[411,221],[412,221],[413,221],[414,221],[415,221],[416,221],[417,221],[229,222],[230,222],[231,222],[232,222],[233,222],[234,222],[410,222],[411,222],[412,222],[413,222],[414,222],[415,222],[416,222],[417,222],[418,222],[229,223],[230,223],[231,223],[232,223],[233,223],[411,223],[412,223],[413,223],[414,223],[415,223],[416,223],[417,223],[418,223],[419,223],[228,224],[229,224],[230,224],[231,224],[232,224],[233,224],[412,224],[413,224],[414,224],[415,224],[416,224],[417,224],[418,224],[419,224],[420,224],[228,225],[229,225],[230,225],[231,225],[232,225],[412,225],[413,225],[414,225],[415,225],[416,225],[417,225],[418,225],[419,225],[420,225],[227,226],[228,226],[229,226],[230,226],[231,226],[232,226],[413,226],[414,226],[415,226],[416,226],[417,226],[418,226],[419,226],[420,226],[421,226],[227,227],[228,227],[229,227],[230,227],[231,227],[414,227],[415,227],[416,227],[417,227],[418,227],[419,227],[420,227],[421,227],[422,227],[226,228],[227,228],[228,228],[229,228],[230,228],[231,228],[414,228],[415,228],[416,228],[417,228],[418,228],[419,228],[420,228],[421,228],[422,228],[226,229],[227,229],[228,229],[229,229],[230,229],[231,229],[415,229],[416,229],[417,229],[418,229],[419,229],[420,229],[421,229],[422,229],[423,229],[225,230],[226,230],[227,230],[228,230],[229,230],[230,230],[416,230],[417,230],[418,230],[419,230],[420,230],[421,230],[422,230],[423,230],[225,231],[226,231],[227,231],[228,231],[229,231],[230,231],[416,231],[417,231],[418,231],[419,231],[420,231],[421,231],[422,231],[423,231],[424,231],[225,232],[226,232],[227,232],[228,232],[229,232],[417,232],[418,232],[419,232],[420,232],[421,232],[422,232],[423,232],[424,232],[224,233],[225,233],[226,233],[227,233],[228,233],[229,233],[418,233],[419,233],[420,233],[421,233],[422,233],[423,233],[424,233],[224,234],[225,234],[226,234],[227,234],[228,234],[229,234],[418,234],[419,234],[420,234],[421,234],[422,234],[423,234],[424,234],[425,234],[224,235],[225,235],[226,235],[227,235],[228,235],[229,235],[419,235],[420,235],[421,235],[422,235],[423,235],[424,235],[425,235],[224,236],[225,236],[226,236],[227,236],[228,236],[229,236],[419,236],[420,236],[421,236],[422,236],[423,236],[424,236],[425,236],[426,236],[223,237],[224,237],[225,237],[226,237],[227,237],[228,237],[420,237],[421,237],[422,237],[423,237],[424,237],[425,237],[426,237],[223,238],[224,238],[225,238],[226,238],[227,238],[228,238],[420,238],[421,238],[422,238],[423,238],[424,238],[425,238],[426,238],[223,239],[224,239],[225,239],[226,239],[227,239],[228,239],[420,239],[421,239],[422,239],[423,239],[424,239],[425,239],[426,239],[223,240],[224,240],[225,240],[226,240],[227,240],[228,240],[421,240],[422,240],[423,240],[424,240],[425,240],[426,240],[427,240],[223,241],[224,241],[225,241],[226,241],[227,241],[228,241],[421,241],[422,241],[423,241],[424,241],[425,241],[426,241],[427,241],[223,242],[224,242],[225,242],[226,242],[227,242],[228,242],[421,242],[422,242],[423,242],[424,242],[425,242],[426,242],[427,242],[223,243],[224,243],[225,243],[226,243],[227,243],[228,243],[421,243],[422,243],[423,243],[424,243],[425,243],[426,243],[427,243],[223,244],[224,244],[225,244],[226,244],[227,244],[228,244],[421,244],[422,244],[423,244],[424,244],[425,244],[426,244],[427,244],[223,245],[224,245],[225,245],[226,245],[227,245],[228,245],[422,245],[423,245],[424,245],[425,245],[426,245],[427,245],[223,246],[224,246],[225,246],[226,246],[227,246],[228,246],[422,246],[423,246],[424,246],[425,246],[426,246],[427,246],[223,247],[224,247],[225,247],[226,247],[227,247],[228,247],[422,247],[423,247],[424,247],[425,247],[426,247],[427,247],[223,248],[224,248],[225,248],[226,248],[227,248],[228,248],[422,248],[423,248],[424,248],[425,248],[426,248],[427,248],[223,249],[224,249],[225,249],[226,249],[227,249],[228,249],[422,249],[423,249],[424,249],[425,249],[426,249],[427,249],[223,250],[224,250],[225,250],[226,250],[227,250],[228,250],[229,250],[421,250],[422,250],[423,250],[424,250],[425,250],[426,250],[427,250],[223,251],[224,251],[225,251],[226,251],[227,251],[228,251],[229,251],[421,251],[422,251],[423,251],[424,251],[425,251],[426,251],[427,251],[224,252],[225,252],[226,252],[227,252],[228,252],[229,252],[421,252],[422,252],[423,252],[424,252],[425,252],[426,252],[427,252],[224,253],[225,253],[226,253],[227,253],[228,253],[229,253],[421,253],[422,253],[423,253],[424,253],[425,253],[426,253],[225,254],[226,254],[227,254],[228,254],[229,254],[421,254],[422,254],[423,254],[424,254],[425,254],[225,255],[226,255],[227,255],[228,255],[229,255],[230,255],[421,255],[422,255],[423,255],[424,255],[226,256],[227,256],[228,256],[229,256],[230,256],[421,256],[422,256],[423,256],[226,257],[227,257],[228,257],[229,257],[230,257],[231,257],[421,257],[422,257],[423,257],[226,258],[227,258],[228,258],[229,258],[230,258],[231,258],[422,258],[227,259],[228,259],[229,259],[230,259],[231,259],[422,259],[228,260],[229,260],[230,260],[231,260],[232,260],[421,260],[228,261],[229,261],[230,261],[231,261],[232,261],[421,261],[229,262],[230,262],[231,262],[232,262],[233,262],[420,262],[230,263],[231,263],[232,263],[233,263],[234,263],[419,263],[230,264],[231,264],[232,264],[233,264],[234,264],[418,264],[419,264],[231,265],[232,265],[233,265],[234,265],[235,265],[418,265],[232,266],[233,266],[234,266],[235,266],[236,266],[417,266],[232,267],[233,267],[234,267],[235,267],[236,267],[237,267],[416,267],[417,267],[233,268],[234,268],[235,268],[236,268],[237,268],[416,268],[234,269],[235,269],[236,269],[237,269],[238,269],[415,269],[235,270],[236,270],[237,270],[238,270],[239,270],[414,270],[236,271],[237,271],[238,271],[239,271],[240,271],[413,271],[237,272],[238,272],[239,272],[240,272],[241,272],[412,272],[238,273],[239,273],[240,273],[241,273],[242,273],[411,273],[239,274],[240,274],[241,274],[242,274],[243,274],[410,274],[240,275],[241,275],[242,275],[243,275],[244,275],[245,275],[409,275],[242,276],[243,276],[244,276],[245,276],[246,276],[407,276],[408,276],[243,277],[244,277],[245,277],[246,277],[247,277],[406,277],[407,277],[244,278],[245,278],[246,278],[247,278],[248,278],[405,278],[406,278],[246,279],[247,279],[248,279],[249,279],[403,279],[404,279],[247,280],[248,280],[249,280],[250,280],[251,280],[402,280],[248,281],[249,281],[250,281],[251,281],[252,281],[401,281],[250,282],[251,282],[252,282],[253,282],[254,282],[255,282],[399,282],[251,283],[252,283],[253,283],[254,283],[255,283],[256,283],[397,283],[398,283],[252,284],[253,284],[254,284],[255,284],[256,284],[257,284],[396,284],[254,285],[255,285],[256,285],[257,285],[258,285],[255,286],[256,286],[257,286],[258,286],[259,286],[257,287],[258,287],[259,287],[260,287],[261,287],[259,288],[260,288],[261,288],[262,288],[263,288],[264,289],[265,289],[266,289],[266,290],[267,290],[268,290],[269,290],[268,291],[269,291],[270,291],[271,291],[380,291],[381,291],[271,292],[272,292],[273,292],[378,292],[274,293],[275,293],[276,293],[278,294],[279,294],[364,296],[365,296],[316,300],[317,300],[318,300],[319,300],[320,300],[321,300]]
\ No newline at end of file
diff --git a/extensions/src/doodlebot/Procrustes.ts b/extensions/src/doodlebot/Procrustes.ts
new file mode 100644
index 000000000..7db140142
--- /dev/null
+++ b/extensions/src/doodlebot/Procrustes.ts
@@ -0,0 +1,155 @@
+//import { procrustes } from "./transform-lines-2";
+import {
+ procrustesNormalizeCurve,
+ findProcrustesRotationAngle,
+ rebalanceCurve,
+ shapeSimilarity,
+ rotateCurve
+ } from 'curve-matcher';
+
+import { type Point, type ProcrustesResult, applyTranslation, distanceBetweenPoints, cutOffLineAtOverlap } from './LineHelper';
+
+
+function calculateCentroid(line: Point[]) {
+ const n = line.length;
+
+ // Sum all x and y coordinates
+ const sum = line.reduce((acc: number[], point: Point) => {
+ return [acc[0] + point[0], acc[1] + point[1]];
+ }, [0, 0]);
+
+ // Return the average coordinates as the centroid
+ return [sum[0] / n, sum[1] / n];
+}
+
+
+function getSublinesOfLength(line: Point[], totalDistance: number) {
+ let sublines = [];
+
+ // Iterate over each starting point in the line
+ for (let start = 0; start < line.length - 1; start++) {
+ let currentLine = [line[start]];
+ let currentDistance = 0;
+
+ // Extend the subline from the start point until the total distance is reached
+ for (let i = start + 1; i < line.length; i++) {
+ const point1 = line[i - 1];
+ const point2 = line[i];
+ const segmentDistance = distanceBetweenPoints(point1, point2);
+
+ currentDistance += segmentDistance;
+ currentLine.push(point2);
+
+ // If the accumulated distance meets or exceeds the target, save the subline
+ if (currentDistance >= totalDistance) {
+ sublines.push(currentLine);
+ break;
+ }
+ }
+ }
+
+ return sublines;
+}
+
+
+function findOptimalTranslation(line1: Point[], line2: Point[]) {
+ // Calculate centroids for both lines
+ const centroid1 = calculateCentroid(line1);
+ const centroid2 = calculateCentroid(line2);
+
+ // Determine translation vector needed to align centroids
+ const translationVector = [
+ centroid1[0] - centroid2[0],
+ centroid1[1] - centroid2[1]
+ ];
+
+ // Apply translation to line2
+ const translatedLine = applyTranslation(line2, translationVector);
+
+ return {
+ translationVector,
+ translatedLine
+ };
+}
+
+
+function rebalanceLine(line: Point[]) {
+ return rebalanceCurve(line.map((point: Point) => ({ x: point[0], y: point[1] })), {}).map(point => [point.x, point.y]);
+}
+
+function mapLine(line: Point[]) {
+ return line.map((point: Point) => ({ x: point[0], y: point[1] }))
+}
+
+function mapCurve(line: {x: number, y: number}[]) {
+ return line.map((point: {x: number, y: number}) => [point.x, point.y]);
+}
+
+function getError(line1: Point[], line2: Point[]) {
+ // Normalize and balance each curve
+ const balanced1 = procrustesNormalizeCurve(rebalanceCurve(mapLine(line1), {}));
+ const balanced2 = procrustesNormalizeCurve(rebalanceCurve(mapLine(line2), {}))
+
+ // Find the rotation between the two lines
+ const rotation = findProcrustesRotationAngle(balanced1, balanced2);
+
+ const points1 = rebalanceLine(line1);
+ const points2 = rebalanceLine(line2);
+
+ let rotatedCurve1 = rotateCurve(mapLine(points1), rotation);
+
+ // Find the translation between the two lines
+ const {translatedLine, translationVector} = findOptimalTranslation(points2, mapCurve(rotatedCurve1));
+
+ return {curve1: translatedLine, curve2: points2, rotation: rotation, translation: translationVector}
+}
+
+export function procrustes(line1: Point[], line2: Point[], ratio=0.5): ProcrustesResult {
+ // Balance each line to have the same number of points
+ line1 = mapCurve(rebalanceCurve(mapLine(line1), {}));
+ line2 = mapCurve(rebalanceCurve(mapLine(line2), {}));
+
+ const yValues = line2.map(point => point[1]);
+ const minY = Math.min(...yValues);
+ const maxY = Math.max(...yValues);
+
+ // Get the first ratio % of the 2nd line
+ const range = maxY - minY;
+ const midY = minY + range*ratio;
+ const line2Filtered = line2.filter(point => point[1] >= minY && point[1] <= midY);
+
+ // Get the distance of the filtered line
+ let totalDistance = 0;
+ for (let i = 0; i < line2Filtered.length - 1; i++) {
+ totalDistance += distanceBetweenPoints(line2Filtered[i], line2Filtered[i + 1]);
+ }
+
+ // Get a list of segments from the first line with around the same distance as the first line
+ // TODO: Make this a range instead of a value
+ let sublines = getSublinesOfLength(line1, totalDistance);
+
+ // Get the most similar segment to the filtered second line
+ let maxSimilarity = 0;
+ let maxLine = sublines[0];
+ for (const line of sublines) {
+ const similarity = shapeSimilarity(mapLine(line), mapLine(line2Filtered), {checkRotations: false});
+ if (similarity > maxSimilarity) {
+ maxSimilarity = similarity;
+ maxLine = line;
+ }
+ }
+
+ // Calculate the error between the most similar segments
+ const { rotation, translation } = getError(maxLine, line2Filtered);
+ let rotatedCurve1 = rotateCurve(mapLine(line1), rotation);
+ let translatedLine1 = applyTranslation(mapCurve(rotatedCurve1), translation);
+
+ const end = cutOffLineAtOverlap(translatedLine1, line2Filtered);
+
+ return {rotation, translation, distance: end.distance };
+}
+
+
+
+
+
diff --git a/extensions/src/doodlebot/ReattachBLE.svelte b/extensions/src/doodlebot/ReattachBLE.svelte
new file mode 100644
index 000000000..d9e91cd44
--- /dev/null
+++ b/extensions/src/doodlebot/ReattachBLE.svelte
@@ -0,0 +1,48 @@
+
+
+
+
Doodlebot's bluetooth connection was reset.
+
+ Please click the button below to open up the bluetooth and reselect your
+ device.
+
+
+
+
+
diff --git a/extensions/src/doodlebot/TimeHelper.ts b/extensions/src/doodlebot/TimeHelper.ts
new file mode 100644
index 000000000..532a88b58
--- /dev/null
+++ b/extensions/src/doodlebot/TimeHelper.ts
@@ -0,0 +1,66 @@
+let innerTravelSpeed = 0;
+let adjustedT = 0;
+
+function travelSteps(r: number, angle: number): number {
+ const WHEEL_RADIUS = 2.93; // in inches
+ const STEPLENGTH = 0.055 / 16.0;
+ const radius = r + WHEEL_RADIUS;
+ const outerDistance = (2.0 * Math.PI * radius * (angle / 360.0)) / STEPLENGTH;
+ return outerDistance;
+}
+
+function travelTimeCalc(r: number, a: number): number {
+ const WHEELBASERADIUS = 2.93;
+ const STEPLENGTH = 0.055 / 16.0;
+ const OUTERSPEED = 3200;
+
+ a = Math.abs(a);
+ const radius = r + WHEELBASERADIUS;
+
+ const outerTravelDistance = (2 * Math.PI * radius * (a / 360)) / STEPLENGTH;
+ const outerTravelTime = outerTravelDistance / OUTERSPEED;
+
+ const innerRadius = radius - WHEELBASERADIUS;
+ const innerTravelDistance = (2 * Math.PI * innerRadius * (a / 360)) / STEPLENGTH;
+ innerTravelSpeed = Math.abs(innerTravelDistance / outerTravelTime);
+
+ return outerTravelTime;
+}
+
+function adjustTime(outerTravelTime: number, currentTS: number, targetTS: number, rateOfAcceleration: number) {
+ adjustedT = (targetTS - currentTS) / rateOfAcceleration / 4;
+ return { adjustedT, aT: outerTravelTime + adjustedT };
+}
+
+export function calculateArcTime(radius1: number, angle1: number, radius2: number, angle2: number) {
+ const cmd2p1 = radius2;
+ const cmd2p2 = angle2;
+ const cmd1p1 = radius1;
+ const cmd1p2 = angle1;
+
+ let targetSpeed: number;
+ let travelT: number;
+ let acceleration: number;
+ targetSpeed = 3200; // Turn max speed
+ travelT = travelTimeCalc(cmd2p1, Math.abs(cmd2p2));
+ acceleration = 2500; // Usually 1000
+
+ let priorTravelSpeed: number;
+ travelTimeCalc(cmd1p1, cmd1p2);
+ if ((cmd2p2 < 0 && cmd1p2 >= 0) || (cmd2p2 >= 0 && cmd1p2 < 0)) {
+ priorTravelSpeed = innerTravelSpeed;
+ } else {
+ priorTravelSpeed = 3200;
+ }
+
+ // console.log('Outer travel time for target cmd in seconds: ', travelT);
+ // console.log('Speed of current/previous cmd: ', priorTravelSpeed);
+ // console.log('Target Speed: ', targetSpeed);
+ // console.log('Rate of Acceleration: ', acceleration);
+
+ const { adjustedT, aT } = adjustTime(travelT, priorTravelSpeed, targetSpeed, acceleration);
+ // console.log('Adjustment: ', adjustedT);
+ // console.log('Adjusted time: ', aT);
+ return { travelT, priorTravelSpeed, targetSpeed, acceleration, adjustedT, aT }
+}
+
diff --git a/extensions/src/doodlebot/communication/EventDispatcher.ts b/extensions/src/doodlebot/communication/EventDispatcher.ts
new file mode 100644
index 000000000..5d6c21550
--- /dev/null
+++ b/extensions/src/doodlebot/communication/EventDispatcher.ts
@@ -0,0 +1,57 @@
+/*
+ * micro:bit Web Bluetooth
+ * Copyright (c) 2019 Rob Moran
+ *
+ * The MIT License (MIT)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+import EventEmitter from "events";
+
+export default class EventDispatcher = Record> extends EventEmitter {
+ isEventListenerObject(listener): listener is EventListenerObject {
+ return listener.handleEvent !== undefined;
+ }
+
+ addEventListener(type: K, listener: (event: CustomEvent) => void): void {
+ if (listener) {
+ const handler = this.isEventListenerObject(listener)
+ ? listener.handleEvent
+ : listener;
+ super.addListener(type, handler);
+ }
+ }
+
+ removeEventListener(type: K, callback: (event: CustomEvent) => void): void {
+ if (callback) {
+ const handler = this.isEventListenerObject(callback)
+ ? callback.handleEvent
+ : callback;
+ super.removeListener(type, handler);
+ }
+ }
+
+ dispatchEvent>(eventOrType: K, detail: K extends string ? T[K] : K): boolean {
+ const event = typeof eventOrType === "string"
+ ? new CustomEvent(eventOrType, { detail, })
+ : eventOrType as CustomEvent;
+ return super.emit(event.type, event);
+ }
+}
\ No newline at end of file
diff --git a/extensions/src/doodlebot/communication/PromiseQueue.ts b/extensions/src/doodlebot/communication/PromiseQueue.ts
new file mode 100644
index 000000000..628fe4dfd
--- /dev/null
+++ b/extensions/src/doodlebot/communication/PromiseQueue.ts
@@ -0,0 +1,70 @@
+/*
+ * micro:bit Web Bluetooth
+ * Copyright (c) 2019 Rob Moran
+ *
+ * The MIT License (MIT)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+type QueuedPromise = {
+ fn: () => Promise;
+ resolve: (value?: any | PromiseLike | undefined) => void;
+ reject: (reason?: any) => void;
+}
+
+export default class PromiseQueue {
+ private queue: QueuedPromise[] = [];
+ private running: number = 0;
+
+ constructor(private concurrent = 1) { }
+
+ async pump() {
+ if (this.running >= this.concurrent) return;
+
+ const promise = this.queue.shift();
+
+ if (!promise) return;
+
+ this.running++;
+
+ try {
+ const result = await promise.fn();
+ promise.resolve(result);
+ } catch (error) {
+ promise.reject(error);
+ }
+
+ this.running--;
+
+ return this.pump();
+ }
+
+ add(fn: QueuedPromise["fn"]) {
+ return new Promise((resolve, reject) => {
+ this.queue.push({
+ fn,
+ resolve,
+ reject,
+ });
+
+ return this.pump();
+ });
+ }
+}
\ No newline at end of file
diff --git a/extensions/src/doodlebot/communication/ServiceHelper.ts b/extensions/src/doodlebot/communication/ServiceHelper.ts
new file mode 100644
index 000000000..8bf25265c
--- /dev/null
+++ b/extensions/src/doodlebot/communication/ServiceHelper.ts
@@ -0,0 +1,80 @@
+/*
+ * micro:bit Web Bluetooth
+ * Copyright (c) 2019 Rob Moran
+ *
+ * The MIT License (MIT)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+///
+
+import type EventEmitter from "events";
+import PromiseQueue from "./PromiseQueue";
+
+export interface Service {
+ uuid: BluetoothCharacteristicUUID;
+ create(service: BluetoothRemoteGATTService): Promise;
+}
+
+export default class ServiceHelper {
+ private queue = new PromiseQueue();
+ private characteristics: BluetoothRemoteGATTCharacteristic[];
+
+ constructor(private service: BluetoothRemoteGATTService, private emitter: EventEmitter) { }
+
+ async getCharacteristic(uuid: string) {
+ this.characteristics ??= await this.service.getCharacteristics();
+ return this.characteristics.find((characteristic) => characteristic.uuid === uuid);
+ }
+
+ async getCharacteristicValue(uuid: string) {
+ const characteristic = await this.getCharacteristic(uuid);
+ if (!characteristic) throw new Error("Unable to locate characteristic");
+ return await this.queue.add(async () => characteristic.readValue());
+ }
+
+ async setCharacteristicValue(uuid, value) {
+ const characteristic = await this.getCharacteristic(uuid);
+ if (!characteristic) throw new Error("Unable to locate characteristic");
+ await this.queue.add(async () => characteristic.writeValueWithoutResponse(value));
+ }
+
+ async handleListener(event, uuid, handler) {
+ const characteristic = await this.getCharacteristic(uuid);
+
+ if (!characteristic) return;
+
+ await this.queue.add(async () => characteristic.startNotifications());
+
+ this.emitter.on("newListener", (emitterEvent) => {
+ if (emitterEvent !== event || this.emitter.listenerCount(event) > 0) return;
+ return this.queue.add(async () =>
+ characteristic.addEventListener("characteristicvaluechanged", handler)
+ );
+ });
+
+ this.emitter.on("removeListener", (emitterEvent) => {
+ if (emitterEvent !== event || this.emitter.listenerCount(event) > 0) return;
+ return this.queue.add(async () =>
+ characteristic.removeEventListener("characteristicvaluechanged", handler)
+ );
+ });
+ }
+}
\ No newline at end of file
diff --git a/extensions/src/doodlebot/communication/UartService.ts b/extensions/src/doodlebot/communication/UartService.ts
new file mode 100644
index 000000000..1063bc19c
--- /dev/null
+++ b/extensions/src/doodlebot/communication/UartService.ts
@@ -0,0 +1,111 @@
+/*
+ * micro:bit Web Bluetooth
+ * Copyright (c) 2019 Rob Moran
+ *
+ * The MIT License (MIT)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+import EventDispatcher from "./EventDispatcher";
+import ServiceHelper from "./ServiceHelper";
+
+/**
+ * Events raised by the UART service
+ */
+export interface UartEvents {
+ /**
+ * @hidden
+ */
+ newListener: keyof UartEvents;
+ /**
+ * @hidden
+ */
+ removeListener: keyof UartEvents;
+ /**
+ * Serial data received event
+ */
+ receive: Uint8Array;
+ /**
+ * Serial received text event
+ */
+ receiveText: string;
+}
+
+export default class UartService extends EventDispatcher {
+ static readonly uuid = "6e400001-b5a3-f393-e0a9-e50e24dcca9e";
+
+ static readonly rx_uuid = "6e400002-b5a3-f393-e0a9-e50e24dcca9e";
+
+ static readonly tx_uuid = "6e400003-b5a3-f393-e0a9-e50e24dcca9e";
+
+ static async create(service) {
+ const bluetoothService = new UartService(service);
+ await bluetoothService.init();
+ return bluetoothService;
+ }
+
+ private helper: ServiceHelper;
+
+ constructor(service) {
+ super();
+ this.helper = new ServiceHelper(service, this);
+ }
+
+ async init() {
+ const { tx_uuid } = UartService;
+ await this.helper.handleListener("receive", tx_uuid, this.receiveHandler.bind(this));
+ await this.helper.handleListener("receiveText", tx_uuid, this.receiveTextHandler.bind(this));
+ }
+
+ /**
+ * Send serial data
+ * @param value The buffer to send
+ */
+ async send(value) {
+ return this.helper.setCharacteristicValue(UartService.rx_uuid, value);
+ }
+
+ /**
+ * Send serial text
+ * @param value The text to send
+ */
+ async sendText(value) {
+ console.log("sending text", value);
+ const arrayData = value.split("").map((e) => e.charCodeAt(0));
+ return this.helper.setCharacteristicValue(
+ UartService.rx_uuid,
+ new Uint8Array(arrayData).buffer
+ );
+ }
+
+ receiveHandler(event) {
+ const view = event.target.value;
+ const value = new Uint8Array(view.buffer);
+ this.dispatchEvent("receive", value);
+ }
+
+ receiveTextHandler(event) {
+ const view = event.target.value;
+ const numberArray = new Uint8Array(view.buffer).slice()
+ const value = String.fromCharCode.apply(null, numberArray);
+ console.log("received text", value);
+ this.dispatchEvent("receiveText", value);
+ }
+}
\ No newline at end of file
diff --git a/extensions/src/doodlebot/detection.ts b/extensions/src/doodlebot/detection.ts
new file mode 100644
index 000000000..28dc3c932
--- /dev/null
+++ b/extensions/src/doodlebot/detection.ts
@@ -0,0 +1,161 @@
+import type { ReverseMap } from "$common";
+import {
+ ObjectDetector,
+ FilesetResolver,
+ GestureRecognizer,
+ Detection,
+ ObjectDetectorResult
+} from "@mediapipe/tasks-vision";
+
+let objectDetector: ObjectDetector | null = null;
+
+export const classes = [
+ "person",
+ "bicycle",
+ "car",
+ "motorcycle",
+ "airplane",
+ "bus",
+ "train",
+ "truck",
+ "boat",
+ "traffic light",
+ "fire hydrant",
+ "stop sign",
+ "parking meter",
+ "bench",
+ "bird",
+ "cat",
+ "dog",
+ "horse",
+ "sheep",
+ "cow",
+ "elephant",
+ "bear",
+ "zebra",
+ "giraffe",
+ "backpack",
+ "umbrella",
+ "handbag",
+ "tie",
+ "suitcase",
+ "frisbee",
+ "skis",
+ "snowboard",
+ "sports ball",
+ "kite",
+ "baseball bat",
+ "baseball glove",
+ "skateboard",
+ "surfboard",
+ "tennis racket",
+ "bottle",
+ "wine glass",
+ "cup",
+ "fork",
+ "knife",
+ "spoon",
+ "bowl",
+ "banana",
+ "apple",
+ "sandwich",
+ "orange",
+ "broccoli",
+ "carrot",
+ "hot dog",
+ "pizza",
+ "donut",
+ "cake",
+ "chair",
+ "couch",
+ "potted plant",
+ "bed",
+ "dining table",
+ "toilet",
+ "tv",
+ "laptop",
+ "mouse",
+ "remote",
+ "keyboard",
+ "cell phone",
+ "microwave",
+ "oven",
+ "toaster",
+ "sink",
+ "refrigerator",
+ "book",
+ "clock",
+ "vase",
+ "scissors",
+ "teddy bear",
+ "hair drier",
+ "toothbrush"
+] as const;
+
+const createObjectDetector = async () => {
+ const vision = await FilesetResolver.forVisionTasks(
+ "https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.3/wasm"
+ );
+
+ return ObjectDetector.createFromOptions(vision, {
+ baseOptions: {
+ modelAssetPath: `https://storage.googleapis.com/mediapipe-models/object_detector/efficientdet_lite0/float16/1/efficientdet_lite0.tflite`,
+ delegate: "GPU" // TODO: is this the best setting?
+ },
+ scoreThreshold: 0.5,
+ runningMode: "IMAGE"
+ });
+};
+
+export const objectDetection = async (image: HTMLImageElement) => {
+ objectDetector ??= await createObjectDetector();
+ return objectDetector.detect(image);
+}
+
+let gestureRecognizer: GestureRecognizer | null = null;
+
+export const gestures = {
+ thumbsUp: "Thumb_Up",
+ thumbsDown: "Thumb_Down",
+ peace: "Victory",
+ up: "Pointing_Up",
+ fist: "Closed_Fist",
+ love: "ILoveYou",
+ open: "Open_Palm",
+} as const;
+
+export const categoryByGesture = Object.entries(gestures).reduce(
+ (acc, [key, value]) => ({ ...acc, [value]: key }),
+ {} as ReverseMap
+);
+
+export const emojiByGesture = {
+ "👍": "Thumb_Up",
+ "👎": "Thumb_Down",
+ "✌️": "Victory",
+ "☝️": "Pointing_Up",
+ "✊": "Closed_Fist",
+ "👋": "Open_Palm",
+ "🤟": "ILoveYou",
+} as const satisfies Record;
+
+export const gestureMenuItems = Object.entries(emojiByGesture).map(([text, value]) => ({ text, value })) as Array<{ text: keyof typeof emojiByGesture, value: keyof typeof categoryByGesture }>;
+
+const createGestureRecognizer = async () => {
+ const vision = await FilesetResolver.forVisionTasks(
+ "https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.3/wasm"
+ );
+ return GestureRecognizer.createFromOptions(vision, {
+ baseOptions: {
+ modelAssetPath:
+ "https://storage.googleapis.com/mediapipe-models/gesture_recognizer/gesture_recognizer/float16/1/gesture_recognizer.task",
+ delegate: "GPU"
+ },
+ runningMode: "IMAGE"
+ });
+};
+
+export const gestureDetection = async (image: HTMLImageElement | ImageData) => {
+ gestureRecognizer ??= await createGestureRecognizer();
+ return gestureRecognizer.recognize(image);
+}
\ No newline at end of file
diff --git a/extensions/src/doodlebot/enums.ts b/extensions/src/doodlebot/enums.ts
new file mode 100644
index 000000000..fb34fff09
--- /dev/null
+++ b/extensions/src/doodlebot/enums.ts
@@ -0,0 +1,100 @@
+const anim = {
+ angry: "a",
+ annoyed: "y",
+ confused: "m",
+ disgust: "d",
+ engaged: "e",
+ fear: "f",
+ happy: "h",
+ love: "o",
+ neutral: "n",
+ sad: "s",
+ sleeping: "l",
+ surprise: "p",
+ wink: "i",
+ worried: "r",
+ wrong: "w",
+} as const;
+
+export type Anim = keyof typeof anim;
+
+export const anims = Object.keys(anim) as Anim[];
+
+export const command = {
+ enable: "e",
+ disable: "x",
+ motor: "m",
+ arc: "t",
+ wifi: "k",
+ lowPower: "q",
+ display: "d",
+ pen: "u",
+ network: "g",
+ wait: "w",
+ execute: "z"
+} as const;
+
+export type CommandKey = keyof typeof command;
+export type Command = typeof command[CommandKey];
+
+export const sensor = {
+ battery: "f",
+ bumper: "b",
+ humidity: "h",
+ pressure: "p",
+ distance: "d",
+ altimeter: "u",
+ magnometer: "o",
+ temperature: "t",
+ accelerometer: "a",
+ gyroscope: "g",
+ light: "l",
+} as const;
+
+export type SensorKey = keyof typeof sensor;
+export const sensorKeys = Object.keys(sensor) as SensorKey[];
+export type Sensor = typeof sensor[SensorKey];
+export type SensorKeyByValue = { [K in SensorKey as typeof sensor[K]]: K };
+
+export const display = {
+ clear: "c",
+ sad: "s",
+ happy: "T",
+ child: "H",
+ angry: "a",
+ annoyed: "n",
+ disgust: "d",
+ worried: "w",
+ fear: "f",
+ love: "l",
+ confused: "q",
+} as const;
+
+export type DisplayKey = keyof typeof display;
+export type Display = typeof display[DisplayKey];
+export const displayKeys = Object.keys(display) as DisplayKey[];
+
+
+export const keyBySensor = Object.fromEntries(Object.entries(sensor).map(([key, value]) => [value, key])) as SensorKeyByValue;
+
+export const motorCommandReceived = "ms";
+
+export const networkStatus = {
+ ipPrefix: "RPI ipaddr:",
+ hostnamePrefix: "hname:",
+} as const;
+
+export type NetworkStatusKey = keyof typeof networkStatus;
+export type NetworkStatus = typeof networkStatus[NetworkStatusKey];
+
+export type ReceivedCommand = Sensor | typeof motorCommandReceived;
+
+export const port = {
+ websocket: "8765",
+ camera: "8000",
+ audio: "8771",
+} as const;
+
+export const endpoint = {
+ video: "video_feed"
+} as const;
\ No newline at end of file
diff --git a/extensions/src/doodlebot/index.test.ts b/extensions/src/doodlebot/index.test.ts
new file mode 100644
index 000000000..e468de757
--- /dev/null
+++ b/extensions/src/doodlebot/index.test.ts
@@ -0,0 +1,9 @@
+import { createTestSuite } from "$testing";
+import Extension from '.';
+
+createTestSuite({ Extension, __dirname },
+ {
+ unitTests: undefined,
+ integrationTests: undefined
+ }
+);
\ No newline at end of file
diff --git a/extensions/src/doodlebot/index.ts b/extensions/src/doodlebot/index.ts
new file mode 100644
index 000000000..abe0b94e6
--- /dev/null
+++ b/extensions/src/doodlebot/index.ts
@@ -0,0 +1,1286 @@
+import { Environment, ExtensionMenuDisplayDetails, extension, block, buttonBlock } from "$common";
+import { DisplayKey, displayKeys, command, type Command, SensorKey, sensorKeys } from "./enums";
+import Doodlebot, { NetworkCredentials } from "./Doodlebot";
+import { splitArgsString } from "./utils";
+import EventEmitter from "events";
+import { categoryByGesture, classes, emojiByGesture, gestureDetection, gestureMenuItems, gestures, objectDetection } from "./detection";
+//import { createLineDetector } from "./LineDetection";
+import { line0, line1, line2, line3, line4, line5, line6, line7, line8 } from './Points';
+import { followLine } from "./LineFollowing";
+import { createLineDetector } from "./LineDetection";
+import tmPose from '@teachablemachine/pose';
+import { calculateArcTime } from "./TimeHelper";
+import tmImage from '@teachablemachine/image';
+import * as speechCommands from '@tensorflow-models/speech-commands';
+import JSZip from 'jszip';
+
+const details: ExtensionMenuDisplayDetails = {
+ name: "Doodlebot",
+ description: "Program a doodlebot robot",
+ iconURL: "Replace with the name of your icon image file (which should be placed in the same directory as this file)",
+ insetIconURL: "Replace with the name of your inset icon image file (which should be placed in the same directory as this file)",
+ tags: ["Made by PRG"]
+};
+
+const bumperOptions = ["front", "back", "front or back", "front and back", "neither"] as const;
+
+const looper = (action: () => Promise, profileMarker?: string) => {
+ const controller = new AbortController();
+ let samples = 0;
+ let average = 0;
+ async function loop() {
+ if (controller.signal.aborted) return;
+ const start = performance.now();
+ await action();
+ const end = performance.now();
+ if (profileMarker && samples < Number.MAX_SAFE_INTEGER) {
+ samples++;
+ average = (average * (samples - 1) + end - start) / samples;
+ if (samples % 100 === 0) console.log(`Average time for ${profileMarker}: ${average}ms`);
+ }
+ requestAnimationFrame(loop);
+ }
+ loop();
+ return controller;
+}
+
+export default class DoodlebotBlocks extends extension(details, "ui", "indicators", "video", "drawable") {
+ doodlebot: Doodlebot;
+ private indicator: Promise<{ close(): void; }>;
+ private lineDetector: (() => Promise) | null = null;
+ bluetoothEmitter = new EventEmitter();
+
+ gestureLoop: ReturnType;
+ objectLoop: ReturnType;
+
+ gestureState = {
+ "Closed_Fist": false,
+ "Thumb_Up": false,
+ "Thumb_Down": false,
+ "Victory": false,
+ "Pointing_Up": false,
+ "ILoveYou": false,
+ "Open_Palm": false
+ } satisfies Record;
+
+ imageStream: HTMLImageElement;
+ videoDrawable: ReturnType;
+ predictionState = {};
+ latestAudioResults: any;
+ ModelType = {
+ POSE: 'pose',
+ IMAGE: 'image',
+ AUDIO: 'audio',
+ };
+ teachableImageModel;
+
+ lastUpdate: number = null;
+ maxConfidence: number = null;
+ modelConfidences = {};
+ isPredicting: number = 0;
+ INTERVAL = 16;
+ DIMENSIONS = [480, 360];
+
+ SOCIAL = true;
+ socialness = 1.0; // Value from 0 to 1, where 1 is always social and 0 is never social
+
+ init(env: Environment) {
+ this.setIndicator("disconnected");
+ if (window.isSecureContext) this.openUI("Connect")
+ else this.connectToDoodlebotWithExternalBLE();
+ this._loop();
+ // env.runtime.on("PROJECT_RUN_START", () => {
+ // this.doodlebot?.display("love");
+ // })
+ // env.runtime.on("PROJECT_RUN_STOP", () => {
+ // this.doodlebot?.display("sad");
+ // })
+ }
+
+ private async connectToDoodlebotWithExternalBLE() {
+ // A few globals that currently must be set the same across the playground and the https frontend
+ const handshakeMessage = "doodlebot";
+ const disconnectMessage = "disconnected";
+ const commandCompleteIdentifier = "done";
+
+ const urlParams = new URLSearchParams(window.location.search); // Hack for now
+
+ const ip = urlParams.get("ip");
+
+ if (!ip) {
+ alert("No IP address provided. Please provide an IP address in the URL query string.");
+ return;
+ }
+
+ const networkCredentials: NetworkCredentials = {
+ ssid: "dummy", // NOTE: When using the external BLE, it is assumed a valid ip address will be provided, and thus there is no need for wifi credentials
+ password: "dummy", // NOTE: When using the external BLE, it is assumed a valid ip address will be provided, and thus there is no need for wifi credentials
+ ipOverride: ip
+ }
+
+ type ExternalPageDetails = { source: MessageEventSource, targetOrigin: string }
+
+ const { source, targetOrigin } = await new Promise((resolve) => {
+ const onInitialMessage = ({ data, source, origin }: MessageEvent) => {
+ if (typeof data !== "string" || data !== handshakeMessage) return;
+ window.removeEventListener("message", onInitialMessage);
+ console.log("posting ready");
+ source.postMessage("ready", { targetOrigin: origin })
+ resolve({ source, targetOrigin: origin });
+ }
+ window.addEventListener("message", onInitialMessage);
+ });
+
+ console.log("source", source);
+ console.log("target origin", targetOrigin);
+
+ const doodlebot = new Doodlebot(
+ {
+ send: (text) => new Promise(resolve => {
+ const onMessageReturn = ({ data, origin }: MessageEvent) => {
+ if (origin !== targetOrigin || !data.includes(text) || !data.includes(commandCompleteIdentifier)) {
+ console.log("error -- source");
+ return;
+ }
+ window.removeEventListener("message", onMessageReturn);
+ resolve();
+ }
+ window.addEventListener("message", onMessageReturn);
+ console.log("posting message");
+ source.postMessage(text, { targetOrigin });
+ }),
+
+ onReceive: (callback) => {
+ window.addEventListener('message', ({ data, origin }) => {
+ if (origin !== targetOrigin || data === disconnectMessage || data.includes(commandCompleteIdentifier)) {
+ console.log("error 2 -- source", data);
+ return;
+ }
+ callback(new CustomEvent("ble", { detail: data }));
+ });
+ },
+
+ onDisconnect: () => {
+ window.addEventListener("message", ({ data, origin }) => {
+ if (origin !== targetOrigin || data !== disconnectMessage) return;
+ this.setIndicator("disconnected");
+ alert("Disconnected from robot"); // Decide how to handle (maybe direct user to close window and go back to https)
+ });
+ },
+ },
+ () => alert("requestBluetooth called"), // placeholder
+ networkCredentials,
+ () => alert("save IP called"), // placeholder
+ )
+ this.setDoodlebot(doodlebot);
+ }
+
+ async setDoodlebot(doodlebot: Doodlebot) {
+ this.doodlebot = doodlebot;
+ await this.setIndicator("connected");
+
+ // Wait a short moment to ensure connection is established
+ await new Promise(resolve => setTimeout(resolve, 1000));
+
+ try {
+ if (this.SOCIAL && Math.random() < this.socialness && this.doodlebot) {
+ await this.doodlebot.display("happy");
+ await this.speakText("Hi! I'm Doodlebot! I'm here to help you learn about AI and robotics! If you have any questions, just click on the chat with me block and I'll do my best to help you out!");
+ }
+ } catch (error) {
+ console.error("Error during welcome message:", error);
+ // Don't throw the error - we still want the robot to be usable even if the welcome message fails
+ }
+ }
+
+ async setIndicator(status: "connected" | "disconnected") {
+ if (this.indicator) (await this.indicator)?.close();
+ this.indicator = status == "connected"
+ ? this.indicate({ position: "category", msg: "Connected to robot", type: "success", retry: true })
+ : this.indicate({ position: "category", msg: "Not connected to robot", type: "warning", retry: true });
+ }
+
+ requestBluetooth(callback: (bluetooth: Bluetooth) => any) {
+ this.bluetoothEmitter.once("bluetooth", callback);
+ this.openUI("ReattachBLE");
+ }
+
+ async getImageStream() {
+ this.imageStream ??= await this.doodlebot?.getImageStream();
+ return this.imageStream;
+ }
+
+ async createVideoStreamDrawable() {
+ this.imageStream ??= await this.doodlebot?.getImageStream();
+ if (!this.imageStream) {
+ console.error("Failed to get image stream");
+ return;
+ }
+ console.log("Image stream dimensions:", this.imageStream.width, "x", this.imageStream.height);
+ const drawable = this.createDrawable(this.imageStream);
+ drawable.setVisible(true);
+ const self = this;
+ const update = () => {
+ drawable.update(self.imageStream);
+ requestAnimationFrame(update);
+ }
+ requestAnimationFrame(update);
+ return drawable;
+ }
+
+ @buttonBlock("Connect Robot")
+ connect() {
+ this.openUI("Connect");
+ }
+
+ @block({
+ type: "command",
+ text: (value) => `set socialness to ${value}`,
+ arg: {
+ type: "number",
+ defaultValue: 1.0
+ }
+ })
+ async setSocialness(value: number) {
+ // Ensure value is between 0 and 1
+ this.socialness = Math.max(0, Math.min(1, value));
+
+ if (this.SOCIAL && Math.random() < this.socialness) {
+ await this.doodlebot?.display("happy");
+ await this.speakText(`I'll be ${Math.round(this.socialness * 100)}% social from now on!`);
+ }
+ }
+
+ @block({
+ type: "command",
+ text: (seconds) => `chat with me for ${seconds} seconds`,
+ arg: { type: "number", defaultValue: 3 }
+ })
+ async testChatAPI(seconds: number) {
+ await this.handleChatInteraction(seconds);
+ }
+
+ @block({
+ type: "command",
+ text: (seconds) => `listen for ${seconds} seconds and repeat`,
+ arg: { type: "number", defaultValue: 3 }
+ })
+ async repeatAfterMe(seconds: number) {
+ // Record the audio
+ const { context, buffer } = await this.doodlebot?.recordAudio(seconds);
+
+ // Convert to WAV format
+ const wavBlob = await this.saveAudioBufferToWav(buffer);
+ const arrayBuffer = await wavBlob.arrayBuffer();
+
+ // Send the audio data directly to the Doodlebot for playback
+ await this.doodlebot.sendAudioData(new Uint8Array(arrayBuffer));
+
+ // Wait until playback is complete (approximately buffer duration)
+ const playbackDuration = buffer.duration * 1000; // convert to milliseconds
+ await new Promise(resolve => setTimeout(resolve, playbackDuration));
+ }
+
+ @block({
+ type: "command",
+ text: (text) => `speak ${text}`,
+ arg: { type: "string", defaultValue: "Hello!" }
+ })
+ async speak(text: string) {
+ await this.speakText(text);
+ }
+
+ @block({
+ type: "command",
+ text: (type: DisplayKey) => `display emotion ${type}`,
+ arg: { type: "string", options: displayKeys.filter(key => key !== "clear"), defaultValue: "happy" }
+ })
+ async setDisplay(display: DisplayKey) {
+ await this.doodlebot?.display(display);
+ }
+
+ @block({
+ type: "command",
+ text: (text: string) => `display text ${text}`,
+ arg: { type: "string", defaultValue: "hello world!" }
+ })
+ async setText(text: string) {
+ await this.doodlebot?.displayText(text);
+ }
+
+ @block({
+ type: "command",
+ text: "clear display"
+ })
+ async clearDisplay() {
+ await this.doodlebot?.display("clear");
+ }
+
+ @block({
+ type: "command",
+ text: (direction, steps) => `drive ${direction} for ${steps} steps`,
+ args: [
+ { type: "string", options: ["forward", "backward", "left", "right"], defaultValue: "forward" },
+ { type: "number", defaultValue: 2000 }
+ ]
+ })
+ async drive(direction: "left" | "right" | "forward" | "backward", steps: number) {
+
+ if (this.SOCIAL && Math.random() < this.socialness) {
+ await this.doodlebot?.display("love");
+ await this.speakText(`Driving ${direction} for ${steps} steps`);
+ }
+
+ const leftSteps = direction == "left" || direction == "backward" ? -steps : steps;
+ const rightSteps = direction == "right" || direction == "backward" ? -steps : steps;
+ const stepsPerSecond = 2000;
+
+ await this.doodlebot?.motorCommand(
+ "steps",
+ { steps: leftSteps, stepsPerSecond },
+ { steps: rightSteps, stepsPerSecond }
+ );
+ }
+
+ @block({
+ type: "command",
+ text: (direction, radius, degrees) => `arc ${direction} with radius ${radius} for ${degrees} degrees`,
+ args: [
+ { type: "string", options: ["left", "right"], defaultValue: "left" },
+ { type: "number", defaultValue: 2 },
+ { type: "number", defaultValue: 90 }
+ ]
+ })
+ async arc(direction: "left" | "right", radius: number, degrees: number) {
+ if (this.SOCIAL && Math.random() < this.socialness) {
+ await this.doodlebot?.display("happy");
+ await this.speakText(`Driving ${direction} arc with radius ${radius} for ${degrees} degrees`);
+ }
+ if (direction == "right") degrees *= -1;
+ await this.doodlebot?.motorCommand("arc", radius, degrees);
+ }
+
+ @block({
+ type: "command",
+ text: (degrees) => `spin ${degrees} degrees`,
+ arg: { type: "angle", defaultValue: 90 }
+ })
+ async spin(degrees: number) {
+ if (this.SOCIAL && Math.random() < this.socialness) {
+ await this.doodlebot?.display("happy");
+ await this.speakText(`Spinning ${degrees} degrees`);
+ }
+ if (degrees === 0) return;
+ await this.doodlebot?.motorCommand("arc", 0, -degrees);
+ }
+
+ @block({
+ type: "command",
+ text: "stop driving"
+ })
+ async stop() {
+ if (this.SOCIAL && Math.random() < this.socialness) {
+ await this.doodlebot?.display("disgust");
+ await this.speakText("okk, I will stop driving now.");
+ }
+ await this.doodlebot?.motorCommand("stop");
+ }
+
+
+ // @block({
+ // type: "command",
+ // text: "perform line following"
+ // })
+ // async testLine2() {
+ // if (this.SOCIAL) {
+ // await this.speakText("Starting line following now!");
+ // }
+ // await this.doodlebot.followLine();
+ // }
+
+ // @block({
+ // type: "command",
+ // text: (direction) => `move pen ${direction}`,
+ // arg: { type: "string", options: ["up", "down"], defaultValue: "up" }
+ // })
+ // async movePen(direction: "up" | "down") {
+ // await this.doodlebot?.penCommand(direction);
+ // }
+
+ // @block({
+ // type: "reporter",
+ // text: (sensor: SensorKey) => `${sensor} sensor`,
+ // arg: { type: "string", options: ["battery", "temperature", "humidity", "pressure", "distance"], defaultValue: "battery" }
+ // })
+ // async getSingleSensorReading(sensor: "battery" | "temperature" | "humidity" | "pressure" | "distance") {
+ // const reading = await this.doodlebot?.getSensorReading(sensor);
+ // return reading;
+ // }
+
+ // @block({
+ // type: "Boolean",
+ // text: (bumper) => `is ${bumper} bumper pressed`,
+ // arg: { type: "string", options: bumperOptions, defaultValue: bumperOptions[0] }
+ // })
+ // async isBumperPressed(bumber: typeof bumperOptions[number]) {
+ // const isPressed = await this.doodlebot?.getSensorReading("bumper");
+ // switch (bumber) {
+ // case "back":
+ // return isPressed.back > 0;
+ // case "front":
+ // return isPressed.front > 0;
+ // case "front or back":
+ // return isPressed.front > 0 || isPressed.back > 0;
+ // case "front and back":
+ // return isPressed.front > 0 && isPressed.back > 0;
+ // case "neither":
+ // return isPressed.front === 0 && isPressed.back === 0;
+ // }
+ // }
+
+ // @block({
+ // type: "hat",
+ // text: (bumper, condition) => `when ${bumper} bumper ${condition}`,
+ // args: [
+ // { type: "string", options: bumperOptions, defaultValue: bumperOptions[0] },
+ // { type: "string", options: ["release", "pressed"], defaultValue: "pressed" }
+ // ]
+ // })
+ // whenBumperPressed(bumber: typeof bumperOptions[number], condition: "release" | "pressed") {
+ // const isPressed = this.doodlebot?.getSensorReadingImmediately("bumper");
+ // const isPressedCondition = condition === "pressed";
+ // switch (bumber) {
+ // case "back":
+ // return isPressedCondition ? isPressed.back > 0 : isPressed.back === 0;
+ // case "front":
+ // return isPressedCondition ? isPressed.front > 0 : isPressed.front === 0;
+ // case "front or back":
+ // return isPressedCondition ? isPressed.front > 0 || isPressed.back > 0 : isPressed.front === 0 && isPressed.back === 0;
+ // case "front and back":
+ // return isPressedCondition ? isPressed.front > 0 && isPressed.back > 0 : isPressed.front === 0 || isPressed.back === 0;
+ // case "neither":
+ // return isPressedCondition ? isPressed.front === 0 && isPressed.back === 0 : isPressed.front > 0 && isPressed.back > 0;
+ // }
+ // }
+
+ // @block({
+ // type: "command",
+ // text: (sensor: SensorKey) => `disable ${sensor}`,
+ // arg: { type: "string", options: sensorKeys, defaultValue: sensorKeys[0] }
+ // })
+ // async disableSensor(sensor: SensorKey) {
+ // await this.doodlebot?.disableSensor(sensor);
+ // }
+
+ // @block({
+ // type: "command",
+ // text: (sound) => `play sound track${sound}`,
+ // arg: { type: "number", defaultValue: 1 }
+ // })
+ // async playSound(sound: number) {
+ // await this.doodlebot?.sendWebsocketCommand("m", sound)
+ // }
+
+ // @block({
+ // type: "command",
+ // text: (transparency) => `display video with ${transparency}% transparency`,
+ // arg: { type: "number", defaultValue: 0 }
+ // })
+ // async connectToVideo(transparency: number) {
+ // this.videoDrawable ??= await this.createVideoStreamDrawable();
+ // this.videoDrawable.setTransparency(transparency);
+ // }
+
+ @block({
+ type: "command",
+ text: "display video",
+ })
+ async connectToVideo() {
+ this.videoDrawable ??= await this.createVideoStreamDrawable();
+ if (this.SOCIAL && Math.random() < this.socialness) {
+ await this.doodlebot?.display("happy");
+ await this.speakText("You can now see what I see on your screen!");
+ }
+ }
+
+ // @block({
+ // type: "hat",
+ // text: (gesture) => `when ${gesture} detected`,
+ // arg: { type: "string", defaultValue: "Thumb_Up", options: gestureMenuItems }
+ // })
+ // whenGesture(gesture: keyof typeof this.gestureState) {
+ // const self = this;
+
+ // this.gestureLoop ??= looper(async () => {
+ // self.imageStream ??= await self.doodlebot?.getImageStream();
+ // const result = await gestureDetection(self.imageStream);
+
+ // for (const k in self.gestureState) self.gestureState[k] = false;
+
+ // for (const arr of result.gestures)
+ // for (const gesture of arr)
+ // self.gestureState[gesture.categoryName] = true;
+ // }, "gesture detection");
+
+ // return this.gestureState[gesture];
+ // }
+
+ // @block({
+ // type: "reporter",
+ // text: (object) => `degrees from ${object}`,
+ // arg: { type: "string", defaultValue: "cup", options: classes }
+ // })
+ // async getOffsetFromObject(object: typeof classes[number]) {
+ // this.imageStream ??= await this.doodlebot?.getImageStream();
+ // const result = await objectDetection(this.imageStream);
+ // for (const detection of result.detections) {
+ // const isCup = detection.categories.some(({ categoryName }) => categoryName === object);
+ // if (!isCup) continue;
+ // if (!detection.boundingBox) continue;
+ // const x = detection.boundingBox.originX + detection.boundingBox.width / 2;
+ // const xOffset = x - this.imageStream.width / 2;
+ // return xOffset * 90 / this.imageStream.width;
+ // }
+ // return 0;
+ // }
+
+ // @block({
+ // type: "command",
+ // text: (seconds) => `record for ${seconds} seconds and play`,
+ // arg: { type: "number", defaultValue: 1 }
+ // })
+ // async recordAudio(seconds: number) {
+ // const { context, buffer } = await this.doodlebot?.recordAudio(seconds);
+
+ // const audioBufferSource = context.createBufferSource();
+ // audioBufferSource.buffer = buffer;
+
+ // const gainNode = context.createGain();
+ // audioBufferSource.connect(gainNode);
+ // gainNode.connect(context.destination);
+
+ // const fadeInDuration = 0.1;
+ // const fadeOutDuration = 0.1;
+ // const audioDuration = audioBufferSource.buffer.duration;
+
+ // // Start with silence
+ // gainNode.gain.setValueAtTime(0, context.currentTime);
+ // gainNode.gain.linearRampToValueAtTime(1, context.currentTime + fadeInDuration);
+
+ // gainNode.gain.setValueAtTime(1, context.currentTime + audioDuration - fadeOutDuration);
+ // gainNode.gain.linearRampToValueAtTime(0, context.currentTime + audioDuration);
+
+ // audioBufferSource.start();
+ // audioBufferSource.stop(context.currentTime + audioDuration);
+
+ // await new Promise((resolve) => setTimeout(resolve, audioDuration * 1000));
+ // }
+
+
+ // @block({
+ // type: "reporter",
+ // text: "get IP address"
+ // })
+ // async getIP() {
+ // return this.doodlebot?.getIPAddress();
+ // }
+
+ // @block({
+ // type: "command",
+ // text: (_command, args, protocol) => `send (${_command}, ${args}) over ${protocol}`,
+ // args: [
+ // { type: "string", defaultValue: "u" },
+ // { type: "string", defaultValue: "0" },
+ // { type: "string", options: ["BLE", "Websocket"], defaultValue: "BLE" }
+ // ]
+ // })
+ // async sendMessage(_command: string, args: string, protocol: "BLE" | "Websocket") {
+ // const candidates = Object.values(command).filter((entry) => entry === _command)
+ // if (candidates.length === 0) return console.error(`Command ${command} not found`);
+
+ // protocol === "BLE"
+ // ? await this.doodlebot?.sendBLECommand(candidates[0], ...splitArgsString(args))
+ // : await this.doodlebot?.sendWebsocketCommand(candidates[0], ...splitArgsString(args));
+ // }
+
+ @block({
+ type: "command",
+ text: (url) => `import AI model ${url}`,
+ arg: {
+ type: "string",
+ defaultValue: "URL HERE"
+ }
+ })
+ async importModel(url: string) {
+ if (this.SOCIAL && Math.random() < this.socialness) {
+ await this.doodlebot?.display("happy");
+ await this.speakText(`Importing Teachable Machine model`);
+ }
+ await this.useModel(url);
+
+ if (this.SOCIAL && Math.random() < this.socialness) {
+ await this.doodlebot?.display("happy");
+ await this.speakText(`Model imported successfully. You can access your image classes using the model prediction blocks.`);
+ await this.speakText(`Let me know if you have any questions.`);
+ }
+ }
+
+
+ // @block({
+ // type: "hat",
+ // text: (className) => `when model detects ${className}`,
+ // arg: {
+ // type: "string",
+ // options: function () {
+ // if (!this) {
+ // throw new Error('Context is undefined');
+ // }
+ // return this.getModelClasses() || ["Select a class"];
+ // },
+ // defaultValue: "Select a class"
+ // }
+ // })
+ // whenModelDetects(className: string) {
+ // return this.model_match(className);
+ // }
+
+ @block({
+ type: "reporter",
+ text: "get AI prediction",
+ })
+ modelPrediction() {
+ return this.getModelPrediction();
+ }
+
+ @block({
+ type: "reporter",
+ text: (className) => `confidence for ${className}`,
+ arg: {
+ type: "string",
+ options: function () {
+ if (!this) {
+ throw new Error('Context is undefined');
+ }
+ return this.getModelClasses() || ["Select a class"];
+ },
+ defaultValue: "Select a class"
+ }
+ })
+ getConfidence(className: string) {
+ if (!this.modelConfidences || !this.modelConfidences[className]) {
+ return 0;
+ }
+ return Math.round(this.modelConfidences[className] * 100);
+ }
+
+ writeString(view: DataView, offset: number, text: string) {
+ for (let i = 0; i < text.length; i++) {
+ view.setUint8(offset + i, text.charCodeAt(i));
+ }
+ }
+
+ async saveAudioBufferToWav(buffer) {
+ function createWavHeader(buffer) {
+ const numChannels = buffer.numberOfChannels;
+ const sampleRate = buffer.sampleRate / 4;
+ const bitsPerSample = 16; // 16-bit PCM
+ const blockAlign = (numChannels * bitsPerSample) / 8;
+ const byteRate = sampleRate * blockAlign;
+ const dataLength = buffer.length * numChannels * 2; // 16-bit PCM = 2 bytes per sample
+ const header = new ArrayBuffer(44);
+ const view = new DataView(header);
+ // "RIFF" chunk descriptor
+ writeString(view, 0, "RIFF");
+ view.setUint32(4, 36 + dataLength, true); // File size - 8 bytes
+ writeString(view, 8, "WAVE");
+ // "fmt " sub-chunk
+ writeString(view, 12, "fmt ");
+ view.setUint32(16, 16, true); // Sub-chunk size (16 for PCM)
+ view.setUint16(20, 1, true); // Audio format (1 = PCM)
+ view.setUint16(22, numChannels, true); // Number of channels
+ view.setUint32(24, sampleRate, true); // Sample rate
+ view.setUint32(28, byteRate, true); // Byte rate
+ view.setUint16(32, blockAlign, true); // Block align
+ view.setUint16(34, bitsPerSample, true); // Bits per sample
+ // "data" sub-chunk
+ writeString(view, 36, "data");
+ view.setUint32(40, dataLength, true); // Data length
+ console.log("WAV Header:", new Uint8Array(header));
+ return header;
+ }
+ function writeString(view, offset, string) {
+ for (let i = 0; i < string.length; i++) {
+ view.setUint8(offset + i, string.charCodeAt(i));
+ }
+ }
+ function interleave(buffer) {
+ const numChannels = buffer.numberOfChannels;
+ const length = buffer.length * numChannels;
+ const result = new Float32Array(length);
+ const channelData = [];
+ for (let i = 0; i < numChannels; i++) {
+ channelData.push(buffer.getChannelData(i));
+ }
+ let index = 0;
+ for (let i = 0; i < buffer.length; i++) {
+ for (let j = 0; j < numChannels; j++) {
+ result[index++] = channelData[j][i];
+ }
+ }
+ console.log("Interleaved data:", result);
+ return result;
+ }
+ function floatTo16BitPCM(output, offset, input) {
+ for (let i = 0; i < input.length; i++, offset += 2) {
+ let s = Math.max(-1, Math.min(1, input[i])); // Clamp to [-1, 1]
+ s = s < 0 ? s * 0x8000 : s * 0x7FFF; // Convert to 16-bit PCM
+ output.setInt16(offset, s, true); // Little-endian
+ }
+ }
+ const header = createWavHeader(buffer);
+ const interleaved = interleave(buffer);
+ const wavBuffer = new ArrayBuffer(header.byteLength + interleaved.length * 2);
+ const view = new DataView(wavBuffer);
+ //return this.createAndSaveWAV(interleaved, buffer.sampleRate);
+ // Write header
+ new Uint8Array(wavBuffer).set(new Uint8Array(header), 0);
+ // Write PCM data
+ floatTo16BitPCM(view, header.byteLength, interleaved);
+ console.log("Final WAV buffer length:", wavBuffer.byteLength);
+ console.log("Expected data length:", header.byteLength + interleaved.length * 2);
+ // Return a Blob
+ return new Blob([wavBuffer], { type: "audio/wav" });
+}
+
+generateWAV(interleaved: Float32Array, sampleRate: number): Uint8Array {
+ const numChannels = 1; // Mono
+ const bitsPerSample = 16;
+ const byteRate = sampleRate * numChannels * (bitsPerSample / 8);
+ const blockAlign = numChannels * (bitsPerSample / 8);
+ const dataLength = interleaved.length * (bitsPerSample / 8);
+ const bufferLength = 44 + dataLength;
+ const buffer = new ArrayBuffer(bufferLength);
+ const view = new DataView(buffer);
+ // RIFF header
+ this.writeString(view, 0, "RIFF");
+ view.setUint32(4, bufferLength - 8, true); // File size
+ this.writeString(view, 8, "WAVE");
+ // fmt subchunk
+ this.writeString(view, 12, "fmt ");
+ view.setUint32(16, 16, true); // Subchunk size
+ view.setUint16(20, 1, true); // PCM format
+ view.setUint16(22, numChannels, true); // Channels
+ view.setUint32(24, sampleRate, true); // Sample rate
+ view.setUint32(28, byteRate, true); // Byte rate
+ view.setUint16(32, blockAlign, true); // Block align
+ view.setUint16(34, bitsPerSample, true); // Bits per sample
+ // data subchunk
+ this.writeString(view, 36, "data");
+ view.setUint32(40, dataLength, true);
+ // PCM data
+ const offset = 44;
+ for (let i = 0; i < interleaved.length; i++) {
+ const sample = Math.max(-1, Math.min(1, interleaved[i]));
+ view.setInt16(offset + i * 2, sample < 0 ? sample * 0x8000 : sample * 0x7FFF, true);
+ }
+ return new Uint8Array(buffer);
+}
+
+createAndSaveWAV(interleaved, sampleRate) {
+ // Step 1: Get interleaved audio data and sample rate
+ // Step 2: Generate WAV file
+ const wavData = this.generateWAV(interleaved, sampleRate);
+ // Step 3: Save or process the WAV file
+ // Example: Create a Blob and download the file
+ const blob = new Blob([wavData], { type: "audio/wav" });
+ const url = URL.createObjectURL(blob);
+ // Create a link to download the file
+ const a = document.createElement("a");
+ a.href = url;
+ a.download = "output.wav";
+ document.body.appendChild(a);
+ a.click();
+ document.body.removeChild(a);
+ return blob;
+}
+
+ async sendAudioFileToChatEndpoint(file) {
+ const url = "http://doodlebot.media.mit.edu/chat";
+ const formData = new FormData();
+ formData.append("audio_file", file);
+ const audioURL = URL.createObjectURL(file);
+ const audio = new Audio(audioURL);
+ //audio.play();
+
+ try {
+ const response = await fetch(url, {
+ method: "POST",
+ body: formData,
+ });
+
+ if (!response.ok) {
+ const errorText = await response.text();
+ console.log("Error response:", errorText);
+ throw new Error(`HTTP error! status: ${response.status}`);
+ }
+
+ const textResponse = response.headers.get("text-response");
+ console.log("Text Response:", textResponse);
+
+ const blob = await response.blob();
+ const audioUrl = URL.createObjectURL(blob);
+ console.log("Audio URL:", audioUrl);
+
+ const audio = new Audio(audioUrl);
+ const array = await blob.arrayBuffer();
+ this.doodlebot.sendAudioData(new Uint8Array(array));
+
+ } catch (error) {
+ console.error("Error sending audio file:", error);
+ }
+ }
+ async isValidWavFile(file) {
+ const arrayBuffer = await file.arrayBuffer();
+ const dataView = new DataView(arrayBuffer);
+
+ // Check the "RIFF" chunk descriptor
+ const riff = String.fromCharCode(...new Uint8Array(arrayBuffer.slice(0, 4)));
+ if (riff !== "RIFF") {
+ console.error("Invalid WAV file: Missing RIFF header");
+ return false;
+ }
+
+ // Check the "WAVE" format
+ const wave = String.fromCharCode(...new Uint8Array(arrayBuffer.slice(8, 12)));
+ if (wave !== "WAVE") {
+ console.error("Invalid WAV file: Missing WAVE format");
+ return false;
+ }
+
+ // Check for "fmt " subchunk
+ const fmt = String.fromCharCode(...new Uint8Array(arrayBuffer.slice(12, 16)));
+ if (fmt !== "fmt ") {
+ console.error("Invalid WAV file: Missing fmt subchunk");
+ return false;
+ }
+
+ // Check for "data" subchunk
+ const dataIndex = arrayBuffer.byteLength - 8; // Approximate location
+ const dataChunk = String.fromCharCode(...new Uint8Array(arrayBuffer.slice(dataIndex, dataIndex + 4)));
+ if (dataChunk !== "data") {
+ console.error("Invalid WAV file: Missing data subchunk");
+ return false;
+ }
+
+ console.log("Valid WAV file");
+ return true;
+ }
+
+ async processAndSendAudio(buffer) {
+ try {
+ const wavBlob = await this.saveAudioBufferToWav(buffer);
+ console.log(wavBlob);
+ const wavFile = new File([wavBlob], "output.wav", { type: "audio/wav" });
+ // const isValid = await this.isValidWavFile(wavFile);
+ // if (!isValid) {
+ // throw new Error("Generated file is not a valid WAV file");
+ // }
+ await this.sendAudioFileToChatEndpoint(wavFile);
+ } catch (error) {
+ console.error("Error processing and sending audio:", error);
+ }
+ }
+
+ // Internal method that can be called directly
+ private async handleChatInteraction(seconds: number) {
+ console.log(`recording audio for ${seconds} seconds`);
+ // Display "listening" while recording
+ await this.doodlebot?.display("clear");
+ await this.doodlebot?.displayText("listening");
+
+ const { context, buffer } = await this.doodlebot?.recordAudio(seconds);
+ console.log("finished recording audio");
+
+ // Display "thinking" while processing and waiting for response
+ await this.doodlebot?.display("clear");
+ await this.doodlebot?.displayText("thinking");
+
+ // Before sending audio to be played
+ await this.processAndSendAudio(buffer);
+
+ // Display "speaking" when ready to speak
+ await this.doodlebot?.display("clear");
+ await this.doodlebot?.displayText("speaking");
+
+ // Wait a moment before clearing the display
+ await new Promise(resolve => setTimeout(resolve, 2000));
+ await this.doodlebot?.display("clear");
+ }
+
+ async useModel(url: string) {
+ try {
+ const modelUrl = this.modelArgumentToURL(url);
+ console.log('Loading model from URL:', modelUrl);
+
+ // Initialize prediction state if needed
+ this.predictionState[modelUrl] = {};
+
+ // Load and initialize the model
+ const { model, type } = await this.initModel(modelUrl);
+ this.predictionState[modelUrl].modelType = type;
+ this.predictionState[modelUrl].model = model;
+
+ // Update the current model reference
+ this.teachableImageModel = modelUrl;
+
+ await this.indicate({
+ type: "success",
+ msg: "Model loaded successfully"
+ });
+ } catch (e) {
+ console.error('Error loading model:', e);
+ this.teachableImageModel = null;
+ await this.indicate({
+ type: "error",
+ msg: "Failed to load model"
+ });
+ }
+ }
+
+ modelArgumentToURL(modelArg: string) {
+ // Convert user-provided model URL/ID to the correct format
+ const endpointProvidedFromInterface = "https://teachablemachine.withgoogle.com/models/";
+ const redirectEndpoint = "https://storage.googleapis.com/tm-model/";
+
+ return modelArg.startsWith(endpointProvidedFromInterface)
+ ? modelArg.replace(endpointProvidedFromInterface, redirectEndpoint)
+ : redirectEndpoint + modelArg + "/";
+ }
+
+ async initModel(modelUrl: string) {
+ const avoidCache = `?x=${Date.now()}`;
+ const modelURL = modelUrl + "model.json" + avoidCache;
+ const metadataURL = modelUrl + "metadata.json" + avoidCache;
+
+ // First try loading as an image model
+ try {
+ const customMobileNet = await tmImage.load(modelURL, metadataURL);
+
+ // Check if it's actually an audio model
+ if ((customMobileNet as any)._metadata.hasOwnProperty('tfjsSpeechCommandsVersion')) {
+ const recognizer = await speechCommands.create("BROWSER_FFT", undefined, modelURL, metadataURL);
+ await recognizer.ensureModelLoaded();
+
+ // Setup audio listening
+ await recognizer.listen(async result => {
+ this.latestAudioResults = result;
+ }, {
+ includeSpectrogram: true,
+ probabilityThreshold: 0.75,
+ invokeCallbackOnNoiseAndUnknown: true,
+ overlapFactor: 0.50
+ });
+
+ return { model: recognizer, type: this.ModelType.AUDIO };
+ }
+ // Check if it's a pose model
+ else if ((customMobileNet as any)._metadata.packageName === "@teachablemachine/pose") {
+ const customPoseNet = await tmPose.load(modelURL, metadataURL);
+ return { model: customPoseNet, type: this.ModelType.POSE };
+ }
+ // Otherwise it's an image model
+ else {
+ return { model: customMobileNet, type: this.ModelType.IMAGE };
+ }
+ } catch (e) {
+ console.error("Failed to load model:", e);
+ throw e;
+ }
+ }
+
+ updateStageModel(modelUrl) {
+ const stage = this.runtime.getTargetForStage();
+ this.teachableImageModel = modelUrl;
+ if (stage) {
+ (stage as any).teachableImageModel = modelUrl;
+ }
+ }
+
+ private getPredictionStateOrStartPredicting(modelUrl: string) {
+ if (!modelUrl || !this.predictionState || !this.predictionState[modelUrl]) {
+ console.warn('No prediction state available for model:', modelUrl);
+ return null;
+ }
+ return this.predictionState[modelUrl];
+ }
+
+ model_match(state) {
+ const modelUrl = this.teachableImageModel;
+ const className = state;
+
+ const predictionState = this.getPredictionStateOrStartPredicting(modelUrl);
+ if (!predictionState) {
+ return false;
+ }
+
+ const currentMaxClass = predictionState.topClass;
+ return (currentMaxClass === String(className));
+ }
+
+ getModelClasses(): string[] {
+ if (
+ !this.teachableImageModel ||
+ !this.predictionState ||
+ !this.predictionState[this.teachableImageModel] ||
+ !this.predictionState[this.teachableImageModel].hasOwnProperty('model')
+ ) {
+ return ["Select a class"];
+ }
+
+ if (this.predictionState[this.teachableImageModel].modelType === this.ModelType.AUDIO) {
+ return this.predictionState[this.teachableImageModel].model.wordLabels();
+ }
+
+ return this.predictionState[this.teachableImageModel].model.getClassLabels();
+ }
+
+ getModelPrediction() {
+ const modelUrl = this.teachableImageModel;
+ const predictionState: { topClass: string } = this.getPredictionStateOrStartPredicting(modelUrl);
+ if (!predictionState) {
+ console.error("No prediction state found");
+ return '';
+ }
+ return predictionState.topClass;
+ }
+
+ private _loop() {
+ setTimeout(this._loop.bind(this), Math.max(this.runtime.currentStepTime, this.INTERVAL));
+ const time = Date.now();
+ if (this.lastUpdate === null) {
+ this.lastUpdate = time;
+ }
+ if (!this.isPredicting) {
+ this.isPredicting = 0;
+ }
+ const offset = time - this.lastUpdate;
+
+ if (offset > this.INTERVAL && this.isPredicting === 0) {
+ this.lastUpdate = time;
+ this.isPredicting = 0;
+ this.getImageStreamAndPredict();
+ }
+ }
+
+ private async getImageStreamAndPredict() {
+ try {
+ const imageStream = await this.getImageStream();
+ if (!imageStream) {
+ console.error("Failed to get image stream");
+ return;
+ }
+ const imageBitmap = await createImageBitmap(imageStream);
+ this.predictAllBlocks(imageBitmap);
+ } catch (error) {
+ console.error("Error in getting image stream and predicting:", error);
+ }
+ }
+
+ private async predictAllBlocks(frame: ImageBitmap) {
+ for (let modelUrl in this.predictionState) {
+ if (!this.predictionState[modelUrl].model) {
+ console.log('No model found for:', modelUrl);
+ continue;
+ }
+ if (this.teachableImageModel !== modelUrl) {
+ console.log('Model URL mismatch:', modelUrl);
+ continue;
+ }
+ ++this.isPredicting;
+ const prediction = await this.predictModel(modelUrl, frame);
+ console.log('Prediction:', prediction);
+ this.predictionState[modelUrl].topClass = prediction;
+ --this.isPredicting;
+ }
+ }
+
+ private async predictModel(modelUrl: string, frame: ImageBitmap) {
+ const predictions = await this.getPredictionFromModel(modelUrl, frame);
+ if (!predictions) {
+ return;
+ }
+ let maxProbability = 0;
+ let maxClassName = "";
+ for (let i = 0; i < predictions.length; i++) {
+ const probability = predictions[i].probability.toFixed(2);
+ const className = predictions[i].className;
+ this.modelConfidences[className] = probability;
+ if (probability > maxProbability) {
+ maxClassName = className;
+ maxProbability = probability;
+ }
+ }
+ this.maxConfidence = maxProbability;
+ return maxClassName;
+ }
+
+ private async getPredictionFromModel(modelUrl: string, frame: ImageBitmap) {
+ const { model, modelType } = this.predictionState[modelUrl];
+ switch (modelType) {
+ case this.ModelType.IMAGE:
+ if (!frame) return null;
+ return await model.predict(frame);
+ case this.ModelType.POSE:
+ if (!frame) return null;
+ const { pose, posenetOutput } = await model.estimatePose(frame);
+ return await model.predict(posenetOutput);
+ case this.ModelType.AUDIO:
+ if (this.latestAudioResults) {
+ return model.wordLabels().map((label, i) => ({
+ className: label,
+ probability: this.latestAudioResults.scores[i]
+ }));
+ }
+ return null;
+ }
+ }
+
+ @block({
+ type: "command",
+ text: (imageClass, seconds) => `capture snapshots of ${imageClass} class for ${seconds} seconds`,
+ args: [
+ { type: "string", defaultValue: "class name" },
+ { type: "number", defaultValue: 10 }
+ ]
+ })
+ async captureSnapshots(imageClass: string, seconds: number) {
+ if (this.SOCIAL && Math.random() < this.socialness) {
+ await this.doodlebot?.display("love");
+ await this.speakText(`Bring the ${imageClass} to my camera, and I will capture snapshots of it for you.`);
+ await this.speakText("Ready Set Go!");
+ }
+
+ // Create indicator to show progress
+ const indicator = await this.indicate({
+ type: "warning",
+ msg: `Capturing snapshots of ${imageClass}...`
+ });
+
+ const snapshots: string[] = [];
+ const zip = new JSZip();
+
+ // Ensure we have video stream
+ this.imageStream ??= await this.doodlebot?.getImageStream();
+ if (!this.imageStream) {
+ indicator.close();
+ await this.indicate({
+ type: "error",
+ msg: "No video stream available"
+ });
+ return;
+ }
+
+ // Capture a snapshot every 500ms
+ const interval = 100; // 500ms between snapshots
+ const iterations = (seconds * 1000) / interval;
+
+ for (let i = 0; i < iterations; i++) {
+ // Create a canvas to draw the current frame
+ const canvas = document.createElement('canvas');
+ canvas.width = this.imageStream.width;
+ canvas.height = this.imageStream.height;
+ const ctx = canvas.getContext('2d');
+
+ // Draw current frame to canvas
+ ctx.drawImage(this.imageStream, 0, 0);
+
+ // Convert to base64 and store
+ const dataUrl = canvas.toDataURL('image/jpeg');
+ snapshots.push(dataUrl);
+
+ // Add to zip file
+ const base64Data = dataUrl.replace(/^data:image\/jpeg;base64,/, "");
+ zip.file(`${imageClass}_${i+1}.jpg`, base64Data, {base64: true});
+
+ // Wait for next interval
+ await new Promise(resolve => setTimeout(resolve, interval));
+ }
+
+ // Generate zip file
+ const content = await zip.generateAsync({type: "blob"});
+
+ // Create download link with image class in filename
+ const downloadUrl = URL.createObjectURL(content);
+ const link = document.createElement('a');
+ link.href = downloadUrl;
+ link.download = `${imageClass}_snapshots.zip`;
+
+ // Trigger download
+ document.body.appendChild(link);
+ link.click();
+ document.body.removeChild(link);
+
+ // Cleanup
+ URL.revokeObjectURL(downloadUrl);
+ indicator.close();
+
+ await this.indicate({
+ type: "success",
+ msg: `Captured ${snapshots.length} snapshots of ${imageClass}`
+ });
+
+ if (this.SOCIAL && Math.random() < this.socialness) {
+ await this.doodlebot?.display("happy");
+ await this.speakText(`Done. I've saved the snapshots to the Downloads folder, to a zip file called ${imageClass}_snapshots`);
+ await this.speakText("You can now upload this file to the Teachable Machine interface to train your model.");
+ await this.speakText("Let me know if you have any questions.");
+ }
+ }
+
+ private async speakText(text: string, showDisplay: boolean = false) {
+ try {
+ // Display "speaking" while processing if showDisplay is true
+ if (showDisplay) {
+ await this.doodlebot?.display("clear");
+ await this.doodlebot?.displayText("speaking");
+ }
+
+ const url = "http://doodlebot.media.mit.edu/speak";
+ const response = await fetch(url, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({ text }),
+ });
+
+ if (!response.ok) {
+ throw new Error(`HTTP error! status: ${response.status}`);
+ }
+
+ const blob = await response.blob();
+ const audioUrl = URL.createObjectURL(blob);
+ const audio = new Audio(audioUrl);
+
+ // Convert blob to Uint8Array and send to Doodlebot
+ const array = await blob.arrayBuffer();
+ await this.doodlebot.sendAudioData(new Uint8Array(array));
+
+ // Wait a moment before clearing the display if showDisplay is true
+ if (showDisplay) {
+ await new Promise(resolve => setTimeout(resolve, 2000));
+ await this.doodlebot?.display("clear");
+ }
+
+ } catch (error) {
+ console.error("Error in speak function:", error);
+ if (showDisplay) {
+ await this.doodlebot?.display("clear");
+ await this.doodlebot?.displayText("error");
+ await new Promise(resolve => setTimeout(resolve, 2000));
+ await this.doodlebot?.display("clear");
+ }
+ throw error; // Re-throw the error so calling functions can handle it
+ }
+ }
+}
\ No newline at end of file
diff --git a/extensions/src/doodlebot/messages.ts b/extensions/src/doodlebot/messages.ts
new file mode 100644
index 000000000..024c396d3
--- /dev/null
+++ b/extensions/src/doodlebot/messages.ts
@@ -0,0 +1,6 @@
+import { Sensor } from "./enums"
+
+type SensorMessageReceived> = {
+ type: Sensor,
+ parameters: (string | number)[]
+}
\ No newline at end of file
diff --git a/extensions/src/doodlebot/package-lock.json b/extensions/src/doodlebot/package-lock.json
new file mode 100644
index 000000000..8181915c8
--- /dev/null
+++ b/extensions/src/doodlebot/package-lock.json
@@ -0,0 +1,53 @@
+{
+ "name": "doodlebot-extension",
+ "version": "1.0.0",
+ "lockfileVersion": 2,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "doodlebot-extension",
+ "version": "1.0.0",
+ "license": "ISC",
+ "dependencies": {
+ "@mediapipe/tasks-vision": "^0.10.12",
+ "@types/web-bluetooth": "^0.0.20",
+ "events": "^3.3.0"
+ }
+ },
+ "node_modules/@mediapipe/tasks-vision": {
+ "version": "0.10.12",
+ "resolved": "https://registry.npmjs.org/@mediapipe/tasks-vision/-/tasks-vision-0.10.12.tgz",
+ "integrity": "sha512-688Vukid7hvGmx+7hzS/EQ3Q4diz4eeX4/FYDw8f/t56UjFueD8LTvA2rX5BCIwvT0oy8QHKh5uKIyct1AOFtQ=="
+ },
+ "node_modules/@types/web-bluetooth": {
+ "version": "0.0.20",
+ "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz",
+ "integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow=="
+ },
+ "node_modules/events": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
+ "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==",
+ "engines": {
+ "node": ">=0.8.x"
+ }
+ }
+ },
+ "dependencies": {
+ "@mediapipe/tasks-vision": {
+ "version": "0.10.12",
+ "resolved": "https://registry.npmjs.org/@mediapipe/tasks-vision/-/tasks-vision-0.10.12.tgz",
+ "integrity": "sha512-688Vukid7hvGmx+7hzS/EQ3Q4diz4eeX4/FYDw8f/t56UjFueD8LTvA2rX5BCIwvT0oy8QHKh5uKIyct1AOFtQ=="
+ },
+ "@types/web-bluetooth": {
+ "version": "0.0.20",
+ "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz",
+ "integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow=="
+ },
+ "events": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
+ "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="
+ }
+ }
+}
diff --git a/extensions/src/doodlebot/package.json b/extensions/src/doodlebot/package.json
new file mode 100644
index 000000000..3f9121176
--- /dev/null
+++ b/extensions/src/doodlebot/package.json
@@ -0,0 +1,30 @@
+{
+ "name": "doodlebot-extension",
+ "version": "1.0.0",
+ "description": "An extension created using the PRG AI Blocks framework",
+ "main": "index.ts",
+ "scripts": {
+ "directory": "echo doodlebot",
+ "test": "npm run test --prefix ../../ -- doodlebot/index.test.ts",
+ "dev": "npm run dev --prefix ../../../ -- --include doodlebot",
+ "add:ui": "npm run add:ui --prefix ../../../ -- doodlebot",
+ "add:arg": "npm run --prefix ../../../-- doodlebot"
+ },
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "@mediapipe/tasks-vision": "^0.10.12",
+ "@teachablemachine/image": "^0.8.5",
+ "@teachablemachine/pose": "^0.8.6",
+ "@tensorflow-models/speech-commands": "^0.5.4",
+ "@tensorflow/tfjs": "^4.17.0",
+ "@types/web-bluetooth": "^0.0.20",
+ "axios": "^1.7.7",
+ "bezier-js": "^6.1.4",
+ "canvas": "^2.11.2",
+ "cubic-spline": "^3.0.3",
+ "curve-matcher": "^1.1.1",
+ "events": "^3.3.0",
+ "jszip": "^3.10.1"
+ }
+}
\ No newline at end of file
diff --git a/extensions/src/doodlebot/pnpm-lock.yaml b/extensions/src/doodlebot/pnpm-lock.yaml
new file mode 100644
index 000000000..bd4c89c00
--- /dev/null
+++ b/extensions/src/doodlebot/pnpm-lock.yaml
@@ -0,0 +1,1053 @@
+lockfileVersion: '9.0'
+
+settings:
+ autoInstallPeers: true
+ excludeLinksFromLockfile: false
+
+importers:
+
+ .:
+ dependencies:
+ '@mediapipe/tasks-vision':
+ specifier: ^0.10.12
+ version: 0.10.14
+ '@teachablemachine/image':
+ specifier: ^0.8.5
+ version: 0.8.5(@tensorflow/tfjs@4.22.0(seedrandom@3.0.5))
+ '@teachablemachine/pose':
+ specifier: ^0.8.6
+ version: 0.8.6(@tensorflow/tfjs-converter@4.22.0(@tensorflow/tfjs-core@4.22.0))(@tensorflow/tfjs-core@4.22.0)(@tensorflow/tfjs@4.22.0(seedrandom@3.0.5))
+ '@tensorflow-models/speech-commands':
+ specifier: ^0.5.4
+ version: 0.5.4(@tensorflow/tfjs-core@4.22.0)(@tensorflow/tfjs-data@4.22.0(@tensorflow/tfjs-core@4.22.0)(seedrandom@3.0.5))(@tensorflow/tfjs-layers@4.22.0(@tensorflow/tfjs-core@4.22.0))
+ '@tensorflow/tfjs':
+ specifier: ^4.17.0
+ version: 4.22.0(seedrandom@3.0.5)
+ '@types/web-bluetooth':
+ specifier: ^0.0.20
+ version: 0.0.20
+ axios:
+ specifier: ^1.7.7
+ version: 1.7.7
+ bezier-js:
+ specifier: ^6.1.4
+ version: 6.1.4
+ canvas:
+ specifier: ^2.11.2
+ version: 2.11.2
+ cubic-spline:
+ specifier: ^3.0.3
+ version: 3.0.3
+ curve-matcher:
+ specifier: ^1.1.1
+ version: 1.1.1
+ events:
+ specifier: ^3.3.0
+ version: 3.3.0
+ jszip:
+ specifier: ^3.10.1
+ version: 3.10.1
+
+packages:
+
+ '@mapbox/node-pre-gyp@1.0.11':
+ resolution: {integrity: sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==}
+ hasBin: true
+
+ '@mediapipe/tasks-vision@0.10.14':
+ resolution: {integrity: sha512-vOifgZhkndgybdvoRITzRkIueWWSiCKuEUXXK6Q4FaJsFvRJuwgg++vqFUMlL0Uox62U5aEXFhHxlhV7Ja5e3Q==}
+
+ '@teachablemachine/image@0.8.5':
+ resolution: {integrity: sha512-u6BLQ8RBn38BKee7mmAl+HS9qWgM/2YmUbQYy21eQfqhb7E8xT5LDp7QqEU31rtgEIMDwB/OG8cjmSiuHpqb7A==}
+ peerDependencies:
+ '@tensorflow/tfjs': 1.3.1
+
+ '@teachablemachine/pose@0.8.6':
+ resolution: {integrity: sha512-72UHof2okPdyKlDMSnFcDk/h7dj4I9BBbGsJs64P+B/WQb1rwFcxAeeUFud5rJ2HFwElLDLEjnyaHZljeWV2Bg==}
+ peerDependencies:
+ '@tensorflow/tfjs': 1.3.1
+
+ '@tensorflow-models/posenet@2.2.2':
+ resolution: {integrity: sha512-0SXIksRet/IdX7WVH+JSD6W3upkGHix1hwtd3xykIoIMGR7zQ4SC5+wZcNt9ofASyxNYQoI+tUULUo4LNw0c3w==}
+ peerDependencies:
+ '@tensorflow/tfjs-converter': ^3.0.0-rc.1
+ '@tensorflow/tfjs-core': ^3.0.0-rc.1
+
+ '@tensorflow-models/speech-commands@0.5.4':
+ resolution: {integrity: sha512-r0c/MvC15/09xWujx1pKe6mA0nta+4jQWDXGkqfSVkXLo8ARrwcZ4mTGLlfvT43ySfidiveUo0m+P51+UK821Q==}
+ peerDependencies:
+ '@tensorflow/tfjs-core': ^3.0.0
+ '@tensorflow/tfjs-data': ^3.0.0
+ '@tensorflow/tfjs-layers': ^3.0.0
+
+ '@tensorflow/tfjs-backend-cpu@4.22.0':
+ resolution: {integrity: sha512-1u0FmuLGuRAi8D2c3cocHTASGXOmHc/4OvoVDENJayjYkS119fcTcQf4iHrtLthWyDIPy3JiPhRrZQC9EwnhLw==}
+ engines: {yarn: '>= 1.3.2'}
+ peerDependencies:
+ '@tensorflow/tfjs-core': 4.22.0
+
+ '@tensorflow/tfjs-backend-webgl@4.22.0':
+ resolution: {integrity: sha512-H535XtZWnWgNwSzv538czjVlbJebDl5QTMOth4RXr2p/kJ1qSIXE0vZvEtO+5EC9b00SvhplECny2yDewQb/Yg==}
+ engines: {yarn: '>= 1.3.2'}
+ peerDependencies:
+ '@tensorflow/tfjs-core': 4.22.0
+
+ '@tensorflow/tfjs-converter@4.22.0':
+ resolution: {integrity: sha512-PT43MGlnzIo+YfbsjM79Lxk9lOq6uUwZuCc8rrp0hfpLjF6Jv8jS84u2jFb+WpUeuF4K33ZDNx8CjiYrGQ2trQ==}
+ peerDependencies:
+ '@tensorflow/tfjs-core': 4.22.0
+
+ '@tensorflow/tfjs-core@4.22.0':
+ resolution: {integrity: sha512-LEkOyzbknKFoWUwfkr59vSB68DMJ4cjwwHgicXN0DUi3a0Vh1Er3JQqCI1Hl86GGZQvY8ezVrtDIvqR1ZFW55A==}
+ engines: {yarn: '>= 1.3.2'}
+
+ '@tensorflow/tfjs-data@4.22.0':
+ resolution: {integrity: sha512-dYmF3LihQIGvtgJrt382hSRH4S0QuAp2w1hXJI2+kOaEqo5HnUPG0k5KA6va+S1yUhx7UBToUKCBHeLHFQRV4w==}
+ peerDependencies:
+ '@tensorflow/tfjs-core': 4.22.0
+ seedrandom: ^3.0.5
+
+ '@tensorflow/tfjs-layers@4.22.0':
+ resolution: {integrity: sha512-lybPj4ZNj9iIAPUj7a8ZW1hg8KQGfqWLlCZDi9eM/oNKCCAgchiyzx8OrYoWmRrB+AM6VNEeIT+2gZKg5ReihA==}
+ peerDependencies:
+ '@tensorflow/tfjs-core': 4.22.0
+
+ '@tensorflow/tfjs@4.22.0':
+ resolution: {integrity: sha512-0TrIrXs6/b7FLhLVNmfh8Sah6JgjBPH4mZ8JGb7NU6WW+cx00qK5BcAZxw7NCzxj6N8MRAIfHq+oNbPUNG5VAg==}
+ hasBin: true
+
+ '@types/long@4.0.2':
+ resolution: {integrity: sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==}
+
+ '@types/node-fetch@2.6.12':
+ resolution: {integrity: sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==}
+
+ '@types/node@22.10.1':
+ resolution: {integrity: sha512-qKgsUwfHZV2WCWLAnVP1JqnpE6Im6h3Y0+fYgMTasNQ7V++CBX5OT1as0g0f+OyubbFqhf6XVNIsmN4IIhEgGQ==}
+
+ '@types/offscreencanvas@2019.3.0':
+ resolution: {integrity: sha512-esIJx9bQg+QYF0ra8GnvfianIY8qWB0GBx54PK5Eps6m+xTj86KLavHv6qDhzKcu5UUOgNfJ2pWaIIV7TRUd9Q==}
+
+ '@types/offscreencanvas@2019.7.3':
+ resolution: {integrity: sha512-ieXiYmgSRXUDeOntE1InxjWyvEelZGP63M+cGuquuRLuIKKT1osnkXjxev9B7d1nXSug5vpunx+gNlbVxMlC9A==}
+
+ '@types/seedrandom@2.4.34':
+ resolution: {integrity: sha512-ytDiArvrn/3Xk6/vtylys5tlY6eo7Ane0hvcx++TKo6RxQXuVfW0AF/oeWqAj9dN29SyhtawuXstgmPlwNcv/A==}
+
+ '@types/web-bluetooth@0.0.20':
+ resolution: {integrity: sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==}
+
+ '@webgpu/types@0.1.38':
+ resolution: {integrity: sha512-7LrhVKz2PRh+DD7+S+PVaFd5HxaWQvoMqBbsV9fNJO1pjUs1P8bM2vQVNfk+3URTqbuTI7gkXi0rfsN0IadoBA==}
+
+ abbrev@1.1.1:
+ resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==}
+
+ agent-base@6.0.2:
+ resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==}
+ engines: {node: '>= 6.0.0'}
+
+ ansi-regex@5.0.1:
+ resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
+ engines: {node: '>=8'}
+
+ ansi-styles@4.3.0:
+ resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
+ engines: {node: '>=8'}
+
+ aproba@2.0.0:
+ resolution: {integrity: sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==}
+
+ are-we-there-yet@2.0.0:
+ resolution: {integrity: sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==}
+ engines: {node: '>=10'}
+ deprecated: This package is no longer supported.
+
+ argparse@1.0.10:
+ resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==}
+
+ asynckit@0.4.0:
+ resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
+
+ autobind-decorator@2.4.0:
+ resolution: {integrity: sha512-OGYhWUO72V6DafbF8PM8rm3EPbfuyMZcJhtm5/n26IDwO18pohE4eNazLoCGhPiXOCD0gEGmrbU3849QvM8bbw==}
+ engines: {node: '>=8.10', npm: '>=6.4.1'}
+
+ axios@1.7.7:
+ resolution: {integrity: sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==}
+
+ balanced-match@1.0.2:
+ resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
+
+ bezier-js@6.1.4:
+ resolution: {integrity: sha512-PA0FW9ZpcHbojUCMu28z9Vg/fNkwTj5YhusSAjHHDfHDGLxJ6YUKrAN2vk1fP2MMOxVw4Oko16FMlRGVBGqLKg==}
+
+ brace-expansion@1.1.11:
+ resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
+
+ canvas@2.11.2:
+ resolution: {integrity: sha512-ItanGBMrmRV7Py2Z+Xhs7cT+FNt5K0vPL4p9EZ/UX/Mu7hFbkxSjKF2KVtPwX7UYWp7dRKnrTvReflgrItJbdw==}
+ engines: {node: '>=6'}
+
+ chalk@4.1.2:
+ resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
+ engines: {node: '>=10'}
+
+ chownr@2.0.0:
+ resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==}
+ engines: {node: '>=10'}
+
+ cliui@7.0.4:
+ resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==}
+
+ color-convert@2.0.1:
+ resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
+ engines: {node: '>=7.0.0'}
+
+ color-name@1.1.4:
+ resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
+
+ color-support@1.1.3:
+ resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==}
+ hasBin: true
+
+ combined-stream@1.0.8:
+ resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
+ engines: {node: '>= 0.8'}
+
+ concat-map@0.0.1:
+ resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
+
+ console-control-strings@1.1.0:
+ resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==}
+
+ core-js@3.29.1:
+ resolution: {integrity: sha512-+jwgnhg6cQxKYIIjGtAHq2nwUOolo9eoFZ4sHfUH09BLXBgxnH4gA0zEd+t+BO2cNB8idaBtZFcFTRjQJRJmAw==}
+
+ core-util-is@1.0.3:
+ resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==}
+
+ cubic-spline@3.0.3:
+ resolution: {integrity: sha512-yAvcHgrpf/k83pZiO4+R2reWOJlufgjpQhmDD3OXa8YMbjmRgjtUK8pcFOCZvJwqXaMD1isZdL7Z4ghqDPN/yw==}
+
+ curve-matcher@1.1.1:
+ resolution: {integrity: sha512-dVjbB4izZ+nkxU5oq15ElZk2IFpR1oTb1wEfMolOADse8veTn+iwF6wXejS4ll9DZRtO0/s95BDiIz6QXuE5bA==}
+ engines: {node: '>=6.0.0'}
+
+ debug@4.3.7:
+ resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==}
+ engines: {node: '>=6.0'}
+ peerDependencies:
+ supports-color: '*'
+ peerDependenciesMeta:
+ supports-color:
+ optional: true
+
+ decompress-response@4.2.1:
+ resolution: {integrity: sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==}
+ engines: {node: '>=8'}
+
+ delayed-stream@1.0.0:
+ resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
+ engines: {node: '>=0.4.0'}
+
+ delegates@1.0.0:
+ resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==}
+
+ detect-libc@2.0.3:
+ resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==}
+ engines: {node: '>=8'}
+
+ emoji-regex@8.0.0:
+ resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
+
+ escalade@3.2.0:
+ resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
+ engines: {node: '>=6'}
+
+ events@3.3.0:
+ resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==}
+ engines: {node: '>=0.8.x'}
+
+ follow-redirects@1.15.9:
+ resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==}
+ engines: {node: '>=4.0'}
+ peerDependencies:
+ debug: '*'
+ peerDependenciesMeta:
+ debug:
+ optional: true
+
+ form-data@4.0.1:
+ resolution: {integrity: sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==}
+ engines: {node: '>= 6'}
+
+ fs-minipass@2.1.0:
+ resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==}
+ engines: {node: '>= 8'}
+
+ fs.realpath@1.0.0:
+ resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
+
+ gauge@3.0.2:
+ resolution: {integrity: sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==}
+ engines: {node: '>=10'}
+ deprecated: This package is no longer supported.
+
+ get-caller-file@2.0.5:
+ resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
+ engines: {node: 6.* || 8.* || >= 10.*}
+
+ glob@7.2.3:
+ resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
+ deprecated: Glob versions prior to v9 are no longer supported
+
+ has-flag@4.0.0:
+ resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
+ engines: {node: '>=8'}
+
+ has-unicode@2.0.1:
+ resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==}
+
+ https-proxy-agent@5.0.1:
+ resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==}
+ engines: {node: '>= 6'}
+
+ immediate@3.0.6:
+ resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==}
+
+ inflight@1.0.6:
+ resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==}
+ deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.
+
+ inherits@2.0.4:
+ resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
+
+ is-fullwidth-code-point@3.0.0:
+ resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
+ engines: {node: '>=8'}
+
+ isarray@1.0.0:
+ resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==}
+
+ jszip@3.10.1:
+ resolution: {integrity: sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==}
+
+ lie@3.3.0:
+ resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==}
+
+ long@4.0.0:
+ resolution: {integrity: sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==}
+
+ make-dir@3.1.0:
+ resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==}
+ engines: {node: '>=8'}
+
+ mime-db@1.52.0:
+ resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
+ engines: {node: '>= 0.6'}
+
+ mime-types@2.1.35:
+ resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
+ engines: {node: '>= 0.6'}
+
+ mimic-response@2.1.0:
+ resolution: {integrity: sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==}
+ engines: {node: '>=8'}
+
+ minimatch@3.1.2:
+ resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
+
+ minipass@3.3.6:
+ resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==}
+ engines: {node: '>=8'}
+
+ minipass@5.0.0:
+ resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==}
+ engines: {node: '>=8'}
+
+ minizlib@2.1.2:
+ resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==}
+ engines: {node: '>= 8'}
+
+ mkdirp@1.0.4:
+ resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==}
+ engines: {node: '>=10'}
+ hasBin: true
+
+ ms@2.1.3:
+ resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
+
+ nan@2.20.0:
+ resolution: {integrity: sha512-bk3gXBZDGILuuo/6sKtr0DQmSThYHLtNCdSdXk9YkxD/jK6X2vmCyyXBBxyqZ4XcnzTyYEAThfX3DCEnLf6igw==}
+
+ node-fetch@2.6.13:
+ resolution: {integrity: sha512-StxNAxh15zr77QvvkmveSQ8uCQ4+v5FkvNTj0OESmiHu+VRi/gXArXtkWMElOsOUNLtUEvI4yS+rdtOHZTwlQA==}
+ engines: {node: 4.x || >=6.0.0}
+ peerDependencies:
+ encoding: ^0.1.0
+ peerDependenciesMeta:
+ encoding:
+ optional: true
+
+ node-fetch@2.7.0:
+ resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==}
+ engines: {node: 4.x || >=6.0.0}
+ peerDependencies:
+ encoding: ^0.1.0
+ peerDependenciesMeta:
+ encoding:
+ optional: true
+
+ nopt@5.0.0:
+ resolution: {integrity: sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==}
+ engines: {node: '>=6'}
+ hasBin: true
+
+ npmlog@5.0.1:
+ resolution: {integrity: sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==}
+ deprecated: This package is no longer supported.
+
+ object-assign@4.1.1:
+ resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
+ engines: {node: '>=0.10.0'}
+
+ once@1.4.0:
+ resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
+
+ pako@1.0.11:
+ resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==}
+
+ path-is-absolute@1.0.1:
+ resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==}
+ engines: {node: '>=0.10.0'}
+
+ process-nextick-args@2.0.1:
+ resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==}
+
+ proxy-from-env@1.1.0:
+ resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
+
+ readable-stream@2.3.8:
+ resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==}
+
+ readable-stream@3.6.2:
+ resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==}
+ engines: {node: '>= 6'}
+
+ regenerator-runtime@0.13.11:
+ resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==}
+
+ require-directory@2.1.1:
+ resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
+ engines: {node: '>=0.10.0'}
+
+ rimraf@3.0.2:
+ resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==}
+ deprecated: Rimraf versions prior to v4 are no longer supported
+ hasBin: true
+
+ safe-buffer@5.1.2:
+ resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==}
+
+ safe-buffer@5.2.1:
+ resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
+
+ seedrandom@2.4.4:
+ resolution: {integrity: sha512-9A+PDmgm+2du77B5i0Ip2cxOqqHjgNxnBgglxLcX78A2D6c2rTo61z4jnVABpF4cKeDMDG+cmXXvdnqse2VqMA==}
+
+ seedrandom@3.0.5:
+ resolution: {integrity: sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==}
+
+ semver@6.3.1:
+ resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
+ hasBin: true
+
+ semver@7.6.3:
+ resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==}
+ engines: {node: '>=10'}
+ hasBin: true
+
+ set-blocking@2.0.0:
+ resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==}
+
+ setimmediate@1.0.5:
+ resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==}
+
+ signal-exit@3.0.7:
+ resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==}
+
+ simple-concat@1.0.1:
+ resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==}
+
+ simple-get@3.1.1:
+ resolution: {integrity: sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==}
+
+ sprintf-js@1.0.3:
+ resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==}
+
+ string-width@4.2.3:
+ resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
+ engines: {node: '>=8'}
+
+ string_decoder@1.1.1:
+ resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==}
+
+ string_decoder@1.3.0:
+ resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==}
+
+ strip-ansi@6.0.1:
+ resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
+ engines: {node: '>=8'}
+
+ supports-color@7.2.0:
+ resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
+ engines: {node: '>=8'}
+
+ tar@6.2.1:
+ resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==}
+ engines: {node: '>=10'}
+
+ tr46@0.0.3:
+ resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
+
+ undici-types@6.20.0:
+ resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==}
+
+ util-deprecate@1.0.2:
+ resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
+
+ webidl-conversions@3.0.1:
+ resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
+
+ whatwg-url@5.0.0:
+ resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==}
+
+ wide-align@1.1.5:
+ resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==}
+
+ wrap-ansi@7.0.0:
+ resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
+ engines: {node: '>=10'}
+
+ wrappy@1.0.2:
+ resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
+
+ y18n@5.0.8:
+ resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
+ engines: {node: '>=10'}
+
+ yallist@4.0.0:
+ resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
+
+ yargs-parser@20.2.9:
+ resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==}
+ engines: {node: '>=10'}
+
+ yargs@16.2.0:
+ resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==}
+ engines: {node: '>=10'}
+
+snapshots:
+
+ '@mapbox/node-pre-gyp@1.0.11':
+ dependencies:
+ detect-libc: 2.0.3
+ https-proxy-agent: 5.0.1
+ make-dir: 3.1.0
+ node-fetch: 2.7.0
+ nopt: 5.0.0
+ npmlog: 5.0.1
+ rimraf: 3.0.2
+ semver: 7.6.3
+ tar: 6.2.1
+ transitivePeerDependencies:
+ - encoding
+ - supports-color
+
+ '@mediapipe/tasks-vision@0.10.14': {}
+
+ '@teachablemachine/image@0.8.5(@tensorflow/tfjs@4.22.0(seedrandom@3.0.5))':
+ dependencies:
+ '@tensorflow/tfjs': 4.22.0(seedrandom@3.0.5)
+ autobind-decorator: 2.4.0
+ seedrandom: 2.4.4
+
+ '@teachablemachine/pose@0.8.6(@tensorflow/tfjs-converter@4.22.0(@tensorflow/tfjs-core@4.22.0))(@tensorflow/tfjs-core@4.22.0)(@tensorflow/tfjs@4.22.0(seedrandom@3.0.5))':
+ dependencies:
+ '@tensorflow-models/posenet': 2.2.2(@tensorflow/tfjs-converter@4.22.0(@tensorflow/tfjs-core@4.22.0))(@tensorflow/tfjs-core@4.22.0)
+ '@tensorflow/tfjs': 4.22.0(seedrandom@3.0.5)
+ autobind-decorator: 2.4.0
+ seedrandom: 3.0.5
+ transitivePeerDependencies:
+ - '@tensorflow/tfjs-converter'
+ - '@tensorflow/tfjs-core'
+
+ '@tensorflow-models/posenet@2.2.2(@tensorflow/tfjs-converter@4.22.0(@tensorflow/tfjs-core@4.22.0))(@tensorflow/tfjs-core@4.22.0)':
+ dependencies:
+ '@tensorflow/tfjs-converter': 4.22.0(@tensorflow/tfjs-core@4.22.0)
+ '@tensorflow/tfjs-core': 4.22.0
+
+ '@tensorflow-models/speech-commands@0.5.4(@tensorflow/tfjs-core@4.22.0)(@tensorflow/tfjs-data@4.22.0(@tensorflow/tfjs-core@4.22.0)(seedrandom@3.0.5))(@tensorflow/tfjs-layers@4.22.0(@tensorflow/tfjs-core@4.22.0))':
+ dependencies:
+ '@tensorflow/tfjs-core': 4.22.0
+ '@tensorflow/tfjs-data': 4.22.0(@tensorflow/tfjs-core@4.22.0)(seedrandom@3.0.5)
+ '@tensorflow/tfjs-layers': 4.22.0(@tensorflow/tfjs-core@4.22.0)
+
+ '@tensorflow/tfjs-backend-cpu@4.22.0(@tensorflow/tfjs-core@4.22.0)':
+ dependencies:
+ '@tensorflow/tfjs-core': 4.22.0
+ '@types/seedrandom': 2.4.34
+ seedrandom: 3.0.5
+
+ '@tensorflow/tfjs-backend-webgl@4.22.0(@tensorflow/tfjs-core@4.22.0)':
+ dependencies:
+ '@tensorflow/tfjs-backend-cpu': 4.22.0(@tensorflow/tfjs-core@4.22.0)
+ '@tensorflow/tfjs-core': 4.22.0
+ '@types/offscreencanvas': 2019.3.0
+ '@types/seedrandom': 2.4.34
+ seedrandom: 3.0.5
+
+ '@tensorflow/tfjs-converter@4.22.0(@tensorflow/tfjs-core@4.22.0)':
+ dependencies:
+ '@tensorflow/tfjs-core': 4.22.0
+
+ '@tensorflow/tfjs-core@4.22.0':
+ dependencies:
+ '@types/long': 4.0.2
+ '@types/offscreencanvas': 2019.7.3
+ '@types/seedrandom': 2.4.34
+ '@webgpu/types': 0.1.38
+ long: 4.0.0
+ node-fetch: 2.6.13
+ seedrandom: 3.0.5
+ transitivePeerDependencies:
+ - encoding
+
+ '@tensorflow/tfjs-data@4.22.0(@tensorflow/tfjs-core@4.22.0)(seedrandom@3.0.5)':
+ dependencies:
+ '@tensorflow/tfjs-core': 4.22.0
+ '@types/node-fetch': 2.6.12
+ node-fetch: 2.6.13
+ seedrandom: 3.0.5
+ string_decoder: 1.3.0
+ transitivePeerDependencies:
+ - encoding
+
+ '@tensorflow/tfjs-layers@4.22.0(@tensorflow/tfjs-core@4.22.0)':
+ dependencies:
+ '@tensorflow/tfjs-core': 4.22.0
+
+ '@tensorflow/tfjs@4.22.0(seedrandom@3.0.5)':
+ dependencies:
+ '@tensorflow/tfjs-backend-cpu': 4.22.0(@tensorflow/tfjs-core@4.22.0)
+ '@tensorflow/tfjs-backend-webgl': 4.22.0(@tensorflow/tfjs-core@4.22.0)
+ '@tensorflow/tfjs-converter': 4.22.0(@tensorflow/tfjs-core@4.22.0)
+ '@tensorflow/tfjs-core': 4.22.0
+ '@tensorflow/tfjs-data': 4.22.0(@tensorflow/tfjs-core@4.22.0)(seedrandom@3.0.5)
+ '@tensorflow/tfjs-layers': 4.22.0(@tensorflow/tfjs-core@4.22.0)
+ argparse: 1.0.10
+ chalk: 4.1.2
+ core-js: 3.29.1
+ regenerator-runtime: 0.13.11
+ yargs: 16.2.0
+ transitivePeerDependencies:
+ - encoding
+ - seedrandom
+
+ '@types/long@4.0.2': {}
+
+ '@types/node-fetch@2.6.12':
+ dependencies:
+ '@types/node': 22.10.1
+ form-data: 4.0.1
+
+ '@types/node@22.10.1':
+ dependencies:
+ undici-types: 6.20.0
+
+ '@types/offscreencanvas@2019.3.0': {}
+
+ '@types/offscreencanvas@2019.7.3': {}
+
+ '@types/seedrandom@2.4.34': {}
+
+ '@types/web-bluetooth@0.0.20': {}
+
+ '@webgpu/types@0.1.38': {}
+
+ abbrev@1.1.1: {}
+
+ agent-base@6.0.2:
+ dependencies:
+ debug: 4.3.7
+ transitivePeerDependencies:
+ - supports-color
+
+ ansi-regex@5.0.1: {}
+
+ ansi-styles@4.3.0:
+ dependencies:
+ color-convert: 2.0.1
+
+ aproba@2.0.0: {}
+
+ are-we-there-yet@2.0.0:
+ dependencies:
+ delegates: 1.0.0
+ readable-stream: 3.6.2
+
+ argparse@1.0.10:
+ dependencies:
+ sprintf-js: 1.0.3
+
+ asynckit@0.4.0: {}
+
+ autobind-decorator@2.4.0: {}
+
+ axios@1.7.7:
+ dependencies:
+ follow-redirects: 1.15.9
+ form-data: 4.0.1
+ proxy-from-env: 1.1.0
+ transitivePeerDependencies:
+ - debug
+
+ balanced-match@1.0.2: {}
+
+ bezier-js@6.1.4: {}
+
+ brace-expansion@1.1.11:
+ dependencies:
+ balanced-match: 1.0.2
+ concat-map: 0.0.1
+
+ canvas@2.11.2:
+ dependencies:
+ '@mapbox/node-pre-gyp': 1.0.11
+ nan: 2.20.0
+ simple-get: 3.1.1
+ transitivePeerDependencies:
+ - encoding
+ - supports-color
+
+ chalk@4.1.2:
+ dependencies:
+ ansi-styles: 4.3.0
+ supports-color: 7.2.0
+
+ chownr@2.0.0: {}
+
+ cliui@7.0.4:
+ dependencies:
+ string-width: 4.2.3
+ strip-ansi: 6.0.1
+ wrap-ansi: 7.0.0
+
+ color-convert@2.0.1:
+ dependencies:
+ color-name: 1.1.4
+
+ color-name@1.1.4: {}
+
+ color-support@1.1.3: {}
+
+ combined-stream@1.0.8:
+ dependencies:
+ delayed-stream: 1.0.0
+
+ concat-map@0.0.1: {}
+
+ console-control-strings@1.1.0: {}
+
+ core-js@3.29.1: {}
+
+ core-util-is@1.0.3: {}
+
+ cubic-spline@3.0.3: {}
+
+ curve-matcher@1.1.1: {}
+
+ debug@4.3.7:
+ dependencies:
+ ms: 2.1.3
+
+ decompress-response@4.2.1:
+ dependencies:
+ mimic-response: 2.1.0
+
+ delayed-stream@1.0.0: {}
+
+ delegates@1.0.0: {}
+
+ detect-libc@2.0.3: {}
+
+ emoji-regex@8.0.0: {}
+
+ escalade@3.2.0: {}
+
+ events@3.3.0: {}
+
+ follow-redirects@1.15.9: {}
+
+ form-data@4.0.1:
+ dependencies:
+ asynckit: 0.4.0
+ combined-stream: 1.0.8
+ mime-types: 2.1.35
+
+ fs-minipass@2.1.0:
+ dependencies:
+ minipass: 3.3.6
+
+ fs.realpath@1.0.0: {}
+
+ gauge@3.0.2:
+ dependencies:
+ aproba: 2.0.0
+ color-support: 1.1.3
+ console-control-strings: 1.1.0
+ has-unicode: 2.0.1
+ object-assign: 4.1.1
+ signal-exit: 3.0.7
+ string-width: 4.2.3
+ strip-ansi: 6.0.1
+ wide-align: 1.1.5
+
+ get-caller-file@2.0.5: {}
+
+ glob@7.2.3:
+ dependencies:
+ fs.realpath: 1.0.0
+ inflight: 1.0.6
+ inherits: 2.0.4
+ minimatch: 3.1.2
+ once: 1.4.0
+ path-is-absolute: 1.0.1
+
+ has-flag@4.0.0: {}
+
+ has-unicode@2.0.1: {}
+
+ https-proxy-agent@5.0.1:
+ dependencies:
+ agent-base: 6.0.2
+ debug: 4.3.7
+ transitivePeerDependencies:
+ - supports-color
+
+ immediate@3.0.6: {}
+
+ inflight@1.0.6:
+ dependencies:
+ once: 1.4.0
+ wrappy: 1.0.2
+
+ inherits@2.0.4: {}
+
+ is-fullwidth-code-point@3.0.0: {}
+
+ isarray@1.0.0: {}
+
+ jszip@3.10.1:
+ dependencies:
+ lie: 3.3.0
+ pako: 1.0.11
+ readable-stream: 2.3.8
+ setimmediate: 1.0.5
+
+ lie@3.3.0:
+ dependencies:
+ immediate: 3.0.6
+
+ long@4.0.0: {}
+
+ make-dir@3.1.0:
+ dependencies:
+ semver: 6.3.1
+
+ mime-db@1.52.0: {}
+
+ mime-types@2.1.35:
+ dependencies:
+ mime-db: 1.52.0
+
+ mimic-response@2.1.0: {}
+
+ minimatch@3.1.2:
+ dependencies:
+ brace-expansion: 1.1.11
+
+ minipass@3.3.6:
+ dependencies:
+ yallist: 4.0.0
+
+ minipass@5.0.0: {}
+
+ minizlib@2.1.2:
+ dependencies:
+ minipass: 3.3.6
+ yallist: 4.0.0
+
+ mkdirp@1.0.4: {}
+
+ ms@2.1.3: {}
+
+ nan@2.20.0: {}
+
+ node-fetch@2.6.13:
+ dependencies:
+ whatwg-url: 5.0.0
+
+ node-fetch@2.7.0:
+ dependencies:
+ whatwg-url: 5.0.0
+
+ nopt@5.0.0:
+ dependencies:
+ abbrev: 1.1.1
+
+ npmlog@5.0.1:
+ dependencies:
+ are-we-there-yet: 2.0.0
+ console-control-strings: 1.1.0
+ gauge: 3.0.2
+ set-blocking: 2.0.0
+
+ object-assign@4.1.1: {}
+
+ once@1.4.0:
+ dependencies:
+ wrappy: 1.0.2
+
+ pako@1.0.11: {}
+
+ path-is-absolute@1.0.1: {}
+
+ process-nextick-args@2.0.1: {}
+
+ proxy-from-env@1.1.0: {}
+
+ readable-stream@2.3.8:
+ dependencies:
+ core-util-is: 1.0.3
+ inherits: 2.0.4
+ isarray: 1.0.0
+ process-nextick-args: 2.0.1
+ safe-buffer: 5.1.2
+ string_decoder: 1.1.1
+ util-deprecate: 1.0.2
+
+ readable-stream@3.6.2:
+ dependencies:
+ inherits: 2.0.4
+ string_decoder: 1.3.0
+ util-deprecate: 1.0.2
+
+ regenerator-runtime@0.13.11: {}
+
+ require-directory@2.1.1: {}
+
+ rimraf@3.0.2:
+ dependencies:
+ glob: 7.2.3
+
+ safe-buffer@5.1.2: {}
+
+ safe-buffer@5.2.1: {}
+
+ seedrandom@2.4.4: {}
+
+ seedrandom@3.0.5: {}
+
+ semver@6.3.1: {}
+
+ semver@7.6.3: {}
+
+ set-blocking@2.0.0: {}
+
+ setimmediate@1.0.5: {}
+
+ signal-exit@3.0.7: {}
+
+ simple-concat@1.0.1: {}
+
+ simple-get@3.1.1:
+ dependencies:
+ decompress-response: 4.2.1
+ once: 1.4.0
+ simple-concat: 1.0.1
+
+ sprintf-js@1.0.3: {}
+
+ string-width@4.2.3:
+ dependencies:
+ emoji-regex: 8.0.0
+ is-fullwidth-code-point: 3.0.0
+ strip-ansi: 6.0.1
+
+ string_decoder@1.1.1:
+ dependencies:
+ safe-buffer: 5.1.2
+
+ string_decoder@1.3.0:
+ dependencies:
+ safe-buffer: 5.2.1
+
+ strip-ansi@6.0.1:
+ dependencies:
+ ansi-regex: 5.0.1
+
+ supports-color@7.2.0:
+ dependencies:
+ has-flag: 4.0.0
+
+ tar@6.2.1:
+ dependencies:
+ chownr: 2.0.0
+ fs-minipass: 2.1.0
+ minipass: 5.0.0
+ minizlib: 2.1.2
+ mkdirp: 1.0.4
+ yallist: 4.0.0
+
+ tr46@0.0.3: {}
+
+ undici-types@6.20.0: {}
+
+ util-deprecate@1.0.2: {}
+
+ webidl-conversions@3.0.1: {}
+
+ whatwg-url@5.0.0:
+ dependencies:
+ tr46: 0.0.3
+ webidl-conversions: 3.0.1
+
+ wide-align@1.1.5:
+ dependencies:
+ string-width: 4.2.3
+
+ wrap-ansi@7.0.0:
+ dependencies:
+ ansi-styles: 4.3.0
+ string-width: 4.2.3
+ strip-ansi: 6.0.1
+
+ wrappy@1.0.2: {}
+
+ y18n@5.0.8: {}
+
+ yallist@4.0.0: {}
+
+ yargs-parser@20.2.9: {}
+
+ yargs@16.2.0:
+ dependencies:
+ cliui: 7.0.4
+ escalade: 3.2.0
+ get-caller-file: 2.0.5
+ require-directory: 2.1.1
+ string-width: 4.2.3
+ y18n: 5.0.8
+ yargs-parser: 20.2.9
diff --git a/extensions/src/doodlebot/utils.ts b/extensions/src/doodlebot/utils.ts
new file mode 100644
index 000000000..7b903ed35
--- /dev/null
+++ b/extensions/src/doodlebot/utils.ts
@@ -0,0 +1,38 @@
+/**
+ *
+ * @param input
+ * @example splitArgsString("hello ahoy"); // Output: ["hello", "ahoy"]
+ * @example splitArgsString("hello,ahoy"); // Output: ["hello", "ahoy"]
+ * @example splitArgsString("hello, ahoy"); // Output: ["hello", "ahoy"]
+ * @example splitArgsString("hello"); // Output: ["hello"]
+ * @example splitArgsString(""); // Output: []
+ * @returns
+ */
+export const splitArgsString = (input: string): string[] => {
+ if (!input) return [];
+ // Regular expression to split the string by either comma followed by optional space characters or by space characters alone
+ const regex = /,\s*|\s+/;
+ const words = input.split(regex);
+ return words;
+}
+
+
+export const base64ToInt32Array = async (base64) => {
+ const response = await fetch(`data:application/octet-stream;base64,${base64}`);
+ const blob = await response.blob();
+ const arrayBuffer = await blob.arrayBuffer();
+ return new Int32Array(arrayBuffer);
+}
+
+export const makeWebsocket = (ip: string, port: string | number) => new WebSocket(`ws://${ip}:${port}`);
+
+export const testWebSocket = (ip: string, port: string | number, timeoutSeconds?: number) => {
+ const websocket = makeWebsocket(ip, port);
+ return new Promise((resolve) => {
+ websocket.onopen = () => websocket.close();
+ websocket.onclose = (event) => resolve(event.wasClean);
+ if (timeoutSeconds) setTimeout(() => resolve(false), timeoutSeconds * 1000);
+ });
+}
+
+export const Max32Int = 2147483647;
\ No newline at end of file
diff --git a/package.json b/package.json
index 44fd3ca97..41ad05bd2 100644
--- a/package.json
+++ b/package.json
@@ -33,5 +33,11 @@
"ts-node": {
"typescript": "$typescript"
}
+ },
+ "dependencies": {
+ "@teachablemachine/image": "^0.8.5",
+ "@teachablemachine/pose": "^0.8.6",
+ "@tensorflow-models/speech-commands": "0.4.2",
+ "@tensorflow/tfjs": "1.3.1"
}
}
\ No newline at end of file
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 40e737ba1..208556893 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -7,6 +7,19 @@ settings:
importers:
.:
+ dependencies:
+ '@teachablemachine/image':
+ specifier: ^0.8.5
+ version: 0.8.5(@tensorflow/tfjs@1.3.1(seedrandom@3.0.5))
+ '@teachablemachine/pose':
+ specifier: ^0.8.6
+ version: 0.8.6(@tensorflow/tfjs-converter@3.21.0(@tensorflow/tfjs-core@1.3.1))(@tensorflow/tfjs-core@1.3.1)(@tensorflow/tfjs@1.3.1(seedrandom@3.0.5))
+ '@tensorflow-models/speech-commands':
+ specifier: 0.4.2
+ version: 0.4.2(@tensorflow/tfjs@1.3.1(seedrandom@3.0.5))
+ '@tensorflow/tfjs':
+ specifier: 1.3.1
+ version: 1.3.1(seedrandom@3.0.5)
devDependencies:
'@types/node':
specifier: ^20.12.2
@@ -49,6 +62,55 @@ packages:
'@jridgewell/trace-mapping@0.3.9':
resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==}
+ '@teachablemachine/image@0.8.5':
+ resolution: {integrity: sha512-u6BLQ8RBn38BKee7mmAl+HS9qWgM/2YmUbQYy21eQfqhb7E8xT5LDp7QqEU31rtgEIMDwB/OG8cjmSiuHpqb7A==}
+ peerDependencies:
+ '@tensorflow/tfjs': 1.3.1
+
+ '@teachablemachine/pose@0.8.6':
+ resolution: {integrity: sha512-72UHof2okPdyKlDMSnFcDk/h7dj4I9BBbGsJs64P+B/WQb1rwFcxAeeUFud5rJ2HFwElLDLEjnyaHZljeWV2Bg==}
+ peerDependencies:
+ '@tensorflow/tfjs': 1.3.1
+
+ '@tensorflow-models/posenet@2.2.2':
+ resolution: {integrity: sha512-0SXIksRet/IdX7WVH+JSD6W3upkGHix1hwtd3xykIoIMGR7zQ4SC5+wZcNt9ofASyxNYQoI+tUULUo4LNw0c3w==}
+ peerDependencies:
+ '@tensorflow/tfjs-converter': ^3.0.0-rc.1
+ '@tensorflow/tfjs-core': ^3.0.0-rc.1
+
+ '@tensorflow-models/speech-commands@0.4.2':
+ resolution: {integrity: sha512-XpfRUzVy0DtQkMvR/jqRWCFgcka9pgwEdfqtHTqnIBoueAUJiuq1edD+RNzbK01O3bAJ9FIL/VuiTZmxNx92NQ==}
+ peerDependencies:
+ '@tensorflow/tfjs': ^1.5.2
+
+ '@tensorflow/tfjs-converter@1.3.1':
+ resolution: {integrity: sha512-8IHzcIZwqCofcRBhaCTN6W73m2RGdBDH6BEORf0KDdbgbz4DouItVcKX692PrA8gchY+Xy8ZHMpj93PcAOs17g==}
+ peerDependencies:
+ '@tensorflow/tfjs-core': 1.3.1
+
+ '@tensorflow/tfjs-converter@3.21.0':
+ resolution: {integrity: sha512-12Y4zVDq3yW+wSjSDpSv4HnpL2sDZrNiGSg8XNiDE4HQBdjdA+a+Q3sZF/8NV9y2yoBhL5L7V4mMLDdbZBd9/Q==}
+ peerDependencies:
+ '@tensorflow/tfjs-core': 3.21.0
+
+ '@tensorflow/tfjs-core@1.3.1':
+ resolution: {integrity: sha512-X4MKhpg1gLEZetKUMeQNW6diP3gbFFddeF6UT816sH8jOenX/8x2HnVmANpNnUxCTPhDniY3V9zhBWwbl13+Yg==}
+ engines: {yarn: '>= 1.3.2'}
+
+ '@tensorflow/tfjs-data@1.3.1':
+ resolution: {integrity: sha512-GCATkRXKBewLdri+qfzTLcVsToX9W7fz5JwApY6Ysel/aTtKSYArU8+OppUKAfWkczBA/fZKlJTONnqC42H6bA==}
+ peerDependencies:
+ '@tensorflow/tfjs-core': 1.3.1
+ seedrandom: ~2.4.3
+
+ '@tensorflow/tfjs-layers@1.3.1':
+ resolution: {integrity: sha512-LiC9mGU9ZQ7+c3PiQ6MDkvT/gZgqNN67TsypfKm+L44u2TLtMZf77mRaQ/8rW/F89Ey4PDDFftDRDYv0t3fukw==}
+ peerDependencies:
+ '@tensorflow/tfjs-core': 1.3.1
+
+ '@tensorflow/tfjs@1.3.1':
+ resolution: {integrity: sha512-eJ8+WYRYYx6o3R4daV3T46ZHI/FN0+wslNnobh6d2Sp/T8PfaFpdR38nRrYtxnz33A80B2pMk4VxzoVivS4/jA==}
+
'@tsconfig/node10@1.0.11':
resolution: {integrity: sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==}
@@ -61,9 +123,24 @@ packages:
'@tsconfig/node16@1.0.4':
resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==}
+ '@types/node-fetch@2.6.12':
+ resolution: {integrity: sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==}
+
'@types/node@20.12.12':
resolution: {integrity: sha512-eWLDGF/FOSPtAvEqeRAQ4C8LSA7M1I7i0ky1I8U7kD1J5ITyW3AsRhQrKVoWf5pFKZ2kILsEGJhsI9r93PYnOw==}
+ '@types/offscreencanvas@2019.3.0':
+ resolution: {integrity: sha512-esIJx9bQg+QYF0ra8GnvfianIY8qWB0GBx54PK5Eps6m+xTj86KLavHv6qDhzKcu5UUOgNfJ2pWaIIV7TRUd9Q==}
+
+ '@types/seedrandom@2.4.27':
+ resolution: {integrity: sha512-YvMLqFak/7rt//lPBtEHv3M4sRNA+HGxrhFZ+DQs9K2IkYJbNwVIb8avtJfhDiuaUBX/AW0jnjv48FV8h3u9bQ==}
+
+ '@types/webgl-ext@0.0.30':
+ resolution: {integrity: sha512-LKVgNmBxN0BbljJrVUwkxwRYqzsAEPcZOe6S2T6ZaBDIrFp0qu4FNlpc5sM1tGbXUYFgdVQIoeLk1Y1UoblyEg==}
+
+ '@types/webgl2@0.0.4':
+ resolution: {integrity: sha512-PACt1xdErJbMUOUweSrbVM7gSIYm1vTncW2hF6Os/EeWi6TXYAYMPp+8v6rzHmypE5gHrxaxZNXgMkJVIdZpHw==}
+
'@types/webgl2@0.0.6':
resolution: {integrity: sha512-50GQhDVTq/herLMiqSQkdtRu+d5q/cWHn4VvKJtrj4DJAjo1MNkWYa2MA41BaBO1q1HgsUjuQvEOk0QHvlnAaQ==}
@@ -93,6 +170,13 @@ packages:
arg@4.1.3:
resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==}
+ asynckit@0.4.0:
+ resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
+
+ autobind-decorator@2.4.0:
+ resolution: {integrity: sha512-OGYhWUO72V6DafbF8PM8rm3EPbfuyMZcJhtm5/n26IDwO18pohE4eNazLoCGhPiXOCD0gEGmrbU3849QvM8bbw==}
+ engines: {node: '>=8.10', npm: '>=6.4.1'}
+
cliui@8.0.1:
resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
engines: {node: '>=12'}
@@ -104,9 +188,17 @@ packages:
color-name@1.1.4:
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
+ combined-stream@1.0.8:
+ resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
+ engines: {node: '>= 0.8'}
+
create-require@1.1.1:
resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==}
+ delayed-stream@1.0.0:
+ resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
+ engines: {node: '>=0.4.0'}
+
diff@4.0.2:
resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==}
engines: {node: '>=0.3.1'}
@@ -118,6 +210,10 @@ packages:
resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==}
engines: {node: '>=6'}
+ form-data@4.0.1:
+ resolution: {integrity: sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==}
+ engines: {node: '>= 6'}
+
get-caller-file@2.0.5:
resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
engines: {node: 6.* || 8.* || >= 10.*}
@@ -134,13 +230,34 @@ packages:
make-error@1.3.6:
resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==}
+ mime-db@1.52.0:
+ resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
+ engines: {node: '>= 0.6'}
+
+ mime-types@2.1.35:
+ resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
+ engines: {node: '>= 0.6'}
+
minimist@1.2.8:
resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
+ node-fetch@2.1.2:
+ resolution: {integrity: sha512-IHLHYskTc2arMYsHZH82PVX8CSKT5lzb7AXeyO06QnjGDKtkv+pv3mEki6S7reB/x1QPo+YPxQRNEVgR5V/w3Q==}
+ engines: {node: 4.x || >=6.0.0}
+
require-directory@2.1.1:
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
engines: {node: '>=0.10.0'}
+ seedrandom@2.4.3:
+ resolution: {integrity: sha512-2CkZ9Wn2dS4mMUWQaXLsOAfGD+irMlLEeSP3cMxpGbgyOOzJGFa+MWCOMTOCMyZinHRPxyOj/S/C57li/1to6Q==}
+
+ seedrandom@2.4.4:
+ resolution: {integrity: sha512-9A+PDmgm+2du77B5i0Ip2cxOqqHjgNxnBgglxLcX78A2D6c2rTo61z4jnVABpF4cKeDMDG+cmXXvdnqse2VqMA==}
+
+ seedrandom@3.0.5:
+ resolution: {integrity: sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==}
+
string-width@4.2.3:
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
engines: {node: '>=8'}
@@ -220,6 +337,68 @@ snapshots:
'@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.4.15
+ '@teachablemachine/image@0.8.5(@tensorflow/tfjs@1.3.1(seedrandom@3.0.5))':
+ dependencies:
+ '@tensorflow/tfjs': 1.3.1(seedrandom@3.0.5)
+ autobind-decorator: 2.4.0
+ seedrandom: 2.4.4
+
+ '@teachablemachine/pose@0.8.6(@tensorflow/tfjs-converter@3.21.0(@tensorflow/tfjs-core@1.3.1))(@tensorflow/tfjs-core@1.3.1)(@tensorflow/tfjs@1.3.1(seedrandom@3.0.5))':
+ dependencies:
+ '@tensorflow-models/posenet': 2.2.2(@tensorflow/tfjs-converter@3.21.0(@tensorflow/tfjs-core@1.3.1))(@tensorflow/tfjs-core@1.3.1)
+ '@tensorflow/tfjs': 1.3.1(seedrandom@3.0.5)
+ autobind-decorator: 2.4.0
+ seedrandom: 3.0.5
+ transitivePeerDependencies:
+ - '@tensorflow/tfjs-converter'
+ - '@tensorflow/tfjs-core'
+
+ '@tensorflow-models/posenet@2.2.2(@tensorflow/tfjs-converter@3.21.0(@tensorflow/tfjs-core@1.3.1))(@tensorflow/tfjs-core@1.3.1)':
+ dependencies:
+ '@tensorflow/tfjs-converter': 3.21.0(@tensorflow/tfjs-core@1.3.1)
+ '@tensorflow/tfjs-core': 1.3.1
+
+ '@tensorflow-models/speech-commands@0.4.2(@tensorflow/tfjs@1.3.1(seedrandom@3.0.5))':
+ dependencies:
+ '@tensorflow/tfjs': 1.3.1(seedrandom@3.0.5)
+
+ '@tensorflow/tfjs-converter@1.3.1(@tensorflow/tfjs-core@1.3.1)':
+ dependencies:
+ '@tensorflow/tfjs-core': 1.3.1
+
+ '@tensorflow/tfjs-converter@3.21.0(@tensorflow/tfjs-core@1.3.1)':
+ dependencies:
+ '@tensorflow/tfjs-core': 1.3.1
+
+ '@tensorflow/tfjs-core@1.3.1':
+ dependencies:
+ '@types/offscreencanvas': 2019.3.0
+ '@types/seedrandom': 2.4.27
+ '@types/webgl-ext': 0.0.30
+ '@types/webgl2': 0.0.4
+ node-fetch: 2.1.2
+ seedrandom: 2.4.3
+
+ '@tensorflow/tfjs-data@1.3.1(@tensorflow/tfjs-core@1.3.1)(seedrandom@3.0.5)':
+ dependencies:
+ '@tensorflow/tfjs-core': 1.3.1
+ '@types/node-fetch': 2.6.12
+ node-fetch: 2.1.2
+ seedrandom: 3.0.5
+
+ '@tensorflow/tfjs-layers@1.3.1(@tensorflow/tfjs-core@1.3.1)':
+ dependencies:
+ '@tensorflow/tfjs-core': 1.3.1
+
+ '@tensorflow/tfjs@1.3.1(seedrandom@3.0.5)':
+ dependencies:
+ '@tensorflow/tfjs-converter': 1.3.1(@tensorflow/tfjs-core@1.3.1)
+ '@tensorflow/tfjs-core': 1.3.1
+ '@tensorflow/tfjs-data': 1.3.1(@tensorflow/tfjs-core@1.3.1)(seedrandom@3.0.5)
+ '@tensorflow/tfjs-layers': 1.3.1(@tensorflow/tfjs-core@1.3.1)
+ transitivePeerDependencies:
+ - seedrandom
+
'@tsconfig/node10@1.0.11': {}
'@tsconfig/node12@1.0.11': {}
@@ -228,10 +407,23 @@ snapshots:
'@tsconfig/node16@1.0.4': {}
+ '@types/node-fetch@2.6.12':
+ dependencies:
+ '@types/node': 20.12.12
+ form-data: 4.0.1
+
'@types/node@20.12.12':
dependencies:
undici-types: 5.26.5
+ '@types/offscreencanvas@2019.3.0': {}
+
+ '@types/seedrandom@2.4.27': {}
+
+ '@types/webgl-ext@0.0.30': {}
+
+ '@types/webgl2@0.0.4': {}
+
'@types/webgl2@0.0.6': {}
'@types/yargs-parser@21.0.3': {}
@@ -252,6 +444,10 @@ snapshots:
arg@4.1.3: {}
+ asynckit@0.4.0: {}
+
+ autobind-decorator@2.4.0: {}
+
cliui@8.0.1:
dependencies:
string-width: 4.2.3
@@ -264,14 +460,26 @@ snapshots:
color-name@1.1.4: {}
+ combined-stream@1.0.8:
+ dependencies:
+ delayed-stream: 1.0.0
+
create-require@1.1.1: {}
+ delayed-stream@1.0.0: {}
+
diff@4.0.2: {}
emoji-regex@8.0.0: {}
escalade@3.1.2: {}
+ form-data@4.0.1:
+ dependencies:
+ asynckit: 0.4.0
+ combined-stream: 1.0.8
+ mime-types: 2.1.35
+
get-caller-file@2.0.5: {}
is-fullwidth-code-point@3.0.0: {}
@@ -280,10 +488,24 @@ snapshots:
make-error@1.3.6: {}
+ mime-db@1.52.0: {}
+
+ mime-types@2.1.35:
+ dependencies:
+ mime-db: 1.52.0
+
minimist@1.2.8: {}
+ node-fetch@2.1.2: {}
+
require-directory@2.1.1: {}
+ seedrandom@2.4.3: {}
+
+ seedrandom@2.4.4: {}
+
+ seedrandom@3.0.5: {}
+
string-width@4.2.3:
dependencies:
emoji-regex: 8.0.0