Showing posts with label development. Show all posts
Showing posts with label development. Show all posts

Wednesday, March 23, 2011

Code reading and Bug fixing 101

Summer is here, and Google Summer of Code is on its way. The biggest hurdle new contributors often face (after compiling trunk ;)) is to get their head around the project they would like to work on, understanding how it works, where the parts fit in, and how to fix bugs or make improvements. Speaking from my experience, it took me the better part of a month to understand how KWin worked before I could actually hack on it for Season on KDE.

This is my attempt to explain how I approach new code and the tools I use. To demonstrate, I am going to try and fix this bug in Amarok. I am sorry for stealing a Junior Job.

The most important thing when working on existing code is:

DO NOT modify existing code that works, even if you absolutely have too1


This means, avoid changing function signatures, variable names, and especially surrounding code that does not affect you. You may inadvertently introduce bugs.
That said, remember that you are using a version control system, so code fearlessly. The best way to understand new code conceptually is to liberally insert some kind of debug/print statements all over relevant functions. With that in mind, let’s start.

Understand the problem

The bug report says:

When you filter all items in Amarok, a warning notice is shown that “tracks have been hidden in the playlist”. However, if you filter, and then delete all results of that filter from the playlist, this warning is not shown

Reproducible: Always

Steps to Reproduce:

Set a filter
Remove all matcheS
This is a fairly straight-forward bug with well written steps to reproduce. If not, its best to ask questions on the bug tracker or talk to someone more experienced to understand what the bug/feature actually requires. The next step is to reproduce the bug. If you cannot make the bug occur, you have no way to prove that your changes fix it. Again, try to reproduce bug in the bleeding edge version of the project. Otherwise it has already been fixed. So let’s add some tracks to the playlist, put something in the filter text. Observe that if you put a pattern that doesn’t match any track, you get the warning. Now change the pattern to match a few tracks. Remove those tracks from the playlist. No warning! This is what we have to fix.
Up to this point, the process has been just what a user would do, now its time to enter the code.

Where is the problem?

The Amarok source tree is pretty large. By convention, all source code is in the src/ directory. This is true for almost all open source projects. But what now?
The easiest way to find out the source of the problem is to find a nice clue in the interface that gives us a good idea of what the relevant file will be called or where we can make a change.
At this point, let me introduce a tool called ack, which is grep optimized for programmers. I won’t bother describing the features, but trust me, you should be using it!
Let’s enter the Amarok source tree, and try to find “Warning: tracks have been hidden in the playlist”. Why so, well, since it is being hidden and shown, it obviously has something to do with our task.

$ cs amarok
$ cd src # all code is here
$ ls
...
playlist
...
$ ack "Warning: tracks have been hidden" playlist/
playlist/ProgressiveSearchWidget.cpp
45: m_warningLabel = new QLabel( i18n("Warning: tracks have been hidden in the playlist"), this );

Notice a few things. First, I ran the search only on the playlist directory. You could have run it on src and it would just have taken more time. But its good to do a ls on src because knowing the directory structure is a good way to get the highest-level overview of a project. playlist is a suspiciously strong pointer to our bug. So let’s run it on this.

This seems like a good start, there is a label being created that has the message. But we need more information. So fire up your editor/IDE and go to line 45 of src/playlist/ProgressiveSearchWidget.cpp. Knowing nifty utilities in your editor is a good investment, you absolutely must know the shortcut to jump to a specific line since line numbers are used all over programming, in compilers, editors, and humans talking to each other. With vim it’s:

$ vim playlist/ProgressiveSearchWidget.cpp +45

Now, let’s see where this label is being manipulated. For this we need another useful editor feature, to find instances of symbol under cursor. QtCreator or KDevelop allow you to just right click on the variable name and find all uses. With vim I hit the * key.

void ProgressiveSearchWidget::showHiddenTracksWarning()
{
m_warningLabel->show();
}

void ProgressiveSearchWidget::hideHiddenTracksWarning()
{
m_warningLabel->hide();
}
And there is our first break. A pair of functions which show or hide the label. Continue this technique of ‘follow the useful symbol’. Let’s see where showHiddenTracksWarning() is being called. In this case, its just above the definition.
void ProgressiveSearchWidget::noMatch()
{
...
if( m_showOnlyMatches )
showHiddenTracksWarning();
}

void ProgressiveSearchWidget::showHiddenTracksWarning()
But if you try to follow noMatch(), it won’t be in this file. Running ack again:

$ ack 'noMatch' playlist/
playlist/ProgressiveSearchWidget.h
130: void noMatch();
playlist/ProgressiveSearchWidget.cpp
216:void ProgressiveSearchWidget::noMatch()
playlist/PlaylistDock.cpp
144: connect( m_playlistView, SIGNAL( notFound() ), m_searchWidget, SLOT( noMatch() ) );

At this point, I assume you are smart enough to follow the code on your own, because pasting every sample here is annoying :) If you see playlist/PlaylistDock.cpp, then m_playlistView represents the playlist in some manner. m_searchWidget on the other hand is the place where the user types the filter. So when the playlist can’t find any matches, it tells the search widget, which then shows the label!

Except, something is going wrong in the exact circumstances of the bug. One possible explanation is that noMatch() never gets called. Your first idea might be – lets call notFound() when tracks are deleted and be done with it. But let’s dig deeper.

So what is m_playlistView? Simple, open the header file for PlaylistDock.

PrettyListView* m_playlistView;

Hmm, now where might PrettyListView be? At this point, you can use your fancy IDE, but I am going to introduce another ancient UNIX tool – find. When programming, it is helpful to generalize assumptions about how the code is organized and named, since it makes navigation a lot easier. If you’ve seen even the Amarok code that is just in this article, you can see that classes usually map one-to-one to file names.
$ find -iname 'prettylistview*'
./playlist/view/listview/PrettyListView.cpp
./playlist/view/listview/PrettyListView.h
find takes a lot of powerful options, but here we say

Find all files from the current directory (src) downwards, whose name (-name) matches the pattern ‘prettylistview*’, but ignore case (-iname)

and there you go, open PrettyListView.cpp and search for notFound. So notFound is emitted by the PrettyListView::find method, which itself is incidentally connected to ProgressiveSearchWidget::filterChanged (connected in Playlist::Dock::polish). Here is how our mental model goes till now:


Mental model

When the user types something, ProgressiveSearchWidget::filterChanged is being invoked, which is triggering PrettyListView::find. When find sees that no tracks are visible, it emits notFound. This triggers ProgressiveSearchWidget::noMatch which shows the warning label.
With some more effort, you will also find that PrettyListView::find is only called due to filterChanged. We can now use this knowledge to figure out why the bug happens.
Deleting a track does not change the filter in any manner. So following the call chain, noMatch never gets called when tracks are deleted to re-evaluate the situation!


The solution

The obvious solution is to somehow call PrettyListView::find manually when tracks are deleted in the playlist. But how will we know that? We will again use some GUI hints. Tracks are deleted by right-clicking them and selecting ‘Remove From Playlist’. If you ack this, you will find the action triggers a removeSelection() (playlist/view/PlaylistViewCommon.cpp). The type of the receiver is just QWidget, so it would be a hassle to try and find out the type of parent, but there is only one definition of a method called removeSelection() related to playlists. It is in PrettyListView. At this point, you suddenly feel empowered as the solution clicks before your eyes.

As soon as we remove the tracks, we can just call find() again and we will be done!

A fundamental constraint that inhibits us is the loose coupling made possible by signals and slots.
PrettyListView is unaware of ProgressiveSearchWidget and its properties.
All it knows is that it is expected to emit certain signals (found/notFound) and things happen. PrettyListView also does not have direct access to whether items are being filtered or not. These things are passed on to it from the filterChanged() signal’s arguments.
At this stage, I hope that you now have a good idea of how things are connected. For a little indepth understanding of how things are playing out see 2.

With this in mind, there are atleast 2 solutions that come to mind. Our task is to somehow force the filter action to be performed again on the view so that it emits the relevant signals. Unfortunately the view itself does not have access to the showOnlyMatches attribute present in the dock, and in the SortFilterProxies. We can,

1. Use PlaylistDock, which has access to the search widget

A roundabout method I did first, involving:
  1. adding a getter, ProgressiveSearchWidget::currentSearchFields().
  2. Connecting to the controller’s (The::playlistController()) changed() signal, a custom slot in Playlist::Dock called slotReapplyFilter().
  3. slotReapplyFilter() calls PrettyListView::find() again with relevant arguments.

2. Make the view store showOnlyMatches

A much simpler three line change.
  1. Add a bool m_showOnlyMatches to PrettyListView.
  2. When PrettyListView::showOnlyMatches() is called, along with passing along the value to the playlist, we also set m_showOnlyMatches.
  3. In PrettyListView::removeSelection(), call PrettyListView::find() since we now have complete knowledge.

Programmers exchange changes in code with something called a diff, or a file which only logs the changes made in the code.

$ git diff
diff --git a/src/playlist/view/listview/PrettyListView.cpp b/src/playlist/view/listview/PrettyListView.cpp
index cd650f6..e81d396 100644
--- a/src/playlist/view/listview/PrettyListView.cpp
+++ b/src/playlist/view/listview/PrettyListView.cpp
@@ -194,6 +194,8 @@ Playlist::PrettyListView::removeSelection()
QModelIndex newSelectionIndex = model()->index( firstRow, 0 );
setCurrentIndex( newSelectionIndex );
selectionModel()->select( newSelectionIndex, QItemSelectionModel::Select );
+
+ find( The::playlist()->currentSearchTerm(), The::playlist()->currentSearchFields(), m_showOnlyMatches );
}
}

@@ -912,6 +914,7 @@ void Playlist::PrettyListView::updateProxyTimeout()

void Playlist::PrettyListView::showOnlyMatches( bool onlyMatches )
{
+ m_showOnlyMatches = onlyMatches;
The::playlist()->showOnlyMatches( onlyMatches );
}

diff --git a/src/playlist/view/listview/PrettyListView.h b/src/playlist/view/listview/PrettyListView.h
index f22a7c8..612be0b 100644
--- a/src/playlist/view/listview/PrettyListView.h
+++ b/src/playlist/view/listview/PrettyListView.h
@@ -139,6 +139,8 @@ private:

QTimer *m_animationTimer;

+ bool m_showOnlyMatches;
+
public:
QList<int> selectedRows() const;
};
Since this is a minor patch, I could just commit this change. But I’m not going to for two reasons:
  1. You don’t have commit access, so I will show you how its done in that case.
  2. This is not code that I have worked with before, so however small, it should go through review. The reason is that I may have inadvertently affected the system due to incomplete knowledge. This is were unit and regression tests can also come in handy.
So let’s first get our diff into a file

$ git diff > /tmp/amarok-bugfix-260352.patch
Now hop on to reviewboard, and submit it. (Don’t actually do it, I’ve already done it!). Here is how the final submission looks.
Now wait for somebody to reply or commit on behalf of you. That is it! Your first bug fix.

Code reading only gets easier as you go along. It does not involve complex equations, but instead mentally executing the program just as a computer would. The catch is to be able to go from the low level details to creating the software architecture in your head, and watching the messages flow through the program. You must know your tools really well since they are a big time-saver.

To summarise, the following usually help to get a good idea of the code and allow you to fix bugs or add features.


  1. Use UI hints
  2. Use debug statements where required.
  3. Sometimes purposely crashing a program (assert(0); anyone?) is a great way to see what code-path is being followed.
  4. Follow the code along, until you can build a mental model.
  5. Use the mental model to figure out multiple solutions.
  6. Implement a solution.
  7. Test it.
  8. Submit for review or commit.

I hope this post has been useful. If you do wish to continue participating in an open source project, it is a good idea to spend the first few days just glossing over various parts of the code to get a feel of the system. Then you can go into your little section and get comfortable.



  1. except when really required


  2. If you don’t understand what I say in this paragraph, accept it at face value and continue. There is a powerful design pattern called Model-view-controller that allows separation of concern between the playlist contents, modifying them and drawing them. This is embedded into Qt using the various Item/View classes. Amarok uses this extensively. (probably one of the biggest uses of Model View within KDE?) The PrettyListView is just deciding how to show and draw the tracks, which are actually stored in a set of models. Similarly a Controller will often modify the models as required, and the changes will automatically show up in the PrettyListView.


Sunday, August 29, 2010

Knocked out of Node Knockout

(This is one of those you-don’t-really-have-to-read-it kind of posts)

I was very excited about Node Knockout. I had a great idea for an IRC bouncer + websocket based client called Ircsome using node, qooxdoo and a couple of other things.

I knew that I would only get 24 hours since Sunday had higher priority events, but I believed I could get the basic app working in 24 hours. Until things went downhill from last week. First my brand new shoes tore, and since Adidas has an idiotic replacement policy (even in their centralised, computer based inventory systems) where-in you’ve to go to the same store you bought it from, I lost about 3 hours of my time at home ( although I managed to pick up Erlang Programming. Then in the train home, I rebooted my cell phone and my SIM card just gave, it refused to work. There went my internet connection, no frequent push-pulls. Still I managed to get the client and server atleast talking to each other and logging onto IRC when the user filled in the details in the browser. I pushed out the changes over my home connection, and the push to Joyent succeeded, yet my server didn’t seem to be running, atleast my home internet configuration couldn’t manage to connect to it on any of the expected ports (even though push to the same server worked). By this time I was really annoyed, and had to sleep to, so I stopped. Knockout!

I plan to continue on Ircsome though, so I can deploy my own home brewed bouncer on my (coming soon) VPS and access IRC and logs from anywhere. Qooxdoo is a pretty great toolkit, but lacking certain kinds of documentation and being very heavy in size. Its almost like Qt, though I dislike the Java getter naming convention.

Posted via email from nikhil's posterous

Monday, April 26, 2010

KWin tiling is merged

I'm glad to announce that yesterday the kwin-tiling branch was merged into kwin trunk by commit 1118677!. It will be available in KDE SC 4.5. Please keep in mind that it is an experimental feature with rough edges. Bug fixes are already on the way, but some things, like session saving and so on are absent. Please do add feature requests and bugs to the KDE bug tracker.

This screencast should show off a few things. Apparently xvidcap produced really bad video, so I will have to do it again, watch out for updates here.

Thanks to Martin Graesslin for being my mentor during Season of KDE, and all the others who bugged me with emails about when tiling would be integrated into KWin :)

GSoC results now 13 hours away...

( Mamma, I'm not stressed out :-) )

Thursday, April 22, 2010

Quick and dirty Instant Messaging with Redis

(Aside: Yes this is a post after a zillion years, but I have a few more lined up, and waiting for some important stuff in about a week)

Redis is a wicked cool NoSQL database, in that storing stuff is not the only thing that it does. Mathias Meyer already has a collection of Redis use cases, but this great idea is mine. Like all good ideas it emerged in the shower :) ( I was not aware of Pieter Noordhuis' MUC when I did this, in either case mine deals with one-to-one IM )

Using the new publish-subscribe commands and a bit of node.js code, here is a tiny instant messaging server. An explanation follows, refer to the code while reading it, Blogger sucks for publishing code.

Here is how it works. For every client, the server maintains two connections to redis. This is because a subscriber is not allowed to invoke other commands. So we have a subscriber connection and publisher connection. Consider two clients are now connected, Alice and Bob.

When Alice connects ( NICK Alice ), we insert her nickname into 'mclarens:inside', a Redis set. This allows us to have a WHO command to list online members. Bob to does the same. To start chatting with Alice, Bob initiates a conversation ( TALKTO Alice ). The node server does none of its own client management. Instead each client just maintains subscriptions to two channels/classes. Each client is always subscribe to '[nick]:info', where it is notified of talk initiations and exits etc. When Bob wants to talk to Alice, he sends a 'start Bob' to Alice's info channel, 'Alice:info'. Then both Alice and Bob subscribe to the class 'Alice:Bob', using alphabetical order to decide the name. When either of them wants to talk to the other, they do MSG [nick] [message]. The connection uses the publish redis connection to send a message to the channel, resulting in both sides being notified of the incoming message.

To terminate the chat, one side just has to send a 'STOP [nick]'. That unsubscribes the user from the class so that he no longer receives messages. It also sends a 'stop [nick]' to the other side, so that he/she can also unsubscribe.

On QUIT, we simply remove ourselves from the mclarens:inside set, unsubscription is handled automatically by Redis!

That's it, simple Instant Messaging! Now this lacks any kind of security and ignore lists and status but proves the point. In fact I'm thinking of using this as the backend for the IM part of the XMPP server I'm hacking on. At this point it is in no shape to have this feature just yet, but it should some day.

(Thanks to roidrage and tnm on #redis for pointing out that I had to use two Redis connections for PubSub.)

Thursday, November 26, 2009

Automatic builds with some inotify magic

The inotify feature in the linux kernel allows you to receive events about changed files. Using pyinotify, here is a simple script to watch for changes to source code and run "make". Everything is hard coded right now, and since I was using CMake with out-of-source builds, the directory structure is that way. It also uses the command line notify-send tool to display notifications. Perhaps some day I will make it configurable and more useful.

Cheers.



# Notification tool
# Run with the directory to watch
# currently hardcoded to use
#
# +
# |- src - contains source code and is monitored
# |- build - assumes build to be here
#
# (c) 2009, Nikhil Marathe
# Licensed under the MIT License

import sys
import os
import subprocess
import re
from pyinotify import WatchManager, Notifier, ProcessEvent, IN_MODIFY

wm = WatchManager()

class Compile( ProcessEvent ):
def process_IN_MODIFY( self, event ):
print event.path, event.name, event.mask & IN_MODIFY

if re.match( '[a-zA-Z]*.cpp', event.name ):
os.chdir( event.path.replace( 'src', 'build' ) )
ex = subprocess.call( 'make' )
subprocess.call( ['notify-send', "Build output", "%s" % ( ex == 0 and "Success" or "Error" )] )

c = Compile()
notifier = Notifier( wm, c )
wm.add_watch( sys.argv[1], IN_MODIFY, rec=True )

notifier.loop()


Friday, June 05, 2009

KWin preliminary tiling

Yesterday I committed code which adds a semblance of tiling to kwin. Every time you launch a window, it will be maximized vertically, but each window shares the width of the screen equally. You can check out code from KDE svn. The location is /home/kde/branches/work/kwin-tiling/

You'll need to edit $KDEDIR/share/config/kwinrc and set Placement=Tiling.

Then you are ready to go.

Right now there are no configuration options, no key bindings, nothing! Still a long long way from a functional release.

Please leave feedback and bugs.

Wednesday, August 27, 2008

Reworked Graffiti's first render


After quite a few weeks of work, Graffiti has (again) reached the stage where it can render it's first words. It's now at 488 lines of code, of which the CSS related stuff itself comes in at 288 lines! There is no word wrapping yet, or any layout logic. I've been busy fixing quite a few bugs in the CSS overlays and so on.

This is how you would use it.



import pygame
from pygame.locals import *

import graffiti as g # 1

pygame.init()

g.init() # 2

pygame.display.set_caption('Graffiti Render Test')

screen = pygame.display.set_mode((800, 600))
screen.fill((0, 0, 0))

page = g.page.Page('<body><p>Testing Graffiti</p></body>') # 3

page.render.on(screen) # 4

while True:
for event in pygame.event.get():
if event.type == QUIT or (event.type == KEYDOWN and event.key == K_ESCAPE):
pygame.quit()
sys.exit(0)

pygame.display.update()


Sunday, April 20, 2008

opensocial Last.fm recent tracks


After two days of OpenSocial hacking, here is my app, which fetches the users Recent tracks and displays it on the profile. Now since there is no way to directly fetch the last.fm username, I had to resort to asking the user. Also 1.0 although usable has a few errors which aren't handled smoothly. But they do not pose security risks. For now its in the sandbox, if you have access you can view it here Last.fm Recent Track and give me some feedback. It won't be on Orkut for a few days since there is a pretty large queue for applications waiting to be moderated.

Oh yeah, here's the project page on Google Code.

Monday, December 31, 2007

Stop coding already

I am sorry to say this to myself, and it is a sad way to end the year, but I've to give up on decent projects for a few months. The next few months are 'while true: study' months for 12th standard students, since the exams supposedly decide your life. Which means most of the time my mind is just looking forward to not thinking and unfortunately programming is thinking, even if it is fun. So its time to not commit to the SVN repo for some time.

Sorry

Saturday, November 24, 2007

No, my code is not stagnating

Though it does seem like that, since I haven't posted about any development in weeks.

I've been working on a game in C++/SDL and its taking some time since this is the first time I'm building something with levels and passcodes and stuff. It's not going to be anything extraordinary, but it will take about 2 weeks more.

Friday, August 31, 2007

Tetrablocks and Login Win32 versions

I forgot to post this but Windows executables of both Tetrablocks and Login have been uploaded to 22bits. Compiling them on Windows was a bit of a nightmare. Tetrablocks has got all of its text drawing code removed since I couldn't get SDL_ttf to link. So you won't see the score or the game over message when playing it on Windows.

Wednesday, August 15, 2007

ColourCode: 1.0 Released

I'm quite pleased to announce the release of version 1.0 of ColourCode. You can download and know more about it from the ColourCode homepage. Please leave your feedback and bugs in the comments

Thursday, August 09, 2007

ColourCode: Some thoughts about the future

Before I write about the possible future of ColourCode, let me get a few things out of the way. First today I fixed an extensionless file bug, and you can now force languages in ColourCode.

Now that thats out of the way, I was just thinking about how much interest I still have in ColourCode. I mean writing it was a lot of fun and gave me some new insights into design but I don't feel the same interest I had in parsing and analysing language files, that I had when I began ColourCode. Then it was a new challenge, now its really piling up and intruding in other plans I have. Also I am now officially in the 12th standard, which in India means a really tough exam at the end of the year, and a hundred other entrance tests. My computer time has dropped to just 1.5 hours a day, not enough to do everything. So this version of ColourCode will be the last for a long time ( atleast till the end of March ). I will continue fixing any bugs found, but there will be no new features. So please forgive me.

Thanks a lot for being a user of ColourCode, and check out my other projects.

Tuesday, August 07, 2007

ColourCode: 1.0 almost done

ColourCode is done. It now has support for PHP and HTML which it didn't have before. Perl support will probably get in before the stable release. This version has been bumped to 1.0. I think I've made a really good project and that it deserves the 1.0 tag. PDF support will not enter the 1.0 release but is on track for 1.1.

All that is left now is testing some highly tweaked files. All the standard test files I've been using are passing well. Also I need to write some documentation about implementing custom language handlers and formatters.

You can download today's development build which fixed all known bugs - colourcode-20070807.tar.gz

Please leave your comments. For usage see the usage.html file under the docs folder.

Monday, July 30, 2007

ColourCode:Updates

Language support


ColourCode now has support for C# and Javascript, completing support for the original language set. I'l be adding Perl and HTML soon enough.

Bugs


On the C based languages front there is a small logic error in the multiline comment system. I am currently trying to iron it out.

Snippet feature dropped


I've decided to drop the snippet feature since its not universal (doesn't make sense in PDF) and since CSS is now dynamically generated, giving the CSS data to the user and having him embed it will be quite annoying. So you can ofcourse embed the file in your HTML since its valid. You'll just need to remove the DOCTYPE declaration and the head section, move the style to your head and remove the end body and html tags. But its best if you just use the pages by themselves, perhaps linking to them.

Note: Nightly builds are now updated everyday at the homepage. So you can always check out the developments yourself.

Thursday, July 26, 2007

ColourCode: Almost done

In the last two days ColourCode has reached a stage where I can say that it is finally ready for release. Everything apart from implementing language handlers is done. PDF support might not be added. It all depends on how patient I am in releasing it. Meanwhile you can download a nightly build and view demos for Ruby and Python from the link above.

Friday, July 20, 2007

ColourCode: Making progress

ColourCode has been getting along quite well for the last week. I now firmly have the basic design for my plans in working code. The language parser and handlers are coordinating well and this might just be one of the best designs I've ever envisioned.
Once the main program is in place writing language handlers and formatters for ColourCode will be really easy.
At this stage only the Ruby language handler is complete, and until I can make it work perfectly for a large amount of code, no other handlers are expected.
ColourCode 0.x will also have PDF support due to the Ruby-PDF project.

You can download a development snapshot made at the time of writing this. Most code which needs to be commented is commented.

Friday, July 06, 2007

ColourCode: New ideas and old improvements

Now that TetraBlocks is done, its time to start working on the latest and greatest ColourCode release ever. Though the next version should be called 0.3, I might bump it up a few notches if everything works out as planned.

One of the major things I've been planning is to implement the Language Descriptors in Ruby itself rather than text files or XML. This would allow them to be more context sensitive and basically do more stuff to improve highlighting. For now this is just a vision, and I haven't really got around to even scribbling exactly what it's going to be.

Other improvements include:
  • Support for highlighting multiple files at once, or a complete directory
  • Using the optparse module to parse command line arguments
  • Decoupling of the actual highlighting code and the interface code so that ColourCode can be used as a library
  • Change the colour map from CSS to a format independent one
  • HTML line numbers are generated using an ordered list.
  • Add support for more languages.

Tuesday, June 26, 2007

TetraBlocks: It is done

To much applause and enthusiasm (atleast by one guy) TetraBlocks is released to the world. Probably the millionth clone of Tetris, you can proudly claim your own version at the Tetrablocks page. Only available as a source archive for now, a windows executable is in the works

Wednesday, June 20, 2007

TetraBlocks:The finishing run

After having dumped the initial TetraBlocks code, the new version is coming along quite well and has proceeded especially well for the last few days. The few things left involve the deletion of lines and score keeping and putting a cap on the height. I've decided to do away with the menu and highscores system since it added for excess complexity.

Here is a screenshot of the latest developments.


PS. I play tetris quite well, but then there wouldn't be a screenshot