0 votes
by (190 points)
edited by
So, I wrote some code for a game of mine like half a year ago, in Harlowe 1.2.4 (or whatever iteration of that was out at the time, I don't quite remember). The idea for the code is to be able to combine two objects to create a third object. I don't know if that code already exists out there at this moment; when I was scouring the internet at that time it did not, so I sort of cobbled together something by myself.

It works! Well, at least in Harlowe 1.2.4. I doubt it's the best-written or neatest code out there, but it works.

Unfortunately, when I switch story formats it....completely breaks? Looking at the variables in Harlowe 2.0.1, clicking on the inventory items does change the $clickedItem accordingly, but it doesn't change anything else. I suspect it has something to do with the (replace:?click) stuff I'd coded to simulate a loop in Harlow 1.2.4, but I'm not sure, especially since (replace:) still seems to have the same functionality in Harlowe 2.0.1. I don't remember where I got the idea to use (replace:) for loops, surely from the old forums but I can't find the actual thread where I found the suggestion. If there's a better way now, please let me know!

The reason I'd like to switch story formats is because Harlowe 2.0.1 has more functionality and is easier to use for things like formatting the css of passages based on their tags, which I do use a lot in the game. I'm pondering whether I should just stay with Harlowe 1.2.4 just to not have to worry about the broken code, at least for this game, but I am curious as to why it doesn't work in the first place...

I won't put my full game here, but I made a little demo of the combination system. It works almost exactly the same as in the actual game, stripped of the stuff that's not relevant to the combination code itself. I tested it out in both story formats and sure enough, it works like a charm in Harlow 1.2.4 (as far as I've found, there could always be bugs of course...) and pretty much not at all in Harlowe 2.0.1 (Also I apologize, I don't quite know how to upload the demo other than just putting it on a hosting site, nor if this is the right protocol for these things...I could post all the code snippets but there's a lot of passages and that would be tedious to replicate for yall, I would think. Can post any relevant code as needed!).



Tl;dr: My code that works fine in Harlowe 1.2.4 doesn't in Harlowe 2.0.1 and I have no idea why, and would like some input.

3 Answers

+1 vote
by (159k points)
selected by
Best answer

fore thoughts:
a. I'm unsure why you are using a (datamap:) object to store your inventory in because in your example the key and value are the same for each key/value pair, I believe that a (dataset:) object would be better suited to what your example is currently doing.

b. Due to how each (click:) (and related) macro needs to wait until after the whole of passage has been rendered before it can scan the whole of the generated HTML structure to find it's target(s), and due to how certain post modifications to the current page's structure can cause that scanning to be repeated, it is generally a good idea to only use the (click:) (or related) macro whenever you can't achieve the result you're looking for with a (link:) (or related) macro.

c. You should change any short term variables in a Harlowe 2 project to temporary variables whenever possible, that way they don't pad your story History or Saves with unnecessary data.

d. The correct way to hide any visual output generated by a startup tagged passage is to include the following CSS within your story's Story Stylesheet area:

tw-include[type="startup"] {
	display: none;

e. It is generally a good idea to give meaningful names to your named hooks, as it helps to make them both unique and easier to remember what they are being used for.

f. The Array documentation of both Harlowe 1.x and 2.x states that you should use parentheses when using an expression (like the contents of a variable) to indicate which collection element you want to reference, this syntax is also true for Datamaps and Datasets.

(set: $map to (datamap: "first", 1, "second", 2))
(set: $key to "first")

Correct syntax to use to access collection element via a value in a variable.
first: (print: $map's ($key))

Invalid syntax that the story format is currently allowing.
first: (print: $map's $key)


The following prototype either modifies the content of one of your existing Passage or it adds new Passages to replace the functionality of some of your own, I used new Passages so that you could keep your existing ones to compare the new code to the old. If you wish you can move the code from my new Passages into your existing ones.
It also adds a new variable named $selected, replaces some existing story variables with temporary variables, and totally ignores some of your existing variables which you could delete if you wish.

1. Initialising the $selected variable in your startup tagged Passage.

(set: $selected to (dataset:))

2. Modifying your Inventory Passage to show the selected items and inventory lists.
It uses two meaningful named hooks to display the list of currently selected items and the inventory list.

Click on two clues to try to link them.

|selected>[(display: "List Selected")]

|inventory>[(display: "List Inventory")]

(link: "Return to game.")[(go-to: $returnTo)]

3. New List Selected Passage, it replaces your current Selecting Items Passage.
It uses a (for:) macro to loop through and displays the current content (if there is any) of the $selected collection.

(if: $selected's length > 0)[\
	(for: each _item, ...$selected)[\
		_item selected.

(link: "Combine")[(display: "Combine Selected")]

Click a third item to reset.

4. New List Inventory Passage, it replaces your current Inventory List Passage.
It loops through the names of the items in your inventory and uses a (print:) macro to dynamically create a link to allow the selection of each item. It needs to dynamically create the link because the _item temporary variable will not be available at the time the link is selected.

(set: _names to (datanames: $inv))\
(if: _names's length > 0)[\
	(for: each _item, ...(datanames: $inv))[\
		(print: '(link: _item)[(set: _selectedItem to "' + _item + '")(display: "Select Item")]')

5. New Select Item Passage, it replaces your current Item Selector Passage.
It either tries to a the selected item to the $selected collection or resets the collection, it doesn't need to test if the selected item is already in the collections because a (dataset:) object won't allow the same value to be added more than once. The code includes HTML comments that you can remove if you wish, although the main one does remind you which temporary variables you need to use.

This code block expects the _selectedItem temporary variable to content the ID of the selected item.
(if: $selected's length < 2)[
	<!-- Try to add the item to the unique collection. --> 
	(set: $selected to it + (dataset: _selectedItem))

] (else:) [
	<!-- reset the collection. -->
	(set: $selected to (dataset:))

<!-- Refresh the page. -->
(replace: ?selected)[(display: "List Selected")]
(replace: ?inventory)[(display: "List Inventory")]

6. New Combine Selected Passage, it replaces your current Evaluation Passage.
It checks if the $selected collection contains particular combinations of items, and does what is needed based on the outcomes. 

(if: $selected contains "Cheese" and $selected contains "Pie")[
	(goto: "Cheese Pie")

] (elseif: $selected contains "Cheese" and $selected contains "Cake")[
	(goto: "Cheesecake")

] (else:) [\
Don't be ridiculous, that's not a thing!\

I was going to debug your existing code so that I could explain exactly why it wasn't working in Harlowe 2.x but it was easier and less time consuming just to fix it instead.

by (190 points)
Yeah that makes sense to just keep movin forward! I think I was stuck trying to figure that out rather than just rewrite it using the new tools in Harlowe 2.x...and now I see how clunky my old code was in comparison O-O

Thank you for putting in the work to fix it! Also thank you for displaying how to use the (for:) loops to print each inventory item...I was trying to add them manually with the (click:) macro in Harlowe 1.x so this is much better :'D
+2 votes
by (63.1k points)

I'll look into this more when I have a chance, but generally speaking, you can't expect to a port code from one major version of anything to the next major version without having to rewrite some stuff. Major versions, at least in proper semantic versioning, imply breaking changes, i.e. not all old code is supported, and some old code does different stuff. 

That doesn't mean you can't change versions, but it does mean that you need to really read up on the changes and you can't expect to just change versions and go without any revisions. There are a number of potential causes for the change in behavior you're seeing, and I don't want to hazard a guess until I can test it out myself for fear of leading you down a rabbit hole. 

I'll load up your code later tonight or tomorrow and have a look if no one else gets to it first. 

by (190 points)
I am not surprised that it broke, I just don't know why it broke and would like input. I read up on the changes to the replace macro (where I suspect the trouble is) and can't figure out any changes to it between 1.2.4 and 2.0.1 that would break it in this way. But I will read more thoroughly on the other changes...thank you for taking a look at it. If I figure something out myself after relooking at the changes I will report back.
0 votes
by (6.2k points)
Really, I wouldn't switch formats mid-way through a project.