Interface Graphique Textuel pour Graph 35+ et 90
Posté le 24/07/2021 19:56
Bonjour les amis !
Je créé ce nouveau Topic pour proposer une partie précise des sources de mon projet que j'ai soumis pour le
Jam Python #1
Comme certains l'ont remarqué python (que ça soit sur Graph 35 ou 90) n'est pas le langage le plus adapté pour créer des jeux sous calculatrice.
Malgré tout, j'ai tenté de créer une "Moteur Graphique Textuel" conçu pour python. Il sera donc utilisable normalement pour Graph 35 ou 90.
Je vais donc détailler son fonctionnement ici.
Voici tout d'abord le code au complet :
import math
dim = [30,8]
def Clear():
print("""
"""*(dim[1]+1))
class Curseur:
def __init__(self):
self.LstBoutons=[]
self.index = 0
class Bouton:
def __init__(self,curseur,Txt):
curseur.LstBoutons.append(self)
self.curseur = curseur
self.text = Txt
def GetString(self):
if self.curseur.LstBoutons[self.curseur.index]==self:
return self.text
else:
return self.text.replace("["," ").replace("]"," ")
class Image:
def __init__(self,data):
self.data=data
def GetWidth(name):
w=0
for i in name.split("\n"):
if len(i)>w:
w=len(i)
return w
def GetHeight(name):
return len(name.split("\n"))
def GetImage(name):
if name[0]=="\n":
name=name[1:]
w,h=GetWidth(name),GetHeight(name)
Lst = name.split("\n")
Screen=[]
for i in range(h):
temp=[]
for o in range(w):
temp.append(" ")
Screen.append(temp)
for Y,i in enumerate(Lst):
for X,o in enumerate(i):
(Screen[Y])[X]=o
return Screen
class StringVar:
def __init__(self,data):
self.data=data
class Interface:
def __init__(self):
self.curseur = Curseur()
self.Widgets = []
self._lastCmd=""
def FindIndexOfWidget(self,adress):
for i in range(len(self.Widgets)):
if (self.Widgets[i])[0]==adress:
return i
def Clear(self):
self.Widgets = []
self._lastCmd=""
self.curseur.index = 0
self.curseur.LstBoutons=[]
def Print(self):
Screen=[]
for i in range(dim[1]):
temp=[]
for o in range(dim[0]):
temp.append(" ")
Screen.append(temp)
for i in self.Widgets:
Text = ""
try:
if type(i[0])==Image:
x,y=i[3],i[2]
for indY,y1 in enumerate(i[0].data):
for indX,x1 in enumerate(y1):
try:
(Screen[indY+y])[indX+x]=x1
except:pass
else:
if type(i[0])==str:
Text=i[0]
elif type(i[0])==StringVar:
Text=str(i[0].data)
elif type(i[0])==Bouton:
Text=i[0].GetString()
if i[1]=="PlaceCenter":
x=int((dim[0]/2)-math.ceil(len(Text)/2))
for x1,lettre in enumerate(Text):
(Screen[i[2]])[x+x1]=lettre
elif i[1]=="Locate":
x=i[3]
for x1,lettre in enumerate(Text):
(Screen[i[2]])[x+x1]=lettre
except:
pass
for i in Screen:
line = ""
for o in i:
line+=o
print(line)
def Draw(self):
self.Print()
cmd = input()
self._lastCmd = cmd
if cmd=="":
return self.curseur.index,cmd,self.curseur.LstBoutons[self.curseur.index]
else:
return None,cmd,None
def RemoveWidget(self,adress):
if type(adress)==Bouton:
self.curseur.LstBoutons.remove(adress)
self.curseur.index=0
for i in range(len(self.Widgets)):
if (self.Widgets[i])[0]==adress:
del self.Widgets[i]
return
def PlaceCenter(self,obj,Ln):
self.Widgets.append([obj,"PlaceCenter",Ln])
def Locate(self,obj,Ln,Col):
self.Widgets.append([obj,"Locate",Ln,Col])
def UpdateCursor(self):
if self._lastCmd in ["8","4","6","2"]:
x,y=0,0
for i in self.Widgets:
if type(i[0])!=Bouton:
continue
Text = i[0].GetString()
if i[0]==self.curseur.LstBoutons[self.curseur.index]:
if i[1]=="PlaceCenter":
x=int((dim[0]/2)-math.ceil(len(Text)/2))
y=i[2]
elif i[1]=="Locate":
x=i[3]
y=i[2]
direction=[None,100]
for i in self.Widgets:
if type(i[0])!=Bouton:
continue
dis = 100
x1,y1=0,0
Text = i[0].GetString()
if i[1]=="PlaceCenter":
x1,y1= int((dim[0]/2)-math.ceil(len(Text)/2)),i[2]
elif i[1]=="Locate" :
x1,y1= i[3],i[2]
dis = math.sqrt(((y1-y)**2)+ (x1-x)**2)
if self._lastCmd=="8":
if y1<y and dis<direction[1]:
direction=[i[0],dis]
if self._lastCmd=="2":
if y1>y and dis<direction[1]:
direction=[i[0],dis]
if self._lastCmd=="4":
if x1<x and dis<direction[1]:
direction=[i[0],dis]
if self._lastCmd=="6":
if x1>x and dis<direction[1]:
direction=[i[0],dis]
if direction[0]!=None:
self.curseur.index = self.curseur.LstBoutons.index(direction[0])
def WaitBouttonInput(self):
retour = None
while retour == None:
retour , _ , _ = self.Draw()
if retour == None:
self.UpdateCursor()
return retour
def BoiteConfirmValide(txt):
interface = Interface()
txt = txt.split(" ")
interface.Locate("#"*dim[0],0,0)
y=1
while len(txt)>0:
msg=""
while len(txt)>0 and len(msg)+len(txt[0])<dim[0]-3:
msg+=" "+txt.pop(0)
interface.Locate(msg,y,0)
y+=1
interface.Locate("#"*dim[0],dim[1]-1,0)
interface.Locate(Bouton(interface.curseur," [OUI] "),dim[1]-1,int((dim[0]/4)-math.ceil(7/2))+1)
interface.Locate(Bouton(interface.curseur," [NON] "),dim[1]-1,int((3*dim[0]/4)-math.ceil(7/2)))
return interface.WaitBouttonInput() == 0
def BoiteConfirmOK(txt):
interface = Interface()
txt = txt.split(" ")
interface.Locate("#"*dim[0],0,0)
y=1
while len(txt)>0:
msg=""
while len(txt)>0 and len(msg)+len(txt[0])<dim[0]-3:
msg+=" "+txt.pop(0)
interface.Locate(msg,y,0)
y+=1
interface.Locate("#"*dim[0],dim[1]-1,0)
interface.PlaceCenter(Bouton(interface.curseur," [OK] "),dim[1]-1)
interface.Draw()
return True
def MainMenu(Titre):
Clear()
interface = Interface()
interface.PlaceCenter((len(Titre)+2)*"=",0)
interface.PlaceCenter(" "+Titre+" ",1)
interface.PlaceCenter((len(Titre)+2)*"=",2)
interface.PlaceCenter(Bouton(interface.curseur,"[Commencer une partie]"),4)
interface.PlaceCenter(Bouton(interface.curseur,"[Entrer un code] "),5)
interface.PlaceCenter(Bouton(interface.curseur,"[Quitter] "),7)
return interface.WaitBouttonInput()
La variable Dim est très importante, comme vos aurez pus le deviner c'est les dimensions de l'écran. (pour Graph 35 les dimensions sont 30x8)
La fonction Clear permet de "nettoyer" l'écran. Pour être précis, l'écran n'est jamais nettoyé, il défile plutôt, c'est-à-dire que si vous appuyez sur la flèche du haut, vous vos apercevrez qu'il y a les anciens affichages.
La Class Curseur sert à pointer un Bouton (c'est comme une souris d'ordinateur)
La Class Bouton est comme son nom l'indique permet de créer un bouton, on lui donne un curseur et son texte en entrée.
Il est obligatoire de mettre entre crochets le texte que vous souhaitez (exemple : "[Continuer]")
Exemple de bouton :
curseur = Curseur()
Bouton(curseur,"[Commencer une partie]")
Mais jusqu'à présent tout ça ne sert a rien sans la Class "Interface".
Cette Class permet de manipuler facilement les éléments présentés ci-dessus.
C'est à dire qu'elle permet de se balader facilement sur un menu par exemple et se charge de l'affichage ainsi que l'ordre de déplacement du curseur sur les Boutons.
Pour placer un élément on peut utiliser les fonctions PlaceCenter et Locate.
PlaceCenter permet de placer au centre d'une ligne l'élément voulu. (PlaceCenter(élément,ligne)
Exemple :
interface = Interface()
interface.PlaceCenter("Tire du jeu",0)
interface.PlaceCenter(Bouton(interface.curseur,"[Commencer une partie]"),4)
Quant à Locate, cette fonction permet de placer un élément à des coordonnées exactes. (Locate(élément,ligne,colonne)
Exemple :
interface = Interface()
interface.Locate("Coucou",0,0)#place "coucou" sur le coin supérieur gauche
interface.Locate("Coucou",dim[1]-1,0)#place "coucou" sur le coin inférieur gauche
Maintenant qu'on a placé les éléments, on peut les afficher, et il existe 3 manières de le faire.
-Print
-Draw
-WaitBouttonInput
Print affiche juste l'interface. (cette fonction renvoie None)
Quant à Draw, cette fonction effectue un Print puis attend une entrée de l'utilisateur. (cette fonction renvoie l'index du curseur,l'entrée brut et le bouton qui est sélèctionné par le curseur.
Pour finir, la fonction WaitBouttonInput (qui est la plus conseillé) attent que l'utilisateur valide sont choix (renvoie l'index du bouton sélèctionner).
Exemple:
def Confirm():
interface = Interface()
interface.PlaceCenter("Voulez-vous quittez ?",0)
interface.PlaceCenter(Bouton(interface.curseur," [OUI] "),3)#index = 0 car initialisé en premier
interface.PlaceCenter(Bouton(interface.curseur," [NON] "),4)#index = 1 car initialisé en second etc...
return interface.WaitBouttonInput() == 0#renvoie True si [OUI] est sélectionné
Comme python est capricieux, il n'existe pas de GetKey, il a fallu créer un système pour contrôler l'interface.
Tout d'abord, oubliez la croix directionnelle de votre calculatrice !
La croix directionnelle est remplacée par les touches 2,4,6,8.
8 : Haut
4 : gauche
6 : droite
2 : bas
Chaque action doit être validée par la touche EXE
(par exemple si vous voulez aller en haut appuyez sur 8 puis EXE)
Autrement dit, pour effectuer une action vous devez appuyer sur au moins deux touches.
Vous pouvez aussi supprimer les éléments d'une interface en utilisant RemoveWidget.
Exemple :
interface = Interface()
interface.PlaceCenter("coucou",0)
interface.RemoveWidget("coucou")
Exemple 2 :
interface = Interface()
btn=Bouton(interface.curseur,"[Commencer une partie]")
interface.PlaceCenter(btn,4)
interface.RemoveWidget(btn)
Maintenant je vais vous présenter d'autre type qui existe : Image et StringVar
Oui c'est possible d'afficher des images faites en caractère ascii
Exemple : un coffre
__________
/\____;;___\
| / /
./_________/
|\ \
| |---------|
\ | )) |
\|_________|
Exemple 2 : Un soldat
|
_|_ o __
I /|\)_)
/ \
Le double slash est indispensable donc les images ci-dessus devrai être :
player1="""
|
_|_ o __
I /|\\)_)
/ \\
"""
chest="""
__________
/\\____;;___\\
| / /
./_________/
|\\ \\
| |---------|
\\ | )) |
\\|_________|
"""
Ensuite, il faut transformer cette chaine de caractère en Image donc pour faire ça voici le code :
player1_Image=Image(GetImage(player1))
Puis on peut l'afficher sur l'interface avec Locate (PlaceCenter ne fonctionne pas pour les Images)
Exemple :
player1_Image=Image(GetImage(player1))
interface = Interface()
interface.Locate(player1_Image,0,0)
Pour finir, la Class StringVar permet de changer le texte de l'interface.
Exemple sans StringVar:
interface = Interface()
interface.PlaceCenter("test",0)
interface.RemoveWidget("test")
interface.PlaceCenter("test 2",0)
Exemple avec StringVar:
interface = Interface()
txt = StringVar("test")
interface.PlaceCenter(txt,0)
txt.data = "test 2"
Une interface doit toujour contenir au moins un bouton même s'il est invisible
Exemple de bouton invisible :
interface = Interface()
interface.Locate(Bouton(interface.curseur,""),-1,-1)
Dans le code intégral vous trouverez trois fonctions supplémentaire : BoiteConfirmValide et BoiteConfirmOK et MainMenu
Dont je vous laisse le soin de découvrir leur utilité par vous même.
Bug que vous allez sûrement rencontrer :
"Maximum recursion depth exceeded"
Ce bug arrive quand vous faites trop d'appel d'initiation de class à la suite.
Exemple où ça risque de planter :
Class A:
def __init__(self):
pass
Class B:
def __init__(self):
self.a = a()
Class C:
def __init__(self):
self.b = B()
Class D:
def __init__(self):
self.c = C()
d= D()
Donc pour palier à cela, vous pouvez transformer ce code :
class Fight:
def __init__(self,plateau,monster):
self.interface = Interface()
etc...
class PlateauDeJeu:
def __init__(self,x,y):
self.fight = Fight(self,"Monstre1")
etc...
pdj = PlateauDeJeu(0,0)
en :
interfaceFight=Interface()
class Fight:
def __init__(self,plateau,monster):
interfaceFight.Clear()
self.interface = interfaceFight
etc...
class PlateauDeJeu:
def __init__(self,x,y):
self.fight = Fight(self,"Monstre1")
etc...
pdj = PlateauDeJeu(0,0)
Voilà, j'espère que cela vous sera utile.
Citer : Posté le 26/08/2021 00:19 | #
Hey o/
Pour éviter les doubles slash, tu peux utiliser des r-docstrings :
|
_|_ o __
I /|\)_)
/ \
"""
Voila
Citer : Posté le 08/09/2021 21:22 | #
Ha merci je savais pas
Albert Einstein
Citer : Posté le 08/09/2021 21:23 | #
Mais de rien