diff --git a/build.sbt b/build.sbt index cf57f57876754569aa1677f4eba934d1424fe530..9cb8d9ae305236a44f3f29733817abcf161c88a7 100644 --- a/build.sbt +++ b/build.sbt @@ -2,11 +2,14 @@ lazy val root = (project in file(".")). settings( name := "Reversi", version := "2023.0", - scalaVersion := "3.1.0" + scalaVersion := "3.2.0" ) run / fork := true libraryDependencies += "org.scalafx" %% "scalafx" % "20.0.0-R31" libraryDependencies += "org.scalameta" %% "munit" % "0.7.29" % Test + +libraryDependencies += "com.typesafe.play" %% "play-ahc-ws-standalone" % "2.2.0-M2" + testFrameworks += new TestFramework("munit.Framework") diff --git a/src/main/scala/cosc250/reversi/App.scala b/src/main/scala/cosc250/reversi/App.scala index 470630c6b4fececdaaac2f31ea226bc7ab9fd58e..b8fd4faae96985768fe90989ca9a149423695bbd 100644 --- a/src/main/scala/cosc250/reversi/App.scala +++ b/src/main/scala/cosc250/reversi/App.scala @@ -11,139 +11,55 @@ import scalafx.geometry.* import scalafx.collections.* import scalafx.animation.* import scalafx.beans.property.* +import javafx.application.Platform + +import java.util.{TimerTask, Timer} + object App extends JFXApp3 { override def start() = { + + val r = Rectangle(200, 200) + val b = Button("Click me") + var hue = 1 + var rot = 0d + stage = new JFXApp3.PrimaryStage { title.value = "Reversi" width = 480 height = 600 scene = new Scene { - content = new UI().subscene + content = new VBox( + r, b + ) } } - } - -} - -class UI() { - - // A constant for how bix the squares are - val squareSize = 40 - - /** Represents a square on the board. */ - class Square(col:Int, row:Int) { - - private val square = new Rectangle { - width = squareSize - height = squareSize - fill = if (col + row) % 2 == 0 then Color.hsb(114, 0.85, 0.33) else Color.hsb(114, 0.65, 0.43) - } + stage.setOnCloseRequest((_) => System.exit(0)) - val ui = new Group { - translateX = col * squareSize - translateY = row * squareSize - children = Seq( - square - ) - } - - def clear():Unit = { - ui.children = Seq(square) - } + val timerTask = new TimerTask { + override def run():Unit = + hue = (hue + 1) % 255 + rot = (rot + 3) % 360 - def place(p:Player) = { - ui.children = Seq( - square, - new Circle { - centerX = squareSize / 2 - centerY = squareSize / 2 - radius = squareSize / 3 - fill = if p == Player.White then Color.White else Color.Black + Platform.runLater { () => + r.fill = Color.hsb(hue, 1, 1) + r.rotate = rot } - ) - } - } - - private val squares = for y <- 0 until boardSize yield - for x <- 0 until boardSize yield - new Square(x, y) - - - var game = newGame - - private def showGameState(g:GameState):Unit = { - for - y <- 0 until boardSize - x <- 0 until boardSize - do - if g.board.contains((x, y)) then - squares(y)(x).place(g.board((x, y))) - else - squares(y)(x).clear() - } - - private def showGame(gs:Seq[GameState]):Unit = { - showGameState(gs.last) - history.items = ObservableBuffer((for - (g, i) <- gs.zipWithIndex - (p, loc) <- g.lastMove - yield - (i, loc, p) - )*) - } - - - val history = new ListView[(Int, Player, Location)] { - orientation = Orientation.Vertical - cellFactory = (cell, data) => { - val (i, p, (x, y)) = data - val col = "abcdefgh"(x) - cell.text = s"$i. $col${y+1}" - cell.textFill = if p == Player.Black then Color.Black else Color.White - cell.onMouseClicked = { _ => - lastTime = System.nanoTime() - game = rewindTo(game, i) - showGame(game) - } - - cell.style = - if i % 2 == 0 then "-fx-background-color: #1D7A12;" - else "-fx-background-color: #1A6E10;" } + val timer = new Timer() + timer.schedule(timerTask, 1000/60, 1000/60); + + b.setOnAction((_) => + new Thread(() => { + try { + Thread.sleep(10000); + } catch { + case _ => // ignore + } + }).start(); + + ) } - private def step():Unit = { - println(s"Lookahead ${lookAhead.value.value}") - game = play(game, lookAhead.value.value) - showGame(game) - } - - private var lastTime = 0L - private val playing = BooleanProperty(false) - - private val lookAhead = new Spinner[Int](0, 5, 0) - - AnimationTimer({ now => - if playing.value && (now > lastTime + 1e9) then - lastTime = now - step() - }).start() - - private val startStop = new Button { - text <== (for p <- playing yield if p then "Stop" else "Start") - onAction = { _ => playing.value = !playing.value } - } - - val subscene = new HBox( - new VBox( - new Group(squares.flatten.map(_.ui).toSeq*), - new HBox(5, startStop, Label("Lookahead"), lookAhead) - ), - history - ) - - - -} \ No newline at end of file +} diff --git a/src/main/scala/cosc250/reversi/FuturesDemo.scala b/src/main/scala/cosc250/reversi/FuturesDemo.scala new file mode 100644 index 0000000000000000000000000000000000000000..1326e24d805fc8c2c3add4a4f149be16efa2abf8 --- /dev/null +++ b/src/main/scala/cosc250/reversi/FuturesDemo.scala @@ -0,0 +1,19 @@ +package cosc250.reversi + +import scala.util.* +import scala.concurrent.* +import scala.concurrent.ExecutionContext.Implicits.global + + + +@main def futuresDemo() = { + + val promise = Promise[Int]() + val future = promise.future + + println(s"Before the future is completed. Future is completed ${future.isCompleted}") + future.onComplete { case Success(v) => println(s"Future completed with value $v") } + promise.success(2) + //println(s"After the future is completed. Future is completed ${future.isCompleted}") + +} diff --git a/src/main/scala/cosc250/reversi/Reversi.scala b/src/main/scala/cosc250/reversi/Reversi.scala deleted file mode 100644 index 945b2012f97bfcfdcf5c6821f3584f235027396d..0000000000000000000000000000000000000000 --- a/src/main/scala/cosc250/reversi/Reversi.scala +++ /dev/null @@ -1,63 +0,0 @@ -package cosc250.reversi - -import scala.collection.immutable.Queue - -enum Player: - case Black - case White - -/** The board size is always 8 by 8 */ -val boardSize = 8 - -/** A location on the board. Zero-indexed */ -type Location = (Int, Int) - -/** - * The state of the board - * - * @param lastMove - the location of the last move - * @param board - maps the locations of pieces on the board (note that if a piece has not been played in a square, it won't be in the map) - * @param turn - whose turn it is next - */ -case class GameState(lastMove:Option[(Location, Player)], board:Map[Location, Player], turn:Player) { - - /** The number of black pieces */ - def blackPieces:Int = ??? - - /** The number of white pieces */ - def whitePieces:Int = ??? - - /** True if neither player can play a move */ - def gameOver:Boolean = ??? - - /** Whether a particular move is valid */ - def isValidMove(location:Location):Boolean = - ??? - - /** Performs a move */ - def move(location:Location):GameState = - ??? - - - // Other methods you write - -} - -object GameState { - def newGame = GameState(None, Map.empty, Player.Black) -} - -/** A game is a sequence of game-states (so it remembers past moves). The most recent move is at the end. */ -type Game = Seq[GameState] - -/** Creates a new game, containing just the start game state */ -def newGame:Seq[GameState] = Seq(GameState.newGame) - -/** Called by the UI on each animation tick to make your AI play the game */ -def play(state:Seq[GameState], lookAhead:Int):Seq[GameState] = - ??? - -/** Called by the UI when the user clicks back in the game histry */ -def rewindTo(state:Seq[GameState], move:Int):Seq[GameState] = - ??? - diff --git a/src/main/scala/cosc250/reversi/ws.scala b/src/main/scala/cosc250/reversi/ws.scala new file mode 100644 index 0000000000000000000000000000000000000000..a46a2ece5ef3c19f0d5e64ebdfc9570a40d39025 --- /dev/null +++ b/src/main/scala/cosc250/reversi/ws.scala @@ -0,0 +1,41 @@ +package cosc250.reversi + +import akka.actor.ActorSystem +import akka.stream._ +import play.api.libs.ws._ +import play.api.libs.ws.ahc.StandaloneAhcWSClient +import scala.concurrent.ExecutionContext.Implicits.global + + +import scala.util.Random + +import scala.concurrent.Future + +@main def wsmain(): Unit = { + // A little boilerplate - this is needed by the web client we're using + given actorSystem:ActorSystem = ActorSystem() + given materializer:Materializer = SystemMaterializer(actorSystem).materializer + + // Now we can create our web client and make a request + val wsClient = StandaloneAhcWSClient() + + def zen():Future[String] = + val randomStr = Random.nextString(4) // Makes sure we don't get a cached reply + wsClient.url(s"https://api.github.com/zen?$randomStr").get().map(_.body) + + def nZens(n:Int):Future[Seq[String]] = { + val futures:Seq[Future[String]] = for + i <- 1 to n + yield zen() + + Future.sequence(futures) + } + + val f = for + first <- zen() + second <- zen() + yield s"$first, and then $second" + + // Print this statement immediately + println("This prints immediately") +} \ No newline at end of file diff --git a/src/test/scala/cosc250/reversi/ReversiSuite.scala b/src/test/scala/cosc250/reversi/ReversiSuite.scala index 121660610870dfe844baf6fcce8c8ecd70714279..bb381ca3cc7b77bf6b0524eefff45359a2ef1782 100644 --- a/src/test/scala/cosc250/reversi/ReversiSuite.scala +++ b/src/test/scala/cosc250/reversi/ReversiSuite.scala @@ -9,23 +9,4 @@ package cosc250.reversi */ class ReversiSuite extends munit.FunSuite { - // A place for you to write tests. Some suggested tests to start with have been sketched below - - test("Counts pieces") { - assertEquals(2, GameState(None, Map((3, 3) -> Player.Black, (3, 4) -> Player.Black), Player.White).blackPieces) - assertEquals(2, GameState(None, Map((3, 3) -> Player.White, (3, 4) -> Player.White), Player.White).whitePieces) - - // add some more! - } - - test("Should be able to detect if a move is valid") { - ??? - } - - test("Should be able to count the score for one player") { - ??? - } - - // You'll need to write some additional tests - }