Functional alternative to the game cycle

I am just starting out with Scala and trying a small toy program - in this case the text TicTacToe. I wrote a working version based on what I know about scala, but noticed that it was mostly mandatory and my classes were mutable.

I worry and try to implement some functional idioms and managed to at least make the classes that represent the state of the game unchanged. However, I still have the class responsible for executing the game loop, relying on mutable state and imperative loop as follows:

var board: TicTacToeBoard = new TicTacToeBoard def start() { var gameState: GameState = new XMovesNext outputState(gameState) while (!gameState.isGameFinished) { val position: Int = getSelectionFromUser board = board.updated(position, gameState.nextTurn) gameState = getGameState(board) outputState(gameState) } } 

What would be a more idiomatic way of programming what I'm doing imperatively in this loop?

The full source code is here https://github.com/whaley/TicTacToe-in-Scala/tree/master/src/main/scala/com/jasonwhaley/tictactoe

+4
source share
4 answers

You can implement it as a recursive method. Here's an unrelated example:

 object Guesser extends App { val MIN = 1 val MAX = 100 readLine("Think of a number between 1 and 100. Press enter when ready") def guess(max: Int, min: Int) { val cur = (max + min) / 2 readLine("Is the number "+cur+"? (y/n) ") match { case "y" => println("I thought so") case "n" => { def smallerGreater() { readLine("Is it smaller or greater? (s/g) ") match { case "s" => guess(cur - 1, min) case "g" => guess(max, cur + 1) case _ => smallerGreater() } } smallerGreater() } case _ => { println("Huh?") guess(max, min) } } } guess(MAX, MIN) } 
+5
source

imho for Scala, the imperative loop is just fine. You can always write a recursive function to behave like a loop. I also added some templates.

 def start() { def loop(board: TicTacToeBoard) = board.state match { case Finished => Unit case Unfinished(gameState) => { gameState.output() val position: Int = getSelectionFromUser() loop(board.updated(position)) } } loop(new TicTacToeBoard) } 

Suppose we have a function whileSome : (a -> Option[a]) a -> () , which runs the input function until its result is equal. This will undo the small pattern.

 def start() { def step(board: TicTacToeBoard) = { board.gameState.output() val position: Int = getSelectionFromUser() board.updated(position) // returns either Some(nextBoard) or None } whileSome(step, new TicTacToeBoard) } 

whileSome should be trivial to write; it is just an abstraction of the old drawing. I'm not sure if this is in any common Scala libs, but in Haskell you can grab whileJust_ from monad-loops .

+6
source

How about something like:

 Stream.continually(processMove).takeWhile(!_.isGameFinished) 

where processMove is a function that gets a selection from the user, updates the board and returns a new state.

+1
source

I would go with a recursive version, but here was the correct implementation of the Stream version:

var board: TicTacToeBoard = new TicTacToeBoard

 def start() { def initialBoard: TicTacToeBoard = new TicTacToeBoard def initialGameState: GameState = new XMovesNext def gameIterator = Stream.iterate(initialBoard -> initialGameState) _ def game: Stream[GameState] = { val (moves, end) = gameIterator { case (board, gameState) => val position: Int = getSelectionFromUser val updatedBoard = board.updated(position, gameState.nextTurn) (updatedBoard, getGameState(board)) }.span { case (_, gameState) => !gameState.isGameFinished } (moves ::: end.take(1)) map { case (_, gameState) => gameState } } game foreach outputState } 

It looks weirder than necessary. Ideally, I would use takeWhile and then map after that, but it will not work, as the last case will be left out!

If the game’s progress can be discarded, then dropWhile followed by head will work. If I had a side effect ( outputState ) instead of Stream , I could go through this route, but having a side effect inside Stream much worse than a var with a while .

So, I use a span that gives me both takeWhile and dropWhile , but makes me save intermediate results - which can be very bad if the memory is troubling, as the whole game will be stored in memory, because moves points to the Stream head . So I had to encapsulate it all in another method, game . That way, when I foreach through the results of the game , nothing will point to the Stream head .

Another alternative would be to get rid of another side effect that you have: getSelectionFromUser . You can get rid of this with Iteratee , and then you can save the last move and reapply it.

OR ... you can write takeTo and use this.

+1
source

Source: https://habr.com/ru/post/1382048/


All Articles