NetHack/nh-timeline.py

From NetHackWiki
Jump to navigation Jump to search

The MediaWiki timeline facility seems to be broken. This program will update the file nh-timeline.svg in the meantime.

After running this, upload nh-timeline.svg as File:nh-timeline.svg and imagemap.txt as Template:Timeline of NetHack.

#!/usr/bin/env python

import datetime

# Raw data for the timeline
# datetime.date takes parameters as year, month, day
# The second element is text that appears in the image
# The third element is a vertical adjustment to keep the texts from overlapping
# Positive adjustments go down
# The elements do not need to be in any particular order
#
#Sources:
#https://nethack.org/download/
#https://groups.google.com/forum/#!searchin/net.sources/Hack%7Csort:relevance
#https://groups.google.com/forum/#!searchin/net.sources.games/Hack%7Csort:relevance
#https://groups.google.com/forum/#!searchin/comp.sources.games/NetHack%7Csort:relevance
#https://groups.google.com/forum/#!searchin/comp.sources.games.bugs/NetHack%7Csort:relevance
##http://web.archive.org/web/20120211120040/www.spod-central.org/~psmith/nh/anhftime.html (www.google.com → groups.google.com)
##http://www.juiblex.co.uk/nethack/source.html

date_list = [
    ( datetime.date(1984,  1, 17), 'HACK', 0 ),
    ( datetime.date(1984,  9, 22), 'Hack 1.21', +11 ), #placeholder date
    ( datetime.date(1984, 12, 12), 'Hack 1.0', +6 ),
    ( datetime.date(1985,  1, 21), 'Hack 1.0.1', -3 ),
    ( datetime.date(1985,  2, 10), 'Hack for PDP-11', -14 ),
    ( datetime.date(1985,  4,  1), 'Hack 1.0.2', -22 ),
    ( datetime.date(1985,  5, 28), 'PC/IX Hack', -29 ),
    ( datetime.date(1985,  7, 23), 'Hack 1.0.3', -37 ),
    ( datetime.date(1987,  7, 28), 'NetHack 1.3d', +14 ),
    ( datetime.date(1987,  8, 14), 'NetHack 1.4f', +2 ),
    ( datetime.date(1987, 12,  3), 'NetHack 2.2a', 0 ),
    ( datetime.date(1988,  4,  9), 'NetHack 2.3e', 0 ),
    ( datetime.date(1989,  7, 23), 'NetHack 3.0.0', +32-7 ),
    ( datetime.date(1989,  8,  3), 'NetHack 3.0.1', +20-7 ),
    ( datetime.date(1989,  8, 18), 'NetHack 3.0.2', +9-7 ),
    ( datetime.date(1989,  9,  7), 'NetHack 3.0.3', -2-7 ),
    ( datetime.date(1989,  9, 29), 'NetHack 3.0.4', -13-7 ),
    ( datetime.date(1989, 10, 17), 'NetHack 3.0.5', -24-7 ),
    ( datetime.date(1989, 11, 22), 'NetHack 3.0.6', -34-7 ),
    ( datetime.date(1990,  2, 23), 'NetHack 3.0.7', -38-7 ),
    ( datetime.date(1990,  6,  5), 'NetHack 3.0.8', -41-7 ),
    ( datetime.date(1990,  6, 28), 'NetHack 3.0.9', -51-7 ),
    ( datetime.date(1991,  2,  8), 'NetHack 3.0.10', -42-7 ),
    ( datetime.date(1993,  1, 28), 'NetHack 3.1.0', 0 ),
    ( datetime.date(1993,  3,  5), 'NetHack 3.1.1', -10 ),
    ( datetime.date(1993,  6, 10), 'NetHack 3.1.2', -13 ),
    ( datetime.date(1993,  7, 20), 'NetHack 3.1.3', -22 ),
    ( datetime.date(1996,  4, 18), 'NetHack 3.2.0', +4 ),
    ( datetime.date(1996,  6, 16), 'NetHack 3.2.1', -3 ),
    ( datetime.date(1996, 12, 12), 'NetHack 3.2.2', 0 ),
    ( datetime.date(1999, 12, 12), 'NetHack 3.2.3', +7 ),
    ( datetime.date(1999, 12, 12), 'NetHack 3.3.0', -6 ),
    ( datetime.date(2000,  8, 10), 'NetHack 3.3.1', 0 ),
    ( datetime.date(2002,  3, 21), 'NetHack 3.4.0', 0 ),
    ( datetime.date(2003,  2, 24), 'NetHack 3.4.1', 0 ),
    ( datetime.date(2003,  8, 31), 'NetHack 3.4.2', 0 ),
    ( datetime.date(2003, 12,  9), 'NetHack 3.4.3', -3 ),
    ( datetime.date(2015, 12,  7), 'NetHack 3.6.0', 0 ),
    ( datetime.date(2018,  4, 27), 'NetHack 3.6.1', 0 ),
    ( datetime.date(2019,  5,  9), 'NetHack 3.6.2', 0 ),
    ( datetime.date(2019, 12,  5), 'NetHack 3.6.3', +7 ),
    ( datetime.date(2019, 12, 18), 'NetHack 3.6.4', -4 ),
    ( datetime.date(2020,  1, 27), 'NetHack 3.6.5', -13 ),
    ( datetime.date(2020,  3,  8), 'NetHack 3.6.6', -22 ),
    ( datetime.date(2023,  2, 16), 'NetHack 3.6.7', 0 ),
    ( datetime.date(2026,  5,  2), 'NetHack 5.0.0', 0 ),
]

# Get a range of years to determine the vertical scale
year_min = 9999
year_max =    0
for date, text, y_adj in date_list:
    year = date.year
    if year_min > year:
        year_min = year
    if year_max < year:
        year_max = year
year_max += 1

# Constants for placement of text and graphics
month_step = 3
year_step = month_step*12
top_margin = 24
width = 210
rect_left = 50
rect_width = 26
text_spacing = 14

# Generate the document
rect_height = (year_max-year_min)*year_step
all_height = rect_height + top_margin*2
text_left = rect_left + rect_width + text_spacing
imagemap = []
with open('nh-timeline.svg', 'w') as fp:
    fp.write('<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n')
    fp.write('<!--To change this, please edit NetHack/nh-timeline.py-->\n')
    fp.write('<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n')
    fp.write('<svg width="%d" height="%d" viewBox="0 0 %d %d" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">\n'
            % (width, all_height, width, all_height))
    # Styles for drawing text
    fp.write('<style type="text/css">\n')
    fp.write('.year {\n')
    fp.write('font-family: Arial, Helvetica, sans-serif;\n')
    fp.write('font-style: normal;\n')
    fp.write('font-size: 15px;\n')
    fp.write('}\n')
    fp.write('.version {\n')
    fp.write('font-family: Arial, Helvetica, sans-serif;\n')
    fp.write('font-style: normal;\n')
    fp.write('font-size: 15px;\n')
    fp.write('fill: blue;\n')
    fp.write('}\n')
    fp.write('</style>\n')
    # The big rectangle down the left side
    fp.write('<rect fill="#000" stroke="#000" x="%d" y="%d" width="%d" height="%d"/>\n'
            % (rect_left, top_margin, rect_width, rect_height))
    # The years and the little lines that mark the years and the months
    for year in range(year_min, year_max+1):
        y = top_margin + year_step*(year_max-year)
        fp.write('<text x="%d" y="%d" class="year">%d</text>\n' % (8, y+4, year))
        fp.write('<line x1="%d" y1="%d" x2="%d" y2="%d" stroke="#000" stroke-width="%d"/>\n'
            % (rect_left-4, y, rect_left, y, 3))
        if year < year_max:
            for month in range(1, 12):
                y2 = y - month*month_step
                fp.write('<line x1="%d" y1="%d" x2="%d" y2="%d" stroke="#000" stroke-width="%d"/>\n'
                    % (rect_left-2, y2, rect_left, y2, 1))

    # month_len[i] is the length of month i
    month_len = [ None, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ]

    # Place the texts
    for date, text, y_adj in date_list:
        y = top_margin + year_step*(year_max-date.year)
        y -= (date.month-1)*month_step
        mlen = month_len[date.month]
        if date.month == 2 and date.year%4 == 0:
            mlen += 1
        y -= (date.day-1)*month_step/mlen
        # Stroke across the big rectangle
        fp.write('<line x1="%d" y1="%f" x2="%d" y2="%f" stroke="#fff" stroke-width="%d"/>\n'
            % (rect_left, y, rect_left+rect_width, y, 1))
        # From the rectangle to the text
        y2 = y+y_adj
        fp.write('<line x1="%d" y1="%f" x2="%d" y2="%f" stroke="#000" stroke-width="%d"/>\n'
            % (rect_left+rect_width, y, text_left, y2, 1))
        # The text
        link = text.replace(' ', '_')
        fp.write('<a xlink:href="https://nethackwiki.com/wiki/%s">\n' % link)
        fp.write('<text x="%d" y="%f" class="version">%s</text>\n'
                % (text_left, y2+4, text))
        fp.write('</a>\n')
        imagemap.append((y2+4, text))
    fp.write('</svg>\n')

with open('imagemap.txt', 'w') as fp:
    fp.write('<noinclude>\n')
    fp.write('To change this, edit the script at [[NetHack/nh-timeline.py]] ')
    fp.write('and upload nh-timeline.svg as [[:File:nh-timeline.svg]] and ')
    fp.write('imagemap.txt as this template.\n')
    fp.write('</noinclude>\n')
    fp.write('<div class="thumb tright hoverlist">')
    fp.write('<imagemap>\n')
    fp.write('Image:nh-timeline.svg|Timeline of NetHack\n')
    for y, text in imagemap:
        fp.write('rect %d %d %d %d [[%s]]\n' % (text_left, y-13, width-10, y, text))
    fp.write('</imagemap>\n')
    fp.write('</div>\n')