Compare commits

...

465 Commits
v1.1.0 ... v1.4

Author SHA1 Message Date
SpudGunMan
abeb28c9cc Update golfsim.py 2024-10-02 23:20:59 -07:00
SpudGunMan
2325f74581 Update simulator.py 2024-10-02 15:13:28 -07:00
SpudGunMan
e75ef3e44d Update simulator.py 2024-10-02 14:51:46 -07:00
SpudGunMan
6431b45769 Update mesh_bot.py 2024-10-02 10:39:48 -07:00
SpudGunMan
7dfcbb619f Update simulator.py 2024-10-02 01:39:07 -07:00
SpudGunMan
051b58ca6f bleEnhance 2024-10-01 17:17:50 -07:00
SpudGunMan
5777b39c22 Update mmind.py 2024-10-01 14:11:38 -07:00
SpudGunMan
eb4f135698 🪣 2024-10-01 11:16:26 -07:00
SpudGunMan
3bcb04ece7 ️HighScore 2024-10-01 00:17:52 -07:00
SpudGunMan
e42aca875f fixStroke 2024-09-30 23:49:56 -07:00
SpudGunMan
28915ab848 Update golfsim.py 2024-09-30 23:25:38 -07:00
SpudGunMan
0fe491871d Update golfsim.py 2024-09-30 23:25:12 -07:00
SpudGunMan
6fc6a483a8 Update golfsim.py 2024-09-30 23:06:27 -07:00
SpudGunMan
13236880b5 Update golfsim.py 2024-09-30 22:56:37 -07:00
SpudGunMan
9461719039 Update golfsim.py 2024-09-30 22:50:31 -07:00
SpudGunMan
63163cc4c1 ️Caddy
added a caddy
2024-09-30 22:28:59 -07:00
SpudGunMan
5cdf159bc1 add TZ local
resolves https://github.com/SpudGunMan/meshing-around/issues/72
2024-09-30 21:17:03 -07:00
SpudGunMan
47a9981fdf enhance
resolve https://github.com/SpudGunMan/meshing-around/issues/73

new game play hazards
2024-09-30 21:16:00 -07:00
SpudGunMan
f85bdb7d02 clubOrder🥪
resolve https://github.com/SpudGunMan/meshing-around/issues/75
2024-09-30 16:56:41 -07:00
SpudGunMan
7796d03e21 Update mmind.py 2024-09-30 16:53:33 -07:00
SpudGunMan
90094c082a Update mmind.py
resolved https://github.com/SpudGunMan/meshing-around/issues/74
2024-09-30 16:53:11 -07:00
SpudGunMan
2b695e2f2e Update wx_meteo.py 2024-09-30 16:43:10 -07:00
SpudGunMan
2e05f3ef64 Update wx_meteo.py
reference
https://github.com/open-meteo/open-meteo/issues/287

and also https://github.com/SpudGunMan/meshing-around/issues/71
2024-09-30 16:39:25 -07:00
SpudGunMan
316f1efd08 Update Dockerfile 2024-09-30 16:18:37 -07:00
SpudGunMan
4678a63955 enhance
expert mode and saves score
2024-09-30 12:56:04 -07:00
SpudGunMan
8584454d5d Update README.md 2024-09-30 09:15:28 -07:00
SpudGunMan
2c6cf76a10 Update mmind.py 2024-09-30 03:17:15 -07:00
SpudGunMan
cd3226df21 enhance 2024-09-30 03:04:38 -07:00
SpudGunMan
4bcc6ef1f2 Update mesh_bot.py 2024-09-30 02:57:46 -07:00
SpudGunMan
77e56c25ae golfEnhance 2024-09-30 02:51:54 -07:00
SpudGunMan
e7b363612a timePlay 2024-09-30 02:44:46 -07:00
SpudGunMan
a217c61ba1 LateNightDoubleFeature
* golfsim
* masterMind

New Games!
2024-09-30 02:37:08 -07:00
SpudGunMan
e7b4fe44c8 Update simulator.py 2024-09-29 21:39:05 -07:00
SpudGunMan
f8cc580b99 Update mesh_bot.py 2024-09-29 15:01:45 -07:00
SpudGunMan
03057b3263 Update mesh_bot.py 2024-09-29 12:55:41 -07:00
SpudGunMan
452b9b7c67 Update mesh_bot.py 2024-09-29 12:50:57 -07:00
SpudGunMan
5ae16d0adc Update videopoker.py 2024-09-29 12:49:14 -07:00
SpudGunMan
5ed135c023 Update README.md 2024-09-28 14:05:03 -07:00
SpudGunMan
d425298cd9 Update dopewar.py 2024-09-28 12:17:16 -07:00
SpudGunMan
786815d073 Update dopewar.py 2024-09-28 12:04:17 -07:00
SpudGunMan
54cad92a3f enhance 💊 game display 2024-09-28 12:03:08 -07:00
SpudGunMan
54e21f4644 Update mesh_bot.py 2024-09-28 00:53:19 -07:00
SpudGunMan
3c76f177cd Update simulator.py 2024-09-28 00:46:47 -07:00
SpudGunMan
aa05c62d94 fix🍋hs 2024-09-28 00:45:57 -07:00
SpudGunMan
3f16158e27 Update mesh_bot.py 2024-09-28 00:28:59 -07:00
SpudGunMan
6f2824512d highscoreEnhance 2024-09-28 00:27:45 -07:00
SpudGunMan
723b67f886 🥔 2024-09-28 00:14:27 -07:00
SpudGunMan
008d55e63b Update mesh_bot.py 2024-09-28 00:12:33 -07:00
SpudGunMan
79885454ab fixLongStandingIssues
fix long time bugs of 21 and the suggestions
2024-09-27 23:47:14 -07:00
SpudGunMan
ba21723bdc fix BlackJackwin! 2024-09-27 23:38:40 -07:00
SpudGunMan
c36c4918a8 Update mesh_bot.py 2024-09-27 22:33:07 -07:00
SpudGunMan
853147518d space 2024-09-27 22:32:46 -07:00
SpudGunMan
2f19d86c95 💩
: get it
2024-09-27 22:17:07 -07:00
SpudGunMan
39bdabffcb Update blackjack.py 2024-09-27 21:57:40 -07:00
SpudGunMan
a7bdaedfe1 Update blackjack.py 2024-09-27 21:57:22 -07:00
SpudGunMan
1c6106081f Weight2Wait
add weight to the wait for responses. as the comment says this is a specific use case for the Q: blah blah A: 2. I am seeing a hang up this is to help keep delivery high.
2024-09-27 21:48:13 -07:00
SpudGunMan
8ab6cded2e 🥝 2024-09-27 21:14:06 -07:00
SpudGunMan
ff63bb959a Update mesh_bot.py 2024-09-27 20:55:50 -07:00
SpudGunMan
6c79bb1ff0 Update mesh_bot.py 2024-09-27 20:50:36 -07:00
SpudGunMan
ce73336c0c Update mesh_bot.py 2024-09-27 20:49:05 -07:00
SpudGunMan
248977c5b5 enhance
* replay hand with H or R
2024-09-27 18:06:11 -07:00
SpudGunMan
77a6e63210 howRandom 2024-09-27 17:39:55 -07:00
SpudGunMan
6f6fb35177 IgnoreDefaultChannel
ability to fully ignore the default channel if wanted, default is false
2024-09-27 17:37:34 -07:00
SpudGunMan
9db565cb4f changeLogPath
tidy up this mess
2024-09-27 17:18:35 -07:00
SpudGunMan
2a254a7fab fixNewTable.pkl 2024-09-27 01:06:15 -07:00
SpudGunMan
15e76ab029 fixCasinoGamesForGood? 2024-09-27 00:57:57 -07:00
SpudGunMan
66b95cdaa0 casinoHighScores
I got a really good score its a shame it was wasted
2024-09-27 00:25:17 -07:00
SpudGunMan
32ea93cb88 Update mesh_bot.py 2024-09-26 22:11:42 -07:00
SpudGunMan
22a2a64861 Update bbstools.py 2024-09-26 21:08:29 -07:00
SpudGunMan
d841fdf02c bbsinfo 2024-09-26 21:03:57 -07:00
Kelly
9421f09ded Merge pull request #69 from SpudGunMan/lab
messages from !hex ID
2024-09-26 20:56:35 -07:00
SpudGunMan
b4af552fb9 Update mesh_bot.py 2024-09-26 20:55:32 -07:00
Kelly
69dfb17460 Merge pull request #68 from Nestpebble/main
Add in BBS-DM via node hex ID
2024-09-26 20:47:09 -07:00
SpudGunMan
4703750c27 Update videopoker.py 2024-09-26 20:45:49 -07:00
Nestpebble
40caf99939 Merge branch 'SpudGunMan:main' into main 2024-09-27 03:43:45 +01:00
Nestpebble
df5f648b26 try and round off adding an interpreter to the bbs DM 2024-09-27 03:30:21 +01:00
Nestpebble
55472bbbc0 try this 2024-09-27 03:12:40 +01:00
Nestpebble
f23c4e2b6a sdf 2024-09-27 02:29:58 +01:00
Nestpebble
0b101d662e trying to simplify nodeid entry... 2024-09-27 02:26:55 +01:00
SpudGunMan
a7f07afc14 consolidateTime 2024-09-26 18:13:58 -07:00
SpudGunMan
2715021898 Update README.md 2024-09-26 18:02:52 -07:00
SpudGunMan
e8fa0036e2 Update mesh_bot.py 2024-09-26 17:57:05 -07:00
SpudGunMan
f628a5e7ef Update mesh_bot.py 2024-09-26 17:56:56 -07:00
SpudGunMan
95e6bc120e Update mesh_bot.py 2024-09-26 17:32:34 -07:00
SpudGunMan
0e35c891c4 Update mesh_bot.py 2024-09-26 17:29:54 -07:00
SpudGunMan
b7a3e7014c Update mesh_bot.py 2024-09-26 17:26:42 -07:00
Nestpebble
0c1c587bc7 strip ! from tonode, cos its copied by default on
the android app.
2024-09-27 01:26:01 +01:00
SpudGunMan
a0a2c60e63 Update mesh_bot.py 2024-09-26 17:25:29 -07:00
SpudGunMan
45c912a0d6 Update mesh_bot.py 2024-09-26 17:19:50 -07:00
Kelly
39945f161d Merge pull request #66 from Nestpebble/main
Help messages, condensed ping
2024-09-26 17:15:46 -07:00
Nestpebble
ed958302bd sdfsdfsdf 2024-09-26 23:07:19 +01:00
Nestpebble
477f2141d7 sdfsdf 2024-09-26 22:57:20 +01:00
Nestpebble
d321a958f0 more tidying 2024-09-26 22:50:45 +01:00
Nestpebble
d14f1df823 tweak some help messages 2024-09-26 22:49:20 +01:00
Nestpebble
f7e3b9f6c7 Merge branch 'SpudGunMan:main' into main 2024-09-26 22:25:20 +01:00
Nestpebble
cd3ac201f8 general tidy up, done 2024-09-26 22:23:15 +01:00
Nestpebble
ceef493b01 dfgdf 2024-09-26 22:17:38 +01:00
Nestpebble
480a75e30c Merge branch 'main' of https://github.com/Nestpebble/meshing-around 2024-09-26 22:17:02 +01:00
Nestpebble
d8cc953fe7 Removed handle_testing & handle_ack,
now in handle_ping
2024-09-26 22:16:17 +01:00
Nestpebble
0baec88321 Removed handle_testing & handle_ack,
now in handle_ping
2024-09-26 22:12:10 +01:00
Nestpebble
74bd3f681f dssdf 2024-09-26 22:05:31 +01:00
Nestpebble
713b750f4a sdfsd 2024-09-26 21:56:41 +01:00
Nestpebble
11eee911ca sdfsdf 2024-09-26 21:54:47 +01:00
Nestpebble
b288aaea90 szadsad 2024-09-26 21:32:25 +01:00
Nestpebble
7acc018fd2 sdfsd 2024-09-26 21:30:35 +01:00
Nestpebble
7aba1096f9 szd 2024-09-26 21:20:30 +01:00
Nestpebble
0be7202144 dsfrg 2024-09-26 21:18:49 +01:00
Nestpebble
83a5db74e5 Merge branch 'main' of https://github.com/Nestpebble/meshing-around 2024-09-26 21:16:20 +01:00
Nestpebble
8dc4371beb deghdfgdrfgdf 2024-09-26 21:16:08 +01:00
Nestpebble
e5045a0984 drfgdf 2024-09-26 21:14:29 +01:00
Nestpebble
2c9b37a0cc sdfsdfsdf 2024-09-26 21:09:53 +01:00
Nestpebble
b608482220 typo 2024-09-26 21:05:11 +01:00
Nestpebble
9290fac899 I think I consolidated the pings... 2024-09-26 21:03:43 +01:00
Nestpebble
d7901ee575 sdfsdfsd 2024-09-26 18:57:55 +01:00
Nestpebble
7eb33a5aef sdfsdfsdf 2024-09-26 18:56:36 +01:00
Nestpebble
c5dc103ac0 compressed ping response 2024-09-26 15:28:48 +01:00
Nestpebble
c90172a862 fixed it 2024-09-26 14:31:58 +01:00
Nestpebble
8540786c2c added help text to history 2024-09-26 14:28:49 +01:00
Nestpebble
7aeb8e851d Update mesh_bot.py 2024-09-26 14:24:27 +01:00
Nestpebble
2f207dc3d9 Update mesh_bot.py 2024-09-26 14:18:25 +01:00
Nestpebble
7e2be73962 Update mesh_bot.py 2024-09-26 14:17:29 +01:00
SpudGunMan
e2a87eb945 ReticulatingSplines
Reticulating some Splines and other items which need full Reticulation.
2024-09-26 00:18:07 -07:00
SpudGunMan
4b79c6304f Update install.sh 2024-09-25 23:00:02 -07:00
SpudGunMan
22f63e1056 cleanup 2024-09-25 22:56:01 -07:00
SpudGunMan
bf1613ba66 Update mesh_bot.py 2024-09-25 22:40:26 -07:00
SpudGunMan
38e04db0cb Update install.sh 2024-09-25 22:38:45 -07:00
SpudGunMan
08cc3034a2 Update install.sh 2024-09-25 22:37:13 -07:00
SpudGunMan
a0eb176b6e Update install.sh 2024-09-25 22:33:32 -07:00
SpudGunMan
a43774b33f Update install.sh 2024-09-25 22:32:01 -07:00
SpudGunMan
8369b4d205 Update install.sh 2024-09-25 22:26:37 -07:00
SpudGunMan
87e51cf9f9 Update install.sh 2024-09-25 22:24:08 -07:00
SpudGunMan
8a8fb79fde Update install.sh 2024-09-25 22:19:36 -07:00
SpudGunMan
1dc4cf36b1 Update install.sh 2024-09-25 22:16:13 -07:00
SpudGunMan
75cf43e02a enhance 2024-09-25 21:58:34 -07:00
SpudGunMan
dd8357453b serviceFix 2024-09-25 21:43:04 -07:00
SpudGunMan
e9b273a62c Update system.py 2024-09-25 21:23:12 -07:00
SpudGunMan
ebebd0fda6 Update install.sh 2024-09-25 21:09:29 -07:00
SpudGunMan
3e06902b07 Update config.template 2024-09-25 21:09:11 -07:00
SpudGunMan
a874b42c41 Update install.sh 2024-09-25 19:27:30 -07:00
Nestpebble
f6215d3563 Tidied up, added a few help messages. 2024-09-26 02:02:26 +01:00
SpudGunMan
4fad58f7fe Update system.py 2024-09-25 17:12:05 -07:00
SpudGunMan
276e4c3d09 ? trap 2024-09-25 16:38:27 -07:00
SpudGunMan
8aef4d605b Update README.md 2024-09-25 15:42:59 -07:00
SpudGunMan
962c891baa fixes 2024-09-25 15:40:50 -07:00
Kelly
eb596ea901 Merge pull request #65 from SpudGunMan/lab
enhance CMD Logic with isDM
2024-09-25 14:30:20 -07:00
SpudGunMan
e708ec9adc Update mesh_bot.py 2024-09-25 14:27:39 -07:00
Kelly
c631a083ea Merge pull request #64 from Nestpebble/main
Nestpebble isDM
2024-09-25 13:36:29 -07:00
Nestpebble
169ea8c233 Update mesh_bot.py 2024-09-25 13:12:08 +01:00
Nestpebble
241e2258e8 Update mesh_bot.py 2024-09-25 13:04:08 +01:00
Nestpebble
5d2d6bc5fb utilise isDM to split responses 2024-09-25 12:53:42 +01:00
Nestpebble
c7ef22f5c2 Update mesh_bot.py 2024-09-25 12:41:52 +01:00
Nestpebble
3ec89decf0 Update mesh_bot.py 2024-09-25 12:36:39 +01:00
SpudGunMan
2693f47ed5 Update locationdata.py 2024-09-24 20:01:05 -07:00
SpudGunMan
496a13e0e0 Update mesh_bot.py 2024-09-24 19:33:17 -07:00
SpudGunMan
71ef416dbb Update videopoker.py 2024-09-24 19:32:48 -07:00
SpudGunMan
5bc80c8677 Update dopewar.py 2024-09-24 19:32:44 -07:00
SpudGunMan
8c300f467c Update blackjack.py 2024-09-24 19:32:39 -07:00
SpudGunMan
78b3bf1475 Update bbstools.py 2024-09-24 19:32:32 -07:00
SpudGunMan
ce74f910a7 Update pong_bot.py 2024-09-24 19:32:26 -07:00
SpudGunMan
ece531249e Update bbsdb_admin.py 2024-09-24 19:32:21 -07:00
Nestpebble
5052e2510e Update mesh_bot.py 2024-09-25 01:33:21 +01:00
Nestpebble
b8f8f80499 Update mesh_bot.py 2024-09-25 01:28:47 +01:00
Nestpebble
a13d98e32b Update mesh_bot.py 2024-09-25 01:19:18 +01:00
SpudGunMan
94996dcec8 fixHours 2024-09-24 12:31:44 -07:00
SpudGunMan
4b8ce30df8 sleep1
the short responces of a single letter cause quick turn around
2024-09-24 12:28:51 -07:00
SpudGunMan
485a37e9b5 Update mesh_bot.py 2024-09-23 22:58:36 -07:00
SpudGunMan
b41adb12f7 Update blackjack.py 2024-09-23 22:54:46 -07:00
SpudGunMan
a9cd443bdd Update mesh_bot.py 2024-09-23 21:03:05 -07:00
SpudGunMan
92b9df718f gameplayEnhance 2024-09-23 20:03:34 -07:00
SpudGunMan
a6a4f91d83 Update mesh_bot.py 2024-09-23 19:43:23 -07:00
SpudGunMan
0fb2c498f4 Update mesh_bot.py 2024-09-23 19:43:00 -07:00
SpudGunMan
d87e70f7ee Update mesh_bot.py 2024-09-23 19:35:22 -07:00
SpudGunMan
bee8bae0ae Revert "fixing"
This reverts commit d5ef277121.
2024-09-23 19:29:14 -07:00
SpudGunMan
d5ef277121 fixing 2024-09-23 19:24:14 -07:00
SpudGunMan
dfc8e6c108 Update mesh_bot.py 2024-09-23 19:20:51 -07:00
SpudGunMan
9100aee91f Update mesh_bot.py 2024-09-23 19:10:38 -07:00
SpudGunMan
c94cc92d1c Update mesh_bot.py 2024-09-23 17:41:44 -07:00
SpudGunMan
a41f5e3aca Update blackjack.py 2024-09-23 17:03:43 -07:00
SpudGunMan
cc2d15de0d Update mesh_bot.py 2024-09-23 17:03:40 -07:00
SpudGunMan
39e08aedae borked 2024-09-23 14:43:55 -07:00
SpudGunMan
fe8730fb1f Update mesh_bot.py 2024-09-23 14:42:48 -07:00
SpudGunMan
aac3ac8947 Update mesh_bot.py 2024-09-23 14:42:22 -07:00
SpudGunMan
1acc908bb8 Update mesh_bot.py 2024-09-23 14:33:48 -07:00
SpudGunMan
47ed98a5e1 Update mesh_bot.py 2024-09-23 14:23:07 -07:00
SpudGunMan
103034d1b8 enhance
line 1000
2024-09-23 14:16:26 -07:00
SpudGunMan
608714d00a more enhancing 2024-09-23 13:35:23 -07:00
SpudGunMan
575c287860 unoReverse! 2024-09-23 13:24:45 -07:00
SpudGunMan
820470bef7 tuning 2024-09-23 12:18:23 -07:00
SpudGunMan
ea000ef56c Update mesh_bot.py 2024-09-23 11:22:27 -07:00
SpudGunMan
793b8f8495 Update mesh_bot.py 2024-09-23 01:46:01 -07:00
SpudGunMan
373eee3024 Update mesh_bot.py 2024-09-23 01:43:35 -07:00
SpudGunMan
194273635e kiss 2024-09-23 01:31:24 -07:00
SpudGunMan
0972d7d89d fix 2024-09-23 00:22:58 -07:00
SpudGunMan
d1415f9d86 enhance 2024-09-22 23:56:27 -07:00
SpudGunMan
b3ff3bb406 enhanceHistory
enhance.. enhance...
2024-09-22 23:38:55 -07:00
SpudGunMan
1efce62a8a Update mesh_bot.py 2024-09-22 22:47:28 -07:00
SpudGunMan
442f2ff927 command history feature
fun idea from discord bitflip for command history and showing last time someone used the bot
2024-09-22 22:23:31 -07:00
SpudGunMan
84cefa1be8 Update mesh_bot.py 2024-09-22 18:28:54 -07:00
SpudGunMan
45e9c1eccb Update mesh_bot.py 2024-09-22 18:28:21 -07:00
SpudGunMan
e7e2c9604e 2.5firmware PKI enhance 2024-09-22 18:15:24 -07:00
SpudGunMan
edaf6875ef Update pong_bot.py 2024-09-22 17:45:42 -07:00
SpudGunMan
e7976a0a88 packetDebug 2024-09-22 17:45:33 -07:00
SpudGunMan
e7daae9250 Update mesh_bot.py
Therac-25
2024-09-21 19:02:01 -07:00
SpudGunMan
91a188f3fd Update config.template 2024-09-21 18:35:57 -07:00
SpudGunMan
49a6d48101 Update config.template 2024-09-21 18:35:04 -07:00
SpudGunMan
a4b5ed3597 Update mesh_bot.py 2024-09-21 17:33:59 -07:00
SpudGunMan
2efbfeef8c Update blackjack.py 2024-09-21 16:59:04 -07:00
SpudGunMan
1a9093bbdf Update blackjack.py 2024-09-21 16:47:04 -07:00
SpudGunMan
9b3d3c6bdc enhance 2024-09-21 16:44:52 -07:00
SpudGunMan
620a6f4795 fix loss payout
the payout has been taken in the betPlaced so its gone already no reason to recalculate (which subtracts it from bank)
2024-09-21 16:33:31 -07:00
SpudGunMan
1615fa9e51 Update videopoker.py 2024-09-21 16:21:57 -07:00
SpudGunMan
205906ebb7 Update mesh_bot.py 2024-09-21 16:19:32 -07:00
SpudGunMan
0d8016651c bet correction for video poker
the winnings multiplier considers the base bet, so there is no reason to add it on after the calculations its a free bet every time loss for the casino we need to account for these coins
2024-09-21 16:16:13 -07:00
SpudGunMan
ffdc776413 Update videopoker.py 2024-09-21 15:44:49 -07:00
SpudGunMan
9b92b3d1ae Update dopewar.py 2024-09-21 14:53:00 -07:00
SpudGunMan
3bfea2fd0e Update dopewar.py 2024-09-21 14:36:48 -07:00
SpudGunMan
97636ca575 Update dopewar.py 2024-09-21 14:36:14 -07:00
SpudGunMan
8c4c2095d7 Update dopewar.py 2024-09-21 14:25:51 -07:00
SpudGunMan
9855656e9a Update dopewar.py 2024-09-21 14:23:10 -07:00
SpudGunMan
cffdf92daf Update mesh_bot.py 2024-09-21 13:15:49 -07:00
SpudGunMan
d07ca4076e Update lemonade.py 2024-09-21 01:55:17 -07:00
SpudGunMan
47740df9a9 Update lemonade.py 2024-09-21 01:43:39 -07:00
SpudGunMan
e830d3fef9 Update lemonade.py 2024-09-21 01:43:03 -07:00
SpudGunMan
3c0a87354e Update lemonade.py 2024-09-21 01:32:32 -07:00
SpudGunMan
b5a2eb9fb9 Update lemonade.py 2024-09-21 01:21:02 -07:00
SpudGunMan
244bd02771 Update lemonade.py 2024-09-21 01:15:10 -07:00
SpudGunMan
95a978495d text💣 2024-09-21 01:04:59 -07:00
SpudGunMan
292ad8bb61 ahhh 2024-09-21 00:49:56 -07:00
SpudGunMan
8b0a2263e7 Update lemonade.py 2024-09-21 00:49:36 -07:00
SpudGunMan
bb9ffe3492 forgot to. enhance this 2024-09-21 00:44:34 -07:00
SpudGunMan
ea7ef074e1 enhance 2024-09-21 00:40:41 -07:00
SpudGunMan
897d6727b4 enhance 2024-09-21 00:30:46 -07:00
SpudGunMan
0f99728be3 Update lemonade.py 2024-09-21 00:23:12 -07:00
SpudGunMan
51903d04f5 Update lemonade.py 2024-09-21 00:03:38 -07:00
SpudGunMan
dde7dbf3a0 Update lemonade.py 2024-09-21 00:00:13 -07:00
SpudGunMan
508998efa8 Update lemonade.py 2024-09-20 23:24:34 -07:00
SpudGunMan
2691247532 Update lemonade.py 2024-09-20 23:07:02 -07:00
SpudGunMan
f3c9d8211f Update lemonade.py 2024-09-20 23:03:58 -07:00
SpudGunMan
ec970d9f08 Update lemonade.py 2024-09-20 23:00:41 -07:00
SpudGunMan
04b86ae7e4 Update lemonade.py 2024-09-20 22:56:47 -07:00
SpudGunMan
b7b31ca5c2 Update lemonade.py 2024-09-20 22:55:21 -07:00
SpudGunMan
7c76e3b3a9 Update lemonade.py 2024-09-20 22:53:45 -07:00
SpudGunMan
863f8d8630 Update lemonade.py 2024-09-20 22:49:06 -07:00
SpudGunMan
b12d16f7a7 Update dopewar.py 2024-09-20 22:36:41 -07:00
SpudGunMan
ead8474783 Update lemonade.py 2024-09-20 22:29:50 -07:00
SpudGunMan
03a1ead721 Update lemonade.py 2024-09-20 22:25:48 -07:00
SpudGunMan
234dbb60fe Update mesh_bot.py 2024-09-20 22:03:05 -07:00
SpudGunMan
43503df53c Update mesh_bot.py 2024-09-20 22:02:17 -07:00
Kelly
6c381aecb7 Merge pull request #62 from SpudGunMan/lab
Nestpebble Enhancements
2024-09-20 21:36:51 -07:00
SpudGunMan
8d1e366e10 Update system.py 2024-09-20 21:34:16 -07:00
SpudGunMan
d6a0713f52 Update settings.py 2024-09-20 21:33:11 -07:00
SpudGunMan
113bde4bab Nestpebble Ideas
@Nestpebble here is the changes I did to bring this in thank you for the ideas and enhancements!! 🥔

* whoami new command
* enhancing MOTD
* new and better icons
2024-09-20 21:30:18 -07:00
Kelly
b5354a2431 Merge pull request #61 from Nestpebble/main
Nestpebble enhancements
2024-09-20 20:26:24 -07:00
SpudGunMan
c166dd9ed8 Update mesh_bot.py 2024-09-20 19:47:06 -07:00
SpudGunMan
80b73da72c Update blackjack.py 2024-09-20 19:24:07 -07:00
SpudGunMan
98490e6444 WHITESPACE 2024-09-20 18:54:38 -07:00
SpudGunMan
56649471d0 Update blackjack.py 2024-09-20 18:48:39 -07:00
SpudGunMan
0186bdc15a Update mesh_bot.py 2024-09-20 18:43:54 -07:00
SpudGunMan
eb02014371 Update mesh_bot.py 2024-09-20 18:34:10 -07:00
SpudGunMan
993292cec8 Update blackjack.py 2024-09-20 18:30:50 -07:00
SpudGunMan
aac99eaf07 Update blackjack.py 2024-09-20 18:28:15 -07:00
SpudGunMan
e5d82df65c Update blackjack.py 2024-09-20 18:02:10 -07:00
SpudGunMan
10bbbecf7b Update videopoker.py 2024-09-20 17:54:23 -07:00
SpudGunMan
8b5f7c4bcb Update simulator.py 2024-09-20 17:40:51 -07:00
SpudGunMan
10c568fcac enhance 2024-09-20 12:48:35 -07:00
SpudGunMan
7443a2fbc4 Update blackjack.py 2024-09-20 12:29:28 -07:00
SpudGunMan
3e5681abc5 Update README.md 2024-09-20 12:11:16 -07:00
SpudGunMan
e5036c0b45 Update README.md 2024-09-20 12:07:52 -07:00
SpudGunMan
a312a81141 enhance 2024-09-20 12:04:45 -07:00
SpudGunMan
40dd55d828 enhance 2024-09-20 11:34:44 -07:00
SpudGunMan
135bb622e1 Update videopoker.py 2024-09-20 11:20:48 -07:00
SpudGunMan
ed8911be5f Update videopoker.py 2024-09-20 11:12:43 -07:00
SpudGunMan
a126d0610f Update videopoker.py 2024-09-20 11:02:37 -07:00
SpudGunMan
946301ba86 Update videopoker.py 2024-09-20 10:57:48 -07:00
SpudGunMan
604e8ec366 Update videopoker.py 2024-09-20 10:36:23 -07:00
SpudGunMan
2593dd4e86 Vpoker 2024-09-20 10:22:51 -07:00
SpudGunMan
9ee9cdd969 Update mesh_bot.py 2024-09-20 01:21:31 -07:00
SpudGunMan
ad3918f071 Update mesh_bot.py 2024-09-20 01:08:07 -07:00
SpudGunMan
3cbc1efa59 Update mesh_bot.py 2024-09-20 01:02:30 -07:00
SpudGunMan
446f0336b6 Update mesh_bot.py 2024-09-20 00:58:07 -07:00
SpudGunMan
e3c3becb63 Update mesh_bot.py 2024-09-20 00:56:01 -07:00
SpudGunMan
8564c85378 Update mesh_bot.py 2024-09-20 00:40:20 -07:00
SpudGunMan
eedca4c1d0 Update videopoker.py 2024-09-19 23:38:06 -07:00
SpudGunMan
28c46dcbfd Update mesh_bot.py 2024-09-19 23:19:42 -07:00
SpudGunMan
544c4c3e80 Update mesh_bot.py 2024-09-19 23:18:06 -07:00
SpudGunMan
8bfd1a4d11 Update mesh_bot.py 2024-09-19 23:13:35 -07:00
SpudGunMan
3fae6241a7 refactorLLMlocations 2024-09-19 23:06:13 -07:00
SpudGunMan
48ff4209c4 Update mesh_bot.py 2024-09-19 22:01:41 -07:00
SpudGunMan
1d3986c589 fix a dict list 2024-09-19 21:55:39 -07:00
SpudGunMan
52b2ec776b Update mesh_bot.py 2024-09-19 21:51:34 -07:00
SpudGunMan
9d3c6b28ef GAMEDELAY 2024-09-19 21:26:43 -07:00
SpudGunMan
c242eb3750 documentation 2024-09-19 21:22:57 -07:00
SpudGunMan
e718fea565 wopr 2024-09-19 21:19:58 -07:00
SpudGunMan
14644da9c1 Update videopoker.py 2024-09-19 20:40:53 -07:00
SpudGunMan
0c99dc52d3 Update videopoker.py 2024-09-19 20:40:35 -07:00
SpudGunMan
9cf94d2d1d Update videopoker.py 2024-09-19 20:37:04 -07:00
SpudGunMan
de560f9b8a Update videopoker.py 2024-09-19 20:30:17 -07:00
SpudGunMan
47b8b7a9b8 Update videopoker.py 2024-09-19 20:28:11 -07:00
SpudGunMan
216c468118 Update videopoker.py 2024-09-19 20:24:39 -07:00
SpudGunMan
dd6735167b Update videopoker.py 2024-09-19 20:19:09 -07:00
SpudGunMan
a400eabc0e Update videopoker.py 2024-09-19 20:13:53 -07:00
SpudGunMan
3b95f1b873 VideoPoker 2024-09-19 20:10:46 -07:00
SpudGunMan
7b1441814d Update simulator.py 2024-09-19 16:45:04 -07:00
SpudGunMan
cc39a3a173 Merge branch 'main' of https://github.com/SpudGunMan/meshing-around 2024-09-19 16:01:30 -07:00
SpudGunMan
34df5cfc6a Update .gitignore 2024-09-19 16:01:29 -07:00
Kelly
37836e7842 Merge pull request #60 from SpudGunMan/games
GAMES!
2024-09-19 16:00:15 -07:00
SpudGunMan
121231f6fa Update README.md 2024-09-19 15:57:42 -07:00
SpudGunMan
71c8c98ce9 Black21Jack! 2024-09-19 15:48:10 -07:00
SpudGunMan
2c3cce4d51 Update dopewar.py 2024-09-19 11:54:05 -07:00
SpudGunMan
34ed1247e3 Update mesh_bot.py 2024-09-19 11:53:06 -07:00
SpudGunMan
e8351a1136 Update mesh_bot.py 2024-09-19 11:52:03 -07:00
SpudGunMan
4f93e3f5e2 Update lemonade.py 2024-09-19 11:24:59 -07:00
SpudGunMan
33017aa948 Update lemonade.py 2024-09-19 11:19:21 -07:00
SpudGunMan
cbfe68230c Update lemonade.py 2024-09-19 11:13:38 -07:00
SpudGunMan
775c998e4d oofWhySoPainfiull 2024-09-19 11:03:12 -07:00
SpudGunMan
46b8fc7dcd Update dopewar.py 2024-09-19 10:59:33 -07:00
SpudGunMan
d671eb14ce Update dopewar.py 2024-09-19 10:57:29 -07:00
SpudGunMan
cd7d4969ef Update dopewar.py 2024-09-19 10:54:24 -07:00
SpudGunMan
a4e7509354 Update dopewar.py 2024-09-19 10:49:23 -07:00
SpudGunMan
cd74573adc Update dopewar.py 2024-09-19 10:43:45 -07:00
SpudGunMan
86f14ad88a Update dopewar.py 2024-09-19 10:29:31 -07:00
SpudGunMan
847634e524 Update lemonade.py 2024-09-19 01:34:11 -07:00
SpudGunMan
f162019f77 Update lemonade.py 2024-09-19 01:28:53 -07:00
SpudGunMan
b814632f74 Update dopewar.py 2024-09-19 01:26:13 -07:00
SpudGunMan
741ced6289 Update dopewar.py 2024-09-19 01:20:47 -07:00
SpudGunMan
d939a69d16 Update dopewar.py 2024-09-19 01:18:45 -07:00
SpudGunMan
08416cb4e1 Update dopewar.py 2024-09-19 01:18:09 -07:00
SpudGunMan
a30a607f4f Update dopewar.py 2024-09-19 01:17:10 -07:00
SpudGunMan
5caae28b55 Update dopewar.py 2024-09-19 01:16:23 -07:00
SpudGunMan
c85b9d9ee1 Update dopewar.py 2024-09-19 01:10:06 -07:00
SpudGunMan
3d8c8520de Update lemonade.py 2024-09-19 00:58:18 -07:00
SpudGunMan
9e8aec16e5 Update dopewar.py 2024-09-19 00:56:18 -07:00
SpudGunMan
91ea47a6f4 Update simulator.py 2024-09-18 16:36:41 -07:00
SpudGunMan
c0d108ee77 enhance 2024-09-18 16:35:29 -07:00
SpudGunMan
94479f47d9 fixHighScore 2024-09-18 16:08:43 -07:00
SpudGunMan
f6fd017871 Update simulator.py 2024-09-18 15:55:18 -07:00
SpudGunMan
15de706fba Update simulator.py 2024-09-18 15:54:39 -07:00
SpudGunMan
f7745ac556 Update simulator.py 2024-09-18 15:51:48 -07:00
SpudGunMan
ad32ed5a80 Create simulator.py
simulator for prototyping code bits
2024-09-18 15:45:38 -07:00
SpudGunMan
18e1522374 fixTypo 2024-09-18 15:03:40 -07:00
SpudGunMan
bd01da15cc gamesTables 2024-09-18 15:02:00 -07:00
SpudGunMan
1b4ca71e38 Update bbsdb_admin.py 2024-09-18 14:59:40 -07:00
SpudGunMan
68fabef2be Update mesh_bot.py 2024-09-18 14:54:37 -07:00
SpudGunMan
c07f756d67 enhanceDM logic 2024-09-18 01:21:26 -07:00
SpudGunMan
bf52369d2d endgame cleanup 2024-09-18 01:14:57 -07:00
SpudGunMan
e49e63f39a Update dopewar.py 2024-09-18 01:09:36 -07:00
SpudGunMan
f651f2e577 fix stale players 2024-09-18 01:05:28 -07:00
SpudGunMan
f424da410b fixes 2024-09-17 03:13:17 -07:00
SpudGunMan
ada809066c highscore 2024-09-17 02:54:45 -07:00
SpudGunMan
e7ec8518e7 Update lemonade.py 2024-09-17 02:34:50 -07:00
SpudGunMan
1ea6a4d987 Update lemonade.py 2024-09-17 02:32:12 -07:00
SpudGunMan
f111aad6b7 Games
A late night double feature of dopewars and lemonade stand.
2024-09-17 02:21:31 -07:00
Nestpebble
88282db0e4 put everything where it should be... 2024-09-16 12:08:33 +01:00
Nestpebble
55e09938bb sdrfsdf 2024-09-16 11:55:44 +01:00
Nestpebble
add6c73af7 asdasd 2024-09-16 11:54:44 +01:00
Nestpebble
62a932b9ed sdfsdf 2024-09-16 11:51:16 +01:00
Nestpebble
037024395d asdasd 2024-09-16 11:49:07 +01:00
Nestpebble
e2105e1fa3 zdzsdf 2024-09-16 11:46:21 +01:00
Nestpebble
be67b33f99 dsasd 2024-09-16 11:45:20 +01:00
Nestpebble
7224da9c7a dfsdf 2024-09-16 11:41:54 +01:00
Nestpebble
fd68007c41 sedfsedf 2024-09-16 11:31:46 +01:00
Nestpebble
e77c7935de Update mesh_bot.py 2024-09-16 11:21:58 +01:00
Nestpebble
cf0072fce4 asdasd 2024-09-16 11:14:37 +01:00
Nestpebble
6f8a8b4264 sdfsdf 2024-09-16 11:13:13 +01:00
Nestpebble
746db62fdb sdfsdf 2024-09-16 11:12:32 +01:00
Nestpebble
fa4e254ba4 sfsdf 2024-09-16 11:09:06 +01:00
Nestpebble
0a2e385f41 see if this works 2024-09-16 10:43:58 +01:00
Nestpebble
2d6e939306 sdfsdf 2024-09-16 10:27:56 +01:00
Nestpebble
b5d7107760 sdfsdf 2024-09-16 10:26:14 +01:00
Nestpebble
575dd08b74 strings 2024-09-16 10:20:25 +01:00
Nestpebble
b043541dca traplist updated 2024-09-16 10:17:09 +01:00
Nestpebble
fa5fd85b5d Update mesh_bot.py 2024-09-16 10:12:17 +01:00
Nestpebble
be68cece47 Update mesh_bot.py 2024-09-16 10:11:24 +01:00
Nestpebble
50245e618b Update mesh_bot.py 2024-09-16 09:56:48 +01:00
Nestpebble
e0d85a65f9 Update mesh_bot.py 2024-09-16 09:56:03 +01:00
Nestpebble
a414a994fb Update mesh_bot.py 2024-09-16 09:42:57 +01:00
Nestpebble
c0e7f4c0fa Update mesh_bot.py 2024-09-16 09:04:38 +01:00
Nestpebble
c03375c7f0 Update mesh_bot.py 2024-09-16 09:04:28 +01:00
Nestpebble
453cfbb306 Update mesh_bot.py 2024-09-16 02:30:22 +01:00
Nestpebble
deb5ee8374 Update mesh_bot.py 2024-09-16 02:29:34 +01:00
Nestpebble
5d2f7dbe8a Update mesh_bot.py 2024-09-16 02:13:10 +01:00
Nestpebble
3831998add Update mesh_bot.py 2024-09-16 02:12:13 +01:00
Nestpebble
e85982e17c Update mesh_bot.py 2024-09-16 02:00:04 +01:00
Nestpebble
46c17c8470 Update mesh_bot.py 2024-09-16 01:54:38 +01:00
Nestpebble
5fd326f2e8 Update mesh_bot.py add nodeID check to MOTD change 2024-09-16 01:41:43 +01:00
Nestpebble
ad5e086eb5 Update mesh_bot.py handle_testing 2024-09-16 01:22:17 +01:00
Nestpebble
ef9231f51f Update settings.py 2024-09-16 01:20:11 +01:00
Nestpebble
bf34661e42 Update settings.py 2024-09-16 01:19:06 +01:00
Nestpebble
286db4fbea add messagingSettings 2024-09-16 00:44:46 +01:00
Nestpebble
4f4dbfbc6f add messagingSettings 2024-09-16 00:44:17 +01:00
Nestpebble
1046daaf16 remove responseDelay from here, moved to settings.ini 2024-09-16 00:43:50 +01:00
Nestpebble
b7df9d05a7 Add time.sleep(splitDelay) between split messages 2024-09-16 00:42:59 +01:00
Nestpebble
394c3ef4f6 update ping messages 2024-09-16 00:16:42 +01:00
Nestpebble
7a071c33cd Merge branch 'SpudGunMan:main' into main 2024-09-13 23:28:47 +01:00
SpudGunMan
71733de05f enhance DM/Channel logic for llm
fix logic for DM or Channel Sending
2024-09-10 12:59:00 -07:00
SpudGunMan
d5e48a3e36 Update README.md 2024-09-10 00:56:16 -07:00
SpudGunMan
e36755a21d Update llm.py
fix typo
2024-09-10 00:52:20 -07:00
SpudGunMan
9620164884 Update locationdata.py
enhance for LLM
2024-09-10 00:46:43 -07:00
SpudGunMan
711844cc83 enhanceMessages
fixes a few things I didnt wrap up and also enhances this suggestion https://github.com/SpudGunMan/meshing-around/issues/59
2024-09-10 00:46:32 -07:00
SpudGunMan
6eb82b26a7 Update mesh_bot.py 2024-09-09 23:27:12 -07:00
SpudGunMan
b12cf6219a responseDelay 2024-09-06 09:31:30 -07:00
SpudGunMan
7e46305277 responseDelay 2024-09-06 09:30:07 -07:00
Nestpebble
14aa127f31 Update mesh_bot.py 2024-09-06 13:33:30 +01:00
Nestpebble
6e7e89c2d0 Update pong_bot.py 2024-09-06 13:31:46 +01:00
SpudGunMan
819c37bdec networkErrorFix 2024-09-05 23:01:46 -07:00
SpudGunMan
c80690d66e Update README.md 2024-09-05 09:38:56 -07:00
SpudGunMan
084f879537 Update settings.py 2024-09-05 09:09:38 -07:00
SpudGunMan
7afd6bbbe9 Update README.md 2024-09-05 00:54:32 -07:00
SpudGunMan
46641e8a86 Update README.md 2024-09-05 00:51:01 -07:00
SpudGunMan
010b386ce1 Update README.md 2024-09-05 00:43:02 -07:00
SpudGunMan
a73b320715 Update llm.py 2024-09-05 00:22:33 -07:00
SpudGunMan
5f822a6230 Update mesh_bot.py 2024-09-04 22:27:15 -07:00
SpudGunMan
024fac90cd Update install.sh 2024-09-04 19:50:49 -07:00
SpudGunMan
133bc36cca Update install.sh 2024-09-04 19:22:21 -07:00
SpudGunMan
51a7ff2820 checkPip 2024-09-04 19:17:37 -07:00
SpudGunMan
bc96f8df49 installOllama 2024-09-04 18:42:04 -07:00
Kelly
979f197476 Merge pull request #57 from SpudGunMan/llmLocationAware
LLM location aware enhancement
2024-09-04 17:18:03 -07:00
SpudGunMan
1677b69363 comments# 2024-09-04 15:10:38 -07:00
SpudGunMan
d627f694df typo 2024-09-04 15:05:15 -07:00
SpudGunMan
4c52cba21f SpErr 2024-09-04 15:00:44 -07:00
SpudGunMan
597fdd1695 Update llm.py 2024-09-04 14:53:33 -07:00
SpudGunMan
9031704b9b Update llm.py 2024-09-04 14:51:50 -07:00
SpudGunMan
510a5c5007 Update llm.py 2024-09-04 14:42:23 -07:00
SpudGunMan
469e76c50b Update llm.py 2024-09-04 13:14:28 -07:00
SpudGunMan
f6c6c58c17 Update README.md 2024-09-04 11:06:09 -07:00
SpudGunMan
e546866f78 Update llm.py 2024-09-04 09:40:57 -07:00
SpudGunMan
081566b5d9 lower value to speed up query 2024-09-04 09:40:04 -07:00
SpudGunMan
ec078666ae saveSomeAPIcalls 2024-09-04 00:50:57 -07:00
SpudGunMan
1ce394c7a1 Update for LLM 2024-09-04 00:20:06 -07:00
SpudGunMan
2fc3930b43 Update mesh_bot.py 2024-09-03 23:22:02 -07:00
SpudGunMan
9fa9da5e74 Update mesh_bot.py 2024-09-03 23:20:23 -07:00
SpudGunMan
d6ad0b5e94 Update mesh_bot.py 2024-09-03 23:20:13 -07:00
SpudGunMan
15dc50804f Update mesh_bot.py 2024-09-03 23:17:34 -07:00
SpudGunMan
63c3e35064 Update llm.py 2024-09-03 23:12:32 -07:00
SpudGunMan
297930c4d1 Update llm.py 2024-09-03 23:09:45 -07:00
SpudGunMan
098c344047 Update llm.py 2024-09-03 23:08:37 -07:00
SpudGunMan
4f74677d14 Update mesh_bot.py 2024-09-03 23:05:34 -07:00
SpudGunMan
0869b19408 addTimeAware
include the current date in the awareness of location
2024-09-03 23:05:00 -07:00
SpudGunMan
9b02611700 LocationAware 2024-09-03 23:02:04 -07:00
SpudGunMan
5daa71e6c1 llmLocationAware
enhance with local data to the AI
2024-09-03 22:52:27 -07:00
SpudGunMan
aa5f2f66f8 Update llm.py 2024-09-03 21:10:11 -07:00
SpudGunMan
92d04f81c3 contextFromGoogle 2024-09-03 21:08:06 -07:00
SpudGunMan
5d53db4211 enhance 2024-09-03 17:13:04 -07:00
SpudGunMan
eb3bbdd3c5 Update llm.py 2024-09-03 00:48:06 -07:00
SpudGunMan
1ac816ca37 Update README.md 2024-09-03 00:42:38 -07:00
SpudGunMan
33cf18cde5 enhance wiki 2024-09-03 00:29:41 -07:00
SpudGunMan
0c0d53dd78 Update README.md 2024-09-02 23:25:53 -07:00
Kelly
1959ee7560 Merge pull request #53 from mrpatrick1991/docker
Docker
2024-09-02 23:22:51 -07:00
Matthew Patrick
ee13401b5a Update config.template
reset to be identical to main branch
2024-09-02 12:37:30 -06:00
Matthew Patrick
78b1cf4af5 edit docs and make dockerfile use config.ini not config.template 2024-09-02 12:35:44 -06:00
Matthew Patrick
0599260e31 created docker file
docker file and entry point script which copies the values in config.template to the container.
2024-09-02 12:18:20 -06:00
SpudGunMan
08dd921088 gemma2:2b 2024-09-02 11:09:03 -07:00
SpudGunMan
e66e938d7d Update README.md 2024-09-02 11:04:23 -07:00
SpudGunMan
b5b7d2a9d2 Update llm.py 2024-09-02 10:58:13 -07:00
SpudGunMan
46298d555b enhance 2024-09-02 10:47:39 -07:00
SpudGunMan
8fb34b5fde Update config.template 2024-09-02 10:46:31 -07:00
SpudGunMan
28f8986837 Update README.md 2024-09-02 10:46:12 -07:00
SpudGunMan
e968173f61 Update pong_bot.py 2024-09-01 21:53:01 -07:00
SpudGunMan
f703a8868b Update mesh_bot.py 2024-09-01 21:51:21 -07:00
SpudGunMan
0a29e5f156 Update mesh_bot.py 2024-09-01 11:16:01 -07:00
SpudGunMan
c5c28ee042 Update llm.py 2024-09-01 10:57:44 -07:00
SpudGunMan
44ca43399d Update config.template 2024-09-01 09:01:49 -07:00
SpudGunMan
13a47d822d Update config.template 2024-09-01 09:01:00 -07:00
SpudGunMan
5621cd90bb Update config.template 2024-09-01 09:00:44 -07:00
SpudGunMan
9f7055ffd2 model to settings for LLM 2024-09-01 08:59:40 -07:00
28 changed files with 4396 additions and 324 deletions

5
.gitignore vendored
View File

@@ -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
View 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"]

View File

@@ -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!

View File

@@ -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
View 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

View File

@@ -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
View 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")

View File

@@ -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

View File

@@ -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
View 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

View File

@@ -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
View 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
```

File diff suppressed because it is too large Load Diff

View File

@@ -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
View 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
View 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
View 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
View 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

View File

@@ -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

View File

@@ -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"
}

View File

@@ -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
View 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

View File

@@ -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}")

View File

@@ -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
View 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

View File

@@ -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 + ". "

View File

@@ -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:

View File

@@ -16,4 +16,4 @@ wikipedia
langchain
langchain-ollama
ollama
googlesearch-python