├── .gitignore ├── LICENSE ├── README.md ├── assets ├── icons │ ├── correct.png │ └── incorrect.png └── images │ ├── Animals.jpeg │ ├── Presidents.jpeg │ └── us_map.png ├── capsule.bxb ├── code ├── ExtractAnswerFromOption.js ├── FindQuiz.js ├── StartQuiz.js ├── UpdateQuiz.js ├── data │ └── quizzes.js └── lib │ └── util.js ├── contentUtility └── CreateQuizContent.xlsx ├── models ├── actions │ ├── FindQuiz.model.bxb │ ├── StartQuiz.model.bxb │ ├── UpdateQuiz.model.bxb │ └── utils │ │ └── ExtractAnswerFromOption.model.bxb └── concepts │ ├── primitive │ ├── Answer.model.bxb │ └── SearchTerm.model.bxb │ └── structures │ ├── CorrectAnswer.model.bxb │ ├── Option.model.bxb │ ├── Question.model.bxb │ └── Quiz.model.bxb └── resources ├── base └── views │ ├── FindQuiz.view.bxb │ ├── QuizCompleted.view.bxb │ ├── UpdateQuiz.view.bxb │ └── macros │ ├── QuizCompleted.layout.bxb │ └── QuizDetails.layout.bxb └── en ├── capsule-info.bxb ├── dialogs ├── NoQuiz.dialog.bxb └── macros │ ├── FindQuiz.dialog.bxb │ ├── LayoutMacros.dialog.bxb │ └── UpdateQuiz.dialog.bxb ├── example-quiz.hints.bxb ├── quiz.endpoints.bxb └── training ├── t-4.training.bxb ├── t-5.training.bxb ├── t-7.training.bxb ├── t-8.training.bxb ├── t-a.training.bxb ├── t-h.training.bxb ├── t-i.training.bxb ├── t-p.training.bxb ├── t-q.training.bxb ├── t-r.training.bxb └── t-x.training.bxb /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |
4 |

Bixby Quiz Sample Capsule

5 |

6 | 7 | ## Overview 8 | 9 | Do you have a great idea for a quiz that will entertain and delight Bixby users? Do you want to create a fun quiz to help you learn something new? A quiz to stump your friends? The quiz sample capsule is the perfect place to start building a Bixby quiz capsule! 10 | 11 | Both the fundamentals of a Bixby Capsule and some more intermediate/advanced concepts are used. This is a great capsule to learn even more about how to develop for Bixby. 12 | 13 | You can easily customize this capsule with your own quiz without any development! Just use the spreadsheet in the `contentUtility` folder and cut and paste from there into the `quizzes.js` file, update the `capsule.id` and `training` as necessary and you have made this capsule your own! 14 | 15 | ## How to get started 16 | 17 | * Download and install the Bixby Studio IDE from the [Bixby Developer Center](http://bixbydevelopers.com) 18 | * Download this capsule (zip is the easiest way) from Github. Unzip in your directory of choice 19 | * Open the Capsule in Bixby Studio 20 | * Open the simulator and give it a try! 21 | 22 | ## How to try 23 | Ask Bixby to start a quiz, try these: 24 | ``` 25 | Start a quiz (Bixby asks you which quiz you want to start) 26 | 27 | Start a states quiz (Bixby starts a quiz about US States) 28 | 29 | Start a presidents quiz (Bixby starts a quiz about US presidents) 30 | 31 | Start a funny quiz (Bixby starts a quiz that is funny) 32 | 33 | ``` 34 | 35 | # How to customize 36 | * Change the capsule id to reflect your organization and your content. The capsule id is defined in `capsule.bxb` file. 37 | * Create your own quiz questions and answers by editing the `code/data/quizzes.js` file. You may also use the spreadsheet located in `contentUtility/createQuizContent` The `quizzes.js` file has comments that explain each of the fields and how they customize a quiz 38 | * You can customize the Quiz NoResult dialog by modifying the `resources/en/dialogs/NoQuiz.dialog.bxb` 39 | * You can customize the Quiz Found dialog by modifying the `resources/base/views/FindQuiz.view.bxb` view file 40 | * You can customize the Quiz Question/Answer prompts by directly modifying the `resources/base/views/UpdateQuiz.view.bxb` file and by modifying the dialog macros in `resources/en/dialog/macros/`. 41 | * The training in this sample capsule is basic. You will likely need to add additional training for your quiz 42 | * Have fun! 43 | 44 | --- 45 | 46 | ## Additional Resources 47 | 48 | ### Your Source for Everything Bixby 49 | * [Bixby Developer Center](http://bixbydevelopers.com) - Everything you need to get started with Bixby Development! 50 | * [Bixby News, Blogs and Tutorials](https://bixby.developer.samsung.com/) - Bixby News, Tutorials, Blogs and Events 51 | 52 | ### Guides & Best Practices 53 | * [Quick Start Guide](https://bixbydevelopers.com/dev/docs/get-started/quick-start) - Build your first capsule 54 | * [Design Guides](https://bixbydevelopers.com/dev/docs/dev-guide/design-guides) - Best practices for designing your capsules 55 | * [Developer Guides](https://bixbydevelopers.com/dev/docs/dev-guide/developers) - Guides that take you from design and modeling all the way through deployment of your capsules 56 | 57 | ### Bixby Videos 58 | * [Bixby Developers YouTube Channel](https://www.youtube.com/c/bixbydevelopers) - Tutorial videos, Presentations, Capsule Demos and more 59 | 60 | ### Bixby Podcast 61 | * [Bixby Developers Chat](http://bixbydev.buzzsprout.com/) - Voice, Conversational AI and Bixby discussions 62 | 63 | ### Bixby on Social Media 64 | * [@BixbyDevelopers](https://twitter.com/bixbydevelopers) - Twitter 65 | * [Facebook](https://facebook.com/BixbyDevelopers) 66 | * [Instagram](https://www.instagram.com/bixbydevelopers/) 67 | 68 | ### Need Support? 69 | * Have a feature request? Please suggest it in our [Support Community](https://support.bixbydevelopers.com/hc/en-us/community/topics/360000183273-Feature-Requests) to help us prioritize. 70 | * Have a technical question? Ask on [Stack Overflow](https://stackoverflow.com/questions/tagged/bixby) with tag “bixby” 71 | -------------------------------------------------------------------------------- /assets/icons/correct.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bixbydevelopers/capsule-sample-quiz/51eb3d267edd3f9b866b55131f01f27a5bf74286/assets/icons/correct.png -------------------------------------------------------------------------------- /assets/icons/incorrect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bixbydevelopers/capsule-sample-quiz/51eb3d267edd3f9b866b55131f01f27a5bf74286/assets/icons/incorrect.png -------------------------------------------------------------------------------- /assets/images/Animals.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bixbydevelopers/capsule-sample-quiz/51eb3d267edd3f9b866b55131f01f27a5bf74286/assets/images/Animals.jpeg -------------------------------------------------------------------------------- /assets/images/Presidents.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bixbydevelopers/capsule-sample-quiz/51eb3d267edd3f9b866b55131f01f27a5bf74286/assets/images/Presidents.jpeg -------------------------------------------------------------------------------- /assets/images/us_map.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bixbydevelopers/capsule-sample-quiz/51eb3d267edd3f9b866b55131f01f27a5bf74286/assets/images/us_map.png -------------------------------------------------------------------------------- /capsule.bxb: -------------------------------------------------------------------------------- 1 | capsule { 2 | id (example.quiz) 3 | version (0.2.0) //JSRS migration VCAP-24311 4 | format (3) 5 | targets { 6 | target (bixby-mobile-en-US) 7 | } 8 | capsule-imports { 9 | import (viv.core) { 10 | as (core) 11 | } 12 | } 13 | runtime-version (2) { 14 | js-runtime-version (2) 15 | } 16 | store-countries { 17 | all 18 | } 19 | store-sections { 20 | section (GamesAndFun) 21 | } 22 | } -------------------------------------------------------------------------------- /code/ExtractAnswerFromOption.js: -------------------------------------------------------------------------------- 1 | //Convert the option structure to a text answer. 2 | export default function extractAnswerFromOption({ option }) { 3 | return option.text; 4 | } 5 | -------------------------------------------------------------------------------- /code/FindQuiz.js: -------------------------------------------------------------------------------- 1 | import console from 'console'; 2 | import { buildQuizzes } from "./lib/util.js"; 3 | 4 | export default function ({ searchTerm }) { 5 | return buildQuizzes(searchTerm); 6 | } 7 | -------------------------------------------------------------------------------- /code/StartQuiz.js: -------------------------------------------------------------------------------- 1 | export default function startQuiz({ quiz }) { 2 | return quiz; 3 | } 4 | -------------------------------------------------------------------------------- /code/UpdateQuiz.js: -------------------------------------------------------------------------------- 1 | import * as util from "./lib/util"; 2 | 3 | // Will repromt if the user does not give a valid input. Set to false to not reprompt 4 | // Futue enhancement would be to reprompt only n times and then give up (suggest n = 1 or 2) 5 | const reprompt = true; 6 | 7 | import console from 'console'; 8 | 9 | export default function updateQuiz({ quiz, answer }) { 10 | answer = answer.toLowerCase(); 11 | const i = quiz.index; 12 | const options = quiz.questions[i].options; 13 | const hasOptions = !(options === undefined || options.length == 0); 14 | const correctAnswers = quiz.questions[i].correctAnswer.acceptedAnswers; 15 | const correctAlias = String( 16 | quiz.questions[i].correctAnswer.acceptedAlias 17 | ).toLowerCase(); 18 | const currentQuestion = quiz.questions[i]; 19 | quiz.questions[i].answer = answer; 20 | quiz.questions[quiz.index].invalidAnswer = false; 21 | var correct = false; 22 | var answeredOption = false; 23 | 24 | // Check for match with answer option e.g. "California" or "B Calfornia" assuming B was the option letter 25 | // Matches answer only and answer in sentence 26 | correct = util.checkAnswerMatch(correctAnswers, answer); 27 | 28 | var possOptionAlias = []; 29 | if (!correct && hasOptions) { 30 | // Check if user answered an incorrect option text, also contruct possible aliases 31 | var ret = util.checkIncorrectOption(options, answer); 32 | answeredOption = ret.answeredOption; 33 | possOptionAlias = ret.possOptionAlias; 34 | 35 | if (!answeredOption) { 36 | // Did user answer with an alias and was the alias correct 37 | ret = util.checkAliasMatch(answer, possOptionAlias, correctAlias); 38 | answeredOption = ret.answeredOption; 39 | correct = ret.correct; 40 | } 41 | } 42 | 43 | if (correct) { 44 | quiz.questions[quiz.index].correct = true; 45 | quiz.score++; 46 | } 47 | 48 | if (!correct && !answeredOption && hasOptions && reprompt) { 49 | // Answered with an invalid option. Reprompt 50 | quiz.questions[quiz.index].invalidAnswer = true; 51 | var validOptionsText = util.arrayToListForSpeech(possOptionAlias); 52 | quiz.textToSpeak = 53 | util.buildQuestionToSpeak(quiz.questions[quiz.index]) + 54 | '. Please answer with a letter: ' + 55 | validOptionsText || ' '; 56 | } else if (quiz.index < quiz.questions.length - 1) { 57 | quiz.index++; 58 | quiz.textToSpeak = 59 | util.buildQuestionToSpeak(quiz.questions[quiz.index]) || ' '; 60 | } else { 61 | quiz.completed = true; 62 | } 63 | return quiz; 64 | } 65 | -------------------------------------------------------------------------------- /code/data/quizzes.js: -------------------------------------------------------------------------------- 1 | export default [ 2 | // Start of funny quiz 3 | { 4 | title: 'Funny Quiz', 5 | tags: ['funny', 'animal'], //used to find this quiz 6 | image: { 7 | url: '/images/Animals.jpeg', 8 | }, 9 | questions: [ 10 | { 11 | question: 'What do cats like to eat on a hot day?', 12 | options: ['Mice cream', 'Ice Cream', 'Hot Cream'], 13 | answer: 0, // Corresponds to "Mice cream" 14 | }, 15 | { 16 | question: 'What do you call a cold dog?', 17 | options: ['Frozen', 'Hot dog', 'Chilli Dog'], 18 | answer: 'Chilli Dog', 19 | explanation: "Because it's cold. Get it?", 20 | }, 21 | ], 22 | }, 23 | // End of funny quiz 24 | 25 | // Start of president quiz 26 | { 27 | title: 'President Quiz', 28 | tags: ['president', 'presidents'], //used to find this quiz 29 | image: { 30 | url: '/images/Presidents.jpeg', 31 | }, 32 | questions: [ 33 | { 34 | question: 35 | "Which president said 'Efforts and courage are not enough without purpose and direction.'?", 36 | options: ['Kennedy', 'Obama'], 37 | answer: 0, 38 | }, 39 | { 40 | question: 41 | "Which president said 'Men are not prisoners of fate, but only prisoners of their own minds'?", 42 | options: ['Hoover', 'Roosevelt', 'Truman'], 43 | answer: [ 44 | 'Roosevelt', 45 | 'FDR', 46 | 'Franklin Roosevelt', 47 | 'Franklin D Roosevelt', 48 | ], 49 | }, 50 | { 51 | question: "Which president said 'Speak softly, and carry a big stick'?", 52 | answer: ['Teddy Roosevelt', 'Roosevelt', 'Theodore Roosevelt'], 53 | explanation: 54 | "That famous quote describes Teddy Roosevelt's foreign policy", 55 | }, 56 | ], 57 | }, 58 | // End of presidents quiz 59 | 60 | // Start of states quiz generated from the spreadsheet 61 | { 62 | title: 'States Quiz', 63 | tags: ['states', 'state', 'united states'], 64 | image: { url: '/images/us_map.png' }, 65 | questions: [ 66 | { 67 | question: "Which state has the nickname 'Spud State'?", 68 | options: ['Idaho', 'Wyoming', 'Florida'], 69 | answer: 'Idaho', 70 | explanation: 71 | 'Idaho is well known for growing potatoes and thus the nickname, Spud State', 72 | }, 73 | { 74 | question: "Which state has the nickname 'Peach State'?", 75 | options: ['Hawaii', 'California', 'Georgia'], 76 | answer: 2, 77 | }, 78 | { 79 | question: "Which state has the nickname 'Silver State'?", 80 | options: ['New York', 'Montana', 'Nevada'], 81 | answer: 'Nevada', 82 | explanation: 83 | "Nevada had a silver rush in the mid 1800's and thus has the nickname, Silver State", 84 | }, 85 | { 86 | question: 'What state is known as the Land of 10,000 lakes?', 87 | options: ['Wisconsin', 'Louisiana', 'Minnesota'], 88 | answer: 'Minnesota', 89 | explanation: 90 | 'Minnesota has over 11,000 lakes, so the land of 10,000 lakes is under counting!', 91 | }, 92 | { 93 | question: 'What state is the largest in area?', 94 | options: ['California', 'Alaska', 'Texas'], 95 | answer: 1, 96 | explanation: 97 | 'Alaska is the largest state by far with 665,384 square miles. Second place Texas has 268,596 square miles and third place California has 163,694 square miles', 98 | }, 99 | { 100 | question: 'What state is the largest agricultural producer?', 101 | options: ['Iowa', 'Kansas', 'California'], 102 | answer: 'California', 103 | explanation: 104 | "Caifornia produces over 11% of the nation's agricultural output followed by #2 Iowa which produces over 8%.", 105 | }, 106 | { 107 | question: 'Which of the following states does not have a panhandle?', 108 | options: ['Idaho', 'Florida', 'New Mexico'], 109 | answer: 'New Mexico', 110 | }, 111 | { 112 | question: 'Which state does not border the Great Lakes?', 113 | options: ['Ohio', 'Michigan', 'Iowa'], 114 | answer: 'Iowa', 115 | }, 116 | { 117 | question: 'What is the smallest US state?', 118 | options: ['Delaware', 'Rhode Island', 'Connecticut'], 119 | answer: 'Rhode Island', 120 | explanation: 121 | 'Rhode Island is the smallest US state at 1,045 square miles. The next smallest is Delaware which at 1,954 square miles is almost twice the size of Rhode Island', 122 | }, 123 | { 124 | question: "How many states are prefixed with 'New'?", 125 | answer: ['four', '4'], 126 | explanation: 127 | "There are four states prefixed by 'New': New Hampshire, New Jersey, New Mexico and New York", 128 | }, 129 | ], 130 | }, 131 | // End of States quiz 132 | ]; 133 | -------------------------------------------------------------------------------- /code/lib/util.js: -------------------------------------------------------------------------------- 1 | import quizzes from '../data/quizzes'; 2 | import console from 'console'; 3 | 4 | function findItems(items, searchTerm) { 5 | var matches = []; 6 | for (var i = 0; i < items.length; i++) { 7 | if (matchTag(items[i].tags, searchTerm)) { 8 | matches.push(items[i]); 9 | } 10 | } 11 | return matches; 12 | } 13 | 14 | //returns true if there is an element in a list of tags that matches a specific tag 15 | function matchTag(tags, tag) { 16 | for (var i = 0; i < tags.length; i++) { 17 | if (tag.toLowerCase() == tags[i].toLowerCase()) { 18 | return true; 19 | } 20 | } 21 | return false; 22 | } 23 | 24 | function buildQuestionsFromJson(quizJson) { 25 | var questions = []; 26 | for (var i = 0; i < quizJson.questions.length; i++) { 27 | questions.push(buildQuestionFromJson(quizJson.questions[i], i)); 28 | } 29 | return questions; 30 | } 31 | 32 | function buildCorrectAnswerText(question) { 33 | if (Array.isArray(question.answer)) { 34 | return question.answer[0]; 35 | } else if (typeof question.answer === 'number') { 36 | return question.options[question.answer]; 37 | } else if (typeof question.answer === 'string') { 38 | return question.answer; 39 | } 40 | } 41 | 42 | function buildQuestionFromJson(questionJson) { 43 | if (!questionJson) { 44 | return null; 45 | } 46 | var options = []; 47 | if (questionJson.options) { 48 | for (var i = 0; i < questionJson.options.length; i++) { 49 | options.push({ 50 | text: questionJson.options[i], 51 | alias: String.fromCharCode('A'.charCodeAt(0) + i), 52 | }); 53 | } 54 | } 55 | 56 | var acceptedAnswers = buildAcceptedAnswers(questionJson.answer, options); 57 | 58 | var question = { 59 | text: questionJson.question, 60 | options: options, 61 | correctAnswer: { 62 | acceptedAnswers: acceptedAnswers.answers, 63 | acceptedAlias: acceptedAnswers.alias, 64 | explanation: questionJson.explanation, 65 | text: buildCorrectAnswerText(questionJson), 66 | }, 67 | }; 68 | return question; 69 | } 70 | 71 | // 72 | export function buildQuestionToSpeak(question) { 73 | var options = question.options; 74 | var optionsString = ''; 75 | for (var i = 0; i < options.length; i++) { 76 | optionsString += 77 | options[i].alias + 78 | '... ' + 79 | options[i].text + 80 | (i + 1 < options.length ? ', ... ' : ''); 81 | } 82 | return optionsString; 83 | } 84 | 85 | function buildAcceptedAnswers(answer, options) { 86 | var acceptedAnswers = []; 87 | var alias; 88 | 89 | if (Array.isArray(answer)) { 90 | //is answer an array? 91 | for (var i = 0; i < answer.length; i++) { 92 | let acceptedAnswer = buildAcceptedAnswer(answer[i], options); 93 | acceptedAnswers = acceptedAnswers.concat(acceptedAnswer.answer); 94 | if (acceptedAnswer.alias) { 95 | alias = acceptedAnswer.alias; 96 | } 97 | } 98 | } else { 99 | let acceptedAnswer = buildAcceptedAnswer(answer, options); 100 | acceptedAnswers = acceptedAnswer.answer; 101 | if (acceptedAnswer.alias) { 102 | alias = acceptedAnswer.alias; 103 | } 104 | } 105 | return { 106 | answers: acceptedAnswers, 107 | alias: alias, 108 | }; 109 | } 110 | 111 | function buildAcceptedAnswer(answer, options) { 112 | var acceptedAnswer; 113 | var alias; 114 | if (typeof answer === 'number' && options && answer < options.length) { 115 | alias = options[answer].alias; 116 | acceptedAnswer = options[answer].text; 117 | } else if (answer) { 118 | acceptedAnswer = answer; 119 | //also find alias matching the answer 120 | if (options) { 121 | for (var i = 0; i < options.length; i++) { 122 | if (options[i].text == answer) { 123 | alias = options[i].alias; 124 | } 125 | } 126 | } 127 | } 128 | 129 | return { 130 | answer: acceptedAnswer, 131 | alias: alias, 132 | }; 133 | } 134 | 135 | export function buildQuizzes(searchTerm) { 136 | var quizzValue = quizzes; 137 | if (searchTerm) { 138 | const foundQuiz = findItems(quizzes, searchTerm); 139 | if (foundQuiz.length > 0) { 140 | quizzValue = foundQuiz; 141 | } 142 | } 143 | var formattedQuizzes = []; 144 | //read the questions in the quiz and initialize the state 145 | for (var i = 0; i < quizzValue.length; i++) { 146 | var quiz = quizzValue[i]; 147 | quiz.completed = false; 148 | quiz.score = 0; 149 | quiz.index = 0; 150 | var questions = buildQuestionsFromJson(quiz); 151 | quiz.textToSpeak = buildQuestionToSpeak(questions[0]); 152 | //cannot start a quiz without any questions 153 | if (!questions || !questions.length) { 154 | console.log('Chosen quiz has no questions!'); 155 | } else { 156 | quiz.questions = questions; 157 | formattedQuizzes.push(quiz); 158 | } 159 | } 160 | return formattedQuizzes; 161 | } 162 | 163 | export function arrayToListForSpeech(input) { 164 | var output = input.join(); 165 | output = output.replace(/,/g, '...'); 166 | output = 167 | output.substring(0, output.lastIndexOf('...')) + 168 | '... or ' + 169 | output.substring(output.lastIndexOf('...') + 3, output.length) + 170 | '...'; 171 | output = output.toUpperCase(); 172 | return output; 173 | } 174 | 175 | // ASR Aliases to handle ASR variations for "A", "B" "C" or "D" etc 176 | // This is a temporary workaround for an in progress bug that will be fixed 177 | const inputAliases = { 178 | a: 'a', 179 | hey: 'a', 180 | b: 'b', 181 | be: 'b', 182 | bee: 'b', 183 | c: 'c', 184 | see: 'c', 185 | sea: 'c', 186 | si: 'c', 187 | d: 'd', 188 | t: 'd', 189 | de: 'd', 190 | }; 191 | // Regular expresion to find ASR alias - creatd by ASRaliases const 192 | const aliasesRE = 193 | '^(' + 194 | String(Object.getOwnPropertyNames(inputAliases)) 195 | .replace('/"/g', '') 196 | .replace(/,/g, '|') + 197 | ')'; 198 | 199 | export function checkAnswerMatch(correctAnswers, answer) { 200 | var correct = false; 201 | correctAnswers.forEach((o) => { 202 | var lowerO = o.toLowerCase(); 203 | var corrAnsRegExp = '^' + lowerO + '$|' + aliasesRE + ' ' + lowerO + '$'; 204 | if (answer.match(corrAnsRegExp)) { 205 | correct = true; 206 | } 207 | }); 208 | return correct; 209 | } 210 | 211 | export function checkIncorrectOption(options, answer) { 212 | var answeredOption = false; 213 | var possOptionAlias = []; 214 | options.forEach((o) => { 215 | var lowerO = o.text.toLowerCase(); 216 | var optionsRegExp = 217 | lowerO + '| ' + lowerO + ' |^' + lowerO + ' | ' + lowerO + '$'; 218 | if (answer.match(optionsRegExp)) { 219 | answeredOption = true; 220 | } 221 | var x = o.alias.toLowerCase(); 222 | possOptionAlias.push(x); 223 | }); 224 | 225 | return { 226 | answeredOption: answeredOption, 227 | possOptionAlias: possOptionAlias, 228 | }; 229 | } 230 | 231 | export function checkAliasMatch(answer, possOptionAlias, correctAlias) { 232 | var answeredOption = false; 233 | var correct = false; 234 | var aliasMatch = inputAliases[answer]; 235 | if (aliasMatch) { 236 | if (possOptionAlias.indexOf(aliasMatch) > -1) { 237 | answeredOption = true; 238 | if (aliasMatch == correctAlias) { 239 | correct = true; 240 | } 241 | } 242 | } 243 | return { 244 | answeredOption: answeredOption, 245 | correct: correct, 246 | }; 247 | } 248 | -------------------------------------------------------------------------------- /contentUtility/CreateQuizContent.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bixbydevelopers/capsule-sample-quiz/51eb3d267edd3f9b866b55131f01f27a5bf74286/contentUtility/CreateQuizContent.xlsx -------------------------------------------------------------------------------- /models/actions/FindQuiz.model.bxb: -------------------------------------------------------------------------------- 1 | action (FindQuiz) { 2 | description (Finds the quiz the user specified. If no quiz specified, gives users all the quizzes so they can choose one.) 3 | type (Calculation) 4 | collect { 5 | input (searchTerm) { 6 | type (SearchTerm) 7 | min (Optional) max (One) 8 | description (Try to match a specific quiz) 9 | } 10 | } 11 | output (Quiz) 12 | } 13 | -------------------------------------------------------------------------------- /models/actions/StartQuiz.model.bxb: -------------------------------------------------------------------------------- 1 | action (StartQuiz) { 2 | description (Keep prompting the user for answers until quiz is completed.) 3 | type (Calculation) 4 | collect { 5 | input (quiz) { 6 | type (Quiz) 7 | min (Required) max (One) 8 | default-init { 9 | intent { 10 | goal: FindQuiz 11 | } 12 | } 13 | validate { 14 | if (!quiz.completed) { 15 | replan { 16 | intent { 17 | goal: UpdateQuiz 18 | value { $expr(quiz) } 19 | } 20 | } 21 | } 22 | } 23 | } 24 | } 25 | output (Quiz) 26 | } -------------------------------------------------------------------------------- /models/actions/UpdateQuiz.model.bxb: -------------------------------------------------------------------------------- 1 | action (UpdateQuiz) { 2 | type (Calculation) 3 | description (Compares the user's answer to the correct answer and updates your score and move to the next question) 4 | collect { 5 | input (answer) { 6 | type (Answer) 7 | min (Required) max (One) 8 | } 9 | input (quiz) { 10 | type (Quiz) 11 | min (Required) max (One) 12 | } 13 | } 14 | output (Quiz) 15 | } 16 | -------------------------------------------------------------------------------- /models/actions/utils/ExtractAnswerFromOption.model.bxb: -------------------------------------------------------------------------------- 1 | action (ExtractAnswerFromOption) { 2 | description (Convert an option - the option user tapped on - to a (text) answer) 3 | type (Constructor) 4 | collect { 5 | input (option) { 6 | type (Option) 7 | min (Required) max (One) 8 | } 9 | } 10 | output (Answer) 11 | } 12 | -------------------------------------------------------------------------------- /models/concepts/primitive/Answer.model.bxb: -------------------------------------------------------------------------------- 1 | text (Answer) { 2 | description (The user's answer) 3 | } 4 | -------------------------------------------------------------------------------- /models/concepts/primitive/SearchTerm.model.bxb: -------------------------------------------------------------------------------- 1 | text (SearchTerm) { 2 | description (User searching for a quiz with this term) 3 | } 4 | -------------------------------------------------------------------------------- /models/concepts/structures/CorrectAnswer.model.bxb: -------------------------------------------------------------------------------- 1 | structure (CorrectAnswer) { 2 | description (A structure to hold information on the correct answer) 3 | property (text) { 4 | description (The correct answer to display to the user.) 5 | type (core.Text) 6 | min (Required) max (One) 7 | visibility (Private) 8 | } 9 | property (acceptedAnswers) { 10 | description (additional acceptable answers. For example, Roosevelt, FDR, and Franklin Delano Roosevelt are all correct.) 11 | type (core.Text) 12 | min (Optional) max (Many) 13 | visibility (Private) 14 | } 15 | property (acceptedAlias) { 16 | description (Alias for correct answer) 17 | type (core.Text) 18 | min (Optional) max (Many) 19 | visibility (Private) 20 | } 21 | property (explanation) { 22 | description (a description of why this is the correct answer) 23 | type (core.Text) 24 | min (Optional) max (One) 25 | visibility (Private) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /models/concepts/structures/Option.model.bxb: -------------------------------------------------------------------------------- 1 | structure (Option) { 2 | description(An option presented to the user for a multi choice question) 3 | property (text) { 4 | description ("Text description of the option") 5 | type (Answer) 6 | min (Required) max (One) 7 | visibility (Private) 8 | } 9 | property (alias) { 10 | description("Alias used for the option (e.g. first option 'A', second option 'B', etc)") 11 | type (core.Text) 12 | min (Required) max (One) 13 | visibility (Private) 14 | } 15 | 16 | } -------------------------------------------------------------------------------- /models/concepts/structures/Question.model.bxb: -------------------------------------------------------------------------------- 1 | structure (Question) { 2 | description (A question in a quiz) 3 | property (text) { 4 | description(Question to be displayed) 5 | type (core.Text) 6 | min (Required) max (One) 7 | visibility (Private) 8 | } 9 | property (options) { 10 | description(Options provided to the user) 11 | type (Option) 12 | min (Optional) max (Many) 13 | visibility (Private) 14 | } 15 | property (correctAnswer) { 16 | description(Information on the correct answer) 17 | type (CorrectAnswer) 18 | min (Required) max (One) 19 | visibility (Private) 20 | } 21 | property (correct) { 22 | description (Whether the user got this right or not) 23 | type (core.Boolean) 24 | min (Optional) max (One) 25 | visibility (Private) 26 | } 27 | property (invalidAnswer) { 28 | description (Whether the last answer to this question was invalid) 29 | type (core.Boolean) 30 | min (Optional) max (One) 31 | visibility (Private) 32 | } 33 | property (answer) { 34 | description (What answer the user provided) 35 | type (Answer) 36 | min (Optional) max (One) 37 | visibility (Private) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /models/concepts/structures/Quiz.model.bxb: -------------------------------------------------------------------------------- 1 | structure (Quiz) { 2 | description (A whole Quiz structure) 3 | property (title) { 4 | type (core.Text) 5 | min (Required) max (One) 6 | visibility (Private) 7 | } 8 | property (tags) { 9 | description (Labels attached to this quiz to enable searching for the quiz by keyword) 10 | type (core.Text) 11 | min (Optional) max (Many) 12 | visibility (Private) 13 | } 14 | property (questions) { 15 | description (Questions in this quiz) 16 | type (Question) 17 | min (Required) max (Many) 18 | visibility (Private) 19 | } 20 | property (image) { 21 | description (Image describing this quiz) 22 | type (core.BaseImage) 23 | min (Optional) max (One) 24 | visibility (Private) 25 | } 26 | property (textToSpeak) { 27 | description(Question formatted for speech channel) 28 | type (core.Text) 29 | min (Optional) max (One) 30 | visibility (Private) 31 | } 32 | property (index) { 33 | description (Index of the current question) 34 | type (core.Integer) 35 | min (Required) max (One) 36 | } 37 | property (score) { 38 | description (User's score) 39 | type (core.Integer) 40 | min (Required) max (One) 41 | visibility (Private) 42 | } 43 | property (completed) { 44 | description (Toggles true when the quiz is completed) 45 | type (core.Boolean) 46 | min (Required) max (One) 47 | visibility (Private) 48 | } 49 | features{ 50 | transient 51 | } 52 | } -------------------------------------------------------------------------------- /resources/base/views/FindQuiz.view.bxb: -------------------------------------------------------------------------------- 1 | // See the FindQuiz.dialog file for localized strings for UI 2 | 3 | input-view { 4 | match { 5 | Quiz(quizzes) 6 | } 7 | 8 | message { 9 | template ("#{macro('QUIZ_SELECTION_QUESTION', quizzes)}") 10 | } 11 | 12 | render { 13 | if ($handsFree) { // If hands free mode, read out the quiz options and provide navigation guidance to the user 14 | selection-of (quizzes) { 15 | navigation-mode { 16 | read-many { 17 | list-summary { 18 | template ("#{macro('WELCOME_TO_QUIZ', quizzes)}") 19 | } 20 | page-content (page) { 21 | item-selection-question { 22 | if (isLastNavPage(page)) { 23 | template ("#{macro('ITEM-SELECTION-QUESTION', quizzes)}") 24 | } else { 25 | template ("#{macro('ITEM-SELECTION-QUESTION', quizzes)} #{macro('RESULT_NEXT_QUESTION')}") 26 | } 27 | } 28 | overflow-statement { 29 | template-macro (OVERFLOW_STATEMENT) 30 | } 31 | underflow-statement { 32 | template-macro (UNDERFLOW_STATEMENT) 33 | } 34 | page-marker { 35 | if (isLastNavPage(page) && !isFirstNavPage(page)){ 36 | if (size(page) == 1) { 37 | template ("#{macro('LAST_QUIZ')}") 38 | } else { 39 | template ("#{macro('LAST_QUIZZES', size(page))}") 40 | } 41 | } else-if (!isFirstNavPage(page)) { 42 | template ("#{macro('NEXT_QUIZZES', size(page))}") 43 | } 44 | } 45 | } 46 | page-size (3) 47 | } 48 | } 49 | where-each (quiz) { 50 | spoken-summary { 51 | template("#{macro('QUIZ_LIST_SUMMARY', quiz)}") 52 | } 53 | layout-macro (QuizDetails) { 54 | param (quiz) { expression (quiz) } 55 | } 56 | } 57 | } 58 | } 59 | else { 60 | selection-of (quizzes) { 61 | where-each (quiz) { 62 | layout-macro (QuizDetails) { 63 | param (quiz) { expression (quiz) } 64 | } 65 | } 66 | } 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /resources/base/views/QuizCompleted.view.bxb: -------------------------------------------------------------------------------- 1 | // See the UpdateQuiz.dialog for localized strings 2 | 3 | result-view{ 4 | match: Quiz(quiz){ 5 | from-output:StartQuiz(action) 6 | } 7 | 8 | message { 9 | if (action.quiz.questions[action.quiz.index].correct) { 10 | template ("#{macro('COMPLETED_LAST_CORRECT', action.quiz)}") { 11 | } 12 | } else { 13 | template ("#{macro('COMPLETED_LAST_INCORRECT', action.quiz)}") { 14 | } 15 | } 16 | } 17 | 18 | render{ 19 | layout-macro (QuizCompleted){ 20 | param (quiz){ 21 | expression (quiz) 22 | } 23 | } 24 | } 25 | conversation-drivers { 26 | conversation-driver { 27 | template-macro (ANOTHER_QUIZ) 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /resources/base/views/UpdateQuiz.view.bxb: -------------------------------------------------------------------------------- 1 | // See the UpdatQuiz.dialog for localized strings 2 | 3 | input-view { 4 | match: Answer(this) { 5 | to-input: UpdateQuiz(action) 6 | } 7 | 8 | message { 9 | if (action.quiz.index > 0) { // if not on first question 10 | if (action.quiz.questions[action.quiz.index].invalidAnswer) { // if could not understand last question answer 11 | template ("#{macro('INVALID_QUESTION_ANSWER', action.quiz)}") { 12 | } 13 | } 14 | else-if (action.quiz.questions[action.quiz.index-1].correct) { // if last question correct 15 | template ("#{macro('CORRECT_QUESTION_ANSWER', action.quiz)}") { 16 | } 17 | } else { 18 | template ("#{macro('INCORRECT_QUESTION_ANSWER', action.quiz)}") { 19 | } 20 | } 21 | } else { // first question 22 | if (action.quiz.questions[action.quiz.index].invalidAnswer) { // if could not understand last question answer 23 | template ("#{macro('INVALID_QUESTION_ANSWER', action.quiz)}") { 24 | } 25 | } else { 26 | template ("#{macro('FIRST_QUESTION', action.quiz)}") 27 | } 28 | 29 | 30 | } 31 | } 32 | 33 | render { 34 | if (size(action.quiz.questions[action.quiz.index].options) > 0) { 35 | selection-of (action.quiz.questions[action.quiz.index].options) { 36 | where-each (option) { 37 | cell-card { 38 | slot2 { 39 | content { 40 | primary { 41 | template ("[#{value(option.alias)}. ]#{value(option.text)}") 42 | } 43 | } 44 | } 45 | } 46 | } 47 | } 48 | } else { 49 | form { 50 | elements { 51 | text-input { 52 | id (answer) 53 | type (Answer) 54 | } 55 | } 56 | on-submit { 57 | goal: Answer 58 | value: viv.core.FormElement(answer) 59 | } 60 | } 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /resources/base/views/macros/QuizCompleted.layout.bxb: -------------------------------------------------------------------------------- 1 | // See the LayoutMacros.dialog for localized strings 2 | 3 | layout-macro-def (QuizCompleted){ 4 | params { 5 | param (quiz) { 6 | type (Quiz) 7 | min (Optional) 8 | max (Many) 9 | } 10 | } 11 | content{ 12 | section{ 13 | content{ 14 | title-area { 15 | slot1 { 16 | text { 17 | style(Title_M) 18 | value("#{macro('QUESTION_REVIEW')}:") 19 | } 20 | } 21 | } 22 | for-each (quiz.questions){ 23 | as (question) { 24 | paragraph { 25 | style (Title_S) 26 | value ("#{macro('QUESTION')}: #{value(question.text)}") 27 | } 28 | cell-area { 29 | slot1 { 30 | image { 31 | shape (Circle) 32 | url { 33 | if (exists(question.correct) && question.correct == true) { 34 | template ("/icons/correct.png") 35 | } else { 36 | template ("/icons/incorrect.png") 37 | } 38 | } 39 | } 40 | } 41 | slot2 { 42 | content { 43 | order (PrimarySecondary) 44 | primary { 45 | if (exists(question.answer)) { 46 | template ("'#{title(question.answer)}'") 47 | } 48 | } 49 | } 50 | } 51 | } 52 | if (exists(question.correct) && question.correct == false) { 53 | paragraph { 54 | style (Detail_L) 55 | value { 56 | template ("#{macro('CORRECT_ANSWER')}: '#{value(question.correctAnswer[0].text)}'[ - #{value(question.correctAnswer.explanation)}]") 57 | } 58 | } 59 | } 60 | paragraph { 61 | value { 62 | template ("") 63 | } 64 | } 65 | divider 66 | } 67 | } 68 | } 69 | } 70 | } 71 | } -------------------------------------------------------------------------------- /resources/base/views/macros/QuizDetails.layout.bxb: -------------------------------------------------------------------------------- 1 | //See the LayoutMacros.dialog for localized strings 2 | 3 | layout-macro-def(QuizDetails) { 4 | params { 5 | param (quiz) { 6 | type (Quiz) 7 | min (Required) 8 | max (One) 9 | } 10 | } 11 | content { 12 | cell-card { 13 | slot1 { 14 | if (exists(quiz.image.url)) { 15 | image { 16 | shape (Square) 17 | url { 18 | template ("#{value(quiz.image.url)}") 19 | } 20 | } 21 | } 22 | } 23 | slot2 { 24 | content { 25 | primary { 26 | template ("#{value(quiz.title)}") 27 | } 28 | secondary { 29 | template ("#{number(size(quiz.questions))} #{macro('QUESTIONS')}") 30 | } 31 | } 32 | } 33 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /resources/en/capsule-info.bxb: -------------------------------------------------------------------------------- 1 | capsule-info { 2 | display-name (Example Quiz) 3 | developer-name (Viv Labs, Inc.) 4 | icon-asset (icons/correct.png) 5 | description ("An example capsule that demonstrates how to build a quiz") 6 | website-url (https://bixbydevelopers.com/) 7 | search-keywords{ 8 | keyword (quiz) 9 | } 10 | dispatch-name (Example Quiz) 11 | } -------------------------------------------------------------------------------- /resources/en/dialogs/NoQuiz.dialog.bxb: -------------------------------------------------------------------------------- 1 | dialog (NoResult) { 2 | match: Quiz { 3 | from-output: FindQuiz(action) 4 | } 5 | template("I didn't find any quiz[ matching #{value(action.searchTerm)}].") 6 | } 7 | -------------------------------------------------------------------------------- /resources/en/dialogs/macros/FindQuiz.dialog.bxb: -------------------------------------------------------------------------------- 1 | // Localized strings used by FindQuiz view 2 | 3 | template-macro-def (WELCOME_TO_QUIZ) { 4 | params { 5 | param (quizzes) { 6 | type (Quiz) 7 | min (Required) 8 | max (One) 9 | } 10 | } 11 | content { 12 | template ("Welcome to Bixby Quiz. There are #{size(quizzes)} quizzes available") 13 | } 14 | } 15 | 16 | template-macro-def (QUIZ_SELECTION_QUESTION) { 17 | params { 18 | param (quizzes) { 19 | type (Quiz) 20 | min (Required) 21 | max (One) 22 | } 23 | } 24 | content { 25 | choose (Random) { 26 | template ("Welcome to Bixby Quiz. Which quiz would you like to take?") 27 | template ("Welcome to Bixby Quiz. Which of these #{size(quizzes)} quizzes would you like to take?") 28 | } 29 | } 30 | } 31 | 32 | template-macro-def (ITEM-SELECTION-QUESTION) { 33 | params { 34 | param (quizzes) { 35 | type (Quiz) 36 | min (Required) 37 | max (One) 38 | } 39 | } 40 | content { 41 | choose (Random) { 42 | template ("Which quiz would you like to take?") 43 | template ("Which quiz sounds good to you?") 44 | } 45 | } 46 | } 47 | 48 | template-macro-def (OVERFLOW_STATEMENT) { 49 | content { 50 | template ("That's all the quizzes I have.") 51 | } 52 | } 53 | 54 | template-macro-def (UNDERFLOW_STATEMENT) { 55 | content { 56 | template ("This is the first list of quizzes.") 57 | } 58 | } 59 | 60 | template-macro-def (RESULT_NEXT_QUESTION) { 61 | content { 62 | template ("Or say next for more quizzes.") 63 | } 64 | } 65 | 66 | template-macro-def (NEXT_QUIZZES) { 67 | params { 68 | param (pageSize) { 69 | type (core.Integer) 70 | min (Required) 71 | max (One) 72 | } 73 | } 74 | content { 75 | template ("The next #{pageSize} quizzes are") 76 | } 77 | } 78 | 79 | template-macro-def (LAST_QUIZZES) { 80 | params { 81 | param (pageSize) { 82 | type (core.Integer) 83 | min (Required) 84 | max (One) 85 | } 86 | } 87 | content { 88 | template ("The last #{pageSize} quizzes are") 89 | } 90 | } 91 | 92 | template-macro-def (LAST_QUIZ) { 93 | content { 94 | template ("The last quiz is") 95 | } 96 | } 97 | 98 | 99 | template-macro-def (QUIZ_LIST_SUMMARY) { 100 | params { 101 | param (quizzes) { 102 | type (Quiz) 103 | min (Required) max (Many) 104 | } 105 | } 106 | content { 107 | template("#{value(quizzes.title)} has #{size(quizzes.questions)} questions.") 108 | } 109 | } -------------------------------------------------------------------------------- /resources/en/dialogs/macros/LayoutMacros.dialog.bxb: -------------------------------------------------------------------------------- 1 | // Misc localized dialogs used by layout macros 2 | 3 | template-macro-def (ANOTHER_QUIZ) { 4 | content: template ("Take another quiz") 5 | } 6 | 7 | template-macro-def (QUESTIONS) { 8 | content: template ("Questions") 9 | } 10 | 11 | template-macro-def (QUESTION) { 12 | content: template ("Question") 13 | } 14 | 15 | template-macro-def (QUESTION_REVIEW) { 16 | content: template ("Question Review") 17 | } 18 | 19 | template-macro-def (CORRECT_ANSWER) { 20 | content: template ("Correct answer") 21 | } 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /resources/en/dialogs/macros/UpdateQuiz.dialog.bxb: -------------------------------------------------------------------------------- 1 | // Localized strings used by UpdateQuiz view 2 | 3 | template-macro-def (FIRST_QUESTION) { 4 | params { 5 | param (quiz) { 6 | type (Quiz) 7 | min (Required) 8 | max (One) 9 | } 10 | } 11 | content { 12 | template ("#{value(quiz.questions[quiz.index].text)}") { 13 | speech ("#{value(quiz.questions[quiz.index].text)}... [#{value(quiz.textToSpeak)}]") 14 | } 15 | } 16 | } 17 | 18 | template-macro-def (CORRECT_QUESTION_ANSWER) { 19 | params { 20 | param (quiz) { 21 | type (Quiz) 22 | min (Required) 23 | max (One) 24 | } 25 | } 26 | content { 27 | template ("Correct. #{value(quiz.questions[quiz.index].text)}") { 28 | speech ("Correct... #{value(quiz.questions[quiz.index].text)}... [#{value(quiz.textToSpeak)}]") 29 | } 30 | } 31 | } 32 | 33 | template-macro-def (INCORRECT_QUESTION_ANSWER) { 34 | params { 35 | param (quiz) { 36 | type (Quiz) 37 | min (Required) 38 | max (One) 39 | } 40 | } 41 | content { 42 | template ("Incorrect. The correct answer is #{value(quiz.questions[quiz.index-1].correctAnswer.text)}. #{value(quiz.questions[quiz.index].text)}") { 43 | speech ("Incorrect... The correct answer is #{value(quiz.questions[quiz.index-1].correctAnswer.text)}... #{value(quiz.questions[quiz.index].text)}... [#{value(quiz.textToSpeak)}]") 44 | } 45 | } 46 | } 47 | 48 | template-macro-def (INVALID_QUESTION_ANSWER) { 49 | params { 50 | param (quiz) { 51 | type (Quiz) 52 | min (Required) 53 | max (One) 54 | } 55 | } 56 | content { 57 | template ("Sorry, I didn't understand. #{value(quiz.questions[quiz.index].text)}") { 58 | speech ("Sorry...I didn't understand... #{value(quiz.questions[quiz.index].text)}... [#{value(quiz.textToSpeak)}]") 59 | } 60 | } 61 | } 62 | 63 | template-macro-def (COMPLETED_LAST_CORRECT) { 64 | params { 65 | param (quiz) { 66 | type (Quiz) 67 | min (Required) 68 | max (One) 69 | } 70 | } 71 | content { 72 | template ("Correct. You got #{value(quiz.score)} out of #{size(quiz.questions)} right!") { 73 | speech ("Correct... You got #{value(quiz.score)} out of #{size(quiz.questions)} right!") 74 | } 75 | } 76 | } 77 | 78 | template-macro-def (COMPLETED_LAST_INCORRECT) { 79 | params { 80 | param (quiz) { 81 | type (Quiz) 82 | min (Required) 83 | max (One) 84 | } 85 | } 86 | content { 87 | template ("Incorrect. The correct answer is #{value(quiz.questions[quiz.index].correctAnswer.text)}. You got #{value(quiz.score)} out of #{size(quiz.questions)} right!") { 88 | speech ("Incorrect... The correct answer is #{value(quiz.questions[quiz.index].correctAnswer.text)}... You got #{value(quiz.score)} out of #{size(quiz.questions)} right!") 89 | } 90 | } 91 | } 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /resources/en/example-quiz.hints.bxb: -------------------------------------------------------------------------------- 1 | hints { 2 | uncategorized { 3 | hint (With Example Quiz, start a quiz) 4 | hint (In Example Quiz, start the president quiz) 5 | hint (In Example Quiz, start the funny quiz) 6 | } 7 | } -------------------------------------------------------------------------------- /resources/en/quiz.endpoints.bxb: -------------------------------------------------------------------------------- 1 | endpoints { 2 | action-endpoints { 3 | action-endpoint (FindQuiz) { 4 | accepted-inputs (searchTerm) 5 | local-endpoint (FindQuiz.js) 6 | } 7 | action-endpoint (StartQuiz) { 8 | accepted-inputs (quiz) 9 | local-endpoint (StartQuiz.js) 10 | } 11 | action-endpoint (UpdateQuiz) { 12 | accepted-inputs (quiz, answer) 13 | local-endpoint (UpdateQuiz.js) 14 | } 15 | action-endpoint (ExtractAnswerFromOption) { 16 | accepted-inputs (option) 17 | local-endpoint (ExtractAnswerFromOption.js) 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /resources/en/training/t-4.training.bxb: -------------------------------------------------------------------------------- 1 | train (t-4vwgbhq5kwk84it2qngh0vwj9) { 2 | utterance ("[g:Quiz:prompt] the (funny)[v:SearchTerm] one") 3 | plan (Y2BmEEytSMwtyEnVKyzNrNILBBIMoihCbpl5KWBhcRTh4NTEouSMkNSiXAYmBkYgZgCSjAwgwATBAA==) 4 | last-modified (1569982429459) 5 | } -------------------------------------------------------------------------------- /resources/en/training/t-5.training.bxb: -------------------------------------------------------------------------------- 1 | train (t-5as44ztbuhebtyy97sicyiun2) { 2 | utterance ("[g:StartQuiz] start the (president)[v:SearchTerm] quiz") 3 | plan (Y2BlEEytSMwtyEnVKyzNrNILBBIMYihCwSWJRSVgcSxKRVGE3DLzUsDC4qgmpCYWJWeEpBblMrAwMDIwMTADaQYoixHMAgEmIA0UBwA=) 4 | last-modified (1569982429784) 5 | } -------------------------------------------------------------------------------- /resources/en/training/t-7.training.bxb: -------------------------------------------------------------------------------- 1 | train (t-74ylfgy64fg78k7y5z4ck27v6) { 2 | utterance ("[g:Quiz:prompt] (foo bar)[v:SearchTerm]") 3 | plan (Y2BmEEytSMwtyEnVKyzNrNILBBIMoihCbpl5KWBhcRTh4NTEouSMkNSiXAYmBkYgZgCSjAwgwATBAA==) 4 | last-modified (1569982429575) 5 | } 6 | train (t-7vj6xlz29ebf97j8a04ej4uxk) { 7 | utterance ([g:StartQuiz] quiz) 8 | plan (Y2BmEEytSMwtyEnVKyzNrNILBBIMYihCwSWJRSVgcSxKmRgYgZgBSDIyMMBoAA==) 9 | last-modified (1569982429700) 10 | } -------------------------------------------------------------------------------- /resources/en/training/t-8.training.bxb: -------------------------------------------------------------------------------- 1 | train (t-815a9m07q9nskqvmnd80p8x4p) { 2 | utterance ("[g:StartQuiz] give me a (funny)[v:SearchTerm] quiz") 3 | plan (Y2BlEEytSMwtyEnVKyzNrNILBBIMYihCwSWJRSVgcSxKRVGE3DLzUsDC4qgmpCYWJWeEpBblMrAwMDIwMTADaQYoixHMAgEmIA0UBwA=) 4 | last-modified (1569982429865) 5 | } -------------------------------------------------------------------------------- /resources/en/training/t-a.training.bxb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bixbydevelopers/capsule-sample-quiz/51eb3d267edd3f9b866b55131f01f27a5bf74286/resources/en/training/t-a.training.bxb -------------------------------------------------------------------------------- /resources/en/training/t-h.training.bxb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bixbydevelopers/capsule-sample-quiz/51eb3d267edd3f9b866b55131f01f27a5bf74286/resources/en/training/t-h.training.bxb -------------------------------------------------------------------------------- /resources/en/training/t-i.training.bxb: -------------------------------------------------------------------------------- 1 | train (t-izysb78007qgj4ijcyn57p94b) { 2 | utterance ("[g:Quiz:prompt] give me the (funny)[v:SearchTerm] quiz") 3 | plan (Y2BmEEytSMwtyEnVKyzNrNILBBIMoihCbpl5KWBhcRTh4NTEouSMkNSiXAYmBkYgZgCSjAwgwATBAA==) 4 | last-modified (1569982430132) 5 | } -------------------------------------------------------------------------------- /resources/en/training/t-p.training.bxb: -------------------------------------------------------------------------------- 1 | train (t-pz8x5ohru6pbxz2ujn1khw440) { 2 | utterance ("[g:Quiz:prompt] (funny)[v:SearchTerm] quiz") 3 | plan (Y2BmEEytSMwtyEnVKyzNrNILBBIMoihCbpl5KWBhcRTh4NTEouSMkNSiXAYmBkYgZgCSjAwgwATBAA==) 4 | last-modified (1569982429973) 5 | } -------------------------------------------------------------------------------- /resources/en/training/t-q.training.bxb: -------------------------------------------------------------------------------- 1 | train (t-qcwihhq2mqxtic76jjlue9wfq) { 2 | utterance ([g:StartQuiz] Take another quiz) 3 | last-modified (1598465149954) 4 | } -------------------------------------------------------------------------------- /resources/en/training/t-r.training.bxb: -------------------------------------------------------------------------------- 1 | train (t-r3icl2t4r8bxhcrhfesszye75) { 2 | utterance ([g:StartQuiz] start) 3 | plan (Y2BmEEytSMwtyEnVKyzNrNILBBIMYihCwSWJRSVgcSxKmRgYgZgBSDIyMMBoAA==) 4 | last-modified (1569982430029) 5 | } 6 | train (t-rhqrvtq43qwp4v4gmvqt6avk6) { 7 | utterance ("[g:Answer:prompt] (foo bar foo bar)[v:Answer]") 8 | plan (Y2BiEE6tSMwtyEnVKyzNrNJzzCsuTy3CLsgIhAwMPEDMBMRANgA=) 9 | last-modified (1569982430081) 10 | } -------------------------------------------------------------------------------- /resources/en/training/t-x.training.bxb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bixbydevelopers/capsule-sample-quiz/51eb3d267edd3f9b866b55131f01f27a5bf74286/resources/en/training/t-x.training.bxb --------------------------------------------------------------------------------