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 @@

-[
](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")