NaaLaa
Small pinball game using circle-line collision routines from Marcus - Printable Version

+- NaaLaa (https://www.naalaa.com/forum)
+-- Forum: NaaLaa (https://www.naalaa.com/forum/forum-1.html)
+--- Forum: NaaLaa 7 Code (https://www.naalaa.com/forum/forum-4.html)
+--- Thread: Small pinball game using circle-line collision routines from Marcus (/thread-129.html)



Small pinball game using circle-line collision routines from Marcus - kevin - 05-01-2024

This is not meant to be a proper simulation (as you will see if you run it Smile), but just an experiment with the terrific circle-line functions that Marcus provided a while back. I've used the functions as they were originally written by Marcus, with one small alteration to the polygon function, where I have added a fifth argument to allow me to specify a point around which the polygon is rotated, rather than the center point of the polygon (I needed this for the 2 flippers).

There a few values that can be altered in the code to change the way that the ball reacts to certain lines - I've marked some of these as "arbitrary values", and I'm sure changing these will make for a smoother game, but I've decided to move on to a new project.

Control the flippers with the A and D keys, and launch the ball using the UP arrow.

   

Code:
' Experiment.

set window "PINBALL", 480, 640
set redraw off

#win32
visible score = 0
' List of lines that objects can collide with.

left_flipper = []
right_flipper = []

l_flipper = Polygon([0,0,10,-10,60,30,60,40], 140,580,  true,[0,0])
r_flipper = Polygon([0,0,-10,-10,-60,30,-60,40], 300,580,  true,[0,0])
'=============================================================================
'Note  -added fifth argument to Polygon - 0 rotates around center point, or
'use a 2 part table to rotate around any point in the polygon - i.e. [32,34]
' to rotate around the fourth point in the polygon array here : -
'l_flipper = Polygon([0,0,10,0,32,24,32,34,22,34,0,10], 320, 240, true,[32,34])
'==============================================================================
l_flipper.AddTo(left_flipper)
left_flipper_timer = 0
left_ang = 0

r_flipper.AddTo(right_flipper)
right_flipper_timer = 0
right_ang = 0



bouncers = []
lines = []
circles = []
table1 = Polygon([180,0,300,0,360,20,420,60,460,120,480,180,480,640,
                    0,640,0,180,20,120,60,60,120,20,180,0],0,0,false,0)
table1.AddTo(lines)


visible circle_hit = unset
circle1 = Polygon([0,0,9,3,13,8,15,15,13,23,9,27,0,30,-9,27,-13,23,-15,15,-13,8,-9,3],100,230,true,0)
circle1.AddTo(circles)

circle2 = Polygon([0,0,9,3,13,8,15,15,13,23,9,27,0,30,-9,27,-13,23,-15,15,-13,8,-9,3],225,230,true,0)
circle2.AddTo(circles)

circle3 = Polygon([0,0,9,3,13,8,15,15,13,23,9,27,0,30,-9,27,-13,23,-15,15,-13,8,-9,3],350,230,true,0)
circle3.AddTo(circles)

circle4 = Polygon([0,0,9,3,13,8,15,15,13,23,9,27,0,30,-9,27,-13,23,-15,15,-13,8,-9,3],162,180,true,0)
circle4.AddTo(circles)

circle5 = Polygon([0,0,9,3,13,8,15,15,13,23,9,27,0,30,-9,27,-13,23,-15,15,-13,8,-9,3],287,180,true,0)
circle5.AddTo(circles)

circle6 = Polygon([0,0,9,3,13,8,15,15,13,23,9,27,0,30,-9,27,-13,23,-15,15,-13,8,-9,3],130,440,true,0)
circle6.AddTo(circles)

circle7 = Polygon([0,0,9,3,13,8,15,15,13,23,9,27,0,30,-9,27,-13,23,-15,15,-13,8,-9,3],310,440,true,0)
circle7.AddTo(circles)


'additional parts of table
'lines - no bouncing
lines[sizeof(lines)] = Line(440,640,440,180)
lines[sizeof(lines)] = Line(360,640,440,590)
lines[sizeof(lines)] = Line(0,590,80,640)
'left upper curve (inner) - no bouncing
lines[sizeof(lines)] = Line(65,330,40,290)
lines[sizeof(lines)] = Line(40,290,40,200)
lines[sizeof(lines)] = Line(40,200,55,150)
lines[sizeof(lines)] = Line(55,150,95,95)
lines[sizeof(lines)] = Line(95,95,150,60)
lines[sizeof(lines)] = Line(150,60,200,45)


'right upper curve (inner) - no bouncing
lines[sizeof(lines)] = Line(300,85,315,90)
lines[sizeof(lines)] = Line(315,90,360,120)
lines[sizeof(lines)] = Line(360,120,385,160)
lines[sizeof(lines)] = Line(385,160,400,200)


lines[sizeof(lines)] = Line(40,490,40,430)
lines[sizeof(lines)] = Line(400,430,400,490)

'triangles sticking out of side - not bouncing
lines[sizeof(lines)] = Line(0,380,40,380)
lines[sizeof(lines)] = Line(0,310,40,380)
lines[sizeof(lines)] = Line(440,380,400,380)
lines[sizeof(lines)] = Line(400,380,440,310)

'bouncers

bouncers[sizeof(bouncers)] = Line(40,490,130,550)
bouncers[sizeof(bouncers)] = Line(400,490,310,550)

bouncers[sizeof(bouncers)] = Line(40,430,70,435)
bouncers[sizeof(bouncers)] = Line(400,430,370,435)


' Player.
visible obj = Object(472, 632, 16)
obj.dx = 0
obj.dy = 0


############  to calculate FPS  ##########################
visible framecount,lasttime = 999,fps,frametime = 0,starttime = 0,endtime = 0,min_fps = 99999,max_fps = 0
##########################################################

while not keydown(KEY_ESCAPE)
###  for FPS calc #########
framecount = framecount + 1
starttime = clock()
###########################
    ' Rotate obstacles.
    circle1.SetAngle(circle1.Angle() + rad(3))
    circle2.SetAngle(circle2.Angle() + rad(3))
    circle3.SetAngle(circle3.Angle() + rad(3))
    circle4.SetAngle(circle4.Angle() + rad(3))
    circle5.SetAngle(circle5.Angle() + rad(3))
    circle6.SetAngle(circle5.Angle() + rad(3))
    circle7.SetAngle(circle5.Angle() + rad(3))
   
set mouse off   
if obj.y >= 624 and obj.x < 440 then game_over()   


    obj.x = obj.x + obj.dx
    obj.y = obj.y + obj.dy


    '-------   left flipper  ------------
    if keydown(KEY_A,true) and left_flipper_timer <= 0
        left_flipper_timer = 16
    endif
    if left_flipper_timer > 0
        left_flipper_timer = left_flipper_timer - 1
        l_flipper.SetAngle(max(l_flipper.Angle() - rad(4),-1.2))
    elseif left_flipper_timer <= 0
        l_flipper.SetAngle(0)
    endif
 
    PushOut(obj, left_flipper)
    if obj.pdy < 0
    if l_flipper.Angle() <> 0
        obj.dy = - 10
        left_ang = angle_rad(l_flipper.trans[1][0],l_flipper.trans[1][1],l_flipper.trans[2][0],l_flipper.trans[2][1])
        left_vecs = get_vecs(left_ang)
        obj.dx = obj.dx + left_vecs[0] * 16'arbitrary figure
    endif
    endif
    '==================================
    '-------   right flipper  ------------
    if keydown(KEY_D,true) and right_flipper_timer <= 0
        right_flipper_timer = 16
    endif
    if right_flipper_timer > 0
        right_flipper_timer = right_flipper_timer - 1
        r_flipper.SetAngle(max(r_flipper.Angle() + rad(4),-1.2))
    elseif right_flipper_timer <= 0
        r_flipper.SetAngle(0)
    endif

    PushOut(obj, right_flipper)
    if obj.pdy < 0
    if r_flipper.Angle() <> 0
        obj.dy = - 10
       right_ang = angle_rad(r_flipper.trans[2][0],r_flipper.trans[2][1],r_flipper.trans[1][0],r_flipper.trans[1][1])
        right_vecs = get_vecs(right_ang)
        obj.dx = obj.dx + right_vecs[0] * 32'arbitrary figure
    endif
    endif
    '==================================
    PushOut(obj, bouncers)' make them bounce a little
    if obj.pdx <> 0 or obj.pdy <> 0
       obj.dx = obj.dx + obj.pdx*12'arbitrary figure
       obj.dy = obj.dy + obj.pdy*12 'arbitrary figure
    endif

 
   
    PushOut(obj, lines)
  ' 
   if obj.y < 180 and obj.x > 240  and obj.dx < 0' help with initial lauch of ball
        if obj.pdx <> 0 or obj.pdy <> 0
            obj.dx = obj.dx + obj.pdx*4'arbitrary figure
         
        endif
   endif
   if obj.y < 180 and obj.x < 240 ' help with initial lauch of ball
        if obj.pdx <> 0 or obj.pdy <> 0
            obj.dx = obj.dx + obj.pdx*0.3'arbitrary figure
            obj.dy = obj.dy + obj.pdy*1
        endif
   endif
    ' obj.pdx and obj.pdy has now been set to the average "push direction" caused by all lines
    ' pushing the object around.
  
    ' Push direction y < 0 means the object is being pushed UP. In that case, set dy to 0.
    if obj.pdy < 0
        ' If push direction y < -0.25, let the player jump.
        if obj.pdy < -0.25 and keydown(KEY_UP, true) and obj.x >= 455
            obj.dy = -14
       endif
    endif
    ' Apply gravity.
    obj.dy = min(obj.dy + 0.1, 6)
    ' So ... stuff to dx.
    obj.dx = obj.dx + obj.pdx*0.25 ' I have no idea
    obj.dx = obj.dx*0.95
   
    PushOut(obj, circles)
        if obj.pdx <> 0 or obj.pdy <> 0
        score = score + 10
        obj.dx = obj.pdx * 8'arbitrary
        obj.dy = obj.pdy * 8'arbitrary
    endif
    
    set color 0, 0, 0
    cls
    set color 255, 255, 255
    DrawLines(lines)
    DrawLines(circles)
    DrawLines(bouncers)
    DrawLines(left_flipper)
    DrawLines(right_flipper)
    obj.Draw()
   
    set caret 20,10
    set justification left
    write "SCORE : " + score;wln
  

'    set caret 460,10
'    set justification right
'    write "FPS = " + str(fps);wln

'    write "MIN_FPS = " + str(min_fps);wln
'    write "MAX_FPS = " + str(max_fps);wln

    redraw
    fwait 60
   
    #######  FPS calc  ############################
endtime = clock()
frametime = frametime + endtime - starttime
if frametime > 1000 # 1 second
    fps = framecount
    framecount = 0
    frametime = 0
    if fps < min_fps then min_fps = fps
    if fps > max_fps then max_fps = fps
endif
################################################
wend

function DrawLines(lines)
    foreach ln in lines  draw line ln[0], ln[1], ln[2], ln[3]
endfunc


function Object(x, y, r)
    return [x: x, y: y, r: r, rsqr: r*r, pdx: 0, pdy: 0,
            Draw: function(); draw ellipse .x, .y, .r, .r, false; endfunc]
endfunc

' Pushout
' -------
function PushOut(obj, lines)
    tests = 4
    obj.pdx = 0
    obj.pdy = 0
    for i = 1 to tests
        col = false
        foreach ln in lines
            dp = max(0, min(ln[6], (obj.x - ln[0])*ln[4] + (obj.y - ln[1])*ln[5]))
            px = ln[0] + dp*ln[4]; py = ln[1] + dp*ln[5]
            dx = obj.x - px; dy = obj.y - py
            d = dx*dx + dy*dy
            if d < obj.rsqr
                k = 1/sqr(d)
                obj.x = px + dx*k*obj.r
                obj.y = py + dy*k*obj.r
                obj.pdx = obj.pdx + dx*k
                obj.pdy = obj.pdy + dy*k
                col = true
         
            endif
        next
        if not col break
    next
    if obj.pdx or obj.pdy
        k = 1/sqr(obj.pdx*obj.pdx + obj.pdy*obj.pdy)
        obj.pdx = obj.pdx*k
        obj.pdy = obj.pdy*k
    endif
endfunc

' Polygon
' -------
' Return polygon at position x, y.
' Polygon
' -------
' Return polygon at position x, y.
'function Polygon(points, x, y, closed,rotation_point_x,rotation_point_y)
function Polygon(points, x, y, closed,rotation_point)
'========================================================================
'Note -  added fifth argument to Polygon - 0 rotates around center point, or
'use a 2 part table to rotate around any point in the polygon - i.e. [32,34]
' to rotate around the fourth point in the polygon array here :-
'l_flipper = Polygon([0,0,10,0,32,24,32,34,22,34,0,10], 320, 240, true,[32,34])
'========================================================================

    p = [trans: unset, org: [], x: x, y: y, a: 0, cx:0, cy: 0]
    pcount = sizeof(points)/2
    centerX = 0
    centerY = 0
    if closed  n = pcount - 1
    else    n = pcount - 2
    for i = 0 to n
        j = (i + 1)%pcount
        p.org[sizeof(p.org)] = Line(
                points[i*2], points[i*2 + 1],
                points[j*2], points[j*2 + 1])
    next
    for i = 0 to pcount - 1
        if rotation_point = 0
            p.cx = p.cx + points[i*2]; p.cy = p.cy + points[i*2 + 1]
        else
            p.cx = rotation_point[0] * pcount 'rotation_point_x
            p.cy = rotation_point[1] * pcount ' rotation_point_y
        endif
    next

    p.cx = p.cx/pcount; p.cy = p.cy/pcount
    p.trans = copy(p.org)
  
    ' AddTo
    ' -----
    ' Add to list of lines.
    p.AddTo = function(lines)
        foreach ln in .trans  lines[sizeof(lines)] = ln
    endfunc

    ' X
    ' -
    ' Return x coordinate.
    p.X = function()
        return .x
    endfunc
  
    ' Y
    ' -
    ' Return y coordinate.
    p.Y = function()
        return .y
    endfunc
  
    ' Angle
    ' -----
    ' Return angle.
    p.Angle = function()
        return .a
    endfunc
  
    ' SetTransform
    ' ------------
    ' Set position and angle and apply.      
    p.SetTransform = function(x, y, angle)
        .x = x
        .y = y
        .a = angle
        .Transform()
    endfunc
  
    ' SetPosition
    ' -----------
    ' Set position and apply.
    p.SetPosition = function(x, y)
        .x = x
        .y = y
        .Transform()
    endfunc
  
    ' SetAngle
    ' --------
    ' Set angle and apply.
    p.SetAngle = function(angle)
        .a = angle
        .Transform()
    endfunc
  
    ' Transform
    ' ---------
    ' Update transformed polygon.
    p.Transform = function()
        RotateLines(.org, .trans, .cx, .cy, .a)
        foreach ln in .trans
            ln[0] = ln[0] + .x; ln[1] = ln[1] + .y
            ln[2] = ln[2] + .x; ln[3] = ln[3] + .y
            ln[4] = (ln[2] - ln[0])/ln[6]; ln[5] = (ln[3] - ln[1])/ln[6]
        next
    endfunc
  
    p.Transform()
  
    return p
  
    ' RotateLines
    ' -----------
    ' Helper.
    function RotateLines(srcLines, dstLines, aroundX, aroundY, angle)
        c = cos(angle); s = sin(angle)
        for i = 0 to sizeof(srcLines) - 1
            srcLn = srcLines[i]; dstLn = dstLines[i]
            x = srcLn[0] - aroundX; y = srcLn[1] - aroundY
            dstLn[0] = aroundX + x*c - y*s; dstLn[1] = aroundY + y*c + x*s
            x = srcLn[2] - aroundX; y = srcLn[3] - aroundY
            dstLn[2] = aroundX + x*c - y*s; dstLn[3] = aroundY + y*c + x*s
        next
    endfunc  
endfunc


' Rectangle
' ---------
function Rectangle(x, y, w, h)
    w = w - 1
    h = h - 1
    return Polygon([0, 0, w, 0, w, h, 0, h], x, y, true,0)
endfunc

' Line
' ----
' Return a new line.
function Line(x0, y0, x1, y1)
    ln = [x0, y0, x1, y1]
    dx = ln[2] - ln[0]; dy = ln[3] - ln[1]
    ln[6] = sqr(dx*dx + dy*dy)
    ln[4] = dx/ln[6]
    ln[5] = dy/ln[6]
    return ln
endfunc

'-------------------------
function angle_rad(x0,y0,x1,y1)
dx = x1 - x0
dy = y1 - y0
   
angle_r = atan2(dx,dy)
return angle_r
endfunc
'-----------------------
function get_vecs(angle_r)
vector_x =  -cos(angle_r - PI)
vector_y = sin(angle_r - PI)

return [vector_x,vector_y]
endfunc
'-----------------------
function game_over()
set caret 240,320
set justification center
write "GAME OVER"
'pln "GAME OVER"
redraw
wait 4000
obj.x = 472
obj.y = 632
score = 0
endfunc



RE: Small pinball game using circle-line collision routines from Marcus - Marcus - 05-01-2024

Dude, that's really cool Smile

Probably it would be possible to ... I'm not sure, just thinking out loud ... split up the movement of the flippers into tinier steps (just logically, while updating) to get better precision when they hit the ball. That way the flippers could move a lot faster without any loss of accuracy.

I know it's just a test. But still, it would probably not be that hard to use this in combination with 'draw poly image' or 'draw image xform' to get textured graphics. I'll look into that when I get the time Smile

Well done!


RE: Small pinball game using circle-line collision routines from Marcus - 1micha.elok - 05-02-2024

(05-01-2024, 02:30 PM)kevin Wrote: ... an experiment with the terrific circle-line functions that Marcus provided a while back...
Control the flippers with the A and D keys, and launch the ball using the UP arrow.

It's awesome. Pinball game in N7, great !
Insert Coins to continue the game...

Big Grin Big Grin Big Grin