inform6lib 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -60,7 +60,6 @@
60
60
  # which supports the referencing of Special Global Variables like
61
61
  # $LAST_MATCH_INFO by their screaming snake case names.
62
62
  require 'English'
63
- require 'set'
64
63
 
65
64
  require_relative 'linklpa.h'
66
65
 
@@ -249,7 +248,7 @@ module Parser
249
248
  if !defined?(MAX_TIMERS)
250
249
  MAX_TIMERS = 32 # Max number timers/daemons active at once
251
250
  end # if !defined?(MAX_TIMERS)
252
- @the_timers = [MAX_TIMERS]
251
+ @the_timers = []
253
252
  @active_timers # Number of timers/daemons actives
254
253
 
255
254
  @score # The current score
@@ -516,7 +515,7 @@ module Parser
516
515
 
517
516
  # If you want to use the third-person of the narrative voice, you will
518
517
  # need to replace this selfobj with your own.
519
- SelfObj = Object("(self object)") {
518
+ SelfObj = Inform::Ephemeral::Object.new("(self object)") {
520
519
  with {
521
520
  def short_name; return L__M(:Miscellany, 18); end
522
521
  def description; return L__M(:Miscellany, 19); end
@@ -540,22 +539,22 @@ module Parser
540
539
  @selfobj ||= SelfObj
541
540
  end
542
541
 
543
- def selfobj=(o)
544
- @selfobj = o
542
+ def selfobj=(obj)
543
+ @selfobj = obj
545
544
  end
546
545
 
547
546
  InitalState = Struct.new(:memo).new
548
547
 
549
548
  def self.initial_state
550
- InitalState.memo ||= Inform::Parser.instance_variables.each_with_object({}) do |variable, memo|
551
- memo[variable] = Inform::Parser.instance_variable_get(variable)
549
+ InitalState.memo ||= Inform::Parser.instance_variables.to_h do |variable|
550
+ [variable, Inform::Parser.instance_variable_get(variable)]
552
551
  end
553
552
  end
554
553
 
555
554
  def initialize_state
556
555
  DidScopeAction.clear # TODO: Remove
557
- Inform::Parser.initial_state.each_pair do |k, v|
558
- self.instance_variable_set(k, v)
556
+ Inform::Parser.initial_state.each_pair do |key, value|
557
+ self.instance_variable_set(key, value)
559
558
  end
560
559
  end
561
560
 
@@ -816,7 +815,9 @@ module Parser
816
815
  # Note that there are three different places where a return can happen.
817
816
  # ----------------------------------------------------------------------------
818
817
 
819
- ConversationStarterPattern = %r{\w+, }.freeze
818
+ QuotesSayCommandPattern = %r{^['"]}
819
+ ConversationStarterPattern = %r{\w+, }
820
+ WordOrCommaSplitPattern = %r{\s+|(?=,)|(?<=,)}
820
821
 
821
822
  def Parser__parse
822
823
  @meta = false
@@ -843,23 +844,28 @@ module Parser
843
844
 
844
845
  if @held_back_mode
845
846
  @held_back_mode = false
846
- throw :ReParse
847
+ return ReParse()
847
848
  end
848
849
 
849
850
  # Handle the apostrophe shorthand for the Say command
850
- @input = "say '#{@input[1..]}'" if @input =~ /^['"]/
851
+ @input = "say '#{@input[1..]}'" if QuotesSayCommandPattern.match?(@input)
851
852
 
852
853
  # Get the words from the player's input
853
- @words = @input.split
854
+ # @words = @input.split
855
+ @words = @input.split(WordOrCommaSplitPattern)
854
856
 
855
- # :ReParse
857
+ ReParse()
858
+ end
859
+ # def Parser__parse
856
860
 
861
+ def ReParse
857
862
  @parser_inflection = :name
858
863
 
859
864
  # Initially assume the command is aimed at the player, and the verb
860
865
  # is the first word
861
866
 
862
867
  # num_words = WordCount()
868
+ @num_words = @words.length
863
869
  @wn = 0 # Our words "buffer" is indexed from 0, unlike Inform
864
870
 
865
871
  if defined? LanguageToInformese
@@ -912,14 +918,19 @@ module Parser
912
918
  @location ||= @actors_location # TODO: Necessary?
913
919
  @usual_grammar_after = nil
914
920
 
915
- # :AlmostReParse
921
+ AlmostReParse()
922
+ end
923
+ # def ReParse
916
924
 
925
+ def AlmostReParse
917
926
  @scope_token = nil
918
927
  @action_to_be = nil
919
928
 
920
- # Begin from what we currently think is the verb word
929
+ BeginCommand()
930
+ end
921
931
 
922
- # :BeginCommand
932
+ def BeginCommand()
933
+ # Begin from what we currently think is the verb word
923
934
 
924
935
  @wn = @verb_wordnum
925
936
  # @verb_word = NextWordStopped() # TODO: FIXME?
@@ -938,7 +949,7 @@ module Parser
938
949
 
939
950
  @verb_word = AGAIN1__WD if @verb_word == AGAIN2__WD || @verb_word == AGAIN3__WD
940
951
  if @verb_word == AGAIN1__WD
941
- raise RepeatedOrder if @actor != @player
952
+ raise RepeatedCommand if @actor != @player
942
953
  raise NothingToRepeat if history.empty?
943
954
  @input = history.last
944
955
  @words = @input.split
@@ -956,8 +967,6 @@ module Parser
956
967
  # return DoOops()
957
968
  end
958
969
 
959
- initialize_state
960
-
961
970
  if @match_list then @match_list.clear else @match_list = [] end
962
971
  if @multiple_object then @multiple_object.clear else @multiple_object = [] end
963
972
  if @pattern then @pattern.clear else @pattern = [] end
@@ -1005,7 +1014,7 @@ module Parser
1005
1014
  end # DEBUG
1006
1015
  @wn = @verb_wordnum; @indef_mode = false; @token_filter = nil
1007
1016
  l = NounDomain(Compass)
1008
- # throw :ReParse if l == REPARSE_CODE
1017
+ # return ReParse() if l == REPARSE_CODE
1009
1018
 
1010
1019
  if l
1011
1020
  # If it is a direction, send back the results:
@@ -1031,41 +1040,23 @@ module Parser
1031
1040
  # tell the wizard that she is an idiot.)
1032
1041
 
1033
1042
  if @actor == @player
1034
- if ConversationStarterPattern.match?(@words.join(' '))
1035
- # TODO: Implement conversation and imperative commands
1036
- log.debug "jump Conversation"
1037
- # jump Conversation
1038
- else
1039
- @verb = UnknownVerb(@verb)
1040
- if @verb.nil?
1041
- @best_etype = VerbUnrecognized
1042
- raise @best_etype
1043
- else
1044
- log.debug "jump VerbAccepted"
1045
- # jump VerbAccepted
1046
- end
1043
+ j = 2
1044
+ while j <= @num_words
1045
+ i = NextWord(); j += 1
1046
+ return Conversation() if i == @comma_word
1047
1047
  end
1048
- else
1049
- @best_etype = VerbUnrecognized
1050
- raise @best_etype
1051
- end
1052
-
1053
- # Conversation()
1054
-
1055
- @actor = l
1056
- @actors_location = ScopeCeiling(l)
1057
- if defined?(DEBUG)
1058
- if @parser_trace >= 1
1059
- println "[Actor is " + the(@actor) + " in " + @actors_location + "]"
1048
+ @verb = UnknownVerb(@verb)
1049
+ return VerbAccepted() unless @verb.nil?
1060
1050
  end
1061
- end # DEBUG
1062
- return true
1051
+ @best_etype = VerbUnrecognized
1052
+ return GiveError()
1053
+ # raise @best_etype
1063
1054
  end
1064
- # of first-word-not-a-verb
1055
+ # end of first-word-not-a-verb
1065
1056
 
1066
1057
  VerbAccepted()
1067
1058
  end
1068
- # def Parser__parse
1059
+ # def BeginCommand
1069
1060
 
1070
1061
  ############################################################################
1071
1062
  #
@@ -1143,46 +1134,41 @@ module Parser
1143
1134
 
1144
1135
  return true
1145
1136
  end
1146
- # def Parser__parse
1137
+ # def VerbAccepted
1147
1138
 
1148
1139
  # NextWord nudges the word number wn on by one each time, so we've now
1149
1140
  # advanced past a comma. (A comma is a word all on its own in the table.)
1150
1141
 
1151
1142
  def Conversation
1152
- j = @wn - 1
1153
- if j == 1
1154
- raise UnexpectedComma
1155
- end
1143
+ j = @wn
1144
+ raise UnexpectedComma if j == 1
1156
1145
 
1157
1146
  # Use NounDomain (in the context of "animate creature") to see if the
1158
1147
  # words make sense as the name of someone held or nearby
1159
1148
 
1160
- @wn = 1; @lookahead = HELD_TOKEN
1149
+ @wn = 0; @lookahead = :held
1161
1150
  @scope_reason = TALKING_REASON
1162
1151
  l = NounDomain(@player,@actors_location,6)
1163
1152
  @scope_reason = PARSING_REASON
1164
1153
  throw :ReParse if l == REPARSE_CODE
1165
- raise MissingAddressee if l == 0
1154
+ raise MissingAddressee if l == false
1166
1155
 
1167
- Conversation2()
1156
+ Conversation2(l, j)
1168
1157
  end
1169
1158
 
1170
- def Conversation2
1159
+ def Conversation2(l, j)
1171
1160
 
1172
1161
  # The object addressed must at least be "talkable" if not actually "animate"
1173
1162
  # (the distinction allows, for instance, a microphone to be spoken to,
1174
1163
  # without the parser thinking that the microphone is human).
1175
1164
 
1176
- if l.hasnt? :animate, :talkable
1177
- raise InanimateAddressee
1178
- end
1165
+ raise InanimateAddressee if l.hasnt? :animate, :talkable
1179
1166
 
1180
1167
  # Check that there aren't any mystery words between the end of the person's
1181
1168
  # name and the comma (eg, throw out "dwarf sdfgsdgs, go north").
1182
1169
 
1183
- if @wn != j
1184
- raise SpeechImpediment
1185
- end
1170
+ @wn += 1
1171
+ raise SpeechImpediment if @wn != j
1186
1172
 
1187
1173
  # The player has now successfully named someone. Adjust "him", "her", "it":
1188
1174
 
@@ -1191,15 +1177,14 @@ module Parser
1191
1177
  # Set the global variable "actor", adjust the number of the first word,
1192
1178
  # and begin parsing again from there.
1193
1179
 
1194
- @verb_wordnum = j + 1
1180
+ @verb_wordnum = j
1195
1181
 
1196
1182
  # Stop things like "me, again":
1197
1183
 
1198
1184
  if l == @player
1199
1185
  @wn = @verb_wordnum
1200
1186
  if [AGAIN1__WD, AGAIN2__WD, AGAIN3__WD].include?(NextWordStopped())
1201
- L__M(:Miscellany, 20)
1202
- jump ReType
1187
+ raise RepeatedCommand
1203
1188
  end
1204
1189
  end
1205
1190
 
@@ -1316,7 +1301,8 @@ module Parser
1316
1301
  prepositions = @line_token[@pcount].split(/\//).map { |s| s[1..-2] }
1317
1302
  @pcount += 1 while @line_ttype[@pcount] == :preposition
1318
1303
 
1319
- if ElementaryTokens.include?(@line_ttype[@pcount]) && @line_tdata[@pcount] == :noun
1304
+ if ElementaryTokens.include?(@line_ttype[@pcount]) &&
1305
+ @line_tdata[@pcount] == :noun
1320
1306
 
1321
1307
  # Advance past the last preposition
1322
1308
 
@@ -1402,6 +1388,7 @@ module Parser
1402
1388
  @scope_reason = PARSING_REASON
1403
1389
  AnalyseToken(token)
1404
1390
 
1391
+ # TODO: FIXME
1405
1392
  # if @action_to_be == :AskTo && ElementaryTokens.include?(@found_ttype) &&
1406
1393
  # @found_tdata == :topic
1407
1394
  # l = @results[2]
@@ -1414,7 +1401,8 @@ module Parser
1414
1401
 
1415
1402
  if defined?(DEBUG) && @parser_trace >= 10
1416
1403
  new_line
1417
- println "Parsing the current word '#{current_word}' as a #{@found_ttype} in this grammar:"
1404
+ println "Parsing the current word '#{current_word}' " +
1405
+ "as a #{@found_ttype} in this grammar:"
1418
1406
  println grammar.to_s
1419
1407
  println ">>Before invoking ParseToken(#{@found_ttype})..."
1420
1408
  println ">>...The found token data is #{@found_tdata}"
@@ -1603,7 +1591,8 @@ module Parser
1603
1591
  # We continue the outer "for" loop, trying the next line in the grammar.
1604
1592
 
1605
1593
  @best_etype = @etype if @etype > @best_etype
1606
- @nextbest_etype = @etype if @etype != AskScope && !@etype.nil? && @etype > @nextbest_etype
1594
+ @nextbest_etype = @etype if @etype != AskScope &&
1595
+ !@etype.nil? && @etype > @nextbest_etype
1607
1596
 
1608
1597
  # ...unless the line was something like "take all" which failed because
1609
1598
  # nothing matched the "all", in which case we stop and give an error now.
@@ -1615,14 +1604,17 @@ module Parser
1615
1604
 
1616
1605
  # The grammar is exhausted: every line has failed to match.
1617
1606
 
1607
+ GiveError()
1608
+ end
1609
+ # def BestGrammar
1610
+
1618
1611
  ############################################################################
1619
1612
  #
1620
1613
  # H: Cheaply parse otherwise unrecognised conversation and return
1621
1614
  #
1622
1615
  ############################################################################
1623
1616
 
1624
- # label :GiveError do
1625
-
1617
+ def GiveError
1626
1618
  if defined?(DEBUG) && @parser_trace >= 10
1627
1619
  println ">>@etype is #{@etype}"
1628
1620
  println ">>@best_etype is #{@best_etype}"
@@ -1633,26 +1625,25 @@ module Parser
1633
1625
  # If the command was addressed to somebody else (eg, "dwarf, sfgh") then
1634
1626
  # it is taken as conversation which the parser has no business in disallowing.
1635
1627
 
1636
- # TODO: Implement conversations
1637
- # if @actor != @player
1638
- # if @usual_grammar_after
1639
- # @verb_wordnum = @usual_grammar_after
1640
- # throw :AlmostReParse
1641
- # end
1642
- # @wn = @verb_wordnum
1643
- # @special_word = NextWord()
1644
- # if @special_word == @comma_word
1645
- # @special_word = NextWord()
1646
- # @verb_wordnum++
1647
- # end
1648
- # @special_number = TryNumber(@verb_wordnum)
1649
- # @results[0] = :NotUnderstood
1650
- # @results[1] = 2
1651
- # @results[2] = 1; @special_number1 = @special_word
1652
- # @results[3] = @actor
1653
- # @consult_from = @verb_wordnum; @consult_words = @num_words-@consult_from+1
1654
- # return true
1655
- # end
1628
+ if @actor != @player
1629
+ unless @usual_grammar_after.nil?
1630
+ @verb_wordnum = @usual_grammar_after
1631
+ return AlmostReParse()
1632
+ end
1633
+ @wn = @verb_wordnum
1634
+ @special_word = NextWord()
1635
+ if @special_word == @comma_word
1636
+ @special_word = NextWord()
1637
+ @verb_wordnum += 1
1638
+ end
1639
+ @special_number = TryNumber(@verb_wordnum)
1640
+ @results[0] = :NotUnderstood
1641
+ @results[1] = 2
1642
+ @results[2] = 1; @special_number1 = @special_word
1643
+ @results[3] = @actor
1644
+ @consult_from = @verb_wordnum; @consult_words = @num_words-@consult_from+1
1645
+ return true
1646
+ end
1656
1647
 
1657
1648
  ############################################################################
1658
1649
  #
@@ -1679,7 +1670,7 @@ module Parser
1679
1670
  # ...being careful not to go all the way back, to avoid infinite repetition
1680
1671
  # of a deferred command causing an error.
1681
1672
  end
1682
- # def BestGrammar
1673
+ # def GiveError
1683
1674
 
1684
1675
  ############################################################################
1685
1676
  #
@@ -1896,7 +1887,7 @@ module Parser
1896
1887
  # (F) Return the conclusion of parsing an object list
1897
1888
  # ----------------------------------------------------------------------------
1898
1889
 
1899
- CommaWordPattern = %r{,$}.freeze
1890
+ CommaWordPattern = %r{,$}
1900
1891
 
1901
1892
  # rubocop: disable Layout/EmptyLinesAroundBlockBody
1902
1893
  def ParseToken(given_ttype, given_tdata, token_n, token)
@@ -2041,7 +2032,10 @@ module Parser
2041
2032
 
2042
2033
  end
2043
2034
 
2044
- token = given_ttype
2035
+ # TODO: Remove (2026-08-21)
2036
+ # token = given_ttype
2037
+ # TODO: Test
2038
+ token = given_tdata
2045
2039
  prev_indef_wanted = 0
2046
2040
 
2047
2041
  ############################################################################
@@ -2116,7 +2110,9 @@ module Parser
2116
2110
  end
2117
2111
  end
2118
2112
 
2119
- if [ME1__WD, ME2__WD, ME3__WD].include?(o) then @pronoun_word = o; @pronoun_obj = selfobj; end
2113
+ if [ME1__WD, ME2__WD, ME3__WD].include?(o)
2114
+ @pronoun_word = o; @pronoun_obj = selfobj
2115
+ end
2120
2116
 
2121
2117
  @allow_plurals = true; desc_wn = @wn;
2122
2118
 
@@ -2197,7 +2193,8 @@ module Parser
2197
2193
  @wn = desc_wn
2198
2194
  throw :TryAgain2
2199
2195
  end
2200
- @etype = CannotUnderstandSentence if @multiflag && [UnexpectedMultiple, TooFew].include?(@etype)
2196
+ @etype = CannotUnderstandSentence if @multiflag &&
2197
+ [UnexpectedMultiple, TooFew].include?(@etype)
2201
2198
  @etype = CantSee()
2202
2199
  return FailToken(desc_wn, prev_indef_wanted)
2203
2200
  end # Choose best error
@@ -2749,7 +2746,8 @@ module Parser
2749
2746
  next unless @pattern[j]
2750
2747
 
2751
2748
  if defined?(DEBUG)
2752
- println "[Gluing in inference with pattern code " + @pattern[j] + "]" if @parser_trace >= 5
2749
+ println "[Gluing in inference with pattern code " +
2750
+ @pattern[j] + "]" if @parser_trace >= 5
2753
2751
  end # DEBUG
2754
2752
 
2755
2753
  # Conveniently, parse2[1] is the first word in both ZCODE and GLULX.
@@ -2975,7 +2973,8 @@ module Parser
2975
2973
  end
2976
2974
  @multi_context = context
2977
2975
  if defined?(DEBUG)
2978
- println " Made multiple object of size #{@multiple_object.length}]" if @parser_trace >= 4
2976
+ println " Made multiple object of size " +
2977
+ "#{@multiple_object.length}]" if @parser_trace >= 4
2979
2978
  end # DEBUG
2980
2979
  return 1
2981
2980
  end
@@ -3007,7 +3006,8 @@ module Parser
3007
3006
  println " Grouped into #{n} possibilities by name:"
3008
3007
  for i in (0...@number_matched)
3009
3008
  if @match_classes[i] > 0
3010
- print " " + The(@match_list[i]) + " (#{@match_list[i].object_id}) --- group #{@match_classes[i]}\n"
3009
+ print " " + The(@match_list[i]) +
3010
+ " (#{@match_list[i].object_id}) --- group #{@match_classes[i]}\n"
3011
3011
  end
3012
3012
  end
3013
3013
  end
@@ -3097,7 +3097,8 @@ module Parser
3097
3097
  # entries which fail the basic requirements of the descriptors.
3098
3098
  # ----------------------------------------------------------------------------
3099
3099
 
3100
- def ScoreMatchL(context, match_list = [], match_scores = [], number_matched = 0, threshold = 0)
3100
+ def ScoreMatchL(context, match_list = [], match_scores = [],
3101
+ number_matched = 0, threshold = 0)
3101
3102
  threshold += 1 if (@indef_type & OTHER_BIT) != 0
3102
3103
  threshold += 1 if (@indef_type & MY_BIT) != 0
3103
3104
  threshold += 1 if (@indef_type & THAT_BIT) != 0
@@ -3129,7 +3130,8 @@ module Parser
3129
3130
  for i in (0...number_matched)
3130
3131
  obj = match_list[i]; its_owner = parent(obj); its_score = 0; met = 0
3131
3132
 
3132
- # met+=1 if (@indef_type & OTHER_BIT) != 0 && obj != @itobj && obj != @himobj && obj != @herobj
3133
+ # met+=1 if (@indef_type & OTHER_BIT) != 0 &&
3134
+ # obj != @itobj && obj != @himobj && obj != @herobj
3133
3135
  met += 1 if (@indef_type & MY_BIT) != 0 && (its_owner == @actor)
3134
3136
  met += 1 if (@indef_type & THAT_BIT) != 0 && (its_owner == @actors_location)
3135
3137
  met += 1 if (@indef_type & LIT_BIT) != 0 && obj.has?(:light)
@@ -3209,7 +3211,9 @@ module Parser
3209
3211
  if defined?(DEBUG)
3210
3212
  if @parser_trace >= 4
3211
3213
  if best < 0 then println " Best guess ran out of choices"
3212
- else println " Best guess " + the(@match_list[earliest]) + " (#{@match_list[earliest]})"
3214
+ else
3215
+ println " Best guess " + the(@match_list[earliest]) +
3216
+ " (#{@match_list[earliest]})"
3213
3217
  end
3214
3218
  end
3215
3219
  end # DEBUG
@@ -3301,7 +3305,8 @@ module Parser
3301
3305
 
3302
3306
  # def PrintInferredCommand(from = 0)
3303
3307
  # singleton_noun = false
3304
- # if from != 0 && from == (@pcount-1) && @pattern[from].object? # > 1 && @pattern[from] < REPARSE_CODE
3308
+ # if from != 0 && from == (@pcount-1) &&
3309
+ # @pattern[from].object? # > 1 && @pattern[from] < REPARSE_CODE
3305
3310
  # singleton_noun = true
3306
3311
  # end
3307
3312
 
@@ -3310,7 +3315,8 @@ module Parser
3310
3315
 
3311
3316
  # def PrintInferredCommand(from = nil)
3312
3317
  # singleton_noun = false
3313
- # if from != 0 && from == @pcount-1 && @pattern[from].object? # && @pattern[from] < REPARSE_CODE
3318
+ # if from != 0 && from == @pcount-1 &&
3319
+ # @pattern[from].object? # && @pattern[from] < REPARSE_CODE
3314
3320
  # singleton_noun = true
3315
3321
  # end
3316
3322
  # if singleton_noun == true
@@ -3593,10 +3599,7 @@ module Parser
3593
3599
  if @parser_trace >= 6
3594
3600
  println "[DSA on " + the(thing) + " with reason = " + scope_reason.to_s +
3595
3601
  " p1 = #{@parser_one} p2 = #{@parser_two}]"
3596
- # TODO: Remove
3597
- if name(thing) == 'Nels'
3598
- caller.each { |t| println t }
3599
- elsif DidScopeAction[thing][scope_reason] > 1
3602
+ if DidScopeAction[thing][scope_reason] > 1
3600
3603
  println "Did DoScopeAction(#{thing}) with reason = #{scope_reason} more than once!"
3601
3604
  caller.each { |t| println t }
3602
3605
  end
@@ -3604,13 +3607,13 @@ module Parser
3604
3607
  end # DEBUG
3605
3608
  case scope_reason
3606
3609
  when REACT_BEFORE_REASON
3607
- return unless thing.react_before?(action)
3610
+ return unless thing.react_before?(@action)
3608
3611
  if defined?(DEBUG)
3609
3612
  println "[Considering react_before for " + the(thing) + "]" if @parser_trace >= 2
3610
3613
  end
3611
3614
  @parser_one ||= RunRoutines(thing, :react_before)
3612
3615
  when REACT_AFTER_REASON
3613
- return unless thing.react_after?(action)
3616
+ return unless thing.react_after?(@action)
3614
3617
  if defined?(DEBUG)
3615
3618
  println "[Considering react_after for " + the(thing) + "]" if @parser_trace >= 2
3616
3619
  end # DEBUG
@@ -3790,7 +3793,8 @@ module Parser
3790
3793
  println ">>...Matches are [#{matches_}] ([#{object_ids_}])"
3791
3794
  end
3792
3795
  println ">>...No matches yet" if @match_list.empty?
3793
- println ">>...Matched by #{@match_length} word" + (!@match_length.nil? && @match_length == 1 ? 's' : '')
3796
+ println ">>...Matched by #{@match_length} word" +
3797
+ (!@match_length.nil? && @match_length == 1 ? 's' : '')
3794
3798
  println ">>...Match is #{obj} (#{obj.object_id})"
3795
3799
  already_in_list = @match_list.include?(obj) ? 'Yes' : 'No'
3796
3800
  println ">>...Is object id #{obj.object_id} already in the match list? #{already_in_list}"
@@ -3866,7 +3870,8 @@ module Parser
3866
3870
 
3867
3871
  def TryGivenObject(obj)
3868
3872
  if defined?(DEBUG)
3869
- println " Trying " + the(obj) + " (#{obj.object_id}) at word #{@wn + 1}" if @parser_trace >= 5
3873
+ println " Trying " + the(obj) +
3874
+ " (#{obj.object_id}) at word #{@wn + 1}" if @parser_trace >= 5
3870
3875
  end # DEBUG
3871
3876
 
3872
3877
  @dict_flags_of_noun = 0
@@ -4060,7 +4065,7 @@ module Parser
4060
4065
  return NextWord()
4061
4066
  end
4062
4067
 
4063
- def WordAddress(wordnum); @words[wordnum]; end
4068
+ def WordAddress(wordnum); @buffer[wordnum]; end
4064
4069
 
4065
4070
  def WordLength(wordnum); @words[wordnum] ? @words[wordnum].length : 0; end
4066
4071
 
@@ -4196,20 +4201,20 @@ module Parser
4196
4201
  i = @lightflag
4197
4202
  @lightflag = OffersLight(parent(@player))
4198
4203
 
4199
- if (i.nil? || i == false) && @lightflag
4204
+ if i == false && @lightflag == true
4200
4205
  @location = @real_location
4201
4206
  log.debug "Looking after light adjustment for #{@player}" # TODO: Remove
4202
- return _invoke :Look unless flag
4207
+ return _invoke :Look if flag == false
4203
4208
  end
4204
4209
 
4205
- if !i.nil? && !@lightflag
4210
+ if i == true && @lightflag == false
4206
4211
  @real_location = @location; @location = thedark
4207
- unless flag
4212
+ if flag == false
4208
4213
  NoteArrival()
4209
4214
  return L__M(:Miscellany, 9)
4210
4215
  end
4211
4216
  end
4212
- @location = thedark if !i.nil? && !@lightflag
4217
+ @location = thedark if i == false && @lightflag == false
4213
4218
  end
4214
4219
 
4215
4220
  def OffersLight(i)
@@ -4219,8 +4224,10 @@ module Parser
4219
4224
  if i.has?(:container)
4220
4225
  return OffersLight(parent(i)) if i.has?(:open) || i.has?(:transparent)
4221
4226
  else
4222
- return OffersLight(parent(i)) if i.has?(:enterable) || i.has?(:transparent) || i.has?(:supporter)
4227
+ return OffersLight(parent(i)) if i.has?(:enterable) ||
4228
+ i.has?(:transparent) || i.has?(:supporter)
4223
4229
  end
4230
+
4224
4231
  false
4225
4232
  end
4226
4233
 
@@ -4271,7 +4278,7 @@ module Parser
4271
4278
  i = @player; i = parent(i) until parent(i).nil?
4272
4279
  @location = i; @real_location = @location
4273
4280
  return RunTimeError(10) unless parent(@player)
4274
- # MoveFloatingObjects()
4281
+ MoveFloatingObjects()
4275
4282
  @lightflag = OffersLight(parent(@player))
4276
4283
  @location = thedark unless @lightflag
4277
4284
  @print_player_flag = flag
@@ -4358,7 +4365,7 @@ module Parser
4358
4365
  alias ShowverbSub ShowVerbSub
4359
4366
 
4360
4367
  KnownNumberProperties = %i[number capacity time_left].freeze
4361
- MethodWriterPattern = %r{=$}.freeze
4368
+ MethodWriterPattern = %r{=$}
4362
4369
 
4363
4370
  def ShowobjSub
4364
4371
  f = 0
@@ -4765,7 +4772,9 @@ module Parser
4765
4772
  if defined? LanguagePrintShortName
4766
4773
  return true if LanguagePrintShortName(o)
4767
4774
  end # LanguagePrintShortName
4768
- return true if @indef_mode && o.respond_to?(:short_name_indef) && PrintOrRun(o, :short_name_indef, 1) != false
4775
+ return true if @indef_mode &&
4776
+ o.respond_to?(:short_name_indef) &&
4777
+ PrintOrRun(o, :short_name_indef, 1) != false
4769
4778
  return true if !o.&(:short_name).nil? && PrintOrRun(o, :short_name, 1) != false
4770
4779
  print o
4771
4780
  end
@@ -4951,7 +4960,7 @@ module Parser
4951
4960
 
4952
4961
  class NothingToRepeat < StandardError; end
4953
4962
 
4954
- class RepeatedOrder < StandardError; end
4963
+ class RepeatedCommand < StandardError; end
4955
4964
 
4956
4965
  class SpeechImpediment < StandardError; end
4957
4966
 
@@ -4980,203 +4989,9 @@ class NoVerbRoutine < StandardError; end
4980
4989
 
4981
4990
  class CycleError < StandardError; end
4982
4991
 
4983
- # The Inform module
4984
- module Inform
4985
- # The Rules module defines functional additions to the Session state
4986
- # machine. The additional functionality supports user interface patterns
4987
- # commonly found in interactive fiction games, specifically those created
4988
- # using Inform. Supporting the principle of least surprise mandates that
4989
- # the expectations held by the users of such games should be satisfied.
4990
- # TODO: Perhaps this should go in the mudlib
4991
- module Rules
4992
- def ask(s = nil)
4993
- println s if s.is_a?(String)
4994
- if session
4995
- @previous ||= session.state
4996
- session.state = :asked
4997
- end
4998
- # true
4999
- session.state
5000
- end
5001
-
5002
- def yesorno(s = nil)
5003
- print s.to_s unless s.nil?
5004
- unless session.nil?
5005
- @previous ||= session.state
5006
- session.state = :asked
5007
- end
5008
- session&.state || false
5009
- end
5010
-
5011
- def asked
5012
- yesorno?
5013
- end
5014
-
5015
- def yesorno?
5016
- reset_io # TODO: Remove this. Why is the buffer getting a newline character added to it?
5017
- case @buffer.split.first
5018
- when /^(y|yes)$/i
5019
- println(resume)
5020
- responded
5021
- when /^(n|no)$/i
5022
- prompt
5023
- responded
5024
- else
5025
- L__M(:Quit, 1); print "> "
5026
- session&.state
5027
- end
5028
- end
5029
-
5030
- def disambiguate
5031
- session.state = :disambiguating unless session.nil?
5032
- false
5033
- end
5034
-
5035
- def disambiguating
5036
- WhichOne() if @ambiguity
5037
- session.state = :playing unless session.nil?
5038
- end
5039
-
5040
- def complete
5041
- session.state = :completing unless session.nil?
5042
- false
5043
- end
5044
-
5045
- def completing
5046
- Incomplete() if @ambiguity
5047
- session.state = :playing unless session.nil?
5048
- end
5049
-
5050
- SolicitedStates = %i[asked yesorno? solicited].freeze
5051
- def solicit(s = nil)
5052
- if !session.nil? && !SolicitedStates.include?(session.state)
5053
- prompt s if s.is_a?(String)
5054
- @previous ||= session.state
5055
- end
5056
- session.state = :solicited unless session.nil?
5057
- end
5058
-
5059
- def disambiguated
5060
- session.state = :playing unless session.nil?
5061
- queue @input
5062
- end
5063
-
5064
- def completed
5065
- session.state = :playing unless session.nil?
5066
- queue @input
5067
- end
5068
-
5069
- def solicited
5070
- responded? @input
5071
- end
5072
-
5073
- def responded
5074
- session&.state = previous_state = @previous
5075
- @previous = nil
5076
- session&.state || previous_state
5077
- end
5078
-
5079
- def asked?
5080
- session&.state == :asked
5081
- end
5082
-
5083
- def disambiguating?
5084
- session&.state == :disambiguating
5085
- end
5086
-
5087
- def completing?
5088
- session&.state == :completing
5089
- end
5090
-
5091
- def solicited?
5092
- session&.state == :solicited
5093
- end
5094
-
5095
- def responded?(_s)
5096
- reset_io # TODO: Remove this. Why is the buffer getting a newline character added to it?
5097
- if @buffer.empty?
5098
- prompt
5099
- else
5100
- responded
5101
- @consult_words = @input = @buffer.dup
5102
- result = resume
5103
- println(result)
5104
- if result == :playing
5105
- reset_prompt
5106
- prompt
5107
- end
5108
- end
5109
- session&.state
5110
- end
5111
-
5112
- More = Struct.new(:text, :page_index)
5113
-
5114
- DefaultMoreParameters = {
5115
- page_index: 0, limit: 10, help: nil, done: :playing
5116
- }.freeze
5117
-
5118
- # This method supports the MoreSub verb implementation.
5119
- def more(text = nil, params = {})
5120
- params = DefaultMoreParameters.merge(params)
5121
- unless session
5122
- println text
5123
- return params[:done]
5124
- end
5125
- input = @buffer.dup
5126
- @more ||= More.new([], params[:page_index])
5127
-
5128
- if text
5129
- @more.text = text
5130
- else
5131
- text = @more.text
5132
- end
5133
-
5134
- case input
5135
- when /\?/
5136
- println params[:help] || "<Help for More goes here>"
5137
- when /^(q|quit)$/
5138
- @more = nil
5139
- @prompt = nil
5140
- prompt
5141
- return session.state = params[:done]
5142
- else
5143
- text = @more.text.split('\n') if @more.text.is_a? String
5144
- unless text.is_a?(Array)
5145
- println "[Error: Format problem using the more command -- cannot handle given text.]"
5146
- return session.state = params[:done]
5147
- end
5148
-
5149
- println text[@more.page_index, params[:limit]].join("\n")
5150
-
5151
- @more.page_index += params[:limit]
5152
- end
5153
-
5154
- percent_complete = [100, (100 * (@more.page_index.to_f / text.length)).floor].min
5155
- @more = nil unless @more.page_index < @more.text.length
5156
-
5157
- new_line
5158
- print "--More--(#{percent_complete}%)"
5159
-
5160
- if percent_complete < 100
5161
- prompt " [?]: "
5162
- session.state = :more
5163
- else
5164
- new_line
5165
- prompt nil
5166
- session.state = params[:done]
5167
- end
5168
- end
5169
- # def more
5170
- end
5171
- # module Rules
5172
- end
5173
- # module Inform
5174
-
5175
4992
  # The InformLibrary class.
5176
4993
  # Inform 6 defines this as an Object.
5177
4994
  class InformLibrary < Inform::Ephemeral::Object
5178
- REGISTRY = Struct.new(:memo).new(defined?(Java) ? java.util.concurrent.ConcurrentHashMap.new : {})
5179
- include Inform::Rules
5180
4995
  include Inform::Parser
5181
4996
 
5182
4997
  attr_accessor :player
@@ -5204,48 +5019,45 @@ class InformLibrary < Inform::Ephemeral::Object
5204
5019
  def play
5205
5020
  @real_location = thedark
5206
5021
  @player = selfobj; @actor = @player
5207
- selfobj.capacity = MAX_CARRIED # ### change?
5022
+ log.debug "Setting capacity of #{selfobj.identity} to #{MAX_CARRIED}"
5023
+ selfobj.&(:capacity, MAX_CARRIED) # ### change?
5024
+ log.debug " selfobj.&(:capacity): #{selfobj.&(:capacity)}"
5208
5025
 
5209
5026
  new_line
5210
5027
  LibraryExtensions.RunAll(:ext_initialise)
5211
5028
  j = Initialise()
5029
+ # Ruby doesn't automatically print implicitly returned strings :(
5212
5030
  println j if j.is_a?(String)
5031
+ @last_score = @score
5213
5032
  move @player, @location
5214
- light_adjusted @player
5215
5033
 
5216
5034
  @location = @location.parent until @location.parent.nil?
5217
5035
  @real_location = @location
5218
5036
  @actor = @player # resync, because player may have been changed in initialise()
5219
5037
  Banner() if j != 2
5038
+ MoveFloatingObjects()
5220
5039
  @lightflag = OffersLight(@player.parent)
5221
5040
  if @lightflag == false
5222
5041
  @real_location = @location
5223
5042
  @location = @thedark
5224
5043
  end
5225
- OBIT_HEAD() if deadflag
5226
- @player.mute if deadflag
5044
+ OBIT_HEAD() if deadflag != 0
5227
5045
  invoke :Look
5228
- deadflag ? :death : :playing
5046
+ deadflag != 0 ? :death : :playing
5229
5047
  end
5230
5048
 
5231
5049
  def playing
5232
- @player.unmute if @player.respond_to?(:unmute) && !deadflag
5050
+ @player.unmute if @player.respond_to?(:unmute) && deadflag != 0
5233
5051
  enqueue(command: @buffer.dup)
5234
5052
 
5235
- unless deadflag
5236
- return session.state if respond_to?(:session) && !session.nil?
5237
- return __method__
5053
+ if deadflag != 0
5054
+ log.debug "#{@player} is entering the death state" # TODO: Remove
5055
+ :death
5238
5056
  end
5239
5057
 
5240
- AfterLife() if deadflag != 2
5241
- return very__late__error if deadflag == 0
5058
+ return session.state if respond_to?(:session) && !session.nil?
5242
5059
 
5243
- OBIT_HEAD()
5244
- ScoreSub()
5245
- DisplayStatus()
5246
- AfterGameOver()
5247
- log.debug "#{@player} is entering the death state" # TODO: Remove
5248
- :death
5060
+ __method__
5249
5061
  end
5250
5062
 
5251
5063
  def stop
@@ -5286,18 +5098,48 @@ class InformLibrary < Inform::Ephemeral::Object
5286
5098
 
5287
5099
  @results = [nil, 0, nil, nil]; @meta = false
5288
5100
 
5289
- 2.times do # NotHeld
5290
- catch :ReParse do
5101
+ # i = 0
5102
+ # catch(:continue) do
5103
+ # rubocop: disable Lint/UnreachableLoop
5104
+ while @deadflag == 0 # everything happens in this loop
5105
+ return very__late__error()
5106
+ end
5107
+ # rubocop: enable Lint/UnreachableLoop
5108
+ # end
5109
+ AfterLife() if deadflag != 2
5110
+ return very__late__error if deadflag == 0
5111
+
5112
+ OBIT_HEAD()
5113
+ ScoreSub()
5114
+ DisplayStatus()
5115
+ AfterGameOver()
5116
+ end
5117
+
5118
+ def very__late__error
5119
+ if @score != @last_score
5120
+ NotifyTheScore() if @notify_mode == true
5121
+ @last_score = @score
5122
+ end
5123
+ late__error()
5124
+ rescue NoVerbRoutine, NotImplemented
5125
+ "Sorry, that action is not yet implemented."
5126
+ rescue NoMethodError => e
5127
+ parser_error e, "Sorry, there was a program error involving a missing method."
5128
+ rescue TypeError => e
5129
+ parser_error e, "Sorry, there was a program error involving the wrong type of object."
5130
+ rescue StandardError => e
5131
+ parser_error e, "Sorry, there was an unhandled program error."
5132
+ end
5133
+
5134
+ def late__error
5135
+
5291
5136
  # The Parser writes its results into @results and @meta,
5292
5137
  # a flag indicating a "meta-verb". This can only be set for
5293
5138
  # commands by the player, not for orders to others.
5294
5139
 
5295
- parse_input # inputobjs is now just results
5296
-
5297
- return if deadflag && !@meta
5298
- end
5299
- # ReParse
5140
+ parse_input
5300
5141
 
5142
+ # (Port note: inputobjs is now just results.)
5301
5143
  @action = @results[0]
5302
5144
 
5303
5145
  # --------------------------------------------------------------
@@ -5329,119 +5171,18 @@ class InformLibrary < Inform::Ephemeral::Object
5329
5171
  @multiflag = false; @onotheld_mode = @notheld_mode; @notheld_mode = false
5330
5172
  # For implicit taking and multiple object detection
5331
5173
 
5332
- # label :begin__action do
5333
- @inp1 = nil; @inp2 = nil; i = @results[1]
5334
- @inp1 = @results[2] if i >= 1
5335
- @inp2 = @results[3] if i >= 2
5336
-
5337
- # inp1 and inp2 hold: object numbers, or 0 for "multiple object",
5338
- # or 1 for "a number or dictionary address"
5339
-
5340
- if @inp1 == 1 then @noun = @special_number1 else @noun = @inp1 end
5341
- if @inp2 == 1
5342
- if @inp1 == 1 then @second = @special_number2
5343
- else @second = @special_number1
5344
- end
5345
- else @second = @inp2
5346
- end
5347
-
5348
- # --------------------------------------------------------------
5349
-
5350
- # TODO: FIXME
5351
- # act_requester = nil
5352
- #
5353
- # if @actor != @player
5354
- #
5355
- # # The player's "orders" property can refuse to allow conversation
5356
- # # here, by returning true. If not, the order is sent to the
5357
- # # other person's "orders" property. If that also returns false,
5358
- # # then: if it was a misunderstood command anyway, it is converted
5359
- # # to an Answer action (thus "floyd, grrr" ends up as
5360
- # # "say grrr to floyd"). If it was a good command, it is finally
5361
- # # offered to the Order: part of the other person's "life"
5362
- # # property, the old-fashioned way of dealing with conversation.
5363
- #
5364
- # j = RunRoutines(@player, :orders)
5365
- # unless j
5366
- # j = RunRoutines(@actor, :orders)
5367
- # unless j
5368
- # if action == :NotUnderstood
5369
- # @results[3] = @actor; @actor = @player; @action = :Answer
5370
- # #jump begin__action
5371
- # end
5372
- # if RunLife(@actor, :Order) == 0; L__M(:Order, 1, @actor); end
5373
- # end
5374
- # end
5375
- # return # jump turn__end
5376
- # end
5377
-
5378
- # --------------------------------------------------------------
5379
- # Generate the action...
5380
-
5381
- if (i == 0) ||
5382
- (i == 1 && @inp1 != 0) ||
5383
- (i == 2 && @inp1 != 0 && @inp2 != 0) ||
5384
- (i == @parameters && @inp1 != 0 && @inp2 != 0)
5385
- result = self.begin_action(@action, @noun, @second, 0)
5386
- # jump turn__end
5387
- else
5388
-
5389
- # ...unless a multiple object must be substituted. First:
5390
- # (a) check the multiple list isn't empty;
5391
- # (b) warn the player if it has been cut short because too long;
5392
- # (c) generate a sequence of actions from the list
5393
- # (stopping in the event of death or movement away).
5394
-
5395
- @multiflag = true
5396
- if @multiple_object.empty?
5397
- return L__M(:Miscellany, 2)
5398
- # jump late__error
5399
- end
5400
- if @toomany_flag
5401
- @toomany_flag = false
5402
- print L__M(:Miscellany, 1)
5403
- end
5404
- i = @location
5405
- for l in @multiple_object
5406
- return if deadflag
5407
- return L__M(:Miscellany, 51) if @location != i
5408
- PronounNotice(l)
5409
- @second = @results[3]
5410
- print "#{l}: "
5411
- if @inp1 == 0
5412
- @inp1 = l; zmsnp self.begin_action(@action, l, @second, 0); @inp1 = 0
5413
- else
5414
- @inp2 = l; zmsnp self.begin_action(@action, @noun, l, 0); @inp2 = 0
5415
- end
5416
- end
5417
- end
5418
-
5419
- # --------------------------------------------------------------
5420
-
5421
- # label :turn__end do
5174
+ begin__action()
5422
5175
 
5423
- # No time passes if either (i) the verb was meta, or
5424
- # (ii) we've only had the implicit take before the "real"
5425
- # action to follow.
5426
- if @notheld_mode
5427
- NoteObjectAcquisitions()
5428
- println(result, should_prompt: false)
5429
- return true if result == false
5430
- next
5431
- end
5432
- return result if @meta
5433
- if defined? SINGLE_PLAYER
5434
- self.end_turn_sequence() unless deadflag
5435
- end
5436
- println(result) if result.is_a?(String)
5437
- # return result if defined? result
5438
- return true
5439
- end
5440
- # 2.times do # NotHeld
5441
- rescue RepeatedOrder
5442
- return L__M(:Miscellany, REPEATED_ORDER)
5176
+ return session.state if respond_to?(:session) && !session.nil?
5177
+ true
5178
+ rescue RepeatedCommand
5179
+ return L__M(:Miscellany, REPEATED_COMMAND)
5443
5180
  rescue NothingToRepeat
5444
5181
  return L__M(:Miscellany, NOTHING_TO_REPEAT)
5182
+ rescue UnexpectedComma
5183
+ return L__M(:Miscellany, UNEXPECTED_COMMA)
5184
+ rescue MissingAddressee
5185
+ return L__M(:Miscellany, MISSING_ADDRESSEE)
5445
5186
  rescue InanimateAddressee
5446
5187
  return L__M(:Miscellany, CANT_TALK, noun)
5447
5188
  rescue SpeechImpediment
@@ -5519,46 +5260,125 @@ class InformLibrary < Inform::Ephemeral::Object
5519
5260
  return L__M(:Miscellany, NO_OBJECT)
5520
5261
  rescue CycleError
5521
5262
  return L__M(:Miscellany, TREE_ERROR)
5522
- rescue NoVerbRoutine, NotImplemented
5523
- return "Sorry, that action is not yet implemented."
5524
- rescue NoMethodError => e
5525
- log.error "Error parsing user command: #{e.class}: #{e}"
5526
- e.backtrace.each { |t| log.error t }
5527
- log.error "Parser was #{self.identity}"
5528
- log.error "Input was: #{@input}"
5529
- log.error "Actor was: #{@actor}"
5530
- log.error "Action was: #{@action}"
5531
- return "Sorry, something went wrong."
5532
- rescue TypeError => e
5533
- log.error "Error parsing user command: #{e.class}: #{e}"
5534
- e.backtrace.each { |t| log.error t }
5535
- log.error "Parser was #{self.identity}"
5536
- log.error "Input was: #{@input}"
5537
- log.error "Actor was: #{@actor}"
5538
- log.error "Action was: #{@action}"
5539
- return "Sorry, something went wrong."
5540
- rescue StandardError => e
5541
- log.error "Error parsing user command: #{e.class}: #{e}"
5542
- e.backtrace.each { |t| log.error t }
5543
- log.error "Parser was #{self.identity}"
5544
- log.error "Input was: #{@input}"
5545
- log.error "Actor was: #{@actor}"
5546
- log.error "Action was: #{@action}"
5547
- return "Sorry, something went seriously wrong."
5548
5263
  end
5549
- # def parse
5264
+ # def late__error
5265
+
5266
+ def begin__action()
5267
+ @inp1 = nil; @inp2 = nil; i = @results[1]
5268
+ @inp1 = @results[2] if i >= 1
5269
+ @inp2 = @results[3] if i >= 2
5270
+
5271
+ # inp1 and inp2 hold: object numbers, or 0 for "multiple object",
5272
+ # or 1 for "a number or dictionary address"
5273
+
5274
+ if @inp1 == 1 then @noun = @special_number1 else @noun = @inp1 end
5275
+ if @inp2 == 1
5276
+ if @inp1 == 1 then @second = @special_number2
5277
+ else @second = @special_number1
5278
+ end
5279
+ else @second = @inp2
5280
+ end
5281
+
5282
+ # --------------------------------------------------------------
5283
+
5284
+ if @actor != @player
5285
+
5286
+ # The player's "orders" property can refuse to allow conversation
5287
+ # here, by returning true. If not, the order is sent to the
5288
+ # other person's "orders" property. If that also returns false,
5289
+ # then: if it was a misunderstood command anyway, it is converted
5290
+ # to an Answer action (thus "floyd, grrr" ends up as
5291
+ # "say grrr to floyd"). If it was a good command, it is finally
5292
+ # offered to the Order: part of the other person's "life"
5293
+ # property, the old-fashioned way of dealing with conversation.
5294
+
5295
+ j = RunRoutines(@player, :orders)
5296
+ if j == false || j.nil?
5297
+ j = RunRoutines(@actor, :orders)
5298
+ if j == false || j.nil?
5299
+ if @action == :NotUnderstood
5300
+ @results[3] = @actor; @actor = @player; @action = :Answer
5301
+ return begin__action()
5302
+ end
5303
+ if RunLife(@actor, :Order) == 0 then L__M(:Order, 1, @actor); end
5304
+ end
5305
+ end
5306
+ return turn__end()
5307
+ end
5308
+
5309
+ # --------------------------------------------------------------
5310
+ # Generate the action...
5311
+
5312
+ if (i == 0) ||
5313
+ (i == 1 && @inp1 != 0) ||
5314
+ (i == 2 && @inp1 != 0 && @inp2 != 0) ||
5315
+ (i == @parameters && @inp1 != 0 && @inp2 != 0)
5316
+ result = self.begin_action(@action, @noun, @second, 0)
5317
+ print_routine_result(result)
5318
+ return turn__end(result)
5319
+ end
5320
+
5321
+ # ...unless a multiple object must be substituted. First:
5322
+ # (a) check the multiple list isn't empty;
5323
+ # (b) warn the player if it has been cut short because too long;
5324
+ # (c) generate a sequence of actions from the list
5325
+ # (stopping in the event of death or movement away).
5326
+
5327
+ @multiflag = true
5328
+ if @multiple_object.empty?
5329
+ return L__M(:Miscellany, 2)
5330
+ return late__error() # rubocop: disable Lint/UnreachableCode
5331
+ end
5332
+ if @toomany_flag
5333
+ @toomany_flag = false
5334
+ print L__M(:Miscellany, 1)
5335
+ end
5336
+ i = @location
5337
+ for l in @multiple_object
5338
+ return if deadflag != 0
5339
+ return L__M(:Miscellany, 51) if @location != i
5340
+ PronounNotice(l)
5341
+ @second = @results[3]
5342
+ print "#{l}: "
5343
+ if @inp1.nil?
5344
+ @inp1 = l; result = self.begin_action(@action, l, @second, 0); @inp1 = nil
5345
+ else
5346
+ @inp2 = l; result = self.begin_action(@action, @noun, l, 0); @inp2 = nil
5347
+ end
5348
+ print_routine_result(result)
5349
+ end
5350
+ turn__end(result)
5351
+ end
5352
+ # def begin__action
5353
+
5354
+ # --------------------------------------------------------------
5355
+
5356
+ def turn__end(result = nil)
5357
+
5358
+ # No time passes if either (i) the verb was meta, or
5359
+ # (ii) we've only had the implicit take before the "real"
5360
+ # action to follow.
5361
+ # if @notheld_mode then NoteObjectAcquisitions(); throw :continue; end
5362
+ if @notheld_mode then NoteObjectAcquisitions(); return true; end
5363
+ # throw :continue if @meta
5364
+ return true if @meta
5365
+ unless defined?(MULTI_PLAYER)
5366
+ self.end_turn_sequence() if deadflag == 0
5367
+ end
5368
+ result
5369
+ end
5550
5370
 
5551
5371
  def end_turn_sequence
5552
5372
  AdvanceWorldClock()
5553
- return if deadflag
5373
+ return if deadflag != 0
5554
5374
  RunTimersAndDaemons()
5555
- return if deadflag
5375
+ return if deadflag != 0
5556
5376
  RunEachTurnProperties()
5557
- return if deadflag
5377
+ return if deadflag != 0
5558
5378
  TimePasses()
5559
- return if deadflag
5379
+ return if deadflag != 0
5560
5380
  AdjustLight()
5561
- return if deadflag
5381
+ return if deadflag != 0
5562
5382
  NoteObjectAcquisitions()
5563
5383
  end
5564
5384
 
@@ -5575,7 +5395,8 @@ class InformLibrary < Inform::Ephemeral::Object
5575
5395
  end # DEBUG
5576
5396
 
5577
5397
  contextualize
5578
- if (!@verb.nil? && @verb.meta?) || BeforeRoutines(a) == false
5398
+ before = BeforeRoutines(a)
5399
+ if (!@verb.nil? && @verb.meta?) || before == false
5579
5400
  rv = ActionPrimitive(a)
5580
5401
  end
5581
5402
  return rv
@@ -5589,9 +5410,9 @@ class InformLibrary < Inform::Ephemeral::Object
5589
5410
  color :black_on_white
5590
5411
  bgcolor :white
5591
5412
  print "***"
5592
- L__M(:Miscellany, 3) if deadflag == true
5413
+ L__M(:Miscellany, 3) if deadflag != 0
5593
5414
  L__M(:Miscellany, 4) if deadflag == 2
5594
- if deadflag != false && deadflag != true && deadflag != 2
5415
+ if deadflag != 0 && deadflag != 1 && deadflag != 2
5595
5416
  print " "
5596
5417
  DeathMessage()
5597
5418
  print " "
@@ -5609,7 +5430,7 @@ class InformLibrary < Inform::Ephemeral::Object
5609
5430
  end
5610
5431
 
5611
5432
  def AdvanceWorldClock
5612
- return unless defined? SINGLE_PLAYER
5433
+ return if defined?(MULTI_PLAYER)
5613
5434
  @turns += 1
5614
5435
  if !@the_time.nil?
5615
5436
  if @time_rate >= 0 then @the_time = @the_time + @time_rate
@@ -5625,10 +5446,10 @@ class InformLibrary < Inform::Ephemeral::Object
5625
5446
  end
5626
5447
 
5627
5448
  def RunTimersAndDaemons
5628
- return if !defined?(SINGLE_PLAYER)
5449
+ return if defined?(MULTI_PLAYER)
5629
5450
  if defined?(DEBUG)
5630
5451
  if (@debug_flag & 4) != 0
5631
- @active_timers.each do |the_timer|
5452
+ @the_timers.each do |the_timer|
5632
5453
  next if the_timer.nil?
5633
5454
  print name(the_timer & !WORD_HIGHBIT) + ": "
5634
5455
  if (the_timer & WORD_HIGHBIT) != 0 then print "daemon"
@@ -5640,21 +5461,21 @@ class InformLibrary < Inform::Ephemeral::Object
5640
5461
  end
5641
5462
  end # DEBUG
5642
5463
 
5643
- @active_timers.each do |the_timer|
5464
+ @the_timers.each do |the_timer|
5644
5465
  break if deadflag != 0
5645
5466
  next if the_timer.nil?
5646
- if (the_timer & WORD_HIGHBIT) != 0 then RunRoutines(the_timer & !WORD_HIGHBIT, daemon)
5647
- elsif the_timer.time_left == 0
5467
+ if the_timer.respond_to?(:daemon) then RunRoutines(the_timer, :daemon)
5468
+ elsif (the_timer.&(:time_left) || nil) == 0
5648
5469
  StopTimer(the_timer)
5649
- RunRoutines(the_timer, time_out)
5470
+ RunRoutines(the_timer, :time_out)
5650
5471
  else
5651
- the_timer.time_left = the_timer.time_left - 1
5472
+ the_timer.&(:time_left, the_timer.|(:time_left, 0) - 1)
5652
5473
  end
5653
5474
  end
5654
5475
  end
5655
5476
 
5656
5477
  def RunEachTurnProperties
5657
- return unless defined? SINGLE_PLAYER
5478
+ return if defined?(MULTI_PLAYER)
5658
5479
  @scope_reason = scope_reason = EACH_TURN_REASON; @verb_word = nil
5659
5480
  DoScopeAction(@location, scope_reason)
5660
5481
  SearchScope(ScopeCeiling(@player), @player, nil)
@@ -5671,7 +5492,7 @@ class InformLibrary < Inform::Ephemeral::Object
5671
5492
  end
5672
5493
 
5673
5494
  def AfterGameOver
5674
- return unless defined? SINGLE_PLAYER
5495
+ return if defined?(MULTI_PLAYER)
5675
5496
  prompt
5676
5497
  @temp_global = 0
5677
5498
  i = @parse[1]
@@ -5700,13 +5521,15 @@ class InformLibrary < Inform::Ephemeral::Object
5700
5521
 
5701
5522
  def R_Process(a, i, j)
5702
5523
  s1 = @inp1; s2 = @inp2
5703
- @inp1 = i; @inp2 = j; InformLibrary.begin_action(a, i, j, 1)
5524
+ @inp1 = i; @inp2 = j; result = self.begin_action(a, i, j, 1)
5525
+ print_routine_result(result)
5526
+ ensure
5704
5527
  @inp1 = s1; @inp2 = s2
5705
5528
  end
5706
5529
 
5707
5530
  def NoteObjectAcquisitions
5708
- return unless defined? SINGLE_PLAYER
5709
- player.objectloop.each do |i|
5531
+ return if defined?(MULTI_PLAYER)
5532
+ player.objectloop do |i|
5710
5533
  if i.hasnt?(:moved)
5711
5534
  give i, :moved
5712
5535
  if i.has?(:scored)
@@ -5775,24 +5598,27 @@ class InformLibrary < Inform::Ephemeral::Object
5775
5598
 
5776
5599
  def ZRegion(z)
5777
5600
  return 0 if z.nil?
5778
- case z.class # Left over from Inform 5
5601
+ return 1 if z.object?
5602
+ case z # Left over from Inform 5
5779
5603
  # when NilClass then 0
5780
- when Object, Class then 1
5604
+ when Class then 1
5781
5605
  when Symbol then 2
5782
5606
  when String then 3
5783
5607
  end
5784
5608
  end
5785
5609
 
5786
- def PrintOrRun(obj, prop, flag = 0)
5610
+ def PrintOrRun(obj, prop, flag = false)
5787
5611
  si = obj.inflib; obj.inflib = inflib
5788
- return RunRoutines(obj, prop) if obj.properties.include?(prop)
5612
+ # return RunRoutines(obj, prop) if obj.properties.include?(prop)
5613
+ return RunRoutines(obj, prop) if obj.properties[prop].is_a?(Array)
5789
5614
  if obj.respond_to?(prop) && !obj.method(prop).nil?
5790
5615
  result = obj.send(prop)
5791
5616
  if result.is_a?(Class) || result.nil? || obj.linked?(prop)
5792
5617
  return RunTimeError(2, obj, prop)
5793
5618
  elsif result.is_a?(String)
5794
5619
  print result.to_s
5795
- new_line if flag == 0
5620
+ raise "Why is flag true?" if flag == true
5621
+ new_line if flag == false
5796
5622
  return true
5797
5623
  else
5798
5624
  return result
@@ -5804,12 +5630,12 @@ class InformLibrary < Inform::Ephemeral::Object
5804
5630
  obj.inflib = si
5805
5631
  end
5806
5632
 
5807
- def PrintOrRunVar(var, flag)
5633
+ def PrintOrRunVar(var, flag = false)
5808
5634
  if var.respond_to?(:name)
5809
5635
  print var.name
5810
5636
  elsif var.is_a?(String)
5811
5637
  print var
5812
- new_line if flag == 0
5638
+ new_line if flag == false
5813
5639
  elsif var.respond_to?(:call)
5814
5640
  return var.call
5815
5641
  else
@@ -5819,15 +5645,77 @@ class InformLibrary < Inform::Ephemeral::Object
5819
5645
  end
5820
5646
 
5821
5647
  def ValueOrRun(obj, prop)
5822
- value = obj.respond_to?(prop) ? obj.send(prop) : RunRoutines(obj, prop)
5823
- return value.number? ? value.to_i : value
5648
+ if obj.properties.key?(prop)
5649
+ value = obj.&(prop)
5650
+ return value.number? ? value.to_i : value
5651
+ elsif obj.respond_to?(prop)
5652
+ value = obj.send(prop)
5653
+ return value.number? ? value.to_i : value
5654
+ end
5655
+ return RunRoutines(obj, prop)
5824
5656
  end
5825
5657
 
5826
5658
  def RunRoutines(obj, prop)
5827
5659
  obj = @real_location if obj == thedark && prop.notin?(:initial, :short_name, :description, :life)
5828
5660
  return false if prop.nil?
5829
- val = obj.&(prop); return val unless val.nil?
5830
- return reacts_to?(obj, prop)
5661
+
5662
+ value = obj.&(prop)
5663
+ return print_routine_result(value) unless value.nil?
5664
+
5665
+ reacts_to?(obj, prop)
5666
+ end
5667
+
5668
+ # ----------------------------------------------------------------------------
5669
+
5670
+ def StartTimer(obj, timer)
5671
+ return false if @the_timers.include?(obj)
5672
+ if @the_timers.length >= MAX_TIMERS
5673
+ RunTimeError(4)
5674
+ return
5675
+ end
5676
+ if obj.&(:time_left) == 0
5677
+ RunTimeError(5,obj)
5678
+ return
5679
+ end
5680
+ @the_timers << obj
5681
+ obj.&(:time_left, timer)
5682
+ end
5683
+
5684
+ def StopTimer(obj)
5685
+ return false unless @the_timers.include?(obj)
5686
+ if obj.&(:time_left) == 0
5687
+ RunTimeError(5,obj)
5688
+ return
5689
+ end
5690
+ @the_timers.delete(obj)
5691
+ obj.&(:time_left, 0)
5692
+ end
5693
+
5694
+ def StartDaemon(obj)
5695
+ return false if @the_timers.include?(obj)
5696
+ RunTimeError(4) if @the_timers.length >= MAX_TIMERS
5697
+ @the_timers << obj
5698
+ end
5699
+
5700
+ def StopDaemon(obj)
5701
+ return false unless @the_timers.include?(obj)
5702
+ @the_timers.delete(obj)
5703
+ end
5704
+
5705
+ # ----------------------------------------------------------------------------
5706
+
5707
+ def DisplayStatus
5708
+ if @sys_statusline_flag == 0 then @sline1 = @score; @sline2 = @turns
5709
+ else @sline1 = @the_time/60; @sline2 = @the_time%60; end
5710
+ end
5711
+
5712
+ def SetTime(t, s)
5713
+ @the_time = t; @time_rate = s; @time_step = 0
5714
+ @time_step = 0 - s if s < 0
5715
+ end
5716
+
5717
+ def NotifyTheScore
5718
+ print "\n["; L__M(:Miscellany, 50, @score - @last_score); print ".]\n"
5831
5719
  end
5832
5720
  # ----------------------------------------------------------------------------
5833
5721
  end