Author Topic: Tag! v2 - the chasing game  (Read 4466 times)

0 Members and 1 Guest are viewing this topic.

Offline dajan

  • Newbie
  • Posts: 41
    • View Profile
Tag! v2 - the chasing game
« on: March 16, 2019, 04:14:54 pm »
Greetings,

I accidentally came up to QB64 site and it immediately recalled me a little game I made years ago in the old DOS version of QBasic.
Found the code, tried to run it in QB64 and it worked just fine in Windows 10.  Anyway, I remembered there were few things, that frustrated me back then, like bad keyboard control (via INKEY$), low resolution screen modes, bad timing control. So just a quick look into wiki manual - there are some pretty interesting new commands, wonder how would they improve the old code experience...
In a minute, few code lines are replaced with _KEYDOWN, _DISPLAY, _LIMIT and voila! Instantly the game becomes completely fluid and responsive, just like I desired back then!

In short, about 10 days later I ended up with totaly different / enhanced version of the game, with more colors, more players, more balls, higer framerate, AI implemented, gamepad support and more. The code of the new version I'd like to share is all commented, just some variable names remained in Slovak language, their meanings are mostly explained, though.

 

Code: QB64: [Select]
  1. '--------constants----------------------------------------------------------------------------------------------------------------------------------
  2. CONST DEBUGMODE = 0 'Initial state of debug mode
  3. CONST FPS = 100 'Set target frames per seconds to 100 for proper physicss speed
  4. CONST RES_X = -1 'Screen horizontal resolution, set to -1 to match the dsektop
  5. CONST RES_Y = -1 'Screen vertical resolution, set to -1 to match the dsektop
  6. CONST FULLSCREENMODE = 1 'set 1 for fullscreen on startup, other value to window mode
  7. CONST RES_X_TARG = 1920 'target vertical resolution
  8. CONST MAX_COL = 247 'max. color number to use of 255
  9. CONST PLAYER_COUNT_INI = 4 'Initial players count
  10. CONST POCET_GUL_INI = 20 'Initial non player balls count
  11. CONST PLAYER_COUNT_MAX = 32 'Maximum players count
  12. CONST POCET_GUL_MAX = 512 'Maximal balls count
  13. CONST STRAT_COUNT_MAX = 20 'Maximum players strategies
  14. CONST PLAY_BALL_R = 20 'Players ball size
  15. CONST V_MAX = 300 / FPS 'Max player ball velocity
  16. CONST ACC_X = 10 / FPS 'Balls acceleration X
  17. CONST ACC_Y = 10 / FPS 'Balls accelereation Y
  18. CONST FRICTION = 0.998 'Space fricition coeffifcient
  19. CONST ENERGY_ALL_MIN = 600 'min.starting energy of all players together
  20. CONST ENERGY_PLAYER_MIN = 50 'min. player's starting energy
  21. CONST ENERGY_PLAYER_MAX = 200 'max. player's starting energy
  22. CONST ENERGY_DEC = 5 / FPS 'ball energy decrement
  23. CONST REACTIME_AVG_NORMAL = 0 * FPS 'Average AI reaction time in sec*FPS
  24. CONST CHYBASMERU_NORMAL = 0.0 'Improper reaction rate
  25. CONST PRIESTOR_MIN = PLAY_BALL_R + 100
  26. CONST ROUNDSTART_DELAY = 3 'delay in seconds after round end to accept resume
  27. CONST FREEZE_TIME = 1.0 * FPS 'player freeze time after getting tag in seconds
  28. CONST SCORE_POS_X = 50 'Score horizontal draw position
  29. CONST SCORE_DELTA_Y = 13 'Score draw - vertictal offset from screen top
  30. CONST SCORE_DELTA_X = 350 'Score draw - players score horizontal offset
  31. CONST SCORE_NAME_LEN = 120 'Score draw - horizontal space for player name
  32. CONST BOOST_V_KOEF = 1.0 'boost factor of extra maximum speed
  33. CONST BOOST_TRVANIE = 1 * FPS 'boost duration
  34. CONST BOOST_TIME = 1 * FPS 'min time between boosts in sec*fps
  35. CONST MENUCURSOR_COLOR = 15 'Menu cursor color
  36. CONST MENU_PLAYERS_COUNT = 8 'Number of players in menu
  37. CONST FONT_W = 8 'Font width in pixels
  38. CONST FONT_H = 16 'Font height in pixels
  39. CONST GAMESTICK_DEADZONE = 0.05 'gamepad stick axis maximum value considered as zero
  40.  
  41. '--------type definitions----------------------------------------------------------------------------------------------------------------------------
  42. TYPE vektor 'general 2D vector type
  43.     x AS SINGLE
  44.     y AS SINGLE
  45.  
  46. TYPE tstav 'game world state type  (cca 100B)
  47.     minx AS INTEGER 'world - minimum x position
  48.     maxx AS INTEGER 'world - maximum x position
  49.     miny AS INTEGER 'world - minimum y position
  50.     maxy AS INTEGER 'world - maximum y position
  51.     vmax AS SINGLE 'maximum unboosted ball velocity
  52.     maxcol AS INTEGER 'world - maximum of colors
  53.     pocetgulzvoleny AS INTEGER 'number of balls desired
  54.     pocetgul AS INTEGER 'number of balls actual
  55.     pocetgulx AS INTEGER 'number of balls in bottom row
  56.     pocetguly AS INTEGER 'number of balls in left column
  57.     pocethracov AS INTEGER 'number of players
  58.     pocetgamepadov AS INTEGER 'number of gamepads detected
  59.     pocetkontrolerov AS INTEGER 'number of game controllers
  60.     energy_allmin AS SINGLE 'all players starting energy sum
  61.     energy_playermin AS SINGLE 'minimum starting energy of player
  62.     energy_playermax AS SINGLE 'maximum starting energy of player
  63.     energy_player_start AS SINGLE 'player's starting energy
  64.     energydec AS SINGLE 'energy decrease unit
  65.     baljeto AS INTEGER 'actual tagged ball idx
  66.     balbudeto AS INTEGER 'successor tagged ball idx
  67.     vitaz AS INTEGER 'winner player idx
  68.     koniec_kola AS INTEGER 'end of round flag
  69.     koniec_hry AS INTEGER 'end of game flag (0 or 1)
  70.     exituj AS INTEGER 'exit game flag (0 or 1)
  71.     odpor AS SINGLE 'space friction
  72.     massallowed AS INTEGER 'calculate balls with different masses
  73.     pauza AS LONG 'game pause flag (0 or 1)
  74.     fps AS SINGLE 'fps target set
  75.     fpsactual AS SINGLE 'fps achieved
  76.     fpscoef AS SINGLE 'fps actual/target
  77.     tstart AS DOUBLE 'timer start time
  78.     tend AS DOUBLE 'timer start time
  79.     zacinabal AS INTEGER 'initially tagged ball idx
  80.     printdebug AS INTEGER 'debug mode flag (0 or 1)
  81.     boostallowed AS INTEGER 'player boost mode allowed (0 or 1)
  82.  
  83. TYPE gula 'ball  type  (cca 90B)
  84.     i AS INTEGER 'balls id
  85.     x AS SINGLE 'balls y position
  86.     y AS SINGLE 'balls y position
  87.     r AS SINGLE 'radius
  88.     m AS SINGLE 'mass
  89.     v AS vektor 'velocity
  90.     vi AS vektor 'desired velocity
  91.     a AS vektor 'acceleration
  92.     col AS INTEGER 'balls color
  93.     riadena AS INTEGER 'is actually player controllable (0 or 1)
  94.     complayer AS INTEGER 'is computer player's ball (0 or 1)
  95.     protivnik AS INTEGER 'actual ball's enemy ball id
  96.     strat AS INTEGER 'balls behaviour strategy id
  97.     energia AS SINGLE 'players energy
  98.     freezedtime AS INTEGER 'actual time to unfreeze players control
  99.     smer_odchyl AS SINGLE 'actual deviance from intended direction in radians
  100.     klucka_time AS INTEGER 'actual time to start doging
  101.     klucka_uhol AS SINGLE 'actual dodge angle
  102.     klucka_trvanie AS INTEGER 'actual dodge duration
  103.     klucka_pocet AS INTEGER 'actual dodge count
  104.     boost_time AS INTEGER 'actual time to boost allowed again
  105.     boost_trvanie AS INTEGER 'actual time to end boost
  106.     reactdrop_time AS INTEGER 'actual time to start reaction drop in frames
  107.     reactdrop_dur AS INTEGER 'actual time to stop reaction drop in frames
  108.     boostintended AS INTEGER 'intention to boost or not (1, 0)
  109.  
  110. TYPE tstrat 'balls behaviour strategy type
  111.     okraj AS INTEGER 'distance from screen border to turn
  112.     okraj_rot AS SINGLE 'distance from screen border to turn
  113.     chybasmeru AS SINGLE 'percentage of deviance from desired direction -- not used currently
  114.     react_lag AS INTEGER 'average reaction time in frames
  115.     klucka_avgtm AS SINGLE 'average time between dodging in fps +-50%
  116.     klucka_uhol AS SINGLE 'average dodge angle
  117.     klucka_trvanie AS INTEGER 'average dodge time +-50%
  118.     klucka_pocet AS INTEGER 'average dodge count
  119.     klucka_uholodch AS SINGLE 'dodge angle deviation percentage
  120.     detekcia_sin AS SINGLE 'sinus of frontal enemy detection angle
  121.     detekcia_vzdfull AS SINGLE 'distance of frontal enemy full detection
  122.     detekcia_vzdmin AS SINGLE 'distance of frontal enemy minimum detection
  123.     detekcia_rot AS SINGLE 'amount of rotation on enemy detection in radians
  124.     reactdrop_per AS INTEGER 'average reaction drop period in frames
  125.     reactdrop_dur AS INTEGER 'average reaction drop duration in frames
  126.     boost_rate AS SINGLE 'inclination to use boost in percent
  127.     boost_dist AS SINGLE 'enemy distance activating boost deciding
  128.     boost_chasesin AS SINGLE 'max. sinus of enemy to player velocity angle to apply chasing boost
  129.     mate_cosenmax AS SINGLE 'max. cosinus of (player-enemy vector to player velocity) to rotate to mate
  130.     mate_rot AS SINGLE 'amount of unit rotation towards nearest mate in rad per frame
  131.     mate_en_distmin AS SINGLE 'min. distance from enemy to apply rotation towards mate
  132.     mate_distmin AS SINGLE 'min. distance from mate to apply rotation towards mate
  133.     adrenalin_rate AS SINGLE 'percentage of reaction improved in adrenalin mode
  134.     adrenalin_energy AS SINGLE 'energy level precentage starting adrenalin mode
  135.  
  136. TYPE thrac 'player type
  137.     i AS INTEGER 'player index number
  138.     meno AS STRING * 10 'players name
  139.     skore AS INTEGER 'players score
  140.     klavesy AS INTEGER 'players control keys set index
  141.     bal AS INTEGER 'player driven ball idx
  142.     col AS INTEGER 'player's choosed color
  143.  
  144. TYPE klavesy 'players controller type
  145.     id AS INTEGER 'controllers id
  146.     devid AS INTEGER 'controllers device id  (0=unused)
  147.     typ AS INTEGER 'type of controller (0=unused, 1=keyboard, 2=mouse reserved, 3=gamepad, 4=AI)
  148.     nazov AS STRING * 25 'game controllers name
  149.     up AS LONG 'key code for up
  150.     down AS LONG 'keyp code for down
  151.     right AS LONG 'key code for right
  152.     left AS LONG 'key code for left
  153.     shoota AS LONG 'keyborad key code or gamepad button id
  154.     shootb AS LONG 'keyborad key code or gamepad button id
  155.     xaxis AS INTEGER 'gamepad analog x-asis id
  156.     yaxis AS INTEGER 'gamepad analog y-asis id
  157.     dxaxis AS INTEGER 'gamepad digital x-asis id
  158.     dyaxis AS INTEGER 'gamepad digital y-asis id
  159.     isup AS INTEGER 'controllers actual state for up
  160.     isdown AS INTEGER 'controllers actual state for down
  161.     isright AS INTEGER 'controllers actual state for right
  162.     isleft AS INTEGER 'controllers actual state for left
  163.     isshoota AS INTEGER 'controllers actual state for fire 1
  164.     isshootb AS INTEGER 'controllers actual state for fire 2
  165.     gvektor AS vektor 'controllers actual x and y axis state
  166.     buttonstate AS LONG 'all gamepads buttons cumulative state value
  167.     buttonstatechanged AS INTEGER '0 if no button state change, 1 if any button state changed
  168.     buttonstatetimer AS INTEGER
  169.  
  170. TYPE tmenu
  171.     title AS STRING * 10 'menu title use for nothing
  172.     items_count AS INTEGER 'menu items count
  173.     item_active AS INTEGER 'active item index
  174.     item_height AS INTEGER 'menu item height in pixels
  175.     cursor_col AS INTEGER 'menu cursor color
  176.     cursor_xpos AS INTEGER 'x position of cursors left side
  177.     ypos AS INTEGER 'y position of cursor
  178.     cursor_width AS INTEGER 'cursor width in pixels
  179.     menucode AS INTEGER 'stores code of selected action upon active menuitem
  180.     playercode AS INTEGER 'stores  player id of active menuitem
  181.  
  182. TYPE tgame 'game session properties --not applied yet
  183.     isnetgame AS _BYTE
  184.     isserver AS _BYTE
  185.     isclient AS _BYTE
  186.  
  187. '-----global variables-------------------------------------------------------------------------------------------------------------------------------
  188. REDIM SHARED bal(1 TO POCET_GUL_MAX + PLAYER_COUNT_MAX) AS gula 'balls
  189. DIM SHARED klav(1 TO PLAYER_COUNT_MAX) AS klavesy 'keyboard control sets
  190. DIM SHARED stav AS tstav 'game world state
  191. DIM SHARED strat(1 TO STRAT_COUNT_MAX) AS tstrat 'computer players strategies
  192. DIM SHARED hrac(1 TO PLAYER_COUNT_MAX) AS thrac 'players balls
  193. DIM SHARED menu AS tmenu
  194.  
  195. '----main program------------------------------------------------------------------------------------------------------------------------------------
  196. SitoGame 'run the game
  197.  
  198. '----------------------------------------------------------------------------------------------------------------------------------------------------
  199. SUB SetGlobalParameters 'set pre-game world parameters
  200.  
  201.     '-------------------------------set main world parameters----------------------------------------------------------------------------------------
  202.     pi = _PI
  203.     stav.pauza = 0
  204.     stav.fps = FPS
  205.     stav.fpsactual = FPS
  206.     stav.pocethracov = PLAYER_COUNT_INI
  207.     stav.pocetgulzvoleny = POCET_GUL_INI
  208.     stav.pocetgul = POCET_GUL_INI
  209.     stav.energy_allmin = ENERGY_ALL_MIN
  210.     stav.energy_playermin = ENERGY_PLAYER_MIN
  211.     stav.energy_playermax = ENERGY_PLAYER_MAX
  212.     stav.energydec = ENERGY_DEC
  213.     stav.printdebug = DEBUGMODE
  214.     stav.odpor = FRICTION
  215.     stav.minx = 0
  216.     stav.miny = 0
  217.     IF RES_X = -1 THEN stav.maxx = _DESKTOPWIDTH ELSE stav.maxx = RES_X
  218.     IF RES_Y = -1 THEN stav.maxy = _DESKTOPHEIGHT ELSE stav.maxy = RES_Y
  219.  
  220.     stav.vmax = V_MAX * stav.maxx / RES_X_TARG
  221.     stav.maxcol = MAX_COL
  222.     stav.boostallowed = 1
  223.     stav.massallowed = 0 'sticky balls problem if mass allowed unsolved yet
  224.     stav.energy_player_start = (stav.energy_allmin / stav.pocethracov) 'calculate  players energy
  225.     IF stav.energy_player_start < stav.energy_playermin THEN stav.energy_player_start = stav.energy_playermin
  226.     IF stav.energy_player_start > stav.energy_playermax THEN stav.energy_player_start = stav.energy_playermax
  227.  
  228.  
  229.     '-------------------------------set game controllers---------------------------------------------------------------------------------------------
  230.     FOR i = 1 TO UBOUND(klav) 'set all the other game controllers as unused
  231.         klav(i).typ = 0
  232.         klav(i).devid = 0
  233.     NEXT i
  234.  
  235.     'set default controllers
  236.     kid = 1 'CPU void controller
  237.     klav(kid).id = kid: klav(kid).devid = 1: klav(kid).nazov = "<CPU>": klav(kid).typ = 4
  238.  
  239.     kid = 2 'keyboard set 1 controller
  240.     klav(kid).id = kid: klav(kid).devid = 1: klav(kid).nazov = "<Arrow keys + LShift>": klav(kid).typ = 1
  241.     klav(kid).up = 18432: klav(kid).down = 20480: klav(kid).left = 19200: klav(kid).right = 19712
  242.     klav(kid).shoota = 100304: klav(kid).shootb = 100306
  243.  
  244.     kid = 3 'keyboard set 2 controller
  245.     klav(kid).id = kid: klav(kid).devid = 1: klav(kid).nazov = "<W,A,S,D + RShift>": klav(kid).typ = 1
  246.     klav(kid).up = 119: klav(kid).down = 115: klav(kid).left = 97: klav(kid).right = 100
  247.     klav(kid).shoota = 100303: klav(kid).shootb = 100305
  248.  
  249.     kid = 4 'keyboard set 3 controller
  250.     klav(kid).id = kid: klav(kid).devid = 1: klav(kid).nazov = "<I,J,K,L + Enter>": klav(kid).typ = 1
  251.     klav(kid).up = 105: klav(kid).down = 107: klav(kid).left = 106: klav(kid).right = 108
  252.     klav(kid).shoota = 13: klav(kid).shootb = 21248
  253.  
  254.     '-------------------------------set players------------------------------------------------------------------------------------------------------
  255.     FOR i = 1 TO UBOUND(hrac) 'initial setting for all players
  256.         hrac(i).bal = i
  257.         hrac(i).meno = "Player" + LTRIM$(STR$(i))
  258.         hrac(i).skore = 0
  259.         hrac(i).klavesy = 1
  260.         hrac(i).col = 32 + i * 3
  261.         bal(hrac(i).bal).complayer = 1
  262.     NEXT i
  263.  
  264.     'personalize some players initially
  265.     bal(hrac(1).bal).complayer = 0
  266.     hrac(1).klavesy = 2
  267.     hrac(1).meno = "Chuck"
  268.     hrac(2).meno = "Arnold"
  269.     hrac(3).meno = "Jean"
  270.     hrac(4).meno = "Bruce"
  271.     hrac(5).meno = "Dolph"
  272.     hrac(6).meno = "Sylvester"
  273.     hrac(7).meno = "Jet"
  274.     hrac(8).meno = "Dwayne"
  275.  
  276.     '-------------------------------set behaviour strategies------------------------------------------------------------------------------------------
  277.     'some void strategy
  278.     strat(1).okraj = PRIESTOR_MIN:
  279.     strat(1).okraj_rot = 0.1
  280.     strat(1).chybasmeru = 0.00:
  281.     strat(1).klucka_avgtm = 1 * FPS: strat(1).klucka_pocet = 5
  282.     strat(1).klucka_trvanie = 0.25 * FPS: strat(1).klucka_uhol = pi / 2: strat(1).klucka_uholodch = 0.2
  283.     strat(1).detekcia_sin = SIN(pi / 4): strat(1).detekcia_vzdfull = 500: strat(1).detekcia_vzdmin = 1000: strat(1).detekcia_rot = 0.5
  284.     strat(1).boost_dist = 200
  285.     strat(1).boost_chasesin = SIN(pi / 8)
  286.     strat(1).mate_cosenmax = -0.8 'cos(140/180*pi)
  287.     strat(1).mate_rot = 0.1 ' 3*pi /FPS
  288.     strat(1).mate_en_distmin = 5 * PLAY_BALL_R
  289.     strat(1).mate_distmin = 4 * PLAY_BALL_R
  290.  
  291.     'non dodging strategy
  292.     strat(2).okraj = PRIESTOR_MIN + 10
  293.     strat(2).okraj_rot = 0.1
  294.     strat(2).chybasmeru = 0.00
  295.     strat(2).reactdrop_per = 0 * FPS
  296.     strat(2).reactdrop_dur = 0 * FPS
  297.     strat(2).klucka_avgtm = 10 * FPS
  298.     strat(2).klucka_pocet = 0
  299.     strat(2).klucka_trvanie = 0 * FPS
  300.     strat(2).klucka_uhol = 0 * pi / 3
  301.     strat(2).klucka_uholodch = 0.2
  302.     strat(2).detekcia_sin = SIN(pi / 6)
  303.     strat(2).detekcia_vzdfull = 500
  304.     strat(2).detekcia_vzdmin = 1000
  305.     strat(2).detekcia_rot = 0.5
  306.     strat(2).boost_dist = 200
  307.     strat(2).boost_chasesin = SIN(pi / 8)
  308.     strat(2).mate_cosenmax = -0.8 'cos(140/180*pi)
  309.     strat(2).mate_rot = 0.1 ' 3*pi /FPS
  310.     strat(2).mate_en_distmin = 5 * PLAY_BALL_R
  311.     strat(2).mate_distmin = 4 * PLAY_BALL_R
  312.  
  313.     'dodging strategy - currently used for all AI players
  314.     strat(3).okraj = PRIESTOR_MIN + 10
  315.     strat(3).okraj_rot = 0.1
  316.     strat(3).chybasmeru = 0.30
  317.     strat(3).reactdrop_per = 0.8 * FPS
  318.     strat(3).reactdrop_dur = 0.4 * FPS
  319.     strat(3).klucka_avgtm = 5 * FPS
  320.     strat(3).klucka_pocet = 3
  321.     strat(3).klucka_trvanie = 2 * FPS
  322.     strat(3).klucka_uhol = pi * 2 / 3
  323.     strat(3).klucka_uholodch = 0.2
  324.     strat(3).detekcia_sin = SIN(pi / 6)
  325.     strat(3).detekcia_vzdfull = 300
  326.     strat(3).detekcia_vzdmin = 1500
  327.     strat(3).detekcia_rot = 0.5 '10 * pi / FPS
  328.     strat(3).boost_dist = 200
  329.     strat(3).boost_chasesin = SIN(pi / 8)
  330.     strat(3).mate_cosenmax = -0.8 'cos(140/180*pi)
  331.     strat(3).mate_rot = 0.1 ' 3*pi /FPS
  332.     strat(3).mate_en_distmin = 8 * PLAY_BALL_R
  333.     strat(3).mate_distmin = 4 * PLAY_BALL_R
  334.     strat(3).adrenalin_rate = 0.90
  335.     strat(3).adrenalin_energy = 0.70
  336.  
  337.     '-------------------------------set main menu-----------------------------------------------------------------------------------------------------
  338.     menu.item_active = 1
  339.     menu.items_count = 22
  340.     menu.cursor_col = 15
  341.     menu.cursor_width = 400
  342.     menu.item_height = FONT_H + 8
  343.     menu.cursor_xpos = INT((stav.maxx - menu.cursor_width) / 2)
  344.     menu.ypos = INT((stav.maxy - 500) / 2)
  345.  
  346. '-----------------------------------------------------------------------------------------------------------------------------------------------------
  347. SUB SitoGame 'main game sub
  348.     SetGlobalParameters
  349.     SetUpControllers
  350.     IF stav.pocetgamepadov > 0 THEN hrac(1).klavesy = stav.pocetkontrolerov 'set gamepad as default controller for player1
  351.  
  352.     SCREEN _NEWIMAGE(stav.maxx, stav.maxy, 256), , ,
  353.     IF FULLSCREENMODE = 1 THEN _FULLSCREEN
  354.     WINDOW (0, 0)-(stav.maxx, stav.maxy)
  355.  
  356.     '----------------------------------new game start-------------------------------------------------------------------------------------------------
  357.     DO
  358.         MainMenuScreen 'go to main menu
  359.  
  360.         IF stav.exituj <> 1 THEN 'new game starts here
  361.             '----------------------------------set the world on the new game start-------------------------------
  362.             stav.koniec_hry = 0
  363.             stav.pocetgul = stav.pocetgulzvoleny + stav.pocethracov
  364.             stav.pocetgulx = MATHROUND(SQR(4 / 3 * stav.pocetgul))
  365.             IF stav.pocetgulx < stav.pocethracov THEN stav.pocetgulx = stav.pocethracov
  366.             stav.pocetguly = _CEIL(stav.pocetgul / stav.pocetgulx)
  367.  
  368.             IF stav.pocethracov < 2 THEN stav.pocethracov = 2
  369.             FOR i = 1 TO stav.pocethracov
  370.                 hrac(i).skore = 0
  371.             NEXT i
  372.  
  373.             stav.zacinabal = hrac(INT(RND * stav.pocethracov + 1)).bal
  374.  
  375.             DO 'new round starts here
  376.                 '-------------------------------set the world on the new round start---------------------------
  377.                 stav.koniec_kola = 0
  378.                 stav.vitaz = 0
  379.                 stav.exituj = 0
  380.                 stav.baljeto = 0
  381.  
  382.                 RANDOMIZE TIMER
  383.                 coloffset = 104 + INT(RND * 10)
  384.                 colcoef = (1 + INT(RND * (246 - coloffset))) / stav.pocetgulx
  385.                 FOR i = 1 TO stav.pocetguly + 1 'set all balls
  386.                     FOR j = 1 TO stav.pocetgulx
  387.                         n = (i - 1) * stav.pocetgulx + j:
  388.                         IF n <= stav.pocetgul THEN
  389.                             ry = 20 + RND * INT(((stav.maxy - 100) / (stav.pocetguly + 1) - 25) / 2)
  390.                             rx = 10 + RND * INT(((stav.maxx - 100) / (stav.pocetgulx + 1) - 25) / 2)
  391.                             bcol = coloffset + j * colcoef
  392.                             bal(n).i = n
  393.                             bal(n).col = bcol
  394.                             bal(n).x = 100 + j * INT((stav.maxx - 100) / (stav.pocetgulx + 1))
  395.                             bal(n).y = 100 + i * INT((stav.maxy - 100) / (stav.pocetguly + 1))
  396.                             bal(n).r = rx * -(rx < ry) + ry * -(ry < rx)
  397.                             bal(n).m = 4 / 4 * pi * bal(n).r ^ 2
  398.                             bal(n).v.x = -10 / FPS: bal(n).v.y = -30 / FPS
  399.                             bal(n).a.x = ACC_X * stav.maxx / RES_X_TARG
  400.                             bal(n).a.y = ACC_Y * stav.maxx / RES_X_TARG
  401.                             bal(n).vi.x = -(bal(n).complayer = 1) * RND * stav.vmax - stav.vmax / 2
  402.                             bal(n).vi.y = -(bal(n).complayer = 1) * RND * stav.vmax - stav.vmax / 2
  403.                             bal(n).riadena = 0
  404.                             bal(n).energia = stav.energy_player_start
  405.                             bal(n).freezedtime = 0: bal(n).klucka_time = 0: bal(n).klucka_pocet = 0
  406.                             bal(n).klucka_trvanie = 0: bal(n).klucka_uhol = 0
  407.                             bal(n).boost_time = 0: bal(n).boost_trvanie = 0
  408.                         END IF
  409.                     NEXT j
  410.                 NEXT i
  411.  
  412.                 '---set players balls default atributtes--------------------------------------------------------
  413.                 FOR i = 1 TO stav.pocethracov
  414.                     hrac(i).bal = i
  415.                     bal(hrac(i).bal).riadena = 1
  416.                     bal(hrac(i).bal).r = PLAY_BALL_R * stav.maxx / RES_X_TARG
  417.                     bal(hrac(i).bal).m = 4 / 4 * pi * PLAY_BALL_R ^ 2 / 2 'Players have only half density
  418.                     bal(hrac(i).bal).col = hrac(i).col
  419.                     bal(hrac(i).bal).strat = 3
  420.                 NEXT i
  421.  
  422.                 '---decide initially tagged player-------------------------------------------------------------
  423.                 zacinalNaposledyBal = stav.zacinabal
  424.                 stav.balbudeto = stav.zacinabal
  425.                 DO
  426.                     stav.zacinabal = stav.zacinabal + 1
  427.                     IF stav.zacinabal > stav.pocetgulx THEN stav.zacinabal = 1
  428.                 LOOP UNTIL bal(stav.zacinabal).riadena = 1
  429.                 stav.baljeto = stav.zacinabal
  430.  
  431.                 FOR b = 1 TO stav.pocethracov
  432.                     bal(hrac(b).bal).protivnik = stav.baljeto * -(hrac(b).bal <> stav.baljeto)
  433.                 NEXT b
  434.                 bal(stav.zacinabal).protivnik = zacinalNaposledyBal
  435.  
  436.                 '----------------------------------game action loop--------------------------------------------
  437.                 DO
  438.                     IF stav.pauza = 1 THEN PauseGame 'game pause
  439.                     stav.tstart = TIMER(.001)
  440.                     _LIMIT stav.fps
  441.                     k = _KEYHIT
  442.                     IF k = 32 OR k = 102 THEN stav.pauza = 1
  443.                     CLS 1
  444.  
  445.                     '---debug code begin---------------------------------------------
  446.                     IF k = 98 THEN stav.printdebug = -(stav.printdebug = 0)
  447.                     IF stav.printdebug = 1 THEN DebugScreen k 'print debug info on screen
  448.                     '---debug code end-----------------------------------------------
  449.  
  450.                     DrawScore 'Draw players score on screen
  451.  
  452.                     FOR i = 1 TO stav.pocethracov
  453.                         ResolvePlayerControl bal(hrac(i).bal), klav(hrac(i).klavesy) 'resolve players control
  454.                         IF bal(hrac(i).bal).riadena = 1 THEN
  455.                             CalculatePlayerState bal(hrac(i).bal) 'calculate player status
  456.                         END IF
  457.                     NEXT i
  458.  
  459.                     FOR n = 1 TO stav.pocetgul
  460.                         PerformMovement bal(n) 'do ball movement
  461.                     NEXT n
  462.  
  463.                     FOR i = 1 TO stav.pocetgul
  464.                         FOR j = i + 1 TO stav.pocetgul
  465.                             BounceTest bal(i), bal(j) 'Test balls bounce
  466.                         NEXT j
  467.                     NEXT i
  468.  
  469.                     FOR n = 1 TO stav.pocetgul
  470.                         DrawBall bal(n) 'draw ball
  471.                     NEXT n
  472.  
  473.                     _DISPLAY 'Turn video page over
  474.                     stav.tend = TIMER(.001)
  475.                     CalculateFps
  476.                 LOOP UNTIL k = 27 OR stav.koniec_kola = 1
  477.                 '---------------------------end of game action loop---------------------------------------------
  478.  
  479.                 IF k = 27 THEN
  480.                     stav.koniec_hry = 1
  481.                 END IF
  482.                 IF stav.vitaz <> 0 THEN
  483.                     winstr1$ = "....and the winner of this round is... " + RTRIM$(hrac(stav.vitaz).meno) + " !"
  484.                     winstr2$ = "Press <fire> for the next round"
  485.                     COLOR bal(hrac(stav.vitaz).bal).col
  486.                     _PRINTSTRING ((stav.maxx - LEN(winstr1$) * 10) / 2, (stav.maxy - 50) / 2), winstr1$
  487.                     _DISPLAY
  488.                     _DELAY ROUNDSTART_DELAY
  489.                     _PRINTSTRING ((stav.maxx - LEN(winstr2$) * 10) / 2, (stav.maxy - 50) / 2 + 50), winstr2$
  490.                     _DISPLAY
  491.                 END IF
  492.                 anyHumanPlayer = 0
  493.                 FOR i = 1 TO stav.pocethracov
  494.                     IF bal(hrac(i).bal).complayer = 0 THEN anyHumanPlayer = 1
  495.                 NEXT i
  496.                 resumegame = 0
  497.                 DO
  498.                     _LIMIT stav.fps
  499.                     FOR i = 1 TO stav.pocethracov
  500.                         IF klav(hrac(i).klavesy).typ = 3 THEN ReadGamepad klav(hrac(i).klavesy)
  501.                         IF klav(hrac(i).klavesy).isshoota THEN resumegame = 1
  502.                         IF klav(hrac(i).klavesy).isshootb THEN stav.koniec_hry = 1
  503.                         IF klav(hrac(i).klavesy).typ = 1 THEN IF _KEYDOWN(klav(hrac(i).klavesy).shoota) THEN resumegame = 1
  504.                     NEXT i
  505.                     k = _KEYHIT
  506.                 LOOP UNTIL (k = 27) OR (resumegame = 1) OR (anyHumanPlayer = 0) OR (stav.koniec_hry = 1)
  507.             LOOP UNTIL k = 27 OR stav.koniec_hry = 1
  508.             '---------------------------end of round-----------------------------------------------------------
  509.         END IF
  510.         '---------------------------end of game------------------------------------------------------------
  511.     LOOP UNTIL stav.exituj = 1
  512.     CLS 1
  513.  
  514. '----------------------------------------------------------------------------------------------------------------------------------------------------
  515. SUB SetUpControllers 'detect gamepads and set them as game controllers
  516.     DIM klav_volne AS INTEGER, devices AS INTEGER
  517.     DIM dev AS STRING
  518.  
  519.     FOR i = 1 TO UBOUND(klav) 'dispose all gamepad constrollers
  520.         IF klav(i).typ = 3 THEN
  521.             klav(i).typ = 0
  522.             klav(i).devid = 0
  523.         END IF
  524.     NEXT i
  525.     klav_volne = 1
  526.     stav.pocetgamepadov = 0
  527.     DO
  528.         klav_volne = klav_volne + 1
  529.     LOOP UNTIL klav(klav_volne).typ = 0 OR klav_volne = UBOUND(klav)
  530.     klav_used = klav_volne - 1
  531.     devices = _DEVICES 'must be read prior to other device functions usage
  532.     FOR i = 1 TO devices
  533.         dev = _DEVICE$(i)
  534.         IF INSTR(dev, "[CONTROLLER]") > 0 AND INSTR(dev, "[AXIS]") > 0 AND klav_volne <= UBOUND(klav) THEN 'set new controller if gamepad
  535.             klav(klav_volne).id = klav_volne
  536.             klav(klav_volne).devid = i
  537.             klav(klav_volne).typ = 3
  538.             klav(klav_volne).nazov = "<Gamepad" + LTRIM$(STR$(klav_volne - klav_used)) + ">"
  539.             klav(klav_volne).xaxis = 1
  540.             klav(klav_volne).yaxis = 2
  541.             klav(klav_volne).dxaxis = 6
  542.             klav(klav_volne).dyaxis = 7
  543.             klav(klav_volne).shoota = 1
  544.             klav(klav_volne).shootb = 8
  545.             klav_volne = klav_volne + 1
  546.             stav.pocetgamepadov = stav.pocetgamepadov + 1
  547.         END IF
  548.     NEXT i
  549.     stav.pocetkontrolerov = klav_volne - 1
  550.  
  551. '----------------------------------------------------------------------------------------------------------------------------------------------------
  552. SUB MainMenuScreen 'perform choises in main menu
  553.     vys = FONT_H
  554.     sir = FONT_W
  555.     menu.items_count = 22
  556.  
  557.     DO
  558.         CLS 1
  559.         PrintMenuItem 0, menu, 0, 0, 0, "Tag! v.2", 0, 0, -3, 13
  560.         PrintMenuItem 0, menu, 0, 0, 0, "Daniel Janec (2019)", 0, 0, -2, 20
  561.         PrintMenuItem 1, menu, 6, 0, 0, "Play <Space>", 0, 0, 0, 7
  562.         PrintMenuItem 2, menu, 1, 0, 0, "Refresh gamepads detected: " + STR$(stav.pocetgamepadov), 0, 0, 0, 7
  563.         PrintMenuItem 3, menu, 2, 1, 1, hrac(1).meno + " controls: " + klav(hrac(1).klavesy).nazov, 150, -10 * sir, 1, hrac(1).col
  564.         PrintMenuItem 4, menu, 3, 1, 1, "              color:" + STR$(hrac(1).col), 150, -10 * sir, 1, hrac(1).col
  565.         PrintMenuItem 5, menu, 2, 2, 1, hrac(2).meno + " controls: " + klav(hrac(2).klavesy).nazov, 150, -10 * sir, 1, hrac(2).col
  566.         PrintMenuItem 6, menu, 3, 2, 1, "              color:" + STR$(hrac(2).col), 150, -10 * sir, 1, hrac(2).col
  567.         PrintMenuItem 7, menu, 2, 3, 1, hrac(3).meno + " controls: " + klav(hrac(3).klavesy).nazov, 150, -10 * sir, 1, hrac(3).col
  568.         PrintMenuItem 8, menu, 3, 3, 1, "              color:" + STR$(hrac(3).col), 150, -10 * sir, 1, hrac(3).col
  569.         PrintMenuItem 9, menu, 2, 4, 1, hrac(4).meno + " controls: " + klav(hrac(4).klavesy).nazov, 150, -10 * sir, 1, hrac(4).col
  570.         PrintMenuItem 10, menu, 3, 4, 1, "              color:" + STR$(hrac(4).col), 150, -10 * sir, 1, hrac(4).col
  571.         PrintMenuItem 11, menu, 2, 5, 1, hrac(5).meno + " controls: " + klav(hrac(5).klavesy).nazov, 150, -10 * sir, 1, hrac(5).col
  572.         PrintMenuItem 12, menu, 3, 5, 1, "              color:" + STR$(hrac(5).col), 150, -10 * sir, 1, hrac(5).col
  573.         PrintMenuItem 13, menu, 2, 6, 1, hrac(6).meno + " controls: " + klav(hrac(6).klavesy).nazov, 150, -10 * sir, 1, hrac(6).col
  574.         PrintMenuItem 14, menu, 3, 6, 1, "              color:" + STR$(hrac(6).col), 150, -10 * sir, 1, hrac(6).col
  575.         PrintMenuItem 15, menu, 2, 7, 1, hrac(7).meno + " controls: " + klav(hrac(7).klavesy).nazov, 150, -10 * sir, 1, hrac(7).col
  576.         PrintMenuItem 16, menu, 3, 7, 1, "              color:" + STR$(hrac(7).col), 150, -10 * sir, 1, hrac(7).col
  577.         PrintMenuItem 17, menu, 2, 8, 1, hrac(8).meno + " controls: " + klav(hrac(8).klavesy).nazov, 150, -10 * sir, 1, hrac(8).col
  578.         PrintMenuItem 18, menu, 3, 8, 1, "              color:" + STR$(hrac(8).col), 150, -10 * sir, 1, hrac(8).col
  579.         PrintMenuItem 19, menu, 8, 0, 1, "Player starting energy: " + STR$(stav.energy_player_start), 100, -10 * sir, 2, 7
  580.         PrintMenuItem 20, menu, 4, 0, 1, "    Number of players: " + STR$(stav.pocethracov), 100, -10 * sir, 2, 7
  581.         PrintMenuItem 21, menu, 5, 0, 1, "          Balls count: " + STR$(stav.pocetgulzvoleny), 100, -10 * sir, 2, 7
  582.         PrintMenuItem 22, menu, 7, 0, 0, "Exit <Esc>", 0, 0, 2, 7
  583.         _DISPLAY
  584.  
  585.         DO 'read menu action
  586.             _LIMIT stav.fps
  587.             k = _KEYHIT
  588.             FOR i = 1 TO stav.pocetkontrolerov
  589.                 IF klav(i).typ = 3 THEN
  590.                     ReadGamepad klav(i)
  591.                     IF klav(i).buttonstatechanged THEN
  592.                         IF klav(i).isshoota THEN k = 13: klav(i).isshoota = 0
  593.                         IF klav(i).isup THEN k = 18432: klav(i).isup = 0
  594.                         IF klav(i).isdown THEN k = 20480: klav(i).isdown = 0
  595.                         IF klav(i).isright THEN k = 19712: klav(i).isright = 0
  596.                         IF klav(i).isleft THEN k = 19200: klav(i).isleft = 0
  597.                     END IF
  598.                 END IF
  599.             NEXT i
  600.         LOOP UNTIL k <> 0
  601.  
  602.         SELECT CASE k 'perform menu choice
  603.             CASE 18432 'up
  604.                 IF menu.item_active > 1 THEN menu.item_active = menu.item_active - 1
  605.             CASE 20480 'down
  606.                 IF menu.item_active < menu.items_count THEN menu.item_active = menu.item_active + 1
  607.             CASE 19200 'left
  608.                 SELECT CASE menu.menucode
  609.                     CASE 2 'players controller
  610.                         i = menu.playercode
  611.                         hrac(i).klavesy = hrac(i).klavesy - 1
  612.                         IF hrac(i).klavesy < 1 THEN hrac(i).klavesy = stav.pocetkontrolerov
  613.                     CASE 3 'players color
  614.                         i = menu.playercode
  615.                         hrac(i).col = hrac(i).col - 1
  616.                         IF hrac(i).col < 1 THEN hrac(i).col = stav.maxcol
  617.                     CASE 4 'players count
  618.                         IF stav.pocethracov > 2 THEN
  619.                             stav.pocethracov = stav.pocethracov - 1
  620.                         END IF
  621.                     CASE 5 'balls count
  622.                         IF stav.pocetgulzvoleny > 0 THEN
  623.                             stav.pocetgulzvoleny = stav.pocetgulzvoleny - 1
  624.                         END IF
  625.                     CASE 8 'starting energy
  626.                         IF stav.energy_player_start > 10 THEN
  627.                             stav.energy_player_start = stav.energy_player_start - 1
  628.                         END IF
  629.                 END SELECT
  630.             CASE 19712 'right
  631.                 SELECT CASE menu.menucode
  632.                     CASE 2 'players controller
  633.                         i = menu.playercode
  634.                         hrac(i).klavesy = hrac(i).klavesy + 1
  635.                         IF hrac(i).klavesy > stav.pocetkontrolerov THEN hrac(i).klavesy = 1
  636.                     CASE 3 'players color
  637.                         i = menu.playercode
  638.                         hrac(i).col = hrac(i).col + 1
  639.                         IF hrac(i).col > stav.maxcol THEN hrac(i).col = 1
  640.                     CASE 4 'players count
  641.                         IF stav.pocethracov < PLAYER_COUNT_MAX THEN
  642.                             stav.pocethracov = stav.pocethracov + 1
  643.                         END IF
  644.                     CASE 5 'balls count
  645.                         IF stav.pocetgulzvoleny < POCET_GUL_MAX THEN
  646.                             stav.pocetgulzvoleny = stav.pocetgulzvoleny + 1
  647.                         END IF
  648.                     CASE 8 'starting energy
  649.                         IF stav.energy_player_start < ENERGY_PLAYER_MAX THEN
  650.                             stav.energy_player_start = stav.energy_player_start + 1
  651.                         END IF
  652.                 END SELECT
  653.             CASE 13 'enter
  654.                 SELECT CASE menu.menucode
  655.                     CASE 1
  656.                         SetUpControllers
  657.                     CASE 2 'set new name
  658.                         COLOR
  659.                         hrac(menu.playercode).meno = InputName$(hrac(menu.playercode).meno, menu.cursor_xpos + 2 * sir, menu.ypos + menu.item_active * menu.item_height, hrac(menu.playercode).col)
  660.                     CASE 6 'start game
  661.                         k = 32
  662.                     CASE 7 'exit game
  663.                         k = 27
  664.                 END SELECT
  665.         END SELECT
  666.  
  667.         FOR i = 1 TO PLAYER_COUNT_MAX 'set AI controller type for computer players
  668.             IF klav(hrac(i).klavesy).typ = 4 THEN bal(hrac(i).bal).complayer = 1 ELSE bal(hrac(i).bal).complayer = 0
  669.         NEXT i
  670.  
  671.         IF menu.menucode = 4 AND (k = 19712 OR k = 19200) THEN 'after changing players count
  672.             stav.energy_player_start = INT(stav.energy_allmin / stav.pocethracov) 'calculate  players energy
  673.             IF stav.energy_player_start < stav.energy_playermin THEN stav.energy_player_start = stav.energy_playermin
  674.             IF stav.energy_player_start > stav.energy_playermax THEN stav.energy_player_start = stav.energy_playermax
  675.         END IF
  676.  
  677.     LOOP UNTIL k = 32 OR k = 27
  678.     IF k = 27 THEN
  679.         stav.exituj = 1
  680.     END IF
  681.  
  682. '----------------------------------------------------------------------------------------------------------------------------------------------------
  683. FUNCTION InputName$ (meno AS STRING, xpos AS INTEGER, ypos AS INTEGER, col AS INTEGER) 'get new player name from keyboard input
  684.     DIM tmpstr AS STRING * 14
  685.     res$ = ""
  686.     COLOR col
  687.     DO
  688.         DO
  689.             _LIMIT FPS
  690.             k = _KEYHIT
  691.         LOOP UNTIL k <> 0
  692.         IF k >= 33 AND k <= 126 AND LEN(res$) < LEN(meno) THEN res$ = res$ + CHR$(k)
  693.         IF k = 8 AND LEN(res$) > 0 THEN res$ = LEFT$(res$, LEN(res$) - 1)
  694.         IF res$ = "" THEN tmpstr = "<Type name>" ELSE tmpstr = res$
  695.         _PRINTSTRING (xpos, ypos), tmpstr
  696.         _DISPLAY
  697.     LOOP UNTIL k = 13 OR k = 27
  698.     IF k = 13 AND LEN(res$) > 0 THEN InputName$ = res$ ELSE InputName$ = meno
  699.  
  700. '----------------------------------------------------------------------------------------------------------------------------------------------------
  701. SUB PrintMenuItem (id AS INTEGER, amenu AS tmenu, menucode AS INTEGER, playercode AS INTEGER, rollable AS INTEGER, s AS STRING, fixedwidth AS INTEGER, deltax AS INTEGER, lineoff AS INTEGER, col AS INTEGER) 'print horizontaly centered text at line y
  702.     slen = LEN(RTRIM$(s)) * FONT_W
  703.     IF fixedwidth > 0 THEN slen = fixedwidth
  704.     px = INT((stav.maxx - slen) / 2) + deltax
  705.     py = amenu.ypos + (id - 1 + lineoff) * amenu.item_height
  706.     COLOR col
  707.     _PRINTSTRING (px, py), s
  708.     IF id = amenu.item_active THEN
  709.         COLOR amenu.cursor_col
  710.         LINE (amenu.cursor_xpos - 5, stav.maxy - (py - 5))-(amenu.cursor_xpos + 5 + amenu.cursor_width, stav.maxy - (py + FONT_H) - 5), amenu.cursor_col, B
  711.         IF rollable = 1 THEN
  712.             _PRINTSTRING (amenu.cursor_xpos - 10 - FONT_W, py), "<"
  713.             _PRINTSTRING (amenu.cursor_xpos + amenu.cursor_width + 10, py), ">"
  714.         END IF
  715.         amenu.menucode = menucode
  716.         amenu.playercode = playercode
  717.     END IF
  718.  
  719. '----------------------------------------------------------------------------------------------------------------------------------------------------
  720. SUB PauseGame 'pause gameplay
  721.     DO
  722.         _LIMIT stav.fps
  723.         k = _KEYHIT
  724.     LOOP UNTIL k = 32 OR k = 102 OR k = 27
  725.     IF k = 102 THEN stav.pauza = 1
  726.     IF k = 32 THEN stav.pauza = 0
  727.     IF k = 27 THEN
  728.         stav.pauza = 0
  729.     END IF
  730.  
  731. '----------------------------------------------------------------------------------------------------------------------------------------------------
  732. SUB DrawBall (baln AS gula) 'draw ball
  733.     koef = (10 + 90 * stav.printdebug) * FPS / 100
  734.     IF baln.i = stav.baljeto THEN
  735.         CIRCLE (baln.x, baln.y), baln.r * 2 / 3, 15
  736.     END IF
  737.     IF baln.freezedtime > 0 THEN
  738.         CIRCLE (baln.x, baln.y), baln.r * 4 / 5, baln.col
  739.     END IF
  740.     IF baln.riadena = 1 THEN
  741.         CIRCLE (baln.x, baln.y), baln.r * 1 / 3, baln.col
  742.         'PAINT STEP(0, 0), baln.col 'filling players heads with solid color tragically reduces perforamance
  743.         LINE (baln.x, baln.y)-(baln.x + baln.v.x * koef, baln.y + baln.v.y * koef), baln.col 'draw actual velocity
  744.         IF stav.printdebug = 1 THEN LINE (baln.x, baln.y)-(baln.x + baln.vi.x * koef, baln.y + baln.vi.y * koef), bal(baln.protivnik).col 'draw intended velocity
  745.     END IF
  746.     CIRCLE (baln.x, baln.y), baln.r, baln.col
  747.  
  748. '----------------------------------------------------------------------------------------------------------------------------------------------------
  749. SUB DrawScore 'Draw player score
  750.     FOR i = 1 TO stav.pocethracov
  751.         scxl = SCORE_DELTA_X * -(stav.maxx >= 4 * SCORE_DELTA_X) + stav.maxx / 4 * -(stav.maxx < 4 * SCORE_DELTA_X)
  752.         dx = (stav.maxx - 4 * scxl) / 2
  753.         scx = dx + scxl * ((i - 4 * INT((i - 1) / 4)) - 1)
  754.         scty = SCORE_DELTA_Y + 15 * INT((i - 1) / 4)
  755.         scly = stav.maxy - SCORE_DELTA_Y - 15 * INT((i - 1) / 4)
  756.         COLOR bal(hrac(i).bal).col
  757.         _PRINTSTRING (scx, scty - 10), hrac(i).meno + "-" + STR$(hrac(i).skore)
  758.         LINE (scx + SCORE_NAME_LEN, scly)-(scx + SCORE_NAME_LEN + bal(hrac(i).bal).energia, scly + 7), bal(hrac(i).bal).col, BF
  759.         IF bal(hrac(i).bal).energia <= 0 THEN
  760.             _PRINTSTRING (scx + SCORE_NAME_LEN, scty - 10), "OUT"
  761.         END IF
  762.     NEXT i
  763.  
  764. '----------------------------------------------------------------------------------------------------------------------------------------------------
  765. SUB CalculatePlayerState (baln AS gula) 'calculate players states
  766.     DIM od AS SINGLE
  767.     stav.fpscoef = stav.fps / stav.fpsactual
  768.     '-----------------------energy decreasing-------------------------------------
  769.     IF baln.freezedtime > 0 THEN
  770.         baln.freezedtime = baln.freezedtime - 1
  771.     ELSE
  772.         baln.energia = baln.energia - (stav.energydec * -(stav.baljeto = baln.i)) * stav.fpscoef
  773.     END IF
  774.     IF baln.energia <= 0 THEN
  775.         baln.riadena = 0
  776.         stav.baljeto = stav.balbudeto
  777.         stav.balbudeto = nearestPlayer(bal(stav.baljeto), 0)
  778.         pocetriadenych = 0
  779.         FOR i = 1 TO stav.pocethracov
  780.             IF bal(hrac(i).bal).riadena = 1 THEN
  781.                 pocetriadenych = pocetriadenych + 1
  782.                 priebeznyvitaz = i
  783.             END IF
  784.         NEXT i
  785.         IF pocetriadenych = 1 THEN
  786.             stav.koniec_kola = 1
  787.             stav.vitaz = priebeznyvitaz
  788.             hrac(stav.vitaz).skore = hrac(stav.vitaz).skore + 1
  789.             DrawScore
  790.         END IF
  791.     END IF
  792.     '-----------------------dodging time-----------------------------------------
  793.     IF baln.klucka_time = 0 AND baln.klucka_trvanie = 0 AND baln.klucka_pocet = 0 THEN
  794.         baln.klucka_time = INT(RND * strat(baln.strat).klucka_avgtm + strat(baln.strat).klucka_avgtm / 2) / stav.fpscoef
  795.         baln.klucka_pocet = strat(baln.strat).klucka_pocet
  796.     END IF
  797.     IF baln.klucka_time > 0 THEN
  798.         baln.klucka_time = baln.klucka_time - 1
  799.     END IF
  800.     IF baln.klucka_time = 0 AND baln.klucka_trvanie > 0 THEN
  801.         baln.klucka_trvanie = baln.klucka_trvanie - 1
  802.     END IF
  803.     IF baln.klucka_time = 0 AND baln.klucka_trvanie = 0 AND baln.klucka_pocet > 0 THEN
  804.         baln.klucka_pocet = baln.klucka_pocet - 1
  805.         baln.klucka_trvanie = INT(RND * strat(baln.strat).klucka_trvanie + strat(baln.strat).klucka_trvanie / 2) / stav.fpscoef
  806.         baln.klucka_uhol = RND * strat(baln.strat).klucka_uhol * (2 * strat(baln.strat).klucka_uholodch) + strat(baln.strat).klucka_uhol * (1 - strat(baln.strat).klucka_uholodch) * (3 - INT(RND * 2 + 1) * 2)
  807.     END IF
  808.     '-----------------------choose nearest enemy if tagged-----------------------
  809.     IF stav.baljeto = baln.i THEN
  810.         novyprotivnik = nearestPlayer(baln, 0)
  811.         IF novyprotivnik <> 0 THEN baln.protivnik = novyprotivnik
  812.         stav.balbudeto = baln.protivnik
  813.     END IF
  814.     '-----------------------boosting time----------------------------------------
  815.     IF baln.boost_time > 0 AND baln.boost_trvanie = 0 THEN baln.boost_time = baln.boost_time - 1
  816.     IF baln.boost_trvanie > 0 THEN baln.boost_trvanie = baln.boost_trvanie - 1
  817.  
  818.     '-----------------------reaction drop time-----------------------------------
  819.     IF baln.reactdrop_time > 0 THEN baln.reactdrop_time = baln.reactdrop_time - 1
  820.     IF baln.reactdrop_time <= 0 AND baln.reactdrop_dur > 0 THEN baln.reactdrop_dur = baln.reactdrop_dur - 1
  821.     IF baln.reactdrop_time <= 0 AND baln.reactdrop_dur <= 0 THEN
  822.         IF baln.energia < (stav.energy_player_start * strat(baln.strat).adrenalin_energy) THEN reactkoef = (1 - strat(baln.strat).adrenalin_rate) ELSE reactkoef = 1
  823.         baln.reactdrop_time = INT((RND * strat(baln.strat).reactdrop_per * reactkoef + strat(baln.strat).reactdrop_per * reactkoef / 2) / stav.fpscoef)
  824.         baln.reactdrop_dur = INT((RND * strat(baln.strat).reactdrop_dur * reactkoef + strat(baln.strat).reactdrop_dur * reactkoef / 2) / stav.fpscoef)
  825.         baln.smer_odchyl = (RND * strat(baln.strat).chybasmeru - strat(baln.strat).chybasmeru / 2) * pi 'apply random radian deviance from intended direction
  826.     END IF
  827.  
  828. '----------------------------------------------------------------------------------------------------------------------------------------------------
  829. FUNCTION nearestPlayer (baln AS gula, mateonly AS _BYTE) 'returns nearest opponent idx
  830.     vzdakt = 0
  831.     nearestPlayer = 0
  832.     FOR i = 1 TO stav.pocetgulx
  833.         IF bal(i).i <> baln.i AND bal(i).riadena = 1 AND (mateonly = 1 IMP i <> stav.baljeto) THEN
  834.             vzd = SQR((baln.x - bal(i).x) ^ 2 + (baln.y - bal(i).y) ^ 2)
  835.             IF nearestPlayer = 0 OR vzd < vzdakt THEN
  836.                 nearestPlayer = i
  837.                 vzdakt = vzd
  838.             END IF
  839.         END IF
  840.     NEXT i
  841.  
  842. '----------------------------------------------------------------------------------------------------------------------------------------------------
  843. SUB GenerateAIControl (baln AS gula) 'Decide computer player's control keys hits
  844.     DIM dd AS SINGLE, vid AS SINGLE, vd AS SINGLE, eid AS SINGLE, vnd AS SINGLE, ved AS SINGLE, did AS SINGLE, thit AS SINGLE
  845.     DIM rot_klucka AS SINGLE, rot_border AS SINGLE, rot_enemy AS SINGLE, rot_mate AS SINGLE
  846.     DIM siner AS SINGLE, coser AS SINGLE, sineri AS SINGLE, coseri AS SINGLE
  847.     DIM sinevr AS SINGLE, cosevr AS SINGLE, sinmt AS SINGLE, cosnmt AS SINGLE
  848.     DIM okrajrot AS SINGLE, enemy_dist_factor AS SINGLE, smerdookraju AS INTEGER, nearmate AS INTEGER
  849.     DIM d AS vektor, vi AS vektor, ei AS vektor, ve AS vektor, di AS vektor, dm AS vektor
  850.  
  851.     vi = baln.vi 'player's intended speed vector copy
  852.     vid = SQR(vi.x ^ 2 + vi.y ^ 2)
  853.     vi.x = vi.x / vid * stav.vmax
  854.     vi.y = vi.y / vid * stav.vmax
  855.     vd = SQR(baln.v.x ^ 2 + baln.v.y ^ 2)
  856.     ve = bal(baln.protivnik).v 'enemy speed vector copy
  857.     ved = SQR(ve.x ^ 2 + ve.y ^ 2)
  858.  
  859.     d.x = bal(baln.protivnik).x - baln.x 'player to enemy postition vector
  860.     d.y = bal(baln.protivnik).y - baln.y
  861.     dd = SQR(d.x ^ 2 + d.y ^ 2)
  862.     coser = (d.x * baln.v.x + d.y * baln.v.y) / (dd * vd)
  863.     siner = (d.x * baln.v.y - d.y * baln.v.x) / (dd * vd)
  864.     cosevr = (ve.x * baln.v.x + ve.y * baln.v.y) / (ved * vd)
  865.     sinevr = (ve.x * baln.v.y - ve.y * baln.v.x) / (ved * vd)
  866.  
  867.     '!EXPERIMENTAL CODE! - try more exact enemy future position vector, but  does not work so well as used approximation
  868.     'di.x = d.x + ve.x * d.x / ((((d.x / dd) + (ve.x / ved)) * stav.vmax) - ve.x) 'new intended direction to enemy
  869.     'di.y = d.y + ve.y * d.y / ((((d.y / dd) + (ve.y / ved)) * stav.vmax) - ve.y) 'new intended direction to enemy
  870.     '!EXPERIMENTAL CODE! end
  871.  
  872.     di.x = d.x + ve.x * dd / vd * ABS(siner) 'player to enemy approximate future position vector, future enemy position more relevant when enemy closer
  873.     di.y = d.y + ve.y * dd / vd * ABS(siner)
  874.     did = SQR(di.x ^ 2 + di.y ^ 2)
  875.     coseri = (di.x * baln.v.x + di.y * baln.v.y) / (did * vd)
  876.     sineri = (di.x * baln.v.y - di.y * baln.v.x) / (did * vd)
  877.  
  878.     rot_enemy = 0 'rotation away from enemy in rad
  879.     rot_border = 0 'rotation away from border in rad
  880.     rot_mate = 0 'rotation towards nearest mate in rad
  881.     rot_odchyl = 0 'baln.smer_odchyl * -(baln.reactdrop_dur = 1) 'apply direction error if react time
  882.     smerdookraju = 0 'is players direction to border
  883.  
  884.     IF NOT (stav.baljeto = baln.i) THEN 'decide flee action
  885.         enemy_dist_factor = 1 * -(dd <= strat(baln.strat).detekcia_vzdfull) + (1 - (dd - strat(baln.strat).detekcia_vzdfull) / _
  886.           (strat(baln.strat).detekcia_vzdmin - strat(baln.strat).detekcia_vzdfull)) * -(dd > strat(baln.strat).detekcia_vzdfull AND dd <= strat(baln.strat).detekcia_vzdmin)' factor=1 if enemy distance within treshold, then decreasing
  887.  
  888.         IF enemy_dist_factor > 0 THEN 'if enemy in detection distance, rotate away
  889.             rot_enemy = -(coseri > 0) * -(ABS(sineri) <= strat(baln.strat).detekcia_sin) * (-(sineri > 0) + (sineri < 0)) * strat(baln.strat).detekcia_rot * enemy_dist_factor * stav.fpscoef
  890.  
  891.             '!EXPERIMENTAL CODE! - try better rotation from enemy
  892.             'strat(baln.strat).detekcia_rot = 10 * pi / FPS 'TEMPORARY MODIFICATION
  893.             'cosevi = (di.x * vi.x + di.y * vi.y) / (did * vid)
  894.             'sinevi = (di.x * vi.y - di.y * vi.x) / (did * vid)
  895.             'cosvvi = (v.x * vi.x + v.y * vi.y) / (vd * vid)
  896.             'sinvvi = (v.x * vi.y - v.y * vi.x) / (vd * vid)
  897.             'IF (coseri > 0) AND (ABS(sineri) <= strat(baln.strat).detekcia_sin) THEN
  898.             '    rot_dir = -(sineri > 0) + (sineri < 0)
  899.             '    IF NOT ((cosevi > 0 AND (ABS(sinevi) <= strat(baln.strat).detekcia_sin)) AND NOT (rot_dir = (-(sinevi > 0) + (sinevi < 0)))) THEN
  900.             '        rot_enemy = rot_dir * strat(baln.strat).detekcia_rot * enemy_dist_factor * stav.fpscoef
  901.             '    END IF
  902.             'END IF
  903.             '!EXPERIMENTAL CODE! end
  904.  
  905.         END IF
  906.  
  907.         okrajrot = strat(baln.strat).okraj_rot 'border unit rotation in rad
  908.         IF baln.x < strat(baln.strat).okraj AND vi.x < 0 THEN
  909.             rot_border = (okrajrot * (vi.y >= 0) + okrajrot * -(vi.y < 0)) '* (1 - enemy_dist_factor)
  910.             smerdookraju = 1
  911.         END IF
  912.         IF baln.x > stav.maxx - strat(baln.strat).okraj AND vi.x > 0 THEN
  913.             rot_border = (okrajrot * -(vi.y >= 0) + okrajrot * (vi.y < 0)) '* (1 - enemy_dist_factor)
  914.             smerdookraju = 1
  915.         END IF
  916.         IF baln.y < strat(baln.strat).okraj AND vi.y < 0 THEN
  917.             rot_border = (okrajrot * (vi.x >= 0) + okrajrot * (vi.x < 0)) '* (1 - enemy_dist_factor)
  918.             smerdookraju = 1
  919.         END IF
  920.         IF baln.y > stav.maxy - strat(baln.strat).okraj AND vi.y > 0 THEN
  921.             rot_border = (okrajrot * (vi.x >= 0) + okrajrot * -(vi.x < 0)) '* (1 - enemy_dist_factor)
  922.             smerdookraju = 1
  923.         END IF
  924.     END IF
  925.  
  926.     rot_klucka = baln.klucka_uhol * -(baln.klucka_time = 0) * -(baln.klucka_trvanie = 1) * -(stav.baljeto <> baln.i) * _
  927.       (dd > strat(baln.strat).mate_distmin OR (coseri < 0 AND sineri >= 0 AND baln.klucka_uhol > 0 AND baln.klucka_uhol < pi) OR (coseri < 0 AND sineri <= 0 AND baln.klucka_uhol < 0 AND baln.klucka_uhol > -pi)) 'apply dodge if dodging time - TEMPORARY DISABLED!!!
  928.  
  929.  
  930.     IF baln.i = bal(stav.baljeto).protivnik THEN 'if ball being chased
  931.         nearmate = nearestPlayer(baln, 1)
  932.         IF nearmate > 0 THEN
  933.             dm.x = bal(nearmate).x - baln.x
  934.             dm.y = bal(nearmate).y - baln.y
  935.             dmd = SQR(dm.x ^ 2 + dm.y ^ 2)
  936.             cosnm = (dm.x * baln.v.x + dm.y * baln.v.y) / (dmd * vd)
  937.             sinnm = (dm.x * baln.v.y - dm.y * baln.v.x) / (dmd * vd)
  938.             IF (rot_border = 0) AND (dmd > strat(baln.strat).mate_distmin) AND (((coseri < strat(baln.strat).mate_cosenmax) AND (dd < strat(baln.strat).mate_en_distmin)) OR ((coseri < 0) AND (dd > strat(baln.strat).mate_en_distmin))) THEN
  939.                 rot_mate = ((sinnm > 0) * strat(baln.strat).mate_rot * stav.fpscoef - (sinnm <= 0) * strat(baln.strat).mate_rot * stav.fpscoef) 'rotate towards mate
  940.             END IF
  941.         END IF
  942.     END IF
  943.  
  944.     '---debug code begin---
  945.     'IF stav.printdebug THEN
  946.     '    COLOR baln.col
  947.     '    PRINT "baln.i  "; baln.i
  948.     '    PRINT "vi       "; vi.x; vi.y; vid
  949.     '    PRINT "d        "; d.x; d.y; dd
  950.     '    PRINT "di        "; di.x; di.y; did
  951.     '    PRINT "rot_border  "; rot_border
  952.     '    PRINT "rot_enemy   "; rot_enemy
  953.     '    PRINT "rot_mate    "; rot_mate
  954.     '    PRINT "okrajrot    "; okrajrot
  955.     '    PRINT "nearmate    "; nearmate
  956.     '    PRINT "dm          "; dm.x; dm.y; dmd
  957.     '    PRINT "cosnm;sinnm "; cosnm; sinnm
  958.     'END IF
  959.     '---debug code end---
  960.  
  961.     RotateVector vi, rot_border + rot_klucka + rot_odchyl + rot_enemy + rot_mate 'aplly all calculated rotations to intended velocity
  962.  
  963.     IF stav.baljeto = baln.i THEN 'chasing direction intended
  964.         IF baln.reactdrop_time > 0 THEN 'if time is up for reaction
  965.             baln.boostintended = -(-(coser > 0) AND -(cosevr > 0) AND -(ABS(sinevr) < strat(baln.strat).boost_chasesin) AND baln.boost_time = 0 AND smerdookraju = 0)
  966.             baln.vi.x = stav.vmax * di.x / did
  967.             baln.vi.y = stav.vmax * di.y / did
  968.         END IF
  969.     ELSE 'flee direction intended
  970.         baln.boostintended = -(-(coser < 0) AND baln.boost_time = 0 AND dd < (RND * strat(baln.strat).boost_dist + 2 * PLAY_BALL_R) AND smerdookraju = 0)
  971.         baln.vi.x = vi.x
  972.         baln.vi.y = vi.y
  973.     END IF
  974.  
  975. '----------------------------------------------------------------------------------------------------------------------------------------------------
  976. SUB ReadGamepad (klavn AS klavesy) 'read state of gamepad
  977.     di = _DEVICEINPUT(klavn.devid)
  978.     IF klavn.typ = 3 THEN
  979.         klavn.gvektor.x = _AXIS(klavn.xaxis)
  980.         klavn.gvektor.y = -_AXIS(klavn.yaxis)
  981.         klavn.isshoota = _BUTTON(klavn.shoota)
  982.         klavn.isshootb = _BUTTON(klavn.shootb)
  983.         klavn.isup = (_AXIS(klavn.dyaxis) = -1)
  984.         klavn.isdown = (_AXIS(klavn.dyaxis) = 1)
  985.         klavn.isleft = (_AXIS(klavn.dxaxis) = -1)
  986.         klavn.isright = (_AXIS(klavn.dxaxis) = 1)
  987.         IF klavn.gvektor.x > 0.5 THEN klavn.isright = -1
  988.         IF klavn.gvektor.x < -0.5 THEN klavn.isleft = -1
  989.         IF klavn.gvektor.y > 0.5 THEN klavn.isup = -1
  990.         IF klavn.gvektor.y < -0.5 THEN klavn.isdown = -1
  991.  
  992.         IF klavn.buttonstate <> (klavn.isshoota + klavn.isshootb * 2 + klavn.isup * 4 + klavn.isdown * 8 + klavn.isright * 16 + klavn.isleft * 32) THEN
  993.             klavn.buttonstatechanged = -1
  994.             klavn.buttonstatetimer = 50
  995.         ELSE
  996.             klavn.buttonstatechanged = 0
  997.         END IF
  998.         klavn.buttonstate = (klavn.isshoota + klavn.isshootb * 2 + klavn.isup * 4 + klavn.isdown * 8 + klavn.isright * 16 + klavn.isleft * 32)
  999.     END IF
  1000.     IF klavn.buttonstatetimer > 0 THEN klavn.buttonstatetimer = klavn.buttonstatetimer - 1
  1001.     IF klavn.buttonstatetimer = 0 THEN klavn.buttonstatechanged = -1
  1002.  
  1003. '----------------------------------------------------------------------------------------------------------------------------------------------------
  1004. SUB ResolvePlayerControl (baln AS gula, klavn AS klavesy) 'perform players control keys actions
  1005.     DIM vn AS vektor, gvector AS vektor
  1006.  
  1007.     IF baln.complayer = 0 THEN 'read human player controls
  1008.         IF klavn.typ = 1 THEN 'read keyboard control
  1009.             klavn.isright = _KEYDOWN(klavn.right)
  1010.             klavn.isleft = _KEYDOWN(klavn.left)
  1011.             klavn.isdown = _KEYDOWN(klavn.down)
  1012.             klavn.isup = _KEYDOWN(klavn.up)
  1013.             klavn.isshoota = _KEYDOWN(klavn.shoota)
  1014.             klavn.isshootb = _KEYDOWN(klavn.shootb)
  1015.             baln.vi.x = baln.v.x
  1016.             baln.vi.y = baln.v.y
  1017.         END IF
  1018.         IF klavn.typ = 3 THEN 'read gamepad control
  1019.             ReadGamepad klavn
  1020.             baln.vi.x = klavn.gvektor.x * stav.vmax
  1021.             baln.vi.y = klavn.gvektor.y * stav.vmax
  1022.  
  1023.             IF ABS(klavn.gvektor.x) > GAMESTICK_DEADZONE OR ABS(klavn.gvektor.y) > GAMESTICK_DEADZONE THEN
  1024.                 klavn.isright = (baln.v.x < baln.vi.x) 'set control keys status for desired direction
  1025.                 klavn.isleft = (baln.v.x > baln.vi.x)
  1026.                 klavn.isup = (baln.v.y < baln.vi.y)
  1027.                 klavn.isdown = (baln.v.y > baln.vi.y)
  1028.             END IF
  1029.             IF klavn.isshootb THEN stav.koniec_kola = 1: stav.koniec_hry = 1
  1030.         END IF
  1031.     ELSE 'get controls of computer player
  1032.         IF baln.riadena = 1 THEN GenerateAIControl baln
  1033.         klavn.isright = (baln.v.x < baln.vi.x) 'set control keys status for desired direction
  1034.         klavn.isleft = (baln.v.x > baln.vi.x)
  1035.         klavn.isup = (baln.v.y < baln.vi.y)
  1036.         klavn.isdown = (baln.v.y > baln.vi.y)
  1037.         klavn.isshoota = -baln.boostintended
  1038.     END IF
  1039.  
  1040.     IF baln.freezedtime <= 0 AND baln.riadena = 1 THEN 'apply acquired controls to player movement
  1041.         vn.x = baln.v.x
  1042.         vn.y = baln.v.y
  1043.  
  1044.         stav.fpscoef = stav.fps / stav.fpsactual
  1045.         IF klavn.isright THEN vn.x = vn.x + baln.a.x * stav.fpscoef 'accelerate player according to control keys states
  1046.         IF klavn.isleft THEN vn.x = vn.x - baln.a.x * stav.fpscoef
  1047.         IF klavn.isdown THEN vn.y = vn.y - baln.a.y * stav.fpscoef
  1048.         IF klavn.isup THEN vn.y = vn.y + baln.a.x * stav.fpscoef
  1049.         IF klavn.isshoota AND baln.boost_time = 0 AND stav.boostallowed = 1 THEN 'set boost state
  1050.             baln.boost_trvanie = BOOST_TRVANIE / stav.fpscoef
  1051.             baln.boost_time = BOOST_TIME / stav.fpscoef
  1052.         END IF
  1053.         vnd = SQR(vn.x ^ 2 + vn.y ^ 2) 'limit actual velocity to max. velocity
  1054.         IF vnd > stav.vmax THEN
  1055.             vn.x = vn.x * stav.vmax / vnd
  1056.             vn.y = vn.y * stav.vmax / vnd
  1057.         END IF
  1058.  
  1059.         IF baln.boost_trvanie > 0 THEN 'add some extra speed if boosted
  1060.             vn.x = vn.x + stav.vmax * BOOST_V_KOEF * (baln.boost_trvanie / BOOST_TRVANIE) * stav.fpscoef * vn.x / vnd
  1061.             vn.y = vn.y + stav.vmax * BOOST_V_KOEF * (baln.boost_trvanie / BOOST_TRVANIE) * stav.fpscoef * vn.y / vnd
  1062.         END IF
  1063.  
  1064.         baln.v.x = vn.x 'apply controler modified velocity
  1065.         baln.v.y = vn.y
  1066.     END IF
  1067.  
  1068. '----------------------------------------------------------------------------------------------------------------------------------------------------
  1069. SUB PerformMovement (baln AS gula) 'Calculate ball movement
  1070.     baln.x = baln.x + baln.v.x * stav.fpscoef
  1071.     baln.y = baln.y + baln.v.y * stav.fpscoef
  1072.     baln.v.x = baln.v.x * stav.odpor ^ stav.fpscoef
  1073.     baln.v.y = baln.v.y * stav.odpor ^ stav.fpscoef
  1074.     IF (baln.x + baln.r > stav.maxx) THEN
  1075.         baln.v.x = -baln.v.x
  1076.         baln.x = stav.maxx - baln.r
  1077.     END IF
  1078.     IF (baln.x - baln.r < stav.minx) THEN
  1079.         baln.v.x = -baln.v.x
  1080.         baln.x = stav.minx + baln.r
  1081.     END IF
  1082.     IF (baln.y + baln.r > stav.maxy) THEN
  1083.         baln.v.y = -baln.v.y
  1084.         baln.y = stav.maxy - baln.r
  1085.     END IF
  1086.     IF (baln.y - baln.r < stav.miny) THEN
  1087.         baln.v.y = -baln.v.y
  1088.         baln.y = stav.miny + baln.r
  1089.     END IF
  1090.  
  1091. '----------------------------------------------------------------------------------------------------------------------------------------------------
  1092. SUB BounceTest (bala AS gula, balb AS gula) 'Balls bounce test
  1093.     IF (bala.x - balb.x) * (bala.x - balb.x) + (bala.y - balb.y) * (bala.y - balb.y) <= (bala.r + balb.r + 0) * (bala.r + balb.r + 0) THEN
  1094.         PerformBounce bala, balb
  1095.         IF bala.riadena AND balb.riadena AND bala.freezedtime <= 0 AND balb.freezedtime <= 0 AND (stav.baljeto = bala.i OR stav.baljeto = balb.i) THEN 'balb.col = bal(bala.protivnik).col
  1096.             stav.baljeto = bala.i * -(stav.baljeto <> bala.i) + balb.i * -(stav.baljeto <> balb.i)
  1097.             bala.freezedtime = -(stav.baljeto = bala.i) * FREEZE_TIME / stav.fpscoef
  1098.             balb.freezedtime = -(stav.baljeto = balb.i) * FREEZE_TIME / stav.fpscoef
  1099.             bala.protivnik = balb.i
  1100.             balb.protivnik = bala.i
  1101.             FOR i = 1 TO stav.pocetgulx
  1102.                 IF bal(i).i <> stav.baljeto AND bal(i).riadena = 1 THEN bal(i).protivnik = bal(stav.baljeto).i
  1103.             NEXT i
  1104.         END IF
  1105.     END IF
  1106.  
  1107. '----------------------------------------------------------------------------------------------------------------------------------------------------
  1108. SUB PerformBounce (bala AS gula, balb AS gula) 'Calculate balls bounce
  1109.     DIM dx AS SINGLE, dy AS SINGLE, d AS SINGLE
  1110.     DIM l AS SINGLE, l1 AS SINGLE, l2 AS SINGLE
  1111.     DIM k AS SINGLE, k1 AS SINGLE, k2 AS SINGLE
  1112.     DIM m AS SINGLE, m1 AS SINGLE, m2 AS SINGLE
  1113.     DIM n AS SINGLE, n1 AS SINGLE, n2 AS SINGLE
  1114.     DIM cosa AS SINGLE, cosb AS SINGLE, p AS SINGLE
  1115.     DIM ma AS SINGLE, mb AS SINGLE
  1116.  
  1117.     IF stav.massallowed = 1 THEN
  1118.         ma = bala.m
  1119.         mb = balb.m
  1120.     ELSE
  1121.         ma = 1
  1122.         mb = 1
  1123.     END IF
  1124.  
  1125.     dx = balb.x - bala.x
  1126.     dy = balb.y - bala.y
  1127.     d = SQR(dx ^ 2 + dy ^ 2)
  1128.  
  1129.     p = bala.r + balb.r - d 'Move overlapping balls away from each other
  1130.     IF p >= 0 THEN
  1131.         bala.x = bala.x - dx * p / d / 2 * 2
  1132.         bala.y = bala.y - dy * p / d / 2 * 2
  1133.         balb.x = balb.x + dx * p / d / 2 * 2
  1134.         balb.y = balb.y + dy * p / d / 2 * 2
  1135.  
  1136.         dx = balb.x - bala.x
  1137.         dy = balb.y - bala.y
  1138.         d = SQR(dx ^ 2 + dy ^ 2)
  1139.     END IF
  1140.  
  1141.     cosa = dy / d
  1142.     cosb = -cosa
  1143.  
  1144.     l = (dx * bala.v.x * ma + dy * bala.v.y * ma) / d
  1145.     l2 = l * cosa
  1146.     k = (dy * bala.v.x * ma - dx * bala.v.y * ma) / d
  1147.     k1 = k * cosa
  1148.     l1 = bala.v.x * ma - k1
  1149.     k2 = bala.v.y * ma - l2
  1150.  
  1151.     n = (-dx * balb.v.x * mb - dy * balb.v.y * mb) / d
  1152.     n2 = n * cosb
  1153.     m = (-dy * balb.v.x * mb + dx * balb.v.y * mb) / d
  1154.     m1 = m * cosb
  1155.     n1 = balb.v.x * mb - m1
  1156.     m2 = balb.v.y * mb - n2
  1157.  
  1158.     bala.v.x = (k1 + n1) / ma
  1159.     bala.v.y = (k2 + n2) / ma
  1160.     balb.v.x = (m1 + l1) / mb
  1161.     balb.v.y = (m2 + l2) / mb
  1162.  
  1163. '----------------------------------------------------------------------------------------------------------------------------------------------------
  1164. FUNCTION MATHROUND (n)
  1165.     MATHROUND = FIX(n + 0.5 * SGN(n))
  1166.  
  1167. '----------------------------------------------------------------------------------------------------------------------------------------------------
  1168. SUB RotateVector (vn AS vektor, b AS SINGLE)
  1169.     DIM vt AS vektor
  1170.     sinb = SIN(b): cosb = COS(b)
  1171.     vt.x = vn.x * cosb - vn.y * sinb
  1172.     vt.y = vn.y * cosb + vn.x * sinb
  1173.     vn = vt
  1174.  
  1175. '----------------------------------------------------------------------------------------------------------------------------------------------------
  1176. SUB CalculateFps 'calculate actual fps coeficient for correct timig
  1177.     IF stav.tend = stav.tstart THEN
  1178.         stav.fpsactual = stav.fps
  1179.     ELSE
  1180.         stav.fpsactual = ((1 / (stav.tend - stav.tstart)) * 1 + stav.fpsactual * 99) / 100
  1181.     END IF
  1182.     stav.tstart = stav.tend
  1183.     stav.fpscoef = stav.fps / stav.fpsactual
  1184.  
  1185. '----------------------------------------------------------------------------------------------------------------------------------------------------
  1186. SUB DebugScreen (K AS INTEGER) 'print debyg information on screen in runtime
  1187.     COLOR 7
  1188.     PRINT "FPS:   "; stav.fpsactual
  1189.  
  1190.     IF K = 101 THEN stav.energydec = -(stav.energydec = 0) * ENERGY_DEC
  1191.     IF K = 113 THEN
  1192.         bal(hrac(2).bal).strat = bal(hrac(2).bal).strat + 1
  1193.         IF bal(hrac(2).bal).strat > UBOUND(strat) THEN
  1194.             bal(hrac(2).bal).strat = 1
  1195.         END IF
  1196.     END IF
  1197.     COLOR 13
  1198.     PRINT "hrac(1)..isleft   "; klav(hrac(1).klavesy).isleft
  1199.     PRINT "hrac(1)..isright  "; klav(hrac(1).klavesy).isright
  1200.     PRINT "hrac(1)..isdown   "; klav(hrac(1).klavesy).isdown
  1201.     PRINT "hrac(1)..isup     "; klav(hrac(1).klavesy).isup
  1202.     PRINT "bal(2).vi        "; bal(hrac(2).bal).vi.x; bal(hrac(2).bal).vi.y
  1203.     PRINT "bal(2).strat     "; bal(2).strat
  1204.     PRINT "bal(2).klucka_time    "; bal(hrac(2).bal).klucka_time
  1205.     PRINT "bal(2).klucka_pocet   "; bal(hrac(2).bal).klucka_pocet
  1206.     PRINT "bal(2).klucka_trvanie "; bal(hrac(2).bal).klucka_trvanie
  1207.     PRINT "bal(2).klucka_uhol    "; bal(hrac(2).bal).klucka_uhol
  1208.     PRINT "bal(n).protivnik      "; bal(1).protivnik; bal(2).protivnik; bal(3).protivnik; bal(stav.pocetgulx).protivnik
  1209.     PRINT "stav.baljeto          "; stav.baljeto
  1210.     PRINT "stav.balbudeto        "; stav.balbudeto
  1211.     PRINT "nearestPlayer(1) "; nearestPlayer(bal(1), 0)
  1212.     PRINT "bal(n).reactdrop_time _dur "; bal(2).reactdrop_time; bal(2).reactdrop_dur
  1213.     PRINT "bal(1).v              "; bal(1).v.x; bal(1).v.y
  1214.     PRINT "bal(1).a              "; bal(1).a.x; bal(1).a.y
  1215.     PRINT "bal(2).boostintended  "; bal(2).boostintended
  1216.     PRINT "bal(2).boost_time     "; bal(1).boost_time
  1217.     PRINT "bal(2).boost_trvanie  "; bal(1).boost_trvanie
  1218.  
  1219. '----------------------------------------------------------------------------------------------------------------------------------------------------
  1220.  
  1221.  

The game mechanic is quite simple. At the start of each round, one ball shaped player is tagged with white circle inside him. The tag causes player's energy to decrease continually in time. The tagged player is chasing untagged players. When he touches an untagged player, the tag skips to him and he starts to chase. As soon as a player is out of energy, he falls out of the game. In this moment, the tag skips to the nearest active player, who becomes tagged and has to chase.
After getting tagged, there is a short while, when the newly tagged player is "freezed".  This enables the "untagged" player to move away a little. There is also a possibility to boost players movement once in around one second. The last man standing wins the round and gets his score increased.

 
Tag02.jpg


I am aware of two logic flaws in SUB generating AI control response (they are described in comments - first causes uncontrolled rotation on enemy detection and the second causes incorrect enemy positioning), but as I corrected them, chasing the enemy became noticably easier, so I went with the "flawed" verions.
Regarding AI behaviour, you can further experiment with different strategy parameters like reaction drop times/frequencies, dodging times/conuts/angles and thus create different strategies for each player. Currently, all players have strat(3) assigned, which I found quite playable.

And just for case of interest, I also enclose the original ancient version 1 code for DOS.

 
« Last Edit: March 16, 2019, 04:26:00 pm by dajan »

Offline Jack002

  • Forum Regular
  • Posts: 123
  • Boss, l wanna talk about arrays
    • View Profile
Re: Tag! v2 - the chasing game
« Reply #1 on: March 16, 2019, 04:35:17 pm »
Cool game, thanks for posting it!
QB64 is the best!

Offline bplus

  • Global Moderator
  • Forum Resident
  • Posts: 8053
  • b = b + ...
    • View Profile
Re: Tag! v2 - the chasing game
« Reply #2 on: March 16, 2019, 04:40:50 pm »
Yes, I like the bouncing circles, welcome to the forum.

Offline dajan

  • Newbie
  • Posts: 41
    • View Profile
Re: Tag! v2 - the chasing game
« Reply #3 on: March 17, 2019, 12:47:45 pm »
@bplus @Jack002  thanks guys, glad you like it.
I also consider making a version with network game, just to prove it can work.

Offline Petr

  • Forum Resident
  • Posts: 1720
  • The best code is the DNA of the hops.
    • View Profile
Re: Tag! v2 - the chasing game
« Reply #4 on: March 17, 2019, 01:25:00 pm »
Ahoj. Pěkná práce!

Hi. Nice work!

Offline dajan

  • Newbie
  • Posts: 41
    • View Profile
Re: Tag! v2 - the chasing game
« Reply #5 on: March 17, 2019, 01:27:07 pm »
Petre, diki moc.