Skip to content

Instantly share code, notes, and snippets.

@arturopala
Last active August 29, 2015 14:03
Show Gist options
  • Save arturopala/63d4cd18cd0c335f38db to your computer and use it in GitHub Desktop.
Save arturopala/63d4cd18cd0c335f38db to your computer and use it in GitHub Desktop.
FrogStorkGame example of Scala language dealing with complexity
object Example3 extends App {
import games.board.xgame._
val game = new XGame() with reporting.ConsoleReporter
implicit val board = game.board(5, 5)
import Commands._
import BluePlayer.{ Frog => BlueFrog, Stork => BlueStork }
import RedPlayer.{ Frog => RedFrog, Stork => RedStork }
game play Forward(BlueFrog)
game play Forward(RedStork)
game play Forward(BlueStork)
game play Forward(RedFrog)
game play Left(RedStork)
game play Right(RedStork)
game play Forward(BlueStork)
game play Forward(BlueStork)
game play Right(BlueStork)
game play Right(BlueStork)
game play Right(BlueStork)
}
object games {
trait Command
trait Event {
override def toString = this.getClass().getName().split('$').last
}
trait Success extends Event
trait Failure extends Event
trait State
type Events = List[Event]
type Result = (Command, Events)
trait Game[T <: State] { def play(command: Command)(implicit state: T): Result }
object board {
trait Board extends State
trait BoardGame[T <: Board] extends Game[T]
trait Cell
type Position = (Int, Int)
type Orientation = (Int, Int) => (Int, Int)
trait Player { def orientation: Orientation }
implicit class IntPairExt(p: (Int, Int)) {
def +(v: (Int, Int)): Position = (p._1 + v._1, p._2 + v._2)
def <(v: (Int, Int)): Boolean = p._1 < v._1 && p._2 < v._2
def >=(v: (Int, Int)): Boolean = p._1 >= v._1 && p._2 >= v._2
}
object xgame {
type Transformation = (Position, Cell) => ((Position, Cell), Events)
case class Board(w: Int, h: Int) extends games.board.Board {
val size = (w, h)
private val cells: Array[Array[Cell]] = (for (i <- 1 to w) yield {
(for (j <- 1 to h) yield EmptyCell).toArray[Cell]
}).toArray[Array[Cell]]
private val positions: List[Position] = (for (i <- 0 until w; j <- 0 until h) yield (i, j)).toList
var count = 1;
def apply(x: Int, y: Int): Cell = cells(x)(y)
def apply(p: Position): Cell = apply(p._1, p._2)
def update(p: Position, cell: Cell): Unit = update(p._1, p._2, cell)
def update(x: Int, y: Int, cell: Cell): Unit = {
cells(x)(y) = cell
}
def findFirst(filter: Cell => Boolean): Option[(Position, Cell)] = {
positions.find(pos => filter(this(pos))).map(pos => (pos, this(pos)))
}
def findAll(filter: Cell => Boolean): List[(Position, Cell)] = {
positions.foldLeft(List.empty[(Position, Cell)]) {
case (acc, pos) =>
if (filter(this(pos))) { (pos, this(pos)) :: acc } else acc
}
}
def print(report: Any => Unit): Unit = {
def separator = report {
(for (i <- 0 until w) yield '-') mkString ("--", "---", "--")
}
separator
for (j <- 0 until h) {
report {
(for (i <- 0 until w) yield this(i, j)) map { case FullCell(f) => f.symbol; case _ => ' ' } mkString ("| ", " | ", " |")
}
separator
}
}
}
object EmptyCell extends Cell
case class Figure(player: Player, symbol: Char)
case class FullCell(figure: Figure) extends Cell
trait Frog
trait Stork
trait Player extends games.board.Player {
object Frog extends Figure(this, 'F') with Frog
object Stork extends Figure(this, 'S') with Stork
}
object BluePlayer extends Player {
val orientation = (x: Int, y: Int) => (1 * x, 1 * y)
override def toString = "Blue"
}
object RedPlayer extends Player {
val orientation = (x: Int, y: Int) => (-1 * x, -1 * y)
override def toString = "Red"
}
object Commands {
case class Forward(figure: Figure) extends Command
case class Left(figure: Figure) extends Command
case class Right(figure: Figure) extends Command
}
object Events {
object Moved extends Success
object CannotEscapeBoard extends Failure
object CannotMove extends Failure
object UnknownCommand extends Failure
object FrogWasEaten extends Failure
}
abstract class XGame extends BoardGame[Board] with reporting.Reporter {
def board(w: Int, h: Int): Board = {
val board = new Board(w, h)
board(0, 0) = FullCell(BluePlayer.Frog)
board(w - 1, 0) = FullCell(BluePlayer.Stork)
board(0, h - 1) = FullCell(RedPlayer.Stork)
board(w - 1, h - 1) = FullCell(RedPlayer.Frog)
report("New board:")
board.print(report)
return board
}
def byFigure(figure: Figure): Cell => Boolean = { case FullCell(fig) if fig == figure => true; case _ => false }
def move(figure: Figure, x: Int, y: Int)(implicit board: Board): ((Position, Cell)) => Events = {
case (position, cell) =>
val newposition = position + figure.player.orientation(x, y)
if (newposition >= (0, 0) && newposition < board.size) {
val toBeReplaced:Cell = board(newposition)
toBeReplaced match {
case EmptyCell => {
board(position) = EmptyCell
board(newposition) = cell
Events.Moved :: Nil
}
case FullCell(existingFigure) => {
if(figure.player == existingFigure.player){
Events.CannotMove :: Nil
} else {
(figure,existingFigure) match {
case (f1:Frog,f2:Frog) => {
Events.CannotMove :: Nil
}
case (s1:Stork,s2:Stork) => {
Events.CannotMove :: Nil
}
case (f:Frog,s:Stork) => {
board(position) = EmptyCell
Events.FrogWasEaten :: Nil
}
case (s:Stork,f:Frog) => {
board(position) = EmptyCell
board(newposition) = cell
Events.FrogWasEaten :: Nil
}
}
}
}
}
} else {
Events.CannotEscapeBoard :: Nil
}
}
def findFigureAndMove(figure: Figure, x: Int, y: Int)(implicit board: Board): Events = {
board findFirst byFigure(figure) map move(figure, x, y) getOrElse Nil
}
override def play(command: Command)(implicit board: Board): Result = {
val events: Events = command match {
case Commands.Forward(figure) => findFigureAndMove(figure, 0, 1)
case Commands.Left(figure) => findFigureAndMove(figure, 1, 0)
case Commands.Right(figure) => findFigureAndMove(figure, -1, 0)
case _ => List(Events.UnknownCommand)
}
report(s"${board.count}. $command -> $events")
board.print(report)
board.count += 1
(command, events)
}
}
}
}
}
package reporting {
trait Reporter { def report(event: Any): Unit }
trait ConsoleReporter extends Reporter {
def report(event: Any): Unit = Console.println(event)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment