mirror of
https://github.com/SpudGunMan/meshing-around.git
synced 2026-03-28 17:32:36 +01:00
Compare commits
465 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
abeb28c9cc | ||
|
|
2325f74581 | ||
|
|
e75ef3e44d | ||
|
|
6431b45769 | ||
|
|
7dfcbb619f | ||
|
|
051b58ca6f | ||
|
|
5777b39c22 | ||
|
|
eb4f135698 | ||
|
|
3bcb04ece7 | ||
|
|
e42aca875f | ||
|
|
28915ab848 | ||
|
|
0fe491871d | ||
|
|
6fc6a483a8 | ||
|
|
13236880b5 | ||
|
|
9461719039 | ||
|
|
63163cc4c1 | ||
|
|
5cdf159bc1 | ||
|
|
47a9981fdf | ||
|
|
f85bdb7d02 | ||
|
|
7796d03e21 | ||
|
|
90094c082a | ||
|
|
2b695e2f2e | ||
|
|
2e05f3ef64 | ||
|
|
316f1efd08 | ||
|
|
4678a63955 | ||
|
|
8584454d5d | ||
|
|
2c6cf76a10 | ||
|
|
cd3226df21 | ||
|
|
4bcc6ef1f2 | ||
|
|
77e56c25ae | ||
|
|
e7b363612a | ||
|
|
a217c61ba1 | ||
|
|
e7b4fe44c8 | ||
|
|
f8cc580b99 | ||
|
|
03057b3263 | ||
|
|
452b9b7c67 | ||
|
|
5ae16d0adc | ||
|
|
5ed135c023 | ||
|
|
d425298cd9 | ||
|
|
786815d073 | ||
|
|
54cad92a3f | ||
|
|
54e21f4644 | ||
|
|
3c76f177cd | ||
|
|
aa05c62d94 | ||
|
|
3f16158e27 | ||
|
|
6f2824512d | ||
|
|
723b67f886 | ||
|
|
008d55e63b | ||
|
|
79885454ab | ||
|
|
ba21723bdc | ||
|
|
c36c4918a8 | ||
|
|
853147518d | ||
|
|
2f19d86c95 | ||
|
|
39bdabffcb | ||
|
|
a7bdaedfe1 | ||
|
|
1c6106081f | ||
|
|
8ab6cded2e | ||
|
|
ff63bb959a | ||
|
|
6c79bb1ff0 | ||
|
|
ce73336c0c | ||
|
|
248977c5b5 | ||
|
|
77a6e63210 | ||
|
|
6f6fb35177 | ||
|
|
9db565cb4f | ||
|
|
2a254a7fab | ||
|
|
15e76ab029 | ||
|
|
66b95cdaa0 | ||
|
|
32ea93cb88 | ||
|
|
22a2a64861 | ||
|
|
d841fdf02c | ||
|
|
9421f09ded | ||
|
|
b4af552fb9 | ||
|
|
69dfb17460 | ||
|
|
4703750c27 | ||
|
|
40caf99939 | ||
|
|
df5f648b26 | ||
|
|
55472bbbc0 | ||
|
|
f23c4e2b6a | ||
|
|
0b101d662e | ||
|
|
a7f07afc14 | ||
|
|
2715021898 | ||
|
|
e8fa0036e2 | ||
|
|
f628a5e7ef | ||
|
|
95e6bc120e | ||
|
|
0e35c891c4 | ||
|
|
b7a3e7014c | ||
|
|
0c1c587bc7 | ||
|
|
a0a2c60e63 | ||
|
|
45c912a0d6 | ||
|
|
39945f161d | ||
|
|
ed958302bd | ||
|
|
477f2141d7 | ||
|
|
d321a958f0 | ||
|
|
d14f1df823 | ||
|
|
f7e3b9f6c7 | ||
|
|
cd3ac201f8 | ||
|
|
ceef493b01 | ||
|
|
480a75e30c | ||
|
|
d8cc953fe7 | ||
|
|
0baec88321 | ||
|
|
74bd3f681f | ||
|
|
713b750f4a | ||
|
|
11eee911ca | ||
|
|
b288aaea90 | ||
|
|
7acc018fd2 | ||
|
|
7aba1096f9 | ||
|
|
0be7202144 | ||
|
|
83a5db74e5 | ||
|
|
8dc4371beb | ||
|
|
e5045a0984 | ||
|
|
2c9b37a0cc | ||
|
|
b608482220 | ||
|
|
9290fac899 | ||
|
|
d7901ee575 | ||
|
|
7eb33a5aef | ||
|
|
c5dc103ac0 | ||
|
|
c90172a862 | ||
|
|
8540786c2c | ||
|
|
7aeb8e851d | ||
|
|
2f207dc3d9 | ||
|
|
7e2be73962 | ||
|
|
e2a87eb945 | ||
|
|
4b79c6304f | ||
|
|
22f63e1056 | ||
|
|
bf1613ba66 | ||
|
|
38e04db0cb | ||
|
|
08cc3034a2 | ||
|
|
a0eb176b6e | ||
|
|
a43774b33f | ||
|
|
8369b4d205 | ||
|
|
87e51cf9f9 | ||
|
|
8a8fb79fde | ||
|
|
1dc4cf36b1 | ||
|
|
75cf43e02a | ||
|
|
dd8357453b | ||
|
|
e9b273a62c | ||
|
|
ebebd0fda6 | ||
|
|
3e06902b07 | ||
|
|
a874b42c41 | ||
|
|
f6215d3563 | ||
|
|
4fad58f7fe | ||
|
|
276e4c3d09 | ||
|
|
8aef4d605b | ||
|
|
962c891baa | ||
|
|
eb596ea901 | ||
|
|
e708ec9adc | ||
|
|
c631a083ea | ||
|
|
169ea8c233 | ||
|
|
241e2258e8 | ||
|
|
5d2d6bc5fb | ||
|
|
c7ef22f5c2 | ||
|
|
3ec89decf0 | ||
|
|
2693f47ed5 | ||
|
|
496a13e0e0 | ||
|
|
71ef416dbb | ||
|
|
5bc80c8677 | ||
|
|
8c300f467c | ||
|
|
78b3bf1475 | ||
|
|
ce74f910a7 | ||
|
|
ece531249e | ||
|
|
5052e2510e | ||
|
|
b8f8f80499 | ||
|
|
a13d98e32b | ||
|
|
94996dcec8 | ||
|
|
4b8ce30df8 | ||
|
|
485a37e9b5 | ||
|
|
b41adb12f7 | ||
|
|
a9cd443bdd | ||
|
|
92b9df718f | ||
|
|
a6a4f91d83 | ||
|
|
0fb2c498f4 | ||
|
|
d87e70f7ee | ||
|
|
bee8bae0ae | ||
|
|
d5ef277121 | ||
|
|
dfc8e6c108 | ||
|
|
9100aee91f | ||
|
|
c94cc92d1c | ||
|
|
a41f5e3aca | ||
|
|
cc2d15de0d | ||
|
|
39e08aedae | ||
|
|
fe8730fb1f | ||
|
|
aac3ac8947 | ||
|
|
1acc908bb8 | ||
|
|
47ed98a5e1 | ||
|
|
103034d1b8 | ||
|
|
608714d00a | ||
|
|
575c287860 | ||
|
|
820470bef7 | ||
|
|
ea000ef56c | ||
|
|
793b8f8495 | ||
|
|
373eee3024 | ||
|
|
194273635e | ||
|
|
0972d7d89d | ||
|
|
d1415f9d86 | ||
|
|
b3ff3bb406 | ||
|
|
1efce62a8a | ||
|
|
442f2ff927 | ||
|
|
84cefa1be8 | ||
|
|
45e9c1eccb | ||
|
|
e7e2c9604e | ||
|
|
edaf6875ef | ||
|
|
e7976a0a88 | ||
|
|
e7daae9250 | ||
|
|
91a188f3fd | ||
|
|
49a6d48101 | ||
|
|
a4b5ed3597 | ||
|
|
2efbfeef8c | ||
|
|
1a9093bbdf | ||
|
|
9b3d3c6bdc | ||
|
|
620a6f4795 | ||
|
|
1615fa9e51 | ||
|
|
205906ebb7 | ||
|
|
0d8016651c | ||
|
|
ffdc776413 | ||
|
|
9b92b3d1ae | ||
|
|
3bfea2fd0e | ||
|
|
97636ca575 | ||
|
|
8c4c2095d7 | ||
|
|
9855656e9a | ||
|
|
cffdf92daf | ||
|
|
d07ca4076e | ||
|
|
47740df9a9 | ||
|
|
e830d3fef9 | ||
|
|
3c0a87354e | ||
|
|
b5a2eb9fb9 | ||
|
|
244bd02771 | ||
|
|
95a978495d | ||
|
|
292ad8bb61 | ||
|
|
8b0a2263e7 | ||
|
|
bb9ffe3492 | ||
|
|
ea7ef074e1 | ||
|
|
897d6727b4 | ||
|
|
0f99728be3 | ||
|
|
51903d04f5 | ||
|
|
dde7dbf3a0 | ||
|
|
508998efa8 | ||
|
|
2691247532 | ||
|
|
f3c9d8211f | ||
|
|
ec970d9f08 | ||
|
|
04b86ae7e4 | ||
|
|
b7b31ca5c2 | ||
|
|
7c76e3b3a9 | ||
|
|
863f8d8630 | ||
|
|
b12d16f7a7 | ||
|
|
ead8474783 | ||
|
|
03a1ead721 | ||
|
|
234dbb60fe | ||
|
|
43503df53c | ||
|
|
6c381aecb7 | ||
|
|
8d1e366e10 | ||
|
|
d6a0713f52 | ||
|
|
113bde4bab | ||
|
|
b5354a2431 | ||
|
|
c166dd9ed8 | ||
|
|
80b73da72c | ||
|
|
98490e6444 | ||
|
|
56649471d0 | ||
|
|
0186bdc15a | ||
|
|
eb02014371 | ||
|
|
993292cec8 | ||
|
|
aac99eaf07 | ||
|
|
e5d82df65c | ||
|
|
10bbbecf7b | ||
|
|
8b5f7c4bcb | ||
|
|
10c568fcac | ||
|
|
7443a2fbc4 | ||
|
|
3e5681abc5 | ||
|
|
e5036c0b45 | ||
|
|
a312a81141 | ||
|
|
40dd55d828 | ||
|
|
135bb622e1 | ||
|
|
ed8911be5f | ||
|
|
a126d0610f | ||
|
|
946301ba86 | ||
|
|
604e8ec366 | ||
|
|
2593dd4e86 | ||
|
|
9ee9cdd969 | ||
|
|
ad3918f071 | ||
|
|
3cbc1efa59 | ||
|
|
446f0336b6 | ||
|
|
e3c3becb63 | ||
|
|
8564c85378 | ||
|
|
eedca4c1d0 | ||
|
|
28c46dcbfd | ||
|
|
544c4c3e80 | ||
|
|
8bfd1a4d11 | ||
|
|
3fae6241a7 | ||
|
|
48ff4209c4 | ||
|
|
1d3986c589 | ||
|
|
52b2ec776b | ||
|
|
9d3c6b28ef | ||
|
|
c242eb3750 | ||
|
|
e718fea565 | ||
|
|
14644da9c1 | ||
|
|
0c99dc52d3 | ||
|
|
9cf94d2d1d | ||
|
|
de560f9b8a | ||
|
|
47b8b7a9b8 | ||
|
|
216c468118 | ||
|
|
dd6735167b | ||
|
|
a400eabc0e | ||
|
|
3b95f1b873 | ||
|
|
7b1441814d | ||
|
|
cc39a3a173 | ||
|
|
34df5cfc6a | ||
|
|
37836e7842 | ||
|
|
121231f6fa | ||
|
|
71c8c98ce9 | ||
|
|
2c3cce4d51 | ||
|
|
34ed1247e3 | ||
|
|
e8351a1136 | ||
|
|
4f93e3f5e2 | ||
|
|
33017aa948 | ||
|
|
cbfe68230c | ||
|
|
775c998e4d | ||
|
|
46b8fc7dcd | ||
|
|
d671eb14ce | ||
|
|
cd7d4969ef | ||
|
|
a4e7509354 | ||
|
|
cd74573adc | ||
|
|
86f14ad88a | ||
|
|
847634e524 | ||
|
|
f162019f77 | ||
|
|
b814632f74 | ||
|
|
741ced6289 | ||
|
|
d939a69d16 | ||
|
|
08416cb4e1 | ||
|
|
a30a607f4f | ||
|
|
5caae28b55 | ||
|
|
c85b9d9ee1 | ||
|
|
3d8c8520de | ||
|
|
9e8aec16e5 | ||
|
|
91ea47a6f4 | ||
|
|
c0d108ee77 | ||
|
|
94479f47d9 | ||
|
|
f6fd017871 | ||
|
|
15de706fba | ||
|
|
f7745ac556 | ||
|
|
ad32ed5a80 | ||
|
|
18e1522374 | ||
|
|
bd01da15cc | ||
|
|
1b4ca71e38 | ||
|
|
68fabef2be | ||
|
|
c07f756d67 | ||
|
|
bf52369d2d | ||
|
|
e49e63f39a | ||
|
|
f651f2e577 | ||
|
|
f424da410b | ||
|
|
ada809066c | ||
|
|
e7ec8518e7 | ||
|
|
1ea6a4d987 | ||
|
|
f111aad6b7 | ||
|
|
88282db0e4 | ||
|
|
55e09938bb | ||
|
|
add6c73af7 | ||
|
|
62a932b9ed | ||
|
|
037024395d | ||
|
|
e2105e1fa3 | ||
|
|
be67b33f99 | ||
|
|
7224da9c7a | ||
|
|
fd68007c41 | ||
|
|
e77c7935de | ||
|
|
cf0072fce4 | ||
|
|
6f8a8b4264 | ||
|
|
746db62fdb | ||
|
|
fa4e254ba4 | ||
|
|
0a2e385f41 | ||
|
|
2d6e939306 | ||
|
|
b5d7107760 | ||
|
|
575dd08b74 | ||
|
|
b043541dca | ||
|
|
fa5fd85b5d | ||
|
|
be68cece47 | ||
|
|
50245e618b | ||
|
|
e0d85a65f9 | ||
|
|
a414a994fb | ||
|
|
c0e7f4c0fa | ||
|
|
c03375c7f0 | ||
|
|
453cfbb306 | ||
|
|
deb5ee8374 | ||
|
|
5d2f7dbe8a | ||
|
|
3831998add | ||
|
|
e85982e17c | ||
|
|
46c17c8470 | ||
|
|
5fd326f2e8 | ||
|
|
ad5e086eb5 | ||
|
|
ef9231f51f | ||
|
|
bf34661e42 | ||
|
|
286db4fbea | ||
|
|
4f4dbfbc6f | ||
|
|
1046daaf16 | ||
|
|
b7df9d05a7 | ||
|
|
394c3ef4f6 | ||
|
|
7a071c33cd | ||
|
|
71733de05f | ||
|
|
d5e48a3e36 | ||
|
|
e36755a21d | ||
|
|
9620164884 | ||
|
|
711844cc83 | ||
|
|
6eb82b26a7 | ||
|
|
b12cf6219a | ||
|
|
7e46305277 | ||
|
|
14aa127f31 | ||
|
|
6e7e89c2d0 | ||
|
|
819c37bdec | ||
|
|
c80690d66e | ||
|
|
084f879537 | ||
|
|
7afd6bbbe9 | ||
|
|
46641e8a86 | ||
|
|
010b386ce1 | ||
|
|
a73b320715 | ||
|
|
5f822a6230 | ||
|
|
024fac90cd | ||
|
|
133bc36cca | ||
|
|
51a7ff2820 | ||
|
|
bc96f8df49 | ||
|
|
979f197476 | ||
|
|
1677b69363 | ||
|
|
d627f694df | ||
|
|
4c52cba21f | ||
|
|
597fdd1695 | ||
|
|
9031704b9b | ||
|
|
510a5c5007 | ||
|
|
469e76c50b | ||
|
|
f6c6c58c17 | ||
|
|
e546866f78 | ||
|
|
081566b5d9 | ||
|
|
ec078666ae | ||
|
|
1ce394c7a1 | ||
|
|
2fc3930b43 | ||
|
|
9fa9da5e74 | ||
|
|
d6ad0b5e94 | ||
|
|
15dc50804f | ||
|
|
63c3e35064 | ||
|
|
297930c4d1 | ||
|
|
098c344047 | ||
|
|
4f74677d14 | ||
|
|
0869b19408 | ||
|
|
9b02611700 | ||
|
|
5daa71e6c1 | ||
|
|
aa5f2f66f8 | ||
|
|
92d04f81c3 | ||
|
|
5d53db4211 | ||
|
|
eb3bbdd3c5 | ||
|
|
1ac816ca37 | ||
|
|
33cf18cde5 | ||
|
|
0c0d53dd78 | ||
|
|
1959ee7560 | ||
|
|
ee13401b5a | ||
|
|
78b1cf4af5 | ||
|
|
0599260e31 | ||
|
|
08dd921088 | ||
|
|
e66e938d7d | ||
|
|
b5b7d2a9d2 | ||
|
|
46298d555b | ||
|
|
8fb34b5fde | ||
|
|
28f8986837 | ||
|
|
e968173f61 | ||
|
|
f703a8868b | ||
|
|
0a29e5f156 | ||
|
|
c5c28ee042 | ||
|
|
44ca43399d | ||
|
|
13a47d822d | ||
|
|
5621cd90bb | ||
|
|
9f7055ffd2 |
5
.gitignore
vendored
5
.gitignore
vendored
@@ -1,9 +1,8 @@
|
||||
# config
|
||||
config.ini
|
||||
|
||||
# Pickle files, specifically, bbsdb.pkl
|
||||
bbsdb.pkl
|
||||
bbsdm.pkl
|
||||
# Pickle files
|
||||
*.pkl
|
||||
|
||||
# virtualenv
|
||||
venv/
|
||||
|
||||
24
Dockerfile
Normal file
24
Dockerfile
Normal file
@@ -0,0 +1,24 @@
|
||||
FROM python:3.10-slim
|
||||
ENV PYTHONUNBUFFERED=1
|
||||
|
||||
RUN apt-get update && apt-get install -y gettext tzdata locales && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Set the locale default to en_US.UTF-8
|
||||
RUN sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && \
|
||||
dpkg-reconfigure --frontend=noninteractive locales && \
|
||||
update-locale LANG=en_US.UTF-8
|
||||
ENV LANG en_US.UTF-8
|
||||
ENV TZ="America/Los_Angeles"
|
||||
|
||||
WORKDIR /app
|
||||
COPY . /app
|
||||
COPY requirements.txt .
|
||||
|
||||
RUN pip install -r requirements.txt
|
||||
COPY . .
|
||||
|
||||
COPY config.ini /app/config.ini
|
||||
COPY entrypoint.sh /app/entrypoint.sh
|
||||
|
||||
RUN chmod +x /app/entrypoint.sh
|
||||
ENTRYPOINT ["/app/entrypoint.sh"]
|
||||
81
README.md
81
README.md
@@ -8,20 +8,20 @@ The feature-rich bot requires the internet for full functionality. These respond
|
||||
|
||||
Along with network testing, this bot has a lot of other fun features, like simple mail messaging you can leave for another device, and when that device is seen, it can send the mail as a DM. Or a scheduler to send weather or a reminder weekly for the VHF net.
|
||||
|
||||
The bot is also capable of using dual radio/nodes, so you can monitor two networks at the same time and send messages to nodes using the same `bbspost @nodeNumber #message` or `bbspost @nodeShportName #message` function. There is a small message board to fit in the constraints of Meshtastic for posting bulletin messages with `bbspost $subject #message`.
|
||||
The bot is also capable of using dual radio/nodes, so you can monitor two networks at the same time and send messages to nodes using the same `bbspost @nodeNumber #message` or `bbspost @nodeShortName #message` function. There is a small message board to fit in the constraints of Meshtastic for posting bulletin messages with `bbspost $subject #message`.
|
||||
|
||||
Look up data using wiki results or interact with [Ollama](https://ollama.com) LLM AI see the [OllamaDocs](https://github.com/ollama/ollama/tree/main/docs) If Ollama is enabled you can DM the bot directly.
|
||||
Look up data using wiki results, or interact with [Ollama](https://ollama.com) LLM AI see the [OllamaDocs](https://github.com/ollama/ollama/tree/main/docs) If Ollama is enabled you can DM the bot directly. The default model for mesh-bot which is currently `gemma2:2b`
|
||||
|
||||
The bot will report on anyone who is getting close to the configured lat/long, if in a remote location.
|
||||
The bot will report on anyone who is getting close to the configured lat/long, if in a remote location. For example having the bot in your camp site alerts when members arive back at camp.
|
||||
|
||||
Store and forward-like message re-play with `messages`, and there is a repeater module for dual radio bots to cross post messages. Messages are also logged locally to disk.
|
||||
|
||||
There is a small collection of games to play like DopeWars, Lemonade Stand, and BlackJack or VideoPoker to name a few, issuing `games` displays help
|
||||
|
||||
The bot can also be used to monitor a radio frequency and let you know when high SNR RF activity is seen. Using Hamlib(rigctld) to watch the S meter on a connected radio. You can send alerts to channels when a frequency is detected for 20 seconds within the thresholds set in config.ini
|
||||
|
||||
Any messages that are over 160 characters are chunked into 160 message bytes to help traverse hops, in testing, this keeps delivery success higher.
|
||||
|
||||
[Donate$](https://www.paypal.com/donate?token=ZpiU7zDh-AQDyK76nWmWPQLf04iOm-Iyr3f85lpubt37NWGRYtfe11UyC0LmY1wdcC20UubWo4Kec-_G) via PayPal if you like the project!
|
||||
|
||||
## Full list of commands for the bot
|
||||
|
||||
- Various solar details for radio propagation (spaceWeather module)
|
||||
@@ -32,20 +32,30 @@ Any messages that are over 160 characters are chunked into 160 message bytes to
|
||||
- `bbshelp` returns the following
|
||||
- `bbslist` list the messages by ID and subject
|
||||
- `bbsread` read a message example use: `bbsread #1`
|
||||
- `bbspost` post a message to public board or send a DM example use: `bbspost $subject #message, or bbspost @nodeNumber #message or bbspost @nodeShportName #message`
|
||||
- `bbspost` post a message to public board or send a DM example use: `bbspost $subject #message, or bbspost @nodeNumber #message or bbspost @nodeShortName #message`
|
||||
- `bbsdelete` delete a message example use: `bbsdelete #4`
|
||||
- `bbsinfo` Stats on BBS delivery and messages (sysop)
|
||||
- Other functions
|
||||
- `whereami` returns the address of location of sender if known
|
||||
- `whoami` returns some details of the node asking
|
||||
- `tide` returns the local tides, NOAA data source
|
||||
- `wx` and `wxc` returns local weather forecast, (wxc is metric value), NOAA or Open Meteo for weather forecasting.
|
||||
- `wxa` and `wxalert` return NOAA alerts. Short title or expanded details
|
||||
- `joke` tells a joke
|
||||
- `wiki: ` search wikipedia, return the first few sentances of first result if a match `wiki: lora radio`
|
||||
- `ask: ` ask Ollama LLM AI for a response `ask: what temp do I cook chicken`
|
||||
- `wiki: ` will search wikipedia, return the first few sentances of first result if a match `wiki: lora radio`
|
||||
- `askai` and `ask:` will ask Ollama LLM AI for a response `askai what temp do I cook chicken`
|
||||
- `messages` Replay the last messages heard, like Store and Forward
|
||||
- `motd` or to set the message `motd $New Message Of the day`
|
||||
- `lheard` returns the last 5 heard nodes with SNR, can also use `sitrep`
|
||||
- `history` returns the last commands ran by user(s)
|
||||
- `cmd` returns the list of commands (the help message)
|
||||
- Games - via DM
|
||||
- `lemonstand` plays the classic Lemonade Stand Finance
|
||||
- `dopewars` plays the classic drug trader
|
||||
- `blackjack` BlackJack, Casino 21
|
||||
- `videopoker` Video Poker, basic 5 card hold
|
||||
- `mastermind` Classic code-breaking game
|
||||
- `golfsim` Golf Simulator, 9 Hole
|
||||
|
||||
## pong_bot.sh
|
||||
Stripped-down bot, mostly around for archive purposes. The mesh-bot enhanced modules can be disabled by config to disable features.
|
||||
@@ -63,6 +73,12 @@ Optionally:
|
||||
- `install.sh` will automate optional venv and requirements installation.
|
||||
- `launch.sh` will activate and launch the app in the venv if built.
|
||||
|
||||
For Docker:
|
||||
Check you have serial port properly shared and the GPU if using LLM with [NVidia](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/docker-specialized.html)
|
||||
- `git clone https://github.com/spudgunman/meshing-around`
|
||||
- `cd meshing-around && docker build -t meshing-around`
|
||||
- `docker run meshing-around`
|
||||
|
||||
### Configurations
|
||||
Copy the [config.template](config.template) to `config.ini` and set the appropriate interface for your method (serial/ble/tcp). While BLE and TCP will work, they are not as reliable as serial connections. There is a watchdog to reconnect tcp if possible. To get BLE mac `meshtastic --ble-scan` **NOTE** I have only tested with a single BLE device and the code is written to only have one interface be a BLE port
|
||||
|
||||
@@ -109,6 +125,13 @@ enabled = False
|
||||
DadJokes = False
|
||||
StoreForward = False
|
||||
```
|
||||
History command is like a linix terminal, shows the last commands the user ran and the `lheard` reflects last users on the bot.
|
||||
```
|
||||
# history command
|
||||
enableCmdHistory = True
|
||||
# command history ignore list ex: 2813308004,4258675309
|
||||
lheardCmdIgnoreNodes =
|
||||
```
|
||||
Sentry Bot detects anyone coming close to the bot-node
|
||||
```
|
||||
# detect anyone close to the bot
|
||||
@@ -148,19 +171,28 @@ signalHoldTime = 10
|
||||
signalCooldown = 5
|
||||
signalCycleLimit = 5
|
||||
```
|
||||
Ollama Settings, for Ollama to work the command line `ollama run 'model'` needs to work properly. Check that you have enough RAM and your GPU are working as expected. The default model for this project, is set to `gemma2:2b` (run `ollama pull gemma2:2b` on command line, to download and setup)
|
||||
- From the command terminal of your system with mesh-bot, download the default model for mesh-bot which is currently `ollama pull gemma2:2b`
|
||||
|
||||
Enable History, set via code readme Ollama Config in [Settings](https://github.com/SpudGunMan/meshing-around?tab=readme-ov-file#configurations) and [llm.py](https://github.com/SpudGunMan/meshing-around/blob/eb3bbdd3c5e0f16fe3c465bea30c781bd132d2d3/modules/llm.py#L12)
|
||||
|
||||
Tested models are `llama3.1, gemma2 (and variants), phi3.5, mistrial` other models may not handle the template as well.
|
||||
|
||||
Logging messages to disk or Syslog to disk uses the python native logging function. Take a look at the [/modules/log.py](/modules/log.py) you can set the file logger for syslog to INFO for example to not log DEBUG messages to file log, or modify the stdOut level.
|
||||
```
|
||||
[general]
|
||||
# logging to file of the non Bot messages
|
||||
LogMessagesToFile = True
|
||||
# Logging of system messages to file
|
||||
SyslogToFile = True
|
||||
# Enable ollama LLM see more at https://ollama.com
|
||||
ollama = True
|
||||
# Ollama model to use (defaults to gemma2:2b)
|
||||
ollamaModel = gemma2
|
||||
#ollamaModel = llama3.1
|
||||
```
|
||||
Example to log to disk only INFO and higher (ignore DEBUG)
|
||||
|
||||
also see llm.py for changing the defaults of
|
||||
```
|
||||
*log.py
|
||||
file_handler.setLevel(logging.INFO) # DEBUG used by default for system logs to disk example here shows INFO
|
||||
# LLM System Variables
|
||||
llmEnableHistory = False # enable history for the LLM model to use in responses adds to compute time
|
||||
llmContext_fromGoogle = True # enable context from google search results adds to compute time but really helps with responses accuracy
|
||||
googleSearchResults = 3 # number of google search results to include in the context more results = more compute time
|
||||
llm_history_limit = 6 # limit the history to 3 messages (come in pairs) more results = more compute time
|
||||
```
|
||||
|
||||
The Scheduler is enabled in the [settings.py](modules/settings.py) by setting `scheduler_enabled = True` the actions and settings are via code only at this time. see [mesh_bot.py](mesh_bot.py) around line [425](https://github.com/SpudGunMan/meshing-around/blob/22983133ee4db3df34f66699f565e506de296197/mesh_bot.py#L425-L435) to edit schedule its most flexible to edit raw code right now. See https://schedule.readthedocs.io/en/stable/ for more.
|
||||
@@ -173,7 +205,7 @@ The Scheduler is enabled in the [settings.py](modules/settings.py) by setting `s
|
||||
#schedule.every().wednesday.at("19:00").do(lambda: send_message("Net Starting Now", 2, 0, 1))
|
||||
```
|
||||
# requirements
|
||||
Python 3.4 and likely higher is needed, developed on latest release.
|
||||
Python 3.10 minimally is needed, developed on latest release.
|
||||
|
||||
The following can also be installed with `pip install -r requirements.txt` or using the install.sh script for venv and automation
|
||||
|
||||
@@ -205,6 +237,7 @@ The following is for the Ollama LLM
|
||||
pip install langchain
|
||||
pip install langchain-ollama
|
||||
pip install ollama
|
||||
pip install googlesearch-python
|
||||
```
|
||||
|
||||
To enable emoji in the Debian console, install the fonts `sudo apt-get install fonts-noto-color-emoji`
|
||||
@@ -215,6 +248,14 @@ I used ideas and snippets from other responder bots and want to call them out!
|
||||
- https://github.com/pdxlocations/meshtastic-Python-Examples
|
||||
- https://github.com/geoffwhittington/meshtastic-matrix-relay
|
||||
|
||||
GitHub user PiDiBi looking at test functions and other suggestions like wxc, CPU use, and alerting ideas
|
||||
Discord and Mesh user Cisien, and github Hailo1999, for testing and ideas!
|
||||
Games Ported from..
|
||||
- https://github.com/tigerpointe/Lemonade-Stand/
|
||||
- https://github.com/Reconfirefly/drugwars
|
||||
- https://github.com/Himan10/BlackJack
|
||||
- https://github.com/devtronvarma/Video-Poker-Terminal-Game
|
||||
- https://github.com/pwdkramer/pythonMastermind/
|
||||
- https://github.com/danfriedman30/pythongame (Golf)
|
||||
|
||||
GitHub user Nestpebble, for new ideas and enhancments, mrpatrick1991 For Docker configs, PiDiBi looking at test functions and other suggestions like wxc, CPU use, and alerting ideas
|
||||
Discord and Mesh user Cisien, bitflip, and github Hailo1999, for testing and feature ideas! Lots of individuals on the Meshtastic discord who have tossed out ideas and tested code!
|
||||
|
||||
|
||||
@@ -25,11 +25,15 @@ port = /dev/ttyUSB0
|
||||
[general]
|
||||
# if False will respond on all channels but the default channel
|
||||
respond_by_dm_only = True
|
||||
# defaultChannel is the meshtastic default public channel
|
||||
# defaultChannel is the meshtastic default public channel, e.g. LongFast
|
||||
defaultChannel = 0
|
||||
# ignoreDefaultChannel, the bot will ignore the default channel set above
|
||||
ignoreDefaultChannel = False
|
||||
# motd is reset to this value on boot
|
||||
motd = Thanks for using MeshBOT! Have a good day!
|
||||
welcome_message = MeshBot, here for you like a friend who is not. Try sending: ping @foo or, cmd
|
||||
# whoami
|
||||
whoami = True
|
||||
# enable or disable the Joke module
|
||||
DadJokes = True
|
||||
# enable or disable the Solar module
|
||||
@@ -38,9 +42,15 @@ spaceWeather = True
|
||||
wikipedia = True
|
||||
# Enable ollama LLM see more at https://ollama.com
|
||||
ollama = False
|
||||
# Ollama model to use (defaults to gemma2:2b)
|
||||
# ollamaModel = llama3.1
|
||||
# StoreForward Enabled and Limits
|
||||
StoreForward = True
|
||||
StoreLimit = 3
|
||||
# history command
|
||||
enableCmdHistory = True
|
||||
# command history ignore list ex: 2813308004,4258675309
|
||||
lheardCmdIgnoreNodes =
|
||||
# 24 hour clock
|
||||
zuluTime = False
|
||||
# wait time for URL requests
|
||||
@@ -50,6 +60,15 @@ LogMessagesToFile = False
|
||||
# Logging of system messages to file
|
||||
SyslogToFile = False
|
||||
|
||||
[games]
|
||||
# enable or disable the games module(s)
|
||||
dopeWars = True
|
||||
lemonade = True
|
||||
blackjack = True
|
||||
videopoker = True
|
||||
mastermind = True
|
||||
golfsim = True
|
||||
|
||||
[sentry]
|
||||
# detect anyone close to the bot
|
||||
SentryEnabled = True
|
||||
@@ -96,7 +115,7 @@ repeater_channels =
|
||||
# using Hamlib rig control will monitor and alert on channel use
|
||||
enabled = False
|
||||
rigControlServerAddress = localhost:4532
|
||||
# broadcast to all nodes on the channel can alsp be = 2,3
|
||||
# broadcast to all nodes on the channel can also be = 2,3
|
||||
sigWatchBroadcastCh = 2
|
||||
# minimum SNR as reported by radio via hamlib
|
||||
signalDetectionThreshold = -10
|
||||
@@ -105,3 +124,11 @@ signalHoldTime = 10
|
||||
# the following are combined to reset the monitor
|
||||
signalCooldown = 5
|
||||
signalCycleLimit = 5
|
||||
|
||||
[messagingSettings]
|
||||
# delay in seconds for response to avoid message collision
|
||||
responseDelay = 0.7
|
||||
# delay in seconds for splits in messages to avoid message collision
|
||||
splitDelay = 0.0
|
||||
# message chunk size for sending at high success rate
|
||||
MESSAGE_CHUNK_SIZE = 160
|
||||
|
||||
6
entrypoint.sh
Normal file
6
entrypoint.sh
Normal file
@@ -0,0 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Substitute environment variables in the config file
|
||||
envsubst < /app/config.ini > /app/config.tmp && mv /app/config.tmp /app/config.ini
|
||||
|
||||
exec python /app/mesh_bot.py
|
||||
@@ -1,29 +0,0 @@
|
||||
# Load the bbs messages from the database file to screen for admin functions
|
||||
import pickle # pip install pickle
|
||||
|
||||
|
||||
# load the bbs messages from the database file
|
||||
try:
|
||||
with open('../bbsdb.pkl', 'rb') as f:
|
||||
bbs_messages = pickle.load(f)
|
||||
except:
|
||||
try:
|
||||
with open('bbsdb.pkl', 'rb') as f:
|
||||
bbs_messages = pickle.load(f)
|
||||
except:
|
||||
print ("\nSystem: bbsdb.pkl not found")
|
||||
|
||||
try:
|
||||
with open('../bbsdm.pkl', 'rb') as f:
|
||||
bbs_dm = pickle.load(f)
|
||||
except:
|
||||
try:
|
||||
with open('bbsdm.pkl', 'rb') as f:
|
||||
bbs_dm = pickle.load(f)
|
||||
except:
|
||||
print ("\nSystem: bbsdm.pkl not found")
|
||||
|
||||
print ("\nSystem: bbs_messages")
|
||||
print (bbs_messages)
|
||||
print ("\nSystem: bbs_dm")
|
||||
print (bbs_dm)
|
||||
100
etc/db_admin.py
Normal file
100
etc/db_admin.py
Normal file
@@ -0,0 +1,100 @@
|
||||
# Load the bbs messages from the database file to screen for admin functions
|
||||
import pickle # pip install pickle
|
||||
|
||||
|
||||
# load the bbs messages from the database file
|
||||
try:
|
||||
with open('../bbsdb.pkl', 'rb') as f:
|
||||
bbs_messages = pickle.load(f)
|
||||
except Exception as e:
|
||||
try:
|
||||
with open('bbsdb.pkl', 'rb') as f:
|
||||
bbs_messages = pickle.load(f)
|
||||
except Exception as e:
|
||||
bbs_messages = "System: bbsdb.pkl not found"
|
||||
|
||||
try:
|
||||
with open('../bbsdm.pkl', 'rb') as f:
|
||||
bbs_dm = pickle.load(f)
|
||||
except Exception as e:
|
||||
try:
|
||||
with open('bbsdm.pkl', 'rb') as f:
|
||||
bbs_dm = pickle.load(f)
|
||||
except Exception as e:
|
||||
bbs_dm = "System: bbsdm.pkl not found"
|
||||
|
||||
# Game HS tables
|
||||
try:
|
||||
with open('../lemonade_hs.pkl', 'rb') as f:
|
||||
lemon_score = pickle.load(f)
|
||||
except Exception as e:
|
||||
try:
|
||||
with open('lemonade_hs.pkl', 'rb') as f:
|
||||
lemon_score = pickle.load(f)
|
||||
except Exception as e:
|
||||
lemon_score = "System: lemonade_hs.pkl not found"
|
||||
|
||||
try:
|
||||
with open('../dopewar_hs.pkl', 'rb') as f:
|
||||
dopewar_score = pickle.load(f)
|
||||
except Exception as e:
|
||||
try:
|
||||
with open('dopewar_hs.pkl', 'rb') as f:
|
||||
dopewar_score = pickle.load(f)
|
||||
except Exception as e:
|
||||
dopewar_score = "System: dopewar_hs.pkl not found"
|
||||
|
||||
try:
|
||||
with open('../blackjack_hs.pkl', 'rb') as f:
|
||||
blackjack_score = pickle.load(f)
|
||||
except Exception as e:
|
||||
try:
|
||||
with open('blackjack_hs.pkl', 'rb') as f:
|
||||
blackjack_score = pickle.load(f)
|
||||
except Exception as e:
|
||||
blackjack_score = "System: blackjack_hs.pkl not found"
|
||||
|
||||
try:
|
||||
with open('../videopoker_hs.pkl', 'rb') as f:
|
||||
videopoker_score = pickle.load(f)
|
||||
except Exception as e:
|
||||
try:
|
||||
with open('videopoker_hs.pkl', 'rb') as f:
|
||||
videopoker_score = pickle.load(f)
|
||||
except Exception as e:
|
||||
videopoker_score = "System: videopoker_hs.pkl not found"
|
||||
|
||||
try:
|
||||
with open('../mmind_hs.pkl', 'rb') as f:
|
||||
mmind_score = pickle.load(f)
|
||||
except Exception as e:
|
||||
try:
|
||||
with open('mmind_hs.pkl', 'rb') as f:
|
||||
mmind_score = pickle.load(f)
|
||||
except Exception as e:
|
||||
mmind_score = "System: mmind_hs.pkl not found"
|
||||
|
||||
try:
|
||||
with open('../golfsim_hs.pkl', 'rb') as f:
|
||||
golfsim_score = pickle.load(f)
|
||||
except Exception as e:
|
||||
try:
|
||||
with open('golfsim_hs.pkl', 'rb') as f:
|
||||
golfsim_score = pickle.load(f)
|
||||
except Exception as e:
|
||||
golfsim_score = "System: golfsim_hs.pkl not found"
|
||||
|
||||
|
||||
print ("\n Meshing-Around Database Admin Tool\n")
|
||||
print ("System: bbs_messages")
|
||||
print (bbs_messages)
|
||||
print ("\nSystem: bbs_dm")
|
||||
print (bbs_dm)
|
||||
print (f"\n\nGame HS tables\n")
|
||||
print (f"lemon:{lemon_score}")
|
||||
print (f"dopewar:{dopewar_score}")
|
||||
print (f"blackjack:{blackjack_score}")
|
||||
print (f"videopoker:{videopoker_score}")
|
||||
print (f"mmind:{mmind_score}")
|
||||
print (f"golfsim:{golfsim_score}")
|
||||
print ("\n")
|
||||
@@ -7,8 +7,12 @@ Description=MESH-BOT
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=pi
|
||||
Group=pi
|
||||
WorkingDirectory=/dir/
|
||||
ExecStart=/usr/bin/bash /dir/launch.sh mesh
|
||||
ExecStart=python3 mesh_bot.py
|
||||
ExecStop=pkill -f mesh_bot.py
|
||||
|
||||
# Disable Python's buffering of STDOUT and STDERR, so that output from the
|
||||
# service shows up immediately in systemd's logs
|
||||
|
||||
@@ -7,8 +7,12 @@ Description=PONG-BOT
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=pi
|
||||
Group=pi
|
||||
WorkingDirectory=/dir/
|
||||
ExecStart=/usr/bin/bash /dir/launch.sh pong
|
||||
ExecStart=python3 pong_bot.py
|
||||
ExecStop=pkill -f pong_bot.py
|
||||
|
||||
# Disable Python's buffering of STDOUT and STDERR, so that output from the
|
||||
# service shows up immediately in systemd's logs
|
||||
|
||||
64
etc/simulator.py
Normal file
64
etc/simulator.py
Normal file
@@ -0,0 +1,64 @@
|
||||
#!/usr/bin/env python3
|
||||
# # Simulate meshing-around de K7MHI 2024
|
||||
from modules.log import * # err? Move .py out of etc/ and place it in the root of the project
|
||||
import time
|
||||
import random
|
||||
|
||||
# Initialize the tool
|
||||
projectName = "example_handler" # name of _handler function to match the function name under test
|
||||
randomNode = False # Set to True to use random node IDs
|
||||
|
||||
# bot.py Simulated functions
|
||||
def get_NodeID():
|
||||
nodeList = [4258675309, 1212121212, 1234567890, 9876543210]
|
||||
if randomNode:
|
||||
nodeID = random.choice(nodeList) # get a random node ID
|
||||
else:
|
||||
nodeID = nodeList[0]
|
||||
return nodeID
|
||||
def get_name_from_number(nodeID, length='short', interface=1):
|
||||
# return random name for nodeID
|
||||
names = ["Max","Molly","Jake","Kelly"]
|
||||
return names[nodeID % len(names)]
|
||||
# # end Initialization of the tool
|
||||
|
||||
# # Function to handle, or the project in test
|
||||
|
||||
|
||||
def example_handler(nodeID, message):
|
||||
readableTime = time.ctime(time.time())
|
||||
msg = "Hello World! "
|
||||
msg += f" You are Node ID: {nodeID} "
|
||||
msg += f" Its: {readableTime} "
|
||||
msg += f" You just sent: {message}"
|
||||
return msg
|
||||
|
||||
|
||||
# # end of function test code
|
||||
|
||||
# # Simulate the meshing-around mesh-bot for prototyping new projects
|
||||
if __name__ == '__main__': # represents the bot's main loop
|
||||
packet = ""
|
||||
nodeInt = 1 # represents the device/node number
|
||||
logger.info(f"System: Meshing-Around Simulator Starting for {projectName}")
|
||||
nodeID = get_NodeID() # assign a nodeID
|
||||
projectResponse = globals()[projectName](0, 0, " ") # Call the project handler under test
|
||||
while True: # represents the onReceive() loop in the bot.py
|
||||
projectResponse = ""
|
||||
responseLength = 0
|
||||
if randomNode:
|
||||
nodeID = get_NodeID() # assign a random nodeID
|
||||
packet = input(f"CLIENT {nodeID} INPUT: " ) # Emulate the client input
|
||||
if packet != "":
|
||||
#try:
|
||||
projectResponse = globals()[projectName](nodeID, deviceID=nodeInt, message=packet) # Call the project handler under test
|
||||
# except Exception as e:
|
||||
# logger.error(f"System: Handler: {e}")
|
||||
# projectResponse = "Error in handler"
|
||||
if projectResponse:
|
||||
responseLength = len(projectResponse) # Evaluate the response length
|
||||
logger.info(f"Device:{nodeInt} " + CustomFormatter.red + f"Sending {responseLength} long DM: " +\
|
||||
CustomFormatter.white + projectResponse + CustomFormatter.purple + " To: " + CustomFormatter.white + str(nodeID))
|
||||
time.sleep(0.5)
|
||||
nodeID = get_NodeID() # assign a nodeID
|
||||
# # End of launcher
|
||||
97
install.sh
97
install.sh
@@ -2,10 +2,29 @@
|
||||
|
||||
# install.sh
|
||||
cd "$(dirname "$0")"
|
||||
program_path=$(pwd)
|
||||
cp etc/pong_bot.tmp etc/pong_bot.service
|
||||
cp etc/mesh_bot.tmp etc/mesh_bot.service
|
||||
|
||||
printf "\nMeshing Around Installer\n"
|
||||
printf "\nThis script will install the Meshing Around bot and its dependencies works best in debian/ubuntu\n"
|
||||
printf "\nChecking for dependencies\n"
|
||||
|
||||
|
||||
# add user to groups for serial access
|
||||
printf "\nAdding user to dialout and tty groups for serial access\n"
|
||||
sudo usermod -a -G dialout $USER
|
||||
sudo usermod -a -G tty $USER
|
||||
sudo usermod -a -G bluetooth $USER
|
||||
|
||||
# check for pip
|
||||
if ! command -v pip &> /dev/null
|
||||
then
|
||||
printf "pip not found, please install pip with your OS\n"
|
||||
sudo apt-get install python3-pip
|
||||
else
|
||||
printf "python pip found\n"
|
||||
fi
|
||||
|
||||
# generate config file, check if it exists
|
||||
if [ -f config.ini ]; then
|
||||
@@ -20,21 +39,12 @@ printf "\nConfig file generated\n"
|
||||
# set virtual environment and install dependencies
|
||||
printf "\nMeshing Around Installer\n"
|
||||
|
||||
#check if python3 has venv module
|
||||
if ! python3 -m venv --help &> /dev/null
|
||||
then
|
||||
printf "Python3 venv module not found, please install python3-venv with your OS\n"
|
||||
else
|
||||
printf "Python3 venv module found\n"
|
||||
fi
|
||||
|
||||
echo "Do you want to install the bot in a virtual environment? (y/n)"
|
||||
read venv
|
||||
|
||||
if [ $venv == "y" ]; then
|
||||
# set virtual environment
|
||||
if ! python3 -m venv --help &> /dev/null
|
||||
then
|
||||
if ! python3 -m venv --help &> /dev/null; then
|
||||
printf "Python3 venv module not found, please install python3-venv with your OS\n"
|
||||
exit 1
|
||||
else
|
||||
@@ -42,13 +52,28 @@ if [ $venv == "y" ]; then
|
||||
python3 -m venv venv
|
||||
source venv/bin/activate
|
||||
|
||||
# install dependencies
|
||||
pip install -U -r requirements.txt
|
||||
#check if python3 has venv module
|
||||
if [ -f venv/bin/activate ]; then
|
||||
printf "\nFpund virtual environment for python\n"
|
||||
else
|
||||
sudo apt-get install python3-venv
|
||||
printf "\nPython3 venv module not found, please install python3-venv with your OS if not already done. re-run the script\n"
|
||||
exxt 1
|
||||
fi
|
||||
|
||||
# config service files for virtual environment
|
||||
replace="s|python3 mesh_bot.py|/usr/bin/bash launch.sh mesh|g"
|
||||
sed -i "$replace" etc/mesh_bot.service
|
||||
replace="s|python3 pong_bot.py|/usr/bin/bash launch.sh pong|g"
|
||||
sed -i "$replace" etc/pong_bot.service
|
||||
|
||||
# install dependencies
|
||||
pip install -U -r requirements.txt
|
||||
fi
|
||||
else
|
||||
printf "\nSkipping virtual environment...\n"
|
||||
# install dependencies
|
||||
printf "Are you on Raspberry Pi?\nshould we add --break-system-packages to the pip install command? (y/n)"
|
||||
printf "Are you on Raspberry Pi(debian/ubuntu)?\nshould we add --break-system-packages to the pip install command? (y/n)"
|
||||
read rpi
|
||||
if [ $rpi == "y" ]; then
|
||||
pip install -U -r requirements.txt --break-system-packages
|
||||
@@ -61,33 +86,39 @@ printf "\n\n"
|
||||
echo "Which bot do you want to install as a service? Pong Mesh or None? (pong/mesh/n)"
|
||||
read bot
|
||||
|
||||
#set the correct path in the service file
|
||||
program_path=$(pwd)
|
||||
cp etc/pong_bot.tmp etc/pong_bot.service
|
||||
cp etc/mesh_bot.tmp etc/mesh_bot.service
|
||||
# set the correct path in the service file
|
||||
replace="s|/dir/|$program_path/|g"
|
||||
sed -i $replace etc/pong_bot.service
|
||||
sed -i $replace etc/mesh_bot.service
|
||||
# set the correct user in the service file?
|
||||
whoami=$(whoami)
|
||||
replace="s|User=pi|User=$whoami|g"
|
||||
sed -i $replace etc/pong_bot.service
|
||||
sed -i $replace etc/mesh_bot.service
|
||||
replace="s|Group=pi|Group=$whoami|g"
|
||||
sed -i $replace etc/pong_bot.service
|
||||
sed -i $replace etc/mesh_bot.service
|
||||
sudo systemctl daemon-reload
|
||||
printf "\n service files updated\n"
|
||||
|
||||
# ask if emoji font should be installed for linux
|
||||
echo "Do you want to install the emoji font for debian linux? (y/n)"
|
||||
echo "Do you want to install the emoji font for debian/ubuntu linux? (y/n)"
|
||||
read emoji
|
||||
if [ $emoji == "y" ]; then
|
||||
sudo apt-get install fonts-noto-color-emoji
|
||||
sudo apt-get install -y fonts-noto-color-emoji
|
||||
echo "Emoji font installed!, reboot to load the font"
|
||||
fi
|
||||
|
||||
if [ $bot == "pong" ]; then
|
||||
# install service for pong bot
|
||||
sudo cp etc/pong_bot.service /etc/systemd/system/
|
||||
exit 0
|
||||
sudo systemctl enable pong_bot.service
|
||||
fi
|
||||
|
||||
if [ $bot == "mesh" ]; then
|
||||
# install service for mesh bot
|
||||
sudo cp etc/mesh_bot.service /etc/systemd/system/
|
||||
exit 0
|
||||
sudo systemctl enable mesh_bot.service
|
||||
fi
|
||||
|
||||
if [ $bot == "n" ]; then
|
||||
@@ -97,6 +128,28 @@ if [ $bot == "n" ]; then
|
||||
fi
|
||||
fi
|
||||
|
||||
printf "\nOptionally if you want to install the LLM Ollama compnents we will execute the following commands\n"
|
||||
printf "\ncurl -fsSL https://ollama.com/install.sh | sh\n"
|
||||
|
||||
# ask if the user wants to install the LLM Ollama components
|
||||
echo "Do you want to install the LLM Ollama components? (y/n)"
|
||||
read ollama
|
||||
if [ $ollama == "y" ]; then
|
||||
curl -fsSL https://ollama.com/install.sh | sh
|
||||
|
||||
# ask if want to install gemma2:2b
|
||||
printf "\n Ollama install done now we can install the Gemma2:2b components, multi GB download\n"
|
||||
echo "Do you want to install the Gemma2:2b components? (y/n)"
|
||||
read gemma
|
||||
if [ $gemma == "y" ]; then
|
||||
ollama pull gemma2:2b
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "Good time to reboot? (y/n)"
|
||||
read reboot
|
||||
if [ $reboot == "y" ]; then
|
||||
sudo reboot
|
||||
fi
|
||||
|
||||
printf "\nGoodbye!"
|
||||
exit 0
|
||||
|
||||
15
logs/README.md
Normal file
15
logs/README.md
Normal file
@@ -0,0 +1,15 @@
|
||||
Logs will collect here.
|
||||
|
||||
Logging messages to disk or Syslog to disk uses the python native logging function. Take a look at the [/modules/log.py](/modules/log.py) you can set the file logger for syslog to INFO for example to not log DEBUG messages to file log, or modify the stdOut level.
|
||||
```
|
||||
[general]
|
||||
# logging to file of the non Bot messages
|
||||
LogMessagesToFile = True
|
||||
# Logging of system messages to file
|
||||
SyslogToFile = True
|
||||
```
|
||||
Example to log to disk only INFO and higher (ignore DEBUG)
|
||||
```
|
||||
*log.py
|
||||
file_handler.setLevel(logging.INFO) # DEBUG used by default for system logs to disk example here shows INFO
|
||||
```
|
||||
892
mesh_bot.py
892
mesh_bot.py
File diff suppressed because it is too large
Load Diff
@@ -4,7 +4,7 @@
|
||||
import pickle # pip install pickle
|
||||
from modules.log import *
|
||||
|
||||
trap_list_bbs = ("bbslist", "bbspost", "bbsread", "bbsdelete", "bbshelp")
|
||||
trap_list_bbs = ("bbslist", "bbspost", "bbsread", "bbsdelete", "bbshelp", "bbsinfo")
|
||||
|
||||
# global message list, later we will use a pickle on disk
|
||||
bbs_messages = []
|
||||
@@ -16,7 +16,7 @@ def load_bbsdb():
|
||||
try:
|
||||
with open('bbsdb.pkl', 'rb') as f:
|
||||
bbs_messages = pickle.load(f)
|
||||
except:
|
||||
except Exception as e:
|
||||
bbs_messages = [[1, "Welcome to meshBBS", "Welcome to the BBS, please post a message!",0]]
|
||||
logger.debug("System: Creating new bbsdb.pkl")
|
||||
with open('bbsdb.pkl', 'wb') as f:
|
||||
@@ -130,6 +130,11 @@ def bbs_post_dm(toNode, message, fromNode):
|
||||
save_bbsdm()
|
||||
return "BBS DM Posted for node " + str(toNode)
|
||||
|
||||
def get_bbs_stats():
|
||||
global bbs_messages, bbs_dm
|
||||
# Return some stats on the bbs pending messages and total posted messages
|
||||
return f"📡BBSdb has {len(bbs_messages)} messages. Direct ✉️ Messages waiting: {(len(bbs_dm) - 1)}"
|
||||
|
||||
def bbs_check_dm(toNode):
|
||||
global bbs_dm
|
||||
# Check for any messages for toNode
|
||||
|
||||
473
modules/blackjack.py
Normal file
473
modules/blackjack.py
Normal file
@@ -0,0 +1,473 @@
|
||||
# Port of https://github.com/Himan10/BlackJack
|
||||
# Adapted for Meshtastic mesh-bot by K7MHI Kelly Keeton 2024
|
||||
|
||||
from random import choices, shuffle
|
||||
from modules.log import *
|
||||
import time
|
||||
import pickle
|
||||
|
||||
jack_starting_cash = 100 # Replace 100 with your desired starting cash value
|
||||
jackTracker= [{'nodeID': 0, 'cmd': 'new', 'time': time.time(), 'cash': jack_starting_cash,\
|
||||
'bet': 0, 'gameStats': {'p_win': 0, 'd_win': 0, 'draw': 0}, 'p_cards':[], 'd_cards':[], 'p_hand':[], 'd_hand':[], 'next_card':[]}]
|
||||
|
||||
SUITS = ("♥️", "♦️", "♠️", "♣️")
|
||||
RANKS = (
|
||||
"2",
|
||||
"3",
|
||||
"4",
|
||||
"5",
|
||||
"6",
|
||||
"7",
|
||||
"8",
|
||||
"9",
|
||||
"10",
|
||||
"J",
|
||||
"Q",
|
||||
"K",
|
||||
"A",
|
||||
)
|
||||
VALUES = {
|
||||
"2": 2,
|
||||
"3": 3,
|
||||
"4": 4,
|
||||
"5": 5,
|
||||
"6": 6,
|
||||
"7": 7,
|
||||
"8": 8,
|
||||
"9": 9,
|
||||
"10": 10,
|
||||
"J": 10,
|
||||
"Q": 10,
|
||||
"K": 10,
|
||||
"A": 11,
|
||||
}
|
||||
|
||||
class jackCard:
|
||||
def __init__(self, suit, rank):
|
||||
self.suit = suit
|
||||
self.rank = rank
|
||||
|
||||
def __str__(self):
|
||||
return self.rank + " of " + self.suit
|
||||
|
||||
class jackDeck:
|
||||
""" Creating a Deck of cards and Deal two cards to both player and dealer. """
|
||||
|
||||
def __init__(self):
|
||||
self.deck = []
|
||||
self.player = []
|
||||
self.dealer = []
|
||||
for suit in SUITS:
|
||||
for rank in RANKS:
|
||||
self.deck.append((suit, rank))
|
||||
|
||||
def shuffle(self):
|
||||
shuffle(self.deck)
|
||||
|
||||
def deal_cards(self):
|
||||
self.player = choices(self.deck, k=2)
|
||||
self.delete_cards(self.player)
|
||||
self.dealer = choices(self.deck, k=2)
|
||||
self.delete_cards(self.dealer) # Delete Drawn Cards
|
||||
return self.player, self.dealer
|
||||
|
||||
def delete_cards(self, total_drawn):
|
||||
""" Delete Drawn cards from the Decks """
|
||||
try:
|
||||
for i in total_drawn:
|
||||
self.deck.remove(i)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
class jackHand:
|
||||
""" Adding the values of player/dealer cards and change the values of Aces acc. to situation. """
|
||||
def __init__(self):
|
||||
self.cards = []
|
||||
self.value = 0
|
||||
self.aces = 0
|
||||
|
||||
def add_cards(self, card):
|
||||
self.cards.extend(card)
|
||||
for count, ele in enumerate(card, 0):
|
||||
if ele[1] == "A":
|
||||
self.aces += 1
|
||||
self.value += VALUES[ele[1]]
|
||||
self.adjust_for_ace()
|
||||
|
||||
def adjust_for_ace(self):
|
||||
while self.aces > 0 and self.value > 21:
|
||||
self.value -= 10
|
||||
self.aces -= 1
|
||||
|
||||
class jackChips:
|
||||
""" Player/dealer chips for making bets and Adding/Deducting amount in/from Player's total. """
|
||||
def __init__(self):
|
||||
self.total = jack_starting_cash
|
||||
self.bet = 0
|
||||
self.winnings = 0
|
||||
|
||||
def win_bet(self):
|
||||
self.total += self.bet
|
||||
self.winnings += 1
|
||||
|
||||
def loss_bet(self):
|
||||
self.total -= self.bet
|
||||
self.winnings -= 1
|
||||
|
||||
def success_rate(card, obj_h):
|
||||
""" Calculate Success rate of 'HIT' new cards """
|
||||
msg = ""
|
||||
rate = 0
|
||||
diff = 21 - obj_h.value
|
||||
if diff != 0:
|
||||
rate = (VALUES[card[0][1]] / diff) * 100
|
||||
|
||||
if rate < 100:
|
||||
msg += f"If Hit, chance {int(rate)}% failure, {100-int(rate)}% success."
|
||||
else:
|
||||
l_rate = int(rate - (rate - 99)) # Round to 99
|
||||
if card[0][1] == "A":
|
||||
l_rate -= 99
|
||||
msg += f"If Hit, chance {100-l_rate}% failure, and {l_rate}% success"
|
||||
return msg
|
||||
|
||||
def hits(obj_de):
|
||||
new_card = [obj_de.deal_cards()[0][0]]
|
||||
# obj_h.add_cards(new_card)
|
||||
return new_card
|
||||
|
||||
def display_hand(hand):
|
||||
# Display the cards in the hand nicely
|
||||
d = "" # display
|
||||
for card in hand:
|
||||
d += f"{card[1]}{card[0]}"
|
||||
if card != hand[-1]:
|
||||
d += ", "
|
||||
return d
|
||||
|
||||
def show_some(player_cards, dealer_cards, obj_h):
|
||||
msg = f"Player[{obj_h.value}] {display_hand(player_cards)} "
|
||||
msg += f"Dealer[{VALUES[dealer_cards[1][1]]}] {dealer_cards[1][1]}{dealer_cards[1][0]} "
|
||||
return msg
|
||||
|
||||
def show_all(player_cards, dealer_cards, obj_h, obj_d):
|
||||
msg = f"Player[{obj_h.value}] {display_hand(player_cards)} "
|
||||
msg += f"Dealer[{obj_d.value}] {display_hand(dealer_cards)}"
|
||||
return msg
|
||||
|
||||
def player_bust(obj_h, obj_c):
|
||||
if obj_h.value > 21:
|
||||
obj_c.loss_bet()
|
||||
return True
|
||||
return False
|
||||
|
||||
def player_wins(obj_h, obj_d, obj_c):
|
||||
if any((obj_h.value == 21, obj_h.value > obj_d.value and obj_h.value < 21)):
|
||||
obj_c.win_bet()
|
||||
return True
|
||||
return False
|
||||
|
||||
def dealer_bust(obj_d, obj_h, obj_c):
|
||||
if obj_d.value > 21:
|
||||
if obj_h.value < 21:
|
||||
obj_c.win_bet()
|
||||
return True
|
||||
return False
|
||||
|
||||
def dealer_wins(obj_h, obj_d, obj_c):
|
||||
if any((obj_d.value == 21, obj_d.value > obj_h.value and obj_d.value < 21)):
|
||||
obj_c.loss_bet()
|
||||
return True
|
||||
return False
|
||||
|
||||
def push(obj_h, obj_d):
|
||||
if obj_h.value == obj_d.value:
|
||||
return True
|
||||
return False
|
||||
|
||||
def player_surrender(obj_c):
|
||||
obj_c.loss_bet()
|
||||
return True
|
||||
|
||||
def gameStats(p_count, d_count, draw_c):
|
||||
msg = f"\n📊🏆P:{p_count},D:{d_count},T:{draw_c}"
|
||||
return msg
|
||||
|
||||
def getLastCmdJack(nodeID):
|
||||
for i in range(len(jackTracker)):
|
||||
if jackTracker[i]['nodeID'] == nodeID:
|
||||
return jackTracker[i]['cmd']
|
||||
return None
|
||||
|
||||
def setLastCmdJack(nodeID, cmd):
|
||||
for i in range(len(jackTracker)):
|
||||
if jackTracker[i]['nodeID'] == nodeID:
|
||||
jackTracker[i]['cmd'] = cmd
|
||||
return True
|
||||
return False
|
||||
|
||||
def saveHSJack(nodeID, highScore):
|
||||
# Save the game state to pickle
|
||||
highScore = {'nodeID': nodeID, 'highScore': highScore}
|
||||
try:
|
||||
with open('blackjack_hs.pkl', 'wb') as file:
|
||||
pickle.dump(highScore, file)
|
||||
except FileNotFoundError:
|
||||
logger.debug("System: BlackJack: Creating new blackjack_hs.pkl file")
|
||||
with open('blackjack_hs.pkl', 'wb') as file:
|
||||
pickle.dump(highScore, file)
|
||||
|
||||
def loadHSJack():
|
||||
try:
|
||||
with open('blackjack_hs.pkl', 'rb') as file:
|
||||
highScore = pickle.load(file)
|
||||
return highScore
|
||||
except FileNotFoundError:
|
||||
logger.debug("System: BlackJack: Creating new blackjack_hs.pkl file")
|
||||
highScore = {'nodeID': 0, 'highScore': 0}
|
||||
with open('blackjack_hs.pkl', 'wb') as file:
|
||||
pickle.dump(highScore, file)
|
||||
return 0
|
||||
|
||||
def playBlackJack(nodeID, message):
|
||||
# Initalize the Game
|
||||
msg, last_cmd = '', None
|
||||
blackJack = False
|
||||
p_win, d_win, draw = 0, 0, 0
|
||||
p_chips = jackChips()
|
||||
p_hand = jackHand()
|
||||
d_hand = jackHand()
|
||||
p_cards, d_cards = [], []
|
||||
bet_money = 0
|
||||
# Initalize the Cards
|
||||
cards_deck = jackDeck()
|
||||
cards_deck.shuffle()
|
||||
p_cards, d_cards = cards_deck.deal_cards()
|
||||
# Deal the cards to player and dealer
|
||||
p_hand.add_cards(p_cards)
|
||||
d_hand.add_cards(d_cards)
|
||||
next_card = hits(cards_deck)
|
||||
|
||||
# Check if player, use tracking
|
||||
for i in range(len(jackTracker)):
|
||||
if jackTracker[i]['nodeID'] == nodeID:
|
||||
last_cmd = jackTracker[i]['cmd']
|
||||
p_chips.total = jackTracker[i]['cash']
|
||||
p_win = jackTracker[i]['gameStats']['p_win']
|
||||
d_win = jackTracker[i]['gameStats']['d_win']
|
||||
draw = jackTracker[i]['gameStats']['draw']
|
||||
bet_money = jackTracker[i]['bet']
|
||||
if last_cmd == "playing":
|
||||
p_chips.bet = bet_money
|
||||
p_cards = jackTracker[i]['p_cards']
|
||||
d_cards = jackTracker[i]['d_cards']
|
||||
p_hand = jackTracker[i]['p_hand']
|
||||
d_hand = jackTracker[i]['d_hand']
|
||||
next_card = jackTracker[i]['next_card']
|
||||
|
||||
if last_cmd is None:
|
||||
# create new player if not in tracker
|
||||
logger.debug(f"System: BlackJack: New Player {nodeID}")
|
||||
jackTracker.append({'nodeID': nodeID, 'cmd': 'new', 'time': time.time(), 'cash': jack_starting_cash,\
|
||||
'bet': 0, 'gameStats': {'p_win': p_win, 'd_win': d_win, 'draw': draw}, 'p_cards':p_cards, 'd_cards':d_cards, 'p_hand':p_hand.cards, 'd_hand':d_hand.cards, 'next_card':next_card})
|
||||
return f"Welcome to ♠️♥️BlackJack♣️♦️ you have {p_chips.total} chips. Whats your bet?"
|
||||
|
||||
if getLastCmdJack(nodeID) == "new":
|
||||
# Place Bet
|
||||
try:
|
||||
# handle B letter
|
||||
if message.lower() == "b":
|
||||
if bet_money == 0:
|
||||
bet_money = 5
|
||||
elif message.lower() == "r":
|
||||
#resend the hand
|
||||
msg += show_some(p_cards, d_cards, p_hand)
|
||||
return msg
|
||||
else:
|
||||
try:
|
||||
bet_money = int(message)
|
||||
except ValueError:
|
||||
return "Invalid Bet, please enter a valid number."
|
||||
|
||||
if bet_money <= p_chips.total and bet_money >= 1:
|
||||
p_chips.bet = bet_money
|
||||
else:
|
||||
return f"Invalid Bet, the maximum bet you can place is {p_chips.total} and the minimum bet is 1."
|
||||
except ValueError:
|
||||
return f"Invalid Bet, the maximum bet, {p_chips.total}"
|
||||
|
||||
# Show the cards
|
||||
msg += show_some(p_cards, d_cards, p_hand)
|
||||
# check for blackjack 21 and only two cards
|
||||
if p_hand.value == 21 and len(p_hand.cards) == 2:
|
||||
msg += "Player 🎰 BLAAAACKJACKKKK 💰"
|
||||
p_chips.total += round(p_chips.bet * 1.5)
|
||||
setLastCmdJack(nodeID, "dealerTurn")
|
||||
blackJack = True
|
||||
# Save the game state
|
||||
for i in range(len(jackTracker)):
|
||||
if jackTracker[i]['nodeID'] == nodeID:
|
||||
jackTracker[i]['cash'] = int(p_chips.total)
|
||||
break
|
||||
else:
|
||||
# Display the statistics
|
||||
stats = success_rate(next_card, p_hand)
|
||||
msg += stats
|
||||
setLastCmdJack(nodeID, "betPlaced")
|
||||
|
||||
if getLastCmdJack(nodeID) == "betPlaced":
|
||||
setLastCmdJack(nodeID, "playing")
|
||||
msg += "(H)it,(S)tand,(F)orfit,(D)ouble,(R)esend,(L)eave table"
|
||||
|
||||
# save the game state
|
||||
for i in range(len(jackTracker)):
|
||||
if jackTracker[i]['nodeID'] == nodeID:
|
||||
jackTracker[i]['cash'] = p_chips.total
|
||||
jackTracker[i]['bet'] = p_chips.bet
|
||||
jackTracker[i]['p_cards'] = p_cards
|
||||
jackTracker[i]['d_cards'] = d_cards
|
||||
jackTracker[i]['p_hand'] = p_hand
|
||||
jackTracker[i]['d_hand'] = d_hand
|
||||
jackTracker[i]['next_card'] = next_card
|
||||
return msg
|
||||
|
||||
|
||||
while getLastCmdJack(nodeID) == "playing": # Recall var. from hit and stand function
|
||||
next_card = hits(cards_deck)
|
||||
|
||||
# Get the statistics
|
||||
stats = success_rate(next_card, p_hand)
|
||||
|
||||
# Player's Turn
|
||||
choice = message.lower()
|
||||
|
||||
if choice == "hit" or choice == "h":
|
||||
# hits(obj_de, p_hand)
|
||||
p_hand.add_cards(next_card)
|
||||
msg += show_some(p_hand.cards, d_cards, p_hand)
|
||||
elif choice == "stand" or choice == "s":
|
||||
setLastCmdJack(nodeID, "dealerTurn")
|
||||
elif choice == "forfit" or choice == "f":
|
||||
p_chips.bet = p_chips.bet / 2
|
||||
setLastCmdJack(nodeID, "dealerTurn")
|
||||
p_hand.value += 21
|
||||
elif choice == "double" or choice == "d":
|
||||
if p_chips.bet * 2 <= p_chips.total:
|
||||
p_chips.bet *= 2
|
||||
next_d_card = hits(cards_deck)
|
||||
p_hand.add_cards(next_d_card)
|
||||
setLastCmdJack(nodeID, "dealerTurn")
|
||||
else:
|
||||
return "You can't Double Down, dont have enough chips"
|
||||
elif choice == "resend" or choice == "r":
|
||||
msg += show_some(p_hand.cards, d_cards, p_hand)
|
||||
else:
|
||||
return "(H)it,(S)tand,(F)orfit,(D)ouble,(R)esend,(L)eave table"
|
||||
|
||||
# Check if player bust
|
||||
if player_bust(p_hand, p_chips):
|
||||
d_win += 1
|
||||
msg += "💥PlayerBUST💥"
|
||||
setLastCmdJack(nodeID, "dealerTurn")
|
||||
|
||||
if getLastCmdJack(nodeID) == "playing":
|
||||
msg += stats
|
||||
msg += "[H,S,F,D]"
|
||||
|
||||
# Save the game state
|
||||
for i in range(len(jackTracker)):
|
||||
if jackTracker[i]['nodeID'] == nodeID:
|
||||
jackTracker[i]['cash'] = int(p_chips.total)
|
||||
jackTracker[i]['bet'] = int(p_chips.bet)
|
||||
jackTracker[i]['gameStats']['p_win'] = int(p_win)
|
||||
jackTracker[i]['gameStats']['d_win'] = int(d_win)
|
||||
jackTracker[i]['gameStats']['draw'] = int(draw)
|
||||
jackTracker[i]['p_cards'] = p_cards
|
||||
jackTracker[i]['d_cards'] = d_cards
|
||||
jackTracker[i]['p_hand'] = p_hand
|
||||
jackTracker[i]['d_hand'] = d_hand
|
||||
break
|
||||
|
||||
# Exit player's turn
|
||||
if getLastCmdJack(nodeID) == "dealerTurn":
|
||||
break
|
||||
|
||||
return msg
|
||||
|
||||
if getLastCmdJack(nodeID) == "dealerTurn":
|
||||
# Dealers Turn
|
||||
if not blackJack:
|
||||
# recall the game state
|
||||
for i in range(len(jackTracker)):
|
||||
if jackTracker[i]['nodeID'] == nodeID:
|
||||
p_chips.total = jackTracker[i]['cash']
|
||||
p_chips.bet = jackTracker[i]['bet']
|
||||
p_win = jackTracker[i]['gameStats']['p_win']
|
||||
d_win = jackTracker[i]['gameStats']['d_win']
|
||||
draw = jackTracker[i]['gameStats']['draw']
|
||||
p_cards = jackTracker[i]['p_cards']
|
||||
d_cards = jackTracker[i]['d_cards']
|
||||
p_hand = jackTracker[i]['p_hand']
|
||||
d_hand = jackTracker[i]['d_hand']
|
||||
next_card = jackTracker[i]['next_card']
|
||||
break
|
||||
|
||||
if p_hand.value <= 21:
|
||||
# Dealer's Turn
|
||||
while d_hand.value < 17:
|
||||
d_card = hits(cards_deck)
|
||||
d_hand.add_cards(d_card)
|
||||
if dealer_bust(d_hand, p_hand, p_chips):
|
||||
p_win += 1
|
||||
msg += "💰DealerBUST💥"
|
||||
break
|
||||
# Show all cards
|
||||
msg += show_all(p_hand.cards, d_hand.cards, p_hand, d_hand)
|
||||
|
||||
# Check who wins
|
||||
if push(p_hand, d_hand):
|
||||
draw += 1
|
||||
msg += "👌PUSH"
|
||||
elif player_wins(p_hand, d_hand, p_chips):
|
||||
p_win += 1
|
||||
msg += "🎉PLAYER WINS🎰"
|
||||
elif dealer_wins(p_hand, d_hand, p_chips):
|
||||
d_win += 1
|
||||
msg += "👎DEALER WINS"
|
||||
else:
|
||||
msg += "👎DEALER WINS"
|
||||
|
||||
# Display the Game Stats
|
||||
msg += gameStats(str(p_win), str(d_win), str(draw))
|
||||
|
||||
# Display the chips left
|
||||
if p_chips.total < 1:
|
||||
if p_chips.total > 0:
|
||||
msg += "🪙Keep the change you filthy animal!"
|
||||
else:
|
||||
msg += "💸NO MORE CHIPS!🏧💳"
|
||||
p_chips.total = jack_starting_cash
|
||||
else:
|
||||
# check high score
|
||||
highScore = loadHSJack()
|
||||
if highScore != 0 and p_chips.total > highScore['highScore']:
|
||||
msg += f"💰HighScore💰{p_chips.total} "
|
||||
saveHSJack(nodeID, p_chips.total)
|
||||
else:
|
||||
msg += f"💰You have {p_chips.total} chips "
|
||||
|
||||
msg += " Bet or Leave?"
|
||||
|
||||
# Reset the game
|
||||
setLastCmdJack(nodeID, "new")
|
||||
jackTracker[i]['cash'] = p_chips.total
|
||||
jackTracker[i]['gameStats']['p_win'] = p_win
|
||||
jackTracker[i]['gameStats']['d_win'] = d_win
|
||||
jackTracker[i]['gameStats']['draw'] = draw
|
||||
jackTracker[i]['p_cards'] = []
|
||||
jackTracker[i]['d_cards'] = []
|
||||
jackTracker[i]['p_hand'] = []
|
||||
jackTracker[i]['d_hand'] = []
|
||||
jackTracker[i]['time'] = time.time()
|
||||
|
||||
return msg
|
||||
652
modules/dopewar.py
Normal file
652
modules/dopewar.py
Normal file
@@ -0,0 +1,652 @@
|
||||
# Port of https://github.com/Reconfirefly/drugwars/tree/master
|
||||
# Adapted for Meshtastic mesh-bot by K7MHI Kelly Keeton 2024
|
||||
|
||||
import random
|
||||
import time
|
||||
import pickle
|
||||
from modules.log import *
|
||||
|
||||
# Global variables
|
||||
total_days = 7 # number of days or rotations the player has to play
|
||||
starting_cash = 5000
|
||||
# Database for the game reset on boot
|
||||
dwInventoryDb = [{'userID': 1234567890, 'inventory': 0, 'priceList': [], 'amount': []}]
|
||||
dwCashDb = [{'userID': 1234567890, 'cash': starting_cash},]
|
||||
dwGameDayDb = [{'userID': 1234567890, 'day': 0},]
|
||||
dwLocationDb = [{'userID': 1234567890, 'location': 'USA', 'loc_choice': 0},]
|
||||
dwPlayerTracker = [{'userID': 1234567890, 'last_played': time.time(), 'cmd': 'start'},]
|
||||
# high score is saved in a pickle file
|
||||
dwHighScore = {}
|
||||
|
||||
class Drugs:
|
||||
|
||||
def __init__(self, name, price_range):
|
||||
self.name = name
|
||||
self.price_range = price_range
|
||||
self.price_check()
|
||||
|
||||
def price_check(self):
|
||||
# the * is to unpack the touple of values that the random goes between
|
||||
self.price = random.randint(*self.price_range)
|
||||
# print("the price of " + self.name + " is " + str(self.price))
|
||||
return self.price
|
||||
|
||||
class Events:
|
||||
|
||||
def __init__(self, name, text, price_range):
|
||||
self.name = name
|
||||
self.price_range = price_range
|
||||
self.text = text
|
||||
self.price_mod()
|
||||
|
||||
def price_mod(self):
|
||||
self.price = random.randint(*self.price_range)
|
||||
return self.price
|
||||
|
||||
my_drugs = [
|
||||
# Drugs("Name", (min price, max price), amount)
|
||||
Drugs("Cocaine", (15000, 28000)),
|
||||
Drugs("Heroin", (2000, 10000)),
|
||||
Drugs("Weed", (300, 1000)),
|
||||
Drugs("Hash", (200, 1200)),
|
||||
Drugs("Opium", (400, 1800)),
|
||||
Drugs("Acid", (1000, 4200)),
|
||||
Drugs("Ludes", (18, 75)),
|
||||
]
|
||||
|
||||
event_list = [
|
||||
# Events("Name", "Text", (min price, max price))
|
||||
Events("Cocaine", 'El Chapo Arrested! 🚔 Coke price thru the roof! 📈', (40000, 110000)),
|
||||
Events("Heroin", 'Trump cracks down on opiates! Heroin in high demand by addicts📈', (9000, 25000)),
|
||||
Events("Weed", 'The DEA has fully legalized weed! Prices are at an all time low!📉', (50, 400)),
|
||||
Events("Hash", 'Ricky\'s hash driveway burned down! 🚒 Look at the price boys!📈', (800, 2000)),
|
||||
Events("Opium", 'Shenzhen 深圳 Opium 鸦片 Den 塔 was raided! 🚔 Street price is popping off!📈', (1800, 6000)),
|
||||
Events("Acid", 'The Grateful Dead are on tour! Acid prices are skyrocketing!📈', (5000, 15000)),
|
||||
Events("Ludes", 'The Wolf of Wall Street is back! Ludes are in demand!', (100, 300)),
|
||||
Events("Cocaine", "The Biden administration has legalized cocaine! Prices are at an all time low!📉", (3000, 10000)),
|
||||
Events("Heroin", "Oregon has legalized heroin! Prices are at an all time low!📉", (500, 2500)),
|
||||
Events("Weed", "Prices are at an all time HIGH!📈", (1000, 5000)),
|
||||
Events("Hash", "The Middle East has legalised hash! Prices are at an all time low!📉", (50, 1000)),
|
||||
Events("Opium", "The Sackler's flood the market with cheap opium! Prices are at an all time low!📉", (300, 900)),
|
||||
Events("Acid", "The FBI admits to dosing the water supply with LSD! Acid at an all time low!📉", (500, 2000)),
|
||||
Events("Ludes", "The FDA approves ludes for sale! Prices are at an all time low!📉", (3, 45))
|
||||
]
|
||||
|
||||
def generatelocations():
|
||||
# dictionary of locations
|
||||
locs = {'Canada': ('Red Deer', 'Edmonton', 'Calgary', 'Toronto', 'Vancouver', 'St. Johns'),
|
||||
'USA': ('L.A.', 'NYC', 'Chicago', 'Miami', 'Houston', 'Phoenix'), 'Mexico': ('Tijuana', 'Mexico City', 'Cancun', 'Juarez', 'Acapulco', 'Guadalajara'),\
|
||||
'South America': ('Bogota', 'Caracas', 'Lima', 'Santiago', 'Buenos Aires', 'Rio'), 'Europe': ('London', 'Paris', 'Berlin', 'Rome', 'Madrid', 'Moscow')}
|
||||
|
||||
country = list(locs.keys())
|
||||
country = country[random.randint(0, len(country)-1)]
|
||||
|
||||
# return the location list for the user's country
|
||||
location = []
|
||||
for i in range(len(locs[country])):
|
||||
location.append(locs[country][i])
|
||||
return location
|
||||
|
||||
def generate_event():
|
||||
# roll to see if an event happens
|
||||
event_choice = random.randint(0, len(event_list)-1)
|
||||
if random.randint(0, 100) > 35:
|
||||
return event_choice
|
||||
else:
|
||||
return -1
|
||||
|
||||
def officer(nodeID):
|
||||
global dwCashDb, dwInventoryDb
|
||||
|
||||
# get the inventory for the user
|
||||
for i in range(0, len(dwInventoryDb)):
|
||||
if dwInventoryDb[i].get('userID') == nodeID:
|
||||
inventory = dwInventoryDb[i].get('inventory')
|
||||
amount = check_inv(nodeID)
|
||||
|
||||
# get the cash for the user
|
||||
for i in range(0, len(dwCashDb)):
|
||||
if dwCashDb[i].get('userID') == nodeID:
|
||||
cash = dwCashDb[i].get('cash')
|
||||
|
||||
# rolls to see if the officer takes drugs from you
|
||||
# odds are (1 - event chance) * (officer chance) * (confiscation chance)
|
||||
# currently (1 - 0.35) * (0.20) * (0.35) = 4.55%
|
||||
# chance is approximate, not sure how randint handles endpoints, close enough for my purposes
|
||||
if random.randint(0, 100) > 65: # confiscation chance
|
||||
k = 0
|
||||
j = 0
|
||||
# removes all drugs from inventory tally and individual class attirbute
|
||||
for i in range(0, len(my_drugs)):
|
||||
j = amount[i]
|
||||
amount[i] = 0
|
||||
k += j
|
||||
inventory -= k
|
||||
# sends 'conf' for confiscated. sending a string is better than a number here
|
||||
cash_taken = 'conf'
|
||||
return cash_taken
|
||||
# rolls to see if the officer takes cash from you
|
||||
# odds are (1 - event chance) * (officer chance) * (1 - confiscation chance)
|
||||
# currently (1 - 0.35) * (0.20) * (0.65) = 8.45%
|
||||
# chance is approximate, not sure how randint handles endpoints, close enough for my purposes
|
||||
cash_taken = random.randint(1, cash-1)
|
||||
cash -= cash_taken
|
||||
|
||||
# Update the cash_db and inventory_db
|
||||
for i in range(0, len(dwCashDb)):
|
||||
if dwCashDb[i].get('userID') == nodeID:
|
||||
dwCashDb[i]['cash'] = cash
|
||||
for i in range(0, len(dwInventoryDb)):
|
||||
if dwInventoryDb[i].get('userID') == nodeID:
|
||||
dwInventoryDb[i]['inventory'] = inventory
|
||||
amount = dwInventoryDb[i].get('amount')
|
||||
|
||||
return cash_taken
|
||||
|
||||
|
||||
def price_change(event_number):
|
||||
price_list = []
|
||||
for i in range(0, len(my_drugs)):
|
||||
j = my_drugs[i]
|
||||
k = j.price_check()
|
||||
price_list.append(k)
|
||||
|
||||
# check if IndexError will be thrown and find a new event_number with generate_event
|
||||
while event_number > len(price_list)-1:
|
||||
event_number = generate_event()
|
||||
|
||||
if event_number != -1:
|
||||
price_list[event_number] = event_list[event_number].price_mod()
|
||||
|
||||
return price_list
|
||||
|
||||
def check_inv(nodeID):
|
||||
global dwInventoryDb
|
||||
|
||||
# get the inventory ammount for the user
|
||||
for i in range(0, len(dwInventoryDb)):
|
||||
if dwInventoryDb[i].get('userID') == nodeID:
|
||||
amount = dwInventoryDb[i].get('amount')
|
||||
|
||||
# if ammount is empty list initialize it
|
||||
if not amount:
|
||||
amount = []
|
||||
for i in range(0, len(my_drugs)):
|
||||
amount.append(0,)
|
||||
|
||||
# save the amount to the inventory_db
|
||||
for i in range(0, len(dwInventoryDb)):
|
||||
if dwInventoryDb[i].get('userID') == nodeID:
|
||||
dwInventoryDb[i]['amount'] = amount
|
||||
|
||||
return amount
|
||||
|
||||
def buy_func(nodeID, price_list, choice=0, value='0'):
|
||||
global dwCashDb, dwInventoryDb, dwPlayerTracker
|
||||
msg = ''
|
||||
|
||||
# get the inventory for the user
|
||||
for i in range(0, len(dwInventoryDb)):
|
||||
if dwInventoryDb[i].get('userID') == nodeID:
|
||||
inventory = dwInventoryDb[i].get('inventory')
|
||||
amount = check_inv(nodeID)
|
||||
|
||||
# get the cash for the user
|
||||
for i in range(0, len(dwCashDb)):
|
||||
if dwCashDb[i].get('userID') == nodeID:
|
||||
cash = dwCashDb[i].get('cash')
|
||||
|
||||
drug_choice = choice
|
||||
if choice == 0:
|
||||
msg = f"Didnt see a drug chouce. ex: s,1,10 sells 10 of drug 1{my_drugs[1].name}, or p for price list"
|
||||
return msg
|
||||
else:
|
||||
if drug_choice in range(1, len(my_drugs) + 1):
|
||||
drug_choice = drug_choice - 1
|
||||
msg = my_drugs[drug_choice].name + ": you have🎒 " + str(amount[drug_choice]) + " "
|
||||
msg += " The going price is: $" + "{:,}".format(price_list[drug_choice]) + " "
|
||||
|
||||
buy_amount = value
|
||||
if buy_amount == 'm':
|
||||
buy_amount = cash // price_list[drug_choice]
|
||||
if buy_amount > 100 - inventory:
|
||||
buy_amount = 100 - inventory
|
||||
# set the buy amount to the max if the user enters m
|
||||
buy_amount = int(buy_amount)
|
||||
|
||||
if buy_amount == 0:
|
||||
msg = f"Didnt see a qty. ex: b,1,10 buys 10 of {my_drugs[1].name}, can also use m for max"
|
||||
return msg
|
||||
elif buy_amount not in range(1, 101):
|
||||
msg = "Enter qty or m for max"
|
||||
return msg
|
||||
elif buy_amount > 100 - inventory:
|
||||
msg = "You don\'t have enough space for all that.🎒"
|
||||
return msg
|
||||
elif buy_amount * price_list[drug_choice] <= cash:
|
||||
amount[drug_choice] += buy_amount
|
||||
cash -= buy_amount * price_list[drug_choice]
|
||||
inventory += buy_amount
|
||||
msg += "You bought " + str(buy_amount) + " " + my_drugs[drug_choice].name + '. Remaining cash: $' + str(cash)
|
||||
msg += f"\nBuy💸, Sell💰, Fly🛫?"
|
||||
else:
|
||||
msg = "You don't have enough cash!😭"
|
||||
return msg
|
||||
|
||||
# update the cash_db and inventory_db values
|
||||
for i in range(0, len(dwCashDb)):
|
||||
if dwCashDb[i].get('userID') == nodeID:
|
||||
dwCashDb[i]['cash'] = cash
|
||||
for i in range(0, len(dwInventoryDb)):
|
||||
if dwInventoryDb[i].get('userID') == nodeID:
|
||||
dwInventoryDb[i]['inventory'] = inventory
|
||||
# save the last command as ask_bsf
|
||||
for i in range(0, len(dwPlayerTracker)):
|
||||
if dwPlayerTracker[i].get('userID') == nodeID:
|
||||
dwPlayerTracker[i]['cmd'] = 'ask_bsf'
|
||||
|
||||
return msg
|
||||
|
||||
|
||||
def sell_func(nodeID, price_list, choice=0, value='0'):
|
||||
global dwCashDb, dwInventoryDb, dwPlayerTracker
|
||||
msg = ''
|
||||
|
||||
# get the inventory for the user
|
||||
for i in range(0, len(dwInventoryDb)):
|
||||
if dwInventoryDb[i].get('userID') == nodeID:
|
||||
inventory = dwInventoryDb[i].get('inventory')
|
||||
amount = check_inv(nodeID)
|
||||
|
||||
# get the cash for the user
|
||||
for i in range(0, len(dwCashDb)):
|
||||
if dwCashDb[i].get('userID') == nodeID:
|
||||
cash = dwCashDb[i].get('cash')
|
||||
|
||||
# get the drug choice and amount to sell
|
||||
drug_choice = choice
|
||||
sell_amount = value
|
||||
|
||||
try:
|
||||
if sell_amount == 'm':
|
||||
sell_amount = amount[drug_choice - 1]
|
||||
else:
|
||||
sell_amount = int(sell_amount)
|
||||
if sell_amount not in range(1, 101):
|
||||
msg = "You can only sell between 1 and 100"
|
||||
return msg
|
||||
except ValueError:
|
||||
msg = "Enter qty or m for max"
|
||||
return msg
|
||||
|
||||
# check if the user has any of the drug they are trying to sell
|
||||
if choice == 0:
|
||||
msg = "Enter b or s and the drug number and qty you want to buy or sell. ex: b,1,10 buys 10 of drug 1"
|
||||
return msg
|
||||
else:
|
||||
if drug_choice in range(1, len(my_drugs) + 1) and amount[drug_choice - 1] > 0:
|
||||
drug_choice = drug_choice - 1
|
||||
msg = my_drugs[drug_choice].name + ": you have " + str(amount[drug_choice]) +\
|
||||
" The going price is: $" + str(price_list[drug_choice])
|
||||
# check if the user has enough of the drug to sell
|
||||
if sell_amount <= amount[drug_choice]:
|
||||
amount[drug_choice] -= sell_amount
|
||||
cash += sell_amount * price_list[drug_choice]
|
||||
inventory -= sell_amount
|
||||
msg += " You sold " + str(sell_amount) + " " + my_drugs[drug_choice].name + ' for $' +\
|
||||
str(sell_amount * price_list[drug_choice]) + '. Total cash: $' + "{:,}".format(cash)
|
||||
else:
|
||||
msg = "You don't have that much"
|
||||
return msg
|
||||
else:
|
||||
msg = "You don't have any " + my_drugs[drug_choice - 1].name
|
||||
return msg
|
||||
|
||||
# update the cash_db and inventory_db values
|
||||
for i in range(0, len(dwCashDb)):
|
||||
if dwCashDb[i].get('userID') == nodeID:
|
||||
dwCashDb[i]['cash'] = cash
|
||||
for i in range(0, len(dwInventoryDb)):
|
||||
if dwInventoryDb[i].get('userID') == nodeID:
|
||||
dwInventoryDb[i]['inventory'] = inventory
|
||||
dwInventoryDb[i]['amount'] = amount
|
||||
# save the last command as ask_bsf
|
||||
for i in range(0, len(dwPlayerTracker)):
|
||||
if dwPlayerTracker[i].get('userID') == nodeID:
|
||||
dwPlayerTracker[i]['cmd'] = 'ask_bsf'
|
||||
|
||||
return msg
|
||||
|
||||
|
||||
def get_location_table(nodeID, choice=0):
|
||||
global dwLocationDb
|
||||
# get the location for the user
|
||||
for i in range(0, len(dwLocationDb)):
|
||||
if dwLocationDb[i].get('userID') == nodeID:
|
||||
loc = dwLocationDb[i].get('location')
|
||||
|
||||
# list the lcaitons and their index in two columns
|
||||
loc_table_string = ''
|
||||
for i in range(len(loc)):
|
||||
loc_table_string += str(i+1) + '. ' + loc[i] + ' '
|
||||
loc_table_string += ' Where do you want to 🛫?#'
|
||||
return loc_table_string
|
||||
|
||||
|
||||
def endGameDw(nodeID):
|
||||
global dwCashDb, dwInventoryDb, dwLocationDb, dwGameDayDb, dwHighScore
|
||||
msg = ''
|
||||
dwHighScore = getHighScoreDw()
|
||||
# Confirm the cash for the user
|
||||
for i in range(0, len(dwCashDb)):
|
||||
if dwCashDb[i].get('userID') == nodeID:
|
||||
cash = dwCashDb[i].get('cash')
|
||||
logger.debug("System: DopeWars: Game Over for user: " + str(nodeID) + " with cash: " + str(cash))
|
||||
|
||||
# remove the player from the game databases
|
||||
for i in range(0, len(dwCashDb)):
|
||||
if dwCashDb[i].get('userID') == nodeID:
|
||||
dwCashDb.pop(i)
|
||||
for i in range(0, len(dwInventoryDb)):
|
||||
if dwInventoryDb[i].get('userID') == nodeID:
|
||||
dwInventoryDb.pop(i)
|
||||
for i in range(0, len(dwLocationDb)):
|
||||
if dwLocationDb[i].get('userID') == nodeID:
|
||||
dwLocationDb.pop(i)
|
||||
for i in range(0, len(dwGameDayDb)):
|
||||
if dwGameDayDb[i].get('userID') == nodeID:
|
||||
dwGameDayDb.pop(i)
|
||||
for i in range(0, len(dwPlayerTracker)):
|
||||
if dwPlayerTracker[i].get('userID') == nodeID:
|
||||
dwPlayerTracker.pop(i)
|
||||
|
||||
# checks if the player's score is higher than the high score and writes a new high score if it is
|
||||
if cash > dwHighScore.get('cash'):
|
||||
dwHighScore = ({'userID': nodeID, 'cash': round(cash, 2)})
|
||||
with open('dopewar_hs.pkl', 'wb') as file:
|
||||
pickle.dump(dwHighScore, file)
|
||||
msg = "You finished with $" + str(cash) + " and beat the high score!🎉💰"
|
||||
return msg
|
||||
if cash > starting_cash:
|
||||
msg = 'You made money! 💵 Up ' + str((cash/starting_cash).__round__()) + 'x! Well done.'
|
||||
return msg
|
||||
if cash == starting_cash:
|
||||
msg = 'You broke even... hope you at least had fun 💉💊'
|
||||
return msg
|
||||
if cash < starting_cash:
|
||||
msg = "You lost money, better go get a real job.💸"
|
||||
|
||||
logger.debug("System: DopeWars: Game Over for user: " + str(nodeID) + " with cash: " + str(cash))
|
||||
|
||||
return msg
|
||||
|
||||
def getHighScoreDw():
|
||||
global dwHighScore
|
||||
# Load high score table
|
||||
try:
|
||||
with open('dopewar_hs.pkl', 'rb') as file:
|
||||
dwHighScore = pickle.load(file)
|
||||
except FileNotFoundError:
|
||||
logger.debug("System: DopeWars: No high score table found")
|
||||
# high score pickle file is a touple of the nodeID and the high score
|
||||
dwHighScore = ({"userID": 4258675309, "cash": 100})
|
||||
# write a new high score file if one is not found
|
||||
with open('dopewar_hs.pkl', 'wb') as file:
|
||||
pickle.dump(dwHighScore, file)
|
||||
return dwHighScore
|
||||
|
||||
def render_game_screen(userID, day_play, total_day, loc_choice, event_number, price_list, cash_stolen):
|
||||
global dwCashDb, dwInventoryDb, dwLocationDb
|
||||
msg = ''
|
||||
# get the location for the user
|
||||
for i in range(0, len(dwLocationDb)):
|
||||
if dwLocationDb[i].get('userID') == userID:
|
||||
loc = dwLocationDb[i].get('location')
|
||||
|
||||
if event_number != -1:
|
||||
msg += event_list[event_number].text + f"\n"
|
||||
if event_number == -1 and cash_stolen != 0 and cash_stolen != 'conf':
|
||||
msg += "🚔Officer Leroy stopped you and took $" + str(cash_stolen) + "💸" + f"\n"
|
||||
if event_number == -1 and cash_stolen == 'conf':
|
||||
msg += "🚔Officer Leroy stopped you and took all of your drugs.🚭" + f"\n"
|
||||
|
||||
# get the inventory for the user
|
||||
for i in range(0, len(dwInventoryDb)):
|
||||
if dwInventoryDb[i].get('userID') == userID:
|
||||
inventory = dwInventoryDb[i].get('inventory')
|
||||
|
||||
amount = check_inv(userID)
|
||||
# get the cash for the user
|
||||
for i in range(0, len(dwCashDb)):
|
||||
if dwCashDb[i].get('userID') == userID:
|
||||
cash = dwCashDb[i].get('cash')
|
||||
|
||||
msg += "🗺️" + loc[int(loc_choice) - 1] + " 📆" + str(day_play) + '/' + str(total_day) + " 🎒" + str(inventory) + "/100" + " 💵" + "{:,}".format(cash) + f"\n"
|
||||
|
||||
for i, drug in enumerate(my_drugs, 1):
|
||||
qty = amount[i-1]
|
||||
msg += f'#{str(i)}.{drug.name}${"{:,}".format(price_list[i-1])}({qty}) '
|
||||
|
||||
return msg
|
||||
|
||||
|
||||
def dopeWarGameDay(nodeID, day_play, total_day):
|
||||
global dwCashDb, dwLocationDb, dwInventoryDb
|
||||
cash_stolen = 0
|
||||
|
||||
# roll for the event of the day
|
||||
event_number = generate_event()
|
||||
|
||||
# get the location for the user
|
||||
for i in range(0, len(dwLocationDb)):
|
||||
if dwLocationDb[i].get('userID') == nodeID:
|
||||
loc = dwLocationDb[i].get('location')
|
||||
loc_choice = dwLocationDb[i].get('loc_choice')
|
||||
|
||||
# rolls to see if the officer event happens
|
||||
# odds are (1 - event chance) * (officer chance)
|
||||
# currently (1 - 0.35) * (0.20) = 13%
|
||||
# chance is approximate, not sure how randint handles endpoints, close enough for my purposes
|
||||
if event_number == -1 and random.randint(0, 100) > 80:
|
||||
cash_stolen = officer(nodeID)
|
||||
|
||||
price_list = price_change(event_number)
|
||||
|
||||
# set the price_list for the user
|
||||
for i in range(0, len(dwInventoryDb)):
|
||||
if dwInventoryDb[i].get('userID') == nodeID:
|
||||
dwInventoryDb[i]['priceList'] = price_list
|
||||
|
||||
check_inv(nodeID)
|
||||
|
||||
# main game display print
|
||||
msg = render_game_screen(nodeID, day_play, total_day, loc_choice, event_number, price_list, cash_stolen)
|
||||
|
||||
return msg
|
||||
|
||||
def playDopeWars(nodeID, cmd):
|
||||
global dwGameDayDb, dwPlayerTracker, dwCashDb, dwInventoryDb, dwLocationDb, dwHighScore
|
||||
|
||||
inGame = False
|
||||
msg = ''
|
||||
|
||||
# check if the player is currently playing the game
|
||||
for i in range(0, len(dwGameDayDb)):
|
||||
if dwGameDayDb[i].get('userID') == nodeID:
|
||||
inGame = True
|
||||
|
||||
if not inGame:
|
||||
# initalize player in the database
|
||||
loc = generatelocations()
|
||||
dwInventoryDb.append({'userID': nodeID, 'inventory': 0, 'priceList': []})
|
||||
dwCashDb.append({'userID': nodeID, 'cash': starting_cash})
|
||||
dwLocationDb.append({'userID': nodeID, 'location': loc, 'loc_choice': 0})
|
||||
dwGameDayDb.append({'userID': nodeID, 'day': 0})
|
||||
dwPlayerTracker.append({'userID': nodeID, 'last_played': time.time(), 'cmd': 'start'})
|
||||
logger.debug("System: DopeWars: New player: " + str(nodeID))
|
||||
|
||||
# get the day for the user
|
||||
for i in range(0, len(dwGameDayDb)):
|
||||
if dwGameDayDb[i].get('userID') == nodeID:
|
||||
game_day = dwGameDayDb[i].get('day')
|
||||
|
||||
# get the player's last command
|
||||
for i in range(0, len(dwPlayerTracker)):
|
||||
if dwPlayerTracker[i].get('userID') == nodeID:
|
||||
last_cmd = dwPlayerTracker[i].get('cmd')
|
||||
|
||||
# get the price_list for the user
|
||||
for i in range(0, len(dwInventoryDb)):
|
||||
if dwInventoryDb[i].get('userID') == nodeID:
|
||||
price_list = dwInventoryDb[i].get('priceList')
|
||||
|
||||
# get the location for the user
|
||||
for i in range(0, len(dwLocationDb)):
|
||||
if dwLocationDb[i].get('userID') == nodeID:
|
||||
loc_choice = dwLocationDb[i].get('loc_choice')
|
||||
|
||||
# Pick Starting City
|
||||
if last_cmd == 'start':
|
||||
# print the location table
|
||||
msg = get_location_table(nodeID)
|
||||
|
||||
# set the player's last command to location to start the game
|
||||
for i in range(0, len(dwPlayerTracker)):
|
||||
if dwPlayerTracker[i].get('userID') == nodeID:
|
||||
dwPlayerTracker[i]['cmd'] = 'location'
|
||||
|
||||
if last_cmd == 'ask_bsf':
|
||||
msg = f'example buy:\nb,drug#,qty# or Sell: s,1,10 qty can be (m)ax\n f,p or end'
|
||||
menu_choice = cmd.lower()
|
||||
if ',' in menu_choice or '.' in menu_choice:
|
||||
#split the choice into a letter and a number for the buy/sell functions
|
||||
try:
|
||||
if '.' in menu_choice:
|
||||
menu_choice = menu_choice.split('.')
|
||||
if ',' in menu_choice:
|
||||
menu_choice = menu_choice.split(',')
|
||||
|
||||
if int(menu_choice[1]) not in range(1, 8):
|
||||
raise ValueError
|
||||
else:
|
||||
menu_choice[1] = int(menu_choice[1])
|
||||
if menu_choice[0] not in ['b', 's']:
|
||||
raise ValueError
|
||||
if menu_choice[2] != 'm':
|
||||
if int(menu_choice[2]) not in range(1, 101):
|
||||
raise ValueError
|
||||
else:
|
||||
menu_choice[2] = int(menu_choice[2])
|
||||
|
||||
except ValueError:
|
||||
msg = f'a value was bad, example dopeware Buy or Sell\n b,1,10 or s,1,m'
|
||||
return msg
|
||||
|
||||
if menu_choice[0] == 'b':
|
||||
# set last command to ask_bsf and buy
|
||||
for i in range(0, len(dwPlayerTracker)):
|
||||
if dwPlayerTracker[i].get('userID') == nodeID:
|
||||
dwPlayerTracker[i]['cmd'] = 'ask_bsf'
|
||||
msg = buy_func(nodeID, price_list, menu_choice[1], menu_choice[2])
|
||||
return msg
|
||||
|
||||
if menu_choice[0] == 's':
|
||||
# set last command to ask_bsf and sell
|
||||
for i in range(0, len(dwPlayerTracker)):
|
||||
if dwPlayerTracker[i].get('userID') == nodeID:
|
||||
dwPlayerTracker[i]['cmd'] = 'ask_bsf'
|
||||
msg = sell_func(nodeID, price_list, menu_choice[1], menu_choice[2])
|
||||
return msg
|
||||
elif 's' in menu_choice:
|
||||
msg = ''
|
||||
# sell everything we have in backpack
|
||||
for i in range(0, len(dwInventoryDb)):
|
||||
if dwInventoryDb[i].get('userID') == nodeID:
|
||||
inventory = dwInventoryDb[i].get('inventory')
|
||||
if inventory == 0:
|
||||
msg = "You don't have anything to sell🚭"
|
||||
else:
|
||||
for i in range(1, (len(my_drugs) +1)):
|
||||
sell = sell_func(nodeID, price_list, i, 'm')
|
||||
# ignore starts with "You don't have any"
|
||||
if not sell.startswith("You don't have any"):
|
||||
msg += sell
|
||||
if i != len(my_drugs):
|
||||
msg += '\n'
|
||||
return msg
|
||||
elif 'f' in menu_choice:
|
||||
# set last command to location
|
||||
for i in range(0, len(dwPlayerTracker)):
|
||||
if dwPlayerTracker[i].get('userID') == nodeID:
|
||||
dwPlayerTracker[i]['cmd'] = 'location'
|
||||
last_cmd = 'location'
|
||||
|
||||
elif 'p' in menu_choice:
|
||||
# render_game_screen
|
||||
msg = render_game_screen(nodeID, game_day, total_days, loc_choice, -1, price_list, 0)
|
||||
return msg
|
||||
elif 'end' in menu_choice:
|
||||
msg = endGameDw(nodeID)
|
||||
return msg
|
||||
else:
|
||||
msg = f'example buy:\nb,drug#,qty# or Sell: s,1,10 qty can be (m)ax\n f,p or end'
|
||||
return msg
|
||||
|
||||
# Buy
|
||||
if last_cmd == 'buy':
|
||||
# ned to collect which drug # and qty to buy
|
||||
msg = buy_func(nodeID, price_list)
|
||||
return msg
|
||||
|
||||
# Sell
|
||||
if last_cmd == 'sell':
|
||||
msg = sell_func(nodeID, price_list)
|
||||
return msg
|
||||
|
||||
# Pick Location, and display main game screen
|
||||
if last_cmd == 'location':
|
||||
# validate the location choice
|
||||
try:
|
||||
loc_choice = int(cmd)
|
||||
if loc_choice not in range(1, 6):
|
||||
raise ValueError
|
||||
except ValueError:
|
||||
loc_choice = random.randint(1, 6)
|
||||
|
||||
# set the player's location choice
|
||||
for i in range(0, len(dwLocationDb)):
|
||||
if dwLocationDb[i].get('userID') == nodeID:
|
||||
dwLocationDb[i]['loc_choice'] = loc_choice
|
||||
|
||||
# set the player's last command
|
||||
for i in range(0, len(dwPlayerTracker)):
|
||||
if dwPlayerTracker[i].get('userID') == nodeID:
|
||||
dwPlayerTracker[i]['cmd'] = 'display_main'
|
||||
|
||||
# increment the game_day
|
||||
game_day += 1
|
||||
for i in range(0, len(dwGameDayDb)):
|
||||
if dwGameDayDb[i].get('userID') == nodeID:
|
||||
dwGameDayDb[i]['day'] = game_day
|
||||
|
||||
# update the player's last played time
|
||||
for i in range(0, len(dwPlayerTracker)):
|
||||
if dwPlayerTracker[i].get('userID') == nodeID:
|
||||
dwPlayerTracker[i]['last_played'] = time.time()
|
||||
|
||||
last_cmd = 'display_main'
|
||||
|
||||
# Display Main Game Screen and ask for buy, sell, or fly
|
||||
if last_cmd == 'display_main':
|
||||
msg = dopeWarGameDay(nodeID, game_day, total_days)
|
||||
msg += f"\nBuy💸, Sell💰, (F)ly🛫? (P)riceList?"
|
||||
# set the player's last command
|
||||
for i in range(0, len(dwPlayerTracker)):
|
||||
if dwPlayerTracker[i].get('userID') == nodeID:
|
||||
dwPlayerTracker[i]['cmd'] = 'ask_bsf'
|
||||
|
||||
# Game end
|
||||
if game_day == total_days + 1:
|
||||
msg = endGameDw(nodeID)
|
||||
|
||||
return msg
|
||||
409
modules/golfsim.py
Normal file
409
modules/golfsim.py
Normal file
@@ -0,0 +1,409 @@
|
||||
# https://github.com/danfriedman30/pythongame
|
||||
# Adapted for Meshtastic mesh-bot by K7MHI Kelly Keeton 2024
|
||||
|
||||
import random
|
||||
import time
|
||||
import pickle
|
||||
from modules.log import *
|
||||
|
||||
# Clubs setup
|
||||
driver_distances = list(range(230, 280, 5))
|
||||
low_distances = list(range(185, 215, 5))
|
||||
mid_distances = list(range(130, 185, 5))
|
||||
high_distances = list(range(90, 135, 5))
|
||||
gap_wedge_distances = list(range(50, 85, 5))
|
||||
lob_wedge_distances = list(range(10, 50, 5))
|
||||
putt_outcomes = [1, 2, 3]
|
||||
|
||||
# Hole/Course Setup
|
||||
full_hole_range = list(range(130, 520, 5))
|
||||
par3_range = list(range(130, 255, 5))
|
||||
par4_range = list(range(255, 445, 5))
|
||||
par5_range = list(range(445, 520, 5))
|
||||
par3_4_range = par3_range + par4_range
|
||||
par3_5_range = par3_range + par5_range
|
||||
par4_5_range = par4_range + par5_range
|
||||
|
||||
# Player setup
|
||||
playingHole = False
|
||||
golfTracker = [{'nodeID': 0, 'last_played': time.time(), 'cmd': '', 'hole': 0, 'distance_remaining': 0, 'hole_shots': 0, 'hole_strokes': 0, 'hole_to_par': 0, 'total_strokes': 0, 'total_to_par': 0, 'par': 0, 'hazard': ''}]
|
||||
|
||||
# Club functions
|
||||
def hit_driver():
|
||||
club_distance = random.choice(driver_distances)
|
||||
return club_distance
|
||||
|
||||
def hit_low_iron():
|
||||
club_distance = random.choice(low_distances)
|
||||
return club_distance
|
||||
|
||||
def hit_mid_iron():
|
||||
club_distance = random.choice(mid_distances)
|
||||
return club_distance
|
||||
|
||||
def hit_high_iron():
|
||||
club_distance = random.choice(high_distances)
|
||||
return club_distance
|
||||
|
||||
def hit_gap_wedge():
|
||||
club_distance = random.choice(gap_wedge_distances)
|
||||
return club_distance
|
||||
|
||||
def hit_lob_wedge():
|
||||
club_distance = random.choice(lob_wedge_distances)
|
||||
return club_distance
|
||||
|
||||
def finish_hole():
|
||||
finish = random.choice(putt_outcomes)
|
||||
return finish
|
||||
|
||||
def endGameGolf(nodeID):
|
||||
# pop player from tracker
|
||||
for i in range(len(golfTracker)):
|
||||
if golfTracker[i]['nodeID'] == nodeID:
|
||||
golfTracker.pop(i)
|
||||
logger.debug("System: GolfSim: Player " + str(nodeID) + " has ended their round.")
|
||||
|
||||
def getScorecardGolf(scorecard):
|
||||
# Scorecard messages, convert score to message comment
|
||||
msg = ""
|
||||
if scorecard == 8:
|
||||
# Quadruple bogey
|
||||
msg += " +Quad Bogey☃️ "
|
||||
elif scorecard == 7:
|
||||
# Triple bogey
|
||||
msg += " +Triple Bogey "
|
||||
elif scorecard == 6:
|
||||
# Double bogey
|
||||
msg += " +Double Bogey "
|
||||
elif scorecard == 5:
|
||||
# Bogey
|
||||
msg += " +Bogey "
|
||||
elif scorecard > 0:
|
||||
# Over par
|
||||
msg += f" +Par {str(scorecard)} "
|
||||
elif scorecard == 0:
|
||||
# Even par
|
||||
msg += " Even Par💪 "
|
||||
elif scorecard == -1:
|
||||
# Birdie
|
||||
msg += " -Birdie🐦 "
|
||||
elif scorecard == -2:
|
||||
# Eagle
|
||||
msg += " -Eagle🦅 "
|
||||
elif scorecard == -3:
|
||||
# Albatross
|
||||
msg += " -Albatross🦅🦅 "
|
||||
else:
|
||||
# Under par
|
||||
msg += f" -Par {str(abs(scorecard))} "
|
||||
return msg
|
||||
|
||||
def getHighScoreGolf(nodeID, strokes, par):
|
||||
# check if player is in high score list
|
||||
try:
|
||||
with open('golfsim_hs.pkl', 'rb') as f:
|
||||
golfHighScore = pickle.load(f)
|
||||
except:
|
||||
logger.debug("System: GolfSim: High Score file not found.")
|
||||
golfHighScore = [{'nodeID': nodeID, 'strokes': strokes, 'par': par}]
|
||||
with open('golfsim_hs.pkl', 'wb') as f:
|
||||
pickle.dump(golfHighScore, f)
|
||||
|
||||
if strokes < golfHighScore[0]['strokes']:
|
||||
# player got new low score which is high score
|
||||
golfHighScore[0]['nodeID'] = nodeID
|
||||
golfHighScore[0]['strokes'] = strokes
|
||||
golfHighScore[0]['par'] = par
|
||||
with open('golfsim_hs.pkl', 'wb') as f:
|
||||
pickle.dump(golfHighScore, f)
|
||||
return golfHighScore
|
||||
|
||||
return 0
|
||||
|
||||
# Main game loop
|
||||
def playGolf(nodeID, message, finishedHole=False):
|
||||
msg = ''
|
||||
global golfTracker
|
||||
# Course setup
|
||||
par3_count = 0
|
||||
par4_count = 0
|
||||
par5_count = 0
|
||||
# Scorecard setup
|
||||
total_strokes = 0
|
||||
total_to_par = 0
|
||||
par = 0
|
||||
|
||||
# get player's last command from tracker if not new player
|
||||
last_cmd = ""
|
||||
for i in range(len(golfTracker)):
|
||||
if golfTracker[i]['nodeID'] == nodeID:
|
||||
last_cmd = golfTracker[i]['cmd']
|
||||
hole = golfTracker[i]['hole']
|
||||
distance_remaining = golfTracker[i]['distance_remaining']
|
||||
hole_shots = golfTracker[i]['hole_shots']
|
||||
par = golfTracker[i]['par']
|
||||
total_strokes = golfTracker[i]['total_strokes']
|
||||
total_to_par = golfTracker[i]['total_to_par']
|
||||
|
||||
if last_cmd == "" or last_cmd == "new":
|
||||
# Start a new hole
|
||||
if hole <= 9:
|
||||
# Set up hole count restrictions on par
|
||||
if par3_count < 2 and par4_count < 5 and par5_count < 2:
|
||||
hole_length = random.choice(full_hole_range)
|
||||
if par3_count >= 2 and par4_count < 5 and par5_count < 2:
|
||||
hole_length = random.choice(par4_5_range)
|
||||
if par3_count >= 2 and par4_count < 5 and par5_count >= 2:
|
||||
hole_length = random.choice(par4_range)
|
||||
if par3_count < 2 and par4_count < 5 and par5_count >= 2:
|
||||
hole_length = random.choice(par3_4_range)
|
||||
if par3_count < 2 and par4_count >= 5 and par5_count >= 2:
|
||||
hole_length = random.choice(par3_range)
|
||||
if par3_count >= 2 and par4_count >= 5 and par5_count < 2:
|
||||
hole_length = random.choice(par5_range)
|
||||
if par3_count < 2 and par4_count >= 5 and par5_count < 2:
|
||||
hole_length = random.choice(par3_5_range)
|
||||
|
||||
# Set up par for the hole
|
||||
if hole_length <= 250:
|
||||
par = 3
|
||||
par3_count += 1
|
||||
elif hole_length > 250 and hole_length <= 440:
|
||||
par = 4
|
||||
par4_count += 1
|
||||
elif hole_length > 440:
|
||||
par = 5
|
||||
par5_count += 1
|
||||
|
||||
# roll for chance of hazard
|
||||
hazard_chance = random.randint(1, 100)
|
||||
weather_chance = random.randint(1, 100)
|
||||
# have low chances of hazards and weather
|
||||
hasHazard = False
|
||||
hazard = ""
|
||||
if hazard_chance < 25:
|
||||
# Further reduce chance of hazards with weather
|
||||
if weather_chance < 15:
|
||||
# randomly calculate a hazard for the hole sand, 🌊, 🌲, 🏘️, etc
|
||||
hazard = random.choice(["🏖️", "🌊", "🌲", "🏘️"])
|
||||
hasHazard = True
|
||||
|
||||
|
||||
# Set initial parameters before starting a hole
|
||||
distance_remaining = hole_length
|
||||
hole_shots = 0
|
||||
|
||||
# save player's current game state
|
||||
for i in range(len(golfTracker)):
|
||||
if golfTracker[i]['nodeID'] == nodeID:
|
||||
golfTracker[i]['distance_remaining'] = distance_remaining
|
||||
golfTracker[i]['cmd'] = 'stroking'
|
||||
golfTracker[i]['par'] = par
|
||||
golfTracker[i]['total_strokes'] = total_strokes
|
||||
golfTracker[i]['total_to_par'] = total_to_par
|
||||
golfTracker[i]['hazard'] = hazard
|
||||
golfTracker[i]['hole'] = hole
|
||||
golfTracker[i]['last_played'] = time.time()
|
||||
golfTracker[i]['hole_shots'] = hole_shots
|
||||
|
||||
# Show player the hole information
|
||||
msg += "⛳️#" + str(hole) + " is a " + str(hole_length) + "-yard Par " + str(par) + "."
|
||||
if hasHazard:
|
||||
msg += "⚠️" + hazard + "."
|
||||
else:
|
||||
# add weather conditions with random choice from list, this is fluff
|
||||
msg += random.choice(["☀️", "💨", "☀️", "☀️", "⛅️", "☁️", "☀️"])
|
||||
|
||||
if not finishedHole:
|
||||
msg += f"\nChoose your club."
|
||||
|
||||
return msg
|
||||
|
||||
if last_cmd == 'stroking':
|
||||
|
||||
# Get player's current game state
|
||||
for i in range(len(golfTracker)):
|
||||
if golfTracker[i]['nodeID'] == nodeID:
|
||||
distance_remaining = golfTracker[i]['distance_remaining']
|
||||
hole = golfTracker[i]['hole']
|
||||
hole_shots = golfTracker[i]['hole_shots']
|
||||
par = golfTracker[i]['par']
|
||||
total_strokes = golfTracker[i]['total_strokes']
|
||||
total_to_par = golfTracker[i]['total_to_par']
|
||||
hazard = golfTracker[i]['hazard']
|
||||
|
||||
# Start loop to be able to choose clubs while at least 20 yards away
|
||||
if distance_remaining >= 20:
|
||||
msg = ""
|
||||
club = message.lower()
|
||||
shot_distance = 0
|
||||
|
||||
pin_distance = distance_remaining
|
||||
|
||||
if club == "driver" or club.startswith("d"):
|
||||
shot_distance = hit_driver()
|
||||
msg += "🏌️Hit D " + str(shot_distance) + "yd. "
|
||||
distance_remaining = abs(distance_remaining - shot_distance)
|
||||
hole_shots += 1
|
||||
elif "low" in club or club.startswith("l"):
|
||||
shot_distance = hit_low_iron()
|
||||
msg += "🏌️Hit L Iron " + str(shot_distance) + "yd. "
|
||||
distance_remaining = abs(distance_remaining - shot_distance)
|
||||
hole_shots += 1
|
||||
elif "mid" in club or club.startswith("m"):
|
||||
shot_distance = hit_mid_iron()
|
||||
msg += "🏌️Hit M Iron " + str(shot_distance) + "yd. "
|
||||
distance_remaining = abs(distance_remaining - shot_distance)
|
||||
hole_shots += 1
|
||||
elif "high" in club or club.startswith("h"):
|
||||
shot_distance = hit_high_iron()
|
||||
msg += "🏌️Hit H Iron " + str(shot_distance) + "yd. "
|
||||
distance_remaining = abs(distance_remaining - shot_distance)
|
||||
hole_shots += 1
|
||||
elif "gap" in club or club.startswith("g"):
|
||||
shot_distance = hit_gap_wedge()
|
||||
msg += "🏌️Hit G Wedge " + str(shot_distance) + "yd ."
|
||||
distance_remaining = abs(distance_remaining - shot_distance)
|
||||
hole_shots += 1
|
||||
elif "wedge" in club or club.startswith("w"):
|
||||
shot_distance = hit_lob_wedge()
|
||||
msg += "🏌️Hit L Wedge " + str(shot_distance) + "yd. "
|
||||
distance_remaining = abs(distance_remaining - shot_distance)
|
||||
hole_shots += 1
|
||||
elif club == "caddy" or club.startswith("c"):
|
||||
# Show player the club distances
|
||||
msg += f"Caddy Guess:\nD:{hit_driver()} L:{hit_low_iron()} M:{hit_mid_iron()} H:{hit_high_iron()} G:{hit_gap_wedge()} W:{hit_lob_wedge()}"
|
||||
else:
|
||||
msg += "Didnt get your club 🥪♣️🪩 choice"
|
||||
return msg
|
||||
|
||||
if distance_remaining - pin_distance > pin_distance or shot_distance > pin_distance:
|
||||
# Check for over-shooting the hole
|
||||
if distance_remaining > 20:
|
||||
# did it go off the "green"?
|
||||
msg += "Overshot the green!🚀"
|
||||
if distance_remaining == 0:
|
||||
msg += "🎯Perfect shot! "
|
||||
last_cmd = 'putt'
|
||||
elif distance_remaining < 20:
|
||||
# Roll Dice
|
||||
hole_in_one_chance = random.randint(1, 100)
|
||||
wind_factor = random.randint(1, 10)
|
||||
skill_factor = random.randint(1, 10)
|
||||
critter_factor = random.randint(1, 50)
|
||||
|
||||
# Check for hole in one
|
||||
if hole_in_one_chance <= 5 and wind_factor > 7 and skill_factor > 8:
|
||||
distance_remaining = 0
|
||||
# Check for critters
|
||||
if skill_factor > 8 and critter_factor < 40 and wind_factor > 2 and hole_in_one_chance > 5:
|
||||
msg += random.choice(["A 🐿️ steals your ball!😡 ","You Hit a 🦅 soring past ", "🐊 need we say more? ", "hit a 🪟 of a 🏡 "])
|
||||
distance_remaining = -1
|
||||
# Handle hazard
|
||||
if hazard == "🌊" and skill_factor < 7:
|
||||
msg += "In the water!🌊"
|
||||
distance_remaining = -1
|
||||
if hazard == "🏖️" and skill_factor < 5:
|
||||
msg += "In the sand!🏖️"
|
||||
distance_remaining = random.randint(5, 10)
|
||||
if hazard == "🌲" and skill_factor < 3:
|
||||
msg += "In the trees!🌲"
|
||||
distance_remaining += random.randint(5, 20)
|
||||
if hazard == "🏘️" and skill_factor < 2:
|
||||
msg += "In the parking lot!🚗"
|
||||
distance_remaining += random.randint(10, 30)
|
||||
|
||||
# Check we didnt go off the green or into a hazard
|
||||
if distance_remaining < 20:
|
||||
last_cmd = 'putt'
|
||||
else:
|
||||
last_cmd = 'stroking'
|
||||
else:
|
||||
msg += "\nYou have " + str(distance_remaining) + "yd. ⛳️"
|
||||
msg += "\nClub?[D, L, M, H, G, W]🏌️"
|
||||
|
||||
# save player's current game state, keep stroking
|
||||
for i in range(len(golfTracker)):
|
||||
if golfTracker[i]['nodeID'] == nodeID:
|
||||
golfTracker[i]['distance_remaining'] = distance_remaining
|
||||
golfTracker[i]['hole_shots'] = hole_shots
|
||||
golfTracker[i]['total_strokes'] = total_strokes
|
||||
golfTracker[i]['cmd'] = 'stroking'
|
||||
|
||||
return msg
|
||||
|
||||
if last_cmd == 'putt':
|
||||
# Finish the hole by putting
|
||||
critter = False
|
||||
if distance_remaining < 20:
|
||||
if distance_remaining == 0:
|
||||
putts = 0
|
||||
elif distance_remaining == -1:
|
||||
putts = 0
|
||||
critter = True
|
||||
else:
|
||||
putts = finish_hole()
|
||||
|
||||
# Calculate hole and round scores
|
||||
hole_strokes = hole_shots + putts
|
||||
hole_to_par = hole_strokes - par
|
||||
total_strokes += hole_strokes
|
||||
total_to_par += hole_to_par
|
||||
|
||||
|
||||
if not critter:
|
||||
# Show player hole/round scoring info
|
||||
if putts == 0 and hole_strokes == 1:
|
||||
msg += "🎯Hole in one!⛳️"
|
||||
elif putts == 0:
|
||||
msg += "You're in the hole at " + str(hole_strokes) + " strokes!"
|
||||
else:
|
||||
msg += "You're on the green! After " + str(putts) + " putt(s), you're in for " + str(hole_strokes) + " strokes."
|
||||
msg += getScorecardGolf(hole_to_par)
|
||||
|
||||
if hole not in [1, 10]:
|
||||
# Show player total scoring info for the round, except hole 1 and 10
|
||||
msg += "\nYou've hit a total of " + str(total_strokes) + " strokes today, for"
|
||||
msg += getScorecardGolf(total_to_par)
|
||||
|
||||
# Move to next hole
|
||||
hole += 1
|
||||
else:
|
||||
msg += f"Got a new ball at Pro-Shop, marshal put you @" # flow into same hole haha
|
||||
|
||||
# Scorecard reset
|
||||
hole_to_par = 0
|
||||
total_to_par = 0
|
||||
hole_strokes = 0
|
||||
hole_shots = 0
|
||||
|
||||
# Save player's current game state
|
||||
for i in range(len(golfTracker)):
|
||||
if golfTracker[i]['nodeID'] == nodeID:
|
||||
golfTracker[i]['hole_strokes'] = hole_strokes
|
||||
golfTracker[i]['hole_to_par'] = hole_to_par
|
||||
golfTracker[i]['total_strokes'] = total_strokes
|
||||
golfTracker[i]['total_to_par'] = total_to_par
|
||||
golfTracker[i]['hole'] = hole
|
||||
golfTracker[i]['cmd'] = 'new'
|
||||
golfTracker[i]['last_played'] = time.time()
|
||||
|
||||
if hole >= 9:
|
||||
# Final score messages & exit prompt
|
||||
msg += f"🎉Finished 9-hole round⛳️"
|
||||
#HighScore Display
|
||||
highscore = getHighScoreGolf(nodeID, total_strokes, total_to_par)
|
||||
if highscore != 0:
|
||||
msg += "\n🏆New Club Record🏆"
|
||||
# pop player from tracker
|
||||
for i in range(len(golfTracker)):
|
||||
if golfTracker[i]['nodeID'] == nodeID:
|
||||
golfTracker.pop(i)
|
||||
logger.debug("System: GolfSim: Player " + str(nodeID) + " has finished their round.")
|
||||
else:
|
||||
# Show player the next hole
|
||||
msg += playGolf(nodeID, 'new', True)
|
||||
msg += "\n🏌️[D, L, M, H, G, W, End]🏌️"
|
||||
|
||||
return msg
|
||||
569
modules/lemonade.py
Normal file
569
modules/lemonade.py
Normal file
@@ -0,0 +1,569 @@
|
||||
# Port of https://github.com/tigerpointe/Lemonade-Stand/blob/main/lemonade.py MIT License Copyright (c) 2023 TigerPointe Software, LLC
|
||||
# Adapted for Meshtastic mesh-bot by K7MHI Kelly Keeton 2024
|
||||
|
||||
from collections import OrderedDict # ordered dictionaries
|
||||
from random import randrange, uniform # random numbers
|
||||
from types import SimpleNamespace # namespaces support
|
||||
import pickle # pickle file support
|
||||
import time # time functions
|
||||
from modules.log import * # mesh-bot logging
|
||||
|
||||
import locale # culture specific locale
|
||||
import math # math functions
|
||||
import re # regular expressions
|
||||
|
||||
# Set all of the locale category elements as default
|
||||
# ex. print(locale.currency(12345.67, grouping=True))
|
||||
locale.setlocale(locale.LC_ALL, '')
|
||||
lemon_starting_cash = 30.00
|
||||
lemon_total_weeks = 7
|
||||
|
||||
lemonadeTracker = [{'nodeID': 0, 'cups': 0, 'lemons': 0, 'sugar': 0, 'cash': lemon_starting_cash, 'start': lemon_starting_cash, 'cmd': 'new', 'time': time.time()}]
|
||||
lemonadeCups = [{'nodeID': 0, 'cost': 2.50, 'count': 25, 'min': 0.99, 'unit': 0.00}]
|
||||
lemonadeLemons = [{'nodeID': 0, 'cost': 4.00, 'count': 8, 'min': 2.00, 'unit': 0.00}]
|
||||
lemonadeSugar = [{'nodeID': 0, 'cost': 3.00, 'count': 15, 'min': 1.50, 'unit': 0.00}]
|
||||
lemonadeWeeks = [{'nodeID': 0, 'current': 1, 'total': lemon_total_weeks, 'sales': 99, 'potential': 0, 'unit': 0.00, 'price': 0.00}]
|
||||
lemonadeScore = [{'nodeID': 0, 'value': 0.00, 'total': 0.00}]
|
||||
|
||||
def get_sales_amount(potential, unit, price):
|
||||
"""Gets the sales amount.
|
||||
Multiply the potential sales by a ratio of unit cost to actual price; the
|
||||
exponent results in the values falling along a curve, rather than along a
|
||||
straight line, resulting in more realistic sales values at each price.
|
||||
Parameters
|
||||
potential : Potential sales
|
||||
unit : Unit cost
|
||||
price : Actual price
|
||||
"""
|
||||
return math.floor(potential * (unit / (price ** 1.5)))
|
||||
|
||||
def getHighScoreLemon():
|
||||
high_score = {"userID": 0, "cash": 0, "success": 0}
|
||||
# Load high score table
|
||||
try:
|
||||
with open('lemonade_hs.pkl', 'rb') as file:
|
||||
high_score = pickle.load(file)
|
||||
except FileNotFoundError:
|
||||
logger.debug("System: Lemonade: No high score table found")
|
||||
# write a new high score file if one is not found
|
||||
with open('lemonade_hs.pkl', 'wb') as file:
|
||||
pickle.dump(high_score, file)
|
||||
return high_score
|
||||
|
||||
def start_lemonade(nodeID, message, celsius=False):
|
||||
global lemonadeTracker, lemonadeCups, lemonadeLemons, lemonadeSugar, lemonadeWeeks, lemonadeScore
|
||||
potential = 0
|
||||
unit = 0.0
|
||||
price = 0.0
|
||||
|
||||
high_score = getHighScoreLemon()
|
||||
|
||||
def saveValues():
|
||||
# save playerDB values
|
||||
for i in range(len(lemonadeTracker)):
|
||||
if lemonadeTracker[i]['nodeID'] == nodeID:
|
||||
lemonadeTracker[i]['cups'] = inventory.cups
|
||||
lemonadeTracker[i]['lemons'] = inventory.lemons
|
||||
lemonadeTracker[i]['sugar'] = inventory.sugar
|
||||
lemonadeTracker[i]['cash'] = inventory.cash
|
||||
lemonadeTracker[i]['start'] = inventory.start
|
||||
for i in range(len(lemonadeCups)):
|
||||
if lemonadeCups[i]['nodeID'] == nodeID:
|
||||
lemonadeCups[i]['cost'] = cups.cost
|
||||
lemonadeCups[i]['unit'] = cups.unit
|
||||
for i in range(len(lemonadeLemons)):
|
||||
if lemonadeLemons[i]['nodeID'] == nodeID:
|
||||
lemonadeLemons[i]['cost'] = lemons.cost
|
||||
lemonadeLemons[i]['unit'] = lemons.unit
|
||||
for i in range(len(lemonadeSugar)):
|
||||
if lemonadeSugar[i]['nodeID'] == nodeID:
|
||||
lemonadeSugar[i]['cost'] = sugar.cost
|
||||
lemonadeSugar[i]['unit'] = sugar.unit
|
||||
for i in range(len(lemonadeWeeks)):
|
||||
if lemonadeWeeks[i]['nodeID'] == nodeID:
|
||||
lemonadeWeeks[i]['current'] = weeks.current
|
||||
lemonadeWeeks[i]['total'] = weeks.total
|
||||
lemonadeWeeks[i]['sales'] = weeks.sales
|
||||
lemonadeWeeks[i]['potential'] = potential
|
||||
lemonadeWeeks[i]['unit'] = unit
|
||||
lemonadeWeeks[i]['price'] = price
|
||||
for i in range(len(lemonadeScore)):
|
||||
if lemonadeScore[i]['nodeID'] == nodeID:
|
||||
lemonadeScore[i]['value'] = score.value
|
||||
lemonadeScore[i]['total'] = score.total
|
||||
|
||||
def endGame(nodeID):
|
||||
# remove the player from the tracker
|
||||
for i in range(len(lemonadeTracker)):
|
||||
if lemonadeTracker[i]['nodeID'] == nodeID:
|
||||
lemonadeTracker.pop(i)
|
||||
for i in range(len(lemonadeCups)):
|
||||
if lemonadeCups[i]['nodeID'] == nodeID:
|
||||
lemonadeCups.pop(i)
|
||||
for i in range(len(lemonadeLemons)):
|
||||
if lemonadeLemons[i]['nodeID'] == nodeID:
|
||||
lemonadeLemons.pop(i)
|
||||
for i in range(len(lemonadeSugar)):
|
||||
if lemonadeSugar[i]['nodeID'] == nodeID:
|
||||
lemonadeSugar.pop(i)
|
||||
for i in range(len(lemonadeWeeks)):
|
||||
if lemonadeWeeks[i]['nodeID'] == nodeID:
|
||||
lemonadeWeeks.pop(i)
|
||||
for i in range(len(lemonadeScore)):
|
||||
if lemonadeScore[i]['nodeID'] == nodeID:
|
||||
lemonadeScore.pop(i)
|
||||
logger.debug("System: Lemonade: Game Over for " + str(nodeID))
|
||||
|
||||
# Check for end of game
|
||||
if "end" in message.lower():
|
||||
endGame(nodeID)
|
||||
return "Goodbye!👋"
|
||||
|
||||
title="LemonStand🍋"
|
||||
# Define the temperature unit symbols
|
||||
fahrenheit_unit = "ºF"
|
||||
celsius_unit = "ºC"
|
||||
|
||||
# Inventory data (contains the item levels)
|
||||
inventoryd = {
|
||||
'cups' : 0,
|
||||
'lemons' : 0,
|
||||
'sugar' : 0,
|
||||
'cash' : lemon_starting_cash,
|
||||
'start' : lemon_starting_cash
|
||||
}
|
||||
inventory = SimpleNamespace(**inventoryd)
|
||||
|
||||
# Cups data (includes a calculated cost per unit)
|
||||
cupsd = {
|
||||
'cost' : 2.50, # current price
|
||||
'count' : 25, # servings per box
|
||||
'min' : 0.99, # minimum price
|
||||
'unit' : 0.00 # unit price
|
||||
}
|
||||
cups = SimpleNamespace(**cupsd)
|
||||
cups.unit = round(cups.cost / cups.count, 2)
|
||||
|
||||
# Lemons data (includes a calculated cost per unit)
|
||||
lemonsd = {
|
||||
'cost' : 4.00, # current price
|
||||
'count' : 8, # servings per bag
|
||||
'min' : 2.00, # minimum price
|
||||
'unit' : 0.00 # unit price
|
||||
}
|
||||
lemons = SimpleNamespace(**lemonsd)
|
||||
lemons.unit = round(lemons.cost / lemons.count, 2)
|
||||
|
||||
# Sugar data (includes a calculated cost per unit)
|
||||
sugard = {
|
||||
'cost' : 3.00, # current price
|
||||
'count' : 15, # servings per bag
|
||||
'min' : 1.50, # minimum price
|
||||
'unit' : 0.00 # unit price
|
||||
}
|
||||
sugar = SimpleNamespace(**sugard)
|
||||
sugar.unit = round(sugar.cost / sugar.count, 2)
|
||||
|
||||
# Weeks data (measures the session duration)
|
||||
weeksd = {
|
||||
'current' : 1, # start with the 1st week
|
||||
'total' : 12, # span the 12 weeks of Summer
|
||||
'sales' : 99, # 99 maximum sales per week
|
||||
'summary' : [] # empty array
|
||||
}
|
||||
weeks = SimpleNamespace(**weeksd)
|
||||
|
||||
# Forecast data (includes percentage values, UTF8 glyphs and display names)
|
||||
forecastd = OrderedDict()
|
||||
forecastd['sunny'] = [1.00, 0x2600, "Sunny"]
|
||||
forecastd['partly'] = [0.90, 0x26C5, "Partly Sunny"]
|
||||
forecastd['cloudy'] = [0.70, 0x2601, "Mostly Cloudy"]
|
||||
forecastd['rainy'] = [0.40, 0x2602, "Rainy"]
|
||||
forecastd['stormy'] = [0.10, 0x26C8, "Stormy"]
|
||||
|
||||
# Temperature data (uses Fahrenheit as the percentage values)
|
||||
temperatured = {
|
||||
'min' : 69,
|
||||
'max' : 100,
|
||||
'units' : fahrenheit_unit,
|
||||
'forecast' : None,
|
||||
'value' : None
|
||||
}
|
||||
temperature = SimpleNamespace(**temperatured)
|
||||
|
||||
# Score data (based on actual vs. maximum net sales)
|
||||
scored = {
|
||||
'value' : 0.00,
|
||||
'total' : 0.00
|
||||
}
|
||||
score = SimpleNamespace(**scored)
|
||||
|
||||
# Check for Celsius
|
||||
if (celsius):
|
||||
temperature.units = celsius_unit
|
||||
|
||||
# load playerDB values
|
||||
for i in range(len(lemonadeTracker)):
|
||||
if lemonadeTracker[i]['nodeID'] == nodeID:
|
||||
inventory.cups = lemonadeTracker[i]['cups']
|
||||
inventory.lemons = lemonadeTracker[i]['lemons']
|
||||
inventory.sugar = lemonadeTracker[i]['sugar']
|
||||
inventory.cash = lemonadeTracker[i]['cash']
|
||||
inventory.start = lemonadeTracker[i]['start']
|
||||
last_cmd = lemonadeTracker[i]['cmd']
|
||||
for i in range(len(lemonadeCups)):
|
||||
if lemonadeCups[i]['nodeID'] == nodeID:
|
||||
cups.cost = lemonadeCups[i]['cost']
|
||||
cups.unit = lemonadeCups[i]['unit']
|
||||
for i in range(len(lemonadeLemons)):
|
||||
if lemonadeLemons[i]['nodeID'] == nodeID:
|
||||
lemons.cost = lemonadeLemons[i]['cost']
|
||||
lemons.unit = lemonadeLemons[i]['unit']
|
||||
for i in range(len(lemonadeSugar)):
|
||||
if lemonadeSugar[i]['nodeID'] == nodeID:
|
||||
sugar.cost = lemonadeSugar[i]['cost']
|
||||
sugar.unit = lemonadeSugar[i]['unit']
|
||||
for i in range(len(lemonadeWeeks)):
|
||||
if lemonadeWeeks[i]['nodeID'] == nodeID:
|
||||
weeks.current = lemonadeWeeks[i]['current']
|
||||
weeks.total = lemonadeWeeks[i]['total']
|
||||
weeks.sales = lemonadeWeeks[i]['sales']
|
||||
potential = lemonadeWeeks[i]['potential']
|
||||
unit = lemonadeWeeks[i]['unit']
|
||||
price = lemonadeWeeks[i]['price']
|
||||
for i in range(len(lemonadeScore)):
|
||||
if lemonadeScore[i]['nodeID'] == nodeID:
|
||||
score.value = lemonadeScore[i]['value']
|
||||
score.total = lemonadeScore[i]['total']
|
||||
|
||||
logger.debug("System: Lemonade: Last Command: " + last_cmd)
|
||||
|
||||
# Start the main loop
|
||||
if (weeks.current <= weeks.total):
|
||||
|
||||
if "new" in last_cmd:
|
||||
# set the last command to cups in the inventory db
|
||||
for i in range(len(lemonadeTracker)):
|
||||
if lemonadeTracker[i]['nodeID'] == nodeID:
|
||||
lemonadeTracker[i]['cmd'] = "cups"
|
||||
# Create a new display buffer for the text messages
|
||||
buffer= ""
|
||||
|
||||
# the current week number
|
||||
buffer += title + "Week #" + str(weeks.current) + "of" + str(weeks.total)
|
||||
|
||||
# Generate a random weather forecast and temperature and display
|
||||
temperature.forecast = randrange(0, len(forecastd))
|
||||
temperature.value = randrange(temperature.min, temperature.max)
|
||||
formatted = str(temperature.value)
|
||||
if (temperature.units == celsius_unit):
|
||||
formatted = str(round(((temperature.value - 32) * (5/9))))
|
||||
glyph = chr(forecastd[list(forecastd)[temperature.forecast]][1])
|
||||
buffer += ". " + \
|
||||
formatted + temperature.units + " " + \
|
||||
forecastd[list(forecastd)[temperature.forecast]][2] + \
|
||||
" " + glyph
|
||||
|
||||
# Calculate the potential sales as a percentage of the maximum value
|
||||
# (lower temperature = fewer sales, severe weather = fewer sales)
|
||||
forecast = forecastd[list(forecastd)[temperature.forecast]][0]
|
||||
potential = math.floor(weeks.sales * \
|
||||
(temperature.value / 100) * \
|
||||
forecast)
|
||||
|
||||
# Update the cups cost
|
||||
cups.cost = cups.cost + round(uniform(-1.50, 1.50), 2)
|
||||
if (cups.cost < cups.min):
|
||||
cups.cost = cups.min
|
||||
cups.unit = round(cups.cost / cups.count, 2)
|
||||
|
||||
# Update the lemons cost
|
||||
lemons.cost = lemons.cost + round(uniform(-1.50, 1.50), 2)
|
||||
if (lemons.cost < lemons.min):
|
||||
lemons.cost = lemons.min
|
||||
lemons.unit = round(lemons.cost / lemons.count, 2)
|
||||
|
||||
# Update the sugar cost
|
||||
sugar.cost = sugar.cost + round(uniform(-1.50, 1.50), 2)
|
||||
if (sugar.cost < sugar.min):
|
||||
sugar.cost = sugar.min
|
||||
sugar.unit = round(sugar.cost / sugar.count, 2)
|
||||
|
||||
# Calculate the unit cost and display the estimated sales from the forecast potential
|
||||
unit = cups.unit + lemons.unit + sugar.unit
|
||||
buffer += " SupplyCost" + locale.currency(unit, grouping=True) + " a cup."
|
||||
buffer += " Sales Potential:" + str(potential) + " cups."
|
||||
|
||||
# Display the current inventory
|
||||
buffer += " Inventory:"
|
||||
buffer += "🥤:" + str(inventory.cups)
|
||||
buffer += "🍋:" + str(inventory.lemons)
|
||||
buffer += "🍚:" + str(inventory.sugar)
|
||||
|
||||
# Display the updated item prices
|
||||
buffer += f"\nPrices: "
|
||||
buffer += "🥤:" + \
|
||||
locale.currency(cups.cost, grouping=True) + " 📦 of " + str(cups.count) + "."
|
||||
buffer += " 🍋:" + \
|
||||
locale.currency(lemons.cost, grouping=True) + " 🧺 of " + str(lemons.count) + "."
|
||||
buffer += " 🍚:" + \
|
||||
locale.currency(sugar.cost, grouping=True) + " bag for " + str(sugar.count) + "🥤."
|
||||
|
||||
# Display the current cash
|
||||
gainloss = inventory.cash - inventory.start
|
||||
buffer += " 💵:" + \
|
||||
locale.currency(inventory.cash, grouping=True)
|
||||
|
||||
|
||||
# if the player is in the red
|
||||
pnl = locale.currency(gainloss, grouping=True)
|
||||
if "0.00" not in pnl:
|
||||
if pnl.startswith("-"):
|
||||
buffer += "📊P&L📉" + pnl
|
||||
else:
|
||||
buffer += "📊P&L📈" + pnl
|
||||
|
||||
buffer += f"\n🥤 to buy? Have {inventory.cups} Cost {locale.currency(cups.cost, grouping=True)} a 📦 of {str(cups.count)}"
|
||||
saveValues()
|
||||
return buffer
|
||||
|
||||
if "cups" in last_cmd:
|
||||
# Read the number of cup boxes to purchase
|
||||
newcups = -1
|
||||
if "n" in message.lower():
|
||||
message = "0"
|
||||
try:
|
||||
newcups = int(message)
|
||||
if (newcups > 0):
|
||||
cost = round(newcups * cups.cost, 2)
|
||||
if (cost > inventory.cash):
|
||||
return "You do not have enough 💵."
|
||||
inventory.cups += (newcups * cups.count)
|
||||
inventory.cash -= cost
|
||||
msg = "Purchased " + str(newcups) + " 📦 "
|
||||
msg += str(inventory.cups) + " 🥤 in inventory. " + locale.currency(inventory.cash, grouping=True) + f" remaining"
|
||||
else:
|
||||
msg = "No 🥤 were purchased"
|
||||
except Exception as e:
|
||||
return "invalid input, enter the number of 🥤 to purchase or (N)one"
|
||||
|
||||
# set the last command to lemons in the inventory db
|
||||
for i in range(len(lemonadeTracker)):
|
||||
if lemonadeTracker[i]['nodeID'] == nodeID:
|
||||
lemonadeTracker[i]['cmd'] = "lemons"
|
||||
saveValues()
|
||||
msg += f"\n 🍋 to buy? Have {inventory.lemons}🥤 of 🍋 Cost {locale.currency(lemons.cost, grouping=True)} a 🧺 for {str(lemons.count)}🥤"
|
||||
return msg
|
||||
|
||||
|
||||
if "lemons" in last_cmd:
|
||||
# Read the number of lemon bags to purchase
|
||||
newlemons = -1
|
||||
if "n" in message.lower():
|
||||
message = "0"
|
||||
try:
|
||||
newlemons = int(message)
|
||||
if (newlemons > 0):
|
||||
cost = round(newlemons * lemons.cost, 2)
|
||||
if (cost > inventory.cash):
|
||||
return "You do not have enough cash."
|
||||
inventory.lemons += (newlemons * lemons.count)
|
||||
inventory.cash -= cost
|
||||
msg = "Purchased " + str(newlemons) + " 🧺 "
|
||||
msg += str(inventory.lemons) + " 🍋 in inventory. " + locale.currency(inventory.cash, grouping=True) + f" remaining"
|
||||
else:
|
||||
msg = "No 🍋 were purchased"
|
||||
except Exception as e:
|
||||
newlemons = -1
|
||||
return "⛔️invalid input, enter the number of 🍋 to purchase"
|
||||
|
||||
# set the last command to sugar in the inventory db
|
||||
for i in range(len(lemonadeTracker)):
|
||||
if lemonadeTracker[i]['nodeID'] == nodeID:
|
||||
lemonadeTracker[i]['cmd'] = "sugar"
|
||||
saveValues()
|
||||
msg += f"\n 🍚 to buy? You have {inventory.sugar}🥤 of 🍚, Cost {locale.currency(sugar.cost, grouping=True)} a bag for {str(sugar.count)}🥤"
|
||||
return msg
|
||||
|
||||
if "sugar" in last_cmd:
|
||||
# Read the number of sugar bags to purchase
|
||||
newsugar = -1
|
||||
if "n" in message.lower():
|
||||
message = "0"
|
||||
try:
|
||||
newsugar = int(message)
|
||||
if (newsugar > 0):
|
||||
cost = round(newsugar * sugar.cost, 2)
|
||||
if (cost > inventory.cash):
|
||||
return "You do not have enough cash."
|
||||
inventory.sugar += (newsugar * sugar.count)
|
||||
inventory.cash -= cost
|
||||
msg = " Purchased " + str(newsugar) + " bag(s) of 🍚 for " + locale.currency(cost, grouping=True)
|
||||
msg += ". " + str(inventory.sugar) + f"🥤🍚 in inventory."
|
||||
else:
|
||||
msg = "No additional 🍚 was purchased"
|
||||
except Exception as e:
|
||||
return "⛔️invalid input, enter the number of 🍚 bags to purchase"
|
||||
|
||||
msg += f"Cost of goods is {locale.currency(unit, grouping=True)}"
|
||||
msg += f"per 🥤 {locale.currency(inventory.cash, grouping=True)} 💵 remaining."
|
||||
msg += f"\nPrice to Sell? or (G)rocery to buy more 🥤🍋🍚"
|
||||
|
||||
# set the last command to price in the inventory db
|
||||
for i in range(len(lemonadeTracker)):
|
||||
if lemonadeTracker[i]['nodeID'] == nodeID:
|
||||
lemonadeTracker[i]['cmd'] = "price"
|
||||
saveValues()
|
||||
return msg
|
||||
|
||||
if "price" in last_cmd:
|
||||
# set the last command to sales in the inventory db
|
||||
for i in range(len(lemonadeTracker)):
|
||||
if lemonadeTracker[i]['nodeID'] == nodeID:
|
||||
lemonadeTracker[i]['cmd'] = "sales"
|
||||
if "g" in message.lower():
|
||||
lemonadeTracker[i]['cmd'] = "cups"
|
||||
msg = f"#of🥤 to buy? Have {inventory.cups} Cost {locale.currency(cups.cost, grouping=True)} a 📦 of {str(cups.count)}"
|
||||
return msg
|
||||
else:
|
||||
last_cmd = "sales"
|
||||
|
||||
# Read the actual price
|
||||
price = 0.00
|
||||
while (price <= 0.00):
|
||||
try:
|
||||
raw = message
|
||||
price = float(re.sub("[^0-9.-]", "", raw) or 0.00)
|
||||
if (price <= 0.00):
|
||||
return "The price must be greater than zero."
|
||||
except Exception as e:
|
||||
price = 0.00
|
||||
return "⛔️Invalid input, enter the price of the lemonade per 🥤"
|
||||
|
||||
# this isnt sent to the user, not needed
|
||||
#msg = " Setting the price at " + locale.currency(price, grouping=True)
|
||||
saveValues()
|
||||
|
||||
|
||||
if "sales" in last_cmd:
|
||||
# Calculate the weekly sales based on price and lowest inventory level
|
||||
# (higher markup price = fewer sales, limited by the inventory on-hand)
|
||||
sales = get_sales_amount(potential, unit, price)
|
||||
sales = min(potential, sales, \
|
||||
inventory.cups, inventory.lemons, \
|
||||
inventory.sugar) # "min" returns lowest value
|
||||
margin = price - unit
|
||||
gross = sales * price
|
||||
net = sales * margin
|
||||
|
||||
# Add a new row to the summary
|
||||
weeks.summary.append({ 'sales' : sales, 'price' : price })
|
||||
|
||||
# Update the inventory levels to reflect consumption
|
||||
inventory.cups = inventory.cups - sales
|
||||
inventory.lemons = inventory.lemons - sales
|
||||
inventory.sugar = inventory.sugar - sales
|
||||
inventory.cash = inventory.cash + gross
|
||||
gainloss= inventory.cash - inventory.start
|
||||
|
||||
# Display the calculated sales information
|
||||
msg = "Results Week📊#" + str(weeks.current) + "of" + str(weeks.total)
|
||||
msg += " Cost/Price:" + locale.currency(unit, grouping=True) + "/" + locale.currency(price, grouping=True)
|
||||
msg += " P.Margin:" + locale.currency(margin, grouping=True)
|
||||
msg += " T.Sales:" + str(sales) + "@" + locale.currency(price, grouping=True)
|
||||
msg += " G.Profit: " + locale.currency(gross, grouping=True)
|
||||
msg += " N.Profit:" + locale.currency(net, grouping=True)
|
||||
|
||||
# Display the updated inventory levels
|
||||
msg += "\nRemaining"
|
||||
msg += " 🥤:" + str(inventory.cups)
|
||||
msg += " 🍋:" + str(inventory.lemons)
|
||||
msg += " 🍚:" + str(inventory.sugar)
|
||||
msg += " 💵:" + locale.currency(inventory.cash, grouping=True)
|
||||
# Display the gain/loss
|
||||
pnl = locale.currency(gainloss, grouping=True)
|
||||
if "0.00" not in pnl:
|
||||
if pnl.startswith("-"):
|
||||
msg += "📊P&L📉" + pnl
|
||||
else:
|
||||
msg += "📊P&L📈" + pnl
|
||||
|
||||
# Display the weekly sales summary
|
||||
pad_week = len(str(weeks.total))
|
||||
pad_sale = len(str(weeks.sales))
|
||||
total = 0
|
||||
msg += "\nWeekly📊"
|
||||
for i in range(len(weeks.summary)):
|
||||
msg += "#" + str(weeks.current).rjust(pad_week) + ". " + str(weeks.summary[i]['sales']).rjust(pad_sale) + \
|
||||
" sold x " + locale.currency(weeks.summary[i]['price'], grouping=True) + "ea. "
|
||||
total = total + weeks.summary[i]['sales']
|
||||
|
||||
# Loop through a range of prices to find the highest net profit
|
||||
maxsales = 0
|
||||
maxprice = 0.00
|
||||
maxgross = 0.00
|
||||
maxnet = 0.00
|
||||
minnet = net
|
||||
for i in range(25, 2500, 25):
|
||||
price = i / 100 # range uses integers, not currency (floats)
|
||||
sales = get_sales_amount(potential, unit, price)
|
||||
margin = price - unit
|
||||
gross = sales * price
|
||||
net = sales * margin
|
||||
if (sales > 0) and \
|
||||
(sales <= potential) and \
|
||||
(unit <= price):
|
||||
if (net > maxnet):
|
||||
maxsales = sales
|
||||
maxprice = price
|
||||
maxgross = gross
|
||||
maxnet = net
|
||||
if (maxnet > minnet):
|
||||
msg += "Sales could have been:"
|
||||
msg += " " + str(maxsales) + " sold x " + locale.currency(maxprice, grouping=True) + "ea. @" + \
|
||||
locale.currency(maxgross, grouping=True) + " for a net profit of " + locale.currency(maxnet, grouping=True)
|
||||
if (inventory.cups <= 0):
|
||||
msg += " You ran out of cups.🥤"
|
||||
if (inventory.lemons <= 0):
|
||||
msg += " You ran out of lemons.🍋"
|
||||
if (inventory.sugar <= 0):
|
||||
msg += " You ran out of sugar.🍚"
|
||||
else:
|
||||
msg += "\nCongratulations 🍋🍋 your sales were perfect!🎉"
|
||||
|
||||
# Increment the score counters
|
||||
score.value = score.value + minnet
|
||||
score.total = score.total + maxnet
|
||||
|
||||
# Increment the week number
|
||||
if (weeks.current == weeks.total):
|
||||
# end of the game
|
||||
success = round((score.value / score.total) * 100)
|
||||
msg += "\nYou've made " + locale.currency(score.value, grouping=True) + " out of a possible " + \
|
||||
locale.currency(score.total, grouping=True) + " for a score of " + str(success) + "% "
|
||||
msg += "You've sold " + str(total) + " total 🥤🍋"
|
||||
|
||||
# check for high score
|
||||
high_score = getHighScoreLemon()
|
||||
if (inventory.cash > int(high_score['cash'])):
|
||||
msg += "\nCongratulations! You've set a new high score!🎉💰🍋"
|
||||
high_score['cash'] = inventory.cash
|
||||
high_score['success'] = success
|
||||
high_score['userID'] = nodeID
|
||||
with open('lemonade_hs.pkl', 'wb') as file:
|
||||
pickle.dump(high_score, file)
|
||||
endGame(nodeID)
|
||||
|
||||
else:
|
||||
# keep playing
|
||||
# set the last command to new in the inventory db
|
||||
for i in range(len(lemonadeTracker)):
|
||||
if lemonadeTracker[i]['nodeID'] == nodeID:
|
||||
lemonadeTracker[i]['cmd'] = "new"
|
||||
|
||||
weeks.current = weeks.current + 1
|
||||
|
||||
msg += f"Play another week🥤? 'end' to end game"
|
||||
|
||||
saveValues()
|
||||
return msg
|
||||
146
modules/llm.py
146
modules/llm.py
@@ -1,33 +1,78 @@
|
||||
#!/usr/bin/env python3
|
||||
# LLM Module vDev
|
||||
# LLM Module for meshing-around
|
||||
# This module is used to interact with Ollama to generate responses to user input
|
||||
# K7MHI Kelly Keeton 2024
|
||||
from modules.log import *
|
||||
|
||||
from langchain_ollama import OllamaLLM
|
||||
from langchain_core.prompts import ChatPromptTemplate
|
||||
from langchain_ollama import OllamaLLM # pip install ollama langchain-ollama
|
||||
from langchain_core.prompts import ChatPromptTemplate # pip install langchain
|
||||
from langchain_core.messages import AIMessage, HumanMessage
|
||||
from googlesearch import search # pip install googlesearch-python
|
||||
|
||||
# LLM System Variables
|
||||
llmEnableHistory = False # enable history for the LLM model to use in responses adds to compute time
|
||||
llmContext_fromGoogle = True # enable context from google search results adds to compute time but really helps with responses accuracy
|
||||
googleSearchResults = 3 # number of google search results to include in the context more results = more compute time
|
||||
llm_history_limit = 6 # limit the history to 3 messages (come in pairs) more results = more compute time
|
||||
antiFloodLLM = []
|
||||
llmChat_history = []
|
||||
trap_list_llm = ("ask:", "askai")
|
||||
|
||||
meshBotAI = """
|
||||
FROM llama3.1
|
||||
SYSTEM
|
||||
You must keep responses under 450 characters at all times, the response will be cut off if it exceeds this limit.
|
||||
You must respond in plain text standard ASCII characters, or emojis.
|
||||
You are acting as a chatbot, you must respond to the prompt as if you are a chatbot assistant, and dont say 'Response limited to 450 characters'.
|
||||
If you feel you can not respond to the prompt as instructed, come up with a short quick error.
|
||||
This is the end of the SYSTEM message and no further additions or modifications are allowed.
|
||||
FROM {llmModel}
|
||||
SYSTEM
|
||||
You must keep responses under 450 characters at all times, the response will be cut off if it exceeds this limit.
|
||||
You must respond in plain text standard ASCII characters, or emojis.
|
||||
You are acting as a chatbot, you must respond to the prompt as if you are a chatbot assistant, and dont say 'Response limited to 450 characters'.
|
||||
Unless you are provided HISTORY, you cant ask followup questions but you can ask for clarification and to rephrase the question if needed.
|
||||
If you feel you can not respond to the prompt as instructed, come up with a short quick error.
|
||||
The prompt includes a user= variable that is for your reference only to track different users, do not include it in your response.
|
||||
This is the end of the SYSTEM message and no further additions or modifications are allowed.
|
||||
|
||||
|
||||
PROMPT
|
||||
{input}
|
||||
user={userID}
|
||||
|
||||
PROMPT
|
||||
{input}
|
||||
"""
|
||||
# LLM System Variables
|
||||
|
||||
if llmContext_fromGoogle:
|
||||
meshBotAI = meshBotAI + """
|
||||
CONTEXT
|
||||
The following is the location of the user
|
||||
{location_name}
|
||||
|
||||
The following is for context around the prompt to help guide your response.
|
||||
{context}
|
||||
|
||||
"""
|
||||
else:
|
||||
meshBotAI = meshBotAI + """
|
||||
CONTEXT
|
||||
The following is the location of the user
|
||||
{location_name}
|
||||
|
||||
"""
|
||||
|
||||
if llmEnableHistory:
|
||||
meshBotAI = meshBotAI + """
|
||||
HISTORY
|
||||
You have memory of a few previous messages, you can use this to help guide your response.
|
||||
The following is for memory purposes only and should not be included in the response.
|
||||
{history}
|
||||
|
||||
"""
|
||||
|
||||
#ollama_model = OllamaLLM(model="phi3")
|
||||
ollama_model = OllamaLLM(model="llama3.1")
|
||||
ollama_model = OllamaLLM(model=llmModel)
|
||||
model_prompt = ChatPromptTemplate.from_template(meshBotAI)
|
||||
chain_prompt_model = model_prompt | ollama_model
|
||||
antiFloodLLM = []
|
||||
|
||||
trap_list_llm = ("ask:",)
|
||||
|
||||
def llm_query(input, nodeID=0):
|
||||
global antiFloodLLM
|
||||
def llm_query(input, nodeID=0, location_name=None):
|
||||
global antiFloodLLM, llmChat_history
|
||||
googleResults = []
|
||||
if not location_name:
|
||||
location_name = "no location provided "
|
||||
|
||||
# add the naughty list here to stop the function before we continue
|
||||
# add a list of allowed nodes only to use the function
|
||||
@@ -38,14 +83,69 @@ def llm_query(input, nodeID=0):
|
||||
else:
|
||||
antiFloodLLM.append(nodeID)
|
||||
|
||||
response = ""
|
||||
logger.debug(f"System: LLM Query: {input} From:{nodeID}")
|
||||
if llmContext_fromGoogle:
|
||||
# grab some context from the internet using google search hits (if available)
|
||||
# localization details at https://pypi.org/project/googlesearch-python/
|
||||
|
||||
# remove common words from the search query
|
||||
# commonWordsList = ["is", "for", "the", "of", "and", "in", "on", "at", "to", "with", "by", "from", "as", "a", "an", "that", "this", "these", "those", "there", "here", "where", "when", "why", "how", "what", "which", "who", "whom", "whose", "whom"]
|
||||
# sanitizedSearch = ' '.join([word for word in input.split() if word.lower() not in commonWordsList])
|
||||
|
||||
try:
|
||||
googleSearch = search(input, advanced=True, num_results=googleSearchResults)
|
||||
if googleSearch:
|
||||
for result in googleSearch:
|
||||
# SearchResult object has url= title= description= just grab title and description
|
||||
googleResults.append(f"{result.title} {result.description}")
|
||||
else:
|
||||
googleResults = ['no other context provided']
|
||||
except Exception as e:
|
||||
logger.debug(f"System: LLM Query: context gathering failed, likely due to network issues")
|
||||
googleResults = ['no other context provided']
|
||||
|
||||
|
||||
if googleResults:
|
||||
logger.debug(f"System: LLM Query: {input} From:{nodeID} with context from google")
|
||||
else:
|
||||
logger.debug(f"System: LLM Query: {input} From:{nodeID}")
|
||||
|
||||
response = ""
|
||||
result = ""
|
||||
location_name += f" at the current time of {datetime.now().strftime('%Y-%m-%d %H:%M:%S %Z')}"
|
||||
|
||||
try:
|
||||
result = chain_prompt_model.invoke({"input": input, "llmModel": llmModel, "userID": nodeID, \
|
||||
"history": llmChat_history, "context": googleResults, "location_name": location_name})
|
||||
#logger.debug(f"System: LLM Response: " + result.strip().replace('\n', ' '))
|
||||
except Exception as e:
|
||||
logger.warning(f"System: LLM failure: {e}")
|
||||
return "I am having trouble processing your request, please try again later."
|
||||
|
||||
|
||||
result = chain_prompt_model.invoke({"input": input})
|
||||
#logger.debug(f"System: LLM Response: " + result.strip().replace('\n', ' '))
|
||||
response = result.strip().replace('\n', ' ')
|
||||
|
||||
# Store history of the conversation, with limit to prevent template growing too large causing speed issues
|
||||
if len(llmChat_history) > llm_history_limit:
|
||||
# remove the oldest two messages
|
||||
llmChat_history.pop(0)
|
||||
llmChat_history.pop(1)
|
||||
inputWithUserID = input + f" user={nodeID}"
|
||||
llmChat_history.append(HumanMessage(content=inputWithUserID))
|
||||
llmChat_history.append(AIMessage(content=response))
|
||||
|
||||
# done with the query, remove the user from the anti flood list
|
||||
antiFloodLLM.remove(nodeID)
|
||||
|
||||
return response
|
||||
|
||||
# import subprocess
|
||||
# def get_ollama_cpu():
|
||||
# try:
|
||||
# psOutput = subprocess.run(['ollama', 'ps'], capture_output=True, text=True)
|
||||
# if "GPU" in psOutput.stdout:
|
||||
# logger.debug(f"System: Ollama process with GPU")
|
||||
# else:
|
||||
# logger.debug(f"System: Ollama process with CPU, query time will be slower")
|
||||
# except Exception as e:
|
||||
# logger.debug(f"System: Ollama process not found, {e}")
|
||||
# return False
|
||||
|
||||
@@ -11,7 +11,7 @@ from modules.log import *
|
||||
|
||||
trap_list_location = ("whereami", "tide", "moon", "wx", "wxc", "wxa", "wxalert")
|
||||
|
||||
def where_am_i(lat=0, lon=0):
|
||||
def where_am_i(lat=0, lon=0, short=False):
|
||||
whereIam = ""
|
||||
grid = mh.to_maiden(float(lat), float(lon))
|
||||
|
||||
@@ -22,22 +22,33 @@ def where_am_i(lat=0, lon=0):
|
||||
# initialize Nominatim API
|
||||
geolocator = Nominatim(user_agent="mesh-bot")
|
||||
|
||||
# Nomatim API call to get address
|
||||
if float(lat) == latitudeValue and float(lon) == longitudeValue:
|
||||
# redacted address when no GPS and using default location
|
||||
location = geolocator.reverse(lat + ", " + lon)
|
||||
address = location.raw['address']
|
||||
address_components = ['city', 'state', 'postcode', 'county', 'country']
|
||||
whereIam += ' '.join([address.get(component, '') for component in address_components if component in address])
|
||||
whereIam += " Grid: " + grid
|
||||
return whereIam
|
||||
else:
|
||||
location = geolocator.reverse(lat + ", " + lon)
|
||||
address = location.raw['address']
|
||||
address_components = ['house_number', 'road', 'city', 'state', 'postcode', 'county', 'country']
|
||||
whereIam += ' '.join([address.get(component, '') for component in address_components if component in address])
|
||||
whereIam += " Grid: " + grid
|
||||
try:
|
||||
# Nomatim API call to get address
|
||||
if short:
|
||||
location = geolocator.reverse(lat + ", " + lon)
|
||||
address = location.raw['address']
|
||||
address_components = ['city', 'state', 'county', 'country']
|
||||
whereIam = f"City: {address.get('city', '')}. State: {address.get('state', '')}. County: {address.get('county', '')}. Country: {address.get('country', '')}."
|
||||
return whereIam
|
||||
|
||||
if float(lat) == latitudeValue and float(lon) == longitudeValue:
|
||||
# redacted address when no GPS and using default location
|
||||
location = geolocator.reverse(lat + ", " + lon)
|
||||
address = location.raw['address']
|
||||
address_components = ['city', 'state', 'postcode', 'county', 'country']
|
||||
whereIam += ' '.join([address.get(component, '') for component in address_components if component in address])
|
||||
whereIam += " Grid: " + grid
|
||||
else:
|
||||
location = geolocator.reverse(lat + ", " + lon)
|
||||
address = location.raw['address']
|
||||
address_components = ['house_number', 'road', 'city', 'state', 'postcode', 'county', 'country']
|
||||
whereIam += ' '.join([address.get(component, '') for component in address_components if component in address])
|
||||
whereIam += " Grid: " + grid
|
||||
return whereIam
|
||||
except Exception as e:
|
||||
logger.debug("Location:Error fetching location data with whereami, likely network error")
|
||||
return ERROR_FETCHING_DATA
|
||||
|
||||
|
||||
def get_tide(lat=0, lon=0):
|
||||
station_id = ""
|
||||
@@ -193,6 +204,7 @@ def abbreviate_weather(row):
|
||||
"precipitation": "precip",
|
||||
"showers": "shwrs",
|
||||
"thunderstorms": "t-storms",
|
||||
"thunderstorm": "t-storm",
|
||||
"quarters": "qtrs",
|
||||
"quarter": "qtr"
|
||||
}
|
||||
|
||||
@@ -59,14 +59,14 @@ stdout_handler.setFormatter(CustomFormatter(logFormat))
|
||||
logger.addHandler(stdout_handler)
|
||||
if syslog_to_file:
|
||||
# Create file handler for logging to a file
|
||||
file_handler = logging.FileHandler('system{}.log'.format(today.strftime('%Y_%m_%d')))
|
||||
file_handler = logging.FileHandler('logs/system{}.log'.format(today.strftime('%Y_%m_%d')))
|
||||
file_handler.setLevel(logging.DEBUG) # DEBUG used by default for system logs to disk
|
||||
file_handler.setFormatter(logging.Formatter(logFormat))
|
||||
logger.addHandler(file_handler)
|
||||
|
||||
if log_messages_to_file:
|
||||
# Create file handler for logging to a file
|
||||
file_handler = logging.FileHandler('messages{}.log'.format(today.strftime('%Y_%m_%d')))
|
||||
file_handler = logging.FileHandler('logs/messages{}.log'.format(today.strftime('%Y_%m_%d')))
|
||||
file_handler.setLevel(logging.INFO) # INFO used for messages to disk
|
||||
file_handler.setFormatter(logging.Formatter(msgLogFormat))
|
||||
msgLogger.addHandler(file_handler)
|
||||
|
||||
341
modules/mmind.py
Normal file
341
modules/mmind.py
Normal file
@@ -0,0 +1,341 @@
|
||||
# https://github.com/pwdkramer/pythonMastermind/blob/main/main.py
|
||||
# Adapted for Meshtastic mesh-bot by K7MHI Kelly Keeton 2024
|
||||
|
||||
import random
|
||||
import time
|
||||
import pickle
|
||||
from modules.log import *
|
||||
|
||||
mindTracker = [{'nodeID': 0, 'last_played': time.time(), 'cmd': '', 'secret_code': '', 'diff': 'n', 'turns': 1}]
|
||||
|
||||
def chooseDifficultyMMind(message):
|
||||
usrInput = message.lower()
|
||||
msg = ''
|
||||
valid_colorsMMind = "RYGB"
|
||||
|
||||
if not usrInput.startswith("n") and not usrInput.startswith("h") and not usrInput.startswith("x"):
|
||||
# default to normal difficulty
|
||||
usrInput = "n"
|
||||
|
||||
if usrInput == "n":
|
||||
msg += f"The colors to choose from are:\nR🔴, Y🟡, G🟢, B🔵"
|
||||
elif usrInput == "h":
|
||||
valid_colorsMMind += "OP"
|
||||
msg += f"The colors to choose from are\nR🔴, Y🟡, G🟢, B🔵, O🟠, P🟣"
|
||||
elif usrInput == "x":
|
||||
valid_colorsMMind += "OPWK"
|
||||
msg += f"The colors to choose from are\nR🔴, Y🟡, G🟢, B🔵, O🟠, P🟣, W⚪, K⚫"
|
||||
return msg
|
||||
|
||||
|
||||
#possible colors on nomral: Red, Yellow, Green, Blue
|
||||
#added colors on hard: Orange, Purple
|
||||
def makeCodeMMind(diff):
|
||||
secret_code = ""
|
||||
for i in range(4):
|
||||
if diff == "n":
|
||||
roll = random.randrange(1, 5)
|
||||
elif diff == "h":
|
||||
roll = random.randrange(1,7)
|
||||
elif diff == "x":
|
||||
roll = random.randrange(1,9)
|
||||
else:
|
||||
print("Difficulty error in makeCode()")
|
||||
if roll == 1:
|
||||
secret_code += "R"
|
||||
elif roll == 2:
|
||||
secret_code += "Y"
|
||||
elif roll == 3:
|
||||
secret_code += "G"
|
||||
elif roll == 4:
|
||||
secret_code += "B"
|
||||
elif roll == 5:
|
||||
secret_code += "O"
|
||||
elif roll == 6:
|
||||
secret_code += "P"
|
||||
elif roll == 7:
|
||||
secret_code += "W"
|
||||
elif roll == 8:
|
||||
secret_code += "K"
|
||||
else:
|
||||
print("Error with range of roll in makeCode()")
|
||||
return secret_code
|
||||
|
||||
#get guess from user
|
||||
def getGuessMMind(diff, guess):
|
||||
msg = ''
|
||||
if diff == "n":
|
||||
valid_colorsMMind = "RYGB"
|
||||
elif diff == "h":
|
||||
valid_colorsMMind = "RYGBOP"
|
||||
elif diff == "x":
|
||||
valid_colorsMMind = "RYGBOPWK"
|
||||
|
||||
user_guess = guess.upper()
|
||||
valid_guess = True
|
||||
if len(user_guess) != 4:
|
||||
valid_guess = False
|
||||
for i in range(len(user_guess)):
|
||||
if user_guess[i] not in valid_colorsMMind:
|
||||
valid_guess = False
|
||||
if valid_guess == False:
|
||||
user_guess = "XXXX"
|
||||
return user_guess
|
||||
|
||||
def getHighScoreMMind(nodeID, turns, diff):
|
||||
# check if player is in high score list and pick the lowest score
|
||||
try:
|
||||
with open('mmind_hs.pkl', 'rb') as f:
|
||||
mindHighScore = pickle.load(f)
|
||||
except:
|
||||
logger.debug("System: MasterMind: High Score file not found.")
|
||||
mindHighScore = [{'nodeID': nodeID, 'turns': turns, 'diff': diff}]
|
||||
with open('mmind_hs.pkl', 'wb') as f:
|
||||
pickle.dump(mindHighScore, f)
|
||||
|
||||
if nodeID == 0:
|
||||
# just return the high score
|
||||
return mindHighScore
|
||||
|
||||
# calculate lowest score
|
||||
lowest_score = mindHighScore[0]['turns']
|
||||
|
||||
if mindHighScore[0]['diff'] == "n" and diff == "n":
|
||||
if lowest_score > turns:
|
||||
# update the high score for normal if new score is lower
|
||||
mindHighScore[0]['nodeID'] = nodeID
|
||||
mindHighScore[0]['turns'] = turns
|
||||
mindHighScore[0]['diff'] = diff
|
||||
|
||||
# write new high score to file
|
||||
with open('mmind_hs.pkl', 'wb') as f:
|
||||
pickle.dump(mindHighScore, f)
|
||||
return mindHighScore
|
||||
elif mindHighScore[0]['diff'] == "n" and diff == "h":
|
||||
# update the high score for hard if normal is the only high score
|
||||
mindHighScore[0]['nodeID'] = nodeID
|
||||
mindHighScore[0]['turns'] = turns
|
||||
mindHighScore[0]['diff'] = diff
|
||||
|
||||
# write new high score to file
|
||||
with open('mmind_hs.pkl', 'wb') as f:
|
||||
pickle.dump(mindHighScore, f)
|
||||
return mindHighScore
|
||||
elif mindHighScore[0]['diff'] == "h" and diff == "h":
|
||||
if lowest_score > turns:
|
||||
# update the high score for hard if new score is lower
|
||||
mindHighScore[0]['nodeID'] = nodeID
|
||||
mindHighScore[0]['turns'] = turns
|
||||
mindHighScore[0]['diff'] = diff
|
||||
|
||||
# write new high score to file
|
||||
with open('mmind_hs.pkl', 'wb') as f:
|
||||
pickle.dump(mindHighScore, f)
|
||||
return mindHighScore
|
||||
elif mindHighScore[0]['diff'] == "n" or mindHighScore[0]['diff'] == "h" and diff == "x":
|
||||
# update the high score for expert if normal or high is the only high score
|
||||
mindHighScore[0]['nodeID'] = nodeID
|
||||
mindHighScore[0]['turns'] = turns
|
||||
mindHighScore[0]['diff'] = diff
|
||||
|
||||
# write new high score to file
|
||||
with open('mmind_hs.pkl', 'wb') as f:
|
||||
pickle.dump(mindHighScore, f)
|
||||
return mindHighScore
|
||||
elif mindHighScore[0]['diff'] == "x" and diff == "x":
|
||||
if lowest_score > turns:
|
||||
# update the high score for expert if new score is lower
|
||||
mindHighScore[0]['nodeID'] = nodeID
|
||||
mindHighScore[0]['turns'] = turns
|
||||
mindHighScore[0]['diff'] = diff
|
||||
|
||||
# write new high score to file
|
||||
with open('mmind_hs.pkl', 'wb') as f:
|
||||
pickle.dump(mindHighScore, f)
|
||||
return mindHighScore
|
||||
return 0
|
||||
|
||||
|
||||
def getEmojiMMind(secret_code):
|
||||
# for each letter in the secret code, convert to emoji for display
|
||||
secret_code = secret_code.upper()
|
||||
secret_code_emoji = ""
|
||||
for i in range(len(secret_code)):
|
||||
if secret_code[i] == "R":
|
||||
secret_code_emoji += "🔴"
|
||||
elif secret_code[i] == "Y":
|
||||
secret_code_emoji += "🟡"
|
||||
elif secret_code[i] == "G":
|
||||
secret_code_emoji += "🟢"
|
||||
elif secret_code[i] == "B":
|
||||
secret_code_emoji += "🔵"
|
||||
elif secret_code[i] == "O":
|
||||
secret_code_emoji += "🟠"
|
||||
elif secret_code[i] == "P":
|
||||
secret_code_emoji += "🟣"
|
||||
elif secret_code[i] == "W":
|
||||
secret_code_emoji += "⚪"
|
||||
elif secret_code[i] == "K":
|
||||
secret_code_emoji += "⚫"
|
||||
elif secret_code[i] == "X":
|
||||
secret_code_emoji += "❌"
|
||||
return secret_code_emoji
|
||||
|
||||
#compare userGuess with secret code and provide feedback
|
||||
def compareCodeMMind(secret_code, user_guess):
|
||||
game_won = False
|
||||
perfect_pins = 0
|
||||
wrong_position = 0
|
||||
msg = ''
|
||||
#logger.debug("System: MasterMind: secret_code: " + str(secret_code) + " user_guess: " + str(user_guess))
|
||||
if secret_code == user_guess: #correct guess, user wins
|
||||
perfect_pins = 4
|
||||
game_won = True
|
||||
else:
|
||||
# check for perfect pins and right color wrong position
|
||||
temp_code = []
|
||||
temp_guess = []
|
||||
for i in range(len(user_guess)): #check for perfect pins
|
||||
if user_guess[i] == secret_code[i]:
|
||||
perfect_pins += 1
|
||||
else:
|
||||
temp_code.append(secret_code[i])
|
||||
temp_guess.append(user_guess[i])
|
||||
for i in range(len(temp_guess)): #check for right color wrong position
|
||||
for j in range(len(temp_code)):
|
||||
if temp_guess[i] == temp_code[j]:
|
||||
wrong_position += 1
|
||||
temp_code[j] = "0"
|
||||
break
|
||||
# display feedback
|
||||
if game_won:
|
||||
msg += f"Correct{getEmojiMMind(user_guess)}\n"
|
||||
else:
|
||||
msg += f"Guess{getEmojiMMind(user_guess)}\n"
|
||||
|
||||
if perfect_pins > 0 and game_won == False:
|
||||
msg += "✅ color ✅ position: {}".format(perfect_pins)
|
||||
|
||||
if wrong_position > 0:
|
||||
if "✅" in msg: msg += f"\n"
|
||||
msg += "✅ color 🚫 position: {}".format(wrong_position)
|
||||
|
||||
if "✅" not in msg and game_won == False:
|
||||
msg += "🚫No pins in your guess😿 are in the code!"
|
||||
|
||||
return msg
|
||||
|
||||
#game loop with turn counter
|
||||
def playGameMMind(diff, secret_code, turn_count, nodeID, message):
|
||||
msg = ''
|
||||
won = False
|
||||
if turn_count <= 10:
|
||||
user_guess = getGuessMMind(diff, message)
|
||||
if user_guess == "XXXX":
|
||||
msg += "Invalid guess. Please enter 4 valid colors."
|
||||
return msg
|
||||
check_guess = compareCodeMMind(secret_code, user_guess)
|
||||
|
||||
# display turn count and feedback
|
||||
msg += "Turn {}:".format(turn_count)
|
||||
if check_guess.startswith("Correct"):
|
||||
won = True
|
||||
msg += check_guess
|
||||
|
||||
if won == True:
|
||||
msg += f"\n🎉🧠 you win 🥷🤯"
|
||||
# get high score
|
||||
high_score = getHighScoreMMind(nodeID, turn_count, diff)
|
||||
if high_score != 0:
|
||||
msg += f"\n🏆 High Score:{high_score[0]['turns']} turns, Difficulty:{high_score[0]['diff'].upper()}"
|
||||
|
||||
msg += "\nWould you like to play again?\n(N)ormal, (H)ard, e(X)pert?"
|
||||
# reset turn count in tracker
|
||||
for i in range(len(mindTracker)):
|
||||
if mindTracker[i]['nodeID'] == nodeID:
|
||||
mindTracker[i]['turns'] = 1
|
||||
mindTracker[i]['secret_code'] = ''
|
||||
mindTracker[i]['cmd'] = 'new'
|
||||
else:
|
||||
# increment turn count and keep playing
|
||||
turn_count += 1
|
||||
# store turn count in tracker
|
||||
for i in range(len(mindTracker)):
|
||||
if mindTracker[i]['nodeID'] == nodeID:
|
||||
mindTracker[i]['turns'] = turn_count
|
||||
elif won == False:
|
||||
msg += f"🙉Game Over🙈\nThe code was: {getEmojiMMind(secret_code)}"
|
||||
msg += "\nYou have run out of turns.😿"
|
||||
msg += "\nWould you like to play again? (N)ormal, (H)ard, or e(X)pert?"
|
||||
# reset turn count in tracker
|
||||
for i in range(len(mindTracker)):
|
||||
if mindTracker[i]['nodeID'] == nodeID:
|
||||
mindTracker[i]['turns'] = 1
|
||||
mindTracker[i]['secret_code'] = ''
|
||||
mindTracker[i]['cmd'] = 'new'
|
||||
|
||||
return msg
|
||||
|
||||
def endGameMMind(nodeID):
|
||||
global mindTracker
|
||||
# remove player from tracker
|
||||
for i in range(len(mindTracker)):
|
||||
if mindTracker[i]['nodeID'] == nodeID:
|
||||
del mindTracker[i]
|
||||
logger.debug("System: MasterMind: Player removed: " + str(nodeID))
|
||||
break
|
||||
|
||||
#main game
|
||||
def start_mMind(nodeID, message):
|
||||
global mindTracker
|
||||
last_cmd = ""
|
||||
msg = ''
|
||||
|
||||
# get player's last command from tracker if not new player
|
||||
for i in range(len(mindTracker)):
|
||||
if mindTracker[i]['nodeID'] == nodeID:
|
||||
last_cmd = mindTracker[i]['cmd']
|
||||
|
||||
logger.debug("System: MasterMind: last_cmd: " + str(last_cmd))
|
||||
|
||||
if last_cmd == "new":
|
||||
if message.lower().startswith("n") or message.lower().startswith("h") or message.lower().startswith("x"):
|
||||
diff = message.lower()[0]
|
||||
else:
|
||||
diff = "n"
|
||||
|
||||
# set player's last command to makeCode
|
||||
for i in range(len(mindTracker)):
|
||||
if mindTracker[i]['nodeID'] == nodeID:
|
||||
mindTracker[i]['cmd'] = 'makeCode'
|
||||
mindTracker[i]['diff'] = diff
|
||||
# Return color message to player
|
||||
msg += chooseDifficultyMMind(message.lower()[0])
|
||||
return msg
|
||||
|
||||
if last_cmd == "makeCode":
|
||||
# get difficulty from tracker
|
||||
for i in range(len(mindTracker)):
|
||||
if mindTracker[i]['nodeID'] == nodeID:
|
||||
diff = mindTracker[i]['diff']
|
||||
|
||||
secret_code = makeCodeMMind(diff)
|
||||
last_cmd = "playGame"
|
||||
# set player's last command to playGame
|
||||
for i in range(len(mindTracker)):
|
||||
if mindTracker[i]['nodeID'] == nodeID:
|
||||
mindTracker[i]['cmd'] = 'playGame'
|
||||
mindTracker[i]['secret_code'] = secret_code
|
||||
mindTracker[i]['last_played'] = time.time()
|
||||
|
||||
if last_cmd == "playGame":
|
||||
# get difficulty, secret code, and turn count from tracker
|
||||
for i in range(len(mindTracker)):
|
||||
if mindTracker[i]['nodeID'] == nodeID:
|
||||
diff = mindTracker[i]['diff']
|
||||
secret_code = mindTracker[i]['secret_code']
|
||||
turn_count = mindTracker[i]['turns']
|
||||
|
||||
msg += playGameMMind(diff, secret_code, turn_count, nodeID=nodeID, message=message)
|
||||
|
||||
return msg
|
||||
@@ -10,7 +10,6 @@ MOTD = 'Thanks for using MeshBOT! Have a good day!'
|
||||
NO_ALERTS = "No weather alerts found."
|
||||
|
||||
# setup the global variables
|
||||
MESSAGE_CHUNK_SIZE = 160 # message chunk size for sending at high success rate
|
||||
SITREP_NODE_COUNT = 3 # number of nodes to report in the sitrep
|
||||
msg_history = [] # message history for the store and forward feature
|
||||
bbs_ban_list = [] # list of banned users, imported from config
|
||||
@@ -26,8 +25,8 @@ retry_int1 = False
|
||||
retry_int2 = False
|
||||
scheduler_enabled = False # enable the scheduler currently config via code only
|
||||
wiki_return_limit = 3 # limit the number of sentences returned off the first paragraph first hit
|
||||
llmRunCounter = 0
|
||||
llmTotalRuntime = []
|
||||
playingGame = False
|
||||
GAMEDELAY = 28800 # 8 hours in seconds for game mode holdoff
|
||||
|
||||
# Read the config file, if it does not exist, create basic config file
|
||||
config = configparser.ConfigParser()
|
||||
@@ -66,6 +65,14 @@ if 'radioMon' not in config:
|
||||
config['radioMon'] = {'enabled': 'False', 'rigControlServerAddress': 'localhost:4532', 'sigWatchBrodcastCh': '2', 'signalDetectionThreshold': '-10', 'signalHoldTime': '10', 'signalCooldown': '5', 'signalCycleLimit': '5'}
|
||||
config.write(open(config_file, 'w'))
|
||||
|
||||
if 'games' not in config:
|
||||
config['games'] = {'dopeWars': 'True', 'lemonade': 'True', 'blackjack': 'True', 'videoPoker': 'True'}
|
||||
config.write(open(config_file, 'w'))
|
||||
|
||||
if 'messagingSettings' not in config:
|
||||
config['messagingSettings'] = {'responseDelay': '0.7', 'splitDelay': '0', 'MESSAGE_CHUNK_SIZE': '160'}
|
||||
config.write(open(config_file, 'w'))
|
||||
|
||||
# interface1 settings
|
||||
interface1_type = config['interface'].get('type', 'serial')
|
||||
port1 = config['interface'].get('port', '')
|
||||
@@ -84,28 +91,37 @@ else:
|
||||
|
||||
# variables
|
||||
try:
|
||||
# general
|
||||
useDMForResponse = config['general'].getboolean('respond_by_dm_only', True)
|
||||
publicChannel = config['general'].getint('defaultChannel', 0) # the meshtastic public channel
|
||||
ignoreDefaultChannel = config['general'].getboolean('ignoreDefaultChannel', False)
|
||||
zuluTime = config['general'].getboolean('zuluTime', False) # aka 24 hour time
|
||||
log_messages_to_file = config['general'].getboolean('LogMessagesToFile', True) # default True
|
||||
syslog_to_file = config['general'].getboolean('SyslogToFile', False)
|
||||
urlTimeoutSeconds = config['general'].getint('urlTimeout', 10) # default 10 seconds
|
||||
store_forward_enabled = config['general'].getboolean('StoreForward', True)
|
||||
storeFlimit = config['general'].getint('StoreLimit', 3) # default 3 messages for S&F
|
||||
welcome_message = config['general'].get(f'welcome_message', WELCOME_MSG)
|
||||
welcome_message = config['general'].get('welcome_message', WELCOME_MSG)
|
||||
welcome_message = (f"{welcome_message}").replace('\\n', '\n') # allow for newlines in the welcome message
|
||||
motd_enabled = config['general'].getboolean('motdEnabled', True)
|
||||
MOTD = config['general'].get('motd', MOTD)
|
||||
enableCmdHistory = config['general'].getboolean('enableCmdHistory', True)
|
||||
lheardCmdIgnoreNode = config['general'].get('lheardCmdIgnoreNode', '').split(',')
|
||||
whoami_enabled = config['general'].getboolean('whoami', True)
|
||||
dad_jokes_enabled = config['general'].getboolean('DadJokes', False)
|
||||
solar_conditions_enabled = config['general'].getboolean('spaceWeather', True)
|
||||
wikipedia_enabled = config['general'].getboolean('wikipedia', False)
|
||||
llm_enabled = config['general'].getboolean('ollama', False) # https://ollama.com
|
||||
llmModel = config['general'].get('ollamaModel', 'gemma2:2b') # default gemma2:2b
|
||||
|
||||
# sentry
|
||||
sentry_enabled = config['sentry'].getboolean('SentryEnabled', False) # default False
|
||||
secure_channel = config['sentry'].getint('SentryChannel', 2) # default 2
|
||||
sentry_holdoff = config['sentry'].getint('SentryHoldoff', 9) # default 9
|
||||
sentryIgnoreList = config['sentry'].get('sentryIgnoreList', '').split(',')
|
||||
sentry_radius = config['sentry'].getint('SentryRadius', 100) # default 100 meters
|
||||
|
||||
# location
|
||||
location_enabled = config['location'].getboolean('enabled', True)
|
||||
latitudeValue = config['location'].getfloat('lat', 48.50)
|
||||
longitudeValue = config['location'].getfloat('lon', -123.0)
|
||||
@@ -115,14 +131,17 @@ try:
|
||||
numWxAlerts = config['location'].getint('NOAAalertCount', 2) # default 2 alerts
|
||||
wxAlertsEnabled = config['location'].getboolean('NOAAalertsEnabled', True) # default True not enabled yet
|
||||
|
||||
# bbs
|
||||
bbs_enabled = config['bbs'].getboolean('enabled', False)
|
||||
bbsdb = config['bbs'].get('bbsdb', 'bbsdb.pkl')
|
||||
bbs_ban_list = config['bbs'].get('bbs_ban_list', '').split(',')
|
||||
bbs_admin_list = config['bbs'].get('bbs_admin_list', '').split(',')
|
||||
|
||||
# repeater
|
||||
repeater_enabled = config['repeater'].getboolean('enabled', False)
|
||||
repeater_channels = config['repeater'].get('repeater_channels', '').split(',')
|
||||
|
||||
# radio monitoring
|
||||
radio_detection_enabled = config['radioMon'].getboolean('enabled', False)
|
||||
rigControlServerAddress = config['radioMon'].get('rigControlServerAddress', 'localhost:4532') # default localhost:4532
|
||||
sigWatchBroadcastCh = config['radioMon'].get('sigWatchBroadcastCh', '2').split(',') # default Channel 2
|
||||
@@ -130,6 +149,19 @@ try:
|
||||
signalHoldTime = config['radioMon'].getint('signalHoldTime', 10) # default 10 seconds
|
||||
signalCooldown = config['radioMon'].getint('signalCooldown', 5) # default 1 second
|
||||
signalCycleLimit = config['radioMon'].getint('signalCycleLimit', 5) # default 5 cycles, used with SIGNAL_COOLDOWN
|
||||
|
||||
# games
|
||||
dopewars_enabled = config['games'].getboolean('dopeWars', True)
|
||||
lemonade_enabled = config['games'].getboolean('lemonade', True)
|
||||
blackjack_enabled = config['games'].getboolean('blackjack', True)
|
||||
videoPoker_enabled = config['games'].getboolean('videoPoker', True)
|
||||
mastermind_enabled = config['games'].getboolean('mastermind', True)
|
||||
golfSim_enabled = config['games'].getboolean('golfSim', True)
|
||||
|
||||
# messaging settings
|
||||
responseDelay = config['messagingSettings'].getfloat('responseDelay', 0.7) # default 0.7
|
||||
splitDelay = config['messagingSettings'].getfloat('splitDelay', 0) # default 0
|
||||
MESSAGE_CHUNK_SIZE = config['messagingSettings'].getint('MESSAGE_CHUNK_SIZE', 160) # default 160
|
||||
|
||||
except KeyError as e:
|
||||
print(f"System: Error reading config file: {e}")
|
||||
|
||||
@@ -13,6 +13,7 @@ from modules.log import *
|
||||
trap_list = ("cmd","cmd?") # default trap list
|
||||
help_message = "CMD?:"
|
||||
asyncLoop = asyncio.new_event_loop()
|
||||
games_enabled = False
|
||||
|
||||
# Ping Configuration
|
||||
if ping_enabled:
|
||||
@@ -33,11 +34,24 @@ if motd_enabled:
|
||||
trap_list = trap_list + trap_list_motd
|
||||
help_message = help_message + ", motd"
|
||||
|
||||
# whoami Configuration
|
||||
if whoami_enabled:
|
||||
trap_list_whoami = ("whoami",)
|
||||
trap_list = trap_list + trap_list_whoami
|
||||
help_message = help_message + ", whoami"
|
||||
|
||||
# Solar Conditions Configuration
|
||||
if solar_conditions_enabled:
|
||||
from modules.solarconditions import * # from the spudgunman/meshing-around repo
|
||||
trap_list = trap_list + trap_list_solarconditions # items hfcond, solar, sun, moon
|
||||
help_message = help_message + ", sun, hfcond, solar, moon"
|
||||
else:
|
||||
hf_band_conditions = False
|
||||
|
||||
# Command History Configuration
|
||||
if enableCmdHistory:
|
||||
trap_list = trap_list + ("history",)
|
||||
#help_message = help_message + ", history"
|
||||
|
||||
# Location Configuration
|
||||
if location_enabled:
|
||||
@@ -51,12 +65,16 @@ if location_enabled:
|
||||
else:
|
||||
# NOAA only features
|
||||
help_message = help_message + ", wxa, tide"
|
||||
|
||||
|
||||
# BBS Configuration
|
||||
if bbs_enabled:
|
||||
from modules.bbstools import * # from the spudgunman/meshing-around repo
|
||||
trap_list = trap_list + trap_list_bbs # items bbslist, bbspost, bbsread, bbsdelete, bbshelp
|
||||
help_message = help_message + ", bbslist, bbshelp"
|
||||
else:
|
||||
bbs_help = False
|
||||
bbs_list_messages = False
|
||||
|
||||
|
||||
# Dad Jokes Configuration
|
||||
if dad_jokes_enabled:
|
||||
@@ -67,14 +85,72 @@ if dad_jokes_enabled:
|
||||
# Wikipedia Search Configuration
|
||||
if wikipedia_enabled:
|
||||
import wikipedia # pip install wikipedia
|
||||
trap_list = trap_list + ("wiki:",)
|
||||
trap_list = trap_list + ("wiki:", "wiki?",)
|
||||
help_message = help_message + ", wiki:"
|
||||
|
||||
# LLM Configuration
|
||||
if llm_enabled:
|
||||
from modules.llm import * # from the spudgunman/meshing-around repo
|
||||
trap_list = trap_list + trap_list_llm # items ask:
|
||||
help_message = help_message + ", ask:"
|
||||
help_message = help_message + ", askai"
|
||||
|
||||
# DopeWars Configuration
|
||||
if dopewars_enabled:
|
||||
from modules.dopewar import * # from the spudgunman/meshing-around repo
|
||||
trap_list = trap_list + ("dopewars",)
|
||||
games_enabled = True
|
||||
|
||||
# Lemonade Stand Configuration
|
||||
if lemonade_enabled:
|
||||
from modules.lemonade import * # from the spudgunman/meshing-around repo
|
||||
trap_list = trap_list + ("lemonstand",)
|
||||
games_enabled = True
|
||||
|
||||
# BlackJack Configuration
|
||||
if blackjack_enabled:
|
||||
from modules.blackjack import * # from the spudgunman/meshing-around repo
|
||||
trap_list = trap_list + ("blackjack",)
|
||||
games_enabled = True
|
||||
|
||||
# Video Poker Configuration
|
||||
if videoPoker_enabled:
|
||||
from modules.videopoker import * # from the spudgunman/meshing-around repo
|
||||
trap_list = trap_list + ("videopoker",)
|
||||
games_enabled = True
|
||||
|
||||
if mastermind_enabled:
|
||||
from modules.mmind import * # from the spudgunman/meshing-around repo
|
||||
trap_list = trap_list + ("mastermind",)
|
||||
games_enabled = True
|
||||
|
||||
if golfSim_enabled:
|
||||
from modules.golfsim import * # from the spudgunman/meshing-around repo
|
||||
trap_list = trap_list + ("golfsim",)
|
||||
games_enabled = True
|
||||
|
||||
# Games Configuration
|
||||
if games_enabled is True:
|
||||
help_message = help_message + ", games"
|
||||
trap_list = trap_list + ("games",)
|
||||
gTnW_enabled = True
|
||||
gamesCmdList = "Play via DM🕹️ CMD: "
|
||||
if dopewars_enabled:
|
||||
gamesCmdList += "dopeWars, "
|
||||
if lemonade_enabled:
|
||||
gamesCmdList += "lemonStand, "
|
||||
if gTnW_enabled:
|
||||
trap_list = trap_list + ("globalthermonuclearwar",)
|
||||
if blackjack_enabled:
|
||||
gamesCmdList += "blackJack, "
|
||||
if videoPoker_enabled:
|
||||
gamesCmdList += "videoPoker, "
|
||||
if mastermind_enabled:
|
||||
gamesCmdList += "masterMind, "
|
||||
if golfSim_enabled:
|
||||
gamesCmdList += "golfSim, "
|
||||
gamesCmdList = gamesCmdList[:-2] # remove the last comma
|
||||
else:
|
||||
gamesCmdList = ""
|
||||
|
||||
# Scheduled Broadcast Configuration
|
||||
if scheduler_enabled:
|
||||
@@ -103,7 +179,7 @@ if interface1_type == 'ble' and interface2_type == 'ble':
|
||||
|
||||
# Interface1 Configuration
|
||||
try:
|
||||
logger.debug(f"System: Initalizing Interface1")
|
||||
logger.debug(f"System: Initializing Interface1")
|
||||
if interface1_type == 'serial':
|
||||
interface1 = meshtastic.serial_interface.SerialInterface(port1)
|
||||
elif interface1_type == 'tcp':
|
||||
@@ -114,12 +190,12 @@ try:
|
||||
logger.critical(f"System: Interface Type: {interface1_type} not supported. Validate your config against config.template Exiting")
|
||||
exit()
|
||||
except Exception as e:
|
||||
logger.critical(f"System: script abort. Initalizing Interface1 {e}")
|
||||
logger.critical(f"System: script abort. Initializing Interface1 {e}")
|
||||
exit()
|
||||
|
||||
# Interface2 Configuration
|
||||
if interface2_enabled:
|
||||
logger.debug(f"System: Initalizing Interface2")
|
||||
logger.debug(f"System: Initializing Interface2")
|
||||
try:
|
||||
if interface2_type == 'serial':
|
||||
interface2 = meshtastic.serial_interface.SerialInterface(port2)
|
||||
@@ -131,7 +207,7 @@ if interface2_enabled:
|
||||
logger.critical(f"System: Interface Type: {interface2_type} not supported. Validate your config against config.template Exiting")
|
||||
exit()
|
||||
except Exception as e:
|
||||
logger.critical(f"System: script abort. Initalizing Interface2 {e}")
|
||||
logger.critical(f"System: script abort. Initializing Interface2 {e}")
|
||||
exit()
|
||||
|
||||
#Get the node number of the device, check if the device is connected
|
||||
@@ -472,6 +548,7 @@ def send_message(message, ch, nodeid=0, nodeInt=1):
|
||||
interface1.sendText(text=m, channelIndex=ch, destinationId=nodeid)
|
||||
if nodeInt == 2:
|
||||
interface2.sendText(text=m, channelIndex=ch, destinationId=nodeid)
|
||||
time.sleep(splitDelay) # wait an amout of time between sending each split message
|
||||
else: # message is less than MESSAGE_CHUNK_SIZE characters
|
||||
if nodeid == 0:
|
||||
# Send to channel
|
||||
@@ -498,23 +575,64 @@ def tell_joke():
|
||||
return ''
|
||||
|
||||
def get_wikipedia_summary(search_term):
|
||||
# search wikipedia for a summary of the search term
|
||||
try:
|
||||
logger.debug(f"System: Searching Wikipedia for:{search_term}")
|
||||
summary = wikipedia.summary(search_term, sentences=wiki_return_limit)
|
||||
return summary
|
||||
except Exception as e:
|
||||
# The errors are vebose, normallly around trying to guess the search term
|
||||
logger.warning(f"System: Error searching Wikipedia for:{search_term}")
|
||||
wikipedia_search = wikipedia.search(search_term, results=3)
|
||||
wikipedia_suggest = wikipedia.suggest(search_term)
|
||||
#wikipedia_aroundme = wikipedia.geosearch(location[0], location[1], results=3)
|
||||
#logger.debug(f"System: Wikipedia Nearby:{wikipedia_aroundme}")
|
||||
|
||||
if len(wikipedia_search) == 0:
|
||||
logger.warning(f"System: No Wikipedia Results for:{search_term}")
|
||||
return ERROR_FETCHING_DATA
|
||||
|
||||
try:
|
||||
logger.debug(f"System: Searching Wikipedia for:{search_term}, First Result:{wikipedia_search[0]}, Suggest Word:{wikipedia_suggest}")
|
||||
summary = wikipedia.summary(search_term, sentences=wiki_return_limit, auto_suggest=False, redirect=True)
|
||||
except wikipedia.DisambiguationError as e:
|
||||
logger.warning(f"System: Disambiguation Error for:{search_term} trying {wikipedia_search[0]}")
|
||||
summary = wikipedia.summary(wikipedia_search[0], sentences=wiki_return_limit, auto_suggest=True, redirect=True)
|
||||
except wikipedia.PageError as e:
|
||||
logger.warning(f"System: Wikipedia Page Error for:{search_term} {e} trying {wikipedia_search[0]}")
|
||||
summary = wikipedia.summary(wikipedia_search[0], sentences=wiki_return_limit, auto_suggest=True, redirect=True)
|
||||
except Exception as e:
|
||||
logger.error(f"System: Error with Wikipedia for:{search_term} {e}")
|
||||
return ERROR_FETCHING_DATA
|
||||
|
||||
return summary
|
||||
|
||||
def getPrettyTime(seconds):
|
||||
# convert unix time to minutes, hours, or days, or years for simple display
|
||||
designator = "s"
|
||||
if seconds > 0:
|
||||
seconds = round(seconds / 60)
|
||||
designator = "m"
|
||||
if seconds > 60:
|
||||
seconds = round(seconds / 60)
|
||||
designator = "h"
|
||||
if seconds > 24:
|
||||
seconds = round(seconds / 24)
|
||||
designator = "d"
|
||||
if seconds > 365:
|
||||
seconds = round(seconds / 365)
|
||||
designator = "y"
|
||||
return str(seconds) + designator
|
||||
|
||||
def messageTrap(msg):
|
||||
# Check if the message contains a trap word
|
||||
# Check if the message contains a trap word, this is the first filter for listning to messages
|
||||
# after this the message is passed to the command_handler in the bot.py which is switch case filter for applying word to function
|
||||
|
||||
# Split Message on assumed words spaces m for m = msg.split(" ")
|
||||
# t in trap_list, built by the config and system.py not the user
|
||||
message_list=msg.split(" ")
|
||||
for m in message_list:
|
||||
for t in trap_list:
|
||||
# if word in message is in the trap list, return True
|
||||
if t.lower() == m.lower():
|
||||
return True
|
||||
# if no trap words found, run a search for near misses like ping? or cmd?
|
||||
for m in message_list:
|
||||
for t in range(len(trap_list)):
|
||||
if m.endswith('?') and m[:-1].lower() == trap_list[t]:
|
||||
return True
|
||||
return False
|
||||
|
||||
def exit_handler():
|
||||
@@ -656,7 +774,6 @@ async def watchdog():
|
||||
with contextlib.redirect_stdout(None):
|
||||
interface1.localNode.getMetadata()
|
||||
print(f"System: if you see this upgrade python to >3.4")
|
||||
#if "device_state_version:" not in meta:
|
||||
except Exception as e:
|
||||
logger.error(f"System: communicating with interface1, trying to reconnect: {e}")
|
||||
retry_int1 = True
|
||||
|
||||
450
modules/videopoker.py
Normal file
450
modules/videopoker.py
Normal file
@@ -0,0 +1,450 @@
|
||||
# Port of https://github.com/devtronvarma/Video-Poker-Terminal-Game
|
||||
# Adapted for Meshtastic mesh-bot by K7MHI Kelly Keeton 2024
|
||||
import random
|
||||
import time
|
||||
import pickle
|
||||
from modules.log import *
|
||||
|
||||
vpStartingCash = 20
|
||||
vpTracker= [{'nodeID': 0, 'cmd': 'new', 'time': time.time(), 'cash': vpStartingCash, 'player': None, 'deck': None, 'highScore': 0, 'drawCount': 0}]
|
||||
|
||||
# Define the Card class
|
||||
class CardVP:
|
||||
|
||||
card_values = { # value of the ace is high until it needs to be low
|
||||
2: 2,
|
||||
3: 3,
|
||||
4: 4,
|
||||
5: 5,
|
||||
6: 6,
|
||||
7: 7,
|
||||
8: 8,
|
||||
9: 9,
|
||||
10: 10,
|
||||
'Jack': 11,
|
||||
'Queen': 12,
|
||||
'King': 13,
|
||||
'Ace': 14
|
||||
}
|
||||
|
||||
def __init__(self, suit, rank):
|
||||
"""
|
||||
:param suit: The face of the card, e.g. Spade or Diamond
|
||||
:param rank: The value of the card, e.g 3 or King
|
||||
"""
|
||||
self.suit = suit.capitalize()
|
||||
self.rank = rank
|
||||
self.points = self.card_values[rank]
|
||||
|
||||
# Function to output ascii version of the cards in a hand in the terminal
|
||||
def drawCardsVp(*cards, return_string=True):
|
||||
"""
|
||||
Instead of a boring text version of the card we render an ASCII image of the card.
|
||||
:param cards: One or more card objects
|
||||
:param return_string: By default we return the string version of the card, but the dealer hide the 1st card and we
|
||||
keep it as a list so that the dealer can add a hidden card in front of the list
|
||||
"""
|
||||
# we will use this to prints the appropriate icons for each card
|
||||
suits_name = ['Spades', 'Diamonds', 'Hearts', 'Clubs']
|
||||
suits_symbols = ['♠️', '♦️', '♥️', '♣️']
|
||||
|
||||
# create an empty list of list, each sublist is a line 2 lines for the card
|
||||
lines = [[] for i in range(1)]
|
||||
|
||||
for index, card in enumerate(cards):
|
||||
# "King" should be "K" and "10" should still be "10"
|
||||
if card.rank == 10: # ten is the only one who's rank is 2 char long
|
||||
rank = str(card.rank)
|
||||
else:
|
||||
rank = str(card.rank)[0] # some have a rank of 'King' this changes that to a simple 'K' ("King" doesn't fit)
|
||||
# get the cards suit in two steps
|
||||
suit = suits_name.index(card.suit)
|
||||
suit = suits_symbols[suit]
|
||||
|
||||
# add the individual card on a line by line basis
|
||||
lines[0].append('{}{} '.format(rank, suit))
|
||||
|
||||
result = []
|
||||
#result.append('1 2 3 4 5') # add the index for the cards to top row
|
||||
for index, line in enumerate(lines):
|
||||
result.append(''.join(lines[index]))
|
||||
|
||||
# hidden cards do not use string
|
||||
if return_string:
|
||||
return '\n'.join(result)
|
||||
else:
|
||||
return result
|
||||
|
||||
# Define Deck class
|
||||
class DeckVP:
|
||||
|
||||
def __init__(self):
|
||||
self.cards = []
|
||||
self.build()
|
||||
|
||||
# method for building the deck
|
||||
def build(self):
|
||||
for s in ['Spades', 'Diamonds', 'Hearts', 'Clubs']:
|
||||
for v in range(2, 11):
|
||||
self.cards.append(CardVP(s,v))
|
||||
for c in ["Jack", "Queen", "King", "Ace"]:
|
||||
self.cards.append(CardVP(s,c))
|
||||
|
||||
# method to show cards in deck
|
||||
def display(self):
|
||||
for c in self.cards:
|
||||
print(drawCardsVp(c))
|
||||
|
||||
# method to shuffle cards in deck
|
||||
def shuffle(self):
|
||||
for i in range(len(self.cards) - 1, 0, -1):
|
||||
r = random.randint(0, i)
|
||||
self.cards[i], self.cards[r] = self.cards[r], self.cards[i]
|
||||
|
||||
# method to draw card from the deck
|
||||
def draw_card(self):
|
||||
return self.cards.pop()
|
||||
|
||||
# Define Player Class
|
||||
class PlayerVP:
|
||||
def __init__(self):
|
||||
self.hand = []
|
||||
self.bankroll = 20
|
||||
|
||||
# Method for initial five-card draw
|
||||
def draw_cards(self, deck):
|
||||
for i in range(5):
|
||||
self.hand.append(deck.draw_card())
|
||||
return self
|
||||
|
||||
# Method for displaying player's hand
|
||||
def show_hand(self):
|
||||
msg = (drawCardsVp(
|
||||
self.hand[0],
|
||||
self.hand[1],
|
||||
self.hand[2],
|
||||
self.hand[3],
|
||||
self.hand[4]))
|
||||
return msg
|
||||
|
||||
# Method for placing a bet
|
||||
def bet(self, ammount=0):
|
||||
bet = int(ammount)
|
||||
self.bet_size = bet
|
||||
self.bankroll -= self.bet_size
|
||||
|
||||
# Method for selecting cards to redraw
|
||||
def redraw(self, deck, message):
|
||||
# if message has single digit, then it is the card to redraw, else it is the list of cards to redraw with a comma
|
||||
if len(message) == 1:
|
||||
try:
|
||||
# if single digit is the letter a redraw all cards
|
||||
if message.lower() == "a":
|
||||
for i in range(5):
|
||||
self.hand[i] = deck.draw_card()
|
||||
else:
|
||||
# error trap for bad input
|
||||
redraw_index = int(message) - 1
|
||||
self.hand[redraw_index] = deck.draw_card()
|
||||
|
||||
return self.show_hand()
|
||||
except Exception as e:
|
||||
pass
|
||||
else:
|
||||
try:
|
||||
# error trap for bad input
|
||||
if "," in message:
|
||||
redraw_list = [int(x) - 1 for x in message.split(',')]
|
||||
if "." in message:
|
||||
redraw_list = [int(x) - 1 for x in message.split('.')]
|
||||
if " " in message:
|
||||
redraw_list = [int(x) - 1 for x in message.split(' ')]
|
||||
for i in redraw_list:
|
||||
self.hand[i] = deck.draw_card()
|
||||
return self.show_hand()
|
||||
except Exception as e:
|
||||
pass
|
||||
|
||||
return "Re-Draw/Deal ex:1,3,4 to hold cards 1,3 and 4, or (N)o to keep current (H)and"
|
||||
|
||||
# Method for scoring hand, calculating winnings, and outputting message
|
||||
def score_hand(self, resetHand = True):
|
||||
points = sorted([self.hand[i].points for i in range(5)])
|
||||
suits = [self.hand[i].suit for i in range(5)]
|
||||
points_repeat = [points.count(i) for i in points]
|
||||
suits_repeat = [suits.count(i) for i in suits]
|
||||
diff = max(points) - min(points)
|
||||
hand_name = ""
|
||||
msg = ""
|
||||
payoff = {
|
||||
"👑Royal Flush🚽": 10,
|
||||
"🧻Straight Flush🚽": 9,
|
||||
"Flush🚽": 8,
|
||||
"Full House🏠": 7,
|
||||
"Four of a Kind👯👯": 6,
|
||||
"Three of a Kind☘️": 5,
|
||||
"Two Pair👯👯": 4,
|
||||
"Straight📏": 3,
|
||||
"Pair👯": 2,
|
||||
"Bad Hand 🙈": -1,
|
||||
}
|
||||
|
||||
if 5 in suits_repeat:
|
||||
if points == [10, 11, 12, 13, 14]: #find royal flush
|
||||
hand_name = "👑Royal Flush🚽"
|
||||
if resetHand:
|
||||
self.bankroll += self.bet_size * payoff[hand_name]
|
||||
elif diff == 4 and max(points_repeat) == 1: # find straight flush w/o ace low
|
||||
hand_name = "🧻Straight Flush🚽"
|
||||
if resetHand:
|
||||
self.bankroll += self.bet_size * payoff[hand_name]
|
||||
elif diff == 12 and points[4] == 14: # find straight flush w/ace low
|
||||
check = 0
|
||||
for i in range(1, 4):
|
||||
check += points[i] - points[i - 1]
|
||||
if check == 3:
|
||||
hand_name = "🧻Straight Flush🚽"
|
||||
if resetHand:
|
||||
self.bankroll += self.bet_size * payoff[hand_name]
|
||||
else:
|
||||
hand_name = "Flush🚽"
|
||||
if resetHand:
|
||||
self.bankroll += self.bet_size * payoff[hand_name]
|
||||
else:
|
||||
hand_name = "Flush🚽"
|
||||
if resetHand:
|
||||
self.bankroll += self.bet_size * payoff[hand_name]
|
||||
elif sorted(points_repeat) == [2,2,3,3,3]: # find full house
|
||||
hand_name = "Full House🏠"
|
||||
if resetHand:
|
||||
self.bankroll += self.bet_size * payoff[hand_name]
|
||||
elif 4 in points_repeat: # find four of a kind
|
||||
hand_name = "Four of a Kind👯👯"
|
||||
if resetHand:
|
||||
self.bankroll += self.bet_size * payoff[hand_name]
|
||||
elif 3 in points_repeat: # find three of a kind
|
||||
hand_name = "Three of a Kind☘️"
|
||||
if resetHand:
|
||||
self.bankroll += self.bet_size * payoff[hand_name]
|
||||
elif points_repeat.count(2) == 4: # find two-pair
|
||||
hand_name = "Two Pair👯👯"
|
||||
if resetHand:
|
||||
self.bankroll += self.bet_size * payoff[hand_name]
|
||||
elif 2 in points_repeat: # find pair
|
||||
hand_name = "Pair👯"
|
||||
if resetHand:
|
||||
self.bankroll += self.bet_size * payoff[hand_name]
|
||||
elif diff == 4 and max(points_repeat) == 1: # find straight w/o ace low
|
||||
hand_name = "Straight📏"
|
||||
if resetHand:
|
||||
self.bankroll += self.bet_size * payoff[hand_name]
|
||||
elif diff == 12 and points[4] == 14: # find straight w/ace low
|
||||
check = 0
|
||||
for i in range(1, 4):
|
||||
check += points[i] - points[i - 1]
|
||||
if check == 3:
|
||||
hand_name = "Straight📏"
|
||||
if resetHand:
|
||||
self.bankroll += self.bet_size * payoff[hand_name]
|
||||
else:
|
||||
hand_name = "Bad Hand 🙈"
|
||||
else: # for everything Hand
|
||||
hand_name = "Bad Hand 🙈"
|
||||
|
||||
if resetHand:
|
||||
self.hand = []
|
||||
msg = f"\nYour hand, {hand_name}. Your bankroll is now {self.bankroll} coins."
|
||||
else:
|
||||
if hand_name != "":
|
||||
msg = f"\nShowing:{hand_name}"
|
||||
return msg
|
||||
|
||||
|
||||
def getLastCmdVp(nodeID):
|
||||
last_cmd = ""
|
||||
for i in range(len(vpTracker)):
|
||||
if vpTracker[i]['nodeID'] == nodeID:
|
||||
last_cmd = vpTracker[i]['cmd']
|
||||
return last_cmd
|
||||
|
||||
def setLastCmdVp(nodeID, cmd):
|
||||
for i in range(len(vpTracker)):
|
||||
if vpTracker[i]['nodeID'] == nodeID:
|
||||
vpTracker[i]['cmd'] = cmd
|
||||
|
||||
def saveHSVp(nodeID, highScore):
|
||||
# Save the game high_score to pickle
|
||||
highScore = {'nodeID': nodeID, 'highScore': highScore}
|
||||
try:
|
||||
with open('videopoker_hs.pkl', 'wb') as file:
|
||||
pickle.dump(highScore, file)
|
||||
except FileNotFoundError:
|
||||
logger.debug("System: BlackJack: Creating new videopoker_hs.pkl file")
|
||||
with open('videopoker_hs.pkl', 'wb') as file:
|
||||
pickle.dump(highScore, file)
|
||||
|
||||
def loadHSVp():
|
||||
# Load the game high_score from pickle
|
||||
try:
|
||||
with open('videopoker_hs.pkl', 'rb') as file:
|
||||
highScore = pickle.load(file)
|
||||
return highScore
|
||||
except FileNotFoundError:
|
||||
logger.debug("System: VideoPoker: Creating new videopoker_hs.pkl file")
|
||||
highScore = {'nodeID': 0, 'highScore': 0}
|
||||
with open('videopoker_hs.pkl', 'wb') as file:
|
||||
pickle.dump(highScore, file)
|
||||
return 0
|
||||
|
||||
def playVideoPoker(nodeID, message):
|
||||
msg = ""
|
||||
|
||||
# Initialize the player
|
||||
if getLastCmdVp(nodeID) is None or getLastCmdVp(nodeID=nodeID) == "":
|
||||
# create new player if not in tracker
|
||||
logger.debug(f"System: VideoPoker: New Player {nodeID}")
|
||||
vpTracker.append({'nodeID': nodeID, 'cmd': 'new', 'time': time.time(), 'cash': vpStartingCash, 'player': None, 'deck': None, 'highScore': 0, 'drawCount': 0})
|
||||
return f"Welcome to 🎰VideoPoker♥️ you have {vpStartingCash} coins, Whats your bet?"
|
||||
|
||||
# Gather the player's bet
|
||||
if getLastCmdVp(nodeID) == "new" or getLastCmdVp(nodeID) == "gameOver":
|
||||
# Initialize shuffled Deck and Player
|
||||
player = PlayerVP()
|
||||
deck = DeckVP()
|
||||
deck.shuffle()
|
||||
drawCount = 1
|
||||
bet = 0
|
||||
msg = ''
|
||||
|
||||
# load the player bankroll from tracker
|
||||
for i in range(len(vpTracker)):
|
||||
if vpTracker[i]['nodeID'] == nodeID:
|
||||
player.bankroll = vpTracker[i]['cash']
|
||||
vpTracker[i]['time'] = time.time()
|
||||
|
||||
# Detect if message is a bet
|
||||
try:
|
||||
bet = int(message)
|
||||
except ValueError:
|
||||
msg += "Please enter a valid bet amount. 1 to 5 coins."
|
||||
|
||||
# Check if bet is valid
|
||||
if bet > player.bankroll:
|
||||
msg += "You can only bet the money you have. No strip poker here..."
|
||||
elif bet < 1:
|
||||
msg += "You must bet at least 1 coin."
|
||||
elif bet > 5:
|
||||
msg += "You can only bet up to 5 coins."
|
||||
|
||||
# if msg contains an error, return it
|
||||
if msg is not None and msg != '':
|
||||
return msg
|
||||
else:
|
||||
# Take the bet
|
||||
player.bet(str(message))
|
||||
# Bet placed, start the game
|
||||
setLastCmdVp(nodeID, "playing")
|
||||
|
||||
# save player and deck to tracker
|
||||
for i in range(len(vpTracker)):
|
||||
if vpTracker[i]['nodeID'] == nodeID:
|
||||
vpTracker[i]['player'] = player
|
||||
vpTracker[i]['deck'] = deck
|
||||
vpTracker[i]['cash'] = player.bankroll
|
||||
|
||||
# Play the game
|
||||
if getLastCmdVp(nodeID) == "playing":
|
||||
msg = ''
|
||||
|
||||
player.draw_cards(deck)
|
||||
msg += player.show_hand()
|
||||
# give hint to player
|
||||
msg += player.score_hand(resetHand=False)
|
||||
|
||||
# save player and deck to tracker
|
||||
for i in range(len(vpTracker)):
|
||||
if vpTracker[i]['nodeID'] == nodeID:
|
||||
vpTracker[i]['player'] = player
|
||||
vpTracker[i]['deck'] = deck
|
||||
vpTracker[i]['drawCount'] = drawCount
|
||||
|
||||
msg += f"\nDeal new card? \nex: 1,3,4 or (N)o,(A)ll (H)and"
|
||||
setLastCmdVp(nodeID, "redraw")
|
||||
return msg
|
||||
|
||||
if getLastCmdVp(nodeID) == "redraw":
|
||||
msg = ''
|
||||
# load the player and deck from tracker
|
||||
for i in range(len(vpTracker)):
|
||||
if vpTracker[i]['nodeID'] == nodeID:
|
||||
player = vpTracker[i]['player']
|
||||
deck = vpTracker[i]['deck']
|
||||
drawCount = vpTracker[i]['drawCount']
|
||||
|
||||
# if player wants to redraw cards, and not done already
|
||||
if message.lower().startswith("n"):
|
||||
setLastCmdVp(nodeID, "endGame")
|
||||
if message.lower().startswith("h"):
|
||||
msg = player.show_hand()
|
||||
return msg
|
||||
else:
|
||||
if drawCount <= 1:
|
||||
msg = player.redraw(deck, message)
|
||||
if msg.startswith("Send Card"):
|
||||
# if returned error message, return it
|
||||
return msg
|
||||
drawCount += 1
|
||||
# save player and deck to tracker
|
||||
for i in range(len(vpTracker)):
|
||||
if vpTracker[i]['nodeID'] == nodeID:
|
||||
vpTracker[i]['player'] = player
|
||||
vpTracker[i]['deck'] = deck
|
||||
vpTracker[i]['drawCount'] = drawCount
|
||||
if drawCount == 2:
|
||||
# this is the last draw will carry on to endGame for scoring
|
||||
msg = player.redraw(deck, message) + f"\n"
|
||||
if msg.startswith("Send Card"):
|
||||
# if returned error message, return it
|
||||
return msg
|
||||
# redraw done
|
||||
setLastCmdVp(nodeID, "endGame")
|
||||
else:
|
||||
# show redrawn hand
|
||||
return msg
|
||||
else:
|
||||
# redraw already done
|
||||
setLastCmdVp(nodeID, "endGame")
|
||||
|
||||
if getLastCmdVp(nodeID) == "endGame":
|
||||
# load the player and deck from tracker
|
||||
for i in range(len(vpTracker)):
|
||||
if vpTracker[i]['nodeID'] == nodeID:
|
||||
player = vpTracker[i]['player']
|
||||
deck = vpTracker[i]['deck']
|
||||
|
||||
msg += player.score_hand()
|
||||
|
||||
if player.bankroll < 1:
|
||||
player.bankroll = vpStartingCash
|
||||
msg += "\nLooks 💸 like you're out of money. 💳 resetting ballance 🏧"
|
||||
elif player.bankroll > vpTracker[i]['highScore']:
|
||||
vpTracker[i]['highScore'] = player.bankroll
|
||||
msg += " 🎉HighScore!"
|
||||
# save high score
|
||||
saveHSVp(nodeID, vpTracker[i]['highScore'])
|
||||
|
||||
msg += f"\nPlace your Bet, 'L' to leave the game."
|
||||
|
||||
setLastCmdVp(nodeID, "gameOver")
|
||||
# reset player and deck in tracker
|
||||
for i in range(len(vpTracker)):
|
||||
if vpTracker[i]['nodeID'] == nodeID:
|
||||
vpTracker[i]['player'] = None
|
||||
vpTracker[i]['deck'] = None
|
||||
vpTracker[i]['drawCount'] = 0
|
||||
# save bankroll
|
||||
vpTracker[i]['cash'] = player.bankroll
|
||||
|
||||
return msg
|
||||
|
||||
|
||||
@@ -94,10 +94,20 @@ def get_wx_meteo(lat=0, lon=0, unit=0):
|
||||
code_string = ""
|
||||
if daily_weather_code[i] == 0:
|
||||
code_string = "Clear sky"
|
||||
elif daily_weather_code[i] == 1 or 2 or 3:
|
||||
code_string = "Partly cloudy"
|
||||
elif daily_weather_code[i] == 45 or 48:
|
||||
elif daily_weather_code[i] == 1:
|
||||
code_string = "Mostly Cloudy"
|
||||
elif daily_weather_code[i] == 2:
|
||||
code_string = "Partly Cloudy"
|
||||
elif daily_weather_code[i] == 3:
|
||||
code_string = "Overcast"
|
||||
elif daily_weather_code[i] == 5:
|
||||
code_string = "Haze"
|
||||
elif daily_weather_code[i] == 10:
|
||||
code_string = "Mist"
|
||||
elif daily_weather_code[i] == 45:
|
||||
code_string = "Fog"
|
||||
elif daily_weather_code[i] == 48:
|
||||
code_string = "Freezing Fog"
|
||||
elif daily_weather_code[i] == 51:
|
||||
code_string = "Drizzle: Light"
|
||||
elif daily_weather_code[i] == 53:
|
||||
@@ -126,6 +136,10 @@ def get_wx_meteo(lat=0, lon=0, unit=0):
|
||||
code_string = "Snow: Heavy"
|
||||
elif daily_weather_code[i] == 77:
|
||||
code_string = "Snow Grains"
|
||||
elif daily_weather_code[i] == 78:
|
||||
code_string = "Ice Crystals"
|
||||
elif daily_weather_code[i] == 79:
|
||||
code_string = "Ice Pellets"
|
||||
elif daily_weather_code[i] == 80:
|
||||
code_string = "Rain showers: Slight"
|
||||
elif daily_weather_code[i] == 81:
|
||||
@@ -133,15 +147,17 @@ def get_wx_meteo(lat=0, lon=0, unit=0):
|
||||
elif daily_weather_code[i] == 82:
|
||||
code_string = "Rain showers: Heavy"
|
||||
elif daily_weather_code[i] == 85:
|
||||
code_string = "Snow showers: Light"
|
||||
code_string = "Snow showers"
|
||||
elif daily_weather_code[i] == 86:
|
||||
code_string = "Snow showers: Moderate"
|
||||
code_string = "Snow showers: Heavy"
|
||||
elif daily_weather_code[i] == 95:
|
||||
code_string = "Thunderstorm: Slight"
|
||||
code_string = "Thunderstorm"
|
||||
elif daily_weather_code[i] == 96:
|
||||
code_string = "Thunderstorm: Moderate"
|
||||
code_string = "Hailstorm"
|
||||
elif daily_weather_code[i] == 97:
|
||||
code_string = "Thunderstorm Heavy"
|
||||
elif daily_weather_code[i] == 99:
|
||||
code_string = "Thunderstorm: Heavy"
|
||||
code_string = "Hailstorm Heavy"
|
||||
|
||||
weather_report += "Cond: " + code_string + ". "
|
||||
|
||||
|
||||
42
pong_bot.py
42
pong_bot.py
@@ -8,6 +8,8 @@ from pubsub import pub # pip install pubsub
|
||||
from modules.log import *
|
||||
from modules.system import *
|
||||
|
||||
DEBUGpacket = False # Debug print the packet rx
|
||||
|
||||
def auto_response(message, snr, rssi, hop, message_from_id, channel_number, deviceID):
|
||||
# Auto response to messages
|
||||
message_lower = message.lower()
|
||||
@@ -37,8 +39,8 @@ def auto_response(message, snr, rssi, hop, message_from_id, channel_number, devi
|
||||
# run the first command after sorting
|
||||
bot_response = command_handler[cmds[0]['cmd']]()
|
||||
|
||||
# wait a 700ms to avoid message collision from lora-ack
|
||||
time.sleep(0.7)
|
||||
# wait a responseDelay to avoid message collision from lora-ack
|
||||
time.sleep(responseDelay)
|
||||
|
||||
return bot_response
|
||||
|
||||
@@ -75,15 +77,15 @@ def handle_lheard(interface1, interface2_enabled, myNodeNum1, myNodeNum2):
|
||||
|
||||
def handle_ack(hop, snr, rssi):
|
||||
if hop == "Direct":
|
||||
return "🏓ACK-ACK! " + f"SNR:{snr} RSSI:{rssi}"
|
||||
return "✋ACK-ACK! " + f"SNR:{snr} RSSI:{rssi}"
|
||||
else:
|
||||
return "🏓ACK-ACK! " + hop
|
||||
return "✋ACK-ACK! " + hop
|
||||
|
||||
def handle_testing(hop, snr, rssi):
|
||||
if hop == "Direct":
|
||||
return "🏓Testing 1,2,3 " + f"SNR:{snr} RSSI:{rssi}"
|
||||
return "🎙Testing 1,2,3 " + f"SNR:{snr} RSSI:{rssi}"
|
||||
else:
|
||||
return "🏓Testing 1,2,3 " + hop
|
||||
return "🎙Testing 1,2,3 " + hop
|
||||
|
||||
def onDisconnect(interface):
|
||||
global retry_int1, retry_int2
|
||||
@@ -115,8 +117,18 @@ def onReceive(packet, interface):
|
||||
# extract interface defailts from interface object
|
||||
rxType = type(interface).__name__
|
||||
rxNode = 0
|
||||
# Debug print the interface object
|
||||
#for item in interface.__dict__.items(): print (item)
|
||||
message_from_id = 0
|
||||
snr = 0
|
||||
rssi = 0
|
||||
hop = 0
|
||||
hop_away = 0
|
||||
|
||||
if DEBUGpacket:
|
||||
# Debug print the interface object
|
||||
for item in interface.__dict__.items(): intDebug = f"{item}\n"
|
||||
logger.debug(f"System: Packet Received on {rxType} Interface\n {intDebug} \n END of interface \n")
|
||||
# Debug print the packet for debugging
|
||||
logger.debug(f"Packet Received\n {packet} \n END of packet \n")
|
||||
|
||||
if rxType == 'SerialInterface':
|
||||
rxInterface = interface.__dict__.get('devPath', 'unknown')
|
||||
@@ -138,10 +150,6 @@ def onReceive(packet, interface):
|
||||
elif interface2_enabled and interface2_type == 'ble':
|
||||
rxNode = 2
|
||||
|
||||
# Debug print the packet for debugging
|
||||
#print(f"Packet Received\n {packet} \n END of packet \n")
|
||||
message_from_id = 0
|
||||
|
||||
# check for a message packet and process it
|
||||
try:
|
||||
if 'decoded' in packet and packet['decoded']['portnum'] == 'TEXT_MESSAGE_APP':
|
||||
@@ -250,8 +258,8 @@ def onReceive(packet, interface):
|
||||
|
||||
# repeat the message on the other device
|
||||
if repeater_enabled and interface2_enabled:
|
||||
# wait a 700ms to avoid message collision from lora-ack.
|
||||
time.sleep(0.7)
|
||||
# wait a responseDelay to avoid message collision from lora-ack.
|
||||
time.sleep(responseDelay)
|
||||
rMsg = (f"{message_string} From:{get_name_from_number(message_from_id, 'short', rxNode)}")
|
||||
# if channel found in the repeater list repeat the message
|
||||
if str(channel_number) in repeater_channels:
|
||||
@@ -277,13 +285,13 @@ async def start_rx():
|
||||
logger.info(f"System: Autoresponder Started for Device2 {get_name_from_number(myNodeNum2, 'long', 2)},"
|
||||
f"{get_name_from_number(myNodeNum2, 'short', 2)}. NodeID: {myNodeNum2}, {decimal_to_hex(myNodeNum2)}")
|
||||
if log_messages_to_file:
|
||||
logger.debug(f"System: Logging Messages to disk")
|
||||
logger.debug("System: Logging Messages to disk")
|
||||
if sentry_enabled:
|
||||
logger.debug(f"System: Sentry Enabled")
|
||||
logger.debug("System: Sentry Enabled")
|
||||
if store_forward_enabled:
|
||||
logger.debug(f"System: Store and Forward Enabled using limit: {storeFlimit}")
|
||||
if useDMForResponse:
|
||||
logger.debug(f"System: Respond by DM only")
|
||||
logger.debug("System: Respond by DM only")
|
||||
if repeater_enabled and interface2_enabled:
|
||||
logger.debug(f"System: Repeater Enabled for Channels: {repeater_channels}")
|
||||
if radio_detection_enabled:
|
||||
|
||||
@@ -16,4 +16,4 @@ wikipedia
|
||||
langchain
|
||||
langchain-ollama
|
||||
ollama
|
||||
|
||||
googlesearch-python
|
||||
|
||||
Reference in New Issue
Block a user