Page 1 of 1

Shipyard generation

Posted: Tue Dec 30, 2014 5:27 pm
by jh145
[url=https://bb.oolite.space/viewtopic.php?p=231204#p231204]in another thread[/url], cim wrote:
jh145 wrote:
I can't tell how that "random" value is (re)seeded
In theory that random value was itself reseeded every 90ish days, but there was an error in that code so it was in effect a constant. With changes elsewhere over the last few versions that seed value is now only used by the shipyard, so it's been replaced with the constant for clarity until someone gets round to getting rid of it entirely and rewriting the shipyard generation to be something more transparent.
It got me thinking, so I hacked together an alternative way of generating shipyards. Simple demonstrator code below (sorry I can't find a way of making it smaller on the screen), fwiw.

If this comes at the wrong time, in the wrong language, on the wrong forum, that's okay; I'm here to learn.

Code: Select all

#!/usr/bin/env python
#
# Demonstration of simple no-state shipyard generation.
#
# This shows one way to generate shipyards in a reasonably robust manner
# with no state saved to disk.  The shipyard generation function just
# needs a shipyard id, the time, and a list of ships allowed to be sold
# in the yard (or we could change it to take a list of all ships in the
# game and let the yard filter by tech level etc).  Each ship in the
# list needs two fields: guid and chance (and, for the demo, name).
# The shipyard may optionally support a non-standard number of sales
# slots, sales rate, and replacement efficiency.
#
# The shipyard evolves gradually over time, which I hope is considered
# an improvement over the current all-or-nothing update behaviour.

from hashlib import md5

__all__ = ['shipyard']   ### API


# -------------------------------------------------------------------------
# defaults for the shipyard() function call
# -------------------------------------------------------------------------

DEFAULT_NUMSLOTS   = 20         ### up to 20 ships for sale
DEFAULT_CYCLE      = 90*86400   ### inventory lasts ~90 days
DEFAULT_EFFICIENCY = 0.8        ### typically ~80% stocked


# -------------------------------------------------------------------------
# utility functions
# -------------------------------------------------------------------------

def B128(string):
    return int(md5(string).hexdigest(),16)

def U01(string):
    return B128(string) / 2.0**128


# -------------------------------------------------------------------------
# what ship is in which slot at what time?
# -------------------------------------------------------------------------

def ship_in_slot(
    yard_id,
    ship_catalogue,
    when,
    yard_slotidx,
    yard_numslots,
    yard_cycle,
    yard_efficiency
):

    # catch various degenerate conditions
    #
    if yard_numslots==0 or yard_efficiency==0 or not ship_catalogue:
        return None
    assert(0<=yard_slotidx<yard_numslots)
    assert(0<yard_efficiency<=1)
    assert(yard_cycle>0)

    # 1/slot_cycle = x/yard_cycle for x in [0.5,1.5]
    # then use that to quantise time
    #
    if yard_numslots == 1:
        x = 1
    else:
        x = 0.5 + yard_slotidx/(yard_numslots-1.0)
    cycle_nr = int(when*x/yard_cycle)

    # seed the PRNG on this shipyard, this slot, this cycle nr
    #
    tag = 'yard=%s,slot=%s,cycnr=%s' % (yard_id,yard_slotidx,cycle_nr)

    # choose a ship, but reorder first to give a little
    # robustness in the face of catalogue changes
    #
    ship_catalogue = sorted(
        ship_catalogue,
        key=lambda s:md5('catalogue[%s,ship=%s]'%(tag,s.guid)).digest()
    )
    prtot  = sum(ship.chance for ship in ship_catalogue)
    ship_p = U01('ship[%s]'%tag) * prtot/yard_efficiency
    prsum  = 0
    for ship in ship_catalogue:
        prsum += ship.chance
        if prsum > ship_p:
            break
    else:
        return None   ### inefficient yard hasn't restocked

    # choose equipment
    # -- for the demo, just give a raw bit vector
    #
    eq_bits = B128('equipment[%s,ship=%s]'%(tag,ship.guid))
    equipment = eq_bits

    # we're done
    #
    return ship,equipment


# -------------------------------------------------------------------------
# calculate a whole shipyard's contents
# (simplified to return a printable string for this demo)
# -------------------------------------------------------------------------

def shipyard(
    yard_id,
    ship_catalogue,
    when,
    yard_numslots=DEFAULT_NUMSLOTS,
    yard_cycle=DEFAULT_CYCLE,
    yard_efficiency=DEFAULT_EFFICIENCY
):
    def printable_slot(s):
        name,equipment = (s[0].name,s[1]) if s else ('---',0)
        return '%20s (eq=%032x)' % (name,equipment)
    return [
        printable_slot( ship_in_slot(
            yard_id,
            ship_catalogue,
            when,
            yard_slotidx,
            yard_numslots,
            yard_cycle,
            yard_efficiency
        ) ) for yard_slotidx in range(yard_numslots)
    ]


# -------------------------------------------------------------------------
# simple demo
# -------------------------------------------------------------------------

def demo():

    class Ship:
        def __init__(self,guid,name,chance):
            self.guid      = guid
            self.name      = name
            self.chance    = chance
            # don't bother emulating anything else for this simple demo

    DAY_BEFORE  = 50
    DAY_AFTER   = 90
    SHIPYARD_ID = 'Honest Jameson\'s'
    PV_RARE     = Ship('Pit Viper','Pit Viper',0.1)
    PV_COMMON   = Ship('Pit Viper','Pit Viper',0.5)

    def show(title,left,right):
        print
        print title
        print
        for l,r in zip(left,right):
            if l == r:
                print l, '   ', r
            elif '--- (' in l:
                print l, ' > ', r
            elif '--- (' in r:
                print l, ' < ', r
            else:
                print l, ' | ', r

    # you must pass a catalogue of ships suitable for this station
    # i.e. caller does filtering by tech level etc.
    #
    ship_catalogue = [Ship(name,name,chance) for name,chance in (
        ( 'Cobra Mk III', 1 ),
        ( 'Fer-de-Lance', 0.9 ),
        ( 'Python', 1 ),
        ( 'Python ET Special', 0.6 ),
        ( 'Wolf Mk I', 0.7 ),
    )]
    ship_catalogue.append(PV_RARE)

    # Normal behaviour: some ships sell (and perhaps get replaced),
    # others don't.  Later slots update more quickly.
    #
    before = shipyard(SHIPYARD_ID,ship_catalogue,DAY_BEFORE*86400)
    after  = shipyard(SHIPYARD_ID,ship_catalogue,DAY_AFTER*86400)
    show(
        'Day %d, then %d days later ...' % (DAY_BEFORE,DAY_AFTER-DAY_BEFORE),
        before, after
    )

    # Introducing new ships to the game causes some shipyard slots
    # to be given over to the new ships, but will also cause some
    # replacement of old ships with different old ships.
    #
    ship_catalogue += [Ship(name,name,chance) for name,chance in (
        ( '*** OXP ship 1 ***', 0.8 ),
        ( '*** OXP ship 2 ***', 0.8 ),
    )]
    reloaded = shipyard(SHIPYARD_ID,ship_catalogue,DAY_AFTER*86400)
    show(
        'Reloading day %d with two new OXP ships ...' % DAY_AFTER,
        after, reloaded
    )

    # Changing the chance of a ship has a similar effect to the
    # introduction of new ships.
    #
    ship_catalogue = [s for s in ship_catalogue if s.guid!=PV_RARE.guid]
    ship_catalogue.append(PV_COMMON)
    morevipers = shipyard(SHIPYARD_ID,ship_catalogue,DAY_AFTER*86400)
    show(
        'Making the Pit Viper more probable ...',
        reloaded, morevipers
    )

    print


# -------------------------------------------------------------------------
# CLI
# -------------------------------------------------------------------------

if __name__ == '__main__':
    demo()
[/size]

Re: Shipyard generation

Posted: Tue Dec 30, 2014 5:49 pm
by cim
Interesting idea, and it should be relatively straightforward to implement something similar in the shipyard generation code.

How many slots should a shipyard actually have? At the moment they all have 256 but the generation algorithm is horribly inefficient so almost all of those slots are empty at any given time. I'm thinking that if it's actually cycling the ships round and mostly filling the slots it doesn't need to be that many, and something like 8-32 (depending on system TL) should be fine.

Re: Shipyard generation

Posted: Tue Dec 30, 2014 6:22 pm
by Disembodied
cim wrote:
How many slots should a shipyard actually have? At the moment they all have 256 but the generation algorithm is horribly inefficient so almost all of those slots are empty at any given time. I'm thinking that if it's actually cycling the ships round and mostly filling the slots it doesn't need to be that many, and something like 8-32 (depending on system TL) should be fine.
Should ships have the same price on every system? Or should there be a variable element to the ship price (sometimes positive, sometimes negative), depending on what a given system is like? Should ships be cheaper at a poor backwater than at a rich hub, for example? And/or should a Python be cheaper at a TL8 system than at a TL4 system (and cheaper still at a TL12 system)? There's adequate handwavium available to cover uniform pricing (for example, all ships are sold to, and bought from, the Co-operative, which regulates prices), but is there any scope/desire/benefit from giving a bit of system-based variety here?

A similar variable element could conceivably be applied to equipment prices, too ...

Re: Shipyard generation

Posted: Tue Dec 30, 2014 6:29 pm
by jh145
cim> How many slots should a shipyard actually have?
From limited play, it's nice to have up to 20 ships available, but who wants all shipyards to be the same? Buying a ship is a relatively rare thing: perhaps the norm should be more like 5-10 ships, with a wider selection available only at special places (like high TL systems, as currently).

Disembodied> Should ships have the same price on every system?
Not sure about ships, but I think I've seen equipment being overpriced (rightly so) at Space Bars.

I'd be happy to code-up variations for the Python demonstrator, if that's of any use to people in seeing how the proposal might work in practice. (But if not, that's fine too.)

______________
PS: (with apologies) my nine-year-old has just demanded I include a smiley, so here are two: :mrgreen: :lol:

Re: Shipyard generation

Posted: Tue Dec 30, 2014 6:35 pm
by Cody
jh145 wrote:
... perhaps the norm should be more like 5-10 ships, with a wider selection available only at special places (like high TL systems, as currently).
The number of available ships at any one station does vary - I was at a high TL last night that had no ships for sale at all.

<grins - waves at nine-year-old>

Re: Shipyard generation

Posted: Tue Dec 30, 2014 6:42 pm
by Disembodied
jh145 wrote:
Not sure about ships, but I think I've seen equipment being overpriced (rightly so) at Space Bars.
That's true - there's already a variable component which can be applied to equipment prices at various stations. That could still work, of course, with a base price which varied from system to system.

Re: Shipyard generation

Posted: Tue Dec 30, 2014 6:58 pm
by Venator Dha
I wonder if the condition/maintenance proposals if implemented in 1.82 could have an effect on the price of ships. Do you by a FdL in need of some TLC or wait until you've earned the cash for a mint condition one?

Re: Shipyard generation

Posted: Tue Dec 30, 2014 7:29 pm
by cim
Disembodied wrote:
That's true - there's already a variable component which can be applied to equipment prices at various stations. That could still work, of course, with a base price which varied from system to system.
Risks making it possible to make some potentially very large profits on buying and selling ships, unless the trade-in value of a ship is always stuck at 1x multiplier, and only the ship purchase prices at the station are multiplied higher.

There's already a 75% multiplier on trade-in value compared with your ship's actual value which - according to the comments in the source - was inserted to try to stop that sort of exploit.

(See for instance the GalNavy OXP exploit where you could buy Q-mines cheap at the naval station and then sell them for their normal price at the nearby main station)
Venator Dha wrote:
I wonder if the condition/maintenance proposals if implemented in 1.82 could have an effect on the price of ships. Do you by a FdL in need of some TLC or wait until you've earned the cash for a mint condition one?
Unless maintenance costs were increased to be based on ~100% of ship value rather than ~1% of ship value, which is OXPable now but not part of the maintenance proposals, I'm not sure it would ever be advantageous to buy the new one.

Re: Shipyard generation

Posted: Tue Dec 30, 2014 9:42 pm
by Disembodied
cim wrote:
Risks making it possible to make some potentially very large profits on buying and selling ships, unless the trade-in value of a ship is always stuck at 1x multiplier, and only the ship purchase prices at the station are multiplied higher.

There's already a 75% multiplier on trade-in value compared with your ship's actual value which - according to the comments in the source - was inserted to try to stop that sort of exploit.

(See for instance the GalNavy OXP exploit where you could buy Q-mines cheap at the naval station and then sell them for their normal price at the nearby main station)
Ah yes, I hadn't considered the ship trade-in exploit aspect ... although a big negative on the player's selling price could prevent that. If it was possible to make a profit, it would probably be a rare occurrence (and might result in an interesting new career, with the player having to buy and fly some odd clunkers across the galaxy in the hope of selling them at a profit elsewhere, without knowing what sorts of ships will be on sale when they get there).

Re: Shipyard generation

Posted: Tue Dec 30, 2014 11:00 pm
by Fatleaf
That could be an oxp idea, you are contracted to pilot and deliver different ships to clients. There could be a lot of potential to vary the difficulty and get a bonus for delivering it without a scratch and you get reduced pay depending on damage.

Re: Shipyard generation

Posted: Wed Dec 31, 2014 7:20 am
by another_commander
jh145 wrote:
[...] so I hacked together an alternative way of generating shipyards.
The Dark Side is strong with this one...


Which is a Good Thing in my book. ;-)

Re: Shipyard generation

Posted: Wed Dec 31, 2014 6:08 pm
by jh145
I'm toying with the idea of dealership tie-ins. The shipyard would have a yearly (say) tie-in to particular ships, with 80% of the shipyard devoted to that make/model. The remaining 20% would be random ships as usual. This would give a bit more "personality" to shipyards whilst, if implemented carefully, not changing the overall distribution of ships in the Ooniverse. You want a Python ET Special? -- then go look for a dealership (expect to pay higher when you find one), or get lucky seeing the ship of your dreams in the odds 'n' sods corner of another shipyard.

Interesting, or just a pain in the aft?

Re: Shipyard generation

Posted: Wed Dec 31, 2014 6:30 pm
by cim
jh145 wrote:
Interesting, or just a pain in the aft?
Definitely sounds interesting, but it feels a bit too much complexity for a relatively little-used mechanic in core Oolite.

However, what this is suggesting to me is that the generation of the station shipyard should be run at least partially through Javascript, so that OXPs can customise the generation routines to implement something like this. As shipyard generation is a relatively rare event and happens only while docked, potentially being slightly slower as a result shouldn't be a major problem.