pi-roundtable-drawing 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +31 -0
- package/LICENSE +21 -0
- package/README.md +231 -0
- package/examples/decks/playing-cards/deck.json +274 -0
- package/examples/decks/tarot/deck.json +395 -0
- package/examples/roundtable.config.ts +23 -0
- package/fonts/NotoSansTC-Bold.otf +0 -0
- package/fonts/OFL.txt +92 -0
- package/package.json +54 -0
- package/src/cards/deck.ts +204 -0
- package/src/cards/draw.ts +66 -0
- package/src/cards/spread.ts +232 -0
- package/src/errors.ts +4 -0
- package/src/index.ts +5 -0
- package/src/magic.ts +525 -0
- package/src/plugin.ts +61 -0
- package/src/random.ts +19 -0
- package/src/relationship-map.ts +466 -0
- package/src/style.ts +31 -0
- package/src/tools/draw-cards-tool.ts +121 -0
- package/src/tools/image-tool.ts +97 -0
- package/src/tools/magic-tools.ts +140 -0
- package/src/tools/relationship-map-tool.ts +65 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
5
|
+
|
|
6
|
+
## [Unreleased]
|
|
7
|
+
|
|
8
|
+
## [0.7.0] - Unreleased
|
|
9
|
+
|
|
10
|
+
Prepared for the first npm publication.
|
|
11
|
+
The earlier `0.1.0` was local-only and was never published on npm.
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
|
|
15
|
+
- Move into the pi-roundtable workspace with preserved Git history and lockstep version `0.7.0`.
|
|
16
|
+
The shared `publish.yml` releases the core and all packages from one `v*` tag.
|
|
17
|
+
- The tools attach each picture to the agent's own reply with `turn.attachFile` instead of posting it as a separate message from the bot, so on Discord the picture arrives under the agent's name and avatar, after its text.
|
|
18
|
+
This needs pi-roundtable 0.7.0 or newer; the peer dependency is now `>=0.7.0 <0.8.0`.
|
|
19
|
+
- A picture over the 10 MiB file limit of `REPLY_FILE_LIMITS` is refused with advice to ask for a smaller one.
|
|
20
|
+
A turn that already holds the most files, or a surface that cannot carry files, fails the call with the host's message instead of dropping the picture.
|
|
21
|
+
- The tool results now say the picture is attached to the reply, not posted to the channel.
|
|
22
|
+
- Ship `@types/d3-force` as a dependency, since consuming hosts compile the published TypeScript source and need its declarations.
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
|
|
26
|
+
- `drawing(options)`: a pi-roundtable plugin whose tools draw images locally with node-canvas and post them to the channel of the turn as files.
|
|
27
|
+
- `relationship_map`: hand-drawn relationship maps, Rough.js on canvas laid out with d3-force.
|
|
28
|
+
- `magic_circle_generate`, `sigil_generate` and `sacred_geometry_generate`: magic circles, sigils, and sacred geometry. The Sri Yantra is a simplified figure, not the canonical nine-triangle construction.
|
|
29
|
+
- `draw_cards`: card draws and spreads from the operator's own decks, read from `deckDir` (one subdirectory with a `deck.json` for each deck). The package ships no card faces.
|
|
30
|
+
- The options `deckDir`, `random` (a seedable random source) and `minTier`.
|
|
31
|
+
- Noto Sans TC Bold under the SIL Open Font License, with its license, for the labels.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 wayne930242
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
# pi-roundtable-drawing
|
|
2
|
+
|
|
3
|
+
A plugin for [pi-roundtable](https://github.com/wayne930242/pi-roundtable), the Discord agent server, that gives agents tools to draw images locally and post them in the channel:
|
|
4
|
+
|
|
5
|
+
- Relationship maps: characters and factions joined by typed relationships, hand-drawn.
|
|
6
|
+
- Magic circles, sigils, and sacred geometry.
|
|
7
|
+
- Card draws and spreads, from tarot or playing-card decks that you supply.
|
|
8
|
+
|
|
9
|
+
Everything is drawn on the host with [node-canvas](https://github.com/Automattic/node-canvas); there is no image service, API key, or network call.
|
|
10
|
+
Bun only, like pi-roundtable.
|
|
11
|
+
The package ships its TypeScript source, so there is no build step.
|
|
12
|
+
MIT licensed.
|
|
13
|
+
|
|
14
|
+
## What you need
|
|
15
|
+
|
|
16
|
+
- [Bun](https://bun.sh/docs/installation) 1.3 or newer.
|
|
17
|
+
- A running pi-roundtable host, version 0.7.0 or newer in the 0.7 line (`pi-roundtable` is a peer dependency, `>=0.7.0 <0.8.0`). Earlier versions cannot attach files to an agent's reply, which these tools rely on.
|
|
18
|
+
- The native [canvas](#the-native-canvas-dependency) package, which comes with this one.
|
|
19
|
+
- For `draw_cards`, a deck directory of your own; see [the deck directory](#the-deck-directory).
|
|
20
|
+
|
|
21
|
+
## Install
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
bun add pi-roundtable-drawing
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
canvas fetches its native binary in an install script.
|
|
28
|
+
Bun runs it without further setup, since canvas is on Bun's default list of trusted dependencies; if your project sets `trustedDependencies` itself, keep `canvas` in it.
|
|
29
|
+
|
|
30
|
+
### The native canvas dependency
|
|
31
|
+
|
|
32
|
+
[canvas](https://github.com/Automattic/node-canvas) is a native module, which draws with Cairo and Pango.
|
|
33
|
+
Its install script downloads a prebuilt binary where it has one: macOS (Intel and Apple silicon), Linux on x86-64 with glibc, and Windows on x86-64.
|
|
34
|
+
Anywhere else, such as Linux on ARM64 or Alpine, it compiles from source, which needs a compiler and the libraries below.
|
|
35
|
+
|
|
36
|
+
| System | Command |
|
|
37
|
+
| --- | --- |
|
|
38
|
+
| Ubuntu and Debian | `sudo apt-get install build-essential libcairo2-dev libpango1.0-dev libjpeg-dev libgif-dev librsvg2-dev` |
|
|
39
|
+
| Fedora | `sudo yum install gcc-c++ cairo-devel pango-devel libjpeg-turbo-devel giflib-devel` |
|
|
40
|
+
| macOS | `brew install pkg-config cairo pango libpng jpeg giflib librsvg pixman python-setuptools` |
|
|
41
|
+
|
|
42
|
+
On a prebuilt platform you need none of these.
|
|
43
|
+
If the module cannot load, the host fails when it imports the plugin, with the loader's own message; reinstall after the libraries are in place, with `bun install --force`.
|
|
44
|
+
|
|
45
|
+
## Configure
|
|
46
|
+
|
|
47
|
+
List the plugin in `roundtable.config.ts`.
|
|
48
|
+
It reads nothing from the environment and needs no database.
|
|
49
|
+
|
|
50
|
+
<!-- example: examples/roundtable.config.ts -->
|
|
51
|
+
```ts
|
|
52
|
+
import type { RoundtableConfig } from "pi-roundtable";
|
|
53
|
+
import { drawing } from "pi-roundtable-drawing";
|
|
54
|
+
|
|
55
|
+
// Credentials come from .env, which Bun loads on its own; nothing secret belongs in this file.
|
|
56
|
+
const env = (name: string): string => process.env[name] ?? "";
|
|
57
|
+
|
|
58
|
+
export default {
|
|
59
|
+
name: "Roundtable",
|
|
60
|
+
owner: { id: env("OWNER_ID"), name: env("OWNER_NAME") },
|
|
61
|
+
discord: {
|
|
62
|
+
token: env("DISCORD_TOKEN"),
|
|
63
|
+
guild: env("DISCORD_GUILD_ID"),
|
|
64
|
+
entryChannel: env("DISCORD_ENTRY_CHANNEL_ID"),
|
|
65
|
+
},
|
|
66
|
+
database: { url: env("DATABASE_URL") },
|
|
67
|
+
dataDir: "./data",
|
|
68
|
+
model: env("MODEL"),
|
|
69
|
+
http: { publicUrl: env("PUBLIC_URL") },
|
|
70
|
+
plugins: [
|
|
71
|
+
// The deck directory is optional: without it the plugin has no draw_cards tool.
|
|
72
|
+
drawing({ deckDir: "./decks" }),
|
|
73
|
+
],
|
|
74
|
+
} satisfies RoundtableConfig;
|
|
75
|
+
```
|
|
76
|
+
<!-- /example -->
|
|
77
|
+
|
|
78
|
+
### `drawing(options)`
|
|
79
|
+
|
|
80
|
+
| Option | Type | Default | What it is |
|
|
81
|
+
| --- | --- | --- | --- |
|
|
82
|
+
| `deckDir` | `string` | none | A directory with one subdirectory for each deck; see [the deck directory](#the-deck-directory). Without it the plugin has no `draw_cards` tool. A directory that does not exist, holds no deck, or holds an invalid manifest stops the start with a message that names the deck and what to fix |
|
|
83
|
+
| `random` | `() => number` | `Math.random` | The random source of the map layouts and the card draws, returning a number from 0 up to, not including, 1. A seeded source makes a run reproducible: a new `seededRandom(seed)` given the same calls draws the same pictures. `seededRandom(seed)` is exported for that |
|
|
84
|
+
| `minTier` | `"owner" \| "admin" \| "member"` | `member` | The lowest tier that may use the tools. The operator's `toolTiers` setting still wins |
|
|
85
|
+
|
|
86
|
+
The plugin is named `drawing`.
|
|
87
|
+
|
|
88
|
+
## The tools
|
|
89
|
+
|
|
90
|
+
Each tool draws one PNG and attaches it to the agent's reply with `turn.attachFile`.
|
|
91
|
+
The model reads a short text back (the file name, and for card draws the cards), and a request it can fix comes back as a refusal with a plain message, such as `An edge references the unknown node "Ghost". Add it to nodes or fix the edge.`
|
|
92
|
+
Nothing is attached for a refused call.
|
|
93
|
+
|
|
94
|
+
The tool does not send a message of its own: the host delivers the picture with the reply of the turn, after the agent's text, under the same name and avatar as the text.
|
|
95
|
+
On Discord each picture goes out as one file in its own message.
|
|
96
|
+
|
|
97
|
+
The host limits what one turn may attach (`REPLY_FILE_LIMITS`: 10 files, 10 MiB for a file, 50 MiB in all), and a chat surface may be stricter.
|
|
98
|
+
A picture over 10 MiB is refused with a message that suggests a smaller one, such as fewer cards or a smaller `size`.
|
|
99
|
+
A turn that already holds 10 files, or a surface that cannot carry files, fails the call with the host's message; the picture is never dropped silently.
|
|
100
|
+
Each tool call attaches one picture, so one reply can carry up to ten of them.
|
|
101
|
+
|
|
102
|
+
| Tool | What it draws | Arguments |
|
|
103
|
+
| --- | --- | --- |
|
|
104
|
+
| `relationship_map` | A hand-drawn map of characters and factions with typed relationships, sized to its content: a node grows to hold its name | `title` (up to 80 characters); `nodes` (1 to 40, each with a unique `id` of up to 60 characters, `type` of `pc`, `npc` or `faction`, and an optional `label` of up to 60); `edges` (up to 80, each between two different nodes, with `from` and `to` node ids, `type` of `romantic`, `entanglement`, `bond`, `faction` or `hostile`, and an optional `label` of up to 60) |
|
|
105
|
+
| `magic_circle_generate` | A pentagram, hexagram, or Tree of Life, with optional elemental symbols and text around the rim | `type` (`pentagram`, `hexagram`, `tree_of_life` or `custom`, which draws a pentagram); `style` (`traditional`, `modern`, `geometric`: the line weight); `elements` (up to four of `fire`, `air`, `water`, `earth`, placed north, east, south, west); `text` (up to 60 characters); `size` (`small`, `medium`, `large`: 512, 1024 or 2048 px); `background` |
|
|
106
|
+
| `sigil_generate` | A sigil from an intention, 512 px square | `intention` (up to 200 characters); `method` (`chaos`, `rose_cross`, `planetary` or `geometric`); `complexity` (`simple`, `elaborate`); `style` (`traditional`, `modern`); `background` |
|
|
107
|
+
| `sacred_geometry_generate` | The Flower of Life, Metatron's Cube, a simplified Sri Yantra, or the Vesica Piscis, 1024 px square, always scaled to fit the frame | `pattern` (`flower_of_life`, `metatron`, `sri_yantra`, `vesica_pisces`); `layers` (1 to 9, default 3); `rotation` (degrees); `colors` (up to 9 CSS colors, cycled per layer); `background` |
|
|
108
|
+
| `draw_cards` | A spread of cards drawn from a deck | See [card draws](#card-draws) |
|
|
109
|
+
|
|
110
|
+
`background` is `dark` (the default), `white`, or `transparent`.
|
|
111
|
+
The chaos, rose cross, and planetary sigils draw from the letters of the intention; an intention with no letters is refused, and the geometric method takes any text.
|
|
112
|
+
|
|
113
|
+
A relationship map and a card draw are random: a map's layout and hand-drawn wobble come from the `random` option, and a draw shuffles with it.
|
|
114
|
+
The magic circle, sigil, and sacred geometry tools draw the same picture for the same arguments.
|
|
115
|
+
|
|
116
|
+
What `layers` does depends on the pattern: the Flower of Life has that many rings of circles around the centre, the Vesica Piscis that many nested pairs of circles, and the Sri Yantra that many nested pairs of triangles.
|
|
117
|
+
Metatron's Cube has one fixed form that `layers` only switches: one layer is the inner seven circles, two or more the full thirteen, each joined to every other by a line.
|
|
118
|
+
The Sri Yantra is a simplified figure of interlocking upward and downward triangles around a point, in a circle; it is not the canonical nine-triangle construction.
|
|
119
|
+
|
|
120
|
+
### Card draws
|
|
121
|
+
|
|
122
|
+
`draw_cards` draws from a full, freshly shuffled deck on every call; there is no deck kept between draws.
|
|
123
|
+
It takes:
|
|
124
|
+
|
|
125
|
+
| Argument | What it is |
|
|
126
|
+
| --- | --- |
|
|
127
|
+
| `deck` | The id of one of your decks: the name of its directory |
|
|
128
|
+
| `count` | How many cards, from 1 to 100 and no more than the deck has left. The cards fill rows of up to 5, 5, or 7 |
|
|
129
|
+
| `group` | Draw only from the cards of this group, such as `major` in a tarot deck |
|
|
130
|
+
| `exclude` | Card ids to leave out; an id the deck does not list is refused |
|
|
131
|
+
| `allow_reversed` | Whether cards may come up reversed, which draws them upside down. The deck's `reversals` setting when omitted |
|
|
132
|
+
| `spread` | One `{ row, col, label }` for each card, to place them in a named spread (rows and columns from 0 to 10, no two cards in one place). Without it the cards fill rows in order |
|
|
133
|
+
| `question` | Shown under the deck's name on the picture |
|
|
134
|
+
|
|
135
|
+
The tool's description lists your decks by id and the `name` of each manifest, with the number of cards and their groups, so the model knows what it can draw from.
|
|
136
|
+
No deck name is built into the package.
|
|
137
|
+
|
|
138
|
+
## The deck directory
|
|
139
|
+
|
|
140
|
+
The package ships no card faces.
|
|
141
|
+
You point `deckDir` at your own directory, which holds one subdirectory for each deck.
|
|
142
|
+
The subdirectory's name is the deck's id (lowercase letters, digits, dashes, and underscores), and it holds a `deck.json` and the face images the manifest names:
|
|
143
|
+
|
|
144
|
+
```text
|
|
145
|
+
decks/
|
|
146
|
+
my-tarot/
|
|
147
|
+
deck.json
|
|
148
|
+
faces/
|
|
149
|
+
fool.jpg
|
|
150
|
+
magician.jpg
|
|
151
|
+
playing-cards/
|
|
152
|
+
deck.json
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
A subdirectory without a `deck.json` is ignored.
|
|
156
|
+
Use only images that you may use: the package cannot tell you whether a deck's artwork is free to copy.
|
|
157
|
+
[`examples/decks`](examples/decks) holds two example manifests, with no faces and no artwork: a 54-card deck of playing cards and a 78-card tarot deck with the traditional names.
|
|
158
|
+
A card without a `file` is drawn as a plain tile with its name, so a deck works before you have any images.
|
|
159
|
+
|
|
160
|
+
### The manifest
|
|
161
|
+
|
|
162
|
+
```json
|
|
163
|
+
{
|
|
164
|
+
"name": "My Tarot",
|
|
165
|
+
"reversals": true,
|
|
166
|
+
"aspect": 1.714,
|
|
167
|
+
"cards": [
|
|
168
|
+
{ "id": "fool", "name": "The Fool", "file": "faces/fool.jpg", "group": "major" },
|
|
169
|
+
{ "id": "ace-of-cups", "name": "Ace of Cups", "file": "faces/cups-01.jpg", "group": "cups" },
|
|
170
|
+
{ "id": "joker", "name": "Joker" }
|
|
171
|
+
]
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
| Field | Required | What it is |
|
|
176
|
+
| --- | --- | --- |
|
|
177
|
+
| `name` | yes | The deck's name, shown on the picture and in the tool's description |
|
|
178
|
+
| `reversals` | no | Whether a draw may turn cards upside down unless the call says otherwise; default `true`. Set `false` for a deck that is read upright, or for playing cards |
|
|
179
|
+
| `aspect` | no | A card's height divided by its width, from 0.25 to 4. Without it the ratio of the first face drawn is used, and 1.5 when no drawn card has a face |
|
|
180
|
+
| `cards` | yes | At least one card |
|
|
181
|
+
| `cards[].id` | yes | What the model passes to `exclude` and reads back, unique within the deck |
|
|
182
|
+
| `cards[].name` | yes | What is written under the card and returned to the model |
|
|
183
|
+
| `cards[].file` | no | The face image, relative to the deck's directory: `.png`, `.jpg`, `.jpeg`, or `.gif`. It must exist and be a readable image whose height is between 0.25 and 4 times its width, and it may not lead outside the deck's directory, through a `..` segment, an absolute path, or a link |
|
|
184
|
+
| `cards[].group` | no | A group the model can draw from, such as a suit |
|
|
185
|
+
|
|
186
|
+
Any other field is refused, so a misspelled key does not pass silently.
|
|
187
|
+
The manifests, and every face they name, are read when the host starts: a bad one stops the start with a message such as `plugin drawing: deck my-tarot: card fool: file faces/fool.jpg does not exist in the deck directory.`
|
|
188
|
+
|
|
189
|
+
## Fonts and licenses
|
|
190
|
+
|
|
191
|
+
Labels are drawn in Noto Sans TC Bold, which covers Latin and Chinese, so names in either script render.
|
|
192
|
+
The font is under the SIL Open Font License 1.1; its license is [`fonts/OFL.txt`](fonts/OFL.txt) and ships with the package.
|
|
193
|
+
The package itself is MIT licensed.
|
|
194
|
+
|
|
195
|
+
## Testing a plugin that uses it
|
|
196
|
+
|
|
197
|
+
`testPlugin` from `pi-roundtable/testing` runs the plugin offline.
|
|
198
|
+
Pass a chat surface for the prefix of the channel the test uses (the default channel is `test:1`) that declares `supportsFiles: true`, since a surface that does not makes the call fail, and read the files from `harness.files`, each with the channel of the turn it was attached to:
|
|
199
|
+
|
|
200
|
+
```ts
|
|
201
|
+
import { testPlugin } from "pi-roundtable/testing";
|
|
202
|
+
import { drawing, seededRandom } from "pi-roundtable-drawing";
|
|
203
|
+
|
|
204
|
+
const harness = await testPlugin(drawing({ random: seededRandom(1) }), {
|
|
205
|
+
surfaces: [
|
|
206
|
+
{
|
|
207
|
+
surface: "test",
|
|
208
|
+
supportsFiles: true,
|
|
209
|
+
start: async () => {},
|
|
210
|
+
sendReply: async () => {},
|
|
211
|
+
},
|
|
212
|
+
],
|
|
213
|
+
});
|
|
214
|
+
await harness.runTool("sigil_generate", { intention: "home", method: "chaos" });
|
|
215
|
+
const { channel, file } = harness.files[0]; // "test:1", sigil.png
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## Development and publishing
|
|
219
|
+
|
|
220
|
+
This package lives in `packages/drawing` in the pi-roundtable workspace.
|
|
221
|
+
Run these commands from the repository root:
|
|
222
|
+
|
|
223
|
+
```sh
|
|
224
|
+
bun install --frozen-lockfile
|
|
225
|
+
bun run --cwd packages/drawing typecheck
|
|
226
|
+
bun run --cwd packages/drawing lint
|
|
227
|
+
bun run --cwd packages/drawing test
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
The package shares the core's version and single `v*` release tag.
|
|
231
|
+
The shared `publish.yml` checks all workspaces and publishes each npm package separately with provenance.
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "Playing cards",
|
|
3
|
+
"reversals": false,
|
|
4
|
+
"cards": [
|
|
5
|
+
{
|
|
6
|
+
"id": "AS",
|
|
7
|
+
"name": "A of Spades",
|
|
8
|
+
"group": "spades"
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"id": "2S",
|
|
12
|
+
"name": "2 of Spades",
|
|
13
|
+
"group": "spades"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"id": "3S",
|
|
17
|
+
"name": "3 of Spades",
|
|
18
|
+
"group": "spades"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"id": "4S",
|
|
22
|
+
"name": "4 of Spades",
|
|
23
|
+
"group": "spades"
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"id": "5S",
|
|
27
|
+
"name": "5 of Spades",
|
|
28
|
+
"group": "spades"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"id": "6S",
|
|
32
|
+
"name": "6 of Spades",
|
|
33
|
+
"group": "spades"
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"id": "7S",
|
|
37
|
+
"name": "7 of Spades",
|
|
38
|
+
"group": "spades"
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"id": "8S",
|
|
42
|
+
"name": "8 of Spades",
|
|
43
|
+
"group": "spades"
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"id": "9S",
|
|
47
|
+
"name": "9 of Spades",
|
|
48
|
+
"group": "spades"
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"id": "10S",
|
|
52
|
+
"name": "10 of Spades",
|
|
53
|
+
"group": "spades"
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"id": "JS",
|
|
57
|
+
"name": "J of Spades",
|
|
58
|
+
"group": "spades"
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"id": "QS",
|
|
62
|
+
"name": "Q of Spades",
|
|
63
|
+
"group": "spades"
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"id": "KS",
|
|
67
|
+
"name": "K of Spades",
|
|
68
|
+
"group": "spades"
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"id": "AH",
|
|
72
|
+
"name": "A of Hearts",
|
|
73
|
+
"group": "hearts"
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"id": "2H",
|
|
77
|
+
"name": "2 of Hearts",
|
|
78
|
+
"group": "hearts"
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"id": "3H",
|
|
82
|
+
"name": "3 of Hearts",
|
|
83
|
+
"group": "hearts"
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
"id": "4H",
|
|
87
|
+
"name": "4 of Hearts",
|
|
88
|
+
"group": "hearts"
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
"id": "5H",
|
|
92
|
+
"name": "5 of Hearts",
|
|
93
|
+
"group": "hearts"
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"id": "6H",
|
|
97
|
+
"name": "6 of Hearts",
|
|
98
|
+
"group": "hearts"
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
"id": "7H",
|
|
102
|
+
"name": "7 of Hearts",
|
|
103
|
+
"group": "hearts"
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
"id": "8H",
|
|
107
|
+
"name": "8 of Hearts",
|
|
108
|
+
"group": "hearts"
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
"id": "9H",
|
|
112
|
+
"name": "9 of Hearts",
|
|
113
|
+
"group": "hearts"
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
"id": "10H",
|
|
117
|
+
"name": "10 of Hearts",
|
|
118
|
+
"group": "hearts"
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
"id": "JH",
|
|
122
|
+
"name": "J of Hearts",
|
|
123
|
+
"group": "hearts"
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
"id": "QH",
|
|
127
|
+
"name": "Q of Hearts",
|
|
128
|
+
"group": "hearts"
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
"id": "KH",
|
|
132
|
+
"name": "K of Hearts",
|
|
133
|
+
"group": "hearts"
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
"id": "AD",
|
|
137
|
+
"name": "A of Diamonds",
|
|
138
|
+
"group": "diamonds"
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
"id": "2D",
|
|
142
|
+
"name": "2 of Diamonds",
|
|
143
|
+
"group": "diamonds"
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
"id": "3D",
|
|
147
|
+
"name": "3 of Diamonds",
|
|
148
|
+
"group": "diamonds"
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
"id": "4D",
|
|
152
|
+
"name": "4 of Diamonds",
|
|
153
|
+
"group": "diamonds"
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
"id": "5D",
|
|
157
|
+
"name": "5 of Diamonds",
|
|
158
|
+
"group": "diamonds"
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
"id": "6D",
|
|
162
|
+
"name": "6 of Diamonds",
|
|
163
|
+
"group": "diamonds"
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
"id": "7D",
|
|
167
|
+
"name": "7 of Diamonds",
|
|
168
|
+
"group": "diamonds"
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
"id": "8D",
|
|
172
|
+
"name": "8 of Diamonds",
|
|
173
|
+
"group": "diamonds"
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
"id": "9D",
|
|
177
|
+
"name": "9 of Diamonds",
|
|
178
|
+
"group": "diamonds"
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
"id": "10D",
|
|
182
|
+
"name": "10 of Diamonds",
|
|
183
|
+
"group": "diamonds"
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
"id": "JD",
|
|
187
|
+
"name": "J of Diamonds",
|
|
188
|
+
"group": "diamonds"
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
"id": "QD",
|
|
192
|
+
"name": "Q of Diamonds",
|
|
193
|
+
"group": "diamonds"
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
"id": "KD",
|
|
197
|
+
"name": "K of Diamonds",
|
|
198
|
+
"group": "diamonds"
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
"id": "AC",
|
|
202
|
+
"name": "A of Clubs",
|
|
203
|
+
"group": "clubs"
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
"id": "2C",
|
|
207
|
+
"name": "2 of Clubs",
|
|
208
|
+
"group": "clubs"
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
"id": "3C",
|
|
212
|
+
"name": "3 of Clubs",
|
|
213
|
+
"group": "clubs"
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
"id": "4C",
|
|
217
|
+
"name": "4 of Clubs",
|
|
218
|
+
"group": "clubs"
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
"id": "5C",
|
|
222
|
+
"name": "5 of Clubs",
|
|
223
|
+
"group": "clubs"
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
"id": "6C",
|
|
227
|
+
"name": "6 of Clubs",
|
|
228
|
+
"group": "clubs"
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
"id": "7C",
|
|
232
|
+
"name": "7 of Clubs",
|
|
233
|
+
"group": "clubs"
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
"id": "8C",
|
|
237
|
+
"name": "8 of Clubs",
|
|
238
|
+
"group": "clubs"
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
"id": "9C",
|
|
242
|
+
"name": "9 of Clubs",
|
|
243
|
+
"group": "clubs"
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
"id": "10C",
|
|
247
|
+
"name": "10 of Clubs",
|
|
248
|
+
"group": "clubs"
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
"id": "JC",
|
|
252
|
+
"name": "J of Clubs",
|
|
253
|
+
"group": "clubs"
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
"id": "QC",
|
|
257
|
+
"name": "Q of Clubs",
|
|
258
|
+
"group": "clubs"
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
"id": "KC",
|
|
262
|
+
"name": "K of Clubs",
|
|
263
|
+
"group": "clubs"
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
"id": "JOKER_B",
|
|
267
|
+
"name": "Black Joker"
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
"id": "JOKER_R",
|
|
271
|
+
"name": "Red Joker"
|
|
272
|
+
}
|
|
273
|
+
]
|
|
274
|
+
}
|