Skip to content
NYU ITP Thesis Project Prototype
Branch: master
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
public
views
.env
.gitignore
Procfile
README.md
app.json
index.js
package-lock.json
package.json
test.js

README.md

North Central Positronics

A game built with Twine.

You can play it here. If that link isn't working, try this.

This game is a prototype of my graduate thesis project - more details available here.

Development Documentation

App Setup

Created with node.js/express and automatically deployed to Heroku using the node-js-getting-started guide, example app, and GitHub integration guide.

I also added nodemon to develop with greater ease locally.

If you want to use this implementation of Twine for your own purposes try the following steps:

  1. Clone or download this repository
  2. npm install
  3. Create a firebase database. Add your credentials to your .env file and update the database name in the index.js file
  4. run the game with node index.js
  5. Make changes!

Many more details on these steps and my development process are below

Development Details

Heroku Configuration

For ease of heroku configuration, I also set the heroku.remote key to this app, as per these instructions

I set the app to the custom (sub)domain thesis.calebfergie.com by following this guide. I personally manage the calebfergie.com domain through DreamHost, so I followed these instructions to help complete the process.

For a secure HTTPS connection, I also turned off DreamHost's HTTPS certificate management and used Heroku's built-in automatic SSL configuration instead.

Collecting Responses

The goal of this prototype is to provide an online game and collect users responses to prompts in order to expand the game. Doing this with Twine required a bit of customization, detailed below:

Customizing Twine

I use the Sugarcube 2.2.1 format within Twine for all the helpful functions it provides see Allison's guide for more details.

I added custom javascript to the Twine game, which can be found in the twine-global.js file. This file includes:

  • a minified version of jQuery
  • a function to add data (the passage the user was on and the content of the response) to the DOM through invisible divs with id passagetransfer and responsetransfer:
postrender.collect = function() {
  var fieldval = $("input").val();
  var previousPassage = State.index(State.length - 1).title;

	if(typeof fieldval !== "undefined") {
	   console.log("postrender says the user answered: " + fieldval + "on page " + previousPassage);
     $("#passagetransfer").text(previousPassage);
	    $("#responsetransfer").text(fieldval);
	} else {
	console.log("no data recorded")
};
};

The postrender function fires after a user navigates away from a specific passage, collecting the passage name via the State function/API. This link was useful in figuring out how to use State.

My current workflow is to copy/paste the HTML file generated by Twine into into the index.ejs file, and then manually add the following elements to the file:

<!-- - add to top: -->
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
<script src="/js/cfscript.js"></script>
<link rel="stylesheet" type="text/css" href="/stylesheets/main.css">

<!-- - add to bottom of body: -->
<div id="passagetransfer">no passage</div>
<div id="responsetransfer">no content</div>

These divs are made invisible with CSS. I used the code from this fun little article to do so.

The record function in the cfscripts.js file plucks the values out of the DOM and calls the postToServer function to POST it to the server.

Connecting to Firebase

I followed Google's guide to setup my firebase account with this nodeJS app.

When the app server 'index.js' gets a POST, it uploads the data to Firebase. To keep my private key secure, I added dotnev to the project and store the key information in the hidden '.env' file.

It was also necessary to add .replace(/\\n/g, '\n') to the end of the app initialization as per this post.

For this repository, I have included a fake .env file. Remember to add .env to your .gitignore file before uploading it anywhere!

As a security practice, I also set my database permissions to authorized users only. More details on firebase security can be found here. Here is the code I used for my database rules:

{
  "rules": {
    ".read": "auth != null",
    ".write": "auth != null"
  }
}

Since I am the only authorized user, this means only I can read or write to the database.

POSTing to Firebase

The postToServer function in the cfscripts.js file sends a JSON file to the node server that looks like this:

{passage: passage,
  answer: answer}

The Firebase data hierarchy is structured like so:

user-responses
  |
  sessionID
    |
    UUID
      |
      Passage
        |
        User response
  1. The sessionID is generated every time the app server app server (index.js) starts up, allowing me to separate versions of the game in the DB. This ID is made with the express-session module.
  2. Whenever the game page is loaded, a unique ID (UUID) is generated with the uuid module. This allows me to track/group the responses of a user/playthrough.
  3. Passage and user response information is stored under that, such that if a user plays through multiple times, all of their responses are stored under one UUID record.

Getting this structure to POST correctly was done with a lot of trial and error - but finally, voila responses are being recorded.

As a final step, I added a feature to group my local development testing responses in one userID called "debugging" and a SessionID of the current time.

This is done throught the setIDs function in the index.js file, which checks if the localhost is making a request.

function setIDs(req) {
  var sess = req.session.id;
  //check if the app is being run locally
  var isLocal = (req.connection.localAddress === req.connection.remoteAddress);
  if (isLocal) {
    //set database info to help group and debug
    sess_ref = ref.child("debugging");
    date = new Date();
    user_id = date.getMonth()+1 + "-" + date.getDate() + "-" + date.getFullYear();
  } else {
    //set database info to unique user store
    sess_ref = ref.child(sess);           //add sessionid (sever session, not user) to database and set it as global reference variable
    user_id = uuidV4();                   //create a unique id for the user session
  }
}

Additional resources:

You can’t perform that action at this time.