RSS Git Download  Clone
Raw Blame History
package AdvancedShop;

use Types::Standard qw(Int ArrayRef HashRef);
use Data::Printer;

use MaleCustomer;
use FemaleCustomer;

use Moo;

has seats => ( is => 'ro', isa => Int, required => 1, );

has queue => ( 
    is => 'lazy', 
    isa => ArrayRef[Int],
    builder => sub { [ map 0, 1 .. shift->seats ] }, # this needs to be mutable!!    
);

# hold value of Customer->getRequiredTime(), or 10 if no customer waiting:
has duration  => ( is => 'rw', isa => Int );
# how long left in day after subtraction of each duration value:
has time_left => ( is => 'rw', isa => Int );
# hold revenue taken during day:
has cumulative_revenue => ( 
    is => 'rw', 
    isa => Int, 
    default => 0, 
);
# has Customer => ( is => 'lazy' ); # not sure what this is for
has customers => ( is => 'rw', isa => HashRef, default => sub { {} } ); # internal counter for customers recruited & served

# get total revenue taken at end of day, or end of period for averageRevenue():
sub getTotalRevenue { shift->cumulative_revenue }
sub addRevenue {
    my ($self, $amount) = @_; # p $amount;
    $self->cumulative_revenue( $self->cumulative_revenue + $amount );
}

sub getQueue { shift->queue }

sub addToQueue {
    my ($self, $customer) = @_; # $customer is Customer object
    
    my $que = $self->getQueue; # array of Customer objects

    my $i = 0;
    for (@$que) {
        unless ($_) { # skip non-zero vals
            $que->[$i] = $customer; 
            $self->customers->{ref $customer}->{joined_queue}++;
            return 1; # return 'true'
        }
        $i++;
    }
    return 0; # return 'false'
}

sub removeFromQueue {
    my $self = shift;
    
    my $que = $self->getQueue; # array of Customer objects
    my $o   = shift @$que; # p $o; # a Customer object
    push @$que, 0; # p $que;
    return $o || undef; # returns customer object or null
}

# needs to remove empty seats, or seats with Customer object with wait time < 0:
# uses the served (shifted) Customer getRequiredTime() method to determine expire time
sub expire {
    my ($self, $element) = @_; # element = Customer object or undef - shifted from top of queue

    my $que = $self->getQueue; # array of Customer objects
    my $no_of_seats = $self->seats; # p $no_of_seats; # original array size
    
    # get time value to expire from each element of queue, either getRequiredTime()
    # or 10 minutes:
    my $duration = ref $element
        ? $element->getRequiredTime
        : 10; # p $duration;
    # initialise duration() for use in serveOneCustomer:
    $self->duration($duration); 
    
    if ( $self->time_left ) { # adjust time_left today:
        my $time_left = $self->time_left - $duration;
        $self->time_left($time_left);
    }

    my $i = 0; # initialise array index counter
    # for array position 0 to last index position:
    for ( 0 .. $no_of_seats - 1 ) { # p $i;
        my $seat = $que->[$i]; # p $seat;
        my $t = 0; # set default, maybe updated in block:
        if ( ref $seat ) { # we have a Customer object
            # adjust waiting time to subtract $duration derived above:
            $t = $seat->wait - $duration; # p $seat->wait; # p $t;
            $seat->wait($t);
            # need to adjust for male customer leaving if $duration > 35
            $t = 0 if ref $seat eq 'MaleCustomer' && $duration > 35; # p $t;
        } # p $t;
        # auto-increment counter then test element at index position, if element <=0 remove
        # it and decrement counter as next element now occupies current index position:
        $i++;
        splice @$que, --$i, 1 if $t <= 0;
    } # p $que;
    # append zeros until array grows to original size:
    push @$que, 0 while @$que < $no_of_seats;
}

# return male or female customer at 0.3 : 0.7 frequency
sub arrivingCustomer {
    my $self = shift;    
    
    my $rand = rand(1);
    
    # if rand <= 0.7 (70% of the time) , return female customer with random 
    # (>=20 and <50 ) requiredTime:
    if ( $rand <= 0.7 ) { # female 
        my $t = int rand(29) + 20; # p $t;
        my $o = FemaleCustomer->new( requiredTime => $t );
        return $o;    
    }
    # if rand > 0.7 (30% of the time), return male customer with random
    # (>=30 and <90 ) tolerance:
    else { # male 
        my $t = int rand(59) + 30; # p $t;
        my $o = MaleCustomer->new( tolerance => $t );
        return $o;
    }    
}

sub serveOneCustomer {
    my ($self, $probability) = @_;
    
    # remove 1st element & add zero to end, returns Customer object, or undef:
    my $element = $self->removeFromQueue(); # p $element;
    $self->customers->{ref $element}->{served}++ if ref $element;
    
    # run expire function on queue, if $element is an object exipre getRequiredTime 
    # (if false all queue values must be zero), otherwise 10 minutes; sets 
    # duration():
    $self->expire($element);
    
    my $duration = $self->duration; # p $duration;
    my $que      = $self->getQueue; # p $que; # array of Customer objects

    for ( 1 .. $duration ) { # p $_;
        my $rand = rand(1); # p $rand;
        # add new customer if rand value LESS THAN p value:
        if ( $rand < $probability ) {
            my $new_customer = arrivingCustomer(); # p $new_customer;
            $self->addToQueue($new_customer);
        }
    } # p $self->getQueue;
    # return revenue value of customer served (if any):
    return $element
        ? $element->getPayment
        : 0;
}

sub runDay {
    my ($self, $number_of_minutes, $probability) = @_;
    
    # set duration of day in minutes:
    $self->time_left($number_of_minutes); 
    
    while ( $self->time_left > 0 ) { # p $self->time_left;
        my $revenue = $self->serveOneCustomer($probability); # p $revenue;
        $self->addRevenue($revenue); # p $self->cumulative_revenue;
    }
}

sub averageRevenue {
    my ($self, $number_of_minutes, $number_of_days, $probability) = @_;
    
    for ( 1 .. $number_of_days ) { # p $_;
        # create new instance of self:
        my $o = AdvancedShop->new( seats => $self->seats );
        $o->runDay(480, $probability);
        my $revenue_for_day = $o->getTotalRevenue; # p $revenue_for_day;
        # add daily revenue to outer (parent) object, not local instance:
        $self->addRevenue($revenue_for_day);
        my $customers = $o->customers;
        # add customer counters to outer object:
        while ( my ($type, $d) = each %$customers ) {
            $self->customers->{$type}->{$_} += $d->{$_} for keys %$d;
        }
    }
    return sprintf '%.02f', $self->getTotalRevenue / $number_of_days;
}

1;