Como puedo agregar Sistema de Guardado a mi juego?

Hola. En mi escuela tengo que realizar un juego y me entusiasme tanto que realice las imágenes tanto de los personajes como lo demás, incluso la música. Mi juego es un RPG, Y para los RPG las historias son fundamentales. Y no solo eso, sino que también es importantisimo el guardado. Sino se pude guardar el progreso que caso tiene progresar en el juego. Por lo menos, si Pilas Engine no tiene algún sistema de guardado, Me gustaría saber como pudo hacer un sistema de guardado en Python que pueda ser leído por Pilas.
Igual, tengo la intención de hacer mi juego un exe, para que no se tenga que jugar en el interprete y para poderlo pasárselo a mis amigos y de paso proteger el código fuente,
Alguien tiene la solución?

Hola @Leyaud, pilas no trae un sistema de guardado propio. Sin embargo, como mencionabas, python sí tiene un sistema de guardado que te permite hacer esa parte del trabajo bastante fácil. Te paso un ejemplo de cómo se pueden guardar archivos en python:

Lo que llaman “pickle” en python es solo una de las formas de guardar datos en archivos. También podrías usar archivos de texto regulares si lo ves más conveniente en tu caso.

Con respecto al otro tema que nos consultas, sí, pilas puede generar un archivo .exe para que puedas compartir tu juego con otras personas. Pero tienes que seguir un proceso manual para generarlo, acá en el foro hay algunas instrucciones sobre cómo lograrlo:

Saludos!

Hola. Muchas gracias por tu respuesta. La verdad lo de los Pickles me sirvio una banda. Pero te consulto si conoces alguna forma de hacer que un juego mande información a otro juego.(Con Python) Básicamente que puedan enviar y recibir formación entre ellos. Porqué estaba pensando en incluir un “modo multijugador” de manera inalámbrica en mi juego(específicamente una red LAN).
Creo estar consiente de que para ello los dos dispositivos que estan ejecutando el juego deben estar conectados a la misma red. ¿Pero como hago la transferencia de datos?. Muchísimas Gracias por tu atención.

Hola @Leyaud, se me ocurre una forma pero no estoy seguro si es la mejor, o la más conveniente en tu caso:

Se me ocurre que podrías tener un programa separado, que actúe como servidor y corra en un equipo de tu red, con una IP conocida. Ese servidor, podría escuchar conexiones de otros equipos de la red y recibir y enviar mensajes.

Del lado de pilas, no tendías que escuchar conexiones. Solo tendrías que abrir una conexión a ese servidor, usando la IP y el puerto del servidor, y enviarle datos reales de tu personaje.

El servidor va a funcionar como si se tratara de una “sala” de juego, así que en teoría le podrías conectar varios juegos hechos con pilas en distintas máquinas.

Te paso un ejemplo de programa servidor en python que armé mirando otros de internet, creo que te puede servir. Tendrías que ejecutarlo desde una consola:

import select, socket, sys, Queue

PUERTO = 8989
LIMITE = 5
IP = '0.0.0.0'

servidor = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
servidor.setblocking(0)
servidor.bind((IP, PUERTO))
servidor.listen(LIMITE)

print("Iniciando el servidor en {}:{}".format(IP, PUERTO))
print("Esperando conexiones")

inputs = [servidor]
outputs = []
message_queues = {}

while inputs:
    readable, writable, exceptional = select.select(inputs, outputs, inputs)

    for s in readable:
        if s is servidor:
            connection, client_address = s.accept()
            print("Aceptando conexion")
            connection.setblocking(0)
            inputs.append(connection)
            message_queues[connection] = Queue.Queue()
            print("hay {} clientes conectados".format(len(inputs) -1))
        else:
            data = s.recv(1024)

            if data:
                message_queues[s].put(data)

                if s not in outputs:
                    outputs.append(s)
            else:
                print("Desconectando cliente")

                if s in outputs:
                    outputs.remove(s)

                inputs.remove(s)
                s.close()
                del message_queues[s]
                print("hay {} clientes conectados".format(len(inputs) -1))

    for s in writable:
        try:
            next_msg = message_queues[s].get_nowait()
        except Queue.Empty:
            outputs.remove(s)
        else:
            s.send(next_msg)

    for s in exceptional:
        inputs.remove(s)

        if s in outputs:
            outputs.remove(s)

        s.close()
        del message_queues[s]

Este servidor simplemente se queda escuchando conexiones, y cuando recibe una espera que le manden un mensaje y contesta lo mismo.

Algo así. En la consola de atrás está ejecutando el servidor:

Si queres probar cómo sería enviarle mensajes te paso otro script que actúa como cliente:

import socket
import time

IP = "localhost"
PUERTO = 8989

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print("Intentando conectar a {}:{}".format(IP, PUERTO))
s.connect((IP, PUERTO))


print("enviando HOLA")
s.sendall('HOLA')
data = s.recv(1024)
print('Le han contestado: ' + repr(data))

print("Esperando 5 segundos para desconectarse")
time.sleep(5)

print("enviando chau")
s.sendall('chau')
data = s.recv(1024)
print('Le han contestado: ' + repr(data))
s.close()
print("cerrando conexion")

No llegué a hacer una prueba muy grande, pero entiendo que el servidor te puede servir como punto de partida. Mientras que el script cliente te puede dar algunas pistas de cómo armar la comunicación desde pilas.

Por ejemplo, me imagino que tu juego en pilas podría comenzar conectándose al servidor así:

import socket


IP = "localhost"
PUERTO = 8989

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print("Intentando conectar a {}:{}".format(IP, PUERTO))
s.connect((IP, PUERTO))

Luego, esa variable s será el canal de comunicación con el servidor, cuando tu personaje se mueve podrías avisarle al servidor su posición actualizada así, por ejemplo dentro del método actualizar del actor:

s.sendall('pos:{},{}'.format(self.x, self.y))

Con respecto a tu consulta de transferencia de datos, hay varias formas, la más común es enviar texto de un equipo a otro. En esta última linea de código lo que le va a llegar al servidor es una cadena de la forma “pos:20,30”. No siempre tiene que ser así, podrías pasar datos mas complejos convirtiéndolos a json por ejemplo (https://programacion.net/articulo/como_trabajar_con_datos_json_utilizando_python_1403)

Lamento no haber armado un ejemplo mejor y más completo. Ojalá te sirva al menos de punto de partida.

Abrazo!

Hola. Muchas gracias por ayudarme tanto. Te comento.

Resulta que introduje tu código en una consola y hubo unos problemitas.

La lib/Queue se me marcaba como <módulo no incluido o no existente.

Empecé a pensar que mi versión de python no traía la librería. Asi que me puse a investigar y resulta que a partir de la versión 3 en adelante de python, la librería cambia de nombre como mostre en la captura 1.

La verdad aún tengo un concepto bastante básico de redes. Pero leí tu codigo unas 50 veces hasta tener una idea. Después investigare lo que no entienda.

Bueno, continuando; Después de haber corregido ese pequeño inconveniente abrí un nuevo py y ejecute el código del Cliente al mismo tiempo que ejecutaba el del Server.

Pasó esto:

Por alguna razón me sale que tiene que ser bytes y no str. No entiendo como solucionarlo. Pero ya lo estudiaré más a fondo.

Pero bueno, dejando de lado el Client, que a pesar del error ese se finalizo con éxito. Parece que el Server funcionó perfectamente.

Bueno. Muchísimas gracias Hugo!! Saludos!!

Hola @Leyaud, recién pude revisar nuevamente el código y si… no me había dado cuenta que tenía python2.7 en la máquina en lugar de python3, parece que Queue era antes y en python3 es queue, entre todos cambios…

En fin, le hice unos cambios para tratar de simplificarlo y que guarde algo de información del juego. Te paso la versión mejorada:

import sys
import select
import socket
import json

PUERTO = 8989
LIMITE = 200
IP = '0.0.0.0'

servidor = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
servidor.setblocking(0)
servidor.bind((IP, PUERTO))
servidor.listen(LIMITE)

print("Iniciando el servidor en {}:{}".format(IP, PUERTO))
print("Esperando conexiones")
print("(pulsa ctrl+c para cerrar)")

inputs = [servidor]
actores = {}

while 1:
    infds, outfds, errfds = select.select(inputs, [], [], LIMITE)

    if infds:
        for fds in infds:
            if fds is servidor:
                clientsock, clientaddr = fds.accept()
                inputs.append(clientsock)
                print("Aceptando conexion desde", clientaddr)
                cantidad = len(inputs) - 1
                print("Hay {} clientes conectados".format(cantidad))
            else:
                try:
                    data = fds.recv(1024)

                    # Si le envian las coordenadas las guarda.
                    if data:
                        datos = json.loads(data)
                        actores[datos['identificador']] = datos

                        # Le contesta con todo el mapa de actores.
                        fds.send(json.dumps(actores).encode())
                    else:
                        print("Se ha desconectado un cliente")
                        inputs.remove(fds)

                except ConnectionResetError:
                    print("Se ha desconectado un cliente")
                    inputs.remove(fds)

También armé un pequeño ejemplo con pilas a modo de cliente (en lugar del script cliente.py). Hice algo muy básico, no funciona del todo bien, pero es una aproximación: Cuando abris el programa desde pilas te muestra un personaje arrastrable con el mouse, cada vez que arrastras ese personaje le avisa al servidor la nueva posición:

Si abris otra ventana de pilas, con el mismo script, vas a ver que “se ven” entre sí a través del servidor:

Entiendo que debería funcionar incluso si el servidor está en otra computadora, solo que vas a tener que cambiar la “IP” del ejemplo de pilas para que apunte a la ip real en lugar de “localhost” pero no probé.

Lo que no pude hacer correctamente es que se manejen bien las desconexiones, es como si la conexión de pilas al servidor quedara abierta, no logré encontrarle la vuelta.

Bueno, te paso el código para ejecutar en pilas en lugar del script cliente.py:

# coding: utf-8
import pilasengine
import socket
import json

IP = "localhost"
PUERTO = 8989


pilas = pilasengine.iniciar()

actores = {}


class MiEscena(pilasengine.escenas.Escena):

    def iniciar(self, *k):
        self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.mi_actor = pilas.actores.MiActor()
        print("Intentando conectar a {}:{}".format(IP, PUERTO))
        self.socket.connect((IP, PUERTO))


    def actualizar(self):
        datos = json.dumps({
            'identificador': self.mi_actor.identificador,
            'x': self.mi_actor.x,
            'y':self.mi_actor.y
        })

        # Envia la coordenada de este actor.
        self.socket.send(datos)

        # Espera recibir la posicion de todos los actores.
        respuesta = self.socket.recv(1024)
        datos = json.loads(respuesta.decode())

        # Por cada posicion, intenta emparentarla con un actor en
        # pantalla usando el identificador. Si no existe ese identificador
        # lo crea.
        for key, value in datos.items():

            # Si el identificador es de mi actor, ignora el mensaje
            if key != self.mi_actor.identificador:
                if not key in actores:
                    actores[key] = self.pilas.actores.ActorRemoto()

                actores[key].x = value['x']
                actores[key].y = value['y']


class MiActor(pilasengine.actores.Actor):

    def iniciar(self):
        self.identificador = str(pilas.azar(100000, 999999))
        self.imagen = "aceituna.png"
        self.aprender("arrastrable")
        self.x = pilas.azar(-200, 200)
        self.decir("Este soy yo")
        self.y = pilas.azar(-200, 200)


class ActorRemoto(pilasengine.actores.Actor):

    def iniciar(self):
        self.imagen = "aceituna_grita.png"


pilas.actores.vincular(MiActor)
pilas.actores.vincular(ActorRemoto)


pilas.escenas.vincular(MiEscena)
pilas.escenas.MiEscena()

pilas.ejecutar()