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

User avatar
keoz
Transport Coordinator
Transport Coordinator
Posts: 321
Joined: 16 Jul 2009 10:04

Re: Squirrel questions ...

Post by keoz »

Hi folks,

A new question. I'm saving some datas, and among others, a boolean variable ("MonitorCheck", member variable of a class GoalTown), which has as value only "true" or "false". It is defined like that

Code: Select all

class GoalTown
{
     	[...]
        MonitorCheck = false;
        [...]
}
The point is, when data are reloaded, that datas come back as integers, with value 0 or 1, not as a boolean value. That makes problems when I test for the variable content. A test such as :

Code: Select all

if (this.MonitorCheck == false) {...}
doesn't work anymore.

Here is the relevant code:

Code: Select all

class MainClass extends GSController 
{	constructor()
	{
		[...]
		::TownMonitorCheckTable <- {};
		[...]
	}
}

function MainClass::Save()
{
	local table = {};
	
	// Building the tables of town's data
	foreach (i, town in this.towns)
	{
		[...]
		::TownMonitorCheckTable[town.id] <- town.MonitorCheck;
	}

	[...]
	table.TownMonitorCheckTable <- ::TownMonitorCheckTable;
	
	return table;
}

function MainClass::Load(version, saved_data)
{
	[...]
	::TownMonitorCheckTable <- saved_data.TownMonitorCheckTable;
}
What I am doing wrong ?
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 »

Just convert it back to boolean, there's no tobool(); function but it's not really a needed one.
boolean in integer == 0 || 1

Code: Select all

function MainClass::Load(version, saved_data)
{
	[...]
	::TownMonitorCheckTable <- saved_data.TownMonitorCheckTable;
	// Building the tables of town's data
	foreach (i, town in this.towns)
	{
		town.MonitorCheck = (::TownMonitorCheckTable[town.id] != 0);
	}


}
You can just write it as (if this.towns is already set):

Code: Select all

foreach (t in this.towns) { t.MonitorCheck = (t.MonitorCheck != 0); }
and tests could be shorten too :

Code: Select all

if (this.MonitorCheck == false) {...} == if (!this.MonitorCheck) {}
User avatar
keoz
Transport Coordinator
Transport Coordinator
Posts: 321
Joined: 16 Jul 2009 10:04

Re: Squirrel questions ...

Post by keoz »

Edit.
Last edited by keoz on 23 Dec 2013 14:59, 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.
User avatar
keoz
Transport Coordinator
Transport Coordinator
Posts: 321
Joined: 16 Jul 2009 10:04

Re: Squirrel questions ...

Post by keoz »

krinn wrote:Just convert it back to boolean, there's no tobool(); function but it's not really a needed one.
boolean in integer == 0 || 1
That's what I did for now, though in a less elegant way as your proposal.

But is this behaviour normal ?
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 »

Well, couldn't tell you. It's common to see boolean handle as int. So it's not normal if you look at ottd wiki about save/load, maybe a note on loading is missing.
But i'm not shock that much by it and feel it normal.
User avatar
Zuu
OpenTTD Developer
OpenTTD Developer
Posts: 4553
Joined: 09 Jun 2003 18:21
Location: /home/sweden

Re: Squirrel questions ...

Post by Zuu »

Code: Select all

if (this.MonitorCheck == false) {...}
if you change the code above (from this post by keoz) to the code below, IIRC it should trigger the code block on both value == false and == 0 in Squirrel.

Code: Select all

if (!this.MonitorCheck) {...}
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 »

Ok, thank you both !
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
skippern
Traffic Manager
Traffic Manager
Posts: 195
Joined: 30 Oct 2013 13:57

Re: Squirrel questions ...

Post by skippern »

Is there a way to get a random element from an array?

something in the way of:

Code: Select all

local arr = [ "a", "b", "c", "d"];
return arr[arr.sizeOf() * rand()];
this code snip should give the values a, b, c or d, but at random selection - I see the function rand() is defined in squirrel3, but cannot find it in squirrel2, also I cannot figure out completely how that function should work - please help with this code bit
Skippern
OpenTTD Mac user
krinn
Transport Coordinator
Transport Coordinator
Posts: 339
Joined: 29 Dec 2010 19:36

Re: Squirrel questions ...

Post by krinn »

R2dical
Traffic Manager
Traffic Manager
Posts: 163
Joined: 18 Mar 2013 22:22

Re: Squirrel questions ...

Post by R2dical »

If you are reusing the array or some such you may like to do it like so:

Code: Select all

/**
 *	Randomly reorder all elements in an array.
 *	@param array The array to reorder.
 *	@return An array containing all elements from the input array, but reordered.
 *	@note It returns a new array.
 */
function Array::RandomSort(input_array) {
	if(typeof(input_array) != "array") throw("Array::RandomSort: Argument has to be an array!");
	local random_array = [];
	local temp_array =  [];
	temp_array.extend(input_array);
	
	while (temp_array.len() > 0) {
		local index = AIBase.RandRange(temp_array.len());
		random_array.append(temp_array[index]);
		temp_array.remove(index);
	}
	
	return random_array;
}
User avatar
skippern
Traffic Manager
Traffic Manager
Posts: 195
Joined: 30 Oct 2013 13:57

Re: Squirrel questions ...

Post by skippern »

So this will be valid when I have two arrays of strings:

Code: Select all

while (!AICompany.GetName(AICompany.COMPANY_SELF)) {
    AICompany.SetName(prefixes[AIBase.RandRange(prefixes.len())] + suffixes[AIBase.RandRange(suffixes.len())]);
}
Skippern
OpenTTD Mac user
User avatar
skippern
Traffic Manager
Traffic Manager
Posts: 195
Joined: 30 Oct 2013 13:57

Re: Squirrel questions ...

Post by skippern »

It seems the code over didn't work, maybe because AICompany.SetName() only takes static string as argument?

This on the other hand worked:

Code: Select all

while (1) {
    local myname = (prefixes[AIBase.RandRange(prefixes.len())] + suffixes[AIBase.RandRange(suffixes.len())]);
    AICompany.SetName(myname);
    if (AICompany.GetName(AICompany.COMPANY_SELF) == myname) break;
}
Skippern
OpenTTD Mac user
R2dical
Traffic Manager
Traffic Manager
Posts: 163
Joined: 18 Mar 2013 22:22

Re: Squirrel questions ...

Post by R2dical »

Yeah maybe the ambiguous variable type in squirrel causing trouble, in those cases you can usually just do an "explicit cast" by "" + variable or (variable).tostring().

For you method above you can test for AICompany.SetName as it returns a bool regarding success. Also you may want to account for the case where all possible prefix + suffix are taken by other companies (multiple instances of AI possibly), so you run an infinite loop...

My AI name function is very similar:

Code: Select all

function RadAI::_SetCompanyName() {

	// Arrays of possible company name parts.
	local company_name_prefixs = ["", ...];
	local company_name_suffixs = ["", ...];
	
	// Get try counter, not perfect since random can choose a combo tried already...so add a modifier.
	local tries = pow(company_name_prefixs.len(), company_name_suffixs.len()) * 2;
	
	// Try using randomized prefix and suffix.
	local company_name = null;
	do {
		// Get a name combination.
		company_name = "RadAI " + company_name_prefixs[AIBase.RandRange(company_name_prefixs.len())] + " " + company_name_suffixs[AIBase.RandRange(company_name_suffixs.len())];
		
	} while(!AICompany.SetName(company_name) && tries-- > 0);
	
	// Try using incremented name.
	if(AICompany.GetName(AICompany.COMPANY_SELF) != company_name) {
		local i = 1;
		while(!AICompany.SetName(company_name + " " + i++)) {
			if(i > 255) {
				Log.Error("RadAI::_SetCompanyName: Unable to set company name!", Log.MAIN);
				return false;
			}
       	}
	}
	return true;
}
	
User avatar
skippern
Traffic Manager
Traffic Manager
Posts: 195
Joined: 30 Oct 2013 13:57

Re: Squirrel questions ...

Post by skippern »

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.

Currently it is like this:

Code: Select all

local townlist = AITownList();
townlist.Valuate(AITown.GetPopulation); /* This was needed so that next function didn't return index 0 on every run */
local townid = townlist[AIBase.RandRange(AITown.GetTownCount())];
Now I want to sort the list based on the distance to townid, I have tried

Code: Select all

for (local i = 0; i < AITown.GetTownCount(); i++) {
   townlist.SetValue(townlist[i], AITown.GetDistanceManhattanToTile(townid, AITown.GetLocation(townlist[i])));
}
townlist.Sort(AIList.SORT_BY_VALUE, true);
But it seems like it still is sorted by population. I cannot find anything in AITown that allows me to sort on distances to other towns, so I did a try with a homemade function on Valuate:

Code: Select all

function SortByDistance() { return AITown.GetDistanceManhattanToTile(townid, AITown.GetLocation(townlist[i++])); }

townlist.Valuate(SortByDistance());
But this gives me strange errors, I also tried with a few arguments to SortByDistance, but that also gives errors - any suggestions what I do wrong?
Skippern
OpenTTD Mac user
R2dical
Traffic Manager
Traffic Manager
Posts: 163
Joined: 18 Mar 2013 22:22

Re: Squirrel questions ...

Post by R2dical »

So you just want the list sorted by distance to townid? The population sort was just your means to that end and can be discarded? If yeah then what you need is a custom valuator for the list. The example on the docs was a little confusing for me:
NoAI Docs wrote:Example: list.Valuate(AIBridge.GetPrice, 5); list.Valuate(AIBridge.GetMaxLength); function MyVal(bridge_id, myparam) { return myparam * bridge_id; // This is silly } list.Valuate(MyVal, 12);
But its easy:

Code: Select all

local townlist = AITownList();
// Assumes you have your townid variable ready initialized by now. The townid_onlist used below is part of the valuator and will not be in scope here.
// townid_onlist will be each of the town ids on the list in this case, for each function call and we pass in the second param after the function definition.
townlist.Valuate(function(townid_onlist, townid_location) { return AIMap.DistanceManhattan(AITown.GetLocation(townid_onlist), townid_location) }, AITown.GetLocation(townid))
townlist.Sort(AIList.SORT_BY_VALUE, AIList.SORT_ASCENDING);
Also fyi, I think your sort method is not working because an AIList is a class object and not a native squirrel table and so cannot be directly manipulated using squirrel default delegates. The class methods in the docs can be used to replicate those however, but AFAIK the valuate approach is faster anyway.

Your custom valuator is wrong syntax and usage, but you can also used named functions as opposed to the in line one above.
User avatar
skippern
Traffic Manager
Traffic Manager
Posts: 195
Joined: 30 Oct 2013 13:57

Re: Squirrel questions ...

Post by skippern »

Thanks, that worked :) need to learn these things
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 »

In addition to the API docs, there is an introduction to [AI|GS]List on the wiki: http://wiki.openttd.org/AI:Lists
My OpenTTD contributions (AIs, Game Scripts, patches, OpenTTD Auto Updater, and some sprites)
Junctioneer (a traffic intersection simulator)
krinn
Transport Coordinator
Transport Coordinator
Posts: 339
Joined: 29 Dec 2010 19:36

Re: Squirrel questions ...

Post by krinn »

To sort an ailist of town by distance from another town

When you valuate, the first parameter will be the item of the list, so when you valuate a list of town, the first parameter pass to the function use to valuate is the townID as a list of town hold townID
Other parameters are pass by comma :
So to use AITown.GetDistanceManhattanToTile(townid, tileindex) -> townlist.Valuate(AITown.GetDistanceManhattanToTile, tileindex) // townid will be first parameter pass by the valuate.

Code: Select all

yourtownlist.Valuate(AITown.DistanceManhattanToTile, AITown.GetLocation(town_to_use));
Here's a custom valuator to show how to use custom valuator.

Code: Select all

function SortByDistance(townid, townid_compare)
{
return AITown.GetDistanceManhattanToTile(townid, AITown.GetLocation(townid_compare));
}
yourtownlist.Valuate(SortByDistance, townid_compare);
As you see, you can change from town_id + location parameters of AITown.DistanceManhattanToTile to town_id + town_id parameters using custom valuator.
krinn
Transport Coordinator
Transport Coordinator
Posts: 339
Joined: 29 Dec 2010 19:36

Re: Squirrel questions ...

Post by krinn »

skippern wrote:So this will be valid when I have two arrays of strings:

Code: Select all

while (!AICompany.GetName(AICompany.COMPANY_SELF)) {
    AICompany.SetName(prefixes[AIBase.RandRange(prefixes.len())] + suffixes[AIBase.RandRange(suffixes.len())]);
}
This doesn't fail on AICompany.SetName, this fail on the while check, and AICompany.SetName is never run...
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 this will be valid when I have two arrays of strings:

Code: Select all

while (!AICompany.GetName(AICompany.COMPANY_SELF)) {
    AICompany.SetName(prefixes[AIBase.RandRange(prefixes.len())] + suffixes[AIBase.RandRange(suffixes.len())]);
}
This doesn't fail on AICompany.SetName, this fail on the while check, and AICompany.SetName is never run...
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;
}
Skippern
OpenTTD Mac user
Post Reply

Return to “OpenTTD AIs and Game Scripts”

Who is online

Users browsing this forum: No registered users and 13 guests