@@ -53,18 +53,19 @@ object GameUI {
5353 }
5454
5555 @ tailrec
56- private def getCommand (player : Player ): UICommand = {
56+ private def getCommand (io : UserTextIO , player : Player ): UICommand = {
5757
5858 // ?? clean embedded references to stdin/console and stdout
59- print(s " Player $player command?: " )
60- val rawCmd = scala.io.StdIn .readLine()
59+ io.print(s " Player $player command?: " )
60+
61+ val rawCmd = io.readLine()
6162
6263 import scala .Left
6364 parseCommand(rawCmd) match {
6465 case Right (cmd) => cmd
6566 case Left (msg) =>
66- println(msg)
67- getCommand(player) // loop
67+ io. println(msg)
68+ getCommand(io, player) // loop
6869 }
6970 }
7071
@@ -81,15 +82,15 @@ object GameUI {
8182 }
8283
8384 // ?? "place mark"?
84- private def markAtSelection (uiState : GameUIState ): GameUIState = {
85+ private def markAtSelection (io : UserTextIO , uiState : GameUIState ): GameUIState = {
8586 val moveResult = uiState.gameState.tryMoveAt(uiState.selectedRow,
8687 uiState.selectedColumn)
8788 moveResult match {
8889 case Right (newGameState) =>
8990 uiState.copy(gameState = newGameState)
9091 case Left (errorMsg) =>
91- // ?? clean I/O? add to result and have cmd loop show? call ~injected error reporter?
92- println(errorMsg)
92+ // ?? add to result and have command ~ loop show?:
93+ io. println(errorMsg)
9394 uiState // no change
9495 }
9596 }
@@ -104,11 +105,11 @@ object GameUI {
104105 * game over or quit.
105106 */
106107 @ tailrec
107- private def getAndDoUiCommands (uiState : GameUIState ): GameUIResult = {
108- println()
109- println(uiState.toDisplayString)
108+ private def getAndDoUiCommands (io : UserTextIO , uiState : GameUIState ): GameUIResult = {
109+ io. println()
110+ io. println(uiState.toDisplayString)
110111
111- val command = getCommand(uiState.gameState.currentPlayer)
112+ val command = getCommand(io, uiState.gameState.currentPlayer)
112113
113114 import UICommand ._
114115 command match {
@@ -117,12 +118,12 @@ object GameUI {
117118 doQuit(uiState)
118119 case move : UIMoveCommand => // any move-selection command
119120 val nextState = moveSelection(uiState, move)
120- getAndDoUiCommands(nextState)
121+ getAndDoUiCommands(io, nextState) // loop
121122 case Mark =>
122- val nextState = markAtSelection(uiState)
123+ val nextState = markAtSelection(io, uiState)
123124 nextState.gameState.gameResult match {
124125 case None => // game not done yet
125- getAndDoUiCommands(nextState)
126+ getAndDoUiCommands(io, nextState) // loop
126127 case Some (gameResult) =>
127128 import GameState .GameResult ._ // ???
128129 val textResult =
@@ -135,11 +136,36 @@ object GameUI {
135136 }
136137 }
137138
138- def runGame (): GameUIResult = {
139+ // ?? revisit name
140+ trait UserTextIO {
141+ def print (lineOrPart : String ): Unit
142+ def println (): Unit = println(" " )
143+ def println (fullLine : String ): Unit
144+ def readLine (): String
145+
146+ // ??? soon, try with separate methods for prompt vs. error (etc.) lines
147+ // (imagine highlighting errors, using bold for actual prompt line
148+ // after plain lines for board rendering)
149+ }
150+
151+ object ConsoleUserTextIO extends UserTextIO {
152+ def print (lineOrPart : String ): Unit = Predef .print(lineOrPart)
153+ def println (fullLine : String ): Unit = Predef .println(fullLine)
154+ def readLine (): String = scala.io.StdIn .readLine()
155+ }
156+
157+
158+ // ???? next, probably create class GameUI to hold NameThisIO (to avoid passing all around)
159+ // ???? add GameUI tests:
160+ // - 1: driving from outside to normal insides--mocking/etc. UserTextIO?
161+ // - 2: driving from outside to special GameState (test double; spy/reporter/?)
162+
163+
164+ def runGame (io : UserTextIO ): GameUIResult = {
139165 val initialState =
140166 GameUIState (GameState .initial, RowIndex (Index (1 )), ColumnIndex (Index (1 )))
141167
142- getAndDoUiCommands(initialState)
168+ getAndDoUiCommands(io, initialState)
143169
144170 }
145171
0 commit comments