"Art of Fugue" by "Victor Gijsbers, Jimmy Maher, Dorte Lassen and Johan" [For those who would simply like to create their own puzzles: I suggest scrolling down to where the actual puzzles are, at the bottom of the source, and learn from them what to do.] The story headline is "Interactive studies in counterpoint". The story genre is "Puzzle". The release number is 1. Release along with cover art. [The Music section can be commented out to create a version without music.] Section - Music To loopplay (SFX - sound name): (- PlaySoundLoop(ResourceIDsOfSounds-->{SFX}); -). Include (- [ PlaySoundLoop resource_ID; if (resource_ID == 0) return; ResourceUsageFlags->resource_ID = true; if (glk_gestalt(gestalt_Sound, 0)) { glk_schannel_play_ext(gg_foregroundchan, resource_ID, -1, 0); } ]; -). To soundstop (SFX - sound name): (-StopSound(ResourceIDsOfSounds-->{SFX}); -). Include (- [ StopSound resource_ID; if (resource_ID == 0) return; ResourceUsageFlags->resource_ID = true; if (glk_gestalt(gestalt_Sound, 0)) { glk_schannel_stop(gg_foregroundchan); } ]; -). Sound of Grosse Fuge is the file "GrosseFuge.ogg". When play begins: loopplay the sound of Grosse Fuge. Stopping music is an action out of world. Understand "stop music" and "end music" and "stop sound" and "end sound" as stopping music. Carry out stopping music: say "Music is now turned off."; soundstop the sound of Grosse Fuge. Starting music is an action out of world. Understand "start music" and "begin music" and "continue music" and "start sound" and "begin sound" and "continue sound" as starting music. Carry out starting music: say "Music is now turned on."; loopplay the sound of Grosse Fuge. Section - Includes Include Custom Library Messages by David Fisher. Include Editable Stored Actions by Ron Newcomb. Include Menus by Emily Short. Include Questions by Michael Callaghan. [Include Title Page by Jon Ingold.] Use MAX_STATIC_DATA of 3000000. [I might change the layout so that each turn simply produces the name of the actor + a response that no longer mentions the actor by name. This is not very important right now.] When play begins: change the library message person to third person; set the library message third person text to "[bold type][current-player][roman type]"; choose row with a Word of "Himself" in the Table of 'himself' forms; now the Word entry is "[bold type][current-player][roman type]"; choose row with a Word of "himself" in the Table of 'himself' forms; now the Word entry is "[bold type][current-player][roman type]". To say current-player: if Een is the player, say "Een"; if Twee is the player, say "Twee"; if Drie is the player, say "Drie"; if Vier is the player, say "Vier". [Before an actor doing anything except looking: say "([count + 1]) [run paragraph on]".] [I'm not sure about the typography here. Try uncommenting this line and tell me what you think.] When play begins: say "Welcome to [bold type]Art of Fugue[roman type], a collection of fiendish puzzles. Well--the first puzzles aren't too fiendish. You can change between puzzles by typing 'next puzzle' and 'previous puzzle', or restart the current puzzle by typing 'restart puzzle'. You can get to the main menu by typing 'menu'. Have fun!" Chapter - Voices and the Fugue First Puzzle is a room. [We are going to create four actors. Een, Twee, Drie and Vier are the first four numbers in Dutch. Using the English names would be... problematic, to say the least.] Een is a person. The printed name of Een is "[bold type]Een[roman type]". Twee is a person. The printed name of Twee is "[bold type]Twee[roman type]". Drie is a person. The printed name of Drie is "[bold type]Drie[roman type]". Vier is a person. The printed name of Vier is "[bold type]Vier[roman type]". Een, Twee, Drie and Vier are scenery. The player is Een. [At the start of the game at least.] Count is a number that varies. Count is 0. [Count goes from 0 to 1 to 2 to 3, and is equivalent to the person who has had his turn last. (Where 4 is 0.) So if count is 0, Een is going to take the next turn.] tmpcmd is some indexed text that varies. [tmpcmd will be a very important variable, because we use it to manipulate and store the player's input. It is after all the INPUT, not the PARSED COMMAND that we want to store. "X me", for instance, is parsed to "examine Een"--and we don't want Twee, Drie and Vier to examine Een in response to "x me". We want them to examine themselves. In order to get that, we store the player's input, and reparse it when needed.] [The Table of Melodies stores the commands. We make 15 rows, for possible later use, but use only 4 or 5 now.] Table of Melodies Twee Stored Action (indexed text) Drie Stored Action (indexed text) Vier Stored Action (indexed text) "wait" "wait" "wait" "wait" "wait" "wait" "wait" "wait" "wait" "wait" "wait" "wait" "wait" "wait" "wait" "wait" "wait" "wait" "wait" "wait" "wait" "wait" "wait" "wait" "wait" "wait" "wait" "wait" "wait" "wait" "wait" "wait" "wait" "wait" "wait" "wait" "wait" "wait" "wait" "wait" "wait" "wait" "wait" "wait" "wait" [15 rows] [This is the most important routine!] First every turn when not looking (this is the play and write the fugue rule): if count is 0: [New commands are stored only on Een's turn.] now tmpcmd is the player's command; [ if the again boolean is true, now tmpcmd is "again"; [See below, the section about again.]] choose row 3 in the Table of Melodies; now the Twee Stored Action entry is tmpcmd; choose row 4 in the Table of Melodies; now the Drie Stored Action entry is tmpcmd; choose row 5 in the Table of Melodies; now the Vier Stored Action entry is tmpcmd; repeat with N running from 1 to 14: [because 15 rows] [Here we push all the rows up.] choose row N in the Table of Melodies; let M be N + 1; let Q be the Twee Stored Action in row M of the Table of Melodies; now the Twee Stored Action entry is Q; [ say Q;] let Q be the Drie Stored Action in row M of the Table of Melodies; now the Drie Stored Action entry is Q; [ say Q;] let Q be the Vier Stored Action in row M of the Table of Melodies; now the Vier Stored Action entry is Q; [ say Q;] increase move counter by 1; if move counter is less than 501: [For move counter, see the chapter on keeping track of the player's solution, at the end of this file] choose row move counter in the Table of Player Solution; now the move entry is tmpcmd; now count is 1 + count; [We increase the person whose turn it is.] if count is 4, now count is 0; if count is 0, change player to Een. Rule for reading a command when count is not 0: [When count is not 0, this routine reads a command from the Table of Melodies and uses it as input.] choose row 1 in the Table of Melodies; if count is 1: change player to Twee; [We make the actor the protagonist. Otherwise, the standard library would give us insane amounts of headaches. Most actions are not reported for non-protagonists.] now tmpcmd is the Twee Stored Action entry; if count is 2: change player to Drie; now tmpcmd is the Drie Stored Action entry; if count is 3: change player to Vier; now tmpcmd is the Vier Stored Action entry; change the text of the player's command to tmpcmd. [And now Inform 7's standard parsing + running the action routines take over.] Chapter - Commands The again-text is some indexed text that varies. After reading a command when count is 0 (this is the handle again rule): if the player's command matches the regular expression "^(again|g)\b": if the again-text is "": [if "again" is the very first command] say "You can hardly repeat that."; reject the player's command; change the text of the player's command to the again-text; otherwise: change the again-text to the player's command. Section - Disable "all" and multiple objects [Here we rewrite all the grammar lines that include "all".] Understand the commands "take", "carry", "hold", "get", "pick", "remove", "put", "insert", "drop", "throw" and "discard" as something new. Understand "take [something]" as taking. Understand "take off [something]" as taking off. Understand "take [something] from [something]" as removing it from. Understand "take [something] off [something]" as removing it from. Understand "take inventory" as taking inventory. Understand the commands "carry" and "hold" as "take". Understand "get out/off/up" as exiting. Understand "get [something]" as taking. Understand "get in/into/on/onto [something]" as entering. Understand "get off [something]" as getting off. Understand "get [something] from [something]" as removing it from. Understand "pick up [something]" or "pick [something] up" as taking. Understand "remove [something preferably held]" as taking off. Understand "remove [something] from [something]" as removing it from. Understand "put [something] in/inside/into [something]" as inserting it into. Understand "put [something] on/onto [something]" as putting it on. Understand "put on [something preferably held]" as wearing. Understand "put down [something preferably held]" or "put [something preferably held] down" as dropping. Understand "insert [something] in/into [something]" as inserting it into. Understand "drop [something preferably held]" as dropping. Understand "drop [something] in/into/down [something]" as inserting it into. Understand "drop [something] on/onto [something]" as putting it on. Understand "drop [something preferably held] at/against/on/onto [something]" as throwing it at. Understand the commands "throw" and "discard" as "drop". Rule for printing a parser error when the latest parser error is can't use multiple objects error: say "You can't use multiple objects in this game." Section - Disable disambiguation questions [Here we rewrite NounDomain to stop disambiguation questions.] Include (- [ NounDomain domain1 domain2 context first_word i j k l answer_words marker; #Ifdef DEBUG; if (parser_trace >= 4) { print " [NounDomain called at word ", wn, "^"; print " "; if (indef_mode) { print "seeking indefinite object: "; if (indef_type & OTHER_BIT) print "other "; if (indef_type & MY_BIT) print "my "; if (indef_type & THAT_BIT) print "that "; if (indef_type & PLURAL_BIT) print "plural "; if (indef_type & LIT_BIT) print "lit "; if (indef_type & UNLIT_BIT) print "unlit "; if (indef_owner ~= 0) print "owner:", (name) indef_owner; new_line; print " number wanted: "; if (indef_wanted == INDEF_ALL_WANTED) print "all"; else print indef_wanted; new_line; print " most likely GNAs of names: ", indef_cases, "^"; } else print "seeking definite object^"; } #Endif; ! DEBUG match_length = 0; number_matched = 0; match_from = wn; SearchScope(domain1, domain2, context); #Ifdef DEBUG; if (parser_trace >= 4) print " [ND made ", number_matched, " matches]^"; #Endif; ! DEBUG wn = match_from+match_length; ! If nothing worked at all, leave with the word marker skipped past the ! first unmatched word... if (number_matched == 0) { wn++; rfalse; } ! Suppose that there really were some words being parsed (i.e., we did ! not just infer). If so, and if there was only one match, it must be ! right and we return it... if (match_from <= num_words) { if (number_matched == 1) { i=match_list-->0; return i; } ! ...now suppose that there was more typing to come, i.e. suppose that ! the user entered something beyond this noun. If nothing ought to follow, ! then there must be a mistake, (unless what does follow is just a full ! stop, and or comma) if (wn <= num_words) { i = NextWord(); wn--; if (i ~= AND1__WD or AND2__WD or AND3__WD or comma_word or THEN1__WD or THEN2__WD or THEN3__WD or BUT1__WD or BUT2__WD or BUT3__WD) { if (lookahead == ENDIT_TOKEN) rfalse; } } } ! Now look for a good choice, if there's more than one choice... number_of_classes = 0; if (number_matched == 1) i = match_list-->0; if (number_matched > 1) { i = Adjudicate(context); if (i == -1) rfalse; if (i == 1) rtrue; ! Adjudicate has made a multiple ! object, and we pass it on } ! If i is non-zero here, one of two things is happening: either ! (a) an inference has been successfully made that object i is ! the intended one from the user's specification, or ! (b) the user finished typing some time ago, but we've decided ! on i because it's the only possible choice. ! In either case we have to keep the pattern up to date, ! note that an inference has been made and return. ! (Except, we don't note which of a pile of identical objects.) if (i ~= 0) { if (dont_infer) return i; if (inferfrom == 0) inferfrom=pcount; pattern-->pcount = i; return i; } ! If we get here, there was no obvious choice of object to make. if (match_from > num_words) etype = "You need to supply a noun as well.^"; else etype = "You'll have to be more specific.^"; rfalse; ]; ! end of NounDomain -) instead of "Noun Domain" in "Parser.i6t". Section - Disable "someone, do something" and "someone, hello" Include (- ! Only check for a comma (a "someone, do something" command) if we are ! not already in the middle of one. (This simplification stops us from ! worrying about "robot, wizard, you are an idiot", telling the robot to ! tell the wizard that she is an idiot.) if (actor == player) { for (j=2 : j<=num_words : j++) { i=NextWord(); if (i == comma_word) jump Conversation; } } jump NotConversation; ! jump GiveError; ! NextWord nudges the word number wn on by one each time, so we've now ! advanced past a comma. (A comma is a word all on its own in the table.) .Conversation; j = wn - 1; if (j == 1) { L__M(##Miscellany, 22); jump ReType; } ! Use NounDomain (in the context of "animate creature") to see if the ! words make sense as the name of someone held or nearby wn = 1; lookahead = HELD_TOKEN; scope_reason = TALKING_REASON; l = NounDomain(player,actors_location,6); scope_reason = PARSING_REASON; if (l == REPARSE_CODE) jump ReParse; if (l == 0) { if (verb_word && ((verb_word->#dict_par1) & 1)) jump NotConversation; L__M(##Miscellany, 23); jump ReType; } .Conversation2; ! The object addressed must at least be "talkable" if not actually "animate" ! (the distinction allows, for instance, a microphone to be spoken to, ! without the parser thinking that the microphone is human). if (l hasnt animate && l hasnt talkable) { L__M(##Miscellany, 24, l); jump ReType; } ! Check that there aren't any mystery words between the end of the person's ! name and the comma (eg, throw out "dwarf sdfgsdgs, go north"). if (wn ~= j) { L__M(##Miscellany, 25); jump ReType; } ! The player has now successfully named someone. ! print "You don't need to talk to anyone in this game.^^"; print "You don't have to name anyone -- just give the command and ",(name) l, " will try it soon enough.^"; jump ReType; ! end of first-word-not-a-verb -) instead of "Parser Letter C" in "Parser.i6t". Section - Disable multiple commands Include (- for (pcount=1 : : pcount++) { pattern-->pcount = PATTERN_NULL; scope_token = 0; token = line_token-->(pcount-1); lookahead = line_token-->pcount; #Ifdef DEBUG; if (parser_trace >= 2) print " [line ", line, " token ", pcount, " word ", wn, " : ", (DebugToken) token, "]^"; #Endif; ! DEBUG if (token ~= ENDIT_TOKEN) { scope_reason = PARSING_REASON; AnalyseToken(token); l = ParseToken(found_ttype, found_tdata, pcount-1, token); while ((l >= GPR_NOUN) && (l < -1)) l = ParseToken(ELEMENTARY_TT, l + 256); scope_reason = PARSING_REASON; if (l == GPR_PREPOSITION) { if (found_ttype~=PREPOSITION_TT && (found_ttype~=ELEMENTARY_TT || found_tdata~=TOPIC_TOKEN)) params_wanted--; l = true; } else if (l < 0) l = false; else if (l ~= GPR_REPARSE) { if (l == GPR_NUMBER) { if (nsns == 0) special_number1 = parsed_number; else special_number2 = parsed_number; nsns++; l = 1; } if (l == GPR_MULTIPLE) l = 0; parser_results-->(parameters+INP1_PRES) = l; parameters++; pattern-->pcount = l; l = true; } #Ifdef DEBUG; if (parser_trace >= 3) { print " [token resulted in "; if (l == REPARSE_CODE) print "re-parse request]^"; if (l == 0) print "failure with error type ", etype, "]^"; if (l == 1) print "success]^"; } #Endif; ! DEBUG if (l == REPARSE_CODE) jump ReParse; if (l == false) break; } else { ! If the player has entered enough already but there's still ! text to wade through: store the pattern away so as to be able to produce ! a decent error message if this turns out to be the best we ever manage, ! and in the mean time give up on this line ! However, if the superfluous text begins with a comma or "then" then ! take that to be the start of another instruction if (wn <= num_words) { l = NextWord(); if (l == THEN1__WD or THEN2__WD or THEN3__WD or comma_word) { if (wn <= num_words) { print "(You can only give one command at a time. The part after ~"; PrintCommand(); print "~ will be ignored.)^"; CommandClarificationBreak(); } } else { for (m=0 : m<32 : m++) pattern2-->m = pattern-->m; pcount2 = pcount; etype = UPTO_PE; break; } } ! Now, we may need to revise the multiple object because of the single one ! we now know (but didn't when the list was drawn up). if (parameters >= 1 && parser_results-->INP1_PRES == 0) { l = ReviseMulti(parser_results-->INP2_PRES); if (l ~= 0) { etype = l; parser_results-->ACTION_PRES = action_to_be; break; } } if (parameters >= 2 && parser_results-->INP2_PRES == 0) { l = ReviseMulti(parser_results-->INP1_PRES); if (l ~= 0) { etype = l; break; } } ! To trap the case of "take all" inferring only "yourself" when absolutely ! nothing else is in the vicinity... if (take_all_rule == 2 && parser_results-->INP1_PRES == actor) { best_etype = NOTHING_PE; jump GiveError; } #Ifdef DEBUG; if (parser_trace >= 1) print "[Line successfully parsed]^"; #Endif; ! DEBUG ! The line has successfully matched the text. Declare the input error-free... oops_from = 0; ! ...explain any inferences made (using the pattern)... if (inferfrom ~= 0) { PrintInferredCommand(inferfrom); ClearParagraphing(); } ! ...copy the action number, and the number of parameters... parser_results-->ACTION_PRES = action_to_be; parser_results-->NO_INPS_PRES = parameters; ! ...reverse first and second parameters if need be... if (action_reversed && parameters == 2) { i = parser_results-->INP1_PRES; parser_results-->INP1_PRES = parser_results-->INP2_PRES; parser_results-->INP2_PRES = i; if (nsns == 2) { i = special_number1; special_number1 = special_number2; special_number2 = i; } } ! ...and to reset "it"-style objects to the first of these parameters, if ! there is one (and it really is an object)... if (parameters > 0 && parser_results-->INP1_PRES >= 2) PronounNotice(parser_results-->INP1_PRES); ! ...and return from the parser altogether, having successfully matched ! a line. if (held_back_mode == 1) { wn=hb_wn; jump LookForMore; } rtrue; } ! end of if(token ~= ENDIT_TOKEN) else } ! end of for(pcount++) .LineFailed; ! The line has failed to match. ! We continue the outer "for" loop, trying the next line in the grammar. if (etype > best_etype) best_etype = etype; if (etype ~= ASKSCOPE_PE && etype > nextbest_etype) nextbest_etype = etype; ! ...unless the line was something like "take all" which failed because ! nothing matched the "all", in which case we stop and give an error now. if (take_all_rule == 2 && etype==NOTHING_PE) break; } ! end of for(line++) ! The grammar is exhausted: every line has failed to match. -) instead of "Parser Letter G" in "Parser.i6t". Chapter - Actions [Lots of text replaced with text that mentions the actor. Much of this may be removed if I introduce the alternative layout mentioned at the beginning of the source. But I'm not sure if that's a good idea or not!] Section - Singing A person has a number called the singing number. The singing number of a person is usually 1. Instead of an actor singing: if the singing number of the actor is: -- 1: say "[The actor] sings '[italic type]Frère Jacques, frère Jacques,[roman type]'."; -- 2: say "[The actor] sings '[italic type]Dormez-vous? Dormez-vous?[roman type]'."; -- 3: say "[The actor] sings '[italic type]Sonnez les matines! Sonnez les matines![roman type]'."; -- 4: say "[The actor] sings '[italic type]Din, dan, don. Din, dan, don.[roman type]'."; increase the singing number of the actor by 1; if the singing number of the actor is 5, now the singing number of the actor is 1. Section - Jumping Instead of an actor jumping: say "[The actor] jumps with a soft 'thump'.". Section - Waiting Instead of an actor waiting: say "[The actor] remains absolutely silent." Section - Looking Check an actor looking: if the actor is not Een, say "[The actor] looks at the surroundings." instead. Section - Examining Instead of an actor examining: say "[The actor] examines [the noun] with obvious interest.". Section - Inventory Instead of an actor taking inventory: say "[The actor] carries:[line break]"; list the contents of the actor, with newlines, indented, giving inventory information, including contents, with extra indentation. Section - Taking The can't take people's possessions rule is not listed in any rulebook. Check an actor taking (this is the new can’t take people’s possessions rule): let the local ceiling be the common ancestor of the actor with the noun; let H be the not-counting-parts holder of the noun; while H is not nothing and H is not the local ceiling: if H is a person: say "[The actor]: [=> noun][that|those] seem[-s] to belong to [H]."; stop the action; let H be the not-counting-parts holder of H. Section - Custom Library Messages [There are undoubtedly things missing here! Search the right line in David Fisher's extension.] Table of custom library messages (continued) Message Id Message Text LibMsg "[The actor]: Taken." LibMsg "[The actor]: Removed." LibMsg "[The actor]: Dropped." LibMsg "[The actor] [eats*] [the % dobj]. Not bad." LibMsg