Initial commit

This commit is contained in:
2023-02-04 08:23:51 +02:00
parent d8d853060e
commit 9d3f27b49e
26 changed files with 632 additions and 39 deletions

View File

@@ -136,7 +136,8 @@
"cli": { "cli": {
"schematicCollections": [ "schematicCollections": [
"@ionic/angular-toolkit" "@ionic/angular-toolkit"
] ],
"analytics": false
}, },
"schematics": { "schematics": {
"@ionic/angular-toolkit:component": { "@ionic/angular-toolkit:component": {

6
src/app/farmer.dto.ts Normal file
View File

@@ -0,0 +1,6 @@
export type FarmerDto = {
name: string,
level: number,
basePrice: number,
boost: number,
}

View File

@@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { FormulasService } from './formulas.service';
describe('FormulasService', () => {
let service: FormulasService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(FormulasService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

View File

@@ -0,0 +1,13 @@
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class FormulasService {
constructor() { }
getPrice(basePrice: number, growthRate: number, currentLevel: number): number {
return Math.floor(basePrice * Math.pow(growthRate, currentLevel));
}
}

View File

@@ -5,6 +5,8 @@ import { FormsModule } from '@angular/forms';
import { HomePage } from './home.page'; import { HomePage } from './home.page';
import { HomePageRoutingModule } from './home-routing.module'; import { HomePageRoutingModule } from './home-routing.module';
import {PlantContainerComponent} from "../plant-container/plant-container.component";
import {ProgressBarComponent} from "../progress-bar/progress-bar.component";
@NgModule({ @NgModule({
@@ -14,6 +16,6 @@ import { HomePageRoutingModule } from './home-routing.module';
IonicModule, IonicModule,
HomePageRoutingModule HomePageRoutingModule
], ],
declarations: [HomePage] declarations: [HomePage, PlantContainerComponent, ProgressBarComponent]
}) })
export class HomePageModule {} export class HomePageModule {}

View File

@@ -1,20 +1,32 @@
<ion-header [translucent]="true">
<ion-toolbar>
<ion-title>
Blank
</ion-title>
</ion-toolbar>
</ion-header>
<ion-content [fullscreen]="true"> <ion-content [fullscreen]="true">
<ion-header collapse="condense"> <div id="game">
<ion-toolbar>
<ion-title size="large">Blank</ion-title>
</ion-toolbar>
</ion-header>
<div id="container"> <div id="header">
<strong>Ready to create an app?</strong> <div id="profile-picture">
<p>Start with Ionic <a target="_blank" rel="noopener noreferrer" href="https://ionicframework.com/docs/components">UI Components</a></p> <img src="/assets/pfp-default.png"/>
</div>
<div id="currencies">
<div id="seeds">{{ getSeeds() }} Seeds</div>
<div id="seeds-per-second">0 Seeds/sec</div>
<div id="fertilizer">0 Fertilizer</div>
<div id="star-dust">0 Start Dust</div>
<div id="settings">Q</div>
</div>
</div>
<div id="navigation">
<div>Plants</div>
<div>Farmers</div>
<div>Boosters</div>
<div>Upgrades</div>
<div>Stats</div>
</div>
<div id="plants">
<ng-container *ngFor="let plant of getPlants()">
<app-plant-container
[plant]="plant"
(onBuy)="onBuy(plant)"
></app-plant-container>
</ng-container>
</div>
</div> </div>
</ion-content> </ion-content>

View File

@@ -1,27 +1,81 @@
#container {
text-align: center;
#game {
height: 100vh;
width: var(--game-width);
background: #edd6ff;
margin: auto;
position: relative;
}
#header {
display: block;
width: var(--game-width);
height: calc(100vh / 6);
background: #d7a1fa;
position: relative;
color: black;
font-weight: bold;
}
#profile-picture {
position: absolute; position: absolute;
top: 0;
left: 0; left: 0;
right: 0; height: calc((100vh / 6) - 4vh);
top: 50%; aspect-ratio: calc(54/64);
transform: translateY(-50%);
img {
height: calc((100vh / 6) - 4vh);
aspect-ratio: calc(54/64);
margin: 2vh;
}
} }
#container strong { #seeds {
font-size: 20px; position: absolute;
line-height: 26px; top: 3vh;
left: calc(var(--game-width) / 2);
transform: translate(-50%, 0);
} }
#container p { #seeds-per-second {
font-size: 16px; position: absolute;
line-height: 22px; top: 6vh;
left: calc(var(--game-width) / 2);
color: #8c8c8c; transform: translate(-50%, 0);
margin: 0;
} }
#container a { #fertilizer {
text-decoration: none; position: absolute;
} top: 6vh;
right: 1vh;
}
#star-dust {
position: absolute;
top: 9vh;
right: 1vh;
}
#settings {
position: absolute;
top: 1vh;
right: 1vh;
}
#navigation {
position: relative;
height: 3vh;
background: #9961c2;
display: grid;
grid-template-columns: repeat(5, 1fr);
div {
height: 3vh;
width: 100%;
line-height: 3.6vh;
text-align: center;
}
}

View File

@@ -1,12 +1,58 @@
import { Component } from '@angular/core'; import {ChangeDetectionStrategy, Component} from '@angular/core';
import {PlayerService} from "../player.service";
import {FormulasService} from "../formulas.service";
import {UtilitiesService} from "../utilities.service";
import {PlantDto} from "../plant.dto";
@Component({ @Component({
selector: 'app-home', selector: 'app-home',
templateUrl: 'home.page.html', templateUrl: 'home.page.html',
styleUrls: ['home.page.scss'], styleUrls: ['home.page.scss'],
// changeDetection: ChangeDetectionStrategy.OnPush,
}) })
export class HomePage { export class HomePage {
constructor() {} constructor(
private playerService: PlayerService,
private formulasService: FormulasService
) {
this.gameLoop();
}
getSeeds(): number {
return this.playerService.seeds;
}
getPlants(): PlantDto[] {
return this.playerService.plants;
}
onBuy(plant: PlantDto) {
const price = this.formulasService.getPrice(plant.basePrice, plant.growthRate, plant.level);
if (price <= this.playerService.seeds) {
plant.level += 1;
this.playerService.seeds -= price;
}
}
gameLoop() {
const currentTime = Date.now();
const dt = currentTime - this.playerService.lastTime;
this.playerService.lastTime = currentTime;
for(let plant of this.playerService.plants) {
if (plant.level > 0) {
plant.timePassed += dt;
while (plant.timePassed >= plant.timeToGrow) {
this.playerService.seeds += plant.reward * plant.level;
plant.timePassed -= plant.timeToGrow;
}
}
}
this.playerService.save();
requestAnimationFrame(() => this.gameLoop());
}
} }

View File

@@ -0,0 +1,26 @@
<div class="plant-container" *ngIf="plant">
<div class="plant-image">
<img src="/assets/plant.png" alt="plant" />
<div class="plant-level">Lv. {{ plant.level }}</div>
</div>
<div class="upgrade-button" (click)="buy()">
Upgrade to Lv. {{ plant.level + 1 }}<br />
{{ getPrice() }} seeds
</div>
<div class="name">
{{ plant.name }}
</div>
<div class="reward">
{{ getReward() }} seeds/plant
</div>
<app-progress-bar
[fill]="progress()"
[fillColor]="'#FFD500'"
[backgroundColor]="'#005BBB'"
[progressBarText]="timeLeft()"
class="plant-progress"
></app-progress-bar>
<div class="reward-total">
+ {{ getRewardTotal() }} seeds
</div>
</div>

View File

@@ -0,0 +1,78 @@
.plant-container {
position: relative;
width: 100%;
height: calc(100vh / 8);
background: #005BBB;
margin-top: 1vh;
}
.plant-image {
position: absolute;
top: 1vh;
right: 1vh;
height: calc(100vh / 8 - 2vh);
width: calc(100vh / 8 - 2vh);
.plant-level {
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 1.2em;
line-height: 1.6em;
text-align: center;
background: #0d0d0d66;
}
}
.plant-progress {
position: absolute;
bottom: 1vh;
left: 1vh;
width: calc(100% - 1vh - 100vh / 8);
height: 3vh;
border: 1px solid black;
}
.reward-total {
position: absolute;
bottom: 1vh;
right: calc(100vh / 8);
width: calc((100% - 1vh - 100vh / 8) / 3);
text-align: right;
padding-right: 1vh;
height: 3vh;
line-height: 180%;
}
.upgrade-button {
position: absolute;
top: 1vh;
left: 1vh;
text-align: center;
height: calc(100vh / 8 - 6vh);
line-height: calc((100vh / 8 - 6vh) / 2);
background: #FFD500;
width: 20vh;
color: black;
font-weight: bold;
}
.reward {
position: absolute;
top: calc(100vh / 16 - 2vh);
left: 22vh;
text-align: center;
height: calc(100vh / 16 - 3vh);
line-height: calc(100vh / 16 - 3vh);
width: calc(100% - 1vh - 100vh / 8 - 21vh);
}
.name {
position: absolute;
top: 1vh;
left: 22vh;
text-align: center;
height: calc(100vh / 16 - 3vh);
line-height: calc(100vh / 16 - 3vh);
width: calc(100% - 1vh - 100vh / 8 - 21vh);
}

View File

@@ -0,0 +1,24 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { IonicModule } from '@ionic/angular';
import { PlantContainerComponent } from './plant-container.component';
describe('PlantContainerComponent', () => {
let component: PlantContainerComponent;
let fixture: ComponentFixture<PlantContainerComponent>;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [ PlantContainerComponent ],
imports: [IonicModule.forRoot()]
}).compileComponents();
fixture = TestBed.createComponent(PlantContainerComponent);
component = fixture.componentInstance;
fixture.detectChanges();
}));
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,53 @@
import {ChangeDetectionStrategy, Component, EventEmitter, Input, OnInit, Output} from '@angular/core';
import {FormulasService} from "../formulas.service";
import {UtilitiesService} from "../utilities.service";
import {PlantDto} from "../plant.dto";
@Component({
selector: 'app-plant-container',
templateUrl: './plant-container.component.html',
styleUrls: ['./plant-container.component.scss'],
// changeDetection: ChangeDetectionStrategy.OnPush,
})
export class PlantContainerComponent implements OnInit {
@Input('plant') plant: PlantDto | undefined;
@Output('onBuy') onBuy = new EventEmitter<PlantDto>();
constructor(
private formulasService: FormulasService,
private utilitiesService: UtilitiesService
) { }
ngOnInit() {}
buy() {
this.onBuy.emit(this.plant);
}
getPrice() {
if (!this.plant) return 0;
return this.formulasService.getPrice(this.plant.basePrice, this.plant.growthRate, this.plant.level);
}
getReward() {
if (!this.plant) return 0;
return this.plant.reward;
}
getRewardTotal() {
if (!this.plant) return 0;
return this.plant.level * this.plant.reward;
}
progress(): number {
if (!this.plant) return 0;
return this.plant.timePassed / this.plant.timeToGrow * 100;
}
timeLeft(): string {
if (!this.plant) return '';
return this.utilitiesService.convertToTime(this.plant.timeToGrow - this.plant.timePassed);
}
}

9
src/app/plant.dto.ts Normal file
View File

@@ -0,0 +1,9 @@
export type PlantDto = {
name: string,
level: number,
basePrice: number,
growthRate: number,
timeToGrow: number,
timePassed: number,
reward: number,
}

View File

@@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { PlayerService } from './player.service';
describe('PlayerService', () => {
let service: PlayerService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(PlayerService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

102
src/app/player.service.ts Normal file
View File

@@ -0,0 +1,102 @@
import { Injectable } from '@angular/core';
import {PlantDto} from "./plant.dto";
@Injectable({
providedIn: 'root'
})
export class PlayerService {
public lastTime = Date.now();
public seeds: number = 0;
public plants: PlantDto[] = [
{
name: 'Plant 1',
level: 1,
basePrice: 4,
growthRate: 1.07,
timeToGrow: 600,
timePassed: 0,
reward: 1,
},
{
name: 'Plant 2',
level: 0,
basePrice: 60,
growthRate: 1.15,
timeToGrow: 3000,
timePassed: 0,
reward: 60,
},
{
name: 'Plant 3',
level: 0,
basePrice: 720,
growthRate: 1.14,
timeToGrow: 6000,
timePassed: 0,
reward: 540,
},
{
name: 'Plant 4',
level: 0,
basePrice: 8640,
growthRate: 1.13,
timeToGrow: 12000,
timePassed: 0,
reward: 4320,
},
{
name: 'Plant 5',
level: 0,
basePrice: 103680,
growthRate: 1.12,
timeToGrow: 24000,
timePassed: 0,
reward: 51840,
},
{
name: 'Plant 6',
level: 0,
basePrice: 51200,
growthRate: 1.11,
timeToGrow: 45000,
timePassed: 0,
reward: 102400,
},
{
name: 'Plant 7',
level: 0,
basePrice: 1000000,
growthRate: 1.106,
timeToGrow: 60000,
timePassed: 0,
reward: 204800,
},
{
name: 'Plant 8',
level: 0,
basePrice: 5000000,
growthRate: 1.10,
timeToGrow: 60000,
timePassed: 0,
reward: 614150,
}
];
constructor() {
const data = localStorage.getItem('PlayerSave');
if (data) {
const parsed = JSON.parse(data);
this.plants = parsed.plants;
this.seeds = parsed.seeds;
this.lastTime = parsed.lastTime ? parsed.lastTime : Date.now();
}
}
save() {
localStorage.setItem('PlayerSave', JSON.stringify({
seeds: this.seeds,
plants: this.plants,
lastTime: this.lastTime
}));
}
}

View File

@@ -0,0 +1,4 @@
<div class="progress-bar-container" [style]="'background: ' + backgroundColor + ';'">
<div class="progress-bar-progress" [style]="'width: '+fill+'%; background: ' + fillColor + ';'"></div>
<div class="progress-bar-text">{{ progressBarText }}</div>
</div>

View File

@@ -0,0 +1,20 @@
.progress-bar-container {
position: relative;
width: 100%;
height: 100%;
.progress-bar-progress {
position: absolute;
right: 0;
top: 0;
height: 100%;
}
.progress-bar-text {
position: absolute;
width: 100%;
height: 100%;
text-align: center;
line-height: 180%;
}
}

View File

@@ -0,0 +1,24 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { IonicModule } from '@ionic/angular';
import { ProgressBarComponent } from './progress-bar.component';
describe('ProgressBarComponent', () => {
let component: ProgressBarComponent;
let fixture: ComponentFixture<ProgressBarComponent>;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [ ProgressBarComponent ],
imports: [IonicModule.forRoot()]
}).compileComponents();
fixture = TestBed.createComponent(ProgressBarComponent);
component = fixture.componentInstance;
fixture.detectChanges();
}));
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,20 @@
import {ChangeDetectionStrategy, Component, Input, OnInit} from '@angular/core';
@Component({
selector: 'app-progress-bar',
templateUrl: './progress-bar.component.html',
styleUrls: ['./progress-bar.component.scss'],
// changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ProgressBarComponent implements OnInit {
@Input('fill') fill: number = 0;
@Input('progressBarText') progressBarText: string = '';
@Input('backgroundColor') backgroundColor: string = '#1a1a1a';
@Input('fillColor') fillColor: string = '#2dd36f';
constructor() { }
ngOnInit() {}
}

View File

@@ -0,0 +1,34 @@
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class UtilitiesService {
constructor() { }
convertToTime(num: number) {
num = Math.round(num / 1000);
const days = Math.floor(num / 60 / 60 / 24);
num -= days * 60 * 60 * 24;
const hours = Math.floor(num / 60 / 60);
num -= hours * 60 * 60;
const minutes = Math.floor(num / 60);
num -= minutes * 60;
const seconds = num;
if (days) {
return days + ' ' + (hours > 9 ? hours : '0' + hours) + ':' + (minutes > 9 ? minutes : '0' + minutes) + ':' + (seconds > 9 ? seconds : '0' + seconds);
}
if (hours) {
return (hours > 9 ? hours : '0' + hours) + ':' + (minutes > 9 ? minutes : '0' + minutes) + ':' + (seconds > 9 ? seconds : '0' + seconds);
}
if (minutes) {
return (minutes > 9 ? minutes : '0' + minutes) + ':' + (seconds > 9 ? seconds : '0' + seconds);
}
return seconds + ' sec';
}
}

View File

@@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { UtilitiesService } from './utilities.service';
describe('UtilsService', () => {
let service: UtilitiesService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(UtilitiesService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

BIN
src/assets/pfp-default.kra Normal file

Binary file not shown.

BIN
src/assets/pfp-default.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
src/assets/plant.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -24,3 +24,20 @@
@import "~@ionic/angular/css/text-alignment.css"; @import "~@ionic/angular/css/text-alignment.css";
@import "~@ionic/angular/css/text-transformation.css"; @import "~@ionic/angular/css/text-transformation.css";
@import "~@ionic/angular/css/flex-utils.css"; @import "~@ionic/angular/css/flex-utils.css";
@import url('https://fonts.googleapis.com/css2?family=Solitreo&display=swap');
:root {
--game-width: calc(100vh / 16 * 9)
}
body {
overflow: hidden;
}
* {
font-family: 'Solitreo', cursive;
font-size: 2vh;
-webkit-user-select: none; /* Safari */
-ms-user-select: none; /* IE 10 and IE 11 */
user-select: none; /* Standard syntax */
}

View File

@@ -3,7 +3,7 @@
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<title>Ionic App</title> <title>Lily - Idle</title>
<base href="/" /> <base href="/" />