scoring.py 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. import math
  2. from typing import Tuple
  3. import haversine
  4. mean_earth_radius_km = (6378 + 6357) / 2
  5. # if you're more than 1/4 of the Earth's circumfrence away, you get 0
  6. max_dist_km = (math.pi * mean_earth_radius_km) / 2 # this is about 10,000 km
  7. # if you're within 1/16 of the Earth's circumfrence away, you get at least 1000 points
  8. quarter_of_max_km = max_dist_km / 4 # this is about 2,500 km
  9. # this is the total of the land area of all continents on Earth except Antarctica
  10. relevant_land_area = sum([
  11. 3.036e7, # https://www.wolframalpha.com/input?i=geographic+area+of+africa+in+km2
  12. 4.981e7, # https://www.wolframalpha.com/input?i=geographic+area+of+asia+in+km2
  13. 2.450e7, # https://www.wolframalpha.com/input?i=geographic+area+of+north+america+in+km2
  14. 1.782e7, # https://www.wolframalpha.com/input?i=geographic+area+of+south+america+in+km2
  15. 5.973e6, # https://www.wolframalpha.com/input?i=geographic+area+of+europe+in+km2
  16. 8.563e6, # https://www.wolframalpha.com/input?i=geographic+area+of+oceania+in+km2
  17. ])
  18. # this is the "radius" of an "average" continent
  19. # within this radius, you get at least 2000 points
  20. avg_continental_rad_km = math.sqrt(relevant_land_area / (6 * math.pi))
  21. # this works out to be approx 2700 km
  22. # somewhat arbitrarily, if you're within 1000 km, you get at least 3000 points
  23. one_thousand = 1000.0
  24. # this is the "radius" of the "average" country
  25. # within this radius, you get at least 4000 points
  26. # TODO increment or decrement number of countries as wars develop
  27. avg_country_rad_km = math.sqrt(relevant_land_area / (206 * math.pi))
  28. # this works out to be approx 460 km
  29. # if you're within 150m, you get a perfect score of 5000
  30. min_dist_km = 0.15
  31. def score_within(raw_dist: float, min_dist: float, max_dist: float) -> int:
  32. """
  33. Gives a score between 0 and 1000, with 1000 for the min_dist and 0 for the max_dist
  34. """
  35. # scale the distance down to [0.0, 1.0], then multiply it by 2 for easing
  36. pd2 = 2 * (raw_dist - min_dist) / (max_dist - min_dist)
  37. # perform a quadratic ease-in-out on pd2
  38. r = (pd2 ** 2) / 2 if pd2 < 1 else 1 - (((2 - pd2) ** 2) / 2)
  39. # use this to ease between 1000 and 0
  40. return int(1000 * (1 - r))
  41. def ramp(dist_km: float) -> float:
  42. if dist_km <= min_dist_km:
  43. return 5000
  44. elif dist_km <= avg_country_rad_km:
  45. return 4000 + score_within(dist_km, min_dist_km, avg_country_rad_km)
  46. elif dist_km <= one_thousand:
  47. return 3000 + score_within(dist_km, avg_country_rad_km, one_thousand)
  48. elif dist_km <= avg_continental_rad_km:
  49. return 2000 + score_within(dist_km, one_thousand, avg_continental_rad_km)
  50. elif dist_km <= quarter_of_max_km:
  51. return 1000 + score_within(dist_km, avg_continental_rad_km, quarter_of_max_km)
  52. elif dist_km <= max_dist_km:
  53. return score_within(dist_km, quarter_of_max_km, max_dist_km)
  54. else: # dist_km > max_dist_km
  55. return 0
  56. def score(target: Tuple[float, float], guess: Tuple[float, float]) -> Tuple[int, float]:
  57. """
  58. Takes in two (latitude, longitude) pairs and produces an int score.
  59. Score is in the (inclusive) range [0, 5000]
  60. Higher scores are closer.
  61. Returns (score, distance in km)
  62. """
  63. dist_km = haversine.haversine(target, guess)
  64. return ramp(dist_km), dist_km
  65. def score_pow(target: Tuple[float, float], guess: Tuple[float, float]) -> Tuple[int, float]:
  66. """
  67. Takes in two (latitude, longitude) pairs and produces an int score.
  68. Score is in the (inclusive) range [0, 5000]
  69. Higher scores are closer.
  70. Uses the same ramp as standard score, but raises the distance to a power first
  71. Returns (score, distance in km)
  72. """
  73. dist_km = haversine.haversine(target, guess)
  74. return ramp(dist_km ** 1.2), dist_km
  75. def score_country_race(target: str, guess: str, time_remaining: int, time_total: int):
  76. if target != guess:
  77. return 0
  78. time_used = time_total - time_remaining
  79. if time_used <= 5:
  80. return 5000
  81. # TODO make this into an interesting curve but for now linear is fine
  82. return int(5000 * (time_remaining / time_total))
  83. def score_hard(target: Tuple[float, float], guess: Tuple[float, float]) -> Tuple[int, float]:
  84. """
  85. Takes in two (latitude, longitude) pairs and produces an int score.
  86. Score is in the (inclusive) range [0, 5000]
  87. Higher scores are closer.
  88. Scoring is much more punishing than standard
  89. Returns (score, distance in km)
  90. """
  91. dist_km = haversine.haversine(target, guess)
  92. return max(0, 5000 - int(dist_km * 10)), dist_km
  93. def score_nightmare(target: Tuple[float, float], guess: Tuple[float, float]) -> Tuple[int, float]:
  94. """
  95. Takes in two (latitude, longitude) pairs and produces an int score.
  96. Score is in the range (-inf, 5000]
  97. Higher scores are closer.
  98. Scoring is much, MUCH more punishing than standard
  99. Returns (score, distance in km)
  100. """
  101. dist_km = haversine.haversine(target, guess)
  102. return 5000 - int(dist_km * 1000), dist_km