commit 7f5f52e0ebb510b4f72065ac91e6c27610e1a634 Author: oupson Date: Sat Nov 19 21:06:11 2022 +0100 Initial Commit diff --git a/README b/README new file mode 100755 index 0000000..242823d --- /dev/null +++ b/README @@ -0,0 +1,71 @@ +Serveur Blackjack + +Le but de ce DM est d'écrire un serveur pour un jeu simplifié de blackjack en suivant un protocole fourni. Un lab est proposé contenant une machine server sur lequel lancer votre application, ainsi que 6 machines pc1 à pc6 sur lesquelles vous pourrez faire tourner des clients. Deux applications clientes sont fournies ("shared/croupier.py") et ("shared/joueur.py"). + +Vous devrez programmer ce serveur en python en utilisant l'API asyncio vue en TP. + +1. Le lab et le serveur + + La machine server du lab doit héberger le serveur, qui sera lancé au démarrage (commande à décommenter dans "server.startup"). Le code du serveur est contenu dans un fichier "serverblackjack.py" placé dans le répertoire "shared" du lab. Au lancement, le serveur se positionne en écoute sur les ports 667 et 668. Un croupier ("shared/croupier.py"), exécuté depuis une des 6 autres machines, se connecte au serveur et l'échange permet au croupier de donner au serveur les paramètres de la table de jeu que le serveur va ensuite créer. La connexion se termine une fois les paramètres transmis. Un joueur ("shared/joueur.py"), exécuté depuis une des 6 autres machines, se connecte au serveur, rejoint une table et joue. + + Le serveur doit pouvoir gérer un nombre arbitraire de tables en attente de joueurs ou en cours de jeu, et un nombre arbitraire de joueurs par table. + +2. Le jeu + + On joue avec un paquet de 52 cartes classiques : 4 couleurs, 13 cartes par couleur (cf https://fr.wikipedia.org/wiki/Jeu_de_cartes_fran%C3%A7ais). + Lorsqu'un joueur se connecte au serveur, il donne un nom de table auquel il souhaite se connecter. Cette table doit avoir été créée par un croupier auparavant. S'il est le premier joueur à cette table, une attente débute pendant un temps passé en paramètre lors de la création de la table. Pendant ce temps, d'autres joueurs peuvent se connecter à la même table. Une fois ce délai passé, il ne sera plus possible de rejoindre la table. + Le serveur prend alors un jeu de 52 cartes, qu'il mélange puis donne une carte à chaque joueur, prend une carte et recommence l'opération, de sorte que chaque joueur a 2 cartes ainsi que le donneur. Le but du jeu est de constituer un ensemble de cartes dont la valeur totale est la plus grande possible tout en restant inférieure ou égale à 21. Seule la première carte du donneur est connue des joueurs. Partant de ses 2 cartes, chaque joueur peut choisir de continuer à prendre des cartes une par une et de s'arrêter lorsqu'il le souhaite. S'il dépasse 21, il a perdu, s'il s'arrête avant, le donneur doit retourner sa seconde carte puis à son tour prendre des cartes une par une et s'arrêter lorsqu'il le souhaite. Si le donneur non plus n'a pas dépassé 21, le joueur gagne s'il a un meilleur total que le donneur, fait match nul en cas d'égalité et perd sinon. Dans une table à plusieurs joueurs, tous les joueurs jouent avant que le donneur ne retourne sa seconde carte et complète éventuellement sa main. + La stratégie du donneur est fixée : il s'arrête s'il a 17 ou plus et prend une carte sinon. + Les valeurs des cartes ne dépendent pas de la couleur : + - les as valent 1 ou 11, on choisit la valeur la plus favorable; + - les dix, valets, dames ou rois valent 10; + - les autres cartes valent leur chiffre, par exemple le sept de pique vaut 7. + +Une fois la partie démarrée sur une table, pour chaque joueur successivement : + - le serveur envoie au joueur la liste de ses cartes, ainsi que la première carte du donneur; + - le serveur demande au joueur s'il souhaite une carte supplémentaire; + - le joueur demande une carte ou s'arrête; + - on répète ces 3 opérations jusqu'à ce que le joueur décide de s'arrêter. +Quand tous les joueurs ont joué, le donneur joue à son tour en prenant des cartes tant qu'il n'a pas atteint 17, puis il informe les joueurs du résultat. + +3. Le protocole + +On implémente un protocole textuel avec des messages de trois types : +1) "COMMAND" où "COMMAND" est une commande parmi {END, .}; +2) "COMMAND value" où "COMMAND" est une commande parmi {NAME, TIME, MORE} et "value" est un paramètre (éventuellement vide) transmis sous forme de chaîne de caractères; +3) une chaîne de caractères quelconque. +Dans le cas du 3ème type, la syntaxe est libre. En revanche, dans le second cas, "value" est un paramètre de la commande envoyée par le client. Sauf précision dans le corps du sujet, les messages envoyés consistent en une unique ligne terminée par un "\n". + + 3.1 Le croupier comme client + Lorsqu'un croupier se connecte sur le port 668 du serveur, le serveur envoie un message de bienvenue. Le croupier répond avec la commande NAME suivie du nom de la table que le croupier veut créer. Le serveur confirme la réception et attend ensuite le délai (en secondes) que le croupier veut fixer entre la connexion d'un premier joueur et le début de la partie. Le croupier répond avec la commande TIME suivie du délai. + Le rôle du croupier s'arrête ici. + + 3.2 Le joueur comme client + Lorsqu'un joueur se connecte sur le port 667 du serveur, le serveur envoie un message de bienvenue. Le joueur répond avec la commande NAME suivie du nom de la table que le joueur veut rejoindre. Si aucune table ouverte ne correspond, le serveur termine l'échange avec la commande END. Sinon, il enregistre le joueur comme participant à cette table. Si c'est le premier joueur à cette table, l'attente d'autres joueurs commence et se terminera à la fin du délai passé en paramètre à la table. Une fois le délai passé, il n'est plus possible de se connecter et la partie commence. Le serveur distribue puis communique avec chaque joueur : + - le serveur donne au joueur la liste des cartes du joueur, le score correspondant ainsi que la première carte du donneur. Il attend ensuite de savoir si le joueur veut une carte supplémentaire. Cela peut se faire en plusieurs messages et se termine par la commande '.' + - le joueur répond par la commande MORE suivie de 1 pour une carte, 0 sinon. + - ces étapes sont répétées jusqu'à ce que le joueur s'arrête + - si le joueur a dépassé 21, le serveur lui donne le résultat. + Une fois que tous les joueurs ont joué, le serveur choisit lui même de prendre des cartes tant qu'il n'a pas atteint au moins 17. Il annonce ensuite le résultat à chaque joueur : + - le serveur envoie ses cartes et son score ainsi que le résultat puis termine avec la commande END + - le serveur ferme la connexion avec le joueur + Le serveur supprime finalement la table. + +4. Attendus du serveur + - Le serveur doit pouvoir gérer toutes les demandes de connexion de manière concurrente, il doit être possible de créer plusieurs tables et plusieurs parties peuvent être en cours à un moment donné. + - Chaque table ne peut accueillir qu'une partie et doit être supprimée à la fin de celle-ci. + - Les tours de jeu des joueurs doivent se faire de manière concurrente sans ordre établi entre eux. + +5. Travail demandé + +Vous écrirez le code du serveur dans un fichier "serverblackjack.py". Il doit pouvoir interagir avec les clients proposés comme exemples (fichier "/shared/croupier.py" et "/shared/joueur.py" dans le lab). + +6. Réalisation, retour et évaluation + +Le travail est à effectuer en binôme ou individuellement. Vous écrirez un unique fichier "serverblackjack.py" que vous placerez dans une archive "prenom1-nom1.prenom2-nom2.tar.gz (en remplaçant prénoms et noms par ceux de votre binôme) à déposer au plus tard le 27 novembre 2022 à 23h59 dans le dépôt prévu à cet effet sur la page celene. Tous les binômes sont autorisés y compris entre Ingé et MIAGE, mais chaque binôme doit rendre un et un seul devoir. + +Des pénalités seront appliquées en cas de retard, d'archive mal formée (il ne faut pas mettre le lab dans l'archive !) ou mal nommée... + +L'évaluation sera réalisée en testant et en lisant votre code, le test aura lieu dans un lab similaire à celui qui vous est fourni. + +Le but est de fournir une application fonctionnelle : en cas d'erreur ou de message d'erreur sur une fonctionnalité, il n'y aura pas de point au titre de l'esthétique, de l'originalité ou d'un code partiellement correct. \ No newline at end of file diff --git a/README~ b/README~ new file mode 100755 index 0000000..6317e1c --- /dev/null +++ b/README~ @@ -0,0 +1,70 @@ +Serveur Blackjack + +Le but de ce DM est d'écrire un serveur pour un jeu simplifié de blackjack en suivant un protocole fourni. Un lab est proposé contenant une machine server sur lequel lancer votre application, ainsi que 6 machines pc1 à pc6 sur lesquelles vous pourrez faire tourner des clients. Deux applications clientes sont fournies ("shared/croupier.py") et ("shared/joueur.py"). + +Vous devrez programmer ce serveur en python en utilisant l'API asyncio vue en TP. + +1. Le lab et le serveur + + La machine server du lab doit héberger le serveur, qui sera lancé au démarrage (commande à décommenter dans "server.startup"). Le code du serveur est contenu dans un fichier "serverblackjack.py" placé dans le répertoire "shared" du lab. Au lancement, le serveur se positionne en écoute sur les ports 667 et 668. Un croupier ("shared/croupier.py"), exécuté depuis une des 6 autres machines, se connecte au serveur et l'échange permet au croupier de donner au serveur les paramètres de la table de jeu que le serveur va ensuite créer. La connexion se termine une fois les paramètres transmis. Un joueur ("shared/joueur.py"), exécuté depuis une des 6 autres machines, se connecte au serveur, rejoint une table et joue. + + Le serveur doit pouvoir gérer un nombre arbitraire de tables en attente de joueurs ou en cours de jeu. + +2. Le jeu + On joue avec un paquet de 52 cartes classiques (4 couleurs, 13 cartes par couleur). + Lorsqu'un joueur se connecte au serveur, il donne un nom de table auquel il souhaite se connecter. S'il est le premier joueur à cette table, une attente débute pendant un temps passé en paramètre lors de la création de la table. Pendant ce temps, d'autres joueurs peuvent se connecter à la même table. Une fois ce délai passé, il ne sera plus possible de rejoindre la table. + Le serveur prend alors un jeu de 52 cartes, qu'il mélange puis donne une carte à chaque joueur, prend une carte et recommence l'opération, de sorte que chaque joueur a 2 cartes ainsi que le donneur. Le but du jeu est de constituer un ensemble de cartes dont la valeur totale est la plus grande possible tout en restant inférieure à 21. Partant de 2 cartes, le joueur peut choisir de continuer à prendre des cartes une par une et de s'arrêter lorsqu'il le souhaite. Il connaît la première carte du donneur uniquement. S'il dépasse 21, il a perdu, s'il s'arrête avant, le donneur doit retourner sa seconde carte puis à son tour prendre des cartes une par une et s'arrêter lorsqu'il le souhaite. Si personne n'a dépassé 21, le joueur gagne s'il a un meilleur total que le donneur, fait match nul en cas d'égalité et perd sinon. Dans une table à plusieurs joueurs, tous les joueurs jouent avant que le donneur ne retourne sa seconde carte et complète éventuellement sa main. + La stratégie du donneur est fixée : il s'arrête s'il a 17 ou plus et prend une carte sinon. + Les valeurs des cartes ne dépendent pas de la couleur : + - les as valent 1 ou 11, on choisit la valeur la plus favorable; + - les dix, valets, dames ou rois valent 10; + - les autres cartes valent leur chiffre, par exemple le sept de pique vaut 7. + +Une fois la partie démarrée sur une table, pour chaque joueur successivement : + - le serveur envoie au joueur la liste de ses cartes, ainsi que la première carte du donneur; + - le serveur demande au joueur s'il souhaite une carte supplémentaire; + - le joueur demande une carte ou s'arrête; + - on répète ces 3 opérations jusqu'à ce que le joueur décide de s'arrêter. +Quand tous les joueurs ont joué, le donneur joue à son tour en prenant des cartes tant qu'il n'a pas atteint 17, puis il informe les joueurs du résultat. + +3. Le protocole + +On implémente un protocole textuel avec des messages de trois types : +1) "COMMAND" où "COMMAND" est une commande parmi {END, .}; +2) "COMMAND value" où "COMMAND" est une commande parmi {NAME, TIME, MORE} et "value" est un paramètre (éventuellement vide) transmis sous forme de chaîne de caractères; +3) une chaîne de caractères quelconque. +Dans le cas du 3ème type, la syntaxe est libre. En revanche, "value" est un paramètre de la commande envoyée par le client. Sauf précision dans le corps du sujet, les messages envoyés consistent en une unique ligne terminée par un "\n". + + 3.1 Le croupier comme client + Lorsqu'un croupier se connecte sur le port 668 du serveur, le serveur envoie un message de bienvenue et demande le nom de la table à créer. Le croupier répond avec la commande NAME suivie du nom. Le serveur demande ensuite le délai à attendre entre la connexion d'un premier joueur et le début de la partie. Le croupier répond avec la commande TIME suivie du délai. + Le rôle du croupier s'arrête ici. + + 3.2 Le joueur comme client + Lorsqu'un joueur se connecte sur le port 667 du serveur, le serveur envoie un message de bienvenue et demande le nom de la table que le joueur veut rejoindre. Le joueur répond avec la commande NAME suivie du nom. Si aucune table ouverte ne correspond, le serveur termine l'échange avec la commande END. Sinon, il enregistre le joueur comme participant à cette table. Si c'est le premier joueur à cette table, l'attente d'autres joueurs commence et se termine à la fin du délai passé en paramètre à la table. Une fois le dali passé, il n'est plus possible de se connecter et la partie commence. Le serveur distribue puis échange avec chaque joueur : + - le serveur donne au joueur la liste des cartes du joueur, son score du moment ainsi que la première carte du donneur. Il demande ensuite au joueur si celui-ci veut une carte supplémentaire.Ccela peut se faire en plusieurs messages et se termine par la commande '.' + - le joueur répond par la commande MORE suivie de 1 pour une carte, 0 sinon. + - ces étapes sont répétées jusqu'à ce que le joueur s'arrête + - si le joueur a dépassé 21, le serveur lui donne le résultat. + Une fois que tous les joueurs ont joué, le serveur choisit lui même de prendre des cartes tant qu'il n'a pas atteint 17. Il annonce ensuite le résultat à chaque joueur : + - le serveur envoie ses cartes et son score ainsi que le résultat puis termine avec la commande END + - le serveur ferme la connexion avec le joueur + Le serveur supprime finalement la table. + +4. Attendus du serveur + - Le serveur doit pouvoir gérer toutes les demandes de connexion de manière concurrente, il doit être possible de créer plusieurs tables et plusieurs parties peuvent être en cours à un moment donné. + - Chaque table ne peut accueillir qu'une partie et doit être supprimée à la fin de celle-ci. + - Les tours de jeu des joueurs doivent se faire de manière concurrente sans ordre établi. + +5. Travail demandé + +Vous écrirez le code du serveur dans un fichier "serverblackjack.py". Il doit pouvoir interagir avec les clients proposés comme exemples (fichier "/shared/croupier.py" et "/shared/joueur.py" dans le lab). + +6. Réalisation, retour et évaluation + +Le travail est à effectuer en binôme ou individuellement. Vous écrirez un unique fichier "serverblackjack.py" que vous placerez dans une archive "prenom1-nom1.prenom2-nom2.tar.gz (en remplaçant prénoms et noms par ceux de votre binôme) à déposer au plus tard le 20 novembre 2022 à 23h59 dans le dépôt prévu à cet effet pour votre groupe sur la page celene. Tous les binômes sont autorisés y compris entre Ingé et MIAGE, mais chaque binôme doit rendre un et un seul devoir dans l'un quelconque de vos dépôts si vous n'appartenez pas au même groupe.. + +Des pénalités seront appliquées en cas de retard, d'archive mal formée (il ne faut pas mettre le lab dans l'archive !) ou mal nommée... + +L'évaluation sera réalisée en testant et en lisant votre code, le test aura lieu dans un lab similaire à celui qui vous est fourni. + +Le but est de fournir une application fonctionnelle : en cas d'erreur ou de message d'erreur sur une fonctionnalité, il n'y aura pas de point au titre de l'esthétique, de l'originalité ou d'un code partiellement correct. \ No newline at end of file diff --git a/lab.conf b/lab.conf new file mode 100755 index 0000000..c9b5da1 --- /dev/null +++ b/lab.conf @@ -0,0 +1,28 @@ +LAB_DESCRIPTION="Devoir de programmation avec l'API socket -- Kathará edition" +LAB_VERSION=1.0 +LAB_AUTHOR="Martin Delacourt" +LAB_EMAIL=martin.delacourt@univ-orleans.fr + +###### pc1, pc2, pc3, pc4, pc5, pc6 on a same network +pc1[0]="rezo" +pc1[image]="nopid/3ia:latest" + +pc2[0]="rezo" +pc2[image]="nopid/3ia:latest" + +pc3[0]="rezo" +pc3[image]="nopid/3ia:latest" + +pc4[0]="rezo" +pc4[image]="nopid/3ia:latest" + +pc5[0]="rezo" +pc5[image]="nopid/3ia:latest" + +pc6[0]="rezo" +pc6[image]="nopid/3ia:latest" + +###### server running without terminal +server[0]="rezo" +server[image]="nopid/3ia:latest" +server[num_terms]=0 diff --git a/lab.dep b/lab.dep new file mode 100755 index 0000000..201d8d1 --- /dev/null +++ b/lab.dep @@ -0,0 +1,6 @@ +pc1: server +pc2: server +pc3: server +pc4: server +pc5: server +pc6: server \ No newline at end of file diff --git a/pc1.startup b/pc1.startup new file mode 100755 index 0000000..5d468f9 --- /dev/null +++ b/pc1.startup @@ -0,0 +1 @@ +dhclient diff --git a/pc2.startup b/pc2.startup new file mode 100755 index 0000000..5d468f9 --- /dev/null +++ b/pc2.startup @@ -0,0 +1 @@ +dhclient diff --git a/pc3.startup b/pc3.startup new file mode 100755 index 0000000..5d468f9 --- /dev/null +++ b/pc3.startup @@ -0,0 +1 @@ +dhclient diff --git a/pc4.startup b/pc4.startup new file mode 100755 index 0000000..5d468f9 --- /dev/null +++ b/pc4.startup @@ -0,0 +1 @@ +dhclient diff --git a/pc5.startup b/pc5.startup new file mode 100755 index 0000000..5d468f9 --- /dev/null +++ b/pc5.startup @@ -0,0 +1 @@ +dhclient diff --git a/pc6.startup b/pc6.startup new file mode 100755 index 0000000..5d468f9 --- /dev/null +++ b/pc6.startup @@ -0,0 +1 @@ +dhclient diff --git a/server.startup b/server.startup new file mode 100755 index 0000000..364bb94 --- /dev/null +++ b/server.startup @@ -0,0 +1,6 @@ +ip addr add 10.0.1.1/8 dev eth0 +echo "10.0.1.1 server" >> /etc/hosts +/etc/init.d/bind start +/etc/init.d/isc-dhcp-server start +/etc/init.d/ssh start +#/shared/serverblackjack.py \ No newline at end of file diff --git a/server.startup~ b/server.startup~ new file mode 100755 index 0000000..dc63f45 --- /dev/null +++ b/server.startup~ @@ -0,0 +1,6 @@ +ip addr add 10.0.1.1/8 dev eth0 +echo "10.0.1.1 server" >> /etc/hosts +/etc/init.d/bind start +/etc/init.d/isc-dhcp-server start +/etc/init.d/ssh start +#/shared/servershifumi.py \ No newline at end of file diff --git a/server/etc/bind/db.10 b/server/etc/bind/db.10 new file mode 100755 index 0000000..3b549ae --- /dev/null +++ b/server/etc/bind/db.10 @@ -0,0 +1,17 @@ +$TTL 60000 +@ IN SOA 10.in-addr.arpa. nobody.nowhere. ( + 1 ; serial + 28 ; refresh + 14 ; retry + 3600000 ; expire + 0 ; negative cache ttl + ) +@ IN NS 1.1.0.10.in-addr.arpa. +1.1.0.10.in-addr.arpa. IN A 10.0.1.1 + +1.1.0 PTR dns.iiia.net. +1.1.0 PTR www.iiia.net. +1.0.0 PTR pc1.iiia.net. +2.0.0 PTR pc2.iiia.net. +3.0.0 PTR pc3.iiia.net. +4.0.0 PTR pc4.iiia.net. diff --git a/server/etc/bind/db.root b/server/etc/bind/db.root new file mode 100755 index 0000000..b0bece6 --- /dev/null +++ b/server/etc/bind/db.root @@ -0,0 +1,24 @@ +$TTL 60000 +@ IN SOA dns.iiia.net. root.dns.iiia.net. ( + 1 ; serial + 28 ; refresh + 14 ; retry + 3600000 ; expire + 60000 ; negative cache ttl + ) +@ IN NS dns.iiia.net. +dns.iiia.net. IN A 10.0.1.1 + +serveur.iiia.net. IN A 10.0.1.1 +server.iiia.net. IN A 10.0.1.1 +dns.iiia.net. IN A 10.0.1.1 +www.iiia.net. IN A 10.0.1.1 +smtp.iiia.net. IN A 10.0.1.1 +pc1.iiia.net. IN A 10.0.0.1 +pc2.iiia.net. IN A 10.0.0.2 +pc3.iiia.net. IN A 10.0.0.3 +pc4.iiia.net. IN A 10.0.0.4 +pc5.iiia.net. IN A 10.0.0.5 +pc6.iiia.net. IN A 10.0.0.6 + +iiia.net. IN MX 1 smtp.iiia.net. diff --git a/server/etc/bind/db.root~ b/server/etc/bind/db.root~ new file mode 100755 index 0000000..dc28d3c --- /dev/null +++ b/server/etc/bind/db.root~ @@ -0,0 +1,22 @@ +$TTL 60000 +@ IN SOA dns.iiia.net. root.dns.iiia.net. ( + 1 ; serial + 28 ; refresh + 14 ; retry + 3600000 ; expire + 60000 ; negative cache ttl + ) +@ IN NS dns.iiia.net. +dns.iiia.net. IN A 10.0.1.1 + +serveur.iiia.net. IN A 10.0.1.1 +server.iiia.net. IN A 10.0.1.1 +dns.iiia.net. IN A 10.0.1.1 +www.iiia.net. IN A 10.0.1.1 +smtp.iiia.net. IN A 10.0.1.1 +pc1.iiia.net. IN A 10.0.0.1 +pc2.iiia.net. IN A 10.0.0.2 +pc3.iiia.net. IN A 10.0.0.3 +pc4.iiia.net. IN A 10.0.0.4 + +iiia.net. IN MX 1 smtp.iiia.net. diff --git a/server/etc/bind/named.conf b/server/etc/bind/named.conf new file mode 100755 index 0000000..5265d21 --- /dev/null +++ b/server/etc/bind/named.conf @@ -0,0 +1,43 @@ +// This is the primary configuration file for the BIND DNS server named. +// +// Please read /usr/share/doc/bind9/README.Debian.gz for information on the +// structure of BIND configuration files in Debian, *BEFORE* you customize +// this configuration file. +// +// If you are just adding zones, please do that in /etc/bind/named.conf.local + +include "/etc/bind/named.conf.options"; + +// prime the server with knowledge of the root servers +zone "." { + type master; + file "/etc/bind/db.root"; +}; + +// be authoritative for the localhost forward and reverse zones, and for +// broadcast zones as per RFC 1912 + +zone "localhost" { + type master; + file "/etc/bind/db.local"; +}; + +zone "127.in-addr.arpa" { + type master; + file "/etc/bind/db.127"; +}; + +zone "0.in-addr.arpa" { + type master; + file "/etc/bind/db.0"; +}; + +zone "255.in-addr.arpa" { + type master; + file "/etc/bind/db.255"; +}; + +zone "10.in-addr.arpa" { + type master; + file "/etc/bind/db.10"; +}; diff --git a/server/etc/dhcp/dhcpd.conf b/server/etc/dhcp/dhcpd.conf new file mode 100755 index 0000000..3037f3c --- /dev/null +++ b/server/etc/dhcp/dhcpd.conf @@ -0,0 +1,7 @@ +option domain-name "iiia.net"; +option domain-name-servers 10.0.1.1; + +subnet 10.0.0.0 netmask 255.0.0.0 { + range 10.0.0.1 10.0.0.50; +} + diff --git a/server/etc/dhcp/dhcpd.conf~ b/server/etc/dhcp/dhcpd.conf~ new file mode 100755 index 0000000..96e5b1f --- /dev/null +++ b/server/etc/dhcp/dhcpd.conf~ @@ -0,0 +1,13 @@ +option domain-name "iiia.net"; +option domain-name-servers 10.0.1.1; + +subnet 10.0.0.0 netmask 255.0.0.0 { + range 10.0.0.1 10.0.0.50; + + host pc1 { hardware ethernet 1e:c6:ad:0d:26:fc; fixed-address pc1.iiia.net; } + host pc2 { hardware ethernet fe:94:e6:de:3d:36; fixed-address pc2.iiia.net; } + host pc3 { hardware ethernet be:07:e1:9d:1c:85; fixed-address pc3.iiia.net; } + host pc4 { hardware ethernet 0a:3b:af:03:d5:c7; fixed-address pc4.iiia.net; } + +} + diff --git a/shared.startup b/shared.startup new file mode 100755 index 0000000..4404509 --- /dev/null +++ b/shared.startup @@ -0,0 +1 @@ +bash /shared/mojo/mojo diff --git a/shared/.idea/.gitignore b/shared/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/shared/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/shared/.idea/misc.xml b/shared/.idea/misc.xml new file mode 100644 index 0000000..e142396 --- /dev/null +++ b/shared/.idea/misc.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/shared/.idea/modules.xml b/shared/.idea/modules.xml new file mode 100644 index 0000000..9614368 --- /dev/null +++ b/shared/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/shared/.idea/shared.iml b/shared/.idea/shared.iml new file mode 100644 index 0000000..d6ebd48 --- /dev/null +++ b/shared/.idea/shared.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/shared/.idea/sonarlint/issuestore/9/5/95f3485543394e27f59a77c00936786a521a06f2 b/shared/.idea/sonarlint/issuestore/9/5/95f3485543394e27f59a77c00936786a521a06f2 new file mode 100644 index 0000000..e69de29 diff --git a/shared/.idea/sonarlint/issuestore/index.pb b/shared/.idea/sonarlint/issuestore/index.pb new file mode 100644 index 0000000..16feb79 --- /dev/null +++ b/shared/.idea/sonarlint/issuestore/index.pb @@ -0,0 +1,3 @@ + +9 + mojo/mojo,9/5/95f3485543394e27f59a77c00936786a521a06f2 \ No newline at end of file diff --git a/shared/README.md b/shared/README.md new file mode 100755 index 0000000..242823d --- /dev/null +++ b/shared/README.md @@ -0,0 +1,71 @@ +Serveur Blackjack + +Le but de ce DM est d'écrire un serveur pour un jeu simplifié de blackjack en suivant un protocole fourni. Un lab est proposé contenant une machine server sur lequel lancer votre application, ainsi que 6 machines pc1 à pc6 sur lesquelles vous pourrez faire tourner des clients. Deux applications clientes sont fournies ("shared/croupier.py") et ("shared/joueur.py"). + +Vous devrez programmer ce serveur en python en utilisant l'API asyncio vue en TP. + +1. Le lab et le serveur + + La machine server du lab doit héberger le serveur, qui sera lancé au démarrage (commande à décommenter dans "server.startup"). Le code du serveur est contenu dans un fichier "serverblackjack.py" placé dans le répertoire "shared" du lab. Au lancement, le serveur se positionne en écoute sur les ports 667 et 668. Un croupier ("shared/croupier.py"), exécuté depuis une des 6 autres machines, se connecte au serveur et l'échange permet au croupier de donner au serveur les paramètres de la table de jeu que le serveur va ensuite créer. La connexion se termine une fois les paramètres transmis. Un joueur ("shared/joueur.py"), exécuté depuis une des 6 autres machines, se connecte au serveur, rejoint une table et joue. + + Le serveur doit pouvoir gérer un nombre arbitraire de tables en attente de joueurs ou en cours de jeu, et un nombre arbitraire de joueurs par table. + +2. Le jeu + + On joue avec un paquet de 52 cartes classiques : 4 couleurs, 13 cartes par couleur (cf https://fr.wikipedia.org/wiki/Jeu_de_cartes_fran%C3%A7ais). + Lorsqu'un joueur se connecte au serveur, il donne un nom de table auquel il souhaite se connecter. Cette table doit avoir été créée par un croupier auparavant. S'il est le premier joueur à cette table, une attente débute pendant un temps passé en paramètre lors de la création de la table. Pendant ce temps, d'autres joueurs peuvent se connecter à la même table. Une fois ce délai passé, il ne sera plus possible de rejoindre la table. + Le serveur prend alors un jeu de 52 cartes, qu'il mélange puis donne une carte à chaque joueur, prend une carte et recommence l'opération, de sorte que chaque joueur a 2 cartes ainsi que le donneur. Le but du jeu est de constituer un ensemble de cartes dont la valeur totale est la plus grande possible tout en restant inférieure ou égale à 21. Seule la première carte du donneur est connue des joueurs. Partant de ses 2 cartes, chaque joueur peut choisir de continuer à prendre des cartes une par une et de s'arrêter lorsqu'il le souhaite. S'il dépasse 21, il a perdu, s'il s'arrête avant, le donneur doit retourner sa seconde carte puis à son tour prendre des cartes une par une et s'arrêter lorsqu'il le souhaite. Si le donneur non plus n'a pas dépassé 21, le joueur gagne s'il a un meilleur total que le donneur, fait match nul en cas d'égalité et perd sinon. Dans une table à plusieurs joueurs, tous les joueurs jouent avant que le donneur ne retourne sa seconde carte et complète éventuellement sa main. + La stratégie du donneur est fixée : il s'arrête s'il a 17 ou plus et prend une carte sinon. + Les valeurs des cartes ne dépendent pas de la couleur : + - les as valent 1 ou 11, on choisit la valeur la plus favorable; + - les dix, valets, dames ou rois valent 10; + - les autres cartes valent leur chiffre, par exemple le sept de pique vaut 7. + +Une fois la partie démarrée sur une table, pour chaque joueur successivement : + - le serveur envoie au joueur la liste de ses cartes, ainsi que la première carte du donneur; + - le serveur demande au joueur s'il souhaite une carte supplémentaire; + - le joueur demande une carte ou s'arrête; + - on répète ces 3 opérations jusqu'à ce que le joueur décide de s'arrêter. +Quand tous les joueurs ont joué, le donneur joue à son tour en prenant des cartes tant qu'il n'a pas atteint 17, puis il informe les joueurs du résultat. + +3. Le protocole + +On implémente un protocole textuel avec des messages de trois types : +1) "COMMAND" où "COMMAND" est une commande parmi {END, .}; +2) "COMMAND value" où "COMMAND" est une commande parmi {NAME, TIME, MORE} et "value" est un paramètre (éventuellement vide) transmis sous forme de chaîne de caractères; +3) une chaîne de caractères quelconque. +Dans le cas du 3ème type, la syntaxe est libre. En revanche, dans le second cas, "value" est un paramètre de la commande envoyée par le client. Sauf précision dans le corps du sujet, les messages envoyés consistent en une unique ligne terminée par un "\n". + + 3.1 Le croupier comme client + Lorsqu'un croupier se connecte sur le port 668 du serveur, le serveur envoie un message de bienvenue. Le croupier répond avec la commande NAME suivie du nom de la table que le croupier veut créer. Le serveur confirme la réception et attend ensuite le délai (en secondes) que le croupier veut fixer entre la connexion d'un premier joueur et le début de la partie. Le croupier répond avec la commande TIME suivie du délai. + Le rôle du croupier s'arrête ici. + + 3.2 Le joueur comme client + Lorsqu'un joueur se connecte sur le port 667 du serveur, le serveur envoie un message de bienvenue. Le joueur répond avec la commande NAME suivie du nom de la table que le joueur veut rejoindre. Si aucune table ouverte ne correspond, le serveur termine l'échange avec la commande END. Sinon, il enregistre le joueur comme participant à cette table. Si c'est le premier joueur à cette table, l'attente d'autres joueurs commence et se terminera à la fin du délai passé en paramètre à la table. Une fois le délai passé, il n'est plus possible de se connecter et la partie commence. Le serveur distribue puis communique avec chaque joueur : + - le serveur donne au joueur la liste des cartes du joueur, le score correspondant ainsi que la première carte du donneur. Il attend ensuite de savoir si le joueur veut une carte supplémentaire. Cela peut se faire en plusieurs messages et se termine par la commande '.' + - le joueur répond par la commande MORE suivie de 1 pour une carte, 0 sinon. + - ces étapes sont répétées jusqu'à ce que le joueur s'arrête + - si le joueur a dépassé 21, le serveur lui donne le résultat. + Une fois que tous les joueurs ont joué, le serveur choisit lui même de prendre des cartes tant qu'il n'a pas atteint au moins 17. Il annonce ensuite le résultat à chaque joueur : + - le serveur envoie ses cartes et son score ainsi que le résultat puis termine avec la commande END + - le serveur ferme la connexion avec le joueur + Le serveur supprime finalement la table. + +4. Attendus du serveur + - Le serveur doit pouvoir gérer toutes les demandes de connexion de manière concurrente, il doit être possible de créer plusieurs tables et plusieurs parties peuvent être en cours à un moment donné. + - Chaque table ne peut accueillir qu'une partie et doit être supprimée à la fin de celle-ci. + - Les tours de jeu des joueurs doivent se faire de manière concurrente sans ordre établi entre eux. + +5. Travail demandé + +Vous écrirez le code du serveur dans un fichier "serverblackjack.py". Il doit pouvoir interagir avec les clients proposés comme exemples (fichier "/shared/croupier.py" et "/shared/joueur.py" dans le lab). + +6. Réalisation, retour et évaluation + +Le travail est à effectuer en binôme ou individuellement. Vous écrirez un unique fichier "serverblackjack.py" que vous placerez dans une archive "prenom1-nom1.prenom2-nom2.tar.gz (en remplaçant prénoms et noms par ceux de votre binôme) à déposer au plus tard le 27 novembre 2022 à 23h59 dans le dépôt prévu à cet effet sur la page celene. Tous les binômes sont autorisés y compris entre Ingé et MIAGE, mais chaque binôme doit rendre un et un seul devoir. + +Des pénalités seront appliquées en cas de retard, d'archive mal formée (il ne faut pas mettre le lab dans l'archive !) ou mal nommée... + +L'évaluation sera réalisée en testant et en lisant votre code, le test aura lieu dans un lab similaire à celui qui vous est fourni. + +Le but est de fournir une application fonctionnelle : en cas d'erreur ou de message d'erreur sur une fonctionnalité, il n'y aura pas de point au titre de l'esthétique, de l'originalité ou d'un code partiellement correct. \ No newline at end of file diff --git a/shared/__pycache__/message.cpython-310.pyc b/shared/__pycache__/message.cpython-310.pyc new file mode 100644 index 0000000..0906fc4 Binary files /dev/null and b/shared/__pycache__/message.cpython-310.pyc differ diff --git a/shared/croupier.py b/shared/croupier.py new file mode 100755 index 0000000..3c7bec8 --- /dev/null +++ b/shared/croupier.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 - + +from sys import argv +from socket import gethostbyname +import asyncio + +async def sndrcv(reader,writer,msg): + """envoie le message [msg] au serveur et affiche la réponse""" + if msg is not None: + writer.write(msg.encode() + b"\r\n") + data = await reader.readline() + print(data.decode()) + +def getint(msg): + """demande une valeur entière à l'utilisateur""" + while True: + try: + y = int(input(msg)) + break + except ValueError: + print("Oups! Réessaie avec un nombre valide...") + return(y) + + +async def blackjack_croupier(server): + reader, writer = await asyncio.open_connection(server, 6668) # TODO 668 + await sndrcv(reader,writer,None) + name = input("Nom de la table ?\n") + await sndrcv(reader,writer,"NAME "+name) + time = getint("Temps d'attente entre la connexion du premier joueur et le début de la partie ?\n") + await sndrcv(reader,writer,"TIME "+str(time)) + + +if __name__ == '__main__': + if len(argv)!=2: + print("usage: {scriptname} server".format(scriptname= argv[0])) + exit(1) + sname=argv[1] + server=gethostbyname(sname) + print("connecting to :", sname, server) + asyncio.run(blackjack_croupier(server)) + diff --git a/shared/joueur.py b/shared/joueur.py new file mode 100755 index 0000000..caf4de3 --- /dev/null +++ b/shared/joueur.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 - + +from sys import argv +from socket import gethostbyname +import asyncio + +async def sndrcv(reader,writer,msg): + """envoie le message [msg] au serveur et affiche la réponse""" + if msg is not None: + writer.write(msg.encode() + b"\r\n") + data = await reader.readline() + print (data.decode().strip()) + return data.decode().strip() + +def getint(msg): + """demande une valeur entière à l'utilisateur""" + while True: + try: + y = int(input(msg)) + break + except ValueError: + print("Oups! Réessaie avec un nombre valide...") + return(y) + + +async def blackjack_client(server): + reader, writer = await asyncio.open_connection(server, 6667) + await sndrcv(reader,writer,None) + name = input("À quelle table voulez-vous vous connecter ?\n") + s = await sndrcv(reader,writer,"NAME "+name) + while s.strip() != "END": + if s == '.': + more = getint("Voulez-vous une carte supplémentaire ? 1 pour oui, 0 pour non.\n") + while more not in range(2): + print("Une valeur parmi 0 et 1 est attendue.\n") + more = getint("Voulez-vous une carte supplémentaire ? 1 pour oui, 0 pour non.\n") + s = await sndrcv(reader,writer,"MORE "+str(more)) + else : + s = await sndrcv(reader,writer,None) + +if __name__ == '__main__': + if len(argv)!=2: + print("usage: {scriptname} server".format(scriptname= argv[0])) + exit(1) + sname=argv[1] + server=gethostbyname(sname) + print("connecting to :", sname, server) + asyncio.run(blackjack_client(server)) + diff --git a/shared/mojo/macaddr b/shared/mojo/macaddr new file mode 100755 index 0000000..fffb249 --- /dev/null +++ b/shared/mojo/macaddr @@ -0,0 +1,12 @@ +#!/usr/bin/env python3 +from hashlib import sha1 +from sys import argv +m = sha1() +m.update(argv[1].encode()) +m.update(b"-") +m.update(argv[2].encode()) +d = m.digest() +addr = [ (d[i]+d[i+6]) % 256 for i in range(6) ] +addr[0] &= 0xfe +addr[0] |= 0x02 +print(':'.join(map(lambda x : ("00"+hex(x)[2:])[-2:],addr))) diff --git a/shared/mojo/mojo b/shared/mojo/mojo new file mode 100755 index 0000000..9164f42 --- /dev/null +++ b/shared/mojo/mojo @@ -0,0 +1,6 @@ +umount /etc/resolv.conf +umount /etc/hosts +echo '127.0.0.1 localhost' > /etc/hosts +for eth in $(ip -br a | grep '^eth' | cut -d@ -f1); do + ip link set $eth address $(/shared/mojo/macaddr $HOSTNAME $eth) +done diff --git a/shared/server/__init__.py b/shared/server/__init__.py new file mode 100644 index 0000000..91130ac --- /dev/null +++ b/shared/server/__init__.py @@ -0,0 +1,124 @@ +import asyncio +import logging +import socket + +from .game import ConcurrentData, TableAlreadyExistException +from .message import parse_message, BiCommand + + +class Server: + def __init__(self) -> None: + self.__data = ConcurrentData() + + async def handle_client(self, client: socket.socket) -> None: + loop = asyncio.get_event_loop() + + logging.info("New client connected") + await loop.sock_sendall(client, b"HELLO\r\n") + + try: + request = parse_message((await loop.sock_recv(client, 4096)).decode('utf8')) + except BrokenPipeError: + return + + if len(request) != 2: + await loop.sock_sendall(client, b"ERROR: INVALID COMMAND\r\nEND\r\n") + client.close() + return + + if request[0] == BiCommand.NAME: + name = request[1] + else: + await loop.sock_sendall(client, b"ERROR: INVALID COMMAND\r\nEND\r\n") + client.close() + return + + if not await self.__data.has_table(name): + await loop.sock_sendall(client, b"ERROR: NO SUCH TABLE\r\nEND\r\n") + client.close() + return + + table = await self.__data.get_table(name) + if await table.add_client(client, self.__data): + await loop.sock_sendall(client, b"OK\r\n") + else: + await loop.sock_sendall(client, b"ERROR: ROOM FULL\r\nEND\r\n") + client.close() + + async def handle_croupier(self, client: socket.socket) -> None: + logging.debug("New croupier connected") + loop = asyncio.get_event_loop() + await loop.sock_sendall(client, b"HELLO\r\n") + + try: + request = parse_message((await loop.sock_recv(client, 4096)).decode('utf8')) + except BrokenPipeError: + return + + if len(request) != 2: + await loop.sock_sendall(client, b"ERROR: INVALID COMMAND\r\nEND\r\n") + client.close() + return + + if request[0] == BiCommand.NAME: + name = request[1] + await loop.sock_sendall(client, b"OK\r\n") + else: + await loop.sock_sendall(client, b"ERROR: INVALID COMMAND\r\nEND\r\n") + client.close() + return + + try: + request = parse_message((await loop.sock_recv(client, 4096)).decode('utf8')) + except BrokenPipeError: + return + + if len(request) != 2: + await loop.sock_sendall(client, b"ERROR: INVALID COMMAND\r\nEND\r\n") + client.close() + return + + if request[0] == BiCommand.TIME: + time = request[1] + try: + await self.__data.add_table(name, int(time)) + await loop.sock_sendall(client, b"OK\r\n") + except TableAlreadyExistException: + await loop.sock_sendall(client, b"ERROR: TABLE ALREADY EXIST\r\nEND\r\n") + else: + await loop.sock_sendall(client, b"ERROR: INVALID COMMAND\r\nEND\r\n") + + client.close() + + async def listen_client(self) -> None: + listen_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + listen_socket.bind(('localhost', 667)) + listen_socket.listen(8) + listen_socket.setblocking(False) + + loop = asyncio.get_event_loop() + + while True: + client, _ = await loop.sock_accept(listen_socket) + loop.create_task(self.handle_client(client)) + + async def listen_croupier(self) -> None: + server_croupier = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + server_croupier.bind(('localhost', 668)) + server_croupier.listen(8) + server_croupier.setblocking(False) + + loop = asyncio.get_event_loop() + + while True: + client, _ = await loop.sock_accept(server_croupier) + loop.create_task(self.handle_croupier(client)) + + async def run(self): + loop = asyncio.get_event_loop() + + loop_client = loop.create_task(self.listen_client()) + loop_croupier = loop.create_task(self.listen_croupier()) + + logging.info("Running server ...") + await asyncio.gather(loop_client, loop_croupier) diff --git a/shared/server/__pycache__/__init__.cpython-310.pyc b/shared/server/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000..6f2be11 Binary files /dev/null and b/shared/server/__pycache__/__init__.cpython-310.pyc differ diff --git a/shared/server/__pycache__/game.cpython-310.pyc b/shared/server/__pycache__/game.cpython-310.pyc new file mode 100644 index 0000000..7cab841 Binary files /dev/null and b/shared/server/__pycache__/game.cpython-310.pyc differ diff --git a/shared/server/__pycache__/message.cpython-310.pyc b/shared/server/__pycache__/message.cpython-310.pyc new file mode 100644 index 0000000..ca337e1 Binary files /dev/null and b/shared/server/__pycache__/message.cpython-310.pyc differ diff --git a/shared/server/game.py b/shared/server/game.py new file mode 100644 index 0000000..c1d98a9 --- /dev/null +++ b/shared/server/game.py @@ -0,0 +1,250 @@ +import asyncio +import logging +import random +import socket + +from enum import Enum +from typing import List, Dict, Tuple, Optional + +from .message import parse_message, BiCommand + + +class Color(Enum): + TREFLE = "trefle" + CARRAUX = "carraux" + COEUR = "cœurs" + PIQUES = "piques" + + +class Carte: + color: Color + value: int + + def __init__(self, color: Color, value: int): + self.color = color + self.value = value + + def __str__(self): + return str(self.color.value) + " " + str(self.value) + + +def create_deck() -> List[Carte]: + cartes = [] + for color in [Color.TREFLE, Color.CARRAUX, Color.COEUR, Color.PIQUES]: + for num in range(1, 14): + cartes.append(Carte(color, num)) + + random.shuffle(cartes) + + return cartes + + +class Table: + __name: str + __delay: int + + def __init__(self, name: str, delay: int) -> None: + self.__name = name + self.__delay = delay + + @property + def delay(self) -> int: + return self.__delay + + @property + def name(self) -> str: + return self.__name + + +class Joueur: + __socket: socket.socket + cartes: List[Carte] + + def __init__(self, client_socket: socket.socket): + self.__socket = client_socket + self.cartes = list() + + @property + def socket(self) -> socket.socket: + return self.__socket + + @property + def points(self) -> int: + return points_cartes(self.cartes) + + +def points_cartes(cartes: List[Carte]) -> int: + value = 0 + as_count = 0 + + for carte in cartes: + if carte.value == 1: + as_count += 1 + else: + value += carte.value + + if as_count > 0: + if value + 11 + as_count - 1 < 21: + value += 11 + as_count - 1 + else: + value += as_count + + return value + + +class Game: + __players: List[Joueur] + __table: 'Table' + __started: bool + __accepting: bool + __cards: List[Carte] + __dealer_cards: List[Carte] + __logger: logging.Logger + + def __init__(self, table: 'Table') -> None: + self.__table = table + self.__started = False + self.__players = [] + self.__accepting = True + self.__cards = create_deck() + self.__dealer_cards = list() + self.__logger = logging.getLogger(f"Game-{table.name}") + + async def add_client(self, client: socket.socket, data: 'ConcurrentData') -> bool: + if self.__accepting: + loop = asyncio.get_event_loop() + joueur = Joueur(client) + self.__players.append(joueur) + if not self.__started: + loop.create_task(self.run_game(data)) + return True + else: + return False + + async def run_game(self, data: 'ConcurrentData'): + self.__logger.error(f"Waiting {self.__table.delay}s") + self.__started = True + await asyncio.sleep(self.__table.delay) + self.__accepting = False + for _ in range(0, 2): + self.__dealer_cards.append(self.__cards.pop(0)) + for joueur in self.__players: + joueur.cartes.append(self.__cards.pop(0)) + + loop = asyncio.get_event_loop() + for c in self.__players: + await loop.sock_sendall(c.socket, ( + "Vos cartes sont : " + ", ".join(str(carte) for carte in c.cartes) + "\r\n").encode("utf8")) + await loop.sock_sendall(c.socket, ( + "La premiere carte du donneur est : " + str(self.__dealer_cards[0]) + "\r\n").encode('utf8')) + + await self.run_game_loop(loop) + + for c in self.__players: + await loop.sock_sendall(c.socket, b"Service termine\r\n") + await loop.sock_sendall(c.socket, + f"L'autre carte du donneur est : {self.__dealer_cards[1]}\r\n".encode( + 'utf8')) + await loop.sock_sendall(c.socket, + f"Vous avez {c.points} points\r\n".encode('utf8')) + + max_player, max_player_index = self.max_player() + self.__logger.debug(f"Max player have {max_player} points") + + while max_player is not None and len(self.__cards) > 0 and points_cartes(self.__dealer_cards) < max_player: + carte = self.__cards.pop(0) + for c in self.__players: + await loop.sock_sendall(c.socket, f"Le donneur a pioché : {carte}\r\n".encode('utf8')) + self.__dealer_cards.append(carte) + + await self.send_victory_status(loop, max_player, max_player_index) + await data.remove_table(self.__table.name) + + self.__logger.debug(f"Removed table \"{self.__table.name}\"") + + async def run_game_loop(self, loop): + for i, joueur in enumerate(self.__players): + for j in self.__players: + await loop.sock_sendall(j.socket, f"C'est au tour de joueur {i + 1}\r\n".encode('utf8')) + + playing = True + while playing: + if len(self.__cards) > 0 and joueur.points < 21: + await loop.sock_sendall(joueur.socket, b".\r\n") + request = parse_message((await loop.sock_recv(joueur.socket, 4096)).decode('utf8')) + if len(request) == 2 and request[0] == BiCommand.MORE: + if request[1] == "0": + playing = False + elif request[1] == "1": + carte = self.__cards.pop(0) + joueur.cartes.append(carte) + await loop.sock_sendall(joueur.socket, + f"Votre carte est : {carte}, il reste {len(self.__cards)} cartes\r\n".encode( + 'utf8')) + else: + await loop.sock_sendall(joueur.socket, b"ERROR : INVALID COMMAND\r\n") + else: + await loop.sock_sendall(joueur.socket, b"ERROR : INVALID COMMAND\r\n") + else: + playing = False + + async def send_victory_status(self, loop, max_player, max_player_index: List[int]): + points_donneur = points_cartes(self.__dealer_cards) + self.__logger.debug(f"Dealer have {points_donneur} points") + if (-1 if max_player is None else max_player) < points_donneur < 21: + for c in self.__players: + await loop.sock_sendall(c.socket, b"Vous avez perdu !\r\nEND\r\n") + c.socket.close() + else: + for i, c in enumerate(self.__players): + if i in max_player_index: + if max_player == points_donneur or len(max_player_index) > 1: + await loop.sock_sendall(c.socket, b"Egalite !\r\nEND\r\n") + else: + await loop.sock_sendall(c.socket, b"Vous avez gagne !\r\nEND\r\n") + else: + await loop.sock_sendall(c.socket, b"Vous avez perdu !\r\nEND\r\n") + c.socket.close() + + def max_player(self) -> Tuple[Optional[int], Optional[List[int]]]: + m = None + mp = [] + for i, j in enumerate(self.__players): + p = j.points + if p < 21: + if m is None or m < p: + m = p + mp = [i] + elif m == p: + mp.append(i) + return m, mp + + +class TableAlreadyExistException(Exception): + pass + + +class ConcurrentData: + def __init__(self) -> None: + self.__lock = asyncio.Lock() + self.__data: Dict[str, Game] = dict() + + async def add_table(self, table_name: str, delay: int): + async with self.__lock: + if table_name in self.__data.keys(): + raise TableAlreadyExistException() + else: + self.__data[table_name] = Game(Table(table_name, delay)) + logging.debug("Created a new table called %s" % table_name) + + async def has_table(self, table_name: str) -> bool: + async with self.__lock: + return table_name in self.__data.keys() + + async def get_table(self, table_name: str) -> Game: + async with self.__lock: + return self.__data[table_name] + + async def remove_table(self, table_name: str): + async with self.__lock: + self.__data.pop(table_name) diff --git a/shared/server/message.py b/shared/server/message.py new file mode 100644 index 0000000..4dd5bff --- /dev/null +++ b/shared/server/message.py @@ -0,0 +1,54 @@ +import logging +import re + +from enum import Enum +from typing import Union, Tuple, AnyStr, Any + +regex_command = r"^(?:(END|\.)|(?:(NAME|TIME|MORE) (\w+))|(?:(.+)\n))\r\n$" + + +class UniCommand(Enum): + END = 1 + POINT = 2 + + @staticmethod + def from_str(command: str) -> 'UniCommand': + if command == "END": + return UniCommand.END + elif command == ".": + return UniCommand.POINT + else: + # TODO + pass + + +class BiCommand(Enum): + NAME = 1 + TIME = 2 + MORE = 3 + + @staticmethod + def from_str(command: str) -> 'BiCommand': + if command == "NAME": + return BiCommand.NAME + elif command == "TIME": + return BiCommand.TIME + elif command == "MORE": + return BiCommand.MORE + else: + # TODO + pass + + +def parse_message(message: str) -> Union[UniCommand, Tuple[BiCommand, str], Tuple[str, str]]: + matches = re.match(regex_command, message) + if matches: + groups: tuple[AnyStr | Any, ...] = matches.groups() + if groups[0] is not None: + return UniCommand.from_str(groups[0]) + elif groups[1] is not None and groups[2] is not None: + return BiCommand.from_str(groups[1]), groups[2] + elif groups[3] is not None: + return groups[3] + # TODO Exception + pass diff --git a/shared/serverblackjack.py b/shared/serverblackjack.py new file mode 100755 index 0000000..d508b62 --- /dev/null +++ b/shared/serverblackjack.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 - + +import asyncio +import logging +import os + +from server import Server + +if __name__ == "__main__": + logging.basicConfig(level=os.environ.get("LOGLEVEL", "DEBUG")) + server = Server() + asyncio.run(server.run())