#!/usr/bin/env python3

"""
A curses-based version of Conway's Game of Life.

An empty board will be displayed, and the following commands are available:
 E : Erase the board
 R : Fill the board randomly
 S : Step for a single generation
 C : Update continuously until a key is struck
 Q : Quit
 Cursor keys :  Move the cursor around the board
 Space or Enter : Toggle the contents of the cursor's position

Contributed by Andrew Kuchling, Mouse support and color by Dafydd Crosby.
"""

import curses
import random


class LifeBoard:
    """Encapsulates a Life board

    Attributes:
    X,Y : horizontal and vertical size of the board
    state : dictionary mapping (x,y) to 0 or 1

    Methods:
    display(update_board) -- If update_board is true, compute the
                             next generation.  Then display the state
                             of the board and refresh the screen.
    erase() -- clear the entire board
    make_random() -- fill the board randomly
    set(y,x) -- set the given cell to Live; doesn't refresh the screen
    toggle(y,x) -- change the given cell from live to dead, or vice
                   versa, and refresh the screen display

    """
    def __init__(self, scr, char=ord('*')):
        """Create a new LifeBoard instance.

        scr -- curses screen object to use for display
        char -- character used to render live cells (default: '*')
        """
        self.state = {}
        self.scr = scr
        Y, X = self.scr.getmaxyx()
        self.X, self.Y = X - 2, Y - 2 - 1
        self.char = char
        self.scr.clear()

        # Draw a border around the board
        border_line = '+' + (self.X * '-') + '+'
        self.scr.addstr(0, 0, border_line)
        self.scr.addstr(self.Y + 1, 0, border_line)
        for y in range(0, self.Y):
            self.scr.addstr(1 + y, 0, '|')
            self.scr.addstr(1 + y, self.X + 1, '|')
        self.scr.refresh()

    def set(self, y, x):
        """Set a cell to the live state"""
        if x < 0 or self.X <= x or y < 0 or self.Y <= y:
            raise ValueError("Coordinates out of range %i,%i" % (y, x))
        self.state[x, y] = 1

    def toggle(self, y, x):
        """Toggle a cell's state between live and dead"""
        if x < 0 or self.X <= x or y < 0 or self.Y <= y:
            raise ValueError("Coordinates out of range %i,%i" % (y, x))
        if (x, y) in self.state:
            del self.state[x, y]
            self.scr.addch(y + 1, x + 1, ' ')
        else:
            self.state[x, y] = 1
            if curses.has_colors():
                # Let's pick a random color!
                self.scr.attrset(curses.color_pair(random.randrange(1, 7)))
            self.scr.addch(y + 1, x + 1, self.char)
            self.scr.attrset(0)
        self.scr.refresh()

    def erase(self):
        """Clear the entire board and update the board display"""
        self.state = {}
        self.display(update_board=False)

    def display(self, update_board=True):
        """Display the whole board, optionally computing one generation"""
        M, N = self.X, self.Y
        if not update_board:
            for i in range(0, M):
                for j in range(0, N):
                    if (i, j) in self.state:
                        self.scr.addch(j + 1, i + 1, self.char)
                    else:
                        self.scr.addch(j + 1, i + 1, ' ')
            self.scr.refresh()
            return

        d = {}
        self.boring = 1
        for i in range(0, M):
            L = range(max(0, i - 1), min(M, i + 2))
            for j in range(0, N):
                s = 0
                live = (i, j) in self.state
                for k in range(max(0, j - 1), min(N, j + 2)):
                    for l in L:
                        if (l, k) in self.state:
                            s += 1
                s -= live
                if s == 3:
                    # Birth
                    d[i, j] = 1
                    if curses.has_colors():
                        # Let's pick a random color!
                        self.scr.attrset(curses.color_pair(
                            random.randrange(1, 7)))
                    self.scr.addch(j + 1, i + 1, self.char)
                    self.scr.attrset(0)
                    if not live:
                        self.boring = 0
                elif s == 2 and live:
                    # Survival
                    d[i, j] = 1
                elif live:
                    # Death
                    self.scr.addch(j + 1, i + 1, ' ')
                    self.boring = 0
        self.state = d
        self.scr.refresh()

    def make_random(self):
        "Fill the board with a random pattern"
        self.state = {}
        for i in range(0, self.X):
            for j in range(0, self.Y):
                if random.random() > 0.5:
                    self.set(j, i)


def erase_menu(stdscr, menu_y):
    "Clear the space where the menu resides"
    stdscr.move(menu_y, 0)
    stdscr.clrtoeol()
    stdscr.move(menu_y + 1, 0)
    stdscr.clrtoeol()


def display_menu(stdscr, menu_y):
    "Display the menu of possible keystroke commands"
    erase_menu(stdscr, menu_y)

    # If color, then light the menu up :-)
    if curses.has_colors():
        stdscr.attrset(curses.color_pair(1))
    stdscr.addstr(menu_y, 4,
        'Use the cursor keys to move, and space or Enter to toggle a cell.')
    stdscr.addstr(menu_y + 1, 4,
        'E)rase the board, R)andom fill, S)tep once or C)ontinuously, Q)uit')
    stdscr.attrset(0)


def keyloop(stdscr):
    # Clear the screen and display the menu of keys
    stdscr.clear()
    stdscr_y, stdscr_x = stdscr.getmaxyx()
    menu_y = (stdscr_y - 3) - 1
    display_menu(stdscr, menu_y)

    # If color, then initialize the color pairs
    if curses.has_colors():
        curses.init_pair(1, curses.COLOR_BLUE, 0)
        curses.init_pair(2, curses.COLOR_CYAN, 0)
        curses.init_pair(3, curses.COLOR_GREEN, 0)
        curses.init_pair(4, curses.COLOR_MAGENTA, 0)
        curses.init_pair(5, curses.COLOR_RED, 0)
        curses.init_pair(6, curses.COLOR_YELLOW, 0)
        curses.init_pair(7, curses.COLOR_WHITE, 0)

    # Set up the mask to listen for mouse events
    curses.mousemask(curses.BUTTON1_CLICKED)

    # Allocate a subwindow for the Life board and create the board object
    subwin = stdscr.subwin(stdscr_y - 3, stdscr_x, 0, 0)
    board = LifeBoard(subwin, char=ord('*'))
    board.display(update_board=False)

    # xpos, ypos are the cursor's position
    xpos, ypos = board.X // 2, board.Y // 2

    # Main loop:
    while True:
        stdscr.move(1 + ypos, 1 + xpos)   # Move the cursor
        c = stdscr.getch()                # Get a keystroke
        if 0 < c < 256:
            c = chr(c)
            if c in ' \n':
                board.toggle(ypos, xpos)
            elif c in 'Cc':
                erase_menu(stdscr, menu_y)
                stdscr.addstr(menu_y, 6, ' Hit any key to stop continuously '
                              'updating the screen.')
                stdscr.refresh()
                # Activate nodelay mode; getch() will return -1
                # if no keystroke is available, instead of waiting.
                stdscr.nodelay(1)
                while True:
                    c = stdscr.getch()
                    if c != -1:
                        break
                    stdscr.addstr(0, 0, '/')
                    stdscr.refresh()
                    board.display()
                    stdscr.addstr(0, 0, '+')
                    stdscr.refresh()

                stdscr.nodelay(0)       # Disable nodelay mode
                display_menu(stdscr, menu_y)

            elif c in 'Ee':
                board.erase()
            elif c in 'Qq':
                break
            elif c in 'Rr':
                board.make_random()
                board.display(update_board=False)
            elif c in 'Ss':
                board.display()
            else:
                # Ignore incorrect keys
                pass
        elif c == curses.KEY_UP and ypos > 0:
            ypos -= 1
        elif c == curses.KEY_DOWN and ypos + 1 < board.Y:
            ypos += 1
        elif c == curses.KEY_LEFT and xpos > 0:
            xpos -= 1
        elif c == curses.KEY_RIGHT and xpos + 1 < board.X:
            xpos += 1
        elif c == curses.KEY_MOUSE:
            mouse_id, mouse_x, mouse_y, mouse_z, button_state = curses.getmouse()
            if (mouse_x > 0 and mouse_x < board.X + 1 and
                mouse_y > 0 and mouse_y < board.Y + 1):
                xpos = mouse_x - 1
                ypos = mouse_y - 1
                board.toggle(ypos, xpos)
            else:
                # They've clicked outside the board
                curses.flash()
        else:
            # Ignore incorrect keys
            pass


def main(stdscr):
    keyloop(stdscr)                 # Enter the main loop

if __name__ == '__main__':
    curses.wrapper(main)

# SIG # Begin Windows Authenticode signature block
# MIIoPwYJKoZIhvcNAQcCoIIoMDCCKCwCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQse8BENmB6EqSR2hd
# JGAGggIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCDFA3PL7Gt5T+1p
# uMP0kCsQ09CFMr63/JYegzCGQDFU56CCDYswggYJMIID8aADAgECAhMzAAAEbVXA
# 1DsonAveAAAAAARtMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMjUwNTE1MTg0ODMxWhcNMjYwNzA3MTg0ODMxWjCBiDEL
# MAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1v
# bmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWlj
# cm9zb2Z0IDNyZCBQYXJ0eSBBcHBsaWNhdGlvbiBDb21wb25lbnQwggEiMA0GCSqG
# SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDIEe35V5JshjrrrauRQvQ/Z9R1AC3h3jun
# AD5tsTvSTnKETv3eNVBVA4JQ1LCLx+wb32BjBysUhodwDDdP8ZCkzGUJjLB0QR1b
# ohBMCzYmd1yprxvSCgPu6RdoAu7+XgNR9WZpzM7fqhtAl5I4jKoZIr1d7N6pODOf
# KmGu5wSRhWNmzxkrDo1NCdZraD6rSeh/Ga282umIZ4jJWz8e6xe40pkOcL7wDFNj
# jqDED1rg8v93It3Ubz8Rr0eXD0TcJ3I9Z0E3V2uYdvKkUTvFudo1+LntLL4L4XVp
# QYXzwZywfe1uZkGAqaF8uWLBI+DkHhIi+gd+FgoIhkBDefYMgdSNAgMBAAGjggFz
# MIIBbzAfBgNVHSUEGDAWBgorBgEEAYI3TBEBBggrBgEFBQcDAzAdBgNVHQ4EFgQU
# DD/V7OROmk7PmdGPwd/xd17JCHcwRQYDVR0RBD4wPKQ6MDgxHjAcBgNVBAsTFU1p
# Y3Jvc29mdCBDb3Jwb3JhdGlvbjEWMBQGA1UEBRMNMjMxNTIyKzUwNTExODAfBgNV
# HSMEGDAWgBRIbmTlUAXTgqoXNzcitW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNo
# dHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0Ey
# MDExXzIwMTEtMDctMDguY3JsMGEGCCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZF
# aHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQ
# Q0EyMDExXzIwMTEtMDctMDguY3J0MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQEL
# BQADggIBAG3wJoQsAIMcLpk2KV7LTKcezSxeADN5Qa7DZKqL4m6rgeojav64PEC/
# 7KC+X2I0MxRNkYKn3ViW2knfu6F615kJmO/BN0kjN/VYbvuUQ11e2yPg2ncQ9ybX
# O4aEV+69wlpNNSNdPHkPVJBgwpmOkShStgAMdtGtL8zyyFKVdU5P0v+HEOBppb+T
# 05XxP1S3IuvnTZHWyRnCdSeli6ZLe9muaUHhQgA2YDBfqgC9RmZRR1+gqgtehynW
# 44Y1hrtCNo5Tbuq9pQfvuzTbhLXzkTVGFRJbyPZS72IjIcDh1H12pj5JAEuFOJNB
# 32jkzCLbLA6kUq7NJKL/BqkHKnPW85Hh2RGjSVVV+4CzeB/VdtNkywo2KDracBT+
# 0oOTfQMpXEbJzQwsW7KTEO62NUUbtZNireEeJiTfb2fxFMo7Gawcjp5mlVzcxM2U
# f9yV8YE6WadmkfWOO+SLYuccSHPsMPmNIEi5m3BaBU6ycmU5DpTDg0k61wZww8bI
# V8zOKGmq87PSlv5Jsb/5yEZ7yMMDRWeAgWLgw0hslAYgsr9I6j6n5UpcY+aAXUre
# TgEeTcusVv4N5+hh3cv103GhliSKe+iAk5grgw3v3FKJ05NmkFkLwrjDWV8sddU4
# Fvo5mqV23YLyMZw9n6hj5+v3UHEMI4T0MAqQnJKz2gdUcMRQBCu3MIIHejCCBWKg
# AwIBAgIKYQ6Q0gAAAAAAAzANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMx
# EzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoT
# FU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3Qg
# Q2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYw
# NzA4MjEwOTA5WjB+MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQ
# MA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9u
# MSgwJgYDVQQDEx9NaWNyb3NvZnQgQ29kZSBTaWduaW5nIFBDQSAyMDExMIICIjAN
# BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGf
# Qhsqa+laUKq4BjgaBEm6f8MMHt03a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRg
# JGyvnkmc6Whe0t+bU7IKLMOv2akrrnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NE
# t13YxC4Ddato88tt8zpcoRb0RrrgOGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnn
# Db6gE3e+lD3v++MrWhAfTVYoonpy4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+E
# GvKhL1nkkDstrjNYxbc+/jLTswM9sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+t
# GSOEy/S6A4aN91/w0FK/jJSHvMAhdCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxh
# H2rhKEmdX4jiJV3TIUs+UsS1Vz8kA/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AV
# s70b1FVL5zmhD+kjSbwYuER8ReTBw3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f
# 7Fufr/zdsGbiwZeBe+3W7UvnSSmnEyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3D
# KI8sj0A3T8HhhUSJxAlMxdSlQy90lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9Jaw
# vEagbJjS4NaIjAsCAwEAAaOCAe0wggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1Ud
# DgQWBBRIbmTlUAXTgqoXNzcitW2oynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBi
# AEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRy
# LToCMZBDuRQFTuHqp8cx0SOJNDBaBgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3Js
# Lm1pY3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDEx
# XzIwMTFfMDNfMjIuY3JsMF4GCCsGAQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0
# cDovL3d3dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDEx
# XzIwMTFfMDNfMjIuY3J0MIGfBgNVHSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/
# BggrBgEFBQcCARYzaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2Nz
# L3ByaW1hcnljcHMuaHRtMEAGCCsGAQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAA
# bwBsAGkAYwB5AF8AcwB0AGEAdABlAG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUA
# A4ICAQBn8oalmOBUeRou09h0ZyKbC5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+
# vj/oCso7v0epo/Np22O/IjWll11lhJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4H
# Limb5j0bpdS1HXeUOeLpZMlEPXh6I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6
# aC6VoCo/KmtYSWMfCWluWpiW5IP0wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiX
# mE0OPQvyCInWH8MyGOLwxS3OW560STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn
# +N4sOiBpmLJZiWhub6e3dMNABQamASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAq
# ZaPDXVJihsMdYzaXht/a8/jyFqGaJ+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5h
# YbXw3MYbBL7fQccOKO7eZS/sl/ahXJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/
# RXceNcbSoqKfenoi+kiVH6v7RyOA9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXm
# r/r8i+sLgOppO6/8MO0ETI7f33VtY5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMyk
# XcGhiJtXcVZOSEXAQsmbdlsKgEhr/Xmfwb1tbWrJUnMTDXpQzTGCGgowghoGAgEB
# MIGVMH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQH
# EwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNV
# BAMTH01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTECEzMAAARtVcDUOyic
# C94AAAAABG0wDQYJYIZIAWUDBAIBBQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQB
# gjcCAQQwHAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkE
# MSIEIPkhfUho5D7e8DhzQC0gbjEU1z8x9xSqGJMwfbC7y3QKMEIGCisGAQQBgjcC
# AQwxNDAyoBSAEgBNAGkAYwByAG8AcwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jv
# c29mdC5jb20wDQYJKoZIhvcNAQEBBQAEggEAGUC0h7VmJwdFhdBOppbmW+SjYRvd
# jd9t0kc7sUZAzAThojEBzg7yn+2u2gvEM391ykTMzJqyLehRd+KzbdrQDm36pn5E
# OPODIkNL6vdigEwulAUNEhQ3F67piupnnFBalWFp1Cujh/gS6bOWsl2WKMggD7pa
# C4EvZcztKkmRSY1uWD+vUSYQulG66tweeAF71mLm+ji1svLX8vaqlR2Q9Jtn0mUA
# RutycguLVWG2A7AEB+5NtC3ordp50ZuWH6He1GWg5RIiI6oEEdL7ax1yKm4OoEbI
# cosR8ey6iqpkJYZRyrtcbPeE5ESmqXBvycI2iIdCGaTujcyk5T2H2A8dSaGCF5Qw
# gheQBgorBgEEAYI3AwMBMYIXgDCCF3wGCSqGSIb3DQEHAqCCF20wghdpAgEDMQ8w
# DQYJYIZIAWUDBAIBBQAwggFSBgsqhkiG9w0BCRABBKCCAUEEggE9MIIBOQIBAQYK
# KwYBBAGEWQoDATAxMA0GCWCGSAFlAwQCAQUABCBcJbv2HBcny3Y2x9ZyDLOOqKHA
# X7HWQ2Nh46WYApiNgwIGaO/jiX2lGBMyMDI1MTAxNjE2NTAwMC41MjlaMASAAgH0
# oIHRpIHOMIHLMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4G
# A1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUw
# IwYDVQQLExxNaWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25zMScwJQYDVQQLEx5u
# U2hpZWxkIFRTUyBFU046QTkzNS0wM0UwLUQ5NDcxJTAjBgNVBAMTHE1pY3Jvc29m
# dCBUaW1lLVN0YW1wIFNlcnZpY2WgghHqMIIHIDCCBQigAwIBAgITMwAAAgy5ZOM1
# nOz0rgABAAACDDANBgkqhkiG9w0BAQsFADB8MQswCQYDVQQGEwJVUzETMBEGA1UE
# CBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9z
# b2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQ
# Q0EgMjAxMDAeFw0yNTAxMzAxOTQzMDBaFw0yNjA0MjIxOTQzMDBaMIHLMQswCQYD
# VQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEe
# MBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3Nv
# ZnQgQW1lcmljYSBPcGVyYXRpb25zMScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046
# QTkzNS0wM0UwLUQ5NDcxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNl
# cnZpY2UwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKAVYmPeRtga/U
# 6jzqyqLD0MAool23gcBN58+Z/XskYwNJsZ+O+wVyQYl8dPTK1/BC2xAic1m+Jvck
# qjVaQ32KmURsEZotirQY4PKVW+eXwRt3r6szgLuic6qoHlbXox/l0HJtgURkzDXW
# MkKmGSL7z8/crqcvmYqv8t/slAF4J+mpzb9tMFVmjwKXONVdRwg9Q3WaPZBC7Wvo
# i7PRIN2jgjSBnHYyAZSlstKNrpYb6+Gu6oSFkQzGpR65+QNDdkP4ufOf4PbOg3fb
# 4uGPjI8EPKlpwMwai1kQyX+fgcgCoV9J+o8MYYCZUet3kzhhwRzqh6LMeDjaXLP7
# 01SXXiXc2ZHzuDHbS/sZtJ3627cVpClXEIUvg2xpr0rPlItHwtjo1PwMCpXYqnYK
# vX8aJ8nawT9W8FUuuyZPG1852+q4jkVleKL7x+7el8ETehbdkwdhAXyXimaEzWet
# NNSmG/KfHAp9czwsL1vKr4Rgn+pIIkZHuomdf5e481K+xIWhLCPdpuV87EqGOK/j
# bhOnZEqwdvA0AlMaLfsmCemZmupejaYuEk05/6cCUxgF4zCnkJeYdMAP+9Z4kVh7
# tzRFsw/lZSl2D7EhIA6Knj6RffH2k7YtSGSv86CShzfiXaz9y6sTu8SGqF6ObL/e
# u/DkivyVoCfUXWLjiSJsrS63D0EHHQIDAQABo4IBSTCCAUUwHQYDVR0OBBYEFHUO
# RSH/sB/rQ/beD0l5VxQ706GIMB8GA1UdIwQYMBaAFJ+nFV0AXmJdg/Tl0mWnG1M1
# GelyMF8GA1UdHwRYMFYwVKBSoFCGTmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9w
# a2lvcHMvY3JsL01pY3Jvc29mdCUyMFRpbWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEp
# LmNybDBsBggrBgEFBQcBAQRgMF4wXAYIKwYBBQUHMAKGUGh0dHA6Ly93d3cubWlj
# cm9zb2Z0LmNvbS9wa2lvcHMvY2VydHMvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUy
# MFBDQSUyMDIwMTAoMSkuY3J0MAwGA1UdEwEB/wQCMAAwFgYDVR0lAQH/BAwwCgYI
# KwYBBQUHAwgwDgYDVR0PAQH/BAQDAgeAMA0GCSqGSIb3DQEBCwUAA4ICAQDZMPr4
# gVmwwf4GMB5ZfHSr34uhug6yzu4HUT+JWMZqz9uhLZBoX5CPjdKJzwAVvYoNuLmS
# 0+9lA5S74rvKqd/u9vp88VGk6U7gMceatdqpKlbVRdn2ZfrMcpI4zOc6BtuYrzJV
# 4cEs1YmX95uiAxaED34w02BnfuPZXA0edsDBbd4ixFU8X/1J0DfIUk1YFYPOrmwm
# I2k16u6TcKO0YpRlwTdCq9vO0eEIER1SLmQNBzX9h2ccCvtgekOaBoIQ3ZRai8Ds
# 1f+wcKCPzD4qDX3xNgvLFiKoA6ZSG9S/yOrGaiSGIeDy5N9VQuqTNjryuAzjvf5W
# 8AQp31hV1GbUDOkbUdd+zkJWKX4FmzeeN52EEbykoWcJ5V9M4DPGN5xpFqXy9aO0
# +dR0UUYWuqeLhDyRnVeZcTEu0xgmo+pQHauFVASsVORMp8TF8dpesd+tqkkQ8VNv
# I20oOfnTfL+7ZgUMf7qNV0ll0Wo5nlr1CJva1bfk2Hc5BY1M9sd3blBkezyvJPn4
# j0bfOOrCYTwYsNsjiRl/WW18NOpiwqciwFlUNqtWCRMzC9r84YaUMQ82Bywk48d4
# uBon5ZA8pXXS7jwJTjJj5USeRl9vjT98PDZyCFO2eFSOFdDdf6WBo/WZUA2hGZ0q
# +J7j140fbXCfOUIm0j23HaAV0ckDS/nmC/oF1jCCB3EwggVZoAMCAQICEzMAAAAV
# xedrngKbSZkAAAAAABUwDQYJKoZIhvcNAQELBQAwgYgxCzAJBgNVBAYTAlVTMRMw
# EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN
# aWNyb3NvZnQgQ29ycG9yYXRpb24xMjAwBgNVBAMTKU1pY3Jvc29mdCBSb290IENl
# cnRpZmljYXRlIEF1dGhvcml0eSAyMDEwMB4XDTIxMDkzMDE4MjIyNVoXDTMwMDkz
# MDE4MzIyNVowfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAO
# BgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEm
# MCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwggIiMA0GCSqG
# SIb3DQEBAQUAA4ICDwAwggIKAoICAQDk4aZM57RyIQt5osvXJHm9DtWC0/3unAcH
# 0qlsTnXIyjVX9gF/bErg4r25PhdgM/9cT8dm95VTcVrifkpa/rg2Z4VGIwy1jRPP
# dzLAEBjoYH1qUoNEt6aORmsHFPPFdvWGUNzBRMhxXFExN6AKOG6N7dcP2CZTfDlh
# AnrEqv1yaa8dq6z2Nr41JmTamDu6GnszrYBbfowQHJ1S/rboYiXcag/PXfT+jlPP
# 1uyFVk3v3byNpOORj7I5LFGc6XBpDco2LXCOMcg1KL3jtIckw+DJj361VI/c+gVV
# mG1oO5pGve2krnopN6zL64NF50ZuyjLVwIYwXE8s4mKyzbnijYjklqwBSru+cakX
# W2dg3viSkR4dPf0gz3N9QZpGdc3EXzTdEonW/aUgfX782Z5F37ZyL9t9X4C626p+
# Nuw2TPYrbqgSUei/BQOj0XOmTTd0lBw0gg/wEPK3Rxjtp+iZfD9M269ewvPV2HM9
# Q07BMzlMjgK8QmguEOqEUUbi0b1qGFphAXPKZ6Je1yh2AuIzGHLXpyDwwvoSCtdj
# bwzJNmSLW6CmgyFdXzB0kZSU2LlQ+QuJYfM2BjUYhEfb3BvR/bLUHMVr9lxSUV0S
# 2yW6r1AFemzFER1y7435UsSFF5PAPBXbGjfHCBUYP3irRbb1Hode2o+eFnJpxq57
# t7c+auIurQIDAQABo4IB3TCCAdkwEgYJKwYBBAGCNxUBBAUCAwEAATAjBgkrBgEE
# AYI3FQIEFgQUKqdS/mTEmr6CkTxGNSnPEP8vBO4wHQYDVR0OBBYEFJ+nFV0AXmJd
# g/Tl0mWnG1M1GelyMFwGA1UdIARVMFMwUQYMKwYBBAGCN0yDfQEBMEEwPwYIKwYB
# BQUHAgEWM2h0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvRG9jcy9SZXBv
# c2l0b3J5Lmh0bTATBgNVHSUEDDAKBggrBgEFBQcDCDAZBgkrBgEEAYI3FAIEDB4K
# AFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSME
# GDAWgBTV9lbLj+iiXGJo0T2UkFvXzpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRw
# Oi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJB
# dXRfMjAxMC0wNi0yMy5jcmwwWgYIKwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5o
# dHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8y
# MDEwLTA2LTIzLmNydDANBgkqhkiG9w0BAQsFAAOCAgEAnVV9/Cqt4SwfZwExJFvh
# nnJL/Klv6lwUtj5OR2R4sQaTlz0xM7U518JxNj/aZGx80HU5bbsPMeTCj/ts0aGU
# GCLu6WZnOlNN3Zi6th542DYunKmCVgADsAW+iehp4LoJ7nvfam++Kctu2D9IdQHZ
# GN5tggz1bSNU5HhTdSRXud2f8449xvNo32X2pFaq95W2KFUn0CS9QKC/GbYSEhFd
# PSfgQJY4rPf5KYnDvBewVIVCs/wMnosZiefwC2qBwoEZQhlSdYo2wh3DYXMuLGt7
# bj8sCXgU6ZGyqVvfSaN0DLzskYDSPeZKPmY7T7uG+jIa2Zb0j/aRAfbOxnT99kxy
# bxCrdTDFNLB62FD+CljdQDzHVG2dY3RILLFORy3BFARxv2T5JL5zbcqOCb2zAVdJ
# VGTZc9d/HltEAY5aGZFrDZ+kKNxnGSgkujhLmm77IVRrakURR6nxt67I6IleT53S
# 0Ex2tVdUCbFpAUR+fKFhbHP+CrvsQWY9af3LwUFJfn6Tvsv4O+S3Fb+0zj6lMVGE
# vL8CwYKiexcdFYmNcP7ntdAoGokLjzbaukz5m/8K6TT4JDVnK+ANuOaMmdbhIurw
# J0I9JZTmdHRbatGePu1+oDEzfbzL6Xu/OHBE0ZDxyKs6ijoIYn/ZcGNTTY3ugm2l
# BRDBcQZqELQdVTNYs6FwZvKhggNNMIICNQIBATCB+aGB0aSBzjCByzELMAkGA1UE
# BhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAc
# BgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjElMCMGA1UECxMcTWljcm9zb2Z0
# IEFtZXJpY2EgT3BlcmF0aW9uczEnMCUGA1UECxMeblNoaWVsZCBUU1MgRVNOOkE5
# MzUtMDNFMC1EOTQ3MSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2
# aWNloiMKAQEwBwYFKw4DAhoDFQDvu8hkhEMt5Z8Ldefls7z1LVU8pqCBgzCBgKR+
# MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS
# ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMT
# HU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMA0GCSqGSIb3DQEBCwUAAgUA
# 7JsKrzAiGA8yMDI1MTAxNjA2MDk1MVoYDzIwMjUxMDE3MDYwOTUxWjB0MDoGCisG
# AQQBhFkKBAExLDAqMAoCBQDsmwqvAgEAMAcCAQACAgGKMAcCAQACAhOqMAoCBQDs
# nFwvAgEAMDYGCisGAQQBhFkKBAIxKDAmMAwGCisGAQQBhFkKAwKgCjAIAgEAAgMH
# oSChCjAIAgEAAgMBhqAwDQYJKoZIhvcNAQELBQADggEBAAWh74Wca3x++/3NC7lF
# fSauyX57LX+Fg0+gh2HWDBvdzELZX8XyDoJ/f0IOgLn3uoNo3FxVHDItxqmXtnRK
# cYeJP/KkO2pMVIkru8JBH1N0JBGBN++nfzBNAX9ZBRpkm0O6RbIjSomZ4ytDFDK4
# oSBFsRihmkMyoXiR7aBOlYRH3ZiXx0Aoac/ji6aa6zcSMx/d5RUk5yS3vn2LD8K3
# 5fgMwx1TUmVc5vIujEi5wrTbCMbo2jeRG8D+XI6yWQP+OxvntRD/r7IeeIVTfXxR
# d5IF7O4Hm16F1bow1uLXwWuebwmoqnia31lh5e47PEkITk5gOzMhzMz+Ci9JkY0Y
# fjcxggQNMIIECQIBATCBkzB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGlu
# Z3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBv
# cmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAIT
# MwAAAgy5ZOM1nOz0rgABAAACDDANBglghkgBZQMEAgEFAKCCAUowGgYJKoZIhvcN
# AQkDMQ0GCyqGSIb3DQEJEAEEMC8GCSqGSIb3DQEJBDEiBCAFp4sv8FMHwHyMkTrC
# jydOv99nEoimI6OpClEmJtk/fjCB+gYLKoZIhvcNAQkQAi8xgeowgecwgeQwgb0E
# INUo17cFMZN46MI5NfIAg9Ux5cO5xM9inre5riuOZ8ItMIGYMIGApH4wfDELMAkG
# A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx
# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9z
# b2Z0IFRpbWUtU3RhbXAgUENBIDIwMTACEzMAAAIMuWTjNZzs9K4AAQAAAgwwIgQg
# ppvs/jw2nEYpb870nj/+6c+oRyCBQK8kostz2rWWhtUwDQYJKoZIhvcNAQELBQAE
# ggIApSyihzyHbEeKHTyYLoxe/TuKB4LSe0rXZ+HlBEDDpBZqZsLWENXe9iN/hNoa
# h/ca1J576ijvSGx8L58FwqGrCJ0NkobMs/6WT7Z4kFiBejiiT5aQZ53TeM/TzgXv
# qjIvu1yrdb135txznVcjZ+MQMOWpTK6Oi6ttHxyMMWBIxpMIaGTv0aLflHenkv8N
# jenDkd7fibYGjPWcRkKI7SraGMeOiewNt43xB2mnV1CcDhKaSYGwM2zJbhDANIu0
# 29wdN2pXBsnm8QDTVoJ9PWa9zCuUF1Lov6xyI7NnzJn2kM9Pctcv3ojMioTiGApm
# xQjye3l1PIepabtgRRW1dHZoVvimY50gjHJxLQoNbbq4xwd0+dOeE+1RCZDp9CfC
# m80VqfXUEyCcYe8jFfyU22ieBHI+t74cIcCIMspIh9coG06PmMOVVXDusTlNlQh6
# fvn2ox9Y40vwsmSwrckrycpOKVSLkhqu8nvBJ64g0PemQ/KawjzQJtBzMgvSqJNg
# K2lVYJSCVLRq7kU74TpbWQT+pMG/OZQsPjCfB++6BVhZCNmqXey4HWICTnIpNcO8
# 7YIz6s7NSdl+QF8aqOZmSZ85oDwdqtANKIF4siK4KZUo5TIw3hU0X7J0ObON/VcC
# 6Hp5xxiWerQcDn6z8kj12GQ6j51VYUPjxD7qnzhHTh5z0s4=
# SIG # End Windows Authenticode signature block