Neither one nor Many

 
October 21 2012

Smash Battle is a really cool game made by Bert Hekman and Jeroen Groeneweg. Of which Jeroen is now a collegue of mine at Tweakers.net. It supports up to four players, you can use gamepads and the multiplayer is best out of five. You can get powerups in the game like extra damage, health pack, armor etc. :) (I see that in the codebase also a single player is under development!)

Network version

I decided to add a network multiplayer gametype to it, and I develop that in a separate branch. It supports more than four players.

Currently the network multiplayer supports only bullets and mines (your default equipment). Powerups do not yet appear. All damage to players or tiles is determined on the server. The clients are trusted though, to send their correct player positions, shots fired etc. You could theoretically cheat (up to a certain level) by modifying and compiling your own client, but it is far easier to implement a network multiplayer if I can trust clients somewhat. This can easily be rewritten though, and if you play with low lags you will probably not notice any difference. But I think you will notice if someone is cheating.

Pre-alpha test release

My fork is https://bitbucket.org/rayburgemeestre/smashbattle

It's a pre-alpha because the gametype is not completely finished yet, if there are more than two players a normal best out of five multiplayer starts. Once the game has started, you cannot join the server anymore. You can already test it out simply install the current release of Smashbattle.

On Windows:

  • It will default install to C:\Program Files\Smash Battle\
  • Extract the contents of smashbattle-pre-alpha.zip into your installation dir. (Use BattleXP.exe if you're running Windows XP)
  • If you changed the install location of it, modify the path in register_server.reg.
  • Double click the .reg file, now smashbattle:// protocol should be available.

On Ubuntu:

  • Put this line in your /etc/apt/sources.list: deb http://repository.condor.tv lucid main

  • apt-get update

  • apt-get install battle
  • cd /usr/share/games/smashbattle/ <<<<< important! the game expects level data in ".".
  • ./battle

Note that the update command might give you this if you are running 64 bit:

Ign http://us.archive.ubuntu.com quantal-backports/universe Translation-en_US
Fetched 1,032 kB in 30s (33.7 kB/s)
W: Failed to fetch http://repository.condor.tv/dists/lucid/main/binary-amd64/Packages  404  Not Found

E: Some index files failed to download. They have been ignored, or old ones used instead.

You can ignore this error and continue installing the 32 bit version.

The game should now run, but to use the pre-alpha, you have to replace the 'battle' binary with mine:

  • rm -f battle && wget http://blog.cppse.nl/battle && chmod +x battle
  • Edit: you may need to: apt-get install libsdl-net1.2

Server usage

You can start your server with these parameters:

Battle.exe -s 
Example: Battle.exe -s

Battle.exe -s <listen port>
Example: Battle.exe -s 1100

Battle.exe -s <levelname> <listen port>
Example: Battle.exe -s "TRAINING DOJO" 1100

In case no level is given as a parameter, the server will start with level selector. In case no port is given, default will be used (1100).

Client usage

To connect to a client, you need to have registered the .reg file. You can click links like:

smashbattle://<domain.tld>:<port>
Example: smashbattle://cppse.nl:1100

You could type such an url in your WINDOWS+R (Run command) or in command prompt start <url>.

If you do not like to register the .reg file, you can also give it to Battle.exe as a parameter:

Battle.exe <url>
Example: Battle.exe smashbattle://cppse.nl:1100

After you have set a server on your machine, you should be able to connect using ---> smashbattle://localhost:1100

The level names are

"TRAINING DOJO"
"PLATFORM ALLEY"
"PITTFALL"
"DUCK'N'HUNT"
"COMMON GROUNDS"
"POGOSTICK"
"LA MOUSTACHE"
"THE FUNNEL"
"BLAST BOWL"
"PIT OF DEATH"
"RABBIT HOLE"
"STAY HIGH"
"PIE PIT"
"SLIP'N'SLIDE"
"BOULDERDASH"
"SNOW FIGHT"

In game controls

  • F1 - toggle console (for debugging)
  • F10 - toggle fullscreen
  • F11 - toggle FPS
  • ESCAPE - pause menu

Default your keyboard controls are

  • a, s, d, w - left, duck, right, jump.
  • CONTROL - shoot
  • ALT - mine
  • SHIFT - run

Roadmap

  • Link gameloop to time
  • Create client and server class
  • Design efficient protocol
  • Create solution for lag
  • Introduce nicknames
  • Introduce ingame chat
  • Support powerups
  • Introduce more gametypes?
    • Red team vs Blue team
    • King of the Hill
    • Capture the flag
  • Refactor*
  • Merge with master

Refactoring:

While developing I sometimes put #include's above the function where I use stuff from it. This is when I feel like I might refactor the code, I can easily remove the #include again. Works for me, but it results in some stray #include's. Also I'm not sure about my design choice of making server and client singleton's (basically global classes). It was easy so I could weave the client/server code into the game rapidly, but I think it may need to integrate with the existing classes better, and use polymorphism a bit more here and there. Example: I have a few places in the code where I do different stuff based on Main::runmode static global, for server do this, for client do this..

Blog Comments (2)
 
October 21 2012

Select behaviour in address bar in Linux

I use control + arrow keys and control + shift + arrow keys for selecting a lot. And as a webdeveloper especially in the address bar. I think it is somehow the default under linux distributions, under OpenSuse anyways, that always all text is selected. I find that very VERY annoying. Because you cannot quickly select (a) piece(es) from the URL. But luckily I found the config setting where you can change this! img1

Enable H264 support in Windows

In about:config, enable the value media.windows-media-foundation.enabled. Especially useful if you disable Flash. A lot of video players use a HTML5 player as fallback support only the H264 codec.

Blog Comments (2)
 
October 19 2012

Multiple iterators that use the same begin() or end() functions.

In C++ you cannot differentiate based on the type-to-return. Like have two begin() methods in a class that return different iterators.

class foo 
{
    public:
        some_iterator begin() 
        {
            return some_iterator(); 
        }

        // Not possible
        other_iterator begin() 
        {
            return other_iterator(); 
        }
};

some_iterator it = fooinstance.begin();

There is also no template syntax to implement a begin() method for this purpose. Note that you cannot use "straightforward" polymorphism because the subclasses are on the LHS of the assignment.

I still wanted it though and came up with the following solution. img1

For example an instance of a NumberRange class provides two iterators the default "iterator" simply outputs all the numbers. The "cumulative_iterator" outputs all numbers cumulatively.

int main(int argc, char **argv)
{
    NumberRange range(1, 10);

    cout << "NumberRange::iterator:" << endl;
    for (NumberRange::iterator iter = range.begin(); iter != range.end(); iter++)
        cout << *iter << endl;

    cout << "NumberRange::cumulative_iterator:" << endl;
    for (NumberRange::cumulative_iterator iter = range.begin(); iter != range.end(); iter++)
        cout << *iter << endl;

    return EXIT_SUCCESS;
}

/**
 * Desired output:
 * -----------------------------------------------------------
 * ksh$ g++ iterators.cpp &&./a.out
 * Constructing NumberRange object with numbers 1 to 10
 * NumberRange::iterator:
 * 1
 * 2
 * 3
 * 4
 * 5
 * 6
 * 7
 * 8
 * 9
 * 10
 * NumberRange::cumulative_iterator:
 * 1
 * 3
 * 6
 * 10
 * 15
 * 21
 * 28
 * 36
 * 45
 * 55
 */

I really like this as an API because if you want to change the way of iterating through the range (in this example), you only change NumberRange::iterator to something else.

Implementation of NumberRange

class NumberRange
{
public:
    NumberRange(int rangebegin, int rangeend)
    {
        cout << "Constructing NumberRange object with numbers " << rangebegin << " to " << rangeend << endl;

        for (int i=rangebegin; i<=rangeend; i++)
            numbers_.push_back(i);
    }

    NumberIter<void> begin()
    {
        return NumberIter<void>(numbers_, 0);
    }

    NumberIter<void> end()
    {
        return NumberIter<void>(numbers_, numbers_.size());
    }

    typedef NumberIter<Traits_Normal> iterator;
    typedef NumberIter<Traits_Cumulative> cumulative_iterator;

private:
    vector<int> numbers_;
};

It's a very simple implementation. It stores the numbers in a vector. The functions begin() and end() provide iterators of NumberIter. NumberIter is a templated class with traits. Possible traits that we are going to define are: void, Traits_Normal, Traits_Cumulative. I prefer to use void where the specific Trait is not yet known. I could have also have made a Traits_Null.

NumberRange only works with NumberIter<void> because begin() does not know what specifc NumberIter instance to return (Traits_Normal or Traits_Cumulative). In the assignment "NumberRange::iterator iter = range.begin()" the NumberIter<void> is converted into a NumberIter<Traits_Normal>. (NumberRange::iterator is a typedef for NumberIter<Traits_Normal>.)

Implementation of NumberIter

This class is templated to provide multiple kinds of iterators, by using NumberIterTraits. These traits provide the implementation of the specific iterators. So this class only provides the API.

template <typename T, typename Traits = NumberIterTraits<T> >
class NumberIter: public std::iterator< std::forward_iterator_tag, string >
{
public:

    // Constructors
    NumberIter(const vector<int> &numbers, size_t seq)
        : sequence_(seq), numbers_(numbers)
    {}

    // Copy constructor
    NumberIter(const NumberIter<void> &other)
    {
        numbers_ = other.numbers_;
        sequence_ = other.sequence_;
    }

    // Operators
    const int operator*() const
    {
        return Traits::next(numbers_, sequence_);
    }

    NumberIter & operator++(int)
    {
        sequence_++;
        return *this;
    }

    template <typename N>
    bool operator==(const NumberIter<N>& other)
    {
        return sequence_ == other.sequence_;
    }

    template <typename X>
    bool operator!=(const NumberIter<X>& other)
    {
        return !((*this) == other);
    }

private:

    vector<int> numbers_;
    size_t sequence_;

    friend class NumberIter<Traits_Normal>;
    friend class NumberIter<Traits_Cumulative>;
};

  • Constructor takes a copy of the numbers vector, which is really inefficient. But I wanted to keep the example simple. Sequence parameter is the current position of the iterator.
  • There is a copy constructor (used in the assignment "NumberRange::iterator iter = range.begin();")
  • operator* returns the current value of the operator. Note that the traits implement different processing and return.
  • operator++ increments the sequence.
  • operator== and operator!= are required for "i != range.end()".

Traits classes

template<typename T> class NumberIterTraits;
template<> class NumberIterTraits<void>
{
public:
    static int next(const vector<int> &numbers, size_t sequence)
    {
        throw logic_error("NumberIterTraits<void>::next should not be used.");
    }
};


class Traits_Normal;
template<> class NumberIterTraits<Traits_Normal>
{
public:
    static int next(const vector<int> &numbers, size_t sequence)
    {
        return numbers[sequence];
    }
};


class Traits_Cumulative;
template<> class NumberIterTraits<Traits_Cumulative>
{
public:
    static int next(const vector<int> &numbers, size_t sequence)
    {
        if (sequence < 0)
            return 0;

        int value = 0;

        for (int i=0; i <= sequence; i++)
            value += numbers[i];

        return value;
    }
};

The Traits_Normal version simply returns the number at the index. The Traits_Cumulative sums all numbers from first to current index.

Note that to add another iterator you only need to add another Traits class. (Well in my case another typedef in NumberRange for consistency as well. But you could do without and omit them like "for (NumberIter<Traits_Something> i = range.begin(); ...)".)

[Edit: also a friend class declaration in NumberIter. That's so that the "generated" NumberIter classes can reference internals. Personal preference over adding more class functions.]

[Edit2: You could add a Traits_Reverse with "return numbers[numbers.size() - ++sequence];"]

Final notes

IIRC there are some compilers that require an implementation of "operator=" for the conversion in "NumberRange::iterator = range.begin()". They refuse to use the copy constructor for this statement. In that case use this on the class.

NumberIter operator=(NumberIter<void> val)
{
    numbers_ = val.numbers_;
    sequence_ = val.sequence_;
    return *this;
}

Complete source code can be downloaded here. img1

There are probably more alternatives for this, i.e. you could probably do without templates.

The iterators in this example are not fully std compliant. I.e. you cannot use them in functions from #include <algorithm>.

Code tested on gcc version 4.3.4 [gcc-4_3-branch revision 152973] (SUSE Linux).

C++ Comments (1)
 
September 23 2012

I have an unfinished project with--in general--some really horrible sourcecode, but with some cool functions and solutions I came up with as well. One thing I needed for example was to calculate latitude and longitude coordinates from X and Y positions on a google maps canvas, taking zoom level into account. I could not find these conversion functions (around august 2011 anyway).

[Edit, now since May 21, 2015 Google Maps Api V3 was released, which makes it possible with the Google API. I also found an example gist here]

Aligning markers to a grid

The reason I needed these convertion functions in the first place was for creating an align feature for (custom) markers on a google maps canvas. This is how it works before/after aligning:

The (very simple) algorithm I came up with divides the map in slots. A grid with a width of 100 for example, only positions markers on 100, 200, 300, 400 pixels. In this example, the 'nearest' slot's width of a marker at position 220,50 pixels wouuld be 200.

The algorithm in pseudocode:

INITIALIZE GRID HEIGHT AND WIDTH ACCORDING TO MAP'S ZOOMLEVEL
    /* gridwidth = 100 << (21 - map.getZoom()) */
    /* gridheight = 30 << (21 - map.getZoom()) */

FOREACH MARKER

    CONVERT MARKER LAT,LON TO X,Y COORDINATES
        /* y = latToY(marker.getPosition().lat()); */
        /* x = lonToX(marker.getPosition().lng()); */

    CONVERT X,Y TO NEAREST SLOT X,Y
        /* slot_y = Math.round(y - (y % gridheight)) */
        /* slot_x = Math.round(x - (x % gridwidth)) */

    SET MARKER X,Y TO SLOT X,Y

    WHILE SLOT POSITION IS OCCUPIED BY ANOTHER MARKER

        MOVE TO NEXT SLOT POSITION
            /* Next slot position is according to a simple spiral movement [1] */

        SET MARKER X,Y TO SLOT X,Y

    ENDWHILE

    CONVERT MARKER X,Y TO LAT,LON
        /* marker.setPosition(new google.maps.LatLng(lat, lon)); */
        /* marker.setPosition(new google.maps.LatLng(lat, log)); */

    STORE SOMEWHERE THAT MARKER IS IN THIS SLOT POSITION

ENDFOREACH

[1]: The search for next slot position is according to this pattern:

up, right, down, down, left, left, up, up, up, right, right, right, etc.

  • The change of direction is continious (a spiral): {up, right, down, left, ..}
  • The number of 'steps' in each direction is {1, 1, 2, 2, .... n, n}

latToX() and lonToY()

I'm not an expert in math but I was able to find some expressions online that resolved lat+lon for x+y (the other way around). I simply replaced all the constants with their values and put them in a solver to solve them for the variables I was interested in (e.g. longitude for XtoLon). I probably have the sites bookmarked somewhere but I can't find them.

var glOffset = 268435456;
var glRadius = 85445659.4471;// offset / pi

function lonToX(lon) 
{
    var p = Math.PI / 180;
    var b = glRadius * lon;
    var c = b * p;
    return Math.round(glOffset + c);
}

function XtoLon(x) 
{
    return -180 + 0.0000006705522537 * x;
}

function latToY(lat)
{
    return Math.round(glOffset - glRadius *
            Math.log((1 + Math.sin(lat * Math.PI / 180)) /
                (1 - Math.sin(lat * Math.PI / 180))) / 2);
}

function YtoLat(y) 
{
    var e = 2.7182818284590452353602875;
    var a = 268435456;
    var b = 85445659.4471;
    var c = 0.017453292519943;

    return Math.asin(Math.pow(e,(2*a/b-2*y/b))/(Math.pow(e,(2*a/b-2*y/b))+1)-1/(Math.pow(e,(2*a/b-2*y/b))+1))/c;
}

They are not pretty but I like them because they work really well img1

Edit 10-AUG-2015: deltaLonPerDeltaX(), deltaLatPerDeltaY()

I found out somebody on Stackoverflow elaborated my functions with a deltaLonPerDeltaX() and deltaLatPerDeltaY(). The original poster's image is no longer available, so I'm not sure if I understand the question correctly, and therefore these additional functions. But there is a nice extra info cited from Google, which I will copy here:

At zoom level 1, the map consists of 4 256x256 pixels tiles, resulting in a pixel space from 512x512. At zoom level 19, each x and y pixel on the map can be referenced using a value between 0 and 256 * 2^19

(See [https://developers.google.com/maps/documentation/javascript/maptypes?hl=en#MapCoordinates][https://developers.google.com/maps/documentation/javascript/maptypes?hl=en#MapCoordinates])

Webdevelopment Comments (0)
 
July 30 2012

Just one thing that was annoying me for a long while, and how I fixed it. I tend to switch back and forth between insert and command mode in vim. And somehow PhpStorm with IdeaVim plugin enabled felt non-responsive. I press escape, start hitting :wq, and I have :wq in my code.

I got accustomed hitting Escape twice, and later even three times, by default so that I was more certain I was out of insert mode. I also tried Control+C, and Control+[, but they have the same problem.

I know the 'problem' always occured when i.e. PhpStorm started rendering an Intellisense popup: press '.' somewhere, in a large file it may take a few moments before that popup appears (maybe due to parsing etc.), so you don't see it. Assuming you are now in command mode, the escape press was actually consumed by the popup. Then of course you do escape to command, and try to undo, but it undo's a lot more than the chars you now accidentally sprayed in the code (also not exactly the same behaviour as Vim, but alas :D)

Fix

Right mouse click -> Remove Escape:

Go to Plug-ins -> IdeaVIM ->

Find the row with all the keybindings on it.. right click on it -> Add Keyboard Shortcut

Hit escape, save that. -> Apply -> Ok.

Annnnnd you're done!

Blog Comments (7)
 
July 12 2012

StarcryPublic Comments (0)
 
May 12 2012

I posted this in a comment here, a long while ago. I forgot about it but yesterday someone posted in the same topic, and therefore I received an e-mail.

As my comment somehow isn't visible on the blog (maybe it was never moderated?) i'll post it here. I was using it in a non-unicode project myself, so I encountered the same problem. According to the mail I recvd this is what I wrote:

Nice fixes.

How I get the sourcecode to work in my unicode program however, without modifying the source is as follows.

Simply don't compile the file all_in_one.cpp (or all ibpp/core/*.cpp files individually) with the defines/"preprocessor definitions" _UNICODE and UNICODE enabled.

I didn't look at the Flamerobin source, but my guess is that they do the same.
Posted by rayburgemeestre to Untouched at 6:05 PM 

IBPP in a Qt Creator project

Add the following in your .pro file:

DEFINES += IBPP_WINDOWS=value

LIBS += Advapi32.lib

the lib is for fixing

all_in_one.obj : error LNK2019: unresolved external symbol __imp__RegCloseKey@4 referenced in function "public: struct ibpp_internals::GDS * __thiscall ibpp_internals::GDS::Call(void)" (?Call@GDS@ibpp_internals@@QAEPAU12@XZ)
all_in_one.obj : error LNK2019: unresolved external symbol __imp__RegOpenKeyExA@20 referenced in function "public: struct ibpp_internals::GDS * __thiscall ibpp_internals::GDS::Call(void)" (?Call@GDS@ibpp_internals@@QAEPAU12@XZ)
all_in_one.obj : error LNK2019: unresolved external symbol __imp__RegQueryValueExA@24 referenced in function "public: struct ibpp_internals::GDS * __thiscall ibpp_internals::GDS::Call(void)" (?Call@GDS@ibpp_internals@@QAEPAU12@XZ)

Also added the following two #undefs to all_in_one.cpp.

#undef _UNICODE
#undef UNICODE
C++ Comments (1)
 
April 18 2012

I find it pleasant to have nicknames coloured in busy channels, that's why I made this. It simply generates colours by hashing the nicknames. This ensures that a given nickname will always be the same colour.

    

The script

;;;
;;; Lazy nickname coloring script
;;;
;;; Color all nicknames automatically by calculating a numeric hash over the nickname.
;;; The calculated number is used to pick a (space delimited) color from the %colors variable 
;;;  (set in "on START" event).
;;; Colors are made configurable because yellow on white is annoying, and you may want to use
;;;  black or white depending on your background color.
;;;

;; Initialize

on 1:START: {
  .initialize_coloring
}

alias initialize_coloring {
  ; use the following colors only
  .set %colors 1 2 3 4 5 6 7 9 10 11 12 13 14 15

  ; reset all entries in the clist
  while ($cnick(1)) {
    .uncolor_nick $cnick(1)
  }
}

;; Events

; Parse the /names <channel> response(s)
raw 353:*: {
  var %names = $4-  
  var %i = 1
  var %n = $gettok(%names,0,32)
  while (%i <= %n) {
    var %current_nick = $gettok(%names,%i,32)
    var %firstchar = $mid(%current_nick, 1, 1)
    while (%firstchar isin @+%) {

      %current_nick = $mid(%current_nick, 2)
      %firstchar = $mid(%current_nick, 1, 1)
    }
    .color_nick %current_nick

    inc %i
  }
}

; Handle nick changes/joins/quits
on 1:NICK: { 
  .uncolor_nick $nick
  .color_nick $newnick 
}

on 1:JOIN:*: { 
  .color_nick $nick
}

on 1:QUIT: { 
  .uncolor_nick $nick
}

;; Helper functions

; usage: color_nick <nickname>
alias color_nick {
  if (!%colors) {
    .initialize_coloring 
  }
  var %colors_idx = $calc($hash($1, 16) % $numtok(%colors, 32)) + 1
  var %nick_color = $gettok(%colors, %colors_idx, 32)
  .cnick $1 %nick_color 
}

; usage: uncolor_nick <nickname>
alias uncolor_nick {
  .cnick -r $1
}

Copy & paste it in your remote (open with alt + r).

You may need to enable nicklist colouring in general. Use alt + b, Nick colors, choose "Enable".

Update!!!

Note that I have a new version of this available, see this blogpost.. It also provides a script that makes nicks marked as away light-grey!

Blog Comments (3)
 
April 8 2012

The following image was rendered by another process

Motivation for using separate processes for rendering is if you wish to have multiple threads rendering. I do a lot of set blending type, put pixels, set blender type again, more pixels, etc. If I use async() to render multiple images at once these function calls might interfere as race conditions.

Probably a noobish moment, but I never realized the "stack" was this limited. I tried declaring something like

struct structw800h600
{
    ...
    Pixels pixels[800 * 600].
};

message_queue mq (create_only, "pixels",
    1, //max message number
    sizeof(structw800h600)); //max message size

structw800h600 img;
memset(&img, 0x00, sizeof(structw800h600));

This code caused an exception while constructing the object that declared an instance of the struct on the stack:

Unhandled exception at 0x003E5017 in Starcry.exe: 0xC00000FD: Stack overflow (parameters: 0x00000000, 0x00702000).

Shows break here in chkstk.asm (because I am in debug mode):

[...]
; Find next lower page and probe
cs20:
        sub     eax, _PAGESIZE_         ; decrease by PAGESIZE
        test    dword ptr [eax],eax     ; probe page. <<<<<<<<<<<<<<<<<<<< here
        jmp     short cs10

_chkstk endp

        end

I did not find out the exact threshold but the the crash occured when the size of the struct was above ~1024972 bytes or ~1000 kB. (Size of each pixel object is 16 byte). If I understand it correctly the stack is only several MB so I was simply storing too much data on it.

Still posting this because I almost jumped to the false conclusion that it was a windows platform shared memory limitation. Simply allocate the Pixel objects from the free-store and send that through the message queue. Something like: Pixel *pixels = new Pixel[800 * 600];

C++ Comments (0)
 
April 8 2012

I have posted on using allegro 4 with wxWidgets before. Allegro 5 is more easy.

Just the stuff I encountered and how to fix

Fix main conflict

#define ALLEGRO_USE_CONSOLE 1

Avoids the following error.

1>MSVCRTD.lib(crtexe.obj) : error LNK2019: unresolved external symbol _main referenced in function ___tmainCRTStartup

#define ALLEGRO_USE_CONSOLE 1

#include <allegro5/allegro.h>
#include <allegro5/allegro_image.h>
#include <allegro5/allegro_primitives.h>
#include <allegro5/allegro_font.h>
#include <allegro5/allegro_ttf.h>

Draw ALLEGRO_BITMAP on static canvas

Note that there is no equivalent of Allegro 4's draw_to_hdc() function. With a little grepping in the source code I found out that (for windows anyway) you have functions that do the same in C:\allegro5\src\win\wmcursor.c

Just borrow local_draw_to_hdc from there and use it in the paint event.

staticbitmap->Connect(wxID_STATIC, wxEVT_PAINT, wxPaintEventHandler(SharedMemoryTest::OnPaint), NULL, this);

void SharedMemoryTest::OnPaint( wxPaintEvent& event )
{
    wxPaintDC dc(wxDynamicCast(event.GetEventObject(), wxWindow));
    WXHDC wxHDC = wxPaintDC::FindDCInCache((wxWindow*) event.GetEventObject());
    HDC hDC = (HDC) wxHDC;

    local_draw_to_hdc(hDC, bmp, 0, 0);
}
C++ Comments (0)

Paging
Page 1
Page 2
Page 3
Page 4
Page 5
Page 6 <<<< You are Here!
Page 7
Page 8
Page 9
Author:
Ray Burgemeestre
february 23th, 1984

Topics:
C++, Linux, Webdev

Other interests:
Music, Art, Zen