├── .prettierrc ├── LICENSE ├── README.md ├── castle.png ├── cats-of-jasnah.css ├── cats-of-jasnah.js ├── cats.png ├── dog.png ├── index.html ├── levels.js ├── pig.png ├── raindough.png ├── tree.png └── trophy.png /.prettierrc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/countable/cats-of-jasnah/90c56df1be1107b81d8a0c1028f30aae5dfc04de/.prettierrc -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Clark Van Oyen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cats-of-jasnah 2 | Cats of Jasnah, a web based game for learning categories and logic 3 | 4 | I made this game with my 3 year old to develop logical language skills. It progresses from counting up to increasingly difficult word problems using boolean logic. This seems like an effective path for her to learn at her current level of development and I couldn't find something like this elsewhere. 5 | 6 | Enjoy! 7 | 8 | [Play the game](https://countable.github.io/cats-of-jasnah) 9 | 10 | ![cats of jasnah](./cats.png) 11 | 12 | ### References 13 | 14 | Thanks for all the feedback, folks from [Hacker News!](https://news.ycombinator.com/item?id=21880446#21886290) 15 | 16 | [The Game of Logic, Lewis Carroll](https://www.gutenberg.org/files/4763/4763-h/4763-h.htm) 17 | 18 | A work by [RainDough](https://countable.github.io/raindough/) 19 | -------------------------------------------------------------------------------- /castle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/countable/cats-of-jasnah/90c56df1be1107b81d8a0c1028f30aae5dfc04de/castle.png -------------------------------------------------------------------------------- /cats-of-jasnah.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: #ffddee; 3 | font-family: Helvetica, Arial, sans-serif; 4 | font-size: 15px; 5 | margin: 0; 6 | text-align: center; 7 | overflow: hidden; 8 | } 9 | 10 | html, 11 | body { 12 | height: 100%; 13 | } 14 | .github { 15 | position: absolute; 16 | display: block; 17 | bottom: 0; 18 | right: 0; 19 | z-index: 111; 20 | font-size: 2vmin; 21 | } 22 | p { 23 | font-size: 22px; 24 | margin: 0; 25 | padding: 1em 0 0 0; 26 | } 27 | .trophy { 28 | position:fixed; 29 | display:none; 30 | top: 50%; 31 | height: 30vmax; 32 | left: 50%; 33 | margin-left:-15vmax; 34 | margin-top:-20vmax; 35 | z-index: 10000; 36 | box-shadow: 10px 10px 50px 50px rgba(50,0,0,0.2); 37 | } 38 | .instructions { 39 | position: relative; 40 | z-index: 10000; 41 | padding: 25px 25px; 42 | } 43 | .coj-btn { 44 | align-items: center; 45 | background-color: #ffeef8; 46 | border: 1px solid transparent; 47 | box-shadow: 1px 1px 5px rgba(0, 0, 0, 0.3); 48 | color: #000; 49 | cursor: pointer; 50 | display: inline-flex; 51 | font-size: 1.5rem; 52 | height: 40px; 53 | justify-content: center; 54 | margin: 4px 5px; 55 | outline: 0; 56 | padding: 0; 57 | text-align: center; 58 | -moz-user-select: none; 59 | -ms-user-select: none; 60 | -webkit-user-select: none; 61 | user-select: none; 62 | vertical-align: middle; 63 | width: calc(16.66667% - 10px); 64 | } 65 | 66 | .coj-btn:focus { 67 | background-color: #b3cdff; 68 | } 69 | 70 | .spinning { 71 | animation: 2s spin infinite; 72 | } 73 | 74 | .hidden { 75 | display: none; 76 | } 77 | .ghost { 78 | opacity: 0.5; 79 | } 80 | 81 | svg .face { 82 | fill: white; 83 | } 84 | 85 | .smile, 86 | .frown { 87 | display: none; 88 | } 89 | 90 | .ducks .smile, 91 | .ducks .frown { 92 | display: inline; 93 | stroke: none; 94 | fill: orange; 95 | } 96 | 97 | .mouth { 98 | stroke: black; 99 | } 100 | 101 | .red .face { 102 | fill: red; 103 | } 104 | 105 | .blue .face { 106 | fill: #8888ff; 107 | } 108 | 109 | .yellow .face { 110 | fill: yellow; 111 | } 112 | 113 | .purple .face { 114 | fill: #aa66ff; 115 | } 116 | 117 | @keyframes spin { 118 | 0% { 119 | transform: translate(0, 0) rotate(0deg); 120 | } 121 | 50% { 122 | transform: translate(1em, -1em) rotate(360deg); 123 | } 124 | 100% { 125 | transform: translate(0, 0) rotate(0deg); 126 | } 127 | } 128 | 129 | .bouncing { 130 | animation: bounce 2s infinite; 131 | animation-timing-function: cubic-bezier(0.28, 0.84, 0.42, 1); 132 | } 133 | 134 | @keyframes bounce { 135 | 0% { 136 | transform: scale(1, 1) translateY(0); 137 | } 138 | 10% { 139 | transform: scale(1.1, 0.9) translateY(0); 140 | } 141 | 30% { 142 | transform: scale(0.9, 1.1) translateY(-50px); 143 | } 144 | 50% { 145 | transform: scale(1.05, 0.95) translateY(0); 146 | } 147 | 57% { 148 | transform: scale(1, 1) translateY(-5px); 149 | } 150 | 64% { 151 | transform: scale(1, 1) translateY(0); 152 | } 153 | 100% { 154 | transform: scale(1, 1) translateY(0); 155 | } 156 | } 157 | 158 | h1 { 159 | font-size: 2em; 160 | font-variant: small-caps; 161 | text-align: center; 162 | text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.5); 163 | color: white; 164 | margin: 0; 165 | cursor: pointer; 166 | } 167 | 168 | .back, 169 | .forward { 170 | font-size: 25px; 171 | position: fixed; 172 | top: 0; 173 | width: 25px; 174 | cursor: pointer; 175 | } 176 | 177 | .back { 178 | left: 0; 179 | } 180 | 181 | .forward { 182 | right: 0; 183 | } 184 | 185 | .level-display { 186 | box-shadow: 1px 1px 5px rgba(0, 0, 0, 0.3); 187 | background: #ffeef8; 188 | padding: 5px; 189 | margin: 0; 190 | text-align: center; 191 | display: none; 192 | } 193 | 194 | .number-bar { 195 | position: fixed; 196 | bottom: 1rem; 197 | left: 0; 198 | right: 0; 199 | display: none; 200 | } 201 | 202 | .number-bar .desc { 203 | display: none; 204 | } 205 | 206 | li { 207 | padding: 10px 5px; 208 | list-style: none; 209 | cursor: pointer; 210 | margin: 7px; 211 | box-shadow: 1px 1px 5px rgba(0, 0, 0, 0.3); 212 | border-radius: 5px; 213 | background: #ffeef8; 214 | } 215 | 216 | ul { 217 | margin: 0; 218 | padding: 0; 219 | float:left; 220 | } 221 | 222 | svg { 223 | height: 14vmax; 224 | width: 14vmax; 225 | } 226 | #logo { 227 | max-width: 100% 228 | } 229 | .splash { 230 | top: 50%; 231 | left: 50%; 232 | z-index: 100; 233 | position: fixed; 234 | transform: translate(-50%, -50%) scale(10); 235 | } 236 | @keyframes example { 237 | from {transform: translate(-50%, -50%) scale(5);} 238 | to {transform: translate(-50%, -50%) scale(10);} 239 | } 240 | .topics { 241 | display: flex; 242 | } 243 | .topics ul { 244 | flex: 1; 245 | } 246 | .yay { 247 | animation-name: example; 248 | animation-duration: 2s; 249 | } 250 | 251 | @media (min-width: 768px) { 252 | .coj-btn { 253 | height: 60px; 254 | width: 60px; 255 | } 256 | 257 | .number-bar .desc { 258 | display: block; 259 | font-size: 14px; 260 | opacity: 0.7; 261 | padding-top: 0.25rem; 262 | } 263 | } 264 | 265 | /*some vmax/vh elements get crazy at hi-res, cap their size.*/ 266 | @media screen and (min-width: 1024px) { 267 | svg { 268 | height: 160px; 269 | width: 140px; 270 | } 271 | } 272 | 273 | #forkongithub a { 274 | background: #000; 275 | color: #fff; 276 | text-decoration: none; 277 | font-family: arial, sans-serif; 278 | text-align: center; 279 | font-weight: bold; 280 | padding: 5px 40px; 281 | font-size: 1rem; 282 | line-height: 2rem; 283 | position: relative; 284 | transition: 0.5s; 285 | } 286 | #forkongithub a:hover { 287 | background: #c11; 288 | color: #fff; 289 | } 290 | #forkongithub a::before, 291 | #forkongithub a::after { 292 | content: ''; 293 | width: 100%; 294 | display: block; 295 | position: absolute; 296 | top: 1px; 297 | left: 0; 298 | height: 1px; 299 | background: #fff; 300 | } 301 | #forkongithub a::after { 302 | bottom: 1px; 303 | top: auto; 304 | } 305 | @media screen and (min-width: 800px) { 306 | #forkongithub { 307 | position: absolute; 308 | display: block; 309 | top: 0; 310 | right: 0; 311 | width: 200px; 312 | overflow: hidden; 313 | height: 200px; 314 | z-index: 9999; 315 | } 316 | #forkongithub a { 317 | width: 200px; 318 | position: absolute; 319 | top: 60px; 320 | right: -60px; 321 | transform: rotate(45deg); 322 | -webkit-transform: rotate(45deg); 323 | -ms-transform: rotate(45deg); 324 | -moz-transform: rotate(45deg); 325 | -o-transform: rotate(45deg); 326 | box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.8); 327 | } 328 | } 329 | 330 | ellipse.face { 331 | stroke: #ff88dd; 332 | } 333 | .circle ellipse.face { 334 | stroke: lightgreen; 335 | stroke-width: 10px; 336 | } 337 | -------------------------------------------------------------------------------- /cats-of-jasnah.js: -------------------------------------------------------------------------------- 1 | const COLOR_ATTS = ['red', 'blue', 'yellow', 'purple'] 2 | const MOTION_ATTS = ['bouncing', 'spinning'] 3 | const ANIMAL_ATTS = ['ducks'] 4 | const ALL_ATTS = COLOR_ATTS.concat(MOTION_ATTS).concat(ANIMAL_ATTS) 5 | let ATTS 6 | let cur_atts = {} 7 | let clue 8 | let stage 9 | 10 | const pluralize_cat = function(count) { 11 | return Math.abs(count) > 1 ? 'cats' : 'cat'; 12 | }; 13 | 14 | const pick_rand = function(seq) { 15 | return seq[Math.floor(Math.random() * seq.length)] 16 | } 17 | const set_level = function(topic_num, stage_num) { 18 | // initialization 19 | $('#logo').hide() 20 | $('.instructions').hide() 21 | $('#forkongithub').hide() 22 | $('.number-bar').show() 23 | $('.level-display').show() 24 | 25 | console.log(topic_num, stage_num) 26 | if (stage_num >= TOPICS[topic_num].stages.length) { 27 | 28 | topic_num ++ 29 | if (topic_num >= TOPICS.length) { 30 | return alert('this is the highest level already.') 31 | } 32 | stage_num = 0 33 | } 34 | stage = {} 35 | stage.__proto__ = TOPICS[topic_num].stages[stage_num] 36 | 37 | $('.level-number').text( 38 | topic_num + '-' + stage_num + ' | ' + stage.name 39 | ) 40 | 41 | make_cats() 42 | } 43 | 44 | const permute_atts = function() { 45 | cur_atts = {} 46 | for (let i = 0; i < ATTS.length && i < stage.max_asked_atts; i++) { 47 | if (Math.random() < stage.att_chance) { 48 | cur_atts[ATTS[i]] = !stage.get_value('negation') 49 | } 50 | } 51 | } 52 | 53 | 54 | const speak = function(text, opts) { 55 | opts = opts || {} 56 | $('p.clue').html(text + "🔈") 57 | responsiveVoice.speak(text, 'US English Female', opts) 58 | } 59 | 60 | const draw_stars = function() { 61 | $('.stars').html(stage.get_stars() + '★') 62 | } 63 | 64 | var make_cats = function() { 65 | stage.init() 66 | draw_stars() 67 | stage.set_avail_atts() 68 | permute_atts() 69 | is_reversed = Math.random() < 0.5 70 | let text = 'how many ' 71 | const keys = Object.keys(cur_atts) 72 | const prefix_pos = stage.get_num_adjectives(keys) 73 | const prefix_keys = keys.slice(0, prefix_pos) 74 | if (prefix_keys.length) { 75 | let prefix_words = [] 76 | for (let att in prefix_keys) { 77 | prefix_words.push( 78 | (cur_atts[prefix_keys[att]] ? '' : 'non-') + prefix_keys[att] 79 | ) 80 | } 81 | text += prefix_words.join(', ') + ' ' 82 | // ducks is just 'duck' when used as an adjective 83 | text = text.replace('ducks', 'duck') 84 | } 85 | postfix_keys = keys.slice(prefix_pos) 86 | if (postfix_keys.length) { 87 | text += 'cats ' + stage.get_equality_operator() + ' ' 88 | 89 | let items = [] 90 | for (let att in postfix_keys) { 91 | items.push( 92 | (cur_atts[postfix_keys[att]] ? '' : 'not ') + postfix_keys[att] 93 | ) 94 | } 95 | text += items.join(' ' + stage.operator + ' are ') 96 | } else { 97 | text += 'cats ' + stage.get_equality_operator() + ' here' 98 | } 99 | if (stage.successor) { 100 | // alternate wording 101 | if (Math.random() > .5) { 102 | text += ' if we had ' + Math.abs(stage.successor) + ' ' 103 | + (stage.successor > 0 ? 'more ' : 'less ') 104 | + (ATTS.length ? ATTS[0] + ' ' + pluralize_cat(stage.successor): '') 105 | } else { 106 | text += ' if ' + Math.abs(stage.successor) + ' ' 107 | + (stage.successor > 0 108 | ? 'more ' + (ATTS.length ? ATTS[0] + ' ' + pluralize_cat(stage.successor) + ' ' : '') + 'came' 109 | : (ATTS.length ? ATTS[0] + ' ' + pluralize_cat(stage.successor) + ' ' : '') + 'went away') 110 | } 111 | } 112 | 113 | text += '?' 114 | 115 | // substitution. 116 | if (stage.get_value('substitution')) {1 117 | if (Math.random() < 0.5) { 118 | text = text.replace(/not.blue/, 'white') 119 | text = text.replace(/not.red/, 'white') 120 | text = text.replace(/not.yellow/, 'white') 121 | } 122 | } 123 | 124 | clue = text 125 | 126 | // remove existing cats and add new ones for the current level. 127 | $('svg:gt(0)').remove() 128 | num_cats = stage.num_cats() 129 | for (var i = 0; i < num_cats + stage.get_added_num(); i++) { 130 | $('svg') 131 | .eq(0) 132 | .clone() 133 | .appendTo('body') 134 | .each(function(svg) { 135 | const $t = $(this) 136 | $(this).removeClass('hidden') 137 | if (i >= num_cats) { 138 | $(this).addClass('hidden') 139 | } 140 | for (var att = 0; att < ATTS.length; att++) { 141 | if (i >= num_cats) { 142 | // for now set 1 (will chance base on gameplay) 143 | chance = 1; 144 | } else if (cur_atts[ATTS[att]] === true) { 145 | chance = stage.chance() 146 | } else if (cur_atts[ATTS[att]] === false) { 147 | chance = 1 - stage.chance() 148 | } else { 149 | chance = 0.5 150 | } 151 | if (Math.random() < chance) $t.addClass(ATTS[att]) 152 | } 153 | }) 154 | } 155 | console.log('num_cats rendered', num_cats + stage.get_added_num(), $('svg:gt(0)').length) 156 | const num_answer = get_answer().length 157 | if (num_answer > 9) { 158 | console.log('Too many cats, generate a new puzzle.') 159 | return make_cats() 160 | } else if (num_answer == 0 &! stage.min > 0) { 161 | console.log('not enough cats.') 162 | return make_cats() 163 | } 164 | 165 | speak(clue) 166 | } 167 | 168 | const next_level = function(){ 169 | let next_stage_num = stage.number + 1 170 | set_level(stage.topic.topic_number, next_stage_num) 171 | } 172 | const sound = function(s) { 173 | var snd = new Audio(s + '.mp3') 174 | snd.play() 175 | } 176 | 177 | $('body').keyup(function(e) { 178 | if (e.key === ' ') return next_level() 179 | if (!/\d/.test(e.key)) return 180 | submit(parseInt(e.key)) 181 | }) 182 | 183 | const get_answer = function() { 184 | let set = $('svg:gt(0)').filter(function(svg) { 185 | let match = stage.operator === 'and'; 186 | for (let att in cur_atts) { 187 | // consider negation 188 | let in_set = (cur_atts[att] ? $(this).hasClass(att) : !$(this).hasClass(att)); 189 | // conjunction / disjunction 190 | match = 191 | stage.operator === 'and' ? (match && in_set) : (match || in_set) 192 | 193 | } 194 | return match 195 | }) 196 | if (set.length + stage.get_added_num() < 0) { 197 | // return invalid answer to generate again 198 | return { length: 10 } 199 | } 200 | set = set.slice(0, set.length + stage.get_added_num()) 201 | console.log(set.length, 'answer size counted', set) 202 | return set 203 | } 204 | 205 | const submit = function(value) { 206 | let answer_set = get_answer() 207 | answer = answer_set.length 208 | console.log("COMPARING", value, 'to answer', answer) 209 | answer_set.addClass('circle') 210 | answer_set.filter('.hidden').addClass('ghost').removeClass('hidden') 211 | 212 | if (value === answer) { 213 | stage.add_star() 214 | //yay() 215 | let congrats = "That's right, " + answer + '.' 216 | if (stage.get_stars() == 5) { 217 | congrats += " You're on a Winning Streak!" 218 | $('.trophy').show() 219 | } 220 | speak(congrats, { 221 | onend: function() { 222 | /*if (stage.get_stars() == 5) { 223 | next_level() 224 | } else {*/ 225 | make_cats() 226 | 227 | $('.trophy').hide() 228 | //} 229 | } 230 | }) 231 | } else { 232 | speak("Good try, but that's wrong.", { 233 | onend: function() { 234 | stage.lose_stars() 235 | draw_stars() 236 | speak(clue) 237 | } 238 | }) 239 | } 240 | } 241 | 242 | const n_str = function(n, s) { 243 | if (n < 2) return s 244 | return s + n_str(n-1, s) 245 | } 246 | 247 | const yay = function() { 248 | $(".splash").html(n_str(stage.get_stars(), '★')).show().addClass('yay') 249 | setTimeout(function(){ 250 | $('.splash').hide().removeClass('yay') 251 | }, 2000); 252 | } 253 | -------------------------------------------------------------------------------- /cats.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/countable/cats-of-jasnah/90c56df1be1107b81d8a0c1028f30aae5dfc04de/cats.png -------------------------------------------------------------------------------- /dog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/countable/cats-of-jasnah/90c56df1be1107b81d8a0c1028f30aae5dfc04de/dog.png -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Cats of Jasnah. Teach your 4-year-old logic 5 | 6 | 7 | 8 | 9 | 10 |
11 |
12 | → 13 |
14 |

Cats of Jasnah

15 | 16 |
LEVEL: |
17 | 18 |
19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 |
32 | To answer questions, tap the numbers above or just use your keyboard. 33 |
34 |
35 |

36 | 37 |
38 | by RainDough
39 | GitHub 40 |
41 | 42 |
43 | Instructions:

44 | 45 | A logic game for young children. 46 | Warning: the game will speak out loud (since young kids cannot read the 48 | questions), so check your volume. 50 | Choose a level to start.

51 |
52 |
53 |
54 |
55 | 101 | 102 | 103 | 108 | 109 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /levels.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | const Level = { 4 | init: function(){ 5 | if (this.addition > 0) { 6 | this.successor = Math.floor(Math.random() * (this.addition) + 1) 7 | } else if (this.addition < 0) { // range from negative 1 to negative N 8 | this.successor = Math.floor(Math.random() * (this.addition * -1) + 1) * -1 9 | } 10 | }, 11 | num_cats: function(){ 12 | return Math.floor(Math.random() * (this.max + 1 - this.min)) + this.min 13 | }, 14 | get_value: function(key) { 15 | // fetch a value in the level using rich 16 | // specifiers, like a list of possible values 17 | let value = this[key] 18 | if (Array.isArray(value)) { 19 | value = value[Math.floor(Math.random()*value.length)] 20 | } 21 | return value 22 | }, 23 | chance: function() { 24 | if (this.operator === 'or') return 0.6 - 0.1 * this.num_atts; 25 | else return 0.4 + 0.1 * this.num_atts; 26 | }, 27 | min: 0, 28 | max: 9, 29 | operator: 'and', 30 | get_num_adjectives: function(keys) { // no semantic difference, just use adjectives in sentence structure. 31 | if (this.num_adjectives !== null) return this.num_adjectives 32 | return Math.floor(Math.random()*keys.length) 33 | }, 34 | num_adjectives: null, // by default, it's random. 35 | 36 | max_asked_atts: 99, // no more than this many atts asked. normally don't set. 37 | 38 | num_atts: 3, // attributes to apply to cats 39 | att_chance: 1, 40 | set_avail_atts: function() { 41 | ATTS = [ 42 | pick_rand(COLOR_ATTS), 43 | pick_rand(MOTION_ATTS), 44 | pick_rand(ANIMAL_ATTS) 45 | ].slice(0,this.num_atts) 46 | }, 47 | negation: false, 48 | get_stars: function() { 49 | return parseInt(localStorage[this.get_key()] || '0') 50 | }, 51 | add_star: function() { 52 | localStorage[this.get_key()] = Math.min(this.get_stars() + 1, 5) 53 | }, 54 | get_key: function() { 55 | return this.name.replace(/\s/g, '-') 56 | }, 57 | lose_stars: function() { 58 | localStorage[this.get_key()] = 0 59 | }, 60 | get_equality_operator: function() { 61 | if (this.successor) { 62 | return 'would be' 63 | } else { 64 | return 'are' 65 | } 66 | }, 67 | get_added_num: function() { 68 | if (this.successor) { 69 | return this.successor 70 | } else { 71 | return 0 72 | } 73 | } 74 | } 75 | 76 | 77 | const TOPICS = [ 78 | { 79 | name: 'counting', 80 | stages: [ 81 | { 82 | name: 'little cardinals', 83 | min: 1, 84 | max: 3, 85 | max_asked_atts: 0, 86 | num_atts: 0 87 | }, 88 | { 89 | name: 'big cardinals', 90 | min: 4, 91 | max_asked_atts: 0, 92 | num_atts: 0 93 | }, 94 | { 95 | name: 'distraction', 96 | min: 1, 97 | max: 7, 98 | max_asked_atts: 0, 99 | num_atts: 1 100 | }, 101 | { 102 | name: 'big distraction', 103 | min: 1, 104 | max_asked_atts: 0, 105 | num_atts: 3 106 | }, 107 | { 108 | name: 'with zero', 109 | max: 2, 110 | max_asked_atts: 0, 111 | num_atts: 3 112 | }, 113 | { 114 | name: 'counting mastery', 115 | num_atts: 3, 116 | max_asked_atts: 0 117 | } 118 | ] 119 | }, 120 | { 121 | name: 'subsets', 122 | stages: [ 123 | { 124 | name: 'little subset', 125 | min: 2, 126 | max: 3, 127 | max_asked_atts: 1, 128 | num_atts: 1, 129 | num_adjectives: 0 130 | }, 131 | { 132 | name: 'subset', 133 | min: 4, 134 | max: 5, 135 | max_asked_atts: 1, 136 | num_atts: 1, 137 | num_adjectives: 0 138 | }, 139 | { 140 | name: 'big subset', 141 | min: 4, 142 | max: 7, 143 | max_asked_atts: 1, 144 | num_atts: 1, 145 | num_adjectives: 0 146 | }, 147 | { 148 | name: 'adjectives', 149 | min: 2, 150 | max: 7, 151 | max_asked_atts: 1, 152 | num_adjectives: 1, 153 | num_atts: 1 154 | }, 155 | { 156 | name: 'subset distraction', 157 | min: 3, 158 | max: 5, 159 | max_asked_atts: 2, 160 | num_atts: 2 161 | }, 162 | { 163 | name: 'big subset distraction', 164 | min: 4, 165 | max_asked_atts: 2 166 | }, 167 | { 168 | name: 'subset with zero', 169 | max: 2, 170 | max_asked_atts: 1, 171 | num_atts: 1 172 | }, 173 | { 174 | name: 'subset mastery', 175 | max_asked_atts: 3 176 | } 177 | ] 178 | }, 179 | { 180 | name: 'operators', 181 | stages: [ 182 | { 183 | name: 'little negation', // how many cats are not A 184 | negation: true, 185 | min: 2, 186 | max: 4, 187 | num_atts: 1 188 | // returns whether a given value is to be negated. 189 | }, 190 | { 191 | name: 'big negation', // how many cats are not A 192 | negation: true, 193 | num_atts: 2 194 | // returns whether a given value is to be negated. 195 | }, 196 | { 197 | name: 'intersection', // how many A cats are B 198 | max_asked_atts: 2, 199 | num_atts: 2, 200 | min: 2, max: 5, 201 | num_adjectives: 1 202 | }, 203 | { 204 | name: 'conjunction', // how many A cats are B 205 | max_asked_atts: 2, 206 | num_atts: 2, 207 | min: 2, max: 5, 208 | num_adjectives: 0 209 | }, 210 | { 211 | name: 'union', 212 | max_asked_atts: 2, 213 | num_atts: 2, 214 | min: 2, max: 5, 215 | num_adjectives: 0, 216 | operator: 'or' 217 | }, 218 | { 219 | name: 'disjunction', 220 | max_asked_atts: 2, 221 | num_atts: 2, 222 | min: 2, max: 5, 223 | num_adjectives: 0, 224 | operator: 'or' 225 | }, 226 | { 227 | name: 'triple intersection', // how many A cats are B and C 228 | max_asked_atts: 3, 229 | num_atts: 3, 230 | min: 4, max: 7, 231 | num_adjectives: 1 232 | }, 233 | { 234 | name: 'operator mastery', // how many A cats are B and C 235 | max_asked_atts: 3, 236 | num_atts: 3, 237 | min: 2, max: 9, 238 | num_adjectives: 0, 239 | operator: 'or', 240 | negation: [false, false, true] 241 | } 242 | ] 243 | }, 244 | { 245 | name: 'arithmetic group', 246 | stages: [ 247 | { 248 | name:'successor', // if we had another, how many cats would there be 249 | successor: 1, 250 | num_adjectives: 0, 251 | max_asked_atts: 0, 252 | max: 3, 253 | num_atts: 0 254 | }, 255 | { 256 | name:'successor successor', // if we had two more, how many cats would there be? 257 | successor: 2, 258 | num_adjectives: 0, 259 | max_asked_atts: 0, 260 | num_atts: 0, 261 | max: 7 262 | }, 263 | { 264 | name:'addition', // if we had two more, how many cats would there be? 265 | addition: 4, 266 | num_adjectives: 0, 267 | max_asked_atts: 0, 268 | num_atts: 0, 269 | max: 6 270 | }, 271 | { 272 | name:'inverse successor', // if we had two more, how many cats would there be? 273 | successor: -1, 274 | num_adjectives: 0, 275 | max_asked_atts: 0, 276 | num_atts: 0, 277 | min: 1 278 | }, 279 | { 280 | name:'subtraction', // if we had two more, how many cats would there be? 281 | addition: -4, 282 | num_adjectives: 0, 283 | max_asked_atts: 0, 284 | num_atts: 0, 285 | min: 4 286 | }, 287 | { 288 | name: 'successor subset', // if we had another pink cats, how many pink cats would there be? 289 | successor: 1, 290 | num_adjectives: 0, 291 | max_asked_atts: 1, 292 | num_atts: 1, 293 | max: 8 294 | }, 295 | { 296 | name: 'addition subset', // if we had two more pink cats, how many pink cats would there be? 297 | addition: 4, 298 | num_adjectives: 0, 299 | max_asked_atts: 1, 300 | num_atts: 1, 301 | max: 5 302 | }, 303 | { 304 | name: 'subtraction subset', // if we had two less pink cats, how many cats would there be? 305 | addition: -4, 306 | num_adjectives: 0, 307 | max_asked_atts: 1, 308 | num_atts: 1, 309 | min: 4 310 | } 311 | ] 312 | }/*, 313 | { 314 | name: 'word problems', 315 | stages: [ 316 | 'supposition', // if all red cats were blue, how many red cats? 317 | 'independence', // if all red cats were blue, how many spinning cats? 318 | 'supremum', // what is the most cats that is less than the number of red cats? 319 | 'infimum', // what is the least cats that is more than the number of red cats? 320 | 'implication' // how many cats obey/break the rule 'red cats must be spinning' 321 | ] 322 | }*/ 323 | ] 324 | 325 | h = '' 326 | for (j=0;j' 336 | + topic.stages[i].name 337 | + (topic.stages[i].get_stars() > 0 ? ' ' + topic.stages[i].get_stars() + '★': '') 338 | + '' 339 | } 340 | h += '' 341 | } 342 | $('.topics').html(h) -------------------------------------------------------------------------------- /pig.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/countable/cats-of-jasnah/90c56df1be1107b81d8a0c1028f30aae5dfc04de/pig.png -------------------------------------------------------------------------------- /raindough.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/countable/cats-of-jasnah/90c56df1be1107b81d8a0c1028f30aae5dfc04de/raindough.png -------------------------------------------------------------------------------- /tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/countable/cats-of-jasnah/90c56df1be1107b81d8a0c1028f30aae5dfc04de/tree.png -------------------------------------------------------------------------------- /trophy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/countable/cats-of-jasnah/90c56df1be1107b81d8a0c1028f30aae5dfc04de/trophy.png --------------------------------------------------------------------------------