PortableGUI

SourceForge Logo
Hosted by SourceForge
Project page

Huarong

Code for Huarong app with PortableGUI

/***
 * Portable GUI
 * Code for Huarong Path
 * Copyright (C) 2001-2001 LittlePanda <littlepanda@mostlysunny.com>
 */

#include <portablegui.h>
#include "huarong.h"

class HRBoard;
class HRPiece;
class HRPossibleMove;

#define mokka &g_hMokka
#define choco &g_hChoco
PGColor g_hMokka(187,170,142);
PGColor g_hChoco(59,42,14);

PGFont* g_hBoldfont;
PGFrame* g_hMainFrame;
PGPanel* g_hStatus;
PGIcon* g_hIcon;
PGImage* g_hLogo;
HRBoard* g_hBoard;
PGPanel* g_hControl;
PGLabel* g_hSteps;
PGButton* g_hButtonNew;
PGButton* g_hButtonRandom;
PGButton* g_hButtonUndo;
PGButton* g_hButtonSolve;
PGButton* g_hButtonHelp;
PGButton* g_hButtonAbout;
PGButton* g_hButtonQuit;

void setSteps(int nb);
void enableUndoButton();
void disableUndoButton();

class HRBoard : public PGCanvas {
private:
    HRPiece* piece[10];
    float xUnit, yUnit;
    HRPiece* pieceSelected;
    int oldX, oldY, steps;
    PGStack stack;
    int possibles[10][4][2];
    boolean refresh;
    int margin;
    PGString message;
    PGDimension bufferdim;
    PGImage bufferimg;
    PGGraphics* buffergfx;
private:
    HRPiece* selectPiece(int x, int y);
    int gridifyX(int x);
    int gridifyY(int y);
public:
    HRBoard();
    float getXUnit();
    float getYUnit();
    int getMargin();
    void reposition();
    void resetboard();
    virtual void getPreferredSize(PGDimension* dim, BOOL recalc);
    virtual void paint(PGGraphics* g);
    virtual void update(PGGraphics* g);
    int isEmpty(int x, int y);
    void mousePressed(int x, int y);
    void mouseReleased(int x, int y);
    void randomize();
    void autosolve();
    void undo();
    void possibleMoves(HRPossibleMove* marray);
    int nbMovables(HRPossibleMove* marray, int idx);
    int isDone();
};

class HRMouseListener : public PGMouseListener {
public:
    virtual void mousePressed(PGMouseEvent* evt) {
        g_hBoard->mousePressed(evt->getX(), evt->getY());
    }
    virtual void mouseReleased(PGMouseEvent* evt) {
        g_hBoard->mouseReleased(evt->getX(), evt->getY());
    }
};

class HRPiece {
private:
    HRBoard *board;
    int pwidth, pheight, xpos, ypos;

public:

    HRPiece::HRPiece(HRBoard* b, int w, int h) {
        board = b;
        pwidth = w;
        pheight = h;
    }

    void setPos(int x, int y) {
        xpos = x;
        ypos = y;
    }

    int getXPos() {
        return xpos;
    }

    int getYPos() {
        return ypos;
    }

    void show(PGGraphics* g) {
        g->setColor(mokka);
        int x1 = PGMathRound(xpos * board->getXUnit()) + board->getMargin();
        int y1 = PGMathRound(ypos * board->getYUnit()) + board->getMargin();
        int x2 = PGMathRound(pwidth * board->getXUnit()) - 2;
        int y2 = PGMathRound(pheight * board->getYUnit()) - 2;
        g->fillRect(x1,y1,x2,y2 );
        g->draw3DRect(x1,y1,x2-1,y2-1,true);
    }

    void moveBy(PGGraphics* g, int x, int y) {
        x += xpos;
        y += ypos;
        int x1 = PGMathRound(xpos * board->getXUnit()) + board->getMargin();
        int y1 = PGMathRound(ypos * board->getYUnit()) + board->getMargin();
        int x2 = PGMathRound(pwidth * board->getXUnit()) - 2;
        int y2 = PGMathRound(pheight * board->getYUnit()) - 2;
        if (g != NULL) {
            g->setColor(PGCOLOR_BLACK);
            g->fillRect(x1,y1,x2,y2);
        }
        setPos(x,y);
        if (g != NULL)
            show(g);
    }

    void moveBy(int x, int y) {
        x += xpos;
        y += ypos;
        setPos(x,y);
    }

    boolean isSelected(int x, int y) {
        return (xpos <= x && ypos <= y && x < (xpos + pwidth) && y < (ypos + pheight));
    }

    boolean isLegalMove(int* dir) {
        if (PGMathAbs(dir[0]) > 2)
            dir[0] = 2*dir[0]/PGMathAbs(dir[0]);
        if (PGMathAbs(dir[1]) > 2)
            dir[1] = 2*dir[1]/PGMathAbs(dir[1]);
        int dirX = dir[0];
        int dirY = dir[1];
        if (dirX == 0 && dirY == 0)
            return false;
        if (PGMathAbs(pwidth*dirY)+PGMathAbs(pheight*dirX) > 2)
            return false;
        if (PGMathAbs(dirX) == 2) {
            dir[0] /= 2;
            if (isLegalMove(dir)) {
                xpos += dir[0];
                if (isLegalMove(dir))
                    dir[0] *= 2;
                xpos -= dirX/2;
                return true;
            } else
                return false;
        }
        if (PGMathAbs(dirY) == 2) {
            dir[1] /= 2;
            if (isLegalMove(dir)) {
                ypos += dir[1];
                if (isLegalMove(dir))
                    dir[1] *= 2;
                ypos -= dirY/2;
                return true;
            } else
                return false;
        }
        if (PGMathAbs(dirX) == 1 && PGMathAbs(dirY) == 1) {
            dir[1] = 0;
            if (isLegalMove(dir)) {
                xpos += dirX;
                dir[0] = 0;
                dir[1] = dirY;
                if (!isLegalMove(dir))
                    dir[1] = 0;
                dir[0] = dirX;
                xpos -= dirX;
                return true;
            } else {
                dir[1] = dirY;
                dir[0] = 0;
                if (isLegalMove(dir)) {
                    ypos += dirY;
                    dir[0] = dirX;
                    dir[1] = 0;
                    if (!isLegalMove(dir))
                        dir[0] = 0;
                    dir[1] = dirY;
                    ypos -= dirY;
                    return true;
                } else
                    return false;
            }
        }

        int newX = xpos + dirX;
        int newY = ypos + dirY;

        if (newX < 0 || (newX + pwidth - 1) >= 4  || newY < 0 || (newY + pheight - 1) >= 5)
            return false;

        if (dirX == 1)
            return (g_hBoard->isEmpty(xpos + pwidth, ypos) && g_hBoard->isEmpty(xpos + pwidth, ypos+pheight-1));
        if (dirX == -1)
            return (g_hBoard->isEmpty(xpos - 1, ypos)&& g_hBoard->isEmpty(xpos - 1, ypos + pheight - 1));
        if (dirY == 1)
            return (g_hBoard->isEmpty(xpos, ypos + pheight) && g_hBoard->isEmpty(xpos + pwidth - 1, ypos + pheight));
        if (dirY == -1)
            return (g_hBoard->isEmpty(xpos, ypos - 1) && g_hBoard->isEmpty(xpos+pwidth - 1, ypos - 1));
        return false;
    }
};

class HRMove : public PGObject {
public:
    int px, py, dx, dy;
public:
    HRMove(int* dir, HRPiece* piece) {
        px = dir[0];
        py = dir[1];
        dx = piece->getXPos();
        dy = piece->getYPos();
    }
};

class HRPossibleMove : public PGObject {
public:
    int dx, dy;
    boolean possible;
public:
    HRPossibleMove() {
        dx = 0;
        dy = 0;
        possible = false;
    }
    void setPossible(boolean pos) {
        possible = pos;
    }
    void setDirection(int x, int y) {
        dx = x;
        dy = y;
    }
    boolean isPossible() {
        return possible;
    }
};

HRPiece* HRBoard::selectPiece(int x, int y) {
    for (int i = 0; i < 10; i++) {
        if (piece[i]->isSelected(x, y)) {
            return piece[i];
        }
    }
    return NULL;
}

int HRBoard::gridifyX(int x) {
    return (int)((x - margin)/xUnit);
}

int HRBoard::gridifyY(int y){
    return (int)((y - margin)/yUnit);
}

HRBoard::HRBoard(){
    piece[0] = new HRPiece(this, 2, 2);
    piece[1] = new HRPiece(this, 1, 2);
    piece[2] = new HRPiece(this, 1, 2);
    piece[3] = new HRPiece(this, 1, 2);
    piece[4] = new HRPiece(this, 1, 2);
    piece[5] = new HRPiece(this, 2, 1);
    piece[6] = new HRPiece(this, 1, 1);
    piece[7] = new HRPiece(this, 1, 1);
    piece[8] = new HRPiece(this, 1, 1);
    piece[9] = new HRPiece(this, 1, 1);

    int dirL[2];
    dirL[0] = -1;
    dirL[1] = 0;

    int dirR[2];
    dirR[0] = 1;
    dirR[1] = 0;

    int dirD[2];
    dirD[0] = 0;
    dirD[1] = -1;

    int dirU[2];
    dirU[0] = 0;
    dirU[1] = 1;

    for (int i = 0; i < 10; i++) {
        for (int j = 0; j < 2; j++) {
            possibles[i][0][j] = dirL[j];
            possibles[i][1][j] = dirR[j];
            possibles[i][2][j] = dirD[j];
            possibles[i][3][j] = dirU[j];
        }
    }
    margin = 8;
    message = "";
    resetboard();
    reposition();
    addMouseListener(new HRMouseListener());
}

float HRBoard::getXUnit(){
    return xUnit;
}

float HRBoard::getYUnit(){
    return yUnit;
}

int HRBoard::getMargin(){
    return margin;
}

void HRBoard::reposition(){
    piece[0]->setPos(1, 0);
    piece[1]->setPos(0, 0);
    piece[2]->setPos(0, 2);
    piece[3]->setPos(3, 0);
    piece[4]->setPos(3, 2);
    piece[5]->setPos(1, 2);
    piece[6]->setPos(0, 4);
    piece[7]->setPos(1, 3);
    piece[8]->setPos(2, 3);
    piece[9]->setPos(3, 4);
    repaint();
    refresh = true;
}

void HRBoard::resetboard(){
    message = "";
    steps = 0;
    stack.removeAll();
}

void HRBoard::getPreferredSize(PGDimension* dim, BOOL recalc) {
	dim->width = 200;
	dim->height = 200;
}

void HRBoard::paint(PGGraphics* g){
    PGDimension d;
    getSize(&d);
    if ((buffergfx == NULL)
        || (d.width != bufferdim.width)
        || (d.height != bufferdim.height)) {
        bufferdim = d;
        xUnit = (d.width - 2*margin)/4.0f;
        yUnit = (d.height - 2*margin)/5.0f;
        createImage(d.width, d.height, &bufferimg);
        refresh = true;
    }
    if (refresh) {
        buffergfx = bufferimg.getGraphics();
        buffergfx->setColor(PGCOLOR_BLACK);
        buffergfx->fillRect(0, 0, d.width, d.height);
        buffergfx->setColor(PGCOLOR_WHITE);
        buffergfx->draw3DRect(2, 2, d.width-5, d.height-5, false);
        buffergfx->draw3DRect(margin-2, margin-2, d.width-2*margin+3, d.height-2*margin+3, false);
        buffergfx->setColor(PGCOLOR_BLACK);
        buffergfx->fillRect(margin + PGMathRound(xUnit)-1, d.height-margin, PGMathRound(xUnit*2), margin);
        for (int i = 0; i < 10; i++)
            piece[i]->show(buffergfx);
        refresh = false;
    }
    if (strcmp(message,"") != 0) {
        buffergfx->setColor(choco);
        buffergfx->setFont(this, g_hBoldfont);
        buffergfx->drawString(message,12,d.height/2-12);
    }
    g->drawImage(&bufferimg, 0, 0);
}

void HRBoard::update(PGGraphics* g){
    paint(g);
}

int HRBoard::isEmpty(int x, int y){
    return (selectPiece(x, y) == NULL);
}

void HRBoard::mousePressed(int x, int y){
    oldX = gridifyX(x);
    oldY = gridifyY(y);
    pieceSelected = selectPiece(oldX, oldY);
}

void HRBoard::mouseReleased(int x, int y){
    if (pieceSelected != NULL) {
        x = gridifyX(x);
        y = gridifyY(y);
        int dir[2];
        dir[0] = x - oldX;
        dir[1] = y - oldY;
        if (pieceSelected->isLegalMove(dir)) {
            oldX = pieceSelected->getXPos();
            oldY = pieceSelected->getYPos();
            if (steps == 0)
                enableUndoButton();
            pieceSelected->moveBy(buffergfx, dir[0], dir[1]);
            HRMove* move = new HRMove(dir, pieceSelected);
            stack.push(move);
            PGINCREF(move);
            steps++;
            setSteps(steps);
            if (isDone()) {
                message = "Puzzle solved! :O)";
            } else {
                if (strcmp(message,"") != 0){
                    message = "";
                    refresh = true;
                }
            }
            repaint();
        }
        pieceSelected = NULL;
    }
}

void HRBoard::randomize(){
    PGRandom* randomizer = new PGRandom();
    PGINCREF(randomizer);
    HRPossibleMove movables[40];
    for (int i = 0; i < 10000; i++) {
        possibleMoves(movables);
        int s = randomizer->nextInt(10);
        int nb = nbMovables(movables,s);
        while (nb == 0) {
            s = randomizer->nextInt(10);
            nb = nbMovables(movables,s);
        }
        int posidx = 0;
        int idx = randomizer->nextInt(nb);
        for (int ctr = 0; ctr < 4; ctr++) {
            if (movables[s*4+ctr].isPossible()) {
                if (posidx == idx) {
                    piece[s]->moveBy(movables[s*4+ctr].dx, movables[s*4+ctr].dy);
                    break;
                }
                posidx++;
            }
        }
    }
    PGDECREF(randomizer);
    refresh = true;
    repaint();
}

int HRBoard::nbMovables(HRPossibleMove* marray, int idx) {
    int nb = 0;
    for(int i = 0; i < 4; i++) {
        if (marray[idx*4+i].isPossible()) nb++;
    }
    return nb;
}

void HRBoard::autosolve(){
    piece[0]->setPos(2, 3);
    piece[1]->setPos(0, 0);
    piece[2]->setPos(1, 0);
    piece[3]->setPos(2, 0);
    piece[4]->setPos(3, 0);
    piece[5]->setPos(0, 2);
    piece[6]->setPos(2, 2);
    piece[7]->setPos(3, 2);
    piece[8]->setPos(0, 3);
    piece[9]->setPos(0, 4);
    refresh = true;
    repaint();
}

void HRBoard::undo(){
    if (! stack.empty()) {
        HRMove* move = (HRMove*)stack.pop();
        int newX = move->dx;
        int newY = move->dy;
        int dirX = move->px;
        int dirY = move->py;
        PGDECREF(move);
        selectPiece(newX, newY)->moveBy(buffergfx, -dirX, -dirY);
        if (isDone()) {
            message = "Puzzle solved! :O)";
        } else {
            if (strcmp(message, "") != 0){
                message = "";
                refresh = true;
            }
        }
        repaint();
        steps --;
        if (steps == 0)
            disableUndoButton();
        setSteps(steps);
    } else {
        steps = 0;
        disableUndoButton();
    }
}

void HRBoard::possibleMoves(HRPossibleMove *movables){
    for (int i = 0; i < 10; i++) {
        for (int j = 0; j < 4; j++) {
            int idir[2];
            idir[0] = possibles[i][j][0];
            idir[1] = possibles[i][j][1];
            if (piece[i]->isLegalMove(idir)) {
                movables[i*4+j].setPossible(true);
                movables[i*4+j].setDirection(idir[0], idir[1]);
            } else
                movables[i*4+j].setPossible(false);
        }
    }
}

int HRBoard::isDone(){
    return (piece[0]->getXPos() == 1 && piece[0]->getYPos() == 3);
}

class MyWindowListener : public PGWindowListener {

	virtual void windowClosing(PGWindow* wnd) {
		getPGApplication()->exit(0);
	}
};

void setSteps(int nb) {
    if (nb == 1) g_hSteps->setText(STR(nb) + STR(" step"));
    else g_hSteps->setText(STR(nb) + STR(" steps"));
}

void enableAllButtons() {
    g_hButtonNew->setEnabled(true);
    g_hButtonRandom->setEnabled(true);
    g_hButtonSolve->setEnabled(true);
    g_hButtonHelp->setEnabled(true);
    g_hButtonAbout->setEnabled(true);
    g_hButtonQuit->setEnabled(true);
}

void disableAllButtons() {
    g_hButtonNew->setEnabled(false);
    g_hButtonRandom->setEnabled(false);
    g_hButtonSolve->setEnabled(false);
    g_hButtonHelp->setEnabled(false);
    g_hButtonAbout->setEnabled(false);
    g_hButtonQuit->setEnabled(false);
}

void enableUndoButton() {
    g_hButtonUndo->setEnabled(true);
}

void disableUndoButton() {
    g_hButtonUndo->setEnabled(false);
}

class HRButtonNewListener : public PGActionListener {
public:
    virtual void actionPerformed(PGActionListenerParent* from) {
        g_hBoard->resetboard();
        setSteps(0);
        disableUndoButton();
        g_hBoard->reposition();
    }
};

class HRButtonRandomListener : public PGActionListener {
public:
    virtual void actionPerformed(PGActionListenerParent* from) {
        g_hBoard->resetboard();
        setSteps(0);
        disableUndoButton();
        disableAllButtons();
        g_hBoard->randomize();
        enableAllButtons();
    }
};

class HRButtonUndoListener : public PGActionListener {
public:
    virtual void actionPerformed(PGActionListenerParent* from) {
        g_hBoard->undo();
    }
};

class HRButtonSolveListener : public PGActionListener {
public:
    virtual void actionPerformed(PGActionListenerParent* from) {
        g_hBoard->resetboard();
        setSteps(0);
        disableUndoButton();
        g_hBoard->autosolve();
    }
};

class HRButtonHelpListener : public PGActionListener {
public:
    virtual void actionPerformed(PGActionListenerParent* from) {
		PGStyle* style = new PGStyle();
		style->setFont(new PGFont("Dialog", PGFONT_BOLD | PGFONT_ITALIC, 16));
		// Create help dialog
		PGDialog help(g_hMainFrame, "Help - Huarong Path Puzzle");		
		help.addWindowListener(new PGCloseWindowListener());
		help.setLayout(new PGBorderLayout(1,1,5,5));
		PGTextArea* ta = new PGTextArea("The goal of this puzzle is to move the big square to the exit at the bottom center of the game board.", 6, 30, 0);
		ta->append("\r\n");
		ta->append("Use the mouse to drag the pieces.");
		ta->setEditable(false);
		help.add(ta, PGBORDERLAYOUT_CENTER);
		// Create and add close button
		PGButton* ok = new PGButton(" &Close ", true);
		ok->setToolTipText("Close this window");
		ok->addActionListener(new PGDialogCancelListener(&help));
		help.add(ok, PGBORDERLAYOUT_SOUTH);
		// Pack components in window and show modal
		help.pack();
		help.doModal();
    }
};

class HRButtonAboutListener : public PGActionListener {
public:
    virtual void actionPerformed(PGActionListenerParent* from) {
		PGStyle* style = new PGStyle();
		style->setFont(new PGFont("Dialog", PGFONT_BOLD | PGFONT_ITALIC, 16));
		// Create about dialog
		PGDialog about(g_hMainFrame, "About Huarong Path Puzzle");
		about.addWindowListener(new PGCloseWindowListener());
		about.setLayout(new PGBorderLayout(1,1,5,5));
		PGTextArea* ta = new PGTextArea("Hua Rong Path Puzzle v1.0", 6, 30, 0);
        	ta->append("\r\n");
		ta->append("Author: LittlePanda <littlepanda@mostlysunny.com>");
		ta->append("Version: v 1.0");
		ta->append("Last Update: 18 June 2001");
		ta->setEditable(false);
		about.add(ta, PGBORDERLAYOUT_CENTER);
		// Create and add close button
		PGButton* ok = new PGButton(" &Close ", true);
		ok->setToolTipText("Close this window");
		ok->addActionListener(new PGDialogCancelListener(&about));
		about.add(ok, PGBORDERLAYOUT_SOUTH);
		// Pack components in window and show modal
		about.pack();
		about.doModal();
    }
};

class HRButtonQuitListener : public PGActionListener {
public:
    virtual void actionPerformed(PGActionListenerParent* from) {
		getPGApplication()->exit(0);
    }
};

PGPanel* makeControlPanel(PGFrame* frame) {
    PGPanel* panel = new PGPanel();
    panel->setLayout(new PGPercentLayout("12% 12% 12% 12% 12% 12% 12% 4%d 12%", 4, 0, true));
    g_hButtonNew = new PGButton("New");
    g_hButtonNew->addActionListener(new HRButtonNewListener());
    g_hButtonRandom = new PGButton("Random");
    g_hButtonRandom->addActionListener(new HRButtonRandomListener());
    g_hButtonUndo = new PGButton("Undo");
    g_hButtonUndo->addActionListener(new HRButtonUndoListener());
    g_hButtonUndo->setEnabled(false);
    g_hButtonSolve = new PGButton("Solve");
    g_hButtonSolve->addActionListener(new HRButtonSolveListener());
    //g_hButtonSolve->setEnabled(false);
    g_hButtonHelp = new PGButton("Help");
    g_hButtonHelp->addActionListener(new HRButtonHelpListener());
    g_hButtonAbout = new PGButton("About");
    g_hButtonAbout->addActionListener(new HRButtonAboutListener());
    g_hButtonQuit = new PGButton("Quit");
    g_hButtonQuit->addActionListener(new HRButtonQuitListener());
    g_hSteps = new PGLabel("0 steps");
    panel->add(g_hButtonNew);
    panel->add(g_hButtonRandom);
    panel->add(g_hButtonUndo);
    panel->add(g_hButtonSolve);
    panel->add(g_hButtonHelp);
    panel->add(g_hButtonAbout);
    panel->add(g_hButtonQuit);
    panel->add(g_hSteps);
    return panel;
}

PGPanel* makeStatusPanel(PGFrame* frame) {
	// Create style for status line
	PGStyle* style = new PGStyle();
	style->setFont(new PGFont("Dialog", PGFONT_PLAIN, 8));
	// Create panel
	PGPanel* panel = new PGPanel();
	panel->setLayout(new PGBorderLayout(3, 3));
	// Create and add status line
	PGLabel* status = new PGLabel("Huarong Path v 1.0", PGLABEL_SUNKEN);
	status->setStyle(style);
	panel->add(status, PGBORDERLAYOUT_CENTER);
	// Add panel to main frame
	frame->setStatusDisplay(status);
	return panel;
}

PGImage* loadMemoryGIF(PGWindow* owner, const PGBYTE* data, long size) {
	PGImage* image = new PGImage(owner);
	PGMemoryInputStream file(data, size);
	PGImageReader reader(image);
	image->m_iError = PGLoadGIF(&file, &reader);	
	return image;
}

PGMAINPROC {
	PGApplication app;
	// Initialize the app using Mainproc args
	PGINIT(app);
	// Create main frame window
	g_hMainFrame = new PGFrame("HuaRong Path");
	PGINCREF(g_hMainFrame);
	g_hBoldfont = new PGFont("Dialog", PGFONT_BOLD, 16);
	PGINCREF(g_hBoldfont);
	// Load Huarong title image
	g_hLogo = loadMemoryGIF(g_hMainFrame, HUARONG_IMG, HUARONG_IMG_SIZE);
	PGINCREF(g_hLogo);
	// Add window listener to frame
	g_hMainFrame->addWindowListener(new MyWindowListener());
	// Use simple layoutmanager
	g_hMainFrame->setLayout(new PGBorderLayout(5,5,8,8));
	// Add the panels
	g_hMainFrame->add(new PGImageViewer(g_hLogo), PGBORDERLAYOUT_NORTH);
	g_hControl = makeControlPanel(g_hMainFrame);
	g_hMainFrame->add(g_hControl, PGBORDERLAYOUT_EAST);
    	g_hStatus = makeStatusPanel(g_hMainFrame);
	g_hMainFrame->add(g_hStatus, PGBORDERLAYOUT_SOUTH);
	// Add the main Huarong Board
	g_hBoard = new HRBoard();
	g_hMainFrame->add(g_hBoard, PGBORDERLAYOUT_CENTER);
	// Pack and show the frame
	g_hMainFrame->pack();
	g_hMainFrame->setVisible(true);
	// Run the main event loop
	PGEXITCODE exitCode = app.run();
	// Free global references
	PGDECREF(g_hBoldfont);
	PGDECREF(g_hMainFrame);
	PGDECREF(g_hLogo);
	return exitCode;
}
Syntax highlighted by Code2HTML, v. 0.9

Screenshots

How to run

Back to PortableGUI