Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fd9d7dadf6 | ||
|
|
73bfce908e | ||
|
|
dd23c1630e | ||
|
|
7377068a9a | ||
|
|
8a06b668cd |
6
.github/workflows/deploy.yml
vendored
6
.github/workflows/deploy.yml
vendored
@@ -11,17 +11,17 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v1
|
- uses: actions/checkout@v1
|
||||||
- uses: actions/setup-node@v1
|
- uses: actions/setup-node@v1.4.2
|
||||||
with:
|
with:
|
||||||
node-version: 14
|
node-version: 14
|
||||||
|
|
||||||
- uses: bahmutov/npm-install@v1
|
- uses: bahmutov/npm-install@v1.4.1
|
||||||
|
|
||||||
- run: yarn build:demo
|
- run: yarn build:demo
|
||||||
env:
|
env:
|
||||||
BASE_PATHNAME: "snk"
|
BASE_PATHNAME: "snk"
|
||||||
|
|
||||||
- uses: crazy-max/ghaction-github-pages@068e494
|
- uses: crazy-max/ghaction-github-pages@v2.1.1
|
||||||
with:
|
with:
|
||||||
target_branch: gh-pages
|
target_branch: gh-pages
|
||||||
build_dir: packages/demo/dist
|
build_dir: packages/demo/dist
|
||||||
|
|||||||
35
.github/workflows/main.yml
vendored
35
.github/workflows/main.yml
vendored
@@ -7,23 +7,40 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
# - run: sudo apt-get install gifsicle graphicsmagick
|
- run: sudo apt-get install gifsicle graphicsmagick
|
||||||
- uses: actions/checkout@v1
|
- uses: actions/checkout@v1
|
||||||
- uses: actions/setup-node@v1
|
- uses: actions/setup-node@v1.4.2
|
||||||
with:
|
with:
|
||||||
node-version: 14
|
node-version: 14
|
||||||
|
|
||||||
- uses: bahmutov/npm-install@v1
|
- uses: bahmutov/npm-install@v1.4.1
|
||||||
|
|
||||||
# - run: yarn type
|
- run: yarn type
|
||||||
# - run: yarn lint
|
- run: yarn lint
|
||||||
# - run: yarn test --ci
|
- run: yarn test --ci
|
||||||
# - run: yarn build:lib
|
- run: yarn build:action
|
||||||
|
|
||||||
|
test-action:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- run: mkdir dist
|
||||||
|
|
||||||
- name: generate-snake-game-from-github-contribution-grid
|
- name: generate-snake-game-from-github-contribution-grid
|
||||||
|
id: snake-gif
|
||||||
uses: Platane/snk@master
|
uses: Platane/snk@master
|
||||||
with:
|
with:
|
||||||
github_user_name: platane
|
github_user_name: platane
|
||||||
|
gif_out_path: dist/github-contribution-grid-snake.gif
|
||||||
|
|
||||||
- run: ls
|
- name: ensure the generated file exists
|
||||||
- run: ls
|
run: |
|
||||||
|
ls -l ${{ steps.snake-gif.outputs.gif_out_path }}
|
||||||
|
test -f ${{ steps.snake-gif.outputs.gif_out_path }}
|
||||||
|
|
||||||
|
- uses: crazy-max/ghaction-github-pages@v2.1.1
|
||||||
|
with:
|
||||||
|
target_branch: output
|
||||||
|
build_dir: dist
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.MY_GITHUB_TOKEN_GH_PAGES }}
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -3,3 +3,4 @@ npm-debug.log*
|
|||||||
yarn-error.log*
|
yarn-error.log*
|
||||||
dist
|
dist
|
||||||
build
|
build
|
||||||
|
out.gif
|
||||||
18
Dockerfile
18
Dockerfile
@@ -4,12 +4,16 @@ RUN apt-get update \
|
|||||||
&& apt-get install -y --no-install-recommends gifsicle graphicsmagick \
|
&& apt-get install -y --no-install-recommends gifsicle graphicsmagick \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
COPY tsconfig.json package.json yarn.lock ./generate-snake-game-from-github-contribution-grid/
|
COPY tsconfig.json package.json yarn.lock /github/snk/
|
||||||
COPY packages ./generate-snake-game-from-github-contribution-grid/packages/
|
COPY packages /github/snk/packages
|
||||||
|
|
||||||
RUN ( cd ./generate-snake-game-from-github-contribution-grid ; yarn install --frozen-lockfile )
|
RUN ( \
|
||||||
|
cd /github/snk \
|
||||||
RUN ( cd ./generate-snake-game-from-github-contribution-grid ; yarn build:action )
|
&& find . \
|
||||||
|
&& yarn install --frozen-lockfile \
|
||||||
CMD ["node", "generate-snake-game-from-github-contribution-grid/packages/action/dist/index.js"]
|
&& yarn build:action \
|
||||||
|
&& mv packages/action/dist/* . \
|
||||||
|
&& rm -rf packages tsconfig.json package.json yarn.lock node_modules \
|
||||||
|
)
|
||||||
|
|
||||||
|
CMD ["node", "/github/snk/index.js"]
|
||||||
|
|||||||
@@ -1,3 +1,12 @@
|
|||||||
# snk
|
# snk
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
Generates a snake game from a github user contributions grid and output a screen capture as gif
|
Generates a snake game from a github user contributions grid and output a screen capture as gif
|
||||||
|
|
||||||
|
- [demo](https://platane.github.io/snk/index.html)
|
||||||
|
|
||||||
|
- [github action](https://github.com/marketplace/actions/generate-snake-game-from-github-contribution-grid)
|
||||||
|
|||||||
@@ -9,9 +9,6 @@ outputs:
|
|||||||
runs:
|
runs:
|
||||||
using: "docker"
|
using: "docker"
|
||||||
image: "Dockerfile"
|
image: "Dockerfile"
|
||||||
args:
|
|
||||||
- ${{ inputs.github_user_name }}
|
|
||||||
- ${{ inputs.gif_out_path }}
|
|
||||||
|
|
||||||
inputs:
|
inputs:
|
||||||
github_user_name:
|
github_user_name:
|
||||||
|
|||||||
3
packages/action/.gitignore
vendored
3
packages/action/.gitignore
vendored
@@ -1,3 +0,0 @@
|
|||||||
!dist
|
|
||||||
!dist/build
|
|
||||||
out.gif
|
|
||||||
BIN
packages/action/dist/build/Release/canvas.node
vendored
BIN
packages/action/dist/build/Release/canvas.node
vendored
Binary file not shown.
BIN
packages/action/dist/build/Release/libcairo.so.2
vendored
BIN
packages/action/dist/build/Release/libcairo.so.2
vendored
Binary file not shown.
BIN
packages/action/dist/build/Release/libcroco-0.6.so.3
vendored
BIN
packages/action/dist/build/Release/libcroco-0.6.so.3
vendored
Binary file not shown.
BIN
packages/action/dist/build/Release/libexpat.so.1
vendored
BIN
packages/action/dist/build/Release/libexpat.so.1
vendored
Binary file not shown.
BIN
packages/action/dist/build/Release/libffi.so.6
vendored
BIN
packages/action/dist/build/Release/libffi.so.6
vendored
Binary file not shown.
Binary file not shown.
BIN
packages/action/dist/build/Release/libfreetype.so.6
vendored
BIN
packages/action/dist/build/Release/libfreetype.so.6
vendored
Binary file not shown.
BIN
packages/action/dist/build/Release/libfribidi.so.0
vendored
BIN
packages/action/dist/build/Release/libfribidi.so.0
vendored
Binary file not shown.
Binary file not shown.
BIN
packages/action/dist/build/Release/libgif.so.7
vendored
BIN
packages/action/dist/build/Release/libgif.so.7
vendored
Binary file not shown.
BIN
packages/action/dist/build/Release/libgio-2.0.so.0
vendored
BIN
packages/action/dist/build/Release/libgio-2.0.so.0
vendored
Binary file not shown.
BIN
packages/action/dist/build/Release/libglib-2.0.so.0
vendored
BIN
packages/action/dist/build/Release/libglib-2.0.so.0
vendored
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
packages/action/dist/build/Release/libharfbuzz.so.0
vendored
BIN
packages/action/dist/build/Release/libharfbuzz.so.0
vendored
Binary file not shown.
BIN
packages/action/dist/build/Release/libjpeg.so.62
vendored
BIN
packages/action/dist/build/Release/libjpeg.so.62
vendored
Binary file not shown.
BIN
packages/action/dist/build/Release/libpango-1.0.so.0
vendored
BIN
packages/action/dist/build/Release/libpango-1.0.so.0
vendored
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
packages/action/dist/build/Release/libpcre.so.1
vendored
BIN
packages/action/dist/build/Release/libpcre.so.1
vendored
Binary file not shown.
BIN
packages/action/dist/build/Release/libpixman-1.so.0
vendored
BIN
packages/action/dist/build/Release/libpixman-1.so.0
vendored
Binary file not shown.
BIN
packages/action/dist/build/Release/libpng16.so.16
vendored
BIN
packages/action/dist/build/Release/libpng16.so.16
vendored
Binary file not shown.
BIN
packages/action/dist/build/Release/librsvg-2.so.2
vendored
BIN
packages/action/dist/build/Release/librsvg-2.so.2
vendored
Binary file not shown.
BIN
packages/action/dist/build/Release/libstdc++.so.6
vendored
BIN
packages/action/dist/build/Release/libstdc++.so.6
vendored
Binary file not shown.
BIN
packages/action/dist/build/Release/libxml2.so.2
vendored
BIN
packages/action/dist/build/Release/libxml2.so.2
vendored
Binary file not shown.
BIN
packages/action/dist/build/Release/libz.so.1
vendored
BIN
packages/action/dist/build/Release/libz.so.1
vendored
Binary file not shown.
174133
packages/action/dist/index.js
vendored
174133
packages/action/dist/index.js
vendored
File diff suppressed because one or more lines are too long
84
packages/action/dist/index1.js
vendored
84
packages/action/dist/index1.js
vendored
@@ -1,84 +0,0 @@
|
|||||||
const Canvas = require('./lib/canvas')
|
|
||||||
const Image = require('./lib/image')
|
|
||||||
const CanvasRenderingContext2D = require('./lib/context2d')
|
|
||||||
const parseFont = require('./lib/parse-font')
|
|
||||||
const packageJson = require('./package.json')
|
|
||||||
const bindings = require('./lib/bindings')
|
|
||||||
const fs = require('fs')
|
|
||||||
const PNGStream = require('./lib/pngstream')
|
|
||||||
const PDFStream = require('./lib/pdfstream')
|
|
||||||
const JPEGStream = require('./lib/jpegstream')
|
|
||||||
const DOMMatrix = require('./lib/DOMMatrix').DOMMatrix
|
|
||||||
const DOMPoint = require('./lib/DOMMatrix').DOMPoint
|
|
||||||
|
|
||||||
function createCanvas (width, height, type) {
|
|
||||||
return new Canvas(width, height, type)
|
|
||||||
}
|
|
||||||
|
|
||||||
function createImageData (array, width, height) {
|
|
||||||
return new bindings.ImageData(array, width, height)
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadImage (src) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const image = new Image()
|
|
||||||
|
|
||||||
function cleanup () {
|
|
||||||
image.onload = null
|
|
||||||
image.onerror = null
|
|
||||||
}
|
|
||||||
|
|
||||||
image.onload = () => { cleanup(); resolve(image) }
|
|
||||||
image.onerror = (err) => { cleanup(); reject(err) }
|
|
||||||
|
|
||||||
image.src = src
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolve paths for registerFont. Must be called *before* creating a Canvas
|
|
||||||
* instance.
|
|
||||||
* @param src {string} Path to font file.
|
|
||||||
* @param fontFace {{family: string, weight?: string, style?: string}} Object
|
|
||||||
* specifying font information. `weight` and `style` default to `"normal"`.
|
|
||||||
*/
|
|
||||||
function registerFont (src, fontFace) {
|
|
||||||
// TODO this doesn't need to be on Canvas; it should just be a static method
|
|
||||||
// of `bindings`.
|
|
||||||
return Canvas._registerFont(fs.realpathSync(src), fontFace)
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
Canvas,
|
|
||||||
Context2d: CanvasRenderingContext2D, // Legacy/compat export
|
|
||||||
CanvasRenderingContext2D,
|
|
||||||
CanvasGradient: bindings.CanvasGradient,
|
|
||||||
CanvasPattern: bindings.CanvasPattern,
|
|
||||||
Image,
|
|
||||||
ImageData: bindings.ImageData,
|
|
||||||
PNGStream,
|
|
||||||
PDFStream,
|
|
||||||
JPEGStream,
|
|
||||||
DOMMatrix,
|
|
||||||
DOMPoint,
|
|
||||||
|
|
||||||
registerFont,
|
|
||||||
parseFont,
|
|
||||||
|
|
||||||
createCanvas,
|
|
||||||
createImageData,
|
|
||||||
loadImage,
|
|
||||||
|
|
||||||
backends: bindings.Backends,
|
|
||||||
|
|
||||||
/** Library version. */
|
|
||||||
version: packageJson.version,
|
|
||||||
/** Cairo version. */
|
|
||||||
cairoVersion: bindings.cairoVersion,
|
|
||||||
/** jpeglib version. */
|
|
||||||
jpegVersion: bindings.jpegVersion,
|
|
||||||
/** gif_lib version. */
|
|
||||||
gifVersion: bindings.gifVersion ? bindings.gifVersion.replace(/[^.\d]/g, '') : undefined,
|
|
||||||
/** freetype version. */
|
|
||||||
freetypeVersion: bindings.freetypeVersion
|
|
||||||
}
|
|
||||||
60
packages/action/dist/xhr-sync-worker.js
vendored
60
packages/action/dist/xhr-sync-worker.js
vendored
@@ -1,60 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
/* eslint-disable no-process-exit */
|
|
||||||
const util = require("util");
|
|
||||||
const { JSDOM } = require("../../../..");
|
|
||||||
const { READY_STATES } = require("./xhr-utils");
|
|
||||||
const idlUtils = require("../generated/utils");
|
|
||||||
const tough = require("tough-cookie");
|
|
||||||
|
|
||||||
const dom = new JSDOM();
|
|
||||||
const xhr = new dom.window.XMLHttpRequest();
|
|
||||||
const xhrImpl = idlUtils.implForWrapper(xhr);
|
|
||||||
|
|
||||||
const chunks = [];
|
|
||||||
|
|
||||||
process.stdin.on("data", chunk => {
|
|
||||||
chunks.push(chunk);
|
|
||||||
});
|
|
||||||
|
|
||||||
process.stdin.on("end", () => {
|
|
||||||
const buffer = Buffer.concat(chunks);
|
|
||||||
|
|
||||||
const flag = JSON.parse(buffer.toString());
|
|
||||||
if (flag.body && flag.body.type === "Buffer" && flag.body.data) {
|
|
||||||
flag.body = Buffer.from(flag.body.data);
|
|
||||||
}
|
|
||||||
if (flag.cookieJar) {
|
|
||||||
flag.cookieJar = tough.CookieJar.fromJSON(flag.cookieJar);
|
|
||||||
}
|
|
||||||
|
|
||||||
flag.synchronous = false;
|
|
||||||
Object.assign(xhrImpl.flag, flag);
|
|
||||||
const { properties } = xhrImpl;
|
|
||||||
xhrImpl.readyState = READY_STATES.OPENED;
|
|
||||||
try {
|
|
||||||
xhr.addEventListener("loadend", () => {
|
|
||||||
if (properties.error) {
|
|
||||||
properties.error = properties.error.stack || util.inspect(properties.error);
|
|
||||||
}
|
|
||||||
process.stdout.write(JSON.stringify({
|
|
||||||
responseURL: xhrImpl.responseURL,
|
|
||||||
status: xhrImpl.status,
|
|
||||||
statusText: xhrImpl.statusText,
|
|
||||||
properties
|
|
||||||
}), () => {
|
|
||||||
process.exit(0);
|
|
||||||
});
|
|
||||||
}, false);
|
|
||||||
xhr.send(flag.body);
|
|
||||||
} catch (error) {
|
|
||||||
properties.error += error.stack || util.inspect(error);
|
|
||||||
process.stdout.write(JSON.stringify({
|
|
||||||
responseURL: xhrImpl.responseURL,
|
|
||||||
status: xhrImpl.status,
|
|
||||||
statusText: xhrImpl.statusText,
|
|
||||||
properties
|
|
||||||
}), () => {
|
|
||||||
process.exit(0);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@@ -37,7 +37,10 @@ export const generateContributionSnake = async (userName: string) => {
|
|||||||
colorSnake: "purple",
|
colorSnake: "purple",
|
||||||
};
|
};
|
||||||
|
|
||||||
const gameOptions = { maxSnakeLength: 5 };
|
const gameOptions = {
|
||||||
|
maxSnakeLength: 5,
|
||||||
|
colors: Array.from({ length: colorScheme.length - 1 }, (_, i) => i + 1),
|
||||||
|
};
|
||||||
|
|
||||||
const gifOptions = { delay: 10 };
|
const gifOptions = { delay: 10 };
|
||||||
|
|
||||||
|
|||||||
@@ -4,23 +4,14 @@ import { generateContributionSnake } from "./generateContributionSnake";
|
|||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
console.log("argv", process.argv);
|
const userName = core.getInput("github_user_name");
|
||||||
|
const gifOutPath = core.getInput("gif_out_path");
|
||||||
|
|
||||||
console.log(core.getInput("user_name"));
|
const buffer = await generateContributionSnake(userName);
|
||||||
console.log(core.getInput("gif_out_path"));
|
|
||||||
console.log("--");
|
|
||||||
console.log("--");
|
|
||||||
console.log(process.cwd());
|
|
||||||
console.log("--");
|
|
||||||
console.log(fs.readdirSync(process.cwd()));
|
|
||||||
console.log("--");
|
|
||||||
console.log("--");
|
|
||||||
console.log(process.env.GITHUB_WORKSPACE);
|
|
||||||
console.log("--");
|
|
||||||
console.log(fs.readdirSync(process.cwd()));
|
|
||||||
|
|
||||||
const buffer = await generateContributionSnake(core.getInput("user_name"));
|
fs.writeFileSync(gifOutPath, buffer);
|
||||||
fs.writeFileSync(core.getInput("gif_out_path"), buffer);
|
|
||||||
|
console.log(`::set-output name=gif_out_path::${gifOutPath}`);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
core.setFailed(`Action failed with "${e.message}"`);
|
core.setFailed(`Action failed with "${e.message}"`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ import { Grid, Color } from "./grid";
|
|||||||
const rand = (a: number, b: number) => Math.floor(Math.random() * (b - a)) + a;
|
const rand = (a: number, b: number) => Math.floor(Math.random() * (b - a)) + a;
|
||||||
|
|
||||||
export const generateEmptyGrid = (width: number, height: number) =>
|
export const generateEmptyGrid = (width: number, height: number) =>
|
||||||
generateGrid(width, height, { colors: [], emptyP: 1 });
|
generateRandomGrid(width, height, { colors: [], emptyP: 1 });
|
||||||
|
|
||||||
export const generateGrid = (
|
export const generateRandomGrid = (
|
||||||
width: number,
|
width: number,
|
||||||
height: number,
|
height: number,
|
||||||
options: { colors: Color[]; emptyP: number } = {
|
options: { colors: Color[]; emptyP: number } = {
|
||||||
|
|||||||
@@ -1,44 +1,167 @@
|
|||||||
import { Grid, Color, copyGrid, isInsideLarge } from "./grid";
|
import { Grid, Color, copyGrid, isInsideLarge, getColor } from "./grid";
|
||||||
import { Point, around4 } from "./point";
|
import { Point, around4 } from "./point";
|
||||||
import { stepSnake, step } from "./step";
|
import { step } from "./step";
|
||||||
import { copySnake, snakeSelfCollide } from "./snake";
|
import { copySnake, snakeSelfCollide, Snake } from "./snake";
|
||||||
|
|
||||||
const isGridEmpty = (grid: Grid) => grid.data.every((x) => x === null);
|
const isGridEmpty = (grid: Grid) => grid.data.every((x) => x === null);
|
||||||
|
|
||||||
export const computeBestRun = (
|
const createComputeHeuristic = (
|
||||||
grid: Grid,
|
grid0: Grid,
|
||||||
snake: Point[],
|
_snake0: Snake,
|
||||||
options: { maxSnakeLength: number }
|
colors: Color[]
|
||||||
) => {
|
) => {
|
||||||
const g = copyGrid(grid);
|
const colorCount: Record<Color, number> = {};
|
||||||
const s = copySnake(snake);
|
for (let x = grid0.width; x--; )
|
||||||
const q: Color[] = [];
|
for (let y = grid0.height; y--; ) {
|
||||||
|
const c = getColor(grid0, x, y);
|
||||||
const commands: Point[] = [];
|
if (c !== null) colorCount[c] = 1 + (colorCount[c] || 0);
|
||||||
|
|
||||||
let u = 500;
|
|
||||||
|
|
||||||
while (!isGridEmpty(g) && u-- > 0) {
|
|
||||||
let direction;
|
|
||||||
|
|
||||||
for (let k = 10; k--; ) {
|
|
||||||
direction = around4[Math.floor(Math.random() * around4.length)];
|
|
||||||
|
|
||||||
const sn = copySnake(s);
|
|
||||||
stepSnake(sn, direction, options);
|
|
||||||
|
|
||||||
if (isInsideLarge(g, 1, sn[0].x, sn[0].y) && !snakeSelfCollide(sn)) {
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
direction = undefined;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (direction !== undefined) {
|
const values = colors
|
||||||
step(g, s, q, direction, options);
|
.map((k) => Array.from({ length: colorCount[k] }, () => k))
|
||||||
commands.push(direction);
|
.flat();
|
||||||
|
|
||||||
|
return (_grid: Grid, _snake: Snake, stack: Color[]) => {
|
||||||
|
let score = 0;
|
||||||
|
|
||||||
|
for (let i = 0; i < stack.length; i++) {
|
||||||
|
const k = Math.abs(stack[i] - values[i]);
|
||||||
|
score += k === 0 ? 100 : -100 * k;
|
||||||
|
}
|
||||||
|
|
||||||
|
return score;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const computeKey = (grid: Grid, snake: Snake, stack: Color[]) =>
|
||||||
|
grid.data.map((x) => x || 0).join("") +
|
||||||
|
"|" +
|
||||||
|
snake.map((p) => p.x + "." + p.y).join(",") +
|
||||||
|
"|" +
|
||||||
|
stack.join("");
|
||||||
|
|
||||||
|
const createCell = (
|
||||||
|
key: string,
|
||||||
|
grid: Grid,
|
||||||
|
snake: Snake,
|
||||||
|
stack: Color[],
|
||||||
|
direction: Point | null,
|
||||||
|
parent: any | null,
|
||||||
|
heuristic: number
|
||||||
|
) => ({
|
||||||
|
key,
|
||||||
|
parent,
|
||||||
|
direction,
|
||||||
|
grid,
|
||||||
|
snake,
|
||||||
|
stack,
|
||||||
|
weight: 1 + (parent?.weight || 0),
|
||||||
|
f: heuristic - 0 * (1 + (parent?.weight || 0)),
|
||||||
|
});
|
||||||
|
|
||||||
|
const unwrap = (c: ReturnType<typeof createCell> | null): Point[] =>
|
||||||
|
c && c.direction ? [...unwrap(c.parent), c.direction] : [];
|
||||||
|
// c && c.parent
|
||||||
|
// ? [
|
||||||
|
// ...unwrap(c.parent),
|
||||||
|
// { x: c.snake[1].x - c.snake[0].x, y: c.snake[1].y - c.snake[0].y },
|
||||||
|
// ]
|
||||||
|
// : [];
|
||||||
|
|
||||||
|
export const computeBestRun = (
|
||||||
|
grid0: Grid,
|
||||||
|
snake0: Snake,
|
||||||
|
options: { maxSnakeLength: number; colors: Color[] }
|
||||||
|
) => {
|
||||||
|
// const grid = copyGrid(grid0);
|
||||||
|
// const snake = copySnake(snake0);
|
||||||
|
// const stack: Color[] = [];
|
||||||
|
|
||||||
|
const computeHeuristic = createComputeHeuristic(
|
||||||
|
grid0,
|
||||||
|
snake0,
|
||||||
|
options.colors
|
||||||
|
);
|
||||||
|
|
||||||
|
const closeList: any = {};
|
||||||
|
const openList = [
|
||||||
|
createCell(
|
||||||
|
computeKey(grid0, snake0, []),
|
||||||
|
grid0,
|
||||||
|
snake0,
|
||||||
|
[],
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
computeHeuristic(grid0, snake0, [])
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
let u = 7000;
|
||||||
|
|
||||||
|
let best = openList[0];
|
||||||
|
|
||||||
|
while (openList.length && u-- > 0) {
|
||||||
|
openList.sort((a, b) => b.f - a.f);
|
||||||
|
const c = openList.shift()!;
|
||||||
|
|
||||||
|
closeList[c.key] = true;
|
||||||
|
|
||||||
|
if (isGridEmpty(c.grid)) return unwrap(c);
|
||||||
|
|
||||||
|
if (c.f > best.f) best = c;
|
||||||
|
|
||||||
|
for (const direction of around4) {
|
||||||
|
const snake = copySnake(c.snake);
|
||||||
|
const stack = c.stack.slice();
|
||||||
|
const grid = copyGrid(c.grid);
|
||||||
|
|
||||||
|
step(grid, snake, stack, direction, options);
|
||||||
|
|
||||||
|
const key = computeKey(grid, snake, stack);
|
||||||
|
|
||||||
|
if (
|
||||||
|
!closeList[key] &&
|
||||||
|
isInsideLarge(grid, 1, snake[0].x, snake[0].y) &&
|
||||||
|
!snakeSelfCollide(snake)
|
||||||
|
) {
|
||||||
|
openList.push(
|
||||||
|
createCell(
|
||||||
|
key,
|
||||||
|
grid,
|
||||||
|
snake,
|
||||||
|
stack,
|
||||||
|
direction,
|
||||||
|
c,
|
||||||
|
computeHeuristic(grid, snake, stack)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return commands;
|
return unwrap(best);
|
||||||
|
|
||||||
|
// while (!isGridEmpty(g) && u-- > 0) {
|
||||||
|
// let direction;
|
||||||
|
|
||||||
|
// for (let k = 10; k--; ) {
|
||||||
|
// direction = around4[Math.floor(Math.random() * around4.length)];
|
||||||
|
|
||||||
|
// const sn = copySnake(s);
|
||||||
|
// stepSnake(sn, direction, options);
|
||||||
|
|
||||||
|
// if (isInsideLarge(g, 1, sn[0].x, sn[0].y) && !snakeSelfCollide(sn)) {
|
||||||
|
// break;
|
||||||
|
// } else {
|
||||||
|
// direction = undefined;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (direction !== undefined) {
|
||||||
|
// step(g, s, q, direction, options);
|
||||||
|
// commands.push(direction);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return commands;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import { Point } from "./point";
|
import { Point } from "./point";
|
||||||
|
|
||||||
|
export type Snake = Point[];
|
||||||
|
|
||||||
export const snakeSelfCollideNext = (
|
export const snakeSelfCollideNext = (
|
||||||
snake: Point[],
|
snake: Snake,
|
||||||
direction: Point,
|
direction: Point,
|
||||||
options: { maxSnakeLength: number }
|
options: { maxSnakeLength: number }
|
||||||
) => {
|
) => {
|
||||||
@@ -14,11 +16,11 @@ export const snakeSelfCollideNext = (
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const snakeSelfCollide = (snake: Point[]) => {
|
export const snakeSelfCollide = (snake: Snake) => {
|
||||||
for (let i = 1; i < snake.length; i++)
|
for (let i = 1; i < snake.length; i++)
|
||||||
if (snake[i].x === snake[0].x && snake[i].y === snake[0].y) return true;
|
if (snake[i].x === snake[0].x && snake[i].y === snake[0].y) return true;
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const copySnake = (x: Point[]) => x.map((p) => ({ ...p }));
|
export const copySnake = (x: Snake) => x.map((p) => ({ ...p }));
|
||||||
|
|||||||
@@ -1,81 +1,91 @@
|
|||||||
// import { generateGrid } from "@snk/compute/generateGrid";
|
import { generateRandomGrid } from "@snk/compute/generateGrid";
|
||||||
|
|
||||||
import { generateGrid } from "@snk/compute/generateGrid";
|
|
||||||
import { Color, copyGrid } from "@snk/compute/grid";
|
import { Color, copyGrid } from "@snk/compute/grid";
|
||||||
import { computeBestRun } from "@snk/compute";
|
import { computeBestRun } from "@snk/compute";
|
||||||
import { step } from "@snk/compute/step";
|
import { step } from "@snk/compute/step";
|
||||||
import { drawWorld } from "@snk/draw/drawWorld";
|
import { drawWorld } from "@snk/draw/drawWorld";
|
||||||
import { Point } from "@snk/compute/point";
|
import { copySnake } from "@snk/compute/snake";
|
||||||
|
|
||||||
const copySnake = (x: Point[]) => x.map((p) => ({ ...p }));
|
const drawOptions = {
|
||||||
|
sizeBorderRadius: 2,
|
||||||
export const run = async () => {
|
sizeCell: 16,
|
||||||
const drawOptions = {
|
sizeDot: 12,
|
||||||
sizeBorderRadius: 2,
|
colorBorder: "#1b1f230a",
|
||||||
sizeCell: 16,
|
colorDots: { 1: "#9be9a8", 2: "#40c463", 3: "#30a14e", 4: "#216e39" },
|
||||||
sizeDot: 12,
|
colorEmpty: "#ebedf0",
|
||||||
colorBorder: "#1b1f230a",
|
colorSnake: "purple",
|
||||||
colorDots: { 1: "#9be9a8", 2: "#40c463", 3: "#30a14e", 4: "#216e39" },
|
|
||||||
colorEmpty: "#ebedf0",
|
|
||||||
colorSnake: "purple",
|
|
||||||
};
|
|
||||||
|
|
||||||
const gameOptions = { maxSnakeLength: 5 };
|
|
||||||
|
|
||||||
const grid0 = generateGrid(42, 7, { colors: [1, 2, 3, 4], emptyP: 3 });
|
|
||||||
|
|
||||||
const snake0 = [
|
|
||||||
{ x: 4, y: -1 },
|
|
||||||
{ x: 3, y: -1 },
|
|
||||||
{ x: 2, y: -1 },
|
|
||||||
{ x: 1, y: -1 },
|
|
||||||
{ x: 0, y: -1 },
|
|
||||||
];
|
|
||||||
const stack0: Color[] = [];
|
|
||||||
|
|
||||||
const chain = computeBestRun(grid0, snake0, gameOptions);
|
|
||||||
|
|
||||||
const canvas = document.createElement("canvas");
|
|
||||||
canvas.width = drawOptions.sizeCell * (grid0.width + 4);
|
|
||||||
canvas.height = drawOptions.sizeCell * (grid0.height + 4) + 100;
|
|
||||||
document.body.appendChild(canvas);
|
|
||||||
const ctx = canvas.getContext("2d")!;
|
|
||||||
|
|
||||||
const update = (n: number) => {
|
|
||||||
const snake = copySnake(snake0);
|
|
||||||
const stack = stack0.slice();
|
|
||||||
const grid = copyGrid(grid0);
|
|
||||||
|
|
||||||
for (let i = 0; i < n; i++) step(grid, snake, stack, chain[i], gameOptions);
|
|
||||||
|
|
||||||
ctx.clearRect(0, 0, 9999, 9999);
|
|
||||||
drawWorld(ctx, grid, snake, stack, drawOptions);
|
|
||||||
};
|
|
||||||
|
|
||||||
const input: any = document.createElement("input");
|
|
||||||
input.type = "range";
|
|
||||||
input.style.width = "100%";
|
|
||||||
input.min = 0;
|
|
||||||
input.max = chain.length;
|
|
||||||
input.step = 1;
|
|
||||||
input.value = 0;
|
|
||||||
input.addEventListener("input", () => update(+input.value));
|
|
||||||
document.addEventListener("click", () => input.focus());
|
|
||||||
|
|
||||||
document.body.appendChild(input);
|
|
||||||
|
|
||||||
update(+input.value);
|
|
||||||
|
|
||||||
// while (chain.length) {
|
|
||||||
// await wait(100);
|
|
||||||
|
|
||||||
// step(grid, snake, stack, chain.shift()!, gameOptions);
|
|
||||||
|
|
||||||
// ctx.clearRect(0, 0, 9999, 9999);
|
|
||||||
// drawWorld(ctx, grid, snake, stack, options);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// const wait = (delay = 0) => new Promise((r) => setTimeout(r, delay));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
run();
|
const gameOptions = { colors: [1, 2, 3, 4], maxSnakeLength: 5 };
|
||||||
|
|
||||||
|
const grid0 = generateRandomGrid(42, 7, { ...gameOptions, emptyP: 3 });
|
||||||
|
|
||||||
|
const snake0 = [
|
||||||
|
{ x: 4, y: -1 },
|
||||||
|
{ x: 3, y: -1 },
|
||||||
|
{ x: 2, y: -1 },
|
||||||
|
{ x: 1, y: -1 },
|
||||||
|
{ x: 0, y: -1 },
|
||||||
|
];
|
||||||
|
const stack0: Color[] = [];
|
||||||
|
|
||||||
|
const chain = computeBestRun(grid0, snake0, gameOptions);
|
||||||
|
|
||||||
|
//
|
||||||
|
// draw
|
||||||
|
|
||||||
|
const canvas = document.createElement("canvas");
|
||||||
|
canvas.width = drawOptions.sizeCell * (grid0.width + 4);
|
||||||
|
canvas.height = drawOptions.sizeCell * (grid0.height + 4) + 100;
|
||||||
|
document.body.appendChild(canvas);
|
||||||
|
const ctx = canvas.getContext("2d")!;
|
||||||
|
|
||||||
|
const update = (n: number) => {
|
||||||
|
const snake = copySnake(snake0);
|
||||||
|
const stack = stack0.slice();
|
||||||
|
const grid = copyGrid(grid0);
|
||||||
|
|
||||||
|
for (let i = 0; i < n; i++) step(grid, snake, stack, chain[i], gameOptions);
|
||||||
|
|
||||||
|
ctx.clearRect(0, 0, 9999, 9999);
|
||||||
|
drawWorld(ctx, grid, snake, stack, drawOptions);
|
||||||
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
// controls
|
||||||
|
|
||||||
|
const input: any = document.createElement("input");
|
||||||
|
input.type = "range";
|
||||||
|
input.style.width = "100%";
|
||||||
|
input.min = 0;
|
||||||
|
input.max = chain.length;
|
||||||
|
input.step = 1;
|
||||||
|
input.value = 0;
|
||||||
|
input.addEventListener("input", () => {
|
||||||
|
setAutoPlay(false);
|
||||||
|
update(+input.value);
|
||||||
|
});
|
||||||
|
document.addEventListener("click", () => input.focus());
|
||||||
|
|
||||||
|
document.body.appendChild(input);
|
||||||
|
|
||||||
|
const autoplayButton = document.createElement("button");
|
||||||
|
let cancel: any;
|
||||||
|
const loop = () => {
|
||||||
|
input.value = (+input.value + 1) % +input.max;
|
||||||
|
update(+input.value);
|
||||||
|
cancelAnimationFrame(cancel);
|
||||||
|
cancel = requestAnimationFrame(loop);
|
||||||
|
};
|
||||||
|
const setAutoPlay = (a: boolean) => {
|
||||||
|
autoplayButton.innerHTML = a ? "pause" : "play";
|
||||||
|
if (a) loop();
|
||||||
|
else cancelAnimationFrame(cancel);
|
||||||
|
};
|
||||||
|
autoplayButton.addEventListener("click", () => {
|
||||||
|
debugger;
|
||||||
|
setAutoPlay(autoplayButton.innerHTML === "play");
|
||||||
|
});
|
||||||
|
document.body.appendChild(autoplayButton);
|
||||||
|
|
||||||
|
setAutoPlay(true);
|
||||||
|
update(+input.value);
|
||||||
|
|||||||
@@ -2,7 +2,8 @@
|
|||||||
"name": "@snk/demo",
|
"name": "@snk/demo",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@snk/compute": "1.0.0"
|
"@snk/compute": "1.0.0",
|
||||||
|
"@snk/draw": "1.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"webpack": "4.43.0",
|
"webpack": "4.43.0",
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ const config: Configuration = {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
// game
|
|
||||||
new HtmlWebpackPlugin({
|
new HtmlWebpackPlugin({
|
||||||
title: "demo",
|
title: "demo",
|
||||||
filename: "index.html",
|
filename: "index.html",
|
||||||
@@ -39,9 +38,6 @@ const config: Configuration = {
|
|||||||
|
|
||||||
devtool: false,
|
devtool: false,
|
||||||
stats: "errors-only",
|
stats: "errors-only",
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
devServer: {},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default config;
|
export default config;
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ export const drawWorld = (
|
|||||||
) => {
|
) => {
|
||||||
ctx.save();
|
ctx.save();
|
||||||
|
|
||||||
ctx.translate(2 * o.sizeCell, 2 * o.sizeCell);
|
ctx.translate(1 * o.sizeCell, 2 * o.sizeCell);
|
||||||
drawGrid(ctx, grid, o);
|
drawGrid(ctx, grid, o);
|
||||||
drawSnake(ctx, snake, o);
|
drawSnake(ctx, snake, o);
|
||||||
|
|
||||||
|
|||||||
1
packages/gif-creator/.gitignore
vendored
1
packages/gif-creator/.gitignore
vendored
@@ -1 +0,0 @@
|
|||||||
out.gif
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { createGif } from "..";
|
import { createGif } from "..";
|
||||||
import { generateGrid } from "@snk/compute/generateGrid";
|
import { generateRandomGrid } from "@snk/compute/generateGrid";
|
||||||
import { computeBestRun } from "@snk/compute";
|
import { computeBestRun } from "@snk/compute";
|
||||||
|
|
||||||
const drawOptions = {
|
const drawOptions = {
|
||||||
@@ -12,12 +12,12 @@ const drawOptions = {
|
|||||||
colorSnake: "purple",
|
colorSnake: "purple",
|
||||||
};
|
};
|
||||||
|
|
||||||
const gameOptions = { maxSnakeLength: 5 };
|
const gameOptions = { maxSnakeLength: 5, colors: [1, 2, 3, 4] };
|
||||||
|
|
||||||
const gifOptions = { delay: 200 };
|
const gifOptions = { delay: 200 };
|
||||||
|
|
||||||
it("should generate gif", async () => {
|
it("should generate gif", async () => {
|
||||||
const grid = generateGrid(14, 7, { colors: [1, 2, 3, 4], emptyP: 3 });
|
const grid = generateRandomGrid(14, 7, { ...gameOptions, emptyP: 3 });
|
||||||
|
|
||||||
const snake = [
|
const snake = [
|
||||||
{ x: 4, y: -1 },
|
{ x: 4, y: -1 },
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { createGif } from "..";
|
import { createGif } from "..";
|
||||||
import { generateGrid } from "@snk/compute/generateGrid";
|
import { generateRandomGrid } from "@snk/compute/generateGrid";
|
||||||
import { computeBestRun } from "@snk/compute";
|
import { computeBestRun } from "@snk/compute";
|
||||||
|
|
||||||
const drawOptions = {
|
const drawOptions = {
|
||||||
@@ -12,11 +12,11 @@ const drawOptions = {
|
|||||||
colorSnake: "purple",
|
colorSnake: "purple",
|
||||||
};
|
};
|
||||||
|
|
||||||
const gameOptions = { maxSnakeLength: 5 };
|
const gameOptions = { maxSnakeLength: 5, colors: [1, 2, 3, 4] };
|
||||||
|
|
||||||
const gifOptions = { delay: 20 };
|
const gifOptions = { delay: 20 };
|
||||||
|
|
||||||
const grid = generateGrid(42, 7, { colors: [1, 2, 3, 4], emptyP: 3 });
|
const grid = generateRandomGrid(42, 7, { ...gameOptions, emptyP: 3 });
|
||||||
|
|
||||||
const snake = [
|
const snake = [
|
||||||
{ x: 4, y: -1 },
|
{ x: 4, y: -1 },
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import { copySnake } from "@snk/compute/snake";
|
|||||||
import { drawWorld } from "@snk/draw/drawWorld";
|
import { drawWorld } from "@snk/draw/drawWorld";
|
||||||
import { step } from "@snk/compute/step";
|
import { step } from "@snk/compute/step";
|
||||||
import * as tmp from "tmp";
|
import * as tmp from "tmp";
|
||||||
// @ts-ignore
|
|
||||||
import * as execa from "execa";
|
import * as execa from "execa";
|
||||||
|
|
||||||
export const createGif = async (
|
export const createGif = async (
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { getGithubUserContribution } from "..";
|
|||||||
it("should get user contribution", async () => {
|
it("should get user contribution", async () => {
|
||||||
const { cells, colorScheme } = await getGithubUserContribution("platane");
|
const { cells, colorScheme } = await getGithubUserContribution("platane");
|
||||||
|
|
||||||
expect(cells).toBeDefined();
|
expect(cells.length).toBeGreaterThan(300);
|
||||||
expect(colorScheme).toEqual([
|
expect(colorScheme).toEqual([
|
||||||
"#ebedf0",
|
"#ebedf0",
|
||||||
"#9be9a8",
|
"#9be9a8",
|
||||||
|
|||||||
@@ -1,26 +1,11 @@
|
|||||||
// import * as https from "https";
|
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
// import * as cheerio from "cheerio";
|
|
||||||
|
|
||||||
import { JSDOM } from "jsdom";
|
import { JSDOM } from "jsdom";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get the contribution grid from a github user page
|
||||||
|
*
|
||||||
|
* @param userName
|
||||||
|
*/
|
||||||
export const getGithubUserContribution = async (userName: string) => {
|
export const getGithubUserContribution = async (userName: string) => {
|
||||||
// const content: string = await new Promise((resolve, reject) => {
|
|
||||||
// const req = https.request(`https://github.com/${userName}`, (res) => {
|
|
||||||
// let data = "";
|
|
||||||
|
|
||||||
// res.on("error", reject);
|
|
||||||
// res.on("data", (chunk) => (data += chunk));
|
|
||||||
// res.on("end", () => resolve(data));
|
|
||||||
// });
|
|
||||||
|
|
||||||
// req.on("error", reject);
|
|
||||||
// req.end();
|
|
||||||
// });
|
|
||||||
|
|
||||||
// const dom = new JSDOM(content);
|
|
||||||
|
|
||||||
const dom = await JSDOM.fromURL(`https://github.com/${userName}`);
|
const dom = await JSDOM.fromURL(`https://github.com/${userName}`);
|
||||||
|
|
||||||
const colorScheme = Array.from(
|
const colorScheme = Array.from(
|
||||||
@@ -34,14 +19,14 @@ export const getGithubUserContribution = async (userName: string) => {
|
|||||||
dom.window.document.querySelectorAll(".js-calendar-graph-svg > g > g")
|
dom.window.document.querySelectorAll(".js-calendar-graph-svg > g > g")
|
||||||
)
|
)
|
||||||
.map((column, x) =>
|
.map((column, x) =>
|
||||||
Array.from(column.querySelectorAll("rect")).map((element, y) => ({
|
Array.from(column.querySelectorAll("rect")).map((element, y) => {
|
||||||
x,
|
const count = +element.getAttribute("data-count")!;
|
||||||
y,
|
const date = element.getAttribute("data-date")!;
|
||||||
count: element.getAttribute("data-count"),
|
const color = element.getAttribute("fill")!;
|
||||||
date: element.getAttribute("data-date"),
|
const k = colorScheme.indexOf(color);
|
||||||
color: element.getAttribute("fill"),
|
|
||||||
k: colorScheme.indexOf(element.getAttribute("fill")!),
|
return { x, y, count, date, color, k };
|
||||||
}))
|
})
|
||||||
)
|
)
|
||||||
.flat();
|
.flat();
|
||||||
|
|
||||||
@@ -53,9 +38,3 @@ type ThenArg<T> = T extends PromiseLike<infer U> ? U : T;
|
|||||||
export type Cell = ThenArg<
|
export type Cell = ThenArg<
|
||||||
ReturnType<typeof getGithubUserContribution>
|
ReturnType<typeof getGithubUserContribution>
|
||||||
>["cells"][number];
|
>["cells"][number];
|
||||||
|
|
||||||
// "#ebedf0";
|
|
||||||
// "#9be9a8";
|
|
||||||
// "#40c463";
|
|
||||||
// "#30a14e";
|
|
||||||
// "#216e39";
|
|
||||||
|
|||||||
Reference in New Issue
Block a user