viernes, 19 de noviembre de 2010

canvas-event: fácil interacción con el elemento canvas

Uno de los elementos HTML5 que sin duda ofrece mayores posibilidades es el elemento canvas, gracias a este es posible crear y manipular imágenes directamente en el navegador, pero a la hora de crear algo mas que imágenes estáticas  nos tropezamos con la falta de métodos para el manejo de eventos, aun así encontramos aplicaciones web que permiten la interacción por medio del teclado y el ratón.

¿Como logran esta interacción?, si vemos en el código de estas aplicaciones, todas siguen mas o menos el mismo patrón:
  1. Agregar evento mousemove al elemento canvas
  2. comparar coordenadas del cursor con las de Objetos dibujados
  3. Seleccionar objeto sobre el cual se encuentra el cursor
  4. Al ocurrir un evento, ejecutarlo sobre el Objeto seleccionado
  5. Redibujar el canvas
No es algo difícil, pero podría ser mucho mas fácil.

Con esto en mente y luego de robar algunas ideas y algo de código de otros proyectos como procesingjs, jQuery, jquery-hotkeys y canto-js, nació canvas-event, una librería que se encarga del manejo de los eventos, facilitando el desarrollo de aplicaciones interactivas en el elemento canvas; ademas cuenta con objetos predefinidos como círculos, rectángulos, rutas e imágenes entre otros.

Algunos ejemplos de lo que se puede hacer con canvas-event:
Cv = Cevent("id-canvas");

// encadenamiento de métodos
Cv.circle( 40, 40, 40 )
.attr({fill: 'red'})
.rotate( 30 );

// manejo de eventos al estilo jquery
Cv.click( function(e) {
  this.fill = this.fill == 'green' ? 'red' : 'green';
});

// eventos del teclado asociados a combinación de teclas
Cv.keydown( 'ctrl+s', function(e){
  alert( "el Objeto ha sido guardados");
  return false;
});

// Drag and drop
Cv.ellipse( 30, 40, 50, 10 )
.drag({
  start: function() { console.log( "drag start" ); },
  
  move: function() { console.log( "move to ", this.x, this.y ); },

  end: function() { console.log( "drag end" ); }
})

// identificadores de Objetos
Cv.image(50, 100, "ball.gif").addId("pelota").rect(100, 50, 20).draw();

// Recuperar elementos por su Id o tipo;
Cv.find('#pelota').attr("alpha", .5).redraw();

Cv.find('rect').click(function(){
  alert( "Hola, soy un rectangulo" );
});

// Eventos live
Cv.mouseover( "#pelota", function(e){
   alert( "Soy una pelota" );
});

// Este Objeto también tendrá el evento anterior
Cv.circle(175, 75, 20).addId('pelota');
Ademas, podemos definir nuestras propias figuras extendiendo el objeto Shape:
var Triangle = Cevent.Shape.extend({
  // inicializador
  init: function( x, y, size ){
    this.size = size || 5;
    this._super( x, y );
  },

  // ctx es el context en el que se debe dibujar
  draw: function( ctx ) {

    ctx.save();

    // aplicamos estilo y trasformaciones
    this.applyStyle( ctx );
    this.setTransform( ctx );

    // definimos el contexto en el cual dibujaremos
    // para poder usar todos los métodos svg definidos en Cevent
    // como M (move) c (cubicBlazier) etc
    Cevent.beginPath( ctx )
    .M( this.x, this.y )
    .h( 1 * this.size )
    .v( 1 * this.size )
    .z();

    if ( this.fill ) { ctx.fill(); }

    if ( this.stroke ) { ctx.stroke(); }

    ctx.restore();
  }
});

// registramos el nuevo objeto
Cevent.registre( "triangle", Triangle );

// ahora podemos hacer uso del nuevo objeto
var Cv = Cevent( "canvas-id" );

Cv.triangle( 40, 30, 10 )
.attr({
  fill: "#050",
  alpha: .5
})
.focus(function() {
  this.fill = "#999";
})
.blur(function() {
  this.fill = "#050";
})
.draw();
Por el momento falta documentación pero se incluyen varios ejemplos de su uso, si tienen alguna duda y/o sugerencia por favor déjenla en los comentarios o en la pagina del proyecto en google code.

martes, 2 de noviembre de 2010

background transparent == problemas en IE

Este mas que un articulo es un recordatorio. Resulta que me pase todo un domingo tratando posicionar un div sobre un textarea para evitar que fuera editado; sí, ya se que para evitar que lo editen solo hay que usar el atributo disabled, pero en algunos navegadores esto no evita que el texto se pueda seleccionar.

Los oscuros motivos que me llevaron a intentar esto son irrelevantes, lo importante es que en la mayoria de exploradores la solucion fue muy sencilla: un div con posición absoluta y background transparente sobre el textarea y listo; pero no contaba con el siempre querido internet explorer (en todas sus presentaciones); resulta que al tener el div un background transparente, era como si colocara un marco sobre el textarea, por en medio del cual el cursor podía seleccionar todo lo de su interior (osease, no servia para nada); después de mucha lucha, cuando ya estaba a punto de rendirme, probé cambiando el color de fondo del div por blanco y como por arte de magia todo funciono, pero el contenido del textarea quedo oculto.

La solucion fue muy simple, ya que no podia usar un el background transparente use un png y listo todo funciono como quería.
/* en ie hace comportar al elemento como un marco */
div {
  background: transparent;
}

/* esto si funciona */
div {
  background: url(png-transparente.png);
}

Si algún día les sucede algo parecido quedan advertimos u_u

miércoles, 6 de octubre de 2010

jquery.placehold: placeholders para inputs text y password

Aun cuando los navegadores modernos soportan el nuevo atributo placeholder (firefox 3.7+, safari 4.0+, chrome 4.0+), debemos seguir usando javascript para navegadores mas antiguos; por esto decidí buscar algún plugin para jQuery que me facilitara el trabajo, pero muy pocos soportaban inputs tipo password y los que lo hacían tenían comportamientos extraños en IE (como raro no creen), así que tome el mejor que encontré y le hice algunas modificaciones.

Su utilización es muy simple:
$("[placeholder]").placeholder( "myPlaceholderClass" );
Podemos pasar un nombre de clase por medio del cual aplicar estilo al placeholder (por defecto es placeholder o_O).
.myPlaceholderClass {
  color: #999;
}
Pueden descargarlo del siguiente enlace, espero les sea útil.

jueves, 2 de septiembre de 2010

Revertir guardado en Wingdings

El otro día me encontraba aburrido a mas no poder, cuando de pronto me llaman a hacerme la siguiente pregunta: Imaginen que tienen un documento y no quiere que nadie mas se entere de lo que contiene, nada mejor que guardar dicho documento con el tipo de letra Wingdings, el problema es revertir el proceso, aunque no lo crean es algo mas común de lo que párese, la única solución es ir reemplazando cada signo por el caracter original, un proceso algo tedioso si se decide hacer a mano, así que porque no crear un script (en python) que automatice el proceso.

Para empezar tenemos que saber la correspondencia entre caracteres y signos, así que procedemos a abrir word e introducir los caracteres mas utilizados (letras mayúsculas y minúsculas, números, signos de puntuación, espacio) en una tipo de letra normal, con estos creamos una variable que llamaremos decode en nuestro código fuente, luego nuevamente en word cambiamos el tipo de letra por Wingdings y creamos otra variable que llamaremos encode, estas variables tienen que ser string unicode ya que los símbolos usados por la letra Wingdings son de lo mas raros; luego es cuestión de en un bucle ir reemplazando las coincidencias del caracter de encode en la posicion i con el carracter de decode en la misma posición. El script completo seria algo así:

#!/usr/bin/python
#!/usr/bin/python
# -*- coding: UTF-8 -*-
# Por: Jhonatan sneyder Salguero Villa (sney2002@gmail.com)

import codecs
from os.path import split, splitext

def translate(path=""):
 """" Convertir texto en Wingdings a caracteres normales ""

 text2decode = codecs.open(path, 'r', 'utf-8').read()

 decode = u"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!”?¿¡.,;():/*-+=&%$# @"
 encode = u"”¿¡"

 for i in range( len(decode) ):
  text2decode = text2decode.replace( encode[i], decode[i] )
 
 filename = splitext( split( path )[1] )[0]
 
 file = codecs.open('resultado_%s.txt' % filename, 'wb', 'utf-8')
 
 file.write( text2decode )
 
 file.close()
  
if __name__ == "__main__":
 import sys

 try:
  file = sys.argv[1]
  translate( file )
 except:
  print "uso: translate /ruta/archivo"
Ahora solo hay que copiar en un editor de texto plano lo que desean convertir, guardarlo y ejecutar el script dándole la ruta al archivo. Si alguien se pregunta por que no utilice el método translate de string, la razón es que la función maketrans no acepta caracteres unicode.

El script tiene algunos defectos como el no manejar excepciones en caso que el archivo en path no exista, solo funciona con este tipo de letra y en cada iteración crear un nuevo string. Como siempre todas las criticas son bienvenidas.

Si no tienen instalado el interprete python, pueden usar este "codificador/decodificador" Wingdings XDDD, esta escrito en javascript así que si usan demasiado texto puede bloquearse.




viernes, 27 de agosto de 2010

Fondo de escritorio cambiantes para ubuntu

El otro día convencí a un amigo para que se pasara a linux y entre las muchas preguntas que me empezaron a llegar por parte de él (todo sea por la causa), una era sobre como hacer un fondo de pantalla como esos que trae por defecto Ubuntu que cambian cada cierto tiempo, me dio mucha pena el no poder responder en el momento ya que no tenia ni idea de como funcionaba, así que me puse a la tarea y la verdad no era nada del otro mundo, solo un pequeño archivo xml que describe las transiciones de las imágenes; para ver como se estructura el archivo podemos ojear el que se encuentra en /usr/share/backgrounds/cosmos/background-1.xml.

Ahora que ya sabia como crear mis propios sliders, solo faltaba automatizar el proceso (flojo que soy), y como decidí hacer de python mi lenguaje por defecto XD, pues me puse a trabajar y esto fue lo que me salio, no es el súper programa pero intente hacerlo lo mas pythonista que pude, por favor si alguien lee esto y tiene mas conocimientos de python, se le agradecerán todos las criticas que me ayuden a mejorar u_u

#!/usr/bin/python
# -*- coding: UTF-8 -*-

# Por: Jhonatan sneyder Salguero Villa (sney2002@gmail.com)
# Crear fondos de escritorio cambiantes
import sys, os

FORMAT = """ 
  %(static_time).1f
  %(current)s
 
 
  %(transition_time).1f
  %(current)s
  %(next)s
 """
 
 
class BackgroundSlider():
 EXTENSIONS = [".jpg", ".jpeg", ".png"]
 
 def __init__(self, static_time = 1200):
  self.xml_parts       = [""]
  self.static_time     = static_time
  self.path = os.getcwd()

 def get_images(self, ext=EXTENSIONS):
  """ Guardar archivos con extencion (jpg, jpeg, png) en self.images """
  # Se puede especificar los archivos como argumentos
  files = sys.argv[1:] or os.listdir(".")
  # Filtrar imágenes
  self.images = [os.path.join(os.getcwd(), img) for img in files if os.path.splitext(img)[1].lower() in ext]


 def add_image(self, current, next, static_time, transition_time = 5):
  """ Adicionar imagen al slider """
  self.xml_parts.append( FORMAT % locals() )


 def make_slider(self):
  """ Crear archivo xml que define el slider """
  images = self.images

  # Todo termina donde empiesa U_U
  images.append( images[0] )

  for i in range( len(images)-1 ):
   self.add_image(images[i], images[i+1], self.static_time)
  
  self.xml_parts.append("")


 def save(self, filename = "background" ):
  """ Guardar el slider """

  xml = file(filename + ".xml", 'w')

  try:
   xml.write( "\n".join(self.xml_parts) )

  except IOError:
   print "Error al Guardar el archivo. Intente de Nuevo"
  
  finally:
   xml.close()


if __name__ == "__main__":
 slider = BackgroundSlider()

 slider.get_images()

 slider.make_slider()

 slider.save()
Luego puse este archivo con los demás script de nautilus (~/.gnome2/nautilus-scripts) y ahora mi amigo cuando quiera un fondo de escritorio, solo selecciona las imágenes, click derecho crear wallpaper y listo. Si les interesa pueden bajar el script de este enlace:

viernes, 20 de agosto de 2010

Saltándose los captchas de Megaupload con python

Como todo buen gorrón me gusta las cosas gratis y fáciles, sobre todo cuando se tiene una serie de 24 capítulos que se quiere descargar sin estar pendiente rellenando captchas, y aunque las cuentas premiun no son tan caras, la verdad no me apetece gastarme una platica en algo que voy a utilizar muy rara vez, así que hice lo que cualquier persona normal haría, juntar un montón de software libre y pegarlos con un poco de python y voila mi propio gestor de descarga con reconocimiento de captchas (solo megaupload), ya se que hay muchos programas que hacen lo mismo y ademas libres (Tucan por ejemplo), pero usarlos no seria tan divertido, así que me puse a recolectar los materiales para mi "manualidad". Gestor de descargas ya tenia y no podía ser mejor que wget, solo faltaba el reconocimiento de caracteres, ocr que llaman; después de un poco de búsqueda apareció tesseract que sin lugar a dudas es el mejor programa de reconocimiento de caracteres que haya visto hasta el momento. Solo faltaban algunas librerías como pytesser para "comunicarse" con tesseract y  libxml2dom (requiere libxml2) para consultar el dom en documentos aun cuando estos estén mal formados.

Como ven, lo bonito del software libre es que con tan solo una búsqueda nos encontramos con programas maravillosos, los cuales podemos utilizar sin ninguna restricción (sí, ya se que esto no es completamente cierto). Ahora si, con ustedes el script:
import libxml2dom
import urllib2
import urllib
import pytesser
import Image

MAX_TRY = 5 # intentos antes de rendirse
WAIT_TIME = 45 # tiempo de espera en segundos
CAPTCHA_INPUT_NAME = "captcha"
TEMP_FILE_NAME   = "tempImg.gif"
ID_DOWNLOAD_LINK = "downloadlink"

def urlopen(url, data={}):
 """Abrir un recurso remoto, si se pasa data se envía por POST"""
 req = urllib2.Request(url, urllib.urlencode(data) if data else None)
 try:
  sock = urllib2.urlopen(req)
  try:
   data = sock.read()
  finally:
   sock.close()
 except IOError:
  print "el archivo no existe"
 else:
  return data

def html2dom(page):
 """Retornar representación DOM de un documento html"""
 dom = libxml2dom.parseString(page, html=1)
 return dom

def tag(elem, tag):
 """Encontrar descendientes de un elementos por su tag"""
 elements = elem.getElementsByTagName(tag)
 return elements[0] if len(elements) == 1 else elements
 
def id(elem, Id):
 return elem.getElementById(Id)
 
def attr(elem, attr):
 """Retornar atributo de un elemento"""
 return elem.getAttribute(attr)

def save_img(uri):
 """Guardar imagen en local"""
 data = urlopen(uri)
 temp = file(TEMP_FILE_NAME, 'wb')
 temp.write(data)
 temp.close()

def resolver_captcha():
 """Resolver captcha"""
 data = Image.open(TEMP_FILE_NAME)
 return pytesser.image_to_string(data)[:4]

def fill_form(page):
 """Recolectar datos de formulario"""
 try:
  form = tag(page, 'form')
  img = tag(form, 'img')

  save_img(attr(img, "src"))
 
  # crear diccionario valores a enviar
  data = dict([(attr(i, 'name'),attr(i, 'value')) for i in tag(form, 'input')])

  # "rellenar" campo captcha
  captcha = resolver_captcha()
  data[CAPTCHA_INPUT_NAME] = captcha
 except:
  return None
 else:
  return (data, captcha)

# Donde la magia sucede XD
def get(url):
 """Descargar archivo"""
 times = 1

 # solo links de megaupload
 if url.find('http://www.megaupload.com/') != 0:
  print "%s no es un link de megaupload" % url
  return 0
  
 # Abrir pagina de formulario
 page = html2dom( urlopen(url) )

 while True:
  # llenamos el formulario
  data, captcha = fill_form(page)
  
  if not data or times == MAX_TRY:
   print "Error al abrir %s, puede que el link haya sido deshabilitado" % url
   return 0

  print "%s ==> intento %d\ncaptcha = %s" % (url, times, captcha)
  times += 1

  # Enviamos formulario por POST
  page = html2dom( urlopen(url, data) )
  
  # Si el captcha esta correcto obtenemos la página de descarga
  download_page = id(page, ID_DOWNLOAD_LINK)
  
  if download_page:
   download_link = tag( download_page , 'a' )
   import subprocess, time
   
   # please wait.... XD
   time.sleep(WAIT_TIME)

   args = ['wget', '-c', attr(download_link, 'href')]
   
   # empezamos a descargar ^_^
   proc = subprocess.Popen(args)
   retcode = proc.wait()
   return 1
   
def get_from_file(file_path):
 """descargar múltiples archivos leyendo urls almacenadas en un archivo"""
 try:
  sock = file(file_path)
  try:
   lines = sock.readlines()
  finally:
   sock.close()
 except IOError:
  print "el archivo %s no existe" % file_path
 else:
  for line in lines:
   url = line.strip()
   if url:
    get( url )

if __name__ == "__main__":
 import sys

 arg = sys.argv[1:]
 
 if arg[0] == "-f":
  get_from_file(arg[1])
 elif "http" in arg[0]:
  get(arg[0])
 else:
  print "uso:\nmget link_descarga\nmget -f /ruta/archivo/links_descarga\n" 

Claro que le hacen falta algunas funcionalidades, pero para lo que lo necesito me basta y me sobra, ademas como ya dije existe una gran variedad de programas que hacen lo mismo. Si lo desean pueden descargar los archivos desde el siguiente enlace:
P.D.: Se aceptan criticas, solo no sean tan duros XD

jueves, 25 de marzo de 2010

Entendiendo la función Y combinatoria

Estando un poco aburrido y sin nada que hacer, se me vino a la mente una extraña pieza de código que había visto por ahí, esta era la función Y combinatoria. donde la vi o porque de repente pensé en ella no importa, lo importante era que ya no me la podía sacar de la mente, era como cuando se escucha mucho una canción y luego sin pensarlo terminas repitiéndola una y otra vez en la mente; así que hice lo único que podía hacer, partirme el coco intentando entender que era lo que hacia y como lo hacia. Empece como siempre preguntándole al omnisciente Google, gracias al cual encontré artículos muy interesantes, pero el que me pareció mas interesante fue uno donde a modo de ejercicio mental se va obteniendo la función Y a partir del ya conocido factorial, pero todo iba bien hasta que como mas o menos a la mitad de la explicación me perdí, así que decidí realizar el mismo ejercicio pero a mi propia manera; el resultado de ese ejercicio es este post que me salio mas largo de lo que pensé; alguien puede argumentar que es una vil copia, pero me vale. Ahora si después de esta pequeña introducción manos a la obra. A continuación la ya mencionada función Y para que vayan viendo de lo que estoy hablando:

Antes de empezar debemos tener claro lo que es closure, hay muchos artículos en los que hablan sobre este tema, pero aquí daré una explicación a mi forma de entender. una closure (no encuentro una forma adecuada de traducirla al español) es la "propiedad" que tienen las funciones anidadas de tener acceso a las variable definida en la función en la cual están contenidas (siempre y cuando no la sobrescriban) aun después de que esta ultima sea "destruida". Como sabemos todas la variables definidas dentro de una función (por medio de var) son locales a esta y al terminar su ejecución son "destruidas". a continuación un ejemplo:
function f(){
  var x = 2;
  alert( 'x vale ' + x );
};

f();// alerta 'x = 2'

alert(x):// Error variable no definida
Como vemos la variable x solo existía en el interior f. Ahora viene lo divertido, ya que si creamos una función anidada y la retornamos para hacerla visible al exterior, esta seguirá teniendo acceso a x aun después de finalizada la ejecución de f:
function f(){
  var x = 2;
  alert( 'x = ' + x );
  return function(n){
     x += n;
    alert('ahora x vale ' + x );    
  }
};

fclosure = f(); // alerta 'x vale 2'

alert('x vale '+ x):// Error x no esta definida fuera de f

/* fclosure aun tiene acceso a x */
fclosure(1);// alerta 'ahora x vale 3'
Creo que no hacen falta mas explicaciones, esto es closure; ya que quedo claro este concepto (eso creo) podemos continuar. Ahora si empezaremos con la función factorial he iremos avanzando hasta llegar paulatinamente a la definición de Y, como ya saben la función factorial luce algo así:
function factorial(n) {
  return n < 2 ? 1 : n * factorial(n-1);
}
Como vemos la función factorial se llama a si misma usando su propio nombre, este es precisamente el "problema" que resuelve Y al permitir la recursion anónima en lenguajes donde no esta soportada nativamente. como una anotación aparte dejemos en claro que javascript permite la recursion anónima gracias a la propiedad callee del objeto arguments que esta disponible en el interior de todas las funciones como vemos a continuación:
// arguments.callee es una referencia a la propia funcion
(function(n){ return n<2 ? 1 : n * arguments.callee(n-1)})(4) // retorna 24
De esta forma definimos una función que se llama a si misma sin la necesidad de usar su propio nombre (lo cual seria imposible ya que no posee uno). una vez aclarado esto empecemos por eliminar la necesidad de crear recursion en la función factorial por medio de su nombre, esto se puede lograr creando una función que llamaremos curry la cual toma como argumento una función f que en este caso sera ella misma, curry retornara la función #1 (factorial) que gracias a una closure creada (suena feo) podrá acceder a f para iniciar la recursion:
curry = function(f) {
  return function(n) { // #1
    return n < 2 ? 1 : n * f(f)(n-1);// llamamos la función sin usar su nombre
  };
};
curry(curry)(4); // pasamos como argumento la propia función
Con esto solucionamos el problema de utilizar el nombre de la función para crear la recursion, pero la parte f(f)(n-1) se ve algo antinatural, seria mejor algo como f(n-1); si pensamos bien, la solución mas lógica es crear otra función anidada (#2) dentro de #1 la cual tome como argumento el resultado de f(f), se vería así:
curry = function(f) {
  return function(n) { // #1
    function g(h, n) { // #2 toma el resultado de f(f) en h
      return n < 2 ? 1 : n * h(n-1); // h == f(f)
    };
    return g(f(f), n); // pasamos como parámetro a f(f) y n
  };
};

curry(curry)(4); // retorna 24
Podríamos decir que hemos terminado, pero el tener que llamar a factorial (#2) con un argumento de mas (h) no parece algo lógico, así que separamos estos argumentos gracias a una tercera función anidada:
curry = function(f) {
  return function(n) { // #1
    function g(h) { // #2 toma el resultado de f(f) en h
       return function (n) { // #3 solo toma n
        return n < 2 ? 1 : n * h(n-1); // h == f(f);
      };
    };
    return g(f(f))(n); // ejecutamos #2
  };
};

curry(curry)(4); // retorna 24
Si nos fijamos bien la función #2 (factorial) podría ser cualquier otra (eje. fibonacci), lo realmente importante es donde se ejecuta, para lo cual lo único necesario es tener acceso a ella ya sea por estar definida globalmente o por closure, como se desaconseja el uso de variables globales lo haremos de la segunda forma tomando la función curry y anidandola en otra función que llamaremos Y :), que recibirá como argumento alguna función f para que #2 pueda acceder a ella por closure y así poder ejecutarla, también cambiamos la forma de llamar a f y usaremos apply para poder usar funciones que reciban mas de un argumento:
function Y(f){ // f = cualquier función con el formato de #factorial
  function curry(g) { // g == curry
    return function() { // #1
      return f(g(g)).apply(null, arguments);
    };
  };
  return curry(curry); // ejecutamos curry
};

/* ¡funciona! */
fac = Y(function(f){ // f = funcion
 return function(n) { // retornar una función
   return n < 2 ? 1 : n * f(n-1); // acceder a f por closure
 };
});

fac(4); // retorna 24
y así es como funciona Y, aunque generalmente se encuentra escrita de la siguiente forma:
function Y(f){
  return (function(h) { // h = curry
    return h(h); // ejecutamos curry
  }(function (g) { // la que llame curry
      return function() { // #1
        return f(g(g)).apply(null, arguments);
     };
  }));
};
Pues eso era todo, aunque casi se me estalla un aneurisma, por fin entendí como funciona la bendita función Y. espero que a alguien mas le haya sido de utilidad este ejercicio y perdonaran los errores ortográficos y/o de redacción pero esto de la escritura no me va de a mucho.

lunes, 8 de febrero de 2010

I'm back

Hola a todos (otra vez hablando solo!), pues de nuevo me dio por escribir, como deben saber los que me conocen, esto de la escritura no me va muy bien, pero espero poder mantener un ritmo constante como si ha alguien le importara y contarles todos los acontecimientos, proyectos y otras chorradas que se me ocurran. pues eso, y para empezar, decir que en estos momentos, me encuentro en el limbo en lo que al estudio se refiere, ya que al parecer no hay recursos para continuar con el convenio con la universidad en la que estudio, y al parecer a algunos compañeros esto les vale huevo y no piensan hacer nada, esto no quiere decir que me haya quedado rascándome el ombligo, últimamente he estado experimentado un poco con el elemento Canvas, intentando crear un framework que permita hacer animaciones y dibujos de manera sencilla, al estilo de jQuery, y la cosa va mas o menos bien, aunque el código, a mi parecer es un desastre, ya que esta poco documentado y hay algunas funcionalidades un poco rebuscadas (cosas de hacer todo al paso), pero bueno, como experiencia de aprendizaje esta bien; otra cosa que me ha rondado en la mente es todo eso de la IA (inteligencia artificial que le llaman) y pues para estar aprendiendo solo, me ha ido bien, en especial al no haber encontrado mucho material en español de donde agarrar, y mi ingles es m.... bueno digamos que regular.

Bueno creo que eso es todo, espero poder cumplir la promesa que me he hecho a mi mismo de mantener este blog actualizado y no es que a alguien le importe, pero tal vez, solo tal vez alguien me llegue a leer y hasta le parezca interesante algo de lo que escriba (soñar no cuesta nada). Pues eso es todo, adiós y que vuelvan por aquí.
 
© 2009 NovatoZ. All Rights Reserved | Powered by Blogger
Design by psdvibe | Bloggerized By LawnyDesignz