There are no arrays in cmd/batch
. The one and only variable type is STRING (although Lists and Arrays can be simulated to a certain degree). I therefore would change from arrays (independent variables for each element) to lists (one variable containing all elements) and put all "array/list stuff handling" into a subroutine, making it easy in the main code to get a certain element:
@echo off
setlocal enabledelayedexpansion
REM define readable names for indexes (for more readable code later)
set "MaxHealth=2"
set "Damage=1"
set "Dodge=3"
set "Name=4"
set "Goblin=1"
set "Zombie=2"
set "Skeleton=3"
set "Orc=4"
set "Acrobat=5"
REM define a list of properties for each creature
set "_Goblin=5,20,7,little Goblin"
set "_Zombie=7,15,3,angry Zombie"
set "_Skeleton=3,11,10,bony Skeleton"
set "_Orc=7,25,0,furious Orc"
set "_Acrobat=4,15,80,fast Acrobat"
REM define a list of all creatures
set "PossibleEnemies=Goblin,Zombie,Skeleton,Orc,Acrobat"
set /a Enemy1=%random% %%5
set /a Enemy2=%random% %%5
REM get properties
call :GetProperty %Enemy1% Name Enemy1
call :GetProperty %Enemy2% Name Enemy2
call :GetProperty %Enemy1% MaxHealth Enemy1
call :GetProperty %Enemy2% MaxHealth Enemy2
call :GetProperty %Enemy1% Damage Enemy1
call :GetProperty %Enemy2% Damage Enemy2
call :GetProperty %Enemy1% Dodge Enemy1
call :GetProperty %Enemy2% Dodge Enemy2
echo You fight against:
set Enemy
goto :eof
:GetProperty Type Property VarName ; resulting variable = [VarName].[Property]
echo DBG: EnemyType=%1
echo DBG: Property=%2
for /f "tokens=%1 delims=," %%a in ("%PossibleEnemies%") do set "Type=%%a"
echo DBG: Type=%Type%
echo DBG: ReturnVar=%3.%2
set "opt=tokens=!%2! delims=," & REM not possible to use delayed expansion in for /f "[options]"
for /f "%opt%" %%a in ("!_%Type%!") do set "%3.%2=%%a"
echo DBG: Returnval=!%3.%2!
echo DBG: -----
goto :of
Example output (without the DBG
lines):
You fight against:
Enemy1=3
Enemy1.Damage=3
Enemy1.Dodge=10
Enemy1.MaxHealth=11
Enemy1.Name=bony Skeleton
Enemy2=1
Enemy2.Damage=5
Enemy2.Dodge=7
Enemy2.MaxHealth=20
Enemy2.Name=little Goblin
If you want, you can put the REM get properties
(all those call ...
lines) into another subroutine for even more readable main code.
To answer your actual question: you need one layer of expansion per depth-layer. You have a variable containing a variable containing a variable, so you need three layers of parsing (the original layer plus two more. One more layer is usually achieved by delayed expansion; three layers are a bit more complicated: use call
for an additional layer. The innermost variable is used "as usual" %c%
for the next layer you have to double each %
, as one %
gets consumed by the additional layer of parsing (the second call
), the same is true for the outermost (third) layer (the first call
), resulting in four %
, because each layer of parsing halves them:
@echo off
setlocal
set a[2]=1000
set b[1]=2
set c=1
call call set d=%%%%a[%%b[%c%]%%]%%%%
echo %d%
result: 1000
Some words on efficiency:
As @Darin already mentioned, call
is slow (because it creates another instance of cmd
. Of course call call
is even slower. Take a look at the times the three loops take (an empty loop, a "call-call" loop and a loop using another loop to force another layer of parsing):
@echo off
setlocal
set a[2]=1000
set b[1]=2
set c=1
echo start empty loop %time%
for /l %%i in (1,1,10000) do (
REM
)
echo start call-call %time%
for /l %%i in (1,1,10000) do (
call call set d=%%%%a[%%b[%c%]%%]%%%%
)
echo (proof it worked: %d%)
echo start speed-optimized %time%
setlocal enabledelayedexpansion
for /l %%i in (1,1,10000) do (
for %%j in (!b[%c%]!) do set "x=!a[%%j]!"
)
echo (proof it worked: %x%)
endlocal
echo End %time%
Output:
start empty loop 16:20:49,05
start call-call 16:20:49,06
(proof it worked: 1000)
start speed-optimized 16:21:02,51
(proof it worked: 1000)
End 16:21:03,06
(~0.1 seconds / ~12 seconds / ~ 0,6 seconds) - YMMV depending on hardware and processor load. Ok, it takes 10000 iterations to get to those numbers, but nevertheless - its a factor of ~20)