Guest User

Untitled

a guest
Jul 19th, 2018
67
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 8.45 KB | None | 0 0
  1. package io.scalac
  2.  
  3. import akka.actor.typed.{ActorRef, ActorSystem, Behavior}
  4. import akka.actor.typed.scaladsl.{ActorContext, Behaviors}
  5. import akka.actor.typed.scaladsl.Behaviors.Receive
  6. import akka.util.Timeout
  7.  
  8. import scala.concurrent.duration._
  9. import scala.concurrent.Future
  10. import scala.util.Random
  11.  
  12. // goals:
  13. // 1) player can only submit moves as himself
  14. // 2) player cannot submit invalid moves
  15. // 3) system should honor move order
  16. // 4) we can use actor code from non actor code
  17. // 5) should support request-reply from the client code
  18.  
  19. object TicTacToe2 extends App {
  20. println("Starting TicTacToe2")
  21.  
  22. //primitives and utils
  23. sealed trait Pos {
  24. def asInt: Int
  25. }
  26. case object Zero extends Pos {
  27. def asInt: Int = 0
  28. }
  29. case object One extends Pos {
  30. def asInt: Int = 1
  31. }
  32. case object Two extends Pos {
  33. def asInt: Int = 2
  34. }
  35.  
  36. sealed trait Player
  37. case object XPlayer extends Player
  38. case object CirclePlayer extends Player
  39.  
  40. type Board = Seq[Seq[Option[Player]]]
  41.  
  42. def randomMove() = {
  43. Random.nextInt(3) match {
  44. case 0 =>
  45. Zero
  46. case 1 =>
  47. One
  48. case _ =>
  49. Two
  50. }
  51. }
  52.  
  53. // player proxies
  54. case class YourTurnX(b: Board, replyTo: ActorRef[MoveX])
  55. val playerXProxy = Behaviors.receiveMessage[YourTurnX]{ msg =>
  56. msg.replyTo ! MoveX(randomMove(), randomMove())
  57. Behaviors.same
  58. }
  59. case class YourTurnCircle(b: Board, replyTo: ActorRef[MoveCircle])
  60. val playerCircleProxy = Behaviors.receiveMessage[YourTurnCircle]{ msg =>
  61. msg.replyTo ! MoveCircle(randomMove(), randomMove())
  62. Behaviors.same
  63. }
  64.  
  65. case class MoveX(x: Pos, y: Pos)
  66. case class MoveCircle(x: Pos, y: Pos)
  67.  
  68. // translators
  69. def xTranslator(parent: ActorRef[GenericMove]) = Behaviors.receiveMessage[MoveX]{ msg =>
  70. val translated = GenericMove(XPlayer, msg.x, msg.y)
  71. parent ! translated
  72. Behaviors.stopped // usable one time
  73. }
  74.  
  75. def circleTranslator(parent: ActorRef[GenericMove]) = Behaviors.receiveMessage[MoveCircle]{ msg =>
  76. val translated = GenericMove(CirclePlayer, msg.x, msg.y)
  77. parent ! translated
  78. Behaviors.stopped // usable one time
  79. }
  80.  
  81. // manager
  82. sealed trait MoveCommand
  83. case class GenericMove(p: Player, x: Pos, y: Pos) extends MoveCommand
  84. sealed trait MoveResult extends MoveCommand {
  85. def currentBoard: Board
  86. def currentlyPlaced: Int = currentBoard.flatten.flatten.size
  87. }
  88. case class ConfirmedMove(currentBoard: Board) extends MoveResult
  89. case class RejectedMove(currentBoard: Board) extends MoveResult
  90.  
  91. trait Winner
  92. case object XWon extends Winner
  93. case object CircleWon extends Winner
  94. case object Draw extends Winner
  95.  
  96. def getWinner(currentBoard: Board): Option[Winner] = {
  97. val check1 = for {
  98. c <- Seq(Zero, One, Two)
  99. if currentBoard(Zero.asInt)(c.asInt) == currentBoard(One.asInt)(c.asInt)
  100. if currentBoard(Zero.asInt)(c.asInt) == currentBoard(Two.asInt)(c.asInt)
  101. } yield {
  102. currentBoard(Zero.asInt)(c.asInt)
  103. }
  104.  
  105. val check2 = for {
  106. c <- Seq(Zero, One, Two)
  107. if currentBoard(c.asInt)(Zero.asInt) == currentBoard(c.asInt)(One.asInt)
  108. if currentBoard(c.asInt)(Zero.asInt) == currentBoard(c.asInt)(Two.asInt)
  109. } yield {
  110. currentBoard(c.asInt)(Zero.asInt)
  111. }
  112.  
  113. //TODO: include wins going from corner to corner
  114.  
  115. val piecesPlaced = currentBoard.flatten.flatten.size
  116. val winningPiece = (check1 ++ check2).flatten.headOption
  117. winningPiece match {
  118. case None if piecesPlaced < 9 =>
  119. None // the game is on
  120. case None if piecesPlaced >= 9 =>
  121. Option(Draw)
  122. case Some(XPlayer) =>
  123. Option(XWon)
  124. case Some(CirclePlayer) =>
  125. Option(CircleWon)
  126. }
  127. }
  128.  
  129. def managerBehaviour(reportResultTo: ActorRef[Winner],
  130. board: ActorRef[UpdateBoard],
  131. x: ActorRef[YourTurnX],
  132. circle: ActorRef[YourTurnCircle],
  133. currentMove: Player): Receive[MoveCommand] = Behaviors.receive[MoveCommand] {
  134. case (ctx, msg) =>
  135. msg match {
  136. case m: MoveResult if getWinner(m.currentBoard).isDefined =>
  137. getWinner(m.currentBoard) match {
  138. case Some(Draw) =>
  139. println("Nobody won")
  140. reportResultTo ! Draw
  141. Behaviors.stopped
  142. case Some(w) =>
  143. println(s"${w} is the winner!")
  144. reportResultTo ! w
  145. Behaviors.stopped
  146. case None => //TODO: this should NOT happen
  147. println("Game is on!")
  148. Behaviors.same
  149. }
  150.  
  151.  
  152. case evt: ConfirmedMove if currentMove == XPlayer => // x has successfully placed a piece
  153. askForCircleMove(circle, ctx, evt)
  154. managerBehaviour(reportResultTo, board, x, circle, CirclePlayer)
  155.  
  156. case evt: ConfirmedMove if currentMove == CirclePlayer => // o has successfully placed a piece
  157. askForXMove(x, ctx, evt)
  158. managerBehaviour(reportResultTo, board, x, circle, XPlayer)
  159.  
  160. case evt: RejectedMove if currentMove == XPlayer => // x could NOT place a piece
  161. askForXMove(x, ctx, evt)
  162. managerBehaviour(reportResultTo, board, x, circle, XPlayer)
  163.  
  164. case evt: RejectedMove if currentMove == CirclePlayer => // o could NOT place a piece
  165. askForCircleMove(circle, ctx, evt)
  166. managerBehaviour(reportResultTo, board, x, circle, CirclePlayer)
  167.  
  168. case GenericMove(p, x, y) =>
  169. board ! UpdateBoard(p, x, y, ctx.self)
  170. Behaviors.same
  171. }
  172. }
  173.  
  174. private def askForXMove(x: ActorRef[YourTurnX], ctx: ActorContext[MoveCommand], evt: MoveResult) = {
  175. val translator = ctx.spawn(xTranslator(ctx.self), "x_" + System.currentTimeMillis() + "_" + Random.nextInt())
  176. x ! YourTurnX(evt.currentBoard, translator)
  177. }
  178.  
  179. private def askForCircleMove(circle: ActorRef[YourTurnCircle], ctx: ActorContext[MoveCommand], evt: MoveResult) = {
  180. val translator = ctx.spawn(circleTranslator(ctx.self), "o_" + System.currentTimeMillis() + "_" + Random.nextInt())
  181. circle ! YourTurnCircle(evt.currentBoard, translator)
  182. }
  183.  
  184. // board
  185. def updateTheBoard(oldBoard: Board, x: Pos, y: Pos, p: Player): Either[Throwable, Board] = {
  186. oldBoard(x.asInt)(y.asInt) match {
  187. case None =>
  188. val updatedRow = oldBoard(x.asInt).patch(y.asInt, Seq(Option(p)), 1)
  189. val updatedBoard = oldBoard.patch(x.asInt, Seq(updatedRow), 1)
  190. Right(updatedBoard)
  191. case Some(takenBy) =>
  192. Left(new Exception(s"Collision with ${takenBy}"))
  193. }
  194. }
  195.  
  196. def boardAsString(board: Board): String = board.map(_.map{
  197. case Some(XPlayer) => "x"
  198. case Some(CirclePlayer) => "o"
  199. case None => " "
  200. }.mkString("|")).mkString("\n")
  201.  
  202. def emptyBoard() = Seq.fill(3)(Seq.fill(3)(None))
  203.  
  204. case class UpdateBoard(p: Player, x: Pos, y: Pos, replyTo: ActorRef[MoveResult])
  205.  
  206. def boardUpdateBehaviour(board: Board): Behavior[UpdateBoard] = Behaviors.receiveMessage { msg =>
  207. updateTheBoard(board, msg.x, msg.y, msg.p) match {
  208. case Left(_) =>
  209. msg.replyTo ! RejectedMove(board)
  210. boardUpdateBehaviour(board)
  211. case Right(updated) =>
  212. println(s"Updating the Board ${msg}\n${boardAsString(updated)}")
  213. msg.replyTo ! ConfirmedMove(board)
  214. boardUpdateBehaviour(updated)
  215. }
  216. }
  217.  
  218.  
  219.  
  220. /// DEMO starts here
  221. case class StartDemo(replyTo: ActorRef[Winner])
  222.  
  223. val mainBehaviour = Behaviors.setup[StartDemo] { ctx =>
  224. val initialBoardState = emptyBoard()
  225. val board = ctx.spawn(boardUpdateBehaviour(initialBoardState), "game-board")
  226. val x = ctx.spawn(playerXProxy, "x-proxy")
  227. val circle = ctx.spawn(playerCircleProxy, "circle-proxy")
  228. val whoWillGoFirst = XPlayer
  229.  
  230. Behaviors.receiveMessage { msg =>
  231. val manager = ctx.spawn(managerBehaviour(msg.replyTo, board, x, circle, whoWillGoFirst), "game-manager")
  232.  
  233. manager ! RejectedMove(initialBoardState) // this will kick start the process
  234. Behaviors.empty
  235. }
  236. }
  237.  
  238.  
  239. trait External {
  240. def startTheGame(): Future[Winner]
  241. }
  242. case object ExternalAkkaTyped extends External {
  243. override def startTheGame(): Future[Winner] = {
  244. import akka.actor.typed.scaladsl.AskPattern._
  245.  
  246. val system = ActorSystem(mainBehaviour, "hello")
  247.  
  248. implicit val timeout: Timeout = 3.seconds
  249. implicit val scheduler = system.scheduler
  250. implicit val ec = system.executionContext
  251.  
  252. system ? (ref => StartDemo(ref)) // this is untyped!
  253. }
  254. }
  255.  
  256. import scala.concurrent.ExecutionContext.Implicits.global
  257. ExternalAkkaTyped.startTheGame().onComplete { r =>
  258. println(s"Completed the game with ${r}")
  259. }
  260. }
Add Comment
Please, Sign In to add comment