This repository has been archived by the owner on Sep 28, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
*create the ground *create the missile *check collisions *restart the game Co-authored-by: Matteo Lazzari <[email protected]> Co-authored-by: Daniele Di Lillo <[email protected]>
- Loading branch information
1 parent
e450e4e
commit ed7bdb2
Showing
100 changed files
with
4,762 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,7 @@ | ||
# PPS-22-mis-com | ||
# PPS-22-mis-com | ||
# Missile Command | ||
- - - | ||
## pps-2022-missile-command | ||
authors: Andrea Brighi, Daniele Di Lillo, Matteo Lazzari | ||
## Introduction | ||
This project is a reimplementation of the classic game Missile Command, developed Scala. | ||
The project is developed using a functional programming paradigm and a TDD approach. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,24 @@ | ||
ThisBuild / version := "0.1.0-SNAPSHOT" | ||
ThisBuild / version := "1.0.0" | ||
|
||
ThisBuild / scalaVersion := "3.2.0" | ||
|
||
val scalatest = "org.scalatest" %% "scalatest" % "3.2.12" % Test | ||
val scalaCheck = "org.scalacheck" %% "scalacheck" % "1.17.0" % "test" | ||
val scalactic = "org.scalactic" %% "scalactic" % "3.2.14" | ||
|
||
ThisBuild / scalaVersion := "3.1.3" | ||
|
||
lazy val root = (project in file(".")) | ||
.settings( | ||
name := "missile command" | ||
name := "missile command", | ||
libraryDependencies ++= Seq(scalatest, scalaCheck, scalactic) | ||
++ | ||
Seq( | ||
"io.monix" %% "monix" % "3.4.1", | ||
"dev.optics" %% "monocle-core" % "3.1.0", | ||
"dev.optics" %% "monocle-macro" % "3.1.0" | ||
), | ||
assembly / mainClass := Some("view.startGame"), | ||
assembly / assemblyJarName := "missile_command.jar", | ||
) | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.0.3") | ||
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "2.0.0") |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
package controller | ||
|
||
import model.elements2d.Point2D | ||
|
||
/** | ||
* Enum of the possible events in the game. | ||
*/ | ||
enum Event: | ||
/** | ||
* Class that represents the event of passing time. | ||
* | ||
* @param time the time passed | ||
*/ | ||
case TimePassed(deltaTime: Double) | ||
|
||
/** | ||
* Class that represents the event of launching a friendly missile. | ||
* | ||
* @param position the ending position of the missile | ||
*/ | ||
case LaunchMissileTo(position: Point2D) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
package controller | ||
|
||
import monix.eval.Task | ||
import monix.execution.Ack | ||
import monix.execution.Scheduler.Implicits.global | ||
import monix.reactive.subjects.PublishSubject | ||
import monix.reactive.{Observable, Observer, OverflowStrategy} | ||
import org.reactivestreams.Subscriber | ||
import view.gui.UI | ||
import model.World | ||
import controller.update._ | ||
|
||
import scala.concurrent.Future | ||
import scala.concurrent.duration.DurationInt | ||
import scala.language.postfixOps | ||
import scala.util.Random | ||
|
||
/** | ||
* Object that represents the controller of the game. | ||
*/ | ||
object GameLoop: | ||
/** | ||
* The observable that will be used to schedule time in the game. | ||
*/ | ||
private val time: Observable[Event] = TimeFlow | ||
.tickEach(50 milliseconds) | ||
.map(_.toDouble/1000) | ||
.map(Event.TimePassed.apply) | ||
|
||
/** | ||
* start the game loop. | ||
* | ||
* @param ui the ui that will be used to show the game. | ||
* @return a task that will execute the game. | ||
*/ | ||
def start(ui: UI): Task[Unit] = | ||
|
||
given OverflowStrategy[Event] = OverflowStrategy.Default | ||
|
||
val world = World.initialWorld | ||
val controls: Update = Update.combine( | ||
UpdateTime(), | ||
UpdatePosition(), | ||
ActivateSpecialAbility(), | ||
CollisionsDetection(), | ||
LaunchNewMissile() | ||
) | ||
|
||
val init = Task((world, controls)) | ||
val events = | ||
Observable(time, ui.events.throttleLast(50 milliseconds)).merge | ||
events | ||
.scanEval(init) { case ((world, controls), event) => controls(event, world) } | ||
.doOnNext { case (world, _) => ui.render(world) } | ||
.takeWhile { case (world, _) => world.ground.stillAlive } | ||
.completedL | ||
.doOnFinish(_ => ui.gameOver) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package controller | ||
|
||
import monix.reactive.Observable | ||
import scala.concurrent.duration.FiniteDuration | ||
|
||
/** | ||
* A object that con be used to create a [[monix.reactive.Observable]] that emits a value every time a given time interval has passed. | ||
*/ | ||
object TimeFlow: | ||
/** | ||
* Creates a [[monix.reactive.Observable]] that emits a value every time a given time interval has passed. | ||
* | ||
* @param duration the time interval between two consecutive values | ||
* @return an [[monix.reactive.Observable]] that emits a value every time a given time interval has passed | ||
*/ | ||
def tickEach(duration: FiniteDuration): Observable[Long] = | ||
Observable | ||
.fromIterable(LazyList.continually(duration)) | ||
.delayOnNext(duration) | ||
.map(_.toMillis) |
37 changes: 37 additions & 0 deletions
37
src/main/scala/controller/update/ActivateSpecialAbility.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
package controller.update | ||
|
||
import controller.Event | ||
import controller.Event.TimePassed | ||
import controller.update.Update.on | ||
import model.World | ||
import model.explosion.Explosion | ||
import model.behavior.Moveable | ||
import model.collisions.Collisionable | ||
import model.missile.Missile | ||
import monix.eval.Task | ||
|
||
/** | ||
* Object that return an update function for the world to be update with the special abilities of its components | ||
*/ | ||
object ActivateSpecialAbility: | ||
/** | ||
* Apply function used to update the world to be update with the special abilities of its components | ||
* | ||
* @return An Update that update the world to be update with the special abilities of its components | ||
*/ | ||
def apply(): Update = on[TimePassed] { (_: Event, world: World) => | ||
Task { | ||
def activateSpecialAbility(collisionable: Collisionable): Collisionable = collisionable match | ||
case missile: Missile if missile.destinationReached => missile.explode | ||
case _ => collisionable | ||
|
||
def isTerminated(collisionable: Collisionable): Boolean = collisionable match | ||
case explosion: Explosion => explosion.terminated | ||
case _ => false | ||
|
||
val collisionables = world.collisionables.map(activateSpecialAbility) | ||
val remainedCollisionables = collisionables.filterNot(isTerminated) | ||
val (newMissiles, spawner) = world.spawner.spawn() | ||
world.copy(collisionables = remainedCollisionables ++ newMissiles, spawner = spawner) | ||
} | ||
} |
49 changes: 49 additions & 0 deletions
49
src/main/scala/controller/update/CollisionsDetection.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
package controller.update | ||
|
||
import controller.Event | ||
import controller.Event.TimePassed | ||
import controller.update.Update.on | ||
import model.{World, calculateNewScore} | ||
import model.collisions.{Collisionable, Damageable, applyDamage, calculateCollisions} | ||
import model.ground.{City, Ground, MissileBattery} | ||
import model.missile.Missile | ||
import monix.eval.Task | ||
|
||
/** | ||
* Object that return an update function for the world to be update with the aftermath of its components collisions | ||
*/ | ||
object CollisionsDetection: | ||
/** | ||
* Apply function used to update the world to be update with the aftermath of its components collisions | ||
* | ||
* @return An Update that update the world to be update with the aftermath of its components collisions | ||
*/ | ||
def apply(): Update = on[TimePassed] { (_: Event, world: World) => | ||
Task { | ||
def createNewGround(collisionables: List[Collisionable]): (Ground, List[Collisionable]) = | ||
val (collisions, groundElements) = collisionables.partition(_ match | ||
case _: MissileBattery => false | ||
case _: City => false | ||
case _ => true | ||
) | ||
val (missileBatteries, cities) = groundElements.partition(_ match | ||
case _: MissileBattery => true | ||
case _: City => false | ||
) | ||
(Ground( | ||
cities.map(_.asInstanceOf[City]), | ||
missileBatteries.map(_.asInstanceOf[MissileBattery]) | ||
), collisions) | ||
|
||
def isDestroyed(collisionable: Collisionable): Boolean = collisionable match | ||
case damageable: Damageable => damageable.isDestroyed | ||
case _ => false | ||
|
||
val collisionables = world.collisionables ++ world.ground.cities ++ world.ground.turrets | ||
val collisionableAfterCollisions = applyDamage(calculateCollisions(collisionables)) | ||
val newScore = calculateNewScore(collisionableAfterCollisions, world.score) | ||
val (newGround, newCollisionables) = createNewGround(collisionableAfterCollisions.keys.toList) | ||
val newNotDestroyedCollisionables = newCollisionables.filterNot(isDestroyed) | ||
world.copy(collisionables = newNotDestroyedCollisionables, score = newScore, ground = newGround) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
package controller.update | ||
|
||
import controller.Event | ||
import controller.Event.LaunchMissileTo | ||
import controller.update.Update.on | ||
import model.World | ||
import model.behavior.Moveable | ||
import model.collisions.Collisionable | ||
import monix.eval.Task | ||
|
||
/** | ||
* Object that return an update function for the world to be updated when the user launch a missile. | ||
*/ | ||
object LaunchNewMissile: | ||
|
||
/** | ||
* Apply function used to update the world to be updated when the user launch a missile. | ||
* | ||
* @return an update function for the world to be updated when the user launch a missile. | ||
*/ | ||
def apply(): Update = on[LaunchMissileTo] { (event: LaunchMissileTo, world: World) => | ||
Task { | ||
val (ground, missile) = world.ground.shootMissile(event.position) | ||
missile match | ||
case Some(missile) => world.copy(collisionables = world.collisionables :+ missile, ground = ground) | ||
case None => world.copy(collisionables = world.collisionables, ground = ground) | ||
} | ||
} |
Oops, something went wrong.