Created
April 16, 2018 10:47
-
-
Save lispandfound/afa89003e63e55cfc6d3c9857b48a42c to your computer and use it in GitHub Desktop.
Towers of Hanoi Cairo Background
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import itertools | |
| import cairo | |
| def interpret_state(disks, counter): | |
| ''' Return the position of the towers of hanoi after `counter` moves. | |
| Efficient algorithm, runs in O(k) time, k being the number of disks. | |
| This method abuses the binary relationship between Towers of Hanoi and moves. | |
| See https://en.wikipedia.org/wiki/Tower_of_Hanoi#Binary_solution | |
| for more info. Although their explanation of how the disks move is | |
| rather counter intuitive, I recommend running though a few | |
| solutions yourself to try and observe these patterns. | |
| Args: | |
| disks (int): The number of tower of hanoi disks | |
| Returns: | |
| list of int: The position of the disks (on peg 0..2) | |
| Examples: | |
| >>> interpret_state(3, 7) | |
| [2, 2, 2] | |
| >>> interpret_state(3, 4) | |
| [1, 1, 2] ''' | |
| # The general rule to be observed (took some time to figure out) is | |
| # that a disk k has moved (m + 2**k) // (2**(k + 1)) times after m moves. | |
| move_map = [(counter + 2**i) // (2**(i + 1)) for i in range(disks)] | |
| # We then map the number of times the disks have moved to their | |
| # current positions. | |
| # The pattern to observe (again this takes some time to work out) | |
| # is that for an even number of disks, the even disks move left | |
| # and the odd disks move right. | |
| # The opposite is true for odd disk counts, the odd disks move | |
| # left and the even disks move right. | |
| # m moves right corresponds to m % 3, whilst m moves left | |
| # corresponds to -m % 3. | |
| peg_positions = [ | |
| -move_map[k] % 3 if (k % 2 != 0 and disks % 2 == 0) | |
| or (k % 2 == 0 and disks % 2 != 0) else move_map[k] % 3 | |
| for k in range(disks) | |
| ] | |
| return peg_positions | |
| def draw_hanoi(width, | |
| height, | |
| state, | |
| disk_widths, | |
| peg_width, | |
| peg_height=None, | |
| disk_height=None, | |
| background=(1, 1, 1), | |
| peg=(0, 0, 0), | |
| disk_colour=(0.207, 0.361, 0.231)): | |
| ''' Draw a tower of state of the tower of hanoi on a canvas. | |
| Draw a tower of state of the tower of hanoi on a canvas of a | |
| specified width, and height, and customizable peg/disk | |
| heights/widths/colours. | |
| Args: | |
| width (int): The width of the canvas. | |
| height (int): The height of the canvas. | |
| state (list of int): The state (current positions) of the disks in the puzzle. | |
| disk_widths (list of int): The widths of the individual disks. | |
| peg_width (int): The widths of the pegs. | |
| peg_height (int, optional): The height of the pegs. Defaults to 2/3rds of the height of the canvas. | |
| disk_height (int, optional): The height of the disks. Defaults to `peg_height // len(state)` | |
| background ((float, float, float), optional): The colour of the background. Defaults to #FFFFFF | |
| peg ((float, float, float), optional): The colour of the pegs. Defaults to #000000. | |
| disk_colour ((float, float, float), optional): The colour of the disks. Defaults to green. | |
| Returns: | |
| cairo.Surface: A cairo surface representing the state of the | |
| towers of hanoi puzzle. | |
| ''' | |
| peg_height = peg_height or int(height * 0.67) | |
| disk_height = disk_height or peg_height // len(state) | |
| surface = cairo.ImageSurface(cairo.FORMAT_RGB24, width, height) | |
| context = cairo.Context(surface) | |
| context.set_source_rgb(*background) | |
| context.rectangle(0, 0, width, height) | |
| context.fill() | |
| # Split the width into 5 roughly equal parts, the first and | |
| # last used for padding, the middle three as disk positions. | |
| positions = [i * (width // 4) + min(i, width % 4) for i in range(4)] | |
| for position in itertools.islice(positions, 1, len(positions)): | |
| peg_rect = cairo.Rectangle(position - peg_width // 2, | |
| height - peg_height, peg_width, peg_height) | |
| context.set_source_rgb(*peg) | |
| context.rectangle(*peg_rect) | |
| context.fill() | |
| # Now we draw the disks | |
| # Disks are drawn in reverse order (largest to smallest) and | |
| # on top of each other. | |
| counts = [1, 1, 1] | |
| for disk, peg in enumerate(reversed(state)): | |
| disk_rect = cairo.Rectangle( | |
| positions[peg + 1] - disk_widths[disk] // 2, | |
| height - counts[peg] * disk_height, disk_widths[disk], disk_height) | |
| counts[peg] += 1 | |
| context.set_source_rgb(*disk_colour) | |
| context.rectangle(*disk_rect) | |
| context.fill() | |
| return surface | |
| def rgb_to_cairo(rgb_string, prefixed_hash=True): | |
| ''' Convert a RGB hex colour to a cairo colour tuple. | |
| Args: | |
| rgb_string (string): An RGB hex colour, e.g #FFFFFF | |
| prefixed_hash (bool): If True string will be parsed from an | |
| offset of 1 (thus skipping '#'). | |
| Returns: | |
| (float, float, float): A cairo compatible colour tuple, e.g (1.0, 1.0, 1.0) | |
| Examples: | |
| >>> rgb_to_cairo('#FFFFFF') | |
| (1.0, 1.0, 1.0) | |
| >>> rgb_to_cairo('000000', prefixed_hash=False) | |
| (0.0, 0.0, 0.0) ''' | |
| start = 1 if prefixed_hash else 0 | |
| red = int(rgb_string[start:start + 2], 16) | |
| blue = int(rgb_string[start + 2:start + 4], 16) | |
| green = int(rgb_string[start + 4:start + 6], 16) | |
| return (red / 255, blue / 255, green / 255) | |
| WIDTH = 1920 | |
| HEIGHT = 1080 | |
| PEG_COLOUR = rgb_to_cairo('#272d2d') | |
| BACKGROUND_COLOUR = rgb_to_cairo('#f8fff7') | |
| DISK_COLOUR = rgb_to_cairo('#8d99ae') | |
| PEG_HEIGHT = 360 | |
| PEG_WIDTH = 40 | |
| DISKS = 8 | |
| DISK_WIDTHS = list(range(360, 40, -40)) | |
| DISK_HEIGHT = 40 | |
| BACKGROUND_LOCATION = '/home/jake/.config/i3/desktop.png' | |
| COUNTER_LOCATION = '/home/jake/.config/i3/counter' | |
| if __name__ == '__main__': | |
| with open(COUNTER_LOCATION, 'r+') as f: | |
| counter = int(f.readline()) | |
| state = interpret_state(DISKS, counter) | |
| surface = draw_hanoi(WIDTH, HEIGHT, state, DISK_WIDTHS, PEG_WIDTH, | |
| PEG_HEIGHT, DISK_HEIGHT, BACKGROUND_COLOUR, | |
| PEG_COLOUR, DISK_COLOUR) | |
| surface.write_to_png(BACKGROUND_LOCATION) | |
| f.seek(0) | |
| f.write(str(counter + 1)) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment