sparkletown/sparkle

View on GitHub
src/utils/address.ts

Summary

Maintainability
B
4 hrs
Test Coverage
import { PLAYA_AVATAR_SIZE, PLAYA_VENUE_NAME } from "settings";

type AddressEvaluatorFunc = (x: number, y: number, point: Point) => string;

type Point = {
  x: number;
  y: number;
  name: string;
  area?: string;
  radius?: number;
  evaluator: AddressEvaluatorFunc;
};

const MINIMUM_DISTANCE = PLAYA_AVATAR_SIZE / 2;

const distance = (x1: number, y1: number, x2: number, y2: number) =>
  Math.floor(Math.hypot(x2 - x1, y2 - y1));

const clockTime = (cx: number, cy: number, tox: number, toy: number) => {
  const CLOCK_ROTATION_HOURS = 0.6; // Because the city is 18 degrees off-axis
  const hours =
    (Math.atan2(cy - toy, cx - tox) * 6) / Math.PI - 3 - CLOCK_ROTATION_HOURS;
  const wholeHours = Math.floor(hours);
  const displayHours =
    wholeHours === 0 ? 12 : wholeHours < 0 ? wholeHours + 12 : wholeHours;
  const minutes = (hours - wholeHours) * 60;
  const wholeMinutes = Math.floor(minutes);
  const zeroPaddedMinutes =
    wholeMinutes < 10 ? `0${wholeMinutes}` : wholeMinutes;
  return `${displayHours}:${zeroPaddedMinutes}`;
};

const clockEvaluator = (x: number, y: number, point: Point) => {
  const distanceFromCenter = distance(point.x, point.y, x, y);
  if (distanceFromCenter < MINIMUM_DISTANCE) {
    return point.name;
  } else {
    const clock = clockTime(point.x, point.y, x, y);
    return `${clock}, ${distanceFromCenter} ${PLAYA_VENUE_NAME.toLowerCase()}-pixels from ${
      point.name
    }`;
  }
};

const ESPLANADE_DISTANCE = 300;
const STREET_WIDTH = 48;

const cityEvaluator = (x: number, y: number, man: Point) => {
  const distanceFromTheMan = distance(man.x, man.y, x, y);
  const clockFromTheMan = clockTime(man.x, man.y, x, y);
  if (
    clockFromTheMan.startsWith("11:") ||
    clockFromTheMan.startsWith("12:") ||
    clockFromTheMan.startsWith("1:") ||
    distanceFromTheMan < ESPLANADE_DISTANCE ||
    distanceFromTheMan >= ESPLANADE_DISTANCE + STREET_WIDTH * 7
  ) {
    return `Open ${PLAYA_VENUE_NAME}, ${distanceFromTheMan} ${PLAYA_VENUE_NAME.toLowerCase()}-pixels from The Man @ ${clockFromTheMan}`;
  }
  if (distanceFromTheMan < ESPLANADE_DISTANCE + STREET_WIDTH) {
    return `${clockFromTheMan} & Esplanade`;
  }
  if (distanceFromTheMan < ESPLANADE_DISTANCE + STREET_WIDTH * 2) {
    return `${clockFromTheMan} & A`;
  }
  if (distanceFromTheMan < ESPLANADE_DISTANCE + STREET_WIDTH * 3) {
    return `${clockFromTheMan} & B`;
  }
  if (distanceFromTheMan < ESPLANADE_DISTANCE + STREET_WIDTH * 4) {
    return `${clockFromTheMan} & C`;
  }
  if (distanceFromTheMan < ESPLANADE_DISTANCE + STREET_WIDTH * 5) {
    return `${clockFromTheMan} & D`;
  }
  if (distanceFromTheMan < ESPLANADE_DISTANCE + STREET_WIDTH * 6) {
    return `${clockFromTheMan} & E`;
  }
  if (distanceFromTheMan < ESPLANADE_DISTANCE + STREET_WIDTH * 7) {
    return `${clockFromTheMan} & F`;
  }
  return `Open ${PLAYA_VENUE_NAME}, ${distanceFromTheMan} ${PLAYA_VENUE_NAME.toLowerCase()}-pixels from The Man`;
};

const MAN: Point = {
  x: 2028,
  y: 1873,
  name: "The Man",
  evaluator: cityEvaluator,
};
const CENTER_SPACE: Point = {
  x: 1915,
  y: 2179,
  name: "Center Camp",
  radius: 94,
  evaluator: clockEvaluator,
};
const NORTHWEST_SATELLITE: Point = {
  x: 1485,
  y: 677,
  name: "Northwest ✨",
  evaluator: clockEvaluator,
};
const SOUTHEAST_SATELLITE: Point = {
  x: 2468,
  y: 3132,
  name: "Southeast ✨",
  evaluator: clockEvaluator,
};
const DEEP_PLAYA: Point = {
  x: 2254,
  y: 1261,
  name: `Deep ${PLAYA_VENUE_NAME}`,
  evaluator: () => `Deep ${PLAYA_VENUE_NAME}`,
};

const POINTS: Point[] = [
  MAN,
  CENTER_SPACE,
  NORTHWEST_SATELLITE,
  SOUTHEAST_SATELLITE,
  DEEP_PLAYA,
];

export const playaAddress: (x: number, y: number) => string = (x, y) => {
  const distancesToPointsList = POINTS.map((point) => ({
    point,
    distance: distance(point.x, point.y, x, y),
  })).sort((a, b) => a.distance - b.distance);
  for (const entry of distancesToPointsList) {
    // If point is too far, use the next closest
    if (entry.point.radius && entry.distance > entry.point.radius) {
      continue;
    }
    return entry.point.evaluator(x, y, entry.point);
  }
  return "(unknown)";
};