juicyjuicy 0.8.0 → 0.9.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/dist/index.js
CHANGED
|
@@ -19807,7 +19807,7 @@ function date4(params) {
|
|
|
19807
19807
|
}
|
|
19808
19808
|
|
|
19809
19809
|
// ../shared/src/cli.ts
|
|
19810
|
-
var CLI_RELEASE = "0.
|
|
19810
|
+
var CLI_RELEASE = "0.9.0";
|
|
19811
19811
|
function cliCommand(args) {
|
|
19812
19812
|
return `npx -y juicyjuicy@${CLI_RELEASE} ${args}`;
|
|
19813
19813
|
}
|
|
@@ -19888,6 +19888,10 @@ var RATE_LIMITS = {
|
|
|
19888
19888
|
* so it also bounds how many rooms a member can open.
|
|
19889
19889
|
*/
|
|
19890
19890
|
roomTicket: { limit: 60, windowSeconds: 10 * 60 },
|
|
19891
|
+
/** Highscores sent (D56); the player sends at most one per game every 5 s, and only a new best. */
|
|
19892
|
+
scoreSubmit: { limit: 60, windowSeconds: 10 * 60 },
|
|
19893
|
+
/** Leaderboard reads (D56), per member or, for visitors, per network. */
|
|
19894
|
+
scoreRead: { limit: 120, windowSeconds: 10 * 60 },
|
|
19891
19895
|
/** Thumbnails uploaded from the dashboard (each publish can bring one too). */
|
|
19892
19896
|
thumbnail: { limit: 20, windowSeconds: 60 * 60, newAccountLimit: 10 },
|
|
19893
19897
|
/**
|
|
@@ -20240,7 +20244,14 @@ var GAME_MESSAGES = {
|
|
|
20240
20244
|
*/
|
|
20241
20245
|
room: "juicyjuicy:room",
|
|
20242
20246
|
/** `{ type, code }`: show the share sheet for an invite link to this room (D54). No answer. */
|
|
20243
|
-
invite: "juicyjuicy:invite"
|
|
20247
|
+
invite: "juicyjuicy:invite",
|
|
20248
|
+
/**
|
|
20249
|
+
* `{ type, id, score, order, format }`: the member's score, kept on the game's leaderboard when
|
|
20250
|
+
* it beats their best (D56). Answered with `scored`.
|
|
20251
|
+
*/
|
|
20252
|
+
score: "juicyjuicy:score",
|
|
20253
|
+
/** `{ type, id, limit }`: the game's leaderboard, its top `limit` (D56). Answered with `leaderboard`. */
|
|
20254
|
+
scores: "juicyjuicy:scores"
|
|
20244
20255
|
};
|
|
20245
20256
|
|
|
20246
20257
|
// ../shared/src/features.ts
|
|
@@ -20285,10 +20296,15 @@ var GAME_FEATURES = [
|
|
|
20285
20296
|
{
|
|
20286
20297
|
id: "highscores",
|
|
20287
20298
|
name: "Global highscores",
|
|
20288
|
-
|
|
20299
|
+
summary: "A leaderboard of every signed-in player's best, in the game and on its page.",
|
|
20300
|
+
adds: "A leaderboard kept by JuicyJuicy with its juicy.scores snippet: each signed-in player's best score or time, shown in the game and on its page on JuicyJuicy.",
|
|
20289
20301
|
why: "Players compete with everyone who played, not just their own best, and come back to climb.",
|
|
20290
|
-
detection: { kind: "
|
|
20291
|
-
status: "
|
|
20302
|
+
detection: { kind: "marker", marker: GAME_MESSAGES.score },
|
|
20303
|
+
status: "available",
|
|
20304
|
+
recipe: "reference/highscores.md",
|
|
20305
|
+
task: "add JuicyJuicy global highscores: send each finished run's score and show the leaderboard in the game",
|
|
20306
|
+
docs: "/docs/publish#highscores",
|
|
20307
|
+
missingHint: "paste the juicy.scores snippet (https://juicyjuicy.games/docs/publish/cli#highscores) and send scores with juicy.scores.submit(), or take it out of features"
|
|
20292
20308
|
},
|
|
20293
20309
|
{
|
|
20294
20310
|
id: "multiplayer",
|
|
@@ -21175,6 +21191,9 @@ var ROOM_CODE_ALPHABET = "ABCDEFGHJKMNPQRSTUVWXYZ23456789";
|
|
|
21175
21191
|
var ROOM_CODE_LENGTH = 6;
|
|
21176
21192
|
var ROOM_CODE_PATTERN = new RegExp(`^[${ROOM_CODE_ALPHABET}]{${ROOM_CODE_LENGTH}}$`);
|
|
21177
21193
|
|
|
21194
|
+
// ../shared/src/scores.ts
|
|
21195
|
+
var points = new Intl.NumberFormat("en-US");
|
|
21196
|
+
|
|
21178
21197
|
// ../shared/src/security-headers.ts
|
|
21179
21198
|
var DENIED_FEATURES = [
|
|
21180
21199
|
"camera",
|
|
@@ -24702,7 +24721,7 @@ var program = new Command();
|
|
|
24702
24721
|
// package.json
|
|
24703
24722
|
var package_default = {
|
|
24704
24723
|
name: "juicyjuicy",
|
|
24705
|
-
version: "0.
|
|
24724
|
+
version: "0.9.0",
|
|
24706
24725
|
description: "Publish AI-made browser games to juicyjuicy.games",
|
|
24707
24726
|
type: "module",
|
|
24708
24727
|
license: "MIT",
|
package/dist/skill/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: juicyjuicy-publish
|
|
3
|
-
description: Publish or update a browser game on JuicyJuicy (juicyjuicy.games) and give the user a link to play it. Use when the user asks to publish, upload, share, deploy, update or release a game "on JuicyJuicy" or "to juicyjuicy.games", or to get a playable link for a web game they made with you. Also use to remix a game from JuicyJuicy (download a remixable game, change it, publish the remix), and to add a JuicyJuicy upgrade to a game (cloud saves, multiplayer, more languages, a browser-friendly menu).
|
|
3
|
+
description: Publish or update a browser game on JuicyJuicy (juicyjuicy.games) and give the user a link to play it. Use when the user asks to publish, upload, share, deploy, update or release a game "on JuicyJuicy" or "to juicyjuicy.games", or to get a playable link for a web game they made with you. Also use to remix a game from JuicyJuicy (download a remixable game, change it, publish the remix), and to add a JuicyJuicy upgrade to a game (cloud saves, global highscores, multiplayer, more languages, a browser-friendly menu).
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Publish a game to JuicyJuicy
|
|
@@ -9,19 +9,19 @@ JuicyJuicy hosts **static browser games** (HTML/JS/CSS/assets, WebAssembly) and
|
|
|
9
9
|
|
|
10
10
|
Run every CLI command with `--json`. Each output line is a JSON object; the **last** line is the result, either `{"ok": true, …}` or `{"ok": false, "error": {"code", "message", "details"?, "hint"?}}`. The exit code is `0` (ok), `1` (fix the game or `juicy.json`), `2` (log in) or `3` (network or server problem, so retry).
|
|
11
11
|
|
|
12
|
-
Run the CLI exactly as written here, `npx -y juicyjuicy@0.
|
|
12
|
+
Run the CLI exactly as written here, `npx -y juicyjuicy@0.9.0 …`: this skill is made for that release. If JuicyJuicy answers that the CLI is out of date, run the command its message gives. It updates the CLI and this skill together; read this skill again afterwards and use the release it names from then on.
|
|
13
13
|
|
|
14
14
|
## Procedure
|
|
15
15
|
|
|
16
16
|
### 1. Make sure the CLI is logged in
|
|
17
17
|
|
|
18
18
|
```bash
|
|
19
|
-
npx -y juicyjuicy@0.
|
|
19
|
+
npx -y juicyjuicy@0.9.0 whoami --json
|
|
20
20
|
```
|
|
21
21
|
|
|
22
22
|
If it exits with `2`, log in. `login` opens the user's browser and **waits up to 10 minutes** for them to approve:
|
|
23
23
|
|
|
24
|
-
- Run `npx -y juicyjuicy@0.
|
|
24
|
+
- Run `npx -y juicyjuicy@0.9.0 login --json` in the background if your shell can, and read its first output line (`{"event": "device_code", "userCode", "verificationUrl"}`) right away, and show the user the code and link without waiting for them to ask: the page shows the same code, and that's how they know the login is theirs.
|
|
25
25
|
- Then tell the user, with the real code and link: *"A JuicyJuicy page opened in your browser. Check it shows the code **WXTZ-BCDF** and click **Approve**. If no page opened, go to <verificationUrl>."*
|
|
26
26
|
- If your shell can't run commands in the background, say that first (without a code: it doesn't exist yet), then run it in the foreground with a timeout of at least 10 minutes. The code is also printed as `Your code: …`.
|
|
27
27
|
- The user needs a JuicyJuicy account (it's invite-only for now). If the page says they can't sign in, they need an invite link from a friend who's a member.
|
|
@@ -45,7 +45,7 @@ Files and folders starting with `.` (`.env`, `.git/`, …) and `node_modules/` a
|
|
|
45
45
|
`juicy.json` sits in the project folder and describes the game. Create or update it with `init`, filling in what you know from the game you built:
|
|
46
46
|
|
|
47
47
|
```bash
|
|
48
|
-
npx -y juicyjuicy@0.
|
|
48
|
+
npx -y juicyjuicy@0.9.0 init --yes --json \
|
|
49
49
|
--title "Snake Deluxe" \
|
|
50
50
|
--description "Eat the apples, don't bite your tail. Arrow keys or swipe." \
|
|
51
51
|
--made-with "<your model name>, <the tool you run in>" \
|
|
@@ -84,7 +84,7 @@ To replace it, the user saves a screenshot as `thumbnail.png` in the project fol
|
|
|
84
84
|
|
|
85
85
|
Do this **every time**, also for updates. It protects the user and their players; JuicyJuicy's server runs its own checks as well, so this is help, not a formality to get past.
|
|
86
86
|
|
|
87
|
-
1. **Check.** Run `npx -y juicyjuicy@0.
|
|
87
|
+
1. **Check.** Run `npx -y juicyjuicy@0.9.0 check --json`. Errors (a failed result) must be fixed: explain each one (`details` has a `rule` and a `path`), and offer to fix it. If the user says no, **stop and don't publish**: the server would refuse it anyway. Warnings in the result (`findings`) go into step 2.
|
|
88
88
|
Every file in the result's `files` will be public. If `unusualFiles` isn't empty (names that look like keys or private data, project files like `package.json`, notes and other files a web game doesn't load), **show the user that list and ask whether those files should be public** before publishing; offer to move them out of the build folder or set `buildDir` to a folder with just the game.
|
|
89
89
|
2. **Security review.** Follow [reference/review.md](reference/review.md): keys and tokens, `innerHTML`/`eval` on network or multiplayer data, `http://` loads, trackers, login or payment forms, anything that leaves the page. Same rule: for what it marks as must-fix, fix it or don't publish.
|
|
90
90
|
3. **Standards.** Check the game against these and tell the user, in a sentence or two, what falls short; offer to fix it. A "no" still publishes: not every game can have touch controls.
|
|
@@ -113,7 +113,7 @@ Keep it short when there's nothing to say: "Checked: no problems found" is enoug
|
|
|
113
113
|
### 6. Publish
|
|
114
114
|
|
|
115
115
|
```bash
|
|
116
|
-
npx -y juicyjuicy@0.
|
|
116
|
+
npx -y juicyjuicy@0.9.0 publish --json --message "<what changed, in a few words>"
|
|
117
117
|
```
|
|
118
118
|
|
|
119
119
|
This makes the new version live: when the user says "publish", that's what they want. Don't add `--draft` (see "Other commands") unless they asked in words for it not to go live yet. Not for a first publish, a big game or one you're unsure of either: the user would have to find the dashboard and make it live themselves.
|
|
@@ -134,12 +134,13 @@ The game's page under My games on JuicyJuicy has an **Upgrades** panel; its Add
|
|
|
134
134
|
|---|---|---|
|
|
135
135
|
| Browser-friendly menu | [reference/browser-menu.md](reference/browser-menu.md) | `"features": ["browser-menu"]` |
|
|
136
136
|
| Cloud saves | [reference/cloud-saves.md](reference/cloud-saves.md) | `"features": ["cloud-saves"]` |
|
|
137
|
+
| Global highscores | [reference/highscores.md](reference/highscores.md) | `"features": ["highscores"]` |
|
|
137
138
|
| Multi-language | [reference/languages.md](reference/languages.md) | `"languages": ["en", …]` |
|
|
138
139
|
| Multiplayer | [reference/multiplayer.md](reference/multiplayer.md) | `"features": ["multiplayer"]` |
|
|
139
140
|
|
|
140
|
-
Other features on the panel (
|
|
141
|
+
Other features on the panel (touch controls, gamepad, mouse lock, auto-pause) are "Coming soon" and have no recipe yet: if asked for one, say so, and build it your own way only if the user wants.
|
|
141
142
|
|
|
142
|
-
1. **Find the project.** Log in first (step 1). The project is the folder whose `juicy.json` has the game's `slug`: the current folder, or one the user points you to. **If it isn't on this computer** (or you're in a chat app or sandbox), download the live version: `npx -y juicyjuicy@0.
|
|
143
|
+
1. **Find the project.** Log in first (step 1). The project is the folder whose `juicy.json` has the game's `slug`: the current folder, or one the user points you to. **If it isn't on this computer** (or you're in a chat app or sandbox), download the live version: `npx -y juicyjuicy@0.9.0 pull <slug> --json` writes it into a new folder named after the slug (`dir` in the result) with its `juicy.json`, and you work there. Read the result's `next`: the download is the built game, so for a bundled one (minified code), ask whether the user has the source somewhere, or use `sourceUrl`, before changing the bundle itself. `pull` only works for the user's own games (`NOT_FOUND` otherwise).
|
|
143
144
|
2. **Read the recipe** for the upgrade and make the change. Keep everything else about the game as it is.
|
|
144
145
|
3. **Build and try it** (step 2), then **declare it** in `juicy.json` as the table says, adding to what's already listed.
|
|
145
146
|
4. **Review and publish** as steps 5–7 say, with `--message "Add <upgrade>"`. In step 5's `check` result, `features.detected` lists what JuicyJuicy found in the files; a `feature-missing` warning means a declared upgrade isn't there yet: fix it before publishing.
|
|
@@ -147,14 +148,14 @@ Other features on the panel (global highscores, touch controls, gamepad, mouse l
|
|
|
147
148
|
|
|
148
149
|
## Bring all my games up to date
|
|
149
150
|
|
|
150
|
-
When the user asks to bring their JuicyJuicy games up to date or fix a **Needs update** badge, when `check` warns `requirement-missing`, or after updating the CLI: follow [reference/update-all-games.md](reference/update-all-games.md). It starts with `npx -y juicyjuicy@0.
|
|
151
|
+
When the user asks to bring their JuicyJuicy games up to date or fix a **Needs update** badge, when `check` warns `requirement-missing`, or after updating the CLI: follow [reference/update-all-games.md](reference/update-all-games.md). It starts with `npx -y juicyjuicy@0.9.0 check-games --json`, which checks every game of the user's at once and settles what a script can; the rest are code changes, one game at a time.
|
|
151
152
|
|
|
152
153
|
## Remix a game
|
|
153
154
|
|
|
154
155
|
A game marked remixable on JuicyJuicy can be downloaded, changed and published as a new game that links back to it. Log in first (step 1), then:
|
|
155
156
|
|
|
156
157
|
```bash
|
|
157
|
-
npx -y juicyjuicy@0.
|
|
158
|
+
npx -y juicyjuicy@0.9.0 remix <slug> --json
|
|
158
159
|
```
|
|
159
160
|
|
|
160
161
|
- The first line is `{"event": "license", "name", "text"}`: the remix license. Tell the user in a sentence what it says. In short: the remix may be published **on JuicyJuicy only**, never anywhere else, and the files aren't to be passed on. The full text is:
|
|
@@ -189,9 +190,9 @@ Everything else is in [reference/troubleshooting.md](reference/troubleshooting.m
|
|
|
189
190
|
|
|
190
191
|
## Other commands
|
|
191
192
|
|
|
192
|
-
- `npx -y juicyjuicy@0.
|
|
193
|
-
- `npx -y juicyjuicy@0.
|
|
194
|
-
- `npx -y juicyjuicy@0.
|
|
195
|
-
- `npx -y juicyjuicy@0.
|
|
196
|
-
- `npx -y juicyjuicy@0.
|
|
197
|
-
- `npx -y juicyjuicy@0.
|
|
193
|
+
- `npx -y juicyjuicy@0.9.0 list --json`: the user's games; `list <slug>` shows one game's versions. `--all` lists every game on JuicyJuicy instead (60 a page, `--page 2` and on; `nextPage` in the result), `--owner <username>` another creator's. Narrow and order any list with `--search <title text>`, `--tag`, `--engine`, `--remixes`, `--sort newest|upvoted|downvoted|played` and `--period 24h|week|month|all`, e.g. to find a game to remix.
|
|
194
|
+
- `npx -y juicyjuicy@0.9.0 publish --draft --json`: uploads a new version without making it live (players keep the current one). Only when the user asks for that in words (e.g. "upload it but don't put it live yet"), never on your own judgment. The result has `draft: true`; when they say so, make it live with `rollback <version>`, or they can on the dashboard.
|
|
195
|
+
- `npx -y juicyjuicy@0.9.0 rollback <version> --slug <slug> --json`: makes an earlier version or a draft live, e.g. when the user says the last update broke the game. Run `list <slug>` first to pick the version with the user; one waiting for an admin's review can't be made live (`FORBIDDEN`).
|
|
196
|
+
- `npx -y juicyjuicy@0.9.0 pull <slug> --json`: downloads the live version of one of the user's own games into a new folder with its `juicy.json`, to change it when the project isn't on this computer (see "Add a JuicyJuicy upgrade").
|
|
197
|
+
- `npx -y juicyjuicy@0.9.0 check-games --json`: checks all the user's games against JuicyJuicy's features and required updates, declares the features it finds where a game never declared them, and lists what still needs a change (see "Bring all my games up to date").
|
|
198
|
+
- `npx -y juicyjuicy@0.9.0 open <slug>`: opens the game page in the browser.
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# Global highscores: `juicy.scores`
|
|
2
|
+
|
|
3
|
+
On JuicyJuicy a game can have a **leaderboard** shared by every player: each signed-in player's best score (or time), kept by JuicyJuicy, shown in the game and on the game's page. The game sends a finished run's score and reads the top of the board with a small snippet. Players who aren't signed in keep their best in the game's own `localStorage` (it doesn't move to their account later). Outside JuicyJuicy (opened on its own, a local server, the automatic screenshot) the snippet uses that local best alone.
|
|
4
|
+
|
|
5
|
+
## Adding it to a published game
|
|
6
|
+
When the user asks for global highscores (the JuicyJuicy Upgrades panel's prompt names this file), follow "Add a JuicyJuicy upgrade" in SKILL.md, and in the game:
|
|
7
|
+
1. **Decide what's ranked**, from the game, and say it in your summary: a score where more is better (`order: "high"`, the default), or a time or count where less is better (`order: "low"`); points (`format: "points"`) or milliseconds (`format: "time"`, shown as `1:23.45`). A game with several modes or levels ranks one thing: the main mode, or a total. If it isn't clear, ask the user in one short question.
|
|
8
|
+
2. Paste the snippet below and use it as "Using it" says: send the score when a run ends, and show the leaderboard on the game-over screen (and in the menu if there's room).
|
|
9
|
+
3. Add `"highscores"` to `features` in `juicy.json`: `"features": ["highscores"]`. JuicyJuicy also finds the snippet's `juicyjuicy:score` in the files; `check` warns when `features` lists highscores and the files don't have it, and its result's `features.detected` includes `highscores` once they do.
|
|
10
|
+
|
|
11
|
+
## The snippet
|
|
12
|
+
Paste it as is, before the game's own code: an inline `<script>` in `index.html`, or the top of the main script. Don't change it; it only posts messages to the page around the frame. It works next to the cloud-saves and multiplayer snippets, in any order (an older copy of the cloud-saves snippet, without `window.juicy.save ||` near its top, must come first).
|
|
13
|
+
|
|
14
|
+
```js
|
|
15
|
+
// JuicyJuicy highscores: https://juicyjuicy.games/docs/publish#highscores
|
|
16
|
+
window.juicy = window.juicy || {};
|
|
17
|
+
window.juicy.scores || (function (juicy) {
|
|
18
|
+
var KEY = "juicyjuicy:best";
|
|
19
|
+
var framed = window.parent !== window;
|
|
20
|
+
var onSite = false;
|
|
21
|
+
var next = 0;
|
|
22
|
+
var waiting = {};
|
|
23
|
+
window.addEventListener("message", function (event) {
|
|
24
|
+
var m = event.data;
|
|
25
|
+
if (event.source !== window.parent || !m || typeof m.id !== "string") return;
|
|
26
|
+
if (m.type !== "juicyjuicy:scored" && m.type !== "juicyjuicy:leaderboard") return;
|
|
27
|
+
var done = waiting[m.id];
|
|
28
|
+
if (!done) return;
|
|
29
|
+
delete waiting[m.id];
|
|
30
|
+
onSite = true;
|
|
31
|
+
done(m);
|
|
32
|
+
});
|
|
33
|
+
// The player's answer, or null when nothing answered (not on JuicyJuicy).
|
|
34
|
+
function ask(message) {
|
|
35
|
+
return new Promise(function (resolve) {
|
|
36
|
+
if (!framed) return resolve(null);
|
|
37
|
+
var id = "scores-" + ++next;
|
|
38
|
+
var timer = setTimeout(function () {
|
|
39
|
+
delete waiting[id];
|
|
40
|
+
resolve(null);
|
|
41
|
+
}, onSite ? 30000 : 10000);
|
|
42
|
+
waiting[id] = function (m) {
|
|
43
|
+
clearTimeout(timer);
|
|
44
|
+
resolve(m);
|
|
45
|
+
};
|
|
46
|
+
message.id = id;
|
|
47
|
+
window.parent.postMessage(message, "*");
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
function failed(m) {
|
|
51
|
+
var error = new Error(m ? m.error.message : "JuicyJuicy didn't answer");
|
|
52
|
+
error.code = m ? m.error.code : "TIMEOUT";
|
|
53
|
+
return error;
|
|
54
|
+
}
|
|
55
|
+
function readLocal() {
|
|
56
|
+
try {
|
|
57
|
+
var best = JSON.parse(localStorage.getItem(KEY));
|
|
58
|
+
return best && typeof best.score === "number" ? best : null;
|
|
59
|
+
} catch (e) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function keepLocal(score, order, format) {
|
|
64
|
+
var best = readLocal();
|
|
65
|
+
var improved = !best || (order === "low" ? score < best.score : score > best.score);
|
|
66
|
+
if (improved) {
|
|
67
|
+
best = { score: score, order: order, format: format };
|
|
68
|
+
try {
|
|
69
|
+
localStorage.setItem(KEY, JSON.stringify(best));
|
|
70
|
+
} catch (e) {}
|
|
71
|
+
}
|
|
72
|
+
return { cloud: false, best: best.score, rank: null, improved: improved };
|
|
73
|
+
}
|
|
74
|
+
juicy.scores = {
|
|
75
|
+
// Sends a finished run's score: a whole number, or milliseconds for a time. Options (pass the
|
|
76
|
+
// same ones every time): order "high" (more is better, the default) or "low", format "points"
|
|
77
|
+
// (the default) or "time". Resolves to { cloud: true, best, rank, improved } or
|
|
78
|
+
// { cloud: false, best, rank: null, improved } (signed out: kept in this browser); rejects with
|
|
79
|
+
// an error that has a code.
|
|
80
|
+
submit: function (score, options) {
|
|
81
|
+
var order = (options && options.order) || "high";
|
|
82
|
+
var format = (options && options.format) || "points";
|
|
83
|
+
var message = { type: "juicyjuicy:score", score: score, order: order, format: format };
|
|
84
|
+
return ask(message).then(function (m) {
|
|
85
|
+
if (!m) {
|
|
86
|
+
if (onSite) throw failed(null);
|
|
87
|
+
return keepLocal(score, order, format);
|
|
88
|
+
}
|
|
89
|
+
if (m.ok) return { cloud: true, best: m.best, rank: m.rank, improved: m.improved };
|
|
90
|
+
if (m.error.code === "SIGNED_OUT") return keepLocal(score, order, format);
|
|
91
|
+
throw failed(m);
|
|
92
|
+
});
|
|
93
|
+
},
|
|
94
|
+
// The leaderboard's top "limit" (10 by default, at most 100): { cloud, signedIn, order,
|
|
95
|
+
// format, entries: [{ rank, name, score, me }], me: { rank, score } or null }.
|
|
96
|
+
top: function (limit) {
|
|
97
|
+
return ask({ type: "juicyjuicy:scores", limit: limit || 10 }).then(function (m) {
|
|
98
|
+
if (!m) {
|
|
99
|
+
if (onSite) throw failed(null);
|
|
100
|
+
var best = readLocal();
|
|
101
|
+
return {
|
|
102
|
+
cloud: false,
|
|
103
|
+
signedIn: false,
|
|
104
|
+
order: best ? best.order : null,
|
|
105
|
+
format: best ? best.format : null,
|
|
106
|
+
entries: best ? [{ rank: 1, name: "You", score: best.score, me: true }] : [],
|
|
107
|
+
me: best ? { rank: 1, score: best.score } : null
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
if (!m.ok) throw failed(m);
|
|
111
|
+
return {
|
|
112
|
+
cloud: true,
|
|
113
|
+
signedIn: m.signedIn,
|
|
114
|
+
order: m.order,
|
|
115
|
+
format: m.format,
|
|
116
|
+
entries: m.entries,
|
|
117
|
+
me: m.me
|
|
118
|
+
};
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
})(window.juicy);
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Using it
|
|
126
|
+
```js
|
|
127
|
+
const BOARD = { order: "high", format: "points" }; // or { order: "low", format: "time" } for times in ms
|
|
128
|
+
|
|
129
|
+
// When a run ends (game over, level cleared, race finished):
|
|
130
|
+
juicy.scores.submit(score, BOARD).then((result) => {
|
|
131
|
+
// result: { cloud, best, rank, improved }; rank is null when the best is only kept in this browser
|
|
132
|
+
if (result.improved) showNewBest(result.best, result.rank);
|
|
133
|
+
}).catch(() => {});
|
|
134
|
+
|
|
135
|
+
// On the game-over screen:
|
|
136
|
+
const board = await juicy.scores.top(10);
|
|
137
|
+
// board: { cloud, signedIn, order, format, entries: [{ rank, name, score, me }], me: { rank, score } or null }
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
- **Send every finished run**, not just new bests, and don't wait for the answer before showing the game-over screen. Only a player's best counts; JuicyJuicy sends at most one score every 5 seconds, keeping the best.
|
|
141
|
+
- **Scores are whole numbers** (`Math.round` first); times are milliseconds and can't be negative. Pass **the same `BOARD` options every time**: the first score sets how the board ranks, and a score sent another way is refused (`INVALID`) until the creator resets the board on the game's page.
|
|
142
|
+
- **Show the board** with each entry's `rank`, `name` (the player's JuicyJuicy username) and `score`, highlight `me`, and when `board.me` isn't among the entries show it under them ("You: #42"). Format times as `m:ss.cc`. Put names on screen with `textContent` or the engine's text object, **never `innerHTML`** (anyone picks their own username). When `board.signedIn` is false, add a line like "Sign in on JuicyJuicy to get on the leaderboard".
|
|
143
|
+
- **An empty board** (`entries` is `[]`) says "No scores yet: be the first!".
|
|
144
|
+
- `submit()` resolves to `{ cloud: true, best, rank, improved }`, or `{ cloud: false, best, rank: null, improved }` when the player isn't signed in (kept in this browser), and rejects with an `Error` whose `code` is `INVALID`, `RATE_LIMITED`, `PAUSED` (highscores paused for a while), `FAILED` or `TIMEOUT`. `top()` rejects with the same codes. The game keeps running either way; just leave the board out.
|
|
145
|
+
- **A game that already keeps a local high score** keeps showing it as "your best" if it likes; the leaderboard is what `top()` returns. Don't send old local high scores to `submit()`: a board is about runs played on JuicyJuicy.
|
|
146
|
+
- **Scores are what the game sends.** Don't add anti-cheat code; JuicyJuicy's creator and admins remove scores that look wrong.
|
|
147
|
+
- **Godot, Unity and other engines** call the same functions from JavaScript: Godot's `JavaScriptBridge.eval`, a Unity `.jslib` plugin. Put the snippet in the exported `index.html` (Godot: the HTML shell's head include; Unity: the WebGL template).
|
|
@@ -16,4 +16,4 @@ JuicyJuicy offers English, French, Italian, German, Spanish and Polish (`en`, `f
|
|
|
16
16
|
Run the game, switch to each language and go through the menus and the first minute of play: no missing keys (an untranslated key shows up as the key's name), no clipped text, and the choice is still there after a reload.
|
|
17
17
|
|
|
18
18
|
## Declare it
|
|
19
|
-
Set `languages` in `juicy.json` to every language the game now has, English first, e.g. `"languages": ["en", "fr", "pl"]` (or `npx -y juicyjuicy@0.
|
|
19
|
+
Set `languages` in `juicy.json` to every language the game now has, English first, e.g. `"languages": ["en", "fr", "pl"]` (or `npx -y juicyjuicy@0.9.0 init --yes --json --languages en,fr,pl`). Don't add it to `features`: JuicyJuicy reads `languages`, and shows the game in each language's filter.
|
|
@@ -6,7 +6,7 @@ With `--json`, a failed command's last line is `{"ok": false, "error": {"code",
|
|
|
6
6
|
|
|
7
7
|
| Code | Meaning | Fix |
|
|
8
8
|
|---|---|---|
|
|
9
|
-
| `UNAUTHORIZED` | No saved login, or the token was revoked. | `npx -y juicyjuicy@0.
|
|
9
|
+
| `UNAUTHORIZED` | No saved login, or the token was revoked. | `npx -y juicyjuicy@0.9.0 login --json` (the user approves in the browser). In CI, set `JUICY_TOKEN`. |
|
|
10
10
|
| `EXPIRED_TOKEN` | The login code expired (10 minutes) or was used. | Run `login` again. |
|
|
11
11
|
| `ACCESS_DENIED` | The user clicked Deny. | Ask them, then run `login` again. |
|
|
12
12
|
|
|
@@ -5,7 +5,7 @@ For "bring my JuicyJuicy games up to date", "fix the required updates", a **Need
|
|
|
5
5
|
1. **Check every game at once.** Log in first (SKILL.md step 1), then run:
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
npx -y juicyjuicy@0.
|
|
8
|
+
npx -y juicyjuicy@0.9.0 check-games --json
|
|
9
9
|
```
|
|
10
10
|
|
|
11
11
|
JuicyJuicy checks the live version of each of the user's games on its side. It records which features each one has and, for a game whose `juicy.json` never listed its `features`, declares what it finds in the files (like cloud saves). Nothing is uploaded and no new version is made. The result has one entry per game in `games` (`features` with `on`/`off`/`unknown`, `requirements` with `passed`), `toFix` (the games that still need a code change, each with its `prompts`), `notReached` and `next`.
|
|
@@ -13,7 +13,7 @@ For "bring my JuicyJuicy games up to date", "fix the required updates", a **Need
|
|
|
13
13
|
2. **If `notReached` isn't empty**, run `check-games` again: it had no time for those games.
|
|
14
14
|
|
|
15
15
|
3. **For each game in `toFix`**, one at a time:
|
|
16
|
-
- Find its project, or download the live version with `npx -y juicyjuicy@0.
|
|
16
|
+
- Find its project, or download the live version with `npx -y juicyjuicy@0.9.0 pull <slug> --json` (SKILL.md, "Add a JuicyJuicy upgrade", step 1).
|
|
17
17
|
- Do what each prompt says, with the recipe it names when it names one.
|
|
18
18
|
- Make sure `juicy.json` has `features` (`[]` if the game has none of the upgrades).
|
|
19
19
|
- Check and publish an update as SKILL.md steps 5–7 say, with `--message "Required update: <what>"`.
|