diff --git a/Dockerfile b/Dockerfile index d61d53643d7a68bd34a311caf5a11038122a18a6..3791e42b6485a2b43f7b8033ce4e9303313c92bc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -24,16 +24,21 @@ RUN git clone https://github.com/a16z-infra/ai-town.git . && \ RUN npm install --include=dev @huggingface/inference RUN npm install --include=dev @huggingface/hub + RUN curl -L -O https://github.com/get-convex/convex-backend/releases/download/precompiled-2024-05-07-13337fd/convex-local-backend-x86_64-unknown-linux-gnu.zip && \ unzip convex-local-backend-x86_64-unknown-linux-gnu.zip -COPY ./patches/llm.ts ./convex/util/ +COPY ./patches ./ COPY ./patches/vite.config.ts ./ -COPY ./patches/constants.ts ./patches/music.ts ./convex/ COPY ./patches/characters.ts ./patches/gentle.js ./data/ -COPY ./patches/PixiStaticMap.tsx ./src/components/ -COPY ./patches/Button.tsx ./src/components/buttons/Button.tsx -COPY ./patches/App.tsx ./src/App.tsx COPY ./patches/run.sh ./ +COPY ./map.png ./assets/map.png +COPY ./patches/assets/GrayCat.png ./assets/GrayCat.png +COPY ./patches/assets/OrangeCat.png ./assets/OrangeCat.png +COPY ./patches/assets/hf.svg ./assets/hf.svg +COPY ./patches/data/spritesheets/c1.ts ./data/spritesheets/c1.ts + + + CMD ["./run.sh"] \ No newline at end of file diff --git a/README.md b/README.md index 193cd16e6aa8cb03368edb2f3b58d4a11190f5e6..f2024f17dd199cc13b376aabfddb5f9ea4fa778f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ --- title: AI Town on HuggingFace -emoji: 🏠💻💌🤗 +emoji: 🏠🐈‍⬛ colorFrom: green colorTo: red sdk: docker @@ -9,6 +9,7 @@ pinned: false disable_embedding: true # header: mini short_description: AI Town on HuggingFace +hf_oauth: true --- # AI Town 🏠💻💌 on Hugging Face 🤗 diff --git a/README.md.yml b/README.md.yml index d794565d159e4249824fb7dbd9fb7b3ac659ff3b..3027fb0c94f952c4671ba8a3094445562c80d50b 100644 --- a/README.md.yml +++ b/README.md.yml @@ -8,3 +8,11 @@ pinned: false disable_embedding: true # header: mini short_description: AI Town on HuggingFace +hf_oauth: true +# optional, default duration is 8 hours/480 minutes. Max duration is 30 days/43200 minutes. +hf_oauth_expiration_minutes: 480 +# optional, see "Scopes" below. "openid profile" is always included. +hf_oauth_scopes: + - read-repos + - manage-repos + - inference-api diff --git a/map.png b/map.png new file mode 100644 index 0000000000000000000000000000000000000000..2af844a19132e1eb8fe275b8882ec5d678227897 Binary files /dev/null and b/map.png differ diff --git a/patches/PixiStaticMap.tsx b/patches/PixiStaticMap.tsx deleted file mode 100644 index f23952af2da47a6a3f33c95103d39f667c060583..0000000000000000000000000000000000000000 --- a/patches/PixiStaticMap.tsx +++ /dev/null @@ -1,128 +0,0 @@ -import { PixiComponent, applyDefaultProps } from '@pixi/react'; -import * as PIXI from 'pixi.js'; -import { AnimatedSprite, WorldMap } from '../../convex/aiTown/worldMap'; -import * as campfire from '../../data/animations/campfire.json'; -import * as gentlesparkle from '../../data/animations/gentlesparkle.json'; -import * as gentlewaterfall from '../../data/animations/gentlewaterfall.json'; -import * as gentlesplash from '../../data/animations/gentlesplash.json'; -import * as windmill from '../../data/animations/windmill.json'; - -const animations = { - 'campfire.json': { spritesheet: campfire, url: '/assets/spritesheets/campfire.png' }, - 'gentlesparkle.json': { - spritesheet: gentlesparkle, - url: '/assets/spritesheets/gentlesparkle32.png', - }, - 'gentlewaterfall.json': { - spritesheet: gentlewaterfall, - url: '/assets/spritesheets/gentlewaterfall32.png', - }, - 'windmill.json': { spritesheet: windmill, url: '/assets/spritesheets/windmill.png' }, - 'gentlesplash.json': { - spritesheet: gentlesplash, - url: '/assets/spritesheets/gentlewaterfall32.png', - }, -}; - -export const PixiStaticMap = PixiComponent('StaticMap', { - create: (props: { map: WorldMap; [k: string]: any }) => { - const map = props.map; - const numxtiles = Math.floor(map.tileSetDimX / map.tileDim); - const numytiles = Math.floor(map.tileSetDimY / map.tileDim); - const bt = PIXI.BaseTexture.from(map.tileSetUrl, { - scaleMode: PIXI.SCALE_MODES.NEAREST, - }); - - const tiles = []; - for (let x = 0; x < numxtiles; x++) { - for (let y = 0; y < numytiles; y++) { - tiles[x + y * numxtiles] = new PIXI.Texture( - bt, - new PIXI.Rectangle(x * map.tileDim, y * map.tileDim, map.tileDim, map.tileDim), - ); - } - } - const screenxtiles = map.bgTiles[0].length; - const screenytiles = map.bgTiles[0][0].length; - - const container = new PIXI.Container(); - const allLayers = [...map.bgTiles, ...map.objectTiles]; - - // blit bg & object layers of map onto canvas - for (let i = 0; i < screenxtiles * screenytiles; i++) { - const x = i % screenxtiles; - const y = Math.floor(i / screenxtiles); - const xPx = x * map.tileDim; - const yPx = y * map.tileDim; - - // Add all layers of backgrounds. - for (const layer of allLayers) { - const tileIndex = layer[x][y]; - // Some layers may not have tiles at this location. - if (tileIndex === -1) continue; - const ctile = new PIXI.Sprite(tiles[tileIndex]); - ctile.x = xPx; - ctile.y = yPx; - container.addChild(ctile); - } - } - - // TODO: Add layers. - const spritesBySheet = new Map(); - for (const sprite of map.animatedSprites) { - const sheet = sprite.sheet; - if (!spritesBySheet.has(sheet)) { - spritesBySheet.set(sheet, []); - } - spritesBySheet.get(sheet)!.push(sprite); - } - for (const [sheet, sprites] of spritesBySheet.entries()) { - const animation = (animations as any)[sheet]; - if (!animation) { - console.error('Could not find animation', sheet); - continue; - } - const { spritesheet, url } = animation; - const texture = PIXI.BaseTexture.from(url, { - scaleMode: PIXI.SCALE_MODES.NEAREST, - }); - const spriteSheet = new PIXI.Spritesheet(texture, spritesheet); - spriteSheet.parse().then(() => { - for (const sprite of sprites) { - const pixiAnimation = spriteSheet.animations[sprite.animation]; - if (!pixiAnimation) { - console.error('Failed to load animation', sprite); - continue; - } - const pixiSprite = new PIXI.AnimatedSprite(pixiAnimation); - pixiSprite.animationSpeed = 0.1; - pixiSprite.autoUpdate = true; - pixiSprite.x = sprite.x; - pixiSprite.y = sprite.y; - pixiSprite.width = sprite.w; - pixiSprite.height = sprite.h; - container.addChild(pixiSprite); - pixiSprite.play(); - } - }); - } - - container.x = 0; - container.y = 0; - - // Set the hit area manually to ensure `pointerdown` events are delivered to this container. - container.interactive = true; - container.hitArea = new PIXI.Rectangle( - 0, - 0, - screenxtiles * map.tileDim, - screenytiles * map.tileDim, - ); - - return container; - }, - - applyProps: (instance, oldProps, newProps) => { - applyDefaultProps(instance, oldProps, newProps); - }, -}); diff --git a/patches/assets/GrayCat.png b/patches/assets/GrayCat.png new file mode 100644 index 0000000000000000000000000000000000000000..f9d6a821b8073a4eed83705618553b357714dfe4 Binary files /dev/null and b/patches/assets/GrayCat.png differ diff --git a/patches/assets/OrangeCat.png b/patches/assets/OrangeCat.png new file mode 100644 index 0000000000000000000000000000000000000000..ec2d07c8bf901a0322aeb2beeafb7d1fa3352384 Binary files /dev/null and b/patches/assets/OrangeCat.png differ diff --git a/patches/assets/hf.svg b/patches/assets/hf.svg new file mode 100644 index 0000000000000000000000000000000000000000..34702b1192bbc03f8d042edd3fd9b9b32fb8140b --- /dev/null +++ b/patches/assets/hf.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/patches/assets/map.png b/patches/assets/map.png new file mode 100644 index 0000000000000000000000000000000000000000..d6fc33bb762626d8d0a1565c6f3f592a88334ec8 Binary files /dev/null and b/patches/assets/map.png differ diff --git a/patches/assets/map_night.png b/patches/assets/map_night.png new file mode 100644 index 0000000000000000000000000000000000000000..8a5eea1095e9e732ccbcb32ce418fd1a3fc1fccd Binary files /dev/null and b/patches/assets/map_night.png differ diff --git a/patches/characters.ts b/patches/characters.ts index b9e7ff97ec4fe79df6e46e987b24caca93de9dd6..3394438ff82f81076bd4eef3f21136ea8931ac27 100644 --- a/patches/characters.ts +++ b/patches/characters.ts @@ -6,7 +6,7 @@ import { data as f5SpritesheetData } from './spritesheets/f5'; import { data as f6SpritesheetData } from './spritesheets/f6'; import { data as f7SpritesheetData } from './spritesheets/f7'; import { data as f8SpritesheetData } from './spritesheets/f8'; - +import { data as c1SpritesheetData } from './spritesheets/c1'; export const Descriptions = [ // { // name: 'Alex', @@ -19,7 +19,7 @@ export const Descriptions = [ // }, { name: 'Lucky', - character: 'f1', + character: 'c1', identity: `Lucky is always happy and curious, and he loves cheese. He spends most of his time reading about the history of science and traveling through the galaxy on whatever ship will take him. He's very articulate and @@ -30,7 +30,7 @@ export const Descriptions = [ }, { name: 'Bob', - character: 'f4', + character: 'c2', identity: `Bob is always grumpy and he loves trees. He spends most of his time gardening by himself. When spoken to he'll respond but try and get out of the conversation as quickly as possible. Secretly he resents @@ -39,7 +39,7 @@ export const Descriptions = [ }, { name: 'Stella', - character: 'f6', + character: 'c1', identity: `Stella can never be trusted. she tries to trick people all the time. normally into giving her money, or doing things that will make her money. she's incredibly charming and not afraid to use her charm. she's a sociopath who has no empathy. but hides it well.`, @@ -55,7 +55,7 @@ export const Descriptions = [ // }, { name: 'Alice', - character: 'f3', + character: 'c2', identity: `Alice is a famous scientist. She is smarter than everyone else and has discovered mysteries of the universe no one else can understand. As a result she often speaks in oblique riddles. She comes across as confused and forgetful.`, @@ -85,51 +85,63 @@ export const characters = [ name: 'f1', textureUrl: '/assets/32x32folk.png', spritesheetData: f1SpritesheetData, - speed: 0.1, + speed: 0.19, }, { name: 'f2', textureUrl: '/assets/32x32folk.png', spritesheetData: f2SpritesheetData, - speed: 0.1, + speed: 0.19, }, { name: 'f3', textureUrl: '/assets/32x32folk.png', spritesheetData: f3SpritesheetData, - speed: 0.1, + speed: 0.19, }, { name: 'f4', textureUrl: '/assets/32x32folk.png', spritesheetData: f4SpritesheetData, - speed: 0.1, + speed: 0.19, }, { name: 'f5', textureUrl: '/assets/32x32folk.png', spritesheetData: f5SpritesheetData, - speed: 0.1, + speed: 0.19, }, { name: 'f6', textureUrl: '/assets/32x32folk.png', spritesheetData: f6SpritesheetData, - speed: 0.1, + speed: 0.19, }, { name: 'f7', textureUrl: '/assets/32x32folk.png', spritesheetData: f7SpritesheetData, - speed: 0.1, + speed: 0.19, }, { name: 'f8', textureUrl: '/assets/32x32folk.png', spritesheetData: f8SpritesheetData, - speed: 0.1, + speed: 0.19, + }, + { + name: 'c1', + textureUrl: '/assets/GrayCat.png', + spritesheetData: c1SpritesheetData, + speed: 0.19, + }, + { + name: 'c2', + textureUrl: '/assets/OrangeCat.png', + spritesheetData: c1SpritesheetData, + speed: 0.19, }, ]; // Characters move at 0.75 tiles per second. -export const movementSpeed = 0.75; +export const movementSpeed = 2; diff --git a/patches/convex/agent/conversation.ts b/patches/convex/agent/conversation.ts new file mode 100644 index 0000000000000000000000000000000000000000..1619a12432949aeb7c2426108c3c8090c3abd1ad --- /dev/null +++ b/patches/convex/agent/conversation.ts @@ -0,0 +1,345 @@ +import { v } from 'convex/values'; +import { Id } from '../_generated/dataModel'; +import { ActionCtx, internalQuery } from '../_generated/server'; +import { LLMMessage, chatCompletion } from '../util/llm'; +import * as memory from './memory'; +import { api, internal } from '../_generated/api'; +import * as embeddingsCache from './embeddingsCache'; +import { GameId, conversationId, playerId } from '../aiTown/ids'; +import { NUM_MEMORIES_TO_SEARCH } from '../constants'; + +const selfInternal = internal.agent.conversation; + +export async function startConversationMessage( + ctx: ActionCtx, + worldId: Id<'worlds'>, + conversationId: GameId<'conversations'>, + playerId: GameId<'players'>, + otherPlayerId: GameId<'players'>, +) { + const { player, otherPlayer, agent, otherAgent, lastConversation } = await ctx.runQuery( + selfInternal.queryPromptData, + { + worldId, + playerId, + otherPlayerId, + conversationId, + }, + ); + const embedding = await embeddingsCache.fetch( + ctx, + `${player.name} is talking to ${otherPlayer.name}`, + ); + + const memories = await memory.searchMemories( + ctx, + player.id as GameId<'players'>, + embedding, + Number(process.env.NUM_MEMORIES_TO_SEARCH) || NUM_MEMORIES_TO_SEARCH, + ); + + const memoryWithOtherPlayer = memories.find( + (m) => m.data.type === 'conversation' && m.data.playerIds.includes(otherPlayerId), + ); + const prompt = [ + `You are ${player.name}, and you just started a conversation with ${otherPlayer.name}.`, + ]; + prompt.push(...agentPrompts(otherPlayer, agent, otherAgent ?? null)); + prompt.push(...previousConversationPrompt(otherPlayer, lastConversation)); + prompt.push(...relatedMemoriesPrompt(memories)); + if (memoryWithOtherPlayer) { + prompt.push( + `Be sure to include some detail or question about a previous conversation in your greeting.`, + ); + } + prompt.push(`${player.name}:`); + + const { content } = await chatCompletion({ + messages: [ + { + role: 'user', + content: prompt.join('\n'), + }, + ], + max_tokens: 300, + stream: true, + stop: stopWords(otherPlayer.name, player.name), + }); + return content; +} + +export async function continueConversationMessage( + ctx: ActionCtx, + worldId: Id<'worlds'>, + conversationId: GameId<'conversations'>, + playerId: GameId<'players'>, + otherPlayerId: GameId<'players'>, +) { + const { player, otherPlayer, conversation, agent, otherAgent } = await ctx.runQuery( + selfInternal.queryPromptData, + { + worldId, + playerId, + otherPlayerId, + conversationId, + }, + ); + const now = Date.now(); + const started = new Date(conversation.created); + const embedding = await embeddingsCache.fetch( + ctx, + `What do you think about ${otherPlayer.name}?`, + ); + const memories = await memory.searchMemories(ctx, player.id as GameId<'players'>, embedding, 3); + const prompt = [ + `You are ${player.name}, and you're currently in a conversation with ${otherPlayer.name}.`, + `The conversation started at ${started.toLocaleString()}. It's now ${now.toLocaleString()}.`, + ]; + prompt.push(...agentPrompts(otherPlayer, agent, otherAgent ?? null)); + prompt.push(...relatedMemoriesPrompt(memories)); + prompt.push( + `Below is the current chat history between you and ${otherPlayer.name}.`, + `DO NOT greet them again. Do NOT use the word "Hey" too often. Your response should be brief and within 200 characters.`, + ); + + const llmMessages: LLMMessage[] = [ + { + role: 'user', + content: prompt.join('\n'), + }, + ...(await previousMessages( + ctx, + worldId, + player, + otherPlayer, + conversation.id as GameId<'conversations'>, + )), + ]; + llmMessages.push({ role: 'user', content: `${player.name}:` }); + + const { content } = await chatCompletion({ + messages: llmMessages, + max_tokens: 300, + stream: true, + stop: stopWords(otherPlayer.name, player.name), + }); + return content; +} + +export async function leaveConversationMessage( + ctx: ActionCtx, + worldId: Id<'worlds'>, + conversationId: GameId<'conversations'>, + playerId: GameId<'players'>, + otherPlayerId: GameId<'players'>, +) { + const { player, otherPlayer, conversation, agent, otherAgent } = await ctx.runQuery( + selfInternal.queryPromptData, + { + worldId, + playerId, + otherPlayerId, + conversationId, + }, + ); + const prompt = [ + `You are ${player.name}, and you're currently in a conversation with ${otherPlayer.name}.`, + `You've decided to leave the question and would like to politely tell them you're leaving the conversation.`, + ]; + prompt.push(...agentPrompts(otherPlayer, agent, otherAgent ?? null)); + prompt.push( + `Below is the current chat history between you and ${otherPlayer.name}.`, + `How would you like to tell them that you're leaving? Your response should be brief and within 200 characters.`, + ); + const llmMessages: LLMMessage[] = [ + { + role: 'user', + content: prompt.join('\n'), + }, + ...(await previousMessages( + ctx, + worldId, + player, + otherPlayer, + conversation.id as GameId<'conversations'>, + )), + ]; + llmMessages.push({ role: 'user', content: `${player.name}:` }); + + const { content } = await chatCompletion({ + messages: llmMessages, + max_tokens: 300, + stream: true, + stop: stopWords(otherPlayer.name, player.name), + }); + return content; +} + +function agentPrompts( + otherPlayer: { name: string }, + agent: { identity: string; plan: string } | null, + otherAgent: { identity: string; plan: string } | null, +): string[] { + const prompt = []; + if (agent) { + prompt.push(`About you: ${agent.identity}`); + prompt.push(`Your goals for the conversation: ${agent.plan}`); + } + if (otherAgent) { + prompt.push(`About ${otherPlayer.name}: ${otherAgent.identity}`); + } + return prompt; +} + +function previousConversationPrompt( + otherPlayer: { name: string }, + conversation: { created: number } | null, +): string[] { + const prompt = []; + if (conversation) { + const prev = new Date(conversation.created); + const now = new Date(); + prompt.push( + `Last time you chatted with ${ + otherPlayer.name + } it was ${prev.toLocaleString()}. It's now ${now.toLocaleString()}.`, + ); + } + return prompt; +} + +function relatedMemoriesPrompt(memories: memory.Memory[]): string[] { + const prompt = []; + if (memories.length > 0) { + prompt.push(`Here are some related memories in decreasing relevance order:`); + for (const memory of memories) { + prompt.push(' - ' + memory.description); + } + } + return prompt; +} + +async function previousMessages( + ctx: ActionCtx, + worldId: Id<'worlds'>, + player: { id: string; name: string }, + otherPlayer: { id: string; name: string }, + conversationId: GameId<'conversations'>, +) { + const llmMessages: LLMMessage[] = []; + const prevMessages = await ctx.runQuery(api.messages.listMessages, { worldId, conversationId }); + for (const message of prevMessages) { + const author = message.author === player.id ? player : otherPlayer; + const recipient = message.author === player.id ? otherPlayer : player; + llmMessages.push({ + role: 'user', + content: `${author.name} to ${recipient.name}: ${message.text}`, + }); + } + return llmMessages; +} + +export const queryPromptData = internalQuery({ + args: { + worldId: v.id('worlds'), + playerId, + otherPlayerId: playerId, + conversationId, + }, + handler: async (ctx, args) => { + const world = await ctx.db.get(args.worldId); + if (!world) { + throw new Error(`World ${args.worldId} not found`); + } + const player = world.players.find((p) => p.id === args.playerId); + if (!player) { + throw new Error(`Player ${args.playerId} not found`); + } + const playerDescription = await ctx.db + .query('playerDescriptions') + .withIndex('worldId', (q) => q.eq('worldId', args.worldId).eq('playerId', args.playerId)) + .first(); + if (!playerDescription) { + throw new Error(`Player description for ${args.playerId} not found`); + } + const otherPlayer = world.players.find((p) => p.id === args.otherPlayerId); + if (!otherPlayer) { + throw new Error(`Player ${args.otherPlayerId} not found`); + } + const otherPlayerDescription = await ctx.db + .query('playerDescriptions') + .withIndex('worldId', (q) => q.eq('worldId', args.worldId).eq('playerId', args.otherPlayerId)) + .first(); + if (!otherPlayerDescription) { + throw new Error(`Player description for ${args.otherPlayerId} not found`); + } + const conversation = world.conversations.find((c) => c.id === args.conversationId); + if (!conversation) { + throw new Error(`Conversation ${args.conversationId} not found`); + } + const agent = world.agents.find((a) => a.playerId === args.playerId); + if (!agent) { + throw new Error(`Player ${args.playerId} not found`); + } + const agentDescription = await ctx.db + .query('agentDescriptions') + .withIndex('worldId', (q) => q.eq('worldId', args.worldId).eq('agentId', agent.id)) + .first(); + if (!agentDescription) { + throw new Error(`Agent description for ${agent.id} not found`); + } + const otherAgent = world.agents.find((a) => a.playerId === args.otherPlayerId); + let otherAgentDescription; + if (otherAgent) { + otherAgentDescription = await ctx.db + .query('agentDescriptions') + .withIndex('worldId', (q) => q.eq('worldId', args.worldId).eq('agentId', otherAgent.id)) + .first(); + if (!otherAgentDescription) { + throw new Error(`Agent description for ${otherAgent.id} not found`); + } + } + const lastTogether = await ctx.db + .query('participatedTogether') + .withIndex('edge', (q) => + q + .eq('worldId', args.worldId) + .eq('player1', args.playerId) + .eq('player2', args.otherPlayerId), + ) + // Order by conversation end time descending. + .order('desc') + .first(); + + let lastConversation = null; + if (lastTogether) { + lastConversation = await ctx.db + .query('archivedConversations') + .withIndex('worldId', (q) => + q.eq('worldId', args.worldId).eq('id', lastTogether.conversationId), + ) + .first(); + if (!lastConversation) { + throw new Error(`Conversation ${lastTogether.conversationId} not found`); + } + } + return { + player: { name: playerDescription.name, ...player }, + otherPlayer: { name: otherPlayerDescription.name, ...otherPlayer }, + conversation, + agent: { identity: agentDescription.identity, plan: agentDescription.plan, ...agent }, + otherAgent: otherAgent && { + identity: otherAgentDescription!.identity, + plan: otherAgentDescription!.plan, + ...otherAgent, + }, + lastConversation, + }; + }, +}); + +function stopWords(otherPlayer: string, player: string) { + // These are the words we ask the LLM to stop on. OpenAI only supports 4. + const variants = [`${otherPlayer} to ${player}`]; + return variants.flatMap((stop) => [stop + ':', stop.toLowerCase() + ':']); +} diff --git a/patches/convex/agent/embeddingsCache.ts b/patches/convex/agent/embeddingsCache.ts new file mode 100644 index 0000000000000000000000000000000000000000..1c4583cd0e053a3f4e427f9b8e5bbde886ab7832 --- /dev/null +++ b/patches/convex/agent/embeddingsCache.ts @@ -0,0 +1,110 @@ +import { v } from 'convex/values'; +import { ActionCtx, internalMutation, internalQuery } from '../_generated/server'; +import { internal } from '../_generated/api'; +import { Id } from '../_generated/dataModel'; +import { fetchEmbeddingBatch } from '../util/llm'; + +const selfInternal = internal.agent.embeddingsCache; + +export async function fetch(ctx: ActionCtx, text: string) { + const result = await fetchBatch(ctx, [text]); + return result.embeddings[0]; +} + +export async function fetchBatch(ctx: ActionCtx, texts: string[]) { + const start = Date.now(); + + const textHashes = await Promise.all(texts.map((text) => hashText(text))); + const results = new Array(texts.length); + const cacheResults = await ctx.runQuery(selfInternal.getEmbeddingsByText, { + textHashes, + }); + for (const { index, embedding } of cacheResults) { + results[index] = embedding; + } + const toWrite = []; + if (cacheResults.length < texts.length) { + const missingIndexes = [...results.keys()].filter((i) => !results[i]); + const missingTexts = missingIndexes.map((i) => texts[i]); + const response = await fetchEmbeddingBatch(missingTexts); + if (response.embeddings.length !== missingIndexes.length) { + throw new Error( + `Expected ${missingIndexes.length} embeddings, got ${response.embeddings.length}`, + ); + } + for (let i = 0; i < missingIndexes.length; i++) { + const resultIndex = missingIndexes[i]; + toWrite.push({ + textHash: textHashes[resultIndex], + embedding: response.embeddings[i], + }); + results[resultIndex] = response.embeddings[i]; + } + } + if (toWrite.length > 0) { + await ctx.runMutation(selfInternal.writeEmbeddings, { embeddings: toWrite }); + } + return { + embeddings: results, + hits: cacheResults.length, + ms: Date.now() - start, + }; +} + +async function hashText(text: string) { + const textEncoder = new TextEncoder(); + const buf = textEncoder.encode(text); + if (typeof crypto === 'undefined') { + // Ugly, ugly hax to get ESBuild to not try to bundle this node dependency. + const f = () => 'node:crypto'; + const crypto = (await import(f())) as typeof import('crypto'); + const hash = crypto.createHash('sha256'); + hash.update(buf); + return hash.digest().buffer; + } else { + return await crypto.subtle.digest('SHA-256', buf); + } +} + +export const getEmbeddingsByText = internalQuery({ + args: { textHashes: v.array(v.bytes()) }, + handler: async ( + ctx, + args, + ): Promise<{ index: number; embeddingId: Id<'embeddingsCache'>; embedding: number[] }[]> => { + const out = []; + for (let i = 0; i < args.textHashes.length; i++) { + const textHash = args.textHashes[i]; + const result = await ctx.db + .query('embeddingsCache') + .withIndex('text', (q) => q.eq('textHash', textHash)) + .first(); + if (result) { + out.push({ + index: i, + embeddingId: result._id, + embedding: result.embedding, + }); + } + } + return out; + }, +}); + +export const writeEmbeddings = internalMutation({ + args: { + embeddings: v.array( + v.object({ + textHash: v.bytes(), + embedding: v.array(v.float64()), + }), + ), + }, + handler: async (ctx, args): Promise[]> => { + const ids = []; + for (const embedding of args.embeddings) { + ids.push(await ctx.db.insert('embeddingsCache', embedding)); + } + return ids; + }, +}); diff --git a/patches/convex/agent/memory.ts b/patches/convex/agent/memory.ts new file mode 100644 index 0000000000000000000000000000000000000000..b049d52119ab63a0a14c430525f59f3b4e5ede97 --- /dev/null +++ b/patches/convex/agent/memory.ts @@ -0,0 +1,450 @@ +import { v } from 'convex/values'; +import { ActionCtx, DatabaseReader, internalMutation, internalQuery } from '../_generated/server'; +import { Doc, Id } from '../_generated/dataModel'; +import { internal } from '../_generated/api'; +import { LLMMessage, chatCompletion, fetchEmbedding } from '../util/llm'; +import { asyncMap } from '../util/asyncMap'; +import { GameId, agentId, conversationId, playerId } from '../aiTown/ids'; +import { SerializedPlayer } from '../aiTown/player'; +import { memoryFields } from './schema'; + +// How long to wait before updating a memory's last access time. +export const MEMORY_ACCESS_THROTTLE = 300_000; // In ms +// We fetch 10x the number of memories by relevance, to have more candidates +// for sorting by relevance + recency + importance. +const MEMORY_OVERFETCH = 10; +const selfInternal = internal.agent.memory; + +export type Memory = Doc<'memories'>; +export type MemoryType = Memory['data']['type']; +export type MemoryOfType = Omit & { + data: Extract; +}; + +export async function rememberConversation( + ctx: ActionCtx, + worldId: Id<'worlds'>, + agentId: GameId<'agents'>, + playerId: GameId<'players'>, + conversationId: GameId<'conversations'>, +) { + const data = await ctx.runQuery(selfInternal.loadConversation, { + worldId, + playerId, + conversationId, + }); + const { player, otherPlayer } = data; + const messages = await ctx.runQuery(selfInternal.loadMessages, { worldId, conversationId }); + if (!messages.length) { + return; + } + + const llmMessages: LLMMessage[] = [ + { + role: 'user', + content: `You are ${player.name}, and you just finished a conversation with ${otherPlayer.name}. I would + like you to summarize the conversation from ${player.name}'s perspective, using first-person pronouns like + "I," and add if you liked or disliked this interaction.`, + }, + ]; + const authors = new Set>(); + for (const message of messages) { + const author = message.author === player.id ? player : otherPlayer; + authors.add(author.id as GameId<'players'>); + const recipient = message.author === player.id ? otherPlayer : player; + llmMessages.push({ + role: 'user', + content: `${author.name} to ${recipient.name}: ${message.text}`, + }); + } + llmMessages.push({ role: 'user', content: 'Summary:' }); + const { content } = await chatCompletion({ + messages: llmMessages, + max_tokens: 500, + }); + const description = `Conversation with ${otherPlayer.name} at ${new Date( + data.conversation._creationTime, + ).toLocaleString()}: ${content}`; + const importance = await calculateImportance(description); + const { embedding } = await fetchEmbedding(description); + authors.delete(player.id as GameId<'players'>); + await ctx.runMutation(selfInternal.insertMemory, { + agentId, + playerId: player.id, + description, + importance, + lastAccess: messages[messages.length - 1]._creationTime, + data: { + type: 'conversation', + conversationId, + playerIds: [...authors], + }, + embedding, + }); + await reflectOnMemories(ctx, worldId, playerId); + return description; +} + +export const loadConversation = internalQuery({ + args: { + worldId: v.id('worlds'), + playerId, + conversationId, + }, + handler: async (ctx, args) => { + const world = await ctx.db.get(args.worldId); + if (!world) { + throw new Error(`World ${args.worldId} not found`); + } + const player = world.players.find((p) => p.id === args.playerId); + if (!player) { + throw new Error(`Player ${args.playerId} not found`); + } + const playerDescription = await ctx.db + .query('playerDescriptions') + .withIndex('worldId', (q) => q.eq('worldId', args.worldId).eq('playerId', args.playerId)) + .first(); + if (!playerDescription) { + throw new Error(`Player description for ${args.playerId} not found`); + } + const conversation = await ctx.db + .query('archivedConversations') + .withIndex('worldId', (q) => q.eq('worldId', args.worldId).eq('id', args.conversationId)) + .first(); + if (!conversation) { + throw new Error(`Conversation ${args.conversationId} not found`); + } + const otherParticipator = await ctx.db + .query('participatedTogether') + .withIndex('conversation', (q) => + q + .eq('worldId', args.worldId) + .eq('player1', args.playerId) + .eq('conversationId', args.conversationId), + ) + .first(); + if (!otherParticipator) { + throw new Error( + `Couldn't find other participant in conversation ${args.conversationId} with player ${args.playerId}`, + ); + } + const otherPlayerId = otherParticipator.player2; + let otherPlayer: SerializedPlayer | Doc<'archivedPlayers'> | null = + world.players.find((p) => p.id === otherPlayerId) ?? null; + if (!otherPlayer) { + otherPlayer = await ctx.db + .query('archivedPlayers') + .withIndex('worldId', (q) => q.eq('worldId', world._id).eq('id', otherPlayerId)) + .first(); + } + if (!otherPlayer) { + throw new Error(`Conversation ${args.conversationId} other player not found`); + } + const otherPlayerDescription = await ctx.db + .query('playerDescriptions') + .withIndex('worldId', (q) => q.eq('worldId', args.worldId).eq('playerId', otherPlayerId)) + .first(); + if (!otherPlayerDescription) { + throw new Error(`Player description for ${otherPlayerId} not found`); + } + return { + player: { ...player, name: playerDescription.name }, + conversation, + otherPlayer: { ...otherPlayer, name: otherPlayerDescription.name }, + }; + }, +}); + +export async function searchMemories( + ctx: ActionCtx, + playerId: GameId<'players'>, + searchEmbedding: number[], + n: number = 3, +) { + const candidates = await ctx.vectorSearch('memoryEmbeddings', 'embedding', { + vector: searchEmbedding, + filter: (q) => q.eq('playerId', playerId), + limit: n * MEMORY_OVERFETCH, + }); + const rankedMemories = await ctx.runMutation(selfInternal.rankAndTouchMemories, { + candidates, + n, + }); + return rankedMemories.map(({ memory }) => memory); +} + +function makeRange(values: number[]) { + const min = Math.min(...values); + const max = Math.max(...values); + return [min, max] as const; +} + +function normalize(value: number, range: readonly [number, number]) { + const [min, max] = range; + return (value - min) / (max - min); +} + +export const rankAndTouchMemories = internalMutation({ + args: { + candidates: v.array(v.object({ _id: v.id('memoryEmbeddings'), _score: v.number() })), + n: v.number(), + }, + handler: async (ctx, args) => { + const ts = Date.now(); + const relatedMemories = await asyncMap(args.candidates, async ({ _id }) => { + const memory = await ctx.db + .query('memories') + .withIndex('embeddingId', (q) => q.eq('embeddingId', _id)) + .first(); + if (!memory) throw new Error(`Memory for embedding ${_id} not found`); + return memory; + }); + + // TODO: fetch recent memories and important memories + // so we don't miss them in case they were a little less relevant. + const recencyScore = relatedMemories.map((memory) => { + const hoursSinceAccess = (ts - memory.lastAccess) / 1000 / 60 / 60; + return 0.99 ** Math.floor(hoursSinceAccess); + }); + const relevanceRange = makeRange(args.candidates.map((c) => c._score)); + const importanceRange = makeRange(relatedMemories.map((m) => m.importance)); + const recencyRange = makeRange(recencyScore); + const memoryScores = relatedMemories.map((memory, idx) => ({ + memory, + overallScore: + normalize(args.candidates[idx]._score, relevanceRange) + + normalize(memory.importance, importanceRange) + + normalize(recencyScore[idx], recencyRange), + })); + memoryScores.sort((a, b) => b.overallScore - a.overallScore); + const accessed = memoryScores.slice(0, args.n); + await asyncMap(accessed, async ({ memory }) => { + if (memory.lastAccess < ts - MEMORY_ACCESS_THROTTLE) { + await ctx.db.patch(memory._id, { lastAccess: ts }); + } + }); + return accessed; + }, +}); + +export const loadMessages = internalQuery({ + args: { + worldId: v.id('worlds'), + conversationId, + }, + handler: async (ctx, args): Promise[]> => { + const messages = await ctx.db + .query('messages') + .withIndex('conversationId', (q) => + q.eq('worldId', args.worldId).eq('conversationId', args.conversationId), + ) + .collect(); + return messages; + }, +}); + +async function calculateImportance(description: string) { + const { content: importanceRaw } = await chatCompletion({ + messages: [ + { + role: 'user', + content: `On the scale of 0 to 9, where 0 is purely mundane (e.g., brushing teeth, making bed) and 9 is extremely poignant (e.g., a break up, college acceptance), rate the likely poignancy of the following piece of memory. + Memory: ${description} + Answer on a scale of 0 to 9. Respond with number only, e.g. "5"`, + }, + ], + temperature: 0.0, + max_tokens: 1, + }); + + let importance = parseFloat(importanceRaw); + if (isNaN(importance)) { + importance = +(importanceRaw.match(/\d+/)?.[0] ?? NaN); + } + if (isNaN(importance)) { + console.debug('Could not parse memory importance from: ', importanceRaw); + importance = 5; + } + return importance; +} + +const { embeddingId: _embeddingId, ...memoryFieldsWithoutEmbeddingId } = memoryFields; + +export const insertMemory = internalMutation({ + args: { + agentId, + embedding: v.array(v.float64()), + ...memoryFieldsWithoutEmbeddingId, + }, + handler: async (ctx, { agentId: _, embedding, ...memory }): Promise => { + const embeddingId = await ctx.db.insert('memoryEmbeddings', { + playerId: memory.playerId, + embedding, + }); + await ctx.db.insert('memories', { + ...memory, + embeddingId, + }); + }, +}); + +export const insertReflectionMemories = internalMutation({ + args: { + worldId: v.id('worlds'), + playerId, + reflections: v.array( + v.object({ + description: v.string(), + relatedMemoryIds: v.array(v.id('memories')), + importance: v.number(), + embedding: v.array(v.float64()), + }), + ), + }, + handler: async (ctx, { playerId, reflections }) => { + const lastAccess = Date.now(); + for (const { embedding, relatedMemoryIds, ...rest } of reflections) { + const embeddingId = await ctx.db.insert('memoryEmbeddings', { + playerId, + embedding, + }); + await ctx.db.insert('memories', { + playerId, + embeddingId, + lastAccess, + ...rest, + data: { + type: 'reflection', + relatedMemoryIds, + }, + }); + } + }, +}); + +async function reflectOnMemories( + ctx: ActionCtx, + worldId: Id<'worlds'>, + playerId: GameId<'players'>, +) { + const { memories, lastReflectionTs, name } = await ctx.runQuery( + internal.agent.memory.getReflectionMemories, + { + worldId, + playerId, + numberOfItems: 100, + }, + ); + + // should only reflect if lastest 100 items have importance score of >500 + const sumOfImportanceScore = memories + .filter((m) => m._creationTime > (lastReflectionTs ?? 0)) + .reduce((acc, curr) => acc + curr.importance, 0); + const shouldReflect = sumOfImportanceScore > 500; + + if (!shouldReflect) { + return false; + } + console.debug('sum of importance score = ', sumOfImportanceScore); + console.debug('Reflecting...'); + const prompt = ['[no prose]', '[Output only JSON]', `You are ${name}, statements about you:`]; + memories.forEach((m, idx) => { + prompt.push(`Statement ${idx}: ${m.description}`); + }); + prompt.push('What 3 high-level insights can you infer from the above statements?'); + prompt.push( + 'Return in JSON format, where the key is a list of input statements that contributed to your insights and value is your insight. Make the response parseable by Typescript JSON.parse() function. DO NOT escape characters or include "\n" or white space in response.', + ); + prompt.push( + 'Example: [{insight: "...", statementIds: [1,2]}, {insight: "...", statementIds: [1]}, ...]', + ); + + const { content: reflection } = await chatCompletion({ + messages: [ + { + role: 'user', + content: prompt.join('\n'), + }, + ], + }); + + try { + const insights = JSON.parse(reflection) as { insight: string; statementIds: number[] }[]; + const memoriesToSave = await asyncMap(insights, async (item) => { + const relatedMemoryIds = item.statementIds.map((idx: number) => memories[idx]._id); + const importance = await calculateImportance(item.insight); + const { embedding } = await fetchEmbedding(item.insight); + console.debug('adding reflection memory...', item.insight); + return { + description: item.insight, + embedding, + importance, + relatedMemoryIds, + }; + }); + + await ctx.runMutation(selfInternal.insertReflectionMemories, { + worldId, + playerId, + reflections: memoriesToSave, + }); + } catch (e) { + console.error('error saving or parsing reflection', e); + console.debug('reflection', reflection); + return false; + } + return true; +} +export const getReflectionMemories = internalQuery({ + args: { worldId: v.id('worlds'), playerId, numberOfItems: v.number() }, + handler: async (ctx, args) => { + const world = await ctx.db.get(args.worldId); + if (!world) { + throw new Error(`World ${args.worldId} not found`); + } + const player = world.players.find((p) => p.id === args.playerId); + if (!player) { + throw new Error(`Player ${args.playerId} not found`); + } + const playerDescription = await ctx.db + .query('playerDescriptions') + .withIndex('worldId', (q) => q.eq('worldId', args.worldId).eq('playerId', args.playerId)) + .first(); + if (!playerDescription) { + throw new Error(`Player description for ${args.playerId} not found`); + } + const memories = await ctx.db + .query('memories') + .withIndex('playerId', (q) => q.eq('playerId', player.id)) + .order('desc') + .take(args.numberOfItems); + + const lastReflection = await ctx.db + .query('memories') + .withIndex('playerId_type', (q) => + q.eq('playerId', args.playerId).eq('data.type', 'reflection'), + ) + .order('desc') + .first(); + + return { + name: playerDescription.name, + memories, + lastReflectionTs: lastReflection?._creationTime, + }; + }, +}); + +export async function latestMemoryOfType( + db: DatabaseReader, + playerId: GameId<'players'>, + type: T, +) { + const entry = await db + .query('memories') + .withIndex('playerId_type', (q) => q.eq('playerId', playerId).eq('data.type', type)) + .order('desc') + .first(); + if (!entry) return null; + return entry as MemoryOfType; +} diff --git a/patches/convex/agent/schema.ts b/patches/convex/agent/schema.ts new file mode 100644 index 0000000000000000000000000000000000000000..be822bcee1d5bfe78c37661d85ef37958617adcb --- /dev/null +++ b/patches/convex/agent/schema.ts @@ -0,0 +1,53 @@ +import { v } from 'convex/values'; +import { playerId, conversationId } from '../aiTown/ids'; +import { defineTable } from 'convex/server'; +import { LLM_CONFIG } from '../util/llm'; + +export const memoryFields = { + playerId, + description: v.string(), + embeddingId: v.id('memoryEmbeddings'), + importance: v.number(), + lastAccess: v.number(), + data: v.union( + // Setting up dynamics between players + v.object({ + type: v.literal('relationship'), + // The player this memory is about, from the perspective of the player + // whose memory this is. + playerId, + }), + v.object({ + type: v.literal('conversation'), + conversationId, + // The other player(s) in the conversation. + playerIds: v.array(playerId), + }), + v.object({ + type: v.literal('reflection'), + relatedMemoryIds: v.array(v.id('memories')), + }), + ), +}; +export const memoryTables = { + memories: defineTable(memoryFields) + .index('embeddingId', ['embeddingId']) + .index('playerId_type', ['playerId', 'data.type']) + .index('playerId', ['playerId']), + memoryEmbeddings: defineTable({ + playerId, + embedding: v.array(v.float64()), + }).vectorIndex('embedding', { + vectorField: 'embedding', + filterFields: ['playerId'], + dimensions: LLM_CONFIG.embeddingDimension, + }), +}; + +export const agentTables = { + ...memoryTables, + embeddingsCache: defineTable({ + textHash: v.bytes(), + embedding: v.array(v.float64()), + }).index('text', ['textHash']), +}; diff --git a/patches/convex/aiTown/agent.ts b/patches/convex/aiTown/agent.ts new file mode 100644 index 0000000000000000000000000000000000000000..a32022ce78a5326759ce483332b14d828c49b5c6 --- /dev/null +++ b/patches/convex/aiTown/agent.ts @@ -0,0 +1,368 @@ +import { ObjectType, v } from 'convex/values'; +import { GameId, parseGameId } from './ids'; +import { agentId, conversationId, playerId } from './ids'; +import { serializedPlayer } from './player'; +import { Game } from './game'; +import { + ACTION_TIMEOUT, + AWKWARD_CONVERSATION_TIMEOUT, + CONVERSATION_COOLDOWN, + CONVERSATION_DISTANCE, + INVITE_ACCEPT_PROBABILITY, + INVITE_TIMEOUT, + MAX_CONVERSATION_DURATION, + MAX_CONVERSATION_MESSAGES, + MESSAGE_COOLDOWN, + MIDPOINT_THRESHOLD, + PLAYER_CONVERSATION_COOLDOWN, +} from '../constants'; +import { FunctionArgs } from 'convex/server'; +import { MutationCtx, internalMutation, internalQuery } from '../_generated/server'; +import { distance } from '../util/geometry'; +import { internal } from '../_generated/api'; +import { movePlayer } from './movement'; +import { insertInput } from './insertInput'; + +export class Agent { + id: GameId<'agents'>; + playerId: GameId<'players'>; + toRemember?: GameId<'conversations'>; + lastConversation?: number; + lastInviteAttempt?: number; + inProgressOperation?: { + name: string; + operationId: string; + started: number; + }; + + constructor(serialized: SerializedAgent) { + const { id, lastConversation, lastInviteAttempt, inProgressOperation } = serialized; + const playerId = parseGameId('players', serialized.playerId); + this.id = parseGameId('agents', id); + this.playerId = playerId; + this.toRemember = + serialized.toRemember !== undefined + ? parseGameId('conversations', serialized.toRemember) + : undefined; + this.lastConversation = lastConversation; + this.lastInviteAttempt = lastInviteAttempt; + this.inProgressOperation = inProgressOperation; + } + + tick(game: Game, now: number) { + const player = game.world.players.get(this.playerId); + if (!player) { + throw new Error(`Invalid player ID ${this.playerId}`); + } + if (this.inProgressOperation) { + if (now < this.inProgressOperation.started + ACTION_TIMEOUT) { + // Wait on the operation to finish. + return; + } + console.log(`Timing out ${JSON.stringify(this.inProgressOperation)}`); + delete this.inProgressOperation; + } + const conversation = game.world.playerConversation(player); + const member = conversation?.participants.get(player.id); + + const recentlyAttemptedInvite = + this.lastInviteAttempt && now < this.lastInviteAttempt + CONVERSATION_COOLDOWN; + const doingActivity = player.activity && player.activity.until > now; + if (doingActivity && (conversation || player.pathfinding)) { + player.activity!.until = now; + } + // If we're not in a conversation, do something. + // If we aren't doing an activity or moving, do something. + // If we have been wandering but haven't thought about something to do for + // a while, do something. + if (!conversation && !doingActivity && (!player.pathfinding || !recentlyAttemptedInvite)) { + this.startOperation(game, now, 'agentDoSomething', { + worldId: game.worldId, + player: player.serialize(), + otherFreePlayers: [...game.world.players.values()] + .filter((p) => p.id !== player.id) + .filter( + (p) => ![...game.world.conversations.values()].find((c) => c.participants.has(p.id)), + ) + .map((p) => p.serialize()), + agent: this.serialize(), + map: game.worldMap.serialize(), + }); + return; + } + // Check to see if we have a conversation we need to remember. + if (this.toRemember) { + // Fire off the action to remember the conversation. + console.log(`Agent ${this.id} remembering conversation ${this.toRemember}`); + this.startOperation(game, now, 'agentRememberConversation', { + worldId: game.worldId, + playerId: this.playerId, + agentId: this.id, + conversationId: this.toRemember, + }); + delete this.toRemember; + return; + } + if (conversation && member) { + const [otherPlayerId, otherMember] = [...conversation.participants.entries()].find( + ([id]) => id !== player.id, + )!; + const otherPlayer = game.world.players.get(otherPlayerId)!; + if (member.status.kind === 'invited') { + // Accept a conversation with another agent with some probability and with + // a human unconditionally. + if (otherPlayer.human || Math.random() < INVITE_ACCEPT_PROBABILITY) { + console.log(`Agent ${player.id} accepting invite from ${otherPlayer.id}`); + conversation.acceptInvite(game, player); + // Stop moving so we can start walking towards the other player. + if (player.pathfinding) { + delete player.pathfinding; + } + } else { + console.log(`Agent ${player.id} rejecting invite from ${otherPlayer.id}`); + conversation.rejectInvite(game, now, player); + } + return; + } + if (member.status.kind === 'walkingOver') { + // Leave a conversation if we've been waiting for too long. + if (member.invited + INVITE_TIMEOUT < now) { + console.log(`Giving up on invite to ${otherPlayer.id}`); + conversation.leave(game, now, player); + return; + } + + // Don't keep moving around if we're near enough. + const playerDistance = distance(player.position, otherPlayer.position); + if (playerDistance < CONVERSATION_DISTANCE) { + return; + } + + // Keep moving towards the other player. + // If we're close enough to the player, just walk to them directly. + if (!player.pathfinding) { + let destination; + if (playerDistance < MIDPOINT_THRESHOLD) { + destination = { + x: Math.floor(otherPlayer.position.x), + y: Math.floor(otherPlayer.position.y), + }; + } else { + destination = { + x: Math.floor((player.position.x + otherPlayer.position.x) / 2), + y: Math.floor((player.position.y + otherPlayer.position.y) / 2), + }; + } + console.log(`Agent ${player.id} walking towards ${otherPlayer.id}...`, destination); + movePlayer(game, now, player, destination); + } + return; + } + if (member.status.kind === 'participating') { + const started = member.status.started; + if (conversation.isTyping && conversation.isTyping.playerId !== player.id) { + // Wait for the other player to finish typing. + return; + } + if (!conversation.lastMessage) { + const isInitiator = conversation.creator === player.id; + const awkwardDeadline = started + AWKWARD_CONVERSATION_TIMEOUT; + // Send the first message if we're the initiator or if we've been waiting for too long. + if (isInitiator || awkwardDeadline < now) { + // Grab the lock on the conversation and send a "start" message. + console.log(`${player.id} initiating conversation with ${otherPlayer.id}.`); + const messageUuid = crypto.randomUUID(); + conversation.setIsTyping(now, player, messageUuid); + this.startOperation(game, now, 'agentGenerateMessage', { + worldId: game.worldId, + playerId: player.id, + agentId: this.id, + conversationId: conversation.id, + otherPlayerId: otherPlayer.id, + messageUuid, + type: 'start', + }); + return; + } else { + // Wait on the other player to say something up to the awkward deadline. + return; + } + } + // See if the conversation has been going on too long and decide to leave. + const tooLongDeadline = started + MAX_CONVERSATION_DURATION; + if (tooLongDeadline < now || conversation.numMessages > MAX_CONVERSATION_MESSAGES) { + console.log(`${player.id} leaving conversation with ${otherPlayer.id}.`); + const messageUuid = crypto.randomUUID(); + conversation.setIsTyping(now, player, messageUuid); + this.startOperation(game, now, 'agentGenerateMessage', { + worldId: game.worldId, + playerId: player.id, + agentId: this.id, + conversationId: conversation.id, + otherPlayerId: otherPlayer.id, + messageUuid, + type: 'leave', + }); + return; + } + // Wait for the awkward deadline if we sent the last message. + if (conversation.lastMessage.author === player.id) { + const awkwardDeadline = conversation.lastMessage.timestamp + AWKWARD_CONVERSATION_TIMEOUT; + if (now < awkwardDeadline) { + return; + } + } + // Wait for a cooldown after the last message to simulate "reading" the message. + const messageCooldown = conversation.lastMessage.timestamp + MESSAGE_COOLDOWN; + if (now < messageCooldown) { + return; + } + // Grab the lock and send a message! + console.log(`${player.id} continuing conversation with ${otherPlayer.id}.`); + const messageUuid = crypto.randomUUID(); + conversation.setIsTyping(now, player, messageUuid); + this.startOperation(game, now, 'agentGenerateMessage', { + worldId: game.worldId, + playerId: player.id, + agentId: this.id, + conversationId: conversation.id, + otherPlayerId: otherPlayer.id, + messageUuid, + type: 'continue', + }); + return; + } + } + } + + startOperation( + game: Game, + now: number, + name: Name, + args: Omit, 'operationId'>, + ) { + if (this.inProgressOperation) { + throw new Error( + `Agent ${this.id} already has an operation: ${JSON.stringify(this.inProgressOperation)}`, + ); + } + const operationId = game.allocId('operations'); + console.log(`Agent ${this.id} starting operation ${name} (${operationId})`); + game.scheduleOperation(name, { operationId, ...args } as any); + this.inProgressOperation = { + name, + operationId, + started: now, + }; + } + + serialize(): SerializedAgent { + return { + id: this.id, + playerId: this.playerId, + toRemember: this.toRemember, + lastConversation: this.lastConversation, + lastInviteAttempt: this.lastInviteAttempt, + inProgressOperation: this.inProgressOperation, + }; + } +} + +export const serializedAgent = { + id: agentId, + playerId: playerId, + toRemember: v.optional(conversationId), + lastConversation: v.optional(v.number()), + lastInviteAttempt: v.optional(v.number()), + inProgressOperation: v.optional( + v.object({ + name: v.string(), + operationId: v.string(), + started: v.number(), + }), + ), +}; +export type SerializedAgent = ObjectType; + +type AgentOperations = typeof internal.aiTown.agentOperations; + +export async function runAgentOperation(ctx: MutationCtx, operation: string, args: any) { + let reference; + switch (operation) { + case 'agentRememberConversation': + reference = internal.aiTown.agentOperations.agentRememberConversation; + break; + case 'agentGenerateMessage': + reference = internal.aiTown.agentOperations.agentGenerateMessage; + break; + case 'agentDoSomething': + reference = internal.aiTown.agentOperations.agentDoSomething; + break; + default: + throw new Error(`Unknown operation: ${operation}`); + } + await ctx.scheduler.runAfter(0, reference, args); +} + +export const agentSendMessage = internalMutation({ + args: { + worldId: v.id('worlds'), + conversationId, + agentId, + playerId, + text: v.string(), + messageUuid: v.string(), + leaveConversation: v.boolean(), + operationId: v.string(), + }, + handler: async (ctx, args) => { + await ctx.db.insert('messages', { + conversationId: args.conversationId, + author: args.playerId, + text: args.text, + messageUuid: args.messageUuid, + worldId: args.worldId, + }); + await insertInput(ctx, args.worldId, 'agentFinishSendingMessage', { + conversationId: args.conversationId, + agentId: args.agentId, + timestamp: Date.now(), + leaveConversation: args.leaveConversation, + operationId: args.operationId, + }); + }, +}); + +export const findConversationCandidate = internalQuery({ + args: { + now: v.number(), + worldId: v.id('worlds'), + player: v.object(serializedPlayer), + otherFreePlayers: v.array(v.object(serializedPlayer)), + }, + handler: async (ctx, { now, worldId, player, otherFreePlayers }) => { + const { position } = player; + const candidates = []; + + for (const otherPlayer of otherFreePlayers) { + // Find the latest conversation we're both members of. + const lastMember = await ctx.db + .query('participatedTogether') + .withIndex('edge', (q) => + q.eq('worldId', worldId).eq('player1', player.id).eq('player2', otherPlayer.id), + ) + .order('desc') + .first(); + if (lastMember) { + if (now < lastMember.ended + PLAYER_CONVERSATION_COOLDOWN) { + continue; + } + } + candidates.push({ id: otherPlayer.id, position }); + } + + // Sort by distance and take the nearest candidate. + candidates.sort((a, b) => distance(a.position, position) - distance(b.position, position)); + return candidates[0]?.id; + }, +}); diff --git a/patches/convex/aiTown/agentDescription.ts b/patches/convex/aiTown/agentDescription.ts new file mode 100644 index 0000000000000000000000000000000000000000..7205d798412332dfc1c8a6ede071ad15ed86dcfd --- /dev/null +++ b/patches/convex/aiTown/agentDescription.ts @@ -0,0 +1,27 @@ +import { ObjectType, v } from 'convex/values'; +import { GameId, agentId, parseGameId } from './ids'; + +export class AgentDescription { + agentId: GameId<'agents'>; + identity: string; + plan: string; + + constructor(serialized: SerializedAgentDescription) { + const { agentId, identity, plan } = serialized; + this.agentId = parseGameId('agents', agentId); + this.identity = identity; + this.plan = plan; + } + + serialize(): SerializedAgentDescription { + const { agentId, identity, plan } = this; + return { agentId, identity, plan }; + } +} + +export const serializedAgentDescription = { + agentId, + identity: v.string(), + plan: v.string(), +}; +export type SerializedAgentDescription = ObjectType; diff --git a/patches/convex/aiTown/agentInputs.ts b/patches/convex/aiTown/agentInputs.ts new file mode 100644 index 0000000000000000000000000000000000000000..e7e61462f380638dba784296dccc88d9cd047af4 --- /dev/null +++ b/patches/convex/aiTown/agentInputs.ts @@ -0,0 +1,155 @@ +import { v } from 'convex/values'; +import { agentId, conversationId, parseGameId } from './ids'; +import { Player, activity } from './player'; +import { Conversation, conversationInputs } from './conversation'; +import { movePlayer } from './movement'; +import { inputHandler } from './inputHandler'; +import { point } from '../util/types'; +import { Descriptions } from '../../data/characters'; +import { AgentDescription } from './agentDescription'; +import { Agent } from './agent'; + +export const agentInputs = { + finishRememberConversation: inputHandler({ + args: { + operationId: v.string(), + agentId, + }, + handler: (game, now, args) => { + const agentId = parseGameId('agents', args.agentId); + const agent = game.world.agents.get(agentId); + if (!agent) { + throw new Error(`Couldn't find agent: ${agentId}`); + } + if ( + !agent.inProgressOperation || + agent.inProgressOperation.operationId !== args.operationId + ) { + console.debug(`Agent ${agentId} isn't remembering ${args.operationId}`); + } else { + delete agent.inProgressOperation; + delete agent.toRemember; + } + return null; + }, + }), + finishDoSomething: inputHandler({ + args: { + operationId: v.string(), + agentId: v.id('agents'), + destination: v.optional(point), + invitee: v.optional(v.id('players')), + activity: v.optional(activity), + }, + handler: (game, now, args) => { + const agentId = parseGameId('agents', args.agentId); + const agent = game.world.agents.get(agentId); + if (!agent) { + throw new Error(`Couldn't find agent: ${agentId}`); + } + if ( + !agent.inProgressOperation || + agent.inProgressOperation.operationId !== args.operationId + ) { + console.debug(`Agent ${agentId} didn't have ${args.operationId} in progress`); + return null; + } + delete agent.inProgressOperation; + const player = game.world.players.get(agent.playerId)!; + if (args.invitee) { + const inviteeId = parseGameId('players', args.invitee); + const invitee = game.world.players.get(inviteeId); + if (!invitee) { + throw new Error(`Couldn't find player: ${inviteeId}`); + } + Conversation.start(game, now, player, invitee); + agent.lastInviteAttempt = now; + } + if (args.destination) { + movePlayer(game, now, player, args.destination); + } + if (args.activity) { + player.activity = args.activity; + } + return null; + }, + }), + agentFinishSendingMessage: inputHandler({ + args: { + agentId, + conversationId, + timestamp: v.number(), + operationId: v.string(), + leaveConversation: v.boolean(), + }, + handler: (game, now, args) => { + const agentId = parseGameId('agents', args.agentId); + const agent = game.world.agents.get(agentId); + if (!agent) { + throw new Error(`Couldn't find agent: ${agentId}`); + } + const player = game.world.players.get(agent.playerId); + if (!player) { + throw new Error(`Couldn't find player: ${agent.playerId}`); + } + const conversationId = parseGameId('conversations', args.conversationId); + const conversation = game.world.conversations.get(conversationId); + if (!conversation) { + throw new Error(`Couldn't find conversation: ${conversationId}`); + } + if ( + !agent.inProgressOperation || + agent.inProgressOperation.operationId !== args.operationId + ) { + console.debug(`Agent ${agentId} wasn't sending a message ${args.operationId}`); + return null; + } + delete agent.inProgressOperation; + conversationInputs.finishSendingMessage.handler(game, now, { + playerId: agent.playerId, + conversationId: args.conversationId, + timestamp: args.timestamp, + }); + if (args.leaveConversation) { + conversation.leave(game, now, player); + } + return null; + }, + }), + createAgent: inputHandler({ + args: { + descriptionIndex: v.number(), + }, + handler: (game, now, args) => { + const description = Descriptions[args.descriptionIndex]; + const playerId = Player.join( + game, + now, + description.name, + description.character, + description.identity, + ); + const agentId = game.allocId('agents'); + game.world.agents.set( + agentId, + new Agent({ + id: agentId, + playerId: playerId, + inProgressOperation: undefined, + lastConversation: undefined, + lastInviteAttempt: undefined, + toRemember: undefined, + }), + ); + game.agentDescriptions.set( + agentId, + new AgentDescription({ + agentId: agentId, + identity: description.identity, + plan: description.plan, + }), + ); + return { agentId }; + }, + }), +}; diff --git a/patches/convex/aiTown/agentOperations.ts b/patches/convex/aiTown/agentOperations.ts new file mode 100644 index 0000000000000000000000000000000000000000..6c1396eb3494b21984b2a9ae0d94e78c3bd5238e --- /dev/null +++ b/patches/convex/aiTown/agentOperations.ts @@ -0,0 +1,182 @@ +'use node'; + +import { v } from 'convex/values'; +import { internalAction } from '../_generated/server'; +import { WorldMap, serializedWorldMap } from './worldMap'; +import { rememberConversation } from '../agent/memory'; +import { GameId, agentId, conversationId, playerId } from './ids'; +import { + continueConversationMessage, + leaveConversationMessage, + startConversationMessage, +} from '../agent/conversation'; +import { assertNever } from '../util/assertNever'; +import { serializedAgent } from './agent'; +import { ACTIVITIES, ACTIVITY_COOLDOWN, CONVERSATION_COOLDOWN } from '../constants'; +import { api, internal } from '../_generated/api'; +import { sleep } from '../util/sleep'; +import { serializedPlayer } from './player'; + +export const agentRememberConversation = internalAction({ + args: { + worldId: v.id('worlds'), + playerId, + agentId, + conversationId, + operationId: v.string(), + }, + handler: async (ctx, args) => { + await rememberConversation( + ctx, + args.worldId, + args.agentId as GameId<'agents'>, + args.playerId as GameId<'players'>, + args.conversationId as GameId<'conversations'>, + ); + await sleep(Math.random() * 1000); + await ctx.runMutation(api.aiTown.main.sendInput, { + worldId: args.worldId, + name: 'finishRememberConversation', + args: { + agentId: args.agentId, + operationId: args.operationId, + }, + }); + }, +}); + +export const agentGenerateMessage = internalAction({ + args: { + worldId: v.id('worlds'), + playerId, + agentId, + conversationId, + otherPlayerId: playerId, + operationId: v.string(), + type: v.union(v.literal('start'), v.literal('continue'), v.literal('leave')), + messageUuid: v.string(), + }, + handler: async (ctx, args) => { + let completionFn; + switch (args.type) { + case 'start': + completionFn = startConversationMessage; + break; + case 'continue': + completionFn = continueConversationMessage; + break; + case 'leave': + completionFn = leaveConversationMessage; + break; + default: + assertNever(args.type); + } + const completion = await completionFn( + ctx, + args.worldId, + args.conversationId as GameId<'conversations'>, + args.playerId as GameId<'players'>, + args.otherPlayerId as GameId<'players'>, + ); + // TODO: stream in the text instead of reading it all at once. + const text = await completion.readAll(); + + await ctx.runMutation(internal.aiTown.agent.agentSendMessage, { + worldId: args.worldId, + conversationId: args.conversationId, + agentId: args.agentId, + playerId: args.playerId, + text, + messageUuid: args.messageUuid, + leaveConversation: args.type === 'leave', + operationId: args.operationId, + }); + }, +}); + +export const agentDoSomething = internalAction({ + args: { + worldId: v.id('worlds'), + player: v.object(serializedPlayer), + agent: v.object(serializedAgent), + map: v.object(serializedWorldMap), + otherFreePlayers: v.array(v.object(serializedPlayer)), + operationId: v.string(), + }, + handler: async (ctx, args) => { + const { player, agent } = args; + const map = new WorldMap(args.map); + const now = Date.now(); + // Don't try to start a new conversation if we were just in one. + const justLeftConversation = + agent.lastConversation && now < agent.lastConversation + CONVERSATION_COOLDOWN; + // Don't try again if we recently tried to find someone to invite. + const recentlyAttemptedInvite = + agent.lastInviteAttempt && now < agent.lastInviteAttempt + CONVERSATION_COOLDOWN; + const recentActivity = player.activity && now < player.activity.until + ACTIVITY_COOLDOWN; + // Decide whether to do an activity or wander somewhere. + if (!player.pathfinding) { + if (recentActivity || justLeftConversation) { + await sleep(Math.random() * 1000); + await ctx.runMutation(api.aiTown.main.sendInput, { + worldId: args.worldId, + name: 'finishDoSomething', + args: { + operationId: args.operationId, + agentId: agent.id, + destination: wanderDestination(map), + }, + }); + return; + } else { + // TODO: have LLM choose the activity & emoji + const activity = ACTIVITIES[Math.floor(Math.random() * ACTIVITIES.length)]; + await sleep(Math.random() * 1000); + await ctx.runMutation(api.aiTown.main.sendInput, { + worldId: args.worldId, + name: 'finishDoSomething', + args: { + operationId: args.operationId, + agentId: agent.id, + activity: { + description: activity.description, + emoji: activity.emoji, + until: Date.now() + activity.duration, + }, + }, + }); + return; + } + } + const invitee = + justLeftConversation || recentlyAttemptedInvite + ? undefined + : await ctx.runQuery(internal.aiTown.agent.findConversationCandidate, { + now, + worldId: args.worldId, + player: args.player, + otherFreePlayers: args.otherFreePlayers, + }); + + // TODO: We hit a lot of OCC errors on sending inputs in this file. It's + // easy for them to get scheduled at the same time and line up in time. + await sleep(Math.random() * 1000); + await ctx.runMutation(api.aiTown.main.sendInput, { + worldId: args.worldId, + name: 'finishDoSomething', + args: { + operationId: args.operationId, + agentId: args.agent.id, + invitee, + }, + }); + }, +}); + +function wanderDestination(worldMap: WorldMap) { + // Wander someonewhere at least one tile away from the edge. + return { + x: 1 + Math.floor(Math.random() * (worldMap.width - 2)), + y: 1 + Math.floor(Math.random() * (worldMap.height - 2)), + }; +} diff --git a/patches/convex/aiTown/conversation.ts b/patches/convex/aiTown/conversation.ts new file mode 100644 index 0000000000000000000000000000000000000000..ccfa1d559e845a88a56c62881111c95f14084fd4 --- /dev/null +++ b/patches/convex/aiTown/conversation.ts @@ -0,0 +1,395 @@ +import { ObjectType, v } from 'convex/values'; +import { GameId, parseGameId } from './ids'; +import { conversationId, playerId } from './ids'; +import { Player } from './player'; +import { inputHandler } from './inputHandler'; + +import { TYPING_TIMEOUT, CONVERSATION_DISTANCE } from '../constants'; +import { distance, normalize, vector } from '../util/geometry'; +import { Point } from '../util/types'; +import { Game } from './game'; +import { stopPlayer, blocked, movePlayer } from './movement'; +import { ConversationMembership, serializedConversationMembership } from './conversationMembership'; +import { parseMap, serializeMap } from '../util/object'; + +export class Conversation { + id: GameId<'conversations'>; + creator: GameId<'players'>; + created: number; + isTyping?: { + playerId: GameId<'players'>; + messageUuid: string; + since: number; + }; + lastMessage?: { + author: GameId<'players'>; + timestamp: number; + }; + numMessages: number; + participants: Map, ConversationMembership>; + + constructor(serialized: SerializedConversation) { + const { id, creator, created, isTyping, lastMessage, numMessages, participants } = serialized; + this.id = parseGameId('conversations', id); + this.creator = parseGameId('players', creator); + this.created = created; + this.isTyping = isTyping && { + playerId: parseGameId('players', isTyping.playerId), + messageUuid: isTyping.messageUuid, + since: isTyping.since, + }; + this.lastMessage = lastMessage && { + author: parseGameId('players', lastMessage.author), + timestamp: lastMessage.timestamp, + }; + this.numMessages = numMessages; + this.participants = parseMap(participants, ConversationMembership, (m) => m.playerId); + } + + tick(game: Game, now: number) { + if (this.isTyping && this.isTyping.since + TYPING_TIMEOUT < now) { + delete this.isTyping; + } + if (this.participants.size !== 2) { + console.warn(`Conversation ${this.id} has ${this.participants.size} participants`); + return; + } + const [playerId1, playerId2] = [...this.participants.keys()]; + const member1 = this.participants.get(playerId1)!; + const member2 = this.participants.get(playerId2)!; + + const player1 = game.world.players.get(playerId1)!; + const player2 = game.world.players.get(playerId2)!; + + const playerDistance = distance(player1?.position, player2?.position); + + // If the players are both in the "walkingOver" state and they're sufficiently close, transition both + // of them to "participating" and stop their paths. + if (member1.status.kind === 'walkingOver' && member2.status.kind === 'walkingOver') { + if (playerDistance < CONVERSATION_DISTANCE) { + console.log(`Starting conversation between ${player1.id} and ${player2.id}`); + + // First, stop the two players from moving. + stopPlayer(player1); + stopPlayer(player2); + + member1.status = { kind: 'participating', started: now }; + member2.status = { kind: 'participating', started: now }; + + // Try to move the first player to grid point nearest the other player. + const neighbors = (p: Point) => [ + { x: p.x + 1, y: p.y }, + { x: p.x - 1, y: p.y }, + { x: p.x, y: p.y + 1 }, + { x: p.x, y: p.y - 1 }, + ]; + const floorPos1 = { x: Math.floor(player1.position.x), y: Math.floor(player1.position.y) }; + const p1Candidates = neighbors(floorPos1).filter((p) => !blocked(game, now, p, player1.id)); + p1Candidates.sort((a, b) => distance(a, player2.position) - distance(b, player2.position)); + if (p1Candidates.length > 0) { + const p1Candidate = p1Candidates[0]; + + // Try to move the second player to the grid point nearest the first player's + // destination. + const p2Candidates = neighbors(p1Candidate).filter( + (p) => !blocked(game, now, p, player2.id), + ); + p2Candidates.sort( + (a, b) => distance(a, player2.position) - distance(b, player2.position), + ); + if (p2Candidates.length > 0) { + const p2Candidate = p2Candidates[0]; + movePlayer(game, now, player1, p1Candidate, true); + movePlayer(game, now, player2, p2Candidate, true); + } + } + } + } + + // Orient the two players towards each other if they're not moving. + if (member1.status.kind === 'participating' && member2.status.kind === 'participating') { + const v = normalize(vector(player1.position, player2.position)); + if (!player1.pathfinding && v) { + player1.facing = v; + } + if (!player2.pathfinding && v) { + player2.facing.dx = -v.dx; + player2.facing.dy = -v.dy; + } + } + } + + static start(game: Game, now: number, player: Player, invitee: Player) { + if (player.id === invitee.id) { + throw new Error(`Can't invite yourself to a conversation`); + } + // Ensure the players still exist. + if ([...game.world.conversations.values()].find((c) => c.participants.has(player.id))) { + const reason = `Player ${player.id} is already in a conversation`; + console.log(reason); + return { error: reason }; + } + if ([...game.world.conversations.values()].find((c) => c.participants.has(invitee.id))) { + const reason = `Player ${player.id} is already in a conversation`; + console.log(reason); + return { error: reason }; + } + const conversationId = game.allocId('conversations'); + console.log(`Creating conversation ${conversationId}`); + game.world.conversations.set( + conversationId, + new Conversation({ + id: conversationId, + created: now, + creator: player.id, + numMessages: 0, + participants: [ + { playerId: player.id, invited: now, status: { kind: 'walkingOver' } }, + { playerId: invitee.id, invited: now, status: { kind: 'invited' } }, + ], + }), + ); + return { conversationId }; + } + + setIsTyping(now: number, player: Player, messageUuid: string) { + if (this.isTyping) { + if (this.isTyping.playerId !== player.id) { + throw new Error(`Player ${this.isTyping.playerId} is already typing in ${this.id}`); + } + return; + } + this.isTyping = { playerId: player.id, messageUuid, since: now }; + } + + acceptInvite(game: Game, player: Player) { + const member = this.participants.get(player.id); + if (!member) { + throw new Error(`Player ${player.id} not in conversation ${this.id}`); + } + if (member.status.kind !== 'invited') { + throw new Error( + `Invalid membership status for ${player.id}:${this.id}: ${JSON.stringify(member)}`, + ); + } + member.status = { kind: 'walkingOver' }; + } + + rejectInvite(game: Game, now: number, player: Player) { + const member = this.participants.get(player.id); + if (!member) { + throw new Error(`Player ${player.id} not in conversation ${this.id}`); + } + if (member.status.kind !== 'invited') { + throw new Error( + `Rejecting invite in wrong membership state: ${this.id}:${player.id}: ${JSON.stringify( + member, + )}`, + ); + } + this.stop(game, now); + } + + stop(game: Game, now: number) { + delete this.isTyping; + for (const [playerId, member] of this.participants.entries()) { + const agent = [...game.world.agents.values()].find((a) => a.playerId === playerId); + if (agent) { + agent.lastConversation = now; + agent.toRemember = this.id; + } + } + game.world.conversations.delete(this.id); + } + + leave(game: Game, now: number, player: Player) { + const member = this.participants.get(player.id); + if (!member) { + throw new Error(`Couldn't find membership for ${this.id}:${player.id}`); + } + this.stop(game, now); + } + + serialize(): SerializedConversation { + const { id, creator, created, isTyping, lastMessage, numMessages } = this; + return { + id, + creator, + created, + isTyping, + lastMessage, + numMessages, + participants: serializeMap(this.participants), + }; + } +} + +export const serializedConversation = { + id: conversationId, + creator: playerId, + created: v.number(), + isTyping: v.optional( + v.object({ + playerId, + messageUuid: v.string(), + since: v.number(), + }), + ), + lastMessage: v.optional( + v.object({ + author: playerId, + timestamp: v.number(), + }), + ), + numMessages: v.number(), + participants: v.array(v.object(serializedConversationMembership)), +}; +export type SerializedConversation = ObjectType; + +export const conversationInputs = { + // Start a conversation, inviting the specified player. + // Conversations can only have two participants for now, + // so we don't have a separate "invite" input. + startConversation: inputHandler({ + args: { + playerId, + invitee: playerId, + }, + handler: (game: Game, now: number, args): GameId<'conversations'> => { + const playerId = parseGameId('players', args.playerId); + const player = game.world.players.get(playerId); + if (!player) { + throw new Error(`Invalid player ID: ${playerId}`); + } + const inviteeId = parseGameId('players', args.invitee); + const invitee = game.world.players.get(inviteeId); + if (!invitee) { + throw new Error(`Invalid player ID: ${inviteeId}`); + } + console.log(`Starting ${playerId} ${inviteeId}...`); + const { conversationId, error } = Conversation.start(game, now, player, invitee); + if (!conversationId) { + // TODO: pass it back to the client for them to show an error. + throw new Error(error); + } + return conversationId; + }, + }), + + startTyping: inputHandler({ + args: { + playerId, + conversationId, + messageUuid: v.string(), + }, + handler: (game: Game, now: number, args): null => { + const playerId = parseGameId('players', args.playerId); + const player = game.world.players.get(playerId); + if (!player) { + throw new Error(`Invalid player ID: ${playerId}`); + } + const conversationId = parseGameId('conversations', args.conversationId); + const conversation = game.world.conversations.get(conversationId); + if (!conversation) { + throw new Error(`Invalid conversation ID: ${conversationId}`); + } + if (conversation.isTyping && conversation.isTyping.playerId !== playerId) { + throw new Error( + `Player ${conversation.isTyping.playerId} is already typing in ${conversationId}`, + ); + } + conversation.isTyping = { playerId, messageUuid: args.messageUuid, since: now }; + return null; + }, + }), + + finishSendingMessage: inputHandler({ + args: { + playerId, + conversationId, + timestamp: v.number(), + }, + handler: (game: Game, now: number, args): null => { + const playerId = parseGameId('players', args.playerId); + const conversationId = parseGameId('conversations', args.conversationId); + const conversation = game.world.conversations.get(conversationId); + if (!conversation) { + throw new Error(`Invalid conversation ID: ${conversationId}`); + } + if (conversation.isTyping && conversation.isTyping.playerId === playerId) { + delete conversation.isTyping; + } + conversation.lastMessage = { author: playerId, timestamp: args.timestamp }; + conversation.numMessages++; + return null; + }, + }), + + // Accept an invite to a conversation, which puts the + // player in the "walkingOver" state until they're close + // enough to the other participant. + acceptInvite: inputHandler({ + args: { + playerId, + conversationId, + }, + handler: (game: Game, now: number, args): null => { + const playerId = parseGameId('players', args.playerId); + const player = game.world.players.get(playerId); + if (!player) { + throw new Error(`Invalid player ID ${playerId}`); + } + const conversationId = parseGameId('conversations', args.conversationId); + const conversation = game.world.conversations.get(conversationId); + if (!conversation) { + throw new Error(`Invalid conversation ID ${conversationId}`); + } + conversation.acceptInvite(game, player); + return null; + }, + }), + + // Reject the invite. Eventually we might add a message + // that explains why! + rejectInvite: inputHandler({ + args: { + playerId, + conversationId, + }, + handler: (game: Game, now: number, args): null => { + const playerId = parseGameId('players', args.playerId); + const player = game.world.players.get(playerId); + if (!player) { + throw new Error(`Invalid player ID ${playerId}`); + } + const conversationId = parseGameId('conversations', args.conversationId); + const conversation = game.world.conversations.get(conversationId); + if (!conversation) { + throw new Error(`Invalid conversation ID ${conversationId}`); + } + conversation.rejectInvite(game, now, player); + return null; + }, + }), + // Leave a conversation. + leaveConversation: inputHandler({ + args: { + playerId, + conversationId, + }, + handler: (game: Game, now: number, args): null => { + const playerId = parseGameId('players', args.playerId); + const player = game.world.players.get(playerId); + if (!player) { + throw new Error(`Invalid player ID ${playerId}`); + } + const conversationId = parseGameId('conversations', args.conversationId); + const conversation = game.world.conversations.get(conversationId); + if (!conversation) { + throw new Error(`Invalid conversation ID ${conversationId}`); + } + conversation.leave(game, now, player); + return null; + }, + }), +}; diff --git a/patches/convex/aiTown/conversationMembership.ts b/patches/convex/aiTown/conversationMembership.ts new file mode 100644 index 0000000000000000000000000000000000000000..7dd5eaf56f41a2ed10d9607b6c6d183379330e96 --- /dev/null +++ b/patches/convex/aiTown/conversationMembership.ts @@ -0,0 +1,38 @@ +import { ObjectType, v } from 'convex/values'; +import { GameId, parseGameId, playerId } from './ids'; + +export const serializedConversationMembership = { + playerId, + invited: v.number(), + status: v.union( + v.object({ kind: v.literal('invited') }), + v.object({ kind: v.literal('walkingOver') }), + v.object({ kind: v.literal('participating'), started: v.number() }), + ), +}; +export type SerializedConversationMembership = ObjectType; + +export class ConversationMembership { + playerId: GameId<'players'>; + invited: number; + status: + | { kind: 'invited' } + | { kind: 'walkingOver' } + | { kind: 'participating'; started: number }; + + constructor(serialized: SerializedConversationMembership) { + const { playerId, invited, status } = serialized; + this.playerId = parseGameId('players', playerId); + this.invited = invited; + this.status = status; + } + + serialize(): SerializedConversationMembership { + const { playerId, invited, status } = this; + return { + playerId, + invited, + status, + }; + } +} diff --git a/patches/convex/aiTown/dayNightCycle.ts b/patches/convex/aiTown/dayNightCycle.ts new file mode 100644 index 0000000000000000000000000000000000000000..638a08ce5af054efc95f602f9203e48c0e8a7502 --- /dev/null +++ b/patches/convex/aiTown/dayNightCycle.ts @@ -0,0 +1,71 @@ +import { v, Infer, ObjectType } from 'convex/values'; +import { Game } from './game'; + +// Define the schema for DayNightCycle +export const dayNightCycleSchema = { + currentTime: v.number(), + isDay: v.boolean(), + dayDuration: v.number(), + nightDuration: v.number(), +}; +export type SerializedDayNightCycle = ObjectType; + +export class DayNightCycle { + currentTime: number; + isDay: boolean; + dayDuration: number; + nightDuration: number; + + constructor(serialized: SerializedDayNightCycle) { + const { currentTime, isDay, dayDuration, nightDuration } = serialized; + this.currentTime = currentTime; + this.isDay = isDay; + this.dayDuration = dayDuration; + this.nightDuration = nightDuration; + } + + // Tick method to increment the counter + tick(game: Game, tickDuration: number) { + this.currentTime += tickDuration; + + if (this.isDay && this.currentTime >= this.dayDuration) { + this.isDay = false; + this.currentTime = 0; + this.onNightStart(game); + } else if (!this.isDay && this.currentTime >= this.nightDuration) { + this.isDay = true; + this.currentTime = 0; + this.onDayStart(game); + } + } + + onDayStart(game: Game) { + console.log("Day has started!"); + for (const player of game.world.players.values()) { + // player.onDayStart(game); + } + for (const agent of game.world.agents.values()) { + // agent.onDayStart(game); + } + } + + onNightStart(game: Game) { + console.log("Night has started!"); + for (const player of game.world.players.values()) { + // player.onNightStart(game); + } + for (const agent of game.world.agents.values()) { + // agent.onNightStart(game); + } + } + + serialize(): SerializedDayNightCycle { + const { currentTime, isDay, dayDuration, nightDuration } = this; + return { + currentTime, + isDay, + dayDuration, + nightDuration, + }; + } +} diff --git a/patches/convex/aiTown/game.ts b/patches/convex/aiTown/game.ts new file mode 100644 index 0000000000000000000000000000000000000000..080016136371ee470f573e290714a6133d43e5c7 --- /dev/null +++ b/patches/convex/aiTown/game.ts @@ -0,0 +1,374 @@ +import { Infer, v } from 'convex/values'; +import { Doc, Id } from '../_generated/dataModel'; +import { + ActionCtx, + DatabaseReader, + MutationCtx, + internalMutation, + internalQuery, +} from '../_generated/server'; +import { World, serializedWorld } from './world'; +import { WorldMap, serializedWorldMap } from './worldMap'; +import { PlayerDescription, serializedPlayerDescription } from './playerDescription'; +import { Location, locationFields, playerLocation } from './location'; +import { runAgentOperation } from './agent'; +import { GameId, IdTypes, allocGameId } from './ids'; +import { InputArgs, InputNames, inputs } from './inputs'; +import { + AbstractGame, + EngineUpdate, + applyEngineUpdate, + engineUpdate, + loadEngine, +} from '../engine/abstractGame'; +import { internal } from '../_generated/api'; +import { HistoricalObject } from '../engine/historicalObject'; +import { AgentDescription, serializedAgentDescription } from './agentDescription'; +import { parseMap, serializeMap } from '../util/object'; + +const gameState = v.object({ + world: v.object(serializedWorld), + playerDescriptions: v.array(v.object(serializedPlayerDescription)), + agentDescriptions: v.array(v.object(serializedAgentDescription)), + worldMap: v.object(serializedWorldMap), +}); +type GameState = Infer; + +const gameStateDiff = v.object({ + world: v.object(serializedWorld), + playerDescriptions: v.optional(v.array(v.object(serializedPlayerDescription))), + agentDescriptions: v.optional(v.array(v.object(serializedAgentDescription))), + worldMap: v.optional(v.object(serializedWorldMap)), + agentOperations: v.array(v.object({ name: v.string(), args: v.any() })), +}); +type GameStateDiff = Infer; + +export class Game extends AbstractGame { + tickDuration = 16; + stepDuration = 1000; + maxTicksPerStep = 600; + maxInputsPerStep = 32; + + world: World; + + historicalLocations: Map, HistoricalObject>; + + descriptionsModified: boolean; + worldMap: WorldMap; + playerDescriptions: Map, PlayerDescription>; + agentDescriptions: Map, AgentDescription>; + + pendingOperations: Array<{ name: string; args: any }> = []; + + numPathfinds: number; + + constructor( + engine: Doc<'engines'>, + public worldId: Id<'worlds'>, + state: GameState, + ) { + super(engine); + + this.world = new World(state.world); + delete this.world.historicalLocations; + + this.descriptionsModified = false; + this.worldMap = new WorldMap(state.worldMap); + this.agentDescriptions = parseMap(state.agentDescriptions, AgentDescription, (a) => a.agentId); + this.playerDescriptions = parseMap( + state.playerDescriptions, + PlayerDescription, + (p) => p.playerId, + ); + + this.historicalLocations = new Map(); + + this.numPathfinds = 0; + } + + static async load( + db: DatabaseReader, + worldId: Id<'worlds'>, + generationNumber: number, + ): Promise<{ engine: Doc<'engines'>; gameState: GameState }> { + const worldDoc = await db.get(worldId); + if (!worldDoc) { + throw new Error(`No world found with id ${worldId}`); + } + const worldStatus = await db + .query('worldStatus') + .withIndex('worldId', (q) => q.eq('worldId', worldId)) + .unique(); + if (!worldStatus) { + throw new Error(`No engine found for world ${worldId}`); + } + const engine = await loadEngine(db, worldStatus.engineId, generationNumber); + const playerDescriptionsDocs = await db + .query('playerDescriptions') + .withIndex('worldId', (q) => q.eq('worldId', worldId)) + .collect(); + const agentDescriptionsDocs = await db + .query('agentDescriptions') + .withIndex('worldId', (q) => q.eq('worldId', worldId)) + .collect(); + const worldMapDoc = await db + .query('maps') + .withIndex('worldId', (q) => q.eq('worldId', worldId)) + .unique(); + if (!worldMapDoc) { + throw new Error(`No map found for world ${worldId}`); + } + // Discard the system fields and historicalLocations from the world state. + const { _id, _creationTime, historicalLocations: _, ...world } = worldDoc; + const playerDescriptions = playerDescriptionsDocs + // Discard player descriptions for players that no longer exist. + .filter((d) => !!world.players.find((p) => p.id === d.playerId)) + .map(({ _id, _creationTime, worldId: _, ...doc }) => doc); + const agentDescriptions = agentDescriptionsDocs + .filter((a) => !!world.agents.find((p) => p.id === a.agentId)) + .map(({ _id, _creationTime, worldId: _, ...doc }) => doc); + const { + _id: _mapId, + _creationTime: _mapCreationTime, + worldId: _mapWorldId, + ...worldMap + } = worldMapDoc; + return { + engine, + gameState: { + world, + playerDescriptions, + agentDescriptions, + worldMap, + }, + }; + } + + allocId(idType: T): GameId { + const id = allocGameId(idType, this.world.nextId); + this.world.nextId += 1; + return id; + } + + scheduleOperation(name: string, args: unknown) { + this.pendingOperations.push({ name, args }); + } + + handleInput(now: number, name: Name, args: InputArgs) { + const handler = inputs[name]?.handler; + if (!handler) { + throw new Error(`Invalid input: ${name}`); + } + return handler(this, now, args as any); + } + + beginStep(_now: number) { + // Store the current location of all players in the history tracking buffer. + this.historicalLocations.clear(); + for (const player of this.world.players.values()) { + this.historicalLocations.set( + player.id, + new HistoricalObject(locationFields, playerLocation(player)), + ); + } + this.numPathfinds = 0; + } + + tick(now: number) { + // update day&nigth cycle counter + this.world.dayNightCycle.tick(this, this.tickDuration); + + for (const player of this.world.players.values()) { + player.tick(this, now); + } + for (const player of this.world.players.values()) { + player.tickPathfinding(this, now); + } + for (const player of this.world.players.values()) { + player.tickPosition(this, now); + } + for (const conversation of this.world.conversations.values()) { + conversation.tick(this, now); + } + for (const agent of this.world.agents.values()) { + agent.tick(this, now); + } + + // Save each player's location into the history buffer at the end of + // each tick. + for (const player of this.world.players.values()) { + let historicalObject = this.historicalLocations.get(player.id); + if (!historicalObject) { + historicalObject = new HistoricalObject(locationFields, playerLocation(player)); + this.historicalLocations.set(player.id, historicalObject); + } + historicalObject.update(now, playerLocation(player)); + } + } + + async saveStep(ctx: ActionCtx, engineUpdate: EngineUpdate): Promise { + const diff = this.takeDiff(); + await ctx.runMutation(internal.aiTown.game.saveWorld, { + engineId: this.engine._id, + engineUpdate, + worldId: this.worldId, + worldDiff: diff, + }); + } + + takeDiff(): GameStateDiff { + const historicalLocations = []; + let bufferSize = 0; + for (const [id, historicalObject] of this.historicalLocations.entries()) { + const buffer = historicalObject.pack(); + if (!buffer) { + continue; + } + historicalLocations.push({ playerId: id, location: buffer }); + bufferSize += buffer.byteLength; + } + if (bufferSize > 0) { + console.debug( + `Packed ${Object.entries(historicalLocations).length} history buffers in ${( + bufferSize / 1024 + ).toFixed(2)}KiB.`, + ); + } + this.historicalLocations.clear(); + + const result: GameStateDiff = { + world: { ...this.world.serialize(), historicalLocations }, + agentOperations: this.pendingOperations, + }; + this.pendingOperations = []; + if (this.descriptionsModified) { + result.playerDescriptions = serializeMap(this.playerDescriptions); + result.agentDescriptions = serializeMap(this.agentDescriptions); + result.worldMap = this.worldMap.serialize(); + this.descriptionsModified = false; + } + return result; + } + + static async saveDiff(ctx: MutationCtx, worldId: Id<'worlds'>, diff: GameStateDiff) { + const existingWorld = await ctx.db.get(worldId); + if (!existingWorld) { + throw new Error(`No world found with id ${worldId}`); + } + const newWorld = diff.world; + // Archive newly deleted players, conversations, and agents. + for (const player of existingWorld.players) { + if (!newWorld.players.some((p) => p.id === player.id)) { + await ctx.db.insert('archivedPlayers', { worldId, ...player }); + } + } + for (const conversation of existingWorld.conversations) { + if (!newWorld.conversations.some((c) => c.id === conversation.id)) { + const participants = conversation.participants.map((p) => p.playerId); + const archivedConversation = { + worldId, + id: conversation.id, + created: conversation.created, + creator: conversation.creator, + ended: Date.now(), + lastMessage: conversation.lastMessage, + numMessages: conversation.numMessages, + participants, + }; + await ctx.db.insert('archivedConversations', archivedConversation); + for (let i = 0; i < participants.length; i++) { + for (let j = 0; j < participants.length; j++) { + if (i == j) { + continue; + } + const player1 = participants[i]; + const player2 = participants[j]; + await ctx.db.insert('participatedTogether', { + worldId, + conversationId: conversation.id, + player1, + player2, + ended: Date.now(), + }); + } + } + } + } + for (const conversation of existingWorld.agents) { + if (!newWorld.agents.some((a) => a.id === conversation.id)) { + await ctx.db.insert('archivedAgents', { worldId, ...conversation }); + } + } + // Update the world state. + await ctx.db.replace(worldId, newWorld); + + // Update the larger description tables if they changed. + const { playerDescriptions, agentDescriptions, worldMap } = diff; + if (playerDescriptions) { + for (const description of playerDescriptions) { + const existing = await ctx.db + .query('playerDescriptions') + .withIndex('worldId', (q) => + q.eq('worldId', worldId).eq('playerId', description.playerId), + ) + .unique(); + if (existing) { + await ctx.db.replace(existing._id, { worldId, ...description }); + } else { + await ctx.db.insert('playerDescriptions', { worldId, ...description }); + } + } + } + if (agentDescriptions) { + for (const description of agentDescriptions) { + const existing = await ctx.db + .query('agentDescriptions') + .withIndex('worldId', (q) => q.eq('worldId', worldId).eq('agentId', description.agentId)) + .unique(); + if (existing) { + await ctx.db.replace(existing._id, { worldId, ...description }); + } else { + await ctx.db.insert('agentDescriptions', { worldId, ...description }); + } + } + } + if (worldMap) { + const existing = await ctx.db + .query('maps') + .withIndex('worldId', (q) => q.eq('worldId', worldId)) + .unique(); + if (existing) { + await ctx.db.replace(existing._id, { worldId, ...worldMap }); + } else { + await ctx.db.insert('maps', { worldId, ...worldMap }); + } + } + // Start the desired agent operations. + for (const operation of diff.agentOperations) { + await runAgentOperation(ctx, operation.name, operation.args); + } + } +} + +export const loadWorld = internalQuery({ + args: { + worldId: v.id('worlds'), + generationNumber: v.number(), + }, + handler: async (ctx, args) => { + return await Game.load(ctx.db, args.worldId, args.generationNumber); + }, +}); + +export const saveWorld = internalMutation({ + args: { + engineId: v.id('engines'), + engineUpdate, + worldId: v.id('worlds'), + worldDiff: gameStateDiff, + }, + handler: async (ctx, args) => { + await applyEngineUpdate(ctx, args.engineId, args.engineUpdate); + await Game.saveDiff(ctx, args.worldId, args.worldDiff); + }, +}); diff --git a/patches/convex/aiTown/ids.ts b/patches/convex/aiTown/ids.ts new file mode 100644 index 0000000000000000000000000000000000000000..0592e7c1141a264a3701e6ca1281f15004dd5822 --- /dev/null +++ b/patches/convex/aiTown/ids.ts @@ -0,0 +1,32 @@ +import { v } from 'convex/values'; + +const IdShortCodes = { agents: 'a', conversations: 'c', players: 'p', operations: 'o' }; +export type IdTypes = keyof typeof IdShortCodes; + +export type GameId = string & { __type: T }; + +export function parseGameId(idType: T, gameId: string): GameId { + const type = gameId[0]; + const match = Object.entries(IdShortCodes).find(([_, value]) => value === type); + if (!match || match[0] !== idType) { + throw new Error(`Invalid game ID type: ${type}`); + } + const number = parseInt(gameId.slice(2), 10); + if (isNaN(number) || !Number.isInteger(number) || number < 0) { + throw new Error(`Invalid game ID number: ${gameId}`); + } + return gameId as GameId; +} + +export function allocGameId(idType: T, idNumber: number): GameId { + const type = IdShortCodes[idType]; + if (!type) { + throw new Error(`Invalid game ID type: ${idType}`); + } + return `${type}:${idNumber}` as GameId; +} + +export const conversationId = v.string(); +export const playerId = v.string(); +export const agentId = v.string(); +export const operationId = v.string(); diff --git a/patches/convex/aiTown/inputHandler.ts b/patches/convex/aiTown/inputHandler.ts new file mode 100644 index 0000000000000000000000000000000000000000..940bf59d3220d29d6bd77d66646a18fe007fe0b0 --- /dev/null +++ b/patches/convex/aiTown/inputHandler.ts @@ -0,0 +1,9 @@ +import { ObjectType, PropertyValidators, Value } from 'convex/values'; +import type { Game } from './game'; + +export function inputHandler(def: { + args: ArgsValidator; + handler: (game: Game, now: number, args: ObjectType) => Return; +}) { + return def; +} diff --git a/patches/convex/aiTown/inputs.ts b/patches/convex/aiTown/inputs.ts new file mode 100644 index 0000000000000000000000000000000000000000..144009d74fb38b31bc0e0c0407662926675eb365 --- /dev/null +++ b/patches/convex/aiTown/inputs.ts @@ -0,0 +1,25 @@ +import { ObjectType } from 'convex/values'; +import { playerInputs } from './player'; +import { conversationInputs } from './conversation'; +import { agentInputs } from './agentInputs'; + +// It's easy to hit circular dependencies with these imports, +// so assert at module scope so we hit errors when analyzing. +if (playerInputs === undefined || conversationInputs === undefined || agentInputs === undefined) { + throw new Error("Input map is undefined, check if there's a circular import."); +} +export const inputs = { + ...playerInputs, + // Inputs for the messaging layer. + ...conversationInputs, + // Inputs for the agent layer. + ...agentInputs, +}; +export type Inputs = typeof inputs; +export type InputNames = keyof Inputs; +export type InputArgs = ObjectType; +export type InputReturnValue = ReturnType< + Inputs[Name]['handler'] +> extends Promise + ? T + : never; diff --git a/patches/convex/aiTown/insertInput.ts b/patches/convex/aiTown/insertInput.ts new file mode 100644 index 0000000000000000000000000000000000000000..26d8d297ad14972e6dd012aef6d47e7983206b38 --- /dev/null +++ b/patches/convex/aiTown/insertInput.ts @@ -0,0 +1,20 @@ +import { MutationCtx } from '../_generated/server'; +import { Id } from '../_generated/dataModel'; +import { engineInsertInput } from '../engine/abstractGame'; +import { InputNames, InputArgs } from './inputs'; + +export async function insertInput( + ctx: MutationCtx, + worldId: Id<'worlds'>, + name: Name, + args: InputArgs, +): Promise> { + const worldStatus = await ctx.db + .query('worldStatus') + .withIndex('worldId', (q) => q.eq('worldId', worldId)) + .unique(); + if (!worldStatus) { + throw new Error(`World for engine ${worldId} not found`); + } + return await engineInsertInput(ctx, worldStatus.engineId, name, args); +} diff --git a/patches/convex/aiTown/location.ts b/patches/convex/aiTown/location.ts new file mode 100644 index 0000000000000000000000000000000000000000..c66c6a76781f874d78f3ed53d27274056b1195f6 --- /dev/null +++ b/patches/convex/aiTown/location.ts @@ -0,0 +1,32 @@ +import { FieldConfig } from '../engine/historicalObject'; +import { Player } from './player'; + +export type Location = { + // Unpacked player position. + x: number; + y: number; + + // Normalized facing vector. + dx: number; + dy: number; + + speed: number; +}; + +export const locationFields: FieldConfig = [ + { name: 'x', precision: 8 }, + { name: 'y', precision: 8 }, + { name: 'dx', precision: 8 }, + { name: 'dy', precision: 8 }, + { name: 'speed', precision: 16 }, +]; + +export function playerLocation(player: Player): Location { + return { + x: player.position.x, + y: player.position.y, + dx: player.facing.dx, + dy: player.facing.dy, + speed: player.speed, + }; +} diff --git a/patches/convex/aiTown/main.ts b/patches/convex/aiTown/main.ts new file mode 100644 index 0000000000000000000000000000000000000000..f0d7617beb90d4a835e22525b2d52711fe854719 --- /dev/null +++ b/patches/convex/aiTown/main.ts @@ -0,0 +1,154 @@ +import { ConvexError, v } from 'convex/values'; +import { DatabaseReader, MutationCtx, internalAction, mutation, query } from '../_generated/server'; +import { insertInput } from './insertInput'; +import { Game } from './game'; +import { internal } from '../_generated/api'; +import { sleep } from '../util/sleep'; +import { Id } from '../_generated/dataModel'; +import { ENGINE_ACTION_DURATION } from '../constants'; + +export async function createEngine(ctx: MutationCtx) { + const now = Date.now(); + const engineId = await ctx.db.insert('engines', { + currentTime: now, + generationNumber: 0, + running: true, + }); + return engineId; +} + +async function loadWorldStatus(db: DatabaseReader, worldId: Id<'worlds'>) { + const worldStatus = await db + .query('worldStatus') + .withIndex('worldId', (q) => q.eq('worldId', worldId)) + .unique(); + if (!worldStatus) { + throw new Error(`No engine found for world ${worldId}`); + } + return worldStatus; +} + +export async function startEngine(ctx: MutationCtx, worldId: Id<'worlds'>) { + const { engineId } = await loadWorldStatus(ctx.db, worldId); + const engine = await ctx.db.get(engineId); + if (!engine) { + throw new Error(`Invalid engine ID: ${engineId}`); + } + if (engine.running) { + throw new Error(`Engine ${engineId} isn't currently stopped`); + } + const now = Date.now(); + const generationNumber = engine.generationNumber + 1; + await ctx.db.patch(engineId, { + // Forcibly advance time to the present. This does mean we'll skip + // simulating the time the engine was stopped, but we don't want + // to have to simulate a potentially large stopped window and send + // it down to clients. + lastStepTs: engine.currentTime, + currentTime: now, + running: true, + generationNumber, + }); + await ctx.scheduler.runAfter(0, internal.aiTown.main.runStep, { + worldId: worldId, + generationNumber, + maxDuration: ENGINE_ACTION_DURATION, + }); +} + +export async function kickEngine(ctx: MutationCtx, worldId: Id<'worlds'>) { + const { engineId } = await loadWorldStatus(ctx.db, worldId); + const engine = await ctx.db.get(engineId); + if (!engine) { + throw new Error(`Invalid engine ID: ${engineId}`); + } + if (!engine.running) { + throw new Error(`Engine ${engineId} isn't currently running`); + } + const generationNumber = engine.generationNumber + 1; + await ctx.db.patch(engineId, { generationNumber }); + await ctx.scheduler.runAfter(0, internal.aiTown.main.runStep, { + worldId: worldId, + generationNumber, + maxDuration: ENGINE_ACTION_DURATION, + }); +} + +export async function stopEngine(ctx: MutationCtx, worldId: Id<'worlds'>) { + const { engineId } = await loadWorldStatus(ctx.db, worldId); + const engine = await ctx.db.get(engineId); + if (!engine) { + throw new Error(`Invalid engine ID: ${engineId}`); + } + if (!engine.running) { + throw new Error(`Engine ${engineId} isn't currently running`); + } + await ctx.db.patch(engineId, { running: false }); +} + +export const runStep = internalAction({ + args: { + worldId: v.id('worlds'), + generationNumber: v.number(), + maxDuration: v.number(), + }, + handler: async (ctx, args) => { + try { + const { engine, gameState } = await ctx.runQuery(internal.aiTown.game.loadWorld, { + worldId: args.worldId, + generationNumber: args.generationNumber, + }); + const game = new Game(engine, args.worldId, gameState); + + let now = Date.now(); + const deadline = now + args.maxDuration; + while (now < deadline) { + await game.runStep(ctx, now); + const sleepUntil = Math.min(now + game.stepDuration, deadline); + await sleep(sleepUntil - now); + now = Date.now(); + } + await ctx.scheduler.runAfter(0, internal.aiTown.main.runStep, { + worldId: args.worldId, + generationNumber: game.engine.generationNumber, + maxDuration: args.maxDuration, + }); + } catch (e: unknown) { + if (e instanceof ConvexError) { + if (e.data.kind === 'engineNotRunning') { + console.debug(`Engine is not running: ${e.message}`); + return; + } + if (e.data.kind === 'generationNumber') { + console.debug(`Generation number mismatch: ${e.message}`); + return; + } + } + throw e; + } + }, +}); + +export const sendInput = mutation({ + args: { + worldId: v.id('worlds'), + name: v.string(), + args: v.any(), + }, + handler: async (ctx, args) => { + return await insertInput(ctx, args.worldId, args.name as any, args.args); + }, +}); + +export const inputStatus = query({ + args: { + inputId: v.id('inputs'), + }, + handler: async (ctx, args) => { + const input = await ctx.db.get(args.inputId); + if (!input) { + throw new Error(`Invalid input ID: ${args.inputId}`); + } + return input.returnValue ?? null; + }, +}); diff --git a/patches/convex/aiTown/movement.ts b/patches/convex/aiTown/movement.ts new file mode 100644 index 0000000000000000000000000000000000000000..1a552ae17620bd206e8e9260fa34eeae4a68bd0c --- /dev/null +++ b/patches/convex/aiTown/movement.ts @@ -0,0 +1,189 @@ +import { movementSpeed } from '../../data/characters'; +import { COLLISION_THRESHOLD } from '../constants'; +import { compressPath, distance, manhattanDistance, pointsEqual } from '../util/geometry'; +import { MinHeap } from '../util/minheap'; +import { Point, Vector } from '../util/types'; +import { Game } from './game'; +import { GameId } from './ids'; +import { Player } from './player'; +import { WorldMap } from './worldMap'; + +type PathCandidate = { + position: Point; + facing?: Vector; + t: number; + length: number; + cost: number; + prev?: PathCandidate; +}; + +export function stopPlayer(player: Player) { + delete player.pathfinding; + player.speed = 0; +} + +export function movePlayer( + game: Game, + now: number, + player: Player, + destination: Point, + allowInConversation?: boolean, +) { + if (Math.floor(destination.x) !== destination.x || Math.floor(destination.y) !== destination.y) { + throw new Error(`Non-integral destination: ${JSON.stringify(destination)}`); + } + const { position } = player; + // Close enough to current position or destination => no-op. + if (pointsEqual(position, destination)) { + return; + } + // Don't allow players in a conversation to move. + const inConversation = [...game.world.conversations.values()].some( + (c) => c.participants.get(player.id)?.status.kind === 'participating', + ); + if (inConversation && !allowInConversation) { + throw new Error(`Can't move when in a conversation. Leave the conversation first!`); + } + player.pathfinding = { + destination: destination, + started: now, + state: { + kind: 'needsPath', + }, + }; + return; +} + +export function findRoute(game: Game, now: number, player: Player, destination: Point) { + const minDistances: PathCandidate[][] = []; + const explore = (current: PathCandidate): Array => { + const { x, y } = current.position; + const neighbors = []; + + // If we're not on a grid point, first try to move horizontally + // or vertically to a grid point. Note that this can create very small + // deltas between the current position and the nearest grid point so + // be careful to preserve the `facing` vectors rather than trying to + // derive them anew. + if (x !== Math.floor(x)) { + neighbors.push( + { position: { x: Math.floor(x), y }, facing: { dx: -1, dy: 0 } }, + { position: { x: Math.floor(x) + 1, y }, facing: { dx: 1, dy: 0 } }, + ); + } + if (y !== Math.floor(y)) { + neighbors.push( + { position: { x, y: Math.floor(y) }, facing: { dx: 0, dy: -1 } }, + { position: { x, y: Math.floor(y) + 1 }, facing: { dx: 0, dy: 1 } }, + ); + } + // Otherwise, just move to adjacent grid points. + if (x == Math.floor(x) && y == Math.floor(y)) { + neighbors.push( + { position: { x: x + 1, y }, facing: { dx: 1, dy: 0 } }, + { position: { x: x - 1, y }, facing: { dx: -1, dy: 0 } }, + { position: { x, y: y + 1 }, facing: { dx: 0, dy: 1 } }, + { position: { x, y: y - 1 }, facing: { dx: 0, dy: -1 } }, + ); + } + const next = []; + for (const { position, facing } of neighbors) { + const segmentLength = distance(current.position, position); + const length = current.length + segmentLength; + if (blocked(game, now, position, player.id)) { + continue; + } + const remaining = manhattanDistance(position, destination); + const path = { + position, + facing, + // Movement speed is in tiles per second. + t: current.t + (segmentLength / movementSpeed) * 1000, + length, + cost: length + remaining, + prev: current, + }; + const existingMin = minDistances[position.y]?.[position.x]; + if (existingMin && existingMin.cost <= path.cost) { + continue; + } + minDistances[position.y] ??= []; + minDistances[position.y][position.x] = path; + next.push(path); + } + return next; + }; + + const startingLocation = player.position; + const startingPosition = { x: startingLocation.x, y: startingLocation.y }; + let current: PathCandidate | undefined = { + position: startingPosition, + facing: player.facing, + t: now, + length: 0, + cost: manhattanDistance(startingPosition, destination), + prev: undefined, + }; + let bestCandidate = current; + const minheap = MinHeap((p0, p1) => p0.cost > p1.cost); + while (current) { + if (pointsEqual(current.position, destination)) { + break; + } + if ( + manhattanDistance(current.position, destination) < + manhattanDistance(bestCandidate.position, destination) + ) { + bestCandidate = current; + } + for (const candidate of explore(current)) { + minheap.push(candidate); + } + current = minheap.pop(); + } + let newDestination = null; + if (!current) { + if (bestCandidate.length === 0) { + return null; + } + current = bestCandidate; + newDestination = current.position; + } + const densePath = []; + let facing = current.facing!; + while (current) { + densePath.push({ position: current.position, t: current.t, facing }); + facing = current.facing!; + current = current.prev; + } + densePath.reverse(); + + return { path: compressPath(densePath), newDestination }; +} + +export function blocked(game: Game, now: number, pos: Point, playerId?: GameId<'players'>) { + const otherPositions = [...game.world.players.values()] + .filter((p) => p.id !== playerId) + .map((p) => p.position); + return blockedWithPositions(pos, otherPositions, game.worldMap); +} + +export function blockedWithPositions(position: Point, otherPositions: Point[], map: WorldMap) { + if (isNaN(position.x) || isNaN(position.y)) { + throw new Error(`NaN position in ${JSON.stringify(position)}`); + } + if (position.x < 0 || position.y < 0 || position.x >= map.width || position.y >= map.height) { + return 'out of bounds'; + } + for (const layer of map.objectTiles) { + if (layer[Math.floor(position.x)][Math.floor(position.y)] !== -1) { + return 'world blocked'; + } + } + for (const otherPosition of otherPositions) { + if (distance(otherPosition, position) < COLLISION_THRESHOLD) { + return 'player'; + } + } + return null; +} diff --git a/patches/convex/aiTown/player.ts b/patches/convex/aiTown/player.ts new file mode 100644 index 0000000000000000000000000000000000000000..871dab3c11f072e3d353aa87acd4f761d955a095 --- /dev/null +++ b/patches/convex/aiTown/player.ts @@ -0,0 +1,310 @@ +import { Infer, ObjectType, v } from 'convex/values'; +import { Point, Vector, path, point, vector } from '../util/types'; +import { GameId, parseGameId } from './ids'; +import { playerId } from './ids'; +import { + PATHFINDING_TIMEOUT, + PATHFINDING_BACKOFF, + HUMAN_IDLE_TOO_LONG, + MAX_HUMAN_PLAYERS, + MAX_PATHFINDS_PER_STEP, +} from '../constants'; +import { pointsEqual, pathPosition } from '../util/geometry'; +import { Game } from './game'; +import { stopPlayer, findRoute, blocked, movePlayer } from './movement'; +import { inputHandler } from './inputHandler'; +import { characters } from '../../data/characters'; +import { PlayerDescription } from './playerDescription'; + +const pathfinding = v.object({ + destination: point, + started: v.number(), + state: v.union( + v.object({ + kind: v.literal('needsPath'), + }), + v.object({ + kind: v.literal('waiting'), + until: v.number(), + }), + v.object({ + kind: v.literal('moving'), + path, + }), + ), +}); +export type Pathfinding = Infer; + +export const activity = v.object({ + description: v.string(), + emoji: v.optional(v.string()), + until: v.number(), +}); +export type Activity = Infer; + +export const serializedPlayer = { + id: playerId, + human: v.optional(v.string()), + pathfinding: v.optional(pathfinding), + activity: v.optional(activity), + + // The last time they did something. + lastInput: v.number(), + + position: point, + facing: vector, + speed: v.number(), +}; +export type SerializedPlayer = ObjectType; + +export class Player { + id: GameId<'players'>; + human?: string; + pathfinding?: Pathfinding; + activity?: Activity; + + lastInput: number; + + position: Point; + facing: Vector; + speed: number; + + constructor(serialized: SerializedPlayer) { + const { id, human, pathfinding, activity, lastInput, position, facing, speed } = serialized; + this.id = parseGameId('players', id); + this.human = human; + this.pathfinding = pathfinding; + this.activity = activity; + this.lastInput = lastInput; + this.position = position; + this.facing = facing; + this.speed = speed; + } + + tick(game: Game, now: number) { + if (this.human && this.lastInput < now - HUMAN_IDLE_TOO_LONG) { + this.leave(game, now); + } + } + + tickPathfinding(game: Game, now: number) { + // There's nothing to do if we're not moving. + const { pathfinding, position } = this; + if (!pathfinding) { + return; + } + + // Stop pathfinding if we've reached our destination. + if (pathfinding.state.kind === 'moving' && pointsEqual(pathfinding.destination, position)) { + stopPlayer(this); + } + + // Stop pathfinding if we've timed out. + if (pathfinding.started + PATHFINDING_TIMEOUT < now) { + console.warn(`Timing out pathfinding for ${this.id}`); + stopPlayer(this); + } + + // Transition from "waiting" to "needsPath" if we're past the deadline. + if (pathfinding.state.kind === 'waiting' && pathfinding.state.until < now) { + pathfinding.state = { kind: 'needsPath' }; + } + + // Perform pathfinding if needed. + if (pathfinding.state.kind === 'needsPath' && game.numPathfinds < MAX_PATHFINDS_PER_STEP) { + game.numPathfinds++; + if (game.numPathfinds === MAX_PATHFINDS_PER_STEP) { + console.warn(`Reached max pathfinds for this step`); + } + const route = findRoute(game, now, this, pathfinding.destination); + if (route === null) { + console.log(`Failed to route to ${JSON.stringify(pathfinding.destination)}`); + stopPlayer(this); + } else { + if (route.newDestination) { + console.warn( + `Updating destination from ${JSON.stringify( + pathfinding.destination, + )} to ${JSON.stringify(route.newDestination)}`, + ); + pathfinding.destination = route.newDestination; + } + pathfinding.state = { kind: 'moving', path: route.path }; + } + } + } + + tickPosition(game: Game, now: number) { + // There's nothing to do if we're not moving. + if (!this.pathfinding || this.pathfinding.state.kind !== 'moving') { + this.speed = 0; + return; + } + + // Compute a candidate new position and check if it collides + // with anything. + const candidate = pathPosition(this.pathfinding.state.path as any, now); + if (!candidate) { + console.warn(`Path out of range of ${now} for ${this.id}`); + return; + } + const { position, facing, velocity } = candidate; + const collisionReason = blocked(game, now, position, this.id); + if (collisionReason !== null) { + const backoff = Math.random() * PATHFINDING_BACKOFF; + console.warn(`Stopping path for ${this.id}, waiting for ${backoff}ms: ${collisionReason}`); + this.pathfinding.state = { + kind: 'waiting', + until: now + backoff, + }; + return; + } + // Update the player's location. + this.position = position; + this.facing = facing; + this.speed = velocity; + } + + static join( + game: Game, + now: number, + name: string, + character: string, + description: string, + tokenIdentifier?: string, + ) { + if (tokenIdentifier) { + let numHumans = 0; + for (const player of game.world.players.values()) { + if (player.human) { + numHumans++; + } + if (player.human === tokenIdentifier) { + throw new Error(`You are already in this game!`); + } + } + if (numHumans >= MAX_HUMAN_PLAYERS) { + throw new Error(`Only ${MAX_HUMAN_PLAYERS} human players allowed at once.`); + } + } + let position; + for (let attempt = 0; attempt < 10; attempt++) { + const candidate = { + x: Math.floor(Math.random() * game.worldMap.width), + y: Math.floor(Math.random() * game.worldMap.height), + }; + if (blocked(game, now, candidate)) { + continue; + } + position = candidate; + break; + } + if (!position) { + throw new Error(`Failed to find a free position!`); + } + const facingOptions = [ + { dx: 1, dy: 0 }, + { dx: -1, dy: 0 }, + { dx: 0, dy: 1 }, + { dx: 0, dy: -1 }, + ]; + const facing = facingOptions[Math.floor(Math.random() * facingOptions.length)]; + if (!characters.find((c) => c.name === character)) { + throw new Error(`Invalid character: ${character}`); + } + const playerId = game.allocId('players'); + game.world.players.set( + playerId, + new Player({ + id: playerId, + human: tokenIdentifier, + lastInput: now, + position, + facing, + speed: 0, + }), + ); + game.playerDescriptions.set( + playerId, + new PlayerDescription({ + playerId, + character, + description, + name, + }), + ); + game.descriptionsModified = true; + return playerId; + } + + leave(game: Game, now: number) { + // Stop our conversation if we're leaving the game. + const conversation = [...game.world.conversations.values()].find((c) => + c.participants.has(this.id), + ); + if (conversation) { + conversation.stop(game, now); + } + game.world.players.delete(this.id); + } + + serialize(): SerializedPlayer { + const { id, human, pathfinding, activity, lastInput, position, facing, speed } = this; + return { + id, + human, + pathfinding, + activity, + lastInput, + position, + facing, + speed, + }; + } +} + +export const playerInputs = { + join: inputHandler({ + args: { + name: v.string(), + character: v.string(), + description: v.string(), + tokenIdentifier: v.optional(v.string()), + }, + handler: (game, now, args) => { + Player.join(game, now, args.name, args.character, args.description, args.tokenIdentifier); + return null; + }, + }), + leave: inputHandler({ + args: { playerId }, + handler: (game, now, args) => { + const playerId = parseGameId('players', args.playerId); + const player = game.world.players.get(playerId); + if (!player) { + throw new Error(`Invalid player ID ${playerId}`); + } + player.leave(game, now); + return null; + }, + }), + moveTo: inputHandler({ + args: { + playerId, + destination: v.union(point, v.null()), + }, + handler: (game, now, args) => { + const playerId = parseGameId('players', args.playerId); + const player = game.world.players.get(playerId); + if (!player) { + throw new Error(`Invalid player ID ${playerId}`); + } + if (args.destination) { + movePlayer(game, now, player, args.destination); + } else { + stopPlayer(player); + } + return null; + }, + }), +}; diff --git a/patches/convex/aiTown/playerDescription.ts b/patches/convex/aiTown/playerDescription.ts new file mode 100644 index 0000000000000000000000000000000000000000..901f3bbab86ffa46699f3387ff17cb0999b89efc --- /dev/null +++ b/patches/convex/aiTown/playerDescription.ts @@ -0,0 +1,35 @@ +import { ObjectType, v } from 'convex/values'; +import { GameId, parseGameId, playerId } from './ids'; + +export const serializedPlayerDescription = { + playerId, + name: v.string(), + description: v.string(), + character: v.string(), +}; +export type SerializedPlayerDescription = ObjectType; + +export class PlayerDescription { + playerId: GameId<'players'>; + name: string; + description: string; + character: string; + + constructor(serialized: SerializedPlayerDescription) { + const { playerId, name, description, character } = serialized; + this.playerId = parseGameId('players', playerId); + this.name = name; + this.description = description; + this.character = character; + } + + serialize(): SerializedPlayerDescription { + const { playerId, name, description, character } = this; + return { + playerId, + name, + description, + character, + }; + } +} diff --git a/patches/convex/aiTown/schema.ts b/patches/convex/aiTown/schema.ts new file mode 100644 index 0000000000000000000000000000000000000000..20cdc40986437325fd92dc150f9d270ab9c90b9f --- /dev/null +++ b/patches/convex/aiTown/schema.ts @@ -0,0 +1,79 @@ +import { v } from 'convex/values'; +import { defineTable } from 'convex/server'; +import { serializedPlayer } from './player'; +import { serializedPlayerDescription } from './playerDescription'; +import { serializedAgent } from './agent'; +import { serializedAgentDescription } from './agentDescription'; +import { serializedWorld } from './world'; +import { serializedWorldMap } from './worldMap'; +import { serializedConversation } from './conversation'; +import { conversationId, playerId } from './ids'; + +export const aiTownTables = { + // This table has a single document that stores all players, conversations, and agents. This + // data is small and changes regularly over time. + worlds: defineTable({ ...serializedWorld }), + + // Worlds can be started or stopped by the developer or paused for inactivity, and this + // infrequently changing document tracks this world state. + worldStatus: defineTable({ + worldId: v.id('worlds'), + isDefault: v.boolean(), + engineId: v.id('engines'), + lastViewed: v.number(), + status: v.union(v.literal('running'), v.literal('stoppedByDeveloper'), v.literal('inactive')), + }).index('worldId', ['worldId']), + + // This table contains the map data for a given world. Since it's a bit larger than the player + // state and infrequently changes, we store it in a separate table. + maps: defineTable({ + worldId: v.id('worlds'), + ...serializedWorldMap, + }).index('worldId', ['worldId']), + + // Human readable text describing players and agents that's stored in separate tables, just like `maps`. + playerDescriptions: defineTable({ + worldId: v.id('worlds'), + ...serializedPlayerDescription, + }).index('worldId', ['worldId', 'playerId']), + agentDescriptions: defineTable({ + worldId: v.id('worlds'), + ...serializedAgentDescription, + }).index('worldId', ['worldId', 'agentId']), + + //The game engine doesn't want to track players that have left or conversations that are over, since + // it wants to keep its managed state small. However, we may want to look at old conversations in the + // UI or from the agent code. So, whenever we delete an entry from within the world's document, we + // "archive" it within these tables. + archivedPlayers: defineTable({ worldId: v.id('worlds'), ...serializedPlayer }).index('worldId', [ + 'worldId', + 'id', + ]), + archivedConversations: defineTable({ + worldId: v.id('worlds'), + id: conversationId, + creator: playerId, + created: v.number(), + ended: v.number(), + lastMessage: serializedConversation.lastMessage, + numMessages: serializedConversation.numMessages, + participants: v.array(playerId), + }).index('worldId', ['worldId', 'id']), + archivedAgents: defineTable({ worldId: v.id('worlds'), ...serializedAgent }).index('worldId', [ + 'worldId', + 'id', + ]), + + // The agent layer wants to know what the last (completed) conversation was between two players, + // so this table represents a labelled graph indicating which players have talked to each other. + participatedTogether: defineTable({ + worldId: v.id('worlds'), + conversationId, + player1: playerId, + player2: playerId, + ended: v.number(), + }) + .index('edge', ['worldId', 'player1', 'player2', 'ended']) + .index('conversation', ['worldId', 'player1', 'conversationId']) + .index('playerHistory', ['worldId', 'player1', 'ended']), +}; diff --git a/patches/convex/aiTown/world.ts b/patches/convex/aiTown/world.ts new file mode 100644 index 0000000000000000000000000000000000000000..c107fe9a980677a4ec19badd65c84905fad9c1be --- /dev/null +++ b/patches/convex/aiTown/world.ts @@ -0,0 +1,70 @@ +import { ObjectType, v } from 'convex/values'; +import { Conversation, serializedConversation } from './conversation'; +import { Player, serializedPlayer } from './player'; +import { Agent, serializedAgent } from './agent'; +import { GameId, parseGameId, playerId } from './ids'; +import { parseMap } from '../util/object'; +import { DayNightCycle, SerializedDayNightCycle, dayNightCycleSchema } from './dayNightCycle'; + +export const historicalLocations = v.array( + v.object({ + playerId, + location: v.bytes(), + }), +); + +export const serializedWorld = { + nextId: v.number(), + conversations: v.array(v.object(serializedConversation)), + players: v.array(v.object(serializedPlayer)), + agents: v.array(v.object(serializedAgent)), + historicalLocations: v.optional(historicalLocations), + dayNightCycle: v.object(dayNightCycleSchema), +}; +export type SerializedWorld = ObjectType; + +export class World { + nextId: number; + conversations: Map, Conversation>; + players: Map, Player>; + agents: Map, Agent>; + historicalLocations?: Map, ArrayBuffer>; + dayNightCycle: DayNightCycle; + + constructor(serialized: SerializedWorld) { + const { nextId, historicalLocations } = serialized; + + this.nextId = nextId; + this.conversations = parseMap(serialized.conversations, Conversation, (c) => c.id); + this.players = parseMap(serialized.players, Player, (p) => p.id); + this.agents = parseMap(serialized.agents, Agent, (a) => a.id); + this.dayNightCycle = new DayNightCycle(serialized.dayNightCycle); + + if (historicalLocations) { + this.historicalLocations = new Map(); + for (const { playerId, location } of historicalLocations) { + this.historicalLocations.set(parseGameId('players', playerId), location); + } + } + } + + playerConversation(player: Player): Conversation | undefined { + return [...this.conversations.values()].find((c) => c.participants.has(player.id)); + } + + serialize(): SerializedWorld { + return { + nextId: this.nextId, + conversations: [...this.conversations.values()].map((c) => c.serialize()), + players: [...this.players.values()].map((p) => p.serialize()), + agents: [...this.agents.values()].map((a) => a.serialize()), + historicalLocations: + this.historicalLocations && + [...this.historicalLocations.entries()].map(([playerId, location]) => ({ + playerId, + location, + })), + dayNightCycle: this.dayNightCycle.serialize(), + }; + } +} diff --git a/patches/convex/aiTown/worldMap.ts b/patches/convex/aiTown/worldMap.ts new file mode 100644 index 0000000000000000000000000000000000000000..7d16bcdc9eca5345466eafc6ebf6d308c4b8400c --- /dev/null +++ b/patches/convex/aiTown/worldMap.ts @@ -0,0 +1,94 @@ +import { Infer, ObjectType, v } from 'convex/values'; + +// `layer[position.x][position.y]` is the tileIndex or -1 if empty. +const tileLayer = v.array(v.array(v.number())); +export type TileLayer = Infer; + +const animatedSprite = { + x: v.number(), + y: v.number(), + w: v.number(), + h: v.number(), + layer: v.number(), + sheet: v.string(), + animation: v.string(), +}; +export type AnimatedSprite = ObjectType; + +export const serializedWorldMap = { + width: v.number(), + height: v.number(), + + tileSetUrl: v.string(), + tileSetAlternateUrl: v.string(), + // Width & height of tileset image, px. + tileSetDimX: v.number(), + tileSetDimY: v.number(), + + // Tile size in pixels (assume square) + tileDim: v.number(), + bgTiles: v.array(v.array(v.array(v.number()))), + decorTiles: v.array(v.array(v.array(v.number()))), + objectTiles: v.array(tileLayer), + bgTilesN: v.array(v.array(v.array(v.number()))), + decorTilesN: v.array(v.array(v.array(v.number()))), + objectTilesN: v.array(tileLayer), + animatedSprites: v.array(v.object(animatedSprite)), +}; +export type SerializedWorldMap = ObjectType; + +export class WorldMap { + width: number; + height: number; + + tileSetUrl: string; + tileSetAlternateUrl: string; + tileSetDimX: number; + tileSetDimY: number; + + tileDim: number; + + bgTiles: TileLayer[]; + decorTiles: TileLayer[]; + objectTiles: TileLayer[]; + bgTilesN: TileLayer[]; + decorTilesN: TileLayer[]; + objectTilesN: TileLayer[]; + animatedSprites: AnimatedSprite[]; + + constructor(serialized: SerializedWorldMap) { + this.width = serialized.width; + this.height = serialized.height; + this.tileSetUrl = serialized.tileSetUrl; + this.tileSetAlternateUrl = serialized.tileSetAlternateUrl; + this.tileSetDimX = serialized.tileSetDimX; + this.tileSetDimY = serialized.tileSetDimY; + this.tileDim = serialized.tileDim; + this.bgTiles = serialized.bgTiles; + this.decorTiles = serialized.decorTiles; + this.objectTiles = serialized.objectTiles; + this.bgTilesN = serialized.bgTilesN; + this.decorTilesN = serialized.decorTilesN; + this.objectTilesN = serialized.objectTilesN; + this.animatedSprites = serialized.animatedSprites; + } + + serialize(): SerializedWorldMap { + return { + width: this.width, + height: this.height, + tileSetUrl: this.tileSetUrl, + tileSetAlternateUrl: this.tileSetAlternateUrl, + tileSetDimX: this.tileSetDimX, + tileSetDimY: this.tileSetDimY, + tileDim: this.tileDim, + bgTiles: this.bgTiles, + objectTiles: this.objectTiles, + decorTiles:this.decorTiles, + bgTilesN: this.bgTilesN, + objectTilesN: this.objectTilesN, + decorTilesN:this.decorTilesN, + animatedSprites: this.animatedSprites, + }; + } +} diff --git a/patches/constants.ts b/patches/convex/constants.ts similarity index 94% rename from patches/constants.ts rename to patches/convex/constants.ts index c02f8420318c74a12648e5af49eb26235ab78239..3e747fe6ce09aa3b92bc9b4a488a18d229209c82 100644 --- a/patches/constants.ts +++ b/patches/convex/constants.ts @@ -1,78 +1,81 @@ -// export const ACTION_TIMEOUT = 120_000; // more time for local dev -export const ACTION_TIMEOUT = 60_000; // normally fine - -export const IDLE_WORLD_TIMEOUT = 5 * 60 * 1000; -export const WORLD_HEARTBEAT_INTERVAL = 60 * 1000; - -export const MAX_STEP = 10 * 60 * 1000; -export const TICK = 16; -export const STEP_INTERVAL = 1000; - -export const PATHFINDING_TIMEOUT = 60 * 1000; -export const PATHFINDING_BACKOFF = 1000; -export const CONVERSATION_DISTANCE = 1.3; -export const MIDPOINT_THRESHOLD = 4; -export const TYPING_TIMEOUT = 15 * 1000; -export const COLLISION_THRESHOLD = 0.75; - -// How many human players can be in a world at once. -export const MAX_HUMAN_PLAYERS = 8; - -// Don't talk to anyone for 15s after having a conversation. -export const CONVERSATION_COOLDOWN = 15000; - -// Don't do another activity for 10s after doing one. -export const ACTIVITY_COOLDOWN = 10_000; - -// Don't talk to a player within 60s of talking to them. -export const PLAYER_CONVERSATION_COOLDOWN = 60000; - -// Invite 80% of invites that come from other agents. -export const INVITE_ACCEPT_PROBABILITY = 0.8; - -// Wait for 1m for invites to be accepted. -export const INVITE_TIMEOUT = 60000; - -// Wait for another player to say something before jumping in. -export const AWKWARD_CONVERSATION_TIMEOUT = 60_000; // more time locally -// export const AWKWARD_CONVERSATION_TIMEOUT = 20_000; - -// Leave a conversation after participating too long. -export const MAX_CONVERSATION_DURATION = 10 * 60_000; // more time locally -// export const MAX_CONVERSATION_DURATION = 2 * 60_000; - -// Leave a conversation if it has more than 8 messages; -export const MAX_CONVERSATION_MESSAGES = 8; - -// Wait for 1s after sending an input to the engine. We can remove this -// once we can await on an input being processed. -export const INPUT_DELAY = 1000; - -// How many memories to get from the agent's memory. -// This is over-fetched by 10x so we can prioritize memories by more than relevance. -export const NUM_MEMORIES_TO_SEARCH = 1; - -// Wait for at least two seconds before sending another message. -export const MESSAGE_COOLDOWN = 2000; - -// Don't run a turn of the agent more than once a second. -export const AGENT_WAKEUP_THRESHOLD = 1000; - -// How old we let memories be before we vacuum them -export const VACUUM_MAX_AGE = 2 * 7 * 24 * 60 * 60 * 1000; -export const DELETE_BATCH_SIZE = 64; - -export const HUMAN_IDLE_TOO_LONG = 5 * 60 * 1000; - -export const ACTIVITIES = [ - { description: 'reading a book', emoji: '📖', duration: 60_000 }, - { description: 'daydreaming', emoji: '🤔', duration: 60_000 }, - { description: 'gardening', emoji: '🥕', duration: 60_000 }, -]; - -export const ENGINE_ACTION_DURATION = 30000; - -// Bound the number of pathfinding searches we do per game step. -export const MAX_PATHFINDS_PER_STEP = 16; - -export const DEFAULT_NAME = 'Me'; +// export const ACTION_TIMEOUT = 120_000; // more time for local dev +export const ACTION_TIMEOUT = 60_000; // normally fine + +export const IDLE_WORLD_TIMEOUT = 5 * 60 * 1000; +export const WORLD_HEARTBEAT_INTERVAL = 60 * 1000; + +export const MAX_STEP = 10 * 60 * 1000; +export const TICK = 16; +export const STEP_INTERVAL = 1000; + +export const PATHFINDING_TIMEOUT = 60 * 1000; +export const PATHFINDING_BACKOFF = 1000; +export const CONVERSATION_DISTANCE = 1.3; +export const MIDPOINT_THRESHOLD = 4; +export const TYPING_TIMEOUT = 15 * 1000; +export const COLLISION_THRESHOLD = 0.75; + +// How many human players can be in a world at once. +export const MAX_HUMAN_PLAYERS = 8; + +// Don't talk to anyone for 15s after having a conversation. +export const CONVERSATION_COOLDOWN = 15000; + +// Don't do another activity for 10s after doing one. +export const ACTIVITY_COOLDOWN = 10_000; + +// Don't talk to a player within 60s of talking to them. +export const PLAYER_CONVERSATION_COOLDOWN = 60000; + +// Invite 80% of invites that come from other agents. +export const INVITE_ACCEPT_PROBABILITY = 0.8; + +// Wait for 1m for invites to be accepted. +export const INVITE_TIMEOUT = 60000; + +// Wait for another player to say something before jumping in. +export const AWKWARD_CONVERSATION_TIMEOUT = 60_000; // more time locally +// export const AWKWARD_CONVERSATION_TIMEOUT = 20_000; + +// Leave a conversation after participating too long. +export const MAX_CONVERSATION_DURATION = 10 * 60_000; // more time locally +// export const MAX_CONVERSATION_DURATION = 2 * 60_000; + +// Leave a conversation if it has more than 8 messages; +export const MAX_CONVERSATION_MESSAGES = 8; + +// Wait for 1s after sending an input to the engine. We can remove this +// once we can await on an input being processed. +export const INPUT_DELAY = 1000; + +// How many memories to get from the agent's memory. +// This is over-fetched by 10x so we can prioritize memories by more than relevance. +export const NUM_MEMORIES_TO_SEARCH = 1; + +// Wait for at least two seconds before sending another message. +export const MESSAGE_COOLDOWN = 2000; + +// Don't run a turn of the agent more than once a second. +export const AGENT_WAKEUP_THRESHOLD = 1000; + +// How old we let memories be before we vacuum them +export const VACUUM_MAX_AGE = 2 * 7 * 24 * 60 * 60 * 1000; +export const DELETE_BATCH_SIZE = 64; + +export const HUMAN_IDLE_TOO_LONG = 5 * 60 * 1000; + +export const ACTIVITIES = [ + { description: 'reading a book', emoji: '📖', duration: 60_000 }, + { description: 'daydreaming', emoji: '🤔', duration: 60_000 }, + { description: 'gardening', emoji: '🥕', duration: 60_000 }, +]; + +export const ENGINE_ACTION_DURATION = 30000; + +export const DAY_DURATION = 120000; +export const NIGHT_DURATION = 30000; + +// Bound the number of pathfinding searches we do per game step. +export const MAX_PATHFINDS_PER_STEP = 16; + +export const DEFAULT_NAME = 'Me'; diff --git a/patches/convex/crons.ts b/patches/convex/crons.ts new file mode 100644 index 0000000000000000000000000000000000000000..9e6c1977a1f82ef660eee97dc9d0c9d2d06ecdfa --- /dev/null +++ b/patches/convex/crons.ts @@ -0,0 +1,89 @@ +import { cronJobs } from 'convex/server'; +import { DELETE_BATCH_SIZE, IDLE_WORLD_TIMEOUT, VACUUM_MAX_AGE } from './constants'; +import { internal } from './_generated/api'; +import { internalMutation } from './_generated/server'; +import { TableNames } from './_generated/dataModel'; +import { v } from 'convex/values'; + +const crons = cronJobs(); + +crons.interval( + 'stop inactive worlds', + { seconds: IDLE_WORLD_TIMEOUT / 1000 }, + internal.world.stopInactiveWorlds, +); + +crons.interval('restart dead worlds', { seconds: 60 }, internal.world.restartDeadWorlds); + +crons.daily('vacuum old entries', { hourUTC: 4, minuteUTC: 20 }, internal.crons.vacuumOldEntries); + +export default crons; + +const TablesToVacuum: TableNames[] = [ + // Un-comment this to also clean out old conversations. + // 'conversationMembers', 'conversations', 'messages', + + // Inputs aren't useful unless you're trying to replay history. + // If you want to support that, you should add a snapshot table, so you can + // replay from a certain time period. Or stop vacuuming inputs and replay from + // the beginning of time + 'inputs', + + // We can keep memories without their embeddings for inspection, but we won't + // retrieve them when searching memories via vector search. + 'memories', + // We can vacuum fewer tables without serious consequences, but the only + // one that will cause issues over time is having >>100k vectors. + 'memoryEmbeddings', +]; + +export const vacuumOldEntries = internalMutation({ + args: {}, + handler: async (ctx, args) => { + const before = Date.now() - VACUUM_MAX_AGE; + for (const tableName of TablesToVacuum) { + console.log(`Checking ${tableName}...`); + const exists = await ctx.db + .query(tableName) + .withIndex('by_creation_time', (q) => q.lt('_creationTime', before)) + .first(); + if (exists) { + console.log(`Vacuuming ${tableName}...`); + await ctx.scheduler.runAfter(0, internal.crons.vacuumTable, { + tableName, + before, + cursor: null, + soFar: 0, + }); + } + } + }, +}); + +export const vacuumTable = internalMutation({ + args: { + tableName: v.string(), + before: v.number(), + cursor: v.union(v.string(), v.null()), + soFar: v.number(), + }, + handler: async (ctx, { tableName, before, cursor, soFar }) => { + const results = await ctx.db + .query(tableName as TableNames) + .withIndex('by_creation_time', (q) => q.lt('_creationTime', before)) + .paginate({ cursor, numItems: DELETE_BATCH_SIZE }); + for (const row of results.page) { + await ctx.db.delete(row._id); + } + if (!results.isDone) { + await ctx.scheduler.runAfter(0, internal.crons.vacuumTable, { + tableName, + before, + soFar: results.page.length + soFar, + cursor: results.continueCursor, + }); + } else { + console.log(`Vacuumed ${soFar + results.page.length} entries from ${tableName}`); + } + }, +}); diff --git a/patches/convex/engine/abstractGame.ts b/patches/convex/engine/abstractGame.ts new file mode 100644 index 0000000000000000000000000000000000000000..13f983ca133bfd0300d694c9d958a200dc29ccd7 --- /dev/null +++ b/patches/convex/engine/abstractGame.ts @@ -0,0 +1,199 @@ +import { ConvexError, Infer, Value, v } from 'convex/values'; +import { Doc, Id } from '../_generated/dataModel'; +import { ActionCtx, DatabaseReader, MutationCtx, internalQuery } from '../_generated/server'; +import { engine } from '../engine/schema'; +import { internal } from '../_generated/api'; + +export abstract class AbstractGame { + abstract tickDuration: number; + abstract stepDuration: number; + abstract maxTicksPerStep: number; + abstract maxInputsPerStep: number; + + constructor(public engine: Doc<'engines'>) {} + + abstract handleInput(now: number, name: string, args: object): Value; + abstract tick(now: number): void; + + // Optional callback at the beginning of each step. + beginStep(now: number) {} + abstract saveStep(ctx: ActionCtx, engineUpdate: EngineUpdate): Promise; + + async runStep(ctx: ActionCtx, now: number) { + const inputs = await ctx.runQuery(internal.engine.abstractGame.loadInputs, { + engineId: this.engine._id, + processedInputNumber: this.engine.processedInputNumber, + max: this.maxInputsPerStep, + }); + + const lastStepTs = this.engine.currentTime; + const startTs = lastStepTs ? lastStepTs + this.tickDuration : now; + let currentTs = startTs; + let inputIndex = 0; + let numTicks = 0; + let processedInputNumber = this.engine.processedInputNumber; + const completedInputs = []; + + this.beginStep(currentTs); + + while (numTicks < this.maxTicksPerStep) { + numTicks += 1; + + // Collect all of the inputs for this tick. + const tickInputs = []; + while (inputIndex < inputs.length) { + const input = inputs[inputIndex]; + if (input.received > currentTs) { + break; + } + inputIndex += 1; + processedInputNumber = input.number; + tickInputs.push(input); + } + + // Feed the inputs to the game. + for (const input of tickInputs) { + let returnValue; + try { + const value = this.handleInput(currentTs, input.name, input.args); + returnValue = { kind: 'ok' as const, value }; + } catch (e: any) { + console.error(`Input ${input._id} failed: ${e.message}`); + returnValue = { kind: 'error' as const, message: e.message }; + } + completedInputs.push({ inputId: input._id, returnValue }); + } + + // Simulate the game forward one tick. + this.tick(currentTs); + + const candidateTs = currentTs + this.tickDuration; + if (now < candidateTs) { + break; + } + currentTs = candidateTs; + } + + // Commit the step by moving time forward, consuming our inputs, and saving the game's state. + const expectedGenerationNumber = this.engine.generationNumber; + this.engine.currentTime = currentTs; + this.engine.lastStepTs = lastStepTs; + this.engine.generationNumber += 1; + this.engine.processedInputNumber = processedInputNumber; + const { _id, _creationTime, ...engine } = this.engine; + const engineUpdate = { engine, completedInputs, expectedGenerationNumber }; + await this.saveStep(ctx, engineUpdate); + + console.debug(`Simulated from ${startTs} to ${currentTs} (${currentTs - startTs}ms)`); + } +} + +const completedInput = v.object({ + inputId: v.id('inputs'), + returnValue: v.union( + v.object({ + kind: v.literal('ok'), + value: v.any(), + }), + v.object({ + kind: v.literal('error'), + message: v.string(), + }), + ), +}); + +export const engineUpdate = v.object({ + engine, + expectedGenerationNumber: v.number(), + completedInputs: v.array(completedInput), +}); +export type EngineUpdate = Infer; + +export async function loadEngine( + db: DatabaseReader, + engineId: Id<'engines'>, + generationNumber: number, +) { + const engine = await db.get(engineId); + if (!engine) { + throw new Error(`No engine found with id ${engineId}`); + } + if (!engine.running) { + throw new ConvexError({ + kind: 'engineNotRunning', + message: `Engine ${engineId} is not running`, + }); + } + if (engine.generationNumber !== generationNumber) { + throw new ConvexError({ kind: 'generationNumber', message: 'Generation number mismatch' }); + } + return engine; +} + +export async function engineInsertInput( + ctx: MutationCtx, + engineId: Id<'engines'>, + name: string, + args: any, +): Promise> { + const now = Date.now(); + const prevInput = await ctx.db + .query('inputs') + .withIndex('byInputNumber', (q) => q.eq('engineId', engineId)) + .order('desc') + .first(); + const number = prevInput ? prevInput.number + 1 : 0; + const inputId = await ctx.db.insert('inputs', { + engineId, + number, + name, + args, + received: now, + }); + return inputId; +} + +export const loadInputs = internalQuery({ + args: { + engineId: v.id('engines'), + processedInputNumber: v.optional(v.number()), + max: v.number(), + }, + handler: async (ctx, args) => { + return await ctx.db + .query('inputs') + .withIndex('byInputNumber', (q) => + q.eq('engineId', args.engineId).gt('number', args.processedInputNumber ?? -1), + ) + .order('asc') + .take(args.max); + }, +}); + +export async function applyEngineUpdate( + ctx: MutationCtx, + engineId: Id<'engines'>, + update: EngineUpdate, +) { + const engine = await loadEngine(ctx.db, engineId, update.expectedGenerationNumber); + if ( + engine.currentTime && + update.engine.currentTime && + update.engine.currentTime < engine.currentTime + ) { + throw new Error('Time moving backwards'); + } + await ctx.db.replace(engine._id, update.engine); + + for (const completedInput of update.completedInputs) { + const input = await ctx.db.get(completedInput.inputId); + if (!input) { + throw new Error(`Input ${completedInput.inputId} not found`); + } + if (input.returnValue) { + throw new Error(`Input ${completedInput.inputId} already completed`); + } + input.returnValue = completedInput.returnValue; + await ctx.db.replace(input._id, input); + } +} diff --git a/patches/convex/engine/historicalObject.test.ts b/patches/convex/engine/historicalObject.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..312651bd6538d960233d13b1204dd40ee402dc78 --- /dev/null +++ b/patches/convex/engine/historicalObject.test.ts @@ -0,0 +1,47 @@ +import { History, packSampleRecord, unpackSampleRecord } from './historicalObject'; + +describe('HistoricalObject', () => { + test('pack sample record roundtrips', () => { + let data: Record = { + x: { + initialValue: 0, + samples: [ + { time: 1696021246740, value: 1 }, + { time: 1696021246756, value: 2 }, + { time: 1696021246772, value: 3 }, + { time: 1696021246788, value: 4 }, + ], + }, + y: { + initialValue: 140.2, + samples: [ + { time: 1696021246740, value: 169.7 }, + { time: 1696021246756, value: 237.59 }, + { time: 1696021246772, value: 344.44 }, + { time: 1696021246788, value: 489.13 }, + ], + }, + }; + const fields = [ + { name: 'x', precision: 4 }, + { name: 'y', precision: 4 }, + ]; + const packed = packSampleRecord(fields, data); + const unpacked = unpackSampleRecord(fields, packed); + const maxError = Math.max(1 / (1 << 4), 1e-8); + + expect(Object.keys(data)).toEqual(Object.keys(unpacked)); + for (const key of Object.keys(data)) { + const { initialValue, samples } = data[key]; + const { initialValue: unpackedInitialValue, samples: unpackedSamples } = unpacked[key]; + expect(Math.abs(initialValue - unpackedInitialValue)).toBeLessThanOrEqual(maxError); + expect(samples.length).toEqual(unpackedSamples.length); + for (let i = 0; i < samples.length; i++) { + const sample = samples[i]; + const unpackedSample = unpackedSamples[i]; + expect(sample.time).toEqual(unpackedSample.time); + expect(Math.abs(sample.value - unpackedSample.value)).toBeLessThanOrEqual(maxError); + } + } + }); +}); diff --git a/patches/convex/engine/historicalObject.ts b/patches/convex/engine/historicalObject.ts new file mode 100644 index 0000000000000000000000000000000000000000..d9f9f6130df39e5e90dd5de16dbc86371ebc86ca --- /dev/null +++ b/patches/convex/engine/historicalObject.ts @@ -0,0 +1,355 @@ +import { xxHash32 } from '../util/xxhash'; +import { compressSigned, uncompressSigned } from '../util/FastIntegerCompression'; +import { + runLengthEncode, + deltaEncode, + quantize, + deltaDecode, + runLengthDecode, + unquantize, +} from '../util/compression'; + +// `HistoricalObject`s require the developer to pass in the +// field names that'll be tracked and sent down to the client. +// +// By default, the historical tracking will round each floating point +// value to an integer. The developer can specify more or less precision +// via the `precision` parameter: the table's quantization will maintain +// less than `1 / 2^precision` error. Note that higher precision values +// imply less error. +export type FieldConfig = Array; + +// `HistoricalObject`s support at most 16 fields. +const MAX_FIELDS = 16; + +const PACKED_VERSION = 1; + +type NormalizedFieldConfig = Array<{ + name: string; + precision: number; +}>; + +// The `History` structure represents the history of a continuous +// value over all bounded time. Each sample represents a line +// segment that's extends to the previous sample's time inclusively +// and to the sample's time non-inclusively. We track an `initialValue` +// that goes to `-\infty` up until the first sample, and the final +// sample persists out to `+\infty`. +// ``` +// ^ +// position +// | +// samples[0].value - | x---------------o +// | +// samples[1].value - | x--------> +// | +// initialValue - <---------o +// | +// ------------------------------> time +// | | +// samples[0].time samples[1].time +// ``` +export type History = { + initialValue: number; + samples: Sample[]; +}; + +export type Sample = { + time: number; + value: number; +}; + +// `HistoricalObject` tracks a set of numeric fields over time and +// supports compressing the fields' histories into a binary buffer. +// This can be useful for continuous properties like position, where +// we'd want to smoothly replay their tick-by-tick progress at a high +// frame rate on the client. +// +// `HistoricalObject`s have a few limitations: +// - Documents in a historical can only have up to 16 fields. +// - The historical tracking only applies to a specified list of fields, +// and these fields must match between the client and server. +export class HistoricalObject> { + startTs?: number; + + fieldConfig: NormalizedFieldConfig; + + data: T; + history: Record = {}; + + constructor(fields: FieldConfig, initialValue: T) { + if (fields.length >= MAX_FIELDS) { + throw new Error(`HistoricalObject can have at most ${MAX_FIELDS} fields.`); + } + this.fieldConfig = normalizeFieldConfig(fields); + this.checkShape(initialValue); + this.data = initialValue; + } + + historyLength() { + return Object.values(this.history) + .map((h) => h.samples.length) + .reduce((a, b) => a + b, 0); + } + + checkShape(data: any) { + for (const [key, value] of Object.entries(data)) { + if (!this.fieldConfig.find((f) => f.name === key)) { + throw new Error(`Cannot set undeclared field '${key}'`); + } + if (typeof value !== 'number') { + throw new Error( + `HistoricalObject only supports numeric values, found: ${JSON.stringify(value)}`, + ); + } + } + } + + update(now: number, data: T) { + this.checkShape(data); + for (const [key, value] of Object.entries(data)) { + const currentValue = this.data[key]; + if (currentValue !== value) { + let history = this.history[key]; + if (!history) { + this.history[key] = history = { initialValue: currentValue, samples: [] }; + } + const { samples } = history; + let inserted = false; + if (samples.length > 0) { + const last = samples[samples.length - 1]; + if (now < last.time) { + throw new Error(`Server time moving backwards: ${now} < ${last.time}`); + } + if (now === last.time) { + last.value = value; + inserted = true; + } + } + if (!inserted) { + samples.push({ time: now, value }); + } + } + } + this.data = data; + } + + pack(): ArrayBuffer | null { + if (this.historyLength() === 0) { + return null; + } + return packSampleRecord(this.fieldConfig, this.history); + } +} + +// Pack (normalized) field configuration into a binary buffer. +// +// Format: +// ``` +// [ u8 version ] +// for each field config: +// [ u8 field name length ] +// [ UTF8 encoded field name ] +// [ u8 precision ] +// ``` +function packFieldConfig(fields: NormalizedFieldConfig) { + const out = new ArrayBuffer(1024); + const outView = new DataView(out); + let pos = 0; + + outView.setUint8(pos, PACKED_VERSION); + pos += 1; + + const encoder = new TextEncoder(); + for (const fieldConfig of fields) { + const name = encoder.encode(fieldConfig.name); + + outView.setUint8(pos, name.length); + pos += 1; + + new Uint8Array(out, pos, name.length).set(name); + pos += name.length; + + outView.setUint8(pos, fieldConfig.precision); + pos += 1; + } + return out.slice(0, pos); +} + +// Pack a document's sample record into a binary buffer. +// +// We encode each field's history with a few layered forms of +// compression: +// 1. Quantization: Turn each floating point number into an integer +// by multiplying by 2^precision and then `Math.floor()`. +// 2. Delta encoding: Assume that values are continuous and don't +// abruptly change over time, so their differences will be small. +// This step turns the large integers from (1) into small ones. +// 3. Run length encoding (optional): Assume that some quantities +// in the system will have constant velocity, so encode `k` +// repetitions of `n` as `[k, n]`. If run length encoding doesn't +// make (2) smaller, we skip it. +// 4. Varint encoding: Using FastIntegerCompression.js, we use a +// variable length integer encoding that uses fewer bytes for +// smaller numbers. +// +// Format: +// ``` +// [ 4 byte xxhash of packed field config ] +// +// for each set field: +// [ 0 0 0 useRLE? ] +// [ u4 field number ] +// +// Sample timestamps: +// [ u64le initial timestamp ] +// [ u16le timestamp buffer length ] +// [ vint(RLE(delta(remaining timestamps)))] +// +// Sample values: +// [ u16le value buffer length ] +// [ vint(RLE?(delta([initialValue, ...values])))] +// ``` +export function packSampleRecord( + fields: NormalizedFieldConfig, + sampleRecord: Record, +): ArrayBuffer { + const out = new ArrayBuffer(65536); + const outView = new DataView(out); + let pos = 0; + + const configHash = xxHash32(new Uint8Array(packFieldConfig(fields))); + outView.setUint32(pos, configHash, true); + pos += 4; + + for (let fieldNumber = 0; fieldNumber < fields.length; fieldNumber += 1) { + const { name, precision } = fields[fieldNumber]; + const history = sampleRecord[name]; + if (!history || history.samples.length === 0) { + continue; + } + + const timestamps = history.samples.map((s) => Math.floor(s.time)); + const initialTimestamp = timestamps[0]; + const encodedTimestamps = runLengthEncode(deltaEncode(timestamps.slice(1), initialTimestamp)); + const compressedTimestamps = compressSigned(encodedTimestamps); + if (compressedTimestamps.byteLength >= 1 << 16) { + throw new Error(`Compressed buffer too long: ${compressedTimestamps.byteLength}`); + } + + const values = [history.initialValue, ...history.samples.map((s) => s.value)]; + const quantized = quantize(values, precision); + const deltaEncoded = deltaEncode(quantized); + const runLengthEncoded = runLengthEncode(deltaEncoded); + + // Decide if we're going to run length encode the values based on whether + // it actually made the encoded buffer smaller. + const useRLE = runLengthEncoded.length < deltaEncoded.length; + let fieldHeader = fieldNumber; + if (useRLE) { + fieldHeader |= 1 << 4; + } + + const encoded = useRLE ? runLengthEncoded : deltaEncoded; + const compressed = compressSigned(encoded); + if (compressed.byteLength >= 1 << 16) { + throw new Error(`Compressed buffer too long: ${compressed.byteLength}`); + } + + outView.setUint8(pos, fieldHeader); + pos += 1; + + outView.setBigUint64(pos, BigInt(initialTimestamp), true); + pos += 8; + + outView.setUint16(pos, compressedTimestamps.byteLength, true); + pos += 2; + + new Uint8Array(out, pos, compressedTimestamps.byteLength).set( + new Uint8Array(compressedTimestamps), + ); + pos += compressedTimestamps.byteLength; + + outView.setUint16(pos, compressed.byteLength, true); + pos += 2; + + new Uint8Array(out, pos, compressed.byteLength).set(new Uint8Array(compressed)); + pos += compressed.byteLength; + } + + return out.slice(0, pos); +} + +export function unpackSampleRecord(fields: FieldConfig, buffer: ArrayBuffer) { + const view = new DataView(buffer); + let pos = 0; + + const normalizedFields = normalizeFieldConfig(fields); + const expectedConfigHash = xxHash32(new Uint8Array(packFieldConfig(normalizedFields))); + + const configHash = view.getUint32(pos, true); + pos += 4; + + if (configHash !== expectedConfigHash) { + throw new Error(`Config hash mismatch: ${configHash} !== ${expectedConfigHash}`); + } + + const out = {} as Record; + while (pos < buffer.byteLength) { + const fieldHeader = view.getUint8(pos); + pos += 1; + + const fieldNumber = fieldHeader & 0b00001111; + const useRLE = (fieldHeader & (1 << 4)) !== 0; + const fieldConfig = normalizedFields[fieldNumber]; + if (!fieldConfig) { + throw new Error(`Invalid field number: ${fieldNumber}`); + } + + const initialTimestamp = Number(view.getBigUint64(pos, true)); + pos += 8; + + const compressedTimestampLength = view.getUint16(pos, true); + pos += 2; + + const compressedTimestampBuffer = buffer.slice(pos, pos + compressedTimestampLength); + pos += compressedTimestampLength; + + const timestamps = [ + initialTimestamp, + ...deltaDecode( + runLengthDecode(uncompressSigned(compressedTimestampBuffer)), + initialTimestamp, + ), + ]; + + const compressedLength = view.getUint16(pos, true); + pos += 2; + + const compressedBuffer = buffer.slice(pos, pos + compressedLength); + pos += compressedLength; + + const encoded = uncompressSigned(compressedBuffer); + const deltaEncoded = useRLE ? runLengthDecode(encoded) : encoded; + const quantized = deltaDecode(deltaEncoded); + const values = unquantize(quantized, fieldConfig.precision); + + if (timestamps.length + 1 !== values.length) { + throw new Error(`Invalid sample record: ${timestamps.length} + 1 !== ${values.length}`); + } + const initialValue = values[0]; + const samples = []; + for (let i = 0; i < timestamps.length; i++) { + const time = timestamps[i]; + const value = values[i + 1]; + samples.push({ value, time }); + } + const history = { initialValue, samples }; + out[fieldConfig.name] = history; + } + return out; +} + +function normalizeFieldConfig(fields: FieldConfig): NormalizedFieldConfig { + return fields.map((f) => (typeof f === 'string' ? { name: f, precision: 0 } : f)); +} diff --git a/patches/convex/engine/schema.ts b/patches/convex/engine/schema.ts new file mode 100644 index 0000000000000000000000000000000000000000..79f8dff6feb28f7fd479c4bcf43eebaebd06c0d5 --- /dev/null +++ b/patches/convex/engine/schema.ts @@ -0,0 +1,56 @@ +import { defineTable } from 'convex/server'; +import { Infer, v } from 'convex/values'; + +const input = v.object({ + // Inputs are scoped to a single engine. + engineId: v.id('engines'), + + // Monotonically increasing input number within a world starting at 0. + number: v.number(), + + // Name of the input handler to run. + name: v.string(), + // Dynamically typed arguments and return value for the input handler. We'll + // provide type safety at a higher layer. + args: v.any(), + returnValue: v.optional( + v.union( + v.object({ + kind: v.literal('ok'), + value: v.any(), + }), + v.object({ + kind: v.literal('error'), + message: v.string(), + }), + ), + ), + + // Timestamp when the server received the input. This timestamp is best-effort, + // since we don't guarantee strict monotonicity here. So, an input may not get + // assigned to the engine step whose time interval contains this timestamp. + received: v.number(), +}); + +export const engine = v.object({ + // What is the current simulation time for the engine? Monotonically increasing. + currentTime: v.optional(v.number()), + // What was `currentTime` for the preceding step of the engine? + lastStepTs: v.optional(v.number()), + + // How far has the engine processed in the input queue? + processedInputNumber: v.optional(v.number()), + + running: v.boolean(), + + // Monotonically increasing counter that serializes all engine runs. If we ever + // end up with two steps overlapping in time, this counter will force them to + // conflict. + generationNumber: v.number(), +}); +export type Engine = Infer; + +export const engineTables = { + inputs: defineTable(input).index('byInputNumber', ['engineId', 'number']), + engines: defineTable(engine), +}; diff --git a/patches/convex/http.ts b/patches/convex/http.ts new file mode 100644 index 0000000000000000000000000000000000000000..9012b5887deaec70e1ff6be97a912b9bedca4628 --- /dev/null +++ b/patches/convex/http.ts @@ -0,0 +1,10 @@ +import { httpRouter } from 'convex/server'; +import { handleReplicateWebhook } from './music'; + +const http = httpRouter(); +http.route({ + path: '/replicate_webhook', + method: 'POST', + handler: handleReplicateWebhook, +}); +export default http; diff --git a/patches/convex/init.ts b/patches/convex/init.ts new file mode 100644 index 0000000000000000000000000000000000000000..7bbe0fa8ea782443e748b3674dacb74a0a1dd041 --- /dev/null +++ b/patches/convex/init.ts @@ -0,0 +1,125 @@ +import { v } from 'convex/values'; +import { internal } from './_generated/api'; +import { DatabaseReader, MutationCtx, mutation } from './_generated/server'; +import { Descriptions } from '../data/characters'; +import * as map from '../data/gentle'; +import { insertInput } from './aiTown/insertInput'; +import { Id } from './_generated/dataModel'; +import { createEngine } from './aiTown/main'; +import { ENGINE_ACTION_DURATION, DAY_DURATION, NIGHT_DURATION } from './constants'; +import { assertApiKey } from './util/llm'; + +const init = mutation({ + args: { + numAgents: v.optional(v.number()), + }, + handler: async (ctx, args) => { + assertApiKey(); + const { worldStatus, engine } = await getOrCreateDefaultWorld(ctx); + if (worldStatus.status !== 'running') { + console.warn( + `Engine ${engine._id} is not active! Run "npx convex run testing:resume" to restart it.`, + ); + return; + } + const shouldCreate = await shouldCreateAgents( + ctx.db, + worldStatus.worldId, + worldStatus.engineId, + ); + if (shouldCreate) { + const toCreate = args.numAgents !== undefined ? args.numAgents : Descriptions.length; + for (let i = 0; i < toCreate; i++) { + await insertInput(ctx, worldStatus.worldId, 'createAgent', { + descriptionIndex: i % Descriptions.length, + }); + } + } + }, +}); +export default init; + +async function getOrCreateDefaultWorld(ctx: MutationCtx) { + const now = Date.now(); + + let worldStatus = await ctx.db + .query('worldStatus') + .filter((q) => q.eq(q.field('isDefault'), true)) + .unique(); + if (worldStatus) { + const engine = (await ctx.db.get(worldStatus.engineId))!; + return { worldStatus, engine }; + } + + const engineId = await createEngine(ctx); + const engine = (await ctx.db.get(engineId))!; + const worldId = await ctx.db.insert('worlds', { + nextId: 0, + agents: [], + conversations: [], + players: [], + // initialize day & night cycle counter + dayNightCycle: { + currentTime: 0, + isDay: true, + dayDuration: DAY_DURATION, + nightDuration: NIGHT_DURATION, + }, + }); + const worldStatusId = await ctx.db.insert('worldStatus', { + engineId: engineId, + isDefault: true, + lastViewed: now, + status: 'running', + worldId: worldId, + }); + worldStatus = (await ctx.db.get(worldStatusId))!; + await ctx.db.insert('maps', { + worldId, + width: map.mapwidth, + height: map.mapheight, + tileSetUrl: map.tilesetpath, + tileSetAlternateUrl: map.tilesetalternatepath, + tileSetDimX: map.tilesetpxw, + tileSetDimY: map.tilesetpxh, + tileDim: map.tiledim, + bgTiles: map.bgtiles, + objectTiles: map.objmap, + decorTiles: map.decors, + bgTilesN: map.bgtilesN, + objectTilesN: map.objmapN, + decorTilesN: map.decorsN, + animatedSprites: map.animatedsprites, + }); + await ctx.scheduler.runAfter(0, internal.aiTown.main.runStep, { + worldId, + generationNumber: engine.generationNumber, + maxDuration: ENGINE_ACTION_DURATION, + }); + return { worldStatus, engine }; +} + +async function shouldCreateAgents( + db: DatabaseReader, + worldId: Id<'worlds'>, + engineId: Id<'engines'>, +) { + const world = await db.get(worldId); + if (!world) { + throw new Error(`Invalid world ID: ${worldId}`); + } + if (world.agents.length > 0) { + return false; + } + const unactionedJoinInputs = await db + .query('inputs') + .withIndex('byInputNumber', (q) => q.eq('engineId', engineId)) + .order('asc') + .filter((q) => q.eq(q.field('name'), 'createAgent')) + .filter((q) => q.eq(q.field('returnValue'), undefined)) + .collect(); + if (unactionedJoinInputs.length > 0) { + return false; + } + return true; +} diff --git a/patches/convex/messages.ts b/patches/convex/messages.ts new file mode 100644 index 0000000000000000000000000000000000000000..67bb42ae61f65546d8ebacb22bb16289db2999d0 --- /dev/null +++ b/patches/convex/messages.ts @@ -0,0 +1,53 @@ +import { v } from 'convex/values'; +import { mutation, query } from './_generated/server'; +import { insertInput } from './aiTown/insertInput'; +import { conversationId, playerId } from './aiTown/ids'; + +export const listMessages = query({ + args: { + worldId: v.id('worlds'), + conversationId, + }, + handler: async (ctx, args) => { + const messages = await ctx.db + .query('messages') + .withIndex('conversationId', (q) => q.eq('worldId', args.worldId).eq('conversationId', args.conversationId)) + .collect(); + const out = []; + for (const message of messages) { + const playerDescription = await ctx.db + .query('playerDescriptions') + .withIndex('worldId', (q) => q.eq('worldId', args.worldId).eq('playerId', message.author)) + .first(); + if (!playerDescription) { + throw new Error(`Invalid author ID: ${message.author}`); + } + out.push({ ...message, authorName: playerDescription.name }); + } + return out; + }, +}); + +export const writeMessage = mutation({ + args: { + worldId: v.id('worlds'), + conversationId, + messageUuid: v.string(), + playerId, + text: v.string(), + }, + handler: async (ctx, args) => { + await ctx.db.insert('messages', { + conversationId: args.conversationId, + author: args.playerId, + messageUuid: args.messageUuid, + text: args.text, + worldId: args.worldId, + }); + await insertInput(ctx, args.worldId, 'finishSendingMessage', { + conversationId: args.conversationId, + playerId: args.playerId, + timestamp: Date.now(), + }); + }, +}); diff --git a/patches/music.ts b/patches/convex/music.ts similarity index 97% rename from patches/music.ts rename to patches/convex/music.ts index 56ab5f36ae5df59c08a4b9eda4375f8c626d1137..3c42607e21be706f657859996c730e1e35718e2d 100644 --- a/patches/music.ts +++ b/patches/convex/music.ts @@ -1,135 +1,135 @@ -import { v } from 'convex/values'; -import { query, internalMutation } from './_generated/server'; -import Replicate, { WebhookEventType } from 'replicate'; -import { httpAction, internalAction } from './_generated/server'; -import { internal, api } from './_generated/api'; - -function client(): Replicate { - const replicate = new Replicate({ - auth: process.env.REPLICATE_API_TOKEN || '', - }); - return replicate; -} - -function replicateAvailable(): boolean { - return !!process.env.REPLICATE_API_TOKEN; -} - -export const insertMusic = internalMutation({ - args: { storageId: v.string(), type: v.union(v.literal('background'), v.literal('player')) }, - handler: async (ctx, args) => { - await ctx.db.insert('music', { - storageId: args.storageId, - type: args.type, - }); - }, -}); - -export const getBackgroundMusic = query({ - handler: async (ctx) => { - const music = await ctx.db - .query('music') - .filter((entry) => entry.eq(entry.field('type'), 'background')) - .order('desc') - .first(); - if (!music) { - return '/assets/background.mp3'; - } - const url = await ctx.storage.getUrl(music.storageId); - if (!url) { - throw new Error(`Invalid storage ID: ${music.storageId}`); - } - return url; - }, -}); - -export const enqueueBackgroundMusicGeneration = internalAction({ - handler: async (ctx): Promise => { - if (!replicateAvailable()) { - return; - } - const worldStatus = await ctx.runQuery(api.world.defaultWorldStatus); - if (!worldStatus) { - console.log('No active default world, returning.'); - return; - } - // TODO: MusicGen-Large on Replicate only allows 30 seconds. Use MusicGen-Small for longer? - await generateMusic('16-bit RPG adventure game with wholesome vibe', 30); - }, -}); - -export const handleReplicateWebhook = httpAction(async (ctx, request) => { - const req = await request.json(); - if (req.id) { - const prediction = await client().predictions.get(req.id); - const response = await fetch(prediction.output); - const music = await response.blob(); - const storageId = await ctx.storage.store(music); - await ctx.runMutation(internal.music.insertMusic, { type: 'background', storageId }); - } - return new Response(); -}); - -enum MusicGenNormStrategy { - Clip = 'clip', - Loudness = 'loudness', - Peak = 'peak', - Rms = 'rms', -} - -enum MusicGenFormat { - wav = 'wav', - mp3 = 'mp3', -} - -/** - * - * @param prompt A description of the music you want to generate. - * @param duration Duration of the generated audio in seconds. - * @param webhook webhook URL for Replicate to call when @param webhook_events_filter is triggered - * @param webhook_events_filter Array of event names to filter the webhook. See https://replicate.com/docs/reference/http#predictions.create--webhook_events_filter - * @param normalization_strategy Strategy for normalizing audio. - * @param top_k Reduces sampling to the k most likely tokens. - * @param top_p Reduces sampling to tokens with cumulative probability of p. When set to `0` (default), top_k sampling is used. - * @param temperature Controls the 'conservativeness' of the sampling process. Higher temperature means more diversity. - * @param classifer_free_gudance Increases the influence of inputs on the output. Higher values produce lower-varience outputs that adhere more closely to inputs. - * @param output_format Output format for generated audio. See @ - * @param seed Seed for random number generator. If None or -1, a random seed will be used. - * @returns object containing metadata of the prediction with ID to fetch once result is completed - */ -export async function generateMusic( - prompt: string, - duration: number, - webhook: string = process.env.CONVEX_SITE_URL + '/replicate_webhook' || '', - webhook_events_filter: [WebhookEventType] = ['completed'], - normalization_strategy: MusicGenNormStrategy = MusicGenNormStrategy.Peak, - output_format: MusicGenFormat = MusicGenFormat.mp3, - top_k = 250, - top_p = 0, - temperature = 1, - classifer_free_gudance = 3, - seed = -1, - model_version = 'large', -) { - if (!replicateAvailable()) { - throw new Error('Replicate API token not set'); - } - return await client().predictions.create({ - // https://replicate.com/facebookresearch/musicgen/versions/7a76a8258b23fae65c5a22debb8841d1d7e816b75c2f24218cd2bd8573787906 - version: '7a76a8258b23fae65c5a22debb8841d1d7e816b75c2f24218cd2bd8573787906', - input: { - model_version, - prompt, - duration, - normalization_strategy, - top_k, - top_p, - temperature, - classifer_free_gudance, - output_format, - seed, - }, - webhook, - webhook_events_filter, - }); -} +import { v } from 'convex/values'; +import { query, internalMutation } from './_generated/server'; +import Replicate, { WebhookEventType } from 'replicate'; +import { httpAction, internalAction } from './_generated/server'; +import { internal, api } from './_generated/api'; + +function client(): Replicate { + const replicate = new Replicate({ + auth: process.env.REPLICATE_API_TOKEN || '', + }); + return replicate; +} + +function replicateAvailable(): boolean { + return !!process.env.REPLICATE_API_TOKEN; +} + +export const insertMusic = internalMutation({ + args: { storageId: v.string(), type: v.union(v.literal('background'), v.literal('player')) }, + handler: async (ctx, args) => { + await ctx.db.insert('music', { + storageId: args.storageId, + type: args.type, + }); + }, +}); + +export const getBackgroundMusic = query({ + handler: async (ctx) => { + const music = await ctx.db + .query('music') + .filter((entry) => entry.eq(entry.field('type'), 'background')) + .order('desc') + .first(); + if (!music) { + return '/assets/background.mp3'; + } + const url = await ctx.storage.getUrl(music.storageId); + if (!url) { + throw new Error(`Invalid storage ID: ${music.storageId}`); + } + return url; + }, +}); + +export const enqueueBackgroundMusicGeneration = internalAction({ + handler: async (ctx): Promise => { + if (!replicateAvailable()) { + return; + } + const worldStatus = await ctx.runQuery(api.world.defaultWorldStatus); + if (!worldStatus) { + console.log('No active default world, returning.'); + return; + } + // TODO: MusicGen-Large on Replicate only allows 30 seconds. Use MusicGen-Small for longer? + await generateMusic('16-bit RPG adventure game with wholesome vibe', 30); + }, +}); + +export const handleReplicateWebhook = httpAction(async (ctx, request) => { + const req = await request.json(); + if (req.id) { + const prediction = await client().predictions.get(req.id); + const response = await fetch(prediction.output); + const music = await response.blob(); + const storageId = await ctx.storage.store(music); + await ctx.runMutation(internal.music.insertMusic, { type: 'background', storageId }); + } + return new Response(); +}); + +enum MusicGenNormStrategy { + Clip = 'clip', + Loudness = 'loudness', + Peak = 'peak', + Rms = 'rms', +} + +enum MusicGenFormat { + wav = 'wav', + mp3 = 'mp3', +} + +/** + * + * @param prompt A description of the music you want to generate. + * @param duration Duration of the generated audio in seconds. + * @param webhook webhook URL for Replicate to call when @param webhook_events_filter is triggered + * @param webhook_events_filter Array of event names to filter the webhook. See https://replicate.com/docs/reference/http#predictions.create--webhook_events_filter + * @param normalization_strategy Strategy for normalizing audio. + * @param top_k Reduces sampling to the k most likely tokens. + * @param top_p Reduces sampling to tokens with cumulative probability of p. When set to `0` (default), top_k sampling is used. + * @param temperature Controls the 'conservativeness' of the sampling process. Higher temperature means more diversity. + * @param classifer_free_gudance Increases the influence of inputs on the output. Higher values produce lower-varience outputs that adhere more closely to inputs. + * @param output_format Output format for generated audio. See @ + * @param seed Seed for random number generator. If None or -1, a random seed will be used. + * @returns object containing metadata of the prediction with ID to fetch once result is completed + */ +export async function generateMusic( + prompt: string, + duration: number, + webhook: string = process.env.CONVEX_SITE_URL + '/replicate_webhook' || '', + webhook_events_filter: [WebhookEventType] = ['completed'], + normalization_strategy: MusicGenNormStrategy = MusicGenNormStrategy.Peak, + output_format: MusicGenFormat = MusicGenFormat.mp3, + top_k = 250, + top_p = 0, + temperature = 1, + classifer_free_gudance = 3, + seed = -1, + model_version = 'large', +) { + if (!replicateAvailable()) { + throw new Error('Replicate API token not set'); + } + return await client().predictions.create({ + // https://replicate.com/facebookresearch/musicgen/versions/7a76a8258b23fae65c5a22debb8841d1d7e816b75c2f24218cd2bd8573787906 + version: '7a76a8258b23fae65c5a22debb8841d1d7e816b75c2f24218cd2bd8573787906', + input: { + model_version, + prompt, + duration, + normalization_strategy, + top_k, + top_p, + temperature, + classifer_free_gudance, + output_format, + seed, + }, + webhook, + webhook_events_filter, + }); +} diff --git a/patches/convex/schema.ts b/patches/convex/schema.ts new file mode 100644 index 0000000000000000000000000000000000000000..b5dd400020c29d6bd93ac2014ee0b887503ad79f --- /dev/null +++ b/patches/convex/schema.ts @@ -0,0 +1,27 @@ +import { defineSchema, defineTable } from 'convex/server'; +import { v } from 'convex/values'; +import { agentTables } from './agent/schema'; +import { aiTownTables } from './aiTown/schema'; +import { conversationId, playerId } from './aiTown/ids'; +import { engineTables } from './engine/schema'; + +export default defineSchema({ + music: defineTable({ + storageId: v.string(), + type: v.union(v.literal('background'), v.literal('player')), + }), + + messages: defineTable({ + conversationId, + messageUuid: v.string(), + author: playerId, + text: v.string(), + worldId: v.optional(v.id('worlds')), + }) + .index('conversationId', ['worldId', 'conversationId']) + .index('messageUuid', ['conversationId', 'messageUuid']), + + ...agentTables, + ...aiTownTables, + ...engineTables, +}); diff --git a/patches/convex/testing.ts b/patches/convex/testing.ts new file mode 100644 index 0000000000000000000000000000000000000000..8ac0a72c02ef52a453680d4864510da0f73e4cca --- /dev/null +++ b/patches/convex/testing.ts @@ -0,0 +1,202 @@ +import { Id, TableNames } from './_generated/dataModel'; +import { internal } from './_generated/api'; +import { + DatabaseReader, + internalAction, + internalMutation, + mutation, + query, +} from './_generated/server'; +import { v } from 'convex/values'; +import schema from './schema'; +import { DELETE_BATCH_SIZE } from './constants'; +import { kickEngine, startEngine, stopEngine } from './aiTown/main'; +import { insertInput } from './aiTown/insertInput'; +import { fetchEmbedding, LLM_CONFIG } from './util/llm'; +import { chatCompletion } from './util/llm'; +import { startConversationMessage } from './agent/conversation'; +import { GameId } from './aiTown/ids'; + +// Clear all of the tables except for the embeddings cache. +const excludedTables: Array = ['embeddingsCache']; + +export const wipeAllTables = internalMutation({ + handler: async (ctx) => { + for (const tableName of Object.keys(schema.tables)) { + if (excludedTables.includes(tableName as TableNames)) { + continue; + } + await ctx.scheduler.runAfter(0, internal.testing.deletePage, { tableName, cursor: null }); + } + }, +}); + +export const deletePage = internalMutation({ + args: { + tableName: v.string(), + cursor: v.union(v.string(), v.null()), + }, + handler: async (ctx, args) => { + const results = await ctx.db + .query(args.tableName as TableNames) + .paginate({ cursor: args.cursor, numItems: DELETE_BATCH_SIZE }); + for (const row of results.page) { + await ctx.db.delete(row._id); + } + if (!results.isDone) { + await ctx.scheduler.runAfter(0, internal.testing.deletePage, { + tableName: args.tableName, + cursor: results.continueCursor, + }); + } + }, +}); + +export const kick = internalMutation({ + handler: async (ctx) => { + const { worldStatus } = await getDefaultWorld(ctx.db); + await kickEngine(ctx, worldStatus.worldId); + }, +}); + +export const stopAllowed = query({ + handler: async () => { + return !process.env.STOP_NOT_ALLOWED; + }, +}); + +export const stop = mutation({ + handler: async (ctx) => { + if (process.env.STOP_NOT_ALLOWED) throw new Error('Stop not allowed'); + const { worldStatus, engine } = await getDefaultWorld(ctx.db); + if (worldStatus.status === 'inactive' || worldStatus.status === 'stoppedByDeveloper') { + if (engine.running) { + throw new Error(`Engine ${engine._id} isn't stopped?`); + } + console.debug(`World ${worldStatus.worldId} is already inactive`); + return; + } + console.log(`Stopping engine ${engine._id}...`); + await ctx.db.patch(worldStatus._id, { status: 'stoppedByDeveloper' }); + await stopEngine(ctx, worldStatus.worldId); + }, +}); + +export const resume = mutation({ + handler: async (ctx) => { + const { worldStatus, engine } = await getDefaultWorld(ctx.db); + if (worldStatus.status === 'running') { + if (!engine.running) { + throw new Error(`Engine ${engine._id} isn't running?`); + } + console.debug(`World ${worldStatus.worldId} is already running`); + return; + } + console.log( + `Resuming engine ${engine._id} for world ${worldStatus.worldId} (state: ${worldStatus.status})...`, + ); + await ctx.db.patch(worldStatus._id, { status: 'running' }); + await startEngine(ctx, worldStatus.worldId); + }, +}); + +export const archive = internalMutation({ + handler: async (ctx) => { + const { worldStatus, engine } = await getDefaultWorld(ctx.db); + if (engine.running) { + throw new Error(`Engine ${engine._id} is still running!`); + } + console.log(`Archiving world ${worldStatus.worldId}...`); + await ctx.db.patch(worldStatus._id, { isDefault: false }); + }, +}); + +async function getDefaultWorld(db: DatabaseReader) { + const worldStatus = await db + .query('worldStatus') + .filter((q) => q.eq(q.field('isDefault'), true)) + .first(); + if (!worldStatus) { + throw new Error('No default world found'); + } + const engine = await db.get(worldStatus.engineId); + if (!engine) { + throw new Error(`Engine ${worldStatus.engineId} not found`); + } + return { worldStatus, engine }; +} + +export const debugCreatePlayers = internalMutation({ + args: { + numPlayers: v.number(), + }, + handler: async (ctx, args) => { + const { worldStatus } = await getDefaultWorld(ctx.db); + for (let i = 0; i < args.numPlayers; i++) { + const inputId = await insertInput(ctx, worldStatus.worldId, 'join', { + name: `Robot${i}`, + description: `This player is a robot.`, + character: `f${1 + (i % 8)}`, + }); + } + }, +}); + +export const randomPositions = internalMutation({ + handler: async (ctx) => { + const { worldStatus } = await getDefaultWorld(ctx.db); + const map = await ctx.db + .query('maps') + .withIndex('worldId', (q) => q.eq('worldId', worldStatus.worldId)) + .unique(); + if (!map) { + throw new Error(`No map for world ${worldStatus.worldId}`); + } + const world = await ctx.db.get(worldStatus.worldId); + if (!world) { + throw new Error(`No world for world ${worldStatus.worldId}`); + } + for (const player of world.players) { + await insertInput(ctx, world._id, 'moveTo', { + playerId: player.id, + destination: { + x: 1 + Math.floor(Math.random() * (map.width - 2)), + y: 1 + Math.floor(Math.random() * (map.height - 2)), + }, + }); + } + }, +}); + +export const testEmbedding = internalAction({ + args: { input: v.string() }, + handler: async (_ctx, args) => { + return await fetchEmbedding(args.input); + }, +}); + +export const testCompletion = internalAction({ + args: {}, + handler: async (ctx, args) => { + return await chatCompletion({ + messages: [ + { content: 'You are helpful', role: 'system' }, + { content: 'Where is pizza?', role: 'user' }, + ], + }); + }, +}); + +export const testConvo = internalAction({ + args: {}, + handler: async (ctx, args) => { + const a: any = (await startConversationMessage( + ctx, + 'm1707m46wmefpejw1k50rqz7856qw3ew' as Id<'worlds'>, + 'c:115' as GameId<'conversations'>, + 'p:0' as GameId<'players'>, + 'p:6' as GameId<'players'>, + )) as any; + return await a.readAll(); + }, +}); diff --git a/patches/convex/util/FastIntegerCompression.ts b/patches/convex/util/FastIntegerCompression.ts new file mode 100644 index 0000000000000000000000000000000000000000..e240fd6e305b159c09a3968d6ec20a43b0472654 --- /dev/null +++ b/patches/convex/util/FastIntegerCompression.ts @@ -0,0 +1,221 @@ +/** + * FastIntegerCompression.js : a fast integer compression library in JavaScript. + * From https://github.com/lemire/FastIntegerCompression.js/ + * (c) the authors + * Licensed under the Apache License, Version 2.0. + * + *FastIntegerCompression + * Simple usage : + * // var FastIntegerCompression = require("fastintcompression");// if you use node + * var array = [10,100000,65999,10,10,0,1,1,2000]; + * var buf = FastIntegerCompression.compress(array); + * var back = FastIntegerCompression.uncompress(buf); // gets back [10,100000,65999,10,10,0,1,1,2000] + * + * + * You can install the library under node with the command line + * npm install fastintcompression + */ + +function bytelog(val: number) { + if (val < 1 << 7) { + return 1; + } else if (val < 1 << 14) { + return 2; + } else if (val < 1 << 21) { + return 3; + } else if (val < 1 << 28) { + return 4; + } + return 5; +} + +function zigzag_encode(val: number) { + return (val + val) ^ (val >> 31); +} + +function zigzag_decode(val: number) { + return (val >> 1) ^ -(val & 1); +} + +// Compute how many bytes an array of integers would use once compressed. +// The input is expected to be an array of non-negative integers. +export function computeCompressedSizeInBytes(input: number[]) { + var c = input.length; + var answer = 0; + for (var i = 0; i < c; i++) { + answer += bytelog(input[i]); + } + return answer; +} + +// Compute how many bytes an array of integers would use once compressed. +// The input is expected to be an array of integers, some of them can be negative. +export function computeCompressedSizeInBytesSigned(input: number[]) { + var c = input.length; + var answer = 0; + for (var i = 0; i < c; i++) { + answer += bytelog(zigzag_encode(input[i])); + } + return answer; +} + +// Compress an array of integers, return a compressed buffer (as an ArrayBuffer). +// It is expected that the integers are non-negative: the caller is responsible +// for making this check. Floating-point numbers are not supported. +export function compress(input: number[]) { + var c = input.length; + var buf = new ArrayBuffer(computeCompressedSizeInBytes(input)); + var view = new Int8Array(buf); + var pos = 0; + for (var i = 0; i < c; i++) { + var val = input[i]; + if (val < 1 << 7) { + view[pos++] = val; + } else if (val < 1 << 14) { + view[pos++] = (val & 0x7f) | 0x80; + view[pos++] = val >>> 7; + } else if (val < 1 << 21) { + view[pos++] = (val & 0x7f) | 0x80; + view[pos++] = ((val >>> 7) & 0x7f) | 0x80; + view[pos++] = val >>> 14; + } else if (val < 1 << 28) { + view[pos++] = (val & 0x7f) | 0x80; + view[pos++] = ((val >>> 7) & 0x7f) | 0x80; + view[pos++] = ((val >>> 14) & 0x7f) | 0x80; + view[pos++] = val >>> 21; + } else { + view[pos++] = (val & 0x7f) | 0x80; + view[pos++] = ((val >>> 7) & 0x7f) | 0x80; + view[pos++] = ((val >>> 14) & 0x7f) | 0x80; + view[pos++] = ((val >>> 21) & 0x7f) | 0x80; + view[pos++] = val >>> 28; + } + } + return buf; +} + +// From a compressed array of integers stored ArrayBuffer, +// compute the number of compressed integers by scanning the input. +export function computeHowManyIntegers(input: ArrayBuffer) { + var view = new Uint8Array(input); + var c = view.length; + var count = 0; + for (var i = 0; i < c; i++) { + count += view[i] >>> 7; + } + return c - count; +} +// Uncompress an array of integer from an ArrayBuffer, return the array. +// It is assumed that they were compressed using the compress function, the caller +// is responsible for ensuring that it is the case. +export function uncompress(input: ArrayBuffer) { + var array = []; // The size of the output is not yet known. + var inbyte = new Int8Array(input); + var end = inbyte.length; + var pos = 0; + while (end > pos) { + var c = inbyte[pos++]; + var v = c & 0x7f; + if (c >= 0) { + array.push(v); + continue; + } + c = inbyte[pos++]; + v |= (c & 0x7f) << 7; + if (c >= 0) { + array.push(v); + continue; + } + c = inbyte[pos++]; + v |= (c & 0x7f) << 14; + if (c >= 0) { + array.push(v); + continue; + } + c = inbyte[pos++]; + v |= (c & 0x7f) << 21; + if (c >= 0) { + array.push(v); + continue; + } + c = inbyte[pos++]; + v |= c << 28; + v >>>= 0; // make positive + array.push(v); + } + return array; +} + +// Compress an array of integers, return a compressed buffer (as an ArrayBuffer). +// The integers can be signed (negative), but floating-point values are not supported. +export function compressSigned(input: number[]) { + var c = input.length; + var buf = new ArrayBuffer(computeCompressedSizeInBytesSigned(input)); + var view = new Int8Array(buf); + var pos = 0; + for (var i = 0; i < c; i++) { + var val = zigzag_encode(input[i]); + if (val < 1 << 7) { + view[pos++] = val; + } else if (val < 1 << 14) { + view[pos++] = (val & 0x7f) | 0x80; + view[pos++] = val >>> 7; + } else if (val < 1 << 21) { + view[pos++] = (val & 0x7f) | 0x80; + view[pos++] = ((val >>> 7) & 0x7f) | 0x80; + view[pos++] = val >>> 14; + } else if (val < 1 << 28) { + view[pos++] = (val & 0x7f) | 0x80; + view[pos++] = ((val >>> 7) & 0x7f) | 0x80; + view[pos++] = ((val >>> 14) & 0x7f) | 0x80; + view[pos++] = val >>> 21; + } else { + view[pos++] = (val & 0x7f) | 0x80; + view[pos++] = ((val >>> 7) & 0x7f) | 0x80; + view[pos++] = ((val >>> 14) & 0x7f) | 0x80; + view[pos++] = ((val >>> 21) & 0x7f) | 0x80; + view[pos++] = val >>> 28; + } + } + return buf; +} + +// Uncompress an array of integer from an ArrayBuffer, return the array. +// It is assumed that they were compressed using the compressSigned function, the caller +// is responsible for ensuring that it is the case. +export function uncompressSigned(input: ArrayBuffer) { + var array = []; // The size of the output is not yet known. + var inbyte = new Int8Array(input); + var end = inbyte.length; + var pos = 0; + while (end > pos) { + var c = inbyte[pos++]; + var v = c & 0x7f; + if (c >= 0) { + array.push(zigzag_decode(v)); + continue; + } + c = inbyte[pos++]; + v |= (c & 0x7f) << 7; + if (c >= 0) { + array.push(zigzag_decode(v)); + continue; + } + c = inbyte[pos++]; + v |= (c & 0x7f) << 14; + if (c >= 0) { + array.push(zigzag_decode(v)); + continue; + } + c = inbyte[pos++]; + v |= (c & 0x7f) << 21; + if (c >= 0) { + array.push(zigzag_decode(v)); + continue; + } + c = inbyte[pos++]; + v |= c << 28; + array.push(zigzag_decode(v)); + } + return array; +} diff --git a/patches/convex/util/assertNever.ts b/patches/convex/util/assertNever.ts new file mode 100644 index 0000000000000000000000000000000000000000..110924d4b402f1cee53bb4bb29ee5c09460c089f --- /dev/null +++ b/patches/convex/util/assertNever.ts @@ -0,0 +1,4 @@ +// From https://www.typescriptlang.org/docs/handbook/unions-and-intersections.html#union-exhaustiveness-checking +export function assertNever(x: never): never { + throw new Error(`Unexpected object: ${JSON.stringify(x)}`); +} diff --git a/patches/convex/util/asyncMap.test.ts b/patches/convex/util/asyncMap.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..e87d1975dbfbcad0ad6bb57b827e5d2ebbcb8866 --- /dev/null +++ b/patches/convex/util/asyncMap.test.ts @@ -0,0 +1,15 @@ +import { asyncMap } from './asyncMap'; + +describe('asyncMap', () => { + it('should map over a list asynchronously', async () => { + const list = [1, 2, 3]; + const result = await asyncMap(list, async (item: number) => item * 2); + expect(result).toEqual([2, 4, 6]); + }); + + it('should handle empty list input', async () => { + const list: number[] = []; + const result = await asyncMap(list, async (item: number) => item * 2); + expect(result).toEqual([]); + }); +}); \ No newline at end of file diff --git a/patches/convex/util/asyncMap.ts b/patches/convex/util/asyncMap.ts new file mode 100644 index 0000000000000000000000000000000000000000..ca28fb44a84e2e270eed5ceb2f6723ae1674f7da --- /dev/null +++ b/patches/convex/util/asyncMap.ts @@ -0,0 +1,20 @@ +/** + * asyncMap returns the results of applying an async function over an list. + * + * @param list - Iterable object of items, e.g. an Array, Set, Object.keys + * @param asyncTransform + * @returns + */ + +export async function asyncMap( + list: Iterable, + asyncTransform: (item: FromType, index: number) => Promise, +): Promise { + const promises: Promise[] = []; + let idx = 0; + for (const item of list) { + promises.push(asyncTransform(item, idx)); + idx += 1; + } + return Promise.all(promises); +} diff --git a/patches/convex/util/compression.test.ts b/patches/convex/util/compression.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..4ed56693faa34ae0564461884a12117d9776f9b3 --- /dev/null +++ b/patches/convex/util/compression.test.ts @@ -0,0 +1,90 @@ +import { + deltaDecode, + deltaEncode, + quantize, + runLengthDecode, + runLengthEncode, + unquantize, +} from './compression'; + +describe('compression', () => { + test('quantize (approximately) roundtrips', () => { + const precisions = [-1, 0, 1, 4, 8]; + const datasets = [ + // Random samples from [-2^32, 2^32] + [ + -2331813745.435792, 4165391630.4586916, 2508162414.104561, -3815881222.355323, + 3182227671.241928, -2091141304.634983, -3454731809.638463, 1539778764.4030657, + 3723556916.971266, 4014694279.989772, 1165331218.5641785, -4209073662.9696226, + -3837962324.440032, 2145014827.7712336, -631662265.4694176, 4116219084.927844, + ], + + // [-2^16, 2^16] + [ + -29109.399926296363, 24836.163035466132, 59528.43800645282, 5706.0239888604265, + 61844.35496542655, -46030.9434605508, 10288.243500897894, -48623.38350764701, + -62182.09862667126, 20639.535833017246, -7691.974206406943, -44505.52704528734, + -28755.644095767944, 38244.45061335398, -14135.607864461621, -14792.956311113172, + ], + + // [-2^8, 2^8] + [ + -67.02672070745166, -117.41024397385388, -243.41065459675673, 160.3825635900851, + 191.79026087008378, 89.76668679513216, -10.719096486254784, 205.25021491717217, + -68.83096015839055, 44.321620651742364, -203.44266714551503, -19.734642986127426, + 159.0214530150044, 72.07459707399431, -242.49909539291787, -246.50759645751867, + ], + + // [-2^4, 2^4] + [ + 14.993015665565746, -14.206729228453774, -1.503306544783097, -8.618521795982875, + 15.14825900944064, -0.7561338814569538, -4.372631369200661, -14.296889398516797, + -0.7673738652041102, 5.880288329769968, -0.12246711347653516, 2.6074790469727773, + -1.0378494460674226, -5.395209965702431, -0.9218194118035932, -1.8677599340100492, + ], + ]; + for (const values of datasets) { + for (const precision of precisions) { + const maxError = Math.max(1 / (1 << precision), 1e-8); + const roundTripped = unquantize(quantize(values, precision), precision); + expect(values.length).toEqual(roundTripped.length); + for (let i = 0; i < values.length; i++) { + const value = values[i]; + const roundtrippedValue = roundTripped[i]; + expect(Math.abs(value - roundtrippedValue)).toBeLessThanOrEqual(maxError); + } + } + } + }); + + test('delta encode roundtrips', () => { + const data = [ + 41476, -13450, -59451, -65102, -32493, -39078, -53884, 40784, 32081, -40422, 43421, 17184, + 23042, 27548, -61705, -45215, -39037, 61611, -43945, 28001, -64417, -54192, -56325, 24401, + 17735, 37464, -39842, 54964, 14469, -47248, -39450, + ]; + const roundtripped = deltaDecode(deltaEncode(data)); + expect(data).toEqual(roundtripped); + }); + + test('run length encode roundtrips', () => { + const datasets = [ + // No repetitions. + [ + 41476, -13450, -59451, -65102, -32493, -39078, -53884, 40784, 32081, -40422, 43421, 17184, + 23042, 27548, -61705, -45215, -39037, 61611, -43945, 28001, -64417, -54192, -56325, 24401, + 17735, 37464, -39842, 54964, 14469, -47248, -39450, + ], + // All repetitions. + [10, 10, 10, 10, 10, 10], + // Just one value. + [11], + // Repetitions in the middle of unique values. + [1, 2, 3, 4, 4, 4, 4, 5, 6, 7], + ]; + for (const data of datasets) { + const roundtripped = runLengthDecode(runLengthEncode(data)); + expect(data).toEqual(roundtripped); + } + }); +}); diff --git a/patches/convex/util/compression.ts b/patches/convex/util/compression.ts new file mode 100644 index 0000000000000000000000000000000000000000..a9e639e1a4ab9801bd22fbeafa45478b8accd470 --- /dev/null +++ b/patches/convex/util/compression.ts @@ -0,0 +1,71 @@ +export function quantize(values: number[], precision: number) { + const factor = 1 << precision; + return values.map((v) => Math.floor(v * factor)); +} + +export function unquantize(quantized: number[], precision: number) { + const reciprocal = 1 / (1 << precision); + return quantized.map((q) => q * reciprocal); +} + +export function deltaEncode(values: number[], initialValue = 0) { + let prev = initialValue; + const deltas = []; + for (const value of values) { + deltas.push(value - prev); + prev = value; + } + return deltas; +} + +export function deltaDecode(deltas: number[], initialValue = 0) { + let prev = initialValue; + const values = []; + for (const delta of deltas) { + const value = prev + delta; + values.push(value); + prev = value; + } + return values; +} + +export function runLengthEncode(values: number[]) { + let hasPrevious = false; + let previous = 0; + let count = 0; + const encoded = []; + for (const value of values) { + if (!hasPrevious) { + previous = value; + count = 1; + hasPrevious = true; + continue; + } + if (previous === value) { + count += 1; + continue; + } + encoded.push(previous, count); + previous = value; + count = 1; + } + if (hasPrevious) { + encoded.push(previous, count); + } + return encoded; +} + +export function runLengthDecode(encoded: number[]) { + if (encoded.length % 2 !== 0) { + throw new Error(`Invalid RLE encoded length: ${encoded.length}`); + } + const values = []; + for (let i = 0; i < encoded.length; i += 2) { + const value = encoded[i]; + const count = encoded[i + 1]; + for (let j = 0; j < count; j++) { + values.push(value); + } + } + return values; +} diff --git a/patches/convex/util/geometry.test.ts b/patches/convex/util/geometry.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..5c811a934a15d4c5c6b52ff0e33d98ecb09c3933 --- /dev/null +++ b/patches/convex/util/geometry.test.ts @@ -0,0 +1,298 @@ +import { compressPath, distance, manhattanDistance, normalize, orientationDegrees, pathOverlaps, pathPosition, pointsEqual, vector, vectorLength } from './geometry'; +import { Path, Vector } from './types'; + +describe('distance', () => { + test('should return the correct distance for two points', () => { + const p0 = { x: 0, y: 0 }; + const p1 = { x: 3, y: 4 }; + const expectedDistance = 5; + + const actualDistance = distance(p0, p1); + + expect(actualDistance).toBe(expectedDistance); + }); + + test('should return 0 for the same point', () => { + const p0 = { x: 1, y: 2 }; + const expectedDistance = 0; + + const actualDistance = distance(p0, p0); + + expect(actualDistance).toBe(expectedDistance); + }); + + test('should return the correct distance for negative points', () => { + const p0 = { x: -2, y: -3 }; + const p1 = { x: 1, y: 2 }; + const expectedDistance = 5.83; + + const actualDistance = distance(p0, p1); + + expect(actualDistance).toBeCloseTo(expectedDistance); + }); +}); + +describe('pointsEqual', () => { + test('should return true for identical points', () => { + const p0 = { x: 1, y: 2 }; + const p1 = { x: 1, y: 2 }; + expect(pointsEqual(p0, p1)).toBe(true); + }); + + test('should return false for non-idential points', () => { + const p0 = { x: 3, y: 2 }; + const p1 = { x: 5, y: 3 }; + expect(pointsEqual(p0, p1)).toBe(false); + }); + + test('should return false for different x coordinates', () => { + const p0 = { x: 1, y: 2 }; + const p1 = { x: 2, y: 2 }; + expect(pointsEqual(p0, p1)).toBe(false); + }); + + test('should return false for different y coordinates', () => { + const p0 = { x: 1, y: 2 }; + const p1 = { x: 1, y: 3 }; + expect(pointsEqual(p0, p1)).toBe(false); + }); +}); + +describe("manhattanDistance", () => { + test("should return correct distance for points on the same axis", () => { + const p0 = { x: 1, y: 0 }; + const p1 = { x: 1, y: 2 }; + expect(manhattanDistance(p0, p1)).toBe(2); + }); + + test("should return correct distance for points on different axes", () => { + const p0 = { x: 1, y: 0 }; + const p1 = { x: 3, y: 2 }; + expect(manhattanDistance(p0, p1)).toBe(4); + }); + + test("should return correct distance for negative points", () => { + const p0 = { x: -2, y: 0 }; + const p1 = { x: 1, y: -2 }; + expect(manhattanDistance(p0, p1)).toBe(5); + }); + + test("should return correct distance for identical points", () => { + const p0 = { x: 1, y: 2 }; + const p1 = { x: 1, y: 2 }; + expect(manhattanDistance(p0, p1)).toBe(0); + }); +}); + +describe('pathOverlaps', () => { + test('should throw an error if the path does not have 2 entries', () => { + const path: Path = [ + [0, 0, 0, 1, 0] + ]; + const time = 0; + expect(() => pathOverlaps(path, time)).toThrowError('Invalid path: [[0,0,0,1,0]]'); + }); + + test('should return true if the time is within the path', () => { + const path: Path = [ + [0, 0, 0, 1, 1], + [0, 2, 0, 1, 2] + ]; + const time = 1.5; + expect(pathOverlaps(path, time)).toBe(true); + }); + + test('should return false if the time is before the start of the path', () => { + const path: Path = [ + [0, 0, 0, 1, 1], + [0, 2, 0, 1, 2] + ]; + const time = 0.5; + expect(pathOverlaps(path, time)).toBe(false); + }); + + test('should return false if the time is after the end of the path', () => { + const path: Path = [ + [0, 0, 0, 1, 1], + [0, 2, 0, 1, 2] + ]; + const time = 2.5; + expect(pathOverlaps(path, time)).toBe(false); + }); +}); + +describe('pathPosition', () => { + test('should throw an error if the path does not have 2 entries', () => { + const path: Path = [ + [0, 0, 0, 1, 0] + ]; + const time = 0; + expect(() => pathPosition(path, time)).toThrowError('Invalid path: [[0,0,0,1,0]]'); + }); + + test('returns the first point when time is less than the start time', () => { + const path: Path = [ + [1, 2, 3, 4, 2], + [5, 6, 3, 4, 3] + ]; + + const result = pathPosition(path, 1); + + expect(result.position).toEqual({ x: 1, y: 2 }); + expect(result.facing).toEqual({ dx: 3, dy: 4 }); + expect(result.velocity).toBe(0); + }); + + test('returns the last point when time is greater than the end time', () => { + const path: Path = [ + [1, 2, 3, 4, 2], + [5, 6, 3, 4, 3] + ]; + + const result = pathPosition(path, 4); + + expect(result.position).toEqual({ x: 5, y: 6 }); + expect(result.facing).toEqual({ dx: 3, dy: 4 }); + expect(result.velocity).toBe(0); + }); + + test('returns the interpolated point for time between two segments', () => { + const path: Path = [ + [1, 2, 7, 8, 2], + [5, 6, 7, 8, 3], + [10, 11, 7, 8, 4], + [14, 15, 7, 8, 5] + ]; + + const result = pathPosition(path, 4.5); + + expect(result.position).toEqual({ x: 12, y: 13 }); + expect(result.facing).toEqual({ dx: 7, dy: 8 }); + expect(result.velocity).toBeCloseTo(5.657); + }); +}); + +describe('vector', () => { + test('should return a vector with dx = 1 and dy = 2', () => { + const p0 = { x: 1, y: 2 }; + const p1 = { x: 2, y: 4 }; + const expected = { dx: 1, dy: 2 }; + const actual = vector(p0, p1); + expect(actual).toEqual(expected); + }); + + test('should return a vector with dx = 0 and dy = 0', () => { + const p0 = { x: 1, y: 2 }; + const p1 = { x: 1, y: 2 }; + const expected = { dx: 0, dy: 0 }; + const actual = vector(p0, p1); + expect(actual).toEqual(expected); + }); + + test('should return a vector with dx = 0 and dy = -1', () => { + const p0 = { x: 1, y: 2 }; + const p1 = { x: 1, y: 1 }; + const expected = { dx: 0, dy: -1 }; + const actual = vector(p0, p1); + expect(actual).toEqual(expected); + }); +}); + +describe('vectorLength', () => { + test('returns the correct length for a vector', () => { + const vector: Vector = { dx: 3.14, dy: 4 }; + expect(vectorLength(vector)).toBeCloseTo(5.09); + }); + + test('returns the correct length for a vector with negative components', () => { + const vector: Vector = { dx: -3, dy: -4 }; + expect(vectorLength(vector)).toBeCloseTo(5); + }); + + test('returns the correct length for a vector with zero components', () => { + const vector: Vector = { dx: 0, dy: 0 }; + expect(vectorLength(vector)).toBeCloseTo(0); + }); +}); + +describe('normalize', () => { + test('should return null for vector length less than EPSILON', () => { + const vector: Vector = { dx: 0, dy: 0 }; + const result = normalize(vector); + expect(result).toBeNull(); + }); + + test('should return a normalized vector', () => { + const vector: Vector = { dx: 3, dy: 4 }; + const result = normalize(vector); + expect(result).toEqual({ dx: 0.6, dy: 0.8 }); + }); +}); + +describe('orientationDegrees', () => { + test('should throw an error for a vector length smaller than EPSILON', () => { + expect(() => orientationDegrees({ dx: 0, dy: 0 })).toThrowError("Can't compute the orientation of too small vector {\"dx\":0,\"dy\":0}"); + }); + test('should return 0 for a vector pointing to the right', () => { + expect(orientationDegrees({ dx: 1, dy: 0 })).toBe(0); + }); + + test('should return 90 for a vector pointing up', () => { + expect(orientationDegrees({ dx: 0, dy: 1 })).toBe(90); + }); + + test('should return 180 for a vector pointing to the left', () => { + expect(orientationDegrees({ dx: -1, dy: 0 })).toBe(180); + }); + + test('should return 270 for a vector pointing down', () => { + expect(orientationDegrees({ dx: 0, dy: -1 })).toBe(270); + }); +}); + + +describe('compressPath', () => { + test('should not compress a path with only 2 entries', () => { + const facing = { dx: 0, dy: 1 }; + const compressed = compressPath([ + { position: { x: 0, y: 0 }, facing, t: 0 }, + { position: { x: 0, y: 1 }, facing, t: 1 }, + ]); + expect(compressed).toEqual([ + [0, 0, 0, 1, 0], + [0, 1, 0, 1, 1], + ]); + }); + + test('should compress a line', () => { + const facing = { dx: 0, dy: 1 }; + const compressed = compressPath([ + { position: { x: 0, y: 0 }, facing, t: 0 }, + { position: { x: 0, y: 1 }, facing, t: 1 }, + { position: { x: 0, y: 2 }, facing, t: 2 }, + { position: { x: 0, y: 3 }, facing, t: 3 }, + { position: { x: 0, y: 4 }, facing, t: 4 }, + ]); + expect(compressed).toEqual([ + [0, 0, 0, 1, 0], + [0, 4, 0, 1, 4], + ]); + }); + + test('should compress a line with a turn', () => { + const facingUp = { dx: 0, dy: 1 }; + const facingRight = { dx: 1, dy: 0 }; + const compressed = compressPath([ + { position: { x: 0, y: 0 }, facing: facingUp, t: 0 }, + { position: { x: 0, y: 1 }, facing: facingUp, t: 1 }, + { position: { x: 0, y: 2 }, facing: facingRight, t: 2 }, + { position: { x: 1, y: 2 }, facing: facingRight, t: 3 }, + { position: { x: 2, y: 2 }, facing: facingRight, t: 4 }, + ]); + expect(compressed).toEqual([ + [0, 0, 0, 1, 0], + [0, 2, 1, 0, 2], + [2, 2, 1, 0, 4], + ]); + }); +}); diff --git a/patches/convex/util/geometry.ts b/patches/convex/util/geometry.ts new file mode 100644 index 0000000000000000000000000000000000000000..4f9f3923014a10580667d560f774a7a94333c051 --- /dev/null +++ b/patches/convex/util/geometry.ts @@ -0,0 +1,132 @@ +import { Path, PathComponent, Point, Vector, packPathComponent, queryPath } from './types'; + +export function distance(p0: Point, p1: Point): number { + const dx = p0.x - p1.x; + const dy = p0.y - p1.y; + return Math.sqrt(dx * dx + dy * dy); +} + +export function pointsEqual(p0: Point, p1: Point): boolean { + return p0.x == p1.x && p0.y == p1.y; +} + +export function manhattanDistance(p0: Point, p1: Point) { + return Math.abs(p0.x - p1.x) + Math.abs(p0.y - p1.y); +} + +export function pathOverlaps(path: Path, time: number): boolean { + if (path.length < 2) { + throw new Error(`Invalid path: ${JSON.stringify(path)}`); + } + const start = queryPath(path, 0); + const end = queryPath(path, path.length - 1); + return start.t <= time && time <= end.t; +} + +export function pathPosition( + path: Path, + time: number, +): { position: Point; facing: Vector; velocity: number } { + if (path.length < 2) { + throw new Error(`Invalid path: ${JSON.stringify(path)}`); + } + const first = queryPath(path, 0); + if (time < first.t) { + return { position: first.position, facing: first.facing, velocity: 0 }; + } + const last = queryPath(path, path.length - 1); + if (last.t < time) { + return { position: last.position, facing: last.facing, velocity: 0 }; + } + for (let i = 0; i < path.length - 1; i++) { + const segmentStart = queryPath(path, i); + const segmentEnd = queryPath(path, i + 1); + if (segmentStart.t <= time && time <= segmentEnd.t) { + const interp = (time - segmentStart.t) / (segmentEnd.t - segmentStart.t); + return { + position: { + x: segmentStart.position.x + interp * (segmentEnd.position.x - segmentStart.position.x), + y: segmentStart.position.y + interp * (segmentEnd.position.y - segmentStart.position.y), + }, + facing: segmentStart.facing, + velocity: + distance(segmentStart.position, segmentEnd.position) / (segmentEnd.t - segmentStart.t), + }; + } + } + throw new Error(`Timestamp checks not exhaustive?`); +} + +export const EPSILON = 0.0001; + +export function vector(p0: Point, p1: Point): Vector { + const dx = p1.x - p0.x; + const dy = p1.y - p0.y; + return { dx, dy }; +} + +export function vectorLength(vector: Vector): number { + return Math.sqrt(vector.dx * vector.dx + vector.dy * vector.dy); +} + +export function normalize(vector: Vector): Vector | null { + const len = vectorLength(vector); + if (len < EPSILON) { + return null; + } + const { dx, dy } = vector; + return { + dx: dx / len, + dy: dy / len, + }; +} + +export function orientationDegrees(vector: Vector): number { + if (Math.sqrt(vector.dx * vector.dx + vector.dy * vector.dy) < EPSILON) { + throw new Error(`Can't compute the orientation of too small vector ${JSON.stringify(vector)}`); + } + const twoPi = 2 * Math.PI; + const radians = (Math.atan2(vector.dy, vector.dx) + twoPi) % twoPi; + return (radians / twoPi) * 360; +} + +export function compressPath(densePath: PathComponent[]): Path { + const packed = densePath.map(packPathComponent); + if (densePath.length <= 2) { + return densePath.map(packPathComponent); + } + const out = [packPathComponent(densePath[0])]; + let last = densePath[0]; + let candidate; + for (const point of densePath.slice(1)) { + if (!candidate) { + candidate = point; + continue; + } + // We can skip `candidate` if it interpolates cleanly between + // `last` and `point`. + const { position, facing } = pathPosition( + [packPathComponent(last), packPathComponent(point)], + candidate.t, + ); + const positionCloseEnough = distance(position, candidate.position) < EPSILON; + const facingDifference = { + dx: facing.dx - candidate.facing.dx, + dy: facing.dy - candidate.facing.dy, + }; + const facingCloseEnough = vectorLength(facingDifference) < EPSILON; + + if (positionCloseEnough && facingCloseEnough) { + candidate = point; + continue; + } + + out.push(packPathComponent(candidate)); + last = candidate; + candidate = point; + } + if (candidate) { + out.push(packPathComponent(candidate)); + } + return out; +} diff --git a/patches/convex/util/isSimpleObject.ts b/patches/convex/util/isSimpleObject.ts new file mode 100644 index 0000000000000000000000000000000000000000..20e993225184fa8ad6886aab2acf0cfdd28fe611 --- /dev/null +++ b/patches/convex/util/isSimpleObject.ts @@ -0,0 +1,11 @@ +export function isSimpleObject(value: unknown) { + const isObject = typeof value === 'object'; + const prototype = Object.getPrototypeOf(value); + const isSimple = + prototype === null || + prototype === Object.prototype || + // Objects generated from other contexts (e.g. across Node.js `vm` modules) will not satisfy the previous + // conditions but are still simple objects. + prototype?.constructor?.name === 'Object'; + return isObject && isSimple; +} diff --git a/patches/llm.ts b/patches/convex/util/llm.ts similarity index 96% rename from patches/llm.ts rename to patches/convex/util/llm.ts index 9a0b1b5baae63ced9b44c753be4f0f26f6260975..b82d77fd4a364267614bd5bfc9a2b90a097b1256 100644 --- a/patches/llm.ts +++ b/patches/convex/util/llm.ts @@ -1,657 +1,657 @@ -import { HfInference } from "@huggingface/inference"; - -export const LLM_CONFIG = { - /* Hugginface config: */ - ollama: false, - huggingface: true, - url: "https://api-inference.huggingface.co/models/meta-llama/Meta-Llama-3-8B-Instruct", - chatModel: "meta-llama/Meta-Llama-3-8B-Instruct", - embeddingModel: - "https://api-inference.huggingface.co/models/mixedbread-ai/mxbai-embed-large-v1", - embeddingDimension: 1024, - - /* Ollama (local) config: - */ - // ollama: true, - // url: 'http://127.0.0.1:11434', - // chatModel: 'llama3' as const, - // embeddingModel: 'mxbai-embed-large', - // embeddingDimension: 1024, - // embeddingModel: 'llama3', - // embeddingDimension: 4096, - - /* Together.ai config: - ollama: false, - url: 'https://api.together.xyz', - chatModel: 'meta-llama/Llama-3-8b-chat-hf', - embeddingModel: 'togethercomputer/m2-bert-80M-8k-retrieval', - embeddingDimension: 768, - */ - - /* OpenAI config: - ollama: false, - url: 'https://api.openai.com', - chatModel: 'gpt-3.5-turbo-16k', - embeddingModel: 'text-embedding-ada-002', - embeddingDimension: 1536, - */ -}; - -function apiUrl(path: string) { - // OPENAI_API_BASE and OLLAMA_HOST are legacy - const host = - process.env.LLM_API_URL ?? - process.env.OLLAMA_HOST ?? - process.env.OPENAI_API_BASE ?? - LLM_CONFIG.url; - if (host.endsWith("/") && path.startsWith("/")) { - return host + path.slice(1); - } else if (!host.endsWith("/") && !path.startsWith("/")) { - return host + "/" + path; - } else { - return host + path; - } -} - -function apiKey() { - return process.env.LLM_API_KEY ?? process.env.OPENAI_API_KEY; -} - -const AuthHeaders = (): Record => - apiKey() - ? { - Authorization: "Bearer " + apiKey(), - } - : {}; - -// Overload for non-streaming -export async function chatCompletion( - body: Omit & { - model?: CreateChatCompletionRequest["model"]; - } & { - stream?: false | null | undefined; - } -): Promise<{ content: string; retries: number; ms: number }>; -// Overload for streaming -export async function chatCompletion( - body: Omit & { - model?: CreateChatCompletionRequest["model"]; - } & { - stream?: true; - } -): Promise<{ content: ChatCompletionContent; retries: number; ms: number }>; -export async function chatCompletion( - body: Omit & { - model?: CreateChatCompletionRequest["model"]; - } -) { - assertApiKey(); - // OLLAMA_MODEL is legacy - body.model = - body.model ?? - process.env.LLM_MODEL ?? - process.env.OLLAMA_MODEL ?? - LLM_CONFIG.chatModel; - const stopWords = body.stop - ? typeof body.stop === "string" - ? [body.stop] - : body.stop - : []; - if (LLM_CONFIG.ollama || LLM_CONFIG.huggingface) stopWords.push("<|eot_id|>"); - - const { - result: content, - retries, - ms, - } = await retryWithBackoff(async () => { - const hf = new HfInference(apiKey()); - const model = hf.endpoint(apiUrl("/v1/chat/completions")); - if (body.stream) { - const completion = model.chatCompletionStream({ - ...body, - }); - return new ChatCompletionContent(completion, stopWords); - } else { - const completion = await model.chatCompletion({ - ...body, - }); - const content = completion.choices[0].message?.content; - if (content === undefined) { - throw new Error( - "Unexpected result from OpenAI: " + JSON.stringify(completion) - ); - } - return content; - } - }); - - return { - content, - retries, - ms, - }; -} - -export async function tryPullOllama(model: string, error: string) { - if (error.includes("try pulling")) { - console.error("Embedding model not found, pulling from Ollama"); - const pullResp = await fetch(apiUrl("/api/pull"), { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ name: model }), - }); - console.log("Pull response", await pullResp.text()); - throw { - retry: true, - error: `Dynamically pulled model. Original error: ${error}`, - }; - } -} - -export async function fetchEmbeddingBatch(texts: string[]) { - if (LLM_CONFIG.ollama) { - return { - ollama: true as const, - embeddings: await Promise.all( - texts.map(async (t) => (await ollamaFetchEmbedding(t)).embedding) - ), - }; - } - assertApiKey(); - - if (LLM_CONFIG.huggingface) { - const result = await fetch(LLM_CONFIG.embeddingModel, { - method: "POST", - headers: { - "Content-Type": "application/json", - "X-Wait-For-Model": "true", - ...AuthHeaders(), - }, - body: JSON.stringify({ - inputs: texts.map((text) => text.replace(/\n/g, " ")), - }), - }); - const embeddings = await result.json(); - return { - ollama: true as const, - embeddings: embeddings, - }; - } - - const { - result: json, - retries, - ms, - } = await retryWithBackoff(async () => { - const result = await fetch(apiUrl("/v1/embeddings"), { - method: "POST", - headers: { - "Content-Type": "application/json", - ...AuthHeaders(), - }, - - body: JSON.stringify({ - model: LLM_CONFIG.embeddingModel, - input: texts.map((text) => text.replace(/\n/g, " ")), - }), - }); - if (!result.ok) { - throw { - retry: result.status === 429 || result.status >= 500, - error: new Error( - `Embedding failed with code ${result.status}: ${await result.text()}` - ), - }; - } - return (await result.json()) as CreateEmbeddingResponse; - }); - if (json.data.length !== texts.length) { - console.error(json); - throw new Error("Unexpected number of embeddings"); - } - const allembeddings = json.data; - allembeddings.sort((a, b) => a.index - b.index); - return { - ollama: false as const, - embeddings: allembeddings.map(({ embedding }) => embedding), - usage: json.usage?.total_tokens, - retries, - ms, - }; -} - -export async function fetchEmbedding(text: string) { - const { embeddings, ...stats } = await fetchEmbeddingBatch([text]); - return { embedding: embeddings[0], ...stats }; -} - -export async function fetchModeration(content: string) { - assertApiKey(); - const { result: flagged } = await retryWithBackoff(async () => { - const result = await fetch(apiUrl("/v1/moderations"), { - method: "POST", - headers: { - "Content-Type": "application/json", - ...AuthHeaders(), - }, - - body: JSON.stringify({ - input: content, - }), - }); - if (!result.ok) { - throw { - retry: result.status === 429 || result.status >= 500, - error: new Error( - `Embedding failed with code ${result.status}: ${await result.text()}` - ), - }; - } - return (await result.json()) as { results: { flagged: boolean }[] }; - }); - return flagged; -} - -export function assertApiKey() { - if (!LLM_CONFIG.ollama && !apiKey()) { - throw new Error( - "\n Missing LLM_API_KEY in environment variables.\n\n" + - (LLM_CONFIG.ollama ? "just" : "npx") + - " convex env set LLM_API_KEY 'your-key'" - ); - } -} - -// Retry after this much time, based on the retry number. -const RETRY_BACKOFF = [1000, 10_000, 20_000]; // In ms -const RETRY_JITTER = 100; // In ms -type RetryError = { retry: boolean; error: any }; - -export async function retryWithBackoff( - fn: () => Promise -): Promise<{ retries: number; result: T; ms: number }> { - let i = 0; - for (; i <= RETRY_BACKOFF.length; i++) { - try { - const start = Date.now(); - const result = await fn(); - const ms = Date.now() - start; - return { result, retries: i, ms }; - } catch (e) { - const retryError = e as RetryError; - if (i < RETRY_BACKOFF.length) { - if (retryError.retry) { - console.log( - `Attempt ${i + 1} failed, waiting ${ - RETRY_BACKOFF[i] - }ms to retry...`, - Date.now() - ); - await new Promise((resolve) => - setTimeout(resolve, RETRY_BACKOFF[i] + RETRY_JITTER * Math.random()) - ); - continue; - } - } - if (retryError.error) throw retryError.error; - else throw e; - } - } - throw new Error("Unreachable"); -} - -// Lifted from openai's package -export interface LLMMessage { - /** - * The contents of the message. `content` is required for all messages, and may be - * null for assistant messages with function calls. - */ - content: string | null; - - /** - * The role of the messages author. One of `system`, `user`, `assistant`, or - * `function`. - */ - role: "system" | "user" | "assistant" | "function"; - - /** - * The name of the author of this message. `name` is required if role is - * `function`, and it should be the name of the function whose response is in the - * `content`. May contain a-z, A-Z, 0-9, and underscores, with a maximum length of - * 64 characters. - */ - name?: string; - - /** - * The name and arguments of a function that should be called, as generated by the model. - */ - function_call?: { - // The name of the function to call. - name: string; - /** - * The arguments to call the function with, as generated by the model in - * JSON format. Note that the model does not always generate valid JSON, - * and may hallucinate parameters not defined by your function schema. - * Validate the arguments in your code before calling your function. - */ - arguments: string; - }; -} - -// Non-streaming chat completion response -interface CreateChatCompletionResponse { - id: string; - object: string; - created: number; - model: string; - choices: { - index?: number; - message?: { - role: "system" | "user" | "assistant"; - content: string; - }; - finish_reason?: string; - }[]; - usage?: { - completion_tokens: number; - - prompt_tokens: number; - - total_tokens: number; - }; -} - -interface CreateEmbeddingResponse { - data: { - index: number; - object: string; - embedding: number[]; - }[]; - model: string; - object: string; - usage: { - prompt_tokens: number; - total_tokens: number; - }; -} - -export interface CreateChatCompletionRequest { - /** - * ID of the model to use. - * @type {string} - * @memberof CreateChatCompletionRequest - */ - model: string; - // | 'gpt-4' - // | 'gpt-4-0613' - // | 'gpt-4-32k' - // | 'gpt-4-32k-0613' - // | 'gpt-3.5-turbo' - // | 'gpt-3.5-turbo-0613' - // | 'gpt-3.5-turbo-16k' // <- our default - // | 'gpt-3.5-turbo-16k-0613'; - /** - * The messages to generate chat completions for, in the chat format: - * https://platform.openai.com/docs/guides/chat/introduction - * @type {Array} - * @memberof CreateChatCompletionRequest - */ - messages: LLMMessage[]; - /** - * What sampling temperature to use, between 0 and 2. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic. We generally recommend altering this or `top_p` but not both. - * @type {number} - * @memberof CreateChatCompletionRequest - */ - temperature?: number | null; - /** - * An alternative to sampling with temperature, called nucleus sampling, where the model considers the results of the tokens with top_p probability mass. So 0.1 means only the tokens comprising the top 10% probability mass are considered. We generally recommend altering this or `temperature` but not both. - * @type {number} - * @memberof CreateChatCompletionRequest - */ - top_p?: number | null; - /** - * How many chat completion choices to generate for each input message. - * @type {number} - * @memberof CreateChatCompletionRequest - */ - n?: number | null; - /** - * If set, partial message deltas will be sent, like in ChatGPT. Tokens will be sent as data-only [server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format) as they become available, with the stream terminated by a `data: [DONE]` message. - * @type {boolean} - * @memberof CreateChatCompletionRequest - */ - stream?: boolean | null; - /** - * - * @type {CreateChatCompletionRequestStop} - * @memberof CreateChatCompletionRequest - */ - stop?: Array | string; - /** - * The maximum number of tokens allowed for the generated answer. By default, - * the number of tokens the model can return will be (4096 - prompt tokens). - * @type {number} - * @memberof CreateChatCompletionRequest - */ - max_tokens?: number; - /** - * Number between -2.0 and 2.0. Positive values penalize new tokens based on - * whether they appear in the text so far, increasing the model\'s likelihood - * to talk about new topics. See more information about frequency and - * presence penalties: - * https://platform.openai.com/docs/api-reference/parameter-details - * @type {number} - * @memberof CreateChatCompletionRequest - */ - presence_penalty?: number | null; - /** - * Number between -2.0 and 2.0. Positive values penalize new tokens based on - * their existing frequency in the text so far, decreasing the model\'s - * likelihood to repeat the same line verbatim. See more information about - * presence penalties: - * https://platform.openai.com/docs/api-reference/parameter-details - * @type {number} - * @memberof CreateChatCompletionRequest - */ - frequency_penalty?: number | null; - /** - * Modify the likelihood of specified tokens appearing in the completion. - * Accepts a json object that maps tokens (specified by their token ID in the - * tokenizer) to an associated bias value from -100 to 100. Mathematically, - * the bias is added to the logits generated by the model prior to sampling. - * The exact effect will vary per model, but values between -1 and 1 should - * decrease or increase likelihood of selection; values like -100 or 100 - * should result in a ban or exclusive selection of the relevant token. - * @type {object} - * @memberof CreateChatCompletionRequest - */ - logit_bias?: object | null; - /** - * A unique identifier representing your end-user, which can help OpenAI to - * monitor and detect abuse. Learn more: - * https://platform.openai.com/docs/guides/safety-best-practices/end-user-ids - * @type {string} - * @memberof CreateChatCompletionRequest - */ - user?: string; - tools?: { - // The type of the tool. Currently, only function is supported. - type: "function"; - function: { - /** - * The name of the function to be called. Must be a-z, A-Z, 0-9, or - * contain underscores and dashes, with a maximum length of 64. - */ - name: string; - /** - * A description of what the function does, used by the model to choose - * when and how to call the function. - */ - description?: string; - /** - * The parameters the functions accepts, described as a JSON Schema - * object. See the guide[1] for examples, and the JSON Schema reference[2] - * for documentation about the format. - * [1]: https://platform.openai.com/docs/guides/gpt/function-calling - * [2]: https://json-schema.org/understanding-json-schema/ - * To describe a function that accepts no parameters, provide the value - * {"type": "object", "properties": {}}. - */ - parameters: object; - }; - }[]; - /** - * Controls which (if any) function is called by the model. `none` means the - * model will not call a function and instead generates a message. - * `auto` means the model can pick between generating a message or calling a - * function. Specifying a particular function via - * {"type: "function", "function": {"name": "my_function"}} forces the model - * to call that function. - * - * `none` is the default when no functions are present. - * `auto` is the default if functions are present. - */ - tool_choice?: - | "none" // none means the model will not call a function and instead generates a message. - | "auto" // auto means the model can pick between generating a message or calling a function. - // Specifies a tool the model should use. Use to force the model to call - // a specific function. - | { - // The type of the tool. Currently, only function is supported. - type: "function"; - function: { name: string }; - }; - // Replaced by "tools" - // functions?: { - // /** - // * The name of the function to be called. Must be a-z, A-Z, 0-9, or - // * contain underscores and dashes, with a maximum length of 64. - // */ - // name: string; - // /** - // * A description of what the function does, used by the model to choose - // * when and how to call the function. - // */ - // description?: string; - // /** - // * The parameters the functions accepts, described as a JSON Schema - // * object. See the guide[1] for examples, and the JSON Schema reference[2] - // * for documentation about the format. - // * [1]: https://platform.openai.com/docs/guides/gpt/function-calling - // * [2]: https://json-schema.org/understanding-json-schema/ - // * To describe a function that accepts no parameters, provide the value - // * {"type": "object", "properties": {}}. - // */ - // parameters: object; - // }[]; - // /** - // * Controls how the model responds to function calls. "none" means the model - // * does not call a function, and responds to the end-user. "auto" means the - // * model can pick between an end-user or calling a function. Specifying a - // * particular function via {"name":\ "my_function"} forces the model to call - // * that function. - // * - "none" is the default when no functions are present. - // * - "auto" is the default if functions are present. - // */ - // function_call?: 'none' | 'auto' | { name: string }; - /** - * An object specifying the format that the model must output. - * - * Setting to { "type": "json_object" } enables JSON mode, which guarantees - * the message the model generates is valid JSON. - * *Important*: when using JSON mode, you must also instruct the model to - * produce JSON yourself via a system or user message. Without this, the model - * may generate an unending stream of whitespace until the generation reaches - * the token limit, resulting in a long-running and seemingly "stuck" request. - * Also note that the message content may be partially cut off if - * finish_reason="length", which indicates the generation exceeded max_tokens - * or the conversation exceeded the max context length. - */ - response_format?: { type: "text" | "json_object" }; -} - -// Checks whether a suffix of s1 is a prefix of s2. For example, -// ('Hello', 'Kira:') -> false -// ('Hello Kira', 'Kira:') -> true -const suffixOverlapsPrefix = (s1: string, s2: string) => { - for (let i = 1; i <= Math.min(s1.length, s2.length); i++) { - const suffix = s1.substring(s1.length - i); - const prefix = s2.substring(0, i); - if (suffix === prefix) { - return true; - } - } - return false; -}; - -export class ChatCompletionContent { - private readonly completion: AsyncIterable; - private readonly stopWords: string[]; - - constructor( - completion: AsyncIterable, - stopWords: string[] - ) { - this.completion = completion; - this.stopWords = stopWords; - } - - async *readInner() { - for await (const chunk of this.completion) { - yield chunk.choices[0].delta.content; - } - } - - // stop words in OpenAI api don't always work. - // So we have to truncate on our side. - async *read() { - let lastFragment = ""; - for await (const data of this.readInner()) { - lastFragment += data; - let hasOverlap = false; - for (const stopWord of this.stopWords) { - const idx = lastFragment.indexOf(stopWord); - if (idx >= 0) { - yield lastFragment.substring(0, idx); - return; - } - if (suffixOverlapsPrefix(lastFragment, stopWord)) { - hasOverlap = true; - } - } - if (hasOverlap) continue; - yield lastFragment; - lastFragment = ""; - } - yield lastFragment; - } - - async readAll() { - let allContent = ""; - for await (const chunk of this.read()) { - allContent += chunk; - } - return allContent; - } -} - -export async function ollamaFetchEmbedding(text: string) { - const { result } = await retryWithBackoff(async () => { - const resp = await fetch(apiUrl("/api/embeddings"), { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ model: LLM_CONFIG.embeddingModel, prompt: text }), - }); - if (resp.status === 404) { - const error = await resp.text(); - await tryPullOllama(LLM_CONFIG.embeddingModel, error); - throw new Error(`Failed to fetch embeddings: ${resp.status}`); - } - return (await resp.json()).embedding as number[]; - }); - return { embedding: result }; -} +import { HfInference } from "@huggingface/inference"; + +export const LLM_CONFIG = { + /* Hugginface config: */ + ollama: false, + huggingface: true, + url: "https://api-inference.huggingface.co/models/meta-llama/Meta-Llama-3-8B-Instruct", + chatModel: "meta-llama/Meta-Llama-3-8B-Instruct", + embeddingModel: + "https://api-inference.huggingface.co/models/mixedbread-ai/mxbai-embed-large-v1", + embeddingDimension: 1024, + + /* Ollama (local) config: + */ + // ollama: true, + // url: 'http://127.0.0.1:11434', + // chatModel: 'llama3' as const, + // embeddingModel: 'mxbai-embed-large', + // embeddingDimension: 1024, + // embeddingModel: 'llama3', + // embeddingDimension: 4096, + + /* Together.ai config: + ollama: false, + url: 'https://api.together.xyz', + chatModel: 'meta-llama/Llama-3-8b-chat-hf', + embeddingModel: 'togethercomputer/m2-bert-80M-8k-retrieval', + embeddingDimension: 768, + */ + + /* OpenAI config: + ollama: false, + url: 'https://api.openai.com', + chatModel: 'gpt-3.5-turbo-16k', + embeddingModel: 'text-embedding-ada-002', + embeddingDimension: 1536, + */ +}; + +function apiUrl(path: string) { + // OPENAI_API_BASE and OLLAMA_HOST are legacy + const host = + process.env.LLM_API_URL ?? + process.env.OLLAMA_HOST ?? + process.env.OPENAI_API_BASE ?? + LLM_CONFIG.url; + if (host.endsWith("/") && path.startsWith("/")) { + return host + path.slice(1); + } else if (!host.endsWith("/") && !path.startsWith("/")) { + return host + "/" + path; + } else { + return host + path; + } +} + +function apiKey() { + return process.env.LLM_API_KEY ?? process.env.OPENAI_API_KEY; +} + +const AuthHeaders = (): Record => + apiKey() + ? { + Authorization: "Bearer " + apiKey(), + } + : {}; + +// Overload for non-streaming +export async function chatCompletion( + body: Omit & { + model?: CreateChatCompletionRequest["model"]; + } & { + stream?: false | null | undefined; + } +): Promise<{ content: string; retries: number; ms: number }>; +// Overload for streaming +export async function chatCompletion( + body: Omit & { + model?: CreateChatCompletionRequest["model"]; + } & { + stream?: true; + } +): Promise<{ content: ChatCompletionContent; retries: number; ms: number }>; +export async function chatCompletion( + body: Omit & { + model?: CreateChatCompletionRequest["model"]; + } +) { + assertApiKey(); + // OLLAMA_MODEL is legacy + body.model = + body.model ?? + process.env.LLM_MODEL ?? + process.env.OLLAMA_MODEL ?? + LLM_CONFIG.chatModel; + const stopWords = body.stop + ? typeof body.stop === "string" + ? [body.stop] + : body.stop + : []; + if (LLM_CONFIG.ollama || LLM_CONFIG.huggingface) stopWords.push("<|eot_id|>"); + + const { + result: content, + retries, + ms, + } = await retryWithBackoff(async () => { + const hf = new HfInference(apiKey()); + const model = hf.endpoint(apiUrl("/v1/chat/completions")); + if (body.stream) { + const completion = model.chatCompletionStream({ + ...body, + }); + return new ChatCompletionContent(completion, stopWords); + } else { + const completion = await model.chatCompletion({ + ...body, + }); + const content = completion.choices[0].message?.content; + if (content === undefined) { + throw new Error( + "Unexpected result from OpenAI: " + JSON.stringify(completion) + ); + } + return content; + } + }); + + return { + content, + retries, + ms, + }; +} + +export async function tryPullOllama(model: string, error: string) { + if (error.includes("try pulling")) { + console.error("Embedding model not found, pulling from Ollama"); + const pullResp = await fetch(apiUrl("/api/pull"), { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ name: model }), + }); + console.log("Pull response", await pullResp.text()); + throw { + retry: true, + error: `Dynamically pulled model. Original error: ${error}`, + }; + } +} + +export async function fetchEmbeddingBatch(texts: string[]) { + if (LLM_CONFIG.ollama) { + return { + ollama: true as const, + embeddings: await Promise.all( + texts.map(async (t) => (await ollamaFetchEmbedding(t)).embedding) + ), + }; + } + assertApiKey(); + + if (LLM_CONFIG.huggingface) { + const result = await fetch(LLM_CONFIG.embeddingModel, { + method: "POST", + headers: { + "Content-Type": "application/json", + "X-Wait-For-Model": "true", + ...AuthHeaders(), + }, + body: JSON.stringify({ + inputs: texts.map((text) => text.replace(/\n/g, " ")), + }), + }); + const embeddings = await result.json(); + return { + ollama: true as const, + embeddings: embeddings, + }; + } + + const { + result: json, + retries, + ms, + } = await retryWithBackoff(async () => { + const result = await fetch(apiUrl("/v1/embeddings"), { + method: "POST", + headers: { + "Content-Type": "application/json", + ...AuthHeaders(), + }, + + body: JSON.stringify({ + model: LLM_CONFIG.embeddingModel, + input: texts.map((text) => text.replace(/\n/g, " ")), + }), + }); + if (!result.ok) { + throw { + retry: result.status === 429 || result.status >= 500, + error: new Error( + `Embedding failed with code ${result.status}: ${await result.text()}` + ), + }; + } + return (await result.json()) as CreateEmbeddingResponse; + }); + if (json.data.length !== texts.length) { + console.error(json); + throw new Error("Unexpected number of embeddings"); + } + const allembeddings = json.data; + allembeddings.sort((a, b) => a.index - b.index); + return { + ollama: false as const, + embeddings: allembeddings.map(({ embedding }) => embedding), + usage: json.usage?.total_tokens, + retries, + ms, + }; +} + +export async function fetchEmbedding(text: string) { + const { embeddings, ...stats } = await fetchEmbeddingBatch([text]); + return { embedding: embeddings[0], ...stats }; +} + +export async function fetchModeration(content: string) { + assertApiKey(); + const { result: flagged } = await retryWithBackoff(async () => { + const result = await fetch(apiUrl("/v1/moderations"), { + method: "POST", + headers: { + "Content-Type": "application/json", + ...AuthHeaders(), + }, + + body: JSON.stringify({ + input: content, + }), + }); + if (!result.ok) { + throw { + retry: result.status === 429 || result.status >= 500, + error: new Error( + `Embedding failed with code ${result.status}: ${await result.text()}` + ), + }; + } + return (await result.json()) as { results: { flagged: boolean }[] }; + }); + return flagged; +} + +export function assertApiKey() { + if (!LLM_CONFIG.ollama && !apiKey()) { + throw new Error( + "\n Missing LLM_API_KEY in environment variables.\n\n" + + (LLM_CONFIG.ollama ? "just" : "npx") + + " convex env set LLM_API_KEY 'your-key'" + ); + } +} + +// Retry after this much time, based on the retry number. +const RETRY_BACKOFF = [1000, 10_000, 20_000]; // In ms +const RETRY_JITTER = 100; // In ms +type RetryError = { retry: boolean; error: any }; + +export async function retryWithBackoff( + fn: () => Promise +): Promise<{ retries: number; result: T; ms: number }> { + let i = 0; + for (; i <= RETRY_BACKOFF.length; i++) { + try { + const start = Date.now(); + const result = await fn(); + const ms = Date.now() - start; + return { result, retries: i, ms }; + } catch (e) { + const retryError = e as RetryError; + if (i < RETRY_BACKOFF.length) { + if (retryError.retry) { + console.log( + `Attempt ${i + 1} failed, waiting ${ + RETRY_BACKOFF[i] + }ms to retry...`, + Date.now() + ); + await new Promise((resolve) => + setTimeout(resolve, RETRY_BACKOFF[i] + RETRY_JITTER * Math.random()) + ); + continue; + } + } + if (retryError.error) throw retryError.error; + else throw e; + } + } + throw new Error("Unreachable"); +} + +// Lifted from openai's package +export interface LLMMessage { + /** + * The contents of the message. `content` is required for all messages, and may be + * null for assistant messages with function calls. + */ + content: string | null; + + /** + * The role of the messages author. One of `system`, `user`, `assistant`, or + * `function`. + */ + role: "system" | "user" | "assistant" | "function"; + + /** + * The name of the author of this message. `name` is required if role is + * `function`, and it should be the name of the function whose response is in the + * `content`. May contain a-z, A-Z, 0-9, and underscores, with a maximum length of + * 64 characters. + */ + name?: string; + + /** + * The name and arguments of a function that should be called, as generated by the model. + */ + function_call?: { + // The name of the function to call. + name: string; + /** + * The arguments to call the function with, as generated by the model in + * JSON format. Note that the model does not always generate valid JSON, + * and may hallucinate parameters not defined by your function schema. + * Validate the arguments in your code before calling your function. + */ + arguments: string; + }; +} + +// Non-streaming chat completion response +interface CreateChatCompletionResponse { + id: string; + object: string; + created: number; + model: string; + choices: { + index?: number; + message?: { + role: "system" | "user" | "assistant"; + content: string; + }; + finish_reason?: string; + }[]; + usage?: { + completion_tokens: number; + + prompt_tokens: number; + + total_tokens: number; + }; +} + +interface CreateEmbeddingResponse { + data: { + index: number; + object: string; + embedding: number[]; + }[]; + model: string; + object: string; + usage: { + prompt_tokens: number; + total_tokens: number; + }; +} + +export interface CreateChatCompletionRequest { + /** + * ID of the model to use. + * @type {string} + * @memberof CreateChatCompletionRequest + */ + model: string; + // | 'gpt-4' + // | 'gpt-4-0613' + // | 'gpt-4-32k' + // | 'gpt-4-32k-0613' + // | 'gpt-3.5-turbo' + // | 'gpt-3.5-turbo-0613' + // | 'gpt-3.5-turbo-16k' // <- our default + // | 'gpt-3.5-turbo-16k-0613'; + /** + * The messages to generate chat completions for, in the chat format: + * https://platform.openai.com/docs/guides/chat/introduction + * @type {Array} + * @memberof CreateChatCompletionRequest + */ + messages: LLMMessage[]; + /** + * What sampling temperature to use, between 0 and 2. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic. We generally recommend altering this or `top_p` but not both. + * @type {number} + * @memberof CreateChatCompletionRequest + */ + temperature?: number | null; + /** + * An alternative to sampling with temperature, called nucleus sampling, where the model considers the results of the tokens with top_p probability mass. So 0.1 means only the tokens comprising the top 10% probability mass are considered. We generally recommend altering this or `temperature` but not both. + * @type {number} + * @memberof CreateChatCompletionRequest + */ + top_p?: number | null; + /** + * How many chat completion choices to generate for each input message. + * @type {number} + * @memberof CreateChatCompletionRequest + */ + n?: number | null; + /** + * If set, partial message deltas will be sent, like in ChatGPT. Tokens will be sent as data-only [server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format) as they become available, with the stream terminated by a `data: [DONE]` message. + * @type {boolean} + * @memberof CreateChatCompletionRequest + */ + stream?: boolean | null; + /** + * + * @type {CreateChatCompletionRequestStop} + * @memberof CreateChatCompletionRequest + */ + stop?: Array | string; + /** + * The maximum number of tokens allowed for the generated answer. By default, + * the number of tokens the model can return will be (4096 - prompt tokens). + * @type {number} + * @memberof CreateChatCompletionRequest + */ + max_tokens?: number; + /** + * Number between -2.0 and 2.0. Positive values penalize new tokens based on + * whether they appear in the text so far, increasing the model\'s likelihood + * to talk about new topics. See more information about frequency and + * presence penalties: + * https://platform.openai.com/docs/api-reference/parameter-details + * @type {number} + * @memberof CreateChatCompletionRequest + */ + presence_penalty?: number | null; + /** + * Number between -2.0 and 2.0. Positive values penalize new tokens based on + * their existing frequency in the text so far, decreasing the model\'s + * likelihood to repeat the same line verbatim. See more information about + * presence penalties: + * https://platform.openai.com/docs/api-reference/parameter-details + * @type {number} + * @memberof CreateChatCompletionRequest + */ + frequency_penalty?: number | null; + /** + * Modify the likelihood of specified tokens appearing in the completion. + * Accepts a json object that maps tokens (specified by their token ID in the + * tokenizer) to an associated bias value from -100 to 100. Mathematically, + * the bias is added to the logits generated by the model prior to sampling. + * The exact effect will vary per model, but values between -1 and 1 should + * decrease or increase likelihood of selection; values like -100 or 100 + * should result in a ban or exclusive selection of the relevant token. + * @type {object} + * @memberof CreateChatCompletionRequest + */ + logit_bias?: object | null; + /** + * A unique identifier representing your end-user, which can help OpenAI to + * monitor and detect abuse. Learn more: + * https://platform.openai.com/docs/guides/safety-best-practices/end-user-ids + * @type {string} + * @memberof CreateChatCompletionRequest + */ + user?: string; + tools?: { + // The type of the tool. Currently, only function is supported. + type: "function"; + function: { + /** + * The name of the function to be called. Must be a-z, A-Z, 0-9, or + * contain underscores and dashes, with a maximum length of 64. + */ + name: string; + /** + * A description of what the function does, used by the model to choose + * when and how to call the function. + */ + description?: string; + /** + * The parameters the functions accepts, described as a JSON Schema + * object. See the guide[1] for examples, and the JSON Schema reference[2] + * for documentation about the format. + * [1]: https://platform.openai.com/docs/guides/gpt/function-calling + * [2]: https://json-schema.org/understanding-json-schema/ + * To describe a function that accepts no parameters, provide the value + * {"type": "object", "properties": {}}. + */ + parameters: object; + }; + }[]; + /** + * Controls which (if any) function is called by the model. `none` means the + * model will not call a function and instead generates a message. + * `auto` means the model can pick between generating a message or calling a + * function. Specifying a particular function via + * {"type: "function", "function": {"name": "my_function"}} forces the model + * to call that function. + * + * `none` is the default when no functions are present. + * `auto` is the default if functions are present. + */ + tool_choice?: + | "none" // none means the model will not call a function and instead generates a message. + | "auto" // auto means the model can pick between generating a message or calling a function. + // Specifies a tool the model should use. Use to force the model to call + // a specific function. + | { + // The type of the tool. Currently, only function is supported. + type: "function"; + function: { name: string }; + }; + // Replaced by "tools" + // functions?: { + // /** + // * The name of the function to be called. Must be a-z, A-Z, 0-9, or + // * contain underscores and dashes, with a maximum length of 64. + // */ + // name: string; + // /** + // * A description of what the function does, used by the model to choose + // * when and how to call the function. + // */ + // description?: string; + // /** + // * The parameters the functions accepts, described as a JSON Schema + // * object. See the guide[1] for examples, and the JSON Schema reference[2] + // * for documentation about the format. + // * [1]: https://platform.openai.com/docs/guides/gpt/function-calling + // * [2]: https://json-schema.org/understanding-json-schema/ + // * To describe a function that accepts no parameters, provide the value + // * {"type": "object", "properties": {}}. + // */ + // parameters: object; + // }[]; + // /** + // * Controls how the model responds to function calls. "none" means the model + // * does not call a function, and responds to the end-user. "auto" means the + // * model can pick between an end-user or calling a function. Specifying a + // * particular function via {"name":\ "my_function"} forces the model to call + // * that function. + // * - "none" is the default when no functions are present. + // * - "auto" is the default if functions are present. + // */ + // function_call?: 'none' | 'auto' | { name: string }; + /** + * An object specifying the format that the model must output. + * + * Setting to { "type": "json_object" } enables JSON mode, which guarantees + * the message the model generates is valid JSON. + * *Important*: when using JSON mode, you must also instruct the model to + * produce JSON yourself via a system or user message. Without this, the model + * may generate an unending stream of whitespace until the generation reaches + * the token limit, resulting in a long-running and seemingly "stuck" request. + * Also note that the message content may be partially cut off if + * finish_reason="length", which indicates the generation exceeded max_tokens + * or the conversation exceeded the max context length. + */ + response_format?: { type: "text" | "json_object" }; +} + +// Checks whether a suffix of s1 is a prefix of s2. For example, +// ('Hello', 'Kira:') -> false +// ('Hello Kira', 'Kira:') -> true +const suffixOverlapsPrefix = (s1: string, s2: string) => { + for (let i = 1; i <= Math.min(s1.length, s2.length); i++) { + const suffix = s1.substring(s1.length - i); + const prefix = s2.substring(0, i); + if (suffix === prefix) { + return true; + } + } + return false; +}; + +export class ChatCompletionContent { + private readonly completion: AsyncIterable; + private readonly stopWords: string[]; + + constructor( + completion: AsyncIterable, + stopWords: string[] + ) { + this.completion = completion; + this.stopWords = stopWords; + } + + async *readInner() { + for await (const chunk of this.completion) { + yield chunk.choices[0].delta.content; + } + } + + // stop words in OpenAI api don't always work. + // So we have to truncate on our side. + async *read() { + let lastFragment = ""; + for await (const data of this.readInner()) { + lastFragment += data; + let hasOverlap = false; + for (const stopWord of this.stopWords) { + const idx = lastFragment.indexOf(stopWord); + if (idx >= 0) { + yield lastFragment.substring(0, idx); + return; + } + if (suffixOverlapsPrefix(lastFragment, stopWord)) { + hasOverlap = true; + } + } + if (hasOverlap) continue; + yield lastFragment; + lastFragment = ""; + } + yield lastFragment; + } + + async readAll() { + let allContent = ""; + for await (const chunk of this.read()) { + allContent += chunk; + } + return allContent; + } +} + +export async function ollamaFetchEmbedding(text: string) { + const { result } = await retryWithBackoff(async () => { + const resp = await fetch(apiUrl("/api/embeddings"), { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ model: LLM_CONFIG.embeddingModel, prompt: text }), + }); + if (resp.status === 404) { + const error = await resp.text(); + await tryPullOllama(LLM_CONFIG.embeddingModel, error); + throw new Error(`Failed to fetch embeddings: ${resp.status}`); + } + return (await resp.json()).embedding as number[]; + }); + return { embedding: result }; +} diff --git a/patches/convex/util/minheap.test.ts b/patches/convex/util/minheap.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..6bc6064d2cb63e513899192509d179350a8bd944 --- /dev/null +++ b/patches/convex/util/minheap.test.ts @@ -0,0 +1,62 @@ +import { MinHeap } from './minheap'; + +describe('MinHeap', () => { + const compareNumbers = (a: number, b: number): boolean => a > b; + + test('should initialize an empty heap', () => { + const heap = MinHeap(compareNumbers); + expect(heap.length()).toBe(0); + expect(heap.peek()).toBeUndefined(); + }); + + test('should insert values correctly and maintain the min property', () => { + const heap = MinHeap(compareNumbers); + heap.push(3); + heap.push(1); + heap.push(4); + heap.push(2); + + expect(heap.peek()).toBe(1); + expect(heap.length()).toBe(4); + }); + + test('should pop values correctly and maintain the min property', () => { + const heap = MinHeap(compareNumbers); + heap.push(3); + heap.push(1); + heap.push(4); + heap.push(2); + + expect(heap.pop()).toBe(1); + expect(heap.length()).toBe(3); + expect(heap.peek()).toBe(2); + + expect(heap.pop()).toBe(2); + expect(heap.length()).toBe(2); + expect(heap.peek()).toBe(3); + }); + + test('should handle popping from an empty heap', () => { + const heap = MinHeap(compareNumbers); + expect(heap.pop()).toBeUndefined(); + expect(heap.length()).toBe(0); + expect(heap.peek()).toBeUndefined(); + }); + + test('should handle peeking from an empty heap', () => { + const heap = MinHeap(compareNumbers); + expect(heap.peek()).toBeUndefined(); + }); + + test('should handle custom comparison functions', () => { + const compareStringsByLength = (a: string, b: string): boolean => a.length > b.length; + const heap = MinHeap(compareStringsByLength); + heap.push('apple'); + heap.push('banana'); + heap.push('cherry'); + + expect(heap.peek()).toBe('apple'); + heap.push('kiwi'); + expect(heap.peek()).toBe('kiwi'); + }); +}); diff --git a/patches/convex/util/minheap.ts b/patches/convex/util/minheap.ts new file mode 100644 index 0000000000000000000000000000000000000000..8f83b945aa7cfb8d6a4f6a6d54591651da5128f3 --- /dev/null +++ b/patches/convex/util/minheap.ts @@ -0,0 +1,38 @@ +// Basic 1-indexed minheap implementation +export function MinHeap(compare: (a: T, b: T) => boolean) { + const tree = [null as T]; + let endIndex = 1; + return { + peek: (): T | undefined => tree[1], + length: () => endIndex - 1, + push: (newValue: T) => { + let destinationIndex = endIndex++; + let nextToCheck; + while ((nextToCheck = destinationIndex >> 1) > 0) { + const existing = tree[nextToCheck]; + if (compare(newValue, existing)) break; + tree[destinationIndex] = existing; + destinationIndex = nextToCheck; + } + tree[destinationIndex] = newValue; + }, + pop: () => { + if (endIndex == 1) return undefined; + endIndex--; + const value = tree[1]; + const lastValue = tree[endIndex]; + let destinationIndex = 1; + let nextToCheck; + while ((nextToCheck = destinationIndex << 1) < endIndex) { + if (nextToCheck + 1 <= endIndex && compare(tree[nextToCheck], tree[nextToCheck + 1])) + nextToCheck++; + const existing = tree[nextToCheck]; + if (compare(existing, lastValue)) break; + tree[destinationIndex] = existing; + destinationIndex = nextToCheck; + } + tree[destinationIndex] = lastValue; + return value; + }, + }; +} diff --git a/patches/convex/util/object.ts b/patches/convex/util/object.ts new file mode 100644 index 0000000000000000000000000000000000000000..acc07b68ca0bfe22d4c5e543e8026d8ae7870dea --- /dev/null +++ b/patches/convex/util/object.ts @@ -0,0 +1,22 @@ +export function parseMap( + records: Serialized[], + constructor: new (r: Serialized) => Parsed, + getId: (r: Parsed) => Id, +): Map { + const out = new Map(); + for (const record of records) { + const parsed = new constructor(record); + const id = getId(parsed); + if (out.has(id)) { + throw new Error(`Duplicate ID ${id}`); + } + out.set(id, parsed); + } + return out; +} + +export function serializeMap( + map: Map, +): Serialized[] { + return [...map.values()].map((v) => v.serialize()); +} diff --git a/patches/convex/util/sleep.ts b/patches/convex/util/sleep.ts new file mode 100644 index 0000000000000000000000000000000000000000..5a311c590889d6594625d98a9243639918ddcae5 --- /dev/null +++ b/patches/convex/util/sleep.ts @@ -0,0 +1,3 @@ +export async function sleep(ms: number) { + return new Promise((resolve) => setTimeout(resolve, ms)); +} diff --git a/patches/convex/util/types.test.ts b/patches/convex/util/types.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..a4ba8dab6e4d22856df89880837b7e8ec0c67985 --- /dev/null +++ b/patches/convex/util/types.test.ts @@ -0,0 +1,42 @@ +import { Path, PathComponent, packPathComponent, queryPath, unpackPathComponent } from "./types"; + +describe('queryPath', () => { + it('should return the correct path component', () => { + const p: Path = [ + [1, 2, 3, 4, 5], + [6, 7, 8, 9, 10], + [11, 12, 13, 14, 15] + ]; + const expected = { + position: { x: 6, y: 7 }, + facing: { dx: 8, dy: 9 }, + t: 10, + }; + expect(queryPath(p, 1)).toEqual(expected); + }); +}); + +describe('packPathComponent', () => { + it('should correctly pack a path component', () => { + const p: PathComponent = { + position: { x: 10, y: 20 }, + facing: { dx: 3, dy: 4 }, + t: 5, + }; + const expected = [10, 20, 3, 4, 5]; + expect(packPathComponent(p)).toEqual(expected); + }) +}); + +describe('unpackPathComponent', () => { + it('should unpack a path component with positive values', () => { + const input: [number, number, number, number, number] = [10, 20, 3, 4, 5]; + const expected = { + position: { x: 10, y: 20 }, + facing: { dx: 3, dy: 4 }, + t: 5, + } + const actual = unpackPathComponent(input); + expect(actual).toEqual(expected); + }); +}); \ No newline at end of file diff --git a/patches/convex/util/types.ts b/patches/convex/util/types.ts new file mode 100644 index 0000000000000000000000000000000000000000..6a33fb3636274206fbf6267594ef8a8ab0c99bbc --- /dev/null +++ b/patches/convex/util/types.ts @@ -0,0 +1,33 @@ +import { Infer, v } from 'convex/values'; + +export const point = v.object({ + x: v.number(), + y: v.number(), +}); +export type Point = Infer; + +export const vector = v.object({ + dx: v.number(), + dy: v.number(), +}); +export type Vector = Infer; + +// Paths are arrays of [x, y, dx, dy, t] tuples; +export const path = v.array(v.array(v.number())); +export type Path = [number, number, number, number, number][]; + +export type PathComponent = { position: Point; facing: Vector; t: number }; + +export function queryPath(p: Path, at: number): PathComponent { + return unpackPathComponent(p[at]); +} +export function packPathComponent(p: PathComponent): [number, number, number, number, number] { + return [p.position.x, p.position.y, p.facing.dx, p.facing.dy, p.t]; +} +export function unpackPathComponent(p: [number, number, number, number, number]): PathComponent { + return { + position: { x: p[0], y: p[1] }, + facing: { dx: p[2], dy: p[3] }, + t: p[4], + }; +} diff --git a/patches/convex/util/xxhash.ts b/patches/convex/util/xxhash.ts new file mode 100644 index 0000000000000000000000000000000000000000..aa6cd95cffc7af21b1cef2d8e7b1d131eae49509 --- /dev/null +++ b/patches/convex/util/xxhash.ts @@ -0,0 +1,228 @@ +/* +From https://github.com/Jason3S/xxhash + +MIT License + +Copyright (c) 2019 Jason Dent + +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. +*/ + +const PRIME32_1 = 2654435761; +const PRIME32_2 = 2246822519; +const PRIME32_3 = 3266489917; +const PRIME32_4 = 668265263; +const PRIME32_5 = 374761393; + +export function toUtf8(text: string): Uint8Array { + const bytes: number[] = []; + for (let i = 0, n = text.length; i < n; ++i) { + const c = text.charCodeAt(i); + if (c < 0x80) { + bytes.push(c); + } else if (c < 0x800) { + bytes.push(0xc0 | (c >> 6), 0x80 | (c & 0x3f)); + } else if (c < 0xd800 || c >= 0xe000) { + bytes.push(0xe0 | (c >> 12), 0x80 | ((c >> 6) & 0x3f), 0x80 | (c & 0x3f)); + } else { + const cp = 0x10000 + (((c & 0x3ff) << 10) | (text.charCodeAt(++i) & 0x3ff)); + bytes.push( + 0xf0 | ((cp >> 18) & 0x7), + 0x80 | ((cp >> 12) & 0x3f), + 0x80 | ((cp >> 6) & 0x3f), + 0x80 | (cp & 0x3f), + ); + } + } + return new Uint8Array(bytes); +} + +/** + * + * @param buffer - byte array or string + * @param seed - optional seed (32-bit unsigned); + */ +export function xxHash32(buffer: Uint8Array | string, seed = 0): number { + buffer = typeof buffer === 'string' ? toUtf8(buffer) : buffer; + const b = buffer; + + /* + Step 1. Initialize internal accumulators + Each accumulator gets an initial value based on optional seed input. Since the seed is optional, it can be 0. + + ``` + u32 acc1 = seed + PRIME32_1 + PRIME32_2; + u32 acc2 = seed + PRIME32_2; + u32 acc3 = seed + 0; + u32 acc4 = seed - PRIME32_1; + ``` + Special case : input is less than 16 bytes + When input is too small (< 16 bytes), the algorithm will not process any stripe. Consequently, it will not + make use of parallel accumulators. + + In which case, a simplified initialization is performed, using a single accumulator : + + u32 acc = seed + PRIME32_5; + The algorithm then proceeds directly to step 4. + */ + + let acc = (seed + PRIME32_5) & 0xffffffff; + let offset = 0; + + if (b.length >= 16) { + const accN = [ + (seed + PRIME32_1 + PRIME32_2) & 0xffffffff, + (seed + PRIME32_2) & 0xffffffff, + (seed + 0) & 0xffffffff, + (seed - PRIME32_1) & 0xffffffff, + ]; + + /* + Step 2. Process stripes + A stripe is a contiguous segment of 16 bytes. It is evenly divided into 4 lanes, of 4 bytes each. + The first lane is used to update accumulator 1, the second lane is used to update accumulator 2, and so on. + + Each lane read its associated 32-bit value using little-endian convention. + + For each {lane, accumulator}, the update process is called a round, and applies the following formula : + + ``` + accN = accN + (laneN * PRIME32_2); + accN = accN <<< 13; + accN = accN * PRIME32_1; + ``` + + This shuffles the bits so that any bit from input lane impacts several bits in output accumulator. + All operations are performed modulo 2^32. + + Input is consumed one full stripe at a time. Step 2 is looped as many times as necessary to consume + the whole input, except the last remaining bytes which cannot form a stripe (< 16 bytes). When that + happens, move to step 3. + */ + + const b = buffer; + const limit = b.length - 16; + let lane = 0; + for (offset = 0; (offset & 0xfffffff0) <= limit; offset += 4) { + const i = offset; + const laneN0 = (b[i + 0] as any) + ((b[i + 1] as any) << 8); + const laneN1 = (b[i + 2] as any) + ((b[i + 3] as any) << 8); + const laneNP = laneN0 * PRIME32_2 + ((laneN1 * PRIME32_2) << 16); + let acc = (accN[lane] + laneNP) & 0xffffffff; + acc = (acc << 13) | (acc >>> 19); + const acc0 = acc & 0xffff; + const acc1 = acc >>> 16; + accN[lane] = (acc0 * PRIME32_1 + ((acc1 * PRIME32_1) << 16)) & 0xffffffff; + lane = (lane + 1) & 0x3; + } + + /* + Step 3. Accumulator convergence + All 4 lane accumulators from previous steps are merged to produce a single remaining accumulator + of same width (32-bit). The associated formula is as follows : + + ``` + acc = (acc1 <<< 1) + (acc2 <<< 7) + (acc3 <<< 12) + (acc4 <<< 18); + ``` + */ + acc = + (((accN[0] << 1) | (accN[0] >>> 31)) + + ((accN[1] << 7) | (accN[1] >>> 25)) + + ((accN[2] << 12) | (accN[2] >>> 20)) + + ((accN[3] << 18) | (accN[3] >>> 14))) & + 0xffffffff; + } + + /* + Step 4. Add input length + The input total length is presumed known at this stage. This step is just about adding the length to + accumulator, so that it participates to final mixing. + + ``` + acc = acc + (u32)inputLength; + ``` + */ + acc = (acc + buffer.length) & 0xffffffff; + + /* + Step 5. Consume remaining input + There may be up to 15 bytes remaining to consume from the input. The final stage will digest them according + to following pseudo-code : + ``` + while (remainingLength >= 4) { + lane = read_32bit_little_endian(input_ptr); + acc = acc + lane * PRIME32_3; + acc = (acc <<< 17) * PRIME32_4; + input_ptr += 4; remainingLength -= 4; + } + ``` + This process ensures that all input bytes are present in the final mix. + */ + + const limit = buffer.length - 4; + for (; offset <= limit; offset += 4) { + const i = offset; + const laneN0 = (b[i + 0] as any) + ((b[i + 1] as any) << 8); + const laneN1 = (b[i + 2] as any) + ((b[i + 3] as any) << 8); + const laneP = laneN0 * PRIME32_3 + ((laneN1 * PRIME32_3) << 16); + acc = (acc + laneP) & 0xffffffff; + acc = (acc << 17) | (acc >>> 15); + acc = ((acc & 0xffff) * PRIME32_4 + (((acc >>> 16) * PRIME32_4) << 16)) & 0xffffffff; + } + + /* + ``` + while (remainingLength >= 1) { + lane = read_byte(input_ptr); + acc = acc + lane * PRIME32_5; + acc = (acc <<< 11) * PRIME32_1; + input_ptr += 1; remainingLength -= 1; + } + ``` + */ + + for (; offset < b.length; ++offset) { + const lane = b[offset]; + acc = acc + (lane as any) * PRIME32_5; + acc = (acc << 11) | (acc >>> 21); + acc = ((acc & 0xffff) * PRIME32_1 + (((acc >>> 16) * PRIME32_1) << 16)) & 0xffffffff; + } + + /* + Step 6. Final mix (avalanche) + The final mix ensures that all input bits have a chance to impact any bit in the output digest, + resulting in an unbiased distribution. This is also called avalanche effect. + ``` + acc = acc xor (acc >> 15); + acc = acc * PRIME32_2; + acc = acc xor (acc >> 13); + acc = acc * PRIME32_3; + acc = acc xor (acc >> 16); + ``` + */ + + acc = acc ^ (acc >>> 15); + acc = (((acc & 0xffff) * PRIME32_2) & 0xffffffff) + (((acc >>> 16) * PRIME32_2) << 16); + acc = acc ^ (acc >>> 13); + acc = (((acc & 0xffff) * PRIME32_3) & 0xffffffff) + (((acc >>> 16) * PRIME32_3) << 16); + acc = acc ^ (acc >>> 16); + + // turn any negatives back into a positive number; + return acc < 0 ? acc + 4294967296 : acc; +} diff --git a/patches/convex/world.ts b/patches/convex/world.ts new file mode 100644 index 0000000000000000000000000000000000000000..628f1b32d2781b8c05e3208f36bee8c119071239 --- /dev/null +++ b/patches/convex/world.ts @@ -0,0 +1,270 @@ +import { ConvexError, v } from 'convex/values'; +import { internalMutation, mutation, query } from './_generated/server'; +import { characters } from '../data/characters'; +import { insertInput } from './aiTown/insertInput'; +import { + DEFAULT_NAME, + ENGINE_ACTION_DURATION, + IDLE_WORLD_TIMEOUT, + WORLD_HEARTBEAT_INTERVAL, +} from './constants'; +import { playerId } from './aiTown/ids'; +import { kickEngine, startEngine, stopEngine } from './aiTown/main'; +import { engineInsertInput } from './engine/abstractGame'; + +export const defaultWorldStatus = query({ + handler: async (ctx) => { + const worldStatus = await ctx.db + .query('worldStatus') + .filter((q) => q.eq(q.field('isDefault'), true)) + .first(); + return worldStatus; + }, +}); + +export const heartbeatWorld = mutation({ + args: { + worldId: v.id('worlds'), + }, + handler: async (ctx, args) => { + const worldStatus = await ctx.db + .query('worldStatus') + .withIndex('worldId', (q) => q.eq('worldId', args.worldId)) + .first(); + if (!worldStatus) { + throw new Error(`Invalid world ID: ${args.worldId}`); + } + const now = Date.now(); + + // Skip the update (and then potentially make the transaction readonly) + // if it's been viewed sufficiently recently.. + if (!worldStatus.lastViewed || worldStatus.lastViewed < now - WORLD_HEARTBEAT_INTERVAL / 2) { + await ctx.db.patch(worldStatus._id, { + lastViewed: Math.max(worldStatus.lastViewed ?? now, now), + }); + } + + // Restart inactive worlds, but leave worlds explicitly stopped by the developer alone. + if (worldStatus.status === 'stoppedByDeveloper') { + console.debug(`World ${worldStatus._id} is stopped by developer, not restarting.`); + } + if (worldStatus.status === 'inactive') { + console.log(`Restarting inactive world ${worldStatus._id}...`); + await ctx.db.patch(worldStatus._id, { status: 'running' }); + await startEngine(ctx, worldStatus.worldId); + } + }, +}); + +export const stopInactiveWorlds = internalMutation({ + handler: async (ctx) => { + const cutoff = Date.now() - IDLE_WORLD_TIMEOUT; + const worlds = await ctx.db.query('worldStatus').collect(); + for (const worldStatus of worlds) { + if (cutoff < worldStatus.lastViewed || worldStatus.status !== 'running') { + continue; + } + console.log(`Stopping inactive world ${worldStatus._id}`); + await ctx.db.patch(worldStatus._id, { status: 'inactive' }); + await stopEngine(ctx, worldStatus.worldId); + } + }, +}); + +export const restartDeadWorlds = internalMutation({ + handler: async (ctx) => { + const now = Date.now(); + + // Restart an engine if it hasn't run for 2x its action duration. + const engineTimeout = now - ENGINE_ACTION_DURATION * 2; + const worlds = await ctx.db.query('worldStatus').collect(); + for (const worldStatus of worlds) { + if (worldStatus.status !== 'running') { + continue; + } + const engine = await ctx.db.get(worldStatus.engineId); + if (!engine) { + throw new Error(`Invalid engine ID: ${worldStatus.engineId}`); + } + if (engine.currentTime && engine.currentTime < engineTimeout) { + console.warn(`Restarting dead engine ${engine._id}...`); + await kickEngine(ctx, worldStatus.worldId); + } + } + }, +}); + +export const userStatus = query({ + args: { + worldId: v.id('worlds'), + oauthToken: v.optional(v.string()), + + }, + handler: async (ctx, args) => { + const { worldId, oauthToken } = args; + + if (!oauthToken) { + return null; + } + + return oauthToken; + }, +}); + +export const joinWorld = mutation({ + args: { + worldId: v.id('worlds'), + oauthToken: v.optional(v.string()), + + }, + handler: async (ctx, args) => { + const { worldId, oauthToken } = args; + + if (!oauthToken) { + throw new ConvexError(`Not logged in`); + } + // if (!identity) { + // throw new ConvexError(`Not logged in`); + // } + // const name = + // identity.givenName || identity.nickname || (identity.email && identity.email.split('@')[0]); + const name = oauthToken; + + // if (!name) { + // throw new ConvexError(`Missing name on ${JSON.stringify(identity)}`); + // } + const world = await ctx.db.get(args.worldId); + if (!world) { + throw new ConvexError(`Invalid world ID: ${args.worldId}`); + } + // const { tokenIdentifier } = identity; + return await insertInput(ctx, world._id, 'join', { + name, + character: characters[Math.floor(Math.random() * characters.length)].name, + description: `${oauthToken} is a human player`, + // description: `${identity.givenName} is a human player`, + tokenIdentifier: oauthToken, + }); + }, +}); + +export const leaveWorld = mutation({ + args: { + worldId: v.id('worlds'), + oauthToken: v.optional(v.string()), + }, + handler: async (ctx, args) => { + const { worldId, oauthToken } = args; + + + console.log('OAuth Name:', oauthToken); + if (!oauthToken) { + throw new ConvexError(`Not logged in`); + } + + const world = await ctx.db.get(args.worldId); + if (!world) { + throw new Error(`Invalid world ID: ${args.worldId}`); + } + // const existingPlayer = world.players.find((p) => p.human === tokenIdentifier); + const existingPlayer = world.players.find((p) => p.human === oauthToken); + if (!existingPlayer) { + return; + } + await insertInput(ctx, world._id, 'leave', { + playerId: existingPlayer.id, + }); + }, +}); + +export const sendWorldInput = mutation({ + args: { + engineId: v.id('engines'), + name: v.string(), + args: v.any(), + }, + handler: async (ctx, args) => { + // const identity = await ctx.auth.getUserIdentity(); + // if (!identity) { + // throw new Error(`Not logged in`); + // } + return await engineInsertInput(ctx, args.engineId, args.name as any, args.args); + }, +}); + +export const worldState = query({ + args: { + worldId: v.id('worlds'), + }, + handler: async (ctx, args) => { + const world = await ctx.db.get(args.worldId); + if (!world) { + throw new Error(`Invalid world ID: ${args.worldId}`); + } + const worldStatus = await ctx.db + .query('worldStatus') + .withIndex('worldId', (q) => q.eq('worldId', world._id)) + .unique(); + if (!worldStatus) { + throw new Error(`Invalid world status ID: ${world._id}`); + } + const engine = await ctx.db.get(worldStatus.engineId); + if (!engine) { + throw new Error(`Invalid engine ID: ${worldStatus.engineId}`); + } + return { world, engine }; + }, +}); + +export const gameDescriptions = query({ + args: { + worldId: v.id('worlds'), + }, + handler: async (ctx, args) => { + const playerDescriptions = await ctx.db + .query('playerDescriptions') + .withIndex('worldId', (q) => q.eq('worldId', args.worldId)) + .collect(); + const agentDescriptions = await ctx.db + .query('agentDescriptions') + .withIndex('worldId', (q) => q.eq('worldId', args.worldId)) + .collect(); + const worldMap = await ctx.db + .query('maps') + .withIndex('worldId', (q) => q.eq('worldId', args.worldId)) + .first(); + if (!worldMap) { + throw new Error(`No map for world: ${args.worldId}`); + } + return { worldMap, playerDescriptions, agentDescriptions }; + }, +}); + +export const previousConversation = query({ + args: { + worldId: v.id('worlds'), + playerId, + }, + handler: async (ctx, args) => { + // Walk the player's history in descending order, looking for a nonempty + // conversation. + const members = ctx.db + .query('participatedTogether') + .withIndex('playerHistory', (q) => q.eq('worldId', args.worldId).eq('player1', args.playerId)) + .order('desc'); + + for await (const member of members) { + const conversation = await ctx.db + .query('archivedConversations') + .withIndex('worldId', (q) => q.eq('worldId', args.worldId).eq('id', member.conversationId)) + .unique(); + if (!conversation) { + throw new Error(`Invalid conversation ID: ${member.conversationId}`); + } + if (conversation.numMessages > 0) { + return conversation; + } + } + return null; + }, +}); diff --git a/patches/data/spritesheets/c1.ts b/patches/data/spritesheets/c1.ts new file mode 100644 index 0000000000000000000000000000000000000000..34cfd8bc1f3f52c6a73962c4d96e78c3c42c6c64 --- /dev/null +++ b/patches/data/spritesheets/c1.ts @@ -0,0 +1,95 @@ +import { SpritesheetData } from './types'; + +export const data: SpritesheetData = { + frames: { + left: { + frame: { x: 0, y: 96, w: 32, h: 32 }, + sourceSize: { w: 32, h: 32 }, + spriteSourceSize: { x: 0, y: 0 }, + }, + left2: { + frame: { x: 32, y: 96, w: 32, h: 32 }, + sourceSize: { w: 32, h: 32 }, + spriteSourceSize: { x: 0, y: 0 }, + }, + left3: { + frame: { x: 64, y: 96, w: 32, h: 32 }, + sourceSize: { w: 32, h: 32 }, + spriteSourceSize: { x: 0, y: 0 }, + }, + left4: { + frame: { x: 96, y: 96, w: 32, h: 32 }, + sourceSize: { w: 32, h: 32 }, + spriteSourceSize: { x: 0, y: 0 }, + }, + right: { + frame: { x: 0, y: 128, w: 32, h: 32 }, + sourceSize: { w: 32, h: 32 }, + spriteSourceSize: { x: 0, y: 0 }, + }, + right2: { + frame: { x: 32, y: 128, w: 32, h: 32 }, + sourceSize: { w: 32, h: 32 }, + spriteSourceSize: { x: 0, y: 0 }, + }, + right3: { + frame: { x: 64, y: 128, w: 32, h: 32 }, + sourceSize: { w: 32, h: 32 }, + spriteSourceSize: { x: 0, y: 0 }, + }, + right4: { + frame: { x: 96, y: 128, w: 32, h: 32 }, + sourceSize: { w: 32, h: 32 }, + spriteSourceSize: { x: 0, y: 0 }, + }, + up: { + frame: { x: 128, y: 128, w: 32, h: 32 }, + sourceSize: { w: 32, h: 32 }, + spriteSourceSize: { x: 0, y: 0 }, + }, + up2: { + frame: { x: 160, y: 128, w: 32, h: 32 }, + sourceSize: { w: 32, h: 32 }, + spriteSourceSize: { x: 0, y: 0 }, + }, + up3: { + frame: { x: 192, y: 128, w: 32, h: 32 }, + sourceSize: { w: 32, h: 32 }, + spriteSourceSize: { x: 0, y: 0 }, + }, + up4: { + frame: { x: 224, y: 128, w: 32, h: 32 }, + sourceSize: { w: 32, h: 32 }, + spriteSourceSize: { x: 0, y: 0 }, + }, + down: { + frame: { x: 128, y: 32, w: 32, h: 32 }, + sourceSize: { w: 32, h: 32 }, + spriteSourceSize: { x: 0, y: 0 }, + }, + down2: { + frame: { x: 160, y: 32, w: 32, h: 32 }, + sourceSize: { w: 32, h: 32 }, + spriteSourceSize: { x: 0, y: 0 }, + }, + down3: { + frame: { x: 192, y: 32, w: 32, h: 32 }, + sourceSize: { w: 32, h: 32 }, + spriteSourceSize: { x: 0, y: 0 }, + }, + down4: { + frame: { x: 224, y: 32, w: 32, h: 32 }, + sourceSize: { w: 32, h: 32 }, + spriteSourceSize: { x: 0, y: 0 }, + }, + }, + meta: { + scale: '1', + }, + animations: { + left: ['left', 'left2', 'left3','left4'], + right: ['right', 'right2', 'right3','right4'], + up:['up', 'up2', 'up3','up4'], + down: ['down', 'down2', 'down3','down4'], + }, +}; \ No newline at end of file diff --git a/patches/data/spritesheets/types.ts b/patches/data/spritesheets/types.ts new file mode 100644 index 0000000000000000000000000000000000000000..52e4798e56b467339ee1c45c4a26417df0de841e --- /dev/null +++ b/patches/data/spritesheets/types.ts @@ -0,0 +1,26 @@ +export type Frame = { + frame: { + x: number; + y: number; + w: number; + h: number; + }; + rotated?: boolean; + trimmed?: boolean; + spriteSourceSize: { + x: number; + y: number; + }; + sourceSize: { + w: number; + h: number; + }; + }; + + export type SpritesheetData = { + frames: Record; + animations?: Record; + meta: { + scale: string; + }; + }; \ No newline at end of file diff --git a/patches/gentle.js b/patches/gentle.js index e47e2e6c03b6bb5dd6ba14fd5fd17f6b21a361f0..c2abaab53df04715ecff2c70d731023d80023c07 100644 --- a/patches/gentle.js +++ b/patches/gentle.js @@ -1,330 +1,30 @@ -// Map generated by assettool.js [Wed Oct 18 2023 21:07:27 GMT-0700 (Pacific Daylight Time)] +// Map generated by convertMap.js +export const tilesetpath = "/assets/map.png"; +export const tilesetalternatepath = "/assets/map_night.png"; +export const tiledim = 32; +export const screenxtiles = 32; +export const screenytiles = 32; +export const tilesetpxw = 768; +export const tilesetpxh = 800; -export const tilesetpath = "/assets/gentle-obj.png" -export const tiledim = 32 -export const screenxtiles = 45 -export const screenytiles = 32 -export const tilesetpxw = 1440 -export const tilesetpxh = 1024 +export const bgtilesD = [[[430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,401,425,407,407],[430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,401,425,407,407],[430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,401,425,407,407],[430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,401,425,407,407],[430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,401,425,407,407],[430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,401,425,407,407],[430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,401,425,407,407],[430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,401,425,407,407],[430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,401,425,407,407],[430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,401,425,407,407],[430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,401,425,407,407],[430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,401,425,407,407],[430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,401,425,407,407],[430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,401,425,407,407],[430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,401,425,407,407],[430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,401,425,407,407],[430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,401,425,407,407],[430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,401,425,407,407],[430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,401,425,407,407],[430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,401,425,407,407],[430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,401,425,407,407],[430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,401,425,407,407],[430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,401,425,407,407],[430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,401,425,407,407],[430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,401,425,407,407],[430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,401,425,407,407],[430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,401,425,407,407],[430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,401,425,407,407],[430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,401,425,407,407],[430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,401,425,407,407],[430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,401,425,407,407],[378,378,378,378,378,378,378,378,378,378,378,378,378,378,378,378,378,378,378,378,378,378,378,378,378,378,378,378,402,426,407,407]]]; +export const objmapD = [[[285,309,333,285,309,333,285,309,333,285,309,333,285,309,333,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,396,401,425,407,407],[286,310,334,286,310,334,286,310,334,286,310,334,286,310,334,173,197,221,245,269,293,317,-1,-1,-1,-1,-1,396,401,425,407,407],[287,311,335,287,311,335,287,311,335,287,311,335,287,311,335,174,198,222,246,270,294,318,-1,-1,-1,-1,-1,396,401,425,407,407],[-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,175,199,223,247,271,295,319,-1,-1,-1,-1,-1,396,401,425,407,407],[-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,176,200,224,248,272,296,320,-1,-1,-1,-1,-1,396,401,425,407,407],[342,373,373,373,373,373,373,366,366,366,390,414,-1,-1,-1,177,201,225,249,273,297,321,-1,-1,-1,-1,-1,396,401,425,407,407],[343,-1,-1,-1,-1,-1,-1,-1,-1,397,391,415,-1,-1,-1,178,-1,226,250,274,298,322,-1,-1,-1,-1,-1,396,401,425,407,407],[343,-1,-1,-1,-1,-1,-1,-1,-1,397,391,415,-1,-1,-1,179,203,227,251,275,299,-1,-1,-1,-1,-1,-1,396,401,425,407,407],[343,-1,-1,-1,-1,-1,-1,-1,-1,397,391,415,-1,-1,-1,-1,204,228,252,276,300,324,-1,-1,-1,-1,-1,396,401,425,407,407],[343,-1,-1,-1,-1,-1,-1,-1,-1,397,391,415,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,396,401,425,407,407],[343,181,205,229,253,277,301,325,-1,397,391,415,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,396,401,425,407,407],[343,182,206,230,254,278,302,326,-1,397,391,415,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,396,401,425,407,407],[343,183,207,231,255,279,303,327,-1,397,391,415,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,396,401,425,407,407],[343,184,208,232,256,280,304,328,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,396,401,425,407,407],[343,185,209,233,257,281,305,329,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,396,401,425,407,407],[343,186,210,234,258,282,306,330,-1,397,391,415,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,396,401,425,407,407],[343,187,211,235,259,283,307,331,-1,397,391,415,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,396,401,425,407,407],[343,188,212,236,260,284,308,332,-1,397,391,415,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,396,401,425,407,407],[343,285,309,333,-1,-1,-1,-1,-1,397,391,415,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,396,401,425,407,407],[343,286,310,334,-1,-1,-1,-1,-1,397,391,415,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,396,401,425,407,407],[343,287,311,335,-1,-1,-1,-1,-1,397,391,415,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,396,401,425,407,407],[343,213,237,261,-1,-1,-1,-1,-1,397,391,415,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,396,401,425,407,407],[343,214,238,262,-1,-1,-1,-1,-1,397,391,415,-1,-1,-1,168,192,216,240,264,288,312,-1,-1,-1,-1,-1,396,401,425,407,407],[343,215,239,263,-1,355,379,403,-1,397,391,415,-1,-1,-1,169,193,217,241,265,289,313,-1,-1,-1,-1,-1,396,401,425,407,407],[343,-1,-1,-1,-1,356,380,404,-1,397,391,415,-1,-1,-1,170,194,218,242,266,290,314,-1,-1,-1,-1,-1,396,401,425,407,407],[344,368,368,368,368,368,368,368,368,368,392,416,-1,-1,-1,171,195,219,243,267,291,315,-1,-1,-1,-1,-1,396,401,425,407,407],[-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,172,196,220,244,268,292,316,-1,-1,-1,-1,-1,396,401,425,407,407],[141,165,189,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,396,401,425,407,407],[142,166,190,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,397,-1,-1,-1,-1,-1,-1,-1,397,528,552,576,396,401,425,407,407],[143,167,191,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,397,-1,-1,-1,-1,-1,-1,-1,397,529,553,577,396,401,425,407,407],[-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,530,554,578,396,401,425,407,407],[-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,531,555,579,396,402,426,407,407]]]; +export const decorsD = [[[-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,413,413,-1,-1,-1,-1,-1,410,-1,-1,-1,-1,-1,-1,-1,-1,-1],[-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,410,-1,-1,-1,-1,-1,-1,-1,-1,-1,408,-1,-1,-1,-1,-1,-1],[-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,410,-1,-1,-1,-1,-1,-1,-1,-1],[413,410,-1,-1,-1,410,-1,-1,-1,-1,410,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],[410,413,-1,413,-1,-1,-1,410,-1,-1,-1,-1,410,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,410,-1,-1,-1,-1,-1,-1],[-1,1610613133,1610613133,1610613133,1610613133,1610613133,1610613133,1610613133,1610613133,1610613133,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,410,-1,-1,-1,-1,-1,-1,-1],[397,-1,-1,-1,-1,-1,-1,-1,408,-1,-1,-1,-1,410,-1,413,-1,-1,-1,-1,-1,412,370,-1,-1,-1,-1,-1,-1,-1,-1,-1],[397,411,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,377,377,-1,-1,-1,-1,-1,-1,-1,-1,-1],[397,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,412,370,-1,-1,-1,413,-1,-1,-1,-1,-1],[397,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,413,-1,-1,-1,-1,-1,-1,-1,-1,410,-1,-1,-1,-1,-1,-1,-1],[397,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,410,-1,-1,-1,-1,-1,-1],[397,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,410,-1,-1,-1,-1,-1,413,-1,410,-1,430,-1,-1,-1,-1,-1,-1,-1,-1],[397,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,336,372,372,372,384,430,430,430,430,430,430,410,430,430,-1,-1,-1,-1,-1,-1],[397,-1,-1,-1,-1,-1,-1,-1,-1,350,374,398,422,431,431,431,385,413,430,430,336,360,384,430,430,430,-1,-1,-1,-1,-1,-1],[397,-1,-1,-1,-1,-1,-1,-1,-1,351,375,399,423,431,431,431,385,430,430,430,337,431,385,430,413,430,413,-1,-1,-1,-1,-1],[397,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,338,373,373,373,386,430,430,413,338,362,386,430,430,430,-1,-1,-1,-1,-1,-1],[397,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,413,-1,-1,413,-1,-1,-1,-1,-1,-1,413,-1,-1,-1,-1,-1],[397,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,413,-1,-1,413,-1,-1,-1,-1,-1,410,413,-1,413,-1,-1,-1,-1,-1,-1,-1],[397,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],[397,-1,-1,-1,-1,370,370,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,413,-1,-1,410,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],[397,-1,-1,-1,-1,377,377,-1,-1,-1,-1,-1,-1,-1,410,-1,-1,-1,-1,-1,-1,-1,-1,-1,413,-1,-1,-1,-1,-1,-1,-1],[397,-1,-1,-1,-1,370,370,-1,-1,-1,-1,-1,410,-1,-1,-1,410,-1,-1,-1,410,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],[397,-1,-1,-1,-1,370,412,-1,-1,-1,-1,-1,-1,-1,410,-1,-1,-1,-1,-1,-1,-1,410,-1,-1,413,-1,-1,-1,-1,-1,-1],[397,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],[397,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,413,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,413,-1,-1,-1,-1,-1,-1,-1],[-1,2684354957,2684354957,2684354957,2684354957,2684354957,2684354957,2684354957,2684354957,2684354957,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,413,-1,-1,-1,-1,-1],[-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,410,-1,-1,-1,-1,-1,-1,-1,-1,-1],[-1,-1,-1,-1,-1,413,-1,-1,-1,-1,410,-1,-1,413,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,413,-1,-1,-1,-1,-1],[-1,-1,-1,-1,410,-1,-1,410,-1,-1,-1,-1,-1,-1,-1,-1,367,367,367,367,367,367,367,-1,-1,-1,-1,-1,-1,-1,-1,-1],[-1,-1,-1,-1,-1,-1,-1,-1,413,-1,-1,410,-1,-1,410,-1,367,367,367,367,367,367,367,-1,-1,-1,-1,-1,-1,-1,-1,-1],[410,413,-1,411,-1,413,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,413,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],[-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1]]]; -export const bgtiles = [ - [ -[ 732 , 777 , 822 , 867 , 912 , 957 , 912 , 957 , 1002 , 1047 , 912 , 957 , 1002 , 1047 , 912 , 957 , 1002 , 1047 , 1001 , 1046 , 946 , 991 , 1035 , 731 , 776 , 821 , 866 , 911 , 956 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 594 , 639 , 684 , 271 , ], -[ 733 , 778 , 823 , 868 , 913 , 958 , 913 , 958 , 1003 , 1048 , 913 , 958 , 1003 , 1048 , 913 , 958 , 1003 , 1048 , 856 , 901 , 946 , 991 , 1036 , 732 , 777 , 822 , 867 , 912 , 957 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 595 , 640 , 685 , 271 , ], -[ 734 , 779 , 824 , 869 , 914 , 959 , 914 , 959 , 1004 , 1049 , 914 , 959 , 1004 , 1049 , 914 , 959 , 1004 , 1049 , 857 , 902 , 947 , 992 , 1037 , 733 , 778 , 823 , 868 , 913 , 958 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 594 , 639 , 684 , 271 , ], -[ 735 , 780 , 825 , 870 , 915 , 960 , 915 , 960 , 1005 , 1050 , 915 , 960 , 1005 , 1050 , 915 , 960 , 1087 , 1132 , 858 , 903 , 948 , 993 , 1038 , 734 , 779 , 824 , 869 , 914 , 959 , 271 , 271 , 180 , 225 , 225 , 315 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 595 , 640 , 685 , 271 , ], -[ 736 , 781 , 826 , 871 , 916 , 278 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 1087 , 1132 , 859 , 904 , 949 , 994 , 1039 , 735 , 780 , 825 , 870 , 915 , 960 , 271 , 271 , 181 , 226 , 226 , 140 , 270 , 315 , 271 , 271 , 271 , 271 , 271 , 271 , 541 , 586 , 631 , 686 , 271 , ], -[ 737 , 782 , 827 , 872 , 917 , 233 , 271 , 271 , 271 , 271 , 271 , 233 , 271 , 271 , 271 , 271 , 1088 , 1133 , 860 , 905 , 950 , 995 , 1040 , 736 , 781 , 826 , 871 , 916 , 961 , 271 , 271 , 181 , 226 , 278 , 272 , 271 , 316 , 271 , 271 , 271 , 271 , 271 , 551 , 542 , 587 , 632 , 271 , 271 , ], -[ 738 , 783 , 828 , 873 , 271 , 271 , 235 , 962 , 962 , 962 , 962 , 962 , 271 , 271 , 271 , 271 , 1089 , 1134 , 861 , 906 , 951 , 996 , 1041 , 737 , 782 , 827 , 872 , 917 , 962 , 271 , 271 , 181 , 226 , 226 , 272 , 271 , 675 , 271 , 271 , 271 , 271 , 272 , 272 , 642 , 271 , 271 , 271 , 271 , ], -[ 739 , 784 , 829 , 874 , 271 , 271 , 235 , 962 , 962 , 962 , 962 , 962 , 271 , 271 , 271 , 271 , 1090 , 1135 , 862 , 907 , 952 , 997 , 1042 , 738 , 783 , 828 , 873 , 918 , 963 , 271 , 271 , 183 , 228 , 228 , 184 , 271 , 675 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], -[ 736 , 781 , 826 , 871 , 271 , 271 , 962 , 962 , 962 , 962 , 962 , 962 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 962 , 962 , 962 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 182 , 280 , 317 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], -[ 737 , 782 , 827 , 872 , 271 , 271 , 962 , 962 , 962 , 962 , 962 , 962 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 962 , 0 , 135 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 140 , 273 , 318 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], -[ 738 , 783 , 828 , 873 , 271 , 271 , 962 , 962 , 962 , 962 , 962 , 962 , 0 , 45 , 90 , 135 , 271 , 271 , 271 , 271 , 962 , 1 , 136 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], -[ 739 , 784 , 829 , 874 , 271 , 271 , 962 , 962 , 962 , 962 , 962 , 962 , 3 , 48 , 93 , 271 , 271 , 271 , 271 , 271 , 962 , 1 , 136 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], -[ 736 , 781 , 826 , 871 , 271 , 271 , 962 , 962 , 962 , 962 , 962 , 279 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 1 , 136 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], -[ 737 , 782 , 827 , 872 , 271 , 271 , 962 , 962 , 962 , 962 , 143 , 188 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 1 , 136 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], -[ 738 , 783 , 828 , 873 , 271 , 271 , 278 , 962 , 962 , 962 , 144 , 189 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 1 , 136 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], -[ 739 , 784 , 829 , 874 , 271 , 271 , 271 , 271 , 271 , 271 , 145 , 190 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 1 , 136 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], -[ 736 , 781 , 826 , 871 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 1 , 136 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], -[ 737 , 782 , 827 , 872 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 3 , 138 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], -[ 738 , 783 , 828 , 873 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], -[ 739 , 784 , 829 , 874 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 360 , 405 , 405 , 405 , 450 , 495 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], -[ 1270 , 1315 , 1360 , 1405 , 226 , 360 , 405 , 450 , 495 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 361 , 409 , 451 , 451 , 451 , 496 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], -[ 1271 , 1316 , 226 , 226 , 226 , 361 , 409 , 409 , 496 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 361 , 407 , 452 , 452 , 452 , 496 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], -[ 271 , 271 , 271 , 271 , 450 , 362 , 406 , 407 , 451 , 451 , 271 , 271 , 271 , 405 , 405 , 450 , 320 , 409 , 451 , 452 , 452 , 497 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], -[ 405 , 450 , 405 , 450 , 450 , 320 , 406 , 406 , 451 , 589 , 451 , 451 , 451 , 406 , 451 , 409 , 451 , 409 , 451 , 452 , 406 , 275 , 405 , 450 , 405 , 495 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], -[ 406 , 406 , 406 , 451 , 451 , 407 , 406 , 407 , 452 , 451 , 451 , 451 , 451 , 407 , 589 , 452 , 452 , 451 , 406 , 451 , 406 , 406 , 407 , 451 , 451 , 275 , 405 , 450 , 495 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], -[ 408 , 453 , 408 , 453 , 319 , 406 , 407 , 406 , 451 , 451 , 451 , 589 , 451 , 634 , 452 , 589 , 406 , 451 , 634 , 452 , 274 , 408 , 408 , 319 , 451 , 451 , 451 , 451 , 275 , 405 , 405 , 450 , 405 , 495 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], -[ 271 , 271 , 271 , 271 , 361 , 634 , 452 , 407 , 634 , 451 , 451 , 451 , 451 , 407 , 452 , 452 , 407 , 452 , 452 , 274 , 498 , 271 , 271 , 363 , 408 , 453 , 408 , 453 , 408 , 453 , 319 , 451 , 451 , 275 , 405 , 450 , 405 , 450 , 405 , 450 , 495 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], -[ 271 , 271 , 271 , 271 , 362 , 409 , 451 , 451 , 451 , 451 , 271 , 271 , 271 , 408 , 408 , 319 , 407 , 452 , 409 , 496 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 361 , 406 , 451 , 406 , 451 , 451 , 451 , 451 , 544 , 451 , 496 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], -[ 271 , 271 , 271 , 271 , 362 , 407 , 409 , 451 , 451 , 271 , 271 , 271 , 271 , 271 , 271 , 362 , 409 , 409 , 452 , 497 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 362 , 407 , 544 , 407 , 544 , 589 , 452 , 452 , 634 , 452 , 497 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], -[ 271 , 271 , 271 , 271 , 363 , 408 , 453 , 498 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 363 , 408 , 408 , 453 , 498 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 363 , 408 , 319 , 407 , 452 , 452 , 499 , 544 , 544 , 452 , 497 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], -[ 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 363 , 408 , 319 , 544 , 499 , 499 , 544 , 452 , 497 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], -[ 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 361 , 407 , 499 , 499 , 499 , 452 , 497 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], -[ 271 , 271 , 271 , 272 , 271 , 278 , 271 , 271 , 271 , 279 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 362 , 407 , 544 , 499 , 544 , 452 , 497 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], -[ 1129 , 1174 , 1219 , 1264 , 900 , 945 , 990 , 1035 , 1174 , 1219 , 1264 , 1309 , 1354 , 280 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 361 , 407 , 499 , 499 , 499 , 452 , 497 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], -[ 721 , 766 , 811 , 856 , 901 , 946 , 991 , 1036 , 1175 , 1220 , 1265 , 1310 , 1355 , 1400 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 362 , 407 , 499 , 499 , 407 , 452 , 497 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], -[ 722 , 767 , 812 , 857 , 902 , 947 , 992 , 1037 , 1176 , 1221 , 1266 , 1311 , 1356 , 1401 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 361 , 544 , 634 , 452 , 544 , 452 , 497 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], -[ 723 , 768 , 813 , 858 , 903 , 948 , 993 , 1038 , 1177 , 1222 , 1267 , 1312 , 1357 , 1402 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 362 , 407 , 452 , 452 , 452 , 274 , 498 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], -[ 724 , 769 , 814 , 859 , 904 , 949 , 994 , 1039 , 1178 , 1223 , 1268 , 1313 , 1358 , 1403 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 363 , 408 , 453 , 453 , 453 , 498 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], -[ 725 , 770 , 815 , 860 , 905 , 950 , 995 , 1040 , 1179 , 1224 , 1269 , 1314 , 1359 , 1404 , 271 , 271 , 6 , 51 , 96 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], -[ 726 , 771 , 816 , 861 , 906 , 951 , 996 , 1041 , 1180 , 1225 , 1270 , 1315 , 1360 , 1405 , 271 , 271 , 7 , 52 , 97 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], -[ 735 , 780 , 825 , 870 , 907 , 952 , 997 , 1042 , 1181 , 1226 , 1271 , 234 , 1361 , 280 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], -[ 736 , 781 , 826 , 871 , 962 , 962 , 962 , 962 , 962 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 227 , 1129 , 1174 , 1219 , 227 , 1309 , 227 , 278 , 271 , 271 , 271 , 271 , 271 , 271 , ], -[ 737 , 782 , 827 , 872 , 962 , 962 , 962 , 233 , 1007 , 1007 , 1007 , 1007 , 1007 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 234 , 1130 , 1175 , 1220 , 1265 , 1310 , 1355 , 1400 , 271 , 271 , 271 , 271 , 271 , 271 , ], -[ 738 , 783 , 828 , 873 , 962 , 962 , 962 , 1007 , 1007 , 1007 , 1007 , 1007 , 1007 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 227 , 1131 , 1176 , 1221 , 1266 , 1311 , 1356 , 1401 , 271 , 271 , 271 , 271 , 271 , 271 , ], -[ 739 , 784 , 829 , 874 , 962 , 962 , 962 , 1007 , 1007 , 1007 , 1007 , 1007 , 1007 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 1087 , 1132 , 1177 , 1222 , 1267 , 1312 , 1357 , 1402 , 271 , 271 , 271 , 271 , 271 , 271 , ], -[ 736 , 781 , 826 , 871 , 962 , 962 , 962 , 1007 , 1007 , 1007 , 1007 , 1007 , 1007 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 728 , 773 , 818 , 863 , 908 , 953 , 998 , 1043 , 271 , 271 , 271 , 271 , 271 , 271 , ], -[ 737 , 782 , 827 , 872 , 962 , 962 , 962 , 1007 , 1007 , 1007 , 1007 , 1007 , 1007 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 729 , 774 , 819 , 864 , 909 , 954 , 999 , 1044 , 271 , 271 , 271 , 271 , 271 , 271 , ], -[ 738 , 783 , 828 , 873 , 962 , 962 , 962 , 1007 , 1007 , 1007 , 1007 , 1007 , 1007 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 234 , 730 , 775 , 820 , 865 , 910 , 955 , 1000 , 1045 , 271 , 271 , 271 , 280 , 271 , 271 , ], -[ 739 , 784 , 829 , 874 , 962 , 962 , 962 , 1007 , 1007 , 1007 , 1007 , 1007 , 143 , 188 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 731 , 776 , 821 , 866 , 911 , 956 , 1001 , 1046 , 271 , 271 , 271 , 271 , 271 , 271 , ], -[ 736 , 781 , 826 , 871 , 962 , 962 , 962 , 962 , 1007 , 1007 , 1007 , 1007 , 144 , 189 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 1088 , 1133 , 1178 , 1223 , 1268 , 1313 , 1358 , 1403 , 271 , 271 , 271 , 271 , 271 , 271 , ], -[ 737 , 782 , 827 , 872 , 271 , 1007 , 1007 , 1007 , 1007 , 1007 , 1007 , 1007 , 145 , 190 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 1089 , 1134 , 1179 , 1224 , 1269 , 1314 , 1359 , 1404 , 271 , 271 , 271 , 271 , 271 , 271 , ], -[ 738 , 783 , 828 , 873 , 271 , 271 , 271 , 271 , 271 , 271 , 233 , 271 , 1007 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 280 , 1135 , 1180 , 1225 , 1270 , 1315 , 1360 , 1405 , 271 , 271 , 271 , 271 , 271 , 271 , ], -[ 739 , 784 , 829 , 874 , 271 , 271 , 271 , 1010 , 1055 , 962 , 962 , 271 , 271 , 271 , 1309 , 1354 , 1399 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 280 , 143 , 188 , 1226 , 1271 , 227 , 1361 , 234 , 271 , 280 , 1129 , 1174 , 1219 , 1264 , ], -[ 740 , 785 , 830 , 875 , 920 , 965 , 1010 , 740 , 785 , 830 , 875 , 920 , 965 , 1010 , 1310 , 1355 , 1400 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 144 , 189 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 1130 , 1175 , 1220 , 1265 , ], -[ 741 , 786 , 831 , 876 , 921 , 966 , 1011 , 741 , 786 , 831 , 876 , 921 , 966 , 1011 , 1311 , 1356 , 1401 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 145 , 190 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 1131 , 1176 , 1221 , 1266 , ], -[ 742 , 787 , 832 , 877 , 922 , 967 , 1012 , 742 , 787 , 832 , 877 , 922 , 967 , 1012 , 1312 , 1357 , 1402 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 1087 , 1132 , 1177 , 1222 , 1267 , ], -[ 743 , 788 , 833 , 878 , 923 , 968 , 1013 , 743 , 788 , 833 , 878 , 923 , 968 , 1013 , 1313 , 1358 , 1403 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 278 , 271 , 271 , 271 , 271 , 1088 , 1133 , 1178 , 1223 , 1268 , ], -[ 1178 , 1223 , 1268 , 1223 , 1268 , 1223 , 1268 , 1223 , 1268 , 1223 , 1268 , 1178 , 1223 , 1268 , 1314 , 1359 , 1404 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 227 , 271 , 1134 , 1179 , 1224 , 1269 , ], -[ 1179 , 1224 , 1269 , 1224 , 1269 , 1224 , 1269 , 1224 , 1269 , 1224 , 1269 , 1179 , 1224 , 1269 , 1315 , 1360 , 1405 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 233 , 271 , 271 , 1143 , 1188 , 1233 , ], -[ 1180 , 1225 , 1270 , 1225 , 1270 , 1225 , 1270 , 1225 , 1270 , 1225 , 1270 , 1180 , 1225 , 1270 , 1316 , 1361 , 1406 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 1009 , 1054 , 1099 , 1144 , 1189 , 1234 , ], -[ 1181 , 1226 , 1271 , 1226 , 1271 , 1226 , 1271 , 1226 , 1271 , 1226 , 1271 , 1181 , 1226 , 1271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 272 , 1129 , 272 , 965 , 1010 , 1055 , 920 , 965 , 1010 , 1055 , 1100 , 1145 , 1190 , 1235 , ], -[ 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 272 , 1130 , 1219 , 966 , 1011 , 1056 , 921 , 966 , 1011 , 1056 , 1101 , 1146 , 1191 , 1236 , ], -[ 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 1086 , 1131 , 922 , 967 , 1012 , 1057 , 922 , 967 , 1012 , 1057 , 1102 , 1147 , 1192 , 1237 , ], -[ 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 1087 , 1132 , 923 , 968 , 1013 , 1058 , 923 , 968 , 1013 , 1058 , 1103 , 1148 , 1193 , 1238 , ], -], -[ -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 231 , 276 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 232 , 277 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 416 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 370 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 371 , 416 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 763 , 808 , 754 , 799 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 8 , 53 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 417 , 462 , 507 , 552 , 597 , 642 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 764 , 809 , 755 , 800 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 9 , 54 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 844 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , 762 , 751 , 796 , 841 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , 752 , 797 , 842 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 141 , 186 , -1 , -1 , -1 , -1 , -1 , -1 , 896 , -1 , -1 , 547 , 592 , 637 , 682 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , 753 , 798 , 843 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 142 , 187 , -1 , -1 , 11 , -1 , -1 , -1 , 191 , 236 , -1 , 548 , 593 , 638 , 683 , 682 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 11 , 56 , 101 , 146 , 191 , 236 , -1 , 548 , 593 , 638 , 683 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 893 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 278 , -1 , -1 , -1 , -1 , -1 , -1 , 12 , 57 , 102 , 147 , 192 , 237 , 941 , -1 , 594 , 639 , 684 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , 893 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 13 , 58 , 103 , 148 , 193 , 238 , -1 , -1 , 595 , 640 , 685 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 1026 , 1071 , 1116 , 1161 , -1 , -1 , -1 , 278 , -1 , -1 , -1 , 15 , 14 , 59 , 104 , 149 , 194 , 239 , 850 , -1 , 594 , 639 , 684 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 1027 , 1072 , 1117 , 1162 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 15 , 60 , 105 , 150 , 195 , 850 , -1 , -1 , 595 , 640 , 685 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 1028 , 1073 , 1118 , 1163 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 850 , -1 , 551 , 596 , 641 , 686 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 1029 , 1074 , 1119 , 1164 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 371 , 416 , -1 , -1 , -1 , -1 , 551 , 596 , 641 , 686 , 687 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 417 , 507 , 462 , 507 , 552 , 597 , 642 , 642 , 687 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 547 , 592 , 637 , 682 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 231 , 276 , -1 , -1 , -1 , 894 , 939 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 548 , 593 , 638 , 683 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 232 , 277 , 235 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 549 , 594 , 639 , 684 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 380 , 380 , 425 , 470 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 381 , 381 , 426 , 471 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 382 , 382 , 427 , 472 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 280 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 383 , 428 , 473 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 550 , 595 , 640 , 685 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 384 , 429 , 474 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 551 , 596 , 641 , 686 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 233 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 385 , 385 , 430 , 475 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , 462 , 507 , 552 , 552 , 597 , 642 , 687 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 386 , 386 , 431 , 476 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 387 , 387 , 432 , 477 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 11 , 56 , 101 , 146 , 191 , 236 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 279 , -1 , -1 , -1 , -1 , 12 , 57 , 102 , 147 , 192 , 237 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 380 , 425 , 470 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 13 , 58 , 103 , 148 , 193 , 238 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 381 , 426 , 471 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 14 , 59 , 104 , 149 , 194 , 239 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 382 , 427 , 472 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 15 , 60 , 105 , 150 , 195 , 240 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 383 , 428 , 473 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 384 , 429 , 474 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 385 , 430 , 475 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 386 , 431 , 476 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 387 , 432 , 477 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 935 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 893 , 893 , 844 , -1 , -1 , -1 , -1 , -1 , 934 , -1 , -1 , -1 , -1 , 935 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , 751 , 796 , 841 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 889 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 937 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , 752 , 797 , 842 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , 894 , 939 , 753 , 798 , 843 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 893 , 893 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 937 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , 845 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , 806 , -1 , -1 , -1 , -1 , 846 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , 806 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 936 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , 806 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 934 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 936 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 936 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 763 , 808 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 764 , 809 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 934 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 934 , -1 , -1 , -1 , 851 , -1 , 852 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -],]; +export const bgtilesN =[[[526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 497, 521, 503, 503], [526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 497, 521, 503, 503], [526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 497, 521, 503, 503], [526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 497, 521, 503, 503], [526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 497, 521, 503, 503], [526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 497, 521, 503, 503], [526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 497, 521, 503, 503], [526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 497, 521, 503, 503], [526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 497, 521, 503, 503], [526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 497, 521, 503, 503], [526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 497, 521, 503, 503], [526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 497, 521, 503, 503], [526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 497, 521, 503, 503], [526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 497, 521, 503, 503], [526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 497, 521, 503, 503], [526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 497, 521, 503, 503], [526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 497, 521, 503, 503], [526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 497, 521, 503, 503], [526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 497, 521, 503, 503], [526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 497, 521, 503, 503], [526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 497, 521, 503, 503], [526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 497, 521, 503, 503], [526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 497, 521, 503, 503], [526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 497, 521, 503, 503], [526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 497, 521, 503, 503], [526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 497, 521, 503, 503], [526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 497, 521, 503, 503], [526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 497, 521, 503, 503], [526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 497, 521, 503, 503], [526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 497, 521, 503, 503], [526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 497, 521, 503, 503], [474, 474, 474, 474, 474, 474, 474, 474, 474, 474, 474, 474, 474, 474, 474, 474, 474, 474, 474, 474, 474, 474, 474, 474, 474, 474, 474, 474, 498, 522, 503, 503]]]; +export const objmapN =[[[540, 564, 588, 540, 564, 588, 540, 564, 588, 540, 564, 588, 540, 564, 588, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 492, 497, 521, 503, 503], [541, 565, 589, 541, 565, 589, 541, 565, 589, 541, 565, 589, 541, 565, 589, 5, 29, 53, 77, 101, 125, 149, -1, -1, -1, -1, -1, 492, 497, 521, 503, 503], [542, 566, 590, 542, 566, 590, 542, 566, 590, 542, 566, 590, 542, 566, 590, 6, 30, 54, 78, 102, 126, 150, -1, -1, -1, -1, -1, 492, 497, 521, 503, 503], [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 7, 31, 55, 79, 103, 127, 151, -1, -1, -1, -1, -1, 492, 497, 521, 503, 503], [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 8, 32, 56, 80, 104, 128, 152, -1, -1, -1, -1, -1, 492, 497, 521, 503, 503], [438, 469, 469, 469, 469, 469, 469, 462, 462, 462, 486, 510, -1, -1, -1, 9, 33, 57, 81, 105, 129, 153, -1, -1, -1, -1, -1, 492, 497, 521, 503, 503], [439, -1, -1, -1, -1, -1, -1, -1, -1, 493, 487, 511, -1, -1, -1, 10, -1, 58, 82, 106, 130, 154, -1, -1, -1, -1, -1, 492, 497, 521, 503, 503], [439, -1, -1, -1, -1, -1, -1, -1, -1, 493, 487, 511, -1, -1, -1, 11, 35, 59, 83, 107, 131, -1, -1, -1, -1, -1, -1, 492, 497, 521, 503, 503], [439, -1, -1, -1, -1, -1, -1, -1, -1, 493, 487, 511, -1, -1, -1, -1, 36, 60, 84, 108, 132, 156, -1, -1, -1, -1, -1, 492, 497, 521, 503, 503], [439, -1, -1, -1, -1, -1, -1, -1, -1, 493, 487, 511, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 492, 497, 521, 503, 503], [439, 13, 37, 61, 85, 109, 133, 157, -1, 493, 487, 511, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 492, 497, 521, 503, 503], [439, 14, 38, 62, 86, 110, 134, 158, -1, 493, 487, 511, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 492, 497, 521, 503, 503], [439, 15, 39, 63, 87, 111, 135, 159, -1, 493, 487, 511, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 492, 497, 521, 503, 503], [439, 16, 40, 64, 88, 112, 136, 160, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 492, 497, 521, 503, 503], [439, 17, 41, 65, 89, 113, 137, 161, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 492, 497, 521, 503, 503], [439, 18, 42, 66, 90, 114, 138, 162, -1, 493, 487, 511, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 492, 497, 521, 503, 503], [439, 19, 43, 67, 91, 115, 139, 163, -1, 493, 487, 511, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 492, 497, 521, 503, 503], [439, 20, 44, 68, 92, 116, 140, 164, -1, 493, 487, 511, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 492, 497, 521, 503, 503], [439, 540, 564, 588, -1, -1, -1, -1, -1, 493, 487, 511, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 492, 497, 521, 503, 503], [439, 541, 565, 589, -1, -1, -1, -1, -1, 493, 487, 511, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 492, 497, 521, 503, 503], [439, 542, 566, 590, -1, -1, -1, -1, -1, 493, 487, 511, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 492, 497, 521, 503, 503], [439, 543, 567, 591, -1, -1, -1, -1, -1, 493, 487, 511, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 492, 497, 521, 503, 503], [439, 544, 568, 592, -1, -1, -1, -1, -1, 493, 487, 511, -1, -1, -1, 0, 24, 48, 72, 96, 120, 144, -1, -1, -1, -1, -1, 492, 497, 521, 503, 503], [439, 545, 569, 593, -1, 451, 475, 499, -1, 493, 487, 511, -1, -1, -1, 1, 25, 49, 73, 97, 121, 145, -1, -1, -1, -1, -1, 492, 497, 521, 503, 503], [439, -1, -1, -1, -1, 452, 476, 500, -1, 493, 487, 511, -1, -1, -1, 2, 26, 50, 74, 98, 122, 146, -1, -1, -1, -1, -1, 492, 497, 521, 503, 503], [440, 464, 464, 464, 464, 464, 464, 464, 464, 464, 488, 512, -1, -1, -1, 3, 27, 51, 75, 99, 123, 147, -1, -1, -1, -1, -1, 492, 497, 521, 503, 503], [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 4, 28, 52, 76, 100, 124, 148, -1, -1, -1, -1, -1, 492, 497, 521, 503, 503], [546, 570, 594, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 492, 497, 521, 503, 503], [547, 571, 595, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 493, -1, -1, -1, -1, -1, -1, -1, 493, 534, 558, 582, 492, 497, 521, 503, 503], [548, 572, 596, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 493, -1, -1, -1, -1, -1, -1, -1, 493, 535, 559, 583, 492, 497, 521, 503, 503], [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 536, 560, 584, 492, 497, 521, 503, 503], [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 537, 561, 585, 492, 498, 522, 503, 503]]]; +export const decorsN =[[[-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 509, 509, -1, -1, -1, -1, -1, 506, -1, -1, -1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 506, -1, -1, -1, -1, -1, -1, -1, -1, -1, 504, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 506, -1, -1, -1, -1, -1, -1, -1, -1], [509, 506, -1, -1, -1, 506, -1, -1, -1, -1, 506, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], [506, 509, -1, 509, -1, -1, -1, 506, -1, -1, -1, -1, 506, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 506, -1, -1, -1, -1, -1, -1], [-1, 1610613133, 1610613133, 1610613133, 1610613133, 1610613133, 1610613133, 1610613133, 1610613133, 1610613133, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 506, -1, -1, -1, -1, -1, -1, -1], [493, -1, -1, -1, -1, -1, -1, -1, 504, -1, -1, -1, -1, 506, -1, 509, -1, -1, -1, -1, -1, 508, 466, -1, -1, -1, -1, -1, -1, -1, -1, -1], [493, 507, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 473, 473, -1, -1, -1, -1, -1, -1, -1, -1, -1], [493, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 508, 466, -1, -1, -1, 509, -1, -1, -1, -1, -1], [493, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 509, -1, -1, -1, -1, -1, -1, -1, -1, 506, -1, -1, -1, -1, -1, -1, -1], [493, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 506, -1, -1, -1, -1, -1, -1], [493, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 506, -1, -1, -1, -1, -1, 509, -1, 506, -1, 526, -1, -1, -1, -1, -1, -1, -1, -1], [493, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 432, 468, 468, 468, 480, 526, 526, 526, 526, 526, 526, 506, 526, 526, -1, -1, -1, -1, -1, -1], [493, -1, -1, -1, -1, -1, -1, -1, -1, 446, 470, 494, 518, 527, 527, 527, 481, 509, 526, 526, 432, 456, 480, 526, 526, 526, -1, -1, -1, -1, -1, -1], [493, -1, -1, -1, -1, -1, -1, -1, -1, 447, 471, 495, 519, 527, 527, 527, 481, 526, 526, 526, 433, 527, 481, 526, 509, 526, 509, -1, -1, -1, -1, -1], [493, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 434, 469, 469, 469, 482, 526, 526, 509, 434, 458, 482, 526, 526, 526, -1, -1, -1, -1, -1, -1], [493, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 509, -1, -1, 509, -1, -1, -1, -1, -1, -1, 509, -1, -1, -1, -1, -1], [493, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 509, -1, -1, 509, -1, -1, -1, -1, -1, 506, 509, -1, 509, -1, -1, -1, -1, -1, -1, -1], [493, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], [493, -1, -1, -1, -1, 466, 466, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 509, -1, -1, 506, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], [493, -1, -1, -1, -1, 473, 473, -1, -1, -1, -1, -1, -1, -1, 506, -1, -1, -1, -1, -1, -1, -1, -1, -1, 509, -1, -1, -1, -1, -1, -1, -1], [493, -1, -1, -1, -1, 466, 466, -1, -1, -1, -1, -1, 506, -1, -1, -1, 506, -1, -1, -1, 506, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], [493, -1, -1, -1, -1, 466, 508, -1, -1, -1, -1, -1, -1, -1, 506, -1, -1, -1, -1, -1, -1, -1, 506, -1, -1, 509, -1, -1, -1, -1, -1, -1], [493, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], [493, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 509, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 509, -1, -1, -1, -1, -1, -1, -1], [-1, 2684354957, 2684354957, 2684354957, 2684354957, 2684354957, 2684354957, 2684354957, 2684354957, 2684354957, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 509, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 506, -1, -1, -1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, 509, -1, -1, -1, -1, 506, -1, -1, 509, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 509, -1, -1, -1, -1, -1], [-1, -1, -1, -1, 506, -1, -1, 506, -1, -1, -1, -1, -1, -1, -1, -1, 463, 463, 463, 463, 463, 463, 463, -1, -1, -1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1, -1, 509, -1, -1, 506, -1, -1, 506, -1, 463, 463, 463, 463, 463, 463, 463, -1, -1, -1, -1, -1, -1, -1, -1, -1], [506, 509, -1, 507, -1, 509, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 509, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1]]] + +export const bgtiles=bgtilesD +export const objmap = objmapD +export const decors = decorsD -export const objmap = [ -[ -[ 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , -1 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , -1 , ], -[ 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , -1 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , -1 , ], -[ 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , -1 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , -1 , ], -[ 458 , 458 , 458 , 458 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 458 , 458 , 458 , 458 , -1 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , -1 , -1 , 367 , 367 , 367 , 367 , -1 , -1 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , -1 , ], -[ 458 , 458 , 458 , 458 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 458 , 458 , 458 , 458 , -1 , 367 , 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , 367 , -1 , -1 , 367 , 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , 367 , -1 , -1 , 367 , -1 , ], -[ 458 , 458 , 458 , 458 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 458 , 458 , 458 , 458 , -1 , 367 , 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , 367 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , -1 , -1 , -1 , -1 , 367 , -1 , -1 , -1 , -1 , ], -[ 458 , 458 , 458 , 458 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , 458 , 458 , 458 , 458 , -1 , 367 , 367 , 367 , 367 , -1 , 458 , 458 , -1 , -1 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , 367 , 367 , 367 , -1 , -1 , -1 , -1 , ], -[ 458 , 458 , 458 , 458 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 458 , -1 , -1 , 367 , 367 , 367 , 367 , -1 , 458 , 458 , -1 , -1 , 367 , 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ 458 , 458 , 458 , 458 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , -1 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ 458 , 458 , 458 , 458 , -1 , -1 , -1 , -1 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ 458 , 458 , 458 , 458 , -1 , -1 , -1 , -1 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , -1 , -1 , -1 , ], -[ 458 , 458 , 458 , 458 , -1 , -1 , -1 , -1 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , -1 , -1 , -1 , ], -[ 458 , 458 , 458 , 458 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , -1 , -1 , -1 , 367 , 367 , 367 , 367 , -1 , ], -[ 458 , 458 , 458 , 458 , -1 , -1 , -1 , -1 , 367 , -1 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , 367 , 367 , 367 , -1 , 367 , 367 , 367 , 367 , -1 , ], -[ 458 , 458 , 458 , 458 , -1 , -1 , -1 , 367 , -1 , -1 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , 367 , 367 , 367 , -1 , -1 , 367 , 367 , 367 , -1 , ], -[ 458 , 458 , 458 , 458 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 458 , 458 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , 367 , 367 , 367 , -1 , -1 , 367 , 367 , 367 , -1 , ], -[ 458 , 458 , 458 , 458 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 458 , 458 , 458 , 458 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , -1 , ], -[ 458 , 458 , 458 , 458 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 458 , 458 , 458 , 458 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , -1 , ], -[ 458 , 458 , 458 , 458 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 458 , 458 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , -1 , -1 , ], -[ 458 , 458 , 458 , 458 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 458 , 458 , 458 , 458 , 458 , 458 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , -1 , -1 , -1 , ], -[ 458 , 458 , 458 , 458 , -1 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , -1 , -1 , -1 , 458 , 458 , 458 , 458 , 458 , 458 , -1 , 458 , 458 , -1 , -1 , -1 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , -1 , -1 , -1 , 458 , 458 , 458 , 458 , 458 , 458 , -1 , 458 , 458 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , -1 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , -1 , -1 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , ], -[ 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , -1 , -1 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , ], -[ 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , -1 , -1 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , ], -[ -1 , -1 , -1 , -1 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , -1 , -1 , 458 , 458 , 458 , 458 , 458 , -1 , -1 , -1 , -1 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , ], -[ -1 , -1 , -1 , -1 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , -1 , 458 , 458 , 458 , 458 , 458 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , ], -[ -1 , -1 , -1 , -1 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , -1 , -1 , -1 , 458 , 458 , 458 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , -1 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , ], -[ -1 , -1 , -1 , -1 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , -1 , -1 , -1 , -1 , 458 , 458 , 458 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , -1 , -1 , -1 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , 367 , 367 , 367 , -1 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , 367 , 367 , 367 , -1 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , 367 , 367 , 367 , -1 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , ], -[ 367 , 367 , 367 , 367 , 367 , 367 , -1 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , -1 , -1 , -1 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , ], -[ 367 , 367 , 367 , 367 , 367 , 367 , -1 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , ], -[ 367 , 367 , 367 , 367 , 367 , 367 , -1 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 458 , 458 , 458 , 458 , 458 , 458 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , ], -[ 367 , 367 , 367 , 367 , 367 , 367 , -1 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , -1 , -1 , 367 , 367 , 367 , -1 , -1 , 367 , 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , ], -[ 367 , 367 , 367 , 367 , 367 , 367 , -1 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , -1 , -1 , 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ 367 , 367 , 367 , -1 , -1 , -1 , -1 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ 367 , 367 , 367 , -1 , -1 , 367 , 367 , -1 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , 367 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ 367 , 367 , 367 , -1 , -1 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , 367 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ 367 , 367 , 367 , -1 , -1 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ 367 , 367 , 367 , -1 , 367 , -1 , -1 , -1 , -1 , -1 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , 367 , ], -[ 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , 367 , ], -[ 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , -1 , -1 , -1 , 367 , 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , 367 , ], -[ 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , -1 , -1 , -1 , 367 , 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , 367 , ], -[ 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , 367 , ], -[ 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , 367 , ], -[ 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , 367 , ], -[ 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , 367 , ], -[ 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , -1 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , 367 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , ], -], -[ -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], -],]; export const animatedsprites = [ -{ x: 1440, y: 352, w: 32, h: 32, layer: 1, sheet: "campfire.json", animation: "pixels_large" }, -{ x: 736, y: 288, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, -{ x: 768, y: 288, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, -{ x: 800, y: 288, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, -{ x: 800, y: 256, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, -{ x: 832, y: 288, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, -{ x: 832, y: 224, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, -{ x: 800, y: 224, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, -{ x: 800, y: 192, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, -{ x: 768, y: 192, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, -{ x: 768, y: 160, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, -{ x: 800, y: 128, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, -{ x: 768, y: 96, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, -{ x: 800, y: 64, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, -{ x: 736, y: 448, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, -{ x: 768, y: 448, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, -{ x: 800, y: 448, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, -{ x: 832, y: 448, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, -{ x: 832, y: 480, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, -{ x: 800, y: 480, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, -{ x: 800, y: 512, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, -{ x: 768, y: 512, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, -{ x: 768, y: 544, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, -{ x: 800, y: 576, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, -{ x: 768, y: 480, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, -{ x: 768, y: 736, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, -{ x: 800, y: 768, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, -{ x: 800, y: 800, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, -{ x: 800, y: 832, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, -{ x: 800, y: 864, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, -{ x: 864, y: 1024, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, -{ x: 896, y: 1056, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, -{ x: 864, y: 1088, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, -{ x: 896, y: 1088, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, -{ x: 896, y: 1120, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, -{ x: 896, y: 1152, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, -{ x: 896, y: 1184, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, -{ x: 928, y: 1152, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, -{ x: 736, y: 320, w: 32, h: 96, layer: 1, sheet: "gentlewaterfall.json", animation: "pixels_large" }, -{ x: 768, y: 320, w: 32, h: 96, layer: 1, sheet: "gentlewaterfall.json", animation: "pixels_large" }, -{ x: 800, y: 320, w: 32, h: 96, layer: 1, sheet: "gentlewaterfall.json", animation: "pixels_large" }, -{ x: 832, y: 320, w: 32, h: 96, layer: 1, sheet: "gentlewaterfall.json", animation: "pixels_large" }, -{ x: 1664, y: 576, w: 208, h: 208, layer: 1, sheet: "windmill.json", animation: "pixels_large" }, -{ x: 1440, y: 768, w: 208, h: 208, layer: 1, sheet: "windmill.json", animation: "pixels_large" }, -{ x: 1120, y: 608, w: 208, h: 208, layer: 1, sheet: "windmill.json", animation: "pixels_large" }, -{ x: 736, y: 384, w: 32, h: 64, layer: 2, sheet: "gentlesplash.json", animation: "pixels_large" }, -{ x: 768, y: 384, w: 32, h: 64, layer: 2, sheet: "gentlesplash.json", animation: "pixels_large" }, -{ x: 800, y: 384, w: 32, h: 64, layer: 2, sheet: "gentlesplash.json", animation: "pixels_large" }, -{ x: 832, y: 384, w: 32, h: 64, layer: 2, sheet: "gentlesplash.json", animation: "pixels_large" }, -]; + { x: 448, y: 672, w: 32, h: 32, layer: 1, sheet: "campfire.json", animation: "pixels_large" }, + { x: 352, y: 224, w: 32, h: 32, layer: 1, sheet: "campfire.json", animation: "pixels_large" }, + { x: 512, y: 224, w: 32, h: 32, layer: 1, sheet: "campfire.json", animation: "pixels_large" }, + { x: 864, y: 160, w: 208, h: 208, layer: 1, sheet: "windmill.json", animation: "pixels_large" }, +] export const mapwidth = bgtiles[0].length; export const mapheight = bgtiles[0][0].length; \ No newline at end of file diff --git a/patches/run.sh b/patches/run.sh index c89d64ec5e4fc60bbd82b1bfa5f899e6632abd54..4d1454fbdd68d8c78bb2088ff3b5362f4970f712 100755 --- a/patches/run.sh +++ b/patches/run.sh @@ -17,5 +17,10 @@ else export VITE_CONVEX_URL=https://$SPACE_HOST/backend.convex.cloud fi +export VITE_OAUTH_CLIENT_ID=$OAUTH_CLIENT_ID +export OAUTH_CLIENT_SECRET=$OAUTH_CLIENT_SECRET +export OAUTH_SCOPES=$OAUTH_SCOPES +export OPENID_PROVIDER_URL=$OPENID_PROVIDER_URL + npm run dev:frontend -- --host 0.0.0.0 & run_convex_command dev \ No newline at end of file diff --git a/patches/App.tsx b/patches/src/App.tsx similarity index 62% rename from patches/App.tsx rename to patches/src/App.tsx index 951c1b25c634fe4de794bc4a23814b5c58d03aab..4a033f508e4b8333a7890dc50d74aa6b0ad033f2 100644 --- a/patches/App.tsx +++ b/patches/src/App.tsx @@ -1,136 +1,98 @@ -import Game from './components/Game.tsx'; - -import { ToastContainer } from 'react-toastify'; -import a16zImg from '../assets/a16z.png'; -import convexImg from '../assets/convex.svg'; -import starImg from '../assets/star.svg'; -import helpImg from '../assets/help.svg'; -// import { UserButton } from '@clerk/clerk-react'; -// import { Authenticated, Unauthenticated } from 'convex/react'; -// import LoginButton from './components/buttons/LoginButton.tsx'; -import { useState } from 'react'; -import ReactModal from 'react-modal'; -import MusicButton from './components/buttons/MusicButton.tsx'; -import Button from './components/buttons/Button.tsx'; -import InteractButton from './components/buttons/InteractButton.tsx'; -import FreezeButton from './components/FreezeButton.tsx'; -import { MAX_HUMAN_PLAYERS } from '../convex/constants.ts'; -import PoweredByConvex from './components/PoweredByConvex.tsx'; - -export default function Home() { - const [helpModalOpen, setHelpModalOpen] = useState(false); - return ( -
- - - setHelpModalOpen(false)} - style={modalStyles} - contentLabel="Help modal" - ariaHideApp={false} - > -
-

Help

-

- Welcome to AI town. AI town supports both anonymous spectators and logged in{' '} - interactivity. -

-

Spectating

-

- Click and drag to move around the town, and scroll in and out to zoom. You can click on - an individual character to view its chat history. -

-

Interactivity

-

- If you log in, you can join the simulation and directly talk to different agents! After - logging in, click the "Interact" button, and your character will appear somewhere on the - map with a highlighted circle underneath you. -

-

Controls:

-

Click to navigate around.

-

- To talk to an agent, click on them and then click "Start conversation," which will ask - them to start walking towards you. Once they're nearby, the conversation will start, and - you can speak to each other. You can leave at any time by closing the conversation pane - or moving away. They may propose a conversation to you - you'll see a button to accept - in the messages panel. -

-

- AI town only supports {MAX_HUMAN_PLAYERS} humans at a time. If you're idle for five - minutes, you'll be automatically removed from the simulation. -

-
-
- {/*
- - - - - - - -
*/} - -
-

- AI Town -

- -
- A virtual town where AI characters live, chat and socialize. - {/* -
- Log in to join the town -
and the conversation! - */} -
- - - -
-
- - - - - -
- - a16z - - - Convex - -
- -
-
- ); -} - -const modalStyles = { - overlay: { - backgroundColor: 'rgb(0, 0, 0, 75%)', - zIndex: 12, - }, - content: { - top: '50%', - left: '50%', - right: 'auto', - bottom: 'auto', - marginRight: '-50%', - transform: 'translate(-50%, -50%)', - maxWidth: '50%', - - border: '10px solid rgb(23, 20, 33)', - borderRadius: '0', - background: 'rgb(35, 38, 58)', - color: 'white', - fontFamily: '"Upheaval Pro", "sans-serif"', - }, -}; +import Game from './components/Game.tsx'; + +import { ToastContainer } from 'react-toastify'; + +// import { UserButton } from '@clerk/clerk-react'; +// import { Authenticated, Unauthenticated } from 'convex/react'; +// import LoginButton from './components/buttons/LoginButton.tsx'; +import { useState } from 'react'; +import ReactModal from 'react-modal'; +import MusicButton from './components/buttons/MusicButton.tsx'; +import InteractButton from './components/buttons/InteractButton.tsx'; +import OAuthLogin from './components//buttons/OAuthLogin.tsx'; +import { MAX_HUMAN_PLAYERS } from '../convex/constants.ts'; + +export default function Home() { + const [helpModalOpen, setHelpModalOpen] = useState(false); + return ( +
+ + + setHelpModalOpen(false)} + style={modalStyles} + contentLabel="Help modal" + ariaHideApp={false} + > +
+

Help

+

+ Welcome to AI town. AI town supports both anonymous spectators and logged in{' '} + interactivity. +

+

Spectating

+

+ Click and drag to move around the town, and scroll in and out to zoom. You can click on + an individual character to view its chat history. +

+

Interactivity

+

+ If you log in, you can join the simulation and directly talk to different agents! After + logging in, click the "Interact" button, and your character will appear somewhere on the + map with a highlighted circle underneath you. +

+

Controls:

+

Click to navigate around.

+

+ To talk to an agent, click on them and then click "Start conversation," which will ask + them to start walking towards you. Once they're nearby, the conversation will start, and + you can speak to each other. You can leave at any time by closing the conversation pane + or moving away. They may propose a conversation to you - you'll see a button to accept + in the messages panel. +

+

+ AI town only supports {MAX_HUMAN_PLAYERS} humans at a time. If you're idle for five + minutes, you'll be automatically removed from the simulation. +

+
+
+ +
+ + +
+
+ + + + +
+
+ +
+
+ ); +} + +const modalStyles = { + overlay: { + backgroundColor: 'rgb(0, 0, 0, 75%)', + zIndex: 0, + }, + content: { + top: '800%', + left: '80%', + right: 'auto', + bottom: 'auto', + marginRight: '-50%', + transform: 'translate(-50%, -50%)', + maxWidth: '90%', + + border: '1px solid rgb(23, 20, 33)', + borderRadius: '0', + background: 'rgb(35, 38, 58)', + color: 'white', + fontFamily: '"Upheaval Pro", "sans-serif"', + }, +}; diff --git a/patches/src/components/Character.tsx b/patches/src/components/Character.tsx new file mode 100644 index 0000000000000000000000000000000000000000..bbc60518038ad74a6d180a5e1f3932ee36720726 --- /dev/null +++ b/patches/src/components/Character.tsx @@ -0,0 +1,120 @@ +import { BaseTexture, ISpritesheetData, Spritesheet } from 'pixi.js'; +import { useState, useEffect, useRef, useCallback } from 'react'; +import { AnimatedSprite, Container, Graphics, Text } from '@pixi/react'; +import * as PIXI from 'pixi.js'; + +export const Character = ({ + textureUrl, + spritesheetData, + x, + y, + orientation, + isMoving = false, + isThinking = false, + isSpeaking = false, + emoji = '', + isViewer = false, + speed = 0.1, + onClick, +}: { + // Path to the texture packed image. + textureUrl: string; + // The data for the spritesheet. + spritesheetData: ISpritesheetData; + // The pose of the NPC. + x: number; + y: number; + orientation: number; + isMoving?: boolean; + // Shows a thought bubble if true. + isThinking?: boolean; + // Shows a speech bubble if true. + isSpeaking?: boolean; + emoji?: string; + // Highlights the player. + isViewer?: boolean; + // The speed of the animation. Can be tuned depending on the side and speed of the NPC. + speed?: number; + onClick: () => void; +}) => { + const [spriteSheet, setSpriteSheet] = useState(); + useEffect(() => { + const parseSheet = async () => { + const sheet = new Spritesheet( + BaseTexture.from(textureUrl, { + scaleMode: PIXI.SCALE_MODES.NEAREST, + }), + spritesheetData, + ); + await sheet.parse(); + setSpriteSheet(sheet); + }; + void parseSheet(); + }, []); + + // The first "left" is "right" but reflected. + const roundedOrientation = Math.floor(orientation / 90); + const direction = ['right', 'down', 'left', 'up'][roundedOrientation]; + + // Prevents the animation from stopping when the texture changes + // (see https://github.com/pixijs/pixi-react/issues/359) + const ref = useRef(null); + useEffect(() => { + if (isMoving) { + ref.current?.play(); + } + }, [direction, isMoving]); + + if (!spriteSheet) return null; + + let blockOffset = { x: 0, y: 0 }; + switch (roundedOrientation) { + case 2: + blockOffset = { x: -20, y: 0 }; + break; + case 0: + blockOffset = { x: 20, y: 0 }; + break; + case 3: + blockOffset = { x: 0, y: -20 }; + break; + case 1: + blockOffset = { x: 0, y: 20 }; + break; + } + + return ( + + {isThinking && ( + // TODO: We'll eventually have separate assets for thinking and speech animations. + + )} + {isSpeaking && ( + // TODO: We'll eventually have separate assets for thinking and speech animations. + + )} + {isViewer && } + + {emoji && ( + + )} + + ); +}; + +function ViewerIndicator() { + const draw = useCallback((g: PIXI.Graphics) => { + g.clear(); + g.beginFill(0xffff0b, 0.5); + g.drawRoundedRect(-10, 10, 20, 10, 100); + g.endFill(); + }, []); + + return ; +} diff --git a/patches/src/components/ConvexClientProvider.tsx b/patches/src/components/ConvexClientProvider.tsx new file mode 100644 index 0000000000000000000000000000000000000000..7f222816b64e961daa17947a4fbe0f8818c64081 --- /dev/null +++ b/patches/src/components/ConvexClientProvider.tsx @@ -0,0 +1,30 @@ +import { ReactNode } from 'react'; +import { ConvexReactClient, ConvexProvider } from 'convex/react'; +// import { ConvexProviderWithClerk } from 'convex/react-clerk'; +// import { ClerkProvider, useAuth } from '@clerk/clerk-react'; + +/** + * Determines the Convex deployment to use. + * + * We perform load balancing on the frontend, by randomly selecting one of the available instances. + * We use localStorage so that individual users stay on the same instance. + */ +function convexUrl(): string { + const url = import.meta.env.VITE_CONVEX_URL as string; + if (!url) { + throw new Error('Couldn’t find the Convex deployment URL.'); + } + return url; +} + +const convex = new ConvexReactClient(convexUrl(), { unsavedChangesWarning: false }); + +export default function ConvexClientProvider({ children }: { children: ReactNode }) { + return ( + // + // + {children} + // + // + ); +} diff --git a/patches/src/components/DebugPath.tsx b/patches/src/components/DebugPath.tsx new file mode 100644 index 0000000000000000000000000000000000000000..605e268b8beb5e10917f52ddc329d02d01a3f897 --- /dev/null +++ b/patches/src/components/DebugPath.tsx @@ -0,0 +1,36 @@ +import { Graphics } from '@pixi/react'; +import { Graphics as PixiGraphics } from 'pixi.js'; +import { useCallback } from 'react'; +import { Doc } from '../../convex/_generated/dataModel'; +import { Player } from '../../convex/aiTown/player'; +import { unpackPathComponent } from '../../convex/util/types'; + +export function DebugPath({ player, tileDim }: { player: Player; tileDim: number }) { + const path = player.pathfinding?.state.kind == 'moving' && player.pathfinding.state.path; + const draw = useCallback( + (g: PixiGraphics) => { + g.clear(); + if (!path) { + return; + } + let first = true; + for (const p of path) { + const { position } = unpackPathComponent(p as any); + const x = position.x * tileDim + tileDim / 2; + const y = position.y * tileDim + tileDim / 2; + if (first) { + g.moveTo(x, y); + g.lineStyle(2, debugColor(player.id), 0.5); + first = false; + } else { + g.lineTo(x, y); + } + } + }, + [path], + ); + return path ? : null; +} +function debugColor(_id: string) { + return { h: 0, s: 50, l: 90 }; +} diff --git a/patches/src/components/DebugTimeManager.tsx b/patches/src/components/DebugTimeManager.tsx new file mode 100644 index 0000000000000000000000000000000000000000..c9505ce2221784237251690b058ed4778fc04286 --- /dev/null +++ b/patches/src/components/DebugTimeManager.tsx @@ -0,0 +1,156 @@ +import { HistoricalTimeManager } from '@/hooks/useHistoricalTime'; +import { useEffect, useLayoutEffect, useRef, useState } from 'react'; +import uPlot, { AlignedData, Options } from 'uplot'; + +const MAX_DATA_POINTS = 10000; + +export function DebugTimeManager(props: { + timeManager: HistoricalTimeManager; + width: number; + height: number; +}) { + const [plotElement, setPlotElement] = useState(null); + const [plot, setPlot] = useState(); + + useLayoutEffect(() => { + if (!plotElement) { + return; + } + const opts: Options = { + width: props.width, + height: props.height, + series: [ + {}, + { + stroke: 'white', + spanGaps: true, + pxAlign: 0, + points: { show: false }, + label: 'Buffer health', + }, + ], + scales: { + y: { distr: 1 }, + }, + axes: [ + { + side: 0, + show: false, + }, + { + ticks: { size: 0 }, + side: 1, + stroke: 'white', + }, + ], + legend: { + show: false, + }, + }; + const data: AlignedData = [[], []]; + const plot = new uPlot(opts, data, plotElement); + setPlot(plot); + }, [plotElement, props.width, props.height]); + + const timeManager = props.timeManager; + const [intervals, setIntervals] = useState([...timeManager.intervals]); + useEffect(() => { + let reqId: ReturnType = 0; + const data = { + t: [] as number[], + bufferHealth: [] as number[], + }; + const update = () => { + if (plot) { + if (data.t.length > MAX_DATA_POINTS) { + data.t = data.t.slice(-MAX_DATA_POINTS); + data.bufferHealth = data.bufferHealth.slice(-MAX_DATA_POINTS); + } + const now = Date.now() / 1000; + data.t.push(now); + data.bufferHealth.push(timeManager.bufferHealth()); + setIntervals([...timeManager.intervals]); + plot.setData([data.t, data.bufferHealth], true); + plot.setScale('x', { min: now - 10, max: now }); + } + reqId = requestAnimationFrame(update); + }; + update(); + return () => cancelAnimationFrame(reqId); + }, [plot, timeManager]); + + let intervalNode: React.ReactNode | null = null; + if (intervals.length > 0) { + const base = intervals[0].startTs; + const baseAge = Date.now() - base; + + intervalNode = ( +
+ {intervals.length} {intervals.length > 1 ? 'intervals' : 'interval'}: +
+

Base: {toSeconds(baseAge)}s ago

+ {intervals.map((interval) => { + const containsServerTs = + timeManager.prevServerTs && + interval.startTs < timeManager.prevServerTs && + timeManager.prevServerTs <= interval.endTs; + let serverTs = null; + if (containsServerTs) { + serverTs = ` (server: ${toSeconds((timeManager.prevServerTs ?? base) - base)})`; + } + return ( +
+ {toSeconds(interval.startTs - base)} - {toSeconds(interval.endTs - base)} + {serverTs} +
+ ); + })} +
+
+ ); + } + let statusNode: React.ReactNode | null = null; + if (timeManager.latestEngineStatus) { + const status = timeManager.latestEngineStatus; + let statusMsg = status.running ? 'Running' : 'Stopped'; + statusNode = ( +
+

Generation number: {status.generationNumber}

+

Input number: {status.processedInputNumber}

+

Status: {statusMsg}

+

Client skew: {toSeconds(timeManager.clockSkew())}s

+
+ ); + } + timeManager.latestEngineStatus?.generationNumber; + + return ( +
+
Engine stats
+ {statusNode} +
+ {intervalNode} +
+ ); +} + +// D3's Tableau10 +export const COLORS = ( + '4e79a7f28e2ce1575976b7b259a14fedc949af7aa1ff9da79c755fbab0ab'.match(/.{6}/g) as string[] +).map((x) => `#${x}`); + +const toSeconds = (n: number) => (n / 1000).toFixed(2); diff --git a/patches/src/components/FreezeButton.tsx b/patches/src/components/FreezeButton.tsx new file mode 100644 index 0000000000000000000000000000000000000000..0c48bf04c367eee050430d47bee7b8d43aba550c --- /dev/null +++ b/patches/src/components/FreezeButton.tsx @@ -0,0 +1,36 @@ +import { useMutation, useQuery } from 'convex/react'; +import { api } from '../../convex/_generated/api'; +import Button from './buttons/Button'; + +export default function FreezeButton() { + const stopAllowed = useQuery(api.testing.stopAllowed) ?? false; + const defaultWorld = useQuery(api.world.defaultWorldStatus); + + const frozen = defaultWorld?.status === 'stoppedByDeveloper'; + + const unfreeze = useMutation(api.testing.resume); + const freeze = useMutation(api.testing.stop); + + const flipSwitch = async () => { + if (frozen) { + console.log('Unfreezing'); + await unfreeze(); + } else { + console.log('Freezing'); + await freeze(); + } + }; + + return !stopAllowed ? null : ( + <> + + + ); +} diff --git a/patches/src/components/Game.tsx b/patches/src/components/Game.tsx new file mode 100644 index 0000000000000000000000000000000000000000..926b81167eeda86a35c5e3558c9ec8321edf20a6 --- /dev/null +++ b/patches/src/components/Game.tsx @@ -0,0 +1,85 @@ +import { useRef, useState } from 'react'; +import PixiGame from './PixiGame.tsx'; + +import { useElementSize } from 'usehooks-ts'; +import { Stage } from '@pixi/react'; +import { ConvexProvider, useConvex, useQuery } from 'convex/react'; +import PlayerDetails from './PlayerDetails.tsx'; +import { api } from '../../convex/_generated/api'; +import { useWorldHeartbeat } from '../hooks/useWorldHeartbeat.ts'; +import { useHistoricalTime } from '../hooks/useHistoricalTime.ts'; +import { DebugTimeManager } from './DebugTimeManager.tsx'; +import { GameId } from '../../convex/aiTown/ids.ts'; +import { useServerGame } from '../hooks/serverGame.ts'; + +export const SHOW_DEBUG_UI = !!import.meta.env.VITE_SHOW_DEBUG_UI; + +export default function Game() { + const convex = useConvex(); + const [selectedElement, setSelectedElement] = useState<{ + kind: 'player'; + id: GameId<'players'>; + }>(); + const [gameWrapperRef, { width, height }] = useElementSize(); + + const worldStatus = useQuery(api.world.defaultWorldStatus); + const worldId = worldStatus?.worldId; + const engineId = worldStatus?.engineId; + + const game = useServerGame(worldId); + + // Send a periodic heartbeat to our world to keep it alive. + useWorldHeartbeat(); + + const worldState = useQuery(api.world.worldState, worldId ? { worldId } : 'skip'); + const { historicalTime, timeManager } = useHistoricalTime(worldState?.engine); + + const scrollViewRef = useRef(null); + + if (!worldId || !engineId || !game) { + return null; + } + return ( + <> + {SHOW_DEBUG_UI && } +
+ {/* Game area */} +
+
+
+ + {/* Re-propagate context because contexts are not shared between renderers. +https://github.com/michalochman/react-pixi-fiber/issues/145#issuecomment-531549215 */} + + + + +
+
+
+ {/* Right column area */} +
+ +
+
+ + ); +} diff --git a/patches/src/components/MessageInput.tsx b/patches/src/components/MessageInput.tsx new file mode 100644 index 0000000000000000000000000000000000000000..79cef42d538d8a1604d2a5f8797b7fbfa6e720a3 --- /dev/null +++ b/patches/src/components/MessageInput.tsx @@ -0,0 +1,94 @@ +import clsx from 'clsx'; +import { useMutation, useQuery } from 'convex/react'; +import { KeyboardEvent, useRef, useState } from 'react'; +import { api } from '../../convex/_generated/api'; +import { Id } from '../../convex/_generated/dataModel'; +import { useSendInput } from '../hooks/sendInput'; +import { Player } from '../../convex/aiTown/player'; +import { Conversation } from '../../convex/aiTown/conversation'; + +export function MessageInput({ + worldId, + engineId, + humanPlayer, + conversation, +}: { + worldId: Id<'worlds'>; + engineId: Id<'engines'>; + humanPlayer: Player; + conversation: Conversation; +}) { + const descriptions = useQuery(api.world.gameDescriptions, { worldId }); + const humanName = descriptions?.playerDescriptions.find((p) => p.playerId === humanPlayer.id) + ?.name; + const inputRef = useRef(null); + const inflightUuid = useRef(); + const writeMessage = useMutation(api.messages.writeMessage); + const startTyping = useSendInput(engineId, 'startTyping'); + const currentlyTyping = conversation.isTyping; + + const onKeyDown = async (e: KeyboardEvent) => { + e.stopPropagation(); + + // Set the typing indicator if we're not submitting. + if (e.key !== 'Enter') { + console.log(inflightUuid.current); + if (currentlyTyping || inflightUuid.current !== undefined) { + return; + } + inflightUuid.current = crypto.randomUUID(); + try { + // Don't show a toast on error. + await startTyping({ + playerId: humanPlayer.id, + conversationId: conversation.id, + messageUuid: inflightUuid.current, + }); + } finally { + inflightUuid.current = undefined; + } + return; + } + + // Send the current message. + e.preventDefault(); + if (!inputRef.current) { + return; + } + const text = inputRef.current.innerText; + inputRef.current.innerText = ''; + if (!text) { + return; + } + let messageUuid = inflightUuid.current; + if (currentlyTyping && currentlyTyping.playerId === humanPlayer.id) { + messageUuid = currentlyTyping.messageUuid; + } + messageUuid = messageUuid || crypto.randomUUID(); + await writeMessage({ + worldId, + playerId: humanPlayer.id, + conversationId: conversation.id, + text, + messageUuid, + }); + }; + return ( +
+
+ {humanName} +
+
+

onKeyDown(e)} + /> +

+
+ ); +} diff --git a/patches/src/components/Messages.tsx b/patches/src/components/Messages.tsx new file mode 100644 index 0000000000000000000000000000000000000000..52d52f553339a2cf5ceca4ea42b136e271d4c1f3 --- /dev/null +++ b/patches/src/components/Messages.tsx @@ -0,0 +1,167 @@ +import clsx from 'clsx'; +import { Doc, Id } from '../../convex/_generated/dataModel'; +import { useQuery } from 'convex/react'; +import { api } from '../../convex/_generated/api'; +import { MessageInput } from './MessageInput'; +import { Player } from '../../convex/aiTown/player'; +import { Conversation } from '../../convex/aiTown/conversation'; +import { useEffect, useRef } from 'react'; + +export function Messages({ + worldId, + engineId, + conversation, + inConversationWithMe, + humanPlayer, + scrollViewRef, +}: { + worldId: Id<'worlds'>; + engineId: Id<'engines'>; + conversation: + | { kind: 'active'; doc: Conversation } + | { kind: 'archived'; doc: Doc<'archivedConversations'> }; + inConversationWithMe: boolean; + humanPlayer?: Player; + scrollViewRef: React.RefObject; +}) { + const humanPlayerId = humanPlayer?.id; + const descriptions = useQuery(api.world.gameDescriptions, { worldId }); + const messages = useQuery(api.messages.listMessages, { + worldId, + conversationId: conversation.doc.id, + }); + let currentlyTyping = conversation.kind === 'active' ? conversation.doc.isTyping : undefined; + if (messages !== undefined && currentlyTyping) { + if (messages.find((m) => m.messageUuid === currentlyTyping!.messageUuid)) { + currentlyTyping = undefined; + } + } + const currentlyTypingName = + currentlyTyping && + descriptions?.playerDescriptions.find((p) => p.playerId === currentlyTyping?.playerId)?.name; + + const scrollView = scrollViewRef.current; + const isScrolledToBottom = useRef(false); + useEffect(() => { + if (!scrollView) return undefined; + + const onScroll = () => { + isScrolledToBottom.current = !!( + scrollView && scrollView.scrollHeight - scrollView.scrollTop - 50 <= scrollView.clientHeight + ); + }; + scrollView.addEventListener('scroll', onScroll); + return () => scrollView.removeEventListener('scroll', onScroll); + }, [scrollView]); + useEffect(() => { + if (isScrolledToBottom.current) { + scrollViewRef.current?.scrollTo({ + top: scrollViewRef.current.scrollHeight, + behavior: 'smooth', + }); + } + }, [messages, currentlyTyping]); + + if (messages === undefined) { + return null; + } + if (messages.length === 0 && !inConversationWithMe) { + return null; + } + const messageNodes: { time: number; node: React.ReactNode }[] = messages.map((m) => { + const node = ( +
+
+ {m.authorName} + +
+
+

{m.text}

+
+
+ ); + return { node, time: m._creationTime }; + }); + const lastMessageTs = messages.map((m) => m._creationTime).reduce((a, b) => Math.max(a, b), 0); + + const membershipNodes: typeof messageNodes = []; + if (conversation.kind === 'active') { + for (const [playerId, m] of conversation.doc.participants) { + const playerName = descriptions?.playerDescriptions.find((p) => p.playerId === playerId) + ?.name; + let started; + if (m.status.kind === 'participating') { + started = m.status.started; + } + if (started) { + membershipNodes.push({ + node: ( +
+

{playerName} joined the conversation.

+
+ ), + time: started, + }); + } + } + } else { + for (const playerId of conversation.doc.participants) { + const playerName = descriptions?.playerDescriptions.find((p) => p.playerId === playerId) + ?.name; + const started = conversation.doc.created; + membershipNodes.push({ + node: ( +
+

{playerName} joined the conversation.

+
+ ), + time: started, + }); + const ended = conversation.doc.ended; + membershipNodes.push({ + node: ( +
+

{playerName} left the conversation.

+
+ ), + // Always sort all "left" messages after the last message. + // TODO: We can remove this once we want to support more than two participants per conversation. + time: Math.max(lastMessageTs + 1, ended), + }); + } + } + const nodes = [...messageNodes, ...membershipNodes]; + nodes.sort((a, b) => a.time - b.time); + return ( +
+
+ {nodes.length > 0 && nodes.map((n) => n.node)} + {currentlyTyping && currentlyTyping.playerId !== humanPlayerId && ( +
+
+ {currentlyTypingName} + +
+
+

+ typing... +

+
+
+ )} + {humanPlayer && inConversationWithMe && conversation.kind === 'active' && ( + + )} +
+
+ ); +} diff --git a/patches/src/components/PixiGame.tsx b/patches/src/components/PixiGame.tsx new file mode 100644 index 0000000000000000000000000000000000000000..afa330ebe3c0fc318f7f8e6fc5cb0a5748d6a7b1 --- /dev/null +++ b/patches/src/components/PixiGame.tsx @@ -0,0 +1,160 @@ +import * as PIXI from 'pixi.js'; +import { useApp } from '@pixi/react'; +import { Player, SelectElement } from './Player.tsx'; +import { useEffect, useRef, useState } from 'react'; +import PixiStaticMap from './PixiStaticMap.tsx'; +import PixiViewport from './PixiViewport.tsx'; +import { Viewport } from 'pixi-viewport'; +import { Id } from '../../convex/_generated/dataModel'; +import { useQuery } from 'convex/react'; +import { api } from '../../convex/_generated/api.js'; +import { useSendInput } from '../hooks/sendInput.ts'; +import { toastOnError } from '../toasts.ts'; +import { DebugPath } from './DebugPath.tsx'; +import { PositionIndicator } from './PositionIndicator.tsx'; +import { SHOW_DEBUG_UI } from './Game.tsx'; +import { ServerGame } from '../hooks/serverGame.ts'; + +export const PixiGame = (props: { + worldId: Id<'worlds'>; + engineId: Id<'engines'>; + game: ServerGame; + historicalTime: number | undefined; + width: number; + height: number; + setSelectedElement: SelectElement; +}) => { + // PIXI setup. + const pixiApp = useApp(); + const viewportRef = useRef(); + const oauth = JSON.parse(localStorage.getItem('oauth')); + const oauthToken = oauth ? oauth.userInfo.fullname : undefined; + const humanTokenIdentifier = useQuery(api.world.userStatus, { worldId: props.worldId, oauthToken }) ?? null; + + + const humanPlayerId = [...props.game.world.players.values()].find( + (p) => p.human === humanTokenIdentifier, + )?.id; + + const moveTo = useSendInput(props.engineId, 'moveTo'); + + // Interaction for clicking on the world to navigate. + const dragStart = useRef<{ screenX: number; screenY: number } | null>(null); + const onMapPointerDown = (e: any) => { + // https://pixijs.download/dev/docs/PIXI.FederatedPointerEvent.html + dragStart.current = { screenX: e.screenX, screenY: e.screenY }; + }; + + const [lastDestination, setLastDestination] = useState<{ + x: number; + y: number; + t: number; + } | null>(null); + const onMapPointerUp = async (e: any) => { + if (dragStart.current) { + const { screenX, screenY } = dragStart.current; + dragStart.current = null; + const [dx, dy] = [screenX - e.screenX, screenY - e.screenY]; + const dist = Math.sqrt(dx * dx + dy * dy); + if (dist > 10) { + console.log(`Skipping navigation on drag event (${dist}px)`); + return; + } + } + if (!humanPlayerId) { + return; + } + const viewport = viewportRef.current; + if (!viewport) { + return; + } + const gameSpacePx = viewport.toWorld(e.screenX, e.screenY); + const tileDim = props.game.worldMap.tileDim; + const gameSpaceTiles = { + x: gameSpacePx.x / tileDim, + y: gameSpacePx.y / tileDim, + }; + setLastDestination({ t: Date.now(), ...gameSpaceTiles }); + const roundedTiles = { + x: Math.floor(gameSpaceTiles.x), + y: Math.floor(gameSpaceTiles.y), + }; + console.log(`Moving to ${JSON.stringify(roundedTiles)}`); + await toastOnError(moveTo({ playerId: humanPlayerId, destination: roundedTiles })); + }; + const { width, height, tileDim } = props.game.worldMap; + const players = [...props.game.world.players.values()]; + + // State to manage the current tileSet + const [currentTileSet, setCurrentTileSet] = useState({ + background: props.game.worldMap.bgTiles, + objectMap: props.game.worldMap.objectTiles, + decor: props.game.worldMap.decorTiles, + }); + + // Effect hook to change the tileSet based on a day/night condition + useEffect(() => { + const tileSet = props.game.world.dayNightCycle.isDay + ? { + background: props.game.worldMap.bgTiles, + objectMap: props.game.worldMap.objectTiles, + decor: props.game.worldMap.decorTiles, + } : { + background: props.game.worldMap.bgTilesN, + objectMap: props.game.worldMap.objectTilesN, + decor: props.game.worldMap.decorTilesN, + }; + setCurrentTileSet(tileSet); + }, [props.game.world.dayNightCycle.isDay]); + // Zoom on the user’s avatar when it is created + useEffect(() => { + if (!viewportRef.current || humanPlayerId === undefined) return; + + const humanPlayer = props.game.world.players.get(humanPlayerId)!; + viewportRef.current.animate({ + position: new PIXI.Point(humanPlayer.position.x * tileDim, humanPlayer.position.y * tileDim), + scale: 1.5, + }); + }, [humanPlayerId]); + return ( + + + {players.map( + (p) => + // Only show the path for the human player in non-debug mode. + (SHOW_DEBUG_UI || p.id === humanPlayerId) && ( + + ), + )} + {lastDestination && } + {players.map((p) => ( + + ))} + + ); +}; +export default PixiGame; diff --git a/patches/src/components/PixiStaticMap.tsx b/patches/src/components/PixiStaticMap.tsx new file mode 100644 index 0000000000000000000000000000000000000000..02121f9251791f48c33c19b4e6f14f59be9b9336 --- /dev/null +++ b/patches/src/components/PixiStaticMap.tsx @@ -0,0 +1,166 @@ +import { PixiComponent, applyDefaultProps } from '@pixi/react'; +import * as PIXI from 'pixi.js'; +import { AnimatedSprite, WorldMap, TileLayer } from '../../convex/aiTown/worldMap'; +import * as campfire from '../../data/animations/campfire.json'; +import * as gentlesparkle from '../../data/animations/gentlesparkle.json'; +import * as gentlewaterfall from '../../data/animations/gentlewaterfall.json'; +import * as gentlesplash from '../../data/animations/gentlesplash.json'; +import * as windmill from '../../data/animations/windmill.json'; +import React from 'react'; + +export type MapProps = { + map: WorldMap; + [k: string]: any; +}; + +const animations = { + 'campfire.json': { spritesheet: campfire, url: '/ai-town/assets/spritesheets/campfire.png' }, + 'gentlesparkle.json': { + spritesheet: gentlesparkle, + url: '/ai-town/assets/spritesheets/gentlesparkle32.png', + }, + 'gentlewaterfall.json': { + spritesheet: gentlewaterfall, + url: '/ai-town/assets/spritesheets/gentlewaterfall32.png', + }, + 'windmill.json': { spritesheet: windmill, url: '/ai-town/assets/spritesheets/windmill.png' }, + 'gentlesplash.json': { spritesheet: gentlesplash, + url: '/ai-town/assets/spritesheets/gentlewaterfall32.png',}, +}; + +const createTiles = (map: WorldMap) => { + const numxtiles = Math.floor(map.tileSetDimX / map.tileDim); + const numytiles = Math.floor(map.tileSetDimY / map.tileDim); + const bt = PIXI.BaseTexture.from(map.tileSetUrl, { + scaleMode: PIXI.SCALE_MODES.NEAREST, + }); + + const tiles = []; + for (let x = 0; x < numxtiles; x++) { + for (let y = 0; y < numytiles; y++) { + tiles[x + y * numxtiles] = new PIXI.Texture( + bt, + new PIXI.Rectangle(x * map.tileDim, y * map.tileDim, map.tileDim, map.tileDim), + ); + } + } + return tiles; +}; + +const renderMap = (container: PIXI.Container, map:WorldMap, tiles: PIXI.Texture[]) => { + const screenxtiles = map.bgTiles[0].length; + const screenytiles = map.bgTiles[0][0].length; + + const allLayers = [...map.bgTiles, ...map.objectTiles, ...map.decorTiles]; + + // blit bg & object layers of map onto canvas + for (let i = 0; i < screenxtiles * screenytiles; i++) { + const x = i % screenxtiles; + const y = Math.floor(i / screenxtiles); + const xPx = x * map.tileDim; + const yPx = y * map.tileDim; + + // Add all layers of backgrounds. + for (const layer of allLayers) { + const tileIndex = layer[x][y]; + // Some layers may not have tiles at this location. + if (tileIndex === -1) continue; + const ctile = new PIXI.Sprite(tiles[tileIndex]); + ctile.x = xPx; + ctile.y = yPx; + container.addChild(ctile); + } + } +}; + +const createAnimatedSprites = (container: PIXI.Container, map: WorldMap) => { + const spritesBySheet = new Map(); + for (const sprite of map.animatedSprites) { + const sheet = sprite.sheet; + if (!spritesBySheet.has(sheet)) { + spritesBySheet.set(sheet, []); + } + spritesBySheet.get(sheet)!.push(sprite); + } + + for (const [sheet, sprites] of spritesBySheet.entries()) { + const animation = (animations as any)[sheet]; + if (!animation) { + console.error('Could not find animation', sheet); + continue; + } + const { spritesheet, url } = animation; + const texture = PIXI.BaseTexture.from(url, { + scaleMode: PIXI.SCALE_MODES.NEAREST, + }); + const spriteSheet = new PIXI.Spritesheet(texture, spritesheet); + spriteSheet.parse().then(() => { + for (const sprite of sprites) { + const pixiAnimation = spriteSheet.animations[sprite.animation]; + if (!pixiAnimation) { + console.error('Failed to load animation', sprite); + continue; + } + const pixiSprite = new PIXI.AnimatedSprite(pixiAnimation); + pixiSprite.animationSpeed = 0.1; + pixiSprite.autoUpdate = true; + pixiSprite.x = sprite.x; + pixiSprite.y = sprite.y; + pixiSprite.width = sprite.w; + pixiSprite.height = sprite.h; + container.addChild(pixiSprite); + pixiSprite.play(); + } + }); + } +}; + +export const PixiStaticMapComponent = PixiComponent('StaticMap', { + create: (props: { map: WorldMap; [k: string]: any }) => { + const container = new PIXI.Container(); + const tiles = createTiles(props.map); + renderMap(container, props.map, tiles); + createAnimatedSprites(container, props.map); + + container.x = 0; + container.y = 0; + + // Set the hit area manually to ensure `pointerdown` events are delivered to this container. + const screenxtiles = props.map.bgTiles[0].length; + const screenytiles = props.map.bgTiles[0][0].length; + container.interactive = true; + container.hitArea = new PIXI.Rectangle( + 0, + 0, + screenxtiles * props.map.tileDim, + screenytiles * props.map.tileDim, + ); + + return container; + }, + + applyProps: (instance, oldProps: any, newProps: any) => { + if (oldProps.map !== newProps.map) { + instance.removeChildren(); + const tiles = createTiles(newProps.map); + renderMap(instance, newProps.map, tiles); + createAnimatedSprites(instance, newProps.map); + } + + applyDefaultProps(instance, oldProps, newProps); + }, +}); + +const PixiStaticMap = React.memo( + (props: MapProps) => { + return ; + }, + (prevProps, nextProps) => { + return ( + prevProps.map.bgTiles === nextProps.map.bgTiles && + prevProps.map.objectTiles === nextProps.map.objectTiles + ); + } +); + +export default PixiStaticMap; diff --git a/patches/src/components/PixiViewport.tsx b/patches/src/components/PixiViewport.tsx new file mode 100644 index 0000000000000000000000000000000000000000..395da46233a1741bc15f710f6db1eff7bf16d74d --- /dev/null +++ b/patches/src/components/PixiViewport.tsx @@ -0,0 +1,56 @@ +// Based on https://codepen.io/inlet/pen/yLVmPWv. +// Copyright (c) 2018 Patrick Brouwer, distributed under the MIT license. + +import { PixiComponent, useApp } from '@pixi/react'; +import { Viewport } from 'pixi-viewport'; +import { Application } from 'pixi.js'; +import { MutableRefObject, ReactNode } from 'react'; + +export type ViewportProps = { + app: Application; + viewportRef?: MutableRefObject; + + screenWidth: number; + screenHeight: number; + worldWidth: number; + worldHeight: number; + children?: ReactNode; +}; + +// https://davidfig.github.io/pixi-viewport/jsdoc/Viewport.html +export default PixiComponent('Viewport', { + create(props: ViewportProps) { + const { app, children, viewportRef, ...viewportProps } = props; + const viewport = new Viewport({ + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access + events: app.renderer.events, + passiveWheel: false, + ...viewportProps, + }); + if (viewportRef) { + viewportRef.current = viewport; + } + // Activate plugins + viewport + .drag() + .pinch({}) + .wheel() + .decelerate() + .clamp({ direction: 'all', underflow: 'center' }) + .setZoom(-10) + .clampZoom({ + minScale: (1.04 * props.screenWidth) / (props.worldWidth / 2), + maxScale: 3.0, + }); + return viewport; + }, + applyProps(viewport, oldProps: any, newProps: any) { + Object.keys(newProps).forEach((p) => { + if (p !== 'app' && p !== 'viewportRef' && p !== 'children' && oldProps[p] !== newProps[p]) { + // @ts-expect-error Ignoring TypeScript here + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + viewport[p] = newProps[p]; + } + }); + }, +}); diff --git a/patches/src/components/Player.tsx b/patches/src/components/Player.tsx new file mode 100644 index 0000000000000000000000000000000000000000..477863cb7f3888245902fa891e9d778cc81c15a7 --- /dev/null +++ b/patches/src/components/Player.tsx @@ -0,0 +1,91 @@ +import { Character } from './Character.tsx'; +import { orientationDegrees } from '../../convex/util/geometry.ts'; +import { characters } from '../../data/characters.ts'; +import { toast } from 'react-toastify'; +import { Player as ServerPlayer } from '../../convex/aiTown/player.ts'; +import { GameId } from '../../convex/aiTown/ids.ts'; +import { Id } from '../../convex/_generated/dataModel'; +import { Location, locationFields, playerLocation } from '../../convex/aiTown/location.ts'; +import { useHistoricalValue } from '../hooks/useHistoricalValue.ts'; +import { PlayerDescription } from '../../convex/aiTown/playerDescription.ts'; +import { WorldMap } from '../../convex/aiTown/worldMap.ts'; +import { ServerGame } from '../hooks/serverGame.ts'; + +export type SelectElement = (element?: { kind: 'player'; id: GameId<'players'> }) => void; + +const logged = new Set(); + +export const Player = ({ + game, + isViewer, + player, + onClick, + historicalTime, +}: { + game: ServerGame; + isViewer: boolean; + player: ServerPlayer; + + onClick: SelectElement; + historicalTime?: number; +}) => { + const playerCharacter = game.playerDescriptions.get(player.id)?.character; + if (!playerCharacter) { + throw new Error(`Player ${player.id} has no character`); + } + const character = characters.find((c) => c.name === playerCharacter); + + const locationBuffer = game.world.historicalLocations?.get(player.id); + const historicalLocation = useHistoricalValue( + locationFields, + historicalTime, + playerLocation(player), + locationBuffer, + ); + if (!character) { + if (!logged.has(playerCharacter)) { + logged.add(playerCharacter); + toast.error(`Unknown character ${playerCharacter}`); + } + return null; + } + + if (!historicalLocation) { + return null; + } + + const isSpeaking = !![...game.world.conversations.values()].find( + (c) => c.isTyping?.playerId === player.id, + ); + const isThinking = + !isSpeaking && + !![...game.world.agents.values()].find( + (a) => a.playerId === player.id && !!a.inProgressOperation, + ); + const tileDim = game.worldMap.tileDim; + const historicalFacing = { dx: historicalLocation.dx, dy: historicalLocation.dy }; + return ( + <> + 0} + isThinking={isThinking} + isSpeaking={isSpeaking} + emoji={ + player.activity && player.activity.until > (historicalTime ?? Date.now()) + ? player.activity?.emoji + : undefined + } + isViewer={isViewer} + textureUrl={character.textureUrl} + spritesheetData={character.spritesheetData} + speed={character.speed} + onClick={() => { + onClick({ kind: 'player', id: player.id }); + }} + /> + + ); +}; diff --git a/patches/src/components/PlayerDetails.tsx b/patches/src/components/PlayerDetails.tsx new file mode 100644 index 0000000000000000000000000000000000000000..572bb117fd4fe9100d1d90b3ebcebf249e48b3bc --- /dev/null +++ b/patches/src/components/PlayerDetails.tsx @@ -0,0 +1,266 @@ +import { useQuery } from 'convex/react'; +import { api } from '../../convex/_generated/api'; +import { Id } from '../../convex/_generated/dataModel'; +import closeImg from '../../assets/close.svg'; +import { SelectElement } from './Player'; +import { Messages } from './Messages'; +import { toastOnError } from '../toasts'; +import { useSendInput } from '../hooks/sendInput'; +import { Player } from '../../convex/aiTown/player'; +import { GameId } from '../../convex/aiTown/ids'; +import { ServerGame } from '../hooks/serverGame'; + +export default function PlayerDetails({ + worldId, + engineId, + game, + playerId, + setSelectedElement, + scrollViewRef, +}: { + worldId: Id<'worlds'>; + engineId: Id<'engines'>; + game: ServerGame; + playerId?: GameId<'players'>; + setSelectedElement: SelectElement; + scrollViewRef: React.RefObject; +}) { + const oauth = JSON.parse(localStorage.getItem('oauth')); + const oauthToken = oauth ? oauth.userInfo.fullname : undefined; + const humanTokenIdentifier = useQuery(api.world.userStatus, { worldId, oauthToken }); + + + const players = [...game.world.players.values()]; + const humanPlayer = players.find((p) => p.human === humanTokenIdentifier); + const humanConversation = humanPlayer ? game.world.playerConversation(humanPlayer) : undefined; + // Always select the other player if we're in a conversation with them. + if (humanPlayer && humanConversation) { + const otherPlayerIds = [...humanConversation.participants.keys()].filter( + (p) => p !== humanPlayer.id, + ); + playerId = otherPlayerIds[0]; + } + + const player = playerId && game.world.players.get(playerId); + const playerConversation = player && game.world.playerConversation(player); + + const previousConversation = useQuery( + api.world.previousConversation, + playerId ? { worldId, playerId } : 'skip', + ); + + const playerDescription = playerId && game.playerDescriptions.get(playerId); + + const startConversation = useSendInput(engineId, 'startConversation'); + const acceptInvite = useSendInput(engineId, 'acceptInvite'); + const rejectInvite = useSendInput(engineId, 'rejectInvite'); + const leaveConversation = useSendInput(engineId, 'leaveConversation'); + + if (!playerId) { + return ( +
+ Click on an agent on the map to see chat history. +
+ ); + } + if (!player) { + return null; + } + const isMe = humanPlayer && player.id === humanPlayer.id; + const canInvite = !isMe && !playerConversation && humanPlayer && !humanConversation; + const sameConversation = + !isMe && + humanPlayer && + humanConversation && + playerConversation && + humanConversation.id === playerConversation.id; + + const humanStatus = + humanPlayer && humanConversation && humanConversation.participants.get(humanPlayer.id)?.status; + const playerStatus = playerConversation && playerConversation.participants.get(playerId)?.status; + + const haveInvite = sameConversation && humanStatus?.kind === 'invited'; + const waitingForAccept = + sameConversation && playerConversation.participants.get(playerId)?.status.kind === 'invited'; + const waitingForNearby = + sameConversation && playerStatus?.kind === 'walkingOver' && humanStatus?.kind === 'walkingOver'; + + const inConversationWithMe = + sameConversation && + playerStatus?.kind === 'participating' && + humanStatus?.kind === 'participating'; + + const onStartConversation = async () => { + if (!humanPlayer || !playerId) { + return; + } + console.log(`Starting conversation`); + await toastOnError(startConversation({ playerId: humanPlayer.id, invitee: playerId })); + }; + const onAcceptInvite = async () => { + if (!humanPlayer || !humanConversation || !playerId) { + return; + } + await toastOnError( + acceptInvite({ + playerId: humanPlayer.id, + conversationId: humanConversation.id, + }), + ); + }; + const onRejectInvite = async () => { + if (!humanPlayer || !humanConversation) { + return; + } + await toastOnError( + rejectInvite({ + playerId: humanPlayer.id, + conversationId: humanConversation.id, + }), + ); + }; + const onLeaveConversation = async () => { + if (!humanPlayer || !inConversationWithMe || !humanConversation) { + return; + } + await toastOnError( + leaveConversation({ + playerId: humanPlayer.id, + conversationId: humanConversation.id, + }), + ); + }; + // const pendingSuffix = (inputName: string) => + // [...inflightInputs.values()].find((i) => i.name === inputName) ? ' opacity-50' : ''; + + const pendingSuffix = (s: string) => ''; + return ( + <> +
+
+

+ {playerDescription?.name} +

+
+ setSelectedElement(undefined)} + > +

+ +

+
+
+ {canInvite && ( + +
+ Start conversation +
+
+ )} + {waitingForAccept && ( + +
+ Waiting for accept... +
+
+ )} + {waitingForNearby && ( + +
+ Walking over... +
+
+ )} + {inConversationWithMe && ( + +
+ Leave conversation +
+
+ )} + {haveInvite && ( + <> + +
+ Accept +
+
+ +
+ Reject +
+
+ + )} + {!playerConversation && player.activity && player.activity.until > Date.now() && ( +
+

+ {player.activity.description} +

+
+ )} +
+

+ {!isMe && playerDescription?.description} + {isMe && This is you!} + {!isMe && inConversationWithMe && ( + <> +
+
(Conversing with you!) + + )} +

+
+ {!isMe && playerConversation && playerStatus?.kind === 'participating' && ( + + )} + {!playerConversation && previousConversation && ( + <> +
+

Previous conversation

+
+ + + )} + + ); +} diff --git a/patches/src/components/PositionIndicator.tsx b/patches/src/components/PositionIndicator.tsx new file mode 100644 index 0000000000000000000000000000000000000000..dd0c8008258d1571c839840f81734dfd6826035e --- /dev/null +++ b/patches/src/components/PositionIndicator.tsx @@ -0,0 +1,26 @@ +import { useCallback, useState } from 'react'; +import { Graphics } from '@pixi/react'; +import { Graphics as PixiGraphics } from 'pixi.js'; + +const ANIMATION_DURATION = 500; +const RADIUS_TILES = 0.25; + +export function PositionIndicator(props: { + destination: { x: number; y: number; t: number }; + tileDim: number; +}) { + const { destination, tileDim } = props; + const draw = (g: PixiGraphics) => { + g.clear(); + const now = Date.now(); + if (destination.t + ANIMATION_DURATION <= now) { + return; + } + const progress = (now - destination.t) / ANIMATION_DURATION; + const x = destination.x * tileDim; + const y = destination.y * tileDim; + g.lineStyle(1.5, { h: 0, s: 50, l: 90 }, 0.5); + g.drawCircle(x, y, RADIUS_TILES * progress * tileDim); + }; + return ; +} diff --git a/patches/src/components/PoweredByConvex.tsx b/patches/src/components/PoweredByConvex.tsx new file mode 100644 index 0000000000000000000000000000000000000000..195c062186af6d1b5eb1956a1dc6d9486742bca4 --- /dev/null +++ b/patches/src/components/PoweredByConvex.tsx @@ -0,0 +1,50 @@ +import bannerBg from '../../assets/convex-bg.webp'; +export default function PoweredByConvex() { + return ( + + + +
+ +
+
+ + Powered by + + + Convex + + + + + + + + + + + + + + + +
+
+
+ ); +} diff --git a/patches/Button.tsx b/patches/src/components/buttons/Button.tsx similarity index 96% rename from patches/Button.tsx rename to patches/src/components/buttons/Button.tsx index 6968397210f9f1bac00feb84ac104c97f16eb76a..7f85d18f52b0f1bc74674fda613fe7d50042fbb8 100644 --- a/patches/Button.tsx +++ b/patches/src/components/buttons/Button.tsx @@ -1,33 +1,33 @@ -import clsx from 'clsx'; -import { MouseEventHandler, ReactNode } from 'react'; - -export default function Button(props: { - className?: string; - href?: string; - imgUrl: string; - onClick?: MouseEventHandler; - title?: string; - children: ReactNode; -}) { - return ( - -
- -
- - {props.children} -
-
-
-
- ); -} +import clsx from 'clsx'; +import { MouseEventHandler, ReactNode } from 'react'; + +export default function Button(props: { + className?: string; + href?: string; + imgUrl: string; + onClick?: MouseEventHandler; + title?: string; + children: ReactNode; +}) { + return ( + +
+ +
+ + {props.children} +
+
+
+
+ ); +} diff --git a/patches/src/components/buttons/InteractButton.tsx b/patches/src/components/buttons/InteractButton.tsx new file mode 100644 index 0000000000000000000000000000000000000000..2f9e450416a7e0474d06eca1b4dc952621e46b0e --- /dev/null +++ b/patches/src/components/buttons/InteractButton.tsx @@ -0,0 +1,88 @@ +import Button from './Button'; +import { toast } from 'react-toastify'; +import interactImg from '../../../assets/interact.svg'; +import { useConvex, useMutation, useQuery } from 'convex/react'; +import { api } from '../../../convex/_generated/api'; +// import { SignInButton } from '@clerk/clerk-react'; +import { ConvexError } from 'convex/values'; +import { Id } from '../../../convex/_generated/dataModel'; +import { useCallback } from 'react'; +import { waitForInput } from '../../hooks/sendInput'; +import { useServerGame } from '../../hooks/serverGame'; + +export default function InteractButton() { + // const { isAuthenticated } = useConvexAuth(); + const worldStatus = useQuery(api.world.defaultWorldStatus); + const worldId = worldStatus?.worldId; + const game = useServerGame(worldId); + const oauth = JSON.parse(localStorage.getItem('oauth')); + const oauthToken = oauth ? oauth.userInfo.fullname : undefined; + console.log(oauthToken) + const humanTokenIdentifier = useQuery(api.world.userStatus, worldId ? { worldId, oauthToken } : 'skip'); + const userPlayerId = + game && [...game.world.players.values()].find((p) => p.human === humanTokenIdentifier)?.id; + const join = useMutation(api.world.joinWorld); + const leave = useMutation(api.world.leaveWorld); + const isPlaying = !!userPlayerId; + + const convex = useConvex(); + const joinInput = useCallback( + async (worldId: Id<'worlds'>) => { + let inputId; + try { + inputId = await join({ worldId, oauthToken }); + } catch (e: any) { + if (e instanceof ConvexError) { + toast.error(e.data); + return; + } + throw e; + } + try { + await waitForInput(convex, inputId); + } catch (e: any) { + toast.error(e.message); + } + }, + [convex, join, oauthToken], + ); + + + const joinOrLeaveGame = () => { + if ( + !worldId || + // || !isAuthenticated + game === undefined + ) { + return; + } + if (isPlaying) { + console.log(`Leaving game for player ${userPlayerId}`); + void leave({ worldId , oauthToken}); + } else { + console.log(`Joining game`); + void joinInput(worldId); + } + }; + // if (!isAuthenticated || game === undefined) { + // return ( + // + // + // + // ); + // } + return ( + + ); +} diff --git a/patches/src/components/buttons/LoginButton.tsx b/patches/src/components/buttons/LoginButton.tsx new file mode 100644 index 0000000000000000000000000000000000000000..389cf220af1f89ba04956906e1c09a8daa651267 --- /dev/null +++ b/patches/src/components/buttons/LoginButton.tsx @@ -0,0 +1,13 @@ +import { SignInButton } from '@clerk/clerk-react'; + +export default function LoginButton() { + return ( + + + + ); +} diff --git a/patches/src/components/buttons/MusicButton.tsx b/patches/src/components/buttons/MusicButton.tsx new file mode 100644 index 0000000000000000000000000000000000000000..f3a62a6218fa947018935d6b4d1be9aaccebfdba --- /dev/null +++ b/patches/src/components/buttons/MusicButton.tsx @@ -0,0 +1,53 @@ +import { useCallback, useEffect, useState } from 'react'; +import volumeImg from '../../../assets/volume.svg'; +import { sound } from '@pixi/sound'; +import Button from './Button'; +import { useQuery } from 'convex/react'; +import { api } from '../../../convex/_generated/api'; + +export default function MusicButton() { + const musicUrl = useQuery(api.music.getBackgroundMusic); + const [isPlaying, setPlaying] = useState(false); + + useEffect(() => { + if (musicUrl) { + sound.add('background', musicUrl).loop = true; + } + }, [musicUrl]); + + const flipSwitch = async () => { + if (isPlaying) { + sound.stop('background'); + } else { + await sound.play('background'); + } + setPlaying(!isPlaying); + }; + + const handleKeyPress = useCallback( + (event: { key: string }) => { + if (event.key === 'm' || event.key === 'M') { + void flipSwitch(); + } + }, + [flipSwitch], + ); + + useEffect(() => { + window.addEventListener('keydown', handleKeyPress); + return () => window.removeEventListener('keydown', handleKeyPress); + }, [handleKeyPress]); + + return ( + <> + + + ); +} diff --git a/patches/src/components/buttons/OAuthLogin.tsx b/patches/src/components/buttons/OAuthLogin.tsx new file mode 100644 index 0000000000000000000000000000000000000000..19555f26c0f5273ec4d7fc5b13aab53284f63b58 --- /dev/null +++ b/patches/src/components/buttons/OAuthLogin.tsx @@ -0,0 +1,59 @@ +import { useState, useEffect } from 'react'; +import Button from './Button'; +import hf from '../../../assets/hf.svg'; +import { oauthLoginUrl, oauthHandleRedirectIfPresent } from '@huggingface/hub'; + +const OAuthLogin = () => { + const [isSignedIn, setIsSignedIn] = useState(false); + + useEffect(() => { + const checkAuthStatus = async () => { + let oauthResult = localStorage.getItem('oauth'); + if (oauthResult) { + try { + oauthResult = JSON.parse(oauthResult); + } catch { + oauthResult = null; + } + } + + if (!oauthResult) { + oauthResult = await oauthHandleRedirectIfPresent(); + if (oauthResult) { + localStorage.setItem('oauth', JSON.stringify(oauthResult)); + } + } + + setIsSignedIn(!!oauthResult); + }; + + checkAuthStatus(); + }, []); + + const handleSignIn = async () => { + let clientId = import.meta.env.VITE_OAUTH_CLIENT_ID; + window.location.href = await oauthLoginUrl({ clientId }); + }; + + const handleSignOut = () => { + localStorage.removeItem('oauth'); + window.location.href = window.location.href.replace(/\?.*$/, ''); + setIsSignedIn(false); + }; + + return ( +
+ {isSignedIn ? ( +
+ +
+ ) : ( + + )} +
+ ); +}; + +export default OAuthLogin; \ No newline at end of file diff --git a/patches/src/editor/campfire.json b/patches/src/editor/campfire.json new file mode 100644 index 0000000000000000000000000000000000000000..27a70cf9581ac66e31cc2b7f399a60a2cd72201a --- /dev/null +++ b/patches/src/editor/campfire.json @@ -0,0 +1,45 @@ +{"frames": { + +"pixels_large1.png": +{ + "frame": {"x":0,"y":0,"w":32,"h":32}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":32,"h":32}, + "sourceSize": {"w":32,"h":32} +}, +"pixels_large2.png": +{ + "frame": {"x":32,"y":0,"w":32,"h":32}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":32,"h":32}, + "sourceSize": {"w":32,"h":32} +}, +"pixels_large3.png": +{ + "frame": {"x":64,"y":0,"w":32,"h":32}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":32,"h":32}, + "sourceSize": {"w":32,"h":32} +}, +"pixels_large4.png": +{ + "frame": {"x":96,"y":0,"w":32,"h":32}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":32,"h":32}, + "sourceSize": {"w":32,"h":32} +} +}, +"animations": { + "pixels_large": ["pixels_large1.png","pixels_large2.png","pixels_large3.png","pixels_large4.png"] +}, +"meta": { + "image": "./spritesheets/campfire.png", + "format": "RGBA8888", + "size": {"w":128,"h":32}, + "scale": "1" +} +} diff --git a/patches/src/editor/eutils.js b/patches/src/editor/eutils.js new file mode 100644 index 0000000000000000000000000000000000000000..c22a503d74b27bab748c87d6995814d8ab945a25 --- /dev/null +++ b/patches/src/editor/eutils.js @@ -0,0 +1,19 @@ + +// Function to download data to a file +export function download(data, filename, type) { + var file = new Blob([data], {type: type}); + if (window.navigator.msSaveOrOpenBlob) // IE10+ + window.navigator.msSaveOrOpenBlob(file, filename); + else { // Others + var a = document.createElement("a"), + url = URL.createObjectURL(file); + a.href = url; + a.download = filename; + document.body.appendChild(a); + a.click(); + setTimeout(function() { + document.body.removeChild(a); + window.URL.revokeObjectURL(url); + }, 0); + } +} \ No newline at end of file diff --git a/patches/src/editor/gentlesparkle.json b/patches/src/editor/gentlesparkle.json new file mode 100644 index 0000000000000000000000000000000000000000..8f4589b37ca4e709281eb59ea0f5c3ada6508bdb --- /dev/null +++ b/patches/src/editor/gentlesparkle.json @@ -0,0 +1,37 @@ +{"frames": { + +"pixels_large1.png": +{ + "frame": {"x":0,"y":0,"w":32,"h":32}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":32,"h":32}, + "sourceSize": {"w":32,"h":32} +}, +"pixels_large2.png": +{ + "frame": {"x":32,"y":0,"w":32,"h":32}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":32,"h":32}, + "sourceSize": {"w":32,"h":32} +}, +"pixels_large3.png": +{ + "frame": {"x":64,"y":0,"w":32,"h":32}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":32,"h":32}, + "sourceSize": {"w":32,"h":32} +} +}, +"animations": { + "pixels_large": ["pixels_large1.png","pixels_large2.png","pixels_large3.png"] +}, +"meta": { + "image": "./spritesheets/gentlesparkle32.png", + "format": "RGBA8888", + "size": {"w":192,"h":320}, + "scale": "1" +} +} \ No newline at end of file diff --git a/patches/src/editor/gentlesplash.json b/patches/src/editor/gentlesplash.json new file mode 100644 index 0000000000000000000000000000000000000000..2d9c1895fe33879d7299ece6b7fd8fb73d6c26ad --- /dev/null +++ b/patches/src/editor/gentlesplash.json @@ -0,0 +1,61 @@ +{"frames": { + +"pixels_large1.png": +{ + "frame": {"x":0,"y":192,"w":32,"h":64}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":32,"h":64}, + "sourceSize": {"w":32,"h":64} +}, +"pixels_large2.png": +{ + "frame": {"x":32,"y":192,"w":32,"h":64}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":32,"h":64}, + "sourceSize": {"w":32,"h":64} +}, +"pixels_large3.png": +{ + "frame": {"x":64,"y":192,"w":32,"h":64}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":32,"h":64}, + "sourceSize": {"w":32,"h":64} +}, +"pixels_large4.png": +{ + "frame": {"x":64,"y":192,"w":32,"h":64}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":32,"h":64}, + "sourceSize": {"w":32,"h":64} +}, +"pixels_large5.png": +{ + "frame": {"x":128,"y":192,"w":32,"h":64}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":32,"h":64}, + "sourceSize": {"w":32,"h":64} +}, +"pixels_large6.png": +{ + "frame": {"x":160,"y":192,"w":32,"h":64}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":32,"h":64}, + "sourceSize": {"w":32,"h":64} +} +}, +"animations": { + "pixels_large": ["pixels_large1.png","pixels_large2.png","pixels_large3.png","pixels_large4.png","pixels_large5.png","pixels_large6.png"] +}, +"meta": { + "image": "./spritesheets/gentlewaterfall32.png", + "format": "RGBA8888", + "size": {"w":192,"h":320}, + "scale": "1" +} +} \ No newline at end of file diff --git a/patches/src/editor/gentlewaterfall.json b/patches/src/editor/gentlewaterfall.json new file mode 100644 index 0000000000000000000000000000000000000000..814c0404f69d2b263b0ec46e9b6cbd1df81af4c9 --- /dev/null +++ b/patches/src/editor/gentlewaterfall.json @@ -0,0 +1,61 @@ +{"frames": { + +"pixels_large1.png": +{ + "frame": {"x":0,"y":32,"w":32,"h":96}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":32,"h":96}, + "sourceSize": {"w":32,"h":96} +}, +"pixels_large2.png": +{ + "frame": {"x":32,"y":32,"w":32,"h":96}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":32,"h":96}, + "sourceSize": {"w":32,"h":96} +}, +"pixels_large3.png": +{ + "frame": {"x":64,"y":32,"w":32,"h":96}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":32,"h":96}, + "sourceSize": {"w":32,"h":96} +}, +"pixels_large4.png": +{ + "frame": {"x":96,"y":32,"w":32,"h":96}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":32,"h":96}, + "sourceSize": {"w":32,"h":96} +}, +"pixels_large5.png": +{ + "frame": {"x":128,"y":32,"w":32,"h":96}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":32,"h":96}, + "sourceSize": {"w":32,"h":96} +}, +"pixels_large6.png": +{ + "frame": {"x":160,"y":32,"w":32,"h":96}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":32,"h":96}, + "sourceSize": {"w":32,"h":96} +} +}, +"animations": { + "pixels_large": ["pixels_large1.png","pixels_large2.png","pixels_large3.png","pixels_large4.png","pixels_large5.png","pixels_large6.png"] +}, +"meta": { + "image": "./spritesheets/gentlewaterfall32.png", + "format": "RGBA8888", + "size": {"w":192,"h":320}, + "scale": "1" +} +} \ No newline at end of file diff --git a/patches/src/editor/index.html b/patches/src/editor/index.html new file mode 100644 index 0000000000000000000000000000000000000000..309f06ec4ba1a1b0639bb4fa11f54f8dd4dba2ec --- /dev/null +++ b/patches/src/editor/index.html @@ -0,0 +1,7 @@ + + + +Level editer
+Sprite editer + + diff --git a/patches/src/editor/le.html b/patches/src/editor/le.html new file mode 100644 index 0000000000000000000000000000000000000000..d85144f16e4a1c7f73547f0adbfa8053f2fd6fea --- /dev/null +++ b/patches/src/editor/le.html @@ -0,0 +1,99 @@ + + + + + + + + +
+ + + +
+ +
+ + + + + + + + + + + + + + +
+
+
+
+
+
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + +
Composite to pngLoad levelLoad png to CompositeLoad Sprite
+ + + + + + + + +
+ 16 + 32 +
+
Load Tileset
+
+ + + + \ No newline at end of file diff --git a/patches/src/editor/le.js b/patches/src/editor/le.js new file mode 100644 index 0000000000000000000000000000000000000000..17ca68df167fcb89815ebdaad1a61a2cb7ac74ed --- /dev/null +++ b/patches/src/editor/le.js @@ -0,0 +1,1327 @@ +// -- +// Simple level editer. +// +// TODO: +// -- right now if plaxing a sprite, will place based on selected tiles. So need to clear that when +// loading a sprite +// -- fix hardcoded animations, hack of putting spritesheet into g_ctx etc +// -- create tab that contains all animations for a given json file +// -- add portals to level for character start positions +// -- if you load an animated sprite and then load a level, it just puts the sprite everywhere +// +// +// Done: +// -- fix level load bug where texture doesn't fit (load, mage, serene and then gentle) +// -- write maps with sprites +// - clear selected_tiles +// - Delete tiles +// - move magic numbers to context / initialization (zIndex, pane size etc.) +// - todo fudge factor on g_ctx.tileset +// - get rid of dangerous CONFIG.tiledim (use g_ctx.tileDim instead) +// - XXX create tilesetpadding for tilesets whos tiles are spaced (e.g. phantasy star II) +// - only use fudge to pick sprites rather than fudge and non +// - use g_ctx for g_ctx.tileset parameters instead of CONFIG (starting with initTilesetConfig) +// - todo print locations on screen +// +// +// Keybindings: +// f - fill level 0 with current tile +// -z - undo +// g - overlay 32x32 grid +// s - generate .js file to move over to convex/maps/ +// m - place a semi-transparent red mask over all tiles. This helps find invisible tiles +// d - hold while clicking a tile to delete +// p - toggle between 16pixel and 32 pixel. +// +// Known bugs and annoyances +// - if deleting a tile while filter is on, filter isn't refreshed so need to toggle with "m" +// -- + +import * as PIXI from 'pixi.js' +import { g_ctx } from './lecontext.js' // global context +import * as CONFIG from './leconfig.js' +import * as UNDO from './undo.js' +import * as MAPFILE from './mapfile.js' +import * as UI from './lehtmlui.js' +import { EventSystem } from '@pixi/events'; + +g_ctx.debug_flag = true; +g_ctx.debug_flag2 = false; // really verbose output + +function tileset_index_from_coords(x, y) { + let retme = x + (y*g_ctx.tilesettilew); + console.log("tileset_index_from_coord ",retme, x, y); + return retme; +} +function level_index_from_coords(x, y) { + // place 16px tiles in separate index space + let offset = (g_ctx.tiledimx == 16)? CONFIG.MAXTILEINDEX : 0; + let retme = x + (y*CONFIG.leveltilewidth) + offset; + return retme; +} +function tileset_index_from_px(x, y) { + let coord_x = Math.floor(x / (g_ctx.tiledimx + CONFIG.tilesetpadding)); + let coord_y = Math.floor(y / (g_ctx.tiledimx+ CONFIG.tilesetpadding)); + + console.log("tileset_index_from_px ",x, y); + + return tileset_index_from_coords(coord_x, coord_y); +} +function level_index_from_px(x, y) { + let coord_x = Math.floor(x / g_ctx.tiledimx); + let coord_y = Math.floor(y / g_ctx.tiledimx); + return level_index_from_coords(coord_x, coord_y); +} + +function tileset_coords_from_index(index) { + let x = index % (g_ctx.tilesettilew); + let y = Math.floor(index / (g_ctx.tilesettilew)); + // console.log("tilesettilewidth: ",g_ctx.tilesettilew); + // console.log("tileset_coords_from_index tile coords: ",index,x,y); + return [x,y]; +} + +function tileset_px_from_index(index) { + let ret = tileset_coords_from_index(index); + return [ret[0] * (g_ctx.tiledimx+CONFIG.tilesetpadding), ret[1] * (g_ctx.tiledimx+CONFIG.tilesetpadding)] ; +} + + +// return a sprite of size tileDim given (x,y) starting location +function sprite_from_px(x, y) { + + const bt = PIXI.BaseTexture.from(g_ctx.tilesetpath, { + scaleMode: PIXI.SCALE_MODES.NEAREST, + }); + let texture = new PIXI.Texture(bt, + new PIXI.Rectangle(x, y, g_ctx.tiledimx, g_ctx.tiledimx), + ); + return new PIXI.Sprite(texture); +} + +function DragState() { + this.square = new PIXI.Graphics(); + this.tooltip = new PIXI.Text('', { + fontFamily: 'Courier', + fontSize: 12, + fill: 0xffffff, + align: 'center', + }); + this.startx = 0; + this.starty = 0; + this.endx = 0; + this.endy = 0; +} + +class LayerContext { + + constructor(app, pane, num, mod = null) { + this.app = app; + this.scrollpane = pane; + this.num = num; + this.widthpx = CONFIG.levelwidth; + this.heightpx = CONFIG.levelheight; + + + this.container = new PIXI.Container(); + this.sprites = {}; + this.composite_sprites = {}; + this.dragctx = new DragState(); + + app.stage.addChild(this.container); + + this.mouseshadow = new PIXI.Container(); + this.mouseshadow.zIndex = CONFIG.zIndexMouseShadow; + + this.lasttileindex = -1; // current tileset index + this.curanimatedtile = null; + + this.fudgex = 0; // offset from 0,0 + this.fudgey = 0; + + this.square = new PIXI.Graphics(); + this.square.beginFill(0x2980b9); + this.square.drawRect(0, 0, CONFIG.levelwidth, CONFIG.levelheight); + this.square.endFill(); + this.square.eventMode = 'static'; + this.container.addChild(this.square); + + this.square.on('mousemove', onLevelMousemove.bind(this)); + this.square.on('mouseover', onLevelMouseover.bind(this)); + this.square.on('pointerout', onLevelMouseOut.bind(this)) + this.square.on('pointerdown', onLevelPointerDown.bind(null, this)) + .on('pointerup', onLevelDragEnd.bind(null, this)) + .on('pointerupoutside', onLevelDragEnd.bind(null, this)); + + if (mod != null && !(mod === g_ctx)) { + this.loadFromMapFile(mod); + } + } + + loadFromMapFile(mod) { + let tiles = []; + if (this.num == 0) { + tiles = mod.bgtiles[0]; + } else if (this.num == 1) { + tiles = mod.bgtiles[1]; + } else if (this.num == 2) { + tiles = mod.objmap[0]; + } else if (this.num == 3) { + tiles = mod.objmap[1]; + } else { + console.log("loadFromMapFile: Error unknow layer number"); + return; + } + + for (let x = 0; x < tiles.length; x++) { + for (let y = 0; y < tiles[0].length; y++) { + if (tiles[x][y] != -1) { + this.addTileLevelCoords(x, y, mod.tiledim, tiles[x][y]); + } + } + } + } + + // this will create a rectangle with an alpha channel for every square that has a sprite. This helps find + // sprites that are purely transparent + drawFilter() { + + if (typeof this.filtergraphics == 'undefined') { + this.filtertoggle = true; + this.filtergraphics = new PIXI.Graphics(); + this.filtergraphics.zIndex = CONFIG.zIndexFilter; + } + + if (this.filtertoggle) { + + this.filtergraphics.beginFill(0xff0000, 0.3); + for (let i in this.sprites) { + let spr = this.sprites[i]; + this.filtergraphics.drawRect(spr.x, spr.y, g_ctx.tiledimx, g_ctx.tiledimx); + } + this.filtergraphics.endFill(); + this.container.addChild(this.filtergraphics); + }else{ + this.filtergraphics.clear(); + this.container.removeChild(this.filtergraphics); + } + + this.filtertoggle = ! this.filtertoggle; + } + + // add tile of "index" to Level at location x,y + addTileLevelCoords(x, y, dim, index) { + return this.addTileLevelPx(x * dim, y * dim, index); + } + + // add tile of tileset "index" to Level at location x,y + addTileLevelPx(x, y, index) { + + if (x > CONFIG.levelwidth || y > CONFIG.levelheight){ + console.log("tile placed outside of level boundary, ignoring",x,y) + return -1; + } + + let xPx = x; + let yPx = y; + + let ctile = null; + let ctile2 = null; + + if(g_ctx.spritesheet != null){ + ctile = new PIXI.AnimatedSprite(g_ctx.spritesheet.animations['row0']); + ctile2 = new PIXI.AnimatedSprite(g_ctx.spritesheet.animations['row0']); + ctile.animationSpeed = .1; + ctile2.animationSpeed = .1; + ctile.autoUpdate = true; + ctile2.autoUpdate = true; + ctile.play(); + ctile2.play(); + + // HACK for now just stuff animated sprite details into the sprite + ctile.animationname = 'row0'; + ctile.spritesheetname = g_ctx.spritesheetname; + + } else { + let pxloc = tileset_px_from_index(index); + ctile = sprite_from_px(pxloc[0] + g_ctx.tileset.fudgex, pxloc[1] + g_ctx.tileset.fudgey); + ctile.index = index; + ctile2 = sprite_from_px(pxloc[0] + g_ctx.tileset.fudgex, pxloc[1] + g_ctx.tileset.fudgey); + } + + // snap to grid + const dx = g_ctx.tiledimx; + const dy = g_ctx.tiledimy; + ctile.x = Math.floor(xPx / dx) * dx; + ctile2.x = Math.floor(xPx / dx) * dx; + ctile.y = Math.floor(yPx / dy) * dy; + ctile2.y = Math.floor(yPx / dy) * dy; + ctile2.zIndex = this.num; + + // console.log(xPx,yPx,ctile.x,ctile.y); + + let new_index = level_index_from_px(ctile.x, ctile.y); + + if(g_ctx.debug_flag2){ + console.log('addTileLevelPx ',this.num,' ctile.x ', ctile.x, 'ctile.y ', ctile.y, "index ", index, "new_index", new_index); + } + + if (!g_ctx.dkey) { + this.container.addChild(ctile); + g_ctx.composite.container.addChild(ctile2); + } + + + if (this.sprites.hasOwnProperty(new_index)) { + if(g_ctx.debug_flag){ + console.log("addTileLevelPx: ",this.num,"removing old tile", new_index); + } + this.container.removeChild(this.sprites[new_index]); + delete this.sprites[new_index]; + g_ctx.composite.container.removeChild(this.composite_sprites[new_index]); + delete this.composite_sprites[new_index]; + } + + if (!g_ctx.dkey) { + this.sprites[new_index] = ctile; + this.composite_sprites[new_index] = ctile2; + } else if (typeof this.filtergraphics != 'undefined') { + this.filtergraphics.clear(); + this.drawFilter(); + this.drawFilter(); + } + + // consolelog("SETTING ZINDEX ", this.composite_sprites[new_index].zIndex); + return new_index; + } + +} // class LayerContext + +class TilesetContext { + + constructor(app, mod = g_ctx) { + this.app = app; + this.container = new PIXI.Container(); + + this.widthpx = g_ctx.tilesetpxw; + this.heightpx = g_ctx.tilesetpxh; + console.log(mod.tilesetpath); + const texture = PIXI.Texture.from(mod.tilesetpath); + const bg = new PIXI.Sprite(texture); + + this.square = new PIXI.Graphics(); + this.square.beginFill(0x2980b9); + this.square.drawRect(0, 0, mod.tilesetpxw, mod.tilesetpxh); + this.square.endFill(); + this.square.eventMode = 'static'; + this.container.addChild(this.square); + this.container.addChild(bg); + + this.app.stage.addChild(this.container); + + this.fudgex = 0; // offset from 0,0 + this.fudgey = 0; + + this.dragctx = new DragState(); + + this.square.on('mousedown', function (e) { + + // if a spritesheet has been loaded from a file, delete + // FIXME, we should be able to add animated tiles to the + // tileset ... + if(g_ctx.spritesheet != null){ + // FIXME .. creating a leak here. But animatedsprites are still on the map so + // cannot destroy. In the future these should be part of the UI + // g_ctx.spritesheet.destroy(); + g_ctx.spritesheet = null; + } + + g_ctx.tile_index = tileset_index_from_px(e.global.x, e.global.y); + + if(g_ctx.debug_flag) { + console.log("g_ctx.tileset mouse down. index "+g_ctx.tile_index); + } + }); + + this.square.on('pointerdown', onTilesetDragStart) + .on('pointerup', onTilesetDragEnd) + .on('pointerupoutside', onTilesetDragEnd); + } + + addTileSheet(name, sheet){ + console.log(" tileset.addTileSheet ", sheet); + + + // FIXME ... development code + g_ctx.spritesheet = sheet; + g_ctx.spritesheetname = name; + + let as = new PIXI.AnimatedSprite(sheet.animations['row0']); + as.animationSpeed = .1; + as.autoUpdate = true; + as.play(); + as.alpha = .5; + g_ctx.g_layers[0].curanimatedtile = as; + } +} // class TilesetContext + + +class CompositeContext { + + constructor(app) { + this.app = app; + this.widthpx = CONFIG.levelwidth; + this.heightpx = CONFIG.levelheight; + + this.container = new PIXI.Container(); + this.container.sortableChildren = true; + this.app.stage.addChild(this.container); + this.sprites = {}; + this.circle = new PIXI.Graphics(); + this.circle.zIndex = CONFIG.zIndexCompositePointer; + + this.fudgex = 0; // offset from 0,0 + this.fudgey = 0; + + this.mouseshadow = new PIXI.Container(); + this.mouseshadow.zIndex = CONFIG.zIndexMouseShadow; + this.lasttileindex = -1; + + this.square = new PIXI.Graphics(); + this.square.beginFill(0x2980b9); + this.square.drawRect(0, 0, CONFIG.levelwidth, CONFIG.levelheight); + this.square.endFill(); + this.square.eventMode = 'static'; + this.container.addChild(this.square); + + this.square.on('mousedown', onCompositeMousedown.bind(null, this)); + } + +} // class CompositeContext + +function loadAnimatedSpritesFromModule(mod){ + + if(!('animatedsprites' in mod) || mod.animatedsprites.length <= 0){ + return; + } + + let m = new Map(); + + for(let x = 0; x < mod.animatedsprites.length; x++){ + let spr = mod.animatedsprites[x]; + if(! m.has(spr.sheet)){ + m.set(spr.sheet, [spr]); + }else{ + m.get(spr.sheet).push(spr); + } + } + + for(let key of m.keys()){ + console.log("loadAnimatedSpritesFromModule: ",key); + PIXI.Assets.load("./"+key).then( + function(sheet) { + + // setup global state so we can use layer addTileLevelMethod + g_ctx.spritesheet = sheet; + g_ctx.spritesheetname = key; + let asprarray = m.get(key); + for (let asprite of asprarray) { + // TODO FIXME, pass in animation name + console.log("Loading animation", asprite.animation); + g_ctx.g_layers[asprite.layer].addTileLevelPx(asprite.x, asprite.y, -1); + } + g_ctx.spritesheet = null; + g_ctx.spritesheetname = null; + } + ); + } +} + +function loadMapFromModuleFinish(mod) { + g_ctx.composite.container.removeChildren(); + g_ctx.tileset_app.stage.removeChildren() + g_ctx.tileset = new TilesetContext(g_ctx.tileset_app, mod); + g_ctx.g_layer_apps[0].stage.removeChildren() + g_ctx.g_layers[0] = new LayerContext(g_ctx.g_layer_apps[0], document.getElementById("layer0pane"), 0, mod); + g_ctx.g_layer_apps[1].stage.removeChildren() + g_ctx.g_layers[1] = new LayerContext(g_ctx.g_layer_apps[1], document.getElementById("layer1pane"), 1, mod); + g_ctx.g_layer_apps[2].stage.removeChildren() + g_ctx.g_layers[2] = new LayerContext(g_ctx.g_layer_apps[2], document.getElementById("layer2pane"), 2, mod); + g_ctx.g_layer_apps[3].stage.removeChildren() + g_ctx.g_layers[3] = new LayerContext(g_ctx.g_layer_apps[3], document.getElementById("layer3pane"), 3, mod); + + loadAnimatedSpritesFromModule(mod); +} + +function loadMapFromModule(mod) { + g_ctx.tilesetpath = mod.tilesetpath; + initTilesSync(loadMapFromModuleFinish.bind(null, mod)); + initTiles(); +} + +function downloadpng(filename) { + let newcontainer = new PIXI.Container(); + let children = [...g_ctx.composite.container.children]; + for(let i = 0; i < children.length; i++) { + let child = children[i]; + if (! child.hasOwnProperty('isSprite') || !child.isSprite){ + console.log(child); + continue; + } + // console.log(child, typeof child); + g_ctx.composite.container.removeChild(child); + newcontainer.addChild(child); + } + + const { renderer } = g_ctx.composite_app; + renderer.plugins.extract.canvas(newcontainer).toBlob(function (b) { + + console.log(b); + var a = document.createElement("a"); + document.body.append(a); + a.download = filename; + a.href = URL.createObjectURL(b); + a.click(); + a.remove(); + }, "image/png"); + } + +window.saveCompositeAsImage = () => { + downloadpng("g_ctx.composite.png"); +} + +window.onTab = (evt, tabName) => { + // Declare all variables + var i, tabcontent, tablinks; + + // Get all elements with class="tabcontent" and hide them + tabcontent = document.getElementsByClassName("tabcontent"); + for (i = 0; i < tabcontent.length; i++) { + tabcontent[i].style.display = "none"; + } + + // Get all elements with class="tablinks" and remove the class "active" + tablinks = document.getElementsByClassName("tablinks"); + for (i = 0; i < tablinks.length; i++) { + tablinks[i].className = tablinks[i].className.replace(" active", ""); + } + + // Show the current tab, and add an "active" class to the button that opened the tab + document.getElementById(tabName).style.display = "block"; + evt.currentTarget.className += " active"; + + if (tabName == "map"){ + g_ctx.map_app.stage.addChild(g_ctx.composite.container); + }else { + g_ctx.composite.app.stage.addChild(g_ctx.composite.container); + } +} + +// fill base level with currentIndex tile +window.fill0 = () => { + UNDO.undo_mark_task_start(g_ctx.g_layers[0]); + for(let i = 0; i < CONFIG.levelwidth / g_ctx.tiledimx; i++){ + for(let j = 0; j < CONFIG.levelheight / g_ctx.tiledimx; j++){ + let ti = g_ctx.g_layers[0].addTileLevelCoords(i,j,g_ctx.tiledimx, g_ctx.tile_index); + UNDO.undo_add_index_to_task(ti); + } + } + UNDO.undo_mark_task_end(); +} + +window.addEventListener( + "keyup", (event) => { + if (event.code == "KeyD"){ + g_ctx.dkey = false; + g_ctx.g_layers.map( (l) => l.container.addChild(l.mouseshadow)); + g_ctx.composite.container.addChild(g_ctx.composite.mouseshadow); + } + }); +window.addEventListener( + "keydown", (event) => { + + if (event.code == "KeyD"){ + g_ctx.dkey = true; + g_ctx.g_layers.map((l) => l.container.removeChild(l.mouseshadow) ); + g_ctx.composite.container.removeChild(g_ctx.composite.mouseshadow); + } + + if (event.code == 'KeyF'){ + window.fill0(); + } + else if (event.code == 'KeyS'){ + MAPFILE.generate_level_file(); + } + else if (event.code == 'Escape'){ + g_ctx.selected_tiles = []; + g_ctx.g_layers.map((l) => l.mouseshadow.removeChildren()); + g_ctx.composite.mouseshadow.removeChildren(); + } + else if (event.code == 'KeyM'){ + g_ctx.g_layers.map((l) => l.drawFilter () ); + }else if (event.code == 'KeyP'){ + setGridSize((g_ctx.tiledimx == 16)?32:16); + } + else if (event.code == 'KeyG'){ + g_ctx.g_layers.map((l) => redrawGrid (l, false) ); + redrawGrid(g_ctx.tileset, false); + redrawGrid(g_ctx.composite, false); + } + else if (event.ctrlKey && event.code === 'KeyZ'){ + let undome = UNDO.undo_pop(); + if (!undome) { + return; + } + let layer = undome.shift(); + for(let i = 0; i < undome.length; i++) { + if (g_ctx.debug_flag) { + console.log("Undo removing ", undome[i]) + } + layer.container.removeChild(layer.sprites[undome[i]]); + g_ctx.composite.container.removeChild(layer.composite_sprites[undome[i]]); + } + } + else if (event.shiftKey && event.code == 'ArrowUp') { + g_ctx.tileset.fudgey -= 1; + redrawGrid(g_ctx.tileset, true); + } + else if (event.shiftKey && event.code == 'ArrowDown') { + g_ctx.tileset.fudgey += 1; + redrawGrid(g_ctx.tileset, true); + } + else if (event.shiftKey && event.code == 'ArrowLeft') { + g_ctx.tileset.fudgex -= 1; + redrawGrid(g_ctx.tileset, true); + } + else if (event.shiftKey && event.code == 'ArrowRight') { + g_ctx.tileset.fudgex += 1; + redrawGrid(g_ctx.tileset, true); + } + } + ); + +// Listen to pointermove on stage once handle is pressed. + +function onTilesetDragStart(e) +{ + if (g_ctx.debug_flag) { + console.log("onDragStartTileset()"); + } + g_ctx.tileset.app.stage.eventMode = 'static'; + g_ctx.tileset.app.stage.addEventListener('pointermove', onTilesetDrag); + + g_ctx.tileset.dragctx.startx = e.data.global.x; + g_ctx.tileset.dragctx.starty = e.data.global.y; + g_ctx.tileset.dragctx.endx = e.data.global.x; + g_ctx.tileset.dragctx.endy = e.data.global.y; + + g_ctx.tileset.app.stage.addChild(g_ctx.tileset.dragctx.square); + // g_ctx.tileset.app.stage.addChild(g_ctx.tileset.dragctx.tooltip); + + g_ctx.selected_tiles = []; +} + +// Stop dragging feedback once the handle is released. +function onTilesetDragEnd(e) +{ + if (g_ctx.debug_flag) { + console.log("onDragEndTileset()"); + } + + g_ctx.tileset.app.stage.eventMode = 'auto'; + g_ctx.tileset.app.stage.removeEventListener('pointermove', onTilesetDrag); + g_ctx.tileset.app.stage.removeChild(g_ctx.tileset.dragctx.square); + g_ctx.tileset.app.stage.removeChild(g_ctx.tileset.dragctx.tooltip); + + + if(g_ctx.tileset.dragctx.endx < g_ctx.tileset.dragctx.startx){ + let tmp = g_ctx.tileset.dragctx.endx; + g_ctx.tileset.dragctx.endx = g_ctx.tileset.dragctx.startx; + g_ctx.tileset.dragctx.startx = tmp; + } + if(g_ctx.tileset.dragctx.endy < g_ctx.tileset.dragctx.starty){ + let tmp = g_ctx.tileset.dragctx.endy; + g_ctx.tileset.dragctx.endy = g_ctx.tileset.dragctx.starty; + g_ctx.tileset.dragctx.starty = tmp; + } + + let starttilex = Math.floor(g_ctx.tileset.dragctx.startx / g_ctx.tiledimx); + let starttiley = Math.floor(g_ctx.tileset.dragctx.starty / g_ctx.tiledimx); + let endtilex = Math.floor(g_ctx.tileset.dragctx.endx / g_ctx.tiledimx); + let endtiley = Math.floor(g_ctx.tileset.dragctx.endy / g_ctx.tiledimx); + + if (g_ctx.debug_flag) { + console.log("sx sy ex ey ", starttilex, ",", starttiley, ",", endtilex, ",", endtiley); + } + // let mouse clicked handle if there isn't a multiple tile square + if(starttilex === endtilex && starttiley === endtiley ){ + return; + } + +// g_ctx.tile_index = (starttiley * g_ctx.tilesettilew) + starttilex; + + g_ctx.tile_index = tileset_index_from_px(e.global.x, e.global.y); + + let origx = starttilex; + let origy = starttiley; + for(let y = starttiley; y <= endtiley; y++){ + for(let x = starttilex; x <= endtilex; x++){ + let squareindex = (y * g_ctx.tilesettilew) + x; + g_ctx.selected_tiles.push([x - origx,y - origy,squareindex]); + } + } + g_ctx.tileset.dragctx.square.clear(); + // g_ctx.tileset.dragctx.tooltip.clear(); +} + +function onTilesetDrag(e) +{ + if (g_ctx.debug_flag) { + console.log("onDragTileset()"); + } + g_ctx.tileset.dragctx.endx = e.global.x; + g_ctx.tileset.dragctx.endy = e.global.y; + + g_ctx.tileset.dragctx.square.clear(); + g_ctx.tileset.dragctx.square.beginFill(0xFF3300, 0.3); + g_ctx.tileset.dragctx.square.lineStyle(2, 0xffd900, 1); + g_ctx.tileset.dragctx.square.moveTo(g_ctx.tileset.dragctx.startx, g_ctx.tileset.dragctx.starty); + g_ctx.tileset.dragctx.square.lineTo(g_ctx.tileset.dragctx.endx, g_ctx.tileset.dragctx.starty); + g_ctx.tileset.dragctx.square.lineTo(g_ctx.tileset.dragctx.endx, g_ctx.tileset.dragctx.endy); + g_ctx.tileset.dragctx.square.lineTo(g_ctx.tileset.dragctx.startx, g_ctx.tileset.dragctx.endy); + g_ctx.tileset.dragctx.square.closePath(); + g_ctx.tileset.dragctx.square.endFill(); + + + // g_ctx.tileset.dragctx.tooltip.clear(); + // g_ctx.tileset.dragctx.tooltip.beginFill(0xFF3300, 0.3); + // g_ctx.tileset.dragctx.tooltip.lineStyle(2, 0xffd900, 1); + // g_ctx.tileset.dragctx.tooltip.drawRect(e.global.x, e.global.y, 20,8); + // g_ctx.tileset.dragctx.tooltip.endFill(); +} + +//g_ctx.tileset.app.stage.addChild(g_ctx.tileset.container); + +function redrawGrid(pane, redraw = false) { + + if (typeof pane.gridtoggle == 'undefined') { + // first time we're being called, initialized + pane.gridtoggle = false; + pane.gridvisible = false; + redraw = true; + pane.gridvisible = true; + } + + if (redraw) { + if (typeof pane.gridgraphics != 'undefined') { + pane.container.removeChild(pane.gridgraphics); + } + + pane.gridgraphics = new PIXI.Graphics(); + let gridsizex = g_ctx.tiledimx; + let gridsizey = g_ctx.tiledimy; + pane.gridgraphics.lineStyle(1, 0x000000, 1); + + + let index = 0; + for (let i = 0; i < pane.widthpx; i += gridsizex) { + pane.gridgraphics.moveTo(i + pane.fudgex, 0 + pane.fudgey); + pane.gridgraphics.lineTo(i + pane.fudgex, pane.heightpx + pane.fudgey); + pane.gridgraphics.moveTo(i + gridsizex + pane.fudgex, 0 + pane.fudgey); + pane.gridgraphics.lineTo(i + gridsizex + pane.fudgex, pane.heightpx + pane.fudgey); + + } + for (let j = 0; j < pane.heightpx; j += gridsizey) { + pane.gridgraphics.moveTo(0 + pane.fudgex, j + gridsizey + pane.fudgey); + pane.gridgraphics.lineTo(pane.widthpx + pane.fudgex, j + gridsizey + pane.fudgey); + pane.gridgraphics.moveTo(0 + pane.fudgex, j + pane.fudgey); + pane.gridgraphics.lineTo(pane.heightpx + pane.fudgex, j + pane.fudgey); + } + + if(pane.gridvisible){ + pane.container.addChild(pane.gridgraphics); + } + return; + } + + if (pane.gridtoggle) { + pane.container.addChild(pane.gridgraphics); + pane.gridvisible = true; + }else{ + pane.container.removeChild(pane.gridgraphics); + pane.gridvisible = false; + } + + pane.gridtoggle = !pane.gridtoggle; +} + + +// -- +// Variable placement logic Level1 +// -- + +function centerCompositePane(x, y){ + var compositepane = document.getElementById("compositepane"); + compositepane.scrollLeft = x - (CONFIG.htmlCompositePaneW/2); + compositepane.scrollTop = y - (CONFIG.htmlCompositePaneH/2); +} + +function centerLayerPanes(x, y){ + // TODO remove magic number pulled from index.html + g_ctx.g_layers.map((l) => { + l.scrollpane.scrollLeft = x - (CONFIG.htmlLayerPaneW/2); + l.scrollpane.scrollTop = y - (CONFIG.htmlLayerPaneH/2); + }); +} + +function onLevelMouseover(e) { + let x = e.data.global.x; + let y = e.data.global.y; + if(g_ctx.debug_flag2){ + console.log("onLevelMouseOver ",this.num); + } + if (x < this.scrollpane.scrollLeft || x > this.scrollpane.scrollLeft + CONFIG.htmlCompositePaneW) { + return; + } + if (y < this.scrollpane.scrollTop || y > this.scrollpane.scrollTop + CONFIG.htmlCompositePaneH) { + return; + } + + // FIXME test code + if ( g_ctx.spritesheet != null){ + let ctile = new PIXI.AnimatedSprite(g_ctx.spritesheet.animations['row0']); + let ctile2 = new PIXI.AnimatedSprite(g_ctx.spritesheet.animations['row0']); + ctile.animationSpeed = .1; + ctile2.animationSpeed = .1; + ctile.autoUpdate = true; + ctile2.autoUpdate = true; + ctile.alpha = .5; + ctile2.alpha = .5; + ctile.play(); + ctile2.play(); + + this.mouseshadow.addChild(ctile); + g_ctx.composite.mouseshadow.addChild(ctile2); + // FIXME test code + } + else if (this.lasttileindex != g_ctx.tile_index) { + this.mouseshadow.removeChildren(0); + g_ctx.composite.mouseshadow.removeChildren(0); + if (g_ctx.selected_tiles.length == 0) { + let shadowsprite = null; + let shadowsprite2 = null; + + let pxloc = tileset_px_from_index(g_ctx.tile_index); + + shadowsprite = sprite_from_px(pxloc[0] + g_ctx.tileset.fudgex, pxloc[1] + g_ctx.tileset.fudgey); + shadowsprite2 = sprite_from_px(pxloc[0] + g_ctx.tileset.fudgex, pxloc[1] + g_ctx.tileset.fudgey); + + shadowsprite.alpha = .5; + shadowsprite2.alpha = .5; + this.mouseshadow.addChild(shadowsprite); + g_ctx.composite.mouseshadow.addChild(shadowsprite2); + } else { + // TODO! adjust for fudge + for (let i = 0; i < g_ctx.selected_tiles.length; i++) { + let tile = g_ctx.selected_tiles[i]; + let pxloc = tileset_px_from_index(tile[2]); + + const shadowsprite = sprite_from_px(pxloc[0] + g_ctx.tileset.fudgex, pxloc[1] + g_ctx.tileset.fudgey); + const shadowsprite2 = sprite_from_px(pxloc[0] + g_ctx.tileset.fudgex, pxloc[1] + g_ctx.tileset.fudgey); + shadowsprite.x = tile[0] * g_ctx.tiledimx; + shadowsprite.y = tile[1] * g_ctx.tiledimx; + shadowsprite2.x = tile[0] * g_ctx.tiledimx; + shadowsprite2.y = tile[1] * g_ctx.tiledimx; + shadowsprite.alpha = .5; + shadowsprite2.alpha = .5; + this.mouseshadow.addChild(shadowsprite); + g_ctx.composite.mouseshadow.addChild(shadowsprite2); + } + + } + this.mouseshadow.x = x - 16; + this.mouseshadow.y = y - 16; + this.container.removeChild(this.mouseshadow); + g_ctx.composite.container.removeChild(g_ctx.composite.mouseshadow); + this.container.addChild(this.mouseshadow); + g_ctx.composite.container.addChild(g_ctx.composite.mouseshadow); + } + + g_ctx.composite.app.stage.removeChild(g_ctx.composite.circle); + g_ctx.composite.app.stage.addChild(g_ctx.composite.circle); +} + + +function onLevelMouseOut(e) { + if (g_ctx.debug_flag2) { + console.log("onLevelMouseOut ",this.num); + } + + //FIXME there is a funky race condition where the mouse enters a second layer before leaving the last and the following line + //deletes the composite mouseshadow. I'm not quite sure how to solve without mapping the composite.mouseshadow to each layer + + this.mouseshadow.removeChildren(0); + g_ctx.composite.mouseshadow.removeChildren(); +} + +function onLevelMousemove(e) { + let x = e.data.global.x; + let y = e.data.global.y; + + // FIXME TEST CODE + this.mouseshadow.x = x-8; + this.mouseshadow.y = y-8; + g_ctx.composite.mouseshadow.x = x-8; + g_ctx.composite.mouseshadow.y = y-8; + // FIXME TEST CODE + + + if (x < this.scrollpane.scrollLeft || x > this.scrollpane.scrollLeft + CONFIG.htmlCompositePaneW) { + return; + } + if (y < this.scrollpane.scrollTop || y > this.scrollpane.scrollTop + CONFIG.htmlCompositePaneH) { + return; + } + + g_ctx.composite.circle.clear(); + g_ctx.composite.circle.beginFill(0xe50000, 0.5); + g_ctx.composite.circle.drawCircle(e.data.global.x, e.data.global.y, 3); + g_ctx.composite.circle.endFill(); +} +function onCompositeMousedown(layer, e) { + if (g_ctx.debug_flag) { + console.log('onCompositeMouseDown: X', e.data.global.x, 'Y', e.data.global.y); + } + + let xorig = e.data.global.x; + let yorig = e.data.global.y; + + centerLayerPanes(xorig,yorig); +} + + +// Place with no variable target at destination +function levelPlaceNoVariable(layer, e) { + if (g_ctx.debug_flag) { + console.log('levelPlaceNoVariable: X', e.data.global.x, 'Y', e.data.global.y); + } + + let xorig = e.data.global.x; + let yorig = e.data.global.y; + + centerCompositePane(xorig,yorig); + + if (g_ctx.dkey || g_ctx.selected_tiles.length == 0) { + let ti = layer.addTileLevelPx(e.data.global.x, e.data.global.y, g_ctx.tile_index); + UNDO.undo_add_single_index_as_task(layer, ti); + } else { + let undolist = []; + UNDO.undo_mark_task_start(layer); + for (let index of g_ctx.selected_tiles) { + let ti = layer.addTileLevelPx(xorig + index[0] * g_ctx.tiledimx, yorig + index[1] * g_ctx.tiledimx, index[2]); + UNDO.undo_add_index_to_task(ti); + } + UNDO.undo_mark_task_end(); + } +} + +// Listen to pointermove on stage once handle is pressed. +function onLevelPointerDown(layer, e) +{ + if (g_ctx.debug_flag) { + console.log("onLevelPointerDown()"); + } + layer.app.stage.eventMode = 'static'; + layer.app.stage.addEventListener('pointermove', onLevelDrag.bind(null, layer, e)); + + layer.container.removeChild(layer.mouseshadow); + g_ctx.composite.container.removeChild(g_ctx.composite.mouseshadow); + + layer.dragctx.startx = e.data.global.x; + layer.dragctx.starty = e.data.global.y; + layer.dragctx.endx = e.data.global.x; + layer.dragctx.endy = e.data.global.y; + + layer.app.stage.addChild(layer.dragctx.square); + layer.app.stage.addChild(layer.dragctx.tooltip); +} + +function onLevelDrag(layer, e) +{ + if(layer.dragctx.startx == -1){ + layer.dragctx.square.clear(); + return; + } + + layer.dragctx.endx = e.global.x; + layer.dragctx.endy = e.global.y; + + if (g_ctx.debug_flag) { + console.log("onLevelDrag()"); + } + + layer.dragctx.square.clear(); + layer.dragctx.square.beginFill(0xFF3300, 0.3); + layer.dragctx.square.lineStyle(2, 0xffd900, 1); + layer.dragctx.square.moveTo(layer.dragctx.startx, layer.dragctx.starty); + layer.dragctx.square.lineTo(layer.dragctx.endx, layer.dragctx.starty); + layer.dragctx.square.lineTo(layer.dragctx.endx, layer.dragctx.endy); + layer.dragctx.square.lineTo(layer.dragctx.startx, layer.dragctx.endy); + layer.dragctx.square.closePath(); + layer.dragctx.square.endFill(); + + const vwidth = Math.floor((layer.dragctx.endx - layer.dragctx.startx)/g_ctx.tiledimx); + const vheight = Math.floor((layer.dragctx.endy - layer.dragctx.starty)/g_ctx.tiledimx); + layer.dragctx.tooltip.x = e.global.x + 16; + layer.dragctx.tooltip.y = e.global.y - 4; + layer.dragctx.tooltip.text = "["+vwidth+","+vheight+"]\n"+ + "("+Math.floor(e.global.x/g_ctx.tiledimx)+","+Math.floor(e.global.y/g_ctx.tiledimx)+")"; + //layer.dragctx.tooltip.text = "("+e.global.x+","+e.global.y+")"; +} + +// Stop dragging feedback once the handle is released. +function onLevelDragEnd(layer, e) +{ + layer.dragctx.endx = e.data.global.x; + layer.dragctx.endy = e.data.global.y; + + if(layer.dragctx.startx == -1){ + console.log("onLevelDragEnd() start is -1 bailing"); + return; + } + if (g_ctx.debug_flag) { + console.log("onLevelDragEnd()"); + } + + if(layer.dragctx.endx < layer.dragctx.startx){ + let tmp = layer.dragctx.endx; + layer.dragctx.endx = layer.dragctx.startx; + layer.dragctx.startx = tmp; + } + if(layer.dragctx.endy < layer.dragctx.starty){ + let tmp = layer.dragctx.endy; + layer.dragctx.endy = layer.dragctx.starty; + layer.dragctx.starty = tmp; + } + + //FIXME TEST CODE show mouseshadow again once done draggin + layer.container.addChild(layer.mouseshadow); + g_ctx.composite.container.addChild(g_ctx.composite.mouseshadow); + + layer.app.stage.eventMode = 'auto'; + layer.app.stage.removeChild(layer.dragctx.square); + layer.app.stage.removeChild(layer.dragctx.tooltip); + + let starttilex = Math.floor(layer.dragctx.startx / g_ctx.tiledimx); + let starttiley = Math.floor(layer.dragctx.starty / g_ctx.tiledimx); + let endtilex = Math.floor(layer.dragctx.endx / g_ctx.tiledimx); + let endtiley = Math.floor(layer.dragctx.endy / g_ctx.tiledimx); + + if (g_ctx.debug_flag) { + console.log("sx ", starttilex, " ex ", endtilex); + console.log("sy ", starttiley, " ey ", endtiley); + } + + // no variable placement. + if(starttilex === endtilex && starttiley == endtiley ){ + levelPlaceNoVariable(layer, e); + layer.dragctx.startx = -1; + layer.dragctx.endx = -1; + layer.dragctx.starty = -1; + layer.dragctx.endy = -1; + return; + } + + if (g_ctx.selected_tiles.length == 0) { + UNDO.undo_mark_task_start(layer); + for (let i = starttilex; i <= endtilex; i++) { + for (let j = starttiley; j <= endtiley; j++) { + let squareindex = (j * g_ctx.tilesettilew) + i; + let ti = layer.addTileLevelPx(i * g_ctx.tiledimx, j * g_ctx.tiledimx, g_ctx.tile_index); + UNDO.undo_add_index_to_task(ti); + } + } + UNDO.undo_mark_task_end(); + } else { + // figure out selected grid + let selected_grid = Array.from(Array(64), () => new Array(64)); // FIXME ... hope 64x64 is enough + let row = 0; + let column = 0; + let selected_row = g_ctx.selected_tiles[0][1]; + // selected_grid[0] = []; + for (let index of g_ctx.selected_tiles) { + // console.log("Selected row ", selected_row, index); + if(index[1] != selected_row){ + selected_row = index[1]; + row++; + column = 0; + //selected_grid[row] = []; + } + selected_grid[column++][row] = index; + } + // at this point should have a 3D array of the selected tiles and the size should be row, column + + UNDO.undo_mark_task_start(layer); + + let ti=0; + for (let i = starttilex; i <= endtilex; i++) { + for (let j = starttiley; j <= endtiley; j++) { + let squareindex = (j * g_ctx.tilesettilew) + i; + if (j === starttiley) { // first row + if (i === starttilex) { // top left corner + ti = layer.addTileLevelPx(i * g_ctx.tiledimx, j * g_ctx.tiledimx, selected_grid[0][0][2]); + } + else if (i == endtilex) { // top right corner + ti = layer.addTileLevelPx(i * g_ctx.tiledimx, j * g_ctx.tiledimx, selected_grid[column - 1][0][2]); + } else { // top middle + ti = layer.addTileLevelPx(i * g_ctx.tiledimx, j * g_ctx.tiledimx, selected_grid[1][0][2]); + } + } else if (j === endtiley) { // last row + if (i === starttilex) { // bottom left corner + ti = layer.addTileLevelPx(i * g_ctx.tiledimx, j * g_ctx.tiledimx, selected_grid[0][row][2]); + } + else if (i == endtilex) { // bottom right corner + ti = layer.addTileLevelPx(i * g_ctx.tiledimx, j * g_ctx.tiledimx, selected_grid[column - 1][row][2]); + } else { // bottom middle + ti = layer.addTileLevelPx(i * g_ctx.tiledimx, j * g_ctx.tiledimx, selected_grid[1][row][2]); + } + } else { // middle row + if (i === starttilex) { // middle left + ti = layer.addTileLevelPx(i * g_ctx.tiledimx, j * g_ctx.tiledimx, selected_grid[0][(row > 0)? 1 : 0][2]); + } + else if (i === endtilex) { // middle end + ti = layer.addTileLevelPx(i * g_ctx.tiledimx, j * g_ctx.tiledimx, selected_grid[column - 1][(row > 0)? 1 : 0][2]); + } else { // middle middle + ti = layer.addTileLevelPx(i * g_ctx.tiledimx, j * g_ctx.tiledimx, selected_grid[1][(row > 0)? 1 : 0][2]); + } + } + UNDO.undo_add_index_to_task(ti); + } + } + UNDO.undo_mark_task_end(); + } + + layer.dragctx.square.clear(); + + layer.dragctx.startx = -1; + layer.dragctx.starty = -1; +} + + + +// -- +// Initialized all pixi apps / components for application +// -- +function initPixiApps() { + + // -- Editor wide globals -- + + // First layer of level + const level_app0 = new PIXI.Application({ backgroundColor: 0x2980b9, width: CONFIG.levelwidth, height: CONFIG.levelheight, view: document.getElementById('level0') }); + let layer0 = new LayerContext(level_app0, document.getElementById("layer0pane"), 0); + + // second layer of level + const level_app1 = new PIXI.Application({ backgroundColor: 0x2980b9, width: CONFIG.levelwidth, height: CONFIG.levelheight, view: document.getElementById('level1') }); + let layer1 = new LayerContext(level_app1, document.getElementById("layer1pane"), 1); + + // object layer of level + const level_app2 = new PIXI.Application({ backgroundColor: 0x2980b9, width: CONFIG.levelwidth, height: CONFIG.levelheight, view: document.getElementById('level3') }); + let layer2 = new LayerContext(level_app2, document.getElementById("layer2pane"), 2); + + // object layer of level + const level_app3 = new PIXI.Application({ backgroundColor: 0x2980b9, width: CONFIG.levelwidth, height: CONFIG.levelheight, view: document.getElementById('level4') }); + + let layer3 = new LayerContext(level_app3, document.getElementById("layer3pane"), 3); + + g_ctx.g_layer_apps = []; + g_ctx.g_layer_apps.push(level_app0 ); + g_ctx.g_layer_apps.push(level_app1); + g_ctx.g_layer_apps.push(level_app2); + g_ctx.g_layer_apps.push(level_app3); + + + g_ctx.g_layers = []; + g_ctx.g_layers.push(layer0); + g_ctx.g_layers.push(layer1); + g_ctx.g_layers.push(layer2); + g_ctx.g_layers.push(layer3); + + // g_ctx.composite view + g_ctx.composite_app = new PIXI.Application({ backgroundAlpha: 0, width: CONFIG.levelwidth, height: CONFIG.levelheight, view: document.getElementById('composite') }); + g_ctx.composite = new CompositeContext(g_ctx.composite_app); + + // map tab + g_ctx.map_app = new PIXI.Application({ backgroundColor: 0x2980b9, width: CONFIG.levelwidth, height: CONFIG.levelheight, view: document.getElementById('mapcanvas') }); + + // g_ctx.tileset + g_ctx.tileset_app = new PIXI.Application({ width: 5632 , height: 8672, view: document.getElementById('tileset') }); + //g_ctx.tileset_app = new PIXI.Application({ width: g_ctx.tilesetpxw, height: g_ctx.tilesetpxh, view: document.getElementById('tileset') }); + // const { renderer } = g_ctx.tileset_app; + // // Install the EventSystem + // renderer.addSystem(EventSystem, 'tileevents'); + g_ctx.tileset = new TilesetContext(g_ctx.tileset_app); +} + +function setGridSize(size) { + if (size == 16) { + if (g_ctx.tiledimx == 16) { return; } + g_ctx.tilesettilew = (g_ctx.tilesettilew/ (size / g_ctx.tiledimx)); + g_ctx.tilesettileh = (g_ctx.tilesettileh / (size / g_ctx.tiledimy)); + g_ctx.tiledimx = 16; + g_ctx.tiledimy = 16; + g_ctx.curtiles = g_ctx.tiles16; + console.log("set to curTiles16"); + } else if (size == 32) { + if (g_ctx.tiledimx == 32) { return; } + g_ctx.tilesettilew = (g_ctx.tilesettilew/ (size / g_ctx.tiledimx)); + g_ctx.tilesettileh = (g_ctx.tilesettileh / (size / g_ctx.tiledimy)); + g_ctx.tiledimx = 32; + g_ctx.tiledimy = 32; + g_ctx.curtiles = g_ctx.tiles32; + console.log("set to curTiles32"); + } else { + console.debug("Invalid TileDim!"); + return; + } + g_ctx.g_layers.map((l) => redrawGrid (l, true) ); + redrawGrid(g_ctx.tileset, true); + redrawGrid(g_ctx.composite, true); +} + +function initRadios() { + var rad = document.myForm.radioTiledim; + var prev = null; + for (var i = 0; i < rad.length; i++) { + rad[i].addEventListener('change', function () { + if (this !== prev) { + prev = this; + } + setGridSize(this.value); + }); + } +} + +// -- +// Load in default tileset and use to set properties +// -- + +function initTilesSync(callme) { + return new Promise((resolve, reject) => { + + console.log("initTileSync"); + const texture = new PIXI.BaseTexture(g_ctx.tilesetpath); + if(texture.valid) { + console.log("BaseTexture already valid"); + callme(); + return; + } + + console.log("Loading texture ", g_ctx.tilesetpath); + texture.on('loaded', function () { + // size of g_ctx.tileset in px + g_ctx.tilesetpxw = texture.width; + g_ctx.tilesetpxh = texture.height; + console.log("Texture size w:", g_ctx.tilesetpxw, "h:", g_ctx.tilesetpxh); + // size of g_ctx.tileset in tiles + let tileandpad = g_ctx.tiledimx + CONFIG.tilesetpadding; + let numtilesandpadw = Math.floor(g_ctx.tilesetpxw / tileandpad); + g_ctx.tilesettilew = numtilesandpadw + Math.floor((g_ctx.tilesetpxw - (numtilesandpadw * tileandpad)) / g_ctx.tiledimx); + let numtilesandpadh = Math.floor(g_ctx.tilesetpxh / tileandpad); + g_ctx.tilesettileh = numtilesandpadh + Math.floor((g_ctx.tilesetpxh - (numtilesandpadh * tileandpad)) / g_ctx.tiledimx); + console.log("Number of x tiles ", g_ctx.tilesettilew, " y tiles ", g_ctx.tilesettileh); + g_ctx.MAXTILEINDEX = g_ctx.tilesettilew * g_ctx.tilesettileh; + + texture.destroy(); + resolve(); + callme(); + }); + + }); +} + +// -- +// Load default Tileset +// -- + +const initTilesConfig = async () => { + + g_ctx.tilesetpath = CONFIG.DEFAULTTILESETPATH; + + return new Promise((resolve, reject) => { + + const texture = new PIXI.BaseTexture(g_ctx.tilesetpath); + if (g_ctx.debug_flag) { + console.log("initTilessConfi: Loading texture ",g_ctx.tilesetpath); + } + texture .on('loaded', function() { + // size of g_ctx.tileset in px + g_ctx.tilesetpxw = texture.width; + g_ctx.tilesetpxh = texture.height; + if (g_ctx.debug_flag) { + console.log("\tsize w:", g_ctx.tilesetpxw, "h:", g_ctx.tilesetpxh); + } + + // size of g_ctx.tileset in tiles + let tileandpad = g_ctx.tiledimx + CONFIG.tilesetpadding; + let numtilesandpadw = Math.floor(g_ctx.tilesetpxw / tileandpad); + g_ctx.tilesettilew = numtilesandpadw + Math.floor((g_ctx.tilesetpxw - (numtilesandpadw * tileandpad))/g_ctx.tiledimx); + let numtilesandpadh = Math.floor(g_ctx.tilesetpxh / tileandpad); + g_ctx.tilesettileh = numtilesandpadh + Math.floor((g_ctx.tilesetpxh - (numtilesandpadh * tileandpad))/g_ctx.tiledimx); + + if (g_ctx.debug_flag) { + console.log("\tnum tiles x ", g_ctx.tilesettilew, " y ", g_ctx.tilesettileh); + } + + g_ctx.MAXTILEINDEX = g_ctx.tilesettilew * g_ctx.tilesettileh; + + texture.destroy(); + resolve(); + }); + + + }); + }; + +function initTiles() { + // load g_ctx.tileset into a global array of textures for blitting onto levels + const bt = PIXI.BaseTexture.from(g_ctx.tilesetpath, { + scaleMode: PIXI.SCALE_MODES.NEAREST, + }); + for (let x = 0; x < CONFIG.tilesettilewidth; x++) { + for (let y = 0; y < CONFIG.tilesettileheight; y++) { + g_ctx.tiles32[x + y * CONFIG.tilesettilewidth] = new PIXI.Texture( + bt, + new PIXI.Rectangle(x * 32, y * 32, 32, 32), + ); + } + } + for (let x = 0; x < CONFIG.tilesettilewidth * 2; x++) { + for (let y = 0; y < CONFIG.tilesettileheight * 2; y++) { + g_ctx.tiles16[x + y * CONFIG.tilesettilewidth * 2] = new PIXI.Texture( + bt, + new PIXI.Rectangle(x * 16, y * 16, 16, 16), + ); + } + } + + g_ctx.curtiles = g_ctx.tiles32; +} + +async function init() { + + UI.initMainHTMLWindow(); + + // We need to load the Tileset to know how to size things. So we block until done. + await initTilesConfig(); + + initPixiApps(); + initRadios(); + initTiles(); + + UI.initLevelLoader(loadMapFromModule); + UI.initCompositePNGLoader(); + UI.initSpriteSheetLoader(); + UI.initTilesetLoader( loadMapFromModule.bind(null, g_ctx)); +} + +init(); \ No newline at end of file diff --git a/patches/src/editor/leconfig.js b/patches/src/editor/leconfig.js new file mode 100644 index 0000000000000000000000000000000000000000..80629ce24e900da445fef9a086bbc5c68bd3e6b9 --- /dev/null +++ b/patches/src/editor/leconfig.js @@ -0,0 +1,41 @@ +export const DEFAULTTILESETPATH = "./tilesets/gentle.png"; +//export const DEFAULTTILESETPATH = "./tilesets/magecity.png"; +//export const DEFAULTTILESETPATH = "./tilesets/forest.png"; +//export const DEFAULTTILESETPATH = "./tilesets/Serene.png"; +//export const DEFAULTTILESETPATH = "./tilesets/gentletreewall.png"; +//export const DEFAULTTILESETPATH = "./tilesets/Modern.png"; +//export const DEFAULTTILESETPATH = "./tilesets/phantasy2.png"; + +export const tilesetpadding = 0; + + +export const DEFAULTILEDIMX = 32; // px +export const DEFAULTILEDIMY = 32; // px + +export const levelwidth = 2048; // px +export const levelheight = 1536; // px + +export let leveltilewidth = Math.floor(levelwidth / DEFAULTILEDIMX); +export let leveltileheight = Math.floor(levelheight / DEFAULTILEDIMX); + +export const MAXTILEINDEX = leveltilewidth * leveltileheight; + + +// -- HTML + +export const htmlLayerPaneW = 800; +export const htmlLayerPaneH = 600 + +export const htmlTilesetPaneW = 800; +export const htmlTilesetPaneH = 600; + +export const htmlCompositePaneW = 800; +export const htmlCompositePaneH = 600; + +// -- zIndex + +// 1-10 taken by layers +export const zIndexFilter = 20; +export const zIndexMouseShadow = 30; +export const zIndexGrid = 50; +export const zIndexCompositePointer = 100; diff --git a/patches/src/editor/lecontext.js b/patches/src/editor/lecontext.js new file mode 100644 index 0000000000000000000000000000000000000000..3574985effc4a9a2668e5039b00afedd590514b7 --- /dev/null +++ b/patches/src/editor/lecontext.js @@ -0,0 +1,40 @@ +import * as PIXI from 'pixi.js' +import * as CONFIG from './leconfig.js' + +var ContextCreate = (function(){ + + function ContextSingleton() { + this.tilesetpxw = 0; + this.tilesetpxh = 0; + this.tilesettilew = 0; + this.tilesettileh = 0; + this.MAXTILEINDEX = 0; + this.tile_index = 0; + this.selected_tiles = []; // current set of selected tiles + this.spritesheet = null; // loaded spritesheet + this.tiledimx = CONFIG.DEFAULTILEDIMX ; // px + this.tiledimy = CONFIG.DEFAULTILEDIMY; // px + this.dimlog = Math.log2(this.tileDim); //log2(TileDim) + this.dkey = false; // is 'd' key depressed? (for delete) + this.tiles32 = []; // all tiles from tilemap (32x32) + this.tiles16 = []; + this.fudgetiles = []; + this.g_layers = []; // level layers + + } + + var instance; + return { + getInstance: function(){ + if (instance == null) { + instance = new ContextSingleton(); + // Hide the constructor so the returned object can't be new'd... + instance.constructor = null; + } + return instance; + } + }; +})(); + +// global shared state between all panes +export let g_ctx = ContextCreate.getInstance(); \ No newline at end of file diff --git a/patches/src/editor/lehtmlui.js b/patches/src/editor/lehtmlui.js new file mode 100644 index 0000000000000000000000000000000000000000..8cda068f55c4f969055320f747670a1fed53e3a6 --- /dev/null +++ b/patches/src/editor/lehtmlui.js @@ -0,0 +1,134 @@ +import * as PIXI from 'pixi.js' +import { g_ctx } from './lecontext.js' // global context +import * as CONFIG from './leconfig.js' + +// -- +// Set sizes and limits for HTML in main UI +// -- + +export function initMainHTMLWindow() { + document.getElementById("layer0pane").style.maxWidth = ""+CONFIG.htmlLayerPaneW+"px"; + document.getElementById("layer0pane").style.maxHeight = ""+CONFIG.htmlLayerPaneH+"px"; + document.getElementById("layer1pane").style.maxWidth = ""+CONFIG.htmlLayerPaneW+"px"; + document.getElementById("layer1pane").style.maxHeight = ""+CONFIG.htmlLayerPaneH+"px"; + document.getElementById("layer2pane").style.maxWidth = ""+CONFIG.htmlLayerPaneW+"px"; + document.getElementById("layer2pane").style.maxHeight = ""+CONFIG.htmlLayerPaneH+"px"; + document.getElementById("layer3pane").style.maxWidth = ""+CONFIG.htmlLayerPaneW+"px"; + document.getElementById("layer3pane").style.maxHeight = ""+CONFIG.htmlLayerPaneH+"px"; + + document.getElementById("tilesetpane").style.maxWidth = ""+CONFIG.htmlTilesetPaneW+"px"; + document.getElementById("tilesetpane").style.maxHeight = ""+CONFIG.htmlTilesetPaneH+"px"; + document.getElementById("compositepane").style.maxWidth = ""+CONFIG.htmlCompositePaneW+"px"; + document.getElementById("compositepane").style.maxHeight = ""+CONFIG.htmlCompositePaneH+"px"; + + // hide map tab + let mappane = document.getElementById("map"); + mappane.style.display = "none"; +} + +// -- +// Initialize handlers for file loading +// -- + + + + + +// -- +// Initialize handlers loading a PNG file into the composite window +// -- + +export function initCompositePNGLoader() { + const fileInput = document.getElementById('compositepng'); + fileInput.onchange = (evt) => { + if (!window.FileReader) return; // Browser is not compatible + if (g_ctx.debug_flag) { + console.log("compositepng ", fileInput.files[0].name); + } + let bgname = fileInput.files[0].name; + + const texture = PIXI.Texture.from("./tilesets/"+bgname); + const bg = new PIXI.Sprite(texture); + bg.zIndex = 0; + g_ctx.composite.container.addChild(bg); + } +} + +// -- +// initailized handler to load a spriteSheet into current working tile +// -- + +export function initSpriteSheetLoader() { + const fileInput = document.getElementById('spritesheet'); + fileInput.onchange = async (evt) => { + if (!window.FileReader) return; // Browser is not compatible + if (g_ctx.debug_flag) { + console.log("spritesheet ", fileInput.files[0].name); + } + let ssname = fileInput.files[0].name; + + let sheet = await PIXI.Assets.load("./"+ssname); + console.log(sheet); + g_ctx.tileset.addTileSheet(ssname, sheet); + g_ctx.selected_tiles = []; + } +} + +// -- +// initailized handler to load a new tileset +// -- + +export function initTilesetLoader(callme) { + const fileInput = document.getElementById('tilesetfile'); + fileInput.onchange = async (evt) => { + if (!window.FileReader) return; // Browser is not compatible + if (g_ctx.debug_flag) { + console.log("tilesetfile ", fileInput.files[0].name); + } + g_ctx.tilesetpath = "./tilesets/"+fileInput.files[0].name; + + callme(); + } +} + + +// -- +// initailized handler to load a level from a file +// -- + +function doimport (str) { + if (globalThis.URL.createObjectURL) { + const blob = new Blob([str], { type: 'text/javascript' }) + const url = URL.createObjectURL(blob) + const module = import(url) + URL.revokeObjectURL(url) // GC objectURLs + return module + } + + const url = "data:text/javascript;base64," + btoa(moduleData) + return import(url) + } + +export function initLevelLoader(callme) { + let filecontent = ""; + + const fileInput = document.getElementById('levelfile'); + fileInput.onchange = (evt) => { + if (!window.FileReader) return; // Browser is not compatible + + var reader = new FileReader(); + + reader.onload = function (evt) { + if (evt.target.readyState != 2) return; + if (evt.target.error) { + alert('Error while reading file'); + return; + } + + filecontent = evt.target.result; + doimport(filecontent).then(mod => callme(mod)); + }; + + reader.readAsText(evt.target.files[0]); + } +} \ No newline at end of file diff --git a/patches/src/editor/mapfile.js b/patches/src/editor/mapfile.js new file mode 100644 index 0000000000000000000000000000000000000000..f86d936c45f3d3978d1d1c354d478920e01c8f98 --- /dev/null +++ b/patches/src/editor/mapfile.js @@ -0,0 +1,246 @@ +import * as CONFIG from './leconfig.js' +import * as UTIL from './eutils.js' +import { g_ctx } from './lecontext.js' // global context + + + +function generate_preamble() { + const mapfile_preamble = '' + + '// Map generated by assettool.js [' + new Date() + ']\n' + + '\n' + + 'export const tilesetpath = "' + g_ctx.tilesetpath + '"\n' + + 'export const tiledim = ' + g_ctx.tiledimx + '\n' + + 'export const screenxtiles = ' + g_ctx.tilesettilew + '\n' + + 'export const screenytiles = ' + g_ctx.tilesettileh + '\n' + + 'export const tilesetpxw = ' + g_ctx.tilesetpxw + '\n' + + 'export const tilesetpxh = ' + g_ctx.tilesetpxh + '\n\n' + + return mapfile_preamble; +} + +const bgtile_string_start = '' + + 'export const bgtiles = [\n' + + ' [\n' + +function write_map_file(bg_tiles_0, bg_tiles_1, obj_tiles_1, obj_tiles_2, animated_tiles){ + let text = generate_preamble(); + text += bgtile_string_start; + + for(let row = 0; row < bg_tiles_0.length; row++) { + text += '[ '; + for(let column = 0; column < bg_tiles_0[row].length; column++) { + text += bg_tiles_0[row][column]; + if (column != bg_tiles_0.length - 1){ + text += ' , '; + } + } + text += '],\n'; + } + text += '],\n'; + text += '[\n'; + for(let row = 0; row < bg_tiles_1.length; row++) { + text += '[ '; + for(let column = 0; column < bg_tiles_1[row].length; column++) { + text += bg_tiles_1[row][column]; + if (column != bg_tiles_1.length - 1){ + text += ' , '; + } + } + text += '],\n'; + } + text += '],];\n\n'; + + text += ''+ + 'export const objmap = [\n'+ + '[\n'; + + for(let row = 0; row < obj_tiles_1.length; row++) { + text += '[ '; + for(let column = 0; column < obj_tiles_1[row].length; column++) { + text += obj_tiles_1[row][column]; + if (column != obj_tiles_1.length - 1){ + text += ' , '; + } + } + text += '],\n'; + } + text += '],\n'; + text += '[\n'; + + for(let row = 0; row < obj_tiles_2.length; row++) { + text += '[ '; + for(let column = 0; column < obj_tiles_2[row].length; column++) { + text += obj_tiles_2[row][column]; + if (column != obj_tiles_2.length - 1){ + text += ' , '; + } + } + text += '],\n'; + } + text += '],];\n'; + + + text += ''+ + 'export const animatedsprites = [\n'; + + for(let x = 0 ; x < animated_tiles.length; x++){ + let atile = animated_tiles[x]; + text += '{ x: '+atile.x+", y: "+ atile.y+ ", w: "+ atile.width+ ", h: "+ atile.height ; + text += ', layer: '+atile.layer; + text += ', sheet: "'+ atile.spritesheetname+ '", animation: "'+ atile.animationname+'" },\n'; + } + + text += '];\n\n'; + text += 'export const mapwidth = bgtiles[0][0].length;\n'; + text += 'export const mapheight = bgtiles[0].length;\n'; + + UTIL.download(text, "map.js", "text/plain"); +} + + +export function generate_level_file() { + let layer0 = g_ctx.g_layers[0]; + let layer1 = g_ctx.g_layers[1]; + let layer2 = g_ctx.g_layers[2]; + let layer3 = g_ctx.g_layers[3]; + + let animated_tiles = []; + + // level0 + var tile_array0 = Array.from(Array(CONFIG.leveltilewidth), () => new Array(CONFIG.leveltileheight)); + for (let x = 0; x < CONFIG.leveltilewidth; x++) { + for (let y = 0; y < CONFIG.leveltileheight; y++) { + tile_array0[x][y] = -1; + } + } + for (var i = 0; i < layer0.container.children.length; i++) { + var child = layer0.container.children[i]; + + // check if it's an animated sprite + if(child.hasOwnProperty('animationSpeed')){ + child.layer = 0; + animated_tiles.push(child); + continue; + } + + if (!child.hasOwnProperty('index')) { + continue; + } + let x_coord = child.x / g_ctx.tiledimx; + let y_coord = child.y / g_ctx.tiledimy; + + if (typeof tile_array0[x_coord] == 'undefined'){ + console.log("**Error xcoord undefined ", x_coord); + + } + else if (typeof tile_array0[x_coord][y_coord] == 'undefined'){ + console.log("**Error xcoord/ycoord undefined ", x_coord, y_coord); + }else{ + tile_array0[x_coord][y_coord] = child.index; + } + } + + // level1 + var tile_array1 = Array.from(Array(CONFIG.leveltilewidth), () => new Array(CONFIG.leveltileheight)); + for (let x = 0; x < CONFIG.leveltilewidth; x++) { + for (let y = 0; y < CONFIG.leveltileheight; y++) { + tile_array1[x][y] = -1; + } + } + for (var i = 0; i < layer1.container.children.length; i++) { + var child = layer1.container.children[i]; + + // check if it's an animated sprite + if(child.hasOwnProperty('animationSpeed')){ + child.layer = 1; + animated_tiles.push(child); + continue; + } + + if (!child.hasOwnProperty('index')) { + continue; + } + let x_coord = child.x / g_ctx.tiledimx; + let y_coord = child.y / g_ctx.tiledimy; + if (typeof tile_array1[x_coord] == 'undefined'){ + console.log("**Error xcoord undefined ", x_coord); + + } + else if (typeof tile_array1[x_coord][y_coord] == 'undefined'){ + console.log("**Error xcoord/ycoord undefined ", x_coord, y_coord); + }else{ + tile_array1[x_coord][y_coord] = child.index; + } + } + + // object level + var tile_array2 = Array.from(Array(CONFIG.leveltilewidth), () => new Array(CONFIG.leveltileheight)); + for (let x = 0; x < CONFIG.leveltilewidth; x++) { + for (let y = 0; y < CONFIG.leveltileheight; y++) { + tile_array2[x][y] = -1; + } + } + for (var i = 0; i < layer2.container.children.length; i++) { + var child = layer2.container.children[i]; + + // check if it's an animated sprite + if(child.hasOwnProperty('animationSpeed')){ + child.layer = 2; + animated_tiles.push(child); + continue; + + } + + if (!child.hasOwnProperty('index')) { + continue; + } + let x_coord = child.x / g_ctx.tiledimx; + let y_coord = child.y / g_ctx.tiledimy; + if (typeof tile_array2[x_coord] == 'undefined'){ + console.log("**Error xcoord undefined ", x_coord); + + } + else if (typeof tile_array2[x_coord][y_coord] == 'undefined'){ + console.log("**Error xcoord/ycoord undefined ", x_coord, y_coord); + }else{ + tile_array2[x_coord][y_coord] = child.index; + } + } + + // object level + var tile_array3 = Array.from(Array(CONFIG.leveltilewidth), () => new Array(CONFIG.leveltileheight)); + for (let x = 0; x < CONFIG.leveltilewidth; x++) { + for (let y = 0; y < CONFIG.leveltileheight; y++) { + tile_array3[x][y] = -1; + } + } + for (var i = 0; i < layer3.container.children.length; i++) { + var child = layer3.container.children[i]; + + // check if it's an animated sprite + if(child.hasOwnProperty('animationSpeed')){ + child.layer = 3; + animated_tiles.push(child); + continue; + + } + + if (!child.hasOwnProperty('index')) { + continue; + } + let x_coord = child.x / g_ctx.tiledimx; + let y_coord = child.y / g_ctx.tiledimy; + if (typeof tile_array3[x_coord] == 'undefined'){ + console.log("**Error xcoord undefined ", x_coord); + + } + else if (typeof tile_array3[x_coord][y_coord] == 'undefined'){ + console.log("**Error xcoord/ycoord undefined ", x_coord, y_coord); + }else{ + tile_array3[x_coord][y_coord] = child.index; + } + } + + + write_map_file(tile_array0, tile_array1, tile_array2, tile_array3, animated_tiles); +} \ No newline at end of file diff --git a/patches/src/editor/maps/gentle-full.js b/patches/src/editor/maps/gentle-full.js new file mode 100644 index 0000000000000000000000000000000000000000..2e30f23583b5939224edf70f7c135e63d1f55848 --- /dev/null +++ b/patches/src/editor/maps/gentle-full.js @@ -0,0 +1,327 @@ +// Map generated by assettool.js [Mon Oct 02 2023 00:20:59 GMT-0700 (Pacific Daylight Time)] + +export const tilesetpath = "./tilesets/gentle-obj.png" +export const tiledim = 32 +export const screenxtiles = 45 +export const screenytiles = 32 +export const tilesetpxw = 1440 +export const tilesetpxh = 1024 + +export const bgtiles = [ + [ +[ 732 , 777 , 822 , 867 , 912 , 957 , 912 , 957 , 1002 , 1047 , 912 , 957 , 1002 , 1047 , 912 , 957 , 1002 , 1047 , 1001 , 1046 , 946 , 991 , 1035 , 731 , 776 , 821 , 866 , 911 , 956 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 594 , 639 , 684 , 271 , ], +[ 733 , 778 , 823 , 868 , 913 , 958 , 913 , 958 , 1003 , 1048 , 913 , 958 , 1003 , 1048 , 913 , 958 , 1003 , 1048 , 856 , 901 , 946 , 991 , 1036 , 732 , 777 , 822 , 867 , 912 , 957 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 595 , 640 , 685 , 271 , ], +[ 734 , 779 , 824 , 869 , 914 , 959 , 914 , 959 , 1004 , 1049 , 914 , 959 , 1004 , 1049 , 914 , 959 , 1004 , 1049 , 857 , 902 , 947 , 992 , 1037 , 733 , 778 , 823 , 868 , 913 , 958 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 594 , 639 , 684 , 271 , ], +[ 735 , 780 , 825 , 870 , 915 , 960 , 915 , 960 , 1005 , 1050 , 915 , 960 , 1005 , 1050 , 915 , 960 , 1087 , 1132 , 858 , 903 , 948 , 993 , 1038 , 734 , 779 , 824 , 869 , 914 , 959 , 271 , 271 , 180 , 225 , 225 , 315 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 595 , 640 , 685 , 271 , ], +[ 736 , 781 , 826 , 871 , 916 , 278 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 1087 , 1132 , 859 , 904 , 949 , 994 , 1039 , 735 , 780 , 825 , 870 , 915 , 960 , 271 , 271 , 181 , 226 , 226 , 140 , 270 , 315 , 271 , 271 , 271 , 271 , 271 , 271 , 541 , 586 , 631 , 686 , 271 , ], +[ 737 , 782 , 827 , 872 , 917 , 233 , 271 , 271 , 271 , 271 , 271 , 233 , 271 , 271 , 271 , 271 , 1088 , 1133 , 860 , 905 , 950 , 995 , 1040 , 736 , 781 , 826 , 871 , 916 , 961 , 271 , 271 , 181 , 226 , 278 , 272 , 271 , 316 , 271 , 271 , 271 , 271 , 271 , 551 , 542 , 587 , 632 , 271 , 271 , ], +[ 738 , 783 , 828 , 873 , 271 , 271 , 235 , 962 , 962 , 962 , 962 , 962 , 271 , 271 , 271 , 271 , 1089 , 1134 , 861 , 906 , 951 , 996 , 1041 , 737 , 782 , 827 , 872 , 917 , 962 , 271 , 271 , 181 , 226 , 226 , 272 , 271 , 675 , 271 , 271 , 271 , 271 , 272 , 272 , 642 , 271 , 271 , 271 , 271 , ], +[ 739 , 784 , 829 , 874 , 271 , 271 , 235 , 962 , 962 , 962 , 962 , 962 , 271 , 271 , 271 , 271 , 1090 , 1135 , 862 , 907 , 952 , 997 , 1042 , 738 , 783 , 828 , 873 , 918 , 963 , 271 , 271 , 183 , 228 , 228 , 184 , 271 , 675 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], +[ 736 , 781 , 826 , 871 , 271 , 271 , 962 , 962 , 962 , 962 , 962 , 962 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 962 , 962 , 962 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 182 , 280 , 317 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], +[ 737 , 782 , 827 , 872 , 271 , 271 , 962 , 962 , 962 , 962 , 962 , 962 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 962 , 0 , 135 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 140 , 273 , 318 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], +[ 738 , 783 , 828 , 873 , 271 , 271 , 962 , 962 , 962 , 962 , 962 , 962 , 0 , 45 , 90 , 135 , 271 , 271 , 271 , 271 , 962 , 1 , 136 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], +[ 739 , 784 , 829 , 874 , 271 , 271 , 962 , 962 , 962 , 962 , 962 , 962 , 3 , 48 , 93 , 271 , 271 , 271 , 271 , 271 , 962 , 1 , 136 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], +[ 736 , 781 , 826 , 871 , 271 , 271 , 962 , 962 , 962 , 962 , 962 , 279 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 1 , 136 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], +[ 737 , 782 , 827 , 872 , 271 , 271 , 962 , 962 , 962 , 962 , 143 , 188 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 1 , 136 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], +[ 738 , 783 , 828 , 873 , 271 , 271 , 278 , 962 , 962 , 962 , 144 , 189 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 1 , 136 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], +[ 739 , 784 , 829 , 874 , 271 , 271 , 271 , 271 , 271 , 271 , 145 , 190 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 1 , 136 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], +[ 736 , 781 , 826 , 871 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 1 , 136 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], +[ 737 , 782 , 827 , 872 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 3 , 138 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], +[ 738 , 783 , 828 , 873 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], +[ 739 , 784 , 829 , 874 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 360 , 405 , 405 , 405 , 450 , 495 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], +[ 1270 , 1315 , 1360 , 1405 , 226 , 360 , 405 , 450 , 495 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 361 , 409 , 451 , 451 , 451 , 496 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], +[ 1271 , 1316 , 226 , 226 , 226 , 361 , 409 , 409 , 496 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 361 , 407 , 452 , 452 , 452 , 496 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], +[ 271 , 271 , 271 , 271 , 450 , 362 , 406 , 407 , 451 , 451 , 271 , 271 , 271 , 405 , 405 , 450 , 320 , 409 , 451 , 452 , 452 , 497 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], +[ 405 , 450 , 405 , 450 , 450 , 320 , 406 , 406 , 451 , 589 , 451 , 451 , 451 , 406 , 451 , 409 , 451 , 409 , 451 , 452 , 406 , 275 , 405 , 450 , 405 , 495 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], +[ 406 , 406 , 406 , 451 , 451 , 407 , 406 , 407 , 452 , 451 , 451 , 451 , 451 , 407 , 589 , 452 , 452 , 451 , 406 , 451 , 406 , 406 , 407 , 451 , 451 , 275 , 405 , 450 , 495 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], +[ 408 , 453 , 408 , 453 , 319 , 406 , 407 , 406 , 451 , 451 , 451 , 589 , 451 , 634 , 452 , 589 , 406 , 451 , 634 , 452 , 274 , 408 , 408 , 319 , 451 , 451 , 451 , 451 , 275 , 405 , 405 , 450 , 405 , 495 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], +[ 271 , 271 , 271 , 271 , 361 , 634 , 452 , 407 , 634 , 451 , 451 , 451 , 451 , 407 , 452 , 452 , 407 , 452 , 452 , 274 , 498 , 271 , 271 , 363 , 408 , 453 , 408 , 453 , 408 , 453 , 319 , 451 , 451 , 275 , 405 , 450 , 405 , 450 , 405 , 450 , 495 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], +[ 271 , 271 , 271 , 271 , 362 , 409 , 451 , 451 , 451 , 451 , 271 , 271 , 271 , 408 , 408 , 319 , 407 , 452 , 409 , 496 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 361 , 406 , 451 , 406 , 451 , 451 , 451 , 451 , 544 , 451 , 496 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], +[ 271 , 271 , 271 , 271 , 362 , 407 , 409 , 451 , 451 , 271 , 271 , 271 , 271 , 271 , 271 , 362 , 409 , 409 , 452 , 497 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 362 , 407 , 544 , 407 , 544 , 589 , 452 , 452 , 634 , 452 , 497 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], +[ 271 , 271 , 271 , 271 , 363 , 408 , 453 , 498 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 363 , 408 , 408 , 453 , 498 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 363 , 408 , 319 , 407 , 452 , 452 , 499 , 544 , 544 , 452 , 497 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], +[ 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 363 , 408 , 319 , 544 , 499 , 499 , 544 , 452 , 497 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], +[ 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 361 , 407 , 499 , 499 , 499 , 452 , 497 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], +[ 271 , 271 , 271 , 272 , 271 , 278 , 271 , 271 , 271 , 279 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 362 , 407 , 544 , 499 , 544 , 452 , 497 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], +[ 1129 , 1174 , 1219 , 1264 , 900 , 945 , 990 , 1035 , 1174 , 1219 , 1264 , 1309 , 1354 , 280 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 361 , 407 , 499 , 499 , 499 , 452 , 497 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], +[ 721 , 766 , 811 , 856 , 901 , 946 , 991 , 1036 , 1175 , 1220 , 1265 , 1310 , 1355 , 1400 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 362 , 407 , 499 , 499 , 407 , 452 , 497 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], +[ 722 , 767 , 812 , 857 , 902 , 947 , 992 , 1037 , 1176 , 1221 , 1266 , 1311 , 1356 , 1401 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 361 , 544 , 634 , 452 , 544 , 452 , 497 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], +[ 723 , 768 , 813 , 858 , 903 , 948 , 993 , 1038 , 1177 , 1222 , 1267 , 1312 , 1357 , 1402 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 362 , 407 , 452 , 452 , 452 , 274 , 498 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], +[ 724 , 769 , 814 , 859 , 904 , 949 , 994 , 1039 , 1178 , 1223 , 1268 , 1313 , 1358 , 1403 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 363 , 408 , 453 , 453 , 453 , 498 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], +[ 725 , 770 , 815 , 860 , 905 , 950 , 995 , 1040 , 1179 , 1224 , 1269 , 1314 , 1359 , 1404 , 271 , 271 , 6 , 51 , 96 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], +[ 726 , 771 , 816 , 861 , 906 , 951 , 996 , 1041 , 1180 , 1225 , 1270 , 1315 , 1360 , 1405 , 271 , 271 , 7 , 52 , 97 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], +[ 735 , 780 , 825 , 870 , 907 , 952 , 997 , 1042 , 1181 , 1226 , 1271 , 234 , 1361 , 280 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], +[ 736 , 781 , 826 , 871 , 962 , 962 , 962 , 962 , 962 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 227 , 1129 , 1174 , 1219 , 227 , 1309 , 227 , 278 , 271 , 271 , 271 , 271 , 271 , 271 , ], +[ 737 , 782 , 827 , 872 , 962 , 962 , 962 , 233 , 1007 , 1007 , 1007 , 1007 , 1007 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 234 , 1130 , 1175 , 1220 , 1265 , 1310 , 1355 , 1400 , 271 , 271 , 271 , 271 , 271 , 271 , ], +[ 738 , 783 , 828 , 873 , 962 , 962 , 962 , 1007 , 1007 , 1007 , 1007 , 1007 , 1007 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 227 , 1131 , 1176 , 1221 , 1266 , 1311 , 1356 , 1401 , 271 , 271 , 271 , 271 , 271 , 271 , ], +[ 739 , 784 , 829 , 874 , 962 , 962 , 962 , 1007 , 1007 , 1007 , 1007 , 1007 , 1007 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 1087 , 1132 , 1177 , 1222 , 1267 , 1312 , 1357 , 1402 , 271 , 271 , 271 , 271 , 271 , 271 , ], +[ 736 , 781 , 826 , 871 , 962 , 962 , 962 , 1007 , 1007 , 1007 , 1007 , 1007 , 1007 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 728 , 773 , 818 , 863 , 908 , 953 , 998 , 1043 , 271 , 271 , 271 , 271 , 271 , 271 , ], +[ 737 , 782 , 827 , 872 , 962 , 962 , 962 , 1007 , 1007 , 1007 , 1007 , 1007 , 1007 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 729 , 774 , 819 , 864 , 909 , 954 , 999 , 1044 , 271 , 271 , 271 , 271 , 271 , 271 , ], +[ 738 , 783 , 828 , 873 , 962 , 962 , 962 , 1007 , 1007 , 1007 , 1007 , 1007 , 1007 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 234 , 730 , 775 , 820 , 865 , 910 , 955 , 1000 , 1045 , 271 , 271 , 271 , 280 , 271 , 271 , ], +[ 739 , 784 , 829 , 874 , 962 , 962 , 962 , 1007 , 1007 , 1007 , 1007 , 1007 , 143 , 188 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 731 , 776 , 821 , 866 , 911 , 956 , 1001 , 1046 , 271 , 271 , 271 , 271 , 271 , 271 , ], +[ 736 , 781 , 826 , 871 , 962 , 962 , 962 , 962 , 1007 , 1007 , 1007 , 1007 , 144 , 189 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 1088 , 1133 , 1178 , 1223 , 1268 , 1313 , 1358 , 1403 , 271 , 271 , 271 , 271 , 271 , 271 , ], +[ 737 , 782 , 827 , 872 , 271 , 1007 , 1007 , 1007 , 1007 , 1007 , 1007 , 1007 , 145 , 190 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 1089 , 1134 , 1179 , 1224 , 1269 , 1314 , 1359 , 1404 , 271 , 271 , 271 , 271 , 271 , 271 , ], +[ 738 , 783 , 828 , 873 , 271 , 271 , 271 , 271 , 271 , 271 , 233 , 271 , 1007 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 280 , 1135 , 1180 , 1225 , 1270 , 1315 , 1360 , 1405 , 271 , 271 , 271 , 271 , 271 , 271 , ], +[ 739 , 784 , 829 , 874 , 271 , 271 , 271 , 1010 , 1055 , 962 , 962 , 271 , 271 , 271 , 1309 , 1354 , 1399 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 280 , 143 , 188 , 1226 , 1271 , 227 , 1361 , 234 , 271 , 280 , 1129 , 1174 , 1219 , 1264 , ], +[ 740 , 785 , 830 , 875 , 920 , 965 , 1010 , 740 , 785 , 830 , 875 , 920 , 965 , 1010 , 1310 , 1355 , 1400 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 144 , 189 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 1130 , 1175 , 1220 , 1265 , ], +[ 741 , 786 , 831 , 876 , 921 , 966 , 1011 , 741 , 786 , 831 , 876 , 921 , 966 , 1011 , 1311 , 1356 , 1401 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 145 , 190 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 1131 , 1176 , 1221 , 1266 , ], +[ 742 , 787 , 832 , 877 , 922 , 967 , 1012 , 742 , 787 , 832 , 877 , 922 , 967 , 1012 , 1312 , 1357 , 1402 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 1087 , 1132 , 1177 , 1222 , 1267 , ], +[ 743 , 788 , 833 , 878 , 923 , 968 , 1013 , 743 , 788 , 833 , 878 , 923 , 968 , 1013 , 1313 , 1358 , 1403 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 278 , 271 , 271 , 271 , 271 , 1088 , 1133 , 1178 , 1223 , 1268 , ], +[ 1178 , 1223 , 1268 , 1223 , 1268 , 1223 , 1268 , 1223 , 1268 , 1223 , 1268 , 1178 , 1223 , 1268 , 1314 , 1359 , 1404 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 227 , 271 , 1134 , 1179 , 1224 , 1269 , ], +[ 1179 , 1224 , 1269 , 1224 , 1269 , 1224 , 1269 , 1224 , 1269 , 1224 , 1269 , 1179 , 1224 , 1269 , 1315 , 1360 , 1405 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 233 , 271 , 271 , 1143 , 1188 , 1233 , ], +[ 1180 , 1225 , 1270 , 1225 , 1270 , 1225 , 1270 , 1225 , 1270 , 1225 , 1270 , 1180 , 1225 , 1270 , 1316 , 1361 , 1406 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 1009 , 1054 , 1099 , 1144 , 1189 , 1234 , ], +[ 1181 , 1226 , 1271 , 1226 , 1271 , 1226 , 1271 , 1226 , 1271 , 1226 , 1271 , 1181 , 1226 , 1271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 272 , 1129 , 272 , 965 , 1010 , 1055 , 920 , 965 , 1010 , 1055 , 1100 , 1145 , 1190 , 1235 , ], +[ 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 272 , 1130 , 1219 , 966 , 1011 , 1056 , 921 , 966 , 1011 , 1056 , 1101 , 1146 , 1191 , 1236 , ], +[ 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 1086 , 1131 , 922 , 967 , 1012 , 1057 , 922 , 967 , 1012 , 1057 , 1102 , 1147 , 1192 , 1237 , ], +[ 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 1087 , 1132 , 923 , 968 , 1013 , 1058 , 923 , 968 , 1013 , 1058 , 1103 , 1148 , 1193 , 1238 , ], +], +[ +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 231 , 276 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 232 , 277 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 416 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 370 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 371 , 416 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 763 , 808 , 754 , 799 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 8 , 53 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 417 , 462 , 507 , 552 , 597 , 642 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 764 , 809 , 755 , 800 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 9 , 54 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 844 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , 762 , 751 , 796 , 841 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , 752 , 797 , 842 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 141 , 186 , -1 , -1 , -1 , -1 , -1 , -1 , 896 , -1 , -1 , 547 , 592 , 637 , 682 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , 753 , 798 , 843 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 142 , 187 , -1 , -1 , 11 , -1 , -1 , -1 , 191 , 236 , -1 , 548 , 593 , 638 , 683 , 682 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 11 , 56 , 101 , 146 , 191 , 236 , -1 , 548 , 593 , 638 , 683 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 893 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 278 , -1 , -1 , -1 , -1 , -1 , -1 , 12 , 57 , 102 , 147 , 192 , 237 , 941 , -1 , 594 , 639 , 684 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , 893 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 13 , 58 , 103 , 148 , 193 , 238 , -1 , -1 , 595 , 640 , 685 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 1026 , 1071 , 1116 , 1161 , -1 , -1 , -1 , 278 , -1 , -1 , -1 , 15 , 14 , 59 , 104 , 149 , 194 , 239 , 850 , -1 , 594 , 639 , 684 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 1027 , 1072 , 1117 , 1162 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 15 , 60 , 105 , 150 , 195 , 850 , -1 , -1 , 595 , 640 , 685 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 1028 , 1073 , 1118 , 1163 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 850 , -1 , 551 , 596 , 641 , 686 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 1029 , 1074 , 1119 , 1164 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 371 , 416 , -1 , -1 , -1 , -1 , 551 , 596 , 641 , 686 , 687 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 417 , 507 , 462 , 507 , 552 , 597 , 642 , 642 , 687 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 547 , 592 , 637 , 682 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 231 , 276 , -1 , -1 , -1 , 894 , 939 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 548 , 593 , 638 , 683 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 232 , 277 , 235 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 549 , 594 , 639 , 684 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 380 , 380 , 425 , 470 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 381 , 381 , 426 , 471 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 382 , 382 , 427 , 472 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 280 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 383 , 428 , 473 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 550 , 595 , 640 , 685 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 384 , 429 , 474 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 551 , 596 , 641 , 686 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 233 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 385 , 385 , 430 , 475 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , 462 , 507 , 552 , 552 , 597 , 642 , 687 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 386 , 386 , 431 , 476 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 387 , 387 , 432 , 477 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 11 , 56 , 101 , 146 , 191 , 236 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 279 , -1 , -1 , -1 , -1 , 12 , 57 , 102 , 147 , 192 , 237 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 380 , 425 , 470 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 13 , 58 , 103 , 148 , 193 , 238 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 381 , 426 , 471 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 14 , 59 , 104 , 149 , 194 , 239 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 382 , 427 , 472 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 15 , 60 , 105 , 150 , 195 , 240 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 383 , 428 , 473 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 384 , 429 , 474 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 385 , 430 , 475 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 386 , 431 , 476 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 387 , 432 , 477 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 935 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 893 , 893 , 844 , -1 , -1 , -1 , -1 , -1 , 934 , -1 , -1 , -1 , -1 , 935 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , 751 , 796 , 841 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 889 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 937 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , 752 , 797 , 842 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , 894 , 939 , 753 , 798 , 843 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 893 , 893 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 937 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , 845 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , 806 , -1 , -1 , -1 , -1 , 846 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , 806 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 936 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , 806 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 934 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 936 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 936 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 763 , 808 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 764 , 809 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 934 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 934 , -1 , -1 , -1 , 851 , -1 , 852 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +],]; + +export const objmap = [ +[ +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +], +[ +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +],]; +export const animatedsprites = [ +{ x: 736, y: 288, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 768, y: 288, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 800, y: 288, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 800, y: 256, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 832, y: 288, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 832, y: 224, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 800, y: 224, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 800, y: 192, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 768, y: 192, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 768, y: 160, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 800, y: 128, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 768, y: 96, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 800, y: 64, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 736, y: 448, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 768, y: 448, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 800, y: 448, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 832, y: 448, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 832, y: 480, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 800, y: 480, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 800, y: 512, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 768, y: 512, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 768, y: 544, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 800, y: 576, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 768, y: 480, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 736, y: 320, w: 32, h: 96, layer: 1, sheet: "gentlewaterfall.json", animation: "pixels_large" }, +{ x: 768, y: 320, w: 32, h: 96, layer: 1, sheet: "gentlewaterfall.json", animation: "pixels_large" }, +{ x: 800, y: 320, w: 32, h: 96, layer: 1, sheet: "gentlewaterfall.json", animation: "pixels_large" }, +{ x: 832, y: 320, w: 32, h: 96, layer: 1, sheet: "gentlewaterfall.json", animation: "pixels_large" }, +{ x: 1440, y: 352, w: 32, h: 32, layer: 1, sheet: "campfire.json", animation: "pixels_large" }, +{ x: 1664, y: 576, w: 208, h: 208, layer: 1, sheet: "windmill.json", animation: "pixels_large" }, +{ x: 1440, y: 768, w: 208, h: 208, layer: 1, sheet: "windmill.json", animation: "pixels_large" }, +{ x: 1120, y: 608, w: 208, h: 208, layer: 1, sheet: "windmill.json", animation: "pixels_large" }, +{ x: 768, y: 736, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 800, y: 768, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 800, y: 800, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 800, y: 832, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 800, y: 864, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 864, y: 1024, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 896, y: 1056, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 864, y: 1088, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 896, y: 1088, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 896, y: 1120, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 896, y: 1152, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 896, y: 1184, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 928, y: 1152, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 736, y: 384, w: 32, h: 64, layer: 2, sheet: "gentlesplash.json", animation: "pixels_large" }, +{ x: 768, y: 384, w: 32, h: 64, layer: 2, sheet: "gentlesplash.json", animation: "pixels_large" }, +{ x: 800, y: 384, w: 32, h: 64, layer: 2, sheet: "gentlesplash.json", animation: "pixels_large" }, +{ x: 832, y: 384, w: 32, h: 64, layer: 2, sheet: "gentlesplash.json", animation: "pixels_large" }, +]; diff --git a/patches/src/editor/maps/gentle.js b/patches/src/editor/maps/gentle.js new file mode 100644 index 0000000000000000000000000000000000000000..6dda5ecab3e63b0cc5b8f9e392d41539e5cc5b4c --- /dev/null +++ b/patches/src/editor/maps/gentle.js @@ -0,0 +1,327 @@ +// Map generated by assettool.js [Wed Oct 18 2023 21:07:27 GMT-0700 (Pacific Daylight Time)] + +export const tilesetpath = "./tilesets/gentle-obj.png" +export const tiledim = 32 +export const screenxtiles = 45 +export const screenytiles = 32 +export const tilesetpxw = 1440 +export const tilesetpxh = 1024 + +export const bgtiles = [ + [ +[ 732 , 777 , 822 , 867 , 912 , 957 , 912 , 957 , 1002 , 1047 , 912 , 957 , 1002 , 1047 , 912 , 957 , 1002 , 1047 , 1001 , 1046 , 946 , 991 , 1035 , 731 , 776 , 821 , 866 , 911 , 956 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 594 , 639 , 684 , 271 , ], +[ 733 , 778 , 823 , 868 , 913 , 958 , 913 , 958 , 1003 , 1048 , 913 , 958 , 1003 , 1048 , 913 , 958 , 1003 , 1048 , 856 , 901 , 946 , 991 , 1036 , 732 , 777 , 822 , 867 , 912 , 957 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 595 , 640 , 685 , 271 , ], +[ 734 , 779 , 824 , 869 , 914 , 959 , 914 , 959 , 1004 , 1049 , 914 , 959 , 1004 , 1049 , 914 , 959 , 1004 , 1049 , 857 , 902 , 947 , 992 , 1037 , 733 , 778 , 823 , 868 , 913 , 958 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 594 , 639 , 684 , 271 , ], +[ 735 , 780 , 825 , 870 , 915 , 960 , 915 , 960 , 1005 , 1050 , 915 , 960 , 1005 , 1050 , 915 , 960 , 1087 , 1132 , 858 , 903 , 948 , 993 , 1038 , 734 , 779 , 824 , 869 , 914 , 959 , 271 , 271 , 180 , 225 , 225 , 315 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 595 , 640 , 685 , 271 , ], +[ 736 , 781 , 826 , 871 , 916 , 278 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 1087 , 1132 , 859 , 904 , 949 , 994 , 1039 , 735 , 780 , 825 , 870 , 915 , 960 , 271 , 271 , 181 , 226 , 226 , 140 , 270 , 315 , 271 , 271 , 271 , 271 , 271 , 271 , 541 , 586 , 631 , 686 , 271 , ], +[ 737 , 782 , 827 , 872 , 917 , 233 , 271 , 271 , 271 , 271 , 271 , 233 , 271 , 271 , 271 , 271 , 1088 , 1133 , 860 , 905 , 950 , 995 , 1040 , 736 , 781 , 826 , 871 , 916 , 961 , 271 , 271 , 181 , 226 , 278 , 272 , 271 , 316 , 271 , 271 , 271 , 271 , 271 , 551 , 542 , 587 , 632 , 271 , 271 , ], +[ 738 , 783 , 828 , 873 , 271 , 271 , 235 , 962 , 962 , 962 , 962 , 962 , 271 , 271 , 271 , 271 , 1089 , 1134 , 861 , 906 , 951 , 996 , 1041 , 737 , 782 , 827 , 872 , 917 , 962 , 271 , 271 , 181 , 226 , 226 , 272 , 271 , 675 , 271 , 271 , 271 , 271 , 272 , 272 , 642 , 271 , 271 , 271 , 271 , ], +[ 739 , 784 , 829 , 874 , 271 , 271 , 235 , 962 , 962 , 962 , 962 , 962 , 271 , 271 , 271 , 271 , 1090 , 1135 , 862 , 907 , 952 , 997 , 1042 , 738 , 783 , 828 , 873 , 918 , 963 , 271 , 271 , 183 , 228 , 228 , 184 , 271 , 675 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], +[ 736 , 781 , 826 , 871 , 271 , 271 , 962 , 962 , 962 , 962 , 962 , 962 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 962 , 962 , 962 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 182 , 280 , 317 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], +[ 737 , 782 , 827 , 872 , 271 , 271 , 962 , 962 , 962 , 962 , 962 , 962 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 962 , 0 , 135 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 140 , 273 , 318 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], +[ 738 , 783 , 828 , 873 , 271 , 271 , 962 , 962 , 962 , 962 , 962 , 962 , 0 , 45 , 90 , 135 , 271 , 271 , 271 , 271 , 962 , 1 , 136 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], +[ 739 , 784 , 829 , 874 , 271 , 271 , 962 , 962 , 962 , 962 , 962 , 962 , 3 , 48 , 93 , 271 , 271 , 271 , 271 , 271 , 962 , 1 , 136 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], +[ 736 , 781 , 826 , 871 , 271 , 271 , 962 , 962 , 962 , 962 , 962 , 279 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 1 , 136 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], +[ 737 , 782 , 827 , 872 , 271 , 271 , 962 , 962 , 962 , 962 , 143 , 188 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 1 , 136 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], +[ 738 , 783 , 828 , 873 , 271 , 271 , 278 , 962 , 962 , 962 , 144 , 189 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 1 , 136 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], +[ 739 , 784 , 829 , 874 , 271 , 271 , 271 , 271 , 271 , 271 , 145 , 190 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 1 , 136 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], +[ 736 , 781 , 826 , 871 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 1 , 136 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], +[ 737 , 782 , 827 , 872 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 3 , 138 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], +[ 738 , 783 , 828 , 873 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], +[ 739 , 784 , 829 , 874 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 360 , 405 , 405 , 405 , 450 , 495 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], +[ 1270 , 1315 , 1360 , 1405 , 226 , 360 , 405 , 450 , 495 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 361 , 409 , 451 , 451 , 451 , 496 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], +[ 1271 , 1316 , 226 , 226 , 226 , 361 , 409 , 409 , 496 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 361 , 407 , 452 , 452 , 452 , 496 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], +[ 271 , 271 , 271 , 271 , 450 , 362 , 406 , 407 , 451 , 451 , 271 , 271 , 271 , 405 , 405 , 450 , 320 , 409 , 451 , 452 , 452 , 497 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], +[ 405 , 450 , 405 , 450 , 450 , 320 , 406 , 406 , 451 , 589 , 451 , 451 , 451 , 406 , 451 , 409 , 451 , 409 , 451 , 452 , 406 , 275 , 405 , 450 , 405 , 495 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], +[ 406 , 406 , 406 , 451 , 451 , 407 , 406 , 407 , 452 , 451 , 451 , 451 , 451 , 407 , 589 , 452 , 452 , 451 , 406 , 451 , 406 , 406 , 407 , 451 , 451 , 275 , 405 , 450 , 495 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], +[ 408 , 453 , 408 , 453 , 319 , 406 , 407 , 406 , 451 , 451 , 451 , 589 , 451 , 634 , 452 , 589 , 406 , 451 , 634 , 452 , 274 , 408 , 408 , 319 , 451 , 451 , 451 , 451 , 275 , 405 , 405 , 450 , 405 , 495 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], +[ 271 , 271 , 271 , 271 , 361 , 634 , 452 , 407 , 634 , 451 , 451 , 451 , 451 , 407 , 452 , 452 , 407 , 452 , 452 , 274 , 498 , 271 , 271 , 363 , 408 , 453 , 408 , 453 , 408 , 453 , 319 , 451 , 451 , 275 , 405 , 450 , 405 , 450 , 405 , 450 , 495 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], +[ 271 , 271 , 271 , 271 , 362 , 409 , 451 , 451 , 451 , 451 , 271 , 271 , 271 , 408 , 408 , 319 , 407 , 452 , 409 , 496 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 361 , 406 , 451 , 406 , 451 , 451 , 451 , 451 , 544 , 451 , 496 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], +[ 271 , 271 , 271 , 271 , 362 , 407 , 409 , 451 , 451 , 271 , 271 , 271 , 271 , 271 , 271 , 362 , 409 , 409 , 452 , 497 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 362 , 407 , 544 , 407 , 544 , 589 , 452 , 452 , 634 , 452 , 497 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], +[ 271 , 271 , 271 , 271 , 363 , 408 , 453 , 498 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 363 , 408 , 408 , 453 , 498 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 363 , 408 , 319 , 407 , 452 , 452 , 499 , 544 , 544 , 452 , 497 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], +[ 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 363 , 408 , 319 , 544 , 499 , 499 , 544 , 452 , 497 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], +[ 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 361 , 407 , 499 , 499 , 499 , 452 , 497 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], +[ 271 , 271 , 271 , 272 , 271 , 278 , 271 , 271 , 271 , 279 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 362 , 407 , 544 , 499 , 544 , 452 , 497 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], +[ 1129 , 1174 , 1219 , 1264 , 900 , 945 , 990 , 1035 , 1174 , 1219 , 1264 , 1309 , 1354 , 280 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 361 , 407 , 499 , 499 , 499 , 452 , 497 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], +[ 721 , 766 , 811 , 856 , 901 , 946 , 991 , 1036 , 1175 , 1220 , 1265 , 1310 , 1355 , 1400 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 362 , 407 , 499 , 499 , 407 , 452 , 497 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], +[ 722 , 767 , 812 , 857 , 902 , 947 , 992 , 1037 , 1176 , 1221 , 1266 , 1311 , 1356 , 1401 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 361 , 544 , 634 , 452 , 544 , 452 , 497 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], +[ 723 , 768 , 813 , 858 , 903 , 948 , 993 , 1038 , 1177 , 1222 , 1267 , 1312 , 1357 , 1402 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 362 , 407 , 452 , 452 , 452 , 274 , 498 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], +[ 724 , 769 , 814 , 859 , 904 , 949 , 994 , 1039 , 1178 , 1223 , 1268 , 1313 , 1358 , 1403 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 363 , 408 , 453 , 453 , 453 , 498 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], +[ 725 , 770 , 815 , 860 , 905 , 950 , 995 , 1040 , 1179 , 1224 , 1269 , 1314 , 1359 , 1404 , 271 , 271 , 6 , 51 , 96 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], +[ 726 , 771 , 816 , 861 , 906 , 951 , 996 , 1041 , 1180 , 1225 , 1270 , 1315 , 1360 , 1405 , 271 , 271 , 7 , 52 , 97 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], +[ 735 , 780 , 825 , 870 , 907 , 952 , 997 , 1042 , 1181 , 1226 , 1271 , 234 , 1361 , 280 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , ], +[ 736 , 781 , 826 , 871 , 962 , 962 , 962 , 962 , 962 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 227 , 1129 , 1174 , 1219 , 227 , 1309 , 227 , 278 , 271 , 271 , 271 , 271 , 271 , 271 , ], +[ 737 , 782 , 827 , 872 , 962 , 962 , 962 , 233 , 1007 , 1007 , 1007 , 1007 , 1007 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 234 , 1130 , 1175 , 1220 , 1265 , 1310 , 1355 , 1400 , 271 , 271 , 271 , 271 , 271 , 271 , ], +[ 738 , 783 , 828 , 873 , 962 , 962 , 962 , 1007 , 1007 , 1007 , 1007 , 1007 , 1007 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 227 , 1131 , 1176 , 1221 , 1266 , 1311 , 1356 , 1401 , 271 , 271 , 271 , 271 , 271 , 271 , ], +[ 739 , 784 , 829 , 874 , 962 , 962 , 962 , 1007 , 1007 , 1007 , 1007 , 1007 , 1007 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 1087 , 1132 , 1177 , 1222 , 1267 , 1312 , 1357 , 1402 , 271 , 271 , 271 , 271 , 271 , 271 , ], +[ 736 , 781 , 826 , 871 , 962 , 962 , 962 , 1007 , 1007 , 1007 , 1007 , 1007 , 1007 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 728 , 773 , 818 , 863 , 908 , 953 , 998 , 1043 , 271 , 271 , 271 , 271 , 271 , 271 , ], +[ 737 , 782 , 827 , 872 , 962 , 962 , 962 , 1007 , 1007 , 1007 , 1007 , 1007 , 1007 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 729 , 774 , 819 , 864 , 909 , 954 , 999 , 1044 , 271 , 271 , 271 , 271 , 271 , 271 , ], +[ 738 , 783 , 828 , 873 , 962 , 962 , 962 , 1007 , 1007 , 1007 , 1007 , 1007 , 1007 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 234 , 730 , 775 , 820 , 865 , 910 , 955 , 1000 , 1045 , 271 , 271 , 271 , 280 , 271 , 271 , ], +[ 739 , 784 , 829 , 874 , 962 , 962 , 962 , 1007 , 1007 , 1007 , 1007 , 1007 , 143 , 188 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 731 , 776 , 821 , 866 , 911 , 956 , 1001 , 1046 , 271 , 271 , 271 , 271 , 271 , 271 , ], +[ 736 , 781 , 826 , 871 , 962 , 962 , 962 , 962 , 1007 , 1007 , 1007 , 1007 , 144 , 189 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 1088 , 1133 , 1178 , 1223 , 1268 , 1313 , 1358 , 1403 , 271 , 271 , 271 , 271 , 271 , 271 , ], +[ 737 , 782 , 827 , 872 , 271 , 1007 , 1007 , 1007 , 1007 , 1007 , 1007 , 1007 , 145 , 190 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 1089 , 1134 , 1179 , 1224 , 1269 , 1314 , 1359 , 1404 , 271 , 271 , 271 , 271 , 271 , 271 , ], +[ 738 , 783 , 828 , 873 , 271 , 271 , 271 , 271 , 271 , 271 , 233 , 271 , 1007 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 280 , 1135 , 1180 , 1225 , 1270 , 1315 , 1360 , 1405 , 271 , 271 , 271 , 271 , 271 , 271 , ], +[ 739 , 784 , 829 , 874 , 271 , 271 , 271 , 1010 , 1055 , 962 , 962 , 271 , 271 , 271 , 1309 , 1354 , 1399 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 280 , 143 , 188 , 1226 , 1271 , 227 , 1361 , 234 , 271 , 280 , 1129 , 1174 , 1219 , 1264 , ], +[ 740 , 785 , 830 , 875 , 920 , 965 , 1010 , 740 , 785 , 830 , 875 , 920 , 965 , 1010 , 1310 , 1355 , 1400 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 144 , 189 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 1130 , 1175 , 1220 , 1265 , ], +[ 741 , 786 , 831 , 876 , 921 , 966 , 1011 , 741 , 786 , 831 , 876 , 921 , 966 , 1011 , 1311 , 1356 , 1401 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 145 , 190 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 1131 , 1176 , 1221 , 1266 , ], +[ 742 , 787 , 832 , 877 , 922 , 967 , 1012 , 742 , 787 , 832 , 877 , 922 , 967 , 1012 , 1312 , 1357 , 1402 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 1087 , 1132 , 1177 , 1222 , 1267 , ], +[ 743 , 788 , 833 , 878 , 923 , 968 , 1013 , 743 , 788 , 833 , 878 , 923 , 968 , 1013 , 1313 , 1358 , 1403 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 278 , 271 , 271 , 271 , 271 , 1088 , 1133 , 1178 , 1223 , 1268 , ], +[ 1178 , 1223 , 1268 , 1223 , 1268 , 1223 , 1268 , 1223 , 1268 , 1223 , 1268 , 1178 , 1223 , 1268 , 1314 , 1359 , 1404 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 227 , 271 , 1134 , 1179 , 1224 , 1269 , ], +[ 1179 , 1224 , 1269 , 1224 , 1269 , 1224 , 1269 , 1224 , 1269 , 1224 , 1269 , 1179 , 1224 , 1269 , 1315 , 1360 , 1405 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 233 , 271 , 271 , 1143 , 1188 , 1233 , ], +[ 1180 , 1225 , 1270 , 1225 , 1270 , 1225 , 1270 , 1225 , 1270 , 1225 , 1270 , 1180 , 1225 , 1270 , 1316 , 1361 , 1406 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 1009 , 1054 , 1099 , 1144 , 1189 , 1234 , ], +[ 1181 , 1226 , 1271 , 1226 , 1271 , 1226 , 1271 , 1226 , 1271 , 1226 , 1271 , 1181 , 1226 , 1271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 272 , 1129 , 272 , 965 , 1010 , 1055 , 920 , 965 , 1010 , 1055 , 1100 , 1145 , 1190 , 1235 , ], +[ 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 272 , 1130 , 1219 , 966 , 1011 , 1056 , 921 , 966 , 1011 , 1056 , 1101 , 1146 , 1191 , 1236 , ], +[ 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 1086 , 1131 , 922 , 967 , 1012 , 1057 , 922 , 967 , 1012 , 1057 , 1102 , 1147 , 1192 , 1237 , ], +[ 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 271 , 1087 , 1132 , 923 , 968 , 1013 , 1058 , 923 , 968 , 1013 , 1058 , 1103 , 1148 , 1193 , 1238 , ], +], +[ +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 231 , 276 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 232 , 277 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 416 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 370 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 371 , 416 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 763 , 808 , 754 , 799 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 8 , 53 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 417 , 462 , 507 , 552 , 597 , 642 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 764 , 809 , 755 , 800 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 9 , 54 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 844 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , 762 , 751 , 796 , 841 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , 752 , 797 , 842 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 141 , 186 , -1 , -1 , -1 , -1 , -1 , -1 , 896 , -1 , -1 , 547 , 592 , 637 , 682 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , 753 , 798 , 843 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 142 , 187 , -1 , -1 , 11 , -1 , -1 , -1 , 191 , 236 , -1 , 548 , 593 , 638 , 683 , 682 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 11 , 56 , 101 , 146 , 191 , 236 , -1 , 548 , 593 , 638 , 683 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 893 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 278 , -1 , -1 , -1 , -1 , -1 , -1 , 12 , 57 , 102 , 147 , 192 , 237 , 941 , -1 , 594 , 639 , 684 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , 893 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 13 , 58 , 103 , 148 , 193 , 238 , -1 , -1 , 595 , 640 , 685 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 1026 , 1071 , 1116 , 1161 , -1 , -1 , -1 , 278 , -1 , -1 , -1 , 15 , 14 , 59 , 104 , 149 , 194 , 239 , 850 , -1 , 594 , 639 , 684 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 1027 , 1072 , 1117 , 1162 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 15 , 60 , 105 , 150 , 195 , 850 , -1 , -1 , 595 , 640 , 685 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 1028 , 1073 , 1118 , 1163 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 850 , -1 , 551 , 596 , 641 , 686 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 1029 , 1074 , 1119 , 1164 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 371 , 416 , -1 , -1 , -1 , -1 , 551 , 596 , 641 , 686 , 687 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 417 , 507 , 462 , 507 , 552 , 597 , 642 , 642 , 687 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 547 , 592 , 637 , 682 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 231 , 276 , -1 , -1 , -1 , 894 , 939 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 548 , 593 , 638 , 683 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 232 , 277 , 235 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 549 , 594 , 639 , 684 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 380 , 380 , 425 , 470 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 381 , 381 , 426 , 471 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 382 , 382 , 427 , 472 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 280 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 383 , 428 , 473 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 550 , 595 , 640 , 685 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 384 , 429 , 474 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 551 , 596 , 641 , 686 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 233 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 385 , 385 , 430 , 475 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , 462 , 507 , 552 , 552 , 597 , 642 , 687 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 386 , 386 , 431 , 476 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 387 , 387 , 432 , 477 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 11 , 56 , 101 , 146 , 191 , 236 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 279 , -1 , -1 , -1 , -1 , 12 , 57 , 102 , 147 , 192 , 237 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 380 , 425 , 470 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 13 , 58 , 103 , 148 , 193 , 238 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 381 , 426 , 471 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 14 , 59 , 104 , 149 , 194 , 239 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 382 , 427 , 472 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 15 , 60 , 105 , 150 , 195 , 240 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 383 , 428 , 473 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 384 , 429 , 474 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 385 , 430 , 475 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 386 , 431 , 476 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 387 , 432 , 477 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 935 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 893 , 893 , 844 , -1 , -1 , -1 , -1 , -1 , 934 , -1 , -1 , -1 , -1 , 935 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , 751 , 796 , 841 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 889 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 937 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , 752 , 797 , 842 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , 894 , 939 , 753 , 798 , 843 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 893 , 893 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 937 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , 845 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , 806 , -1 , -1 , -1 , -1 , 846 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , 806 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 936 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , 806 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 934 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 936 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 936 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 763 , 808 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 764 , 809 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 934 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 934 , -1 , -1 , -1 , 851 , -1 , 852 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +],]; + +export const objmap = [ +[ +[ 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , -1 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , -1 , ], +[ 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , -1 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , -1 , ], +[ 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , -1 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , -1 , ], +[ 458 , 458 , 458 , 458 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 458 , 458 , 458 , 458 , -1 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , -1 , -1 , 367 , 367 , 367 , 367 , -1 , -1 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , -1 , ], +[ 458 , 458 , 458 , 458 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 458 , 458 , 458 , 458 , -1 , 367 , 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , 367 , -1 , -1 , 367 , 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , 367 , -1 , -1 , 367 , -1 , ], +[ 458 , 458 , 458 , 458 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 458 , 458 , 458 , 458 , -1 , 367 , 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , 367 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , -1 , -1 , -1 , -1 , 367 , -1 , -1 , -1 , -1 , ], +[ 458 , 458 , 458 , 458 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , 458 , 458 , 458 , 458 , -1 , 367 , 367 , 367 , 367 , -1 , 458 , 458 , -1 , -1 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , 367 , 367 , 367 , -1 , -1 , -1 , -1 , ], +[ 458 , 458 , 458 , 458 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 458 , -1 , -1 , 367 , 367 , 367 , 367 , -1 , 458 , 458 , -1 , -1 , 367 , 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 458 , 458 , 458 , 458 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , -1 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 458 , 458 , 458 , 458 , -1 , -1 , -1 , -1 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 458 , 458 , 458 , 458 , -1 , -1 , -1 , -1 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , -1 , -1 , -1 , ], +[ 458 , 458 , 458 , 458 , -1 , -1 , -1 , -1 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , -1 , -1 , -1 , ], +[ 458 , 458 , 458 , 458 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , -1 , -1 , -1 , 367 , 367 , 367 , 367 , -1 , ], +[ 458 , 458 , 458 , 458 , -1 , -1 , -1 , -1 , 367 , -1 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , 367 , 367 , 367 , -1 , 367 , 367 , 367 , 367 , -1 , ], +[ 458 , 458 , 458 , 458 , -1 , -1 , -1 , 367 , -1 , -1 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , 367 , 367 , 367 , -1 , -1 , 367 , 367 , 367 , -1 , ], +[ 458 , 458 , 458 , 458 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 458 , 458 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , 367 , 367 , 367 , -1 , -1 , 367 , 367 , 367 , -1 , ], +[ 458 , 458 , 458 , 458 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 458 , 458 , 458 , 458 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , -1 , ], +[ 458 , 458 , 458 , 458 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 458 , 458 , 458 , 458 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , -1 , ], +[ 458 , 458 , 458 , 458 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 458 , 458 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , -1 , -1 , ], +[ 458 , 458 , 458 , 458 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 458 , 458 , 458 , 458 , 458 , 458 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , -1 , -1 , -1 , ], +[ 458 , 458 , 458 , 458 , -1 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , -1 , -1 , -1 , 458 , 458 , 458 , 458 , 458 , 458 , -1 , 458 , 458 , -1 , -1 , -1 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , -1 , -1 , -1 , 458 , 458 , 458 , 458 , 458 , 458 , -1 , 458 , 458 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , -1 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , -1 , -1 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , ], +[ 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , -1 , -1 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , ], +[ 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , -1 , -1 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , ], +[ -1 , -1 , -1 , -1 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , -1 , -1 , 458 , 458 , 458 , 458 , 458 , -1 , -1 , -1 , -1 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , ], +[ -1 , -1 , -1 , -1 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , -1 , 458 , 458 , 458 , 458 , 458 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , ], +[ -1 , -1 , -1 , -1 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , -1 , -1 , -1 , 458 , 458 , 458 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , -1 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , ], +[ -1 , -1 , -1 , -1 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , -1 , -1 , -1 , -1 , 458 , 458 , 458 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , -1 , -1 , -1 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , 367 , 367 , 367 , -1 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , 367 , 367 , 367 , -1 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , 367 , 367 , 367 , -1 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , ], +[ 367 , 367 , 367 , 367 , 367 , 367 , -1 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , -1 , -1 , -1 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , ], +[ 367 , 367 , 367 , 367 , 367 , 367 , -1 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 458 , 458 , 458 , 458 , 458 , 458 , 458 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , ], +[ 367 , 367 , 367 , 367 , 367 , 367 , -1 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 458 , 458 , 458 , 458 , 458 , 458 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , ], +[ 367 , 367 , 367 , 367 , 367 , 367 , -1 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , -1 , -1 , 367 , 367 , 367 , -1 , -1 , 367 , 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , ], +[ 367 , 367 , 367 , 367 , 367 , 367 , -1 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , -1 , -1 , 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 367 , 367 , 367 , -1 , -1 , -1 , -1 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 367 , 367 , 367 , -1 , -1 , 367 , 367 , -1 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , 367 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 367 , 367 , 367 , -1 , -1 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , 367 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 367 , 367 , 367 , -1 , -1 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 367 , 367 , 367 , -1 , 367 , -1 , -1 , -1 , -1 , -1 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , 367 , ], +[ 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , 367 , ], +[ 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , -1 , -1 , -1 , 367 , 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , 367 , ], +[ 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , -1 , -1 , -1 , 367 , 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , 367 , ], +[ 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , 367 , ], +[ 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , 367 , ], +[ 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , 367 , ], +[ 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , 367 , ], +[ 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , -1 , 367 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , 367 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , 367 , ], +], +[ +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +],]; +export const animatedsprites = [ +{ x: 1440, y: 352, w: 32, h: 32, layer: 1, sheet: "campfire.json", animation: "pixels_large" }, +{ x: 736, y: 288, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 768, y: 288, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 800, y: 288, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 800, y: 256, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 832, y: 288, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 832, y: 224, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 800, y: 224, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 800, y: 192, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 768, y: 192, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 768, y: 160, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 800, y: 128, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 768, y: 96, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 800, y: 64, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 736, y: 448, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 768, y: 448, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 800, y: 448, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 832, y: 448, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 832, y: 480, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 800, y: 480, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 800, y: 512, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 768, y: 512, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 768, y: 544, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 800, y: 576, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 768, y: 480, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 768, y: 736, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 800, y: 768, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 800, y: 800, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 800, y: 832, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 800, y: 864, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 864, y: 1024, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 896, y: 1056, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 864, y: 1088, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 896, y: 1088, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 896, y: 1120, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 896, y: 1152, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 896, y: 1184, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 928, y: 1152, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 736, y: 320, w: 32, h: 96, layer: 1, sheet: "gentlewaterfall.json", animation: "pixels_large" }, +{ x: 768, y: 320, w: 32, h: 96, layer: 1, sheet: "gentlewaterfall.json", animation: "pixels_large" }, +{ x: 800, y: 320, w: 32, h: 96, layer: 1, sheet: "gentlewaterfall.json", animation: "pixels_large" }, +{ x: 832, y: 320, w: 32, h: 96, layer: 1, sheet: "gentlewaterfall.json", animation: "pixels_large" }, +{ x: 1664, y: 576, w: 208, h: 208, layer: 1, sheet: "windmill.json", animation: "pixels_large" }, +{ x: 1440, y: 768, w: 208, h: 208, layer: 1, sheet: "windmill.json", animation: "pixels_large" }, +{ x: 1120, y: 608, w: 208, h: 208, layer: 1, sheet: "windmill.json", animation: "pixels_large" }, +{ x: 736, y: 384, w: 32, h: 64, layer: 2, sheet: "gentlesplash.json", animation: "pixels_large" }, +{ x: 768, y: 384, w: 32, h: 64, layer: 2, sheet: "gentlesplash.json", animation: "pixels_large" }, +{ x: 800, y: 384, w: 32, h: 64, layer: 2, sheet: "gentlesplash.json", animation: "pixels_large" }, +{ x: 832, y: 384, w: 32, h: 64, layer: 2, sheet: "gentlesplash.json", animation: "pixels_large" }, +]; diff --git a/patches/src/editor/maps/gentleanim.js b/patches/src/editor/maps/gentleanim.js new file mode 100644 index 0000000000000000000000000000000000000000..b7f26f8541897d025e4c58f717abe310975a17f0 --- /dev/null +++ b/patches/src/editor/maps/gentleanim.js @@ -0,0 +1,308 @@ +// Map generated by assettool.js [Sat Sep 30 2023 23:42:21 GMT-0700 (Pacific Daylight Time)] + +export const tilesetpath = "./tilesets/gentle.png" +export const tiledim = 32 +export const screenxtiles = 40 +export const screenytiles = 32 +export const tilesetpxw = 1280 +export const tilesetpxh = 1024 + +export const bgtiles = [ + [ +[ 652 , 692 , 732 , 772 , 812 , 852 , 892 , 812 , 852 , 892 , 932 , 812 , 852 , 892 , 932 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 0 , 120 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , ], +[ 653 , 693 , 733 , 773 , 813 , 853 , 1089 , 813 , 853 , 893 , 933 , 813 , 853 , 893 , 933 , 857 , 242 , 242 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 1 , 121 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , ], +[ 654 , 694 , 734 , 774 , 1010 , 1050 , 1090 , 814 , 854 , 894 , 934 , 814 , 854 , 894 , 934 , 857 , 242 , 242 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 1 , 121 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , ], +[ 655 , 695 , 735 , 775 , 815 , 970 , 1091 , 815 , 855 , 895 , 935 , 815 , 855 , 895 , 935 , 857 , 242 , 242 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 1 , 121 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , ], +[ 656 , 696 , 736 , 776 , 242 , 242 , 242 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 248 , 241 , 241 , 241 , 160 , 200 , 200 , 200 , 200 , 280 , 241 , 241 , 241 , 1 , 121 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , ], +[ 657 , 697 , 737 , 777 , 242 , 242 , 242 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 161 , 201 , 201 , 201 , 250 , 281 , 241 , 241 , 241 , 1 , 121 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , ], +[ 658 , 698 , 738 , 778 , 242 , 242 , 242 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 161 , 201 , 201 , 201 , 249 , 281 , 241 , 241 , 241 , 1 , 121 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , ], +[ 655 , 695 , 735 , 777 , 241 , 241 , 241 , 241 , 0 , 40 , 80 , 120 , 241 , 241 , 241 , 241 , 241 , 241 , 161 , 201 , 250 , 201 , 201 , 600 , 241 , 241 , 241 , 1 , 121 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , ], +[ 656 , 696 , 736 , 776 , 241 , 241 , 241 , 241 , 1 , 41 , 81 , 121 , 241 , 241 , 241 , 241 , 241 , 241 , 161 , 201 , 201 , 250 , 201 , 601 , 241 , 241 , 241 , 1 , 121 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , ], +[ 657 , 697 , 737 , 777 , 241 , 241 , 241 , 210 , 3 , 43 , 83 , 123 , 241 , 241 , 241 , 241 , 241 , 241 , 161 , 201 , 249 , 201 , 201 , 281 , 241 , 241 , 241 , 1 , 121 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , ], +[ 658 , 698 , 738 , 778 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 210 , 241 , 241 , 161 , 201 , 201 , 201 , 201 , 281 , 241 , 241 , 241 , 2 , 122 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , ], +[ 659 , 699 , 739 , 779 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 163 , 203 , 203 , 203 , 203 , 283 , 241 , 241 , 241 , 1 , 121 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , ], +[ 658 , 698 , 738 , 778 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 1 , 122 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , ], +[ 659 , 699 , 739 , 779 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 2 , 121 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , ], +[ 360 , 400 , 400 , 400 , 360 , 400 , 400 , 400 , 440 , 50 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 1 , 122 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , ], +[ 362 , 402 , 362 , 402 , 402 , 401 , 402 , 402 , 441 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 1 , 121 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , ], +[ 363 , 403 , 363 , 403 , 403 , 363 , 403 , 284 , 245 , 400 , 413 , 493 , 533 , 573 , 613 , 360 , 400 , 440 , 241 , 241 , 320 , 360 , 400 , 400 , 440 , 241 , 241 , 2 , 122 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , ], +[ 964 , 1004 , 1044 , 1084 , 1124 , 1164 , 1204 , 322 , 362 , 362 , 364 , 362 , 362 , 362 , 362 , 564 , 401 , 441 , 241 , 242 , 322 , 404 , 401 , 362 , 441 , 241 , 241 , 1 , 121 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , ], +[ 965 , 1005 , 1045 , 1085 , 1125 , 1165 , 1205 , 323 , 284 , 362 , 404 , 404 , 444 , 444 , 444 , 362 , 524 , 245 , 360 , 360 , 285 , 444 , 444 , 362 , 442 , 241 , 241 , 2 , 122 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , ], +[ 966 , 1006 , 1046 , 1086 , 1126 , 1166 , 1206 , 1246 , 321 , 362 , 444 , 484 , 362 , 362 , 362 , 362 , 401 , 564 , 361 , 361 , 361 , 444 , 444 , 484 , 441 , 241 , 241 , 1 , 121 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , ], +[ 967 , 1007 , 1047 , 1087 , 1127 , 1167 , 1207 , 1247 , 322 , 402 , 364 , 455 , 495 , 535 , 575 , 615 , 363 , 403 , 363 , 284 , 361 , 444 , 444 , 484 , 442 , 241 , 241 , 1 , 121 , 241 , 241 , 241 , 241 , 241 , 241 , 210 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , ], +[ 968 , 1008 , 1048 , 1088 , 1128 , 1168 , 1208 , 1248 , 323 , 363 , 403 , 443 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 322 , 362 , 362 , 484 , 484 , 442 , 241 , 241 , 2 , 122 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , ], +[ 969 , 1009 , 1049 , 1089 , 1129 , 1169 , 1209 , 1249 , 857 , 857 , 857 , 857 , 857 , 857 , 210 , 241 , 241 , 241 , 241 , 323 , 363 , 284 , 362 , 362 , 442 , 241 , 241 , 2 , 122 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , ], +[ 970 , 1010 , 1050 , 1090 , 1130 , 1170 , 1210 , 1250 , 857 , 857 , 857 , 857 , 857 , 857 , 241 , 241 , 128 , 168 , 241 , 241 , 241 , 323 , 403 , 403 , 443 , 241 , 241 , 84 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , ], +[ 971 , 1011 , 857 , 857 , 857 , 1044 , 1084 , 1124 , 1164 , 1044 , 1084 , 1204 , 1244 , 857 , 857 , 241 , 129 , 169 , 241 , 241 , 241 , 241 , 242 , 241 , 241 , 0 , 45 , 4 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , ], +[ 857 , 857 , 857 , 857 , 857 , 1005 , 1045 , 1085 , 1125 , 1045 , 1085 , 1205 , 1245 , 857 , 248 , 241 , 130 , 170 , 241 , 241 , 241 , 241 , 0 , 80 , 120 , 1 , 83 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , ], +[ 857 , 857 , 857 , 857 , 857 , 648 , 688 , 728 , 768 , 808 , 848 , 888 , 928 , 857 , 857 , 249 , 241 , 241 , 241 , 241 , 241 , 241 , 1 , 41 , 5 , 84 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , ], +[ 857 , 857 , 857 , 857 , 857 , 649 , 689 , 729 , 769 , 809 , 849 , 889 , 929 , 857 , 857 , 857 , 241 , 241 , 241 , 241 , 241 , 241 , 2 , 42 , 122 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , ], +[ 857 , 857 , 857 , 857 , 1004 , 1044 , 1084 , 730 , 770 , 810 , 850 , 890 , 930 , 857 , 857 , 857 , 241 , 241 , 241 , 241 , 241 , 241 , 3 , 43 , 123 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , ], +[ 857 , 857 , 857 , 965 , 1005 , 1045 , 1085 , 731 , 771 , 811 , 851 , 891 , 1205 , 1245 , 857 , 857 , 249 , 241 , 241 , 241 , 241 , 241 , 208 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , ], +[ 857 , 857 , 857 , 966 , 1006 , 1046 , 1086 , 732 , 772 , 812 , 852 , 892 , 1206 , 1246 , 857 , 857 , 857 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , ], +[ 857 , 857 , 857 , 967 , 1007 , 1047 , 1087 , 733 , 773 , 813 , 853 , 893 , 1207 , 1247 , 857 , 857 , 857 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , ], +[ 857 , 857 , 857 , 968 , 1008 , 1048 , 1088 , 734 , 1048 , 1088 , 1128 , 1168 , 1208 , 1248 , 857 , 857 , 857 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , ], +[ 857 , 857 , 857 , 969 , 1009 , 1049 , 1089 , 727 , 1049 , 1089 , 1169 , 1169 , 1209 , 1249 , 857 , 857 , 857 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , ], +[ 857 , 249 , 241 , 242 , 1010 , 1050 , 1090 , 1130 , 1130 , 1170 , 1170 , 1170 , 1210 , 1250 , 857 , 857 , 250 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , ], +[ 241 , 241 , 241 , 242 , 242 , 249 , 1091 , 1131 , 1131 , 1171 , 1211 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , ], +[ 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 6 , 46 , 86 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , ], +[ 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 0 , 120 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 7 , 47 , 87 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , ], +[ 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 2 , 121 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , ], +[ 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 0 , 40 , 40 , 40 , 40 , 40 , 120 , 241 , 2 , 122 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , ], +[ 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 1 , 41 , 41 , 41 , 41 , 41 , 5 , 80 , 45 , 122 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , ], +[ 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 1 , 41 , 41 , 41 , 41 , 82 , 4 , 43 , 83 , 123 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , ], +[ 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 1 , 41 , 41 , 41 , 41 , 41 , 85 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , ], +[ 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 1 , 41 , 41 , 41 , 41 , 41 , 123 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , ], +[ 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 3 , 43 , 43 , 43 , 43 , 43 , 123 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , ], +[ 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , ], +[ 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , ], +[ 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 248 , 241 , 201 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , ], +[ 1004 , 1044 , 1084 , 1124 , 1164 , 1124 , 1164 , 1204 , 1244 , 201 , 201 , 201 , 970 , 210 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , ], +[ 652 , 692 , 732 , 772 , 812 , 1125 , 1165 , 1205 , 1245 , 201 , 201 , 201 , 201 , 970 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , ], +[ 653 , 693 , 733 , 773 , 1089 , 1129 , 1169 , 1209 , 1249 , 201 , 201 , 201 , 128 , 168 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , ], +[ 654 , 694 , 734 , 774 , 1090 , 1130 , 1170 , 1210 , 1250 , 899 , 201 , 250 , 129 , 169 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , ], +[ 655 , 695 , 735 , 775 , 815 , 855 , 970 , 899 , 899 , 201 , 201 , 970 , 130 , 170 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , ], +[ 656 , 696 , 736 , 776 , 208 , 856 , 970 , 936 , 249 , 250 , 970 , 970 , 970 , 249 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , ], +[ 657 , 697 , 737 , 777 , 817 , 857 , 970 , 208 , 201 , 1004 , 1044 , 1084 , 1204 , 1244 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , ], +[ 658 , 698 , 738 , 778 , 818 , 858 , 970 , 938 , 978 , 1005 , 1045 , 1165 , 1205 , 1245 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , ], +[ 659 , 699 , 739 , 779 , 208 , 859 , 208 , 939 , 979 , 1006 , 1046 , 1166 , 1206 , 1246 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , ], +[ 660 , 700 , 740 , 780 , 820 , 860 , 900 , 940 , 980 , 1020 , 1060 , 1100 , 1207 , 1247 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , ], +[ 661 , 701 , 741 , 781 , 821 , 861 , 901 , 941 , 981 , 1021 , 1061 , 1101 , 1208 , 1248 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , ], +[ 662 , 702 , 742 , 782 , 822 , 862 , 902 , 942 , 982 , 1022 , 1062 , 1102 , 1209 , 1249 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , ], +[ 663 , 703 , 743 , 783 , 823 , 863 , 903 , 943 , 983 , 1023 , 1063 , 1207 , 1247 , 1250 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , ], +[ 661 , 701 , 741 , 781 , 821 , 861 , 901 , 941 , 981 , 1021 , 1061 , 1208 , 1248 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , ], +[ 662 , 702 , 742 , 782 , 822 , 862 , 902 , 942 , 982 , 1022 , 1062 , 1209 , 1249 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , ], +[ 663 , 703 , 743 , 783 , 823 , 863 , 903 , 943 , 983 , 1023 , 1063 , 1210 , 1250 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , 241 , ], +], +[ +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 543 , 583 , 623 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 368 , 529 , 569 , 609 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 544 , 584 , 857 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 367 , 368 , 202 , 530 , 570 , 610 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 545 , 585 , 857 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 328 , 368 , 202 , 208 , 530 , 570 , 610 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 546 , 586 , 857 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 329 , 202 , 250 , 202 , 481 , 521 , 561 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 329 , 248 , 202 , 491 , 482 , 522 , 562 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 126 , 166 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 372 , 452 , 492 , 492 , 532 , 572 , 612 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 127 , 167 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 340 , 380 , 420 , 460 , 500 , 540 , 580 , 620 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 341 , 381 , 421 , 461 , 501 , 541 , 581 , 621 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 342 , 382 , 422 , 462 , 502 , 760 , 582 , 248 , 880 , 540 , 580 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 210 , -1 , -1 , -1 , 206 , 246 , -1 , 343 , 383 , 423 , 463 , 503 , 761 , 801 , 841 , 881 , 541 , 581 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 207 , 247 , -1 , 344 , 384 , 424 , 464 , 504 , 762 , 802 , 842 , 882 , 542 , 582 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , 242 , 242 , 242 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 345 , 385 , 425 , 465 , 505 , 763 , 803 , 843 , 883 , 543 , 583 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , 367 , 407 , 447 , 487 , 527 , 567 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 342 , 382 , 422 , 462 , 502 , 764 , 804 , 844 , 884 , 544 , 584 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 488 , 528 , 568 , 608 , 567 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 210 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 343 , 383 , 423 , 463 , 503 , 765 , 805 , 845 , 885 , 545 , 585 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 488 , 528 , 568 , 608 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 344 , 384 , 424 , 464 , 504 , 766 , 806 , 846 , 886 , 546 , 586 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 345 , 385 , 425 , 465 , 505 , 767 , 585 , 847 , 887 , 547 , 587 , -1 , -1 , -1 , -1 , ], +[ 17 , 57 , 97 , 137 , 177 , 217 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 346 , 386 , 426 , 466 , 506 , 546 , 586 , 626 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 18 , 58 , 98 , 138 , 178 , 218 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 347 , 387 , 427 , 467 , 507 , 547 , 587 , 627 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 19 , 59 , 99 , 139 , 179 , 219 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 208 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 20 , 60 , 100 , 140 , 180 , 220 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 209 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 21 , 61 , 101 , 141 , 181 , 221 , -1 , -1 , -1 , -1 , -1 , -1 , 491 , 531 , 571 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 22 , 62 , 102 , 142 , 182 , 222 , -1 , -1 , -1 , -1 , -1 , -1 , 492 , 532 , 572 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 210 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 23 , 63 , 103 , 143 , 183 , 223 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 128 , 168 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 17 , 57 , 97 , 137 , 177 , 217 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 210 , -1 , -1 , -1 , -1 , 129 , 169 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 18 , 58 , 98 , 138 , 178 , 218 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 130 , 170 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 19 , 59 , 99 , 139 , 179 , 219 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 20 , 60 , 100 , 140 , 180 , 220 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 21 , 61 , 101 , 141 , 181 , 221 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 11 , 51 , 91 , 131 , 171 , 211 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 22 , 62 , 102 , 142 , 182 , 222 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 12 , 52 , 92 , 132 , 172 , 212 , 248 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 23 , 63 , 103 , 143 , 183 , 223 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 13 , 53 , 93 , 133 , 173 , 213 , 249 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 386 , 426 , 466 , 506 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 14 , 54 , 94 , 134 , 174 , 214 , 250 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 387 , 427 , 467 , 507 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 340 , 380 , 420 , 460 , 500 , 540 , 580 , 620 , -1 , 15 , 55 , 95 , 135 , 175 , 215 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 341 , 381 , 421 , 461 , 501 , 541 , 581 , 621 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 342 , 382 , 422 , 462 , 502 , 542 , 582 , 622 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 343 , 383 , 423 , 463 , 503 , 543 , 583 , 623 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 8 , 48 , -1 , -1 , -1 , -1 , 344 , 384 , 424 , 464 , 504 , 544 , 584 , 624 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 126 , 166 , -1 , -1 , -1 , 9 , 49 , -1 , -1 , -1 , -1 , 345 , 385 , 425 , 465 , 505 , 545 , 585 , 625 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 127 , 167 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 346 , 386 , 426 , 466 , 506 , 546 , 586 , 626 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 347 , 387 , 427 , 467 , 507 , 547 , 587 , 627 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , 11 , 51 , 91 , 131 , 171 , 211 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , 12 , 52 , 92 , 132 , 172 , 212 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , 13 , 53 , 93 , 133 , 173 , 213 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , 14 , 54 , 94 , 134 , 174 , 214 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , 15 , 55 , 95 , 135 , 175 , 215 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 8 , 48 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , 8 , 48 , -1 , -1 , -1 , -1 , -1 , 9 , 49 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , 9 , 49 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 206 , 246 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 207 , 247 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +],]; + +export const objmap = [ +[ +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +], +[ +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +],]; +export const animatedsprites = [ +{ x: 576, y: 320, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 608, y: 320, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 544, y: 288, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 608, y: 672, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 576, y: 704, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 608, y: 704, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 576, y: 672, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 480, y: 224, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 480, y: 160, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 480, y: 96, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 608, y: 352, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 544, y: 480, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 576, y: 480, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 576, y: 512, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 608, y: 512, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 608, y: 544, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 640, y: 704, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 672, y: 672, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 640, y: 640, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 640, y: 672, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 640, y: 736, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 704, y: 704, w: 32, h: 32, layer: 1, sheet: "gentlesparkle.json", animation: "pixels_large" }, +{ x: 544, y: 352, w: 32, h: 96, layer: 1, sheet: "gentlewaterfall.json", animation: "pixels_large" }, +{ x: 576, y: 352, w: 32, h: 96, layer: 1, sheet: "gentlewaterfall.json", animation: "pixels_large" }, +{ x: 608, y: 384, w: 32, h: 96, layer: 1, sheet: "gentlewaterfall.json", animation: "pixels_large" }, +{ x: 1248, y: 256, w: 208, h: 208, layer: 1, sheet: "windmill.json", animation: "pixels_large" }, +{ x: 1728, y: 160, w: 32, h: 32, layer: 1, sheet: "campfire.json", animation: "pixels_large" }, +{ x: 544, y: 416, w: 32, h: 64, layer: 2, sheet: "gentlesplash.json", animation: "pixels_large" }, +{ x: 576, y: 416, w: 32, h: 64, layer: 2, sheet: "gentlesplash.json", animation: "pixels_large" }, +{ x: 608, y: 448, w: 32, h: 64, layer: 2, sheet: "gentlesplash.json", animation: "pixels_large" }, +]; diff --git a/patches/src/editor/maps/mage3.js b/patches/src/editor/maps/mage3.js new file mode 100644 index 0000000000000000000000000000000000000000..2e6ce9b9af67b0689f51ab8facf409dfa40c4ac4 --- /dev/null +++ b/patches/src/editor/maps/mage3.js @@ -0,0 +1,220 @@ +// Map generated by assettool.jsThu Aug 31 2023 00:08:34 GMT-0700 (Pacific Daylight Time) + +export const tilesetpath = "./tilesets/magecity.png" +export const tiledim = 32 +export const screenxtiles = 8 +export const screenytiles = 44 +export const tilesetpxw = 256 +export const tilesetpxh = 1408 + +export const bgtiles = [ + [ +[ 194 , 194 , 194 , 194 , 194 , 194 , 194 , 194 , 194 , 194 , 194 , 194 , 194 , 194 , 194 , 194 , 194 , 194 , 194 , 194 , 194 , 194 , 194 , 194 , 194 , 194 , 194 , 194 , 194 , 194 , 194 , 194 , 194 , 194 , 194 , 194 , 194 , 194 , 194 , 194 , 194 , 194 , 194 , 194 , 194 , 194 , 194 , 194 , 194 , 194], +[ 193 , 201 , 35 , 43 , 33 , 33 , 33 , 1 , 1 , 1 , 0 , 0 , 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 , 227 , 0 , 0 , 1 , 1 , 1 , 35 , 32 , 40 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 211 , 0 , 211 , 211 , 0 , 0 , 0 , 0 , 0 , 1 , 1 , 1 , 1 , 1], +[ 193 , 201 , 36 , 33 , 33 , 33 , 33 , 1 , 1 , 1 , 0 , 0 , 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 , 227 , 0 , 1 , 1 , 1 , 1 , 36 , 43 , 41 , 264 , 272 , 0 , 211 , 238 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 236 , 0 , 0 , 1 , 1 , 1 , 1 , 1], +[ 193 , 201 , 36 , 33 , 33 , 33 , 33 , 1 , 1 , 1 , 0 , 0 , 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 , 227 , 0 , 1 , 1 , 1 , 1 , 36 , 33 , 41 , 265 , 273 , 238 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 227 , 0 , 1 , 1 , 1 , 1 , 1], +[ 193 , 201 , 36 , 33 , 33 , 33 , 33 , 1 , 1 , 1 , 0 , 0 , 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 , 227 , 0 , 1 , 1 , 1 , 1 , 36 , 326 , 334 , 235 , 238 , 1 , 1 , 1 , 1 , 1 , 1 , 72 , 88 , 1 , 1 , 1 , 1 , 0 , 0 , 1 , 1 , 1 , 1 , 1], +[ 193 , 201 , 36 , 33 , 33 , 33 , 33 , 1 , 1 , 1 , 1 , 236 , 211 , 211 , 211 , 211 , 211 , 238 , 1 , 1 , 227 , 0 , 0 , 1 , 1 , 1 , 37 , 34 , 42 , 235 , 1 , 1 , 1 , 1 , 1 , 1 , 72 , 75 , 83 , 88 , 1 , 1 , 1 , 236 , 0 , 1 , 1 , 1 , 1 , 1], +[ 193 , 201 , 36 , 43 , 33 , 33 , 33 , 1 , 1 , 1 , 1 , 1 , 9 , 1 , 1 , 1 , 1 , 1 , 9 , 1 , 236 , 211 , 211 , 211 , 211 , 211 , 211 , 211 , 211 , 238 , 1 , 1 , 1 , 1 , 1 , 72 , 75 , 81 , 81 , 83 , 88 , 1 , 1 , 1 , 236 , 1 , 1 , 1 , 1 , 1], +[ 193 , 201 , 36 , 33 , 33 , 33 , 33 , 1 , 1 , 1 , 1 , 237 , 237 , 219 , 219 , 328 , 344 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 74 , 76 , 81 , 81 , 84 , 90 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1], +[ 193 , 201 , 36 , 33 , 33 , 33 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 211 , 238 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 237 , 239 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 74 , 76 , 84 , 90 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1], +[ 193 , 201 , 36 , 33 , 33 , 33 , 0 , 0 , 0 , 0 , 0 , 0 , 235 , 1 , 346 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 237 , 219 , 219 , 239 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 74 , 90 , 1 , 1 , 1 , 1 , 1 , 237 , 0 , 0 , 0 , 0 , 1], +[ 193 , 201 , 36 , 33 , 33 , 33 , 0 , 0 , 0 , 292 , 0 , 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 0 , 0 , 0 , 0 , 219 , 239 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 237 , 0 , 0 , 0 , 0 , 0 , 1], +[ 193 , 201 , 36 , 33 , 33 , 33 , 0 , 292 , 0 , 0 , 0 , 211 , 238 , 1 , 9 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 212 , 220 , 228 , 211 , 239 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 227 , 0 , 0 , 0 , 0 , 0 , 1], +[ 193 , 201 , 36 , 33 , 33 , 33 , 0 , 0 , 0 , 0 , 238 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 237 , 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 212 , 220 , 228 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 237 , 0 , 0 , 0 , 0 , 0 , 0 , 1], +[ 193 , 201 , 36 , 33 , 33 , 33 , 0 , 0 , 0 , 292 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 237 , 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 , 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 212 , 220 , 220 , 228 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1], +[ 193 , 201 , 36 , 33 , 33 , 33 , 0 , 0 , 0 , 0 , 1 , 9 , 1 , 1 , 1 , 1 , 1 , 227 , 0 , 0 , 1 , 1 , 211 , 211 , 211 , 211 , 211 , 211 , 211 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 213 , 221 , 221 , 229 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1], +[ 185 , 195 , 200 , 35 , 32 , 32 , 40 , 0 , 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 236 , 227 , 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 41 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 214 , 222 , 222 , 230 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1], +[ 193 , 193 , 195 , 200 , 35 , 32 , 32 , 40 , 0 , 0 , 1 , 1 , 9 , 1 , 346 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 41 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1], +[ 40 , 193 , 193 , 201 , 36 , 33 , 33 , 41 , 0 , 235 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 41 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1], +[ 42 , 193 , 193 , 201 , 45 , 43 , 33 , 41 , 0 , 0 , 1 , 1 , 72 , 80 , 80 , 80 , 88 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 41 , 0 , 0 , 0 , 0 , 0 , 0 , 238 , 1 , 212 , 220 , 220 , 228 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 1 , 1], +[ 185 , 193 , 196 , 202 , 37 , 34 , 34 , 42 , 0 , 238 , 1 , 1 , 73 , 81 , 81 , 81 , 89 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 41 , 0 , 238 , 1 , 1 , 1 , 1 , 1 , 1 , 213 , 221 , 221 , 229 , 266 , 274 , 282 , 0 , 0 , 0 , 1 , 1 , 1], +[ 185 , 196 , 202 , 34 , 34 , 34 , 42 , 1 , 1 , 1 , 1 , 72 , 75 , 81 , 81 , 81 , 83 , 88 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 214 , 222 , 222 , 230 , 267 , 275 , 283 , 0 , 0 , 0 , 1 , 1 , 1], +[ 185 , 201 , 34 , 34 , 34 , 42 , 1 , 1 , 1 , 1 , 1 , 74 , 76 , 81 , 81 , 81 , 84 , 90 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 236 , 268 , 276 , 284 , 0 , 0 , 0 , 1 , 1 , 1], +[ 193 , 201 , 36 , 326 , 334 , 1 , 1 , 237 , 219 , 239 , 1 , 1 , 73 , 81 , 81 , 81 , 89 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 236 , 277 , 285 , 0 , 0 , 0 , 1 , 1 , 1], +[ 193 , 201 , 36 , 326 , 334 , 1 , 1 , 227 , 0 , 238 , 1 , 1 , 74 , 82 , 82 , 82 , 90 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 237 , 219 , 239 , 1 , 1 , 227 , 0 , 0 , 0 , 0 , 1 , 1 , 1], +[ 193 , 201 , 35 , 32 , 40 , 40 , 1 , 236 , 238 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 237 , 219 , 52 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 0 , 0 , 0 , 1 , 1 , 0 , 0 , 0 , 0 , 0 , 1 , 1 , 1], +[ 193 , 195 , 200 , 35 , 32 , 40 , 1 , 1 , 1 , 1 , 9 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 346 , 1 , 1 , 1 , 1 , 1 , 0 , 0 , 52 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 0 , 0 , 0 , 1 , 1 , 0 , 0 , 0 , 0 , 0 , 1 , 1 , 1], +[ 193 , 193 , 195 , 200 , 32 , 32 , 32 , 40 , 239 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 227 , 0 , 52 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 236 , 0 , 238 , 1 , 1 , 227 , 0 , 0 , 0 , 0 , 1 , 1 , 1], +[ 193 , 193 , 193 , 201 , 33 , 33 , 33 , 41 , 235 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 9 , 1 , 0 , 0 , 52 , 0 , 0 , 219 , 0 , 239 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 227 , 339 , 341 , 0 , 0 , 1 , 1 , 1], +[ 193 , 193 , 193 , 201 , 38 , 46 , 33 , 41 , 0 , 239 , 1 , 1 , 1 , 1 , 1 , 346 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 227 , 0 , 52 , 0 , 0 , 0 , 0 , 235 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 0 , 337 , 337 , 0 , 1 , 1 , 1 , 1], +[ 40 , 193 , 193 , 201 , 33 , 33 , 33 , 41 , 0 , 235 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 0 , 0 , 52 , 0 , 0 , 0 , 0 , 235 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 0 , 331 , 337 , 0 , 1 , 1 , 1 , 1], +[ 42 , 193 , 193 , 201 , 33 , 33 , 33 , 41 , 0 , 238 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 227 , 0 , 52 , 0 , 0 , 0 , 0 , 0 , 219 , 239 , 1 , 1 , 1 , 1 , 1 , 237 , 0 , 339 , 337 , 0 , 1 , 1 , 1 , 1], +[ 193 , 193 , 196 , 202 , 34 , 39 , 47 , 47 , 238 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 9 , 1 , 1 , 1 , 1 , 0 , 0 , 52 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 339 , 331 , 0 , 0 , 0 , 0 , 0], +[ 196 , 194 , 202 , 34 , 34 , 48 , 56 , 238 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 237 , 292 , 0 , 52 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 339 , 331 , 0 , 0 , 0 , 0 , 0], +[ 201 , 35 , 32 , 40 , 0 , 49 , 57 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 211 , 285 , 293 , 0 , 0 , 52 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 339 , 341 , 339 , 0 , 0 , 0 , 339 , 331 , 0 , 0 , 0 , 0 , 0], +[ 201 , 36 , 33 , 41 , 0 , 49 , 57 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 285 , 293 , 0 , 0 , 0 , 52 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 331 , 340 , 340 , 0 , 0 , 0 , 340 , 340 , 0 , 0 , 0 , 0 , 0], +[ 201 , 36 , 33 , 41 , 0 , 49 , 57 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 227 , 292 , 227 , 0 , 0 , 52 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 340 , 331 , 340 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0], +[ 201 , 36 , 33 , 41 , 0 , 49 , 57 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 227 , 293 , 0 , 0 , 0 , 52 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 340 , 340 , 331 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0], +[ 201 , 36 , 43 , 41 , 0 , 49 , 57 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 237 , 0 , 219 , 219 , 219 , 0 , 0 , 0 , 0 , 0 , 0 , 52 , 0 , 0 , 0 , 0 , 0 , 0 , 340 , 340 , 340 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0], +[ 201 , 36 , 33 , 41 , 48 , 57 , 57 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 227 , 264 , 272 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 52 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0], +[ 201 , 38 , 46 , 41 , 49 , 57 , 57 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 227 , 265 , 273 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 52 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0], +[ 201 , 36 , 33 , 41 , 49 , 57 , 57 , 239 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 266 , 274 , 0 , 0 , 52 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0], +[ 201 , 36 , 33 , 41 , 49 , 57 , 57 , 0 , 239 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 227 , 0 , 0 , 0 , 0 , 0 , 0 , 267 , 275 , 0 , 0 , 52 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0], +[ 201 , 36 , 33 , 41 , 49 , 57 , 57 , 0 , 235 , 239 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 237 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 268 , 276 , 0 , 0 , 52 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0], +[ 201 , 36 , 33 , 41 , 49 , 57 , 57 , 0 , 0 , 0 , 219 , 219 , 219 , 0 , 0 , 219 , 219 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 277 , 0 , 0 , 52 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0], +[ 201 , 36 , 33 , 41 , 49 , 57 , 57 , 0 , 0 , 0 , 0 , 264 , 272 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , -1 , -1 , -1 , 35 , 32 , 32 , 40 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0], +[ 201 , 36 , 33 , 41 , 49 , 57 , 57 , 0 , 0 , 0 , 0 , 265 , 273 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 190 , 198 , 206 , 1 , 36 , 33 , 33 , 41 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0], +[ 201 , 36 , 33 , 41 , 49 , 57 , 57 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 1 , 36 , 33 , 33 , 41 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0], +[ 201 , 36 , 33 , 41 , 49 , 57 , 57 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 1 , 36 , 33 , 33 , 41 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0], +[ 201 , 36 , 33 , 41 , 49 , 57 , 57 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 1 , 36 , 33 , 33 , 41 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0], +[ 192 , 192 , 192 , 192 , 192 , 192 , 192 , 192 , 192 , 192 , 192 , 192 , 192 , 192 , 192 , 192 , 192 , 192 , 192 , 192 , 192 , 192 , 192 , 192 , 192 , 192 , 192 , 192 , 192 , 192 , 192 , 192 , 192 , 192 , 192 , 192 , 192 , 192 , 192 , 192 , 192 , 192 , 192 , 192 , 192 , 192 , 192 , 192 , 192 , 192], +], +[ +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 184 , 200 , 35 , 32 , 40], +[ -1 , -1 , -1 , -1 , 189 , 197 , 205 , 212 , 220 , 228 , -1 , -1 , -1 , 190 , 198 , 206 , 212 , 220 , 228 , -1 , -1 , 193 , 193 , 193 , 193 , 201 , 168 , 176 , 19 , 27 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 185 , 201 , 36 , 33 , 41], +[ -1 , -1 , -1 , -1 , 190 , 198 , 206 , 213 , 221 , 229 , -1 , -1 , -1 , 190 , 198 , 206 , 213 , 221 , 229 , -1 , -1 , 185 , 193 , 193 , 193 , 201 , 169 , 177 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 185 , 201 , 36 , 33 , 41], +[ -1 , -1 , -1 , -1 , 190 , 198 , 206 , 213 , 221 , 229 , -1 , -1 , -1 , 190 , 188 , 206 , 213 , 221 , 229 , -1 , -1 , 185 , 193 , 193 , 193 , 201 , 232 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 185 , 201 , 36 , 33 , 41], +[ -1 , -1 , -1 , -1 , 190 , 198 , 206 , 214 , 222 , 230 , -1 , -1 , -1 , 191 , 199 , 207 , 214 , 222 , 230 , -1 , -1 , 185 , 193 , 193 , 193 , 201 , 232 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 185 , 201 , 36 , 33 , 41], +[ -1 , -1 , -1 , -1 , 190 , 198 , 203 , 205 , 212 , 220 , 228 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 186 , 194 , 194 , 194 , 202 , 232 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 185 , 201 , 36 , 33 , 41], +[ -1 , -1 , -1 , -1 , 190 , 198 , 198 , 206 , 213 , 221 , 229 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 3 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 60 , 68 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 186 , 202 , 37 , 34 , 42], +[ -1 , -1 , -1 , -1 , 191 , 199 , 199 , 207 , 214 , 222 , 230 , -1 , -1 , 105 , 113 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 61 , 69 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1], +[ -1 , -1 , 116 , 124 , 132 , -1 , 263 , 263 , 263 , -1 , 270 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 62 , 70 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1], +[ -1 , -1 , 117 , 125 , 133 , 130 , 138 , 146 , -1 , -1 , 327 , 263 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 96 , 104 , 112 , -1 , 93 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 184 , 200 , 35 , 32 , 40], +[ -1 , -1 , 118 , 126 , 134 , 131 , 139 , 147 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 189 , 197 , 197 , 205 , 212 , 220 , 228 , 208 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 185 , 201 , 36 , 33 , 41], +[ -1 , -1 , 119 , 127 , 135 , -1 , -1 , 233 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 190 , 198 , 198 , 206 , 209 , 209 , 209 , 209 , -1 , -1 , -1 , 19 , 27 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 185 , 201 , 36 , 33 , 41], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 318 , 263 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 190 , 198 , 198 , 206 , 209 , 209 , 209 , 209 , 189 , 197 , 205 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 185 , 201 , 36 , 33 , 41], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 319 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 191 , 199 , 199 , 207 , 212 , 220 , 228 , 208 , 190 , 198 , 206 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 316 , 316 , -1 , -1 , -1 , -1 , -1 , 185 , 201 , 36 , 33 , 41], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , 263 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 189 , 197 , 205 , 212 , 220 , 228 , -1 , -1 , -1 , 191 , 199 , 207 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 148 , 153 , 161 , -1 , -1 , -1 , -1 , -1 , 185 , 201 , 36 , 33 , 41], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 190 , 198 , 206 , 213 , 221 , 229 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 148 , 148 , 148 , 19 , 27 , -1 , -1 , -1 , 185 , 201 , 36 , 33 , 41], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , 96 , 104 , 112 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 190 , 198 , 206 , 213 , 221 , 229 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 185 , 201 , 36 , 33 , 41], +[ -1 , -1 , -1 , -1 , 153 , 161 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 190 , 198 , 206 , 213 , 326 , 334 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 185 , 201 , 36 , 33 , 41], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 21 , 29 , -1 , -1 , 102 , 102 , 102 , 102 , -1 , -1 , 190 , 198 , 206 , 213 , 221 , 229 , -1 , -1 , -1 , -1 , -1 , -1 , 189 , 197 , 197 , 197 , 197 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 185 , 201 , 36 , 33 , 41], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , 21 , 29 , -1 , -1 , -1 , 102 , 102 , 102 , 102 , -1 , -1 , 190 , 198 , 206 , 213 , 221 , 229 , -1 , -1 , -1 , -1 , 60 , 68 , 190 , 198 , 198 , 198 , 198 , -1 , -1 , -1 , 153 , 161 , -1 , -1 , -1 , -1 , -1 , 185 , 201 , 36 , 33 , 41], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 190 , 198 , 206 , 213 , 221 , 229 , -1 , -1 , -1 , -1 , 61 , 69 , 191 , 199 , 199 , 199 , 199 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 185 , 201 , 36 , 33 , 41], +[ -1 , -1 , 154 , 162 , 66 , 16 , 24 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 190 , 198 , 206 , 213 , 221 , 229 , -1 , -1 , -1 , -1 , 61 , 69 , 208 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 270 , -1 , -1 , -1 , -1 , 185 , 201 , 36 , 33 , 41], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 2 , 10 , -1 , -1 , 102 , 102 , 102 , 102 , -1 , -1 , 191 , 199 , 207 , 214 , 222 , 230 , -1 , -1 , -1 , -1 , 61 , 69 , 208 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 262 , -1 , 185 , 201 , 36 , 33 , 41], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , 10 , -1 , 11 , -1 , -1 , 102 , 102 , 102 , 102 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 61 , 69 , 208 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 185 , 201 , 36 , 33 , 41], +[ -1 , -1 , 154 , 162 , 66 , 16 , 24 , -1 , -1 , -1 , -1 , -1 , 102 , 102 , 102 , 102 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 62 , 70 , 208 , -1 , -1 , -1 , -1 , 122 , 130 , 138 , 146 , -1 , -1 , -1 , -1 , 270 , -1 , 185 , 201 , 36 , 33 , 41], +[ 9 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 123 , 131 , 139 , 147 , -1 , -1 , -1 , -1 , -1 , -1 , 185 , 201 , 36 , 33 , 41], +[ -1 , -1 , -1 , -1 , -1 , -1 , 209 , 209 , 209 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 185 , 201 , 36 , 33 , 41], +[ -1 , -1 , -1 , -1 , -1 , -1 , 324 , 324 , 209 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 185 , 201 , 36 , 33 , 41], +[ -1 , -1 , -1 , -1 , -1 , -1 , 300 , 308 , 209 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 185 , 201 , 36 , 33 , 41], +[ -1 , -1 , -1 , -1 , 153 , 160 , 209 , 209 , 209 , 209 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 185 , 201 , 36 , 33 , 41], +[ -1 , -1 , -1 , -1 , -1 , -1 , 209 , 209 , 209 , 209 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 185 , 201 , 36 , 33 , 41], +[ -1 , -1 , -1 , -1 , -1 , -1 , 100 , 100 , 209 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 120 , 128 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 7 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 185 , 201 , 36 , 33 , 41], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , 209 , 209 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 121 , 129 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 185 , 201 , 36 , 33 , 41], +[ -1 , -1 , -1 , 19 , 27 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 122 , 130 , 138 , 146 , -1 , -1 , -1 , -1 , -1 , -1 , 5 , 13 , -1 , -1 , 314 , 322 , -1 , -1 , 347 , 347 , -1 , -1 , -1 , -1 , 185 , 201 , 36 , 33 , 41], +[ -1 , -1 , -1 , -1 , 10 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 123 , 131 , 139 , 147 , -1 , -1 , -1 , -1 , -1 , -1 , 6 , 14 , -1 , -1 , 315 , 323 , -1 , -1 , 347 , 347 , -1 , -1 , -1 , -1 , 185 , 201 , 36 , 33 , 41], +[ -1 , -1 , -1 , 19 , 27 , -1 , -1 , 22 , 30 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 7 , -1 , 314 , 322 , -1 , -1 , 347 , 347 , -1 , -1 , -1 , -1 , 185 , 201 , 36 , 33 , 41], +[ -1 , -1 , -1 , -1 , 11 , -1 , -1 , 23 , 31 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 7 , -1 , -1 , -1 , 315 , 323 , -1 , -1 , 347 , 347 , -1 , -1 , -1 , -1 , 185 , 201 , 36 , 33 , 41], +[ -1 , -1 , -1 , 19 , 27 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 314 , 322 , -1 , -1 , 347 , 347 , -1 , -1 , -1 , -1 , 185 , 201 , 36 , 33 , 41], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 347 , 347 , -1 , -1 , 347 , 347 , -1 , -1 , -1 , -1 , 185 , 201 , 36 , 33 , 41], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 343 , 351 , -1 , -1 , 185 , 201 , 36 , 33 , 41], +[ -1 , -1 , -1 , -1 , -1 , -1 , 53 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 189 , 197 , 197 , 197 , 205 , 212 , 220 , 228 , -1 , -1 , 185 , 201 , 36 , 33 , 41], +[ -1 , -1 , -1 , -1 , -1 , -1 , 53 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 189 , 197 , 205 , 213 , 221 , 198 , 206 , 213 , 221 , 229 , -1 , -1 , 185 , 201 , 36 , 33 , 41], +[ -1 , -1 , -1 , -1 , -1 , -1 , 53 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 190 , 198 , 206 , 213 , 221 , 198 , 206 , 213 , 221 , 229 , -1 , -1 , 185 , 201 , 36 , 33 , 41], +[ -1 , -1 , -1 , -1 , -1 , -1 , 53 , -1 , -1 , -1 , -1 , 343 , 351 , -1 , -1 , 20 , 28 , 333 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 190 , 198 , 206 , 213 , 221 , 198 , 206 , 213 , 221 , 229 , -1 , -1 , 185 , 201 , 36 , 33 , 41], +[ -1 , -1 , -1 , -1 , -1 , -1 , 53 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 12 , -1 , -1 , 341 , 339 , 339 , -1 , 189 , 197 , 197 , 205 , 208 , 208 , -1 , -1 , -1 , -1 , 190 , 198 , 206 , 213 , 221 , 198 , 206 , 213 , 221 , 229 , -1 , -1 , 185 , 201 , 36 , 33 , 41], +[ -1 , -1 , -1 , -1 , -1 , -1 , 53 , -1 , -1 , -1 , -1 , -1 , -1 , 350 , -1 , 20 , 28 , 101 , -1 , 337 , 331 , 337 , -1 , 190 , 198 , 198 , 206 , 208 , 326 , 334 , 63 , 71 , -1 , 190 , 198 , 206 , 213 , 221 , 198 , 206 , 213 , 221 , 229 , -1 , -1 , 185 , 201 , 36 , 33 , 41], +[ -1 , -1 , -1 , -1 , -1 , -1 , 53 , -1 , -1 , -1 , -1 , -1 , -1 , 343 , 351 , -1 , -1 , -1 , -1 , 337 , 337 , 331 , -1 , 190 , 198 , 198 , 206 , 208 , 300 , 308 , -1 , -1 , -1 , 191 , 199 , 207 , 213 , 221 , 198 , 206 , 326 , 334 , 63 , 71 , -1 , 185 , 201 , 36 , 33 , 41], +[ -1 , -1 , -1 , -1 , -1 , -1 , 53 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 331 , 337 , 331 , -1 , 190 , 198 , 198 , 206 , 152 , 208 , 309 , -1 , -1 , -1 , -1 , -1 , 191 , 199 , 199 , 199 , 207 , 214 , 222 , 230 , -1 , -1 , 185 , 201 , 36 , 33 , 41], +[ -1 , -1 , -1 , -1 , -1 , -1 , 53 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 340 , 340 , 340 , -1 , 190 , 198 , 198 , 206 , 208 , 152 , 302 , 19 , 27 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 185 , 201 , 36 , 33 , 41], +[ -1 , -1 , -1 , -1 , -1 , -1 , 53 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 190 , 198 , 198 , 198 , 208 , 208 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 186 , 202 , 37 , 34 , 42], +],]; + +export const objmap = [ +[ +[ 94 , 94 , 94 , 94 , 94 , 94 , 94 , 94 , 94 , 94 , 94 , 94 , 94 , 94 , 94 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 94 , 94 , 94 , 94 , 94 , 94 , 94 , 94 , 94 , 94 , 94 , 94 , 94 , 94 , 94 , 94 , 94 , 94 , 200 , 243 , 251 , 259], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 116 , 124 , 132 , -1 , -1 , 94 , 94 , 94 , 172 , 180 , 19 , 27 , -1 , 200 , 35 , -1 , -1 , -1 , -1 , -1 , 94 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 185 , 201 , 244 , 252 , 260], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 117 , 125 , 133 , 141 , 149 , 94 , 94 , 94 , 173 , 181 , -1 , -1 , -1 , 201 , 36 , -1 , -1 , -1 , -1 , 172 , 180 , -1 , -1 , -1 , -1 , -1 , 105 , 113 , -1 , 105 , 113 , -1 , 105 , 113 , -1 , -1 , -1 , 185 , 201 , 245 , 253 , 261], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 118 , 126 , 134 , 142 , 150 , 94 , 94 , 94 , -1 , 326 , 334 , -1 , -1 , 202 , 37 , -1 , -1 , -1 , -1 , 173 , 181 , -1 , -1 , 105 , 113 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 185 , 201 , 152 , 240 , 248], +[ -1 , -1 , -1 , -1 , 159 , 167 , -1 , -1 , 119 , 127 , 135 , 143 , -1 , 94 , 94 , 94 , 97 , 136 , 94 , -1 , -1 , 94 , 94 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 98 , 106 , 114 , -1 , -1 , -1 , 185 , 201 , -1 , 241 , 249], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 154 , 162 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 94 , 94 , 94 , 94 , 94 , 94 , 94 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 185 , 201 , 152 , 242 , 250], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 326 , 334 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 98 , 106 , 114 , -1 , -1 , -1 , 122 , 130 , 138 , 146 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 186 , 202 , 37 , 34 , 42], +[ -1 , -1 , -1 , -1 , 93 , 93 , 93 , 93 , 93 , 154 , 162 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 123 , 131 , 139 , 147 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1], +[ 252 , 251 , -1 , 243 , 251 , 93 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1], +[ 252 , 252 , 260 , 244 , 252 , 260 , -1 , 93 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 98 , 106 , 114 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 98 , 106 , 114 , -1 , -1 , -1 , 184 , 200 , 304 , 32 , 40], +[ 253 , 253 , 261 , 245 , 253 , 261 , -1 , 93 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 189 , 197 , 197 , 205 , -1 , -1 , 19 , 27 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 185 , 201 , 152 , -1 , 240], +[ -1 , 240 , 248 , -1 , -1 , 93 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 190 , -1 , -1 , 197 , 197 , 197 , 197 , 197 , 205 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 185 , 201 , -1 , -1 , 241], +[ -1 , 241 , 249 , 257 , -1 , 93 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 190 , -1 , -1 , 199 , 199 , 199 , 199 , 199 , 207 , 197 , 205 , 97 , 136 , -1 , -1 , 105 , 113 , -1 , 105 , 113 , -1 , 105 , 113 , -1 , -1 , -1 , 185 , 201 , 152 , -1 , 242], +[ -1 , 242 , 250 , 258 , -1 , 93 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 191 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 197 , 197 , 197 , 197 , 197 , 197 , 197 , 205 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 185 , 201 , -1 , -1 , -1], +[ -1 , -1 , -1 , -1 , -1 , 93 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 189 , 197 , 205 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 199 , 199 , 199 , 199 , 199 , 199 , 198 , 206 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 185 , 201 , 152 , 243 , 251], +[ -1 , -1 , -1 , -1 , -1 , -1 , 93 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 190 , 198 , 206 , 153 , 161 , -1 , -1 , -1 , -1 , -1 , 41 , -1 , -1 , -1 , -1 , -1 , -1 , 198 , 206 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 185 , 201 , -1 , 244 , 252], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , 93 , 93 , 93 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 190 , 198 , 206 , -1 , -1 , -1 , -1 , -1 , 120 , 128 , 41 , -1 , -1 , -1 , -1 , -1 , -1 , 198 , 206 , 197 , 197 , 197 , 197 , 197 , 197 , 197 , 197 , 185 , 201 , 152 , 245 , 253], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , 93 , 93 , 93 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 190 , 198 , 206 , -1 , -1 , -1 , -1 , -1 , 121 , 129 , 41 , -1 , -1 , -1 , -1 , -1 , -1 , 198 , 206 , 199 , 199 , 199 , 199 , 199 , 199 , 199 , 199 , 185 , 201 , -1 , -1 , -1], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , 93 , 93 , 93 , -1 , -1 , -1 , -1 , -1 , -1 , 105 , 113 , 190 , 198 , 206 , -1 , -1 , 116 , 124 , 132 , -1 , -1 , 41 , -1 , 189 , 197 , 197 , 197 , 197 , 198 , 206 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 185 , 201 , 152 , 251 , 251259], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , 93 , 93 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 190 , 198 , 206 , 153 , 161 , 117 , 125 , 133 , 141 , 149 , -1 , -1 , 190 , 198 , 198 , 198 , 198 , 198 , 206 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 185 , 201 , -1 , 252 , 252260], +[ -1 , -1 , -1 , -1 , -1 , -1 , 93 , -1 , -1 , -1 , -1 , -1 , 122 , 130 , 138 , 146 , -1 , -1 , 190 , 198 , 206 , -1 , -1 , 118 , 126 , 134 , 142 , 150 , -1 , -1 , 191 , 199 , 199 , 199 , 199 , 199 , 207 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 185 , 201 , 152 , 253 , 253261], +[ -1 , -1 , 93 , 94 , 93 , 93 , -1 , -1 , -1 , -1 , -1 , -1 , 123 , 131 , 139 , 147 , -1 , -1 , 190 , 198 , 206 , 153 , 161 , 119 , 127 , 135 , 143 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 185 , 201 , -1 , -1 , 240], +[ -1 , -1 , 93 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 191 , 199 , 207 , 214 , 222 , 230 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 185 , 201 , 152 , -1 , 241], +[ -1 , -1 , 93 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 105 , 113 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 243 , 251 , -1 , -1 , -1 , -1 , 96 , 104 , 112 , -1 , -1 , 185 , 201 , -1 , -1 , 242], +[ -1 , -1 , 93 , 93 , 94 , 93 , 93 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 244 , 252 , 260 , -1 , -1 , -1 , -1 , 314 , 322 , -1 , -1 , 185 , 201 , 152 , -1 , 240], +[ -1 , -1 , -1 , -1 , -1 , 93 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 96 , 104 , 112 , 41 , -1 , -1 , -1 , -1 , -1 , 245 , 253 , 261 , -1 , -1 , -1 , -1 , 315 , 323 , -1 , -1 , 185 , 201 , -1 , -1 , 241], +[ -1 , -1 , -1 , -1 , -1 , -1 , 94 , 21 , 29 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 41 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 314 , 322 , -1 , -1 , 185 , 201 , 152 , -1 , 242], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 21 , 29 , -1 , -1 , -1 , 116 , 124 , 132 , 140 , 105 , 113 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 41 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 315 , 323 , -1 , -1 , 185 , 201 , -1 , 240 , 248], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , 94 , -1 , -1 , -1 , -1 , -1 , 117 , 125 , 133 , 141 , 149 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 41 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 314 , 322 , -1 , -1 , 185 , 201 , 152 , 241 , 249], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , 94 , -1 , -1 , -1 , -1 , -1 , 118 , 126 , 134 , 142 , 150 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 41 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 315 , 323 , -1 , -1 , 185 , 201 , -1 , 242 , 250], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , 94 , -1 , -1 , -1 , -1 , -1 , 119 , 127 , 135 , 143 , 105 , 113 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 41 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 314 , 322 , -1 , -1 , 185 , 201 , 152 , -1 , 243], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , 96 , 104 , 112 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 41 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 185 , 201 , -1 , -1 , 244], +[ -1 , -1 , -1 , -1 , -1 , 48 , 56 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 240 , 248 , -1 , -1 , -1 , -1 , -1 , 41 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 185 , 201 , 152 , -1 , 245], +[ -1 , -1 , -1 , -1 , -1 , 49 , 57 , 19 , 27 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 241 , 249 , 257 , -1 , -1 , -1 , -1 , -1 , 41 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 185 , 201 , -1 , -1 , 243], +[ -1 , -1 , -1 , -1 , -1 , 49 , 57 , 16 , 24 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 242 , 250 , -1 , -1 , -1 , -1 , -1 , -1 , 41 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 185 , 201 , 152 , -1 , 244], +[ -1 , -1 , -1 , -1 , -1 , 49 , 57 , -1 , -1 , -1 , -1 , -1 , -1 , 15 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 41 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 185 , 201 , -1 , -1 , 245], +[ -1 , -1 , -1 , -1 , -1 , 49 , 57 , -1 , -1 , -1 , 15 , -1 , 5 , 13 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 41 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 185 , 201 , 152 , -1 , -1], +[ -1 , -1 , -1 , -1 , -1 , 49 , 57 , -1 , -1 , -1 , -1 , -1 , 6 , 14 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 96 , 104 , 112 , 41 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 185 , 201 , -1 , -1 , 243], +[ -1 , -1 , -1 , -1 , 48 , 57 , 57 , -1 , -1 , -1 , 5 , 13 , 15 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 314 , 322 , 41 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 185 , 201 , 152 , -1 , 244], +[ -1 , -1 , -1 , -1 , 49 , 57 , 19 , 27 , -1 , -1 , 6 , 14 , -1 , 15 , -1 , -1 , 22 , 30 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 315 , 323 , 41 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 185 , 201 , -1 , -1 , 245], +[ -1 , -1 , -1 , -1 , 49 , 57 , 57 , -1 , -1 , -1 , 15 , -1 , -1 , -1 , -1 , -1 , 23 , 31 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 314 , 322 , 41 , -1 , -1 , -1 , -1 , 189 , 197 , 197 , 197 , 205 , 212 , 220 , 228 , -1 , -1 , 185 , 201 , 152 , -1 , 243], +[ -1 , -1 , -1 , -1 , 49 , 57 , 57 , 20 , 28 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 16 , 24 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 315 , 323 , 41 , -1 , -1 , 189 , 197 , 205 , -1 , -1 , -1 , -1 , 171 , 301 , 229 , -1 , -1 , 185 , 201 , -1 , -1 , 244], +[ -1 , -1 , -1 , -1 , 49 , 57 , 57 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 19 , 27 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 350 , 41 , -1 , -1 , 190 , -1 , -1 , 153 , 161 , -1 , -1 , -1 , -1 , 229 , -1 , -1 , 185 , 201 , 152 , -1 , 245], +[ -1 , -1 , -1 , -1 , 49 , 57 , 57 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 79 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 41 , -1 , -1 , 190 , -1 , -1 , -1 , 17 , 25 , -1 , 171 , 179 , 229 , -1 , -1 , 185 , 201 , -1 , -1 , -1], +[ -1 , -1 , -1 , -1 , 49 , 57 , 57 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 189 , 197 , 197 , 205 , 35 , 32 , 32 , 40 , -1 , -1 , 190 , -1 , -1 , -1 , 18 , 26 , -1 , -1 , -1 , 230 , -1 , -1 , 185 , 201 , 152 , -1 , -1], +[ -1 , -1 , -1 , -1 , 49 , 57 , 57 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 85 , 20 , 28 , -1 , -1 , -1 , -1 , -1 , 190 , 198 , 198 , 206 , -1 , -1 , -1 , -1 , -1 , -1 , 190 , -1 , -1 , 153 , 161 , -1 , -1 , 213 , 221 , 229 , -1 , -1 , 185 , 201 , -1 , -1 , -1], +[ -1 , -1 , -1 , -1 , 49 , 57 , 57 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 190 , 198 , 198 , 206 , -1 , -1 , -1 , 41 , -1 , -1 , 191 , 199 , 207 , -1 , -1 , -1 , 206 , -1 , -1 , -1 , -1 , -1 , 185 , 201 , 152 , -1 , -1], +[ -1 , -1 , -1 , -1 , 49 , 57 , 57 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 190 , 198 , 198 , 206 , -1 , -1 , -1 , 41 , -1 , -1 , -1 , -1 , 191 , 199 , 199 , 199 , 207 , 214 , 222 , 230 , -1 , -1 , 185 , 201 , -1 , -1 , -1], +[ -1 , -1 , -1 , -1 , 50 , 58 , 58 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 190 , 198 , 198 , 206 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 4 , 4 , 185 , 201 , -1 , -1 , -1], +[ 192 , 192 , 192 , 192 , 192 , 192 , 192 , 192 , 192 , 192 , 192 , 192 , 192 , 192 , 192 , 192 , 192 , 192 , 192 , 192 , 192 , 192 , 192 , 192 , 192 , 192 , 192 , 192 , 192 , 192 , 192 , 192 , 192 , 192 , 192 , 192 , 192 , 192 , 192 , 192 , 192 , 192 , 192 , 192 , 192 , 193 , 193 , -1 , 192 , 192192], +], +[ +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 259 , 252 , 252], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 184 , 192 , 216 , 92 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 259 , 244 , 252], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 185 , 193 , 316 , 324 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 259 , 245 , 253], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 186 , 194 , 216 , 324 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 189 , 197 , 205 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 190 , 198 , 197 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 191 , 199 , 199 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 189 , 189 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 159 , 167 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 159 , 167 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 240 , 248 , 256], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 241 , 249 , 257], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 242 , 250 , 258], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 156 , 164 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 243 , 251], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 240 , 248 , 256 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 244 , 252], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 241 , 249 , 257 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 245 , 253], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 243 , 251 , 259 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 242 , 250 , 258 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 244 , 252 , 260 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 245 , 253 , 261 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 243 , 251 , 259 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 244 , 252 , 260 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 245 , 253 , 261 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 243 , 251], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 17 , 25 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 240 , 248 , 256 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 244 , 252], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 18 , 26 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 241 , 249 , 257 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 245 , 253], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 242 , 250 , 258 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 77 , 85 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1], +[ -1 , -1 , -1 , -1 , -1 , -1 , 16 , 24 , 25 , -1 , -1 , -1 , 78 , 86 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , 18 , 26 , -1 , 54 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 55 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 240], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 241], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 20 , 28 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 17 , 25 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 171 , 179 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 242], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 20 , 28 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 18 , 26 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 243 , 251 , 259 , -1 , 243 , 251 , 259 , -1], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 243 , 251 , 244 , 252 , 260 , -1 , 244 , 252 , 260 , -1], +],]; diff --git a/patches/src/editor/maps/serene.js b/patches/src/editor/maps/serene.js new file mode 100644 index 0000000000000000000000000000000000000000..8a4b1214cca71194b95f01c1233557c93d65a93e --- /dev/null +++ b/patches/src/editor/maps/serene.js @@ -0,0 +1,276 @@ +// Map generated by assettool.js [Tue Sep 19 2023 09:13:08 GMT-0700 (Pacific Daylight Time)] + +export const tilesetpath = "./tilesets/Serene.png" +export const tiledim = 32 +export const screenxtiles = 19 +export const screenytiles = 45 +export const tilesetpxw = 608 +export const tilesetpxh = 1440 + +export const bgtiles = [ + [ +[ 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , ], +[ 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , ], +[ 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , ], +[ 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , ], +[ 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , ], +[ 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , ], +[ 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , ], +[ 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , ], +[ 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , ], +[ 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , ], +[ 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , ], +[ 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , ], +[ 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , ], +[ 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , ], +[ 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , ], +[ 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , ], +[ 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , ], +[ 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , ], +[ 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , ], +[ 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , ], +[ 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , ], +[ 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , ], +[ 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , ], +[ 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , ], +[ 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , ], +[ 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , ], +[ 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , ], +[ 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , ], +[ 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , ], +[ 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , ], +[ 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , ], +[ 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , ], +[ 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , ], +[ 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , ], +[ 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , ], +[ 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , ], +[ 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , ], +[ 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , ], +[ 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , ], +[ 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , ], +[ 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , ], +[ 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , ], +[ 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , ], +[ 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , ], +[ 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , ], +[ 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , ], +[ 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , ], +[ 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , ], +[ 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , ], +[ 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , ], +[ 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , ], +[ 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , ], +[ 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , ], +[ 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , ], +[ 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , ], +[ 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , ], +[ 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , ], +[ 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , ], +[ 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , ], +[ 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , ], +[ 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , ], +[ 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , ], +[ 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , ], +[ 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , ], +], +[ +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 11 , 30 , 30 , 30 , 30 , 30 , 30 , 30 , 30 , 30 , 49 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 12 , 31 , 31 , 31 , 31 , 31 , 31 , 31 , 31 , 31 , 50 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , 133 , 152 , 152 , 152 , 152 , 152 , 152 , 152 , 152 , 152 , 190 , -1 , -1 , -1 , 12 , 31 , 31 , 31 , 31 , 31 , 31 , 31 , 31 , 31 , 50 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , 134 , 153 , 153 , 153 , 153 , 153 , 153 , 153 , 153 , 153 , 191 , -1 , -1 , -1 , 12 , 31 , 31 , 31 , 31 , 31 , 31 , 31 , 31 , 31 , 50 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , 134 , 153 , 153 , 153 , 153 , 153 , 153 , 153 , 153 , 153 , 191 , -1 , -1 , -1 , 12 , 31 , 31 , 79 , 98 , 117 , 31 , 31 , 31 , 31 , 50 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , 134 , 153 , 153 , 153 , 153 , 153 , 153 , 153 , 153 , 153 , 191 , -1 , -1 , -1 , 12 , 31 , 31 , 80 , 99 , 118 , 123 , 31 , 31 , 31 , 50 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , 134 , 153 , 153 , 153 , 153 , 153 , 153 , 153 , 153 , 153 , 191 , -1 , -1 , -1 , 12 , 31 , 31 , 84 , 103 , 122 , 31 , 31 , 31 , 31 , 50 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , 134 , 153 , 153 , 153 , 153 , 153 , 43 , 24 , 153 , 153 , 191 , -1 , -1 , -1 , 12 , 31 , 31 , 31 , 31 , 31 , 31 , 31 , 31 , 31 , 50 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , 134 , 153 , 153 , 153 , 153 , 153 , 65 , 26 , 153 , 153 , 191 , -1 , -1 , -1 , 12 , 31 , 31 , 31 , 31 , 31 , 31 , 31 , 31 , 31 , 50 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , 134 , 153 , 153 , 153 , 153 , 153 , 65 , 26 , 153 , 153 , 191 , -1 , -1 , -1 , 12 , 31 , 31 , 31 , 31 , 31 , 31 , 31 , 31 , 31 , 50 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , 134 , 153 , 153 , 153 , 46 , 46 , 65 , 26 , 153 , 153 , 191 , -1 , -1 , -1 , 12 , 31 , 31 , 31 , 31 , 31 , 31 , 31 , 31 , 31 , 50 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , 134 , 153 , 153 , 153 , 153 , 45 , 65 , 27 , 46 , 153 , 191 , -1 , -1 , -1 , 12 , 31 , 31 , 31 , 31 , 31 , 31 , 123 , 31 , 31 , 50 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , 134 , 153 , 153 , 153 , 153 , 153 , 65 , 28 , 47 , 173 , 192 , -1 , -1 , -1 , 12 , 31 , 31 , 31 , 31 , 31 , 31 , 31 , 31 , 31 , 50 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , 134 , 153 , 153 , 153 , 153 , 153 , 41 , 60 , 60 , 174 , 193 , -1 , -1 , -1 , 12 , 31 , 31 , 123 , 31 , 31 , 31 , 31 , 31 , 31 , 50 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , 134 , 153 , 153 , 153 , 153 , 153 , 153 , 153 , 153 , 153 , 191 , -1 , -1 , -1 , 12 , 31 , 31 , 31 , 31 , 31 , 31 , 31 , 31 , 31 , 50 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , 134 , 153 , 153 , 153 , 153 , 153 , 153 , 153 , 153 , 153 , 191 , -1 , -1 , -1 , 12 , 31 , 31 , 31 , 31 , 31 , 31 , 31 , 31 , 31 , 50 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , 134 , 153 , 153 , 153 , 153 , 153 , 153 , 153 , 153 , 153 , 191 , -1 , -1 , -1 , 12 , 31 , 79 , 98 , 117 , 123 , 31 , 31 , 31 , 31 , 50 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , 134 , 153 , 153 , 153 , 153 , 153 , 153 , 153 , 153 , 153 , 191 , -1 , -1 , -1 , 12 , 31 , 80 , 99 , 118 , 31 , 31 , 31 , 31 , 31 , 50 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , 134 , 153 , 153 , 153 , 153 , 153 , 153 , 153 , 153 , 153 , 191 , -1 , -1 , -1 , 12 , 31 , 81 , 100 , 119 , 31 , 31 , 31 , 31 , 31 , 50 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , 134 , 153 , 153 , 153 , 153 , 153 , 153 , 153 , 153 , 153 , 191 , -1 , -1 , -1 , 12 , 31 , 82 , 101 , 120 , 31 , 31 , 31 , 31 , 31 , 50 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , 134 , 153 , 153 , 153 , 153 , 153 , 153 , 153 , 153 , 153 , 191 , -1 , -1 , -1 , 12 , 31 , 83 , 102 , 121 , 31 , 31 , 31 , 31 , 31 , 50 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , 138 , 157 , 157 , 157 , 157 , 157 , 157 , 157 , 157 , 157 , 195 , -1 , -1 , -1 , 12 , 31 , 84 , 103 , 122 , 31 , 123 , 31 , 31 , 31 , 50 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 12 , 31 , 31 , 31 , 31 , 31 , 31 , 31 , 31 , 31 , 50 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 47 , 47 , 73 , 92 , 111 , 130 , 31 , 31 , 31 , 31 , 31 , 31 , 31 , 31 , 31 , 50 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 48 , 55 , 74 , 93 , 112 , 131 , 31 , 31 , 31 , 31 , 31 , 31 , 31 , 31 , 31 , 50 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 44 , 56 , 75 , 94 , 113 , 132 , 31 , 31 , 31 , 31 , 31 , 31 , 31 , 31 , 31 , 50 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 13 , 32 , 32 , 32 , 32 , 32 , 32 , 32 , 32 , 32 , 51 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +],]; + +export const objmap = [ +[ +[ 180 , 199 , 218 , -1 , -1 , 356 , 375 , 394 , -1 , -1 , -1 , -1 , -1 , 286 , 294 , 313 , 332 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 181 , 200 , 219 , -1 , -1 , 357 , 376 , 395 , -1 , -1 , -1 , -1 , -1 , -1 , 295 , 314 , 333 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 182 , 201 , 220 , -1 , -1 , 358 , 377 , 396 , -1 , -1 , -1 , -1 , -1 , -1 , 296 , 315 , 334 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 183 , 202 , 221 , -1 , -1 , 359 , 378 , 397 , -1 , -1 , -1 , -1 , -1 , -1 , 297 , 316 , 335 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 184 , 203 , 222 , -1 , -1 , 360 , 379 , 398 , -1 , -1 , -1 , -1 , -1 , -1 , 298 , 317 , 336 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 185 , 204 , 223 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 186 , 205 , 224 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 187 , 206 , 225 , -1 , -1 , -1 , 294 , 313 , 332 , 356 , 375 , 394 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 180 , 199 , 218 , -1 , -1 , -1 , 295 , 314 , 333 , 357 , 376 , 395 , -1 , 285 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 181 , 200 , 219 , -1 , -1 , -1 , 296 , 315 , 334 , 358 , 377 , 396 , 268 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 232 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 182 , 201 , 220 , -1 , -1 , -1 , 297 , 316 , 335 , 359 , 378 , 397 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 183 , 202 , 221 , -1 , -1 , -1 , 298 , 317 , 336 , 360 , 379 , 398 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 184 , 203 , 222 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 185 , 204 , 223 , -1 , -1 , -1 , 289 , 308 , 308 , 308 , 327 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 186 , 205 , 224 , -1 , -1 , -1 , 290 , 309 , 309 , 309 , 328 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 187 , 206 , 225 , -1 , -1 , -1 , 290 , 309 , 309 , 309 , 312 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 180 , 199 , 218 , -1 , -1 , -1 , 290 , 309 , 309 , 309 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 181 , 200 , 219 , -1 , -1 , -1 , 290 , 309 , 309 , 309 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 182 , 201 , 220 , -1 , -1 , -1 , 290 , 309 , 309 , 309 , 328 , -1 , -1 , -1 , -1 , -1 , 235 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 183 , 202 , 221 , -1 , -1 , -1 , 291 , 310 , 310 , 310 , 329 , -1 , -1 , -1 , -1 , -1 , 235 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 184 , 203 , 222 , -1 , -1 , -1 , -1 , 232 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 235 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 185 , 204 , 223 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 264 , 283 , -1 , -1 , -1 , -1 , 235 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 186 , 205 , 224 , -1 , -1 , -1 , -1 , -1 , 235 , -1 , 265 , 284 , -1 , -1 , -1 , -1 , 235 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 187 , 206 , 225 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 235 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 180 , 199 , 218 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 235 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 181 , 200 , 219 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 235 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 182 , 201 , 220 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 351 , 370 , 389 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 183 , 202 , 221 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 352 , 371 , 390 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 184 , 203 , 222 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 353 , 372 , 391 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 185 , 204 , 223 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 354 , 373 , 392 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 186 , 205 , 224 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 355 , 374 , 393 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 187 , 206 , 225 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 180 , 199 , 218 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 181 , 200 , 219 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 182 , 201 , 220 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 183 , 202 , 221 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 184 , 203 , 222 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 185 , 204 , 223 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 186 , 205 , 224 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 187 , 206 , 225 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 180 , 199 , 218 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 181 , 200 , 219 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 182 , 201 , 220 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 183 , 202 , 221 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 184 , 203 , 222 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 185 , 204 , 223 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 186 , 205 , 224 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 187 , 206 , 225 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 180 , 199 , 218 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 181 , 200 , 219 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 182 , 201 , 220 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 183 , 202 , 221 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 184 , 203 , 222 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 185 , 204 , 223 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 186 , 205 , 224 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 187 , 206 , 225 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 180 , 199 , 218 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 181 , 200 , 219 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 182 , 201 , 220 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 183 , 202 , 221 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 184 , 203 , 222 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 185 , 204 , 223 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 186 , 205 , 224 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ 187 , 206 , 225 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +], +[ +[ -1 , -1 , 351 , 370 , 389 , -1 , -1 , 294 , 313 , 332 , 241 , 260 , 279 , -1 , -1 , -1 , -1 , 264 , 283 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , 352 , 371 , 390 , -1 , -1 , 295 , 314 , 333 , 242 , 261 , 280 , -1 , -1 , -1 , -1 , 265 , 284 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , 353 , 372 , 391 , -1 , -1 , 296 , 315 , 334 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , 354 , 373 , 392 , -1 , -1 , 297 , 316 , 335 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , 355 , 374 , 393 , -1 , -1 , 298 , 317 , 336 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , 414 , 433 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , 415 , 434 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , 416 , 435 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , 417 , 436 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , 713 , 732 , 751 , 770 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , 714 , 733 , 752 , 771 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , 715 , 734 , 753 , 772 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , 716 , 735 , 754 , 773 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , 717 , 736 , 755 , 774 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 356 , 375 , 394 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 357 , 376 , 395 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 358 , 377 , 396 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 359 , 378 , 397 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 360 , 379 , 398 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +[ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , ], +],]; diff --git a/patches/src/editor/se.html b/patches/src/editor/se.html new file mode 100644 index 0000000000000000000000000000000000000000..1de3a60e14d96a5973c584ea9cee629360b112da --- /dev/null +++ b/patches/src/editor/se.html @@ -0,0 +1,92 @@ + + + + + + + + +
+ + + +
+ +
+ + + + + + + + + + + + + + +
+
+
+
+
+
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+ +
+ +
+ + + + + + + + + + + + + + + +
levelComposite
+ + + + + + +
+ 16 + 32 +
+
Load Tileset
+
+ + + + \ No newline at end of file diff --git a/patches/src/editor/se.js b/patches/src/editor/se.js new file mode 100644 index 0000000000000000000000000000000000000000..17a016214a5be8da112661b75553e2d55ff1fced --- /dev/null +++ b/patches/src/editor/se.js @@ -0,0 +1,1217 @@ +// -- +// Simple level editer. +// +// TODO: +// - clear selected_tiles +// +// Done: +// - Delete tiles +// - move magic numbers to context / initialization (zIndex, pane size etc.) +// - todo fudge factor on g_ctx.tileset +// - get rid of dangerous CONFIG.tiledim (use g_ctx.tileDim instead) +// - XXX create tilesetpadding for tilesets whos tiles are spaced (e.g. phantasy star II) +// - only use fudge to pick sprites rather than fudge and non +// - use g_ctx for g_ctx.tileset parameters instead of CONFIG (starting with initTilesetConfig) +// - todo print locations on screen +// +// +// Keybindings: +// -z - undo +// g - overlay 32x32 grid +// s - generate .js file to move over to convex/maps/ +// m - place a semi-transparent red mask over all tiles. This helps find invisible tiles +// d - hold while clicking a tile to delete +// p - toggle between 16pixel and 32 pixel. +// +// Known bugs and annoyances +// - if deleting a tile while filter is on, filter isn't refreshed so need to toggle with "m" +// -- + +import * as PIXI from 'pixi.js' +import { g_ctx } from './secontext.js' // global context +import * as CONFIG from './seconfig.js' +import * as UNDO from './undo.js' +import * as SPRITEFILE from './spritefile.js' +import * as UI from './sehtmlui.js' +import { EventSystem } from '@pixi/events'; + +g_ctx.debug_flag = true; + +function tileset_index_from_coords(x, y) { + let retme = x + (y*g_ctx.tilesettilew); + console.log("tileset_index_from_coord ",retme, x, y); + return retme; +} +function level_index_from_coords(x, y) { + // place 16px tiles in separate index space + let offset = (g_ctx.tiledimx == 16)? CONFIG.MAXTILEINDEX : 0; + let retme = x + (y*CONFIG.leveltilewidth) + offset; + return retme; +} +function tileset_index_from_px(x, y) { + let coord_x = Math.floor((x - g_ctx.tileset.fudgex) / (g_ctx.tiledimx + CONFIG.tilesetpadding)); + let coord_y = Math.floor((y - g_ctx.tileset.fudgey) / (g_ctx.tiledimy+ CONFIG.tilesetpadding)); + + console.log("tileset_index_from_px ",x, y); + + return tileset_index_from_coords(coord_x, coord_y); +} +function level_index_from_px(x, y) { + let coord_x = Math.floor(x / g_ctx.tiledimx); + let coord_y = Math.floor(y / g_ctx.tiledimy); + return level_index_from_coords(coord_x, coord_y); +} + +function tileset_coords_from_index(index) { + let x = index % (g_ctx.tilesettilew); + let y = Math.floor(index / (g_ctx.tilesettilew)); + console.log("tilesettilewidth: ",g_ctx.tilesettilew); + console.log("tileset_coords_from_index tile coords: ",index,x,y); + return [x,y]; +} + +function tileset_px_from_index(index) { + let ret = tileset_coords_from_index(index); + return [ret[0] * (g_ctx.tiledimx+CONFIG.tilesetpadding), ret[1] * (g_ctx.tiledimy+CONFIG.tilesetpadding)] ; +} + + +// return a sprite of size tileDim given (x,y) starting location +function sprite_from_px(x, y) { + + const bt = PIXI.BaseTexture.from(g_ctx.tilesetpath, { + scaleMode: PIXI.SCALE_MODES.NEAREST, + }); + let w = g_ctx.tiledimx; + let h = g_ctx.tiledimy; + if(x + w > g_ctx.tilesetpxw) { + console.log("sprite_from_px: Warning, texture overrun, truncating"); + w = g_ctx.tilesetpxw - x; + } + if(y + h > g_ctx.tilesetpxh) { + console.log("sprite_from_px: Warning, texture overrun, truncating"); + y = g_ctx.tilesetpxh - h; + } + let texture = new PIXI.Texture(bt, + new PIXI.Rectangle(x, y, w, h) + ); + return new PIXI.Sprite(texture); +} + +function DragState() { + this.square = new PIXI.Graphics(); + this.tooltip = new PIXI.Text('', { + fontFamily: 'Courier', + fontSize: 12, + fill: 0xffffff, + align: 'center', + }); + this.startx = 0; + this.starty = 0; + this.endx = 0; + this.endy = 0; +} + +class LayerContext { + + constructor(app, pane, num, mod = null) { + this.app = app; + this.scrollpane = pane; + this.num = num; + this.widthpx = CONFIG.levelwidth; + this.heightpx = CONFIG.levelheight; + + + this.container = new PIXI.Container(); + this.sprites = {}; + this.composite_sprites = {}; + this.dragctx = new DragState(); + this.tilearray = Array.from(Array(CONFIG.leveltileheight), () => new Array().fill(null)); + + app.stage.addChild(this.container); + + this.mouseshadow = new PIXI.Container(); + this.mouseshadow.zIndex = CONFIG.zIndexMouseShadow; + this.lasttileindex = -1; + + this.fudgex = 0; // offset from 0,0 + this.fudgey = 0; + + this.square = new PIXI.Graphics(); + this.square.beginFill(0x2980b9); + this.square.drawRect(0, 0, CONFIG.levelwidth, CONFIG.levelheight); + this.square.endFill(); + this.square.eventMode = 'static'; + this.container.addChild(this.square); + + this.square.on('mousemove', onLevelMousemove.bind(this)); + this.square.on('mouseover', onLevelMouseover.bind(this)); + this.square.on('pointerout', onLevelMouseOut.bind(this)) + this.square.on('pointerdown', onLevelPointerDown.bind(null, this)) + .on('pointerup', onLevelDragEnd.bind(null, this)) + .on('pointerupoutside', onLevelDragEnd.bind(null, this)); + + if (mod != null && !(mod === g_ctx)) { + this.loadFromMapFile(mod); + } + } + + loadFromMapFile(mod) { + let tiles = []; + if (this.num == 0) { + tiles = mod.bgtiles[0]; + } else if (this.num == 1) { + tiles = mod.bgtiles[1]; + } else if (this.num == 2) { + tiles = mod.objmap[0]; + } else if (this.num == 3) { + tiles = mod.objmap[1]; + } else { + console.log("loadFromMapFile: Error unknow layer number"); + return; + } + + for (let x = 0; x < tiles.length; x++) { + for (let y = 0; y < tiles[0].length; y++) { + if (tiles[x][y] != -1) { + this.addTileLevelCoords(x, y, mod.DEFAULTILEDIMX, tiles[x][y]); + } + } + } + } + + // this will create a rectangle with an alpha channel for every square that has a sprite. This helps find + // sprites that are purely transparent + drawFilter() { + + if (typeof this.filtergraphics == 'undefined') { + this.filtertoggle = true; + this.filtergraphics = new PIXI.Graphics(); + this.filtergraphics.zIndex = CONFIG.zIndexFilter; + } + + if (this.filtertoggle) { + + this.filtergraphics.beginFill(0xff0000, 0.3); + for (let i in this.sprites) { + let spr = this.sprites[i]; + this.filtergraphics.drawRect(spr.x, spr.y, g_ctx.tiledimx, g_ctx.tiledimy); + } + this.filtergraphics.endFill(); + this.container.addChild(this.filtergraphics); + }else{ + this.filtergraphics.clear(); + this.container.removeChild(this.filtergraphics); + } + + this.filtertoggle = ! this.filtertoggle; + } + + // add tile of "index" to Level at location x,y + addTileLevelCoords(x, y, dim, index) { + return this.addTileLevelPx(x * dim, y * dim, index); + } + + // -- delete all sprites / textures on a given index + // will NOOP if no tile exists + deleteFromIndex(index) { + if(g_ctx.debug_flag){ + console.log("deleteFromIndex ",index) + } + + if(this.sprites.hasOwnProperty(index)){ + let ctile = this.sprites[index]; + let row = Math.floor(ctile.y / g_ctx.tiledimy); + let col = Math.floor(ctile.x / g_ctx.tiledimx); + for(let x = 0; x < this.tilearray[row].length; x++){ + if(this.tilearray[row][x].x == col * g_ctx.tiledimx){ + console.log("Removing texture from tilearray ",x,row); + this.tilearray[row].splice(x, 1); + } + } + + this.container.removeChild(this.sprites[index]); + delete this.sprites[index]; + this.updateAnimatedTiles(); + } + + } + + // add tile of "index" to Level at location x,y + addTileLevelPx(x, y, index) { + let xPx = x; + let yPx = y; + + let ctile = null; + let ctile2 = null; + + let pxloc = tileset_px_from_index(index); + ctile = sprite_from_px(pxloc[0] + g_ctx.tileset.fudgex, pxloc[1] + g_ctx.tileset.fudgey); + ctile.index = index; + + // snap to grid + const dx = g_ctx.tiledimx; + const dy = g_ctx.tiledimy; + ctile.x = Math.floor(xPx / dx) * dx; + ctile.y = Math.floor(yPx / dy) * dy; + // Stuff tileset coords into ctile for writing to ts file + ctile.tspx = [pxloc[0] + g_ctx.tileset.fudgex, pxloc[1] + g_ctx.tileset.fudgey]; + + let new_index = level_index_from_px(ctile.x, ctile.y); + + if (g_ctx.debug_flag) { + console.log('addTileLevelPx ', this.num, ' ctile.x ', ctile.x, 'ctile.y ', ctile.y, "index ", index, "new_index", new_index); + } + + if (!g_ctx.dkey) { + this.container.addChild(ctile); + } + + if (this.sprites.hasOwnProperty(new_index)) { + this.deleteFromIndex(new_index); + } + + if (!g_ctx.dkey) { + this.tilearray[Math.floor(yPx / dy)].push(ctile); + this.sprites[new_index] = ctile; + } else if (typeof this.filtergraphics != 'undefined') { + this.filtergraphics.clear(); + this.drawFilter(); + this.drawFilter(); // do twice to get toggle back to original state + } + + return new_index; + } + + // -- + // FIXME : currently just a naive loop. + // -- + updateAnimatedTiles() { + console.log("updateAnimatedTiles"); + for (let row = 0; row < CONFIG.leveltileheight; row++) { + if (!this.tilearray[row][0]) { + continue; + } + let textures = []; + for (let x = 0; x < this.tilearray[row].length; x++) { + textures.push(this.tilearray[row][x].texture); + } + const as = new PIXI.AnimatedSprite(textures); + as.x = row * g_ctx.tiledimx; + as.y = this.num * g_ctx.tiledimy; + as.animationSpeed = .1; + as.autoUpdate = true; + as.play(); + if (this.tilearray[row].hasOwnProperty('as')){ + g_ctx.composite.container.removeChild(this.tilearray[row].as); + } + this.tilearray[row].as = as; + g_ctx.composite.container.addChild(as); + } + } +} // class LayerContext + +class TilesetContext { + + constructor(app, mod = g_ctx) { + this.app = app; + this.container = new PIXI.Container(); + + + console.log(mod.tilesetpath); + const texture = PIXI.Texture.from(mod.tilesetpath); + const bg = new PIXI.Sprite(texture); + + this.widthpx = g_ctx.tilesetpxw; + this.heightpx = g_ctx.tilesetpxh; + + this.square = new PIXI.Graphics(); + this.square.beginFill(0x2980b9); + this.square.drawRect(0, 0, mod.tilesetpxw, mod.tilesetpxh); + this.square.endFill(); + this.square.eventMode = 'static'; + this.container.addChild(this.square); + this.container.addChild(bg); + + this.app.stage.addChild(this.container); + + this.fudgex = 0; // offset from 0,0 + this.fudgey = 0; + + this.dragctx = new DragState(); + + this.square.on('mousedown', function (e) { + + g_ctx.tile_index = tileset_index_from_px(e.global.x, e.global.y); + + if(g_ctx.debug_flag) { + console.log("g_ctx.tileset mouse down. index "+g_ctx.tile_index); + } + }); + + this.square.on('pointerdown', onTilesetDragStart) + .on('pointerup', onTilesetDragEnd) + .on('pointerupoutside', onTilesetDragEnd); + } +} // class TilesetContext + + +class CompositeContext { + + constructor(app) { + this.app = app; + this.widthpx = CONFIG.levelwidth; + this.heightpx = CONFIG.levelheight; + + this.container = new PIXI.Container(); + this.container.sortableChildren = true; + this.app.stage.addChild(this.container); + this.sprites = {}; + this.circle = new PIXI.Graphics(); + this.circle.zIndex = CONFIG.zIndexCompositePointer; + + this.fudgex = 0; // offset from 0,0 + this.fudgey = 0; + + this.mouseshadow = new PIXI.Container(); + this.mouseshadow.zIndex = CONFIG.zIndexMouseShadow; + this.lasttileindex = -1; + + this.square = new PIXI.Graphics(); + this.square.beginFill(0x2980b9); + this.square.drawRect(0, 0, CONFIG.levelwidth, CONFIG.levelheight); + this.square.endFill(); + this.square.eventMode = 'static'; + this.container.addChild(this.square); + + this.square.on('mousedown', onCompositeMousedown.bind(null, this)); + } + +} // class CompositeContext + + +function doimport (str) { + if (globalThis.URL.createObjectURL) { + const blob = new Blob([str], { type: 'text/javascript' }) + const url = URL.createObjectURL(blob) + const module = import(url) + URL.revokeObjectURL(url) // GC objectURLs + return module + } + + const url = "data:text/javascript;base64," + btoa(moduleData) + return import(url) + } + +function resetPanes() { + g_ctx.tiledimx = 16; + g_ctx.tiledimy = 16; + + g_ctx.composite.container.removeChildren(); + g_ctx.composite = new CompositeContext(g_ctx.composite_app); + g_ctx.tileset_app.stage.removeChildren() + g_ctx.tileset = new TilesetContext(g_ctx.tileset_app); + g_ctx.g_layer_apps[0].stage.removeChildren() + g_ctx.g_layers[0] = new LayerContext(g_ctx.g_layer_apps[0], document.getElementById("layer0pane"), 0); + g_ctx.g_layer_apps[1].stage.removeChildren() + g_ctx.g_layers[1] = new LayerContext(g_ctx.g_layer_apps[1], document.getElementById("layer1pane"), 1); + g_ctx.g_layer_apps[2].stage.removeChildren() + g_ctx.g_layers[2] = new LayerContext(g_ctx.g_layer_apps[2], document.getElementById("layer2pane"), 2); + g_ctx.g_layer_apps[3].stage.removeChildren() + g_ctx.g_layers[3] = new LayerContext(g_ctx.g_layer_apps[3], document.getElementById("layer3pane"), 3); + + redrawGrid(); +} + +function downloadpng(filename) { + let newcontainer = new PIXI.Container(); + let children = [...g_ctx.composite.container.children]; + for(let i = 0; i < children.length; i++) { + let child = children[i]; + if (! child.hasOwnProperty('isSprite') || !child.isSprite){ + console.log(child); + continue; + } + // console.log(child, typeof child); + g_ctx.composite.container.removeChild(child); + newcontainer.addChild(child); + } + renderer.plugins.extract.canvas(newcontainer).toBlob(function (b) { + //renderer.plugins.extract.canvas(g_ctx.composite.container).toBlob(function (b) { + console.log(b); + var a = document.createElement("a"); + document.body.append(a); + a.download = filename; + a.href = URL.createObjectURL(b); + a.click(); + a.remove(); + }, "image/png"); + } + +window.saveCompositeAsImage = () => { + downloadpng("g_ctx.composite.png"); +} + +window.onTab = (evt, tabName) => { + // Declare all variables + var i, tabcontent, tablinks; + + // Get all elements with class="tabcontent" and hide them + tabcontent = document.getElementsByClassName("tabcontent"); + for (i = 0; i < tabcontent.length; i++) { + tabcontent[i].style.display = "none"; + } + + // Get all elements with class="tablinks" and remove the class "active" + tablinks = document.getElementsByClassName("tablinks"); + for (i = 0; i < tablinks.length; i++) { + tablinks[i].className = tablinks[i].className.replace(" active", ""); + } + + // Show the current tab, and add an "active" class to the button that opened the tab + document.getElementById(tabName).style.display = "block"; + evt.currentTarget.className += " active"; + + if (tabName == "map"){ + map_app.stage.addChild(g_ctx.composite.container); + }else { + g_ctx.composite.app.stage.addChild(g_ctx.composite.container); + } +} + +window.addEventListener( + "keyup", (event) => { + if (event.code == "KeyD"){ + g_ctx.dkey = false; + g_ctx.g_layers.map( (l) => l.container.addChild(l.mouseshadow)); + g_ctx.composite.container.addChild(g_ctx.composite.mouseshadow); + } + }); +window.addEventListener( + "keydown", (event) => { + + if (event.code == "KeyD"){ + g_ctx.dkey = true; + g_ctx.g_layers.map((l) => l.container.removeChild(l.mouseshadow) ); + g_ctx.composite.container.removeChild(g_ctx.composite.mouseshadow); + } + + if (event.code == 'KeyS'){ + SPRITEFILE.generate_sprite_file(); + } + else if (event.code == 'Escape'){ + g_ctx.selected_tiles = []; + g_ctx.g_layers.map((l) => l.mouseshadow.removeChildren()); + composite.mouseshadow.removeChildren(); + } + else if (event.code == 'KeyM'){ + g_ctx.g_layers.map((l) => l.drawFilter () ); + }else if (event.code == 'KeyP'){ + setGridSize((g_ctx.tiledimx == 16)?32:16); + } + else if (event.ctrlKey && event.code === 'KeyZ'){ + let undome = UNDO.undo_pop(); + if (!undome) { + return; + } + let layer = undome.shift(); + for(let i = 0; i < undome.length; i++) { + if (g_ctx.debug_flag) { + console.log("Undo removing ", undome[i]) + } + layer.container.removeChild(layer.sprites[undome[i]]); + g_ctx.composite.container.removeChild(layer.composite_sprites[undome[i]]); + } + } + else if (event.shiftKey && event.code == 'ArrowUp') { + g_ctx.tileset.fudgey -= 1; + redrawGridPane(g_ctx.tileset); + } + else if (event.shiftKey && event.code == 'ArrowDown') { + g_ctx.tileset.fudgey += 1; + redrawGridPane(g_ctx.tileset); + } + else if (event.shiftKey && event.code == 'ArrowLeft') { + g_ctx.tileset.fudgex -= 1; + redrawGridPane(g_ctx.tileset); + } + else if (event.shiftKey && event.code == 'ArrowRight') { + g_ctx.tileset.fudgex += 1; + redrawGridPane(g_ctx.tileset); + } + } + ); + +// Listen to pointermove on stage once handle is pressed. + +function onTilesetDragStart(e) +{ + if (g_ctx.debug_flag) { + console.log("onDragStartTileset()"); + } + g_ctx.tileset.app.stage.eventMode = 'static'; + g_ctx.tileset.app.stage.addEventListener('pointermove', onTilesetDrag); + + g_ctx.tileset.dragctx.startx = e.global.x; + g_ctx.tileset.dragctx.starty = e.global.y; + g_ctx.tileset.dragctx.endx = e.global.x; + g_ctx.tileset.dragctx.endy = e.global.y; + + g_ctx.tileset.app.stage.addChild(g_ctx.tileset.dragctx.square); + // g_ctx.tileset.app.stage.addChild(g_ctx.tileset.dragctx.tooltip); + + g_ctx.selected_tiles = []; +} + +// Stop dragging feedback once the handle is released. +function onTilesetDragEnd(e) +{ + if (g_ctx.debug_flag) { + console.log("onDragEndTileset()"); + } + g_ctx.tileset.app.stage.eventMode = 'auto'; + g_ctx.tileset.app.stage.removeEventListener('pointermove', onTilesetDrag); + g_ctx.tileset.app.stage.removeChild(g_ctx.tileset.dragctx.square); + g_ctx.tileset.app.stage.removeChild(g_ctx.tileset.dragctx.tooltip); + + let starttilex = Math.floor((g_ctx.tileset.dragctx.startx - g_ctx.tileset.fudgex) / g_ctx.tiledimx); + let starttiley = Math.floor((g_ctx.tileset.dragctx.starty - g_ctx.tileset.fudgey) / g_ctx.tiledimy); + let endtilex = Math.floor((g_ctx.tileset.dragctx.endx - g_ctx.tileset.fudgex) / g_ctx.tiledimx); + let endtiley = Math.floor((g_ctx.tileset.dragctx.endy - g_ctx.tileset.fudgey) / g_ctx.tiledimy); + + if (g_ctx.debug_flag) { + console.log("sx sy ex ey ", starttilex, ",", starttiley, ",", endtilex, ",", endtiley); + } + // let mouse clicked handle if there isn't a multiple tile square + if(starttilex === endtilex && starttiley === endtiley ){ + return; + } + + g_ctx.tile_index = tileset_index_from_px(e.global.x, e.global.y); + + let origx = starttilex; + let origy = starttiley; + for(let y = starttiley; y <= endtiley; y++){ + for(let x = starttilex; x <= endtilex; x++){ + let squareindex = (y * g_ctx.tilesettilew) + x; + console.log("Pushing into st ", x - origx,y - origy, squareindex); + g_ctx.selected_tiles.push([x - origx,y - origy,squareindex]); + } + } + + // if we're still 16x16 assume we're resizing the grid + if (g_ctx.tiledimx == 16 && g_ctx.tiledimy == 16) { + // for sprite tool, we want to set the tilesize based on the selected region + // FIXME .. don't use magic number 16 + g_ctx.tiledimx = (1 + endtilex - starttilex) * 16; + g_ctx.tiledimy = (1 + endtiley - starttiley) * 16; + redrawGrid(); + g_ctx.selected_tiles = []; + g_ctx.tile_index = tileset_index_from_px(g_ctx.tileset.dragctx.startx - g_ctx.tileset.fudgex, g_ctx.tileset.dragctx.starty - g_ctx.tileset.fudgey); + } + + g_ctx.tileset.dragctx.square.clear(); + // g_ctx.tileset.dragctx.tooltip.clear(); +} + +function onTilesetDrag(e) +{ + if (g_ctx.debug_flag) { + console.log("onDragTileset()"); + } + g_ctx.tileset.dragctx.endx = e.global.x; + g_ctx.tileset.dragctx.endy = e.global.y; + + g_ctx.tileset.dragctx.square.clear(); + g_ctx.tileset.dragctx.square.beginFill(0xFF3300, 0.3); + g_ctx.tileset.dragctx.square.lineStyle(2, 0xffd900, 1); + g_ctx.tileset.dragctx.square.moveTo(g_ctx.tileset.dragctx.startx, g_ctx.tileset.dragctx.starty); + g_ctx.tileset.dragctx.square.lineTo(g_ctx.tileset.dragctx.endx, g_ctx.tileset.dragctx.starty); + g_ctx.tileset.dragctx.square.lineTo(g_ctx.tileset.dragctx.endx, g_ctx.tileset.dragctx.endy); + g_ctx.tileset.dragctx.square.lineTo(g_ctx.tileset.dragctx.startx, g_ctx.tileset.dragctx.endy); + g_ctx.tileset.dragctx.square.closePath(); + g_ctx.tileset.dragctx.square.endFill(); + + + // g_ctx.tileset.dragctx.tooltip.clear(); + // g_ctx.tileset.dragctx.tooltip.beginFill(0xFF3300, 0.3); + // g_ctx.tileset.dragctx.tooltip.lineStyle(2, 0xffd900, 1); + // g_ctx.tileset.dragctx.tooltip.drawRect(e.global.x, e.global.y, 20,8); + // g_ctx.tileset.dragctx.tooltip.endFill(); +} + +//g_ctx.tileset.app.stage.addChild(g_ctx.tileset.container); + +function redrawGridPane(pane) { + + if (typeof pane.gridgraphics != 'undefined') { + pane.container.removeChild(pane.gridgraphics); + } + + pane.gridgraphics = new PIXI.Graphics(); + let gridsizex = g_ctx.tiledimx; + let gridsizey = g_ctx.tiledimy; + pane.gridgraphics.lineStyle(1, 0x000000, 1); + + + let index = 0; + for (let i = 0; i < pane.widthpx; i += gridsizex) { + pane.gridgraphics.moveTo(i + pane.fudgex, 0 + pane.fudgey); + pane.gridgraphics.lineTo(i + pane.fudgex, pane.heightpx + pane.fudgey); + pane.gridgraphics.moveTo(i + gridsizex + pane.fudgex, 0 + pane.fudgey); + pane.gridgraphics.lineTo(i + gridsizex + pane.fudgex, pane.heightpx + pane.fudgey); + + } + for (let j = 0; j < pane.heightpx; j += gridsizey) { + pane.gridgraphics.moveTo(0 + pane.fudgex, j + gridsizey + pane.fudgey); + pane.gridgraphics.lineTo(pane.widthpx + pane.fudgex, j + gridsizey + pane.fudgey); + pane.gridgraphics.moveTo(0 + pane.fudgex, j + pane.fudgey); + pane.gridgraphics.lineTo(pane.heightpx + pane.fudgex, j + pane.fudgey); + } + + if (pane.gridvisible) { + pane.container.addChild(pane.gridgraphics); + } + pane.container.addChild(pane.gridgraphics); + pane.gridvisible = true; +} + +function redrawGrid() { + g_ctx.g_layers.map((l) => redrawGridPane(l)); + redrawGridPane(g_ctx.tileset); + redrawGridPane(g_ctx.composite); +} + + +// -- +// Variable placement logic Level1 +// -- + +function centerCompositePane(x, y){ + var compositepane = document.getElementById("compositepane"); + compositepane.scrollLeft = x - (CONFIG.htmlCompositePaneW/2); + compositepane.scrollTop = y - (CONFIG.htmlCompositePaneH/2); +} + +function centerLayerPanes(x, y){ + // TODO remove magic number pulled from index.html + g_ctx.g_layers.map((l) => { + l.scrollpane.scrollLeft = x - (CONFIG.htmlCompositePaneW/2); + l.scrollpane.scrollTop = y - (CONFIG.htmlCompositePaneH/2); + }); +} + +function onLevelMouseover(e) { + let x = e.data.global.x; + let y = e.data.global.y; + if(g_ctx.debug_flag){ + console.log("onLevelMouseOver ",this.num); + } + if (x < this.scrollpane.scrollLeft || x > this.scrollpane.scrollLeft + CONFIG.htmlCompositePaneW) { + return; + } + if (y < this.scrollpane.scrollTop || y > this.scrollpane.scrollTop + CONFIG.htmlCompositePaneH) { + return; + } + + if (this.lasttileindex != g_ctx.tile_index) { + this.mouseshadow.removeChildren(0); + g_ctx.composite.mouseshadow.removeChildren(0); + if (g_ctx.selected_tiles.length == 0) { + let shadowsprite = null; + let shadowsprite2 = null; + + let pxloc = tileset_px_from_index(g_ctx.tile_index); + shadowsprite = sprite_from_px(pxloc[0] + g_ctx.tileset.fudgex, pxloc[1] + g_ctx.tileset.fudgey); + shadowsprite2 = sprite_from_px(pxloc[0] + g_ctx.tileset.fudgex, pxloc[1] + g_ctx.tileset.fudgey); + + shadowsprite.alpha = .5; + shadowsprite2.alpha = .5; + this.mouseshadow.addChild(shadowsprite); + g_ctx.composite.mouseshadow.addChild(shadowsprite2); + } else { + // TODO! adjust for fudge + for (let i = 0; i < g_ctx.selected_tiles.length; i++) { + let tile = g_ctx.selected_tiles[i]; + console.log("TILE", tile); + let pxloc = tileset_px_from_index(tile[2]); + console.log('PXLOC',pxloc); + + + const shadowsprite = sprite_from_px(pxloc[0] + g_ctx.tileset.fudgex, pxloc[1] + g_ctx.tileset.fudgey); + const shadowsprite2 = sprite_from_px(pxloc[0] + g_ctx.tileset.fudgex, pxloc[1] + g_ctx.tileset.fudgey); + shadowsprite.x = tile[0] * g_ctx.tiledimx; + shadowsprite.y = tile[1] * g_ctx.tiledimy; + shadowsprite2.x = tile[0] * g_ctx.tiledimx; + shadowsprite2.y = tile[1] * g_ctx.tiledimy; + shadowsprite.alpha = .5; + shadowsprite2.alpha = .5; + this.mouseshadow.addChild(shadowsprite); + g_ctx.composite.mouseshadow.addChild(shadowsprite2); + } + + } + this.mouseshadow.x = x - 16; + this.mouseshadow.y = y - 16; + this.container.removeChild(this.mouseshadow); + g_ctx.composite.container.removeChild(g_ctx.composite.mouseshadow); + this.container.addChild(this.mouseshadow); + g_ctx.composite.container.addChild(g_ctx.composite.mouseshadow); + } + + g_ctx.composite.app.stage.removeChild(g_ctx.composite.circle); + g_ctx.composite.app.stage.addChild(g_ctx.composite.circle); +} + + +function onLevelMouseOut(e) { + if (g_ctx.debug_flag) { + console.log("onLevelMouseOut ",this.num); + } + this.mouseshadow.removeChildren(0); + g_ctx.composite.mouseshadow.removeChildren(0); +} + +function onLevelMousemove(e) { + let x = e.data.global.x; + let y = e.data.global.y; + + // FIXME TEST CODE + this.mouseshadow.x = x-8; + this.mouseshadow.y = y-8; + g_ctx.composite.mouseshadow.x = x-8; + g_ctx.composite.mouseshadow.y = y-8; + // FIXME TEST CODE + + + if (x < this.scrollpane.scrollLeft || x > this.scrollpane.scrollLeft + CONFIG.htmlCompositePaneW) { + return; + } + if (y < this.scrollpane.scrollTop || y > this.scrollpane.scrollTop + CONFIG.htmlCompositePaneH) { + return; + } + + g_ctx.composite.circle.clear(); + g_ctx.composite.circle.beginFill(0xe50000, 0.5); + g_ctx.composite.circle.drawCircle(e.data.global.x, e.data.global.y, 3); + g_ctx.composite.circle.endFill(); +} +function onCompositeMousedown(layer, e) { + if (g_ctx.debug_flag) { + console.log('onCompositeMouseDown: X', e.data.global.x, 'Y', e.data.global.y); + } + + let xorig = e.data.global.x; + let yorig = e.data.global.y; + + centerLayerPanes(xorig,yorig); +} + + +// Place with no variable target at destination +function levelPlaceNoVariable(layer, e) { + if (g_ctx.debug_flag) { + console.log('levelPlaceNoVariable: X', e.data.global.x, 'Y', e.data.global.y); + } + + let xorig = e.global.x; + let yorig = e.global.y; + + // No need to center pane + // centerCompositePane(xorig,yorig); + + if (g_ctx.dkey || g_ctx.selected_tiles.length == 0) { + let ti = layer.addTileLevelPx(e.global.x, e.global.y, g_ctx.tile_index); + UNDO.undo_add_single_index_as_task(layer, ti); + } else { + let undolist = []; + UNDO.undo_mark_task_start(layer); + for (let index of g_ctx.selected_tiles) { + let ti = layer.addTileLevelPx(xorig + index[0] * g_ctx.tiledimx, yorig + index[1] * g_ctx.tiledimy, index[2]); + UNDO.undo_add_index_to_task(ti); + } + UNDO.undo_mark_task_end(); + } + + layer.updateAnimatedTiles(); +} + +// Listen to pointermove on stage once handle is pressed. +function onLevelPointerDown(layer, e) +{ + if (g_ctx.debug_flag) { + console.log("onLevelPointerDown()"); + } + layer.app.stage.eventMode = 'static'; + layer.app.stage.addEventListener('pointermove', onLevelDrag.bind(null, layer, e)); + + layer.container.removeChild(layer.mouseshadow); + g_ctx.composite.container.removeChild(g_ctx.composite.mouseshadow); + + layer.dragctx.startx = e.data.global.x; + layer.dragctx.starty = e.data.global.y; + layer.dragctx.endx = e.data.global.x; + layer.dragctx.endy = e.data.global.y; + + layer.app.stage.addChild(layer.dragctx.square); + layer.app.stage.addChild(layer.dragctx.tooltip); +} + +function onLevelDrag(layer, e) +{ + if(layer.dragctx.startx == -1){ + layer.dragctx.square.clear(); + return; + } + + layer.dragctx.endx = e.global.x; + layer.dragctx.endy = e.global.y; + + if (g_ctx.debug_flag) { + console.log("onLevelDrag()"); + } + + layer.dragctx.square.clear(); + layer.dragctx.square.beginFill(0xFF3300, 0.3); + layer.dragctx.square.lineStyle(2, 0xffd900, 1); + layer.dragctx.square.moveTo(layer.dragctx.startx, layer.dragctx.starty); + layer.dragctx.square.lineTo(layer.dragctx.endx, layer.dragctx.starty); + layer.dragctx.square.lineTo(layer.dragctx.endx, layer.dragctx.endy); + layer.dragctx.square.lineTo(layer.dragctx.startx, layer.dragctx.endy); + layer.dragctx.square.closePath(); + layer.dragctx.square.endFill(); + + const vwidth = Math.floor((layer.dragctx.endx - layer.dragctx.startx)/g_ctx.tiledimx); + const vheight = Math.floor((layer.dragctx.endy - layer.dragctx.starty)/g_ctx.tiledimy); + layer.dragctx.tooltip.x = e.global.x + 16; + layer.dragctx.tooltip.y = e.global.y - 4; + layer.dragctx.tooltip.text = "["+vwidth+","+vheight+"]\n"+ + "("+Math.floor(e.global.x/g_ctx.tiledimx)+","+Math.floor(e.global.y/g_ctx.tiledimy)+")"; + //layer.dragctx.tooltip.text = "("+e.global.x+","+e.global.y+")"; +} + +function onLevelCreateAnimatedSprite(row) { + + +} + +// Stop dragging feedback once the handle is released. +function onLevelDragEnd(layer, e) +{ + layer.dragctx.endx = e.data.global.x; + layer.dragctx.endy = e.data.global.y; + + if(layer.dragctx.startx == -1){ + console.log("onLevelDragEnd() start is -1 bailing"); + return; + } + if (g_ctx.debug_flag) { + console.log("onLevelDragEnd()"); + } + + layer.container.addChild(layer.mouseshadow); + g_ctx.composite.container.addChild(g_ctx.composite.mouseshadow); + + layer.app.stage.eventMode = 'auto'; + layer.app.stage.removeChild(layer.dragctx.square); + layer.app.stage.removeChild(layer.dragctx.tooltip); + + let starttilex = Math.floor(layer.dragctx.startx / g_ctx.tiledimx); + let starttiley = Math.floor(layer.dragctx.starty / g_ctx.tiledimy); + let endtilex = Math.floor(layer.dragctx.endx / g_ctx.tiledimx); + let endtiley = Math.floor(layer.dragctx.endy / g_ctx.tiledimy); + + if (g_ctx.debug_flag) { + console.log("sx ", starttilex, " ex ", endtilex); + console.log("sy ", starttiley, " ey ", endtiley); + } + + // no variable placement. + if(starttilex === endtilex && starttiley == endtiley ){ + levelPlaceNoVariable(layer, e); + layer.dragctx.startx = -1; + layer.dragctx.endx = -1; + layer.dragctx.starty = -1; + layer.dragctx.endy = -1; + return; + } + + if (g_ctx.selected_tiles.length == 0) { + UNDO.undo_mark_task_start(layer); + for (let i = starttilex; i <= endtilex; i++) { + for (let j = starttiley; j <= endtiley; j++) { + let squareindex = (j * g_ctx.tilesettilew) + i; + let ti = layer.addTileLevelPx(i * g_ctx.tiledimx, j * g_ctx.tiledimy, g_ctx.tile_index); + UNDO.undo_add_index_to_task(ti); + } + } + UNDO.undo_mark_task_end(); + } else { + // figure out selected grid + let selected_grid = Array.from(Array(64), () => new Array(64)); // FIXME ... hope 64x64 is enough + let row = 0; + let column = 0; + let selected_row = g_ctx.selected_tiles[0][1]; + // selected_grid[0] = []; + for (let index of g_ctx.selected_tiles) { + // console.log("Selected row ", selected_row, index); + if(index[1] != selected_row){ + selected_row = index[1]; + row++; + column = 0; + //selected_grid[row] = []; + } + selected_grid[column++][row] = index; + } + // at this point should have a 3D array of the selected tiles and the size should be row, column + + UNDO.undo_mark_task_start(layer); + + let ti=0; + for (let i = starttilex; i <= endtilex; i++) { + for (let j = starttiley; j <= endtiley; j++) { + let squareindex = (j * g_ctx.tilesettilew) + i; + if (j === starttiley) { // first row + if (i === starttilex) { // top left corner + ti = layer.addTileLevelPx(i * g_ctx.tiledimx, j * g_ctx.tiledimy, selected_grid[0][0][2]); + } + else if (i == endtilex) { // top right corner + ti = layer.addTileLevelPx(i * g_ctx.tiledimx, j * g_ctx.tiledimy, selected_grid[column - 1][0][2]); + } else { // top middle + ti = layer.addTileLevelPx(i * g_ctx.tiledimx, j * g_ctx.tiledimy, selected_grid[1][0][2]); + } + } else if (j === endtiley) { // last row + if (i === starttilex) { // bottom left corner + ti = layer.addTileLevelPx(i * g_ctx.tiledimx, j * g_ctx.tiledimy, selected_grid[0][row][2]); + } + else if (i == endtilex) { // bottom right corner + ti = layer.addTileLevelPx(i * g_ctx.tiledimx, j * g_ctx.tiledimy, selected_grid[column - 1][row][2]); + } else { // bottom middle + ti = layer.addTileLevelPx(i * g_ctx.tiledimx, j * g_ctx.tiledimy, selected_grid[1][row][2]); + } + } else { // middle row + if (i === starttilex) { // middle left + ti = layer.addTileLevelPx(i * g_ctx.tiledimx, j * g_ctx.tiledimy, selected_grid[0][(row > 0)? 1 : 0][2]); + } + else if (i === endtilex) { // middle end + ti = layer.addTileLevelPx(i * g_ctx.tiledimx, j * g_ctx.tiledimy, selected_grid[column - 1][(row > 0)? 1 : 0][2]); + } else { // middle middle + ti = layer.addTileLevelPx(i * g_ctx.tiledimx, j * g_ctx.tiledimy, selected_grid[1][(row > 0)? 1 : 0][2]); + } + } + UNDO.undo_add_index_to_task(ti); + } + } + UNDO.undo_mark_task_end(); + } + + layer.dragctx.square.clear(); + + layer.dragctx.startx = -1; + layer.dragctx.starty = -1; +} + + + +// -- +// Initialized all pixi apps / components for application +// -- +function initPixiApps() { + + // -- Editor wide globals -- + + // First layer of level + const level_app0 = new PIXI.Application({ backgroundColor: 0x2980b9, width: CONFIG.levelwidth, height: CONFIG.levelheight, view: document.getElementById('level0') }); + let layer0 = new LayerContext(level_app0, document.getElementById("layer0pane"), 0); + + // second layer of level + const level_app1 = new PIXI.Application({ backgroundColor: 0x2980b9, width: CONFIG.levelwidth, height: CONFIG.levelheight, view: document.getElementById('level1') }); + let layer1 = new LayerContext(level_app1, document.getElementById("layer1pane"), 1); + + // object layer of level + const level_app2 = new PIXI.Application({ backgroundColor: 0x2980b9, width: CONFIG.levelwidth, height: CONFIG.levelheight, view: document.getElementById('level3') }); + let layer2 = new LayerContext(level_app2, document.getElementById("layer2pane"), 2); + + // object layer of level + const level_app3 = new PIXI.Application({ backgroundColor: 0x2980b9, width: CONFIG.levelwidth, height: CONFIG.levelheight, view: document.getElementById('level4') }); + + let layer3 = new LayerContext(level_app3, document.getElementById("layer3pane"), 3); + + g_ctx.g_layer_apps = []; + g_ctx.g_layer_apps.push(level_app0 ); + g_ctx.g_layer_apps.push(level_app1); + g_ctx.g_layer_apps.push(level_app2); + g_ctx.g_layer_apps.push(level_app3); + + + g_ctx.g_layers = []; + g_ctx.g_layers.push(layer0); + g_ctx.g_layers.push(layer1); + g_ctx.g_layers.push(layer2); + g_ctx.g_layers.push(layer3); + + // g_ctx.composite view + g_ctx.composite_app = new PIXI.Application({ backgroundAlpha: 0, width: CONFIG.levelwidth, height: CONFIG.levelheight, view: document.getElementById('composite') }); + g_ctx.composite = new CompositeContext(g_ctx.composite_app); + + // map tab + g_ctx.map_app = new PIXI.Application({ backgroundColor: 0x2980b9, width: CONFIG.levelwidth, height: CONFIG.levelheight, view: document.getElementById('mapcanvas') }); + + // g_ctx.tileset + // g_ctx.tileset_app = new PIXI.Application({ width: g_ctx.tilesetpxw, height: g_ctx.tilesetpxh, view: document.getElementById('tileset') }); + g_ctx.tileset_app = new PIXI.Application({ width: 5632 , height: 8672, view: document.getElementById('tileset') }); + //g_ctx.tileset_app = new PIXI.Application({ backgroundColor: 0x2980b9, width: 5632 , height: 8672, view: document.getElementById('tileset') }); + + const { renderer } = g_ctx.tileset_app; + // Install the EventSystem + renderer.addSystem(EventSystem, 'tileevents'); + g_ctx.tileset = new TilesetContext(g_ctx.tileset_app); +} + +// -- +// Initialize handlers for file loading +// -- + +function initLevelLoader() { + let filecontent = ""; + + const fileInput = document.getElementById('levelfile'); + fileInput.onchange = (evt) => { + if (!window.FileReader) return; // Browser is not compatible + + var reader = new FileReader(); + + reader.onload = function (evt) { + if (evt.target.readyState != 2) return; + if (evt.target.error) { + alert('Error while reading file'); + return; + } + + filecontent = evt.target.result; + doimport(filecontent).then(mod => loadMapFromModule(mod)); + }; + + reader.readAsText(evt.target.files[0]); + } +} + + +function setGridSize(size) { + if (size == 16) { + if (g_ctx.tiledimx == 16) { return; } + g_ctx.tilesettilew = (g_ctx.tilesettilew/ (size / g_ctx.tiledimx)); + g_ctx.tilesettileh = (g_ctx.tilesettileh / (size / g_ctx.tiledimy)); + g_ctx.tiledimx = 16; + g_ctx.dimlog = Math.log2(g_ctx.tiledimx); + g_ctx.curtiles = g_ctx.tiles16; + console.log("set to curTiles16"); + } else if (size == 32) { + if (g_ctx.tiledimx == 32) { return; } + g_ctx.tilesettilew = (g_ctx.tilesettilew/ (size / g_ctx.tiledimx)); + g_ctx.tilesettileh = (g_ctx.tilesettileh / (size / g_ctx.tiledimy)); + g_ctx.tiledimx = 32; + g_ctx.dimlog = Math.log2(g_ctx.tiledimx); + g_ctx.curtiles = g_ctx.tiles32; + console.log("set to curTiles32"); + } else { + console.debug("Invalid TileDim!"); + return; + } + redrawGrid(); +} + +function initRadios() { + var rad = document.myForm.radioTiledim; + var prev = null; + for (var i = 0; i < rad.length; i++) { + rad[i].addEventListener('change', function () { + if (this !== prev) { + prev = this; + } + setGridSize(this.value); + }); + } +} + +// -- +// Load in default tileset and use to set properties +// -- +const initTilesConfig = async (path = CONFIG.DEFAULTTILESETPATH) => { + + g_ctx.tilesetpath = path; + + return new Promise((resolve, reject) => { + + const texture = new PIXI.BaseTexture(g_ctx.tilesetpath); + + console.log("Loading texture ",g_ctx.tilesetpath); + texture .on('loaded', function() { + // size of g_ctx.tileset in px + g_ctx.tilesetpxw = texture.width; + g_ctx.tilesetpxh = texture.height; + console.log("Texture size w:", g_ctx.tilesetpxw, "h:", g_ctx.tilesetpxh); + // size of g_ctx.tileset in tiles + let tileandpad = g_ctx.tiledimx + CONFIG.tilesetpadding; + let numtilesandpadw = Math.floor(g_ctx.tilesetpxw / tileandpad); + g_ctx.tilesettilew = numtilesandpadw + Math.floor((g_ctx.tilesetpxw - (numtilesandpadw * tileandpad))/g_ctx.tiledimx); + let numtilesandpadh = Math.floor(g_ctx.tilesetpxh / tileandpad); + g_ctx.tilesettileh = numtilesandpadh + Math.floor((g_ctx.tilesetpxh - (numtilesandpadh * tileandpad))/g_ctx.tiledimy); + console.log("Number of x tiles ",g_ctx.tilesettilew," y tiles ",g_ctx.tilesettileh); + + g_ctx.MAXTILEINDEX = g_ctx.tilesettilew * g_ctx.tilesettileh; + + texture.destroy(); + resolve(); + }); + + + }); + }; + +function initTiles() { + // load g_ctx.tileset into a global array of textures for blitting onto levels + const bt = PIXI.BaseTexture.from(g_ctx.tilesetpath, { + scaleMode: PIXI.SCALE_MODES.NEAREST, + }); + for (let x = 0; x < CONFIG.tilesettilewidth; x++) { + for (let y = 0; y < CONFIG.tilesettileheight; y++) { + g_ctx.tiles32[x + y * CONFIG.tilesettilewidth] = new PIXI.Texture( + bt, + new PIXI.Rectangle(x * 32, y * 32, 32, 32), + ); + } + } + for (let x = 0; x < CONFIG.tilesettilewidth * 2; x++) { + for (let y = 0; y < CONFIG.tilesettileheight * 2; y++) { + g_ctx.tiles16[x + y * CONFIG.tilesettilewidth * 2] = new PIXI.Texture( + bt, + new PIXI.Rectangle(x * 16, y * 16, 16, 16), + ); + } + } + + g_ctx.curtiles = g_ctx.tiles32; +} + +function newTilesetFromFile(){ + + initTilesConfig(g_ctx.tilesetpath).then(resetPanes); +} + +async function init() { + + UI.initMainHTMLWindow(); + await initTilesConfig(); // needs to be called before Pixi apps are initialized + + initPixiApps(); + initRadios(); + initTiles(); + initLevelLoader(); + UI.initCompositePNGLoader(); + UI.initTilesetLoader(newTilesetFromFile); + + redrawGrid(); +} + +init(); \ No newline at end of file diff --git a/patches/src/editor/seconfig.js b/patches/src/editor/seconfig.js new file mode 100644 index 0000000000000000000000000000000000000000..87f130481e422f341a785ceef3628b550d92b912 --- /dev/null +++ b/patches/src/editor/seconfig.js @@ -0,0 +1,54 @@ +//export const DEFAULTTILESETPATH = "./spritesheets/women.png"; +//export const DEFAULTILEDIMX = 32; // px +//export const DEFAULTILEDIMY = 34; // px + +//export const DEFAULTTILESETPATH = "./spritesheets/doll.png"; +//export const DEFAULTILEDIMX = 48; // px +//export const DEFAULTILEDIMY = 48; // px + +// export const DEFAULTTILESETPATH = "./spritesheets/peeps.png"; +// export const DEFAULTILEDIMX = 48; // px +// export const DEFAULTILEDIMY = 96; // px + +export const DEFAULTTILESETPATH = "./spritesheets/tall.png"; +//export const DEFAULTTILESETPATH = "./spritesheets/Clothes_Hanging_1_32x32.png" +export const DEFAULTILEDIMX = 16; // px +export const DEFAULTILEDIMY = 16; // px + +// export const DEFAULTTILESETPATH = "./spritesheets/wateranimate2.png"; +// export const DEFAULTILEDIMX = 32; // px +// export const DEFAULTILEDIMY = 32; // px + + +// If there is padding between tilesets, set this to the pixel size +export const tilesetpadding = 0; + + +// width / height of layer panes +export const levelwidth = 2048; // px +export const levelheight = 1536; // px + +export let leveltilewidth = Math.floor(levelwidth / DEFAULTILEDIMX); +export let leveltileheight = Math.floor(levelheight / DEFAULTILEDIMX); + +export const MAXTILEINDEX = leveltilewidth * leveltileheight; + + +// -- HTML + +export const htmlLayerPaneW = 800; +export const htmlLayerPaneH = 600; + +export const htmlTilesetPaneW = 800; +export const htmlTilesetPaneH = 600; + +export const htmlCompositePaneW = 800; +export const htmlCompositePaneH = 600; + +// -- zIndex + +// 1-10 taken by layers +export const zIndexFilter = 20; +export const zIndexMouseShadow = 30; +export const zIndexGrid = 50; +export const zIndexCompositePointer = 100; diff --git a/patches/src/editor/secontext.js b/patches/src/editor/secontext.js new file mode 100644 index 0000000000000000000000000000000000000000..e682ab25161f26a3f8a0dda35f8f06a0ea8b8e4e --- /dev/null +++ b/patches/src/editor/secontext.js @@ -0,0 +1,39 @@ +import * as PIXI from 'pixi.js' +import * as CONFIG from './seconfig.js' + +var ContextCreate = (function(){ + + function ContextSingleton() { + this.tilesetpxw = 0; + this.tilesetpxh = 0; + this.tilesettilew = 0; + this.tilesettileh = 0; + this.MAXTILEINDEX = 0; + this.tile_index = 0; + this.selected_tiles = []; // current set of selected tiles + this.tiledimx = CONFIG.DEFAULTILEDIMX ; // px + this.tiledimy = CONFIG.DEFAULTILEDIMY; // px + this.dimlog = Math.log2(this.tileDim); //log2(TileDim) + this.dkey = false; // is 'd' key depressed? (for delete) + this.tiles32 = []; // all tiles from tilemap (32x32) + this.tiles16 = []; + this.fudgetiles = []; + this.g_layers = []; // level layers + + } + + var instance; + return { + getInstance: function(){ + if (instance == null) { + instance = new ContextSingleton(); + // Hide the constructor so the returned object can't be new'd... + instance.constructor = null; + } + return instance; + } + }; +})(); + +// global shared state between all panes +export let g_ctx = ContextCreate.getInstance(); \ No newline at end of file diff --git a/patches/src/editor/sehtmlui.js b/patches/src/editor/sehtmlui.js new file mode 100644 index 0000000000000000000000000000000000000000..2cc0fea8db30267dc4cc85e9d4a9d7b0493b3ef4 --- /dev/null +++ b/patches/src/editor/sehtmlui.js @@ -0,0 +1,66 @@ +import * as PIXI from 'pixi.js' +import { g_ctx } from './secontext.js' // global context +import * as CONFIG from './seconfig.js' + +// -- +// Set sizes and limits for HTML in main UI +// -- + +export function initMainHTMLWindow() { + document.getElementById("layer0pane").style.maxWidth = ""+CONFIG.htmlLayerPaneW+"px"; + document.getElementById("layer0pane").style.maxHeight = ""+CONFIG.htmlLayerPaneH+"px"; + document.getElementById("layer1pane").style.maxWidth = ""+CONFIG.htmlLayerPaneW+"px"; + document.getElementById("layer1pane").style.maxHeight = ""+CONFIG.htmlLayerPaneH+"px"; + document.getElementById("layer2pane").style.maxWidth = ""+CONFIG.htmlLayerPaneW+"px"; + document.getElementById("layer2pane").style.maxHeight = ""+CONFIG.htmlLayerPaneH+"px"; + document.getElementById("layer3pane").style.maxWidth = ""+CONFIG.htmlLayerPaneW+"px"; + document.getElementById("layer3pane").style.maxHeight = ""+CONFIG.htmlLayerPaneH+"px"; + + document.getElementById("tilesetpane").style.maxWidth = ""+CONFIG.htmlTilesetPaneW+"px"; + document.getElementById("tilesetpane").style.maxHeight = ""+CONFIG.htmlTilesetPaneH+"px"; + document.getElementById("compositepane").style.maxWidth = ""+CONFIG.htmlCompositePaneW+"px"; + document.getElementById("compositepane").style.maxHeight = ""+CONFIG.htmlCompositePaneH+"px"; + + // hide map tab + let mappane = document.getElementById("map"); + mappane.style.display = "none"; +} + +// -- +// Initialize handlers loading a PNG file into the composite window +// -- + +export function initCompositePNGLoader() { + const fileInput = document.getElementById('compositepng'); + fileInput.onchange = (evt) => { + if (!window.FileReader) return; // Browser is not compatible + if (g_ctx.debug_flag) { + console.log("compositepng ", fileInput.files[0].name); + } + let bgname = fileInput.files[0].name; + + const texture = PIXI.Texture.from("./"+bgname); + const bg = new PIXI.Sprite(texture); + bg.zIndex = 0; + g_ctx.composite.container.addChild(bg); + } +} +// -- +// initailized handler to load a new tileset +// -- + +export function initTilesetLoader(callme) { + const fileInput = document.getElementById('tilesetfile'); + fileInput.onchange = async (evt) => { + if (!window.FileReader) return; // Browser is not compatible + if (g_ctx.debug_flag) { + console.log("spritesheet ", fileInput.files[0].name); + } + g_ctx.tilesetpath = "./spritesheets/"+fileInput.files[0].name; + + g_ctx.tiledimx = 16; + g_ctx.tiledimy = 16; + + callme(); + } +} diff --git a/patches/src/editor/spritefile.js b/patches/src/editor/spritefile.js new file mode 100644 index 0000000000000000000000000000000000000000..982eaaf812d6c860fa5004d74825257b31de4517 --- /dev/null +++ b/patches/src/editor/spritefile.js @@ -0,0 +1,112 @@ +import * as CONFIG from './seconfig.js' +import * as UTIL from './eutils.js' +import { g_ctx } from './secontext.js' // global context + + +function generate_preamble() { + const preamble = '' + + '{"frames": {\n' + + '\n'; + return preamble; +} + +// Function to download data to a file +function download(data, filename, type) { + var file = new Blob([data], {type: type}); + if (window.navigator.msSaveOrOpenBlob) // IE10+ + window.navigator.msSaveOrOpenBlob(file, filename); + else { // Others + var a = document.createElement("a"), + url = URL.createObjectURL(file); + a.href = url; + a.download = filename; + document.body.appendChild(a); + a.click(); + setTimeout(function() { + document.body.removeChild(a); + window.URL.revokeObjectURL(url); + }, 0); + } +} + +export function generate_sprite_file() { + + let layer0 = g_ctx.g_layers[0]; + console.log("generate_sprite_file"); + + let text = generate_preamble(); + + let animations = Array.from(Array(CONFIG.leveltileheight), () => new Array().fill(null)); + + for (let row = 0; row < CONFIG.leveltileheight; row++) { + if (!layer0.tilearray[row][0]) { + // FIXME + // Assume row is empty if first tile is. + continue; + } + + for (let x = 0; x < layer0.tilearray[row].length; x++) { + + //"pixels_large1.png": + // { + // "frame": {"x":0,"y":192,"w":32,"h":64}, + // "rotated": false, + // "trimmed": true, + // "spriteSourceSize": {"x":0,"y":0,"w":32,"h":64}, + // "sourceSize": {"w":32,"h":64} + // }, + + let framename = '"tile' + row + "_" + x + '"'; + + animations[row].push(framename); + let frame = layer0.tilearray[row][x]; + text += framename + ": { \n"; + text += '\t"frame": {'; + text += '"x": '+ frame.tspx[0]+ ', "y": '+ frame.tspx[1]+ ', "w": '+ g_ctx.tiledimx+ ', "h": '+ g_ctx.tiledimy+ ' },\n'; + text += '\t"rotated": false,\n'; + text += '\t"trimmed": true,\n'; + text += '\t"spriteSourceSize": {'; + text += '"x":0, "y":0, "w": '+ g_ctx.tiledimx+ ', "h": '+ g_ctx.tiledimy+ ' },\n'; + text += '\t"sourceSize": {'; + text += '"w": '+ g_ctx.tiledimx+ ', "h": '+ g_ctx.tiledimy+ ' }\n'; + text += '\t}'; + + text += (x === layer0.tilearray[row].length - 1)? '\n':',\n' + } + } + text += '},\n'; + text += '"animations": {\n'; + + for (let row = 0; row < CONFIG.leveltileheight; row++) { + if(animations[row].length == 0) { + continue; + } + text += '"row'+row+'" : ['; + for (let x = 0; x < animations[row].length; x++){ + text += ''+animations[row][x]; + if (x < animations[row].length - 1){ + text += ','; + } + } + text += "],\n" + } + + // remove the trailing comma + text = text.slice(0,-2); + text += '\n'; + + + text += '},\n'; + text += '"meta": {\n'; + text += '\t"image": "'+ g_ctx.tilesetpath+'",\n' + text += '\t"format": "RGBA8888",\n'; + text += '\t"scale": "1"\n'; + text += '}\n'; + text += '}\n'; + + //console.log(text); + let filename = g_ctx.tilesetpath.split('/').slice(-1)[0]; + filename = filename.split('.')[0]; + console.log("spritefile: saving to file ",filename); + UTIL.download(text, filename+".json", "text/plain"); +} \ No newline at end of file diff --git a/patches/src/editor/spritesheets/campfire.png b/patches/src/editor/spritesheets/campfire.png new file mode 100644 index 0000000000000000000000000000000000000000..383e2811c5d8b6f226aefbf6ad4552295bfd801f Binary files /dev/null and b/patches/src/editor/spritesheets/campfire.png differ diff --git a/patches/src/editor/spritesheets/doll.png b/patches/src/editor/spritesheets/doll.png new file mode 100644 index 0000000000000000000000000000000000000000..6c7b96eb62703f7a6004aaf3b3aaa24612d983ab Binary files /dev/null and b/patches/src/editor/spritesheets/doll.png differ diff --git a/patches/src/editor/spritesheets/gentlesparkle32.png b/patches/src/editor/spritesheets/gentlesparkle32.png new file mode 100644 index 0000000000000000000000000000000000000000..ef7085d8115c9a576236d09fc1d27c1c11d0f972 Binary files /dev/null and b/patches/src/editor/spritesheets/gentlesparkle32.png differ diff --git a/patches/src/editor/spritesheets/gentlewaterfall32.png b/patches/src/editor/spritesheets/gentlewaterfall32.png new file mode 100644 index 0000000000000000000000000000000000000000..ff07b1cc05859cbe6e2de1938428ac1e52a3f4fc Binary files /dev/null and b/patches/src/editor/spritesheets/gentlewaterfall32.png differ diff --git a/patches/src/editor/spritesheets/peeps.png b/patches/src/editor/spritesheets/peeps.png new file mode 100644 index 0000000000000000000000000000000000000000..6a5f0a35fa41133a9099092ab6f61ce75b100b28 Binary files /dev/null and b/patches/src/editor/spritesheets/peeps.png differ diff --git a/patches/src/editor/spritesheets/tall.png b/patches/src/editor/spritesheets/tall.png new file mode 100644 index 0000000000000000000000000000000000000000..6d7a66ed6eb77cbfee392b68d141f589a2317160 Binary files /dev/null and b/patches/src/editor/spritesheets/tall.png differ diff --git a/patches/src/editor/spritesheets/windmill.png b/patches/src/editor/spritesheets/windmill.png new file mode 100644 index 0000000000000000000000000000000000000000..ca6f0a8b738033bf3ccee63bd99cbcecbf72584f Binary files /dev/null and b/patches/src/editor/spritesheets/windmill.png differ diff --git a/patches/src/editor/spritesheets/women.png b/patches/src/editor/spritesheets/women.png new file mode 100644 index 0000000000000000000000000000000000000000..dceb5e05c522b112421f65629cf3862ed85747f9 Binary files /dev/null and b/patches/src/editor/spritesheets/women.png differ diff --git a/patches/src/editor/undo.js b/patches/src/editor/undo.js new file mode 100644 index 0000000000000000000000000000000000000000..770dfd18140d7953bf9049dc694bf3215f476d45 --- /dev/null +++ b/patches/src/editor/undo.js @@ -0,0 +1,32 @@ + +const UNDO_STAX_MAX_LEN = 16 + +let undo_stack = []; +let undoqueu = []; + +export function undo_mark_task_start(layer) { + undoqueu = []; + undoqueu.push(layer); +} + +export function undo_add_index_to_task(tileindex) { + undoqueu.push(tileindex); +} + +export function undo_mark_task_end() { + undo_stack.push(undoqueu); + if (undo_stack.length > UNDO_STAX_MAX_LEN){ + undo_stack.shift(); + } +} + +// utility function for adding a single tile as a task +export function undo_add_single_index_as_task(layer, tileindex) { + undo_mark_task_start(layer); + undo_add_index_to_task(tileindex); + undo_mark_task_end(); +} + +export function undo_pop() { + return undo_stack.pop(); +} \ No newline at end of file diff --git a/patches/src/editor/windmill.json b/patches/src/editor/windmill.json new file mode 100644 index 0000000000000000000000000000000000000000..34ce53cff8a3cd2e275e07a575ca6cf3a1a1e948 --- /dev/null +++ b/patches/src/editor/windmill.json @@ -0,0 +1,77 @@ +{"frames": { + +"pixels_large1.png": +{ + "frame": {"x":0,"y":0,"w":208,"h":208}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":208,"h":208}, + "sourceSize": {"w":208,"h":208} +}, +"pixels_large2.png": +{ + "frame": {"x":208,"y":0,"w":208,"h":208}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":208,"h":208}, + "sourceSize": {"w":208,"h":208} +}, +"pixels_large3.png": +{ + "frame": {"x":416,"y":0,"w":208,"h":208}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":208,"h":208}, + "sourceSize": {"w":208,"h":208} +}, +"pixels_large4.png": +{ + "frame": {"x":0,"y":208,"w":208,"h":208}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":208,"h":208}, + "sourceSize": {"w":208,"h":208} +}, +"pixels_large5.png": +{ + "frame": {"x":208,"y":208,"w":208,"h":208}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":208,"h":208}, + "sourceSize": {"w":208,"h":208} +}, +"pixels_large6.png": +{ + "frame": {"x":416,"y":208,"w":208,"h":208}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":208,"h":208}, + "sourceSize": {"w":208,"h":208} +}, +"pixels_large7.png": +{ + "frame": {"x":0,"y":416,"w":208,"h":208}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":208,"h":208}, + "sourceSize": {"w":208,"h":208} +}, +"pixels_large8.png": +{ + "frame": {"x":208,"y":416,"w":208,"h":208}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":208,"h":208}, + "sourceSize": {"w":208,"h":208} +} +}, +"animations": { + "row0": ["pixels_large1.png","pixels_large2.png","pixels_large3.png","pixels_large4.png","pixels_large5.png","pixels_large6.png","pixels_large7.png","pixels_large8.png"] +}, +"meta": { + "image": "./spritesheets/windmill.png", + "format": "RGBA8888", + "size": {"w":624,"h":624}, + "scale": "1" +} +} diff --git a/patches/src/hooks/sendInput.ts b/patches/src/hooks/sendInput.ts new file mode 100644 index 0000000000000000000000000000000000000000..bb4e9a678f5f77fb9b05b5c7738635d303d14866 --- /dev/null +++ b/patches/src/hooks/sendInput.ts @@ -0,0 +1,51 @@ +import { ConvexReactClient, useConvex } from 'convex/react'; +import { InputArgs, InputReturnValue, Inputs } from '../../convex/aiTown/inputs'; +import { api } from '../../convex/_generated/api'; +import { Id } from '../../convex/_generated/dataModel'; + +export async function waitForInput(convex: ConvexReactClient, inputId: Id<'inputs'>) { + const watch = convex.watchQuery(api.aiTown.main.inputStatus, { inputId }); + let result = watch.localQueryResult(); + // The result's undefined if the query's loading and null if the input hasn't + // been processed yet. + if (result === undefined || result === null) { + let dispose: undefined | (() => void); + try { + await new Promise((resolve, reject) => { + dispose = watch.onUpdate(() => { + try { + result = watch.localQueryResult(); + } catch (e: any) { + reject(e); + return; + } + if (result !== undefined && result !== null) { + resolve(); + } + }); + }); + } finally { + if (dispose) { + dispose(); + } + } + } + if (!result) { + throw new Error(`Input ${inputId} was never processed.`); + } + if (result.kind === 'error') { + throw new Error(result.message); + } + return result.value; +} + +export function useSendInput( + engineId: Id<'engines'>, + name: Name, +): (args: InputArgs) => Promise> { + const convex = useConvex(); + return async (args) => { + const inputId = await convex.mutation(api.world.sendWorldInput, { engineId, name, args }); + return await waitForInput(convex, inputId); + }; +} diff --git a/patches/src/hooks/serverGame.ts b/patches/src/hooks/serverGame.ts new file mode 100644 index 0000000000000000000000000000000000000000..3b0c2f217131acfca6199e21e9105bf9184a6b62 --- /dev/null +++ b/patches/src/hooks/serverGame.ts @@ -0,0 +1,44 @@ +import { GameId } from '../../convex/aiTown/ids.ts'; +import { AgentDescription } from '../../convex/aiTown/agentDescription.ts'; +import { PlayerDescription } from '../../convex/aiTown/playerDescription.ts'; +import { World } from '../../convex/aiTown/world.ts'; +import { WorldMap } from '../../convex/aiTown/worldMap.ts'; +import { Id } from '../../convex/_generated/dataModel'; +import { useMemo } from 'react'; +import { useQuery } from 'convex/react'; +import { api } from '../../convex/_generated/api'; +import { parseMap } from '../../convex/util/object.ts'; + +export type ServerGame = { + world: World; + playerDescriptions: Map, PlayerDescription>; + agentDescriptions: Map, AgentDescription>; + worldMap: WorldMap; +}; + +// TODO: This hook reparses the game state (even if we're not rerunning the query) +// when used in multiple components. Move this to a context to only parse it once. +export function useServerGame(worldId: Id<'worlds'> | undefined): ServerGame | undefined { + const worldState = useQuery(api.world.worldState, worldId ? { worldId } : 'skip'); + const descriptions = useQuery(api.world.gameDescriptions, worldId ? { worldId } : 'skip'); + const game = useMemo(() => { + if (!worldState || !descriptions) { + return undefined; + } + return { + world: new World(worldState.world), + agentDescriptions: parseMap( + descriptions.agentDescriptions, + AgentDescription, + (p) => p.agentId, + ), + playerDescriptions: parseMap( + descriptions.playerDescriptions, + PlayerDescription, + (p) => p.playerId, + ), + worldMap: new WorldMap(descriptions.worldMap), + }; + }, [worldState, descriptions]); + return game; +} diff --git a/patches/src/hooks/useHistoricalTime.ts b/patches/src/hooks/useHistoricalTime.ts new file mode 100644 index 0000000000000000000000000000000000000000..83aeba7816c5473a8a80678a7d20769195c51f4f --- /dev/null +++ b/patches/src/hooks/useHistoricalTime.ts @@ -0,0 +1,143 @@ +import { Doc } from '../../convex/_generated/dataModel'; +import { useEffect, useRef, useState } from 'react'; + +export function useHistoricalTime(engineStatus?: Doc<'engines'>) { + const timeManager = useRef(new HistoricalTimeManager()); + const rafRef = useRef(); + const [historicalTime, setHistoricalTime] = useState(undefined); + if (engineStatus) { + timeManager.current.receive(engineStatus); + } + const updateTime = (performanceNow: number) => { + // We don't need sub-millisecond precision for interpolation, so just use `Date.now()`. + const now = Date.now(); + setHistoricalTime(timeManager.current.historicalServerTime(now)); + rafRef.current = requestAnimationFrame(updateTime); + }; + useEffect(() => { + rafRef.current = requestAnimationFrame(updateTime); + return () => cancelAnimationFrame(rafRef.current!); + }, []); + return { historicalTime, timeManager: timeManager.current }; +} + +type ServerTimeInterval = { + startTs: number; + endTs: number; +}; + +export class HistoricalTimeManager { + intervals: Array = []; + prevClientTs?: number; + prevServerTs?: number; + totalDuration: number = 0; + + latestEngineStatus?: Doc<'engines'>; + + receive(engineStatus: Doc<'engines'>) { + this.latestEngineStatus = engineStatus; + if (!engineStatus.currentTime || !engineStatus.lastStepTs) { + return; + } + const latest = this.intervals[this.intervals.length - 1]; + if (latest) { + if (latest.endTs === engineStatus.currentTime) { + return; + } + if (latest.endTs > engineStatus.currentTime) { + throw new Error(`Received out-of-order engine status`); + } + } + const newInterval = { + startTs: engineStatus.lastStepTs, + endTs: engineStatus.currentTime, + }; + this.intervals.push(newInterval); + this.totalDuration += newInterval.endTs - newInterval.startTs; + } + + historicalServerTime(clientNow: number): number | undefined { + if (this.intervals.length == 0) { + return undefined; + } + if (clientNow === this.prevClientTs) { + return this.prevServerTs; + } + // If this is our first time simulating, start at the beginning of the buffer. + const prevClientTs = this.prevClientTs ?? clientNow; + const prevServerTs = this.prevServerTs ?? this.intervals[0].startTs; + const lastServerTs = this.intervals[this.intervals.length - 1].endTs; + + // Simple rate adjustment: run time at 1.2 speed if we're more than 1s behind and + // 0.8 speed if we only have 100ms of buffer left. A more sophisticated approach + // would be to continuously adjust the rate based on the size of the buffer. + const bufferDuration = lastServerTs - prevServerTs; + let rate = 1; + if (bufferDuration < SOFT_MIN_SERVER_BUFFER_AGE) { + rate = 0.8; + } else if (bufferDuration > SOFT_MAX_SERVER_BUFFER_AGE) { + rate = 1.2; + } + let serverTs = Math.max( + prevServerTs + (clientNow - prevClientTs) * rate, + // Jump forward if we're too far behind. + lastServerTs - MAX_SERVER_BUFFER_AGE, + ); + + let chosen = null; + for (let i = 0; i < this.intervals.length; i++) { + const snapshot = this.intervals[i]; + // We're past this snapshot, continue to the next one. + if (snapshot.endTs < serverTs) { + continue; + } + // We're cleanly within this snapshot. + if (serverTs >= snapshot.startTs) { + chosen = i; + break; + } + // We've gone past the desired timestamp, which implies a gap in our server state. + // Jump time forward to the beginning of this snapshot. + if (serverTs < snapshot.startTs) { + serverTs = snapshot.startTs; + chosen = i; + } + } + if (chosen === null) { + serverTs = this.intervals.at(-1)!.endTs; + chosen = this.intervals.length - 1; + } + // Time only moves forward, so we can trim all of the snapshots before our chosen one. + const toTrim = Math.max(chosen - 1, 0); + if (toTrim > 0) { + for (const snapshot of this.intervals.slice(0, toTrim)) { + this.totalDuration -= snapshot.endTs - snapshot.startTs; + } + this.intervals = this.intervals.slice(toTrim); + } + + this.prevClientTs = clientNow; + this.prevServerTs = serverTs; + + return serverTs; + } + + bufferHealth(): number { + if (!this.intervals.length) { + return 0; + } + const lastServerTs = this.prevServerTs ?? this.intervals[0].startTs; + return this.intervals[this.intervals.length - 1].endTs - lastServerTs; + } + + clockSkew(): number { + if (!this.prevClientTs || !this.prevServerTs) { + return 0; + } + return this.prevClientTs - this.prevServerTs; + } +} + +const MAX_SERVER_BUFFER_AGE = 1500; +const SOFT_MAX_SERVER_BUFFER_AGE = 1250; +const SOFT_MIN_SERVER_BUFFER_AGE = 250; diff --git a/patches/src/hooks/useHistoricalValue.ts b/patches/src/hooks/useHistoricalValue.ts new file mode 100644 index 0000000000000000000000000000000000000000..8f896ce7fcc32bfea3bf89ab6ff0893a4a991c77 --- /dev/null +++ b/patches/src/hooks/useHistoricalValue.ts @@ -0,0 +1,78 @@ +import { FieldConfig, History, unpackSampleRecord } from '../../convex/engine/historicalObject'; +import { useMemo, useRef } from 'react'; + +export function useHistoricalValue>( + fields: FieldConfig, + historicalTime: number | undefined, + value: T | undefined, + history: ArrayBuffer | undefined, +): T | undefined { + const manager = useRef(new HistoryManager()); + const sampleRecord: Record | undefined = useMemo(() => { + if (!value || !history) { + return undefined; + } + if (!(history instanceof ArrayBuffer)) { + throw new Error(`Expected ArrayBuffer, found ${typeof history}`); + } + return unpackSampleRecord(fields, history); + }, [value && history]); + if (sampleRecord) { + manager.current.receive(sampleRecord); + } + if (value === undefined) { + return undefined; + } + if (!historicalTime) { + return value; + } + const historicalFields = manager.current.query(historicalTime); + return { ...value, ...historicalFields }; +} + +class HistoryManager { + histories: Record = {}; + + receive(sampleRecord: Record) { + for (const [fieldName, history] of Object.entries(sampleRecord)) { + let histories = this.histories[fieldName]; + if (!histories) { + histories = []; + this.histories[fieldName] = histories; + } + if (histories[histories.length - 1] == history) { + continue; + } + histories.push(history); + } + } + + query(historicalTime: number): Record { + const result: Record = {}; + for (const [fieldName, histories] of Object.entries(this.histories)) { + if (histories.length == 0) { + continue; + } + let foundIndex = null; + let currentValue = histories[0].initialValue; + for (let i = 0; i < histories.length; i++) { + const history = histories[i]; + for (const sample of history.samples) { + if (sample.time > historicalTime) { + foundIndex = i; + break; + } + currentValue = sample.value; + } + if (foundIndex !== null) { + break; + } + } + if (foundIndex !== null) { + this.histories[fieldName] = histories.slice(foundIndex); + } + result[fieldName] = currentValue; + } + return result; + } +} diff --git a/patches/src/hooks/useWorldHeartbeat.ts b/patches/src/hooks/useWorldHeartbeat.ts new file mode 100644 index 0000000000000000000000000000000000000000..cabbd8dac63a4bc152d140d2a5fa4c5230c2a30b --- /dev/null +++ b/patches/src/hooks/useWorldHeartbeat.ts @@ -0,0 +1,30 @@ +import { useMutation, useQuery } from 'convex/react'; +import { useEffect } from 'react'; +import { api } from '../../convex/_generated/api'; +import { WORLD_HEARTBEAT_INTERVAL } from '../../convex/constants'; + +export function useWorldHeartbeat() { + const worldStatus = useQuery(api.world.defaultWorldStatus); + const worldId = worldStatus?.worldId; + + // Send a periodic heartbeat to our world to keep it alive. + const heartbeat = useMutation(api.world.heartbeatWorld); + useEffect(() => { + const sendHeartBeat = () => { + if (!worldStatus) { + return; + } + // Don't send a heartbeat if we've observed one sufficiently close + // to the present. + if (Date.now() - WORLD_HEARTBEAT_INTERVAL / 2 < worldStatus.lastViewed) { + return; + } + void heartbeat({ worldId: worldStatus.worldId }); + }; + sendHeartBeat(); + const id = setInterval(sendHeartBeat, WORLD_HEARTBEAT_INTERVAL); + return () => clearInterval(id); + // Rerun if the `worldId` changes but not `worldStatus`, since don't want to + // resend the heartbeat whenever its last viewed timestamp changes. + }, [worldId, heartbeat]); +} diff --git a/patches/src/index.css b/patches/src/index.css new file mode 100644 index 0000000000000000000000000000000000000000..c32de4b047723719ecc0a0137e5a338ae5f386cb --- /dev/null +++ b/patches/src/index.css @@ -0,0 +1,185 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +@font-face { + font-family: 'Upheaval Pro'; + src: url(/assets/fonts/upheaval_pro.ttf); +} + +@font-face { + font-family: 'VCR OSD Mono'; + src: url(/assets/fonts/vcr_osd_mono.ttf); +} + +.font-display { + font-family: 'Upheaval Pro', 'sans-serif'; +} + +.font-body { + font-family: 'VCR OSD Mono', 'monospace'; +} + +.font-system { + font-family: system-ui, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; +} + +:root { + --foreground-rgb: 0, 0, 0; + --background-start-rgb: 214, 219, 220; + --background-end-rgb: 255, 255, 255; +} + +@media (prefers-color-scheme: dark) { + :root { + --foreground-rgb: 255, 255, 255; + --background-start-rgb: 0, 0, 0; + --background-end-rgb: 0, 0, 0; + } +} + +body { + color: rgb(var(--foreground-rgb)); + background: linear-gradient(to bottom, transparent, rgb(var(--background-end-rgb))) + rgb(var(--background-start-rgb)); +} + +.game-background { + background: linear-gradient(rgba(41, 41, 41, 0.8), rgba(41, 41, 41, 0.8)), + url(../assets/background.webp); + background-blend-mode: hard-light; + background-position: center; + background-repeat: no-repeat; + background-size: cover; + background-attachment: fixed; +} + +.game-title { + background: linear-gradient(to bottom, #fec742, #dd7c42); + background-clip: text; + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + filter: drop-shadow(0px 0.08em 0px #6e2146); +} + +.game-frame { + border-width: 36px; + border-image-source: url(../assets/ui/frame.svg); + border-image-repeat: stretch; + border-image-slice: 25%; +} + +.game-progress-bar { + border: 5px solid rgb(23, 20, 33); +} + +@keyframes moveStripes { + to { + background-position: calc(100% + 28px) 0; + } +} + +.game-progress-bar-progress { + background: repeating-linear-gradient(135deg, white, white 10px, #dfdfdf 10px, #dfdfdf 20px); + background-size: 200% 100%; + background-position: 100% 0; + animation: moveStripes 0.5s linear infinite; +} + +@media screen and (min-width: 640px) { + .game-frame { + border-width: 48px; + } +} + +.shadow-solid { + text-shadow: 0 0.1em 0 rgba(0, 0, 0, 0.5); +} + +.bubble { + border-width: 30px; + border-image-source: url(../assets/ui/bubble-left.svg); + border-image-repeat: stretch; + border-image-slice: 20%; +} + +.bubble-mine { + border-image-source: url(../assets/ui/bubble-right.svg); +} + +.box { + border-width: 12px; + border-image-source: url(../assets/ui/box.svg); + border-image-repeat: stretch; + border-image-slice: 12.5%; +} + +.desc { + border-width: 56px; + border-image-source: url(../assets/ui/desc.svg); + border-image-repeat: stretch; + border-image-slice: 28%; +} + +.chats { + border-width: 24px; + border-image-source: url(../assets/ui/chats.svg); + border-image-repeat: stretch; + border-image-slice: 40%; +} + +.login-prompt { + border-width: 48px; + border-image-source: url(../assets/ui/jewel_box.svg); + border-image-repeat: stretch; + border-image-slice: 40%; +} + +.button { + border-width: 1em; + border-image-source: url(../assets/ui/button.svg); + border-image-repeat: stretch; + border-image-slice: 25%; + cursor: pointer; +} + +.button span { + display: inline-block; + transform: translateY(-15%); +} + +@media (max-width: 640px) { + .button { + height: 40px; + border-width: 0.75em; + font-size: 16px; + } + + .button > div, + .button > span { + vertical-align: top; + line-height: 1; + } +} + +.button:hover { + opacity: 0.8; +} + +.button:active { + /* Inlining this image to avoid flashing during loading */ + border-image-source: url("data:image/svg+xml,%3Csvg width='16' height='16' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Crect x='1' y='2' width='14' height='13' fill='%23181425'/%3E%3Crect x='2' y='1' width='12' height='15' fill='%23181425'/%3E%3Crect y='3' width='16' height='11' fill='%23181425'/%3E%3Crect x='2' y='14' width='12' height='1' fill='%23262B44'/%3E%3Crect x='1' y='3' width='14' height='11' fill='%233A4466'/%3E%3Crect x='2' y='2' width='12' height='9' fill='%233A4466'/%3E%3Crect x='1' y='13' width='1' height='1' fill='%23262B44'/%3E%3Crect x='14' y='13' width='1' height='1' fill='%23262B44'/%3E%3C/svg%3E%0A"); +} + +.button:active span { + transform: none; +} + +p[contenteditable='true']:empty::before { + content: attr(placeholder); + color: #aaa; +} + +.shape-top-left-corner { + clip-path: polygon(0 0, 100% 0, 0 100%); +} diff --git a/patches/src/main.tsx b/patches/src/main.tsx new file mode 100644 index 0000000000000000000000000000000000000000..0c1b20610f0fdf9e763dc7d7b99ade867ccbc515 --- /dev/null +++ b/patches/src/main.tsx @@ -0,0 +1,15 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import Home from './App.tsx'; +import './index.css'; +import 'uplot/dist/uPlot.min.css'; +import 'react-toastify/dist/ReactToastify.css'; +import ConvexClientProvider from './components/ConvexClientProvider.tsx'; + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + + + , +); diff --git a/patches/src/toasts.ts b/patches/src/toasts.ts new file mode 100644 index 0000000000000000000000000000000000000000..df7b056220060e4d02bab0b8d7a1fad83acc0485 --- /dev/null +++ b/patches/src/toasts.ts @@ -0,0 +1,10 @@ +import { toast } from 'react-toastify'; + +export async function toastOnError(promise: Promise): Promise { + try { + return await promise; + } catch (error: any) { + toast.error(error.message); + throw error; + } +} diff --git a/patches/src/vite-env.d.ts b/patches/src/vite-env.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..7d0ff9efa9d6b0fd823a2f371308a9f92c7771c1 --- /dev/null +++ b/patches/src/vite-env.d.ts @@ -0,0 +1 @@ +///