forum

Osu!API++

posted
Total Posts
25
Topic Starter
abraker
Hi all! As I'm working on the player skill analyzer, I came up with a C++ API that would allow me to interface with the Osu! API in a good way and figured why not share it. There are somethings that are still unsupported and I will implement those things eventually. In the mean time, enjoy Osu API++, and I would love to hear some feedback.

Head over here!
DeathAlchemy
This seems like a good idea. :)

You could add:
Heat maps from plays
How close you are to hitting the middles of the hit circles over the time of the map with a graph
How close you are to hitting the circles on time over the time of the map with a graph
Tips for players to help them with speeding up their playing, streams, and jumps
Recording stats while playing with ranks and such

This is a good idea, i would like to use it if there could be some stats info to look at and also tips to help on peoples playing styles.
Topic Starter
abraker

DeathAlchemy wrote:

Heat maps from plays
How close you are to hitting the middles of the hit circles over the time of the map with a graph
How close you are to hitting the circles on time over the time of the map with a graph
Tips for players to help them with speeding up their playing, streams, and jumps
Recording stats while playing with ranks and such
Woah, woah, woah! This is just a C++ equivelant to osu! API, not its own thing. To do what you have mentioned would require reading the replay file and not this. I suggest you go here to see what this API can and cannot do. Apart from that, I will definitely consider making an API to read replay files in the near future.
DeathAlchemy

abraker wrote:

DeathAlchemy wrote:

Heat maps from plays
How close you are to hitting the middles of the hit circles over the time of the map with a graph
How close you are to hitting the circles on time over the time of the map with a graph
Tips for players to help them with speeding up their playing, streams, and jumps
Woah, woah, woah! This is just a C++ equivelant to osu! API, not its own thing. To do what you have mentioned would require reading the replay file and not this. I suggest you go hereto see what this API can and cannot do. Apart from that, I will definitely consider making an API to read replay files in the near future.
Sorry i got really excited :oops:

also while trying to build the application this happened...http://imgur.com/sbEvLxU
Bauxe

DeathAlchemy wrote:

Sorry i got really excited :oops:

also while trying to build the application this happened...http://imgur.com/sbEvLxU
I don't think you really understand what this is for.

However the issue there seems to be the path to JSON_Parser.cpp is incorrect.
Topic Starter
abraker
Make sure you have these set correctly, referencing where the files are. Also this IS NOT an application, it's a library.
DeathAlchemy

Bauxe wrote:

DeathAlchemy wrote:

Sorry i got really excited :oops:

also while trying to build the application this happened...http://imgur.com/sbEvLxU
I don't think you really understand what this is for.

However the issue there seems to be the path to JSON_Parser.cpp is incorrect.
You are correct as i said before i didn't read the post all the way through, as i got excited and wrote what i did.

abraker wrote:

Make sure you have these set correctly, referencing where the files are. Also this IS NOT an application, it's a library.
http://imgur.com/gIWLZuk
Topic Starter
abraker
Pull the new update. I separated the headers and sources into their own folders.
DeathAlchemy

abraker wrote:

Pull the new update. I separated the headers and sources into their own folders.
built went well but now a lib file is missing.
http://imgur.com/WaTyCQQ
Topic Starter
abraker

abraker wrote:

this IS NOT an application, it's a library.
Change the target.

Soinou
Not a bad idea, but here are some remarks:

  1. The usage seems awfully hard for what it does, I mean instead of registering the parameters as params[whatever], then call the get method, why not just call the get methods with the parameters ?
  2. Your project file is called Project4, you should probably rename it (You can juste rename it then reopen it, everything will work. Maybe you'll have to delete and reimport the vcxproj file, but well ...)
  3. Oh, and you have something agains conventions it seems. You have JSON_Parser, OsuInfo, download as filenames. You can't have three name conventions for three files, that's just wrong.
  4. Instead of using static methods on a class, consider making this class a singleton.
  5. If you want to use constants that are 0/1/2/3, instead of putting them in a namespace and making them strings, just use an enumeration, they will be considered as integers. Or just pass the game mode as a third arguments and use defines, it will be the same and probably faster since you will in the end pass numbers directly.
  6. You should use a JSON library instead of creating your own or copying it from somewhere else. Use something like JsonCpp or any other library of the kind. They're easy to use, have a lot of useful functionalities and make your code simpler. Libraries are made to be reused, you should use them. Don't recode something that is already coded.
  7. You are returning weird arrays from your functions. Why not make a custom type, like an OsuUser or an OsuBeatmap and return it ? Seems a lot easier to code and use.
Well, those are just remarks, you can do whatever you want with them. I'm not a pro, so feel free to just ignore me :D.
Topic Starter
abraker

Soinou wrote:

The usage seems awfully hard for what it does, I mean instead of registering the parameters as params[whatever], then call the get method, why not just call the get methods with the parameters ?
I might not fully understood what you meant, but I did it this way to make the coding cleaner. If I did
 Osu_Info::getInfo(Osu_Info::MODE::get_user_best, "param1", "param2", "param3", etc);
Then the order of the parameters would matter and in most cases not all paramaters will be used. Consider this code:
// load parameters
params[U32 Osu_Info::PARAM::user_ID] = "abraker";
params[U32 Osu_Info::PARAM::game_mode] = GAMEMODE::Mania;
params[U32 Osu_Info::PARAM::limit] = "50";
user_best = Osu_Info::getInfo(Osu_Info::MODE::get_user_best, params);
If I was to do what you propose it would look like this:
Osu_Info::getInfo(Osu_Info::MODE::get_user_best,  "abraker", "", "50", "", "", "",  GAMEMODE::Mania, "");
I suppose I can do this, but it looks ugly IMO.

Soinou wrote:

Your project file is called Project4, you should probably rename it (You can juste rename it then reopen it, everything will work. Maybe you'll have to delete and reimport the vcxproj file, but well ...)
I can't figure this out for the life of me! There doesn't seem an easy to do this within Visual Studio. I think I will have to create a new project and copy everything to the new folder. I'll do this in the next update.

Soinou wrote:

Oh, and you have something agains conventions it seems. You have JSON_Parser, OsuInfo, download as filenames. You can't have three name conventions for three files, that's just wrong.
XD well that slipped out of my coding standards some how. Nice Catch! Will fix in the next update, and yes it's just wrong.

Soinou wrote:

Instead of using static methods on a class, consider making this class a singleton.
Will do.

Soinou wrote:

If you want to use constants that are 0/1/2/3, instead of putting them in a namespace and making them strings, just use an enumeration, they will be considered as integers. Or just pass the game mode as a third arguments and use defines, it will be the same and probably faster since you will in the end pass numbers directly.
Do I? I am using it as a string.
if (_param[U32 PARAM::game_mode] != "")
url += ("m=" + _param[U32 PARAM::game_mode] + "&");
I could pass it as a 3rd parameter, but I want to keep consistency. All parameters will be passed through an array of 8 strings.

Soinou wrote:

You should use a JSON library instead of creating your own or copying it from somewhere else. Use something like JsonCpp or any other library of the kind. They're easy to use, have a lot of useful functionalities and make your code simpler. Libraries are made to be reused, you should use them. Don't recode something that is already coded.
I agree and was thinking of doing so. Perhaps it's my laziness of bothering of including other libraries that lead to this. I might consider doing this after the next update.

Soinou wrote:

You are returning weird arrays from your functions. Why not make a custom type, like an OsuUser or an OsuBeatmap and return it ? Seems a lot easier to code and use.
You mean typedef? If so, I did it for the previous design with tables, but didn't feel like vector<vector<string>> needed one. Though, I can make it so.

  • *updated the git with todo's. Will start working on fixing/adding the features this weekend or next week.
Topic Starter
abraker
Alright I
  • - Renamed the project4 folder to Osu!API++
    - Made filenames follow one naming convention
    - And Made the static classes singletons
In the next update expect the current JSON code to be replaced by a JASON library. Also unfortunately when overwriting the master branch, I lost the readme text file which had the info on how to use the Osu!API++ library, so I'll have fun rewriting that too. The tutorial.cpp file should still be there (renamed it from main.cpp to tutorial.cpp).
Nabile

abraker wrote:

  • - And Made the static classes singletons
You might want to read this about singletons.
Topic Starter
abraker

Nabile wrote:

abraker wrote:

  • - And Made the static classes singletons
You might want to read this about singletons.
I realized I didn't use the instance variable declared in the singleton classes and is fixed now. Other than that, I am not sure if you pointed me there due to design or incorrect implementation, so I'll address both. Honestly I am split between the static design and singleton design. If you think my implementation of a singleton is wrong, mind pointing out where and/or how?
Nabile

abraker wrote:

I realized I didn't use the instance variable declared in the singleton classes and is fixed now. Other than that, I am not sure if you pointed me there due to design or incorrect implementation, so I'll address both. Honestly I am split between the static design and singleton design. If you think my implementation of a singleton is wrong, mind pointing out where and/or how?
Singletons are a solution in the short term. As explained in the chapter, they are going to be a pain in the long term (for reasons described in Why we regret using it).
Depending on what you were trying to solve, you could choose a different approach, like letting the user of this library instantiate the API and ensuring it has only one instance, and passing the reference when required instead of having a global variable. Or see if you need an instance at all, if not a simple static class will do. Not only that but it'll be better to write Foo::bar() instead of Foo::getInstance().bar() everywhere.

But that's only an advice :p
Topic Starter
abraker

Soinou wrote:

Instead of using static methods on a class, consider making this class a singleton.
Since I am split between the two, I followed Soinou's advice. Unless those classes will be extended and the code becomes more complex, I don't think it matters which design I use.
Bobbias

abraker wrote:

Unless those classes will be extended and the code becomes more complex, I don't think it matters which design I use.
Actually, it totally does matter. Singletons are much harder for anyone to extend. Programming is one of the few places where considering how things might change in the future can be extremely helpful. Singletons are extremely difficult to refactor compared to other approaches, among other issues.

I feel dirty just looking at code with a lot of static/global values. That would be the reason I never refactored wanwan159's o2jam to osu converter.
Topic Starter
abraker

Bobbias wrote:

stuff
I am probably seeing this in the perspective where I can't imagine why you would want to extend the classes in this library. Just in case that I'm missing something, there is the static approach and singleton approach. Is there a better approach to this? The original idea was to make everything static so that the code would be clean while forcing there to be only one copy of the class.
theLiminator
Oftentimes inheritance is a code smell anyways. Though singletons kinda are too.
Soinou
Seems like I need to argue to why I told him to make the class a singleton.

The osu!api is something we want to get from everywhere and I can't see a reason why we would need to create multiple osu!api objects.

In the end, there will always have only one osu!api object, on which we will call the classic "getBeatmaps" or "getUser" methods.

So if you don't want it as a singleton, I can't see how you want to make the osu!api class so it's not a singleton yet only one can be used.

I know singleton is something that is always see as "bad practice", but there is some cases where it can fit what we want to do, and I think this is the perfect case where singleton is a good way to do what we want to do.

Also, it's been a long time since I checked here, so:

abraker wrote:

I might not fully understood what you meant, but I did it this way to make the coding cleaner. If I did
 Osu_Info::getInfo(Osu_Info::MODE::get_user_best, "param1", "param2", "param3", etc);
Then the order of the parameters would matter and in most cases not all paramaters will be used. Consider this code:
// load parameters
params[U32 Osu_Info::PARAM::user_ID] = "abraker";
params[U32 Osu_Info::PARAM::game_mode] = GAMEMODE::Mania;
params[U32 Osu_Info::PARAM::limit] = "50";
user_best = Osu_Info::getInfo(Osu_Info::MODE::get_user_best, params);
If I was to do what you propose it would look like this:
Osu_Info::getInfo(Osu_Info::MODE::get_user_best,  "abraker", "", "50", "", "", "",  GAMEMODE::Mania, "");
I suppose I can do this, but it looks ugly IMO.
Well, in cases like this you have the method overloading that can make things work pretty well.

For example, instead of having only one method with a lot of arguments, you can always have multiple methods with the most used arguments. But this is your choice. I just tend to prefer having multiple methods with variable number of arguments instead of passing an array/object of parameters.

Edit: I forgot to add that you can always separate the method in multiple methods like getBeatmap, getUser, getUserBest, ...
This will simplify a lot the method calls as we will have the correct method to call for what we want, will be less prone to errors and will be a lot easier to use.

abraker wrote:

I can't figure this out for the life of me! There doesn't seem an easy to do this within Visual Studio. I think I will have to create a new project and copy everything to the new folder. I'll do this in the next update.
Apparently you fixed it, but in cases like this you just need to rename the project.sln file and the project.vcxproj file.
No need to recreate the project, as visual studio doesn't care how the files are called, as long as they contain the right informations.

abraker wrote:

Do I? I am using it as a string.
if (_param[U32 PARAM::game_mode] != "")
url += ("m=" + _param[U32 PARAM::game_mode] + "&");
I could pass it as a 3rd parameter, but I want to keep consistency. All parameters will be passed through an array of 8 strings.
No, but I mean, instead of doing something like:

params[U32 PARAM:game_mode] = "something";
params[U32 PARAM:limit] = 50;

you could do:

enum Parameter
{
kGameMode = 0,
kLimit = 1
}

params[Parameter::kGameMode] = "something";
params[Parameter::kLimit] = 50;

And your parameters could be defined in a map<int, string> or even better, in a custom class, where your [] operator would look like:

operator[] (Parameter param)
{
switch(param)
{
case Parameter::kGameMode:
// Handle game mode setter
break;
case Parameter::kLimit:
// Handle limit setter
break;
default:
break;
}
}

Sorry if I'm not really clear. If this is the case I'll try to make a better example, or something like that.

abraker wrote:

You mean typedef? If so, I did it for the previous design with tables, but didn't feel like vector<vector<string>> needed one. Though, I can make it so.
No, I meant that you would make something like an Osu::User that will contain all the informations parsed from the json.
For example:

namespace Osu
{
struct User
{
long id;
std::string username;
...
}
}

This would be way better to understand and to use than a vector, and would be less prone to errors.

Oh and a last thing about the singleton.

Instead of putting in the object a field named instance and initializing/returning it from a get_instance method, you can just make a get_instance method containing the following:

const Osu_Info& Osu_Info::getInstance()
{
static Osu_Info instance;

return instance;
}

This will create a static instance of the Osu_Info object the first time this method will be called and will use the same each time this method is called.

Oh and you can also make a shortcut macro, like:

#define [whatever] Osu_Info::getInstance()

This will enable users to call [whatever].setApiKey(), or [whatever].getInfo() instead of calling the cumbersome Osu_Info::getInstance() each time.
Topic Starter
abraker

Soinou wrote:

stuff about singletons
While I port the code to use the JsonCpp library and refactor some code based on what you mentioned, I will wait to see what others have to say on that matter.

Also considering that macro at the end, even though I actually used it in tutorial.cpp, I removed it before pushing the code to git because I think that people don't like the hidden effect such macros produce. They can add this themselves if they want. I have nothing against it myself, though.
Topic Starter
abraker
Well that took quite some time to figure out! Despite the lack of documentation on JsonCpp, I managed to get it working. I took Soinou's suggestion and changed the data type into something more meaningful as well.

Examples:
  • Data type:
    vector<vector<string>> data; --> Osu_Info::OsuData<MODE::get_user_best> data;

    Method of accessing data:
    data[i][U32 Osu_Info::get_user_best::beatmap_id]; --> data.getValue(i, "beatmap_id");
The new method of getting data ensures that the correct info is being requested under the given mode (otherwise JsonCpp would complain... A LOT) and allowed me to easier interface with the JsonCpp library. I still need to refactor the PARAM enum, but most of the heavy refactoring is now done. And I still need to rewrite the documentation =.= ...So what's with the singleton argument so far?
Nabile

abraker wrote:

So what's with the singleton argument so far?
I'll be honest and face the controversy... This all seems like some patchwork to make this work (notably the macro mentioned).

If someone with lots of experience in the field and who has witnessed how different design patterns work over the years recommend not to use them and seek alternatives, then that's a good indication you should be wary of it.

But if in the end it works for you, then it's all good ! We won't force you to avoid using them since you own your project anyway.
Topic Starter
abraker
Finally rewrote the tutorial! I hope it's clear on how to use the library.
Please sign in to reply.

New reply