How I saved a few lives by optimization and a bit about working at Zeptolab

    Hello!

    23derevo before speaking at Mobius asked me to tell a little about the customer development process at Zeptolab.



    To begin with, we write in C ++ and in our framework, from any client device we only need the OpenGL context. Further, from scratch we build our interface, our controls, and so on. Accordingly, in order to take a developer into a team, in theory, it is enough for him to know the pluses. In practice, this is a bit wrong.



    About work


    I came to Zeptolab when we had as many as three developers: CTO, iOS-developer and Android-developer. Prior to that, I studied at Yandex SHAD and, at the same time, sawed through a customs documentation database with the possibility of Rich-formatting, storing files and images - in general, a kind of MSDN, only for customs needs. Until now, it is used, and until now, analogues are just beginning to be found for it.

    I didn’t have super cool technological knowledge, I was engaged in graphics, wrote small projects on OpenGL, made shaders. This, in general, was enough to begin to study on the mobile development branch.

    In my opinion, the most important thing for the candidate (and the developer in general) is general intelligence, technical outlook and technical thinking. By the way, at the interview I was asked the notorious task about round hatches. Now I conduct interviews myself and give similar abstract tasks. They frustrate some candidates because they rarely change the list of such tasks (if given randomly, there will be no common metric - it will not be very honest with respect to the candidates). But, given that they "leak" beyond the boundaries of the interviews, we usually prepare a couple of our tasks to check if the candidate is cheating. If everything is in order with logic, then ignorance of some syntactic features of the language is a problem of a smaller scale. The syntax can be learned, programming patterns are also studied, but, alas, you need to think right away.

    In general, the encoder differs from the developer in that the latter is able to come up with ideas for solving problems. In one well-known large company my good friend works. Their Russian office is only occupied with inventing algorithms, testing them in Python or C # for prototypes, and then giving the results to units in India and China. There already distant coders without imagination, but with the utmost pedantry and with purely Asian persistence take the ideas described and ideally implement them in the code for microcontrollers in C ++ or C for each device.

    I would advise those who are looking for work right after university to get a rating in the 2000 area at Codeforces. If you are slightly yellow there, these are high chances to go, for example, to Google. In addition, you will quickly enough realize that in the first place is the ability to think and solve unique problems when specific technologies are already being studied “locally” to the necessary (or sufficient) level.

    A few words about the artists


    First we had Cocos2D. It’s a good framework, but many things simply didn’t suit us in terms of implementation, so we started to write our own system. Quickly enough, we managed to implement in C ++ a very cool animation system and good preparation of resources. We already talked about animation, in short, it is prepared in Flash, then we parse FLA files, and then recreate the same animations in the application. The most important thing for us has always been the emphasis on quality. In the case of animations, this is smooth: artists often stand behind programmers and say what’s wrong. Without training, you cannot see where and what is jerking slightly, but the artists definitely feel it. An ordinary person will not be able to understand what is wrong, and indeed not always be able to describe it, even if he sees it. But he will feel, often not very consciously, that it’s “rough.”



    At the conference, our guys will tell you exactly how we achieved this quality of the picture and show what is under the hood of the framework. I will talk about the evolution of our technologies, about the preparation of resources. It is very important that the controls get exactly pixel by pixel, correctly prepare fonts, take into account low-res devices, and much more. Again, I will show specific examples at the conference.



    For me, perhaps the greatest buzz is to stand behind the artist and watch how he creates a masterpiece from nothing. Sometimes they also look at us and try to understand what we are doing. It seems to us in development that there are few good artists in the market, and it is unrealistic to find such cool artists. It seems to them that the developers who understand what they need are unrealistically small.



    By the way, with all their sincerely humanitarian education, there have never been problems with the technical part. Tasks formulate perfectly, represent the general architecture. There was even such a funny incident: we took a picture for the contest on Codeforces as an artist as a developer, for fun. Glasses, a complex face, a thought. So, after that, he suddenly began to write code in JavaScript. At first there were very simple macros for Photoshop and for Flash. Then for several months, in fact, he went through the entire history of the evolution of development, discovering new opportunities. I remember at some point he came up and began rather clumsily explaining a concept that would help him write complex scripts a lot: after a while I realized that he wanted to set breakpoints and look at the values ​​of variables. Itself reached use of assert'ov. Prior to this, we sometimes laughed at his code sneakily: it interfered with expressions in one line, without indentation, it really looked a bit wild. And then, imperceptibly, he began to make very cool scripts. Now we think who else to photograph with a complex face.
    But back to the framework. We have quite a lot of routine, in particular, related to its constant improvements. The framework is developing, new hardware appears, new requirements, I want to deal with the legacy code in a timely manner. Of the last major tasks, for example, we needed our own particle system. We looked at what is in Unity, the artists say it's mega-cool, but you still need this, this and that.

    As a result, the task was reduced not only to writing a particle generator, but also to implementing a convenient interface. We have several emission layers, and particles move according to different laws. An arbitrary formula for each would very heavily load client devices (it would be necessary, in fact, to parse each separately), and the general one was not flexible enough to implement the ideas of artists. They decided by mathematics - they deduced some general formula for each particle, where by changing the coefficients you can run at least a parabola, at least a sinusoid. And it does not slow down, and there is visual wealth.

    In a team



    Once every two weeks we teach ourselves. The guys (and now we have 21 developers in the team) are learning something new that they have not yet used on other projects, or which is not available in other companies. They gather everyone, tell that they found interesting. These can be a variety of topics: from how to make the download subjectively fast for the user and ending with a quick background blur behind the popup (as done in King of Thieves). Mikhail Mirzayanov regularly came to our office (by the way, he coached our team, which won first place at ACM / ICPC). I read 3 blocks of the coolest lectures on algorithms and data structures, showed rare little-known structures and tasks (for example, about the tree of segments, which he independently opened one of the first in the world and the first in Russia). Like learning



    From examples of tasks - in 2013 a rather large article was published on the solutions of the well-known NP-difficult problem of packing texture atlases. According to the results of one of the Codeforces contests, a strong algorithm specialist came to us. I read the article, thought for a long time, then wrote my own algorithm, an improved version of the well-known, which we immediately checked on one of the most difficultly packed atlases. If we take 100% perfect packaging, then our previous algorithm gave a result of more than 120%, and a new one on the same data set began to show 104%. In practice, this means a decrease in memory consumption per megabyte.

    In general, for 500 million installations such things look very funny. For example, our very first system for storing and loading images operated on PNG files, and it took about 15 seconds to load a level on a test device. Profiled, sorted out - most of the time it took PNG decoding. I rewrote this code (it took a new internal format for storing graphics) - and on the same test device, loading began to take 6 seconds. Saved 9 seconds - we rolled out a new storage system for all of our games. If you count 20 downloads of the game for some basic indicator, it seems to me that at least fifty lives have been saved by this. Further, this mechanism was accelerated by another 20-30% on the advice of a beginner who did the same thing at his old place of work, because at some point simple calculations on the processor ceased to be a bottleneck in the boot system, everything began to rest on the speed of reading from the storage. Modified their format.
    There is quite a lot of work on optimization in general. Our games even work on the old hardware, the framework supports iOS 4.3 (now iOS 5: initially due to the affiliate code, then we started using libc ++, which is also available starting from iOS 5, in the second version of the framework). We are doing development of completely new applications and experiments for top models, because by the end of development they will become the most massive device - but we don’t forget the “oldies”. With the same Cut the Rope, most of the release is content updates. We do not spoil the old code. New games are already much richer visually, but their hardware requirements are higher.

    Prototyping is done very quickly, faster than in many studios. The game designer gives out a concept, then in 1-2 days one of the developers does a “dream job” - collects a prototype from scratch without graphics, on primitives. If jumping the ball and the square after this pins the game designer, he goes on to work. Naturally, there are much fewer prototypes than ordinary tasks, but we try to have everyone in the team write their own sooner or later.

    Again, of course, we immediately do this on a ready-made draft project, where there are all the basic things. For people coming from the development of native mobile applications, this is just a different world - there are no standard controllers, the preparation of resources is their own, in general, everything is different and not even very attached to the platform. Those who worked with Unity - they are more interested in digging "under the hood", seeing the implementation of some things that are difficult to do there. With Coconut, in general, there are parallels at a high level, but it's still interesting to parse the game and see how it works inside.

    A little bit about the test



    Finally - my little argument with friends. Below under the spoiler are 5 code samples from test tasks from different people. The code is published with the consent of all candidates. (carefully, the source code is quite large)

    App.cpp
    //
    //  App.cpp
    //  Asteroids
    //
    //  Created by xxxx
    //
    //
    #include 
    #include "App.h"
    #include "RenderCommandPolygonConvex.h"
    #include "Vec2.h"
    #include "Color.h"
    #include "GameMap.h"
    #include "Camera.h"
    #include "MapDrawObjectPolygon.h"
    #include "MapObjectMovable.h"
    #include "IMovable.h"
    #include "MovableObjectTouch.h"
    #include "MapObjectEmitter.h"
    #include "EmitterLineContinuous.h"
    #include "MovableInDirection.h"
    #include "MapObjectHero.h"
    #include "MapObjectAsteroid.h"
    #include "MapObjectDebris.h"
    const float LOGIC_MAP_WIDTH = 100;
    const float GAMEPLAY_ACCELERATION = 0.003;
    namespace
    {
        void initAsteroidsEmitters(GameMapPtr gameMap, float logicMapWidth, std::vector& asteroidEmitters)
        {
            for_each(asteroidEmitters.begin(), asteroidEmitters.end(), [](EmitterLineContinuousPtr emitter){emitter->die();});
            MapObjectEmitterPtr emitterMapObject(new MapObjectEmitter());
            EmitterLineContinuousPtr emitter(new EmitterLineContinuous(Vec2(-logicMapWidth*0.5f, 0), Vec2(logicMapWidth*1.5f, 0), Vec2(0, 0), 8, 25, -1, gameMap));
            emitter->setParticlesMapObject(MapObjectAsteroidPtr(new MapObjectAsteroid()));
            asteroidEmitters.push_back(emitter);
            emitterMapObject->setEmitter(emitter);
            gameMap->addMapObject(emitterMapObject, Vec2(0, -10), 0);
            MapObjectEmitterPtr emitterMapObject2(new MapObjectEmitter());
            EmitterLineContinuousPtr emitter2(new EmitterLineContinuous(Vec2(0, 0), Vec2(logicMapWidth, 0), Vec2(0, 1), 1, 30, -1, gameMap));
            emitter2->setParticlesMapObject(MapObjectAsteroidPtr(new MapObjectAsteroid()));
            emitterMapObject2->setEmitter(emitter2);
            asteroidEmitters.push_back(emitter2);
            gameMap->addMapObject(emitterMapObject2, Vec2(0, -10), 0);
            MapObjectEmitterPtr emitterMapObject3(new MapObjectEmitter());
            EmitterLineContinuousPtr emitter3(new EmitterLineContinuous(Vec2(0, 0), Vec2(logicMapWidth, 0), Vec2(0, 1), 3, 20, -1, gameMap));
            emitter3->setParticlesMapObject(MapObjectDebrisPtr(new MapObjectDebris()));
            emitterMapObject3->setEmitter(emitter3);
            asteroidEmitters.push_back(emitter3);
            gameMap->addMapObject(emitterMapObject3, Vec2(0, -10), 0);
            MapObjectEmitterPtr emitterMapObject4(new MapObjectEmitter());
            EmitterLineContinuousPtr emitter4(new EmitterLineContinuous(Vec2(0, 0), Vec2(logicMapWidth, 0), Vec2(0, 1), 1, 40, -1, gameMap));
            emitter4->setParticlesMapObject(MapObjectAsteroidPtr(new MapObjectAsteroid()));
            emitterMapObject4->setEmitter(emitter4);
            asteroidEmitters.push_back(emitter4);
            gameMap->addMapObject(emitterMapObject4, Vec2(0, -10), 0);
        }
    }
    App::App()
    :time_(0)
    {
    }
    void App::updateAndRender(float dtSec, std::vector& renderCommands)
    {
        update(dtSec);
        collectRenderData(renderCommands);
    }
    bool App::touch(const std::vector& events) const
    {
        if (events.empty())
            return false;
        if (gameMap_)
            gameMap_->touch(events);
        return true;
    }
    void App::update(float dtSec)
    {
        if (gameMap_)
            gameMap_->update(dtSec);
        tryRespawnHero();
        updateGameplayAcceleration();
        time_+= dtSec;
    }
    void App::resetGameplay()
    {
        time_ = 0;
        ::initAsteroidsEmitters(gameMap_, LOGIC_MAP_WIDTH, asteroidEmitters_);
    }
    void App::tryRespawnHero()
    {
        if (!hero_ || hero_->isReadyToDestruct() )
        {
            if (!gameMap_->hasObjectOfType(MAP_OBJECT_HERO_DEBRIS))
            {
                resetGameplay();
                hero_ = MapObjectHeroPtr(new MapObjectHero(Rect(0, 0, logicMapSize_.x, logicMapSize_.y)));
                gameMap_->addMapObject(hero_, Vec2(50, logicMapSize_.y - 10), 0);
            }
        }
    }
    void App::setScreenSize(int screenW, int screenH)
    {
        screenSize_ = Vec2(screenW, screenH);
        float logicCellSize = screenW/LOGIC_MAP_WIDTH;
        logicMapSize_ = Vec2(screenW/logicCellSize, screenH/logicCellSize);
        CameraPtr camera = CameraPtr(new Camera(logicCellSize, logicCellSize));
        gameMap_ = GameMapPtr(new GameMap(Size(logicMapSize_.x, logicMapSize_.y), camera));
        gameMap_->setLiveAreaRect(Rect(-logicMapSize_.x/2, -10, logicMapSize_.x*2, logicMapSize_.y + 20));
        resetGameplay();
    }
    void App::collectRenderData(std::vector& renderCommands) const
    {
        gameMap_->collectRenderData(renderCommands);
    }
    void App::updateGameplayAcceleration()
    {
        for (auto emitter: asteroidEmitters_)
        {
            emitter->setSpeedParticles(emitter->getSpeedParticles() + time_*GAMEPLAY_ACCELERATION);
        }
    }
    


    game.cpp
    /*управление w,a,d для полета коробляб r для возобновления иры, space для выстрела*/
    #include "stdafx.h"
    #include 
    #include 
    #include 
    #include 
    #include  
    using namespace std;
    const float Pi=3.14159265358;
    float winwid=400;
    float winhei=400;
    bool game_end=0;
    /////bullet////
    float dx=0,dy=0;
    float bull_speed=6;
    float betta=0;
    bool fl1=0, fl2=0;
    /////ship////
    float speed=0;
    float angle=0;
    float acsel=0;
    /////asteroid/////
    float ast_size=50;
    float aster_speed=3;
    /////0-rand////
    int kol_aster=0;
    class bullet
    {
    public:
    	float dxb;
    	float dyb;
    	float angleb;
    	bullet()
    	{	dxb=dx;
    		dyb=dy;
    		angleb=betta;
    	}
    };
    class asteroid
    {
    public:
    	float anglea;
    	float dx;
    	float dy;
    	float depth;
    	int n;
    	int i_big;
    	int  ifsmall;
    	vector  x;
    	vector  y;
    	void create(int i,bool param);
    	void create_small(int i,int j,bool param,float depth1,float dx1,float dy1);
    };
    void asteroid:: create_small(int i,int j,bool param,float depth1,float dx1,float dy1)
    	{
    	ifsmall=0;
    	int size=ast_size/2;
    	depth=depth1+(j+2)*1.0/(8.0*(kol_aster));
    	dx=dx1;
    	dy=dy1;
    	i_big=i;
    	/////////////////////////////////////////////////
    	int quat=rand()%4;
    	int n1=rand()%2+1;
    	int n2=rand()%2+1;
    	int n3=rand()%2+1;
    	int n4=rand()%2+1;
    	n1=n2=n3=n4=1;
    	n=n1+n2+n3+n4;
    	double xi,yi;
    	anglea=rand()%360;
    	x.clear();
    	y.clear();
    	for (int i=0;i vecb;
    vector  veca;
    void destroy_small_ast( int i)
    {
    	//////если удалено 4 маленького-создать новый большой/////
    	bool create_big=1;
    	float up_boarder=(float)(veca[i].i_big)/((kol_aster));
    	float down_boarder=(float)(veca[i].i_big)/((kol_aster));
    	asteroid a_big;
    	a_big.create(veca[i].i_big,1);
    	if (i>0) if(veca[i-1].depth>down_boarder) create_big=0; 
    	if (iwinwid/2-1) ||(vecb[i].dxb<-winwid/2+1) ||(vecb[i].dyb<-winhei/2+1) || (vecb[i].dyb>winhei/2-1)) {vecb.erase(vecb.begin()+i);i--;}
    		else{
    			/////буффер глубины////
    			glReadPixels((vecb[i].dxb+winwid/2),-vecb[i].dyb+winhei/2,2,2,GL_DEPTH_COMPONENT,GL_FLOAT,depth);
    			if (depth[0]!=1)
    			{
    				destroy_aster(depth[0]);
    				vecb.erase(vecb.begin()+i);
    				i--;
    			}
    			else
    			{
    				//////нарисовали пулю//////
    				glTranslatef(vecb[i].dxb,vecb[i].dyb,0.0f);	
    				glColor3f(1.0f,1.0f,1.0f);
    				glBegin(GL_LINES);
    				glVertex3f( 0.0,0.0, 0.5f);
    				glVertex3f(1.0,0.0, 0.5f);
    				glEnd();
    			}
    		}
    	}
    }
    void aster_draw()
    {
    	glColor3f(0.5f,1.0f,1.0f);
    	glLoadIdentity();
    	for (int i=0;iwinwid/2+ast_size) ||(veca[i].dx<-winwid/2-ast_size) ||(veca[i].dy<-winhei/2-ast_size) || (veca[i].dy>winhei/2+ast_size)) 
    		if (veca[i].ifsmall==0)
    		{destroy_small_ast( i);i--;}
    		else veca[i].create(i,1);
    	}
    }
    void asteroidsinit()
    {
    	int k;
    	k=rand()%6+4;
    	if(kol_aster!=0)	k=kol_aster;
    	else  kol_aster=k;
    	veca.resize(k);
    	for(int i=0;i-winwid/2+1)&&(dy-winwid/2+1))
    		glReadPixels((dx+winwid/2),-dy+winhei/2,1,1,GL_DEPTH_COMPONENT,GL_FLOAT,depth);
    	else depth[0]=1;
    	dx1=dx-10*cos(Pi*betta/180)-10*sin(Pi*betta/180)+winwid/2;
    	dy1=-dy+10*sin(Pi*betta/180)-10*cos(Pi*betta/180)+winhei/2;
    	if ((dx10+1)&&(dy10+1))
    		glReadPixels(dx1,dy1,1,1,GL_DEPTH_COMPONENT,GL_FLOAT,depth+1);
    	else 
    		depth[1]=1;
    	dx2=dx-10*cos(Pi*betta/180)+10*sin(Pi*betta/180)+winwid/2;
    	dy2=-dy+10*sin(Pi*betta/180)+10*cos(Pi*betta/180)+winhei/2;
    	if ((dx20+1)&&(dy20+1))
    		glReadPixels(dx2,dy2,1,1,GL_DEPTH_COMPONENT,GL_FLOAT,depth+2);
    	else
    		depth[2]=1;
    	///////корабль за пределами экрана////
    	if(dx>winwid/2) dx=-winwid/2;
    	if(dx<-winwid/2) dx=winwid/2;
    	if(dy<-winhei/2) dy=winhei/2;
    	if(dy>winhei/2) dy=-winhei/2;
    /////////рисуем//////
    	glColor3f(0.8f,0.0f,0.8f);
    	glLoadIdentity();
    	glTranslatef(dx,dy,0.0f);	
    	glRotatef(betta,0.0f,0.0f,1.0f);  			
    	glBegin(GL_TRIANGLES);
    	glVertex3f( -10.0f,-10.0f, 1.0f);
    	glVertex3f(-10.0f,10.0f, 1.0f);
    	glVertex3f(0.0f,0.0f, 1.0f);	
    	if (fl2==1){
    		glVertex3f( -10.0f,-3.0f, 1.0f);	
    		glVertex3f(-10.0f,3.0f, 1.0f);	
    		glVertex3f(-15.0f,0.0f, 1.0f);	
    	}
    	glEnd();
    /////////корабль столкнулся-игра закончилась/////////
    			if ((depth[0]!=1)||(depth[1]!=1)||(depth[2]!=1))	
    				game_end=1;
    }
    void display() 
    { 
    	glClearDepth( 1.0f );
    	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    	aster_draw();
    	draw_ship();
    	shoot();
    	glutSwapBuffers(); 
    } 
    void Timer(int)
    {
    	acsel--;
    	if(speed>10) speed=10;	
    	if (fl1==1) {angle=betta;fl1=0;}
    	if (acsel==0) {fl2=0;}
    	dx=dx+speed*cos(Pi*angle/180.0);
    	dy=dy+speed*sin(Pi*angle/180.0);
    	if(speed>0)speed=speed-0.1;
    	else speed=0;
    	display();
    	if(game_end==0) glutTimerFunc(50,Timer,0);
    }
    void Initialize()
    {
    	dx=0;
    	dy=0;
    	vecb.empty();
    	angle=betta=speed=0;
    	glClearColor(0, 0, 0.0, 1.0); 
    	glMatrixMode(GL_PROJECTION); 
    	glLoadIdentity(); 
    	glOrtho(-winwid/2, winwid/2, winhei/2, -winhei/2, -1, 1); 
    	glMatrixMode(GL_MODELVIEW);
    	glEnable(GL_DEPTH_TEST);
    	glDepthFunc( GL_LEQUAL ); 
    	float depth[5];
    	glClearDepth( 1.0f );              // Разрешить очистку буфера глубины
    	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    	asteroidsinit();
    	glutTimerFunc(500,Timer,0);
    }
    void keyboard(unsigned char key,int x,int y)
    {
    	if (key=='w') {fl1=1;speed++;fl2=1;acsel=10;}
    	if (key=='d') {betta+=7;}
    	if (key=='a') betta-=7;
    	if (key==' ') {bullet b1;vecb.push_back(b1);}
    	if(key=='r') {if(game_end==1) {game_end=0;Initialize();}}
    }
    int main(int argc, char **argv)//Главная часть 
    { 
    	glutInit(&argc, argv); 
    	glutInitDisplayMode(GLUT_DEPTH |GLUT_DOUBLE | GLUT_RGB); 
    	glutInitWindowSize(winwid, winhei); 
    	glutInitWindowPosition(200, 200); 
    	glutCreateWindow("Powder Toy"); 
    	Initialize();
    	glutDisplayFunc(display); 
    	glutKeyboardFunc(keyboard);
    	glutMainLoop(); 
    }


    game.cpp
    #include "game.h"
    #include "logic.h"
    Game::Game(unsigned width, unsigned height)
        : _asteroids(std::vector()), _shots(std::vector()), _booms(std::vector()),
          _score(0), _livesBonus(10000), _level(0), _isAsteroidsEmpty(true), _gameOver(false),
          _playerPoints(new std::vector(3)), _shotsPoints(new std::vector(4)),
          _boomPoints(new std::vector(8)), _lastTimepoint(std::chrono::high_resolution_clock::now()),
          _lastShotTimepoint(std::chrono::high_resolution_clock::now()),
          _gameOverTimepoint(std::chrono::high_resolution_clock::now()), _timeMultiplier(0.0f), _width(width),
          _height(height), _aspectRatio((float)width / (float)height),
          _render(new GL(&_aspectRatio, &_halfWidth, &_halfHeight)),
          _controls(new Controls(&_width, &_height, &_halfWidth, &_halfHeight)) {
        _halfHeight = GAME_HEIGHT;
        _halfWidth = _aspectRatio * _halfHeight;
        _playerPoints = new std::vector(3);
        (*_playerPoints)[0].x = -0.6f;
        (*_playerPoints)[0].y = -0.5f;
        (*_playerPoints)[1].x = -0.6f;
        (*_playerPoints)[1].y = 0.5f;
        (*_playerPoints)[2].x = 0.6f;
        (*_playerPoints)[2].y =  0.0f;
        _shotsPoints = new std::vector(4);
        (*_shotsPoints)[0].x = 0.02f;
        (*_shotsPoints)[0].y = 0.02f;
        (*_shotsPoints)[1].x = 0.02f;
        (*_shotsPoints)[1].y = -0.02f;
        (*_shotsPoints)[2].x = -0.02f;
        (*_shotsPoints)[2].y = -0.02f;
        (*_shotsPoints)[3].x = -0.02f;
        (*_shotsPoints)[3].y = 0.02f;
        _boomPoints = new std::vector(8);
        (*_boomPoints)[0].x = 0.1f;
        (*_boomPoints)[0].y = 0.1f;
        (*_boomPoints)[1].x = 0.5f;
        (*_boomPoints)[1].y = 0.4f;
        (*_boomPoints)[2].x = -0.1f;
        (*_boomPoints)[2].y = -0.2f;
        (*_boomPoints)[3].x = -0.5f;
        (*_boomPoints)[3].y = -0.4f;
        (*_boomPoints)[4].x = 0.2f;
        (*_boomPoints)[4].y = -0.1f;
        (*_boomPoints)[5].x = 0.5f;
        (*_boomPoints)[5].y = -0.5f;
        (*_boomPoints)[6].x = -0.1f;
        (*_boomPoints)[6].y = 0.2f;
        (*_boomPoints)[7].x = -0.5f;
        (*_boomPoints)[7].y = 0.5f;
        Shot::SetStaticPoints(_shotsPoints);
        Boom::SetStaticPoints(_boomPoints);
        Random::Init(&_halfWidth, &_halfHeight);
        _player = new Player(_playerPoints, 0.0f, 0.0f);
    }
    Game::~Game() {
        delete _player;
        delete _render;
        for (Shot *item : _shots)
            delete item;
        for (Boom *item : _booms)
            delete item;
        for (AsteroidFamily *item : _asteroids)
            delete item;
        delete _playerPoints;
        delete _shotsPoints;
        delete _boomPoints;
    }
    void Game::Refresh() {
    	_controls->Refresh();
        if (_score >= _livesBonus) {
            _livesBonus += 10000;
            _player->SetLives(_player->GetLives() + 1);
        }
        if (_isAsteroidsEmpty) {
            _level++;
            for (AsteroidFamily *item : _asteroids)
                delete item;
            _asteroids.clear();
            for (unsigned i = 0; i < (_level + 1) * 2; ++i)
                _asteroids.push_back(Random::GenerateAsteroidFamily());
            _isAsteroidsEmpty = false;
        }
        _isAsteroidsEmpty = true;
        std::chrono::high_resolution_clock::time_point now = std::chrono::high_resolution_clock::now();
        auto time_span = std::chrono::duration_cast(now - _lastTimepoint).count();
        _timeMultiplier = (float)time_span / 16666666.67f;
        _lastTimepoint = now;
        if (_controls->GetHyperspace()) {
            if (!_gameOver) {
                Random::ChangePlayerCoords(_player);
                _controls->SetHyperspace(false);
            } else {
                if (std::chrono::duration_cast(now - _gameOverTimepoint).count() >=
                        GAMEOVER_SCORE_TIME) {
                    _player->SetCoord(0.0f, 0.0f);
                    _player->SetAngle(0.0f);
                    _player->SetLives(PLAYER_DEFAULT_LIVES);
                    for (AsteroidFamily *item : _asteroids)
                        delete item;
                    _asteroids.clear();
                    _isAsteroidsEmpty = true;
                    _score = 0;
                    _level = 0;
                    _livesBonus = 10000;
                    _gameOver = false;
                    _controls->SetHyperspace(false);
                }
            }
        }
        if (_player->GetIsGhost() && !_gameOver) {
            if (!_player->GetIsRendering()) {
                if (std::chrono::duration_cast(now - _player->GetDeadTime()).count() >=
                        PLAYER_BLACKOUT_TIME) {
                    _player->SetIsRendering(true);
                }
            }
            if (std::chrono::duration_cast(now - _player->GetDeadTime()).count() >= PLAYER_GHOST_TIME) {
                _player->SetIsGhost(false);
            }
        }
        if (_player->GetLives() <= 0 && !_gameOver) {
            _gameOver = true;
            _gameOverTimepoint = now;
            _player->SetIsGhost(true);
            _player->Stop();
        }
        if (_player->GetIsRendering() && !_gameOver) {
            RefreshObjectCoord(_player);
            _player->Refresh(_controls->GetAngle(), _controls->GetAcceleration());
            if (_controls->GetShoot()) {
                if (std::chrono::duration_cast(now - _lastShotTimepoint).count() >= NEXT_SHOT_TIME) {
                    _shots.push_back(_player->GenerateShot());
                    _lastShotTimepoint = now;
                }
            }
        }
        for (auto it = _shots.begin(); it != _shots.end();) {
            RefreshObjectCoord(*it);
            (*it)->Refresh(_timeMultiplier);
            if ((*it)->GetDistance() >= std::min(_halfHeight, _halfWidth) * 2 - 1.2f) {
                delete(*it);
                it = _shots.erase(it);
            } else {
                ++it;
            }
        }
        for (AsteroidFamily *item : _asteroids) {
            if (item->GetLarge()->GetIsRendering()) {
                _isAsteroidsEmpty = false;
                RefreshObjectCoord(item->GetLarge());
                item->GetLarge()->Refresh();
                if (!_player->GetIsGhost()) {
                    if (isCollision(_player, item->GetLarge())) {
                        item->DestroyLarge();
                        _score += SCORE_LARGE;
                        ProcessCollision(_player, item->GetLarge());
                    }
                }
                if (item->GetLarge()->GetIsRendering())
                    for (auto it = _shots.begin(); it != _shots.end();) {
                        if (isCollision(*it, item->GetLarge())) {
                            delete(*it);
                            it = _shots.erase(it);
                            item->DestroyLarge();
                            _booms.push_back(new Boom(item->GetLarge()->GetCoord().x, item->GetLarge()->GetCoord().y));
                            _score += SCORE_LARGE;
                        } else {
                            ++it;
                        }
                    }
            } else {
                if (item->GetFirstSmall()->GetIsRendering()) {
                    _isAsteroidsEmpty = false;
                    RefreshObjectCoord(item->GetFirstSmall());
                    item->GetFirstSmall()->Refresh();
                    if (!_player->GetIsGhost()) {
                        if (isCollision(_player, item->GetFirstSmall())) {
                            item->GetFirstSmall()->SetIsRendering(false);
                            _score += SCORE_SMALL;
                            ProcessCollision(_player, item->GetFirstSmall());
                        }
                    }
                }
                if (item->GetSecondSmall()->GetIsRendering()) {
                    _isAsteroidsEmpty = false;
                    RefreshObjectCoord(item->GetSecondSmall());
                    item->GetSecondSmall()->Refresh();
                    if (!_player->GetIsGhost()) {
                        if (isCollision(_player, item->GetSecondSmall())) {
                            item->GetSecondSmall()->SetIsRendering(false);
                            _score += SCORE_SMALL;
                            ProcessCollision(_player, item->GetSecondSmall());
                        }
                    }
                }
                for (auto it = _shots.begin(); it != _shots.end();) {
                    bool isFirstCollision = false, isSecondCollision = false;
                    if (item->GetFirstSmall()->GetIsRendering())
                        isFirstCollision = isCollision(*it, item->GetFirstSmall());
                    if (item->GetSecondSmall()->GetIsRendering())
                        isSecondCollision = isCollision(*it, item->GetSecondSmall());
                    if (isFirstCollision || isSecondCollision) {
                        delete(*it);
                        it = _shots.erase(it);
                        if (isFirstCollision) {
                            item->GetFirstSmall()->SetIsRendering(false);
                            _booms.push_back(new Boom(item->GetFirstSmall()->GetCoord().x, item->GetFirstSmall()->GetCoord().y));
                            _score += SCORE_SMALL;
                        }
                        if (isSecondCollision) {
                            item->GetSecondSmall()->SetIsRendering(false);
                            _booms.push_back(new Boom(item->GetSecondSmall()->GetCoord().x, item->GetSecondSmall()->GetCoord().y));
                            _score += SCORE_SMALL;
                        }
                    } else {
                        ++it;
                    }
                }
            }
        }
        for (auto it = _booms.begin(); it != _booms.end();) {
            (*it)->Refresh(_timeMultiplier);
            if ((*it)->GetDuration() >= BOOM_MAX_DURATION) {
                delete(*it);
                it = _booms.erase(it);
            } else {
                ++it;
            }
        }
    }
    void Game::RefreshObjectCoord(Object *object) {
        object->SetCoord(object->GetCoord().x + object->GetVelocity().x * _timeMultiplier,
                         object->GetCoord().y + object->GetVelocity().y * _timeMultiplier);
        if (object->GetCoord().x <= -_halfWidth) object->SetCoord(object->GetCoord().x + _halfWidth * 2, object->GetCoord().y);
        else if (object->GetCoord().x >= _halfWidth) object->SetCoord(object->GetCoord().x - _halfWidth * 2,
                    object->GetCoord().y);
        if (object->GetCoord().y <= -_halfHeight) object->SetCoord(object->GetCoord().x,
                    object->GetCoord().y + _halfHeight * 2);
        else if (object->GetCoord().y >= _halfHeight) object->SetCoord(object->GetCoord().x,
                    object->GetCoord().y - _halfHeight * 2);
    }
    void Game::Render() {
        Refresh();
        _render->Clear();
        _render->RenderControls(_controls);
        _render->SetColor(OBJECTS_COLOR);
        if (_player->GetIsRendering() && !_gameOver) {
            if (_player->GetIsGhost())
                _render->SetColor(PLAYER_GHOST_COLOR);
            _render->RenderPlayer(_player);
        }
        if (_gameOver)
            _render->SetColor(OBJECTS_GAMEOVER_COLOR);
        else
            _render->SetColor(OBJECTS_COLOR);
        for (AsteroidFamily *item : _asteroids) {
            if (item->GetLarge()->GetIsRendering()) {
                _render->RenderAsteroid(item->GetLarge());
            } else {
                if (item->GetFirstSmall()->GetIsRendering()) {
                    _render->RenderAsteroid(item->GetFirstSmall());
                }
                if (item->GetSecondSmall()->GetIsRendering()) {
                    _render->RenderAsteroid(item->GetSecondSmall());
                }
            }
        }
        for (Shot *item : _shots) {
            _render->RenderShot(item);
        }
        _render->SetColor(BOOM_COLOR);
        for (Boom *item : _booms) {
            _render->RenderBoom(item);
        }
        _render->SetColor(TEXT_COLOR);
        if (!_gameOver) {
            _render->RenderScoreAndLives(_score, _player->GetLives());
        } else {
            _render->RenderGameOver(_score);
        }
    }
    bool Game::isCollision(Player *player, Asteroid *asteroid) {
        const std::vector &playerPoints = *(player->GetPoints());
        const std::vector &asteroidPoints = *(asteroid->GetPoints());
        if (TestAABB(player, asteroid)) {
            for (unsigned i = 0; i < playerPoints.size(); i++) {
                for (unsigned j = 0; j < asteroidPoints.size(); j++) {
                    if (Logic::IsLinesCross(playerPoints[i], playerPoints[(i + 1 == playerPoints.size()) ? 0 : i + 1],
                                            asteroidPoints[j], asteroidPoints[(j + 1 == asteroidPoints.size()) ? 0 : j + 1])) {
                        return true;
                    }
                }
            }
            if (Logic::IsInside(asteroidPoints, playerPoints[0])) {
                return true;
            }
        }
        return false;
    }
    bool Game::isCollision(Shot *shot, Asteroid *asteroid) {
        if (Logic::IsInside(*(asteroid->GetPoints()), shot->GetCoord()))
            return true;
        else
            return false;
    }
    bool Game::TestAABB(Player *player, Asteroid *asteroid) {
        return (player->GetSizes()[0] < asteroid->GetSizes()[1] && player->GetSizes()[1] > asteroid->GetSizes()[0] &&
                player->GetSizes()[2] < asteroid->GetSizes()[3] && player->GetSizes()[3] > asteroid->GetSizes()[2]);
    }
    void Game::ProcessCollision(Player *player, Asteroid *asteroid) {
        _booms.push_back(new Boom(asteroid->GetCoord().x, asteroid->GetCoord().y));
        player->SetIsRendering(false);
        player->SetIsGhost(true);
        player->SetLives(player->GetLives() - 1);
        player->SetCoord(0.0f, 0.0f);
        player->SetDeadTime(std::chrono::high_resolution_clock::now());
    }
    void Game::Resize(float width, float height) {
        _aspectRatio = (float)width / (float)height;
        _halfWidth = _aspectRatio * _halfHeight;
        _width = width;
        _height = height;
        _render->Resize();
    }
    Controls *Game::GetControls() {
        return _controls;
    }
    bool Game::GetIsPaused() {
    	return _isPaused;
    }
    void Game::SetIsPaused(bool isPaused) {
    	_isPaused = isPaused;
    }
    


    Game.cpp
    #include 
    #include 
    #include 
    Game::Game() {
        isLevelRunning = true;
        ResetLogic();
        RequestRestart();
        Renderer::InitInternals();
        Controls::Init();
        Score::Init();
    }
    Game& Game::Get() {
        static Game instance;
        return instance;
    }
    void Game::Restart() {
        objects.clear();
        Score::OnRestart();
        ResetLogic();
        GameObject::Create();
        SpawnAsteroids(Constant::asteroidTargetCount);
    }
    //Внутри управляем рестартом, а наружу выдаем текущее состояние уровня (false - на паузе)
    bool Game::IsLevelRunning(float dt) {
        if(wantRestart) {
            if(restartTimer < 0.0) {
                if(isLevelRunning) {
                    Restart();
                    wantRestart = false;
                }
            } else {
                restartTimer -= dt;
            }
        }
        return isLevelRunning;
    }
    void Game::Update() {
        float deltaTime = timer.Tick();
        if(IsLevelRunning(deltaTime)) {
            for(auto& go : objects) {
                go->Update(deltaTime);
            }
            DetectCollisions(deltaTime);
            DestroyRequestedObjects(); //Удаление объектов предполагается только здесь
        }
        Renderer::Draw();
    }
    void Game::OnGLInit() {
        Renderer::InitGLContext();
    }
    void Game::OnResolutionChange(int w, int h) {
        Renderer::OnResolutionChange(w, h);
        Controls::Resize();
        Score::Resize();
    }
    GameObject& Game::AddGameObject(std::unique_ptr obj) {
        objects.push_back(std::move(obj));
        return *objects.back().get();
    }
    void Game::DestroyRequestedObjects() {
        for(auto i = objects.begin(); i != objects.end();) {
            if((*i)->isDestructionRequested()) {
                i = objects.erase(i);
            } else {
                ++i;
            }
        }
    }
    void Game::DetectCollisions(float dt) {
        for(auto a = objects.begin(); a != objects.end(); ++a) {
            for(auto b = std::next(a); b != objects.end(); ++b) { //Для каждой пары объектов
                GameObject& ra = **a;
                GameObject& rb = **b;
                if(CollisionMask(ra, rb)) { //Требуется ли обработка столкновения
                    if(DetectCollision(ra, rb, dt)) { //Базовый алгоритм
                        if(RefineCollision(ra, rb, dt)) { //Более точный
                            ra.OnCollision(rb);
                            rb.OnCollision(ra);
                        }
                    }
                }
            }
        }
    }
    bool Game::CollisionMask(const GameObject& a, const GameObject& b) {
        //Если один из объектов логически уже уничтожен, то он не сталкивается с другими (return false)
        return !(a.isDestructionRequested() || b.isDestructionRequested()) &&
        //Если ни одному из объектов не требуется обработка столкновения с другим, то возвращаем false
        (a.CollisionMask(b.getStaticType()) || b.CollisionMask(a.getStaticType()));
    }
    //Обнаружение столкновений по радиусу окружности, описывающей модель объекта
    //В непрерывном случае радиус расширяется на расстояние, которое объекты могут пройти за время dt
    bool Game::DetectCollision(const GameObject& a, const GameObject& b, float dt) {
        if(Constant::continuousCollisions) {
            return (a.getPosition() - b.getPosition()).getLength() <
                a.getRadius() + b.getRadius() + (a.getVelocity().getLength() + b.getVelocity().getLength()) * dt;
        } else {
            return (a.getPosition() - b.getPosition()).getLength() < a.getRadius() + b.getRadius();
        }
    }
    bool Game::RefineCollision(const GameObject& a, const GameObject& b, float dt) {
        if(Constant::refineCollisions) {
            const Model& am = a.getModel();
            const std::vector& av = am.getTransformed();
            const std::vector& ai = am.getIndices();
            const Vec2 aBackVel = a.getVelocity() * -1;
            const Model& bm = b.getModel();
            const std::vector& bv = bm.getTransformed();
            const std::vector& bi = bm.getIndices();
            const Vec2 bBackVel = b.getVelocity() * -1;
            //Обработка для каждой пары отрезков, из которых состоит модель объекта
            for(int i = 0; i < ai.size(); i += 2) {
                Vec2 a0(i, av, ai);
                Vec2 at = Vec2(i + 1, av, ai) - a0;
                for(int j = 0; j < bi.size(); j += 2) {
                    Vec2 b0(j, bv, bi);
                    Vec2 bt = Vec2(j + 1, bv, bi) - b0;
                    if(Constant::continuousCollisions ?
                        //Алгоритм считает, что отрезки передаются в момент времени t0,
                        //но наши отрезки уже в t0+dt, поэтому передаем скорости со знаком -
                        MovingSegmentCollision(a0, at, aBackVel, b0, bt, bBackVel, dt) :
                        SegmentCollision(a0, at, b0, bt)) return true;
                }
            }
            return false;
        } else {
            return true;
        }
    }
    bool Game::SegmentCollision(Vec2 p, Vec2 r, Vec2 q, Vec2 s) {
        //http://stackoverflow.com/a/565282/2502024
        float det = Vec2::CrossProd2D(r, s);
        if(fabs(det) > Constant::smallNumber) {
            Vec2 diff = q - p;
            float f = Vec2::CrossProd2D(diff, s / det);
            float g = Vec2::CrossProd2D(diff, r / det);
            return f >= 0 && f <= 1 && g >= 0 && g <= 1;
        }
        return false;
    }
    bool Game::MovingSegmentCollision(Vec2 p, Vec2 r, Vec2 vp, Vec2 q, Vec2 s, Vec2 vq, float dt) {
        float det = Vec2::CrossProd2D(r, s);
        if(fabs(det) > Constant::smallNumber) {
            const Vec2 v = vq - vp;
            const Vec2 diff = q - p;
            //Расширение предыдущего алгоритма с учетом:
            //q = q0 + v*t, t in [0, dt]
            //(v.x * s.y - v.y * s.x) * t + ((q.x - p.x) * s.y - (q.y - p.y) * s.x)
            //Точки пересечения f и g из SegmentCollision теперь зависят от t.
            //Отрезки пересекаются в точке t из [0, dt], если найдется такое t,
            //что f и g одновременно лежат в [0, 1]. Т.е. решаем 3 пары неравенств относительно t
            auto getInequation = [=](Vec2 dir)->std::tuple {
                //0 <= a*t + cp <= 1
                float cp = Vec2::CrossProd2D(diff, dir / det);
                float a = Vec2::CrossProd2D(v, dir / det);
                float left = -cp, right = 1 - cp;
                if(fabs(a) < Constant::smallNumber) {
                    if(cp >= 0 && cp <= 1) {
                        left = 0;
                        right = dt;
                    } else {
                        left = dt + 1;
                        right = -1;
                    }
                } else {
                    left /= a;
                    right /= a;
                    if(a < 0) std::swap(left, right);
                }
                return std::make_tuple(left, right);
            };
            float ls, rs, lr, rr;
            //ls <= t <= rs
            std::tie(ls, rs) = getInequation(s);
            //lr <= t <= rr
            std::tie(lr, rr) = getInequation(r);
            //одновременно с 0 <= t <= dt
            float mx = std::max(0.f, std::max(ls, lr));
            float mn = std::min(dt, std::min(rs, rr));
            return mx <= mn;
        }
        return false;
    }
    void Game::RequestRestart(float t) {
        wantRestart = true;
        restartTimer = t;
    }
    void Game::Pause() {
        isLevelRunning = false;
        Controls::onPause();
    }
    void Game::Resume() {
        isLevelRunning = true;
        Controls::onResume();
        timer.Tick();
    }
    void Game::SetPlayerPos(const Ship& player) {
        playerPos = player.getPosition();
    }
    Vec2 Game::GetPlayerPos() {
        return playerPos;
    }
    void Game::AddPoints(int pointsToAdd) {
        Score::AddPoints(pointsToAdd);
    }
    void Game::DecAsteroidCount(const Asteroid& a) {
        asteroidCount--;
        if(asteroidCount == Constant::asteroidUfoCount * 2 && !isUfoPresent) {
            GameObject::Create(GetUfoSpawn());
        }
        if(asteroidCount <= Constant::asteroidRespawnCount * 2) {
            SpawnAsteroids(Constant::asteroidTargetCount - Constant::asteroidRespawnCount);
        }
    }
    //Увеличиваем на 2, т.к. asteroidCount считаем по половинам большого астероида
    void Game::IncAsteroidCount(const Asteroid& a) {
        asteroidCount += 2;
    }
    void Game::ResetLogic() {
        asteroidCount = 0;
        isUfoPresent = false;
    }
    void Game::SpawnAsteroids(int n) {
        for(int i = 0; i < n; ++i) {
            GameObject::Create(GetSpawnPosition());
        }
    }
    //Создаем астероид так, чтобы сразу не убить игрока (с учетом зацикленности игровых координат)
    Transform Game::GetSpawnPosition() {
        std::uniform_real_distribution zone(-Constant::asteroidSpawnZone, Constant::asteroidSpawnZone);
        Vec2 pos(Constant::worldRatio + 0.2, 1.2);
        if(fabs(playerPos.x) > Constant::asteroidSpawnZone &&
             fabs(playerPos.y) < Constant::asteroidSpawnZone) {
            pos.y = zone(Random::generator);
        } else if(fabs(playerPos.x) < Constant::asteroidSpawnZone &&
                    fabs(playerPos.y) > Constant::asteroidSpawnZone) {
            pos.x = zone(Random::generator);
        } else {
            if(Random::flipCoin()) {
                pos.x = zone(Random::generator);
            } else {
                pos.y = zone(Random::generator);
            }
        }
        return Transform(pos);
    }
    Transform Game::GetUfoSpawn() {
        std::uniform_real_distribution zone(-Constant::ufoZone, Constant::ufoZone);
        return Transform((Constant::worldRatio + 0.12) * (playerPos.x > 0 ? -1 : 1), zone(Random::generator));
    }
    void Game::OnUfoCreated(const UFO& u) {
        isUfoPresent = true;
    }
    void Game::OnUfoDestroyed(const UFO& u) {
        isUfoPresent = false;
    }
    


    model_handler.cpp
    #include "model_handler.h"
    #include 
    #include 
    #include 
    using namespace model;
    namespace {
        const double tickTime = 40.00;
        const unsigned int asteroidNumber = 8;
        const float projectileSpeed = 10.0;
        const float smallAsteroidRadiusK =  1.5;
        const float minLargeAsteroidRadius = 35.0;
        const float maxLargeAsteroidRadius = minLargeAsteroidRadius * smallAsteroidRadiusK - 1.0;
        const float minAsteroidSpeed = 1.5;
        const float maxAsteroidSpeed = 6.5;
        const float explosionK = 50000.0;
    }
    ModelHandler::ModelHandler(float worldWidth, float worldHeight):
        _isGameOver(false),
        _worldWidth(worldWidth),
        _worldHeight(worldHeight),
        _tickTime(0),
        _ship(ShipPtr(new Ship(Point(worldWidth / 2.0, worldHeight / 2.0)))) {
        srand(time(0));
    }
    void ModelHandler::newGame() {
        _asteroids.clear();
        _projectiles.clear();
        _ship.reset(new Ship(Point(_worldWidth / 2.0, _worldHeight / 2.0)));
        _isGameOver = false;
    }
    bool ModelHandler::isGameOver() const {
        return _isGameOver;
    }
    void ModelHandler::update(double deltaTime) {
        if (this->isGameOver()) return;
        _tickTime += deltaTime;
        while (_tickTime > tickTime) {
            if (_asteroids.size() < ::asteroidNumber) {
                this->addAsteroid();
            }
            for (ObjectPtr& obj: this->allObjects()) {
                obj->move();
            }
            this->checkObjects(&_asteroids);
            this->checkObjects(&_projectiles);
            if (!this->withinBoundaries(_ship)) {
                this->removeShip();
            }
            _tickTime -= tickTime;
        }
    }
    ShipPtr ModelHandler::ship() const {
        return _ship;
    }
    void ModelHandler::removeShip() {
        _isGameOver = true;
    }
    void ModelHandler::removeAsteroid(const AsteroidPtr& asteroid) {
        if (asteroid->collisionRadius() >= ::minLargeAsteroidRadius) {
            const model::Vector v1(asteroid->velocity()
                                   + model::Vector(-asteroid->velocity().y, asteroid->velocity().x));
            const model::Vector v2(asteroid->velocity()
                                   + model::Vector(asteroid->velocity().y, -asteroid->velocity().x));
            this->addAsteroid(asteroid, v1);
            this->addAsteroid(asteroid, v2);
        }
        _asteroids.remove(asteroid);
    }
    void ModelHandler::addAsteroid(const AsteroidPtr& asteroid) {
        _asteroids.push_back(asteroid);
    }
    std::list ModelHandler::asteroids() const {
        return _asteroids;
    }
    std::list ModelHandler::projectiles() const {
        return _projectiles;
    }
    void ModelHandler::removeProjectile(const ProjectilePtr& projectile) {
        _projectiles.remove(projectile);
    }
    void ModelHandler::addProjectile() {
        Vector projectileSpeed = _ship->direction() * ::projectileSpeed + ship()->velocity();
        _projectiles.push_back(ProjectilePtr(new Projectile(_ship->point(), projectileSpeed)));
    }
    void ModelHandler::processHit(const ProjectilePtr& projectile, const AsteroidPtr& asteroid) {
        if (asteroid->collisionRadius() >= ::minLargeAsteroidRadius) {
            const Vector ox = asteroid->velocity().normaVector();
            Vector explosionVector(ox.y, -ox.x);
            if (ox * asteroid->velocity() < 0) {
                explosionVector = explosionVector * (-1.0);
            }
            const Vector v1(asteroid->velocity() + model::Vector(-asteroid->velocity().y, asteroid->velocity().x)
                            + explosionVector * (-::explosionK / asteroid->mass()));
            const Vector v2(asteroid->velocity() + model::Vector(asteroid->velocity().y, -asteroid->velocity().x)
                            + explosionVector * (::explosionK / asteroid->mass()));
            this->addAsteroid(asteroid, v1);
            this->addAsteroid(asteroid, v2);
        }
        _projectiles.remove(projectile);
        _asteroids.remove(asteroid);
    }
    std::list ModelHandler::allObjects() const {
        std::list objects;
        objects.insert(objects.end(), _asteroids.begin(), _asteroids.end());
        objects.insert(objects.end(), _projectiles.begin(), _projectiles.end());
        objects.push_back(_ship);
        return objects;
    }
    void ModelHandler::addAsteroid() {
        const float r = common::rangeRand(::minLargeAsteroidRadius, ::maxLargeAsteroidRadius);
        Point point;
        bool isCorrect = false;
        while (!isCorrect) {
            point = randPoint(r);
            isCorrect = true;
            for (const AsteroidPtr& asteroid : _asteroids) {
                const float d = model::distance(asteroid->point(), point);
                if (d < (asteroid->collisionRadius() + r)) {
                    isCorrect = false;
                    break;
                }
            }
        }
        const float distToCenter = ::distance(point.x, point.y, _worldWidth / 2.0, _worldHeight / 2.0);
        const float vx = (_worldWidth / 2.0 - point.x) / distToCenter
                * common::rangeRand(::minAsteroidSpeed, ::maxAsteroidSpeed);
        const float vy = (_worldHeight / 2.0 - point.y) / distToCenter
                * common::rangeRand(::minAsteroidSpeed, ::maxAsteroidSpeed);
        _asteroids.push_back(AsteroidPtr(new Asteroid(point, r, Vector(vx, vy))));
    }
    void ModelHandler::addAsteroid(const AsteroidPtr& oldAsteroid, const Vector& newVelocity) {
        Point point = oldAsteroid->point();
        point.move(newVelocity.normaVector() * oldAsteroid->collisionRadius());
        _asteroids.push_back(AsteroidPtr(new Asteroid(point, oldAsteroid->collisionRadius()
                                                      / ::smallAsteroidRadiusK, newVelocity)));
    }
    Point ModelHandler::randPoint(float r) const {
        float x = 0;
        float y = 0;
        switch (rand() % 4) {
        case 0:
            x = common::rangeRand(0 - r, _worldWidth + r);
            y = 0 - r;
            break;
        case 1:
            x = common::rangeRand(0 - r, _worldWidth + r);
            y = _worldHeight - r;
            break;
        case 2:
            x = 0 - r;
            y = common::rangeRand(0 - r, _worldHeight + r);
            break;
        case 3:
            x = _worldWidth + r;
            y = common::rangeRand(0 - r, _worldHeight + r);
            break;
        default:
            break;
        }
        return Point(x, y);
    }
    bool ModelHandler::withinBoundaries(const ObjectPtr& obj) const {
        return (obj->x() > (0 - _worldWidth * 0.1) && obj->x() < (_worldWidth * 1.1)
                && obj->y() > (0 - _worldHeight * 0.1) && obj->y() < (_worldHeight * 1.1));
    }
    template void ModelHandler::checkObjects(std::list* objects) {
        typename std::list::iterator it = objects->begin();
        while (it != objects->end())     {
            if (!this->withinBoundaries(*it)) {
                it = objects->erase(it++);
            } else {
                ++it;
            }
        }
    }
    



    In my opinion, among the listed test tasks, only one stands out a bit. We invited all authors for an interview. Can you guess how one candidate was different from the rest? For me it is almost obvious when I see the next test, but my friends do not believe it.

    Also popular now: