Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added challenge parametrization and leaderboard real data #88

Merged
merged 1 commit into from
Nov 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 0 additions & 21 deletions backend/docs/example.sql

This file was deleted.

15 changes: 0 additions & 15 deletions backend/docs/table.sql

This file was deleted.

93 changes: 50 additions & 43 deletions backend/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ use async_graphql::*;
use async_graphql_rocket::GraphQLRequest as Request;
use async_graphql_rocket::GraphQLResponse as Response;
use rocket::State;
use rand::{distributions::Alphanumeric, Rng};
use rocket::fairing::{Fairing, Info, Kind};
use rocket::http::{Header, Method, Status};

Expand Down Expand Up @@ -40,16 +39,13 @@ impl Fairing for CORS {
struct QueryRoot;

pub struct ChainParameters {
shipyard_policy_id: String,
ship_address: String,
fuel_address: String,
asteria_address: String,
}
impl ChainParameters {
pub fn from_env() -> Self {
Self {
shipyard_policy_id: env::var("SHIPYARD_POLICY_ID")
.expect("SHIPYARD_POLICY_ID must be set in the environment"),
ship_address: env::var("SHIP_ADDRESS")
.expect("SHIP_ADDRESS must be set in the environment"),
fuel_address: env::var("FUEL_ADDRESS")
Expand Down Expand Up @@ -104,7 +100,7 @@ impl Data {
self.fuels.clone()[offset..offset + limit].to_vec()
}

pub fn objects_in_radius(self, center: Position, radius: i32) -> Vec<PositionalInterface> {
pub fn objects_in_radius(self, center: Position, radius: i32, _shipyard_policy_id: String) -> Vec<PositionalInterface> {
let mut retval = Vec::new();

for ship in self.ships {
Expand Down Expand Up @@ -252,7 +248,6 @@ pub struct LeaderboardRecord {
ship_name: String,
pilot_name: String,
fuel: i32,
movements: i32,
distance: i32,
}

Expand Down Expand Up @@ -327,6 +322,7 @@ impl QueryRoot {
ctx: &Context<'_>,
center: PositionInput,
radius: i32,
shipyard_policy_id: String,
) -> Result<Vec<PositionalInterface>, Error> {
// Access the connection pool from the GraphQL context
let pool = ctx
Expand Down Expand Up @@ -413,7 +409,7 @@ impl QueryRoot {
center.x,
center.y,
radius,
chain_parameters.shipyard_policy_id,
shipyard_policy_id,
chain_parameters.ship_address,
chain_parameters.fuel_address,
chain_parameters.asteria_address,
Expand Down Expand Up @@ -536,43 +532,54 @@ impl QueryRoot {

async fn leaderboard(
&self,
_ctx: &Context<'_>,
) -> Vec<LeaderboardRecord> {
let mut results = Vec::new();

for i in 0..100 {
let address: String = rand::thread_rng()
.sample_iter(&Alphanumeric)
.take(64)
.map(char::from)
.collect();

let ship_name: String = rand::thread_rng()
.sample_iter(&Alphanumeric)
.take(6)
.map(char::from)
.collect();

let pilot_name: String = rand::thread_rng()
.sample_iter(&Alphanumeric)
.take(6)
.map(char::from)
.collect();

results.push(
LeaderboardRecord {
ranking: i + 1,
address: address,
ship_name: ship_name.to_uppercase(),
pilot_name: pilot_name.to_uppercase(),
fuel: rand::thread_rng().gen_range(0..100),
movements: i * 10,
distance: i * 10
}
);
}
ctx: &Context<'_>,
shipyard_policy_id: String,
) -> Result<Vec<LeaderboardRecord>, Error> {
let pool = ctx
.data::<sqlx::PgPool>()
.map_err(|e| Error::new(e.message))?;

results
let chain_parameters = ctx
.data::<ChainParameters>()
.map_err(|e| Error::new(e.message))?;

let fetched_objects = sqlx::query!(
"
SELECT
id,
CAST(utxo_plutus_data(era, cbor) -> 'fields' -> 0 ->> 'int' AS INTEGER) AS fuel,
CAST(utxo_plutus_data(era, cbor) -> 'fields' -> 3 ->> 'bytes' AS TEXT) AS ship_token_name,
CAST(utxo_plutus_data(era, cbor) -> 'fields' -> 4 ->> 'bytes' AS TEXT) AS pilot_token_name,
ABS(CAST(utxo_plutus_data(era, cbor) -> 'fields' -> 1 ->> 'int' AS INTEGER)) + ABS(CAST(utxo_plutus_data(era, cbor) -> 'fields' -> 2 ->> 'int' AS INTEGER)) AS distance
FROM
utxos
WHERE
utxo_address(era, cbor) = from_bech32($2::varchar)
AND utxo_has_policy_id(era, cbor, decode($1::varchar, 'hex'))
AND spent_slot IS NULL
ORDER BY distance ASC
",
shipyard_policy_id,
chain_parameters.ship_address,
)
.fetch_all(pool)
.await
.map_err(|e| Error::new(e.to_string()))?;

let map_objects: Vec<LeaderboardRecord> = fetched_objects
.into_iter()
.enumerate()
.map(|(i, record)| LeaderboardRecord {
ranking: i as i32 + 1,
address: record.id,
ship_name: record.ship_token_name.unwrap_or_default(),
pilot_name: record.pilot_token_name.unwrap_or_default(),
fuel: record.fuel.unwrap_or(0),
distance: record.distance.unwrap_or(0)
})
.collect();

Ok(map_objects)
}
}

Expand Down
32 changes: 31 additions & 1 deletion frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
"next": "14.2.15",
"next-mdx-remote": "^5.0.0",
"react": "^18",
"react-dom": "^18"
"react-dom": "^18",
"zustand": "^5.0.1"
},
"devDependencies": {
"@tailwindcss/forms": "^0.5.9",
Expand Down
90 changes: 55 additions & 35 deletions frontend/src/components/NavBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,50 +3,70 @@
import React from 'react';
import Link from 'next/link';
import { usePathname } from 'next/navigation';
import { useChallengeStore } from '@/stores/challenge';

const NavBar: React.FunctionComponent = () => {
const { challenges, selected, select } = useChallengeStore();
const pathname = usePathname() || '';
const isActive = (route: string): string => pathname.includes(route) ? 'text-[#FFF75D]' : 'text-[#F1E9D9]';

const handleSelect = (event: React.FormEvent<HTMLSelectElement>) => {
select(parseInt(event.currentTarget.value));
}

return (
<div className="w-full h-[64px] px-10 flex flex-row items-center">
<div className="flex flex-row items-end flex-auto basis-1/4">
<div className="flex-none">
<Link href="/">
<img className="h-full w-auto mx-2" src="/logo.svg" />
<div className="w-full h-[64px]">
<div className="fixed w-full h-[64px] px-10 flex flex-row items-center bg-[#171717] z-[9000]">
<div className="flex flex-row items-end flex-auto basis-1/4">
<div className="flex-none">
<Link href="/">
<img className="h-full w-auto mx-2" src="/logo.svg" />
</Link>
</div>
{pathname !== '/' && (
<span className="font-inter-regular text-md text-[#606060] ml-[-16px] pointer-events-none">
By TxPipe
</span>
)}
</div>
<div className="flex flex-row items-center flex-initial">
<Link href="/how-to-play">
<button className={`font-monocraft-regular py-2 px-4 rounded-full text-md mx-4 ${isActive('how-to-play')}`}>
How to play
</button>
</Link>
<span className="border-l border-l-solid border-l-[#F1E9D9] w-0 h-7 opacity-50" />
<Link href="/map">
<button className={`font-monocraft-regular py-2 px-4 rounded-full text-md mx-4 ${isActive('map')}`}>
Map
</button>
</Link>
<span className="border-l border-l-solid border-l-[#F1E9D9] w-0 h-7 opacity-50" />
<Link href="/leaderboard">
<button className={`font-monocraft-regular py-2 px-4 rounded-full text-md mx-4 ${isActive('leaderboard')}`}>
Leaderboard
</button>
</Link>
</div>
{pathname !== '/' && (
<span className="font-inter-regular text-md text-[#606060] ml-[-16px] pointer-events-none">
By TxPipe
</span>
)}
</div>
<div className="flex flex-row items-center flex-initial">
<Link href="/how-to-play">
<button className={`font-monocraft-regular py-2 px-4 rounded-full text-md mx-4 ${isActive('how-to-play')}`}>
How to play
<div className="flex flex-row justify-end flex-auto basis-1/4">
<button
className="border border-solid border-[#5B5B5B] bg-black py-3 px-4 rounded-full mx-2 flex flex-row items-center"
>
<img src="/challenge-icon.svg" className="w-6 h-6 mr-3 pointer-events-none" />
<select
value={selected}
onChange={handleSelect}
className="font-inter-regular text-[#F1E9D9] bg-transparent focus:outline-none appearance-none text-md text-left min-w-36"
>
{ challenges.map((challenge, index) =>
<option key={index} value={index}>
{ challenge.label }
</option>
)}
</select>
<img src="/chevron.svg" className="w-5 h-5 ml-3 pointer-events-none" />
</button>
</Link>
<span className="border-l border-l-solid border-l-[#F1E9D9] w-0 h-7 opacity-50" />
<Link href="/map">
<button className={`font-monocraft-regular py-2 px-4 rounded-full text-md mx-4 ${isActive('map')}`}>
Map
</button>
</Link>
<span className="border-l border-l-solid border-l-[#F1E9D9] w-0 h-7 opacity-50" />
<Link href="/leaderboard">
<button className={`font-monocraft-regular py-2 px-4 rounded-full text-md mx-4 ${isActive('leaderboard')}`}>
Leaderboard
</button>
</Link>
</div>
<div className="flex flex-row justify-end flex-auto basis-1/4">
<button className="border border-solid border-[#5B5B5B] bg-black py-3 px-4 rounded-full mx-2 flex flex-row items-center">
<img src="/challenge-icon.svg" className="w-6 h-6 mr-3 pointer-events-none" />
<span className="font-inter-regular text-[#F1E9D9] text-md text-left w-36">Select challenge</span>
<img src="/chevron.svg" className="w-5 h-5 ml-3 pointer-events-none" />
</button>
</div>
</div>
</div>
);
Expand Down
27 changes: 25 additions & 2 deletions frontend/src/pages/_app.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { useEffect } from 'react';
import { ApolloClient, InMemoryCache, ApolloProvider } from '@apollo/client';
import type { ReactElement, ReactNode } from 'react';
import type { NextPage } from 'next';
import type { AppProps } from 'next/app';

import NavBar from "@/components/NavBar";
import { useChallengeStore } from '@/stores/challenge';

import "./globals.css";

Expand All @@ -21,10 +23,31 @@ const client = new ApolloClient({
});

export default function App({ Component, pageProps }: AppPropsWithLayout) {
const { select, selected } = useChallengeStore();

useEffect(() => {
const request = window.indexedDB.open('/userfs');
request.onsuccess = () => {
const db = request.result;
db.transaction('FILE_DATA', 'readwrite').objectStore('FILE_DATA').put(
{
contents: new TextEncoder().encode(process.env.API_URL),
timestamp: new Date(),
mode: 33206,
},
'/userfs/godot/app_userdata/visualizer/api_url'
).onsuccess = () => select(0);
};
}, []);

return (
<ApolloProvider client={client}>
<NavBar />
<Component {...pageProps} />
{ selected !== null && (
<>
<NavBar />
<Component {...pageProps} />
</>
)}
</ApolloProvider>
);
}
Loading
Loading