inital commit

This commit is contained in:
nessar committed 2022-11-10 14:16:27 +01:00
1 parent 8900f81765
commit 0692ed1fd9
86 files changed
+1925 -58

No files matched your search

+178 -58
View File
@@ -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.
![Bombeirb](img/ubomb.png)
## 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
--- | --- |
![nb+](src/main/resources/images/bonus_bomb_nb_inc.png) | Augmente la capacité du sac de bombes d’une unité. |
![nb-](src/main/resources/images/bonus_bomb_nb_dec.png) | Diminue la capacité du sac de bombes d’une unité. Le sac contient toujours au minimum une bombe. |
| ![range+](src/main/resources/images/bonus_bomb_range_inc.png) | Augmente la portée des bombes d’une unité. La modification de portée n'affecte pas les bombes déjà posées. |
| ![range-](src/main/resources/images/bonus_bomb_range_dec.png) | 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. |
| ![live](src/main/resources/images/heart.png) | 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.
+29
View File
@@ -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']
}
Binary file not shown.
+5
View File
@@ -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
Vendored Executable
+240
View File
@@ -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" "$@"
Vendored
+91
View File
@@ -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
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 508 KiB

+1
View File
@@ -0,0 +1 @@
rootProject.name = "UBomb"
+21
View File
@@ -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);
}
}
+29
View File
@@ -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;
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 580 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 323 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 311 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 316 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 289 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 432 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 367 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 370 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 380 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

+10
View File
@@ -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