Personnalized video game to gift

As a side project, I'm developing KustGame a mobile video game where you can :
  • personnalize the player
  • add custom pictures
  • add special messages
The idea came when looking for a original way to offer a gift to my girlfriend : she had to finish the game to get the message indicating where was hidden the gift. In order to have quickly a product to sell, I used :
  • GameMaker, since the software allowing you to develop quickly and easily a game compatible with iOS, Android, windows, html5
  • Mobirize, to create the static homepage without the need of knowing HTML (for free).
  • Plain PHP without framework to develop the simple backend (no Java, no Symfony)
  • Shared host
  • jQuery to add nice interactions in the customize form (no angular, no backbone)
  • PayPal for it's simple API (NVP)
The aim of that version is to be as simple as possible and to test if there is a market. The video game is planned to be sell before Christmas and published on the AppStore (iPhone) and Google Play Store (Android). By the way, Christmas is in 7 days, so the deadline is imminent.  
Posted in Uncategorized | Leave a comment

Parsing empty column with OpenCSV

Lets say you have the following 2 lines csv file :
jules,23 thomas,
And object :
Class Person { String name; Integer age; //getter + setter }
When you use the java opencsv library to map that csv to the object, it will throw an exception :
Caused by: java.lang.NumberFormatException: Zero length string at java.lang.Integer.decode(Integer.java:1162) at com.sun.beans.editors.IntegerEditor.setAsText(IntegerEditor.java:39) at au.com.bytecode.opencsv.bean.CsvToBean.convertValue(CsvToBean.java:82) at au.com.bytecode.opencsv.bean.CsvToBean.processLine(CsvToBean.java:63) at au.com.bytecode.opencsv.bean.CsvToBean.parse(CsvToBean.java:48) ... 22 more
[doesn't work!!!!] After looking at the source code I tried to add my custom PropertyEditor:
public class IntegerEmptyEditor extends NumberEditor {
    public IntegerEmptyEditor() {
    }
    public void setAsText(String var1) throws IllegalArgumentException {
        this.setValue(var1 == null || var1.equals("")?null:Integer.decode(var1));
    }
}

PropertyEditorManager.registerEditor(Integer.class, IntegerEmptyEditor.class);
PropertyEditor pe = PropertyEditorManager.findEditor(Integer.class);
//  BUT RETURN IntegerEditor instead of IntegerEmptyEditor
Instead, let's treat all empty string value like null value :
CsvToBean csv = new CsvToBean() {
    protected Object convertValue(String value, PropertyDescriptor prop) throws InstantiationException, IllegalAccessException {
        if(StringUtils.isEmpty(value)) {
            value = null;
        }
        return super.convertValue(value, prop);
    }
};
Complete code mapping the csv file to the object :
CSVReader csvReader = new CSVReader(new FileReader("src/test/resources/utils/csv/person.csv"));
ColumnPositionMappingStrategy strat = new ColumnPositionMappingStrategy();
strat.setType(Person.class);
String[] columns = new String[] {"name", "age"};
strat.setColumnMapping(columns);
CsvToBean csv = new CsvToBean() {
    protected Object convertValue(String value, PropertyDescriptor prop) throws InstantiationException, IllegalAccessException {
        if(StringUtils.isEmpty(value)) {
            value = null;
        }
        return super.convertValue(value, prop);
    }
};
List<StopExt> list = csv.parse(strat, csvReader);
csvReader.close();
Posted in Uncategorized | Leave a comment

Create and test a service using translator container with Symfony2

Here is the code to create a service which uses the translator component from Symfony2. The service is also tested.

Create the service

in messages.en.yml :
cb.texta1: Travel from %A% to %B%
in services.yml :
cb.labelbuilder:
  class: AppBundle\Service\LabelBuilder
  arguments: [@translator]
LabelBuilder.php
namespace AppBundle\Service;

use Symfony\Component\Translation\TranslatorInterface;

class LabelBuilder {
    /**
     * @var TranslatorInterface
     */
    private $translator;

    public function __construct(TranslatorInterface $translator) {
        $this->translator = $translator;
    }

    public function buildText1($labelA, $labelB) {
        return $this->translator->trans(
            'cb.texta1',
            [
                '%A%' => $labelA,
                '%B%' => $labelB
            ]
        );
    }
}

Use the service in your controller

$this->get('cb.labelbuilder')->buildText1($lblA, $lblB);

To test with PHPUnit

Source SO LabelBuilderTest.php
namespace AppBundle\Service;

use AppBundle\Entity\StatPriceDuration;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;

class LabelBuilderTest extends WebTestCase {

    protected static $translation;

    public static function setUpBeforeClass() {
        $kernel = static::createKernel();
        $kernel->boot();
        self::$translation = $kernel->getContainer()->get('translator');
    }

    public function testBuildText1() {
        $builder = new LabelBuilder(self::$translation);

        $result = $builder->buildText1("Paris", "Lille");

        $this->assertEquals(
            trim($result),
            "Travel from Paris to Lille"
        );
    }
}
Posted in Uncategorized | Leave a comment

Comparabus

Je développe depuis un an (Janvier 2013) un site permettant de comparer les prix des billets de car (ligne de bus régulières nationales et internationales). Vous pouvez y accéder depuis www.comparabus.com .
La plateforme permet à l'heure actuelle de comparer les prix d'Eurolines, iDBUS et megabus. Ce qui représente 630 villes desservies.
Si vous recherchez des prix imbattable pour vous déplacer (lentement!), venez de temps en temps jeter un coup d'oeil sur comparabus.com . Il vous est par exemple possible en ce moment d'effectuer un aller-retour Paris-Londres pour 2€50. Ou encore un Paris-Bruxelles pour 2€50 également.

Pourquoi avoir créé ce site?

Depuis 2 ans les compagnies de car ont le droit d’effectuer des trajets nationaux (avant c’était la SNCF qui avait le monopole, cf cabotage). Ainsi il est désormais possible d'effectuer un Paris-Lille en bus (avec idbus ou eurolines par exemple) ce qui était avant illégal. Etant donné que plusieurs autocaristes proposaient le même trajet (3 par exemple pour un Paris-Londres) il devenait intéressant de pouvoir comparer leurs tarifs facilement et rapidement; ce qui n'était pas possible au lancement de mon site.

Côté technique

Si vous vous interressez à la technique, le site est développé en php (framework Symfony), bien que j'aurai préféré le faire en JEE avec Spring MVC, et est hébergé sur une machine virtuelle d'OVH (Debian, VPS 512Mo de RAM). Les scripts sont quant à eux développés en Java.
Si vous avez des conseils ou critique, n'hésitez pas à utiliser formulaire de feedback disponible sur le site comparabus.com à droite. Si vous souhaitez que je continu à améliorer et reférencer de nouveaux autocariste soutenez moi en "likant" la page et en n'en parlant autour de vous!
Posted in Uncategorized | Leave a comment

Storing opening and closing times in database for stores

Requirement:
  • Store can open and close many times during the day
  • Store can close the day after it opens (store opened after midnight)
  • Precision of the time must be 1/2 hour / 30minutes
  • Searching for the store opened at a certain time must be done efficiently
  • Every weeks have same business hours
Info:
  • Type "Time" doesn't exists in MySQL
Example:
  • Mon : 10.00-14.00 17.00-22.00
  • Tue : 10.00-22.00
  • Wed : 10.00-22.30
  • Thu : 10.00-22.30
  • Fri : 10.30-01.00 (+1d)
  • Sat : 11.30-02.00 (+1d)
  • Sun : Closed

1/Encode all the opening-closing time of a store in one number

Let's see the opening time table as an array. For instance if the store would be open from 10am to 2pm and 5pm to 10pm we would have: 000000000000000000001111111100000011111111110000 <=> 17575273562112 20*0 (20 first 1/2 hour closed) ; 8*1 (4 hours / 8 1/2hours openend) ; etc...

For one day there are 2^(24*2) = 2^48 possibilities
For one week there are 2^(24*2*7) = 2^336
As the bigger mysql integer size is 2^64 we don't have big enough integer to encode it.
More explication here: http://remy-mellet.com/dev/b48encode.html

2/OpeningPeriod table

  • id
  • store_id
  • day [0=sun - 1=mon - ... - 6=sat]
  • open [0am=0; 0.30am=1; 1am=2 ... 11pm=46]
  • close [0am=0; 0.30am=1; 1am=2 ... 11pm=46 ... 2am(+1d)=52]
We must be able to store opening time during the night, so for the closing time, if it was open the day before we will add 48. If the lastest time is 10am (d+1) <=> 48+20 = 68. So it can be encoded as a Mysql TINYINT ( 2^8 ).
Current time is 1915, Mon => curTime = ceil(19.25*2) = 39 ; curDay = 1 ; prevDay = 0;
Current time is 0130, Sun => curTime = ceil(1.5*2) = 3; curDay = 0; prevDay = 6;
Get the stores id open:
select distinct(p.store_id) from OpeningPeriod p where (day = curDay and open <= curTime and close >= curTime ) OR (day = prevDay and open <= curTime+48 and close >= curTime+48)

3/OpeningPeriod table w/o day

To simplify, instead of taking as reference for the opening day the beginning of the day, why not just take the beginning of the week? Thus we will take off the day column.
  • id
  • store_id
  • open [0am/Sun=0; 0.30am/Sun=1; 1am/Sun=2 ... 11pm/Sun=46 ... 6pm/Sat=324 (48*6+18*2)]
  • close [0am/Sun=0; 0.30am/Sun=1; 1am/Sun=2 ... 2am/Sun = 4+336 = 340]
The open and close can be encoded as Mysql SMALLINT (2^16)
Current time is 19.15, Mon => curTime = ceil(19.25*2) = 39
Current time is 01.30, Sun => curTime = ceil(1.5*2) = 3
Get the stores id open:
select distinct(p.store_id) from OpeningPeriod p where (open <= curTime and close >= curTime ) OR (open <= curTime and close >= curTime+336)
Links:
Posted in Uncategorized | Leave a comment

Symfony 2 : This script is only accessible from localhost / You are not allowed to access this file

Vous voulez accèder au fichier app_dev.php ou config.php de votre application Symfony 2, mais vous avez l'erreur "This script is only accessible from localhost" ou "You are not allowed to access this file".
Récupérer votre IP et ajouter là dans le fichier app_dev.php et config.php en modifiant le code ci-dessous:
if (!in_array(@$_SERVER['REMOTE_ADDR'], array(
    '127.0.0.1',
    '::1',
	'MONIP'
))) {
    header('HTTP/1.0 403 Forbidden');
    exit('This script is only accessible from localhost.');
}
Posted in Uncategorized | 1 Comment

Utiliser Symfony 2 avec 1and1

Symfony 2 à besoin de php 5.2.3. Vous devez donc créer/éditer le .htaccess à la racine et y ajouter:
AddType x-mapp-php6 .php
Cela va vous permettre d'utiliser la version php 5.4.0beta1.
Il reste maintenant le problème de timezone, pour cela créez un fichier php.ini dans le répertoire Symfony/web contenant:
date.timezone = "Europe/Paris"
short_open_tag = off
magic_quotes_gpc = off

Maintenant vous pouvez lancer, http://foo/bar/Symfony/web/config.php et vous ne devriez plus avoir de problèmes.
Posted in Uncategorized | 2 Comments

Draw SVG rectangle with 1,2,3 or 4 rounded corner

Here is the code allowing you to draw with Raphael JS a rectangle with one or many corner rounded and to have a corner more rounded than the other. This code will add roundedRectangle method to Raphael JS (how to extend Raphael). roundedRectangle(x, y, width, height, upper_left_corner, upper_right_corner, lower_right_corner, lower_left_corner).
window.onload = function () {
    //roundedRectangle(x, y, width, height, upper_left_corner, upper_right_corner, lower_right_corner, lower_left_corner)
    Raphael.fn.roundedRectangle = function (x, y, w, h, r1, r2, r3, r4){
        var array = [];
        array = array.concat(["M",x,r1+y, "Q",x,y, x+r1,y]); //A
        array = array.concat(["L",x+w-r2,y, "Q",x+w,y, x+w,y+r2]); //B
        array = array.concat(["L",x+w,y+h-r3, "Q",x+w,y+h, x+w-r3,y+h]); //C
        array = array.concat(["L",x+r4,y+h, "Q",x,y+h, x,y+h-r4, "Z"]); //D

        return this.path(array);
    };

    var paper = Raphael("canvas", 840, 480);

    paper.roundedRectangle(10, 10, 80, 80, 0, 10, 25, 5).attr({fill: "#f00"});
}
Online example

If you don't want to use Raphael and you don't want neither to understand how svg path is working, use this function to generate the path corresponding to the corner rounded rectangle:
function p(x,y){
  return x+" "+y+" ";
}

function rectangle(x, y, w, h, r1, r2, r3, r4){
  var strPath = "M"+p(x+r1,y); //A
  strPath+="L"+p(x+w-r2,y)+"Q"+p(x+w,y)+p(x+w,y+r2); //B
  strPath+="L"+p(x+w,y+h-r3)+"Q"+p(x+w,y+h)+p(x+w-r3,y+h); //C
  strPath+="L"+p(x+r4,y+h)+"Q"+p(x,y+h)+p(x,y+h-r4); //D
  strPath+="L"+p(x,y+r1)+"Q"+p(x,y)+p(x+r1,y); //A
  strPath+="Z";

  return strPath;
}
Posted in Uncategorized | 1 Comment

Google Chrome extension: Store your Ryanair user’s preferences

Download the google chrome extension. This extension allows you to save time when booking a Ryanair ticket by:
  • storing you preferences (Firstname, Lastname, address, phone number, etc)
  • pre-filling the forms to avoid paying extra charges (No bags, No insurances, No text messages, etc)
You can change you preferences (which will be set automatically the first time you buy a ticket whis the extension enable) by clicking on the icon which appear when you are on ryanair website.

Developers: Download source code : ryanair_preferences This extension uses:
  • content script injection (when the page is changed)
  • localStorage
  • input value and localStorage mapping
  • option page
Posted in Uncategorized | Tagged | 1 Comment

Google Chrome Extension: Inject contentscript in a webpage with js errors

Because the webpage contains errors, we can't inject directly the content script using this code in the manifest.json file
"content_scripts": [
    {
      "matches": ["http://*/*"],
      "js": ["jquery.js", "content_script.js"]
    }
  ]
But we can inject the code each time the page is changed using an url change event listener, inserting the following code in the background.html page:
chrome.tabs.onUpdated.addListener(function(tabId,changeInfo,tab){
	if(changeInfo.status == "complete" && (tab.url.substr(0,25) == "http://www.mypage.com/" || tab.url.substr(0,26) == "https://www.mypage.com/") ){
		chrome.tabs.executeScript(null, {file: "jquery.js"});
		chrome.tabs.executeScript(null, {file: "content_script_injected.js"});
	}
  });
Posted in Uncategorized | Tagged | Leave a comment