Skip to content

Commit

Permalink
Minor advancements
Browse files Browse the repository at this point in the history
  • Loading branch information
MartinPJB committed Jan 23, 2024
1 parent e738ef2 commit a003296
Show file tree
Hide file tree
Showing 6 changed files with 307 additions and 85 deletions.
142 changes: 142 additions & 0 deletions css/index.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@100;200;300;400;500;600;700;800;900&display=swap');

* {
box-sizing: border-box;
}

html {
font-size: 16px;
min-width: 100%;
min-height: 100%;
overflow-x: hidden;
}

body {
margin: 0;
padding: 0;
font-family: 'Inter', sans-serif;
background-color: #f5f5f5;

width: 100vw;
height: 100vh;
}

/* App */
.app {
display: flex;
flex-direction: row;
min-height: 100%;
}

/* Shop */
.store {
display: flex;
flex-direction: column;
flex: 1;
min-height: 100%;
max-width: 20rem;

background-color: #eee;
border-right: 1px dotted #ccc;
}

.store .store__elements {
list-style: none;
margin: 0;
padding: 0;
}

.store .store__elements .item {
display: block;
margin: 1rem;
position: relative;
}
.store .store__elements .item .item__details {
position: absolute;
z-index: 999;
left: 0;
top: 100%;
margin-top: .25rem;
transform: translateY(-1rem);
padding: .5rem 1rem;
background-color: rgba(0, 0, 0, 0.6);
color: #fff;
user-select: none;
pointer-events: none;
opacity: 0;
transition: opacity .2s ease-in-out, transform .2s ease-in-out;
}
.store .store__elements .item:hover .item__details {
opacity: 1;
transform: translateY(0);
}

.store .store__elements .item .item__buy {
display: block;
width: 100%;
padding: 1rem 1rem;
border: none;
text-align: left;
background-color: rgb(26, 173, 75);
font-family: 'Inter', sans-serif;
color: white;
}

/* Main area */
.content {
display: flex;
flex-direction: column;
flex: 1;
min-height: 100%;
align-items: center;
justify-content: center;
}

.content .content__button {
display: flex;
flex-direction: column;
align-items: center;
}

.content .content__button #clicks {
font-size: 2rem;
font-weight: 700;
margin-bottom: 1rem;
}
.content .content__button #clicks::after {
content: '⚡';
}

.content .content__button #clicker {
margin-top: 1rem;
background-color: transparent;
border: none;
cursor: pointer;
animation: rotate 2s infinite ease-in-out;
}
.content .content__button #clicker .clicker__emoji {
display: block;
margin: 0 auto;
font-size: 10rem;
line-height: 1;
transition: transform .2s ease-in-out;
}
.content .content__button #clicker:hover .clicker__emoji {
transform: scale(1.1);
}
.content .content__button #clicker:active .clicker__emoji {
transform: scale(1);
}

/* Animations */
@keyframes rotate {
0% {
transform: rotate(-5deg);
}
50% {
transform: rotate(5deg);
}
100% {
transform: rotate(-5deg);
}
}
21 changes: 16 additions & 5 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,24 @@
<title>PC clicker</title>

<script src="./js/index.js" type="module" async defer></script>
<link rel="stylesheet" href="./css/index.css">
</head>
<body>
<span id="clicks">0</span>
<button id="clicker">
💻
</button>
<main class="app">
<aside class="store">
<ul class="store__elements" id="store"></ul>
</aside>

<ul id="store"></ul>
<section class="content">
<div class="content__button">
<span id="clicks">0</span>
<button id="clicker">
<span class="clicker__emoji">
💻
</span>
</button>
</div>
</section>
</main>
</body>
</html>
118 changes: 83 additions & 35 deletions js/core/game.js
Original file line number Diff line number Diff line change
@@ -1,85 +1,133 @@
import Store from "./store.js";

/**
* Class representing the game.
*/
export default class Game {
/**
* @param {string} id The id of the button
*
* @property {HTMLElement} button The button
* @property {number} clicks The number of clicks
* @property {number} clicksMultiplier The multiplier of the clicks
* @property {number} clicksPerSecondAdder The number of clicks per second added by the upgrades
* @property {number} cps The number of clicks per second of the user
* @property {number} lastClickTime The time of the last click
* @property {Store} store The store associated with the game
* Creates an instance of Game.
* @param {string} id - The id of the button.
*/
constructor(id) {
/* Properties */
this.button = document.getElementById(id);
this.clicks = 0;
this.clicksMultiplier = 1;
this.clicksPerSecondAdder = 0;
this.cps = 0;
this.clicksAdder = 0;
this.lastClickTime = 0;
this.store = null;

this.clickIntervals = [];
this.maxSameInterval = 8;
this.intervalFunction = null;

this.regularityCounter = 0;
this.regularityThreshold = 10; // If the player is too regular and gets a regularityCounter > regularityThreshold, he's most likely cheating

/* Binds */
this.button.addEventListener("click", this.clickHandler.bind(this));
setInterval(this.interval.bind(this), 1000);
this.intervalFunction = setInterval(this.interval.bind(this), 1000);
}

/* Methods */
/**
* Interval CPS adder
* Interval handler.
*/
interval() {
this.clicks += this.clicksPerSecondAdder;
this.renderSpan();
}

/**
* Displays the amount of clicks on the screen
* Displays the amount of clicks on the screen.
*/
renderSpan() {
document.getElementById("clicks").textContent = this.clicks;
}

/**
* Handles the click event
* Handles the click event.
* @param {Event} e - The click event.
*/
clickHandler() {
clickHandler(e) {
const currentTime = Date.now();
const timeSinceLastClick = currentTime - this.lastClickTime;
if (timeSinceLastClick > 0) {
this.cps = 1000 / timeSinceLastClick;

this.clickIntervals.push(timeSinceLastClick);

if (this.clickIntervals.length > this.maxSameInterval) {
this.clickIntervals.shift();
}

this.clicks += 1 * this.clicksMultiplier;
this.clicks += 1 + this.clicksAdder;
this.lastClickTime = currentTime;

this.renderSpan();
this.cheatDetector();
this.cheatDetector(e);
}

/**
* Detects if the user is cheating and clicking in a humanly impossible way
* Detects if the user is cheating and clicking in a humanly impossible way.
* @param {Event} e - The click event.
*/
cheatDetector() {
const maxCPS = 22;
if (this.cps > maxCPS) {
this.clicks = 0;
this.clicksPerSecondAdder = 0;
this.cps = 0;
this.lastClickTime = 0;

this.button.removeEventListener("click", this.clickHandler);
alert("You're cheating!");
cheatDetector(e) {
const irregularityTolerance = 10; // ms

if (this.checkRegularIntervals(irregularityTolerance)) {
this.regularityCounter++;
}

if (!e.isTrusted) return this.cheating(); // Instant ban
if (this.regularityCounter > this.regularityThreshold) {
console.warn("You're clicking too regularly, you're most likely cheating.");
this.cheating();
}
}

/**
* Sets a store to the game
* A cheating behavior is detected, the game is stopped.
*/
cheating() {
clearInterval(this.intervalFunction);
this.button.remove();
this.resetGameProperties();
document.body.innerHTML = "You're cheating, shame on you!";
}

/**
* Sets a store to the game.
* @param {string} storeID - The id of the store.
*/
setStore(storeID) {
this.store = new Store(storeID, this);
}

/**
* Verifies if clicks are legit by checking their regularity.
* @param {number} tolerance - The tolerance of irregularity.
* @returns {boolean} - True if clicks are too regular, false otherwise.
*/
checkRegularIntervals(tolerance) {
const intervals = this.clickIntervals;
const averageInterval = intervals.reduce((sum, interval) => sum + interval, 0) / intervals.length;

for (let i = 0; i < this.clickIntervals.length; i++) {
const difference = Math.abs(this.clickIntervals[i] - averageInterval);
if (difference > tolerance) {
return false;
}
}

return true;
}

/**
* Resets game properties to their initial values.
*/
resetGameProperties() {
this.clicks = 0;
this.clicksAdder = 0;
this.lastClickTime = 0;
this.store = null;
this.clickIntervals = [];
this.maxSameInterval = 8;
this.intervalFunction = null;
}
}
24 changes: 18 additions & 6 deletions js/core/items.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,25 @@
/**
* Note: all numbers must be integers, not floats.
* Available properties:
* - name: name of the item
* - text: text displayed in the button
* - price: price of the item
* - maxQuantity: max quantity of the item (default: Infinity)
* - cpsAdder: CPS added by the item (default: 0)
* - cpsAdderMultiplier: multiplier of the CPS added by the item (default: 2)
* - clicksAdder: clicks added by the item (default: 0)
*/
export default [
{
name: "Server",
text: "+{{cpsAdder}} cps",

text: "+ {{clicksAdder}} to each click",
price: 10,
clicksAdder: 1,
},
{
name: "Developer",
text: "+ {{cpsAdder}}/s",
price: 100,
cpsAdder: 1,
quantity: 0,
// maxQuantity: 1,

cpsAdderMultiplier: 2,
}
];
Loading

0 comments on commit a003296

Please sign in to comment.