From 8ef763127b1ccf0f888dd35a2bcdbdbdb6113c5c Mon Sep 17 00:00:00 2001 From: GabJimz <46873801+GabJimz@users.noreply.github.com> Date: Wed, 14 Apr 2021 14:32:59 +0200 Subject: [PATCH 1/3] initial commit --- package.json | 16 ++++++++++--- public/index.html | 44 +++++++++++++++++------------------ src/App.css | 7 ++++++ src/App.js | 58 +++++++++++++++++++++++++++++++++++++++------- src/Card.css | 28 ++++++++++++++++++++++ src/Card.js | 27 +++++++++++++++++++++ src/GuessCount.css | 7 ++++++ src/GuessCount.js | 12 ++++++++++ src/HallOfFame.css | 14 +++++++++++ src/HallOfFame.js | 40 ++++++++++++++++++++++++++++++++ src/index.js | 11 ++------- 11 files changed, 220 insertions(+), 44 deletions(-) create mode 100644 src/App.css create mode 100644 src/Card.css create mode 100644 src/Card.js create mode 100644 src/GuessCount.css create mode 100644 src/GuessCount.js create mode 100644 src/HallOfFame.css create mode 100644 src/HallOfFame.js diff --git a/package.json b/package.json index 0a80b6a..6c15232 100644 --- a/package.json +++ b/package.json @@ -2,9 +2,14 @@ "name": "react", "version": "1.0.0", "description": "React example starter project", - "keywords": ["react", "starter"], + "keywords": [ + "react", + "starter" + ], "main": "src/index.js", "dependencies": { + "lodash.shuffle": "4.2.0", + "prpo-types": "0.0.1-security", "react": "17.0.2", "react-dom": "17.0.2", "react-scripts": "4.0.0" @@ -19,5 +24,10 @@ "test": "react-scripts test --env=jsdom", "eject": "react-scripts eject" }, - "browserslist": [">0.2%", "not dead", "not ie <= 11", "not op_mini all"] -} + "browserslist": [ + ">0.2%", + "not dead", + "not ie <= 11", + "not op_mini all" + ] +} \ No newline at end of file diff --git a/public/index.html b/public/index.html index 42ae2d2..bd0b93e 100644 --- a/public/index.html +++ b/public/index.html @@ -1,43 +1,41 @@ - - - - - - - - - - React App - + React Memory + - - -
- - - - \ No newline at end of file + + diff --git a/src/App.css b/src/App.css new file mode 100644 index 0000000..e8b3fac --- /dev/null +++ b/src/App.css @@ -0,0 +1,7 @@ +.memory { + display: flex; + flex-wrap: wrap; + width: 300px; + margin: auto; + user-select: none; +} diff --git a/src/App.js b/src/App.js index dbc5c59..8146780 100644 --- a/src/App.js +++ b/src/App.js @@ -1,10 +1,50 @@ -import "./styles.css"; - -export default function App() { - return ( -
-

Hello CodeSandbox

-

Start editing to see some magic happen!

-
- ); +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 = "πŸ˜€πŸŽ‰πŸ’–πŸŽ©πŸΆπŸ±πŸ¦„πŸ¬πŸŒπŸŒ›πŸŒžπŸ’«πŸŽπŸŒπŸ“πŸπŸŸπŸΏ"; + +class App extends Component { + cards = this.generateCards(); + + 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); + } + + handleCardClick = (card) => { + console.log(card, this); + }; + + render() { + const won = new Date().getSeconds() % 2 === 0; + return ( +
+ + {this.cards.map((card, index) => ( + + ))} + {won && } +
+ ); + } } + +export default App; diff --git a/src/Card.css b/src/Card.css new file mode 100644 index 0000000..1a9a336 --- /dev/null +++ b/src/Card.css @@ -0,0 +1,28 @@ +.memory > .card { + font-size: 2em; + flex: 1 1 calc(100% / 6 - 0.4em); + outline: 0.08em solid silver; + margin: 0.2em; + display: flex; + cursor: default; +} + +.memory > .card.hidden { + background: silver; +} + +.memory > .card.justMatched, +.memory > .card.justMismatched { + outline: 0.1em solid green; +} +.memory > .card.justMismatched { + outline-color: red; +} + +.memory > .card.visible { + cursor: not-allowed; +} + +.memory > .card > .symbol { + margin: auto; +} diff --git a/src/Card.js b/src/Card.js new file mode 100644 index 0000000..326f23f --- /dev/null +++ b/src/Card.js @@ -0,0 +1,27 @@ +import React from "react"; + +import "./Card.css"; +import PropTypes from "prop-types"; + +const HIDDEN_SYMBOL = "❓"; + +const Card = ({ card, feedback, onClick }) => ( +
onClick(card)}> + + {feedback === "hidden" ? HIDDEN_SYMBOL : card} + +
+); + +Card.propTypes = { + card: PropTypes.string.isRequired, + feedback: PropTypes.oneOf([ + "hidden", + "justMatched", + "justMismatched", + "visible" + ]).isRequired, + onClick: PropTypes.func.isRequired +}; + +export default Card; diff --git a/src/GuessCount.css b/src/GuessCount.css new file mode 100644 index 0000000..085e35f --- /dev/null +++ b/src/GuessCount.css @@ -0,0 +1,7 @@ +.memory > .guesses { + font-size: 1em; + width: calc(100% - 0.4em); + margin: 0.2em; + text-align: right; + font-family: Menlo, Monaco, Consolas, Inconsolata, "Courier New", monospace; +} diff --git a/src/GuessCount.js b/src/GuessCount.js new file mode 100644 index 0000000..28a24e3 --- /dev/null +++ b/src/GuessCount.js @@ -0,0 +1,12 @@ +import React from "react"; +import PropTypes from "prop-types"; + +import "./GuessCount.css"; + +const GuessCount = ({ guesses }) =>
{guesses}
; + +GuessCount.propTypes = { + guesses: PropTypes.number.isRequired +}; + +export default GuessCount; diff --git a/src/HallOfFame.css b/src/HallOfFame.css new file mode 100644 index 0000000..a8a4944 --- /dev/null +++ b/src/HallOfFame.css @@ -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; +} diff --git a/src/HallOfFame.js b/src/HallOfFame.js new file mode 100644 index 0000000..c1a6db5 --- /dev/null +++ b/src/HallOfFame.js @@ -0,0 +1,40 @@ +import React from "react"; + +import "./HallOfFame.css"; +import PropTypes from "prop-types"; + +const HallOfFame = ({ entries }) => ( + + + {entries.map(({ date, guesses, id, player }) => ( + + + + + + ))} + +
{date}{guesses}{player}
+); + +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" } +]; diff --git a/src/index.js b/src/index.js index d65892e..c24e9d8 100644 --- a/src/index.js +++ b/src/index.js @@ -1,12 +1,5 @@ -import { StrictMode } from "react"; +import React from "react"; import ReactDOM from "react-dom"; - import App from "./App"; -const rootElement = document.getElementById("root"); -ReactDOM.render( - - - , - rootElement -); +ReactDOM.render(, document.getElementById("root")); From 979fb2be863363c1f786964588f8e2cbe3abd52a Mon Sep 17 00:00:00 2001 From: GabJimz <46873801+GabJimz@users.noreply.github.com> Date: Wed, 14 Apr 2021 14:48:31 +0200 Subject: [PATCH 2/3] Resolution dependance... --- package.json | 2 +- public/index.html | 5 ++++- src/Card.css | 4 ++-- src/HallOfFame.css | 2 +- src/index.js | 9 ++++++++- 5 files changed, 16 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 6c15232..b2a720c 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "main": "src/index.js", "dependencies": { "lodash.shuffle": "4.2.0", - "prpo-types": "0.0.1-security", + "prop-types": "15.7.2", "react": "17.0.2", "react-dom": "17.0.2", "react-scripts": "4.0.0" diff --git a/public/index.html b/public/index.html index bd0b93e..587c69a 100644 --- a/public/index.html +++ b/public/index.html @@ -17,11 +17,12 @@ Notice the use of %PUBLIC_URL% in the tags above. It will be replaced with the URL of the `public` folder during the build. Only files inside the `public` folder can be referenced from the HTML. + Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will work correctly both with client-side routing and a non-root public URL. Learn how to configure a non-root public URL by running `npm run build`. --> - React Memory + React App @@ -32,8 +33,10 @@ diff --git a/src/Card.css b/src/Card.css index 1a9a336..716104a 100644 --- a/src/Card.css +++ b/src/Card.css @@ -1,14 +1,14 @@ .memory > .card { font-size: 2em; flex: 1 1 calc(100% / 6 - 0.4em); - outline: 0.08em solid silver; + outline: 0.08em solid rgb(0, 0, 0); margin: 0.2em; display: flex; cursor: default; } .memory > .card.hidden { - background: silver; + background: rgb(90, 69, 69); } .memory > .card.justMatched, diff --git a/src/HallOfFame.css b/src/HallOfFame.css index a8a4944..6a177cb 100644 --- a/src/HallOfFame.css +++ b/src/HallOfFame.css @@ -10,5 +10,5 @@ } .hallOfFame tr:nth-child(even) { - background: #eee; + background: rgb(0, 0, 0); } diff --git a/src/index.js b/src/index.js index c24e9d8..447296c 100644 --- a/src/index.js +++ b/src/index.js @@ -1,5 +1,12 @@ +import { StrictMode } from "react"; import React from "react"; import ReactDOM from "react-dom"; import App from "./App"; -ReactDOM.render(, document.getElementById("root")); +const rootElement = document.getElementById("root"); +ReactDOM.render( + + + , + rootElement +); From 7fed2cd02e0c5ca7774416ec8a5dde75e78ff941 Mon Sep 17 00:00:00 2001 From: GabJimz <46873801+GabJimz@users.noreply.github.com> Date: Wed, 14 Apr 2021 17:02:30 +0200 Subject: [PATCH 3/3] debut --- src/App.css | 2 +- src/App.js | 63 +++++++++++++++++++++++++++++++++++++++++++++++------ src/Card.js | 5 +++-- 3 files changed, 60 insertions(+), 10 deletions(-) diff --git a/src/App.css b/src/App.css index e8b3fac..d1e0a81 100644 --- a/src/App.css +++ b/src/App.css @@ -1,7 +1,7 @@ .memory { display: flex; flex-wrap: wrap; - width: 300px; + width: 350px; margin: auto; user-select: none; } diff --git a/src/App.js b/src/App.js index 8146780..abe13ce 100644 --- a/src/App.js +++ b/src/App.js @@ -9,9 +9,15 @@ 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 = []; @@ -24,19 +30,62 @@ class App extends Component { return shuffle(result); } - handleCardClick = (card) => { - console.log(card, this); + 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); + } + + handleCardClick = (index) => { + const { currentPair } = this.state; + + if (currentPair.length === 2) { + return; + } + + if (currentPair.length === 0) { + this.setState({ currentPair: [index] }); + return; + } + + this.handleNewPairClosedBy(index); }; + 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"; + } + render() { - const won = new Date().getSeconds() % 2 === 0; + const { cards, guesses, matchedCardIndices } = this.state; + const won = matchedCardIndices.length === cards.length; return (
- - {this.cards.map((card, index) => ( + + {cards.map((card, index) => ( diff --git a/src/Card.js b/src/Card.js index 326f23f..90105fc 100644 --- a/src/Card.js +++ b/src/Card.js @@ -5,8 +5,8 @@ import PropTypes from "prop-types"; const HIDDEN_SYMBOL = "❓"; -const Card = ({ card, feedback, onClick }) => ( -
onClick(card)}> +const Card = ({ card, feedback, index, onClick }) => ( +
onClick(index)}> {feedback === "hidden" ? HIDDEN_SYMBOL : card} @@ -21,6 +21,7 @@ Card.propTypes = { "justMismatched", "visible" ]).isRequired, + index: PropTypes.number.isRequired, onClick: PropTypes.func.isRequired };