{"id":13658610,"url":"https://github.com/google/bottery","last_synced_at":"2025-09-27T07:30:59.799Z","repository":{"id":65981068,"uuid":"108880625","full_name":"google/bottery","owner":"google","description":null,"archived":true,"fork":false,"pushed_at":"2018-10-14T18:16:05.000Z","size":2947,"stargazers_count":3291,"open_issues_count":1,"forks_count":149,"subscribers_count":82,"default_branch":"master","last_synced_at":"2025-09-25T04:37:19.139Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":false,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/google.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-10-30T16:52:04.000Z","updated_at":"2025-07-09T16:27:58.000Z","dependencies_parsed_at":"2023-02-19T18:45:35.838Z","dependency_job_id":null,"html_url":"https://github.com/google/bottery","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/google/bottery","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/google%2Fbottery","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/google%2Fbottery/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/google%2Fbottery/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/google%2Fbottery/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/google","download_url":"https://codeload.github.com/google/bottery/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/google%2Fbottery/sbom","scorecard":{"id":436472,"data":{"date":"2025-08-11","repo":{"name":"github.com/google/bottery","commit":"c66291b56f842a8f9625dd01bbb124b4016e7d54"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":5.2,"checks":[{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Maintained","score":0,"reason":"project is archived","details":["Warn: Repository is archived."],"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Code-Review","score":10,"reason":"all changesets reviewed","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: Apache License 2.0: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"Security-Policy","score":10,"reason":"security policy file detected","details":["Info: security policy file detected: github.com/google/.github/SECURITY.md:1","Info: Found linked content: github.com/google/.github/SECURITY.md:1","Info: Found disclosure, vulnerability, and/or timelines in security policy: github.com/google/.github/SECURITY.md:1","Info: Found text in security policy: github.com/google/.github/SECURITY.md:1"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 30 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-19T04:47:43.981Z","repository_id":65981068,"created_at":"2025-08-19T04:47:43.981Z","updated_at":"2025-08-19T04:47:43.981Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":277184144,"owners_count":25775286,"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","status":"online","status_checked_at":"2025-09-27T02:00:08.978Z","response_time":73,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":[],"created_at":"2024-08-02T05:01:01.111Z","updated_at":"2025-09-27T07:30:59.781Z","avatar_url":"https://github.com/google.png","language":"JavaScript","readme":"# Bottery\n\n## A conversational agent prototyping platform by [Kate Compton](https://github.com/galaxykate).\n\n(This is not an official Google product.)\n\n## What is this?\n\nBottery is a syntax, editor, and simulator for prototyping **generative contextual conversations** modeled as **finite state machines**.\n\nBottery takes inspiration from the **[Tracery](http://tracery.io/)** open-source project for generative text (also by katecompton@ in a non-google capacity) and the [Cheap Bots, Done Quick!](https://cheapbotsdonequick.com/) bot-hosting platform, as well as open FSM-based storytelling tools like Twine.  \n\nLike Tracery, Bottery is a *syntax* that specifies the script of a conversation (a *map*) with JSON.  Like Cheap Bots, Done Quick!, the BotteryStudio can take that JSON and run a simulation of that conversation in a nice JavaScript front-end, with helpful visualizations and editing ability.\n\nThe goal of Bottery is to help *everyone*, from designers to writers to coders, be able to write simple and engaging  contextual conversational agents, and to test them out in a realistic interactive simulation, mimicking how they'd work on a \"real\" platform like DialogFlow.  \n\n\n## Bottery concepts\n\nUsers in Tracery write **grammars**, JSON objects that recursively define how to generate some text, like [the musings of a lost self-driving car](https://cheapbotsdonequick.com/source/losttesla) or [outer-space adventures](https://cheapbotsdonequick.com/source/tinyadv).  Tracery grammars are lists of symbol names (like \"animal\") and their expansion rules (like \"emu, okapi, pangolin\").\n\nIn Bottery, users write **maps**. Each map is composed of four sub-components\n* A set of **states**, with information about what to do on entering them, and how to get from one to another\n* A set of initial **blackboard** values\n* An optional Tracery **grammar**\n\n### Blackboard (and the pointer)\n\nYou can imagine a Bottery map like a finite state machine or a [boardgame board](https://www.pinterest.com/pin/361273201334614541/?lp=true): there are spaces, and connections between the spaces, and rules for how to move between them.  The map itself doesn't change or store information during play.  Instead, you have a **pointer** showing which state you are on, all the variables in the blackboard (like the number of kids in your Game of Life car).\n\nAn RPG map might use the blackboard to store the number of hit points for the main character, their current weapon and its stats, their gold, and quest progress.  A quiz bot might store all of its categories, questions and answers, the players' current points, and which questions it wants to ask next.  You can store strings, booleans, numbers, hierarchical objects, and arrays in the blackboard.  Storing and retrieving information is done with a JavaScript-like syntax: `foo.bar[5]` gets the value at the 5th index of object `bar` in object `foo`.  `foo.baz[10][20] = 10` behaves similarly, though unlike JavaScript, if these parameters don't exist, it will create new objects or arrays and fill them rather than throwing an error. See `parseMapPath` in `map.js` for details.\n\nVariables in the blackboard can be accessed from within Tracery with the syntax `You have guessed #/guessCount# times.`\n\n### States\n\nEach state is a node in the Bottery map. A state has\n* An **id**\n* A list of **actions** to be taken when the state is entered\n* A dictionary of **exits** to other states.\n* Optionally, a list of **suggestion chips** (using tracery syntax) of suggested user inputs. This is commonly used in text based bots.\n\nThere are several ways to express the actions that are taken when the state is entered, depending on the desired behavior. The following are currently defined:\n* `onEnter`: This takes a string of actions defined in the action syntax (see below). For example: `“‘hello’ greeting++”`. these are space delineated commands, and the extra quotation marks around the phrase are necessary\n* `onEnterDoOne` Takes an array of strings in format `\"[condition] [action]\"`. The first condition that evaluates to true has its action executed.\n* `onEnterSay` Takes a singe string and outputs it. The string can use tracery expansion syntax.\n* `onEnterPlay` Plays the audio file specified.\n* `onEnterFxn` Executes the given function (but must be defined in `map.js`)\n\nAll bots must have an `origin` state, which is the first state entered when the bot starts.\n\n### Exits\n\nExits are described by strings in the format:\n`[conditions] -\u003eTARGET_NAME [actions to take when taken]`\n\nSyntax for actions and conditions are described below.\n\nIf all the conditions are true then the exit becomes active. If there are *no* conditions, the exit is always active.\n\nThen there is an arrow (`-\u003e`) and a target.  The target is either an **id of a state** or an at sign `@` that indicates the pointer should re-enter the current state.\n\nThe list of actions is in **action syntax** (see below).\n\n### Condition\n\nConditions fall under the following categories:\n* Inputs: User input matching a string. E.g., `\"one\"` or `\"two\"`. The presence of quotes indicates a string that must be matched by the last user input. An asterisk `*` matches *any* user input.\n* Expressions: Mathematical syntax representing equality, inequality, and so on. Most basic math expressions are valid e.g. `count\u003e4`. Expressions can use variables that exist in the blackboard, using the blackboard variable syntax (see above). \n* Values: There is only one type of these at present, `wait:[time in seconds to wait]`. This evaluates to true after that much time has elapsed after entering the current state.\n\n### Actions\n\nAction syntax is similar to condition syntax:\n* Assignment: Sets a variable in the blackboard. If the variable does not exist it is created. E.g. `partyMember[1].weapon=\"sword\"`.\n* Output: Raw text with quotes is outputted using selected output method. ‘\"hello\"’. This uses tracery expansion syntax, so `\"hello #/playerName#\"`\n* Play sound: `playSound([sound name])`\n* Incrementation: Increments or decrements a variable in the blackboard. E.g.:`[varName]++` or `[varName]--`\n\n### How the pointer decides how to move\n\nWhen the pointer enters a state, the following things happen:\n1. Any `onEnter` actions are executed.\n2. Any **suggestion chips** are created and displayed to the user.\n3. All available exits (including the exits specified in the state, as well as global exits) are collected.\n\nThe pointer then waits for state change. At the moment, state change includes user input, and the passage of time. If no `wait` conditions are present, then the bot will wait for user input forever. When that state change occurs, the pointer will re-evaluate all the conditions on the currently available exits. If all the conditions on an exit evaluate to true, then that exit becomes active.\n\nIt is often the case that multiple exits are active at the same time. For example:\n`\"yes\" -\u003estartGame`\n`* -\u003easkForClarification`\n\nIf the user types \"yes\", both exits are active. The first exit in in the list of active exits is selected. In this case `\"yes\" -\u003estartGame` will be chosen.\n\nWhen the pointer uses an exit, the following occurs:\n1. The actions associated with the exit are executed.\n2. The pointer moves to the state of that exit and the process begins anew.\n\n## Interface Overview\n\n![UI overview](doc_images/bottery_ui.png?raw=true)\n\n### Chat\n\nTab for interacting with the bot. Occasionally, the player may be offered suggestion chips (e.g., \"heads\" and \"tails\") that can allow the player to interact without entering text.\n\n### Controls\n\nSwitches between text and speech, and also commands for working with state.\nIf there are errors in the bot’s underlying script, then they will appear here.\n\n### Editor\n\nAn inline editor for the underlying bot script. A user can edit the script and see changes without having to edit the underlying `.js` files. Changes here will be saved in local storage, so they will only be accessible to the current user.\n\n### Blackboard\n\nDisplays the current state of the variables known by the bot. These variables can be used to affect conditional behavior (e.g., the mood of the bot), some tracked information (e.g., the number of correct guesses in a quiz), the name of something (e.g., something the player is allowed to name), and much more.\n\nThis information is typically invisible to an end user interacting with the bot.\n\n### Inspector\n\nPresents a view of the bot’s state machine. This shows all the states that the bot can traverse through, and within them indicates the commands that are executed by the bot, and the ways to traverse to the next state[s]. The initial state is always \"origin\". This view is not interactive, but is a visual representation of the underlying script.\n\n### State view\n\nThis is a representation of the current state of the bot, and the potential next states, as well as the conditions for enabling these particular transitions.\n\n### Viz\n\nDisplays the directed connectivity graph of states and exits. Highlights the current state and any active exit transitions.\n\n## Example bot (kitten simulator!)\n\nNow that we have reviewed the underlying concepts and the interface, it is time to build a bot!\n\nWhen you have checked out the git repository, create a new file `kittens.js` in the `bots` directory, and add `kittens` to the list of bots in `bots.js`.\n\nWe can start with the following in `kittens.js`:\n\n```javascript\nbot = {\n  states: {\n    origin: {\n      onEnter: \"'You have a kitten!'\",\n    },\n  },\n}\n```\n\nThis is a minimal valid bot. It has one state, the `origin`, and that has a single `onEnter` associated with it. Note the fact that the text `'You have a kitten!'` is in single quotes. This is an output action and denotes that this string is to be output as text. We will add additional actions later.\n\nA note on syntax: The format of this is valid javascript, and is very similar to JSON, but is not valid JSON because of two key differences: trailing commas are permitted, and object keys do not require quotes.\n\n### Interactive kitten\n\nA bot isn't very interesting until you can interact with it, so let's add some interactivity:\n\n```javascript\nbot = {\n  states: {\n    origin: {\n      onEnter: \"'You have a kitten!'\",\n      exits: \"-\u003ename\",\n    },\n    name: {\n      onEnter: \"'What do you want to name your kitten?'\",\n      exits: \"'*' -\u003erespondToName name=INPUT\",\n    },\n    respondToName: {\n      onEnterSay: \"The kitten purrs happily, I guess it likes the name #/name#!\",\n    },\n  },\n}\n```\n\nThis example introduces two new states: `name` and `respondToName`. These states are connected via `exits`. The exit on `origin` has no conditions, and therefore is entered immediately by the Pointer. The exit in the state `name` requires some form of user input indicated by the asterisk. This exit has an action associated with it in the form `name=INPUT`. `INPUT` is a special variable indicating the user's input. `name=INPUT` has the effect that the variable `name` is assigned to what the user entered, and is saved in the blackboard. In state `respondToName` there is an `onEnterSay` behavior, which is similar to `onEnter`, but does not require extra single quotes around the text outputted. The blackboard variable `name` is accessed via Tracery syntax using `#/name#`.\n\nInteracting with this bot, you can see that the **viz** view displays the state graph, and the blackboard view displays the user-entered name.\n\n![](doc_images/kittens1.png?raw=true)\n\n### Suggestion chips\n\nUser interactions can be expedited though the use of suggestion chips. These are prompts that are shown to the user when interacting through text. \n\n```javascript\nbot = {\n  states: {\n    origin: {\n      onEnter: \"'You have a kitten!'\",\n      exits: \"-\u003ename\",\n    },\n    name: {\n      onEnter: \"'What do you want to name your kitten?'\",\n      chips: [\"Cupcake\", \"Dark Lord Satan\"],\n      exits: \"'*' -\u003erespond_to_name name=INPUT\",\n    },\n    respond_to_name: {\n      onEnterSay: \"The kitten purrs happily, I guess it likes the name #/name#!\",\n    },\n  },\n}\n```\n\n### Adding Tracery grammar\n\nA little more flavor can be added using a Tracery grammar:\n\n```javascript\nbot = {\n  grammar: {\n    noun: [\"cat\", \"monkey\",\"butter\", \"pants\", \"demon\", \"fluff\", \"taco\", \"mountain\", \"butt\"],\n    adj: [\"fluffy\", \"fat\", \"puff\", \"tepid\", \"love\", \"unruly\"],\n    name: [\"#noun.capitalize##noun#\", \"#adj.capitalize##noun#\", \"#noun.capitalize# the #adj.capitalize#\"],\n  },\n  states: {\n    origin: {\n      onEnter: \"'You have a kitten!'\",\n      exits: \"-\u003ename\",\n    },\n    name: {\n      onEnter: \"'What do you want to name your kitten?'\",\n      chips: [\"#name#\", \"#name#\", \"Cupcake\", \"Dark Lord Satan\"],\n      exits: \"'*' -\u003erespond_to_name name=INPUT\",\n    },\n    respond_to_name: {\n      onEnterSay: \"The kitten purrs happily, I guess it likes the name #/name#!\",\n    },\n  },\n}\n```\n\n![](doc_images/kittens2.png?raw=true)\n\n### Petting the kitten\n\nWhat are some of the things that a user might want to do with a kitten bot? A natural thing to do would be to pet the kitten. Real life kittens are temperamental creatures, and can behave unpredictably. We can use the blackboard to store a variable indicating the number of times the kitten wants to be petted, and anything beyond that will cause the kitten to bite the user.\n\n```javascript\nbot = {\n  grammar: {\n    noun: [\"cat\", \"monkey\",\"butter\", \"pants\", \"demon\", \"fluff\", \"taco\", \"mountain\", \"butt\"],\n    adj: [\"fluffy\", \"fat\", \"puff\", \"tepid\", \"love\", \"unruly\"],\n    name: [\"#noun.capitalize##noun#\", \"#adj.capitalize##noun#\", \"#noun.capitalize# the #adj.capitalize#\"],\n  },\n  states: {\n    origin: {\n      onEnter: \"'You have a kitten!' desired_pets=randomInt(1,5)\",\n      exits: \"-\u003ename\",\n    },\n    name: {\n      onEnter: \"'What do you want to name your kitten?'\",\n      chips: [\"#name#\", \"#name#\", \"Cupcake\", \"Dark Lord Satan\"],\n      exits: \"'*' -\u003erespond_to_name name=INPUT\",\n    },\n    respond_to_name: {\n      onEnterSay: \"The kitten purrs happily, I guess it likes the name #/name#!\",\n    },\n    pet: {\n      onEnter: \"'You pet the kitten' desired_pets--\",\n      exits: [\"desired_pets\u003e=0 -\u003ehappy_pet\", \"-\u003eangry_pet\"]\n    },\n    happy_pet: {\n      onEnterSay: \"#/name# loves you and is in ecstacy\",\n    },\n    angry_pet: {\n      onEnterSay: \"why did you pet #/name# when it didn't want to be petted!?\",\n      onEnter: \"desired_pets=randomInt(1,5)\",\n    }\n  },\n  exits: \"'pet' -\u003epet\",\n  initialBlackboard: {\n    name: \"the kitten\",\n  },\n}\n```\n\nThis example adds a global exit. No matter where the Pointer is at on the graph, the user can always pet the kitten. This introduces a problem, though, because the user could potentially pet the kitten before it was named, so an initial value for the name is configured in the blackboard. When the origin is entered, the variable `desired_pets` is set to a random value between 1 and 5. When the user pets the kitten too much, the `angry_pet` node is entered. \n\n![](doc_images/kittens3.png?raw=true)\n\n### State flow\n\nFinally, we should add some idle behavior for the kitten when it is not being petted.\n\n```javascript\nbot = {\n  grammar: {\n    noun: [\"cat\", \"monkey\",\"butter\", \"pants\", \"demon\", \"fluff\", \"taco\", \"mountain\", \"butt\"],\n    adj: [\"fluffy\", \"fat\", \"puff\", \"tepid\", \"love\", \"unruly\"],\n    name: [\"#noun.capitalize##noun#\", \"#adj.capitalize##noun#\", \"#noun.capitalize# the #adj.capitalize#\"],\n    catSpeak: [\"mmrrr\", \"meow\", \"mmrrrrow\", \"meep\", \"#catSpeak# #catSpeak#\"],\n  },\n  states: {\n    origin: {\n      onEnter: \"'You have a kitten!' desired_pets=randomInt(1,5)\",\n      exits: \"-\u003ename\",\n    },\n    name: {\n      onEnter: \"'What do you want to name your kitten?'\",\n      chips: [\"#name#\", \"#name#\", \"Cupcake\", \"Dark Lord Satan\"],\n      exits: \"'*' -\u003erespond_to_name name=INPUT\",\n    },\n    respond_to_name: {\n      onEnterSay: \"The kitten purrs happily, I guess it likes the name #/name#!\",\n      exits: \"-\u003eidle\"\n    },\n    pet: {\n      onEnter: \"'You pet the kitten' desired_pets--\",\n      exits: [\"desired_pets\u003e=0 -\u003ehappy_pet\", \"-\u003eangry_pet\"]\n    },\n    happy_pet: {\n      onEnterSay: \"#/name# loves you and is in ecstacy\",\n      exits: \"wait:10 -\u003eidle\"\n    },\n    angry_pet: {\n      onEnterSay: \"why did you pet #/name# when it didn't want to be petted!?\",\n      onEnter: \"desired_pets=randomInt(1,5)\",\n      exits: \"-\u003eangry\"\n    },\n    idle: {\n      onEnterSay: \"#/name# rolls around and makes cute noises\",\n      exits: \"wait:10 -\u003ehungry\",\n    },\n    angry: {\n      onEnter: \"'The kitten is angry! *bite*'\",\n      exits: \"wait:10 -\u003esleeping\",\n    },\n    sleeping: {\n      onEnter: \"'The kitten is sleeping! zzzzzzzzz'\",\n      exits: \"wait:10 -\u003ehungry\",\n    },\n    hungry: {\n      onEnter: \"'The kitten is hungry! meow meow #catSpeak#'\",\n      exits: \"wait:10 -\u003eangry\",\n    },\n  },\n  exits: \"'pet' -\u003epet\",\n  initialBlackboard: {\n    name: \"the kitten\",\n  },\n}\n```\n\nThis final example adds state transitions that form a cycle of activity. If no interaction occurs, the kitten will naturally cycle between the states of `hungry`, `sleeping`, and `angry`. The `wait:10` condition on the exit will delay for a particular amount of time before automatically advancing into that state. \n\n![](doc_images/kittens4.png?raw=true)\n\n### Additional resources.\n\nThis concludes the tutorial. For more examples of types of bots, check out:\n* `amIPsychic.js` This is a simple guessing game where the user guesses whether a random coin will flip heads or tails. The bot tracks the longest winning and losing streak.\n* `quiz.js` A basic quiz game where the user answers questions and these are used to determine a Hip Hop DJ name.\n* `tesla.js` A bot based on the tracery [twitter bot](https://twitter.com/losttesla) of the same name.\n\n","funding_links":[],"categories":["JavaScript","📦 Legacy \u0026 Inactive Projects"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgoogle%2Fbottery","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgoogle%2Fbottery","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgoogle%2Fbottery/lists"}