• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

htwg-codebreaker-org / codebreaker / 14938345043

09 May 2025 09:52PM UTC coverage: 92.361%. First build
14938345043

Pull #2

github

web-flow
Merge a9aa27378 into a73d205da
Pull Request #2: testing new repo token in coverals

133 of 144 relevant lines covered (92.36%)

0.92 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

100.0
/src/main/scala/de/htwg/codebreaker/model/ServerGenerator.scala
1
package de.htwg.codebreaker.model
2

3
import scala.util.Random
4

5
/** 
6
 * ServerGenerator kümmert sich um die Erzeugung von Servern auf der Karte:
7
 * - Fixed Servers: Aus einer vordefinierten Liste von Blueprints entstehen immer die gleichen Kern-Server (z.B. "Pentagon").
8
 * - Side Servers: Zufällig verteilte Neben-Server pro Kontinent, mit Mindestabstand zueinander und zu den Fixed Servers.
9
 */
10
object ServerGenerator:
11

12
  // Zufallszahlengenerator für alle zufälligen Aspekte
13
  private val rng = new Random()
1✔
14

15
  /**
16
   * Manhattan‐Abstand zwischen zwei Tiles:
17
   * distance((x1,y1), (x2,y2)) = |x1-x2| + |y1-y2|
18
   * Wird genutzt, um Mindestabstände zwischen Servern sicherzustellen.
19
   */
20
  private[model] def distance(a: Tile, b: Tile): Int =
1✔
21
    math.abs(a.x - b.x) + math.abs(a.y - b.y)
1✔
22

23
  /**
24
   * Wählt bis zu `count` verschiedene Tiles aus `tiles` aus,
25
   * so dass jeder neu hinzugefügte Tile mindestens `minDistance`
26
   * (Manhattan‐Abstand) zu allen bereits gewählten und zusätzlich
27
   * zu den in `avoidTiles` übergebenen Tiles steht.
28
   *
29
   * Ablauf:
30
   * 1. `tiles` wird zufällig durchmischt.
31
   * 2. Jeder Tile aus dem Shuffle wird nacheinander geprüft:
32
   *    - Haben wir bereits `count` Tiles? Dann Abbruch.
33
   *    - Steht `next` mindestens `minDistance` von allen bisher
34
   *      gewählten (`picked`) und `avoidTiles` entfernt? Dann hinzufügen.
35
   *    - Andernfalls überspringen.
36
   */
37
  private[model] def pickNonCloseTiles(
1✔
38
    tiles: Vector[Tile],
39
    count: Int,
40
    minDistance: Int,
41
    avoidTiles: List[Tile] = Nil
1✔
42
  ): Vector[Tile] = {
43
    val shuffled = rng.shuffle(tiles)
1✔
44
    shuffled.foldLeft(Vector.empty[Tile]) { (picked, next) =>
1✔
45
      if picked.size >= count then
1✔
46
        picked
1✔
47
      else if avoidTiles.contains(next) then
1✔
48
        // überspringe alle Tiles, die in der Avoid‑Liste sind
49
        picked
1✔
50
      else if (picked ++ avoidTiles).forall(t => distance(t, next) >= minDistance) then
1✔
51
        picked :+ next
1✔
52
      else
53
        picked
1✔
54
    }
55
  }
56

57

58
  /**
59
   * Generiert pro Kontinent eine zufällige Anzahl (3–6) von Neben‐Servern.
60
   *
61
   * @param continent      Kontinent, auf dem die Neben‐Server entstehen.
62
   * @param map            die Weltkarte, um alle Tiles dieses Kontinents abzufragen.
63
   * @param existingServers bereits platzierte Server (werden als `avoidTiles` verwendet,
64
   *                        damit keine Side‐Server drauf stehen).
65
   * @param minDistance    minimale Manhattan‐Distanz zwischen allen Side‐Servern
66
   *                       und zu den `existingServers` (Standard: 2).
67
   * @return               Liste neu erzeugter Server mit dynamischem Namen,
68
   *                       Zufalls‐Difficulty und -Rewards.
69
   */
70
  def generateSideServersFor(
1✔
71
    continent: Continent,
72
    map: WorldMap,
73
    existingServers: List[Server],
74
    minDistance: Int = 2
1✔
75
  ): List[Server] =
76

77
    // 1) Basisdaten
78
    val tiles      = map.tilesOf(continent)
1✔
79
    val fixedTiles = existingServers.map(_.tile)
1✔
80
    // count ist zufällig zwischen 3 und 6
81
    val count      = 3 + rng.nextInt(4)
1✔
82

83
    // 2) Versuche erst mit Mindestabstand
84
    val pickedStrict = pickNonCloseTiles(tiles, count, minDistance, fixedTiles)
1✔
85

86
    // 3) Fallback falls zu wenige: nur FixedTiles meiden, aber Abstand ignorieren
87
    val picked = 
88
      if pickedStrict.size >= count then pickedStrict 
1✔
89
      else {
1✔
90
        val freeTiles = tiles.filterNot(fixedTiles.contains)
1✔
91
        // Falls selbst hier freeTiles.size < count, dann werden so viele wie möglich genommen
92
        rng.shuffle(freeTiles).take(count).toVector
1✔
93
      }
94

95
    // 4) Aus den ausgewählten Tiles endgültige Server basteln
96
    picked.zipWithIndex.map { case (tile, idx) =>
1✔
97
      val difficulty = 20 + rng.nextInt(31)
1✔
98
      val rewardCpu  = 10 + rng.nextInt(11)
1✔
99
      val rewardRam  = 10 + rng.nextInt(11)
1✔
100
      val name       = s"Nebenserver-${continent.short}-$idx"
1✔
101

102
      Server(
1✔
103
        name        = name,
104
        tile        = tile,
105
        difficulty  = difficulty,
106
        rewardCpu   = rewardCpu,
107
        rewardRam   = rewardRam,
108
        hacked      = false,
109
        serverType  = ServerType.Side
110
      )
111
    }.toList
1✔
112

113

114
  /**
115
   * Erzeugt alle Fixed‐Server gemäß den `fixedBlueprints`.
116
   * Jeder Blueprint gibt einen Namen, eine feste Position und Werte‐Spannen vor.
117
   * Die tatsächlichen Difficulty‐ und Reward‐Werte werden per `rngIn` gezogen.
118
   */
119
  def generateFixedServers(map: WorldMap): List[Server] =
1✔
120
    fixedBlueprints.map { bp =>
1✔
121
      // Blueprint‐Position muss existieren, sonst gibt .get eine Exception
122
      val tile = map.tileAt(bp.preferredPosition._1, bp.preferredPosition._2).get
1✔
123

124
      Server(
1✔
125
        name        = bp.name,
126
        tile        = tile,
127
        difficulty  = rngIn(bp.difficultyRange),
1✔
128
        rewardCpu   = rngIn(bp.rewardCpuRange),
1✔
129
        rewardRam   = rngIn(bp.rewardRamRange),
1✔
130
        hacked      = false,
131
        serverType  = bp.serverType
132
      )
133
    }
134

135
  /**
136
   * Ziehe eine Zufallszahl im gegebenen inklusiven Bereich (min, max).
137
   * Wenn min == max, gib direkt den einzigen Wert zurück.
138
   */
139
  private[model] def rngIn(range: (Int, Int)): Int =
1✔
140
    val (min, max) = range
1✔
141
    if min == max then min
1✔
142
    else rng.nextInt(max - min + 1) + min
1✔
143

144
  // --------------------------------------------
145
  // Vordefinierte Blueprints für Fixed‐Server
146
  // --------------------------------------------
147
  // Die Koordinaten basieren auf der Continent‐Einteilung in WorldMap.classifyContinent.
148
  val fixedBlueprints: List[ServerBlueprint] = List(
1✔
149
    ServerBlueprint("Pentagon",         (2,  2), ServerType.Military, (85, 90),  (30, 35), (50, 60)),
150
    ServerBlueprint("GKS",              (10,  5), ServerType.GKS,      (90,100),  (0,   0), (0,   0)),
151
    ServerBlueprint("Silicon Valley",   (1,   3), ServerType.Cloud,    (60, 70),  (15,  20), (30,  35)),
152
    ServerBlueprint("Wall Street",      (3,   2), ServerType.Bank,     (60, 75),  (20,  25), (25,  30)),
153
    ServerBlueprint("Brussels",         (7,   2), ServerType.Bank,     (70, 80),  (30,  40), (15,  25)),
154
    ServerBlueprint("Frankfurt Hub",    (6,   2), ServerType.Bank,     (60, 75),  (25,  30), (15,  20)),
155
    ServerBlueprint("Tokyo Grid",       (15,  2), ServerType.Cloud,    (60, 70),  (20,  25), (15,  25)),
156
    ServerBlueprint("Cairo",            (8,   5), ServerType.Firm,     (40, 60),  (20,  30), (10,  20)),
157
    ServerBlueprint("Sydney Core",      (18,  6), ServerType.Cloud,    (50, 65),  (20,  25), (20,  30)),
158
    ServerBlueprint("Moscow",           (10,  2), ServerType.Military, (60, 70),  (25,  30), (15,  20)),
159
    ServerBlueprint("Beijing",          (13,  3), ServerType.Military, (65, 75),  (30,  35), (20,  25))
160
  )
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc