Outils pour utilisateurs

Outils du site


jeu_de_la_vie

Ceci est une ancienne révision du document !


Documentation du projet "Jeu de la vie" réalisé pour Centre Sciences en 2019

Introduction:

Cadre

L'organisme CentreSciences met en place une exposition sur l'informatique visant à faire comprendre quelques principes qui sous-tendent “l'intelligence artificielle”. Trois réalisations ont été faites par Labomedia : le Jeu de la vie, le Casque de réalité virtuelle et l'installation Captcha.

Qu'est-ce que le jeu de la vie ?

D'après Wikipedia :

Le jeu de la vie est un automate cellulaire imaginé par John Horton Conway en 1970 et qui est probablement le plus connu de tous les automates cellulaires. Le jeu de la vie n’est pas un jeu, puisqu'il ne nécessite aucun joueur. Il s’agit d’un automate cellulaire, un modèle où chaque état conduit mécaniquement à l’état suivant à partir de règles pré-établies.

Le « jeu » se déroule sur une grille à deux dimensions, théoriquement infinie (mais de longueur et de largeur finies et plus ou moins grandes dans la pratique), dont les cases — qu’on appelle des « cellules », par analogie avec les cellules vivantes — peuvent prendre deux états distincts : « vivante » ou « morte ».

À chaque étape, l’évolution d’une cellule est entièrement déterminée par l’état de ses huit voisines de la façon suivante :

  • Une cellule morte possédant exactement trois voisines vivantes devient vivante (elle naît).
  • Une cellule vivante possédant deux ou trois voisines vivantes le reste, sinon elle meurt.

La configuration de départ détermine entièrement l'évolution future. Il existe des formes remarquables qui donnent naissance à des évolutions particulières: on a par exemple des formes périodiques qui vont faire alterner deux configurations.

Cahier des charges

Dans le cadre d'une exposition, il était nécessaire d'imaginer un dispositif interactif que les visiteurs pourraient manipuler. On s'est donc orienté vers une installation composée d'un plateau de jeu permettant de matérialiser la configuration initiale et un écran qui montrerait l'évolution. Cette évolution serait activée par l'appui sur un bouton ce qui laisse le temps au visiteur d'observer les figures générées et même d'anticiper la génération suivante. Le tout devait être assez compact et transportable.

Réalisation

Les choix techniques ont été les suivants:
la contrainte d'encombrement réduit et de puissance de calcul suffisant a conduit au choix d'une micro-ordinateur pour la partie unité de traitement.
L'écran choisi a une taille de et de résolution ; c'est un écran qui s'interface au micro-ordinateur en HDMI.
Il était initialement question de pions qui se poseraient dans les trous du plateau de jeu. Le choix s'est porté sur des capteurs de pression (Force Sensing Resistance) pour la détection des pions.
Un bouton pour amorcer le jeu et le faire et évoluer et un bouton pour revenir à la configuration initiale ont été ajoutés sur les GPIO du micro-iordinateur.
Enfin, un bouton marche/arrêt du jeu a été intégré. Il est nécessaire d'effectuer un arrêt du

Montage des capteurs dans le plateau de jeu:

Écran d'accueil:

Une étape:

Matériel

Un plateau de jeu dont un carré de 4×4 trous sont évidés de façon à laisser s'enfoncer des billes

16 capteurs FSR pour la détection des billes: Fournisseur Pololu, dimensions 0,2 pouces de diamètre (ce qui correspond à 0,5 cm de diamètre)
Deux boutons d'interaction: démarrage et avance du jeu, retour à zéro

Un bouton marche/arrêt
Une RaspberryPi pour la gestion des événements et l'affichage
Un écran Waveshare 1920×1080 pixels

Logiciel

Un code python pour la prise en compte des billes et l'affichage du jeu de la vie OS Raspbian version Buster (10.4 (?)) Modifications de l'OS:
-prise en compte du bouton marche/arrêt par un démon
-lancement du programme Jeu de la vie au démarrage de l'OS
Sauvegarde du système complet sur une carte SD de secours: procédure
-copie de la carte SD d'origine
-copie sur une autre carte SD
Liste exhaustive du matériel:
Photos
Codes
Le code a été écrit en python et utilise la bibliothèque pygame. Il s'agit de l'adaptation du code de Trevor Appleton, que l'on peut trouver détaillé ici:
http://trevorappleton.blogspot.com/2013/07/python-game-of-life.html

''
import pygame, sys\\
from pygame.locals import *\\
import random\\
import RPi.GPIO as GPIO\\
from time import sleep\\
''
''
GPIOCasesInit = {17:(9,4), 27:(10,4), 22:(11,4), 5:(12,4), 6:(9,5), 13:(10,5), 19:(11,5), 26:(12,5), 18:(9,6), 24:(10,6), 23:(11,6), 25:(12,6), 12:(9,7), 16:(10,7), 20:(11,7), 21:(12,7)}\\
assert len(GPIOCasesInit)==16\\
assert len(set(GPIOCasesInit.values()))==16\\
GPIO.setmode(GPIO.BCM)\\
''
''
for n in GPIOCasesInit:
    GPIO.setup(n, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
    GPIO.setup(14, GPIO.IN, pull_up_down=GPIO.PUD_UP)
    GPIO.setup(15, GPIO.IN, pull_up_down=GPIO.PUD_UP)
''
''
GPIO.add_event_detect(14, GPIO.FALLING)\\
GPIO.add_event_detect(15, GPIO.FALLING)\\
#Number of frames per second\\
FPS = 10\\
 
###Sets size of grid\\
WINDOWWIDTH = 1920\\
WINDOWHEIGHT = 1080\\
CELLSIZE = 40\\
 
#Check to see if the width and height are multiples of the cell size.\\
assert WINDOWWIDTH % CELLSIZE == 0, "Window width must be a multiple of cell size"\\
assert WINDOWHEIGHT % CELLSIZE == 0, "Window height must be a multiple of cell size"\\
 
#Determine number of cells in horizonatl and vertical plane\\
CELLWIDTH = WINDOWWIDTH / CELLSIZE # number of cells wide\\
CELLHEIGHT = WINDOWHEIGHT / CELLSIZE # Number of cells high\\
 
# set up the colours\\
BLACK =    (0,  0,  0)\\
WHITE =    (255,255,255)\\
DARKGRAY = (40, 40, 40)\\
GREEN =    (50,255,100)\\
RED =  (255, 0, 100)\\
 
BoxExample = {1:(3,10), 2:(15,10), 3:(2,11), 4:(4,11), 5:(10,11), 6:(14,11), 7:(16,11), 8:(3,12), 9:(9,12), 10:(10,12), 11:(11,12), 12:(14,12), 13:(16,12), 14:(15,13), 15:(4,16), 16:(9,16), 17:(10,16), 18:(14,16), 19:(16,16), 20:(17,16), 21:(5,17), 22:(8,17), 23:(11,17), 24:(14,17), 25:(15,17), 26:(17,17), 27:(3,18), 28:(4,18), 29:(5,18), 30:(9,18), 31:(11,18), 32:(10,19), 33:(25,17),34:(25,18),35:(26,18)}\\
 
def text_objects(text, font): \\
    textSurface = font.render(text, True, DARKGRAY)\\
    return textSurface, textSurface.get_rect()\\
 
def afficheInit(text, x, y):\\
    largeText = pygame.font.Font('freesansbold.ttf',75)\\
    TextSurf, TextRect = text_objects(text, largeText)\\
    TextRect.center = (x,y)\\
    DISPLAYSURF.blit(TextSurf, TextRect)\\
    pygame.display.update()\\
    #time.sleep(2)\\
 
#Draws the grid lines\\
def drawGrid():\\
    for x in range(0, WINDOWWIDTH, CELLSIZE): # draw vertical lines\\
        pygame.draw.line(DISPLAYSURF, DARKGRAY, (x,0),(x,WINDOWHEIGHT))\\
    for y in range (0, WINDOWHEIGHT, CELLSIZE): # draw horizontal lines\\
        pygame.draw.line(DISPLAYSURF, DARKGRAY, (0,y), (WINDOWWIDTH, y))\\
 
 
def drawGridExample():\\
    for n in BoxExample:\\
    	pygame.draw.rect(DISPLAYSURF, RED, (BoxExample[n][0]*CELLSIZE, BoxExample[n][1]*CELLSIZE, CELLSIZE, CELLSIZE))\\
 
#Colours the cells green for life and white for no life\\
def colourGrid(item, lifeDict):\\
    x = item[0]\\
    y = item[1]\\
    y = y * CELLSIZE # translates array into grid size\\
    x = x * CELLSIZE # translates array into grid size\\
    if lifeDict[item] == 0:\\
        pygame.draw.rect(DISPLAYSURF, WHITE, (x, y, CELLSIZE, CELLSIZE))\\
    if lifeDict[item] == 1:\\
        pygame.draw.rect(DISPLAYSURF, GREEN, (x, y, CELLSIZE, CELLSIZE))\\
 #   pygame.draw.rect(DISPLAYSURF, BLACK, (8*CELLSIZE, 3*CELLSIZE, CELLSIZE, CELLSIZE))\\
    return lifeDict[item]\\
 
#Creation un dictionnaire de l'ensemble des cellules\\
#Toutes les cellules sont "mortes" (valeur 0)\\
def blankGrid():\\
    gridDict = {}\\
    #Creation d un dictionnaire pour toutes les cellules de la grille\\
    for y in range (CELLHEIGHT):\\
        for x in range (CELLWIDTH):\\
            gridDict[x,y] = 0 #Toutes les cellules sont "mortes"\\ 
    return gridDict\\
 
def startingGridInit(lifeDict, GPIOCasesInit):\\
    for ncapt in GPIOCasesInit:\\
		if(GPIO.input(ncapt)!=0):\\
    			lifeDict[GPIOCasesInit[ncapt]] = 1\\
	#	print('pion')\\
	#	print(ncapt)\\
    return lifeDict\\
 
#Determines how many alive neighbours there are around each cell\\
def getNeighbours(item,lifeDict):\\
    neighbours = 0\\
    for x in range (-1,2):\\
        for y in range (-1,2):\\
            checkCell = (item[0]+x,item[1]+y)\\
            if checkCell[0] < CELLWIDTH  and checkCell[0] >=0:\\
                if checkCell [1] < CELLHEIGHT and checkCell[1]>= 0:\\
                    if lifeDict[checkCell] == 1:\\
                        if x == 0 and y == 0: # negate the central cell\\
                            neighbours += 0\\
                        else:\\
                            neighbours += 1\\
    return neighbours\\
 
#determines the next generation by running a 'tick'\\
def tick(lifeDict):\\
    newTick = {}\\
    for item in lifeDict:\\
        #get number of neighbours for that item\\
        numberNeighbours = getNeighbours(item, lifeDict)\\
        if lifeDict[item] == 1: # For those cells already alive\\
            if numberNeighbours < 2: # kill under-population\\
                newTick[item] = 0\\
            elif numberNeighbours > 3: #kill over-population\\
                newTick[item] = 0\\
            else:\\
                newTick[item] = 1 # keep status quo (life)\\
        elif lifeDict[item] == 0:\\
            if numberNeighbours == 3: # cell reproduces\\
                newTick[item] = 1\\
            else:\\
                newTick[item] = 0 # keep status quo (death)\\
    return newTick\\
 
#main function\\
def main():\\
 
    ps14=0\\
    ps15=0\\
    cs14=0\\
    cs15=0\\
#    GPIOCasesInit = {17:(9,4), 27:(10,4), 22:(11,4), 5:(12,4), 6:(9,5), 13:(10,5), 19:(11,5), 26:(12,5), 12:(9,6), 23:(11,6), 25:(12,6), 21:(9,7), 16:(10,7), 20:(11,7)}\\
    etat=0\\
    FLAG_PUSH = 0\\
    pygame.init()\\
    global DISPLAYSURF\\
    FPSCLOCK = pygame.time.Clock()\\
    DISPLAYSURF = pygame.display.set_mode((WINDOWWIDTH,WINDOWHEIGHT), pygame.FULLSCREEN)\\
    pygame.display.set_caption('Game of Life')\\
    DISPLAYSURF.fill(WHITE)\\
#    GPIOCasesInit=valCaptInit()\\
    lifeDict = blankGrid() #Creation un dictionnaire de cellules, initialisation a zero\\
    #lifeDict = startingGridInit(lifeDict,GPIOCasesInit) # Remplissage du dictionnaire de depart\\
    #Colours the live cells, blanks the dead\\
    for item in lifeDict:\\
        colourGrid(item, lifeDict)\\
    drawGrid()\\
    pygame.display.update()\\
    while True: #main game loop\\
       	for event in pygame.event.get():\\
        	if event.type == QUIT:\\
                	pygame.quit()\\
                	sys.exit()\\
		if event.type == pygame.KEYUP:\\
			DISPLAYSURF = pygame.display.set_mode((WINDOWWIDTH,WINDOWHEIGHT), pygame.RESIZABLE)\\
	cs14=GPIO.input(14)\\
	cs15=GPIO.input(15)\\
	if ps14!=cs14:	\\
		print('ps14', ps14)\\
		print('cs14', cs14)\\
	if(etat==0):\\
		afficheInit('Disposez les pions sur le plateau', WINDOWWIDTH/2, WINDOWHEIGHT/2-300)\\
		afficheInit('et appuyez sur SUIVANT', WINDOWWIDTH/2, (WINDOWHEIGHT/2-200))	\\
		afficheInit('Quelques exemples:', WINDOWWIDTH/2, WINDOWHEIGHT/2-100)\\
		drawGridExample()\\
		pygame.display.update()\\
	#passage a etat suivant\\
#		if GPIO.event_detected(14):\\
		if(ps14==1 and  cs14==0):\\
			doit_demarrer=False\\
			nb_viv=0\\
			for elem in GPIOCasesInit:\\
				if(GPIO.input(elem)):\\
					doit_demarrer = True\\
					nb_viv+=1\\
					print(GPIOCasesInit[elem])\\
			print('nb_viv', nb_viv)\\
			if doit_demarrer:		\\			
				etat=1\\
				lifeDict = startingGridInit(lifeDict, GPIOCasesInit)\\
 
		else:\\
			etat=0\\
	elif(etat==1):\\
		if(ps15==1 and cs15==0):\\
#		if GPIO.event_detected(15):
			etat=0
			lifeDict=blankGrid()
		if(ps14==1 and cs14==0):
#		if GPIO.event_detected(14):
        #runs a tick
			nb_viv=0
			lifeDict = tick(lifeDict)
			print('suite')
			for item in lifeDict:
				if(lifeDict[item]):
					nb_viv=nb_viv+1
			print(nb_viv)
			if(nb_viv==0):
				print('vide')
				etat=0
        #Colours the live cells, blanks the dead
        	for item in lifeDict:
            		colourGrid(item, lifeDict)
		drawGrid()
        	pygame.display.update() 
		#if(nb_viv==0):
		#	etat=0  
	ps14=cs14
	ps15=cs15
	sleep(0.1) 
        #FPSCLOCK.tick(FPS)
 
if __name__=='__main__':
    main()
 
 
''

Schéma électrique pour les FSR
Schéma d'intégration
Schéma de câblage sur la Raspberry

Retour d'expérience

FSR:

Tests effectués en mesurant la valeur de la résistance selon la pression exercée :

De 1 je passe à des valeurs plus ou moins pertinentes selon le poids. Difficile sous les 20mm de diamètre de dépasser le seuil des 50 kohms soit un poids de 20 grammes (plexi ou bois) Avec une bille inox de 30mm (110gr), la résistance chute à 4/5 k ohms ; mais elles sont trop facilement volées et très chères. Les billes vertes type calot de 25mm pourraient être un compromis comme les cylindres de pvc denses : test concluant pour un cylindre de 26mm hauteur 50mm poids 45gr résistance mesurée 20kohms.

Il y a quelques incertitudes sur les mesures car faute d’ajustement précis du diamètre d’emboîtements, les objets peuvent toucher les bords, réduisant de fait le poids exercé sur la FSR (et donc la résistance au courant augmente)

Je modifie le plan de la manip avec un entraxe de 30mm et des trous de 27 de diamètre ; la profondeur des inserts sera de 20mm au moins

Le mieux pour simuler la pression du doigt c’est une « goutte d’eau », pieds autocollants antidérapant en forme de demi-hémisphère sur les cylindres

Le 19.07.2019 11:01, Guy Antoine a écrit :
Bonjour Camille
Pour la manip du jeu de la vie, les jetons font 18mm donc découpe à
19mm sur le support de 120×120 selon les plans ci-joint.
Peux tu me valider cette option ? J'ai regardé pour les joncs de 20mm
en plexi mais c'est très “moches” comparés à ceux en bois peints
Donc on vérifieras au règlage s'il faut les lester.

Modifications en vue de la version 2: Il s'avère que la détection par les FSR n'est pas assez robuste. Il faut souvent un appui supplémentaire sur les billes de la part de l'utilisateur. Le mieux serait d'utiliser un contact mécanique du type contacteur ou interrupteur

jeu_de_la_vie.1571411328.txt.gz · Dernière modification : 2019/10/18 15:08 de Mushussu