You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
269 lines
6.7 KiB
269 lines
6.7 KiB
defmodule TetrisuiWeb.TetrisLive do |
|
use Phoenix.LiveView |
|
use TetrisuiWeb, :live_view |
|
import Phoenix.HTML, only: [raw: 1] |
|
|
|
@debug true |
|
@box_width 20 |
|
@box_height 20 |
|
|
|
defp mount_test(_params,_session, socket) do |
|
{ |
|
:ok, |
|
assign(socket, |
|
brick: Tetris.Brick.new_random |> Tetris.Brick.to_string, |
|
tetromino: [ |
|
{1, 1, :orange}, {2, 1, :orange}, {3, 1, :red} , {4, 1, :blue}, |
|
], |
|
name: "julz" |
|
) |
|
} |
|
end |
|
|
|
def mount(_params, _session, socket) do |
|
socket2 = assign(socket,appstate: "loading") |
|
case connected?(socket2) do |
|
true -> {:ok, start_game(socket2)} |
|
false -> {:ok, socket2} |
|
end |
|
end |
|
|
|
defp start_game(socket) do |
|
:timer.send_interval 1000, self(), :tick |
|
assign(socket, |
|
appstate: "loaded", |
|
state: :starting, |
|
) |
|
end |
|
|
|
defp new_game(socket) do |
|
assign(socket, |
|
appstate: "loaded", |
|
state: :playing, |
|
score: 0, |
|
bottom: %{}, |
|
game_over: false |
|
) |
|
|> new_block |
|
|> show |
|
end |
|
|
|
def new_block(socket) do |
|
brick = |
|
Tetris.Brick.new_random() |
|
|> Map.put(:location, {3, -3}) |
|
|
|
assign(socket,brick: brick) |
|
end |
|
|
|
def show(socket) do |
|
brick = socket.assigns.brick |
|
points_with_colour = |
|
brick |
|
|> Tetris.Brick.prepare |
|
|> Tetris.Points.move_to_location(brick.location) |
|
|> Tetris.Points.with_colour(colour(brick)) |
|
assign(socket, tetromino: points_with_colour) |
|
end |
|
|
|
def render(%{appstate: "loading"}=assigns) do |
|
~L""" |
|
<h1>Tetris </h1> |
|
<div id='appstate'>Loading...</div> |
|
<div> |
|
</div> |
|
""" |
|
end |
|
|
|
def render(%{state: :starting}=assigns) do |
|
~L""" |
|
<h1>Welcome to Tetris </h1> |
|
<div id='appstate'></div> |
|
<div> |
|
<button phx-click="start">Start</button> |
|
</div> |
|
""" |
|
|
|
end |
|
|
|
def render(%{appstate: "loaded", state: :playing}=assigns) do |
|
#<pre><%= @brick%></pre> |
|
#<div phx-keydown="keydown" tabindex="0"> |
|
~L""" |
|
<h1>Tetris <%= @score %></h1> |
|
<div id='appstate'></div> |
|
<div phx-window-keydown="keydown"> |
|
<%= raw svg_head()%> |
|
<%= raw boxes(@tetromino)%> |
|
<%= raw boxes(Map.values(@bottom)) %> |
|
<%= raw svg_foot()%> |
|
</div> |
|
<%= debug(assigns) %> |
|
""" |
|
end |
|
|
|
def render(%{appstate: "loaded", state: :game_over}=assigns) do |
|
#<pre><%= @brick%></pre> |
|
#<div phx-keydown="keydown" tabindex="0"> |
|
~L""" |
|
<h1>Tetris - GAME OVER - score: <%= @score %> </h1> |
|
<div id='appstate'></div> |
|
<button phx-click="start">Play Again</button> |
|
<%= debug(assigns) %> |
|
""" |
|
end |
|
|
|
#def render(%{appstate: _other}=assigns) do |
|
# ~L"<h1>tetrisui - unknown app state <%= @appstate %></h1>" |
|
#end |
|
|
|
|
|
|
|
|
|
def svg_head() do |
|
""" |
|
<svg |
|
version="1.0" |
|
style="background-color: #E8E8E8" |
|
id="Layer_1" |
|
xmlns="http://www.w3.org/2000/svg" |
|
xmlns:xlink="http://www.w3.org/1999/xlink" |
|
width="200" height="400" |
|
viewBox="0 0 200 400" |
|
xml:space="preserve"> |
|
""" |
|
end |
|
def svg_foot(), do: "</svg>" |
|
|
|
def boxes(points_with_colours) do |
|
points_with_colours |
|
|> Enum.map( fn {x,y,colour} -> |
|
box({x,y}, colour) |
|
end) |
|
|> Enum.join("\n") |
|
end |
|
|
|
def box(point,colour) do |
|
""" |
|
#{square(point, shades(colour).light)} |
|
#{triangle(point, shades(colour).dark)} |
|
""" |
|
end |
|
|
|
def square(point,shade) do |
|
{x,y} = to_pixels(point) |
|
""" |
|
<rect |
|
x="#{x+1}" y="#{y+1}" |
|
style="fill:##{shade};" |
|
width="#{@box_width - 2}" height="#{@box_height - 1}"/> |
|
""" |
|
end |
|
def triangle(point,shade) do |
|
{x,y} = to_pixels(point) |
|
{w,h} = {@box_width, @box_height} |
|
""" |
|
<polyline |
|
style="fill:##{shade}" |
|
points="#{x + 1}, #{y + 1} #{x + w},#{y+1} #{x + w} ,#{y + h}" /> |
|
""" |
|
end |
|
|
|
|
|
defp to_pixels({x,y}), do: {(x-1) * @box_width, (y-1) * @box_height} |
|
|
|
|
|
defp shades(:red ), do: %{ light: "DB7160", dark: "AB574B"} |
|
defp shades(:blue ), do: %{ light: "83C1C8", dark: "66969C"} |
|
defp shades(:green ), do: %{ light: "8BBF57", dark: "769359"} |
|
defp shades(:orange ), do: %{ light: "CB8E4E", dark: "AC7842"} |
|
defp shades(:grey ), do: %{ light: "A1A09E", dark: "7F7F7E"} |
|
|
|
defp colour(%{name: :t}), do: :red |
|
defp colour(%{name: :i}), do: :blue |
|
defp colour(%{name: :l}), do: :green |
|
defp colour(%{name: :o}), do: :orange |
|
defp colour(%{name: :z}), do: :grey |
|
|
|
def drop(:playing, socket, fast) do |
|
old_brick = socket.assigns.brick |
|
|
|
response = |
|
Tetris.drop( |
|
old_brick, |
|
socket.assigns.bottom, |
|
colour(old_brick) |
|
) |
|
|
|
bonus = if fast, do: 2, else: 0 |
|
|
|
state = if response.game_over, do: :game_over, else: :playing |
|
socket |
|
|> assign( |
|
brick: response.brick, |
|
bottom: response.bottom, |
|
score: socket.assigns.score + response.score + bonus, |
|
state: state |
|
) |
|
|> show |
|
|
|
end |
|
def drop(_not_playing, socket, _dontcare), do: socket |
|
|
|
def move(direction, socket) do |
|
socket |
|
|> do_move(direction) |
|
|> show |
|
end |
|
|
|
def do_move(%{assigns: %{brick: brick, bottom: bottom}}=socket, :left) do |
|
assign(socket, brick: brick |> Tetris.try_left(bottom)) |
|
end |
|
def do_move(%{assigns: %{brick: brick, bottom: bottom}}=socket, :right) do |
|
assign(socket, brick: brick |> Tetris.try_right(bottom)) |
|
end |
|
def do_move(%{assigns: %{brick: brick, bottom: bottom}}=socket, :turn) do |
|
assign(socket, brick: brick |> Tetris.try_spin(bottom)) |
|
end |
|
|
|
|
|
def handle_event("keydown", %{"key" => "ArrowLeft"}, socket) do |
|
{:noreply, move(:left,socket)} |
|
#{:noreply, assign(socket, tetromino: [])} |
|
end |
|
def handle_event("keydown", %{"key" => "ArrowRight"}, socket) do |
|
{:noreply, move(:right,socket)} |
|
end |
|
def handle_event("keydown", %{"key" => "ArrowUp"}, socket) do |
|
{:noreply, move(:turn,socket)} |
|
end |
|
|
|
def handle_event("keydown", %{"key" => "ArrowDown"}, socket) do |
|
{:noreply, drop(socket.assigns.state,socket, :true)} |
|
end |
|
def handle_event("keydown", _other, socket) do |
|
{:noreply, socket} |
|
end |
|
def handle_event("start", _other, socket) do |
|
{:noreply, new_game(socket)} |
|
end |
|
def handle_info(:tick, socket) do |
|
{:noreply, drop(socket.assigns.state, socket, :false)} |
|
end |
|
|
|
|
|
def debug(assigns), do: debug(assigns, @debug, Mix.env) |
|
def debug(assigns, true, :dev) do |
|
~L""" |
|
<pre> |
|
Brick : <%= raw( @tetromino |> inspect) %> |
|
Bottom: <%= raw( @bottom |> inspect) %> |
|
</pre> |
|
""" |
|
|
|
#"<h1> Debugging</h1>" |
|
end |
|
def debug(assigns, _, _), do: "" |
|
|
|
|
|
end
|
|
|