Merge branch 'dev'
This commit is contained in:
Binary file not shown.
|
Before Width: | Height: | Size: 398 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 308 KiB |
@@ -86,7 +86,7 @@ function InventoryList({items, docType, openDoc, defaultTab=null, level=undefine
|
||||
{[...modifiedItems].sort(
|
||||
// For lemas, sort entries `available > disabled > locked`
|
||||
// otherwise alphabetically
|
||||
(x, y) => +(docType == "Lemma") * (+x.locked - +y.locked || +x.disabled - +y.disabled)
|
||||
(x, y) => +(docType == "Lemma") * (+x.locked - +y.locked || +x.disabled - +y.disabled) || x.displayName.localeCompare(y.displayName)
|
||||
).filter(item => !item.hidden && ((tab ?? categories[0]) == item.category)).map((item, i) => {
|
||||
return <InventoryItem key={`${item.category}-${item.name}`}
|
||||
showDoc={() => {openDoc({name: item.name, type: docType})}}
|
||||
|
||||
@@ -7,12 +7,12 @@ import '@fontsource/roboto/500.css';
|
||||
import '@fontsource/roboto/700.css';
|
||||
|
||||
import '../css/landing_page.css'
|
||||
import coverRobo from '../assets/covers/formaloversum.png'
|
||||
import coverNNG from '../assets/covers/nng.png'
|
||||
import bgImage from '../assets/bg.jpg'
|
||||
|
||||
import Markdown from './markdown';
|
||||
import {PrivacyPolicyPopup} from './popup/privacy_policy'
|
||||
import { GameTile, useGetGameInfoQuery } from '../state/api'
|
||||
import path from 'path';
|
||||
|
||||
const flag = {
|
||||
'Dutch': '🇳🇱',
|
||||
@@ -33,47 +33,42 @@ function GithubIcon({url='https://github.com'}) {
|
||||
</div>
|
||||
}
|
||||
|
||||
function GameTile({
|
||||
title,
|
||||
gameId,
|
||||
intro, // Catchy intro phrase.
|
||||
image=null,
|
||||
worlds='?',
|
||||
levels='?',
|
||||
prereq='–', // Optional list of games that this game builds on. Use markdown.
|
||||
description, // Longer description. Supports Markdown.
|
||||
language}) {
|
||||
function Tile({gameId, data}: {gameId: string, data: GameTile|undefined}) {
|
||||
|
||||
let navigate = useNavigate();
|
||||
const routeChange = () =>{
|
||||
navigate(gameId);
|
||||
}
|
||||
|
||||
if (typeof data === 'undefined') {
|
||||
return <></>
|
||||
}
|
||||
|
||||
return <div className="game" onClick={routeChange}>
|
||||
<div className="wrapper">
|
||||
<div className="title">{title}</div>
|
||||
<div className="short-description">{intro}
|
||||
<div className="title">{data.title}</div>
|
||||
<div className="short-description">{data.short}
|
||||
</div>
|
||||
{ image ? <img className="image" src={image} alt="" /> : <div className="image"/> }
|
||||
<div className="long description"><Markdown>{description}</Markdown></div>
|
||||
{ data.image ? <img className="image" src={path.join("data", gameId, data.image)} alt="" /> : <div className="image"/> }
|
||||
<div className="long description"><Markdown>{data.long}</Markdown></div>
|
||||
</div>
|
||||
<table className="info">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td title="consider playing these games first.">Prerequisites</td>
|
||||
<td><Markdown>{prereq}</Markdown></td>
|
||||
<td><Markdown>{data.prerequisites.join(', ')}</Markdown></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Worlds</td>
|
||||
<td>{worlds}</td>
|
||||
<td>{data.worlds}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Levels</td>
|
||||
<td>{levels}</td>
|
||||
<td>{data.levels}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Language</td>
|
||||
<td title={`in ${language}`}>{flag[language]}</td>
|
||||
<td title={`in ${data.languages.join(', ')}`}>{data.languages.map((lan) => flag[lan]).join(', ')}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -89,6 +84,56 @@ function LandingPage() {
|
||||
const openImpressum = () => setImpressum(true);
|
||||
const closeImpressum = () => setImpressum(false);
|
||||
|
||||
// const [allGames, setAllGames] = React.useState([])
|
||||
// const [allTiles, setAllTiles] = React.useState([])
|
||||
|
||||
// const getTiles=()=>{
|
||||
// fetch('featured_games.json', {
|
||||
// headers : {
|
||||
// 'Content-Type': 'application/json',
|
||||
// 'Accept': 'application/json'
|
||||
// }
|
||||
// }
|
||||
// ).then(function(response){
|
||||
// return response.json()
|
||||
// }).then(function(data) {
|
||||
// setAllGames(data.featured_games)
|
||||
|
||||
// })
|
||||
// }
|
||||
|
||||
// React.useEffect(()=>{
|
||||
// getTiles()
|
||||
// },[])
|
||||
|
||||
// React.useEffect(()=>{
|
||||
|
||||
// Promise.allSettled(
|
||||
// allGames.map((gameId) => (
|
||||
// fetch(`data/g/${gameId}/game.json`).catch(err => {return undefined})))
|
||||
// ).then(responses =>
|
||||
// responses.forEach((result) => console.log(result)))
|
||||
// // Promise.all(responses.map(res => {
|
||||
// // if (res.status == "fulfilled") {
|
||||
// // console.log(res.value.json())
|
||||
// // return res.value.json()
|
||||
// // } else {
|
||||
// // return undefined
|
||||
// // }
|
||||
// // }))
|
||||
// // ).then(allData => {
|
||||
// // setAllTiles(allData.map(data => data?.tile))
|
||||
// // })
|
||||
// },[allGames])
|
||||
|
||||
// TODO: I would like to read the supported games list form a JSON,
|
||||
// Then load all these games in
|
||||
//
|
||||
let allGames = [
|
||||
"leanprover-community/nng4",
|
||||
"djvelleman/stg4",
|
||||
"hhu-adam/robo"]
|
||||
let allTiles = allGames.map((gameId) => (useGetGameInfoQuery({game: `g/${gameId}`}).data?.tile))
|
||||
|
||||
return <div className="landing-page">
|
||||
<header style={{backgroundImage: `url(${bgImage})`}}>
|
||||
@@ -105,8 +150,19 @@ function LandingPage() {
|
||||
</div>
|
||||
</header>
|
||||
<div className="game-list">
|
||||
|
||||
<GameTile
|
||||
{allTiles.length == 0 ?
|
||||
<p>No Games loaded. Use <a>http://localhost:3000/#/g/local/FOLDER</a> to open a
|
||||
game directly from a local folder.
|
||||
</p>
|
||||
: allGames.map((id, i) => (
|
||||
<Tile
|
||||
key={id}
|
||||
gameId={`g/${id}`}
|
||||
data={allTiles[i]}
|
||||
/>
|
||||
))
|
||||
}
|
||||
{/* <GameTile
|
||||
title="Natural Number Game"
|
||||
gameId="g/hhu-adam/NNG4"
|
||||
intro="The classical introduction game for Lean."
|
||||
@@ -129,18 +185,7 @@ This is a good first introduction to Lean!"
|
||||
levels="30"
|
||||
language="English"
|
||||
/>
|
||||
|
||||
<GameTile
|
||||
title="Formaloversum"
|
||||
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.
|
||||
|
||||
(Das Spiel befindet sich noch in der Entstehungsphase.)"
|
||||
image={coverRobo}
|
||||
language="German"
|
||||
/>
|
||||
*/}
|
||||
</div>
|
||||
<section>
|
||||
<div className="wrapper">
|
||||
|
||||
@@ -34,6 +34,7 @@ import { DualEditor } from './infoview/main'
|
||||
import { GameHint } from './infoview/rpc_api'
|
||||
import { DeletedHints, Hint, Hints } from './hints'
|
||||
import { PrivacyPolicyPopup } from './popup/privacy_policy'
|
||||
import path from 'path';
|
||||
|
||||
import '@fontsource/roboto/300.css'
|
||||
import '@fontsource/roboto/400.css'
|
||||
@@ -467,6 +468,11 @@ function Introduction({impressum, setImpressum}) {
|
||||
|
||||
const gameInfo = useGetGameInfoQuery({game: gameId})
|
||||
|
||||
const {worldId} = useContext(WorldLevelIdContext)
|
||||
|
||||
let image: string = gameInfo.data?.worlds.nodes[worldId].image
|
||||
|
||||
|
||||
const toggleImpressum = () => {
|
||||
setImpressum(!impressum)
|
||||
}
|
||||
@@ -480,7 +486,12 @@ function Introduction({impressum, setImpressum}) {
|
||||
:
|
||||
<Split minSize={0} snapOffset={200} sizes={[25, 50, 25]} className={`app-content level`}>
|
||||
<IntroductionPanel gameInfo={gameInfo} />
|
||||
<div className="world-image-container empty"></div>
|
||||
<div className="world-image-container empty">
|
||||
{image &&
|
||||
<img src={path.join("data", gameId, image)} alt="" />
|
||||
}
|
||||
|
||||
</div>
|
||||
<InventoryPanel levelInfo={inventory?.data} />
|
||||
</Split>
|
||||
}
|
||||
|
||||
@@ -336,6 +336,16 @@ td code {
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
.world-image-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.world-image-container img {
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.typewriter-interface .proof {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
+22
-7
@@ -3,14 +3,28 @@
|
||||
*/
|
||||
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
|
||||
|
||||
|
||||
export interface GameTile {
|
||||
title: string
|
||||
short: string
|
||||
long: string
|
||||
languages: Array<string>
|
||||
prerequisites: Array<string>
|
||||
worlds: number
|
||||
levels: number
|
||||
image: string
|
||||
}
|
||||
|
||||
export interface GameInfo {
|
||||
title: null|string,
|
||||
introduction: null|string,
|
||||
info: null|string,
|
||||
worlds: null|{nodes: {[id:string]: {id: string, title: string, introduction: string}}, edges: string[][]},
|
||||
worlds: null|{nodes: {[id:string]: {id: string, title: string, introduction: string, image: string}}, edges: string[][]},
|
||||
worldSize: null|{[key: string]: number},
|
||||
authors: null|string[],
|
||||
conclusion: null|string,
|
||||
tile: null|GameTile,
|
||||
image: null|string
|
||||
}
|
||||
|
||||
export interface InventoryTile {
|
||||
@@ -36,7 +50,8 @@ export interface LevelInfo {
|
||||
lemmaTab: null|string,
|
||||
statementName: null|string,
|
||||
displayName: null|string,
|
||||
template: null|string
|
||||
template: null|string,
|
||||
image: null|string
|
||||
}
|
||||
|
||||
/** Used to display the inventory on the welcome page */
|
||||
@@ -59,19 +74,19 @@ interface Doc {
|
||||
// Define a service using a base URL and expected endpoints
|
||||
export const apiSlice = createApi({
|
||||
reducerPath: 'gameApi',
|
||||
baseQuery: fetchBaseQuery({ baseUrl: window.location.origin + "/api" }),
|
||||
baseQuery: fetchBaseQuery({ baseUrl: window.location.origin + "/data" }),
|
||||
endpoints: (builder) => ({
|
||||
getGameInfo: builder.query<GameInfo, {game: string}>({
|
||||
query: ({game}) => `${game}/game`,
|
||||
query: ({game}) => `${game}/game.json`,
|
||||
}),
|
||||
loadLevel: builder.query<LevelInfo, {game: string, world: string, level: number}>({
|
||||
query: ({game, world, level}) => `${game}/level/${world}/${level}`,
|
||||
query: ({game, world, level}) => `${game}/level__${world}__${level}.json`,
|
||||
}),
|
||||
loadInventoryOverview: builder.query<InventoryOverview, {game: string}>({
|
||||
query: ({game}) => `${game}/inventory`,
|
||||
query: ({game}) => `${game}/inventory.json`,
|
||||
}),
|
||||
loadDoc: builder.query<Doc, {game: string, name: string, type: "lemma"|"tactic"}>({
|
||||
query: ({game, type, name}) => `${game}/doc/${type}/${name}`,
|
||||
query: ({game, type, name}) => `${game}/doc__${type}__${name}.json`,
|
||||
}),
|
||||
}),
|
||||
})
|
||||
|
||||
@@ -46,7 +46,9 @@ elab "Title" t:str : command => do
|
||||
match ← getCurLayer with
|
||||
| .Level => modifyCurLevel fun level => pure {level with title := t.getString}
|
||||
| .World => modifyCurWorld fun world => pure {world with title := t.getString}
|
||||
| .Game => modifyCurGame fun game => pure {game with title := t.getString}
|
||||
| .Game => modifyCurGame fun game => pure {game with
|
||||
title := t.getString
|
||||
tile := {game.tile with title := t.getString}}
|
||||
|
||||
/-- Define the introduction of the current game/world/level. -/
|
||||
elab "Introduction" t:str : command => do
|
||||
@@ -58,10 +60,32 @@ elab "Introduction" t:str : command => do
|
||||
/-- Define the info of the current game. Used for e.g. credits -/
|
||||
elab "Info" t:str : command => do
|
||||
match ← getCurLayer with
|
||||
| .Level => pure ()
|
||||
| .World => pure ()
|
||||
| .Level =>
|
||||
logError "Can't use `Info` in a level!"
|
||||
pure ()
|
||||
| .World =>
|
||||
logError "Can't use `Info` in a world"
|
||||
pure ()
|
||||
| .Game => modifyCurGame fun game => pure {game with info := t.getString}
|
||||
|
||||
/-- Provide the location of the image for the current game/world/level.
|
||||
Paths are relative to the lean project's root. -/
|
||||
elab "Image" t:str : command => do
|
||||
let file := t.getString
|
||||
if not <| ← System.FilePath.pathExists file then
|
||||
logWarningAt t s!"Make sure the cover image '{file}' exists."
|
||||
if not <| file.startsWith "images/" then
|
||||
logWarningAt t s!"The file name should start with `images/`. Make sure all images are in that folder."
|
||||
|
||||
match ← getCurLayer with
|
||||
| .Level =>
|
||||
logWarning "Level-images not implemented yet" -- TODO
|
||||
modifyCurLevel fun level => pure {level with image := file}
|
||||
| .World =>
|
||||
modifyCurWorld fun world => pure {world with image := file}
|
||||
| .Game =>
|
||||
logWarning "Main image of the game not implemented yet" -- TODO
|
||||
modifyCurGame fun game => pure {game with image := file}
|
||||
|
||||
/-- Define the conclusion of the current game or current level if some
|
||||
building a level. -/
|
||||
@@ -71,6 +95,38 @@ elab "Conclusion" t:str : command => do
|
||||
| .World => modifyCurWorld fun world => pure {world with conclusion := t.getString}
|
||||
| .Game => modifyCurGame fun game => pure {game with conclusion := t.getString}
|
||||
|
||||
/-- A list of games that should be played before this one. Example `Prerequisites "NNG" "STG"`. -/
|
||||
elab "Prerequisites" t:str* : command => do
|
||||
modifyCurGame fun game => pure {game with
|
||||
tile := {game.tile with prerequisites := t.map (·.getString) |>.toList}}
|
||||
|
||||
/-- Short caption for the game (1 sentence) -/
|
||||
elab "CaptionShort" t:str : command => do
|
||||
modifyCurGame fun game => pure {game with
|
||||
tile := {game.tile with short := t.getString}}
|
||||
|
||||
/-- More detailed description what the game is about (2-4 sentences). -/
|
||||
elab "CaptionLong" t:str : command => do
|
||||
modifyCurGame fun game => pure {game with
|
||||
tile := {game.tile with long := t.getString}}
|
||||
|
||||
/-- A list of Languages the game is translated to. For example `Languages "German" "English"`.
|
||||
NOTE: For the time being, only a single language is supported.
|
||||
-/
|
||||
elab "Languages" t:str* : command => do
|
||||
modifyCurGame fun game => pure {game with
|
||||
tile := {game.tile with languages := t.map (·.getString) |>.toList}}
|
||||
|
||||
/-- The Image of the game (optional). TODO: Not impementeds -/
|
||||
elab "CoverImage" t:str : command => do
|
||||
let file := t.getString
|
||||
if not <| ← System.FilePath.pathExists file then
|
||||
logWarningAt t s!"Make sure the cover image '{file}' exists."
|
||||
if not <| file.startsWith "images/" then
|
||||
logWarningAt t s!"The file name should start with `images/`. Make sure all images are in that folder."
|
||||
|
||||
modifyCurGame fun game => pure {game with
|
||||
tile := {game.tile with image := file}}
|
||||
|
||||
/-! # Inventory
|
||||
|
||||
@@ -626,6 +682,27 @@ elab "Template" tacs:tacticSeq : tactic => do
|
||||
modifyLevel (←getCurLevelId) fun level => do
|
||||
return {level with template := s!"{template}"}
|
||||
|
||||
|
||||
|
||||
open IO.FS System FilePath in
|
||||
/-- Copies the folder `images/` to `.lake/gamedata/images/` -/
|
||||
def copyImages : IO Unit := do
|
||||
let target : FilePath := ".lake" / "gamedata"
|
||||
if ← FilePath.pathExists "images" then
|
||||
for file in ← walkDir "images" do
|
||||
let outFile := target.join file
|
||||
-- create the directories
|
||||
if ← file.isDir then
|
||||
createDirAll outFile
|
||||
else
|
||||
if let some parent := outFile.parent then
|
||||
createDirAll parent
|
||||
-- copy file
|
||||
let content ← readBinFile file
|
||||
writeBinFile outFile content
|
||||
|
||||
|
||||
|
||||
-- TODO: Notes for testing if a declaration has the simp attribute
|
||||
|
||||
-- -- Test: From zulip
|
||||
@@ -647,7 +724,7 @@ elab "Template" tacs:tacticSeq : tactic => do
|
||||
#eval IO.FS.createDirAll ".lake/gamedata/"
|
||||
|
||||
-- TODO: register all of this as ToJson instance?
|
||||
def saveGameData (allItemsByType : HashMap InventoryType (HashSet Name)) : CommandElabM Unit:= do
|
||||
def saveGameData (allItemsByType : HashMap InventoryType (HashSet Name)) : CommandElabM Unit := do
|
||||
let game ← getCurGame
|
||||
let env ← getEnv
|
||||
let path : System.FilePath := s!"{← IO.currentDir}" / ".lake" / "gamedata"
|
||||
@@ -656,6 +733,9 @@ def saveGameData (allItemsByType : HashMap InventoryType (HashSet Name)) : Comma
|
||||
IO.FS.removeDirAll path
|
||||
IO.FS.createDirAll path
|
||||
|
||||
-- copy the images folder
|
||||
copyImages
|
||||
|
||||
for (worldId, world) in game.worlds.nodes.toArray do
|
||||
for (levelId, level) in world.levels.toArray do
|
||||
IO.FS.writeFile (path / s!"level__{worldId}__{levelId}.json") (toString (toJson (level.toInfo env)))
|
||||
@@ -848,8 +928,12 @@ elab "MakeGame" : command => do
|
||||
-- Items that should not be displayed in inventory
|
||||
let mut hiddenItems : HashSet Name := {}
|
||||
|
||||
let allWorlds := game.worlds.nodes.toArray
|
||||
let nrWorlds := allWorlds.size
|
||||
let mut nrLevels := 0
|
||||
|
||||
-- Calculate which "items" are used/new in which world
|
||||
for (worldId, world) in game.worlds.nodes.toArray do
|
||||
for (worldId, world) in allWorlds do
|
||||
let mut usedItems : HashSet Name := {}
|
||||
let mut newItems : HashSet Name := {}
|
||||
for inventoryType in #[.Tactic, .Definition, .Lemma] do
|
||||
@@ -888,9 +972,12 @@ elab "MakeGame" : command => do
|
||||
-- logInfo m!"{worldId} uses: {usedItems.toList}"
|
||||
-- logInfo m!"{worldId} introduces: {newItems.toList}"
|
||||
|
||||
-- Moreover, count the number of levels in the game
|
||||
nrLevels := nrLevels + world.levels.toArray.size
|
||||
|
||||
/- for each "item" this is a HashSet of `worldId`s that introduce this item -/
|
||||
let mut worldsWithNewItem : HashMap Name (HashSet Name) := {}
|
||||
for (worldId, _world) in game.worlds.nodes.toArray do
|
||||
for (worldId, _world) in allWorlds do
|
||||
for newItem in newItemsInWorld.findD worldId {} do
|
||||
worldsWithNewItem := worldsWithNewItem.insert newItem $
|
||||
(worldsWithNewItem.findD newItem {}).insert worldId
|
||||
@@ -902,7 +989,7 @@ elab "MakeGame" : command => do
|
||||
let mut dependencyReasons : HashMap (Name × Name) (HashSet Name) := {}
|
||||
|
||||
-- Calculate world dependency graph `game.worlds`
|
||||
for (dependentWorldId, _dependentWorld) in game.worlds.nodes.toArray do
|
||||
for (dependentWorldId, _dependentWorld) in allWorlds do
|
||||
let mut dependsOnWorlds : HashSet Name := {}
|
||||
-- Adding manual dependencies that were specified via the `Dependency` command.
|
||||
for (sourceId, targetId) in game.worlds.edges do
|
||||
@@ -953,11 +1040,21 @@ elab "MakeGame" : command => do
|
||||
logError m!"{w1} depends on {w2} because of {items.toList}."
|
||||
else
|
||||
worldDependsOnWorlds ← removeTransitive worldDependsOnWorlds
|
||||
|
||||
-- need to delete all existing edges as they are already present in `worldDependsOnWorlds`.
|
||||
modifyCurGame fun game =>
|
||||
pure {game with worlds := {game.worlds with edges := Array.empty}}
|
||||
|
||||
for (dependentWorldId, worldIds) in worldDependsOnWorlds.toArray do
|
||||
modifyCurGame fun game =>
|
||||
pure {game with worlds := {game.worlds with
|
||||
edges := game.worlds.edges.append (worldIds.toArray.map fun wid => (wid, dependentWorldId))}}
|
||||
|
||||
-- Add the number of levels and worlds to the tile for the landing page
|
||||
modifyCurGame fun game => pure {game with tile := {game.tile with
|
||||
levels := nrLevels
|
||||
worlds := nrWorlds }}
|
||||
|
||||
-- Apparently we need to reload `game` to get the changes to `game.worlds` we just made
|
||||
let game ← getCurGame
|
||||
|
||||
|
||||
@@ -265,7 +265,9 @@ structure GameLevel where
|
||||
lemmas: InventoryInfo := default
|
||||
/-- A proof template that is printed in an empty editor. -/
|
||||
template: Option String := none
|
||||
deriving Inhabited, Repr
|
||||
/-- The image for this level. -/
|
||||
image : String := default
|
||||
deriving Inhabited, Repr
|
||||
|
||||
/-- Json-encodable version of `GameLevel`
|
||||
Fields:
|
||||
@@ -286,6 +288,7 @@ structure LevelInfo where
|
||||
displayName : Option String
|
||||
statementName : Option String
|
||||
template : Option String
|
||||
image: Option String
|
||||
deriving ToJson, FromJson
|
||||
|
||||
def GameLevel.toInfo (lvl : GameLevel) (env : Environment) : LevelInfo :=
|
||||
@@ -315,6 +318,7 @@ def GameLevel.toInfo (lvl : GameLevel) (env : Environment) : LevelInfo :=
|
||||
-- Note: we could call `.find!` because we check in `Statement` that the
|
||||
-- lemma doc must exist.
|
||||
template := lvl.template
|
||||
image := lvl.image
|
||||
}
|
||||
|
||||
/-! ## World -/
|
||||
@@ -331,17 +335,46 @@ structure World where
|
||||
conclusion : String := default
|
||||
/-- The levels of the world. -/
|
||||
levels: HashMap Nat GameLevel := default
|
||||
/-- The introduction image of the world. -/
|
||||
image: String := default
|
||||
deriving Inhabited
|
||||
|
||||
instance : ToJson World := ⟨
|
||||
fun world => Json.mkObj [
|
||||
("name", toJson world.name),
|
||||
("title", world.title),
|
||||
("introduction", world.introduction)]
|
||||
("introduction", world.introduction),
|
||||
("image", world.image)]
|
||||
⟩
|
||||
|
||||
/-! ## Game -/
|
||||
|
||||
/-- A tile as they are displayed on the servers landing page. -/
|
||||
structure GameTile where
|
||||
/-- The title of the game -/
|
||||
title: String
|
||||
/-- One catch phrase about the game -/
|
||||
short: String := default
|
||||
/-- One paragraph description what the game is about -/
|
||||
long: String := default
|
||||
/-- List of languages the game supports
|
||||
|
||||
TODO: What's the expectected format
|
||||
TODO: Must be a list with a single language currently
|
||||
-/
|
||||
languages: List String := default
|
||||
/-- A list of games which this one builds upon -/
|
||||
prerequisites: List String := default
|
||||
/-- Number of worlds in the game -/
|
||||
worlds: Nat := default
|
||||
/-- Number of levels in the game -/
|
||||
levels: Nat := default
|
||||
/-- A cover image of the game
|
||||
|
||||
TODO: What's the format? -/
|
||||
image: String := default
|
||||
deriving Inhabited, ToJson
|
||||
|
||||
structure Game where
|
||||
/-- Internal name of the game. -/
|
||||
name : Name
|
||||
@@ -356,6 +389,10 @@ structure Game where
|
||||
/-- TODO: currently unused. -/
|
||||
authors : List String := default
|
||||
worlds : Graph Name World := default
|
||||
/-- The tile displayed on the server's landing page. -/
|
||||
tile : GameTile := default
|
||||
/-- The path to the background image of the world. -/
|
||||
image : String := default
|
||||
deriving Inhabited, ToJson
|
||||
|
||||
def getGameJson (game : «Game») : Json := Id.run do
|
||||
|
||||
@@ -126,7 +126,7 @@ def findHints (goal : MVarId) (doc : FileWorker.EditableDocument) (initParams :
|
||||
then
|
||||
let userFVars := hintFVars.map fun v => bij.forward.findD v.fvarId! v.fvarId!
|
||||
let text := (← evalHintMessage hint.text) (userFVars.map Expr.fvar)
|
||||
let ctx := {env := ← getEnv, mctx := ← getMCtx, lctx := ← getLCtx, opts := {}}
|
||||
let ctx := {env := ← getEnv, mctx := ← getMCtx, lctx := lctx, opts := {}}
|
||||
let text ← (MessageData.withContext ctx text).toString
|
||||
return some { text := text, hidden := hint.hidden }
|
||||
else return none
|
||||
|
||||
+5
-6
@@ -108,12 +108,11 @@ async function doImport (owner, repo, id) {
|
||||
} 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});
|
||||
// }
|
||||
// clean-up temp. files
|
||||
if (artifactId) {
|
||||
fs.rmSync(`${__dirname}/../games/tmp/${owner}_${repo}_${artifactId}.zip`, {force: true, recursive: false});
|
||||
fs.rmSync(`${__dirname}/../games/tmp/${owner}_${repo}_${artifactId}`, {force: true, recursive: true});
|
||||
}
|
||||
progress[id].done = true
|
||||
}
|
||||
await new Promise(resolve => setTimeout(resolve, 10000))
|
||||
|
||||
+12
-31
@@ -34,34 +34,15 @@ var router = express.Router();
|
||||
router.get('/import/status/:owner/:repo', importStatus)
|
||||
router.get('/import/trigger/:owner/:repo', importTrigger)
|
||||
|
||||
function loadJson(req, filename) {
|
||||
const owner = req.params.owner;
|
||||
const repo = req.params.repo
|
||||
return JSON.parse(fs.readFileSync(path.join(getGameDir(owner,repo),".lake","gamedata",filename)))
|
||||
}
|
||||
|
||||
router.get("/api/g/:owner/:repo/game", (req, res) => {
|
||||
res.send(loadJson(req, `game.json`));
|
||||
});
|
||||
|
||||
router.get("/api/g/:owner/:repo/inventory", (req, res) => {
|
||||
res.send(loadJson(req, `inventory.json`));
|
||||
});
|
||||
|
||||
router.get("/api/g/:owner/:repo/level/:world/:level", (req, res) => {
|
||||
const world = req.params.world;
|
||||
const level = req.params.level;
|
||||
res.send(loadJson(req, `level__${world}__${level}.json`));
|
||||
});
|
||||
|
||||
router.get("/api/g/:owner/:repo/doc/:type/:name", (req, res) => {
|
||||
const type = req.params.type;
|
||||
const name = req.params.name;
|
||||
res.send(loadJson(req, `doc__${type}__${name}.json`));
|
||||
});
|
||||
|
||||
const server = app
|
||||
.use(express.static(path.join(__dirname, '../client/dist/')))
|
||||
.use(express.static(path.join(__dirname, '../client/dist/'))) // TODO: add a dist folder from inside the game
|
||||
.use('/data/g/:owner/:repo/*', (req, res, next) => {
|
||||
const owner = req.params.owner;
|
||||
const repo = req.params.repo
|
||||
const filename = req.params[0];
|
||||
req.url = filename;
|
||||
express.static(path.join(getGameDir(owner,repo),".lake","gamedata"))(req, res, next);
|
||||
})
|
||||
.use('/', router)
|
||||
.listen(PORT, () => console.log(`Listening on ${PORT}`));
|
||||
|
||||
@@ -84,13 +65,13 @@ function getGameDir(owner, repo) {
|
||||
if (owner == 'local') {
|
||||
if(!isDevelopment) {
|
||||
console.error(`No local games in production mode.`)
|
||||
return
|
||||
return ""
|
||||
}
|
||||
} else {
|
||||
if(!fs.existsSync(path.join(__dirname, '..', 'games'))) {
|
||||
console.error(`Did not find the following folder: ${path.join(__dirname, '..', 'games')}`)
|
||||
console.error('Did you already import any games?')
|
||||
return
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,7 +81,7 @@ function getGameDir(owner, repo) {
|
||||
|
||||
if(!fs.existsSync(game_dir)) {
|
||||
console.error(`Game '${game_dir}' does not exist!`)
|
||||
return
|
||||
return ""
|
||||
}
|
||||
|
||||
return game_dir;
|
||||
@@ -114,7 +95,7 @@ function startServerProcess(owner, repo) {
|
||||
let serverProcess
|
||||
if (isDevelopment) {
|
||||
let args = ["--server", game_dir]
|
||||
serverProcess = cp.spawn("./gameserver", args,
|
||||
serverProcess = cp.spawn("./gameserver", args, // TODO: find gameserver inside the games
|
||||
{ cwd: path.join(__dirname, "./.lake/build/bin/") })
|
||||
} else {
|
||||
serverProcess = cp.spawn("./bubblewrap.sh",
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
[{"url": "https://github.com/leanprover/std4.git",
|
||||
"type": "git",
|
||||
"subDir": null,
|
||||
"rev": "a652e09bd81bcb43ea132d64ecc16580b0c7fa50",
|
||||
"rev": "2e4a3586a8f16713f16b2d2b3af3d8e65f3af087",
|
||||
"name": "std",
|
||||
"manifestFile": "lake-manifest.json",
|
||||
"inputRev": "v4.3.0-rc2",
|
||||
"inputRev": "v4.3.0",
|
||||
"inherited": false,
|
||||
"configFile": "lakefile.lean"}],
|
||||
"name": "GameServer",
|
||||
|
||||
@@ -1 +1 @@
|
||||
leanprover/lean4:v4.3.0-rc2
|
||||
leanprover/lean4:v4.3.0
|
||||
|
||||
+1
-1
@@ -41,7 +41,7 @@ export default defineConfig({
|
||||
'/import': {
|
||||
target: 'http://localhost:8080',
|
||||
},
|
||||
'/api': {
|
||||
'/data': {
|
||||
target: 'http://localhost:8080',
|
||||
},
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user