Skip to content

Commit

Permalink
more statistics and special filters
Browse files Browse the repository at this point in the history
  • Loading branch information
Trilarion committed Nov 11, 2021
1 parent c0e0993 commit 044a9f9
Show file tree
Hide file tree
Showing 113 changed files with 6,757 additions and 1,379 deletions.
5 changes: 4 additions & 1 deletion code/backlog.txt
Original file line number Diff line number Diff line change
Expand Up @@ -109,4 +109,7 @@ https://www.seul.org/~grumbel/tmp/clanlib/games.html
https://www.tapatalk.com/groups/imperilist/
https://www.wurfelengine.net/
https://zdoom.org/downloads (gzdoom, lzdoom)
https://zope.readthedocs.io/en/latest/
https://zope.readthedocs.io/en/latest/
https://github.com/damian-pastorini/reldens
https://github.com/tainicom/Aether.Physics2D
https://github.com/jarikomppa/soloud
5 changes: 4 additions & 1 deletion code/html/base.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,17 @@
</div>
<div class="navbar-menu" id="navMenu">
<div class="navbar-start">
<a class="navbar-item{% if 'games' in base['active_nav'] %} is-active{% endif %}" href="{{ base['url_to'](['games', 'index.html']) }}">{{ macros.render_icon({'id':'dice'}) }}<span>Games</span></a>
<a class="navbar-item{% if 'games' in base['active_nav'] %} is-active{% endif %}" href="{{ base['url_to'](['games', 'index.html']) }}">{{ macros.render_icon({'id':'dice'}) }}<span>All Games</span></a>
<a class="navbar-item{% if 'table' in base['active_nav'] %} is-active{% endif %}" href="{{ base['url_to'](['table.html']) }}">{{ macros.render_icon({'id':'search'}) }}<span>Table</span></a>
<div class="navbar-item has-dropdown is-hoverable">
<a class="navbar-link{% if 'filter' in base['active_nav'] %} is-active{% endif %}">{{ macros.render_icon({'id':'filter'}) }}<span>Filter</span></a>
<div class="navbar-dropdown">
<a class="navbar-item{% if 'genres' in base['active_nav'] %} is-active{% endif %}" href="{{ base['url_to'](['games', 'genres.html']) }}">{{ macros.render_icon({'id':'price-tag'}) }}<span>By category</span></a>
<a class="navbar-item{% if 'code language' in base['active_nav'] %} is-active{% endif %}" href="{{ base['url_to'](['games', 'languages.html']) }}">{{ macros.render_icon({'id':'language'}) }}<span>By code language</span></a>
<a class="navbar-item{% if 'platforms' in base['active_nav'] %} is-active{% endif %}" href="{{ base['url_to'](['games', 'platforms.html']) }}">{{ macros.render_icon({'id':'laptop'}) }}<span>By OS support</span></a>
<a class="navbar-item{% if 'top-50' in base['active_nav'] %} is-active{% endif %}" href="{{ base['url_to'](['games', 'top50.html']) }}">{{ macros.render_icon({'id':'star'}) }}<span>GitHub Stars Top 50</span></a>
<a class="navbar-item{% if 'web' in base['active_nav'] %} is-active{% endif %}" href="{{ base['url_to'](['games', 'web.html']) }}">{{ macros.render_icon({'id':'earth'}) }}<span>Playable browser games</span></a>
<a class="navbar-item{% if 'kids' in base['active_nav'] %} is-active{% endif %}" href="{{ base['url_to'](['games', 'kids.html']) }}">{{ macros.render_icon({'id':'child'}) }}<span>For Kids</span></a>
</div>
</div>
<a class="navbar-item{% if 'frameworks' in base['active_nav'] %} is-active{% endif %}" href="{{ base['url_to'](['frameworks', 'index.html']) }}">{{ macros.render_icon({'id':'wrench'}) }}<span>Frameworks/Tools</span></a>
Expand Down
177 changes: 105 additions & 72 deletions code/html/generate_static_website.py

Large diffs are not rendered by default.

8 changes: 7 additions & 1 deletion code/html/listing_entries.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@
{#- iterate over items #}
{% for item in listing['items'] %}
<div id="{{ item['anchor-id'] }}" class="box">
{%- if 'for adults' in item['Keyword'] %}
<article class="message is-warning is-size-7">
<div class="message-header"><p>Warning</p></div>
<div class="message-body">This entry is marked as "for adults" and may feature explicit content.</div>
</article>
{%- endif %}
{#- title and platform, activity, state as a level item (all on one line) -#}
<nav class="level">
<div class="level-left">
Expand Down Expand Up @@ -40,7 +46,7 @@
{%- for field in ('code language', 'code license', 'code repository', 'code dependency', 'assets license', 'build system', 'developer') -%}
{%- if field in item -%}
{%- if item[field][1]['entries']|length > 10 -%}
<details><summary>{{ macros.render_element(item[field][0]) }} ({{ item[field][1]['entries']|length }})</summary>{{ macros.render_element(item[field][1]) }}</details>
<details><summary>{{ macros.render_element(item[field][0]) }} ({{ item[field][1]['entries']|length }})</summary><br>{{ macros.render_element(item[field][1]) }}</details>
{%- else -%}
{{ macros.render_element(item[field]) }}
{%- endif -%}
Expand Down
17 changes: 10 additions & 7 deletions code/html/statistics.jinja
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
{% extends "base.jinja" %}
{% block content %}
<div class="container">
<div class="container content">
<div class="box">
<div class="block"><p class="title is-4">{{ data['title'] }}</p></div>
{% set comma = joiner(",") %}
{% for section in data['sections'] %}
{{ comma() }} <a href="#{{ section['id'] }}">{{ section['title'] }}</a>
<div class="block">
<ol type="1">
{%- for section in data['sections'] %}
<li><a href="#{{ section['id'] }}">{{ section['title'] }}</a></li>
{% endfor %}
</div>

<article class="message is-warning">
</ol>
</div>
<article class="message is-warning is-size-7">
<div class="message-header"><p>Warning</p></div>
<div class="message-body">Statistics can be inaccurate.</div>
</article>
</div>
{#- each statistics section -#}
{% for section in data['sections'] %}
<div id="{{ section['id'] }}" class="box">
Expand All @@ -29,6 +31,7 @@
</ul>
</div>
</nav>
<p class="is-size-7 has-text-right"><a href="#">Back to top</a></p>
</div>
{% endfor %}
</div>
Expand Down
18 changes: 10 additions & 8 deletions code/html/table.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,17 @@
</div>
</div>
<div class="box">
<details><summary>Table description</summary>
<ul>
<li><span class="has-text-weight-bold">Title:</span> name with link to OSGL entry (external link to game website)</li>
<li><span class="has-text-weight-bold">State:</span> mature or beta, if inactive then "inactive since year"</li>
<li><span class="has-text-weight-bold">Tags:</span> {{ macros.render_element(index['tags']) }}</li>
<li><span class="has-text-weight-bold">Platform:</span> {{ macros.render_element(index['platforms']) }}</li>
<li><span class="has-text-weight-bold">Language:</span> C, C++, Java, Python, JavaScript, ..</li>
<li><span class="has-text-weight-bold">License:</span> MIT, GPL, BSD, ..</li>
</ul>
</details>
<table class="table is-narrow is-hoverable"></table>
<ul>
<li><span class="has-text-weight-bold">Title:</span> name with link to OSGL entry (external link to game website)</li>
<li><span class="has-text-weight-bold">State:</span> mature or beta, if inactive then "inactive since year"</li>
<li><span class="has-text-weight-bold">Tags:</span> {{ macros.render_element(index['tags']) }}</li>
<li><span class="has-text-weight-bold">Platform:</span> {{ macros.render_element(index['platforms']) }}</li>
<li><span class="has-text-weight-bold">Language:</span> C, C++, Java, Python, JavaScript, ..</li>
<li><span class="has-text-weight-bold">License:</span> MIT, GPL, BSD, ..</li>
</ul>
</div>
</div>
<script>
Expand Down
2 changes: 1 addition & 1 deletion code/utils/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ def get_config(key):
# TODO unmake remake a recommended keyword (should be the same as clone maybe), i.e. add another recommended keyword if only remake is in there

# interesting keywords = recommend keywords + some popular keywords
interesting_keywords = recommended_keywords + ('2D', '3D', 'clone', 'first-person', 'real-time', 'roguelike', 'shooter', 'space', 'turn-based')
interesting_keywords = recommended_keywords + ('2D', '3D', 'clone', 'first-person', 'real-time', 'roguelike', 'shooter', 'space', 'turn-based', 'for kids', 'for adults')

# non game keywords take precedence over other (game) recommended keywords, at most one of them per entry
non_game_keywords = ('framework', 'game engine', 'library', 'tool')
Expand Down
66 changes: 57 additions & 9 deletions code/utils/osg_parse.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,26 +14,36 @@ class ListingTransformer(lark.Transformer):
"""

def unquoted_value(self, x):
return x[0].strip()
"""
:param x:
:return:
"""
return str(x[0]).strip() # strip whitespaces

def quoted_value(self, x):
return x[0][1:-1].strip() # remove quotation marks and strip whitespaces
"""
:param x:
:return:
"""
return str(x[0])[1:-1].strip() # remove quotation marks and strip whitespaces

def property(self, x):
"""
Key is first part, values are following.
:param x:
:return:
"""
return x[0], x[1:]
return str(x[0]).strip(), x[1:]

def name(self, x):
"""
The name part is treated as a property with key "Name"
:param x:
:return:
"""
return 'Name', x[0].strip()
return 'Name', str(x[0]).strip()

def entry(self, x):
"""
Expand All @@ -49,20 +59,43 @@ def entry(self, x):
return d

def start(self, x):
"""
:param x:
:return:
"""
return x


# transformer
class EntryTransformer(lark.Transformer):
"""
"""

def unquoted_value(self, x):
return x[0].strip()
"""
:param x:
:return:
"""
return str(x[0]).strip() # remove whitespaces

def quoted_value(self, x):
return x[0][1:-1].strip() # remove quotation marks
"""
:param x:
:return:
"""
return str(x[0])[1:-1].strip() # remove quotation marks and whitespaces

def comment_value(self, x):
return x[0][1:-1].strip() # remove parenthesis
"""
:param x:
:return:
"""
return str(x[0])[1:-1].strip() # remove parenthesis

def value(self, x):
"""
Expand All @@ -72,7 +105,7 @@ def value(self, x):
:return:
"""
if len(x) == 1:
v = x[0]
v = str(x[0])
else:
v = Value(*x)
return v
Expand All @@ -83,9 +116,14 @@ def property(self, x):
:param x:
:return:
"""
return x[0].strip(), x[1:]
return str(x[0]).strip(), x[1:]

def title(self, x):
"""
:param x:
:return:
"""
return 'Title', x[0].strip()

def note(self, x):
Expand All @@ -99,9 +137,19 @@ def note(self, x):
return 'Note', ''.join(x)

def building(self, x):
"""
:param x:
:return:
"""
return 'Building', x

def start(self, x):
"""
:param x:
:return:
"""
return x


Expand Down
92 changes: 59 additions & 33 deletions code/utils/osg_statistics.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,74 +4,100 @@
"""

import os
import numpy as np
import matplotlib.pyplot as plt

BASE_FIGURE_SIZE = 4

def get_supported_platforms(entries):
"""

:param entries:
:return:
def get_field_statistics(entries, field, sub_field=None, include_NA=True):
"""
supported_platforms = []
for entry in entries:
supported_platforms.extend((entry.get('Platform', ['N/A'])))

unique_supported_platforms = set(supported_platforms)
Given a list of entries, calculates statistics about a field content and returns the statistics.
supported_platforms_stat = [(l, supported_platforms.count(l)) for l in unique_supported_platforms]
supported_platforms_stat.sort(key=lambda x: str.casefold(x[0])) # first sort by name
supported_platforms_stat.sort(key=lambda x: -x[1]) # then sort by occurrence (highest occurrence first)
If include_NA is True, N/A is used if no field was specified
return supported_platforms_stat


def get_build_systems(entries):
"""
Given a list of entries, calculates statistics about the used build systems and returns the statistics as
sorted list of elements (build-system-name, occurence).
"n/a" is used if no build system was specified
:param entries: list of entries
:param include_NA: If true, adds N/A if field is not contained in entry
:return: list of tuples (field content, occurrence) sorted by descending occurrences
"""
build_systems = []

# gather field content
values = []
for entry in entries:
build_systems.extend(entry['Building'].get('Build system', ['N/A']))
if sub_field:
entry = entry[sub_field]
if field in entry:
values.extend(entry[field])
elif include_NA:
values.append('N/A')

unique_build_systems = set(build_systems)
# unique field content
unique_values = set(values)

build_systems_stat = [(l, build_systems.count(l)) for l in unique_build_systems]
build_systems_stat.sort(key=lambda x: str.casefold(x[0])) # first sort by name
build_systems_stat.sort(key=lambda x: -x[1]) # then sort by occurrence (highest occurrence first)
# count occurrences and sort
values_stat = [(l, values.count(l)) for l in unique_values]
values_stat.sort(key=lambda x: str.casefold(x[0])) # first sort by name
values_stat.sort(key=lambda x: -x[1]) # then sort by occurrence (highest occurrence first)

return build_systems_stat
return values_stat


def truncate_stats(stat, threshold, name='Other'):
"""
Combines all entries (name, count) with a count below the threshold and appends a new entry
:return: Truncated statistics with the last item being the addup of all truncated items.
"""
a, b = [], []
for s in stat:
(a, b)[s[1] < threshold].append(s)
c = 0
for s in b:
c += s[1]
a.append([name, c])
if c > 0:
a.append([name, c])
return a


def export_pie_chart(stat, file):
"""
:param stat:
:return:
Given a statistics, creates a pie chart using matplotlib and exports it into a file as SVG.
"""
labels = [x[0] for x in stat]
sizes = [x[1] for x in stat]

fig, ax = plt.subplots(figsize=[4,4], tight_layout=True)
ax.pie(sizes, labels=labels, autopct='%1.1f%%', pctdistance=0.8, shadow=True, labeldistance=1.2)
# create pie chart
fig, ax = plt.subplots(figsize=[BASE_FIGURE_SIZE,BASE_FIGURE_SIZE], tight_layout=True)
ax.pie(sizes, labels=labels, autopct='%1.1f%%', pctdistance=0.8, shadow=True, labeldistance=1.2, normalize=True)

# create output directory if necessary
containing_dir = os.path.dirname(file)
if not os.path.isdir(containing_dir):
os.mkdir(containing_dir)

# save figure
plt.savefig(file, transparent=True) # TODO can we also just generate svg in text form and save later?


def export_bar_chart(stat, file, aspect_ratio = 1, tick_label_rotation=0):
"""
Given a statistics, creates a bar chart using matplotlib and exports it into a file as SVG.
"""
x = np.arange(len(stat))
tick_label, height = [[x[i] for x in stat] for i in (0, 1)]

# create pie chart
fig, ax = plt.subplots(figsize=[aspect_ratio*BASE_FIGURE_SIZE,BASE_FIGURE_SIZE], tight_layout=True)
p = ax.bar(x, height, tick_label=tick_label, edgecolor='black')
ax.bar_label(p)
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
plt.xticks(rotation=tick_label_rotation)

# create output directory if necessary
containing_dir = os.path.dirname(file)
if not os.path.isdir(containing_dir):
os.mkdir(containing_dir)

# save figure
plt.savefig(file, transparent=True)
Loading

0 comments on commit 044a9f9

Please sign in to comment.