# fb7b33bc6d6139f8511c293671a5a344531fcae98e22e261c154c4b751a14deb Elan 2.0.0-alpha4 valid

# Use the w,a,s,d keys to change snake's direction
main
  variable blocks set to createBlockGraphics(white)
  variable head set to [20, 15]
  variable tail set to head
  variable body set to [head]
  variable currentDir set to "right"
  variable gameOn set to true
  variable apple set to [0, 0]
  call setAppleToRandomPosition(apple, body)
  while gameOn
    call updateDisplay(blocks, head, tail, body, apple)
    variable currentDirRef set to new AsRef<of String>(currentDir)
    variable headRef set to new AsRef<of List<of Int>>(head)
    variable tailRef set to new AsRef<of List<of Int>>(tail)
    call updateSnake(currentDirRef, tailRef, headRef, body)
    set head to headRef.value()
    set tail to tailRef.value()
    set currentDir to currentDirRef.value()
    set gameOn to not hasHitEdge(head[0], head[1]) and not body.contains(head)
    if head.equals(apple) then
      call setAppleToRandomPosition(apple, body)
    else
      call body.removeAt(0)
    end if
    call sleep_ms(150)
  end while
  call print($"Game Over! Score: {body.length() - 1}")
end main

procedure updateSnake(currentDirRef as AsRef<of String>, tailRef as AsRef<of List<of Int>>, headRef as AsRef<of List<of Int>>, body as List<of List<of Int>>)
  variable head set to headRef.value()
  variable tail set to tailRef.value()
  variable currentDir set to currentDirRef.value()
  set currentDir to directionByKey(currentDir, getKey())
  call tailRef.set(body[0])
  call body.append(head)
  call headRef.set(getAdjacentSquare(head, currentDir))
  call currentDirRef.set(currentDir)
end procedure

procedure updateDisplay(blocks as List<of List<of Int>>, head as List<of Int>, tail as List<of Int>, body as List<of List<of Int>>, apple as List<of Int>)
  set blocks[head[0]][head[1]] to green
  variable tailColour set to getTailColour(tail, body)
  set blocks[tail[0]][tail[1]] to tailColour
  set blocks[apple[0]][apple[1]] to red
  call displayBlocks(blocks)
end procedure

procedure setAppleToRandomPosition(apple as List<of Int>, body as List<of List<of Int>>)
  variable changePosition set to true
  while changePosition
    set apple[0] to randint(0, 39)
    set apple[1] to randint(0, 29)
    if not body.contains(apple) then
      set changePosition to false
    end if
  end while
end procedure

function getTailColour(tail as List<of Int>, body as List<of List<of Int>>) returns Int
  variable colour set to white
  if body[0].equals(tail) then
    set colour to green
  end if
  return colour
end function

function hasHitEdge(headX as Int, headY as Int) returns Boolean
  return (headX < 0) or (headY < 0) or (headX > 39) or (headY > 29)
end function

function getAdjacentSquare(sq as List<of Int>, dir as String) returns List<of Int>
  variable newX set to sq[0]
  variable newY set to sq[1]
  if dir.equals("left") then
    set newX to newX - 1
  elif dir.equals("right") then
    set newX to newX + 1
  elif dir.equals("up") then
    set newY to newY - 1
  elif dir.equals("down") then
    set newY to newY + 1
  end if
  return [newX, newY]
end function

function directionByKey(current as String, key as String) returns String
  variable dirn set to current
  variable d set to ["w":"up", "s":"down", "a":"left", "d":"right"]
  if d.keys().contains(key) then
    set dirn to d[key]
  end if
  return dirn
end function

test test_getTailColour
  assert getTailColour([3, 4], [[3, 4], [3, 5]]) is green
  assert getTailColour([3, 4], [[3, 5], [3, 6]]) is white
end test

test test_hasHitEdge
  assert hasHitEdge(0, 0) is false
  assert hasHitEdge(0, 29) is false
  assert hasHitEdge(39, 0) is false
  assert hasHitEdge(29, 29) is false
  assert hasHitEdge(-1, 5) is true
  assert hasHitEdge(5, 30) is true
  assert hasHitEdge(40, 5) is true
  assert hasHitEdge(5, -1) is true
end test

test test_getAdjacentSquare
  variable sq set to [20, 15]
  assert getAdjacentSquare(sq, "up") is [20, 14]
  assert getAdjacentSquare(sq, "down") is [20, 16]
  assert getAdjacentSquare(sq, "left") is [19, 15]
  assert getAdjacentSquare(sq, "right") is [21, 15]
  # boundary
  assert getAdjacentSquare([0, 15], "left") is [-1, 15]
end test

test test_directionByKey
  variable current set to "up"
  assert directionByKey(current, "") is "up"
  assert directionByKey(current, "x") is "up"
  assert directionByKey(current, "w") is "up"
  assert directionByKey(current, "s") is "down"
  assert directionByKey(current, "a") is "left"
  assert directionByKey(current, "d") is "right"
  assert directionByKey(current, "D") is "up"
end test
