Sometimes, you don't even need a list.

K.V.
10 Nov 2017, 14:43

This is a spin-off thread.

Here is the original thread, which has been declared solved by the OP (which is why I created this thread: to avoid any confusion or thread-jacking):

http://textadventures.co.uk/forum/quest/topic/gv8tjdacvuwcn6kc9zcxhq/can-i-teleport-using-lists#106dedb2-049b-4d2d-a106-48b57650673e


@mrangel

I figured it's probably easier to filter twice, check for "can_teleport" and then check for "visited".

I had a room you couldn't get to without teleporting, so I didn't use that "visited" check, but here it is with it back in there:

ShowMenu ("Teleport", ListExclude(FilterByAttribute(FilterByAttribute(AllObjects(), "can_teleport", true), "visited", true), game.pov.parent), true) {


If you pass an objectlist to ShowMenu, then result will be a string

This is definitely the case. A quick test proved you right.

This makes the Object List less work.

You'd have to list add (game.rooms, room.name) each time when using a String List (because you can't add an object to a string list). Then the same thing is needed for list remove. That's two extra lines of code in this case, too.

The Object list is six less characters each time you add a room (list add (game.rooms, room)), and one extra line of code, which would only be needed once in the ShowMenu script (result = GetObject(result)).

NOTE: Both methods work. I'm just looking for the most efficient method. One that requires minimal coding and makes it easiest to add or remove rooms which are teleport destinations when editing the game.

On that note...


The LIST REMOVE/LIST ADD method:

First off, you have to create the game.rooms list and modify it numerous times when you do it this way, and, as mrangel pointed out earlier, the entire list is unnecessary.

game.rooms = NewObjectList()
list add (game.rooms, The Lab)
list add (game. rooms, Teleport 1)
list remove (game.rooms, player.parent)
ShowMenu ("Teleport", game.rooms, true) {
  list add (game.rooms, player.parent)
  result = GetObject(result)
  msg ("You teleport to: "+GetDisplayName(result)+".")
  MoveObject (game.pov, result)
}


The following does the same thing, without even having to create the list, let alone modify it repeatedly:

ShowMenu ("Teleport", ListExclude(FilterByAttribute(FilterByAttribute(AllObjects(), "can_teleport", true), "visited", true), game.pov.parent), true) {
  result = GetObject(result)
  msg ("You teleport to: "+GetDisplayName(result)+".")
  MoveObject (game.pov, result)
}

This is the example code I worked up (you can't get to The Lab without teleporting in mine, so I lost the "visited" attribute check):

<!--Saved by Quest 5.7.6404.15496-->
<asl version="550">
  <include ref="English.aslx" />
  <include ref="Core.aslx" />
  <game name="Teleporting">
    <gameid>b1109d19-2c86-407b-972b-7ee2da530b2c</gameid>
    <version>1.0</version>
    <firstpublished>2017</firstpublished>
    <start type="script">
      The Lab.can_teleport = true
    </start>
  </game>
  <object name="room">
    <inherit name="editor_room" />
    <object name="player">
      <inherit name="editor_object" />
      <inherit name="editor_player" />
    </object>
    <exit alias="into the teleport booth" to="Teleport 1">
      <inherit name="indirection" />
    </exit>
  </object>
  <object name="The Lab">
    <inherit name="editor_room" />
    <usedefaultprefix type="boolean">false</usedefaultprefix>
    <description><![CDATA[{if The Lab.can_teleport:<br/>You can {command:TELEPORT} from here.}]]></description>
  </object>
  <object name="Teleport 1">
    <inherit name="editor_room" />
    <usedefaultprefix type="boolean">false</usedefaultprefix>
    <beforefirstenter type="script">
      Teleport 1.can_teleport = true
    </beforefirstenter>
    <description><![CDATA[{if Teleport 1.can_teleport:<br/>You can {command:TELEPORT} from here.}]]></description>
    <exit alias="out" to="room">
      <inherit name="outdirection" />
    </exit>
  </object>
  <command name="teleport">
    <pattern>teleport;tele;port;t;travel</pattern>
    <script>
      if (HasAttribute(game.pov.parent, "can_teleport")) {
        if (game.pov.parent.can_teleport) {
          ShowMenu ("Teleport", ListExclude(FilterByAttribute(AllObjects(), "can_teleport", true), game.pov.parent), true) {
            result = GetObject(result)
            msg ("You teleport to: "+GetDisplayName(result)+".")
            MoveObject (game.pov, result)
          }
        }
        else {
          msg ("It didn't work...")
        }
      }
      else {
        msg ("You can't teleport from here.")
      }
    </script>
  </command>
</asl>

mrangel
10 Nov 2017, 17:18

I figured it's easier to work with the attribute if teleporting is just a way to get back to places you've already visited.

As far as stringlist vs objectlist; I think if it works out about the same amount of code.
list add (game.rooms, The Lab) vs list add (game.rooms, "The Lab").
But ... if you make a typo in the room name, the first will give you an error, making it easier to find the problem.

And, of course, you can't use FilterByAttribute on a stringlist; and it would become a lot more effort to maintain if a room's alias isn't the same as its name. And ShowMenu can easily be made to show rooms in different colours (maybe for different parts of the world? Or highlighting locations with shops?) using "Link colour" on the object tab for the room.


jmnevil54
10 Nov 2017, 23:04

I'm not going to add an attribute to every room I want to teleport to. I thought I said it works fine.

Edit:
This is what it looks like now, in case anyone's interested.

  1. Start script
game.rooms = NewObjectList()
list add (game.rooms, The Lab)
game.firstteleport = false
  1. When you enter the first room in the second town
firsttime {
  list add (game.rooms, Teleport 1)
}
game.firstteleport = true
  1. Teleport command. Its pattern is teleport #object#
list remove (game.rooms, player.parent)
if (game.firstteleport = true) {
  ShowMenu ("Teleport", game.rooms, true) {
    list add (game.rooms, player.parent)
    MoveObject (player, GetObject(result))
  }
}
else {
  msg ("...It didn't work...")
}

My game, check it. http://textadventures.co.uk/games/view/5jllte-m4e2e2whw4gf5jq/pokemon-type-harley-johto-and-sinnoh


K.V.
11 Nov 2017, 03:33

I thought I said it works fine.

You speak the truth, jmne, hence my opening statement and note:

> Here is the original thread, which has been declared [solved] by the OP (which is why I created this thread: to avoid any confusion or thread-jacking):

>NOTE: Both methods work. I'm just looking for the most efficient method. One that requires minimal coding and makes it easiest to add or remove rooms which are teleport destinations when editing the game.


I'm not going to add an attribute to every room I want to teleport to.

I'm not trying to convince you to do that. You've got it working already, and that's great!

...but, having said that, what's the difference between setting up one attribute for each room and taking the time to write the code to add each room to a list?

Each way takes about the same amount of time in the beginning, but I think the scripts posted here would make it very easy to add extra destinations.

Plus, I'm learning new things that can be done with Quest.


hegemonkhan
11 Nov 2017, 07:04

@jmnevil54:

this is going to cause a problem:

list remove (game.rooms, player.parent)
if (game.firstteleport = true) {
  ShowMenu ("Teleport", game.rooms, true) {
    list add (game.rooms, player.parent)
    MoveObject (player, GetObject(result))
  }
}
else {
  msg ("...It didn't work...")
}

as you remove the room you're currently in/at from the list, but if/when your 'game.firstteleport = false', you don't add that room back into your list, which will cause you problems, as you thought that room was in your list, but now its not anymore.

here's the fix (just need to change the ordering/location of the 'list remove' script to get it to work correctly):

// from here
if (game.firstteleport = true) {
  list remove (game.rooms, player.parent) // to here, as now its within the 'if', meaning that the 'list remove' and 'list add' will always happen together, so no issues of removing an item from the list but not re-adding it to the list
  ShowMenu ("Teleport", game.rooms, true) {
    list add (game.rooms, player.parent)
    MoveObject (player, GetObject(result))
  }
}
else {
  msg ("...It didn't work...")
}

here's the code without my comments:

if (game.firstteleport = true) {
  list remove (game.rooms, player.parent)
  ShowMenu ("Teleport", game.rooms, true) {
    list add (game.rooms, player.parent)
    MoveObject (player, GetObject(result))
  }
}
else {
  msg ("...It didn't work...")
}

hegemonkhan
11 Nov 2017, 07:21

this is a lot of operations (and memory space / overhead / functions / activation records), and multiplied by every time its done during game play:

ShowMenu ("Teleport", ListExclude(FilterByAttribute(FilterByAttribute(AllObjects(), "can_teleport", true), "visited", true), game.pov.parent), true) { /* scripting */ }

now, you probably won't notice any actual practical slowdown, as this is getting into it more deeply/technically/theoretically...

this is probably like...

O(M*(N^3)) // big O notation
M = number of times it's run/done
N^3 = its mathematical operations/memory-used (nesting 'X' iterations / 'X' function nesting, is: N^X)


whereas, if you have a single List Attribute for your room teleport choices, and add/remove rooms as/when needed (or not), that's much less operations to be doing (and less memory used too). It gives you more game design control too, as well.

whereas a single list/array is: O(1) // a constant

plus the operation of adding/removing the item to/from the list, which are also constants as well


here's a pretty good quick search resource on the basics of this stuff, if anyone is interested:

ht.tps://rob-bell.net/2009/06/a-beginners-guide-to-big-o-notation/

I still struggle with this stuff, as I hate math and am still noob to Data Structure programming.


The Pixie
11 Nov 2017, 09:10

whereas, if you have a single List Attribute for your room teleport choices, and add/remove rooms as/when needed (or not), that's much less operations to be doing (and less memory used too). It gives you more game design control too, as well.

I would not worry about it. The Quest code makes lists of objects several times each turn, for the panes on the right and the what you can see list, rather than tryng to maintain a list that is modified as it goes along. I accept it wil be slower, but it is easier to code and far less error prone, as the lists cannot get out of sync with the game world.


mrangel
11 Nov 2017, 13:56

now, you probably won't notice any actual practical slowdown, as this is getting into it more deeply/technically/theoretically...

And incorrectly.
The code you're quoting is O(N).

Using a single list is O(N).


jmnevil54
11 Nov 2017, 14:52

Fixed it HK. Thanks.


hegemonkhan
11 Nov 2017, 20:10

oops, my bad. Ya, it's O(N). Sorry about that. Don't know why I typed O(1), lol.


Forgewright
09 Dec 2017, 00:40

@@mrangel,
Thanks for the thread! Found after trying to randomly move a shark around an island with about 100 rooms. 25 are have the Boolean attribute "iswater",true. If the shark moves into a water room where the player is, or player moves to where he is, he will stay there until the player leaves.

This is a turnscript I enable at game start:

if (shark.parent = player.parent) {
  shark.parent = player.parent
  msg ("You can see a shark swimming in the water!")
}
else if (RandomChance (30)) {
  shark.parent = PickOneObject(FilterByAttribute(AllObjects(), "iswater", true))
  if (shark.parent = player.parent) {
    msg ("You can see a shark swimming in the water!")
  }
}
else {
  shark.parent = shark.parent
}

Should be easy to let the shark leave when in player.parent room, with notification to player as well.
Just got excited to see it working while using debugger...had to share.
No big deal to some, but big ta-do-in's for me!