feat: pogoda, poprawki w wyświetlaniu. Clean Architecture
This commit is contained in:
@@ -0,0 +1,130 @@
|
||||
#include "BuddyLogic.h"
|
||||
#include <algorithm>
|
||||
|
||||
// Replaces Arduino constrain()
|
||||
template<typename T>
|
||||
static T clamp(T v, T lo, T hi) { return std::max(lo, std::min(hi, v)); }
|
||||
|
||||
void initBuddy(BuddyState &b, uint32_t now)
|
||||
{
|
||||
b = {};
|
||||
b.blinkState = BLINK_OPEN;
|
||||
b.blinkRy = EYE_RY;
|
||||
b.lastEvent = now;
|
||||
b.nextBlink = now + 3000;
|
||||
b.nextLook = now + 2000;
|
||||
b.nextZzz = now + 3000;
|
||||
b.nextMicroTremor = now + 500;
|
||||
}
|
||||
|
||||
void setBuddyMood(BuddyState &b, Mood m, uint32_t now, uint32_t durationMs)
|
||||
{
|
||||
b.mood = m;
|
||||
b.revertAt = (durationMs > 0) ? now + durationMs : 0;
|
||||
b.lastEvent = now;
|
||||
switch (m) {
|
||||
case MOOD_HAPPY:
|
||||
b.pupilTargetDx = 0; b.pupilTargetDy = -2; break;
|
||||
case MOOD_SLEEPY:
|
||||
b.pupilTargetDx = 0; b.pupilTargetDy = 4; break;
|
||||
case MOOD_SURPRISED:
|
||||
b.pupilTargetDx = 0; b.pupilTargetDy = 0; break;
|
||||
case MOOD_SAD:
|
||||
b.pupilTargetDx = 0; b.pupilTargetDy = 4; break;
|
||||
case MOOD_ANGRY:
|
||||
b.pupilTargetDx = 2; b.pupilTargetDy = 2; break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
void updateBuddyAnim(BuddyState &b, uint32_t now, RngFn rng)
|
||||
{
|
||||
// Timed mood revert
|
||||
if (b.revertAt > 0 && now >= b.revertAt) {
|
||||
b.mood = MOOD_NORMAL;
|
||||
b.revertAt = 0;
|
||||
}
|
||||
// Auto-sleep after 5 min idle
|
||||
if (b.mood == MOOD_NORMAL && now - b.lastEvent > 300000UL)
|
||||
setBuddyMood(b, MOOD_SLEEPY, now, 0);
|
||||
|
||||
// Blink state machine (disabled for SURPRISED, EXCITED, WINKs)
|
||||
if (b.mood != MOOD_SURPRISED && b.mood != MOOD_EXCITED &&
|
||||
b.mood != MOOD_WINK_L && b.mood != MOOD_WINK_R)
|
||||
{
|
||||
switch (b.blinkState) {
|
||||
case BLINK_OPEN:
|
||||
if (now >= b.nextBlink)
|
||||
b.blinkState = BLINK_CLOSING;
|
||||
break;
|
||||
case BLINK_CLOSING:
|
||||
if (b.blinkRy > 3)
|
||||
b.blinkRy -= 4;
|
||||
else {
|
||||
b.blinkRy = 1;
|
||||
b.blinkState = BLINK_CLOSED;
|
||||
b.closedTicks = 0;
|
||||
}
|
||||
break;
|
||||
case BLINK_CLOSED:
|
||||
if (b.closedTicks++ >= 2)
|
||||
b.blinkState = BLINK_OPENING;
|
||||
break;
|
||||
case BLINK_OPENING:
|
||||
b.blinkRy += 4;
|
||||
if (b.blinkRy >= EYE_RY) {
|
||||
b.blinkRy = EYE_RY;
|
||||
b.blinkState = BLINK_OPEN;
|
||||
b.nextBlink = now + (int32_t)((b.mood == MOOD_SLEEPY)
|
||||
? rng(800, 2000) : rng(2500, 6000));
|
||||
}
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
b.blinkRy = EYE_RY;
|
||||
}
|
||||
|
||||
// Saccadic pupil movement — fast (3px/tick) when far (|delta|>=4), slow (1px) when close
|
||||
{
|
||||
int8_t ddx = b.pupilTargetDx - b.pupilDx;
|
||||
int8_t ddy = b.pupilTargetDy - b.pupilDy;
|
||||
int8_t absDdx = ddx < 0 ? (int8_t)-ddx : ddx;
|
||||
int8_t absDdy = ddy < 0 ? (int8_t)-ddy : ddy;
|
||||
if (ddx) b.pupilDx = (int8_t)(b.pupilDx + (ddx > 0 ? 1 : -1) * (absDdx >= 4 ? 3 : 1));
|
||||
if (ddy) b.pupilDy = (int8_t)(b.pupilDy + (ddy > 0 ? 1 : -1) * (absDdy >= 4 ? 3 : 1));
|
||||
}
|
||||
|
||||
// Gaze — random saccades + micro-tremor
|
||||
{
|
||||
bool fixated = (b.pupilDx == b.pupilTargetDx && b.pupilDy == b.pupilTargetDy);
|
||||
|
||||
if (b.mood == MOOD_SLEEPY) {
|
||||
if (fixated && now >= b.nextLook) {
|
||||
b.pupilTargetDx = (int8_t)rng(-3, 4);
|
||||
b.pupilTargetDy = (int8_t)rng(2, 6);
|
||||
b.nextLook = now + (uint32_t)rng(4000, 9000);
|
||||
}
|
||||
} else if (b.mood != MOOD_WINK_L && b.mood != MOOD_WINK_R) {
|
||||
if (fixated && now >= b.nextLook) {
|
||||
b.pupilTargetDx = (int8_t)rng(-7, 8);
|
||||
b.pupilTargetDy = (int8_t)rng(-5, 6);
|
||||
b.nextLook = now + (uint32_t)rng(1500, 4500);
|
||||
}
|
||||
if ((b.mood == MOOD_NORMAL || b.mood == MOOD_HAPPY) &&
|
||||
fixated && now >= b.nextMicroTremor)
|
||||
{
|
||||
b.pupilTargetDx = (int8_t)clamp(
|
||||
(int32_t)b.pupilTargetDx + rng(-1, 2), (int32_t)-7, (int32_t)7);
|
||||
b.pupilTargetDy = (int8_t)clamp(
|
||||
(int32_t)b.pupilTargetDy + rng(-1, 2), (int32_t)-5, (int32_t)5);
|
||||
b.nextMicroTremor = now + (uint32_t)rng(300, 700);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ZZZ bubbles when sleepy
|
||||
if (b.mood == MOOD_SLEEPY && now >= b.nextZzz) {
|
||||
b.zzzPhase = (b.zzzPhase % 3) + 1;
|
||||
b.nextZzz = now + 700;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
#include "BuddyTypes.h"
|
||||
|
||||
// Eye radius — used in blink state machine and main.cpp drawing
|
||||
static const uint8_t EYE_RY = 15;
|
||||
|
||||
// Inject RNG so tests can supply a deterministic function
|
||||
using RngFn = int32_t(*)(int32_t lo, int32_t hi);
|
||||
|
||||
void initBuddy(BuddyState &b, uint32_t now);
|
||||
void setBuddyMood(BuddyState &b, Mood m, uint32_t now, uint32_t durationMs = 0);
|
||||
void updateBuddyAnim(BuddyState &b, uint32_t now, RngFn rng);
|
||||
@@ -0,0 +1,40 @@
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
|
||||
enum Mood : uint8_t {
|
||||
MOOD_NORMAL = 0,
|
||||
MOOD_HAPPY,
|
||||
MOOD_SLEEPY,
|
||||
MOOD_SURPRISED,
|
||||
MOOD_ANGRY,
|
||||
MOOD_SAD,
|
||||
MOOD_EXCITED,
|
||||
MOOD_WINK_L,
|
||||
MOOD_WINK_R,
|
||||
MOOD_HUNGRY,
|
||||
MOOD_PLAYFUL,
|
||||
MOOD_DIRTY
|
||||
};
|
||||
|
||||
enum BlinkState : uint8_t {
|
||||
BLINK_OPEN,
|
||||
BLINK_CLOSING,
|
||||
BLINK_CLOSED,
|
||||
BLINK_OPENING
|
||||
};
|
||||
|
||||
struct BuddyState {
|
||||
Mood mood;
|
||||
uint32_t revertAt;
|
||||
uint32_t lastEvent;
|
||||
BlinkState blinkState;
|
||||
uint8_t blinkRy;
|
||||
uint8_t closedTicks;
|
||||
uint32_t nextBlink;
|
||||
int8_t pupilDx, pupilDy;
|
||||
int8_t pupilTargetDx, pupilTargetDy;
|
||||
uint32_t nextLook;
|
||||
uint8_t zzzPhase;
|
||||
uint32_t nextZzz;
|
||||
uint32_t nextMicroTremor;
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
{ "name": "BuddyDomain", "version": "1.0.0", "frameworks": "*", "platforms": "*" }
|
||||
@@ -0,0 +1,31 @@
|
||||
#include "GestureConfig.h"
|
||||
|
||||
const char *GNAME[NUM_GESTURES] = {
|
||||
"up", "down", "left", "right", "forward", "backward", "clockwise", "anticlockwise", "wave"};
|
||||
|
||||
const char *GKEY[NUM_GESTURES] = {
|
||||
"u", "d", "l", "r", "f", "b", "cw", "ccw", "w"};
|
||||
|
||||
const char *MOOD_LABELS[] = {
|
||||
"-- bez zmiany --", "happy ^_^", "sleepy zZz", "surprised o_O",
|
||||
"angry >_<", "sad T_T", "excited *_*", "wink L ;)", "wink R (;",
|
||||
"hungry :(", "playful :D", "dirty ..."};
|
||||
|
||||
const char *ACTION_LABELS[] = {
|
||||
"-- brak --", "Data i godzina", "Status WiFi",
|
||||
"Nakarm", "Pobaw sie", "Umyj", "Status tamagotchi"};
|
||||
|
||||
const Mood DEFAULT_MOOD[NUM_GESTURES] = {
|
||||
MOOD_HAPPY, // up
|
||||
MOOD_SAD, // down
|
||||
MOOD_SURPRISED, // left
|
||||
MOOD_SURPRISED, // right
|
||||
MOOD_SLEEPY, // forward
|
||||
MOOD_ANGRY, // backward
|
||||
MOOD_EXCITED, // clockwise
|
||||
MOOD_NORMAL, // anticlockwise
|
||||
MOOD_EXCITED, // wave
|
||||
};
|
||||
|
||||
const uint32_t DEFAULT_MOOD_DUR[NUM_GESTURES] = {
|
||||
2000, 2000, 1500, 1500, 0, 3000, 2000, 0, 2000};
|
||||
@@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
#include "BuddyTypes.h"
|
||||
|
||||
static const uint8_t NUM_GESTURES = 9;
|
||||
static const uint8_t NUM_ACTIONS = 7;
|
||||
|
||||
enum Action : uint8_t {
|
||||
ACTION_NONE = 0,
|
||||
ACTION_DATETIME = 1,
|
||||
ACTION_WIFI = 2,
|
||||
ACTION_FEED = 3,
|
||||
ACTION_PLAY = 4,
|
||||
ACTION_CLEAN = 5,
|
||||
ACTION_STATUS = 6
|
||||
};
|
||||
|
||||
struct GestureConfig {
|
||||
char url[128];
|
||||
uint8_t mood; // 0 = no change, 1-11 matches Mood enum
|
||||
uint8_t action; // Action enum
|
||||
bool enabled;
|
||||
};
|
||||
|
||||
extern const char *GNAME[NUM_GESTURES];
|
||||
extern const char *GKEY[NUM_GESTURES];
|
||||
extern const char *MOOD_LABELS[];
|
||||
extern const char *ACTION_LABELS[];
|
||||
extern const Mood DEFAULT_MOOD[NUM_GESTURES];
|
||||
extern const uint32_t DEFAULT_MOOD_DUR[NUM_GESTURES];
|
||||
@@ -0,0 +1 @@
|
||||
{ "name": "GestureConfig", "version": "1.0.0", "frameworks": "*", "platforms": "*" }
|
||||
@@ -0,0 +1,57 @@
|
||||
#include "TamaLogic.h"
|
||||
|
||||
void initTama(TamaState &t, uint32_t now)
|
||||
{
|
||||
t.hunger = 10;
|
||||
t.happiness = 80;
|
||||
t.hygiene = 90;
|
||||
t.nextHungerTick = now + 120000UL;
|
||||
t.nextHappyTick = now + 180000UL;
|
||||
t.nextHygieneTick = now + 240000UL;
|
||||
}
|
||||
|
||||
Mood updateTama(TamaState &t, const BuddyState &b, uint32_t now)
|
||||
{
|
||||
// Tick needs over time
|
||||
if (now >= t.nextHungerTick) {
|
||||
t.nextHungerTick = now + 120000UL;
|
||||
if (t.hunger < 100) t.hunger++;
|
||||
}
|
||||
if (now >= t.nextHappyTick) {
|
||||
t.nextHappyTick = now + 180000UL;
|
||||
if (t.happiness > 0) t.happiness--;
|
||||
}
|
||||
if (now >= t.nextHygieneTick) {
|
||||
t.nextHygieneTick = now + 240000UL;
|
||||
if (t.hygiene > 0) t.hygiene--;
|
||||
}
|
||||
|
||||
// Return mood override based on critical needs.
|
||||
// Guard: skip when buddy has a timed mood or is in non-overridable state.
|
||||
if (b.revertAt == 0 && b.mood != MOOD_SLEEPY &&
|
||||
b.mood != MOOD_WINK_L && b.mood != MOOD_WINK_R)
|
||||
{
|
||||
if (t.hunger >= 80) return MOOD_HUNGRY;
|
||||
if (t.hygiene <= 20) return MOOD_DIRTY;
|
||||
if (t.happiness <= 20) return MOOD_PLAYFUL;
|
||||
// Needs satisfied — signal "reset to normal" (caller handles if buddy is in need-mood)
|
||||
}
|
||||
return MOOD_NORMAL;
|
||||
}
|
||||
|
||||
void tamaFeed(TamaState &t)
|
||||
{
|
||||
t.hunger = (t.hunger >= 30) ? t.hunger - 30 : 0;
|
||||
}
|
||||
|
||||
void tamaPlay(TamaState &t)
|
||||
{
|
||||
uint16_t v = (uint16_t)t.happiness + 25;
|
||||
t.happiness = (v > 100) ? 100 : (uint8_t)v;
|
||||
}
|
||||
|
||||
void tamaClean(TamaState &t)
|
||||
{
|
||||
uint16_t v = (uint16_t)t.hygiene + 40;
|
||||
t.hygiene = (v > 100) ? 100 : (uint8_t)v;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
#include "BuddyTypes.h"
|
||||
|
||||
struct TamaState {
|
||||
uint8_t hunger; // 0=full → 100=starving
|
||||
uint8_t happiness; // 100=happy → 0=bored
|
||||
uint8_t hygiene; // 100=clean → 0=dirty
|
||||
uint32_t nextHungerTick;
|
||||
uint32_t nextHappyTick;
|
||||
uint32_t nextHygieneTick;
|
||||
};
|
||||
|
||||
void initTama(TamaState &t, uint32_t now);
|
||||
|
||||
// Updates ticks and returns the mood override for buddy.
|
||||
// Returns MOOD_HUNGRY / MOOD_DIRTY / MOOD_PLAYFUL when a need is critical.
|
||||
// Returns MOOD_NORMAL when needs are satisfied or guard blocks (revertAt != 0, sleepy, wink).
|
||||
Mood updateTama(TamaState &t, const BuddyState &b, uint32_t now);
|
||||
|
||||
void tamaFeed(TamaState &t); // hunger -= 30 (floor 0)
|
||||
void tamaPlay(TamaState &t); // happy += 25 (cap 100)
|
||||
void tamaClean(TamaState &t); // hygiene += 40 (cap 100)
|
||||
@@ -0,0 +1 @@
|
||||
{ "name": "TamaLogic", "version": "1.0.0", "frameworks": "*", "platforms": "*" }
|
||||
Reference in New Issue
Block a user