Merge pull request 'Full typescript and mvvm implementation' (#3) from Developer into main

Reviewed-on: Schmidii99/CpuHigherLower#3
This commit is contained in:
Florian Schmid 2023-04-23 20:02:32 +02:00
commit 8eced118f2
10 changed files with 429 additions and 247 deletions

3
.gitignore vendored
View File

@ -1,2 +1,3 @@
.DS_Store
node_modules
node_modules
js/

View File

@ -68,11 +68,8 @@
<a href="https://www.flaticon.com/free-icons/processor" title="processor icons">Processor icons created by kerismaker - Flaticon</a>
</div> -->
<script type="module">
import {main, btnHigherClick, btnLowerClick} from "./game.js"
window.btnHigherClick = btnHigherClick
window.btnLowerClick = btnLowerClick
import { main } from "./js/game.js"
main()
</script>
</body>

115
game.js
View File

@ -1,115 +0,0 @@
import { CountUp } from "https://cdnjs.cloudflare.com/ajax/libs/countup.js/2.6.0/countUp.min.js";
var cpuList;
var currentCpu;
var nextCpu;
// current user score
var score;
var highScore;
function setHighScore(score) {
const currHighScore = localStorage.getItem("highScore_cpu")
if (currHighScore < score) {
localStorage.setItem("highScore_cpu", score)
highScore = score
}
}
export async function main() {
await (fetch('./data.json')
.then((response) => response.json())
.then((json) => cpuList = json));
currentCpu = getRandomCpu();
nextCpu = getRandomCpu();
score = 0;
highScore = localStorage.getItem("highScore_cpu") ?? 0
updateLayout();
}
function getRandomCpu() {
let randomIndex = getRandomInt(0, cpuList.length)
return {
name: cpuList[randomIndex]["name"].split('@')[0],
score: cpuList[randomIndex]["cpuScore"]
}
}
export function btnLowerClick() {
nextCpu.score < currentCpu.score ? showResult(true) : showResult(false);
}
export function btnHigherClick() {
nextCpu.score > currentCpu.score ? showResult(true) : showResult(false);
}
function showResult(isCorrect) {
document.getElementById("btnHigher").setAttribute("disabled", "");
document.getElementById("btnLower").setAttribute("disabled", "");
document.getElementById("col2").style.backgroundColor = isCorrect ? "lightgreen" : "#FF4444";
if (!isCorrect) {
setHighScore(score)
}
score = isCorrect ? score + 1 : 0;
if (score > highScore) {
highScore = score;
document.getElementById("highScore").innerText = highScore;
}
document.getElementById("score").innerText = score;
countUp();
}
// updates view based on the cpu objects
function updateLayout() {
document.getElementById("highScore").innerText = highScore;
document.getElementById("currentCpuTitle").innerText = currentCpu.name;
// add "." to large numbers
document.getElementById("currentCpuScore").innerText = new Intl.NumberFormat().format(currentCpu.score)
document.getElementById("nextCpuTitle").innerText = nextCpu.name;
document.getElementById("nextCpuScore").innerText = "?";
document.getElementById("col2").style.backgroundColor = "";
}
function delay(time) {
return new Promise(resolve => setTimeout(resolve, time));
}
async function countUp() {
const options = {
startVal: nextCpu.score / 2,
separator: '.',
decimal: ',',
duration: 2
};
let counter = new CountUp('nextCpuScore', nextCpu.score, options);
if (!counter.error) {
counter.start();
} else {
counter.error(demo.error);
}
await delay(2500)
nextRound();
document.getElementById("btnHigher").removeAttribute("disabled");
document.getElementById("btnLower").removeAttribute("disabled");
}
function nextRound() {
currentCpu = nextCpu;
nextCpu = getRandomCpu();
updateLayout();
}
function getRandomInt(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min) + min); // The maximum is exclusive and the minimum is inclusive
}

View File

@ -32,7 +32,7 @@
</li>
<li class="nav-item">
<a class="nav-link" href="about.html">About</a>
</li>
</li>
</ul>
</div>
</div>
@ -68,8 +68,7 @@
</div>
<script type="module">
import {main, btnClick} from "./gst.js"
window.btnClick = btnClick
import { main } from "./js/gst.js"
main()
</script>
</body>

124
gst.js
View File

@ -1,124 +0,0 @@
var cpuList;
var currentCpu;
var score;
var highScore;
export async function main() {
// init cpu list
await (fetch('./data.json')
.then((response) => response.json())
.then((json) => cpuList = json));
highScore = localStorage.getItem("highScore_socket") ?? 0
score = 0;
updateScores();
nextRound();
}
function nextRound() {
currentCpu = getRandomCpu();
document.getElementById("cpuName").innerText = currentCpu.name;
document.getElementById("mainCol").style.backgroundColor = "";
}
function getRandomCpu() {
let randomIndex;
do {
randomIndex = getRandomInt(0, cpuList.length)
} while (typeof(cpuList[randomIndex]["type"]) == null || cpuList[randomIndex]["type"] == "null" || cpuList[randomIndex]["type"] == null);
return {
name: cpuList[randomIndex]["name"].split('@')[0],
type: cpuList[randomIndex]["type"]
}
}
function delay(time) {
return new Promise(resolve => setTimeout(resolve, time));
}
export async function btnClick(typ) {
// 0 -> Desktop
// 1 -> Laptop
// 2 -> Mobile/Embedded
// 3 -> Server
let btnDesktop = document.getElementById("btnDesktop");
let btnLaptop = document.getElementById("btnLaptop");
let btnMobile = document.getElementById("btnMobile");
let btnServer = document.getElementById("btnServer");
let btns = [btnDesktop, btnLaptop, btnMobile, btnServer];
btns.forEach((el) => {
el.setAttribute("disabled", "");
})
switch (typ) {
case 0:
btnDesktop.style.backgroundColor = "#FF4444";
break;
case 1:
btnLaptop.style.backgroundColor = "#FF4444";
break;
case 2:
btnMobile.style.backgroundColor = "#FF4444";
break;
case 3:
btnServer.style.backgroundColor = "#FF4444";
break;
}
if(currentCpu.type.includes("Desktop")) {
btnDesktop.style.backgroundColor = "lightgreen";
}
if(currentCpu.type.includes("Laptop")) {
btnLaptop.style.backgroundColor = "lightgreen";
}
if(currentCpu.type.includes("Mobile/Embedded")) {
btnMobile.style.backgroundColor = "lightgreen";
}
if(currentCpu.type.includes("Server")) {
btnServer.style.backgroundColor = "lightgreen";
}
// Score
if (btns[typ].style.backgroundColor == "lightgreen") {
score++;
} else {
score = 0;
}
// Highscore
if (score > highScore) {
highScore = score;
localStorage.setItem("highScore_socket", highScore);
}
updateScores();
await delay(1000);
btns.forEach( (el) => {
el.style.backgroundColor = "#3CC3FA";
el.removeAttribute("disabled");
})
nextRound();
}
function updateScores() {
document.getElementById("score").innerText = score;
document.getElementById("highScore").innerText = highScore;
}
function getRandomInt(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min) + min); // The maximum is exclusive and the minimum is inclusive
}
main();

46
src/cpuRepository.ts Normal file
View File

@ -0,0 +1,46 @@
export class CpuRepository {
#cpuList;
#currentCPU;
#nextCPU;
async init() {
const fetchResult = await fetch("../data.json");
this.#cpuList = await fetchResult.json();
this.reset();
}
get currentCpu() {
return this.#currentCPU;
}
get nextCpu() {
return this.#nextCPU;
}
getRandomCpu() {
let randomIndex: number;
do {
randomIndex = this.#getRandomInt(0, this.#cpuList.length);
} while (this.#cpuList[randomIndex]["value"] == null || this.#cpuList[randomIndex]["type"] == null)
this.#cpuList[randomIndex]["name"] = this.#cpuList[randomIndex]["name"].split('@')[0];
return this.#cpuList[randomIndex];
}
#getRandomInt(min, max): number {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min) + min); // The maximum is exclusive and the minimum is inclusive
}
nextRound(): void {
this.#currentCPU = this.#nextCPU
this.#nextCPU = this.getRandomCpu();
}
reset(): void {
this.#currentCPU = this.getRandomCpu();
this.#nextCPU = this.getRandomCpu();
}
}

168
src/game.ts Normal file
View File

@ -0,0 +1,168 @@
// @ts-ignore
import { CountUp } from "https://cdnjs.cloudflare.com/ajax/libs/countup.js/2.6.0/countUp.min.js";
import { Stats } from "./statistics.js";
import { CpuRepository } from "./cpuRepository.js"
class UI {
#model: ViewModel;
#btnLower: HTMLElement;
#btnHigher: HTMLElement;
#btnScore: HTMLElement;
#btnHighScore: HTMLElement;
#curCpuTitle: HTMLElement;
#curCpuScore: HTMLElement;
#nextCpuTitle: HTMLElement;
#nextCpuScore: HTMLElement;
#background: HTMLElement;
constructor() {
this.#model = new ViewModel();
}
async init() {
await this.#model.init();
this.#btnLower = document.getElementById("btnLower");
this.#btnHigher = document.getElementById("btnHigher");
this.#btnScore = document.getElementById("score");
this.#btnHighScore = document.getElementById("highScore");
this.#curCpuTitle = document.getElementById("currentCpuTitle");
this.#curCpuScore = document.getElementById("currentCpuScore");
this.#nextCpuTitle = document.getElementById("nextCpuTitle");
this.#nextCpuScore = document.getElementById("nextCpuScore");
this.#background = document.getElementById("col2");
this.#btnLower.onclick = () => this.handleButtonLowerClick();
this.#btnHigher.onclick = () => this.handleButtonHigherClick();
this.updateLayout();
}
updateLayout() {
this.#btnHighScore.innerText = this.#model.highScore.toString();
this.#curCpuTitle.innerText = this.#model.currentCpu.name;
// add "." to large numbers
this.#curCpuScore.innerText = new Intl.NumberFormat().format(this.#model.currentCpu.cpuScore);
this.#nextCpuTitle.innerText = this.#model.nextCpu.name;
this.#nextCpuScore.innerText = "?";
this.#background.style.backgroundColor = "";
}
handleButtonLowerClick() {
let result = this.#model.buttonClicked("lower");
this.#showResult(result);
}
handleButtonHigherClick() {
let result = this.#model.buttonClicked("higher");
this.#showResult(result);
}
#showResult(isCorrect) {
this.#btnHigher.setAttribute("disabled", "");
this.#btnLower.setAttribute("disabled", "");
this.#background.style.backgroundColor = isCorrect ? "lightgreen" : "#FF4444";
isCorrect ? this.#model.incrementScore() : this.#model.resetScore();
this.#btnHighScore.innerText = this.#model.highScore.toString();
this.#btnScore.innerText = this.#model.score.toString();
this.#countUp();
}
#delay(time) {
return new Promise(resolve => setTimeout(resolve, time));
}
async #countUp() {
const options = {
startVal: this.#model.nextCpu.cpuScore / 2,
separator: '.',
decimal: ',',
duration: 2
};
let counter = new CountUp('nextCpuScore', this.#model.nextCpu.cpuScore, options);
if (!counter.error) {
counter.start();
} else {
console.log(counter.error);
}
await this.#delay(2500)
this.#model.nextRound();
this.#btnHigher.removeAttribute("disabled");
this.#btnLower.removeAttribute("disabled");
this.updateLayout();
}
}
class ViewModel {
#repo: CpuRepository;
#localStats: Stats;
constructor() {
this.#repo = new CpuRepository();
this.#localStats = new Stats("highScore_cpu");
}
async init() {
await this.#repo.init();
}
nextRound(): void {
this.#repo.nextRound();
}
// "lower" and "higher"
buttonClicked(value: string): boolean {
if (value == "higher") {
return this.#repo.nextCpu.cpuScore > this.#repo.currentCpu.cpuScore;
}
if (value == "lower") {
return this.#repo.nextCpu.cpuScore < this.#repo.currentCpu.cpuScore;
}
console.log("nothing found for '" + value + "'");
return false;
}
incrementScore() {
this.#localStats.incrementScore();
}
resetScore() {
this.#localStats.resetScore();
}
get currentCpu() {
return this.#repo.currentCpu;
}
get nextCpu() {
return this.#repo.nextCpu;
}
get highScore() {
return this.#localStats.highScore;
}
get score() {
return this.#localStats.score;
}
}
export async function main() {
const ui = new UI();
await ui.init();
}

166
src/gst.ts Normal file
View File

@ -0,0 +1,166 @@
import { CpuRepository } from "./cpuRepository.js";
import { Stats } from "./statistics.js";
type CPU = {
name: string;
type: string;
}
class UI {
#model: ViewModel;
#btnDesktop: HTMLElement;
#btnLaptop: HTMLElement;
#btnServer: HTMLElement;
#btnMobile: HTMLElement;
#btns: HTMLElement[];
#cpuName: HTMLElement;
#score: HTMLElement;
#highScore: HTMLElement;
#mainCol: HTMLElement;
constructor() {
this.#model = new ViewModel();
this.#btnDesktop = document.getElementById("btnDesktop");
this.#btnDesktop.onclick = () => this.btnClick(0);
this.#btnLaptop = document.getElementById("btnLaptop");
this.#btnLaptop.onclick = () => this.btnClick(1);
this.#btnMobile = document.getElementById("btnMobile");
this.#btnMobile.onclick = () => this.btnClick(2);
this.#btnServer = document.getElementById("btnServer");
this.#btnServer.onclick = () => this.btnClick(3);
this.#btns = [this.#btnDesktop, this.#btnLaptop, this.#btnMobile, this.#btnServer];
this.#cpuName = document.getElementById("cpuName");
this.#score = document.getElementById("score");
this.#highScore = document.getElementById("highScore");
this.#mainCol = document.getElementById("mainCol");
}
async init() {
await this.#model.init();
this.#nextRound();
}
#updateScores() {
this.#score.innerText = this.#model.score.toString();
this.#highScore.innerText = this.#model.highScore.toString();
}
async #delay(time) {
return new Promise(resolve => setTimeout(resolve, time));
}
#nextRound() {
this.#model.nextRound();
this.#cpuName.innerText = this.#model.currentCpu.name;
this.#mainCol.style.backgroundColor = "";
}
async btnClick(typ) {
// 0 -> Desktop
// 1 -> Laptop
// 2 -> Mobile/Embedded
// 3 -> Server
this.#btns.forEach((el) => {
el.setAttribute("disabled", "");
})
switch (typ) {
case 0:
this.#btnDesktop.style.backgroundColor = "#FF4444";
break;
case 1:
this.#btnLaptop.style.backgroundColor = "#FF4444";
break;
case 2:
this.#btnMobile.style.backgroundColor = "#FF4444";
break;
case 3:
this.#btnServer.style.backgroundColor = "#FF4444";
break;
}
const currentType = this.#model.currentCpu.type;
if(currentType.includes("Desktop")) {
this.#btnDesktop.style.backgroundColor = "lightgreen";
}
if(currentType.includes("Laptop")) {
this.#btnLaptop.style.backgroundColor = "lightgreen";
}
if(currentType.includes("Mobile/Embedded")) {
this.#btnMobile.style.backgroundColor = "lightgreen";
}
if(currentType.includes("Server")) {
this.#btnServer.style.backgroundColor = "lightgreen";
}
// Score
if (this.#btns[typ].style.backgroundColor == "lightgreen") {
this.#model.incrementScore();
} else {
this.#model.resetScore();
}
this.#updateScores();
await this.#delay(1000);
this.#btns.forEach( (el) => {
el.style.backgroundColor = "#3CC3FA";
el.removeAttribute("disabled");
})
this.#nextRound();
}
}
class ViewModel {
#repo: CpuRepository;
#stats: Stats;
constructor() {
this.#stats = new Stats("highScore_socket");
this.#repo = new CpuRepository();
}
async init() {
await this.#repo.init();
}
nextRound() {
this.#repo.reset();
}
incrementScore() {
this.#stats.incrementScore();
}
resetScore() {
this.#stats.resetScore();
}
get currentCpu(): CPU {
return this.#repo.currentCpu;
}
get score(): number {
return this.#stats.score;
}
get highScore(): number {
return this.#stats.highScore;
}
}
export async function main() {
const ui = new UI();
await ui.init();
}

34
src/statistics.ts Normal file
View File

@ -0,0 +1,34 @@
export class Stats {
#score: number;
#highScore: number;
#highScoreStorageKey: string;
constructor(highScoreStorageKey) {
// used as key for localStorage
this.#highScoreStorageKey = highScoreStorageKey;
this.#score = 0;
this.#highScore = Number(localStorage.getItem(this.#highScoreStorageKey)) ?? 0;
}
incrementScore(value = 1) {
this.#score += value;
if (this.#highScore < this.#score) {
this.#highScore = this.#score;
localStorage.setItem(this.#highScoreStorageKey, this.#highScore.toString());
}
}
resetScore(): void {
this.#score = 0;
}
get highScore(): number {
return this.#highScore;
}
get score(): number {
return this.#score;
}
}

10
tsconfig.json Normal file
View File

@ -0,0 +1,10 @@
{
"compilerOptions": {
"outDir": "./js",
"allowJs": true,
"target": "ES2017",
"lib": ["es2018", "dom"],
},
"include": ["./src/**/*"]
}