diff options
author | Joel Kronqvist <joel.kronqvist@iki.fi> | 2024-11-05 00:18:36 +0200 |
---|---|---|
committer | Joel Kronqvist <joel.kronqvist@iki.fi> | 2024-11-05 00:18:36 +0200 |
commit | 12cbf4d451d1002b8872b0028acbe6bd886ca9bd (patch) | |
tree | c8d8ed236cc7ae907f52209cb0dd1f7d40ebf346 | |
parent | ae82027a9bd4e75582f9499d4006b18c29a4129c (diff) | |
download | scalevalapokalypsi-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.scala | 27 | ||||
-rw-r--r-- | src/main/scala/Server/ConnectionGetter.scala | 25 | ||||
-rw-r--r-- | src/main/scala/Server/Server.scala | 49 | ||||
-rw-r--r-- | tyylimaare.txt | 157 |
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# + + |