├── test ├── compiler_test_files │ ├── 0 │ │ └── 0.php │ ├── 1 │ │ └── 1.php │ ├── 2 │ │ └── 200.php │ ├── 3 │ │ └── 300.php │ ├── 4 │ │ └── 400.php │ ├── 5 │ │ └── 500.php │ ├── 6 │ │ └── 600.php │ ├── 7 │ │ └── 700.php │ ├── 8 │ │ └── 8.php │ ├── 9 │ │ └── 9.php │ ├── A125 │ │ ├── A12501.adv │ │ ├── A12502.adv │ │ ├── A12503.adv │ │ └── A12504.adv │ ├── last_compile.txt │ └── versions │ │ ├── 1 │ │ └── 1_-675873966.php │ │ ├── 8 │ │ └── 8_-727713902.php │ │ └── 9 │ │ └── 9_-961525309.php ├── commands │ ├── test_comment.rb │ ├── test_unreachable.rb │ ├── test_end_paragraph.rb │ ├── test_add_timejump.rb │ ├── test_unfinished_story.rb │ ├── test_activate_saving.rb │ ├── test_add_kongerupi.rb │ ├── test_count_visits.rb │ ├── test_reinstate_alternatives.rb │ ├── test_add_flag.rb │ ├── test_remove_flag.rb │ ├── test_end_subpage.rb │ ├── test_register_delayed_call.rb │ ├── test_increase_bodycount.rb │ ├── test_remove_alternatives.rb │ ├── test_goto_room.rb │ ├── test_save_command.rb │ ├── test_sentence_with_variable.rb │ ├── test_command_bundle.rb │ ├── test_add_deed.rb │ ├── test_conditional_command.rb │ ├── test_alternatives_from_other.rb │ ├── test_run_saved_command.rb │ ├── test_conditional_goto.rb │ ├── test_procedure_call.rb │ ├── test_summarize_deeds.rb │ ├── test_plain_text.rb │ ├── test_assign_to_variable.rb │ └── test_alternatives.rb ├── test_room.rb ├── test_change_tracker.rb ├── test_page.rb ├── ts_all_tests.rb ├── test_room_loader.rb ├── test_hashcode_keeper.rb ├── test_changes_only_compiler.rb ├── test_conditional.rb ├── test_enterpreter.rb ├── test_cond_tree.rb └── test_timed_jump_coordinator.rb ├── .autotest ├── bin ├── compile_changed.rb ├── integration_test.rb └── all.rb ├── lib ├── commands │ ├── comment.rb │ ├── unreachable.rb │ ├── end_paragraph.rb │ ├── end_subpage.rb │ ├── add_timejump.rb │ ├── add_flag.rb │ ├── add_kongerupi.rb │ ├── plain_text.rb │ ├── reinstate_alternatives.rb │ ├── activate_saving.rb │ ├── increase_bodycount.rb │ ├── remove_flag.rb │ ├── register_delayed_call.rb │ ├── remove_alternatives.rb │ ├── save_command.rb │ ├── unfinished_story.rb │ ├── count_visits.rb │ ├── add_deed.rb │ ├── alternatives_from_other.rb │ ├── goto_room.rb │ ├── if_statement.rb │ ├── conditional_command.rb │ ├── run_saved_command.rb │ ├── conditional_goto.rb │ ├── sentence_with_variable.rb │ ├── assign_to_variable.rb │ ├── procedure_call.rb │ ├── summarize_deeds.rb │ └── alternatives.rb ├── room.rb ├── change_tracker.rb ├── room_loader.rb ├── page.rb ├── hashcode_keeper.rb ├── changes_only_compiler.rb ├── enterpreter.rb ├── cond_tree.rb ├── timed_jump_coordinator.rb └── conditional.rb ├── todo.org ├── notat.txt └── resources └── hashcodefile_original.txt /test/compiler_test_files/0/0.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.autotest: -------------------------------------------------------------------------------- 1 | require 'redgreen/autotest' -------------------------------------------------------------------------------- /test/compiler_test_files/2/200.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/compiler_test_files/3/300.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/compiler_test_files/4/400.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/compiler_test_files/5/500.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/compiler_test_files/6/600.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/compiler_test_files/7/700.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/compiler_test_files/A125/A12501.adv: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/compiler_test_files/A125/A12502.adv: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/compiler_test_files/A125/A12503.adv: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/compiler_test_files/A125/A12504.adv: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/compiler_test_files/last_compile.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /bin/compile_changed.rb: -------------------------------------------------------------------------------- 1 | require 'changes_only_compiler' 2 | 3 | abort "Usage: compile_changed.rb [rooms folder]" unless ARGV.size == 1 4 | 5 | always_compile = [4165] 6 | ChangesOnlyCompiler.new(ARGV.shift, always_compile).compile! -------------------------------------------------------------------------------- /lib/commands/comment.rb: -------------------------------------------------------------------------------- 1 | class Comment 2 | 3 | def self.parse?(line, room = nil, enterpreter = nil) 4 | if line[0..1] === ";;" 5 | new 6 | else 7 | false 8 | end 9 | end 10 | 11 | def code 12 | [] 13 | end 14 | 15 | end 16 | -------------------------------------------------------------------------------- /lib/commands/unreachable.rb: -------------------------------------------------------------------------------- 1 | class Unreachable 2 | 3 | def self.parse?(line, room = nil, enterpreter = nil) 4 | if line === "$%&" 5 | new 6 | else 7 | false 8 | end 9 | end 10 | 11 | def code 12 | [] 13 | end 14 | 15 | end 16 | -------------------------------------------------------------------------------- /lib/commands/end_paragraph.rb: -------------------------------------------------------------------------------- 1 | class EndParagraph 2 | def self.parse?(line, room = nil, enterpreter = nil) 3 | if line === "" 4 | new 5 | else 6 | false 7 | end 8 | end 9 | 10 | def code 11 | "$this->receiver->end_paragraph();" 12 | end 13 | end -------------------------------------------------------------------------------- /lib/commands/end_subpage.rb: -------------------------------------------------------------------------------- 1 | class DramaticPause 2 | def self.parse?(line, room = nil, enterpreter = nil) 3 | if line[0..2] == "---" 4 | new 5 | else 6 | false 7 | end 8 | end 9 | 10 | def code 11 | "$this->receiver->end_subpage();" 12 | end 13 | 14 | end 15 | -------------------------------------------------------------------------------- /lib/room.rb: -------------------------------------------------------------------------------- 1 | module Room 2 | attr_accessor :number 3 | 4 | def current 5 | self[index] 6 | end 7 | 8 | def next 9 | @index = index + 1 10 | current 11 | end 12 | 13 | def peek 14 | self[index+1] 15 | end 16 | 17 | def index 18 | @index ||= 0 19 | end 20 | end -------------------------------------------------------------------------------- /test/commands/test_comment.rb: -------------------------------------------------------------------------------- 1 | require 'test/unit' 2 | require 'commands/comment' 3 | 4 | class CommandsCommentTestCase < Test::Unit::TestCase 5 | 6 | def setup 7 | @story = Comment.parse?(';; min kommentar her') 8 | end 9 | 10 | def test_should_build_no_code 11 | assert_equal([], @story.code) 12 | end 13 | 14 | end 15 | -------------------------------------------------------------------------------- /test/commands/test_unreachable.rb: -------------------------------------------------------------------------------- 1 | require 'test/unit' 2 | require 'commands/unreachable' 3 | 4 | class CommandsUnreachableTestCase < Test::Unit::TestCase 5 | 6 | def setup 7 | @story = Unreachable.parse?('$%&') 8 | end 9 | 10 | def test_should_build_no_code 11 | assert_equal([], @story.code) 12 | end 13 | 14 | end 15 | -------------------------------------------------------------------------------- /bin/integration_test.rb: -------------------------------------------------------------------------------- 1 | require 'room_loader' 2 | require 'page' 3 | 4 | abort "Usage: integration_test.rb [rooms folder] [room number]" if ARGV.size < 2 5 | loader = RoomLoader.new ARGV.shift 6 | coordinator = TimedJumpCoordinator.new loader 7 | 8 | while number = ARGV.shift 9 | puts Page.new(number, loader, coordinator).code 10 | end 11 | -------------------------------------------------------------------------------- /test/commands/test_end_paragraph.rb: -------------------------------------------------------------------------------- 1 | require 'test/unit' 2 | require 'commands/end_paragraph' 3 | 4 | class CommandsEndParagraphTestCase < Test::Unit::TestCase 5 | 6 | def setup 7 | @p = EndParagraph.parse?("") 8 | end 9 | 10 | def test_should_build_code 11 | assert_equal("$this->receiver->end_paragraph();", @p.code) 12 | end 13 | 14 | end -------------------------------------------------------------------------------- /lib/commands/add_timejump.rb: -------------------------------------------------------------------------------- 1 | class AddTimejump 2 | def initialize(num) 3 | @num = num 4 | end 5 | 6 | def self.parse?(line, room = nil, enterpreter = nil) 7 | if line.sub!(/^:tidshopp /, "") 8 | new(line.to_i) 9 | else 10 | false 11 | end 12 | end 13 | 14 | def code 15 | "$this->receiver->add_timejumps(#{@num});" 16 | end 17 | 18 | end 19 | -------------------------------------------------------------------------------- /test/commands/test_add_timejump.rb: -------------------------------------------------------------------------------- 1 | require 'test/unit' 2 | require 'commands/add_timejump' 3 | 4 | class CommandsAddTimejumpTestCase < Test::Unit::TestCase 5 | 6 | def setup 7 | @command = AddTimejump.parse?(":tidshopp +3") 8 | end 9 | 10 | def test_should_build_code 11 | assert_equal('$this->receiver->add_timejumps(3);', @command.code) 12 | end 13 | 14 | end 15 | -------------------------------------------------------------------------------- /lib/commands/add_flag.rb: -------------------------------------------------------------------------------- 1 | class AddFlag 2 | def initialize(flag) 3 | @flag = flag 4 | end 5 | 6 | def self.parse?(line, room = nil, enterpreter = nil) 7 | if line.sub!(/^\+\+ /, "") 8 | new(line) 9 | else 10 | false 11 | end 12 | end 13 | 14 | def code 15 | "if ($this->con(!$this->receiver->has_flag(\"#{@flag}\"))) { $this->receiver->add_flag(\"#{@flag}\"); }" 16 | end 17 | 18 | end 19 | -------------------------------------------------------------------------------- /lib/commands/add_kongerupi.rb: -------------------------------------------------------------------------------- 1 | class AddKongerupi 2 | 3 | def self.parse?(line, room = nil, enterpreter = nil) 4 | if line[0..5] === ":rupi " 5 | AddKongerupi.new line[6..-1].to_i 6 | else 7 | false 8 | end 9 | end 10 | 11 | def initialize(amount) 12 | @amount = amount 13 | end 14 | 15 | def code 16 | "$this->receiver->add_to_detail(\"\\$_KONGERUPI\", #{@amount});" 17 | end 18 | 19 | end 20 | -------------------------------------------------------------------------------- /test/commands/test_unfinished_story.rb: -------------------------------------------------------------------------------- 1 | require 'test/unit' 2 | require 'commands/unfinished_story' 3 | 4 | class CommandsUnfinishedStoryTestCase < Test::Unit::TestCase 5 | 6 | def setup 7 | @story = UnfinishedStory.parse?(':uferdig => Jan "Rudolf" Hansen') 8 | end 9 | 10 | def test_should_build_code 11 | assert_equal('$this->receiver->unfinished_story("Jan \"Rudolf\" Hansen");', @story.code) 12 | end 13 | 14 | end 15 | -------------------------------------------------------------------------------- /lib/commands/plain_text.rb: -------------------------------------------------------------------------------- 1 | class PlainText 2 | def initialize(text) 3 | @text = text 4 | end 5 | 6 | def self.parse?(line, room = nil, enterpreter = nil) 7 | new(line) 8 | end 9 | 10 | def code 11 | "$this->receiver->write(\"#{escape(@text)}\");" 12 | end 13 | 14 | def escape(text) 15 | text.gsub('\\', ':BS::BS::BS::BS::BS:').gsub(':BS:', '\\').gsub('"', '\"').gsub("", '".$this->receiver->get_nickname()."') 16 | end 17 | 18 | end 19 | -------------------------------------------------------------------------------- /test/commands/test_activate_saving.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | require 'test/unit' 3 | require 'commands/activate_saving' 4 | 5 | class CommandsActivateSavingTestCase < Test::Unit::TestCase 6 | 7 | def setup 8 | @save = ActivateSaving.parse?(":save => På torget") 9 | end 10 | 11 | def test_should_build_code 12 | assert_equal(['$this->receiver->set_detail("\$_LAGRINGSPLASS", "På torget");', '$this->receiver->saveable();'], @save.code) 13 | end 14 | 15 | end 16 | -------------------------------------------------------------------------------- /lib/commands/reinstate_alternatives.rb: -------------------------------------------------------------------------------- 1 | class ReinstateAlternatives 2 | 3 | def self.parse?(line, room = nil, enterpreter = nil) 4 | if line =~ /^\*(\d+)$/ 5 | new $1 6 | else 7 | false 8 | end 9 | end 10 | 11 | def initialize(room_number) 12 | @room_number = room_number 13 | end 14 | 15 | def code 16 | "if ($this->con($this->receiver->has_flag(\"nr#{@room_number}\"))) { $this->receiver->remove_flag(\"nr#{@room_number}\"); }" 17 | end 18 | 19 | end -------------------------------------------------------------------------------- /lib/commands/activate_saving.rb: -------------------------------------------------------------------------------- 1 | class ActivateSaving 2 | 3 | def self.parse?(line, room = nil, enterpreter = nil) 4 | if line[0..8] === ":save => " 5 | new line[9..-1].lstrip 6 | else 7 | false 8 | end 9 | end 10 | 11 | def initialize(location) 12 | @location = location 13 | end 14 | 15 | def code 16 | [ 17 | '$this->receiver->set_detail("\$_LAGRINGSPLASS", "' + @location + '");', 18 | '$this->receiver->saveable();' 19 | ] 20 | end 21 | 22 | end 23 | -------------------------------------------------------------------------------- /test/commands/test_add_kongerupi.rb: -------------------------------------------------------------------------------- 1 | require 'test/unit' 2 | require 'commands/add_kongerupi' 3 | 4 | class CommandsAddKongerupiTestCase < Test::Unit::TestCase 5 | 6 | def setup 7 | @add = AddKongerupi.parse?(":rupi +2") 8 | @sub = AddKongerupi.parse?(":rupi -23") 9 | end 10 | 11 | def test_should_build_code 12 | assert_equal('$this->receiver->add_to_detail("\$_KONGERUPI", 2);', @add.code) 13 | assert_equal('$this->receiver->add_to_detail("\$_KONGERUPI", -23);', @sub.code) 14 | end 15 | 16 | end 17 | -------------------------------------------------------------------------------- /lib/commands/increase_bodycount.rb: -------------------------------------------------------------------------------- 1 | class IncreaseBodycount 2 | 3 | def self.parse?(line, room, enterpreter = nil) 4 | if line.sub!(/^:drap /, "") 5 | new(line.to_i) 6 | else 7 | false 8 | end 9 | end 10 | 11 | def initialize(count) 12 | raise "Bodycount was increased by '#{count}', which is not a valid bodycount increase." if count.to_i == 0 13 | @count = count 14 | end 15 | 16 | def code 17 | "$this->receiver->add_to_detail(\"\\$_BODYCOUNT\", #{@count});" 18 | end 19 | 20 | end 21 | -------------------------------------------------------------------------------- /lib/commands/remove_flag.rb: -------------------------------------------------------------------------------- 1 | class RemoveFlag 2 | def initialize(flag) 3 | @flag = flag 4 | end 5 | 6 | def self.parse?(line, room = nil, enterpreter = nil) 7 | if line[0..2] == "-- " 8 | new line[3..-1] 9 | else 10 | false 11 | end 12 | end 13 | 14 | def code 15 | "if ($this->con($this->receiver->has_flag(\"#{escape(@flag)}\"))) { $this->receiver->remove_flag(\"#{escape(@flag)}\"); }" 16 | end 17 | 18 | private 19 | 20 | def escape(var) 21 | var.gsub('$', '\$') 22 | end 23 | 24 | 25 | end 26 | -------------------------------------------------------------------------------- /lib/commands/register_delayed_call.rb: -------------------------------------------------------------------------------- 1 | class RegisterDelayedCall 2 | 3 | def self.parse?(line, room, enterpreter = nil) 4 | if line =~ /^\(@\)(\d+) om (\d+) ...$/ 5 | new $1, $2 6 | else 7 | false 8 | end 9 | end 10 | 11 | def initialize(destination, delay) 12 | @destination, @delay = destination, delay 13 | end 14 | 15 | def code 16 | [ 17 | "$this->receiver->set_detail('$_CALL_DELAY', #{@delay});", 18 | "$this->receiver->set_detail('$_DELAYED_CALL', #{@destination});" 19 | ] 20 | end 21 | 22 | end 23 | -------------------------------------------------------------------------------- /test/commands/test_count_visits.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | require 'test/unit' 3 | require 'commands/count_visits' 4 | require 'room' 5 | 6 | class CommandsCountVisitsTestCase < Test::Unit::TestCase 7 | 8 | def test_should_count_limited_visits 9 | room = [':besøk ++ (max 4)'].extend(Room) 10 | room.number = 7 11 | command = CountVisits.parse?(room.current, room) 12 | assert_equal('if ($this->con($this->receiver->get_detail("\$_VISITS_TO_7") < 4)) { $this->receiver->add_to_detail("\$_VISITS_TO_7", 1); }', command.code) 13 | end 14 | 15 | end 16 | -------------------------------------------------------------------------------- /lib/commands/remove_alternatives.rb: -------------------------------------------------------------------------------- 1 | class RemoveAlternatives 2 | def self.parse?(line, room = nil, enterpreter = nil) 3 | if line =~ /^#(\d+)$/ 4 | RemoveAlternatives.new $1 5 | elsif line === "#!" 6 | RemoveAlternatives.new room.number 7 | else 8 | false 9 | end 10 | end 11 | 12 | def initialize(room_number) 13 | @room_number = room_number 14 | end 15 | 16 | def code 17 | "if ($this->con(!$this->receiver->has_flag(\"nr#{@room_number}\"))) { $this->receiver->add_flag(\"nr#{@room_number}\"); }" 18 | end 19 | 20 | end 21 | -------------------------------------------------------------------------------- /test/commands/test_reinstate_alternatives.rb: -------------------------------------------------------------------------------- 1 | require 'test/unit' 2 | require 'commands/reinstate_alternatives' 3 | 4 | class CommandsReinstateAlternativesTestCase < Test::Unit::TestCase 5 | 6 | def test_should_expose_contents_and_build_code 7 | command = ReinstateAlternatives.parse?("*872") 8 | assert_equal('if ($this->con($this->receiver->has_flag("nr872"))) { $this->receiver->remove_flag("nr872"); }', command.code) 9 | end 10 | 11 | def test_should_let_non_numeric_be 12 | assert !ReinstateAlternatives.parse?("*wow*") 13 | end 14 | 15 | 16 | end 17 | -------------------------------------------------------------------------------- /lib/commands/save_command.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | class SaveCommand 3 | def initialize(name, contents, hashcode_keeper) 4 | @name, @contents, @contents_hash = name, contents, hashcode_keeper.code_for(name, contents) 5 | end 6 | 7 | def self.parse?(line, room = nil, enterpreter = nil) 8 | if line =~ /^([A-ZÆØÅ_.-]+) => (.+)$/ 9 | new $1, $2, enterpreter.hashcode_keeper 10 | else 11 | false 12 | end 13 | end 14 | 15 | def code 16 | "$this->receiver->set_detail(\"]C[#{@name}\", \"#{@contents_hash}\");" 17 | end 18 | 19 | end 20 | -------------------------------------------------------------------------------- /lib/commands/unfinished_story.rb: -------------------------------------------------------------------------------- 1 | class UnfinishedStory 2 | 3 | def self.parse?(line, room = nil, enterpreter = nil) 4 | if line[0..11] === ":uferdig => " 5 | new line[12..-1].lstrip 6 | else 7 | false 8 | end 9 | end 10 | 11 | def initialize(description) 12 | @description = description 13 | end 14 | 15 | def code 16 | "$this->receiver->unfinished_story(\"#{escape(@description)}\");" 17 | end 18 | 19 | def escape(text) 20 | text.gsub('\\', ':BS::BS::BS::BS::BS:').gsub(':BS:', '\\').gsub('"', '\"').gsub("", '".$this->receiver->get_nickname()."') 21 | end 22 | 23 | 24 | end 25 | -------------------------------------------------------------------------------- /test/commands/test_add_flag.rb: -------------------------------------------------------------------------------- 1 | require 'test/unit' 2 | require 'commands/add_flag' 3 | 4 | class CommandsAddFlagTestCase < Test::Unit::TestCase 5 | 6 | def setup 7 | @command = AddFlag.parse?("++ FLAG") 8 | end 9 | 10 | def test_should_only_parse_adding_of_flags 11 | assert(!AddFlag.parse?("some text"), "AddFlag should not parse plain text.") 12 | assert(!AddFlag.parse?("-- FLAG"), "AddFlag not parse )(FLAG).") 13 | end 14 | 15 | def test_should_build_code 16 | assert_equal('if ($this->con(!$this->receiver->has_flag("FLAG"))) { $this->receiver->add_flag("FLAG"); }', @command.code) 17 | end 18 | 19 | end 20 | -------------------------------------------------------------------------------- /test/commands/test_remove_flag.rb: -------------------------------------------------------------------------------- 1 | require 'test/unit' 2 | require 'commands/remove_flag' 3 | 4 | class CommandsRemoveFlagTestCase < Test::Unit::TestCase 5 | 6 | def test_should_remove_normal_flags 7 | command = RemoveFlag.parse?("-- FLAG") 8 | assert_equal('if ($this->con($this->receiver->has_flag("FLAG"))) { $this->receiver->remove_flag("FLAG"); }', command.code) 9 | end 10 | 11 | def test_should_remove_value_flags 12 | command = RemoveFlag.parse?("-- $VAL") 13 | assert_equal('if ($this->con($this->receiver->has_flag("\$VAL"))) { $this->receiver->remove_flag("\$VAL"); }', command.code) 14 | end 15 | 16 | end 17 | -------------------------------------------------------------------------------- /test/commands/test_end_subpage.rb: -------------------------------------------------------------------------------- 1 | require 'test/unit' 2 | require 'commands/end_subpage' 3 | 4 | class CommandsDramaticPauseTestCase < Test::Unit::TestCase 5 | 6 | def setup 7 | @command1 = DramaticPause.parse?("--") 8 | @command2 = DramaticPause.parse?("---") 9 | @command3 = DramaticPause.parse?("-------") 10 | end 11 | 12 | def test_should_not_end_subpage_with_few 13 | assert(!@command1) 14 | end 15 | 16 | def test_should_end_the_subpage 17 | assert_equal('$this->receiver->end_subpage();', @command2.code) 18 | assert_equal('$this->receiver->end_subpage();', @command3.code) 19 | end 20 | 21 | end 22 | -------------------------------------------------------------------------------- /test/commands/test_register_delayed_call.rb: -------------------------------------------------------------------------------- 1 | require 'test/unit' 2 | require 'commands/register_delayed_call' 3 | require 'room' 4 | 5 | class CommandsRegisterDelayedCallTestCase < Test::Unit::TestCase 6 | 7 | def setup 8 | room = ['(@)177 om 3 ...'].extend(Room) 9 | @command = RegisterDelayedCall.parse?(room.first, room) 10 | end 11 | 12 | def test_should_generate_code 13 | assert_equal(expected_code, @command.code) 14 | end 15 | 16 | def expected_code 17 | [ 18 | "$this->receiver->set_detail('$_CALL_DELAY', 3);", 19 | "$this->receiver->set_detail('$_DELAYED_CALL', 177);" 20 | ] 21 | end 22 | 23 | end 24 | -------------------------------------------------------------------------------- /lib/commands/count_visits.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | class CountVisits 3 | 4 | def self.parse?(line, room, enterpreter = nil) 5 | if line =~ /^:besøk \+\+ \(max (\d+)\)$/ 6 | new room.number, $1 7 | else 8 | false 9 | end 10 | end 11 | 12 | def initialize(room_number, limit) 13 | @room_number, @limit = room_number, limit 14 | end 15 | 16 | def code 17 | wrap_in_if("$this->receiver->add_to_detail(\"\\$_VISITS_TO_#{@room_number}\", 1);") 18 | end 19 | 20 | private 21 | 22 | def wrap_in_if(statement) 23 | "if ($this->con($this->receiver->get_detail(\"\\$_VISITS_TO_#{@room_number}\") < #{@limit})) { #{statement} }" 24 | end 25 | 26 | end 27 | -------------------------------------------------------------------------------- /test/test_room.rb: -------------------------------------------------------------------------------- 1 | require 'test/unit' 2 | require 'room' 3 | 4 | class RoomTestCase < Test::Unit::TestCase 5 | 6 | def setup 7 | @room = ["line1", "line2", "line3"].extend(Room) 8 | end 9 | 10 | def test_should_show_current_line 11 | assert_equal("line1", @room.current) 12 | end 13 | 14 | def test_should_jump_to_next_line 15 | assert_equal("line2", @room.next) 16 | assert_equal("line2", @room.current) 17 | end 18 | 19 | def test_should_allow_peeking_at_next 20 | assert_equal("line2", @room.peek) 21 | assert_equal("line1", @room.current) 22 | end 23 | 24 | def test_should_hold_room_number 25 | @room.number = 23 26 | assert_equal(23, @room.number) 27 | end 28 | 29 | end -------------------------------------------------------------------------------- /lib/commands/add_deed.rb: -------------------------------------------------------------------------------- 1 | class AddDeed 2 | def self.parse?(line, room, enterpreter = nil) 3 | if line.start_with?(":slem => ") 4 | AddDeed.new("EVIL", line[9..-1]) 5 | elsif line.start_with?(":snill => ") 6 | AddDeed.new("GOOD", line[10..-1]) 7 | else 8 | false 9 | end 10 | end 11 | 12 | def initialize(type, description) 13 | @type, @description = type, description 14 | end 15 | 16 | def code 17 | flag = "_#{@type}_DEED_#{@description.hash}" 18 | [ 19 | "if ($this->con(!$this->receiver->has_flag(\"#{flag}\"))) {", 20 | " $this->receiver->add_deed(\"#{@type}\");", 21 | " $this->receiver->add_flag(\"#{flag}\");", 22 | "}" 23 | ] 24 | end 25 | 26 | end 27 | -------------------------------------------------------------------------------- /test/commands/test_increase_bodycount.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | require 'test/unit' 3 | require 'commands/increase_bodycount' 4 | require 'room' 5 | 6 | class CommandsIncreaseBodycountTestCase < Test::Unit::TestCase 7 | 8 | def setup 9 | room = [':drap +3'].extend(Room) 10 | @command = IncreaseBodycount.parse?(room.current, room) 11 | end 12 | 13 | def test_should_generate_code 14 | assert_equal('$this->receiver->add_to_detail("\$_BODYCOUNT", 3);', @command.code) 15 | end 16 | 17 | def test_should_complain_about_illegal_values 18 | room = [':drap Du får en bodycount men det bør helst være et tall her også.'].extend(Room) 19 | assert_raise(RuntimeError) { IncreaseBodycount.parse?(room.current, room) } 20 | end 21 | 22 | end 23 | -------------------------------------------------------------------------------- /test/commands/test_remove_alternatives.rb: -------------------------------------------------------------------------------- 1 | require 'test/unit' 2 | require 'room' 3 | require 'commands/remove_alternatives' 4 | 5 | class CommandsRemoveAlternativesTestCase < Test::Unit::TestCase 6 | 7 | def test_should_remove_alternatives 8 | command = RemoveAlternatives.parse?("#23") 9 | assert_equal('if ($this->con(!$this->receiver->has_flag("nr23"))) { $this->receiver->add_flag("nr23"); }', command.code) 10 | end 11 | 12 | def test_should_remove_alternatives_to_current_room 13 | room = ["#!"].extend(Room) 14 | room.number = 17 15 | command = RemoveAlternatives.parse?("#!", room) 16 | assert_equal('if ($this->con(!$this->receiver->has_flag("nr17"))) { $this->receiver->add_flag("nr17"); }', command.code) 17 | end 18 | 19 | end 20 | -------------------------------------------------------------------------------- /lib/commands/alternatives_from_other.rb: -------------------------------------------------------------------------------- 1 | class AlternativesFromOther 2 | def initialize(room_number, alternatives) 3 | @room_number, @alternatives = room_number, alternatives 4 | end 5 | 6 | def self.parse?(line, room, enterpreter) 7 | if line[0..2] == "{@}" 8 | room_number = line[3..-1].to_i 9 | other = enterpreter.room_loader.get(room_number) 10 | other.next until other.current == "=" or other.current == nil 11 | raise "Other room has no alternatives" if other.current == nil 12 | new room_number, enterpreter.current_command(other) 13 | else 14 | false 15 | end 16 | end 17 | 18 | def code 19 | ["$this->receiver->room_number_changed(#{@room_number});", @alternatives.code].flatten 20 | end 21 | 22 | end 23 | -------------------------------------------------------------------------------- /lib/commands/goto_room.rb: -------------------------------------------------------------------------------- 1 | class GotoRoom 2 | def initialize(room_number) 3 | @room_number = room_number 4 | end 5 | 6 | def self.parse?(line, room, enterpreter) 7 | if line[0..0] == "@" 8 | room_number = line[1..-1].to_i 9 | unless enterpreter.has_function("execute_room_#{room_number}") 10 | enterpreter.register_function("execute_room_#{room_number}") 11 | commands = enterpreter.parse(room_number) 12 | enterpreter.define_function("execute_room_#{room_number}", commands) 13 | end 14 | new room_number 15 | else 16 | false 17 | end 18 | end 19 | 20 | def code 21 | ["$this->receiver->room_number_changed(#{@room_number});", 22 | "$this->execute_room_#{@room_number}();", 23 | 'return;'].flatten 24 | end 25 | 26 | end -------------------------------------------------------------------------------- /lib/commands/if_statement.rb: -------------------------------------------------------------------------------- 1 | require "conditional" 2 | 3 | class IfStatement 4 | def initialize(conditional, commands) 5 | @conditional, @commands = conditional, commands 6 | end 7 | 8 | def self.parse?(line, room, enterpreter) 9 | if line =~ /^\? (.+) \{$/ 10 | cond = Conditional.parse($1, room.number) 11 | commands = [] 12 | until room.next == "}" 13 | raise "Missing closing bracket" if room.current == nil 14 | commands << enterpreter.current_command(room) 15 | end 16 | new(cond, commands) 17 | else 18 | false 19 | end 20 | end 21 | 22 | def code 23 | codes = @commands.map { |c| c.code }.flatten 24 | [ "if ($this->con(#{@conditional.code})) {", 25 | codes.map { |line| " #{line}" }, 26 | '}'].flatten 27 | end 28 | 29 | end 30 | -------------------------------------------------------------------------------- /lib/commands/conditional_command.rb: -------------------------------------------------------------------------------- 1 | require 'conditional' 2 | 3 | class ConditionalCommand 4 | attr_reader :command 5 | 6 | def initialize(conditional, command) 7 | @conditional, @command = conditional, command 8 | end 9 | 10 | def self.parse?(line, room, enterpreter) 11 | if line.sub!(/ \?\? (.+)/, "") 12 | conditional = Conditional.parse($1, room.number) 13 | room[room.index] = line # replace with non-conditional before enterpreting again 14 | new(conditional, enterpreter.current_command(room)) 15 | else 16 | false 17 | end 18 | end 19 | 20 | def code 21 | if @conditional.is_a? TrueConditional 22 | @command.code 23 | else 24 | [ "if ($this->con(#{@conditional.code})) {", 25 | @command.code.map { |line| " #{line}" }, 26 | '}'].flatten 27 | end 28 | end 29 | 30 | end 31 | -------------------------------------------------------------------------------- /test/commands/test_goto_room.rb: -------------------------------------------------------------------------------- 1 | require 'test/unit' 2 | require 'rubygems' 3 | require 'mocha' 4 | require 'commands/goto_room' 5 | require 'commands/plain_text' 6 | require 'room' 7 | 8 | class CommandsGotoRoomTestCase < Test::Unit::TestCase 9 | 10 | def setup 11 | @room = ['@6', 'should not show'].extend(Room) 12 | enterpreter = mock('enterpreter') 13 | enterpreter.expects(:parse).with(6).returns(commands = [PlainText.new("contents")]) 14 | enterpreter.stubs(:has_function).returns(false) 15 | enterpreter.expects(:register_function).with("execute_room_6") 16 | enterpreter.expects(:define_function).with("execute_room_6", commands) 17 | @command = GotoRoom.parse?(@room.current, @room, enterpreter) 18 | end 19 | 20 | def test_should_build_code 21 | assert_equal(['$this->receiver->room_number_changed(6);', '$this->execute_room_6();', 'return;'], @command.code) 22 | end 23 | 24 | end -------------------------------------------------------------------------------- /lib/change_tracker.rb: -------------------------------------------------------------------------------- 1 | class ChangeTracker 2 | def initialize(source_directory, target_directory) 3 | @source_directory, @target_directory = source_directory, target_directory 4 | @state_file = "#{@target_directory}/last_compile.txt" 5 | end 6 | 7 | def changed_rooms 8 | #puts "finding all changed rooms... " 9 | start = Time.now 10 | changed_rooms = find_changed_rooms 11 | #puts "done after #{Time.now - start} seconds." 12 | changed_rooms 13 | end 14 | 15 | def save_current_state! 16 | `touch #{@state_file}` 17 | end 18 | 19 | private 20 | 21 | def find_changed_rooms 22 | if File.exists? @state_file 23 | `find #{@source_directory} -newer #{@state_file} | grep -v .svn | grep .adv` 24 | else 25 | `find #{@source_directory} | grep -v .svn | grep -v last_compile.txt | grep .adv` 26 | end.map { |line| $1.to_i if line =~ /A(\d+)\.adv$/ } 27 | end 28 | 29 | end 30 | -------------------------------------------------------------------------------- /lib/commands/run_saved_command.rb: -------------------------------------------------------------------------------- 1 | require 'commands/if_statement' 2 | require 'commands/conditional_command' 3 | require 'conditional' 4 | require 'room' 5 | 6 | class RunSavedCommand 7 | def self.parse?(line, room, enterpreter) 8 | if line[0..2] === "<= " 9 | name = line[3..-1] 10 | commands = enterpreter.hashcode_keeper.contentlist_for(name).map do |content| 11 | conditional = OrConditional.new 12 | content.codes.each { |code| conditional << ValueConditional.new("]C[#{name}", "==", code) } 13 | command = enterpreter.current_command([content.text.clone].extend(Room)) 14 | ConditionalCommand.new(conditional, command) 15 | end 16 | new commands 17 | else 18 | false 19 | end 20 | end 21 | 22 | def initialize(commands) 23 | @commands = commands 24 | end 25 | 26 | def code 27 | @commands.map { |c| c.code }.flatten 28 | end 29 | 30 | end 31 | -------------------------------------------------------------------------------- /test/commands/test_save_command.rb: -------------------------------------------------------------------------------- 1 | require 'test/unit' 2 | require 'rubygems' 3 | require 'mocha' 4 | require 'commands/save_command' 5 | require 'room' 6 | require 'commands/plain_text' 7 | 8 | class CommandsSaveCommandTestCase < Test::Unit::TestCase 9 | 10 | def setup 11 | @enterpreter = mock('enterpreter') 12 | @keeper = mock('hashcode_keeper') 13 | @enterpreter.stubs(:hashcode_keeper).returns(@keeper) 14 | end 15 | 16 | def test_should_save_hash_of_command_to_details 17 | @keeper.stubs(:code_for).returns(-245371941) 18 | room = ['MY-SAVED.TEST => contents1'].extend(Room) 19 | 20 | command = SaveCommand.parse?(room.current, room, @enterpreter) 21 | 22 | assert_equal('$this->receiver->set_detail("]C[MY-SAVED.TEST", "-245371941");', command.code) 23 | end 24 | 25 | def test_should_not_match_other_commands 26 | assert(!SaveCommand.parse?(":slem => Du er slem")) 27 | end 28 | 29 | 30 | end 31 | -------------------------------------------------------------------------------- /test/commands/test_sentence_with_variable.rb: -------------------------------------------------------------------------------- 1 | require 'test/unit' 2 | require 'rubygems' 3 | require 'mocha' 4 | require 'commands/sentence_with_variable' 5 | require 'room' 6 | 7 | class CommandsSentenceWithVariableTestCase < Test::Unit::TestCase 8 | 9 | def setup 10 | room = ['[$]$VAL', '$$ vals is "roughly" $$ vals.', 'Single val.'].extend(Room) 11 | @command = SentenceWithVariable.parse?(room.current, room) 12 | end 13 | 14 | def test_should_generate_code 15 | assert_equal(expected_code, @command.code) 16 | end 17 | 18 | private 19 | 20 | def expected_code 21 | [ 22 | "$value = $this->receiver->get_detail_to_display('$VAL');", 23 | "if ($value == 1) {", 24 | ' $this->receiver->write("Single val.");', 25 | "} else if ($value) {", 26 | " if (is_numeric($value)) { $value = TextFormatter::number($value); }", 27 | " $lvalue = TextFormatter::lowercase($value);", 28 | ' $this->receiver->write("$value vals is \\"roughly\\" $lvalue vals.");', 29 | "}" 30 | ] 31 | end 32 | end -------------------------------------------------------------------------------- /lib/commands/conditional_goto.rb: -------------------------------------------------------------------------------- 1 | require 'commands/conditional_command' 2 | require 'commands/goto_room' 3 | require 'conditional' 4 | require 'cond_tree' 5 | 6 | class ConditionalGoto 7 | def self.parse?(line, room, enterpreter) 8 | if line =~ /^@(\d+) \?\?$/ 9 | conditional = Conditional.build([:not, "nr#{$1}"], room.number) 10 | command = GotoRoom.parse?("@#{$1}", room, enterpreter) 11 | ConditionalCommand.new conditional, command 12 | elsif line =~ /^@(\d+) \?\? (.+)$/ 13 | implicit_condition = [:not, "nr#{$1}"] 14 | explicit_condition = CondTree.parse($2) 15 | if explicit_condition === "-" 16 | conditional = Conditional.build(implicit_condition, room.number) 17 | else 18 | both = [:and, implicit_condition, explicit_condition] 19 | conditional = Conditional.build(both, room.number) 20 | end 21 | command = GotoRoom.parse?("@#{$1}", room, enterpreter) 22 | ConditionalCommand.new conditional, command 23 | else 24 | false 25 | end 26 | end 27 | 28 | end 29 | -------------------------------------------------------------------------------- /test/commands/test_command_bundle.rb: -------------------------------------------------------------------------------- 1 | require 'test/unit' 2 | require 'rubygems' 3 | require 'mocha' 4 | require 'commands/if_statement' 5 | require 'commands/plain_text' 6 | require 'room' 7 | 8 | class CommandsIfStatementTestCase < Test::Unit::TestCase 9 | 10 | def setup 11 | room = ["? FLAG {", "content", "content", "}", "outside"].extend(Room) 12 | (@enterpreter = mock('enterpreter')).stubs(:current_command).returns(PlainText.new("content")) 13 | @bundle = IfStatement.parse?(room.current, room, @enterpreter) 14 | end 15 | 16 | def test_should_complain_about_missing_closing_brackets 17 | room = ["? FLAG {", "content"].extend(Room) 18 | assert_raise(RuntimeError) { IfStatement.parse?(room.current, room, @enterpreter) } 19 | end 20 | 21 | def test_should_build_code 22 | assert_equal(['if ($this->con($this->receiver->has_flag("FLAG"))) {', 23 | ' $this->receiver->write("content");', 24 | ' $this->receiver->write("content");', 25 | '}' 26 | ], @bundle.code) 27 | end 28 | 29 | end 30 | -------------------------------------------------------------------------------- /lib/commands/sentence_with_variable.rb: -------------------------------------------------------------------------------- 1 | class SentenceWithVariable 2 | 3 | def self.parse?(line, room, enterpreter = nil) 4 | if line[0..2] === "[$]" 5 | new line[3..-1], room.next, room.next 6 | else 7 | false 8 | end 9 | end 10 | 11 | def initialize(value, multiple_text, single_text) 12 | @value, @multiple_text, @single_text = value, multiple_text, single_text 13 | end 14 | 15 | def code 16 | [ 17 | "$value = $this->receiver->get_detail_to_display('#{@value}');", 18 | "if ($value == 1) {", 19 | " $this->receiver->write(\"#{escape(@single_text)}\");", 20 | "} else if ($value) {", 21 | " if (is_numeric($value)) { $value = TextFormatter::number($value); }", 22 | " $lvalue = TextFormatter::lowercase($value);", 23 | " $this->receiver->write(\"#{escape(@multiple_text)}\");", 24 | "}" 25 | ] 26 | end 27 | 28 | private 29 | 30 | def escape(text) 31 | text.sub(/^\$\$/, "$value").gsub("$$", "$lvalue").gsub('\\', ':BS::BS::BS::BS::BS:').gsub(':BS:', '\\').gsub('"', '\"') 32 | end 33 | 34 | end -------------------------------------------------------------------------------- /test/test_change_tracker.rb: -------------------------------------------------------------------------------- 1 | require 'test/unit' 2 | require 'rubygems' 3 | require 'mocha' 4 | require 'change_tracker' 5 | 6 | class ChangeTrackerTestCase < Test::Unit::TestCase 7 | 8 | def setup 9 | @testfiles = "#{File.dirname(__FILE__)}/compiler_test_files" 10 | @temporary = "#{File.dirname(__FILE__)}/temporary_directory" 11 | teardown 12 | Dir.mkdir @temporary 13 | end 14 | 15 | def teardown 16 | `rm -rf #{@temporary}` 17 | end 18 | 19 | def test_should_find_changed_rooms 20 | tracker = ChangeTracker.new @testfiles, @testfiles 21 | assert_equal([12503, 12504], tracker.changed_rooms) 22 | end 23 | 24 | def test_should_find_all_rooms_if_last_compile_file_is_missing 25 | tracker = ChangeTracker.new @testfiles, @temporary 26 | assert_equal([12501, 12502, 12503, 12504], tracker.changed_rooms) 27 | end 28 | 29 | def test_should_save_current_state 30 | tracker = ChangeTracker.new @temporary, @temporary 31 | tracker.save_current_state! 32 | assert File.exists?("#{@temporary}/last_compile.txt"), "Room-state file not created" 33 | end 34 | 35 | end -------------------------------------------------------------------------------- /test/commands/test_add_deed.rb: -------------------------------------------------------------------------------- 1 | require 'test/unit' 2 | require 'commands/add_deed' 3 | require 'room' 4 | 5 | class CommandsAddDeedTestCase < Test::Unit::TestCase 6 | 7 | def setup 8 | room = [':slem => An evil deed'].extend(Room) 9 | @evil_deed = AddDeed.parse?(room.current, room) 10 | room = [':snill => A good deed'].extend(Room) 11 | @good_deed = AddDeed.parse?(room.current, room) 12 | end 13 | 14 | def test_should_generate_code 15 | assert_equal(expected_code_for_evil_deed, @evil_deed.code) 16 | assert_equal(expected_code_for_good_deed, @good_deed.code) 17 | end 18 | 19 | def expected_code_for_evil_deed 20 | [ 21 | 'if ($this->con(!$this->receiver->has_flag("_EVIL_DEED_389028181"))) {', 22 | ' $this->receiver->add_deed("EVIL");', 23 | ' $this->receiver->add_flag("_EVIL_DEED_389028181");', 24 | '}' 25 | ] 26 | end 27 | 28 | def expected_code_for_good_deed 29 | [ 30 | 'if ($this->con(!$this->receiver->has_flag("_GOOD_DEED_1072018006"))) {', 31 | ' $this->receiver->add_deed("GOOD");', 32 | ' $this->receiver->add_flag("_GOOD_DEED_1072018006");', 33 | '}' 34 | ] 35 | end 36 | 37 | 38 | end 39 | -------------------------------------------------------------------------------- /lib/room_loader.rb: -------------------------------------------------------------------------------- 1 | require 'room' 2 | 3 | class RoomLoader 4 | def initialize(folder) 5 | @folder = folder 6 | end 7 | 8 | def get(room_number) 9 | abort "Tried loading room #{room_number} that does not exist" unless room_exists?(room_number) 10 | @@last_loaded_room_number = room_number 11 | room = IO.read(path_to(room_number)).map { |line| line.gsub(/\s*$/, "").gsub(/^\s*/, "") }.extend(Room) 12 | room.number = room_number 13 | room 14 | end 15 | 16 | def room_exists?(room_number) 17 | File.exists? path_to(room_number) 18 | end 19 | 20 | def all_room_numbers 21 | Dir["#{@folder}/**/A*.adv"].map { |filename| filename.scan(/A(\d+)\.adv$/) }.flatten.map { |s| s.to_i } 22 | end 23 | 24 | def self.last_loaded_room_number 25 | @@last_loaded_room_number ||= "none" 26 | end 27 | 28 | private 29 | 30 | def path_to(room_number) 31 | "#{@folder}/#{subfolder(room_number)}/#{roomfile(room_number)}" 32 | end 33 | 34 | def subfolder(room_number) 35 | hundreds = room_number.to_i / 100 36 | hundreds < 10 ? "A0#{hundreds}" : "A#{hundreds}" 37 | end 38 | 39 | def roomfile(room_number) 40 | "A#{room_number}.adv" 41 | end 42 | 43 | end 44 | -------------------------------------------------------------------------------- /test/commands/test_conditional_command.rb: -------------------------------------------------------------------------------- 1 | require 'test/unit' 2 | require 'rubygems' 3 | require 'mocha' 4 | require 'commands/conditional_command' 5 | require 'commands/plain_text' 6 | require 'room' 7 | 8 | class CommandsConditionalCommandTestCase < Test::Unit::TestCase 9 | 10 | def test_should_parse_normal_conditional 11 | room = ["command ?? FLAG", "outside"].extend(Room) 12 | (enterpreter = mock('enterpreter')).stubs(:current_command).returns(PlainText.new("command")) 13 | conditional = ConditionalCommand.parse?(room.current, room, enterpreter) 14 | assert_equal(expected_code, conditional.code) 15 | end 16 | 17 | def test_should_not_add_if_condition_when_always_true 18 | room = ["command ?? -", "outside"].extend(Room) 19 | (enterpreter = mock('enterpreter')).stubs(:current_command).returns(PlainText.new("command")) 20 | conditional = ConditionalCommand.parse?(room.current, room, enterpreter) 21 | assert_equal('$this->receiver->write("command");', conditional.code) 22 | end 23 | 24 | private 25 | 26 | def expected_code 27 | ['if ($this->con($this->receiver->has_flag("FLAG"))) {', 28 | ' $this->receiver->write("command");', 29 | '}'] 30 | end 31 | 32 | end 33 | -------------------------------------------------------------------------------- /lib/commands/assign_to_variable.rb: -------------------------------------------------------------------------------- 1 | class AssignToVariable 2 | def self.parse?(line, room = nil, enterpreter = nil) 3 | case 4 | when line =~ /^(\$[^ ]+) ?= ?(.*)/ then new $1, $2 5 | when line =~ /^(\$[^ ]+)\+\+/ then new $1, "#{$1} + 1" 6 | when line =~ /^(\$[^ ]+)\-\-/ then new $1, "#{$1} - 1" 7 | when true then false 8 | end 9 | end 10 | 11 | def initialize(variable, expression) 12 | @variable, @expression = variable, expression 13 | end 14 | 15 | def code 16 | variable = escape(@variable) 17 | case 18 | when @expression =~ /^Tilfeldig: ([^ ]+) til ([^ ]+)$/ then "$this->receiver->set_to_random(\"#{variable}\", #{get_details($1)}, #{get_details($2)});" 19 | when @expression =~ /^#{variable} \+ (\d+)$/ then "$this->receiver->add_to_detail(\"#{variable}\", #{$1});" 20 | when @expression =~ /^#{variable} \- (\d+)$/ then "$this->receiver->add_to_detail(\"#{variable}\", -#{$1});" 21 | when true then "$this->receiver->set_detail(\"#{variable}\", #{get_details(@expression)});" 22 | end 23 | end 24 | 25 | private 26 | 27 | def escape(var) 28 | var.gsub('$', '\$') 29 | end 30 | 31 | def get_details(expression) 32 | expression.split(/(\$[^ ]+)/).map do |part| 33 | if part =~ /^\$([^ ]+)$/ 34 | "$this->receiver->get_detail(\"\\$#{$1}\")" 35 | else 36 | part 37 | end 38 | end.join 39 | end 40 | 41 | 42 | end -------------------------------------------------------------------------------- /lib/page.rb: -------------------------------------------------------------------------------- 1 | require 'enterpreter' 2 | require 'room_loader' 3 | 4 | class Page 5 | 6 | def initialize(starting_room, room_loader, timed_jump_coordinator) 7 | @starting_room = starting_room 8 | begin 9 | @code = Enterpreter.new(starting_room, room_loader, timed_jump_coordinator).enterpret 10 | rescue 11 | raise [ 12 | "", 13 | "--------------------------------------------------------------", 14 | " Error while initializing page from room #{@starting_room}!", 15 | " Message: #{$!}", 16 | " Last loaded room number: #{RoomLoader.last_loaded_room_number}", 17 | "--------------------------------------------------------------" 18 | ].join("\n") 19 | end 20 | end 21 | 22 | def identifier 23 | "#{@starting_room}_#{@code.hash}" 24 | end 25 | 26 | def code 27 | code_skeleton.sub(":CODE:", indented_code).sub(":IDENTIFIER:", identifier) 28 | end 29 | 30 | def indented_code 31 | @code.map { |line| " #{line}" }.join 32 | end 33 | 34 | private 35 | 36 | def code_skeleton 37 | <<-CODE 38 | receiver = $receiver; } 43 | :CODE: 44 | private function con($bool) { return $this->receiver->conditional($bool); } 45 | } 46 | ?> 47 | CODE 48 | end 49 | 50 | end 51 | -------------------------------------------------------------------------------- /lib/commands/procedure_call.rb: -------------------------------------------------------------------------------- 1 | class ProcedureCall 2 | 3 | def initialize(room_number, procedure_number) 4 | @room_number, @procedure_number = room_number, procedure_number 5 | end 6 | 7 | def self.parse?(line, room = nil, enterpreter = nil) 8 | if line =~ /^\(@\)(\d+)$/ 9 | procedure_number = $1.to_i 10 | unless enterpreter.has_function("procedure_call_to_#{procedure_number}") 11 | enterpreter.register_function("procedure_call_to_#{procedure_number}") 12 | procedure = enterpreter.room_loader.get(procedure_number) 13 | commands = [] 14 | until procedure.current == "=" or procedure.current == nil 15 | commands << enterpreter.current_command(procedure) 16 | procedure.next 17 | end 18 | commands << ReturnFromProcedureCall.new 19 | enterpreter.define_function("procedure_call_to_#{procedure_number}", commands) 20 | end 21 | new room.number, procedure_number 22 | else 23 | false 24 | end 25 | end 26 | 27 | def code 28 | ["$this->receiver->room_number_changed(#{@procedure_number});", 29 | "$continue = $this->procedure_call_to_#{@procedure_number}();", 30 | "if ($this->con(!$continue)) { return; }", 31 | "$this->receiver->room_number_changed(#{@room_number});"].flatten 32 | end 33 | 34 | class ReturnFromProcedureCall 35 | def code 36 | "return true;" 37 | end 38 | end 39 | 40 | end 41 | -------------------------------------------------------------------------------- /test/test_page.rb: -------------------------------------------------------------------------------- 1 | require 'test/unit' 2 | require 'rubygems' 3 | require 'mocha' 4 | require 'page' 5 | 6 | class PageTestCase < Test::Unit::TestCase 7 | 8 | def setup 9 | room_loader = mock('room_loader') 10 | Enterpreter.expects(:new).with(0, room_loader, nil).returns(enterpreter = mock('enterpreter')) 11 | enterpreter.expects(:enterpret).returns("public function execute() {\n $this->receiver->code();\n}") 12 | @page = Page.new(0, room_loader, nil) 13 | end 14 | 15 | def test_should_base_identifier_on_starting_room_and_page_code_hash 16 | assert_equal("0_87002840", @page.identifier) 17 | end 18 | 19 | def test_should_generate_page_code 20 | assert_equal(expected_code, @page.code) 21 | end 22 | 23 | def test_should_give_nice_error_messages 24 | error = assert_raise(RuntimeError) { Page.new(1234, nil, nil) } 25 | assert_match(/room 1234/, error.message) 26 | assert_match(/expected exactly once/, error.message) 27 | assert_match(/none/, error.message) 28 | end 29 | 30 | private 31 | 32 | def expected_code 33 | <<-CODE 34 | receiver = $receiver; } 39 | public function execute() { 40 | $this->receiver->code(); 41 | } 42 | private function con($bool) { return $this->receiver->conditional($bool); } 43 | } 44 | ?> 45 | CODE 46 | end 47 | 48 | end 49 | -------------------------------------------------------------------------------- /test/ts_all_tests.rb: -------------------------------------------------------------------------------- 1 | require 'test/unit' 2 | 3 | require 'commands/test_plain_text' 4 | require 'commands/test_add_flag' 5 | require 'commands/test_conditional_command' 6 | require 'commands/test_if_statement' 7 | require 'commands/test_alternatives' 8 | require 'commands/test_remove_flag' 9 | require 'commands/test_goto_room' 10 | require 'commands/test_alternatives_from_other' 11 | require 'commands/test_procedure_call' 12 | require 'commands/test_save_command' 13 | require 'commands/test_run_saved_command' 14 | require 'commands/test_end_paragraph' 15 | require 'commands/test_add_deed' 16 | require 'commands/test_add_kongerupi' 17 | require 'commands/test_remove_alternatives' 18 | require 'commands/test_assign_to_variable' 19 | require 'commands/test_reinstate_alternatives' 20 | require 'commands/test_conditional_goto' 21 | require 'commands/test_count_visits' 22 | require 'commands/test_activate_saving' 23 | require 'commands/test_unfinished_story' 24 | require 'commands/test_end_subpage' 25 | require 'commands/test_increase_bodycount' 26 | require 'commands/test_add_timejump' 27 | require 'commands/test_sentence_with_variable' 28 | require 'commands/test_summarize_deeds' 29 | require 'commands/test_register_delayed_call' 30 | require 'test_room' 31 | require 'test_room_loader' 32 | require 'test_conditional' 33 | require 'test_enterpreter' 34 | require 'test_page' 35 | require 'test_timed_jump_coordinator' 36 | require 'test_changes_only_compiler' 37 | require 'test_change_tracker' 38 | require 'test_hashcode_keeper' 39 | -------------------------------------------------------------------------------- /test/commands/test_alternatives_from_other.rb: -------------------------------------------------------------------------------- 1 | require 'test/unit' 2 | require 'rubygems' 3 | require 'mocha' 4 | require 'commands/alternatives_from_other' 5 | require 'commands/plain_text' 6 | require 'room' 7 | 8 | class CommandsAlternativesFromOtherTestCase < Test::Unit::TestCase 9 | 10 | def setup 11 | @room = ['{@}17', 'should not show'].extend(Room) 12 | @room17 = ['should not show either', '=', 'alternative', '123'].extend(Room) 13 | room_loader = mock('room_loader') 14 | room_loader.expects(:get).with(17).returns(@room17) 15 | enterpreter = mock('enterpreter') 16 | enterpreter.stubs(:room_loader).returns(room_loader) 17 | enterpreter.expects(:current_command).with(@room17).returns(@alternatives = mock('alternatives')) 18 | @command = AlternativesFromOther.parse?(@room.current, @room, enterpreter) 19 | end 20 | 21 | def test_should_add_note_about_room_change 22 | @alternatives.expects(:code).returns("$this->code();") 23 | assert_equal(['$this->receiver->room_number_changed(17);', '$this->code();'], @command.code) 24 | end 25 | 26 | def test_should_complain_if_other_room_has_no_alternatives 27 | @room = ['{@}17'].extend(Room) 28 | @room17 = ['line 1', 'line 2'].extend(Room) 29 | room_loader = mock('room_loader') 30 | room_loader.expects(:get).with(17).returns(@room17) 31 | enterpreter = mock('enterpreter') 32 | enterpreter.stubs(:room_loader).returns(room_loader) 33 | assert_raise(RuntimeError) { AlternativesFromOther.parse?(@room.current, @room, enterpreter) } 34 | end 35 | 36 | end 37 | -------------------------------------------------------------------------------- /test/commands/test_run_saved_command.rb: -------------------------------------------------------------------------------- 1 | require 'test/unit' 2 | require 'rubygems' 3 | require 'mocha' 4 | require 'commands/run_saved_command' 5 | require 'commands/plain_text' 6 | require 'enterpreter' 7 | 8 | class CommandsRunSavedCommandTestCase < Test::Unit::TestCase 9 | 10 | def setup 11 | @room = ['<= TEST'].extend(Room) 12 | create_enterpreter_mock 13 | end 14 | 15 | def test_should_build_code 16 | @keeper.stubs(:contentlist_for).returns([content_mock("contents1", [-245371941]), content_mock("contents2", [-245371940, 123456778])]) 17 | @enterpreter.stubs(:current_command).returns(PlainText.new("contents1"), PlainText.new("contents2")) 18 | command = RunSavedCommand.parse?(@room.current, @room, @enterpreter) 19 | assert_equal(['if ($this->con(($this->receiver->get_detail("]C[TEST") == -245371941))) {', 20 | ' $this->receiver->write("contents1");', 21 | '}', 22 | 'if ($this->con(($this->receiver->get_detail("]C[TEST") == -245371940 || $this->receiver->get_detail("]C[TEST") == 123456778))) {', 23 | ' $this->receiver->write("contents2");', 24 | '}' 25 | ], command.code) 26 | end 27 | 28 | private 29 | 30 | def content_mock(text, codes) 31 | content = mock('content') 32 | content.stubs(:text).returns(text) 33 | content.stubs(:codes).returns(codes) 34 | content 35 | end 36 | 37 | def create_enterpreter_mock 38 | @enterpreter = mock('enterpreter') 39 | @keeper = mock('hashcode_keeper') 40 | @enterpreter.stubs(:hashcode_keeper).returns(@keeper) 41 | end 42 | 43 | end 44 | -------------------------------------------------------------------------------- /test/test_room_loader.rb: -------------------------------------------------------------------------------- 1 | require 'test/unit' 2 | require 'rubygems' 3 | require 'mocha' 4 | require 'room_loader' 5 | 6 | class RoomLoaderTestCase < Test::Unit::TestCase 7 | 8 | def setup 9 | @folder = "testrooms" 10 | @subfolder = "#{@folder}/A00" 11 | @roomfile = "#{@subfolder}/A0.adv" 12 | teardown 13 | 14 | Dir.mkdir(@folder) 15 | Dir.mkdir(@subfolder) 16 | File.open(@roomfile, 'w') { |file| file.print "Line 1 of room #0\n Line 2 of room #0 \n" } 17 | 18 | @loader = RoomLoader.new @folder 19 | end 20 | 21 | def teardown 22 | File.delete(@roomfile) if File.exists? @roomfile 23 | Dir.rmdir(@subfolder) if File.exists? @subfolder 24 | Dir.rmdir(@folder) if File.exists? @folder 25 | end 26 | 27 | def test_should_know_if_a_room_exists 28 | assert(@loader.room_exists?(0), "Room 0 should exist") 29 | assert( ! @loader.room_exists?(17), "Room 17 should not exist") 30 | end 31 | 32 | def test_should_always_keep_last_loaded_room_number 33 | assert_equal("none", RoomLoader.last_loaded_room_number) 34 | @loader.get(0) 35 | assert_equal(0, RoomLoader.last_loaded_room_number) 36 | end 37 | 38 | def test_should_extend_with_room_module 39 | assert_kind_of(Room, @loader.get(0)) 40 | end 41 | 42 | def test_should_insert_room_number 43 | assert_equal(0, @loader.get(0).number) 44 | end 45 | 46 | def test_should_list_all_room_numbers 47 | assert_equal([0], @loader.all_room_numbers) 48 | end 49 | 50 | def test_should_load_rooms_from_given_source 51 | assert_equal(['Line 1 of room #0', 'Line 2 of room #0'], @loader.get(0)) 52 | end 53 | 54 | end 55 | -------------------------------------------------------------------------------- /test/commands/test_conditional_goto.rb: -------------------------------------------------------------------------------- 1 | require 'test/unit' 2 | require 'rubygems' 3 | require 'mocha' 4 | require 'room' 5 | require 'commands/plain_text' 6 | require 'commands/conditional_goto' 7 | 8 | class CommandsConditionalGotoTestCase < Test::Unit::TestCase 9 | 10 | def test_should_jump_on_conditional 11 | command = setup_jump_mocks ['@623 ?? REQUIREMENT', 'should not show'].extend(Room) 12 | assert_equal(expected_code_for_normal_jump, command.code) 13 | end 14 | 15 | def test_should_add_implicit_requirement_when_no_requirements 16 | command = setup_jump_mocks ['@623 ??'].extend(Room) 17 | assert_equal(expected_code_for_nonrequirement_jump, command.code) 18 | end 19 | 20 | private 21 | 22 | def setup_jump_mocks(room) 23 | enterpreter = mock('enterpreter') 24 | enterpreter.expects(:parse).with(623).returns([PlainText.new("contents")]) 25 | enterpreter.stubs(:has_function).returns(false) 26 | enterpreter.stubs(:register_function) 27 | enterpreter.stubs(:define_function) 28 | ConditionalGoto.parse?(room.current, room, enterpreter) 29 | end 30 | 31 | def expected_code_for_normal_jump 32 | [ 33 | 'if ($this->con((!($this->receiver->has_flag("nr623")) && $this->receiver->has_flag("REQUIREMENT")))) {', 34 | ' $this->receiver->room_number_changed(623);', 35 | ' $this->execute_room_623();', 36 | ' return;', 37 | '}' 38 | ] 39 | end 40 | 41 | def expected_code_for_nonrequirement_jump 42 | [ 43 | 'if ($this->con(!($this->receiver->has_flag("nr623")))) {', 44 | ' $this->receiver->room_number_changed(623);', 45 | ' $this->execute_room_623();', 46 | ' return;', 47 | '}' 48 | ] 49 | end 50 | 51 | end 52 | -------------------------------------------------------------------------------- /lib/commands/summarize_deeds.rb: -------------------------------------------------------------------------------- 1 | class SummarizeDeeds 2 | def self.parse?(line, room, enterpreter) 3 | if line === ":gud-og-satan-krangler" 4 | new enterpreter.hashcode_keeper.contentlist_for("_EVIL_DEED"), enterpreter.hashcode_keeper.contentlist_for("_GOOD_DEED") 5 | else 6 | false 7 | end 8 | end 9 | 10 | def initialize(evil_deeds, good_deeds) 11 | @evil_deeds, @good_deeds = evil_deeds, good_deeds 12 | end 13 | 14 | def code 15 | code = [ 16 | '$evil = array();', 17 | '$good = array();', 18 | '$seed = 0;' 19 | ] 20 | @evil_deeds.each do |deed| 21 | codeif = deed.codes.map { |c| "$this->receiver->has_flag(\"_EVIL_DEED_#{c}\")" }.join(" || ") 22 | code << "if ($this->con(#{codeif})) { $evil[] = \"#{escape(deed.text)} \"; $seed += #{deed.codes.first.to_s[0..2]}; }" 23 | end 24 | @good_deeds.each do |deed| 25 | codeif = deed.codes.map { |c| "$this->receiver->has_flag(\"_GOOD_DEED_#{c}\")" }.join(" || ") 26 | code << "if ($this->con(#{codeif})) { $good[] = \"#{escape(deed.text)} \"; $seed += #{deed.codes.first.to_s[0..2]}; }" 27 | end 28 | code << [ 29 | 'srand($seed);', 30 | 'while (sizeof($good) > 0) {', 31 | ' $r = array_rand($good);', 32 | ' $this->receiver->write($good[$r]);', 33 | ' unset($good[$r]);', 34 | ' if (sizeof($evil) > 0) {', 35 | ' $r = array_rand($evil);', 36 | ' $this->receiver->write($evil[$r]);', 37 | ' unset($evil[$r]);', 38 | ' } else {', 39 | ' break;', 40 | ' }', 41 | '}' 42 | ] 43 | code.flatten 44 | end 45 | 46 | def escape(text) 47 | text.gsub('\\', ':BS::BS::BS::BS::BS:').gsub(':BS:', '\\').gsub('"', '\"').gsub("", '".$this->receiver->get_nickname()."') 48 | end 49 | 50 | end 51 | -------------------------------------------------------------------------------- /test/commands/test_procedure_call.rb: -------------------------------------------------------------------------------- 1 | require 'test/unit' 2 | require 'rubygems' 3 | require 'mocha' 4 | require 'commands/procedure_call' 5 | require 'room' 6 | 7 | class CommandsProcedureCallTestCase < Test::Unit::TestCase 8 | def test_should_not_be_confused_with_delayed_call 9 | assert(!ProcedureCall.parse?("(@)123 om 3 ...")) 10 | end 11 | 12 | def test_should_generate_code 13 | room = ['(@)23', 'should not show'].extend(Room) 14 | room.number = 17 15 | @room23 = ['contents', 'contents', '=', 'alternative', '123'].extend(Room) 16 | room_loader = mock('room_loader') 17 | room_loader.expects(:get).with(23).returns(@room23) 18 | enterpreter = mock('enterpreter') 19 | enterpreter.stubs(:room_loader).returns(room_loader) 20 | enterpreter.expects(:current_command).with(@room23).returns(@contents = mock('contents')).times(2) 21 | @contents.stubs(:contents).returns("contents") 22 | enterpreter.stubs(:has_function).returns(false) 23 | enterpreter.expects(:register_function).with("procedure_call_to_23") 24 | ProcedureCall::ReturnFromProcedureCall.stubs(:new).returns(ret = mock('return')) 25 | ret.stubs(:code).returns([]) 26 | enterpreter.expects(:define_function).with("procedure_call_to_23", [@contents, @contents, ret]) 27 | @procedure = ProcedureCall.parse?(room.current, room, enterpreter) 28 | @contents.stubs(:code).returns("$this->receiver->code();") 29 | assert_equal(expected_code, @procedure.code) 30 | end 31 | 32 | def test_should_return_from_procedure_call_with_true 33 | ret = ProcedureCall::ReturnFromProcedureCall.new 34 | assert_equal('return true;', ret.code) 35 | end 36 | 37 | private 38 | 39 | def expected_code 40 | ['$this->receiver->room_number_changed(23);', 41 | '$continue = $this->procedure_call_to_23();', 42 | 'if ($this->con(!$continue)) { return; }', 43 | '$this->receiver->room_number_changed(17);'] 44 | end 45 | 46 | end 47 | -------------------------------------------------------------------------------- /test/commands/test_summarize_deeds.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | require 'test/unit' 3 | require 'rubygems' 4 | require 'mocha' 5 | require 'commands/summarize_deeds' 6 | require 'room' 7 | 8 | class CommandsSummarizeDeedsTestCase < Test::Unit::TestCase 9 | 10 | def setup 11 | enterpreter = mock('enterpreter') 12 | keeper = mock('hashcode_keeper') 13 | enterpreter.stubs(:hashcode_keeper).returns(keeper) 14 | keeper.stubs(:contentlist_for).returns([content_mock('"Slemt altså"', [608088895])], [content_mock('Snilt også', [224333227]), content_mock('Mer snilt', [739606083])]) 15 | 16 | @command = SummarizeDeeds.parse?(":gud-og-satan-krangler", nil, enterpreter) 17 | end 18 | 19 | def content_mock(text, codes) 20 | content = mock('content') 21 | content.stubs(:text).returns(text) 22 | content.stubs(:codes).returns(codes) 23 | content 24 | end 25 | 26 | def test_should_generate_code 27 | assert_equal(expected_code, @command.code) 28 | end 29 | 30 | def expected_code 31 | [ 32 | '$evil = array();', 33 | '$good = array();', 34 | '$seed = 0;', 35 | 'if ($this->con($this->receiver->has_flag("_EVIL_DEED_608088895"))) { $evil[] = "\\"Slemt altså\\" "; $seed += 608; }', 36 | 'if ($this->con($this->receiver->has_flag("_GOOD_DEED_224333227"))) { $good[] = "Snilt også "; $seed += 224; }', 37 | 'if ($this->con($this->receiver->has_flag("_GOOD_DEED_739606083"))) { $good[] = "Mer snilt "; $seed += 739; }', 38 | 'srand($seed);', 39 | 'while (sizeof($good) > 0) {', 40 | ' $r = array_rand($good);', 41 | ' $this->receiver->write($good[$r]);', 42 | ' unset($good[$r]);', 43 | ' if (sizeof($evil) > 0) {', 44 | ' $r = array_rand($evil);', 45 | ' $this->receiver->write($evil[$r]);', 46 | ' unset($evil[$r]);', 47 | ' } else {', 48 | ' break;', 49 | ' }', 50 | '}' 51 | ] 52 | end 53 | 54 | end 55 | -------------------------------------------------------------------------------- /test/commands/test_plain_text.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | require 'test/unit' 3 | require 'commands/plain_text' 4 | 5 | class CommandsPlainTextTestCase < Test::Unit::TestCase 6 | 7 | def setup 8 | @text = PlainText.parse?("a line of prose") 9 | end 10 | 11 | def test_should_parse_anything 12 | assert(PlainText.parse?("any line of text"), "PlainText should accept anything.") 13 | end 14 | 15 | def test_should_build_code 16 | assert_equal('$this->receiver->write("a line of prose");', @text.code) 17 | end 18 | 19 | def test_should_not_add_spaces_after_every_80_chars_anymore 20 | assert_equal('$this->receiver->write("\"..og for å skape en troverdig front for dette mørkeste av ritualer, så skal de laveste medlemmene av banklosjen arrangere en liksomfest med liksompunsj og liksomdansing etterpå. For en farse! Hadde det ikke vært for mitt dårlige kne så skulle jeg satt en stopper for det mørke ritualet selv.\" Sjømannen nikker ivrig, veldig enig med seg selv.");', 21 | PlainText.parse?('"..og for å skape en troverdig front for dette mørkeste av ritualer, så skal de laveste medlemmene av banklosjen arrangere en liksomfest med liksompunsj og liksomdansing etterpå. For en farse! Hadde det ikke vært for mitt dårlige kne så skulle jeg satt en stopper for det mørke ritualet selv." Sjømannen nikker ivrig, veldig enig med seg selv.').code) 22 | end 23 | 24 | def test_should_escape_quotation_marks 25 | assert_equal('$this->receiver->write("\"Hello,\" he said.");', PlainText.new("\"Hello,\" he said.").code) 26 | end 27 | 28 | def test_should_escape_backslash 29 | # denne sinnsyke mengden backslasher er det som skal til for å få et dobbelt sett helt ut til eksporten.. 30 | assert_equal('$this->receiver->write("Backslash: \\\\\\\\\");', PlainText.new("Backslash: \\").code) 31 | end 32 | 33 | def test_should_insert_nickname 34 | assert_equal('$this->receiver->write("You are known as ".$this->receiver->get_nickname()." the great.");', 35 | PlainText.new("You are known as the great.").code) 36 | end 37 | 38 | end 39 | -------------------------------------------------------------------------------- /lib/commands/alternatives.rb: -------------------------------------------------------------------------------- 1 | require 'commands/conditional_command' 2 | 3 | class Alternatives 4 | def initialize(alternatives) 5 | @alternatives = alternatives 6 | end 7 | 8 | def self.parse?(line, room, enterpreter) 9 | if line == "=" 10 | return CancelProcedureCall.new if room.peek.nil? 11 | 12 | room_loader = enterpreter.room_loader 13 | alternatives = [] 14 | 15 | while room.peek and room.peek != "}" 16 | alternatives << parse_alternative(room) 17 | end 18 | 19 | alternatives = alternatives.select do |alt| 20 | room_loader.room_exists? alt.command.room_number 21 | end 22 | 23 | new alternatives 24 | else 25 | false 26 | end 27 | end 28 | 29 | def self.parse_alternative(room) 30 | text = room.next 31 | until room.peek =~ /^ *@/ 32 | raise "Irregular alternatives at: #{room.current}" if room.peek.nil? 33 | text = "#{text} #{room.next}" 34 | end 35 | details = room.next 36 | if details =~ /^ *@(-?\d+)$/ 37 | room_number = $1 38 | condition = "-" 39 | elsif details =~ /^ *@(-?\d+) \? (.+)$/ 40 | room_number = $1 41 | condition = $2 42 | else 43 | end 44 | room.next if room.peek == "" # support blank line between alternatives 45 | alternative = Alternative.new(text, room_number) 46 | ConditionalCommand.new(Conditional.parse(condition, room.number), alternative) 47 | end 48 | 49 | def code 50 | ['$visible_alternatives = 0;', @alternatives.map { |a| a.code }, 'return;'].flatten 51 | end 52 | 53 | end 54 | 55 | class Alternative 56 | attr_reader :text, :room_number 57 | 58 | def initialize(text, room_number) 59 | @text, @room_number = text, room_number 60 | end 61 | 62 | def code 63 | "if ($this->con(!$this->receiver->has_flag(\"nr#{@room_number}\"))) { $this->receiver->add_alternative(\"#{escape(@text)}\", #{@room_number}); $visible_alternatives++; }" 64 | end 65 | 66 | def escape(text) 67 | text.gsub('\\', ':BS::BS::BS::BS::BS:').gsub(':BS:', '\\').gsub('"', '\"').gsub("", '".$this->receiver->get_nickname()."') 68 | end 69 | 70 | end 71 | 72 | class CancelProcedureCall 73 | def code 74 | "return true;" 75 | end 76 | end 77 | -------------------------------------------------------------------------------- /lib/hashcode_keeper.rb: -------------------------------------------------------------------------------- 1 | 2 | class HashcodeKeeper 3 | def initialize(hashcodefile = false) 4 | @variables = [] 5 | parse hashcodefile if hashcodefile 6 | end 7 | 8 | def add(variable_name, content) 9 | find_or_create_variable(variable_name).add_content(content) 10 | end 11 | 12 | def contentlist_for(variable_name) 13 | variable(variable_name).contentlist 14 | end 15 | 16 | def code_for(variable_name, content) 17 | variable(variable_name).contentlist.find { |c| c.text == content }.codes.first 18 | end 19 | 20 | def variable(name) 21 | @variables.find { |var| var.name == name } or throw "Ukjent variabel #{name}" 22 | end 23 | 24 | def parse(hashcodefile) 25 | hashcodefile.to_s.split("\n\n").each { |variable| parse_variable variable.to_a.map { |string| string.chomp } } 26 | end 27 | 28 | def parse_variable(definition) 29 | name = definition.shift[3..-1] 30 | contentlist = [] 31 | definition.each_slice(2) { |text, codes| contentlist << Content.new(text, codes.split(',').map { |code| code.to_i }) } 32 | @variables << Variable.new(name, contentlist) 33 | end 34 | 35 | def save(filename) 36 | File.open(filename, "w") do |file| 37 | @variables.each { |var| var.save(file); file.puts "" } 38 | end 39 | end 40 | 41 | private 42 | 43 | def find_or_create_variable(name) 44 | @variables << Variable.new(name) if not @variables.find { |var| var.name == name } 45 | @variables.find { |var| var.name == name } 46 | end 47 | 48 | end 49 | 50 | class Variable 51 | attr_reader :name, :contentlist 52 | 53 | def initialize(name, contentlist = []) 54 | @name, @contentlist = name, contentlist 55 | end 56 | 57 | def add_content(content) 58 | @contentlist << Content.new(content) unless @contentlist.any? { |c| c.text == content } 59 | end 60 | 61 | def save(file) 62 | file.puts "]C[#{@name}" 63 | @contentlist.each { |content| content.save(file) } 64 | end 65 | 66 | end 67 | 68 | class Content 69 | attr_reader :text 70 | 71 | def initialize(text, codes = []) 72 | @text, @codes = text, codes 73 | end 74 | 75 | def codes 76 | @codes << @text.hash if @codes.empty? 77 | @codes 78 | end 79 | 80 | def save(file) 81 | file.puts @text 82 | file.puts codes.join(",") 83 | end 84 | 85 | end -------------------------------------------------------------------------------- /test/test_hashcode_keeper.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | require 'test/unit' 3 | require 'hashcode_keeper' 4 | 5 | class HashcodeKeeperTestCase < Test::Unit::TestCase 6 | 7 | def setup 8 | @keeper = HashcodeKeeper.new 9 | @keeper.add("GI_URTE", "()FIKK-INGEFÆR") 10 | end 11 | 12 | def teardown 13 | File.delete("hck_test.txt") if File.exists? "hck_test.txt" 14 | end 15 | 16 | def test_should_parse_hashcodefile 17 | keeper = HashcodeKeeper.new hashcodefile 18 | assert_equal 2, keeper.contentlist_for("KRAV").size 19 | assert_equal 1, keeper.contentlist_for("VRAK").size 20 | assert_equal [987, 654, 321], keeper.contentlist_for("VRAK").first.codes 21 | end 22 | 23 | def test_should_save_hashcodefile 24 | keeper = HashcodeKeeper.new hashcodefile 25 | keeper.save "hck_test.txt" 26 | assert_equal hashcodefile, IO.readlines("hck_test.txt").to_s 27 | end 28 | 29 | def test_should_add_codes_to_new_content_when_saving 30 | @keeper.save "hck_test.txt" 31 | assert_equal "]C[GI_URTE\n()FIKK-INGEFÆR\n-1954996374\n\n", IO.readlines("hck_test.txt").to_s 32 | end 33 | 34 | def hashcodefile 35 | <<-FILE 36 | ]C[KRAV 37 | content1 38 | 123 39 | content2 40 | 456,789 41 | 42 | ]C[VRAK 43 | content3 44 | 987,654,321 45 | 46 | FILE 47 | end 48 | 49 | def test_should_get_first_code_for_var_content_combo 50 | assert_equal -1954996374, @keeper.code_for("GI_URTE", "()FIKK-INGEFÆR") 51 | end 52 | 53 | def test_should_avoid_duplicates 54 | @keeper.add("GI_URTE", "()FIKK-INGEFÆR") 55 | assert_equal 1, @keeper.contentlist_for("GI_URTE").size 56 | end 57 | 58 | def test_should_keep_codes_for_var 59 | contentlist = @keeper.contentlist_for("GI_URTE") 60 | assert_equal 1, contentlist.size 61 | content = contentlist.first 62 | assert_equal "()FIKK-INGEFÆR", content.text 63 | assert_equal [-1954996374], content.codes 64 | end 65 | 66 | def test_should_keep_variables_separated 67 | @keeper.add("GI_URTE", "$URTE:BEKMYRT++") 68 | @keeper.add("GI_URTE_HOPP", "@1234") 69 | contentlist = @keeper.contentlist_for("GI_URTE") 70 | assert_equal 2, contentlist.size 71 | content = contentlist.first 72 | assert_equal "()FIKK-INGEFÆR", content.text 73 | assert_equal [-1954996374], content.codes 74 | content = contentlist.last 75 | assert_equal "$URTE:BEKMYRT++", content.text 76 | assert_equal [-2109176345], content.codes 77 | end 78 | 79 | end 80 | -------------------------------------------------------------------------------- /test/test_changes_only_compiler.rb: -------------------------------------------------------------------------------- 1 | require 'test/unit' 2 | require 'rubygems' 3 | require 'mocha' 4 | require 'changes_only_compiler' 5 | 6 | class ChangesOnlyCompilerTestCase < Test::Unit::TestCase 7 | 8 | def setup 9 | @compiler = ChangesOnlyCompiler.new "../../../eventyr/master" 10 | end 11 | 12 | def test_should_know_target_directory 13 | assert_equal("../compiled_pages/master", @compiler.target_directory) 14 | end 15 | 16 | def test_should_compile_nothing_for_no_changes 17 | setup_change_tracker([]) 18 | assert_equal([], @compiler.affected_rooms) 19 | end 20 | 21 | def test_should_find_affected_rooms 22 | setup_change_tracker([1]) 23 | assert_equal([9, 8, 1], @compiler.affected_rooms) 24 | end 25 | 26 | def test_should_remove_duplicates 27 | setup_change_tracker([1, 8]) 28 | assert_equal([9, 8, 1], @compiler.affected_rooms) 29 | end 30 | 31 | def test_should_create_pages 32 | setup_change_tracker([9]) 33 | setup_temporary_target 34 | Page.expects(:new).returns(room_mock(9)) 35 | @compiler.compile! 36 | assert File.exists?("temporary_target_folder/9/9.php"), "No master-copy created" 37 | assert File.exists?("temporary_target_folder/versions/9/9_123.php"), "No versions-copy created" 38 | assert_equal(["\n"], IO.readlines("temporary_target_folder/9/9.php")) 39 | assert_equal(["\n"], IO.readlines("temporary_target_folder/versions/9/9_123.php")) 40 | `rm -rf temporary_target_folder` 41 | end 42 | 43 | def test_should_include_files_to_always_compile 44 | @compiler = ChangesOnlyCompiler.new "/", [4165] 45 | setup_change_tracker([1]) 46 | assert_equal([4165, 9, 8, 1], @compiler.affected_rooms) 47 | end 48 | 49 | def test_should_save_current_state 50 | setup_change_tracker([]) 51 | @compiler.change_tracker.expects(:save_current_state!) 52 | @compiler.compile! 53 | end 54 | 55 | private 56 | 57 | def setup_change_tracker(rooms) 58 | @compiler.target_directory = File.dirname(__FILE__) + "/compiler_test_files" 59 | @compiler.change_tracker = mock('change_tracker') 60 | @compiler.change_tracker.stubs(:changed_rooms).returns(rooms) 61 | @compiler.change_tracker.stubs(:save_current_state!) 62 | end 63 | 64 | def setup_temporary_target 65 | `rm -rf temporary_target_folder` 66 | `cp -R #{File.dirname(__FILE__)}/compiler_test_files temporary_target_folder` 67 | @compiler.target_directory = "temporary_target_folder" 68 | end 69 | 70 | def room_mock(number) 71 | @compiler.room_loader.stubs(:room_exists?).returns(true) 72 | room = mock('room') 73 | room.stubs(:code).returns("") 74 | room.stubs(:identifier).returns("#{number}_123") 75 | room 76 | end 77 | 78 | end -------------------------------------------------------------------------------- /test/commands/test_assign_to_variable.rb: -------------------------------------------------------------------------------- 1 | require 'test/unit' 2 | require 'commands/assign_to_variable' 3 | 4 | class CommandsAssignToVariableTestCase < Test::Unit::TestCase 5 | 6 | def test_should_assign_numbers 7 | command = AssignToVariable.parse?("$VAR = 237") 8 | assert_equal('$this->receiver->set_detail("\$VAR", 237);', command.code) 9 | command = AssignToVariable.parse?("$VAR=237") 10 | assert_equal('$this->receiver->set_detail("\$VAR", 237);', command.code) 11 | end 12 | 13 | def test_should_assign_other_variables 14 | command = AssignToVariable.parse?("$VAR = $TMP") 15 | assert_equal('$this->receiver->set_detail("\$VAR", $this->receiver->get_detail("\$TMP"));', command.code) 16 | end 17 | 18 | def test_should_assign_itself 19 | command = AssignToVariable.parse?("$VAR = $VAR * $VAR") 20 | assert_equal('$this->receiver->set_detail("\$VAR", $this->receiver->get_detail("\$VAR") * $this->receiver->get_detail("\$VAR"));', command.code) 21 | end 22 | 23 | def test_should_assign_complex_formulas 24 | command = AssignToVariable.parse?("$VAR = (100 * $TMP + 23) / 15 + $VAR") 25 | assert_equal('$this->receiver->set_detail("\$VAR", (100 * $this->receiver->get_detail("\$TMP") + 23) / 15 + $this->receiver->get_detail("\$VAR"));', command.code) 26 | end 27 | 28 | def test_should_assign_random_values 29 | command = AssignToVariable.parse?("$RANDOM = Tilfeldig: 2 til 4") 30 | assert_equal('$this->receiver->set_to_random("\$RANDOM", 2, 4);', command.code) 31 | end 32 | 33 | def test_should_assign_random_values_with_vars 34 | command = AssignToVariable.parse?("$RANDOM = Tilfeldig: 0 til $MAX") 35 | assert_equal('$this->receiver->set_to_random("\$RANDOM", 0, $this->receiver->get_detail("\$MAX"));', command.code) 36 | command = AssignToVariable.parse?("$RANDOM = Tilfeldig: $MIN til $MAX") 37 | assert_equal('$this->receiver->set_to_random("\$RANDOM", $this->receiver->get_detail("\$MIN"), $this->receiver->get_detail("\$MAX"));', command.code) 38 | end 39 | 40 | def test_should_add_if_known_value 41 | command = AssignToVariable.parse?("$VAR = $VAR + 4") 42 | assert_equal('$this->receiver->add_to_detail("\$VAR", 4);', command.code) 43 | end 44 | 45 | def test_should_set_if_unknown_value 46 | command = AssignToVariable.parse?("$VAR = $VAR + $TMP") 47 | assert_equal('$this->receiver->set_detail("\$VAR", $this->receiver->get_detail("\$VAR") + $this->receiver->get_detail("\$TMP"));', command.code) 48 | end 49 | 50 | def test_should_increment_value 51 | command = AssignToVariable.parse?("$VAR++") 52 | assert_equal('$this->receiver->add_to_detail("\$VAR", 1);', command.code) 53 | end 54 | 55 | def test_should_decrease_value 56 | command = AssignToVariable.parse?("$VAR--") 57 | assert_equal('$this->receiver->add_to_detail("\$VAR", -1);', command.code) 58 | end 59 | 60 | end -------------------------------------------------------------------------------- /test/compiler_test_files/1/1.php: -------------------------------------------------------------------------------- 1 | receiver = $receiver; } 6 | public function execute() { 7 | $this->receiver->room_number_changed(1); 8 | $this->receiver->write("Du vandrer inn blant den store mengden flyktninger som har søkt tilflukt her"); 9 | $this->receiver->write("utenfor katedralen. Mange er skadd. De har dårlig med tepper, dårlig med"); 10 | $this->receiver->write("bandasjer, og dårlig med mat. Du ser en og annen kirketjener som gjør sitt beste"); 11 | $this->receiver->write("for å hjelpe, men de er for få."); 12 | if ($this->con($this->receiver->has_flag("\$MAT"))) { $this->receiver->remove_flag("\$MAT"); } 13 | if ($this->con($this->receiver->has_flag("EPLE"))) { 14 | $this->receiver->add_to_detail("\$MAT", 1); 15 | } 16 | if ($this->con($this->receiver->has_flag("FISK"))) { 17 | $this->receiver->add_to_detail("\$MAT", 1); 18 | } 19 | if ($this->con($this->receiver->has_flag("PØLSE"))) { 20 | $this->receiver->add_to_detail("\$MAT", 1); 21 | } 22 | if ($this->con($this->receiver->has_flag("GAMMELOST"))) { 23 | $this->receiver->add_to_detail("\$MAT", 1); 24 | } 25 | if ($this->con($this->receiver->has_flag("BANANER"))) { 26 | $this->receiver->add_to_detail("\$MAT", 1); 27 | } 28 | if ($this->con($this->receiver->has_flag("VANNMELON"))) { 29 | $this->receiver->add_to_detail("\$MAT", 1); 30 | } 31 | $visible_alternatives = 0; 32 | if ($this->con($this->receiver->has_flag("HEALINGSKROLL"))) { 33 | if ($this->con(!$this->receiver->has_flag("nr3"))) { $this->receiver->add_alternative("Jeg helbreder en kar med store brannskader med min healingskroll.", 3); $visible_alternatives++; } 34 | } 35 | if ($this->con($this->receiver->get_detail("\$MAT") >= 2)) { 36 | if ($this->con(!$this->receiver->has_flag("nr4"))) { $this->receiver->add_alternative("Jeg deler ut litt mat til de sultne.", 4); $visible_alternatives++; } 37 | } 38 | if ($this->con(!((!($this->receiver->has_flag("POSEMEDSMULTRINGER")) && !($this->receiver->has_flag("MARIEKJEKS")))))) { 39 | if ($this->con(!$this->receiver->has_flag("nr5"))) { $this->receiver->add_alternative("Jeg muntrer opp barna med noen søtsaker.", 5); $visible_alternatives++; } 40 | } 41 | if ($this->con($this->receiver->has_flag("HAR-HJULPET-FLYKTNINGER"))) { 42 | if ($this->con(!$this->receiver->has_flag("nr7"))) { $this->receiver->add_alternative("Jeg ser om jeg finner en kirketjener, og spør hva mer jeg kan gjøre.", 7); $visible_alternatives++; } 43 | } 44 | if ($this->con(!(($this->receiver->has_flag("HAR-HJULPET-FLYKTNINGER") && !($visible_alternatives <= 1))))) { 45 | if ($this->con(!$this->receiver->has_flag("nr6"))) { $this->receiver->add_alternative("Det er ikke noe jeg kan gjøre for å hjelpe til, så jeg går.", 6); $visible_alternatives++; } 46 | } 47 | return; 48 | } 49 | 50 | 51 | private function con($bool) { return $this->receiver->conditional($bool); } 52 | } 53 | ?> 54 | -------------------------------------------------------------------------------- /test/compiler_test_files/versions/1/1_-675873966.php: -------------------------------------------------------------------------------- 1 | receiver = $receiver; } 6 | public function execute() { 7 | $this->receiver->room_number_changed(1); 8 | $this->receiver->write("Du vandrer inn blant den store mengden flyktninger som har søkt tilflukt her"); 9 | $this->receiver->write("utenfor katedralen. Mange er skadd. De har dårlig med tepper, dårlig med"); 10 | $this->receiver->write("bandasjer, og dårlig med mat. Du ser en og annen kirketjener som gjør sitt beste"); 11 | $this->receiver->write("for å hjelpe, men de er for få."); 12 | if ($this->con($this->receiver->has_flag("\$MAT"))) { $this->receiver->remove_flag("\$MAT"); } 13 | if ($this->con($this->receiver->has_flag("EPLE"))) { 14 | $this->receiver->add_to_detail("\$MAT", 1); 15 | } 16 | if ($this->con($this->receiver->has_flag("FISK"))) { 17 | $this->receiver->add_to_detail("\$MAT", 1); 18 | } 19 | if ($this->con($this->receiver->has_flag("PØLSE"))) { 20 | $this->receiver->add_to_detail("\$MAT", 1); 21 | } 22 | if ($this->con($this->receiver->has_flag("GAMMELOST"))) { 23 | $this->receiver->add_to_detail("\$MAT", 1); 24 | } 25 | if ($this->con($this->receiver->has_flag("BANANER"))) { 26 | $this->receiver->add_to_detail("\$MAT", 1); 27 | } 28 | if ($this->con($this->receiver->has_flag("VANNMELON"))) { 29 | $this->receiver->add_to_detail("\$MAT", 1); 30 | } 31 | $visible_alternatives = 0; 32 | if ($this->con($this->receiver->has_flag("HEALINGSKROLL"))) { 33 | if ($this->con(!$this->receiver->has_flag("nr3"))) { $this->receiver->add_alternative("Jeg helbreder en kar med store brannskader med min healingskroll.", 3); $visible_alternatives++; } 34 | } 35 | if ($this->con($this->receiver->get_detail("\$MAT") >= 2)) { 36 | if ($this->con(!$this->receiver->has_flag("nr4"))) { $this->receiver->add_alternative("Jeg deler ut litt mat til de sultne.", 4); $visible_alternatives++; } 37 | } 38 | if ($this->con(!((!($this->receiver->has_flag("POSEMEDSMULTRINGER")) && !($this->receiver->has_flag("MARIEKJEKS")))))) { 39 | if ($this->con(!$this->receiver->has_flag("nr5"))) { $this->receiver->add_alternative("Jeg muntrer opp barna med noen søtsaker.", 5); $visible_alternatives++; } 40 | } 41 | if ($this->con($this->receiver->has_flag("HAR-HJULPET-FLYKTNINGER"))) { 42 | if ($this->con(!$this->receiver->has_flag("nr7"))) { $this->receiver->add_alternative("Jeg ser om jeg finner en kirketjener, og spør hva mer jeg kan gjøre.", 7); $visible_alternatives++; } 43 | } 44 | if ($this->con(!(($this->receiver->has_flag("HAR-HJULPET-FLYKTNINGER") && !($visible_alternatives <= 1))))) { 45 | if ($this->con(!$this->receiver->has_flag("nr6"))) { $this->receiver->add_alternative("Det er ikke noe jeg kan gjøre for å hjelpe til, så jeg går.", 6); $visible_alternatives++; } 46 | } 47 | return; 48 | } 49 | 50 | 51 | private function con($bool) { return $this->receiver->conditional($bool); } 52 | } 53 | ?> 54 | -------------------------------------------------------------------------------- /test/compiler_test_files/8/8.php: -------------------------------------------------------------------------------- 1 | receiver = $receiver; } 6 | public function execute() { 7 | $this->receiver->room_number_changed(8); 8 | if ($this->con($this->receiver->has_flag("MARKUS-ER-DØD"))) { 9 | $this->receiver->write("Etter hvert går hulkingen over, og mannen krøller seg sammen under et teppe."); 10 | $this->receiver->write("Uten smerter får han endelig sove."); 11 | $this->receiver->room_number_changed(1); 12 | $visible_alternatives = 0; 13 | if ($this->con($this->receiver->has_flag("HEALINGSKROLL"))) { 14 | if ($this->con(!$this->receiver->has_flag("nr3"))) { $this->receiver->add_alternative("Jeg helbreder en kar med store brannskader med min healingskroll.", 3); $visible_alternatives++; } 15 | } 16 | if ($this->con($this->receiver->get_detail("\$MAT") >= 2)) { 17 | if ($this->con(!$this->receiver->has_flag("nr4"))) { $this->receiver->add_alternative("Jeg deler ut litt mat til de sultne.", 4); $visible_alternatives++; } 18 | } 19 | if ($this->con(!((!($this->receiver->has_flag("POSEMEDSMULTRINGER")) && !($this->receiver->has_flag("MARIEKJEKS")))))) { 20 | if ($this->con(!$this->receiver->has_flag("nr5"))) { $this->receiver->add_alternative("Jeg muntrer opp barna med noen søtsaker.", 5); $visible_alternatives++; } 21 | } 22 | if ($this->con($this->receiver->has_flag("HAR-HJULPET-FLYKTNINGER"))) { 23 | if ($this->con(!$this->receiver->has_flag("nr7"))) { $this->receiver->add_alternative("Jeg ser om jeg finner en kirketjener, og spør hva mer jeg kan gjøre.", 7); $visible_alternatives++; } 24 | } 25 | if ($this->con(!(($this->receiver->has_flag("HAR-HJULPET-FLYKTNINGER") && !($visible_alternatives <= 1))))) { 26 | if ($this->con(!$this->receiver->has_flag("nr6"))) { $this->receiver->add_alternative("Det er ikke noe jeg kan gjøre for å hjelpe til, så jeg går.", 6); $visible_alternatives++; } 27 | } 28 | return; 29 | } 30 | $this->receiver->write("En annen kar som vandrer omkring og hjelper til kommer bort til dere. \"Jeg så"); 31 | $this->receiver->write("hva du gjorde,\" sier han til deg. \"Det var veldig bra av deg. Jeg heter Markus.\""); 32 | $this->receiver->write("Markus er en stor blond kar i en skinnende rustning. \"".$this->receiver->get_nickname()."\" sier du. \"Hyggelig"); 33 | $this->receiver->write("å møte deg,\" sier han. Markus har med suppe, og gir den til den gråtende fyren."); 34 | $this->receiver->write("Han roer seg ned og starter å slurke i seg suppa."); 35 | if ($this->con(!$this->receiver->has_flag("KJENNER-MARKUS"))) { $this->receiver->add_flag("KJENNER-MARKUS"); } 36 | if ($this->con(!$this->receiver->has_flag("MØTTE-MARKUS-UTENFOR-KATEDRALEN"))) { $this->receiver->add_flag("MØTTE-MARKUS-UTENFOR-KATEDRALEN"); } 37 | $visible_alternatives = 0; 38 | if ($this->con(!$this->receiver->has_flag("nr10"))) { $this->receiver->add_alternative("\"Har du noen idé om hvor ille situasjonen her er?\"", 10); $visible_alternatives++; } 39 | if ($this->con(!$this->receiver->has_flag("nr10"))) { $this->receiver->add_alternative("\"Hvor mange har blitt hjemløse etter angrepet?\"", 10); $visible_alternatives++; } 40 | return; 41 | } 42 | 43 | 44 | private function con($bool) { return $this->receiver->conditional($bool); } 45 | } 46 | ?> 47 | -------------------------------------------------------------------------------- /test/compiler_test_files/versions/8/8_-727713902.php: -------------------------------------------------------------------------------- 1 | receiver = $receiver; } 6 | public function execute() { 7 | $this->receiver->room_number_changed(8); 8 | if ($this->con($this->receiver->has_flag("MARKUS-ER-DØD"))) { 9 | $this->receiver->write("Etter hvert går hulkingen over, og mannen krøller seg sammen under et teppe."); 10 | $this->receiver->write("Uten smerter får han endelig sove."); 11 | $this->receiver->room_number_changed(1); 12 | $visible_alternatives = 0; 13 | if ($this->con($this->receiver->has_flag("HEALINGSKROLL"))) { 14 | if ($this->con(!$this->receiver->has_flag("nr3"))) { $this->receiver->add_alternative("Jeg helbreder en kar med store brannskader med min healingskroll.", 3); $visible_alternatives++; } 15 | } 16 | if ($this->con($this->receiver->get_detail("\$MAT") >= 2)) { 17 | if ($this->con(!$this->receiver->has_flag("nr4"))) { $this->receiver->add_alternative("Jeg deler ut litt mat til de sultne.", 4); $visible_alternatives++; } 18 | } 19 | if ($this->con(!((!($this->receiver->has_flag("POSEMEDSMULTRINGER")) && !($this->receiver->has_flag("MARIEKJEKS")))))) { 20 | if ($this->con(!$this->receiver->has_flag("nr5"))) { $this->receiver->add_alternative("Jeg muntrer opp barna med noen søtsaker.", 5); $visible_alternatives++; } 21 | } 22 | if ($this->con($this->receiver->has_flag("HAR-HJULPET-FLYKTNINGER"))) { 23 | if ($this->con(!$this->receiver->has_flag("nr7"))) { $this->receiver->add_alternative("Jeg ser om jeg finner en kirketjener, og spør hva mer jeg kan gjøre.", 7); $visible_alternatives++; } 24 | } 25 | if ($this->con(!(($this->receiver->has_flag("HAR-HJULPET-FLYKTNINGER") && !($visible_alternatives <= 1))))) { 26 | if ($this->con(!$this->receiver->has_flag("nr6"))) { $this->receiver->add_alternative("Det er ikke noe jeg kan gjøre for å hjelpe til, så jeg går.", 6); $visible_alternatives++; } 27 | } 28 | return; 29 | } 30 | $this->receiver->write("En annen kar som vandrer omkring og hjelper til kommer bort til dere. \"Jeg så"); 31 | $this->receiver->write("hva du gjorde,\" sier han til deg. \"Det var veldig bra av deg. Jeg heter Markus.\""); 32 | $this->receiver->write("Markus er en stor blond kar i en skinnende rustning. \"".$this->receiver->get_nickname()."\" sier du. \"Hyggelig"); 33 | $this->receiver->write("å møte deg,\" sier han. Markus har med suppe, og gir den til den gråtende fyren."); 34 | $this->receiver->write("Han roer seg ned og starter å slurke i seg suppa."); 35 | if ($this->con(!$this->receiver->has_flag("KJENNER-MARKUS"))) { $this->receiver->add_flag("KJENNER-MARKUS"); } 36 | if ($this->con(!$this->receiver->has_flag("MØTTE-MARKUS-UTENFOR-KATEDRALEN"))) { $this->receiver->add_flag("MØTTE-MARKUS-UTENFOR-KATEDRALEN"); } 37 | $visible_alternatives = 0; 38 | if ($this->con(!$this->receiver->has_flag("nr10"))) { $this->receiver->add_alternative("\"Har du noen idé om hvor ille situasjonen her er?\"", 10); $visible_alternatives++; } 39 | if ($this->con(!$this->receiver->has_flag("nr10"))) { $this->receiver->add_alternative("\"Hvor mange har blitt hjemløse etter angrepet?\"", 10); $visible_alternatives++; } 40 | return; 41 | } 42 | 43 | 44 | private function con($bool) { return $this->receiver->conditional($bool); } 45 | } 46 | ?> 47 | -------------------------------------------------------------------------------- /lib/changes_only_compiler.rb: -------------------------------------------------------------------------------- 1 | require 'room_loader' 2 | require 'timed_jump_coordinator' 3 | require 'page' 4 | require 'change_tracker' 5 | 6 | class ChangesOnlyCompiler 7 | attr_accessor :source_directory, :target_directory, :change_tracker, :room_loader 8 | 9 | def initialize(source_directory, always_compile = []) 10 | @source_directory = source_directory 11 | @target_directory = "../compiled_pages/#{File.basename(source_directory).downcase}" 12 | @room_loader = RoomLoader.new source_directory 13 | @coordinator = TimedJumpCoordinator.new @room_loader 14 | @change_tracker = ChangeTracker.new @source_directory, @target_directory 15 | @always_compile = always_compile 16 | end 17 | 18 | def compile! 19 | affected_rooms.each do |number| 20 | if @room_loader.room_exists?(number) 21 | #puts "Compiling #{number}" 22 | page = Page.new(number, @room_loader, @coordinator) 23 | dir = number.to_i % 100 24 | Dir.mkdir("#{@target_directory}/#{dir}") unless File.exists?("#{@target_directory}/#{dir}") 25 | File.open("#{@target_directory}/#{dir}/#{number}.php", "w") { |file| file.puts(page.code) } 26 | Dir.mkdir("#{@target_directory}/versions/#{dir}") unless File.exists?("#{@target_directory}/versions/#{dir}") 27 | File.open("#{@target_directory}/versions/#{dir}/#{page.identifier}.php", "w") { |file| file.puts(page.code) } 28 | else 29 | puts "Warning - #{number} used to depend on a changed room, but no longer exists" 30 | end 31 | end 32 | @change_tracker.save_current_state! 33 | end 34 | 35 | def affected_rooms 36 | affected_rooms = @change_tracker.changed_rooms.map { |room| find_affected_rooms_for(room) }.flatten 37 | affected_rooms << @always_compile if affected_rooms.size > 0 38 | affected_rooms.flatten.sort.uniq.reverse 39 | end 40 | 41 | def find_affected_rooms_for(room) 42 | rooms_dependent_on[room] || [room] 43 | end 44 | 45 | def rooms_dependent_on 46 | @rooms_dependencies ||= find_room_dependencies 47 | end 48 | 49 | def find_room_dependencies 50 | # puts "finding all room dependencies... " 51 | start = Time.now 52 | dependencies = {} 53 | lines = `cd #{@target_directory} && find 0*/*.php -print | xargs grep -H "room_number_changed"`.to_a + 54 | `cd #{@target_directory} && find 1*/*.php -print | xargs grep -H "room_number_changed"`.to_a + 55 | `cd #{@target_directory} && find 2*/*.php -print | xargs grep -H "room_number_changed"`.to_a + 56 | `cd #{@target_directory} && find 3*/*.php -print | xargs grep -H "room_number_changed"`.to_a + 57 | `cd #{@target_directory} && find 4*/*.php -print | xargs grep -H "room_number_changed"`.to_a + 58 | `cd #{@target_directory} && find 5*/*.php -print | xargs grep -H "room_number_changed"`.to_a + 59 | `cd #{@target_directory} && find 6*/*.php -print | xargs grep -H "room_number_changed"`.to_a + 60 | `cd #{@target_directory} && find 7*/*.php -print | xargs grep -H "room_number_changed"`.to_a + 61 | `cd #{@target_directory} && find 8*/*.php -print | xargs grep -H "room_number_changed"`.to_a + 62 | `cd #{@target_directory} && find 9*/*.php -print | xargs grep -H "room_number_changed"`.to_a 63 | lines.each do |line| 64 | (dependencies[$2.to_i] ||= []) << $1.to_i if line =~ /(\d+)\.php:\s+\$this->receiver->room_number_changed\((\d+)\);/ 65 | end 66 | # puts "done after #{Time.now - start} seconds." 67 | dependencies 68 | end 69 | 70 | end 71 | -------------------------------------------------------------------------------- /bin/all.rb: -------------------------------------------------------------------------------- 1 | require 'room_loader' 2 | require 'page' 3 | require 'change_tracker' 4 | 5 | abort "Usage: all.rb [rooms folder]" unless ARGV.size == 1 6 | 7 | def linked_room_numbers 8 | puts "finding all linked room numbers... " 9 | start = Time.now 10 | @links = [0] 11 | @visited = [] 12 | @unvisited = [0] 13 | while not @unvisited.empty? do 14 | visit(@unvisited.first) 15 | end 16 | puts "done after #{Time.now - start} seconds." 17 | @links.uniq 18 | end 19 | 20 | def visit(number) 21 | @visited << number 22 | @unvisited.delete number 23 | links = find_links_in_room(number) 24 | @links += links 25 | links.each do |link| 26 | link = link.to_i 27 | if not @visited.include? link and File.exist? room_file(link) 28 | @unvisited << link 29 | end 30 | end 31 | end 32 | 33 | def find_links_in_room(number) 34 | lines = load_room(number) 35 | links = [] 36 | previous = "" 37 | lines.each do |line| 38 | line = line.strip 39 | links += find_links(line, previous) 40 | previous = line 41 | end 42 | links.uniq 43 | end 44 | 45 | def find_links(line, previous) 46 | number = /^\d+$/ 47 | if 48 | line =~ LinkingCommands.jump_to or 49 | line =~ LinkingCommands.alternatives_from or 50 | line =~ LinkingCommands.procedure_call or 51 | line =~ LinkingCommands.conditional_jump 52 | then 53 | [$1] 54 | elsif previous =~ LinkingCommands.change_room or 55 | previous =~ LinkingCommands.delayed_jump 56 | [line] 57 | elsif line =~ number and 58 | not previous =~ LinkingCommands.bodycount and 59 | not previous =~ LinkingCommands.alternative_separator 60 | [line] 61 | else 62 | [] 63 | end 64 | end 65 | 66 | def load_room(number) 67 | File.open(room_file(number)).readlines 68 | end 69 | 70 | def room_file(number) 71 | "#{ARGV[0]}/#{room_folder(number)}/A#{number}.txt" 72 | end 73 | 74 | def room_folder(number) 75 | prefix = number < 1000 ? "A0" : "A" 76 | folder = prefix + (number / 100).to_s 77 | end 78 | 79 | class LinkingCommands 80 | 81 | def self.jump_to 82 | /^@(\d+)$/ 83 | end 84 | 85 | def self.alternatives_from 86 | /^\{@\}(\d+)$/ 87 | end 88 | 89 | def self.procedure_call 90 | /^\(@\)(\d+)$/ 91 | end 92 | 93 | def self.conditional_jump 94 | /^\[@\](\d+)$/ 95 | end 96 | 97 | def self.change_room 98 | /^\[\](\d+)$/ 99 | end 100 | 101 | def self.delayed_jump 102 | /^\{\}(\d+)$/ 103 | end 104 | 105 | def self.bodycount 106 | /^\$\$\$$/ 107 | end 108 | 109 | def self.alternative_separator 110 | /^(\-|\+)$/ 111 | end 112 | 113 | end 114 | 115 | if $0 == __FILE__ 116 | source_folder = ARGV[0] 117 | loader = RoomLoader.new source_folder 118 | coordinator = TimedJumpCoordinator.new loader 119 | destination = "../compiled_pages/#{File.basename(ARGV[0]).downcase}" 120 | linked_room_numbers.each do |number| 121 | if File.exists?(room_file(number.to_i)) then 122 | puts "Compiling #{number}" 123 | page = Page.new(number, loader, coordinator) 124 | dir = number.to_i % 100 125 | Dir.mkdir("#{destination}/#{dir}") unless File.exists?("#{destination}/#{dir}") 126 | File.open("#{destination}/#{dir}/#{number}.php", "w") { |file| file.puts(page.code) } 127 | Dir.mkdir("#{destination}/versions/#{dir}") unless File.exists?("#{destination}/versions/#{dir}") 128 | File.open("#{destination}/versions/#{dir}/#{page.identifier}.php", "w") { |file| file.puts(page.code) } 129 | end 130 | end 131 | ChangeTracker.new(source_folder, destination).save_current_state! 132 | end 133 | 134 | -------------------------------------------------------------------------------- /test/compiler_test_files/9/9.php: -------------------------------------------------------------------------------- 1 | receiver = $receiver; } 6 | public function execute() { 7 | $this->receiver->room_number_changed(9); 8 | $this->receiver->write("Mannen er for opptatt med å strigråte rundt benet ditt til å legge merke til"); 9 | $this->receiver->write("ditt tilbud om økonomisk støtte."); 10 | $this->receiver->room_number_changed(8); 11 | $this->execute_room_8(); 12 | return; 13 | } 14 | private function execute_room_8() { 15 | if ($this->con($this->receiver->has_flag("MARKUS-ER-DØD"))) { 16 | $this->receiver->write("Etter hvert går hulkingen over, og mannen krøller seg sammen under et teppe."); 17 | $this->receiver->write("Uten smerter får han endelig sove."); 18 | $this->receiver->room_number_changed(1); 19 | $visible_alternatives = 0; 20 | if ($this->con($this->receiver->has_flag("HEALINGSKROLL"))) { 21 | if ($this->con(!$this->receiver->has_flag("nr3"))) { $this->receiver->add_alternative("Jeg helbreder en kar med store brannskader med min healingskroll.", 3); $visible_alternatives++; } 22 | } 23 | if ($this->con($this->receiver->get_detail("\$MAT") >= 2)) { 24 | if ($this->con(!$this->receiver->has_flag("nr4"))) { $this->receiver->add_alternative("Jeg deler ut litt mat til de sultne.", 4); $visible_alternatives++; } 25 | } 26 | if ($this->con(!((!($this->receiver->has_flag("POSEMEDSMULTRINGER")) && !($this->receiver->has_flag("MARIEKJEKS")))))) { 27 | if ($this->con(!$this->receiver->has_flag("nr5"))) { $this->receiver->add_alternative("Jeg muntrer opp barna med noen søtsaker.", 5); $visible_alternatives++; } 28 | } 29 | if ($this->con($this->receiver->has_flag("HAR-HJULPET-FLYKTNINGER"))) { 30 | if ($this->con(!$this->receiver->has_flag("nr7"))) { $this->receiver->add_alternative("Jeg ser om jeg finner en kirketjener, og spør hva mer jeg kan gjøre.", 7); $visible_alternatives++; } 31 | } 32 | if ($this->con(!(($this->receiver->has_flag("HAR-HJULPET-FLYKTNINGER") && !($visible_alternatives <= 1))))) { 33 | if ($this->con(!$this->receiver->has_flag("nr6"))) { $this->receiver->add_alternative("Det er ikke noe jeg kan gjøre for å hjelpe til, så jeg går.", 6); $visible_alternatives++; } 34 | } 35 | return; 36 | } 37 | $this->receiver->write("En annen kar som vandrer omkring og hjelper til kommer bort til dere. \"Jeg så"); 38 | $this->receiver->write("hva du gjorde,\" sier han til deg. \"Det var veldig bra av deg. Jeg heter Markus.\""); 39 | $this->receiver->write("Markus er en stor blond kar i en skinnende rustning. \"".$this->receiver->get_nickname()."\" sier du. \"Hyggelig"); 40 | $this->receiver->write("å møte deg,\" sier han. Markus har med suppe, og gir den til den gråtende fyren."); 41 | $this->receiver->write("Han roer seg ned og starter å slurke i seg suppa."); 42 | if ($this->con(!$this->receiver->has_flag("KJENNER-MARKUS"))) { $this->receiver->add_flag("KJENNER-MARKUS"); } 43 | if ($this->con(!$this->receiver->has_flag("MØTTE-MARKUS-UTENFOR-KATEDRALEN"))) { $this->receiver->add_flag("MØTTE-MARKUS-UTENFOR-KATEDRALEN"); } 44 | $visible_alternatives = 0; 45 | if ($this->con(!$this->receiver->has_flag("nr10"))) { $this->receiver->add_alternative("\"Har du noen idé om hvor ille situasjonen her er?\"", 10); $visible_alternatives++; } 46 | if ($this->con(!$this->receiver->has_flag("nr10"))) { $this->receiver->add_alternative("\"Hvor mange har blitt hjemløse etter angrepet?\"", 10); $visible_alternatives++; } 47 | return; 48 | } 49 | 50 | private function con($bool) { return $this->receiver->conditional($bool); } 51 | } 52 | ?> 53 | -------------------------------------------------------------------------------- /test/compiler_test_files/versions/9/9_-961525309.php: -------------------------------------------------------------------------------- 1 | receiver = $receiver; } 6 | public function execute() { 7 | $this->receiver->room_number_changed(9); 8 | $this->receiver->write("Mannen er for opptatt med å strigråte rundt benet ditt til å legge merke til"); 9 | $this->receiver->write("ditt tilbud om økonomisk støtte."); 10 | $this->receiver->room_number_changed(8); 11 | $this->execute_room_8(); 12 | return; 13 | } 14 | private function execute_room_8() { 15 | if ($this->con($this->receiver->has_flag("MARKUS-ER-DØD"))) { 16 | $this->receiver->write("Etter hvert går hulkingen over, og mannen krøller seg sammen under et teppe."); 17 | $this->receiver->write("Uten smerter får han endelig sove."); 18 | $this->receiver->room_number_changed(1); 19 | $visible_alternatives = 0; 20 | if ($this->con($this->receiver->has_flag("HEALINGSKROLL"))) { 21 | if ($this->con(!$this->receiver->has_flag("nr3"))) { $this->receiver->add_alternative("Jeg helbreder en kar med store brannskader med min healingskroll.", 3); $visible_alternatives++; } 22 | } 23 | if ($this->con($this->receiver->get_detail("\$MAT") >= 2)) { 24 | if ($this->con(!$this->receiver->has_flag("nr4"))) { $this->receiver->add_alternative("Jeg deler ut litt mat til de sultne.", 4); $visible_alternatives++; } 25 | } 26 | if ($this->con(!((!($this->receiver->has_flag("POSEMEDSMULTRINGER")) && !($this->receiver->has_flag("MARIEKJEKS")))))) { 27 | if ($this->con(!$this->receiver->has_flag("nr5"))) { $this->receiver->add_alternative("Jeg muntrer opp barna med noen søtsaker.", 5); $visible_alternatives++; } 28 | } 29 | if ($this->con($this->receiver->has_flag("HAR-HJULPET-FLYKTNINGER"))) { 30 | if ($this->con(!$this->receiver->has_flag("nr7"))) { $this->receiver->add_alternative("Jeg ser om jeg finner en kirketjener, og spør hva mer jeg kan gjøre.", 7); $visible_alternatives++; } 31 | } 32 | if ($this->con(!(($this->receiver->has_flag("HAR-HJULPET-FLYKTNINGER") && !($visible_alternatives <= 1))))) { 33 | if ($this->con(!$this->receiver->has_flag("nr6"))) { $this->receiver->add_alternative("Det er ikke noe jeg kan gjøre for å hjelpe til, så jeg går.", 6); $visible_alternatives++; } 34 | } 35 | return; 36 | } 37 | $this->receiver->write("En annen kar som vandrer omkring og hjelper til kommer bort til dere. \"Jeg så"); 38 | $this->receiver->write("hva du gjorde,\" sier han til deg. \"Det var veldig bra av deg. Jeg heter Markus.\""); 39 | $this->receiver->write("Markus er en stor blond kar i en skinnende rustning. \"".$this->receiver->get_nickname()."\" sier du. \"Hyggelig"); 40 | $this->receiver->write("å møte deg,\" sier han. Markus har med suppe, og gir den til den gråtende fyren."); 41 | $this->receiver->write("Han roer seg ned og starter å slurke i seg suppa."); 42 | if ($this->con(!$this->receiver->has_flag("KJENNER-MARKUS"))) { $this->receiver->add_flag("KJENNER-MARKUS"); } 43 | if ($this->con(!$this->receiver->has_flag("MØTTE-MARKUS-UTENFOR-KATEDRALEN"))) { $this->receiver->add_flag("MØTTE-MARKUS-UTENFOR-KATEDRALEN"); } 44 | $visible_alternatives = 0; 45 | if ($this->con(!$this->receiver->has_flag("nr10"))) { $this->receiver->add_alternative("\"Har du noen idé om hvor ille situasjonen her er?\"", 10); $visible_alternatives++; } 46 | if ($this->con(!$this->receiver->has_flag("nr10"))) { $this->receiver->add_alternative("\"Hvor mange har blitt hjemløse etter angrepet?\"", 10); $visible_alternatives++; } 47 | return; 48 | } 49 | 50 | private function con($bool) { return $this->receiver->conditional($bool); } 51 | } 52 | ?> 53 | -------------------------------------------------------------------------------- /todo.org: -------------------------------------------------------------------------------- 1 | * Gjennomgang [8/8] 2 | ** DONE change_tracker.rb 3 | ** DONE changes_only_compiler.rb 4 | ** DONE enterpreter.rb 5 | ** DONE hashcode_keeper.rb 6 | ** DONE page.rb 7 | ** DONE room.rb 8 | ** DONE room_loader.rb 9 | ** DONE timed_jump_coordinator.rb 10 | * Commands [29/30] 11 | ** DONE activate_saving.rb 12 | ** DONE add_deed.rb 13 | ** DONE add_flag.rb 14 | ** DONE add_kongerupi.rb 15 | ** DONE add_timejump.rb 16 | ** DONE assign_to_variable.rb 17 | ** DONE change_location.rb (removed) 18 | ** DONE count_visits.rb 19 | ** DONE end_paragraph.rb 20 | ** DONE end_subpage.rb 21 | ** DONE goto_room.rb 22 | ** DONE increase_bodycount.rb 23 | ** DONE plain_text.rb 24 | ** DONE register_delayed_call.rb 25 | ** DONE reinstate_alternatives.rb 26 | ** DONE remove_alternatives.rb 27 | ** DONE remove_flag.rb 28 | ** DONE run_saved_command.rb 29 | ** DONE summarize_deeds.rb 30 | ** DONE unfinished_story.rb 31 | ** DONE unreachable 32 | ** DONE save_command.rb 33 | ** DONE procedure_call.rb 34 | ** DONE comments 35 | ** DONE conditional_goto.rb 36 | ** DONE alternatives_from_other.rb 37 | ** DONE alternatives.rb 38 | ** DONE conditional_command.rb 39 | ** DONE command_bundle.rb -> if_statement.rb 40 | ** TODO sentence_with_variable.rb 41 | * Commands som gjemmer seg [5/5] 42 | ** DONE Enterpreter.initialize_hashcode_keeper 43 | Denne har meninger om saved_command_regexp og deed_regexp. Scanner alle 44 | filene for disse. 45 | ** DONE timed_jump_coordinator lager old-code 46 | ** DONE timed_jump_coordinator scanner etter delayed calls med regexp 47 | ** DONE timed_jump_coordinator finner room_references med regexp 48 | ** DONE timed_jump_coordinator finner romreferanser i alternativer selv 49 | * Conditionals [4/4] 50 | ** DONE Port fra intelliadv 51 | ** DONE visit count 52 | def test_should_jump_on_visit_count 53 | room = ['[X]3', '623'].extend(Room) 54 | room.number = 453 55 | command = setup_jump_mocks room 56 | assert_equal(expected_code_for_jump_on_visit_count, command.code) 57 | end 58 | 59 | def expected_code_for_jump_on_visit_count 60 | [ 61 | 'if ($this->con($this->receiver->get_detail("\$_VISITS_TO_453") == 3)) {', 62 | ' $this->receiver->room_number_changed(623);', 63 | ' $this->execute_room_623();', 64 | ' return;', 65 | '}' 66 | ] 67 | end 68 | 69 | ---- 70 | 71 | elsif line[0..2] == "[X]" 72 | command = GotoRoom.parse?("@#{room.next}", room, enterpreter) 73 | ConditionalCommand.new Conditional.parse("$_VISITS_TO_#{room.number} == #{line[3..-1]}"), command 74 | 75 | ---- 76 | 77 | def test_should_parse_visit_count_conditional 78 | room = ["[X!]3", "command"].extend(Room) 79 | room.number = 5244 80 | (enterpreter = mock('enterpreter')).stubs(:current_command).returns(PlainText.new("command")) 81 | conditional = ConditionalCommand.parse?(room.current, room, enterpreter) 82 | assert_equal(expected_code_for_visit_count, conditional.code) 83 | end 84 | 85 | def expected_code_for_visit_count 86 | ['if ($this->con($this->receiver->get_detail("\$_VISITS_TO_5244") == 3)) {', 87 | ' $this->receiver->write("command");', 88 | '}'] 89 | end 90 | 91 | ** DONE Conditional.parse i conditional_goto + both_conditions 92 | ** DONE CondTree.parse av $VALUE == 12 93 | * Ekstra problemer [4/6] 94 | ** DONE Hashcoding av Saved Commands vil bli helt feil. 95 | Løsningen er å manuelt endre oppslagene i hashcodefile 96 | ** TODO :tidshopp +1 går til en kommando ->add_timejump(); ... trenger params 97 | ** DONE den gamle løsningen har significant whitespace. 98 | Det funker dårlig med emacs, og det er ørlitt tullete med { blocks } 99 | - må gjøre noe med plain-tekstene som starter med " *", " #" o.l. 100 | 101 | ** DONE
 er ødelagt pga fjerning av whitespace foran --> ha 
først 102 | ** TODO kommentarene kommer ut som plaintext (ref #0) 103 | ** DONE conditional_command kommer ut uten if-setning (ref #100) 104 | -------------------------------------------------------------------------------- /notat.txt: -------------------------------------------------------------------------------- 1 | Er ferdig med testene i testsuiten. Det trengs flere tester. 2 | 3 | ---> Compileren bør klage når den finner uregelmessigheter. For øyeblikket er det 4 | en haug med feil som går rett gjennom. For eksempel [X]IKKE_ET_TALL vil bli 5 | compilert til en PlainText. Den burde heller klage på at [X] krever et tall. 6 | 7 | --------------------- 8 | Hver enkelt side er bygget opp av alle rommene den refererer til før alternativene. Poenget med 9 | denne kompilatoren er dette: Hver side spilleren blir presentert med skal bygges av ett 10 | prekompilert script. Disse scriptene skal være 100% reproduserbare og reversible (gitt en veldig 11 | enkel historikk, som beskrevet under). 12 | 13 | 14 | Uttalt mål: 15 | ----------- 16 | $this->receiver skal være så dum som mulig. All funksjonalitet skal være i den genererte koden. 17 | Denne tanken har fordeler og ulemper: 18 | 19 | - den genererte koden blir veldig stor med mye repetert kode 20 | - gamle sider vil beholde gamle bugs (dette er faktisk en fordel, pga. historikk) 21 | - lett å implementere mange forskjellige receivers, uten duplisering i "live kode" 22 | - for den kompilerte koden er ikke "live" - den er en kompilering av live A00-script, 23 | og da er det ingen krise at ting gjentas. 24 | 25 | 26 | Historikk: 27 | ---------- 28 | Historikken til spilleren skal være på dette formatet: 29 | 30 | [room_number]:[page_number]:[conditionals]:[choice] 31 | 32 | Det er dette $this->receiver->conditional brukes til. Den bygger opp en oversikt 33 | over hva som var resultatet av hver enkelt if-statement. På den måten kan en 34 | nøyaktig historikk alltid gjenoppbygges, fra hvilket som helst tidspunkt. 35 | Den samme metoden brukes av andre receivere til å repetere tidligere hendelser. 36 | 37 | 38 | Rekursjon: 39 | ---------- 40 | Procedure-calls kan skape rekursjon. 41 | Det er lov å deklarere en funksjon inni en annen funksjon i PHP. 42 | Rom som kaller seg selv, bør deklarere seg selv som en funksjon. 43 | 44 | 45 | Sideoversikt: 46 | ------------- 47 | Sammen med alle side-filene, må det bygges opp en oversikt over nyeste rom->side mappings 48 | Denne fila blir temmelig stor, og må lastes inn hver gang. Kan det være en idé å bruke 49 | mappings-fila til å bygge opp en lang rekke symlinker fra rom til korresponderende sidefil? 50 | 51 | Løsningen ble en pages/ mappe som inneholder sider på formatet 1439.php .. denne fila har en 52 | version() som returnerer versjonnummeret. Disse ligger i pages/versions/ a.la 1439_-124857342.php, 53 | og dette versjonsnummeret lagres i historikken. Så når 1439 oppdateres, vil pages/1439.php også 54 | endres, men det vil lages en ny pages/versions/1439_xxx.php-fil. 55 | 56 | 57 | Metoder som kreves av en Receiver: 58 | ---------------------------------- 59 | $this->receiver->write(string) : void 60 | $this->receiver->end_paragraph() : void 61 | $this->receiver->end_subpage() : void 62 | $this->receiver->add_alternative(text, room_number) : void 63 | $this->receiver->saveable() : void 64 | $this->receiver->add_flag(flag) : void 65 | $this->receiver->has_flag(flag) : bool 66 | $this->receiver->remove_flag(flag) : void 67 | $this->receiver->set_detail(flag, value) : void 68 | $this->receiver->add_to_detail(flag, value) : void 69 | $this->receiver->multiply_detail(flag, value) : void 70 | $this->receiver->set_to_random(flag, min, max) : void 71 | $this->receiver->get_detail(flag) : string 72 | $this->receiver->get_detail_to_display(flag) : string 73 | $this->receiver->room_number_changed(room_number) : void 74 | $this->receiver->add_deed(description, type) : void 75 | $this->receiver->conditional(bool) : bool 76 | 77 | For å gjøre SETTING reversibelt, må den gamle verdien taes vare på. Ettersom dette er noe 78 | som bare har med reversering å gjøre, viser det seg å være vanskelig i denne koden. Derfor ser 79 | jeg meg nødt til å flytte reversering og lagring av gamle verdier over til $receiveren. Dessverre. 80 | For at dette ikke skal skje så ofte (vi vil ikke ha krav-tabellen fyllt med backupkopier..), så 81 | legger jeg til "add". Jeg droppet "multiply", siden det skjer så utrolig sjeldent. 82 | 83 | Jeg la på "set_to_random".. implementasjonen av random vil jeg ha mulighet til å tweake, og 84 | er nok greiest å ta på php-siden av saken. Det skal jo slettes ikke være random, men heller 85 | en kraftig seedet tilfeldighetsfunksjon. Gitt samme omstendigheter skal den gi samme tall. 86 | 87 | -------------------------------------------------------------------------------- /test/test_conditional.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | require 'test/unit' 3 | require 'conditional' 4 | 5 | class ConditionalTestCase < Test::Unit::TestCase 6 | 7 | def test_should_parse_true 8 | assert_equal("true", Conditional.build("-").code) 9 | end 10 | 11 | def test_should_know_dates 12 | assert_equal('date("H") > 4 && date("dm") == "3110"', Conditional.build("DATO3110").code) 13 | assert_equal('date("H") > 4 && date("dm") == "0505"', Conditional.build("DATO0505").code) 14 | end 15 | 16 | def test_should_parse_alternative_number_conditionals 17 | assert_equal('$visible_alternatives <= 0', Conditional.build("*0*").code) 18 | end 19 | 20 | def test_should_parse_flags 21 | assert_equal('$this->receiver->has_flag("FLAG")', 22 | c = Conditional.build("FLAG").code) 23 | end 24 | 25 | def test_should_parse_not_statements 26 | assert_equal('!($this->receiver->has_flag("FLAG"))', 27 | Conditional.build([:not, "FLAG"]).code) 28 | end 29 | 30 | def test_should_parse_and_statements 31 | assert_equal('($this->receiver->has_flag("FLAG") && $this->receiver->has_flag("REQUIREMENT"))', 32 | Conditional.build([:and, "FLAG", "REQUIREMENT"]).code) 33 | end 34 | 35 | def test_should_parse_all_statements 36 | assert_equal('($this->receiver->has_flag("FLAG") && $this->receiver->has_flag("JAPP") && $this->receiver->has_flag("BANAN"))', 37 | Conditional.build([:all, "FLAG", "JAPP", "BANAN"]).code) 38 | end 39 | 40 | def test_should_parse_or_statements 41 | assert_equal('($this->receiver->has_flag("FLAG") || $this->receiver->has_flag("REQUIREMENT"))', 42 | Conditional.build([:or, "FLAG", "REQUIREMENT"]).code) 43 | end 44 | 45 | def test_should_parse_some_statements 46 | assert_equal('($this->receiver->has_flag("FLAG") || $this->receiver->has_flag("JAPP") || $this->receiver->has_flag("BANAN"))', 47 | Conditional.build([:some, "FLAG", "JAPP", "BANAN"]).code) 48 | end 49 | 50 | def test_should_parse_this_but_not_that_statements 51 | assert_equal('($this->receiver->has_flag("FLAG") && !$this->receiver->has_flag("REQUIREMENT"))', 52 | Conditional.build([:this_but_not_that, "FLAG", "REQUIREMENT"]).code) 53 | end 54 | 55 | def test_should_parse_not_this_without_that_statements 56 | assert_equal('(!$this->receiver->has_flag("FLAG") || $this->receiver->has_flag("REQUIREMENT"))', 57 | Conditional.build([:not_this_without_that, "FLAG", "REQUIREMENT"]).code) 58 | end 59 | 60 | def test_should_parse_values 61 | assert_equal('$this->receiver->get_detail("\$VALUE") > 0', Conditional.build("$VALUE").code) 62 | assert_equal('$this->receiver->get_detail("\$VALUE") >= 12', Conditional.build("$VALUE >= 12").code) 63 | assert_equal('$this->receiver->get_detail("\$VALUE") != 4', Conditional.build("$VALUE != 4").code) 64 | assert_equal('23 < $this->receiver->get_detail("\$SECOND_VALUE")', Conditional.build("23 < $SECOND_VALUE").code) 65 | assert_equal('$this->receiver->get_detail("\$VALUE") == $this->receiver->get_detail("\$SECOND_VALUE")', Conditional.build("$VALUE == $SECOND_VALUE").code) 66 | assert_equal('$this->receiver->get_detail("\$HØYBORG") < -531243', Conditional.build("$HØYBORG < -531243").code) 67 | assert_equal('$this->receiver->get_detail("\$TIDSPUNKT") > 2 + $this->receiver->get_detail("\$HØRTE_SISTE_RYKTE")', Conditional.build("$TIDSPUNKT > 2 + $HØRTE_SISTE_RYKTE").code) 68 | assert_equal('$this->receiver->get_detail("\$TIDSPUNKT") > $this->receiver->get_detail("\$HØRTE_SISTE_RYKTE") + 2', Conditional.build("$TIDSPUNKT > $HØRTE_SISTE_RYKTE + 2").code) 69 | assert_equal('$this->receiver->get_detail("\$VALUE") + 7 >= 12', Conditional.build("$VALUE + 7 >= 12").code) 70 | end 71 | 72 | def test_should_inject_dates 73 | assert_equal('date("m") == 12', Conditional.build("$_MND == 12").code) 74 | assert_equal('date("H") > 4 && date("d") < 24', Conditional.build("$_DATO < 24").code) 75 | assert_equal('(date("m") == 12 && date("H") > 4 && date("d") < 24)', Conditional.build([:and, "$_MND == 12", "$_DATO < 24"]).code) 76 | end 77 | 78 | def test_should_parse_mixed 79 | assert_equal('($this->receiver->has_flag("PØLSE") && ($this->receiver->get_detail("\$TIDSPUNKT") > 7 && $this->receiver->has_flag("ÆLG")))', 80 | Conditional.build([:and, "PØLSE", [:and, "$TIDSPUNKT > 7", "ÆLG"]]).code) 81 | end 82 | 83 | def test_should_allow_numbers 84 | assert_equal('$this->receiver->get_detail("]C[TEST") == -245371941', ValueConditional.new("]C[TEST", "==", -245371941).code) 85 | end 86 | 87 | def test_should_know_kongerupi 88 | assert_equal('$this->receiver->get_detail("\$_KONGERUPI") >= 12', Conditional.build("kr.12").code) 89 | end 90 | 91 | def test_should_parse_visits 92 | assert_equal('$this->receiver->get_detail("\$_VISITS_TO_453") == 3', 93 | Conditional.build("_BESØK_3", 453).code) 94 | end 95 | 96 | 97 | end 98 | -------------------------------------------------------------------------------- /test/test_enterpreter.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | require 'test/unit' 3 | require 'rubygems' 4 | require 'mocha' 5 | require 'enterpreter' 6 | require 'room' 7 | require 'commands/plain_text' 8 | 9 | class EnterpreterTestCase < Test::Unit::TestCase 10 | 11 | def setup 12 | @room_loader = mock('room_loader') 13 | (@tjump = mock('timed jump coordinator')).stubs(:jump_candidates_for).returns([]) 14 | Enterpreter.reset_hashcode_keeper 15 | @hashcode_keeper = HashcodeKeeper.new 16 | HashcodeKeeper.stubs(:new).returns(@hashcode_keeper) 17 | @enterpreter = Enterpreter.new(0, @room_loader, @tjump) 18 | end 19 | 20 | def test_should_parse_multiple_lines 21 | mock_room(["line1", "line2", "line3"]) 22 | assert_equal(3, @enterpreter.parse(0).size) 23 | end 24 | 25 | def test_should_return_code 26 | mock_room(["line1", "line2", "line3"]) 27 | assert_equal(expected_code_for_three_lines, @enterpreter.enterpret) 28 | end 29 | 30 | def test_should_properly_indent_multiline_code_from_commands 31 | mock_room(["line1", "line2 ?? TEST", "line3"]) 32 | assert_equal(expected_code_for_conditional, @enterpreter.enterpret) 33 | end 34 | 35 | def test_should_allow_registering_and_definition_of_functions 36 | mock_room(["line1", "line2", "line3"]) 37 | @enterpreter.register_function("procedure_call_to_531") 38 | @enterpreter.define_function("procedure_call_to_531", [PlainText.new("line4")]) 39 | assert_equal(expected_code_for_added_functions, @enterpreter.enterpret) 40 | end 41 | 42 | def test_should_allow_checking_if_a_function_exists 43 | assert(!@enterpreter.has_function("alternatives_from_844"), "should not have function yet") 44 | @enterpreter.register_function("alternatives_from_844") 45 | assert(@enterpreter.has_function("alternatives_from_844"), "should have function now") 46 | end 47 | 48 | def test_should_find_all_unqiue_lines_for_a_C_command 49 | @room_loader.expects(:all_room_numbers).returns([1]).times(1) 50 | mock_room(["line1", "TEST => contents", "line2"]) 51 | @hashcode_keeper.expects(:add).with("TEST", "contents") 52 | @hashcode_keeper.expects(:save) 53 | assert_equal @hashcode_keeper, @enterpreter.hashcode_keeper 54 | assert_equal @hashcode_keeper, @enterpreter.hashcode_keeper # test cache 55 | end 56 | 57 | def test_should_find_multiple_C_command_lines_in_one_room 58 | @room_loader.expects(:all_room_numbers).returns([1]) 59 | mock_room(["line1", "TEST => contents", "line2", "TEST => contents2", "line3"]) 60 | @hashcode_keeper.expects(:save) 61 | assert_equal @hashcode_keeper, @enterpreter.hashcode_keeper 62 | assert_equal 2, (list = @hashcode_keeper.contentlist_for("TEST")).size 63 | assert_equal "contents", list.first.text 64 | assert_equal "contents2", list.last.text 65 | end 66 | 67 | def test_should_find_good_and_evil_deeds 68 | @room_loader.expects(:all_room_numbers).returns([1]) 69 | mock_room(["Hei", ':slem => "Slemt altså"', "Hallo", ":snill => Snilt også", ":snill => Mer snilt", ":snill => Mer snilt", "Hadet"]) 70 | @hashcode_keeper.expects(:save) 71 | assert_equal @hashcode_keeper, @enterpreter.hashcode_keeper 72 | assert_equal 1, (list = @hashcode_keeper.contentlist_for("_EVIL_DEED")).size 73 | assert_equal '"Slemt altså"', list.first.text 74 | assert_equal 2, (list = @hashcode_keeper.contentlist_for("_GOOD_DEED")).size 75 | assert_equal "Snilt også", list.first.text 76 | assert_equal "Mer snilt", list.last.text 77 | end 78 | 79 | def test_should_not_change_original_line 80 | room = ['++ KARDEMOMME'].extend(Room) 81 | @enterpreter.current_command(room) 82 | assert_equal('++ KARDEMOMME', room.first) 83 | end 84 | 85 | private 86 | 87 | def mock_room(lines) 88 | @room_loader.stubs(:get).returns(lines.extend(Room)) 89 | end 90 | 91 | def expected_code_for_three_lines 92 | <<-EXPECTED 93 | public function execute() { 94 | $this->receiver->room_number_changed(0); 95 | $this->receiver->write("line1"); 96 | $this->receiver->write("line2"); 97 | $this->receiver->write("line3"); 98 | } 99 | 100 | EXPECTED 101 | end 102 | 103 | def expected_code_for_conditional 104 | <<-EXPECTED 105 | public function execute() { 106 | $this->receiver->room_number_changed(0); 107 | $this->receiver->write("line1"); 108 | if ($this->con($this->receiver->has_flag("TEST"))) { 109 | $this->receiver->write("line2"); 110 | } 111 | $this->receiver->write("line3"); 112 | } 113 | 114 | EXPECTED 115 | end 116 | 117 | def expected_code_for_added_functions 118 | <<-EXPECTED 119 | public function execute() { 120 | $this->receiver->room_number_changed(0); 121 | $this->receiver->write("line1"); 122 | $this->receiver->write("line2"); 123 | $this->receiver->write("line3"); 124 | } 125 | private function procedure_call_to_531() { 126 | $this->receiver->write("line4"); 127 | } 128 | EXPECTED 129 | end 130 | 131 | end 132 | -------------------------------------------------------------------------------- /lib/enterpreter.rb: -------------------------------------------------------------------------------- 1 | require 'commands/plain_text' 2 | require 'commands/add_flag' 3 | require 'commands/remove_flag' 4 | require 'commands/conditional_command' 5 | require 'commands/if_statement' 6 | require 'commands/alternatives' 7 | require 'commands/goto_room' 8 | require 'commands/alternatives_from_other' 9 | require 'commands/procedure_call' 10 | require 'commands/save_command' 11 | require 'commands/run_saved_command' 12 | require 'commands/end_paragraph' 13 | require 'commands/add_deed' 14 | require 'commands/add_kongerupi' 15 | require 'commands/remove_alternatives' 16 | require 'commands/assign_to_variable' 17 | require 'commands/reinstate_alternatives' 18 | require 'commands/conditional_goto' 19 | require 'commands/count_visits' 20 | require 'commands/activate_saving' 21 | require 'commands/unfinished_story' 22 | require 'commands/unreachable' 23 | require 'commands/end_subpage' 24 | require 'commands/increase_bodycount' 25 | require 'commands/add_timejump' 26 | require 'commands/sentence_with_variable' 27 | require 'commands/summarize_deeds' 28 | require 'commands/register_delayed_call' 29 | require 'enumerator' 30 | require 'hashcode_keeper' 31 | require 'timed_jump_coordinator' 32 | 33 | class Enterpreter 34 | attr_reader :room_loader 35 | 36 | def self.command_types 37 | [ 38 | ConditionalCommand, # important that this is first, to always ensure inline ? tests are parsed correctly 39 | IfStatement, 40 | SaveCommand, 41 | RunSavedCommand, 42 | GotoRoom, 43 | ConditionalGoto, 44 | ProcedureCall, 45 | CountVisits, 46 | AssignToVariable, 47 | AlternativesFromOther, 48 | Alternatives, 49 | RemoveAlternatives, 50 | ReinstateAlternatives, 51 | RemoveFlag, 52 | AddFlag, 53 | AddKongerupi, 54 | ActivateSaving, 55 | UnfinishedStory, 56 | Unreachable, 57 | DramaticPause, 58 | IncreaseBodycount, 59 | SummarizeDeeds, 60 | RegisterDelayedCall, 61 | AddTimejump, 62 | AddDeed, 63 | EndParagraph, 64 | SentenceWithVariable, 65 | PlainText 66 | ] 67 | end 68 | 69 | def initialize(room_number, room_loader, timed_jump_coordinator) 70 | @room_loader, @room_number, @timed_jump_coordinator = room_loader, room_number, timed_jump_coordinator 71 | @functions = [] 72 | end 73 | 74 | def enterpret() 75 | code_for(parse(@room_number)) 76 | end 77 | 78 | def parse(room_number) 79 | room = @room_loader.get(room_number) 80 | room.unshift(@timed_jump_coordinator.jump_candidates_for(room_number)).flatten! 81 | commands = [] 82 | begin 83 | commands << current_command(room) 84 | end while room.next 85 | commands 86 | end 87 | 88 | def current_command(room) 89 | prevl = room.index == 0 ? "" : room[room.index-1] 90 | line = room.current 91 | nextl = room.peek or "" 92 | begin 93 | self.class.command_types.inject(false) do |command, type| 94 | command = type.parse?(room.current.clone, room, self) and break command 95 | end 96 | rescue 97 | raise ["", 98 | "--------------------------------------------------------------", 99 | " Error while parsing in room #{room.number}:", 100 | " Stumbled on line:", 101 | " #{prevl}", 102 | " --> #{line}", 103 | " #{nextl}", 104 | " Message: #{$!}", 105 | "--------------------------------------------------------------" 106 | ].join("\n") 107 | end 108 | end 109 | 110 | def register_function(name) 111 | @functions << Function.new(name, []) 112 | end 113 | 114 | def define_function(name, commands) 115 | @functions.find { |f| f.name === name }.commands = commands 116 | end 117 | 118 | def has_function(name) 119 | !! @functions.find { |f| f.name === name } 120 | end 121 | 122 | def hashcode_keeper 123 | @@hashcode_keeper ||= initialize_hashcode_keeper 124 | end 125 | 126 | def self.reset_hashcode_keeper 127 | @@hashcode_keeper = nil 128 | end 129 | 130 | private 131 | 132 | def initialize_hashcode_keeper 133 | filename = "#{File.dirname(__FILE__)}/../resources/hashcodefile.txt" 134 | keeper = HashcodeKeeper.new IO.readlines(filename) 135 | saved_command_regexp = /^([^\n ]+) => ([^\n]+)$/ 136 | deed_regexp = /^(:snill|:slem) => ([^\n]+)$/ 137 | @room_loader.all_room_numbers.each do |number| 138 | room_contents = @room_loader.get(number).join("\n") 139 | room_contents.scan(saved_command_regexp).each {|key, command| keeper.add(key, command) } 140 | room_contents.scan(deed_regexp).each { |type, description| keeper.add((type === ":slem" ? "_EVIL_DEED" : "_GOOD_DEED"), description) } 141 | end 142 | keeper.save(filename) 143 | keeper 144 | end 145 | 146 | def code_for(commands) 147 | code_skeleton.gsub(":ROOMNUMBER:", @room_number.to_s).gsub(":COMMANDS:", indented_commands(commands)).gsub(":FUNCTIONS:", @functions.map { |f| ["private function #{f.name}() {", indented_commands(f.commands), "}"] }.flatten.join("\n")) 148 | end 149 | 150 | def indented_commands(commands) 151 | commands.map { |c| c.code }.flatten.map { |line| " #{line}" }.join("\n") 152 | end 153 | 154 | class Function 155 | attr_reader :name 156 | attr_accessor :commands 157 | def initialize(name, commands) 158 | @name, @commands = name, commands 159 | end 160 | end 161 | 162 | def code_skeleton 163 | <<-CODE 164 | public function execute() { 165 | $this->receiver->room_number_changed(:ROOMNUMBER:); 166 | :COMMANDS: 167 | } 168 | :FUNCTIONS: 169 | CODE 170 | end 171 | 172 | end 173 | -------------------------------------------------------------------------------- /test/commands/test_alternatives.rb: -------------------------------------------------------------------------------- 1 | require 'test/unit' 2 | require 'rubygems' 3 | require 'mocha' 4 | require 'commands/alternatives' 5 | require 'room' 6 | require 'enterpreter' 7 | 8 | class CommandsAlternativesTestCase < Test::Unit::TestCase 9 | 10 | def setup 11 | @room_loader = mock('room_loader') 12 | @room_loader.stubs(:room_exists?).returns(true) 13 | @enterpreter = mock('enterpreter') 14 | @enterpreter.stubs(:room_loader).returns(@room_loader) 15 | end 16 | 17 | def test_should_parse_alternatives 18 | room = ["=", "Alternative 1", "@123", "Alternative 2", "@234"].extend(Room) 19 | alternatives = Alternatives.parse?(room.current, room, @enterpreter) 20 | assert_equal(expected_code_for_alternatives, alternatives.code) 21 | end 22 | 23 | def test_should_allow_blank_lines 24 | room = ["=", "Alternative 1", "@123", "", "Alternative 2", "@234", ""].extend(Room) 25 | alternatives = Alternatives.parse?(room.current, room, @enterpreter) 26 | assert_equal(expected_code_for_alternatives, alternatives.code) 27 | end 28 | 29 | def test_should_quit_when_reaching_block_end 30 | room = ["=", "Alternative 1", "@123", "", "Alternative 2", "@234", "", "}", "more stuff"].extend(Room) 31 | alternatives = Alternatives.parse?(room.current, room, @enterpreter) 32 | assert_equal(expected_code_for_alternatives, alternatives.code) 33 | end 34 | 35 | def test_should_parse_multiline_alternative_texts 36 | room = ["=", "This alternative", "consists of multiple", "lines", " @123", "", "This one", "too", "@234"].extend(Room) 37 | alternatives = Alternatives.parse?(room.current, room, @enterpreter) 38 | assert_equal(expected_code_for_multiline_alternatives, alternatives.code) 39 | end 40 | 41 | def test_should_parse_alternative_with_conditionals 42 | room = ["=", "Alternative", "@345 ? CONDITION"].extend(Room) 43 | conditional_alternatives = Alternatives.parse?(room.current, room, @enterpreter) 44 | assert_equal(expected_code_for_conditional_alternatives, conditional_alternatives.code) 45 | end 46 | 47 | def test_should_always_be_at_end_of_room 48 | room = ["=", "Alternative", "@345 ? CONDITION", "should not be here"].extend(Room) 49 | assert_raise(RuntimeError) { Alternatives.parse?(room.current, room, @enterpreter) } 50 | end 51 | 52 | def test_should_escape_quotation_marks 53 | room = ['=', 'I say "Hello"', '@123'].extend(Room) 54 | assert_equal(expected_code_for_quotations_in_alternatives, Alternatives.parse?(room.current, room, @enterpreter).code) 55 | end 56 | 57 | def test_should_not_include_alternatives_to_nonexistant_rooms 58 | room = ["=", "Alternative 1", "@123", "Nonexitant room alt", "@333", "Alternative 2", "@234"].extend(Room) 59 | @room_loader.stubs(:room_exists?).returns(true, false, true).then.raises("too many calls to room_exists") 60 | assert_equal(expected_code_for_alternatives, Alternatives.parse?(room.current, room, @enterpreter).code) 61 | end 62 | 63 | def test_should_not_include_alternatives_to_nonexistant_rooms 64 | room = ["=", "Alternative 1", "@123", "Nonexitant room alt", "@-1", "Alternative 2", "@234"].extend(Room) 65 | @room_loader.stubs(:room_exists?).returns(true, false, true).then.raises("too many calls to room_exists") 66 | assert_equal(expected_code_for_alternatives, Alternatives.parse?(room.current, room, @enterpreter).code) 67 | end 68 | 69 | def test_should_insert_player_name_in_alternative_text 70 | room = ['=', 'My name is !', '@123'].extend(Room) 71 | assert_equal(expected_code_for_name_in_alternatives, Alternatives.parse?(room.current, room, @enterpreter).code) 72 | end 73 | 74 | def test_should_not_freak_out_over_a_cancelled_procedure_call 75 | room = ['='].extend(Room) # this is a way to quickly exit a procedure call 76 | assert_equal('return true;', Alternatives.parse?(room.current, room, @enterpreter).code) 77 | end 78 | 79 | private 80 | 81 | def expected_code_for_name_in_alternatives 82 | ['$visible_alternatives = 0;', 83 | 'if ($this->con(!$this->receiver->has_flag("nr123"))) { $this->receiver->add_alternative("My name is ".$this->receiver->get_nickname()."!", 123); $visible_alternatives++; }', 84 | 'return;'] 85 | end 86 | 87 | def expected_code_for_alternatives 88 | ['$visible_alternatives = 0;', 89 | 'if ($this->con(!$this->receiver->has_flag("nr123"))) { $this->receiver->add_alternative("Alternative 1", 123); $visible_alternatives++; }', 90 | 'if ($this->con(!$this->receiver->has_flag("nr234"))) { $this->receiver->add_alternative("Alternative 2", 234); $visible_alternatives++; }', 91 | 'return;'] 92 | end 93 | 94 | def expected_code_for_multiline_alternatives 95 | ['$visible_alternatives = 0;', 96 | 'if ($this->con(!$this->receiver->has_flag("nr123"))) { $this->receiver->add_alternative("This alternative consists of multiple lines", 123); $visible_alternatives++; }', 97 | 'if ($this->con(!$this->receiver->has_flag("nr234"))) { $this->receiver->add_alternative("This one too", 234); $visible_alternatives++; }', 98 | 'return;'] 99 | end 100 | 101 | def expected_code_for_conditional_alternatives 102 | ['$visible_alternatives = 0;', 103 | 'if ($this->con($this->receiver->has_flag("CONDITION"))) {', 104 | ' if ($this->con(!$this->receiver->has_flag("nr345"))) { $this->receiver->add_alternative("Alternative", 345); $visible_alternatives++; }', 105 | '}', 106 | 'return;'] 107 | end 108 | 109 | def expected_code_for_quotations_in_alternatives 110 | ['$visible_alternatives = 0;', 111 | 'if ($this->con(!$this->receiver->has_flag("nr123"))) { $this->receiver->add_alternative("I say \"Hello\"", 123); $visible_alternatives++; }', 112 | 'return;'] 113 | end 114 | 115 | end 116 | -------------------------------------------------------------------------------- /lib/cond_tree.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | require "conditional" 3 | 4 | class CondTree 5 | def self.parse(s) 6 | begin 7 | strip_out_containers(req_parse(replace_with_oldies(s), [:container])) 8 | rescue 9 | raise ["", 10 | "--------------------------------------------------------------", 11 | " Error while parsing this string:", 12 | s, 13 | " Message: #{$!}", 14 | "--------------------------------------------------------------" 15 | ].join("\n") 16 | end 17 | end 18 | 19 | def self.cons(el, list) 20 | list.unshift(el) 21 | end 22 | 23 | def self.size(s) 24 | s.split(//u).size 25 | end 26 | 27 | def self.subs(s, from, to = nil) 28 | cs = s.split(//u) 29 | return cs[from..-1].join if to.nil? 30 | cs[from...to].join 31 | end 32 | 33 | def self.req_parse(s, top) 34 | return top if (s.empty?) 35 | return parse_single(s, top) if next_is_single_node(s) 36 | return [:and, top, parse(subs(s, 4))] if s.start_with? " og " 37 | return [:or, top, parse(subs(s, 7))] if s.start_with? " eller " 38 | return [:not_this_without_that, pop_off_not(top), parse(subs(s, 11))] if s.start_with? " uten også " 39 | return [:this_but_not_that, top, parse(subs(s, 11))] if s.start_with? ", men ikke " 40 | return [:and, top, parse(subs(s, 6))] if s.start_with? ", men " 41 | raise "Unable to parse: #{s}" 42 | end 43 | 44 | def self.parse_single(s, top) 45 | return parse_value(s, top) if s =~ value_regexp 46 | return parse_flag(s, top) if s =~ flag_regexp 47 | return parse_all(s, top) if s =~ all_regexp 48 | return parse_some(s, top) if s =~ some_regexp 49 | return parse_parens(s, top) if s.start_with? "(" 50 | return top.push(parse_single(subs(s, 5), [:not])) if s.start_with? "ikke " 51 | raise "Unable to parse single: #{s}" 52 | end 53 | 54 | def self.next_is_single_node(s) 55 | (s =~ value_regexp) or 56 | (s =~ flag_regexp) or 57 | (s =~ all_regexp) or 58 | (s =~ some_regexp) or 59 | (s.start_with? "(") or 60 | (s.start_with? "ikke ") 61 | end 62 | 63 | def self.pop_off_not(top) 64 | raise "Expected :not, but was #{top[0]}" unless top[0] == :not 65 | top[1] 66 | end 67 | 68 | def self.parse_parens(s, top) 69 | req_parse(subs(s, 1 + find_matching_paren_index(s, 1)), 70 | top.push(parse(subs(s, 1, find_matching_paren_index(s, 1))))) 71 | end 72 | 73 | def self.parse_flag(s, top) 74 | match = get_flag(s) 75 | req_parse(subs(s, size(match)), 76 | top.push(match)) 77 | end 78 | 79 | def self.parse_value(s, top) 80 | match = get_value(s) 81 | req_parse(subs(s, size(match)), 82 | top.push(match)) 83 | end 84 | 85 | def self.parse_all(s, top) 86 | match = get_all(s) 87 | req_parse(subs(s, size(match)), 88 | top.push(cons(:all, subs(match, 5).split(/(?:, | og )/)))) 89 | end 90 | 91 | def self.parse_some(s, top) 92 | match = get_some(s) 93 | req_parse(subs(s, size(match)), 94 | top.push(cons(:some, subs(match, 6).split(/(?:, | eller )/)))) 95 | end 96 | 97 | def self.find_matching_paren_index(s, i) 98 | old_i = i 99 | cs = s.split(//u) 100 | num_start = 1 101 | i = i - 1 102 | until num_start == 0 do 103 | i = i + 1 104 | num_start = num_start + 1 if cs[i] == "(" 105 | num_start = num_start - 1 if cs[i] == ")" 106 | raise "found no matching paren from #{old_i} in: #{s}" if cs[i].nil? 107 | end 108 | i 109 | end 110 | 111 | def self.strip_out_containers(n) 112 | return n if n.class == String 113 | return n if n.class == Symbol 114 | return strip_out_containers(n[1]) if n[0] == :container 115 | n.map { |e| strip_out_containers(e) } 116 | end 117 | 118 | def self.replace_with_oldies(s) 119 | s = replace_with_old_date(s) 120 | s = replace_with_old_alt_req(s) 121 | s = replace_with_old_visits(s) 122 | replace_neither(s) 123 | end 124 | 125 | def self.replace_neither(s) 126 | s = s.gsub("(verken ", "ikke (enten ") 127 | s.gsub("verken ", "ikke enten ") 128 | end 129 | 130 | def self.replace_with_old_alt_req(s) 131 | s.gsub(/(\d)\. alternativ/) { |i| "*#{i.to_i - 1}*" } 132 | end 133 | 134 | def self.replace_with_old_date(s) 135 | s.gsub(/den (\d\d)\/(\d\d)/) { "DATO#{$1}#{$2}" } 136 | end 137 | 138 | def self.replace_with_old_visits(s) 139 | s.gsub(/(\d+)\. besøk/) { "_BESØK_#{$1}" } 140 | end 141 | 142 | def self.get_some(s) 143 | get_leading(":some", some_regexp, s) 144 | end 145 | 146 | def self.some_regexp() 147 | /^enten (?:(?:[0-9]+|(?:\$|\]C\[|[nk]r)?[A-ZÆØÅ0-9:.*_-]+)(?: (?:[+*\/<>-]|<=|>=|==) (?:[0-9]+|(?:\$|\]C\[|[nk]r)?[A-ZÆØÅ0-9:.*_-]+))*(?:, )?)+ eller (?:[0-9]+|(?:\$|\]C\[|[nk]r)?[A-ZÆØÅ0-9:.*_-]+)(?: (?:[+*\/<>-]|<=|>=|==) (?:[0-9]+|(?:\$|\]C\[|[nk]r)?[A-ZÆØÅ0-9:.*_-]+))*/ 148 | end 149 | 150 | def self.get_all(s) 151 | get_leading(":all", all_regexp, s) 152 | end 153 | 154 | def self.all_regexp() 155 | /^både (?:(?:[0-9]+|(?:\$|\]C\[|[nk]r)?[A-ZÆØÅ0-9:.*_-]+)(?: (?:[+*\/<>-]|<=|>=|==) (?:[0-9]+|(?:\$|\]C\[|[nk]r)?[A-ZÆØÅ0-9:.*_-]+))*(?:, )?)+ og (?:[0-9]+|(?:\$|\]C\[|[nk]r)?[A-ZÆØÅ0-9:.*_-]+)(?: (?:[+*\/<>-]|<=|>=|==) (?:[0-9]+|(?:\$|\]C\[|[nk]r)?[A-ZÆØÅ0-9:.*_-]+))*/ 156 | end 157 | 158 | def self.get_value(s) 159 | get_leading("value", value_regexp, s) 160 | end 161 | 162 | def self.value_regexp() 163 | /^(?:[0-9]+|(?:\$|\]C\[|[nk]r)?[A-ZÆØÅ0-9:.*_-]+)(?: (?:[+*\/<>-]|<=|>=|==) (?:[0-9]+|(?:\$|\]C\[|[nk]r)?[A-ZÆØÅ0-9:.*_-]+))* [!<>=]=? (?:[0-9]+|(?:\$|\]C\[|[nk]r)?[A-ZÆØÅ0-9:.*_-]+)(?: (?:[+*\/<>-]|<=|>=|==) (?:[0-9]+|(?:\$|\]C\[|[nk]r)?[A-ZÆØÅ0-9:.*_-]+))*/ 164 | end 165 | 166 | def self.get_flag(s) 167 | get_leading("flag", flag_regexp, s) 168 | end 169 | 170 | def self.flag_regexp() 171 | /^^(?:\$|\]C\[|[nk]r)?[A-ZÆØÅ0-9:.*_-]+/ 172 | end 173 | 174 | def self.get_leading(name, regexp, s) 175 | f = s.match(regexp) 176 | raise "#{s} is not recognizeable as a #{name}" if f.nil? 177 | #puts "got leading #{name}: #{f.to_s}" 178 | f.to_s 179 | end 180 | 181 | end 182 | -------------------------------------------------------------------------------- /test/test_cond_tree.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | require 'test/unit' 3 | require 'cond_tree' 4 | 5 | class CondTreeTestCase < Test::Unit::TestCase 6 | 7 | def test_parse_flags 8 | assert_equal("BØLLE", CondTree.parse("BØLLE")) 9 | assert_equal("AKTIVT_RYKTE:HUSKER_I_TREET", CondTree.parse("AKTIVT_RYKTE:HUSKER_I_TREET")) 10 | assert_equal("]C[TEST", CondTree.parse("]C[TEST")) 11 | end 12 | 13 | def test_parse_nots 14 | assert_equal([:not, "BØLLE"], CondTree.parse("ikke BØLLE")) 15 | end 16 | 17 | def test_replace_with_oldies 18 | assert_equal("DATO2412", CondTree.parse("den 24/12")) 19 | end 20 | 21 | def test_replace_with_oldies 22 | assert_equal("_BESØK_3", CondTree.parse("3. besøk")) 23 | end 24 | 25 | def test_parse_all_two 26 | assert_equal([:all, "TØY", "DATO0412"], 27 | CondTree.parse("både TØY og den 04/12")) 28 | end 29 | 30 | def test_parse_all_more 31 | assert_equal([:all, "TØY1", "TØY2", "*1*"], 32 | CondTree.parse("både TØY1, TØY2 og 2. alternativ")) 33 | end 34 | 35 | def test_parse_all_with_expressions 36 | assert_equal([:all, "$SAMUELSEN == 1", "HØRER-VÅPENHUS"], 37 | CondTree.parse("både $SAMUELSEN == 1 og HØRER-VÅPENHUS")) 38 | end 39 | 40 | def test_parse_all_with_nr_and_kr 41 | assert_equal([:all, "nr10825", "kr.19"], 42 | CondTree.parse("både nr10825 og kr.19")) 43 | end 44 | 45 | 46 | def test_parse_some 47 | assert_equal([:some, "TØY1", "TØY2", "*1*"], 48 | CondTree.parse("enten TØY1, TØY2 eller 2. alternativ")) 49 | end 50 | 51 | def test_parse_some_with_expressions 52 | assert_equal([:some, "$SAMUELSEN == 1", "HØRER-VÅPENHUS"], 53 | CondTree.parse("enten $SAMUELSEN == 1 eller HØRER-VÅPENHUS")) 54 | end 55 | 56 | def test_strip_parens 57 | assert_equal("BØLLE", CondTree.parse("(BØLLE)")) 58 | end 59 | 60 | def test_parse_but_without_parens 61 | assert_equal([:this_but_not_that, "BØY1", "BØY2"], 62 | CondTree.parse("BØY1, men ikke BØY2")) 63 | end 64 | 65 | def test_parse_but_without_parens_complex 66 | assert_equal([:this_but_not_that, [:all, "PÅ1", "PÅ2", "PÅ3"], [:all, "PÅ4", "PÅ5"]], 67 | CondTree.parse("både PÅ1, PÅ2 og PÅ3, men ikke både PÅ4 og PÅ5")) 68 | end 69 | 70 | def test_parse_not_this_without_that 71 | assert_equal([:not_this_without_that, "BØY1", "BØY2"], 72 | CondTree.parse("ikke BØY1 uten også BØY2")) 73 | end 74 | 75 | def test_parse_not_this_without_that_complex 76 | assert_equal([:not_this_without_that, [:some, "PÅ1", "PÅ2"], [:all, "PÅ3", "PÅ4"]], 77 | CondTree.parse("verken PÅ1 eller PÅ2 uten også både PÅ3 og PÅ4")) 78 | end 79 | 80 | def test_parse_not_this_without_that_complex_2 81 | assert_equal([:not_this_without_that, [:some, "PÅ1", "PÅ2"], [:and, "PÅ3", "PÅ4"]], 82 | CondTree.parse("verken PÅ1 eller PÅ2 uten også PÅ3 og PÅ4")) 83 | end 84 | 85 | def test_parse_and 86 | assert_equal([:and, "TØY1", "TØY2"], CondTree.parse("TØY1 og TØY2")) 87 | end 88 | 89 | def test_parse_values 90 | assert_equal("$VALUE", CondTree.parse("$VALUE")) 91 | assert_equal("$VALUE >= 12", CondTree.parse("$VALUE >= 12")) 92 | assert_equal("$VALUE != 4", CondTree.parse("$VALUE != 4")) 93 | assert_equal("23 < $SECOND_VALUE", CondTree.parse("23 < $SECOND_VALUE")) 94 | assert_equal("$VALUE == $SECOND_VALUE", CondTree.parse("$VALUE == $SECOND_VALUE")) 95 | assert_equal("$HØYBORG < -531243", CondTree.parse("$HØYBORG < -531243")) 96 | assert_equal("$TIDSPUNKT > 2 + $HØRTE_SISTE_RYKTE", CondTree.parse("$TIDSPUNKT > 2 + $HØRTE_SISTE_RYKTE")) 97 | assert_equal("$TIDSPUNKT > $HØRTE_SISTE_RYKTE + 2", CondTree.parse("$TIDSPUNKT > $HØRTE_SISTE_RYKTE + 2")) 98 | assert_equal("$VALUE + 7 >= 12", CondTree.parse("$VALUE + 7 >= 12")) 99 | assert_equal("$TIDSPUNKT - $VENTELISTE_FOR_BODPLASS >= 40", CondTree.parse("$TIDSPUNKT - $VENTELISTE_FOR_BODPLASS >= 40")) 100 | end 101 | 102 | def test_parse_and_more 103 | assert_equal([:and, "TØY0", [:and, "TØY1", "TØY2"]], 104 | CondTree.parse("TØY0 og TØY1 og TØY2")) 105 | end 106 | 107 | def test_parse_parens 108 | assert_equal([:and, [:not, "PÅ1"], [:all, "PÅ2", "PÅ3"]], 109 | CondTree.parse("(ikke PÅ1) og (både PÅ2 og PÅ3)")) 110 | end 111 | 112 | def test_parse_and_without_parens 113 | assert_equal([:and, [:not, "PÅ1"], [:all, "PÅ2", "PÅ3"]], 114 | CondTree.parse("ikke PÅ1 og både PÅ2 og PÅ3")) 115 | end 116 | 117 | def test_parse_equals_and 118 | assert_equal([:and, [:not, "PÅ1"], [:all, "PÅ2", "PÅ3"]], 119 | CondTree.parse("ikke PÅ1, men både PÅ2 og PÅ3")) 120 | end 121 | 122 | def test_parse_basic_or 123 | assert_equal([:or, "TØY1", "TØY2"], 124 | CondTree.parse("TØY1 eller TØY2")) 125 | end 126 | 127 | def test_parse_or_with_parens 128 | assert_equal([:or, [:not, "PÅ1"], [:all, "PÅ2", "PÅ3"]], 129 | CondTree.parse("(ikke PÅ1) eller (både PÅ2 og PÅ3)")) 130 | end 131 | 132 | def test_parse_or_without_parens 133 | assert_equal([:or, [:not, "PÅ1"], [:all, "PÅ2", "PÅ3"]], 134 | CondTree.parse("ikke PÅ1 eller både PÅ2 og PÅ3")) 135 | end 136 | 137 | def test_parens_needed 138 | assert_equal([:and, [:or, "Æ1", "Æ2"], [:or, "Æ3", "Æ4"]], 139 | CondTree.parse("(Æ1 eller Æ2) og (Æ3 eller Æ4)")) 140 | end 141 | 142 | def test_parens_skipped 143 | assert_equal([:or, "Æ1", [:and, "Æ2", [:or, "Æ3", "Æ4"]]], 144 | CondTree.parse("Æ1 eller Æ2 og Æ3 eller Æ4")) 145 | end 146 | 147 | def test_not_all 148 | assert_equal([:not, [:all, "FØY1", "FØY2"]], 149 | CondTree.parse("ikke både FØY1 og FØY2")) 150 | end 151 | 152 | def test_not_some 153 | assert_equal([:not, [:some, "FØY1", "FØY2"]], 154 | CondTree.parse("ikke enten FØY1 eller FØY2")) 155 | end 156 | 157 | def test_neither 158 | assert_equal([:not, [:some, "FØY1", "FØY2"]], 159 | CondTree.parse("verken FØY1 eller FØY2")) 160 | end 161 | 162 | def test_neither_with_parens 163 | assert_equal([:not, [:some, "FØY1", "FØY2"]], 164 | CondTree.parse("(verken FØY1 eller FØY2)")) 165 | end 166 | 167 | def test_not_neither_with_parens 168 | assert_equal([:not, [:not, [:some, "FØY1", "FØY2"]]], 169 | CondTree.parse("ikke (verken FØY1 eller FØY2)")) 170 | end 171 | 172 | end 173 | -------------------------------------------------------------------------------- /lib/timed_jump_coordinator.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | require 'room' 3 | 4 | # For å lagre den massive jobben det er å finne alle kandidater, holder det å: 5 | # - lagre @all_candidates, som er en map room_number => [candidate, candidate] 6 | # - kjenne alle origins slik at den reindekserer dersom en origin er endret 7 | 8 | # Jeg trodde det var dette som tok lang tid .. det var helt feil, den bruker 6-7 sek på starten 9 | # og så er den ferdig.. ingen grunn til å cache dette. Hovedmengden med tid brukes på å kompilere 10 | # alle rommene.. (sekundært: det tar litt tid å finne alle de linkede rommene) 11 | 12 | # !!! Størst besparing er helt klart å bare kompilere de rommene som er endret eller nye 13 | # - da blir en full scan av alle links unødvendig 14 | # - da blir en full rekompilering unødvendig 15 | 16 | # det er rett og slett kun all.rb som skal byttes ut.. på tide å dra den inn i testcase-varmen? 17 | 18 | class TimedJumpCoordinator 19 | def initialize(room_loader) 20 | @room_loader = room_loader 21 | end 22 | 23 | def candidates_for(room_number) 24 | (candidates = find_candidates[room_number.to_i]) ? candidates.sort.uniq : [] 25 | end 26 | 27 | def jump_candidates_for(room_number) 28 | (candidates = find_candidates[room_number.to_i]) ? generate_code_for(candidates.sort.uniq) : [] 29 | end 30 | 31 | def generate_code_for(candidates) 32 | code = [ 33 | '? $_CALL_DELAY > 0 {', 34 | ' $_CALL_DELAY--', 35 | ] 36 | candidates.each do |number| 37 | code << " (@)#{number} ? $_CALL_DELAY == 0 og $_DELAYED_CALL == #{number}" 38 | end 39 | code << '}' 40 | code 41 | end 42 | 43 | def find_candidates 44 | @all_candidates || find_all_candidates 45 | end 46 | 47 | 48 | # dict: decal = et utsatt prosedyrekall 49 | # executer = et rom som kanskje kjører en decal 50 | # origin = et rom som starter en decal 51 | # target = prosedyrerommet som kalles etter delayen 52 | # candidate = en decal som kanskje skal kjøres i et gitt rom 53 | 54 | def find_all_candidates 55 | @all_candidates = {} 56 | @unhandled_in_a_row = 0 57 | unhandled_decalls = all_decalls.clone 58 | while unhandled_decalls.size > 0 59 | decall = unhandled_decalls.shift 60 | if unhandled_decalls.any? { |unhandled| decall.origin == unhandled.target } then 61 | unhandled_decalls.push decall 62 | @unhandled_in_a_row = @unhandled_in_a_row + 1 63 | if @unhandled_in_a_row > 1000 64 | raise "Too many unhandled decalls in a row" 65 | end 66 | else 67 | handle decall 68 | @unhandled_in_a_row = 0 69 | end 70 | end 71 | @all_candidates 72 | end 73 | 74 | def handle(decall) 75 | origins = ([decall.origin] + procedure_callers(decall.origin) + chained_origins(decall)).sort.uniq 76 | decall_executers = origins.map { |origin| find_executers_for(origin, decall.delay) }.flatten.sort.uniq 77 | decall_executers.each do |executer| 78 | (@all_candidates[executer] ||= []) << decall.target 79 | decall.add_executer(executer) 80 | end 81 | end 82 | 83 | def chained_origins(me) 84 | all_decalls.select { |parent| parent.target == me.origin }.map { |parent| parent.executers }.flatten 85 | end 86 | 87 | def procedure_callers(procedure) 88 | all_procedure_calls.select { |pc| pc.procedure == procedure }.map { |pc| pc.caller } 89 | end 90 | 91 | def find_executers_for(origin, delay) 92 | if delay == 0 then 93 | [] 94 | else 95 | refs = (room_references_in(origin) - [0, 115]) # don't move into starting room or death room 96 | (refs + refs.map {|reference| find_executers_for(reference, delay - 1) }).flatten.sort.uniq 97 | end 98 | end 99 | 100 | # -------------------------------------------------------------# 101 | 102 | def all_procedure_calls 103 | @all_procedure_calls ||= find_all_procedure_calls 104 | end 105 | 106 | def find_all_procedure_calls 107 | procedure_calls = [] 108 | @room_loader.all_room_numbers.each do |caller| 109 | load_room(caller).join("\n").scan(/^\(@\)(\d+)$/m).each do |procedure| 110 | procedure_calls << ProcedureCall.new(caller, procedure.first.to_i) 111 | end 112 | end 113 | procedure_calls 114 | end 115 | 116 | class ProcedureCall 117 | attr_reader :caller, :procedure 118 | def initialize(caller, procedure) 119 | @caller, @procedure = caller, procedure 120 | end 121 | end 122 | 123 | def all_decalls 124 | @all_decalls ||= find_all_decalls 125 | end 126 | 127 | def find_all_decalls 128 | decalls = [] 129 | @room_loader.all_room_numbers.each do |origin| 130 | load_room(origin).join("\n").scan(/^\(@\)(\d+) om (\d+) ...$/m).each do |target, delay| 131 | decalls << Decall.new(target.to_i, delay.to_i, origin) 132 | end 133 | end 134 | decalls 135 | end 136 | 137 | class Decall 138 | attr_reader :target, :delay, :origin, :executers 139 | 140 | def initialize(target, delay, origin) 141 | @target = target 142 | @delay = delay 143 | @origin = origin 144 | @executers = [] 145 | end 146 | 147 | def add_executer(executer) 148 | @executers << executer 149 | end 150 | 151 | # def to_s 152 | # "" 153 | # end 154 | 155 | end 156 | 157 | def room_references_in(number) 158 | room = load_room(number) 159 | room.map! { |line| line =~ /^\(@\)(\d+)$/ ? room_without_alternatives($1) : line } 160 | room.flatten! # adding procedure calls to room body 161 | joined = room.join("\n") 162 | my_jumps = (joined.scan(/^@(\d+)/m)).flatten 163 | my_alternatives = references_in_alternatives_in(room) 164 | other_alternatives = joined.scan(/^\{@\}(\d+)/m).map { |number| references_in_alternatives_in(load_room(number.first)) } 165 | (my_jumps + my_alternatives + other_alternatives).flatten.map { |s| s.to_i } 166 | end 167 | 168 | def references_in_alternatives_in(room) 169 | refs = [] 170 | while room.current do 171 | if room.current === "=" then 172 | while room.next 173 | break if room.current === "}" 174 | refs << $1 if room.current =~ /^@(\d+)/ 175 | end 176 | end 177 | room.next 178 | end 179 | refs 180 | end 181 | 182 | def room_without_alternatives(number) 183 | alternatives_reached = false 184 | load_room(number).select do |line| 185 | alternatives_reached ||= line == '=' 186 | !alternatives_reached 187 | end 188 | end 189 | 190 | def load_room(number) 191 | @room_loader.room_exists?(number) ? @room_loader.get(number) : empty_room(number) 192 | end 193 | 194 | def empty_room(number) 195 | r = [].extend Room 196 | r.number = number 197 | r 198 | end 199 | 200 | end 201 | -------------------------------------------------------------------------------- /test/test_timed_jump_coordinator.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | require 'test/unit' 3 | require 'timed_jump_coordinator' 4 | require 'room' 5 | 6 | class TimedJumpCoordinatorTestCase < Test::Unit::TestCase 7 | 8 | def setup 9 | @rooms = MockRoomLoader.new 10 | @coordinator = TimedJumpCoordinator.new @rooms 11 | end 12 | 13 | def test_should_return_empty_array_when_no_candidates 14 | @rooms.add 0, ['(@)199 om 0 ...', '@1'] 15 | assert_candidates_for_executor([], 1) 16 | end 17 | 18 | def test_should_find_candidates_after_regular_jumps 19 | @rooms.add 0, ['(@)199 om 1 ...', '@1'] 20 | assert_candidates_for_executor([199], 1) 21 | end 22 | 23 | def test_should_find_candidates_after_conditional_jumps 24 | @rooms.add 0, ['(@)199 om 1 ...', '@1 ?? REQ', '@2 ?? UIR'] 25 | assert_candidates_for_executor([199], 1) 26 | assert_candidates_for_executor([199], 2) 27 | end 28 | 29 | def test_should_find_candidates_after_jump_on_visit_count 30 | @rooms.add 0, ['(@)199 om 1 ...', '@1 ? 3. besøk'] 31 | assert_candidates_for_executor([199], 1) 32 | end 33 | 34 | def test_should_find_candidates_after_alternatives_without_reqs 35 | @rooms.add 0, ['(@)199 om 1 ...', '=', 'Alternativ 1', '@1', '', 'Alternativ 2', '@2'] 36 | assert_candidates_for_executor([199], 1) 37 | assert_candidates_for_executor([199], 2) 38 | end 39 | 40 | def test_should_find_candidates_after_alternatives_with_reqs 41 | @rooms.add 0, ['(@)199 om 1 ...', '=', 'Alternativ 1', '@1 ? REQ', 'Alternativ 2', '@2'] 42 | assert_candidates_for_executor([199], 1) 43 | assert_candidates_for_executor([199], 2) 44 | end 45 | 46 | def test_should_find_candidates_from_other_rooms_alternatives 47 | @rooms.add 0, ['(@)199 om 1 ...', '{@}1'] 48 | @rooms.add 1, ['Candidate', '=', 'Alternativ 1', '@1 ? REQ', 'Alternativ 2', '@2'] 49 | assert_candidates_for_executor([199], 1) 50 | assert_candidates_for_executor([199], 2) 51 | end 52 | 53 | def test_should_find_candidates_from_jumps_in_procedure_calls 54 | @rooms.add 0, ['(@)199 om 1 ...', '(@)1'] 55 | @rooms.add 1, ['Candidate', '@155 ?? BØTTE', '=', 'Alternativ 1', '@1 ? REQ', 'Alternativ 2', '@2'] 56 | assert_candidates_for_executor([199], 155) 57 | assert_candidates_for_executor([], 1) 58 | assert_candidates_for_executor([], 2) 59 | end 60 | 61 | def test_should_use_delay_declarations_from_procedure_calls 62 | @rooms.add 0, ['(@)1', '=', 'Alternativ 1', '@1 ? REQ', 'Alternativ 2', '@2'] 63 | @rooms.add 1, ['(@)199 om 1 ...'] 64 | assert_candidates_for_executor([199], 1) 65 | assert_candidates_for_executor([199], 2) 66 | end 67 | 68 | def test_should_find_candidates_on_deeper_levels 69 | @rooms.add 0, ['(@)199 om 2 ...', '@1'] 70 | @rooms.add 1, ['@2'] 71 | @rooms.add 2, ['@3'] 72 | assert_candidates_for_executor([199], 1) 73 | assert_candidates_for_executor([199], 2) 74 | assert_candidates_for_executor([], 3) 75 | end 76 | 77 | def test_should_support_multiple_jumps 78 | @rooms.add 0, ['(@)844 om 1 ...', '(@)199 om 2 ...', '@1'] 79 | @rooms.add 1, ['@2'] 80 | assert_candidates_for_executor([199, 844], 1) 81 | assert_candidates_for_executor([199], 2) 82 | end 83 | 84 | def test_should_support_chained_calls 85 | @rooms.add 0, ['(@)199 om 1 ...', '@1'] 86 | @rooms.add 1, ['@4'] 87 | @rooms.add 4, ['@5'] 88 | @rooms.add 199, ['(@)276 om 1 ...'] 89 | assert_candidates_for_executor([199], 1) 90 | assert_candidates_for_executor([276], 4) 91 | end 92 | 93 | def test_should_delay_decalls_that_arent_ready 94 | @rooms.add 199, ['(@)276 om 1 ...'] 95 | @rooms.add 1000, ['(@)199 om 1 ...', '@1001'] 96 | @rooms.add 1001, ['@1004'] 97 | @rooms.add 1004, ['@1005'] 98 | assert_candidates_for_executor([199], 1001) 99 | assert_candidates_for_executor([276], 1004) 100 | end 101 | 102 | def test_should_complain_about_endless_loops 103 | @rooms.add 199, ['(@)276 om 1 ...', '@1004'] 104 | @rooms.add 276, ['(@)199 om 1 ...', '@1001'] 105 | @rooms.add 1001, ['@1004'] 106 | @rooms.add 1004, [''] 107 | assert_raise(RuntimeError) { @coordinator.jump_candidates_for(1001) } 108 | end 109 | 110 | def test_should_not_make_a_big_deal_of_nonexistant_rooms 111 | @rooms.add 0, ['(@)199 om 2 ...', '@1'] 112 | assert_candidates_for_executor([199], 1) 113 | end 114 | 115 | def test_should_generate_code 116 | @rooms.add 0, ['(@)199 om 1 ...', '@1'] 117 | assert_equal(expected_code, @coordinator.jump_candidates_for(1)) 118 | end 119 | 120 | def expected_code 121 | [ 122 | '? $_CALL_DELAY > 0 {', 123 | ' $_CALL_DELAY--', 124 | ' (@)199 ? $_CALL_DELAY == 0 og $_DELAYED_CALL == 199', 125 | '}' 126 | ] 127 | end 128 | 129 | def assert_candidates_for_executor(candidates, executor) 130 | assert_equal(candidates, @coordinator.candidates_for(executor)) 131 | end 132 | 133 | def basic_decall_setup 134 | @rooms.add 0, dummy_room 135 | @rooms.add 1, ['(@)5 om 1 ...', '@3'] 136 | @rooms.add 2, ['(@)5 om 2 ...', '=', 'Alt3', '@3', 'Alt4', '@4'] 137 | @rooms.add 3, ['(@)7 om 1 ...'] 138 | @rooms.add 4, dummy_room 139 | @rooms.add 5, dummy_room 140 | @rooms.add 7, ['(@)0 om 3 ...', 'Text', '(@)5 om 1 ...', 'More text'] 141 | end 142 | 143 | def dummy_room 144 | ['Nothing to see here', '=', 'Alt1', '@1', 'Alt2', '@2'] 145 | end 146 | 147 | def test_should_find_all_procedure_calls 148 | @rooms.add 0, dummy_room 149 | @rooms.add 1, ['(@)5'] 150 | @rooms.add 2, ['(@)7', '(@)3'] 151 | @rooms.add 3, ['(@)5'] 152 | @rooms.add 5, ['(@)0'] 153 | @rooms.add 7, ['(@)5'] 154 | procedure_calls = @coordinator.all_procedure_calls 155 | assert_equal(6, procedure_calls.size) 156 | assert_equal(3, procedure_calls.select { |call| call.procedure == 5 }.size) 157 | assert_equal([1, 7, 3], @coordinator.procedure_callers(5)) 158 | end 159 | 160 | def test_should_find_all_decalls 161 | basic_decall_setup 162 | decalls = @coordinator.all_decalls 163 | assert_equal(5, decalls.size) 164 | assert_equal(3, decalls.select { |decall| decall.target == 5 }.size) 165 | end 166 | 167 | def test_should_find_executers_for_origin_at_a_given_delay 168 | basic_decall_setup 169 | assert_equal([], @coordinator.find_executers_for(0, 0)) 170 | assert_equal([1, 2], @coordinator.find_executers_for(0, 1)) 171 | assert_equal([1, 2, 3, 4], @coordinator.find_executers_for(0, 2)) 172 | end 173 | 174 | class MockRoomLoader 175 | 176 | def initialize 177 | @rooms = {} 178 | end 179 | 180 | def add(number, lines) 181 | lines.extend Room 182 | lines.number = number 183 | @rooms[number] = lines 184 | end 185 | 186 | def all_room_numbers 187 | @rooms.keys 188 | end 189 | 190 | def room_exists?(number) 191 | @rooms.has_key? number.to_i 192 | end 193 | 194 | def get(number) 195 | number = number.to_i 196 | raise "tried to get unknown room number #{number} from MockRoomLoader" unless @rooms.has_key? number 197 | @rooms[number].clone 198 | end 199 | 200 | end 201 | 202 | end 203 | -------------------------------------------------------------------------------- /lib/conditional.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | require "cond_tree" 3 | 4 | class Conditional 5 | def self.conditionals 6 | [ 7 | TrueConditional, 8 | NotConditional, 9 | AndConditional, 10 | AllConditional, 11 | OrConditional, 12 | SomeConditional, 13 | ThisButNotThatConditional, 14 | NotThisWithoutThatConditional, 15 | DateConditional, 16 | KongerupiConditional, 17 | VisitConditional, 18 | ValueConditional, 19 | AlternativeNumberConditional, 20 | FlagConditional 21 | ] 22 | end 23 | 24 | def self.parse(string, room_number) 25 | begin 26 | build(CondTree.parse(string), room_number) 27 | rescue 28 | raise ["", 29 | "--------------------------------------------------------------", 30 | " Error while parsing in room #{room_number}:", 31 | " Message: #{$!}", 32 | "--------------------------------------------------------------" 33 | ].join("\n") 34 | end 35 | end 36 | 37 | def self.build(node, room_number = 0) 38 | conditionals.inject(false) do |conditional, kind| 39 | conditional = kind.parse?(node, room_number) and break conditional 40 | end 41 | end 42 | end 43 | 44 | class TrueConditional 45 | def self.parse?(node, room_number) 46 | if node === "-" then 47 | new 48 | else 49 | false 50 | end 51 | end 52 | 53 | def code 54 | return "true" 55 | end 56 | end 57 | 58 | class DateConditional 59 | def self.parse?(node, room_number) 60 | if node =~ /^DATO(\d\d\d\d)$/ then 61 | new $1 62 | else 63 | false 64 | end 65 | end 66 | 67 | def initialize(date) 68 | @date = date 69 | end 70 | 71 | def code 72 | "date(\"H\") > 4 && date(\"dm\") == \"#{@date}\"" 73 | end 74 | end 75 | 76 | class FlagConditional 77 | def self.parse?(node, room_number) 78 | new node 79 | end 80 | 81 | def initialize(flag) 82 | @flag = flag 83 | end 84 | 85 | def code 86 | "$this->receiver->has_flag(\"#{@flag}\")" 87 | end 88 | end 89 | 90 | class NotConditional 91 | def self.parse?(node, room_number) 92 | if node[0] == :not then 93 | NotConditional.new(Conditional.build(node[1], room_number)) 94 | else 95 | false 96 | end 97 | end 98 | 99 | def initialize(conditional) 100 | @conditional = conditional 101 | end 102 | 103 | def code 104 | "!(#{@conditional.code})" 105 | end 106 | end 107 | 108 | class AndConditional 109 | def self.parse?(node, room_number) 110 | if node[0] == :and then 111 | one = Conditional.build(node[1], room_number) 112 | other = Conditional.build(node[2], room_number) 113 | instance = new 114 | instance << one 115 | instance << other 116 | instance 117 | else 118 | false 119 | end 120 | end 121 | 122 | def initialize 123 | @options = [] 124 | end 125 | 126 | def add(conditional) 127 | @options << conditional 128 | end 129 | 130 | def <<(conditional) 131 | add(conditional) 132 | end 133 | 134 | def code 135 | ops = @options.map { |o| o.code }.join(" && ") 136 | "(#{ops})" 137 | end 138 | end 139 | 140 | class AllConditional 141 | def self.parse?(node, room_number) 142 | if node[0] == :all then 143 | instance = AndConditional.new 144 | node[1..-1].each { |n| instance << Conditional.build(n, room_number) } 145 | instance 146 | else 147 | false 148 | end 149 | end 150 | end 151 | 152 | class OrConditional 153 | def self.parse?(node, room_number) 154 | if node[0] == :or then 155 | one = Conditional.build(node[1], room_number) 156 | other = Conditional.build(node[2], room_number) 157 | instance = new 158 | instance << one 159 | instance << other 160 | instance 161 | else 162 | false 163 | end 164 | end 165 | 166 | def initialize 167 | @options = [] 168 | end 169 | 170 | def add(conditional) 171 | @options << conditional 172 | end 173 | 174 | def <<(conditional) 175 | add(conditional) 176 | end 177 | 178 | def code 179 | ops = @options.map { |o| o.code }.join(" || ") 180 | "(#{ops})" 181 | end 182 | end 183 | 184 | class SomeConditional 185 | def self.parse?(node, room_number) 186 | if node[0] == :some then 187 | instance = OrConditional.new 188 | node[1..-1].each { |n| instance << Conditional.build(n, room_number) } 189 | instance 190 | else 191 | false 192 | end 193 | end 194 | end 195 | 196 | class ThisButNotThatConditional 197 | def self.parse?(node, room_number) 198 | if node[0] == :this_but_not_that then 199 | this = Conditional.build(node[1], room_number) 200 | that = Conditional.build(node[2], room_number) 201 | new this, that 202 | else 203 | false 204 | end 205 | end 206 | 207 | def initialize(this, that) 208 | @this, @that = this, that 209 | end 210 | 211 | def code 212 | "(#{@this.code} && !#{@that.code})" 213 | end 214 | end 215 | 216 | class NotThisWithoutThatConditional 217 | def self.parse?(node, room_number) 218 | if node[0] == :not_this_without_that then 219 | this = Conditional.build(node[1], room_number) 220 | that = Conditional.build(node[2], room_number) 221 | new this, that 222 | else 223 | false 224 | end 225 | end 226 | 227 | def initialize(this, that) 228 | @this, @that = this, that 229 | end 230 | 231 | def code 232 | "(!#{@this.code} || #{@that.code})" 233 | end 234 | end 235 | 236 | class AlternativeNumberConditional 237 | def self.parse?(node, room_number) 238 | if node =~ /^\*(\d+)\*$/ then 239 | new $1 240 | else 241 | false 242 | end 243 | end 244 | 245 | def initialize(number) 246 | @number = number 247 | end 248 | 249 | def code 250 | "$visible_alternatives <= #{@number}" 251 | end 252 | end 253 | 254 | class ValueConditional 255 | def self.parse?(node, room_number) 256 | if node =~ /^(.*) (<|<=|==|!=|>=|>) (.*)/ then 257 | new $1, $2, $3 258 | elsif node =~ /^(\$[\S]+|\-?\d+)$/ then 259 | new $1, '>', '0' 260 | else 261 | false 262 | end 263 | end 264 | 265 | def initialize(operand1, operator, operand2) 266 | @operand1, @operator, @operand2 = operand1.to_s, operator, operand2.to_s 267 | end 268 | 269 | def code 270 | [get_details(@operand1), @operator, get_details(@operand2)].join(" ") 271 | end 272 | 273 | private 274 | 275 | def get_details(expression) 276 | expression.split(/(\$[^ ]+)/).map do |part| 277 | if part == "$_MND" then 278 | 'date("m")' 279 | elsif part == "$_DATO" then 280 | 'date("H") > 4 && date("d")' 281 | elsif part =~ /^\$([^ ]+)$/ then 282 | "$this->receiver->get_detail(\"\\$#{$1}\")" 283 | elsif part =~ /^\]C\[([^ ]+)$/ then 284 | "$this->receiver->get_detail(\"]C[#{$1}\")" 285 | else 286 | part 287 | end 288 | end.join 289 | end 290 | 291 | end 292 | 293 | class KongerupiConditional 294 | def self.parse?(node, room_number) 295 | if node =~ /^kr\.(\d+)$/ then 296 | ValueConditional.new "$_KONGERUPI", ">=", $1 297 | else 298 | false 299 | end 300 | end 301 | end 302 | 303 | class VisitConditional 304 | def self.parse?(node, room_number) 305 | if node =~ /^_BESØK_(\d+)$/ then 306 | ValueConditional.new "$_VISITS_TO_#{room_number}", "==", $1 307 | else 308 | false 309 | end 310 | end 311 | end 312 | -------------------------------------------------------------------------------- /resources/hashcodefile_original.txt: -------------------------------------------------------------------------------- 1 | ]C[HULLORKER01 2 | Ved siden av hullet ligger det to forkullede lik av orker. Du går nærmere og 3 | 472020804 4 | Ved siden av hullet står det en enorm stein med en del størkna blod på. 5 | -206499030 6 | Ved siden av hullet ligger de oppstykka bitene av tre døde orker. De ble 7 | -469291701 8 | På bakken ved hullet ligger de to opptinede likene av orkene. De har begynt 9 | -2047069082,100414566 10 | 11 | ]C[VÅPENKONTROLL.HB 12 | @11082 13 | 123167991,-2024315657 14 | @9944 15 | -151205540,1996278108 16 | @9948 17 | -151205536,1996278112 18 | @11109 19 | 131146483,-2016337165 20 | 21 | ]C[KJØPEAVØRKENFYR 22 | ()SABOTASJETØNNE 23 | -205130311,1942353337 24 | ()TRESPRITFLASKE 25 | -164624283 26 | ()HVITLØK 27 | 518910420,-1628573228 28 | @2701 29 | 773289336 30 | ()MORDERKNIV 31 | 378525266,-1768958382 32 | 33 | ]C[HULLORKER02 34 | drept her av en kar du summonet fram med skrollen din. 35 | 296588304 36 | å råtne. Oppi hullet ligger det en del kjøttbiter rundt omkring. Ekkelt. 37 | 848000608 38 | ser etter. Nedi hullet ligger det også et forkullet lik. Du smiler skjevt. 39 | -388674888 40 | Under den ligger de knuste kroppene etter to ganske så bøse orker. 41 | -131818879 42 | 43 | ]C[INN_PÅ_MARKEDSPLASSEN 44 | @10856 45 | -479640357,1667843291 46 | @10846 47 | -479708006,1667775642 48 | @10826 49 | -479843304,1667640344 50 | 51 | ]C[TILBAKE7026 52 | @6319 53 | -469984543 54 | @6309 55 | -470052192 56 | 57 | ]C[PRESENTERTSOMLG 58 | "Hau, hau! Du må vere han osteinnkjøpar-karen, du. Kan eg hjelpe deg med noko?" 59 | 352218702 60 | "Hau, hau! Du må vere han derre krigaren. Rett så gildt sverd du har, då." 61 | 1811777373,-335706275 62 | "Herreminjemini! Du er han bestialske mordaren som skal drepe hele bygda!" 63 | 1562828432,-584655216 64 | "Hau, hau! Du må vere den hardbarkade sjørøveren. Eg kjennar saltlukta, ja!" 65 | 1282390560,-865093088 66 | "Hau, hau! Du må vere han derre trollkaren, du." 67 | -166800548 68 | "Hei, framandkar, deg har eg ikkje sedd føyr. Kva er du før ein fyr, då?" 69 | -1691017040,456466608 70 | "Hau, hau! Du må vere han derre rett så kloke fyren ved hoffet, du." 71 | 1357647043,-789836605 72 | "Hau, hau! Du må vere han derre mestertyven som kjem fra nedanfor fjella, du." 73 | -1854324233,293159415 74 | 75 | ]C[GI_URTE 76 | $URTE:DYVELSKREK++ 77 | 165126258 78 | ()FIKK-INGEFÆR 79 | 192487274,-1954996374 80 | $URTE:BEKMYRT++ 81 | 38307303,-2109176345 82 | FIKK-INGEFÆR 83 | 370327905 84 | $URTE:SORTENKE++ 85 | -490807704 86 | $URTE:EINERGRESS++ 87 | 526593223,-1620890425 88 | ()FIKK-KANEL 89 | -165325657,1982157991 90 | FIKK-KANEL 91 | -142131606 92 | ()FIKK-KARDEMOMME 93 | 828115281 94 | $URTE:BUKKEHORNKLØVER++ 95 | -127151772 96 | FIKK-KARDEMOMME 97 | 551161924 98 | $URTE:GROMGRAS++ 99 | 939032176,-1208451472 100 | ()FIKK-NELLIK 101 | -130764049 102 | $URTE:TANNISROT++ 103 | -238363893 104 | FIKK-NELLIK 105 | -384814614 106 | $URTE:MÅNEBLOM++ 107 | 755174153,-1392309495 108 | $URTE:ULVETUNGE++ 109 | 653133952,-1494349696 110 | $URTE:GULLSKIMMER++ 111 | -668682459,1478801189 112 | 113 | ]C[KUTTATAUSTIGEN 114 | Du kan se at orkene har hengt opp en ny taustige. Du kappa jo den gamle. 115 | -999983909 116 | 117 | ]C[KATTUNGEN 118 | Brått ser du det komme en liten kattunge ut av et smug. Den mjauer søtt. 119 | 1752127534,-395356114 120 | 121 | ]C[ØRKENDEST 122 | @6663 123 | -444087274 124 | @6665 125 | -444087272 126 | @6668 127 | -444087268 128 | @6670 129 | -444019628 130 | @6661 131 | -444087276 132 | 133 | ]C[LENNARTS-OPPSLAGSTAVLE 134 | "Erfaren smed og murer ønskes for sikring av byens tre brønner." "Drømmetydning for folk flest, spør etter Elna." "Stort tørt håndkle ønskes kjøpt." "Byvakten ønsker vitneutsagn i forbindelse med et voldsomt drønn i nærheten av Skipperkroa." "Krystallklart, renset, rensende kildevann på flasker selges på markedsplassen. Begrenset opplag. Kjøp nå!" 135 | 201903418 136 | "Kjeller leies ut til høystbydende, oppussingsobjekt. Riktig mye rengjøring må medregnes." "Lykkeridderens Leverandør har fått inn ekstra lanterner og flasker med olje, så nå er det nok til alle. Kom innom før du tar turen til lysfattige fangehull og mørke ganger." 137 | 1889191531,-258292117 138 | "Overkonstabel for Fotgjengere, Ryttere og Vognførere oppfordrer allmenheten tilforsiktighet i Høyborgs gater." "Skakkkjørt vogn uten hest selges. Opphugging til pinneved skjer ved kjøpers egen regning." "Bøtte på bøtte med lim selges på markedsplassen, spør etter herr Karlson." 139 | 2021958104,-125525544 140 | "Eier av svart katt ettersøkes." "Omreisende teater søker ny trapêsartist." "Speil selges - oppussingsobjekt." "Kjemiker søkes for fjerning av maling fra dress. Kan betale i kongerupi eller ett stykk stige." "Har du sett Pussi? Hun forsvant fra vårt hjem klokken tretten fredag i forrige uke." 141 | -1750338146,397145502 142 | Med en viss overraskelse observerer du at oppslagstavlen er borte. En hvit firkant i malingen er det eneste som er igjen. Det, og en liten påteipet lapp. "Ny oppslagstavle er bestilt. Den gamle var neddynket av klumpete magesyre. -Lennart" 143 | -1541809093,605674555 144 | "En haug med forkullede rester av falleferdige hus ønskes solgt, eller byttet i tipi." "Emba og Kamuf Larsen søker kost og losji hos romslig velgjører." "Aske etter nedbrent vindmølle byttes i velholdt pappeske." "Skrammel og skrot ønskes solgt for en slikk, eller ingenting." 145 | 760476879 146 | "Trivelig ung mann med vogn full av Vipafrukt ønsker jobb." "Skrammel selges tilhøyestbydende på markedsplassen. Noe kruttslam må medregnes." "Viktig meddelelsefra Kloakkansvarlig Turgelsen: Det er ikke under noen omstendighet lov for den gemene hop å bevege seg i Høyborgs kloakk. Det er ingen eventyr å finne, kun slam, slum og slim." 147 | 958008845 148 | "Hevn ønskes, fortrinnsvis kald." "Er du en dreven forsvarsspiller i dengeball? Ta kontakt med Egil." "Navnet på de involverte selges. Sonny." "Når du svever mellom død og liv, bruk healingsskroll fra din vennlige antikvitetsforhandler i Svarthaug." "Dirigent ønskes til djevelens orkester." "Om folk vennligst kan slutte å snike i køen til depotet. Takk." 149 | 2124859231,-22624417 150 | 151 | ]C[HELVETEFOLK1 152 | Rett foran deg i køen står de to vaktene du nettopp drepte. De ser forvirret rundt seg. Den ene har et stygt sår i ansiktet, den andre henger slapt med skulderen. Du smiler skjevt. 153 | -10748949 154 | Rett foran deg i køen står de to lattermilde piratene du tok livet av. 155 | 1875589298,-271894350 156 | Foran deg i køa ser du sjørøveren du drepte. Han snerrer mot deg. 157 | 1030221902 158 | 159 | ]C[SKJELETTRESTER 160 | Det ligger noen skjelettbein på gulvet, enkelte av dem knust. 161 | 913940422,-1233543226 162 | 163 | ]C[FISKERDRAUME 164 | Ved kanten av vannet står en kar med stor grønn hatt og fisker. Draume? 165 | -2983907 166 | Du sniker deg stille ut. 167 | 1970921284,-176562364 168 | 169 | ]C[HOPP_ETTER_KALENDERGAVE 170 | @13125 171 | -406240773 172 | @13059 173 | -414557499 174 | @13159 175 | -406037822 176 | @13082 177 | -414354559 178 | @13138 179 | -406173121 180 | @13127 181 | -406240771 182 | @13072 183 | -414422208 184 | @13162 185 | -405970180 186 | @13129 187 | -406240769 188 | @13074 189 | -414422206 190 | @13130 191 | -406173129 192 | @13119 193 | -406308418 194 | @13086 195 | -414354555 196 | @13164 197 | -405970178 198 | @13131 199 | -406173128 200 | @13120 201 | -406240778 202 | @13132 203 | -406173127 204 | @13110 205 | -406308427 206 | @13121 207 | -406240777 208 | @13077 209 | -414422203 210 | @13056 211 | -414557502 212 | @13134 213 | -406173125 214 | @13079 215 | -414422201 216 | @13168 217 | -405970174 218 | @13157 219 | -406037824 220 | 221 | ]C[GJENNOM_FOLKSOM_GATE 222 | @9966 223 | 1996413408,-151070240 224 | @10865 225 | 1667910939,-479572709 226 | 227 | ]C[ORKERDREPTTÅRN01 228 | elendige orkene. Likene av orkene ligger spredt rundt det ødelagte vakttårnet 229 | -307062000 230 | orkene med steinskrollen din. Det lukter egentlig ganske ille her. Likene til 231 | -1668381823,479101825 232 | orkene. To av dem ihvertfall. Også ødela du vakttårnet dems. Men så feka du 233 | -1082142069,1065341579 234 | tåpelige orkene. Toppen av treet vakttårnet var er sprengt i fillebiter, og 235 | -1996452648,151031000 236 | 237 | ]C[VAMPYRSEIER 238 | Vampyren bryter ut av den skrekkelige angsten og slår bibelen fra dine hender. 239 | -394579156,1752904492 240 | Med stor innsats tar vampyren tak rundt hvitløken og røsker den av seg. 241 | -568664879 242 | Vampyren overvinner sin angst for korset, farer mot deg og slår det til siden. 243 | 606607340,-1540876308 244 | 245 | ]C[ØRKENGÅING6675 246 | {@}6816 247 | 77234718,-2070248930 248 | {@}6794 249 | 69256231,-2078227417 250 | {@}6662 251 | 60533605,-2086950043 252 | {@}6795 253 | 69256232,-2078227416 254 | {@}6663 255 | 60533606,-2086950042 256 | {@}6817 257 | 77234720,-2070248928 258 | {@}6796 259 | 69256233,-2078227415 260 | {@}6664 261 | 60533607,-2086950041 262 | {@}6818 263 | 77234721,-2070248927 264 | {@}6797 265 | 69256234,-2078227414 266 | {@}6665 267 | 60533608,-2086950040 268 | {@}5884 269 | -593054562,1554429086 270 | {@}6819 271 | 77234722,-2070248926 272 | {@}6666 273 | 60533609,-2086950039 274 | {@}6402 275 | 43088357,-2104395291 276 | {@}6810 277 | 77234712,-2070248936 278 | {@}6809 279 | 77167073,-2070316575 280 | {@}6799 281 | 69256236,-2078227412 282 | {@}6667 283 | 60533610,-2086950038 284 | {@}6811 285 | 77234713,-2070248935 286 | {@}6800 287 | 77167063,-2070316585 288 | {@}6789 289 | 69188587,-2078295061 290 | {@}6668 291 | 60533611,-2086950037 292 | {@}6360 293 | 34974572,-2112509076 294 | {@}5381 295 | -635652950,1511830698 296 | {@}6812 297 | 77234714,-2070248934 298 | {@}6801 299 | 77167064,-2070316584 300 | {@}6790 301 | 69256227,-2078227421 302 | {@}6669 303 | 60533612,-2086950036 304 | {@}5723 305 | -601980134,1545503514 306 | {@}6813 307 | 77234715,-2070248933 308 | {@}6670 309 | 60601252,-2086882396 310 | {@}6814 311 | 77234716,-2070248932 312 | {@}6792 313 | 69256229,-2078227419 314 | {@}6671 315 | 60601253,-2086882395 316 | {@}6815 317 | 77234717,-2070248931 318 | {@}6793 319 | 69256230,-2078227418 320 | {@}6661 321 | 60533604,-2086950044 322 | {@}4342 323 | 706579554 324 | 325 | ]C[DRAGENDOEDUTENFOR01 326 | Utenfor hula ligger dragens hodeløse, råtnende lik. Det surrer fluer rundt 327 | 1882268995,-265214653 328 | Utenfor hulen ligger den døde dragen og råtner i solsteken. 329 | 444275311 330 | Utenfor hula ligger dragen. Hodet ligger knust under en enorm stein, og 331 | -1994356139,153127509 332 | Utenfor hula ligger det råtnende liket av dragen Magnus. Det er en veldig 333 | 1553985485,-593498163 334 | 335 | ]C[ORKERDREPTTÅRN02 336 | og råtner. Det surrer fluer rundt dem og det lukter egentlig ganske vondt. 337 | 853875586 338 | det til. Hvorfor ikke drepe den våkne orken først, og SÅ lete etter ting? 339 | -890588336 340 | orkene ligger vel fortsatt knust utover i det lille tårnet oppe i treet. 341 | 1630261041,-517222607 342 | biter av orkene ligger rundt og råtner. Ikke et deilig syn akkurat. 343 | 1138465132,-1009018516 344 | det til. Hvorfor ikke bry deg mer om den våkne orken enn den besvimte? 345 | 2111696613,-35787035 346 | 347 | ]C[FJERN_KALENDERGAVE 348 | #13163 349 | -391240007,1756243641 350 | #13141 351 | -391375307,1756108341 352 | #13064 353 | -399759683,1747723965 354 | #13053 355 | -399827333,1747656315 356 | #13076 357 | -399692032,1747791616 358 | #13165 359 | -391240005,1756243643 360 | #13055 361 | -399827331,1747656317 362 | #13155 363 | -391307654,1756175994 364 | #13133 365 | -391442954,1756040694 366 | #13122 367 | -391510604,1755973044 368 | #13123 369 | -391510603,1755973045 370 | #13124 371 | -391510602,1755973046 372 | #13080 373 | -399624389,1747859259 374 | #13069 375 | -399759678,1747723970 376 | #13136 377 | -391442951,1756040697 378 | #13137 379 | -391442950,1756040698 380 | #13126 381 | -391510600,1755973048 382 | #13160 383 | -391240010,1756243638 384 | #13116 385 | -391578249,1755905399 386 | #2973 387 | -3295739,2144187909 388 | #13128 389 | -391510598,1755973050 390 | #13073 391 | -399692035,1747791613 392 | #13085 393 | -399624384,1747859264 394 | 395 | ]C[GI_URTE_HOPP 396 | {@}4738 397 | 740590620 398 | {@}13066 399 | 360779683 400 | @5565 401 | 889896150,-1257587498 402 | {@}12787 403 | -384427828 404 | {@}12622 405 | -393353404 406 | {@}12623 407 | -393353403 408 | {@}4510 409 | 723415959 410 | @6742 411 | -435702896 412 | {@}459 413 | -139522286,2007961362 414 | {@}10063 415 | 93321681,-2054161967 416 | {@}1451 417 | 447708880,-1699774768 418 | {@}4346 419 | 706579558 420 | @3994 421 | -551565565,1595918083 422 | {@}275 423 | -156426347,1991057301 424 | @976 425 | 1005360833,-1142122815 426 | {@}12636 427 | -393285750 428 | {@}10889 429 | 161614402,-1985869246 430 | {@}1090 431 | 413900767,-1733582881 432 | {@}6370 433 | 35042221,-2112441427 434 | {@}750 435 | -113963265,2033520383 436 | {@}7229 437 | 831164857,-1316318791 438 | {@}178 439 | -164946021,1982537627 440 | {@}7064 441 | 814396094,-1333087554 442 | {@}3993 443 | -46944687 444 | {@}135 445 | -165216620,1982267028 446 | {@}5778 447 | -601641884,1545841764 448 | {@}13066 449 | -886071589 450 | {@}9081 451 | 277008839 452 | {@}2323 453 | -903516841 454 | {@}345 455 | -148109617,1999374031 456 | {@}12652 457 | -393150457 458 | {@}2598 459 | -886003939 460 | @4045 461 | 42181918,-2105301730 462 | {@}1907 463 | 489969027,-1657514621 464 | @13058 465 | -414557500 466 | @421 467 | 962424198,-1185059450 468 | 469 | ]C[GGGSALG 470 | )(GULLARMBÅND 471 | 790958531 472 | )(MAGISKRING 473 | -64587173,2082896475 474 | )(GREVEAMULETT 475 | 38818782 476 | )(LITENDIAMANT 477 | -912083019 478 | )(SPESIELLRING 479 | -594986711 480 | )(LITENSAFIR 481 | -295692738 482 | )(PRINSMILDSKRONE 483 | -816986698,1330496950 484 | )(ZOMBIEGULLRING 485 | 675130934,-1472352714 486 | )(LITENRUBIN 487 | -840374864,1307108784 488 | )(GYLDENTDRIKKEBEGER 489 | 989166227 490 | )(BROSJE 491 | 252276370,-1895207278 492 | )(PERLEKJEDE 493 | 904877211,-1242606437 494 | )(GULLØYEEPLE 495 | 728051034 496 | )(LITENSMARAGD 497 | 367119542,-1780364106 498 | )(DIAMANTBESATTGULLRING 499 | -1016366433 500 | )(VAKKERGULLRING 501 | 649889976 502 | )(LITENGULLKLUMP 503 | -634928871,1512554777 504 | )(SØLVBEGER 505 | -828061118,1319422530 506 | 507 | ]C[VIDERE_DIAMETRALSK 508 | @9976 509 | 1996481056,-151002592 510 | @9934 511 | 1996210459,-151273189 512 | @10857 513 | 1667843292,-479640356 514 | @10846 515 | 1667775642,-479708006 516 | @10813 517 | 1667572692,-479910956 518 | @10858 519 | 1667843293,-479640355 520 | @10847 521 | 1667775643,-479708005 522 | @10837 523 | 1667707994,-479775654 524 | @10826 525 | 1667640344,-479843304 526 | @10838 527 | 1667707995,-479775653 528 | @10827 529 | 1667640345,-479843303 530 | 531 | ]C[TABORTHJELM 532 | )(HORNHJELM 533 | -541084762 534 | )(ROSEMØNSTERHJELM 535 | -901164962 536 | 537 | ]C[FÅTILBAKEBANANBUTIKK 538 | ()LITENDIAMANT 539 | 645591365 540 | ()LITENSMARAGD 541 | -222689722 542 | ()PRINSMILDSKRONE 543 | -2018630350,128853298 544 | ()SØLVBEGER 545 | -1561465125,586018523 546 | ()MAGISKRING 547 | -663773453 548 | ()KJEMPEPERLE 549 | 675556159 550 | ()GULLARMBÅND 551 | -2080552109,66931539 552 | ()DIAMANTBESATTGULLRING 553 | 28717363 554 | ()LITENSAFIR 555 | 1252604630,-894879018 556 | ()VAKKERGULLRING 557 | -403121344 558 | ()PERLEKJEDE 559 | 305690931 560 | ()LITENRUBIN 561 | -1573778872,573704776 562 | ()ZOMBIEGULLRING 563 | 1903820990,-243662658 564 | ()GYLDENTDRIKKEBEGER 565 | 678497035 566 | ()LITENGULLKLUMP 567 | 459543457 568 | 569 | ]C[DRAGENDOEDUTENFOR02 570 | kroppen har begynt å råtne. Det myldrer av fluer. 571 | -666136331 572 | kroppen, deler av hodet ligger spredt rundt, eller splattet oppover veggen. 573 | 823170639 574 | rift i magen, og han har begynt å råtne. Fluer surrer rundt liket. 575 | 721464398 576 | 577 | ]C[HOPP_ETTER_MANGLENDE_LANTERNE 578 | {@}8795 579 | -468266318 580 | 581 | ]C[UT_FRA_MARKEDSSENTERET 582 | @10828 583 | 1667640346,-479843302 584 | @10858 585 | 1667843293,-479640355 586 | @10848 587 | 1667775644,-479708004 588 | @10838 589 | 1667707995,-479775653 590 | 591 | ]C[GGGPRIS 592 | kr.25 593 | 876801067 594 | kr.70 595 | 877139307 596 | kr.50 597 | 877004009 598 | kr.30 599 | 876868711 600 | kr.20 601 | 876801062 602 | kr.10 603 | 876733413 604 | kr.90 605 | 877274604 606 | kr.45 607 | 876936365 608 | kr.80 609 | 877206955 610 | 611 | ]C[ØRKENGÅING 612 | @6696 613 | -443884324 614 | @4938 615 | -2028692282,118791366 616 | @676 617 | -1167681846,979801802 618 | @1782 619 | -31150020 620 | @15 621 | 548574184 622 | @427 623 | -1185059444,962424204 624 | @1168 625 | -82403374 626 | @4899 627 | -2036806064,110677584 628 | @155 629 | -1210415530,937068118 630 | 631 | ]C[HULLIVEGGFIKSET 632 | Du sjekker muren. Det ser ut til at noen har tettet igjen hullet du gravde. 633 | -991780430 634 | Du sjekker muren, og det ser ut til at noen har fikset hullet du sprengte. 635 | 488223213 636 | 637 | ]C[GI_URTE_TEKST 638 | @6762 639 | -435567598 640 | Du røsker med deg hele bunten av gyldent gress og legger i urtetaska. 641 | 437339683,-1710143965 642 | Du river til deg en tust med gromgraset og legger det i urtetaska. 643 | 201420648 644 | Du legger barkrullen forsiktig i taska. 645 | 202541482,-1944942166 646 | Du løsner dyvelskreken fra jordsmonnet, børster rota for jord, og legger den forsiktig i urtetaska. 647 | 115639078,-2031844570 648 | Du rasker til deg så mye gromgras som du kan, og legger det i urtetaska. 649 | -965541382,1181942266 650 | Forsiktig plukker du gullskimmeren og legger den i din urtetaske. 651 | -48627824,2098855824 652 | Du plukker gullskimmeren forsiktig, beundrer dens vakre kronblader og legger deni urtetasken. 653 | -35257277,2112226371 654 | Du slenger rota opp i urtetaska og grøsser. 655 | 162222468 656 | Du legger planten varsomt ned i urtetaska. 657 | 797269958 658 | Du plukker planten nennsomt og legger den på plass i urtetaska. 659 | 325182651,-1822300997 660 | Du røsker det opp med rota og legger det i urtetaska. 661 | -96772710,2050710938 662 | Du røsker til deg det du finner og legger det i urtetaska. 663 | -770505935 664 | Du legger gresset på plass i din urtetaske. 665 | -119512284 666 | Du legger rota forsiktig i taska. 667 | 571191239 668 | Du røsker til deg det som er av gromgras ved brønnen og stapper det i urtetaska. 669 | -66004070,2081479578 670 | Forsiktig plukker du blomsten. Et svosj kan høres i det noe av den svarte intensiteten i blomsten forsvinner. Det er som om den slipper taket på lyset rundt seg. Du legger den i urtetasken. 671 | 990144927 672 | Forsiktig finner du en plass til planten i urtetasken. 673 | -633553987,1513929661 674 | Du røsker den til deg, og kommer deg vekk fra kanten i en fei. Så plasserer du urten pent på plass i taska. 675 | 212549703 676 | Du røsker til deg gromgraset og legger det i urtetaska. 677 | 1072506429 678 | Du plukker med deg alt det du kan, og legger det på plass i urtetaska. 679 | -559663246 680 | Du slenger rota opp i urtetaska og rynker på nesa. 681 | -232526843 682 | Du røsker til deg det som er av gromgras ved foten av tårnet, og legger det i urtetaska. 683 | 545539028 684 | Du legger blomsterknoppene forsiktig i taska. 685 | 275017596 686 | Du legger frøkapslene forsiktig i taska. 687 | -434800297 688 | Du river opp alt gromgraset med rota. Det skimrer fint når du legger det fint påplass i urtetaska. 689 | -531877065 690 | Du plasserer den stinkende rota i urtetaska med en rynket mine. 691 | -669001010,1478482638 692 | Du redder så mye av graset som du kan, og legger det pent i urtetaska. 693 | -637040740 694 | Du røsker den til deg, reiser deg opp med skjelvende bein. Så legger du urten pent på plass i taska. 695 | -218603060,1928880588 696 | Du legger blomsten forsiktig på plass i urtetaska. 697 | 373888282,-1773595366 698 | 699 | ]C[UT_FRA_MARKEDSPLASSEN 700 | @10813 701 | 1667572692,-479910956 702 | @9940 703 | 1996278103,-151205545 704 | @9941 705 | 1996278104,-151205544 706 | 707 | ]C[PARISERVELTET 708 | @1044 709 | -91058354 710 | @3912 711 | -552106759,1595376889 712 | @3913 713 | -552106758,1595376890 714 | 715 | ]C[_EVIL_DEED 716 | "Dessuten så han bare på mens en av dine prester ble drept av min imp." 717 | -39389102,2108094546 718 | "Det første han gjorde var å drepe vakta bare fordi han gjorde jobben sin!" 719 | -1879205921,268277727 720 | "Ha-ha! Han maltrakterte et uskyldig lite barn i Veslegrend." ler Satan rått. 721 | 820882646 722 | "Han angrep en prest og tre enker under en begravelse utenfor Høyborg." 723 | -686399979 724 | "Han banket opp og sparket istykker huset til den fylliken på brygga." 725 | -1078894225,1068589423 726 | "Han bare så på mens banansoldatene skambanket en dverg," sier satan. 727 | -659198808 728 | "Han brant ned fire hus i Dødens Dal, og drepte tre av dem som bodde der." 729 | -292444542 730 | "Han brukte den onde sorte amuletten, et grufullt våpen, mot andre mennesker." 731 | -1465248374,682235274 732 | "Han drepte den stakkars nybegynneren som vaktene hadde tatt til fange, og det på en særdeles grim måte" smiler Satan og gnir seg fornøyd i hendene. 733 | -136549231 734 | "Han drepte den vakre, uskyldige møyen i Veslegrend" sier Satan sleskt. 735 | -1901931385,245552263 736 | "Han drepte ei fattig jente og tok pengene hennes, på den nedbrente gården." 737 | 573373008 738 | "Han drepte jo de tre bevisstløse banansoldatene, sann ondskap!" 739 | -1469365610,678118038 740 | "Han drepte jo vakta i Dypvann, bare fordi han blandet seg inn" sier satan. 741 | 493737103 742 | "Han drepte mange munker og plyndret tempelet deres." 743 | -947255954 744 | "Han drepte tre barn som lekte sisten. Og moren dems etterpå," gliser Satan. 745 | -67701084 746 | "Han druknet den uskyldige arbeidskaren i kloakk! I KLOAKK!" sier satan. 747 | -2106046199,41437449 748 | "Han forgiftet maten i orkefengselet, og gadd ikke si ifra til fangene." 749 | 914625681 750 | "Han forvandlet jo alle de "uskyldige" bankfolka til rare vesner da, hehe!" 751 | -1500657876,646825772 752 | "Han hadde planer om å hjelpe skurken i sidegata i Dypvann." sier satan. 753 | 641231903 754 | "Han holdt jo et rituale til ære for meg i den skumle skogen." 755 | 417626612 756 | "Han jaget den fattige familien fra sandhulene de hadde jobbet så mye med." 757 | 601774310 758 | "Han jaget den fattige familien fra sandhulene til en uviss skjebne i ørkenen." 759 | -1615837735,531645913 760 | "Han kvesta en nykristen jypling som ba for sitt liv på gravplassen i skogen." 761 | -401453682 762 | "Han la ut om Lennart sin hemmelige oppskrift til hans argeste konkurrent." 763 | -958166351,-929018199 764 | "Han leverte fordervet gromgras til Lennart, med masseoppkast og elendighet som resultat." 765 | 1044359164 766 | "Han lot den vakta blø ihjel i smuget i Dypvann også!" sier satan stolt. 767 | -241111582 768 | "Han plyndret og drepte en gjeng imbesiler ombord på en brigg utenfor Dypvann!" 769 | -416508958 770 | "Han robbet konas perlekjede fra den sørgende enkemannen på brygga i Høyborg." 771 | -199665051 772 | "Han slakta jo ned de tre snille bøndene på torpofarmen, hehe!" 773 | 34185460 774 | "Han slapp jo den hjelpeløse ballongmannen rett i døden, uten grunn,hehe." 775 | -1213534466,933949182 776 | "Han slapp jo ut impen i kjelleren på kapellet da." 777 | 36236400 778 | "Han slapp løs de siste, sorte kamelene til de omreisende handelsmennene!" 779 | -271870129,1875613519 780 | "Han solgte en flaske med tresprit til en stakkars uteligger," sier satan. 781 | -767333694,1380149954 782 | "Han sparka til en stakkarslig fyr som lå på gulvet under et barslagsmål." 783 | -910692411 784 | "Han sparket et forsvarsløst pinnsvin" sier djevelen stolt. 785 | -1784711884,362771764 786 | "Han stakk sverdet i magen på Margot etter at hun hadde helbredet ham!" 787 | 517216209 788 | "Han stjal alle rupiene til den uskyldige unge seileren på Skipperkroa," sier satan. 789 | 355398399 790 | "Han stod bare og så på mens kannibalene spiste den stakkars, unge piken." 791 | -81656514 792 | "Han torturerte den stygge gnomen på Friesland." flirer Satan ondt. 793 | -649439408 794 | "Han tråkket med vilje på brillene til den stakkars skribenten," sier satan. 795 | -172249469,1975234179 796 | "Han utøver blind vold mot hyggelige, joviale og uskyldige gullgravere." 797 | -1189979433,957504215 798 | "Han var med på å utslette den stakkars, fattige familien i ørkenen." 799 | 567810623 800 | "Helt uprovosert drepte han en forsvarsløs negerskipper." 801 | -238563317,1908920331 802 | "Husk! Han slapp løs den onde, onde heksa fra stubben!" sier satan. 803 | 24262939 804 | "I Veslegrend drepte han tre uskyldige barn og moren deres," gliser Satan. 805 | -806090698,1341392950 806 | "Men han brant ei uskyldig dame som heks uten annen grunn enn et myntkast." 807 | -797655887,1349827761 808 | "Og etterpå kappet han Margot opp i småbiter og lo av det", Sier Satan. 809 | -84355973,2063127675 810 | "Og han kastet den hjelpeløse kattungen hardt i ruta til Vidar", sier Satan. 811 | 823305844 812 | "Og så SLO han den ene ungen!" sier satan. Gud rister oppgitt på hodet. 813 | 166050364 814 | "Også drepte han den greie karen i ørkenbyen, etter å ha lovt å ligge unna." 815 | -660831382 816 | "Også drepte han den uskyldige spåkona i tyvbyen." smiler satan. 817 | -614396905,1533086743 818 | "Også drepte han en helt uskyldig vaskedame!" sier satan, nesten litt stolt. 819 | -1410342537,737141111 820 | "Også drepte han en vakt i Dypvann, bare fordi han gjorde jobben sin." 821 | -1608194815,539288833 822 | "Også drepte han mora til ungen han slo!" sier satan, og ler enda mer. 823 | -1011727528 824 | "Også hev han den søte kattungen til de slemme cerberusene!" sier satan. 825 | -156784035,1990699613 826 | "Også hev han en søt, liten kattunge til to slemme rotweilere!" sier satan. 827 | -223488203 828 | "Også drepte han både kone og barn i kaldt blod! De var helt forsvarsløse." 829 | 444209964 830 | "Også kasta han den søte kattungen til to sinte rottweilere!" ler satan. 831 | -644228320 832 | "Også stjal han de ungenes kattunge i Dypvann! De var skikkelig lei seg!" 833 | 581874258 834 | "Og når han fant borgermesterens datter, så ofret han henne til lavaguden!" 835 | -530254235 836 | "Så kasta han den søte kattungen til to sinte rottweilere!" ler satan ondt. 837 | -309002055,1838481593 838 | "Uten skrupler hjalp han en ond demon å finne den siste kullsteinen." 839 | -455357346,1692126302 840 | 'Også drepte han Lars i smultringboden i Dypvann' sier satan. Gud nikker. 841 | 906710001 842 | Med et smil sier Satan "Han banket opp en fiskestang-selger helt uten grunn." 843 | 693087547 844 | Satan smiler og sier: "Men han drepte stakkars Grind-Gunnar i Veslegrend." 845 | -1626735117,520748531 846 | 847 | ]C[_GOOD_DEED 848 | "Han befridde Veslegrend fra den slemme Griffen," sier Gud fornøyd. 849 | -751581393 850 | "Han beseiret vampyren Rudolf Blodstrupmoen på Sumpøya" erklærer Gud strålende. 851 | 98010539 852 | "Han ble kvitt den slemme zombielorden på kirkegården utenfor Svarthaug." 853 | -1147634499,999849149 854 | "Han brukte opp healingskrollen sin på å redde en vakt i Dypvann!" sier Gud. 855 | -364970838,1782512810 856 | "Han brukte skrollen sin på mannen som var blitt stygt skadet på søppelplassen!" 857 | -1195518068,951965580 858 | "Han delte ut mat til flyktningene utenfor katedralen i Høyborg." 859 | -1004719976,1142763672 860 | "Han drepte den forferdelige trollmannen som ødela en hel by med en grusom zombieforbannelse!" sier Gud. Han ser på deg og smiler: "Godt jobba!" 861 | -438775743,1708707905 862 | "Han drepte det onde treet Gromsommur, et ondt, ondt tre." sier Gud. 863 | -1796446071,351037577 864 | "Han drepte egenhendig en av dine onde demoner i fjella" sier Gud stolt. 865 | 29961152 866 | "Han drepte jo den onde bloben som lenge hadde plaget Svarthaug." 867 | 643801684 868 | "Han drepte vampyren i krypten, en skikkelig slemming og blodsuger av rang!" 869 | -733211562,1414272086 870 | "Han døde jo for en verdig sak da - han ville ikke hjelpe de slemme orkene." 871 | -547111240 872 | "Han døde på en så bestialsk og grusom måte at han fortjener litt medfølelse" ymter Gud frampå. 873 | 931034287 874 | "Han er organdonor, og tenker på andre selv i de mest tragiske omstendigheter." 875 | -1212156921,935326727 876 | "Han er snill og omtenksom, selv mot motbydelige slabbedasker som Feite Frank." 877 | -65724922,2081758726 878 | "Han ga den sotete jenta tilbake alle sine penger, uten et ond ord eller tanke." 879 | 185667902 880 | "Han ga en gammel mann nytt livsmot når han hjalp til med ballkasting." 881 | -156047978,1991435670 882 | "Han ga jo mat til den sultne fangen fra landsbyen." sier Gud. 883 | -608371661 884 | "Han gav jo bort grøtsleiva si gratis til den trengende mannen i Dypvann." 885 | 1009249670 886 | "Han gjenopplivet fyren utenfor zombiebyen og fikk ham over på de snilles side!" 887 | -415403913,1732079735 888 | "Han gjorde kål på den slemme heksa i Dypvann!" sier Gud. Satan knurrer beskt. 889 | -854184589,1293299059 890 | "Han har vært generøs med de fattige og krigsrammede i Høyborg." 891 | 670226288 892 | "Han helbredet den stygt forbrente flyktningen utenfor katedralen i Høyborg." 893 | -917118809 894 | "Han hjalp den stakkars skribenten fra å få knust brillene sine," sier gud. 895 | -484481852,1663001796 896 | "Han hjalp den stakkars uteliggeren ved brygga i Svarthaug." sier Gud. 897 | -971118318,1176365330 898 | "Han hjalp den stakkars vakta i Dypvann da." sier gud. Satan snøfter ondt. 899 | 530934075 900 | "Han hjalp den utmatta, dødstørste mannen til brønnen i ørkenbyen," sier Gud. 901 | -143442580,2004041068 902 | "Han hjalp jo de stakkars folka i ørkenen da, de uten noen penger!" 903 | -47137741,2100345907 904 | "Han hjalp jo til etter angrepet på Dypvann uten å kreve noe igjen for det." 905 | -909724056 906 | "Han hjalp mannen i Veslegrend med å male huset sitt." Gud smiler varmt. 907 | -820234826 908 | "Han hjalp uselvisk å hjelpe fiskestang-selgeren med den tunge kassa" sier Gud. 909 | -720336391 910 | "Han hjelper med glede sin medmennesker, uten å be om noe i gjengjeld" sier Gud. 911 | -2020124283,127359365 912 | "Han klarte å avsløre en av dine onde hekser som var innom Veslegrend" sier Gud. 913 | -60144114,2087339534 914 | "Han kuttet ned de de tre hengte bøndene og ga dem en skikkelig begravelse." 915 | -1796713313,350770335 916 | "Han leverte pengene som Fru Fransen hadde glemt på Alle Sine Dager," sier gud. 917 | -404257350 918 | "Han lånte bort bibelen sin under begravelsen utenfor Høyborg," smiler Gud. 919 | -77080904,2070402744 920 | "Han muntret opp de skadde barna utenfor katedralen i Høyborg." 921 | 222900866 922 | "Han ofret seg jo for at den unge jenta skulle overleve mot lavamonsteret" 923 | -773030645,1374453003 924 | "Han prøvde ihvertfall å redde kone og barn under angrepet på ørkenfolka da." 925 | -584409240,1563074408 926 | "Han redda ihvertfall fyren oppe på loftet i heksehuset fra en fæl død." 927 | -581841128,1565642520 928 | "Han redda jo borgermesterens datter fra kannibalene!" sier gud og smiler. 929 | -66130604,2081353044 930 | "Han reddet ballongfantomet fra den sikre død, han gir synderen livet igjen!" 931 | -596487747,1550995901 932 | "Han reddet eventyrerdvergen Dunder fra de voldelige banansoldatene," sier Gud. 933 | -1203010617,944473031 934 | "Han reddet rundt tretti sjømenn fra drukningsdøden utenfor Svarthaug" sier Gud. 935 | -1854039138,293444510 936 | "Han reddet søppeltømmeren som var i livsfare på søppelplassen!" sier Gud. 937 | 185898401 938 | "Han ryddet en av de blodtørstige vampyrene i Svarthaug av veien." sier Gud. 939 | 922059410 940 | "Han satte livet på spill for å redde den gode presten i Svarthaug." 941 | -1963365932,184117716 942 | "Han skaffet perlekjedet til den sørgende enkemannen på brygga. Det var en god gjerning," sier gud. 943 | -52696433,2094787215 944 | "Han sluttet å jobbe for Yulak når han fant ut hva Yulak stod for." sier Gud. 945 | -1636111778,511371870 946 | "Han solgte sin Sorte Enke til spottpris, så Lennart kunne redde baren sin." 947 | -936815392,1210668256 948 | "Han var virkelig til stor hjelp for ofrene av Kåre Bananers angrep på Høyborg." 949 | -1003499794,1143983854 950 | "Jammen han viste de to herrene veien til toalettet da." 951 | -1632204313,515279335 952 | "Men han bærte den halvdøde fyren hele veien fra ørkenen til Dypvann!" 953 | -1957912489,189571159 954 | "Men han ga jo vina si til den tørste, halvdøde fyren i ørkenen!" sier gud. 955 | -441143665 956 | "Men han redda den stakkars fyren som fikk bank i Dypvann" svarer gud. 957 | 779042675 958 | "Men han sluttet jo sitt samarbeid med Yulak, den onde mafiabossen" sier gud. 959 | -1739980153,407503495 960 | "Men under angrepet på ørkenfolka så han nåde i kone og barn. Det var snilt." 961 | -767677282,1379806366 962 | "Når andre har problemer hjelper han til, helt uten egeninteresse." 963 | -1017164776,1130318872 964 | "Tross alt ga han vannet sitt til den tørste, halvdøde fyren i ørkenen!" 965 | -1126608650,1020874998 966 | Gud leter litt mer i boka si. "Skal vi se," sier han og smiler. "Han hjalp en fyr i Veslegrend fra å falle ned til den visse død i brønnen." 967 | -782916052,1364567596 968 | Gud tenker seg om. "Han ønsker å beskytte alle mine skapninger, da. Maur og!" 969 | -465279516 970 | --------------------------------------------------------------------------------