Universe generator: resource providers

* The universe generator has been modified to generate resource
providers. The code attempts to keep the universe balanced according to
the natural resources definitions.
This commit is contained in:
Emmanuel BENOîT 2012-01-06 10:05:47 +01:00
parent e7d2072813
commit b054a379a9
24 changed files with 1105 additions and 6 deletions

View file

@ -16,6 +16,8 @@
\i parts/functions/035-users-view.sql
\i parts/functions/040-empire-functions.sql
\i parts/functions/050-computation-functions.sql
\i parts/functions/053-generator-basics.sql
\i parts/functions/055-generator-resources.sql
\i parts/functions/060-universe-functions.sql
\i parts/functions/070-users-functions.sql
\i parts/functions/075-session-functions.sql

View file

@ -7,6 +7,115 @@
-- --------------------------------------------------------
/*
* Random value with deviation
*
* Parameters:
* _mean the mean value
* _deviation the deviation
*
* Returns:
* ? a random value between _mean - _deviation and
* _mean + _deviation, with a higher probability
* of a value that is close to _mean
*/
DROP FUNCTION IF EXISTS verse.random_deviation( DOUBLE PRECISION , DOUBLE PRECISION );
CREATE FUNCTION verse.random_deviation( _mean DOUBLE PRECISION , _deviation DOUBLE PRECISION )
RETURNS DOUBLE PRECISION
STRICT VOLATILE
SECURITY INVOKER
AS $random_deviation$
DECLARE
_result DOUBLE PRECISION;
BEGIN
_result := _deviation * RANDOM( ) ^ 2.5;
IF RANDOM() < 0.5 THEN
_result := -_result;
END IF;
RETURN _result + _mean;
END;
$random_deviation$ LANGUAGE PLPGSQL;
REVOKE EXECUTE
ON FUNCTION verse.random_deviation( DOUBLE PRECISION , DOUBLE PRECISION )
FROM PUBLIC;
/*
* Randomly distribute some part of a total value
*
* This function can be used when a total value must be distributed between
* various items. It will compute the minimal and maximal values that may be
* attributed, enforcing the fact that the whole value needs to be consumed
* in the end, and that values must conform to a specific range expressed as
* a mean value and a deviation.
*
* The total value is assumed to be valid with regards to the mean and
* deviation. That is:
*
* _parts * ( _mean - _deviation ) <= _quantity
* _parts * ( _mean + _deviation ) >= _quantity
*
* Parameters:
* _quantity the total quantity left to distribute
* _parts the amount of items left
* _mean the result's mean value
* _deviation the result's deviation
*
* Returns:
* ? the value to attribute to the nex item
*/
DROP FUNCTION IF EXISTS verse.get_random_part( DOUBLE PRECISION , INT , DOUBLE PRECISION , DOUBLE PRECISION );
CREATE FUNCTION verse.get_random_part(
_quantity DOUBLE PRECISION ,
_parts INT ,
_mean DOUBLE PRECISION ,
_deviation DOUBLE PRECISION )
RETURNS DOUBLE PRECISION
STRICT VOLATILE
SECURITY INVOKER
AS $get_random_part$
DECLARE
_min DOUBLE PRECISION;
_max DOUBLE PRECISION;
_n_mean DOUBLE PRECISION;
BEGIN
IF _parts = 1 THEN
RETURN _quantity;
END IF;
_min := _quantity - ( _mean + _deviation ) * ( _parts - 1 );
IF _min < _mean - _deviation THEN
_min := _mean - _deviation;
END IF;
_max := _quantity - ( _mean - _deviation ) * ( _parts - 1 );
IF _max > _mean + _deviation THEN
_max := _mean + _deviation;
END IF;
IF _min = _max THEN
RETURN _min;
END IF;
_n_mean := ( _min + _max ) * 0.5;
RETURN verse.random_deviation( _n_mean , _n_mean - _min );
END;
$get_random_part$ LANGUAGE PLPGSQL;
REVOKE EXECUTE
ON FUNCTION verse.get_random_part( DOUBLE PRECISION , INT , DOUBLE PRECISION , DOUBLE PRECISION )
FROM PUBLIC;
--
-- sigma( x ) = exp( x ) / ( 1 + exp( x ) )
--

View file

@ -0,0 +1,51 @@
-- LegacyWorlds Beta 6
-- PostgreSQL database scripts
--
-- A few types and internal functions used in most parts
-- of the universe generator.
--
-- Copyright(C) 2004-2012, DeepClone Development
-- --------------------------------------------------------
/* The coordinates of the area being generated. */
DROP TYPE IF EXISTS verse.generator_area_type CASCADE;
CREATE TYPE verse.generator_area_type AS (
x0 INT , y0 INT ,
x1 INT , y1 INT
);
/*
* List some quantity of random planets from an area of the universe
*
* This function returns a set of planet identifiers chosen at random from the
* specified area of the universe.
*
* Parameters:
* _area The area to select planets from
* _count The maximal amount of planets to return
*/
DROP FUNCTION IF EXISTS verse.list_random_planets_in( verse.generator_area_type , INT );
CREATE FUNCTION verse.list_random_planets_in( _area verse.generator_area_type , _count INT )
RETURNS SETOF INT
STRICT VOLATILE
SECURITY INVOKER
AS $list_random_planets_in$
SELECT _planets.name_id
FROM verse.planets _planets
INNER JOIN verse.systems _systems
ON _planets.system_id = _systems.id
WHERE _systems.x BETWEEN $1.x0 AND $1.x1
AND _systems.y BETWEEN $1.y0 AND $1.y1
ORDER BY RANDOM( )
LIMIT $2;
$list_random_planets_in$ LANGUAGE SQL;
REVOKE EXECUTE
ON FUNCTION verse.list_random_planets_in( verse.generator_area_type ,
INT )
FROM PUBLIC;

View file

@ -0,0 +1,384 @@
-- LegacyWorlds Beta 6
-- PostgreSQL database scripts
--
-- Functions for the resource provider generator
--
-- Copyright(C) 2004-2012, DeepClone Development
-- --------------------------------------------------------
/*
* Resource provider generator data
*
* This data type is used to store statistics about the existing resource
* providers. A single item of the type represents both statistics and
* parameters for a given resource type.
*/
DROP TYPE IF EXISTS verse.resprov_generator_type CASCADE;
CREATE TYPE verse.resprov_generator_type AS (
/* Type of natural resource */
resource_name_id INT ,
/* Planets in the universe */
planets DOUBLE PRECISION ,
/* Providers of this type in the universe */
providers DOUBLE PRECISION ,
/* Presence probability (from the resource's definition) */
presence DOUBLE PRECISION ,
/* Total maximal quantity of this type of resource in the whole
* universe.
*/
quantity DOUBLE PRECISION ,
/* Average quantity (from the resource's definition) */
quantity_avg DOUBLE PRECISION ,
/* Maximal deviation from the average quantity (from the resource's
* definition)
*/
quantity_dev DOUBLE PRECISION ,
/* Total extraction difficulty for this type of resource in the whole
* universe.
*/
difficulty DOUBLE PRECISION ,
/* Average difficulty (from the resource's definition) */
difficulty_avg DOUBLE PRECISION ,
/* Maximal deviation from the average difficulty (from the resource's
* definition)
*/
difficulty_dev DOUBLE PRECISION ,
/* Total recovery rate for this type of resource in the whole
* universe.
*/
recovery DOUBLE PRECISION ,
/* Average recovery rate (from the resource's definition) */
recovery_avg DOUBLE PRECISION ,
/* Maximal deviation from the average recovery rate (from the
*/
recovery_dev DOUBLE PRECISION
);
/*
* Collect resource provider statistics
*
* This procedure collects statistics about resource providers into a
* temporary table named resource_statistics, using the resprov_generator_type
* as the table's structure. The table will be dropped on commit.
*
* This function is necessary because the statistics must be collected before
* new planets are generated.
*/
DROP FUNCTION IF EXISTS verse.collect_resprov_statistics( );
CREATE FUNCTION verse.collect_resprov_statistics( )
RETURNS VOID
STRICT VOLATILE
SECURITY INVOKER
AS $collect_resprov_statistics$
BEGIN
CREATE TEMP TABLE rp_stats
OF verse.resprov_generator_type
ON COMMIT DROP;
INSERT INTO rp_stats
SELECT resource_name_id ,
_pcount.planets AS planets ,
( CASE
WHEN _rp_stats.providers IS NULL THEN 0
ELSE _rp_stats.providers
END ) AS providers ,
natres_p_presence AS presence ,
( CASE
WHEN _rp_stats.tot_quantity IS NULL THEN 0
ELSE _rp_stats.tot_quantity
END ) AS quantity ,
natres_quantity_avg AS quantity_avg ,
natres_quantity_dev AS quantity_dev ,
( CASE
WHEN _rp_stats.tot_difficulty IS NULL THEN 0
ELSE _rp_stats.tot_difficulty
END ) AS difficulty ,
natres_difficulty_avg AS difficulty_avg ,
natres_difficulty_dev AS difficulty_dev ,
( CASE
WHEN _rp_stats.tot_recovery IS NULL THEN 0
ELSE _rp_stats.tot_recovery
END ) AS recovery ,
natres_recovery_avg AS recovery_avg ,
natres_recovery_dev AS recovery_dev
FROM defs.natural_resources
LEFT OUTER JOIN (
SELECT resource_name_id ,
COUNT(*) AS providers ,
SUM( resprov_quantity_max ) AS tot_quantity ,
SUM( resprov_difficulty ) AS tot_difficulty ,
SUM( resprov_recovery ) AS tot_recovery
FROM verse.resource_providers
GROUP BY resource_name_id
) AS _rp_stats USING ( resource_name_id )
CROSS JOIN (
SELECT COUNT(*) AS planets
FROM verse.planets
) AS _pcount;
END;
$collect_resprov_statistics$ LANGUAGE PLPGSQL;
REVOKE EXECUTE
ON FUNCTION verse.collect_resprov_statistics( )
FROM PUBLIC;
/* Compute a random delta for one of the resource provider parameters
*
* This function computes the total change on one of the resource provider
* parameters. The resulting value can then be split amongst resource
* providers as they are created.
*
* Parameters:
* _existing Amount of existing resource providers
* _new Amount of resource providers being created
* _total Current total value for the parameter
* _p_average Average parameter value (from the definition)
* _p_deviation Parameter value deviation (from the definition)
*
* Returns:
* ? The total value to distribute amongst new resource
* providers
*/
DROP FUNCTION IF EXISTS verse.compute_rpp_delta( DOUBLE PRECISION , DOUBLE PRECISION ,
DOUBLE PRECISION , DOUBLE PRECISION , DOUBLE PRECISION );
CREATE FUNCTION verse.compute_rpp_delta(
_existing DOUBLE PRECISION ,
_new DOUBLE PRECISION ,
_total DOUBLE PRECISION ,
_p_average DOUBLE PRECISION ,
_p_deviation DOUBLE PRECISION )
RETURNS DOUBLE PRECISION
STRICT VOLATILE
SECURITY INVOKER
AS $compute_rpp_delta$
DECLARE
_result DOUBLE PRECISION;
BEGIN
_result := verse.random_deviation( _p_average , _p_deviation )
* ( _existing + _new ) - _total;
IF _result < ( _p_average - _p_deviation ) * _new THEN
_result := ( _p_average - _p_deviation ) * _new;
ELSIF _result > ( _p_average + _p_deviation ) * _new THEN
_result := ( _p_average + _p_deviation ) * _new;
END IF;
RETURN _result;
END;
$compute_rpp_delta$ LANGUAGE PLPGSQL;
REVOKE EXECUTE
ON FUNCTION verse.compute_rpp_delta( DOUBLE PRECISION , DOUBLE PRECISION ,
DOUBLE PRECISION , DOUBLE PRECISION , DOUBLE PRECISION )
FROM PUBLIC;
/*
* Create a single resource provider
*
* This function creates a single resource provider on some planet. It will
* return the updated values for the amount of providers left to handle and
* the totals.
*
* Parameters:
* _planet The identifier of the planet to create a provider on
* _data The statistics and parameters for the type of resource
* _providers_left The amount of resource providers that still need to be
* generated (including the current provider)
* _tot_quantity The total value left to distribute for the providers'
* maximal quantity
* _tot_difficulty The total value left to distribute for the providers'
* extraction difficulty
* _tot_recovery The total value left to distribute for the providers'
* recovery rate
*
* Returns:
* _providers_left The updated value for the amount of providers left
* _tot_quantity The updated value for the total maximal quantity to
* distribute
* _tot_difficulty The updated value for the total extraction difficulty
* to distribute
* _tot_recovery The updated value for the total recovery rate to
* distribute
*/
DROP FUNCTION IF EXISTS verse.create_resource_provider(
INT , verse.resprov_generator_type , INT , DOUBLE PRECISION ,
DOUBLE PRECISION , DOUBLE PRECISION );
CREATE FUNCTION verse.create_resource_provider(
_planet INT ,
_data verse.resprov_generator_type ,
INOUT _providers_left INT ,
INOUT _tot_quantity DOUBLE PRECISION ,
INOUT _tot_difficulty DOUBLE PRECISION ,
INOUT _tot_recovery DOUBLE PRECISION )
STRICT VOLATILE
SECURITY INVOKER
AS $create_resource_provider$
DECLARE
_quantity DOUBLE PRECISION;
_difficulty DOUBLE PRECISION;
_recovery DOUBLE PRECISION;
BEGIN
_quantity := verse.get_random_part( _tot_quantity , _providers_left ,
_data.quantity_avg , _data.quantity_dev );
_difficulty := verse.get_random_part( _tot_difficulty , _providers_left ,
_data.difficulty_avg , _data.difficulty_dev );
_recovery := verse.get_random_part( _tot_recovery , _providers_left ,
_data.recovery_avg , _data.recovery_dev );
RAISE NOTICE 'Resource #% planet #%: quantity: % difficulty: % recovery: %',
_data.resource_name_id , _planet ,
_quantity , _difficulty , _recovery;
INSERT INTO verse.resource_providers (
planet_id , resource_name_id , resprov_quantity_max ,
resprov_quantity , resprov_difficulty , resprov_recovery
) VALUES (
_planet , _data.resource_name_id , _quantity ,
_quantity , _difficulty , _recovery
);
_tot_quantity := _tot_quantity - _quantity;
_tot_difficulty := _tot_difficulty - _difficulty;
_tot_recovery := _tot_recovery - _recovery;
_providers_left := _providers_left - 1;
END;
$create_resource_provider$ LANGUAGE PLPGSQL;
REVOKE EXECUTE
ON FUNCTION verse.create_resource_provider( INT ,
verse.resprov_generator_type , INT , DOUBLE PRECISION ,
DOUBLE PRECISION , DOUBLE PRECISION )
FROM PUBLIC;
/*
* Create resource providers for a given type of resource
*
* This function will create resource providers for some specified type of
* resource in an area of the universe. It tries to balance the generated
* values according to the resource's definition.
*
* Parameters:
* _area The area to generate resource providers in
* _data The identifier, definition and statistics for the type of
* resource
*/
DROP FUNCTION IF EXISTS verse.create_resource_providers(
verse.generator_area_type , verse.resprov_generator_type );
CREATE FUNCTION verse.create_resource_providers(
_area verse.generator_area_type ,
_data verse.resprov_generator_type )
RETURNS VOID
STRICT VOLATILE
SECURITY INVOKER
AS $create_resource_providers$
DECLARE
_ncount INT;
_create INT;
_tot_quantity DOUBLE PRECISION;
_tot_difficulty DOUBLE PRECISION;
_tot_recovery DOUBLE PRECISION;
_planet INT;
BEGIN
_ncount := ( _area.x1 - _area.x0 + 1 ) * ( _area.y1 - _area.y0 + 1 ) * 5;
-- Determine the amount of providers to create
_create := FLOOR( ( _data.planets + _ncount ) * _data.presence - _data.providers )::INT;
IF _create <= 0 THEN
RETURN;
ELSIF _create > _ncount THEN
_create := _ncount;
END IF;
-- Compute the total delta for quantity, difficulty and recovery rate
_tot_quantity := verse.compute_rpp_delta( _data.providers , _create ,
_data.quantity , _data.quantity_avg , _data.quantity_dev );
_tot_difficulty := verse.compute_rpp_delta( _data.providers , _create ,
_data.difficulty , _data.difficulty_avg , _data.difficulty_dev );
_tot_recovery := verse.compute_rpp_delta( _data.providers , _create ,
_data.recovery , _data.recovery_avg , _data.recovery_dev );
RAISE NOTICE 'Resource #%: % new provider(s), quantity: % (avg. %) , difficulty: % (avg. %), recovery: % (avg. %)',
_data.resource_name_id , _create ,
_tot_quantity , _tot_quantity / _create ,
_tot_difficulty , _tot_difficulty / _create ,
_tot_recovery , _tot_recovery / _create;
-- Select random planets to add resource providers to
FOR _planet IN SELECT * FROM verse.list_random_planets_in( _area , _create )
LOOP
SELECT INTO _create , _tot_quantity , _tot_difficulty , _tot_recovery
* FROM verse.create_resource_provider( _planet , _data , _create ,
_tot_quantity , _tot_difficulty , _tot_recovery );
END LOOP;
END;
$create_resource_providers$ LANGUAGE PLPGSQL;
REVOKE EXECUTE
ON FUNCTION verse.create_resource_providers( verse.generator_area_type ,
verse.resprov_generator_type )
FROM PUBLIC;
/*
* Create resource providers in some area of the universe
*
* This function creates resource providers in the specified area using the
* statistics collected before the area was created to balance the resource
* providers' parameters.
*
* Parameters:
* _area The area to generate resource providers in
*/
DROP FUNCTION IF EXISTS verse.create_resource_providers( verse.generator_area_type );
CREATE FUNCTION verse.create_resource_providers( _area verse.generator_area_type )
RETURNS VOID
STRICT VOLATILE
SECURITY INVOKER
AS $create_resource_providers$
DECLARE
_rp_data verse.resprov_generator_type;
BEGIN
FOR _rp_data IN SELECT * FROM rp_stats
LOOP
PERFORM verse.create_resource_providers( _area , _rp_data );
END LOOP;
END;
$create_resource_providers$ LANGUAGE PLPGSQL;
REVOKE EXECUTE
ON FUNCTION verse.create_resource_providers( verse.generator_area_type )
FROM PUBLIC;

View file

@ -245,11 +245,12 @@ $$ LANGUAGE plpgsql;
-- Generate multiple systems at the specified coordinates
--
-- Parameters:
-- (x0,y0)-(x1,y1) Area to generate
-- _area Area to generate
-- ipop Initial population
--
CREATE OR REPLACE FUNCTION verse.create_systems( x0 INT , y0 INT , x1 INT , y1 INT , ipop REAL )
DROP FUNCTION IF EXISTS verse.create_systems( verse.generator_area_type , REAL );
CREATE FUNCTION verse.create_systems( _area verse.generator_area_type , ipop REAL )
RETURNS VOID
STRICT VOLATILE
SECURITY INVOKER
@ -259,18 +260,27 @@ DECLARE
y INT;
npics INT;
BEGIN
PERFORM verse.collect_resource_statistics( );
npics := floor( sys.get_constant( 'game.universe.pictures' ) );
FOR x IN x0 .. x1
FOR x IN _area.x0 .. area.x1
LOOP
FOR y IN y0 .. y1
FOR y IN area.y0 .. area.y1
LOOP
PERFORM verse.create_system( x , y , ipop , npics );
END LOOP;
END LOOP;
PERFORM verse.create_resource_providers( _area );
END;
$$ LANGUAGE plpgsql;
REVOKE EXECUTE
ON FUNCTION verse.create_systems( _area verse.generator_area_type , REAL )
FROM PUBLIC;
--
-- Generate the initial universe
@ -348,7 +358,7 @@ BEGIN
-- Get average population and generate new systems
SELECT INTO pop AVG( population ) FROM verse.planets;
PERFORM verse.create_systems( x0 , y0 , x1 , y1 , pop );
PERFORM verse.create_systems( ROW( x0 , y0 , x1 , y1 ) , pop );
END;
$$ LANGUAGE plpgsql;
@ -391,4 +401,4 @@ BEGIN
END;
$$ LANGUAGE plpgsql;
GRANT EXECUTE ON FUNCTION verse.generate() TO :dbuser;
GRANT EXECUTE ON FUNCTION verse.generate() TO :dbuser;