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

axle.game.Game.scala Maven / Gradle / Ivy

The newest version!

package axle.game

import scala.Stream.cons
import scala.Stream.empty
import scala.util.Random.nextInt

import spire.compat.integral
import spire.implicits.eqOps
import spire.math.Real

abstract class Game[G <: Game[G]] {

  self: G =>

  type PLAYER <: Player[G]
  type STATE <: State[G]
  type EVENT <: Event[G]
  type MOVE <: Move[G]
  type OUTCOME <: Outcome[G]

  def players: Set[G#PLAYER]

  def introMessage: String

  def startState: G#STATE

  def startFrom(s: G#STATE): Option[G#STATE]

  def minimax(state: G#STATE, depth: Int, heuristic: G#STATE => Map[G#PLAYER, Real]): (G#MOVE, G#STATE, Map[G#PLAYER, Real]) =
    if (state.outcome.isDefined || depth <= 0) {
      (null.asInstanceOf[MOVE], null.asInstanceOf[G#STATE], heuristic(state)) // TODO null
    } else {
      // TODO: .get
      val moveValue = state.moves.map(move => {
        val newState = state(move).get // TODO: .get
        (move, state, minimax(newState, depth - 1, heuristic)._3)
      })
      val bestValue = moveValue.map(mcr => (mcr._3)(state.player)).max
      val matches = moveValue.filter(mcr => (mcr._3)(state.player) === bestValue).toIndexedSeq
      matches(nextInt(matches.length))
    }

  /**
   * α-β pruning generalized for N-player non-zero-sum games
   *
   * 2-player zero-sum version described at:
   *
   *   http://en.wikipedia.org/wiki/Alpha-beta_pruning
   *
   */

  def alphabeta(state: G#STATE, depth: Int, heuristic: G#STATE => Map[G#PLAYER, Double]): (G#MOVE, Map[G#PLAYER, Double]) =
    _alphabeta(state, depth, players.map((_, Double.MinValue)).toMap, heuristic)

  case class AlphaBetaFold(move: G#MOVE, cutoff: Map[G#PLAYER, Double], done: Boolean) {

    def process(m: G#MOVE, state: G#STATE, heuristic: G#STATE => Map[G#PLAYER, Double]): AlphaBetaFold =
      if (done) {
        this
      } else {
        val α = heuristic(state(m).get)
        if (cutoff(state.player) <= α(state.player)) // TODO: forall other players ??
          AlphaBetaFold(m, α, false) // TODO move = m?
        else
          AlphaBetaFold(m, cutoff, true)
      }
  }

  def _alphabeta(state: G#STATE, depth: Int, cutoff: Map[G#PLAYER, Double], heuristic: G#STATE => Map[G#PLAYER, Double]): (G#MOVE, Map[G#PLAYER, Double]) =
    if (state.outcome.isDefined || depth <= 0) {
      (null.asInstanceOf[G#MOVE], heuristic(state)) // TODO null
    } else {
      val result = state.moves.foldLeft(AlphaBetaFold(null.asInstanceOf[G#MOVE], cutoff, false))(
        (in: AlphaBetaFold, move: G#MOVE) => in.process(move, state, heuristic)
      )
      (result.move, result.cutoff)
    }

  def moveStateStream(s0: G#STATE): Stream[(G#MOVE, G#STATE)] =
    if (s0.outcome.isDefined) {
      empty
    } else {
      val s1 = s0.displayEvents(Set(s0.player))
      val (move, _) = s1.player.move(s1) // TODO: figure out why in some cases the second argument (a State) wasn't modified (eg minimax)
      val s2 = s1(move).get // TODO .get
      val s3 = s2.broadcast(players, move)
      cons((move, s3), moveStateStream(s3))
    }

  def scriptedMoveStateStream(state: G#STATE, moveIt: Iterator[G#MOVE]): Stream[(G#MOVE, G#STATE)] =
    if (state.outcome.isDefined || !moveIt.hasNext) {
      empty
    } else {
      val move = moveIt.next
      val nextState = state(move).get // TODO .get
      cons((move, nextState), scriptedMoveStateStream(nextState, moveIt))
    }

  def play(start: G#STATE = startState, intro: Boolean = true): Option[G#STATE] = {
    if (intro) {
      players foreach { player =>
        player.introduceGame()
      }
    }
    moveStateStream(start).lastOption.map({
      case (lastMove, s0) => {
        val s1 = s0.outcome.map(o => s0.broadcast(players, o)).getOrElse(s0)
        val s2 = s1.displayEvents(players)
        players foreach { player =>
          player.endGame(s2)
        }
        s2
      }
    })
  }

  def gameStream(start: G#STATE, intro: Boolean = true): Stream[G#STATE] =
    play(start, intro).flatMap(end => {
      startFrom(end).map(newStart =>
        cons(end, gameStream(newStart, false))
      )
    }).getOrElse(empty)

  def playContinuously(start: G#STATE = startState): G#STATE =
    gameStream(start).last

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy