Client for playing 300 publicly available Sokoban puzzles on a computer or phone.
-- routines for drawing and sizing to available space

function car.draw()
  if dark_theme then
    g.setBackgroundColor(0.4,0.4, 0.4)
  end
  ui_state.button_handlers = {}
  draw_level_state()
  draw_buttons()
  draw_level_number()
  draw_win_state()
  draw_crate_to_move()
  draw_hud()
end

function draw_level_state()
  for r,row in ipairs(level_state) do
    for c,cell in ipairs(row) do
      draw_sprite(cell, left+(c-1)*side, top+(r-1)*side)
--?       if crate_id[r][c] then
--?         color(1,1,1)
--?         g.print(crate_id[r][c], left+(c-1)*side, top+(r-1)*side)
--?       end
    end end end

function draw_sprite(cell, x,y)
  if cell >= CELL_VACANT then
    color(unpack(SC_AIR))
    rect('fill', x,y, side,side)
    return
  end
  local sprite = sprites[cell+1]
  for r,row in ipairs(sprite) do
    for c,clr in ipairs(row) do
      color(unpack(clr))
      rect('fill', x+(c-1)*pxside, y+(r-1)*pxside, pxside, pxside)
    end end end

function draw_level_number()
  local lx, ly = left-level_width, top+10
  if Safe_height > Safe_width then
    lx, ly = (Safe_width - level_width)/2, top-side-Line_height-10
  end
  color(unpack(level_color))
  g.print(curr_level, lx,ly)
end

function draw_win_state()
  if not any_state_in_level(CELL_CRATE) then
    color(0,1,0)
    g.print('success!', left-2*side, top+side)
  end
end

function any_state_in_level(needle)
  for r,row in ipairs(level_state) do
    for c,cell in ipairs(row) do
      if cell == needle then
        return true
      end end end end

function draw_buttons()
  local bg
  if dark_theme then
    bg = {r=0.4,g=0.2,b=0.4}
  else
    bg = {r=0.6,g=0.4,b=0.6}
  end
  button(ui_state, 'left', {x=0, y=bside, w=bside, h=Safe_height-bside*2,
    bg=bg, onpress1 = function() move_left(true) end})
  button(ui_state, 'right', {x=Safe_width-bside, y=bside, w=bside, h=Safe_height-bside*2,
    bg=bg, onpress1 = function() move_right(true) end})
  button(ui_state, 'up', {x=bside, y=0, w=Safe_width-bside*2, h=bside,
    bg=bg, onpress1 = function() move_up(true) end})
  button(ui_state, 'down', {x=bside, y=Safe_height-bside, w=Safe_width-bside*2, h=bside,
    bg=bg, onpress1 = function() move_down(true) end})

  -- level navigation
  local ns, ps = tostring('>>'), tostring('<<')
  local nw, pw = App.width(ns)+10, App.width(ps)+10
  local h = Line_height+10
  local nx,px, y
  if Safe_width > Safe_height then
    px, nx = left-side-pw-10, left+lw*side+side+10
    y = top + lh*side/2
  else
    px, nx = left+side+10, left+lw*side-side-nw-10
    y = top-side-h
  end
  if curr_level > 1 then
    button(ui_state, 'previous level', {x=px, y=y, w=pw, h=h,
      bg=bg, onpress1=previous_level,
      icon=function(p)
        color(unpack(level_color))
        g.print(ps, p.x+5, p.y+5)
      end,
    })
  end
  if curr_level < #levels then
    button(ui_state, 'next level', {x=nx, y=y, w=nw, h=h,
      bg=bg, onpress1=next_level,
      icon=function(p)
        color(unpack(level_color))
        g.print(ns, p.x+5, p.y+5)
      end,
    })
  end
  if #undo_history > 0 then
    local w = App.width('undo')+10
    local ux, uy = left+lw*side+10, top+10
    if Safe_width < Safe_height then
      ux, uy = left+lw*side-w-10, top+lh*side+10
    end
    button(ui_state, 'undo', {x=ux, y=uy, w=w, h=Line_height+10,
      bg=bg, onpress1=undo_move,
      icon=function(p)
        color(0,0,0)
        g.print('undo', p.x+5, p.y+5)
      end,
    })
  end
end

-- leave 6 tiles worth of margin on all sides for the nav buttons
function car.resize()
  local w,h = Safe_width, Safe_height
  local w1, w2 = landscape_button_offsets()
  local h1, h2 = portrait_button_offsets()
  if Safe_width > Safe_height then
    side = min((w-w1-w2)/(1+lw+1+6), h/(lh+6))  -- leave 1 side between board and buttons
  else
    side = min(w/(lw+6), (h-h1-h2)/(1+lh+1+6))  -- leave 1 side between board and buttons
  end
  pxside = side/num_tile_px
  left = (w-side*lw)/2
  top = (h-side*lh)/2
  if Safe_width > Safe_height then
    bside = min((w-w1-w2-side*(1+lw+1))/2, (h-side*lh)/2)
  else
    bside = min((h-h1-h2-side*(1+lh+1))/2, (w-side*lw)/2)
  end
end

-- space needed for buttons to the left and right
function landscape_button_offsets()
  local ns, ps = tostring('>>'), tostring('<<')
  local nw, pw = App.width(ns)+10, App.width(ps)+10
  return 10+pw+10, 10+nw+10
  -- we assume the level name and undo button will be within this width
end

-- space needed for buttons to the top and bottom
function portrait_button_offsets()
  return 10+Line_height+10, 10+Line_height+10
end