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;