diff --git a/README.md b/README.md index f110a57..7d6100f 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ Minecraft content and materials are trademarks and copyrights of [Mojang Studios The mob names and images are fetched from the [Minecraft Wiki](https://minecraft.wiki/) (CC BY-NC-SA 3.0). - Thank you for letting me use your API! The font used is the [Minecraft Font by JDGraphics](https://www.fontspace.com/minecraft-font-f28180) (Public Domain). The icons are [Font Awesome v4.7.0](http://fontawesome.io/) (OFL-1.1 & MIT). +The emoji are [Twemoji](https://github.com/twitter/twemoji/) (CC BY 4.0 & MIT). ## Contribution diff --git a/view/pages/about.php b/view/pages/about.php index 7a4736d..cc7a752 100644 --- a/view/pages/about.php +++ b/view/pages/about.php @@ -5,110 +5,131 @@
+ An arcane mystery as old as time itself: What is the best Minecraft mob? +
++ MobMash is a website that lets visitors vote on which mob from the video game Minecraft they like best by + choosing one of the two mobs presented on the homepage. Based on this relative decision, an algorithm then + calculates an absolute rating for the mobs. If you are interested in the technical + implementation, skip down to the Implementation section, or take a look at the + source code on GitHub. +
+- Back when I was in school I watched - The Social Network movie. I was fascinates by the scene in which Zuckerberg implemented Facemash in an - eventing. So, I was kinda curious if I could manage that too. I didn't. I took me 3 days to implement a very - simple version. (Writing this now, I also found out - that it took Zuckerberg - a week - - not just an evening. Welp.) Instead of students, my version used pictures of my professors, that I scraped - from my school's website - which, in retrospect, was morally a bit problematic. + Back when I was in school, I watched + The Social Network movie. I was fascinates by the scene in which Zuckerberg implemented Facemash in an + eventing. So, I was kind of curious if I could manage that too - I couldn't. I took me 3 evenings to implement + even a rather basic version. + (Writing this now, I also found out that it took Zuckerberg + + a week - not just an evening. Welp.) Instead of students, my version used pictures of my professors, + that I scraped from my school's website - which, in retrospect, was morally a bit problematic. +
Recently, I found the old source code again, and I was thinking: Maybe I can rewrite it. Let's not use humans - this time, and instead use something nerdy: Minecraft mobs. And while we are at it, let's also do it properly + this time. Instead, let's use something nerdy: Minecraft mobs. And while we are at it, let's also do it properly - the previous version really was quite bad from a technical perspective.
- The basic idea is to give the users the choice between mobs. The better one is selected and its internal - rating increases. The algorithm used is the Elo - rating system. I won't go into details here, but the basic idea is the following: It works by assigning - each candidate a number, and calculate the winning probabilities for each match. This winning probability - then determines how many points are gained/lost by each candidate. + The basic idea is to give the users a choice between mobs. The better one is selected, and its internal + rating increases. The algorithm used is the + Elo rating system. I won't go + into details here, but the basic idea is the following: It works by assigning each candidate a number and + calculating the winning probabilities for each match. This winning probability then determines how many + points are gained/lost by each candidate.
- We can also use this rating to calculate the idea pairing. Mobs that have a similar rating might be more + We can also use this rating to calculate the ideal pairing. Mobs that have a similar rating might be more engaging to compare than mobs where it's obvious which one is better.
The challenge here is twofold: First, we need to be able to asynchronously calculate and write the ratings to - the database. This could theoretically be solved by utilizing transactions. The other problem is, that I want + the database. This could theoretically be solved by utilizing transactions. The other problem is that I want to be able to delete entries from the database in case I detect spam or "cheating". So, we need a list of all - changes, so we can undo them if necessary - which would also mean we need to recompute all ratings. + changes, so we can undo them if necessary - which would also mean we need to recompute all ratings after + the deleted ones, as they linearly depend on each other.
- The way I chose to solve those problems, is by doing the calculations in on-the-fly in the database. The
- only thing we need to store, is the match-ups including the winner and the date for sorting. The rating
+ The way I chose to solve those problems was by doing the calculations in on-the-fly in the database. The
+ only thing we have to store are the match-ups, including the winner and the date for sorting. The rating
calculation is then done by starting from a base rating for each mob (1500 in this case) and applying each
- match to the ratings in sequence using a recursive query. (Initially, I tried to keep each rating as its
- own row. However, I was not able to make it work. Not sure if that's a limitation of PostgreSQL or if I'm
- just not smart enough. ^^ Either way, the current solution uses jsonb
objects for the rating
- state - which also turned out to be much faster than having each rating as its own object.)
+ match to the ratings in sequence using a recursive query. (As a side note: Initially, I tried to keep each
+ rating in its own row. However, I was unable to make it work. I'm not sure if that's a limitation of
+ PostgreSQL or if I'm just not smart enough.
+ Either way, the current solution uses jsonb
objects for the rating state - which also turned
+ out to be much faster than having each rating as its own object.)
- This approach works quite well and can handle a few thousands of votes with no issues. However, since we + This approach works quite well and can handle a few thousand votes with no issues. However, since we need to iterate over all matches every time we want to calculate the next pairing, it doesn't scale well - beyond that. My solution for this was to introduce a caching table, that contains a recent snapshot of - the ratings. The recursive calculation than seeds itself with the cache instead of starting from the - beginning. This cache can be generated regularly (maybe once a day) to keep the responses snappy. + beyond that. My solution for this was to introduce a caching table that contains a recent snapshot of + the ratings. The recursive calculation then seeds itself with the cache instead of starting from scratch. + This cache can be generated regularly (maybe once a day) to keep the responses snappy.
- The flip side of using this cache is, of course, that in case I need to delete some matches, I also need + The flip side of using this cache is, of course, that in case I need to delete some matches, I also have to delete the corresponding cache entries. It's not a huge deal, but certainly something to be aware of.
- If you want to learn more, check out the source code over on - Github. Pull Requests are welcome! + If you would like to learn more, check out the source code over on + GitHub. Pull Requests are welcome!
- There are currently = $stats["mobs"] ?> mobs in the system. - The top ranked mob is "= $mobStats["highest_rating"]["name"] ?>" with a rating of + There are currently = number_format($stats["mobs"]) ?> mobs in the system. + The top-ranked mob is "= $mobStats["highest_rating"]["name"] ?>", with a rating of = number_format($mobStats["highest_rating"]["rating"]) ?>. Last place is "= $mobStats["lowest_rating"]["name"] ?>" with a rating of = number_format($mobStats["lowest_rating"]["rating"]) ?>.
"= $mobStats["most_matches"]["name"] ?>" has fought the most matches: - = $mobStats["most_matches"]["matches"] ?>, out of which it won = $mobStats["most_matches"]["wins"] ?>. + = number_format($mobStats["most_matches"]["matches"]) ?>, out of which it won + = number_format($mobStats["most_matches"]["wins"]) ?>. Which also makes it the mob with the most wins. Speaking of wins: The mob with the most wins is "= $mobStats["most_wins"]["name"] ?>" with - = $mobStats["most_wins"]["wins"] ?> wins out of = $mobStats["most_wins"]["matches"] ?> matches. + = number_format($mobStats["most_wins"]["wins"]) ?> wins out of + = number_format($mobStats["most_wins"]["matches"]) ?> matches.
- Until now, there have been = $stats["votes"] ?> votes. + Until now, there have been = number_format($stats["votes"]) ?> votes.
- Over the past 6 months, there have been = $stats["voters"] ?> unique voters. On average, each one voted - = number_format($stats["avg"], 1) ?> times with = $stats["max"] ?> = $stats["max"] > 80 ? "(You people are mad!)" : "" ?> + Over the past 6 months, there have been = number_format($stats["voters"]) ?> unique voters. + On average, each one voted + = number_format($stats["avg"], 1) ?> times, with = number_format($stats["max"]) ?> + = $stats["max"] > 80 ? "(You people are mad!)" : "" ?> as the maximum.
- So far, none have voted for all = $stats["mobs"] * $stats["mobs"] ?> pairings yet. : ( + So far, none have voted for all = number_format($stats["mobs"] * $stats["mobs"]) ?> pairings yet. + - Coincidentally, that maximum is the highest possible number - this one person voted for every single paring. ^^ + Coincidentally, that maximum is the highest possible number - this one person voted for every single paring. + - Coincidentally, that maximum is the highest possible number - = $stats["maxed_out"] ?> visitors voted for every - single paring. D: + Coincidentally, that maximum is the highest possible number - = number_format($stats["maxed_out"]) ?> visitors voted for every + single paring. @@ -117,7 +138,7 @@
- In case you want to contact me regarding this website, please use the following email address: + If you want to contact me regarding this website, please use the following email address:
= GENERAL_CONTACT_EMAIL ?> @@ -125,43 +146,47 @@
- Minecraft content and materials are trademarks and copyrights of Mojang Studios. + Minecraft content and materials are trademarks and copyrights of + Mojang Studios.
-- I'd like to thank the Minecraft Wiki for letting me let use their API to - automatically update the mobs in the vote. The pictures for the mobs are also provided by them. + I'd like to thank the Minecraft Wiki for letting me + use their API to automatically update the mobs in the vote. The pictures for the mobs are also + provided by them.
- Also, I'm very, very bad at design. So a big thanks goes to the web designer who helped me! (They would like - to stay anonymous. Apparently, the design is not good enough. ^^) + Also, I'm awful at design. So, a big thanks goes to the web designer who helped me! (They would like + to stay anonymous. Apparently, the design is not good enough. )