From 28d81263b00c39ee2127374761f88b19ddfb93a4 Mon Sep 17 00:00:00 2001 From: EliasAAradsson Date: Sun, 20 Oct 2024 01:29:48 +0200 Subject: [PATCH 1/2] Add idea about exercise 5.8 --- compendium/modules/w05-classes-exercise.tex | 61 ++++++++++++++------- 1 file changed, 42 insertions(+), 19 deletions(-) diff --git a/compendium/modules/w05-classes-exercise.tex b/compendium/modules/w05-classes-exercise.tex index f5307f51..1c148748 100644 --- a/compendium/modules/w05-classes-exercise.tex +++ b/compendium/modules/w05-classes-exercise.tex @@ -510,12 +510,11 @@ \item \code{y: Double} för y-koordinaten. \end{itemize} -\item \code{Point} ska ha följande publika medlemmar (två oföränderliga attribut och två metoder): +\item \code{Point} ska ha följande publika medlemmar (två oföränderliga attribut och en metod): \begin{itemize}[nolistsep, noitemsep] -\item \code{val r: Double} ska ge motsvarande polära koordinatens +\item \code{lazy val r: Double} ska ge motsvarande polära koordinatens avstånd till origo. -\item \code{val theta: Double} ska ge polära koordinatens vinkel i radianer. -\item \code{def negY: Point} ska ge en ny punkt med y-koordinaten negerad. +\item \code{lazy val theta: Double} ska ge polära koordinatens vinkel i radianer. \item \code{def +(p: Point): Point} ska ge en ny punkt vars koordinat är summan av x- respektive y-koordinaterna för denna instans och punkten \code{p}. \end{itemize} @@ -559,16 +558,32 @@ \item Du har nytta av metoderna \code{math.cos(theta)} och \code{math.sin(theta)} vid omvandling från polära koordinater. -\item Attributet \code{negY} är användbar vid omvandling till fönsterkoordinater där y-axeln är omvänd jämfört med kartesiska koordinater. - \item Notera att klassens attribut är av typen \code{Double} och inte \code{Int}, trots att vi senare ska använda punkten för att beskriva en diskret pixelposition i ett \code{PixelWindow}. Anledningen till detta är att det kan uppstå avrundningsfel vid numeriska beräkningar. Detta blir särskilt märkbart vid upprepad räkning med små värden, t.ex. när man ritar en approximerad cirkel med många linjesegment. \end{itemize} -\Subtask Använd din punktklass då du deklarerar en funktion \code{cyclic(n, r, p)} som ger en sekvens av punkter som beskriver en liksidig månghörning med \code{n} hörn där hörnen är placerade på en cirkel med radien \code{r} och mittpunkten \code{p}. - -\Subtask Skapa en funktion \code{drawPolygon(pts)} som ritar månghörningar enligt en punktsekvens \code{pts} i ett \code{PixelWindow}. - -\Subtask Hur många hörn behövs det för att en liksidig månghörning ska se ut som en cirkel? +\Subtask Studera nu följande singelobjekt \code{PolygonWindow} som med hjälp av ett \code{PixelWindow} ger möjlighet att rita ut polygoner: +\begin{Code} +object PolygonWindow: + import introprog.PixelWindow + import java.awt.Color + + val black = Color(0,0,0) + val coolGreen = Color(0,255,100) + val width = 500 + val height = 500 + val window = PixelWindow(width, height, title = "Polygons", background = black, foreground = coolGreen) + + private def convertPoint(point: Point): Vector[Int] = + Vector(point.x.toInt, point.y.toInt) + + def draw(polygon: Polygon): Unit = + for i <- 0 until polygon.nbrOfCorners do + val from = convertPoint(polygon.pointsForDrawing(i)).map(_ + width/2) + val to = convertPoint(polygon.pointsForDrawing((i+1) % polygon.nbrOfCorners)).map(_ + width/2) + window.line(from(0), from(1), to(0), to(1), lineWidth = 2) +\end{Code} +Skapa case-klassen \code{Polygon(points: Vector[Point], midPoint: Point)} med attributen \code{lazy val nbrOfCorners: Int} och \code{lazy val pointsForDrawing: Vector[Point]} så att ovanstående kod går att köra. +\code{Polygon} ska ha ett kompanjonsobjekt med en metod \code{makePolygon(nbrOfCorners: Int, radius: Double, midPoint: Point): Polygon} för att skapa polygoner. \SOLUTION @@ -578,9 +593,8 @@ package graphics case class Point(x: Double, y: Double): - val r: Double = math.hypot(x, y) - val theta: Double = math.atan2(y, x) - def negY: Point = Point(x, -y) + lazy val r: Double = math.hypot(x, y) + lazy val theta: Double = math.atan2(y, x) def +(p: Point): Point = Point(x + p.x, y + p.y) object Point: @@ -588,11 +602,20 @@ Point(r * math.cos(theta), r * math.sin(theta)) \end{Code} -\Subtask \TODO - -\Subtask \TODO - -\Subtask \TODO +\Subtask \begin{Code} +case class Polygon(points: Vector[Point], midPoint: Point): + lazy val nbrOfCorners: Int = points.length + lazy val pointsForDrawing: Vector[Point] = points.map(_ + midPoint) + +object Polygon: + def makePolygon(nbrOfCorners: Int, radius: Double, midPoint: Point): Polygon = + val points = new Array[Point](nbrOfCorners) + for i <- 0 until nbrOfCorners do + val theta = i * (2 * math.Pi) / nbrOfCorners + points(i) = Point.polar(radius, theta) + end for + Polygon(points.toVector, midPoint) +\end{Code} \QUESTEND From 2ed62a77d0d79a0586c9c5ef094ae84e190fa9da Mon Sep 17 00:00:00 2001 From: EliasAAradsson Date: Tue, 29 Oct 2024 17:15:27 +0100 Subject: [PATCH 2/2] Implement idea about exercise 5.8 --- compendium/modules/w05-classes-exercise.tex | 79 ++++++++++++++------- 1 file changed, 53 insertions(+), 26 deletions(-) diff --git a/compendium/modules/w05-classes-exercise.tex b/compendium/modules/w05-classes-exercise.tex index 1c148748..877aff79 100644 --- a/compendium/modules/w05-classes-exercise.tex +++ b/compendium/modules/w05-classes-exercise.tex @@ -492,15 +492,15 @@ -\WHAT{Skapa en punktklass som kan hantera polära koordinater.} +\WHAT{Skapa en punktklass som kan hantera polära koordinater och en klass som representerar en polygon m.h.a. dessa punkter.} \QUESTBEGIN \Task \what~ -Du ska skapa en oföränderlig case-klass \code{Point} som kan representera en koordinat både med ''vanliga'' kartesiska koordinater\footnote{\url{https://sv.wikipedia.org/wiki/Kartesiskt_koordinatsystem}} och med polära koordinater% -\footnote{\url{https://sv.wikipedia.org/wiki/Pol\%C3\%A4ra\_koordinater}}. +Du ska skapa en oföränderlig case-klass \code{Point} som kan representera en koordinat både med ''vanliga'' kartesiska koordinater\footnote{\url{https://sv.wikipedia.org/wiki/Kartesiskt_koordinatsystem}} och med polära koordinater +\footnote{\url{https://sv.wikipedia.org/wiki/Pol\%C3\%A4ra\_koordinater}}. Sedan ska du använda denna klass för att skapa regelbundna polygoner med en oföränderlig case-klass \code{Polygon}. -\Subtask Skapa kod med hjälp av en editor, t.ex. VS \code{code}, i filen \code{Point.scala} enligt följande riktlinjer: +\Subtask Skapa kod med hjälp av en editor, t.ex. VS \code{code}, i filen \code{Point.scala} enligt följande riktlinjer: \begin{enumerate}[noitemsep] \item \code{Point} ska ligga i paketet \code{graphics}. @@ -512,9 +512,8 @@ \item \code{Point} ska ha följande publika medlemmar (två oföränderliga attribut och en metod): \begin{itemize}[nolistsep, noitemsep] -\item \code{lazy val r: Double} ska ge motsvarande polära koordinatens - avstånd till origo. -\item \code{lazy val theta: Double} ska ge polära koordinatens vinkel i radianer. +\item \code{val r: Double} ska ge motsvarande polära koordinatens avstånd till origo. +\item \code{val theta: Double} ska ge polära koordinatens vinkel i radianer. \item \code{def +(p: Point): Point} ska ge en ny punkt vars koordinat är summan av x- respektive y-koordinaterna för denna instans och punkten \code{p}. \end{itemize} @@ -561,29 +560,41 @@ \item Notera att klassens attribut är av typen \code{Double} och inte \code{Int}, trots att vi senare ska använda punkten för att beskriva en diskret pixelposition i ett \code{PixelWindow}. Anledningen till detta är att det kan uppstå avrundningsfel vid numeriska beräkningar. Detta blir särskilt märkbart vid upprepad räkning med små värden, t.ex. när man ritar en approximerad cirkel med många linjesegment. \end{itemize} -\Subtask Studera nu följande singelobjekt \code{PolygonWindow} som med hjälp av ett \code{PixelWindow} ger möjlighet att rita ut polygoner: +\Subtask Klassen \code{PolygonWindow} nedan innehåller ett \code{PixelWindow} och ger möjlighet att rita ut polygoner. +Kopiera koden för \code{PolygonWindow} till en ny kodfil \code{PolygonWindow.scala} i samma katalog som du placerade \code{Point} ovan i. + \begin{Code} -object PolygonWindow: +//> using dep "se.lth.cs::introprog:1.4.0" + +package graphics + +class PolygonWindow: import introprog.PixelWindow import java.awt.Color - val black = Color(0,0,0) - val coolGreen = Color(0,255,100) - val width = 500 - val height = 500 + val black = Color(0,0,0) + val coolGreen = Color(0,255,111) + val width = 500 + val height = 500 + val window = PixelWindow(width, height, title = "Polygons", background = black, foreground = coolGreen) - private def convertPoint(point: Point): Vector[Int] = + private def convertToVector(point: Point): Vector[Int] = Vector(point.x.toInt, point.y.toInt) def draw(polygon: Polygon): Unit = for i <- 0 until polygon.nbrOfCorners do - val from = convertPoint(polygon.pointsForDrawing(i)).map(_ + width/2) - val to = convertPoint(polygon.pointsForDrawing((i+1) % polygon.nbrOfCorners)).map(_ + width/2) + val from = convertToVector(polygon.points(i)).map(_ + width/2) + val to = convertToVector(polygon.points((i+1) % polygon.nbrOfCorners)).map(_ + width/2) window.line(from(0), from(1), to(0), to(1), lineWidth = 2) \end{Code} -Skapa case-klassen \code{Polygon(points: Vector[Point], midPoint: Point)} med attributen \code{lazy val nbrOfCorners: Int} och \code{lazy val pointsForDrawing: Vector[Point]} så att ovanstående kod går att köra. -\code{Polygon} ska ha ett kompanjonsobjekt med en metod \code{makePolygon(nbrOfCorners: Int, radius: Double, midPoint: Point): Polygon} för att skapa polygoner. + +Skapa nu en ny kodfil innehållandes en case-klass \code{Polygon(points: Vector[Point])} med attributet \code{val nbrOfCorners: Int}. \code{Polygon} ska också ligga i paketet \code{graphics}. +Likt klassen \code{Point} ovan ska också \code{Polygon} ha ett kompanjonsobjekt. Kompanjonsobjektet ska ha en metod \code{regularPolygon(nbrOfCorners: Int, radius: Double, midPoint: Point): Polygon} för att skapa regelbundna polygoner. +Fundera över hur case-klassen \code{Polygon} och dess kompanjonsobjekt ska se ut för att koden ovan i \code{PolygonWindow} ska fungera som tänkt. Testa i Scala REPL! + +\Subtask Kan man använda metoden \code{regularPolygon} för att rita cirklar? Kan man använda \code{Polygon} för att representera oregelbundna polygoner? +Testa i Scala REPL! \SOLUTION @@ -593,8 +604,8 @@ package graphics case class Point(x: Double, y: Double): - lazy val r: Double = math.hypot(x, y) - lazy val theta: Double = math.atan2(y, x) + val r: Double = math.hypot(x, y) + val theta: Double = math.atan2(y, x) def +(p: Point): Point = Point(x + p.x, y + p.y) object Point: @@ -603,20 +614,36 @@ \end{Code} \Subtask \begin{Code} -case class Polygon(points: Vector[Point], midPoint: Point): - lazy val nbrOfCorners: Int = points.length - lazy val pointsForDrawing: Vector[Point] = points.map(_ + midPoint) +package graphics + +case class Polygon(points: Vector[Point]): + val nbrOfCorners: Int = points.length object Polygon: - def makePolygon(nbrOfCorners: Int, radius: Double, midPoint: Point): Polygon = + def regularPolygon(nbrOfCorners: Int, radius: Double, midPoint: Point): Polygon = val points = new Array[Point](nbrOfCorners) for i <- 0 until nbrOfCorners do val theta = i * (2 * math.Pi) / nbrOfCorners - points(i) = Point.polar(radius, theta) + points(i) = Point.polar(radius, theta) + midPoint end for - Polygon(points.toVector, midPoint) + Polygon(points.toVector) \end{Code} +\Subtask +En perfekt cirkel går inte att skapa, men det går att komma tillräckligt nära för att göra det omöjligt att se hörnen. +Testa till exempel med 50 hörn likt nedan. +\begin{REPL} +scala> import graphics.* +scala> val circle = Polygon.regularPolygon(50,70,Point(-25,-25)) +scala> val window = PolygonWindow() +scala> window.draw(circle) +\end{REPL} + +Även en oregelbunden polygon går att skapa. Använd då konstruktorn till \code{Polygon} direkt. Till exempel likt nedan. +\begin{REPL} +scala> val irregular = Polygon(Vector(Point(34,83), Point(16,42), Point(77,77), Point(100,138))) +scala> window.draw(irregular) +\end{REPL} \QUESTEND