├── .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 | 
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 |
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 |
--------------------------------------------------------------------------------