mirror of
https://github.com/sigmasternchen/mobmash.click
synced 2025-03-15 08:09:02 +00:00
feat: Update About page + README
add Emoji credits
This commit is contained in:
parent
d5df0b0e71
commit
40f86598a1
2 changed files with 82 additions and 56 deletions
|
@ -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 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 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
|
## Contribution
|
||||||
|
|
|
@ -5,110 +5,131 @@
|
||||||
|
|
||||||
<h1>About</h1>
|
<h1>About</h1>
|
||||||
|
|
||||||
<div class="text-container">
|
<div class="text-container" data-hx-boost="true">
|
||||||
|
<p>
|
||||||
|
An arcane mystery as old as time itself: What is the best Minecraft mob?
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
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 <a href="/results">absolute rating</a> for the mobs. If you are interested in the technical
|
||||||
|
implementation, skip down to the <a href="#implementation">Implementation section</a>, or take a look at the
|
||||||
|
source code on <a target="_blank" href="https://github.com/overflowerror/mobmash.click">GitHub</a>.
|
||||||
|
</p>
|
||||||
|
|
||||||
<h2 id="story">The Story</h2>
|
<h2 id="story">The Story</h2>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
Back when I was in school I watched <a href="https://en.wikipedia.org/wiki/The_Social_Network">
|
Back when I was in school, I watched <a target="_blank" href="https://en.wikipedia.org/wiki/The_Social_Network">
|
||||||
The Social Network movie</a>. I was fascinates by the scene in which Zuckerberg implemented Facemash in an
|
The Social Network</a> 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
|
eventing. So, I was kind of curious if I could manage that too - I couldn't. I took me 3 evenings to implement
|
||||||
<a href="https://github.com/overflowerror/promash">simple version</a>. (Writing this now, I also found out
|
even a <a target="_blank" href="https://github.com/overflowerror/promash">rather basic version</a>.
|
||||||
that it took Zuckerberg
|
(Writing this now, I also found out that it took Zuckerberg
|
||||||
<a href="https://www.thecrimson.com/article/2003/11/19/facemash-creator-survives-ad-board-the/">a week</a>
|
<a target="_blank" href="https://www.thecrimson.com/article/2003/11/19/facemash-creator-survives-ad-board-the/">
|
||||||
- not just an evening. Welp.) Instead of students, my version used pictures of my professors, that I scraped
|
a week</a> - not just an evening. Welp.) Instead of students, my version used pictures of my professors,
|
||||||
from my school's website - which, in retrospect, was morally a bit problematic.
|
that I scraped from my school's website - which, in retrospect, was morally a bit problematic.
|
||||||
|
<i class="emoji grinning-face-with-sweat"></i>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Recently, I found the old source code again, and I was thinking: Maybe I can rewrite it. Let's not use humans
|
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 previous version really was quite bad from a technical perspective.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h2 id="implementation">Implementation</h2>
|
<h2 id="implementation">Implementation</h2>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
The basic idea is to give the users the choice between mobs. The better one is selected and its internal
|
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 <a href="https://en.wikipedia.org/wiki/Elo_rating_system">Elo
|
rating increases. The algorithm used is the
|
||||||
rating system</a>. I won't go into details here, but the basic idea is the following: It works by assigning
|
<a target="_blank" href="https://en.wikipedia.org/wiki/Elo_rating_system">Elo rating system</a>. I won't go
|
||||||
each candidate a number, and calculate the winning probabilities for each match. This winning probability
|
into details here, but the basic idea is the following: It works by assigning each candidate a number and
|
||||||
then determines how many points are gained/lost by each candidate.
|
calculating the winning probabilities for each match. This winning probability then determines how many
|
||||||
|
points are gained/lost by each candidate.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
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.
|
engaging to compare than mobs where it's obvious which one is better.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
The challenge here is twofold: First, we need to be able to asynchronously calculate and write the ratings to
|
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
|
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.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
The way I chose to solve those problems, is by doing the calculations in on-the-fly in the database. The
|
The way I chose to solve those problems was 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
|
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
|
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
|
match to the ratings in sequence using a recursive query. (As a side note: Initially, I tried to keep each
|
||||||
own row. However, I was not able to make it work. Not sure if that's a limitation of PostgreSQL or if I'm
|
rating in its own row. However, I was unable to make it work. I'm not sure if that's a limitation of
|
||||||
just not smart enough. ^^ Either way, the current solution uses <code>jsonb</code> objects for the rating
|
PostgreSQL or if I'm just not smart enough. <i class="emoji grinning-face-with-smiling-eyes"></i>
|
||||||
state - which also turned out to be much faster than having each rating as its own object.)
|
Either way, the current solution uses <code>jsonb</code> objects for the rating state - which also turned
|
||||||
|
out to be much faster than having each rating as its own object.)
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
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
|
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
|
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
|
the ratings. The recursive calculation then seeds itself with the cache instead of starting from scratch.
|
||||||
beginning. This cache can be generated regularly (maybe once a day) to keep the responses snappy.
|
This cache can be generated regularly (maybe once a day) to keep the responses snappy.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
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.
|
to delete the corresponding cache entries. It's not a huge deal, but certainly something to be aware of.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
If you want to learn more, check out the source code over on
|
If you would like to learn more, check out the source code over on
|
||||||
<a href="https://github.com/overflowerror/mobmash.click">Github</a>. Pull Requests are welcome!
|
<a target="_blank" href="https://github.com/overflowerror/mobmash.click">GitHub</a>. Pull Requests are welcome!
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h2 id="statistics">Statistics</h2>
|
<h2 id="statistics">Statistics</h2>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
There are currently <?= $stats["mobs"] ?> mobs in the system.
|
There are currently <?= number_format($stats["mobs"]) ?> mobs in the system.
|
||||||
The top ranked mob is "<?= $mobStats["highest_rating"]["name"] ?>" with a rating of
|
The top-ranked mob is "<?= $mobStats["highest_rating"]["name"] ?>", with a rating of
|
||||||
<?= number_format($mobStats["highest_rating"]["rating"]) ?>. Last place is
|
<?= number_format($mobStats["highest_rating"]["rating"]) ?>. Last place is
|
||||||
"<?= $mobStats["lowest_rating"]["name"] ?>" with a rating of
|
"<?= $mobStats["lowest_rating"]["name"] ?>" with a rating of
|
||||||
<?= number_format($mobStats["lowest_rating"]["rating"]) ?>.
|
<?= number_format($mobStats["lowest_rating"]["rating"]) ?>.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
"<?= $mobStats["most_matches"]["name"] ?>" has fought the most matches:
|
"<?= $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"]) ?>.
|
||||||
<?php if ($mobStats["most_matches"]["id"] == $mobStats["most_wins"]["id"]): ?>
|
<?php if ($mobStats["most_matches"]["id"] == $mobStats["most_wins"]["id"]): ?>
|
||||||
Which also makes it the mob with the most wins.
|
Which also makes it the mob with the most wins.
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
Speaking of wins: The mob with the most wins is "<?= $mobStats["most_wins"]["name"] ?>" with
|
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.
|
||||||
<?php endif ?>
|
<?php endif ?>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Until now, there have been <?= $stats["votes"] ?> votes.
|
Until now, there have been <?= number_format($stats["votes"]) ?> votes.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Over the past 6 months, there have been <?= $stats["voters"] ?> unique voters. On average, each one voted
|
Over the past 6 months, there have been <?= number_format($stats["voters"]) ?> unique voters.
|
||||||
<?= number_format($stats["avg"], 1) ?> times with <?= $stats["max"] ?> <?= $stats["max"] > 80 ? "(You people are mad!)" : "" ?>
|
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.
|
as the maximum.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<?php
|
<?php
|
||||||
if ($stats["maxed_out"] == 0) {
|
if ($stats["maxed_out"] == 0) {
|
||||||
?>
|
?>
|
||||||
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.
|
||||||
|
<i class="emoji disappointed-face"></i>
|
||||||
<?php
|
<?php
|
||||||
} else if ($stats["maxed_out"] == 1) {
|
} else if ($stats["maxed_out"] == 1) {
|
||||||
?>
|
?>
|
||||||
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.
|
||||||
|
<i class="emoji grinning-face-with-smiling-eyes"></i>
|
||||||
<?php
|
<?php
|
||||||
} else {
|
} else {
|
||||||
?>
|
?>
|
||||||
Coincidentally, that maximum is the highest possible number - <?= $stats["maxed_out"] ?> visitors voted for every
|
Coincidentally, that maximum is the highest possible number - <?= number_format($stats["maxed_out"]) ?> visitors voted for every
|
||||||
single paring. D:
|
single paring. <i class="emoji face-screaming-in-fear"></i>
|
||||||
<?php
|
<?php
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
|
@ -117,7 +138,7 @@
|
||||||
<h2 id="contact">Contact</h2>
|
<h2 id="contact">Contact</h2>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
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:
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<a href="mailto:<?= GENERAL_CONTACT_EMAIL ?>"><?= GENERAL_CONTACT_EMAIL ?></a>
|
<a href="mailto:<?= GENERAL_CONTACT_EMAIL ?>"><?= GENERAL_CONTACT_EMAIL ?></a>
|
||||||
|
@ -125,43 +146,47 @@
|
||||||
|
|
||||||
<h2 id="credits">Credits & Tech Stack</h2>
|
<h2 id="credits">Credits & Tech Stack</h2>
|
||||||
|
|
||||||
<h3>Minecraft Related Content</h3>
|
<h3>Minecraft-Related Content</h3>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
Minecraft content and materials are trademarks and copyrights of <a href="https://www.minecraft.net/">Mojang Studios</a>.
|
Minecraft content and materials are trademarks and copyrights of
|
||||||
|
<a target="_blank" href="https://www.minecraft.net/">Mojang Studios</a>.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h3>Fonts</h3>
|
<h3>Fonts, Icons, Emoji</h3>
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="https://www.fontspace.com/minecraft-font-f28180">Minecraft Font by JDGraphics</a> (Public Domain)</li>
|
<li><a target="_blank" href="https://www.fontspace.com/minecraft-font-f28180">Minecraft Font by JDGraphics</a>
|
||||||
<li><a href="http://fontawesome.io">Font Awesome v4.7.0</a> (OFL-1.1 & MIT)</li>
|
(Public Domain)</li>
|
||||||
|
<li><a target="_blank" href="http://fontawesome.io">Font Awesome v4.7.0</a> (OFL-1.1 & MIT)</li>
|
||||||
|
<li><a target="_blank" href="https://github.com/twitter/twemoji/">Twemoji</a> (CC BY 4.0 & MIT)</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h3>Frontend Frameworks</h3>
|
<h3>Frontend Frameworks</h3>
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="https://htmx.org/">HTMX</a> (0-Clause BSD)</li>
|
<li><a target="_blank" href="https://htmx.org/">HTMX</a> (0-Clause BSD)</li>
|
||||||
<li><a href="https://www.chartjs.org/">Chart.js</a> (MIT)</li>
|
<li><a target="_blank" href="https://www.chartjs.org/">Chart.js</a> (MIT)</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
<h3>Backend Technologies</h3>
|
<h3>Backend Technologies</h3>
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="https://www.php.net/">PHP</a></li>
|
<li><a target="_blank" href="https://www.php.net/">PHP</a></li>
|
||||||
<li><a href="https://www.postgresql.org/">PostgreSQL</a></li>
|
<li><a target="_blank" href="https://www.postgresql.org/">PostgreSQL</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h2 id="thanks">Special Thanks</h2>
|
<h2 id="thanks">Special Thanks</h2>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
I'd like to thank the <a href="https://minecraft.wiki/">Minecraft Wiki</a> for letting me let use their API to
|
I'd like to thank the <a target="_blank" href="https://minecraft.wiki/">Minecraft Wiki</a> for letting me
|
||||||
automatically update the mobs in the vote. The pictures for the mobs are also provided by them.
|
use their API to automatically update the mobs in the vote. The pictures for the mobs are also
|
||||||
|
provided by them.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Also, I'm very, very bad at design. So a big thanks goes to the web designer who helped me! (They would like
|
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. ^^)
|
to stay anonymous. Apparently, the design is not good enough. <i class="emoji grinning-face-with-smiling-eyes"></i>)
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
</div>
|
</div>
|
Loading…
Reference in a new issue