diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 7e0488f..3a1a008 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -7,5 +7,5 @@ jobs: - name: Check out repository code uses: actions/checkout@v2 - name: SBT Build - run: sbt compile + run: sbt test shell: bash diff --git a/PUBLISH.md b/PUBLISH.md index c59bd71..88da291 100644 --- a/PUBLISH.md +++ b/PUBLISH.md @@ -68,7 +68,7 @@ openpgp-revocs.d pubring.asc trustdb.gpg ## How to publish -1. Build and test locally. +1. Build and test locally using `sbt "compile;test;doc"` 2. Bump `lazy val Version` in `build.sbt`, run `package` in sbt. Note no plus before package as from 1.2.0 we only publish for Scala 3. We also want a release on github and the course home page aligned with the release on Sonatype Central. Therefore You should also: - Don't forget to update the `doc/index.md` file with current version information and package contents etc. Read more on scaladoc here: https://docs.scala-lang.org/scala3/scaladoc.html @@ -92,7 +92,43 @@ openpgp-revocs.d pubring.asc trustdb.gpg 4. In `sbt>` run `publishSigned` - a plus sign is not used since we only publish for Scala 3 from 1.2.0. -5. Log into Sonatype Nexus here: (if the page does not load, clear the browser's cache by pressing Ctrl+F5) https://oss.sonatype.org/#welcome +Note: It is falsely said to be `sbt publish` according to https://www.scala-sbt.org/1.x/docs/Publishing.html but you need to use `sbt publishSigned` +after creating a .credentials file in ~/.sbt including below where xxx and yyy is replaced with secret values that is access according to https://central.sonatype.org/publish/generate-token/ If you do just `publish` you will get an error later in the process after closing below that complains that .asc files are missing etc. + +Put .credentials in ~/.sbt +``` +realm=Sonatype Nexus Repository Manager +host=oss.sonatype.org +user=xxx +password=yyy +``` + +When I did publishSIgend last time I got these errors but the publishing went through anyway with the above .credentials in ~/.sbt: +``` +sbt:introprog> publishSigned +[info] Wrote /home/bjornr/git/hub/lunduniversity/introprog-scalalib/target/scala-3.3.3/introprog_3-1.4.0.pom +[warn] multiple main classes detected: run 'show discoveredMainClasses' to see the list +[error] gpg: Warning: not using 'E7232FE8B8357EEC786315FE821738D92B63C95F' as default key: No secret key +[error] gpg: all values passed to '--default-key' ignored +[error] gpg: Warning: not using 'E7232FE8B8357EEC786315FE821738D92B63C95F' as default key: No secret key +[error] gpg: all values passed to '--default-key' ignored +[error] gpg: Warning: not using 'E7232FE8B8357EEC786315FE821738D92B63C95F' as default key: No secret key +[error] gpg: all values passed to '--default-key' ignored +[error] gpg: Warning: not using 'E7232FE8B8357EEC786315FE821738D92B63C95F' as default key: No secret key +[error] gpg: all values passed to '--default-key' ignored +[info] published introprog_3 to https://oss.sonatype.org/service/local/staging/deploy/maven2/se/lth/cs/introprog_3/1.4.0/introprog_3-1.4.0.pom.asc +[info] published introprog_3 to https://oss.sonatype.org/service/local/staging/deploy/maven2/se/lth/cs/introprog_3/1.4.0/introprog_3-1.4.0-javadoc.jar +[info] published introprog_3 to https://oss.sonatype.org/service/local/staging/deploy/maven2/se/lth/cs/introprog_3/1.4.0/introprog_3-1.4.0.pom +[info] published introprog_3 to https://oss.sonatype.org/service/local/staging/deploy/maven2/se/lth/cs/introprog_3/1.4.0/introprog_3-1.4.0.jar.asc +[info] published introprog_3 to https://oss.sonatype.org/service/local/staging/deploy/maven2/se/lth/cs/introprog_3/1.4.0/introprog_3-1.4.0.jar +[info] published introprog_3 to https://oss.sonatype.org/service/local/staging/deploy/maven2/se/lth/cs/introprog_3/1.4.0/introprog_3-1.4.0-javadoc.jar.asc +[info] published introprog_3 to https://oss.sonatype.org/service/local/staging/deploy/maven2/se/lth/cs/introprog_3/1.4.0/introprog_3-1.4.0-sources.jar +[info] published introprog_3 to https://oss.sonatype.org/service/local/staging/deploy/maven2/se/lth/cs/introprog_3/1.4.0/introprog_3-1.4.0-sources.jar.asc +``` + +OOOPS! TODO: I already had this file: `cat ~/.sbt/sonatype_credential` pulled in by `cat ~/.sbt/1.0/sonatype.sbt` so I should remove the last of them as Credentials is now included in the build.sbt + +5. After you have done `sbt publishSigned` then log into Sonatype Nexus here: (if the page does not load, clear the browser's cache by pressing Ctrl+F5) https://oss.sonatype.org/#welcome 6. Click on *Staging Repositories* in the Build Promotion list to the left. Click "Refresh" if list is empty. https://oss.sonatype.org/#stagingRepositories @@ -100,7 +136,7 @@ openpgp-revocs.d pubring.asc trustdb.gpg 8. Download the staged jar by clicking on it and selecting the *Artifact* tab to the right and click the Repository Path to download. Save it e.g. in `tmp`. -9. Verify that the staged jar downloaded from sonatype works by running `scala -cp introprog_3-x.y.z.jar` and in REPL e.g. `val w = new introprog.PixelWindow`. The reason for this step is that there has been incidents where the uploading has failed and the jar was empty. A published jar can not be retracted even if corrupted according to Sonatype policies. +9. Verify that the staged jar downloaded from sonatype works by running something similar to `scala-cli repl . -S 3.4.2 --jar introprog_3-1.4.0.jar` and in REPL e.g. `val w = new introprog.PixelWindow` or `introprog.examples.TestPixelWindow.main(Array())`. The reason for this step is that there has been incidents where the uploading has failed and the jar was empty. A published jar can not be retracted even if corrupted according to Sonatype policies. 10. Click the *Close* icon with a diskette above the repository list to "close" the staging repository. No need to write anything in the "Description" field in the popup. It has happened that the Close failed - then the repo is still "Open" so try to close it again and hope it works this time... @@ -109,3 +145,4 @@ openpgp-revocs.d pubring.asc trustdb.gpg 12. By searching here you can see the repo in progress of being published but it takes a while before it is publicly visible on Central (typically 10-15 minutes). https://oss.sonatype.org/#nexus-search;quick~se.lth.cs 13. When visible on Central at https://repo1.maven.org/maven2/se/lth/cs/introprog_3/ verify with a simple sbt project that it works as shown in [README usage instructions for sbt](https://github.com/lunduniversity/introprog-scalalib/blob/master/README.md#using-sbt). + diff --git a/README.md b/README.md index cca14a5..9d11fab 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ![Build Status](https://github.com/lunduniversity/introprog-scalalib/actions/workflows/main.yml/badge.svg) -[](http://search.maven.org/#search%7Cga%7C1%7Cg%3Ase.lth.cs%20a%3Aintroprog_3) [](http://search.maven.org/#search%7Cga%7C1%7Cg%3Ase.lth.cs%20a%3Aintroprog_2.13) [](http://search.maven.org/#search%7Cga%7C1%7Cg%3Ase.lth.cs%20a%3Aintroprog_2.12) +[](http://search.maven.org/#search%7Cga%7C1%7Cg%3Ase.lth.cs%20a%3Aintroprog_3) [](http://search.maven.org/#search%7Cga%7C1%7Cg%3Ase.lth.cs%20a%3Aintroprog_2.13) [](http://search.maven.org/#search%7Cga%7C1%7Cg%3Ase.lth.cs%20a%3Aintroprog_2.12) This is a library with Scala utilities for Computer Science teaching. The library is maintained by Björn Regnell at Lund University, Sweden. Contributions are welcome! @@ -14,12 +14,49 @@ This repo is used in this course *(in Swedish)*: http://cs.lth.se/pgk with cours ## How to use introprog-scalalib + +### Using scala-cli + +You need [Scala Command Line Interface](https://scala-cli.virtuslab.org/install) at least version 1.0.0. + +Add these magic comment lines starting with `//>` in the beginning of your Scala 3 file: + +``` +//> using scala 3.3 +//> using lib se.lth.cs::introprog:1.3.1 +``` + +You run your code with `scala-cli run .` (note the ending dot, meaning "this dir") + +If your program looks like this: + +``` +//> using scala 3.3 +//> using lib se.lth.cs::introprog:1.3.1 + +@main def run = + val w = introprog.PixelWindow() + w.drawText("Hello introprog.PixelWindow!", x = 100, y = 100) +``` +You should see green text in a new window after executing: +``` +scala-cli run . +``` +See: [api documentation for PixelWindow](https://fileadmin.cs.lth.se/pgk/api/api/introprog/PixelWindow.html) + +You can also give the `introprog` dependency directly at the command line, instead of the `using lib` directive: +``` +scala-cli run . --dep se.lth.cs::introprog:1.3.1 +``` + ### Using sbt -You need to have [Scala Build Tool](https://www.scala-sbt.org/download.html) version 1.5.2 or later and put this text in a file called `build.sbt` +You need [Scala Build Tool](https://www.scala-sbt.org/download.html) at least version 1.5.2 (preferably 1.6.2 or later). + +Put this text in a file called `build.sbt` ``` -scalaVersion := "3.0.1" -libraryDependencies += "se.lth.cs" %% "introprog" % "1.2.0" +scalaVersion := "3.3.0" +libraryDependencies += "se.lth.cs" %% "introprog" % "1.3.1" ``` When you run `sbt` in terminal the `introprog` package is automatically downloaded and made available on your classpath. @@ -30,18 +67,18 @@ sbt> console scala> val w = new introprog.PixelWindow() scala> w.fill(100,100,100,100,java.awt.Color.red) ``` - +See: [api documentation for PixelWindow](https://fileadmin.cs.lth.se/pgk/api/api/introprog/PixelWindow.html) ### Older Scala versions -If you want to use Scala 2.13 with 2.13.5 or later then use these special settings in `build.sbt`: +If you want to use Scala 2.13 with 2.13.5 or later then use these special settings in `build.sbt`, esp. note that you should use version 1.1.5 of introprog: ``` -scalaVersion := "2.13.6" +scalaVersion := "2.13.8" //2.13.5 or any later 2.13 version scalacOptions += "-Ytasty-reader" libraryDependencies += ("se.lth.cs" %% "introprog" % "1.1.5").cross(CrossVersion.for2_13Use3) ``` -For Scala 2.12.x and 2.13.4 and older you need to use the old version `"1.1.4"` of `introprog`. +For Scala 2.12.x and 2.13.4 and older you need to use version 1.1.4 of introprog or older. ### Manual download @@ -63,13 +100,24 @@ With [`sbt`](https://www.scala-sbt.org/download.html) and [`git`](https://git-sc > sbt package ``` +## How to build and see the doc pages using a local server + +Run this in linux bash terminal: +``` +sbt doc && cd target/scala-3.3.3/api && python3 -m http.server 8080 +``` +Open Firefox and type this url in the address field: +``` +http://localhost:8080/ +``` + ## Intentions and philosophy behind introprog-scalalib This repo includes utilities to empower learners to advance from basic to intermediate levels of computer science by providing easy-to-use constructs for creating simple desktop apps in terminal and using simple 2D graphics. The utilities are implemented and exposed through an api that follows these guidelines: * Use as simple constructs as possible. * Follow Scala idioms with a pragmatic mix of imperative, functional and object-oriented programming. -* Don't use advanced functional programming concepts and magical implicit. +* Don't use advanced functional programming concepts and magical implicits. * Prefer a clean api with single-responsibility functions in simple modules. * Prefer immutability over mutable state, `Vector` for sequences and case classes for data. * Hide/avoid threading and complicated concurrency. @@ -81,4 +129,4 @@ Areas currently in scope of this library: * Simple pixel-based 2D graphics for single-threaded game programming with explicit game loop. * Simple blocking IO that hides the underlying complication of releasing resources etc. -* Simple modal GUI dialogs that blocks while waiting for user response. +* Simple modal GUI dialogs that block while waiting for user response. diff --git a/build.sbt b/build.sbt index afb2282..c6aae06 100644 --- a/build.sbt +++ b/build.sbt @@ -1,19 +1,18 @@ -lazy val Version = "1.3.1" +lazy val Version = "1.4.0" lazy val Name = "introprog" -//lazy val scala213 = "2.13.6" -lazy val scala3 = "3.0.2" -//lazy val supportedScalaVersions = List(scala213, scala3) +lazy val scala3 = "3.3.3" + +Global / onChangedBuildSource := ReloadOnSourceChanges // to avoid strange warnings, these lines with excludeLintKeys are needed: Global / excludeLintKeys += ThisBuild / Compile / console / fork - lazy val introprog = (project in file(".")) .settings( name := Name, version := Version, scalaVersion := scala3, - //crossScalaVersions := supportedScalaVersions, + libraryDependencies += "org.scalameta" %% "munit" % "0.7.29" % Test, ) ThisBuild / Compile / console / fork := true @@ -21,9 +20,9 @@ ThisBuild / Compile / console / fork := true //https://github.com/scalacenter/sbt-version-policy ThisBuild / versionScheme := Some("early-semver") ThisBuild / versionPolicyIntention := Compatibility.None +//ThisBuild / versionPolicyIntention := Compatibility.None //ThisBuild / versionPolicyIntention := Compatibility.BinaryAndSourceCompatible //ThisBuild / versionPolicyIntention := Compatibility.BinaryCompatible - //In the sbt shell check version using: //sbt> versionCheck //sbt> versionPolicyCheck @@ -42,7 +41,7 @@ ThisBuild / scalacOptions ++= Seq( // "-Ywarn-unused" ) -ThisBuild / Compile / compile / javacOptions ++= Seq("-target", "1.8") +ThisBuild / Compile / compile / javacOptions ++= Seq("-target", "1.8") // for backward compat Compile / doc / scalacOptions ++= Seq( "-groups", @@ -96,10 +95,12 @@ publishConfiguration := publishConfiguration.value.withOverwrite(true) publishLocalConfiguration := publishLocalConfiguration.value.withOverwrite(true) //pushRemoteCacheConfiguration := pushRemoteCacheConfiguration.value.withOverwrite(true) +credentials += Credentials(Path.userHome / ".sbt" / ".credentials") + //https://oss.sonatype.org/#stagingRepositories //https://oss.sonatype.org/#nexus-search;quick~se.lth.cs //https://repo1.maven.org/maven2/se/lth/cs/introprog_2.12/ -//usePgpKeyHex("E7232FE8B8357EEC786315FE821738D92B63C95F") -//https://github.com/sbt/sbt-pgp \ No newline at end of file +//https://github.com/sbt/sbt-pgp + diff --git a/docs/index.md b/docs/index.md index 3d90bef..f42c663 100644 --- a/docs/index.md +++ b/docs/index.md @@ -18,13 +18,45 @@ The open source code is hosted at [[https://github.com/lunduniversity/introprog- - [[introprog.examples]] with code examples demonstrating how to use this library. -## How to use this library with `sbt` +## How to use introprog-scalalib -If you have [sbt](https://www.scala-sbt.org/) installed then you can put this text in a file called `build.sbt` +### Using scala-cli + +You need [Scala Command Line Interface](https://scala-cli.virtuslab.org/install) + +Add these magic comment lines starting with `//>` in the beginning of your Scala 3 file: + +``` +//> using scala 3 +//> using dep "se.lth.cs::introprog:1.4.0" +``` +You can choose the latest stable Scala version, or any version from at least Scala 3.3.3. + +You run your code with `scala-cli run .` (note the ending dot, meaning "this dir") + +If your program looks like this: + +``` +//> using scala 3 +//> using dep "se.lth.cs::introprog:1.4.0" + +@main def MyMain = + val w = introprog.PixelWindow() + w.drawText("Hello introprog.PixelWindow!", x = 100, y = 100) +``` +You should see green text in a new window after executing: +``` +scala-cli run . +``` +See: [api documentation for PixelWindow](https://fileadmin.cs.lth.se/pgk/api/api/introprog/PixelWindow.html) + +### Using sbt + +If you have [sbt](https://www.scala-sbt.org/) installed at least version 1.10.0 then you can put this text in a file called `build.sbt` ``` -scalaVersion := "3.0.2" -libraryDependencies += "se.lth.cs" %% "introprog" % "1.3.1" +scalaVersion := "3.4.2" // or any Scala version from at least 3.3.3 +libraryDependencies += "se.lth.cs" %% "introprog" % "1.4.0" ``` When you run `sbt` in a terminal, with the above in your `build.sbt`, the introprog lib is automatically downloaded and made available on your classpath. Then you can do things like: diff --git a/project/build.properties b/project/build.properties index 10fd9ee..e8a1e24 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.5.5 +sbt.version=1.9.7 diff --git a/project/plugins.sbt b/project/plugins.sbt index 4092d54..b7a2210 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,2 +1,2 @@ // https://github.com/scalacenter/sbt-version-policy -addSbtPlugin("ch.epfl.scala" % "sbt-version-policy" % "1.2.1") \ No newline at end of file +addSbtPlugin("ch.epfl.scala" % "sbt-version-policy" % "3.2.1") \ No newline at end of file diff --git a/publish-doc.sh b/publish-doc.sh index 7185127..79245a7 100644 --- a/publish-doc.sh +++ b/publish-doc.sh @@ -1,7 +1,7 @@ echo "*** Generating docs and copy api to fileadmin then zip it for local download" set -x -SCALAVERSION=3.0.2 +SCALAVERSION=3.3.3 sbt doc ssh $LUCATID@fileadmin.cs.lth.se rm -r pgk/api diff --git a/publish-jar.sh b/publish-jar.sh index 8acc71b..8ffcd90 100644 --- a/publish-jar.sh +++ b/publish-jar.sh @@ -1,6 +1,6 @@ #VERSION="$(grep -m 1 -Po -e '\d+.\d+.\d+' build.sbt)" -VERSION=1.3.1 -SCALAVERSION=3.0.2 +VERSION=1.4.0 +SCALAVERSION=3.3.3 SCALACOMPAT=3 JARFILE="introprog_$SCALACOMPAT-$VERSION.jar" diff --git a/src/main/scala/introprog/BlockGame.scala b/src/main/scala/introprog/BlockGame.scala index 9b443f8..2222d9a 100644 --- a/src/main/scala/introprog/BlockGame.scala +++ b/src/main/scala/introprog/BlockGame.scala @@ -2,7 +2,9 @@ package introprog import java.awt.Color -/** A class for creating games with block-based graphics. +/** A class for creating games with block-based graphics. + * See example usage in [introprog.examples.TestBlockGame](https://github.com/lunduniversity/introprog-scalalib/blob/master/src/main/scala/introprog/examples/TestBlockGame.scala#L7) + * * @constructor Create a new game. * @param title the title of the window * @param dim the (width, height) of the window in number of blocks @@ -84,6 +86,8 @@ abstract class BlockGame( /** The game loop that continues while not `stopWhen` is true. * It draws only updated blocks aiming at the desired frame rate. * It calls each `onXXX` method if a corresponding event is detected. + * Use the call-by-name `stopWhen` to pass a condition that ends the loop if false. + * See example usage in `introprog.examples.TestBlockGame`. */ protected def gameLoop(stopWhen: => Boolean): Unit = while !stopWhen do import PixelWindow.Event diff --git a/src/main/scala/introprog/IO.scala b/src/main/scala/introprog/IO.scala index 5f382b9..dd4e32a 100644 --- a/src/main/scala/introprog/IO.scala +++ b/src/main/scala/introprog/IO.scala @@ -1,5 +1,9 @@ package introprog +import java.io.BufferedWriter +import java.io.FileWriter +import java.nio.charset.Charset + /** A module with input/output operations from/to the underlying file system. */ object IO: /** @@ -47,7 +51,34 @@ object IO: * @param enc the encoding of the file. * */ def saveLines(lines: Seq[String], fileName: String, enc: String = "UTF-8"): Unit = - saveString(lines.mkString("\n"), fileName, enc) + if lines.nonEmpty then saveString(lines.mkString("", "\n", "\n"), fileName, enc) + + /** + * Appends `string` to the text file `fileName` using encoding `enc`. + * + * @param text the text to be appended to the file. + * @param fileName the path of the file. + * @param enc the encoding of the file. + * */ + def appendString(text: String, fileName: String, enc: String = "UTF-8"): Unit = + val f = new java.io.File(fileName); + require(!f.isDirectory(), "The file you're trying to write to can't be a directory.") + val w = + if f.exists() then + new BufferedWriter(new FileWriter(fileName, Charset.forName(enc), true)) + else + new java.io.PrintWriter(f, enc) + try w.write(text) finally w.close() + + /** + * Appends `lines` to the text file `fileName` using encoding `enc`. + * + * @param lines the lines to append to the file. + * @param fileName the path of the file. + * @param enc the encoding of the file. + * */ + def appendLines(lines: Seq[String], fileName: String, enc: String = "UTF-8"): Unit = + if lines.nonEmpty then appendString(lines.mkString("","\n","\n"), fileName, enc) /** * Load a serialized object from a binary file called `fileName`. diff --git a/src/main/scala/introprog/Image.scala b/src/main/scala/introprog/Image.scala index 5c5ad9d..669de8d 100644 --- a/src/main/scala/introprog/Image.scala +++ b/src/main/scala/introprog/Image.scala @@ -1,18 +1,16 @@ package introprog - +/** Companion object to create Image instances. */ object Image: import java.awt.image.BufferedImage /** Create new empty Image with specified dimensions `(width, height)`*/ def ofDim(width: Int, height: Int) = Image(BufferedImage(width, height, BufferedImage.TYPE_INT_RGB)) - - +/** Image represents pixel arrays backed by underlying java.awtimage.BufferedImage */ class Image (val underlying: java.awt.image.BufferedImage): import java.awt.Color import java.awt.image.BufferedImage - /** Get color of pixel at `(x, y)`.*/ def apply(x: Int, y: Int): Color = Color(underlying.getRGB(x, y)) @@ -25,14 +23,13 @@ class Image (val underlying: java.awt.image.BufferedImage): for x <- 0 until width; y <- 0 until height do update(x, y, f(x, y)) - /** Set color of pixels by passing `f(x, y)` and return self*/ + /** Set color of pixels by passing `f(x, y)` and return self. */ def updated(f: (Int, Int) => Color): Image = for x <- 0 until width; y <- 0 until height do update(x, y, f(x, y)) this - - /** Extract and return image pixels.*/ + /** Extract and return image pixels. */ def toMatrix: Array[Array[Color]] = val xs: Array[Array[Color]] = Array.ofDim(width, height) for x <- 0 until width; y <- 0 until height do @@ -62,9 +59,13 @@ class Image (val underlying: java.awt.image.BufferedImage): val bi = BufferedImage(width, height, imageType) bi.createGraphics().drawImage(underlying, 0, 0, width, height, null) Image(bi) - - + + /** Test if alpha channel is supperted. */ val hasAlpha = underlying.getColorModel.hasAlpha + + /** The height of this image. */ val height = underlying.getHeight + + /** The width of this image. */ val width = underlying.getWidth \ No newline at end of file diff --git a/src/main/scala/introprog/PixelWindow.scala b/src/main/scala/introprog/PixelWindow.scala index 9693fc7..939ec06 100644 --- a/src/main/scala/introprog/PixelWindow.scala +++ b/src/main/scala/introprog/PixelWindow.scala @@ -96,7 +96,7 @@ object PixelWindow: case _ => throw new IllegalArgumentException(s"Unknown event number: $event") -/** A window with a canvas for pixel-based drawing. +/** A window with a canvas for pixel-based drawing. Y-coordinates are increasing downwards. * * @constructor Create a new window for pixel-based drawing. * @param width the number of horizontal pixels diff --git a/src/main/scala/introprog/examples/TestBlockGame.scala b/src/main/scala/introprog/examples/TestBlockGame.scala index 018a3c3..5f7d810 100644 --- a/src/main/scala/introprog/examples/TestBlockGame.scala +++ b/src/main/scala/introprog/examples/TestBlockGame.scala @@ -1,12 +1,17 @@ package introprog.examples -/** Example of a simple BlockGame app with overridden callbacks to handle events +/** Examples of a simple BlockGame app with overridden callbacks to handle events * See the documentation of BlockGame and the source code of TestBlockGame * for inspiration on how to inherit BlockGame to create your own block game. */ object TestBlockGame: /** Create Game and start playing. */ - def main(args: Array[String]): Unit = (new RandomBlocks).play() + def main(args: Array[String]): Unit = + println("Press Enter to toggle random blocks. Close window to continue.") + (new RandomBlocks).play() + println("Opening MovingBlock. Press Ctrl+C to exit.") + (new MovingBlock).start() + println("MovingBlock has ended.") /** A class extending `introprog.BlockGame`, see source code. */ class RandomBlocks extends introprog.BlockGame: @@ -61,3 +66,59 @@ object TestBlockGame: showEnterMessage() gameLoop(stopWhen = state == GameOver) println("Goodbye!") + + end RandomBlocks + + class MovingBlock extends introprog.BlockGame( + title = "MovingBlock", + dim = (10,5), + blockSize = 40, + background = java.awt.Color.BLACK, + framesPerSecond = 50, + messageAreaHeight = 1, + messageAreaBackground = java.awt.Color.DARK_GRAY + ): + + var movesPerSecond: Double = 2 + + def millisBetweenMoves: Int = (1000 / movesPerSecond).round.toInt max 1 + + var _timestampLastMove: Long = System.currentTimeMillis + + def timestampLastMove = _timestampLastMove + + var x = 0 + + var y = 0 + + def move(): Unit = + if x == dim._1 - 1 then + x = -1 + y += 1 + end if + x = x+1 + + def erase(): Unit = drawBlock(x, y, java.awt.Color.BLACK) + + def draw(): Unit = drawBlock(x, y, java.awt.Color.CYAN) + + def update(): Unit = + if System.currentTimeMillis > _timestampLastMove + millisBetweenMoves then + move() + _timestampLastMove = System.currentTimeMillis() + + var loopCounter: Int = 0 + + override def gameLoopAction(): Unit = + erase() + update() + draw() + clearMessageArea() + drawTextInMessageArea(s"Loop number: $loopCounter", 1, 0, java.awt.Color.PINK, size = 30) + loopCounter += 1 + + final def start(): Unit = + pixelWindow.show() // show window again if closed and start() is called again + gameLoop(stopWhen = x == dim._1 - 1 && y == dim._2 - 1) + + end MovingBlock diff --git a/src/test/scala/testIO.scala b/src/test/scala/testIO.scala new file mode 100644 index 0000000..a5d3e44 --- /dev/null +++ b/src/test/scala/testIO.scala @@ -0,0 +1,28 @@ +package introprog + +val tmpDir = "target/tmp" +def createTmp(): Boolean = IO.createDirIfNotExist(tmpDir) + +class TestIO extends munit.FunSuite: + + test("TestIO: createDirIfNotExist"): + val existed = createTmp() + assert(IO.isExisting(tmpDir), s"dir should exists: $tmpDir") + + test("TestIO: saveString, loadString, appendString, loadLines, appendLines"): + createTmp() + val s1 = "hello" + val fn = s"$tmpDir/hello.txt" + IO.saveString(s1, fileName = fn) + val s2 = IO.loadString(fileName = fn) + assertEquals(s1, s2, "saved string different from loaded") + IO.appendString("!\n", fileName = fn ) + val s3 = IO.loadString(fileName = fn) + assertEquals(s3, s2 + "!\n", "saved string is missing appended '!+newline'") + IO.appendLines(Seq("line2"),fileName = fn) + val s4 = IO.loadLines(fileName = fn) + assertEquals(s4, Vector("hello!", "line2"), s"loadLines not as expected: $s4") + val s5 = IO.loadString(fileName = fn) + assertEquals(s5, "hello!\nline2\n", s"loadLines not as expected: $s5") + IO.appendLines(Seq(),fileName = fn) // nothing should be added, not even newline + assertEquals(s5, IO.loadString(fileName = fn), s"loadLines not as expected: $s5")