Microsoft Small Basic

Program Listing: JDR564-10
' Spring 0.6
' Copyright (c) 2012 Nonki Takahashi. All rights reserved.
'
' History:
' 0.6 2012/10/16 Supported mouse operation. (JDR564-10)
' 0.5 2012/10/15 Considered horizontal parameters. (JDR564-9)
' 0.42 2012/10/15 Minor change.
' 0.41 2012/10/15 Fixed to be worked on Silverlight. (JDR564-8)
' 0.32 2012/10/14 Work on the web too + restart option by GoToLoop. (JDR564-6)
' 0.31 2012/10/14 Minor change. (JDR564-2)
' 0.3 2012/10/14 Changed to use image. (JDR564-1)
' 0.2 2012/10/13 Timer event mod by GoToLoop. (JDR564-0)
' 0.1 2012/10/13 Created. (JDR564)
'
pgm = "Spring 0.6"
msg = " - Drag and release the weight to restart motion."
title = pgm + msg
GraphicsWindow.Title = title
debug = "False"

scale = 3139 ' [dot/m]

spring[1]["l"] = 0.05 ' spring length [m]
spring[1]["w"] = 0.006 ' spring width [m]
spring[1]["le"] = 0.006 ' spring edge length [m]
spring[1]["k"] = 20 ' spring constant [N/m]
spring[1]["xs"] = 0.1 ' stationary point from left [m]
spring[1]["ys"] = 0 ' stationary point from top [m]
spring[1]["a"] = 0 ' angle of spring [degree]
spring[1]["cl"] = spring[1]["l"] ' initial length of spring [m]

m = 0.01 ' mass of weight [kg]
d = 0.01 ' diameter of weight [m]

g = 9.8 ' acceleration of gravity [m/(s^2)]
x0 = spring[1]["xs"] ' initial center x of weight from left [m]
y0 = spring[1]["ys"] + spring[1]["cl"] + d / 2 ' initial center y of weight from top [m]
vx0 = 0 ' initial x velocity of weight [m/s]
vy0 = 0 ' initial y velocity of weight [m/s]
InitParam()

fps = 12 ' frame rate [FPS (frames per second)]
time = Math.Round(1000 / fps)
dt = time / 1000

InitWeight()

Timer.Tick = OnTick
Timer.Interval = time
tick = "False"
continue = "True"
Mouse_Init()
param = "Down=True;"
Mouse_SetHandler()

While continue
If tick Then
Animation()
tick = "False"
ElseIf clicked Then
If Math.SquareRoot(Math.Power(mxD - x * scale, 2) + Math.Power(myD - y * scale, 2)) <= d * scale / 2 Then
Timer.Pause()
param = "Up=True;Move=True;"
Mouse_SetHandler()
While released = "False"
x = mxM / scale
y = myM / scale
ShowWeight()
EndWhile
vx = 0
vy = 0
Timer.Resume()
EndIf
param = "Down=True;"
Mouse_SetHandler()
Else
Program.Delay(time / 10)
EndIf
EndWhile

Sound.PlayClickAndWait()
Program.End()

Sub Animation
kx = spring[1]["k"] * Math.Sin(Math.GetRadians(spring[1]["a"]))
ky = spring[1]["k"] * Math.Cos(Math.GetRadians(spring[1]["a"]))
dvx = -(spring[1]["cl"] - spring[1]["l"]) * kx * dt
dvy = (m * g - (spring[1]["cl"] - spring[1]["l"]) * ky) * dt
vx = vx + dvx ' next x velocity of weight
vy = vy + dvy ' next y velocity of weight
dx = (2 * vx + dvx) * dt / 2
dy = (2 * vy + dvy) * dt / 2
x = x + dx ' next center x of weight
y = y + dy ' next center y of weight
ShowWeight()
EndSub

Sub OnTick
tick = "True"
If debug Then
Mouse_OnTick()
EndIf
EndSub

Sub OnKeyDown
key = GraphicsWindow.LastKey
EndSub

Sub InitParam
x = x0 ' initial center x of weight
y = y0 ' initial center y of weight
vx = vx0 ' initial x velocity of weight
vy = vy0 ' initial y velocity of weight
EndSub

Sub InitWeight
url = "http://www.nonkit.com/smallbasic.files/spring.png"
img = ImageList.LoadImage(url)
spring[1]["spring"] = Shapes.AddImage(img)
spring[1]["ld"] = ImageList.GetHeightOfImage(img) ' spring length (without edges) [dot]
If spring[1]["ld"] < 1 Then ' patch to make it work on Silverlight
spring[1]["ld"] = 276
EndIf

GraphicsWindow.PenColor = "DimGray"
spring[1]["edge0"] = Shapes.AddLine(0, 0, 0, spring[1]["le"] * scale)

GraphicsWindow.PenColor = "DimGray"
spring[1]["edge1"] = Shapes.AddLine(0, 0, 0, spring[1]["le"] * scale)

pw = GraphicsWindow.PenWidth
GraphicsWindow.PenWidth = 0
GraphicsWindow.BrushColor = "Black"
weight = Shapes.AddEllipse(d * scale, d * scale)

ShowWeight()
EndSub

Sub ShowWeight
' param y
Stack.PushValue("local", x)
Stack.PushValue("local", y)
_y = x - spring[1]["xs"]
x = y - spring[1]["ys"]
y = _y
Math_CartesianToPolar()
spring[1]["a"] = a
spring[1]["cl"] = r - d / 2 ' current length of spring
y = Stack.PopValue("local")
x = Stack.PopValue("local")

Shapes.Move(weight, (x - d / 2) * scale, (y - d / 2) * scale)

Stack.PushValue("local", x)
Stack.PushValue("local", y)
param = "x=" + spring[1]["xs"] + ";y=" + (spring[1]["ys"] + spring[1]["le"] / 2) + ";width=0;height=0;cx=" + spring[1]["xs"] + ";cy=" + spring[1]["ys"] + ";angle=" + (-spring[1]["a"]) + ";"
Shapes_CalcRotatePos()
Shapes.Move(spring[1]["edge0"], x * scale, (y - spring[1]["le"] / 2) * scale)
Shapes.Rotate(spring[1]["edge0"], -spring[1]["a"])

param["y"] = spring[1]["ys"] + spring[1]["le"] + (spring[1]["cl"] - 2 * spring[1]["le"]) / 2
Shapes_CalcRotatePos()
Shapes.Move(spring[1]["spring"], (x - spring[1]["w"] / 2) * scale, y * scale - spring[1]["ld"] / 2)
Shapes.Zoom(spring[1]["spring"], 1, (spring[1]["cl"] - 2 * spring[1]["le"]) * scale / spring[1]["ld"])
Shapes.Rotate(spring[1]["spring"], -spring[1]["a"])

param["y"] = spring[1]["ys"] + spring[1]["cl"] - spring[1]["le"] / 2
Shapes_CalcRotatePos()
Shapes.Move(spring[1]["edge1"], x * scale, (y - spring[1]["le"] / 2) * scale)
Shapes.Rotate(spring[1]["edge1"], -spring[1]["a"])
y = Stack.PopValue("local")
x = Stack.PopValue("local")
EndSub

Sub Math_CartesianToPolar
' Math | convert cartesian coodinate to polar coordinate
' param x, y - cartesian coordinate
' return r, a - polar coordinate
r = Math.SquareRoot(x * x + y * y)
If x = 0 And y > 0 Then
a = 90 ' [degree]
ElseIf x = 0 And y < 0 Then
a = -90
Else
a = Math.ArcTan(y / x) * 180 / Math.Pi
EndIf
If x < 0 Then
a = a + 180
ElseIf x > 0 And y < 0 Then
a = a + 360
EndIf
EndSub

Sub Mouse_Init
' Mouse | Initialize for common event handler
clicked = "False"
moved = "False"
released = "False"
'If debug Then ' alternate is in OnTick for this program
'Timer.Interval = 200
'Timer.Tick = Mouse_OnTick
'EndIf
EndSub

Sub Mouse_SetHandler
' Mouse | Set or reset common event handler
' param["down"] - "True" if set, "False" if reset
' param["move"] - "True" if set, "False" if reset
' param["up"] - - "True" if set, "False" if reset
' return clicked - "False" if set MouseDown
' return moved - "False" if set MouseMove
' return released - "False" if set MouseUp
' return dmu - which handlers are set for debug
If param["up"] Then
released = "False"
GraphicsWindow.MouseUp = Mouse_OnUp
handler["up"] = "U"
ElseIf param["up"] = "False" Then
GraphicsWindow.MouseUp = Mouse_DoNothing
handler["up"] = ""
EndIf
If param["down"] Then
clicked = "False"
GraphicsWindow.MouseDown = Mouse_OnDown
handler["down"] = "D"
ElseIf param["down"] = "False" Then
GraphicsWindow.MouseDown = Mouse_DoNothing
handler["down"] = ""
EndIf
If param["move"] Then
moved = "False"
GraphicsWindow.MouseMove = Mouse_OnMove
handler["move"] = "M"
ElseIf param["move"] = "False" Then
GraphicsWindow.MouseMove = Mouse_DoNothing
handler["move"] = ""
EndIf
dmu = handler["down"] + handler["move"] + handler["up"]
If debug Then
smrc = " set "
EndIf
EndSub

Sub Mouse_OnDown
' Mouse | Common event handler on mouse down
' return mxD, myD - position on mouse down
mxD = GraphicsWindow.MouseX
myD = GraphicsWindow.MouseY
clicked = "True"
released = "False"
If debug Then
smrc = " clicked " + mxD + "," + myD
EndIf
EndSub

Sub Mouse_DoNothing
' Mouse | Common event handler to do nothing
EndSub

Sub Mouse_OnMove
' Mouse | Common event handler on mouse move
' return mxM, myM - position on mouse move
mxM = GraphicsWindow.MouseX
myM = GraphicsWindow.MouseY
moved = "True"
If debug Then
smrc = " moved " + mxM + "," + myM
EndIf
EndSub

Sub Mouse_OnTick
' Mouse | debug routine
If clicked Then
cmr = "C"
Else
cmr = ""
EndIf
If moved Then
cmr = cmr + "M"
EndIf
If released Then
cmr = cmr + "R"
EndIf
GraphicsWindow.Title = title + smrc + " " + dmu + " " + cmr
EndSub

Sub Mouse_OnUp
' Mouse | Common event handler on mouse up
' return mxU, myU - position on mouse up
mxU = GraphicsWindow.MouseX
myU = GraphicsWindow.MouseY
released = "True"
If debug Then
smrc = " released " + mxU + "," + myU
EndIf
EndSub

Sub Shapes_CalcRotatePos
' Shapes | Calculate position for rotated shape
' param["x"], param["y"] - position of a shape
' param["width"], param["height"] - size of a shape
' param ["cx"], param["cy"] - center of shapes
' param ["angle"] - rotate angle
' return x, y - rotated position of a shape
_cx = param["x"] + param["width"] / 2
_cy = param["y"] + param["height"] / 2
x = _cx - param["cx"]
y = _cy - param["cy"]
Math_CartesianToPolar()
a = a + param["angle"]
x = r * Math.Cos(a * Math.Pi / 180)
y = r * Math.Sin(a * Math.Pi / 180)
_cx = x + param["cx"]
_cy = y + param["cy"]
x = _cx - param["width"] / 2
y = _cy - param["height"] / 2
EndSub