Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

Blockr

livebook-sessions/Blockr.livemd

Blockr

alias Blockr.Game.{Canvas, Group, Point, Tetromino, Board}

single point

alias Blockr.Game
p =
  Point.new(1, 5)
  |> Point.move_down()
  |> Point.move_down()
  |> Point.move_left()

Canvas.new(p)

multiple points

points =
  [
    Point.new(2, 5),
    Point.new(2, 6),
    Point.new(3, 4),
    Point.new(3, 5)
  ]

Canvas.new(points)

Big Tetromino

points =
  [
    Point.new(2, 2),
    Point.new(3, 2),
    Point.new(4, 2),
    Point.new(4, 3)
  ]

Canvas.tetromino(points)

:s Shape

Tetromino.new(:s)
|> Tetromino.to_group()
|> Canvas.tetromino()
# |> IO.inspect(pretty: true)

:z shape

#CRC = constructor, reducer, converter
Tetromino.new(:z) # constructor
|> Tetromino.rotate_right_90() # reducer
|> Tetromino.left()
|> Tetromino.right()
|> Tetromino.right() # reducer
|> Tetromino.to_group() # converter
|> Group.paint(:z)
# |> Canvas.new()
|> Canvas.tetromino()
Tetromino.new(:t)
|> Tetromino.to_group()
|> Canvas.tetromino()

Canvas with color

Tetromino.new(:z)
|> Tetromino.to_group()
|> Group.paint("green")
|> Canvas.tetromino()
Canvas.draw({1, 1}, 10)
Canvas.draw({{1, 1}, "green"}, 10)
Canvas.draw([{{1, 1}, "green"}, {{1, 2}, "green"}], 10)
Group.paint([{1, 1}, {2, 2}], :l)
Group.paint([{1, 1}, {2, 2}], :l) |> Canvas.draw(10) 
Point

Complete through part 1

Next in our implementation:

  • Add a game Board
    • Add walls to our Board
  • Enhance Canvas dimensions to show walls
  • Enhance Canvas to show background color
  • Add score to our Board
  • Add Game.move, Game.right and check overlaps when tetromino moves
  • Add Game.fall and check overlaps when tetromino moves
  • Detach Tetromino is fall fails
  • Score Tetromino if fall fails
  • Remove scored rows on score

Show-Board

game = Board.new().walls
game = Board.new()
game
|> Game.left()
|> Game.left()
|> Game.left()
|> Game.left()
|> Game.left()
|> Game.left()
|> Board.show()
|> Canvas.new()
game
|> Game.right()
|> Game.right()
|> Game.right()
|> Game.right()
|> Game.right()
|> Game.right()
|> Game.right()
|> Game.right()
|> Board.show()
|> Canvas.new()
game2 = 
  Enum.reduce(1..74, game, fn _i, acc -> Game.fall(acc) end)
# Enum.reduce(1..15, game2, fn _i, acc -> Game.fall(acc) end)
|> Board.show()
|> Canvas.new()

Within LiveBook, build a board with a junkyard by setting points directly onto the board. This will be important for test purposes.

board = Board.new()

points = for r <- 18..20, c <- 1..10, do: {r, c}
colors = for r <- 18..20, c <- 1..10, do: {{r, c}, "green"}

board = 
  %{board| points: MapSet.new(points ++ board.walls), junkyard: colors}

board
|> Board.show()
|> Canvas.new()
board.junkyard
|> Map.new()
|> Map.keys()
|> Enum.group_by(fn  {r, _c} -> r end)
# |> Map.values()
# |> Enum.count(&length(&1) == 10)
completed_rows = 
  board.junkyard
  |> Map.new()
  |> Map.keys()
  |> Enum.group_by(fn  {r, _c} -> r end)
  |> Enum.to_list()
  |> Enum.filter(fn {_row_num, row_cells} -> length(row_cells) == 10 end)
  |> Enum.into(%{})
  |> Map.values()
  |> List.flatten()
  |> Enum.map(fn point -> {point, "white"} end)


board =
  %{board| junkyard: completed_rows}
  |> Board.show()
  |> Canvas.new()
# board.junkyard
~c"\n\n\n\n" == [10, 10, 10, 10]

Score and doubling

  • 1 row: 100 2 1 -> 2. 50 * 21 = 100
  • 2 row: 200 2 2 -> 4. 50 * 22 = 200
  • 3 row: 400 2 3 -> 8. 50 * 23 = 400
  • 4 row: 800 2 4 -> 4. 50 * 24 = 800
defmodule Double do
  def power(exp) do
    :math.pow(2, exp)
    |> round()
    |> Kernel.*(50)
  end

  def reduce(exp) do
    Enum.reduce(1..exp, 50, fn _i, acc -> acc + acc end)
  end

  def unfold() do
    Stream.unfold(100, fn n -> 
      {n, n * 2}
    end)
  end

  def unfold(exp) do
    Stream.unfold(100, fn n -> {n, n * 2} end)
    |> Stream.drop(exp - 1)
    |> Enum.take(1)
    |> hd()
  end
end

[
  Enum.map(1..4, &amp;Double.power/1),
  Enum.map(1..8, &amp;Double.reduce/1),
  Enum.map(1..4, &amp;Double.unfold/1),
  Double.unfold() |> Enum.take(4)
  # Double.unfold(4)
]
# :math.pow(2, 3)
Double.power(3)
Stream.unfold(5, fn
  0 -> nil |> IO.inspect(label: "nil")
  n -> {n, n - 1}
end) |> Enum.to_list()
Stream.unfold(1, fn
  n -> {n, n * 2}
end) |> Enum.take(10)

Eat-rows

exclude = [{{14, 5}, "green"}, {{15, 5}, "green"}, {{18, 5}, "green"}]
colored_points = 
   (for row <- 14..19, 
     col <- 1..10, 
     do: {{row, col}, "green"}) -- exclude
  
board1 = Board.new(
  junkyard: colored_points
)

board1
|> Board.show()
|> Canvas.new()
defmodule Pacman do
  def eat_completed(board) do
    rows = Enum.group_by(board.junkyard, fn {{row, _col}, _color} -> row end)

    completed = 
      rows
      |> Enum.filter(fn {_row, list} -> length(list) == 10 end)
      |> Map.new()
      |> Map.keys()

    junkyard = 
      Enum.reduce(completed, rows, &amp;eat_row/2)
      |> Map.values()
      |> List.flatten()

    %{board| junkyard: junkyard}
  end

  defp eat_row(row_number, rows) do
    rows
    |> Map.delete(row_number)
    |> Enum.map(fn {rn, list} -> 
      if(row_number > rn) do
        {rn + 1, move_all_down(list)} 
      else
        {rn, list} 
      end
    end)
    |> Map.new()
  end

  defp move_all_down(points) do
    Enum.map(points, fn {{row, col}, color} ->
      {{row + 1, col}, color}
    end)
  end
end

Pacman.eat_completed(board1)
|> Board.show()
|> Canvas.new()