inital commit
No files matched your search
@@ -1,92 +1,212 @@
|
||||
# Projet POO
|
||||
<link rel="stylesheet" href="readme.css">
|
||||
|
||||
|
||||
# Projet de POO
|
||||
|
||||
## Getting started
|
||||
Réalisation d'un jeu vidéo 2D : **UBomb**.
|
||||
|
||||
To make it easy for you to get started with GitLab, here's a list of recommended next steps.
|
||||
|
||||
Already a pro? Just edit this README.md and make it your own. Want to make it easy? [Use the template at the bottom](#editing-this-readme)!
|
||||
## INFO du 08/11/2022
|
||||
|
||||
## Add your files
|
||||
Il y a un bug sur les machines Linux du CREMI exclusivement qui empêche la taille de la fenêtre de s'ajuster automatiquement à la taille de la carte. Pour y remédier dans l'immédiat, remplacer `stage.setResizable(false);` par `stage.setResizable(true);` dans la méthode `initialize` de la classe `GameEngine`.
|
||||
|
||||
- [ ] [Create](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#create-a-file) or [upload](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#upload-a-file) files
|
||||
- [ ] [Add files using the command line](https://docs.gitlab.com/ee/gitlab-basics/add-file.html#add-a-file-using-the-command-line) or push an existing Git repository with the following command:
|
||||
## Principes du jeu
|
||||
|
||||
```
|
||||
cd existing_repo
|
||||
git remote add origin https://gitlab.emi.u-bordeaux.fr/thoguet/projet-poo.git
|
||||
git branch -M main
|
||||
git push -uf origin main
|
||||
Une princesse est détenue prisonnière par de méchants monstres verts. Votre mission, si vous l'acceptez, est d'aller la délivrer. Pour cela, vous devrez traverser plusieurs mondes, plus effrayants les uns que les autres. Des portes vous permettront de passer de monde en monde. Certaines portes seront fermées à clé et nécessiteront d'avoir une clé dans votre inventaire. Vous êtes un expert en explosif et utiliserez vos bombes pour détruire les obstacles devant vous et tuer les monstres qui vous attaqueront.
|
||||
|
||||
|
||||
## Représentation du jeu
|
||||
|
||||
Chaque monde est représenté par une carte (rectangulaire) composée de cellules. Chaque cellule peut contenir :
|
||||
|
||||
- le joueur ;
|
||||
- la princesse ;
|
||||
- des monstres ;
|
||||
- des éléments de décor (arbres, pierres...) infranchissables et
|
||||
indestructibles ;
|
||||
- des caisses destructibles et déplaçables ;
|
||||
- des portes, ouvertes ou fermées, permettant d’évoluer entre les
|
||||
mondes ;
|
||||
- des clés pour débloquer les portes fermées ;
|
||||
- des bonus ou des malus qu'il est possible de ramasser.
|
||||
|
||||

|
||||
|
||||
## Prise en main
|
||||
|
||||
Nous vous fournissons une première ébauche du jeu, utilisant la bibliothèque JavaFX. Le lancement du jeu
|
||||
fait apparaître une carte minimaliste, chargée statiquement en mémoire, dans laquelle le joueur peut se déplacer dans toutes les directions, quelle que soit la nature des cellules. Le code utilise `gradle` comme moteur de production. Il suffit de lancer la commande suivante pour compiler et exécuter le jeu. Toutes les dépendances seront automatiquement téléchargées et installées. Le jeu nécessite une version de Java au moins égale à 14. La version 17 est recommandée car il s'agit de la dernière version LTS (*Long Term Support*). Pour utiliser Java 17 sur les machines du CREMI, il suffit d'exécuter la commande `source /opt/local/bin/java17.env` dans votre fichier de configuration.
|
||||
|
||||
$ ./gradlew run
|
||||
|
||||
|
||||
Le jeu utilise quelques fonctionnalités nouvelles de Java qui n'ont pas été vues en cours ou en TD :
|
||||
|
||||
### Switch Expression (Java 13)
|
||||
|
||||
Les expressions `Switch` peuvent désormais retourner une valeur et vous pouvez utiliser une syntaxe de style lambda.
|
||||
|
||||
```java
|
||||
boolean result = switch (status) {
|
||||
case SUBSCRIBER -> true;
|
||||
case FREE_TRIAL -> false;
|
||||
default -> throw new IllegalArgumentException("something is murky!");
|
||||
};
|
||||
```
|
||||
|
||||
## Integrate with your tools
|
||||
### Records (Java 14)
|
||||
|
||||
- [ ] [Set up project integrations](https://gitlab.emi.u-bordeaux.fr/thoguet/projet-poo/-/settings/integrations)
|
||||
Il existe désormais des classes d'enregistrement (`record`) qui permettent de ne pas devoir écrire beaucoup de code pour manipuler des champs *immutables* (déclarés avec `final`). Ainsi le code suivant :
|
||||
|
||||
## Collaborate with your team
|
||||
```java
|
||||
final class Point {
|
||||
private final int x;
|
||||
private final int y;
|
||||
|
||||
- [ ] [Invite team members and collaborators](https://docs.gitlab.com/ee/user/project/members/)
|
||||
- [ ] [Create a new merge request](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html)
|
||||
- [ ] [Automatically close issues from merge requests](https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#closing-issues-automatically)
|
||||
- [ ] [Enable merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/approvals/)
|
||||
- [ ] [Automatically merge when pipeline succeeds](https://docs.gitlab.com/ee/user/project/merge_requests/merge_when_pipeline_succeeds.html)
|
||||
public Point(int x, int y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
## Test and Deploy
|
||||
public int x() { return x; }
|
||||
public int y() { return y; }
|
||||
|
||||
Use the built-in continuous integration in GitLab.
|
||||
// state-based implementations of equals, hashCode, toString
|
||||
// nothing else
|
||||
}
|
||||
```
|
||||
s'écrit simplement par :
|
||||
```java
|
||||
record Point(int x, int y) { }
|
||||
```
|
||||
|
||||
- [ ] [Get started with GitLab CI/CD](https://docs.gitlab.com/ee/ci/quick_start/index.html)
|
||||
- [ ] [Analyze your code for known vulnerabilities with Static Application Security Testing(SAST)](https://docs.gitlab.com/ee/user/application_security/sast/)
|
||||
- [ ] [Deploy to Kubernetes, Amazon EC2, or Amazon ECS using Auto Deploy](https://docs.gitlab.com/ee/topics/autodevops/requirements.html)
|
||||
- [ ] [Use pull-based deployments for improved Kubernetes management](https://docs.gitlab.com/ee/user/clusters/agent/)
|
||||
- [ ] [Set up protected environments](https://docs.gitlab.com/ee/ci/environments/protected_environments.html)
|
||||
|
||||
***
|
||||
### Pattern Matching pour InstanceOf (Java 14)
|
||||
|
||||
# Editing this README
|
||||
Alors que précédemment vous deviez écrire :
|
||||
|
||||
When you're ready to make this README your own, just edit this file and use the handy template below (or feel free to structure it however you want - this is just a starting point!). Thank you to [makeareadme.com](https://www.makeareadme.com/) for this template.
|
||||
```java
|
||||
if (obj instanceof String) {
|
||||
String s = (String) obj;
|
||||
// use s
|
||||
}
|
||||
```
|
||||
|
||||
## Suggestions for a good README
|
||||
Every project is different, so consider which of these sections apply to yours. The sections used in the template are suggestions for most open source projects. Also keep in mind that while a README can be too long and detailed, too long is better than too short. If you think your README is too long, consider utilizing another form of documentation rather than cutting out information.
|
||||
vous pouvez maintenant écrire directement
|
||||
```java
|
||||
if (obj instanceof String s) {
|
||||
System.out.println(s.contains("hello"));
|
||||
}
|
||||
```
|
||||
|
||||
## Name
|
||||
Choose a self-explaining name for your project.
|
||||
Travail à fournir
|
||||
=================
|
||||
|
||||
## Description
|
||||
Let people know what your project can do specifically. Provide context and add a link to any reference visitors might be unfamiliar with. A list of Features or a Background subsection can also be added here. If there are alternatives to your project, this is a good place to list differentiating factors.
|
||||
|
||||
## Badges
|
||||
On some READMEs, you may see small images that convey metadata, such as whether or not all the tests are passing for the project. You can use Shields to add some to your README. Many services also have instructions for adding a badge.
|
||||
|
||||
## Visuals
|
||||
Depending on what you are making, it can be a good idea to include screenshots or even a video (you'll frequently see GIFs rather than actual videos). Tools like ttygif can help, but check out Asciinema for a more sophisticated method.
|
||||
## Premiers pas
|
||||
|
||||
## Installation
|
||||
Within a particular ecosystem, there may be a common way of installing things, such as using Yarn, NuGet, or Homebrew. However, consider the possibility that whoever is reading your README is a novice and would like more guidance. Listing specific steps helps remove ambiguity and gets people to using your project as quickly as possible. If it only runs in a specific context like a particular programming language version or operating system or has dependencies that have to be installed manually, also add a Requirements subsection.
|
||||
Ajouter l'affichage de tous les éléments (caisses, bonus, clés...) ainsi que les monstres et la princesse. Modifier le code pour que les mouvements du joueur soient limités par le cadre de la carte, les éléments de décor et les caisses.
|
||||
Le joueur peut marcher sur une case où se trouve un bonus, une clé, ou un autre personnage.
|
||||
S’il atteint la princesse, la partie se termine par une victoire.
|
||||
Si ses points de vie tombent à 0, la partie se termine par une défaite.
|
||||
|
||||
## Usage
|
||||
Use examples liberally, and show the expected output if you can. It's helpful to have inline the smallest example of usage that you can demonstrate, while providing links to more sophisticated examples if they are too long to reasonably include in the README.
|
||||
## Ajoutons des monstres
|
||||
Ajouter des monstres. Pour le moment, les monstres ne savent pas marcher, ils ne bougent pas. Faire en sorte que le joueur perde une vie lorsqu'il se trouve sur la même case qu'un monstre.
|
||||
|
||||
## Support
|
||||
Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc.
|
||||
## Gestion du panneau d’informations
|
||||
|
||||
## Roadmap
|
||||
If you have ideas for releases in the future, it is a good idea to list them in the README.
|
||||
Le panneau d’information doit afficher le nombre de vies, le nombre de bombes et leur portée, le nombre de clés dans l’inventaire et le numéro de niveau courant.
|
||||
|
||||
## Contributing
|
||||
State if you are open to contributions and what your requirements are for accepting them.
|
||||
## Gestion des mondes
|
||||
|
||||
For people who want to make changes to your project, it's helpful to have some documentation on how to get started. Perhaps there is a script that they should run or some environment variables that they need to set. Make these steps explicit. These instructions could also be useful to your future self.
|
||||
Dans la version de base, le jeu ne dispose que d'un seul niveau codé en dur dans le code. Nous allons maintenant charger une configuration complète de jeu depuis un fichier. Vous trouverez un répertoire `world` à la racine du projet avec un fichier `sample.properties` représentant un monde avec 3 niveaux. Les fichiers [properties](https://docs.oracle.com/javase/tutorial/essential/environment/properties.html) en Java permettent de facilement stocker des couples de clés/valeurs.
|
||||
|
||||
You can also document commands to lint the code or run tests. These steps help to ensure high code quality and reduce the likelihood that the changes inadvertently break something. Having instructions for running tests is especially helpful if it requires external setup, such as starting a Selenium server for testing in a browser.
|
||||
Voici un exemple de code pour lire la valeur correspondant à la clé `compression` dans le fichier `sample.properties`.
|
||||
|
||||
## Authors and acknowledgment
|
||||
Show your appreciation to those who have contributed to the project.
|
||||
```java
|
||||
Properties config = new Properties();;
|
||||
Reader in = new FileReader(file);
|
||||
config.load(in);
|
||||
boolean compression = Boolean.parseBoolean(integerProperty(config, "compression", "false"));
|
||||
```
|
||||
|
||||
## License
|
||||
For open source projects, say how it is licensed.
|
||||
Comme vous pouvez le constater, certaines clés peuvent avoir des valeurs par défaut si elles ne sont pas présentes dans le fichier. Nous utiliserons les valeurs par défaut suivantes :
|
||||
|
||||
## Project status
|
||||
If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers.
|
||||
Clé | Valeur par défaut
|
||||
--- | --- |
|
||||
levels | 1 |
|
||||
compression | false |
|
||||
bombBagCapacity | 3 |
|
||||
playerLives | 5 |
|
||||
playerInvisibilityTime | 4000 |
|
||||
monsterVelocity | 5 |
|
||||
monsterInvisibilityTime | 1000 |
|
||||
|
||||
La clé `player` est obligatoire et sa valeur représente les coordonnées (`i` et `j`) du joueur sur le premier niveau. Le fichier contient des clés de la forme `levelX` ou `X` représente un numéro de niveau compris entre 1 et la valeur de la clé `levels`. La valeur associée à un niveau est une chaine de caractère encodant le niveau avec ou sans compression (RLE) en fonction de la variable `compression`.
|
||||
Modifier le code dans la classe `GameLauncher` pour que le monde du jeu puisse être chargé depuis un fichier.
|
||||
|
||||
## Gestion des portes
|
||||
|
||||
Lorsque le joueur arrive sur la case d’une porte ouverte, il passe
|
||||
automatiquement au niveau correspondant à cette porte (niveau supérieur
|
||||
ou inférieur). Il se retrouve automatiquement sur la porte du niveau
|
||||
correspondant. Seuls le premier niveau (on ne peut pas passer au niveau inférieur) et le dernier (on ne peut pas aller plus loin) n'ont qu'une seule porte. Si la porte est fermée, le joueur doit utiliser une des
|
||||
clés de son inventaire. Pour ce faire, il doit appuyer sur la touche `[ENTER]` lorsqu'il est à côté de la porte à ouvrir et qu'il regarde la porte. Une fois utilisée, la clé disparaît de
|
||||
l’inventaire. Chaque clé peut ouvrir indifféremment n’importe quelle
|
||||
porte fermée. Une fois qu'une porte est ouverte, elle le reste pour toute la partie du jeu.
|
||||
|
||||
|
||||
## Gestion des bonus et malus
|
||||
|
||||
Le joueur ramasse automatiquement un bonus lorsqu'il marche sur la case qui le contient. Les monstres peuvent marcher sur les cases des bonus, mais ne peuvent pas les ramasser. Il existe 5 bonus différents :
|
||||
|
||||
Bonus | Effet
|
||||
--- | --- |
|
||||
 | Augmente la capacité du sac de bombes d’une unité. |
|
||||
 | Diminue la capacité du sac de bombes d’une unité. Le sac contient toujours au minimum une bombe. |
|
||||
|  | Augmente la portée des bombes d’une unité. La modification de portée n'affecte pas les bombes déjà posées. |
|
||||
|  | Diminue la portée des bombes d’une unité. La portée minimale est d’un. La modification de portée n'affecte pas les bombes déjà posées. |
|
||||
|  | Ajoute une vie. |
|
||||
|
||||
## Déplacement des caisses
|
||||
|
||||
Les caisses doivent pouvoir être déplacées par le joueur si rien ne gêne dans le sens de la poussée. Le joueur ne peut déplacer qu'une seule caisse à la fois. Si un bonus ou un monstre se trouve dans la direction de déplacement d’une caisse, la caisse reste bloquée. Le joueur ne peut pas déplacer deux caisses à la fois. Vous pouvez représenter les caisses comme des éléments de décor. Dans ce cas, déplacer une caisse revient à la supprimer et en créer une nouvelle aux bonnes coordonnées.
|
||||
|
||||
## Gestion des bombes
|
||||
|
||||
Lorsque le joueur presse la touche `[ESPACE]`, il dépose une bombe sur
|
||||
la case sur laquelle il se trouve, déclenchant une explosion au bout de
|
||||
4 secondes. La mèche de la bombe diminue chaque seconde. La portée de
|
||||
la bombe est par défaut de 1 case, en croix (case du dessus, case du
|
||||
dessous, case de gauche, case de droite). Les éléments de décor stoppent
|
||||
la propagation de l’explosion dans le sens qu’ils obstruent. Si une caisse est sur le chemin de l’explosion, elle
|
||||
disparaît. Une explosion ne peut
|
||||
détruire qu’une seule caisse dans une même direction. Si un bonus (ou un malus) se trouve sur le chemin de l’explosion, il disparaît.
|
||||
Enfin, si un joueur ou un monstre est sur une cellule touchée par une explosion, il
|
||||
perd une vie. Les explosions n’ont aucun effet sur les portes et les
|
||||
clés. Lorsqu’une bombe explose, une nouvelle bombe est ajoutée à
|
||||
l’inventaire du joueur.
|
||||
|
||||
Si le joueur pose une bombe et change ensuite de niveau en franchissant une porte, la bombe doit tout de même exploser au bout de 4 secondes. Les éléments de décor détruits sur un niveau doivent le rester pendant toute la durée de la partie.
|
||||
|
||||
## Gestion des vies
|
||||
|
||||
Le joueur peut perdre une vie s’il se trouve sur une case à portée de l’explosion d’une bombe ou s'il croise un monstre. Le joueur bénéficie alors d’une temporisation d'une seconde pendant laquelle il est invulnérable. Si le joueur n’a plus de vie, la partie se termine.
|
||||
|
||||
## Gestion des monstres
|
||||
|
||||
Les déplacements des monstres sont entièrement aléatoires. Une collision avec un monstre déclenche la perte d’une vie. Commencer par ajouter un seul monstre à la fois, puis augmenter le nombre de monstres. Les monstres ne peuvent pas ramasser les bonus qui se trouvent sur le sol. Les monstres ont peur des portes et ne peuvent pas les franchir. Ils ne peuvent pas marcher sur les cases des portes. Les monstres ne peuvent pas déplacer les caisse.
|
||||
|
||||
## Fin de partie
|
||||
|
||||
La partie est finie lorsque le joueur arrive sur la case de la
|
||||
princesse. Les monstres ne veulent pas de mal à la princesse, mais feront
|
||||
tout pour la garder prisonnière. La touche `[ESCAPE]` permet de quitter la partie à tout moment.
|
||||
|
||||
|
||||
## Pour aller plus loin et gagner des points bonus
|
||||
|
||||
- Faire en sorte que la vitesse de déplacement des monstres soit faible dans les premiers niveaux et augmente plus on se rapproche de la princesse.
|
||||
- Faire en sorte que les monstres possèdent une vie supplémentaire tous les 2 niveaux (2 vies à partir du niveau 2, 3 vies à partir du niveau 4, …) Il n'est pas demandé d'afficher les vies restantes d'un monstre.
|
||||
- Faire en sorte que les monstres du dernier niveau se dirigent vers le joueur et non plus de manière aléatoire.
|
||||
@@ -0,0 +1,29 @@
|
||||
plugins {
|
||||
id 'application'
|
||||
id 'org.openjfx.javafxplugin' version '0.0.13'
|
||||
}
|
||||
|
||||
group 'fr.ubx.poo.ubomb'
|
||||
version '1.0'
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
}
|
||||
|
||||
sourceCompatibility = '17'
|
||||
targetCompatibility = '17'
|
||||
|
||||
tasks.withType(JavaCompile) {
|
||||
options.encoding = 'UTF-8'
|
||||
}
|
||||
|
||||
mainClassName = 'fr.ubx.poo.ubomb.Main'
|
||||
|
||||
javafx {
|
||||
version = '17'
|
||||
modules = ['javafx.controls', 'javafx.fxml']
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
@@ -0,0 +1,240 @@
|
||||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright © 2015-2021 the original authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Gradle start up script for POSIX generated by Gradle.
|
||||
#
|
||||
# Important for running:
|
||||
#
|
||||
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||
# noncompliant, but you have some other compliant shell such as ksh or
|
||||
# bash, then to run this script, type that shell name before the whole
|
||||
# command line, like:
|
||||
#
|
||||
# ksh Gradle
|
||||
#
|
||||
# Busybox and similar reduced shells will NOT work, because this script
|
||||
# requires all of these POSIX shell features:
|
||||
# * functions;
|
||||
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||
# * compound commands having a testable exit status, especially «case»;
|
||||
# * various built-in commands including «command», «set», and «ulimit».
|
||||
#
|
||||
# Important for patching:
|
||||
#
|
||||
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||
#
|
||||
# The "traditional" practice of packing multiple parameters into a
|
||||
# space-separated string is a well documented source of bugs and security
|
||||
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||
# options in "$@", and eventually passing that to Java.
|
||||
#
|
||||
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||
# see the in-line comments for details.
|
||||
#
|
||||
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
|
||||
# Resolve links: $0 may be a link
|
||||
app_path=$0
|
||||
|
||||
# Need this for daisy-chained symlinks.
|
||||
while
|
||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||
[ -h "$app_path" ]
|
||||
do
|
||||
ls=$( ls -ld "$app_path" )
|
||||
link=${ls#*' -> '}
|
||||
case $link in #(
|
||||
/*) app_path=$link ;; #(
|
||||
*) app_path=$APP_HOME$link ;;
|
||||
esac
|
||||
done
|
||||
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=${0##*/}
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
} >&2
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
} >&2
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "$( uname )" in #(
|
||||
CYGWIN* ) cygwin=true ;; #(
|
||||
Darwin* ) darwin=true ;; #(
|
||||
MSYS* | MINGW* ) msys=true ;; #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||
else
|
||||
JAVACMD=$JAVA_HOME/bin/java
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD=java
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
fi
|
||||
|
||||
# Collect all arguments for the java command, stacking in reverse order:
|
||||
# * args from the command line
|
||||
# * the main class name
|
||||
# * -classpath
|
||||
# * -D...appname settings
|
||||
# * --module-path (only if needed)
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if "$cygwin" || "$msys" ; then
|
||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||
|
||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
for arg do
|
||||
if
|
||||
case $arg in #(
|
||||
-*) false ;; # don't mess with options #(
|
||||
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||
[ -e "$t" ] ;; #(
|
||||
*) false ;;
|
||||
esac
|
||||
then
|
||||
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||
fi
|
||||
# Roll the args list around exactly as many times as the number of
|
||||
# args, so each arg winds up back in the position where it started, but
|
||||
# possibly modified.
|
||||
#
|
||||
# NB: a `for` loop captures its iteration list before it begins, so
|
||||
# changing the positional parameters here affects neither the number of
|
||||
# iterations, nor the values presented in `arg`.
|
||||
shift # remove old arg
|
||||
set -- "$@" "$arg" # push replacement arg
|
||||
done
|
||||
fi
|
||||
|
||||
# Collect all arguments for the java command;
|
||||
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||
# shell script including quotes and variable substitutions, so put them in
|
||||
# double quotes to make sure that they get re-expanded; and
|
||||
# * put everything else in single quotes, so that it's not re-expanded.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
if ! command -v xargs >/dev/null 2>&1
|
||||
then
|
||||
die "xargs is not available"
|
||||
fi
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
#
|
||||
# In Bash we could simply go:
|
||||
#
|
||||
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||
# set -- "${ARGS[@]}" "$@"
|
||||
#
|
||||
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||
# character that might be a shell metacharacter, then use eval to reverse
|
||||
# that process (while maintaining the separation between arguments), and wrap
|
||||
# the whole thing up as a single "set" statement.
|
||||
#
|
||||
# This will of course break if any of these variables contains a newline or
|
||||
# an unmatched quote.
|
||||
#
|
||||
|
||||
eval "set -- $(
|
||||
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||
xargs -n1 |
|
||||
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||
tr '\n' ' '
|
||||
)" '"$@"'
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
@@ -0,0 +1,91 @@
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
set EXIT_CODE=%ERRORLEVEL%
|
||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||
exit /b %EXIT_CODE%
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
|
After Width: | Height: | Size: 508 KiB |
@@ -0,0 +1 @@
|
||||
rootProject.name = "UBomb"
|
||||
@@ -0,0 +1,21 @@
|
||||
package fr.ubx.poo.ubomb;
|
||||
|
||||
import fr.ubx.poo.ubomb.view.*;
|
||||
import javafx.application.Application;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
public class Main extends Application {
|
||||
|
||||
@Override
|
||||
public void start(Stage stage) {
|
||||
GameLauncherView launcher = new GameLauncherView(stage);
|
||||
Scene scene = new Scene(launcher);
|
||||
stage.setTitle("UBomb 2022 Student");
|
||||
stage.setScene(scene);
|
||||
stage.sizeToScene();
|
||||
stage.show();
|
||||
}
|
||||
|
||||
public static void main(String[] args) { launch(); }
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
/*
|
||||
* Copyright (c) 2020. Laurent Réveillère
|
||||
*/
|
||||
|
||||
package fr.ubx.poo.ubomb.engine;
|
||||
|
||||
import fr.ubx.poo.ubomb.game.Direction;
|
||||
import fr.ubx.poo.ubomb.game.Game;
|
||||
import fr.ubx.poo.ubomb.game.Position;
|
||||
import fr.ubx.poo.ubomb.go.character.Player;
|
||||
import fr.ubx.poo.ubomb.view.ImageResource;
|
||||
import fr.ubx.poo.ubomb.view.Sprite;
|
||||
import fr.ubx.poo.ubomb.view.SpriteFactory;
|
||||
import fr.ubx.poo.ubomb.view.SpritePlayer;
|
||||
import javafx.animation.AnimationTimer;
|
||||
import javafx.animation.TranslateTransition;
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.Group;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.text.Font;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.scene.text.TextAlignment;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.util.Duration;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
|
||||
public final class GameEngine {
|
||||
|
||||
private static AnimationTimer gameLoop;
|
||||
private final Game game;
|
||||
private final Player player;
|
||||
private final List<Sprite> sprites = new LinkedList<>();
|
||||
private final Set<Sprite> cleanUpSprites = new HashSet<>();
|
||||
private final Stage stage;
|
||||
private StatusBar statusBar;
|
||||
private Pane layer;
|
||||
private Input input;
|
||||
|
||||
public GameEngine(Game game, final Stage stage) {
|
||||
this.stage = stage;
|
||||
this.game = game;
|
||||
this.player = game.player();
|
||||
initialize();
|
||||
buildAndSetGameLoop();
|
||||
}
|
||||
|
||||
private void initialize() {
|
||||
Group root = new Group();
|
||||
layer = new Pane();
|
||||
|
||||
int height = game.grid().height();
|
||||
int width = game.grid().width();
|
||||
int sceneWidth = width * ImageResource.size;
|
||||
int sceneHeight = height * ImageResource.size;
|
||||
Scene scene = new Scene(root, sceneWidth, sceneHeight + StatusBar.height);
|
||||
scene.getStylesheets().add(getClass().getResource("/css/application.css").toExternalForm());
|
||||
|
||||
stage.setScene(scene);
|
||||
stage.setResizable(false);
|
||||
stage.sizeToScene();
|
||||
stage.hide();
|
||||
stage.show();
|
||||
|
||||
input = new Input(scene);
|
||||
root.getChildren().add(layer);
|
||||
statusBar = new StatusBar(root, sceneWidth, sceneHeight, game);
|
||||
|
||||
// Create sprites
|
||||
for (var decor : game.grid().values()) {
|
||||
sprites.add(SpriteFactory.create(layer, decor));
|
||||
decor.setModified(true);
|
||||
}
|
||||
|
||||
sprites.add(new SpritePlayer(layer, player));
|
||||
}
|
||||
|
||||
void buildAndSetGameLoop() {
|
||||
gameLoop = new AnimationTimer() {
|
||||
public void handle(long now) {
|
||||
// Check keyboard actions
|
||||
processInput(now);
|
||||
|
||||
// Do actions
|
||||
update(now);
|
||||
createNewBombs(now);
|
||||
checkCollision(now);
|
||||
checkExplosions();
|
||||
|
||||
// Graphic update
|
||||
cleanupSprites();
|
||||
render();
|
||||
statusBar.update(game);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void checkExplosions() {
|
||||
// Check explosions of bombs
|
||||
}
|
||||
|
||||
private void animateExplosion(Position src, Position dst) {
|
||||
ImageView explosion = new ImageView(ImageResource.EXPLOSION.getImage());
|
||||
TranslateTransition tt = new TranslateTransition(Duration.millis(200), explosion);
|
||||
tt.setFromX(src.x() * Sprite.size);
|
||||
tt.setFromY(src.y() * Sprite.size);
|
||||
tt.setToX(dst.x() * Sprite.size);
|
||||
tt.setToY(dst.y() * Sprite.size);
|
||||
tt.setOnFinished(e -> {
|
||||
layer.getChildren().remove(explosion);
|
||||
});
|
||||
layer.getChildren().add(explosion);
|
||||
tt.play();
|
||||
}
|
||||
|
||||
private void createNewBombs(long now) {
|
||||
// Create a new Bomb is needed
|
||||
}
|
||||
|
||||
private void checkCollision(long now) {
|
||||
// Check a collision between a monster and the player
|
||||
}
|
||||
|
||||
private void processInput(long now) {
|
||||
if (input.isExit()) {
|
||||
gameLoop.stop();
|
||||
Platform.exit();
|
||||
System.exit(0);
|
||||
} else if (input.isMoveDown()) {
|
||||
player.requestMove(Direction.DOWN);
|
||||
} else if (input.isMoveLeft()) {
|
||||
player.requestMove(Direction.LEFT);
|
||||
} else if (input.isMoveRight()) {
|
||||
player.requestMove(Direction.RIGHT);
|
||||
} else if (input.isMoveUp()) {
|
||||
player.requestMove(Direction.UP);
|
||||
}
|
||||
input.clear();
|
||||
}
|
||||
|
||||
private void showMessage(String msg, Color color) {
|
||||
Text waitingForKey = new Text(msg);
|
||||
waitingForKey.setTextAlignment(TextAlignment.CENTER);
|
||||
waitingForKey.setFont(new Font(60));
|
||||
waitingForKey.setFill(color);
|
||||
StackPane root = new StackPane();
|
||||
root.getChildren().add(waitingForKey);
|
||||
Scene scene = new Scene(root, 400, 200, Color.WHITE);
|
||||
stage.setScene(scene);
|
||||
input = new Input(scene);
|
||||
stage.show();
|
||||
new AnimationTimer() {
|
||||
public void handle(long now) {
|
||||
processInput(now);
|
||||
}
|
||||
}.start();
|
||||
}
|
||||
|
||||
|
||||
private void update(long now) {
|
||||
player.update(now);
|
||||
|
||||
if (player.getLives() == 0) {
|
||||
gameLoop.stop();
|
||||
showMessage("Perdu!", Color.RED);
|
||||
}
|
||||
}
|
||||
|
||||
public void cleanupSprites() {
|
||||
sprites.forEach(sprite -> {
|
||||
if (sprite.getGameObject().isDeleted()) {
|
||||
game.grid().remove(sprite.getPosition());
|
||||
cleanUpSprites.add(sprite);
|
||||
}
|
||||
});
|
||||
cleanUpSprites.forEach(Sprite::remove);
|
||||
sprites.removeAll(cleanUpSprites);
|
||||
cleanUpSprites.clear();
|
||||
}
|
||||
|
||||
private void render() {
|
||||
sprites.forEach(Sprite::render);
|
||||
}
|
||||
|
||||
public void start() {
|
||||
gameLoop.start();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright (c) 2020. Laurent Réveillère
|
||||
*/
|
||||
|
||||
package fr.ubx.poo.ubomb.engine;
|
||||
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
|
||||
import java.util.BitSet;
|
||||
|
||||
import static javafx.scene.input.KeyCode.*;
|
||||
|
||||
public final class Input {
|
||||
|
||||
/**
|
||||
* Bitset which registers if any {@link KeyCode} keeps being pressed or if it is
|
||||
* released.
|
||||
*/
|
||||
private final BitSet keyboardBitSet = new BitSet();
|
||||
/**
|
||||
* "Key Pressed" handler for all input events: register pressed key in the bitset
|
||||
*/
|
||||
private final EventHandler<KeyEvent> keyPressedEventHandler = event -> {
|
||||
|
||||
// register key down
|
||||
keyboardBitSet.set(event.getCode().ordinal(), true);
|
||||
};
|
||||
/**
|
||||
* "Key Released" handler for all input events: unregister released key in the bitset
|
||||
*/
|
||||
private final EventHandler<KeyEvent> keyReleasedEventHandler = event -> {
|
||||
|
||||
// register key up
|
||||
keyboardBitSet.set(event.getCode().ordinal(), false);
|
||||
};
|
||||
private final Scene scene;
|
||||
|
||||
public Input(Scene scene) {
|
||||
this.scene = scene;
|
||||
scene.addEventFilter(KeyEvent.KEY_PRESSED, keyPressedEventHandler);
|
||||
scene.addEventFilter(KeyEvent.KEY_RELEASED, keyReleasedEventHandler);
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
keyboardBitSet.clear();
|
||||
}
|
||||
|
||||
private boolean is(KeyCode key) {
|
||||
return keyboardBitSet.get(key.ordinal());
|
||||
}
|
||||
|
||||
// -------------------------------------------------
|
||||
// Evaluate bitset of pressed keys and return the player input.
|
||||
// If direction and its opposite direction are pressed simultaneously, then the
|
||||
// direction isn't handled.
|
||||
// -------------------------------------------------
|
||||
|
||||
public boolean isMoveUp() {
|
||||
return is(UP) && !is(DOWN);
|
||||
}
|
||||
|
||||
public boolean isMoveDown() {
|
||||
return is(DOWN) && !is(UP);
|
||||
}
|
||||
|
||||
public boolean isMoveLeft() {
|
||||
return is(LEFT) && !is(RIGHT);
|
||||
}
|
||||
|
||||
public boolean isMoveRight() {
|
||||
return is(RIGHT) && !is(LEFT);
|
||||
}
|
||||
|
||||
public boolean isBomb() {
|
||||
return is(SPACE);
|
||||
}
|
||||
|
||||
public boolean isKey() {
|
||||
return is(ENTER);
|
||||
}
|
||||
|
||||
public boolean isExit() {
|
||||
return is(ESCAPE);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright (c) 2020. Laurent Réveillère
|
||||
*/
|
||||
|
||||
package fr.ubx.poo.ubomb.engine;
|
||||
|
||||
import fr.ubx.poo.ubomb.game.Game;
|
||||
import fr.ubx.poo.ubomb.go.character.Player;
|
||||
import fr.ubx.poo.ubomb.view.ImageResource;
|
||||
import fr.ubx.poo.ubomb.view.ImageResourceFactory;
|
||||
import javafx.scene.Group;
|
||||
import javafx.scene.effect.DropShadow;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.text.Text;
|
||||
|
||||
public class StatusBar {
|
||||
public static final int height = 55;
|
||||
private final Game game;
|
||||
private final DropShadow ds = new DropShadow();
|
||||
private final HBox hBox = new HBox();
|
||||
private final Text lives = new Text();
|
||||
private final Text availableBombs = new Text();
|
||||
private final Text bombRange = new Text();
|
||||
private final Text keys = new Text();
|
||||
private final HBox level = new HBox();
|
||||
|
||||
|
||||
public StatusBar(Group root, int sceneWidth, int sceneHeight, Game game) {
|
||||
// Status bar
|
||||
this.game = game;
|
||||
|
||||
level.getStyleClass().add("level");
|
||||
level.getChildren().add(new ImageView(ImageResourceFactory.digit(1).getImage()));
|
||||
|
||||
ds.setRadius(5.0);
|
||||
ds.setOffsetX(3.0);
|
||||
ds.setOffsetY(3.0);
|
||||
ds.setColor(Color.color(0.5f, 0.5f, 0.5f));
|
||||
|
||||
|
||||
HBox status = new HBox();
|
||||
status.getStyleClass().add("status");
|
||||
HBox live = statusGroup(ImageResource.HEART.getImage(), this.lives);
|
||||
HBox bombs = statusGroup(ImageResource.BANNER_BOMB.getImage(), availableBombs);
|
||||
HBox range = statusGroup(ImageResource.BANNER_RANGE.getImage(), bombRange);
|
||||
HBox key = statusGroup(ImageResource.KEY.getImage(), keys);
|
||||
status.setSpacing(40.0);
|
||||
status.getChildren().addAll(live, bombs, range, key);
|
||||
|
||||
hBox.getChildren().addAll(level, status);
|
||||
hBox.getStyleClass().add("statusBar");
|
||||
hBox.relocate(0, sceneHeight);
|
||||
hBox.setPrefSize(sceneWidth, height);
|
||||
root.getChildren().add(hBox);
|
||||
}
|
||||
|
||||
private HBox statusGroup(Image kind, Text number) {
|
||||
HBox group = new HBox();
|
||||
ImageView img = new ImageView(kind);
|
||||
group.setSpacing(4);
|
||||
number.setEffect(ds);
|
||||
number.setCache(true);
|
||||
number.setFill(Color.BLACK);
|
||||
number.getStyleClass().add("number");
|
||||
group.getChildren().addAll(img, number);
|
||||
return group;
|
||||
}
|
||||
|
||||
public void update(Game game) {
|
||||
Player player = game.player();
|
||||
lives.setText("?");
|
||||
bombRange.setText("?");
|
||||
availableBombs.setText("?");
|
||||
keys.setText("?");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright (c) 2020. Laurent Réveillère
|
||||
*/
|
||||
|
||||
package fr.ubx.poo.ubomb.engine;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
public class Timer {
|
||||
private final long duration;
|
||||
private long startTime;
|
||||
private boolean running = false;
|
||||
private boolean requested = false;
|
||||
private long remaining;
|
||||
|
||||
// Set a timer for a duration in ms
|
||||
public Timer(long duration) {
|
||||
this.duration = duration;
|
||||
remaining = duration;
|
||||
}
|
||||
|
||||
public void update(long now) {
|
||||
// time is in ns
|
||||
if (running) {
|
||||
remaining = duration - (now - startTime) / 1000000;
|
||||
if (remaining < 0) {
|
||||
running = false;
|
||||
}
|
||||
} else if (requested) {
|
||||
running = true;
|
||||
requested = false;
|
||||
startTime = now;
|
||||
remaining = duration;
|
||||
}
|
||||
}
|
||||
|
||||
public long remaining() {
|
||||
return remaining;
|
||||
}
|
||||
|
||||
public void start() {
|
||||
if (!running)
|
||||
requested = true;
|
||||
}
|
||||
|
||||
public boolean isRunning() {
|
||||
return running || requested;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package fr.ubx.poo.ubomb.game;
|
||||
|
||||
public record Configuration(Position playerPosition, int bombBagCapacity, int playerLives, long playerInvisibilityTime,
|
||||
int monsterVelocity, long monsterInvisibilityTime) {
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright (c) 2020. Laurent Réveillère
|
||||
*/
|
||||
|
||||
package fr.ubx.poo.ubomb.game;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
public enum Direction {
|
||||
UP {
|
||||
@Override
|
||||
public Position nextPosition(Position pos, int delta) {
|
||||
return new Position(pos.x(), pos.y() - delta);
|
||||
}
|
||||
},
|
||||
RIGHT {
|
||||
@Override
|
||||
public Position nextPosition(Position pos, int delta) {
|
||||
return new Position(pos.x() + delta, pos.y());
|
||||
}
|
||||
},
|
||||
DOWN {
|
||||
@Override
|
||||
public Position nextPosition(Position pos, int delta) {
|
||||
return new Position(pos.x(), pos.y() + delta);
|
||||
}
|
||||
},
|
||||
LEFT {
|
||||
@Override
|
||||
public Position nextPosition(Position pos, int delta) {
|
||||
return new Position(pos.x() - delta, pos.y());
|
||||
}
|
||||
},
|
||||
;
|
||||
|
||||
private static final Random randomGenerator = new Random();
|
||||
|
||||
public static Direction random() {
|
||||
int i = randomGenerator.nextInt(values().length);
|
||||
return values()[i];
|
||||
}
|
||||
|
||||
public abstract Position nextPosition(Position pos, int delta);
|
||||
|
||||
public Position nextPosition(Position pos) {
|
||||
return nextPosition(pos, 1);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package fr.ubx.poo.ubomb.game;
|
||||
|
||||
import fr.ubx.poo.ubomb.go.GameObject;
|
||||
import fr.ubx.poo.ubomb.go.character.Player;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
public class Game {
|
||||
|
||||
private final Configuration configuration;
|
||||
private final Player player;
|
||||
private final Grid grid;
|
||||
|
||||
public Game(Configuration configuration, Grid grid) {
|
||||
this.configuration = configuration;
|
||||
this.grid = grid;
|
||||
player = new Player(this, configuration.playerPosition());
|
||||
}
|
||||
|
||||
public Configuration configuration() {
|
||||
return configuration;
|
||||
}
|
||||
|
||||
// Returns the player, monsters and bomb at a given position
|
||||
public List<GameObject> getGameObjects(Position position) {
|
||||
List<GameObject> gos = new LinkedList<>();
|
||||
if (player().getPosition().equals(position))
|
||||
gos.add(player);
|
||||
return gos;
|
||||
}
|
||||
|
||||
public Grid grid() {
|
||||
return grid;
|
||||
}
|
||||
|
||||
public Player player() {
|
||||
return this.player;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package fr.ubx.poo.ubomb.game;
|
||||
|
||||
|
||||
import fr.ubx.poo.ubomb.go.decor.Decor;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public interface Grid {
|
||||
int width();
|
||||
int height();
|
||||
|
||||
Decor get(Position position);
|
||||
|
||||
void remove(Position position);
|
||||
|
||||
Collection<Decor> values();
|
||||
|
||||
|
||||
boolean inside(Position nextPos);
|
||||
|
||||
void set(Position position, Decor decor);
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package fr.ubx.poo.ubomb.game;
|
||||
|
||||
import fr.ubx.poo.ubomb.go.decor.bonus.*;
|
||||
import fr.ubx.poo.ubomb.go.decor.*;
|
||||
import fr.ubx.poo.ubomb.launcher.Entity;
|
||||
import fr.ubx.poo.ubomb.launcher.MapLevel;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class Level implements Grid {
|
||||
|
||||
private final int width;
|
||||
|
||||
private final int height;
|
||||
|
||||
private final MapLevel entities;
|
||||
|
||||
|
||||
private final Map<Position, Decor> elements = new HashMap<>();
|
||||
|
||||
public Level(MapLevel entities) {
|
||||
this.entities = entities;
|
||||
this.width = entities.width();
|
||||
this.height = entities.height();
|
||||
|
||||
for (int i = 0; i < width; i++)
|
||||
for (int j = 0; j < height; j++) {
|
||||
Position position = new Position(i, j);
|
||||
Entity entity = entities.get(i, j);
|
||||
switch (entity) {
|
||||
case Stone:
|
||||
elements.put(position, new Stone(position));
|
||||
break;
|
||||
case Tree:
|
||||
elements.put(position, new Tree(position));
|
||||
break;
|
||||
case Key:
|
||||
elements.put(position, new Key(position));
|
||||
break;
|
||||
case Empty: break;
|
||||
default:
|
||||
throw new RuntimeException("EntityCode " + entity.name() + " not processed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int width() {
|
||||
return this.width;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int height() {
|
||||
return this.height;
|
||||
}
|
||||
|
||||
public Decor get(Position position) {
|
||||
return elements.get(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(Position position) {
|
||||
elements.remove(position);
|
||||
}
|
||||
|
||||
public Collection<Decor> values() {
|
||||
return elements.values();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean inside(Position position) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(Position position, Decor decor) {
|
||||
if (!inside(position))
|
||||
throw new IllegalArgumentException("Illegal Position");
|
||||
if (decor != null)
|
||||
elements.put(position, decor);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package fr.ubx.poo.ubomb.game;
|
||||
|
||||
public record Position (int x, int y) {
|
||||
|
||||
public Position(Position position) {
|
||||
this(position.x, position.y);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright (c) 2020. Laurent Réveillère
|
||||
*/
|
||||
|
||||
package fr.ubx.poo.ubomb.go;
|
||||
|
||||
import fr.ubx.poo.ubomb.game.Game;
|
||||
import fr.ubx.poo.ubomb.game.Position;
|
||||
|
||||
public abstract class GameObject implements Walkable {
|
||||
public final Game game;
|
||||
private boolean deleted = false;
|
||||
private boolean modified = true;
|
||||
private Position position;
|
||||
|
||||
public GameObject(Game game, Position position) {
|
||||
this.game = game;
|
||||
this.position = position;
|
||||
}
|
||||
|
||||
public GameObject(Position position) {
|
||||
this(null, position);
|
||||
}
|
||||
|
||||
public Position getPosition() {
|
||||
return position;
|
||||
}
|
||||
|
||||
public void setPosition(Position position) {
|
||||
this.position = position;
|
||||
setModified(true);
|
||||
}
|
||||
|
||||
public boolean isModified() {
|
||||
return modified;
|
||||
}
|
||||
|
||||
public void setModified(boolean modified) {
|
||||
this.modified = modified;
|
||||
}
|
||||
|
||||
public boolean isDeleted() {
|
||||
return deleted;
|
||||
}
|
||||
|
||||
public void remove() {
|
||||
deleted = true;
|
||||
}
|
||||
|
||||
public void explode() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
/*
|
||||
* Copyright (c) 2020. Laurent Réveillère
|
||||
*/
|
||||
|
||||
package fr.ubx.poo.ubomb.go;
|
||||
|
||||
import fr.ubx.poo.ubomb.game.Direction;
|
||||
|
||||
// For GameObjects that can move
|
||||
public interface Movable {
|
||||
boolean canMove(Direction direction);
|
||||
void doMove(Direction direction);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package fr.ubx.poo.ubomb.go;
|
||||
|
||||
import fr.ubx.poo.ubomb.go.decor.bonus.*;
|
||||
|
||||
// Double dispatch visitor pattern
|
||||
public interface TakeVisitor {
|
||||
// Key
|
||||
default void take(Key key) {}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
* Copyright (c) 2020. Laurent Réveillère
|
||||
*/
|
||||
|
||||
package fr.ubx.poo.ubomb.go;
|
||||
|
||||
|
||||
import fr.ubx.poo.ubomb.go.character.Player;
|
||||
|
||||
public interface Takeable {
|
||||
default void takenBy(Player player) {}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package fr.ubx.poo.ubomb.go;
|
||||
|
||||
import fr.ubx.poo.ubomb.go.character.Player;
|
||||
|
||||
public interface Walkable {
|
||||
default boolean walkableBy(Player player) { return false; }
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright (c) 2020. Laurent Réveillère
|
||||
*/
|
||||
|
||||
package fr.ubx.poo.ubomb.go.character;
|
||||
|
||||
import fr.ubx.poo.ubomb.engine.Timer;
|
||||
import fr.ubx.poo.ubomb.game.Direction;
|
||||
import fr.ubx.poo.ubomb.game.Game;
|
||||
import fr.ubx.poo.ubomb.game.Position;
|
||||
import fr.ubx.poo.ubomb.go.GameObject;
|
||||
import fr.ubx.poo.ubomb.go.Movable;
|
||||
import fr.ubx.poo.ubomb.go.TakeVisitor;
|
||||
import fr.ubx.poo.ubomb.go.decor.bonus.*;
|
||||
|
||||
public class Player extends GameObject implements Movable, TakeVisitor {
|
||||
|
||||
private Direction direction;
|
||||
private boolean moveRequested = false;
|
||||
private final int lives;
|
||||
|
||||
public Player(Game game, Position position) {
|
||||
super(game, position);
|
||||
this.direction = Direction.DOWN;
|
||||
this.lives = game.configuration().playerLives();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void take(Key key) {
|
||||
System.out.println("Take the key ...");
|
||||
}
|
||||
|
||||
public void doMove(Direction direction) {
|
||||
// This method is called only if the move is possible, do not check again
|
||||
Position nextPos = direction.nextPosition(getPosition());
|
||||
GameObject next = game.grid().get(nextPos);
|
||||
if (next instanceof Bonus bonus) {
|
||||
bonus.takenBy(this);
|
||||
}
|
||||
setPosition(nextPos);
|
||||
}
|
||||
|
||||
|
||||
public int getLives() {
|
||||
return lives;
|
||||
}
|
||||
|
||||
public Direction getDirection() {
|
||||
return direction;
|
||||
}
|
||||
|
||||
public void requestMove(Direction direction) {
|
||||
if (direction != this.direction) {
|
||||
this.direction = direction;
|
||||
setModified(true);
|
||||
}
|
||||
moveRequested = true;
|
||||
}
|
||||
|
||||
public final boolean canMove(Direction direction) {
|
||||
// Need to be updated ;-)
|
||||
return true;
|
||||
}
|
||||
|
||||
public void update(long now) {
|
||||
if (moveRequested) {
|
||||
if (canMove(direction)) {
|
||||
doMove(direction);
|
||||
}
|
||||
}
|
||||
moveRequested = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void explode() {
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package fr.ubx.poo.ubomb.go.decor;
|
||||
|
||||
import fr.ubx.poo.ubomb.game.Game;
|
||||
import fr.ubx.poo.ubomb.game.Position;
|
||||
import fr.ubx.poo.ubomb.go.GameObject;
|
||||
|
||||
public abstract class Decor extends GameObject {
|
||||
|
||||
public Decor(Game game, Position position) {
|
||||
super(game, position);
|
||||
}
|
||||
|
||||
public Decor(Position position) {
|
||||
super(position);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
/*
|
||||
* Copyright (c) 2020. Laurent Réveillère
|
||||
*/
|
||||
|
||||
package fr.ubx.poo.ubomb.go.decor;
|
||||
|
||||
import fr.ubx.poo.ubomb.game.Position;
|
||||
|
||||
public class Stone extends Decor {
|
||||
public Stone(Position position) {
|
||||
super(position);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
* Copyright (c) 2020. Laurent Réveillère
|
||||
*/
|
||||
|
||||
package fr.ubx.poo.ubomb.go.decor;
|
||||
import fr.ubx.poo.ubomb.game.Position;
|
||||
|
||||
public class Tree extends Decor {
|
||||
public Tree(Position position) {
|
||||
super(position);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright (c) 2020. Laurent Réveillère
|
||||
*/
|
||||
|
||||
package fr.ubx.poo.ubomb.go.decor.bonus;
|
||||
|
||||
import fr.ubx.poo.ubomb.game.Position;
|
||||
import fr.ubx.poo.ubomb.go.Takeable;
|
||||
import fr.ubx.poo.ubomb.go.character.Player;
|
||||
import fr.ubx.poo.ubomb.go.decor.Decor;
|
||||
|
||||
public abstract class Bonus extends Decor implements Takeable {
|
||||
public Bonus(Position position) {
|
||||
super(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean walkableBy(Player player) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void explode() {
|
||||
remove();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright (c) 2020. Laurent Réveillère
|
||||
*/
|
||||
|
||||
package fr.ubx.poo.ubomb.go.decor.bonus;
|
||||
|
||||
import fr.ubx.poo.ubomb.game.Position;
|
||||
import fr.ubx.poo.ubomb.go.character.Player;
|
||||
|
||||
public class Key extends Bonus {
|
||||
public Key(Position position) {
|
||||
super(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void explode() {}
|
||||
|
||||
@Override
|
||||
public void takenBy(Player player) {
|
||||
player.take(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package fr.ubx.poo.ubomb.launcher;
|
||||
|
||||
public enum Entity {
|
||||
Empty('_'),
|
||||
Box('B'),
|
||||
Stone('S'),
|
||||
Tree('T'),
|
||||
|
||||
BombRangeInc('>'),
|
||||
BombRangeDec('<'),
|
||||
BombNumberInc('+'),
|
||||
BombNumberDec('-'),
|
||||
Heart('H'),
|
||||
Key('K'),
|
||||
DoorPrevOpened('V'),
|
||||
DoorNextOpened('N'),
|
||||
DoorNextClosed('n'),
|
||||
Monster('M'),
|
||||
Princess('W');
|
||||
|
||||
private final char code;
|
||||
|
||||
Entity(char c) {
|
||||
this.code = c;
|
||||
}
|
||||
|
||||
public char getCode() { return this.code; }
|
||||
|
||||
public static Entity fromCode(char c) {
|
||||
for (Entity entity : values()) {
|
||||
if (entity.code == c)
|
||||
return entity;
|
||||
}
|
||||
throw new MapException("Invalid character " + c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return Character.toString(code);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package fr.ubx.poo.ubomb.launcher;
|
||||
|
||||
import fr.ubx.poo.ubomb.game.Configuration;
|
||||
import fr.ubx.poo.ubomb.game.Game;
|
||||
import fr.ubx.poo.ubomb.game.Level;
|
||||
import fr.ubx.poo.ubomb.game.Position;
|
||||
|
||||
public class GameLauncher {
|
||||
|
||||
public static Game load() {
|
||||
Configuration configuration = new Configuration(new Position(0, 0), 3, 5, 4000, 5, 1000);
|
||||
return new Game(configuration, new Level(new MapLevelDefault()));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package fr.ubx.poo.ubomb.launcher;
|
||||
|
||||
public class MapException extends RuntimeException {
|
||||
public MapException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package fr.ubx.poo.ubomb.launcher;
|
||||
|
||||
import fr.ubx.poo.ubomb.game.Grid;
|
||||
|
||||
public class MapLevel {
|
||||
|
||||
private final int width;
|
||||
private final int height;
|
||||
private final Entity[][] grid;
|
||||
|
||||
public MapLevel(int width, int height) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.grid = new Entity[height][width];
|
||||
}
|
||||
|
||||
public int width() {
|
||||
return width; }
|
||||
|
||||
public int height() {
|
||||
return height;
|
||||
}
|
||||
|
||||
public Entity get(int i, int j) {
|
||||
return grid[j][i];
|
||||
}
|
||||
|
||||
public void set(int i, int j, Entity entity) {
|
||||
grid[j][i] = entity;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package fr.ubx.poo.ubomb.launcher;
|
||||
|
||||
import static fr.ubx.poo.ubomb.launcher.Entity.*;
|
||||
|
||||
public class MapLevelDefault extends MapLevel {
|
||||
private final static Entity[][] level1 = {
|
||||
{Empty, Empty, Empty, Empty, Empty, Empty, Empty, Empty, Empty, Empty, Empty, Empty},
|
||||
{Empty, Empty, Stone, Empty, Stone, Empty, Stone, Stone, Stone, Stone, Empty, Empty},
|
||||
{Empty, Empty, Empty, Empty, Stone, Empty, Stone, Empty, Empty, Stone, Empty, Empty},
|
||||
{Empty, Empty, Empty, Empty, Stone, Empty, Stone, Empty, Empty, Stone, Empty, Empty},
|
||||
{Empty, Empty, Empty, Empty, Stone, Stone, Stone, Empty, Empty, Empty, Empty, Empty},
|
||||
{Empty, Empty, Empty, Empty, Empty, Empty, Empty, Key, Empty, Stone, Empty, Empty},
|
||||
{Empty, Tree, Empty, Tree, Empty, Empty, Empty, Empty, Empty, Stone, Empty, Empty},
|
||||
{Empty, Empty, Empty, Tree, Empty, Empty, Empty, Empty, Empty, Stone, Empty, Empty},
|
||||
{Empty, Tree, Tree, Tree, Empty, Empty, Empty, Empty, Empty, Stone, Empty, Empty},
|
||||
{Empty, Empty, Empty, Empty, Empty, Empty, Empty, Empty, Empty, Empty, Empty, Empty},
|
||||
{Stone, Stone, Stone, Stone, Stone, Empty, Empty, Empty, Stone, Stone, Empty, Stone},
|
||||
{Empty, Empty, Empty, Empty, Empty, Empty, Empty, Empty, Empty, Empty, Empty, Empty},
|
||||
{Empty, Empty, Empty, Empty, Empty, Empty, Empty, Empty, Empty, Empty, Empty, Empty}
|
||||
};
|
||||
private final static int width = 12;
|
||||
private final static int height = 13;
|
||||
|
||||
public MapLevelDefault() {
|
||||
super(width, height);
|
||||
for (int i = 0; i < width; i++)
|
||||
for (int j = 0; j < height; j++)
|
||||
set(i, j, level1[j][i]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package fr.ubx.poo.ubomb.launcher;
|
||||
|
||||
public interface MapRepo {
|
||||
|
||||
MapLevel load(String string);
|
||||
|
||||
String export(MapLevel mapLevel);
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package fr.ubx.poo.ubomb.view;
|
||||
|
||||
import fr.ubx.poo.ubomb.engine.GameEngine;
|
||||
import fr.ubx.poo.ubomb.game.Game;
|
||||
import fr.ubx.poo.ubomb.launcher.GameLauncher;
|
||||
import fr.ubx.poo.ubomb.launcher.MapLevel;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.input.KeyCombination;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.stage.FileChooser;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
public class GameLauncherView extends BorderPane {
|
||||
private final FileChooser fileChooser = new FileChooser();
|
||||
|
||||
public GameLauncherView(Stage stage) {
|
||||
// Create menu
|
||||
MenuBar menuBar = new MenuBar();
|
||||
Menu menuFile = new Menu("File");
|
||||
MenuItem loadItem = new MenuItem("Load from file ...");
|
||||
MenuItem defaultItem = new MenuItem("Load default configuration");
|
||||
MenuItem exitItem = new MenuItem("Exit");
|
||||
exitItem.setAccelerator(KeyCombination.keyCombination("Ctrl+Q"));
|
||||
menuFile.getItems().addAll(
|
||||
loadItem, defaultItem, new SeparatorMenuItem(),
|
||||
exitItem);
|
||||
|
||||
menuBar.getMenus().addAll(menuFile);
|
||||
this.setTop(menuBar);
|
||||
|
||||
Text text = new Text("UBomb 2022");
|
||||
text.getStyleClass().add("message");
|
||||
VBox scene = new VBox();
|
||||
scene.getChildren().add(text);
|
||||
scene.getStylesheets().add(getClass().getResource("/css/application.css").toExternalForm());
|
||||
scene.getStyleClass().add("message");
|
||||
this.setCenter(scene);
|
||||
|
||||
// Load from file
|
||||
loadItem.setOnAction(e -> {
|
||||
File file = fileChooser.showOpenDialog(stage);
|
||||
if (file != null) {
|
||||
// TODO
|
||||
System.err.println("[TODO] Not implemented");
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
defaultItem.setOnAction(e -> {
|
||||
Game game = GameLauncher.load();
|
||||
GameEngine engine = new GameEngine(game, stage);
|
||||
engine.start();
|
||||
});
|
||||
|
||||
// Exit
|
||||
exitItem.setOnAction(e -> System.exit(0));
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package fr.ubx.poo.ubomb.view;
|
||||
|
||||
import javafx.scene.image.Image;
|
||||
|
||||
|
||||
public enum ImageResource {
|
||||
// Only for the editor
|
||||
GROUND("ground.png"),
|
||||
|
||||
STONE("stone.png"),
|
||||
TREE("tree.png"),
|
||||
DOOR_OPENED("door_opened.png"),
|
||||
DOOR_CLOSED("door_closed.png"),
|
||||
|
||||
// Bonus
|
||||
HEART("heart.png"),
|
||||
KEY("key.png"),
|
||||
|
||||
BONUS_BOMB_NB_INC("bonus_bomb_nb_inc.png"),
|
||||
BONUS_BOMB_NB_DEC("bonus_bomb_nb_dec.png"),
|
||||
BONUS_BOMB_RANGE_INC("bonus_bomb_range_inc.png"),
|
||||
BONUS_BOMB_RANGE_DEC("bonus_bomb_range_dec.png"),
|
||||
|
||||
BOX("box.png"),
|
||||
|
||||
// Player, princess and monsters
|
||||
PLAYER_UP("player_up.png"),
|
||||
PLAYER_RIGHT("player_right.png"),
|
||||
PLAYER_DOWN("player_down.png"),
|
||||
PLAYER_LEFT("player_left.png"),
|
||||
|
||||
MONSTER_UP("monster_up.png"),
|
||||
MONSTER_RIGHT("monster_right.png"),
|
||||
MONSTER_DOWN("monster_down.png"),
|
||||
MONSTER_LEFT("monster_left.png"),
|
||||
|
||||
PRINCESS("princess.png"),
|
||||
|
||||
// Bombs
|
||||
BOMB_0("bomb_0.png"),
|
||||
BOMB_1("bomb_1.png"),
|
||||
BOMB_2("bomb_2.png"),
|
||||
BOMB_3("bomb_3.png"),
|
||||
EXPLOSION("explosion.png"),
|
||||
|
||||
// Status bar
|
||||
BANNER_BOMB("banner_bomb.png"),
|
||||
BANNER_RANGE("banner_range.png"),
|
||||
|
||||
DIGIT_0("banner_0.jpg"),
|
||||
DIGIT_1("banner_1.jpg"),
|
||||
DIGIT_2("banner_2.jpg"),
|
||||
DIGIT_3("banner_3.jpg"),
|
||||
DIGIT_4("banner_4.jpg"),
|
||||
DIGIT_5("banner_5.jpg"),
|
||||
DIGIT_6("banner_6.jpg"),
|
||||
DIGIT_7("banner_7.jpg"),
|
||||
DIGIT_8("banner_8.jpg"),
|
||||
DIGIT_9("banner_9.jpg"),
|
||||
|
||||
|
||||
|
||||
|
||||
// For the editor
|
||||
DOOR_OPENED_MINUS("door_opened_minus.png"),
|
||||
DOOR_OPENED_PLUS("door_opened_plus.png"),
|
||||
DOOR_CLOSED_MINUS("door_closed_minus.png"),
|
||||
DOOR_CLOSED_PLUS("door_closed_plus.png");
|
||||
|
||||
|
||||
private final Image image;
|
||||
|
||||
public static final int size = 40;
|
||||
|
||||
ImageResource(String file) {
|
||||
try {
|
||||
this.image = new Image(ImageResource.class.getResourceAsStream("/images/" + file));
|
||||
if (image.getWidth() != size && image.getHeight() != size) {
|
||||
String msg = "File " + file + " does not have the correct size " + image.getWidth() + " x " + image.getHeight();
|
||||
throw new RuntimeException (msg);
|
||||
}
|
||||
} catch (NullPointerException e) {
|
||||
System.err.println("Resource not found : " + file);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
public Image getImage() {
|
||||
return image;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package fr.ubx.poo.ubomb.view;
|
||||
|
||||
import fr.ubx.poo.ubomb.game.Direction;
|
||||
import javafx.scene.image.Image;
|
||||
|
||||
public class ImageResourceFactory {
|
||||
public static ImageResource digit(int i) {
|
||||
if (i < 0 || i > 9) throw new IllegalArgumentException("Digit must be in [0-9]");
|
||||
return ImageResource.valueOf("DIGIT_" + i);
|
||||
}
|
||||
|
||||
public static ImageResource getPlayer(Direction direction) {
|
||||
return ImageResource.valueOf("PLAYER_" + direction);
|
||||
}
|
||||
|
||||
public static ImageResource getMonster(Direction direction) {
|
||||
return ImageResource.valueOf("MONSTER_" + direction);
|
||||
}
|
||||
|
||||
public static ImageResource getBomb(int i) {
|
||||
if (i < 0 || i > 3)
|
||||
throw new IllegalArgumentException();
|
||||
return ImageResource.valueOf("BOMB_"+i);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright (c) 2020. Laurent Réveillère
|
||||
*/
|
||||
|
||||
package fr.ubx.poo.ubomb.view;
|
||||
|
||||
import fr.ubx.poo.ubomb.game.Position;
|
||||
import fr.ubx.poo.ubomb.go.GameObject;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.Pane;
|
||||
|
||||
public class Sprite {
|
||||
|
||||
public static final int size = 40;
|
||||
private final Pane layer;
|
||||
private final GameObject gameObject;
|
||||
private ImageView imageView;
|
||||
private Image image;
|
||||
|
||||
public Sprite(Pane layer, Image image, GameObject gameObject) {
|
||||
this.layer = layer;
|
||||
this.image = image;
|
||||
this.gameObject = gameObject;
|
||||
}
|
||||
|
||||
public GameObject getGameObject() {
|
||||
return gameObject;
|
||||
}
|
||||
|
||||
public final void setImage(Image image) {
|
||||
if (this.image == null || this.image != image ) {
|
||||
this.image = image;
|
||||
}
|
||||
}
|
||||
|
||||
public void updateImage() {
|
||||
}
|
||||
|
||||
public Position getPosition() {
|
||||
return getGameObject().getPosition();
|
||||
}
|
||||
|
||||
public final void render() {
|
||||
if (gameObject.isModified()) {
|
||||
if (imageView != null) {
|
||||
remove();
|
||||
}
|
||||
updateImage();
|
||||
imageView = new ImageView(this.image);
|
||||
imageView.setX(getPosition().x() * size);
|
||||
imageView.setY(getPosition().y() * size);
|
||||
layer.getChildren().add(imageView);
|
||||
gameObject.setModified(false);
|
||||
}
|
||||
}
|
||||
|
||||
public final void remove() {
|
||||
layer.getChildren().remove(imageView);
|
||||
imageView = null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright (c) 2020. Laurent Réveillère
|
||||
*/
|
||||
|
||||
package fr.ubx.poo.ubomb.view;
|
||||
|
||||
import fr.ubx.poo.ubomb.go.GameObject;
|
||||
import fr.ubx.poo.ubomb.go.decor.bonus.*;
|
||||
import fr.ubx.poo.ubomb.go.decor.*;
|
||||
import javafx.scene.layout.Pane;
|
||||
|
||||
import static fr.ubx.poo.ubomb.view.ImageResource.*;
|
||||
|
||||
|
||||
public final class SpriteFactory {
|
||||
|
||||
public static Sprite create(Pane layer, GameObject gameObject) {
|
||||
if (gameObject instanceof Stone)
|
||||
return new Sprite(layer, STONE.getImage(), gameObject);
|
||||
if (gameObject instanceof Tree)
|
||||
return new Sprite(layer, TREE.getImage(), gameObject);
|
||||
if (gameObject instanceof Key)
|
||||
return new Sprite(layer, KEY.getImage(), gameObject);
|
||||
throw new RuntimeException("Unsupported sprite for decor " + gameObject);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (c) 2020. Laurent Réveillère
|
||||
*/
|
||||
|
||||
package fr.ubx.poo.ubomb.view;
|
||||
|
||||
import fr.ubx.poo.ubomb.go.character.Player;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.layout.Pane;
|
||||
|
||||
public class SpritePlayer extends Sprite {
|
||||
|
||||
public SpritePlayer(Pane layer, Player player) {
|
||||
super(layer, null, player);
|
||||
updateImage();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateImage() {
|
||||
Player player = (Player) getGameObject();
|
||||
Image image = ImageResourceFactory.getPlayer(player.getDirection()).getImage();
|
||||
setImage(image);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
.statusBar {
|
||||
-fx-border-color: black;
|
||||
-fx-border-style: solid;
|
||||
-fx-border-width: 5px 0 0 0;
|
||||
}
|
||||
|
||||
.level {
|
||||
-fx-padding: 5px 10px 5px 10px;
|
||||
-fx-border-color: black;
|
||||
-fx-border-style: solid;
|
||||
-fx-border-width: 0 5px 0 0;
|
||||
}
|
||||
|
||||
.status {
|
||||
-fx-padding: 5px 15px 5px 15px;
|
||||
}
|
||||
|
||||
.number {
|
||||
-fx-font-size: 28;
|
||||
-fx-font-weight: bold;
|
||||
}
|
||||
|
||||
.message {
|
||||
-fx-font-size: 60;
|
||||
-fx-font-width: bold;
|
||||
-fx-padding: 50px;
|
||||
-fx-font-family: Verdana;
|
||||
-fx-fill: brown;
|
||||
}
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 4.9 KiB |
|
After Width: | Height: | Size: 580 B |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 323 B |
|
After Width: | Height: | Size: 311 B |
|
After Width: | Height: | Size: 316 B |
|
After Width: | Height: | Size: 289 B |
|
After Width: | Height: | Size: 432 B |
|
After Width: | Height: | Size: 367 B |
|
After Width: | Height: | Size: 370 B |
|
After Width: | Height: | Size: 380 B |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 21 KiB |
@@ -0,0 +1,10 @@
|
||||
compression = true
|
||||
levels = 3
|
||||
level1 = _4B_9_x_T_9_4x_<__SBBS5_3x_T__SBBS_3S_3x_T__SH+S_3S_>_x_4S4_3S_3x_9__S_3x_5N_9xMT_5T3_5x_TT_T__T__B_4x__T3BTTHB<S_+_x_5<HTTK_S_3x__B__BBS4T_3x_9_4T_x_9_5Tx
|
||||
level2 = _7T+_3x_n_5TT_T_x_3B5_4x_SSBBHBBT3_x_3BH3B_4xSS_BBHBB__TTx__MB5_4x_S8_3x__S_3MSV_T_xS_3S_7x
|
||||
level3 = _9_5x__TMMS_S_6x_6VMM>_4x_T_3S_S_3H__x_3T_6TT--x_6T_T5MxBTB__>__TT_4xBHBT-T__TMMWMMxBTB_5TT_4x_M_6<M_4x
|
||||
playerLives = 30
|
||||
monsterVelocity = 5
|
||||
playerInvisibilityTime = 4000
|
||||
monsterInvisibilityTime = 10000
|
||||
player = 0x0
|
||||