Skip to content

Commit 17fa1f3

Browse files
author
Daniel Barclay
committed
ManualTicTacToe: Split out GameUI.
1 parent b049491 commit 17fa1f3

File tree

2 files changed

+131
-115
lines changed

2 files changed

+131
-115
lines changed
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
package com.us.dsb.explore.algs.ttt.manual
2+
3+
import cats.syntax.option._
4+
import cats.syntax.either._
5+
import enumeratum.EnumEntry
6+
7+
import scala.annotation.tailrec
8+
9+
10+
//???? object -> class?
11+
/** TTT UI controller. */
12+
object GameUI {
13+
14+
// ??? enhance; maybe just put clean strings in; maybe build on GameResult (plus quit case)
15+
case class GameUIResult(text: String)
16+
17+
18+
sealed trait UICommand extends EnumEntry
19+
object UICommand {
20+
// ?? why doesn't UICommand's "sealed" obviate the following one (for exhaustive-match checks?)
21+
sealed trait UIMoveCommand extends UICommand
22+
case object Up extends UIMoveCommand
23+
case object Down extends UIMoveCommand
24+
case object Left extends UIMoveCommand
25+
case object Right extends UIMoveCommand
26+
case object Mark extends UICommand
27+
case object Quit extends UICommand
28+
}
29+
30+
def parseCommand(rawCmd: String): Either[String, UICommand] = {
31+
import UICommand._
32+
rawCmd match {
33+
case "u" => Up.asRight
34+
case "d" => Down.asRight
35+
case "l" => Left.asRight
36+
case "r" => Right.asRight
37+
case "m" => Mark.asRight
38+
case "q" => Quit.asRight
39+
case _ =>
40+
s"Invalid input \"$rawCmd\"; try u(p), d(own), l(eft), r(right), m(ark), or q(uit)".asLeft
41+
}
42+
}
43+
44+
@tailrec
45+
def getCommand(player: Player): UICommand = {
46+
// ?? clean embedded reference to stdin/console and stdout
47+
print(s"Player $player command?: ")
48+
val rawCmd = scala.io.StdIn.readLine()
49+
50+
parseCommand(rawCmd) match {
51+
case Right(cmd) => cmd
52+
case Left(msg) =>
53+
println(msg)
54+
getCommand(player) // loop
55+
}
56+
}
57+
58+
//import UICommand.UIMoveCommand
59+
def moveSelection(uiState: GameUIState,
60+
moveCommand: UICommand.UIMoveCommand): GameUIState = {
61+
import UICommand._
62+
moveCommand match {
63+
case Up => uiState.withRowAdustedBy(-1)
64+
case Down => uiState.withRowAdustedBy(1)
65+
case Left => uiState.withColumnAdustedBy(-1)
66+
case Right => uiState.withColumnAdustedBy(1)
67+
}
68+
}
69+
70+
// ?? "place mark"?
71+
def markAtSelection(uiState: GameUIState): GameUIState = {
72+
val moveResult = uiState.gameState.tryMoveAt(uiState.selectedRow,
73+
uiState.selectedColumn)
74+
moveResult match {
75+
case Right(newGameState) =>
76+
uiState.copy(gameState = newGameState)
77+
case Left(errorMsg) =>
78+
// ?? clean I/O? add to result and hjave cmd loop show? call ~injected error reporter?
79+
println(errorMsg)
80+
uiState // no change
81+
}
82+
}
83+
84+
def doQuit(uiState: GameUIState): GameUIResult = {
85+
GameUIResult("Game was quit")
86+
}
87+
88+
89+
90+
91+
92+
// ?? clean looping more (was while mess, now recursive; is there better Scala way?)
93+
/**
94+
* Logically, loops on prompting for and executing user UI ~commands until
95+
* game over or quit.
96+
*/
97+
@tailrec
98+
def getAndDoUiCommands(uiState: GameUIState): GameUIResult = {
99+
println()
100+
println(uiState.toDisplayString)
101+
102+
val command = getCommand(uiState.gameState.currentPlayer)
103+
104+
import UICommand._
105+
command match {
106+
// ?? can we factor down the multiple getAndDoUiCommands calls (usefully, in this small case)?
107+
case Quit =>
108+
doQuit(uiState)
109+
case move: UIMoveCommand => // any move-selection command
110+
getAndDoUiCommands(moveSelection(uiState, move))
111+
case Mark =>
112+
val newUiState = markAtSelection(uiState)
113+
newUiState.gameState.gameResult match {
114+
case None => // game not done yet
115+
getAndDoUiCommands(newUiState)
116+
case Some(gameResult) =>
117+
118+
import GameState.GameResult._
119+
val textResult =
120+
gameResult match {
121+
case Draw => "Game ended in draw"
122+
case Win(player) => s"Player $player won"
123+
}
124+
GameUIResult(textResult) // ?? refine from text
125+
}
126+
}
127+
}
128+
129+
}

src/main/scala/com/us/dsb/explore/algs/ttt/manual/ManualTicTacToe.scala

Lines changed: 2 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -8,129 +8,16 @@ import scala.annotation.tailrec
88

99
object ManualTicTacToe extends App {
1010

11-
sealed trait UICommand extends EnumEntry
12-
object UICommand {
13-
// ?? why doesn't UICommand's "sealed" obviate the following one (for exhaustive-match checks?)
14-
sealed trait UIMoveCommand extends UICommand
15-
case object Up extends UIMoveCommand
16-
case object Down extends UIMoveCommand
17-
case object Left extends UIMoveCommand
18-
case object Right extends UIMoveCommand
19-
case object Mark extends UICommand
20-
case object Quit extends UICommand
21-
}
2211

23-
def parseCommand(rawCmd: String): Either[String, UICommand] = {
24-
import UICommand._
25-
rawCmd match {
26-
case "u" => Up.asRight
27-
case "d" => Down.asRight
28-
case "l" => Left.asRight
29-
case "r" => Right.asRight
30-
case "m" => Mark.asRight
31-
case "q" => Quit.asRight
32-
case _ =>
33-
s"Invalid input \"$rawCmd\"; try u(p), d(own), l(eft), r(right), m(ark), or q(uit)".asLeft
34-
}
35-
}
3612

37-
@tailrec
38-
def getCommand(player: Player): UICommand = {
39-
// ?? clean embedded reference to stdin/console and stdout
40-
print(s"Player $player command?: ")
41-
val rawCmd = scala.io.StdIn.readLine()
42-
43-
parseCommand(rawCmd) match {
44-
case Right(cmd) => cmd
45-
case Left(msg) =>
46-
println(msg)
47-
getCommand(player) // loop
48-
}
49-
}
50-
51-
// ??? enhance; maybe just put clean strings in; maybe build on GameResult (plus quit case)
52-
case class GameUIResult(text: String)
53-
54-
55-
object UICommandMethods {
56-
57-
import UICommand.UIMoveCommand
58-
def moveSelection(uiState: GameUIState,
59-
moveCommand: UIMoveCommand): GameUIState = {
60-
import UICommand._
61-
moveCommand match {
62-
case Up => uiState.withRowAdustedBy(-1)
63-
case Down => uiState.withRowAdustedBy(1)
64-
case Left => uiState.withColumnAdustedBy(-1)
65-
case Right => uiState.withColumnAdustedBy(1)
66-
}
67-
}
68-
69-
// ?? "place mark"?
70-
def markAtSelection(uiState: GameUIState): GameUIState = {
71-
val moveResult = uiState.gameState.tryMoveAt(uiState.selectedRow,
72-
uiState.selectedColumn)
73-
moveResult match {
74-
case Right(newGameState) =>
75-
uiState.copy(gameState = newGameState)
76-
case Left(errorMsg) =>
77-
// ?? clean I/O? add to result and hjave cmd loop show? call ~injected error reporter?
78-
println(errorMsg)
79-
uiState // no change
80-
}
81-
}
82-
83-
def doQuit(uiState: GameUIState): GameUIResult = {
84-
GameUIResult("Game was quit")
85-
}
86-
}
87-
88-
// ?? clean looping more (was while mess, now recursive; is there better Scala way?)
89-
/**
90-
* Logically, loops on prompting for and executing user UI ~commands until
91-
* game over or quit.
92-
*/
93-
@tailrec
94-
def getAndDoUiCommands(uiState: GameUIState): GameUIResult = {
95-
println()
96-
println(uiState.toDisplayString)
97-
98-
val command = getCommand(uiState.gameState.currentPlayer)
99-
100-
import UICommand._
101-
import UICommandMethods._
102-
command match {
103-
// ?? can we factor down the multiple getAndDoUiCommands calls (usefully, in this small case)?
104-
case Quit =>
105-
doQuit(uiState)
106-
case move: UIMoveCommand => // any move-selection command
107-
getAndDoUiCommands(moveSelection(uiState, move))
108-
case Mark =>
109-
val newUiState = markAtSelection(uiState)
110-
newUiState.gameState.gameResult match {
111-
case None => // game not done yet
112-
getAndDoUiCommands(newUiState)
113-
case Some(gameResult) =>
114-
115-
import GameState.GameResult._
116-
val textResult =
117-
gameResult match {
118-
case Draw => "Game ended in draw"
119-
case Win(player) => s"Player $player won"
120-
}
121-
GameUIResult(textResult) // ?? refine from text
122-
}
123-
}
124-
}
125-
126-
//////////////////////////////////////////////////////////////////////
12713

14+
// ???? move to GameUI
12815
val initialState =
12916
// ?? maybe clean getting indices; maybe get from index ranges, not
13017
// constructing here (though here exercises refined type_)
13118
GameUIState(GameState.initial, RowIndex(Index(1)), ColumnIndex(Index(1)))
13219

133-
val gameResult: GameUIResult = getAndDoUiCommands(initialState)
20+
val gameResult: GameUI.GameUIResult = GameUI.getAndDoUiCommands(initialState)
13421
println("Result: " + gameResult.text)
13522

13623
}

0 commit comments

Comments
 (0)