Skip to content

Instantly share code, notes, and snippets.

@nebtrx
Forked from jdegoes/GameWorld.scala
Created April 6, 2018 14:58
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nebtrx/04b9c0e517d6771ec945745abffef328 to your computer and use it in GitHub Desktop.
Save nebtrx/04b9c0e517d6771ec945745abffef328 to your computer and use it in GitHub Desktop.
object game {
case class Lens[S, A](set: A => S => S, get: S => A) { self =>
def >>> [B](that: Lens[A, B]): Lens[S, B] =
Lens[S, B](
set = (b: B) => (s: S) => self.set(that.set(b)(self.get(s)))(s),
get = (s: S) => that.get(self.get(s))
)
}
case class Prism[S, A](set: A => S, get: S => Option[A]) { self =>
def >>> [B](that: Prism[A, B]): Prism[S, B] =
Prism[S, B](
set = (b: B) => self.set(that.set(b)),
get = (s: S) => self.get(s).flatMap(that.get)
)
}
def _1[A, B]: Lens[(A, B), A] =
Lens[(A, B), A]((a: A) => (t: (A, B)) => (a, t._2), _._1)
def _2[A, B]: Lens[(A, B), B] =
Lens[(A, B), B]((b: B) => (t: (A, B)) => (t._1, b), _._2)
case class Player(
name : String,
inventory : List[Item],
level : Int,
position : (Int, Int)
)
object Player {
val _Name = Lens[Player, String]((name: String) => (player: Player) => player.copy(name = name), _.name)
val _Level = Lens[Player, Int]((level: Int) => (player: Player) => player.copy(level = level), _.level)
val _Position = Lens[Player, (Int, Int)]((t: (Int, Int)) => (player: Player) => player.copy(position = t), _.position)
}
case class Item(describe: String, name: String)
sealed trait Cell {
def look: String
def toChar: Char
def items: List[Item]
}
case class Land(description: String = "Green rolling hills and shrubbery", items: List[Item] = Nil) extends Cell {
def look = description
def toChar = '_'
}
case class Sea(description: String = "Crystal clear, blue ocean", items: List[Item] = Nil) extends Cell {
def look = description
def toChar = '~'
}
case class GameMap(world: Vector[Vector[Cell]]) {
def cellAt(tuple: (Int, Int)): Option[Cell] = {
val (x, y) = tuple
for {
column <- world.lift(x)
cell <- column.lift(y)
} yield cell
}
def items(position: (Int, Int)): List[Item] = cellAt(position).map(_.items).getOrElse(Nil)
}
case class GameWorld(
player: Player,
map: GameMap
) {
def render: String = {
val chars0: Vector[Vector[Char]] = map.world.map(_.map(_.toChar))
val row = chars0(player.position._1)
val chars = chars0.updated(player.position._1, row.updated(player.position._2, 'X'))
"\n" + chars.map(_.map(_.toString()).mkString("|")).mkString("\n") + "\n"
}
}
object GameWorld {
val _Player : Lens[GameWorld, Player] =
Lens[GameWorld, Player]((player: Player) => (world: GameWorld) => world.copy(player = player), _.player)
val _Map : Lens[GameWorld, GameMap] =
Lens[GameWorld, GameMap]((map: GameMap) => (world: GameWorld) => world.copy(map = map), _.map)
val Initial =
GameWorld(
Player(
name = "John Doe",
inventory = Nil,
level = 1,
position = (0, 0)
),
GameMap(
Vector(
Vector(
Land("A mountain peak with a beautiful vista of the nearby city"),
Sea(),
Land()
),
Vector(
Land(),
Land(),
Land()
),
Vector(
Sea(),
Sea(),
Land()
)
)
)
)
}
case class Character()
sealed trait Command
case class Move(direction: Direction) extends Command
case class Attack(character: Character) extends Command
case class Look(what: Option[Either[Item, Character]]) extends Command
case class Take(what: String) extends Command
case class Combine(item1: Item, item2: Item) extends Command
case class Use(item: Item) extends Command
case object Exit extends Command
object Command {
val _Move = Prism[Command, Direction]((d: Direction) => Move(d), {
case Move(d) => Some(d)
case _ => None
})
}
sealed trait Direction
case object North extends Direction
case object South extends Direction
case object West extends Direction
case object East extends Direction
object Direction {
val _North = Prism[Direction, Unit](_ => North, {
case North => Some(())
case _ => None
})
val _South = Prism[Direction, Unit](_ => South, {
case South => Some(())
case _ => None
})
val _West = Prism[Direction, Unit](_ => West, {
case West => Some(())
case _ => None
})
val _East = Prism[Direction, Unit](_ => East, {
case East => Some(())
case _ => None
})
}
def parse(line: String): Either[String, Command] = {
val words = line.toLowerCase.split("\\s+")
if (words.length == 0) Right(Look(None))
else {
words(0) match {
case "exit" | "quit" | "bye" => Right(Exit)
case "look" => Right(Look(None))
case "go" | "move" =>
if (words.length < 2) Left("Specify a direction to move.")
else {
words(1) match {
case "south" => Right(Move(South))
case "north" => Right(Move(North))
case "west" => Right(Move(West))
case "east" => Right(Move(East))
case _ => Left("Unrecognized direction")
}
}
case "take" =>
if (words.length < 2) Left("Specify an item to take.")
else Right(Take(words(1)))
case _ => Left("I don't recognize your command.")
}
}
}
case class Update(message: String, next: Option[GameWorld])
def update(command: Command, world: GameWorld): Update = {
val unchanged = Update("Too lazy to implement", Some(world))
command match {
case Move(dir) =>
val (deltaX, deltaY) =
dir match {
case North => (0, 1)
case South => (0, -1)
case East => (1, 0)
case West => (-1, 0)
}
val (x, y) = world.player.position
val p = (x + deltaX, y + deltaY)
world.map.cellAt(p).map(cell => {
import GameWorld._
import Player._
val newWorld = (_Player >>> _Position).set(p)(world)
val render = newWorld.render
val around = cell.look
val items = "You can see the following items: " + newWorld.map.items(p).mkString(", ") + "\n"
Update(render + around + items, Some(newWorld))
}).getOrElse(
Update("You cannot move past the edge of the world!", Some(world))
)
case Attack(_) => unchanged
case Look(Some(_)) => unchanged
case Look(None) =>
val cell: Option[Cell] = world.map.cellAt(world.player.position)
cell.map { cell =>
Update(cell.look, Some(world))
}.getOrElse(Update("You are located outside of time and space. Bye-bye!", None))
case Take(name) =>
val items = world.map.items(world.player.position)
val (matching, _) = items.partition(_.name == name)
if (matching.length == 0) {
Update("There is no " + name + " to take!", Some(world))
} else {
Update("You have taken: " + matching.map(_.name).mkString(", "),
Some(world.copy(
player = world.player.copy(inventory = world.player.inventory ++ matching)
))
)
}
case Combine(_, _) => unchanged
case Use(_) => unchanged
case Exit => Update("Bye, bye!", None)
}
}
def gameStep[F[_]: Monad: ConsoleIO](world: GameWorld): F[Option[GameWorld]] =
for {
input <- ConsoleIO[F].readLine
world <- parse(input) match {
case Left(error) => ConsoleIO[F].println(error).map(_ => Some(world))
case Right(command) =>
val Update(message, world2) = update(command, world)
ConsoleIO[F].println(message).map(_ => world2)
}
} yield world
def gameLoop[F[_]: Monad: ConsoleIO](world: GameWorld): F[Unit] =
gameStep(world).flatMap {
case None => Monad[F].point(())
case Some(world) => gameLoop(world)
}
trait Monad[F[_]] {
def point[A](a: A): F[A]
def bind[A, B](fa: F[A])(afb: A => F[B]): F[B]
def fmap[A, B](ab: A => B): F[A] => F[B]
}
object Monad {
def apply[F[_]](implicit F: Monad[F]): Monad[F] = F
}
implicit class MonadSyntax[F[_], A](fa: F[A]) {
def map[B](ab: A => B)(implicit F: Monad[F]): F[B] =
F.fmap(ab)(fa)
def flatMap[B](afb: A => F[B])(implicit F: Monad[F]): F[B] =
F.bind(fa)(afb)
}
implicit class PointSyntax[A](a: A) {
def point[F[_]](implicit F: Monad[F]): F[A] =
F.point(a)
}
final abstract class Void {
def absurd[A]: A
}
final case class IO[E, A](
unsafePerformIO: () => Either[E, A]) { self =>
def map[B](f: A => B): IO[E, B] =
IO(() => {
self.unsafePerformIO() match {
case Left(e) => Left(e)
case Right(a) => Right(f(a))
}
})
def flatMap[B](f: A => IO[E, B]): IO[E, B] =
IO(() => {
self.unsafePerformIO() match {
case Left(e) => Left(e)
case Right(a) => f(a).unsafePerformIO()
}
})
def mapError[E2](f: E => E2): IO[E2, A] =
IO(() => self.unsafePerformIO() match {
case Left(e) => Left(f(e))
case Right(a) => Right(a)
})
def attempt: IO[Void, Either[E, A]] =
IO(() => Right(self.unsafePerformIO()))
def fork: IO[Void, IO[E, A]] = IO(() => {
import java.util.concurrent._
val task = ForkJoinTask.adapt(new Callable[Either[E, A]] {
def call(): Either[E, A] = self.unsafePerformIO()
})
IO.MainPool.invoke(task)
Right(IO(() => task.join()))
})
}
object IO {
import java.util.concurrent._
private[IO] val MainPool: ForkJoinPool = new ForkJoinPool()
def sync[A](effect: => A): IO[Exception, A] =
IO(() => {
try Right(effect)
catch { case e : Exception => Left(e) }
})
def point[E, A](a: A): IO[E, A] = IO(() => Right(a))
def fail[E, A](e: E): IO[E, A] = IO(() => Left(e))
def absolve[E, A](io: IO[Void, Either[E, A]]): IO[E, A] =
IO(() => {
io.unsafePerformIO() match {
case Left(void) => void.absurd[Either[E, A]]
case Right(e) => e
}
})
implicit def MonadIO[E]: Monad[IO[E, ?]] = {
new Monad[IO[E, ?]] {
def bind[A, B](fa: IO[E, A])(afb: A => IO[E, B]): IO[E, B] = fa.flatMap(afb)
def fmap[A, B](ab: A => B): IO[E, A] => IO[E, B] = (fa: IO[E, A]) => fa.map(ab)
def point[A](a: A): IO[E, A] = IO.point(a)
}
}
implicit val ConsoleIOIO: ConsoleIO[IO[Exception, ?]] = {
new ConsoleIO[IO[Exception, ?]] {
def println(line: String): IO[Exception, Unit] = IO.sync(scala.Console.println(line))
def readLine: IO[Exception, String] = IO.sync(scala.io.StdIn.readLine())
}
}
implicit val LoggingIO: Logging[IO[Exception, ?]] = {
new Logging[IO[Exception, ?]] {
def log(level: Level)(line: => String): IO[Exception, Unit] =
IO.sync {
val logger = java.util.logging.Logger.getLogger("IO")
logger.log(level match {
case Level.Info => java.util.logging.Level.INFO
case Level.Debug => java.util.logging.Level.FINE
}, line)
}
}
}
}
class IORef[A] private (ref: java.util.concurrent.atomic.AtomicReference[A]) {
def set(a: A): IO[Void, Unit] = IO(() => Right(ref.set(a)))
def get: IO[Void, A] = IO(() => Right(ref.get()))
def modify(f: A => A): IO[Void, A] = IO(() => {
var loop = true
var next : A = null.asInstanceOf[A]
while (loop) {
val old = ref.get()
next = f(old)
loop = !ref.compareAndSet(old, next)
}
Right(next)
})
}
object IORef {
def apply[A](a: A): IO[Void, IORef[A]] =
IO(() => Right(new IORef[A](new java.util.concurrent.atomic.AtomicReference(a))))
}
trait ConsoleIO[F[_]] {
def println(line: String): F[Unit]
def readLine: F[String]
}
object ConsoleIO {
def apply[F[_]](implicit F: ConsoleIO[F]): ConsoleIO[F] = F
}
sealed trait Level
object Level {
case object Info extends Level
case object Debug extends Level
}
trait Logging[F[_]] {
def log(level: Level)(line: => String): F[Unit]
}
object Logging {
def apply[F[_]](implicit F: Logging[F]): Logging[F] = F
}
def main[F[_]: Monad: ConsoleIO]: F[Unit] =
for {
_ <- ConsoleIO[F].println("Hello. What is your name?")
n <- ConsoleIO[F].readLine
_ <- ConsoleIO[F].println("Hello, " + n + ", welcome to the game!")
world = GameWorld.Initial.copy(player = GameWorld.Initial.player.copy(name = n))
_ <- ConsoleIO[F].println("Enter some command to begin!")
_ <- gameLoop(world)
} yield ()
val mainIO: IO[Exception, Unit] = {
type IOE[A] = IO[Exception, A]
main[IOE]
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment