Skip to content

Commit

Permalink
Improve Tetris sample
Browse files Browse the repository at this point in the history
  • Loading branch information
edgarfgp committed Dec 21, 2024
1 parent 0940ebd commit 97e1df7
Show file tree
Hide file tree
Showing 2 changed files with 214 additions and 77 deletions.
6 changes: 6 additions & 0 deletions Fabulous.Avalonia.Samples.sln
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "TestableApp.Headless.XUnit"
EndProject
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "TestableApp.UnitTests", "samples\TestableApp.UnitTests\TestableApp.UnitTests.fsproj", "{89CE91F9-AFF2-4CD2-BFD0-AC4C781887F2}"
EndProject
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Fabulous.Avalonia", "src\Fabulous.Avalonia\Fabulous.Avalonia.fsproj", "{31D47096-FBF7-4A74-A302-011B9608C32E}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -128,5 +130,9 @@ Global
{89CE91F9-AFF2-4CD2-BFD0-AC4C781887F2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{89CE91F9-AFF2-4CD2-BFD0-AC4C781887F2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{89CE91F9-AFF2-4CD2-BFD0-AC4C781887F2}.Release|Any CPU.Build.0 = Release|Any CPU
{31D47096-FBF7-4A74-A302-011B9608C32E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{31D47096-FBF7-4A74-A302-011B9608C32E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{31D47096-FBF7-4A74-A302-011B9608C32E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{31D47096-FBF7-4A74-A302-011B9608C32E}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
285 changes: 208 additions & 77 deletions samples/Mvu/Tetris/App.fs
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,51 @@ namespace Tetris

open System
open System.Diagnostics
open Avalonia
open Avalonia.Controls
open Avalonia.Input
open Avalonia.Layout
open Avalonia.Media
open Avalonia.Threading
open Fabulous
open Fabulous.Avalonia
open type Fabulous.Avalonia.View
open Avalonia.Themes.Fluent

open type Fabulous.Avalonia.View

// Credits to https://github.com/RyushiAok/Tetris for the original code

module Paths =
[<Literal>]
let Left =
"M11.4939 20.5644C11.1821 20.8372 10.7083 20.8056 10.4356 20.4939L3.43557 12.4939C3.18814 12.2111 3.18814 11.7889 3.43557 11.5061L10.4356 3.50613C10.7083 3.1944 11.1822 3.16281 11.4939 3.43557C11.8056 3.70834 11.8372 4.18216 11.5644 4.49388L5.65283 11.25L20 11.25C20.4142 11.25 20.75 11.5858 20.75 12C20.75 12.4142 20.4142 12.75 20 12.75L5.65283 12.75L11.5644 19.5061C11.8372 19.8179 11.8056 20.2917 11.4939 20.5644Z"

[<Literal>]
let Right =
"M12.5061 3.43557C12.8178 3.16281 13.2917 3.19439 13.5644 3.50612L20.5644 11.5061C20.8119 11.7889 20.8119 12.2111 20.5644 12.4939L13.5644 20.4939C13.2917 20.8056 12.8178 20.8372 12.5061 20.5644C12.1944 20.2917 12.1628 19.8178 12.4356 19.5061L18.3472 12.75H4C3.58579 12.75 3.25 12.4142 3.25 12C3.25 11.5858 3.58579 11.25 4 11.25H18.3472L12.4356 4.49388C12.1628 4.18215 12.1944 3.70833 12.5061 3.43557Z"

[<Literal>]
let Down =
"M20.5644 12.5061C20.8372 12.8178 20.8056 13.2917 20.4939 13.5644L12.4939 20.5644C12.2111 20.8119 11.7889 20.8119 11.5061 20.5644L3.50611 13.5644C3.19439 13.2917 3.1628 12.8178 3.43556 12.5061C3.70832 12.1944 4.18214 12.1628 4.49387 12.4356L11.25 18.3472L11.25 4C11.25 3.58579 11.5858 3.25 12 3.25C12.4142 3.25 12.75 3.58579 12.75 4L12.75 18.3472L19.5061 12.4356C19.8178 12.1628 20.2917 12.1944 20.5644 12.5061Z"

[<Literal>]
let Hold =
"M17.8 21H22v1h-6v-6h1v4.508a9.861 9.861 0 1 0-5 1.373v.837A10.748 10.748 0 1 1 17.8 21zM11 11v2h2v-2z"

[<Literal>]
let RotateLeft =
"M13,3A9,9,0,0,0,4.91,8.08l-1-2.45a1,1,0,0,0-1.86.74l2,5A1,1,0,0,0,5,12a1,1,0,0,0,.37-.07l5-2a1,1,0,0,0-.74-1.86L6.54,9.31a7,7,0,1,1,1.21,7.32,1,1,0,0,0-1.41-.09A1,1,0,0,0,6.25,18,9,9,0,1,0,13,3Z"

[<Literal>]
let RotateRight =
"M21.37,5.07a1,1,0,0,0-1.3.56l-1,2.45A9,9,0,1,0,17.75,18a1,1,0,0,0-.09-1.41,1,1,0,0,0-1.41.09,7,7,0,1,1,1.2-7.33L14.37,8.07a1,1,0,1,0-.74,1.86l5,2A1,1,0,0,0,19,12a1,1,0,0,0,.93-.63l2-5A1,1,0,0,0,21.37,5.07Z"

type Board =
{ width: int
height: int
board: TetrisBoard }

module App =
let theme = FluentTheme()

type Model =
{ Tetrimino: Tetrimino
hold: Tetrimino option
Expand Down Expand Up @@ -76,17 +103,15 @@ module App =

{ model with
isOver = true
grid =
{ model.grid with
board = if existsOtherBlock then model.grid.board else res.newBoard } },
grid.board = if existsOtherBlock then model.grid.board else res.newBoard },
Cmd.none
| false ->
let newMino = Tetrimino.generate false

let res = model.grid.board |> TetrisBoard.setTetrimino nxt

{ model with
grid = { model.grid with board = res.newBoard }
grid.board = res.newBoard
Tetrimino = newMino
lastUpdated = DateTime.Now
isOver = newMino |> Tetrimino.existsOtherBlock res.newBoard
Expand Down Expand Up @@ -235,25 +260,15 @@ module App =
})
.height(70.0)
.width(70.0)
.canvasLeft(360.0)
.canvasTop(0.0)
//.canvasLeft(360.0)
//.canvasTop(0.0)
.background("#222222")

})
.dock(Dock.Top)
.canvasTop(0.0)

let menuView state =
(HStack() {
TextBlock(sprintf "Score: %d " state.score)
.fontSize(16.0)
.horizontalAlignment(HorizontalAlignment.Center)
.width(350.0)
})
.dock(Dock.Top)
.canvasTop(0.0)
//.canvasTop(0.0)

let howToPlayView () =
let howToPlayDesktop () =
(HStack() {
TextBlock("[A] LEFT \n[D] RIGHT \n[SHIFT] ROT L \n[SPACE] ROT R \n[E] HOLD")
.fontSize(12.0)
Expand All @@ -262,34 +277,99 @@ module App =
})
.dock(Dock.Bottom)

let howToPlayMobile model =
(Dock() {

Border(holdView model).dock(Dock.Top).centerHorizontal()

Button(
Hold,
PathIcon(Paths.Hold)
.width(32)
.height(32)
.foreground(Colors.Green)
)
.dock(Dock.Top)
.margin(2.)
.background(SolidColorBrush(Colors.Transparent))
.horizontalAlignment(HorizontalAlignment.Center)


Button(
Down,
PathIcon(Paths.Down)
.width(32)
.height(32)
.foreground(Colors.Green)
)
.dock(Dock.Bottom)
.margin(2.)
.background(SolidColorBrush(Colors.Transparent))
.horizontalAlignment(HorizontalAlignment.Center)
.borderThickness(1.)

Button(
Right,
PathIcon(Paths.Right)
.width(32)
.height(32)
.foreground(Colors.Green)
)
.dock(Dock.Right)
.margin(2.)
.background(SolidColorBrush(Colors.Transparent))
.horizontalAlignment(HorizontalAlignment.Right)
.verticalAlignment(VerticalAlignment.Stretch)

Button(
Left,
PathIcon(Paths.Left)
.width(32)
.height(32)
.foreground(Colors.Green)
)
.dock(Dock.Left)
.margin(2.)
.background(SolidColorBrush(Colors.Transparent))
.horizontalAlignment(HorizontalAlignment.Left)

Button(
RotL,
PathIcon(Paths.RotateLeft)
.width(32)
.height(32)
.foreground(Colors.Green)
)
.dock(Dock.Left)
.margin(2.)
.background(SolidColorBrush(Colors.Transparent))
.horizontalAlignment(HorizontalAlignment.Left)

Button(
RotR,
PathIcon(Paths.RotateRight)
.width(32)
.height(32)
.foreground(Colors.Green)
)
.dock(Dock.Right)
.margin(2.)
.background(SolidColorBrush(Colors.Transparent))
.horizontalAlignment(HorizontalAlignment.Right)
})
.horizontalAlignment(HorizontalAlignment.Stretch)
.gridRow(1)
.margin(16.)

let gameOverView () =
VStack() {
Grid(rowdefs = [ Star; Auto ], coldefs = [ Star ]) {
TextBlock("Game Over").fontSize(16.0).margin(4.0)
Button("New Game", NewGame).fontSize(16.0).margin(4.0)
}

let content (model: Model) =
if model.isOver then
AnyView(gameOverView())
else
AnyView(
(Dock(lastChildFill = true) {
menuView model

Border(boardView model)
.dock(Dock.Left)
.borderThickness(20.0, 0.0, 0.0, 0.0)

Border(howToPlayView())
.dock(Dock.Right)
.borderThickness(30.0, 0.0, 0.0, 0.0)

Border(holdView model)
.dock(Dock.Right)
.borderThickness(20.0, 0.0, 0.0, 250.0)
})
.background("#222222")
)
Button("New Game", NewGame)
.fontSize(16.0)
.margin(4.0)
.gridRow(1)
}

let subscription (_model: Model) =
let timeSub dispatch =
Expand All @@ -302,40 +382,91 @@ module App =

[ [ nameof timeSub ], timeSub ]

let view model =
#if MOBILE
SingleViewApplication(content model)
#else
DesktopApplication(
Window(content model)
.title("Tetris")
.onKeyDown(fun e ->
match e.Key with
| Key.RightShift
| Key.LeftShift -> RotL
| Key.Space -> RotR
| Key.S -> Down
| Key.A -> Left
| Key.D -> Right
| Key.E -> Hold
| _ -> Empty)
let mainWindow model =
Window(
if model.isOver then
AnyView(gameOverView())
else
AnyView(
(Dock(lastChildFill = true) {
TextBlock($"Score: {model.score}")
.fontSize(16.0)
.horizontalAlignment(HorizontalAlignment.Center)
.dock(Dock.Top)

Border(boardView model)
.dock(Dock.Left)
.borderThickness(20.0, 0.0, 0.0, 0.0)

Border(howToPlayDesktop())
.dock(Dock.Right)
.borderThickness(30.0, 0.0, 0.0, 0.0)

Border(holdView model)
.dock(Dock.Right)
.borderThickness(20.0, 0.0, 0.0, 250.0)
})
.background("#222222")
)
)
#endif
let create () =
let theme () = FluentTheme()
.title("Tetris")
.onKeyDown(fun e ->
match e.Key with
| Key.RightShift
| Key.LeftShift -> RotL
| Key.Space -> RotR
| Key.S -> Down
| Key.A -> Left
| Key.D -> Right
| Key.E -> Hold
| _ -> Empty)

let mainView model =
Grid() {
if model.isOver then
gameOverView().center()
else
Grid(rowdefs = [ Star; Auto ], coldefs = [ Star ]) {
Grid(rowdefs = [ Auto; Star ], coldefs = [ Star ]) {
TextBlock($"Score: {model.score}")
.fontSize(28.0)
.fontWeight(FontWeight.Bold)
.foreground(ThemeAware.With(Colors.White, Colors.Black))
.horizontalAlignment(HorizontalAlignment.Center)
.margin(Thickness(0.0, 56.0, 0.0, 0.0))

Border(boardView model).gridRow(1)
}

(howToPlayMobile model).gridRow(1)
}
|> _.background(Colors.Transparent)
}
|> _.background("#222222")

let program =
Program.statefulWithCmd init update
|> Program.withSubscription subscription
|> Program.withTrace(fun (format, args) -> Debug.WriteLine(format, box args))
|> Program.withExceptionHandler(fun ex ->

let program =
Program.statefulWithCmd init update
|> Program.withSubscription subscription
|> Program.withTrace(fun (format, args) -> Debug.WriteLine(format, box args))
|> Program.withExceptionHandler(fun ex ->
#if DEBUG
printfn $"Exception: %s{ex.ToString()}"
false
printfn $"Exception: %s{ex.ToString()}"
false
#else
true
true
#endif
)
|> Program.withView view
)

FabulousAppBuilder.Configure(theme, program)
let view () =
Component("TetrisPage") {
let! model = Context.Mvu program
#if MOBILE
SingleViewApplication(mainView model)
#else
DesktopApplication(mainWindow model)
#endif
}

let create () =
FabulousAppBuilder.Configure(FluentTheme, view)

0 comments on commit 97e1df7

Please sign in to comment.