EDIT: v1.02 update. Tided up the code
Hi folks
A Snooker/Billards simulation for Basic4Android
This example is fully operational although there is no code to "pot" a ball
All you can do here is aim and shoot. Its enough to get you going though
INSTRUCTIONS
# Touch / Move finger on the table to position cue ball travel direction
# Slide the power bar to set the cue power
# Touch the lower/left white ball to shoot
# Use MENU option to reset ball layout
Hi folks
A Snooker/Billards simulation for Basic4Android
This example is fully operational although there is no code to "pot" a ball
All you can do here is aim and shoot. Its enough to get you going though
INSTRUCTIONS
# Touch / Move finger on the table to position cue ball travel direction
# Slide the power bar to set the cue power
# Touch the lower/left white ball to shoot
# Use MENU option to reset ball layout
B4X:
#Region Module Attributes
#FullScreen: False
#IncludeTitle: True
#ApplicationLabel: SnookerSim
#VersionCode: 1
#VersionName: 1.02
#SupportedOrientations: portrait
#CanInstallToExternalStorage: False
#End Region
' 2D Snooker physics (basic simulation)- Jim Brown
' v1.02 - Added additional comments and relabelled variables
' Credits: Joseph Humfrey
' NOTE: Uses the "Phone" library (keep phone awake)
Sub Process_Globals
Dim time As Timer
Dim awake As PhoneWakeState
Dim FRICTION As Float ' fiction of the balls
Dim BALL_MASS As Float ' weight of the balls
Dim BALL_RADIUS As Float ' radius of the balls
Dim BALL_DIAMETER As Float ' diameter of the balls
Dim NUMBALLS As Int ' how many balls to control
Dim mouseX,mouseY,cueAngle,cuePower As Float
Dim ballsmoving As Boolean ' flag to indicate whether or not any of the balls are moving
Type balltype (x As Float, y As Float, dx As Float, dy As Float, color As Int)
Dim ball(50) As balltype
End Sub
Sub Globals
Dim centerX,centerY As Int ' center of the snooker table display area (panel 2)
Dim powerBarX,powerBarY,powerBarWidth,powerBarHeight As Int ' power bar positions (panel 1)
Dim count As Int
' display-related variables
Dim pan1,pan2 As Panel
Dim can1,can2 As Canvas
Dim rec As Rect
End Sub
Sub Activity_Create(FirstTime As Boolean)
' display / system setup
pan1.Initialize("panel1") ' this panel will display the 'power bar' and 'take shot' ball
pan2.Initialize("panel2") ' this panel is the main snooker table area
Activity.AddView(pan1,0%x,0%y,12%x,100%y)
Activity.AddView(pan2,14%x,0%y,100%x-14%x,100%y)
can1.Initialize(pan1) : can2.Initialize(pan2)
centerX=pan2.Width/2 : centerY=pan2.Height/2
' How many pottable balls in game (excluding cue ball)
' NOTE: Use 3,6,10,15,21 ... (since the balls are placed in a triangle format)
NUMBALLS=15
' General physics settings
FRICTION=0.981 : BALL_MASS=60.0 : BALL_RADIUS=pan2.Width/(NUMBALLS*1.5)
BALL_DIAMETER=BALL_RADIUS*2.0
' Timer
time.Initialize("Timer1",12)
' Menus
Activity.AddMenuItem("Reset","Menu_ResetTable")
Activity.AddMenuItem("Exit","Menu_ExitGame")
' power bar position
powerBarX=10 : powerBarY=pan1.Height-128 : powerBarWidth=pan1.Width-20 : powerBarHeight=pan1.Height/2.5
'
If FirstTime=True Then Menu_ResetTable_Click
End Sub
Sub Activity_Resume
time.Enabled=True
awake.KeepAlive(True)
End Sub
Sub Activity_Pause (UserClosed As Boolean)
time.Enabled=False
awake.ReleaseKeepAlive
End Sub
' user has touched Panel 1 (the left panel)
Sub panel1_Touch (Action As Int, X As Float, Y As Float)
If ballsmoving=True Then Return
' check if touched point is over the 'power bar' indicator
If Y>=powerBarY-powerBarHeight AND Y<=powerBarY Then
If Y<powerBarY-powerBarHeight+8 Then Y=powerBarY-powerBarHeight+8
If Y>powerBarY-16 Then Y=powerBarY-16
cuePower=(powerBarY-Y)/8.0
RenderPanel1
Return
End If
' check if touch point is over the 'take shot' ball
If Y>=powerBarY+32 Then
cueAngle=180.0-ATan2D(mouseX-ball(0).X,mouseY-ball(0).y)
ball(0).dx=SinD(cueAngle)*cuePower : ball(0).dy=-CosD(cueAngle)*cuePower
can1.DrawColor(Colors.Black) : pan1.Invalidate
ballsmoving=True
End If
End Sub
' user has touched Panel 2 (the snooker table panel)
Sub panel2_Touch (Action As Int, X As Float, Y As Float)
mouseX=X : mouseY=Y : RenderPanel2
End Sub
' Reset the table
Sub Menu_ResetTable_Click()
SetupTriangle : SetupCueBall : ballsmoving=True
RenderPanel1 : RenderPanel2
End Sub
Sub Menu_ExitGame_Click()
Activity.Finish
End Sub
Sub Timer1_Tick
count=count+1
If ballsmoving=True Then
UpdatePhysics
RenderPanel2
If count Mod(20)=0 Then
If AreBallsMoving=False Then
ballsmoving=False : RenderPanel1 : RenderPanel2
End If
End If
End If
End Sub
Sub RenderPanel1()
' power bar indicator
rec.Initialize(powerBarX,powerBarY-powerBarHeight,powerBarX+powerBarWidth,powerBarY)
can1.DrawRect(rec,Colors.Blue,True,0)
rec.Initialize(powerBarX+5,powerBarY-(cuePower*8),powerBarX+powerBarWidth-6,powerBarY-8)
can1.DrawRect(rec,Colors.White,True,0)
' take shot ball
can1.DrawCircle(powerBarX+(powerBarWidth/2.0),powerBarY+64,pan1.Width/2-20,Colors.LightGray,True,0)
can1.DrawCircle(powerBarX+(powerBarWidth/2.0),powerBarY+64,pan1.Width/2-26,Colors.White,True,0)
pan1.Invalidate
End Sub
Sub RenderPanel2()
can2.DrawColor(Colors.RGB(16,127,78)) ' erase the background with the given colour
' Draw each ball. Note that ball(0) is the cue ball
For i=0 To NUMBALLS
can2.DrawCircle(ball(i).x, ball(i).y, BALL_RADIUS, ball(i).color, True,0)
Next
' Render the aiming line and circle (only when all balls have stopped moving)
If ballsmoving=False Then
can2.DrawLine(ball(0).x,ball(0).y,mouseX,mouseY,Colors.Black,4.0)
can2.DrawCircle(mouseX,mouseY,BALL_RADIUS,Colors.Black,False,6.0)
End If
pan2.Invalidate
End Sub
' Arrange balls in a triangle formation
Sub SetupTriangle()
Dim ballTriangleSize,i As Int
i=0
Do Until i>=NUMBALLS
ballTriangleSize=ballTriangleSize+1
i=i+ballTriangleSize
Loop
i=1
For xloop=ballTriangleSize To 1 Step -1
For yloop=1 To xloop
ball(i).y=(5-xloop)*BALL_DIAMETER+120+RNum
ball(i).x=(yloop*BALL_DIAMETER)-(xloop*BALL_DIAMETER)/2.0+(centerX)+RNum
ball(i).dx=0.0 : ball(i).dy=0.0
' yellow or red ball colour
If i Mod(2)=0 Then
ball(i).color=Colors.RGB(210,30,20)
Else
ball(i).color=Colors.RGB(240,200,18)
End If
i=i+1
Next
Next
End Sub
' Position the cue ball and set the aiming direction to point above the ball
Sub SetupCueBall()
ball(0).x=centerX+BALL_RADIUS+RNum
ball(0).y=pan2.Height-BALL_RADIUS-Rnd(60,65)
ball(0).dx=0.0 : ball(0).dy=0
ball(0).color=Colors.White
mouseX=centerX+BALL_RADIUS+Rnd(-10,10) : mouseY=pan2.Height/2.75 : cuePower=Rnd(70,80)
End Sub
Sub UpdatePhysics()
Dim actualDist, collisionNormalAngle,moveDist As Float
Dim nX,nY,a1,a2,optimisedP As Float
For i=0 To NUMBALLS
' MOVEMENT
' Update ball postion
ball(i).x=ball(i).x+ball(i).dx : ball(i).y=ball(i).y+ball(i).dy
' Slow the ball down via the global friction value
ball(i).dx=ball(i).dx*FRICTION : ball(i).dy=ball(i).dy*FRICTION
' Stop ball completely when below certain speed
If Abs(ball(i).dx)<0.068 Then ball(i).dx=0.0
If Abs(ball(i).dy)<0.068 Then ball(i).dy=0.0
' COLLISION CHECKS
' Check each other ball (b) against current ball (i)
For b=0 To NUMBALLS
' No need to check ball against itself
If b=i Then Continue
' Get the distance between the 2 balls being checked
actualDist=Sqrt( Power(ball(b).x-ball(i).x,2) + Power(ball(b).y-ball(i).y,2) )
' Collided? Check actual distance against ball diameter
If actualDist<BALL_DIAMETER Then
' Obtain the angle of ball (b) against ball (i)
collisionNormalAngle=ATan2D(ball(b).y-ball(i).y,ball(b).x-ball(i).x)
' Position exact touch (no intersection)
moveDist=(BALL_DIAMETER-actualDist)*0.5
ball(i).x=ball(i).x+moveDist*CosD(collisionNormalAngle+180)
ball(i).y=ball(i).y+moveDist*SinD(collisionNormalAngle+180)
ball(b).x=ball(b).x+moveDist*CosD(collisionNormalAngle)
ball(b).y=ball(b).y+moveDist*SinD(collisionNormalAngle)
' COLLISION RESPONSE
' n = vector connecting centres of balls
' Find components normalised vector
nX=CosD(collisionNormalAngle)
nY=SinD(collisionNormalAngle)
' Find length of components movement vectors (via dot product)
a1=ball(i).dx*nX + ball(i).dy*nY
a2=ball(b).dx*nX + ball(b).dy*nY
' Optimised = 2*(a1-a2)/(BallMass1+BallMass2)
optimisedP=(2.0 * (a1-a2) ) / (BALL_MASS*2)
' Find resultant vectors
ball(i).dx=ball(i).dx-(optimisedP*BALL_MASS*nX)
ball(i).dy=ball(i).dy-(optimisedP*BALL_MASS*nY)
ball(b).dx=ball(b).dx+(optimisedP*BALL_MASS*nX)
ball(b).dy=ball(b).dy+(optimisedP*BALL_MASS*nY)
End If
Next
' Simple bounce off walls check
If ball(i).x<BALL_RADIUS Then
ball(i).x=BALL_RADIUS : ball(i).dx=ball(i).dx*-0.9
End If
If ball(i).x>pan2.Width-BALL_RADIUS Then
ball(i).x=pan2.Width-BALL_RADIUS : ball(i).dx=ball(i).dx*-0.9
End If
If ball(i).y<BALL_RADIUS Then
ball(i).y=BALL_RADIUS : ball(i).dy=ball(i).dy*-0.9
End If
If ball(i).y>pan2.Height-BALL_RADIUS Then
ball(i).y=pan2.Height-BALL_RADIUS : ball(i).dy=ball(i).dy*-0.9
End If
Next
End Sub
' Return TRUE if any of the balls are moving
Sub AreBallsMoving() As Boolean
For obj=0 To NUMBALLS
If ball(obj).dx<>0.0 OR ball(obj).dy<>0.0 Then Return True
Next
Return False
End Sub
' Return a random float between -0.5 and +0.5
' Used to add a slight re-positioning of the balls
Sub RNum() As Float
Dim f As Float = Rnd(-100,100)
Return f/200.0
End Sub
Attachments
Last edited: