Rewrite the block system so that I can implement the catamorphism easily

This commit is contained in:
Aly 2020-07-02 18:47:20 -07:00
parent 33df0eb120
commit 8b52321920
11 changed files with 240 additions and 57 deletions

View File

@ -20,9 +20,12 @@ lazy val graph = (project in file("graph")).settings(
name := "fancadegraph",
version := "0.1.0",
scalaVersion := "2.13.3",
resolvers += Resolver.bintrayRepo("alexknvl", "maven"),
libraryDependencies ++= Seq(
"org.scala-graph" %% "graph-core" % "1.13.2",
"com.alexknvl" %% "polymorphic" % "0.5.0",
),
addCompilerPlugin("org.typelevel" %% "kind-projector" % "0.11.0" cross CrossVersion.full),
).dependsOn(scodec)
lazy val tagless = (project in file("tagless")).settings(
@ -36,6 +39,7 @@ lazy val tagless = (project in file("tagless")).settings(
"org.typelevel" %% "cats-core" % "2.1.1",
"org.typelevel" %% "cats-effect" % "2.1.3",
"io.chrisdavenport" %% "fuuid" % "0.4.0",
"io.higherkindness" %% "droste-core" % "0.8.0",
"org.scalameta" %% "munit" % "0.7.9" % Test,
),
testFrameworks += new TestFramework("munit.Framework"),

View File

@ -1,22 +1,14 @@
package tf.bug.fancadegraph
import polymorphic._
import tf.bug.fancadescodec.{Metadata, Position}
case class Block[T](
case class Block(
position: Position,
definition: BlockDefinition[T],
argument: T
)(
implicit ev: Argument[T]
template: Exists[Template]
) {
def metadata: Vector[Metadata] = ev.encode(argument)(position)
}
object Block {
def apply(position: Position, definition: BlockDefinition[Unit]): Block[Unit] =
new Block(
position,
definition,
()
)
def metadata: Vector[Metadata] = {
val t = Exists.unwrap(template)
t.ev.encode(t.args)(position)
}
}

View File

@ -1,14 +1,24 @@
package tf.bug.fancadegraph
import cats.data._
import polymorphic._
import scalax.collection.Graph
import scalax.collection.GraphEdge.DiHyperEdge
import tf.bug.{fancadescodec => fansc}
case class Level(
blocks: Set[Block[_]],
blocks: Set[Block],
wires: Graph[Pin, DiHyperEdge]
)
) {
def boundary =
blocks.foldLeft(fansc.Boundary(0, 0, 0)) {
case (fansc.Boundary(lr, du, nf), block) =>
val tlr = block.position.lr + Exists.unwrap(block.template).definition.width
val tdu = block.position.du + Exists.unwrap(block.template).definition.height
val tnf = block.position.nf + Exists.unwrap(block.template).definition.depth
fansc.Boundary(lr.max(tlr), du.max(tdu), nf.max(tnf))
}
}
object Level {
@ -17,26 +27,21 @@ object Level {
}
def encode(level: Level): tf.bug.fancadescodec.Entry = {
val boundary = level.blocks.foldLeft(fansc.Boundary(0, 0, 0)) {
case (fansc.Boundary(lr, du, nf), block) =>
val tlr = block.position.lr + block.definition.width
val tdu = block.position.du + block.definition.height
val tnf = block.position.nf + block.definition.depth
fansc.Boundary(lr.max(tlr), du.max(tdu), nf.max(tnf))
}
val boundary = level.boundary
val objs: Vector[fansc.Obj] = {
val initialVector = Vector.fill(boundary.sizeNF, boundary.sizeDU, boundary.sizeLR)(fansc.Obj.empty)
level.blocks.foldLeft(initialVector) {
case (ew, nb) =>
val fansc.Position(sx, sy, sz) = nb.position
(0 until nb.definition.depth).foldLeft(ew) {
val nbd = Exists.unwrap(nb.template).definition
(0 until nbd.depth).foldLeft(ew) {
case (zw, z) =>
(0 until nb.definition.height).foldLeft(zw) {
(0 until nbd.height).foldLeft(zw) {
case (yw, y) =>
(0 until nb.definition.width).foldLeft(yw) {
(0 until nbd.width).foldLeft(yw) {
case (xw, x) =>
val objInd = x + (y * nb.definition.width) + (z * nb.definition.width * nb.definition.height)
xw.updated(sz + z, xw(sz + z).updated(sy + y, xw(sz + z)(sy + y).updated(sx + x, nb.definition.ids(objInd))))
val objInd = x + (y * nbd.width) + (z * nbd.width * nbd.height)
xw.updated(sz + z, xw(sz + z).updated(sy + y, xw(sz + z)(sy + y).updated(sx + x, nbd.ids(objInd))))
}
}
}

View File

@ -3,6 +3,7 @@ package tf.bug.fancadegraph
import cats.effect.{Blocker, ExitCode, IO, IOApp}
import fs2._
import java.nio.file.{Paths, StandardOpenOption}
import polymorphic._
import scalax.collection.Graph
import scalax.collection.GraphEdge.DiHyperEdge
import scodec.stream.StreamEncoder
@ -11,8 +12,8 @@ import tf.bug.fancadescodec.{Cartridge, Codecs, Position}
object Main extends IOApp {
override def run(args: List[String]): IO[ExitCode] = {
val winBlock = Block(Position(0, 0, 0), BlockDefinition.Win, true)
val loseBlock = Block(Position(0, 0, 3), BlockDefinition.Lose, true)
val winBlock = Block(Position(0, 0, 0), Exists(Template(BlockDefinition.Win, true)))
val loseBlock = Block(Position(0, 0, 3), Exists(Template(BlockDefinition.Lose, true)))
val loseAfter = Pin(loseBlock, BlockDefinition.Lose.after)
val winBefore = Pin(winBlock, BlockDefinition.Win.before)
val level = Level(

View File

@ -1,6 +1,6 @@
package tf.bug.fancadegraph
case class Pin(
owner: Block[_],
owner: Block,
definition: PinDefinition
)

View File

@ -0,0 +1,3 @@
package tf.bug.fancadegraph
case class Template[T](definition: BlockDefinition[T], args: T)(implicit val ev: Argument[T])

View File

@ -1,35 +1,56 @@
package tf.bug.fancadetagless
import tf.bug.fancadegraph.Level
import tf.bug.fancadegraph.BlockDefinition
import cats._
import cats.implicits._
import higherkindness.droste._
import polymorphic._
import tf.bug.fancadegraph.PinDefinition
import tf.bug.fancadegraph.Level
import tf.bug.fancadegraph.Block
import tf.bug.fancadegraph.Template
import tf.bug.fancadescodec.Position
import scalax.collection.Graph
import tf.bug.fancadegraph.Argument
sealed trait Fancade {
sealed trait Fancade[A] {
val template: Exists[Template]
val pins: Vector[PinDefinition]
def render: Level
}
object Fancade {
final case class Capture[T](newBlock: BlockDefinition[T], args: T, pins: Vector[PinDefinition])(implicit arg: Argument[T]) extends Fancade {
override def render: Level = {
Level(
Set(
Block(
Position(0, 0, 0),
newBlock,
args
)
),
Graph.empty
)
}
implicit val fancadeFunctor: Functor[Fancade] = new Functor[Fancade] {
override def map[A, B](fa: Fancade[A])(f: A => B): Fancade[B] =
fa match {
case Capture(template, pins) =>
Capture(template, pins)
case Use(children, template, inputs, pins) =>
Use(children.map(f), template, inputs, pins)
}
}
case class Capture[A](template: Exists[Template], pins: Vector[PinDefinition]) extends Fancade[A]
case class Use[A](children: Vector[A], template: Exists[Template], inputs: Vector[PinDefinition], pins: Vector[PinDefinition]) extends Fancade[A]
def flatten: Algebra[Fancade, PointerChain[Fancade]] = Algebra {
case Capture(template, pins) =>
PointerChain.single(Capture(template, pins))
case Use(children, template, inputs, pins) =>
???
}
val stack: FancadeF => PointerChain[Fancade] = scheme.cata(flatten)
def render(f: FancadeF): Level = {
val pointerChain = stack(f)
val blocks = pointerChain.map {
case (ind, fc) =>
Block(
Position(0, ind, 0),
fc.template
)
}
Level(
blocks.toSet,
???
)
}
final case class Use(inputs: Vector[Fancade])
}

View File

@ -1,6 +1,11 @@
package tf.bug.fancadetagless
import higherkindness.droste.data._
import polymorphic._
import tf.bug.fancadetagless.Fanscript.{Position, ScreenSize}
import tf.bug.fancadescodec.Metadata.Sample
import tf.bug.fancadegraph.BlockDefinition
import tf.bug.fancadegraph.Template
trait Fanscript[F[_]] {
@ -25,7 +30,7 @@ trait Fanscript[F[_]] {
def createObj(original: F[Obj]): F[Obj]
def destroyObj(obj: F[Obj]): F[Obj]
def playSound(loop: Boolean, sound: Nothing)(volume: F[Float], pitch: F[Float]): F[Float]
def playSound(loop: Boolean, sound: Sample)(volume: F[Float], pitch: F[Float]): F[Float]
def stopSound(channel: F[Float]): F[Unit]
def volumePitch(channel: F[Float], volume: F[Float], pitch: F[Float]): F[Unit]
@ -70,6 +75,92 @@ trait Fanscript[F[_]] {
object Fanscript {
trait Program[R] {
def run[F[_]](implicit interp: Fanscript[F]): F[R]
}
implicit object fancade extends Fanscript[FancadeW] {
override def lift(value: Float): FancadeW[Float] =
Fix(Fancade.Capture(
Exists(Template(
BlockDefinition.NumberValue,
value
)),
Vector(BlockDefinition.NumberValue.output)
))
override def lift(value: Vector3): FancadeW[Vector3] = ???
override def lift(value: Rotation): FancadeW[Rotation] = ???
override def lift(value: Boolean): FancadeW[Boolean] = ???
override def win(stop: Boolean): FancadeW[Unit] = ???
override def lose(stop: Boolean): FancadeW[Unit] = ???
override def setScore(score: FancadeW[Float]): FancadeW[Unit] = ???
override def setCamera(position: FancadeW[Vector3], rotation: FancadeW[Rotation], distance: FancadeW[Float]): FancadeW[Unit] = ???
override def setLight(position: FancadeW[Vector3], rotation: FancadeW[Rotation]): FancadeW[Unit] = ???
override def screenSize: FancadeW[ScreenSize] = ???
override def accelerometer: FancadeW[Vector3] = ???
override def getPosition(obj: FancadeW[Obj]): FancadeW[Position] =
Fix(Fancade.Use(
Vector(obj),
Exists(Template(
BlockDefinition.GetPosition,
()
)),
Vector(BlockDefinition.GetPosition.obj),
Vector(BlockDefinition.GetPosition.position)
))
override def setPosition(obj: FancadeW[Obj], position: FancadeW[Vector3], rotation: FancadeW[Rotation]): FancadeW[Unit] = ???
override def raycast(from: FancadeW[Vector3], to: FancadeW[Vector3]): FancadeW[Raycast] = ???
override def getSize(obj: FancadeW[Obj]): FancadeW[Size] = ???
override def setVisible(obj: FancadeW[Obj], visible: FancadeW[Boolean]): FancadeW[Unit] = ???
override def createObj(original: FancadeW[Obj]): FancadeW[Obj] = ???
override def destroyObj(obj: FancadeW[Obj]): FancadeW[Obj] = ???
override def playSound(loop: Boolean, sound: Sample)(volume: FancadeW[Float], pitch: FancadeW[Float]): FancadeW[Float] = ???
override def stopSound(channel: FancadeW[Float]): FancadeW[Unit] = ???
override def volumePitch(channel: FancadeW[Float], volume: FancadeW[Float], pitch: FancadeW[Float]): FancadeW[Unit] = ???
override def addForce(obj: FancadeW[Obj], force: FancadeW[Vector3], applyAt: FancadeW[Vector3], torque: FancadeW[Vector3]): FancadeW[Unit] = ???
override def getVelocity(obj: FancadeW[Obj]): FancadeW[Velocity] = ???
override def setVelocity(obj: FancadeW[Obj], velocity: FancadeW[Vector3], spin: FancadeW[Vector3]): FancadeW[Unit] = ???
override def setLocked(obj: FancadeW[Obj], position: FancadeW[Vector3], rotation: FancadeW[Vector3]): FancadeW[Unit] = ???
override def setMass(obj: FancadeW[Obj], mass: FancadeW[Float]): FancadeW[Unit] = ???
override def setFriction(obj: FancadeW[Obj], friction: FancadeW[Float]): FancadeW[Unit] = ???
override def setBounciness(obj: FancadeW[Obj], bounciness: FancadeW[Float]): FancadeW[Unit] = ???
override def setGravity(gravity: FancadeW[Vector3]): FancadeW[Unit] = ???
override def addConstraint(base: FancadeW[Obj], part: FancadeW[Obj], pivot: FancadeW[Vector3]): FancadeW[Constraint] = ???
override def linearLimits(constraint: FancadeW[Constraint], lower: FancadeW[Vector3], upper: FancadeW[Vector3]): FancadeW[Unit] = ???
override def angularLimits(constraint: FancadeW[Constraint], lower: FancadeW[Vector3], upper: FancadeW[Vector3]): FancadeW[Unit] = ???
override def linearSpring(constraint: FancadeW[Constraint], stiffness: FancadeW[Vector3], damping: FancadeW[Vector3]): FancadeW[Unit] = ???
override def angularSpring(constraint: FancadeW[Constraint], stiffness: FancadeW[Vector3], damping: FancadeW[Vector3]): FancadeW[Unit] = ???
override def linearMotor(constraint: FancadeW[Constraint], speed: FancadeW[Vector3], force: FancadeW[Vector3]): FancadeW[Unit] = ???
override def angularMotor(constraint: FancadeW[Constraint], speed: FancadeW[Vector3], force: FancadeW[Vector3]): FancadeW[Unit] = ???
override def ifElse(cond: FancadeW[Boolean])(ifTrue: FancadeW[Unit])(ifFalse: FancadeW[Unit]): FancadeW[Unit] = ???
override def onPlay(onPlay: FancadeW[Unit]): FancadeW[Unit] = ???
override def onBoxArt(onBoxArt: FancadeW[Unit]): FancadeW[Unit] = ???
override def onTouch(onTouch: FancadeW[Touch] => FancadeW[Unit]): FancadeW[Unit] = ???
override def screenSizeWidth(screenSize: FancadeW[ScreenSize]): FancadeW[Float] = ???
override def screenSizeHeight(screenSize: FancadeW[ScreenSize]): FancadeW[Float] = ???
override def positionPosition(position: FancadeW[Position]): FancadeW[Vector3] = ???
override def positionRotation(position: FancadeW[Position]): FancadeW[Rotation] = ???
override def raycastHit(raycast: FancadeW[Raycast]): FancadeW[Boolean] = ???
override def raycastPosition(raycast: FancadeW[Raycast]): FancadeW[Vector3] = ???
override def raycastObj(raycast: FancadeW[Raycast]): FancadeW[Obj] = ???
override def sizeMin(size: FancadeW[Size]): FancadeW[Vector3] = ???
override def sizeMax(size: FancadeW[Size]): FancadeW[Vector3] = ???
override def velocityVelocity(velocity: FancadeW[Velocity]): FancadeW[Vector3] = ???
override def velocitySpin(velocity: FancadeW[Velocity]): FancadeW[Vector3] = ???
}
case class ScreenSize(width: Float, height: Float)
case class Position(position: Vector3, rotation: Rotation)
case class Raycast(hit: Boolean, position: Vector3, obj: Obj)

View File

@ -0,0 +1,22 @@
package tf.bug.fancadetagless
import cats._
import cats.implicits._
case class PointerChain[F[_]](internal: Map[Int, F[Int]])(implicit functor: Functor[F]) {
def ++(other: PointerChain[F]): PointerChain[F] = {
???
}
def map[B](f: ((Int, F[Int])) => B): Vector[B] = internal.map(f).toVector
}
object PointerChain {
def empty[F[_] : Functor]: PointerChain[F] = PointerChain(Map.empty)
def single[F[_] : Functor](value: F[Nothing]): PointerChain[F] = PointerChain(Map(0 -> value.widen))
}

View File

@ -1,7 +1,10 @@
package tf.bug
import higherkindness.droste.data.Fix
package object fancadetagless {
type FancadeW[A] = Fancade
type FancadeF = Fix[Fancade]
type FancadeW[A] = FancadeF
}

View File

@ -5,10 +5,51 @@ import tf.bug.fancadegraph.BlockDefinition
class FancadeSuite extends munit.FunSuite {
test("A capture creates one block") {
val capture = Fancade.Capture[String](BlockDefinition.GetNumber, "hello", Vector(
val capture: FancadeF = Fix(Fancade.Capture[String](BlockDefinition.GetNumber, "hello", Vector(
BlockDefinition.GetNumber.output
)))
assertEquals(Fancade.render(capture).blocks.size, 1)
}
test("Stacking 1 + 1 results in a 2-element map") {
val one: FancadeF = Fix(Fancade.Capture(BlockDefinition.NumberValue, 1.0f, Vector(
BlockDefinition.NumberValue.output
)))
val add: FancadeF = Fix(Fancade.Use(
Vector(one, one),
BlockDefinition.AddNumbers,
(),
Vector(
BlockDefinition.AddNumbers.input1,
BlockDefinition.AddNumbers.input2
),
Vector(
BlockDefinition.AddNumbers.output
)
))
val stacked = Fancade.stack(add)
assertEquals(stacked, Map(
1 -> Fancade.Capture(BlockDefinition.NumberValue, 1.0f, Vector(BlockDefinition.NumberValue.output)),
0 -> Fancade.Use(Vector(1, 1), BlockDefinition.AddNumbers, (),
Vector(
BlockDefinition.AddNumbers.input1,
BlockDefinition.AddNumbers.input2
),
Vector(BlockDefinition.AddNumbers.output)
)
))
}
test("A constant creates a capture") {
val program = new Fanscript.Program[Float] {
override def run[F[_]](implicit interp: Fanscript[F]): F[Float] = interp.lift(1.0f)
}
val result = program.run[FancadeW]
assertEquals(result, Fancade.Capture(
BlockDefinition.NumberValue,
1.0f,
Vector(BlockDefinition.NumberValue.output)
))
assertEquals(capture.render.blocks.size, 1)
}
}