Skip to content

Commit

Permalink
stats phases code organization styling webmanifest and favicons
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelkolesidis committed May 30, 2023
1 parent fbaf5be commit b2829a7
Show file tree
Hide file tree
Showing 38 changed files with 484 additions and 125 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# Scratch Bonanza

# Logs
logs
*.log
Expand Down
1 change: 1 addition & 0 deletions FUNDING.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
github: michaelkolesidis
46 changes: 35 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,17 @@ An online scratchcard game. Do you feel lucky?

## Instructions

In order to run the project locally you need to start both the client (this repository) and the server, that can be found in its own repository, [Scratch Bonanza Server](https://github.com/michaelkolesidis/scratch-bonanza-server)
In order to run the project locally you need to start both the client (this repository) and the server, that can be found in its own repository, [Scratch Bonanza Server](https://github.com/michaelkolesidis/scratch-bonanza-server).

Install the project dependencies:
**1.** Start the server.

**2.** Install the project dependencies:

```
yarn
```

Start Vite:
**3.** Start Vite:

```
yarn dev
Expand All @@ -22,20 +24,45 @@ yarn dev

There is also an online deployment of _Scratch Bonanza_ that can be found in [here](https://scratch-bonanza.vercel.app/). The online version is deployed in [Vercel](https://vercel.com/) and is configured to use an online deployment of the server, deployed in [Render](https://render.com/).

When using the online version, you should note that loading the first scratchcard will usually take around 15-20 seconds, as the project is hosted using the free tier of Render, thus the server sleeps when inactive.
When using the online version, you should note that loading the first scratchcard will usually take around 30-50 seconds, as the project is hosted using the free tier of Render, thus the server sleeps when inactive. On rare occasions, the server might take up to 5 minutes to respond to the first call.

## Features

### Architecture

- Three game phases: ready, playing, ended
- Total number of coins won saved both in state and in local storage
- Number of scratchcards used saved both in state and in local storage
- Clear data functionality

### Gameplay

- Each scratchcard features four scratchable areas that have a reward hidden underneath
- Five different reward options: **1**🪙, **10**🪙, **100**🪙, **1000**🪙 or **0**🍌
- Scratching sound while scratching implemented both on touch and on non-touch devices
- Three different success sounds when revealing an award (a sound for 1 coin, another sound for 10 coins, and another sound for 100 and 1000 coins)

### UI/UX

- New game and restart functionality
- Stats bar at the bottom disaplying total coins won and total scratchcards used
- New button is active only when all four areas have been scratched
- Help modal available in all screens
- Responsive for mobile (iPhone 13 Pro and iPhone SE) and desktop
- Animated logo

### Sound

- Scratching sound while scratching implemented both on touch and on non-touch devices
- Three different success sounds when revealing an award (a sound for 1 coin, another sound for 10 coins, and another sound for 100 and 1000 coins)
- Sound when a new card loads

## Screenshots

![Home screen](./screenshots/screenshot_01.png)

![Play screen](./screenshots/screenshot_02.png)

![Help](./screenshots/screenshot_03.png)

## Technologies

The core technologies of _Scratch Bonanza_ are JavaScript, CSS and the Canvas API. The following libraries and tools are used:
Expand All @@ -49,17 +76,14 @@ The core technologies of _Scratch Bonanza_ are JavaScript, CSS and the Canvas AP
| Zustand | MIT | State management |
| Vite | MIT | Frontend development tooling |
| Jest (Planned) | MIT | JavaScript testing framework |
| React Testing Library (Planned) | MIT | Testing utilitie |
| React Testing Library (Planned) | MIT | Testing utility |

## Roadmap

- Testing
- Different game modes with different sets of awards
- Total score
- Credits
- Stats

## Screenshots
- Translations

## Gameplay and Rules

Expand Down
5 changes: 5 additions & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Scratch Bonanza Security Policy

## Reporting a Vulnerability

In case of finding a vulnerabiity, please open an issue. If the vulnerability is related any of the libraries/frameworks used, please contact the respective authors as well.
8 changes: 7 additions & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,13 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.ico" />
<link rel="icon" type="image/svg+xml" href="/icons/favicon.ico" />
<link
rel="apple-touch-icon"
sizes="180x180"
href="/icons/apple-touch-icon.png"
/>
<link rel="manifest" href="/manifest.webmanifest" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="author" content="Michael Kolesidis" />
<meta
Expand Down
21 changes: 21 additions & 0 deletions manifest.webmanifest
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"name": "Scratch Bonanza",
"short_name": "Scratch Bonanza",
"description": "An online scratchcard game. Do you feel lucky?",
"icons": [
{
"src": "/icons/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/icons/android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"start_url": "index.html",
"display": "standalone",
"theme_color": "#cf0032",
"background_color": "#cf0032"
}
Binary file added public/assets/logo_horizontal.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/icons/android-chrome-192x192.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/icons/android-chrome-512x512.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/icons/apple-touch-icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
File renamed without changes.
2 changes: 2 additions & 0 deletions robots.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
User-agent: *
Disallow:
Binary file added screenshots/screenshot_01.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added screenshots/screenshot_02.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added screenshots/screenshot_03.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 14 additions & 0 deletions src/components/helpButton/HelpButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import "./style.css";
import useGame from "../../stores/useGame";

const HelpButton = () => {
const { setModal } = useGame();

const handleHelp = () => {
setModal(true);
};

return <div onClick={handleHelp} className="help-button" />;
};

export default HelpButton;
9 changes: 9 additions & 0 deletions src/components/helpButton/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
.help-button {
position: absolute;
top: 10px;
right: 10px;
background-image: url("/assets/help.svg");
height: min(40px, 10vw);
width: min(40px, 10vw);
cursor: pointer;
}
22 changes: 22 additions & 0 deletions src/components/mainButton/MainButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import "./style.css";

const MainButton = ({
handleClick,
text,
disabled,
}: {
handleClick: () => void;
text: string;
disabled?: boolean;
}) => {
return (
<div
onClick={handleClick}
className={`button-main ${disabled && "disabled-button"}`}
>
{text}
</div>
);
};

export default MainButton;
60 changes: 60 additions & 0 deletions src/components/mainButton/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
.button-main {
width: min(220px, 45vw);
background: linear-gradient(rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.13)),
radial-gradient(
90% 7% at 50% 8%,
rgba(255, 255, 255, 0.47) 25%,
rgba(255, 255, 255, 0) 50%
),
#debb50;
border: 0;
border-radius: 0.375em;
box-shadow: 0.2em 0.2em 0.5em rgba(0, 0, 0, 0.47),
0 -0.1em 0 0.1em rgba(0, 0, 0, 0.27),
0 0.1em 0 0.1em rgba(255, 255, 255, 0.27), -0.2em 0 0.2em #d3a928 inset,
0 0.2em 0.2em rgba(255, 255, 255, 0.27) inset,
0.2em 0 0.2em rgba(255, 255, 255, 0.27) inset, 0 -0.2em 0.2em #d3a928 inset;
cursor: pointer;
margin: 1em auto 0 auto;
padding: 0.5em 1em;
font-size: min(7vw, 40px);
font-weight: 600;
color: #ffffff;
text-align: center;
text-shadow: 0 0 0.3em rgb(0, 0, 0);
letter-spacing: 0.1em;
cursor: pointer;
}

.button-main,
.button-main span {
display: block;
transition-duration: 0.1s;
transition-timing-function: linear;
}

.button-main:focus,
.button-main span:focus {
outline: none;
}

.button-main span {
transition-property: transform;
will-change: transform;
}

.button-main:active {
box-shadow: 0 0 0 rgba(0, 0, 0, 0.47), 0 -0.1em 0 0.1em rgba(0, 0, 0, 0.27),
0 0.1em 0 0.1em rgba(255, 255, 255, 0.27), -0.2em 0 0.2em #deb53b inset,
0 0.2em 0.2em rgba(0, 0, 0, 0.27) inset,
0.2em 0 0.2em rgba(0, 0, 0, 0.27) inset, 0 -0.2em 0.2em #e1b739 inset;
}

.button-main:active span {
transform: scale(0.95);
}

.button-main:focus {
color: #decc97;
text-shadow: 0 0 0.2em rgba(126, 126, 126, 0.47);
}
16 changes: 15 additions & 1 deletion src/components/modal/Modal.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
import "./style.css";
import useGame from "../../stores/useGame";
import { clearLocalStorage } from "../../stores/utils";

const Modal = () => {
const { resetCards, resetCoins } = useGame();

export const Modal = () => {
const { setModal } = useGame();

const handleClear = () => {
clearLocalStorage();
resetCards();
resetCoins();
};

return (
<div className="modal" onClick={() => setModal(false)}>
<div className="modal-box" onClick={(e) => e.stopPropagation()}>
Expand All @@ -22,8 +31,13 @@ export const Modal = () => {
mouse around.
</div>
<div className="modal-text">Scratch your way to great prizes!</div>
<div className="modal-button" onClick={handleClear}>
Clear Data
</div>
</div>
</div>
</div>
);
};

export default Modal;
19 changes: 17 additions & 2 deletions src/components/modal/style.css
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
@import url("https://fonts.googleapis.com/css2?family=Inter:wght@500&display=swap");

.modal {
position: fixed;
top: 0;
Expand Down Expand Up @@ -35,3 +33,20 @@
text-align: left;
font-size: min(4vw, 20px);
}

.modal-button {
letter-spacing: 0.05em;
text-align: left;
font-size: min(4vw, 20px);
background: #000;
color: #fff;
text-align: center;
width: 60%;
margin: auto;
padding: min(4vw, 20px);
border-radius: 10px;
}

.modal-button:active {
background: #d2b14c;
}
12 changes: 6 additions & 6 deletions src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ document.addEventListener("contextmenu", (e) => e.preventDefault());

ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
// <React.StrictMode>
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="play" element={<Play />} />
</Routes>
</BrowserRouter>
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="play" element={<Play />} />
</Routes>
</BrowserRouter>
// </React.StrictMode>
);
18 changes: 12 additions & 6 deletions src/pages/home/Home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,29 @@ import "./style.css";
import { useEffect } from "react";
import { useNavigate } from "react-router-dom";
import useGame from "../../stores/useGame";
import { Modal } from "../../components/modal/Modal";
import Modal from "../../components/modal/Modal";
import MainButton from "../../components/mainButton/MainButton";
import HelpButton from "../../components/helpButton/HelpButton";

export const Home = () => {
const navigate = useNavigate();
const { modal, setModal, resetRevealed } = useGame();
const { modal, resetRevealed, setPhase } = useGame();

useEffect(() => {
setPhase("ready");
resetRevealed();
}, []);

const handlePlay = () => {
navigate("/play");
};

return (
<div className="home-page">
<div className="home-page-logo" />
<div onClick={() => navigate("/play")} className="button-main">
PLAY
</div>
<div onClick={() => setModal(true)} className="help-button" />
<MainButton handleClick={handlePlay} text="PLAY" />
<HelpButton />
<div className="copyright">© 2023 Michael Kolesidis</div>
{modal && <Modal />}
</div>
);
Expand Down
10 changes: 9 additions & 1 deletion src/pages/home/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
}

.home-page-logo {
height: 65vh;
height: 60.75vh;
width: min(500px, 100vw);
background-image: url("/assets/logo.png");
background-repeat: no-repeat;
Expand Down Expand Up @@ -37,3 +37,11 @@
transform: scale(1, 1) rotateZ(0deg);
}
}

.copyright {
color: #fff;
position: fixed;
bottom: 0;
margin: 1vh 0;
font-size: min(5vw, 20px);
}
Loading

1 comment on commit b2829a7

@vercel
Copy link

@vercel vercel bot commented on b2829a7 May 30, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.