bytepet-cli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +77 -0
- package/bin/byte.js +247 -0
- package/lib/pets.js +137 -0
- package/lib/rps.js +84 -0
- package/lib/state.js +89 -0
- package/package.json +36 -0
package/README.md
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# ⚡ bytepet-cli
|
|
2
|
+
|
|
3
|
+
> A terminal pet that lives in your CLI — feed it, play with it, watch it grow.
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
/\_____/\
|
|
7
|
+
( ^ ^ )
|
|
8
|
+
( =^.^= ) Pixel the Cat
|
|
9
|
+
(--m-m----) Level 3 · 45/300 XP
|
|
10
|
+
|
|
11
|
+
❤️ Health ██████████ 100%
|
|
12
|
+
🍖 Hunger ███████░░░ 70%
|
|
13
|
+
😊 Happiness ████████░░ 80%
|
|
14
|
+
⚡ Energy █████░░░░░ 50%
|
|
15
|
+
|
|
16
|
+
[f] Feed [p] Play [s] Sleep [q] Quit
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Install
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install -g bytepet-cli
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Usage
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
byte
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
That's it. Your pet remembers you between sessions — stats decay while you're away, so check in often!
|
|
32
|
+
|
|
33
|
+
## Pets
|
|
34
|
+
|
|
35
|
+
Choose from three pets on first run:
|
|
36
|
+
- 🐱 **Cat** — curious and independent
|
|
37
|
+
- 🐶 **Dog** — loyal and energetic
|
|
38
|
+
- 🐉 **Dragon** — rare and mysterious
|
|
39
|
+
|
|
40
|
+
## Stats
|
|
41
|
+
|
|
42
|
+
| Stat | Description |
|
|
43
|
+
|------|-------------|
|
|
44
|
+
| ❤️ Health | Drops if hunger or happiness hits 0 |
|
|
45
|
+
| 🍖 Hunger | Decays over time — feed your pet! |
|
|
46
|
+
| 😊 Happiness | Decays over time — play with your pet! |
|
|
47
|
+
| ⚡ Energy | Decays over time — let your pet sleep! |
|
|
48
|
+
| ⭐ XP / Level | Earned through feeding, playing, sleeping |
|
|
49
|
+
|
|
50
|
+
## Actions
|
|
51
|
+
|
|
52
|
+
| Key | Action | XP |
|
|
53
|
+
|-----|--------|----|
|
|
54
|
+
| `f` | Feed your pet | +10 XP |
|
|
55
|
+
| `p` | Play Rock Paper Scissors | +5–15 XP |
|
|
56
|
+
| `s` | Put your pet to sleep | +5 XP |
|
|
57
|
+
| `q` | Quit | — |
|
|
58
|
+
|
|
59
|
+
## Moods
|
|
60
|
+
|
|
61
|
+
Your pet's ASCII art changes based on its mood:
|
|
62
|
+
- 😊 **Happy** — all stats healthy
|
|
63
|
+
- 🍖 **Hungry** — hunger below 20%
|
|
64
|
+
- 😴 **Sleepy** — energy below 20%
|
|
65
|
+
- 😢 **Sad** — happiness below 30%
|
|
66
|
+
- 🤒 **Sick** — health below 20%
|
|
67
|
+
|
|
68
|
+
## Roadmap
|
|
69
|
+
|
|
70
|
+
- v0.1.0 — ✅ Core pet, 3 animals, 5 stats, RPS mini game
|
|
71
|
+
- v0.2.0 — More mini games, pet evolution stages, rare pets
|
|
72
|
+
- v0.3.0 — Pet accessories, backgrounds, seasonal events
|
|
73
|
+
- v1.0.0 — Pro tier: multiple pets, cloud sync, pet sharing
|
|
74
|
+
|
|
75
|
+
## License
|
|
76
|
+
|
|
77
|
+
UNLICENSED — © 2025 Muhammad Talha Khan
|
package/bin/byte.js
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
'use strict';
|
|
4
|
+
|
|
5
|
+
const readline = require('readline');
|
|
6
|
+
const { PETS, getMood, getArt } = require('../lib/pets');
|
|
7
|
+
const { load, save, applyDecay, createPet, addXP } = require('../lib/state');
|
|
8
|
+
const rps = require('../lib/rps');
|
|
9
|
+
|
|
10
|
+
// ── Colors ───────────────────────────────────────────────────────────────────
|
|
11
|
+
const c = {
|
|
12
|
+
reset: '\x1b[0m',
|
|
13
|
+
bold: '\x1b[1m',
|
|
14
|
+
dim: '\x1b[2m',
|
|
15
|
+
red: '\x1b[31m',
|
|
16
|
+
green: '\x1b[32m',
|
|
17
|
+
yellow: '\x1b[33m',
|
|
18
|
+
blue: '\x1b[34m',
|
|
19
|
+
magenta: '\x1b[35m',
|
|
20
|
+
cyan: '\x1b[36m',
|
|
21
|
+
white: '\x1b[37m',
|
|
22
|
+
gray: '\x1b[90m',
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const paint = (color, text) => `${color}${text}${c.reset}`;
|
|
26
|
+
|
|
27
|
+
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
28
|
+
function bar(value, max = 100, len = 10) {
|
|
29
|
+
const filled = Math.round((value / max) * len);
|
|
30
|
+
const empty = len - filled;
|
|
31
|
+
const color = value > 60 ? c.green : value > 30 ? c.yellow : c.red;
|
|
32
|
+
return `${color}${'█'.repeat(filled)}${c.gray}${'░'.repeat(empty)}${c.reset}`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function ask(rl, question) {
|
|
36
|
+
return new Promise(res => rl.question(question, ans => res(ans.trim())));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function clearScreen() {
|
|
40
|
+
process.stdout.write('\x1Bc');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function moodMessage(mood, name) {
|
|
44
|
+
const messages = {
|
|
45
|
+
happy: `${name} is happy and energetic! 😊`,
|
|
46
|
+
hungry: `${name} is hungry... feed me! 🍖`,
|
|
47
|
+
sleepy: `${name} is really tired... 😴`,
|
|
48
|
+
sad: `${name} needs some attention 😢`,
|
|
49
|
+
sick: `${name} is not feeling well 🤒`,
|
|
50
|
+
};
|
|
51
|
+
return messages[mood];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ── First run: pick pet ───────────────────────────────────────────────────────
|
|
55
|
+
async function onboard() {
|
|
56
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
57
|
+
|
|
58
|
+
clearScreen();
|
|
59
|
+
console.log('');
|
|
60
|
+
console.log(paint(c.bold + c.cyan, ' 🥚 Welcome to byte-cli!'));
|
|
61
|
+
console.log(paint(c.gray, ' Your terminal pet is waiting...\n'));
|
|
62
|
+
|
|
63
|
+
console.log(paint(c.bold, ' Choose your pet:\n'));
|
|
64
|
+
console.log(` ${paint(c.cyan, '[1]')} 🐱 Cat`);
|
|
65
|
+
console.log(` ${paint(c.yellow, '[2]')} 🐶 Dog`);
|
|
66
|
+
console.log(` ${paint(c.magenta, '[3]')} 🐉 Dragon`);
|
|
67
|
+
console.log('');
|
|
68
|
+
|
|
69
|
+
let petType = null;
|
|
70
|
+
while (!petType) {
|
|
71
|
+
const choice = await ask(rl, ' Your choice: ');
|
|
72
|
+
if (choice === '1') petType = 'cat';
|
|
73
|
+
else if (choice === '2') petType = 'dog';
|
|
74
|
+
else if (choice === '3') petType = 'dragon';
|
|
75
|
+
else console.log(paint(c.red, ' Please pick 1, 2, or 3.\n'));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
console.log('');
|
|
79
|
+
const name = await ask(rl, paint(c.bold, ` What will you name your ${PETS[petType].name}?\n `) + '> ');
|
|
80
|
+
rl.close();
|
|
81
|
+
|
|
82
|
+
const petName = name || PETS[petType].name;
|
|
83
|
+
const state = createPet(petName, petType);
|
|
84
|
+
save(state);
|
|
85
|
+
|
|
86
|
+
clearScreen();
|
|
87
|
+
console.log('');
|
|
88
|
+
console.log(paint(c.bold + c.green, ` 🎉 ${petName} the ${PETS[petType].name} has been born!\n`));
|
|
89
|
+
console.log(paint(c.gray, ' Take good care of them. Come back often — they miss you when you\'re away.\n'));
|
|
90
|
+
|
|
91
|
+
await new Promise(res => setTimeout(res, 1500));
|
|
92
|
+
return state;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ── Render main screen ────────────────────────────────────────────────────────
|
|
96
|
+
function renderHome(state) {
|
|
97
|
+
const { name, petType, stats } = state;
|
|
98
|
+
const mood = getMood(stats);
|
|
99
|
+
const art = getArt(petType, stats);
|
|
100
|
+
const xpNeeded = stats.level * 100;
|
|
101
|
+
|
|
102
|
+
clearScreen();
|
|
103
|
+
console.log('');
|
|
104
|
+
console.log(paint(c.bold + c.cyan, ' ⚡ byte-cli\n'));
|
|
105
|
+
|
|
106
|
+
// ASCII art
|
|
107
|
+
art.forEach(line => console.log(paint(c.yellow, ' ' + line)));
|
|
108
|
+
console.log('');
|
|
109
|
+
|
|
110
|
+
// Name + level
|
|
111
|
+
console.log(` ${paint(c.bold, name + ' the ' + PETS[petType].name)} ${paint(c.gray, '·')} ${paint(c.magenta, 'Level ' + stats.level)} ${paint(c.gray, '·')} ${paint(c.dim, stats.xp + '/' + xpNeeded + ' XP')}`);
|
|
112
|
+
console.log('');
|
|
113
|
+
|
|
114
|
+
// Stats
|
|
115
|
+
console.log(` ❤️ Health ${bar(stats.health)} ${paint(c.gray, Math.round(stats.health) + '%')}`);
|
|
116
|
+
console.log(` 🍖 Hunger ${bar(stats.hunger)} ${paint(c.gray, Math.round(stats.hunger) + '%')}`);
|
|
117
|
+
console.log(` 😊 Happiness ${bar(stats.happiness)} ${paint(c.gray, Math.round(stats.happiness) + '%')}`);
|
|
118
|
+
console.log(` ⚡ Energy ${bar(stats.energy)} ${paint(c.gray, Math.round(stats.energy) + '%')}`);
|
|
119
|
+
console.log('');
|
|
120
|
+
|
|
121
|
+
// Mood message
|
|
122
|
+
console.log(` ${paint(c.dim, moodMessage(mood, name))}`);
|
|
123
|
+
console.log('');
|
|
124
|
+
|
|
125
|
+
// Actions
|
|
126
|
+
console.log(paint(c.gray, ' ─────────────────────────────────────────'));
|
|
127
|
+
console.log(` ${paint(c.cyan, '[f]')} Feed ${paint(c.cyan, '[p]')} Play ${paint(c.cyan, '[s]')} Sleep ${paint(c.cyan, '[q]')} Quit`);
|
|
128
|
+
console.log('');
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// ── Actions ───────────────────────────────────────────────────────────────────
|
|
132
|
+
function feed(state) {
|
|
133
|
+
const s = { ...state, stats: { ...state.stats } };
|
|
134
|
+
const boost = Math.min(100 - s.stats.hunger, 30);
|
|
135
|
+
s.stats.hunger = Math.min(100, s.stats.hunger + 30);
|
|
136
|
+
s.stats.happiness = Math.min(100, s.stats.happiness + 5);
|
|
137
|
+
const { state: newState, leveledUp } = addXP(s, 10);
|
|
138
|
+
return { state: newState, leveledUp, msg: `${paint(c.green, '🍖 Yum!')} Hunger +${boost} XP +10` };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function sleep(state) {
|
|
142
|
+
const s = { ...state, stats: { ...state.stats } };
|
|
143
|
+
s.stats.energy = Math.min(100, s.stats.energy + 40);
|
|
144
|
+
s.stats.health = Math.min(100, s.stats.health + 5);
|
|
145
|
+
const { state: newState, leveledUp } = addXP(s, 5);
|
|
146
|
+
return { state: newState, leveledUp, msg: `${paint(c.blue, '💤 Zzz...')} Energy +40 XP +5` };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// ── Main loop ─────────────────────────────────────────────────────────────────
|
|
150
|
+
async function main() {
|
|
151
|
+
let state = load();
|
|
152
|
+
|
|
153
|
+
if (!state) {
|
|
154
|
+
state = await onboard();
|
|
155
|
+
} else {
|
|
156
|
+
state = applyDecay(state);
|
|
157
|
+
save(state);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
161
|
+
readline.emitKeypressEvents(process.stdin);
|
|
162
|
+
if (process.stdin.isTTY) process.stdin.setRawMode(true);
|
|
163
|
+
|
|
164
|
+
let message = '';
|
|
165
|
+
let running = true;
|
|
166
|
+
|
|
167
|
+
renderHome(state);
|
|
168
|
+
if (message) console.log(` ${message}\n`);
|
|
169
|
+
|
|
170
|
+
process.stdin.on('keypress', async (str, key) => {
|
|
171
|
+
if (!running) return;
|
|
172
|
+
|
|
173
|
+
if (key.name === 'q' || (key.ctrl && key.name === 'c')) {
|
|
174
|
+
running = false;
|
|
175
|
+
state.lastSeen = Date.now();
|
|
176
|
+
save(state);
|
|
177
|
+
rl.close();
|
|
178
|
+
clearScreen();
|
|
179
|
+
console.log('');
|
|
180
|
+
console.log(paint(c.cyan, ` 👋 Goodbye! ${state.name} will miss you.\n`));
|
|
181
|
+
process.exit(0);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (key.name === 'f') {
|
|
185
|
+
const result = feed(state);
|
|
186
|
+
state = result.state;
|
|
187
|
+
message = result.msg;
|
|
188
|
+
if (result.leveledUp) message += paint(c.magenta + c.bold, ` 🎉 LEVEL UP! Now level ${state.stats.level}!`);
|
|
189
|
+
save(state);
|
|
190
|
+
renderHome(state);
|
|
191
|
+
console.log(` ${message}\n`);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (key.name === 's') {
|
|
195
|
+
const result = sleep(state);
|
|
196
|
+
state = result.state;
|
|
197
|
+
message = result.msg;
|
|
198
|
+
if (result.leveledUp) message += paint(c.magenta + c.bold, ` 🎉 LEVEL UP! Now level ${state.stats.level}!`);
|
|
199
|
+
save(state);
|
|
200
|
+
renderHome(state);
|
|
201
|
+
console.log(` ${message}\n`);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (key.name === 'p') {
|
|
205
|
+
process.stdin.setRawMode(false);
|
|
206
|
+
rl.close();
|
|
207
|
+
running = false;
|
|
208
|
+
|
|
209
|
+
const result = await rps.play(state.name);
|
|
210
|
+
|
|
211
|
+
// Apply game results to stats
|
|
212
|
+
state.stats.happiness = Math.min(100, state.stats.happiness + result.happinessBoost);
|
|
213
|
+
state.stats.energy = Math.max(0, state.stats.energy - result.energyCost);
|
|
214
|
+
const { state: newState, leveledUp } = addXP(state, result.xpEarned);
|
|
215
|
+
state = newState;
|
|
216
|
+
|
|
217
|
+
if (leveledUp) {
|
|
218
|
+
console.log(paint(c.magenta + c.bold, ` 🎉 LEVEL UP! ${state.name} is now level ${state.stats.level}!\n`));
|
|
219
|
+
await new Promise(res => setTimeout(res, 1500));
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
save(state);
|
|
223
|
+
|
|
224
|
+
// Restart loop
|
|
225
|
+
running = true;
|
|
226
|
+
const newRl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
227
|
+
readline.emitKeypressEvents(process.stdin);
|
|
228
|
+
if (process.stdin.isTTY) process.stdin.setRawMode(true);
|
|
229
|
+
|
|
230
|
+
renderHome(state);
|
|
231
|
+
|
|
232
|
+
process.stdin.removeAllListeners('keypress');
|
|
233
|
+
process.stdin.on('keypress', async (str, key) => {
|
|
234
|
+
// re-attach same handler by restarting main — simplest approach
|
|
235
|
+
newRl.close();
|
|
236
|
+
process.stdin.setRawMode(false);
|
|
237
|
+
process.stdin.removeAllListeners('keypress');
|
|
238
|
+
main();
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
main().catch(err => {
|
|
245
|
+
console.error('Error:', err.message);
|
|
246
|
+
process.exit(1);
|
|
247
|
+
});
|
package/lib/pets.js
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const PETS = {
|
|
4
|
+
cat: {
|
|
5
|
+
emoji: '🐱',
|
|
6
|
+
name: 'Cat',
|
|
7
|
+
moods: {
|
|
8
|
+
happy: [
|
|
9
|
+
' /\\_____/\\ ',
|
|
10
|
+
' ( ^ ^ ) ',
|
|
11
|
+
' ( =^.^= ) ',
|
|
12
|
+
' (--m-m----) ',
|
|
13
|
+
],
|
|
14
|
+
hungry: [
|
|
15
|
+
' /\\_____/\\ ',
|
|
16
|
+
' ( - - ) ',
|
|
17
|
+
' ( =o.o= ) ',
|
|
18
|
+
' (--m-m----) ',
|
|
19
|
+
],
|
|
20
|
+
sleepy: [
|
|
21
|
+
' /\\_____/\\ ',
|
|
22
|
+
' ( - - ) ',
|
|
23
|
+
' ( =-.-= ) ',
|
|
24
|
+
' (--m-m----) ',
|
|
25
|
+
],
|
|
26
|
+
sad: [
|
|
27
|
+
' /\\_____/\\ ',
|
|
28
|
+
' ( T T ) ',
|
|
29
|
+
' ( =v.v= ) ',
|
|
30
|
+
' (--m-m----) ',
|
|
31
|
+
],
|
|
32
|
+
sick: [
|
|
33
|
+
' /\\_____/\\ ',
|
|
34
|
+
' ( x x ) ',
|
|
35
|
+
' ( =@.@= ) ',
|
|
36
|
+
' (--m-m----) ',
|
|
37
|
+
],
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
dog: {
|
|
41
|
+
emoji: '🐶',
|
|
42
|
+
name: 'Dog',
|
|
43
|
+
moods: {
|
|
44
|
+
happy: [
|
|
45
|
+
' / \\_/ \\ ',
|
|
46
|
+
' ( ^ ^ ) ',
|
|
47
|
+
' ( o.o ) ',
|
|
48
|
+
' \\ ___/ ',
|
|
49
|
+
' | | ',
|
|
50
|
+
],
|
|
51
|
+
hungry: [
|
|
52
|
+
' / \\_/ \\ ',
|
|
53
|
+
' ( - - ) ',
|
|
54
|
+
' ( u.u ) ',
|
|
55
|
+
' \\ ___/ ',
|
|
56
|
+
' | | ',
|
|
57
|
+
],
|
|
58
|
+
sleepy: [
|
|
59
|
+
' / \\_/ \\ ',
|
|
60
|
+
' ( - - ) ',
|
|
61
|
+
' ( -.– ) ',
|
|
62
|
+
' \\ ___/ ',
|
|
63
|
+
' | | ',
|
|
64
|
+
],
|
|
65
|
+
sad: [
|
|
66
|
+
' / \\_/ \\ ',
|
|
67
|
+
' ( T T ) ',
|
|
68
|
+
' ( v.v ) ',
|
|
69
|
+
' \\ ___/ ',
|
|
70
|
+
' | | ',
|
|
71
|
+
],
|
|
72
|
+
sick: [
|
|
73
|
+
' / \\_/ \\ ',
|
|
74
|
+
' ( x x ) ',
|
|
75
|
+
' ( @.@ ) ',
|
|
76
|
+
' \\ ___/ ',
|
|
77
|
+
' | | ',
|
|
78
|
+
],
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
dragon: {
|
|
82
|
+
emoji: '🐉',
|
|
83
|
+
name: 'Dragon',
|
|
84
|
+
moods: {
|
|
85
|
+
happy: [
|
|
86
|
+
' / \\ / \\ ',
|
|
87
|
+
' ( o \\/ o)',
|
|
88
|
+
' ( /\\ )',
|
|
89
|
+
' __\\_/ \\_/__ ',
|
|
90
|
+
'(____________)',
|
|
91
|
+
],
|
|
92
|
+
hungry: [
|
|
93
|
+
' / \\ / \\ ',
|
|
94
|
+
' ( - \\/ -)',
|
|
95
|
+
' ( /\\ )',
|
|
96
|
+
' __\\_/ \\_/__ ',
|
|
97
|
+
'(____________)',
|
|
98
|
+
],
|
|
99
|
+
sleepy: [
|
|
100
|
+
' / \\ / \\ ',
|
|
101
|
+
' ( - \\/ -)',
|
|
102
|
+
' ( -– )',
|
|
103
|
+
' __\\_/ \\_/__ ',
|
|
104
|
+
'(____________)',
|
|
105
|
+
],
|
|
106
|
+
sad: [
|
|
107
|
+
' / \\ / \\ ',
|
|
108
|
+
' ( T \\/ T)',
|
|
109
|
+
' ( /\\ )',
|
|
110
|
+
' __\\_/ \\_/__ ',
|
|
111
|
+
'(____________)',
|
|
112
|
+
],
|
|
113
|
+
sick: [
|
|
114
|
+
' / \\ / \\ ',
|
|
115
|
+
' ( x \\/ x)',
|
|
116
|
+
' ( @~@ )',
|
|
117
|
+
' __\\_/ \\_/__ ',
|
|
118
|
+
'(____________)',
|
|
119
|
+
],
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
function getMood(stats) {
|
|
125
|
+
if (stats.health < 20) return 'sick';
|
|
126
|
+
if (stats.hunger < 20) return 'hungry';
|
|
127
|
+
if (stats.energy < 20) return 'sleepy';
|
|
128
|
+
if (stats.happiness < 30) return 'sad';
|
|
129
|
+
return 'happy';
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function getArt(petType, stats) {
|
|
133
|
+
const mood = getMood(stats);
|
|
134
|
+
return PETS[petType].moods[mood];
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
module.exports = { PETS, getMood, getArt };
|
package/lib/rps.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const readline = require('readline');
|
|
4
|
+
|
|
5
|
+
const CHOICES = ['rock', 'paper', 'scissors'];
|
|
6
|
+
const EMOJI = { rock: '🪨', paper: '📄', scissors: '✂️' };
|
|
7
|
+
|
|
8
|
+
const c = {
|
|
9
|
+
reset: '\x1b[0m',
|
|
10
|
+
bold: '\x1b[1m',
|
|
11
|
+
green: '\x1b[32m',
|
|
12
|
+
red: '\x1b[31m',
|
|
13
|
+
yellow: '\x1b[33m',
|
|
14
|
+
cyan: '\x1b[36m',
|
|
15
|
+
gray: '\x1b[90m',
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
function getResult(player, computer) {
|
|
19
|
+
if (player === computer) return 'draw';
|
|
20
|
+
if (
|
|
21
|
+
(player === 'rock' && computer === 'scissors') ||
|
|
22
|
+
(player === 'paper' && computer === 'rock') ||
|
|
23
|
+
(player === 'scissors' && computer === 'paper')
|
|
24
|
+
) return 'win';
|
|
25
|
+
return 'lose';
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function play(petName) {
|
|
29
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
30
|
+
const ask = (q) => new Promise(res => rl.question(q, res));
|
|
31
|
+
|
|
32
|
+
console.log(`\n${c.cyan}${c.bold} 🎮 Rock Paper Scissors with ${petName}!${c.reset}\n`);
|
|
33
|
+
console.log(`${c.gray} [1] Rock [2] Paper [3] Scissors [q] Quit${c.reset}\n`);
|
|
34
|
+
|
|
35
|
+
let wins = 0, losses = 0, draws = 0;
|
|
36
|
+
let xpEarned = 0;
|
|
37
|
+
let playing = true;
|
|
38
|
+
|
|
39
|
+
while (playing) {
|
|
40
|
+
const input = (await ask(` Your choice: `)).trim().toLowerCase();
|
|
41
|
+
|
|
42
|
+
if (input === 'q') { playing = false; break; }
|
|
43
|
+
|
|
44
|
+
const idx = parseInt(input) - 1;
|
|
45
|
+
if (isNaN(idx) || idx < 0 || idx > 2) {
|
|
46
|
+
console.log(`${c.yellow} Invalid choice. Pick 1, 2, or 3.${c.reset}\n`);
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const playerChoice = CHOICES[idx];
|
|
51
|
+
const computerChoice = CHOICES[Math.floor(Math.random() * 3)];
|
|
52
|
+
const result = getResult(playerChoice, computerChoice);
|
|
53
|
+
|
|
54
|
+
console.log(`\n You: ${EMOJI[playerChoice]} ${playerChoice}`);
|
|
55
|
+
console.log(` ${petName}: ${EMOJI[computerChoice]} ${computerChoice}\n`);
|
|
56
|
+
|
|
57
|
+
if (result === 'win') {
|
|
58
|
+
wins++;
|
|
59
|
+
xpEarned += 15;
|
|
60
|
+
console.log(`${c.green}${c.bold} 🎉 You win! +15 XP${c.reset}\n`);
|
|
61
|
+
} else if (result === 'lose') {
|
|
62
|
+
losses++;
|
|
63
|
+
xpEarned += 5;
|
|
64
|
+
console.log(`${c.red} 😅 ${petName} wins this round! +5 XP${c.reset}\n`);
|
|
65
|
+
} else {
|
|
66
|
+
draws++;
|
|
67
|
+
xpEarned += 8;
|
|
68
|
+
console.log(`${c.yellow} 🤝 Draw! +8 XP${c.reset}\n`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const again = (await ask(` Play again? [y/n]: `)).trim().toLowerCase();
|
|
72
|
+
if (again !== 'y') playing = false;
|
|
73
|
+
console.log('');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
rl.close();
|
|
77
|
+
|
|
78
|
+
console.log(`${c.gray} Score — Wins: ${wins} | Losses: ${losses} | Draws: ${draws}${c.reset}`);
|
|
79
|
+
console.log(`${c.cyan} Total XP earned: +${xpEarned}${c.reset}\n`);
|
|
80
|
+
|
|
81
|
+
return { xpEarned, happinessBoost: wins * 8, energyCost: (wins + losses + draws) * 3 };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
module.exports = { play };
|
package/lib/state.js
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
|
|
7
|
+
const STATE_DIR = path.join(os.homedir(), '.byte-cli');
|
|
8
|
+
const STATE_FILE = path.join(STATE_DIR, 'pet.json');
|
|
9
|
+
|
|
10
|
+
const DECAY_RATES = {
|
|
11
|
+
hunger: 0.8, // per hour
|
|
12
|
+
happiness: 0.5,
|
|
13
|
+
energy: 0.4,
|
|
14
|
+
health: 0.2,
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
function ensureDir() {
|
|
18
|
+
if (!fs.existsSync(STATE_DIR)) {
|
|
19
|
+
fs.mkdirSync(STATE_DIR, { recursive: true });
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function load() {
|
|
24
|
+
ensureDir();
|
|
25
|
+
if (!fs.existsSync(STATE_FILE)) return null;
|
|
26
|
+
try {
|
|
27
|
+
return JSON.parse(fs.readFileSync(STATE_FILE, 'utf8'));
|
|
28
|
+
} catch {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function save(state) {
|
|
34
|
+
ensureDir();
|
|
35
|
+
fs.writeFileSync(STATE_FILE, JSON.stringify(state, null, 2));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function applyDecay(state) {
|
|
39
|
+
const now = Date.now();
|
|
40
|
+
const lastSeen = state.lastSeen || now;
|
|
41
|
+
const hoursElapsed = (now - lastSeen) / (1000 * 60 * 60);
|
|
42
|
+
|
|
43
|
+
if (hoursElapsed < 0.05) return state; // less than 3 mins, skip
|
|
44
|
+
|
|
45
|
+
const newState = { ...state, stats: { ...state.stats } };
|
|
46
|
+
|
|
47
|
+
for (const [stat, rate] of Object.entries(DECAY_RATES)) {
|
|
48
|
+
newState.stats[stat] = Math.max(0, newState.stats[stat] - rate * hoursElapsed);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Health drops if hunger or happiness hits 0
|
|
52
|
+
if (newState.stats.hunger === 0 || newState.stats.happiness === 0) {
|
|
53
|
+
newState.stats.health = Math.max(0, newState.stats.health - 1 * hoursElapsed);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
newState.lastSeen = now;
|
|
57
|
+
return newState;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function createPet(name, petType) {
|
|
61
|
+
return {
|
|
62
|
+
name,
|
|
63
|
+
petType,
|
|
64
|
+
stats: {
|
|
65
|
+
hunger: 80,
|
|
66
|
+
happiness: 80,
|
|
67
|
+
energy: 80,
|
|
68
|
+
health: 100,
|
|
69
|
+
xp: 0,
|
|
70
|
+
level: 1,
|
|
71
|
+
},
|
|
72
|
+
lastSeen: Date.now(),
|
|
73
|
+
born: Date.now(),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function addXP(state, amount) {
|
|
78
|
+
const newState = { ...state, stats: { ...state.stats } };
|
|
79
|
+
newState.stats.xp += amount;
|
|
80
|
+
const xpNeeded = newState.stats.level * 100;
|
|
81
|
+
if (newState.stats.xp >= xpNeeded) {
|
|
82
|
+
newState.stats.xp -= xpNeeded;
|
|
83
|
+
newState.stats.level += 1;
|
|
84
|
+
return { state: newState, leveledUp: true };
|
|
85
|
+
}
|
|
86
|
+
return { state: newState, leveledUp: false };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
module.exports = { load, save, applyDecay, createPet, addXP };
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "bytepet-cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "⚡ A terminal pet that lives in your CLI — feed it, play with it, watch it grow",
|
|
5
|
+
"main": "bin/byte.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"bytepet": "./bin/byte.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"start": "node bin/byte.js"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"cli",
|
|
14
|
+
"pet",
|
|
15
|
+
"tamagotchi",
|
|
16
|
+
"terminal",
|
|
17
|
+
"ascii",
|
|
18
|
+
"fun",
|
|
19
|
+
"game",
|
|
20
|
+
"developer"
|
|
21
|
+
],
|
|
22
|
+
"author": "Muhammad Talha Khan",
|
|
23
|
+
"license": "UNLICENSED",
|
|
24
|
+
"private": false,
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">=14"
|
|
27
|
+
},
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "https://github.com/muhtalhakhan/bytepet-cli.git"
|
|
31
|
+
},
|
|
32
|
+
"homepage": "https://github.com/muhtalhakhan/bytepet-cli#readme",
|
|
33
|
+
"bugs": {
|
|
34
|
+
"url": "https://github.com/muhtalhakhan/bytepet-cli/issues"
|
|
35
|
+
}
|
|
36
|
+
}
|