22

Imagine we have a wizard that knows a few spells. Each spell has 3 attributes: Damage, cooldown time, and a cast time.

Cooldown time: the amount of time (t) it takes before being able to cast that spell again. A spell goes on "cooldown" the moment it begins casting.

Cast time: the amount of time (t) it takes to use a spell. While the wizard is casting something another spell cannot be cast and it cannot be canceled.

The question is: How would you maximize damage given different sets of spells?

It is easy to calculate the highest damage per cast time. But what about in situations where it is better to wait then to get "stuck" casting a low damage spell when a much higher one is available:

For example,

1) 100 damage, 1 second cast time, 10 second cool down.

2) 10 damage, 4 second cast time, 0 second cool down.

So, in this case you would cast #1, #2, wait. Cast #1

aaronfarr
  • 321
  • 1
    Maybe the game dev section might be more suitable? – J. M. ain't a mathematician Nov 15 '10 at 15:30
  • 1
    Thanks, but it was suggested that I try here first since if you remove the spell casting theme it is a game theory question – aaronfarr Nov 15 '10 at 15:42
  • 17
    @aaronfarr: it's not really a game theory problem, it's an optimization problem. In fact it is really a type of scheduling problem. I don't understand the votes to close since it is far from obvious to me how to maximize damage / time in general. Consider this a vote against closing. – Qiaochu Yuan Nov 15 '10 at 15:59
  • I second that.... – user02138 Nov 15 '10 at 16:06
  • @Qiaochu: Thanks, indeed it would be considered a type of scheduling / optimization problem. Hopefully, someone can update the tags as I do not have sufficient privileges. – aaronfarr Nov 15 '10 at 16:15
  • 2
    @aaronfarr: do you want to put any constraints on the numbers here? For example, are they all positive integers? If certain ratios are irrational I think you can arrange it so that an optimal casting schedule is aperiodic. – Qiaochu Yuan Nov 15 '10 at 16:49
  • 2
    We would just be dealing with integers >= 0 for all three variables, thanks for pointing that out – aaronfarr Nov 15 '10 at 18:34
  • With your parameters, you can get two spell 2's in between spell 1's, not just one. – Ross Millikan Nov 16 '10 at 01:20
  • You should link to the dupe at http://gamedev.stackexchange.com/questions/5593/spell-casting-how-to-optimize-damage-per-second – Sparr Nov 16 '10 at 05:43

5 Answers5

19

It is worth noting that, in extreme special cases, this problem degenerates to the Knapsack Problem, which is NP-complete to solve exactly. To see this, imagine that there is one spell, henceforth called the Megaspell, which does a very, very large amount of damage, has zero casting time, and has some positive cooldown $n$. If all the other spells do much less damage than the Megaspell, it will clearly be optimal to cast the Megaspell every $n$ seconds and then optimize the cooldown time with the weaker spells.

Now, assume all the other spells also have cooldown $n$. If one optimizes a given $n$-second downtime with these spells, then the same spell-sequence will also be possible in the next $n$-second downtime, and so we can assume the solution is $n$-periodic.

The problem then reduces to optimizing the $n$ available seconds with the lesser spells, each of which may only be cast once. If one replaces casting time with 'weight' and damage with 'value', one recovers the Knapsack Problem for maximum weight n.

Greg Muller
  • 5,156
  • @Greg: Nice idea. Thought about the same thing earlier today. It seems like the spell with the highest damage per cast time basically becomes the "backbone" spell. You want to cast this as often as possible. So it simply becomes a matter of fitting in as many other "filler" spells in between. However, this does not address the scenario where some "gaps" in between backbone spell casts will result in higher damage per second. Very simple example: 1) 100 damage, 2 second cast, 10 second cooldown. 2) 50 damage, 9 second cast, 0 sec cooldown. – aaronfarr Nov 16 '10 at 04:39
  • 1
    Sure, and I wasn't claiming that this kind of solution is general, or even all that common. My aim was to show that the Spellcasting problem actually contained the well-known Knapsack problem, which is already known to be 'hard' in several precise senses. However, there is already an extensive literature on the Knapsack problem, and I wouldn't be surprised if some of the approaches can be extended to the Spellcasting problem. Unfortunately, I don't really know it at all. – Greg Muller Nov 16 '10 at 06:05
  • Very nice. I was trying to reduce to the knapsack problem earlier but couldn't manage it. – Qiaochu Yuan Nov 16 '10 at 11:34
  • Yes, nice. I was also trying to reduce to knapsack but couldn't get it. – Mike Spivey Nov 16 '10 at 16:39
  • Excuse my ignorance but I am not sure how to apply your parameters to the knapsack solution in order to get a spell sequence. Could you or someone else kindly elaborate? – aaronfarr Nov 18 '10 at 00:10
  • 1
    The knapsack solution only applies to spell sets that satisfy two properties: (A) all cooldown times are the same, and (B) one spell has MUCH larger damage than all the others. Finding an exact threshold needed for a spell to be a Megaspell is a little fiddly (that is, for it to be best to cast it as often as possible). – Greg Muller Nov 18 '10 at 06:20
  • 2
    In such a situation, it is easy to show that at least one optimal spell sequence is periodic with period n. Therefore, we can focus on the first n seconds. Then, we notice that it doesn't matter what order we cast the spells in for those n seconds; we can cast them in any order without changing the damage or the total time. Therefore, it becomes a problem of which subset of spells to cast. Each has a 'cost' (casting time) and a 'benefit' (damage), and we have finite cost to spend (n). Maximizing this is the knapsack problem. – Greg Muller Nov 18 '10 at 06:25
  • @Greg Muller: Thank you for your insights. Very interesting stuff. I wonder how it might be possible to solve the problem in cases without the two restrictions you stated above. I was able to solve the problem with a computer algorithm but have been unsuccessful trying to solve it mathematically. – aaronfarr Nov 22 '10 at 22:09
  • The knapsack problem has a straightforward weakly polynomial dynamic programming solution (i.e., polynomial in the actual numbers involved), so it is always worth checking if the numbers (damage, times) are small or large. – ShreevatsaR Jun 30 '11 at 08:23
5

I don't have an answer, but I just want to point out that the greedy algorithm fails. That is, if we choose to cast, at any point, the available spell which maximizes $\frac{ \text{damage} }{ \text{cast time} }$, we don't actually maximize our damage output because of cooldown. Consider the pair of spells $(300, 5, 12)$ and $(50, 3, 0)$ (where the three numbers are damage, cast time in second times, and cooldown time in seconds): the greedy algorithm suggests the casting schedule

  • Cast spell 1 ($300$ damage, $5$ seconds),
  • Cast spell 2 ($50$ damage, $3$ seconds),
  • Cast spell 2 ($50$ damage, $3$ seconds),
  • Cast spell 2 ($50$ damage, $3$ seconds),
  • Repeat

which gives about $32.1$ damage per second. However, the casting schedule

  • Cast spell 1 ($300$ damage, $5$ seconds),
  • Cast spell 2 ($50$ damage, $3$ seconds),
  • Cast spell 2 ($50$ damage, $3$ seconds),
  • Wait $1$ second,
  • Repeat

gives about $33.3$ damage per second. So a correct algorithm must take into account which spells are about to finish cooling down.

Qiaochu Yuan
  • 419,620
1

How about this algorithm? At each moment your decision is which spell to cast next. Having decided, if the cool down time for that one has expired, then go ahead and cast it. If not, wait until it has expired, then cast it. To make the decision, rank the spells in damage/time where time is from now to completion (including any cool down time left). Then see if you can fit in one of the other spell without delaying the best one. Taking Qiochu's example, at the start spell 1 is 300/5=60 dam/sec and spell 2 is 50/3=16.7dam/sec. Having cast spell 1, it is now 300/17=17.6 dam/sec, still better than 2. But we can get 2 done twice before 1 is available, so we should do that. The question would be whether you can change the parameters so that you should use the weaker spell even though it will delay the strong one by just a little bit.

Ross Millikan
  • 374,822
  • 1
    I considered this, but I don't think it's that simple. You can't just account for the cooldown time left; I think you also have to account for future cooldowns. I don't have an explicit counterexample, though. – Qiaochu Yuan Nov 16 '10 at 01:15
0

Let's rephrase to: what's the maximal damage that I can cause in, say, 100 seconds? The constraints then become:

  1. Can't spend more than 100 seconds casting
  2. Can't cast a single spell more than $\frac{100}{cast\ time + cool\ time}$ times

Let $n_i$ be the number of times I cast spell i. I want to maximize $\sum_i n_i damage_i$ subject to $\sum_i n_i cast_i < 100$ and each $n_i(cast_i + cool_i) < 100$.

This can be rewritten to a linear programming problem as follows:

  1. The first constraint is $[cast_0 \dots cast_m] [n_0 \dots n_m]^T<100$
  2. The other constraints are $[0 \dots (cast_i + cool_i) \dots 0] [n_0 \dots n_m]^T < 100$

EDIT: My assumptions are

  1. You cannot cast two spells at the same time. Constraint #1 ensures that this is true.
  2. You can only cast a spell once every $cast + cool$ period. Constraint #2 ensures this is true. (Note that this does not say you cannot cast a different spell in this period; only that you cannot cast the same spell in this period.)
Xodarap
  • 6,115
  • This is an interesting approach, but I'm not sure if ignoring the ordering of the spells like you do won't cause problems. In any case, you can't ignore (1) because otherwise the optimal solution will always be $n_i = 100/(\text{cast}_i + \text{cool}_i)$. Also, you should mention that this is an integer linear programming problem, which is harder. –  Nov 16 '10 at 01:07
  • Actually, since cooldown begins the moment you begin casting, (2) should just be $100/\text{cool}_i$. –  Nov 16 '10 at 01:09
  • The second condition is wrong. The point of the problem is that we can cast some spells while waiting for others to cooldown, and this formulation doesn't take that account. – Qiaochu Yuan Nov 16 '10 at 01:16
  • @Rahul: you are right, #1 cannot be ignored. @Qiaochu: I examined this, and I do not understand your objection. I tried to make my reasoning more clear - could you let me know where I went wrong? – Xodarap Nov 16 '10 at 01:39
  • @Xodarap: ah, right. In any case, these conditions are only necessary, not sufficient, so I don't see how this helps. – Qiaochu Yuan Nov 16 '10 at 11:35
  • @Qiaochu: I think it is sufficient as well: this is essentially saying that you must not break these constraints on average. E.g. you must only cast one spell at a time on average. The only way you could cast multiple spells at the same time is if you had empty time left at the end, and then of course you could change your actions to cast only one at a time. – Xodarap Nov 16 '10 at 14:00
  • @Xodarap: No, it's not. First of all, there are two small mistakes: the second constraint should be n_i cool_i \le 100 because cooldown starts when the spell is cast, not after (and the first constraint should also be a weak and not strict inequality). Second, in this formulation you can ignore the cooldown of the last spell you cast. But the main problem, as I say, is that this is not sufficient. Consider the case of 2 spells, each of which does 100 damage with a cast time of 1 second and a cooldown of 100 seconds. – Qiaochu Yuan Nov 16 '10 at 15:27
  • @Qiaochu: I'm really trying here, but I don't get it. If cool = 100, in order for $cool * n \leq 100$, you can only cast it once ($n \leq 1$). So the constraint would say you can only cast it once, right? – Xodarap Nov 16 '10 at 16:30
  • @Xodarap: sorry, that didn't work. This one should: consider 2 spells, each of which does 100 damage with a cast time of 20 seconds and cooldowns of 50 and 33 seconds. Your conditions suggest it should be possible to cast spell 1 twice and spell 2 three times, but it's not. You'll find it's only possible to cast four spells. – Qiaochu Yuan Nov 16 '10 at 16:54
  • @Qiaochu: cast+cool=70 and 53 respectively. So constraints = [[20 20]; [70 0]; [0 53]]; constraints * [2 3]' = [100 140 159]' which obviously does not satisfy the constraints. soo...? (I agree that this method requires that cooldown be completed within the 100 second period which may not be required in fact, but it sounds like you're making a different point.) – Xodarap Nov 16 '10 at 17:01
  • @Xodarap: you seem to have misread the original question. Cooldown starts at the beginning of casting, not the end. (In any case, if you are interpreting cooldown as starting at the end of casting, just take the cooldowns to be 30 and 13 seconds.) My point stands regardless of whether the cooldown of the last few spells is completed or not. You can either cast spell 1, then spell 2, after which you will have to wait 10 seconds to cast another spell, or you can cast spell 2, then spell1, then spell2, after which you will again have to wait 10 seconds to cast another spell. – Qiaochu Yuan Nov 16 '10 at 17:03
  • @Xodarap: (cont) and this extra 10 second gap ensures that you cannot cast five spells within 100 seconds, again whether or not you worry about the cooldown from the last few spells completing. So, as I have been saying, these conditions are necessary but not sufficient. – Qiaochu Yuan Nov 16 '10 at 17:05
0

I was able to solve the problem with a computer algorithm but am still not certain how it can be done mathematically. It was pointed out by @Greg Muller that the knapsack problem is applicable but I just don't have the mathematical prowess to apply it. If someone could show how that can be done please do.

Will share my logic here, hopefully it is useful to someone out there.

The first step is to determine the spell with the most damage per cast time.

This spell becomes the "baseline" spell since it will guarantee the highest damage per second. Meaning, you should always cast this spell if the following 2 conditions are met: 1) The baseline spell is available (not on cooldown). 2) You are not currently casting a spell.

So it then becomes a matter of filling in other spells while the baseline spell is on cooldown. Between (cast time) and (cooldown - cast time). However, some overlapping can occur (rule 2 above is false).

It then becomes a matter of recursing through all non-baseline spells to find all sequences of spells which do not violate the 2 rules.

For spells which DO overlap you must penalize them for potential damage the baseline spell could have done (up to its maximum damage).

Take for example, 2 spells

1: 300 damage, 3s cast time, 10s cooldown

2: 290 damage, 3s cast time, 3s cooldown

The most damage comes from the sequence 1 - 2 - 2 - 2. Which causes an overlap of 2 seconds into a potential #1 cast. However, this is still beneficial since if you dont cast the third spell (ie. 1 - 2 - 2) you will do 880 damage with 1 second to spare. If you cast the extra #2 spell you will do 1170 - 2 second of #1 which is 200. So 970 damage is your relative damage.

This algorithm is significantly faster than other algorithms which look for sequences that match a target goal: ie. time limit or damage.

aaronfarr
  • 321
  • 1
    I do not agree that the spell with the most damage per cast time should always be a baseline spell. From this post, though, I can't really tell what your goal is. Do you actually want the optimum damage per time or do you just want a reasonably good amount of damage? – Qiaochu Yuan Nov 19 '10 at 09:47
  • There is no time constraint. So you are trying to maximize damage per second. In what situation would the highest damage per cast time not be optimal? – aaronfarr Nov 19 '10 at 15:46