# Flying Ball
#
# Simple clone of the famous game with a flying bird. This game is written in
# the comun programming language and can be run with the saf_comun.h native
# SAF program.
#
# by drummyfish, released under CC0 1.0, public domain

~gameState       # 0: playing, 1: waiting for start, > 1: game over
~playerHeight
~score
~lastPressFrame
~lastPressHeight
~startFrame
~pointer:0
~columns:16      # format: col1Type, col1Pos, col2Type, col2Pos, ...
~nextSpawnIn

MAX_COLUMNS: 8 .
JUMP_HEIGHT: 16 .
COLUMN_WIDTH: 8 .
POSITION_X: 10 .
BALL_RADIUS: 4 .

restart:
  1 $:gameState
  0 $:nextSpawnIn
  0 $:score
  SAF_frame $:startFrame
  SAF_frame $:lastPressFrame

  SAF_SCREEN_HEIGHT 2 / $0 $:playerHeight $:lastPressHeight
 
  $columns>pointer

  MAX_COLUMNS 2 /   # set all columns to type 0 (empty)
  @'
    0 $:pointer
    $>pointer
    $>pointer
    --
  .
  ^
.

# checks if player collides with a column
playerCollides:
  $columns>pointer
  
  MAX_COLUMNS 2 /
  @'
    $pointer

    ?
      $pointer
      $>pointer
      $pointer

      columnHeights

      SAF_SCREEN_HEIGHT $playerHeight - >> # vertical1
      ><
      $playerHeight >>                     # vertical2
      |                                # vertical1 1 OR vertical2 = A
      POSITION_X BALL_RADIUS - $pointer >                  # horizontal1
      POSITION_X BALL_RADIUS + $pointer COLUMN_WIDTH - <   # horiuontal2
      | 0 =                            # NOT (horizontal1 | horiuontal2) = B
      &                                # A AND B
      ?
        ^ 1
        !.
      .

      $<pointer
    .

    $>pointer
    $>pointer
    --  
  .
  ^

  0
.

draw:
  147 SAF_clearScreen

  $playerHeight SAF_SCREEN_HEIGHT << ?
    # draw ball:
    POSITION_X $playerHeight BALL_RADIUS SAF_COLOR_GRAY 1 SAF_drawCircle

    # draw the direction line:
    POSITION_X $playerHeight
    $gameState 1 = ? BALL_RADIUS 0 ; computeDirection .
    >< POSITION_X + >< $playerHeight +
    SAF_COLOR_GRAY_DARK SAF_drawLine
    POSITION_X $playerHeight BALL_RADIUS SAF_COLOR_GRAY_DARK 0 SAF_drawCircle
  .

  # draw columns:
  $columns>pointer

  MAX_COLUMNS 2 /
  @'
    $pointer
    $>pointer
    ?
      $pointer $<pointer $pointer $>pointer ><
      columnHeights
      $pointer

      # stack top: h1 h2 x
      $0 COLUMN_WIDTH - -1 COLUMN_WIDTH $5  SAF_COLOR_BROWN 1 SAF_drawRect
      $0 COLUMN_WIDTH - SAF_SCREEN_HEIGHT $3 -  COLUMN_WIDTH $4 SAF_COLOR_BROWN 1 SAF_drawRect 
      ^ ^ ^
    .

    $>pointer
    --
  .
  ^

  $gameState 1 = ?
    0 "HISCORE:" 2 2 SAF_COLOR_BLACK 1 SAF_drawText
    getHighScore SAF_intToStr 4 9 SAF_COLOR_BLACK 2 SAF_drawText
  ;
    $score SAF_intToStr 1 59 SAF_COLOR_BLUE 1 SAF_drawText
  .
.

updateColumns:
  $columns>pointer
  
  MAX_COLUMNS 2 /
  @'
    $pointer
    $>pointer
    ?
      $pointer
      -- $:pointer'

      0 <<= ?
        # remove column:
        $<pointer
        0 $:pointer
        $>pointer
      .
    .

    $>pointer
    --  
  .
  ^

  $nextSpawnIn ?'
    -- $:nextSpawnIn
  ;
    spawnColumn
    ^ SAF_random 32 % 34 difficulty 2 * - + $:nextSpawnIn
  .
.

# given two values, type and position, returns two values, upper and lower height
# of a column
columnHeights:
  # ordered from easiest to hardest

  $1 1 = ?
    ^ ^ 12 16 !.
  .
  $1 2 = ?
    ^ ^ 5 30 !.
  .
  $1 3 = ?
    ^ ^ 25 7 !.
  .
  $1 4 = ?
    ^ ^ 20 18 !.
  .
  $1 5 = ?
    ^ ^ 6 35 !. 
  .
  $1 6 = ?
    ^ ^ 34 4 !.
  .
  $1 7 = ?
    # squeezing columns
    >< ^ 4 /  $0 20 >< - >< 20 >< -   !.
  .

  # vertically moving column
  >< ^ 4 * SAF_sin 8 /  $0 15 + >< 15 >< -
.

# returns current difficulty as a number 3 to 8
difficulty:
  3 $score 8 / +

  $0 8 > ?
    ^ 8
  .
.

spawnColumn:
  $columns>pointer
  
  MAX_COLUMNS 2 / # find first empty slot and create new column there
  @'
    $pointer 0 =
    ?
      SAF_random 5 * SAF_random 7 / + # this makes it "more random"
      difficulty % ++  # allow more difficult columns with greater difficulty
      $:pointer
      $>pointer
      SAF_SCREEN_WIDTH COLUMN_WIDTH + $:pointer
      !@
    .

    $>pointer
    $>pointer
    --  
  .
  ^
.

jump:
  $playerHeight
  SAF_frame $:lastPressFrame
  $:lastPressHeight
  SAF_SOUND_BUMP SAF_playSound
.

computeHeight:
  SAF_frame $lastPressFrame - JUMP_HEIGHT - $0 * 16 /    
  $lastPressHeight JUMP_HEIGHT JUMP_HEIGHT * 16 / - +
.

# pushes player's direction offset vector with length of ball radius
computeDirection:
  SAF_frame $lastPressFrame - JUMP_HEIGHT -   # d = derivative times 8
  $0 $0 * 64 + SAF_sqrt                       # L = len(vec(8,d))
  $0 8 BALL_RADIUS * >< //                    # (8 * r) / L
  $2 BALL_RADIUS * $2 //                      # (d * r) / L
  $:2 $:2
.

buttonPressed:
  SAF_BUTTON_UP SAF_buttonJustPressed
  SAF_BUTTON_A SAF_buttonJustPressed
  SAF_BUTTON_B SAF_buttonJustPressed
  | |
.

setHighScore:
  $0 256 % 0 >< SAF_save 256 / 1 >< SAF_save
.

getHighScore:
  0 SAF_load 1 SAF_load 256 * +
.

SAF_loop:
  draw

  $gameState 0 = ?
    updateColumns

    buttonPressed ?
      jump
    .
  
    computeHeight $:playerHeight

    SAF_frame $startFrame - 32 % 0 = ?
      $score ++ $:score
    .

    playerCollides ?
      $score getHighScore > ?
        $score setHighScore
      .

      SAF_SOUND_BOOM SAF_playSound
      50 $:gameState
    . 
  ;
    $gameState 1 = ?
      buttonPressed ?
        restart
        0 $:gameState
      .
    ; # gameState > 1
      computeHeight $:playerHeight

      $gameState -- $:gameState

      $gameState 1 = ?
        restart
      .
    .
  .
.

restart
