Merge branch 'valid-pet-poses'
This commit is contained in:
commit
3f2f77c35d
9 changed files with 154 additions and 39 deletions
6
api/validPetPoses.js
Normal file
6
api/validPetPoses.js
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
import getValidPetPoses from "../src/server/getValidPetPoses";
|
||||||
|
|
||||||
|
export default async (req, res) => {
|
||||||
|
const buffer = await getValidPetPoses();
|
||||||
|
res.status(200).send(buffer);
|
||||||
|
};
|
|
@ -26,7 +26,8 @@
|
||||||
"react-dom": "^16.13.1",
|
"react-dom": "^16.13.1",
|
||||||
"react-helmet": "^6.0.0",
|
"react-helmet": "^6.0.0",
|
||||||
"react-scripts": "3.4.1",
|
"react-scripts": "3.4.1",
|
||||||
"react-transition-group": "^4.3.0"
|
"react-transition-group": "^4.3.0",
|
||||||
|
"use-http": "^1.0.10"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "react-scripts start",
|
"start": "react-scripts start",
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import gql from "graphql-tag";
|
import gql from "graphql-tag";
|
||||||
|
import useFetch from "use-http";
|
||||||
import { useQuery } from "@apollo/react-hooks";
|
import { useQuery } from "@apollo/react-hooks";
|
||||||
import { Box, Flex, Select, Text, useToast } from "@chakra-ui/core";
|
import { Box, Flex, Select, Text, useToast } from "@chakra-ui/core";
|
||||||
|
|
||||||
|
@ -13,7 +14,7 @@ import { Delay } from "./util";
|
||||||
*/
|
*/
|
||||||
function SpeciesColorPicker({ outfitState, dispatchToOutfit }) {
|
function SpeciesColorPicker({ outfitState, dispatchToOutfit }) {
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
const { loading, error, data } = useQuery(gql`
|
const { loading: loadingMeta, error: errorMeta, data: meta } = useQuery(gql`
|
||||||
query {
|
query {
|
||||||
allSpecies {
|
allSpecies {
|
||||||
id
|
id
|
||||||
|
@ -24,35 +25,24 @@ function SpeciesColorPicker({ outfitState, dispatchToOutfit }) {
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
}
|
}
|
||||||
|
|
||||||
allValidSpeciesColorPairs {
|
|
||||||
species {
|
|
||||||
id
|
|
||||||
}
|
|
||||||
color {
|
|
||||||
id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
`);
|
`);
|
||||||
|
const {
|
||||||
const allColors = (data && [...data.allColors]) || [];
|
loading: loadingValids,
|
||||||
allColors.sort((a, b) => a.name.localeCompare(b.name));
|
error: errorValids,
|
||||||
const allSpecies = (data && [...data.allSpecies]) || [];
|
data: validsBuffer,
|
||||||
allSpecies.sort((a, b) => a.name.localeCompare(b.name));
|
} = useFetch("/api/validPetPoses", { responseType: "arrayBuffer" }, []);
|
||||||
|
const valids = React.useMemo(
|
||||||
// Build a large Set where we can quickly look up species/color pairs!
|
() => validsBuffer && new DataView(validsBuffer),
|
||||||
const allValidSpeciesColorPairs = React.useMemo(
|
[validsBuffer]
|
||||||
() =>
|
|
||||||
new Set(
|
|
||||||
((data && data.allValidSpeciesColorPairs) || []).map(
|
|
||||||
(p) => `${p.species.id},${p.color.id}`
|
|
||||||
)
|
|
||||||
),
|
|
||||||
[data]
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (loading) {
|
const allColors = (meta && [...meta.allColors]) || [];
|
||||||
|
allColors.sort((a, b) => a.name.localeCompare(b.name));
|
||||||
|
const allSpecies = (meta && [...meta.allSpecies]) || [];
|
||||||
|
allSpecies.sort((a, b) => a.name.localeCompare(b.name));
|
||||||
|
|
||||||
|
if (loadingMeta || loadingValids) {
|
||||||
return (
|
return (
|
||||||
<Delay ms={5000}>
|
<Delay ms={5000}>
|
||||||
<Text color="gray.50" textShadow="md">
|
<Text color="gray.50" textShadow="md">
|
||||||
|
@ -62,7 +52,7 @@ function SpeciesColorPicker({ outfitState, dispatchToOutfit }) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error) {
|
if (errorMeta || errorValids) {
|
||||||
return (
|
return (
|
||||||
<Text color="gray.50" textShadow="md">
|
<Text color="gray.50" textShadow="md">
|
||||||
Error loading species/color data.
|
Error loading species/color data.
|
||||||
|
@ -75,8 +65,7 @@ function SpeciesColorPicker({ outfitState, dispatchToOutfit }) {
|
||||||
const onChangeColor = (e) => {
|
const onChangeColor = (e) => {
|
||||||
const speciesId = outfitState.speciesId;
|
const speciesId = outfitState.speciesId;
|
||||||
const colorId = e.target.value;
|
const colorId = e.target.value;
|
||||||
const pair = `${speciesId},${colorId}`;
|
if (pairIsValid(valids, meta, speciesId, colorId)) {
|
||||||
if (allValidSpeciesColorPairs.has(pair)) {
|
|
||||||
dispatchToOutfit({ type: "changeColor", colorId: e.target.value });
|
dispatchToOutfit({ type: "changeColor", colorId: e.target.value });
|
||||||
} else {
|
} else {
|
||||||
const species = allSpecies.find((s) => s.id === speciesId);
|
const species = allSpecies.find((s) => s.id === speciesId);
|
||||||
|
@ -93,14 +82,14 @@ function SpeciesColorPicker({ outfitState, dispatchToOutfit }) {
|
||||||
const onChangeSpecies = (e) => {
|
const onChangeSpecies = (e) => {
|
||||||
const colorId = outfitState.colorId;
|
const colorId = outfitState.colorId;
|
||||||
const speciesId = e.target.value;
|
const speciesId = e.target.value;
|
||||||
const pair = `${speciesId},${colorId}`;
|
if (pairIsValid(valids, meta, speciesId, colorId)) {
|
||||||
if (allValidSpeciesColorPairs.has(pair)) {
|
|
||||||
dispatchToOutfit({ type: "changeSpecies", speciesId: e.target.value });
|
dispatchToOutfit({ type: "changeSpecies", speciesId: e.target.value });
|
||||||
} else {
|
} else {
|
||||||
const species = allSpecies.find((s) => s.id === speciesId);
|
const species = allSpecies.find((s) => s.id === speciesId);
|
||||||
const color = allColors.find((c) => c.id === colorId);
|
const color = allColors.find((c) => c.id === colorId);
|
||||||
toast({
|
toast({
|
||||||
title: `We haven't seen a ${color.name} ${species.name} before! 😓`,
|
title: `We haven't seen a ${color.name} ${species.name} before! 😓`,
|
||||||
|
status: "warning",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -144,4 +133,14 @@ function SpeciesColorPicker({ outfitState, dispatchToOutfit }) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function pairIsValid(valids, meta, speciesId, colorId) {
|
||||||
|
// Reading a bit table, owo!
|
||||||
|
const speciesIndex = speciesId - 1;
|
||||||
|
const colorIndex = colorId - 1;
|
||||||
|
const numColors = meta.allColors.length;
|
||||||
|
const pairByteIndex = speciesIndex * numColors + colorIndex;
|
||||||
|
const pairByte = valids.getUint8(pairByteIndex);
|
||||||
|
return pairByte !== 0;
|
||||||
|
}
|
||||||
|
|
||||||
export default SpeciesColorPicker;
|
export default SpeciesColorPicker;
|
||||||
|
|
BIN
src/server/__snapshots__/getValidPetPoses.test.js.snap
Normal file
BIN
src/server/__snapshots__/getValidPetPoses.test.js.snap
Normal file
Binary file not shown.
78
src/server/getValidPetPoses.js
Normal file
78
src/server/getValidPetPoses.js
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
import connectToDb from "./db";
|
||||||
|
|
||||||
|
import { getEmotion, getGenderPresentation } from "./util";
|
||||||
|
|
||||||
|
export default async function getValidPetPoses() {
|
||||||
|
const db = await connectToDb();
|
||||||
|
|
||||||
|
const numSpeciesPromise = getNumSpecies(db);
|
||||||
|
const numColorsPromise = getNumColors(db);
|
||||||
|
const poseTuplesPromise = getPoseTuples(db);
|
||||||
|
|
||||||
|
const [numSpecies, numColors, poseTuples] = await Promise.all([
|
||||||
|
numSpeciesPromise,
|
||||||
|
numColorsPromise,
|
||||||
|
poseTuplesPromise,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const poseStrs = new Set();
|
||||||
|
for (const poseTuple of poseTuples) {
|
||||||
|
const { species_id, color_id, mood_id, female } = poseTuple;
|
||||||
|
const emotion = getEmotion(mood_id);
|
||||||
|
const genderPresentation = getGenderPresentation(female);
|
||||||
|
const poseStr = `${species_id}-${color_id}-${emotion}-${genderPresentation}`;
|
||||||
|
poseStrs.add(poseStr);
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasPose(speciesId, colorId, emotion, genderPresentation) {
|
||||||
|
const poseStr = `${speciesId}-${colorId}-${emotion}-${genderPresentation}`;
|
||||||
|
return poseStrs.has(poseStr);
|
||||||
|
}
|
||||||
|
|
||||||
|
const numPairs = numSpecies * numColors;
|
||||||
|
const buffer = Buffer.alloc(numPairs);
|
||||||
|
|
||||||
|
for (let speciesId = 1; speciesId <= numSpecies; speciesId++) {
|
||||||
|
const speciesIndex = speciesId - 1;
|
||||||
|
for (let colorId = 1; colorId <= numColors; colorId++) {
|
||||||
|
const colorIndex = colorId - 1;
|
||||||
|
|
||||||
|
let byte = 0;
|
||||||
|
byte += hasPose(speciesId, colorId, "HAPPY", "MASCULINE") ? 1 : 0;
|
||||||
|
byte <<= 1;
|
||||||
|
byte += hasPose(speciesId, colorId, "SAD", "MASCULINE") ? 1 : 0;
|
||||||
|
byte <<= 1;
|
||||||
|
byte += hasPose(speciesId, colorId, "SICK", "MASCULINE") ? 1 : 0;
|
||||||
|
byte <<= 1;
|
||||||
|
byte += hasPose(speciesId, colorId, "HAPPY", "FEMININE") ? 1 : 0;
|
||||||
|
byte <<= 1;
|
||||||
|
byte += hasPose(speciesId, colorId, "SAD", "FEMININE") ? 1 : 0;
|
||||||
|
byte <<= 1;
|
||||||
|
byte += hasPose(speciesId, colorId, "SICK", "FEMININE") ? 1 : 0;
|
||||||
|
|
||||||
|
buffer.writeUInt8(byte, speciesIndex * numColors + colorIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getNumSpecies(db) {
|
||||||
|
const [rows, _] = await db.query(`SELECT count(*) FROM species`);
|
||||||
|
return rows[0]["count(*)"];
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getNumColors(db) {
|
||||||
|
const [rows, _] = await db.query(
|
||||||
|
`SELECT count(*) FROM colors WHERE prank = 0`
|
||||||
|
);
|
||||||
|
return rows[0]["count(*)"];
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getPoseTuples(db) {
|
||||||
|
const [rows, _] = await db.query(`
|
||||||
|
SELECT DISTINCT species_id, color_id, mood_id, female FROM pet_states
|
||||||
|
INNER JOIN pet_types ON pet_types.id = pet_states.pet_type_id
|
||||||
|
WHERE mood_id IS NOT NULL AND female IS NOT NULL AND color_id >= 1`);
|
||||||
|
return rows;
|
||||||
|
}
|
8
src/server/getValidPetPoses.test.js
Normal file
8
src/server/getValidPetPoses.test.js
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import getValidPetPoses from "./getValidPetPoses";
|
||||||
|
|
||||||
|
describe("getValidPetPoses", () => {
|
||||||
|
it("gets them and writes them to a buffer", async () => {
|
||||||
|
const buffer = await getValidPetPoses();
|
||||||
|
expect(buffer.toString()).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
|
@ -101,8 +101,7 @@ const typeDefs = gql`
|
||||||
type Query {
|
type Query {
|
||||||
allColors: [Color!]!
|
allColors: [Color!]!
|
||||||
allSpecies: [Species!]!
|
allSpecies: [Species!]!
|
||||||
allValidSpeciesColorPairs: [SpeciesColorPair!]!
|
allValidSpeciesColorPairs: [SpeciesColorPair!]! # deprecated
|
||||||
|
|
||||||
items(ids: [ID!]!): [Item!]!
|
items(ids: [ID!]!): [Item!]!
|
||||||
itemSearch(query: String!): ItemSearchResult!
|
itemSearch(query: String!): ItemSearchResult!
|
||||||
itemSearchToFit(
|
itemSearchToFit(
|
||||||
|
|
|
@ -3,11 +3,11 @@ function capitalize(str) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function getEmotion(moodId) {
|
function getEmotion(moodId) {
|
||||||
if (moodId === "1") {
|
if (String(moodId) === "1") {
|
||||||
return "HAPPY";
|
return "HAPPY";
|
||||||
} else if (moodId === "2") {
|
} else if (String(moodId) === "2") {
|
||||||
return "SAD";
|
return "SAD";
|
||||||
} else if (moodId === "4") {
|
} else if (String(moodId) === "4") {
|
||||||
return "SICK";
|
return "SICK";
|
||||||
} else if (moodId === null) {
|
} else if (moodId === null) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -17,9 +17,9 @@ function getEmotion(moodId) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function getGenderPresentation(modelPetWasFemale) {
|
function getGenderPresentation(modelPetWasFemale) {
|
||||||
if (modelPetWasFemale === 1) {
|
if (String(modelPetWasFemale) === "1") {
|
||||||
return "FEMININE";
|
return "FEMININE";
|
||||||
} else if (modelPetWasFemale === 0) {
|
} else if (String(modelPetWasFemale) === "0") {
|
||||||
return "MASCULINE";
|
return "MASCULINE";
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
|
|
24
yarn.lock
24
yarn.lock
|
@ -11427,6 +11427,11 @@ url@^0.11.0:
|
||||||
punycode "1.3.2"
|
punycode "1.3.2"
|
||||||
querystring "0.2.0"
|
querystring "0.2.0"
|
||||||
|
|
||||||
|
urs@^0.0.4:
|
||||||
|
version "0.0.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/urs/-/urs-0.0.4.tgz#d559d660f2a468e0bb116e0b7b505af57cb59ae4"
|
||||||
|
integrity sha512-+QflFOKa9DmjWclPB2audGCV83uWUnTXHOxLPQyu7XXcaY9yQ4+Tb3UEm8m4N7abJ0kJUCUAQBpFlq6mx80j9g==
|
||||||
|
|
||||||
use-callback-ref@^1.2.1:
|
use-callback-ref@^1.2.1:
|
||||||
version "1.2.3"
|
version "1.2.3"
|
||||||
resolved "https://registry.yarnpkg.com/use-callback-ref/-/use-callback-ref-1.2.3.tgz#9f939dfb5740807bbf9dd79cdd4e99d27e827756"
|
resolved "https://registry.yarnpkg.com/use-callback-ref/-/use-callback-ref-1.2.3.tgz#9f939dfb5740807bbf9dd79cdd4e99d27e827756"
|
||||||
|
@ -11440,6 +11445,15 @@ use-dark-mode@2.3.1:
|
||||||
"@use-it/event-listener" "^0.1.2"
|
"@use-it/event-listener" "^0.1.2"
|
||||||
use-persisted-state "^0.3.0"
|
use-persisted-state "^0.3.0"
|
||||||
|
|
||||||
|
use-http@^1.0.10:
|
||||||
|
version "1.0.10"
|
||||||
|
resolved "https://registry.yarnpkg.com/use-http/-/use-http-1.0.10.tgz#d04da86c65552237ee13ede6218a79693b7fb452"
|
||||||
|
integrity sha512-KSxibM4WoSxnp4B366zVPOEWFadBO84yYtNEMEevupW/6V+D/Gme1Agx0cWwN6AYarrupcUoGT8P8M97jo+pzg==
|
||||||
|
dependencies:
|
||||||
|
urs "^0.0.4"
|
||||||
|
use-ssr "^1.0.22"
|
||||||
|
utility-types "^3.10.0"
|
||||||
|
|
||||||
use-persisted-state@^0.3.0:
|
use-persisted-state@^0.3.0:
|
||||||
version "0.3.0"
|
version "0.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/use-persisted-state/-/use-persisted-state-0.3.0.tgz#f8e3d2fd8eee67e0c86fd596c3ea3e8121c07402"
|
resolved "https://registry.yarnpkg.com/use-persisted-state/-/use-persisted-state-0.3.0.tgz#f8e3d2fd8eee67e0c86fd596c3ea3e8121c07402"
|
||||||
|
@ -11455,6 +11469,11 @@ use-sidecar@^1.0.1:
|
||||||
detect-node "^2.0.4"
|
detect-node "^2.0.4"
|
||||||
tslib "^1.9.3"
|
tslib "^1.9.3"
|
||||||
|
|
||||||
|
use-ssr@^1.0.22:
|
||||||
|
version "1.0.23"
|
||||||
|
resolved "https://registry.yarnpkg.com/use-ssr/-/use-ssr-1.0.23.tgz#3bde1e10cd01b3b61ab6386d7cddb72e74828bf8"
|
||||||
|
integrity sha512-5bvlssgROgPgIrnILJe2mJch4e2Id0/bVm1SQzqvPvEAXmlsinCCVHWK3a2iHcPat7PkdJHBo0gmSmODIz6tNA==
|
||||||
|
|
||||||
use@^3.1.0:
|
use@^3.1.0:
|
||||||
version "3.1.1"
|
version "3.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f"
|
resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f"
|
||||||
|
@ -11502,6 +11521,11 @@ utila@^0.4.0, utila@~0.4:
|
||||||
resolved "https://registry.yarnpkg.com/utila/-/utila-0.4.0.tgz#8a16a05d445657a3aea5eecc5b12a4fa5379772c"
|
resolved "https://registry.yarnpkg.com/utila/-/utila-0.4.0.tgz#8a16a05d445657a3aea5eecc5b12a4fa5379772c"
|
||||||
integrity sha1-ihagXURWV6Oupe7MWxKk+lN5dyw=
|
integrity sha1-ihagXURWV6Oupe7MWxKk+lN5dyw=
|
||||||
|
|
||||||
|
utility-types@^3.10.0:
|
||||||
|
version "3.10.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/utility-types/-/utility-types-3.10.0.tgz#ea4148f9a741015f05ed74fd615e1d20e6bed82b"
|
||||||
|
integrity sha512-O11mqxmi7wMKCo6HKFt5AhO4BwY3VV68YU07tgxfz8zJTIxr4BpsezN49Ffwy9j3ZpwwJp4fkRwjRzq3uWE6Rg==
|
||||||
|
|
||||||
utils-merge@1.0.1:
|
utils-merge@1.0.1:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
|
resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
|
||||||
|
|
Loading…
Reference in a new issue