I have this old bit of demo code (Blitz3D) I did for someone else years ago. It may be of some use, I dunno. Uses a recursive function to check all connected balls. I haven't looked too deeply at the code again - I may well do things differently, now.
Graphics3D 800,600,32
SetBuffer BackBuffer()
WireFrame 0
AntiAlias 0
SeedRnd MilliSecs()
Global frame_count%
Global fps%
Global slowest_fps%
Global fps_timeout%
Global frame_time%
Global slowest_frame%
Global frame_start%
fps_timer = CreateTimer(60)
slowmo% = False
wiref% = False
cam = CreateCamera()
PositionEntity cam,0,1.5,-36
CameraZoom cam,1.5
light = CreateLight()
Type gridT
Field entity
Field ID
Field row%, col%
End Type
Const END_ROW% = 14
Const END_COL% = 20
Const GRID_START_X# = Float(-END_COL)
Const GRID_START_Y# = Float(END_ROW)
Const KEY_MOVE_FRAMES% = 8
Const SHOOT_SPEED# = 1.0
; Grid object IDs.
Const ID_NEW_PLAYER% = -1
Const ID_SPACE% = 0
Const ID_WALL% = 1
Const ID_RED_BALL% = 2
Const ID_GREEN_BALL% = 3
Const ID_YELLOW_BALL% = 4
Const ID_CYAN_BALL% = 5
Const ID_HORZ_END% = 7
Const ID_VERT_END% = 8
Const ID_SET_RANDOM% = 9
Const CLOCKWISE% = 1
Const ANTICLOCKWISE% = -1
Const TYPE_PLAYER% = 1
Const TYPE_WALL% = 2
Const TYPE_BALL% = 3
Global move_row% = 0
Global move_col% = 1
Global move_dx#, move_dy#
Global move_count%
Global player_moving% = False
Dim grid.gridT(END_ROW,END_COL)
create_level()
Global player.gridT = New gridT
player\entity = CreateSphere()
player\ID = ID_NEW_PLAYER
position_player(2,10)
EntityShininess player\entity,1
EntityType player\entity,TYPE_PLAYER
EntityRadius player\entity,.9,.9
Collisions TYPE_PLAYER,TYPE_BALL,1,1
Collisions TYPE_PLAYER,TYPE_WALL,1,1
; --- Main loop ---
While Not KeyHit(1)
frame_start = MilliSecs()
If KeyHit(28) Then slowmo = Not slowmo
If KeyHit(14)
wiref = Not wiref
WireFrame wiref
EndIf
control_player()
update_player()
UpdateWorld
RenderWorld
frame_time = MilliSecs() - frame_start
show_info()
WaitTimer(fps_timer)
Flip(1)
If slowmo Then Delay 500
Wend
ClearWorld
End
; Initial level grid layout info.
.grid_data
Data 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
Data 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1
Data 1,0,0,7,0,0,0,0,0,0,0,0,0,0,0,0,0,7,0,0,1
Data 1,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,1
Data 1,0,0,0,9,9,9,9,9,9,9,9,9,9,9,9,9,0,0,0,1
Data 1,0,0,0,9,9,9,9,9,9,9,9,9,9,9,9,9,0,0,0,1
Data 1,0,0,0,9,9,9,9,9,9,9,9,9,9,9,9,9,0,0,0,1
Data 1,0,0,0,9,9,9,9,9,9,9,9,9,9,9,9,9,0,0,0,1
Data 1,0,0,0,9,9,9,9,9,9,9,9,9,9,9,9,9,0,0,0,1
Data 1,0,0,0,9,9,9,9,9,9,9,9,9,9,9,9,9,0,0,0,1
Data 1,0,0,0,9,9,9,9,9,9,9,9,9,9,9,9,9,0,0,0,1
Data 1,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,1
Data 1,0,0,7,0,0,0,0,0,0,0,0,0,0,0,0,0,7,0,0,1
Data 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1
Data 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
;
; Player control handler.
;
Function control_player()
If Not player_moving
mxs = MouseXSpeed()
MoveMouse 400,300
If mxs
frames = Abs(mxs/3)
If frames > 10 Then frames = 10
frames = 11 - frames
move_player(Sgn(mxs),frames)
Else
If KeyHit(57) Or MouseHit(1)
shoot_player()
Else
If KeyDown(203) Then move_player(-1,KEY_MOVE_FRAMES)
If KeyDown(205) Then move_player(1,KEY_MOVE_FRAMES)
EndIf
EndIf
EndIf
End Function
;
; Updates player's movement and handles collisions.
;
Function update_player()
If player_moving
ent = EntityCollided(player\entity,TYPE_BALL)
If ent
; A ball was hit...
ball.gridT = Object.gridT(EntityName(ent))
If player\ID <> ball\ID
player_is_ball(ball\ID)
reset_player()
position_player(player\row,player\col)
Else
remove_connected(ball)
reset_player()
position_player(player\row,player\col)
EndIf
Else
ent = EntityCollided(player\entity,TYPE_WALL)
If ent
; A wall was hit...
reset_player()
position_player(player\row,player\col)
Else
; Player moving clockwise/anti-clockwise...
TranslateEntity player\entity,move_dx,move_dy,0
move_count = move_count - 1
If Not move_count Then reset_player()
EndIf
EndIf
EndIf
End Function
;
; Recusively removes all connected balls of the same type.
;
Function remove_connected(ball.gridT)
row = ball\row
col = ball\col
ID = ball\ID
HideEntity ball\entity
ball\ID = ID_SPACE
If grid(row,col-1)\ID = ID Then remove_connected(grid(row,col-1))
If grid(row,col+1)\ID = ID Then remove_connected(grid(row,col+1))
If grid(row-1,col)\ID = ID Then remove_connected(grid(row-1,col))
If grid(row+1,col)\ID = ID Then remove_connected(grid(row+1,col))
End Function
;
; Turns player into a ball of type ID.
;
Function player_is_ball(ID)
Select ID
Case ID_RED_BALL
EntityColor player\entity,155,0,0
Case ID_GREEN_BALL
EntityColor player\entity,0,155,0
Case ID_YELLOW_BALL
EntityColor player\entity,155,155,0
Case ID_CYAN_BALL
EntityColor player\entity,0,155,155
End Select
player\ID = ID
End Function
;
; Resets player's status ready for a new movement.
;
Function reset_player()
player_moving = False
; Flush all input accumulated while moving.
MouseXSpeed()
FlushMouse
FlushKeys
End Function
;
; Moves player around ball grid.
; dir - 1=clockwise, -1=anti-clockwise
; frames - Number of animation frames to make the move.
;
Function move_player(dir%, frames%)
mr = move_row * Sgn(dir) ; Sgn() ensures dir is either CLOCKWISE
mc = move_col * Sgn(dir) ; or ANTICLOCKWISE.
new_row = player\row + mr
new_col = player\col + mc
If dir = CLOCKWISE
Select grid(new_row,new_col)\ID
Case ID_HORZ_END
move_row = mc : move_col = 0
new_row = player\row + (move_row*2)
new_col = player\col + (mc*2)
Case ID_VERT_END
move_col = -mr : move_row = 0
new_row = player\row + (mr*2)
new_col = player\col + (move_col*2)
End Select
Else
Select grid(new_row,new_col)\ID
Case ID_HORZ_END
move_row = mc : move_col = 0
new_row = player\row + (move_row*-2)
new_col = player\col + (mc*2)
Case ID_VERT_END
move_col = -mr : move_row = 0
new_row = player\row + (mr*2)
new_col = player\col + (move_col*-2)
End Select
EndIf
player\row = new_row
player\col = new_col
; Calculate movement animation to get from old position to new one.
start_x# = EntityX(player\entity)
start_y# = EntityY(player\entity)
end_x# = GRID_START_X + (new_col*2)
end_y# = GRID_START_Y - (new_row*2)
move_dx# = (end_x - start_x) / frames
move_dy# = (end_y - start_y) / frames
move_count = frames
player_moving = True
End Function
;
; Set up player to shoot forward.
;
Function shoot_player()
If player\row = 2
move_dx = 0 : move_dy = -SHOOT_SPEED
ElseIf player\row = END_ROW-2
move_dx = 0 : move_dy = SHOOT_SPEED
ElseIf player\col = 2
move_dx = SHOOT_SPEED : move_dy = 0
ElseIf player\col = END_COL-2
move_dx = -SHOOT_SPEED : move_dy = 0
EndIf
player_moving = True
move_count = 9999 ; Make sure we can reach a wall!
End Function
;
; Positions player onscreen at level grid row,col.
;
Function position_player(row%, col%)
player\row = row
player\col = col
PositionEntity player\entity,GRID_START_X+(col*2),GRID_START_Y-(row*2),0
ResetEntity player\entity
End Function
;
; Create all level objects.
;
Function create_level()
pos_x# = GRID_START_X
pos_y# = GRID_START_Y
Restore grid_data
For row = 0 To END_ROW
For col = 0 To END_COL
this.gridT = New gridT
this\row = row
this\col = col
Read datum
Select datum
Case ID_WALL
ent = CreateCube()
ScaleMesh ent,1,1,Rnd(1,3) ; For a more interesting looking wall
EntityColor ent,0,0,Rand(100,255) ;
PositionEntity ent,pos_x,pos_y,0
EntityType ent,TYPE_WALL
EntityRadius ent,1,1
NameEntity ent,Handle(this)
this\entity = ent
this\ID = ID_WALL
Case ID_SET_RANDOM
ent = CreateSphere()
EntityShininess ent,1
PositionEntity ent,pos_x,pos_y,0
EntityType ent,TYPE_BALL
EntityRadius ent,1,1
NameEntity ent,Handle(this)
this\entity = ent
random_ball = Rand(ID_RED_BALL,ID_CYAN_BALL)
Select random_ball
Case ID_RED_BALL
EntityColor ent,155,0,0
Case ID_GREEN_BALL
EntityColor ent,0,155,0
Case ID_YELLOW_BALL
EntityColor ent,155,155,0
Case ID_CYAN_BALL
EntityColor ent,0,155,155
End Select
this\ID = random_ball
Default
this\ID = datum
End Select
grid(row,col) = this
pos_x = pos_x + 2.0
Next
pos_x = GRID_START_X
pos_y = pos_y - 2.0
Next
End Function
;
; Display debug info
;
Function show_info()
If fps_timeout
frame_count = frame_count + 1
If MilliSecs() > fps_timeout Then
fps_timeout = MilliSecs() + 1000
fps = frame_count
frame_count = 0
If fps < slowest_fps Or slowest_fps = 0 Then slowest_fps = fps
EndIf
If frame_time > slowest_frame Then slowest_frame = frame_time
Color 0,255,0
Text 10,10," Triangles: " + TrisRendered()
Color 255,255,0
Text 10,25," Millisecs: " + frame_time
Text 10,40," Slowest: " + slowest_frame
Color 0,255,255
Text 10,55," FPS: " + fps
Text 10,70," Worst: " + slowest_fps
Color 255,255,255
Text 200,10,"Move mouse right or press right arrow key to move clockwise"
Text 200,25,"Move mouse left or press left arrow key to move anti-clockwise"
Text 200,40,"Click left mouse button or press SPACE to shoot"
Else
; First call initialization.
fps_timeout = MilliSecs() + 1000
EndIf
End Function