Defining the 'end' variable in a FOR command

Chris Kelly

Active member
Hi

I'd like to be able to wipe all of the RAM storage variables as part of my code

Code:
for bptr = 32 to 128
                @bptr = 0
                next
But let's say that in the rest of my code, there is no data stored after address 64. Then am I wasting 'time' by asking the program to keep incrementing the address pointer all the way up to 128? I only ask because in PE6 this part of the code takes a while to process when I run the program.

Instead of just defining 128 as the 'end' value in my FOR loop, is there instead a way of having this number as a variable? So, if my variable is set to 64, then my code can just say

for bptr = 32 to VARIABLE

Or is this a non-issue, since the Picaxe will run through this FOR loop incredibly fast?

Thanks

Chris
 

lbenson

Senior Member
I'd like to be able to wipe all of the RAM storage variables as part of my code
Code:
for bptr = 32 to 128
I assume this is an 08M2, which has 128 bytes of ram storage. But the addresses run from 0-127. If you get to bptr=128, it will wrap to 0, and you will zero the contents of b0--not what you intended (you can see this in the simulator).

Chris Kelly said:
Instead of just defining 128 as the 'end' value in my FOR loop, is there instead a way of having this number as a variable? So, if my variable is set to 64, then my code can just say

for bptr = 32 to VARIABLE
Yes, but note that if VARIABLE is 64 you will zero the 65th byte.
Chris Kelly said:
Or is this a non-issue, since the Picaxe will run through this FOR loop incredibly fast?
Yes, a non-issue with regard to speed in almost all cases.

Is there a reason you want to zero these bytes? Upon program start-up, they will be zero, so it would only make sense if you had already filled them with some values since start-up and had a specific reason for wanting to zero them again.
 

Chris Kelly

Active member
Hi

It's needed to clear the stored data at some of the addresses. (On a 14M2)

If speed isn't an issue I'll just leave it at 128.

Thanks for the fast reply!
 

Aries

New Member
The answer to your original question
is there instead a way of having this number as a variable?
is YES, you can use a variable (e.g. b0).

Whether you use PE5 or PE6 will make no difference to the speed on the chip, but it might be different in the simulator.
The execution speed on the chip is such that you will barely notice.
If you want to
wipe all of the RAM storage variables
then you need to go up to 255 on a 14M2
 

hippy

Technical Support
Staff member
There should be no problem using "For bPtr = 32 to VARIABLE". So long as the variable used is not itself cleared, in the example case is b0-b31 or w0-w15.

The best way to clear RAM from a location to the very top would be to use -
Code:
bPtr = 32
Do
  @bPtrInc = 0
Loop Until bPtr = 0
This will clear to the top of RAM regardless of which PICAXE is used and how much RAM there is in that chip. After clearing the top of RAM the 'bPtr' is incremented past the top, becomes zero, and that terminates the loop.

However one clears RAM in a PICAXE chip it should be a pretty quick affair, but will take a lot longer if simulating. To avoid that, when simulating using PE6, one can add a "#IfNDef SIMULATING" so the code is only executed when downloaded into an actual PICAXE chip, not during simulation -
Code:
#IfNDef SIMULATING
  bPtr = 32
  Do
    @bPtrInc = 0
  Loop Until bPtr = 0
#EndIf
 

Chris Kelly

Active member
Thanks everyone for your help.

Hippy - thanks for the tip for speeding up PE6 during this process. Should save me alot of hang time!
 

AllyCat

Senior Member
Hi,
If speed isn't an issue I'll just leave it at 128.
It's rather for you to decide if the speed is an issue for your particular application. The FOR : <clear byte> : NEXT loop will take around 2ms for each byte (at 4 MHz) so it will take around one second to clear most of the 512 bytes of RAM in a 14/18+/20 M2 PICaxe. You could of course SETFREQ M32 for typically 125 ms execution time, but that won't speed the simulation. Also, the INC part of hippy's @BPTRINC executes in almost "zero time" , so reduces his loop to perhaps 1.7 ms per byte.

If, for example, you're using the RAM as a variable-length buffer which needs to be cleared before re-use (e.g. to determine where the last received byte was written) then it could be quicker to clear only the bytes that are "known" to be non-zero. But there is no point in testing or "searching" for any non-zero bytes, which would take longer than simply clearing them all regardless.

Something that I've not tried, but if you have a PICaxe with TABLE memory which is not being used, then you could try (for example) TABLECOPY 28 , 512 to clear all the RAM from 28 upwards, very quickly. I can't test in on my 08M2 breadboard, but it simulates extremely quickly on the other M2s , so could be useful for that alone.
Code:
#picaxe 14m2
#terminal 4800
#no_data
poke 500 , 123              ; Any sample byte
peek 500 , b1
tablecopy 28 , 511        ;  Clear all bytes from 28 to 511
peek 500 , b2
sertxd(#b1," --> ",#b2)
Cheers, Alan.
 

Chris Kelly

Active member
Thanks Alan

That actually works extremely quickly compared to the other methods.
(I changed it to " tablecopy 28, 483 " since I read that the second number is the block size and not the end address)

Works great!
 

Chris Kelly

Active member
Hi Alan

You estimated above that the FOR : <clear byte> : NEXT loop will take around 2ms for each byte (at 4 MHz).

This is still early days for me and I've no real grasp on how quickly a Picaxe can execute standard code. Is there something about clearing RAM that takes longer than normal instructions?

My concern is that I'd like my Picaxe to run a 'do … loop' that takes less than 15ms to complete. I've checked and this part of the code is 307 bytes (The declarations at the start of the program make up only 16 bytes of the total 323 bytes).

I'm planning on using a 20M2 which I gather can run at 32MHZ. Is it unrealistic of me to expect to be able to achieve a 15ms loop time? Or is this dependent on a lot of different factors? What is the trade-off if I opt to run a Picaxe at a faster frequency? How can I estimate the time for a section of code to operate?

Sorry for the barrage of questions!

Chris
 

AllyCat

Senior Member
Hi Chris,

PICaxe Basic uses an "interpreter" (generating the "machine code" continuously at "run time") so is relatively slow compared with a (pre-) compiled program. Personally, I prefer to "normalise" most timing discussions to 4 MHz to avoid confusion, so you are basically asking if a program of 300 ish bytes can execute in 8 * 15 = 120 ms.? To which my answer is "perhaps" (but I'm not optimistic). There are some "tricks" to increase execution speeds, but they tend to make the program less "understandable" for novices (or to anybody). ;)

The main "trade-off" in using 32 MHz is that the power drain (current) of the PICaxe (core) may approach 8 times that at 4 MHz, but it's still quite low at around 5 mA. My main concern about designing for 32 MHz is that there's "nowhere to go" (except a 20X2 at 64 MHz) if the code doesn't run quite fast enough. So I suggest a normalised loop target of no more than 100 ms.

Unfortunately there is not much correlation between the number of program bytes and the execution time, although a single instruction line that generates more than the "expected" number of program bytes may run rather slowly (e.g. "internal macros" such as SWAP and BINTOASCII). My "rule of thumb" is that "simple" instructions (e.g. HIGH, INC, var = var, etc.) take around 0.5 ms, "medium" (IF, GOTO, * ) around 1 ms and "slower" instructions (RETURN, SEROUT, etc.) take 2 ms or more. However, nearly all internal calculations are performed on 16 bits by default, so using word variables is NOT any slower than bytes (but the WORD operator/qualifier IS slower).

The only "formal" discussion of execution times (see my link below) is VERY out of date, hardly even mentioning M2s. But I did write a code snippet quite recently which describes how to easily measure the execution times of most PICaxe Basic instructions. You could try posting your program here, but I can only "guess" how quickly it might execute.

Cheers, Alan.
 

Chris Kelly

Active member
Thanks Alan

Why did you calculate 8 * 15ms = 120ms above? Was the 8 related to bits in a byte?

I think I might struggle with the speed in all honesty, as I'm being over-ambitious with my idea.

The end goal is to make use of up to 128 ram addresses to store a sequence. The full 128 addresses correspond to a drum pattern of 16 steps, but each step would comprise of 8 sub-divisions. This would then give more 'fidelity' to the pattern, instead of being confined to the normal (rigid) 16 steps.

For a 16 step pattern on a drum machine to run at say 120bpm, you would need each step to complete in 125ms. My program needs 8 sub-divisions per step i.e - just under 16ms per sub division. And so my do...loop needs to execute in 16ms or less just to get 120bpm tempo.

I'm happy to opt for a 20X2 if this would give me a fighting chance of succeeding. I've already based my code on a 20M2 as I needed the i/o pins. The one missing feature would be the 'tablecopy' command which proved useful in clearing the buffer.

Code:
#picaxe 20m2
#no_data

'==== LOOPER CIRCUIT. Create a sequence of triggers for drum sounds.
'==== Set length of sequence in real time, incrementing the length of
'==== a circular buffer with each clock pulse. Sequence can be from
'==== 1-16 beats in length but fidelity of each beat increases to a
'==== maximum of 8 divisions per beat for less rigid drum patterns.

symbol RecPin        = pinC.0    ' Increments buffer length every clock cycle
symbol ClkPin          = pinC.1    ' Clock Input
symbol DatPin           = pinC.2    ' Data Input
symbol DelPin        = pinC.3    ' Delete Data
symbol OutPin           = B.0       ' Data Output
symbol RstPin        = pinC.4    ' Resets loop when high
symbol DecPin        = pinC.5    ' Manually decrease buffer length
symbol IncPin        = pinC.6    ' Manually increase buffer length
symbol Default16        = pinB.6    ' Set sequence length to 16 steps
symbol TrigRepeat        = pinB.7    ' Switch btwn one-shot and continuous data entry
symbol BufLen           = b4        ' Used to hold length of buffer
symbol BufEnd           = b5        ' Used to hold address of last byte of buffer
symbol MyPtr            = b6        ' Not necessary, see later.
symbol Active        = b7        ' Prevents data output whilst data being deleted
symbol ClkCount        = b8        ' Increments every clock pulse
symbol MaxClkCount    = b9        ' Helps determine when buffer should advance
symbol MaxBufLen        = b10        ' Sets Maximum Buffer length
symbol Fidelity        = b11        ' Setting taken from ADC input at C.7

symbol DatPin1Shot    = bit18    ' To add data from C.2 in one-shot mode
symbol NewLoop        = bit19    ' Enables Reset of buffer for each press of C.0
symbol BufIncCopy       = bit20    ' Used with BufIncMem and BufInc1Shot below
symbol BufIncMem        = bit21     ' Buffer Increase memory
symbol BufInc1Shot      = bit22    ' Detect rising edge of Buffer Increase button
symbol BufDecCopy       = bit23    ' Used with BufDecMem and BufDec1Shot below
symbol BufDecMem        = bit24     ' Buffer Decrease memory
symbol BufDec1Shot      = bit25    ' Detect rising edge of Buffer Decrease button
symbol ClkPinCopy       = bit29    ' Used with ClkPinMem and ClkOneShot below
symbol ClkPinmem        = bit30     ' Clock Pin memory
symbol ClkOneShot       = bit31     ' Used to detect rising edge of clock pulse
symbol DatPinCopy        = bit26    ' Used with DatPinMem and DatOneShot below
symbol DatPinMem        = bit27    ' Data Pin memory
symbol DatOneShot        = bit28    ' Used to detect rising edge of Data Input

symbol BufStart         = 32        ' Buffer Start address
    
'==== MAIN PROGRAM STARTS ====

BufLen = 1                          ' Initial buffer length
BufEnd = BufStart + BufLen - 1      ' Calculate the end of the buffer
bptr = BufStart                     ' Set byte ptr to start of buffer
NewLoop = 1

do   
    '==== Measure FIDELITY settings at C.7 ADC
    '==== Four settings with each with a maximum of 16 'beats'
    '==== #1 - each beat is 1 buffer bit. 8 x clock pulses will advance buffer
    '==== #2 - each beat is 2 buffer bits. 4 x clock pulses will advance buffer
    '==== #3 - each beat is 4 buffer bits. 2 x clock pulses will advance buffer
    '==== #4 - each beat is 8 buffer bits. 1 x clock pulse will advance buffer
    
readadc C.7, b11
    
if Fidelity<64 then            ' === Fidelity #1 ===
    MaxClkCount = 8
    MaxBufLen = 16
    high B.1                ' LED indicator
    else low B.1
end if
        
if Fidelity>=64 and Fidelity <128 then ' === Fidelity #2 ===
    MaxClkCount = 4
    MaxBufLen = 32
    high B.2                ' LED indicator
    else low B.2
end if
        
if Fidelity>=128 and Fidelity <192 then ' === Fidelity #3 ===
    MaxClkCount = 2
    MaxBufLen = 64
    high B.3                ' LED indicator
    else low B.3
end if
        
if Fidelity>192 then            ' === Fidelity #4 ===
    MaxClkCount = 1
    MaxBufLen = 128
    high B.4                ' LED indicator
    else low B.4
end if

    '==== Buffer Length defaults to 16 full beats if B.6 high ====

if Default16 = 1 then
    BufLen = MaxBufLen
    BufEnd = BufStart + BufLen - 1
    high B.5                ' LED indicator
endif

    '==== ONE-SHOT DECLARATIONS ====
    
ClkPinCopy  = ClkPin                ' Advance clock on rising edge
ClkOneShot  = ClkPinCopy &/ ClkPinMem     
ClkPinMem   = ClkPinCopy                 
DatPinCopy  = DatPin            ' Register data on rising edge
DatOneShot  = DatPinCopy &/ DatPinMem
DatPinMem   = DatPinCopy
BufIncCopy  = IncPin            ' Increase buffer length on rising edge
BufInc1Shot = BufIncCopy &/ BufIncMem
BufIncMem   = BufIncCopy
BufDecCopy  = DecPin            ' Decrease buffer length on rising edge
BufDec1Shot = BufDecCopy &/ BufDecMem
BufDecMem   = BufDecCopy
    
    '==== SET DATA ONE-SHOT BETWEEN CLOCK PULSES ====

if DatOneShot = 1 then       
    DatPin1Shot = 1         
end if

    '==== MANUALLY CHANGE BUFFER LENGTH ====
    '==== But only when RecPin = 0 ====
    
if RecPin = 0 then

    if BufInc1Shot = 1 and bptr < MaxBufLen then
        BufLen = Buflen + 1
        BufEnd = BufStart + BufLen - 1
    end if

    if BufDec1Shot = 1 and Buflen > 1 then
        Buflen = Buflen - 1
        BufEnd = BufStart + BufLen - 1
    end if

end if
    
    '==== DETECT RISING EDGE OF CLOCK PULSE AT C.1 ====
    
if ClkOneShot = 1 then               
    ClkCount = ClkCount +1            ' Increment clock count
    
  if ClkCount = MaxClkCount then
    
    if     RstPin = 1 then
        bptr = BufStart            ' Pressing reset pin will return to start of
                            ' the loop in time with clock
    end if
    
    '==== DETECT RECORD MODE AT C.0 AND START/RESET LOOP ====
    
    if Default16 = 0 then            ' Unless in "Default 16 mode"...
        
        if    RecPin = 1 then            ' For each new press of "Record button"
            if NewLoop = 1 then   
                tablecopy 32 , 479     ' Empty buffer into Table Memory
                bptr = BufStart        ' Reset pointer to start of buffer
                BufLen = 1            ' Reset Buffer to start with Length 1
            end if                 
                                
            if BufLen <MaxBufLen then    ' Increase buffer length by 1       
                BufLen = Buflen + 1
                BufEnd = BufStart + BufLen - 1
                NewLoop = 0
            end if
        end if
    end if
    
    if    RecPin = 0 then
        NewLoop = 1
    end if
    
    '==== ADDING DATA TO BUFFER ====
        
    if TrigRepeat = 1 then        ' For B.7 High, data is added at each clock
        if Datpin = 1 then    ' pulse while C.2 is held high
        @bptr = Datpin        ' i.e - "Continuous" Mode
        end if
    end if
    
    if TrigRepeat = 0 then        ' For B.7 Low, data is added at clock pulse
        if DatPin1Shot = 1 then    ' Once every high then low at C.2 pin
            @bptr = DatPin1Shot     ' i.e - "One-Shot" Mode
        end if
    end if
    
    '==== DELETING DATA FROM BUFFER ====
            
    if DelPin = 1 then        ' Delete data from current bptr address
        @bptr = 0
    endif
    
    '==== SHOW POINTER LOCATION DURING SIMULATION ====
      
    MyPtr = bptr             ' Shows value of bptr within variable b6
      
    '==== CONVERT DATA IN BUFFER TO OUTPUT AT PIN B.0 ====
    '==== For a buffer length of 1 no outputs will be sent
    
    if BufLen > 1 then
            
        Active = @bptr &/ DelPin    ' Unless data is being deleted
        if Active = 1 then
            high OutPin            ' Ouput data at B.0
        else
                 low OutPin
            endif
    endif
    
    '==== MOVE POINTER ALONG ONE ADDRESS POSITION ====
    
    inc bptr                      ' Increment bptr address position
      
    if bptr > BufEnd then         ' If past last byte ...
          bptr = BufStart         ' ... reset ptr
      endif
    
    DatPin1Shot = 0            ' Ensure Data One-Shot memory resets
    ClkCount = 0
    
  endif
endif

loop
To add to the problems o_O I'd also hoped to sync the start and end positions of the buffer with other PICaxe chips which I was planning on using the serial In/Out pins to achieve. This could all add to delays.

I know it's asking a lot but if anyone could spot any obvious snippets of the code where it could be made more efficient, I'd be grateful.

Thanks again

Chris
 

techElder

Well-known member
Chris, why not go with the 28X2 at a full blown 64MHz or even overclock it? Lots of room and lots of speed.
 

AllyCat

Senior Member
Hi,
Why did you calculate 8 * 15ms = 120ms above? Was the 8 related to bits in a byte?
The 8 is simply the proposed running clock frequency (32 MHz) divided by the "normalised" (PICaxe default) frequency of 4 MHz (32 / 4 = 8 ;) ).

I've not looked at your code in great detail, but I think all those IF ... THENs in sequence could be a problem. A solution might be to allocate each "option" to a flag or two (e.g. bitx = Fidelity /128) within a single byte and use an ON {byte} GOTO (label1, label2, .........) structure. You might first need to "process" the byte (e.g. using a LOOKDOWN command) to remove gaps between the labels in the GOTO list.

Cheers, Alan.
 

hippy

Technical Support
Staff member
For a 16 step pattern on a drum machine to run at say 120bpm, you would need each step to complete in 125ms. My program needs 8 sub-divisions per step i.e - just under 16ms per sub division. And so my do...loop needs to execute in 16ms or less just to get 120bpm tempo.
I can't see what the particular problem in achieving that would be, especially at 32MHz.

I'm not sure your maths is right though. 120 BPM is 2 beats per second, 500ms per beat. 500 divided by 8 is 64ms ish.

BPM.jpg
 
Last edited:

hippy

Technical Support
Staff member
It's only by a factor of four, and I wasn't entirely sure. 1/1 time and you would be right, just that this didn't seem likely with 4/4 time.

Plus more than 960 strikes per second seemed unlikely. But, if you weren't trying to place strikes on divisions of a beat, were having strikes other than where those divisions precisely fell, it would make sense.

Much like a drum track on a CD having 44,100 samples per second, but the strikes would mostly fall only on a regular few - assuming the perfect time-keeping robot drummer.
 

AllyCat

Senior Member
Hi,

Yes, I was over-pessimistic about the execution time, which demonstrates how relatively meaningless is the number of bytes in the program. In this case much of the code (program bytes) is enclosed within IF .. ENDIF structures, some/most of which may never be executed in a particular pass (loop). For example, only one of four of the "fidelity" sections will be executed and the other three will be skipped. A "jump over" from the IF clause to ENDIF) generally takes a normalised (default 4 MHz) time of about 1.2 ms (1200 base PIC Instruction Cycles) or 0.15 ms at Setfreq m32.

So it looks as if the overall execution time should be easily fast enough, but you might still need to apply structures which give a (nearly) constant execution time, to maintain the rhythm. For example the ON...GOTO does introduce slightly more delay for the later labels in its list, but I believe an ELSE path (within an IF ... ENDIF) is rather longer. However, the inclusion or omission (skipping) of large sections of code will, of course, make a much larger difference!

Cheers, Alan.
 

Chris Kelly

Active member
Thanks again Alan. The issue of inconsistency in execution timing is something I always worried about. My plan had been to externally clock the PICaxe using a 555 timer and have the program wait until each clock input before proceeding.

In the manual it mentions running parallel tasks. I wondered if a second program could run in the background and dictate the timing of the clock cycle? This would make an external clock redundant.
 

AllyCat

Senior Member
Hi,

Trying to use PICaxe multi-tasking will probably make matters MUCH worse, and it can only run at (a notional) 4 MHz clock frequency.

PICaxe instruction execution times are not particularly "predictable" but they are quite "consistent", so I don't think that you will have major problems. However those times are not always negligible, so you may need to ensure that you do not include large chunks of code in one "time slot" and not in another (if they are required to be the same).

A "professional" solution would probably use an interrupt structure driven by "ticks" from dedicated hardware, but that's beginning to get into deeper water. The ticks might come from a 555 timer, an X2's internal timer or perhaps an M2's PWM or servo (pulse) output, but each has it's own particular limitations, so I'd Keep It SimpleS (KISS) for now..

Cheers, Alan.
 

hippy

Technical Support
Staff member
You should be able to do the clocking inside the program without multi-tasking. I would suggest the best starting point is a pre-programmed drum riff and playing it back, build on that.

The following code is for an 18M2 but should run on everything else. Tempo is set by a pot and the drum is a piezo. Sounds fine by me and that's running at 4MHz. No fancy tricks used just FOR-NEXT loops to run through the buffer.

The timing is a bit out, the pot should give 60-180 BPM but the top end is closer to 120 BPM, but that should be easy enough to tweak and would be more accurate at 32MHz.

Code:
#Picaxe 18M2
#No_Data

Symbol POT_PIN    = C.1
Symbol PIEZO_PIN  = C.2

Symbol adcValue   = b0
Symbol bpm        = b1
Symbol tempoPause = w1 ; b3:b2

Init:
  Gosub PreProgramRiff

MainLoop:
  Do
    For bPtr = 128 To 255
      If @bPtr <> 0 Then
        Sound PIEZO_PIN, ( 100, 1 )
      Else
        Sound PIEZO_PIN, (   0, 1 )
      End If
      Gosub Delay
    Next
  Loop

Delay:
  ReadAdc POT_PIN, adcValue       ;  0 .. 255
  bpm = adcValue * 120 / 255 + 60 ; 60 .. 180 BPM
  tempoPause = 7500 / bpm
  Pause tempoPause
  Return

; ***************************
; * Pre-program a drum riff *
; ***************************

#Macro Strike( bar, index, length )
  bPtr  = bar - 1 * 32 + index + 128
  @bPtr = length
#EndMacro

PreProgramRiff:
  
  For bPtr = 128 To 255
    @bPtr = 0
  Next

  ;     Bar 1
  ; 4 ||---------------------------------|
  ; - ||-O-------O-------O-------O-------|
  ; 4 ||-:-------:-------:-------:-------|
  ;      :       : 1111111111222222222233
  ;      01234567890123456789012345678901

  ;       .---------- Bar Number
  ;       |   .------ Where in the bar
  ;       |   |  .--- Length of note
  ;       |   |  |
  Strike( 1,  0, 8 )
  Strike( 1,  8, 8 )
  Strike( 1, 16, 8 )
  Strike( 1, 24, 8 )

  ;     Bar 2
  ; 4  |---------------------------------|
  ; -  |-O---O-O-O-------O---O-O-O-------|
  ; 4  |-:---:-:-:-------:---:-:-:-------|
  ;      :   : : : 1111111111222222222233
  ;      01234567890123456789012345678901

  Strike( 2,  0, 4 )
  Strike( 2,  4, 2 )
  Strike( 2,  6, 2 )
  Strike( 2,  8, 8 )
  Strike( 2, 16, 4 )
  Strike( 2, 20, 2 )
  Strike( 2, 22, 2 )
  Strike( 2, 24, 8 )

  ;     Bar 3
  ; 4  |---------------------------------|
  ; -  |-O-------O---O---O-------O-------|
  ; 4  |-:-------:---:---:-------:-------|
  ;      :       : 1111111111222222222233
  ;      01234567890123456789012345678901

  Strike( 3,  0, 8 )
  Strike( 3,  8, 4 )
  Strike( 3, 12, 4 )
  Strike( 3, 16, 8 )
  Strike( 3, 24, 8 )

  ;     Bar 4
  ; 4  |---------------------------------||
  ; -  |-OOOOOOOO--------OOOOOOOO--------||
  ; 4  |-::::::::--------::::::::--------||
  ;      ::::::::  1111111111222222222233
  ;      01234567890123456789012345678901

  Strike( 4,  0, 1 )
  Strike( 4,  1, 1 )
  Strike( 4,  2, 1 )
  Strike( 4,  3, 1 )
  Strike( 4,  4, 1 )
  Strike( 4,  5, 1 )
  Strike( 4,  6, 1 )
  Strike( 4,  7, 1 )

  Strike( 4, 16, 1 )
  Strike( 4, 17, 1 )
  Strike( 4, 18, 1 )
  Strike( 4, 19, 1 )
  Strike( 4, 20, 1 )
  Strike( 4, 21, 1 )
  Strike( 4, 22, 1 )
  Strike( 4, 23, 1 )

  Return
 
Top