├── .gitignore ├── .config └── configstore │ ├── update-notifier-npm.json │ ├── update-notifier-serve.json.1195833916 │ ├── update-notifier-serve.json.1587055911 │ ├── update-notifier-serve.json.3137924473 │ ├── update-notifier-serve.json.3177954483 │ ├── update-notifier-serve.json.3867454747 │ ├── update-notifier-serve.json.3972972356 │ └── update-notifier-serve.json ├── README.md ├── public ├── styles.css └── index.js ├── package.json ├── index.html ├── .glitch-assets └── server.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env -------------------------------------------------------------------------------- /.config/configstore/update-notifier-npm.json: -------------------------------------------------------------------------------- 1 | { 2 | "optOut": false, 3 | "lastUpdateCheck": 1516796960586 4 | } -------------------------------------------------------------------------------- /.config/configstore/update-notifier-serve.json.1195833916: -------------------------------------------------------------------------------- 1 | { 2 | "optOut": false, 3 | "lastUpdateCheck": 1497395416720 4 | } -------------------------------------------------------------------------------- /.config/configstore/update-notifier-serve.json.1587055911: -------------------------------------------------------------------------------- 1 | { 2 | "optOut": false, 3 | "lastUpdateCheck": 1499564159184 4 | } -------------------------------------------------------------------------------- /.config/configstore/update-notifier-serve.json.3137924473: -------------------------------------------------------------------------------- 1 | { 2 | "optOut": false, 3 | "lastUpdateCheck": 1499564159184 4 | } -------------------------------------------------------------------------------- /.config/configstore/update-notifier-serve.json.3177954483: -------------------------------------------------------------------------------- 1 | { 2 | "optOut": false, 3 | "lastUpdateCheck": 1497395416720 4 | } -------------------------------------------------------------------------------- /.config/configstore/update-notifier-serve.json.3867454747: -------------------------------------------------------------------------------- 1 | { 2 | "optOut": false, 3 | "lastUpdateCheck": 1497395416720 4 | } -------------------------------------------------------------------------------- /.config/configstore/update-notifier-serve.json.3972972356: -------------------------------------------------------------------------------- 1 | { 2 | "optOut": false, 3 | "lastUpdateCheck": 1497395416720 4 | } -------------------------------------------------------------------------------- /.config/configstore/update-notifier-serve.json: -------------------------------------------------------------------------------- 1 | { 2 | "optOut": false, 3 | "lastUpdateCheck": 1499782708058, 4 | "update": { 5 | "latest": "6.0.2", 6 | "current": "5.2.2", 7 | "type": "major", 8 | "name": "serve" 9 | } 10 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # The Noun Game 2 | 3 | Simple icon guessing game using icons from [The Noun Project](https://thenounproject.com). 4 | 5 | [Hosted here](https://the-noun-game.glitch.me/). 6 | 7 | ![image](https://media.giphy.com/media/3ohs4jauWwI9lsIQlq/source.gif) 8 | -------------------------------------------------------------------------------- /public/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: 'Magra', sans-serif; 3 | font-weight: 500; 4 | background: floralwhite; 5 | } 6 | 7 | span.blank:before { 8 | content: "\200b"; 9 | } 10 | 11 | .cover { 12 | background-color: floralwhite; 13 | transition: height ease 0.5s; 14 | } 15 | 16 | .cover.hidden { 17 | height: 0; 18 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "the-noun-game", 3 | "version": "0.0.1", 4 | "description": "", 5 | "main": "", 6 | "scripts": { 7 | "start": "node server.js", 8 | "lint": "standard --fix" 9 | }, 10 | "dependencies": { 11 | "dotenv": "^4.0.0", 12 | "express": "^4.16.2", 13 | "standard": "^10.0.3", 14 | "the-noun-project": "^2.0.1" 15 | }, 16 | "engines": { 17 | "node": "8.6.x" 18 | }, 19 | "license": "MIT" 20 | } 21 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ~noun 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 17 | 18 | -------------------------------------------------------------------------------- /.glitch-assets: -------------------------------------------------------------------------------- 1 | {"name":"drag-in-files.svg","date":"2016-10-22T16:17:49.954Z","url":"https://cdn.hyperdev.com/drag-in-files.svg","type":"image/svg","size":7646,"imageWidth":276,"imageHeight":276,"thumbnail":"https://cdn.hyperdev.com/drag-in-files.svg","thumbnailWidth":276,"thumbnailHeight":276,"dominantColor":"rgb(102, 153, 205)","uuid":"adSBq97hhhpFNUna"} 2 | {"name":"click-me.svg","date":"2016-10-23T16:17:49.954Z","url":"https://cdn.hyperdev.com/click-me.svg","type":"image/svg","size":7116,"imageWidth":276,"imageHeight":276,"thumbnail":"https://cdn.hyperdev.com/click-me.svg","thumbnailWidth":276,"thumbnailHeight":276,"dominantColor":"rgb(243, 185, 186)","uuid":"adSBq97hhhpFNUnb"} 3 | {"name":"paste-me.svg","date":"2016-10-24T16:17:49.954Z","url":"https://cdn.hyperdev.com/paste-me.svg","type":"image/svg","size":7242,"imageWidth":276,"imageHeight":276,"thumbnail":"https://cdn.hyperdev.com/paste-me.svg","thumbnailWidth":276,"thumbnailHeight":276,"dominantColor":"rgb(42, 179, 185)","uuid":"adSBq97hhhpFNUnc"} 4 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config() 2 | 3 | const express = require('express') 4 | const path = require('path') 5 | const NounProject = require('the-noun-project') 6 | 7 | const app = express() 8 | const np = new NounProject({ 9 | key: process.env.NOUN_PROJ_APIKEY, 10 | secret: process.env.NOUN_PROJ_SECRET 11 | }) 12 | 13 | app.use(express.static('public')) 14 | 15 | app.get('/', (req, res) => { 16 | res.sendFile(path.join(__dirname, '/index.html')) 17 | }) 18 | 19 | app.get('/icon', (req, res) => { 20 | const maxTries = 3 21 | const handler = (numTry) => { 22 | if (numTry < maxTries) { 23 | const randomId = Math.floor(Math.random() * 1538985) 24 | np.getIconById(randomId, (err, data) => { 25 | if (err) { 26 | handler(numTry++) 27 | } else { 28 | res.json(data) 29 | } 30 | }) 31 | } else { 32 | res.status(500).json({error: 'Maximum retry exceeded.'}) 33 | } 34 | } 35 | 36 | handler(0) 37 | }) 38 | 39 | const listener = app.listen(process.env.PORT || 3000, () => { 40 | console.log(`Your app is listening on port ${listener.address().port}`) 41 | }) 42 | -------------------------------------------------------------------------------- /public/index.js: -------------------------------------------------------------------------------- 1 | const Vue = require('vue') 2 | 3 | const app = new Vue({ 4 | el: '#app', 5 | 6 | data: { 7 | icon: {}, 8 | iconNext: {}, 9 | isGameover: false, 10 | isStarted: false, 11 | score: 0, 12 | time: 180, 13 | answer: [], 14 | showAnswer: false, 15 | error: null 16 | }, 17 | 18 | created: function () { 19 | this.fetchIcon().then(icon => { this.icon = icon }) 20 | this.fetchIcon().then(icon => { this.iconNext = icon }) 21 | window.addEventListener('keyup', this.checkKey) 22 | }, 23 | 24 | beforeDestroy: function () { 25 | // remove listener 26 | }, 27 | 28 | methods: { 29 | 30 | fetchIcon: function () { 31 | return fetch('/icon') 32 | .then((res) => res.json()) 33 | .then((data) => { 34 | if (!data.icon) { 35 | throw new Error('not ok') 36 | } 37 | let { term, preview_url } = data.icon 38 | term = term.trim() 39 | return { term, preview_url } 40 | }) 41 | .catch((error) => this.error = error) 42 | }, 43 | 44 | checkKey: function (e) { 45 | if (this.error) return 46 | 47 | switch (true) { 48 | case (e.keyCode === 8): // backspace 49 | const del = this.answer.pop() 50 | if (del === ' ') { 51 | this.answer.pop() 52 | } 53 | break 54 | case (e.keyCode === 13): // enter 55 | this.showAnswer = true 56 | setTimeout(() => { 57 | this.showAnswer = false 58 | this.nextRound() 59 | }, 300) 60 | break 61 | case (e.keyCode === 32): // space 62 | if (this.isGameover) { 63 | this.restart() 64 | } else { 65 | this.startGame() 66 | } 67 | break 68 | case (e.keyCode > 47 && e.keyCode < 91 && // alphanumerical keys 69 | this.answer.length < this.icon.term.length): 70 | if (this.icon.term[this.answer.length] === ' ') { 71 | this.answer.push(' ') 72 | } 73 | this.answer.push(String.fromCharCode(e.keyCode)) 74 | break 75 | default: 76 | break 77 | } 78 | if (this.answer.length === this.icon.term.length) { 79 | this.checkAnswer() 80 | } 81 | }, 82 | 83 | startGame: function () { 84 | if (this.isStarted) return 85 | 86 | this.isStarted = true 87 | const timer = setInterval(() => { 88 | if (--this.time === 0) { 89 | clearInterval(timer) 90 | this.gameOver() 91 | } 92 | }, 1000) 93 | }, 94 | 95 | restart: function () { 96 | Object.assign(this, { 97 | isGameover: false, 98 | score: 0, 99 | time: 180, 100 | answer: [] 101 | }) 102 | this.nextRound() 103 | this.startGame() 104 | }, 105 | 106 | gameOver: function () { 107 | this.isStarted = false 108 | this.isGameover = true 109 | }, 110 | 111 | checkAnswer: function () { 112 | if (this.answer.join('').toLowerCase() === this.icon.term.toLowerCase()) { 113 | this.score++ 114 | setTimeout(this.nextRound, 150) 115 | } 116 | }, 117 | 118 | nextRound: function () { 119 | if (Object.keys(this.iconNext).length > 0) { 120 | this.icon = this.iconNext 121 | } 122 | this.fetchIcon().then(icon => { this.iconNext = icon }) 123 | this.answer = [] 124 | } 125 | 126 | }, 127 | 128 | template: ` 129 |
130 |
131 | 137 | 142 | 143 | 144 | 145 |
146 |
147 | gh 148 | tw 149 |
150 |
151 | ` 152 | }) 153 | 154 | Vue.component('guess', { 155 | props: ['term', 'answer', 'showAnswer'], 156 | 157 | methods: { 158 | outputAnswer: function (i) { 159 | if (this.showAnswer) { 160 | return this.term[i].toUpperCase() 161 | } 162 | if (i >= this.answer.length) { 163 | return '' 164 | } 165 | return this.answer[i] 166 | }, 167 | 168 | isCorrect: function (i) { 169 | return i >= this.answer.length || 170 | this.term[i].toLowerCase() === this.answer[i].toLowerCase() 171 | } 172 | }, 173 | 174 | template: ` 175 |

176 | {{ outputAnswer(i) }} 181 | 182 |

183 | 184 | ` 185 | }) 186 | 187 | Vue.component('dashboard', { 188 | props: ['score', 'time'], 189 | 190 | filters: { 191 | formatTime: function (sec) { 192 | const mm = Math.floor(sec / 60) 193 | const ss = Math.floor(sec % 60) 194 | return `${mm < 10 ? '0' + mm : mm}:${ss < 10 ? '0' + ss : ss}` 195 | } 196 | }, 197 | 198 | template: ` 199 |
200 |

Score: {{ score }}

201 |

{{ time | formatTime }}

202 |
203 | ` 204 | }) 205 | 206 | Vue.component('cover', { 207 | props: ['isStarted', 'isGameover', 'score', 'error'], 208 | 209 | computed: { 210 | message: function () { 211 | if (this.error) { 212 | return 'Sorry!
Something went wrong.' 213 | } 214 | if (!this.isGameover) { 215 | return `Press space to start
Press enter to skip` 216 | } else { 217 | return `Fin.
Score: ${this.score}` 218 | } 219 | } 220 | }, 221 | 222 | template: ` 223 |
224 |

225 |
226 | ` 227 | }) 228 | --------------------------------------------------------------------------------