Евгения Акиншина 3 سال پیش
کامیت
02aed8655d

BIN
.DS_Store


+ 6 - 0
.gitignore

@@ -0,0 +1,6 @@
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+node_modules/
+build

+ 45 - 0
README.md

@@ -0,0 +1,45 @@
+
+
+## Memory Game Using TypeScript and React
+To run the app -
+
+In the project directory, you can run:
+### `npm install`
+### `npm start`
+
+Runs the app in the development mode.<br />
+Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
+
+
+### Things done
+
+A React TS application that is used to play the memory game with the help of useReducer 
+
+### Algorithm -
+
+1. The player will start the game 
+2. he/she will click on a card 
+3. once clicked the it will get checked with the last card(which initially is none)
+4. if the two cards matches, both the cards will remain open othervise the older card will be closed
+5. the process will repeate it we see all the cards open
+6. when all the cards are opened 
+7. the user will get a message box indicating "congrats, you won" 
+8. the player can play the game again by clicking on the restart game button
+
+
+
+I have use useReducer, useEffect and useState to save the states. 
+These are called Hooks (such as a useReducer hook) they store the recent addition to React that enable
+more of your components to be written as functions by providing less complex 
+alternatives to class features. One significant advantage is that typing function
+components in TypeScript is simpler and more direct.
+
+### Screens
+
+#### INITIAL-
+
+![Initial Screen](https://github.com/Chhavi-Trivedi/memoryGame_TypeScript/blob/main/src/components/memory/Images/initial.png?raw=true)
+#### MID-
+![Mid Screen](https://github.com/Chhavi-Trivedi/memoryGame_TypeScript/blob/main/src/components/memory/Images/mid.png?raw=true)
+#### RESULTS-
+![Result Screen](https://github.com/Chhavi-Trivedi/memoryGame_TypeScript/blob/main/src/components/memory/Images/result.png?raw=true)

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 15379 - 0
package-lock.json


+ 46 - 0
package.json

@@ -0,0 +1,46 @@
+{
+  "name": "my-app",
+  "version": "0.1.0",
+  "private": true,
+  "homepage": " https://Chhavi-Trivedi.github.io/memoryGame-TypeScript/",
+  "dependencies": {
+    "@testing-library/jest-dom": "^4.2.4",
+    "@testing-library/react": "^9.5.0",
+    "@testing-library/user-event": "^7.2.1",
+    "@types/jest": "^24.9.1",
+    "@types/node": "^12.12.34",
+    "@types/react": "^16.9.31",
+    "@types/react-dom": "^16.9.6",
+    "@types/react-modal": "^3.10.5",
+    "gh-pages": "^2.2.0",
+    "react": "^16.13.1",
+    "react-awesome-reveal": "^2.4.2",
+    "react-dom": "^16.13.1",
+    "react-modal": "^3.11.2",
+    "react-reveal": "^1.2.2",
+    "react-scripts": "3.4.1",
+    "typescript": "^3.7.5"
+  },
+  "scripts": {
+    "start": "react-scripts start",
+    "build": "react-scripts build",
+    "test": "react-scripts test",
+    "eject": "react-scripts eject",
+    "deploy": "gh-pages -d build"
+  },
+  "eslintConfig": {
+    "extends": "react-app"
+  },
+  "browserslist": {
+    "production": [
+      ">0.2%",
+      "not dead",
+      "not op_mini all"
+    ],
+    "development": [
+      "last 1 chrome version",
+      "last 1 firefox version",
+      "last 1 safari version"
+    ]
+  }
+}

BIN
public/.DS_Store


BIN
public/favicon.ico


BIN
public/img/.DS_Store


BIN
public/img/favicon.png


+ 44 - 0
public/index.html

@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="utf-8" />
+    <!-- <link rel="icon" href="%PUBLIC_URL%/favicon.ico" /> -->
+    <link rel="icon" href="%PUBLIC_URL%/img/favicon.png" type="mage/x-icon" />
+    <meta name="viewport" content="width=device-width, initial-scale=1" />
+    <meta name="theme-color" content="#000000" />
+    <meta
+      name="description"
+      content="Web site created using create-react-app"
+    />
+    <!-- <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" /> -->
+    <!--
+      manifest.json provides metadata used when your web app is installed on a
+      user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
+    -->
+    <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
+    <!--
+      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`.
+    -->
+    <title>MemoryGame</title>
+  </head>
+  <body>
+    <noscript>You need to enable JavaScript to run this app.</noscript>
+    <div id="root"></div>
+    <!--
+      This HTML file is a template.
+      If you open it directly in the browser, you will see an empty page.
+
+      You can add webfonts, meta tags, or analytics to this file.
+      The build step will place the bundled scripts into the <body> tag.
+
+      To begin the development, run `npm start` or `yarn start`.
+      To create a production bundle, use `npm run build` or `yarn build`.
+    -->
+  </body>
+</html>

BIN
public/logo192.png


BIN
public/logo512.png


+ 25 - 0
public/manifest.json

@@ -0,0 +1,25 @@
+{
+  "short_name": "typeScipt-React App",
+  "name": "Create typeScipt React App Sample",
+  "icons": [
+    {
+      "src": "favicon.ico",
+      "sizes": "64x64 32x32 24x24 16x16",
+      "type": "image/x-icon"
+    },
+    {
+      "src": "logo192.png",
+      "type": "image/png",
+      "sizes": "192x192"
+    },
+    {
+      "src": "logo512.png",
+      "type": "image/png",
+      "sizes": "512x512"
+    }
+  ],
+  "start_url": ".",
+  "display": "standalone",
+  "theme_color": "#000000",
+  "background_color": "#ffffff"
+}

+ 3 - 0
public/robots.txt

@@ -0,0 +1,3 @@
+# https://www.robotstxt.org/robotstxt.html
+User-agent: *
+Disallow:

BIN
src/.DS_Store


+ 7 - 0
src/App.css

@@ -0,0 +1,7 @@
+.text{
+    font-style: unset;
+    margin-left: 500px;
+    font-weight: bold;
+    color: rgb(12, 75, 146);
+    font-size: 30px;
+}

+ 9 - 0
src/App.test.tsx

@@ -0,0 +1,9 @@
+import React from 'react';
+import { render } from '@testing-library/react';
+import App from './App';
+
+test('renders learn react link', () => {
+  const { getByText } = render(<App />);
+  const linkElement = getByText(/learn react/i);
+  expect(linkElement).toBeInTheDocument();
+});

+ 14 - 0
src/App.tsx

@@ -0,0 +1,14 @@
+import React from 'react';
+import MemoryGame from './components';
+import './App.css'
+
+function App() {
+  return (
+    <>
+    <p className='text'>Memory game</p>
+     <MemoryGame/>
+    </>
+  );
+}
+
+export default App;

BIN
src/components/.DS_Store


+ 12 - 0
src/components/index.tsx

@@ -0,0 +1,12 @@
+import React from 'react';
+import Uirender from './memory/uiComponent';
+
+const MemoryGame : React.FC =() => {
+  return (
+    <>
+    <Uirender/>
+    </>
+  )
+}
+
+export default MemoryGame;

BIN
src/components/memory/.DS_Store


BIN
src/components/memory/Images/.DS_Store


BIN
src/components/memory/Images/default.jpg


+ 18 - 0
src/components/memory/Images/index.js

@@ -0,0 +1,18 @@
+import Img1 from './p1.png';
+import Img2 from './p2.png';
+import Img3 from './p3.png';
+import Img4 from './p4.png';
+import Img5 from './p5.png';
+import Img6 from './p6.png';
+import Default from './default.jpg';
+
+export const Images = [
+  Img1,
+  Img2,
+  Img3,
+  Img4,
+  Img5,
+  Img6,
+];
+
+export const DefaultImg = Default;

BIN
src/components/memory/Images/p1.png


BIN
src/components/memory/Images/p2.png


BIN
src/components/memory/Images/p3.png


BIN
src/components/memory/Images/p4.png


BIN
src/components/memory/Images/p5.png


BIN
src/components/memory/Images/p6.png


+ 44 - 0
src/components/memory/resultModal/index.tsx

@@ -0,0 +1,44 @@
+import * as React from 'react';
+import Modal from 'react-modal';
+
+const customStyles = {
+  content: {
+    top: '50%',
+    left: '50%',
+    right: 'auto',
+    bottom: 'auto',
+    marginRight: '-50%',
+    transform: 'translate(-50%, -50%)'
+  }
+};
+
+
+interface PropTypes {
+  isModalOpen: boolean,
+  onClose(event: React.MouseEvent<Element, MouseEvent>):void ,
+  title: string,
+  renderItems: object
+}
+
+
+const ModalComponent = ({
+  isModalOpen,
+  onClose,
+  title,
+  renderItems
+}: PropTypes) => {
+  return (
+    <div>
+      <Modal
+        isOpen={isModalOpen}
+        onRequestClose={onClose}
+        style={customStyles}
+        contentLabel={title}
+      >
+        {renderItems}
+      </Modal>
+    </div>
+  )
+}
+
+export default ModalComponent;

+ 143 - 0
src/components/memory/uiComponent/index.tsx

@@ -0,0 +1,143 @@
+import React, { useReducer, useEffect, useState } from 'react';
+import { Flip } from 'react-awesome-reveal';
+import ModalComponent from '../resultModal';
+import { Images } from '../Images';
+import { DefaultImg } from '../Images';
+import '../uiComponent/ui.css';
+
+type Actions =
+  | { type: 'CARD_CLICKED', index: number }
+  | { type: 'RESTART' }
+
+interface CardTypes {
+  id: number,
+  imgUrl?: string,
+  open?: boolean,
+}
+
+interface IState {
+  mCards: CardTypes[],
+  clickedCardId: Array<any>,
+  clickedCardCount: number;
+  resultStatus?: boolean
+}
+
+const shuffleCards = () => {
+  let updatedCards = [];
+  for (var i = 0; i < 12; i++) {
+    updatedCards.push({
+      imgUrl: Images[Math.floor(i / 2)],
+      id: i,
+      open: false
+    })
+  }
+  updatedCards.sort(() => Math.random() - 0.5);
+  return updatedCards;
+}
+
+const initialState: IState = { mCards: shuffleCards(), clickedCardCount: 0, clickedCardId: [],resultStatus:false };
+
+const checkResult = (cards: Array<CardTypes>) => {
+  let value = true;
+  cards.map(eachCard => {
+    if (eachCard.open === false) {
+      value = false;
+    }
+    return 1;
+  })
+  if (value) {
+    return value
+  }
+
+}
+
+const reducer: React.Reducer<IState, Actions> = (state, action) => {
+  switch (action.type) {
+    
+    case 'CARD_CLICKED':
+      let value = [...state.mCards];
+      value[action.index].open = true;
+
+      let updatedClickedId = [...state.clickedCardId];
+      if (updatedClickedId.length === 2) {
+        if (value[updatedClickedId[0]].imgUrl !== value[updatedClickedId[1]].imgUrl) {
+          value[updatedClickedId[0]].open = false;
+          value[updatedClickedId[1]].open = false;
+        }
+        updatedClickedId.splice(0, 2);
+      }
+      let result = checkResult([...state.mCards]);
+
+
+      updatedClickedId.push(action.index);
+
+      return { ...state, mCards: value, clickedCardId: updatedClickedId, clickedCardCount: state.clickedCardCount + 1,resultStatus: result };
+
+      case 'RESTART':
+        return { mCards: shuffleCards(), clickedCardCount: 0, clickedCardId: [],resultStatus:false };
+       
+    default:
+      throw new Error();
+  }
+}
+
+const Uirender: React.FC = () => {
+  const [state, dispatch] = useReducer<React.Reducer<IState, Actions>>(reducer, initialState);
+  const [isResultOpen ,setIsResultOpen] = useState<boolean>(false);
+
+  useEffect(() => {
+    if(state.resultStatus){
+      setIsResultOpen(true);
+    }
+  }, [state.resultStatus])
+
+  const handleClick = (id: number, isOpen: boolean) => {
+    if (!isOpen)
+      dispatch({ type: 'CARD_CLICKED', index: id })
+  }
+
+  const handleModalClose = () => {
+   setIsResultOpen(false);
+  }
+
+  const handleRestart = () => {
+    dispatch({type:'RESTART'})
+    handleModalClose();
+  }
+
+  return (
+
+
+    <div className="container">
+      {state.mCards && state.mCards.map((eachCard, i) =>
+        <Flip key={eachCard.id}>
+          <div>
+            <figure className="figure-block">
+              <img className="img-block"
+                src={eachCard.open ? eachCard.imgUrl : DefaultImg}
+                alt="Memory"
+                onClick={() => handleClick(i, eachCard.open || false)} />
+            </figure>
+          </div>
+        </Flip>
+      )}
+      
+      {state.resultStatus &&
+       <ModalComponent
+        isModalOpen = { isResultOpen }
+        title = { 'Result' }
+        onClose = { handleModalClose }
+        renderItems = {
+          <>
+           <h3>You Won!</h3>
+           <button onClick = { handleRestart } className='button-class'>Restart</button>
+          </>
+        }
+        />
+      }
+    </div>
+  );
+}
+export default Uirender;
+
+

+ 54 - 0
src/components/memory/uiComponent/ui.css

@@ -0,0 +1,54 @@
+.container {
+    display: flex;
+    border: 1px solid;
+    width: 1150px;
+    flex-wrap: wrap;
+    margin: 0 0 0 30px;
+}
+.figure-block{
+  width: 200px;
+  height: 200px;
+}
+
+.figure-block :hover
+{
+  box-shadow:
+  1px 1px #53a7ea,
+  2px 2px #53a7ea,
+  3px 3px #53a7ea;
+-webkit-transform: translateX(-3px);
+transform: translateX(-3px);
+}
+
+.img-block {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ object-position: center;
+ border-radius: 15px;
+}
+
+.button-class{
+  padding: 10px;
+  font-weight: bold;
+  font-size: 18px;
+  width: 100px !important;
+  height: 50px !important;
+  font-family: Sans-Serif;
+  text-transform: uppercase;
+  height: auto;
+  width: inherit;
+  text-shadow: 4px 4px 3px rgba(77, 77, 77, 0.5);
+  box-shadow: 14px 14px rgba(77, 77, 77, 0.1);
+  cursor: pointer;
+  transition: transform 0.4s, box-shadow 0.4s;
+}
+
+.button-class:hover {
+  box-shadow: 14px 14px rgba(77, 77, 77, 0.5);
+}
+
+.button-class:active {
+  transform: translate3d(7px, 7px, 0px);
+  box-shadow: 7px 7px rgba(77, 77, 77, 0.5);
+}

+ 14 - 0
src/index.css

@@ -0,0 +1,14 @@
+body {
+  margin-left: 110px;
+  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
+    'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
+    sans-serif;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+  cursor: pointer;
+}
+
+code {
+  font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
+    monospace;
+}

+ 17 - 0
src/index.tsx

@@ -0,0 +1,17 @@
+import React from 'react';
+import ReactDOM from 'react-dom';
+import './index.css';
+import App from './App';
+import * as serviceWorker from './serviceWorker';
+
+ReactDOM.render(
+  <React.StrictMode>
+    <App />
+  </React.StrictMode>,
+  document.getElementById('root')
+);
+
+// If you want your app to work offline and load faster, you can change
+// unregister() to register() below. Note this comes with some pitfalls.
+// Learn more about service workers: https://bit.ly/CRA-PWA
+serviceWorker.unregister();

+ 20 - 0
src/logo.svg

@@ -0,0 +1,20 @@
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
+ "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
+<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
+ width="275.000000pt" height="183.000000pt" viewBox="0 0 275.000000 183.000000"
+ preserveAspectRatio="xMidYMid meet">
+<metadata>
+Created by potrace 1.16, written by Peter Selinger 2001-2019
+</metadata>
+<g transform="translate(0.000000,183.000000) scale(0.100000,-0.100000)"
+fill="#000000" stroke="none">
+<path d="M1030 915 l0 -345 345 0 345 0 0 345 0 345 -345 0 -345 0 0 -345z
+m398 -2 c3 -33 3 -33 -42 -33 l-46 0 0 -125 0 -125 -35 0 -35 0 0 125 0 125
+-44 0 c-50 0 -61 10 -52 46 l7 25 122 -3 122 -3 3 -32z m190 17 c39 -24 40
+-33 7 -54 -23 -15 -27 -15 -42 -1 -20 21 -47 15 -51 -11 -2 -16 8 -25 49 -44
+62 -30 89 -62 89 -107 0 -60 -75 -107 -144 -89 -26 6 -86 52 -86 65 0 4 12 13
+26 20 22 10 30 9 44 -4 24 -21 84 -16 88 7 2 12 -12 25 -47 42 -67 34 -91 62
+-91 104 0 24 8 42 29 63 35 35 81 38 129 9z"/>
+</g>
+</svg>

+ 1 - 0
src/react-app-env.d.ts

@@ -0,0 +1 @@
+/// <reference types="react-scripts" />

+ 149 - 0
src/serviceWorker.ts

@@ -0,0 +1,149 @@
+// This optional code is used to register a service worker.
+// register() is not called by default.
+
+// This lets the app load faster on subsequent visits in production, and gives
+// it offline capabilities. However, it also means that developers (and users)
+// will only see deployed updates on subsequent visits to a page, after all the
+// existing tabs open on the page have been closed, since previously cached
+// resources are updated in the background.
+
+// To learn more about the benefits of this model and instructions on how to
+// opt-in, read https://bit.ly/CRA-PWA
+
+const isLocalhost = Boolean(
+  window.location.hostname === 'localhost' ||
+    // [::1] is the IPv6 localhost address.
+    window.location.hostname === '[::1]' ||
+    // 127.0.0.0/8 are considered localhost for IPv4.
+    window.location.hostname.match(
+      /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
+    )
+);
+
+type Config = {
+  onSuccess?: (registration: ServiceWorkerRegistration) => void;
+  onUpdate?: (registration: ServiceWorkerRegistration) => void;
+};
+
+export function register(config?: Config) {
+  if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
+    // The URL constructor is available in all browsers that support SW.
+    const publicUrl = new URL(
+      process.env.PUBLIC_URL,
+      window.location.href
+    );
+    if (publicUrl.origin !== window.location.origin) {
+      // Our service worker won't work if PUBLIC_URL is on a different origin
+      // from what our page is served on. This might happen if a CDN is used to
+      // serve assets; see https://github.com/facebook/create-react-app/issues/2374
+      return;
+    }
+
+    window.addEventListener('load', () => {
+      const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
+
+      if (isLocalhost) {
+        // This is running on localhost. Let's check if a service worker still exists or not.
+        checkValidServiceWorker(swUrl, config);
+
+        // Add some additional logging to localhost, pointing developers to the
+        // service worker/PWA documentation.
+        navigator.serviceWorker.ready.then(() => {
+          console.log(
+            'This web app is being served cache-first by a service ' +
+              'worker. To learn more, visit https://bit.ly/CRA-PWA'
+          );
+        });
+      } else {
+        // Is not localhost. Just register service worker
+        registerValidSW(swUrl, config);
+      }
+    });
+  }
+}
+
+function registerValidSW(swUrl: string, config?: Config) {
+  navigator.serviceWorker
+    .register(swUrl)
+    .then(registration => {
+      registration.onupdatefound = () => {
+        const installingWorker = registration.installing;
+        if (installingWorker == null) {
+          return;
+        }
+        installingWorker.onstatechange = () => {
+          if (installingWorker.state === 'installed') {
+            if (navigator.serviceWorker.controller) {
+              // At this point, the updated precached content has been fetched,
+              // but the previous service worker will still serve the older
+              // content until all client tabs are closed.
+              console.log(
+                'New content is available and will be used when all ' +
+                  'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
+              );
+
+              // Execute callback
+              if (config && config.onUpdate) {
+                config.onUpdate(registration);
+              }
+            } else {
+              // At this point, everything has been precached.
+              // It's the perfect time to display a
+              // "Content is cached for offline use." message.
+              console.log('Content is cached for offline use.');
+
+              // Execute callback
+              if (config && config.onSuccess) {
+                config.onSuccess(registration);
+              }
+            }
+          }
+        };
+      };
+    })
+    .catch(error => {
+      console.error('Error during service worker registration:', error);
+    });
+}
+
+function checkValidServiceWorker(swUrl: string, config?: Config) {
+  // Check if the service worker can be found. If it can't reload the page.
+  fetch(swUrl, {
+    headers: { 'Service-Worker': 'script' }
+  })
+    .then(response => {
+      // Ensure service worker exists, and that we really are getting a JS file.
+      const contentType = response.headers.get('content-type');
+      if (
+        response.status === 404 ||
+        (contentType != null && contentType.indexOf('javascript') === -1)
+      ) {
+        // No service worker found. Probably a different app. Reload the page.
+        navigator.serviceWorker.ready.then(registration => {
+          registration.unregister().then(() => {
+            window.location.reload();
+          });
+        });
+      } else {
+        // Service worker found. Proceed as normal.
+        registerValidSW(swUrl, config);
+      }
+    })
+    .catch(() => {
+      console.log(
+        'No internet connection found. App is running in offline mode.'
+      );
+    });
+}
+
+export function unregister() {
+  if ('serviceWorker' in navigator) {
+    navigator.serviceWorker.ready
+      .then(registration => {
+        registration.unregister();
+      })
+      .catch(error => {
+        console.error(error.message);
+      });
+  }
+}

+ 5 - 0
src/setupTests.ts

@@ -0,0 +1,5 @@
+// jest-dom adds custom jest matchers for asserting on DOM nodes.
+// allows you to do things like:
+// expect(element).toHaveTextContent(/react/i)
+// learn more: https://github.com/testing-library/jest-dom
+import '@testing-library/jest-dom/extend-expect';

+ 25 - 0
tsconfig.json

@@ -0,0 +1,25 @@
+{
+  "compilerOptions": {
+    "target": "es5",
+    "lib": [
+      "dom",
+      "dom.iterable",
+      "esnext"
+    ],
+    "allowJs": true,
+    "skipLibCheck": true,
+    "esModuleInterop": true,
+    "allowSyntheticDefaultImports": true,
+    "strict": true,
+    "forceConsistentCasingInFileNames": true,
+    "module": "esnext",
+    "moduleResolution": "node",
+    "resolveJsonModule": true,
+    "isolatedModules": true,
+    "noEmit": true,
+    "jsx": "react"
+  },
+  "include": [
+    "src"
+  ]
+}