The International Simutrans Forum

 

Author Topic: Experimental Feature Ideas  (Read 1340 times)

0 Members and 1 Guest are viewing this topic.

Offline chs

  • *
  • Posts: 35
Experimental Feature Ideas
« on: February 13, 2011, 07:16:34 AM »
Cars
According to what I have read, and what I think would be best, the number of private cars in the city directly represent people that use their private car instead of public transport to go somewhere. So in a month, if 1000 people go by car in a city, there should appear a 1000 cars (or configurableValue x 1000). Practically, the amount of cars seems to depend fully on the config entry "citycar_level". ?
The number of owned cars seems to be fixed (also the number of people that have access to a car). In reality, this number decreases slightly within big cities (with increasing efficiency of public transport).
The preference for private cars should also decrease with time and better public transport networks. In reality, the more people use public transport, the more likely they are to do so again in the future. Therefore, I think it makes sense to decrease this preference, based on how many people travel with a public network. Maybe this could be combined with the popularity of public transport (see below).


Popularity (reputation) of public transport
The goal is to include the effect of a good reputation of a company/public transport in the game. People are more likely to travel by public transport, if it (the company) is known to be good. Happy passengers should increase the reputation, unhappy passengers make it worse.
In the game, this could be represented by a % popularity value PV each company (and private cars) have in a city. Example PV: private Cars 50%, company A 40%, company B 10%. This means, in this city, 50% of all journeys within the last x months were done by private car, 40% with company A, 10% with company B.
In detail, maintain a journey counter per company (incl. private cars) per city (and for each industry/attraction located outside city boundaries). After a successful journey, add 1 to private cars / used company. If there are several companies, split the value according to the distance travelled. Example: Travel 80% of the distance with company A, 20% with company B - increase company A`s journey counter by 0.8 and B`s by 0.2. At some point, if example journey counters may be 200 for private cars, 160 for company A and 40 for company B, you get the PV values above. Not successfull journeys would represent in the same negative value (mulitplied by a configurable value). If several companies are involved, only the "guilty" companies should get the penalty. How to find the guilty company is another issue we can discuss if this idea gets implemented.
(It seems more realistic to me to simply count journeys instead of using the distance - the latter is more complicated and makes it very hard to get some popularity starting from zero with only a local network. In reality, you can also get some popularity with only local transport)

Public vs private transport
The PV above should increase a persons chance to travel by public transport. The "car vs public transport" choice could work as follows:
1. Access to car? no = public transport*
2. Reachable by car? no = public transport*
3. Is this a "I always go by car" person? yes = car, no = continue
4. Choose route / method of transport. Assume there are 3 routes: using company A (A), using company B (B) and by car (C). Now the following factors influence the choice
a) journey time (for A and B calculated normally, for C the real route time plus congestion)
b) preference for private car** / popularity of public transport (using values 90% car preference, 40% PV for company A, 10% PV for company B)
c) special penalty or bonus (see below)
d) comfort level

In detail, lets say the journey times are: A 30 minutes, B 45 minutes, C 60 minutes. Represent it in % of the longest route: C 100%, B 75%, A 50% (or 1.0, 0.75, 0.5). Here lower values mean shorter travelling time - for calculation I want higher values to be better. Therefore, compute the inverse values, which is 1 divided by the journey times, resulting if C = 1 / 1.0 = 1, B = 1 / 0.75 = 1.33, A = 1 / 0.5 = 2.0. Again, in percent, now I have: A 200%, B 150%, C 100%.
Include b) = just add the percentage / car preference, resulting in A = 240%, B = 160%, C = 190%. This means, a person with a 90% car preference is willing to spend 90% more time if he can go there by car. A good reputation similarly increases this value. In my experience, this would match reality quite well. For multiple companies, use their popularity values proportional to the distance that would be travelled with their company.
Next, c): just add %-values for special penalities to the values we already have
Next, d): there is an expected comfort value, such as 100. Each journey has an average comfort, such as A: 75, B: 120, C (car)***: 80, resulting in the following percentage changes for comfort: A = -25%, B = +20%, C = -20%. Total values now: A: 215%, B = 180%, C = 170%
Finally, choose the route with the highest percentage (these could also be normal values, using percentages simply seemed to make it simple to include b-d).
Consequences for competing players are that in a certain city, a well established company A has a higher reputation. This results in the fact that people are willing to accept a longer journey time when travelling with this company. So "new" companies would have to offer significantly better connections to make people use their transport network. I think this also matches reality, as people like their habits and only change them for a good reason. See also "returning passenger".
From a realistic perspective, not all passengers take optimal decisions. Therefore, it might make sense to add a certain random value. We already have a number of "car-only" people. There could be another 10% (configurable) of passengers that chooses one of best 3 (configurable) non-car routes at random.

*only if reachable, including journey time, best route, etc
** the preference for private car is now a fixed value. To make it more realistic, I think it should vary with time (as described in the first paragraph "cars") and include some randomization, like a gauss distribution with the prefrence value as mean. It would vary depending on the public transport efficiency, and therefore become a city value instead of a global value.
*** private cars could have a random comfort value within a certain range, where the mean increases over time resembling the increasing car comfort

(I am not exactly sure how overcrowing gets in there, its part of the journey time and comfort level. Probably overcrowded stations are already considered appropriately in the route finding as it is implemented now)


Special Penalties / Bonuses

- "one ticket"
The idea is to represent the fact that a person is more likely to travel with one single company for the whole journey, because it is simpler, even if that journey takes somewhat longer than an alternative route that includes multiple companies. Buy only one ticket, no manual schedule matching, etc. So if a journey includes only 1 company, it should get a bonus, which I call "one ticket bonus". Might be 10-20%.
This would apply to private cars also, actually even more, and could be set in "private car preference".

- "illegal switch" penalty and change option
There are two ideas behind this.
First, if a bus stop becomes suddenly overcrowded and waiting times become too long, people will leave and search for alternative routes. This includes most likely going to another close bus stop (any transport stop) and checking the schedule there, even if that stop is another company /transport method stop. If they find a suitable route there, they would take it.
Second, when planning a trip, people might include "inofficial switches", meaning changing from one means of transport to another where there is no official connection. For example, a person might walk from a bus station to the train station (different company) and continue there. In the game, this is not possible. When planning their route, passengers could consider the option of walking from one stop to another stop within a configurable distance (maybe only directly adjacent stops). As this includes a certain risk, a penalty should be applied in route calculation, for example -50% or -100%.

If passengers are allowed to change their stop in that way, this would have (multiplayer) consequences.
First I think this would match reality better, as people actually do choose such routes.
Second, there are a lot more possible connections - more computation time.
Third and most important, it may be possible for one player to "flood" the stops of another player, even if he is unwilling to cooperate. For example, player A can simply build a stop StopA next to player B`s local transport network stop StopB and transport people there from other cities. StopB might become overcrowded quickly, as people would change from StopA to StopB, forcing player B to do something. It makes sense that people would go that way, in particular if this is the only way to go. On the other hand, it should not be used with purpose of ruining another players network. Therefore the penalty has to be high enough, such that some passengers will go, but not too many. This would allow for more competition. Currently (without passengers being allowed to change as described), continuing the above example, player A would now be required to build a local network in the destination city (which does not resemble reality at all, this never happens). With this change, this would not be necessary, and player A could simply start his connection, which in my experience resembles reality (even more in less developed countries).
Another consequence is, that it now makes sense for BOTH players to cooperate and choose a public transport stop. Both players will get more passengers, because the penaly does no longer apply. The security to have a permanent route increases for both players - without cooperation, both players could simply remove transporting passengers from/to StopB, making the other player`s life considerably more difficult because he has to adjust all the time. Cooperation would be a win-win situation, which also resembles reality quite good.

Returning Passengers
The idea is that passengers go from their home to a destination, stay there for a random amount of time (for example, 0-20 x journey time), and then return home. When going home, they would use the same route as when going there, unless there are major changes. Would apply to private cars also. Consequences are:
First, it resembles reality better.
Second, this doubles the amount of passengers - settings would have to be adjusted accordingly.
Third, regarding competition in multiplayer games and popularity values: it may be very difficult for a "new" company B to get passengers in a city C where another company A has a very good reputation. However, if company B transports people to C, and they return also using company B (which is realistic), it helps company B to increase its reputation. Also they will get passengers in spite of the high reputation of company A and therefore are able to increase their own reputation.
Last, it halves the route computation time at the cost of memory.

For me, the longterm fun of the game lies in its multiplayer part, this is also why many of these ideas relate to multiplayer games. Please tell me what you think about first the ideas, and second (optional) also the way of implementing it.

Online jamespetts gb

  • Simutrans-Extended project coordinator
  • Moderator
  • *
  • Posts: 18721
  • Cake baker
    • Bridgewater-Brunel
  • Languages: EN
Re: Experimental Feature Ideas
« Reply #1 on: February 13, 2011, 03:28:09 PM »
Chs,

welcome to the forum! Thank you for your contribution and your interest in Simutrans-Experimental. To deal with your particular suggestions:

Cars

It already works in the way that you suggest in Experimental: the citycar_level is a multiplier. At 16, for each journey, one car will be generated. At lower levels, for each journey, a fixed proportion of one car will be generated.

The likelihood of passengers using private cars is indeed affected by the quality of the public transport connexion. I have doubts that adding the additional layer of reducing the number of people with access to private cars where the public transport in the area is good (one cannot simply say "big cities", as there may be a poor public transport network in any given big city) adds much, and it would in any event be very difficult to get a definition of "good" that did not cause some distortion or perverse incentive somewhere. It is better, I think, to have the choice as to whether passengers use public transport or their own cars to be based exclusively on the relative merits of each method for the specific journey on which they seek to embark.

Reputation

I don't think that this would work effectively, I am afraid, as "unhappy" passengers refer only to the number of passengers who, on arriving at a station or stop, find it so crowded that they can't get in and therefore can't start their journey. It says nothing about the quality of the company more generally. Basing reputation on this factor would therefore cause significant distortions. It is more effective (and less prone to error), I think, for passengers to evaluate the relative merits of specific alternative journeys when determining which route to use in each case (the journey time being the  means of determination between different public transport routes, and congestion being one of the most significant factors in relation to the choice between public and private transport), as this avoids the possibility of distortions induced by overgeneralisation.

Private car choice

There is already a careful means of determining whether passengers use private cars or public transport in this part of the code:

Code: [Select]
/* this creates passengers and mail for everything is is therefore one of the CPU hogs of the machine
 * think trice, before applying optimisation here ...
 */
void stadt_t::step_passagiere()
{
//@author: jamespetts
// Passenger routing and generation metrics.
const uint32 local_passengers_min_distance = welt->get_einstellungen()->get_local_passengers_min_distance();
const uint32 local_passengers_max_distance = welt->get_einstellungen()->get_local_passengers_max_distance();
const uint32 midrange_passengers_min_distance = welt->get_einstellungen()->get_midrange_passengers_min_distance();
const uint32 midrange_passengers_max_distance = welt->get_einstellungen()->get_midrange_passengers_max_distance();
const uint32 longdistance_passengers_min_distance = welt->get_einstellungen()->get_longdistance_passengers_min_distance();
const uint32 longdistance_passengers_max_distance = welt->get_einstellungen()->get_longdistance_passengers_max_distance();

const uint16 min_local_tolerance = welt->get_einstellungen()->get_min_local_tolerance();
const uint16 max_local_tolerance = max(0, welt->get_einstellungen()->get_max_local_tolerance() - min_local_tolerance);
const uint16 min_midrange_tolerance = welt->get_einstellungen()->get_min_midrange_tolerance();
const uint16 max_midrange_tolerance = max( 0, welt->get_einstellungen()->get_max_midrange_tolerance() - min_midrange_tolerance);
const uint16 min_longdistance_tolerance = welt->get_einstellungen()->get_min_longdistance_tolerance();
const uint16 max_longdistance_tolerance = max(0, welt->get_einstellungen()->get_max_longdistance_tolerance() - min_longdistance_tolerance);

const uint8 passenger_packet_size = welt->get_einstellungen()->get_passenger_routing_packet_size();
const uint8 passenger_routing_local_chance = welt->get_einstellungen()->get_passenger_routing_local_chance();
const uint8 passenger_routing_midrange_chance = welt->get_einstellungen()->get_passenger_routing_midrange_chance();

// DBG_MESSAGE("stadt_t::step_passagiere()", "%s step_passagiere called (%d,%d - %d,%d)\n", name, li, ob, re, un);
// long t0 = get_current_time_millis();

// post oder pax erzeugen ?
// "post or generate pax"
const ware_besch_t* wtyp;
if (simrand(400, "void stadt_t::step_passagiere() (mail or passengers?)") < 300)
{
wtyp = warenbauer_t::passagiere;
}
else
{
wtyp = warenbauer_t::post;
}
const int history_type = (wtyp == warenbauer_t::passagiere) ? HIST_PAS_TRANSPORTED : HIST_MAIL_TRANSPORTED;

// restart at first buiulding?
if (step_count >= buildings.get_count())
{
step_count = 0;
}
if(buildings.get_count()==0)
{
return;
}
const gebaeude_t* gb = buildings[step_count];

// prissi: since now backtravels occur, we damp the numbers a little
const int num_pax =
(wtyp == warenbauer_t::passagiere) ?
(gb->get_tile()->get_besch()->get_level()      + 6) >> 2 :
max(1,(gb->get_tile()->get_besch()->get_post_level() + 5) >> 4);


// Hajo: track number of generated passengers.
city_history_year[0][history_type+1] += num_pax;
city_history_month[0][history_type+1] += num_pax;

// create pedestrians in the near area?
if (welt->get_einstellungen()->get_random_pedestrians() && wtyp == warenbauer_t::passagiere)
{
haltestelle_t::erzeuge_fussgaenger(welt, gb->get_pos(), num_pax);
}

// suitable start search
const koord k = gb->get_pos().get_2d();
const planquadrat_t* plan = welt->lookup(k);
const halthandle_t* halt_list = plan->get_haltlist();

minivec_tpl<halthandle_t> start_halts(plan->get_haltlist_count());
for (int h = plan->get_haltlist_count() - 1; h >= 0; h--)
{
halthandle_t halt = halt_list[h];
if (halt->is_enabled(wtyp)  &&  !halt->is_overcrowded(wtyp->get_catg_index()))
{
start_halts.append(halt);
}
}

INT_CHECK( "simcity 2282" );

// Check whether this batch of passengers has access to a private car each.
// Check run in batches to save computational effort.
const sint16 private_car_percent = wtyp == warenbauer_t::passagiere ? get_private_car_ownership(welt->get_timeline_year_month()) : 0;
// Only passengers have private cars
const bool has_private_car = private_car_percent > 0 ? simrand(100, "void stadt_t::step_passagiere() (has private car?)") <= (uint16)private_car_percent : false;

// Record the most useful set of information about why passengers cannot reach their chosen destination:
//  Too slow > overcrowded > no route. Tiebreaker: higher destination preference.
koord best_bad_destination;
uint8 best_bad_start_halt;
bool too_slow_already_set;

//Only continue if there are suitable start halts nearby, or the passengers have their own car.
if(start_halts.get_count() > 0 || has_private_car)
{
//Add 1 because the simuconf.tab setting is for maximum *alternative* destinations, whereas we need maximum *actual* desintations
// const uint8 max_destinations = has_private_car ? 1 : (welt->get_einstellungen()->get_max_alternative_destinations() < 16 ? welt->get_einstellungen()->get_max_alternative_destinations() : 15) + 1;
// Passengers with a private car will not tolerate second best destinations,
// and will use their private car to get to their first choice destination
// regardless of whether they might go to other destinations by public transport.

// Now that private cars also have a journey time tolerance and check whether the roads are connected, passengers *might*
// not be able to get to their destination by private car either, so the above is redundant.
const uint8 max_destinations = (welt->get_einstellungen()->get_max_alternative_destinations() < 16 ? welt->get_einstellungen()->get_max_alternative_destinations() : 15) + 1;

// Find passenger destination
for (int pax_routed = 0; pax_routed < num_pax; pax_routed += passenger_packet_size)
{

/* number of passengers that want to travel
* Hajo: for efficiency we try to route not every
* single pax, but packets. If possible, we do 7 passengers at a time
* the last packet might have less then 7 pax
* Number now not fixed at 7, but set in simuconf.tab (@author: jamespetts)*/

int pax_left_to_do = min(passenger_packet_size, num_pax - pax_routed);

// search target for the passenger
pax_zieltyp will_return;

const uint8 destination_count = simrand(max_destinations, "void stadt_t::step_passagiere() (number of destinations?)") + 1;

// Split passengers: between local, midrange and long-distance
// according to the percentages set in simuconf.tab.
// Note: a random town will be found if there are no towns within range.
const uint8 passenger_routing_choice = simrand(100, "void stadt_t::step_passagiere() (passenger routing choice?)");
const journey_distance_type range =
passenger_routing_choice <= passenger_routing_local_chance ?
local :
passenger_routing_choice <= (passenger_routing_local_chance + passenger_routing_midrange_chance) ?
midrange : longdistance;
const uint16 tolerance =
wtyp != warenbauer_t::passagiere ?
0 :
range == local ?
simrand(max_local_tolerance, "void stadt_t::step_passagiere() (local tolerance?)") + min_local_tolerance :
range == midrange ?
simrand(max_midrange_tolerance, "void stadt_t::step_passagiere() (midrange tolerance?)") + min_midrange_tolerance :
simrand(max_longdistance_tolerance, "void stadt_t::step_passagiere() (longdistance tolerance?)") + min_longdistance_tolerance;
destination destinations[16];
for(int destinations_assigned = 0; destinations_assigned <= destination_count; destinations_assigned ++)
{
if(range == local)
{
//Local - a designated proportion will automatically go to destinations within the town.
if((float)passenger_routing_choice <= adjusted_passenger_routing_local_chance)
{
// Will always be a destination in the current town.
destinations[destinations_assigned] = finde_passagier_ziel(&will_return, 0, local_passengers_max_distance, k);
}
else
{
destinations[destinations_assigned] = finde_passagier_ziel(&will_return, local_passengers_min_distance, local_passengers_max_distance, k);
}
}
else if(range == midrange)
{
//Medium
destinations[destinations_assigned] = finde_passagier_ziel(&will_return, midrange_passengers_min_distance, midrange_passengers_max_distance, k);
}
else
//else if(range == longdistance)
{
//Long distance
destinations[destinations_assigned] = finde_passagier_ziel(&will_return, longdistance_passengers_min_distance, longdistance_passengers_max_distance, k);  //"Ziel" = "target" (Google)
}
}

INT_CHECK( "simcity 2371" );

uint8 current_destination = 0;

route_status route_good = no_route;

const uint16 max_walking_distance = welt->get_einstellungen()->get_max_walking_distance();
uint16 car_minutes = 65535;

best_bad_destination = destinations[0].location;
best_bad_start_halt = 0;
too_slow_already_set = false;

while(route_good != good && route_good != private_car_only && current_destination < destination_count)
{
// Dario: Check if there's a stop near destination
const planquadrat_t* dest_plan = welt->lookup(destinations[current_destination].location);
const halthandle_t* dest_list = dest_plan->get_haltlist();

// Knightly : we can avoid duplicated efforts by building destination halt list here at the same time
minivec_tpl<halthandle_t> destination_list(dest_plan->get_haltlist_count());

halthandle_t start_halt;

// Check whether the destination is within walking distance first.
// @author: jamespetts, December 2009
if(accurate_distance(destinations[current_destination].location, k) <= max_walking_distance)
{
// Passengers will always walk if they are close enough.
route_good = can_walk;
}

if(route_good != can_walk)
{
for (int h = dest_plan->get_haltlist_count() - 1; h >= 0; h--)
{
halthandle_t halt = dest_list[h];
if (halt->is_enabled(wtyp))
{
destination_list.append(halt);
ITERATE(start_halts, i)
{
if(halt == start_halts[i])
{
route_good = can_walk;
start_halt = start_halts[i];
// Mail does not walk, but people delivering it do.
break;
}
}
}
}
}

// check, if they can walk there?

if (route_good == can_walk)
{
break;
}

// ok, they are not in walking distance
// Check whether public transport can be used.
ware_t pax(wtyp); //Journey start information needs to be added later.
pax.set_zielpos(destinations[current_destination].location);
pax.menge = (wtyp == warenbauer_t::passagiere ? pax_left_to_do : 1 );
//"Menge" = volume (Google)

// now, finally search a route; this consumes most of the time

uint16 best_journey_time = 65535;
uint8 best_start_halt = 0;

ITERATE(start_halts,i)
{
halthandle_t current_halt = start_halts[i];

uint16 current_journey_time = current_halt->find_route(&destination_list, pax, best_journey_time);
if(current_journey_time < best_journey_time)
{
best_journey_time = current_journey_time;
best_start_halt = i;
}
if(pax.get_ziel().is_bound())
{
route_good = good;
}
}

// Check first whether the best route is outside
// the passengers' tolerance.

if(route_good == good && tolerance > 0 && best_journey_time > tolerance)
{
route_good = too_slow;

if(!too_slow_already_set)
{
best_bad_destination = destinations[current_destination].location;
best_bad_start_halt = best_start_halt;
too_slow_already_set = true;
}
}
else
{
// All passengers will use the quickest route.
if(start_halts.get_count() > 0)
{
start_halt = start_halts[best_start_halt];
}
}

INT_CHECK("simcity.cc 2882");

if(has_private_car)
{
const uint32 straight_line_distance = accurate_distance(k, destinations[current_destination].location);
uint16 time_per_tile = 65535;
switch(destinations[current_destination].type)
{
case 1:
//Town
time_per_tile = check_road_connexion_to(destinations[current_destination].object.town);
break;
case FACTORY_PAX:
time_per_tile = check_road_connexion_to(destinations[current_destination].object.industry);
break;
case TOURIST_PAX:
time_per_tile = check_road_connexion_to(destinations[current_destination].object.attraction);
break;
default:
//Some error - this should not be reached.
dbg->error("simcity.cc", "Incorrect destination type detected");
};

if(time_per_tile < 65535)
{
// *Tenths* of minutes used here.
car_minutes =  time_per_tile * straight_line_distance;
}
else
{
car_minutes = 65535;
}
}

if(car_minutes <= tolerance)
{
if(route_good != good)
{
// The passengers can get to their destination by car but not by public transport.
// Therefore, they will certainly use their car.
route_good = private_car_only;
}

else
{
// The passengers can get to their destination both by car and public transport.
// Choose which they prefer.

// Now, decide whether passengers would prefer to use their private cars,
// even though they can travel by public transport.
const uint32 distance = accurate_distance(destinations[current_destination].location, k);

//Weighted random.
uint8 private_car_chance = simrand(100, "void stadt_t::step_passagiere() (private car chance?)");
if(private_car_chance >= 1)
{
// The basic preference for using a private car if available.
sint16 car_preference = welt->get_einstellungen()->get_base_car_preference_percent();

//First, adjust for distance. For very long-distance journies, cars are less popular.

if(distance > (midrange_passengers_max_distance * 3))
{
if(distance >= longdistance_passengers_max_distance)
{
car_preference /= 10;
}
else
{
const float proportion = ((float)distance - (float)(midrange_passengers_max_distance * 3.0F)) / (float)longdistance_passengers_max_distance;
car_preference /= (10 * proportion);
}
}

// Secondly, congestion. Drivers will turn to public transport if the origin or destination towns are congested.

// This percentage of drivers will prefer to use the car however congested that it is.
const sint16 always_prefer_car_percent = welt->get_einstellungen()->get_always_prefer_car_percent();

//Average congestion of origin and destination towns, and, at the same time, reduce factor.
uint8 congestion_total;
if(destinations[current_destination].type == 1 && destinations[current_destination].object.town != NULL)
{
congestion_total = (city_history_month[0][HIST_CONGESTION] + destinations[current_destination].object.town->get_congestion()) / 4;
}
else
{
congestion_total = (city_history_month[0][HIST_CONGESTION] / 1.33);
}
car_preference = ((car_preference - congestion_total) > always_prefer_car_percent) ? car_preference - congestion_total : always_prefer_car_percent;

// Thirdly adjust for service quality of the public transport.
// Compare best journey speed on public transport with the
// likely private car journey time.

INT_CHECK( "simcity 3004" );

const float proportion = ((float)best_journey_time / (float)car_minutes) * car_minutes > best_journey_time ? 1.25F : 0.75F;
car_preference *= proportion;

// If identical, no adjustment.

//Fourthly, the number of unhappy passengers at the start station compared with the number of happy passengers.
float unhappy_factor = start_halt->get_unhappy_proportion(0);

if(unhappy_factor > 0.8F)
{
private_car_chance /= unhappy_factor;
}

//Finally, determine whether the private car is used.
if(private_car_chance <= car_preference)
{
// Private cars chosen by preference even though public transport is possible.
route_good = private_car_only;
}
}
}
}

if(route_good == can_walk)
{
// so we have happy passengers

// Passengers who can walk to their destination may be happy about it,
// but they are not happy *because* the player has made them happy.
// Therefore, they should not show on the station's happy graph.
// @author: jamespetts, December 2009

//start_halt->add_pax_happy(pax_left_to_do);

merke_passagier_ziel(destinations[0].location, COL_DARK_YELLOW);
if (welt->get_einstellungen()->get_random_pedestrians() && wtyp == warenbauer_t::passagiere)
{
haltestelle_t::erzeuge_fussgaenger(welt, start_halt->get_basis_pos3d(), pax_left_to_do);
}

// They should show that they have been transported, however, since
// these figures are used for city growth calculations.
city_history_year[0][history_type] += pax_left_to_do;
city_history_month[0][history_type] += pax_left_to_do;
}

else if(route_good == good)
{
// Passengers can and will use public transport.
pax.arrival_time = welt->get_zeit_ms();
pax.set_origin(start_halt);
start_halt->starte_mit_route(pax);
merke_passagier_ziel(destinations[current_destination].location, COL_YELLOW);
}

else if(route_good == private_car_only)
{
// Passengers have either chosen to use their car or have no other choice.
stadt_t* const destination_town = destinations[current_destination].type == 1 ? destinations[current_destination].object.town : NULL;
set_private_car_trip(num_pax, destination_town);
merke_passagier_ziel(destinations[current_destination].location, COL_TURQUOISE);
#ifdef DESTINATION_CITYCARS
erzeuge_verkehrsteilnehmer(k, step_count, destinations[current_destination].location);
}
#endif

// send them also back
// (Calculate a return journey)
if(will_return != no_return && route_good == good || route_good == private_car_only)
{
// this comes most of the times for free and balances also the amounts!
halthandle_t ret_halt = pax.get_ziel();
bool return_in_private_car = private_car_only || !ret_halt.is_bound();

if(!return_in_private_car)
{
// we just have to ensure, the ware can be delivered at this station
bool found = false;
for (uint i = 0; i < plan->get_haltlist_count(); i++)
{
halthandle_t test_halt = halt_list[i];

if(test_halt->is_enabled(wtyp) && (start_halt == test_halt || test_halt->get_connexions(wtyp->get_catg_index())->access(start_halt) != NULL))
{
found = true;
start_halt = test_halt;
break;
}
}

// now try to add them to the target halt
if(!ret_halt->is_overcrowded(wtyp->get_catg_index()))
{
// prissi: not overcrowded and can recieve => add them
if(found)
{
ware_t return_pax(wtyp, ret_halt);
if(  will_return != town_return  &&  wtyp==warenbauer_t::post  )
{
// attractions/factory generate more mail than they recieve
return_pax.menge = pax_left_to_do * 3;
}
else
{
// use normal amount for return pas/mail
return_pax.menge = pax_left_to_do;
}
return_pax.set_zielpos(k);
return_pax.set_ziel(start_halt);
if(ret_halt->find_route(return_pax) != 65535)
{
return_pax.arrival_time = welt->get_zeit_ms();
ret_halt->starte_mit_route(return_pax);
merke_passagier_ziel(destinations[current_destination].location, COL_YELLOW);
//ret_halt->add_pax_happy(pax_left_to_do);
// Experimental 7.2 and onwards does this when passengers reach their destination
}
}
else
{
// no route back
if(car_minutes < 65535)
{
// This assumes that the journey time in both directions is identical: but
// this may not be so if there are one-way routes.
return_in_private_car = true;
}
else
{
ret_halt->add_pax_no_route(pax_left_to_do);
merke_passagier_ziel(destinations[current_destination].location, COL_DARK_ORANGE);
}
}
}
else
{
// Return halt crowded. Either return by car or mark unhappy.
if(car_minutes < 65535)
{
return_in_private_car = true;
}
else
{
ret_halt->add_pax_unhappy(pax_left_to_do);
merke_passagier_ziel(destinations[current_destination].location, COL_RED);
}
}
}

if(return_in_private_car)
{
if(car_minutes < 65535)
{
// Do not check tolerance, as they must come back!
stadt_t* const destination_town = destinations[0].type == 1 ? destinations[0].object.town : NULL;
set_private_car_trip(num_pax, destination_town);
merke_passagier_ziel(destinations[current_destination].location, COL_TURQUOISE);
#ifdef DESTINATION_CITYCARS
//citycars with destination
erzeuge_verkehrsteilnehmer(k, step_count, destinations[0].location);
#endif
}
else
{
if(ret_halt.is_bound())
{
ret_halt->add_pax_no_route(pax_left_to_do);
}
merke_passagier_ziel(destinations[current_destination].location, COL_DARK_ORANGE);
}
}
} // Returning passengers

INT_CHECK( "simcity 3128" );
current_destination ++;
} // While loop (route_good)

if((route_good != good && route_good != private_car_only) && start_halts.get_count() > 0)
{
// Passengers/mail cannot reach their destination at all, either by public transport or private car,
// *but* there are some stops in their locality. Record their inability to get where they are going
// at those local stops accordingly.
halthandle_t start_halt = start_halts[best_bad_start_halt]; //If there is no route, it does not matter where passengers express their unhappiness.
if(!has_private_car || car_minutes > tolerance)
{
// If the passengers are able to use a private car, then they should not record as "no route"
if(too_slow_already_set)
{
if(start_halt.is_bound())
{
start_halt->add_pax_too_slow(pax_left_to_do);
}
merke_passagier_ziel(best_bad_destination, COL_LIGHT_PURPLE);
}
else
{
if(start_halt.is_bound())
{
start_halt->add_pax_no_route(pax_left_to_do);
}
merke_passagier_ziel(destinations[0].location, COL_DARK_ORANGE);
}
}
}

} // For loop (passenger/mail packets)

} // If statement (are some start halts, or passengers have private car)

else
{
// The unhappy passengers will be added to all crowded stops
// however, there might be no stop too
// NOTE: Because of the conditional statement in the original loop,
// reaching this code means that passengers must not be able to use
// a private car for this journey.

// all passengers without suitable start:
// fake one ride to get a proper display of destinations (although there may be more) ...
pax_zieltyp will_return;
//const koord ziel = finde_passagier_ziel(&will_return);
destination destination_now = finde_passagier_ziel(&will_return);

// First, check whether the passengers can *walk*. Just because
// they do not have a start halt does not mean that they cannot
// walk to their destination!
const double tile_distance = accurate_distance(k, destination_now.location);
if(tile_distance < welt->get_einstellungen()->get_max_walking_distance())
{
// Passengers will walk to their destination if it is within the specified range.
// (Default: 1.5km)
merke_passagier_ziel(destination_now.location, COL_DARK_YELLOW);
city_history_year[0][history_type] += num_pax;
city_history_month[0][history_type] += num_pax;
}
else
{
// If the passengers cannot walk, they will be unhappy.

bool crowded_halts = false;
for(  uint h=0;  h<plan->get_haltlist_count(); h++  )
{
halthandle_t halt = halt_list[h];
if (halt->is_enabled(wtyp))
{
halt->add_pax_unhappy(num_pax);
crowded_halts = true;
}
}

// Only show as being overcrowded if there are, in fact, potentially suitable but overcrowded stops.
if(crowded_halts)
{
merke_passagier_ziel(destination_now.location, COL_RED);
}

else
{
merke_passagier_ziel(destination_now.location, COL_DARK_ORANGE);
}
// we do not show no route for destination stop!
}
}

// long t1 = get_current_time_millis();
// DBG_MESSAGE("stadt_t::step_passagiere()", "Zeit für Passagierstep: %ld ms\n", (long)(t1 - t0));
}

Special bonuses, etc.

The difficulty with the "one ticket" system is that anything that changes the way in which passengers and goods find their route is very difficult to implement. Currently, the system is that the one and only criterion is overall journey time. It would be possible to add comfort as a factor, but the problem with doing so (which problem is even greater if something more complicated such as that which you suggest is implemented) is that it would enormously increase the computational load. Instead of a straight comparison between two integers to determine which journey is better, a large number of steps would have to be processed (possibly including division or floating-point arithmetic). The routing of goods/passengers is a performance critical part of the game, and even the change from the very simple system used in Standard to the journey time based system used in Experimental required careful monitoring of performance implications, and was only possible because computing technology has moved on a long way since Standard was first written in the late 1990s. I also have some doubts that the preference to remain within one company's ticketing system has a significant enough effect on real world passenger behaviour to be worthwhile modelling.

The issue that you seek to address by changing route part-way through is addressed in another way by the use of the waiting times feature: if passengers/goods have to wait too long at the stop, the overall journey time of the route through that stop would be greatly increased, such that a journey time based system of route finding would find another route for at least some of the passengers/goods routed through that stop, reducing the numbers who have to transfer through there, and therefore the waiting time. This process would continue until equilibrium is reached, more or less.

I am not sure that I fully follow your "flooding" example, as player A in that instance would simply be increasing demand for player B's network, to which player B could respond by increasing supply (which would increase player B's profitability). If player B could not keep up with the demand, the waiting times would increase, reducing the number of passengers who use the route set by player A, as the journey time tolerances would be exceeded in a greater number of cases.

In any event, the only difference between what you propose and what is actually implemented is that the effect of high waiting times would be felt slightly faster, but at a cost of greatly increased computational load and code that is less easy to maintain.

Returning passengers

Something similar to what you suggest is already implemented in Simutrans (both Standard and Experimental), although the system does not involve tracking individual passengers (as that would add unnecessary computational load): returning passengers are generated at the same time as outward bound passengers are generated, which does indeed reduce the computational load as you suggest.

***

Thank you again for taking the time to set out all of your suggestions, many of which, as you will note, are well-considered enough already to have been included. Your continued feedback would be appreciated - enjoy Simutrans-Experimental!

Offline chs

  • *
  • Posts: 35
Re: Experimental Feature Ideas
« Reply #2 on: February 13, 2011, 05:26:19 PM »
Thanks for your detailed feedback!

Cars
Sorry, I did not notice that this was already done - started in 1990 and got crowded by cars. I did not think that so many people could be travelling. Should have started 100 years earlier.
Is citycar_level a linear value? 16 = 1 car per private journey, 8 = 1 car per 2 private car journeys?

Reputation
I understand your point, and I think that in reality (unfortunately) this overgeneralisation is pretty much the case. I simply thought of it as means to increase the probability of using public transport (as in my settings, the preference for cars was very high). However, it was just an idea, I dont see an issue as is.

Private car choice
I see. Most of my algorithm was based on using the other stuff I thought of. I am fine with the current implementation.
About including the comfort in route selection, I agree that this has the potential to become quite complicated. If you are interested, I could go deeper into this to come up with some realistic ways to do it. I guess it should be possible to implement it with little additional computational load, as most time is probably used to find all possible routes. Once they are found, calculating the time (and comfort) for each route is relatively easy (linear). Just guessing.

Special bonuses
I agree that the "one ticket" influence is not big enough to make it worth the effort.

I try to make my flooding example more clear. Say player A has a local network in City A, player B has a local network in City B. Now player A wants to transport passengers between both cities. Currently, there are several possibilities to do this:
1. Player A builds one stop X in the other city B and creates a intercity connection to this stop. The result is that passengers from city A can only go to stop X, nowhere else in city B.
2. As 1., but make stop X a public stop, player B includes this stop in his local network in city B. Now passengers from city A can go anywhere in city B. This requires the cooperation of player B.
3. Player A builds a complete local network in city B and creates an intercity connection. Again, passengers from city A can now go anywhere in city B. The drawback is that this is completely unrealistic and is a financial loose-loose situation for both players.

In reality, if player A simply adds the intercity connection to anywhere near a local transport stop in city B, passengers will use the intercity connection and then switch to the local transport. A real life example: I am currently in Singapore and will soon go by bus to Kuala Lumpur. Companies for the bus (intercity connection) and monorail in Kuala Lumpur (local transport) are different. The bus simply stops relatively close to a monorail station and I will walk there, buy a ticket and continue my journey by monorail. I will use both companies, though they have no real cooperation whatsoever. This is what I would like to see in the game.
In game terms, if there are 2 separate stops from different companies located next to each other, the passengers should be able to include the option "walk to the other station, continue journey there" in their route calculation.

The programming impact would hopefully be realtively small, as "only" the list of possible connections from a station has to be updated (same as when I create/change a line, but triggered when placing/destroying any station), together with a (relatively high) required time to walk from one stop to another as described.

Returning passengers
This too is already implemented? Nice. I came up with it as I kept shipping busloads of tourists to an attraction, but returned with half-empty buses. Probably I should have observed it better.


Sorry for bringing up points that are already implemented or discussed. Please consider this just ideas, if they were analysed and found not optimal I am fine with that. I might even come up with more ideas, as I like the game and keep playing.

Offline chs

  • *
  • Posts: 35
Re: Experimental Feature Ideas
« Reply #3 on: February 14, 2011, 08:29:42 AM »
Some more, mostly smaller ideas.


Additional Information

In the game, I would like to have access to some more information.

1. Free capacity and transported passengers for each leg of a line, per month.
In particular within cities, this information would allow to improve the network much easier.

2. For a line, how long it takes on average to complete this line once (in game time), and the number of times the line has been completed, both per month.
If I use vehicles of the same type in this line, this information allows to easily improve the scheduling.

3. For a station, I would like to see how many passengers boarded each line (in each direction), per month.
This would be the sum of transported passengers of all lines that happen to have the same leg. Same purpose.

The "convoys per month" turns out to be extremely useful, in particular for competition situations. To set this value optimally, I like to know either how many passengers I have transported (1. or 3.) to set "convoys per month" based on the capacity, or how long a roundtrip takes / how many roundtrips per month (2.) if I base my setting on time. So far, I have been doing this by "trial an error", observing roundtrip time or guessing the amount of transported people (which is difficult to impossible for lines with several legs).

4. Nice to have - infos
- station, vehicle and line count (per player)
Its relatively simple, more out of curiosity than real need.
- how many passengers that started journeys within my stations reach did use my public transport to get to their destination?
- how many did not start because my network is too slow (sum over all stations "too slow" value)?
- how many people were unhappy (sum of station values)?
These would give me global indicators how well my whole network is developing, as now I found these only for each station. If these values are available per player, it would also be helpful for competition. This values would be nice in % and in absolute values.
- How many did not start because their destination cannot be reached with my transport network plus connected networks?
- How many travelled by private car?
- How many journeys were intended, how many really started?
This gives me information about the potential number of passengers.
- how much (%) of the total map is accessible via public transport using a) my network, b) my network plus connected other players networks, c) all players networks (as if they were all connected)
- for each city, how many passengers (%) use cars/my company/each other company to get to their destination?
This shows me how am I doing relatively to other companies.
- number of unhappy people per line (passengers who left unhappy because they had to wait too long for a vehicle of that line to appear at any station of the line)
Makes it easier to find line issues. Currently, at a station, I see unhappy people, but can only guess (based on the waiting time) which line is responsible).

Station Filter
In my station list, I can filter by "overcrowded". This seems to return stations with at least a certain number of unhappy people, even if the station itself is not overcrowded and has never been. In that case, if I understand the waiting time tolerance correctly, this is caused by passengers that  had to wait too long at this station, so they left as unhappy. I would like to be able to separate these two causes in the filter.
(regarding journey time, each person happens to have a journey time limit. this limit is only used to decide whether to travel at all. the actual journey may take more time, i.e. passengers do not leave as "unhappy" once their total journey time is used up. is this correct?)


Station details
In the station details is information about waiting and travelling time to directly connected stops, both in minutes. I would prefer this information in game time because the line schedule settings also use game time, and because on my system the time in minutes does not match real time, so I cannot really use this value (only compare with my competition, which is already nice).
Plus, the acceptable waiting time for each destination would also be great. This tells me how fast my service is compared to expectations.


Maps
I like the maps of simutrans standard, but I did not manage to generate similar maps with simutrans experimental. How is this possible? In particular, I could not get 64 cities on a map, regardless of its size. Optimally, I would like to keep the new settings (nof big cities & clusters) while generating similar maps as in standard.


Change Citycar_level in game
I like to start around 1990, because then already nice transport vehicles are available. If I do this with a citycar_level of 16, I end up with so many private cars, buses are practically unusable. Therefore, I would like to start with maybe 8, and then increase it later as my public transport networks grows. This would be a shortcut compared to playing years more to develop a network.
Maybe the same setting options as when creating a new map in a running game?