aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoel Kronqvist <joel.kronqvist@iki.fi>2024-11-05 00:18:36 +0200
committerJoel Kronqvist <joel.kronqvist@iki.fi>2024-11-05 00:18:36 +0200
commit12cbf4d451d1002b8872b0028acbe6bd886ca9bd (patch)
treec8d8ed236cc7ae907f52209cb0dd1f7d40ebf346
parentae82027a9bd4e75582f9499d4006b18c29a4129c (diff)
downloadscalevalapokalypsi-12cbf4d451d1002b8872b0028acbe6bd886ca9bd.tar.gz
scalevalapokalypsi-12cbf4d451d1002b8872b0028acbe6bd886ca9bd.zip
Small refactoring
* Moved `ConnectionGetter` to its own file * Added method `mapAndRemove` to Server/Client.scala * Replaced usages of `removeNonSatisfying` with the above * Added some documentation
-rw-r--r--src/main/scala/Server/Client.scala27
-rw-r--r--src/main/scala/Server/ConnectionGetter.scala25
-rw-r--r--src/main/scala/Server/Server.scala49
-rw-r--r--tyylimaare.txt157
4 files changed, 219 insertions, 39 deletions
diff --git a/src/main/scala/Server/Client.scala b/src/main/scala/Server/Client.scala
index c7ff075..3bc4012 100644
--- a/src/main/scala/Server/Client.scala
+++ b/src/main/scala/Server/Client.scala
@@ -1,6 +1,7 @@
package o1game.Server
import java.net.Socket
+import scala.util.Try
import scala.math.min
import o1game.constants.*
@@ -69,12 +70,36 @@ class Clients(maxClients: Int):
this.clients(i) = Some(client)
true
+ /** Returns all the clients.
+ *
+ * @return an iterable of all the clients
+ */
def allClients: Iterable[Client] = clients.toVector.flatten
+
+ /** Applies the function `f` to all the clients for its side effects. */
def foreach(f: Client => Any): Unit = this.clients.flatten.foreach(f)
+
+ /** Applies the function `f` to all the clients for its side effects
+ * and removes all the clients for which `f([client])` returns false.
+ * This is useful for doing IO with the client and removing clients
+ * with stale sockets.
+ *
+ * @param f the function to apply to all the clients and filter them with
+ */
def removeNonSatisfying(f: Client => Boolean): Unit =
for i <- this.clients.indices do
this.clients(i) match
case Some(c) =>
if !f(c) then
this.clients(i) = None
- case None => \ No newline at end of file
+ case None =>
+
+ /** Applies the function f to all clients for its side effects.
+ * If the function throws an exception, the client is removed.
+ * Probably a more concise alternative to `removeNonSatisfying`,
+ * but might catch exceptions unintentionally.
+ *
+ * @param f the function to apply for its side effects to each client
+ */
+ def mapAndRemove(f: Client => Unit): Unit =
+ this.removeNonSatisfying(c => Try(f(c)).isSuccess) \ No newline at end of file
diff --git a/src/main/scala/Server/ConnectionGetter.scala b/src/main/scala/Server/ConnectionGetter.scala
new file mode 100644
index 0000000..b3246a7
--- /dev/null
+++ b/src/main/scala/Server/ConnectionGetter.scala
@@ -0,0 +1,25 @@
+package o1game.Server
+
+import java.io.IOException
+import java.net.{ServerSocket, Socket}
+import scala.concurrent.Future
+import scala.util.{Failure, Success}
+import scala.concurrent.ExecutionContext.Implicits.global
+
+/** Small helper class for getting new connections using futures */
+class ConnectionGetter(val socket: ServerSocket):
+
+ private var nextClient: Future[Socket] = Future.failed(IOException())
+
+ /** Returns a new socket to a client if there is any new connections. */
+ def newClient(): Option[Socket] =
+ this.nextClient.value match
+ case Some(Success(s)) =>
+ nextClient = Future(socket.accept())
+ Some(s)
+ case Some(Failure(e)) =>
+ nextClient = Future(socket.accept())
+ None
+ case None => None
+
+end ConnectionGetter
diff --git a/src/main/scala/Server/Server.scala b/src/main/scala/Server/Server.scala
index 829010c..1f0cbfc 100644
--- a/src/main/scala/Server/Server.scala
+++ b/src/main/scala/Server/Server.scala
@@ -6,9 +6,6 @@ package o1game.Server
import java.io.IOException
import java.lang.Thread.sleep
import java.net.{ServerSocket, Socket}
-import scala.concurrent.ExecutionContext.Implicits.global
-import scala.concurrent.Future
-import scala.util.{Failure, Success, Try}
import o1game.constants.*
/** `Server` exists to initialize a server for the game
@@ -47,44 +44,20 @@ class Server(port: Int, maxClients: Int):
* @param message the message to send
*/
private def writeToAll(message: String): Unit =
- clients.removeNonSatisfying((c: Client) =>
- try
- val output = c.socket.getOutputStream
- output.write(message.toVector.map(_.toByte).toArray)
- output.flush()
- true
- catch
- case e: IOException => false
+ clients.mapAndRemove(c =>
+ val output = c.socket.getOutputStream
+ output.write(message.toVector.map(_.toByte).toArray)
+ output.flush()
)
/** Reads data sent by clients and stores it in the `Client`s of `clients` */
private def readFromAll(): Unit =
- clients.removeNonSatisfying((c: Client) =>
- try
- val input = c.socket.getInputStream
- while input.available() != 0 do
- val bytesRead = input.read(buffer)
- if bytesRead != -1 then
- c.receiveData(buffer.take(bytesRead).toVector)
- true
- catch
- case e: IOException => false
+ clients.mapAndRemove(c =>
+ val input = c.socket.getInputStream
+ while input.available() != 0 do
+ val bytesRead = input.read(buffer)
+ if bytesRead != -1 then
+ c.receiveData(buffer.take(bytesRead).toVector)
)
-end Server
-
-class ConnectionGetter(val socket: ServerSocket):
-
- private var nextClient: Future[Socket] = Future.failed(IOException())
-
- def newClient(): Option[Socket] =
- this.nextClient.value match
- case Some(Success(s)) =>
- nextClient = Future(socket.accept())
- Some(s)
- case Some(Failure(e)) =>
- nextClient = Future(socket.accept())
- None
- case None => None
-
-end ConnectionGetter
+end Server \ No newline at end of file
diff --git a/tyylimaare.txt b/tyylimaare.txt
new file mode 100644
index 0000000..2de2c18
--- /dev/null
+++ b/tyylimaare.txt
@@ -0,0 +1,157 @@
+
+Scala-tyylimääre tekstipeliin
+=============================
+
+
+Seuraa kurssin tyyliopasta ja tätä ohjetta. Noudata tätä ohjetta, jos ohjeiden
+välillä on ristiriita. Kirjoita mieluummin vähemmän koodia paremmin kuin
+enemmän koodia laiskasti. Näin vähennetään tulevaisuuden työtä, joka syntyy
+kun koodi on lähtenyt lapasesta eikä kenelläkään ole enää mitään käsitystä
+ohjelman struktuurista. Tee näin etenkin, kun kirjoittamamme koodin laatua ja
+yhtenäisyyttä arvioidaan palautuksessa.
+
+=> https://docs.scala-lang.org/scala3/book/ca-multiversal-equality.html
+Kannattaiskohan käyttää? ^^
+
+
+This-sanan käyttö
+-----------------
+
+This-sanaa tulee käyttää aina, kun se on mahdollista. Se selventää sitä, kenen
+muuttuja on kyseessä ja missä se on määritelty.
+
+
+Merkkijonojen muodostaminen
+---------------------------
+
+Käytä s"Arvo: $a, toinen arvo: ${this.b}" jos se ei ole aivan tajuttoman
+kömpelö ratkaisu tilanteeseen. Se on lähes aina parempi ilmaisu kuin merkki-
+jonojen summaaminen.
+
+
+Tyyppien kirjaaminen
+--------------------
+
+Nimellisiin funktioihin kirjoitetaan aina paluutyyppi, vaikka se olisi Unit tai
+funktio ei olisi julkinen. Paluutyypin kirjoittaminen pakottaa miettimään vielä
+kerran funktion todellista tarkoitusta. Kun sen on kirjoittanut, Scalan tyyppi-
+järjestelmä huomaa virheet aikaisemmin ja ne saadaan korjattua. Julkisilla
+funktioilla tyyppimääre toimii hieman kuin dokumentaatio.
+
+Julkisille muuttujille (etenkin ohjelman laajuisille vakioille) tulee kirjata
+tyypit.
+
+
+Rivien pituudet
+---------------
+
+Rivin maksimipituus on 80 merkkiä. Tässä sisennykset lasketaan kahdeksaksi
+merkiksi. Tämä rajoitus on ikiaikaista perua siitä, kun terminaalien standardi-
+koko leveyssuunnassa oli 80 merkkiä. Tämä rajoitus on kuitenkin nykyäänkin
+kätevä, koska kahta 80-merkkistä riviä on helppo pitää vierekkäin melkein näy-
+töllä kuin näytöllä. Lisäksi ylipitkä rivi voi olla oire epäselkeästä koodista,
+joka muutenkin kuuluisi jakaa osiin.
+
+Alla on esimerkkejä, miten ylipitkiä rivejä saa (ja kuuluu) jakaa.
+
+Esim 1
+```
+def jokuNimi(parametri1: tyyppi1, parametri2: tyyppi2, parametri3: tyyppi3): paluutyyppi =
+ ???
+
+def jokuNimi(
+ parametri1: tyyppi1,
+ parametri2: tyyppi2,
+ parametri3: tyyppi3
+): paluutyyppi =
+ ???
+```
+
+Esim 2
+```
+kokoelma.filter(_ % 2 ==0).flatten.map(_.muutaHauskallaTavalla).contains(condition)
+
+kokoelma
+ .filter(_ % 2 ==0)
+ .flatten
+ .map(_.muutaHauskallaTavalla)
+ .contains(condition)
+```
+
+Esim 3
+```
+val pitkäMuttaHyväMuuttujanNimi = PitkäOlionNimiJotaEiTahdotaMuuttaa(jokuParametri)
+
+val pitkäMuttaHyväMuuttujanNimi =
+ PitkäOlionNimiJotaEiTahdotaMuuttaa(jokuParametri)
+```
+
+Esim 4
+```
+val olio = MoniparametrisenOlionLuoja(parametri1, parametri2, parametri3, parametri4, parametri5)
+
+val olio = MoniparametrisenOlionLuoja(
+ parametri1,
+ parametri2,
+ parametri3,
+ parametri4,
+ parametri5
+)
+```
+
+Esim 5: Huomaa myös tyhjän tilan käyttö ja kommentin alku- ja loppumerkkien
+ paikat. Tuollainen muotoilu on nättiä ja suotavaa.
+```
+/* Pitkä dokumentaatiokommentti funktiolle, jonka kuuluu olla useammalla rivillä, koska muuten koodin dokumentaatiosta ei saa selvää ja on näin ollen turhaa*/
+
+/* Pitkä dokumentaatiokommentti funktiolle, jonka kuuluu olla useammalla
+ rivillä, koska muuten koodin dokumentaatiosta ei saa selvää ja on näin ollen
+ turhaa. */
+```
+
+
+Sisennykset
+-----------
+
+Sisentämiseen käytetään sisennyksiä eikä välilyöntejä. Näin jokainen ohjelmoija
+saa katsoa koodiansa sillä sisennyspituudella josta pitää ja säästetään muisti-
+tilaa.
+
+IntelliJ IDEAn tapauksessa tulee siis muuttaa asetus
+File
+ > Settings
+ > Editor
+ > Code Style
+ > Java
+ > Tabs and Indents
+ > Use tab character
+
+Kuten Linus Torvalds on sanonut, jos tiedostossa on enemmän kuin kolme
+sisennettyä tasoa ([tab][tab][tab]), olet todennäköisesti eksyksissä koodissasi
+ja ohjelmasi sisäinen logiikka ja rakenne kaipaa parantamista.
+
+Kolmen sisennyksen sääntö ei ole kiveen hakattu, koska me ei olla yhtä hyviä
+ohjelmoijia kuin Torvalds. Jos kuitenkin huomaat olevasi sisennysten
+viidakossa, josta ei saa selvää, niin voi olla hyvä miettiä, voisiko ohjelman
+rakennetta muokata vaikkapa lisäämällä apufunktioita yms.
+
+
+If, match, for, while jne...
+----------------------------
+
+Jos saatavilla on ohjelman toimintaa ja datan virtausta kuvaavia korkeamman
+asteen funktioita, ja et tiedä niiden suorituskyvyn olevan heikkoa verrattnua
+itse tekemääsi toteutukseen, käytä korkeamman asteen funktioita. Jos et heti
+keksi sopivaa korkeamman asteen funktiota, ei se tarkoita ettei sellaista ole.
+Lue läpi korkeamman asteen funktiot Scalan dokumentaatiosta, esim.
+
+Vector-luokan dokumentaatio:
+=> https://scala-lang.org/api/3.x/scala/collection/immutable/Vector.html#
+
+Buffer-luokan dokumentaatio:
+=> https://scala-lang.org/api/3.x/scala/collection/mutable/Buffer$.html#
+
+Option-luokan dokumentaatio:
+=> https://scala-lang.org/api/3.x/scala/Option.html#
+
+