{"id":26194766,"url":"https://github.com/benpollarduk/ktvn","last_synced_at":"2025-04-15T03:15:27.170Z","repository":{"id":205686479,"uuid":"710310174","full_name":"benpollarduk/ktvn","owner":"benpollarduk","description":"A Kotlin library that provides a framework for writing visual novels for JVM. At its core Ktvn provides a flexible and extensible DSL for structuring and writing visual novels and interactive stories.","archived":false,"fork":false,"pushed_at":"2024-07-03T12:59:14.000Z","size":28371,"stargazers_count":17,"open_issues_count":2,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-04-15T03:15:20.732Z","etag":null,"topics":["domain-specific-language","dsl","engine","framework","game","game-engine","interactive","interactive-story","java","kotlin","novel","story","visual","visual-novel"],"latest_commit_sha":null,"homepage":"","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/benpollarduk.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-10-26T12:49:47.000Z","updated_at":"2025-02-21T08:46:33.000Z","dependencies_parsed_at":"2023-11-10T05:04:39.045Z","dependency_job_id":"4aead881-8b36-451f-b307-dcf4a3d7fce6","html_url":"https://github.com/benpollarduk/ktvn","commit_stats":{"total_commits":214,"total_committers":2,"mean_commits":107.0,"dds":"0.25233644859813087","last_synced_commit":"4326fc7c0bcba099a57d21382407e090dc625bd9"},"previous_names":["benpollarduk/ktvn"],"tags_count":21,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/benpollarduk%2Fktvn","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/benpollarduk%2Fktvn/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/benpollarduk%2Fktvn/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/benpollarduk%2Fktvn/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/benpollarduk","download_url":"https://codeload.github.com/benpollarduk/ktvn/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248997080,"owners_count":21195799,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["domain-specific-language","dsl","engine","framework","game","game-engine","interactive","interactive-story","java","kotlin","novel","story","visual","visual-novel"],"created_at":"2025-03-12T01:59:57.761Z","updated_at":"2025-04-15T03:15:27.152Z","avatar_url":"https://github.com/benpollarduk.png","language":"Kotlin","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n    \n# Ktvn\nKtvn is a framework for writing visual novels in Kotlin or Java. It is largely based on Kotlins strong Domain Specific Language\n(DSL) capabilities. The aim of Ktvn is to provide a flexible framework for writing visual novels that can be pulled in \nto other projects. Ktvn provides a quick, natural and flexible syntax that is easy to pick up and simple to maintain, \nwhile retaining Kotlins powerful and feature rich syntax.\n\n[![main-ci](https://github.com/benpollarduk/ktvn/actions/workflows/main-ci.yml/badge.svg?branch=main)](https://github.com/benpollarduk/ktvn/actions/workflows/main-ci.yml)\n[![codecov](https://codecov.io/gh/benpollarduk/ktvn/graph/badge.svg?token=H9RCA8F7DE)](https://codecov.io/gh/benpollarduk/ktvn)\n[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=benpollarduk_ktvn\u0026metric=alert_status)](https://sonarcloud.io/summary/new_code?id=benpollarduk_ktvn)\n[![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=benpollarduk_ktvn\u0026metric=sqale_rating)](https://sonarcloud.io/summary/new_code?id=benpollarduk_ktvn)\n[![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=benpollarduk_ktvn\u0026metric=security_rating)](https://sonarcloud.io/summary/new_code?id=benpollarduk_ktvn)\n[![Vulnerabilities](https://sonarcloud.io/api/project_badges/measure?project=benpollarduk_ktvn\u0026metric=vulnerabilities)](https://sonarcloud.io/summary/new_code?id=benpollarduk_ktvn)\n[![Bugs](https://sonarcloud.io/api/project_badges/measure?project=benpollarduk_ktvn\u0026metric=bugs)](https://sonarcloud.io/summary/new_code?id=benpollarduk_ktvn)\n[![GitHub release](https://img.shields.io/github/release/benpollarduk/ktvn.svg)](https://github.com/benpollarduk/ktvn/releases)\n[![License](https://img.shields.io/github/license/benpollarduk/ktvn.svg)](https://opensource.org/licenses/MIT)\n[![Documentation Status](https://img.shields.io/badge/docs-latest-brightgreen.svg)](https://benpollarduk.github.io/ktvn-docs/)\n\n\u003c/div\u003e\n\n## Getting Started\n\n### Clone the repo\nClone the repo to the local machine.\n```bash\ngit clone https://github.com/benpollarduk/ktvn.git\n```\n\n### Hello World\n```kotlin\n// a ktvn visual novel requires a configuration, DynamicGameConfiguration provides a default that can be tailored to the UI being used\nval configuration = DynamicGameConfiguration()\n\n// the configuration requires an engine to process input and output. the engine itself will vary depending on the UI framework being targeted\nconfiguration.engine = AnsiConsoleGameEngine()\n\n// the narrator provides a class that can narrate the story\nval narrator = Narrator(configuration.gameAdapter.narratorAdapter)\n\n// create a simple story\nval story = story {\n\n    // add a single chapter\n    this add chapter {\n\n        // add a new scene\n        this add scene {\n\n            // set the steps that make up the scene\n            this steps listOf(\n\n                // include a simple narration step\n                next { narrator narrates \"Welcome to a Ktvn visual novel!\" }\n            )\n        }\n    }\n}\n\n// a visual novel provides a wrapper for a story that hooks together the story and the configuration\nval visualNovel = VisualNovel.create(story, configuration)\n\n// a game provides an object that is used to control execution of the visual novel\nval game = Game(visualNovel)\n\n// execute the game synchronously\nGameExecutor.execute(game)\n```\n\n### Example visual novels\nThe quickest way to start getting to grips with the structure of Ktvn and the DSL is by taking a look at the examples. \nExample visual novels are provided in the [ktvn-exmples](https://github.com/benpollarduk/ktvn/tree/main/ktvn-examples/src/main/kotlin/com/github/benpollarduk/ktvn/examples) directory \nand have been designed with the aim of showcasing the various features.\n\n### Running the examples\nThe included examples can be run with a couple of included apps:\n\n#### Prototyping console\nThe prototyping console is useful for running through visual novels through a basic console application.\n\n* Build the prototyping console.\n```bash\n./gradlew :app-ktvn-prototyper-console:build\n```\n* Run an example through the prototyping console.\n```bash\ncd app-ktvn-prototyper-console/build/libs\njava -jar app-ktvn-prototyper-console-all.jar\n```\n\n#### Prototyping Swing UI\nThe prototyping Swing app provides a simple application that supports the core Ktvn concepts.\n\n* Build the prototyping Swing app.\n```bash\n./gradlew :app-ktvn-prototyper-swing:build\n```\n* Run an example through the prototyping Swing app.\n```bash\ncd app-ktvn-prototyper-swing/build/libs\njava -jar app-ktvn-prototyper-swing-all.jar\n```\n\n## Story Structure\nA Ktvn visual novel starts with a **Story**. A Story contains one or more **Chapters**. Each Chapter contains one or \nmore **Scenes**. Each Scene contains one or more **Steps**. There are several types of Step, and Step is extensible \nso that the DSL can be customised.\n\nFor example:\n\n```\nStory\n├── Chapter\n│   ├── Scene\n│   |   ├── Step\n│   |   ├── Step\n│   |   ├── Step\n|   ├── Scene\n│   |   ├── Step\n│   |   ├── Step\n├── Chapter\n│   ├── Scene\n│   |   ├── Step\n|   ├── Scene\n│   |   ├── Step\n```   \n  \nSimple classes and DSL exist to support characters, narration, choices, flags, emotions and positioning of characters \nand more. Emotions and character positions are fully and easily extensible. When a characters emotion  or position \nchanges a listener is invoked, so regardless of the UI system being used to render the visual novel these events can be \nheard and invoked. Each event requires an acknowledgment before the story continues, so flow control is easy. \nListeners are provided for:\n* Speak - when a character speaks.\n* Narrate - when the narrator narrates.\n* Move - when a characters position changes.\n* Emote - when a characters emotion changes.\n* Animate - when a characters animation changes.\n* Ask - when either the narrator or a character asks a question.\n* Scene - when scenes transition.\n* Chapter - when chapters transition.\n* Audio - when audio is changed.\n\n## Execution\nStories can be executed as a **Game**. Games must be executed through the **GameExecutor**, an object dedicated to game \nmanagement. The game executor can run games synchronously or asynchronously.\n\n```kotlin\n// execute the game synchronously\nGameExecutor.execute(game)\n```\n\nOr:\n\n```kotlin\n// execute the game asynchronously\nGameExecutor.executeAysnc(game) {\n    // TODO: handle game completion\n}\n```\n\nThe constructor for Game objects accepts an instance of **VisualNovel**. The VisualNovel is a critical component of \nKtvn, it essentially wraps a Story and a **GameConfiguration** into a single discoverable file. The GameConfiguration \nties together how the story and the UI interact with one another. Please see the **Integration** section of this readme \nfor more information.\n\n## Persistence\nPersistence is handled in three distinct parts, **GameSave**, **RestorePoint** and **StepTracker** .\n\n### GameSave\nThe users settings, endings reached and total seconds played are saved in a **GameSave**.\n```kotlin\nval gameSave = game.getGameSave()\nGameSaveSerializer.toFile(gameSave, path)\n```\n\n### RestorePoint\nProgress in a game can be persisted as a **RestorePoint**. A restore point can be generated at any point before, \nduring or after a games execution and persisted to file using the **RestorePointSerializer**. This stores the users \ncurrent position in the game, flags and has a name, a creation date and time and a thumbnail.\n```kotlin\nval restorePoint = game.getRestorePoint(\"File1\")\nRestorePointSerializer.toFile(restorePoint, path)\n```\n\n### StepTracker\nThe **StepTracker** tracks which steps have been viewed by the player. This is important as it allows the skip feature \nto skip viewed steps on a subsequent play through. As default a **StepIdentifierTracker** is provided and records steps \nwith a deterministic identifier.\n```kotlin\ngameEngine.stepTracker.persist(path)\n```\n\n## Core DSL\nThe Ktvn DSL is simple but powerful. Each step in a story has access to the parent story's flags, which allows variables \nsuch as user responses to be captured and made accessible to other steps. **Ktvn is in the very early stages of \ndevelopment and the DSL in particular may change.**\n\n### next ###\nnext is a simple step to allow one or more actions to take place. For example, the narrator can say a line:\n```kotlin\nnext { narrator narrates \"Although Michel has remained amicable, the witch, Morgana, has not.\" }\n```\nOr a character may speak:\n```kotlin\nnext { morgana says \"The two of us are cursed to spend eternity in this mansion.\" }\n```\nOr a character may think:\n```kotlin\nnext { morgana thinks \"I could never tell Michel I don't actually think he is a fool.\" }\n```\nCharacters can show emotions:\n```kotlin\nnext { michel looks concerned }\n```\nCharacters can be animated:\n```kotlin\nnext { michel begins shaking }\n```\nChange position on the screen:\n```kotlin\nnext { layout moveRight morgana }\n```\nOr play a sound effect:\n```kotlin\nnext { audio sfx sfxFromResource(\"crash\") }\n```\n\n### then\nthen is very similar to next, but allows the step to be named. This is useful if the story needs to jump to this step.\n```kotlin\nthen {\n    this name \"Michel shows anger\"\n    this does {\n        michel looks angry\n    }\n}\n```\n\n### choice\nchoice allows the user to present a question to the user and receive an answer. A character or the narrator can ask a \nmultiple choice question:\n```kotlin\nchoice {\n    morgana asks question {\n        this line \"Why we do, don't we dear?\"\n        this option answer {\n            this line \"Of course.\"\n            this does { flags -\u003e\n                flags setTrue \"Michel likes Morgana\"\n            }\n        }\n        this option answer {\n            this line \"I hate you!\"\n            this does { flags -\u003e\n                flags setTrue \"Michel hates Morgana\"\n            }\n        }\n    }\n}\n```\nEach choice can have multiple options. Each option has an optional script specified by the **does** keyword. \nNotice the use of the **flags** class. A single instance of Flags exists for each game and can be used to pass values \nbetween steps. Each flag has a string key and a boolean value. If a flag does not exist when it is read then false will be \nreturned as default. In this \ncase a flag is set to register the option that the user picked.\n\n### decision\ndecision is very similar to choice, but allows the step to be named. This is useful if the story needs to jump to this step.\n```kotlin\ndecision {\n    this name \"Michel's feeling towards Morgana\"\n    this does {\n        morgana asks question {\n            this line \"Why we do, don't we dear?\"\n            this option answer {\n                this line \"Of course.\"\n                this does { flags -\u003e\n                    flags setTrue \"Michel likes Morgana\"\n                }\n            }\n            this option answer {\n                this line \"I hate you!\"\n                this does { flags -\u003e\n                    flags setTrue \"Michel hates Morgana\"\n                }\n            }\n        }\n    }\n}\n```\n\n### conditional\nconditional allows a step to only be invoked if a specified condition is met.\n```kotlin\nconditional {\n    this condition \"Michel likes Morgana\"\n    this does {\n        morgana looks amused\n        morgana says \"Fool, I despise you!\"\n    }\n    this returns Continue\n}\n```\nThe **condition** keyword specifies the flag. If that flag is set to true then the script specified by the **does** \nkeyword will be executed. Lastly the **returns** keyword specifies the result of the step so that the story can \ncontinue, branch or end as required.\n\n### pause\npause is a step that prevents the story from progressing for the specified duration.\n```kotlin\npause {\n    this seconds 5\n}\n```\nThe **seconds** keyword allows the delay time to be specified, in seconds. Shorter delays can be specified in\nmilliseconds using the **milliseconds** keyword.\n\n### clear\nclear is a step that signals that the current scene should be cleared. The engine is responsible for actioning this in a way that makes sense for the executing game.\n```kotlin\nclear { }\n```\nSuggested use cases are for clearing the text from a narrative scene, or removing all dialog from a dialog scene.\n\n### interactive\ninteractive is a step that adds extensibility to the game in the form of allowing other components to be called. For \nexample, some visual novels may contain mini-games. Providing the mini game implements or is wrapped by \n**InteractiveComponent** it can be invoked in a step.\n```kotlin\ninteractive {\n    this element component\n    this args arrayOf(\"args1\", \"args2\")\n}\n```\nAn example of wrapping a component:\n```kotlin\nval component = object: InteractiveComponent {\n    override fun invoke(args: Array\u003cString\u003e, flags: Flags, cancellationToken: CancellationToken): StepResult {\n        /* invoke your component here.\n           for example, you could:\n            -invoke a mini-game\n            -check something on the operating system and set a flag\n            -play a video\n            -anything else, it is up to you.\n          The cancellationToken can be used to signal cancellation to your component.\n          This function will need to return a StepResult which can be used to signal back to the story how it should \n          continue following execution of the component.\n        */\n        return StepResult.Continue\n    }\n}\n```\n\n### end\nend is a simple step that signifies that an ending has been reached.\n```kotlin\nend {\n    this ending Ending(\"True End.\", 1)\n}\n```\nThe ending that was reached can be specified with the **ending** keyword.\n\nFor further examples please see the ktvn-example directory in the repo.\n\n## Progression\nProgression through a story is controlled by the **ProgressionController**, which is part of the **GameEngine**. The \nfollowing progression modes are supported:\n* Wait for acknowledgement - the user must acknowledge each step.\n* Skip - previously viewed steps will be skipped.\n* Auto - the step will be acknowledged after a specified time.\n\n## Integration\nKtvn provides a structure, DSL and flow control for creating visual novels, but it does not provide a framework for \ncreating UIs and managing assets. Many frameworks for this exist. To integrate with a story a **GameConfiguration** is \nrequired. The easiest way of achieving this is by using **DynamicGameConfiguration** with a **GameEngine**. \nPlease see **AnsiConsoleGameEngine** for a simple example that demonstrates how to create a game engine that integrates with an ANSI compatible console.\n\nThe UI and the GameEngine will be unique for each project. In app-ktvn-prototyper-console the UI is provided by the \nterminal and **AnsiConsoleGameEngine** interacts directly with this.\n\n![ktvn-sequencing-overview-components.png](docs%2Foverview%2Fktvn-sequencing-overview-components.png)\n\nThe following sequence diagram shows the sequence of starting a game and a character speaking then asking a question:\n\n![ktvn-sequencing-overview.png](docs%2Foverview%2Fktvn-sequencing-overview.png)\n\n## Prototyping\nKtvn is focused on providing a framework for writing visual novels that can be pulled in to other frameworks rather than \nattempting to provide a full solution for writing visual novels from start to finish. This puts some emphasis on writing \nan engine to fit the target framwork but allows for bespoke UI solutions to be created. However, a swing prototyping app \nis included in the repo to help with the prototyping phases of development. Please see \n[app-ktvn-prototyper-swing](https://github.com/benpollarduk/ktvn/tree/main/app-ktvn-prototyper-swing) for more information.\n\n![ktvn_prototyper_example](https://github.com/benpollarduk/ktvn/assets/129943363/99be2a75-7255-43aa-a17a-120aec1d5a8f)\n\nThe source for some of the above scene:\n\n```kotlin\ninternal fun onTheLaunchPad(): Scene {\n    return scene {\n        this name \"On the launch pad\"\n        this background shuttleDay\n        this music shuttleDayMusic\n        this layout createLayout {\n            this addLeftOf sophie\n            this addRightOf toki\n            this configure configuration.gameAdapter.layoutAdapter\n        }\n        this steps listOf(\n            next {\n                layout moveCenter sophie\n                sophie looks normal\n                sophie says \"Where has that fool gotten to now?\"\n            },\n            next { audio sfx sfxWoosh },\n            next {\n                layout moveLeft sophie\n                layout moveRight toki\n                toki looks normal\n                toki says \"Here I am!\"\n                toki says \"Ready for duty!\"\n            },\n            next {\n                sophie looks happy\n                sophie says \"Ever the fool.\"\n            }\n        )\n    }\n}\n```\n\n## Documentation\nPlease visit [https://benpollarduk.github.io/ktvn-docs/](https://benpollarduk.github.io/ktvn-docs/) to view the Ktvn documentation.\n\nPlease visit [https://benpollarduk.github.io/ktvn-api-docs/](https://benpollarduk.github.io/ktvn-api-docs/) to view the Ktvn Api documentation.\n\n## For Open Questions\nVisit https://github.com/benpollarduk/ktvn/issues\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbenpollarduk%2Fktvn","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbenpollarduk%2Fktvn","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbenpollarduk%2Fktvn/lists"}