diff --git a/code/PtiClic/AndroidManifest.xml b/code/PtiClic/AndroidManifest.xml index b81538f..dd8018f 100644 --- a/code/PtiClic/AndroidManifest.xml +++ b/code/PtiClic/AndroidManifest.xml @@ -19,4 +19,6 @@ + + diff --git a/code/PtiClic/src/org/pticlic/FrontPage.java b/code/PtiClic/src/org/pticlic/FrontPage.java index 5485779..5cf39ff 100644 --- a/code/PtiClic/src/org/pticlic/FrontPage.java +++ b/code/PtiClic/src/org/pticlic/FrontPage.java @@ -1,8 +1,11 @@ package org.pticlic; import org.pticlic.games.BaseGame; +import org.pticlic.model.Network; import android.app.Activity; +import android.app.AlertDialog; +import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; import android.os.Bundle; @@ -19,17 +22,23 @@ public class FrontPage extends Activity implements OnClickListener{ public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.frontpage); - + // Écoute des clics sur les différents boutons ((ImageView)findViewById(R.id.prefs)).setOnClickListener(this); ((ImageView)findViewById(R.id.play)).setOnClickListener(this); ((ImageView)findViewById(R.id.infoButton)).setOnClickListener(this); + } - + @Override protected void onStart() { super.onStart(); - + + if (Network.isConnected(this)) + System.out.println("Connecter"); + else + System.out.println("Non Connecter"); + // On récupère le nom du joueur des préférences. SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this); String loginPref = sp.getString("login", "joueur"); @@ -44,14 +53,34 @@ public class FrontPage extends Activity implements OnClickListener{ public void onClick(View v) { switch (v.getId()) { case (R.id.prefs) : startActivity(new Intent(this, Preference.class)); break; - case (R.id.play) : startActivity(new Intent(this, BaseGame.class)); break; + case (R.id.play) : checkNetworkConnection(BaseGame.class); break; case (R.id.infoButton) : startActivity(new Intent(this, Information.class)); break; } } - + + @SuppressWarnings("rawtypes") + private void checkNetworkConnection(Class c) { + if (Network.isConnected(this)) { + startActivity(new Intent(this, c)); + } else { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(getString(R.string.app_name)) + .setIcon(android.R.drawable.ic_dialog_alert) + .setMessage("Problème de connexion au serveur. Vérifiez que vous êtes connecté au réseau.") + .setCancelable(false) + .setNegativeButton("Ok", new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + dialog.cancel(); + } + }); + AlertDialog alert = builder.create(); + alert.show(); + } + } + @Override public void onBackPressed() { System.exit(0); } - + } diff --git a/code/PtiClic/src/org/pticlic/games/BaseGame.java b/code/PtiClic/src/org/pticlic/games/BaseGame.java index 61e7a57..13a3284 100644 --- a/code/PtiClic/src/org/pticlic/games/BaseGame.java +++ b/code/PtiClic/src/org/pticlic/games/BaseGame.java @@ -10,6 +10,8 @@ import org.pticlic.model.Network.Mode; import org.pticlic.model.Relation; import android.app.Activity; +import android.app.AlertDialog; +import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; import android.os.Bundle; @@ -39,12 +41,14 @@ import android.widget.TextView; * proposer celle qui lui semble le mieux approprier. * */ + public class BaseGame extends Activity implements OnClickListener, AnimationListener { private int currentWord = 0; private TextView currentWordTextView; private int nbWord = 0; private DownloadedGame game; private Match match; + private Network network; /** Called when the activity is first created. */ @Override @@ -60,6 +64,7 @@ public class BaseGame extends Activity implements OnClickListener, AnimationList // On initialise la classe permettant la communication avec le serveur. Network network = new Network(serverURL, Mode.SIMPLE_GAME, id, passwd); + game = network.getGames(1); int nbrel = game.getNbRelation(); nbWord = game.getNbWord(); @@ -68,14 +73,14 @@ public class BaseGame extends Activity implements OnClickListener, AnimationList match = new Match(); match.setGame(game); - Relation r = Relation.getInstance(); - // Boutons des relations ImageView r1 = ((ImageView)findViewById(R.id.relation1)); ImageView r2 = ((ImageView)findViewById(R.id.relation2)); ImageView r3 = ((ImageView)findViewById(R.id.relation3)); ImageView r4 = ((ImageView)findViewById(R.id.relation4)); - + + Relation r = Relation.getInstance(); + // TODO : Pour l'instant la poubelle ne fait rien. Il faudra certainement la ranger dans un categorie dans GamePlayed pour calculer le score. ImageView trash = ((ImageView)findViewById(R.id.trash)); trash.setOnClickListener(this); @@ -120,7 +125,7 @@ public class BaseGame extends Activity implements OnClickListener, AnimationList //On recupere le centre de mainWord pour l'animation de translation. TextView mainWord = (TextView)findViewById(R.id.mainWord); currentWordTextView = (TextView)findViewById(R.id.currentWord); - + // On defini un ensemble d'animation AnimationSet set = new AnimationSet(true); set.setDuration(1000); @@ -187,18 +192,18 @@ public class BaseGame extends Activity implements OnClickListener, AnimationList @Override public void onAnimationEnd(Animation animation) { - + } @Override public void onAnimationRepeat(Animation animation) { // TODO Auto-generated method stub - + } @Override public void onAnimationStart(Animation animation) { // TODO Auto-generated method stub - + } } \ No newline at end of file diff --git a/code/PtiClic/src/org/pticlic/model/Network.java b/code/PtiClic/src/org/pticlic/model/Network.java index 72d3626..4deb98d 100644 --- a/code/PtiClic/src/org/pticlic/model/Network.java +++ b/code/PtiClic/src/org/pticlic/model/Network.java @@ -1,10 +1,18 @@ package org.pticlic.model; +import java.io.BufferedReader; import java.io.IOException; +import java.io.InputStream; import java.io.InputStreamReader; +import java.net.MalformedURLException; import java.net.URL; import java.net.URLConnection; +import android.content.Context; +import android.content.SharedPreferences; +import android.net.ConnectivityManager; +import android.preference.PreferenceManager; + import com.google.gson.Gson; import com.google.gson.stream.JsonReader; @@ -54,6 +62,55 @@ public class Network { this.passwd = passwd; } + /** + * Permet de savoir si l'application a access a internet ou non + * + * @param context l'activite permettant de tester l'access a internet + * @return true si on a access a internet false sinon + */ + public static boolean isConnected(Context context) { + ConnectivityManager cm = (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE); + if (cm != null && (cm.getActiveNetworkInfo() == null + || !cm.getActiveNetworkInfo().isConnected())) { + return false; + } + return true; + } + + /** + * Permet de verifier que la combinaison login/mdp est correct + * + * @param context l'activite permettant de tester l'access a internet + * @param id l'identifiant de l'utilisateur + * @param passwd le mot de passe de l'utilisateur + * @return true si la combinaison login/mdp est correct false sinon + */ + public static boolean isLoginCorrect(Context context, String id, String passwd) { + SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); + String serverURL = sp.getString(Constant.SERVER_URL, "http://dumbs.fr/~bbrun/pticlic.json"); + + URL url; + boolean res = false; + try { + url = new URL(serverURL); + URLConnection connection = url.openConnection(); + connection.addRequestProperty("action", "verifyAccess"); + connection.addRequestProperty("user", id); + connection.addRequestProperty("passwd", passwd); + + InputStream in = connection.getInputStream(); + BufferedReader buf = new BufferedReader(new InputStreamReader(in)); + res = Boolean.getBoolean(buf.readLine()); + + } catch (MalformedURLException e) { + return false; + } catch (IOException e) { + return false; + } + + return res; + } + /** * Cette méthode permet de récupérer du serveur un certain nombre de parties. * @param nbGames Le nombre de parties que l'on veut récupérer. @@ -171,12 +228,12 @@ public class Network { for (Integer i : game.getTrash()) { connection.addRequestProperty("trash[]", i.toString()); } - + Gson gson = new Gson(); JsonReader reader = new JsonReader(new InputStreamReader(connection.getInputStream(), "UTF-8")); score = gson.fromJson(reader, TotalScore.class); - + } catch (IOException e) { return score; diff --git a/code/serveur/dump2sqlite.sh b/code/serveur/dump2sqlite.sh index 728da31..b450e02 100755 --- a/code/serveur/dump2sqlite.sh +++ b/code/serveur/dump2sqlite.sh @@ -15,7 +15,7 @@ create table node(eid integer primary key autoincrement, name, type, weight); create table relation(rid integer primary key autoincrement, start, end, type, weight); create table type_node(name, num); create table type_relation(name, num, extended_name, info); -create table user(login primary key, mail, hash_passwd); +create table user(login primary key, mail, hash_passwd, score); create table game(gid integer primary key autoincrement, eid_central_word, relation_1, relation_2, difficulty); create table game_cloud(gid, num, difficulty, eid_word, totalWeight, probaR1, probaR2, probaR0, probaTrash); create table played_game(pgid integer primary key autoincrement, gid, login); diff --git a/code/serveur/php/pticlic.php b/code/serveur/php/pticlic.php index e46be96..10a2152 100644 --- a/code/serveur/php/pticlic.php +++ b/code/serveur/php/pticlic.php @@ -12,16 +12,16 @@ function mDie($err,$msg) exit; } -if (!$db = new SQLite3('db')) { +if (!$db = new SQlite3($SQL_DBNAME)) { mDie(1,"Erreur lors de l'ouverture de la base de données SQLite3"); } function initdb() { global $db; - $db->exec("insert into user(login, mail, hash_passwd) values('foo', 'foo@isp.com', '".md5('bar')."');"); + $db->exec("insert into user(login, mail, hash_passwd, score) values('foo', 'foo@isp.com', '".md5('bar')."', 0);"); } -if ($do_initdb) initdb(); +if ($do_initdb) { initdb(); } if(!isset($_GET['action']) || !isset($_GET['user']) || !isset($_GET['passwd'])) mDie(2,"La requête est incomplète"); @@ -179,8 +179,6 @@ function create_game($cloudSize) { cg_insert($centerEid, $cloud, $r1, $r2, $totalDifficulty); } -//create_game(10); - function random_game() { global $db; return $db->querySingle("select gid from game where gid = (abs(random()) % (select max(gid) from game))+1 or gid = (select max(gid) from game where gid > 0) order by gid limit 1;"); @@ -196,15 +194,26 @@ function game2json($game_id) { echo "cloudsize:10,cloud:["; // TODO ! compter dynamiquement. $res = $db->query("select eid_word,(select name from node where eid=eid_word) as name_word from game_cloud where gid = ".$game['gid'].";"); + $notfirst = false; while ($x = $res->fetchArray()) { - echo "{id:".$x['eid_word'].",name:".$x['name_word']."}\n"; + if ($notfirst) { echo ","; } else { $notfirst=true; } + echo "{id:".$x['eid_word'].",name:".$x['name_word']."}"; } echo "]}"; } -function main() { +function main($action) { // Sinon tout est bon on effectu l'opération correspondant à la commande passée. - if($action == 0) { // "Get partie" + // TODO : en production, utiliser : header("Content-Type: application/json; charset=utf-8"); + header("Content-Type: text/plain; charset=utf-8"); + if ($action == 2) { // "Create partie" + if(!isset($_GET['nb']) || !isset($_GET['mode'])) + mDie(2,"La requête est incomplète"); + $nbParties = intval($_GET['nb']); + for ($i = 0; $i < $nbParties; $i++) { + create_game(10); + } + } else if ($action == 0) { // "Get partie" if(!isset($_GET['nb']) || !isset($_GET['mode'])) mDie(2,"La requête est incomplète"); $nbGames = intval($_GET['nb']); @@ -220,16 +229,26 @@ function main() { } else if($action == 1) { // "Set partie" // Requête sql d'ajout d'informations (et calcul de résultat). // TODO : nettoyer, finir - $gid = $_GET['gid']; // TODO : vérifier qu'on a bien distribué cette partie à cet utilisateur, et qu'il n'y a pas déjà répondu (répercuter ça sur le random_game). - $userReputation = 5; // TODO : un nombre entre 0 et 5 environ. Par ex. log(pointsUtilisateur) est un bon choix. + + $gid = $_GET['gid']; + + if(ĝid != $db->querySingle("SELECT gid FROM played_game WHERE login='".$user."'")) + mdie(3,"Cette partie n'est associée à votre nom d'utilisateur"); + + $userReputation = log($db->querySingle("SELECT score FROM user WHERE login='".$user."'")); + $db->exec("begin transaction;"); - $db->exec("insert into played_game(pgid, gid, login) values (null, $gid, null);"); + $db->exec("INSERT INTO played_game(pgid, gid, login) VALUES (null, $gid, null);"); $pgid = $db->lastInsertRowID(); - for ($i=0; $i < 10; $i++) { + + for($i=0; $i < 10; $i++) + { $x = $_GET['$i']; + // TODO : calculer le score. Score = proba[réponse de l'utilisateur]*coeff - proba[autres reponses]*coeff // TODO : adapter le score en fonction de la réputation de l'utilisateur (plus quand il est jeune, pour le motiver, par ex. avec un terme constant qu'on ajoute). $score = 1; + $db->exec("insert into played_game_cloud(pgid, gid, type, num, relation, weight, score) values($pgid, $gid, 1, ".$c['pos'].", $r1, ".($x*$userReputation).", ".$score.");"); // TODO : game_cloud(probaR_x_) += $réputationJoueur * $coeff // TODO : game_cloud(totalWeight) += $réputationJoueur * $coeff (NOTE : même coeff que pour game_cloud(probaR_x_)) @@ -241,5 +260,7 @@ function main() { die("Commande inconnue"); } } - + +main($action); + ?> diff --git a/organisation/notes b/organisation/notes index ed2661f..19e53d9 100644 --- a/organisation/notes +++ b/organisation/notes @@ -1,3 +1,24 @@ - Une classe Constante pour toutes les constantes ("symboles" pour les paramètres, ...). - Boutons pour les différents modes de jeu directement sur la "page de garde". - Icônes : http://developer.android.com/guide/practices/ui_guidelines/icon_design.html + +NOTES SUITE A LA REUNION +- addiction -> IMPORTANT ... teasing socialisation +- choix de la thématique : cadeau thématique +- game with a purpose GWAP +- espi +- liberman (sp?) +- indexation des images Google +- code pour Google.com code.google.com/intl/fr~FR/apis/chart/index.html +- qrcode +- lire sur site pour installer appli +- bêta testeurs externes +- intuitivité +- réflexion : intuitivité - prototypes - +- bouton aide -> affiche les icônes en vertical + texte, il y a moins de place pour le mot du nuage, mais on peut continuer à jouer en cliquant sur ces "gros" boutons. +- Modes de jeu supplémentaires payants par ex. + + +POUR LAFOURCADE +- lien : code.google.com/intl/fr~FR/apis/chart/index.html +- note techniques exactes pour le serveur au LIRMM diff --git a/rapport/rapport.tex b/rapport/rapport.tex index bae1067..6198586 100644 --- a/rapport/rapport.tex +++ b/rapport/rapport.tex @@ -60,6 +60,45 @@ Un grand nombre de développeurs ont créés des applications pour étendre la f \section{Conception} +\begin{verbatim} +NODE(EID integer primary key autoincrement, name string, #type (ref TYPE_NODE.num), weight); + +RELATION(RID integer primary key autoincrement, #start (ref NODE.eid), #end (ref NODE.eid), #type (ref TYPE_RELATION.num), weight); + +TYPE_NODE(NUM, name string); + +TYPE_RELATION(NUM, name string, extended_name string, info string); + +USER(LOGIN string primary key, mail string, hash_passwd string (md5sum du password), #score (contrainte : somme de tous les scores des PLAYED_GAME_CLOUD); + +GAME(GID integer primary key autoincrement, #eid_central_word (ref NODE.eid, #relation_1 (ref RELATION.rid), #relation_2 ( (ref RELATION.rid), difficulty); + +GAME_CLOUD(GID, NUM, difficulty, #eid_word(ref NODE.eid), totalWeight (contrainte : = somme des probas), probaR1 (contrainte : = somme des probas des PLAYED_GAME_CLOUD.weight avec la bonne relation et la même gid et num), probaR2 (idem), probaR0 (idem), probaTrash (idem)); + +PLAYED_GAME(PGID integer primary key autoincrement, #gid (ref GAME.gid), #login (ref USER.login); + +PLAYED_GAME_CLOUD(#PGID (ref PLAYED_GAME.pgid), #GID (ref PLAYED_GAME.gid), NUM, type (contrainte : 0 = partie de référence, 1 = réponse d'un joueur), #relation (ref RELATION.rid), weight (contrainte : probabilité estimée de cette réponse pour les bots (robots), réputation du joueur sinon), score (score donné au joueur, 0 pour les bots); + +**INT unless otherwise marked +\end{verbatim} +\begin{verbatim} +create table node(eid integer primary key autoincrement, name, type, weight); +create table relation(rid integer primary key autoincrement, start, end, type, weight); +create table type_node(name, num); +create table type_relation(name, num, extended_name, info); +create table user(login primary key, mail, hash_passwd, score); +create table game(gid integer primary key autoincrement, eid_central_word, relation_1, relation_2, difficulty); +create table game_cloud(gid, num, difficulty, eid_word, totalWeight, probaR1, probaR2, probaR0, probaTrash); +create table played_game(pgid integer primary key autoincrement, gid, login); +create table played_game_cloud(pgid, gid, type, num, relation, weight, score); + +create index i_relation_start on relation(start); +create index i_relation_end on relation(end); +create index i_relation_type on relation(type); +create index i_relation_start_type on relation(start,type); +create index i_relation_end_type on relation(end,type); +\end{verbatim} + TODO: UML, diagrammes de classes, Use cases, etc... @@ -87,6 +126,22 @@ TODO: UML, diagrammes de classes, Use cases, etc... \item Caractères non échappés dans le dump de la base.% gd \end{itemize} +\subsubsection{Itération 1, semaine 3} +\begin{itemize} +\item SQLite3 n'est pas capable d'utiliser un index pour la requête extérieure sur une requête du type +\begin{verbatim} +select * from (select * from table where condition) where condition +\end{verbatim} +Donc nécessité de ré-écrire certaines requêtes avec des jointures à priori beaucoup moins efficaces, mais qui le sont plus grâce aux index. +\item SQLite3 tranforme les requêtes de la forme~: +\begin{verbatim} +select * from table limit 100 order by random(); +\end{verbatim} + en une requête qui récupère tout le set de résultats, ajoute une colonne random(), prend les 100 premiers résultats et les trie. Mais cela + l'oblige à récupérer tout le set de résultats, et calculer le random() pour chaque ligne, pour ensuite jeter tout ce qui dépasse la ligne + 100. Cela est évidemment très coûteux dans le cadre de requêtes avec beaucoup de résultats, et nous avons donc dû isoler la requête avec + \verb!limit! de son \verb!order by! avec des «hacks» assez tordus. +\end{itemize} \section{Conclusions}