Ousret/Picross-L3S6

View on GitHub
main.rb

Summary

Maintainability
D
2 days
Test Coverage
# Picross main program
# Version 0.2

require 'observer'
require 'json'
require 'curb'

load './class/bmp.class.rb'
load './class/crypt.class.rb'
load './class/registre.class.rb'

load './class/grille.class.rb'

load './class/objetgui.class.rb'
load './class/fenetre.class.rb'
load './class/button.class.rb'
load './class/saisie.class.rb'
load './class/text.class.rb'

load './class/render.class.rb'

# Classe de "haut-niveau" utilise les briques fondamentales
# Permet de jouer au Picross
class Jeu

  FAST_GAME = "Partie rapide"
  ADVENTURE = "Aventure"
  QUIT = "Quitter"
  BACK = "Retour"
  URL_FETCH = "http://hop3x.univ-lemans.fr/picross/levels.json"
  NOMBRE_ESSAIS_AUTORISE = 10

  def initialize()

    @kRender = Render::Game.new
    @kRender.game_scenes.add_observer(self) #Pattern Observable

    @kRegistre = Registre.creer("picross-b.db")
    @currentGame = nil

    @kMainMenu = Fenetre.creer("Menu principal", 0, 0, 0, 640, 480)
    @kInGame = Fenetre.creer("Jeu", 0, 0, 0, 640, 480)
    @kAbout = Fenetre.creer("À propos", 0, 0, 0, 640, 480)
    @kMessage = Fenetre.creer("Information(s)", 0, 0, 0, 640, 480)

    @nombreErreurs = NOMBRE_ESSAIS_AUTORISE

    # Récupère les statistiques du disque local
    getStats

    # Si on ne dispose d'aucun niveau, on install ceux présent sur le disque
    install if @nLevel == nil || @nLevel == 0
    puts "Programme chargée avec #{@nLevel} niveau(x).."
    #@kMainMenu.ajouterComposant(Audio.creer("Env", "ressources/son/BackgroundMusicLoop/BackgroundMusicLoop_BPM100.wav", true, 1, 1, 0, 0, 0))

  end

  def fetchOnline
    request = Curl.get(URL_FETCH)
    return false if request.status != "200 OK" || request.status != "301 OK"
    return request.body_str
  end

  def getStats
    # Récupération des données
    @lastLevel = @kRegistre.getValue("lastLevel") || "1"
    @coins = @kRegistre.getValue("coins") || "0"
    @nTry = @kRegistre.getValue("try") || "0"
    @nWin = @kRegistre.getValue("win") || "0"
    @nLevel = @kRegistre.getValue("nbLevels") || "0"
    # Convertir vers fixnum
    @lastLevel = @lastLevel.to_i if @lastLevel != nil
    @coins = @coins.to_i if @coins != nil
    @nTry = @nTry.to_i if @nTry != nil
    @nWin = @nWin.to_i if @nWin != nil
    @nLevel = @nLevel.to_i if @nLevel != nil
  end

  def sauvegarde
    @kRegistre.addParam("sauvegarde-mat", @currentGame.matriceDeJeu.to_json)
    @kRegistre.addParam("sauvegarde-err", @nombreErreurs.to_s)
  end

  def reprendre

    matriceJeu = @kRegistre.getValue("sauvegarde-mat")
    nbErreur = @kRegistre.getValue("sauvegarde-err")

    if (matriceJeu != nil && nbErreur != nil)
      @kRegistre.deleteParam("sauvegarde-err")
      @kRegistre.deleteParam("sauvegarde-mat")
      @nombreErreurs = nbErreur.to_i
      return JSON.load(matriceJeu)
    else
      return nil
    end

  end

  # Ajout une tentative dans statistiques
  def addTry
    @nTry += 1
    @kRegistre.updateParam("try", @nTry.to_s)
  end

  # Ajout une victoire dans statistiques
  def addWin
    @nWin += 1
    @lastLevel += 1
    @kRegistre.updateParam("win", @nWin.to_s)
    @kRegistre.updateParam("lastLevel", @lastLevel.to_s)
  end

  # Ajout de l'argent dans statistiques
  def addCoin(unNombreGems)
    @coins+=unNombreGems
    @kRegistre.updateParam("coins", @coins.to_s)
  end

  # Récupére la matrice du dernier niveau
  def getMatrice
    @lastLevel = 1 if @lastLevel == nil
    JSON.load(@kRegistre.getValue("level_#{@lastLevel}"))
  end

  # Installation des niveaux présent sur le disque (local)
  def install
    onlineLevels = fetchOnline
    uneListeNiveau = Dir["ressources/images/imagesPicross/BMP24bitsRVB/*.bmp"].sort
    i = 1

    if onlineLevels != false # Si les niveaux sont disponible en ligne
      puts "Installation des niveaux depuis le serveur distant.."
      arrayLevels = JSON.load(onlineLevels)
      arrayLevels.each do |level|
        @kRegistre.addParam("level_#{i}", level.to_json)
        i+=1
      end
    else # Récupération hors ligne
      puts "Installation des niveaux depuis le disque local.. (hors ligne)"
      uneListeNiveau.each do |unNiveau|
        @kRegistre.addParam("level_#{i}", BMP::Reader.creer(unNiveau).getMatrice.to_json)
        i+=1
      end
    end

    # On sauvegarde le nombre de niveau chargé
    @nLevel = i
    @kRegistre.addParam("nbLevels", i.to_s)
    @kRegistre.addParam("coins", "0")
    @kRegistre.addParam("try", "0")
    @kRegistre.addParam("win", "0")
    @kRegistre.addParam("lastLevel", "1")
  end

  # Prépare la boite pour affichage statistiques
  def addStatBox(unContexteCible)

    stat_support = Image.creer("Stat-background", "ressources/images/GUI/box/boxNormal.png", 480, 20, 1)

    libell_coins = Text.creer("coins", "Gems   : #{@coins||0}", 10, 500, 50, 2)
    libell_try = Text.creer("try", "Essai    : #{@nTry||0}", 10, 500, 60, 2)
    libell_win = Text.creer("win", "Victoire: #{@nWin||0}", 10, 500, 70, 2)
    libell_avancement = Text.creer("avancement", "Niveau: #{@lastLevel||0}/#{@nLevel||0}", 10, 500, 80, 2)

    unContexteCible.ajouterComposant(stat_support, libell_coins, libell_try, libell_win, libell_avancement)

  end

  def initializeMessage(unMessage)
    @kMessage.supprimeTout

    # Image de fond
    background = Image.creer("Background", "ressources/images/GUI/Prototypes/background-6.png", 0, 0, 0)
    # Information sur version
    libell_alpha = Text.creer("beta-1", "beta-preview 1", 15, 100, 120, 1)
    # Placement du titre
    libell_title = Text.creer("title", "Picross B", 50, 100, 100, 1)
    libell_title.setPolice "ressources/ttf/Starjedi.ttf" #Police d'écriture spéciale pour le titre

    libell_message = Text.creer("message", unMessage, 20, 100, 230, 1)

    btn_ok = Boutton.creer("OK", 50, 400, 1, 0, 0)

    addStatBox @kMessage

    @kMessage.ajouterComposant background, libell_alpha, libell_title, libell_message, btn_ok
  end

  # Préparation de la fenêtre À propos
  def initializeAbout

    @kAbout.supprimeTout
    # Image de fond
    background = Image.creer("Background", "ressources/images/GUI/Prototypes/background-6.png", 0, 0, 0)
    # Information sur version
    libell_alpha = Text.creer("beta-1", "beta-preview 1", 15, 100, 120, 1)
    # Placement du titre
    libell_title = Text.creer("title", "Picross B", 50, 100, 100, 1)
    libell_title.setPolice "ressources/ttf/Starjedi.ttf" #Police d'écriture spéciale pour le titre

    libell_line1 = Text.creer("about_1", "Jeu de Picross Grpe B", 12, 100, 200, 1)
    libell_line2 = Text.creer("about_2", "Projet de semestre 6 en L3 SPI", 12, 100, 215, 1)

    libell_line3 = Text.creer("about_3", "Moteur Jeu: Houssam",12, 100, 230, 1)
    libell_line4 = Text.creer("about_4", "RenduGL: Ahmed", 12, 100, 245, 1)
    libell_line5 = Text.creer("about_5", "Registre: Victorien, Ahmed",12, 100, 260, 1)
    libell_line6 = Text.creer("about_6", "Cryptage: Baptiste", 12,100, 275, 1)
    libell_line7 = Text.creer("about_7", "TDA GUI: Antoine, Ahmed",12, 100, 290, 1)
    libell_line8 = Text.creer("about_8", "BMP: Marius",12, 100, 305, 1)
    libell_line9 = Text.creer("about_9", "Recherches: Matthis",12, 100, 320, 1)

    btn_quit = Boutton.creer("Retour", 50, 400, 1, 0, 0)

    addStatBox @kAbout

    @kAbout.ajouterComposant background, libell_alpha, libell_title, libell_line1, libell_line2, libell_line3, libell_line4, libell_line5, libell_line6, libell_line7, libell_line8, libell_line9, btn_quit

  end

  # Préparation du menu principal
  def initializeMainMenu()

    @kMainMenu.supprimeTout

    # Image de fond
    background = Image.creer("Background", "ressources/images/GUI/Prototypes/background-5.png", 0, 0, 0)

    # Information sur version
    libell_alpha = Text.creer("beta-1", "beta-preview 1", 15, 250, 170, 1)

    # Statistique
    addStatBox @kMainMenu

    # Placement du titre
    libell_title = Text.creer("title", "Picross B", 50, 250, 150, 1)
    libell_title.setPolice "ressources/ttf/Starjedi.ttf" #Police d'écriture spéciale pour le titre

    # Création des boutons
    btn_aventure = Boutton.creer("Aventure", 40, 50, 1, 0, 0)
    btn_newGame = Boutton.creer("Partie rapide", 40, 100, 1, 0, 0)
    btn_params = Boutton.creer("Parametres", 40, 150, 1, 0, 0)
    btn_about = Boutton.creer("À propos", 40, 200, 1, 0, 0)
    btn_quit = Boutton.creer("Quitter", 40, 250, 1, 0, 0)

    # Ajout des composants sur la fenêtre primaire
    @kMainMenu.ajouterComposant(background, libell_alpha, libell_title ,btn_aventure, btn_newGame, btn_params,btn_about ,btn_quit)

  end

  # Préparation du jeu "rapide"
  def initializeGame
    @kInGame.supprimeTout

    background = Image.creer("Background", "ressources/maps/Couloirs-Resized.png", 0, 0, 0)

    libell_niveau = Text.creer("niveau", "Niveau #{@lastLevel}", 19, 50, 40, 1)
    libell_niveau.setPolice "ressources/ttf/Starjedi.ttf"

    # Statistique
    addStatBox @kInGame

    myMat = getMatrice

    @currentGame = Grille.grille(myMat)

    repriseData = reprendre
    if repriseData != nil
      @currentGame.restorer repriseData
    else
      addTry # Ajout +1 aux essai (statistiques)
      @nombreErreurs = NOMBRE_ESSAIS_AUTORISE
    end

    libell_erreur = Text.creer("essais", "Essai(s): #{@nombreErreurs} restant(s)", 15, 50, 90, 1)
    support_grille = Image.creer("Grille", "ressources/images/Grilles/v3/g#{myMat.length}x#{myMat.length}.png", 120, 120, 1)

    btn_quit = Boutton.creer("Abandonner", 50, 430, 1, 0, 0)
    btn_help = Boutton.creer("Aide", 150, 430, 1, 0, 0)
    btn_save = Boutton.creer("Sauvegarder", 250, 430, 1, 0, 0)

    @kInGame.ajouterComposant(background, libell_niveau, libell_erreur, support_grille)

    # On place des élements "case" pour grille
    inHautPosX = 244
    inHautPosY = 245

    (1..myMat.length).step(1) do |n|

      (1..myMat.length).step(1) do |j|
          spriteCase = Sprite.creer("case-#{n}-#{j}", "ressources/images/Grilles/Cases.png", 8, 1, inHautPosX, inHautPosY, 2, 0, 0)
          spriteCase.arr_data = [n, j]

          if repriseData != nil && repriseData[n-1][j-1] == 1
            spriteCase.deplacer 2, 0
          else
            spriteCase.deplacer 1, 0
          end

          @kInGame.ajouterComposant(spriteCase)

          inHautPosX += 16
      end

      inHautPosX = 244
      inHautPosY += 16

    end

    # Positionnement des indices Haut
    inHautPosX = 250
    inHautPosY = 120

    @currentGame.indicesHaut.each do |indice|
      indice.each do |nb|
        @kInGame.ajouterComposant(Text.creer("ind-#{inHautPosX}-#{inHautPosY}", "#{nb}", 15, inHautPosX, inHautPosY, 2))
        inHautPosY += 16
      end
      inHautPosX += 16
      inHautPosY = 120
    end

    # Positionnement des indices Coté
    inHautPosX = 120
    inHautPosY = 240

    @currentGame.indicesCote.each do |indice|
      indice.each do |nb|
        @kInGame.ajouterComposant(Text.creer("ind-#{inHautPosX}-#{inHautPosY}", "#{nb}", 15, inHautPosX, inHautPosY, 2))
        inHautPosX += 16
      end
      inHautPosY += 16
      inHautPosX = 120
    end

    # Ajout des sprites pour case noir et blanche
    @kInGame.ajouterComposant(btn_quit, btn_help, btn_save)
  end

  # Lance le jeu avec le menu principal
  def lanceToi
    initializeMainMenu #Préparation du menu principal
    @kRender.prepare @kMainMenu #Rendu des objets
  end

  def actionOnMessage(unTypeEvenement, unComposantCible, unTexteEntree=nil)
    return if unTypeEvenement != 1 # On ne recherche que le clique souris
    btn_cible_libell = unComposantCible.designation

    if (btn_cible_libell == "OK")
      initializeMainMenu
      @kRender.prepare @kMainMenu
    end
  end

  # Gestion des actions sur le menu principal
  def actionOnMenu(unTypeEvenement, unComposantCible, unTexteEntree=nil)
    # Gestion des événements sur menu principal
    return if unTypeEvenement != 1 # On ne recherche que le clique souris
    btn_cible_libell = unComposantCible.designation

    if (btn_cible_libell == FAST_GAME) #Si le joueur clique sur "Partie rapide"
      #On charge le plateau
      initializeGame
      #On recharge le rendu
      @kRender.end_scene @kInGame
    elsif (btn_cible_libell == "Quitter")
      #On met fin au programme
      exit
    elsif (btn_cible_libell == "À propos")
      #On charge la fenêtre à propos
      initializeAbout
      @kRender.end_scene @kAbout
    elsif (btn_cible_libell == "Aventure")
      initializeMessage "Désolé, le mode aventure à été désactivée!"
      @kRender.end_scene @kMessage
    end
  end

  # Gestion des actions sur le plateau de jeu
  def actionOnGame(unTypeEvenement, unComposantCible, unTexteEntree=nil)
    # Gestion des événements sur menu principal
    return if unTypeEvenement != 1 # On ne recherche que le clique souris
    btn_cible_libell = unComposantCible.designation

    if (btn_cible_libell == "Abandonner") #Si le joueur clique sur "Retour" depuis la séance de jeu
      #On charge le menu principal
      initializeMainMenu
      #On recharge le rendu
      @kRender.end_scene @kMainMenu
    elsif (unComposantCible.designation.match(/case-[0-9]{1,2}-[0-9]{1,2}/) && unComposantCible.arr_data != [-1, -1])

      # On tente de noirsir la case
      if @currentGame.noirsirCase unComposantCible.arr_data[0], unComposantCible.arr_data[1]
        @kRender.game_scenes.animateSprite unComposantCible
        @kRender.game_scenes.instantSound "ressources/son/pop.wav"
      else
        @kRender.game_scenes.instantSound "ressources/son/beep.wav"
        @nombreErreurs -= 1
        if @nombreErreurs == 0
          initializeMessage "Vous avez perdu, votre nombre de tentative est écoulée!"
          @kRender.end_scene @kMessage
        else
          nouveauLibellEssais = @kRender.game_scenes.getVertexIDFromName("essais")
          nouveauLibellEssais.contenu = "Essai(s): #{@nombreErreurs} restant(s)"
          @kRender.game_scenes.updateComposant nouveauLibellEssais
        end
      end
      # On vérifie que l'état de la partie
      if @currentGame.terminer?
        #On ajoute une victoire
        addWin
        addCoin @currentGame.calculeScore

        initializeMessage "Vous avez réussi à résoudre la grille niveau n°#{@lastLevel-1} ! (+ #{@currentGame.calculeScore} Gems)"
        @kRender.end_scene @kMessage
      end
    elsif (btn_cible_libell == "Aide")
      #On vérifie que le joueur a suffisament de credit
      if (@coins > 100*@lastLevel)
        caseCible = @currentGame.demanderAide
        if (caseCible != [-1, -1])
          # On noirsi la case cible
          @currentGame.noirsirCase caseCible[0], caseCible[1]
          # On effectue l'animation sur la scene
          nouvelEtatCase = @kRender.game_scenes.getVertexIDFromName("case-#{caseCible[0]+1}-#{caseCible[1]+1}")
          #puts nouvelEtatCase
          nouvelEtatCase.deplacer 2,0
          @kRender.game_scenes.updateComposant nouvelEtatCase

          # On retire la somme du compte
          addCoin(-100*@lastLevel)

          # On actualise le montant aka. "solde"
          nouvelEtatSolde = @kRender.game_scenes.getVertexIDFromName("coins")
          nouvelEtatSolde.contenu = "Gems:   #{@coins}"
          @kRender.game_scenes.updateComposant nouvelEtatSolde

          @kRender.game_scenes.instantSound "ressources/son/pop.wav"
        end
      end
    elsif (btn_cible_libell == "Quitter")
      #On met fin au programme
      exit
    elsif (btn_cible_libell == "Sauvegarder")
      #On sauvegarde l'état de la partie
      if sauvegarde
        initializeMessage "La partie a été sauvegardée avec succès, à bientôt!"
        @kRender.end_scene @kMessage
      else
        initializeMessage "Une erreur est survenue lors de la sauvegarde"
        @kRender.end_scene @kMessage
      end
    end

  end

  # Gestion des événements sur fenêtre À propos
  def actionOnAbout(unTypeEvenement, unComposantCible, unTexteEntree=nil)
    # Gestion des événements sur menu principal
    return if unTypeEvenement != 1 # On ne recherche que le clique souris
    btn_cible_libell = unComposantCible.designation

    if (btn_cible_libell == "Retour")
      initializeMainMenu
      @kRender.end_scene @kMainMenu
    end

  end

  # Méthode de reception signal pour pattern observateur
  def update(unTypeEvenement, unComposantCible, unTexteEntree=nil)
    #puts "Attention: Evenement Trigger #{unComposantCible.designation} sur typeEvenement = #{unTypeEvenement} avec contexte = #{@kRender.getContext.designation}"

    #On redirige vers la méthode concernée
    if @kRender.getContext.designation == "Menu principal"
      actionOnMenu unTypeEvenement, unComposantCible
    elsif @kRender.getContext.designation == "Jeu"
      actionOnGame unTypeEvenement, unComposantCible
    elsif @kRender.getContext.designation == "À propos"
      actionOnAbout unTypeEvenement, unComposantCible
    elsif @kRender.getContext.designation == "Information(s)"
      actionOnMessage unTypeEvenement, unComposantCible
    end

  end

  private :addWin, :addTry, :addCoin, :initializeMainMenu, :initializeMessage, :initializeGame, :initializeAbout, :actionOnMessage, :actionOnAbout, :actionOnMessage, :actionOnGame, :actionOnMenu, :sauvegarde, :reprendre

end

#Lancement
#puts OpenSSL::Cipher.ciphers
kJeu = Jeu.new
kJeu.lanceToi