Merge branch 'main' of github.com:leanprover-community/lean4game
This commit is contained in:
+1
-1
@@ -16,7 +16,7 @@ function App() {
|
||||
const params = useParams();
|
||||
return (
|
||||
<div className="app">
|
||||
<GameIdContext.Provider value={params.gameId}>
|
||||
<GameIdContext.Provider value={"g/" + params.owner + "/" + params.repo}>
|
||||
<Outlet />
|
||||
</GameIdContext.Provider>
|
||||
</div>
|
||||
|
||||
@@ -1,164 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Lean Game Server</title>
|
||||
<link rel="stylesheet" href="frontpage.css">
|
||||
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.7/dist/katex.min.css" integrity="sha384-3UiQGuEI4TTMaFmGIZumfRPtfKQ3trwQE2JgosJxCnGmQpL/lJdjpcHkaaFwHlcI" crossorigin="anonymous">
|
||||
|
||||
<!-- The loading of KaTeX is deferred to speed up page rendering -->
|
||||
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.7/dist/katex.min.js" integrity="sha384-G0zcxDFp5LWZtDuRMnBkk3EphCK1lhEf4UEyEM693ka574TZGwo4IWwS6QLzM/2t" crossorigin="anonymous"></script>
|
||||
|
||||
<!-- To automatically render math in text elements, include the auto-render extension: -->
|
||||
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.7/dist/contrib/auto-render.min.js" integrity="sha384-+VBxd3r6XgURycqtZ117nYw44OOcIax56Z4dCRWbxyPt0Koah1uHoK0o4+/RRE05" crossorigin="anonymous"
|
||||
onload="renderMathInElement(document.body);"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h1>Lean Game Server</h1>
|
||||
|
||||
<p>Welcome to the Lean Game Server where you can find interactive learning
|
||||
games about <a target="_blank" href="https://leanprover-community.github.io/">Lean</a>.<p>
|
||||
|
||||
<div class="game-list">
|
||||
|
||||
<div class="game" onclick="location.href='#/game/adam';" style="cursor: pointer;">
|
||||
<div class="wrapper">
|
||||
<div class="title">Formaloversum</div>
|
||||
<div class="short-description">Erkunde das Leansche Universum mit deinem Robo,
|
||||
welcher dir bei der Verständigung mit den Formalosophen zur Seite steht.
|
||||
</div>
|
||||
<img class="image" src="data/formaloversum.png" alt="">
|
||||
<div class="long description">
|
||||
<p>Dieses Spiel führt die Grundlagen zur Beweisführung in Lean ein und schneidet danach
|
||||
verschiedene Bereiche des Bachelorstudiums an.</p>
|
||||
|
||||
<p>(Das Spiel befindet sich noch in der Entstehungsphase.)</p>
|
||||
|
||||
<p>Das Spiel wurde im Rahmen des Projekts <a target="_blank" href="https://hhu-adam.github.io/">ADAM</a>
|
||||
an der HHU in Düsseldorf
|
||||
entwickelt.</p>
|
||||
</div>
|
||||
</div>
|
||||
<table class="info">
|
||||
<tr>
|
||||
<td title="consider playing these games first.">Prerequisites</td>
|
||||
<td>-</td>
|
||||
<!-- <td>
|
||||
<ul>
|
||||
<li>-</li>
|
||||
</ul>
|
||||
</td> -->
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Worlds</td>
|
||||
<td>?</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Levels</td>
|
||||
<td>?</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Language</td>
|
||||
<td title="in German">🇩🇪</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="game" onclick="location.href='#/game/nng';" style="cursor: pointer;">
|
||||
<div class="wrapper">
|
||||
<div class="title">Natural Number Game</div>
|
||||
<div class="short-description">
|
||||
The classical introduction game for Lean.
|
||||
</div>
|
||||
<div class="image" src="data/formaloversum.png" alt=""></div>
|
||||
<div class="long description">
|
||||
<p>In this game you recreate the natural numbers
|
||||
\(\mathbb{N}\) from the Peano axioms, learning the basics
|
||||
about theorem proving in Lean.</p>
|
||||
|
||||
<p>This is a good first introduction to Lean!
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<table class="info">
|
||||
<tr>
|
||||
<td title="consider playing these games first.">Prerequisites</td>
|
||||
<td>-</td>
|
||||
<!-- <td>
|
||||
<ul>
|
||||
<li>-</li>
|
||||
</ul>
|
||||
</td> -->
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Worlds</td>
|
||||
<td>9</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Levels</td>
|
||||
<td>72</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Language</td>
|
||||
<td title="in English">🇬🇧</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>Adding new games</h2>
|
||||
<p>
|
||||
If you consider writing your own game, you should use the
|
||||
<a target="_blank" href="https://github.com/hhu-adam/NNG4">NNG Github Repo</a>
|
||||
as a template.
|
||||
</p>
|
||||
<p>
|
||||
There will be an option to load and run games through the server
|
||||
directly by specifying a URL, but this is still in development.
|
||||
</p>
|
||||
<p>
|
||||
To add games to this page, you should get in contact as
|
||||
games will need to be added manually.
|
||||
</p>
|
||||
|
||||
<h2>Impressum</h2>
|
||||
|
||||
<p>
|
||||
When running a game, our server collects metadata
|
||||
(such as IP address, browser, operating system)
|
||||
and the data that the user enters into the editor.
|
||||
|
||||
The data is used to compute the Lean output and display it to the user.
|
||||
The information will be stored as long as the user stays on our
|
||||
website and will be deleted immediately afterwards.
|
||||
We keep logs to improve our software, but the contained
|
||||
data is anonymized.
|
||||
</p>
|
||||
<p>
|
||||
We do not use cookies, but the game progress is stored in the
|
||||
browser as site data. The game progress is not saved on the server;
|
||||
if you delete your browser storage, it will be completely gone.
|
||||
</p>
|
||||
<p>Our server is located in Germany.</p>
|
||||
|
||||
<h3>Contact information</h3>
|
||||
<p>
|
||||
Jon Eugster<br>
|
||||
Mathematisches Institut der Heinrich-Heine-Universität Düsseldorf<br>
|
||||
Universitätsstr. 1<br>
|
||||
40225 Düsseldorf<br>
|
||||
Germany<br>
|
||||
<a target="_blank" href="https://www.math.hhu.de/en/lehrstuehle-/-personen-/-ansprechpartner/innen/lehrstuehle-des-mathematischen-instituts/lehrstuhl-fuer-algebraische-geometrie/team/jon-eugster">Contact Details</a>
|
||||
<p>
|
||||
<div class="github-link">
|
||||
<a target="_blank" href="https://github.com/leanprover-community/lean4game"
|
||||
title="View the lean game server on github">
|
||||
<svg height="32" aria-hidden="true" viewBox="0 0 16 16" version="1.1" width="24" data-view-component="true" class="octicon octicon-mark-github v-align-middle">
|
||||
<path d="M8 0c4.42 0 8 3.58 8 8a8.013 8.013 0 0 1-5.45 7.59c-.4.08-.55-.17-.55-.38 0-.27.01-1.13.01-2.2 0-.75-.25-1.23-.54-1.48 1.78-.2 3.65-.88 3.65-3.95 0-.88-.31-1.59-.82-2.15.08-.2.36-1.02-.08-2.12 0 0-.67-.22-2.2.82-.64-.18-1.32-.27-2-.27-.68 0-1.36.09-2 .27-1.53-1.03-2.2-.82-2.2-.82-.44 1.1-.16 1.92-.08 2.12-.51.56-.82 1.28-.82 2.15 0 3.06 1.86 3.75 3.64 3.95-.23.2-.44.55-.51 1.07-.46.21-1.61.55-2.33-.66-.15-.24-.6-.83-1.23-.82-.67.01-.27.38.01.53.34.19.73.9.82 1.13.16.45.68 1.31 2.69.94 0 .67.01 1.3.01 1.49 0 .21-.15.45-.55.38A7.995 7.995 0 0 1 0 8c0-4.42 3.58-8 8-8Z"></path>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -47,8 +47,7 @@ function GameTile({
|
||||
|
||||
let navigate = useNavigate();
|
||||
const routeChange = () =>{
|
||||
let path = `game/${gameId}`;
|
||||
navigate(path);
|
||||
navigate(gameId);
|
||||
}
|
||||
|
||||
return <div className="game" onClick={routeChange}>
|
||||
@@ -108,7 +107,7 @@ function LandingPage() {
|
||||
|
||||
<GameTile
|
||||
title="Formaloversum"
|
||||
gameId="adam"
|
||||
gameId="g/hhu-adam/Robo"
|
||||
intro="Erkunde das Leansche Universum mit deinem Robo, welcher dir bei der Verständigung mit den Formalosophen zur Seite steht."
|
||||
description="
|
||||
Dieses Spiel führt die Grundlagen zur Beweisführung in Lean ein und schneidet danach verschiedene Bereiche des Bachelorstudiums an.
|
||||
@@ -120,7 +119,7 @@ Dieses Spiel führt die Grundlagen zur Beweisführung in Lean ein und schneidet
|
||||
|
||||
<GameTile
|
||||
title="Natural Number Game"
|
||||
gameId="nng"
|
||||
gameId="g/hhu-adam/NNG4"
|
||||
intro="The classical introduction game for Lean."
|
||||
description="In this game you recreate the natural numbers $\mathbb{N}$ from the Peano axioms,
|
||||
learning the basics about theorem proving in Lean.
|
||||
|
||||
@@ -221,8 +221,8 @@ function PlayableLevel({worldId, levelId}) {
|
||||
</div>
|
||||
}
|
||||
{levelId >= gameInfo.data?.worldSize[worldId] ?
|
||||
<Button to={`/game/${gameId}`}><FontAwesomeIcon icon={faHome} /></Button> :
|
||||
<Button to={`/game/${gameId}/world/${worldId}/level/${levelId + 1}`}>
|
||||
<Button to={`/${gameId}`}><FontAwesomeIcon icon={faHome} /></Button> :
|
||||
<Button to={`/${gameId}/world/${worldId}/level/${levelId + 1}`}>
|
||||
Next <FontAwesomeIcon icon={faArrowRight} /></Button>}
|
||||
|
||||
</div>}
|
||||
@@ -257,8 +257,8 @@ function Introduction({worldId}) {
|
||||
</div>
|
||||
<div className="conclusion">
|
||||
{0 == gameInfo.data?.worldSize[worldId] ?
|
||||
<Button to={`/game/${gameId}`}><FontAwesomeIcon icon={faHome} /></Button> :
|
||||
<Button to={`/game/${gameId}/world/${worldId}/level/1`}>
|
||||
<Button to={`/${gameId}`}><FontAwesomeIcon icon={faHome} /></Button> :
|
||||
<Button to={`/${gameId}/world/${worldId}/level/1`}>
|
||||
Start <FontAwesomeIcon icon={faArrowRight} />
|
||||
</Button>}
|
||||
</div>
|
||||
@@ -272,7 +272,7 @@ function LevelAppBar({isLoading, levelId, worldId, levelTitle}) {
|
||||
|
||||
return <div className="app-bar" style={isLoading ? {display: "none"} : null} >
|
||||
<div>
|
||||
<Button to={`/game/${gameId}`}><FontAwesomeIcon icon={faHome} /></Button>
|
||||
<Button to={`/${gameId}`}><FontAwesomeIcon icon={faHome} /></Button>
|
||||
<span className="app-bar-title">
|
||||
{gameInfo.data?.worlds.nodes[worldId].title && `World: ${gameInfo.data?.worlds.nodes[worldId].title}`}
|
||||
</span>
|
||||
@@ -282,10 +282,10 @@ function LevelAppBar({isLoading, levelId, worldId, levelTitle}) {
|
||||
{levelTitle}
|
||||
</span>
|
||||
<Button disabled={levelId <= 0} inverted={true}
|
||||
to={`/game/${gameId}/world/${worldId}/level/${levelId - 1}`}
|
||||
to={`/${gameId}/world/${worldId}/level/${levelId - 1}`}
|
||||
><FontAwesomeIcon icon={faArrowLeft} /> Previous</Button>
|
||||
<Button disabled={levelId >= gameInfo.data?.worldSize[worldId]} inverted={true}
|
||||
to={`/game/${gameId}/world/${worldId}/level/${levelId + 1}`}
|
||||
to={`/${gameId}/world/${worldId}/level/${levelId + 1}`}
|
||||
>Next <FontAwesomeIcon icon={faArrowRight} /></Button>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ function LevelIcon({ worldId, levelId, position }) {
|
||||
|
||||
// TODO: relative positioning?
|
||||
return (
|
||||
<Link to={`/game/${gameId}/world/${worldId}/level/${levelId}`}>
|
||||
<Link to={`/${gameId}/world/${worldId}/level/${levelId}`}>
|
||||
<circle fill={completed ? "green" :"#999"} cx={x} cy={y} r={r} />
|
||||
</Link>
|
||||
)
|
||||
@@ -76,13 +76,13 @@ function Welcome() {
|
||||
for (let i = 1; i <= gameInfo.data.worldSize[id]; i++) {
|
||||
svgElements.push(
|
||||
<LevelIcon
|
||||
key={`/game/${gameId}/world/${id}/level/${i}`}
|
||||
key={`/${gameId}/world/${id}/level/${i}`}
|
||||
position={position} worldId={id} levelId={i} />
|
||||
)
|
||||
}
|
||||
|
||||
svgElements.push(
|
||||
<Link key={`world${id}`} to={`/game/${gameId}/world/${id}/level/0`}>
|
||||
<Link key={`world${id}`} to={`/${gameId}/world/${id}/level/0`}>
|
||||
<circle className="world-circle" cx={s*position.x} cy={s*position.y} r={R}
|
||||
fill="#1976d2"/>
|
||||
<foreignObject className="world-title-wrapper" x={s*position.x} y={s*position.y}
|
||||
|
||||
@@ -22,19 +22,18 @@ const router = createHashRouter([
|
||||
{
|
||||
path: "/",
|
||||
element: <LandingPage />,
|
||||
// loader: () => redirect("/game/adam")
|
||||
},
|
||||
{
|
||||
path: "/game/:gameId",
|
||||
path: "/g/:owner/:repo",
|
||||
element: <App />,
|
||||
errorElement: <ErrorPage />,
|
||||
children: [
|
||||
{
|
||||
path: "/game/:gameId",
|
||||
path: "/g/:owner/:repo",
|
||||
element: <Welcome />,
|
||||
},
|
||||
{
|
||||
path: "/game/:gameId/world/:worldId/level/:levelId",
|
||||
path: "/g/:owner/:repo/world/:worldId/level/:levelId",
|
||||
element: <Level />,
|
||||
},
|
||||
],
|
||||
|
||||
Generated
+1396
-20
File diff suppressed because it is too large
Load Diff
+7
-3
@@ -17,9 +17,11 @@
|
||||
"cytoscape-elk": "^2.1.0",
|
||||
"cytoscape-klay": "^3.1.4",
|
||||
"debounce": "^1.2.1",
|
||||
"decompress": "^4.2.1",
|
||||
"express": "^4.18.2",
|
||||
"lean4-infoview": "https://gitpkg.now.sh/leanprover/vscode-lean4/lean4-infoview?de0062c",
|
||||
"lean4web": "github:hhu-adam/lean4web",
|
||||
"octokit": "^2.0.14",
|
||||
"path-browserify": "^1.0.1",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
@@ -30,6 +32,8 @@
|
||||
"rehype-katex": "^6.0.2",
|
||||
"remark-gfm": "^3.0.1",
|
||||
"remark-math": "^5.1.1",
|
||||
"request": "^2.88.2",
|
||||
"request-progress": "^3.0.0",
|
||||
"vscode-ws-jsonrpc": "^2.0.1",
|
||||
"web-worker": "^1.2.0",
|
||||
"ws": "^8.11.0"
|
||||
@@ -62,10 +66,10 @@
|
||||
"start": "concurrently -n server,client -c blue,green \"npm run start_server\" \"npm run start_client\"",
|
||||
"start_server": "cd server && lake build && NODE_ENV=development nodemon -e mjs --exec \"node ./index.mjs\"",
|
||||
"start_client": "NODE_ENV=development webpack-dev-server --hot",
|
||||
"build": "npm run build_server && npm run build_client",
|
||||
"build_server": "server/build.sh",
|
||||
"build_client": "NODE_ENV=production webpack",
|
||||
"build": "NODE_ENV=production webpack",
|
||||
"production": "NODE_ENV=production node server/index.mjs",
|
||||
"build_robo": "rm -rf ./Robo && git clone https://github.com/hhu-adam/Robo && docker build ./Robo --file ./Robo/Dockerfile --tag github-hhu-adam:Robo && rm -rf ./Robo",
|
||||
"build_nng": "rm -rf ./NNG4 && git clone https://github.com/hhu-adam/NNG4 && docker build ./NNG4 --file ./NNG4/Dockerfile --tag github-hhu-adam:NNG4 && rm -rf ./NNG4",
|
||||
"update_lean": "./UPDATE_LEAN.sh"
|
||||
},
|
||||
"eslintConfig": {
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
# Operate in the directory where this file is located
|
||||
cd $(dirname $0)
|
||||
|
||||
# Build Adam
|
||||
( rm -rf adam
|
||||
git clone https://github.com/hhu-adam/Robo adam/
|
||||
cd adam
|
||||
docker rmi adam:latest || true
|
||||
docker build \
|
||||
--rm -f Dockerfile -t adam:latest .
|
||||
)
|
||||
|
||||
# Build NNG
|
||||
( rm -rf nng
|
||||
git clone https://github.com/hhu-adam/NNG4 nng/
|
||||
cd nng
|
||||
docker rmi nng:latest || true
|
||||
docker build \
|
||||
--rm -f Dockerfile -t nng:latest .
|
||||
)
|
||||
@@ -0,0 +1,128 @@
|
||||
import { spawn } from 'child_process'
|
||||
import fs from 'fs';
|
||||
import request from 'request'
|
||||
import decompress from 'decompress'
|
||||
import requestProgress from 'request-progress'
|
||||
import { Octokit } from 'octokit';
|
||||
|
||||
const TOKEN = process.env.LEAN4GAME_GITHUB_TOKEN
|
||||
const octokit = new Octokit({
|
||||
auth: TOKEN
|
||||
})
|
||||
|
||||
const progress = {}
|
||||
|
||||
async function runProcess(id, cmd, args, cwd) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const ls = spawn(cmd, args, {cwd});
|
||||
|
||||
ls.stdout.on('data', (data) => {
|
||||
progress[id].output += data.toString()
|
||||
});
|
||||
|
||||
ls.stderr.on('data', (data) => {
|
||||
progress[id].output += data.toString()
|
||||
});
|
||||
|
||||
ls.on('close', (code) => {
|
||||
resolve()
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
async function download(id, url, dest) {
|
||||
return new Promise((resolve, reject) => {
|
||||
// The options argument is optional so you can omit it
|
||||
requestProgress(request({
|
||||
url,
|
||||
headers: {
|
||||
'User-Agent': 'abentkamp',
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
'Authorization': 'Bearer '+TOKEN
|
||||
}
|
||||
}))
|
||||
.on('progress', function (state) {
|
||||
progress[id].output += `Downloaded ${Math.round(state.size.transferred/1024/1024)}MB\n`
|
||||
})
|
||||
.on('error', function (err) {
|
||||
reject(err)
|
||||
})
|
||||
.on('end', function () {
|
||||
resolve()
|
||||
})
|
||||
.pipe(fs.createWriteStream(dest));
|
||||
})
|
||||
}
|
||||
|
||||
async function doImport (owner, repo, id) {
|
||||
progress[id].output += `Import starting in a few seconds...\n`
|
||||
await new Promise(resolve => setTimeout(resolve, 3000))
|
||||
let artifactId = null
|
||||
try {
|
||||
const artifacts = await octokit.request('GET /repos/{owner}/{repo}/actions/artifacts', {
|
||||
owner,
|
||||
repo,
|
||||
headers: {
|
||||
'X-GitHub-Api-Version': '2022-11-28'
|
||||
}
|
||||
})
|
||||
// choose latest artifact
|
||||
const artifact = artifacts.data.artifacts
|
||||
.reduce((acc, cur) => acc.created_at < cur.created_at ? cur : acc)
|
||||
artifactId = artifact.id
|
||||
const url = artifact.archive_download_url
|
||||
if (!fs.existsSync("tmp")){
|
||||
fs.mkdirSync("tmp");
|
||||
}
|
||||
progress[id].output += `Download from ${url}\n`
|
||||
await download(id, url, `tmp/artifact_${artifactId}.zip`)
|
||||
progress[id].output += `Download finished.\n`
|
||||
progress[id].output += `Unpacking ZIP.\n`
|
||||
const files = await decompress(`tmp/artifact_${artifactId}.zip`, `tmp/artifact_${artifactId}`)
|
||||
if (files.length != 1) { throw Error(`Unexpected number of files in ZIP: ${files.length}`) }
|
||||
progress[id].output += `Unpacking TAR.\n`
|
||||
const files_inner = await decompress(`tmp/artifact_${artifactId}/${files[0].path}`, `tmp/artifact_${artifactId}_inner`)
|
||||
let manifest = fs.readFileSync(`tmp/artifact_${artifactId}_inner/manifest.json`);
|
||||
manifest = JSON.parse(manifest);
|
||||
if (manifest.length !== 1) {
|
||||
throw `Unexpected manifest: ${JSON.stringify(manifest)}`
|
||||
}
|
||||
manifest[0].RepoTags = [`github-${owner}:${repo}`]
|
||||
fs.writeFileSync(`tmp/artifact_${artifactId}_inner/manifest.json`, JSON.stringify(manifest));
|
||||
await runProcess(id, "tar", ["-cvf", `../archive_${artifactId}.tar`, "."], `tmp/artifact_${artifactId}_inner/`)
|
||||
await runProcess(id, "docker", ["load", "-i", `tmp/archive_${artifactId}.tar`])
|
||||
progress[id].done = true
|
||||
progress[id].output += `Done.\n`
|
||||
} catch (e) {
|
||||
progress[id].output += `Error: ${e.toString()}\n${e.stack}`
|
||||
} finally {
|
||||
if (artifactId) {
|
||||
fs.rmSync(`tmp/artifact_${artifactId}.zip`, {force: true, recursive: true});
|
||||
fs.rmSync(`tmp/artifact_${artifactId}`, {force: true, recursive: true});
|
||||
fs.rmSync(`tmp/artifact_${artifactId}_inner`, {force: true, recursive: true});
|
||||
fs.rmSync(`tmp/archive_${artifactId}.tar`, {force: true, recursive: true});
|
||||
}
|
||||
progress[id].done = true
|
||||
}
|
||||
}
|
||||
|
||||
export const importTrigger = (req, res) => {
|
||||
const owner = req.params.owner
|
||||
const repo = req.params.repo
|
||||
const id = req.params.owner + '/' + req.params.repo
|
||||
if(!/^[\w.-]+\/[\w.-]+$/.test(id)) { res.send(`Invalid repo name ${id}`); return }
|
||||
|
||||
if(!progress[id] || progress[id].done) {
|
||||
progress[id] = {output: "", done: false}
|
||||
doImport(owner, repo, id)
|
||||
}
|
||||
|
||||
res.redirect(`/import/status/${owner}/${repo}`)
|
||||
}
|
||||
|
||||
export const importStatus = (req, res) => {
|
||||
const owner = req.params.owner
|
||||
const repo = req.params.repo
|
||||
const id = req.params.owner + '/' + req.params.repo
|
||||
res.send(`<html><head><meta http-equiv="refresh" content="5"></head><body><pre>${progress[id]?.output ?? "Nothing here."}</pre></body></html>`)
|
||||
}
|
||||
+41
-24
@@ -7,15 +7,22 @@ import * as rpc from 'vscode-ws-jsonrpc';
|
||||
import * as jsonrpcserver from 'vscode-ws-jsonrpc/server';
|
||||
import os from 'os';
|
||||
import anonymize from 'ip-anonymize';
|
||||
import { importTrigger, importStatus } from './import.mjs'
|
||||
|
||||
/** Preloaded games. The keys refer to the docker tags of the virtual machines.
|
||||
* The number `queueLength` determines how many instances of the docker container
|
||||
* get started before any user shows up to have them up and running immediately.
|
||||
* The values `name`, `module`, and `dir` are just used for development where we
|
||||
* use a project directory instead of a docker container.
|
||||
*/
|
||||
const games = {
|
||||
adam: {
|
||||
"github-hhu-adam:Robo": {
|
||||
name: "Adam",
|
||||
module: "Adam",
|
||||
dir: "../../../../Robo",
|
||||
queueLength: 5
|
||||
},
|
||||
nng: {
|
||||
"github-hhu-adam:NNG4": {
|
||||
name: "NNG",
|
||||
module: "NNG",
|
||||
dir: "../../../../NNG4",
|
||||
@@ -30,8 +37,14 @@ const app = express()
|
||||
|
||||
const PORT = process.env.PORT || 8080;
|
||||
|
||||
var router = express.Router();
|
||||
|
||||
router.get('/import/status/:owner/:repo', importStatus)
|
||||
router.get('/import/trigger/:owner/:repo', importTrigger)
|
||||
|
||||
const server = app
|
||||
.use(express.static(path.join(__dirname, '../client/dist/')))
|
||||
.use('/', router)
|
||||
.listen(PORT, () => console.log(`Listening on ${PORT}`));
|
||||
|
||||
const wss = new WebSocketServer({ server })
|
||||
@@ -43,17 +56,18 @@ const isDevelopment = environment === 'development'
|
||||
|
||||
/** We keep queues of started Lean Server processes to be ready when a user arrives */
|
||||
const queue = {}
|
||||
const queueLength = 5
|
||||
|
||||
function startServerProcess(gameId) {
|
||||
const serverProcess = isDevelopment
|
||||
? cp.spawn("./gameserver",
|
||||
["--server", games[gameId].dir, games[gameId].module, games[gameId].name],
|
||||
function startServerProcess(tag) {
|
||||
let serverProcess
|
||||
if (isDevelopment && games[tag]?.dir) {
|
||||
serverProcess = cp.spawn("./gameserver",
|
||||
["--server", games[tag].dir, games[tag].module, games[tag].name],
|
||||
{ cwd: "./build/bin/" })
|
||||
: cp.spawn("docker",
|
||||
["run", "--runtime=runsc", "--network=none", "--rm", "-i", `${gameId}:latest`,
|
||||
"./gameserver", "--server", "/game/", games[gameId].module, games[gameId].name],
|
||||
} else {
|
||||
serverProcess = cp.spawn("docker",
|
||||
["run", "--runtime=runsc", "--network=none", "--rm", "-i", `${tag}`],
|
||||
{ cwd: "." })
|
||||
}
|
||||
serverProcess.on('error', error =>
|
||||
console.error(`Launching Lean Server failed: ${error}`)
|
||||
);
|
||||
@@ -66,32 +80,35 @@ function startServerProcess(gameId) {
|
||||
}
|
||||
|
||||
/** start Lean Server processes to refill the queue */
|
||||
function fillQueue(gameId) {
|
||||
while (queue[gameId].length < games[gameId].queueLength) {
|
||||
const serverProcess = startServerProcess(gameId)
|
||||
queue[gameId].push(serverProcess)
|
||||
function fillQueue(tag) {
|
||||
while (queue[tag].length < games[tag].queueLength) {
|
||||
const serverProcess = startServerProcess(tag)
|
||||
queue[tag].push(serverProcess)
|
||||
}
|
||||
}
|
||||
|
||||
for (let gameId in games) {
|
||||
queue[gameId] = []
|
||||
fillQueue(gameId)
|
||||
if (!isDevelopment) { // Don't use queue in development
|
||||
for (let tag in games) {
|
||||
queue[tag] = []
|
||||
fillQueue(tag)
|
||||
}
|
||||
}
|
||||
|
||||
const urlRegEx = new RegExp("^/websocket/(.*)$")
|
||||
const urlRegEx = /^\/websocket\/g\/([\w.-]+)\/([\w.-]+)$/
|
||||
|
||||
wss.addListener("connection", function(ws, req) {
|
||||
const reRes = urlRegEx.exec(req.url)
|
||||
if (!reRes) { console.error(`Connection refused because of invalid URL: ${req.url}`); return; }
|
||||
const gameId = reRes[1]
|
||||
if (!games[gameId]) { console.error(`Unknown game: ${gameId}`); return; }
|
||||
const owner = reRes[1]
|
||||
const repo = reRes[2]
|
||||
const tag = `github-${owner}:${repo}`
|
||||
|
||||
let ps;
|
||||
if (isDevelopment) { // Don't use queue in development
|
||||
ps = startServerProcess(gameId)
|
||||
if (!queue[tag] || queue[tag].length == 0) {
|
||||
ps = startServerProcess(tag)
|
||||
} else {
|
||||
ps = queue[gameId].shift() // Pick the first Lean process; it's likely to be ready immediately
|
||||
fillQueue(gameId)
|
||||
ps = queue[tag].shift() // Pick the first Lean process; it's likely to be ready immediately
|
||||
fillQueue(tag)
|
||||
}
|
||||
|
||||
socketCounter += 1;
|
||||
|
||||
@@ -68,6 +68,10 @@ module.exports = env => {
|
||||
target: 'ws://localhost:8080',
|
||||
ws: true
|
||||
},
|
||||
'/import': {
|
||||
target: 'http://localhost:3000',
|
||||
router: () => 'http://localhost:8080',
|
||||
},
|
||||
},
|
||||
static: path.join(__dirname, 'client/public/'),
|
||||
port: 3000,
|
||||
|
||||
Reference in New Issue
Block a user