6 changed files with 214 additions and 19 deletions
+111
View File
@@ -4,6 +4,24 @@
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"asap": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
"integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY="
},
"core-js": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz",
"integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY="
},
"encoding": {
"version": "0.1.12",
"resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz",
"integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=",
"requires": {
"iconv-lite": "0.4.19"
}
},
"eslint-config-prettier": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-2.6.0.tgz",
@@ -51,17 +69,95 @@
}
}
},
"fbjs": {
"version": "0.8.16",
"resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.16.tgz",
"integrity": "sha1-XmdDL1UNxBtXK/VYR7ispk5TN9s=",
"requires": {
"core-js": "1.2.7",
"isomorphic-fetch": "2.2.1",
"loose-envify": "1.3.1",
"object-assign": "4.1.1",
"promise": "7.3.1",
"setimmediate": "1.0.5",
"ua-parser-js": "0.7.17"
}
},
"iconv-lite": {
"version": "0.4.19",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz",
"integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ=="
},
"is-stream": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
"integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ="
},
"isomorphic-fetch": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz",
"integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=",
"requires": {
"node-fetch": "1.7.3",
"whatwg-fetch": "2.0.3"
}
},
"js-tokens": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz",
"integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls="
},
"lodash.shuffle": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/lodash.shuffle/-/lodash.shuffle-4.2.0.tgz",
"integrity": "sha1-FFtQU8+HX29cKjP0i26ZSMbse0s="
},
"loose-envify": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz",
"integrity": "sha1-0aitM/qc4OcT1l/dCsi3SNR4yEg=",
"requires": {
"js-tokens": "3.0.2"
}
},
"node-fetch": {
"version": "1.7.3",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz",
"integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==",
"requires": {
"encoding": "0.1.12",
"is-stream": "1.1.0"
}
},
"object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
},
"prettier": {
"version": "1.7.4",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-1.7.4.tgz",
"integrity": "sha1-XoYkrpNjyA+V7GRFhOzfVddPk/o=",
"dev": true
},
"promise": {
"version": "7.3.1",
"resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz",
"integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==",
"requires": {
"asap": "2.0.6"
}
},
"prop-types": {
"version": "15.6.0",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.0.tgz",
"integrity": "sha1-zq8IMCL8RrSjX2nhPvda7Q1jmFY=",
"requires": {
"fbjs": "0.8.16",
"loose-envify": "1.3.1",
"object-assign": "4.1.1"
}
},
"react": {
"version": "16.0.0",
"resolved": "https://registry.npmjs.org/react/-/react-16.0.0.tgz",
@@ -10319,6 +10415,21 @@
}
}
}
},
"setimmediate": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
"integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU="
},
"ua-parser-js": {
"version": "0.7.17",
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.17.tgz",
"integrity": "sha512-uRdSdu1oA1rncCQL7sCj8vSyZkgtL7faaw9Tc9rZ3mGgraQ7+Pdx7w5mnOSF3gw9ZNG6oc+KXfkon3bKuROm0g=="
},
"whatwg-fetch": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz",
"integrity": "sha1-nITsLc9oGH/wC8ZOEnS0QhduHIQ="
}
}
}
+1
View File
@@ -4,6 +4,7 @@
"private": true,
"dependencies": {
"lodash.shuffle": "^4.2.0",
"prop-types": "^15.6.0",
"react": "^16.0.0",
"react-dom": "^16.0.0",
"react-scripts": "1.0.14"
+63 -16
View File
@@ -5,12 +5,19 @@ import './App.css'
import Card from './Card'
import GuessCount from './GuessCount'
import HallOfFame, { FAKE_HOF } from './HallOfFame'
const SIDE = 6
const SYMBOLS = '😀🎉💖🎩🐶🐱🦄🐬🌍🌛🌞💫🍎🍌🍓🍐🍟🍿'
const VISUAL_PAUSE_MSECS = 750
class App extends Component {
cards = this.generateCards()
state = {
cards: this.generateCards(),
currentPair: [],
guesses: 0,
matchedCardIndices: [],
}
generateCards() {
const result = []
@@ -23,26 +30,66 @@ class App extends Component {
return shuffle(result)
}
handleCardClick(card) {
console.log(card, 'clicked')
getFeedbackForCard(index) {
const { currentPair, matchedCardIndices } = this.state
const indexMatched = matchedCardIndices.includes(index)
if (currentPair.length < 2) {
return indexMatched || index === currentPair[0] ? 'visible' : 'hidden'
}
if (currentPair.includes(index)) {
return indexMatched ? 'justMatched' : 'justMismatched'
}
return indexMatched ? 'visible' : 'hidden'
}
// Arrow fx for binding
handleCardClick = index => {
const { currentPair } = this.state
if (currentPair.length === 2) {
return
}
if (currentPair.length === 0) {
this.setState({ currentPair: [index] })
return
}
this.handleNewPairClosedBy(index)
}
handleNewPairClosedBy(index) {
const { cards, currentPair, guesses, matchedCardIndices } = this.state
const newPair = [currentPair[0], index]
const newGuesses = guesses + 1
const matched = cards[newPair[0]] === cards[newPair[1]]
this.setState({ currentPair: newPair, guesses: newGuesses })
if (matched) {
this.setState({ matchedCardIndices: [...matchedCardIndices, ...newPair] })
}
setTimeout(() => this.setState({ currentPair: [] }), VISUAL_PAUSE_MSECS)
}
render() {
const won = new Date().getSeconds() % 2 === 0
const { cards, guesses, matchedCardIndices } = this.state
const won = matchedCardIndices.length === cards.length
return (
<div className="memory">
<GuessCount guesses={0} />
<Card card="😀" feedback="hidden" onClick={this.handleCardClick} />
<Card card="🎉" feedback="justMatched" onClick={this.handleCardClick} />
<Card
card="💖"
feedback="justMismatched"
onClick={this.handleCardClick}
/>
<Card card="🎩" feedback="visible" onClick={this.handleCardClick} />
<Card card="🐶" feedback="hidden" onClick={this.handleCardClick} />
<Card card="🐱" feedback="justMatched" onClick={this.handleCardClick} />
{won && <p>GAGNÉ !</p>}
<GuessCount guesses={guesses} />
{cards.map((card, index) => (
<Card
card={card}
feedback={this.getFeedbackForCard(index)}
index={index}
key={index}
onClick={this.handleCardClick}
/>
))}
{won && <HallOfFame entries={FAKE_HOF} />}
</div>
)
}
+15 -2
View File
@@ -1,15 +1,28 @@
import PropTypes from 'prop-types'
import React from 'react'
import './Card.css'
const HIDDEN_SYMBOL = '❓'
const Card = ({ card, feedback, onClick }) => (
<div className={`card ${feedback}`} onClick={() => onClick(card)}>
const Card = ({ card, feedback, index, onClick }) => (
<div className={`card ${feedback}`} onClick={() => onClick(index)}>
<span className="symbol">
{feedback === 'hidden' ? HIDDEN_SYMBOL : card}
</span>
</div>
)
Card.propTypes = {
card: PropTypes.string.isRequired,
feedback: PropTypes.oneOf([
'hidden',
'justMatched',
'justMismatched',
'visible',
]).isRequired,
index: PropTypes.number.isRequired,
onClick: PropTypes.func.isRequired,
}
export default Card
+5
View File
@@ -1,7 +1,12 @@
import PropTypes from 'prop-types'
import React from 'react'
import './GuessCount.css'
const GuessCount = ({ guesses }) => <div className="guesses">{guesses}</div>
GuessCount.propTypes = {
guesses: PropTypes.number.isRequired,
}
export default GuessCount
+19 -1
View File
@@ -1,3 +1,4 @@
import PropTypes from 'prop-types'
import React from 'react'
import './HallOfFame.css'
@@ -5,11 +6,28 @@ import './HallOfFame.css'
const HallOfFame = ({ entries }) => (
<table className="hallOfFame">
<tbody>
<tr>FIXME</tr>
{entries.map(({ date, guesses, id, player }) => (
<tr key={id}>
<td className="date">{date}</td>
<td className="guesses">{guesses}</td>
<td className="player">{player}</td>
</tr>
))}
</tbody>
</table>
)
HallOfFame.propTypes = {
entries: PropTypes.arrayOf(
PropTypes.shape({
date: PropTypes.string.isRequired,
guesses: PropTypes.number.isRequired,
id: PropTypes.number.isRequired,
player: PropTypes.string.isRequired,
})
).isRequired,
}
export default HallOfFame
// == Internal helpers ==============================================