diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..306d635 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +log.txt +story.json +package-lock.json +node_modules/ +.vscode/ +.DS_Store +*.DS_Store \ No newline at end of file diff --git a/O10-C.js b/O10-C.js deleted file mode 100644 index 4c2963a..0000000 --- a/O10-C.js +++ /dev/null @@ -1,67 +0,0 @@ -// déclaration des variables Discord -const discord = require('discord.js') -const fs = require('fs') -const bot = new discord.Client() - -let histoire = [] - -// activer la réception des messages -bot.on('message', message => { - - // no bots allowed - if (message.author.bot) return // ignorer les autres bots - // nettoyer le message - message.content = message.cleanContent - message.content = message.content.toLowerCase().trim() - // interprete les messages dans les channels publics type "text" - if (message.channel.type === "text" && message.content.startsWith('!')) { - - message.content = message.content.substring(1).trim() - messageText(message) - } - - }) - - -function messageText(message) { - - //console.log(message.channel.name); - //console.log(message.guil) - // bot.channels.find("name","parc a chien").send("Welcome!") - // message.channel.name == "🙀parc-à-chiens") - // message.reply("Hello") - -} - -// interpreter le script O10-C.txt -function loadText() { - try { - const data = fs.readFileSync('./O10-C.txt', 'utf8') - return data - } catch (err) { - console.error(err) - } -} - - -// aller chercher le fichier .txt et le transformer en commandes -function parseText() { - - let scenario = loadText() - let regexNomsSalons = /^# ([^\n].+)\n/gm - let resultatSalons = [...scenario.matchAll(regexNomsSalons)] - // commencer par toutes les occurences de #salon en cherchant des signes # - // [ '# Toilettes\n', '# Numérique\n' ] - // [ 'Toilettes', 'Numérique' ] - // lister tous les noms de salon - for (i in resultatSalons) { - let nomSalon = resultatSalons[i][1] - console.log(nomSalon) - } - -} - -// démarrer le "parseur" -parseText() - -bot.login('token-here') \ No newline at end of file diff --git a/O10-C.txt b/O10-C.txt deleted file mode 100644 index 0ff240c..0000000 --- a/O10-C.txt +++ /dev/null @@ -1,39 +0,0 @@ -# Toilettes - -! look -+ Detecting your presence, the lights turn on by themselves. There's a sink, a hand dryer, a trash can full of paper, a mirror, and freshly paint red door. - -! inspect mirror -+ Looking in the mirror, you can see your own face. It might be a good place to put on make-up quickly. - -! apply make-up -? makeup kit -+ Using the mirror, you put on your make-up properly. You feel beautiful, and somewhat stronger now. {+made up} -- Unfortunately, you don't have any make-up kit. - -! open red door -? red door -+ The red door is already open. -- The door is now open. The toilets are just in front of you. The view fills you with warth. {+red door} - -! close red door -? red door -+ The red door is now closed. {-red door} -- The red door is already closed. - -! inspect trash can -+ This is a trash can. It is full of unstained sanitary paper. - -! remove paper -? paper removed -+ The sanitary paper is now removed. In the middle of the trash can, there is a damaged frog-shaped USB. {+paper removed} - -! take usb -? paper removed -+ You have taken the frog-shaped USB from the trash can. -- You can't see any USB device in here. - -# Numérique - -! look -+ There's a very large and fancy circle table at the center of the room. On this table, there are computers, some electronic devices, pieces of papers, and two giant TV screens. Oh, god, you've never seen screens that big. There's also a board on your right, and a metal closet on your left. At the back of the room stands an arcade machine. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..750b947 --- /dev/null +++ b/README.md @@ -0,0 +1,7 @@ +# Welcome to ZorkDown! +ZorkDown is a lightweight single file game engine to play Zork-like interactive fiction game. +Its Markdown based syntax is meant to be closer to writing logic than pure programing logic. + +[Live demo](https://happycodefarm.github.io/ZorkDown/examples/html/index.html) + +[Documentation](https://github.com/happycodefarm/ZorkDown/blob/master/docs/documentation.md) diff --git a/bordel.js b/bordel.js deleted file mode 100644 index 0fc97c5..0000000 --- a/bordel.js +++ /dev/null @@ -1,70 +0,0 @@ -const Discord = require('discord.js') -const bot = new Discord.Client() - -bot.on('message', message => { - // no bots allowed - if (message.author.bot) return // ignorer les autres bots - // nettoyer le message - message.content = message.cleanContent - message.content = message.content.toLowerCase().trim() - // est-ce un DM? - if (message.channel.type == "dm") { - - if (message.content.startsWith('!')) { - message.content = message.content.substring(1) - } - messageDM(message) - - } else if (message.channel.type === "text" && message.content.startsWith('!')) { - - message.content = message.content.substring(1).trim() - messageText(message) - - } else if (message.channel.type === "text" && message.content.startsWith('|')) { - - message.content = message.content.substring(1).trim() - messageEvaluate(message) - } -}) - -function messageText(message) { - //console.log(message.channel.name); - //console.log(message.guil) - // bot.channels.find("name","parc a chien").send("Welcome!") - - if (message.channel.name == "🙀parc-à-chiens") { - messageChiens(message) - } - - if (message.content.includes("hello")) { - message.reply("Hello") - return - } - -} - -function messageChiens(message) { - - // on démarre un regex sur le contenu du message entrant - - // Juste en dessous, c'est du Regex lol ↓ - let regex = /woaf/g - let result = message.content.replace(regex, "wouf") // replacer tous les woaf par des wouf - - message.reply(result) // repondre au message -} - -function messageDM(message) { - // Cette fonction était vide - message.reply("Well well, sending me some direct messages ? ;)") -} - -function messageEvaluate(message) { - try { - eval(message.content) - } catch (err) { - - } -} - -bot.login('NjkwMTI5NjkxMzkwMzc3OTk4.XnM8iw.llKbd6iUsWrCaS2ylZgY4tZxCb4') diff --git a/docs/documentation.md b/docs/documentation.md new file mode 100644 index 0000000..eb92282 --- /dev/null +++ b/docs/documentation.md @@ -0,0 +1,451 @@ +Documentation +============= + +ZorkDown is a lightweight single file game engine to play [Zork]-like interactive game. There are two things: the parser on the one hand (a single light .js file), and a your text content on the other hand, i.e. the content of the textual game. + +Its syntax is meant to be closer to writing logic than pure programing logic. (Blablabla logic-less syntaxe based on markdown.) Here is a simple example. + +``` +! look ++ Detecting your presence, the lights turn on by themselves. +``` + +As you can see, anyone can fairly easily understand what these two lines are about : when I look (action), there's a description (response). This syntax is about being easily writtable, even by people who are not confortable with code. It is rather simple and yet robust and powerful. + +[Zork]: https://en.wikipedia.org/wiki/Zork + +### Main functionalities ### + +ZorDown is inspired by [Zork]. (explain how Zork works) + +#### – Actions #### + +Each possible action is described through paragraphs, and each paragraph is composed by lines. The first line of a paragraph must be an action, described by a `!` at the beginning of the line. + +``` +! take lamp +``` + +The paragraph beginning with `! take lamp` is the one played when the players writes `!take lamp`. It is possible, of course, to have multiple ways to write an action, each way being seperated by ` / `. + +``` +! take lamp / grab lamp / take lantern / grab lantern +``` + +> Note that, as long as the action written by the player includes `take lamp`, anything works. That means it is not necesary to write `take the damn lamp` in your game file, because `take the damn lamp` has `take lamp` in it. Note that it is also case-insensitive. Therefore: +> ~~`! Take Lamp / take the lamp / take the magical lamp`~~ :-1: +> `! take lamp` :thumbsup: + +#### – Responses #### + +Each paragraph has at least an action and an response. A response is what the game answers to an action. A response is described with a `+` sign at the beginning of the line. + +``` ++ You have taken the lamp. +``` + +Put together, you have a paragraph: + +``` +! take lamp ++ You have taken the lamp. +``` + +It is also possible to have multiple reponses: + +``` +! take lamp ++ You have taken the lamp ++ You have the lamp, great! ++ The lamp is yours. ++ My prrrrrecioussss! +``` + +In such a case, a random reponse will be picked by the game engine, among the different possibilities. + +#### – Variables #### + +The game engine offers the possibility to declare booleans. Basically, booleans are on / off buttons for code. Their values are `true` (on) or `false` (off), nothing else. A boolean can describe a state (are my hands washed? Is the dog here?), or an item (do I have the key?), or whatever you want that could be answered by yes or no. + +A boolean is set into curly brackets, inside of a response. + +``` ++ You have taken the lamp {+magical lamp} +``` + +A `+` sign in front of the boolean name means an "on" or `true` state, while `-` means an "off" or `false` state. + +``` +! drop lamp ++ You have dropped the magical lamp. {-magical lamp} +``` + +> Note: unlike many programing langages, you don't need to declare your variables at the beginning of your file, like `public bool magicalLamp = false;`. Variables are set to `false` by default, and automatically declared by the parser. Every variables are global. Also, the name of your boolean can contain spaces. + +#### – Items +Every states and changes are possible through booleans, including items. During the game, items collected by the player are stored in his inventory and accessible through the `! inventory` command, like Zork. Here is the way to attach a description to a boolean and to make it an item. +``` +! take lamp ++ You have taken it {+magical lamp -> A magical lamp offered by grandma} +``` +By doing so, the boolean `magical lamp` is going to be considered as an item by the program. When recieving the command `! inventory`, the game will output: +``` +You have: + — A magical lamp offered by grandma +``` +If you want anything to be displayed in the `inventory`, you just have to write its description *once*. +``` +! clean hands ++ Your hands are now clean {+clean hands -> Your hands clean} +``` +In the `inventory`, when having the lamp and your hands clean: +``` +You have: + — A magical lamp offered by grandma + — Your hands clean +``` +> Note: As mentioned above, you only need to write a description once. Plus, **that description mustn't be done** inside of a condition, like `? lamp -> A magical lamp`. It must be done inside of a response, with the boolean setting. Therefore: +> ~~`? lamp -> A magical lamp`~~ :thumbsdown: +> `{+lamp -> A magical lamp}` :thumbsup: + +#### – Conditions #### + +Booleans are used through conditions. Conditions are useful for making different kind of interactions in your game. Conditions are written with a `?` sign at the beginning of the line. + +``` +? magical lamp +``` + +That line above literally means “Do I have the magical lamp?”. If `magical lamp` is set to `true` (or `+` or "on", that's the same), then the condition is `true`. When the condition is true, the game engine replies the positive response (`+` response): + +``` +! take lamp +? magical lamp ++ … But you already have taken the lamp! +``` + +Otherwise, if the condition is `false`, the game engine replies the negative response, written with a `-` sign. It is the equivalent of an `if / else` statement in programing language. + +``` +! take lamp +? magical lamp ++ You already have taken the lamp! +- You have taken the lamp. {+magical lamp} +``` + +It is also possible to reverse a condition with the **not** operator `!`. In this case, `? !magical lamp` means "Do I have **not** the magical lamp?". This condition is `true` if the player do **not** have the magical lamp. + +``` +! take lamp +? !magical lamp ++ You have taken the lamp. {+magical lamp} +- You already have the lamp! +``` + +You can check multiple booleans in a single condition, with the **and** operator `&`. + +``` +! take lamp +? !magical lamp & !blind ++ Thankfully, you're not blind yet. You have taken the lamp. {+magical lamp} +- It is not possible to take the lamp. +``` + +You can aslo check multiple booleans in a single condition, with the **or** operator `/`. + +``` +! take lamp +? magical lamp / night vision ++ You really don't need that lamp. +- You have taken the lamp. {+magical lamp} +``` + +And you can combine the **not**, the **and** and the **or** operators to create a complex condition. + +``` +! take lamp +? super powers / !scared of heights & wooden ladder / debug mode ++ You have taken the lamp. {+magical lamp} +- You don't have what it takes to reach the lamp. +``` + +In this case, the **and** operator takes precedence over the **or**. +To be able to take the lamp you must have **super powers** OR NOT be **scared of heights** AND have the **wooden ladder** OR be in **debug mode**. + +#### – Multiple conditions #### + +Checking multiple booleans in a single condition sure is useful, but can be rather limited. Know that there is the possibility of having multiple conditions sequentially. + +``` +! take lamp +? !troll here ++ You reach for the lamp… +- The troll prevents you from reaching the lamp! +? magical lamp ++ But you already have the lamp, dummy! +- And successfuly take it! {+magical lamp} +``` + +The conditions are checked from up to down. If the first condition is `true`, the game engine outputs its `+` response and then checks the next condition, and so on. However, as soon as a negative response is reached, the multiple conditions take end. The rest is not checked. That would be the equivalent of a `return` in programing langages. + +In the example above, if the troll is *not* not here (i.e the troll is here), then the condition `!troll here` is `false`. The negative response is output (`The troll prevents you from reaching the lamp!`), and the rest is not checked because of the negative answer. + +This possibility is useful for changing environment description depending on different conditions: + +``` +! look ++ The room is rather dark. +? troll here ++ A tall and menacing figure is staring at you. +``` + +Of course, multiple responses work with multiple conditions. + +``` +! take lamp +? !troll here ++ You reach for the lamp… +- The troll prevents you from reaching the lamp! +- The troll roars to intimidate you. +- The troll hits you violently with its club. {=DEATH} +? magical lamp ++ But you already have the lamp, dummy! +- And successfuly take it! {+magical lamp} +``` + +#### Others #### + +It is possible to add comments to your file: words that are going to be ignored by the parser. To do so, type `>` at the beginning of your comment line. + +``` +> This is a comment. This is not read by the parser. +``` + +To make a default response for actions that are not understandable by the game engine, write `! *`, as below: +``` +> This is output by default, if nothing else is to be ouput. +! * ++ I don't understand this command. +``` + +To trigger the end of the game, just add `{=DEATH}` to a response, just as below: + +``` +! take lamp ++ … But the lamp was a lie! You die in the process. {=DEATH} +``` + +### Salons ### + +This part is more specific to Discord. If you want your game to run as a bot on a discord server, you might want to read the "Parser" part down below. + +#### Local actions +This game engine offers the possibility to run through different salons, with specific content for each salon, like a character going from a room to another. The character can be only at one salon at a time. To write content for a salon, write its name as following: + +``` +# 🗺o10-c +> This is the main salon of the game. +``` +Every actions following this line will be only doable in the #🗺010-c salon. + +``` +# 🗺o10-c + +! look ++ Welcome to O10-C! + +# 🏡villa +> This no longer belongs to 🗺010-c. It belongs to 🏡villa. + +! look ++ Welcome to the villa! +``` + +Note that the first salon in your game file will be the default salon: this is where the game is going to start. In our example, that would be #🗺010-c. + +#### Global actions + +For global actions, i.e actions that are possible everywhere, just write those in the top of the story, prior to any #salon creation: + +``` +> The following paragraphs are global + +! say ++ You said that. ++ You said something. ++ You said it. + +# First salon +``` +#### Going from one salon to another +You can go from the current salon to another with a command into curly brackets, like a boolean: `{@(name of salon)}` +``` +# 🗺010-c + +! go to #🌈général / go to general ++ Let's go! Join me in #🌈général channel. {@🌈général} +``` +> Note: do not type `#` for that command. Just type the correct salon name without the `#` sign. + +When the game goes to another salon, it automatically triggers the `! look` action. The `! look` action is what contains the description of the place. Every salon of your game should have at least a `! look` action and a `! leave / go back` action (without the possibility to leave, you would be trapped forever!). + +``` +# 🌈général + +! look / observe ++ You are in the school entrance hall. + +! leave / go back ++ Have you forgotten your lamp? You decide to leave. {@🗺010-c} +``` + + +### Examples ### + +#### Taking and droping an item: +``` +! take lamp +? magical lamp ++ You already have taken the lamp! +- You have taken the lamp. {+magical lamp} + +! drop lamp / put lamp +? magical lamp ++ You have droped the lamp. {-magical lamp} +- You don't have a lamp. +``` +A very basic item interaction: taking and droping. You can't drop something you don't have, or take something you already have, can you? + +#### A default response for an action: +``` +# everywhere + +! take ++ You can't take that. + +! take lamp / take lantern ++ You have taken the lamp. {+lamp} +``` +In this example, when the player tries to `take` something, the response `You can't take that.` is output by default, because there is `take` in the action. However, if the player types `!take lamp` instead of something else, the `! take lamp` action takes priority over the `! take` action, because it is closer to what the player typed. Default responses are very useful for the player to understand what actions are possible, even though they're trying to take / see / use the wrong thing. + +#### Entering a code: +``` +! enter 6666 / code 6666 +? padlock ++ The padlock has already been unlocked! +- The padlock is unlocked, the closet is now open. {+padlock} + +! enter / code +? padlock ++ The padlock has already been unlocked! +- … But the padlock remains locked. +``` +This example is very similar to the previous one, but applied in a different context. If the player tries entering a different code from the correct one, it won't work. + +#### A random death machine: +``` +! russian roulette ++ *click* ! ++ *click* ! ++ *click* ! ++ *click* ! ++ *click* ! ++ BANG! {=DEATH} +``` +In this example, there's a 1/6 chance for the `+ BANG!` response to be triggered. If so, the player dies. This can be useful for a situation in which the player has to do the same thing all over again, until it works. + +#### A changing description +``` +! look +? light on ++ An armor stands still before you. +- You can't see anything right now. +? !sword ++ It holds a great steel sword. +- The sword it was holding has been taken. +``` +In this example, the description will change depending on two things. The light on the one hand, and the sword as an item on the other hand. If there's no light, nothing can be seen, thus the description ends with `You can't see anything right now` regardless of having the sword or not. If the light is on, then the description begins with `An armor stands still before you`. It is then followed by one of the next responses: `It holds a great steel sword` or `The sword it was holding has been taken`, depending on `? !sword`. This is very useful when some actions have impacts on the environment. + +### Quick syntax reference + +#### Single action +``` +! action +``` +#### Alternatives for a single action +``` +! action / action / action +``` +#### Wildcard action +``` +! * +``` +#### Single condition +``` +? boolean +``` +#### Reversed condition +``` +? !boolean +``` +#### Multiple conditions +``` +? boolean1 & boolean2 +``` +#### Positive response +``` ++ You did it. +``` +#### Negative response +``` +- You didn't do it. +``` +#### Random positive responses +``` ++ 1/3 chances to be output. ++ 1/3 chances too. ++ And, 1/3 chances again. +``` +#### Random negative responses +``` +- 1/3 chances to be output. +- 1/3 chances too. +- And, 1/3 chances again. +``` +#### Setting boolean to true +``` +{+boolean} +``` +#### Setting boolean to false +``` +{-boolean} +``` +#### Setting boolean with a description +``` +{+boolean -> Description content} +``` +#### Salon specifying +``` +# salonName +``` +#### Salon changing +``` +{@salonName} +``` +#### Game Over +``` +{=DEATH} +``` +#### Comment +``` +> This is a comment +``` + +### Parser ### + +#### What is a parser? + +#### How to make it run on Discord + + +### Other ### diff --git a/examples/etherpad/index.html b/examples/etherpad/index.html new file mode 100644 index 0000000..eaf5d5e --- /dev/null +++ b/examples/etherpad/index.html @@ -0,0 +1,212 @@ + +
+ + +nowhere
+"+I(e[r].content)+"\n"},R.fence=function(e,r,t,n,s){var o,i,a,c,l,u=e[r],p=u.info?T(u.info).trim():"",h="",f="";return p&&(h=(a=p.split(/(\s+)/g))[0],f=a.slice(2).join("")),0===(o=t.highlight&&t.highlight(u.content,h,f)||I(u.content)).indexOf(""+o+"\n"):""+o+"\n"},R.image=function(e,r,t,n,s){var o=e[r];return o.attrs[o.attrIndex("alt")][1]=s.renderInlineAsText(o.children,t,n),s.renderToken(e,r,t)},R.hardbreak=function(e,r,t){return t.xhtmlOut?"=4))break;s=++n}return e.line=s,(o=e.push("code_block","code",0)).content=e.getLines(r,s,4+e.blkIndent,!1)+"\n",o.map=[r,e.line],!0}],["fence",function(e,r,t,n){var s,o,i,a,c,l,u,p=!1,h=e.bMarks[r]+e.tShift[r],f=e.eMarks[r];if(e.sCount[r]-e.blkIndent>=4)return!1;if(h+3>f)return!1;if(126!==(s=e.src.charCodeAt(h))&&96!==s)return!1;if(c=h,(o=(h=e.skipChars(h,s))-c)<3)return!1;if(u=e.src.slice(c,h),i=e.src.slice(h,f),96===s&&i.indexOf(String.fromCharCode(s))>=0)return!1;if(n)return!0;for(a=r;!(++a>=t)&&!((h=c=e.bMarks[a]+e.tShift[a])<(f=e.eMarks[a])&&e.sCount[a] =4))break;s=++n}return e.line=s,(o=e.push("code_block","code",0)).content=e.getLines(r,s,4+e.blkIndent,!1)+"\n",o.map=[r,e.line],!0}],["fence",function(e,r,t,n){var s,o,i,a,c,l,u,p=!1,h=e.bMarks[r]+e.tShift[r],f=e.eMarks[r];if(e.sCount[r]-e.blkIndent>=4)return!1;if(h+3>f)return!1;if(126!==(s=e.src.charCodeAt(h))&&96!==s)return!1;if(c=h,(o=(h=e.skipChars(h,s))-c)<3)return!1;if(u=e.src.slice(c,h),i=e.src.slice(h,f),96===s&&i.indexOf(String.fromCharCode(s))>=0)return!1;if(n)return!0;for(a=r;!(++a>=t)&&!((h=c=e.bMarks[a]+e.tShift[a])<(f=e.eMarks[a])&&e.sCount[a]The great adventure of the O10-C.
+
+
+
\n"},R.fence=function(e,r,t,n,s){var o,i,a,c,l,u=e[r],p=u.info?T(u.info).trim():"",h="",f="";return p&&(h=(a=p.split(/(\s+)/g))[0],f=a.slice(2).join("")),0===(o=t.highlight&&t.highlight(u.content,h,f)||I(u.content)).indexOf(""+I(e[r].content)+"
\n"):""+o+"
\n"},R.image=function(e,r,t,n,s){var o=e[r];return o.attrs[o.attrIndex("alt")][1]=s.renderInlineAsText(o.children,t,n),s.renderToken(e,r,t)},R.hardbreak=function(e,r,t){return t.xhtmlOut?""+o+"
\n":"
\n"},R.softbreak=function(e,r,t){return t.breaks?t.xhtmlOut?"
\n":"
\n":"\n"},R.text=function(e,r){return I(e[r].content)},R.html_block=function(e,r){return e[r].content},R.html_inline=function(e,r){return e[r].content},M.prototype.renderAttrs=function(e){var r,t,n;if(!e.attrs)return"";for(n="",r=0,t=e.attrs.length;r