Compare commits
3 Commits
feat/wasm
...
usage-stat
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7b5258d549 | ||
|
|
f3820e8edc | ||
|
|
d9d2fa1b52 |
82
.github/workflows/main.yml
vendored
82
.github/workflows/main.yml
vendored
@@ -7,29 +7,21 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v2
|
||||||
- uses: oven-sh/setup-bun@v1
|
- uses: actions/setup-node@v2
|
||||||
- uses: dtolnay/rust-toolchain@stable
|
with:
|
||||||
|
cache: yarn
|
||||||
|
node-version: 16
|
||||||
|
- run: yarn install --frozen-lockfile
|
||||||
|
|
||||||
- run: bun install --frozen-lockfile
|
- run: yarn type
|
||||||
|
- run: yarn lint
|
||||||
- run: bun run build
|
- run: yarn test --ci
|
||||||
working-directory: packages/solver-r
|
|
||||||
|
|
||||||
- run: npm run type
|
|
||||||
|
|
||||||
- run: bun test
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
- run: cargo test
|
|
||||||
working-directory: packages/solver-r
|
|
||||||
|
|
||||||
- run: npm run lint
|
|
||||||
|
|
||||||
test-action:
|
test-action:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
- name: update action.yml to use image from local Dockerfile
|
- name: update action.yml to use image from local Dockerfile
|
||||||
run: |
|
run: |
|
||||||
@@ -52,7 +44,7 @@ jobs:
|
|||||||
test -f dist/github-contribution-grid-snake-dark.svg
|
test -f dist/github-contribution-grid-snake-dark.svg
|
||||||
test -f dist/github-contribution-grid-snake.gif
|
test -f dist/github-contribution-grid-snake.gif
|
||||||
|
|
||||||
- uses: crazy-max/ghaction-github-pages@v4.1.0
|
- uses: crazy-max/ghaction-github-pages@v3.1.0
|
||||||
with:
|
with:
|
||||||
target_branch: output
|
target_branch: output
|
||||||
build_dir: dist
|
build_dir: dist
|
||||||
@@ -62,14 +54,16 @@ jobs:
|
|||||||
test-action-svg-only:
|
test-action-svg-only:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v3
|
||||||
- uses: oven-sh/setup-bun@v1
|
- uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
- run: bun install --frozen-lockfile
|
cache: yarn
|
||||||
|
node-version: 16
|
||||||
|
- run: yarn install --frozen-lockfile
|
||||||
|
|
||||||
- name: build svg-only action
|
- name: build svg-only action
|
||||||
run: |
|
run: |
|
||||||
npm run build:action
|
yarn build:action
|
||||||
rm -r svg-only/dist
|
rm -r svg-only/dist
|
||||||
mv packages/action/dist svg-only/dist
|
mv packages/action/dist svg-only/dist
|
||||||
|
|
||||||
@@ -81,16 +75,14 @@ jobs:
|
|||||||
outputs: |
|
outputs: |
|
||||||
dist/github-contribution-grid-snake.svg
|
dist/github-contribution-grid-snake.svg
|
||||||
dist/github-contribution-grid-snake-dark.svg?palette=github-dark
|
dist/github-contribution-grid-snake-dark.svg?palette=github-dark
|
||||||
dist/github-contribution-grid-snake-blue.svg?color_snake=orange&color_dots=#bfd6f6,#8dbdff,#64a1f4,#4b91f1,#3c7dd9
|
|
||||||
|
|
||||||
- name: ensure the generated file exists
|
- name: ensure the generated file exists
|
||||||
run: |
|
run: |
|
||||||
ls dist
|
ls dist
|
||||||
test -f dist/github-contribution-grid-snake.svg
|
test -f dist/github-contribution-grid-snake.svg
|
||||||
test -f dist/github-contribution-grid-snake-dark.svg
|
test -f dist/github-contribution-grid-snake-dark.svg
|
||||||
test -f dist/github-contribution-grid-snake-blue.svg
|
|
||||||
|
|
||||||
- uses: crazy-max/ghaction-github-pages@v4.1.0
|
- uses: crazy-max/ghaction-github-pages@v3.1.0
|
||||||
with:
|
with:
|
||||||
target_branch: output-svg-only
|
target_branch: output-svg-only
|
||||||
build_dir: dist
|
build_dir: dist
|
||||||
@@ -99,28 +91,22 @@ jobs:
|
|||||||
|
|
||||||
deploy-ghpages:
|
deploy-ghpages:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
|
||||||
pages: write
|
|
||||||
id-token: write
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v2
|
||||||
- uses: oven-sh/setup-bun@v1
|
- uses: actions/setup-node@v2
|
||||||
|
|
||||||
- run: bun install --frozen-lockfile
|
|
||||||
|
|
||||||
- run: npm run build:demo
|
|
||||||
env:
|
|
||||||
GITHUB_USER_CONTRIBUTION_API_ENDPOINT: https://github-user-contribution.platane.workers.dev/github-user-contribution/
|
|
||||||
|
|
||||||
- uses: actions/upload-pages-artifact@v3
|
|
||||||
with:
|
with:
|
||||||
path: packages/demo/dist
|
cache: yarn
|
||||||
|
node-version: 16
|
||||||
|
- run: yarn install --frozen-lockfile
|
||||||
|
|
||||||
- uses: actions/deploy-pages@v4
|
- run: yarn build:demo
|
||||||
if: success() && github.ref == 'refs/heads/main'
|
|
||||||
|
|
||||||
- run: bunx wrangler deploy
|
|
||||||
if: success() && github.ref == 'refs/heads/main'
|
|
||||||
working-directory: packages/github-user-contribution-service
|
|
||||||
env:
|
env:
|
||||||
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
GITHUB_USER_CONTRIBUTION_API_ENDPOINT: https://snk-one.vercel.app/api/github-user-contribution/
|
||||||
|
|
||||||
|
- uses: crazy-max/ghaction-github-pages@v2.6.0
|
||||||
|
if: success() && github.ref == 'refs/heads/main'
|
||||||
|
with:
|
||||||
|
target_branch: gh-pages
|
||||||
|
build_dir: packages/demo/dist
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.MY_GITHUB_TOKEN_GH_PAGES }}
|
||||||
|
|||||||
47
.github/workflows/manual-run.yml
vendored
47
.github/workflows/manual-run.yml
vendored
@@ -1,47 +0,0 @@
|
|||||||
name: manual run
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
generate:
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
timeout-minutes: 5
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: Platane/snk/svg-only@v3
|
|
||||||
with:
|
|
||||||
github_user_name: ${{ github.repository_owner }}
|
|
||||||
outputs: |
|
|
||||||
dist/only-svg/github-contribution-grid-snake.svg
|
|
||||||
dist/only-svg/github-contribution-grid-snake-dark.svg?palette=github-dark
|
|
||||||
dist/only-svg/github-contribution-grid-snake-blue.svg?color_snake=orange&color_dots=#bfd6f6,#8dbdff,#64a1f4,#4b91f1,#3c7dd9
|
|
||||||
|
|
||||||
- uses: Platane/snk@v3
|
|
||||||
with:
|
|
||||||
github_user_name: ${{ github.repository_owner }}
|
|
||||||
outputs: |
|
|
||||||
dist/docker/github-contribution-grid-snake.svg
|
|
||||||
dist/docker/github-contribution-grid-snake-dark.svg?palette=github-dark
|
|
||||||
dist/docker/github-contribution-grid-snake.gif?color_snake=orange&color_dots=#bfd6f6,#8dbdff,#64a1f4,#4b91f1,#3c7dd9
|
|
||||||
|
|
||||||
- name: ensure the generated file exists
|
|
||||||
run: |
|
|
||||||
ls dist
|
|
||||||
test -f dist/only-svg/github-contribution-grid-snake.svg
|
|
||||||
test -f dist/only-svg/github-contribution-grid-snake-dark.svg
|
|
||||||
test -f dist/only-svg/github-contribution-grid-snake-blue.svg
|
|
||||||
|
|
||||||
test -f dist/docker/github-contribution-grid-snake.svg
|
|
||||||
test -f dist/docker/github-contribution-grid-snake-dark.svg
|
|
||||||
test -f dist/docker/github-contribution-grid-snake.gif
|
|
||||||
|
|
||||||
- name: push github-contribution-grid-snake.svg to the output branch
|
|
||||||
uses: crazy-max/ghaction-github-pages@v4.1.0
|
|
||||||
with:
|
|
||||||
target_branch: manual-run-output
|
|
||||||
build_dir: dist
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
28
.github/workflows/release.yml
vendored
28
.github/workflows/release.yml
vendored
@@ -21,19 +21,19 @@ jobs:
|
|||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
- uses: docker/setup-qemu-action@v2
|
- uses: docker/setup-qemu-action@v1
|
||||||
|
|
||||||
- uses: docker/setup-buildx-action@v2
|
- uses: docker/setup-buildx-action@v1
|
||||||
|
|
||||||
- uses: docker/login-action@v2
|
- uses: docker/login-action@v1
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
- name: build and publish the docker image
|
- name: build and publish the docker image
|
||||||
uses: docker/build-push-action@v4
|
uses: docker/build-push-action@v2
|
||||||
id: docker-build
|
id: docker-build
|
||||||
with:
|
with:
|
||||||
push: true
|
push: true
|
||||||
@@ -45,18 +45,20 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
sed -i "s/image: .*/image: docker:\/\/platane\/snk@${{ steps.docker-build.outputs.digest }}/" action.yml
|
sed -i "s/image: .*/image: docker:\/\/platane\/snk@${{ steps.docker-build.outputs.digest }}/" action.yml
|
||||||
|
|
||||||
- uses: oven-sh/setup-bun@v1
|
- uses: actions/setup-node@v2
|
||||||
|
with:
|
||||||
- run: bun install --frozen-lockfile
|
cache: yarn
|
||||||
|
node-version: 16
|
||||||
|
|
||||||
- name: build svg-only action
|
- name: build svg-only action
|
||||||
run: |
|
run: |
|
||||||
npm run build:action
|
yarn install --frozen-lockfile
|
||||||
|
yarn build:action
|
||||||
rm -r svg-only/dist
|
rm -r svg-only/dist
|
||||||
mv packages/action/dist svg-only/dist
|
mv packages/action/dist svg-only/dist
|
||||||
|
|
||||||
- name: bump package version
|
- name: bump package version
|
||||||
run: npm version --no-git-tag-version --new-version ${{ github.event.inputs.version }}
|
run: yarn version --no-git-tag-version --new-version ${{ github.event.inputs.version }}
|
||||||
|
|
||||||
- name: push new build, tag version and push
|
- name: push new build, tag version and push
|
||||||
id: push-tags
|
id: push-tags
|
||||||
@@ -75,11 +77,13 @@ jobs:
|
|||||||
git tag v$( echo $VERSION | cut -d. -f 1-2 )
|
git tag v$( echo $VERSION | cut -d. -f 1-2 )
|
||||||
git push origin --tags --force
|
git push origin --tags --force
|
||||||
echo "prerelease=false" >> $GITHUB_OUTPUT
|
echo "prerelease=false" >> $GITHUB_OUTPUT
|
||||||
else
|
else
|
||||||
echo "prerelease=true" >> $GITHUB_OUTPUT
|
echo "prerelease=true" >> $GITHUB_OUTPUT
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- uses: ncipollo/release-action@v1.15.0
|
- uses: ncipollo/release-action@v1.11.1
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
with:
|
with:
|
||||||
tag: v${{ github.event.inputs.version }}
|
tag: v${{ github.event.inputs.version }}
|
||||||
body: ${{ github.event.inputs.description }}
|
body: ${{ github.event.inputs.description }}
|
||||||
|
|||||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -1,9 +1,6 @@
|
|||||||
node_modules
|
node_modules
|
||||||
npm-debug.log*
|
npm-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
dist
|
dist
|
||||||
!svg-only/dist
|
!svg-only/dist
|
||||||
build
|
build
|
||||||
.env
|
|
||||||
.wrangler
|
|
||||||
.dev.vars
|
|
||||||
target
|
|
||||||
19
Dockerfile
19
Dockerfile
@@ -1,27 +1,32 @@
|
|||||||
FROM oven/bun:1.2.2-slim as builder
|
FROM node:16-slim as builder
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
COPY package.json bun.lock ./
|
COPY package.json yarn.lock ./
|
||||||
|
|
||||||
COPY tsconfig.json ./
|
COPY tsconfig.json ./
|
||||||
|
|
||||||
COPY packages packages
|
COPY packages packages
|
||||||
|
|
||||||
RUN bun install --no-cache
|
RUN export YARN_CACHE_FOLDER="$(mktemp -d)" \
|
||||||
|
&& yarn install --frozen-lockfile \
|
||||||
|
&& rm -r "$YARN_CACHE_FOLDER"
|
||||||
|
|
||||||
RUN bun run build:action
|
RUN yarn build:action
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
FROM oven/bun:1.2.2-slim
|
FROM node:16-slim
|
||||||
|
|
||||||
WORKDIR /action-release
|
WORKDIR /action-release
|
||||||
|
|
||||||
RUN bun add canvas@3.1.0 gifsicle@5.3.0 --no-lockfile --no-cache
|
RUN export YARN_CACHE_FOLDER="$(mktemp -d)" \
|
||||||
|
&& yarn add canvas@2.10.2 gifsicle@5.3.0 --no-lockfile \
|
||||||
|
&& rm -r "$YARN_CACHE_FOLDER"
|
||||||
|
|
||||||
COPY --from=builder /app/packages/action/dist/ /action-release/
|
COPY --from=builder /app/packages/action/dist/ /action-release/
|
||||||
|
|
||||||
CMD ["bun", "/action-release/index.js"]
|
CMD ["node", "/action-release/index.js"]
|
||||||
|
|
||||||
|
|||||||
32
README.md
32
README.md
@@ -8,20 +8,7 @@
|
|||||||
|
|
||||||
Generates a snake game from a github user contributions graph
|
Generates a snake game from a github user contributions graph
|
||||||
|
|
||||||
<picture>
|

|
||||||
<source
|
|
||||||
media="(prefers-color-scheme: dark)"
|
|
||||||
srcset="https://raw.githubusercontent.com/platane/snk/output/github-contribution-grid-snake-dark.svg"
|
|
||||||
/>
|
|
||||||
<source
|
|
||||||
media="(prefers-color-scheme: light)"
|
|
||||||
srcset="https://raw.githubusercontent.com/platane/snk/output/github-contribution-grid-snake.svg"
|
|
||||||
/>
|
|
||||||
<img
|
|
||||||
alt="github contribution grid snake animation"
|
|
||||||
src="https://raw.githubusercontent.com/platane/snk/output/github-contribution-grid-snake.svg"
|
|
||||||
/>
|
|
||||||
</picture>
|
|
||||||
|
|
||||||
Pull a github user's contribution graph.
|
Pull a github user's contribution graph.
|
||||||
Make it a snake Game, generate a snake path where the cells get eaten in an orderly fashion.
|
Make it a snake Game, generate a snake path where the cells get eaten in an orderly fashion.
|
||||||
@@ -35,7 +22,7 @@ Available as github action. It can automatically generate a new image each day.
|
|||||||
**github action**
|
**github action**
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
- uses: Platane/snk@v3
|
- uses: Platane/snk@v2
|
||||||
with:
|
with:
|
||||||
# github user name to read the contribution graph from (**required**)
|
# github user name to read the contribution graph from (**required**)
|
||||||
# using action context var `github.repository_owner` or specified user
|
# using action context var `github.repository_owner` or specified user
|
||||||
@@ -56,20 +43,17 @@ Available as github action. It can automatically generate a new image each day.
|
|||||||
dist/ocean.gif?color_snake=orange&color_dots=#bfd6f6,#8dbdff,#64a1f4,#4b91f1,#3c7dd9
|
dist/ocean.gif?color_snake=orange&color_dots=#bfd6f6,#8dbdff,#64a1f4,#4b91f1,#3c7dd9
|
||||||
```
|
```
|
||||||
|
|
||||||
[example with cron job](https://github.com/Platane/Platane/blob/master/.github/workflows/main.yml#L26-L33)
|
[example with cron job](https://github.com/Platane/Platane/blob/master/.github/workflows/main.yml#L24-L29)
|
||||||
|
|
||||||
If you are only interested in generating a svg, consider using this faster action: `uses: Platane/snk/svg-only@v3`
|
If you are only interested in generating a svg, consider using this faster action: `uses: Platane/snk/svg-only@v2`
|
||||||
|
|
||||||
**dark mode**
|
**dark mode**
|
||||||
|
|
||||||
For **dark mode** support on github, use this [special syntax](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#specifying-the-theme-an-image-is-shown-to) in your readme.
|
For **dark mode** support on github, use this [special syntax](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#specifying-the-theme-an-image-is-shown-to=) in your readme.
|
||||||
|
|
||||||
```html
|
```md
|
||||||
<picture>
|

|
||||||
<source media="(prefers-color-scheme: dark)" srcset="github-snake-dark.svg" />
|

|
||||||
<source media="(prefers-color-scheme: light)" srcset="github-snake.svg" />
|
|
||||||
<img alt="github-snake" src="github-snake.svg" />
|
|
||||||
</picture>
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**interactive demo**
|
**interactive demo**
|
||||||
|
|||||||
@@ -4,18 +4,15 @@ author: "platane"
|
|||||||
|
|
||||||
runs:
|
runs:
|
||||||
using: docker
|
using: docker
|
||||||
image: docker://platane/snk@sha256:96390294299275740e5963058c9784c60c5393b3b8b16082dcf41b240db791f9
|
image: docker://platane/snk@sha256:dcb351bdad223f2a2161fa5d6e3c9102e6ebe9fbde99a10fa3bf443d69f61a0f
|
||||||
|
|
||||||
inputs:
|
inputs:
|
||||||
github_user_name:
|
github_user_name:
|
||||||
description: "github user name"
|
description: "github user name"
|
||||||
required: true
|
required: true
|
||||||
github_token:
|
|
||||||
description: "github token used to fetch the contribution calendar. Default to the action token if empty."
|
|
||||||
required: false
|
|
||||||
default: ${{ github.token }}
|
|
||||||
outputs:
|
outputs:
|
||||||
required: false
|
required: false
|
||||||
|
default: null
|
||||||
description: |
|
description: |
|
||||||
list of files to generate.
|
list of files to generate.
|
||||||
one file per line. Each output can be customized with options as query string.
|
one file per line. Each output can be customized with options as query string.
|
||||||
|
|||||||
37
package.json
37
package.json
@@ -1,25 +1,36 @@
|
|||||||
{
|
{
|
||||||
"name": "snk",
|
"name": "snk",
|
||||||
"description": "Generates a snake game from a github user contributions grid",
|
"description": "Generates a snake game from a github user contributions grid",
|
||||||
"version": "3.3.0",
|
"version": "2.2.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"repository": "github:platane/snk",
|
"repository": "github:platane/snk",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/bun": "1.2.2",
|
"@sucrase/jest-plugin": "3.0.0",
|
||||||
"prettier": "3.5.1",
|
"@types/jest": "29.2.1",
|
||||||
"typescript": "5.7.3"
|
"@types/node": "16.11.7",
|
||||||
|
"jest": "29.2.2",
|
||||||
|
"prettier": "2.7.1",
|
||||||
|
"sucrase": "3.28.0",
|
||||||
|
"typescript": "4.8.4"
|
||||||
},
|
},
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"packages/*"
|
"packages/**"
|
||||||
],
|
],
|
||||||
|
"jest": {
|
||||||
|
"testEnvironment": "node",
|
||||||
|
"testMatch": [
|
||||||
|
"**/__tests__/**/?(*.)+(spec|test).ts"
|
||||||
|
],
|
||||||
|
"transform": {
|
||||||
|
"\\.(ts|tsx)$": "@sucrase/jest-plugin"
|
||||||
|
}
|
||||||
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"type": "tsc --noEmit",
|
"type": "tsc --noEmit",
|
||||||
"lint": "prettier -c '**/*.{ts,js,json,md,yml,yaml}' '!packages/*/dist/**' '!svg-only/dist/**'",
|
"lint": "yarn prettier -c '**/*.{ts,js,json,md,yml,yaml}' '!packages/*/dist/**' '!svg-only/dist/**'",
|
||||||
"dev:demo": "( cd packages/demo ; npm run dev )",
|
"test": "jest --verbose --passWithNoTests --no-cache",
|
||||||
"build:demo": "( cd packages/demo ; npm run build )",
|
"dev:demo": "( cd packages/demo ; yarn dev )",
|
||||||
"build:action": "( cd packages/action ; npm run build )"
|
"build:demo": "( cd packages/demo ; yarn build )",
|
||||||
},
|
"build:action": "( cd packages/action ; yarn build )"
|
||||||
"trustedDependencies": [
|
}
|
||||||
"wasm-pack"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,81 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`should parse /out.svg {"color_snake":"yellow"} 1`] = `
|
||||||
|
{
|
||||||
|
"animationOptions": {
|
||||||
|
"frameDuration": 100,
|
||||||
|
"step": 1,
|
||||||
|
},
|
||||||
|
"drawOptions": {
|
||||||
|
"colorDotBorder": "#1b1f230a",
|
||||||
|
"colorDots": [
|
||||||
|
"#ebedf0",
|
||||||
|
"#9be9a8",
|
||||||
|
"#40c463",
|
||||||
|
"#30a14e",
|
||||||
|
"#216e39",
|
||||||
|
],
|
||||||
|
"colorEmpty": "#ebedf0",
|
||||||
|
"colorSnake": "yellow",
|
||||||
|
"dark": {
|
||||||
|
"colorDotBorder": "#1b1f230a",
|
||||||
|
"colorDots": [
|
||||||
|
"#161b22",
|
||||||
|
"#01311f",
|
||||||
|
"#034525",
|
||||||
|
"#0f6d31",
|
||||||
|
"#00c647",
|
||||||
|
],
|
||||||
|
"colorEmpty": "#161b22",
|
||||||
|
"colorSnake": "purple",
|
||||||
|
},
|
||||||
|
"sizeCell": 16,
|
||||||
|
"sizeDot": 12,
|
||||||
|
"sizeDotBorderRadius": 2,
|
||||||
|
},
|
||||||
|
"filename": "/out.svg",
|
||||||
|
"format": "svg",
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`should parse /out.svg?.gif.svg?color_snake=orange 1`] = `
|
||||||
|
{
|
||||||
|
"animationOptions": {
|
||||||
|
"frameDuration": 100,
|
||||||
|
"step": 1,
|
||||||
|
},
|
||||||
|
"drawOptions": {
|
||||||
|
"colorDotBorder": "#1b1f230a",
|
||||||
|
"colorDots": [
|
||||||
|
"#ebedf0",
|
||||||
|
"#9be9a8",
|
||||||
|
"#40c463",
|
||||||
|
"#30a14e",
|
||||||
|
"#216e39",
|
||||||
|
],
|
||||||
|
"colorEmpty": "#ebedf0",
|
||||||
|
"colorSnake": "orange",
|
||||||
|
"dark": {
|
||||||
|
"colorDotBorder": "#1b1f230a",
|
||||||
|
"colorDots": [
|
||||||
|
"#161b22",
|
||||||
|
"#01311f",
|
||||||
|
"#034525",
|
||||||
|
"#0f6d31",
|
||||||
|
"#00c647",
|
||||||
|
],
|
||||||
|
"colorEmpty": "#161b22",
|
||||||
|
"colorSnake": "purple",
|
||||||
|
},
|
||||||
|
"sizeCell": 16,
|
||||||
|
"sizeDot": 12,
|
||||||
|
"sizeDotBorderRadius": 2,
|
||||||
|
},
|
||||||
|
"filename": "/out.svg?.gif.svg",
|
||||||
|
"format": "svg",
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`should parse /out.svg?{"color_snake":"yellow","color_dots":["#000","#111","#222","#333","#444"]} 1`] = `
|
exports[`should parse /out.svg?{"color_snake":"yellow","color_dots":["#000","#111","#222","#333","#444"]} 1`] = `
|
||||||
{
|
{
|
||||||
"animationOptions": {
|
"animationOptions": {
|
||||||
@@ -72,7 +148,6 @@ exports[`should parse /out.svg?color_snake=orange&color_dots=#000,#111,#222,#333
|
|||||||
"colorEmpty": "#000",
|
"colorEmpty": "#000",
|
||||||
"colorSnake": "orange",
|
"colorSnake": "orange",
|
||||||
"dark": {
|
"dark": {
|
||||||
"colorDotBorder": "#1b1f230a",
|
|
||||||
"colorDots": [
|
"colorDots": [
|
||||||
"#a00",
|
"#a00",
|
||||||
"#a11",
|
"#a11",
|
||||||
@@ -81,7 +156,6 @@ exports[`should parse /out.svg?color_snake=orange&color_dots=#000,#111,#222,#333
|
|||||||
"#a44",
|
"#a44",
|
||||||
],
|
],
|
||||||
"colorEmpty": "#a00",
|
"colorEmpty": "#a00",
|
||||||
"colorSnake": "orange",
|
|
||||||
},
|
},
|
||||||
"sizeCell": 16,
|
"sizeCell": 16,
|
||||||
"sizeDot": 12,
|
"sizeDot": 12,
|
||||||
@@ -109,7 +183,18 @@ exports[`should parse path/to/out.gif 1`] = `
|
|||||||
],
|
],
|
||||||
"colorEmpty": "#ebedf0",
|
"colorEmpty": "#ebedf0",
|
||||||
"colorSnake": "purple",
|
"colorSnake": "purple",
|
||||||
"dark": undefined,
|
"dark": {
|
||||||
|
"colorDotBorder": "#1b1f230a",
|
||||||
|
"colorDots": [
|
||||||
|
"#161b22",
|
||||||
|
"#01311f",
|
||||||
|
"#034525",
|
||||||
|
"#0f6d31",
|
||||||
|
"#00c647",
|
||||||
|
],
|
||||||
|
"colorEmpty": "#161b22",
|
||||||
|
"colorSnake": "purple",
|
||||||
|
},
|
||||||
"sizeCell": 16,
|
"sizeCell": 16,
|
||||||
"sizeDot": 12,
|
"sizeDot": 12,
|
||||||
"sizeDotBorderRadius": 2,
|
"sizeDotBorderRadius": 2,
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import * as fs from "fs";
|
import * as fs from "fs";
|
||||||
import * as path from "path";
|
import * as path from "path";
|
||||||
import { it, expect } from "bun:test";
|
|
||||||
import { generateContributionSnake } from "../generateContributionSnake";
|
import { generateContributionSnake } from "../generateContributionSnake";
|
||||||
import { parseOutputsOption } from "../outputsOptions";
|
import { parseOutputsOption } from "../outputsOptions";
|
||||||
|
|
||||||
|
jest.setTimeout(2 * 60 * 1000);
|
||||||
|
|
||||||
const silent = (handler: () => void | Promise<void>) => async () => {
|
const silent = (handler: () => void | Promise<void>) => async () => {
|
||||||
const originalConsoleLog = console.log;
|
const originalConsoleLog = console.log;
|
||||||
console.log = () => undefined;
|
console.log = () => undefined;
|
||||||
@@ -29,9 +30,7 @@ it(
|
|||||||
|
|
||||||
const outputs = parseOutputsOption(entries);
|
const outputs = parseOutputsOption(entries);
|
||||||
|
|
||||||
const results = await generateContributionSnake("platane", outputs, {
|
const results = await generateContributionSnake("platane", outputs);
|
||||||
githubToken: process.env.GITHUB_TOKEN!,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(results[0]).toBeDefined();
|
expect(results[0]).toBeDefined();
|
||||||
expect(results[1]).toBeDefined();
|
expect(results[1]).toBeDefined();
|
||||||
@@ -40,6 +39,5 @@ it(
|
|||||||
fs.writeFileSync(outputs[0]!.filename, results[0]!);
|
fs.writeFileSync(outputs[0]!.filename, results[0]!);
|
||||||
fs.writeFileSync(outputs[1]!.filename, results[1]!);
|
fs.writeFileSync(outputs[1]!.filename, results[1]!);
|
||||||
fs.writeFileSync(outputs[2]!.filename, results[2]!);
|
fs.writeFileSync(outputs[2]!.filename, results[2]!);
|
||||||
}),
|
})
|
||||||
{ timeout: 2 * 60 * 1000 },
|
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,61 +1,19 @@
|
|||||||
import { parseEntry } from "../outputsOptions";
|
import { parseEntry } from "../outputsOptions";
|
||||||
import { it, expect } from "bun:test";
|
|
||||||
|
|
||||||
it("should parse options as json", () => {
|
|
||||||
expect(
|
|
||||||
parseEntry(`/out.svg {"color_snake":"yellow"}`)?.drawOptions,
|
|
||||||
).toHaveProperty("colorSnake", "yellow");
|
|
||||||
|
|
||||||
expect(
|
|
||||||
parseEntry(`/out.svg?{"color_snake":"yellow"}`)?.drawOptions,
|
|
||||||
).toHaveProperty("colorSnake", "yellow");
|
|
||||||
|
|
||||||
expect(
|
|
||||||
parseEntry(`/out.svg?{"color_dots":["#000","#111","#222","#333","#444"]}`)
|
|
||||||
?.drawOptions.colorDots,
|
|
||||||
).toEqual(["#000", "#111", "#222", "#333", "#444"]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should parse options as searchparams", () => {
|
|
||||||
expect(parseEntry(`/out.svg?color_snake=yellow`)?.drawOptions).toHaveProperty(
|
|
||||||
"colorSnake",
|
|
||||||
"yellow",
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(
|
|
||||||
parseEntry(`/out.svg?color_dots=#000,#111,#222,#333,#444`)?.drawOptions
|
|
||||||
.colorDots,
|
|
||||||
).toEqual(["#000", "#111", "#222", "#333", "#444"]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should parse filename", () => {
|
|
||||||
expect(parseEntry(`/a/b/c.svg?{"color_snake":"yellow"}`)).toHaveProperty(
|
|
||||||
"filename",
|
|
||||||
"/a/b/c.svg",
|
|
||||||
);
|
|
||||||
expect(
|
|
||||||
parseEntry(`/a/b/out.svg?.gif.svg?{"color_snake":"yellow"}`),
|
|
||||||
).toHaveProperty("filename", "/a/b/out.svg?.gif.svg");
|
|
||||||
|
|
||||||
expect(
|
|
||||||
parseEntry(`/a/b/{[-1].svg?.gif.svg?{"color_snake":"yellow"}`),
|
|
||||||
).toHaveProperty("filename", "/a/b/{[-1].svg?.gif.svg");
|
|
||||||
});
|
|
||||||
|
|
||||||
[
|
[
|
||||||
// default
|
|
||||||
"path/to/out.gif",
|
"path/to/out.gif",
|
||||||
|
|
||||||
// overwrite colors (search params)
|
|
||||||
"/out.svg?color_snake=orange&color_dots=#000,#111,#222,#333,#444",
|
"/out.svg?color_snake=orange&color_dots=#000,#111,#222,#333,#444",
|
||||||
|
|
||||||
// overwrite colors (json)
|
|
||||||
`/out.svg?{"color_snake":"yellow","color_dots":["#000","#111","#222","#333","#444"]}`,
|
`/out.svg?{"color_snake":"yellow","color_dots":["#000","#111","#222","#333","#444"]}`,
|
||||||
|
|
||||||
// overwrite dark colors
|
`/out.svg {"color_snake":"yellow"}`,
|
||||||
|
|
||||||
"/out.svg?color_snake=orange&color_dots=#000,#111,#222,#333,#444&dark_color_dots=#a00,#a11,#a22,#a33,#a44",
|
"/out.svg?color_snake=orange&color_dots=#000,#111,#222,#333,#444&dark_color_dots=#a00,#a11,#a22,#a33,#a44",
|
||||||
|
|
||||||
|
"/out.svg?.gif.svg?color_snake=orange",
|
||||||
].forEach((entry) =>
|
].forEach((entry) =>
|
||||||
it(`should parse ${entry}`, () => {
|
it(`should parse ${entry}`, () => {
|
||||||
expect(parseEntry(entry)).toMatchSnapshot();
|
expect(parseEntry(entry)).toMatchSnapshot();
|
||||||
}),
|
})
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -12,11 +12,10 @@ export const generateContributionSnake = async (
|
|||||||
format: "svg" | "gif";
|
format: "svg" | "gif";
|
||||||
drawOptions: DrawOptions;
|
drawOptions: DrawOptions;
|
||||||
animationOptions: AnimationOptions;
|
animationOptions: AnimationOptions;
|
||||||
} | null)[],
|
} | null)[]
|
||||||
options: { githubToken: string },
|
|
||||||
) => {
|
) => {
|
||||||
console.log("🎣 fetching github user contribution");
|
console.log("🎣 fetching github user contribution");
|
||||||
const cells = await getGithubUserContribution(userName, options);
|
const cells = await getGithubUserContribution(userName);
|
||||||
|
|
||||||
const grid = userContributionToGrid(cells);
|
const grid = userContributionToGrid(cells);
|
||||||
const snake = snake4;
|
const snake = snake4;
|
||||||
@@ -43,10 +42,10 @@ export const generateContributionSnake = async (
|
|||||||
cells,
|
cells,
|
||||||
chain,
|
chain,
|
||||||
drawOptions,
|
drawOptions,
|
||||||
animationOptions,
|
animationOptions
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}),
|
})
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -10,17 +10,13 @@ import { parseOutputsOption } from "./outputsOptions";
|
|||||||
core.getMultilineInput("outputs") ?? [
|
core.getMultilineInput("outputs") ?? [
|
||||||
core.getInput("gif_out_path"),
|
core.getInput("gif_out_path"),
|
||||||
core.getInput("svg_out_path"),
|
core.getInput("svg_out_path"),
|
||||||
],
|
]
|
||||||
);
|
);
|
||||||
const githubToken =
|
|
||||||
process.env.GITHUB_TOKEN ?? core.getInput("github_token");
|
|
||||||
|
|
||||||
const { generateContributionSnake } = await import(
|
const { generateContributionSnake } = await import(
|
||||||
"./generateContributionSnake"
|
"./generateContributionSnake"
|
||||||
);
|
);
|
||||||
const results = await generateContributionSnake(userName, outputs, {
|
const results = await generateContributionSnake(userName, outputs);
|
||||||
githubToken,
|
|
||||||
});
|
|
||||||
|
|
||||||
outputs.forEach((out, i) => {
|
outputs.forEach((out, i) => {
|
||||||
const result = results[i];
|
const result = results[i];
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ export const parseEntry = (entry: string) => {
|
|||||||
sizeCell: 16,
|
sizeCell: 16,
|
||||||
sizeDot: 12,
|
sizeDot: 12,
|
||||||
...palettes["default"],
|
...palettes["default"],
|
||||||
dark: palettes["default"].dark && { ...palettes["default"].dark },
|
|
||||||
};
|
};
|
||||||
const animationOptions: AnimationOptions = { step: 1, frameDuration: 100 };
|
const animationOptions: AnimationOptions = { step: 1, frameDuration: 100 };
|
||||||
|
|
||||||
@@ -44,14 +43,6 @@ export const parseEntry = (entry: string) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
|
||||||
const dark_palette = palettes[sp.get("dark_palette")!];
|
|
||||||
if (dark_palette) {
|
|
||||||
const clone = { ...dark_palette, dark: undefined };
|
|
||||||
drawOptions.dark = clone;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sp.has("color_snake")) drawOptions.colorSnake = sp.get("color_snake")!;
|
if (sp.has("color_snake")) drawOptions.colorSnake = sp.get("color_snake")!;
|
||||||
if (sp.has("color_dots")) {
|
if (sp.has("color_dots")) {
|
||||||
const colors = sp.get("color_dots")!.split(/[,;]/);
|
const colors = sp.get("color_dots")!.split(/[,;]/);
|
||||||
@@ -65,8 +56,6 @@ export const parseEntry = (entry: string) => {
|
|||||||
if (sp.has("dark_color_dots")) {
|
if (sp.has("dark_color_dots")) {
|
||||||
const colors = sp.get("dark_color_dots")!.split(/[,;]/);
|
const colors = sp.get("dark_color_dots")!.split(/[,;]/);
|
||||||
drawOptions.dark = {
|
drawOptions.dark = {
|
||||||
colorDotBorder: drawOptions.colorDotBorder,
|
|
||||||
colorSnake: drawOptions.colorSnake,
|
|
||||||
...drawOptions.dark,
|
...drawOptions.dark,
|
||||||
colorDots: colors,
|
colorDots: colors,
|
||||||
colorEmpty: colors[0],
|
colorEmpty: colors[0],
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"name": "@snk/action",
|
"name": "@snk/action",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/core": "1.11.1",
|
"@actions/core": "1.10.0",
|
||||||
"@snk/gif-creator": "1.0.0",
|
"@snk/gif-creator": "1.0.0",
|
||||||
"@snk/github-user-contribution": "1.0.0",
|
"@snk/github-user-contribution": "1.0.0",
|
||||||
"@snk/solver": "1.0.0",
|
"@snk/solver": "1.0.0",
|
||||||
@@ -10,9 +10,10 @@
|
|||||||
"@snk/types": "1.0.0"
|
"@snk/types": "1.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vercel/ncc": "0.38.3"
|
"@vercel/ncc": "0.34.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "ncc build --external canvas --external gifsicle --out dist ./index.ts"
|
"build": "ncc build --external canvas --external gifsicle --out dist ./index.ts",
|
||||||
|
"run:build": "INPUT_GITHUB_USER_NAME=platane INPUT_OUTPUTS='dist/out.svg' node dist/index.js"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,5 +23,8 @@ export const basePalettes: Record<
|
|||||||
|
|
||||||
// aliases
|
// aliases
|
||||||
export const palettes = { ...basePalettes };
|
export const palettes = { ...basePalettes };
|
||||||
palettes["github"] = palettes["github-light"];
|
palettes["github"] = {
|
||||||
|
...palettes["github-light"],
|
||||||
|
dark: { ...palettes["github-dark"] },
|
||||||
|
};
|
||||||
palettes["default"] = palettes["github"];
|
palettes["default"] = palettes["github"];
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ export const createCanvas = ({
|
|||||||
snake0: Snake,
|
snake0: Snake,
|
||||||
snake1: Snake,
|
snake1: Snake,
|
||||||
stack: Color[],
|
stack: Color[],
|
||||||
k: number,
|
k: number
|
||||||
) => {
|
) => {
|
||||||
ctx.clearRect(0, 0, 9999, 9999);
|
ctx.clearRect(0, 0, 9999, 9999);
|
||||||
drawLerpWorld(ctx, grid, null, snake0, snake1, stack, k, drawOptions);
|
drawLerpWorld(ctx, grid, null, snake0, snake1, stack, k, drawOptions);
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ const tunnels = ones.map(({ x, y }) => ({
|
|||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
3 as Color,
|
3 as Color,
|
||||||
getSnakeLength(snake),
|
getSnakeLength(snake)
|
||||||
),
|
),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ const { canvas, ctx, draw, highlightCell } = createCanvas(grid);
|
|||||||
canvas.style.pointerEvents = "auto";
|
canvas.style.pointerEvents = "auto";
|
||||||
|
|
||||||
const target = createSnakeFromCells(
|
const target = createSnakeFromCells(
|
||||||
snakeToCells(snake).map((p) => ({ ...p, x: p.x - 1 })),
|
snakeToCells(snake).map((p) => ({ ...p, x: p.x - 1 }))
|
||||||
);
|
);
|
||||||
|
|
||||||
let chain = [snake, ...getPathToPose(snake, target)!];
|
let chain = [snake, ...getPathToPose(snake, target)!];
|
||||||
|
|||||||
@@ -253,7 +253,7 @@ const createViewer = ({
|
|||||||
: "") +
|
: "") +
|
||||||
`<a href="${svgLink.href}" download="github-user-contribution.svg">` +
|
`<a href="${svgLink.href}" download="github-user-contribution.svg">` +
|
||||||
svgString +
|
svgString +
|
||||||
"<a/>",
|
"<a/>"
|
||||||
);
|
);
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
});
|
});
|
||||||
@@ -277,7 +277,7 @@ const createViewer = ({
|
|||||||
|
|
||||||
const onSubmit = async (userName: string) => {
|
const onSubmit = async (userName: string) => {
|
||||||
const res = await fetch(
|
const res = await fetch(
|
||||||
process.env.GITHUB_USER_CONTRIBUTION_API_ENDPOINT + userName,
|
process.env.GITHUB_USER_CONTRIBUTION_API_ENDPOINT + userName
|
||||||
);
|
);
|
||||||
const cells = (await res.json()) as Res;
|
const cells = (await res.json()) as Res;
|
||||||
|
|
||||||
@@ -294,8 +294,8 @@ const worker = new Worker(
|
|||||||
new URL(
|
new URL(
|
||||||
"./demo.interactive.worker.ts",
|
"./demo.interactive.worker.ts",
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import.meta.url,
|
import.meta.url
|
||||||
),
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
const { getChain } = createRpcClient<WorkerAPI>(worker);
|
const { getChain } = createRpcClient<WorkerAPI>(worker);
|
||||||
|
|||||||
@@ -5,6 +5,5 @@
|
|||||||
"outside",
|
"outside",
|
||||||
"getPathToPose",
|
"getPathToPose",
|
||||||
"getPathTo",
|
"getPathTo",
|
||||||
"svg",
|
"svg"
|
||||||
"rust"
|
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
import { createCanvas } from "./canvas";
|
|
||||||
import "./menu";
|
|
||||||
import { grid } from "./sample";
|
|
||||||
|
|
||||||
(async () => {
|
|
||||||
const api = await import("@snk/solver-r");
|
|
||||||
|
|
||||||
const g = api.IGrid.create(grid.width, grid.height, grid.data);
|
|
||||||
|
|
||||||
const freeCells = api.iget_free_cell(g);
|
|
||||||
|
|
||||||
{
|
|
||||||
const { canvas, draw, highlightCell } = createCanvas(g);
|
|
||||||
document.body.appendChild(canvas);
|
|
||||||
|
|
||||||
draw({ width: g.width, height: g.height, data: g.data }, [] as any, []);
|
|
||||||
|
|
||||||
for (let i = freeCells.length / 2; i--; ) {
|
|
||||||
const x = freeCells[i * 2 + 0];
|
|
||||||
const y = freeCells[i * 2 + 1];
|
|
||||||
highlightCell(x, y);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
@@ -25,7 +25,7 @@ const onChange = () => {
|
|||||||
|
|
||||||
const url = new URL(
|
const url = new URL(
|
||||||
config.demo + ".html?" + search,
|
config.demo + ".html?" + search,
|
||||||
window.location.href,
|
window.location.href
|
||||||
).toString();
|
).toString();
|
||||||
|
|
||||||
window.location.href = url;
|
window.location.href = url;
|
||||||
|
|||||||
@@ -10,15 +10,14 @@
|
|||||||
"@snk/types": "1.0.0"
|
"@snk/types": "1.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/dat.gui": "0.7.13",
|
"@types/dat.gui": "0.7.7",
|
||||||
"dat.gui": "0.7.9",
|
"dat.gui": "0.7.9",
|
||||||
"dotenv": "16.4.7",
|
"html-webpack-plugin": "5.5.0",
|
||||||
"html-webpack-plugin": "5.6.3",
|
"ts-loader": "9.4.1",
|
||||||
"ts-loader": "9.5.2",
|
"ts-node": "10.9.1",
|
||||||
"ts-node": "10.9.2",
|
"webpack": "5.74.0",
|
||||||
"webpack": "5.98.0",
|
"webpack-cli": "4.10.0",
|
||||||
"webpack-cli": "6.0.1",
|
"webpack-dev-server": "4.11.1"
|
||||||
"webpack-dev-server": "5.2.0"
|
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "webpack",
|
"build": "webpack",
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ const stepSpringOne = (
|
|||||||
maxVelocity = Infinity,
|
maxVelocity = Infinity,
|
||||||
}: { tension: number; friction: number; maxVelocity?: number },
|
}: { tension: number; friction: number; maxVelocity?: number },
|
||||||
target: number,
|
target: number,
|
||||||
dt = 1 / 60,
|
dt = 1 / 60
|
||||||
) => {
|
) => {
|
||||||
const a = -tension * (s.x - target) - friction * s.v;
|
const a = -tension * (s.x - target) - friction * s.v;
|
||||||
|
|
||||||
@@ -31,13 +31,13 @@ const stepSpringOne = (
|
|||||||
export const isStable = (
|
export const isStable = (
|
||||||
s: { x: number; v: number },
|
s: { x: number; v: number },
|
||||||
target: number,
|
target: number,
|
||||||
dt = 1 / 60,
|
dt = 1 / 60
|
||||||
) => Math.abs(s.x - target) < epsilon && Math.abs(s.v * dt) < epsilon;
|
) => Math.abs(s.x - target) < epsilon && Math.abs(s.v * dt) < epsilon;
|
||||||
|
|
||||||
export const isStableAndBound = (
|
export const isStableAndBound = (
|
||||||
s: { x: number; v: number },
|
s: { x: number; v: number },
|
||||||
target: number,
|
target: number,
|
||||||
dt?: number,
|
dt?: number
|
||||||
) => {
|
) => {
|
||||||
const stable = isStable(s, target, dt);
|
const stable = isStable(s, target, dt);
|
||||||
if (stable) {
|
if (stable) {
|
||||||
@@ -51,7 +51,7 @@ export const stepSpring = (
|
|||||||
s: { x: number; v: number },
|
s: { x: number; v: number },
|
||||||
params: { tension: number; friction: number; maxVelocity?: number },
|
params: { tension: number; friction: number; maxVelocity?: number },
|
||||||
target: number,
|
target: number,
|
||||||
dt = 1 / 60,
|
dt = 1 / 60
|
||||||
) => {
|
) => {
|
||||||
const interval = 1 / 60;
|
const interval = 1 / 60;
|
||||||
|
|
||||||
|
|||||||
@@ -1,40 +1,27 @@
|
|||||||
import path from "path";
|
import path from "path";
|
||||||
import HtmlWebpackPlugin from "html-webpack-plugin";
|
import HtmlWebpackPlugin from "html-webpack-plugin";
|
||||||
|
|
||||||
|
import type { Configuration as WebpackConfiguration } from "webpack";
|
||||||
|
import type { Configuration as WebpackDevServerConfiguration } from "webpack-dev-server";
|
||||||
import webpack from "webpack";
|
import webpack from "webpack";
|
||||||
import { getGithubUserContribution } from "@snk/github-user-contribution";
|
import { getGithubUserContribution } from "@snk/github-user-contribution";
|
||||||
import type { Configuration as WebpackConfiguration } from "webpack";
|
|
||||||
import {
|
|
||||||
ExpressRequestHandler,
|
|
||||||
type Configuration as WebpackDevServerConfiguration,
|
|
||||||
} from "webpack-dev-server";
|
|
||||||
import { config } from "dotenv";
|
|
||||||
config({ path: __dirname + "/../../.env" });
|
|
||||||
|
|
||||||
const demos: string[] = require("./demo.json");
|
const demos: string[] = require("./demo.json");
|
||||||
|
|
||||||
const webpackDevServerConfiguration: WebpackDevServerConfiguration = {
|
const webpackDevServerConfiguration: WebpackDevServerConfiguration = {
|
||||||
open: { target: demos[1] + ".html" },
|
open: { target: demos[1] + ".html" },
|
||||||
setupMiddlewares: (ms) => [
|
onAfterSetupMiddleware: ({ app }) => {
|
||||||
...ms,
|
app!.get("/api/github-user-contribution/:userName", async (req, res) => {
|
||||||
(async (req, res, next) => {
|
const userName: string = req.params.userName;
|
||||||
const userName = req.url.match(
|
res.send(await getGithubUserContribution(userName));
|
||||||
/\/api\/github-user-contribution\/(\w+)/,
|
});
|
||||||
)?.[1];
|
},
|
||||||
if (userName)
|
|
||||||
res.send(
|
|
||||||
await getGithubUserContribution(userName, {
|
|
||||||
githubToken: process.env.GITHUB_TOKEN!,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
else next();
|
|
||||||
}) as ExpressRequestHandler,
|
|
||||||
],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const webpackConfiguration: WebpackConfiguration = {
|
const webpackConfiguration: WebpackConfiguration = {
|
||||||
mode: "development",
|
mode: "development",
|
||||||
entry: Object.fromEntries(
|
entry: Object.fromEntries(
|
||||||
demos.map((demo: string) => [demo, `./demo.${demo}`]),
|
demos.map((demo: string) => [demo, `./demo.${demo}`])
|
||||||
),
|
),
|
||||||
target: ["web", "es2019"],
|
target: ["web", "es2019"],
|
||||||
resolve: { extensions: [".ts", ".js"] },
|
resolve: { extensions: [".ts", ".js"] },
|
||||||
@@ -42,9 +29,6 @@ const webpackConfiguration: WebpackConfiguration = {
|
|||||||
path: path.join(__dirname, "dist"),
|
path: path.join(__dirname, "dist"),
|
||||||
filename: "[contenthash].js",
|
filename: "[contenthash].js",
|
||||||
},
|
},
|
||||||
experiments: {
|
|
||||||
asyncWebAssembly: true,
|
|
||||||
},
|
|
||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
@@ -68,7 +52,7 @@ const webpackConfiguration: WebpackConfiguration = {
|
|||||||
title: "snk - " + demo,
|
title: "snk - " + demo,
|
||||||
filename: `${demo}.html`,
|
filename: `${demo}.html`,
|
||||||
chunks: [demo],
|
chunks: [demo],
|
||||||
}),
|
})
|
||||||
),
|
),
|
||||||
new HtmlWebpackPlugin({
|
new HtmlWebpackPlugin({
|
||||||
title: "snk - " + demos[0],
|
title: "snk - " + demos[0],
|
||||||
|
|||||||
@@ -54,6 +54,6 @@ export const createRpcClient = <API_ extends API>(worker: Worker) => {
|
|||||||
worker.addEventListener("terminate", onTerminate);
|
worker.addEventListener("terminate", onTerminate);
|
||||||
worker.postMessage({ symbol, key, methodName, args });
|
worker.postMessage({ symbol, key, methodName, args });
|
||||||
}),
|
}),
|
||||||
},
|
}
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ export const getCircleSize = (n: number) => {
|
|||||||
export const drawCircleStack = (
|
export const drawCircleStack = (
|
||||||
ctx: CanvasRenderingContext2D,
|
ctx: CanvasRenderingContext2D,
|
||||||
stack: Color[],
|
stack: Color[],
|
||||||
o: Options,
|
o: Options
|
||||||
) => {
|
) => {
|
||||||
for (let i = stack.length; i--; ) {
|
for (let i = stack.length; i--; ) {
|
||||||
const { x, y } = cellPath[i];
|
const { x, y } = cellPath[i];
|
||||||
@@ -67,7 +67,7 @@ export const drawCircleStack = (
|
|||||||
ctx.save();
|
ctx.save();
|
||||||
ctx.translate(
|
ctx.translate(
|
||||||
x * o.sizeCell + (o.sizeCell - o.sizeDot) / 2,
|
x * o.sizeCell + (o.sizeCell - o.sizeDot) / 2,
|
||||||
y * o.sizeCell + (o.sizeCell - o.sizeDot) / 2,
|
y * o.sizeCell + (o.sizeCell - o.sizeDot) / 2
|
||||||
);
|
);
|
||||||
|
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ export const drawGrid = (
|
|||||||
ctx: CanvasRenderingContext2D,
|
ctx: CanvasRenderingContext2D,
|
||||||
grid: Grid,
|
grid: Grid,
|
||||||
cells: Point[] | null,
|
cells: Point[] | null,
|
||||||
o: Options,
|
o: Options
|
||||||
) => {
|
) => {
|
||||||
for (let x = grid.width; x--; )
|
for (let x = grid.width; x--; )
|
||||||
for (let y = grid.height; y--; ) {
|
for (let y = grid.height; y--; ) {
|
||||||
@@ -27,7 +27,7 @@ export const drawGrid = (
|
|||||||
ctx.save();
|
ctx.save();
|
||||||
ctx.translate(
|
ctx.translate(
|
||||||
x * o.sizeCell + (o.sizeCell - o.sizeDot) / 2,
|
x * o.sizeCell + (o.sizeCell - o.sizeDot) / 2,
|
||||||
y * o.sizeCell + (o.sizeCell - o.sizeDot) / 2,
|
y * o.sizeCell + (o.sizeCell - o.sizeDot) / 2
|
||||||
);
|
);
|
||||||
|
|
||||||
ctx.fillStyle = color;
|
ctx.fillStyle = color;
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ type Options = {
|
|||||||
export const drawSnake = (
|
export const drawSnake = (
|
||||||
ctx: CanvasRenderingContext2D,
|
ctx: CanvasRenderingContext2D,
|
||||||
snake: Snake,
|
snake: Snake,
|
||||||
o: Options,
|
o: Options
|
||||||
) => {
|
) => {
|
||||||
const cells = snakeToCells(snake);
|
const cells = snakeToCells(snake);
|
||||||
|
|
||||||
@@ -25,7 +25,7 @@ export const drawSnake = (
|
|||||||
ctx,
|
ctx,
|
||||||
o.sizeCell - u * 2,
|
o.sizeCell - u * 2,
|
||||||
o.sizeCell - u * 2,
|
o.sizeCell - u * 2,
|
||||||
(o.sizeCell - u * 2) * 0.25,
|
(o.sizeCell - u * 2) * 0.25
|
||||||
);
|
);
|
||||||
ctx.fill();
|
ctx.fill();
|
||||||
ctx.restore();
|
ctx.restore();
|
||||||
@@ -40,7 +40,7 @@ export const drawSnakeLerp = (
|
|||||||
snake0: Snake,
|
snake0: Snake,
|
||||||
snake1: Snake,
|
snake1: Snake,
|
||||||
k: number,
|
k: number,
|
||||||
o: Options,
|
o: Options
|
||||||
) => {
|
) => {
|
||||||
const m = 0.8;
|
const m = 0.8;
|
||||||
const n = snake0.length / 2;
|
const n = snake0.length / 2;
|
||||||
@@ -61,7 +61,7 @@ export const drawSnakeLerp = (
|
|||||||
ctx,
|
ctx,
|
||||||
o.sizeCell - u * 2,
|
o.sizeCell - u * 2,
|
||||||
o.sizeCell - u * 2,
|
o.sizeCell - u * 2,
|
||||||
(o.sizeCell - u * 2) * 0.25,
|
(o.sizeCell - u * 2) * 0.25
|
||||||
);
|
);
|
||||||
ctx.fill();
|
ctx.fill();
|
||||||
ctx.restore();
|
ctx.restore();
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export const drawStack = (
|
|||||||
stack: Color[],
|
stack: Color[],
|
||||||
max: number,
|
max: number,
|
||||||
width: number,
|
width: number,
|
||||||
o: { colorDots: Record<Color, string> },
|
o: { colorDots: Record<Color, string> }
|
||||||
) => {
|
) => {
|
||||||
ctx.save();
|
ctx.save();
|
||||||
|
|
||||||
@@ -39,7 +39,7 @@ export const drawWorld = (
|
|||||||
cells: Point[] | null,
|
cells: Point[] | null,
|
||||||
snake: Snake,
|
snake: Snake,
|
||||||
stack: Color[],
|
stack: Color[],
|
||||||
o: Options,
|
o: Options
|
||||||
) => {
|
) => {
|
||||||
ctx.save();
|
ctx.save();
|
||||||
|
|
||||||
@@ -66,14 +66,14 @@ export const drawWorld = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const drawLerpWorld = (
|
export const drawLerpWorld = (
|
||||||
ctx: CanvasRenderingContext2D | CanvasRenderingContext2D,
|
ctx: CanvasRenderingContext2D,
|
||||||
grid: Grid,
|
grid: Grid,
|
||||||
cells: Point[] | null,
|
cells: Point[] | null,
|
||||||
snake0: Snake,
|
snake0: Snake,
|
||||||
snake1: Snake,
|
snake1: Snake,
|
||||||
stack: Color[],
|
stack: Color[],
|
||||||
k: number,
|
k: number,
|
||||||
o: Options,
|
o: Options
|
||||||
) => {
|
) => {
|
||||||
ctx.save();
|
ctx.save();
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ export const pathRoundedRect = (
|
|||||||
ctx: CanvasRenderingContext2D,
|
ctx: CanvasRenderingContext2D,
|
||||||
width: number,
|
width: number,
|
||||||
height: number,
|
height: number,
|
||||||
borderRadius: number,
|
borderRadius: number
|
||||||
) => {
|
) => {
|
||||||
ctx.moveTo(borderRadius, 0);
|
ctx.moveTo(borderRadius, 0);
|
||||||
ctx.arcTo(width, 0, width, height, borderRadius);
|
ctx.arcTo(width, 0, width, height, borderRadius);
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { getPathToPose } from "@snk/solver/getPathToPose";
|
|||||||
import type { Options as DrawOptions } from "@snk/draw/drawWorld";
|
import type { Options as DrawOptions } from "@snk/draw/drawWorld";
|
||||||
|
|
||||||
let snake = createSnakeFromCells(
|
let snake = createSnakeFromCells(
|
||||||
Array.from({ length: 4 }, (_, i) => ({ x: i, y: -1 })),
|
Array.from({ length: 4 }, (_, i) => ({ x: i, y: -1 }))
|
||||||
);
|
);
|
||||||
|
|
||||||
// const chain = [snake];
|
// const chain = [snake];
|
||||||
@@ -45,7 +45,7 @@ const animationOptions: AnimationOptions = { frameDuration: 100, step: 1 };
|
|||||||
) {
|
) {
|
||||||
const stats: number[] = [];
|
const stats: number[] = [];
|
||||||
|
|
||||||
let buffer: Uint8Array;
|
let buffer: Buffer;
|
||||||
const start = Date.now();
|
const start = Date.now();
|
||||||
const chainL = chain.slice(0, length);
|
const chainL = chain.slice(0, length);
|
||||||
for (let k = 0; k < 10 && (Date.now() - start < 10 * 1000 || k < 2); k++) {
|
for (let k = 0; k < 10 && (Date.now() - start < 10 * 1000 || k < 2); k++) {
|
||||||
@@ -55,7 +55,7 @@ const animationOptions: AnimationOptions = { frameDuration: 100, step: 1 };
|
|||||||
null,
|
null,
|
||||||
chainL,
|
chainL,
|
||||||
drawOptions,
|
drawOptions,
|
||||||
animationOptions,
|
animationOptions
|
||||||
);
|
);
|
||||||
stats.push(performance.now() - s);
|
stats.push(performance.now() - s);
|
||||||
}
|
}
|
||||||
@@ -73,12 +73,12 @@ const animationOptions: AnimationOptions = { frameDuration: 100, step: 1 };
|
|||||||
})}ms`,
|
})}ms`,
|
||||||
"",
|
"",
|
||||||
].join("\n"),
|
].join("\n"),
|
||||||
stats,
|
stats
|
||||||
);
|
);
|
||||||
|
|
||||||
fs.writeFileSync(
|
fs.writeFileSync(
|
||||||
`__tests__/__snapshots__/benchmark-output-${length}.gif`,
|
`__tests__/__snapshots__/benchmark-output-${length}.gif`,
|
||||||
buffer!,
|
buffer!
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import * as fs from "fs";
|
import * as fs from "fs";
|
||||||
import * as path from "path";
|
import * as path from "path";
|
||||||
import { it, expect } from "bun:test";
|
|
||||||
import { AnimationOptions, createGif } from "..";
|
import { AnimationOptions, createGif } from "..";
|
||||||
import * as grids from "@snk/types/__fixtures__/grid";
|
import * as grids from "@snk/types/__fixtures__/grid";
|
||||||
import { snake3 as snake } from "@snk/types/__fixtures__/snake";
|
import { snake3 as snake } from "@snk/types/__fixtures__/snake";
|
||||||
@@ -8,6 +7,8 @@ import { createSnakeFromCells, nextSnake } from "@snk/types/snake";
|
|||||||
import { getBestRoute } from "@snk/solver/getBestRoute";
|
import { getBestRoute } from "@snk/solver/getBestRoute";
|
||||||
import type { Options as DrawOptions } from "@snk/draw/drawWorld";
|
import type { Options as DrawOptions } from "@snk/draw/drawWorld";
|
||||||
|
|
||||||
|
jest.setTimeout(20 * 1000);
|
||||||
|
|
||||||
const upscale = 1;
|
const upscale = 1;
|
||||||
const drawOptions: DrawOptions = {
|
const drawOptions: DrawOptions = {
|
||||||
sizeDotBorderRadius: 2 * upscale,
|
sizeDotBorderRadius: 2 * upscale,
|
||||||
@@ -34,58 +35,44 @@ for (const key of [
|
|||||||
"small",
|
"small",
|
||||||
"smallPacked",
|
"smallPacked",
|
||||||
] as const)
|
] as const)
|
||||||
it(
|
it(`should generate ${key} gif`, async () => {
|
||||||
`should generate ${key} gif`,
|
const grid = grids[key];
|
||||||
async () => {
|
|
||||||
const grid = grids[key];
|
|
||||||
|
|
||||||
const chain = [snake, ...getBestRoute(grid, snake)!];
|
const chain = [snake, ...getBestRoute(grid, snake)!];
|
||||||
|
|
||||||
const gif = await createGif(
|
|
||||||
grid,
|
|
||||||
null,
|
|
||||||
chain,
|
|
||||||
drawOptions,
|
|
||||||
animationOptions,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(gif).toBeDefined();
|
|
||||||
|
|
||||||
fs.writeFileSync(path.resolve(dir, key + ".gif"), gif);
|
|
||||||
},
|
|
||||||
{ timeout: 20 * 1000 },
|
|
||||||
);
|
|
||||||
|
|
||||||
it(
|
|
||||||
`should generate swipper`,
|
|
||||||
async () => {
|
|
||||||
const grid = grids.smallFull;
|
|
||||||
let snk = createSnakeFromCells(
|
|
||||||
Array.from({ length: 6 }, (_, i) => ({ x: i, y: -1 })),
|
|
||||||
);
|
|
||||||
|
|
||||||
const chain = [snk];
|
|
||||||
for (let y = -1; y < grid.height; y++) {
|
|
||||||
snk = nextSnake(snk, 0, 1);
|
|
||||||
chain.push(snk);
|
|
||||||
|
|
||||||
for (let x = grid.width - 1; x--; ) {
|
|
||||||
snk = nextSnake(snk, (y + 100) % 2 ? 1 : -1, 0);
|
|
||||||
chain.push(snk);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const gif = await createGif(
|
const gif = await createGif(
|
||||||
grid,
|
grid,
|
||||||
null,
|
null,
|
||||||
chain,
|
chain,
|
||||||
drawOptions,
|
drawOptions,
|
||||||
animationOptions,
|
animationOptions
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(gif).toBeDefined();
|
expect(gif).toBeDefined();
|
||||||
|
|
||||||
fs.writeFileSync(path.resolve(dir, "swipper.gif"), gif);
|
fs.writeFileSync(path.resolve(dir, key + ".gif"), gif);
|
||||||
},
|
});
|
||||||
{ timeout: 20 * 1000 },
|
|
||||||
);
|
it(`should generate swipper`, async () => {
|
||||||
|
const grid = grids.smallFull;
|
||||||
|
let snk = createSnakeFromCells(
|
||||||
|
Array.from({ length: 6 }, (_, i) => ({ x: i, y: -1 }))
|
||||||
|
);
|
||||||
|
|
||||||
|
const chain = [snk];
|
||||||
|
for (let y = -1; y < grid.height; y++) {
|
||||||
|
snk = nextSnake(snk, 0, 1);
|
||||||
|
chain.push(snk);
|
||||||
|
|
||||||
|
for (let x = grid.width - 1; x--; ) {
|
||||||
|
snk = nextSnake(snk, (y + 100) % 2 ? 1 : -1, 0);
|
||||||
|
chain.push(snk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const gif = await createGif(grid, null, chain, drawOptions, animationOptions);
|
||||||
|
|
||||||
|
expect(gif).toBeDefined();
|
||||||
|
|
||||||
|
fs.writeFileSync(path.resolve(dir, "swipper.gif"), gif);
|
||||||
|
});
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import gifsicle from "gifsicle";
|
|||||||
import GIFEncoder from "gif-encoder-2";
|
import GIFEncoder from "gif-encoder-2";
|
||||||
|
|
||||||
const withTmpDir = async <T>(
|
const withTmpDir = async <T>(
|
||||||
handler: (dir: string) => Promise<T>,
|
handler: (dir: string) => Promise<T>
|
||||||
): Promise<T> => {
|
): Promise<T> => {
|
||||||
const { name: dir, removeCallback: cleanUp } = tmp.dirSync({
|
const { name: dir, removeCallback: cleanUp } = tmp.dirSync({
|
||||||
unsafeCleanup: true,
|
unsafeCleanup: true,
|
||||||
@@ -37,13 +37,13 @@ export const createGif = async (
|
|||||||
cells: Point[] | null,
|
cells: Point[] | null,
|
||||||
chain: Snake[],
|
chain: Snake[],
|
||||||
drawOptions: DrawOptions,
|
drawOptions: DrawOptions,
|
||||||
animationOptions: AnimationOptions,
|
animationOptions: AnimationOptions
|
||||||
) =>
|
) =>
|
||||||
withTmpDir(async (dir) => {
|
withTmpDir(async (dir) => {
|
||||||
const { width, height } = getCanvasWorldSize(grid0, drawOptions);
|
const { width, height } = getCanvasWorldSize(grid0, drawOptions);
|
||||||
|
|
||||||
const canvas = createCanvas(width, height);
|
const canvas = createCanvas(width, height);
|
||||||
const ctx = canvas.getContext("2d") as any as CanvasRenderingContext2D;
|
const ctx = canvas.getContext("2d")!;
|
||||||
|
|
||||||
const grid = copyGrid(grid0);
|
const grid = copyGrid(grid0);
|
||||||
const stack: Color[] = [];
|
const stack: Color[] = [];
|
||||||
@@ -70,7 +70,7 @@ export const createGif = async (
|
|||||||
snake1,
|
snake1,
|
||||||
stack,
|
stack,
|
||||||
k / animationOptions.step,
|
k / animationOptions.step,
|
||||||
drawOptions,
|
drawOptions
|
||||||
);
|
);
|
||||||
|
|
||||||
encoder.addFrame(ctx);
|
encoder.addFrame(ctx);
|
||||||
@@ -92,8 +92,8 @@ export const createGif = async (
|
|||||||
"--colors=18",
|
"--colors=18",
|
||||||
outFileName,
|
outFileName,
|
||||||
["--output", optimizedFileName],
|
["--output", optimizedFileName],
|
||||||
].flat(),
|
].flat()
|
||||||
);
|
);
|
||||||
|
|
||||||
return new Uint8Array(fs.readFileSync(optimizedFileName));
|
return fs.readFileSync(optimizedFileName);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,16 +4,17 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@snk/draw": "1.0.0",
|
"@snk/draw": "1.0.0",
|
||||||
"@snk/solver": "1.0.0",
|
"@snk/solver": "1.0.0",
|
||||||
"canvas": "3.1.0",
|
"canvas": "2.10.2",
|
||||||
"gif-encoder-2": "1.0.5",
|
"gif-encoder-2": "1.0.5",
|
||||||
"gifsicle": "5.3.0",
|
"gifsicle": "5.3.0",
|
||||||
"tmp": "0.2.3"
|
"tmp": "0.2.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/gifsicle": "5.2.2",
|
"@types/gifsicle": "5.2.0",
|
||||||
"@types/tmp": "0.2.6"
|
"@types/tmp": "0.2.3",
|
||||||
|
"@vercel/ncc": "0.34.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"benchmark": "bun __tests__/benchmark.ts"
|
"benchmark": "ncc run __tests__/benchmark.ts --quiet"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,3 @@
|
|||||||
# @snk/github-user-contribution-service
|
# @snk/github-user-contribution-service
|
||||||
|
|
||||||
Expose github-user-contribution as an endpoint. hosted on cloudflare
|
Expose github-user-contribution as an endpoint, using vercel.sh
|
||||||
|
|
||||||
```sh
|
|
||||||
|
|
||||||
|
|
||||||
# deploy
|
|
||||||
bunx wrangler deploy --branch=production
|
|
||||||
|
|
||||||
# change secret
|
|
||||||
bunx wrangler secret put GITHUB_TOKEN
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { getGithubUserContribution } from "@snk/github-user-contribution";
|
||||||
|
import { VercelRequest, VercelResponse } from "@vercel/node";
|
||||||
|
|
||||||
|
export default async (req: VercelRequest, res: VercelResponse) => {
|
||||||
|
const { userName } = req.query;
|
||||||
|
|
||||||
|
try {
|
||||||
|
res.setHeader("Access-Control-Allow-Origin", "https://platane.github.io");
|
||||||
|
res.statusCode = 200;
|
||||||
|
res.json(await getGithubUserContribution(userName as string));
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
res.statusCode = 500;
|
||||||
|
res.end();
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
import { getGithubUserContribution } from "@snk/github-user-contribution";
|
|
||||||
|
|
||||||
const cors =
|
|
||||||
<
|
|
||||||
Req extends { headers: Headers },
|
|
||||||
Res extends { headers: Headers },
|
|
||||||
A extends Array<any>,
|
|
||||||
>(
|
|
||||||
f: (req: Req, ...args: A) => Res | Promise<Res>,
|
|
||||||
) =>
|
|
||||||
async (req: Req, ...args: A) => {
|
|
||||||
const res = await f(req, ...args);
|
|
||||||
|
|
||||||
const origin = req.headers.get("origin");
|
|
||||||
|
|
||||||
if (origin) {
|
|
||||||
const { host, hostname } = new URL(origin);
|
|
||||||
|
|
||||||
if (hostname === "localhost" || host === "platane.github.io")
|
|
||||||
res.headers.set("Access-Control-Allow-Origin", origin);
|
|
||||||
}
|
|
||||||
|
|
||||||
res.headers.set("Access-Control-Allow-Methods", "GET, OPTIONS");
|
|
||||||
res.headers.set("Access-Control-Allow-Headers", "Content-Type");
|
|
||||||
return res;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default {
|
|
||||||
fetch: cors(async (req: Request, env: { GITHUB_TOKEN: string }) => {
|
|
||||||
const url = new URL(req.url);
|
|
||||||
|
|
||||||
const [, userName] =
|
|
||||||
url.pathname.match(/^\/github-user-contribution\/([^\/]*)\/?$/) ?? [];
|
|
||||||
|
|
||||||
if (req.method === "OPTIONS") return new Response();
|
|
||||||
|
|
||||||
if (!userName || req.method !== "GET")
|
|
||||||
return new Response("unknown route", { status: 404 });
|
|
||||||
|
|
||||||
const body = await getGithubUserContribution(userName, {
|
|
||||||
githubToken: env.GITHUB_TOKEN,
|
|
||||||
});
|
|
||||||
|
|
||||||
return new Response(JSON.stringify(body), {
|
|
||||||
status: 200,
|
|
||||||
headers: {
|
|
||||||
"Cache-Control": "max-age=21600, s-maxage=21600",
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
@@ -2,13 +2,7 @@
|
|||||||
"name": "@snk/github-user-contribution-service",
|
"name": "@snk/github-user-contribution-service",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@snk/github-user-contribution": "1.0.0"
|
"@snk/github-user-contribution": "1.0.0",
|
||||||
},
|
"@vercel/node": "2.6.1"
|
||||||
"devDependencies": {
|
|
||||||
"wrangler": "3.109.2",
|
|
||||||
"@cloudflare/workers-types": "4.20250214.0"
|
|
||||||
},
|
|
||||||
"scripts": {
|
|
||||||
"deploy": "wrangler deploy"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
5
packages/github-user-contribution-service/vercel.json
Normal file
5
packages/github-user-contribution-service/vercel.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"github": {
|
||||||
|
"silent": true
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
name = "github-user-contribution"
|
|
||||||
main = "index.ts"
|
|
||||||
compatibility_date = "2024-09-02"
|
|
||||||
|
|
||||||
account_id = "56268cde636c288343cb0767952ecf2e"
|
|
||||||
workers_dev = true
|
|
||||||
|
|
||||||
# [observability]
|
|
||||||
# enabled = true
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,19 @@
|
|||||||
|
import { formatParams } from "../formatParams";
|
||||||
|
|
||||||
|
const params = [
|
||||||
|
//
|
||||||
|
[{}, ""],
|
||||||
|
[{ year: 2017 }, "from=2017-01-01&to=2017-12-31"],
|
||||||
|
[{ from: "2017-12-03" }, "from=2017-12-03"],
|
||||||
|
[{ to: "2017-12-03" }, "to=2017-12-03"],
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
params.forEach(([params, res]) =>
|
||||||
|
it(`should format ${JSON.stringify(params)}`, () => {
|
||||||
|
expect(formatParams(params)).toBe(res);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
it("should fail if the date is in the future", () => {
|
||||||
|
expect(() => formatParams({ to: "9999-01-01" })).toThrow(Error);
|
||||||
|
});
|
||||||
@@ -1,10 +1,7 @@
|
|||||||
import { getGithubUserContribution } from "..";
|
import { getGithubUserContribution } from "..";
|
||||||
import { describe, it, expect } from "bun:test";
|
|
||||||
|
|
||||||
describe("getGithubUserContribution", () => {
|
describe("getGithubUserContribution", () => {
|
||||||
const promise = getGithubUserContribution("platane", {
|
const promise = getGithubUserContribution("platane");
|
||||||
githubToken: process.env.GITHUB_TOKEN!,
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should resolve", async () => {
|
it("should resolve", async () => {
|
||||||
await promise;
|
await promise;
|
||||||
@@ -30,3 +27,9 @@ describe("getGithubUserContribution", () => {
|
|||||||
expect(undefinedDays).toEqual([]);
|
expect(undefinedDays).toEqual([]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
xit("should match snapshot for year=2019", async () => {
|
||||||
|
expect(
|
||||||
|
await getGithubUserContribution("platane", { year: 2019 })
|
||||||
|
).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|||||||
38
packages/github-user-contribution/formatParams.ts
Normal file
38
packages/github-user-contribution/formatParams.ts
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
export type Options = { from?: string; to?: string } | { year: number };
|
||||||
|
|
||||||
|
export const formatParams = (options: Options = {}) => {
|
||||||
|
const sp = new URLSearchParams();
|
||||||
|
|
||||||
|
const o: any = { ...options };
|
||||||
|
|
||||||
|
if ("year" in options) {
|
||||||
|
o.from = `${options.year}-01-01`;
|
||||||
|
o.to = `${options.year}-12-31`;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const s of ["from", "to"])
|
||||||
|
if (o[s]) {
|
||||||
|
const value = o[s];
|
||||||
|
|
||||||
|
if (value >= formatDate(new Date()))
|
||||||
|
throw new Error(
|
||||||
|
"Cannot get contribution for a date in the future.\nPlease limit your range to the current UTC day."
|
||||||
|
);
|
||||||
|
|
||||||
|
sp.set(s, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sp.toString();
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatDate = (d: Date) => {
|
||||||
|
const year = d.getUTCFullYear();
|
||||||
|
const month = d.getUTCMonth() + 1;
|
||||||
|
const date = d.getUTCDate();
|
||||||
|
|
||||||
|
return [
|
||||||
|
year,
|
||||||
|
month.toString().padStart(2, "0"),
|
||||||
|
date.toString().padStart(2, "0"),
|
||||||
|
].join("-");
|
||||||
|
};
|
||||||
@@ -1,3 +1,6 @@
|
|||||||
|
import fetch from "node-fetch";
|
||||||
|
import { formatParams, Options } from "./formatParams";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* get the contribution grid from a github user page
|
* get the contribution grid from a github user page
|
||||||
*
|
*
|
||||||
@@ -16,84 +19,61 @@
|
|||||||
*/
|
*/
|
||||||
export const getGithubUserContribution = async (
|
export const getGithubUserContribution = async (
|
||||||
userName: string,
|
userName: string,
|
||||||
o: { githubToken: string },
|
options: Options = {}
|
||||||
) => {
|
) => {
|
||||||
const query = /* GraphQL */ `
|
// either use github.com/users/xxxx/contributions for previous years
|
||||||
query ($login: String!) {
|
// or github.com/xxxx ( which gives the latest update to today result )
|
||||||
user(login: $login) {
|
const url =
|
||||||
contributionsCollection {
|
"year" in options || "from" in options || "to" in options
|
||||||
contributionCalendar {
|
? `https://github.com/users/${userName}/contributions?` +
|
||||||
weeks {
|
formatParams(options)
|
||||||
contributionDays {
|
: `https://github.com/${userName}`;
|
||||||
contributionCount
|
|
||||||
contributionLevel
|
|
||||||
weekday
|
|
||||||
date
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
const variables = { login: userName };
|
|
||||||
|
|
||||||
const res = await fetch("https://api.github.com/graphql", {
|
const res = await fetch(url);
|
||||||
headers: {
|
|
||||||
Authorization: `bearer ${o.githubToken}`,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
"User-Agent": "me@platane.me",
|
|
||||||
},
|
|
||||||
method: "POST",
|
|
||||||
body: JSON.stringify({ variables, query }),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!res.ok) throw new Error(await res.text().catch(() => res.statusText));
|
if (!res.ok) throw new Error(res.statusText);
|
||||||
|
|
||||||
const { data, errors } = (await res.json()) as {
|
const resText = await res.text();
|
||||||
data: GraphQLRes;
|
|
||||||
errors?: { message: string }[];
|
|
||||||
};
|
|
||||||
|
|
||||||
if (errors?.[0]) throw errors[0];
|
return parseUserPage(resText);
|
||||||
|
|
||||||
return data.user.contributionsCollection.contributionCalendar.weeks.flatMap(
|
|
||||||
({ contributionDays }, x) =>
|
|
||||||
contributionDays.map((d) => ({
|
|
||||||
x,
|
|
||||||
y: d.weekday,
|
|
||||||
date: d.date,
|
|
||||||
count: d.contributionCount,
|
|
||||||
level:
|
|
||||||
(d.contributionLevel === "FOURTH_QUARTILE" && 4) ||
|
|
||||||
(d.contributionLevel === "THIRD_QUARTILE" && 3) ||
|
|
||||||
(d.contributionLevel === "SECOND_QUARTILE" && 2) ||
|
|
||||||
(d.contributionLevel === "FIRST_QUARTILE" && 1) ||
|
|
||||||
0,
|
|
||||||
})),
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
type GraphQLRes = {
|
const parseUserPage = (content: string) => {
|
||||||
user: {
|
// take roughly the svg block
|
||||||
contributionsCollection: {
|
const block = content
|
||||||
contributionCalendar: {
|
.split(`class="js-calendar-graph-svg"`)[1]
|
||||||
weeks: {
|
.split("</svg>")[0];
|
||||||
contributionDays: {
|
|
||||||
contributionCount: number;
|
let x = 0;
|
||||||
contributionLevel:
|
let lastYAttribute = 0;
|
||||||
| "FOURTH_QUARTILE"
|
|
||||||
| "THIRD_QUARTILE"
|
const rects = Array.from(block.matchAll(/<rect[^>]*>[^<]*<\/rect>/g)).map(
|
||||||
| "SECOND_QUARTILE"
|
([m]) => {
|
||||||
| "FIRST_QUARTILE"
|
const date = m.match(/data-date="([^"]+)"/)![1];
|
||||||
| "NONE";
|
const level = +m.match(/data-level="([^"]+)"/)![1];
|
||||||
date: string;
|
const yAttribute = +m.match(/y="([^"]+)"/)![1];
|
||||||
weekday: number;
|
|
||||||
}[];
|
const literalCount = m.match(/(No|\d+) contributions? on/)![1];
|
||||||
}[];
|
const count = literalCount === "No" ? 0 : +literalCount;
|
||||||
};
|
|
||||||
};
|
if (lastYAttribute > yAttribute) x++;
|
||||||
};
|
|
||||||
|
lastYAttribute = yAttribute;
|
||||||
|
|
||||||
|
return { date, count, level, x, yAttribute };
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const yAttributes = Array.from(
|
||||||
|
new Set(rects.map((c) => c.yAttribute)).keys()
|
||||||
|
).sort();
|
||||||
|
|
||||||
|
const cells = rects.map(({ yAttribute, ...c }) => ({
|
||||||
|
y: yAttributes.indexOf(yAttribute),
|
||||||
|
...c,
|
||||||
|
}));
|
||||||
|
|
||||||
|
return cells;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Res = Awaited<ReturnType<typeof getGithubUserContribution>>;
|
export type Res = Awaited<ReturnType<typeof getGithubUserContribution>>;
|
||||||
|
|||||||
@@ -1,4 +1,11 @@
|
|||||||
{
|
{
|
||||||
"name": "@snk/github-user-contribution",
|
"name": "@snk/github-user-contribution",
|
||||||
"version": "1.0.0"
|
"version": "1.0.0",
|
||||||
|
"dependencies": {
|
||||||
|
"cheerio": "1.0.0-rc.10",
|
||||||
|
"node-fetch": "2.6.7"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node-fetch": "2.6.1"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
2
packages/solver-r/.gitignore
vendored
2
packages/solver-r/.gitignore
vendored
@@ -1,2 +0,0 @@
|
|||||||
target
|
|
||||||
pkg
|
|
||||||
341
packages/solver-r/Cargo.lock
generated
341
packages/solver-r/Cargo.lock
generated
@@ -1,341 +0,0 @@
|
|||||||
# This file is automatically @generated by Cargo.
|
|
||||||
# It is not intended for manual editing.
|
|
||||||
version = 4
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "bumpalo"
|
|
||||||
version = "3.17.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "cc"
|
|
||||||
version = "1.2.17"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1fcb57c740ae1daf453ae85f16e37396f672b039e00d9d866e07ddb24e328e3a"
|
|
||||||
dependencies = [
|
|
||||||
"shlex",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "cfg-if"
|
|
||||||
version = "1.0.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "console_error_panic_hook"
|
|
||||||
version = "0.1.7"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if",
|
|
||||||
"wasm-bindgen",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "console_log"
|
|
||||||
version = "1.0.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "be8aed40e4edbf4d3b4431ab260b63fdc40f5780a4766824329ea0f1eefe3c0f"
|
|
||||||
dependencies = [
|
|
||||||
"log",
|
|
||||||
"web-sys",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "js-sys"
|
|
||||||
version = "0.3.77"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
|
|
||||||
dependencies = [
|
|
||||||
"once_cell",
|
|
||||||
"wasm-bindgen",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "log"
|
|
||||||
version = "0.4.26"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "minicov"
|
|
||||||
version = "0.3.7"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f27fe9f1cc3c22e1687f9446c2083c4c5fc7f0bcf1c7a86bdbded14985895b4b"
|
|
||||||
dependencies = [
|
|
||||||
"cc",
|
|
||||||
"walkdir",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "once_cell"
|
|
||||||
version = "1.21.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d75b0bedcc4fe52caa0e03d9f1151a323e4aa5e2d78ba3580400cd3c9e2bc4bc"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "proc-macro2"
|
|
||||||
version = "1.0.94"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84"
|
|
||||||
dependencies = [
|
|
||||||
"unicode-ident",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "quote"
|
|
||||||
version = "1.0.40"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rustversion"
|
|
||||||
version = "1.0.20"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "same-file"
|
|
||||||
version = "1.0.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
|
|
||||||
dependencies = [
|
|
||||||
"winapi-util",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "shlex"
|
|
||||||
version = "1.3.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "snk-solver-rust"
|
|
||||||
version = "1.0.0"
|
|
||||||
dependencies = [
|
|
||||||
"console_error_panic_hook",
|
|
||||||
"console_log",
|
|
||||||
"js-sys",
|
|
||||||
"log",
|
|
||||||
"wasm-bindgen",
|
|
||||||
"wasm-bindgen-test",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "syn"
|
|
||||||
version = "2.0.100"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"unicode-ident",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "unicode-ident"
|
|
||||||
version = "1.0.18"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "walkdir"
|
|
||||||
version = "2.5.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
|
|
||||||
dependencies = [
|
|
||||||
"same-file",
|
|
||||||
"winapi-util",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wasm-bindgen"
|
|
||||||
version = "0.2.100"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if",
|
|
||||||
"once_cell",
|
|
||||||
"rustversion",
|
|
||||||
"wasm-bindgen-macro",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wasm-bindgen-backend"
|
|
||||||
version = "0.2.100"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
|
|
||||||
dependencies = [
|
|
||||||
"bumpalo",
|
|
||||||
"log",
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
"wasm-bindgen-shared",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wasm-bindgen-futures"
|
|
||||||
version = "0.4.50"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if",
|
|
||||||
"js-sys",
|
|
||||||
"once_cell",
|
|
||||||
"wasm-bindgen",
|
|
||||||
"web-sys",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wasm-bindgen-macro"
|
|
||||||
version = "0.2.100"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
|
|
||||||
dependencies = [
|
|
||||||
"quote",
|
|
||||||
"wasm-bindgen-macro-support",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wasm-bindgen-macro-support"
|
|
||||||
version = "0.2.100"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
"wasm-bindgen-backend",
|
|
||||||
"wasm-bindgen-shared",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wasm-bindgen-shared"
|
|
||||||
version = "0.2.100"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
|
|
||||||
dependencies = [
|
|
||||||
"unicode-ident",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wasm-bindgen-test"
|
|
||||||
version = "0.3.50"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "66c8d5e33ca3b6d9fa3b4676d774c5778031d27a578c2b007f905acf816152c3"
|
|
||||||
dependencies = [
|
|
||||||
"js-sys",
|
|
||||||
"minicov",
|
|
||||||
"wasm-bindgen",
|
|
||||||
"wasm-bindgen-futures",
|
|
||||||
"wasm-bindgen-test-macro",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wasm-bindgen-test-macro"
|
|
||||||
version = "0.3.50"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "17d5042cc5fa009658f9a7333ef24291b1291a25b6382dd68862a7f3b969f69b"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "web-sys"
|
|
||||||
version = "0.3.77"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2"
|
|
||||||
dependencies = [
|
|
||||||
"js-sys",
|
|
||||||
"wasm-bindgen",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "winapi-util"
|
|
||||||
version = "0.1.9"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
|
|
||||||
dependencies = [
|
|
||||||
"windows-sys",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows-sys"
|
|
||||||
version = "0.59.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
|
|
||||||
dependencies = [
|
|
||||||
"windows-targets",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows-targets"
|
|
||||||
version = "0.52.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
|
||||||
dependencies = [
|
|
||||||
"windows_aarch64_gnullvm",
|
|
||||||
"windows_aarch64_msvc",
|
|
||||||
"windows_i686_gnu",
|
|
||||||
"windows_i686_gnullvm",
|
|
||||||
"windows_i686_msvc",
|
|
||||||
"windows_x86_64_gnu",
|
|
||||||
"windows_x86_64_gnullvm",
|
|
||||||
"windows_x86_64_msvc",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_aarch64_gnullvm"
|
|
||||||
version = "0.52.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_aarch64_msvc"
|
|
||||||
version = "0.52.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_i686_gnu"
|
|
||||||
version = "0.52.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_i686_gnullvm"
|
|
||||||
version = "0.52.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_i686_msvc"
|
|
||||||
version = "0.52.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_x86_64_gnu"
|
|
||||||
version = "0.52.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_x86_64_gnullvm"
|
|
||||||
version = "0.52.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_x86_64_msvc"
|
|
||||||
version = "0.52.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "snk-solver-rust"
|
|
||||||
version = "1.0.0"
|
|
||||||
authors = ["platane"]
|
|
||||||
edition = "2018"
|
|
||||||
|
|
||||||
[lib]
|
|
||||||
crate-type = ["cdylib", "rlib"]
|
|
||||||
|
|
||||||
[features]
|
|
||||||
default = ["console_error_panic_hook"]
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
wasm-bindgen = "0.2.100"
|
|
||||||
js-sys = "0.3.77"
|
|
||||||
console_log = "1.0.0"
|
|
||||||
log = "0.4"
|
|
||||||
|
|
||||||
# The `console_error_panic_hook` crate provides better debugging of panics by
|
|
||||||
# logging them with `console.error`. This is great for development, but requires
|
|
||||||
# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for
|
|
||||||
# code size when deploying.
|
|
||||||
console_error_panic_hook = { version = "0.1.7", optional = true }
|
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
wasm-bindgen-test = "0.3.34"
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "@snk/solver-r",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"devDependencies": {
|
|
||||||
"wasm-pack": "0.13.1"
|
|
||||||
},
|
|
||||||
"main": "./pkg/snk_solver_rust.js",
|
|
||||||
"scripts": {
|
|
||||||
"build": "wasm-pack build"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
#[derive(Copy, Clone, Hash, Eq, PartialEq, Debug)]
|
|
||||||
pub struct Point {
|
|
||||||
pub x: i8,
|
|
||||||
pub y: i8,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
|
|
||||||
#[repr(u8)]
|
|
||||||
pub enum Cell {
|
|
||||||
Empty = 0,
|
|
||||||
Color1 = 1,
|
|
||||||
Color2 = 2,
|
|
||||||
Color3 = 3,
|
|
||||||
Color4 = 4,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct Grid {
|
|
||||||
pub width: u8,
|
|
||||||
pub height: u8,
|
|
||||||
pub cells: Vec<Cell>,
|
|
||||||
}
|
|
||||||
impl Grid {
|
|
||||||
pub fn create_empty(width: u8, height: u8) -> Grid {
|
|
||||||
let n = (width as usize) * (height as usize);
|
|
||||||
let cells = (0..n).map(|_| Cell::Empty).collect();
|
|
||||||
|
|
||||||
Grid {
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
cells,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_index(&self, x: i8, y: i8) -> usize {
|
|
||||||
return (x as usize) * (self.height as usize) + (y as usize);
|
|
||||||
}
|
|
||||||
pub fn get_cell(&self, p: &Point) -> Cell {
|
|
||||||
let i = self.get_index(p.x, p.y);
|
|
||||||
return self.cells[i];
|
|
||||||
}
|
|
||||||
pub fn set_cell(&mut self, p: &Point, value: Cell) -> () {
|
|
||||||
let i = self.get_index(p.x, p.y);
|
|
||||||
self.cells[i] = value;
|
|
||||||
}
|
|
||||||
pub fn is_inside(&self, p: &Point) -> bool {
|
|
||||||
p.x >= 0 && p.x < (self.width as i8) && p.y >= 0 && p.y < (self.height as i8)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const DIRECTION_RIGHT: Point = Point { x: 1, y: 0 };
|
|
||||||
pub const DIRECTION_LEFT: Point = Point { x: -1, y: 0 };
|
|
||||||
pub const DIRECTION_UP: Point = Point { x: 0, y: 1 };
|
|
||||||
pub const DIRECTION_DOWN: Point = Point { x: 0, y: -1 };
|
|
||||||
pub const DIRECTIONS: [Point; 4] = [
|
|
||||||
DIRECTION_RIGHT,
|
|
||||||
DIRECTION_LEFT,
|
|
||||||
DIRECTION_UP,
|
|
||||||
DIRECTION_DOWN,
|
|
||||||
];
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn it_should_sort_cell() {
|
|
||||||
assert_eq!(Cell::Empty < Cell::Color1, true);
|
|
||||||
assert_eq!(Cell::Color1 < Cell::Color2, true);
|
|
||||||
assert_eq!(Cell::Color2 < Cell::Color3, true);
|
|
||||||
assert_eq!(Cell::Color3 < Cell::Color4, true);
|
|
||||||
}
|
|
||||||
#[test]
|
|
||||||
fn it_should_grid_create() {
|
|
||||||
let grid = Grid::create_empty(30, 10);
|
|
||||||
|
|
||||||
assert_eq!(grid.width, 30);
|
|
||||||
assert_eq!(grid.height, 10);
|
|
||||||
assert_eq!(grid.get_cell(&Point { x: 2, y: 3 }), Cell::Empty);
|
|
||||||
}
|
|
||||||
#[test]
|
|
||||||
fn it_should_grid_setter() {
|
|
||||||
let mut grid = Grid::create_empty(20, 10);
|
|
||||||
|
|
||||||
grid.set_cell(&Point { x: 12, y: 3 }, Cell::Color1);
|
|
||||||
|
|
||||||
assert_eq!(grid.get_cell(&Point { x: 12, y: 3 }), Cell::Color1);
|
|
||||||
}
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
mod grid;
|
|
||||||
mod snake;
|
|
||||||
mod snake_compact;
|
|
||||||
mod snake_walk;
|
|
||||||
mod solver;
|
|
||||||
|
|
||||||
use grid::{Cell, Grid};
|
|
||||||
use js_sys;
|
|
||||||
use solver::get_free_cell;
|
|
||||||
use wasm_bindgen::prelude::*;
|
|
||||||
|
|
||||||
#[wasm_bindgen]
|
|
||||||
extern "C" {
|
|
||||||
fn alert(s: &str);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen]
|
|
||||||
pub fn greet() {
|
|
||||||
alert("Hello, wasm-game-of-life!");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen]
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct IGrid {
|
|
||||||
pub width: u8,
|
|
||||||
pub height: u8,
|
|
||||||
cells: Vec<Cell>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen]
|
|
||||||
impl IGrid {
|
|
||||||
pub fn create(width: u8, height: u8, data: js_sys::Uint8Array) -> IGrid {
|
|
||||||
let cells = data
|
|
||||||
.to_vec()
|
|
||||||
.iter()
|
|
||||||
.map(|u| match u {
|
|
||||||
0 => Cell::Empty,
|
|
||||||
1 => Cell::Color1,
|
|
||||||
2 => Cell::Color2,
|
|
||||||
3 => Cell::Color3,
|
|
||||||
4 => Cell::Color4,
|
|
||||||
_ => panic!("unknown cell"),
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
IGrid {
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
cells,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen(getter)]
|
|
||||||
pub fn data(&self) -> js_sys::Uint8Array {
|
|
||||||
let o: Vec<u8> = self.cells.iter().map(|u| *u as u8).collect();
|
|
||||||
js_sys::Uint8Array::from(&o[..])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<IGrid> for Grid {
|
|
||||||
fn from(value: IGrid) -> Self {
|
|
||||||
Self {
|
|
||||||
width: value.width,
|
|
||||||
height: value.height,
|
|
||||||
cells: value.cells,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen]
|
|
||||||
pub fn iget_free_cell(grid: &IGrid) -> js_sys::Uint8Array {
|
|
||||||
let g = Grid::from(grid.clone());
|
|
||||||
|
|
||||||
let (_, out) = get_free_cell(&g, Cell::Color1);
|
|
||||||
|
|
||||||
let o: Vec<u8> = out.iter().flat_map(|p| [p.x as u8, p.y as u8]).collect();
|
|
||||||
|
|
||||||
js_sys::Uint8Array::from(&o[..])
|
|
||||||
}
|
|
||||||
@@ -1,95 +0,0 @@
|
|||||||
use crate::grid::{Point, DIRECTIONS, DIRECTION_DOWN, DIRECTION_LEFT, DIRECTION_UP};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* head is at 0
|
|
||||||
*/
|
|
||||||
pub type Snake = Vec<Point>;
|
|
||||||
|
|
||||||
pub fn move_snake(s: &mut Snake, dir: &Point) -> () {
|
|
||||||
let mut e = s.pop().unwrap();
|
|
||||||
e.x = s[0].x + dir.x;
|
|
||||||
e.y = s[0].y + dir.y;
|
|
||||||
s.insert(0, e);
|
|
||||||
}
|
|
||||||
pub fn snake_will_self_collide(s: &Snake, dir: &Point) -> bool {
|
|
||||||
let next_head = Point {
|
|
||||||
x: s[0].x + dir.x,
|
|
||||||
y: s[0].y + dir.y,
|
|
||||||
};
|
|
||||||
|
|
||||||
(&s[0..(s.len() - 1)]).contains(&next_head)
|
|
||||||
}
|
|
||||||
pub fn get_snake_head(s: &Snake) -> Point {
|
|
||||||
s[0]
|
|
||||||
}
|
|
||||||
pub fn get_next_snake_head(s: &Snake, dir: &Point) -> Point {
|
|
||||||
Point {
|
|
||||||
x: s[0].x + dir.x,
|
|
||||||
y: s[0].y + dir.y,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn it_should_return_head() {
|
|
||||||
let s = vec![
|
|
||||||
//
|
|
||||||
Point { x: 3, y: 0 },
|
|
||||||
Point { x: 2, y: 0 },
|
|
||||||
Point { x: 1, y: 0 },
|
|
||||||
];
|
|
||||||
|
|
||||||
assert_eq!(get_snake_head(&s), Point { x: 3, y: 0 });
|
|
||||||
}
|
|
||||||
#[test]
|
|
||||||
fn it_should_detect_self_collide() {
|
|
||||||
let mut s = vec![
|
|
||||||
//
|
|
||||||
Point { x: 6, y: 0 },
|
|
||||||
Point { x: 5, y: 0 },
|
|
||||||
Point { x: 4, y: 0 },
|
|
||||||
Point { x: 3, y: 0 },
|
|
||||||
Point { x: 2, y: 0 },
|
|
||||||
Point { x: 1, y: 0 },
|
|
||||||
];
|
|
||||||
|
|
||||||
move_snake(&mut s, &DIRECTION_UP);
|
|
||||||
move_snake(&mut s, &DIRECTION_LEFT);
|
|
||||||
|
|
||||||
assert_eq!(snake_will_self_collide(&s, &DIRECTION_DOWN), true);
|
|
||||||
|
|
||||||
move_snake(&mut s, &DIRECTION_LEFT);
|
|
||||||
|
|
||||||
assert_eq!(snake_will_self_collide(&s, &DIRECTION_DOWN), false);
|
|
||||||
}
|
|
||||||
#[test]
|
|
||||||
fn it_should_detect_self_collide_2() {
|
|
||||||
let s = vec![
|
|
||||||
//
|
|
||||||
Point { x: 3, y: 0 },
|
|
||||||
Point { x: 2, y: 0 },
|
|
||||||
Point { x: 1, y: 0 },
|
|
||||||
];
|
|
||||||
|
|
||||||
assert_eq!(snake_will_self_collide(&s, &DIRECTION_LEFT), true);
|
|
||||||
}
|
|
||||||
#[test]
|
|
||||||
fn it_should_move_snake() {
|
|
||||||
let mut s = vec![
|
|
||||||
//
|
|
||||||
Point { x: 3, y: 0 },
|
|
||||||
Point { x: 2, y: 0 },
|
|
||||||
Point { x: 1, y: 0 },
|
|
||||||
];
|
|
||||||
|
|
||||||
move_snake(&mut s, &DIRECTION_UP);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
s,
|
|
||||||
vec![
|
|
||||||
//
|
|
||||||
Point { x: 3, y: 1 },
|
|
||||||
Point { x: 3, y: 0 },
|
|
||||||
Point { x: 2, y: 0 },
|
|
||||||
]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,176 +0,0 @@
|
|||||||
use crate::grid::Point;
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
|
|
||||||
pub enum Direction {
|
|
||||||
Left = 0,
|
|
||||||
Right = 1,
|
|
||||||
Up = 2,
|
|
||||||
Down = 3,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_direction_vector(dir: Direction) -> Point {
|
|
||||||
match dir {
|
|
||||||
Direction::Down => Point { x: 0, y: -1 },
|
|
||||||
Direction::Up => Point { x: 0, y: 1 },
|
|
||||||
Direction::Left => Point { x: -1, y: 0 },
|
|
||||||
Direction::Right => Point { x: 1, y: 0 },
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn get_direction_from_vector(v: &Point) -> Direction {
|
|
||||||
match v {
|
|
||||||
Point { x: 0, y: -1 } => Direction::Down,
|
|
||||||
Point { x: 0, y: 1 } => Direction::Up,
|
|
||||||
Point { x: -1, y: 0 } => Direction::Left,
|
|
||||||
Point { x: 1, y: 0 } => Direction::Right,
|
|
||||||
_ => panic!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct SnakeC {
|
|
||||||
pub head: Point,
|
|
||||||
pub body: Vec<Direction>,
|
|
||||||
}
|
|
||||||
impl SnakeC {
|
|
||||||
pub fn get_cells(&self) -> Vec<Point> {
|
|
||||||
let mut e = self.head.clone();
|
|
||||||
let mut out = Vec::new();
|
|
||||||
|
|
||||||
out.push(e.clone());
|
|
||||||
for dir in self.body.iter() {
|
|
||||||
let v = get_direction_vector(*dir);
|
|
||||||
e.x -= v.x;
|
|
||||||
e.y -= v.y;
|
|
||||||
out.push(e.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
out
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_head_self_colliding(&self) -> bool {
|
|
||||||
self.get_cells()[1..].contains(&self.head)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn advance(&mut self, dir: Direction) -> () {
|
|
||||||
let v = get_direction_vector(dir);
|
|
||||||
|
|
||||||
self.head.x += v.x;
|
|
||||||
self.head.y += v.y;
|
|
||||||
|
|
||||||
self.body.pop();
|
|
||||||
self.body.insert(0, dir);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Vec<Point>> for SnakeC {
|
|
||||||
fn from(value: Vec<Point>) -> Self {
|
|
||||||
let head = value.get(0).unwrap().clone();
|
|
||||||
let body = value
|
|
||||||
.windows(2)
|
|
||||||
.map(|w| {
|
|
||||||
let v = Point {
|
|
||||||
x: w[0].x - w[1].x,
|
|
||||||
y: w[0].y - w[1].y,
|
|
||||||
};
|
|
||||||
get_direction_from_vector(&v)
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
Self { head, body }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn it_should_get_the_snake_cell() {
|
|
||||||
let s = SnakeC {
|
|
||||||
head: Point { x: 10, y: 5 },
|
|
||||||
body: vec![Direction::Up, Direction::Up, Direction::Left],
|
|
||||||
};
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
s.get_cells(),
|
|
||||||
vec![
|
|
||||||
//
|
|
||||||
Point { x: 10, y: 5 },
|
|
||||||
Point { x: 10, y: 4 },
|
|
||||||
Point { x: 10, y: 3 },
|
|
||||||
Point { x: 11, y: 3 },
|
|
||||||
]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn it_should_get_snake_from_point_list() {
|
|
||||||
let s = SnakeC::from(vec![
|
|
||||||
//
|
|
||||||
Point { x: 10, y: 5 },
|
|
||||||
Point { x: 10, y: 4 },
|
|
||||||
Point { x: 10, y: 3 },
|
|
||||||
Point { x: 11, y: 3 },
|
|
||||||
Point { x: 12, y: 3 },
|
|
||||||
Point { x: 12, y: 2 },
|
|
||||||
]);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
s.get_cells(),
|
|
||||||
vec![
|
|
||||||
//
|
|
||||||
Point { x: 10, y: 5 },
|
|
||||||
Point { x: 10, y: 4 },
|
|
||||||
Point { x: 10, y: 3 },
|
|
||||||
Point { x: 11, y: 3 },
|
|
||||||
Point { x: 12, y: 3 },
|
|
||||||
Point { x: 12, y: 2 },
|
|
||||||
]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn it_should_advance_snake() {
|
|
||||||
let mut s = SnakeC::from(vec![
|
|
||||||
//
|
|
||||||
Point { x: 10, y: 3 },
|
|
||||||
Point { x: 11, y: 3 },
|
|
||||||
Point { x: 12, y: 3 },
|
|
||||||
Point { x: 12, y: 2 },
|
|
||||||
]);
|
|
||||||
|
|
||||||
s.advance(Direction::Up);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
s.get_cells(),
|
|
||||||
vec![
|
|
||||||
//
|
|
||||||
Point { x: 10, y: 4 },
|
|
||||||
Point { x: 10, y: 3 },
|
|
||||||
Point { x: 11, y: 3 },
|
|
||||||
Point { x: 12, y: 3 },
|
|
||||||
]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn it_should_detect_self_collision() {
|
|
||||||
let mut s = SnakeC::from(vec![
|
|
||||||
//
|
|
||||||
Point { x: 0, y: 0 },
|
|
||||||
Point { x: 0, y: 1 },
|
|
||||||
Point { x: 0, y: 2 },
|
|
||||||
Point { x: 0, y: 3 },
|
|
||||||
Point { x: 0, y: 4 },
|
|
||||||
Point { x: 0, y: 5 },
|
|
||||||
Point { x: 0, y: 6 },
|
|
||||||
]);
|
|
||||||
|
|
||||||
assert_eq!(s.is_head_self_colliding(), false);
|
|
||||||
|
|
||||||
s.advance(Direction::Right);
|
|
||||||
s.advance(Direction::Up);
|
|
||||||
s.advance(Direction::Up);
|
|
||||||
|
|
||||||
assert_eq!(s.is_head_self_colliding(), false);
|
|
||||||
|
|
||||||
s.advance(Direction::Left);
|
|
||||||
|
|
||||||
assert_eq!(s.is_head_self_colliding(), true);
|
|
||||||
}
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
use std::collections::HashSet;
|
|
||||||
|
|
||||||
use crate::grid::{Cell, Grid, Point, DIRECTIONS};
|
|
||||||
use crate::snake::{
|
|
||||||
get_next_snake_head, get_snake_head, move_snake, snake_will_self_collide, Snake,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn get_route_to_eat_all(
|
|
||||||
grid: &Grid,
|
|
||||||
walkable: Cell,
|
|
||||||
initial_snake: &Snake,
|
|
||||||
cells_to_eat: HashSet<Point>,
|
|
||||||
) -> Vec<Point> {
|
|
||||||
// let mut targets: Vec<Point> = cells_to_eat.iter().map(|p| p.clone()).collect();
|
|
||||||
|
|
||||||
let mut targets: Vec<&Point> = cells_to_eat.iter().collect();
|
|
||||||
|
|
||||||
let mut path: Vec<Point> = Vec::new();
|
|
||||||
|
|
||||||
let mut initial_snake = initial_snake.clone();
|
|
||||||
|
|
||||||
while let Some(target) = targets.pop() {
|
|
||||||
// prepare
|
|
||||||
let mut open_list: HashSet<(Snake, Vec<Point>)> = HashSet::new();
|
|
||||||
open_list.insert((initial_snake.clone(), Vec::new()));
|
|
||||||
|
|
||||||
while let Some(x) = open_list.iter().next().cloned() {
|
|
||||||
open_list.remove(&x);
|
|
||||||
|
|
||||||
let snake = x.0;
|
|
||||||
let mut sub_path = x.1;
|
|
||||||
|
|
||||||
if get_snake_head(&snake) == *target {
|
|
||||||
path.append(&mut sub_path);
|
|
||||||
initial_snake = snake;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
for dir in DIRECTIONS {
|
|
||||||
if {
|
|
||||||
let h = get_next_snake_head(&snake, &dir);
|
|
||||||
grid.get_cell(&h) <= walkable
|
|
||||||
} && !snake_will_self_collide(&snake, &dir)
|
|
||||||
{
|
|
||||||
let mut next_snake = snake.clone();
|
|
||||||
move_snake(&mut next_snake, &dir);
|
|
||||||
|
|
||||||
let mut next_sub_path = sub_path.clone();
|
|
||||||
next_sub_path.push(dir.clone());
|
|
||||||
|
|
||||||
open_list.insert((next_snake, next_sub_path));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
path
|
|
||||||
}
|
|
||||||
@@ -1,127 +0,0 @@
|
|||||||
use std::collections::HashSet;
|
|
||||||
|
|
||||||
use crate::grid::{Cell, Grid, Point};
|
|
||||||
|
|
||||||
pub fn get_free_cell(grid: &Grid, walkable: Cell) -> (HashSet<Point>, HashSet<Point>) {
|
|
||||||
let mut free_cells: HashSet<Point> = HashSet::new();
|
|
||||||
let mut one_way_cells: HashSet<Point> = HashSet::new();
|
|
||||||
let mut open_list: HashSet<Point> = HashSet::new();
|
|
||||||
|
|
||||||
for x in 0..(grid.width as i8) {
|
|
||||||
open_list.insert(Point { x, y: 0 });
|
|
||||||
open_list.insert(Point {
|
|
||||||
x,
|
|
||||||
y: (grid.height as i8) - 1,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
for y in 0..(grid.height as i8) {
|
|
||||||
open_list.insert(Point { x: 0, y });
|
|
||||||
open_list.insert(Point {
|
|
||||||
x: (grid.width as i8) - 1,
|
|
||||||
y,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
open_list.retain(|p| grid.get_cell(&p) <= walkable);
|
|
||||||
|
|
||||||
let directions = [
|
|
||||||
Point { x: 1, y: 0 },
|
|
||||||
Point { x: -1, y: 0 },
|
|
||||||
Point { x: 0, y: 1 },
|
|
||||||
Point { x: 0, y: -1 },
|
|
||||||
];
|
|
||||||
|
|
||||||
while let Some(p) = open_list.iter().next().cloned() {
|
|
||||||
open_list.remove(&p);
|
|
||||||
|
|
||||||
let has_enough_free_exits = {
|
|
||||||
let mut exit_count = 0;
|
|
||||||
let mut visited: HashSet<Point> = HashSet::new();
|
|
||||||
|
|
||||||
for dir in directions {
|
|
||||||
let neighbour = Point {
|
|
||||||
x: p.x + dir.x,
|
|
||||||
y: p.y + dir.y,
|
|
||||||
};
|
|
||||||
|
|
||||||
if !visited.contains(&neighbour)
|
|
||||||
&& (free_cells.contains(&neighbour) || !grid.is_inside(&neighbour))
|
|
||||||
{
|
|
||||||
visited.insert(neighbour);
|
|
||||||
exit_count += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if grid.is_inside(&neighbour) && grid.get_cell(&neighbour) <= walkable {
|
|
||||||
for alt in [-1, 1] {
|
|
||||||
let corner = {
|
|
||||||
if neighbour.x != 0 {
|
|
||||||
Point {
|
|
||||||
x: neighbour.x,
|
|
||||||
y: neighbour.y + alt,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Point {
|
|
||||||
x: neighbour.x + alt,
|
|
||||||
y: neighbour.y,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if !visited.contains(&neighbour)
|
|
||||||
&& !visited.contains(&corner)
|
|
||||||
&& (free_cells.contains(&corner) || !grid.is_inside(&corner))
|
|
||||||
{
|
|
||||||
visited.insert(neighbour);
|
|
||||||
visited.insert(corner);
|
|
||||||
exit_count += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
exit_count >= 2
|
|
||||||
};
|
|
||||||
|
|
||||||
if has_enough_free_exits {
|
|
||||||
free_cells.insert(p);
|
|
||||||
|
|
||||||
for dir in directions {
|
|
||||||
let neighbour = Point {
|
|
||||||
x: p.x + dir.x,
|
|
||||||
y: p.y + dir.y,
|
|
||||||
};
|
|
||||||
|
|
||||||
if !free_cells.contains(&neighbour)
|
|
||||||
&& grid.is_inside(&neighbour)
|
|
||||||
&& grid.get_cell(&neighbour) <= walkable
|
|
||||||
{
|
|
||||||
open_list.insert(neighbour);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
one_way_cells.insert(p);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
one_way_cells.retain(|p| !free_cells.contains(&p));
|
|
||||||
|
|
||||||
(free_cells, one_way_cells)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn it_should_collect_free_cell() {
|
|
||||||
let mut grid = Grid::create_empty(2, 2);
|
|
||||||
|
|
||||||
grid.set_cell(&Point { x: 1, y: 1 }, Cell::Color2);
|
|
||||||
|
|
||||||
let (free_cells, _) = get_free_cell(&grid, Cell::Color1);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
free_cells,
|
|
||||||
HashSet::from([
|
|
||||||
//
|
|
||||||
Point { x: 0, y: 0 },
|
|
||||||
Point { x: 0, y: 1 },
|
|
||||||
Point { x: 1, y: 0 },
|
|
||||||
])
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
import { it, expect } from "bun:test";
|
|
||||||
import { getBestRoute } from "../getBestRoute";
|
import { getBestRoute } from "../getBestRoute";
|
||||||
import { snake3, snake4 } from "@snk/types/__fixtures__/snake";
|
import { snake3, snake4 } from "@snk/types/__fixtures__/snake";
|
||||||
import {
|
import {
|
||||||
@@ -17,7 +16,7 @@ for (const { width, height, snake } of [
|
|||||||
{ width: 5, height: 5, snake: snake4 },
|
{ width: 5, height: 5, snake: snake4 },
|
||||||
])
|
])
|
||||||
it(`should find solution for ${n} ${width}x${height} generated grids for ${getSnakeLength(
|
it(`should find solution for ${n} ${width}x${height} generated grids for ${getSnakeLength(
|
||||||
snake,
|
snake
|
||||||
)} length snake`, () => {
|
)} length snake`, () => {
|
||||||
const results = Array.from({ length: n }, (_, seed) => {
|
const results = Array.from({ length: n }, (_, seed) => {
|
||||||
const grid = createFromSeed(seed, width, height);
|
const grid = createFromSeed(seed, width, height);
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { it, expect } from "bun:test";
|
|
||||||
import { getBestRoute } from "../getBestRoute";
|
import { getBestRoute } from "../getBestRoute";
|
||||||
import { Color, createEmptyGrid, setColor } from "@snk/types/grid";
|
import { Color, createEmptyGrid, setColor } from "@snk/types/grid";
|
||||||
import { createSnakeFromCells, snakeToCells } from "@snk/types/snake";
|
import { createSnakeFromCells, snakeToCells } from "@snk/types/snake";
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { it, expect } from "bun:test";
|
|
||||||
import { createEmptyGrid } from "@snk/types/grid";
|
import { createEmptyGrid } from "@snk/types/grid";
|
||||||
import { getHeadX, getHeadY } from "@snk/types/snake";
|
import { getHeadX, getHeadY } from "@snk/types/snake";
|
||||||
import { snake3 } from "@snk/types/__fixtures__/snake";
|
import { snake3 } from "@snk/types/__fixtures__/snake";
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { it, expect } from "bun:test";
|
|
||||||
import { createSnakeFromCells } from "@snk/types/snake";
|
import { createSnakeFromCells } from "@snk/types/snake";
|
||||||
import { getPathToPose } from "../getPathToPose";
|
import { getPathToPose } from "../getPathToPose";
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { it, expect, describe } from "bun:test";
|
|
||||||
import { sortPush } from "../utils/sortPush";
|
import { sortPush } from "../utils/sortPush";
|
||||||
|
|
||||||
const sortFn = (a: number, b: number) => a - b;
|
const sortFn = (a: number, b: number) => a - b;
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export const clearCleanColoredLayer = (
|
|||||||
grid: Grid,
|
grid: Grid,
|
||||||
outside: Outside,
|
outside: Outside,
|
||||||
snake0: Snake,
|
snake0: Snake,
|
||||||
color: Color,
|
color: Color
|
||||||
) => {
|
) => {
|
||||||
const snakeN = getSnakeLength(snake0);
|
const snakeN = getSnakeLength(snake0);
|
||||||
|
|
||||||
@@ -55,7 +55,7 @@ const getPathToNextPoint = (
|
|||||||
grid: Grid,
|
grid: Grid,
|
||||||
snake0: Snake,
|
snake0: Snake,
|
||||||
color: Color,
|
color: Color,
|
||||||
points: Point[],
|
points: Point[]
|
||||||
) => {
|
) => {
|
||||||
const closeList: Snake[] = [];
|
const closeList: Snake[] = [];
|
||||||
const openList: M[] = [{ snake: snake0 } as any];
|
const openList: M[] = [{ snake: snake0 } as any];
|
||||||
@@ -96,7 +96,7 @@ export const getTunnellablePoints = (
|
|||||||
grid: Grid,
|
grid: Grid,
|
||||||
outside: Outside,
|
outside: Outside,
|
||||||
snakeN: number,
|
snakeN: number,
|
||||||
color: Color,
|
color: Color
|
||||||
) => {
|
) => {
|
||||||
const points: Point[] = [];
|
const points: Point[] = [];
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ export const clearResidualColoredLayer = (
|
|||||||
grid: Grid,
|
grid: Grid,
|
||||||
outside: Outside,
|
outside: Outside,
|
||||||
snake0: Snake,
|
snake0: Snake,
|
||||||
color: Color,
|
color: Color
|
||||||
) => {
|
) => {
|
||||||
const snakeN = getSnakeLength(snake0);
|
const snakeN = getSnakeLength(snake0);
|
||||||
|
|
||||||
@@ -99,7 +99,7 @@ export const getTunnellablePoints = (
|
|||||||
grid: Grid,
|
grid: Grid,
|
||||||
outside: Outside,
|
outside: Outside,
|
||||||
snakeN: number,
|
snakeN: number,
|
||||||
color: Color,
|
color: Color
|
||||||
) => {
|
) => {
|
||||||
const points: T[] = [];
|
const points: T[] = [];
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export const getBestRoute = (grid0: Grid, snake0: Snake) => {
|
|||||||
for (const color of extractColors(grid)) {
|
for (const color of extractColors(grid)) {
|
||||||
if (color > 1)
|
if (color > 1)
|
||||||
chain.unshift(
|
chain.unshift(
|
||||||
...clearResidualColoredLayer(grid, outside, chain[0], color),
|
...clearResidualColoredLayer(grid, outside, chain[0], color)
|
||||||
);
|
);
|
||||||
chain.unshift(...clearCleanColoredLayer(grid, outside, chain[0], color));
|
chain.unshift(...clearCleanColoredLayer(grid, outside, chain[0], color));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ const getSnakeEscapePath = (
|
|||||||
grid: Grid,
|
grid: Grid,
|
||||||
outside: Outside,
|
outside: Outside,
|
||||||
snake0: Snake,
|
snake0: Snake,
|
||||||
color: Color,
|
color: Color
|
||||||
) => {
|
) => {
|
||||||
const openList: M[] = [{ snake: snake0, w: 0 } as any];
|
const openList: M[] = [{ snake: snake0, w: 0 } as any];
|
||||||
const closeList: Snake[] = [];
|
const closeList: Snake[] = [];
|
||||||
@@ -79,7 +79,7 @@ export const getBestTunnel = (
|
|||||||
x: number,
|
x: number,
|
||||||
y: number,
|
y: number,
|
||||||
color: Color,
|
color: Color,
|
||||||
snakeN: number,
|
snakeN: number
|
||||||
) => {
|
) => {
|
||||||
const c = { x, y };
|
const c = { x, y };
|
||||||
const snake0 = createSnakeFromCells(Array.from({ length: snakeN }, () => c));
|
const snake0 = createSnakeFromCells(Array.from({ length: snakeN }, () => c));
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export const createOutside = (grid: Grid, color: Color = 0 as Color) => {
|
|||||||
export const fillOutside = (
|
export const fillOutside = (
|
||||||
outside: Outside,
|
outside: Outside,
|
||||||
grid: Grid,
|
grid: Grid,
|
||||||
color: Color = 0 as Color,
|
color: Color = 0 as Color
|
||||||
) => {
|
) => {
|
||||||
let changed = true;
|
let changed = true;
|
||||||
while (changed) {
|
while (changed) {
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export const getTunnelPath = (snake0: Snake, tunnel: Point[]) => {
|
|||||||
export const updateTunnel = (
|
export const updateTunnel = (
|
||||||
grid: Grid,
|
grid: Grid,
|
||||||
tunnel: Point[],
|
tunnel: Point[],
|
||||||
toDelete: Point[],
|
toDelete: Point[]
|
||||||
) => {
|
) => {
|
||||||
while (tunnel.length) {
|
while (tunnel.length) {
|
||||||
const { x, y } = tunnel[0];
|
const { x, y } = tunnel[0];
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { it, expect } from "bun:test";
|
|
||||||
import * as fs from "fs";
|
import * as fs from "fs";
|
||||||
import * as path from "path";
|
import * as path from "path";
|
||||||
import { createSvg, DrawOptions as DrawOptions } from "..";
|
import { createSvg, DrawOptions as DrawOptions } from "..";
|
||||||
@@ -38,7 +37,7 @@ for (const [key, grid] of Object.entries(grids))
|
|||||||
null,
|
null,
|
||||||
chain,
|
chain,
|
||||||
drawOptions,
|
drawOptions,
|
||||||
animationOptions,
|
animationOptions
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(svg).toBeDefined();
|
expect(svg).toBeDefined();
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { it, expect } from "bun:test";
|
|
||||||
import { minifyCss } from "../css-utils";
|
import { minifyCss } from "../css-utils";
|
||||||
|
|
||||||
it("should minify css", () => {
|
it("should minify css", () => {
|
||||||
@@ -7,8 +6,8 @@ it("should minify css", () => {
|
|||||||
.c {
|
.c {
|
||||||
color : red ;
|
color : red ;
|
||||||
}
|
}
|
||||||
|
|
||||||
`),
|
`)
|
||||||
).toBe(".c{color:red}");
|
).toBe(".c{color:red}");
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
@@ -18,10 +17,10 @@ it("should minify css", () => {
|
|||||||
color : red ;
|
color : red ;
|
||||||
}
|
}
|
||||||
|
|
||||||
# {
|
# {
|
||||||
animation: linear 10;
|
animation: linear 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
`),
|
`)
|
||||||
).toBe(".c{top:0;color:red}#{animation:linear 10}");
|
).toBe(".c{top:0;color:red}#{animation:linear 10}");
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ const mergeKeyFrames = (keyframes: { t: number; style: string }[]) => {
|
|||||||
*/
|
*/
|
||||||
export const createAnimation = (
|
export const createAnimation = (
|
||||||
name: string,
|
name: string,
|
||||||
keyframes: { t: number; style: string }[],
|
keyframes: { t: number; style: string }[]
|
||||||
) =>
|
) =>
|
||||||
`@keyframes ${name}{` +
|
`@keyframes ${name}{` +
|
||||||
mergeKeyFrames(keyframes)
|
mergeKeyFrames(keyframes)
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export type Options = {
|
|||||||
export const createGrid = (
|
export const createGrid = (
|
||||||
cells: (Point & { t: number | null; color: Color | Empty })[],
|
cells: (Point & { t: number | null; color: Color | Empty })[],
|
||||||
{ sizeDotBorderRadius, sizeDot, sizeCell }: Options,
|
{ sizeDotBorderRadius, sizeDot, sizeCell }: Options,
|
||||||
duration: number,
|
duration: number
|
||||||
) => {
|
) => {
|
||||||
const svgElements: string[] = [];
|
const svgElements: string[] = [];
|
||||||
const styles = [
|
const styles = [
|
||||||
@@ -48,7 +48,7 @@ export const createGrid = (
|
|||||||
`.c.${id}{
|
`.c.${id}{
|
||||||
fill: var(--c${color});
|
fill: var(--c${color});
|
||||||
animation-name: ${animationName}
|
animation-name: ${animationName}
|
||||||
}`,
|
}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,7 +59,7 @@ export const createGrid = (
|
|||||||
y: y * sizeCell + m,
|
y: y * sizeCell + m,
|
||||||
rx: sizeDotBorderRadius,
|
rx: sizeDotBorderRadius,
|
||||||
ry: sizeDotBorderRadius,
|
ry: sizeDotBorderRadius,
|
||||||
}),
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -34,13 +34,13 @@ export type DrawOptions = {
|
|||||||
|
|
||||||
const getCellsFromGrid = ({ width, height }: Grid) =>
|
const getCellsFromGrid = ({ width, height }: Grid) =>
|
||||||
Array.from({ length: width }, (_, x) =>
|
Array.from({ length: width }, (_, x) =>
|
||||||
Array.from({ length: height }, (_, y) => ({ x, y })),
|
Array.from({ length: height }, (_, y) => ({ x, y }))
|
||||||
).flat();
|
).flat();
|
||||||
|
|
||||||
const createLivingCells = (
|
const createLivingCells = (
|
||||||
grid0: Grid,
|
grid0: Grid,
|
||||||
chain: Snake[],
|
chain: Snake[],
|
||||||
cells: Point[] | null,
|
cells: Point[] | null
|
||||||
) => {
|
) => {
|
||||||
const livingCells: (Point & {
|
const livingCells: (Point & {
|
||||||
t: number | null;
|
t: number | null;
|
||||||
@@ -73,7 +73,7 @@ export const createSvg = (
|
|||||||
cells: Point[] | null,
|
cells: Point[] | null,
|
||||||
chain: Snake[],
|
chain: Snake[],
|
||||||
drawOptions: DrawOptions,
|
drawOptions: DrawOptions,
|
||||||
animationOptions: Pick<AnimationOptions, "frameDuration">,
|
animationOptions: Pick<AnimationOptions, "frameDuration">
|
||||||
) => {
|
) => {
|
||||||
const width = (grid.width + 2) * drawOptions.sizeCell;
|
const width = (grid.width + 2) * drawOptions.sizeCell;
|
||||||
const height = (grid.height + 5) * drawOptions.sizeCell;
|
const height = (grid.height + 5) * drawOptions.sizeCell;
|
||||||
@@ -89,7 +89,7 @@ export const createSvg = (
|
|||||||
drawOptions,
|
drawOptions,
|
||||||
grid.width * drawOptions.sizeCell,
|
grid.width * drawOptions.sizeCell,
|
||||||
(grid.height + 2) * drawOptions.sizeCell,
|
(grid.height + 2) * drawOptions.sizeCell,
|
||||||
duration,
|
duration
|
||||||
),
|
),
|
||||||
createSnake(chain, drawOptions, duration),
|
createSnake(chain, drawOptions, duration),
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -3,5 +3,6 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@snk/solver": "1.0.0"
|
"@snk/solver": "1.0.0"
|
||||||
}
|
},
|
||||||
|
"devDependencies": {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ const lerp = (k: number, a: number, b: number) => (1 - k) * a + k * b;
|
|||||||
export const createSnake = (
|
export const createSnake = (
|
||||||
chain: Snake[],
|
chain: Snake[],
|
||||||
{ sizeCell, sizeDot }: Options,
|
{ sizeCell, sizeDot }: Options,
|
||||||
duration: number,
|
duration: number
|
||||||
) => {
|
) => {
|
||||||
const snakeN = chain[0] ? getSnakeLength(chain[0]) : 0;
|
const snakeN = chain[0] ? getSnakeLength(chain[0]) : 0;
|
||||||
|
|
||||||
@@ -64,7 +64,7 @@ export const createSnake = (
|
|||||||
const animationName = id;
|
const animationName = id;
|
||||||
|
|
||||||
const keyframes = removeInterpolatedPositions(
|
const keyframes = removeInterpolatedPositions(
|
||||||
positions.map((tr, i, { length }) => ({ ...tr, t: i / length })),
|
positions.map((tr, i, { length }) => ({ ...tr, t: i / length }))
|
||||||
).map(({ t, ...p }) => ({ t, style: transform(p) }));
|
).map(({ t, ...p }) => ({ t, style: transform(p) }));
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export const createStack = (
|
|||||||
{ sizeDot }: Options,
|
{ sizeDot }: Options,
|
||||||
width: number,
|
width: number,
|
||||||
y: number,
|
y: number,
|
||||||
duration: number,
|
duration: number
|
||||||
) => {
|
) => {
|
||||||
const svgElements: string[] = [];
|
const svgElements: string[] = [];
|
||||||
const styles = [
|
const styles = [
|
||||||
@@ -51,7 +51,7 @@ export const createStack = (
|
|||||||
width: (ts.length * m + 0.6).toFixed(1),
|
width: (ts.length * m + 0.6).toFixed(1),
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
}),
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
styles.push(
|
styles.push(
|
||||||
@@ -68,7 +68,7 @@ export const createStack = (
|
|||||||
].map(({ scale, t }) => ({
|
].map(({ scale, t }) => ({
|
||||||
t,
|
t,
|
||||||
style: `transform:scale(${scale.toFixed(3)},1)`,
|
style: `transform:scale(${scale.toFixed(3)},1)`,
|
||||||
})),
|
}))
|
||||||
),
|
),
|
||||||
|
|
||||||
`.u.${id} {
|
`.u.${id} {
|
||||||
@@ -76,7 +76,7 @@ export const createStack = (
|
|||||||
animation-name: ${animationName};
|
animation-name: ${animationName};
|
||||||
transform-origin: ${x}px 0
|
transform-origin: ${x}px 0
|
||||||
}
|
}
|
||||||
`,
|
`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -84,12 +84,6 @@ export const tunnels = createFromAscii(`
|
|||||||
#.# #.# #.#
|
#.# #.# #.#
|
||||||
#.# ### # #
|
#.# ### # #
|
||||||
`);
|
`);
|
||||||
export const line = createFromAscii(`
|
|
||||||
|
|
||||||
#######
|
|
||||||
.. #
|
|
||||||
##### #
|
|
||||||
`);
|
|
||||||
|
|
||||||
const createRandom = (width: number, height: number, emptyP: number) => {
|
const createRandom = (width: number, height: number, emptyP: number) => {
|
||||||
const grid = createEmptyGrid(width, height);
|
const grid = createEmptyGrid(width, height);
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
import { it, expect, test } from "bun:test";
|
|
||||||
import { createEmptyGrid, setColor, getColor, isInside, Color } from "../grid";
|
import { createEmptyGrid, setColor, getColor, isInside, Color } from "../grid";
|
||||||
|
|
||||||
it("should set / get cell", () => {
|
it("should set / get cell", () => {
|
||||||
const grid = createEmptyGrid(2, 3);
|
const grid = createEmptyGrid(2, 3);
|
||||||
|
|
||||||
expect(getColor(grid, 0, 1)).toBe(0 as any);
|
expect(getColor(grid, 0, 1)).toBe(0);
|
||||||
|
|
||||||
setColor(grid, 0, 1, 1 as Color);
|
setColor(grid, 0, 1, 1 as Color);
|
||||||
|
|
||||||
expect(getColor(grid, 0, 1)).toBe(1 as any);
|
expect(getColor(grid, 0, 1)).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
test.each([
|
test.each([
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { it, expect } from "bun:test";
|
|
||||||
import {
|
import {
|
||||||
createSnakeFromCells,
|
createSnakeFromCells,
|
||||||
nextSnake,
|
nextSnake,
|
||||||
@@ -30,7 +29,7 @@ it("should return next snake", () => {
|
|||||||
];
|
];
|
||||||
|
|
||||||
expect(snakeToCells(nextSnake(createSnakeFromCells(snk0), 1, 0))).toEqual(
|
expect(snakeToCells(nextSnake(createSnakeFromCells(snk0), 1, 0))).toEqual(
|
||||||
snk1,
|
snk1
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ export const setColor = (
|
|||||||
grid: Grid,
|
grid: Grid,
|
||||||
x: number,
|
x: number,
|
||||||
y: number,
|
y: number,
|
||||||
color: Color | Empty,
|
color: Color | Empty
|
||||||
) => {
|
) => {
|
||||||
grid.data[getIndex(grid, x, y)] = color || 0;
|
grid.data[getIndex(grid, x, y)] = color || 0;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export const randomlyFillGrid = (
|
|||||||
colors = [1, 2, 3] as Color[],
|
colors = [1, 2, 3] as Color[],
|
||||||
emptyP = 2,
|
emptyP = 2,
|
||||||
}: { colors?: Color[]; emptyP?: number } = {},
|
}: { colors?: Color[]; emptyP?: number } = {},
|
||||||
rand = defaultRand,
|
rand = defaultRand
|
||||||
) => {
|
) => {
|
||||||
for (let x = grid.width; x--; )
|
for (let x = grid.width; x--; )
|
||||||
for (let y = grid.height; y--; ) {
|
for (let y = grid.height; y--; ) {
|
||||||
|
|||||||
2
packages/usage-stats/.gitignore
vendored
Normal file
2
packages/usage-stats/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
.env
|
||||||
|
cache
|
||||||
53
packages/usage-stats/getDependentInfo-api.ts
Normal file
53
packages/usage-stats/getDependentInfo-api.ts
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import { Octokit } from "octokit";
|
||||||
|
import { httpGet } from "./httpGet";
|
||||||
|
|
||||||
|
require("dotenv").config();
|
||||||
|
|
||||||
|
const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN });
|
||||||
|
|
||||||
|
export const getLastRunInfo = async (repo_: string) => {
|
||||||
|
const [owner, repo] = repo_.split("/");
|
||||||
|
|
||||||
|
try {
|
||||||
|
const {
|
||||||
|
data: { workflow_runs },
|
||||||
|
} = await octokit.request(
|
||||||
|
"GET /repos/{owner}/{repo}/actions/runs{?actor,branch,event,status,per_page,page,created,exclude_pull_requests,check_suite_id,head_sha}",
|
||||||
|
{ owner, repo }
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const r of workflow_runs) {
|
||||||
|
const {
|
||||||
|
run_started_at: date,
|
||||||
|
head_sha,
|
||||||
|
path,
|
||||||
|
conclusion,
|
||||||
|
} = r as {
|
||||||
|
run_started_at: string;
|
||||||
|
head_sha: string;
|
||||||
|
path: string;
|
||||||
|
conclusion: "failure" | "success";
|
||||||
|
};
|
||||||
|
|
||||||
|
const workflow_url = `https://raw.githubusercontent.com/${owner}/${repo}/${head_sha}/${path}`;
|
||||||
|
|
||||||
|
const workflow_code = await httpGet(workflow_url);
|
||||||
|
|
||||||
|
const [_, dependency] =
|
||||||
|
workflow_code.match(/uses\s*:\s*(Platane\/snk(\/svg-only)?@\w*)/) ?? [];
|
||||||
|
|
||||||
|
const cronMatch = workflow_code.match(/cron\s*:([^\n]*)/);
|
||||||
|
|
||||||
|
if (dependency)
|
||||||
|
return {
|
||||||
|
dependency,
|
||||||
|
success: conclusion === "success",
|
||||||
|
date,
|
||||||
|
cron: cronMatch?.[1].replace(/["|']/g, "").trim(),
|
||||||
|
workflow_code,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
56
packages/usage-stats/getDependentInfo.ts
Normal file
56
packages/usage-stats/getDependentInfo.ts
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import { load as CheerioLoad } from "cheerio";
|
||||||
|
import { httpGet } from "./httpGet";
|
||||||
|
|
||||||
|
export const getDependentInfo = async (repo: string) => {
|
||||||
|
const pageText = await httpGet(`https://github.com/${repo}/actions`).catch(
|
||||||
|
() => null
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!pageText) return;
|
||||||
|
|
||||||
|
const $ = CheerioLoad(pageText);
|
||||||
|
|
||||||
|
const runs = $("#partial-actions-workflow-runs [data-url]")
|
||||||
|
.toArray()
|
||||||
|
.map((el) => {
|
||||||
|
const success =
|
||||||
|
$(el).find('[aria-label="completed successfully"]').toArray().length ===
|
||||||
|
1;
|
||||||
|
|
||||||
|
const workflow_file_href = $(el)
|
||||||
|
.find("a")
|
||||||
|
.toArray()
|
||||||
|
.map((el) => $(el).attr("href")!)
|
||||||
|
.find((href) => href.match(/\/actions\/runs\/\d+\/workflow/))!;
|
||||||
|
|
||||||
|
const workflow_file_url = workflow_file_href
|
||||||
|
? new URL(workflow_file_href, "https://github.com").toString()
|
||||||
|
: null;
|
||||||
|
|
||||||
|
const date = $(el).find("relative-time").attr("datetime");
|
||||||
|
|
||||||
|
return { success, workflow_file_url, date };
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const { workflow_file_url, success, date } of runs) {
|
||||||
|
if (!workflow_file_url) continue;
|
||||||
|
|
||||||
|
const $ = CheerioLoad(await httpGet(workflow_file_url));
|
||||||
|
|
||||||
|
const workflow_code = $("table[data-hpc]").text();
|
||||||
|
|
||||||
|
const [_, dependency] =
|
||||||
|
workflow_code.match(/uses\s*:\s*(Platane\/snk(\/svg-only)?@\w*)/) ?? [];
|
||||||
|
|
||||||
|
const cronMatch = workflow_code.match(/cron\s*:([^\n]*)/);
|
||||||
|
|
||||||
|
if (dependency)
|
||||||
|
return {
|
||||||
|
dependency,
|
||||||
|
success,
|
||||||
|
date,
|
||||||
|
cron: cronMatch?.[1].replace(/["|']/g, "").trim(),
|
||||||
|
workflow_code,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
67
packages/usage-stats/getDependents.ts
Normal file
67
packages/usage-stats/getDependents.ts
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
import { load as CheerioLoad } from "cheerio";
|
||||||
|
import { httpGet } from "./httpGet";
|
||||||
|
|
||||||
|
const getPackages = async (repo: string) => {
|
||||||
|
const pageText = await httpGet(
|
||||||
|
`https://github.com/${repo}/network/dependents`
|
||||||
|
);
|
||||||
|
const $ = CheerioLoad(pageText);
|
||||||
|
|
||||||
|
return $("#dependents .select-menu-list a")
|
||||||
|
.toArray()
|
||||||
|
.map((el) => {
|
||||||
|
const name = $(el).text().trim();
|
||||||
|
const href = $(el).attr("href");
|
||||||
|
const u = new URL(href!, "http://example.com");
|
||||||
|
|
||||||
|
return { name, id: u.searchParams.get("package_id")! };
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const getDependentByPackage = async (repo: string, packageId: string) => {
|
||||||
|
const repos = [] as string[];
|
||||||
|
|
||||||
|
const pages = [];
|
||||||
|
|
||||||
|
let url:
|
||||||
|
| string
|
||||||
|
| null = `https://github.com/${repo}/network/dependents?package_id=${packageId}`;
|
||||||
|
|
||||||
|
while (url) {
|
||||||
|
const $ = CheerioLoad(await httpGet(url));
|
||||||
|
|
||||||
|
console.log(repos.length);
|
||||||
|
|
||||||
|
const reposOnPage = $(`#dependents [data-hovercard-type="repository"]`)
|
||||||
|
.toArray()
|
||||||
|
.map((el) => $(el).attr("href")!.slice(1));
|
||||||
|
|
||||||
|
repos.push(...reposOnPage);
|
||||||
|
|
||||||
|
const nextButton = $(`#dependents a`)
|
||||||
|
.filter((_, el) => $(el).text().trim().toLowerCase() === "next")
|
||||||
|
.eq(0);
|
||||||
|
|
||||||
|
const href = nextButton ? nextButton.attr("href") : null;
|
||||||
|
|
||||||
|
pages.push({ url, reposOnPage, next: href });
|
||||||
|
|
||||||
|
url = href ? new URL(href, "https://github.com").toString() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { repos, pages };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getDependents = async (repo: string) => {
|
||||||
|
const packages = await getPackages(repo);
|
||||||
|
|
||||||
|
const ps: (typeof packages[number] & { dependents: string[] })[] = [];
|
||||||
|
|
||||||
|
for (const p of packages)
|
||||||
|
ps.push({
|
||||||
|
...p,
|
||||||
|
dependents: (await getDependentByPackage(repo, p.id)).repos,
|
||||||
|
});
|
||||||
|
|
||||||
|
return ps;
|
||||||
|
};
|
||||||
125
packages/usage-stats/getRunInfo-api-copy.ts
Normal file
125
packages/usage-stats/getRunInfo-api-copy.ts
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
import * as fs from "fs";
|
||||||
|
import fetch from "node-fetch";
|
||||||
|
import { Octokit } from "octokit";
|
||||||
|
|
||||||
|
require("dotenv").config();
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
import packages from "./out.json";
|
||||||
|
|
||||||
|
const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN });
|
||||||
|
|
||||||
|
const getLastRunInfo = async (repo_: string) => {
|
||||||
|
const [owner, repo] = repo_.split("/");
|
||||||
|
|
||||||
|
try {
|
||||||
|
const {
|
||||||
|
data: { workflow_runs },
|
||||||
|
} = await octokit.request(
|
||||||
|
"GET /repos/{owner}/{repo}/actions/runs{?actor,branch,event,status,per_page,page,created,exclude_pull_requests,check_suite_id,head_sha}",
|
||||||
|
{ owner, repo }
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const r of workflow_runs) {
|
||||||
|
const { run_started_at, head_sha, path, conclusion } = r as {
|
||||||
|
run_started_at: string;
|
||||||
|
head_sha: string;
|
||||||
|
path: string;
|
||||||
|
conclusion: "failure" | "success";
|
||||||
|
};
|
||||||
|
|
||||||
|
const workflow_url = `https://raw.githubusercontent.com/${owner}/${repo}/${head_sha}/${path}`;
|
||||||
|
|
||||||
|
const workflow_file = await fetch(workflow_url).then((res) => res.text());
|
||||||
|
|
||||||
|
const [_, dependency, __, version] =
|
||||||
|
workflow_file.match(/uses\s*:\s*(Platane\/snk(\/svg-only)?@(\w*))/) ??
|
||||||
|
[];
|
||||||
|
|
||||||
|
const cronMatch = workflow_file.match(/cron\s*:([^\n]*)/);
|
||||||
|
|
||||||
|
if (dependency)
|
||||||
|
return {
|
||||||
|
dependency,
|
||||||
|
version,
|
||||||
|
run_started_at,
|
||||||
|
conclusion,
|
||||||
|
cron: cronMatch?.[1].replace(/["|']/g, "").trim(),
|
||||||
|
workflow_file,
|
||||||
|
workflow_url,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const wait = (delay = 0) => new Promise((r) => setTimeout(r, delay));
|
||||||
|
|
||||||
|
const getRepos = () => {
|
||||||
|
try {
|
||||||
|
return JSON.parse(fs.readFileSync(__dirname + "/cache/out.json").toString())
|
||||||
|
.map((p: any) => p.dependents)
|
||||||
|
.flat() as string[];
|
||||||
|
} catch (err) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getReposInfo = () => {
|
||||||
|
try {
|
||||||
|
return JSON.parse(
|
||||||
|
fs.readFileSync(__dirname + "/cache/stats.json").toString()
|
||||||
|
) as any[];
|
||||||
|
} catch (err) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const saveRepoInfo = (rr: any[]) => {
|
||||||
|
fs.writeFileSync(__dirname + "/cache/stats.json", JSON.stringify(rr));
|
||||||
|
};
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
const repos = getRepos();
|
||||||
|
const total = repos.length;
|
||||||
|
|
||||||
|
const reposInfo = getReposInfo().slice(0, -20);
|
||||||
|
for (const { repo } of reposInfo) {
|
||||||
|
const i = repos.indexOf(repo);
|
||||||
|
if (i >= 0) repos.splice(i, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (repos.length) {
|
||||||
|
const {
|
||||||
|
data: { rate },
|
||||||
|
} = await octokit.request("GET /rate_limit", {});
|
||||||
|
|
||||||
|
console.log(rate);
|
||||||
|
if (rate.remaining < 100) {
|
||||||
|
const delay = rate.reset - Math.floor(Date.now() / 1000);
|
||||||
|
console.log(
|
||||||
|
`waiting ${delay} second (${(delay / 60).toFixed(
|
||||||
|
1
|
||||||
|
)} minutes) for reset `
|
||||||
|
);
|
||||||
|
await wait(Math.max(0, delay) * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
const rs = repos.splice(0, 20);
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
rs.map(async (repo) => {
|
||||||
|
reposInfo.push({ repo, ...(await getLastRunInfo(repo)) });
|
||||||
|
|
||||||
|
saveRepoInfo(reposInfo);
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
reposInfo.length.toString().padStart(5, " "),
|
||||||
|
"/",
|
||||||
|
total,
|
||||||
|
repo
|
||||||
|
);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})();
|
||||||
84
packages/usage-stats/httpGet.ts
Normal file
84
packages/usage-stats/httpGet.ts
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
import fetch from "node-fetch";
|
||||||
|
import * as path from "path";
|
||||||
|
import * as fs from "fs";
|
||||||
|
|
||||||
|
const CACHE_DIR = path.join(__dirname, "cache", "http");
|
||||||
|
fs.mkdirSync(CACHE_DIR, { recursive: true });
|
||||||
|
|
||||||
|
const createMutex = () => {
|
||||||
|
let locked = false;
|
||||||
|
const q: any[] = [];
|
||||||
|
|
||||||
|
const update = () => {
|
||||||
|
if (locked) return;
|
||||||
|
|
||||||
|
if (q[0]) {
|
||||||
|
locked = true;
|
||||||
|
q.shift()(() => {
|
||||||
|
locked = false;
|
||||||
|
update();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const request = () =>
|
||||||
|
new Promise<() => void>((resolve) => {
|
||||||
|
q.push(resolve);
|
||||||
|
update();
|
||||||
|
});
|
||||||
|
|
||||||
|
return request;
|
||||||
|
};
|
||||||
|
|
||||||
|
const mutex = createMutex();
|
||||||
|
|
||||||
|
export const httpGet = async (url: string | URL): Promise<string> => {
|
||||||
|
const cacheKey = url
|
||||||
|
.toString()
|
||||||
|
.replace(/https?:\/\//, "")
|
||||||
|
.replace(/[^\w=&\?\.]/g, "_");
|
||||||
|
|
||||||
|
const cacheFilename = path.join(CACHE_DIR, cacheKey);
|
||||||
|
|
||||||
|
if (fs.existsSync(cacheFilename))
|
||||||
|
return new Promise((resolve, reject) =>
|
||||||
|
fs.readFile(cacheFilename, (err, data) =>
|
||||||
|
err ? reject(err) : resolve(data.toString())
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
const release = await mutex();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(url);
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
if (res.status === 429 || res.statusText === "Too Many Requests") {
|
||||||
|
const delay = +(res.headers.get("retry-after") ?? 300) * 1000;
|
||||||
|
|
||||||
|
console.log("Too Many Requests", delay);
|
||||||
|
|
||||||
|
await wait(delay);
|
||||||
|
|
||||||
|
console.log("waited long enough");
|
||||||
|
|
||||||
|
return httpGet(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.error(url, res.status, res.statusText);
|
||||||
|
throw new Error("res not ok");
|
||||||
|
}
|
||||||
|
|
||||||
|
const text = await res.text();
|
||||||
|
|
||||||
|
fs.writeFileSync(cacheFilename, text);
|
||||||
|
|
||||||
|
// await wait(Math.random() * 200 + 100);
|
||||||
|
|
||||||
|
return text;
|
||||||
|
} finally {
|
||||||
|
release();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const wait = (delay = 0) => new Promise((r) => setTimeout(r, delay));
|
||||||
51
packages/usage-stats/index.ts
Normal file
51
packages/usage-stats/index.ts
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import { getDependentInfo } from "./getDependentInfo";
|
||||||
|
import { getDependents } from "./getDependents";
|
||||||
|
import ParkMiller from "park-miller";
|
||||||
|
|
||||||
|
const toChunk = <T>(arr: T[], n = 1) =>
|
||||||
|
Array.from({ length: Math.ceil(arr.length / n) }, (_, i) =>
|
||||||
|
arr.slice(i * n, (i + 1) * n)
|
||||||
|
);
|
||||||
|
|
||||||
|
const random = new ParkMiller(10);
|
||||||
|
|
||||||
|
const shuffle = <T>(array: T[]) => {
|
||||||
|
for (let i = array.length - 1; i > 0; i--) {
|
||||||
|
const j = Math.floor(random.float() * (i + 1));
|
||||||
|
const temp = array[i];
|
||||||
|
array[i] = array[j];
|
||||||
|
array[j] = temp;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
const packages = await getDependents("Platane/snk");
|
||||||
|
|
||||||
|
const repos = packages.map((p) => p.dependents).flat();
|
||||||
|
|
||||||
|
shuffle(repos);
|
||||||
|
repos.splice(0, repos.length - 5000);
|
||||||
|
|
||||||
|
console.log(repos);
|
||||||
|
|
||||||
|
const infos: any[] = [];
|
||||||
|
|
||||||
|
// for (const chunk of toChunk(repos, 10))
|
||||||
|
// await Promise.all(
|
||||||
|
// chunk.map(async (repo) => {
|
||||||
|
// console.log(
|
||||||
|
// infos.length.toString().padStart(5, " "),
|
||||||
|
// "/",
|
||||||
|
// repos.length
|
||||||
|
// );
|
||||||
|
|
||||||
|
// infos.push({ repo, ...(await getDependentInfo(repo)) });
|
||||||
|
// })
|
||||||
|
// );
|
||||||
|
|
||||||
|
for (const repo of repos) {
|
||||||
|
console.log(infos.length.toString().padStart(5, " "), "/", repos.length);
|
||||||
|
|
||||||
|
infos.push({ repo, ...(await getDependentInfo(repo)) });
|
||||||
|
}
|
||||||
|
})();
|
||||||
16
packages/usage-stats/package.json
Normal file
16
packages/usage-stats/package.json
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"name": "@snk/usage-stats",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"dependencies": {},
|
||||||
|
"devDependencies": {
|
||||||
|
"sucrase": "3.29.0",
|
||||||
|
"cheerio": "1.0.0-rc.12",
|
||||||
|
"node-fetch": "2.6.7",
|
||||||
|
"octokit": "2.0.11",
|
||||||
|
"dotenv": "16.0.3",
|
||||||
|
"park-miller": "1.1.0"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"start": "sucrase-node index.ts"
|
||||||
|
}
|
||||||
|
}
|
||||||
62
packages/usage-stats/stats.ts
Normal file
62
packages/usage-stats/stats.ts
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
import * as fs from "fs";
|
||||||
|
|
||||||
|
type R = { repo: string } & Partial<{
|
||||||
|
dependency: string;
|
||||||
|
version: string;
|
||||||
|
run_started_at: string;
|
||||||
|
conclusion: "failure" | "success";
|
||||||
|
cron?: string;
|
||||||
|
workflow_file: string;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
const repos: R[] = JSON.parse(
|
||||||
|
fs.readFileSync(__dirname + "/cache/stats.json").toString()
|
||||||
|
);
|
||||||
|
|
||||||
|
const total = repos.length;
|
||||||
|
|
||||||
|
const recent_repos = repos.filter(
|
||||||
|
(r) =>
|
||||||
|
new Date(r.run_started_at!).getTime() >
|
||||||
|
Date.now() - 7 * 24 * 60 * 60 * 1000
|
||||||
|
);
|
||||||
|
|
||||||
|
const recent_successful_repos = recent_repos.filter(
|
||||||
|
(r) => r?.conclusion === "success"
|
||||||
|
);
|
||||||
|
|
||||||
|
const versions = new Map();
|
||||||
|
for (const { dependency } of recent_successful_repos) {
|
||||||
|
versions.set(dependency, (versions.get(dependency) ?? 0) + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`total ${total}`);
|
||||||
|
console.log(
|
||||||
|
`recent_repos ${recent_repos.length} (${(
|
||||||
|
(recent_repos.length / total) *
|
||||||
|
100
|
||||||
|
).toFixed(2)}%)`
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
`recent_successful_repos ${recent_successful_repos.length} (${(
|
||||||
|
(recent_successful_repos.length / total) *
|
||||||
|
100
|
||||||
|
).toFixed(2)}%)`
|
||||||
|
);
|
||||||
|
console.log("versions");
|
||||||
|
for (const [name, count] of Array.from(versions.entries()).sort(
|
||||||
|
([, a], [, b]) => b - a
|
||||||
|
))
|
||||||
|
console.log(
|
||||||
|
`${(name as string).split("Platane/")[1].padEnd(20, " ")} ${(
|
||||||
|
(count / recent_successful_repos.length) *
|
||||||
|
100
|
||||||
|
)
|
||||||
|
.toFixed(2)
|
||||||
|
.padStart(6, " ")}% ${count} `
|
||||||
|
);
|
||||||
|
|
||||||
|
const gif_repos = repos.filter((r) => r.workflow_file?.includes(".gif"));
|
||||||
|
console.log("repo with git ouput", gif_repos.length);
|
||||||
|
})();
|
||||||
@@ -3,19 +3,16 @@ description: "Generates a snake game from a github user contributions grid. Outp
|
|||||||
author: "platane"
|
author: "platane"
|
||||||
|
|
||||||
runs:
|
runs:
|
||||||
using: node20
|
using: node16
|
||||||
main: dist/index.js
|
main: dist/index.js
|
||||||
|
|
||||||
inputs:
|
inputs:
|
||||||
github_user_name:
|
github_user_name:
|
||||||
description: "github user name"
|
description: "github user name"
|
||||||
required: true
|
required: true
|
||||||
github_token:
|
|
||||||
description: "github token used to fetch the contribution calendar. Default to the action token if empty."
|
|
||||||
required: false
|
|
||||||
default: ${{ github.token }}
|
|
||||||
outputs:
|
outputs:
|
||||||
required: false
|
required: false
|
||||||
|
default: null
|
||||||
description: |
|
description: |
|
||||||
list of files to generate.
|
list of files to generate.
|
||||||
one file per line. Each output can be customized with options as query string.
|
one file per line. Each output can be customized with options as query string.
|
||||||
|
|||||||
@@ -1,29 +1,31 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
exports.id = 642;
|
exports.id = 142;
|
||||||
exports.ids = [642];
|
exports.ids = [142];
|
||||||
exports.modules = {
|
exports.modules = {
|
||||||
|
|
||||||
/***/ 3642:
|
/***/ 7142:
|
||||||
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
|
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
|
||||||
|
|
||||||
|
// ESM COMPAT FLAG
|
||||||
|
__webpack_require__.r(__webpack_exports__);
|
||||||
|
|
||||||
// EXPORTS
|
// EXPORTS
|
||||||
__webpack_require__.d(__webpack_exports__, {
|
__webpack_require__.d(__webpack_exports__, {
|
||||||
createGif: () => (/* binding */ createGif)
|
"createGif": () => (/* binding */ createGif)
|
||||||
});
|
});
|
||||||
|
|
||||||
// EXTERNAL MODULE: external "fs"
|
// EXTERNAL MODULE: external "fs"
|
||||||
var external_fs_ = __webpack_require__(9896);
|
var external_fs_ = __webpack_require__(7147);
|
||||||
var external_fs_default = /*#__PURE__*/__webpack_require__.n(external_fs_);
|
var external_fs_default = /*#__PURE__*/__webpack_require__.n(external_fs_);
|
||||||
// EXTERNAL MODULE: external "path"
|
// EXTERNAL MODULE: external "path"
|
||||||
var external_path_ = __webpack_require__(6928);
|
var external_path_ = __webpack_require__(1017);
|
||||||
var external_path_default = /*#__PURE__*/__webpack_require__.n(external_path_);
|
var external_path_default = /*#__PURE__*/__webpack_require__.n(external_path_);
|
||||||
// EXTERNAL MODULE: external "child_process"
|
// EXTERNAL MODULE: external "child_process"
|
||||||
var external_child_process_ = __webpack_require__(5317);
|
var external_child_process_ = __webpack_require__(2081);
|
||||||
// EXTERNAL MODULE: external "canvas"
|
// EXTERNAL MODULE: external "canvas"
|
||||||
var external_canvas_ = __webpack_require__(9919);
|
var external_canvas_ = __webpack_require__(1576);
|
||||||
// EXTERNAL MODULE: ../types/grid.ts
|
// EXTERNAL MODULE: ../types/grid.ts
|
||||||
var types_grid = __webpack_require__(105);
|
var types_grid = __webpack_require__(2881);
|
||||||
;// CONCATENATED MODULE: ../draw/pathRoundedRect.ts
|
;// CONCATENATED MODULE: ../draw/pathRoundedRect.ts
|
||||||
const pathRoundedRect_pathRoundedRect = (ctx, width, height, borderRadius) => {
|
const pathRoundedRect_pathRoundedRect = (ctx, width, height, borderRadius) => {
|
||||||
ctx.moveTo(borderRadius, 0);
|
ctx.moveTo(borderRadius, 0);
|
||||||
@@ -40,7 +42,7 @@ const drawGrid_drawGrid = (ctx, grid, cells, o) => {
|
|||||||
for (let x = grid.width; x--;)
|
for (let x = grid.width; x--;)
|
||||||
for (let y = grid.height; y--;) {
|
for (let y = grid.height; y--;) {
|
||||||
if (!cells || cells.some((c) => c.x === x && c.y === y)) {
|
if (!cells || cells.some((c) => c.x === x && c.y === y)) {
|
||||||
const c = (0,types_grid/* getColor */.oU)(grid, x, y);
|
const c = (0,types_grid/* getColor */.Lq)(grid, x, y);
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const color = !c ? o.colorEmpty : o.colorDots[c];
|
const color = !c ? o.colorEmpty : o.colorDots[c];
|
||||||
ctx.save();
|
ctx.save();
|
||||||
@@ -142,27 +144,27 @@ const getCanvasWorldSize = (grid, o) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// EXTERNAL MODULE: ../types/snake.ts
|
// EXTERNAL MODULE: ../types/snake.ts
|
||||||
var types_snake = __webpack_require__(777);
|
var types_snake = __webpack_require__(9347);
|
||||||
;// CONCATENATED MODULE: ../solver/step.ts
|
;// CONCATENATED MODULE: ../solver/step.ts
|
||||||
|
|
||||||
|
|
||||||
const step = (grid, stack, snake) => {
|
const step = (grid, stack, snake) => {
|
||||||
const x = (0,types_snake/* getHeadX */.tN)(snake);
|
const x = (0,types_snake/* getHeadX */.If)(snake);
|
||||||
const y = (0,types_snake/* getHeadY */.Ap)(snake);
|
const y = (0,types_snake/* getHeadY */.IP)(snake);
|
||||||
const color = (0,types_grid/* getColor */.oU)(grid, x, y);
|
const color = (0,types_grid/* getColor */.Lq)(grid, x, y);
|
||||||
if ((0,types_grid/* isInside */.FK)(grid, x, y) && !(0,types_grid/* isEmpty */.Im)(color)) {
|
if ((0,types_grid/* isInside */.V0)(grid, x, y) && !(0,types_grid/* isEmpty */.xb)(color)) {
|
||||||
stack.push(color);
|
stack.push(color);
|
||||||
(0,types_grid/* setColorEmpty */.l$)(grid, x, y);
|
(0,types_grid/* setColorEmpty */.Dy)(grid, x, y);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// EXTERNAL MODULE: ../../node_modules/tmp/lib/tmp.js
|
// EXTERNAL MODULE: ../../node_modules/tmp/lib/tmp.js
|
||||||
var tmp = __webpack_require__(2644);
|
var tmp = __webpack_require__(6382);
|
||||||
// EXTERNAL MODULE: external "gifsicle"
|
// EXTERNAL MODULE: external "gifsicle"
|
||||||
var external_gifsicle_ = __webpack_require__(5667);
|
var external_gifsicle_ = __webpack_require__(542);
|
||||||
var external_gifsicle_default = /*#__PURE__*/__webpack_require__.n(external_gifsicle_);
|
var external_gifsicle_default = /*#__PURE__*/__webpack_require__.n(external_gifsicle_);
|
||||||
// EXTERNAL MODULE: ../../node_modules/gif-encoder-2/index.js
|
// EXTERNAL MODULE: ../../node_modules/gif-encoder-2/index.js
|
||||||
var gif_encoder_2 = __webpack_require__(1680);
|
var gif_encoder_2 = __webpack_require__(3561);
|
||||||
var gif_encoder_2_default = /*#__PURE__*/__webpack_require__.n(gif_encoder_2);
|
var gif_encoder_2_default = /*#__PURE__*/__webpack_require__.n(gif_encoder_2);
|
||||||
;// CONCATENATED MODULE: ../gif-creator/index.ts
|
;// CONCATENATED MODULE: ../gif-creator/index.ts
|
||||||
|
|
||||||
@@ -191,7 +193,7 @@ const createGif = async (grid0, cells, chain, drawOptions, animationOptions) =>
|
|||||||
const { width, height } = getCanvasWorldSize(grid0, drawOptions);
|
const { width, height } = getCanvasWorldSize(grid0, drawOptions);
|
||||||
const canvas = (0,external_canvas_.createCanvas)(width, height);
|
const canvas = (0,external_canvas_.createCanvas)(width, height);
|
||||||
const ctx = canvas.getContext("2d");
|
const ctx = canvas.getContext("2d");
|
||||||
const grid = (0,types_grid/* copyGrid */.mi)(grid0);
|
const grid = (0,types_grid/* copyGrid */.VJ)(grid0);
|
||||||
const stack = [];
|
const stack = [];
|
||||||
const encoder = new (gif_encoder_2_default())(width, height, "neuquant", true);
|
const encoder = new (gif_encoder_2_default())(width, height, "neuquant", true);
|
||||||
encoder.setRepeat(0);
|
encoder.setRepeat(0);
|
||||||
@@ -221,7 +223,7 @@ const createGif = async (grid0, cells, chain, drawOptions, animationOptions) =>
|
|||||||
outFileName,
|
outFileName,
|
||||||
["--output", optimizedFileName],
|
["--output", optimizedFileName],
|
||||||
].flat());
|
].flat());
|
||||||
return new Uint8Array(external_fs_default().readFileSync(optimizedFileName));
|
return external_fs_default().readFileSync(optimizedFileName);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
2129
svg-only/dist/155.index.js
vendored
2129
svg-only/dist/155.index.js
vendored
File diff suppressed because it is too large
Load Diff
3889
svg-only/dist/197.index.js
vendored
Normal file
3889
svg-only/dist/197.index.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -1,18 +1,53 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
exports.id = 324;
|
exports.id = 317;
|
||||||
exports.ids = [324];
|
exports.ids = [317];
|
||||||
exports.modules = {
|
exports.modules = {
|
||||||
|
|
||||||
/***/ 324:
|
/***/ 5317:
|
||||||
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
|
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
|
||||||
|
|
||||||
|
// ESM COMPAT FLAG
|
||||||
|
__webpack_require__.r(__webpack_exports__);
|
||||||
|
|
||||||
// EXPORTS
|
// EXPORTS
|
||||||
__webpack_require__.d(__webpack_exports__, {
|
__webpack_require__.d(__webpack_exports__, {
|
||||||
generateContributionSnake: () => (/* binding */ generateContributionSnake)
|
"generateContributionSnake": () => (/* binding */ generateContributionSnake)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// EXTERNAL MODULE: ../../node_modules/node-fetch/lib/index.js
|
||||||
|
var lib = __webpack_require__(2197);
|
||||||
|
var lib_default = /*#__PURE__*/__webpack_require__.n(lib);
|
||||||
|
;// CONCATENATED MODULE: ../github-user-contribution/formatParams.ts
|
||||||
|
const formatParams = (options = {}) => {
|
||||||
|
const sp = new URLSearchParams();
|
||||||
|
const o = { ...options };
|
||||||
|
if ("year" in options) {
|
||||||
|
o.from = `${options.year}-01-01`;
|
||||||
|
o.to = `${options.year}-12-31`;
|
||||||
|
}
|
||||||
|
for (const s of ["from", "to"])
|
||||||
|
if (o[s]) {
|
||||||
|
const value = o[s];
|
||||||
|
if (value >= formatDate(new Date()))
|
||||||
|
throw new Error("Cannot get contribution for a date in the future.\nPlease limit your range to the current UTC day.");
|
||||||
|
sp.set(s, value);
|
||||||
|
}
|
||||||
|
return sp.toString();
|
||||||
|
};
|
||||||
|
const formatDate = (d) => {
|
||||||
|
const year = d.getUTCFullYear();
|
||||||
|
const month = d.getUTCMonth() + 1;
|
||||||
|
const date = d.getUTCDate();
|
||||||
|
return [
|
||||||
|
year,
|
||||||
|
month.toString().padStart(2, "0"),
|
||||||
|
date.toString().padStart(2, "0"),
|
||||||
|
].join("-");
|
||||||
|
};
|
||||||
|
|
||||||
;// CONCATENATED MODULE: ../github-user-contribution/index.ts
|
;// CONCATENATED MODULE: ../github-user-contribution/index.ts
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* get the contribution grid from a github user page
|
* get the contribution grid from a github user page
|
||||||
*
|
*
|
||||||
@@ -29,66 +64,58 @@ __webpack_require__.d(__webpack_exports__, {
|
|||||||
* getGithubUserContribution("platane", { year: 2019 })
|
* getGithubUserContribution("platane", { year: 2019 })
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
const getGithubUserContribution = async (userName, o) => {
|
const getGithubUserContribution = async (userName, options = {}) => {
|
||||||
const query = /* GraphQL */ `
|
// either use github.com/users/xxxx/contributions for previous years
|
||||||
query ($login: String!) {
|
// or github.com/xxxx ( which gives the latest update to today result )
|
||||||
user(login: $login) {
|
const url = "year" in options || "from" in options || "to" in options
|
||||||
contributionsCollection {
|
? `https://github.com/users/${userName}/contributions?` +
|
||||||
contributionCalendar {
|
formatParams(options)
|
||||||
weeks {
|
: `https://github.com/${userName}`;
|
||||||
contributionDays {
|
const res = await lib_default()(url);
|
||||||
contributionCount
|
|
||||||
contributionLevel
|
|
||||||
weekday
|
|
||||||
date
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
const variables = { login: userName };
|
|
||||||
const res = await fetch("https://api.github.com/graphql", {
|
|
||||||
headers: {
|
|
||||||
Authorization: `bearer ${o.githubToken}`,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
"User-Agent": "me@platane.me",
|
|
||||||
},
|
|
||||||
method: "POST",
|
|
||||||
body: JSON.stringify({ variables, query }),
|
|
||||||
});
|
|
||||||
if (!res.ok)
|
if (!res.ok)
|
||||||
throw new Error(await res.text().catch(() => res.statusText));
|
throw new Error(res.statusText);
|
||||||
const { data, errors } = (await res.json());
|
const resText = await res.text();
|
||||||
if (errors?.[0])
|
return parseUserPage(resText);
|
||||||
throw errors[0];
|
};
|
||||||
return data.user.contributionsCollection.contributionCalendar.weeks.flatMap(({ contributionDays }, x) => contributionDays.map((d) => ({
|
const parseUserPage = (content) => {
|
||||||
x,
|
// take roughly the svg block
|
||||||
y: d.weekday,
|
const block = content
|
||||||
date: d.date,
|
.split(`class="js-calendar-graph-svg"`)[1]
|
||||||
count: d.contributionCount,
|
.split("</svg>")[0];
|
||||||
level: (d.contributionLevel === "FOURTH_QUARTILE" && 4) ||
|
let x = 0;
|
||||||
(d.contributionLevel === "THIRD_QUARTILE" && 3) ||
|
let lastYAttribute = 0;
|
||||||
(d.contributionLevel === "SECOND_QUARTILE" && 2) ||
|
const rects = Array.from(block.matchAll(/<rect[^>]*>[^<]*<\/rect>/g)).map(([m]) => {
|
||||||
(d.contributionLevel === "FIRST_QUARTILE" && 1) ||
|
const date = m.match(/data-date="([^"]+)"/)[1];
|
||||||
0,
|
const level = +m.match(/data-level="([^"]+)"/)[1];
|
||||||
})));
|
const yAttribute = +m.match(/y="([^"]+)"/)[1];
|
||||||
|
const literalCount = m.match(/(No|\d+) contributions? on/)[1];
|
||||||
|
const count = literalCount === "No" ? 0 : +literalCount;
|
||||||
|
if (lastYAttribute > yAttribute)
|
||||||
|
x++;
|
||||||
|
lastYAttribute = yAttribute;
|
||||||
|
return { date, count, level, x, yAttribute };
|
||||||
|
});
|
||||||
|
const yAttributes = Array.from(new Set(rects.map((c) => c.yAttribute)).keys()).sort();
|
||||||
|
const cells = rects.map(({ yAttribute, ...c }) => ({
|
||||||
|
y: yAttributes.indexOf(yAttribute),
|
||||||
|
...c,
|
||||||
|
}));
|
||||||
|
return cells;
|
||||||
};
|
};
|
||||||
|
|
||||||
// EXTERNAL MODULE: ../types/grid.ts
|
// EXTERNAL MODULE: ../types/grid.ts
|
||||||
var types_grid = __webpack_require__(105);
|
var types_grid = __webpack_require__(2881);
|
||||||
;// CONCATENATED MODULE: ./userContributionToGrid.ts
|
;// CONCATENATED MODULE: ./userContributionToGrid.ts
|
||||||
|
|
||||||
const userContributionToGrid = (cells) => {
|
const userContributionToGrid = (cells) => {
|
||||||
const width = Math.max(0, ...cells.map((c) => c.x)) + 1;
|
const width = Math.max(0, ...cells.map((c) => c.x)) + 1;
|
||||||
const height = Math.max(0, ...cells.map((c) => c.y)) + 1;
|
const height = Math.max(0, ...cells.map((c) => c.y)) + 1;
|
||||||
const grid = (0,types_grid/* createEmptyGrid */.Kb)(width, height);
|
const grid = (0,types_grid/* createEmptyGrid */.u1)(width, height);
|
||||||
for (const c of cells) {
|
for (const c of cells) {
|
||||||
if (c.level > 0)
|
if (c.level > 0)
|
||||||
(0,types_grid/* setColor */.wW)(grid, c.x, c.y, c.level);
|
(0,types_grid/* setColor */.vk)(grid, c.x, c.y, c.level);
|
||||||
else
|
else
|
||||||
(0,types_grid/* setColorEmpty */.l$)(grid, c.x, c.y);
|
(0,types_grid/* setColorEmpty */.Dy)(grid, c.x, c.y);
|
||||||
}
|
}
|
||||||
return grid;
|
return grid;
|
||||||
};
|
};
|
||||||
@@ -106,10 +133,10 @@ const pointEquals = (a, b) => a.x === b.x && a.y === b.y;
|
|||||||
|
|
||||||
|
|
||||||
const createOutside = (grid, color = 0) => {
|
const createOutside = (grid, color = 0) => {
|
||||||
const outside = (0,types_grid/* createEmptyGrid */.Kb)(grid.width, grid.height);
|
const outside = (0,types_grid/* createEmptyGrid */.u1)(grid.width, grid.height);
|
||||||
for (let x = outside.width; x--;)
|
for (let x = outside.width; x--;)
|
||||||
for (let y = outside.height; y--;)
|
for (let y = outside.height; y--;)
|
||||||
(0,types_grid/* setColor */.wW)(outside, x, y, 1);
|
(0,types_grid/* setColor */.vk)(outside, x, y, 1);
|
||||||
fillOutside(outside, grid, color);
|
fillOutside(outside, grid, color);
|
||||||
return outside;
|
return outside;
|
||||||
};
|
};
|
||||||
@@ -119,19 +146,19 @@ const fillOutside = (outside, grid, color = 0) => {
|
|||||||
changed = false;
|
changed = false;
|
||||||
for (let x = outside.width; x--;)
|
for (let x = outside.width; x--;)
|
||||||
for (let y = outside.height; y--;)
|
for (let y = outside.height; y--;)
|
||||||
if ((0,types_grid/* getColor */.oU)(grid, x, y) <= color &&
|
if ((0,types_grid/* getColor */.Lq)(grid, x, y) <= color &&
|
||||||
!isOutside(outside, x, y) &&
|
!isOutside(outside, x, y) &&
|
||||||
around4.some((a) => isOutside(outside, x + a.x, y + a.y))) {
|
around4.some((a) => isOutside(outside, x + a.x, y + a.y))) {
|
||||||
changed = true;
|
changed = true;
|
||||||
(0,types_grid/* setColorEmpty */.l$)(outside, x, y);
|
(0,types_grid/* setColorEmpty */.Dy)(outside, x, y);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return outside;
|
return outside;
|
||||||
};
|
};
|
||||||
const isOutside = (outside, x, y) => !(0,types_grid/* isInside */.FK)(outside, x, y) || (0,types_grid/* isEmpty */.Im)((0,types_grid/* getColor */.oU)(outside, x, y));
|
const isOutside = (outside, x, y) => !(0,types_grid/* isInside */.V0)(outside, x, y) || (0,types_grid/* isEmpty */.xb)((0,types_grid/* getColor */.Lq)(outside, x, y));
|
||||||
|
|
||||||
// EXTERNAL MODULE: ../types/snake.ts
|
// EXTERNAL MODULE: ../types/snake.ts
|
||||||
var types_snake = __webpack_require__(777);
|
var types_snake = __webpack_require__(9347);
|
||||||
;// CONCATENATED MODULE: ../solver/utils/sortPush.ts
|
;// CONCATENATED MODULE: ../solver/utils/sortPush.ts
|
||||||
const sortPush = (arr, x, sortFn) => {
|
const sortPush = (arr, x, sortFn) => {
|
||||||
let a = 0;
|
let a = 0;
|
||||||
@@ -164,9 +191,9 @@ const getTunnelPath = (snake0, tunnel) => {
|
|||||||
const chain = [];
|
const chain = [];
|
||||||
let snake = snake0;
|
let snake = snake0;
|
||||||
for (let i = 1; i < tunnel.length; i++) {
|
for (let i = 1; i < tunnel.length; i++) {
|
||||||
const dx = tunnel[i].x - (0,types_snake/* getHeadX */.tN)(snake);
|
const dx = tunnel[i].x - (0,types_snake/* getHeadX */.If)(snake);
|
||||||
const dy = tunnel[i].y - (0,types_snake/* getHeadY */.Ap)(snake);
|
const dy = tunnel[i].y - (0,types_snake/* getHeadY */.IP)(snake);
|
||||||
snake = (0,types_snake/* nextSnake */.Sc)(snake, dx, dy);
|
snake = (0,types_snake/* nextSnake */.kv)(snake, dx, dy);
|
||||||
chain.unshift(snake);
|
chain.unshift(snake);
|
||||||
}
|
}
|
||||||
return chain;
|
return chain;
|
||||||
@@ -194,7 +221,7 @@ const updateTunnel = (grid, tunnel, toDelete) => {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const isEmptySafe = (grid, x, y) => !(0,types_grid/* isInside */.FK)(grid, x, y) || (0,types_grid/* isEmpty */.Im)((0,types_grid/* getColor */.oU)(grid, x, y));
|
const isEmptySafe = (grid, x, y) => !(0,types_grid/* isInside */.V0)(grid, x, y) || (0,types_grid/* isEmpty */.xb)((0,types_grid/* getColor */.Lq)(grid, x, y));
|
||||||
/**
|
/**
|
||||||
* remove empty cell from start
|
* remove empty cell from start
|
||||||
*/
|
*/
|
||||||
@@ -229,14 +256,14 @@ const trimTunnelEnd = (grid, tunnel) => {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
const getColorSafe = (grid, x, y) => (0,types_grid/* isInside */.FK)(grid, x, y) ? (0,types_grid/* getColor */.oU)(grid, x, y) : 0;
|
const getColorSafe = (grid, x, y) => (0,types_grid/* isInside */.V0)(grid, x, y) ? (0,types_grid/* getColor */.Lq)(grid, x, y) : 0;
|
||||||
const setEmptySafe = (grid, x, y) => {
|
const setEmptySafe = (grid, x, y) => {
|
||||||
if ((0,types_grid/* isInside */.FK)(grid, x, y))
|
if ((0,types_grid/* isInside */.V0)(grid, x, y))
|
||||||
(0,types_grid/* setColorEmpty */.l$)(grid, x, y);
|
(0,types_grid/* setColorEmpty */.Dy)(grid, x, y);
|
||||||
};
|
};
|
||||||
const unwrap = (m) => !m
|
const unwrap = (m) => !m
|
||||||
? []
|
? []
|
||||||
: [...unwrap(m.parent), { x: (0,types_snake/* getHeadX */.tN)(m.snake), y: (0,types_snake/* getHeadY */.Ap)(m.snake) }];
|
: [...unwrap(m.parent), { x: (0,types_snake/* getHeadX */.If)(m.snake), y: (0,types_snake/* getHeadY */.IP)(m.snake) }];
|
||||||
/**
|
/**
|
||||||
* returns the path to reach the outside which contains the least color cell
|
* returns the path to reach the outside which contains the least color cell
|
||||||
*/
|
*/
|
||||||
@@ -245,15 +272,15 @@ const getSnakeEscapePath = (grid, outside, snake0, color) => {
|
|||||||
const closeList = [];
|
const closeList = [];
|
||||||
while (openList[0]) {
|
while (openList[0]) {
|
||||||
const o = openList.shift();
|
const o = openList.shift();
|
||||||
const x = (0,types_snake/* getHeadX */.tN)(o.snake);
|
const x = (0,types_snake/* getHeadX */.If)(o.snake);
|
||||||
const y = (0,types_snake/* getHeadY */.Ap)(o.snake);
|
const y = (0,types_snake/* getHeadY */.IP)(o.snake);
|
||||||
if (isOutside(outside, x, y))
|
if (isOutside(outside, x, y))
|
||||||
return unwrap(o);
|
return unwrap(o);
|
||||||
for (const a of around4) {
|
for (const a of around4) {
|
||||||
const c = getColorSafe(grid, x + a.x, y + a.y);
|
const c = getColorSafe(grid, x + a.x, y + a.y);
|
||||||
if (c <= color && !(0,types_snake/* snakeWillSelfCollide */.J)(o.snake, a.x, a.y)) {
|
if (c <= color && !(0,types_snake/* snakeWillSelfCollide */.nJ)(o.snake, a.x, a.y)) {
|
||||||
const snake = (0,types_snake/* nextSnake */.Sc)(o.snake, a.x, a.y);
|
const snake = (0,types_snake/* nextSnake */.kv)(o.snake, a.x, a.y);
|
||||||
if (!closeList.some((s0) => (0,types_snake/* snakeEquals */.sW)(s0, snake))) {
|
if (!closeList.some((s0) => (0,types_snake/* snakeEquals */.kE)(s0, snake))) {
|
||||||
const w = o.w + 1 + +(c === color) * 1000;
|
const w = o.w + 1 + +(c === color) * 1000;
|
||||||
sortPush(openList, { snake, w, parent: o }, (a, b) => a.w - b.w);
|
sortPush(openList, { snake, w, parent: o }, (a, b) => a.w - b.w);
|
||||||
closeList.push(snake);
|
closeList.push(snake);
|
||||||
@@ -270,7 +297,7 @@ const getSnakeEscapePath = (grid, outside, snake0, color) => {
|
|||||||
*/
|
*/
|
||||||
const getBestTunnel = (grid, outside, x, y, color, snakeN) => {
|
const getBestTunnel = (grid, outside, x, y, color, snakeN) => {
|
||||||
const c = { x, y };
|
const c = { x, y };
|
||||||
const snake0 = (0,types_snake/* createSnakeFromCells */.yS)(Array.from({ length: snakeN }, () => c));
|
const snake0 = (0,types_snake/* createSnakeFromCells */.xG)(Array.from({ length: snakeN }, () => c));
|
||||||
const one = getSnakeEscapePath(grid, outside, snake0, color);
|
const one = getSnakeEscapePath(grid, outside, snake0, color);
|
||||||
if (!one)
|
if (!one)
|
||||||
return null;
|
return null;
|
||||||
@@ -278,9 +305,9 @@ const getBestTunnel = (grid, outside, x, y, color, snakeN) => {
|
|||||||
const snakeICells = one.slice(0, snakeN);
|
const snakeICells = one.slice(0, snakeN);
|
||||||
while (snakeICells.length < snakeN)
|
while (snakeICells.length < snakeN)
|
||||||
snakeICells.push(snakeICells[snakeICells.length - 1]);
|
snakeICells.push(snakeICells[snakeICells.length - 1]);
|
||||||
const snakeI = (0,types_snake/* createSnakeFromCells */.yS)(snakeICells);
|
const snakeI = (0,types_snake/* createSnakeFromCells */.xG)(snakeICells);
|
||||||
// remove from the grid the colors that one eat
|
// remove from the grid the colors that one eat
|
||||||
const gridI = (0,types_grid/* copyGrid */.mi)(grid);
|
const gridI = (0,types_grid/* copyGrid */.VJ)(grid);
|
||||||
for (const { x, y } of one)
|
for (const { x, y } of one)
|
||||||
setEmptySafe(gridI, x, y);
|
setEmptySafe(gridI, x, y);
|
||||||
const two = getSnakeEscapePath(gridI, outside, snakeI, color);
|
const two = getSnakeEscapePath(gridI, outside, snakeI, color);
|
||||||
@@ -308,15 +335,15 @@ const getPathTo = (grid, snake0, x, y) => {
|
|||||||
const closeList = [];
|
const closeList = [];
|
||||||
while (openList.length) {
|
while (openList.length) {
|
||||||
const c = openList.shift();
|
const c = openList.shift();
|
||||||
const cx = (0,types_snake/* getHeadX */.tN)(c.snake);
|
const cx = (0,types_snake/* getHeadX */.If)(c.snake);
|
||||||
const cy = (0,types_snake/* getHeadY */.Ap)(c.snake);
|
const cy = (0,types_snake/* getHeadY */.IP)(c.snake);
|
||||||
for (let i = 0; i < around4.length; i++) {
|
for (let i = 0; i < around4.length; i++) {
|
||||||
const { x: dx, y: dy } = around4[i];
|
const { x: dx, y: dy } = around4[i];
|
||||||
const nx = cx + dx;
|
const nx = cx + dx;
|
||||||
const ny = cy + dy;
|
const ny = cy + dy;
|
||||||
if (nx === x && ny === y) {
|
if (nx === x && ny === y) {
|
||||||
// unwrap
|
// unwrap
|
||||||
const path = [(0,types_snake/* nextSnake */.Sc)(c.snake, dx, dy)];
|
const path = [(0,types_snake/* nextSnake */.kv)(c.snake, dx, dy)];
|
||||||
let e = c;
|
let e = c;
|
||||||
while (e.parent) {
|
while (e.parent) {
|
||||||
path.push(e.snake);
|
path.push(e.snake);
|
||||||
@@ -324,11 +351,11 @@ const getPathTo = (grid, snake0, x, y) => {
|
|||||||
}
|
}
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
if ((0,types_grid/* isInsideLarge */.Yd)(grid, 2, nx, ny) &&
|
if ((0,types_grid/* isInsideLarge */.HJ)(grid, 2, nx, ny) &&
|
||||||
!(0,types_snake/* snakeWillSelfCollide */.J)(c.snake, dx, dy) &&
|
!(0,types_snake/* snakeWillSelfCollide */.nJ)(c.snake, dx, dy) &&
|
||||||
(!(0,types_grid/* isInside */.FK)(grid, nx, ny) || (0,types_grid/* isEmpty */.Im)((0,types_grid/* getColor */.oU)(grid, nx, ny)))) {
|
(!(0,types_grid/* isInside */.V0)(grid, nx, ny) || (0,types_grid/* isEmpty */.xb)((0,types_grid/* getColor */.Lq)(grid, nx, ny)))) {
|
||||||
const nsnake = (0,types_snake/* nextSnake */.Sc)(c.snake, dx, dy);
|
const nsnake = (0,types_snake/* nextSnake */.kv)(c.snake, dx, dy);
|
||||||
if (!closeList.some((s) => (0,types_snake/* snakeEquals */.sW)(nsnake, s))) {
|
if (!closeList.some((s) => (0,types_snake/* snakeEquals */.kE)(nsnake, s))) {
|
||||||
const w = c.w + 1;
|
const w = c.w + 1;
|
||||||
const h = Math.abs(nx - x) + Math.abs(ny - y);
|
const h = Math.abs(nx - x) + Math.abs(ny - y);
|
||||||
const f = w + h;
|
const f = w + h;
|
||||||
@@ -349,7 +376,7 @@ const getPathTo = (grid, snake0, x, y) => {
|
|||||||
|
|
||||||
|
|
||||||
const clearResidualColoredLayer = (grid, outside, snake0, color) => {
|
const clearResidualColoredLayer = (grid, outside, snake0, color) => {
|
||||||
const snakeN = (0,types_snake/* getSnakeLength */.T$)(snake0);
|
const snakeN = (0,types_snake/* getSnakeLength */.JJ)(snake0);
|
||||||
const tunnels = getTunnellablePoints(grid, outside, snakeN, color);
|
const tunnels = getTunnellablePoints(grid, outside, snakeN, color);
|
||||||
// sort
|
// sort
|
||||||
tunnels.sort((a, b) => b.priority - a.priority);
|
tunnels.sort((a, b) => b.priority - a.priority);
|
||||||
@@ -368,7 +395,7 @@ const clearResidualColoredLayer = (grid, outside, snake0, color) => {
|
|||||||
fillOutside(outside, grid);
|
fillOutside(outside, grid);
|
||||||
// update tunnels
|
// update tunnels
|
||||||
for (let i = tunnels.length; i--;)
|
for (let i = tunnels.length; i--;)
|
||||||
if ((0,types_grid/* isEmpty */.Im)((0,types_grid/* getColor */.oU)(grid, tunnels[i].x, tunnels[i].y)))
|
if ((0,types_grid/* isEmpty */.xb)((0,types_grid/* getColor */.Lq)(grid, tunnels[i].x, tunnels[i].y)))
|
||||||
tunnels.splice(i, 1);
|
tunnels.splice(i, 1);
|
||||||
else {
|
else {
|
||||||
const t = tunnels[i];
|
const t = tunnels[i];
|
||||||
@@ -389,8 +416,8 @@ const clearResidualColoredLayer = (grid, outside, snake0, color) => {
|
|||||||
const getNextTunnel = (ts, snake) => {
|
const getNextTunnel = (ts, snake) => {
|
||||||
let minDistance = Infinity;
|
let minDistance = Infinity;
|
||||||
let closestTunnel = null;
|
let closestTunnel = null;
|
||||||
const x = (0,types_snake/* getHeadX */.tN)(snake);
|
const x = (0,types_snake/* getHeadX */.If)(snake);
|
||||||
const y = (0,types_snake/* getHeadY */.Ap)(snake);
|
const y = (0,types_snake/* getHeadY */.IP)(snake);
|
||||||
const priority = ts[0].priority;
|
const priority = ts[0].priority;
|
||||||
for (let i = 0; ts[i] && ts[i].priority === priority; i++) {
|
for (let i = 0; ts[i] && ts[i].priority === priority; i++) {
|
||||||
const t = ts[i].tunnel;
|
const t = ts[i].tunnel;
|
||||||
@@ -409,8 +436,8 @@ const getTunnellablePoints = (grid, outside, snakeN, color) => {
|
|||||||
const points = [];
|
const points = [];
|
||||||
for (let x = grid.width; x--;)
|
for (let x = grid.width; x--;)
|
||||||
for (let y = grid.height; y--;) {
|
for (let y = grid.height; y--;) {
|
||||||
const c = (0,types_grid/* getColor */.oU)(grid, x, y);
|
const c = (0,types_grid/* getColor */.Lq)(grid, x, y);
|
||||||
if (!(0,types_grid/* isEmpty */.Im)(c) && c < color) {
|
if (!(0,types_grid/* isEmpty */.xb)(c) && c < color) {
|
||||||
const tunnel = getBestTunnel(grid, outside, x, y, color, snakeN);
|
const tunnel = getBestTunnel(grid, outside, x, y, color, snakeN);
|
||||||
if (tunnel) {
|
if (tunnel) {
|
||||||
const priority = getPriority(grid, color, tunnel);
|
const priority = getPriority(grid, color, tunnel);
|
||||||
@@ -431,7 +458,7 @@ const getPriority = (grid, color, tunnel) => {
|
|||||||
for (let i = 0; i < tunnel.length; i++) {
|
for (let i = 0; i < tunnel.length; i++) {
|
||||||
const { x, y } = tunnel[i];
|
const { x, y } = tunnel[i];
|
||||||
const c = clearResidualColoredLayer_getColorSafe(grid, x, y);
|
const c = clearResidualColoredLayer_getColorSafe(grid, x, y);
|
||||||
if (!(0,types_grid/* isEmpty */.Im)(c) && i === tunnel.findIndex((p) => p.x === x && p.y === y)) {
|
if (!(0,types_grid/* isEmpty */.xb)(c) && i === tunnel.findIndex((p) => p.x === x && p.y === y)) {
|
||||||
if (c === color)
|
if (c === color)
|
||||||
nColor += 1;
|
nColor += 1;
|
||||||
else
|
else
|
||||||
@@ -443,10 +470,10 @@ const getPriority = (grid, color, tunnel) => {
|
|||||||
return nLess / nColor;
|
return nLess / nColor;
|
||||||
};
|
};
|
||||||
const distanceSq = (ax, ay, bx, by) => (ax - bx) ** 2 + (ay - by) ** 2;
|
const distanceSq = (ax, ay, bx, by) => (ax - bx) ** 2 + (ay - by) ** 2;
|
||||||
const clearResidualColoredLayer_getColorSafe = (grid, x, y) => (0,types_grid/* isInside */.FK)(grid, x, y) ? (0,types_grid/* getColor */.oU)(grid, x, y) : 0;
|
const clearResidualColoredLayer_getColorSafe = (grid, x, y) => (0,types_grid/* isInside */.V0)(grid, x, y) ? (0,types_grid/* getColor */.Lq)(grid, x, y) : 0;
|
||||||
const clearResidualColoredLayer_setEmptySafe = (grid, x, y) => {
|
const clearResidualColoredLayer_setEmptySafe = (grid, x, y) => {
|
||||||
if ((0,types_grid/* isInside */.FK)(grid, x, y))
|
if ((0,types_grid/* isInside */.V0)(grid, x, y))
|
||||||
(0,types_grid/* setColorEmpty */.l$)(grid, x, y);
|
(0,types_grid/* setColorEmpty */.Dy)(grid, x, y);
|
||||||
};
|
};
|
||||||
|
|
||||||
;// CONCATENATED MODULE: ../solver/clearCleanColoredLayer.ts
|
;// CONCATENATED MODULE: ../solver/clearCleanColoredLayer.ts
|
||||||
@@ -456,14 +483,14 @@ const clearResidualColoredLayer_setEmptySafe = (grid, x, y) => {
|
|||||||
|
|
||||||
|
|
||||||
const clearCleanColoredLayer = (grid, outside, snake0, color) => {
|
const clearCleanColoredLayer = (grid, outside, snake0, color) => {
|
||||||
const snakeN = (0,types_snake/* getSnakeLength */.T$)(snake0);
|
const snakeN = (0,types_snake/* getSnakeLength */.JJ)(snake0);
|
||||||
const points = clearCleanColoredLayer_getTunnellablePoints(grid, outside, snakeN, color);
|
const points = clearCleanColoredLayer_getTunnellablePoints(grid, outside, snakeN, color);
|
||||||
const chain = [snake0];
|
const chain = [snake0];
|
||||||
while (points.length) {
|
while (points.length) {
|
||||||
const path = getPathToNextPoint(grid, chain[0], color, points);
|
const path = getPathToNextPoint(grid, chain[0], color, points);
|
||||||
path.pop();
|
path.pop();
|
||||||
for (const snake of path)
|
for (const snake of path)
|
||||||
clearCleanColoredLayer_setEmptySafe(grid, (0,types_snake/* getHeadX */.tN)(snake), (0,types_snake/* getHeadY */.Ap)(snake));
|
clearCleanColoredLayer_setEmptySafe(grid, (0,types_snake/* getHeadX */.If)(snake), (0,types_snake/* getHeadY */.IP)(snake));
|
||||||
chain.unshift(...path);
|
chain.unshift(...path);
|
||||||
}
|
}
|
||||||
fillOutside(outside, grid);
|
fillOutside(outside, grid);
|
||||||
@@ -476,19 +503,19 @@ const getPathToNextPoint = (grid, snake0, color, points) => {
|
|||||||
const openList = [{ snake: snake0 }];
|
const openList = [{ snake: snake0 }];
|
||||||
while (openList.length) {
|
while (openList.length) {
|
||||||
const o = openList.shift();
|
const o = openList.shift();
|
||||||
const x = (0,types_snake/* getHeadX */.tN)(o.snake);
|
const x = (0,types_snake/* getHeadX */.If)(o.snake);
|
||||||
const y = (0,types_snake/* getHeadY */.Ap)(o.snake);
|
const y = (0,types_snake/* getHeadY */.IP)(o.snake);
|
||||||
const i = points.findIndex((p) => p.x === x && p.y === y);
|
const i = points.findIndex((p) => p.x === x && p.y === y);
|
||||||
if (i >= 0) {
|
if (i >= 0) {
|
||||||
points.splice(i, 1);
|
points.splice(i, 1);
|
||||||
return clearCleanColoredLayer_unwrap(o);
|
return clearCleanColoredLayer_unwrap(o);
|
||||||
}
|
}
|
||||||
for (const { x: dx, y: dy } of around4) {
|
for (const { x: dx, y: dy } of around4) {
|
||||||
if ((0,types_grid/* isInsideLarge */.Yd)(grid, 2, x + dx, y + dy) &&
|
if ((0,types_grid/* isInsideLarge */.HJ)(grid, 2, x + dx, y + dy) &&
|
||||||
!(0,types_snake/* snakeWillSelfCollide */.J)(o.snake, dx, dy) &&
|
!(0,types_snake/* snakeWillSelfCollide */.nJ)(o.snake, dx, dy) &&
|
||||||
clearCleanColoredLayer_getColorSafe(grid, x + dx, y + dy) <= color) {
|
clearCleanColoredLayer_getColorSafe(grid, x + dx, y + dy) <= color) {
|
||||||
const snake = (0,types_snake/* nextSnake */.Sc)(o.snake, dx, dy);
|
const snake = (0,types_snake/* nextSnake */.kv)(o.snake, dx, dy);
|
||||||
if (!closeList.some((s0) => (0,types_snake/* snakeEquals */.sW)(s0, snake))) {
|
if (!closeList.some((s0) => (0,types_snake/* snakeEquals */.kE)(s0, snake))) {
|
||||||
closeList.push(snake);
|
closeList.push(snake);
|
||||||
openList.push({ snake, parent: o });
|
openList.push({ snake, parent: o });
|
||||||
}
|
}
|
||||||
@@ -503,8 +530,8 @@ const clearCleanColoredLayer_getTunnellablePoints = (grid, outside, snakeN, colo
|
|||||||
const points = [];
|
const points = [];
|
||||||
for (let x = grid.width; x--;)
|
for (let x = grid.width; x--;)
|
||||||
for (let y = grid.height; y--;) {
|
for (let y = grid.height; y--;) {
|
||||||
const c = (0,types_grid/* getColor */.oU)(grid, x, y);
|
const c = (0,types_grid/* getColor */.Lq)(grid, x, y);
|
||||||
if (!(0,types_grid/* isEmpty */.Im)(c) &&
|
if (!(0,types_grid/* isEmpty */.xb)(c) &&
|
||||||
c <= color &&
|
c <= color &&
|
||||||
!points.some((p) => p.x === x && p.y === y)) {
|
!points.some((p) => p.x === x && p.y === y)) {
|
||||||
const tunnel = getBestTunnel(grid, outside, x, y, color, snakeN);
|
const tunnel = getBestTunnel(grid, outside, x, y, color, snakeN);
|
||||||
@@ -516,12 +543,12 @@ const clearCleanColoredLayer_getTunnellablePoints = (grid, outside, snakeN, colo
|
|||||||
}
|
}
|
||||||
return points;
|
return points;
|
||||||
};
|
};
|
||||||
const clearCleanColoredLayer_getColorSafe = (grid, x, y) => (0,types_grid/* isInside */.FK)(grid, x, y) ? (0,types_grid/* getColor */.oU)(grid, x, y) : 0;
|
const clearCleanColoredLayer_getColorSafe = (grid, x, y) => (0,types_grid/* isInside */.V0)(grid, x, y) ? (0,types_grid/* getColor */.Lq)(grid, x, y) : 0;
|
||||||
const clearCleanColoredLayer_setEmptySafe = (grid, x, y) => {
|
const clearCleanColoredLayer_setEmptySafe = (grid, x, y) => {
|
||||||
if ((0,types_grid/* isInside */.FK)(grid, x, y))
|
if ((0,types_grid/* isInside */.V0)(grid, x, y))
|
||||||
(0,types_grid/* setColorEmpty */.l$)(grid, x, y);
|
(0,types_grid/* setColorEmpty */.Dy)(grid, x, y);
|
||||||
};
|
};
|
||||||
const clearCleanColoredLayer_isEmptySafe = (grid, x, y) => !(0,types_grid/* isInside */.FK)(grid, x, y) && (0,types_grid/* isEmpty */.Im)((0,types_grid/* getColor */.oU)(grid, x, y));
|
const clearCleanColoredLayer_isEmptySafe = (grid, x, y) => !(0,types_grid/* isInside */.V0)(grid, x, y) && (0,types_grid/* isEmpty */.xb)((0,types_grid/* getColor */.Lq)(grid, x, y));
|
||||||
|
|
||||||
;// CONCATENATED MODULE: ../solver/getBestRoute.ts
|
;// CONCATENATED MODULE: ../solver/getBestRoute.ts
|
||||||
|
|
||||||
@@ -529,7 +556,7 @@ const clearCleanColoredLayer_isEmptySafe = (grid, x, y) => !(0,types_grid/* isIn
|
|||||||
|
|
||||||
|
|
||||||
const getBestRoute = (grid0, snake0) => {
|
const getBestRoute = (grid0, snake0) => {
|
||||||
const grid = (0,types_grid/* copyGrid */.mi)(grid0);
|
const grid = (0,types_grid/* copyGrid */.VJ)(grid0);
|
||||||
const outside = createOutside(grid);
|
const outside = createOutside(grid);
|
||||||
const chain = [snake0];
|
const chain = [snake0];
|
||||||
for (const color of extractColors(grid)) {
|
for (const color of extractColors(grid)) {
|
||||||
@@ -547,7 +574,7 @@ const extractColors = (grid) => {
|
|||||||
|
|
||||||
;// CONCATENATED MODULE: ../types/__fixtures__/snake.ts
|
;// CONCATENATED MODULE: ../types/__fixtures__/snake.ts
|
||||||
|
|
||||||
const create = (length) => (0,types_snake/* createSnakeFromCells */.yS)(Array.from({ length }, (_, i) => ({ x: i, y: -1 })));
|
const create = (length) => (0,types_snake/* createSnakeFromCells */.xG)(Array.from({ length }, (_, i) => ({ x: i, y: -1 })));
|
||||||
const snake1 = create(1);
|
const snake1 = create(1);
|
||||||
const snake3 = create(3);
|
const snake3 = create(3);
|
||||||
const snake4 = create(4);
|
const snake4 = create(4);
|
||||||
@@ -560,20 +587,20 @@ const snake9 = create(9);
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
const getPathToPose_isEmptySafe = (grid, x, y) => !(0,types_grid/* isInside */.FK)(grid, x, y) || (0,types_grid/* isEmpty */.Im)((0,types_grid/* getColor */.oU)(grid, x, y));
|
const getPathToPose_isEmptySafe = (grid, x, y) => !(0,types_grid/* isInside */.V0)(grid, x, y) || (0,types_grid/* isEmpty */.xb)((0,types_grid/* getColor */.Lq)(grid, x, y));
|
||||||
const getPathToPose = (snake0, target, grid) => {
|
const getPathToPose = (snake0, target, grid) => {
|
||||||
if ((0,types_snake/* snakeEquals */.sW)(snake0, target))
|
if ((0,types_snake/* snakeEquals */.kE)(snake0, target))
|
||||||
return [];
|
return [];
|
||||||
const targetCells = (0,types_snake/* snakeToCells */.HU)(target).reverse();
|
const targetCells = (0,types_snake/* snakeToCells */.Ks)(target).reverse();
|
||||||
const snakeN = (0,types_snake/* getSnakeLength */.T$)(snake0);
|
const snakeN = (0,types_snake/* getSnakeLength */.JJ)(snake0);
|
||||||
const box = {
|
const box = {
|
||||||
min: {
|
min: {
|
||||||
x: Math.min((0,types_snake/* getHeadX */.tN)(snake0), (0,types_snake/* getHeadX */.tN)(target)) - snakeN - 1,
|
x: Math.min((0,types_snake/* getHeadX */.If)(snake0), (0,types_snake/* getHeadX */.If)(target)) - snakeN - 1,
|
||||||
y: Math.min((0,types_snake/* getHeadY */.Ap)(snake0), (0,types_snake/* getHeadY */.Ap)(target)) - snakeN - 1,
|
y: Math.min((0,types_snake/* getHeadY */.IP)(snake0), (0,types_snake/* getHeadY */.IP)(target)) - snakeN - 1,
|
||||||
},
|
},
|
||||||
max: {
|
max: {
|
||||||
x: Math.max((0,types_snake/* getHeadX */.tN)(snake0), (0,types_snake/* getHeadX */.tN)(target)) + snakeN + 1,
|
x: Math.max((0,types_snake/* getHeadX */.If)(snake0), (0,types_snake/* getHeadX */.If)(target)) + snakeN + 1,
|
||||||
y: Math.max((0,types_snake/* getHeadY */.Ap)(snake0), (0,types_snake/* getHeadY */.Ap)(target)) + snakeN + 1,
|
y: Math.max((0,types_snake/* getHeadY */.IP)(snake0), (0,types_snake/* getHeadY */.IP)(target)) + snakeN + 1,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const [t0, ...forbidden] = targetCells;
|
const [t0, ...forbidden] = targetCells;
|
||||||
@@ -582,8 +609,8 @@ const getPathToPose = (snake0, target, grid) => {
|
|||||||
const closeList = [];
|
const closeList = [];
|
||||||
while (openList.length) {
|
while (openList.length) {
|
||||||
const o = openList.shift();
|
const o = openList.shift();
|
||||||
const x = (0,types_snake/* getHeadX */.tN)(o.snake);
|
const x = (0,types_snake/* getHeadX */.If)(o.snake);
|
||||||
const y = (0,types_snake/* getHeadY */.Ap)(o.snake);
|
const y = (0,types_snake/* getHeadY */.IP)(o.snake);
|
||||||
if (x === t0.x && y === t0.y) {
|
if (x === t0.x && y === t0.y) {
|
||||||
const path = [];
|
const path = [];
|
||||||
let e = o;
|
let e = o;
|
||||||
@@ -600,17 +627,17 @@ const getPathToPose = (snake0, target, grid) => {
|
|||||||
const { x: dx, y: dy } = around4[i];
|
const { x: dx, y: dy } = around4[i];
|
||||||
const nx = x + dx;
|
const nx = x + dx;
|
||||||
const ny = y + dy;
|
const ny = y + dy;
|
||||||
if (!(0,types_snake/* snakeWillSelfCollide */.J)(o.snake, dx, dy) &&
|
if (!(0,types_snake/* snakeWillSelfCollide */.nJ)(o.snake, dx, dy) &&
|
||||||
(!grid || getPathToPose_isEmptySafe(grid, nx, ny)) &&
|
(!grid || getPathToPose_isEmptySafe(grid, nx, ny)) &&
|
||||||
(grid
|
(grid
|
||||||
? (0,types_grid/* isInsideLarge */.Yd)(grid, 2, nx, ny)
|
? (0,types_grid/* isInsideLarge */.HJ)(grid, 2, nx, ny)
|
||||||
: box.min.x <= nx &&
|
: box.min.x <= nx &&
|
||||||
nx <= box.max.x &&
|
nx <= box.max.x &&
|
||||||
box.min.y <= ny &&
|
box.min.y <= ny &&
|
||||||
ny <= box.max.y) &&
|
ny <= box.max.y) &&
|
||||||
!forbidden.some((p) => p.x === nx && p.y === ny)) {
|
!forbidden.some((p) => p.x === nx && p.y === ny)) {
|
||||||
const snake = (0,types_snake/* nextSnake */.Sc)(o.snake, dx, dy);
|
const snake = (0,types_snake/* nextSnake */.kv)(o.snake, dx, dy);
|
||||||
if (!closeList.some((s) => (0,types_snake/* snakeEquals */.sW)(snake, s))) {
|
if (!closeList.some((s) => (0,types_snake/* snakeEquals */.kE)(snake, s))) {
|
||||||
const w = o.w + 1;
|
const w = o.w + 1;
|
||||||
const h = Math.abs(nx - x) + Math.abs(ny - y);
|
const h = Math.abs(nx - x) + Math.abs(ny - y);
|
||||||
const f = w + h;
|
const f = w + h;
|
||||||
@@ -628,9 +655,9 @@ const getPathToPose = (snake0, target, grid) => {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
const generateContributionSnake = async (userName, outputs, options) => {
|
const generateContributionSnake = async (userName, outputs) => {
|
||||||
console.log("🎣 fetching github user contribution");
|
console.log("🎣 fetching github user contribution");
|
||||||
const cells = await getGithubUserContribution(userName, options);
|
const cells = await getGithubUserContribution(userName);
|
||||||
const grid = userContributionToGrid(cells);
|
const grid = userContributionToGrid(cells);
|
||||||
const snake = snake4;
|
const snake = snake4;
|
||||||
console.log("📡 computing best route");
|
console.log("📡 computing best route");
|
||||||
@@ -643,12 +670,12 @@ const generateContributionSnake = async (userName, outputs, options) => {
|
|||||||
switch (format) {
|
switch (format) {
|
||||||
case "svg": {
|
case "svg": {
|
||||||
console.log(`🖌 creating svg (outputs[${i}])`);
|
console.log(`🖌 creating svg (outputs[${i}])`);
|
||||||
const { createSvg } = await __webpack_require__.e(/* import() */ 578).then(__webpack_require__.bind(__webpack_require__, 4578));
|
const { createSvg } = await __webpack_require__.e(/* import() */ 340).then(__webpack_require__.bind(__webpack_require__, 8340));
|
||||||
return createSvg(grid, cells, chain, drawOptions, animationOptions);
|
return createSvg(grid, cells, chain, drawOptions, animationOptions);
|
||||||
}
|
}
|
||||||
case "gif": {
|
case "gif": {
|
||||||
console.log(`📹 creating gif (outputs[${i}])`);
|
console.log(`📹 creating gif (outputs[${i}])`);
|
||||||
const { createGif } = await Promise.all(/* import() */[__webpack_require__.e(155), __webpack_require__.e(642)]).then(__webpack_require__.bind(__webpack_require__, 3642));
|
const { createGif } = await Promise.all(/* import() */[__webpack_require__.e(371), __webpack_require__.e(142)]).then(__webpack_require__.bind(__webpack_require__, 7142));
|
||||||
return await createGif(grid, cells, chain, drawOptions, animationOptions);
|
return await createGif(grid, cells, chain, drawOptions, animationOptions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -658,18 +685,18 @@ const generateContributionSnake = async (userName, outputs, options) => {
|
|||||||
|
|
||||||
/***/ }),
|
/***/ }),
|
||||||
|
|
||||||
/***/ 105:
|
/***/ 2881:
|
||||||
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
|
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
|
||||||
|
|
||||||
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
||||||
/* harmony export */ FK: () => (/* binding */ isInside),
|
/* harmony export */ "V0": () => (/* binding */ isInside),
|
||||||
/* harmony export */ Im: () => (/* binding */ isEmpty),
|
/* harmony export */ "HJ": () => (/* binding */ isInsideLarge),
|
||||||
/* harmony export */ Kb: () => (/* binding */ createEmptyGrid),
|
/* harmony export */ "VJ": () => (/* binding */ copyGrid),
|
||||||
/* harmony export */ Yd: () => (/* binding */ isInsideLarge),
|
/* harmony export */ "Lq": () => (/* binding */ getColor),
|
||||||
/* harmony export */ l$: () => (/* binding */ setColorEmpty),
|
/* harmony export */ "xb": () => (/* binding */ isEmpty),
|
||||||
/* harmony export */ mi: () => (/* binding */ copyGrid),
|
/* harmony export */ "vk": () => (/* binding */ setColor),
|
||||||
/* harmony export */ oU: () => (/* binding */ getColor),
|
/* harmony export */ "Dy": () => (/* binding */ setColorEmpty),
|
||||||
/* harmony export */ wW: () => (/* binding */ setColor)
|
/* harmony export */ "u1": () => (/* binding */ createEmptyGrid)
|
||||||
/* harmony export */ });
|
/* harmony export */ });
|
||||||
/* unused harmony exports isGridEmpty, gridEquals */
|
/* unused harmony exports isGridEmpty, gridEquals */
|
||||||
const isInside = (grid, x, y) => x >= 0 && y >= 0 && x < grid.width && y < grid.height;
|
const isInside = (grid, x, y) => x >= 0 && y >= 0 && x < grid.width && y < grid.height;
|
||||||
@@ -702,18 +729,18 @@ const createEmptyGrid = (width, height) => ({
|
|||||||
|
|
||||||
/***/ }),
|
/***/ }),
|
||||||
|
|
||||||
/***/ 777:
|
/***/ 9347:
|
||||||
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
|
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
|
||||||
|
|
||||||
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
||||||
/* harmony export */ Ap: () => (/* binding */ getHeadY),
|
/* harmony export */ "If": () => (/* binding */ getHeadX),
|
||||||
/* harmony export */ HU: () => (/* binding */ snakeToCells),
|
/* harmony export */ "IP": () => (/* binding */ getHeadY),
|
||||||
/* harmony export */ J: () => (/* binding */ snakeWillSelfCollide),
|
/* harmony export */ "JJ": () => (/* binding */ getSnakeLength),
|
||||||
/* harmony export */ Sc: () => (/* binding */ nextSnake),
|
/* harmony export */ "kE": () => (/* binding */ snakeEquals),
|
||||||
/* harmony export */ T$: () => (/* binding */ getSnakeLength),
|
/* harmony export */ "kv": () => (/* binding */ nextSnake),
|
||||||
/* harmony export */ sW: () => (/* binding */ snakeEquals),
|
/* harmony export */ "nJ": () => (/* binding */ snakeWillSelfCollide),
|
||||||
/* harmony export */ tN: () => (/* binding */ getHeadX),
|
/* harmony export */ "Ks": () => (/* binding */ snakeToCells),
|
||||||
/* harmony export */ yS: () => (/* binding */ createSnakeFromCells)
|
/* harmony export */ "xG": () => (/* binding */ createSnakeFromCells)
|
||||||
/* harmony export */ });
|
/* harmony export */ });
|
||||||
/* unused harmony export copySnake */
|
/* unused harmony export copySnake */
|
||||||
const getHeadX = (snake) => snake[0] - 2;
|
const getHeadX = (snake) => snake[0] - 2;
|
||||||
@@ -1,21 +1,23 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
exports.id = 578;
|
exports.id = 340;
|
||||||
exports.ids = [578];
|
exports.ids = [340];
|
||||||
exports.modules = {
|
exports.modules = {
|
||||||
|
|
||||||
/***/ 4578:
|
/***/ 8340:
|
||||||
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
|
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
|
||||||
|
|
||||||
|
// ESM COMPAT FLAG
|
||||||
|
__webpack_require__.r(__webpack_exports__);
|
||||||
|
|
||||||
// EXPORTS
|
// EXPORTS
|
||||||
__webpack_require__.d(__webpack_exports__, {
|
__webpack_require__.d(__webpack_exports__, {
|
||||||
createSvg: () => (/* binding */ createSvg)
|
"createSvg": () => (/* binding */ createSvg)
|
||||||
});
|
});
|
||||||
|
|
||||||
// EXTERNAL MODULE: ../types/grid.ts
|
// EXTERNAL MODULE: ../types/grid.ts
|
||||||
var types_grid = __webpack_require__(105);
|
var types_grid = __webpack_require__(2881);
|
||||||
// EXTERNAL MODULE: ../types/snake.ts
|
// EXTERNAL MODULE: ../types/snake.ts
|
||||||
var types_snake = __webpack_require__(777);
|
var types_snake = __webpack_require__(9347);
|
||||||
;// CONCATENATED MODULE: ../svg-creator/xml-utils.ts
|
;// CONCATENATED MODULE: ../svg-creator/xml-utils.ts
|
||||||
const h = (element, attributes) => `<${element} ${toAttribute(attributes)}/>`;
|
const h = (element, attributes) => `<${element} ${toAttribute(attributes)}/>`;
|
||||||
const toAttribute = (o) => Object.entries(o)
|
const toAttribute = (o) => Object.entries(o)
|
||||||
@@ -60,10 +62,10 @@ const minifyCss = (css) => css
|
|||||||
|
|
||||||
const lerp = (k, a, b) => (1 - k) * a + k * b;
|
const lerp = (k, a, b) => (1 - k) * a + k * b;
|
||||||
const createSnake = (chain, { sizeCell, sizeDot }, duration) => {
|
const createSnake = (chain, { sizeCell, sizeDot }, duration) => {
|
||||||
const snakeN = chain[0] ? (0,types_snake/* getSnakeLength */.T$)(chain[0]) : 0;
|
const snakeN = chain[0] ? (0,types_snake/* getSnakeLength */.JJ)(chain[0]) : 0;
|
||||||
const snakeParts = Array.from({ length: snakeN }, () => []);
|
const snakeParts = Array.from({ length: snakeN }, () => []);
|
||||||
for (const snake of chain) {
|
for (const snake of chain) {
|
||||||
const cells = (0,types_snake/* snakeToCells */.HU)(snake);
|
const cells = (0,types_snake/* snakeToCells */.Ks)(snake);
|
||||||
for (let i = cells.length; i--;)
|
for (let i = cells.length; i--;)
|
||||||
snakeParts[i].push(cells[i]);
|
snakeParts[i].push(cells[i]);
|
||||||
}
|
}
|
||||||
@@ -235,15 +237,15 @@ const createLivingCells = (grid0, chain, cells) => {
|
|||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
t: null,
|
t: null,
|
||||||
color: (0,types_grid/* getColor */.oU)(grid0, x, y),
|
color: (0,types_grid/* getColor */.Lq)(grid0, x, y),
|
||||||
}));
|
}));
|
||||||
const grid = (0,types_grid/* copyGrid */.mi)(grid0);
|
const grid = (0,types_grid/* copyGrid */.VJ)(grid0);
|
||||||
for (let i = 0; i < chain.length; i++) {
|
for (let i = 0; i < chain.length; i++) {
|
||||||
const snake = chain[i];
|
const snake = chain[i];
|
||||||
const x = (0,types_snake/* getHeadX */.tN)(snake);
|
const x = (0,types_snake/* getHeadX */.If)(snake);
|
||||||
const y = (0,types_snake/* getHeadY */.Ap)(snake);
|
const y = (0,types_snake/* getHeadY */.IP)(snake);
|
||||||
if ((0,types_grid/* isInside */.FK)(grid, x, y) && !(0,types_grid/* isEmpty */.Im)((0,types_grid/* getColor */.oU)(grid, x, y))) {
|
if ((0,types_grid/* isInside */.V0)(grid, x, y) && !(0,types_grid/* isEmpty */.xb)((0,types_grid/* getColor */.Lq)(grid, x, y))) {
|
||||||
(0,types_grid/* setColorEmpty */.l$)(grid, x, y);
|
(0,types_grid/* setColorEmpty */.Dy)(grid, x, y);
|
||||||
const cell = livingCells.find((c) => c.x === x && c.y === y);
|
const cell = livingCells.find((c) => c.x === x && c.y === y);
|
||||||
cell.t = i / chain.length;
|
cell.t = i / chain.length;
|
||||||
}
|
}
|
||||||
5865
svg-only/dist/371.index.js
vendored
Normal file
5865
svg-only/dist/371.index.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user