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;