def test_state_after_making_choice(): first_node = DialogNode("START", "Hello!", [DialogChoice("Good bye!", "END")], NodeGraphics(image_ids=["::image::"])) second_node = DialogNode("END", "It's over.", [], NodeGraphics(image_ids=["::image::"])) graph = DialogGraph("START", [first_node, second_node]) graph.make_choice(0) assert graph.current_node() == second_node
def test_reject_duplicate_ids(): with pytest.raises(ValueError) as excinfo: DialogGraph("DUPLICATE", [ DialogNode("DUPLICATE", "::text::", [], NodeGraphics(image_ids=["::image::"])), DialogNode("DUPLICATE", "::text::", [], NodeGraphics(image_ids=["::image::"])) ]) assert "Duplicate node ID found: DUPLICATE" in str(excinfo.value)
def test_reject_missing_root(): with pytest.raises(ValueError) as excinfo: DialogGraph("MISSING", [ DialogNode("::id::", "::text::", [], NodeGraphics(image_ids=["::image::"])) ]) assert "No node found with ID: MISSING" in str(excinfo.value)
def test_simple_graphviz(): dialog_graph = DialogGraph( root_node_id="ROOT_NODE", nodes=[ DialogNode("ROOT_NODE", "Start text", [DialogChoice("Choice text", "OTHER_NODE")], NodeGraphics(image_ids=["some_image.png"])), DialogNode("OTHER_NODE", "Other text", [], NodeGraphics(image_ids=["other_image.png"])) ]) graph = generate_graphviz("Some name", dialog_graph) assert graph.name == "Some name" assert set(graph.body) == { '\tROOT_NODE [label="Start text"]', '\tROOT_NODE -> OTHER_NODE [label="Choice text"]', '\tOTHER_NODE [label="Other text"]' }
def test_reject_missing_child(): with pytest.raises(ValueError) as excinfo: DialogGraph("ROOT", [ DialogNode( "ROOT", "::text::", [DialogChoice("::text::", "MISSING_CHILD")], NodeGraphics(image_ids=["::image::"]), ) ]) assert "Dialog choice leading to missing node: MISSING_CHILD" in str( excinfo.value)
def test_initial_state(): node = DialogNode("START", "Hello!", [DialogChoice("Good bye!", "START")], NodeGraphics(image_ids=["::image::"])) graph = DialogGraph("START", [node]) assert graph.current_node() == node
def main(): pygame.init() directory = Path(__file__).parent font = Font(str(directory.joinpath("demo_font.dfont")), 13) screen_size = (500, 500) dialog_margin = 30 dialog_padding = 5 outer_dialog_size = (screen_size[0] - dialog_margin * 2, 330) dialog_size = (outer_dialog_size[0] - dialog_padding * 2, outer_dialog_size[1] - dialog_padding * 2) picture_component_size = (dialog_size[0], 200) screen = pygame.display.set_mode(screen_size) dialog_surface = Surface(dialog_size) dialog_pos = (dialog_margin + dialog_padding, dialog_margin + dialog_padding) dialog_rect = Rect(dialog_pos, dialog_size) images = { "demo1_background": filled_surface(picture_component_size, (0, 50, 35)) } animations = {"demo1_animation": create_animation(picture_component_size)} text_blip_sound = load_sound("blip.ogg") select_blip_sound = load_sound("blip_2.ogg") select_blip_sound_id = "blip" sound_player = SoundPlayer( sounds={select_blip_sound_id: select_blip_sound}, text_blip_sound=text_blip_sound) dialog_closed_node_id = "DIALOG_CLOSED" dialog_graph = DialogGraph( root_node_id="ROOT", nodes=[ DialogNode( node_id="ROOT", text= "This is a minimal demo app. Let this text slowly appear or click any key to skip it. " "Use the UP/DOWN keys to switch between your dialog choices, and click RETURN to go " "for that choice. Or you could just use the mouse!", choices=[ DialogChoice("See this dialog again", "ROOT"), DialogChoice("Close dialog", dialog_closed_node_id) ], graphics=NodeGraphics(animation_id="demo1_animation")), DialogNode(node_id=dialog_closed_node_id, text="", choices=[], graphics=NodeGraphics(animation_id="demo1_animation")), ], title="DEMO 1", background_image_id="demo1_background") pygame.display.set_caption(dialog_graph.title) dialog_component = DialogComponent( surface=dialog_surface, dialog_font=font, choice_font=font, images=images, animations=animations, sound_player=sound_player, dialog_graph=dialog_graph, picture_size=picture_component_size, select_blip_sound_id=select_blip_sound_id) clock = Clock() is_dialog_shown = True while True: elapsed_time = Millis(clock.tick()) for event in pygame.event.get(): if event.type == pygame.QUIT: _exit_game() elif event.type == pygame.KEYDOWN: if is_dialog_shown: dialog_component.skip_text() if event.key == pygame.K_RETURN: dialog_component.commit_selected_choice() if dialog_component.current_node_id( ) == dialog_closed_node_id: is_dialog_shown = False elif event.key == pygame.K_DOWN: dialog_component.move_choice_selection(1) elif event.key == pygame.K_UP: dialog_component.move_choice_selection(-1) elif event.type == pygame.MOUSEBUTTONDOWN: ui_coordinates = translate_screen_to_ui_coordinates( dialog_rect, pygame.mouse.get_pos()) if ui_coordinates: dialog_component.commit_choice_at_position(ui_coordinates) if dialog_component.current_node_id( ) == dialog_closed_node_id: is_dialog_shown = False elif event.type == pygame.MOUSEMOTION: ui_coordinates = translate_screen_to_ui_coordinates( dialog_rect, pygame.mouse.get_pos()) if ui_coordinates: dialog_component.select_choice_at_position(ui_coordinates) if is_dialog_shown: dialog_component.update(elapsed_time) dialog_component.redraw() screen.fill(BLACK) if is_dialog_shown: screen.blit(dialog_component.surface, dialog_pos) pygame.draw.rect(screen, (255, 100, 100), Rect((dialog_margin, dialog_margin), outer_dialog_size), width=1) fps_string = str(int(clock.get_fps())).rjust(3, ' ') rendered_fps = font.render(f"FPS: {fps_string}", True, WHITE, (0, 0, 0)) screen.blit(rendered_fps, (5, 5)) screen.blit( font.render( "The dialog library is confined to the red rectangle above.", True, WHITE), (15, 400)) screen.blit( font.render("This text is handled separately from the dialog.", True, WHITE), (15, 430)) if not is_dialog_shown: screen.blit( font.render( "Oops, you closed the dialog. Restart app to see it again.", True, (255, 150, 150)), (15, 460)) pygame.display.update()