Planetes

Orbite ou crash !

L'auteur de ce sujet a trouvé une solution à son problème.
Auteur du sujet

Bonjour à tous ! Je vous présente un petit projet que j'ai fait en python + pygame.

J'ai découvert Linux grâce à des liveCD, et à l'époque dessus il y avait un jeu qui s'appelait "planets" dont le but était… de faire tourner des planètes. Cependant je ne retrouve plus ce jeu :/ . De plus y'a un petit moment sur ZdS on avait parlait (ici) de gravitation. J'ai donc réutilisé ce qu'on avait dit et j'ai recodé une partie de ce jeu.

Le jeu

Il faut faire orbiter les planètes. C'est très con comme jeu. Mais j'aime bien :)

En cliquant avec la sourie on fait apparaître une planète avec une vitesse initiale. Et puis alea jacta est. Avec la roulette on zoom et avec le clic droit on se déplace dans l'image. Les planètes on une masse dépendante de notre niveau de zoom, ce qui permet de faire des grosses/petites planètes.

Lorsque 2 planètes entrent en collision, elles fusionnent (c'est pas un jeu réaliste on a le droit :D).

Code

C'est du python3 avec pygame+numpy. Je précise que c'est la première fois que j'utilise python, et encore plus pygame du coup. C'est d'ailleurs pour ca que je post ici : si vous avez des critiques sur le code je suis preneur !

Dépendances

pour python 2
  • installer python2-pygame
  • installer python2-numpy
pour python 3
  • installer python-pygame-hg (sous arch, dans AUR, sinon je sais pas pour les autres distrib')
  • installer python-numpy

Le plus simple reste donc de le faire tourner en python2 puisque ca marche correctement en python2 et que pygame s'y trouve facilement.

Le nom des paquets marche sous Arch, je garantie pas sous d'autre distrib'

Le programme à lancer est : planetes.py, qui appelle ObjCelest et newOC contenu dans objCelest.py, qui sont les 2 classes du jeu.

Mettre planetes.py et objCelest.py dans le même répertoire et python planetes.py lance le jeu (pas oublier de le mettre en exécutable avant).

La configuration d'écran de base est 650x650, mais c'est facilement modifiable (winw et winh au début de planetes.py).

Je n'ai pas de github ou autre, du coup je vous met les 2 fichiers comme ca.

planetes.py

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
# coding: utf8 

from objCelest import ObjCelest, newOC
import numpy as np
from math import *
import pygame
from pygame.locals import *
from random import *

pygame.init()
pygame.display.set_caption("L'art de tourner autour du problème...", "Spine Runtine")
myfont = pygame.font.SysFont("monospace", 15)
fontPause = pygame.font.SysFont("monospace", 30)
winw = 700              # windows width
winh = 700              # windows height
bg = (0,0,0)            # background
myclock = pygame.time.Clock()
fpsLim = 50
userDone = False
UnitPxRange = range(10,1000,1)
UnitPx=float(UnitPxRange[190])
bouge=False

# ======= fonctions
def afficheAide():
   aide=True
   ligne = []
   ligne.append("========== Guide des boutons ===========")
   ligne.append("a : efface toutes les planetes")
   ligne.append("p : met le jeu en pause")
   ligne.append("h : affiche cet aide")
   ligne.append("Echap : quitte le jeu")
   ligne.append("")
   ligne.append("~~~~~~ Gestion de la sourie ~~~~~~")
   ligne.append("Bouton gauche : créer une planète.")
   ligne.append("     Maintenez enfoncé pour une vitesse initiale.")
   ligne.append("     Relachez ou cliquez sur un autre bouton")
   ligne.append("     de la sourie pour l'envoyer")
   ligne.append("Bouton droit : déplace l'écran.")
   ligne.append("     Maintenez enfoncé, déplacez et relachez le bouton.")
   ligne.append("Roulette : zoom")
   ligne.append("")
   ligne.append("Appuyez sur 'p' ou 'echap' pour revenir au jeu.")
   pygame.draw.rect(caneva, (0,0,0), (int(winw/5), 100, 500, len(ligne)*15))
   for i,text in enumerate(ligne):
       text = myfont.render( text, 1 , (0,0,255))
       caneva.blit(text, (int(winw/5), int(100+i*15)))
   pauseFonction(True)

def coord2px(pos, UnitPx, winw, winh):
   """ tranforme les coordonees en px """
   xPx = int(pos[0]*UnitPx + int(winw/2))
   yPx = int(-pos[1]*UnitPx + int(winh/2))

   return np.array([xPx, yPx])

def px2coord(Px, UnitPx, winw, winh):
   """ transforme les px en coordonees """
   posx = float(Px[0] - winw/2)/UnitPx
   posy = -float(Px[1] - winh/2)/UnitPx

   return np.array([posx, posy])

def affichage(listOC, UnitPx, winw, winh, caneva):
   """ affiche les planetes et du texte"""
   for oc in listOC:
       [xPx, yPx] = coord2px(oc.pos, UnitPx, winw, winh)
       color = oc.color
       r = int(oc.r*UnitPx)
       pygame.draw.circle(caneva, color , [xPx, yPx], r, 0)
   # render text
   label = myfont.render("Il y a %d planete(s)" % len(listOC), 1, (0,0,255))
   label2 = myfont.render("Echelle : 1 = %d px" % UnitPx, 1, (0,0,255))
   caneva.blit(label, (10, 10))
   caneva.blit(label2, (10,30))

def drag(listOC, move):
   for oc in listOC:
       oc.pos += move
       oc.posOld += move

def pauseFonction(pause):
   pausetext = fontPause.render("Pause", 1, (255,255,0))
   caneva.blit(pausetext, (winw/2-50, winh/2))
   pygame.display.flip()
   userDone = False
   while pause:
       for event in pygame.event.get():
           if (event.type == pygame.KEYDOWN and event.key == K_p):
               pause = False
           elif (event.type == pygame.QUIT or (event.type == pygame.KEYDOWN and event.key == K_ESCAPE)):
               pause = False
               userDone = True
               pygame.time.wait(10)
   return userDone

# ===== Initialise la fenetre =======================================
caneva = pygame.display.set_mode((winw,winh))
caneva.fill(bg)

# ===== Initialise les planetes =====================================
listOC = [] # Contient la liste des objets celestes
# listOC(masse, vitesse, position)
listOC.append(ObjCelest(1.0,  [0.,0.],  [0.,0]))
listOC.append(ObjCelest(0.01, [0,0.8],    [-1.5,0]))
listOC.append(ObjCelest(0.001, [0,1.175],    [-1.6,0]))
new = newOC(0, np.array([0.,0.]), np.array([0.,0.]), 0) # contient la planète en formation lors d'un clic


# ===== affiche l'aide ==============================================
afficheAide()
# ===== Et c'est partie !!! =========================================
# pas de temps
dt = 1e-2

# Premier pas de temps
for oc in listOC:
   oc.vitesse4pos(oc.v,dt)

while not userDone:

   myclock.tick(fpsLim)

   # Quel bouton de la sourie est appuye
   if (pygame.mouse.get_pressed()[0]): # bouton gauche
       # Bouton gauche, ajout d'une planete
       # On stock temporairement sa vitesse (utilisateur), jusqu'a ce que le bouton soit relache
       [posxtmp, posytmp] = px2coord(pygame.mouse.get_pos(), UnitPx, winw, winh)
       vx = (posxtmp - posxNew) * 1
       vy = (posytmp - posyNew) * 1
       mass = pi * (10/UnitPx)**2
       new = newOC(mass, [vx, vy], [posxNew, posyNew], 1)
   elif pygame.mouse.get_pressed()[2]: # bouton droit
       # On stock temporairement jusqu'ou on veut se deplacer, jusqu'a ce que le bouton soit relache
       [posxBougetmp, posyBougetmp] = px2coord(pygame.mouse.get_pos(), UnitPx, winw, winh)
       bouge = 1 # booleen pour le deplacement

   # ===== gestion des evenements
   for event in pygame.event.get():

       if (event.type == pygame.QUIT):
           userDone = True
       elif (event.type == pygame.KEYDOWN):
           if (event.key == K_ESCAPE):
               userDone = True
           elif (event.key == K_h):
               afficheAide()
           elif (event.key == K_a):
               # supprime les objets celestes
               del listOC
               listOC=[]
           elif (event.key == K_p):
               # ===== mise en pause
               pause = True
               userDone = pauseFonction(pause)

       if (event.type == pygame.MOUSEBUTTONDOWN):
           if event.button == 1:
               # Ajout d'une planete, on note les coordonees du clic
               [posxNew, posyNew] = px2coord(event.pos, UnitPx, winw, winh)
           elif event.button == 3:
               # On bouge l'ecran, on note les coordonees du clic
               [posxBouge, posyBouge] = px2coord(event.pos, UnitPx, winw, winh)
           elif event.button == 4 and UnitPx < len(UnitPxRange): # zoom in
               UnitPx = float(UnitPxRange[int(UnitPx)])
           elif event.button == 5 and UnitPx > 10: # zoom out
               UnitPx = float(UnitPxRange[int(UnitPx)-20])
       elif (event.type == pygame.MOUSEBUTTONUP and new.ok): # on relache un bouton donc on creer une planete
           # ajout de la planete
           listOC.append(ObjCelest(new.m, new.v, [posxNew, posyNew]))
           # calcul de sa vitesse et de son premier deplacement
           oc = listOC[-1]
           oc.vitesse4pos(oc.v, dt)
           new = newOC(0, np.array([0.,0.]), np.array([0.,0.]), 0) # remise a zero
       elif (event.type == pygame.MOUSEBUTTONUP and bouge):
           # on bouge (deplace l'ecran)
           movex = posxBougetmp - posxBouge
           movey = posyBougetmp - posyBouge
           drag(listOC, np.array([movex, movey]))
           bouge = False

   # ===== Gestion des collisions ==================================
   # Ne gere qu'une collision par loop
   # sinon risque de supprimer une planetes qui est dans 2 collisions
   # et error not found...
   for oc in listOC:
       collision = oc.collision(listOC)
       if collision != []:
           msum = collision[0].mass + collision[1].mass
           #V0 = (collision[0].pos-collision[0].posOld)/dt
           #V1 = (collision[1].pos-collision[1].posOld)/dt
           #Vf = (collision[0].mass*V0 + collision[1].mass*V1)/(msum)
           #print(Vf)
           if collision[0].mass>collision[1].mass:
               collision[0].mass=msum
               collision[0].r=sqrt(msum/pi)
               listOC.remove(collision[1])
           else:
               collision[1].mass=msum
               collision[1].r=sqrt(msum/pi)
               listOC.remove(collision[0])
           break # on ne gère qu'une collision par loop, donc on s'arrete la.

   # ===== Deplacement =============================================
   for oc in listOC:
       oc.acceleration(listOC)

   for oc in listOC:
       # x(n+1)=2x(n)-x(n-1)-a(n)*dt^2
       oc.posNplusUn = 2*oc.pos - oc.posOld + oc.a * dt**2
       oc.posOld = oc.pos
       oc.pos = oc.posNplusUn

   # ===== Affichage ===============================================
   caneva.fill(bg)
   affichage(listOC, UnitPx, winw, winh, caneva )
   
   if new.ok: # tracer de la planete en cours d'initialisation
       [posxtmpPx, posytmpPx] = coord2px([posxtmp, posytmp], UnitPx, winw, winh)
       [posxNewPx, posyNewPx] = coord2px([posxNew, posyNew], UnitPx, winw, winh)
       pygame.draw.line(caneva, (255,0,255) , [posxNewPx, posyNewPx], [posxtmpPx, posytmpPx], 1)
       pygame.draw.circle(caneva, (255,0,255), [posxNewPx, posyNewPx], int(10), 1)
   
   pygame.display.flip()

C'était le plus gros, le suivant il est petit :D

objCelest.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# coding: utf8 

import numpy as np
from math import *
from random import *

class ObjCelest:
   """Definit la class pour tous les objets celestes"""
   nbObjCelest=0
   
   listofcolor=[]
   for i in range(50,250,10):
       for j in range(40,250,10):
           for k in range(50,250,10):
               listofcolor.append((i,j,k))

   def __init__(self, m, v, pos):
       """Initialise les caracteristiques de l'objet celeste
       ID, couleur, masse, rayon, vitesse, acceleration et position.
       Le rayon est déterminer par masse = pi * r**2
       L'acceleration est mise à jour ensuite
       posOld est la position l'instant d'avant"""
       self.r = sqrt(m/pi)
       self.mass = m
       self.v = np.array(v)
       self.a = np.array([0.,0.])
       self.pos = np.array(pos)
       self.posOld = np.array(pos)
       self.ID = ObjCelest.nbObjCelest
       self.color = choice(ObjCelest.listofcolor)
       ObjCelest.nbObjCelest += 1

   def distance(self, obj):
       """ Retourne la distance entre lui est un autre objet"""
       D = sqrt( (self.pos[0]-obj.pos[0])**2 + (self.pos[1]-obj.pos[1])**2)
       return D
   def vitesse4pos(self, v, dt):
       """Mise à jour de la position de l'objet grace à sa vitesse """
       self.pos += v*dt

   def acceleration(self, listOC):
       """Renvoi l'acceleration de l'objet "self" due aux autres objet celestes" """
       self.a = np.array([0.,0.])
       for obj in listOC:
           if obj.ID != self.ID:
               D = self.distance(obj)
               ax =  obj.mass * (obj.pos[0]-self.pos[0]) / D**3
               ay =  obj.mass * (obj.pos[1]-self.pos[1]) / D**3
               self.a += np.array([ax, ay])

   def collision(self, listOC):
       """Détermine si l'objet rentre en collision avec un autre """
       collision=[]
       for oc in listOC:
           if oc.ID != self.ID:
               D = self.distance(oc)
               if (D < self.r + oc.r):
                   collision.append(self)
                   collision.append(oc)
                   return collision
       return collision

class newOC():
   """ nouvel objet celeste en formation """
   def __init__(self, m, v, pos, okornot):
       """ on lui donne une masse, vitesse et position.
       okornot (booleen) permet de le placer ou non sur l'ecran et donc de le prendre en consideration """
       self.m = m
       self.v = v
       self.pos = pos
       self.ok = okornot

Capture d'écran

Ouverture du jeu

Au bout d'un moment...

Lors du démarrage du jeu il y a 3 planètes qui orbitent tranquillement. Tout se passe bien sauf qu'au bout d'un moment la "lune" se sépare de sa "planète" et orbite autour du "soleil", pour finalement se crasher sur sa planète :P . Appuyez sur a pour supprimer les planètes.

Je suis preneur de toutes remarques, tant sur la forme que sur le fond. Sachant que c'est volontairement un jeu pas très évolué et simple.

Édité par Gwend@l

+1 -0

Cette réponse a aidé l'auteur du sujet

Ton mini jeu a l'air cool.

J'ai C-c C-v les deux fichiers et voulu les lancer. Apres un problème d'encoding, je retente avec python3. Mais c'est impossible parce ImportError: No module named 'numpy'. Un petit pip install numpy et toujours le même problème.

Je fait jamais de python, il faut que tu fasse une procédure d'installation plus précise, là j'ai la flemme de chercher.

+0 -0
Auteur du sujet

En effet. J'ai oublié de précisé qu'il y a besoin de numpy. Selon ton gestionnaire de paquet ca doit être un truc du genre

sudo apt-get install python-numpy

J'avais oublié que numpy est pas de base dans python.

Édité par Gwend@l

+0 -0

Cette réponse a aidé l'auteur du sujet

J'ai un petit soucis avec l'encodage de ton fichier apparemment:

File "planetes.py", line 10 SyntaxError: Non-ASCII character '\xc3' in file planetes.py on line 10, but no encoding declared;

(je suis un noob en python donc je n'ai aucune idée de comment régler ça :D )

Bon ça y est j'ai tout remplacé, il y avait aussi une erreur d'import:

Traceback (most recent call last): File "planetes.py", line 2, in <module> from newOC import newOC ImportError: No module named newOC

Donc j'ai changé la ligne d'import:

from newOC import newOC –> from objCelest import newOC

En ce qui concerne le jeu, je galère à faire tourner les planètes :P

Édité par albert733

+0 -0

Cette réponse a aidé l'auteur du sujet

En effet. J'ai oublié de précisé qu'il y a besoin de numpy. Selon ton gestionnaire de paquet ca doit être un truc du genre

sudo apt-get install python-numpy

J'avais oublié que numpy est pas de base dans python.

Gwend@l

Tu peux aussi l'installer avec pip, dans un environnement virtuel si tu veux. :)

+0 -0
Staff

Cette réponse a aidé l'auteur du sujet

Et il faut penser à installer la version pour python3 et non python2 si c'est celui que tu utilise. Au final un setup.py faciliterait les choses.

(NB: si vous avez pas de raisons de l'installer avec PIP, numpy est une des rares bibliotèque que je conseillerai d'installer via le genstionnaire de paquet de la distrib car elle prend des plombes a compiler/installer et necessite des dépendances à la con (compilateur fortran, blas, lapack, etC.))

+0 -0

OMG x)

C'est tellement dur …

Essaye de rajouter un truc pour prévoir la trajectoire au-moins sur quelques pixels.

ache.one                                                                                   🦊

+0 -0
Auteur du sujet

Merci pour l'erreur d'import. J'ai pas copier/coller la bonne version x) Maintenant c'est bon. J'ai également rajouté un # coding: utf8 au début de chaque fichier. Je pense que c'est sensé résoudre les problèmes d'encodage.

Je connais pas python, je vais donc jeter un coup d'oeil au setup.py voir ce que c'est :) je met à jour dès que c'est fait ! Merci !

OMG x)

C'est tellement dur …

Essaye de rajouter un truc pour prévoir la trajectoire au-moins sur quelques pixels.

ache

Faut choper le coup de main :D J'ai réussi à faire soleil + terre + lune au clicodrome x) M'enfin je connais comment le code fonctionne. L'astuce c'est de dézoomer, cliquer plusieurs fois au même endroit (ca fait une grosse planète), puis zoomer pour faire une petite planète à coté de la grosse (après faut doser la vitesse :P ).

À savoir que plus tu zoom, plus la distance est petite donc plus les déplacements apparents sont rapides.

Niveau performance par contre faut mieux pas aller au delà de 100 planètes… Mais par contre on voit se dessiner les lignes de champs c'est rigolo :) J'avais commencé avec un carré.

ligne de champs

L'idée c'était d'en mettre un nombre suffisant pour considérer le champs de pesanteur identique en tout point et donc que rien ne bouge, mais ca demande trop de ressources "une infinité de planètes équirépartites".

EDIT : Il semble que ca marche également avec python2. Chez moi ca marche.

EDIT2: Le temps que je comprenne comment fonctionne les setup.py, voilà la démarche à faire manuellement :

pour python 2
  • installer python2-pygame
  • installer python2-numpy
pour python 3
  • installer python-pygame-hg (sous arch, dans AUR, sinon je sais pas pour les autres distrib')
  • installer python-numpy

Le plus simple reste donc de le faire tourner en python2 puisque ca marche correctement en python2 et que pygame s'y trouve facilement.

Édité par Gwend@l

+0 -0
Vous devez être connecté pour pouvoir poster un message.
Connexion

Pas encore inscrit ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte