As Dale M notes, you can't assign directly to sequence elements in AnyDice. What you can do, in general, is rebuild the whole sequence like this:
function: set element I:n in SEQ:s to N:n {
NEW: {}
loop J over {1 .. #SEQ} {
if I = J { NEW: {NEW, N} }
else { NEW: {NEW, J@SEQ} }
}
result: NEW
}
In your case, though, that would be overkill. It's much easier to just unpack your mutable sequence elements into normal numeric variables (and, if it were necessary, later re-pack them into sequences):
function: combat between ATTACKER:s and DEFENDER:s {
S_ATT: 1@ATTACKER
E_ATT: 2@ATTACKER
I_ATT: 3@ATTACKER
S_DEF: 1@DEFENDER
E_DEF: 2@DEFENDER
I_DEF: 3@DEFENDER
loop N over {1..3} {
if S_ATT > 0 & S_DEF > 0 {
if I_ATT < I_DEF {
S_DEF: S_DEF - [S_ATT E_ATT battleroll]
if S_DEF > 0 { S_ATT: S_ATT - [S_DEF E_DEF battleroll] }
} else {
S_ATT: S_ATT - [S_DEF E_DEF battleroll]
if S_ATT > 0 { S_DEF: S_DEF - [S_ATT E_ATT battleroll] }
}
}
}
result: (S_ATT > 0) - (S_DEF > 0)
}
(Actually, in this case, it would be enough to only unpack the S_ATT and S_DEF variables, since those are the only ones that will change. But, IMO, it looks more readable this way.)
Of course, you could also just write your function to take multiple numeric arguments instead of fixed-length sequences, and let the caller handle the unpacking if they need to.
Ps. A few other remarks regarding your code:
First of all, try to format and indent your code cleanly. For "brace languages" like the AnyDice syntax, consistently indenting the content of each brace-delimited block (except possibly for one-liners) makes the code much easier to read later.
Also, your battle roll function may be simplified down to just:
function: STRENGTH:n EFFECTIVENESS:n battleroll {
VALUE: 0
loop N over {1 .. STRENGTH} {
VALUE: VALUE + (1d6 <= EFFECTIVENESS)
}
result: VALUE
}
In fact, it could be simplified further to just:
function: STRENGTH:n EFFECTIVENESS:n battleroll {
result: [count {1 .. EFFECTIVENESS} in STRENGTH d 6]
}
but this becomes inefficient for high STRENGTH values, because AnyDice isn't smart enough to realize that it doesn't need to enumerate every possible outcome of rolling the dice. Probably the most concise and efficient way to implement it, however, is to use custom dice:
function: STRENGTH:n EFFECTIVENESS:n battleroll {
result: STRENGTH d {1:EFFECTIVENESS, 0:(6-EFFECTIVENESS)}
}
Finally, if you actually try to run the fixed code I suggested above, you'll find that it compiles fine, but then fails at runtime with a message saying:
calculation error
Boolean values can only be numbers, but you provided "d{?}".
Depending on what you want, you might need to create a function.
This is because, after the first battle roll, your S_ATT / S_DEF variables are no longer plain numbers, but rather random values that depend on the die rolls inside the battleroll function. AnyDice will not allow such random values to be used in if statements.
There is, however, a workaround, and the is to use a recursive function call, e.g. like this:
function: combat between S_ATT:n E_ATT:n and S_DEF:n E_DEF:n for LIMIT:n steps {
if S_ATT <= 0 { result: -1 }
if S_DEF <= 0 { result: +1 }
if LIMIT <= 0 { result: 0 }
S_NEW: S_DEF - ( S_ATT d {1:E_ATT, 0:(6-E_ATT)} )
\ reverse the roles for the next half-turn, negate result: \
result: -[combat between S_NEW E_DEF and S_ATT E_ATT for LIMIT-1 steps]
}
This works, because if S_NEW is a random value, AnyDice will automatically call the function for each possible value, and average the results according to their probability. (Warning: Resist the temptation to reuse the function argument S_DEF instead of S_NEW as the variable storing new strength value. It seems to work, but apparently can trigger some weird / buggy behavior in AnyDice.)
Note that, if you use recursion like this, you will probably want to increase AnyDice's maximum recursion depth, like this:
set "maximum function depth" to 99
As long as you're controlling your recursion depth yourself (like I did with the LIMIT variable above), just setting the built-in limit to some high enough value is enough.