diff --git a/src/components/market.cairo b/src/components/market.cairo index 314ccd411..343442973 100644 --- a/src/components/market.cairo +++ b/src/components/market.cairo @@ -48,7 +48,7 @@ impl MarketImpl of MarketTrait { if drug_id == 'Acid' { PricingInfos { min_price: 500 * SCALING_FACTOR, - max_price: 1500 * SCALING_FACTOR, + max_price: 1800 * SCALING_FACTOR, min_qty: 400, max_qty: 900, } @@ -75,15 +75,15 @@ impl MarketImpl of MarketTrait { } } else if drug_id == 'Heroin' { PricingInfos { - min_price: 1000 * SCALING_FACTOR, - max_price: 3000 * SCALING_FACTOR, + min_price: 1200 * SCALING_FACTOR, + max_price: 4000 * SCALING_FACTOR, min_qty: 300, max_qty: 700, } } else if drug_id == 'Cocaine' { PricingInfos { - min_price: 2000 * SCALING_FACTOR, - max_price: 6000 * SCALING_FACTOR, + min_price: 3000 * SCALING_FACTOR, + max_price: 8000 * SCALING_FACTOR, min_qty: 250, max_qty: 600, } diff --git a/src/constants.cairo b/src/constants.cairo index 20dbc5629..2bae1e16b 100644 --- a/src/constants.cairo +++ b/src/constants.cairo @@ -1,6 +1,6 @@ const SCALING_FACTOR: u128 = 10_000; -const TRAVEL_RISK: u8 = 30; // 30% chance of travel encounter +const TRAVEL_RISK: u8 = 50; // 50% chance of travel encounter const CAPTURE_RISK: u8 = 50; // 50% chance of capture const ENCOUNTER_BIAS_GANGS: u128 = 60; diff --git a/web/src/components/map/Map.tsx b/web/src/components/map/Map.tsx index 0ec6cdc42..d225eb9f8 100644 --- a/web/src/components/map/Map.tsx +++ b/web/src/components/map/Map.tsx @@ -30,9 +30,7 @@ export const Map = ({ const isMobile = useBreakpointValue([true, false]); useEffect(() => { - console.log({ highlight }); if (highlight !== undefined) { - console.log("got here"); const animation = isMobile ? { scale: 1.75, ...coordinate[highlight] } : { scale: 1, x: 0, y: 0 }; diff --git a/web/src/dojo/components/useGlobalScores.tsx b/web/src/dojo/components/useGlobalScores.tsx index 796a218d5..fa0e0988f 100644 --- a/web/src/dojo/components/useGlobalScores.tsx +++ b/web/src/dojo/components/useGlobalScores.tsx @@ -1,5 +1,5 @@ import { PlayerEdge, Name, useGlobalScoresQuery } from "@/generated/graphql"; -import { useEffect, useState } from "react"; +import { useEffect, useMemo, useState } from "react"; import { shortString } from "starknet"; import { SCALING_FACTOR } from ".."; @@ -42,20 +42,16 @@ export class GlobalScores { } export const useGlobalScores = (offset?: number, limit?: number) => { - const [scores, setScores] = useState([]); // Gets top 100 // TODO: paginate with cursor for more scores const { data, isFetched, refetch } = useGlobalScoresQuery({ limit: limit || 100, }); - useEffect(() => { - const scores = GlobalScores.create( - data?.playerComponents?.edges as PlayerEdge[], + const scores: Score[] = useMemo(() => { + return ( + GlobalScores.create(data?.playerComponents?.edges as PlayerEdge[]) || [] ); - if (scores) { - setScores(scores); - } }, [data]); return { diff --git a/web/src/dojo/components/useMarkets.tsx b/web/src/dojo/components/useMarkets.tsx new file mode 100644 index 000000000..5cad76970 --- /dev/null +++ b/web/src/dojo/components/useMarkets.tsx @@ -0,0 +1,68 @@ +import { Market, MarketEdge, useMarketPricesQuery } from "@/generated/graphql"; +import { useEffect, useMemo, useState } from "react"; +import { num } from "starknet"; +import { REFETCH_INTERVAL, SCALING_FACTOR } from ".."; +import { LocationPrices, DrugMarket } from "../types"; + +export class MarketPrices { + locationPrices: LocationPrices; + + constructor(locationMarkets: LocationPrices) { + this.locationPrices = locationMarkets; + } + + static create(edges: MarketEdge[]): LocationPrices | undefined { + if (!edges || edges.length === 0) return undefined; + + const locationPrices: LocationPrices = new Map(); + + for (let edge of edges) { + const node = edge.node; + const locationId = num.toHexString(node?.location_id); + const drugId = num.toHexString(node?.drug_id); + const price = + Number(node?.cash) / Number(node?.quantity) / SCALING_FACTOR; + + const drugMarket: DrugMarket = { + id: drugId, + price: price, + marketPool: node as Market, + }; + + if (locationPrices.has(locationId)) { + locationPrices.get(locationId)?.push(drugMarket); + } else { + locationPrices.set(locationId, [drugMarket]); + } + } + + return locationPrices; + } +} + +export interface MarketsInterface { + locationPrices?: LocationPrices; +} + +export const useMarketPrices = ({ + gameId, +}: { + gameId?: string; +}): MarketsInterface => { + const { data } = useMarketPricesQuery( + { gameId: Number(gameId) }, + + { + enabled: !!gameId, + refetchInterval: REFETCH_INTERVAL, + }, + ); + + const locationPrices = useMemo(() => { + return MarketPrices.create(data?.marketComponents?.edges as MarketEdge[]); + }, [data]); + + return { + locationPrices, + }; +}; diff --git a/web/src/dojo/entities/useGameEntity.tsx b/web/src/dojo/entities/useGameEntity.tsx index 1c2b8ef5c..d556f4d17 100644 --- a/web/src/dojo/entities/useGameEntity.tsx +++ b/web/src/dojo/entities/useGameEntity.tsx @@ -1,5 +1,5 @@ import { Game, useGameEntityQuery } from "@/generated/graphql"; -import { useEffect, useState } from "react"; +import { useEffect, useMemo, useState } from "react"; import { ec, num } from "starknet"; import { REFETCH_INTERVAL } from ".."; @@ -50,8 +50,11 @@ export const useGameEntity = ({ }: { gameId?: string; }): GameInterface => { - const [game, setGame] = useState(); - const [key, setKey] = useState(""); + const key: string = useMemo(() => { + return num.toHex( + ec.starkCurve.poseidonHashMany([num.toBigInt(gameId || "")]), + ); + }, [gameId]); const { data, isFetched } = useGameEntityQuery( { id: key }, @@ -60,16 +63,8 @@ export const useGameEntity = ({ }, ); - useEffect(() => { - if (gameId) { - const key_ = ec.starkCurve.poseidonHashMany([num.toBigInt(gameId)]); - setKey(num.toHex(key_)); - } - }, [gameId]); - - useEffect(() => { - const game_ = GameEntity.create(data as GameEntityData); - if (game_) setGame(game_); + const game = useMemo(() => { + return GameEntity.create(data as GameEntityData); }, [data]); return { diff --git a/web/src/dojo/entities/useLocationEntity.tsx b/web/src/dojo/entities/useLocationEntity.tsx index df433e33a..091276f91 100644 --- a/web/src/dojo/entities/useLocationEntity.tsx +++ b/web/src/dojo/entities/useLocationEntity.tsx @@ -1,20 +1,13 @@ import { - Name, Market, Risks, useLocationEntitiesQuery, - Entity, EntityEdge, } from "@/generated/graphql"; -import { useEffect, useState } from "react"; -import { num, shortString } from "starknet"; +import { useEffect, useMemo, useState } from "react"; +import { num } from "starknet"; import { REFETCH_INTERVAL, SCALING_FACTOR } from ".."; - -export type DrugMarket = { - id: string; // id is hex encoded drug name - price: number; - marketPool: Market; -}; +import { DrugMarket } from "../types"; export class LocationEntity { id: string; // id is hex encoded location name @@ -91,8 +84,6 @@ export const useLocationEntity = ({ gameId?: string; locationId?: string; }): LocationInterface => { - const [location, setLocation] = useState(); - const { data, isFetched } = useLocationEntitiesQuery( { gameId: gameId || "", @@ -104,11 +95,8 @@ export const useLocationEntity = ({ }, ); - useEffect(() => { - const location_ = LocationEntity.create( - data?.entities?.edges as EntityEdge[], - ); - if (location_) setLocation(location_); + const location = useMemo(() => { + return LocationEntity.create(data?.entities?.edges as EntityEdge[]); }, [data]); return { diff --git a/web/src/dojo/entities/usePlayerEntity.tsx b/web/src/dojo/entities/usePlayerEntity.tsx index 48173503c..595276697 100644 --- a/web/src/dojo/entities/usePlayerEntity.tsx +++ b/web/src/dojo/entities/usePlayerEntity.tsx @@ -4,7 +4,7 @@ import { usePlayerEntityQuery, EntityEdge, } from "@/generated/graphql"; -import { useEffect, useState } from "react"; +import { useEffect, useMemo, useState } from "react"; import { REFETCH_INTERVAL, SCALING_FACTOR } from ".."; import { PlayerStatus } from "../types"; @@ -80,7 +80,6 @@ export const usePlayerEntity = ({ gameId?: string; address?: string; }): PlayerInterface => { - const [player, setPlayer] = useState(); // TODO: remove leading zeros in address, maybe implemented in torii const { data, isFetched } = usePlayerEntityQuery( { gameId: gameId || "", playerId: address || "" }, @@ -90,9 +89,8 @@ export const usePlayerEntity = ({ }, ); - useEffect(() => { - const player_ = PlayerEntity.create(data?.entities?.edges as EntityEdge[]); - if (player_) setPlayer(player_); + const player = useMemo(() => { + return PlayerEntity.create(data?.entities?.edges as EntityEdge[]); }, [data]); return { diff --git a/web/src/dojo/helpers.ts b/web/src/dojo/helpers.ts index 633bf36f5..bd7ab7c11 100644 --- a/web/src/dojo/helpers.ts +++ b/web/src/dojo/helpers.ts @@ -156,37 +156,6 @@ export const outcomes: OutcomeInfo[] = [ getMuggerResponses(Outcome.Escaped, isInitial), color: "neon.200", }, - - // { - // name: "Got Arrested", - // type: Outcome.Captured, - // status: PlayerStatus.BeingArrested, - // imageSrc: "/images/events/police_cruiser.gif", - // description: "You lost some health and drugs", - // getResponse: (isInitial: boolean) => - // getCopResponses(Outcome.Captured, isInitial), - // color: "red", - // }, - // { - // name: "Fought the Gang", - // type: Outcome.Fought, - // status: PlayerStatus.BeingMugged, - // imageSrc: "/images/events/fought.png", - // description: "You lost 20HP", - // getResponse: (isInitial: boolean) => - // getMuggerResponses(Outcome.Fought, isInitial), - // color: "yellow.400", - // }, - // { - // name: "Got Captured", - // type: Outcome.Captured, - // status: PlayerStatus.BeingMugged, - // imageSrc: "/images/sunset.png", - // description: "You some some health and cash", - // getResponse: (isInitial: boolean) => - // getMuggerResponses(Outcome.Captured, isInitial), - // color: "red", - // }, ]; function findBy(array: T[], key: keyof T, value: any): T | undefined { diff --git a/web/src/dojo/types.ts b/web/src/dojo/types.ts index f018fd8fe..137c3c989 100644 --- a/web/src/dojo/types.ts +++ b/web/src/dojo/types.ts @@ -1,3 +1,5 @@ +import { Market } from "@/generated/graphql"; + export enum Location { Queens, Bronx, @@ -59,3 +61,11 @@ export interface OutcomeInfo { getResponse: (isInitial: boolean) => string; color: string; } + +export type DrugMarket = { + id: string; // id is hex encoded drug name + price: number; + marketPool: Market; +}; + +export type LocationPrices = Map; diff --git a/web/src/generated/graphql.ts b/web/src/generated/graphql.ts index bba99ced3..b407071d9 100644 --- a/web/src/generated/graphql.ts +++ b/web/src/generated/graphql.ts @@ -748,6 +748,27 @@ export type GlobalScoresQuery = { } | null; }; +export type MarketPricesQueryVariables = Exact<{ + gameId?: InputMaybe; +}>; + +export type MarketPricesQuery = { + __typename?: "Query"; + marketComponents?: { + __typename?: "MarketConnection"; + edges?: Array<{ + __typename?: "MarketEdge"; + node?: { + __typename?: "Market"; + drug_id?: any | null; + location_id?: any | null; + quantity?: any | null; + cash?: any | null; + } | null; + } | null> | null; + } | null; +}; + export type GameEntityQueryVariables = Exact<{ id: Scalars["ID"]; }>; @@ -974,6 +995,62 @@ useInfiniteGlobalScoresQuery.getKey = ( variables === undefined ? ["GlobalScores.infinite"] : ["GlobalScores.infinite", variables]; +export const MarketPricesDocument = ` + query MarketPrices($gameId: Int) { + marketComponents(first: 36, where: {game_id: $gameId}) { + edges { + node { + drug_id + location_id + quantity + cash + } + } + } +} + `; +export const useMarketPricesQuery = < + TData = MarketPricesQuery, + TError = unknown, +>( + variables?: MarketPricesQueryVariables, + options?: UseQueryOptions, +) => + useQuery( + variables === undefined ? ["MarketPrices"] : ["MarketPrices", variables], + useFetchData( + MarketPricesDocument, + ).bind(null, variables), + options, + ); + +useMarketPricesQuery.getKey = (variables?: MarketPricesQueryVariables) => + variables === undefined ? ["MarketPrices"] : ["MarketPrices", variables]; +export const useInfiniteMarketPricesQuery = < + TData = MarketPricesQuery, + TError = unknown, +>( + variables?: MarketPricesQueryVariables, + options?: UseInfiniteQueryOptions, +) => { + const query = useFetchData( + MarketPricesDocument, + ); + return useInfiniteQuery( + variables === undefined + ? ["MarketPrices.infinite"] + : ["MarketPrices.infinite", variables], + (metaData) => query({ ...variables, ...(metaData.pageParam ?? {}) }), + options, + ); +}; + +useInfiniteMarketPricesQuery.getKey = ( + variables?: MarketPricesQueryVariables, +) => + variables === undefined + ? ["MarketPrices.infinite"] + : ["MarketPrices.infinite", variables]; export const GameEntityDocument = ` query GameEntity($id: ID!) { entity(id: $id) { diff --git a/web/src/graphql/components.graphql b/web/src/graphql/components.graphql index e3647705e..96435f0c1 100644 --- a/web/src/graphql/components.graphql +++ b/web/src/graphql/components.graphql @@ -38,3 +38,16 @@ query GlobalScores($limit: Int) { } } } + +query MarketPrices($gameId: Int) { + marketComponents(first: 36, where: { game_id: $gameId }) { + edges { + node { + drug_id + location_id + quantity + cash + } + } + } +} diff --git a/web/src/pages/[gameId]/[locationSlug]/[drugSlug]/[tradeDirection].tsx b/web/src/pages/[gameId]/[locationSlug]/[drugSlug]/[tradeDirection].tsx index 085ab1713..23f544208 100644 --- a/web/src/pages/[gameId]/[locationSlug]/[drugSlug]/[tradeDirection].tsx +++ b/web/src/pages/[gameId]/[locationSlug]/[drugSlug]/[tradeDirection].tsx @@ -49,7 +49,7 @@ export default function Market() { const [canSell, setCanSell] = useState(false); const [canBuy, setCanBuy] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false); - + const { buy, sell, error: txError } = useSystems(); const { addTrade } = usePlayerStore(); const { account } = useDojo(); diff --git a/web/src/pages/[gameId]/[locationSlug]/index.tsx b/web/src/pages/[gameId]/[locationSlug]/index.tsx index 510e7eb8b..fffec0ba3 100644 --- a/web/src/pages/[gameId]/[locationSlug]/index.tsx +++ b/web/src/pages/[gameId]/[locationSlug]/index.tsx @@ -12,14 +12,12 @@ import { SimpleGrid, StyleProps, useDisclosure, - useBreakpointValue, Flex, } from "@chakra-ui/react"; import Layout from "@/components/Layout"; import { useRouter } from "next/router"; import { Cart } from "@/components/icons"; import { Footer } from "@/components/Footer"; -import { Sounds, playSound } from "@/hooks/sound"; import { useLocationEntity } from "@/dojo/entities/useLocationEntity"; import { usePlayerEntity } from "@/dojo/entities/usePlayerEntity"; import { formatQuantity, formatCash } from "@/utils/ui"; diff --git a/web/src/pages/[gameId]/travel.tsx b/web/src/pages/[gameId]/travel.tsx index af01fd72f..56ad1f083 100644 --- a/web/src/pages/[gameId]/travel.tsx +++ b/web/src/pages/[gameId]/travel.tsx @@ -8,6 +8,10 @@ import { Text, Divider, useEventListener, + SimpleGrid, + Card, + Grid, + GridItem, Spacer, } from "@chakra-ui/react"; import { useRouter } from "next/router"; @@ -19,8 +23,14 @@ import { useSystems } from "@/dojo/systems/useSystems"; import { usePlayerEntity } from "@/dojo/entities/usePlayerEntity"; import { useToast } from "@/hooks/toast"; import { useDojo } from "@/dojo"; -import { getLocationById, getLocationByType, locations } from "@/dojo/helpers"; +import { + getDrugById, + getLocationById, + getLocationByType, + locations, +} from "@/dojo/helpers"; import { LocationInfo } from "@/dojo/types"; +import { useMarketPrices } from "@/dojo/components/useMarkets"; export default function Travel() { const router = useRouter(); @@ -37,6 +47,10 @@ export default function Travel() { address: account?.address, }); + const { locationPrices } = useMarketPrices({ + gameId, + }); + useEffect(() => { if (playerEntity && !isSubmitting) { const location = getLocationById(playerEntity.locationId); @@ -113,7 +127,7 @@ export default function Travel() { showBack={true} > - + {/* {locations.map((location, index) => ( setTargetId(location.id)} /> ))} - + */} + + + + {locationPrices + ?.get(targetId) + ?.sort((a, b) => b.id.localeCompare(a.id)) + .map((market) => { + return ( + + {getDrugById(market.id).icon({ + boxSize: "24px", + marginRight: "10px", + })} + ${market.price.toFixed(0)} + + ); + })} + + +