pear can eat

This commit is contained in:
2026-03-15 19:26:12 +01:00
parent ca11f0f92f
commit fc0da039fa
3 changed files with 128 additions and 50 deletions

View File

@@ -1,7 +1,6 @@
import { Chibi } from "../../types/chibi/chibi"; import { Chibi } from "../../types/chibi/chibi";
import { EChibiInteraction } from "../../types/chibi/chibi-interaction";
import { Food } from "../../types/food";
import { RandomService } from "../random.service"; import { RandomService } from "../random.service";
import { ChibiInteraction } from "./brains/aperio.brain";
export interface IBrain { export interface IBrain {
@@ -12,5 +11,5 @@ export interface IBrain {
exist(): void; exist(): void;
// function which resolves all interactions // function which resolves all interactions
resolveInteraction(interaction: EChibiInteraction, item?: Food): void resolveInteraction(interaction: ChibiInteraction<any>): void
} }

View File

@@ -1,27 +1,49 @@
import { Chibi } from "../../../types/chibi/chibi"; import { Chibi } from "../../../types/chibi/chibi";
import { EChibiInteraction } from "../../../types/chibi/chibi-interaction"; import { EChibiInteraction } from "../../../types/chibi/chibi-interaction";
import { ChibiState, isState, STATE_EATING, STATE_GRUMBLING, STATE_IDLE, STATE_SLEEPING, STATES } from "../../../types/chibi/chibi-state"; import { ChibiState, isState, STATE_AWAKE, STATE_EATING, STATE_GRUMBLING, STATE_IDLE, STATE_SLEEPING, STATES } from "../../../types/chibi/chibi-state";
import { EChibiStateName } from "../../../types/chibi/chibi-state-name"; import { EChibiStateName } from "../../../types/chibi/chibi-state-name";
import { Food } from "../../../types/food"; import { Food } from "../../../types/food";
import { RandomOutcome, RandomService } from "../../random.service"; import { RandomOutcome, RandomService } from "../../random.service";
import { IBrain } from "../brain"; import { IBrain } from "../brain";
type ResolutionFunction = (interaction: EChibiInteraction) => ChibiState; export type ChibiInteraction<T = null> = {
name: EChibiInteraction;
payload: T;
}
type ResolutionFunction = (interaction: ChibiInteraction<any>) => ChibiState;
type InteractionRecord = Partial<Record<EChibiInteraction, ResolutionFunction>>; type InteractionRecord = Partial<Record<EChibiInteraction, ResolutionFunction>>;
const MAX_TIME_AWAKE: number = 3; const MAX_TIME_AWAKE: number = 10;
const HAPPINESS_FROM_SLEEP: number = 100;
const SADNESS_FROM_NEGLECT: number = 50;
const MAX_DURATION_GRUMBLE: number = 1;
const MAX_DURATION_EATING: number = 1;
export class Aperio implements IBrain { export class Aperio implements IBrain {
private randomService!: RandomService; private randomService!: RandomService;
private chibi!: Chibi; private chibi!: Chibi;
private readonly defaultState: EChibiStateName = EChibiStateName.Sleeping;
private previousState: EChibiStateName = this.defaultState;
private hasBeenAwakeFor: number = 0; private hasBeenAwakeFor: number = 0;
private state!: ChibiState; private timeSinceLastInteraction: number = 0;
private timeInCurrentState: number = 0;
private lastConsumedFoods: string[] = [];
private preferredFoods: string[] = [];
private state: ChibiState = STATES[this.defaultState];
private interactionMap: { [key in EChibiStateName]: InteractionRecord | undefined } = { private interactionMap: { [key in EChibiStateName]: InteractionRecord | undefined } = {
[EChibiStateName.Sleeping]: { [EChibiStateName.Sleeping]: {
[EChibiInteraction.WakeUp]: (interaction: EChibiInteraction): ChibiState => this.wakeUp(interaction), [EChibiInteraction.WakeUp]: (interaction: ChibiInteraction): ChibiState => this.wakeUp(interaction),
},
[EChibiStateName.Awake]: {
[EChibiInteraction.Feed]: (interaction: ChibiInteraction<Food>): ChibiState => this.eat(interaction),
}, },
[EChibiStateName.Awake]: undefined,
[EChibiStateName.Snoring]: undefined, [EChibiStateName.Snoring]: undefined,
[EChibiStateName.Nightmare]: undefined, [EChibiStateName.Nightmare]: undefined,
[EChibiStateName.Grumbling]: undefined, [EChibiStateName.Grumbling]: undefined,
@@ -35,72 +57,123 @@ export class Aperio implements IBrain {
[EChibiStateName.Tired]: undefined [EChibiStateName.Tired]: undefined
} }
public readonly defaultState: EChibiStateName = EChibiStateName.Sleeping;
//needed: some sort of legal interactions map
//uses stateNames as keys
//then submap the interactions
//behind every interaction is a dedicated function
init(chibi: Chibi, randomService: RandomService): void { init(chibi: Chibi, randomService: RandomService): void {
this.randomService = randomService; this.randomService = randomService;
this.chibi = chibi; this.chibi = chibi;
this.state = STATES[chibi.state]; this.state = STATES[chibi.state];
console.log(this.wakeUp);
} }
exist(): void { exist(): void {
console.log('Brain is braining'); this.timeInCurrentState++;
if (isState(this.state, EChibiStateName.Awake)) { this.resolveAwake();
console.log('is awake'); this.resolveSleeping();
this.hasBeenAwakeFor++; this.resolveGrumbling();
if (this.hasBeenAwakeFor > MAX_TIME_AWAKE) { this.resolveEating();
this.state = STATE_SLEEPING;
this.chibi.state = EChibiStateName.Sleeping;
this.hasBeenAwakeFor = 0;
}
}
this.chibi.state = this.state.name;
} }
resolveInteraction(interaction: EChibiInteraction, item?: Food): void { resolveInteraction(interaction: ChibiInteraction): void {
// reassign state based on resolving map this.timeSinceLastInteraction = 0;
const resolution: ResolutionFunction | undefined = this.interactionMap[this.state.name]?.[interaction]; const specificMatch: ResolutionFunction | undefined = this.interactionMap[this.state.name]?.[interaction.name];
const awakeMatch: ResolutionFunction | undefined = this.interactionMap[EChibiStateName.Awake]?.[interaction.name];
const asleepMatch: ResolutionFunction | undefined = this.interactionMap[EChibiStateName.Sleeping]?.[interaction.name];
const resolution: ResolutionFunction | undefined = specificMatch || (this.isAwake() ? awakeMatch : asleepMatch);
console.log(`specific match: ${specificMatch}`);
console.log(`${this.chibi.name} should ${interaction.name} with payload: ${interaction.payload}`);
if (resolution) { if (resolution) {
this.state = resolution(interaction); this.changeState(resolution(interaction));
} }
}
isAwake(): boolean {
return isState(this.state, EChibiStateName.Awake);
}
// resolvers
// functions which auitomatically resolve based on the existence cycle
resolveAwake(): void {
if (this.isAwake()) {
console.log(`${this.chibi.name} is awake.`);
this.timeSinceLastInteraction++;
this.hasBeenAwakeFor++;
if (this.timeInCurrentState % 10 === 0) {
this.saddenedByNeglect(this.timeSinceLastInteraction, SADNESS_FROM_NEGLECT);
}
if (this.hasBeenAwakeFor > MAX_TIME_AWAKE) {
this.changeState(STATE_SLEEPING);
}
}
}
increaseHappyness(increase: number): void {
console.log(`${this.chibi.name} happyness increases.`);
this.chibi.happyness = this.chibi.happyness + increase;
}
saddenedByNeglect(neglectTime: number, sadness: number): void {
console.log(`${this.chibi.name} happyness decreases.`);
this.chibi.happyness = this.chibi.happyness - (neglectTime * sadness);
}
resolveSleeping(): void {
if (isState(this.state, EChibiStateName.Sleeping)) {
console.log(`${this.chibi.name} is sleeping.`);
this.increaseHappyness(HAPPINESS_FROM_SLEEP);
const awakeChance = this.randomService.obtainRandom(2048);
if (awakeChance.matchesChance(1)) {
this.changeState(STATE_AWAKE);
}
}
}
resolveGrumbling(): void {
if (isState(this.state, EChibiStateName.Grumbling)) {
console.log(`${this.chibi.name} is grumbling.`);
if (this.timeInCurrentState > MAX_DURATION_GRUMBLE) {
this.changeState(STATE_SLEEPING);
}
}
}
resolveEating(): void {
if (isState(this.state, EChibiStateName.Eating)) {
console.log(`${this.chibi.name} is eating.`);
if (this.timeInCurrentState > MAX_DURATION_EATING) {
this.changeState(STATES[this.previousState]);
}
}
}
private changeState(state: ChibiState): void {
this.previousState = this.state.name;
this.state = state;
this.chibi.state = this.state.name; this.chibi.state = this.state.name;
this.timeInCurrentState = 0;
} }
/* /*
pear mostly sleeps
visiting caethya will wake her up and go into lovey-dovey mode visiting caethya will wake her up and go into lovey-dovey mode
she has a 1/2048 chance of waking up on her own
pears happyness goes up when she sleeps
pear never gets hungry
but she accepts foods she likes, which raise her happyness
as long as the same food is not offered thrice in a row (except banana split) as long as the same food is not offered thrice in a row (except banana split)
pears energy never goes down pears energy never goes down
pear will accept fights and enjoys them pear will accept fights and enjoys them
pear will accept walks and enjoys them pear will accept walks and enjoys them
pear will get sad if she is awake and you do not interact with her
pear gets offended if you suggest she go to sleep pear gets offended if you suggest she go to sleep
after any interaction she has a 1/4 chance of going to sleep after any interaction she has a 1/4 chance of going to sleep
*/ */
private wakeUp(interaction: EChibiInteraction): ChibiState { // interaction functions
// functions which are triggered by user actions
private wakeUp(interaction: ChibiInteraction): ChibiState {
const wakeUpOutcome: RandomOutcome = this.randomService.obtainRandom(128); const wakeUpOutcome: RandomOutcome = this.randomService.obtainRandom(128);
const willWakeUp: boolean = wakeUpOutcome.matchesChance(1); const willWakeUp: boolean = wakeUpOutcome.matchesChance(1);
const willGrumble: boolean = wakeUpOutcome.matchesChance(12, 1); const willGrumble: boolean = wakeUpOutcome.matchesChance(12, 1);
console.log(wakeUpOutcome.random);
console.log(willWakeUp);
console.log(willGrumble);
if (willWakeUp) { if (willWakeUp) {
return STATE_IDLE; return STATE_IDLE;
} else if (willGrumble) { } else if (willGrumble) {
@@ -109,10 +182,16 @@ export class Aperio implements IBrain {
return STATE_SLEEPING; return STATE_SLEEPING;
} }
private resolveAwakeInteraction(interaction: EChibiInteraction, item?: Food): ChibiState { private eat(interaction: ChibiInteraction<Food>): ChibiState {
if (EChibiInteraction.Feed) { console.log(`${this.chibi.name} should eat ${interaction.payload.name}`);
const foodId = interaction.payload.id;
if (this.preferredFoods.includes(foodId)) {
this.lastConsumedFoods.push(foodId);
this.increaseHappyness(100);
return STATE_EATING;
} else {
}
return STATE_EATING; return STATE_EATING;
} }
return STATE_IDLE;
}
}; };

View File

@@ -13,8 +13,8 @@ import { BRAIN_MAP } from '../../logic/chibi-behaviour/brain-map';
import { TranslateableComponent } from '../../components/translateable.component'; import { TranslateableComponent } from '../../components/translateable.component';
import { Food } from '../../types/food'; import { Food } from '../../types/food';
import { interval } from 'rxjs'; import { interval } from 'rxjs';
import { ChibiState, STATES } from '../../types/chibi/chibi-state';
import { RandomService } from '../../logic/random.service'; import { RandomService } from '../../logic/random.service';
import { ChibiInteraction } from '../../logic/chibi-behaviour/brains/aperio.brain';
enum EInteractionMenuState { enum EInteractionMenuState {
Neutral, Neutral,
@@ -22,7 +22,7 @@ enum EInteractionMenuState {
Inventory Inventory
} }
const THINKING_INTERVAL: number = 10000; const THINKING_INTERVAL: number = 2000;
@Component({ @Component({
selector: 'ff-interactions', selector: 'ff-interactions',
@@ -61,7 +61,7 @@ export class InteractionsComponent extends TranslateableComponent {
public interact(interaction: EChibiInteraction, item?: Food): void { public interact(interaction: EChibiInteraction, item?: Food): void {
console.log(`${interaction} ${this.chibi().name} and item ${item}`); console.log(`${interaction} ${this.chibi().name} and item ${item}`);
this.brain.resolveInteraction(interaction, item); this.brain.resolveInteraction({ name: interaction, payload: item } as ChibiInteraction<Food>);
} }
public openFoodPantry(): void { public openFoodPantry(): void {