<SYSTEM_TASK:>
Given the following text description, write Python code to implement the functionality described below step by step
<END_TASK>
<USER_TASK:>
Description:
 Build names mapping
To make it a little easier to check that I'm using the correct guids, construct a mapping from names back to guid.
Note
Step1:  Pikov Classes
These classes are the core resources used in defining a "Pikov" file.
Note
Step2:  Gamekitty
Create instances of the Pikov classes to define a concrete Pikov graph, based on my "gamekitty" animations.
Load the spritesheet
In the previous notebook, we chopped the spritesheet into bitmaps. Find those and save them to an array so that they can be indexed as they were in the original PICO-8 gamekitty doodle.
Step3:  Create frames for each "clip"
Each animation is defined in terms of sprite numbers. Sometimes a clip should loop, but sometimes it's only used as a transition between looping clips.
<ASSISTANT_TASK:>
Python Code:
names = {}
for node in graph:
    for edge in node:
        if edge.guid == "169a81aefca74e92b45e3fa03c7021df":
            value = node[edge].value
            if value in names:
                raise ValueError('name: "{}" defined twice'.format(value))
            names[value] = node
     
names["ctor"]
def name_to_guid(name):
    if name not in names:
        return None
    node = names[name]
    if not hasattr(node, "guid"):
        return None
    return node.guid
Explanation: Build names mapping
To make it a little easier to check that I'm using the correct guids, construct a mapping from names back to guid.
Note: this adds a constraint that no two nodes have the same name, which should not be enforced for general semantic graphs.
End of explanation
from pikov.sprite import Bitmap, Clip, Frame, FrameList, Resource, Transition
Explanation: Pikov Classes
These classes are the core resources used in defining a "Pikov" file.
Note: ideally these classes could be derived from the graph itself, but I don't (yet) encode type or field information in the pikov.json semantic graph.
End of explanation
resource = Resource(graph, guid=name_to_guid("spritesheet"))
spritesheet = []
for row in range(16):
    for column in range(16):
        sprite_number = row * 16 + column
        bitmap_name = "bitmap[{}]".format(sprite_number)
        bitmap = Bitmap(graph, guid=name_to_guid(bitmap_name))
        spritesheet.append(bitmap)
Explanation: Gamekitty
Create instances of the Pikov classes to define a concrete Pikov graph, based on my "gamekitty" animations.
Load the spritesheet
In the previous notebook, we chopped the spritesheet into bitmaps. Find those and save them to an array so that they can be indexed as they were in the original PICO-8 gamekitty doodle.
End of explanation
def find_nodes(graph, ctor, cls):
    nodes = set()
    # TODO: With graph formats that have indexes, there should be a faster way.
    for node in graph:
        if node[names["ctor"]] == ctor:
            node = cls(graph, guid=node.guid)
            nodes.add(node)
    return nodes
def find_frames(graph):
    return find_nodes(graph, names["frame"], Frame)
def find_transitions(graph):
    return find_nodes(graph, names["transition"], Transition)
def find_absorbing_frames(graph):
    transitions = find_transitions(graph)
    target_frames = set()
    source_frames = set()
    for transition in transitions:
        target_frames.add(transition.target.guid)
        source_frames.add(transition.source.guid)
    return target_frames - source_frames  # In but not out. Dead end!
MICROS_12_FPS = int(1e6 / 12)  # 12 frames per second
MICROS_24_FPS = int(1e6 / 24)
def connect_frames(graph, transition_name, source, target):
    transition = Transition(graph, guid=name_to_guid(transition_name))
    transition.name = transition_name
    transition.source = source
    transition.target = target
    return transition
sit = Clip(graph, guid=name_to_guid("clip[sit]"))
sit
sit_to_stand = Clip(graph, guid=name_to_guid("clip[sit_to_stand]"))
sit_to_stand
stand_waggle= Clip(graph, guid=name_to_guid("clip[stand_waggle]"))
stand_waggle
connect_frames(
    graph,
    "transitions[sit_to_stand, stand_waggle]",
    sit_to_stand[-1],
    stand_waggle[0])
stand_to_sit = Clip(graph, guid=name_to_guid("clip[stand_to_sit]"))
stand_to_sit
connect_frames(
    graph,
    "transitions[stand_waggle, stand_to_sit]",
    stand_waggle[-1],
    stand_to_sit[0])
connect_frames(
    graph,
    "transitions[stand_to_sit, sit]",
    stand_to_sit[-1],
    sit[0])
sit_paw = Clip(graph, guid=name_to_guid("clip[sit_paw]"))
sit_paw
connect_frames(
    graph,
    "transitions[sit_paw, sit]",
    sit_paw[-1],
    sit[0])
connect_frames(
    graph,
    "transitions[sit, sit_paw]",
    sit[-1],
    sit_paw[0])
sit_to_crouch = Clip(graph, guid=name_to_guid("clip[sit_to_crouch]"))
connect_frames(
    graph,
    "transitions[sit, sit_to_crouch]",
    sit[-1],
    sit_to_crouch[0])
crouch = Clip(graph, guid=name_to_guid("clip[crouch]"))
connect_frames(
    graph,
    "transitions[sit_to_crouch, crouch]",
    sit_to_crouch[-1],
    crouch[0])
crouch_to_sit = Clip(graph, guid=name_to_guid("clip[crouch_to_sit]"))
connect_frames(
    graph,
    "transitions[crouch_to_sit, sit]",
    crouch[-1],
    crouch_to_sit[0])
connect_frames(
    graph,
    "transitions[crouch_to_sit, sit]",
    crouch_to_sit[-1],
    sit[0])
find_absorbing_frames(graph)
graph.save()
Explanation: Create frames for each "clip"
Each animation is defined in terms of sprite numbers. Sometimes a clip should loop, but sometimes it's only used as a transition between looping clips.
End of explanation
<END_TASK>