Problem:
Model the adventure results for a player. Adventures consist of a
number of sub-adventures (RUNS), ranging from 1 to 12 (integral). Each
sub-adventure returns the multiple values CLASS GOLD+ XP+ HP- where
CLASS is an integer classification of the result ranging from 0 to 5.
The end result consists of
* the number of successful runs
* whether all runs were of class 0 (this means total failure)
* the adjusted sum of GOLD+ and XP+
* the plain sum of HP-
* the mode of the classes
If the sum total of HP- is larger than the player's current hit points
(PLAYER-HP), don't do any more runs and return the current results.
I tried to solve this with SERIES, but I'm a beginner at it and
haven't succeeded even remotely, because the problem is a mix of
termination conditions, reduction and collection.
Here's a LOOP solution that does what I need (slightly simplified):
(loop until (>= hp-- PLAYER-HP)
for nsubs from 1 to RUNS
for (class gold+ xp+ hp-) = (multiple-value-list (sub-
adventure-results))
summing gold+ into gold++
summing xp+ into xp++
summing hp- into hp--
collecting class into classes
finally (return (values
(1- nsubs)
(not (every (curry #'eql 0) classes))
(time-adjusted-bonus gold++)
(time-adjusted-bonus xp++)
hp--
(car (random-distributions::mode
classes)))))
Any general suggestions are also welcome.
I'm not very interested in recursive solutions, though.
I hope I managed to state the problem clearly.
Leslie
In article
<····································@e39g2000hsf.googlegroups.com>,
"Leslie P. Polzer" <·············@gmx.net> wrote:
> Problem:
>
> Model the adventure results for a player. Adventures consist of a
> number of sub-adventures (RUNS), ranging from 1 to 12 (integral). Each
> sub-adventure returns the multiple values CLASS GOLD+ XP+ HP- where
> CLASS is an integer classification of the result ranging from 0 to 5.
> The end result consists of
>
> * the number of successful runs
> * whether all runs were of class 0 (this means total failure)
> * the adjusted sum of GOLD+ and XP+
> * the plain sum of HP-
> * the mode of the classes
>
> If the sum total of HP- is larger than the player's current hit points
> (PLAYER-HP), don't do any more runs and return the current results.
>
> I tried to solve this with SERIES, but I'm a beginner at it and
> haven't succeeded even remotely, because the problem is a mix of
> termination conditions, reduction and collection.
>
> Here's a LOOP solution that does what I need (slightly simplified):
>
> (loop until (>= hp-- PLAYER-HP)
> for nsubs from 1 to RUNS
> for (class gold+ xp+ hp-) = (multiple-value-list (sub-
> adventure-results))
>
> summing gold+ into gold++
> summing xp+ into xp++
> summing hp- into hp--
> collecting class into classes
>
> finally (return (values
> (1- nsubs)
> (not (every (curry #'eql 0) classes))
> (time-adjusted-bonus gold++)
> (time-adjusted-bonus xp++)
> hp--
> (car (random-distributions::mode
> classes)))))
>
> Any general suggestions are also welcome.
> I'm not very interested in recursive solutions, though.
>
> I hope I managed to state the problem clearly.
>
> Leslie
a few remarks:
* I never really wanted to use Series that much, even though I tried it a few times
* you can do for nsubs below RUNS and get rid of the 1-
* you might want to use NOTEVERY instead of (NOT (EVERY ...))
you can get rid of (curry #'eql 0) by just using #'zerop
(notevery #'zerop classes)
CL-USER 3 > (notevery #'zerop '(0 0 0 0 1 0 0 0))
T
CL-USER 4 > (notevery #'zerop '(0 0 0 0 0 0 0 0))
NIL
* your usage of LOOP has an error: you can't use UNTIL at that place
as the first clause:
LOOP has the syntax:
loop [name-clause] {variable-clause}* {main-clause}* => result*
UNTIL is not a variable-clause, but a main-clause.
You have to use UNTIL after all FOR, AS and WITH clauses.
Many Lisp implementations will run similar code, some not.
It is not ANSI CL anyway, so it is best to avoid it
for portable code.
If LOOP is too ugly or lacks a feature, the ITERATE macro
is a good alternative. I found SERIES to be more trouble
than it is worth (IMHO) for more than trivial tasks.
--
http://lispm.dyndns.org/
>>>>> "Leslie" == Leslie P Polzer <·············@gmx.net> writes:
Leslie> Model the adventure results for a player. Adventures consist of a
Leslie> number of sub-adventures (RUNS), ranging from 1 to 12 (integral). Each
Leslie> sub-adventure returns the multiple values CLASS GOLD+ XP+ HP- where
Leslie> CLASS is an integer classification of the result ranging from 0 to 5.
Leslie> The end result consists of
Leslie> * the number of successful runs
Leslie> * whether all runs were of class 0 (this means total failure)
Leslie> * the adjusted sum of GOLD+ and XP+
Leslie> * the plain sum of HP-
Leslie> * the mode of the classes
Leslie> If the sum total of HP- is larger than the player's current hit points
Leslie> (PLAYER-HP), don't do any more runs and return the current results.
Leslie> I tried to solve this with SERIES, but I'm a beginner at it and
Leslie> haven't succeeded even remotely, because the problem is a mix of
Leslie> termination conditions, reduction and collection.
Leslie> Here's a LOOP solution that does what I need (slightly simplified):
Why do you need to use series?
Leslie> (loop until (>= hp-- PLAYER-HP)
Leslie> for nsubs from 1 to RUNS
Leslie> for (class gold+ xp+ hp-) = (multiple-value-list (sub-
Leslie> adventure-results))
Leslie> summing gold+ into gold++
Leslie> summing xp+ into xp++
Leslie> summing hp- into hp--
Leslie> collecting class into classes
Leslie> finally (return (values
Leslie> (1- nsubs)
Leslie> (not (every (curry #'eql 0) classes))
Leslie> (time-adjusted-bonus gold++)
Leslie> (time-adjusted-bonus xp++)
Leslie> hp--
Leslie> (car (random-distributions::mode
Leslie> classes)))))
I think if you use COLLECT-FN you can get what you want, mostly. The
series-input could be (scan-range :below RUNS), and the collecting
function would call SUB-ADVENTURE-RESULTS repeatedly and and
accumulate class, gold+, xp+, and hp- appropriately.
I guess COLLECT-FN would return 4 values, and you would then massage
those 4 values to return the 6 values that the loop would return.
Ray
> Why do you need to use series?
I don't need to, of course. Just curious :)
I thought there might be a neat solution that builds a series
of SUB-ADVENTURE-RESULTS and then works on them...
Naturally your proposed way of using series doesn't work that
way...
>>>>> "Leslie" == Leslie P Polzer <·············@gmx.net> writes:
>> Why do you need to use series?
Leslie> I don't need to, of course. Just curious :)
Leslie> I thought there might be a neat solution that builds a series
Leslie> of SUB-ADVENTURE-RESULTS and then works on them...
Leslie> Naturally your proposed way of using series doesn't work that
Leslie> way...
How about this then:
(scan-fn (values t t t t)
#'(lambda ()
(sub-adventure-results))
#'(lambda (t1 t2 t3 t4)
(declare (ignore t1 t2 t3 t4))
(sub-adventure-results)))
This should produce 4 separate series, one for each of the values
returned by sub-adventure-results.
Then you should be able combine the 4 series together in the way that you want.
Ray
> How about this then:
>
> (scan-fn (values t t t t)
> #'(lambda ()
> (sub-adventure-results))
> #'(lambda (t1 t2 t3 t4)
> (declare (ignore t1 t2 t3 t4))
> (sub-adventure-results)))
>
> This should produce 4 separate series, one for each of the values
> returned by sub-adventure-results.
>
> Then you should be able combine the 4 series together in the way that you want.
Heh, yeah, I got this far.
But the central question is what happens then. It seems I need some
combination
of UNTIL-IF, COLLECT and whatever, but I couldn't think of any clean
solution.
>>>>> "Leslie" == Leslie P Polzer <·············@gmx.net> writes:
>> How about this then:
>>
>> (scan-fn (values t t t t)
>> #'(lambda ()
>> (sub-adventure-results))
>> #'(lambda (t1 t2 t3 t4)
>> (declare (ignore t1 t2 t3 t4))
>> (sub-adventure-results)))
>>
>> This should produce 4 separate series, one for each of the values
>> returned by sub-adventure-results.
>>
>> Then you should be able combine the 4 series together in the way that you want.
Leslie> Heh, yeah, I got this far.
Leslie> But the central question is what happens then. It seems I
Leslie> need some combination of UNTIL-IF, COLLECT and whatever,
Leslie> but I couldn't think of any clean solution.
I don't use series too often anymore, so it always takes a while to
figure this out.
First, your loop has a running sum of hp- in hp-- which you compare
against player-hp. So you need a cumsum function:
(defun cumsum (s)
(declare (optimizable-series-function 1))
(collecting-fn t
#'(lambda () 0)
#'(lambda (sum x)
(+ sum x))
s))
Then something like this might work:
(defun run-it ()
(multiple-value-bind (class gold+ xp+ hp-)
(scan-fn '(values t t t t)
#'(lambda ()
(sub-adventure-results))
#'(lambda (t1 t2 t3 t4)
(declare (ignore t1 t2 t3 t4))
(sub-adventure-results)))
(multiple-value-bind (c g xp hp nsubs)
(cotruncate class gold+ xp+ (until-if #'(lambda (x)
(>= x player-hp))
(cumsum hp-))
(scan-range :from 1 :upto runs))
(values (collect-last nsubs)
(not (every (curry #'eql 0) (collect c)))
(time-adjusted-bonus (collect-sum g))
(time-adjusted-bonus (collect-sum xp))
(collect-last hp)
(car (random-distributions::mode (collect c)))))))
The UNTIL-IF is to stop the iterations if hp-- >= player-hp. The
SCAN-RANGE is for the max number of runs. The call to COTRUNCATE is
it make all the series have the same length.
Or maybe the termination cases can be placed in SCAN-FN itself. I
think that would make it even harder to understand.
There are probably better ways to do this, but that's all I could
think of in a few minutes.
Ray