Jun 16th 2010, 12:49

DRM - Dont rightly mind

Recently Ofcom said it will allow the BBC to use DRM on its HD content. Got a problem with this? I haven't, but I reckon a lot of people have. So what's everyone's beef with DRM? Surely you don't mind artists/companies being paid, and I know you're not all pirates so what's the problem. Well here is why I think it's got a bad rep and where some of the arguments against fall short.

I can't listen to my itunes

Steve Jobs

Source:Matthew Yohe

I completely sympathise. You buy a song and now you can't listen to it unless you sit at your computer. Is this is what the MP3 revolution is about, being tied to your PC? Get mad about this by all means, feel free to fume. Grrr DRM.

Oh what's this, there goes Steve Jobs, looking smug jogging by listening to his tunes. How come he isn't having these issues? Oh, he's got an iphone. Wait a second iphone, ipod, itunes is there a connection? Yes, of course.

So it's not DRM's fault it transpires, just Apple's attempt at forcing you to use their products. DRM is simply the tool and that doesn't make it evil. Rope can be used to strangle just as Apple is using DRM but we cannot hold the rope to blame.

I can't share my music with my friends

Err that's the idea. By all means let them have a listen on your MP3 player and then if they like it they can buy it. Afterall, if we send the music moguls bust there'll be no music to argue over at all.

It doesn't work

England HatSome people will argue that DRM doesn't have any effect in stopping music pirating. I consider this on its own to be a very weak argument indeed. Imagine I go to the world cup wearing a giant England top hat which I believe will help England trounce Algeria. You can well imagine the supporter behind me asking if I could remove my hat. "Why?", I ask, "Because it doesn't work." he replies.

That would not present me with a compelling reason to remove my hat which is afterall keeping my ears warm (it's winter in South Africa). Now if he had said "Because I can't see, and besides it doesn't work" that's a good reason, and I'd happily oblige. Luckily I brought ear muffs.

On it's own 'it doesn't work' just doesn't work.

It's not open

I love open source software, it's great it allows for easy adoption and improvement and it's usually free. The thing is there is no reason DRM can't be free and open source, in fact the people at sun are doing just that. If we had a widely adopted open DRM then you wouldn't get cross platform issues and you could download from wherever and play on whatever. Ideal.

I can't do X which is perfectly fair use

Hamster Handgun Hunting

Original images:Peter Maas,Nemo5576

You can't own a hand Gun in this country. Does that bother you? Not me. See it's not that there is fundamentally anything wrong with having a hand gun it's just that the potential is there for something really awful and since there are very few good reasons why you should actually be using/having one then it's best to ban them altogether. Unfortunately there will be a few people who now can't enjoy handgun hamster hunting (or whatever they did) but that's just tough, a little sacrifice for the greater good. DRM is a bit like that. It should be designed so that we can do as much as possible that is 'fair use' whilst stopping outright pirating, but as a side effect some uses may not be possible any more. As long as these are kept to a minimum and to obscurity, then maybe the pro's outweigh the con's.

I can't afford to buy music

At the moment music isn't a human right, if you can't afford it you don't get it. But since I sympathise here is a tip - try Spotify, the radio or charity shops.

There is nothing wrong with sharing music and DRM stops me doing so

These arguments come in all manner of forms from the 'company x is so rich that..' to 'Mr X wouldn't of bought the track anyway so no one has lost money'. These are arguments basically arguing piracy is fine and should be legal and we should share all we like on peer to peer networks. Now I don't have a clue what would happen if you never had to pay for music/films/games/software/etc because piracy was legal and everything could be shared freely, but I don't think you do either. You would need a fantastic grasp of the industry and economics, probably psychology and maybe a pinch of chaos theory too. I reckon the few people sufficiently qualified to answer that question would admit they were just guessing.

In conclusion

Most of my arguments are based on the premise that we should pay for music/games/films/etc and that while many of the implementations of DRM may be bad that doesn't make the idea bad. I do strongly think however someone needs to sort out DRM and define what fair use really is so people who have bought their music/game/movie can get on with enjoying it.

Share/Bookmark

There are 16 comments. Read them all and add your own.


May 24th 2010, 09:16

A story of 3 e-commerce packages - trying to fit an isometric multi-hexagonal peg into a round hole

A sinking shopping cart

Source: ashley baz

It's been a fantastically long time since my last post and my only excuse is a full time work placement with an hour and a half commute each way. I have been spending the weekends away in my campervan and have also been trying to create an e-commerce site for a client in the free time left over. A poor excuse but I'm sticking with it.

 

Anyway, the last part of my excuse (creating an e-commerce site) is the source of today's post. A small site which sells high-end hats and headwear, a simple enough task. Oh, the site also has to be totally bespoke in it's appearance.

 

So far so good. Now, my client has set aside a bit of money to buy an e-commerce package with the perfectly reasonable plan that we have the robust back-end and features of a paid for CMS and then I come along, help get it set up and apply a bespoke theme so that it looks just right.

Nice plan.

Actinic Catalogue

Actinic logoPlan in place the client contacted a particular site which has a look and setup she likes and found out that they use Actinic Catalogue. Bingo, we had found our CMS.

Actinic Catalogue very helpfully offer a 30 day free trial and so without further ado, I get cracking.

The the thing about Actinic is that it's a Windows based piece of software that creates the site as a load of CGI scripts (I think) and then uploads it to the site via ftp. Now in my mind, this setup is a bad idea. Windows based software, bad! No web interface, bad! CGI that I don't understand, bad! On the other hand the Windows interface does give powerful order and product management and you don't get that lag you usually do with internet based software. Backups are also very simple.

All this was fine though, the client was happy with it and so was I. So I start playing, and the more I play the less I like it. I couldn't get it to upload to the internet, it was having a problem with the ftp connection. Editing the site was through an awkward combination of a WYSIWYG editor and code editing mixed in with if's, else's and for's in some custom language. A bit of a 'mare.

However, I was determined to get to grips with it and I am sure I could given the time but in this instance it was not to be. In the end we had to drop them as the software could not take payments through the gateway of choice (Barclaycard eDPQ).

The gig was up with that one, and I must say I was actually rather relieved. We then pressed on in search of a new e-commerce package to suit our needs.

BossCart

BossCart LogoNext we came accross BossCart. Getting excited now, not only did they promise to suppport ANY payment gateway, they also had a few Wiki pages on creating a custom theme AND I even found a 40% off offer. We were convinced! Unlike the Actinic guys there was no free trial but there was instead a 30 day money back guarantee. So with the software purchased I pressed on.

BossCart is PHP and MySQL powered, ideal as far as I'm concerned. Install was a snap and we had a site up in a few minutes. Next came theme-ing/style-ing. I felt (coming from a CakePHP/ model-view-controller background) that the view was horribly mixed in with the control side. So in the 'theme' files you had all sorts of logic from getting all the items in the shopping basket to sending emails mixed in with the real view components such as divs and links. From a software design point of view I feel this is bad, but on the other hand it does give you a lot of power when theme-ing. "With great power comes great responsibility" however, as Spiderman once said. I.e. it's easy to break things. But all in all I was having a good time with BossCart, it was proving easy to theme and the back-end looked good.

Then a snag was hit. You see a fundamental criteria of my clients turned out to be the ability to have products in more than one category e.g hat's under 'styles' as well as under 'colours'. Now some may say she's mad for such an out outrageous request, no? Well the people at BossCart did. For this is not possible with their software at all.

An interesting thing about the people at BossCart is that I found (when looking to buy) their sales team to be excellent, quick and efficient. My client found when asking after a feature, it to be a slow, awful and useless process. She was told you can do it, you can't do it, wait for the next release, etc, etc. This is probably a more general observation on the differences between sales and after sales customer service which i'm sure isn't limited to BossCart.

VaM Cart

VaM Cart LogoAfter the disappointment of BossCart and Actinic I was once again on the look out for a decent e-commerce package. My next stop was VaM Cart. VaM Cart is based on CakePHP and so I was very excited, finally I thought, something designed on a proper model-view-control framework. Plus being based on CakePHP, it should be easy to theme and customise as I need. I was disappointed.

You see it's true that the back-end is designed in proper CakePHP fashion but the actual shop is a confusing mess (from a developers point of view). The views for the shop are stored in a database, I know, WTF right. Same with the CSS. The idea is  that you can theme your site from inside the back -end, but it makes editing a nightmare because you edit every view through a tiny little textarea field. What's more is it's very confusingly designed with the final page being a baffling  combination of templates, pages, content blocks and micro templates. Oh and then you can add tag's to the mix and user defined tag's too. All of which is rendered through Smarty to really confuse you. I feel that VaM Cart has fallen into the trap of trying to do too much, resulting in something with which I can do nothing.

VaM Cart does do some things fantastically though. Multiple languages are handled in the easiest to understand way of any CMS's i've seen. On each edit page there are different tabs, one for each language you are using, and you simply enter whatever is appropriate for that language. It really is simple. The admin pages are very slick and, apart from the page styling bits, easy to use.

Alas getting to grips with with VaM Cart was no good. I had just about mastered the styling and then there was, again, no way of having the same product in more than one category. Doh.

A little some thing up my sleeve

I've said it on twitter and I'll say it again, all these e-commerce slash shopping cart packages seem to make the hard things easy but the easy impossible. So that's where things stand, all CMS/e-commerce systems are rubbish, none of them will do and I'm just going to give up and despair never to write a line of code again. Almost, but not quite. I have a plan, and a vision. A vision of a better world for developers. A world where the simple things are simple and the hard things, just slightly more difficult. Where a developer doesn't need a degree in cryptology and reverse engineering just to make simple changes and a world where all things don't try to fit in all holes and end up fitting in none. My plan is, if you have a isometric multi-hexagonal peg, that's fine, we'll make a hole for that. Just don't try to use the round one.

Share/Bookmark

There are 8 comments. Read them all and add your own.


Mar 30th 2010, 07:28

Creating an ASCII art generator using PHP

ASCII ART

ASCII art refers to pictures created using the 95 printable characters in the ASCII standard. These are the everyday characters that make up most of the text we see (at least in the Western world).

The 95 printable ASCII characters
Source:http://en.wikipedia.org/wiki/ASCII

This ancient art form has been around for many years, but since it largely missed my generation I thought I would have a play and have just created my ASCII art generator. Despite it being pretty simple, I'm rather proud of it and thought I would share how it's done. Create some ASCII art or browse the ASCII art gallery to see what it's all about and then carry on reading to see how its done.

First, we need an image which we are going to ASCII-ify. To do this we create a simple upload form with which the user can either chose from a selection of images, or upload their own. Mine is perhaps more complicated than necessary but here is

<form id="AsciiArtAddForm" enctype="multipart/form-data" method="post" action="/ascii_art/demo">
    <ul class="ASCII_egs">
        <li>
            <label for="old_pc">
                <img src="/img/thumbs/6e3d1bf91110fbef7c1015a64b879f80.jpg" alt="old_pc" class="ASCII_egs">
            </label>
            <p class="image_creadit">
                    <a href="http://www.flickr.com/photos/befuddledsenses/493303882/sizes/o/">Source: befuddledsenses</a>
            </p>
            <p><input name="data[AsciiArt][demo_image]" id="old_pc" value="/img/labs/old_pc.jpg" type="radio"></p>
        </li>
        <li>
            <label for="cat">
                <img src="/img/thumbs/5538743cfbd2f7660f95c409d169e845.jpg" alt="Cat in tree" class="ASCII_egs">
            </label>
            <p class="image_creadit">
                    <a href="http://www.flickr.com/photos/higgystfc/1074590008/sizes/l/">Source: higgystfc</a>
            </p>
            <p><input id="cat" name="data[AsciiArt][demo_image]" value="/img/labs/cat_on_tree.jpg" type="radio"></p>
        </li>
        <li>
            <label for="whiteHouse">
                <img src="/img/thumbs/06dd77afb83340b37e2838f11c82cac0.jpg" alt="White house" class="ASCII_egs">
            </label>
            <p class="image_creadit">
                    <a href="http://www.flickr.com/photos/bigberto/2770838680/sizes/l/">Source: bigberto</a>
            </p>
            <p><input id="whiteHouse" name="data[AsciiArt][demo_image]" value="/img/labs/whiteHouse.jpg" type="radio"></p>
        </li>
    </ul>
    <div class="input file">
        <label for="AsciiArtUpload">Upload</label>
        <input name="data[AsciiArt][upload]" value="" id="AsciiArtUpload" type="file">
    </div>
    <div class="input text">
        <label for="AsciiArtNumberOfRows">Number Of Rows</label>
        <input name="data[AsciiArt][number_of_rows]" value="120" id="AsciiArtNumberOfRows" type="text">
    </div>
    <div class="submit">
        <input value="Make it ASCII" type="submit">
    </div>
</form>

I hope that's fairly self explanatory. You can chose from a list of images or you can upload your own, just make sure that the encryption type is 'multipart/form-data'.

Once the user hits 'Submit' so begins the server side PHP, the first thing to happen is working out what image we are going to ASCII-ify

if(!empty($this->data["AsciiArt"]['upload']["tmp_name"])){
	$pic = $this->data["AsciiArt"]['upload']["tmp_name"];
        $ext = substr(strrchr($this->data["AsciiArt"]['upload']["name"], '.'), 1);
        $uploaded_file = $pic;
} elseif(!empty($this->data['AsciiArt']['demo_image'])) {
	$pic = WWW_ROOT.$this->data['AsciiArt']['demo_image'];
	$ext = substr(strrchr($pic, '.'), 
} else if(isset($this->data["AsciiArt"]['image_address'])){
	$pic = urldecode($this->data["AsciiArt"]['image_address']);
        $pic_info = pathinfo($pic);
        $ext = strtolower($pic_info['extension']);
}

There are 3 options here, if a file has been uploaded then $this->data["AsciiArt"]['upload']["tmp_name"] is set as the files temporary location. Alternatively, if a demo picture has been chosen $this->data["AsciiArt"]['demo_image'] is the path to the demo image. Lastly, if a url to an image has been given this is stored in $this->data["AsciiArt"]['image_address']. Regardless of which of these is the case $pic is set as the full path to the image and $ext is set as the file extension or mime type of the image.

Since we don't want to kill our server by processing ridiculous sized images we do a quick check to make sure the image file isn't too large

list($w,$h) = getimagesize($pic);
if($w * $h > 4000000){
   $this->Session->setFlash("Image to large at $w by $h");
} else {
   // code for making the ASCII image goes here
} 

I decided on a pixel limit for the image since this more accurately represents the amount of work we are going to have to do to create our ASCII art. In this code snippet we have also calculated the width and height of our image ($w and $h) which are two important variables.

If the image isn't too large we proceed to set another important variable, the width of the ASCII art we are creating

if(!empty($this->data['AsciiArt']['number_of_rows'])){
    if(is_numeric($this->data['AsciiArt']['number_of_rows'])){
        $num_rows = round ($this->data['AsciiArt']['number_of_rows']);
    } else {
        $num_rows = 120;
    }
}else {
	$num_rows = 120;
}

This is simply the width in characters per line (number of columns) of the final art work. This can be user defined or a default.

Now we create the image object from our input file

switch (strtolower($ext)){
        case "jpg" :
                $image = imagecreatefromjpeg($pic);
                break;

        case "gif" :
                $image = imagecreatefromgif($pic);
                break;

        case "png":
                $image = imagecreatefrompng($pic);
                break;

        default:
                die('bad image type');
                break;
}

We are using the brilliant GD image library and this switch statement uses the file extension to call the correct function to open the image.

If our input image width (in pixels) is not equal to our desired output ASCII image width (in characters per line) we resize our image

if($w != $num_rows){
    // re-size image so that width in px is equal to the number of cols
    $new_w = $num_rows;
    $new_h = round ($h*($num_rows/$w));
    $image_resized = imagecreatetruecolor($new_w, $new_h);
    imagecopyresampled($image_resized, $image, 0, 0, 0, 0, $new_w, $new_h, $w, $h);
    imagedestroy($image);
    unset($image);
    $image =& $image_resized ;
    $w = $new_w;
    $h = $new_h;
}

With this done we are in the simple situation that each pixel is mapped on to one ASCII character, much simpler than trying to work with any other mapping.

Now we are ready to create our ASCII masterpiece. As we have a lovely 1:1 pixel:ASCII character mapping, we can simply loop through each pixel in the image and find the best ASCII character to represent it.

$ascii_image = '';
for($y=0;$y<$h;$y++){
    $row='';
    for($x=0;$x<$w;$x++){
        ...

We start by creating a new variable ($ascii_art), which will end up becoming our masterpiece, and setting it as an empty string. Then we have two loops, the outer loop is looping over the height of the image and the inner loop the width. Looping like this we create a row, character by character, and our image, row by row. In between these two loops we are setting $row back to an empty string so we are ready to create our next row. The variables $x and $y are keeping track of what pixel in our image we are at (relative to the top left hand corner).

Next we calculate how dark the current pixel is

$rgb = imagecolorat($image, $x,$y);
$color = imagecolorsforindex($image, $rgb);
// make it greay scale
$gray = (0.299*$color['red']+0.587*$color['green']+0.114*$color['blue']);
// what fractioin 'black' is it?
$black_fraction = $gray/255;

To do this we first retrieve the RGB colours of the current pixel using imagecolorat and imagecolorsforindex and then calculate the grey scale value. More info on this at Wikipedia. By dividing the grey scale value by 255 we get what I'm calling the 'black fraction' i.e. how dark this pixel is 1=black 0=white.

Next we work out which ASCII character we want to use to represent this pixel, based on how black the pixel is. In order to do this I have an array of ASCII characters arranged in order of darkness like so

var $ascii_characters  = array("#", "@", "%", "=", "+", "*", ":", "-", ".", "&nbsp;" );

This is set as a property of the controller and so is accessed as $this->ascii_characters. Our darkest ASCII is '#' and our lightest '&nbsp;' which in HTML is a space i.e ' '.  With this tool in our arsenal we calculate the correct character to use like this

// get the right ascii
$num_asciis = count($this->ascii_characters);
$bin_size = 1/$num_asciis;
for($bin = 0;$bin < $num_asciis; $bin++ ){
    if($black_fraction < ($bin+1)*$bin_size) break; // this is our bin, i.e. which ascii
}
if($bin >= $num_asciis) $bin = $num_asciis -1 ; // this some this happens due to rounding and stuff!
$ascii = $this->ascii_characters [$bin];

This is simply breaking down our blackness range, 0-1, into a number of bins (ranges) equal to the number of different ASCII characters we have in $this->ascii_character. It then finds out which bin our current pixel belongs in given it's 'black_fraction'. We use the bin number as the index of the array $this->ascii_character to get our relevant ASCII character.

The rest is simple

        $row .= $ascii;
    }
    $ascii_image .= "$row<br />\n";
}

We append the character to the end of our current row. Once a row is completed we append this to the ASCII image along with a HTML line break. 

So there we have it we have created our ASCII art work, but as we are tidy souls there is a little garbage collection to do.

if(isset($image)){
    imagedestroy( $image );
}
if(isset($uploaded_file)){
    unlink($uploaded_file);
}

We destroy our image object (a memory hog) and delete the uploaded file if there is one (you ain't wasting space in my temp folder).

Finally some notes on displaying your art work. It's best 'framed' in a <pre> element as it prevents word wrap and it also must be displayed using a fixed width font e.g. monospace. Here's the CSS stying I use

.ascii_image{
    font-family:monospace,Courier;
    font-size:10px;
    line-height: 50%;
    overflow-x:  auto;
    overflow-y:  hidden;
}

So go ahead and make some ASCII art now that you appreciate how it's done, and don't forget, once upon a time that's all there was.

                               -.                 

                             .***:                

                             ::*:*-               

                             *:::**               

                            -*:::*+               


                            :::::*+.              

                            +**::*+.              

                           .=+*:**+.              

                           *=+*:*++.              

                           ==+****+               

                          -=++****+               

                          ==++*::*+               

                         :==++*::**               

                        .==++*::***               


                        ===+**:**+:               

                       +=+++****+=.               

                      *=++++**++==                

                     *=++***:*+=%-                

                    *++****::*+=*                 

                   *++****:::*+=.                 

                 .+++***::::*+=:                  

                -=++***:::**+++                   

               *=+++**:::***++-                   


              +=+++**:::**+++*                    

             +=+++**::****+++                     

            :=++++*::*****++-                     

            =+++***:*****++*                      

           :=++***:::****++-                      

           =++***::::******=++++==+:-..           

          :=++***:::*+*++++====%==%%=%%=-         

         .=+++****:**+++++++++==+==++===%=-       

         +=++**::::::*****:+*++=+++*++++==%+.     


        -=+++**:::::::***::*++*+++**+++++++=%-    

       .=+++***::::::::***::+++=***:++++++==%%.   

      .==++***::-::-:::***::+=====+*=========%%   

     .==+*****::::::::::****++====%%%%======%%@*  

    .==++***:::::::--::::***++%==========++===%%. 

   -==+++**:::-:::---:::****==%===+++++++++===%%* 

  +%==++***:::---:::::::***+%%%%==++++++====%=%%+ 

*%%%=++****:::::::::::::**+=%%%%=++++++=======%%+ 

%%%==+++**:::--:::::::::*++==%@%%=+++========%%%: 


%===++++***:::::::::::::*++==%@@%===========%%%=  

====+++****:::::*:**:::*++==%%%@@%======%%%%%%+   

=====++*****::::****::**++=%%%%=%%%%%%%%%%%%%@+   

=====+++****::::********+==%%==+++++++======%%@.  

=+===+++****:**********+===%%=+++**+*++++++==%%:  

=++===++**********+****+===%%+++++*+++++++++==%+  

=+++==+++*********+*++++==+%%++++++++++++++=+=%%  

=++++==++++******+**+++===+=%=++++++++++======%%  

==++++++++++*****++*+++==+*=%==++++++++++====%%=  


===++++=+++++++**+*+++++++*+%%=+++++++++++===%%*  

====++++==+++++**+*=+++++**+%%%===+++=+======%=   

+=====++++++*++**++=++*****=%=+++===%%%%%%%%@@.   

=======++++++++*++=++*+:*:*=%=*****+++++====%%+   

===++==++++**+++++=+*:*:*:*=%=***********+*++=%.  

====+++++******+==+*:*:*:*+=%=+******+++*+++++%:  

====+++++*****++***::*:::**+%=++++++++++++++++=+  

====++++***::******::::::**+%%=++++++++++++===%+  

=====++***::::::**:::::-:::+=%%=+++++++++++===%*  


======+**::::::::::-:---:::+=%%====++++++=====%.  

======+**:::::::*::-:--::::+==+*++==%===+===%%:   

%=====+**:::::::*::----::::+==+***++==%%%%%=*.    

%%%==++**::::::**:::--:::::*%=+****++++==%%       

@@@%%%+***::::**::::-:::::**=%=+**++++=+==%       

#*:+%%=++*****::*:::::***++*+%%+++++++===%%       

=    :+==+++**++*:::***+++*++=%==++=====%%+       

-         .--+=+****+*::-::***+=%%%%%%%%%%.       


              .-:**:-            .-*=@@@=.        

                                      -.          

                                                  

                                                  

Share/Bookmark

There are 0 comments. Be the first to comment!


Mar 20th 2010, 05:52

Creating a drag and drop twitter game

A screen shoot  of my Who's Tweets Are These gameI recently created my Whos Tweets Are These? game which for me was an adventure into Prototype, script.aculo.us and a small dabble in the Twitter API. Here I share with you how I created it so that you can do the same and tell me anything I did wrong.

The idea of the game is to present the player with a collection of tweets and a selection of celebrities, they simply drag the tweet onto the celebrity they think that twinkling twitter gem originates from.

So first steps first, to access the tweets I needed to use the Twitter API. Rather than re-invent the wheel I used Twitter for PHP, a small easy to use library for sending and receiving tweets. I downloaded Twitter For PHP and as I'm using CakePHP stuck the files in /app/vendors/twitter. If your not using CakePHP just stick it somewhere appropiate on your web server.

Since I plan on creating other 'fun and games' with Twitter I created myself a basic Twitter controller (another CakePHP thing) that looked like this

<?php
class TwitterController extends AppController {

    var $name = 'Twitter';
    var $helpers = array('Html', 'Form','Javascript');
    var $uses = null;

    function beforeFilter(){
        parent::beforeFilter();
        $this->Auth->allow('*');
        App::import('Vendor', 'Twitter', array('file' => 'twitter'.DS.'twitter.class.php'));
        $this->Twitter = new Twitter('****','*********');
    }

    function whos_tweets_are_these(){
        

    }

}
?>

The 4 lines in the beforeFilter function are executed before the rest of the game logic. The first line imports the parent class's (AppController) beforeFilter, the second line lets anyone access the different methods of this controller (each public method is a different page), only necessery if using Auth. The third and forth lines are the most important the third line includes the file twitter.class.php from Twitter For PHP, and the fourth line creates a new Twitter object initialised with our username and password.

Next, I added a new property to the controller - a list of celebrity Twitter accounts. This is how we choose who's tweets we are going to get and looks like this

array('name'=>'Derren Brown','id'=>'DerrenBrown'),
        array('name'=>'Stephen Fry','id'=>'stephenfry'),
        array('name'=>'Jonathan Ross','id'=>'WOSSY'),
        array('name'=>'David Mitchell','id'=>'RealDMitchell'),
        array('name'=>'Barack Obama','id'=>'BarackObama'),
        array('name'=>'The Gok Wan','id'=>'TherealGokwan')

These are arguably not the most worthy of celeb's so feel free to create your own list.

Twitter For PHP doesn't have a method to retrieve the tweets of any singular named user so I added a method to the Twitter class to achieve this. It's very simple, just a couple of lines

public function load_user($id,$type='xml',$count=20,$page=1){
        static $formats = array(self::XML => 'xml', self::JSON => 'json', self::RSS => 'rss', self::ATOM => 'atom');
	return $this->cachedHttpRequest("http://twitter.com/statuses/" . 'user_timeline' . '/' . $id . '.' . $type . "?count=$count&page=$page");
}

It uses all the complicated Curl bits and bobs already in Twitter For PHP but specifies the url so that it uses the user_timeline API method for a specific user. Using this method is dead simple, after creating a new Twitter object (initialised with our username and password) we simply pass in the username of the user we're after, the format we want our response to be in and optionally, the number of tweets required and page number.

$tweets = $this->Twitter->load_user('SomeUser,'json',20,1)) ;

With all this in place it's simple to retrieve the tweets we need. The code we use looks like this

$num_tweeters = 5; // number of different celeb's to get tweets from
$num_tweets = 10 ; // total number of tweets use in the game must be <=  20* $num_tweets


// randomise the celeb's and choose the number required
shuffle($this->celebs) ; 
$tweeters = array_slice($this->celebs,0,$num_tweeters);

// loop over each celeb and get some of there tweets
$tweets=array();
for($i=0;$i<$num_tweeters;$i++){
    for($trys =0;$trys<4;$trys++){
        try{
            $t = $this->Twitter->load_user($tweeters[$i]['id'],'json',20,rand(1,5)) ;
        }
        catch(TwitterException $e){
            usleep(1000000); // pause for 1 second;
            continue;
        }
        $tweets = array_merge($tweets,$t);
        $tweeters[$i]['img'] = $t[0]['user']['profile_image_url'];
        $tweeters[$i]['screen_name'] = $t[0]['user']['screen_name'];
        $tweeters[$i]['name'] = $t[0]['user']['name'];
        break;
    }

}
if(count($tweets) >= $num_tweets){
    unset($t);
    // randomly select the desired number of tweets for us in the game
    shuffle($tweets);
    $tweets = array_slice($tweets, 0,$num_tweets);
    $this->set(array('tweets'=>$tweets,'tweeters'=>$tweeters));
 } else {
    $this->Session->setFlash('Having problems talking to twitter. Please try again in a 10 seconds or so');
    $this->set(array('tweets'=>array(),'tweeters'=>array()));
}

Hopefully it's pretty self explanatory but it's worth noting that the line that retrieves the tweets is in a 'try block'. I'm not sure if the Twitter server, the Twitter For PHP code or my server is to blame but often I got a 'TwitterException' raised, just waiting a second and trying again usually fixes this so that's what I've done. Where we use the Twitter object is in a 'try block' which is in turn in a 'for loop'. If Twitter For PHP doesn't raise an exception we use 'break' to get out of the loop. If an exception is raised, we try again until the tweets are retrieved successfully or the specified number of attempts is reached. Finally, if enough tweets were retrieved we set the variables for use in our view/page, else we ask the user to try again.

Next is the view/page which this looks like this

<?php
$javascript->link('prototype',false);
$javascript->link('script.aculo.us/script.aculo.us',false);
$javascript->link('whos_tweets_are_these',false);
?>

<h1>Who's tweets are these?</h1>

<div class="tweeters" >
    <!-- tweeter images -->
    <?php foreach($tweeters as $tweeter):?>
    <div class="tweeter" id="<?php echo $tweeter['screen_name'];?>" name="<?php echo $tweeter['name'];?>" >
        <div class="tweeter_info">
            <?php echo $html->image($tweeter['img'],array('alt'=>$tweeter['name'])); ?>
            <h3><?php echo $tweeter['name']; ?></h3>
            <div class="clearboth"></div>
        </div>
    </div>
    <?php endforeach; ?>
</div>


<div id="numtweets" class="hidden"><?php echo count($tweets); ?></div>
<div id="tweeters" class="hidden"><?php foreach($tweeters as $key => $t){if($key != 0) echo ','; echo $t['screen_name'];} ?></div>

<div class="tweets">
    <h3>Drag these tweets on to the people you think tweeted them.</h3>
    <!-- tweets -->
    <?php foreach($tweets as $key => $tweet): ?>
    <div id="tweet_<?php echo $key ; ?>" class="rounded_box guess_tweet movable" by="<?php echo $tweet['user']['screen_name']; ?>" name="<?php echo $tweet['user']['screen_name']; ?>" >
        <div class="white-corner tl" ></div>
        <div class="white-corner tr" ></div>
        <div class="white-corner br" ></div>
        <?php echo $tweet['text'] ;?>
    </div>
    <?php endforeach; ?>
</div>
<div class="clearboth" ></div>
<div>
    <p>Your current score is <span id="num_right" >0</span> out of <span id="num_guesses" >0</span></p>
</div>

The second and third line simply create links to the Prototype and script.aculo.us JavaScript libraries in our page header. The third line will create a link to the JavaScript that controls the game. Following this we loop through all our celeb's (tweeters) creating a div box for each one, setting the id to be their username. These will be the drop boxes for the tweets so we need to give them unique and useful id's. The div boxes are then embellished with a picture of the celeb (the url of which is returned with the tweets when we gather them) and the celeb's name.

Next are two hidden div's, one containing the number of tweets used in this game and the other a list of the usernames of each of the celebrity tweeters. The div's are given sensible id's since we need to retrieve the information they contain in the JavaScript code.

We then put each of the tweets in a div box and give it a unique id. The id of each tweet is 'tweet_i' where i is the tweet number, i.e. 'tweet_0','tweet_1' .... up to 'tweet_n' where n is the total numer of tweets -1. This is so the JavaScript code can workout the id's of these tweets, so we can make them draggable. As well as giving each tweet an id we also give it a new attribute 'by' and set this as the username of the person who tweeted it. This is so that when it's 'dropped' we know if it was dropped on the right person or not.

Finally, there is a little paragraph at the bottom to keep you updated on your current score.

To make this an interactive game and not a static page we use JavaScript.

var guesses = 0;
var guesses_right = 0;
var guesses_wrong = 0;
var num_tweets = 0 ;
var draggables = [];

function tweet_dropped(tweet, dropped_on, event){
    var tweet_by = tweet.getAttribute('by');
    var dropped_on_id = dropped_on.getAttribute('id');

    var tweet_class = tweet.getAttribute('class').replace("movable","");

    if(tweet_by == dropped_on_id){var is_correct = true;}
    if(is_correct){
        tweet.setAttribute('class', tweet_class + ' tweet_right')
    }else {
        tweet.setAttribute('class', tweet_class +' tweet_wrong')
    }

    document.getElementById(tweet_by).appendChild(tweet);
    draggables[tweet.id].destroy(); // get rid of draggable
    
    Effect.Pulsate(tweet.id);
    if(is_correct){
        update_score(1);
    } else {
        update_score(-1);
    }
}

function update_score(num){
    if(num >= 0){
        guesses += num;
        guesses_right += num;
    } else {
        guesses -= num;
        guesses_wrong -= num;
    }
    document.getElementById('num_right').innerHTML = guesses_right ;
    document.getElementById('num_guesses').innerHTML = guesses ;
}

Event.observe(window, 'load', function() { 
    num_tweets = document.getElementById('numtweets').innerHTML;
    var tweeters = document.getElementById('tweeters').innerHTML;
    tweeters = tweeters.split(',');
    for ( var i=0, len=tweeters.length; i<len; ++i ){
        Droppables.add(tweeters[i], {
            accept: 'guess_tweet',
            hoverclass: 'hover',
            onDrop: function(dragged, dropped, event) {tweet_dropped(dragged, dropped, event);}
        });
    }
    for( i=0;i<num_tweets;i++){
        div_id = 'tweet_' + i ;
        draggables['tweet_' + i] = new Draggable(div_id, {revert: true,scroll: window}) ;
    }
});

This was my first foray in to the world of Prototype, script.aculo.us and 'drag and drop' so the code might not follow all the best practices or most sensible methods, but it works and this is how.

The first lines are just a few global variables which keep track of how things are going (i.e. how many guesses right/wrong), where the magic starts is at this function

Event.observe(window, 'load', function() { 
    num_tweets = document.getElementById('numtweets').innerHTML;
    var tweeters = document.getElementById('tweeters').innerHTML;
    tweeters = tweeters.split(',');
    for ( var i=0, len=tweeters.length; i<len; ++i ){
        Droppables.add(tweeters[i], {
            accept: 'guess_tweet',
            hoverclass: 'hover',
            onDrop: function(dragged, dropped, event) {tweet_dropped(dragged, dropped, event);}
        });
    }
    for( i=0;i<num_tweets;i++){
        div_id = 'tweet_' + i ;
        draggables['tweet_' + i] = new Draggable(div_id, {revert: true,scroll: window}) ;
    }
});

This uses Prototype's Event.observe to waits until the page/window has loaded before calling the function which makes the elements draggable and droppable. It's important to wait for the page to load first as you can't make an element draggable or droppable until it exists in the DOM. Once the page has loaded, a function (the third argument) is called, this retrieves the number of tweets and the usernames of the celeb tweeters from the hidden div elements in our page. The next section of code is a 'for loop' (from 0 to the number of tweeters -1) in which we use the array of tweeter usernames (which are also the id's of the div boxes to make droppable) to make each of the celeb/tweeter boxes a Droppable, and specify a function to call (tweet_drooped) if something is dropped on it. Following this is another 'for loop' (from 0 to the total number of tweets -1). In this loop we make the tweets draggable by creating a new Draggable with the id of our tweet as the first parameter. This is easy as we have given our tweets predictable id's (i.e. tweet_0, tweet_1, ...). We store these Draggables in an array so we can delete them later after the tweet has been dropped on a tweeter.

We now have draggable tweets and droppable tweeters. Great! Next we specify what happens if a tweet is dropped on a tweeter. This is done in the function cunningly named "tweet_dropped"

function tweet_dropped(tweet, dropped_on, event){
    var tweet_by = tweet.getAttribute('by');
    var dropped_on_id = dropped_on.getAttribute('id');

    var tweet_class = tweet.getAttribute('class').replace("movable","");

    if(tweet_by == dropped_on_id){var is_correct = true;}
    if(is_correct){
        tweet.setAttribute('class', tweet_class + ' tweet_right')
    }else {
        tweet.setAttribute('class', tweet_class +' tweet_wrong')
    }

    document.getElementById(tweet_by).appendChild(tweet);
    draggables[tweet.id].destroy(); // get rid of draggable
    
    Effect.Pulsate(tweet.id);
    if(is_correct){
        update_score(1);
    } else {
        update_score(-1);
    }
}

This function is called whenever a tweet is dropped on a tweeter. It is called with three arguments most importantly 'tweet', which is the DOM element of the tweet that was dragged, and "dropped_on" which is the DOM element it was dropped on. The second line retrieves the username of the tweeter which is stored in the 'by' attribute of 'tweet'. The id of the box it was dropped on is then retrieved, this is also the username of the tweeter that the tweet was dropped on. If these match then the tweet was dropped on the right person and the class 'tweet_right' is added to the tweet element. If the guess was wrong then the class 'tweet_wrong' is added instead. These classes are used to style the tweet depending on whether the guess was right or wrong, i.e. green or red. After the style is set, the tweet is then appended to the the div box of the tweeter the tweet belongs to using appendChild. The Draggable is then destroyed so that this tweet can no longer be dragged around, and for visual effect the tweet is pulsated. Finally that last 'if block' calls a function which updates the score.

That's all there is to it. It's not perfect and some creases need ironing out, for example the tweets go slightly transparent after being dropped and there is no 'Well done you scored x out of y' when you finish, but it's a not bad first go and a great way for getting a feel of Draggables and Droppables as well as Prototype and script.aculo.us in general.

So go on drag a tweet, drop it on a tweeter, and see how well you can do in Who's Tweets are These.

Share/Bookmark

There are 0 comments. Be the first to comment!


Feb 26th 2010, 17:27

Our national web archive

British Library web archive

 

The British Library have unveiled the UK web archive, a digital archive of .uk websites including many websites that have now vanished or are soon to do so. I applaud this effort and think it is very worth while, think of the history, culture, mistakes and art never to return from the big delete key in the sky.

 

Imagin if Van-Gogh's work had disappeared after he had died, or after he had lost interest in a particular piece. His art disappears as it doesn't get the traffic required and he no longer wishes to pay a gallery to display it. It's gone forever long before it's discovered as the masterpiece that it is.

 

While the web is full of drivel and rubbish it's the rubbish of our culture and times and just as archaeologists will happily rummage through an ancient refuse heap so the historians of the future will learn all about us by crawling through our Facebook frolics and Twitter twaddle.

 

The problem is that it's slow work, since due to the current copyright laws the Library feel obliged  (as the BBC put it) to ask the site owners permission before archiving it. Again I must congratulate the Library for their honesty and tact in this matter, even if it is only to stay on the right side of the law.

 

Who should own what I put on the internet? I think I should, just as I should get the flack if its wrong inappropriate or illegal and so I have a right if I wish to hide delete and destroy it as I see fit. Just as Van-Gogh could have burnt his paintings so should I have the option of obliterating my mutterings and musings of this blog should I wish.

 

However, I think the Library work should be made easier but whilst still maintaining the rights to my work. Maybe the law should be changed such that the default position is that material available online is free to archive unless stated otherwise in some sort of meta tag or other method. Obviously if the law was changed in such a way then people should be made aware of this and it should be quick and easy for people to opt-out regardless of how tech savy they are.

 

Also I think that all this data should be both anonymous and traceable such that I can admit my fetish for feather umbrellas or moan about my boss without fear of embarrassment or reprimand, but on the other hand if I post pictures of the feather umbrellas I stole from my boss the police should be able to catch me, whether my boss deserved it or not. Also I imagine that I (and only I if I wish) should be able to find all my entries in this giant archive and delete them should I wish.

 

I realise this is a big ask and that it's a vision for the future but there are my ideas and they're all mine and I reserve the right to obliterate them as and when I see fit, but until then archive away!

Share/Bookmark

There are 0 comments. Be the first to comment!

1 | 2