Page 3 of 4

Re: Squirrel questions ...

Posted: 20 Jan 2014 18:10
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;
}

Re: Squirrel questions ...

Posted: 20 Jan 2014 19:26
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.

Re: Squirrel questions ...

Posted: 23 Jan 2014 23:40
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.

Re: Squirrel questions ...

Posted: 22 Aug 2014 12:49
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 ?

Re: Squirrel questions ...

Posted: 22 Aug 2014 17:22
by Zuu
I think you need to pass the table member name as a string:

Code: Select all

data.rawin("save_version");

Re: Squirrel questions ...

Posted: 22 Aug 2014 18:19
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.

Re: Squirrel questions ...

Posted: 03 Jan 2015 15:49
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.

Re: Squirrel questions ...

Posted: 03 Jan 2015 20:58
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.

Re: Squirrel questions ...

Posted: 04 Jan 2015 01:06
by krinn

Code: Select all

this.tgr_array.extend(::TownDataTable[this.id].tgr_array);

Re: Squirrel questions ...

Posted: 04 Jan 2015 10:07
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 ?

Re: Squirrel questions ...

Posted: 04 Jan 2015 10:42
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.

Re: Squirrel questions ...

Posted: 04 Jan 2015 10:52
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.

Re: Squirrel questions ...

Posted: 04 Jan 2015 15:03
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.

Re: Squirrel questions ...

Posted: 04 Jan 2015 16:47
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.

Re: Squirrel questions ...

Posted: 04 Aug 2015 14:09
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.

Re: Squirrel questions ...

Posted: 04 Aug 2015 14:24
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
}

Re: Squirrel questions ...

Posted: 04 Aug 2015 14:36
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.

Re: Squirrel questions ...

Posted: 13 Aug 2015 09:43
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);

Re: Squirrel questions ...

Posted: 13 Aug 2015 11:46
by Alberth
For your testing question, you could give 'numberlist' as parameter to the checking function

Re: Squirrel questions ...

Posted: 13 Aug 2015 13:01
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)