Squirrel questions ...

Discuss the new AI features ("NoAI") introduced into OpenTTD 0.7, allowing you to implement custom AIs, and the new Game Scripts available in OpenTTD 1.2 and higher.

Moderator: OpenTTD Developers

krinn
Transport Coordinator
Transport Coordinator
Posts: 339
Joined: 29 Dec 2010 19:36

Re: Squirrel questions ...

Post by krinn »

skippern wrote: So why do this fail then?

Code: Select all

local myname = "some name"
local i = 0;
while (1) {
    AICompany.SetName(myname);
    if (AICompany.GetName(AICompany.COMPANY_SELF) == myname) break;
    i++;
    myname = "some name #"+i;
}
Because you think it should fail and it doesn't ?

You don't even need to test your company name, openttd already tell you the answer:

Code: Select all

local myname = "some name"
local i = 0;
while (!AICompany.SetName(myname)) {
    i++;
    myname = "some name #"+i;
}
User avatar
skippern
Traffic Manager
Traffic Manager
Posts: 195
Joined: 30 Oct 2013 13:57

Re: Squirrel questions ...

Post by skippern »

krinn wrote:
skippern wrote: So why do this fail then?

Code: Select all

local myname = "some name"
local i = 0;
while (1) {
    AICompany.SetName(myname);
    if (AICompany.GetName(AICompany.COMPANY_SELF) == myname) break;
    i++;
    myname = "some name #"+i;
}
Because you think it should fail and it doesn't ?
It fails, the company is called "Unnamed until it starts to build than becomes {Town} Transport
krinn wrote: You don't even need to test your company name, openttd already tell you the answer:

Code: Select all

local myname = "some name"
local i = 0;
while (!AICompany.SetName(myname)) {
    i++;
    myname = "some name #"+i;
}
Maybe this works better? Will try it now.
Skippern
OpenTTD Mac user
User avatar
Zuu
OpenTTD Developer
OpenTTD Developer
Posts: 4553
Joined: 09 Jun 2003 18:21
Location: /home/sweden

Re: Squirrel questions ...

Post by Zuu »

skippern wrote:I have a townlist that I wish to have sorted in the distance to other towns with the closest towns first, and moving further away as the list progresses.
If you actually want a list with the distance to the closest town and not the distance to town_id, this is what you need to do in principal:

Code: Select all

function TownDistValuator(town_a_id, town_b_id) {
  return AIMap.DistanceManhattan(AITown.GetLocation(town_a_id), AITown.GetLocation(town_b_id));
}
function ClosestTownValuator(town_id) {
  local all_towns = AITownList();
  all_towns.RemoveItem(town_id); // remove self
  all_towns.Valuate(TownDistValuator, town_id);
  all_towns.SortByValue(AIList.SORT_BY_VALUE, AIList.SORT_ASCENDING);
  return all_towns.Begin();
}

list = AITownList();
list.Valuate(town_id);
list.SortByValue(AIList.SORT_BY_VALUE, AIList.SORT_ASCENDING);
However, as you hopefully know, OpenTTD cannot pause the execution of your code while a validator is running, and this operation could take some time on a large map, it is a good idea to not use the validator construct here.

Code: Select all

local read_only_list = AITownList();
local list = AITownList();
foreach(town_a_id, _ in read_only_list) {
  local min_dist = -1;
  foreach(town_b_id, _ in read_only_list) {
    if(town_a_id == town_b_id) continue;
    local dist = AIMap.DistanceManhattan(AITown.GetLocation(town_a_id), AITown.GetLocation(town_b_id));
    if(dist < min_dist || min_dist == -1) min_dist = dist;
  }
  list.SetValue(town_a_id, min_dist);
}

list.SortByValue(AIList.SORT_BY_VALUE, AIList.SORT_ASCENDING);
// List is now sorted by distance to closest town
I am not sure if it is necessary or not to use a read_only_list or if it is safe to iterate over an AIList while modifying its value inside the loop. Therefore the code example use this construct to ensure you don't get this problem.
My OpenTTD contributions (AIs, Game Scripts, patches, OpenTTD Auto Updater, and some sprites)
Junctioneer (a traffic intersection simulator)
User avatar
keoz
Transport Coordinator
Transport Coordinator
Posts: 321
Joined: 16 Jul 2009 10:04

Re: Squirrel questions ...

Post by keoz »

New question. In my MainClass::Load() function, I try to test the existence of a key in the saved data table. Like this:

Code: Select all

function MainClass::Load(version, saved_data)
	
if (saved_data.rawin(save_version)) {etc. etc.}
The script crashes and complains about that code in a such way: "Your script made an error: the index 'save_version' does not exist"

I'd say, I wouldn't check the existence of a key if I were sure that this actually exists. Can't we ask for a rawin() check in load() function ?
Patch - Let's timetable depot waiting time with the Wait in depot patch.
GameScript - Searching a new way to make your cities growing ? Try the Renewed City Growth GameScript.
My screenshots thread.
User avatar
Zuu
OpenTTD Developer
OpenTTD Developer
Posts: 4553
Joined: 09 Jun 2003 18:21
Location: /home/sweden

Re: Squirrel questions ...

Post by Zuu »

I think you need to pass the table member name as a string:

Code: Select all

data.rawin("save_version");
My OpenTTD contributions (AIs, Game Scripts, patches, OpenTTD Auto Updater, and some sprites)
Junctioneer (a traffic intersection simulator)
User avatar
keoz
Transport Coordinator
Transport Coordinator
Posts: 321
Joined: 16 Jul 2009 10:04

Re: Squirrel questions ...

Post by keoz »

Zuu wrote:I think you need to pass the table member name as a string:

Code: Select all

data.rawin("save_version");
That's it ! Works like a charm. Thank you.
Patch - Let's timetable depot waiting time with the Wait in depot patch.
GameScript - Searching a new way to make your cities growing ? Try the Renewed City Growth GameScript.
My screenshots thread.
User avatar
keoz
Transport Coordinator
Transport Coordinator
Posts: 321
Joined: 16 Jul 2009 10:04

Re: Squirrel questions ...

Post by keoz »

Ok. Something is driving me crazy here. If someone can help.

Background:
- in my script we have a GoalTown class, with an instance per town. For each instance there are several data, such as GoalTown.tgr_array (an array containing 8 integer elements).
- for some reason (mainly for save purpose) there also is a global table (::TownDataTable). In this table I store some of the GoalTown data. In this general table, for each town we have: key = town id; element = table with different data (such as tgr_array).

Here is how ::TownDataTable is updated each month:

Code: Select all

/* Main town management function. Called each month. */
function GoalTown::MonthlyManageTown()
{
	[...]

	::TownDataTable[this.id] = this.CollectingTownData();
}

/* Function called to collect town data. */
function GoalTown::CollectingTownData()
{
	local town_data = {};
	town_data.sign_id <- this.sign_id;
	town_data.is_monitored <- this.is_monitored;
	town_data.last_pax_delivery <- this.last_pax_delivery;
	town_data.town_supplied <- this.town_supplied;
	town_data.town_goals_cat <- this.town_goals_cat;
	town_data.town_supplied_cat <- this.town_supplied_cat;
	town_data.town_stockpiled_cat <- this.town_stockpiled_cat;
	town_data.tgr_array <- this.tgr_array;
	return town_data;
}
Note that ::TownDataTable is built a first time when initializing GoalTown instances the first time. After this, each time I reload a savegame, is it rebuilt from saved data, like that:

Code: Select all

foreach (townid, town_data in saved_data.town_data_table) {
	::TownDataTable[townid] <- town_data;
}
Now the problem. At some point, the content of ::TownDataTable changes without me understanding why. Follows an illustration code. The relevant line is the one in the middle (this.tgr_array...). To understand what happens there: here, the GoalTown.tgr_array is updated and its 8th element (i=7), whose value is 0 until there, receives a positive value (> 0). For some reason that I don't get, the array which is stored in ::TownDataTable also changes. All the code which is before and after the central line is only there to put that in evidence in the Debug window.

Code: Select all

function GoalTown::MonthlyManageTown()
{
	[...]

	// For debug purpose
	Log.Info("MAIN ARRAY TEST 1", Log.LVL_DEBUG);
	local array_text = " ";
	foreach (i, val in ::TownDataTable[this.id].tgr_array) {
		array_text = array_text+"i"+i+"="+val+" ";
	}
	Log.Info(GSTown.GetName(this.id)+": "+array_text+"Average="+this.tgr_average, Log.LVL_DEBUG);

	this.tgr_array[i] = new_town_growth_rate;

	// For debug purpose
	Log.Info("MAIN ARRAY TEST 2", Log.LVL_DEBUG);
	local array_text = " ";
	foreach (i, val in ::TownDataTable[this.id].tgr_array) {
		array_text = array_text+"i"+i+"="+val+" ";
	}
	Log.Info(GSTown.GetName(this.id)+": "+array_text+"Average="+this.tgr_average, Log.LVL_DEBUG);

	[...]
}
Here is the result in my Debug window. As you can see, the 8th element of the array changed value:

Code: Select all

dbg: [script] [18] [I] [1940-07-01]  MAIN ARRAY TEST 1
dbg: [script] [18] [I] [1940-07-01]  Filzbach:  i0=851 i1=10000 i2=217 i3=217 i4=217 i5=217 i6=217 i7=0 Average=(null : 0x(nil))
dbg: [script] [18] [I] [1940-07-01]  MAIN ARRAY TEST 2
dbg: [script] [18] [I] [1940-07-01]  Filzbach:  i0=851 i1=10000 i2=217 i3=217 i4=217 i5=217 i6=217 i7=10000 Average=(null : 0x(nil))
What am I missing here ? Probably something completely stupid/evident... But I can't figure out. I attach here a version of the entire script if someone wants have a look.

Cheers.
Attachments
Renewed_City_Growth-r123-bug.tar
(100 KiB) Downloaded 68 times
Last edited by keoz on 04 Jan 2015 01:02, edited 1 time in total.
Patch - Let's timetable depot waiting time with the Wait in depot patch.
GameScript - Searching a new way to make your cities growing ? Try the Renewed City Growth GameScript.
My screenshots thread.
svetovoi
Engineer
Engineer
Posts: 87
Joined: 12 Oct 2007 14:07

Re: Squirrel questions ...

Post by svetovoi »

My guess: that is happening due to this line in the GoalTown constructor:

Code: Select all

this.tgr_array = ::TownDataTable[this.id].tgr_array;
Try cloning instead.
krinn
Transport Coordinator
Transport Coordinator
Posts: 339
Joined: 29 Dec 2010 19:36

Re: Squirrel questions ...

Post by krinn »

Code: Select all

this.tgr_array.extend(::TownDataTable[this.id].tgr_array);
User avatar
keoz
Transport Coordinator
Transport Coordinator
Posts: 321
Joined: 16 Jul 2009 10:04

Re: Squirrel questions ...

Post by keoz »

Hey guys. Thank you both for watching at this.
svetovoi wrote:My guess: that is happening due to this line in the GoalTown constructor:

Code: Select all

this.tgr_array = ::TownDataTable[this.id].tgr_array;
Try cloning instead.
You were right, that was the problematic line. What did you mean by cloning ? Anyway...
krinn wrote:

Code: Select all

this.tgr_array.extend(::TownDataTable[this.id].tgr_array);
This worked ! Thank you krinn.

Now I'm still wondering. If maybee somebody has the answer. Should I consider that the problem was predictable ? To put it otherway: was there something wrong in my code, or should we consider the issue as unintented behaviour ?
Patch - Let's timetable depot waiting time with the Wait in depot patch.
GameScript - Searching a new way to make your cities growing ? Try the Renewed City Growth GameScript.
My screenshots thread.
krinn
Transport Coordinator
Transport Coordinator
Posts: 339
Joined: 29 Dec 2010 19:36

Re: Squirrel questions ...

Post by krinn »

array are instance, if you do "that = anarray" you endup with "that = pointer to anarray" and not with an array "that" holding the same content of "anarray"
it's a problem AI/GS authors should met early because of AI/GSList handling ; instead of use *List.AddList() they think List_a = List_b would make a copy of it.

entend is the function to copy array content from array to array, or you can copy an array element but in the form "that = anarray[x]";
so, yes, predictable.
svetovoi's eyes are working very fine.
User avatar
keoz
Transport Coordinator
Transport Coordinator
Posts: 321
Joined: 16 Jul 2009 10:04

Re: Squirrel questions ...

Post by keoz »

krinn wrote:array are instance, if you do "that = anarray" you endup with "that = pointer to anarray" and not with an array "that" holding the same content of "anarray"
it's a problem AI/GS authors should met early because of AI/GSList handling ; instead of use *List.AddList() they think List_a = List_b would make a copy of it.

entend is the function to copy array content from array to array, or you can copy an array element but in the form "that = anarray[x]";
so, yes, predictable.
svetovoi's eyes are working very fine.
Ok, thank you. I suspected something like that. But wasn't sure, I still thought that array1 = array2 should only copy the value, not create a link between the arrays. I'll have a look on the code, to check if there are other similar problems.
Patch - Let's timetable depot waiting time with the Wait in depot patch.
GameScript - Searching a new way to make your cities growing ? Try the Renewed City Growth GameScript.
My screenshots thread.
svetovoi
Engineer
Engineer
Posts: 87
Joined: 12 Oct 2007 14:07

Re: Squirrel questions ...

Post by svetovoi »

Cloning is a process of creating exact copy(duplicate, clone) of an object. It is used to avoid such problems as this.

btw i think

Code: Select all

this.tgr_array.extend(::TownDataTable[this.id].tgr_array);
still may be not enough, e.g. if you put other arrays into the ::TownDataTable[this.id].tgr_array bug will probably be repeated with them.
User avatar
keoz
Transport Coordinator
Transport Coordinator
Posts: 321
Joined: 16 Jul 2009 10:04

Re: Squirrel questions ...

Post by keoz »

svetovoi wrote:Cloning is a process of creating exact copy(duplicate, clone) of an object. It is used to avoid such problems as this.

btw i think

Code: Select all

this.tgr_array.extend(::TownDataTable[this.id].tgr_array);
still may be not enough, e.g. if you put other arrays into the ::TownDataTable[this.id].tgr_array bug will probably be repeated with them.
Well for now, it did the trick. But I'll be aware of that if other problems arise.
Patch - Let's timetable depot waiting time with the Wait in depot patch.
GameScript - Searching a new way to make your cities growing ? Try the Renewed City Growth GameScript.
My screenshots thread.
User avatar
3iff
Tycoon
Tycoon
Posts: 1093
Joined: 21 Oct 2005 09:26
Location: Birmingham, England

Re: Squirrel questions ...

Post by 3iff »

I've just jumped into squirrel...confusing.
I have a bit of code that works fine but it seems a bit inelegant. Is there a nicer way of doing it?

I generate a random number:

Code: Select all

local rx = (AIBase.RandRange(11)) + 1
Then I check this value and operate on the block I want...

Code: Select all

if (rx == 1 || rx == 2)
        { do this block}

if (rx == 3)
    { do this block instead}

and so on.
User avatar
Sylf
President
President
Posts: 957
Joined: 23 Nov 2010 21:25
Location: ::1

Re: Squirrel questions ...

Post by Sylf »

You can use a switch statement for something like that. Is this close to what you're looking for?

Code: Select all

// It's still a good idea to use a variable for better clarity IMO
switch ( (AIBase.RandRange(11)) + 1 ) {
  case 1:
  case 2:
    // run this block when random number is 1 or 2
    break;
  case 3:
    // run this block when random number is 3
    break
  default:
    // run this block when random number is anything else
}
User avatar
3iff
Tycoon
Tycoon
Posts: 1093
Joined: 21 Oct 2005 09:26
Location: Birmingham, England

Re: Squirrel questions ...

Post by 3iff »

It might be. I'll try it and see if it's better.

Thanks.

-------------

Yes, managed to get it working. It does look a bit better.
I've also worked out how to reference town names. It took a lot of trial and error but I got there in the end.
User avatar
3iff
Tycoon
Tycoon
Posts: 1093
Joined: 21 Oct 2005 09:26
Location: Birmingham, England

Re: Squirrel questions ...

Post by 3iff »

I have a function that sets some names (for example)
local numberlist = ["one","two,"three"]

I then want to call another function from that first one and still be able to reference (and test) numberlist but (as I expected) the other function syas that numberlist does not exist.

As I have to do lots of tests at different points I was hoping to be able to have a second fuction that does all the checking.

I have had a look at the squirrel docs but they really don't help.

And a separate question, can I do this, to just extract vehicles making less than £1000 last year and less than £500 this year? Does the second filter work on the first filter?

vehicles = AIVehicleList_Group(route.group);
vehicles.Valuate(AIVehicle.GetProfitLastYear);
vehicles.KeepBelowValue(1000);
vehicles.Valuate(AIVehicle.GetProfitThisYear);
vehicles.KeepBelowValue(500);
Alberth
OpenTTD Developer
OpenTTD Developer
Posts: 4763
Joined: 09 Sep 2007 05:03
Location: home

Re: Squirrel questions ...

Post by Alberth »

For your testing question, you could give 'numberlist' as parameter to the checking function
Being a retired OpenTTD developer does not mean I know what I am doing.
User avatar
3iff
Tycoon
Tycoon
Posts: 1093
Joined: 21 Oct 2005 09:26
Location: Birmingham, England

Re: Squirrel questions ...

Post by 3iff »

I never thought of that. It works (unfortunately the function is in runaway mode as I can't stop it reprocessing the function...)

It would have also helped had I used square brackets in one place, numberlist[] not numberlist() !!

Thanks.

To get a value back from the function I needed to use:

j = functionname(numberlist,jj) and a return j; in the function. (of course you knew that...)

It finally does what I need. Hooray! (I hope)
Post Reply

Return to “OpenTTD AIs and Game Scripts”

Who is online

Users browsing this forum: No registered users and 4 guests