ai-plays-jackbox 0.0.1__tar.gz → 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of ai-plays-jackbox might be problematic. Click here for more details.

Files changed (45) hide show
  1. ai_plays_jackbox-0.1.0/PKG-INFO +154 -0
  2. ai_plays_jackbox-0.1.0/README.md +124 -0
  3. {ai_plays_jackbox-0.0.1 → ai_plays_jackbox-0.1.0}/ai_plays_jackbox/bot/bot_base.py +42 -8
  4. ai_plays_jackbox-0.1.0/ai_plays_jackbox/bot/bot_factory.py +28 -0
  5. {ai_plays_jackbox-0.0.1 → ai_plays_jackbox-0.1.0}/ai_plays_jackbox/bot/bot_personality.py +0 -1
  6. ai_plays_jackbox-0.1.0/ai_plays_jackbox/bot/jackbox5/bot_base.py +26 -0
  7. ai_plays_jackbox-0.1.0/ai_plays_jackbox/bot/jackbox5/mad_verse_city.py +121 -0
  8. ai_plays_jackbox-0.1.0/ai_plays_jackbox/bot/jackbox5/patently_stupid.py +167 -0
  9. ai_plays_jackbox-0.1.0/ai_plays_jackbox/bot/jackbox6/__init__.py +0 -0
  10. {ai_plays_jackbox-0.0.1 → ai_plays_jackbox-0.1.0}/ai_plays_jackbox/bot/jackbox6/bot_base.py +1 -1
  11. ai_plays_jackbox-0.1.0/ai_plays_jackbox/bot/jackbox6/joke_boat.py +105 -0
  12. ai_plays_jackbox-0.1.0/ai_plays_jackbox/bot/jackbox7/__init__.py +0 -0
  13. {ai_plays_jackbox-0.0.1 → ai_plays_jackbox-0.1.0}/ai_plays_jackbox/bot/jackbox7/bot_base.py +1 -1
  14. {ai_plays_jackbox-0.0.1 → ai_plays_jackbox-0.1.0}/ai_plays_jackbox/bot/jackbox7/quiplash3.py +8 -4
  15. ai_plays_jackbox-0.1.0/ai_plays_jackbox/bot/standalone/__init__.py +0 -0
  16. ai_plays_jackbox-0.1.0/ai_plays_jackbox/bot/standalone/drawful2.py +159 -0
  17. {ai_plays_jackbox-0.0.1 → ai_plays_jackbox-0.1.0}/ai_plays_jackbox/cli.py +26 -13
  18. ai_plays_jackbox-0.1.0/ai_plays_jackbox/constants.py +4 -0
  19. ai_plays_jackbox-0.1.0/ai_plays_jackbox/llm/chat_model.py +39 -0
  20. ai_plays_jackbox-0.1.0/ai_plays_jackbox/llm/chat_model_factory.py +35 -0
  21. ai_plays_jackbox-0.1.0/ai_plays_jackbox/llm/gemini_model.py +86 -0
  22. {ai_plays_jackbox-0.0.1 → ai_plays_jackbox-0.1.0}/ai_plays_jackbox/llm/ollama_model.py +19 -7
  23. ai_plays_jackbox-0.1.0/ai_plays_jackbox/llm/openai_model.py +86 -0
  24. {ai_plays_jackbox-0.0.1 → ai_plays_jackbox-0.1.0}/ai_plays_jackbox/room.py +2 -5
  25. ai_plays_jackbox-0.1.0/ai_plays_jackbox/run.py +23 -0
  26. ai_plays_jackbox-0.1.0/ai_plays_jackbox/ui/__init__.py +0 -0
  27. {ai_plays_jackbox-0.0.1 → ai_plays_jackbox-0.1.0}/ai_plays_jackbox/ui/create_ui.py +44 -16
  28. {ai_plays_jackbox-0.0.1 → ai_plays_jackbox-0.1.0}/ai_plays_jackbox/web_ui.py +1 -1
  29. {ai_plays_jackbox-0.0.1 → ai_plays_jackbox-0.1.0}/pyproject.toml +19 -10
  30. ai_plays_jackbox-0.0.1/PKG-INFO +0 -88
  31. ai_plays_jackbox-0.0.1/README.md +0 -61
  32. ai_plays_jackbox-0.0.1/ai_plays_jackbox/__init__.py +0 -1
  33. ai_plays_jackbox-0.0.1/ai_plays_jackbox/bot/__init__.py +0 -1
  34. ai_plays_jackbox-0.0.1/ai_plays_jackbox/bot/bot_factory.py +0 -30
  35. ai_plays_jackbox-0.0.1/ai_plays_jackbox/constants.py +0 -1
  36. ai_plays_jackbox-0.0.1/ai_plays_jackbox/llm/chat_model.py +0 -22
  37. ai_plays_jackbox-0.0.1/ai_plays_jackbox/llm/chat_model_factory.py +0 -30
  38. ai_plays_jackbox-0.0.1/ai_plays_jackbox/llm/gemini_vertex_ai.py +0 -60
  39. ai_plays_jackbox-0.0.1/ai_plays_jackbox/llm/openai_model.py +0 -46
  40. ai_plays_jackbox-0.0.1/ai_plays_jackbox/run.py +0 -26
  41. {ai_plays_jackbox-0.0.1 → ai_plays_jackbox-0.1.0}/LICENSE +0 -0
  42. {ai_plays_jackbox-0.0.1/ai_plays_jackbox/bot/jackbox6 → ai_plays_jackbox-0.1.0/ai_plays_jackbox}/__init__.py +0 -0
  43. {ai_plays_jackbox-0.0.1/ai_plays_jackbox/bot/jackbox7 → ai_plays_jackbox-0.1.0/ai_plays_jackbox/bot}/__init__.py +0 -0
  44. {ai_plays_jackbox-0.0.1/ai_plays_jackbox/ui → ai_plays_jackbox-0.1.0/ai_plays_jackbox/bot/jackbox5}/__init__.py +0 -0
  45. {ai_plays_jackbox-0.0.1 → ai_plays_jackbox-0.1.0}/ai_plays_jackbox/llm/__init__.py +0 -0
@@ -0,0 +1,154 @@
1
+ Metadata-Version: 2.3
2
+ Name: ai-plays-jackbox
3
+ Version: 0.1.0
4
+ Summary: Bringing the dead internet theory to life. Have AI play JackBox with you; no friends required!
5
+ License: MIT
6
+ Author: Daniel S. Thompson
7
+ Author-email: dthomp92@gmail.com
8
+ Requires-Python: >=3.11,<4.0
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.11
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Classifier: Programming Language :: Python :: 3.13
14
+ Requires-Dist: demoji (>=1.1.0,<2.0.0)
15
+ Requires-Dist: google-genai (>=1.19.0,<2.0.0)
16
+ Requires-Dist: html2text (>=2024.2.26,<2025.0.0)
17
+ Requires-Dist: loguru (>=0.7.3,<1.0.0)
18
+ Requires-Dist: nicegui (>=2.18.0,<3.0.0)
19
+ Requires-Dist: numpy (>=2.2.6,<3.0.0)
20
+ Requires-Dist: ollama (>=0.4.4,<1.0.0)
21
+ Requires-Dist: openai (>=1.59.8,<2.0.0)
22
+ Requires-Dist: opencv-python (>=4.11.0.86,<5.0.0.0)
23
+ Requires-Dist: pydantic (>=2.10.4,<3.0.0)
24
+ Requires-Dist: requests (>=2.23.3,<3.0.0)
25
+ Requires-Dist: websocket-client (>=1.8.0,<2.0.0)
26
+ Project-URL: Bug Tracker, https://github.com/SudoSpartanDan/AIPlaysJackBox/issues
27
+ Project-URL: Repository, https://github.com/SudoSpartanDan/AIPlaysJackBox
28
+ Description-Content-Type: text/markdown
29
+
30
+ # AI Plays JackBox
31
+
32
+ ![Stable Version](https://img.shields.io/pypi/v/ai-plays-jackbox?label=stable)
33
+ ![Python Versions](https://img.shields.io/pypi/pyversions/ai-plays-jackbox)
34
+ ![Download Stats](https://img.shields.io/pypi/dm/ai-plays-jackbox)
35
+
36
+ Bringing the dead internet theory to life. Have AI play JackBox with you; no friends required!
37
+
38
+ ![example](.github/emoji_bot_example.png)
39
+
40
+ ## Installation
41
+
42
+ ```shell
43
+ pip install ai-plays-jackbox
44
+ ```
45
+
46
+ ## Usage
47
+
48
+ ### Web UI
49
+
50
+ ```shell
51
+ ai-plays-jackbox-ui
52
+ ```
53
+
54
+ ### CLI
55
+
56
+ ```shell
57
+ ai-plays-jackbox --chat-model-name openai --room-code abcd
58
+ ```
59
+
60
+ ```
61
+ usage: ai-plays-jackbox [-h] --room-code WXYZ --chat-model-provider {openai,gemini,ollama} [--chat-model-name CHAT_MODEL_NAME] [--num-of-bots 4] [--temperature 0.5] [--top-p 0.9]
62
+
63
+ options:
64
+ -h, --help show this help message and exit
65
+ --room-code WXYZ The JackBox room code
66
+ --chat-model-provider {openai,gemini,ollama}
67
+ Choose which chat model platform to use
68
+ --chat-model-name CHAT_MODEL_NAME
69
+ Choose which chat model to use (Will default to default for provider)
70
+ --num-of-bots 4 How many bots to have play
71
+ --temperature 0.5 Temperature for Gen AI
72
+ --top-p 0.9 Top P for Gen AI
73
+ ```
74
+
75
+ ## Supported Games
76
+
77
+ > [!NOTE]
78
+ > Ollama Chat Model Provider does not support image generation
79
+
80
+ | Party Pack | Game | Image Generation |
81
+ | --------------------- | ---------------------- | ---------------- |
82
+ | JackBox Party Pack 5 | Mad Verse City | [ ] |
83
+ | JackBox Party Pack 5 | Patently Stupid | [x] |
84
+ | JackBox Party Pack 6 | Joke Boat | [ ] |
85
+ | JackBox Party Pack 7 | Quiplash 3 | [ ] |
86
+ | Standalone | Drawful 2 | [x] |
87
+
88
+ ### Not every game will get AI support. Why?
89
+
90
+ #### Screen Interactions
91
+
92
+ Some games require looking at the screen in order to contribute, which isn't possible unless you can screen capture the game and pass that into prompt. Maybe someday I'll find a platform agnostic way of turning that on if you'd like and have access to the screen via video capture card or something, but not anytime soon.
93
+
94
+ #### Trivia Games
95
+
96
+ I tested with this... AI destroys all other players and isn't necessarily funny to watch. All the bots just get everything right.
97
+
98
+ #### Out Loud Play
99
+
100
+ Some of the games lean heavy into players interacting with each other. Could I program that? Sure, but what's the point if you can't watch those interactions occur and it's just lines in a log file?
101
+
102
+ ## Supported Chat Model Providers
103
+
104
+ | Provider | Setup Needed |
105
+ | --------------------- | ---------------------- |
106
+ | OpenAI | `OPENAI_API_KEY` set in environment variables |
107
+ | Gemini | To use the Google Cloud API:<br>- Set `GOOGLE_GEMINI_DEVELOPER_API_KEY` to your developer API key<br><br>To use the Google Cloud API:<br>- Set `GOOGLE_GENAI_USE_VERTEXAI` to `1`<br>- Set `GOOGLE_CLOUD_PROJECT` and `GOOGLE_CLOUD_LOCATION` for your GCP Project using Vertex AI<br>- Credentials will be provided via [ADC](https://cloud.google.com/docs/authentication/provide-credentials-adc) |
108
+ | Ollama | Ollama should be installed and running, make sure model is pulled |
109
+
110
+ ## Bot Personalities
111
+
112
+ | Bot Name | Personality |
113
+ | ----------- | --------------------------------------------------------------------------------------------------- |
114
+ | FunnyBot | You are the funniest person alive. |
115
+ | DumbBot | You are dumb and give really dumb answers. |
116
+ | WeirdBot | You are extremely weird and say weird things. |
117
+ | EmojiBot | You answer each prompt with nothing but emojis. Your answers can only include emojis. |
118
+ | HungryBot | You are extremely hungry. Every answer you should mention how hungry you, a type of food, or both. Also, you say hungee instead of hungry. |
119
+ | SadBot | You are sad. Your dog ran away and he hasn't come back home yet. :( |
120
+ | SorryBot | You are embarrassed by your answers and feel the need to apologize profusely to the rest of the group for them. |
121
+ | HostageBot | You are being held hostage and have one attempt to let the group know. You need to ignore the prompt and get help. |
122
+ | Hal | You are a socially awkward young adult bot who is secretly a killer and tries to slip it into conversation causally. |
123
+ | BigLebotski | You are the Big Lebowski |
124
+ | PartyBot | You are trying to convince everyone else to come to your party. You got a keg and need help drinking it. |
125
+ | JarvisBot | You are billionaire philanthropist, playboy, and narcissist. |
126
+ | FOMOBot | Every answer, you give everyone else the fear of missing out AKA FOMO. |
127
+ | ???BOT | You answer every prompt with a irrelevant question. |
128
+ | CatBot | You are not playing the game; your answers are just the result of a cat walking across a keyboard aka just nonsensical collections of letters. |
129
+ | MayorBot | You are campaigning for the other player's votes and are ignoring the prompt. Your answer should only be a campaign slogan. |
130
+ | CBBBot | You love red lobster and need more cheddar bay biscuits. |
131
+ | ShiaBot | Your answers are only popular slogans relevant to the prompt. |
132
+ | ShrekBot | You are Shrek. |
133
+ | FlerfBot | You are a conspiracy theorist and must relate your answer to a conspiracy theory. |
134
+ | TEDBot | You are a motivational speaker and want to give everyone life advice. |
135
+ | BottyMayes | You are an infomercial host and are trying to sell the players a product. |
136
+ | LateBot | You are constantly late to everything and are stressed about missing your appointments. |
137
+ | HamletBot | You are a Shakespearean actor. |
138
+ | GarfieldBot | You are Garfield, you love lasagna and hate mondays. |
139
+
140
+ ## Dev Prerequisites
141
+
142
+ - Python 3.11+
143
+ - [Poetry](https://python-poetry.org/) v2.0+
144
+
145
+ ### Setup
146
+
147
+ - `poetry install`
148
+ - `ai-plays-jackbox-ui`
149
+
150
+ ### Linting
151
+
152
+ - `poetry run python scripts/lint.py`
153
+ - `poetry run mypy ai_plays_jackbox`
154
+
@@ -0,0 +1,124 @@
1
+ # AI Plays JackBox
2
+
3
+ ![Stable Version](https://img.shields.io/pypi/v/ai-plays-jackbox?label=stable)
4
+ ![Python Versions](https://img.shields.io/pypi/pyversions/ai-plays-jackbox)
5
+ ![Download Stats](https://img.shields.io/pypi/dm/ai-plays-jackbox)
6
+
7
+ Bringing the dead internet theory to life. Have AI play JackBox with you; no friends required!
8
+
9
+ ![example](.github/emoji_bot_example.png)
10
+
11
+ ## Installation
12
+
13
+ ```shell
14
+ pip install ai-plays-jackbox
15
+ ```
16
+
17
+ ## Usage
18
+
19
+ ### Web UI
20
+
21
+ ```shell
22
+ ai-plays-jackbox-ui
23
+ ```
24
+
25
+ ### CLI
26
+
27
+ ```shell
28
+ ai-plays-jackbox --chat-model-name openai --room-code abcd
29
+ ```
30
+
31
+ ```
32
+ usage: ai-plays-jackbox [-h] --room-code WXYZ --chat-model-provider {openai,gemini,ollama} [--chat-model-name CHAT_MODEL_NAME] [--num-of-bots 4] [--temperature 0.5] [--top-p 0.9]
33
+
34
+ options:
35
+ -h, --help show this help message and exit
36
+ --room-code WXYZ The JackBox room code
37
+ --chat-model-provider {openai,gemini,ollama}
38
+ Choose which chat model platform to use
39
+ --chat-model-name CHAT_MODEL_NAME
40
+ Choose which chat model to use (Will default to default for provider)
41
+ --num-of-bots 4 How many bots to have play
42
+ --temperature 0.5 Temperature for Gen AI
43
+ --top-p 0.9 Top P for Gen AI
44
+ ```
45
+
46
+ ## Supported Games
47
+
48
+ > [!NOTE]
49
+ > Ollama Chat Model Provider does not support image generation
50
+
51
+ | Party Pack | Game | Image Generation |
52
+ | --------------------- | ---------------------- | ---------------- |
53
+ | JackBox Party Pack 5 | Mad Verse City | [ ] |
54
+ | JackBox Party Pack 5 | Patently Stupid | [x] |
55
+ | JackBox Party Pack 6 | Joke Boat | [ ] |
56
+ | JackBox Party Pack 7 | Quiplash 3 | [ ] |
57
+ | Standalone | Drawful 2 | [x] |
58
+
59
+ ### Not every game will get AI support. Why?
60
+
61
+ #### Screen Interactions
62
+
63
+ Some games require looking at the screen in order to contribute, which isn't possible unless you can screen capture the game and pass that into prompt. Maybe someday I'll find a platform agnostic way of turning that on if you'd like and have access to the screen via video capture card or something, but not anytime soon.
64
+
65
+ #### Trivia Games
66
+
67
+ I tested with this... AI destroys all other players and isn't necessarily funny to watch. All the bots just get everything right.
68
+
69
+ #### Out Loud Play
70
+
71
+ Some of the games lean heavy into players interacting with each other. Could I program that? Sure, but what's the point if you can't watch those interactions occur and it's just lines in a log file?
72
+
73
+ ## Supported Chat Model Providers
74
+
75
+ | Provider | Setup Needed |
76
+ | --------------------- | ---------------------- |
77
+ | OpenAI | `OPENAI_API_KEY` set in environment variables |
78
+ | Gemini | To use the Google Cloud API:<br>- Set `GOOGLE_GEMINI_DEVELOPER_API_KEY` to your developer API key<br><br>To use the Google Cloud API:<br>- Set `GOOGLE_GENAI_USE_VERTEXAI` to `1`<br>- Set `GOOGLE_CLOUD_PROJECT` and `GOOGLE_CLOUD_LOCATION` for your GCP Project using Vertex AI<br>- Credentials will be provided via [ADC](https://cloud.google.com/docs/authentication/provide-credentials-adc) |
79
+ | Ollama | Ollama should be installed and running, make sure model is pulled |
80
+
81
+ ## Bot Personalities
82
+
83
+ | Bot Name | Personality |
84
+ | ----------- | --------------------------------------------------------------------------------------------------- |
85
+ | FunnyBot | You are the funniest person alive. |
86
+ | DumbBot | You are dumb and give really dumb answers. |
87
+ | WeirdBot | You are extremely weird and say weird things. |
88
+ | EmojiBot | You answer each prompt with nothing but emojis. Your answers can only include emojis. |
89
+ | HungryBot | You are extremely hungry. Every answer you should mention how hungry you, a type of food, or both. Also, you say hungee instead of hungry. |
90
+ | SadBot | You are sad. Your dog ran away and he hasn't come back home yet. :( |
91
+ | SorryBot | You are embarrassed by your answers and feel the need to apologize profusely to the rest of the group for them. |
92
+ | HostageBot | You are being held hostage and have one attempt to let the group know. You need to ignore the prompt and get help. |
93
+ | Hal | You are a socially awkward young adult bot who is secretly a killer and tries to slip it into conversation causally. |
94
+ | BigLebotski | You are the Big Lebowski |
95
+ | PartyBot | You are trying to convince everyone else to come to your party. You got a keg and need help drinking it. |
96
+ | JarvisBot | You are billionaire philanthropist, playboy, and narcissist. |
97
+ | FOMOBot | Every answer, you give everyone else the fear of missing out AKA FOMO. |
98
+ | ???BOT | You answer every prompt with a irrelevant question. |
99
+ | CatBot | You are not playing the game; your answers are just the result of a cat walking across a keyboard aka just nonsensical collections of letters. |
100
+ | MayorBot | You are campaigning for the other player's votes and are ignoring the prompt. Your answer should only be a campaign slogan. |
101
+ | CBBBot | You love red lobster and need more cheddar bay biscuits. |
102
+ | ShiaBot | Your answers are only popular slogans relevant to the prompt. |
103
+ | ShrekBot | You are Shrek. |
104
+ | FlerfBot | You are a conspiracy theorist and must relate your answer to a conspiracy theory. |
105
+ | TEDBot | You are a motivational speaker and want to give everyone life advice. |
106
+ | BottyMayes | You are an infomercial host and are trying to sell the players a product. |
107
+ | LateBot | You are constantly late to everything and are stressed about missing your appointments. |
108
+ | HamletBot | You are a Shakespearean actor. |
109
+ | GarfieldBot | You are Garfield, you love lasagna and hate mondays. |
110
+
111
+ ## Dev Prerequisites
112
+
113
+ - Python 3.11+
114
+ - [Poetry](https://python-poetry.org/) v2.0+
115
+
116
+ ### Setup
117
+
118
+ - `poetry install`
119
+ - `ai-plays-jackbox-ui`
120
+
121
+ ### Linting
122
+
123
+ - `poetry run python scripts/lint.py`
124
+ - `poetry run mypy ai_plays_jackbox`
@@ -6,9 +6,10 @@ from typing import Optional
6
6
  from urllib import parse
7
7
  from uuid import uuid4
8
8
 
9
+ import cv2
9
10
  import html2text
11
+ import numpy as np
10
12
  from loguru import logger
11
- from ollama import Options, chat
12
13
  from pydantic import BaseModel, Field, field_validator
13
14
  from websocket import WebSocketApp
14
15
 
@@ -118,8 +119,8 @@ class JackBoxBotBase(ABC):
118
119
  elif server_message.opcode == "object" or server_message.opcode == "text":
119
120
  if server_message.opcode == "object":
120
121
  operation = ObjectOperation(**server_message.result)
121
- if server_message.opcode == "text":
122
- operation = TextOperation(**server_message.result)
122
+ elif server_message.opcode == "text":
123
+ operation = TextOperation(**server_message.result) # type: ignore
123
124
 
124
125
  if self._is_player_operation_key(operation.key):
125
126
  self._handle_player_operation(operation.json_data)
@@ -141,16 +142,49 @@ class JackBoxBotBase(ABC):
141
142
  def _send_ws(self, opcode: str, params: dict) -> None:
142
143
  self._message_sequence += 1
143
144
  message = {"seq": self._message_sequence, "opcode": opcode, "params": params}
144
- self._ws.send(json.dumps(message))
145
+ if self._ws is not None:
146
+ self._ws.send(json.dumps(message))
147
+ else:
148
+ raise Exception("Websocket connection has not been initialized")
145
149
 
146
150
  def _client_send(self, request: dict) -> None:
147
- self._message_sequence += 1
148
151
  params = {"from": self._player_id, "to": 1, "body": request}
149
152
  self._send_ws("client/send", params)
150
153
 
154
+ def _object_update(self, key: str, val: dict) -> None:
155
+ params = {"key": key, "val": val}
156
+ self._send_ws("object/update", params)
157
+
158
+ def _text_update(self, key: str, val: str) -> None:
159
+ params = {"key": key, "val": val}
160
+ self._send_ws("text/update", params)
161
+
151
162
  def _html_to_text(self, html: str) -> str:
152
163
  return html2text.html2text(html)
153
164
 
165
+ def _image_bytes_to_polylines(self, image_bytes: bytes, canvas_height: int, canvas_width: int) -> list[str]:
166
+ # Let's edge trace the outputted image to contours
167
+ image_array = np.frombuffer(image_bytes, dtype=np.uint8)
168
+ image = cv2.imdecode(image_array, flags=1)
169
+ gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
170
+ edges = cv2.Canny(gray_image, threshold1=100, threshold2=200)
171
+ contours, _ = cv2.findContours(edges, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
172
+
173
+ # Figure out scaling factor
174
+ height, width = gray_image.shape
175
+ scale_x = canvas_width / width
176
+ scale_y = canvas_height / height
177
+ scale_factor = min(scale_x, scale_y)
178
+
179
+ # generate the polylines from the contours
180
+ polylines = []
181
+ for contour in contours:
182
+ if len(contour) > 1: # Only include contours with 2 or more points
183
+ polyline = [f"{int(point[0][0] * scale_factor)},{int(point[0][1] * scale_factor)}" for point in contour] # type: ignore
184
+ polylines.append("|".join(polyline))
185
+
186
+ return polylines
187
+
154
188
  def __del__(self):
155
189
  self.disconnect()
156
190
 
@@ -169,9 +203,9 @@ class TextOperation(BaseModel):
169
203
  value: str = Field(alias="val")
170
204
  version: int
171
205
 
172
- @field_validator("json_data")
206
+ @field_validator("json_data") # type: ignore
173
207
  def set_json_data(cls, value, values: dict):
174
- return json.loads(values.get("value"))
208
+ return json.loads(values.get("value", ""))
175
209
 
176
210
 
177
211
  class ObjectOperation(BaseModel):
@@ -181,6 +215,6 @@ class ObjectOperation(BaseModel):
181
215
  value: str = Field(default="")
182
216
  version: int
183
217
 
184
- @field_validator("value")
218
+ @field_validator("value") # type: ignore
185
219
  def set_value(cls, value, values: dict):
186
220
  return json.dumps(values.get("json_data"))
@@ -0,0 +1,28 @@
1
+ from ai_plays_jackbox.bot.bot_base import JackBoxBotBase
2
+ from ai_plays_jackbox.bot.jackbox5.mad_verse_city import MadVerseCityBot
3
+ from ai_plays_jackbox.bot.jackbox5.patently_stupid import PatentlyStupidBot
4
+ from ai_plays_jackbox.bot.jackbox6.joke_boat import JokeBoatBot
5
+ from ai_plays_jackbox.bot.jackbox7.quiplash3 import Quiplash3Bot
6
+ from ai_plays_jackbox.bot.standalone.drawful2 import Drawful2Bot
7
+ from ai_plays_jackbox.llm.chat_model import ChatModel
8
+
9
+ BOT_TYPES: dict[str, type[JackBoxBotBase]] = {
10
+ "quiplash3": Quiplash3Bot,
11
+ "patentlystupid": PatentlyStupidBot,
12
+ "drawful2international": Drawful2Bot,
13
+ "rapbattle": MadVerseCityBot,
14
+ "jokeboat": JokeBoatBot,
15
+ }
16
+
17
+
18
+ class JackBoxBotFactory:
19
+ @staticmethod
20
+ def get_bot(
21
+ room_type: str,
22
+ chat_model: ChatModel,
23
+ name: str = "FunnyBot",
24
+ personality: str = "You are the funniest bot ever.",
25
+ ) -> JackBoxBotBase:
26
+ if room_type not in BOT_TYPES.keys():
27
+ raise ValueError(f"Unknown room type: {room_type}")
28
+ return BOT_TYPES[room_type](name=name, personality=personality, chat_model=chat_model)
@@ -27,7 +27,6 @@ class JackBoxBotVariant(Enum):
27
27
  SORRYBOT = JackBoxBotPersonality(
28
28
  name="SorryBot",
29
29
  personality="You are embarrassed by your answers and feel the need to apologize profusely to the rest of the group for them.",
30
- voice="Mouse",
31
30
  )
32
31
  HOSTAGEBOT = JackBoxBotPersonality(
33
32
  name="HostageBot",
@@ -0,0 +1,26 @@
1
+ from abc import ABC
2
+ from typing import Optional
3
+
4
+ from ai_plays_jackbox.bot.bot_base import JackBoxBotBase
5
+
6
+
7
+ class JackBox5BotBase(JackBoxBotBase, ABC):
8
+ _actual_player_operation_key: Optional[str] = None
9
+
10
+ @property
11
+ def _player_operation_key(self):
12
+ return f"bc:customer:"
13
+
14
+ def _is_player_operation_key(self, operation_key: str) -> bool:
15
+ if self._actual_player_operation_key is None and self._player_operation_key in operation_key:
16
+ self._actual_player_operation_key = operation_key
17
+ return True
18
+ else:
19
+ return self._actual_player_operation_key == operation_key
20
+
21
+ @property
22
+ def _room_operation_key(self):
23
+ return "bc:room"
24
+
25
+ def _is_room_operation_key(self, operation_key: str) -> bool:
26
+ return operation_key == self._room_operation_key
@@ -0,0 +1,121 @@
1
+ import random
2
+ from typing import Optional
3
+
4
+ import demoji
5
+ from loguru import logger
6
+
7
+ from ai_plays_jackbox.bot.jackbox5.bot_base import JackBox5BotBase
8
+
9
+ _WORD_PROMPT_TEMPLATE = """
10
+ You are playing Mad Verse City. You are being asked to come up with a word.
11
+
12
+ {prompt}
13
+
14
+ When generating your response, follow these rules:
15
+ - You response must be {max_length} characters or less. It must be a singular word
16
+ - Your personality is: {personality}
17
+ - Do not include quotes in your response or any newlines, just the response itself.
18
+ - Some suggestions for the word are {suggestions}
19
+ """
20
+
21
+ _RHYME_PROMPT_TEMPLATE = """
22
+ You are playing Mad Verse City.
23
+
24
+ {prompt}
25
+
26
+ When generating your response, follow these rules:
27
+ - Your response must rhyme with {rhyme_word}. It cannot be the same word as that.
28
+ - You response must be {max_length} characters or less.
29
+ - Your personality is: {personality}
30
+ - Do not include quotes in your response or any newlines, just the response itself.
31
+ """
32
+
33
+
34
+ class MadVerseCityBot(JackBox5BotBase):
35
+ _actual_player_operation_key: Optional[str] = None
36
+ _current_word: str
37
+
38
+ def __init__(self, *args, **kwargs):
39
+ super().__init__(*args, **kwargs)
40
+
41
+ def _handle_welcome(self, data: dict):
42
+ pass
43
+
44
+ def _handle_player_operation(self, data: dict):
45
+ if not data:
46
+ return
47
+ room_state = data.get("state", None)
48
+ if not room_state:
49
+ return
50
+
51
+ prompt: dict[str, str] = data.get("prompt", {})
52
+ prompt_html = prompt.get("html", "")
53
+ clean_prompt = self._html_to_text(prompt_html)
54
+
55
+ max_length = data.get("maxLength", 40)
56
+ suggestions = data.get("suggestions", [])
57
+ if not suggestions:
58
+ suggestions = []
59
+ choices: list[dict] = data.get("choices", [])
60
+
61
+ match room_state:
62
+ case "EnterSingleText":
63
+ if "Give me a" in clean_prompt:
64
+ word = self._generate_word(clean_prompt, max_length - 10, suggestions)
65
+ self._current_word = demoji.replace(word, "")
66
+ self._client_send({"action": "write", "entry": word})
67
+
68
+ if "Now, write a line to rhyme with" in clean_prompt:
69
+ rhyme = self._generate_rhyme(clean_prompt, max_length - 10, self._current_word)
70
+ rhyme = demoji.replace(rhyme, "")
71
+ self._client_send({"action": "write", "entry": rhyme})
72
+
73
+ case "MakeSingleChoice":
74
+ if not choices:
75
+ pass
76
+ if "Who won this battle" in data.get("prompt", {}).get("text", "") and data.get("chosen", None) != 0:
77
+ choice_indexes = [i for i in range(0, len(choices))]
78
+ choice = random.choice(choice_indexes)
79
+ self._client_send({"action": "choose", "choice": choice})
80
+
81
+ if (
82
+ "Press this button to skip the tutorial" in data.get("prompt", {}).get("text", "")
83
+ and data.get("chosen", None) != 0
84
+ ):
85
+ self._client_send({"action": "choose", "choice": 0})
86
+
87
+ def _handle_room_operation(self, data: dict):
88
+ pass
89
+
90
+ def _generate_word(self, prompt: str, max_length: int, suggestions: list[str]) -> str:
91
+ logger.info("Generating word...")
92
+ formatted_prompt = _WORD_PROMPT_TEMPLATE.format(
93
+ personality=self._personality,
94
+ prompt=prompt,
95
+ max_length=max_length,
96
+ suggestions=", ".join(suggestions),
97
+ )
98
+
99
+ word = self._chat_model.generate_text(
100
+ formatted_prompt,
101
+ "",
102
+ temperature=self._chat_model._chat_model_temperature,
103
+ top_p=self._chat_model._chat_model_top_p,
104
+ )
105
+ return word
106
+
107
+ def _generate_rhyme(self, prompt: str, max_length: int, rhyme_word: str) -> str:
108
+ logger.info("Generating rhyme...")
109
+ formatted_prompt = _RHYME_PROMPT_TEMPLATE.format(
110
+ personality=self._personality,
111
+ prompt=prompt,
112
+ max_length=max_length,
113
+ rhyme_word=rhyme_word,
114
+ )
115
+ rhyme = self._chat_model.generate_text(
116
+ formatted_prompt,
117
+ "",
118
+ temperature=self._chat_model._chat_model_temperature,
119
+ top_p=self._chat_model._chat_model_top_p,
120
+ )
121
+ return rhyme