From 0ed4f98098df17485a39df4c1901d895de115a63 Mon Sep 17 00:00:00 2001 From: Alexander Yakovlev Date: Sat, 16 Jan 2016 23:24:23 +0700 Subject: [PATCH] Object manipulation - WIP I'm stuck because of a COFFEESCRIPT bug or whatever. --- Gulpfile.coffee | 27 ++++++++-------- game/begin.coffee | 3 ++ game/init.coffee | 1 + game/story.coffee | 9 +++--- html/index.html | 4 +-- lib/obj.coffee | 41 ++++++++++++++---------- lib/room.coffee | 74 ++++++++++++++++++++++++-------------------- lib/situation.coffee | 12 +------ lib/undum.js | 21 ++++++++++--- 9 files changed, 105 insertions(+), 87 deletions(-) diff --git a/Gulpfile.coffee b/Gulpfile.coffee index b452c44..4ad5430 100644 --- a/Gulpfile.coffee +++ b/Gulpfile.coffee @@ -51,21 +51,21 @@ opts = _.assign({}, watchify.args, { debug: true transform: [coffeify] }); -bundler = watchify(browserify(opts)); +bundler = watchify(browserify(opts)) bundle = () -> return bundler.bundle() .on('error', gutil.log.bind(gutil, 'Browserify Error')) .pipe(source('bundle.js')) - .pipe(gulp.dest('./build/game')); + .pipe(gulp.dest('./build/game')) gulp.task('concatCoffee', () -> return gulp.src(sources) .pipe(concat('./main.coffee')) - .pipe(gulp.dest('./build/game')); -); + .pipe(gulp.dest('./build/game')) +) -gulp.task('coffee', ['concatCoffee'], bundle); +gulp.task('coffee', ['concatCoffee'], bundle) bundler.on('update', bundle); bundler.on('log', gutil.log); @@ -77,21 +77,20 @@ gulp.task('serve', ['build'], () -> server: { baseDir: 'build' } - }); + }) sassListener = () -> - reload('./build/css/main.css'); + reload('./build/css/main.css') - gulp.watch(['./html/*.html'], ['html']); - gulp.watch(['./sass/*.scss'], ['sass']); - gulp.watch(['./img/*.png', './img/*.jpeg', './img/*.jpg'], ['img']); - gulp.watch(['./game/*.coffee'], ['coffee']); - gulp.watch(['./lib/*.coffee','./lib/*.js'], ['coffee']); + gulp.watch(['./html/*.html'], ['html']) + gulp.watch(['./sass/*.scss'], ['sass']) + gulp.watch(['./img/*.png', './img/*.jpeg', './img/*.jpg'], ['img']) + gulp.watch(['./lib/*.coffee', './lib/*.js', './game/*.coffee'], ['coffee']) - gulp.watch(['./build/css/main.css'], sassListener); + gulp.watch(['./build/css/main.css'], sassListener) gulp.watch( ['./build/game/bundle.js', './build/img/*', './build/index.html'], - browserSync.reload); + browserSync.reload) ) gulp.task('html-dist', html('./dist')); diff --git a/game/begin.coffee b/game/begin.coffee index 64cc316..ec90f2b 100644 --- a/game/begin.coffee +++ b/game/begin.coffee @@ -42,6 +42,9 @@ writemd = (system, text) -> text = markdown(text) system.write(text) +get_room = (name) -> + return undum.game.situations[name] + # The first room of the game. # For accessibility reasons the text is provided in HTML, not here. room "start", diff --git a/game/init.coffee b/game/init.coffee index 658e2ad..1bd3030 100644 --- a/game/init.coffee +++ b/game/init.coffee @@ -2,5 +2,6 @@ # All code in this file comes last, so the game is almost ready by this point. undum.game.init = (character, system) -> + bugg.put("lair") window.onload = undum.begin diff --git a/game/story.coffee b/game/story.coffee index 868128d..fcbdf0c 100644 --- a/game/story.coffee +++ b/game/story.coffee @@ -2,6 +2,8 @@ room "world", tags: ["start"], optionText: "Enter the world", ways: ["plaza"] + enter: () -> + bugg.put(@name) content: """ ### Rhinestone Room @@ -40,7 +42,7 @@ room "plaza", if character.sandbox.has_mark? return "You already talked to him, no need to bug the man twice." character.sandbox.has_mark ?= true - undum.game.situations["lair"].destination() + get_room("lair").destination() """ “Here, let me mark it on your map.” """ @@ -48,7 +50,7 @@ room "plaza", room "shop", title: "The Shop" - ways: ["plaza", "lair"] + ways: ["plaza", "shop-inside", "lair"] content: """ Being the only shop in town, this trendy establishment did not need a name. It's an open question why it had one, especially because its name was "Hung Crossing". @@ -57,8 +59,8 @@ room "shop", """ room "lair", - ways: ["shop"] title: "The Lair" + before: "Seems like you can't leave this just like that." content: """ The Lair of Yog-Sothoth is a very *n'gai* cave, full of *buggs-shoggogs* and *n'ghaa ng'aa*. """ @@ -68,7 +70,6 @@ bugg = obj "bugg-shoggog", act: () -> this.delete() return "You eat the bugg mass. Delicious and raw." -bugg.put("lair") room "shop-inside", ways: ["shop"] diff --git a/html/index.html b/html/index.html index e94eccb..730d04e 100644 --- a/html/index.html +++ b/html/index.html @@ -32,9 +32,9 @@

There are no either cool editor or clear instructions. You'll have to copy the source code and edit the CoffeeScript files in the game folder. Then compile them.

-

This is not a technical documentation or UI tutorial, this is a game. +

This is neither a technical documentation nor UI tutorial, this is a game. You're supposed to note all the cool and curious features yourself. - If you want some technical info, there is the source code and a wiki.

+ If you want some technical info, there are the source code and a wiki.

I'm just here to point out that you are playing this in the web browser, online. And you don't need to learn The Complete Javascript, Volumes I-III to write in this style.

So let me show you... The World.

diff --git a/lib/obj.coffee b/lib/obj.coffee index 204161b..f3247a6 100644 --- a/lib/obj.coffee +++ b/lib/obj.coffee @@ -5,35 +5,42 @@ objlink = (content, ref) -> Array::remove = (e) -> @[t..t] = [] if (t = @indexOf(e)) > -1 -class RaconteurObj +parsedsc = (text, name) -> + window.objname = name + text = text.replace /([\s^])\{\{(.+)\}\}([\s$])/g, (str, p1, p2, p3) -> + name = window.objname + window.objname = undefined + return p1+objlink(p2, name)+p3 + return text + +# An object class. +# An object cannot be in several locations at once, you must clone the variable. +class SaletObj constructor: (spec) -> for key, value of spec this[key] ?= value level: 0 - look: (character, system, f) -> + look: (character, system, f) => if @dsc text = markdown(@dsc.fcall(this, character, system, f)) text = "" + text + "" - window.name = @name - text = text.replace /([\s^])\{\{(\w+)\}\}([\s$])/g, (str, p1, p2, p3) -> - name = window.name - window.name = undefined - return p1+objlink(p2, name)+p3 - return text - take: () -> "You take the #{@name}." # taking to inventory - act: () -> "You don't find anything extraordinary about the #{@name}." # object action - dsc: () -> "You see a {{#{@name}}} here." # object description - inv: () -> "It's a {{#{@name}.}}" # inventory description + # replace braces {{}} with link to _act_ + return parsedsc(text, @name) + take: (character, system) => "You take the #{@name}." # taking to inventory + act: (character, system) => "You don't find anything extraordinary about the #{@name}." # object action + dsc: (character, system) => "You see a {{#{@name}}} here." # object description + inv: (character, system) => "It's a {{#{@name}.}}" # inventory description location: "" - put: (location) -> + put: (location) => @level = 0 # this is scenery - undum.game.situations[location].objects[@name] = this - @location = location - delete: () -> + if undum.game.situations[location]? + undum.game.situations[location].take(this) + @location = location + delete: () => undum.game.situations[@location].objects.remove(this) obj = (name, spec) -> spec ?= {} spec.name = name - return new RaconteurObj(spec) + return new SaletObj(spec) module.exports = obj diff --git a/lib/room.coffee b/lib/room.coffee index d2d011f..c601499 100644 --- a/lib/room.coffee +++ b/lib/room.coffee @@ -31,6 +31,10 @@ addClass = (element, className) -> here = () -> return undum.game.situations[document.getElementById("current-situation").getAttribute("data-situation")] +cls = (system) -> + system.clearContent() + system.clearContent("#intro") + update_ways = (ways, name) -> content = "" distances = [] @@ -62,23 +66,11 @@ update_ways = (ways, name) -> class SaletRoom extends RaconteurSituation constructor: (spec) -> RaconteurSituation.call(this, spec) - if spec.objects? - @objects = spec.objects - if spec.exit? - @exit = spec.exit - if spec.enter? - @enter = spec.enter - if spec.clear? - @clear = spec.clear - if spec.writers? - @writers = spec.writers - if spec.extendSection? - @extendSection = spec.extendSection - if spec.title? - @title = spec.title + for index, value of spec + this[index] = value return this title: "Room" - objects: [] + objects: {} extendSection: false distance: Infinity # distance to the destination clear: true # clear the screen on entering the room? @@ -88,7 +80,7 @@ class SaletRoom extends RaconteurSituation Unlike @after this gets called after the section is closed. It's a styling difference. ### - exit: (character, system, to) -> + exit: (character, system, to) => return true ### @@ -99,7 +91,7 @@ class SaletRoom extends RaconteurSituation The upstream Undum version does not allow you to redefine @enter function easily but allows custom @exit one. It was renamed as @entering to achieve API consistency. ### - enter: (character, system, from) -> + enter: (character, system, from) => return true ### @@ -111,10 +103,9 @@ class SaletRoom extends RaconteurSituation My version of `enter` splits the location description from the effects. Also if f == this.name (we're in the same location) the `before` and `after` callbacks are ignored. ### - entering: (character, system, f) -> + entering: (character, system, f) => if @clear and f? - system.clearContent() - system.clearContent("#intro") + cls(system) if f != @name and f? @visited++ @@ -138,8 +129,7 @@ class SaletRoom extends RaconteurSituation if f != @name and @before? current_situation += markdown(@before.fcall(this, character, system, f)) - if @look - current_situation += @look character, system, f + current_situation += @look character, system, f if f != @name and @after? current_situation += markdown(@after.fcall(this, character, system, f)) @@ -152,7 +142,11 @@ class SaletRoom extends RaconteurSituation if @choices system.writeChoices(system.getSituationIdChoices(@choices, @minChoices, @maxChoices)) - look: (character, system, f) -> + ### + An internal function to get the room's description and the descriptions of + every object in this room. + ### + look: (character, system, f) => update_ways(@ways, @name) retval = "" @@ -160,25 +154,37 @@ class SaletRoom extends RaconteurSituation if @content retval += markdown(@content.fcall(this, character, system, f)) - if @objects? then for thing in @objects + console.log @objects + for name, thing of @objects retval += thing.look() return retval + ### + Puts an object in this room. + ### + take: (thing) => + @objects[thing.name] = thing + # BUG: for some really weird reason if the call is made in init function or + # during the initialization, this ALSO puts the thing in the start room. + undum.game.situations["start"].objects = {} + ### Object action. A function or a string which comes when you click on the object link. You could interpret this as an EXAMINE verb or USE one, it's your call. ### - act: (character, system, action) -> + act: (character, system, action) => if (link = action.match(/^_act_(.+)$/)) #object action - for thing in @objects - if thing.name == link[1] + for name, thing of @objects + if name == link[1] # We check the "take" function. If it exists, the player can take this object. # If not, we check the "act" function. if thing.take - @objects.remove(thing) character.sandbox.inventory.push thing - @enter(character, system, @name) + delete @objects[name] + console.log @objects + cls(system) + @entering.fcall(this, character, system, @name) return print(thing.take.fcall(thing, character, system)) if thing.act return print(thing.act.fcall(thing, character, system)) @@ -189,7 +195,7 @@ class SaletRoom extends RaconteurSituation return RaconteurSituation.prototype.act.call(this, character, system, action) # Marks every room in the game with distance to this room - destination: () -> + destination: () => @distance = 0 candidates = [this] @@ -202,9 +208,9 @@ class SaletRoom extends RaconteurSituation candidates.push(node) room = (name, spec) -> - if spec - spec.name = name - retval = new SaletRoom(spec) - return retval.register() + spec ?= {} + spec.name = name + retval = new SaletRoom(spec) + return retval.register() module.exports = room diff --git a/lib/situation.coffee b/lib/situation.coffee index 08dc7f4..3c3cb1c 100644 --- a/lib/situation.coffee +++ b/lib/situation.coffee @@ -17,16 +17,6 @@ all copies or substantial portions of the Software. undum = require('./undum.js') markdown = require('./markdown.coffee') -### - fcall() (by analogy with fmap) is added to the prototypes of both String and - Function. When called on a Function, it's an - alias for Function#call(); when called on a String, it only returns the - string itself, discarding any input. -### - -Function.prototype.fcall = Function.prototype.call; -String.prototype.fcall = () -> return this - ### The prototype RaconteurSituation is the basic spec for situations created with Raconteur. It should be able to handle any use case for Undum. @@ -56,7 +46,7 @@ RaconteurSituation.inherits(undum.Situation) Undum API. ### -RaconteurSituation.prototype.act = (character, system, action) -> +RaconteurSituation.prototype.act = (character, system, action) => actionClass = action.match(/^_(\w+)_(.+)$/) that = this diff --git a/lib/undum.js b/lib/undum.js index fa67a76..7b15ac3 100644 --- a/lib/undum.js +++ b/lib/undum.js @@ -87,7 +87,7 @@ var assert = function(expression, message) { * parameter is an object. So you could write: * * var situation = Situation({ - * enter: function(character, system, from) { + * entering: function(character, system, from) { * ... your implementation ... * } * }); @@ -151,7 +151,7 @@ var assert = function(expression, message) { var Situation = function(opts) { if (opts) { - if (opts.entering) this._enter = opts.entering; + if (opts.entering) this._entering = opts.entering; if (opts.act) this._act = opts.act; // Options related to this situation being automatically @@ -691,7 +691,7 @@ System.prototype.writeChoices = function(listOfIds, elementSelector) { continue; } - var optionText = situation.optionText(character, this, + var optionText = situation.optionText.fcall(this, character, this, currentSituation); if (!optionText) optionText = "choice".l({number:i+1}); var $option = $("
  • "); @@ -708,6 +708,15 @@ System.prototype.writeChoices = function(listOfIds, elementSelector) { doWrite($options, elementSelector, 'append', 'after'); }; +/* + * fcall() (by analogy with fmap) is added to the prototypes of both String and + * Function. When called on a Function, it's an + * alias for Function#call(); when called on a String, it only returns the + * string itself, discarding any input. + */ +Function.prototype.fcall = Function.prototype.call; +String.prototype.fcall = function() { return this; } + /* Returns a list of situation ids to choose from, given a set of * specifications. * @@ -961,7 +970,9 @@ System.prototype.setQuality = function(quality, newValue) { */ var Character = function() { this.qualities = {}; - this.sandbox = {}; + this.sandbox = { + inventory: [] + }; }; /* The data structure holding the content for the game. By default @@ -1021,7 +1032,7 @@ var game = { * * function(character, system, oldSituationId, newSituationId); */ - enter: null, + entering: null, /* Hook for when the situation has already been carried out * and printed. The signature is: