diff --git a/.gitignore b/.gitignore index 1f588c5..bc82332 100755 --- a/.gitignore +++ b/.gitignore @@ -15,7 +15,6 @@ target/ # Maven log/ -target/ # Gradle .gradle/ @@ -24,15 +23,12 @@ pom.xml # sbt .cache -.history .lib/ dist/* -target/ lib_managed/ src_managed/ project/boot/ project/plugins/project/ -project/target/ project/project/ logs/ project/.sbtserver @@ -47,3 +43,6 @@ project/.sbtserver # Application log sudoku.log + +# Application backup +grid.json \ No newline at end of file diff --git a/src/main/scala/de/htwg/se/sudoku/Sudoku.scala b/src/main/scala/de/htwg/se/sudoku/Sudoku.scala index b2c7754..9377843 100755 --- a/src/main/scala/de/htwg/se/sudoku/Sudoku.scala +++ b/src/main/scala/de/htwg/se/sudoku/Sudoku.scala @@ -6,18 +6,21 @@ import com.google.inject.{Guice, Injector} import de.htwg.se.sudoku.aview.gui.SwingGui import de.htwg.se.sudoku.aview.{HttpServer, Tui} import de.htwg.se.sudoku.controller.controllerComponent.ControllerInterface +import de.htwg.se.sudoku.model.fileIoComponent.FileIOInterface +import de.htwg.se.sudoku.model.fileIoComponent.fileIoMicroImpl.FileIoHttpServer import scala.io.StdIn.readLine object Sudoku { - val injector: Injector = Guice.createInjector(new SudokuModule) + val injector: Injector = Guice.createInjector(new MicroSudokuModule) val controller: ControllerInterface = injector.getInstance(classOf[ControllerInterface]) val tui = new Tui(controller) - + if (!GraphicsEnvironment.isHeadless) { val gui = new SwingGui(controller) } - + + val fileIoHttpServer: FileIoHttpServer = injector.getInstance(classOf[FileIoHttpServer]) val webserver = new HttpServer(controller) controller.createNewGrid @@ -32,5 +35,7 @@ object Sudoku { } } while (input != "q") webserver.unbind() + fileIoHttpServer.unbind() + controller.finish() } } diff --git a/src/main/scala/de/htwg/se/sudoku/SudokuModule.scala b/src/main/scala/de/htwg/se/sudoku/SudokuModule.scala index efa8984..538d676 100644 --- a/src/main/scala/de/htwg/se/sudoku/SudokuModule.scala +++ b/src/main/scala/de/htwg/se/sudoku/SudokuModule.scala @@ -2,17 +2,17 @@ package de.htwg.se.sudoku import com.google.inject.AbstractModule import com.google.inject.name.Names -import net.codingwell.scalaguice.ScalaModule import de.htwg.se.sudoku.controller.controllerComponent._ import de.htwg.se.sudoku.model.fileIoComponent._ import de.htwg.se.sudoku.model.gridComponent.GridInterface import de.htwg.se.sudoku.model.gridComponent.gridAdvancedImpl.Grid +import net.codingwell.scalaguice.ScalaModule class SudokuModule extends AbstractModule with ScalaModule { val defaultSize: Int = 9 - def configure() = { + def configure(): Unit = { bindConstant().annotatedWith(Names.named("DefaultSize")).to(defaultSize) bind[GridInterface].to[Grid] bind[ControllerInterface].to[controllerBaseImpl.Controller] @@ -26,3 +26,26 @@ class SudokuModule extends AbstractModule with ScalaModule { } } + +class MicroSudokuModule extends AbstractModule with ScalaModule { + + val defaultSize: Int = 9 + val defaultHostname: String = "localhost" + val defaultFilePort: Int = 8089 + + def configure(): Unit = { + bindConstant().annotatedWith(Names.named("DefaultSize")).to(defaultSize) + bind[GridInterface].to[Grid] + bind[ControllerInterface].to[controllerBaseImpl.Controller] + + bind[GridInterface].annotatedWithName("tiny").toInstance(new Grid(1)) + bind[GridInterface].annotatedWithName("small").toInstance(new Grid(4)) + bind[GridInterface].annotatedWithName("normal").toInstance(new Grid(9)) + + bindConstant().annotatedWith(Names.named("FileHost")).to(defaultHostname) + bindConstant().annotatedWith(Names.named("FilePort")).to(defaultFilePort) + + bind[FileIOInterface].to[fileIoMicroImpl.FileIO] + } + +} \ No newline at end of file diff --git a/src/main/scala/de/htwg/se/sudoku/aview/HttpServer.scala b/src/main/scala/de/htwg/se/sudoku/aview/HttpServer.scala index 838b663..46445b7 100644 --- a/src/main/scala/de/htwg/se/sudoku/aview/HttpServer.scala +++ b/src/main/scala/de/htwg/se/sudoku/aview/HttpServer.scala @@ -12,7 +12,7 @@ import scala.concurrent.{ExecutionContextExecutor, Future} class HttpServer(controller: ControllerInterface) { - implicit val system: ActorSystem = ActorSystem("system") + implicit val system: ActorSystem = ActorSystem("RestHttpServerSystem") implicit val materializer: ActorMaterializer = ActorMaterializer() // needed for the future flatMap/onComplete in the end implicit val executionContext: ExecutionContextExecutor = system.dispatcher diff --git a/src/main/scala/de/htwg/se/sudoku/controller/controllerComponent/ControllerInterface.scala b/src/main/scala/de/htwg/se/sudoku/controller/controllerComponent/ControllerInterface.scala index db6cdb6..ce7b9e9 100644 --- a/src/main/scala/de/htwg/se/sudoku/controller/controllerComponent/ControllerInterface.scala +++ b/src/main/scala/de/htwg/se/sudoku/controller/controllerComponent/ControllerInterface.scala @@ -1,13 +1,12 @@ package de.htwg.se.sudoku.controller.controllerComponent import de.htwg.se.sudoku.controller.controllerComponent.GameStatus.GameStatus -import de.htwg.se.sudoku.model.gridComponent.CellInterface +import de.htwg.se.sudoku.model.gridComponent.{CellInterface, GridInterface} import play.api.libs.json.JsValue import scala.swing.Publisher trait ControllerInterface extends Publisher { - def gridSize: Int def blockSize: Int @@ -56,8 +55,9 @@ trait ControllerInterface extends Publisher { def statusText: String - def toJson: JsValue + def gridToJson: JsValue + def finish(): Unit } trait ControllerIoInterface { diff --git a/src/main/scala/de/htwg/se/sudoku/controller/controllerComponent/controllerBaseImpl/Controller.scala b/src/main/scala/de/htwg/se/sudoku/controller/controllerComponent/controllerBaseImpl/Controller.scala index f1fe0ac..510e5fc 100644 --- a/src/main/scala/de/htwg/se/sudoku/controller/controllerComponent/controllerBaseImpl/Controller.scala +++ b/src/main/scala/de/htwg/se/sudoku/controller/controllerComponent/controllerBaseImpl/Controller.scala @@ -1,28 +1,29 @@ package de.htwg.se.sudoku.controller.controllerComponent.controllerBaseImpl import com.google.inject.name.Names -import com.google.inject.{Guice, Inject} -import net.codingwell.scalaguice.InjectorExtensions._ -import de.htwg.se.sudoku.SudokuModule +import com.google.inject.{Guice, Inject, Injector} +import com.typesafe.scalalogging.LazyLogging +import de.htwg.se.sudoku.MicroSudokuModule import de.htwg.se.sudoku.controller.controllerComponent.GameStatus._ import de.htwg.se.sudoku.controller.controllerComponent._ import de.htwg.se.sudoku.model.fileIoComponent.FileIOInterface -import de.htwg.se.sudoku.model.gridComponent.GridInterface +import de.htwg.se.sudoku.model.gridComponent.{CellInterface, GridInterface} import de.htwg.se.sudoku.util.UndoManager +import net.codingwell.scalaguice.InjectorExtensions._ +import play.api.libs.json.JsValue -import scala.util.{Success, Failure} -import com.typesafe.scalalogging.{LazyLogging, Logger} +import scala.util.{Failure, Success} class Controller @Inject()(var grid: GridInterface) - extends ControllerInterface + extends ControllerInterface with ControllerIoInterface with LazyLogging { var gameStatus: GameStatus = IDLE var showAllCandidates: Boolean = false private val undoManager = new UndoManager - val injector = Guice.createInjector(new SudokuModule) - val fileIo = injector.instance[FileIOInterface] + val injector: Injector = Guice.createInjector(new MicroSudokuModule) + val fileIo: FileIOInterface = injector.instance[FileIOInterface] def createEmptyGrid: Unit = { grid.size match { @@ -77,7 +78,7 @@ class Controller @Inject()(var grid: GridInterface) publish(new CellChanged) } - def toJson = grid.toJson + def gridToJson: JsValue = grid.toJson def load: Unit = { val gridOptionResult = fileIo.load @@ -114,29 +115,40 @@ class Controller @Inject()(var grid: GridInterface) publish(new CellChanged) } - def cell(row:Int, col:Int) = grid.cell(row,col) + def cell(row:Int, col:Int): CellInterface = grid.cell(row,col) + + def isGiven(row: Int, col: Int): Boolean = grid.cell(row, col).given + + def isSet(row: Int, col: Int): Boolean = grid.cell(row, col).isSet - def isGiven(row: Int, col: Int):Boolean = grid.cell(row, col).given - def isSet(row:Int, col:Int):Boolean = grid.cell(row, col).isSet - def available(row:Int, col:Int):Set[Int] = grid.available(row, col) - def showCandidates(row:Int, col:Int):Unit = { - grid=grid.setShowCandidates(row, col) + def available(row: Int, col: Int): Set[Int] = grid.available(row, col) + + def showCandidates(row: Int, col: Int): Unit = { + grid = grid.setShowCandidates(row, col) gameStatus = CANDIDATES publish(new CandidatesChanged) } - def isShowCandidates(row:Int, col:Int):Boolean = grid.cell(row, col).showCandidates - def gridSize:Int = grid.size - def blockSize:Int = Math.sqrt(grid.size).toInt - def isShowAllCandidates:Boolean = showAllCandidates - def toggleShowAllCandidates:Unit = { + def isShowCandidates(row: Int, col: Int): Boolean = + grid.cell(row, col).showCandidates + + def gridSize: Int = grid.size + + def blockSize: Int = Math.sqrt(grid.size).toInt + + def isShowAllCandidates: Boolean = showAllCandidates + + def toggleShowAllCandidates: Unit = { showAllCandidates = !showAllCandidates gameStatus = CANDIDATES publish(new CellChanged) } - def isHighlighted(row:Int, col: Int):Boolean = grid.isHighlighted(row, col) - def statusText:String = GameStatus.message(gameStatus) - def highlight(index:Int):Unit = { + + def isHighlighted(row: Int, col: Int): Boolean = grid.isHighlighted(row, col) + + def statusText: String = GameStatus.message(gameStatus) + + def highlight(index: Int): Unit = { grid = grid.highlight(index) publish(new CellChanged) } @@ -148,4 +160,6 @@ class Controller @Inject()(var grid: GridInterface) override def setShowCandidates(row: Int, col: Int): Unit = { grid = grid.setShowCandidates(row, col) } + + override def finish(): Unit = fileIo.unbind() } diff --git a/src/main/scala/de/htwg/se/sudoku/controller/controllerComponent/controllerMockImpl/Controller.scala b/src/main/scala/de/htwg/se/sudoku/controller/controllerComponent/controllerMockImpl/Controller.scala index 5ae5613..963ff2f 100644 --- a/src/main/scala/de/htwg/se/sudoku/controller/controllerComponent/controllerMockImpl/Controller.scala +++ b/src/main/scala/de/htwg/se/sudoku/controller/controllerComponent/controllerMockImpl/Controller.scala @@ -59,6 +59,7 @@ class Controller(var grid: GridInterface) extends ControllerInterface { override def load: Unit = {} - override def toJson: JsValue = grid.toJson() + override def gridToJson: JsValue = grid.toJson + override def finish(): Unit = {} } diff --git a/src/main/scala/de/htwg/se/sudoku/model/fileIoComponent/FileIOInterface.scala b/src/main/scala/de/htwg/se/sudoku/model/fileIoComponent/FileIOInterface.scala index 6af4c6c..2bcfde2 100644 --- a/src/main/scala/de/htwg/se/sudoku/model/fileIoComponent/FileIOInterface.scala +++ b/src/main/scala/de/htwg/se/sudoku/model/fileIoComponent/FileIOInterface.scala @@ -8,5 +8,6 @@ trait FileIOInterface { def load: Try[Option[GridInterface]] def save(grid: GridInterface): Try[Unit] + def unbind() } diff --git a/src/main/scala/de/htwg/se/sudoku/model/fileIoComponent/fileIoJsonImpl/FileIO.scala b/src/main/scala/de/htwg/se/sudoku/model/fileIoComponent/fileIoJsonImpl/FileIO.scala index 5ae6b22..6df660c 100644 --- a/src/main/scala/de/htwg/se/sudoku/model/fileIoComponent/fileIoJsonImpl/FileIO.scala +++ b/src/main/scala/de/htwg/se/sudoku/model/fileIoComponent/fileIoJsonImpl/FileIO.scala @@ -2,14 +2,14 @@ package de.htwg.se.sudoku.model.fileIoComponent.fileIoJsonImpl import com.google.inject.Guice import com.google.inject.name.Names -import net.codingwell.scalaguice.InjectorExtensions._ import de.htwg.se.sudoku.SudokuModule import de.htwg.se.sudoku.model.fileIoComponent.FileIOInterface -import de.htwg.se.sudoku.model.gridComponent.{CellInterface, GridInterface} +import de.htwg.se.sudoku.model.gridComponent.GridInterface +import net.codingwell.scalaguice.InjectorExtensions._ import play.api.libs.json._ -import scala.util.{Try, Success, Failure} import scala.io.Source +import scala.util.Try class FileIO extends FileIOInterface { @@ -23,7 +23,8 @@ class FileIO extends FileIOInterface { val json: JsValue = Json.parse(source) val size = (json \ "grid" \ "size").get.toString.toInt - val injector = Guice.createInjector(new SudokuModule) + val injector:ScalaInjector = Guice.createInjector(new SudokuModule) + size match { case 1 => gridOption = @@ -40,9 +41,9 @@ class FileIO extends FileIOInterface { case Some(grid) => { var _grid = grid for (index <- 0 until size * size) { - val row = (json \\ "row")(index).as[Int] - val col = (json \\ "col")(index).as[Int] - val cell = (json \\ "cell")(index) + val row = (json \\ "row") (index).as[Int] + val col = (json \\ "col") (index).as[Int] + val cell = (json \\ "cell") (index) val value = (cell \ "value").as[Int] _grid = _grid.set(row, col, value) val given = (cell \ "given").as[Boolean] @@ -70,7 +71,5 @@ class FileIO extends FileIOInterface { def gridToJson(grid: GridInterface) = grid.toJson - - - + override def unbind(): Unit = {} } diff --git a/src/main/scala/de/htwg/se/sudoku/model/fileIoComponent/fileIoMicroImpl/FileIO.scala b/src/main/scala/de/htwg/se/sudoku/model/fileIoComponent/fileIoMicroImpl/FileIO.scala new file mode 100644 index 0000000..28c5b2d --- /dev/null +++ b/src/main/scala/de/htwg/se/sudoku/model/fileIoComponent/fileIoMicroImpl/FileIO.scala @@ -0,0 +1,107 @@ +package de.htwg.se.sudoku.model.fileIoComponent.fileIoMicroImpl + +import akka.actor.ActorSystem +import akka.http.scaladsl.Http +import akka.http.scaladsl.model.HttpMethods.POST +import akka.http.scaladsl.model._ +import akka.http.scaladsl.unmarshalling.Unmarshal +import akka.stream.ActorMaterializer +import com.google.inject.name.{Named, Names} +import com.google.inject.{Guice, Inject} +import de.htwg.se.sudoku.MicroSudokuModule +import de.htwg.se.sudoku.model.fileIoComponent.FileIOInterface +import de.htwg.se.sudoku.model.gridComponent.GridInterface +import net.codingwell.scalaguice.InjectorExtensions.ScalaInjector +import play.api.libs.json.Json + +import scala.concurrent.duration._ +import scala.concurrent.{Await, ExecutionContextExecutor, Future} +import scala.util.{Success, Try} + +class FileIO @Inject()(@Named("FileHost") host: String, @Named("FilePort") port: Int) extends FileIOInterface { + implicit val system: ActorSystem = ActorSystem("FileIoSystem") + implicit val materializer: ActorMaterializer = ActorMaterializer() + implicit val executionContext: ExecutionContextExecutor = system.dispatcher + + val serverPath: String = "http://" + host + ":" + port + "/" + + override def load: Try[Option[GridInterface]] = { + val responseFuture = Http() + .singleRequest(HttpRequest(uri = serverPath + "sudoku/model/fileio/v1/load")) + + val result = Await.result(responseFuture, 5000 millis) + + if (result.status.isSuccess) { + Success(jsonGridToGridOption(Unmarshal(result.entity).to[String])) + } else { + throw new IllegalStateException("Loading the game failed, reason: " + result.status.reason()) + } + } + + override def save(grid: GridInterface): Try[Unit] = { + Try { + val httpRequest = HttpRequest(POST, + uri = serverPath + "sudoku/model/fileio/v1/save", + entity = grid.toJson.toString()) + + val responseFuture: Future[HttpResponse] = Http().singleRequest(httpRequest) + + val result = Await.result(responseFuture, 5000 millis) + + if (!result.status.isSuccess()) { + throw new IllegalStateException("Saving the game failed, reason: " + result.status.reason()) + } + } + } + + override def unbind(): Unit = { + system.terminate() + } + + def jsonGridToGridOption(jsonString: Future[String]): Option[GridInterface] = { + + var gridOption: Option[GridInterface] = None + + jsonString.map(string => { + val json = Json.parse(string) + + Try { + val size = (json \ "grid" \ "size").get.toString.toInt + val injector: ScalaInjector = Guice.createInjector(new MicroSudokuModule) + + size match { + case 1 => + gridOption = + Some(injector.instance[GridInterface](Names.named("tiny"))) + case 4 => + gridOption = + Some(injector.instance[GridInterface](Names.named("small"))) + case 9 => + gridOption = + Some(injector.instance[GridInterface](Names.named("normal"))) + case _ => + } + gridOption match { + case Some(grid) => + var _grid = grid + for (index <- 0 until size * size) { + val row = (json \\ "row") (index).as[Int] + val col = (json \\ "col") (index).as[Int] + val cell = (json \\ "cell") (index) + val value = (cell \ "value").as[Int] + _grid = _grid.set(row, col, value) + val given = (cell \ "given").as[Boolean] + val showCandidates = (cell \ "showCandidates").as[Boolean] + if (given) _grid = _grid.setGiven(row, col, value) + if (showCandidates) _grid = _grid.setShowCandidates(row, col) + } + gridOption = Some(_grid) + case None => + } + gridOption + } + }) + + gridOption + } +} diff --git a/src/main/scala/de/htwg/se/sudoku/model/fileIoComponent/fileIoMicroImpl/FileIoHttpServer.scala b/src/main/scala/de/htwg/se/sudoku/model/fileIoComponent/fileIoMicroImpl/FileIoHttpServer.scala new file mode 100644 index 0000000..5812bef --- /dev/null +++ b/src/main/scala/de/htwg/se/sudoku/model/fileIoComponent/fileIoMicroImpl/FileIoHttpServer.scala @@ -0,0 +1,66 @@ +package de.htwg.se.sudoku.model.fileIoComponent.fileIoMicroImpl + +import akka.actor.ActorSystem +import akka.http.scaladsl.Http +import akka.http.scaladsl.model.StatusCodes +import akka.http.scaladsl.server.{Directives, Route} +import akka.stream.ActorMaterializer +import com.google.inject.Inject +import com.google.inject.name.Named +import play.api.libs.json.Json + +import scala.concurrent.{ExecutionContextExecutor, Future} +import scala.io.Source +import scala.util.Try + +class FileIoHttpServer @Inject()(@Named("FileHost") host: String, @Named("FilePort") port: Int) extends Directives { + implicit val system: ActorSystem = ActorSystem("FileIoHttpServerSystem") + implicit val materializer: ActorMaterializer = ActorMaterializer() + implicit val executionContext: ExecutionContextExecutor = system.dispatcher + + final val FILE_NAME: String = "grid.json" + + val route: Route = get { + pathPrefix("sudoku" / "model" / "fileio" / "v1") { + path("load") { + + var source = new String + + Try { + source = Source.fromFile(FILE_NAME).getLines.mkString + } + + if (source.nonEmpty) { + complete(StatusCodes.OK -> source) + } else { + complete(StatusCodes.InternalServerError) + } + } + } + } ~ + post { + pathPrefix("sudoku" / "model" / "fileio" / "v1") { + path("save") { + entity(as[String]) { json => + import java.io._ + + Try { + val pw = new PrintWriter(new File(FILE_NAME)) + pw.write(Json.prettyPrint(Json.parse(json))) + pw.close() + } + + complete(StatusCodes.OK) + } + } + } + } + + val bindingFuture: Future[Http.ServerBinding] = Http().bindAndHandle(route, host, port) + + def unbind(): Unit = { + bindingFuture + .flatMap(_.unbind()) + .onComplete(_ => system.terminate()) + } +} diff --git a/src/main/scala/de/htwg/se/sudoku/model/fileIoComponent/fileIoXmlImpl/FileIO.scala b/src/main/scala/de/htwg/se/sudoku/model/fileIoComponent/fileIoXmlImpl/FileIO.scala index 0923204..5d03d15 100644 --- a/src/main/scala/de/htwg/se/sudoku/model/fileIoComponent/fileIoXmlImpl/FileIO.scala +++ b/src/main/scala/de/htwg/se/sudoku/model/fileIoComponent/fileIoXmlImpl/FileIO.scala @@ -83,4 +83,5 @@ class FileIO extends FileIOInterface { } + override def unbind(): Unit = {} } diff --git a/src/main/scala/de/htwg/se/sudoku/model/gridComponent/GridInterface.scala b/src/main/scala/de/htwg/se/sudoku/model/gridComponent/GridInterface.scala index ea7c306..69bfadf 100644 --- a/src/main/scala/de/htwg/se/sudoku/model/gridComponent/GridInterface.scala +++ b/src/main/scala/de/htwg/se/sudoku/model/gridComponent/GridInterface.scala @@ -21,8 +21,7 @@ trait GridInterface { def indexToRowCol(index: Int): (Int, Int) def markFilledCellsAsGiven: GridInterface def setGiven(row: Int, col: Int, value: Int): GridInterface - def toJson(): JsValue - + def toJson: JsValue } trait CellInterface { diff --git a/src/main/scala/de/htwg/se/sudoku/model/playerComponent/Player.scala b/src/main/scala/de/htwg/se/sudoku/model/playerComponent/Player.scala old mode 100755 new mode 100644