Drag & Drop Puzzle: Check All Droppables Have Accepted a Draggable and Proceed to Next Passage

Twine Version: 2.3.9
Story Format: Sugarcube 2.31.1

Hello! I’m not too shabby with HTML and CSS, but new to Twine and Javascript.

The Goal

I’d like to make a drag-and-drop puzzle in my Twine game. For context, the game is a class project that’s supposed to teach students about quantum mechanics. The puzzle is an equation (a series of droppables) that the students have to drag and drop the correct terms (a series of draggables) into. Upon dragging the correct terms in, either text appears with a link to the next passage (pictured in concept art), OR are immediately taken to the next passage - whichever way is easier to code.

Concept Art:

  1. Player must drag the terms into the correct places to build the equation.

  2. Once terms are in correct spots, a “Well done!” message appears which links to the next passage.

The Problem

I’ve got most of the code worked out except for the part about checking the answer, if you will. To be more precise, I need JavaScript to “check” that all of the droppables have draggables dropped on them, and if so, proceed to the next passage. I’d ideally have extra “incorrect” terms/draggables that don’t get accepted into any of the droppables (not pictured in concept art) so the answer to the puzzle isn’t as obvious. I don’t think I need JS to actually check the identity of the draggables since each droppable only accepts a particular draggable already - so just “checking” that all droppables have accepted “something” will work for my purposes. I’m not quite sure how to go about that or if it’s possible.

Possible Solutions/Research I’ve Done So Far
There was a post with a similar goal here on Intfiction, but it doesn’t look like it went anywhere. There was also a post on Reddit, but their puzzle was concerned with collisions. I personally couldn’t extrapolate anything from it. Perhaps someone with more experience can.

I haven’t had any luck finding anything else on Twine/Reddit forums, but I’ve found this (something about a checkFinish() function) and this post (something about utilizing classes) from a Hype forum site that help - I think - but I don’t have enough experience with JavaScript to interpret the answers much less implement them. Any insight from a more experienced Java Script coder as how to move forward would be very appreciated!

The Set Up

To start, I loaded jQuery UI into Twine by following HiEv’s lovely tutorial on integrating jQuery UI with Twine to create draggable/droppable divs. There was something wrong with loading jQuery UI in this way (it kept spitting an error at me), so instead of the JavaScript HiEv uses, I did what greyelf suggested in this forum; I copied/pasted text from the jquery-ui.min.js file into my Story JavaScript and text from the jquery-ui.min.css file into my Story StyleSheet. HiEv’s scripts from within the HTML work just fine!

I also downloaded Bootstrap 4.5.3 and copied/pasted the text from the bootstrap-grid.css file into my Story Stylesheet. (I’m more comfortable with this notation!)

The file is below for you to import into Twine if you’d like.
.zip file: draggable_puzzle_test.zip (173.1 KB)

The Puzzle Code So Far

Everything works beautifully so far using this code in my puzzle’s passage:

<div class="row">
	<div id="droppable-1" class="droppable ui-widget-header col-md-3">
  Put draggable #1 here.
	</div>
	<div id="equal" class="col-md-1">
 		=
	</div>
	<div id="droppable-2" class="droppable ui-widget-header col-md-3">
  Put draggable #2 here.
	</div>
	<div id="droppable-3" class="droppable ui-widget-header col-md-3">
  Put draggable #3 here.
	</div>
	<div id="droppable-4" class="droppable ui-widget-header col-md-2">
  Put draggable #4 here.
	</div>
</div>
<div id="draggable-1" class="draggable ui-widget-content" style="width: 150px; height: 150px; padding: 0.5em;">
  Drag me around #1.
</div>
<div id="draggable-2" class="draggable ui-widget-content" style="width: 150px; height: 150px; padding: 0.5em;">
  Drag me around #2.
</div>
<div id="draggable-3" class="draggable ui-widget-content" style="width: 150px; height: 150px; padding: 0.5em;">
  Drag me around #3.
</div>
<div id="draggable-4" class="draggable ui-widget-content" style="width: 150px; height: 150px; padding: 0.5em;">
  Drag me around #4.
</div>


  <script>
  $( function() {
    $( ".draggable" ).draggable({
		containment: "document",
		cursor: "move",
		cursorAt: { top: 75, left: 75 },
		snap: ".droppable"
	});
	
    $( "#droppable-1" ).droppable({
      accept: "#draggable-1",
      classes: {
        "ui-droppable-hover": "ui-state-hover"
      },
      drop: function( event, ui ) {
        $( this )
          .addClass( "ui-state-highlight" )
          .html( "Dropped!" )
      }
	 });
	  
	 $( "#droppable-2" ).droppable({
      accept: "#draggable-2",
      classes: {
        "ui-droppable-hover": "ui-state-hover"
      },
      drop: function( event, ui ) {
        $( this )
          .addClass( "ui-state-highlight" )
          .html( "Dropped!" )
	 }
    });
	
	$( "#droppable-3" ).droppable({
      accept: "#draggable-3",
      classes: {
        "ui-droppable-hover": "ui-state-hover"
      },
      drop: function( event, ui ) {
        $( this )
          .addClass( "ui-state-highlight" )
          .html( "Dropped!" )
	 }
    });
	
	$( "#droppable-4" ).droppable({
      accept: "#draggable-4",
      classes: {
        "ui-droppable-hover": "ui-state-hover"
      },
      drop: function( event, ui ) {
        $( this )
          .addClass( "ui-state-highlight" )
          .html( "Dropped!" );
	 }
    });
	
  } );
  </script>
  
<<script>>
$(document).one(":passagerender", function(event) {
	// Passage is about to be displayed.
	$(event.content).find(".draggable").draggable();  // Make "draggable" elements draggable.
	$(event.content).find(".droppable").droppable();  // Make "droppable" elements droppable.
});
<</script>>

When put into my Twine passage, it looks like a very bare-bone skeleton of my concept art (demo below.)

puzzle_demo

TL;DR:

How do I get Javascript to “check for the correct answer” (check all of the droppables have draggables dropped on them) and then display a link to the next passage (or immediately take the player to the next passage, whichever is easier to code)? I feel like someone must have tried accomplishing something similar before.

Thank you very much in advance.

1 Like

Hey there,

The fast, crude answer is basically to keep a counter every drop and do something when that counter reaches a specific value.

Try adding this to the start of your script tag

var counter = 0;
function OnDropComplete() {
    // Increment our counter
    counter++;

    if(counter === 4) {
        // TODO DO COMPLETE CODE HERE
    }
}

Then, in each drop function, add a call to this function at the end. Eg:

drop: function( event, ui ) {
        $( this )
          .addClass( "ui-state-highlight" )
          .html( "Dropped!" );
           OnDropComplete();
	 }

Now, the final question is: what to do in that TODO DO COMPLETE CODE HERE section in OnDropComplete.

You could add an empty <span> somewhere in the passage with an id. Example: <span id='result'></span>.

Then replace TODO DO COMPLETE CODE HERE with

$("#result").wiki("You have finished! Click [[here->next passage]] to continue!");

wiki is a built-in function in SugarCube that basically lets you set the contents of an element to some SugarCube code. This way you can throw things like passage links in there.

I haven’t downloaded your test project and tested this yet but if you can’t get it to work maybe I can diagnose it too.

2 Likes

Ah, thank you so much! So it doesn’t like the counter for reasons I’m not sure of. I’ve been playing around with it for a bit, but to no avail. I put this code in the passage:

<div class="row">
	<span id="result"></span>
	<div id="droppable-1" class="droppable ui-widget-header col-md-3">
  Put draggable #1 here.
	</div>
	<div id="equal" class="col-md-1">
 		=
	</div>
	<div id="droppable-2" class="droppable ui-widget-header col-md-3">
  Put draggable #2 here.
	</div>
	<div id="droppable-3" class="droppable ui-widget-header col-md-3">
  Put draggable #3 here.
	</div>
	<div id="droppable-4" class="droppable ui-widget-header col-md-2">
  Put draggable #4 here.
	</div>
</div>
<div id="draggable-1" class="draggable ui-widget-content" style="width: 150px; height: 150px; padding: 0.5em;">
  Drag me around #1.
</div>
<div id="draggable-2" class="draggable ui-widget-content" style="width: 150px; height: 150px; padding: 0.5em;">
  Drag me around #2.
</div>
<div id="draggable-3" class="draggable ui-widget-content" style="width: 150px; height: 150px; padding: 0.5em;">
  Drag me around #3.
</div>
<div id="draggable-4" class="draggable ui-widget-content" style="width: 50px; height: 50px; padding: 0.5em;">
  Drag me around #4.
</div>


<script>
const counter = 0;
function OnDropComplete() {
     // Increment our counter
    counter++;

    if(counter === 4) {
        $("#result").wiki("You have finished! Click [[here->complete]] to continue!");
	}
}
  $( function() {
    $( ".draggable" ).draggable({
		containment: "document",
		cursor: "move",
		cursorAt: { top: 75, left: 75 },
		snap: ".droppable"
	});
	
    $( "#droppable-1" ).droppable({
      accept: "#draggable-1",
      classes: {
        "ui-droppable-hover": "ui-state-hover"
      },
      drop: function( event, ui ) {
        $( this )
          .addClass( "ui-state-highlight" )
          .html( "Dropped!" )
		  OnDropComplete();
      }
	 });
	  
	 $( "#droppable-2" ).droppable({
      accept: "#draggable-2",
      classes: {
        "ui-droppable-hover": "ui-state-hover"
      },
      drop: function( event, ui ) {
        $( this )
          .addClass( "ui-state-highlight" )
          .html( "Dropped!" )
		  OnDropComplete();
	 }
    });
	
	$( "#droppable-3" ).droppable({
      accept: "#draggable-3",
      classes: {
        "ui-droppable-hover": "ui-state-hover"
      },
      drop: function( event, ui ) {
        $( this )
          .addClass( "ui-state-highlight" )
          .html( "Dropped!" )
		  OnDropComplete();
	 }
    });
	
	$( "#droppable-4" ).droppable({
      accept: "#draggable-4",
      classes: {
        "ui-droppable-hover": "ui-state-hover"
      },
      drop: function( event, ui ) {
        $( this )
          .addClass( "ui-state-highlight" )
          .html( "Dropped!" );
		  OnDropComplete();
	 }
    });
	
  } );
  </script>
  
<<script>>
$(document).one(":passagerender", function(event) {
	// Passage is about to be displayed.
	$(event.content).find(".draggable").draggable();  // Make "draggable" elements draggable.
	$(event.content).find(".droppable").droppable();  // Make "droppable" elements droppable.
});
<</script>>

I get this error:

I imagine my lack of Javascript experience is showing. :slight_smile:

1 Like

It’s not you, it’s me. I accidentally put const when I should have put var. I fixed it in my earlier comment.

1 Like

That works! You are amazing!! Thank you so much!!

It ain’t pretty, but it works! Here’s the code for anyone wanting the code all together for their own puzzles.

Edit: I discovered a glitch. See below:

glitch

If you don’t lock the tiles in place once they’re in the correct droppable, you can “re-drag” the tile on the droppable to trigger the counter. I fixed this by disabling the “draggable” property of the tiles once they’ve been accepted. This is in the code below. I’ll go through later to organize what can go into my Twine’s Javascript Story and such. :’)
Edit 2: Lol, just kidding, it’s still broken. I’m working on learning enough Javascript to fix it at the moment.
Edit 3: Fixed!
Edit 4: I found another glitch. When a droppable accepts multiple draggables, the droppable will deactivate both draggables. (So if you accidentally drop the extra draggable on it, you won’t be able to complete the puzzle.) So I added a line to destroy the droppable’s ability to accept things once it’s already accepted something. Code below.

<div class="row">
	
	<div id="droppable-1" class="droppable ui-widget-header col-md-3">
  <br><br><br><br><br>
	</div>
	<div id="equal" class="col-md-1">
 		=
	</div>
	<div id="droppable-2" class="droppable ui-widget-header col-md-3">
  <br><br><br><br><br><br>
	</div>
	<div id="droppable-3" class="droppable ui-widget-header col-md-3">
  <br><br><br><br><br>
	</div>
	<div id="droppable-4" class="droppable ui-widget-header col-md-2">
  <br><br><br><br><br>
	</div>
</div>
<span id="result"></span>
<div id="draggable-1" class="draggable ui-widget-content" style="width: 150px; height: 150px; padding: 0.5em;">
  ψ(ϕ)
</div>
<div id="draggable-2" class="draggable ui-widget-content" style="width: 150px; height: 150px; padding: 0.5em;">
  (-1)
</div>
<div id="draggable-3" class="draggable ui-widget-content" style="width: 150px; height: 150px; padding: 0.5em;">
  ψ(ϕ)
</div>
<div id="draggable-4" class="draggable ui-widget-content" style="width: 100px; height: 100px; padding: 0.5em;">
  4ml
</div>
<div id="draggable-5" class="draggable ui-widget-content" style="width: 100px; height: 100px; padding: 0.5em;">
  2ml
</div>
<div id="draggable-6" class="draggable ui-widget-content" style="width: 150px; height: 150px; padding: 0.5em;">
  (1)
</div>


<script>

var counter = 0;
function OnDropComplete() {
     // Increment our counter
    counter++;

    if(counter === 4) {
        $("#result").wiki("You have finished! Click [[here->complete]] to continue!");
	}
}

function dropAnimate(event, ui) {

    console.log( $(ui.draggable).attr('id'));

    var $this = $(this);

    var width = $this.width();
    var height = $this.height();
    var cntrLeft = (width / 2) - (ui.draggable.width() / 2);
    var cntrTop = (height / 2) - (ui.draggable.height() / 2);

    ui.draggable.position({
      my: "center",
      at: "center",
      of: $this,
      using: function(pos) {
        $(this).animate(pos, "slow", "linear");
      }
    });
	
	 ui.draggable.draggable({disabled: true});
	}
	
	
	
  $( function() {
    $( ".draggable" ).draggable({
		containment: "document",
		cursor: "move",
		cursorAt: { top: 75, left: 75 },
		snap: ".droppable"
	});
	
    $( "#droppable-1" ).droppable({
      accept: "#draggable-1, #draggable-3",
      classes: {
        "ui-droppable-hover": "ui-state-hover"
      },
      drop: function( event, ui ) {
        $( this )
          .addClass( "ui-state-highlight" )
		  OnDropComplete();
		  dropAnimate(event, ui);
$(this).droppable("destroy");
      }
	 });
	  
	 $( "#droppable-2" ).droppable({
      accept: "#draggable-2",
      classes: {
        "ui-droppable-hover": "ui-state-hover"
      },
      drop: function( event, ui ) {
        $( this )
          .addClass( "ui-state-highlight" )
		  OnDropComplete();
                  dropAnimate(event, ui);
$(this).droppable("destroy");
	 }
    });
	
	$( "#droppable-3" ).droppable({
      accept: "#draggable-3, #draggable-1",
      classes: {
        "ui-droppable-hover": "ui-state-hover"
      },
      drop: function( event, ui ) {
        $( this )
          .addClass( "ui-state-highlight" )
		  OnDropComplete();
                  dropAnimate(event, ui);
$(this).droppable("destroy");
	 }
    });
	
	$( "#droppable-4" ).droppable({
      accept: "#draggable-4",
      classes: {
        "ui-droppable-hover": "ui-state-hover"
      },
      drop: function( event, ui ) {
        $( this )
          .addClass( "ui-state-highlight" )
		  OnDropComplete();
                  dropAnimate(event, ui);
$(this).droppable("destroy");
	 }
    });
	
  } );
  </script>
  
<<script>>
$(document).one(":passagerender", function(event) {
	// Passage is about to be displayed.
	$(event.content).find(".draggable").draggable();  // Make "draggable" elements draggable.
	$(event.content).find(".droppable").droppable();  // Make "droppable" elements droppable.
});
<</script>>
1 Like

@brwarner May I trouble you for your Javascript expertise once more?

The puzzle works beautifully, and now I’m just trying to organize my code in Twine. I’d like to move all of my Javascript (or as much as I can) into the Javascript Story. I copied all of this to the bottom of my Javascript Story:

Javascript Story

/* Custom Java Script for Drag & Drop Puzzle */

var counter = 0;
function OnDropComplete() {
     // Increment our counter
    counter++;

    if(counter === 4) {
        $("#result").wiki("You have finished! Click [[here->complete]] to continue!");
	}
}

function dropAnimate(event, ui) {

    console.log( $(ui.draggable).attr('id'));

    var $this = $(this);

    var width = $this.width();
    var height = $this.height();
    var cntrLeft = (width / 2) - (ui.draggable.width() / 2);
    var cntrTop = (height / 2) - (ui.draggable.height() / 2);

    ui.draggable.position({
      my: "center",
      at: "center",
      of: $this,
      using: function(pos) {
        $(this).animate(pos, "slow", "linear");
      }
    });
	
	 ui.draggable.draggable({disabled: true});
	}
	
	
  $( function() {
    $( ".draggable" ).draggable({
		containment: "document",
		cursor: "move",
		cursorAt: { top: 75, left: 75 },
	});
	
    $( "#droppable-1" ).droppable({
      accept: "#draggable-1, #draggable-3",
      classes: {
        "ui-droppable-hover": "ui-state-hover"
      },
      drop: function( event, ui ) {
        $( this )
          .addClass( "ui-state-highlight" )
		  OnDropComplete();
		  dropAnimate(event, ui);
      }
	 });
	  
	 $( "#droppable-2" ).droppable({
      accept: "#draggable-2",
      classes: {
        "ui-droppable-hover": "ui-state-hover"
      },
      drop: function( event, ui ) {
        $( this )
          .addClass( "ui-state-highlight" )
		  OnDropComplete();
		  dropAnimate(event, ui);
	 }
    });
	
	$( "#droppable-3" ).droppable({
      accept: "#draggable-3, #draggable-1",
      classes: {
        "ui-droppable-hover": "ui-state-hover"
      },
      drop: function( event, ui ) {
        $( this )
          .addClass( "ui-state-highlight" )
		  OnDropComplete();
		  dropAnimate(event, ui);
	 }
    });
	
	$( "#droppable-4" ).droppable({
      accept: "#draggable-4",
      classes: {
        "ui-droppable-hover": "ui-state-hover"
      },
      drop: function( event, ui ) {
        $( this )
          .addClass( "ui-state-highlight" )
		  OnDropComplete();
		  dropAnimate(event, ui);
	 }
    });
	
  } );

So my HTML passage looks like…

Twine Passage/HTML

<div class="row">
	
	<div id="droppable-1" class="droppable ui-widget-header col-md-3">
  <br><br><br><br><br>
	</div>
	<div id="equal" class="col-md-1">
 		=
	</div>
	<div id="droppable-2" class="droppable ui-widget-header col-md-3">
  <br><br><br><br><br><br>
	</div>
	<div id="droppable-3" class="droppable ui-widget-header col-md-3">
  <br><br><br><br><br>
	</div>
	<div id="droppable-4" class="droppable ui-widget-header col-md-2">
  <br><br><br><br><br>
	</div>
</div>
<span id="result"></span>
<div id="draggable-1" class="draggable ui-widget-content" style="width: 150px; height: 150px; padding: 0.5em;">
  ψ(ϕ)
</div>
<div id="draggable-2" class="draggable ui-widget-content" style="width: 150px; height: 150px; padding: 0.5em;">
  (-1)
</div>
<div id="draggable-3" class="draggable ui-widget-content" style="width: 150px; height: 150px; padding: 0.5em;">
  ψ(ϕ)
</div>
<div id="draggable-4" class="draggable ui-widget-content" style="width: 100px; height: 100px; padding: 0.5em;">
  4ml
</div>
<div id="draggable-5" class="draggable ui-widget-content" style="width: 100px; height: 100px; padding: 0.5em;">
  2ml
</div>
<div id="draggable-6" class="draggable ui-widget-content" style="width: 150px; height: 150px; padding: 0.5em;">
  (1)
</div>
  
<<script>>
$(document).one(":passagerender", function(event) {
	// Passage is about to be displayed.
	$(event.content).find(".draggable").draggable();  // Make "draggable" elements draggable.
	$(event.content).find(".droppable").droppable();  // Make "droppable" elements droppable.
	$(event.content).find(".OnDropComplete").OnDropComplete();  // Activate the counter to check answer of puzzle.
	$(event.content).find(".dropAnimate").dropAnimate(event, ui);  // Make "draggable" elements lock after being dropped.
});
<</script>>

As you can see there at the bottom, I’ve tried calling my “dropAnimate” and “OnDropComplete” functions the way HiEv called the “draggable” and “droppable” functions, but it doesn’t like that very much and the puzzle breaks when I run it.

Error Message:

I’ve been sifting through Javascript forums to figure out what Twine wants from me to call the rest of the Javascript functions to no avail yet. Do you happen to know off the top of your head what’s going on?

The dropAnimate and OnDropComplete should be called when something is actually dragged and dropped, but everything in that .one(':passagerender') function gets called when the page loads. What you want to do when the page loads is run the anonymous function that hooks up all the dragging-and-dropping stuff: the bit that starts with

$( function() {
    $( ".draggable" ).draggable({
		containment: "document",
...

That part probably needs to be on the page, since presumably different pages have different puzzles? Right? It says droppable-3 accepts draggable-3 or draggable-1, etc.

You could probably have a function that lets you specify that stuff in a more compact form, but for now…I’d just put it in the page.

1 Like

Thank you so much for the speedy reply!

Ah, I see! So would it just be a matter of naming that function and calling it in those script tags if I decide to keep all of the JS to the Javascript Story?

Yes, different pages will have different puzzles. However, this puzzle is as complicated as it gets for my game and there will only be a total of four different puzzles in the whole game. For this quantum mechanics stuff, there’s a lot of redundant terms, so if two tiles have the same text (like draggable 1 and 3 both have “Ψ(ϕ)” on them) I wanted the droppable to accept both to avoid confusing the player.

As for engineering the different puzzles, I was originally thinking I could go back in and just give different puzzles different classes in the Javascript? For example, a draggable in the first puzzle would have a “puzzle-1” class in addition to its “draggable-1” class, and a draggable in puzzle 2 would have a “puzzle-2” class with its “draggable-1” class. So I can still “talk” to each puzzle differently, but keep my draggable classes somewhat organized. I think this would work for me since I plan on giving all of my draggables the same dimensions, colors, etc. and it’s just a matter of telling the droppables what to accept.

I suppose if that gets too confusing, I can just give a number to every single draggable even if that means having “100” draggable classes, haha. But in that case, to your point, it may be better to just leave the Javascript on the page.

Yeah, the more I think about your words, the more I’m leaning toward this as well.

Yeah…I’m trying to make this work, dropped the code into your original example, but not sure I have everything? Do you have a current version of your thing?

You could just take the insides of that function and put that code straight into the function that’s getting called on :passagerender

But it would be cool to separate that out: a lot of the code is the same for each droppable. I think you can do something like this:

Story JavaScript:

/* Custom Java Script for Drag & Drop Puzzle */

var counter = 0;
function OnDropComplete() {
     // Increment our counter
    counter++;

    if(counter === 4) {
        $("#result").wiki("You have finished! Click [[here->complete]] to continue!");
	}
}

function dropAnimate(event, ui) {

    console.log( $(ui.draggable).attr('id'));

    var $this = $(this);

    var width = $this.width();
    var height = $this.height();
    var cntrLeft = (width / 2) - (ui.draggable.width() / 2);
    var cntrTop = (height / 2) - (ui.draggable.height() / 2);

    ui.draggable.position({
      my: "center",
      at: "center",
      of: $this,
      using: function(pos) {
        $(this).animate(pos, "slow", "linear");
      }
    });
	
	 ui.draggable.draggable({disabled: true});
	}
	
	
function setupDragDrop(droppables) {
	$(".draggable").draggable({
		containment: "document",
		cursor: "move",
		cursorAt: { top: 75, left: 75 },
	})

	for(const drop in droppables) {
		$(drop).droppable({
			accept: droppables[drop],
      classes: { "ui-droppable-hover": "ui-state-hover" },
			drop: function( event, ui ) {
				$( this )
					.addClass( "ui-state-highlight" )
				OnDropComplete();
				dropAnimate(event, ui);
			}
		})
	}
}

Passage:


<div class="row">
	
	<div id="droppable-1" class="droppable ui-widget-header col-md-3">
  <br><br><br><br><br>
	</div>
	<div id="equal" class="col-md-1">
 		=
	</div>
	<div id="droppable-2" class="droppable ui-widget-header col-md-3">
  <br><br><br><br><br><br>
	</div>
	<div id="droppable-3" class="droppable ui-widget-header col-md-3">
  <br><br><br><br><br>
	</div>
	<div id="droppable-4" class="droppable ui-widget-header col-md-2">
  <br><br><br><br><br>
	</div>
</div>
<span id="result"></span>
<div id="draggable-1" class="draggable ui-widget-content" style="width: 150px; height: 150px; padding: 0.5em;">
  ψ(ϕ)
</div>
<div id="draggable-2" class="draggable ui-widget-content" style="width: 150px; height: 150px; padding: 0.5em;">
  (-1)
</div>
<div id="draggable-3" class="draggable ui-widget-content" style="width: 150px; height: 150px; padding: 0.5em;">
  ψ(ϕ)
</div>
<div id="draggable-4" class="draggable ui-widget-content" style="width: 100px; height: 100px; padding: 0.5em;">
  4ml
</div>
<div id="draggable-5" class="draggable ui-widget-content" style="width: 100px; height: 100px; padding: 0.5em;">
  2ml
</div>
<div id="draggable-6" class="draggable ui-widget-content" style="width: 150px; height: 150px; padding: 0.5em;">
  (1)
</div>
  
<<script>>
$(document).one(":passagerender", function(event) {
	// Passage is about to be displayed.
	$(event.content).find(".draggable").draggable();  // Make "draggable" elements draggable.
	$(event.content).find(".droppable").droppable();  // Make "droppable" elements droppable.
	setupDragDrop({
		"#droppable-1": "#draggable-1, #draggable-3",
		"#droppable-2": "#draggable-2",
		"#droppable-3": "draggable-3, #draggable-1",
		"#droppable-4": "draggable-4"
	})
})
<</script>>

Edit: hrm. You might want :passagedisplay instead of passagerender? I’m not sure about some of this.

2 Likes

Either :passagerender or :passagedisplay should work. The only difference is if the elements have actually been added to the DOM yet, but none of what you’re doing requires that.

Also, if you’re using this with multiple passages, you’ll want to reset counter to 0 each time. You’ll also need a way of adjusting the check depending on how many slots there are in each passage. Maybe:

var counter = 0;
var total = 0;
function OnDropComplete() {
     // Increment our counter
    counter++;

    if(counter === total) {
        $("#result").wiki("You have finished! Click [[here->complete]] to continue!");
	}
}

function StartupPuzzle(numSlots) {
    counter = 0;
    total = numSlots;
}

Then at the beginning of your <<script>> in each passage:

StartupPuzzle(4)

Where you put whatever number of slots there are in the place of 4.

You’d probably want to revisit all this with a less dirty implementation if you were planning on making a huge game with a large variety of puzzles but this should all work.

Yeah, here you are! I added a second passage for another puzzle. It was actually pretty easy to set it all up with keeping the JS code on the passage. Maybe I should just run with it and tell whoever picks up my project “Don’t panic!” when they see my puzzle passages, haha!
Draggable_Puzzle_Test_v2.zip (173.7 KB)

This is brilliant! I’ll give it a shot and let you know asap!

@brwarner @JoshGrams I cannot thank you both enough for your insight and your help. I appreciate it so much. :pray:

1 Like

Hey, no problem. I am using this to procrastinate on things I don’t want to work on so we’re both winning :stuck_out_tongue:

3 Likes

Josh_Draggable_Test.zip (159.8 KB)

OK, here’s a version that moves most of the code to the Story JavaScript: in your passage you just have to tell it which droppables accept which draggables, and where to link to once the puzzle is solved.

2 Likes

HAH, I’m using this to procrastinate on studying for finals, so we’re just a bunch of procrastinators. The game is due on Monday though, so I have my excuse to work on it in the meantime. :wink:

Wow!!! You did that with such fewer lines than I would’ve ever thought to do. I’m excited to start playing around with this for the other puzzles. I can’t express my gratitude enough! Thank you, thank you, thank you! :smile:

2 Likes