entries with values with comments

This commit is contained in:
Trilarion 2020-09-07 15:48:14 +02:00
parent 6b2edf8f56
commit 8a08425e09
17 changed files with 155 additions and 143 deletions

View File

@ -3,10 +3,11 @@ title: "#" /(?! ).+(?<! )/ _NL // not starting or ending with a
property: "-" _key ":" _values _NL property: "-" _key ":" _values _NL
_key : /(?! ).+?(?=:)(?<! )/ // key: everything until next ":", not beginning or ending with a space _key : /(?! ).+?(?=:)(?<! )/ // key: everything until next ":", not beginning or ending with a space
_values : [_value ("," _value)*] _values : [value ("," value)*]
_value : quoted_value | unquoted_value value : (quoted_value comment_value?) | (unquoted_value comment_value?)
quoted_value : /\".+?\"/ // with quotation marks, can contain commas quoted_value : /\".*?\"/
unquoted_value : /(?![ \"])[^,\n]+(?<![ \"])/ // cannot contain commas, cannot start or end with quotation mark unquoted_value : /(?![ \"]).+?(?=,|\n| \()/
comment_value : /(?<= )\(.+?\)/
note: /(?![\-#]).*\n/+ // Unstructured text, not starting with - or # note: /(?![\-#]).*\n/+ // Unstructured text, not starting with - or #

View File

@ -1,13 +1,14 @@
start: _COMMENT _HEADER entry* start: _COMMENT _HEADER entry*
entry: "##" name "[" _NUMBER "]" _NL property+ entry: "##" name "[" _NUMBER "]" _NL property+
property: "-" _key ":" _values _NL property: "-" _key ":" _values _NL
_key : /(?! ).+?(?=:)(?<! )/ // key: everything until next ":", not beginning or ending with a space _key : /(?! ).+?(?= *:)/ // key: everything until next ":", not beginning or ending with a space
_values : [_value ("," _value)*] _values : [_value ("," _value)*]
_value : quoted_value | unquoted_value _value : quoted_value | unquoted_value
quoted_value : /\".+?\"/ // with quotation marks, can contain commas quoted_value : /\".*?\"/ // with quotation marks, can contain commas
unquoted_value : /(?![ \"])[^,\n]+(?<![ \"])/ // cannot contain commas, cannot start or end with quotation mark unquoted_value : /(?![ \"]).+?(?= *,| *\n)/
name: /(?! ).+?(?= \[)/ // developer name: everything until " ["
name: /(?! ).+?(?= +\[)/ // developer name: everything until " ["
_NUMBER: /[0-9]+/ _NUMBER: /[0-9]+/
@ -15,9 +16,9 @@ CR : /\r/
LF : /\n/ LF : /\n/
_NL : CR? LF _NL : CR? LF
WS : (" "|/\t/)+ WS : (" "|/\t/)+
_EL : /^$\n/m _EL : /^$\n/m // empty new line
_COMMENT : /^\[comment\]: #.*$\n/m // [comment]: # xxx _COMMENT : /^\[comment\]: #.*$\n/m // [comment]: # xxx
_HEADER : /^# .+$\n/m _HEADER : /^# .+$\n/m // line staring with "# "
%ignore WS %ignore WS
%ignore _EL %ignore _EL

View File

@ -33,11 +33,11 @@ def get_config(key):
generic_comment_string = '[comment]: # (partly autogenerated content, edit with care, read the manual before)' generic_comment_string = '[comment]: # (partly autogenerated content, edit with care, read the manual before)'
# these fields have to be present in each entry (in this order) # these fields have to be present in each entry (in this order)
essential_fields = ('Title', 'Home', 'State', 'Keywords', 'Code repository', 'Code language', 'Code license') essential_fields = ('File', 'Title', 'Home', 'State', 'Keywords', 'Code repository', 'Code language', 'Code license')
# only these fields can be used currently (in this order) # only these fields can be used currently (in this order)
valid_fields = ( valid_fields = (
'Title', 'Home', 'Media', 'Inspirations', 'State', 'Play', 'Download', 'Platform', 'Keywords', 'Code repository', 'Code language', 'File', 'Title', 'Home', 'Media', 'Inspirations', 'State', 'Play', 'Download', 'Platform', 'Keywords', 'Code repository', 'Code language',
'Code license', 'Code dependencies', 'Assets license', 'Developer', 'Note', 'Building') 'Code license', 'Code dependencies', 'Assets license', 'Developer', 'Note', 'Building')
valid_building_fields = ('Build system', 'Build instructions') valid_building_fields = ('Build system', 'Build instructions')
@ -93,7 +93,9 @@ general_code_dependencies_without_entry = {'OpenGL': 'https://www.opengl.org/',
# developer information (in the file all fields will be capitalized) # developer information (in the file all fields will be capitalized)
essential_developer_fields = ('Name', 'Games') essential_developer_fields = ('Name', 'Games')
valid_developer_fields = essential_developer_fields + ('Contact', 'Home', 'Organization') valid_developer_fields = essential_developer_fields + ('Contact', 'Home', 'Organization')
url_developer_fields = ('Home',)
# inspiration/original game information (in the file all fields will be capitalized) # inspiration/original game information (in the file all fields will be capitalized)
essential_inspiration_fields = ('Name', 'Inspired entries') essential_inspiration_fields = ('Name', 'Inspired entries')
valid_inspiration_fields = essential_inspiration_fields + ('Media',) valid_inspiration_fields = essential_inspiration_fields + ('Media',)
url_inspiration_fields = ('Media',)

View File

@ -300,28 +300,32 @@ def read_developers():
grammar_file = os.path.join(code_path, 'grammar_listing.lark') grammar_file = os.path.join(code_path, 'grammar_listing.lark')
developers = osg_parse.read_and_parse(developer_file, grammar_file, osg_parse.ListingTransformer) developers = osg_parse.read_and_parse(developer_file, grammar_file, osg_parse.ListingTransformer)
# now transform a bit more # now developers is a list of dictionaries for every entry with some properties
for index, dev in enumerate(developers):
# check for valid keys
for field in dev.keys():
if field not in valid_developer_fields:
raise RuntimeError('Unknown developer field "{}" for developer: {}.'.format(field, dev['Name']))
# strip from name or organization (just in case)
for field in ('Name', ):
if field in dev:
dev[field] = dev[field].strip()
# split games, contact (are lists)
for field in ('Games', 'Contact'):
if field in dev:
content = dev[field]
content = [x.strip() for x in content]
dev[field] = content
# check for duplicate names entries # check for duplicate names entries
names = [dev['Name'] for dev in developers] names = [dev['Name'] for dev in developers]
duplicate_names = (name for name in names if names.count(name) > 1) duplicate_names = (name for name in names if names.count(name) > 1)
duplicate_names = set(duplicate_names) # to avoid duplicates in duplicate_names duplicate_names = set(duplicate_names) # to avoid duplicates in duplicate_names
if duplicate_names: if duplicate_names:
print('Warning: duplicate developer names: {}'.format(', '.join(duplicate_names))) print('Warning: duplicate developer names: {}'.format(', '.join(duplicate_names)))
# check for essential, valid fields
for dev in developers:
# check that essential fields are existing
for field in essential_developer_fields:
if field not in dev:
raise RuntimeError('Essential field "{}" missing in developer {}'.format(field, dev['Name']))
# check that all fields are valid fields
for field in dev.keys():
if field not in valid_developer_fields:
raise RuntimeError('Invalid field "{}" in developer {}.'.format(field, dev['Name']))
# url fields
for field in url_developer_fields:
if field in dev:
content = dev[field]
if any(not (x.startswith('http://') or x.startswith('https://')) for x in content):
raise RuntimeError('Invalid URL in field "{}" in developer {}.'.format(field, dev['Name']))
return developers return developers
@ -343,7 +347,7 @@ def write_developers(developers):
for dev in developers: for dev in developers:
keys = list(dev.keys()) keys = list(dev.keys())
# developer name # developer name
content += '## {} [{}]\n\n'.format(dev['Name'], len(dev['games'])) content += '## {} [{}]\n\n'.format(dev['Name'], len(dev['Games']))
keys.remove('Name') keys.remove('Name')
# all the remaining in alphabetical order, but 'games' first # all the remaining in alphabetical order, but 'games' first
@ -352,7 +356,6 @@ def write_developers(developers):
keys = ['Games'] + keys keys = ['Games'] + keys
for field in keys: for field in keys:
value = dev[field] value = dev[field]
field = field.capitalize()
# lists get special treatment # lists get special treatment
if isinstance(value, list): if isinstance(value, list):
value.sort(key=str.casefold) value.sort(key=str.casefold)
@ -377,20 +380,7 @@ def read_inspirations():
grammar_file = os.path.join(code_path, 'grammar_listing.lark') grammar_file = os.path.join(code_path, 'grammar_listing.lark')
inspirations = osg_parse.read_and_parse(inspirations_file, grammar_file, osg_parse.ListingTransformer) inspirations = osg_parse.read_and_parse(inspirations_file, grammar_file, osg_parse.ListingTransformer)
# now inspirations is a list of dictionaries for every entry with keys (valid_developers_fields) # now inspirations is a list of dictionaries for every entry with some properties
# now transform a bit more
for inspiration in inspirations:
# check that keys are valid keys
for field in inspiration.keys():
if field not in valid_inspiration_fields:
raise RuntimeError('Unknown field "{}" for inspiration: {}.'.format(field, inspiration['Name']))
# split lists
for field in ('Inspired entries',):
if field in inspiration:
content = inspiration[field]
content = [x.strip() for x in content]
inspiration[field] = content
# check for duplicate names entries # check for duplicate names entries
names = [inspiration['Name'] for inspiration in inspirations] names = [inspiration['Name'] for inspiration in inspirations]
@ -399,6 +389,23 @@ def read_inspirations():
if duplicate_names: if duplicate_names:
raise RuntimeError('Duplicate inspiration names: {}'.format(', '.join(duplicate_names))) raise RuntimeError('Duplicate inspiration names: {}'.format(', '.join(duplicate_names)))
# check for essential, valid fields
for inspiration in inspirations:
# check that essential fields are existing
for field in essential_inspiration_fields:
if field not in inspiration:
raise RuntimeError('Essential field "{}" missing in inspiration {}'.format(field, inspiration['Name']))
# check that all fields are valid fields
for field in inspiration.keys():
if field not in valid_inspiration_fields:
raise RuntimeError('Invalid field "{}" in inspiration {}.'.format(field, inspiration['Name']))
# url fields
for field in url_inspiration_fields:
if field in inspiration:
content = inspiration[field]
if any(not (x.startswith('http://') or x.startswith('https://')) for x in content):
raise RuntimeError('Invalid URL in field "{}" in inspiration {}.'.format(field, inspiration['Name']))
# convert to dictionary # convert to dictionary
inspirations = {x['Name']: x for x in inspirations} inspirations = {x['Name']: x for x in inspirations}
@ -417,7 +424,7 @@ def write_inspirations(inspirations):
# comment # comment
content = '{}\n'.format(generic_comment_string) content = '{}\n'.format(generic_comment_string)
# number of developer # updated number of inspirations
content += '# Inspirations [{}]\n\n'.format(len(inspirations)) content += '# Inspirations [{}]\n\n'.format(len(inspirations))
# sort by name # sort by name
@ -436,7 +443,6 @@ def write_inspirations(inspirations):
keys = ['Inspired entries'] + keys keys = ['Inspired entries'] + keys
for field in keys: for field in keys:
value = inspiration[field] value = inspiration[field]
field = field.capitalize()
# lists get special treatment # lists get special treatment
if isinstance(value, list): if isinstance(value, list):
value.sort(key=str.casefold) # sorted alphabetically value.sort(key=str.casefold) # sorted alphabetically
@ -472,15 +478,12 @@ def read_entries():
# parse and transform entry content # parse and transform entry content
try: try:
entry = parse(content) entry = parse(content)
# add file information entry = [('File', file),] + entry # add file information to the beginning
entry['File'] = file entry = check_and_process_entry(entry)
check_entry(entry)
post_process(entry)
except Exception as e: except Exception as e:
print('{} - {}'.format(file, e)) print('{} - {}'.format(file, e))
exception_happened = True exception_happened = True
# raise RuntimeError(e)
continue continue
# add to list # add to list
@ -490,47 +493,44 @@ def read_entries():
return entries return entries
def post_process(entry):
"""
:param entry: def check_and_process_entry(entry):
:return:
"""
# remove all parentheses from developers
if 'Developer' in entry:
devs = entry['Developer']
devs = [re.sub(r'\([^)]*\)', '', x).strip() for x in devs]
if any(not x for x in devs):
raise RuntimeError('Empty developer')
entry['Developer'] = devs
def check_entry(entry):
"""
:param entry:
:return:
"""
message = '' message = ''
file = entry['File'] # check that all fields are valid fields and are existing in that order
index = 0
for e in entry:
field = e[0]
while index < len(valid_fields) and field != valid_fields[index]:
index += 1
if index == len(valid_fields): # must be valid fields and must be in the right order
message += 'Field "{}" either misspelled or in wrong order\n'.format(field)
# check canonical file name # order is fine we can convert to dictionary
canonical_file_name = canonical_entry_name(entry['Title']) + '.md' d = {}
# we also allow -X with X =2..9 as possible extension (because of duplicate canonical file names) for field, value in entry:
if canonical_file_name != file and canonical_file_name != file[:-5] + '.md': if field in d:
message += 'file name should be {}\n'.format(canonical_file_name) message += 'Field "{}" appears twice\n'.format(field)
d[field] = value
entry = d
# check for essential fields # check for essential fields
for field in essential_fields: for field in essential_fields:
if field not in entry: if field not in entry:
message += 'essential property "{}" missing\n'.format(field) message += 'essential property "{}" missing\n'.format(field)
# check canonical file name
file = entry['File']
canonical_file_name = canonical_entry_name(entry['Title']) + '.md'
# we also allow -X with X =2..9 as possible extension (because of duplicate canonical file names)
if canonical_file_name != file and canonical_file_name != file[:-5] + '.md':
message += 'file name should be {}\n'.format(canonical_file_name)
if message: if message:
raise RuntimeError(message) raise RuntimeError(message)
return entry
def write_entries(entries): def write_entries(entries):
""" """

View File

@ -17,7 +17,7 @@ class ListingTransformer(lark.Transformer):
return x[0].value return x[0].value
def quoted_value(self, x): def quoted_value(self, x):
return x[0].value[1:-1] # remove quotation marks return x[0].value[1:-1].strip() # remove quotation marks and strip whitespaces
def property(self, x): def property(self, x):
""" """
@ -61,13 +61,23 @@ class EntryTransformer(lark.Transformer):
def quoted_value(self, x): def quoted_value(self, x):
return x[0].value[1:-1] # remove quotation marks return x[0].value[1:-1] # remove quotation marks
def comment_value(self, x):
return x[0].value[1:-1] # remove parenthesis
def value(self, x):
if len(x) == 1:
v = ValueWithComment(value=x[0])
else:
v = ValueWithComment(value=x[0], comment=x[1])
return v
def property(self, x): def property(self, x):
""" """
The key of a property will be converted to lower case and the value part is the second part The key of a property will be converted to lower case and the value part is the second part
:param x: :param x:
:return: :return:
""" """
return x[0], x[1:] return x[0].value, x[1:]
def title(self, x): def title(self, x):
return 'Title', x[0].value return 'Title', x[0].value
@ -83,33 +93,33 @@ class EntryTransformer(lark.Transformer):
return 'Note', ''.join((x.value for x in x)) return 'Note', ''.join((x.value for x in x))
def building(self, x): def building(self, x):
d = {} return 'Building', x
for key, value in x:
if key in d:
raise RuntimeError('Key in entry appears twice')
d[key] = value
return 'Building', d
def start(self, x): def start(self, x):
# we do the essential fields and valid fields checks right here return x
fields = [x[0] for x in x]
# check for essential fields
for field in c.essential_fields: class ValueWithComment:
if field not in fields: """
raise RuntimeError('Essential field "{}" is missing'.format(field)) All our property values can have (optional) comments. This is the class that represents them to us and implements
# check for valid fields (in that order) equality and 'in' operator functionality purely on the value.
index = 0 """
for field in fields:
while index < len(c.valid_fields) and field != c.valid_fields[index]: def __init__(self, value, comment=None):
index += 1 self.value = value
if index == len(c.valid_fields): self.comment = comment
raise RuntimeError('Field "{}" either not valid or in wrong order'.format(field))
d = {} def __contains__(self, item):
for key, value in x: return item in self.value
if key in d:
raise RuntimeError('Key in entry appears twice') def __eq__(self, other):
d[key] = value return self.value == other
return d
def __repr__(self):
if self.comment:
return '{} ({})'.format(self.value, self.comment)
else:
return '{}'.format(self.value)
def parse(parser, transformer, content): def parse(parser, transformer, content):

View File

@ -32,7 +32,7 @@
## Alex Margarit [1] ## Alex Margarit [1]
- Games: faur - Games: Faur
## Alexander Lang [1] ## Alexander Lang [1]
@ -590,7 +590,7 @@
- Games: Hack - Games: Hack
## Jean-Baptiste Lamy / Nekeme Prod. [1] ## Jean-Baptiste "Jiba" Lamy [1]
- Games: Slune - Games: Slune

View File

@ -5,7 +5,7 @@
- Inspirations: Ballerburg - Inspirations: Ballerburg
- State: mature - State: mature
- Keywords: action, artillery, remake - Keywords: action, artillery, remake
- Code repository: https://git.tuxfamily.org/baller/baller.git, https://gitlab.com/osgames/ballerburg.git (+) (import of original source downloads) - Code repository: https://git.tuxfamily.org/baller/baller.git, https://gitlab.com/osgames/ballerburg.git (@add, import of original source downloads)
- Code language: C - Code language: C
- Code license: GPL-3.0 - Code license: GPL-3.0
- Code dependencies: SDL2 - Code dependencies: SDL2

View File

@ -9,7 +9,7 @@
- Code language: Python - Code language: Python
- Code license: GPL-2.0 - Code license: GPL-2.0
- Code dependencies: pyglet - Code dependencies: pyglet
- Assets license: CC (caprica-letters and all the music in the latest version - Assets license: CC (caprica-letters and all the music in the latest version)
Dual n-back brain training exercise. Dual n-back brain training exercise.

View File

@ -4,7 +4,7 @@
- State: mature, inactive since 2014 - State: mature, inactive since 2014
- Download: https://sourceforge.net/projects/e-adventure/files/ - Download: https://sourceforge.net/projects/e-adventure/files/
- Keywords: adventure, game engine - Keywords: adventure, game engine
- Code repository: https://github.com/e-ucm/eAdventure-legacy.git, https://github.com/e-ucm/eAdventure.git (+), https://github.com/e-ucm/uAdventure.git (+), https://gitlab.com/osgames/e-adventure.git (+) (conversion of svn), https://svn.code.sf.net/p/e-adventure/code (svn) - Code repository: https://github.com/e-ucm/eAdventure-legacy.git, https://github.com/e-ucm/eAdventure.git (+), https://github.com/e-ucm/uAdventure.git (+), https://gitlab.com/osgames/e-adventure.git (@add, conversion of svn), https://svn.code.sf.net/p/e-adventure/code (svn)
- Code language: Java - Code language: Java
- Code license: GPL-3.0 - Code license: GPL-3.0

View File

@ -1,15 +1,12 @@
# Inexor # Inexor
- Home: https://inexor.org/ - Home: https://inexor.org/
- Media: <https://en.wikipedia.org/wiki/Ace_of_Spades_(video_game)>
- Inspirations: Cube 2: Sauerbraten - Inspirations: Cube 2: Sauerbraten
- State: beta, inactive since 2018 - State: beta, inactive since 2018
- Keywords: remake - Keywords: remake, first person, shooter
- Code repository: https://github.com/inexorgame/vulkan-renderer.git, https://github.com/inexorgame/inexor-core.git (+) (archived) - Code repository: https://github.com/inexorgame/vulkan-renderer.git, https://github.com/inexorgame/inexor-core.git (@add, @archived)
- Code language: C++, JavaScript - Code language: C++, JavaScript
- Code license: zlib - Code license: zlib
- Code dependencies: Cube 2 - Code dependencies: Cube 2
Remake of Cube 2: Sauerbraten.
## Building ## Building

View File

@ -8,7 +8,7 @@
- Code repository: https://github.com/miki151/keeperrl.git - Code repository: https://github.com/miki151/keeperrl.git
- Code language: C, C++ - Code language: C, C++
- Code license: GPL-2.0 - Code license: GPL-2.0
- Assets license: Proprietary (partly), CC-BY (partly) (see https://github.com/miki151/keeperrl/blob/master/COPYING-MEDIA.txt) - Assets license: Proprietary (@partly), CC-BY (@partly, see https://github.com/miki151/keeperrl/blob/master/COPYING-MEDIA.txt)
Bungeon builder. Bungeon builder.

View File

@ -8,7 +8,7 @@
- Code repository: https://github.com/lexica/lexica.git - Code repository: https://github.com/lexica/lexica.git
- Code language: Java - Code language: Java
- Code license: GPL-3.0 - Code license: GPL-3.0
- Assets license: Apache-2.0 (word lists), CC-BY-4.0 (icons) (see https://github.com/lexica/lexica/blob/master/LICENSES) - Assets license: Apache-2.0 (word lists), CC-BY-4.0 (icons, see https://github.com/lexica/lexica/blob/master/LICENSES)
Find as many words as possible on a grid of random letters. Find as many words as possible on a grid of random letters.

View File

@ -5,7 +5,7 @@
- State: beta, inactive since 2005 - State: beta, inactive since 2005
- Download: https://sourceforge.net/projects/microracers/files/microracers/ - Download: https://sourceforge.net/projects/microracers/files/microracers/
- Keywords: remake, 2D, racing - Keywords: remake, 2D, racing
- Code repository: https://github.com/rpmcruz/microracers.git, https://gitlab.com/osgames/microracers.git (+) (conversion of cvs), http://microracers.cvs.sourceforge.net (cvs) - Code repository: https://github.com/rpmcruz/microracers.git, https://gitlab.com/osgames/microracers.git (@add, conversion of cvs), http://microracers.cvs.sourceforge.net (cvs)
- Code language: C, C++ - Code language: C, C++
- Code license: GPL-2.0 - Code license: GPL-2.0
- Developer: Ricardo Cruz - Developer: Ricardo Cruz

View File

@ -5,7 +5,7 @@
- State: mature - State: mature
- Download: http://rpg.hamsterrepublic.com/ohrrpgce/Downloads - Download: http://rpg.hamsterrepublic.com/ohrrpgce/Downloads
- Keywords: framework - Keywords: framework
- Code repository: https://bitbucket.org/rbv/ohrrpgce-svn.git (mirror), https://github.com/ohrrpgce/ohrrpgce.git (+) (mirror), https://rpg.hamsterrepublic.com/source/wip (+) (svn) - Code repository: https://bitbucket.org/rbv/ohrrpgce-svn.git (@mirror), https://github.com/ohrrpgce/ohrrpgce.git (@add, @mirror), https://rpg.hamsterrepublic.com/source/wip (@add, svn)
- Code language: Basic - Code language: Basic
- Code license: GPL-2.0 - Code license: GPL-2.0

View File

@ -5,7 +5,7 @@
- Download: https://sourceforge.net/projects/osgg/files/ - Download: https://sourceforge.net/projects/osgg/files/
- Platform: Windows, Linux - Platform: Windows, Linux
- Keywords: arcade, open content, side-scrolling - Keywords: arcade, open content, side-scrolling
- Code repository: https://github.com/DusteDdk/osgg.git, https://gitlab.com/osgames/osgg.git (+) (conversion of svn), https://svn.code.sf.net/p/osgg/code (svn) - Code repository: https://github.com/DusteDdk/osgg.git, https://gitlab.com/osgames/osgg.git (@add, conversion of svn), https://svn.code.sf.net/p/osgg/code (svn)
- Code language: C++ - Code language: C++
- Code license: GPL-3.0 - Code license: GPL-3.0
- Code dependencies: OpenGL, SDL - Code dependencies: OpenGL, SDL

View File

@ -8,7 +8,7 @@
- Code language: Python - Code language: Python
- Code license: GPL-2.0 - Code license: GPL-2.0
- Code dependencies: Cal3D, GLEW, OpenGL, Py2Play, SDL - Code dependencies: Cal3D, GLEW, OpenGL, Py2Play, SDL
- Developer: Jean-Baptiste Lamy (Jiba) / Nekeme Prod. - Developer: Jean-Baptiste "Jiba" Lamy (Nekeme Prod.)
Action game. Action game.

View File

@ -12,7 +12,7 @@
## Abuse [1] ## Abuse [1]
- Inspired entries: Abuse - Inspired entries: Abuse
- Media: <https://en.wikipedia.org/wiki/Abuse_(video_game)> - Media: https://en.wikipedia.org/wiki/Abuse_(video_game)
## Ace Combat: Assault Horizon [1] ## Ace Combat: Assault Horizon [1]
@ -22,7 +22,7 @@
## Ace of Spades [3] ## Ace of Spades [3]
- Inspired entries: BetterSpades, Iceball, OpenSpades - Inspired entries: BetterSpades, Iceball, OpenSpades
- Media: <https://en.wikipedia.org/wiki/Ace_of_Spades_(video_game)> - Media: https://en.wikipedia.org/wiki/Ace_of_Spades_(video_game)
## Achtung, die Kurve! [4] ## Achtung, die Kurve! [4]
@ -37,7 +37,7 @@
## Age of Empires [2] ## Age of Empires [2]
- Inspired entries: 0 A.D., openage - Inspired entries: 0 A.D., openage
- Media: <https://en.wikipedia.org/wiki/Age_of_Empires_(video_game)> - Media: https://en.wikipedia.org/wiki/Age_of_Empires_(video_game)
## Age of Empires II [2] ## Age of Empires II [2]
@ -56,7 +56,7 @@
## Allegiance [1] ## Allegiance [1]
- Inspired entries: Free Allegiance - Inspired entries: Free Allegiance
- Media: <https://en.wikipedia.org/wiki/Allegiance_(video_game)> - Media: https://en.wikipedia.org/wiki/Allegiance_(video_game)
## Anno (series) [1] ## Anno (series) [1]
@ -297,7 +297,7 @@
## Cadaver [1] ## Cadaver [1]
- Inspired entries: Cadaver - Inspired entries: Cadaver
- Media: <https://en.wikipedia.org/wiki/Cadaver_(video_game)> - Media: https://en.wikipedia.org/wiki/Cadaver_(video_game)
## Caesar 3 [2] ## Caesar 3 [2]
@ -462,6 +462,7 @@
## Cube 2: Sauerbraten [2] ## Cube 2: Sauerbraten [2]
- Inspired entries: Inexor, Open Cube - Inspired entries: Inexor, Open Cube
- Media: https://en.wikipedia.org/wiki/Cube_2:_Sauerbraten
## CUBE engine [1] ## CUBE engine [1]
@ -614,7 +615,7 @@
## E.T. the Extra-Terrestrial [1] ## E.T. the Extra-Terrestrial [1]
- Inspired entries: javascript-E.T. - Inspired entries: javascript-E.T.
- Media: <https://en.wikipedia.org/wiki/E.T._the_Extra-Terrestrial_(video_game)> - Media: https://en.wikipedia.org/wiki/E.T._the_Extra-Terrestrial_(video_game)
## Eat The Whistle [1] ## Eat The Whistle [1]
@ -737,7 +738,7 @@
## Freelancer [1] ## Freelancer [1]
- Inspired entries: Librelancer - Inspired entries: Librelancer
- Media: <https://en.wikipedia.org/wiki/Freelancer_(video_game)> - Media: https://en.wikipedia.org/wiki/Freelancer_(video_game)
## Freeserf [1] ## Freeserf [1]
@ -778,7 +779,7 @@
## GoldenEye 007 [2] ## GoldenEye 007 [2]
- Inspired entries: ges-code, GoldenEye: Source - Inspired entries: ges-code, GoldenEye: Source
- Media: <https://en.wikipedia.org/wiki/GoldenEye_007_(1997_video_game)> - Media: https://en.wikipedia.org/wiki/GoldenEye_007_(1997_video_game)
## Gorillas [2] ## Gorillas [2]
@ -944,7 +945,7 @@
## Krush Kill 'n' Destroy [1] ## Krush Kill 'n' Destroy [1]
- Inspired entries: KKnD - Inspired entries: KKnD
- Media: <https://en.wikipedia.org/wiki/KKnD_(video_game)> - Media: https://en.wikipedia.org/wiki/KKnD_(video_game)
## Kula World [1] ## Kula World [1]
@ -981,7 +982,7 @@
## Lemmings [5] ## Lemmings [5]
- Inspired entries: Lemmings.ts, Lemmini, Lix, Pingus, Rabbit Escape - Inspired entries: Lemmings.ts, Lemmini, Lix, Pingus, Rabbit Escape
- Media: <https://en.wikipedia.org/wiki/Lemmings_(video_game)> - Media: https://en.wikipedia.org/wiki/Lemmings_(video_game)
## Liero [4] ## Liero [4]
@ -1056,7 +1057,7 @@
## Marathon [1] ## Marathon [1]
- Inspired entries: Aleph One - Inspired entries: Aleph One
- Media: <https://marathongame.fandom.com/wiki/Marathon_(Game)> - Media: https://marathongame.fandom.com/wiki/Marathon_(Game)
## Marathon 2 [1] ## Marathon 2 [1]
@ -1507,7 +1508,7 @@
## Seven Kingdoms: Ancient Adversaries [1] ## Seven Kingdoms: Ancient Adversaries [1]
- Inspired entries: Seven Kingdoms: Ancient Adversaries - Inspired entries: Seven Kingdoms: Ancient Adversaries
- Media: <https://en.wikipedia.org/wiki/Seven_Kingdoms_(video_game)> - Media: https://en.wikipedia.org/wiki/Seven_Kingdoms_(video_game)
## sfxr [1] ## sfxr [1]
@ -1528,17 +1529,17 @@
## Ship Simulator 2006 [1] ## Ship Simulator 2006 [1]
- Inspired entries: Bridge Command - Inspired entries: Bridge Command
- Media: <https://en.wikipedia.org/wiki/Ship_Simulator_(video_game)> - Media: https://en.wikipedia.org/wiki/Ship_Simulator_(video_game)
## Ship Simulator 2008 [1] ## Ship Simulator 2008 [1]
- Inspired entries: Bridge Command - Inspired entries: Bridge Command
- Media: <https://en.wikipedia.org/wiki/Ship_Simulator_(video_game)> - Media: https://en.wikipedia.org/wiki/Ship_Simulator_(video_game)
## Ship Simulator Extremes [1] ## Ship Simulator Extremes [1]
- Inspired entries: Bridge Command - Inspired entries: Bridge Command
- Media: <https://en.wikipedia.org/wiki/Ship_Simulator_(video_game)> - Media: https://en.wikipedia.org/wiki/Ship_Simulator_(video_game)
## Shobon Action [1] ## Shobon Action [1]
@ -1571,7 +1572,7 @@
## SimCity [9] ## SimCity [9]
- Inspired entries: 3d.city, Citybound, Cytopia, Divercity, Lincity, LinCity-NG, Micropolis, micropolisJS, OpenCity - Inspired entries: 3d.city, Citybound, Cytopia, Divercity, Lincity, LinCity-NG, Micropolis, micropolisJS, OpenCity
- Media: <https://en.wikipedia.org/wiki/SimCity_(1989_video_game)> - Media: https://en.wikipedia.org/wiki/SimCity_(1989_video_game)
## SimCity 2000 [1] ## SimCity 2000 [1]
@ -1783,7 +1784,7 @@
## Super Monkey Ball [4] ## Super Monkey Ball [4]
- Inspired entries: irrlamb, Neverball, Nuncabola, Veraball - Inspired entries: irrlamb, Neverball, Nuncabola, Veraball
- Media: <https://en.wikipedia.org/wiki/Super_Monkey_Ball_(video_game)> - Media: https://en.wikipedia.org/wiki/Super_Monkey_Ball_(video_game)
## Super Smash Bros. [3] ## Super Smash Bros. [3]
@ -2010,7 +2011,7 @@
## Turmoil [1] ## Turmoil [1]
- Inspired entries: Data Storm - Inspired entries: Data Storm
- Media: <https://en.wikipedia.org/wiki/Turmoil_(1984_video_game)> - Media: https://en.wikipedia.org/wiki/Turmoil_(1984_video_game)
## Turok [1] ## Turok [1]
@ -2146,7 +2147,7 @@
## Warlords II [2] ## Warlords II [2]
- Inspired entries: FreeLords, LordsAWar! - Inspired entries: FreeLords, LordsAWar!
- Media: <https://en.wikipedia.org/wiki/Warlords_(game_series)> - Media: https://en.wikipedia.org/wiki/Warlords_(game_series)
## Warrior Kings [1] ## Warrior Kings [1]