Working Draft

Indra’s Net ƒ

The contents and significance of the Indra’s Net HyperCard stack is discussed in the corresponding expository chapter for this notebook. Here, we get on with exploring and reengineering selected dynamic pieces within the collection.

The focal hologogramic transformation within Indra’s Net was the acrostic, which, at this time, I categorized in a number of ways. The basic form was a “strict abecedarian or [head-]acrostic,” the description of which is given in the corresponding exposition. This hologogram will be outline here along with the “free abecedarian or internal acrostic,” which was known to other practitioners as a mesostic. ‘Under It All’ implemented the strict form and ‘Under It All II’ displayed the free form, then considered by me as an advance in terms of poetics. The supply text for both pieces is the same:

We this text into a JavaScript string literal. In HyperCard, this text retained line breaks that are also respected here, in the JavaScript:

const supplyTextString = supplyText.innerText
display(supplyTextString);

The following script in HyperTalk – defFrAcros for ‘define free acrostic’ – in fact allowed both strict and free ‘definitions’ to be created for words being transformed by the acrostic, depending on whether the string "head" (rather than anything else) was passed to the acrosType parameter of the function.

on defFrAcros acrosType
  put item 1 of fld label into textName
  put 1 into xword
  put "abcdefghijklmnopqrstuvwxyz" into alpha
  put word xword of field 1 into testw
  set numberFormat to "0000"
  put the number of words in fld text into totWords
  lock screen
  repeat with i = 1 to totWords
    put word i of fld text into testw
    put i into wordNo
    if acrosType is "head" then
      put 1 into defAllChars
    else
      put the length of testw into defAllChars
    end if
    repeat with c = 1 to defAllChars
      put char c of testw into testc
      if alpha contains testc then
        push card
        go cd textName & ",def," & testc
        if the result is not empty then
          go card "blank,def"
          doMenu "New Card"
          put textName & ",def," & testc into fld label
          set the cantDelete of this cd to false
        end if
        -- next condition would not have regard to frequencies
        -- if testw is not in field defStuff then
        put testw & " " after field defStuff
        put wordNo & " " after field defNos
        -- end if
        pop card
      end if
    end repeat
    put wordNo & "/" & totWords
  end repeat
  unlock screen
end defFrAcros

This script works through all the words in the supply text. For each word, if acrosType is "head" it puts 1 – for the initial character of the word – into defAllChars, the number of characters in the word that will be processed; otherwise it processes all the characters in the word and defAllChars will contain the number of characters in the word. For each defAllChars of the word, the script checks to see if there exists a corresponding card – named for one of the twenty-six letters of the lowercase alphabet. If there isn’t such a card it creates one and adds an instance of the word to a field on the card; if there is such a card it simply adds a word instance to the field. This creates what could be otherwise represented by a JavaScript Map of word occurences indexed by letters of the alphabet. If the type of acrostic is head-acrostic, the a card, for example, will contain a list of all the word occurrences beginning with a; if not, it will contain a list of all word occurrences that contain a letter a.

function acrosticsMap(supplyText, acrosticType) {
  let acrostics = new Map(); // create a Map
  let words = supplyText.split(/\s/); // split string literal supply text into words
  for (let i = 0; i < words.length; i++) {
    // iterate through the words
    // get number of characters to process - only the first one for head-acrostic
    let charactersToProcess = "head" === acrosticType ? 1 : words[i].length;
    for (let j = 0; j < charactersToProcess; j++) {
      // iterate through the characters
      let character = words[i][j]; // get the current character
      if (acrostics.has(character)) {
        // if we already have an entry for the character
        acrostics.get(character).push(words[i]); // add the word-occurrence to the array for the character
      } else {
        // we don't yet have an entry for the character, so
        acrostics.set(character, [words[i]]); // create an entry with an array containing the word-occurrence
      } // end of innner j loop
    }
  }
  return acrostics;
}

And then, we can evoke it in two forms, one for head-acrostics and one for a free-acrostic or mesostic:

const textTransMapHead = acrosticsMap(supplyTextString, "head");
display(textTransMapHead);
const textTransMapFree = acrosticsMap(supplyTextString);
display(textTransMapFree);

Now we code a ‘player’ that performs the letteral hologogram stuctures implicit in both the head-acrostic and free or mesostic Maps. In the HyperCard version, after clicking the relevant item of the stack’s table of contents card, you were sent to a player card that was appropriate for your choice. A text was generated and displayed by a HyperTalk script. In the head-acrostic version, for each letter of the supply text, random words from the same text that begin with the letter are displayed in succession. In the mesostic version, random words that include each letter are displayed.

First, a player function in Javascript that does the same thing.

async function acrosticPlayer (supplyText, supplyTextMap, displayId) {
  let words = supplyText.split(/\s/); // put the supply text's words into an array
  let display = document.getElementById(displayId); // this is our display area
  // get a reference to the element where the word currently generating the acrostic is displayed:
  let displayMonitor = document.getElementById(displayId + "Mntr");
  for (let i = 0; i < words.length; i++) {
    // this loops for the number of words in the supply text
    // and then allows the function to return (as the original did)
    let currentWord = words[i];
    let screenWords = [];
    let j; // we need j outside the loop, so declare it here
    for (j = 0; j < currentWord.length; j++) {
      let character = currentWord[j];
      // add word sastifying the acrostic rule to the screenWords that will be displayed:
      if (supplyTextMap.has(character)) {
        let possibleWords = supplyTextMap.get(character);
        screenWords.push(possibleWords[randomInteger(possibleWords.length)]);
      }
    }
    fadeInByWord([currentWord], displayMonitor, 800); // fade in the supply text word in lower register
    fadeInByWord(screenWords, display, 800); // fade in the words of the acrostic, one by one
    // time is needed to complete this animation
    await Promises.delay(j * 800 + 800);
    // gracefully fade out the acrostic words
    display.style.color = "ivory";
    displayMonitor.style.color = "ivory";
    // time is needed for the color transition
    await Promises.delay(3000);
    // clear and reset the text color of the display area
    display.innerHTML = "";
    display.style.color = "black";
    displayMonitor.innerHTML = "";
    displayMonitor.style.color = "black";
  }
  return "The acrostic player has finished playing. Refresh page to restart.";
}

Here is a display area for the head-acrostic version:

And code that evokes the player for this display:

acrosticPlayer(supplyTextString, textTransMapHead, "uiaHead");

And here is another display for the mesostic version:

With a corresponding invocation:

acrosticPlayer(supplyTextString, textTransMapFree, "uiaMesostic")

The playText HyperTalk script from the original stack is given below for comparison. Details of how the display was animated, however, will be found in the scripts attached to specific HyperCards where specific contents items were actually played. The transWord and showWord scripts of these cards did the work.

Lastly, in these notes, we will explore the implementation of what I then called ‘collocational constraints’ as applied to the mesostic form of ‘Under It All II.’ These constraints were implemented as a simple bigram Markov model, and such models will be discussed in more detail in Collocations ƒ. The reengineered implementation is placed here, to allow readers to compared the juxtapose versions: the generated text-to-be-read of the constrained version with that of the earlier unconstrained version (above). Also, in a further development that was introduced by Moods & Conjuctions, those characters transformed in the mesostic form are highlighted in bold as the text is generated. The HyperTalk scripts that realized these later features are also quoted for comparison with earlier scripts, and with their JavaScript reengineering.

So, first, a new display area where we will animate ‘Under It All II’ as a mesostic with collocational constraints:

We will need to make a Map of word occurrences in the supply text. These are explained in Collocations ƒ from which we will also import the function that constructs such a Map, invoking it immediately following the import.

import { occurrencesMap } from "observable:@shadoof/inapm2_collocations_f"
const uiaOccurrences = occurrencesMap(supplyTextString);
display(uiaOccurrences);

And we’ll need a new player function for this collocationally constrained version, one to which we can pass our uiaOccurrences:

async function acroCollocPlayer (
  supplyText,
  supplyTextMap,
  occurrencesMap,
  displayId
) {
  let words = supplyText.split(/\s/); // put the supply texts words into an array
  let display = document.getElementById(displayId); // this is our display area
  // get a reference to the element where the word currently generating the acrostic is displayed:
  let displayMonitor = document.getElementById(displayId + "Mntr");
  let previousScreenWord = words[words.length - 1]; // initially put last word into previousScreenWord
  for (let i = 0; i < words.length; i++) {
    // this loops for the number of words in the supply text
    // and then allows the function to return (as the original did)
    let currentWord = words[i];
    let screenWords = [];
    let j; // we need j outside the loop, so declare it here
    for (j = 0; j < currentWord.length; j++) {
      let character = currentWord[j];
      // we are going to try and find an acrostic-satisfying collocation
      let foundColloc = false;
      // add a word sastifying the acrostic rule to the display
      if (supplyTextMap.has(character)) {
        let possibleWords = supplyTextMap.get(character);
        // if any of these possible words collocates with the previousWord
        // then pick it, putting true into foundColloc
        let currentScreenWord;
        for (currentScreenWord of possibleWords) {
          let occs;
          try {
            occs = occurrencesMap.get(currentScreenWord);
          } catch (e) {
            console.log("Word not found in occurrences Map."); // should not happen
          }
          // looping through the occurrences of current occ and previous prevOcc
          for (const occ of occs) {
            for (const prevOcc of occurrencesMap.get(previousScreenWord)) {
              // if the occurrence numbers differ by 1, this is a collocation
              if (occ - prevOcc == 1) {
                foundColloc = true;
                // console.log(
                //   "found:",
                //   `${previousScreenWord} ${currentScreenWord}`
                // ); // DEBUG
                break; // from prevOccs
              }
            }
            if (foundColloc) break; // from occs
          }
          if (foundColloc) break; // from posibleWords
        } // we've looped through possibleWords
        // if no foundColloc pick a random acrostically qualifying word
        if (!foundColloc)
          currentScreenWord =
            possibleWords[randomInteger(possibleWords.length)];
        previousScreenWord = currentScreenWord;
        // embolden the acrostic letter
        let charOffset = currentScreenWord.indexOf(character);
        currentScreenWord =
          currentScreenWord.substring(0, charOffset) +
          "<b>" +
          character +
          "</b>" +
          currentScreenWord.substring(charOffset + 1);
        screenWords.push(currentScreenWord);
      } // end characters loop
    } // end words loop
    fadeInByWord([currentWord], displayMonitor, 800);
    fadeInByWord(screenWords, display, 800); // fade in the screenWords, one by one
    // time is needed to complete this animation
    await Promises.delay(j * 800 + 800);
    // gracefully fade out the screen words
    display.style.color = "ivory";
    displayMonitor.style.color = "ivory";
    // time is needed for the color transition
    await Promises.delay(3000);
    // clear and reset the text color of the display area
    display.innerHTML = "";
    display.style.color = "black";
    displayMonitor.innerHTML = "";
    displayMonitor.style.color = "black";
  }
  return "The acrostic player has finished playing. Refresh page to restart.";
}

Finally, the animation is started by invoking this function on page refresh:

const playMesoColloc = acroCollocPlayer(
    supplyTextString,
    textTransMapFree,
    uiaOccurrences,
    "uiaMesoColloc"
  )

1. placeholder note.

This cell is styling CSS for the display elements.

<p><em>This cell is styling CSS for the display elements.</em></p>
<style>
  .display {
    height: 30vw;
    width: 45vw;
    margin-left: 3vw;
    font-size: 3vw;
    background-color: ivory;
    padding: 1vw;
    overflow: hidden;
    transition: color 2.5s ease-in-out;
    -o-transition: color 2.5s ease-in-out;
    -ms-transition: color 2.5s ease-in-out;
    -moz-transition: color 2.5s ease-in-out;
    -webkit-transition: color 2.5s ease-in-out;
  }
  .display b {
    font-weight: 550;
  }
  .display.monitor {
    height: 3vw;
    font-size: 2.5vw;
  }
</style>

Selected Raw HyperTalk Scripts

The original HyperCard stacks had, typically, five backgrounds:

  • PrelimPlus : Pages or screens readable in a conventional manner by book-like navigation.
  • cardSet : A number of cards shared this background with particular named cards used for one another readable animation.
  • texts : With versions of the supply text formatted for preprocessing.
  • defs : Basically used as a relatively simple data structure. Cards with this background were created and named dynamically during preprocessing, storing information about words in the texts.

from the Stack script of Indra’s Net

on playText
  global ¬
  textname,¬
  wherePlay,¬
  numOWords,¬
  newWord, ¬
  i, ¬
  showBase, ¬
  trans, ¬
  playing, ¬
  interrupt,veFactor,ve
  put true into playing
  set cursor to none
  hide menuBar
  put the number of words in fld text of cd textName & ",text" ¬
  into numOWords
  put empty into cd fld baseFld of cd wherePlay
  hide cd fld baseFld of cd wherePlay
  put empty into cd fld playFld of cd wherePlay
  go cd wherePlay
  -- put 0 into i
  put false into interrupt
  repeat until i is numOWords
    add 1 to i
    if the mouse is down and the shiftKey is down then exit repeat
    put word i of fld text of cd textName & ",text" ¬
    into newWord
    if showBase then
      put newWord into cd fld baseFld
    end if
    send transWord to cd
    if interrupt then
      exit repeat
    end if
    if showBase then
      lock screen
      hide cd fld baseFld
      do "unlock screen with" && ve && veFactor
    end if
  end repeat
  show cd fld baseFld of cd wherePlay
end playText

on seekWord xWord
  global ¬
  textname,¬
  wherePlay,¬
  numOWords,¬
  newWord, ¬
  i, ¬
  showBase, ¬
  trans, ¬
  playing, ¬
  interrupt
  put xWord into trans
  put false into wordFound
  repeat until wordFound
    show cd fld baseFld
    put word i of fld text of cd textName & ",text" into newWord
    put newWord into cd fld baseFld
    if newWord is trans then
      put true into wordFound
      subtract 1 from i
    else
      add 1 to i
      if i > numOWords then put 1 into i
    end if
  end repeat
  put false into interrupt
  playText
end seekWord

from the script of the Card acrostic1 in Indra’s Net

on transWord
  global ¬
  textname,¬
  wherePlay,¬
  numOWords,¬
  newWord, ¬
  i, ¬
  showBase, ¬
  trans, ¬
  interrupt,veFactor,sFactor,ve
  put empty into cd fld playFld
  put length(newWord) into wordLen
  repeat with j = 1 to wordLen
    -- taylored transformation routine goes here
    put char j of newWord into newChar
    if not (newChar < "a") or (newChar > "z") then
      put textname & ",def," & newChar into defCard
      put the number of words in fld defStuff of cd defCard ¬
      into numODef
      put trans into lastTrans
      put word random(numODef) of fld defStuff of cd defCard ¬
      into trans
      if (trans = lastTrans) and (numODef > 2) then
        repeat until trans <> lastTrans
          put word random(numODef) of fld defStuff of cd defCard ¬
          into trans
        end repeat
      end if
      -- now show the transformation
      if the mouse is down then
        put lastTrans into trans
        put trans into cd fld baseFld
        put true into interrupt
        exit repeat
      end if
      showTrans
      wait sFactor
    end if
  end repeat
  if not interrupt then
    if i = numOWords then wait 5 seconds else wait sFactor
    lock screen
    put empty into cd fld baseFld
    put empty into cd fld playFld
    do "unlock screen with" && ve && veFactor
  end if
end transWord

on showTrans
  global showBase,trans,veFactor,ve
  -- showing is also taylored for each playing card
  lock screen
  put trans & " " after cd fld playFld
  if showBase then show cd fld baseFld
  do "unlock screen with" && ve && veFactor
end showTrans

updated version of scripts from acrostic1 in Moods & Conjunctions

(as used in Moods & Conjunctions)

on transWord
  global ¬
  textname,¬
  wherePlay,¬
  numOWords,¬
  newWord, ¬
  i, ¬
  showBase, ¬
  trans, ¬
  lastTrans, ¬
  collocRef, ¬
  lastCollocRef, ¬
  interrupt, ¬
  pickedColloc,veFactor,withColloc,defName,j,wordlen,sFactor,newChar,ve
  put empty into cd fld playFld
  put length(newWord) into wordLen
  repeat with j = 1 to wordLen
    -- taylored transformation routine goes here
    put char j of newWord into newChar
    if not (newChar < "a") or (newChar > "z") then
      put defname & ",def," & newChar into defCard
      if there is a cd defName & ",Occs," & lastTrans then
        put (pickOcc(lastTrans)+1) into collocRef
      else
        put (collocRef+1) into collocRef
      end if
      put false into pickedColloc
      put offset(collocRef,fld defNos of cd defCard) into collocOff
      if (collocOff <> 0) and withColloc then
        put true into pickedColloc
        put (((collocOff-1) div 5) + 1) into collocPicked
        put word collocPicked of fld defStuff of ¬
        card defCard into trans
        put word collocPicked of fld defNos of ¬
        card defCard into collocRef
      else
        put the number of words in fld defStuff of cd defCard ¬
        into numODef
        put random(numODef) into randPicked
        put word randPicked of fld defStuff of cd defCard ¬
        into trans
        put word randPicked of fld defNos of cd defCard ¬
        into collocRef
        if (trans = lastTrans) and (numODef > 2) then
          repeat until trans <> lastTrans
            put word random(numODef) of fld defStuff of cd defCard ¬
            into trans
          end repeat
        end if -- there was or wasn't a valid collocation
      end if
      -- now show the transformation
      if the mouse is down then
        put lastTrans into trans
        put trans into cd fld baseFld
        put true into interrupt
        exit repeat
      end if
      showTrans
      wait sFactor
      put trans into lastTrans
      put collocRef into lastCollocRef
    end if
  end repeat
  if not interrupt then
    if i = numOWords then wait 5 seconds else wait 90
    lock screen
    put empty into cd fld baseFld
    put empty into cd fld playFld
    do" unlock screen with " & ve && veFactor
  end if
end transWord

on showTrans
  global showBase,trans,pickedColloc,veFactor,j,wordlen,newChar,textName,ve
  -- showing is also taylored for each playing card
  lock screen
  -- if pickedColloc then put "*" after cd fld playFld
  put trans & " " after cd fld playFld
  set the textStyle of char ¬
  offset(newChar,the last word of cd fld playFld) ¬
  of the last word of cd fld playFld to bold
  if collocRef & "," is in fld endWords of cd textName & ",text" then
    put return after cd fld playFld
  end if
  if showBase then show cd fld baseFld
  do "unlock screen with " & ve && veFactor
  if (j is wordlen) ¬
  and ("," & trans & "," is in ¬
  ",fallen,resolve,rooms,beneath,return,white,themselves,still,forms,pursue,techniques,demands,drawn,gales,lights,") ¬
  then
    push cd
    lock screen
    put empty into cd fld playFld
    put empty into cd fld baseFld
    do "go cd ill0" & random(4)
    do "unlock screen with dissolve very slow"
    wait 180
    lock screen
    pop cd
    do "unlock screen with dissolve very slow"
  end if
end showTrans
import { css, fadeInByWord, randomInteger } from "observable:@shadoof/inapm0_shared"