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.
270 lines
6.7 KiB
270 lines
6.7 KiB
2 years ago
|
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
|