Neither one nor Many
Software engineering blog about my projects, geometry, visualization and music.
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!)
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.
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:
On Ubuntu:
Put this line in your /etc/apt/sources.list: deb http://repository.condor.tv lucid main
apt-get update
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:
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).
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
"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"
Default your keyboard controls are
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..
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!
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.
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.
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.
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>.)
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>;
};
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];"]
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.
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).
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]
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.
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
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])
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)
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!
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
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
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".
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!
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]
;
I have posted on using allegro 4 with wxWidgets before. Allegro 5 is more easy.
Just the stuff I encountered and how to fix
#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>
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);
}