#! /usr/bin/env python # Conozco Mexico # Copyright (C) 2010 # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # # # Based off of Conozco - Numeros / Uruguay, Storybuilder # Contact information: # Cole cshaw@cidesi.mx import sys import random import os import pygame import olpcgames import gtk import pickle import time import logging import pgu import pygame from pygame.locals import * from gettext import gettext as _ from datetime import date from olpcgames import eventwrap from sugar.graphics import style from pgu import text from pgu import gui from pgu import html #from storytheme import theme_list, theme_defs from mxtheme import theme_list, theme_defs # constantes XMAPAMAX = 786 DXPANEL = 414 XCENTROPANEL = 1002 CAMINORECURSOS = "recursos" CAMINOLAMINA = "lamina" CAMINOCOMUN = "comun" CAMINOFUENTES = "fuentes" CAMINODATOS = "datos" CAMINOIMAGENES = "imagenes" CAMINOSONIDOS = "sonidos" COLORNOMBREDEPTO = (200,60,60) COLORPREGUNTAS = (80,80,155) COLORPANEL = (156,158,172) TOTALAVANCE = 7 EVENTORESPUESTA = pygame.USEREVENT+1 TIEMPORESPUESTA = 2300 EVENTOREFRESCO = EVENTORESPUESTA+1 TIEMPOREFRESCO = 250 FPS = 30 # variables globales para adaptar la pantalla a distintas resoluciones ORIG_WIDTH = 1024 ORIG_HEIGHT = 700 FACTOR = min((gtk.gdk.screen_width() / float(ORIG_WIDTH)), (gtk.gdk.screen_height()-style.MEDIUM_ICON_SIZE) / float(ORIG_HEIGHT)) def scale(x): if isinstance(x, int): return int(x * FACTOR) return tuple([scale(i) for i in x]) GAME_WIDTH = scale(ORIG_WIDTH) GAME_HEIGHT = scale(ORIG_HEIGHT) CHAR_PANEL_SIZE = 5 # button positions CLEAR = pygame.rect.Rect(scale((932, 435, 46, 33))) EXIT_GAME = pygame.rect.Rect(scale((46, 93, 46, 33))) ABOUT_GAME = pygame.rect.Rect(scale((46, 491, 46, 33))) PLAY_LEVEL_1 = pygame.rect.Rect(scale((932, 435, 46, 33))) QUIT_LEVEL = pygame.rect.Rect(scale((932, 114, 46, 33))) THEME_LEFT = pygame.rect.Rect(scale((410, 506, 46, 33))) THEME_RIGHT = pygame.rect.Rect(scale((567, 506, 46, 33))) #TEXTAREA = pygame.rect.Rect(scale((62, 560, 875, 114))) TEXTAREA = pygame.rect.Rect(scale((62, 560, 875, 114))) GAMEHEADING = pygame.rect.Rect(scale((300, 200, 50, 100))) BACKGROUNDAREA = pygame.rect.Rect(scale((153, 95, 754, 393))) #scale = 1 shift_x = 0 shift_y = 0 xo_resolution = True clock = pygame.time.Clock() def wait_events(): """ Funcion para esperar por eventos de pygame sin consumir CPU """ global clock clock.tick(20) return pygame.event.get() class Theme: """Model for a theme.""" def __init__(self, themename): """Create the theme based on the name. themename -- string. """ self.themename = themename self.background = theme_defs[themename]['background'] self.buttons = theme_defs[themename]['buttons'] self.icon = theme_defs[themename]['icon'] self.zones = theme_defs[themename]['zones'] self.levels = theme_defs[themename]['levels'] self.zonepic = theme_defs[themename]['zonepic'] def cargarImagen(nombre, (x, y)=(None, None)): """Carga una imagen y la escala de acuerdo a la resolucion""" fullname = os.path.join(CAMINORECURSOS, CAMINOLAMINA, nombre) imagen0 = pygame.image.load(fullname).convert_alpha() imagen = pygame.transform.scale(imagen0, (scale(imagen0.get_width()), scale(imagen0.get_height()))) if x and y: imagen = pygame.transform.scale(imagen0, (scale(x), scale(y))) return imagen, imagen.get_rect() class Icon(pygame.sprite.Sprite): """Icon for Theme.""" def __init__(self, theme, layout=None): """Load the icon. theme -- Theme object layout -- optional sprite to align with """ pygame.sprite.Sprite.__init__(self) self.image, self.rect = cargarImagen(theme.icon, (100, 66)) if layout: x = layout.rect.left + scale(462) y = layout.rect.top + scale(492) self.rect.topleft = (x, y) class Layout(pygame.sprite.Sprite): """Image of the screen layout.""" def __init__(self, name, surfsize=None): """Create a Layout. Parameters: name: (string) filename of background image, from the data/ dir surfsize: (optional tuple) size of surface to center on """ pygame.sprite.Sprite.__init__(self) self.image, self.rect = cargarImagen(name, (ORIG_WIDTH, ORIG_HEIGHT)) if surfsize: mid_x = surfsize[0] / 2 mid_y = surfsize[1] / 2 self.rect.center = (mid_x, mid_y) @property def x(self): return self.rect.top class Background(pygame.sprite.Sprite): """Background image for Story.""" def __init__(self, theme, layout): """Load the background. theme -- Theme object layout -- sprite to align with """ pygame.sprite.Sprite.__init__(self) self.image, self.rect = cargarImagen(theme.background, (754, 393)) x = layout.rect.left + scale(153) y = layout.rect.top + scale(95) self.rect.topleft = (x, y) class Widget: """A button that controls something.""" def __init__(self, rect, layout, click_method): """Create the Widget""" self.rect = pygame.rect.Rect(rect) x = layout.rect.left + rect.left y = layout.rect.top + rect.top self.rect.topleft = (x, y) self.click_method = click_method def is_mouse_over(self): """Check if the mouse pointer is over the sprite.""" return self.rect.collidepoint(pygame.mouse.get_pos()) def click(self): self.click_method() class Zona(): """Clase para zonas de una imagen. La posicion esta dada por una imagen bitmap pintada con un color especifico, dado por la clave (valor 0 a 255 del componente rojo). En multiples de 5 (0, 5, 10, 15, etc.) """ def __init__(self,mapa,nombre,claveColor,tipo): self.mapa = mapa self.nombre = nombre self.claveColor = int(claveColor) self.tipo = int(tipo) def estaAca(self,pos): """Devuelve True si la coordenada pos esta en la zona""" if BACKGROUNDAREA.collidepoint(pos): newx = pos[0] - (BACKGROUNDAREA.topleft[0]) newy = pos[1] - (BACKGROUNDAREA.topleft[1]) colorAca = self.mapa.get_at((newx, newy)) if colorAca[0] == self.claveColor: return True else: return False else: return False def mostrarNombre(self,pantalla,fuente,color,flipAhora): """Escribe el nombre de la zona en su posicion""" text = fuente.render(self.nombre, 1, color) textrot = pygame.transform.rotate(text, self.rotacion) textrect = textrot.get_rect() textrect.center = (self.posicion[0], self.posicion[1]) pantalla.blit(textrot, textrect) if flipAhora: pygame.display.flip() class Nivel(): """Clase para definir los niveles del juego. Cada nivel tiene un dibujo inicial, los elementos pueden estar etiquetados con el nombre o no, y un conjunto de preguntas. """ def __init__(self,nombre): self.nombre = nombre self.preguntas = list() self.indicePreguntaActual = 0 self.elementosActivos = list() def prepararPreguntas(self): """Este metodo sirve para preparar la lista de preguntas al azar.""" random.shuffle(self.preguntas) def siguientePregunta(self,listaSufijos,listaPrefijos): """Prepara el texto de la pregunta siguiente""" self.preguntaActual = self.preguntas[self.indicePreguntaActual] self.sufijoActual = random.randint(1,len(listaSufijos))-1 self.prefijoActual = random.randint(1,len(listaPrefijos))-1 lineas = listaPrefijos[self.prefijoActual].split("\\") lineas.extend(self.preguntaActual[0].split("\\")) lineas.extend(listaSufijos[self.sufijoActual].split("\\")) self.indicePreguntaActual = self.indicePreguntaActual+1 if self.indicePreguntaActual == len(self.preguntas): self.indicePreguntaActual = 0 return lineas def devolverAyuda(self): """Devuelve la linea de ayuda""" self.preguntaActual = self.preguntas[self.indicePreguntaActual-1] return self.preguntaActual[2].split("\\") def mostrarPregunta(self,pantalla,fuente,sufijo,prefijo): """Muestra la pregunta en el globito""" self.preguntaActual = self.preguntas[self.indicePreguntaActual] lineas = prefijo.split("\\") lineas.extend(self.preguntaActual[0].split("\\")) lineas.extend(sufijo.split("\\")) yLinea = 100 for l in lineas: text = fuente.render(l, 1, COLORPREGUNTAS) textrect = text.get_rect() textrect.center = (XCENTROPANEL,yLinea) pantalla.blit(text, textrect) yLinea = yLinea + fuente.get_height() pygame.display.flip() class ConozcoMx(): """Clase principal del juego. """ def mostrarTexto(self,texto,fuente,posicion,color): """Muestra texto en una determinada posicion""" text = fuente.render(texto, 1, color) textrect = text.get_rect() textrect.center = posicion self.pantalla.blit(text, textrect) def cargarZonas(self): """Carga las imagenes y los datos de las zonas""" self.zonas = cargarImagen(self.theme.zonepic)[0] self.listaZonas = list() # falta sanitizar manejo de archivo f = open(os.path.join(self.camino_datos,self.theme.zones),"r") linea = f.readline() while linea: if linea[0] == "#": linea = f.readline() continue [nombreZona,claveColor] = \ linea.strip().split("|") nuevaZona = Zona(self.zonas, unicode(nombreZona,'iso-8859-1'), claveColor,1) self.listaZonas.append(nuevaZona) linea = f.readline() f.close() def cargarNiveles(self): """Carga los niveles del archivo de configuracion""" self.listaNiveles = list() self.listaPrefijos = list() self.listaSufijos = list() self.listaCorrecto = list() self.listaMal = list() self.listaDespedidas = list() # falta sanitizar manejo de archivo f = open(os.path.join(self.camino_datos,self.theme.levels),"r") linea = f.readline() while linea: if linea[0] == "#": linea = f.readline() continue if linea[0] == "[": # empieza nivel nombreNivel = linea.strip("[]\n") nuevoNivel = Nivel(nombreNivel) self.listaNiveles.append(nuevoNivel) linea = f.readline() continue if linea.find("=") == -1: linea = f.readline() continue [var,valor] = linea.strip().split("=") if var.startswith("Prefijo"): self.listaPrefijos.append( unicode(valor.strip(),'iso-8859-1')) elif var.startswith("Sufijo"): self.listaSufijos.append( unicode(valor.strip(),'iso-8859-1')) elif var.startswith("Correcto"): self.listaCorrecto.append( unicode(valor.strip(),'iso-8859-1')) elif var.startswith("Mal"): self.listaMal.append( unicode(valor.strip(),'iso-8859-1')) elif var.startswith("Despedida"): self.listaDespedidas.append( unicode(valor.strip(),'iso-8859-1')) elif var.startswith("Pregunta"): [texto,respuesta,ayuda] = valor.split("|") nuevoNivel.preguntas.append( (unicode(texto.strip(),'iso-8859-1'), unicode(respuesta.strip(),'iso-8859-1'), unicode(ayuda.strip(),'iso-8859-1'))) linea = f.readline() f.close() self.indiceNivelActual = 0 self.numeroNiveles = len(self.listaNiveles) self.numeroSufijos = len(self.listaSufijos) self.numeroPrefijos = len(self.listaPrefijos) self.numeroCorrecto = len(self.listaCorrecto) self.numeroMal = len(self.listaMal) self.numeroDespedidas = len(self.listaDespedidas) def __init__(self): """Esta es la inicializacion del juego""" pygame.init() self.screen = pygame.display.set_mode((GAME_WIDTH, GAME_HEIGHT)) pygame.display.set_caption(_("Conozco Mexico")) self.canvas = pygame.Surface(self.screen.get_size()).convert() self.canvas.fill((0xAC, 0xAC, 0xAC)) # pick the first theme as the default self._themes = load_themes() self.current_theme = 0 self.layout = Layout('layout_cmx.png') self.set_theme(True) self.text = '' self.screen.blit(self.canvas, (0, 0)) pygame.display.flip() self.clock = pygame.time.Clock() self.active_character = None # GUI functionality self.gui = gui.App(theme=gui.Theme('gui.theme')) guicontainer = gui.Container(align=-1, valign=-1) credits_button = gui.Button(_('Creditos')) credits_button.connect(gui.CLICK, self.show_credits_cb) guicontainer.add(credits_button, scale(800)+self.layout.rect.left, scale(25)+self.layout.rect.top) self.credits_texts = [] self.credits_area = None #TEXTAREA.x += self.layout.rect.left #TEXTAREA.y += self.layout.rect.top questionArea = gui.TextArea(value=self.text) questionArea.rect = pygame.rect.Rect(TEXTAREA) #questionArea.connect(gui.CHANGE, self.update_text_cb) guicontainer.add(questionArea, questionArea.rect.x, questionArea.rect.y) self.questionArea = questionArea self.guicontainer = guicontainer self.gui.init(guicontainer) pygame.display.flip() def cargarDirectorio(self): """Carga la informacion especifica de un directorio""" self.camino_imagenes = os.path.join(CAMINORECURSOS,CAMINOLAMINA) self.camino_datos = os.path.join(CAMINORECURSOS,CAMINOLAMINA) self.cargarZonas() self.cargarNiveles() def mostrarGlobito(self,lineas): """Muestra texto en el globito""" global shift_x, shift_y #yLinea = TEXTAREA.y self.questionArea.value = "" for l in lineas: #text = pygame.font.Font(None, scale(32)).render(l, 1, COLORPREGUNTAS) #textrect = text.get_rect() #textrect.x = TEXTAREA.x #textrect.y = yLinea self.questionArea.value = self.questionArea.value + '\n' + l.encode('latin-1') #self.screen.blit(self.questionArea, textrect) #yLinea = yLinea + pygame.font.Font(None, scale(32)).get_height() + scale(10) pygame.display.flip() def borrarGlobito(self): """ Borra el globito, lo deja en blanco""" global shift_x, shift_y #pos = (TEXTAREA.x, TEXTAREA.y) #self.screen.blit(self.questionArea, (pos)) self.questionArea.value = "" def correcto(self): """Muestra texto en el globito cuando la respuesta es correcta""" global shift_x, shift_y self.correctoActual = random.randint(1,self.numeroCorrecto)-1 self.mostrarGlobito([self.listaCorrecto[self.correctoActual]]) self.esCorrecto = True pygame.time.set_timer(EVENTORESPUESTA,TIEMPORESPUESTA) def mal(self): """Muestra texto en el globito cuando la respuesta es incorrecta""" self.malActual = random.randint(1,self.numeroMal)-1 self.mostrarGlobito([self.listaMal[self.malActual]]) self.esCorrecto = False self.nRespuestasMal += 1 pygame.time.set_timer(EVENTORESPUESTA,TIEMPORESPUESTA) def esCorrecta(self,nivel,pos): """Devuelve True si las coordenadas cliqueadas corresponden a la respuesta correcta """ respCorrecta = nivel.preguntaActual[1] encontrado = False for d in self.listaZonas: if d.nombre.startswith(respCorrecta): encontrado = True break if d.estaAca(pos): return True else: return False def credits_tab_cb(self): credits = self.credits_group.value self.credits_hide() self.credits_show(credits) def credits_show(self, credits_to_show): self.credits_area = gui.Container( width=self.background.rect.width, height=self.background.rect.height) self.credits_group = gui.Group() self.credits_group.connect(gui.CHANGE, self.credits_tab_cb) tt = gui.Table(width=self.background.rect.width, height=self.background.rect.height, background=(255, 255, 255)) tt.tr() for credits in self.credits_texts: b = gui.Tool(self.credits_group, gui.Label(credits[0]), credits) tt.td(b) tt.tr() htmltext = html.HTML(credits_to_show[1], align=-1, valign=-1, width=self.background.rect.width-30) self.credits_box = gui.ScrollArea(htmltext, width=self.background.rect.width, height=self.background.rect.height-30) tt.td(self.credits_box, style={'border':1}, colspan=len(self.credits_texts)) #self.lesson_plan_area = gui.ScrollArea(htmltext, # self.background.rect.width, self.background.rect.height) self.credits_area.add(tt, 0, 0) self.guicontainer.add(self.credits_area, self.background.rect.left, self.background.rect.top) def credits_hide(self): # We are showing it already so hide it self.guicontainer.remove(self.credits_area) del self.credits_area self.credits_area = None def show_credits_cb(self): if not self.credits_texts: self.credits_texts = [] for credits in os.listdir('credits'): filename = os.path.join('credits', credits, 'default.html') self.credits_texts.append((credits, open(filename).read())) self.credits_texts.sort() if self.credits_area: self.credits_hide() else: self.credits_show(self.credits_texts[0]) def jugarNivel(self): """Juego principal de preguntas y respuestas""" self.nivelActual = self.listaNiveles[self.indiceNivelActual] self.avanceNivel = 0 self.nivelActual.prepararPreguntas() # presentar pregunta inicial self.lineasPregunta = self.nivelActual.siguientePregunta(\ self.listaSufijos,self.listaPrefijos) self.mostrarGlobito(self.lineasPregunta) self.nRespuestasMal = 0 # leer eventos y ver si la respuesta es correcta while 1: #self.clock.tick(FPS) for event in wait_events(): if event.type == QUIT: return elif event.type == KEYDOWN and event.key == K_ESCAPE: return elif event.type == pygame.MOUSEBUTTONDOWN: if self.avanceNivel < TOTALAVANCE: # if the total number of correct answers hasn't been given yet # check to see if the click was inside of the map / background if (BACKGROUNDAREA.collidepoint(event.pos)): self.borrarGlobito() if self.esCorrecta(self.nivelActual, event.pos): self.correcto() else: self.mal() # if the click was not in the background, check to see # if it was over a widget else: for widget in self.widgets: if widget.is_mouse_over(): widget.click() elif event.type == EVENTORESPUESTA: pygame.time.set_timer(EVENTORESPUESTA,0) if self.esCorrecto: if self.avanceNivel == TOTALAVANCE: return self.avanceNivel = self.avanceNivel + 1 if self.avanceNivel == TOTALAVANCE: # fin self.lineasPregunta = self.listaDespedidas[\ random.randint(1,self.numeroDespedidas)-1]\ .split("\\") self.mostrarGlobito(self.lineasPregunta) pygame.time.set_timer( EVENTORESPUESTA,TIEMPORESPUESTA) else: # pregunta siguiente self.lineasPregunta = \ self.nivelActual.siguientePregunta(\ self.listaSufijos,self.listaPrefijos) self.mostrarGlobito(self.lineasPregunta) self.nRespuestasMal = 0 else: if self.nRespuestasMal >= 2: # ayuda self.mostrarGlobito( self.nivelActual.devolverAyuda()) self.nRespuestasMal = 0 pygame.time.set_timer( EVENTORESPUESTA,TIEMPORESPUESTA) else: # volver a preguntar self.mostrarGlobito(self.lineasPregunta) elif event.type == EVENTOREFRESCO: pygame.display.flip() self.gui.event(event) self.bgsprites.draw(self.screen) self.gui.paint(self.screen) #pygame.display.flip() def save(self, filename): """Save the story. """ f = open(filename, 'w') pickle.dump(self.current_theme, f) pickle.dump(self.text, f) pickle.dump(len(self.stickersprites.sprites()), f) for character in self.stickersprites.sprites(): pickle.dump(character.name, f) pickle.dump(character.rect.topleft, f) f.close() def principal(self): """Este es el loop principal del juego""" global shift_x, shift_y pygame.time.set_timer(EVENTOREFRESCO,TIEMPOREFRESCO) while 1: self.cargarDirectorio() while 1: self.screen.blit(self.canvas, (0, 0)) self.bgsprites.draw(self.screen) if hasattr(self, "gui"): self.gui.paint(self.screen) pygame.display.flip() self.jugarNivel() def update_text_cb(self): self.text = self.text_input.value def load_buttons(self): """Load the character button images for the theme""" buttons = [] for button_def in self.theme.buttons: button = CharacterButton(button_def) buttons.append(button) return buttons def clear(self): """Remove all the characters""" self.stickersprites.empty() self.active_character = None def prev_theme(self): self.current_theme -= 1 self.current_theme %= len(self._themes) self.set_theme() def next_theme(self): self.current_theme += 1 self.current_theme %= len(self._themes) self.set_theme() def set_theme(self, initial = False): # change the theme--background map, zones, # icon, and questions self.theme = self._themes[self.current_theme] self.icon = Icon(theme=self.theme, layout=self.layout) self.background = Background(theme=self.theme, layout=self.layout) self.bgsprites = pygame.sprite.OrderedUpdates( [self.layout, self.background, self.icon]) self.widgets = [ Widget(THEME_LEFT, self.layout, self.prev_theme), Widget(THEME_RIGHT, self.layout, self.next_theme), ] #if this is not the start-up, update the background map, icon, and # questions for the new theme if not initial: self.camino_imagenes = os.path.join(CAMINORECURSOS,CAMINOLAMINA) self.camino_datos = os.path.join(CAMINORECURSOS,CAMINOLAMINA) self.cargarZonas() self.cargarNiveles() self.screen.blit(self.canvas, (0, 0)) self.bgsprites.draw(self.screen) pygame.display.flip() self.principal() def load_themes(): """Create a list of Themes from theme_defs""" themes = [] for t in theme_list: themes.append(Theme(t)) return themes def main(): juego = ConozcoMx() juego.principal() if __name__ == "__main__": main()