Skip to content
Snippets Groups Projects
Commit 3dd68414 authored by Maxime BURON's avatar Maxime BURON
Browse files

premier commit

parents
No related branches found
No related tags found
No related merge requests found
Showing
with 1202 additions and 0 deletions
*.gradle
.classpath
.project
.settings
bin/
build/
#+TITLE: Projet : le jeu de la vie pour plusieurs éditeurs
* Le jeu de la vie
Lire la section "Règles" sur la page wiki: https://fr.wikipedia.org/wiki/Jeu_de_la_vie
Malgré sa simplicité le jeu de la vie est très puissant et permet d'"exécuter" tous les algorithmes que l'on connaît, on dit qu'il est Turing-complet :
- une vidéo de Science étonnante sur le sujet : https://www.youtube.com/watch?v=S-W0NX97DB0
- une vidéo où le jeu de la vie se simule lui-même : https://www.youtube.com/watch?v=xP5-iIeKXE8
** Exécution
#+BEGIN_src bash
./gradlew run
#+END_src
Rendez-vous sur http://localhost:8081/
* Objectifs
Le but de ce TP est de développer un éditeur coopératif pour le jeu de la vie. Il s'agira d'utiliser judicieusement un système de base de données (ici postgresql) pour que les accès et modifications concurrentes faits sur le jeu se fasse de manière cohérente.
Il faudra remplir les objectifs suivants :
** Configurer votre espace de travail
Il vous faut outil de développement en Java, mais surtout l'accès à un serveur postgresql. Deux choix s'offrent à vous :
1. programmer sur votre machine personnelle et installer posgresql dessus
2. installer postgresql sur votre machine virtuelle en suivant la section suivante "Configuration de Postgresql".
Pour configurer l'accès à la base de données, aller dans la classe "com.uca.dao._Connector" et modifier les valeurs suivantes, suivant votre choix précédent :
1. postgres sur votre machine personnelle :
- ~url~ devient "jdbc:postgresql://localhost/life"
- ~user~ devient "votre_login"
- ~passwd~ devient "votre_mot_de_passe_pg"
2. postgres sur votre machine virtuelle :
- ~url~ devient "jdbc:postgresql://vm-etu-votre_login.local.isima.fr/life"
- ~user~ devient "votre_login"
- ~passwd~ devient "votre_mot_de_passe_pg"
** Initialiser la base de donnée
Modifier le code de la classe "com.uca.dao._Initializer" pour initialiser une table dans le cas, où elle n'existe pas. On veut que cette table :
- représente une grille carré
- contienne trois colonnes (deux coordonnées entières et un état pour chaque cellule)
Il faut aussi choisir une clé primaire pour cette table.
Puis, il faut remplir la table avec un état initial. On ajoute des cellules mortes sur un carré finie (par exemple 100 cases de côté) à partir de la coordonnée (0, 0).
Remarque: Vous devez, dès que cela est pertinent, interagir avec la base de données en créant des transactions. Pour cela, vous devez utiliser les méthodes ~setAutoCommit~, ~commit~ et ~rollback~ de l'objet [[https://docs.oracle.com/javase/8/docs/api/java/sql/Connection.html][Connection]]. Chaque connection peut être vu comme une session des précédents TPs. N'oubliez pas non plus de choisir un niveau d'isolation des transactions avec la méthode ~setTransactionIsolation~.
** Rafraîchir la grille
Il s'agit de coder le fonctionnement du bouton "rafraichir" de l'interface Web, qui fait une requête GET sur le chemin ~/grid~. Observer la gestion du chemin dans la classe "com.uca.StartServer" et modifier le reste du projet pour retourner la grille stockée dans la base de données.
Pour tester, modifier des cellules pour les rendre vivante en utilisant ~psql~ et observer les depuis la page Web.
** Changer l'état d'une cellule
Il s'agit de coder le fonctionnement du clic sur la grille depuis l'interface Web, qui fait une requête PUT sur le chemin ~/grid/change~. On souhaite que l'état de la cellule cliquée soit inversé.
** Sauvegarder/annuler plusieurs changements d'état
Il s'agit de coder le fonctionnement du formulaire avec le bouton "sauvegarde" et "annuler", qui fait une requête POST sur le chemin ~/grid/save~ et ~/grid/cancel~. On souhaite que les modifications de chaque utilisateur (chaque onglet ouvert) se fasse de manière isolée.
Il faut ici porter une attention particulière à créer des transactions différentes pour chaque utilisateur. Pour cela, il faut :
- utiliser la méthode ~getSession~ de la classe ~StartServer~ pour identifier chaque utilisateur,
- créer/utiliser une différente [[https://docs.oracle.com/javase/8/docs/api/java/sql/Connection.html][Connexion]] pour chaque utilisateur.
** Importer un fichier RLE à partir d'un URL
Il s'agit de coder le fonctionnement du formulaire avec le bouton "importer", qui fait une requête PUT sur le chemin ~/grid/rle~. On souhaite que le fichier RLE soit lu et importer sur la grille. Des URL de fichiers RLE de différents objets du jeu de vie sont disponibles sur LifeWiki dans la boîte d'information sous "Pattern files". Par exemple, l'URL de la destruction de spaceship 60P5H2V0 mentionné dans la vidéo de Science étonnante est disponible sur cette [[https://conwaylife.com/wiki/60P5H2V0][page]] avec l'URL : https://www.conwaylife.com/patterns/60p5h2v0eaten.rle.
Pour cela, vous pouvez utiliser la méthode ~decodeRLEUrl~ dans la classe ~GridCore~.
** Réinitialiser l'état de la grille
Il s'agit de coder le fonctionnement du bouton "vider" depuis l'interface Web, qui fait une requête POST sur le chemin ~/grid/empty~.
** Calculer la génération suivante de la grille
Il s'agit de coder le fonctionnement du bouton "suivant", qui fait une requête POST sur le chemin ~/grid/next~. Le bouton "lecture", répète le fonctionnement de "suivant" à intervalle plus ou moins long en fonction de la "vitesse" choisie.
** Gérer des grilles infinies (bonus)
Il s'agit de changer la représentation de la grille pour qu'elle ne contienne pas de taille fixée à l'initialisation. Plusieurs solutions sont possibles.
* Configuration de Postgresql
Pour développer, il faudra ouvrir une connection avec un serveur postgres, qui sera installé sur votre machine virtuelle étudiante (celle nommée vm-etu-votre_login.local.isima.fr).
Vous utiliserez donc deux machines en parallèle la machine virtuelle avec le serveur postgresql et la machine sur laquelle vous développez le projet qui se connecte à postgresql.
** Installation de postgresql depuis votre machine virtuelle
#+BEGIN_src bash
sudo apt install postgresql
#+END_src
** Configurer votre utilisateur et votre base de données
Connectez vous au serveur postgres via le client ~psql~:
#+BEGIN_src bash
sudo su postgres
psql
#+END_src
Créer votre utilisateur avec votre login avec le mot de passe de votre choix:
#+BEGIN_src sql
CREATE USER votre_login WITH PASSWORD 'choissisez un mot de passe (pas votre mp UCA !!!!)';
#+END_src
Créer la base de donnée que vous allez utiliser durant le projet :
#+BEGIN_src sql
CREATE DATABASE life;
#+END_src
Accorder tous les droits à votre utilisateur sur cette base :
#+BEGIN_src sql
GRANT ALL PRIVILEGES ON DATABASE life TO votre_login;
#+END_src
Quitter le client ~psql~ avec la commande ~\q~. Et déconnectez vous du login postgres depuis bash.
Connectez-vous à votre base de données avec votre nouvel utilisateur :
#+BEGIN_src bash
psql -U votre_login -d life
#+END_src
** Configuration de postgres pour qu'il soit accessible depuis l'intranet de l'ISIMA
Changer dans le ficher de configuration ~/etc/postgresql/13/main/postgresql.conf~, ~listen_addresses = 'localhost'~ en ~listen_addresses = '*'~ pour que le serveur postgres soit accessible depuis l'intranet. Vous pouvez utiliser l'éditeur nano :
#+BEGIN_src bash
sudo nano /etc/postgresql/13/main/postgresql.conf
#+END_src
Ajouter la ligne suivante à la fin du fichier ~/etc/postgresql/13/main/pg_hba.conf~ pour l'identification avec mot de passe soit autorisée depuis l'intranet :
#+BEGIN_example
host all all 0.0.0.0/0 md5
#+END_example
Puis redémarrer le serveur postgres pour que vos modifications soit prises en compte:
#+BEGIN_src bash
sudo systemctl restart postgresql
#+END_src
Depuis votre machine de développement (celle avec le code et un IDE), essayez de vous connecter de vous connecter au serveur posgresql avec :
#+BEGIN_src bash
psql -h vm-etu-votre_login.local.isima.fr -U votre_login -d life -W
#+END_src
** Commandes utiles dans le client psql
Contrairement au client Oracle, l'auto commit est activé dans psql, ce qui signifie que :
- les transactions doivent explicitement débuter par le mot-clé ~BEGIN~
- les requêtes en dehors de transaction sont automatiquement validé à la fin de l'exécution, s'il n'y pas d'erreur.
- ~\q~ quitter
- ~\dt~ lister les tables de la base de données courante
- ~\c autre_base~ se connecter à une autre base de données
** Sources
- https://www.bigbinary.com/blog/configure-postgresql-to-allow-remote-connection
- https://www.ibm.com/docs/fr/urbancode-deploy/6.2.2?topic=configuration-configuring-postgresql-database-blueprint-design-server
File added
#Thu Feb 13 14:20:59 CET 2020
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip
gradlew 0 → 100755
#!/usr/bin/env sh
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$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=""
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# 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
;;
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" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=$(save "$@")
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@"
@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 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=
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
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 init
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
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
: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 %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="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!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
package com.uca;
import com.uca.dao._Initializer;
import com.uca.gui.*;
import com.uca.core.GridCore;
import com.uca.entity.CellEntity;
import com.google.gson.Gson;
import static spark.Spark.*;
import spark.*;
public class StartServer {
public static void main(String[] args) {
//Configuration de Spark
staticFiles.location("/static/");
port(8081);
// Création de la base de données, si besoin
_Initializer.Init();
/**
* Définition des routes
*/
// index de l'application
get("/", (req, res) -> {
return IndexGUI.getIndex();
});
// retourne l'état de la grille
get("/grid", (req, res) -> {
res.type("application/json");
return new Gson().toJson(GridCore.getGrid(getSession(req)));
});
// inverse l'état d'une cellule
put("/grid/change", (req, res) -> {
Gson gson = new Gson();
CellEntity selectedCell = (CellEntity) gson.fromJson(req.body(), CellEntity.class);
// TODO
return "";
});
// sauvegarde les modifications de la grille
post("/grid/save", (req, res) -> {
// TODO
return "";
});
// annule les modifications de la grille
post("/grid/cancel", (req, res) -> {
// TODO
return "";
});
// charge un fichier rle depuis un URL
put("/grid/rle", (req, res) -> {
String RLEUrl = req.body();
// TODO
return "";
});
// vide la grille
post("/grid/empty", (req, res) -> {
// TODO
return "";
});
// met à jour la grille en la remplaçant par la génération suivante
post("/grid/next", (req, res) -> {
// TODO
return "";
});
}
/**
* retourne le numéro de session
* il y a un numéro de session différent pour chaque onglet de navigateur
* ouvert sur l'application
*/
public static int getSession(Request req) {
return Integer.parseInt(req.queryParams("session"));
}
}
package com.uca.core;
import com.uca.dao.*;
import com.uca.entity.*;
import java.net.*;
import java.io.*;
import java.sql.*;
import java.util.*;
import java.util.regex.*;
public class GridCore {
public static List<CellEntity> getGrid(int session) throws SQLException {
return new GridEntity().getCells();
}
/**
* Décode le contenu d'un fichier RLE sous forme de cases à partir d'un URL
* @param url - url d'un fichier RLE, ex : https://www.conwaylife.com/patterns/glider.rle
*/
private static List<CellEntity> decodeRLEUrl(String url) throws Exception {
URL u = new URL(url);
BufferedReader in = new BufferedReader(
new InputStreamReader(u.openStream()));
StringBuffer sb = new StringBuffer();
String inputLine;
while ((inputLine = in.readLine()) != null) {
sb.append(inputLine);
System.out.println(inputLine);
sb.append("\n");
}
in.close();
return decodeRLE(sb.toString());
}
/**
* Décode le contenu d'un fichier RLE sous forme de cases
* @param rle - un chaîne représentant une serialisation RLE
*/
public static List<CellEntity> decodeRLE(String rle) {
List<CellEntity> cells = new ArrayList<>();
boolean ignore = false;
int step = 1;
int x = 50;
int y = 50;
String number;
Pattern pattern = Pattern.compile("^[0-9]+");
int i = -1;
while (i < rle.length() - 1) {
i++;
if (ignore) {
if (rle.charAt(i) == '\n') {
ignore = false;
}
continue;
}
switch (rle.charAt(i)) {
case '#':
case 'x':
case '!':
ignore = true;
continue;
case '$':
x = 50;
y += step;
step = 1;
continue;
case 'b':
x += step;
step = 1;
continue;
case 'o':
for (int j = 0; j < step; j++) {
CellEntity c = new CellEntity(x++, y);
System.out.println(c);
cells.add(c);
}
System.out.println(rle.substring(Math.max(0, rle.lastIndexOf("$",i))));
step = 1;
continue;
}
Matcher matcher = pattern.matcher(rle.substring(i));
if (matcher.find()) {
number = matcher.group();
step = Integer.parseInt(number);
i += number.length() - 1;
}
}
return cells;
}
}
package com.uca.dao;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.*;
public class _Connector {
private static String url = "";
private static String user = "";
private static String passwd = "";
private static Connection connect;
public static Connection getMainConnection(){
if(connect == null){
connect = getNewConnection();
}
return connect;
}
private static Connection getNewConnection() {
Connection c;
try {
c = DriverManager.getConnection(url, user, passwd);
} catch (SQLException e) {
System.err.println("Erreur en ouvrant une nouvelle connection.");
throw new RuntimeException(e);
}
return c;
}
}
package com.uca.dao;
import java.sql.*;
public class _Initializer {
// nom de la table contenant la grille
final static String TABLE = "plateau";
// taille de grille
final static int SIZE = 1000;
/**
* cette méthode permet d'initialiser en créant une table pour la grille si elle n'existe pas
*/
public static void Init(){
}
/**
* teste si une table existe dans la base de données
*/
private static boolean tableExists(Connection connection, String tableName) throws SQLException {
DatabaseMetaData meta = connection.getMetaData();
ResultSet resultSet = meta.getTables(null, null, tableName, new String[]{"TABLE"});
return resultSet.next();
}
}
package com.uca.entity;
import java.util.List;
import java.util.ArrayList;
/**
* Represente une cellule de coordonné x et y
*/
public class CellEntity {
private final int x;
private final int y;
public CellEntity(int x, int y) {
this.x = x;
this.y = y;
}
public int getX() {
return this.x;
}
public int getY() {
return this.y;
}
public String toString() {
return x+","+y;
}
}
package com.uca.entity;
import java.util.List;
import java.util.ArrayList;
/**
* Représente l'état de la grille
* Toutes les cellules de l'état de la grille sont vivantes
*/
public class GridEntity {
// état de la grille : liste des cellules vivantes
private final List<CellEntity> state;
public GridEntity() {
state = new ArrayList<>();
}
/**
* retourne la grille
*/
public List<CellEntity> getCells() {
return state;
}
/**
* ajoute une cellule vivante à la grille
*/
public void addCell(CellEntity c) {
state.add(c);
}
}
package com.uca.gui;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.util.HashMap;
import java.util.Map;
public class IndexGUI {
public static String getIndex() throws IOException, TemplateException {
Configuration configuration = _FreeMarkerInitializer.getContext();
Map<String, Object> input = new HashMap<>();
Writer output = new StringWriter();
Template template = configuration.getTemplate("index.ftl");
template.setOutputEncoding("UTF-8");
template.process(input, output);
return output.toString();
}
}
package com.uca.gui;
import com.uca.StartServer;
import freemarker.template.Configuration;
import freemarker.template.TemplateExceptionHandler;
import java.util.Locale;
public class _FreeMarkerInitializer {
public static Configuration getContext() {
//Configure FreeMarker
Configuration configuration = new Configuration(Configuration.VERSION_2_3_30);
configuration.setClassForTemplateLoading(StartServer.class, "/views");
configuration.setDefaultEncoding("UTF-8");
configuration.setLocale(Locale.FRANCE);
configuration.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
return configuration;
}
}
window.onload = function() {
// init session
const loading_img = document.getElementById('loading-img');
loading_img.style.opacity = 0;
sessionStorage.setItem("session", sessionStorage.getItem('session') || Math.floor(100000* Math.random()));
const fetchUrl = function(path, option) {
loading_img.style.opacity = 1;
return fetch(path + '?session=' + sessionStorage.getItem('session'), option)
.then(res => {
loading_img.style.opacity = 0;
if (res.status === 200)
return res;
else
return Promise.reject();
})
}
const my_canvas = document.getElementById('mon_canvas');
const canvas = new Canvas(my_canvas,8);
canvas.setWidth(window.innerWidth);
canvas.setHeight(window.innerHeight-50);
const plateau = new Plateau();
let refreshing = null;
let playing = false;
const refreshPlateau = function() {
if (refreshing)
return refreshing
refreshing = fetchUrl('/grid').then(res => {
res.json().then(json => {
plateau.data = json;
canvas.draw(plateau);
refreshing = null;
})
})
return refreshing
}
// empty input
const empty_input = document.getElementById('empty-input');
empty_input.addEventListener('click', () => {
fetchUrl('/grid/empty', {method: 'post'})
.then(() => refreshPlateau())
});
// refresh input
const refresh_input = document.getElementById('refresh-input');
refresh_input.addEventListener('click', refreshPlateau);
refreshPlateau()
// next input
const next_input = document.getElementById('next-input');
const nextPlateau = function() {
return fetchUrl('/grid/next', {method: 'post'})
.then(() => refreshPlateau())
}
next_input.addEventListener('click', nextPlateau, false);
// play input
const play_input = document.getElementById('play-input');
const speed_input = document.getElementById('speed-input');
const start_play = function() {
if (playing) {
const start = new Date();
nextPlateau().then(() => {
const end = new Date();
const waiting_time = Math.max((10000 / parseInt(speed_input.value)) - (end - start), 0);
setTimeout(start_play, waiting_time);
})
}
}
play_input.addEventListener('click', () => {
playing = !playing;
next_input.disabled = playing;
play_input.value = (playing) ? 'stop' : 'lecture';
start_play();
})
// add
const addOrRemove = function(x,y) {
return fetchUrl('/grid/change', {'method': 'put', 'body': JSON.stringify({'x':x, 'y':y})})
}
const ajout_cellule = function(e){
if(!canvas.wasdrag){
var x = Math.floor((e.clientX-my_canvas.getBoundingClientRect().left + canvas.origine[0])/(2*canvas.rayon)),
y = Math.floor((e.clientY-my_canvas.getBoundingClientRect().top + canvas.origine[1])/(2*canvas.rayon));
addOrRemove(x,y).then(refreshPlateau)
}
canvas.wasdrag = (canvas.wasdrag)? false : false;
}
my_canvas.addEventListener('click',ajout_cellule,false);
// save
const save_input = document.getElementById('save-input')
save_input.addEventListener('click', () => {
fetchUrl('/grid/save', {method: 'post'})
.then(refreshPlateau)
})
// cancel
const cancel_input = document.getElementById('cancel-input')
cancel_input.addEventListener('click', () => {
fetchUrl('/grid/cancel', {method: 'post'})
.then(refreshPlateau)
})
// import
const rle_input = document.getElementById('rle-input');
const import_input = document.getElementById('import-input');
const importRLE = function(url) {
return fetchUrl('/grid/rle', {method: 'put', body: url})
.then(() => refreshPlateau())
}
import_input.addEventListener('click', () => {
importRLE(rle_input.value);
}, false);
// zoom et dezoom
my_canvas.addEventListener('mousewheel',mouseWheel,false);
my_canvas.addEventListener('DOMMouseScroll',mouseWheel,false);
function mouseWheel(e) {
// sens du scroll
var delta = Math.max(-1, Math.min(1, (e.wheelDelta || -e.detail)));
var posx = e.clientX-my_canvas.getBoundingClientRect().left;
var posy = e.clientY-my_canvas.getBoundingClientRect().top;
canvas.zoom(delta,posx,posy);
canvas.draw(plateau);
}
//deplacement
let decompte;
my_canvas.addEventListener('mouseout',function(){
//si on sort la souris du canvas pendant un déplacement on l'arrete
canvas.drag = false;
},false);
my_canvas.addEventListener('mousedown',function(e){
//on attend 10ms avant de lancer le déplacement
decompte = setTimeout(function() {canvas.drag = [e.clientX,e.clientY];},100);
},false);
my_canvas.addEventListener('mouseup',function(){
//si le décompte est encore en route alors qu'on lance la souris on le coupe
clearTimeout(decompte);
canvas.drag = false;
canvas.draw(plateau);
}, false);
my_canvas.addEventListener('mousemove', function(e){
if(canvas.drag){
if(canvas.wasdrag){
//signifie que ce n'est pas le premier mouvement ie canvas.drag est un tableau
canvas.origine[0] -= e.clientX - canvas.drag[0];
canvas.origine[1] -= e.clientY - canvas.drag[1];
canvas.wasdrag++;
//on trace le plateau 1 fois sur 10
if(!(canvas.wasdrag%10)){canvas.draw(plateau);}
} else {
//on previent que le prochain evenement click et du au deplacement
canvas.wasdrag = 1;
}
canvas.drag = [e.clientX,e.clientY];
}
},true);
window.addEventListener('resize',function(){
canvas.setWidth(document.getElementById('canvas').offsetWidth);
canvas.setHeight(document.getElementById('canvas').offsetHeight)
canvas.draw(plateau);
},false);
}
function Plateau() {
this.data = [];
this.nombre = 0;
this.generation = 0;
this.empty = function() {
this.data = [];
this.nombre = 0;
this.generation = 0;
}
}
function Canvas(canvas, r){
this.canvas = canvas;
this.context = this.canvas.getContext('2d');
this.rayon = r;
this.drag = false;
this.wasdrag = false;
this.mode = false; // signifie que le jeu n'est pas en marche
this.origine = [-1,-1]; // position du cote haut gauche de l'affichage variable suivant le rayon sur le plan infini
this.minRayon = 0.5
this.zoom = function(d,x,y) {
//zoom ou dezoom en laissant x,y à la meme position
if((this.rayon<10 && this.rayon > this.minRayon) ||
(this.rayon >= 10 && d<0) ||
(this.rayon<= this.minRayon && d>0)){
this.origine[0] = ((this.rayon + d/2)/this.rayon)*(this.origine[0] + x) - x;
this.origine[1] = ((this.rayon + d/2 ) /this.rayon)*(this.origine[1] + y) - y;
if(d > 0) { this.rayon += 0.5 }
else {this.rayon -= 0.5 }
}
}
this.getWidth = function() {
return this.canvas.width;
}
this.setWidth = function(w) {
this.canvas.width = w;
}
this.getHeight = function() {
return this.canvas.height;
}
this.setHeight = function(h) {
this.canvas.height = h;
}
this.draw = function(p) {
// on nettoie le canevas
this.context.clearRect(0,0,this.getWidth(),this.getHeight());
for(i=0;i < p.data.length; i++) {
if(2*this.rayon*(p.data[i].x+1) > this.origine[0] &&
2*this.rayon*p.data[i].x < this.origine[0] + this.getWidth() &&
2*this.rayon*(p.data[i].y+1) > this.origine[1] &&
2*this.rayon*p.data[i].y < this.origine[1] + this.getHeight() ) {
this.context.beginPath();
this.context.arc(this.rayon*(2*p.data[i].x+1) - this.origine[0],
this.rayon*(2*p.data[i].y+1) - this.origine[1],
this.rayon,0,2*Math.PI);
this.context.fillStyle = "#E1170D";
this.context.fill();
this.context.closePath();
}
}
if(!this.mode && this.rayon > 5){
//on cherche les premieres coord
this.context.strokeStyle ='#A2967D';
this.context.fillStyle ='#A2967D';
this.context.font = '8px sans-serif';
var x = - (this.origine[0] %(2*this.rayon)),
y = - (this.origine[1] %(2*this.rayon)),
cx = Math.floor(this.origine[0] / (2*this.rayon)),
cy = Math.floor(this.origine[1] / (2*this.rayon));
cx = (x > 0) ? cx +1 : cx;
cy = (y > 0) ? cy +1 : cy;
while(x < this.getWidth())
{
if (x > 2 * this.rayon) {
this.context.beginPath();
this.context.moveTo(x, 2 * this.rayon);
this.context.lineTo(x, this.getHeight());
this.context.stroke();
this.context.closePath();
}
this.context.textAlign = 'center';
this.context.fillText(cx, x + this.rayon, 2*this.rayon - 2);
x+= 2*this.rayon;
cx++;
}
while(y < this.getHeight())
{
if (y > 2 * this.rayon) {
this.context.beginPath();
this.context.moveTo(2 * this.rayon, y);
this.context.lineTo(this.getWidth(),y);
this.context.stroke();
this.context.closePath();
}
this.context.textAlign = 'right';
this.context.fillText(cy, 2*this.rayon - 2, y + 1.5 * this.rayon);
y+= 2*this.rayon;
cy++;
}
}
}
}
<svg version="1.1"
class="svg-loader"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
x="0px"
y="0px"
viewBox="0 0 80 80"
xml:space="preserve"
>
<path
fill="#D43B11"
d="M10,40c0,0,0-0.4,0-1.1c0-0.3,0-0.8,0-1.3c0-0.3,0-0.5,0-0.8c0-0.3,0.1-0.6,0.1-0.9c0.1-0.6,0.1-1.4,0.2-2.1
c0.2-0.8,0.3-1.6,0.5-2.5c0.2-0.9,0.6-1.8,0.8-2.8c0.3-1,0.8-1.9,1.2-3c0.5-1,1.1-2,1.7-3.1c0.7-1,1.4-2.1,2.2-3.1
c1.6-2.1,3.7-3.9,6-5.6c2.3-1.7,5-3,7.9-4.1c0.7-0.2,1.5-0.4,2.2-0.7c0.7-0.3,1.5-0.3,2.3-0.5c0.8-0.2,1.5-0.3,2.3-0.4l1.2-0.1
l0.6-0.1l0.3,0l0.1,0l0.1,0l0,0c0.1,0-0.1,0,0.1,0c1.5,0,2.9-0.1,4.5,0.2c0.8,0.1,1.6,0.1,2.4,0.3c0.8,0.2,1.5,0.3,2.3,0.5
c3,0.8,5.9,2,8.5,3.6c2.6,1.6,4.9,3.4,6.8,5.4c1,1,1.8,2.1,2.7,3.1c0.8,1.1,1.5,2.1,2.1,3.2c0.6,1.1,1.2,2.1,1.6,3.1
c0.4,1,0.9,2,1.2,3c0.3,1,0.6,1.9,0.8,2.7c0.2,0.9,0.3,1.6,0.5,2.4c0.1,0.4,0.1,0.7,0.2,1c0,0.3,0.1,0.6,0.1,0.9
c0.1,0.6,0.1,1,0.1,1.4C74,39.6,74,40,74,40c0.2,2.2-1.5,4.1-3.7,4.3s-4.1-1.5-4.3-3.7c0-0.1,0-0.2,0-0.3l0-0.4c0,0,0-0.3,0-0.9
c0-0.3,0-0.7,0-1.1c0-0.2,0-0.5,0-0.7c0-0.2-0.1-0.5-0.1-0.8c-0.1-0.6-0.1-1.2-0.2-1.9c-0.1-0.7-0.3-1.4-0.4-2.2
c-0.2-0.8-0.5-1.6-0.7-2.4c-0.3-0.8-0.7-1.7-1.1-2.6c-0.5-0.9-0.9-1.8-1.5-2.7c-0.6-0.9-1.2-1.8-1.9-2.7c-1.4-1.8-3.2-3.4-5.2-4.9
c-2-1.5-4.4-2.7-6.9-3.6c-0.6-0.2-1.3-0.4-1.9-0.6c-0.7-0.2-1.3-0.3-1.9-0.4c-1.2-0.3-2.8-0.4-4.2-0.5l-2,0c-0.7,0-1.4,0.1-2.1,0.1
c-0.7,0.1-1.4,0.1-2,0.3c-0.7,0.1-1.3,0.3-2,0.4c-2.6,0.7-5.2,1.7-7.5,3.1c-2.2,1.4-4.3,2.9-6,4.7c-0.9,0.8-1.6,1.8-2.4,2.7
c-0.7,0.9-1.3,1.9-1.9,2.8c-0.5,1-1,1.9-1.4,2.8c-0.4,0.9-0.8,1.8-1,2.6c-0.3,0.9-0.5,1.6-0.7,2.4c-0.2,0.7-0.3,1.4-0.4,2.1
c-0.1,0.3-0.1,0.6-0.2,0.9c0,0.3-0.1,0.6-0.1,0.8c0,0.5-0.1,0.9-0.1,1.3C10,39.6,10,40,10,40z"
>
<animateTransform
attributeType="xml"
attributeName="transform"
type="rotate"
from="0 40 40"
to="360 40 40"
dur="0.8s"
repeatCount="indefinite"
/>
</path>
<path
fill="#D43B11"
d="M62,40.1c0,0,0,0.2-0.1,0.7c0,0.2,0,0.5-0.1,0.8c0,0.2,0,0.3,0,0.5c0,0.2-0.1,0.4-0.1,0.7
c-0.1,0.5-0.2,1-0.3,1.6c-0.2,0.5-0.3,1.1-0.5,1.8c-0.2,0.6-0.5,1.3-0.7,1.9c-0.3,0.7-0.7,1.3-1,2.1c-0.4,0.7-0.9,1.4-1.4,2.1
c-0.5,0.7-1.1,1.4-1.7,2c-1.2,1.3-2.7,2.5-4.4,3.6c-1.7,1-3.6,1.8-5.5,2.4c-2,0.5-4,0.7-6.2,0.7c-1.9-0.1-4.1-0.4-6-1.1
c-1.9-0.7-3.7-1.5-5.2-2.6c-1.5-1.1-2.9-2.3-4-3.7c-0.6-0.6-1-1.4-1.5-2c-0.4-0.7-0.8-1.4-1.2-2c-0.3-0.7-0.6-1.3-0.8-2
c-0.2-0.6-0.4-1.2-0.6-1.8c-0.1-0.6-0.3-1.1-0.4-1.6c-0.1-0.5-0.1-1-0.2-1.4c-0.1-0.9-0.1-1.5-0.1-2c0-0.5,0-0.7,0-0.7
s0,0.2,0.1,0.7c0.1,0.5,0,1.1,0.2,2c0.1,0.4,0.2,0.9,0.3,1.4c0.1,0.5,0.3,1,0.5,1.6c0.2,0.6,0.4,1.1,0.7,1.8
c0.3,0.6,0.6,1.2,0.9,1.9c0.4,0.6,0.8,1.3,1.2,1.9c0.5,0.6,1,1.3,1.6,1.8c1.1,1.2,2.5,2.3,4,3.2c1.5,0.9,3.2,1.6,5,2.1
c1.8,0.5,3.6,0.6,5.6,0.6c1.8-0.1,3.7-0.4,5.4-1c1.7-0.6,3.3-1.4,4.7-2.4c1.4-1,2.6-2.1,3.6-3.3c0.5-0.6,0.9-1.2,1.3-1.8
c0.4-0.6,0.7-1.2,1-1.8c0.3-0.6,0.6-1.2,0.8-1.8c0.2-0.6,0.4-1.1,0.5-1.7c0.1-0.5,0.2-1,0.3-1.5c0.1-0.4,0.1-0.8,0.1-1.2
c0-0.2,0-0.4,0.1-0.5c0-0.2,0-0.4,0-0.5c0-0.3,0-0.6,0-0.8c0-0.5,0-0.7,0-0.7c0-1.1,0.9-2,2-2s2,0.9,2,2C62,40,62,40.1,62,40.1z"
>
<animateTransform
attributeType="xml"
attributeName="transform"
type="rotate"
from="0 40 40"
to="-360 40 40"
dur="0.6s"
repeatCount="indefinite"
/>
</path>
</svg>
\ No newline at end of file
#loading-img {
width: 1em;
}
.box {
display: inline-block;
border: 1px black solid;
padding: .2em;
vertical-align: middle;
line-height: normal;
/*! height: .1; */
}
\ No newline at end of file
<#ftl encoding="utf-8">
<head>
<title>Jeu de la vie</title>
<link rel="stylesheet" href="style.css"/>
<script src="life.js"></script>
<meta charset="utf-8" />
</head>
<body xmlns="http://www.w3.org/1999/html">
<div>
<img id="loading-img" src="./loading.svg" />
<input id="empty-input" type="button" value="vider" />
<input id="refresh-input" type="button" value="rafraichir"/>
<div class="box">
<input id="cancel-input" type="button" value="annuler" />
<input id="save-input" type="button" value="sauvegarder" />
</div>
<div class="box">
<label for="speed-input">Vitesse: </label>
<input id="speed-input" type="range" value="1" min="1" max="50" />
<input id="play-input" type="button" value="lecture" />
<input id="next-input" type="button" value="suivant"/>
</div>
<div class="box">
<label for="rle-input">RLE: </label>
<input id="rle-input" type="url" placeholder="url" />
<input id="import-input" type="button" value="importer" />
</div>
</div>
<div id="canvas">
<canvas id="mon_canvas" >
Message pour les navigateurs ne supportant pas encore canvas.
</canvas>
</div>
</body>
</html>
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please to comment