website with jinja

This commit is contained in:
Trilarion 2020-09-16 15:09:45 +02:00
parent 60f9523906
commit 7eacd2da44
26 changed files with 547 additions and 19 deletions

View File

@ -5,9 +5,9 @@
[comment]: # (start of autogenerated content, do not edit)
**[Games](entries/tocs/_games.md#Games)** (1276) - **[Tools](entries/tocs/_tools.md#Tools)** (42) - **[Frameworks](entries/tocs/_frameworks.md#Frameworks)** (81) - **[Libraries](entries/tocs/_libraries.md#Libraries)** (49)
By category: **[Action](entries/tocs/_action.md#action)** (237), **[Adventure](entries/tocs/_adventure.md#adventure)** (52), **[Arcade](entries/tocs/_arcade.md#arcade)** (116), **[Board](entries/tocs/_board.md#board)** (23), **[Cards](entries/tocs/_cards.md#cards)** (17), **[Educational](entries/tocs/_educational.md#educational)** (12), **[Framework](entries/tocs/_framework.md#framework)** (81), **[Game engine](entries/tocs/_game-engine.md#game-engine)** (116), **[Library](entries/tocs/_library.md#library)** (49), **[Music](entries/tocs/_music.md#music)** (14), **[Platform](entries/tocs/_platform.md#platform)** (56), **[Puzzle](entries/tocs/_puzzle.md#puzzle)** (140), **[Remake](entries/tocs/_remake.md#remake)** (527), **[Role playing](entries/tocs/_role-playing.md#role-playing)** (190), **[Simulation](entries/tocs/_simulation.md#simulation)** (108), **[Sports](entries/tocs/_sports.md#sports)** (23), **[Strategy](entries/tocs/_strategy.md#strategy)** (266), **[Tool](entries/tocs/_tool.md#tool)** (42), **[Visual novel](entries/tocs/_visual-novel.md#visual-novel)** (6)
By category: **[Action](entries/tocs/_action.md#action)** (237), **[Adventure](entries/tocs/_adventure.md#adventure)** (52), **[Arcade](entries/tocs/_arcade.md#arcade)** (116), **[Board](entries/tocs/_board.md#board)** (23), **[Cards](entries/tocs/_cards.md#cards)** (17), **[Educational](entries/tocs/_educational.md#educational)** (12), **[Framework](entries/tocs/_framework.md#framework)** (81), **[Game engine](entries/tocs/_game-engine.md#game-engine)** (116), **[Library](entries/tocs/_library.md#library)** (49), **[Music](entries/tocs/_music.md#music)** (14), **[Platform](entries/tocs/_platform.md#platform)** (56), **[Puzzle](entries/tocs/_puzzle.md#puzzle)** (140), **[Remake](entries/tocs/_remake.md#remake)** (527), **[Role playing](entries/tocs/_role-playing.md#role-playing)** (191), **[Simulation](entries/tocs/_simulation.md#simulation)** (108), **[Sports](entries/tocs/_sports.md#sports)** (23), **[Strategy](entries/tocs/_strategy.md#strategy)** (266), **[Tool](entries/tocs/_tool.md#tool)** (42), **[Visual novel](entries/tocs/_visual-novel.md#visual-novel)** (6)
By platform: **[Windows](entries/tocs/_windows.md#windows)** (438), **[Linux](entries/tocs/_linux.md#linux)** (427), **[macOS](entries/tocs/_macos.md#macos)** (236), **[Android](entries/tocs/_android.md#android)** (68), **[iOS](entries/tocs/_ios.md#ios)** (15), **[Web](entries/tocs/_web.md#web)** (79)
By platform: **[Windows](entries/tocs/_windows.md#windows)** (438), **[Linux](entries/tocs/_linux.md#linux)** (427), **[macOS](entries/tocs/_macos.md#macos)** (236), **[Android](entries/tocs/_android.md#android)** (68), **[iOS](entries/tocs/_ios.md#ios)** (15), **[Web](entries/tocs/_web.md#web)** (89)
[comment]: # (end of autogenerated content)

View File

@ -252,6 +252,7 @@ https://github.com/enginmanap/limonEngine
https://github.com/ErikLetson/torso-ninja
https://github.com/ErikLetson/torso-ninja-2
https://github.com/ezEngine/ezEngine
https://github.com/fallahn/tmxlite
https://github.com/fallahn/xygine
https://github.com/FaronBracy/RogueSharp
https://github.com/fegennari/3DWorld
@ -282,6 +283,7 @@ https://github.com/Illation/ETEngine
https://github.com/Im-dex/xray-162
https://github.com/jasonrohrer (add gits to his games)
https://github.com/jatinmandav/Gaming-in-Python
https://github.com/Jaxe-Dev/RimHUD
https://github.com/JohanLi/uncharted-waters-2
https://github.com/JohnLamontagne/Lunar-Engine
https://github.com/junkdog/artemis-odb
@ -354,6 +356,7 @@ https://github.com/rotators/Fo1in2
https://github.com/RoxasShadow/Sottaceto
https://github.com/roy-t/MiniRTS
https://github.com/rramsden/ymir
https://github.com/Ruin0x11/OpenNefia
https://github.com/sabresaurus/SabreCSG
https://github.com/SadConsole/SadConsole
https://github.com/SanderMertens/flecs
@ -397,6 +400,7 @@ https://github.com/vocollapse/Blockinger
https://github.com/WagicProject/wagic
https://github.com/wesnoth/haldric
https://github.com/WohlSoft/PGE-Project
https://github.com/wojtekpil/Godot-Octahedral-Impostors
https://github.com/xrOxygen/xray-oxygen
https://github.com/YuriiSalimov/15-puzzle
https://github.com/Zal0/ZGB

View File

@ -0,0 +1,80 @@
# Design of the static website
The website is built with the parsed entries, developers and inspirations read in Python, then a script uses Jinja templates
to generate html pages which use a CSS framework and Javascript data tables. The finished site is pushed to a suitable
location, only changed content would need to be copied though.
## Pages
index.html - overview of all pages
contribute.html - information how to edit and contribute
games/index.html - overview of all games (with recommended keywords) sorted alphabetically
games/table.html - overview of all games as table
games/[A-Z].html - entries sorted by title and categorized alphabetically
games/genres.html - all games in a certain genre
games/languages.html - all games with a certain language
games/platform.html - all games with a certain platform
games/dependencies.html - all games with a certain dependency
inspirations/index.html - overview of all inspirations (with number of games inspired) sorted alphabetically
inspirations/table.html - overview of all inspirations as table
inspirations/[A-Z].html - inspirations sorted by title and categorized alphabetically
developers/index.html - overview of all developers (with number of games created) sorted alphabetically
developers/table.html - overview of all developers as table
developers/[A-Z].html - developers sorted by name and categorized alphabetically
statistics/index.html - overview of statistics
statistics/keywords.html - statistics of keywords (links to genres/xx)
statistics/state.html - statistics of inactive games
statistics/languages.html - statistics of languages (links to languages/xx)
statistics/licenses.html - statistics of licenses
statistics/dependencies.html - statistics of code dependencies (links to dependencies/xx)
statistics/build-systems.html - statistics of build systems
## Header/Footer
Header: link to overview, link to contribute, link to Github
Footer: link to Blog, link to overviews, link to Github
## Pages structure
### Game entry
- Title (anchor) -- [edit] (aligned right, forwards to contribute)
- Genre, Platform (say "unknown" if unknown), State
- Home (main website)
- Secondary homes: (includes code repository)
- Inspirations: (optional)
- Media: (optional)
- Download: (optional)
- Play: (optional)
- Other keywords: (optional)
- Developer: (optional)
- Note: (optional)
Technical info (hidden initially, can be toggled on/off)
- Code language
- Code repository
- Code license
- Code dependencies (optional)
- Build system/information (optional)
- Assets (optional)
### Inspiration entry
- Title (anchor) -- [edit]
- Media: (optional)
- Inspired entries: (with links)
### Developer entry
- Name (anchor) -- [edit]
- Games: (with links)
- Contact: links to profiles on SourceForge, GitHub, .. converted to links
## Overviews
Simple paragraphs with headers and columns (for example game names in three columns)

View File

@ -0,0 +1,233 @@
"""
Generates the static website
Uses Jinja2 (see https://jinja.palletsprojects.com/en/2.11.x/)
"""
import os
import shutil
import math
import datetime
from utils import osg, constants as c, utils
from jinja2 import Environment, FileSystemLoader
alphabet = 'abcdefghijklmnopqrstuvwxyz'
extended_alphabet = '0' + alphabet
def write(text, file):
"""
:param text:
:param file:
"""
file = os.path.join(c.web_path, file)
containing_dir = os.path.dirname(file)
if not os.path.isdir(containing_dir):
os.mkdir(containing_dir)
utils.write_text(file, text)
def sort_into_categories(list, categories, fit, unknown_category_name):
"""
:param list:
:param categories:
:param fit:
:param unknown_category_name:
:return:
"""
categorized_sublists = {}
for category in categories:
sublist = [item for item in list if fit(item, category)]
categorized_sublists[category] = sublist
if unknown_category_name:
# now those that do not fit
sublist = [item for item in list if not any(fit(item, category) for category in categories)]
categorized_sublists[unknown_category_name] = sublist
return categorized_sublists
def sort_by_alphabet(list, key):
"""
:param list:
:param key:
:return:
"""
return sort_into_categories(list, alphabet, lambda item, category: item[key].lower().startswith(category), '0')
def divide_in_columns(categorized_lists, key):
"""
:param categorized_lists:
:param key:
:return:
"""
number_entries = {category: len(categorized_lists[category]) for category in categorized_lists.keys()}
entries = {}
for category in categorized_lists.keys():
e = categorized_lists[category]
e = [e[key] for e in e]
# divide in three equal lists
n = len(e)
n1 = math.ceil(n/3)
n2 = math.ceil(2*n/3)
e = [e[:n1], e[n1:n2], e[n2:]]
entries[category] = e
return {'number_entries': number_entries, 'entries': entries}
def generate(entries, inspirations, developers):
"""
:param entries:
:param inspirations:
:param developers:
"""
# add anchor ref () to every entry
for entry in entries:
entry['title-anchor'] = osg.canonical_entry_name(entry['Title'])
# base dictionary
base = {
'title': 'OSGL',
'paths': {
'css': 'css/bulma.min.css',
'index': 'index.html',
'index-games': 'games/index.html',
'index-developers': 'developers/index.html',
'index-inspirations': 'inspirations/index.html',
'index-statistics': 'index-statistics'
},
'creation-date': datetime.datetime.utcnow()
}
# copy bulma css
os.mkdir(c.web_css_path)
shutil.copy2(os.path.join(c.web_template_path, 'bulma.min.css'), c.web_css_path)
# create Jinja Environment
environment = Environment(loader=FileSystemLoader(c.web_template_path), autoescape=True)
environment.globals['base'] = base
# multiple times used templates
template_categorical_index = environment.get_template('categorical_index.jinja')
template_list = environment.get_template('list.jinja')
# index.html
index = {'number_games': len(entries)} # TODO only count games
template = environment.get_template('index.jinja')
write(template.render(index=index), 'index.html')
# generate games pages
games_by_alphabet = sort_by_alphabet(entries, 'Title')
template = environment.get_template('games_for_letter.jinja')
for letter in extended_alphabet:
write(template.render(letter=letter, games=games_by_alphabet[letter]), os.path.join('games', '{}.html'.format(letter.capitalize())))
# generate games index
index = divide_in_columns(games_by_alphabet, 'Title')
index['title'] = 'Games index'
index['categories'] = extended_alphabet
write(template_categorical_index.render(index=index), os.path.join('games', 'index.html'))
## inspirations
inspirations_by_alphabet = sort_by_alphabet(inspirations, 'Name')
# inspirations index
index = divide_in_columns(inspirations_by_alphabet, 'Name')
index['title'] = 'Inspirations index'
index['categories'] = extended_alphabet
write(template_categorical_index.render(index=index), os.path.join('inspirations', 'index.html'))
# inspirations single pages
template = environment.get_template('inspirations_for_letter.jinja')
for letter in extended_alphabet:
write(template.render(letter=letter, inspirations=inspirations_by_alphabet[letter]), os.path.join('inspirations', '{}.html'.format(letter.capitalize())))
## developers
# developers single pages
developers_by_alphabet = sort_by_alphabet(developers, 'Name')
template = environment.get_template('developers_for_letter.jinja')
for letter in extended_alphabet:
write(template.render(letter=letter, developers=developers_by_alphabet[letter]), os.path.join('developers', '{}.html'.format(letter.capitalize())))
# developers index
index = divide_in_columns(developers_by_alphabet, 'Name')
index['title'] = 'Developers index'
index['categories'] = extended_alphabet
write(template_categorical_index.render(index=index), os.path.join('developers', 'index.html'))
## genres
genres = c.recommended_keywords
games_by_genre = sort_into_categories(entries, genres, lambda item, category: category in item['Keywords'], None)
index = divide_in_columns(games_by_genre, 'Title')
index['title'] = 'Games by genre'
index['categories'] = genres
write(template_categorical_index.render(index=index), os.path.join('games', 'genres.html'))
## games by language TODO make categories bold that have a certain amount of entries!
languages = c.known_languages
games_by_language = sort_into_categories(entries, languages, lambda item, category: category in item['Code language'], None)
index = divide_in_columns(games_by_language, 'Title')
index['title'] = 'Games by language'
index['categories'] = languages # it's fine if they get capitalized, because they are already capitalized
write(template_categorical_index.render(index=index), os.path.join('games', 'languages.html'))
## games by platform
platforms = c.valid_platforms
games_by_platform = sort_into_categories(entries, platforms, lambda item, category: category in item.get('Platform', []), 'Unspecified')
index = divide_in_columns(games_by_platform, 'Title')
index['title'] = 'Games by platform'
index['categories'] = platforms # TODO (do not capitalize automatically)
write(template_categorical_index.render(index=index), os.path.join('games', 'platforms.html'))
## statistics
# index
template = environment.get_template('statistics_index.jinja')
write(template.render(), os.path.join('statistics', 'index.html'))
# build-systems
build_systems = []
field = 'Build system'
for entry in entries:
if field in entry['Building']:
build_systems.extend(entry['Building'][field])
build_systems = [x.value for x in build_systems]
unique_build_systems = set(build_systems)
unique_build_systems = [(l, build_systems.count(l)) for l in unique_build_systems]
unique_build_systems.sort(key=lambda x: str.casefold(x[0])) # first sort by name
unique_build_systems.sort(key=lambda x: -x[1]) # then sort by occurrence (highest occurrence first)
data = {
'title': 'Build system',
'items': ['{} ({})'.format(*item) for item in unique_build_systems]
}
write(template_list.render(data=data), os.path.join('statistics', 'build-systems.html'))
if __name__ == "__main__":
# clean the output directory
print('clean current static website')
utils.recreate_directory(c.web_path)
# load entries, inspirations and developers and sort them
print('load entries, inspirations and developers')
entries = osg.read_entries()
entries.sort(key=lambda x: str.casefold(x['Title']))
inspirations = osg.read_inspirations()
inspirations = list(inspirations.values())
inspirations.sort(key=lambda x: str.casefold(x['Name']))
developers = osg.read_developers()
developers = list(developers.values())
developers.sort(key=lambda x: str.casefold(x['Name']))
# re-generate static website
print('re-generate static website')
generate(entries, inspirations, developers)

68
code/html/base.jinja Normal file
View File

@ -0,0 +1,68 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{ base['title'] }}</title>
<link rel="stylesheet" href="{{ base['paths']['css'] }}">
</head>
<body>
<nav class="container navbar" role="navigation" aria-label="main navigation">
<div class="navbar-menu">
<div class="navbar-start">
<a class="navbar-item" href="{{ base['paths']['index'] }}">
Home
</a>
<a class="navbar-item" href="{{ base['paths']['index-games'] }}">
Games
</a>
<a class="navbar-item" href="{{ base['paths']['index-developers'] }}">
Developers
</a>
<div class="navbar-item has-dropdown is-hoverable">
<a class="navbar-link">
Filter
</a>
<div class="navbar-dropdown">
<a class="navbar-item" href="{{ base['paths']['index-inspirations'] }}">
By inspiration
</a>
<a class="navbar-item" href="{{ base['paths']['index-developers'] }}">
By category
</a>
<a class="navbar-item" href="{{ base['paths']['index-developers'] }}">
By code language
</a>
<a class="navbar-item" href="{{ base['paths']['index-developers'] }}">
By OS support
</a>
<a class="navbar-item" href="{{ base['paths']['index-developers'] }}">
By dependency
</a>
</div>
</div>
<a class="navbar-item" href="{{ base['paths']['index-statistics'] }}">
Statistics
</a>
</div>
</div>
</nav>
{% block content %}{% endblock %}
<footer class="footer">
<div class="content has-text-centered">
<p>
Footer content {{ base['creation-date'] }}
</p>
</div>
</footer>
</body>
</html>

1
code/html/bulma.min.css vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,29 @@
{% extends "base.jinja" %}
{% block content %}
<section class="section">
<div class="container">
<h1>{{ index['title'] }}</h1>
<p>
{% set comma = joiner(",") %}
{% for category in index['categories'] %}
{{ comma() }} <a href="#{{ category }}">{{ category.capitalize() }}</a> ({{ index['number_entries'][category] }})
{% endfor %}
</p>
{% for category in index['categories'] %}
<h2 id="{{ category }}">{{ category.capitalize() }}</h2>
<div class="columns">
{% for entries_column in index['entries'][category] %}
<div class="column">
<ul>
{% for entry in entries_column %}
<li>{{ entry }}</li>
{% endfor %}
</ul>
</div>
{% endfor %}
</div>
{% endfor %}
</div>
</section>
{% endblock %}

View File

@ -0,0 +1,4 @@
{% extends "base.jinja" %}
{% block content %}
{% endblock %}

View File

@ -0,0 +1,15 @@
{% extends "base.jinja" %}
{% block content %}
<section class="section">
<div class="container">
<h1>Inspirations ({{ letter.capitalize() }})</h1>
{% for developer in developers %}
<p>
{{ developer['Name'] }}<br>
Games: {{ developer['Games']|join(', ') }}
</p>
{% endfor %}
</div>
</section>
{% endblock %}

View File

@ -0,0 +1,24 @@
{% extends "base.jinja" %}
{% block content %}
<section class="section">
<div class="container">
<h1>Games ({{ letter.capitalize() }})</h1>
{% for g in games %}
<p>
<a id="#{{ g['title-anchor'] }}">{{ g['Title'] }}</a><br>
Home: {{ g['Home'] }}<br>
{% if 'Inspirations' in g %}{% endif%}
{% if 'Media' in g %}{% endif%}
{% if 'Download' in g %}{% endif%}
{% if 'Play' in g %}{% endif%}
{% if 'Developer' in g %}{% endif%}
{% if 'Note' in g %}{% endif%}
Technical info:<br>
Language: {{ g['Code language'] }}<br>
License: {{ g['Code license'] }}
</p>
{% endfor %}
</div>
</section>
{% endblock %}

14
code/html/index.jinja Normal file
View File

@ -0,0 +1,14 @@
{% extends "base.jinja" %}
{% block content %}
<section class="section">
<div class="container">
<h1 class="title">
Open source games list (OSGL)
</h1>
<p class="subtitle">
Contains information about {{ index['number_games'] }} open source games.
</p>
</div>
</section>
{% endblock %}

View File

@ -0,0 +1,16 @@
{% extends "base.jinja" %}
{% block content %}
<section class="section">
<div class="container">
<h1>Inspirations ({{ letter.capitalize() }})</h1>
{% for inspiration in inspirations %}
<p>
{{ inspiration['Name'] }}<br>
{% if 'Media' in inspiration %}Media: {{ inspiration['Media'] }}<br>{% endif %}
Inspired entries: {% for game in inspiration['Inspired entries'] %}{{ game }}{% endfor %}
</p>
{% endfor %}
</div>
</section>
{% endblock %}

14
code/html/list.jinja Normal file
View File

@ -0,0 +1,14 @@
{% extends "base.jinja" %}
{% block content %}
<section class="section">
<div class="container">
<h1>{{ data['title'] }}</h1>
<ul>
{% for item in data['items'] %}
<li>{{ item }}</li>
{% endfor %}
</ul>
</div>
</section>
{% endblock %}

View File

@ -0,0 +1,4 @@
{% extends "base.jinja" %}
{% block content %}
{% endblock %}

4
code/html/table.jinja Normal file
View File

@ -0,0 +1,4 @@
{% extends "base.jinja" %}
{% block content %}
{% endblock %}

View File

@ -14,6 +14,7 @@ import textwrap
from utils import osg, osg_ui, utils, constants as c
import requests
def check_validity_backlog():
import requests
@ -62,8 +63,6 @@ def create_toc(title, file, entries):
# write to toc file
utils.write_text(toc_file, text)
print('Readme and TOCs updated')
def sort_text_file(file, name):
"""
@ -432,6 +431,8 @@ class EntriesMaintainer:
# write to readme
utils.write_text(readme_file, text)
print('Readme and TOCs updated')
def update_statistics(self):
"""
Generates the statistics page.
@ -821,7 +822,6 @@ if __name__ == "__main__":
'Read entries': m.read_entries,
'Write entries': m.write_entries,
'Check template leftovers': m.check_template_leftovers,
'Check external links': m.check_external_links,
'Check inconsistencies': m.check_inconsistencies,
'Check rejected entries': m.clean_rejected,
'Check external links (takes quite long)': m.check_external_links,

View File

@ -8,6 +8,7 @@ valid_duplicates = ('Age of Empires', 'ARMA', 'Catacomb', 'Civilization', 'Compa
'Final Fantasy', 'Heroes of Might and Magic', 'Jazz Jackrabbit', 'Marathon', 'Master of Orion', 'Quake',
'RollerCoaster Tycoon', 'Star Wars Jedi Knight', 'The Settlers', 'Ultima', 'Ship Simulator')
class InspirationMaintainer:
def __init__(self):
@ -90,6 +91,7 @@ class InspirationMaintainer:
for entry in self.entries:
entry_name = entry['Title']
for inspiration in entry.get('Inspirations', []):
inspiration = inspiration.value
if inspiration in self.inspirations:
self.inspirations[inspiration]['Inspired entries'].append(entry_name)
else:

View File

@ -2,4 +2,5 @@ pygithub
lark-parser
BeautifulSoup
PyQt5
wikipedia
wikipedia
Jinja2

View File

@ -10,6 +10,10 @@ root_path = os.path.realpath(os.path.join(os.path.dirname(__file__), os.path.par
entries_path = os.path.join(root_path, 'entries')
tocs_path = os.path.join(entries_path, 'tocs')
code_path = os.path.join(root_path, 'code')
web_path = os.path.join(root_path, 'docs')
web_template_path = os.path.join(code_path, 'html')
web_css_path = os.path.join(web_path, 'css')
inspirations_file = os.path.join(root_path, 'inspirations.md')
developer_file = os.path.join(root_path, 'developers.md')

View File

@ -1,7 +1,7 @@
# Star-Wars-III
- Home: https://github.com/abhinandanramesh/Star-Wars-III
- Inspirations: Star Wars (1983 arcade game)
- Inspirations: "Star Wars (1983 arcade game)"
- State: mature, inactive since 2014
- Keywords: arcade, remake, skill
- Code repository: https://github.com/abhinandanramesh/Star-Wars-III.git

View File

@ -1,7 +1,7 @@
# Survivor
- Home: http://www.schillmania.com/survivor/
- Inspirations: Survivor (1986)
- Inspirations: "Survivor (1986)"
- State: mature
- Play: http://www.schillmania.com/survivor/
- Platform: Web

View File

@ -2,7 +2,7 @@
- Home: https://github.com/Ponup/thiefcatcher
- Media: https://en.wikipedia.org/wiki/Where_in_the_World_Is_Carmen_Sandiego%3F_(1985_video_game)
- Inspirations: Where in the World Is Carmen Sandiego? (1985)
- Inspirations: "Where in the World Is Carmen Sandiego? (1985)"
- State: beta
- Keywords: educational, remake, strategy
- Code repository: https://github.com/Ponup/thiefcatcher.git

View File

@ -166,6 +166,7 @@
- **[The Clans](../the_clans.md)** (C, GPL-2.0, beta, inactive since 2003)
- **[The Dark Mod](../the_dark_mod.md)** (C++, GPL-3.0, mature)
- **[The Endless Dungeons](../the_endless_dungeons.md)** (C, CC-BY-NC-SA-2.0, mature, inactive since 2015)
- **[The Epic of Heroes](../the_epic_of_heroes.md)** (C++, GPL-3.0, beta, inactive since 2015)
- **[The hunt for the lost rainbow jewels (Jewelhunt)](../the_hunt_for_the_lost_rainbow_jewels_jewelhunt.md)** (Java, GPL-2.0, beta)
- **[The Mana World](../the_mana_world.md)** (PHP, GPL-2.0, mature)
- **[TinTin++](../tintin++.md)** (C, GPL-3.0, mature)

View File

@ -3,8 +3,10 @@
- **[2048](../2048.md)** (JavaScript, MIT, mature)
- **[2Moons Browsergame Engine](../2moons_browsergame_engine.md)** (PHP, JavaScript, MIT, mature)
- **[3d.city](../3dcity.md)** (JavaScript, GPL-3.0, mature, inactive since 2016)
- **[Achtung, die Kurve!](../achtung_die_kurve.md)** (JavaScript, AGPL-3.0, mature)
- **[Ajax3d](../ajax3d.md)** (JavaScript, GPL-2.0, beta, inactive since 2007)
- **[Allure of the Stars](../allure_of_the_stars.md)** (Haskell, AGPL-3.0, beta)
- **[Arashi-JS](../arashi-js.md)** (JavaScript, GPL-2.0, beta, inactive since 2010)
- **[Armor Alley](../armor_alley.md)** (JavaScript, CC-BY-NC-3.0, beta)
- **[asdf](../asdf.md)** (JavaScript, MIT, mature)
@ -12,14 +14,18 @@
- **[boardgame.io](../boardgameio.md)** (JavaScript, TypeScript, MIT, beta)
- **[Boohu](../boohu.md)** (Go, ISC, beta)
- **[British Bingo](../british_bingo.md)** (JavaScript, GPL-3.0, mature)
- **[BrowserQuest](../browserquest.md)** (JavaScript, MPL-2.0, mature)
- **[Candy Box 2](../candy_box_2.md)** (TypeScript, JavaScript, GPL-3.0, mature, inactive since 2013)
- **[Castle of the Winds](../castle_of_the_winds.md)** (JavaScript, MIT, beta, inactive since 2016)
- **[Cattle Bity](../cattle_bity.md)** (TypeScript, MIT, mature)
- **[cc94](../cc94.md)** (C++, Python, AGPL-3.0, beta)
- **[Chess3D](../chess3d.md)** (JavaScript, Python, GPL-3.0, mature, inactive since 2016)
- **[Clumsy Bird](../clumsy_bird.md)** (JavaScript, MIT, mature, inactive since 2017)
- **[Consomaton](../consomaton.md)** (TypeScript, MIT, mature, inactive since 2016)
- **[CrappyBird](../crappybird.md)** (JavaScript, MIT, mature, inactive since 2017)
- **[Dedalus](../dedalus.md)** (JavaScript, GPL-2.0, beta, inactive since 2018)
- **[Devana](../devana.md)** (PHP, zlib, beta)
- **[Enduro tribute](../enduro_tribute.md)** (JavaScript, MIT, mature)
- **[Esenthel Engine](../esenthel_engine.md)** (C, C++, Custom, beta)
- **[Executive Man](../executive_man.md)** (JavaScript, Custom, mature, inactive since 2017)
- **[F.LF](../flf.md)** (JavaScript, CC-BY-NC-SA-3.0, mature)
@ -27,6 +33,8 @@
- **[Faur](../faur.md)** (C, Python, C++, GPL-3.0, beta)
- **[flixel-gdx](../flixel-gdx.md)** (Java, 3-clause BSD, beta, inactive since 2017)
- **[Floppy Birb](../floppy_birb.md)** (JavaScript, MIT, mature)
- **[Fluid Table Tennis](../fluid_table_tennis.md)** (JavaScript, MIT, mature, inactive since 2013)
- **[Gift Grabber](../gift_grabber.md)** (JavaScript, Apache-2.0, mature)
- **[gist-txt](../gist-txt.md)** (JavaScript, MIT, mature, inactive since 2018)
- **[GNU FreeDink](../gnu_freedink.md)** (C, GPL-3.0, mature, inactive since 2012)
- **[Grimsonland](../grimsonland.md)** (JavaScript, MIT, mature, inactive since 2017)
@ -36,11 +44,13 @@
- **[JavaScript Graphic Adventure Maker](../javascript_graphic_adventure_maker.md)** (JavaScript, MIT, mature)
- **[javascript-E.T.](../javascript-et.md)** (JavaScript, Assembly, MIT, beta, inactive since 2017)
- **[Jazz² Resurrection](../jazz_resurrection.md)** (C#, JavaScript, GPL-3.0, mature)
- **[jsFO](../jsfo.md)** (JavaScript, Python, Apache-2.0, beta, inactive since 2017)
- **[LambdaHack](../lambdahack.md)** (Haskell, 3-clause BSD, beta)
- **[Lemmings.ts](../lemmingsts.md)** (TypeScript, MIT, mature)
- **[Lords of the Fey](../lords_of_the_fey.md)** (JavaScript, AGPL-3.0, beta, inactive since 2018)
- **[Lose Your Marbles](../lose_your_marbles.md)** (JavaScript, MIT, mature, inactive since 2014)
- **[melonJS](../melonjs.md)** (JavaScript, MIT, mature)
- **[Minesweeper.Zone](../minesweeperzone.md)** (JavaScript, PHP, MIT, mature)
- **[Minilens](../minilens.md)** (GDScript, GPL-3.0, mature, inactive since 2018)
- **[mk.js](../mkjs.md)** (JavaScript, MIT, beta)
- **[Online Chess Club](../online_chess_club.md)** (PHP, JavaScript, GPL-2.0, mature)

View File

@ -2,7 +2,7 @@
- Home: http://unknown-horizons.org/, https://sourceforge.net/projects/unknownhorizons/
- Media: https://en.wikipedia.org/wiki/Unknown_Horizons
- Inspirations: Anno (series)
- Inspirations: "Anno (series)"
- State: beta
- Download: http://unknown-horizons.org/downloads/
- Keywords: strategy, clone, turn-based

File diff suppressed because one or more lines are too long