All Downloads are FREE. Search and download functionalities are using the official Maven repository.

sss.openstar.message.payloads.TicTacToeGameMessage.scala Maven / Gradle / Ivy

package sss.openstar.message.payloads

import sss.ancillary.Logging
import sss.openstar.UniqueNodeIdentifier
import sss.openstar.message.payloads.TicTacToeGameMessage.GameState
import sss.openstar.message.payloads.TicTacToeGameMessage.TicTacToeState.TicTacToeState
import sss.openstar.message.{MessageComposite, MessageUpdater}


object TicTacToeGameMessage {

  private val maxMoves = 9

  private val r1c1 = 0.toByte
  private val r1c2 = 1.toByte
  private val r1c3 = 2.toByte

  private val r2c1 = 3.toByte
  private val r2c2 = 4.toByte
  private val r2c3 = 5.toByte

  private val r3c1 = 6.toByte
  private val r3c2 = 7.toByte
  private val r3c3 = 8.toByte

  private val winningCombos: Seq[Seq[Byte]] = Seq(
    Seq(r1c1, r1c2, r1c3),
    Seq(r2c1, r2c2, r2c3),
    Seq(r3c1, r3c2, r3c3),
    Seq(r1c1, r2c1, r3c1),
    Seq(r1c2, r2c2, r3c2),
    Seq(r1c3, r2c3, r3c3),
    Seq(r1c1, r2c2, r3c3),
    Seq(r3c1, r2c2, r1c3),
  )

  /**
    * Could be generated from
    * for {
    * a <- Seq()
    */

  val x = 1.toByte
  val o = 0.toByte

  object TicTacToeState extends Enumeration {
    type TicTacToeState = Value
    val notOver = Value(0)
    val xWinner = Value(1)
    val oWinner = Value(2)
    val noWinner = Value(3)
  }

  private val initialState: Seq[Option[Byte]] = Seq.fill(maxMoves)(None)

  case class GameState(boardState: Seq[Option[Byte]] = initialState) extends Logging {

    require(boardState.size == maxMoves, s"Tic Tac Toe needs $maxMoves states, not ${boardState.size}")

    lazy val numMoves: Int = boardState.foldLeft(0)((acc, e) => if (e.isDefined) acc + 1 else acc)

    def checkCombo(winCombo: Seq[Byte]): Option[(TicTacToeState, Seq[Byte])] = {
      (boardState(winCombo(0)), boardState(winCombo(1)), boardState(winCombo(2))) match {
        case (Some(`o`), Some(`o`), Some(`o`)) => Some(TicTacToeState.oWinner, winCombo)
        case (Some(`x`), Some(`x`), Some(`x`)) => Some(TicTacToeState.xWinner, winCombo)
        case _ => None
      }
    }

    val state: (TicTacToeState, Option[Seq[Byte]]) = {
      winningCombos.view.flatMap(checkCombo).headOption match {
        case None =>
          if (numMoves == maxMoves) (TicTacToeState.noWinner, None)
          else (TicTacToeState.notOver, None)
        case Some((winner, winningLine)) => (winner, Some(winningLine))
      }
    }

    def apply(nextState: GameState): GameState = {

      if (state._1 == TicTacToeState.notOver) {
        var moveCount = 0

        val isGoodMove = boardState zip nextState.boardState forall {
          case (Some(s1), Some(s2)) if s1 == s2 => true
          case (None, None) => true
          case (Some(s1), _) => false // cannot change a move
          case (None, Some(_)) if moveCount <= 1 =>
            //this won't catch a move made *for* you by an opponent
            moveCount += 1
            true
          case _ => ???
        }

        if (isGoodMove) nextState
        else {
          log.error(s"Foiled attempted cheating move.")
          this
        }

      } else {
        log.error(s"Attempted move on already ended game.")
        this
      }

    }
  }

}

case class TicTacToeGameMessage(originator: UniqueNodeIdentifier,
                                opponent: UniqueNodeIdentifier,
                                moverSig: Array[Byte],
                                nextMover: UniqueNodeIdentifier,
                                gameState: GameState
                               ) extends MessageComposite
  with Logging {

  private def isSigOfExpectedMover(sig: Array[Byte], identifier: UniqueNodeIdentifier): Boolean = true

  override def apply[C >: MessageComposite](m: MessageUpdater): C = m match {

    case tic@TicTacToeGameMessage(orig, oppo, mvrSg, nxtMvr, state) =>
      if (originator == orig && oppo == opponent) {
        if (isSigOfExpectedMover(mvrSg, nextMover)) {

          if ((nextMover == opponent && nxtMvr == originator) ||
            (nextMover == originator && nxtMvr == opponent)) {

            copy(gameState = gameState(state), nextMover = nxtMvr)
          } else {
            log.error(s"This TicTacToe has a bad next mover $nextMover, reject move")
            this
          }
        } else {
          log.error(s"This TicTacToe has a bad sig, not from $nextMover, reject move")
          this
        }
      } else {
        log.error(s"This TicTacToe has $originator,$opponent and that's not compatible with $orig, $oppo")
        this
      }


    case x =>
      log.error(s"TicTacToecould not merge ${x.getClass}")
      this
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy