8 Commits
7 changed files with 10261 additions and 9934 deletions
+10103 -9925
View File
File diff suppressed because it is too large Load Diff
+2
View File
@@ -3,6 +3,8 @@
"version": "0.1.0",
"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"
+82 -7
View File
@@ -1,20 +1,95 @@
import React, { Component } from 'react'
import shuffle from 'lodash.shuffle'
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 {
state = {
cards: this.generateCards(),
currentPair: [],
guesses: 0,
matchedCardIndices: [],
}
generateCards() {
const result = []
const size = SIDE * SIDE
const candidates = shuffle(SYMBOLS)
while (result.length < size) {
const card = candidates.pop()
result.push(card, card)
}
return shuffle(result)
}
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 { cards, guesses, matchedCardIndices } = this.state
const won = matchedCardIndices.length === cards.length
return (
<div className="memory">
<GuessCount guesses={0} />
<Card card="😀" feedback="hidden" />
<Card card="🎉" feedback="justMatched" />
<Card card="💖" feedback="justMismatched" />
<Card card="🎩" feedback="visible" />
<Card card="🐶" feedback="hidden" />
<Card card="🐱" feedback="justMatched" />
<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 }) => (
<div className={`card ${feedback}`}>
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
+14
View File
@@ -0,0 +1,14 @@
.hallOfFame {
width: 100%;
border-collapse: collapse;
}
.hallOfFame .date,
.hallOfFame .guesses {
width: 20%;
text-align: center;
}
.hallOfFame tr:nth-child(even) {
background: #eee;
}
+40
View File
@@ -0,0 +1,40 @@
import PropTypes from 'prop-types'
import React from 'react'
import './HallOfFame.css'
const HallOfFame = ({ entries }) => (
<table className="hallOfFame">
<tbody>
{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 ==============================================
export const FAKE_HOF = [
{ id: 3, guesses: 18, date: '10/10/2017', player: 'Jane' },
{ id: 2, guesses: 23, date: '11/10/2017', player: 'Kevin' },
{ id: 1, guesses: 31, date: '06/10/2017', player: 'Louisa' },
{ id: 0, guesses: 48, date: '14/10/2017', player: 'Marc' },
]