Скала крестики-нолики без изменяемого состояния


Следующим было упражнение для меня, чтобы попытаться понять, как государство может передаваться вниз по стеку без непреложности.

Я привык к написанию Java-кода и это просто способ для меня, чтобы понять.

Я не пытался заставить компьютер играть в умные игры. Он просто выбирает случайное перемещение. Игрок всегда игрок X. всегда начинается сначала. Ходы делаются путем ввода индекса на основе 0 сетки

 0 | 1 | 2
---+---+---
 3 | 4 | 5
-----------
 6 | 7 | 8 

Однако мне было трудно писать и найти кусок, который вычисляет, если есть какие-либо выигрышная линия очень запутанна, чтобы читать.

import scala.annotation.tailrec
import scala.io.StdIn
import scala.util.{Success, Failure, Random,Try}

/**
 * A simple expirement in tic tac toe the idea
 * is to try to have something that contains state
 * without having vars.  
 */
object ttt extends App {

  object Player extends Enumeration {
    val X, O = Value
  }

  val grid: List[Option[Player.Value]] =
    None :: None :: None ::
      None :: None :: None ::
      None :: None :: None ::
      Nil

  val winningLines = List(0, 1, 2) ::
    List(3, 4, 5) ::
    List(6, 7, 8) ::
    List(0, 3, 6) ::
    List(1, 4, 7) ::
    List(2, 5, 8) ::
    List(1, 4, 8) ::
    List(2, 4, 6) :: Nil
  @tailrec
  def play(next: Player.Value, grid: List[Option[Player.Value]]): String = {
    val sliceHorizontal = (g:List[Option[Player.Value]]) => 
      g.slice(0, 3) :: 
      g.slice(3, 6) :: 
      g.slice(6, 9) :: Nil

    val toPrint = (p: Option[Player.Value]) => if (p.isEmpty) "   " else " " + p.get.toString() + " "
    val gridPrint = (g: List[List[Option[Player.Value]]]) => g.map(_.map(toPrint).mkString("|")).mkString("\n---+---+---\n")+"\n\n"
    println(gridPrint(sliceHorizontal(grid)))

    val emptyCells = (g: List[Option[Any]]) => g.zipWithIndex.
      filter(_._1.isEmpty).
      map(_._2)

    lazy val randomCell = Random.shuffle(emptyCells(grid)).head

    def playerChoice(g: List[Option[Player.Value]]): Int = {
      print("Please enter selection, ")
      val result = Try(StdIn.readInt());
      result match {
        case Success(v) => if (emptyCells(g).contains(v)) v else playerChoice(g)
        case Failure(_) => playerChoice(g)
      }
    }

    if (emptyCells(grid).isEmpty) {
      "Draw"
    } else {
      val newGrid = if (next == Player.O) {
        grid.updated(randomCell, Some(Player.O))
      } else {
        grid.updated(playerChoice(grid), Some(Player.X))
      }

      def listOfDuplicates[A](x: A, length: Int): List[A] = {
        if (length < 1) Nil
        else x :: listOfDuplicates(x, length - 1)
      }

      def allEqual[A](first: A, seq: List[A]):Boolean = {
        if (seq isEmpty) true
        else if (first != seq.head) false
        else allEqual(first, seq.tail)
      }

      //Okay this is very messy not used to this
      val hasWon= (p: Player.Value) => winningLines.
        foldLeft(false)((i, s) => i || allEqual(Some(p), s.map(newGrid(_)))) 
      if (hasWon(Player.X)) {
        println(gridPrint(sliceHorizontal(newGrid)))
        "X Wins"
      } else if (hasWon(Player.O)) {
        println(gridPrint(sliceHorizontal(newGrid)))
        "O Wins"
      } else play(if (next == Player.O) Player.X else Player.O, newGrid)
    }
  }

  println(play(Player.X, grid))
}

Как я могу прибраться и сделать его более устойчивым? легче читать? Также на крестики-нолики это не имеет большого значения, но для большой игры, если бы не хвост рекурсивные я думаю, что я не мог использовать эту стратегию для удержания состояния между ходами.



318
1
задан 11 февраля 2018 в 09:02 Источник Поделиться
Комментарии
1 ответ

Я не буду комментировать про свой алгоритм, как я понимаю, что это не фокус данного обзора - достаточно сказать, что есть лучшие способы проверки выигрышной, чем сравнивать против жестко выигрышных линий, даже в Ла Скала.

Кроме того, лестница имеет красивый и обширная стандартная библиотека, специально для коллекций - изучать и использовать!

В частности, нет порядка:


  • Явно не построить списков - использовать методы фабрики

    т. е., List.apply(values: T*), а вариативным методом, который может быть вызван с синтаксисом сахара как List(value1, value2, ... valueN)так же как и подобный fill и tabulate.

    В самом деле, fill делает ваш listOfDuplicates функция избыточна. Там же forall метод, принимающий логический предикат и список и возвращает все ли элементы списка удовлетворяет предикату или нет, что делает ваш allEqual способ тавтология.


  • Используйте соответствующие запросы и уведомления при взаимодействии с пользователем

    Например, когда вы рекурсию в playerChoiceвы не позволить пользователю знать, почему ты побудило его/ее для ввода снова. позвольте ему/ей знать, что должность занята или недействительна, прежде чем просить его/ее снова.


  • Использовать IndexedSeq для произвольного доступа immutable коллекции

    Здесь я рекомендую использовать IndexedSeq вместо Vector специально для того, чтобы убедиться, что мы типа интерфейсов - возможно, вы захотите поменять реализацию массив позже. Это также заставит вас обновить сигнатуры функций.

    Почему IndexedSeq вместо List? Вы используете updatedфункция, которая получает доступ к элементам по индексу. Это \$o(п)\ долл Listно амортизируется \$О(1)\ долл Vector или Array, которые следуют IndexedSeq интерфейс.


  • collect вместо прикован filter и map


collect позволяет сочетать filter и map, по существу, позволяет делать сопоставление с образцом на месте. Приводит к 1 за вычетом промежуточного сбора (видя, что вы не используете ленивый .viewС) и чистого кода.


  • Использование вложенных/локальные функции вместо функции литералы

    В Scala, вы можете поставить def в def и так далее. Это позволит вам обойти некрасиво List[Option[Any]] у вас есть для emptyCells и заменить его на правильный тип параметра. Вы делаете это для allEqual уже, я не знаю, почему вы не делаете это для всего остального (ты allEqual` откуда-то еще?).


  • Длинный псевдоним, часто используемых типов


Тип-псевдоним Player.Value для Player и импортировать его, чтобы уменьшить визуальный беспорядок. Также сделать то же самое с List[Option[Player.Value]] путем сглаживания его Grid чтобы уменьшить визуальный беспорядок еще больше.


  • Использовать более описательные имена переменных

    grid для g и value/position для v не повредит.


  • Правильный интервал и отступы

    Достаточно хороший, но несколько непоследовательно. Положите его через autoformatter среде IDE, например IntelliJ идея.


  • Некоторые общие идеи для улучшения


    • Включить чистый бросить механизма, может быть q в приглашении?

    • Меню помощи, где вы объяснить, как играть будет приятно.

    • Добавление выбора игроку не так сложно - попробуйте. Просто немного кода инициализации.


В целом, функциональное программирование идет о написании функций для написания программ; неизменяемость и прочее-это хорошо, но это не первично. У вас есть некоторые повторения, как в hasWon проверить - посмотреть, если вы можете избавиться от него (подсказка - вам может понадобиться функция, которая может выполнить итерации по значениям Player enum и проверить, если любой из них выиграли).

Кроме того, это


def hasWon(p: Player) = winningLines.foldLeft(false)((i, s) => i || allEqual(Some(p), s.map(newGrid(_))))

не очень некрасиво. Это полный бред, да, но что может быть с улучшенными лучше имена параметров (что-то я не буду делать для вас прямо сейчас - я надеюсь, что я указал, как это сделать самостоятельно). Не волнуйся - ты привыкнешь к нему. Сейчас я нахожу, что foldLeft легче читать, чем несколько вложенных for петли.

Вот текущий код (я мог бы бросить в Позже больше улучшений):

import scala.annotation.tailrec
import scala.io.StdIn
import scala.util.{Success, Failure, Random,Try}

/**
* A simple expirement in tic tac toe the idea
* is to try to have something that contains state
* without having vars.
*/
object TicTacToe extends App {

object Player extends Enumeration {
val X, O = Value
type Player = Value
}

import Player._
type Grid = Seq[Option[Player]]

val grid: Grid = IndexedSeq.fill(9)(None)

val winningLines =
IndexedSeq(
IndexedSeq(0, 1, 2),
IndexedSeq(3, 4, 5),
IndexedSeq(6, 7, 8),
IndexedSeq(0, 3, 6),
IndexedSeq(1, 4, 7),
IndexedSeq(2, 5, 8),
IndexedSeq(1, 4, 8),
IndexedSeq(2, 4, 6))

@tailrec
def play(next: Player.Value, grid: Grid): String = {
def sliceHorizontal(g: Grid) =
IndexedSeq(
g.slice(0, 3),
g.slice(3, 6),
g.slice(6, 9))

def playerRepr(p: Option[Player]) = p.map(v => s" $v ").getOrElse(" ")
def gridRepr(g: Seq[Grid]) = g.map(_.map(playerRepr).mkString("|")).mkString("", "\n---+---+---\n", "\n\n")

def printGrid(g: Grid) = println(gridRepr(sliceHorizontal(g)))
printGrid(grid)

def emptyCells[T](g: Seq[Option[T]]) =
g.zipWithIndex.collect {
case (value, index) if value.isEmpty => index
}

lazy val randomCell = Random.shuffle(emptyCells(grid)).head

@tailrec
def playerChoice(g: Grid): Int = {
print("Please enter selection: ")
Try(StdIn.readInt()) match {
case Success(v) =>
if (emptyCells(g).contains(v)) v
else {
println("Position already occupied, please try a different one")
playerChoice(g)
}
case Failure(_) =>
println("Invalid Position - try a value between 0 and 8")
playerChoice(g)
}
}

if (emptyCells(grid).isEmpty) {
"Draw"
} else {
val newGrid = if (next == O) {
grid.updated(randomCell, Some(O))
} else {
grid.updated(playerChoice(grid), Some(X))
}

def allEqual[A](first: A, seq: Seq[A]): Boolean = seq.forall(_ == first)

def hasWon(p: Player) = winningLines.foldLeft(false)((i, s) => i || allEqual(Some(p), s.map(newGrid(_))))

if (hasWon(X)) {
printGrid(newGrid)
"X Wins"
} else if (hasWon(O)) {
printGrid(newGrid)
"O Wins"
} else {
play(if (next == O) X else O, newGrid)
}
}
}

println(play(Player.X, grid))
}

4
ответ дан 12 февраля 2018 в 10:02 Источник Поделиться