├── .gitignore ├── .gitmodules ├── README.md ├── core ├── app.js ├── ball.cpp ├── ball.h ├── cell.cpp ├── cell.h ├── food.cpp ├── food.h ├── gameinterface.cpp ├── gameinterface.h ├── player.cpp ├── player.h ├── qwebchannel.js ├── shared.pri ├── virus.cpp └── virus.h ├── eatem-server ├── authentication.h ├── eatem-server.pro ├── game.cpp ├── game.h ├── main.cpp ├── websockettransport.cpp └── websockettransport.h ├── eatem-standalone ├── eatem-standalone.pro ├── main.cpp ├── main.qml └── qml.qrc ├── eatem-web ├── audio │ ├── spawn.mp3 │ └── split.mp3 ├── css │ └── main.css ├── eatem_web.pro ├── img │ ├── feed.png │ └── split.png ├── index.html └── js │ ├── app.js │ ├── global.js │ ├── lib │ ├── canvas.js │ └── jquery.js │ └── require.js └── eatem ├── eatem.pro ├── main.cpp ├── main.qml └── qml.qrc /.gitignore: -------------------------------------------------------------------------------- 1 | # C++ objects and libs 2 | 3 | *.slo 4 | *.lo 5 | *.o 6 | *.a 7 | *.la 8 | *.lai 9 | *.so 10 | *.dll 11 | *.dylib 12 | 13 | # Qt-es 14 | 15 | /.qmake.cache 16 | /.qmake.stash 17 | *.pro.user 18 | *.pro.user.* 19 | *.qbs.user 20 | *.qbs.user.* 21 | *.moc 22 | moc_*.cpp 23 | moc_*.h 24 | qrc_*.cpp 25 | ui_*.h 26 | Makefile* 27 | *build-* 28 | 29 | # QtCreator 30 | 31 | *.autosave 32 | 33 | # QtCtreator Qml 34 | *.qmlproject.user 35 | *.qmlproject.user.* 36 | 37 | # QtCtreator CMake 38 | CMakeLists.txt.user* 39 | 40 | # project specific 41 | *.png 42 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "qtwebchannel"] 2 | path = qtwebchannel 3 | url = https://github.com/benhoff/qtwebchannel 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Qt5 Cross Platform Applications 2 | 3 | Course being developed with Packt Publishing showcasing the portability of Qt5 to do Cross Platform development by building out and distributing an Agar.io clone! 4 | 5 | ## Installation 6 | 7 | Unfortunately, Qt [has a bug](https://bugreports.qt.io/browse/QTBUG-70078) that causes some stale cache issues. 8 | 9 | To clone the patched version of QtWebChannel in. 10 | 11 | ```bash 12 | $ git clone https://github.com/PacktPublishing/Qt5-Cross-Platform-Application-Development 13 | $ git submodule init 14 | $ git submodule update 15 | ``` 16 | 17 | Now to compile the patched QtWebChannel 18 | 19 | ```bash 20 | $ cd qtwebchannel 21 | $ qmake 22 | $ make 23 | ``` 24 | 25 | Build `eatem-server` and then use the LD_PRELOAD to point directly to your patched WebChannel library rather than the system version. 26 | 27 | ```bash 28 | $ LD_PRELOAD=/absolute/path/here/qtwebchannel/lib/libQt5WebChannel.so.5 ./server 29 | ``` 30 | -------------------------------------------------------------------------------- /core/app.js: -------------------------------------------------------------------------------- 1 | function _draw_circle(context, centerX, centerY, radius, sides) { 2 | context.beginPath(); 3 | 4 | for (var i = 0; i < sides; i++) { 5 | var theta = (i / sides) * 2 * Math.PI; 6 | var x = centerX + radius * Math.sin(theta); 7 | var y = centerY + radius * Math.cos(theta); 8 | context.lineTo(x, y); 9 | } 10 | 11 | context.closePath(); 12 | context.stroke(); 13 | context.fill(); 14 | } 15 | 16 | function draw_objects(context, feed, players, viruses, this_player, window_width, window_height) 17 | { 18 | draw_grid(context, this_player, window_width, window_height); 19 | draw_food(context, feed, this_player, window_width, window_height); 20 | draw_players(context, players, this_player, window_width, window_height); 21 | draw_viruses(context, viruses, this_player, window_width, window_height); 22 | } 23 | 24 | function translate(object, this_player) 25 | { 26 | var relative_x = object.x + (width/2); 27 | var relative_y = object.y + (height/2); 28 | // var scaled_x = relative_x * this_player.zoom_factor; 29 | // var scaled_y = relative_y * this_player.zoom_factor; 30 | var scaled_x = relative_x; 31 | var scaled_y = relative_y; 32 | relative_x = scaled_x - this_player.x; 33 | relative_y = scaled_y - this_player.y; 34 | return [relative_x, relative_y]; 35 | } 36 | 37 | function draw_grid(context, this_player, width, height) 38 | { 39 | context.lineWidth = 1; 40 | context.strokeStyle = "#000000" 41 | context.globalAlpha = 0.15 42 | // Left-vertical 43 | if (this_player.x <= width/2) { 44 | context.beginPath(); 45 | context.moveTo(width/2 - this_player.x, 0 ? this_player.y > height/2 : height/2 - this_player.y); 46 | context.lineTo(width/2 - this_player.x, 1000 + height/2 - this_player.y); 47 | context.stroke(); 48 | } 49 | 50 | // Top-horizontal. 51 | if (this_player.y <= height/2) { 52 | context.beginPath(); 53 | context.moveTo(0 ? this_player.x > width/2 : width/2 - this_player.x,height/2 - this_player.y); 54 | context.lineTo(1000 + width/2 - this_player.x, height/2 - this_player.y); 55 | context.stroke(); 56 | } 57 | 58 | // Right-vertical. 59 | if (1000 - this_player.x <= width/2) { 60 | context.beginPath(); 61 | context.moveTo(1000+ width/2 - this_player.x, 62 | height/2 - this_player.y); 63 | context.lineTo(1000+ width/2 - this_player.x, 64 | 1000+ height/2 - this_player.y); 65 | context.stroke(); 66 | } 67 | 68 | // Bottom-horizontal. 69 | if (1000 - this_player.y <= height/2) { 70 | context.beginPath(); 71 | context.moveTo(1000 + width/2 - this_player.x, 72 | 1000 + height/2 - this_player.y); 73 | context.lineTo(width/2 - this_player.x, 74 | 1000 + height/2 - this_player.y); 75 | context.stroke(); 76 | } 77 | 78 | context.stroke(); 79 | context.globalAlpha = 1; 80 | } 81 | 82 | function draw_viruses(context, viruses, this_player, width, height) 83 | { 84 | for (var i = 0; i < viruses.length; i++) 85 | { 86 | var virus = viruses[i]; 87 | var radius = virus.radius 88 | var x_y = translate(virus, this_player) 89 | 90 | var x = x_y[0]; 91 | var y = x_y[1]; 92 | 93 | if (x > width + radius || x < 0 - radius) 94 | continue; 95 | if (y > height + radius || y < 0 - radius) 96 | continue; 97 | 98 | context.fillStyle = "#33ff33" 99 | context.lineWidth = 10 100 | context.strokeStyle = "#19D119" 101 | _draw_circle(context, x, y, virus.radius, 12); 102 | } 103 | 104 | } 105 | 106 | function draw_food(context, feed, this_player, width, height) 107 | { 108 | for (var i = 0; i < feed.length; i++) 109 | { 110 | var food = feed[i]; 111 | var x_y = translate(food, this_player); 112 | 113 | var x = x_y[0]; 114 | var y = x_y[1]; 115 | if (x > width || x < 0) 116 | continue; 117 | if (y > height || y < 0) 118 | continue; 119 | context.beginPath(); 120 | context.fillStyle = food.hue; 121 | context.arc(x, 122 | y, 123 | food.radius, 0, 2*Math.PI); 124 | 125 | context.fill(); 126 | } 127 | } 128 | 129 | function _draw_player_cells_helper(context, player, this_player, width, height) 130 | { 131 | for (var cell_number=0; cell_number < player.cells.length; cell_number++) 132 | { 133 | var cell = player.cells[cell_number]; 134 | var x_y = translate(cell, this_player, true); 135 | var x = x_y[0]; 136 | var y = x_y[1]; 137 | if (x > width || x < 0) 138 | continue; 139 | if (y > height || y < 0) 140 | continue; 141 | context.beginPath(); 142 | context.arc(x_y[0], 143 | x_y[1], 144 | cell.radius, 145 | 0, 2*Math.PI); 146 | 147 | context.fill(); 148 | } 149 | } 150 | 151 | function draw_players(context, players, this_player, width, height) 152 | { 153 | for (var i=0; i < players.length; i++) 154 | { 155 | var player = players[i]; 156 | context.fillStyle = player.hue; 157 | _draw_player_cells_helper(context, player, this_player, width, height) 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /core/ball.cpp: -------------------------------------------------------------------------------- 1 | #include "ball.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | Ball::Ball(QRect *game_size, QVector2D initial_velocity, QPoint initial_position, qreal mass, QObject *parent) 9 | : QObject(parent) 10 | , _game_size(game_size) 11 | , _position(initial_position) 12 | , _mass(mass) 13 | , _velocity_ticks(-1) 14 | , _initial_velocity(initial_velocity) 15 | { 16 | } 17 | 18 | Ball::Ball(QRect *game_size, qreal mass, QObject *parent) 19 | : QObject(parent) 20 | , _game_size(game_size) 21 | , _mass(mass) 22 | , _velocity_ticks(-1) 23 | { 24 | } 25 | 26 | Ball::Ball(const Ball &old_ball) 27 | : QObject(old_ball.parent()) 28 | , _game_size(old_ball.game_size()) 29 | , _position(old_ball.position()) 30 | , _mass(old_ball.mass()) 31 | , _velocity_ticks(-1) 32 | , _initial_velocity(old_ball.initial_velocity()) 33 | { 34 | } 35 | 36 | QRect* Ball::game_size() const 37 | { 38 | return _game_size; 39 | } 40 | 41 | void Ball::request_coordinates(QPoint mouse_position) 42 | { 43 | if (abs(mouse_position.x()- x()) <= 1 && abs(mouse_position.y()-y()) <= 1) 44 | return; 45 | 46 | QPoint target_point = mouse_position - _position; 47 | QVector2D target = QVector2D(target_point).normalized(); 48 | 49 | if (_velocity_ticks > 0) { 50 | target += _initial_velocity; 51 | _velocity_ticks -= 1; 52 | } 53 | else { 54 | target *= speed(); 55 | } 56 | 57 | move(target); 58 | 59 | } 60 | 61 | void Ball::request_coordinates(QPoint mouse_position, Ball *touching_ball) 62 | { 63 | QPoint target_point = mouse_position - _position; 64 | QVector2D to_target = QVector2D(target_point); 65 | QVector2D target_normalized = to_target.normalized(); 66 | 67 | QPoint collision = _position - touching_ball->position(); 68 | 69 | QVector2D collision_normal = QVector2D(collision).normalized(); 70 | float into_collision_contribution; 71 | 72 | into_collision_contribution = qMin(QVector2D::dotProduct(target_normalized, collision_normal), (float) 0); 73 | collision_normal *= into_collision_contribution; 74 | 75 | QVector2D to_target_non_collide = target_normalized - collision_normal; 76 | 77 | if (_velocity_ticks > 0) { 78 | to_target_non_collide += _initial_velocity; 79 | _velocity_ticks -= 1; 80 | } 81 | else { 82 | to_target_non_collide *= speed(); 83 | } 84 | 85 | // FIXME: add in some sort of clamp down to 3. 86 | // or could add in as an acceleration, to avoid the zig-zag 87 | move(to_target_non_collide); 88 | } 89 | 90 | void Ball::request_coordinates(QPoint mouse_position, QList touching_balls) 91 | { 92 | QPoint target_calculation = mouse_position - _position; 93 | QVector2D to_target = QVector2D(target_calculation).normalized(); 94 | QVector2D to_target_non_collide(to_target); 95 | 96 | for (Ball *touching_ball : touching_balls) { 97 | QPoint collision_calc = _position - touching_ball->position(); 98 | QVector2D collision_normal = QVector2D(collision_calc).normalized(); 99 | float into_collision_contribution; 100 | into_collision_contribution = qMin(QVector2D::dotProduct(to_target, collision_normal), (float) 0); 101 | collision_normal *= into_collision_contribution; 102 | to_target_non_collide -= collision_normal; 103 | } 104 | 105 | if (_velocity_ticks > 0) { 106 | to_target_non_collide += _initial_velocity; 107 | _velocity_ticks -= 1; 108 | } 109 | else { 110 | to_target_non_collide *= speed(); 111 | } 112 | 113 | move(to_target_non_collide); 114 | } 115 | 116 | void Ball::add_mass(qreal mass) 117 | { 118 | _mass += mass; 119 | emit radius_changed(); 120 | } 121 | 122 | void Ball::remove_mass(qreal mass) 123 | { 124 | _mass -= mass; 125 | emit radius_changed(); 126 | } 127 | 128 | void Ball::set_mass(qreal mass) 129 | { 130 | _mass = mass; 131 | emit radius_changed(); 132 | } 133 | 134 | void Ball::move() 135 | { 136 | if (_velocity_ticks < 0) 137 | return; 138 | 139 | _position.setX(_position.x() + _initial_velocity.x()); 140 | _position.setY(_position.y() + _initial_velocity.y()); 141 | 142 | _velocity_ticks--; 143 | validate_coordinates(); 144 | } 145 | 146 | void Ball::move(QVector2D distance) 147 | { 148 | QPoint move(distance.x(), distance.y()); 149 | _position += move; 150 | validate_coordinates(); 151 | } 152 | 153 | qreal Ball::speed() 154 | { 155 | return qMax(_initial_player_speed * qPow(_mass / 2827.43, -0.439), 3.); 156 | 157 | } 158 | 159 | void Ball::validate_coordinates() 160 | { 161 | // Check to see if we're within the game width 162 | if (_position.x() < 0) 163 | _position.setX(0); 164 | // Check to see if we're within the game width 165 | else if (_position.x() > _game_size->width()) 166 | _position.setX(_game_size->width()); 167 | 168 | // Check to see if we're within the game height 169 | if (_position.y() < 0) 170 | _position.setY(0); 171 | // Check to see if we're within the game height 172 | else if (_position.y() > _game_size->height()) 173 | _position.setY(_game_size->height()); 174 | 175 | emit x_changed(); 176 | emit y_changed(); 177 | } 178 | 179 | QPoint Ball::position() const 180 | { 181 | return _position; 182 | } 183 | 184 | int Ball::x() const 185 | { 186 | return _position.x(); 187 | } 188 | 189 | bool Ball::is_touching(Ball *other) 190 | { 191 | 192 | int radiuses = qPow(other->radius()+ radius(), 2); 193 | int diff_x = qPow(_position.x() - other->x(), 2); 194 | int diff_y = qPow(_position.y() - other->y(), 2); 195 | 196 | return radiuses > diff_x + diff_y; 197 | } 198 | 199 | int Ball::y() const 200 | { 201 | return _position.y(); 202 | } 203 | 204 | qreal Ball::mass() const 205 | { 206 | return _mass; 207 | } 208 | 209 | 210 | qreal Ball::radius() const 211 | { 212 | return qSqrt(_mass/M_PI); 213 | } 214 | 215 | QVector2D Ball::initial_velocity() const 216 | { 217 | return _initial_velocity; 218 | } 219 | 220 | int Ball::velocity_ticks() const 221 | { 222 | return _velocity_ticks; 223 | } 224 | 225 | void Ball::set_initial_velocity(QVector2D velocity) 226 | { 227 | _initial_velocity = velocity; 228 | } 229 | 230 | void Ball::set_coordinates_random() 231 | { 232 | QRandomGenerator random = QRandomGenerator::securelySeeded(); 233 | _position.setX(random.bounded(_game_size->width())); 234 | _position.setY(random.bounded(_game_size->height())); 235 | } 236 | 237 | void Ball::set_velocity_ticks(int ticks) 238 | { 239 | _velocity_ticks = ticks; 240 | } 241 | -------------------------------------------------------------------------------- /core/ball.h: -------------------------------------------------------------------------------- 1 | #ifndef BALL_H 2 | #define BALL_H 3 | 4 | #include 5 | #include 6 | 7 | 8 | class QTimerEvent; 9 | 10 | 11 | class Ball : public QObject 12 | { 13 | Q_OBJECT 14 | Q_PROPERTY(int x READ x NOTIFY x_changed) 15 | Q_PROPERTY(int y READ y NOTIFY y_changed) 16 | Q_PROPERTY(int radius READ radius NOTIFY radius_changed) 17 | 18 | public: 19 | Ball(QRect *game_size, 20 | QVector2D initial_velocity, 21 | QPoint initial_position, 22 | qreal mass, 23 | QObject *parent = nullptr); 24 | 25 | explicit Ball(QRect *game_size, qreal mass, QObject *parent = nullptr); 26 | explicit Ball(const Ball&); 27 | 28 | int x() const; 29 | int y() const; 30 | qreal radius() const; 31 | qreal mass() const; 32 | QPoint position() const; 33 | QRect* game_size() const; 34 | QVector2D initial_velocity() const; 35 | int velocity_ticks() const; 36 | void set_initial_velocity(QVector2D velocity); 37 | void set_coordinates_random(); 38 | 39 | void request_coordinates(QPoint mouse_position); 40 | void request_coordinates(QPoint mouse_position, Ball* touching_ball); 41 | void request_coordinates(QPoint mouse_position, QList touching_balls); 42 | 43 | void add_mass(qreal mass); 44 | void remove_mass(qreal mass); 45 | void set_mass(qreal mass); 46 | void move(); 47 | 48 | void set_velocity_ticks(int ticks); 49 | bool is_touching(Ball *other); 50 | 51 | protected: 52 | void validate_coordinates(); 53 | void move(QVector2D distance); 54 | 55 | static constexpr qreal _initial_player_speed = 6.; 56 | qreal speed(); 57 | 58 | 59 | signals: 60 | void x_changed(); 61 | void y_changed(); 62 | void radius_changed(); 63 | 64 | private: 65 | QRect *_game_size; 66 | QPoint _position; 67 | qreal _mass; 68 | 69 | int _velocity_ticks; 70 | QVector2D _initial_velocity; 71 | }; 72 | 73 | #endif // BALL_H 74 | -------------------------------------------------------------------------------- /core/cell.cpp: -------------------------------------------------------------------------------- 1 | #include "cell.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "player.h" 9 | #include "food.h" 10 | #include "ball.h" 11 | 12 | Cell::Cell(QRect *game_size, QObject *parent) 13 | : QObject(parent) 14 | , _ball_properties(new Ball(game_size, Cell::initial_mass, this)) 15 | { 16 | _ball_properties->set_coordinates_random(); 17 | _connect_ball_property_signals(); 18 | } 19 | 20 | Cell::Cell(Ball *ball_properties, QObject *parent) 21 | : QObject(parent) 22 | , _ball_properties(ball_properties) 23 | { 24 | _connect_ball_property_signals(); 25 | _ball_properties->setParent(this); 26 | } 27 | 28 | void Cell::_connect_ball_property_signals() 29 | { 30 | connect(_ball_properties, &Ball::x_changed, this, &Cell::x_changed); 31 | connect(_ball_properties, &Ball::y_changed, this, &Cell::y_changed); 32 | connect(_ball_properties, &Ball::radius_changed, this, &Cell::radius_changed); 33 | } 34 | 35 | qreal Cell::radius() 36 | { 37 | return _ball_properties->radius(); 38 | } 39 | 40 | void Cell::add_mass(qreal amount) 41 | { 42 | _ball_properties->add_mass(amount); 43 | } 44 | 45 | qreal Cell::mass() 46 | { 47 | return _ball_properties->mass(); 48 | } 49 | 50 | int Cell::x() 51 | { 52 | return _ball_properties->x(); 53 | } 54 | 55 | int Cell::y() 56 | { 57 | return _ball_properties->y(); 58 | } 59 | 60 | void Cell::eat_food(Food *food) 61 | { 62 | _ball_properties->add_mass(food->mass()); 63 | } 64 | 65 | void Cell::request_coordinates(QPoint mouse_position) 66 | { 67 | _ball_properties->request_coordinates(mouse_position); 68 | } 69 | 70 | void Cell::request_coordinates(QPoint target_position, Ball *touching_ball) 71 | { 72 | _ball_properties->request_coordinates(target_position, touching_ball); 73 | } 74 | 75 | QPoint Cell::position() 76 | { 77 | return _ball_properties->position(); 78 | } 79 | 80 | 81 | Ball *Cell::ball_properties() 82 | { 83 | return _ball_properties; 84 | } 85 | 86 | void Cell::set_mass(qreal mass) 87 | { 88 | _ball_properties->set_mass(mass); 89 | } 90 | 91 | void Cell::move() 92 | { 93 | _ball_properties->move(); 94 | } 95 | 96 | void Cell::request_coordinates(QPoint position, QList touching_cells) 97 | { 98 | if (touching_cells.isEmpty()) 99 | return request_coordinates(position); 100 | 101 | QList touching_balls; 102 | 103 | for (Cell* cell : touching_cells) 104 | touching_balls.append(cell->ball_properties()); 105 | 106 | return _ball_properties->request_coordinates(position, touching_balls); 107 | } 108 | 109 | bool Cell::is_touching(Ball *other) 110 | { 111 | return _ball_properties->is_touching(other); 112 | } 113 | 114 | QPointer Cell::request_split(QPoint mouse_position) 115 | { 116 | QPointer result; 117 | int two_times_intial = 2 * Cell::initial_mass; 118 | if (_ball_properties->mass() > two_times_intial) 119 | { 120 | QPoint target_point = mouse_position - _ball_properties->position(); 121 | 122 | QVector2D target = QVector2D(target_point).normalized(); 123 | 124 | // QVector2D normalized_target(target); 125 | target *= 10; 126 | 127 | qreal new_mass = _ball_properties->mass()/2; 128 | 129 | Ball *new_ball = new Ball(*_ball_properties); 130 | new_ball->set_initial_velocity(target); 131 | new_ball->set_mass(new_mass); 132 | new_ball->set_velocity_ticks(30); 133 | 134 | // FIXME: think about pushing cell half the radius to the right 135 | Cell *split_cell = new Cell(new_ball, parent()); 136 | 137 | // FIXME: verify this works without, see line 118 138 | // _position += normalized_target; 139 | 140 | // QVector2D start_position, QVector2D velocity, qreal mass, QRect *game_size, QPlayer *owning_player 141 | result = split_cell; 142 | _ball_properties->set_mass(new_mass); 143 | } 144 | 145 | return result; 146 | } 147 | 148 | QPointer Cell::request_fire_food(QPoint mouse_position) 149 | { 150 | QPointer result; 151 | qreal requested_mass = Food::initial_mass * 20; 152 | if (_ball_properties->mass() - requested_mass > Cell::initial_mass) 153 | { 154 | QVector2D to_target = QVector2D(mouse_position - _ball_properties->position()).normalized(); 155 | QVector2D start(to_target); 156 | start *= radius(); 157 | start *= 1.3; 158 | 159 | // Need to put the new food starting position outside of the 160 | // current cell 161 | QPoint starting_position(_ball_properties->x() + start.x(), _ball_properties->y() + start.y()); 162 | to_target *= 10; 163 | 164 | Ball *new_ball = new Ball(ball_properties()->game_size(), to_target, starting_position, requested_mass); 165 | new_ball->set_velocity_ticks(30); 166 | 167 | Food *new_food = new Food(new_ball); 168 | result = new_food; 169 | _ball_properties->remove_mass(requested_mass); 170 | } 171 | 172 | return result; 173 | } 174 | -------------------------------------------------------------------------------- /core/cell.h: -------------------------------------------------------------------------------- 1 | #ifndef CELL_H 2 | #define CELL_H 3 | 4 | #include 5 | #include 6 | 7 | 8 | class Food; 9 | class Ball; 10 | 11 | 12 | class Cell : public QObject 13 | { 14 | Q_OBJECT 15 | Q_PROPERTY(int x READ x NOTIFY x_changed) 16 | Q_PROPERTY(int y READ y NOTIFY y_changed) 17 | Q_PROPERTY(int radius READ radius NOTIFY radius_changed) 18 | 19 | public: 20 | explicit Cell(QRect *game_size, QObject *parent); 21 | explicit Cell(Ball *ball_properties, QObject *parent); 22 | static constexpr qreal initial_mass = 2827.43; 23 | 24 | void add_mass(qreal amount); 25 | 26 | void request_coordinates(QPoint mouse_position); 27 | void request_coordinates(QPoint target_position, Ball* touching_ball); 28 | void request_coordinates(QPoint position, QList touching_cells); 29 | 30 | bool is_touching(Ball *other); 31 | 32 | // NOTE: Could probably make a typedef of this 33 | QPointer request_split(QPoint mouse_position); 34 | QPointer request_fire_food(QPoint mouse_position); 35 | void set_mass(qreal mass); 36 | 37 | void move(); 38 | 39 | // Getters 40 | int x(); 41 | int y(); 42 | void eat_food(Food* food); 43 | qreal radius(); 44 | qreal mass(); 45 | QPoint position(); 46 | Ball* ball_properties(); 47 | 48 | 49 | protected: 50 | void _connect_ball_property_signals(); 51 | 52 | signals: 53 | void x_changed(); 54 | void y_changed(); 55 | void radius_changed(); 56 | 57 | private: 58 | Ball *_ball_properties; 59 | }; 60 | 61 | #endif // CELL_H 62 | -------------------------------------------------------------------------------- /core/food.cpp: -------------------------------------------------------------------------------- 1 | #include "food.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "ball.h" 8 | 9 | Food::Food(QRect *game_size, QObject *parent) 10 | : QObject(parent) 11 | , _ball_properties(new Ball(game_size, Food::initial_mass, this)) 12 | { 13 | _ball_properties->set_coordinates_random(); 14 | _connect_ball_property_signals(); 15 | } 16 | 17 | Food::Food(Ball* ball_properties, QObject *parent) 18 | : QObject(parent) 19 | , _ball_properties(ball_properties) 20 | 21 | { 22 | _connect_ball_property_signals(); 23 | _ball_properties->set_velocity_ticks(30); 24 | _ball_properties->setParent(this); 25 | } 26 | 27 | void Food::_connect_ball_property_signals() 28 | { 29 | connect(_ball_properties, &Ball::x_changed, this, &Food::x_changed); 30 | connect(_ball_properties, &Ball::y_changed, this, &Food::y_changed); 31 | } 32 | 33 | QPoint Food::position() 34 | { 35 | return _ball_properties->position(); 36 | } 37 | 38 | int Food::x() 39 | { 40 | return _ball_properties->x(); 41 | } 42 | 43 | int Food::y() 44 | { 45 | return _ball_properties->y(); 46 | } 47 | 48 | qreal Food::mass() 49 | { 50 | return _ball_properties->mass(); 51 | } 52 | 53 | 54 | qreal Food::radius() 55 | { 56 | return _ball_properties->radius(); 57 | } 58 | 59 | Ball *Food::ball_properties() 60 | { 61 | return _ball_properties; 62 | } 63 | 64 | void Food::move() 65 | { 66 | _ball_properties->move(); 67 | } 68 | -------------------------------------------------------------------------------- /core/food.h: -------------------------------------------------------------------------------- 1 | #ifndef FOOD_H 2 | #define FOOD_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | 9 | class QRect; 10 | class Ball; 11 | 12 | 13 | class Food : public QObject 14 | { 15 | Q_OBJECT 16 | Q_PROPERTY(int x READ x NOTIFY x_changed) 17 | Q_PROPERTY(int y READ y NOTIFY y_changed) 18 | Q_PROPERTY(int radius READ radius CONSTANT) 19 | 20 | public: 21 | // Default constructor, uses `game_size` to ensure creation in the game 22 | explicit Food(QRect *game_size, QObject *parent = nullptr); 23 | // Constructor used when players `fire` off, or create food from themselves 24 | explicit Food(Ball* ball_properties, QObject *parent = nullptr); 25 | 26 | // the standard mass used in creation 27 | static constexpr qreal initial_mass = 78.54; 28 | // create a "new" instance of food. Re-enables the food if it's been 29 | // disabled (consumed) 30 | void generate(); 31 | 32 | int x(); 33 | int y(); 34 | qreal radius(); 35 | qreal mass(); 36 | QPoint position(); 37 | QVector2D intial_velocity(); 38 | Ball *ball_properties(); 39 | 40 | void move(); 41 | 42 | signals: 43 | void x_changed(); 44 | void y_changed(); 45 | void enabled_changed(); 46 | 47 | protected: 48 | void _connect_ball_property_signals(); 49 | 50 | private: 51 | Ball *_ball_properties; 52 | }; 53 | 54 | #endif // FOOD_H 55 | -------------------------------------------------------------------------------- /core/gameinterface.cpp: -------------------------------------------------------------------------------- 1 | #include "gameinterface.h" 2 | #include "player.h" 3 | #include "virus.h" 4 | #include "food.h" 5 | #include "cell.h" 6 | #include 7 | #include 8 | 9 | 10 | // Need to declare these pointers as Qt metatypes using the 11 | // `Q_DECLARE_METATYPE` macro so that we can add them into `QVariants`. 12 | // We need to add them into `QVaraint` so that we can pass them to QML 13 | // in a `QVariantList`. `QVariantList` works as a list in JavaScript 14 | 15 | Q_DECLARE_METATYPE(Virus *) 16 | Q_DECLARE_METATYPE(Food *) 17 | Q_DECLARE_METATYPE(Player *) 18 | // NOTE: `QVaraint` will NOT take object values, hence the use of pointers here 19 | 20 | GameInterface::GameInterface(QObject *parent) 21 | : QObject(parent) 22 | , _game_size(new QRect()) 23 | { 24 | _game_interval.setInterval(50); 25 | connect(&_game_interval, &QTimer::timeout, this, &GameInterface::increment_game_step); 26 | } 27 | 28 | void GameInterface::set_game_size(int width, int height) 29 | { 30 | _game_size->setHeight(height); 31 | _game_size->setWidth(width); 32 | } 33 | 34 | void GameInterface::create_viruses(int number) 35 | { 36 | for(int i=0; i < number; i++) { 37 | Virus *virus = new Virus(_game_size, this); 38 | _viruses.append(QVariant::fromValue(virus)); 39 | } 40 | } 41 | 42 | void GameInterface::create_game_objects() 43 | { 44 | // create_food(40); 45 | create_food(); 46 | create_viruses(5); 47 | emit food_changed(); 48 | emit viruses_changed(); 49 | } 50 | 51 | 52 | void GameInterface::create_food(int number) 53 | { 54 | for(int i=0; i(food)); 58 | } 59 | } 60 | 61 | QVariantList GameInterface::food() 62 | { 63 | return _food; 64 | } 65 | 66 | QVariantList GameInterface::viruses() 67 | { 68 | return _viruses; 69 | } 70 | 71 | Player* GameInterface::get_player(QString authentication) 72 | { 73 | for (QVariant player_variant : _players) 74 | { 75 | Player *player = player_variant.value(); 76 | if (player->authentication() == authentication) 77 | { 78 | return player; 79 | } 80 | } 81 | return nullptr; 82 | } 83 | 84 | 85 | QVariantList GameInterface::players() 86 | { 87 | return _players; 88 | } 89 | 90 | void GameInterface::increment_game_step() 91 | { 92 | check_game_object_interactions(); 93 | } 94 | 95 | void GameInterface::remove_player(Player *player) 96 | { 97 | _players.removeOne(QVariant::fromValue(player)); 98 | player->deleteLater(); 99 | emit players_changed(); 100 | } 101 | 102 | bool GameInterface::_check_player_interactions(Food *food) 103 | { 104 | // For each Player QVariant in our QVariantList `_players`... 105 | for(QVariant player_variant : _players){ 106 | // cast each player variant into into a `Player` pointer 107 | Player *player = player_variant.value(); 108 | 109 | for (QVariant cell_variant : player->cells()) 110 | { 111 | Cell *cell = cell_variant.value(); 112 | if (cell->is_touching(food->ball_properties())) 113 | { 114 | cell->eat_food(food); 115 | food->deleteLater(); 116 | return true; 117 | } 118 | } 119 | } 120 | return false; 121 | } 122 | 123 | bool GameInterface::_check_virus_interactions(Food *food) 124 | { 125 | for (QVariant virus_variant : _viruses) { 126 | Virus *virus = virus_variant.value(); 127 | virus->move(); 128 | 129 | if (virus->is_touching(food->ball_properties())) 130 | { 131 | virus->eat_food(food); 132 | food->deleteLater(); 133 | return true; 134 | } 135 | } 136 | 137 | return false; 138 | } 139 | 140 | void GameInterface::check_game_object_interactions() 141 | { 142 | QMutableListIterator i(_food); 143 | while (i.hasNext()) { 144 | QVariant food_variant = i.next(); 145 | Food *food = food_variant.value(); 146 | // NOTE: `_check_virus_interactions` calls `Virus::move()` 147 | bool food_disabled = _check_virus_interactions(food); 148 | 149 | if (food_disabled) 150 | { 151 | i.remove(); 152 | emit food_changed(); 153 | continue; 154 | } 155 | 156 | food_disabled = _check_player_interactions(food); 157 | 158 | if (food_disabled) 159 | { 160 | i.remove(); 161 | emit food_changed(); 162 | } 163 | else 164 | food->move(); 165 | } 166 | 167 | // For each Player QVariant in our QVariantList `_players`... 168 | for(QVariant player_variant : _players) 169 | { 170 | // cast each player variant into into a `Player` pointer 171 | Player *player = player_variant.value(); 172 | 173 | // Now iterate through every virus variant 174 | for (QVariant virus_variant: _viruses) 175 | { 176 | // cast the virius variant into into a `Virus` pointer 177 | Virus *virus = virus_variant.value(); 178 | player->handle_touch(virus); 179 | } 180 | 181 | // Now iterate through every other player variant 182 | for (QVariant other_player_variant : _players) 183 | { 184 | // cast the other player variant into into a `Player` pointer 185 | Player *other_player = other_player_variant.value(); 186 | if (player == other_player) 187 | continue; 188 | player->handle_touch(other_player); 189 | } 190 | 191 | player->move(); 192 | } 193 | } 194 | 195 | void GameInterface::track_food_fired_by_players(Food *food) 196 | { 197 | QVariant food_variant = QVariant::fromValue(food); 198 | _food.append(food_variant); 199 | emit food_changed(); 200 | emit new_food(food_variant); 201 | } 202 | 203 | void GameInterface::track_new_virus(Virus *virus) 204 | { 205 | QVariant virus_variant = QVariant::fromValue(virus); 206 | _viruses.append(virus_variant); 207 | viruses_changed(); 208 | emit new_virus(virus_variant); 209 | } 210 | 211 | void GameInterface::remove_virus_from_game(Virus *virus) 212 | { 213 | _viruses.removeOne(QVariant::fromValue(virus)); 214 | virus->deleteLater(); 215 | emit viruses_changed(); 216 | } 217 | 218 | QRect *GameInterface::game_size() 219 | { 220 | return _game_size; 221 | 222 | } 223 | 224 | void GameInterface::start_game() 225 | { 226 | // FIXME: add in checks for game size! 227 | create_game_objects(); 228 | _game_interval.start(); 229 | } 230 | 231 | void GameInterface::add_player(Player *player) 232 | { 233 | QVariant player_variant = QVariant::fromValue(player); 234 | _players.append(player_variant); 235 | emit players_changed(); 236 | emit new_player(player_variant); 237 | } 238 | -------------------------------------------------------------------------------- /core/gameinterface.h: -------------------------------------------------------------------------------- 1 | #ifndef GAMEINTERFACE_H 2 | #define GAMEINTERFACE_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | class Player; 9 | class Food; 10 | class Virus; 11 | class Cell; 12 | 13 | 14 | class GameInterface : public QObject 15 | { 16 | Q_OBJECT 17 | Q_PROPERTY(QVariantList food READ food NOTIFY food_changed) 18 | Q_PROPERTY(QVariantList players READ players NOTIFY players_changed) 19 | Q_PROPERTY(QVariantList viruses READ viruses NOTIFY viruses_changed) 20 | 21 | public: 22 | explicit GameInterface(QObject *parent = nullptr); 23 | 24 | QVariantList food(); 25 | QVariantList viruses(); 26 | QVariantList players(); 27 | 28 | void track_food_fired_by_players(Food *food); 29 | void track_new_virus(Virus *virus); 30 | void remove_virus_from_game(Virus *virus); 31 | QRect *game_size(); 32 | void start_game(); 33 | void add_player(Player* player); 34 | 35 | void set_game_size(int width, int height); 36 | Q_INVOKABLE Player* get_player(QString authentication); 37 | 38 | public slots: 39 | void increment_game_step(); 40 | void remove_player(Player *player); 41 | 42 | protected: 43 | void create_game_objects(); 44 | void create_viruses(int number=5); 45 | void create_food(int number=500); 46 | void check_game_object_interactions(); 47 | 48 | bool _check_virus_interactions(Food* food); 49 | bool _check_player_interactions(Food* food); 50 | 51 | signals: 52 | void food_changed(); 53 | void viruses_changed(); 54 | void players_changed(); 55 | void new_player(QVariant food); 56 | void new_virus(QVariant virus); 57 | void new_food(QVariant food); 58 | 59 | private: 60 | QVariantList _food; 61 | QVariantList _viruses; 62 | QVariantList _players; 63 | 64 | QRect *_game_size; 65 | QTimer _game_interval; 66 | }; 67 | 68 | #endif // GAMEINTERFACE_H 69 | -------------------------------------------------------------------------------- /core/player.cpp: -------------------------------------------------------------------------------- 1 | #include "player.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "gameinterface.h" 9 | #include "cell.h" 10 | #include "food.h" 11 | #include "virus.h" 12 | #include "ball.h" 13 | 14 | Q_DECLARE_METATYPE(Cell *) 15 | 16 | 17 | // The Player constructor function 18 | Player::Player(QString authentication, QRect *game_size, GameInterface *game_interface, QObject *parent) 19 | : QObject(parent) 20 | , _can_merge(true) 21 | , _merge_tick_countdown(-1) 22 | // `_can_merge` tracks if we can remerge a cell 23 | // into another cell. 24 | // defaults to `true`, but changes to false when 25 | // we request a split 26 | , _game_size(game_size) 27 | , _game_interface(game_interface) 28 | , _authentication(authentication) 29 | { 30 | // A cell is the physcial part of the player, it's the actual representation on the screen 31 | Cell *start_cell = new Cell(_game_size, this); 32 | 33 | // QML needs a `QVariantList`, so we maintain two lists 34 | // 1. an actual cell object list 35 | // 2. a `QVariantList` composed of `QVariant`s who's values are `Cell*` (`Cell` pointers) 36 | // Here we create our first `QVariant` who's value is a pointer to our first player Cell 37 | _cells.append(QVariant::fromValue(start_cell)); 38 | } 39 | 40 | // `_handle_two_cell_case` 41 | // A private function that... 42 | void Player::_handle_two_cell_case(Cell *left, Cell *right, QPoint mouse_position) 43 | { 44 | bool cells_touching = left->is_touching(right->ball_properties()); 45 | // bool overlaped_enough 46 | if (!cells_touching) 47 | { 48 | left->request_coordinates(mouse_position); 49 | right->request_coordinates(mouse_position); 50 | } 51 | else if (_can_merge && cells_touching) 52 | { 53 | combine_cells(left, right); 54 | } 55 | else if (_can_merge) 56 | { 57 | left->request_coordinates(mouse_position); 58 | right->request_coordinates(mouse_position); 59 | } 60 | else { 61 | left->request_coordinates(mouse_position, right->ball_properties()); 62 | right->request_coordinates(mouse_position, left->ball_properties()); 63 | } 64 | } 65 | 66 | int Player::calc_x() 67 | { 68 | if (_cells.length() == 1) 69 | return _cells[0].value()->x(); 70 | 71 | int total_x = 0; 72 | int total_y = 0; 73 | for (QVariant cell_variant : _cells) 74 | { 75 | Cell *cell = cell_variant.value(); 76 | 77 | total_x += cell->x(); 78 | total_y += cell->y(); 79 | } 80 | 81 | // average x 82 | int average_x = total_x / _cells.length(); 83 | int average_y = total_y / _cells.length(); 84 | _average_position.setX(average_x); 85 | _average_position.setY(average_y); 86 | return average_x; 87 | } 88 | 89 | int Player::calc_y() 90 | { 91 | if (_cells.length() == 1) 92 | return _cells[0].value()->y(); 93 | 94 | return _average_position.y(); 95 | } 96 | 97 | qreal Player::calc_zoom_factor() 98 | { 99 | float value; 100 | if (_cells.length() == 1){ 101 | float value = 30./_cells[0].value()->radius(); 102 | } 103 | else{ 104 | qreal total_mass = 0; 105 | for (QVariant cell_variant : _cells){ 106 | Cell *cell = cell_variant.value(); 107 | total_mass += cell->mass(); 108 | } 109 | // TODO: validate if this makes sense 110 | value = Cell::initial_mass / total_mass; 111 | } 112 | 113 | if (value > 0.8) 114 | return 1.; 115 | else if (value > 0.7) 116 | return .95; 117 | else if (value > 0.6) 118 | return .9; 119 | else if (value > 0.5) 120 | return .85; 121 | else 122 | return .8; 123 | } 124 | 125 | QString Player::authentication() 126 | { 127 | return _authentication; 128 | } 129 | 130 | // `combine_cells` 131 | // A protected function that... 132 | void Player::combine_cells(Cell *left, Cell *right) 133 | { 134 | if (left->mass() > right->mass()) 135 | { 136 | left->add_mass(right->mass()); 137 | _cells.removeOne(QVariant::fromValue(right)); 138 | right->deleteLater(); 139 | } 140 | else 141 | { 142 | right->add_mass(left->mass()); 143 | _cells.removeOne(QVariant::fromValue(left)); 144 | left->deleteLater(); 145 | } 146 | 147 | emit cells_changed(); 148 | } 149 | 150 | 151 | // `request_coordinates` 152 | // a `Q_INVOKABLE` 153 | // https://www.reddit.com/r/Agario/comments/34x2fa/game_mechanics_explained_in_depth_numbers_and/ 154 | // https://stackoverflow.com/questions/5060082/eliminating-a-direction-from-a-vector 155 | void Player::request_coordinates(int x, int y, QString authentication) 156 | { 157 | if (authentication != _authentication) 158 | return; 159 | 160 | QPoint mouse_position(x, y); 161 | // Hardcode in the most common options, no cell split 162 | if (_cells.length() == 1) 163 | { 164 | _cells[0].value()->request_coordinates(mouse_position); 165 | emit x_changed(); 166 | emit y_changed(); 167 | return; 168 | } 169 | 170 | // Hardcode in second most common option, cell split once 171 | else if(_cells.length() == 2) 172 | { 173 | Cell* left = _cells[0].value(); 174 | Cell* right = _cells[1].value(); 175 | _handle_two_cell_case(left, right, mouse_position); 176 | emit x_changed(); 177 | emit y_changed(); 178 | return; 179 | } 180 | 181 | bool cell_deleted = _handle_multiple_cells(mouse_position); 182 | 183 | if (cell_deleted) 184 | _handle_multiple_cells(mouse_position); 185 | 186 | 187 | emit x_changed(); 188 | emit y_changed(); 189 | } 190 | 191 | void Player::handle_touch(Player *other_player) 192 | { 193 | for(QVariant cell_variant : _cells) 194 | { 195 | Cell *cell = cell_variant.value(); 196 | for (QVariant other_cell_variant : other_player->cells()) 197 | { 198 | Cell *other_cell = other_cell_variant.value(); 199 | if (cell->is_touching(other_cell->ball_properties())) 200 | { 201 | 202 | } 203 | 204 | } 205 | } 206 | 207 | } 208 | 209 | void Player::request_split(int mouse_x, int mouse_y, QString authentication) 210 | { 211 | if (authentication != _authentication) 212 | return; 213 | 214 | QPoint mouse_position(mouse_x, mouse_y); 215 | for (QVariant cell_variant : _cells) 216 | { 217 | Cell *cell = cell_variant.value(); 218 | // Create a new pointer 219 | QPointer split_cell; 220 | // request the cells to split 221 | split_cell = cell->request_split(mouse_position); 222 | // check to see if we got a new split cell 223 | if (!split_cell.isNull()) 224 | { 225 | // Track the new split cell if we did 226 | Cell* cell_data = split_cell.data(); 227 | _cells.append(QVariant::fromValue(cell_data)); 228 | // TODO: put a smaller number in here if we have a bunch of splits 229 | _merge_tick_countdown = 100 / _cells.length(); 230 | _can_merge = false; 231 | // emit new_cell(cell_data); 232 | emit cells_changed(); 233 | } 234 | } 235 | } 236 | 237 | QVariantList Player::cells() 238 | { 239 | return _cells; 240 | } 241 | 242 | void Player::handle_touch(Virus *virus) 243 | { 244 | for(QVariant cell_variant : _cells) 245 | { 246 | Cell *cell = cell_variant.value(); 247 | 248 | // TODO: add in fudge factor to radius 249 | if (cell->is_touching(virus->ball_properties())) 250 | { 251 | // compare mass since there's no math 252 | if (cell->mass() <= virus->mass()) 253 | continue; 254 | else 255 | explode_cell_from_virus(cell, virus); 256 | } 257 | } 258 | } 259 | 260 | void Player::move() 261 | { 262 | if (_merge_tick_countdown < 0 && !_can_merge) 263 | _can_merge = true; 264 | else if(_merge_tick_countdown >= 0) 265 | _merge_tick_countdown --; 266 | } 267 | 268 | void Player::explode_cell_from_virus(Cell *cell, Virus *virus) 269 | { 270 | if (!_game_interface) 271 | return; 272 | 273 | int number_new_cells = static_cast(cell->mass()) % static_cast(Cell::initial_mass); 274 | if (number_new_cells + _cells.length() > 16) 275 | number_new_cells = 16 - _cells.length(); 276 | 277 | _can_merge = false; 278 | cell->set_mass(Cell::initial_mass); 279 | // two radians 280 | qreal delta = 6.283 / number_new_cells; 281 | 282 | for (int i=0; i < number_new_cells; i++) 283 | { 284 | QVector2D velocity(qSin(delta * i), qCos(delta * i)); 285 | velocity *= 10; 286 | Ball *ball_properties = new Ball(*cell->ball_properties()); 287 | ball_properties->set_initial_velocity(velocity); 288 | ball_properties->set_velocity_ticks(30); 289 | Cell *new_cell = new Cell(ball_properties, this); 290 | _cells.append(QVariant::fromValue(new_cell)); 291 | } 292 | 293 | _game_interface->remove_virus_from_game(virus); 294 | } 295 | 296 | bool Player::_handle_multiple_cells(QPoint mouse_position) 297 | { 298 | QMultiHash cell_touches; 299 | Cell *deleted_cell; 300 | 301 | // For every cell 302 | for (QVariant cell_variant : _cells) 303 | { 304 | Cell *cell = cell_variant.value(); 305 | 306 | // Check if we're in contact with our own cells 307 | // by iterating through every cell again 308 | for (QVariant other_cell_variant : _cells) 309 | { 310 | if (cell_variant == other_cell_variant) 311 | continue; 312 | Cell *other_cell = other_cell_variant.value(); 313 | 314 | if (cell->is_touching(other_cell->ball_properties())) 315 | cell_touches.insert(cell, other_cell); 316 | } 317 | 318 | QList all_cell_touches = cell_touches.values(cell); 319 | all_cell_touches.removeAll(deleted_cell); 320 | 321 | // FIXME: Grab from last? 322 | if (_can_merge && !all_cell_touches.empty()) 323 | { 324 | deleted_cell = all_cell_touches.first(); 325 | combine_cells(cell, deleted_cell); 326 | // 50 ms is the game step interval 327 | _merge_tick_countdown = 100; 328 | _can_merge = false; 329 | return true; 330 | } 331 | else 332 | cell->request_coordinates(mouse_position, all_cell_touches); 333 | } 334 | return false; 335 | 336 | } 337 | 338 | void Player::request_fire_food(int mouse_x, int mouse_y, QString authentication) 339 | { 340 | if (authentication != _authentication) 341 | return; 342 | 343 | QPoint mouse_position(mouse_x, mouse_y); 344 | 345 | for (QVariant cell_variant : _cells) 346 | { 347 | Cell *cell = cell_variant.value(); 348 | // Create a new pointer 349 | QPointer new_food; 350 | // request the cells to split 351 | new_food = cell->request_fire_food(mouse_position); 352 | // check to see if we got a new split cell 353 | if (!new_food.isNull() && _game_interface) 354 | _game_interface->track_food_fired_by_players(new_food); 355 | } 356 | } 357 | -------------------------------------------------------------------------------- /core/player.h: -------------------------------------------------------------------------------- 1 | #ifndef PLAYER_H 2 | #define PLAYER_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | class Ball; 10 | class Cell; 11 | class Food; 12 | class Virus; 13 | class GameInterface; 14 | 15 | 16 | class Player : public QObject 17 | { 18 | Q_OBJECT 19 | Q_PROPERTY(int x READ calc_x NOTIFY x_changed) 20 | Q_PROPERTY(int y READ calc_y NOTIFY y_changed) 21 | Q_PROPERTY(QVariantList cells READ cells NOTIFY cells_changed) 22 | Q_PROPERTY(qreal zoom_factor READ calc_zoom_factor NOTIFY zoom_changed) 23 | 24 | public: 25 | explicit Player(QString authentication, 26 | QRect *game_size, 27 | GameInterface *game_interface = nullptr, 28 | QObject *parent = nullptr); 29 | 30 | Q_INVOKABLE void request_coordinates(int x, int y, QString authentication); 31 | Q_INVOKABLE void request_split(int mouse_x, int mouse_y, QString authentication); 32 | Q_INVOKABLE void request_fire_food(int mouse_x, int mouse_y, QString authentication); 33 | 34 | QVariantList cells(); 35 | 36 | typedef QList CellList; 37 | 38 | CellList internal_cell_list(); 39 | 40 | int calc_x(); 41 | int calc_y(); 42 | qreal calc_zoom_factor(); 43 | QString authentication(); 44 | 45 | void handle_touch(Player *other_player); 46 | void handle_touch(Virus *virus); 47 | 48 | void move(); 49 | 50 | signals: 51 | void x_changed(); 52 | void y_changed(); 53 | void cells_changed(); 54 | void zoom_changed(); 55 | /* 56 | void new_cell(Cell *cell); 57 | void remove_cell(Cell *cell); 58 | */ 59 | 60 | protected: 61 | void combine_cells(Cell* left, Cell* right); 62 | void explode_cell_from_virus(Cell* cell, Virus* virus); 63 | bool _handle_multiple_cells(QPoint mouse_position); 64 | 65 | private: 66 | void validate_coordinates(); 67 | void _handle_two_cell_case(Cell* left, Cell* right, QPoint mouse_position); 68 | 69 | // -------------------------------------------------------- 70 | // Here's a list of all our private variables for the class 71 | // -------------------------------------------------------- 72 | 73 | bool _can_merge; 74 | int _merge_tick_countdown; 75 | 76 | QVariantList _cells; 77 | 78 | QRect *_game_size; 79 | QPoint _average_position; 80 | 81 | GameInterface *_game_interface; 82 | QString _authentication; 83 | }; 84 | 85 | #endif // PLAYER_H 86 | -------------------------------------------------------------------------------- /core/qwebchannel.js: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | ** 3 | ** Copyright (C) 2016 The Qt Company Ltd. 4 | ** Copyright (C) 2016 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Milian Wolff 5 | ** Contact: https://www.qt.io/licensing/ 6 | ** 7 | ** This file is part of the QtWebChannel module of the Qt Toolkit. 8 | ** 9 | ** $QT_BEGIN_LICENSE:LGPL$ 10 | ** Commercial License Usage 11 | ** Licensees holding valid commercial Qt licenses may use this file in 12 | ** accordance with the commercial license agreement provided with the 13 | ** Software or, alternatively, in accordance with the terms contained in 14 | ** a written agreement between you and The Qt Company. For licensing terms 15 | ** and conditions see https://www.qt.io/terms-conditions. For further 16 | ** information use the contact form at https://www.qt.io/contact-us. 17 | ** 18 | ** GNU Lesser General Public License Usage 19 | ** Alternatively, this file may be used under the terms of the GNU Lesser 20 | ** General Public License version 3 as published by the Free Software 21 | ** Foundation and appearing in the file LICENSE.LGPL3 included in the 22 | ** packaging of this file. Please review the following information to 23 | ** ensure the GNU Lesser General Public License version 3 requirements 24 | ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. 25 | ** 26 | ** GNU General Public License Usage 27 | ** Alternatively, this file may be used under the terms of the GNU 28 | ** General Public License version 2.0 or (at your option) the GNU General 29 | ** Public license version 3 or any later version approved by the KDE Free 30 | ** Qt Foundation. The licenses are as published by the Free Software 31 | ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 32 | ** included in the packaging of this file. Please review the following 33 | ** information to ensure the GNU General Public License requirements will 34 | ** be met: https://www.gnu.org/licenses/gpl-2.0.html and 35 | ** https://www.gnu.org/licenses/gpl-3.0.html. 36 | ** 37 | ** $QT_END_LICENSE$ 38 | ** 39 | ****************************************************************************/ 40 | 41 | "use strict"; 42 | 43 | // https://tc39.github.io/ecma262/#sec-array.prototype.findIndex 44 | if (!Array.prototype.findIndex) { 45 | Object.defineProperty(Array.prototype, 'findIndex', { 46 | value: function(predicate) { 47 | // 1. Let O be ? ToObject(this value). 48 | if (this == null) { 49 | throw new TypeError('"this" is null or not defined'); 50 | } 51 | 52 | var o = Object(this); 53 | 54 | // 2. Let len be ? ToLength(? Get(O, "length")). 55 | var len = o.length >>> 0; 56 | 57 | // 3. If IsCallable(predicate) is false, throw a TypeError exception. 58 | if (typeof predicate !== 'function') { 59 | throw new TypeError('predicate must be a function'); 60 | } 61 | 62 | // 4. If thisArg was supplied, let T be thisArg; else let T be undefined. 63 | var thisArg = arguments[1]; 64 | 65 | // 5. Let k be 0. 66 | var k = 0; 67 | 68 | // 6. Repeat, while k < len 69 | while (k < len) { 70 | // a. Let Pk be ! ToString(k). 71 | // b. Let kValue be ? Get(O, Pk). 72 | // c. Let testResult be ToBoolean(? Call(predicate, T, « kValue, k, O »)). 73 | // d. If testResult is true, return k. 74 | var kValue = o[k]; 75 | if (predicate.call(thisArg, kValue, k, o)) { 76 | return k; 77 | } 78 | // e. Increase k by 1. 79 | k++; 80 | } 81 | 82 | // 7. Return -1. 83 | return -1; 84 | }, 85 | configurable: true, 86 | writable: true 87 | }); 88 | } 89 | 90 | 91 | var QWebChannelMessageTypes = { 92 | signal: 1, 93 | propertyUpdate: 2, 94 | init: 3, 95 | idle: 4, 96 | debug: 5, 97 | invokeMethod: 6, 98 | connectToSignal: 7, 99 | disconnectFromSignal: 8, 100 | setProperty: 9, 101 | response: 10, 102 | auth: 11, 103 | }; 104 | 105 | var QWebChannel = function(transport, auth_callback, initCallback) 106 | { 107 | if (typeof transport !== "object" || typeof transport.send !== "function") { 108 | console.error("The QWebChannel expects a transport object with a send function and onmessage callback property." + 109 | " Given is: transport: " + typeof(transport) + ", transport.send: " + typeof(transport.send)); 110 | return; 111 | } 112 | 113 | var channel = this; 114 | this.transport = transport; 115 | 116 | this.send = function(data) 117 | { 118 | if (typeof(data) !== "string") { 119 | data = JSON.stringify(data); 120 | } 121 | channel.transport.send(data); 122 | } 123 | 124 | this.transport.onmessage = function(message) 125 | { 126 | var data = message.data; 127 | if (typeof data === "string") { 128 | data = JSON.parse(data); 129 | } 130 | 131 | switch (data.type) { 132 | case QWebChannelMessageTypes.signal: 133 | channel.handleSignal(data); 134 | break; 135 | case QWebChannelMessageTypes.response: 136 | channel.handleResponse(data); 137 | break; 138 | case QWebChannelMessageTypes.propertyUpdate: 139 | channel.handlePropertyUpdate(data); 140 | break; 141 | case QWebChannelMessageTypes.auth: 142 | if (auth_callback) 143 | auth_callback(data.auth) 144 | break; 145 | default: 146 | console.error("invalid message received:", message.data); 147 | break; 148 | } 149 | } 150 | 151 | this.execCallbacks = {}; 152 | this.execId = 0; 153 | this.exec = function(data, callback) 154 | { 155 | if (!callback) { 156 | // if no callback is given, send directly 157 | channel.send(data); 158 | return; 159 | } 160 | if (channel.execId === Number.MAX_VALUE) { 161 | // wrap 162 | channel.execId = Number.MIN_VALUE; 163 | } 164 | if (data.hasOwnProperty("id")) { 165 | console.error("Cannot exec message with property id: " + JSON.stringify(data)); 166 | return; 167 | } 168 | data.id = channel.execId++; 169 | channel.execCallbacks[data.id] = callback; 170 | channel.send(data); 171 | }; 172 | 173 | this.objects = {}; 174 | 175 | this.handleSignal = function(message) 176 | { 177 | var object = channel.objects[message.object]; 178 | if (object) { 179 | object.signalEmitted(message.signal, message.args); 180 | } else { 181 | console.warn("Unhandled signal: " + message.object + "::" + message.signal); 182 | } 183 | } 184 | 185 | this.handleResponse = function(message) 186 | { 187 | if (!message.hasOwnProperty("id")) { 188 | console.error("Invalid response message received: ", JSON.stringify(message)); 189 | return; 190 | } 191 | channel.execCallbacks[message.id](message.data); 192 | delete channel.execCallbacks[message.id]; 193 | } 194 | 195 | this.handlePropertyUpdate = function(message) 196 | { 197 | for (var i in message.data) { 198 | var data = message.data[i]; 199 | var object = channel.objects[data.object]; 200 | if (object) { 201 | object.propertyUpdate(data.signals, data.properties); 202 | } else { 203 | console.warn("Unhandled property update: " + data.object + "::" + data.signal); 204 | } 205 | } 206 | channel.exec({type: QWebChannelMessageTypes.idle}); 207 | } 208 | 209 | this.debug = function(message) 210 | { 211 | channel.send({type: QWebChannelMessageTypes.debug, data: message}); 212 | }; 213 | 214 | channel.exec({type: QWebChannelMessageTypes.init}, function(data) { 215 | for (var objectName in data) { 216 | var object = new QObject(objectName, data[objectName], channel); 217 | } 218 | // now unwrap properties, which might reference other registered objects 219 | for (var objectName in channel.objects) { 220 | // unwrap properties turns around and calls unwrap qobject 221 | // NOTE: need to figure out what the params are to unwrap QObject 222 | channel.objects[objectName].unwrapProperties(); 223 | } 224 | if (initCallback) { 225 | initCallback(channel); 226 | } 227 | channel.exec({type: QWebChannelMessageTypes.idle}); 228 | }); 229 | }; 230 | 231 | function QObject(name, data, webChannel) 232 | { 233 | this.__id__ = name; 234 | webChannel.objects[name] = this; 235 | 236 | // List of callbacks that get invoked upon signal emission 237 | this.__objectSignals__ = {}; 238 | 239 | // Cache of all properties, updated when a notify signal is emitted 240 | this.__propertyCache__ = {}; 241 | 242 | var object = this; 243 | 244 | // ---------------------------------------------------------------------- 245 | 246 | this.unwrapQObject = function(response) 247 | { 248 | if (response instanceof Array) { 249 | // support list of objects 250 | var ret = new Array(response.length); 251 | for (var i = 0; i < response.length; ++i) { 252 | // what if I wrapped or connected the destroyed to a splice here? 253 | var new_object = object.unwrapQObject(response[i]); 254 | ret[i] = new_object; 255 | new_object.destroyed.connect(function(){ 256 | ret.splice(ret.findIndex(function(element){ 257 | for (var name in element) { 258 | return false; 259 | } 260 | return true; 261 | }), 1); 262 | }) 263 | } 264 | return ret; 265 | } 266 | if (!response 267 | || !response["__QObject*__"] 268 | || response.id === undefined) { 269 | return response; 270 | } 271 | 272 | var objectId = response.id; 273 | if (webChannel.objects[objectId]) 274 | return webChannel.objects[objectId]; 275 | 276 | if (!response.data) { 277 | console.error("Cannot unwrap unknown QObject " + objectId + " without data."); 278 | return; 279 | } 280 | 281 | var qObject = new QObject( objectId, response.data, webChannel ); 282 | qObject.destroyed.connect(function() { 283 | if (webChannel.objects[objectId] === qObject) { 284 | delete webChannel.objects[objectId]; 285 | // reset the now deleted QObject to an empty {} object 286 | // just assigning {} though would not have the desired effect, but the 287 | // below also ensures all external references will see the empty map 288 | // NOTE: this detour is necessary to workaround QTBUG-40021 289 | var propertyNames = []; 290 | for (var propertyName in qObject) { 291 | propertyNames.push(propertyName); 292 | } 293 | for (var idx in propertyNames) { 294 | delete qObject[propertyNames[idx]]; 295 | } 296 | } 297 | }); 298 | // here we are already initialized, and thus must directly unwrap the properties 299 | qObject.unwrapProperties(); 300 | return qObject; 301 | } 302 | 303 | this.unwrapProperties = function() 304 | { 305 | for (var propertyIdx in object.__propertyCache__) { 306 | object.__propertyCache__[propertyIdx] = object.unwrapQObject(object.__propertyCache__[propertyIdx]); 307 | } 308 | } 309 | 310 | function addSignal(signalData, isPropertyNotifySignal) 311 | { 312 | var signalName = signalData[0]; 313 | var signalIndex = signalData[1]; 314 | object[signalName] = { 315 | connect: function(callback) { 316 | if (typeof(callback) !== "function") { 317 | console.error("Bad callback given to connect to signal " + signalName); 318 | return; 319 | } 320 | 321 | object.__objectSignals__[signalIndex] = object.__objectSignals__[signalIndex] || []; 322 | object.__objectSignals__[signalIndex].push(callback); 323 | 324 | if (!isPropertyNotifySignal && signalName !== "destroyed") { 325 | // only required for "pure" signals, handled separately for properties in propertyUpdate 326 | // also note that we always get notified about the destroyed signal 327 | webChannel.exec({ 328 | type: QWebChannelMessageTypes.connectToSignal, 329 | object: object.__id__, 330 | signal: signalIndex 331 | }); 332 | } 333 | }, 334 | disconnect: function(callback) { 335 | if (typeof(callback) !== "function") { 336 | console.error("Bad callback given to disconnect from signal " + signalName); 337 | return; 338 | } 339 | object.__objectSignals__[signalIndex] = object.__objectSignals__[signalIndex] || []; 340 | var idx = object.__objectSignals__[signalIndex].indexOf(callback); 341 | if (idx === -1) { 342 | console.error("Cannot find connection of signal " + signalName + " to " + callback.name); 343 | return; 344 | } 345 | object.__objectSignals__[signalIndex].splice(idx, 1); 346 | if (!isPropertyNotifySignal && object.__objectSignals__[signalIndex].length === 0) { 347 | // only required for "pure" signals, handled separately for properties in propertyUpdate 348 | webChannel.exec({ 349 | type: QWebChannelMessageTypes.disconnectFromSignal, 350 | object: object.__id__, 351 | signal: signalIndex 352 | }); 353 | } 354 | } 355 | }; 356 | } 357 | 358 | /** 359 | * Invokes all callbacks for the given signalname. Also works for property notify callbacks. 360 | */ 361 | function invokeSignalCallbacks(signalName, signalArgs) 362 | { 363 | var connections = object.__objectSignals__[signalName]; 364 | if (connections) { 365 | connections.forEach(function(callback) { 366 | callback.apply(callback, signalArgs); 367 | }); 368 | } 369 | } 370 | 371 | this.propertyUpdate = function(signals, propertyMap) 372 | { 373 | // update property cache 374 | for (var propertyIndex in propertyMap) { 375 | var propertyValue = propertyMap[propertyIndex]; 376 | 377 | object.__propertyCache__[propertyIndex] = object.unwrapQObject(propertyValue); 378 | } 379 | 380 | for (var signalName in signals) { 381 | // Invoke all callbacks, as signalEmitted() does not. This ensures the 382 | // property cache is updated before the callbacks are invoked. 383 | invokeSignalCallbacks(signalName, signals[signalName]); 384 | } 385 | } 386 | 387 | this.signalEmitted = function(signalName, signalArgs) 388 | { 389 | invokeSignalCallbacks(signalName, this.unwrapQObject(signalArgs)); 390 | } 391 | 392 | function addMethod(methodData) 393 | { 394 | var methodName = methodData[0]; 395 | var methodIdx = methodData[1]; 396 | object[methodName] = function() { 397 | var args = []; 398 | var callback; 399 | for (var i = 0; i < arguments.length; ++i) { 400 | var argument = arguments[i]; 401 | if (typeof argument === "function") 402 | callback = argument; 403 | else if (argument instanceof QObject && webChannel.objects[argument.__id__] !== undefined) 404 | args.push({ 405 | "id": argument.__id__ 406 | }); 407 | else 408 | args.push(argument); 409 | } 410 | 411 | webChannel.exec({ 412 | "type": QWebChannelMessageTypes.invokeMethod, 413 | "object": object.__id__, 414 | "method": methodIdx, 415 | "args": args 416 | }, function(response) { 417 | if (response !== undefined) { 418 | var result = object.unwrapQObject(response); 419 | if (callback) { 420 | (callback)(result); 421 | } 422 | } 423 | }); 424 | }; 425 | } 426 | 427 | function bindGetterSetter(propertyInfo) 428 | { 429 | var propertyIndex = propertyInfo[0]; 430 | var propertyName = propertyInfo[1]; 431 | var notifySignalData = propertyInfo[2]; 432 | // initialize property cache with current value 433 | // NOTE: if this is an object, it is not directly unwrapped as it might 434 | // reference other QObject that we do not know yet 435 | object.__propertyCache__[propertyIndex] = propertyInfo[3]; 436 | 437 | if (notifySignalData) { 438 | if (notifySignalData[0] === 1) { 439 | // signal name is optimized away, reconstruct the actual name 440 | notifySignalData[0] = propertyName + "Changed"; 441 | } 442 | addSignal(notifySignalData, true); 443 | } 444 | 445 | Object.defineProperty(object, propertyName, { 446 | configurable: true, 447 | get: function () { 448 | var propertyValue = object.__propertyCache__[propertyIndex]; 449 | if (propertyValue === undefined) { 450 | // This shouldn't happen 451 | console.warn("Undefined value in property cache for property \"" + propertyName + "\" in object " + object.__id__); 452 | } 453 | 454 | return propertyValue; 455 | }, 456 | set: function(value) { 457 | if (value === undefined) { 458 | console.warn("Property setter for " + propertyName + " called with undefined value!"); 459 | return; 460 | } 461 | object.__propertyCache__[propertyIndex] = value; 462 | var valueToSend = value; 463 | if (valueToSend instanceof QObject && webChannel.objects[valueToSend.__id__] !== undefined) 464 | valueToSend = { "id": valueToSend.__id__ }; 465 | webChannel.exec({ 466 | "type": QWebChannelMessageTypes.setProperty, 467 | "object": object.__id__, 468 | "property": propertyIndex, 469 | "value": valueToSend 470 | }); 471 | } 472 | }); 473 | 474 | } 475 | 476 | // ---------------------------------------------------------------------- 477 | 478 | data.methods.forEach(addMethod); 479 | 480 | data.properties.forEach(bindGetterSetter); 481 | 482 | data.signals.forEach(function(signal) { addSignal(signal, false); }); 483 | 484 | for (var name in data.enums) { 485 | object[name] = data.enums[name]; 486 | } 487 | } 488 | 489 | //required for use with nodejs 490 | if (typeof module === 'object') { 491 | module.exports = { 492 | QWebChannel: QWebChannel 493 | }; 494 | } 495 | -------------------------------------------------------------------------------- /core/shared.pri: -------------------------------------------------------------------------------- 1 | INCLUDEPATH += $$PWD 2 | 3 | HEADERS += \ 4 | $$PWD/virus.h \ 5 | $$PWD/food.h \ 6 | $$PWD/player.h \ 7 | $$PWD/cell.h \ 8 | $$PWD/gameinterface.h \ 9 | $$PWD/ball.h 10 | 11 | SOURCES += \ 12 | $$PWD/virus.cpp \ 13 | $$PWD/food.cpp \ 14 | $$PWD/player.cpp \ 15 | $$PWD/cell.cpp \ 16 | $$PWD/gameinterface.cpp \ 17 | $$PWD/ball.cpp 18 | -------------------------------------------------------------------------------- /core/virus.cpp: -------------------------------------------------------------------------------- 1 | #include "virus.h" 2 | 3 | #include 4 | #include 5 | 6 | #include "food.h" 7 | #include "gameinterface.h" 8 | #include "ball.h" 9 | 10 | Virus::Virus(QRect *game_size, GameInterface *game_interface, QObject *parent) 11 | : QObject(parent) 12 | , _ball_properties(new Ball(game_size, Virus::_initial_mass, this)) 13 | , _game_interface(game_interface) 14 | { 15 | _connect_ball_property_signals(); 16 | _ball_properties->set_coordinates_random(); 17 | } 18 | 19 | Virus::Virus(Ball *ball_properties, GameInterface *game_interface, QObject *parent) 20 | : QObject(parent) 21 | , _ball_properties(ball_properties) 22 | , _game_interface(game_interface) 23 | { 24 | _connect_ball_property_signals(); 25 | _ball_properties->set_velocity_ticks(30); 26 | _ball_properties->setParent(this); 27 | } 28 | 29 | void Virus::move() 30 | { 31 | _ball_properties->move(); 32 | } 33 | 34 | QPoint Virus::position() 35 | { 36 | return _ball_properties->position(); 37 | } 38 | 39 | Ball* Virus::ball_properties() 40 | { 41 | return _ball_properties; 42 | } 43 | 44 | bool Virus::is_touching(Ball *other) 45 | { 46 | return _ball_properties->is_touching(other); 47 | } 48 | 49 | void Virus::_connect_ball_property_signals() 50 | { 51 | connect(_ball_properties, &Ball::x_changed, this, &Virus::x_changed); 52 | connect(_ball_properties, &Ball::y_changed, this, &Virus::y_changed); 53 | connect(_ball_properties, &Ball::radius_changed, this, &Virus::radius_changed); 54 | } 55 | 56 | void Virus::eat_food(Food *food) 57 | { 58 | _ball_properties->add_mass(food->mass()); 59 | 60 | if (_ball_properties->mass() > Virus::_split_mass && _game_interface) 61 | { 62 | QVector2D velocity = food->ball_properties()->initial_velocity(); 63 | velocity *= 10; 64 | 65 | Ball *new_ball = new Ball(*_ball_properties); 66 | new_ball->set_initial_velocity(velocity); 67 | 68 | Virus *virus = new Virus(new_ball, _game_interface); 69 | _game_interface->track_new_virus(virus); 70 | _ball_properties->set_mass(Virus::_initial_mass); 71 | } 72 | } 73 | 74 | int Virus::x() 75 | { 76 | return _ball_properties->x(); 77 | } 78 | 79 | int Virus::y() 80 | { 81 | return _ball_properties->y(); 82 | } 83 | 84 | int Virus::radius() 85 | { 86 | return _ball_properties->radius(); 87 | } 88 | 89 | qreal Virus::mass() 90 | { 91 | return _ball_properties->mass(); 92 | } 93 | 94 | void Virus::add_mass(qreal mass) 95 | { 96 | return _ball_properties->add_mass(mass); 97 | } 98 | -------------------------------------------------------------------------------- /core/virus.h: -------------------------------------------------------------------------------- 1 | #ifndef VIRUS_H 2 | #define VIRUS_H 3 | 4 | #include 5 | #include 6 | 7 | class Food; 8 | class GameInterface; 9 | class Ball; 10 | class QRect; 11 | class QPoint; 12 | 13 | class Virus : public QObject 14 | { 15 | Q_OBJECT 16 | Q_PROPERTY(int x READ x NOTIFY x_changed) 17 | Q_PROPERTY(int y READ y NOTIFY y_changed) 18 | Q_PROPERTY(int radius READ radius NOTIFY radius_changed) 19 | 20 | public: 21 | explicit Virus(QRect *game_size, GameInterface *game_interface = nullptr, QObject *parent = nullptr); 22 | explicit Virus(Ball *ball_properties, GameInterface *game_interface = nullptr, QObject *parent = nullptr); 23 | 24 | static constexpr qreal _initial_mass = 22167; 25 | static constexpr qreal _radius = 84; 26 | // agario clone has the intial mass at 100-150, split at 180 27 | static constexpr qreal _split_mass = 34334; 28 | 29 | void move(); 30 | 31 | int x(); 32 | int y(); 33 | 34 | QPoint position(); 35 | Ball* ball_properties(); 36 | 37 | int radius(); 38 | qreal mass(); 39 | 40 | void add_mass(qreal mass); 41 | void eat_food(Food* food); 42 | bool is_touching(Ball *ball_properties); 43 | 44 | signals: 45 | void x_changed(); 46 | void y_changed(); 47 | void radius_changed(); 48 | 49 | protected: 50 | void _connect_ball_property_signals(); 51 | 52 | private: 53 | Ball *_ball_properties; 54 | GameInterface *_game_interface; 55 | }; 56 | 57 | #endif // VIRUS_H 58 | -------------------------------------------------------------------------------- /eatem-server/authentication.h: -------------------------------------------------------------------------------- 1 | #ifndef AUTHENTICATION_H 2 | #define AUTHENTICATION_H 3 | #include 4 | #include 5 | 6 | class Authentication: public QObject 7 | { 8 | Q_OBJECT 9 | Q_PROPERTY(QString auth_code READ auth_code NOTIFY change) 10 | 11 | public: 12 | explicit Authentication(QObject *parent = nullptr) 13 | : QObject(parent) 14 | { 15 | } 16 | 17 | QString auth_code() 18 | { 19 | return QString(); 20 | } 21 | 22 | 23 | signals: 24 | void change(QString new_auth); 25 | 26 | }; 27 | #endif // AUTHENTICATION_H 28 | -------------------------------------------------------------------------------- /eatem-server/eatem-server.pro: -------------------------------------------------------------------------------- 1 | # using gui for QColor 2 | QT += core gui websockets webchannel 3 | 4 | include(../core/shared.pri) 5 | 6 | CONFIG += c++11 7 | CONFIG -= app_bundle 8 | 9 | # The following define makes your compiler emit warnings if you use 10 | # any feature of Qt which as been marked deprecated (the exact warnings 11 | # depend on your compiler). Please consult the documentation of the 12 | # deprecated API in order to know how to port your code away from it. 13 | DEFINES += QT_DEPRECATED_WARNINGS 14 | 15 | # You can also make your code fail to compile if you use deprecated APIs. 16 | # In order to do so, uncomment the following line. 17 | # You can also select to disable deprecated APIs only up to a certain version of Qt. 18 | #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 19 | 20 | SOURCES += \ 21 | main.cpp \ 22 | game.cpp \ 23 | websockettransport.cpp 24 | 25 | HEADERS += \ 26 | game.h \ 27 | websockettransport.h \ 28 | authentication.h 29 | -------------------------------------------------------------------------------- /eatem-server/game.cpp: -------------------------------------------------------------------------------- 1 | #include "game.h" 2 | #include "player.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "authentication.h" 8 | 9 | // https://stackoverflow.com/questions/18862963/qt-c-random-string-generation 10 | QString GetRandomString() 11 | { 12 | const QString possibleCharacters("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"); 13 | const int randomStringLength = 12; // assuming you want random strings of 12 characters 14 | const int length_characters = possibleCharacters.length(); 15 | QRandomGenerator random = QRandomGenerator::securelySeeded(); 16 | QString randomString; 17 | for(int i=0; iregisterObject("interface", _game_interface); 34 | _webchannel->registerObject("authentication", _auth); 35 | connect(_websocket_server, &QWebSocketServer::newConnection, 36 | this, &Game::handle_new_connection); 37 | 38 | connect(this, &Game::client_connected, _webchannel, &QWebChannel::connectTo); 39 | 40 | } 41 | 42 | bool Game::start() 43 | { 44 | bool listening = _websocket_server->listen(QHostAddress::LocalHost, 5555); 45 | 46 | // NOTE: should think about setting game interval in here too? 47 | _game_interface->set_game_size(1000, 1000); 48 | _game_interface->start_game(); 49 | return listening; 50 | } 51 | 52 | QJsonObject GetMessage(QString authentication) 53 | { 54 | QJsonObject message; 55 | // FIXME: Should probably use consts instead of hardcoding strings in here 56 | 57 | message["auth"] = authentication; 58 | message["type"] = 11; 59 | return message; 60 | } 61 | 62 | void Game::handle_new_connection() 63 | { 64 | QWebSocket *web_socket = _websocket_server->nextPendingConnection(); 65 | WebSocketTransport *transport = new WebSocketTransport(web_socket); 66 | 67 | emit client_connected(transport); 68 | QString authentication = GetRandomString(); 69 | Player *new_player = new Player(authentication, _game_interface->game_size(), _game_interface); 70 | _game_interface->add_player(new_player); 71 | 72 | // https://code.woboq.org/qt5/qtwebchannel/src/webchannel/qmetaobjectpublisher.cpp.html#290 73 | // NOTE: might want to take the "specific update" route as seen in the code 74 | QJsonObject message = GetMessage(authentication); 75 | // {"data":[{"object":"authentication","properties":{"1":"QUaP1SVsQhRL"},"signals":{"5":[]}}],"type":2} 76 | transport->sendMessage(message); 77 | 78 | // currently connecting the WebSocket destroyed to the Player delete Later slot 79 | connect(web_socket, &QWebSocket::disconnected, [this, new_player](){ 80 | _game_interface->remove_player(new_player); 81 | }); 82 | } 83 | -------------------------------------------------------------------------------- /eatem-server/game.h: -------------------------------------------------------------------------------- 1 | #ifndef GAME_H 2 | #define GAME_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "websockettransport.h" 9 | 10 | #include "gameinterface.h" 11 | 12 | 13 | class Authentication; 14 | 15 | 16 | class Game : public QObject 17 | { 18 | Q_OBJECT 19 | public: 20 | explicit Game(QObject *parent = nullptr); 21 | bool start(); 22 | 23 | protected slots: 24 | void handle_new_connection(); 25 | 26 | signals: 27 | void client_connected(WebSocketTransport *client); 28 | 29 | private: 30 | GameInterface *_game_interface; 31 | // The Web Channel 32 | QWebChannel *_webchannel; 33 | // Web Socket Server 34 | QWebSocketServer *_websocket_server; 35 | Authentication *_auth; 36 | }; 37 | 38 | #endif // GAME_H 39 | -------------------------------------------------------------------------------- /eatem-server/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "game.h" 3 | 4 | int main(int argc, char *argv[]) 5 | { 6 | QCoreApplication a(argc, argv); 7 | 8 | Game game; 9 | bool running = game.start(); 10 | 11 | if (!running) 12 | { 13 | qCritical("Server socket already in use"); 14 | return -1; 15 | } 16 | 17 | return a.exec(); 18 | } 19 | -------------------------------------------------------------------------------- /eatem-server/websockettransport.cpp: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | ** 3 | ** Copyright (C) 2016 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Milian Wolff 4 | ** Contact: https://www.qt.io/licensing/ 5 | ** 6 | ** This file is part of the QtWebChannel module of the Qt Toolkit. 7 | ** 8 | ** $QT_BEGIN_LICENSE:BSD$ 9 | ** Commercial License Usage 10 | ** Licensees holding valid commercial Qt licenses may use this file in 11 | ** accordance with the commercial license agreement provided with the 12 | ** Software or, alternatively, in accordance with the terms contained in 13 | ** a written agreement between you and The Qt Company. For licensing terms 14 | ** and conditions see https://www.qt.io/terms-conditions. For further 15 | ** information use the contact form at https://www.qt.io/contact-us. 16 | ** 17 | ** BSD License Usage 18 | ** Alternatively, you may use this file under the terms of the BSD license 19 | ** as follows: 20 | ** 21 | ** "Redistribution and use in source and binary forms, with or without 22 | ** modification, are permitted provided that the following conditions are 23 | ** met: 24 | ** * Redistributions of source code must retain the above copyright 25 | ** notice, this list of conditions and the following disclaimer. 26 | ** * Redistributions in binary form must reproduce the above copyright 27 | ** notice, this list of conditions and the following disclaimer in 28 | ** the documentation and/or other materials provided with the 29 | ** distribution. 30 | ** * Neither the name of The Qt Company Ltd nor the names of its 31 | ** contributors may be used to endorse or promote products derived 32 | ** from this software without specific prior written permission. 33 | ** 34 | ** 35 | ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 36 | ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 37 | ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 38 | ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 39 | ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 40 | ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 41 | ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 42 | ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 43 | ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 44 | ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 45 | ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." 46 | ** 47 | ** $QT_END_LICENSE$ 48 | ** 49 | ****************************************************************************/ 50 | 51 | #include "websockettransport.h" 52 | 53 | #include 54 | #include 55 | #include 56 | #include 57 | 58 | /*! 59 | \brief QWebChannelAbstractSocket implementation that uses a QWebSocket internally. 60 | 61 | The transport delegates all messages received over the QWebSocket over its 62 | textMessageReceived signal. Analogously, all calls to sendTextMessage will 63 | be send over the QWebSocket to the remote client. 64 | */ 65 | 66 | /*! 67 | Construct the transport object and wrap the given socket. 68 | 69 | The socket is also set as the parent of the transport object. 70 | */ 71 | WebSocketTransport::WebSocketTransport(QWebSocket *socket) 72 | : QWebChannelAbstractTransport(socket) 73 | , m_socket(socket) 74 | { 75 | connect(socket, &QWebSocket::textMessageReceived, 76 | this, &WebSocketTransport::textMessageReceived); 77 | // FIXME: this causes the server to crash 78 | /* 79 | connect(socket, &QWebSocket::disconnected, 80 | this, &WebSocketTransport::deleteLater); 81 | */ 82 | } 83 | 84 | /*! 85 | Destroys the WebSocketTransport. 86 | */ 87 | WebSocketTransport::~WebSocketTransport() 88 | { 89 | m_socket->deleteLater(); 90 | } 91 | 92 | /*! 93 | Serialize the JSON message and send it as a text message via the WebSocket to the client. 94 | */ 95 | void WebSocketTransport::sendMessage(const QJsonObject &message) 96 | { 97 | QJsonDocument doc(message); 98 | m_socket->sendTextMessage(QString::fromUtf8(doc.toJson(QJsonDocument::Compact))); 99 | } 100 | 101 | /*! 102 | Deserialize the stringified JSON messageData and emit messageReceived. 103 | */ 104 | void WebSocketTransport::textMessageReceived(const QString &messageData) 105 | { 106 | QJsonParseError error; 107 | QJsonDocument message = QJsonDocument::fromJson(messageData.toUtf8(), &error); 108 | if (error.error) { 109 | qWarning() << "Failed to parse text message as JSON object:" << messageData 110 | << "Error is:" << error.errorString(); 111 | return; 112 | } else if (!message.isObject()) { 113 | qWarning() << "Received JSON message that is not an object: " << messageData; 114 | return; 115 | } 116 | // NOTE: could do some validation here 117 | emit messageReceived(message.object(), this); 118 | } 119 | -------------------------------------------------------------------------------- /eatem-server/websockettransport.h: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | ** 3 | ** Copyright (C) 2016 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Milian Wolff 4 | ** Contact: https://www.qt.io/licensing/ 5 | ** 6 | ** This file is part of the QtWebChannel module of the Qt Toolkit. 7 | ** 8 | ** $QT_BEGIN_LICENSE:BSD$ 9 | ** Commercial License Usage 10 | ** Licensees holding valid commercial Qt licenses may use this file in 11 | ** accordance with the commercial license agreement provided with the 12 | ** Software or, alternatively, in accordance with the terms contained in 13 | ** a written agreement between you and The Qt Company. For licensing terms 14 | ** and conditions see https://www.qt.io/terms-conditions. For further 15 | ** information use the contact form at https://www.qt.io/contact-us. 16 | ** 17 | ** BSD License Usage 18 | ** Alternatively, you may use this file under the terms of the BSD license 19 | ** as follows: 20 | ** 21 | ** "Redistribution and use in source and binary forms, with or without 22 | ** modification, are permitted provided that the following conditions are 23 | ** met: 24 | ** * Redistributions of source code must retain the above copyright 25 | ** notice, this list of conditions and the following disclaimer. 26 | ** * Redistributions in binary form must reproduce the above copyright 27 | ** notice, this list of conditions and the following disclaimer in 28 | ** the documentation and/or other materials provided with the 29 | ** distribution. 30 | ** * Neither the name of The Qt Company Ltd nor the names of its 31 | ** contributors may be used to endorse or promote products derived 32 | ** from this software without specific prior written permission. 33 | ** 34 | ** 35 | ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 36 | ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 37 | ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 38 | ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 39 | ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 40 | ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 41 | ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 42 | ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 43 | ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 44 | ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 45 | ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." 46 | ** 47 | ** $QT_END_LICENSE$ 48 | ** 49 | ****************************************************************************/ 50 | 51 | #ifndef WEBSOCKETTRANSPORT_H 52 | #define WEBSOCKETTRANSPORT_H 53 | 54 | #include 55 | 56 | QT_BEGIN_NAMESPACE 57 | class QWebSocket; 58 | QT_END_NAMESPACE 59 | 60 | class WebSocketTransport : public QWebChannelAbstractTransport 61 | { 62 | Q_OBJECT 63 | public: 64 | explicit WebSocketTransport(QWebSocket *socket); 65 | virtual ~WebSocketTransport(); 66 | 67 | void sendMessage(const QJsonObject &message) override; 68 | 69 | private slots: 70 | void textMessageReceived(const QString &message); 71 | 72 | private: 73 | QWebSocket *m_socket; 74 | }; 75 | 76 | #endif // WEBSOCKETTRANSPORT_H 77 | -------------------------------------------------------------------------------- /eatem-standalone/eatem-standalone.pro: -------------------------------------------------------------------------------- 1 | QT += quick 2 | CONFIG += c++11 3 | include(../core/shared.pri) 4 | 5 | # The following define makes your compiler emit warnings if you use 6 | # any feature of Qt which as been marked deprecated (the exact warnings 7 | # depend on your compiler). Please consult the documentation of the 8 | # deprecated API in order to know how to port your code away from it. 9 | DEFINES += QT_DEPRECATED_WARNINGS 10 | 11 | # You can also make your code fail to compile if you use deprecated APIs. 12 | # In order to do so, uncomment the following line. 13 | # You can also select to disable deprecated APIs only up to a certain version of Qt. 14 | #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 15 | 16 | SOURCES += \ 17 | main.cpp 18 | 19 | RESOURCES += qml.qrc 20 | 21 | # Additional import path used to resolve QML modules in Qt Creator's code model 22 | QML_IMPORT_PATH = 23 | 24 | # Additional import path used to resolve QML modules just for Qt Quick Designer 25 | QML_DESIGNER_IMPORT_PATH = 26 | 27 | # Default rules for deployment. 28 | qnx: target.path = /tmp/$${TARGET}/bin 29 | else: unix:!android: target.path = /opt/$${TARGET}/bin 30 | !isEmpty(target.path): INSTALLS += target 31 | 32 | -------------------------------------------------------------------------------- /eatem-standalone/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "gameinterface.h" 6 | #include "player.h" 7 | 8 | 9 | int main(int argc, char *argv[]) 10 | { 11 | // Enable High Dpi Scaling because .... 12 | QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); 13 | 14 | // Create our application, which controls our event loop 15 | QGuiApplication app(argc, argv); 16 | 17 | GameInterface game_interface; 18 | game_interface.set_game_size(1000, 1000); 19 | game_interface.start_game(); 20 | 21 | Player *player = new Player("AUTH", game_interface.game_size(), &game_interface); 22 | game_interface.add_player(player); 23 | 24 | // Create our QML application engine, which handles our QML 25 | QQmlApplicationEngine engine; 26 | engine.rootContext()->setContextProperty("game_interface", &game_interface); 27 | // Load our `main.qml` page 28 | engine.load(QUrl(QStringLiteral("qrc:/main.qml"))); 29 | 30 | 31 | 32 | // Check to see if we loaded the file correctly 33 | if (engine.rootObjects().isEmpty()) 34 | // if we didn't load correctly, exit the main loop with the error/integer, `-1` 35 | return -1; 36 | 37 | return app.exec(); 38 | } 39 | -------------------------------------------------------------------------------- /eatem-standalone/main.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.11 2 | import QtQuick.Controls 1.4 3 | 4 | import "../core/app.js" as App 5 | 6 | 7 | ApplicationWindow { 8 | id: window 9 | title: "eatem" 10 | visible: true 11 | 12 | Canvas { 13 | id: canvas 14 | anchors.fill: parent 15 | focus: true 16 | contextType: "2d" 17 | property color clear_color: 'white' 18 | property var context 19 | property var feed: game_interface.food 20 | property var players: game_interface.players 21 | property var viruses: game_interface.viruses 22 | property var this_player: game_interface.get_player("AUTH") 23 | property int gridSize: 30 24 | property string authentication 25 | 26 | Component.onCompleted: { 27 | for (var i = 0; i < canvas.feed.length; i++){ 28 | var hue_num = Math.round(Math.random() * 360) 29 | canvas.feed[i].hue = 'hsl(' + hue_num + ', 100%, 50%)'; 30 | } 31 | 32 | canvas.players = game_interface.players; 33 | for (i = 0; i < canvas.players.length; i++){ 34 | var hue_num = Math.round(Math.random() * 360) 35 | canvas.players[i].hue = 'hsl(' + hue_num + ', 100%, 50%)'; 36 | } 37 | 38 | } 39 | 40 | onPaint: { 41 | context = getContext("2d"); 42 | game_loop(); 43 | } 44 | 45 | function game_loop() 46 | { 47 | // You can't block with JavaScript... 48 | // So normally this would look like a `while (true):` loop 49 | // But since you never go back into the event loop with that pattern, 50 | // your user interface would hang and become unresponsive. 51 | // So instead we recursively register this same function 52 | // on the next animation frame, using the `requestAnimationFrame` method 53 | requestAnimationFrame(game_loop); 54 | 55 | // Set the fill style to clear the background 56 | context.fillStyle = clear_color; 57 | 58 | // Clear the background 59 | context.clearRect(0, 0, canvas.width, canvas.height); 60 | App.draw_objects(context, 61 | feed, 62 | players, 63 | viruses, 64 | this_player, 65 | canvas.width, 66 | canvas.height); 67 | 68 | } 69 | 70 | Keys.onSpacePressed: { 71 | var x_y = translate_mouse(mouse); 72 | this_player.request_split(x_y[0], x_y[1], "AUTH"); 73 | } 74 | 75 | Keys.onPressed: { 76 | if (event.key === Qt.Key_W) 77 | { 78 | var x_y = translate_mouse(mouse); 79 | this_player.request_fire_food(x_y[0], x_y[1], "AUTH"); 80 | event.accepted = true 81 | } 82 | } 83 | 84 | MouseArea { 85 | id: mouse 86 | anchors.fill: parent 87 | hoverEnabled: true 88 | } 89 | 90 | function translate_mouse(mouse) 91 | { 92 | return [mouse.mouseX - width/2 + this_player.x, 93 | mouse.mouseY - height/2 + this_player.y]; 94 | } 95 | 96 | Timer { 97 | id: lineTimer 98 | interval: 10 99 | repeat: true 100 | running: true 101 | onTriggered: { 102 | var x_y = canvas.translate_mouse(mouse); 103 | canvas.this_player.request_coordinates(x_y[0], x_y[1], "AUTH"); 104 | } 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /eatem-standalone/qml.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | main.qml 4 | ../core/app.js 5 | 6 | 7 | -------------------------------------------------------------------------------- /eatem-web/audio/spawn.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Qt5-Cross-Platform-Application-Development/0eba9e445b6bfd1fb64942561ce49fea3eac146f/eatem-web/audio/spawn.mp3 -------------------------------------------------------------------------------- /eatem-web/audio/split.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Qt5-Cross-Platform-Application-Development/0eba9e445b6bfd1fb64942561ce49fea3eac146f/eatem-web/audio/split.mp3 -------------------------------------------------------------------------------- /eatem-web/css/main.css: -------------------------------------------------------------------------------- 1 | * { 2 | font-family: sans-serif; 3 | font-size: 14px; 4 | } 5 | 6 | html, body { 7 | background-color: #222; 8 | } 9 | 10 | html, body, canvas { 11 | width: 100%; 12 | height: 100%; 13 | margin: 0; 14 | padding: 0; 15 | } 16 | 17 | div { 18 | -webkit-user-select: none; /* webkit (safari, chrome) browsers */ 19 | -moz-user-select: none; /* mozilla browsers */ 20 | -khtml-user-select: none; /* webkit (konqueror) browsers */ 21 | -ms-user-select: none; /* IE10+ */ 22 | } 23 | 24 | #split { 25 | position: absolute; 26 | bottom: 10px; 27 | left: 10px; 28 | width: 100px; 29 | height: 100px; 30 | padding: 5px; 31 | border: none; 32 | } 33 | 34 | #feed { 35 | position: absolute; 36 | bottom: 10px; 37 | right: 10px; 38 | width: 100px; 39 | height: 100px; 40 | padding: 5px; 41 | border: none; 42 | } 43 | 44 | #status { 45 | position: absolute; 46 | padding: 10px; 47 | background: rgba(0, 0, 0, 0.4); 48 | color: #FFF; 49 | font-size: 16.1px; 50 | top: 10px; 51 | right: 10px; 52 | font-weight: bold; 53 | text-align: center; 54 | } 55 | 56 | #status .title { 57 | font-size: 25px; 58 | } 59 | 60 | #status .me { 61 | color: #FF8888; 62 | font-size: 16.1px; 63 | } 64 | 65 | .chatbox { 66 | position: absolute; 67 | width: 300px; 68 | height: 320px; 69 | background: rgba(255, 255, 255, 0.7); 70 | bottom: 5px; 71 | left: 5px; 72 | border-radius: 5px; 73 | pointer-events: none; 74 | } 75 | 76 | .chatbox .chat-list { 77 | padding: 5px; 78 | margin: 0; 79 | list-style: none; 80 | box-sizing: border-box; 81 | height: 285px; 82 | overflow: hidden; 83 | } 84 | 85 | .chatbox .chat-list li { 86 | padding: 2px; 87 | margin: 3px; 88 | } 89 | 90 | .chatbox .chat-list li.me b { 91 | color: #ea6153; 92 | } 93 | 94 | .chatbox .chat-list li.friend b { 95 | color: #2ecc71; 96 | } 97 | 98 | .chatbox .chat-list li.system { 99 | color: #9b59b6; 100 | font-style: italic; 101 | } 102 | 103 | .chatbox .chat-list li.system:before { 104 | content: "» "; 105 | } 106 | 107 | .chatbox .chat-input { 108 | pointer-events: all; 109 | box-sizing: border-box; 110 | width: 100%; 111 | padding: 8px; 112 | background: transparent; 113 | border: none; 114 | border-top: 1px solid #DDD; 115 | outline: none; 116 | } 117 | 118 | #startMenu { 119 | position: relative; 120 | margin: auto; 121 | margin-top: 100px; 122 | width: 350px; 123 | padding: 20px; 124 | border-radius: 5px; 125 | -moz-border-radius: 5px; 126 | -webkit-border-radius: 5px; 127 | background-color: white; 128 | box-sizing: border-box; 129 | } 130 | 131 | #startMenu p { 132 | padding: 0; 133 | text-align: center; 134 | font-size: x-large; 135 | font-weight: bold; 136 | } 137 | 138 | #playerNameInput { 139 | width: 100%; 140 | text-align: center; 141 | padding: 10px; 142 | border: solid 1px #dcdcdc; 143 | transition: box-shadow 0.3s, border 0.3s; 144 | box-sizing: border-box; 145 | border-radius: 5px; 146 | -moz-border-radius: 5px; 147 | -webkit-border-radius: 5px; 148 | margin-bottom: 10px; 149 | outline: none; 150 | } 151 | 152 | #playerNameInput:focus, #playerNameInput.focus { 153 | border: solid 1px #CCCCCC; 154 | box-shadow: 0 0 3px 1px #DDDDDD; 155 | } 156 | 157 | #startButton, #spectateButton { 158 | position: relative; 159 | margin: auto; 160 | margin-top: 10px; 161 | width: 100%; 162 | height: 40px; 163 | box-sizing: border-box; 164 | font-size: large; 165 | color: white; 166 | text-align: center; 167 | text-shadow: 0 1px 2px rgba(0, 0, 0, 0.25); 168 | background: #2ecc71; 169 | border: 0; 170 | border-bottom: 2px solid #28be68; 171 | cursor: pointer; 172 | -webkit-box-shadow: inset 0 -2px #28be68; 173 | box-shadow: inset 0 -2px #28be68; 174 | border-radius: 5px; 175 | -moz-border-radius: 5px; 176 | -webkit-border-radius: 5px; 177 | margin-bottom: 10px; 178 | } 179 | 180 | #spectateButton:active, #spectateButton:hover, 181 | #startButton:active, #startButton:hover { 182 | top: 1px; 183 | background: #55D88B; 184 | outline: none; 185 | -webkit-box-shadow: none; 186 | box-shadow: none; 187 | } 188 | 189 | #settingsButton { 190 | position: relative; 191 | margin: auto; 192 | margin-top: 10px; 193 | width: 100%; 194 | height: 40px; 195 | box-sizing: border-box; 196 | font-size: large; 197 | color: white; 198 | text-align: center; 199 | text-shadow: 0 1px 2px rgba(0, 0, 0, 0.25); 200 | background: #2ecc71; 201 | border: 0; 202 | border-bottom: 2px solid #28be68; 203 | cursor: pointer; 204 | -webkit-box-shadow: inset 0 -2px #28be68; 205 | box-shadow: inset 0 -2px #28be68; 206 | border-radius: 5px; 207 | -moz-border-radius: 5px; 208 | -webkit-border-radius: 5px; 209 | margin-bottom: 10px; 210 | } 211 | 212 | #settingsButton:active, #settingsButton:hover { 213 | top: 1px; 214 | background: #55D88B; 215 | outline: none; 216 | -webkit-box-shadow: none; 217 | box-shadow: none; 218 | } 219 | 220 | #settings, #startMenuWrapper { 221 | -webkit-transition: max-height 1s; 222 | -moz-transition: max-height 1s; 223 | -ms-transition: max-height 1s; 224 | -o-transition: max-height 1s; 225 | transition: max-height 1s; 226 | overflow: hidden; 227 | } 228 | 229 | #settings { 230 | max-height: 0; 231 | } 232 | 233 | #startMenu h3 { 234 | padding-bottom: 0; 235 | margin-bottom: 0; 236 | } 237 | 238 | #startMenu ul { 239 | margin: 10px; 240 | padding: 10px; 241 | margin-top: 0; 242 | } 243 | 244 | #startMenu .input-error { 245 | color: red; 246 | opacity: 0; 247 | font-size : 12px; 248 | } 249 | 250 | #startMenuWrapper { 251 | z-index: 2; 252 | } 253 | 254 | #gameAreaWrapper { 255 | position: absolute !important; 256 | top: 0; 257 | left: 0; 258 | opacity: 0; 259 | } 260 | 261 | @media only screen and (min-width : 1224px) { 262 | #mobile { 263 | display: none; 264 | } 265 | } 266 | 267 | @media only screen and (max-width : 1224px) { 268 | #chatbox { 269 | display: none; 270 | } 271 | } 272 | 273 | input [type="image"]:focus{ 274 | border:none; 275 | outline: 1px solid transparent; 276 | border-style: none; 277 | } 278 | 279 | *:focus { 280 | outline: 1px solid transparent; 281 | border-style: none; 282 | } 283 | -------------------------------------------------------------------------------- /eatem-web/eatem_web.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = app 2 | CONFIG -= app_bundle 3 | CONFIG -= qt 4 | 5 | DISTFILES += \ 6 | index.html \ 7 | css/main.css \ 8 | js/app.js \ 9 | js/global.js \ 10 | js/lib/canvas.js \ 11 | js/global.js \ 12 | ../core/qwebchannel.js 13 | 14 | -------------------------------------------------------------------------------- /eatem-web/img/feed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Qt5-Cross-Platform-Application-Development/0eba9e445b6bfd1fb64942561ce49fea3eac146f/eatem-web/img/feed.png -------------------------------------------------------------------------------- /eatem-web/img/split.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Qt5-Cross-Platform-Application-Development/0eba9e445b6bfd1fb64942561ce49fea3eac146f/eatem-web/img/split.png -------------------------------------------------------------------------------- /eatem-web/index.html: -------------------------------------------------------------------------------- 1 | !doctype html> 2 | 3 | 4 | 5 | 6 | Open Agar 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 |
Leaderboard
17 |
18 | 19 | 20 |
21 | 22 |
23 |
24 |
25 |

Open Agar

26 | 27 | Nick must be alphanumeric characters only! 28 |
29 | 30 | 31 | 32 |
33 |
34 |

Settings

35 |
    36 | 37 | 38 |
    39 | 40 |
    41 | 42 | 43 |
44 |
45 |
46 |

Gameplay

47 |
    48 |
  • Move your mouse on the screen to move your character.
  • 49 |
  • Eat food and other players in order to grow your character (food respawns every time a player eats it).
  • 50 |
  • A player's mass is the number of food particles eaten.
  • 51 |
  • Objective: Try to get fat and eat other players.
  • 52 |
53 |
54 |
55 |
56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /eatem-web/js/app.js: -------------------------------------------------------------------------------- 1 | requirejs.config({ 2 | baseUrl: 'js', 3 | //except, if the module ID starts with "app", 4 | //load it from the js/app directory. paths 5 | //config is relative to the baseUrl, and 6 | //never includes a ".js" extension since 7 | //the paths config could be for a directory. 8 | paths: { 9 | qwebchannel: '../../core/qwebchannel', 10 | global: 'global', 11 | jquery: 'lib/jquery' 12 | 13 | } 14 | }); 15 | 16 | // Start the main app logic. 17 | requirejs(['jquery', 'lib/canvas', 'global', 'qwebchannel'], function($, Canvas, global, WebChannel) { 18 | //jQuery, canvas and the app/sub module are all 19 | //loaded and can be used here now. 20 | 21 | var playerNameInput = document.getElementById('playerNameInput'); 22 | var socket; 23 | var reason; 24 | 25 | var debug = function(args) { 26 | if (console && console.log) { 27 | console.log(args); 28 | } 29 | }; 30 | 31 | if ( /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.userAgent) ) { 32 | global.mobile = true; 33 | } 34 | 35 | function startGame(type) { 36 | global.playerName = playerNameInput.value.replace(/(<([^>]+)>)/ig, '').substring(0,25); 37 | global.playerType = type; 38 | 39 | global.screenWidth = window.innerWidth; 40 | global.screenHeight = window.innerHeight; 41 | 42 | document.getElementById('startMenuWrapper').style.maxHeight = '0px'; 43 | document.getElementById('gameAreaWrapper').style.opacity = 1; 44 | if (!socket) { 45 | socket = new WebSocket("ws://localhost:5555"); 46 | setupSocket(socket); 47 | } 48 | if (!global.animLoopHandle) 49 | animloop(); 50 | socket.emit('respawn'); 51 | window.canvas.socket = socket; 52 | global.socket = socket; 53 | } 54 | 55 | // Checks if the nick chosen contains valid alphanumeric characters (and underscores). 56 | function validNick() { 57 | var regex = /^\w*$/; 58 | debug('Regex Test', regex.exec(playerNameInput.value)); 59 | return regex.exec(playerNameInput.value) !== null; 60 | } 61 | 62 | window.onload = function() { 63 | 64 | var btn = document.getElementById('startButton'), 65 | btnS = document.getElementById('spectateButton'), 66 | nickErrorText = document.querySelector('#startMenu .input-error'); 67 | 68 | btnS.onclick = function () { 69 | startGame('spectate'); 70 | }; 71 | 72 | btn.onclick = function () { 73 | 74 | // Checks if the nick is valid. 75 | if (validNick()) { 76 | nickErrorText.style.opacity = 0; 77 | startGame('player'); 78 | } else { 79 | nickErrorText.style.opacity = 1; 80 | } 81 | }; 82 | 83 | var settingsMenu = document.getElementById('settingsButton'); 84 | var settings = document.getElementById('settings'); 85 | var instructions = document.getElementById('instructions'); 86 | 87 | settingsMenu.onclick = function () { 88 | if (settings.style.maxHeight == '300px') { 89 | settings.style.maxHeight = '0px'; 90 | } else { 91 | settings.style.maxHeight = '300px'; 92 | } 93 | }; 94 | 95 | playerNameInput.addEventListener('keypress', function (e) { 96 | var key = e.which || e.keyCode; 97 | 98 | if (key === global.KEY_ENTER) { 99 | if (validNick()) { 100 | nickErrorText.style.opacity = 0; 101 | startGame('player'); 102 | } else { 103 | nickErrorText.style.opacity = 1; 104 | } 105 | } 106 | }); 107 | }; 108 | 109 | // TODO: Break out into GameControls. 110 | 111 | var foodConfig = { 112 | border: 0, 113 | }; 114 | 115 | var playerConfig = { 116 | border: 6, 117 | textColor: '#FFFFFF', 118 | textBorder: '#000000', 119 | textBorderSize: 3, 120 | defaultSize: 30 121 | }; 122 | 123 | var player = { 124 | id: -1, 125 | x: global.screenWidth / 2, 126 | y: global.screenHeight / 2, 127 | screenWidth: global.screenWidth, 128 | screenHeight: global.screenHeight, 129 | target: {x: global.screenWidth / 2, y: global.screenHeight / 2} 130 | }; 131 | global.player = player; 132 | 133 | var foods = []; 134 | var viruses = []; 135 | var fireFood = []; 136 | var users = []; 137 | var leaderboard = []; 138 | var target = {x: player.x, y: player.y}; 139 | global.target = target; 140 | 141 | window.canvas = new Canvas(); 142 | 143 | var visibleBorderSetting = document.getElementById('visBord'); 144 | visibleBorderSetting.onchange = settings.toggleBorder; 145 | 146 | var showMassSetting = document.getElementById('showMass'); 147 | showMassSetting.onchange = settings.toggleMass; 148 | 149 | var continuitySetting = document.getElementById('continuity'); 150 | continuitySetting.onchange = settings.toggleContinuity; 151 | 152 | var roundFoodSetting = document.getElementById('roundFood'); 153 | roundFoodSetting.onchange = settings.toggleRoundFood; 154 | 155 | var c = window.canvas.cv; 156 | var graph = c.getContext('2d'); 157 | 158 | $( "#feed" ).click(function() { 159 | socket.emit('1'); 160 | window.canvas.reenviar = false; 161 | }); 162 | 163 | $( "#split" ).click(function() { 164 | socket.emit('2'); 165 | window.canvas.reenviar = false; 166 | }); 167 | 168 | // socket stuff. 169 | function setupSocket(socket) { 170 | // Handle error. 171 | socket.onerror = function(){ 172 | console.error("web channel closed") 173 | global.disconnected = true; 174 | } 175 | 176 | /* 177 | socket.on('disconnect', function () { 178 | socket.close(); 179 | global.disconnected = true; 180 | }); 181 | */ 182 | 183 | // Handle connection. 184 | socket.onopen = function(){ 185 | new QWebChannel(socket, function(channel) { 186 | var game_interface = channel.objectgs.interface; 187 | game_interface.food; 188 | game_interface.players; 189 | game_interface.viruses; 190 | 191 | }); 192 | 193 | player = playerSettings; 194 | player.name = global.playerName; 195 | player.screenWidth = global.screenWidth; 196 | player.screenHeight = global.screenHeight; 197 | player.target = window.canvas.target; 198 | global.player = player; 199 | // socket.emit('gotit', player); 200 | global.gameStart = true; 201 | debug('Game started at: ' + global.gameStart); 202 | c.focus(); 203 | }; 204 | 205 | // FIXME 206 | socket.on('gameSetup', function(data) { 207 | global.gameWidth = data.gameWidth; 208 | global.gameHeight = data.gameHeight; 209 | resize(); 210 | }); 211 | 212 | socket.on('leaderboard', function (data) { 213 | leaderboard = data.leaderboard; 214 | var status = 'Leaderboard'; 215 | for (var i = 0; i < leaderboard.length; i++) { 216 | status += '
'; 217 | if (leaderboard[i].id == player.id){ 218 | if(leaderboard[i].name.length !== 0) 219 | status += '' + (i + 1) + '. ' + leaderboard[i].name + ""; 220 | else 221 | status += '' + (i + 1) + ". An unnamed cell"; 222 | } else { 223 | if(leaderboard[i].name.length !== 0) 224 | status += (i + 1) + '. ' + leaderboard[i].name; 225 | else 226 | status += (i + 1) + '. An unnamed cell'; 227 | } 228 | } 229 | //status += '
Players: ' + data.players; 230 | document.getElementById('status').innerHTML = status; 231 | }); 232 | 233 | // Handle movement. 234 | socket.on('serverTellPlayerMove', function (userData, foodsList, massList, virusList) { 235 | var playerData; 236 | for(var i =0; i< userData.length; i++) { 237 | if(typeof(userData[i].id) == "undefined") { 238 | playerData = userData[i]; 239 | i = userData.length; 240 | } 241 | } 242 | if(global.playerType == 'player') { 243 | var xoffset = player.x - playerData.x; 244 | var yoffset = player.y - playerData.y; 245 | 246 | player.x = playerData.x; 247 | player.y = playerData.y; 248 | player.hue = playerData.hue; 249 | player.massTotal = playerData.massTotal; 250 | player.cells = playerData.cells; 251 | player.xoffset = isNaN(xoffset) ? 0 : xoffset; 252 | player.yoffset = isNaN(yoffset) ? 0 : yoffset; 253 | } 254 | users = userData; 255 | foods = foodsList; 256 | viruses = virusList; 257 | fireFood = massList; 258 | }); 259 | 260 | // Death. 261 | socket.on('RIP', function () { 262 | global.gameStart = false; 263 | global.died = true; 264 | window.setTimeout(function() { 265 | document.getElementById('gameAreaWrapper').style.opacity = 0; 266 | document.getElementById('startMenuWrapper').style.maxHeight = '1000px'; 267 | global.died = false; 268 | if (global.animLoopHandle) { 269 | window.cancelAnimationFrame(global.animLoopHandle); 270 | global.animLoopHandle = undefined; 271 | } 272 | }, 2500); 273 | }); 274 | 275 | socket.on('kick', function (data) { 276 | global.gameStart = false; 277 | reason = data; 278 | global.kicked = true; 279 | socket.close(); 280 | }); 281 | 282 | socket.on('virusSplit', function (virusCell) { 283 | socket.emit('2', virusCell); 284 | reenviar = false; 285 | }); 286 | } 287 | 288 | function drawCircle(centerX, centerY, radius, sides) { 289 | var theta = 0; 290 | var x = 0; 291 | var y = 0; 292 | 293 | graph.beginPath(); 294 | 295 | for (var i = 0; i < sides; i++) { 296 | theta = (i / sides) * 2 * Math.PI; 297 | x = centerX + radius * Math.sin(theta); 298 | y = centerY + radius * Math.cos(theta); 299 | graph.lineTo(x, y); 300 | } 301 | 302 | graph.closePath(); 303 | graph.stroke(); 304 | graph.fill(); 305 | } 306 | 307 | function drawFood(food) { 308 | graph.strokeStyle = 'hsl(' + food.hue + ', 100%, 45%)'; 309 | graph.fillStyle = 'hsl(' + food.hue + ', 100%, 50%)'; 310 | graph.lineWidth = foodConfig.border; 311 | drawCircle(food.x - player.x + global.screenWidth / 2, 312 | food.y - player.y + global.screenHeight / 2, 313 | food.radius, global.foodSides); 314 | } 315 | 316 | function drawVirus(virus) { 317 | graph.strokeStyle = virus.stroke; 318 | graph.fillStyle = virus.fill; 319 | graph.lineWidth = virus.strokeWidth; 320 | drawCircle(virus.x - player.x + global.screenWidth / 2, 321 | virus.y - player.y + global.screenHeight / 2, 322 | virus.radius, global.virusSides); 323 | } 324 | 325 | function drawFireFood(mass) { 326 | graph.strokeStyle = 'hsl(' + mass.hue + ', 100%, 45%)'; 327 | graph.fillStyle = 'hsl(' + mass.hue + ', 100%, 50%)'; 328 | graph.lineWidth = playerConfig.border+10; 329 | drawCircle(mass.x - player.x + global.screenWidth / 2, 330 | mass.y - player.y + global.screenHeight / 2, 331 | mass.radius-5, 18 + (~~(mass.masa/5))); 332 | } 333 | 334 | function drawPlayers(order) { 335 | var start = { 336 | x: player.x - (global.screenWidth / 2), 337 | y: player.y - (global.screenHeight / 2) 338 | }; 339 | 340 | for(var z=0; z= player.radius/ 3) inc = -1; 385 | *if (wiggle <= player.radius / -3) inc = +1; 386 | *wiggle += inc; 387 | */ 388 | for (i = 0; i < points; ++i) { 389 | if (i === 0) { 390 | graph.beginPath(); 391 | graph.moveTo(xstore[i], ystore[i]); 392 | } else if (i > 0 && i < points - 1) { 393 | graph.lineTo(xstore[i], ystore[i]); 394 | } else { 395 | graph.lineTo(xstore[i], ystore[i]); 396 | graph.lineTo(xstore[0], ystore[0]); 397 | } 398 | 399 | } 400 | graph.lineJoin = 'round'; 401 | graph.lineCap = 'round'; 402 | graph.fill(); 403 | graph.stroke(); 404 | var nameCell = ""; 405 | if(typeof(userCurrent.id) == "undefined") 406 | nameCell = player.name; 407 | else 408 | nameCell = userCurrent.name; 409 | 410 | var fontSize = Math.max(cellCurrent.radius / 3, 12); 411 | graph.lineWidth = playerConfig.textBorderSize; 412 | graph.fillStyle = playerConfig.textColor; 413 | graph.strokeStyle = playerConfig.textBorder; 414 | graph.miterLimit = 1; 415 | graph.lineJoin = 'round'; 416 | graph.textAlign = 'center'; 417 | graph.textBaseline = 'middle'; 418 | graph.font = 'bold ' + fontSize + 'px sans-serif'; 419 | 420 | if (global.toggleMassState === 0) { 421 | graph.strokeText(nameCell, circle.x, circle.y); 422 | graph.fillText(nameCell, circle.x, circle.y); 423 | } else { 424 | graph.strokeText(nameCell, circle.x, circle.y); 425 | graph.fillText(nameCell, circle.x, circle.y); 426 | graph.font = 'bold ' + Math.max(fontSize / 3 * 2, 10) + 'px sans-serif'; 427 | if(nameCell.length === 0) fontSize = 0; 428 | graph.strokeText(Math.round(cellCurrent.mass), circle.x, circle.y+fontSize); 429 | graph.fillText(Math.round(cellCurrent.mass), circle.x, circle.y+fontSize); 430 | } 431 | } 432 | } 433 | 434 | function valueInRange(min, max, value) { 435 | return Math.min(max, Math.max(min, value)); 436 | } 437 | 438 | function drawgrid() { 439 | graph.lineWidth = 1; 440 | graph.strokeStyle = global.lineColor; 441 | graph.globalAlpha = 0.15; 442 | graph.beginPath(); 443 | 444 | for (var x = global.xoffset - player.x; x < global.screenWidth; x += global.screenHeight / 18) { 445 | graph.moveTo(x, 0); 446 | graph.lineTo(x, global.screenHeight); 447 | } 448 | 449 | for (var y = global.yoffset - player.y ; y < global.screenHeight; y += global.screenHeight / 18) { 450 | graph.moveTo(0, y); 451 | graph.lineTo(global.screenWidth, y); 452 | } 453 | 454 | graph.stroke(); 455 | graph.globalAlpha = 1; 456 | } 457 | 458 | function drawborder() { 459 | graph.lineWidth = 1; 460 | graph.strokeStyle = playerConfig.borderColor; 461 | 462 | // Left-vertical. 463 | if (player.x <= global.screenWidth/2) { 464 | graph.beginPath(); 465 | graph.moveTo(global.screenWidth/2 - player.x, 0 ? player.y > global.screenHeight/2 : global.screenHeight/2 - player.y); 466 | graph.lineTo(global.screenWidth/2 - player.x, global.gameHeight + global.screenHeight/2 - player.y); 467 | graph.strokeStyle = global.lineColor; 468 | graph.stroke(); 469 | } 470 | 471 | // Top-horizontal. 472 | if (player.y <= global.screenHeight/2) { 473 | graph.beginPath(); 474 | graph.moveTo(0 ? player.x > global.screenWidth/2 : global.screenWidth/2 - player.x, global.screenHeight/2 - player.y); 475 | graph.lineTo(global.gameWidth + global.screenWidth/2 - player.x, global.screenHeight/2 - player.y); 476 | graph.strokeStyle = global.lineColor; 477 | graph.stroke(); 478 | } 479 | 480 | // Right-vertical. 481 | if (global.gameWidth - player.x <= global.screenWidth/2) { 482 | graph.beginPath(); 483 | graph.moveTo(global.gameWidth + global.screenWidth/2 - player.x, 484 | global.screenHeight/2 - player.y); 485 | graph.lineTo(global.gameWidth + global.screenWidth/2 - player.x, 486 | global.gameHeight + global.screenHeight/2 - player.y); 487 | graph.strokeStyle = global.lineColor; 488 | graph.stroke(); 489 | } 490 | 491 | // Bottom-horizontal. 492 | if (global.gameHeight - player.y <= global.screenHeight/2) { 493 | graph.beginPath(); 494 | graph.moveTo(global.gameWidth + global.screenWidth/2 - player.x, 495 | global.gameHeight + global.screenHeight/2 - player.y); 496 | graph.lineTo(global.screenWidth/2 - player.x, 497 | global.gameHeight + global.screenHeight/2 - player.y); 498 | graph.strokeStyle = global.lineColor; 499 | graph.stroke(); 500 | } 501 | } 502 | 503 | window.requestAnimFrame = (function() { 504 | return window.requestAnimationFrame || 505 | window.webkitRequestAnimationFrame || 506 | window.mozRequestAnimationFrame || 507 | window.msRequestAnimationFrame || 508 | function( callback ) { 509 | window.setTimeout(callback, 1000 / 60); 510 | }; 511 | })(); 512 | 513 | window.cancelAnimFrame = (function(handle) { 514 | return window.cancelAnimationFrame || 515 | window.mozCancelAnimationFrame; 516 | })(); 517 | 518 | function animloop() { 519 | global.animLoopHandle = window.requestAnimFrame(animloop); 520 | gameLoop(); 521 | } 522 | 523 | function gameLoop() { 524 | if (global.died) { 525 | graph.fillStyle = '#333333'; 526 | graph.fillRect(0, 0, global.screenWidth, global.screenHeight); 527 | 528 | graph.textAlign = 'center'; 529 | graph.fillStyle = '#FFFFFF'; 530 | graph.font = 'bold 30px sans-serif'; 531 | graph.fillText('You died!', global.screenWidth / 2, global.screenHeight / 2); 532 | } 533 | else if (!global.disconnected) { 534 | if (global.gameStart) { 535 | graph.fillStyle = global.backgroundColor; 536 | graph.fillRect(0, 0, global.screenWidth, global.screenHeight); 537 | 538 | drawgrid(); 539 | foods.forEach(drawFood); 540 | fireFood.forEach(drawFireFood); 541 | viruses.forEach(drawVirus); 542 | 543 | if (global.borderDraw) { 544 | drawborder(); 545 | } 546 | var orderMass = []; 547 | for(var i=0; i-1&&(!e[i]||!t(e[i],i,e));i-=1);}}function hasProp(e,t){return hasOwn.call(e,t)}function getOwn(e,t){return hasProp(e,t)&&e[t]}function eachProp(e,t){var i;for(i in e)if(hasProp(e,i)&&t(e[i],i))break}function mixin(e,t,i,r){return t&&eachProp(t,function(t,n){!i&&hasProp(e,n)||(!r||"object"!=typeof t||!t||isArray(t)||isFunction(t)||t instanceof RegExp?e[n]=t:(e[n]||(e[n]={}),mixin(e[n],t,i,r)))}),e}function bind(e,t){return function(){return t.apply(e,arguments)}}function scripts(){return document.getElementsByTagName("script")}function defaultOnError(e){throw e}function getGlobal(e){if(!e)return e;var t=global;return each(e.split("."),function(e){t=t[e]}),t}function makeError(e,t,i,r){var n=new Error(t+"\nhttp://requirejs.org/docs/errors.html#"+e);return n.requireType=e,n.requireModules=r,i&&(n.originalError=i),n}function newContext(e){function t(e){var t,i;for(t=0;t0&&(e.splice(t-1,2),t-=2)}}function i(e,i,r){var n,o,a,s,u,c,d,p,f,l,h=i&&i.split("/"),m=y.map,g=m&&m["*"];if(e&&(c=(e=e.split("/")).length-1,y.nodeIdCompat&&jsSuffixRegExp.test(e[c])&&(e[c]=e[c].replace(jsSuffixRegExp,"")),"."===e[0].charAt(0)&&h&&(e=h.slice(0,h.length-1).concat(e)),t(e),e=e.join("/")),r&&m&&(h||g)){e:for(a=(o=e.split("/")).length;a>0;a-=1){if(u=o.slice(0,a).join("/"),h)for(s=h.length;s>0;s-=1)if((n=getOwn(m,h.slice(0,s).join("/")))&&(n=getOwn(n,u))){d=n,p=a;break e}!f&&g&&getOwn(g,u)&&(f=getOwn(g,u),l=a)}!d&&f&&(d=f,p=l),d&&(o.splice(0,p,d),e=o.join("/"))}return getOwn(y.pkgs,e)||e}function r(e){isBrowser&&each(scripts(),function(t){if(t.getAttribute("data-requiremodule")===e&&t.getAttribute("data-requirecontext")===q.contextName)return t.parentNode.removeChild(t),!0})}function n(e){var t=getOwn(y.paths,e);if(t&&isArray(t)&&t.length>1)return t.shift(),q.require.undef(e),q.makeRequire(null,{skipMap:!0})([e]),!0}function o(e){var t,i=e?e.indexOf("!"):-1;return i>-1&&(t=e.substring(0,i),e=e.substring(i+1,e.length)),[t,e]}function a(e,t,r,n){var a,s,u,c,d=null,p=t?t.name:null,f=e,l=!0,h="";return e||(l=!1,e="_@r"+(T+=1)),c=o(e),d=c[0],e=c[1],d&&(d=i(d,p,n),s=getOwn(j,d)),e&&(d?h=r?e:s&&s.normalize?s.normalize(e,function(e){return i(e,p,n)}):-1===e.indexOf("!")?i(e,p,n):e:(d=(c=o(h=i(e,p,n)))[0],h=c[1],r=!0,a=q.nameToUrl(h))),u=!d||s||r?"":"_unnormalized"+(A+=1),{prefix:d,name:h,parentMap:t,unnormalized:!!u,url:a,originalName:f,isDefine:l,id:(d?d+"!"+h:h)+u}}function s(e){var t=e.id,i=getOwn(S,t);return i||(i=S[t]=new q.Module(e)),i}function u(e,t,i){var r=e.id,n=getOwn(S,r);!hasProp(j,r)||n&&!n.defineEmitComplete?(n=s(e)).error&&"error"===t?i(n.error):n.on(t,i):"defined"===t&&i(j[r])}function c(e,t){var i=e.requireModules,r=!1;t?t(e):(each(i,function(t){var i=getOwn(S,t);i&&(i.error=e,i.events.error&&(r=!0,i.emit("error",e)))}),r||req.onError(e))}function d(){globalDefQueue.length&&(each(globalDefQueue,function(e){var t=e[0];"string"==typeof t&&(q.defQueueMap[t]=!0),O.push(e)}),globalDefQueue=[])}function p(e){delete S[e],delete k[e]}function f(e,t,i){var r=e.map.id;e.error?e.emit("error",e.error):(t[r]=!0,each(e.depMaps,function(r,n){var o=r.id,a=getOwn(S,o);!a||e.depMatched[n]||i[o]||(getOwn(t,o)?(e.defineDep(n,j[o]),e.check()):f(a,t,i))}),i[r]=!0)}function l(){var e,t,i=1e3*y.waitSeconds,o=i&&q.startTime+i<(new Date).getTime(),a=[],s=[],u=!1,d=!0;if(!x){if(x=!0,eachProp(k,function(e){var i=e.map,c=i.id;if(e.enabled&&(i.isDefine||s.push(e),!e.error))if(!e.inited&&o)n(c)?(t=!0,u=!0):(a.push(c),r(c));else if(!e.inited&&e.fetched&&i.isDefine&&(u=!0,!i.prefix))return d=!1}),o&&a.length)return e=makeError("timeout","Load timeout for modules: "+a,null,a),e.contextName=q.contextName,c(e);d&&each(s,function(e){f(e,{},{})}),o&&!t||!u||!isBrowser&&!isWebWorker||w||(w=setTimeout(function(){w=0,l()},50)),x=!1}}function h(e){hasProp(j,e[0])||s(a(e[0],null,!0)).init(e[1],e[2])}function m(e,t,i,r){e.detachEvent&&!isOpera?r&&e.detachEvent(r,t):e.removeEventListener(i,t,!1)}function g(e){var t=e.currentTarget||e.srcElement;return m(t,q.onScriptLoad,"load","onreadystatechange"),m(t,q.onScriptError,"error"),{node:t,id:t&&t.getAttribute("data-requiremodule")}}function v(){var e;for(d();O.length;){if(null===(e=O.shift())[0])return c(makeError("mismatch","Mismatched anonymous define() module: "+e[e.length-1]));h(e)}q.defQueueMap={}}var x,b,q,E,w,y={waitSeconds:7,baseUrl:"./",paths:{},bundles:{},pkgs:{},shim:{},config:{}},S={},k={},M={},O=[],j={},P={},R={},T=1,A=1;return E={require:function(e){return e.require?e.require:e.require=q.makeRequire(e.map)},exports:function(e){if(e.usingExports=!0,e.map.isDefine)return e.exports?j[e.map.id]=e.exports:e.exports=j[e.map.id]={}},module:function(e){return e.module?e.module:e.module={id:e.map.id,uri:e.map.url,config:function(){return getOwn(y.config,e.map.id)||{}},exports:e.exports||(e.exports={})}}},b=function(e){this.events=getOwn(M,e.id)||{},this.map=e,this.shim=getOwn(y.shim,e.id),this.depExports=[],this.depMaps=[],this.depMatched=[],this.pluginMaps={},this.depCount=0},b.prototype={init:function(e,t,i,r){r=r||{},this.inited||(this.factory=t,i?this.on("error",i):this.events.error&&(i=bind(this,function(e){this.emit("error",e)})),this.depMaps=e&&e.slice(0),this.errback=i,this.inited=!0,this.ignore=r.ignore,r.enabled||this.enabled?this.enable():this.check())},defineDep:function(e,t){this.depMatched[e]||(this.depMatched[e]=!0,this.depCount-=1,this.depExports[e]=t)},fetch:function(){if(!this.fetched){this.fetched=!0,q.startTime=(new Date).getTime();var e=this.map;if(!this.shim)return e.prefix?this.callPlugin():this.load();q.makeRequire(this.map,{enableBuildCallback:!0})(this.shim.deps||[],bind(this,function(){return e.prefix?this.callPlugin():this.load()}))}},load:function(){var e=this.map.url;P[e]||(P[e]=!0,q.load(this.map.id,e))},check:function(){if(this.enabled&&!this.enabling){var e,t,i=this.map.id,r=this.depExports,n=this.exports,o=this.factory;if(this.inited){if(this.error)this.emit("error",this.error);else if(!this.defining){if(this.defining=!0,this.depCount<1&&!this.defined){if(isFunction(o)){if(this.events.error&&this.map.isDefine||req.onError!==defaultOnError)try{n=q.execCb(i,o,r,n)}catch(t){e=t}else n=q.execCb(i,o,r,n);if(this.map.isDefine&&void 0===n&&((t=this.module)?n=t.exports:this.usingExports&&(n=this.exports)),e)return e.requireMap=this.map,e.requireModules=this.map.isDefine?[this.map.id]:null,e.requireType=this.map.isDefine?"define":"require",c(this.error=e)}else n=o;if(this.exports=n,this.map.isDefine&&!this.ignore&&(j[i]=n,req.onResourceLoad)){var a=[];each(this.depMaps,function(e){a.push(e.normalizedMap||e)}),req.onResourceLoad(q,this.map,a)}p(i),this.defined=!0}this.defining=!1,this.defined&&!this.defineEmitted&&(this.defineEmitted=!0,this.emit("defined",this.exports),this.defineEmitComplete=!0)}}else hasProp(q.defQueueMap,i)||this.fetch()}},callPlugin:function(){var e=this.map,t=e.id,r=a(e.prefix);this.depMaps.push(r),u(r,"defined",bind(this,function(r){var n,o,d,f=getOwn(R,this.map.id),l=this.map.name,h=this.map.parentMap?this.map.parentMap.name:null,m=q.makeRequire(e.parentMap,{enableBuildCallback:!0});return this.map.unnormalized?(r.normalize&&(l=r.normalize(l,function(e){return i(e,h,!0)})||""),o=a(e.prefix+"!"+l,this.map.parentMap,!0),u(o,"defined",bind(this,function(e){this.map.normalizedMap=o,this.init([],function(){return e},null,{enabled:!0,ignore:!0})})),void((d=getOwn(S,o.id))&&(this.depMaps.push(o),this.events.error&&d.on("error",bind(this,function(e){this.emit("error",e)})),d.enable()))):f?(this.map.url=q.nameToUrl(f),void this.load()):((n=bind(this,function(e){this.init([],function(){return e},null,{enabled:!0})})).error=bind(this,function(e){this.inited=!0,this.error=e,e.requireModules=[t],eachProp(S,function(e){0===e.map.id.indexOf(t+"_unnormalized")&&p(e.map.id)}),c(e)}),n.fromText=bind(this,function(i,r){var o=e.name,u=a(o),d=useInteractive;r&&(i=r),d&&(useInteractive=!1),s(u),hasProp(y.config,t)&&(y.config[o]=y.config[t]);try{req.exec(i)}catch(e){return c(makeError("fromtexteval","fromText eval for "+t+" failed: "+e,e,[t]))}d&&(useInteractive=!0),this.depMaps.push(u),q.completeLoad(o),m([o],n)}),void r.load(e.name,m,n,y))})),q.enable(r,this),this.pluginMaps[r.id]=r},enable:function(){k[this.map.id]=this,this.enabled=!0,this.enabling=!0,each(this.depMaps,bind(this,function(e,t){var i,r,n;if("string"==typeof e){if(e=a(e,this.map.isDefine?this.map:this.map.parentMap,!1,!this.skipMap),this.depMaps[t]=e,n=getOwn(E,e.id))return void(this.depExports[t]=n(this));this.depCount+=1,u(e,"defined",bind(this,function(e){this.undefed||(this.defineDep(t,e),this.check())})),this.errback?u(e,"error",bind(this,this.errback)):this.events.error&&u(e,"error",bind(this,function(e){this.emit("error",e)}))}i=e.id,r=S[i],hasProp(E,i)||!r||r.enabled||q.enable(e,this)})),eachProp(this.pluginMaps,bind(this,function(e){var t=getOwn(S,e.id);t&&!t.enabled&&q.enable(e,this)})),this.enabling=!1,this.check()},on:function(e,t){var i=this.events[e];i||(i=this.events[e]=[]),i.push(t)},emit:function(e,t){each(this.events[e],function(e){e(t)}),"error"===e&&delete this.events[e]}},q={config:y,contextName:e,registry:S,defined:j,urlFetched:P,defQueue:O,defQueueMap:{},Module:b,makeModuleMap:a,nextTick:req.nextTick,onError:c,configure:function(e){if(e.baseUrl&&"/"!==e.baseUrl.charAt(e.baseUrl.length-1)&&(e.baseUrl+="/"),"string"==typeof e.urlArgs){var t=e.urlArgs;e.urlArgs=function(e,i){return(-1===i.indexOf("?")?"?":"&")+t}}var i=y.shim,r={paths:!0,bundles:!0,config:!0,map:!0};eachProp(e,function(e,t){r[t]?(y[t]||(y[t]={}),mixin(y[t],e,!0,!0)):y[t]=e}),e.bundles&&eachProp(e.bundles,function(e,t){each(e,function(e){e!==t&&(R[e]=t)})}),e.shim&&(eachProp(e.shim,function(e,t){isArray(e)&&(e={deps:e}),!e.exports&&!e.init||e.exportsFn||(e.exportsFn=q.makeShimExports(e)),i[t]=e}),y.shim=i),e.packages&&each(e.packages,function(e){var t;t=(e="string"==typeof e?{name:e}:e).name,e.location&&(y.paths[t]=e.location),y.pkgs[t]=e.name+"/"+(e.main||"main").replace(currDirRegExp,"").replace(jsSuffixRegExp,"")}),eachProp(S,function(e,t){e.inited||e.map.unnormalized||(e.map=a(t,null,!0))}),(e.deps||e.callback)&&q.require(e.deps||[],e.callback)},makeShimExports:function(e){return function(){var t;return e.init&&(t=e.init.apply(global,arguments)),t||e.exports&&getGlobal(e.exports)}},makeRequire:function(t,n){function o(i,r,u){var d,p,f;return n.enableBuildCallback&&r&&isFunction(r)&&(r.__requireJsBuild=!0),"string"==typeof i?isFunction(r)?c(makeError("requireargs","Invalid require call"),u):t&&hasProp(E,i)?E[i](S[t.id]):req.get?req.get(q,i,t,o):(p=a(i,t,!1,!0),d=p.id,hasProp(j,d)?j[d]:c(makeError("notloaded",'Module name "'+d+'" has not been loaded yet for context: '+e+(t?"":". Use require([])")))):(v(),q.nextTick(function(){v(),(f=s(a(null,t))).skipMap=n.skipMap,f.init(i,r,u,{enabled:!0}),l()}),o)}return n=n||{},mixin(o,{isBrowser:isBrowser,toUrl:function(e){var r,n=e.lastIndexOf("."),o=e.split("/")[0],a="."===o||".."===o;return-1!==n&&(!a||n>1)&&(r=e.substring(n,e.length),e=e.substring(0,n)),q.nameToUrl(i(e,t&&t.id,!0),r,!0)},defined:function(e){return hasProp(j,a(e,t,!1,!0).id)},specified:function(e){return e=a(e,t,!1,!0).id,hasProp(j,e)||hasProp(S,e)}}),t||(o.undef=function(e){d();var i=a(e,t,!0),n=getOwn(S,e);n.undefed=!0,r(e),delete j[e],delete P[i.url],delete M[e],eachReverse(O,function(t,i){t[0]===e&&O.splice(i,1)}),delete q.defQueueMap[e],n&&(n.events.defined&&(M[e]=n.events),p(e))}),o},enable:function(e){getOwn(S,e.id)&&s(e).enable()},completeLoad:function(e){var t,i,r,o=getOwn(y.shim,e)||{},a=o.exports;for(d();O.length;){if(null===(i=O.shift())[0]){if(i[0]=e,t)break;t=!0}else i[0]===e&&(t=!0);h(i)}if(q.defQueueMap={},r=getOwn(S,e),!t&&!hasProp(j,e)&&r&&!r.inited){if(!(!y.enforceDefine||a&&getGlobal(a)))return n(e)?void 0:c(makeError("nodefine","No define call for "+e,null,[e]));h([e,o.deps||[],o.exportsFn])}l()},nameToUrl:function(e,t,i){var r,n,o,a,s,u,c,d=getOwn(y.pkgs,e);if(d&&(e=d),c=getOwn(R,e))return q.nameToUrl(c,t,i);if(req.jsExtRegExp.test(e))s=e+(t||"");else{for(r=y.paths,o=(n=e.split("/")).length;o>0;o-=1)if(a=n.slice(0,o).join("/"),u=getOwn(r,a)){isArray(u)&&(u=u[0]),n.splice(0,o,u);break}s=n.join("/"),s=("/"===(s+=t||(/^data\:|^blob\:|\?/.test(s)||i?"":".js")).charAt(0)||s.match(/^[\w\+\.\-]+:/)?"":y.baseUrl)+s}return y.urlArgs&&!/^blob\:/.test(s)?s+y.urlArgs(e,s):s},load:function(e,t){req.load(q,e,t)},execCb:function(e,t,i,r){return t.apply(r,i)},onScriptLoad:function(e){if("load"===e.type||readyRegExp.test((e.currentTarget||e.srcElement).readyState)){interactiveScript=null;var t=g(e);q.completeLoad(t.id)}},onScriptError:function(e){var t=g(e);if(!n(t.id)){var i=[];return eachProp(S,function(e,r){0!==r.indexOf("_@r")&&each(e.depMaps,function(e){if(e.id===t.id)return i.push(r),!0})}),c(makeError("scripterror",'Script error for "'+t.id+(i.length?'", needed by: '+i.join(", "):'"'),e,[t.id]))}}},q.require=q.makeRequire(),q}function getInteractiveScript(){return interactiveScript&&"interactive"===interactiveScript.readyState?interactiveScript:(eachReverse(scripts(),function(e){if("interactive"===e.readyState)return interactiveScript=e}),interactiveScript)}var req,s,head,baseElement,dataMain,src,interactiveScript,currentlyAddingScript,mainScript,subPath,version="2.3.5",commentRegExp=/\/\*[\s\S]*?\*\/|([^:"'=]|^)\/\/.*$/gm,cjsRequireRegExp=/[^.]\s*require\s*\(\s*["']([^'"\s]+)["']\s*\)/g,jsSuffixRegExp=/\.js$/,currDirRegExp=/^\.\//,op=Object.prototype,ostring=op.toString,hasOwn=op.hasOwnProperty,isBrowser=!("undefined"==typeof window||"undefined"==typeof navigator||!window.document),isWebWorker=!isBrowser&&"undefined"!=typeof importScripts,readyRegExp=isBrowser&&"PLAYSTATION 3"===navigator.platform?/^complete$/:/^(complete|loaded)$/,defContextName="_",isOpera="undefined"!=typeof opera&&"[object Opera]"===opera.toString(),contexts={},cfg={},globalDefQueue=[],useInteractive=!1;if(void 0===define){if(void 0!==requirejs){if(isFunction(requirejs))return;cfg=requirejs,requirejs=void 0}void 0===require||isFunction(require)||(cfg=require,require=void 0),req=requirejs=function(e,t,i,r){var n,o,a=defContextName;return isArray(e)||"string"==typeof e||(o=e,isArray(t)?(e=t,t=i,i=r):e=[]),o&&o.context&&(a=o.context),(n=getOwn(contexts,a))||(n=contexts[a]=req.s.newContext(a)),o&&n.configure(o),n.require(e,t,i)},req.config=function(e){return req(e)},req.nextTick=void 0!==setTimeout?function(e){setTimeout(e,4)}:function(e){e()},require||(require=req),req.version=version,req.jsExtRegExp=/^\/|:|\?|\.js$/,req.isBrowser=isBrowser,s=req.s={contexts:contexts,newContext:newContext},req({}),each(["toUrl","undef","defined","specified"],function(e){req[e]=function(){var t=contexts[defContextName];return t.require[e].apply(t,arguments)}}),isBrowser&&(head=s.head=document.getElementsByTagName("head")[0],(baseElement=document.getElementsByTagName("base")[0])&&(head=s.head=baseElement.parentNode)),req.onError=defaultOnError,req.createNode=function(e,t,i){var r=e.xhtml?document.createElementNS("http://www.w3.org/1999/xhtml","html:script"):document.createElement("script");return r.type=e.scriptType||"text/javascript",r.charset="utf-8",r.async=!0,r},req.load=function(e,t,i){var r,n=e&&e.config||{};if(isBrowser)return(r=req.createNode(n,t,i)).setAttribute("data-requirecontext",e.contextName),r.setAttribute("data-requiremodule",t),!r.attachEvent||r.attachEvent.toString&&r.attachEvent.toString().indexOf("[native code")<0||isOpera?(r.addEventListener("load",e.onScriptLoad,!1),r.addEventListener("error",e.onScriptError,!1)):(useInteractive=!0,r.attachEvent("onreadystatechange",e.onScriptLoad)),r.src=i,n.onNodeCreated&&n.onNodeCreated(r,n,t,i),currentlyAddingScript=r,baseElement?head.insertBefore(r,baseElement):head.appendChild(r),currentlyAddingScript=null,r;if(isWebWorker)try{setTimeout(function(){},0),importScripts(i),e.completeLoad(t)}catch(r){e.onError(makeError("importscripts","importScripts failed for "+t+" at "+i,r,[t]))}},isBrowser&&!cfg.skipDataMain&&eachReverse(scripts(),function(e){if(head||(head=e.parentNode),dataMain=e.getAttribute("data-main"))return mainScript=dataMain,cfg.baseUrl||-1!==mainScript.indexOf("!")||(src=mainScript.split("/"),mainScript=src.pop(),subPath=src.length?src.join("/")+"/":"./",cfg.baseUrl=subPath),mainScript=mainScript.replace(jsSuffixRegExp,""),req.jsExtRegExp.test(mainScript)&&(mainScript=dataMain),cfg.deps=cfg.deps?cfg.deps.concat(mainScript):[mainScript],!0}),define=function(e,t,i){var r,n;"string"!=typeof e&&(i=t,t=e,e=null),isArray(t)||(i=t,t=null),!t&&isFunction(i)&&(t=[],i.length&&(i.toString().replace(commentRegExp,commentReplace).replace(cjsRequireRegExp,function(e,i){t.push(i)}),t=(1===i.length?["require"]:["require","exports","module"]).concat(t))),useInteractive&&(r=currentlyAddingScript||getInteractiveScript())&&(e||(e=r.getAttribute("data-requiremodule")),n=contexts[r.getAttribute("data-requirecontext")]),n?(n.defQueue.push([e,t,i]),n.defQueueMap[e]=!0):globalDefQueue.push([e,t,i])},define.amd={jQuery:!0},req.exec=function(text){return eval(text)},req(cfg)}}(this,"undefined"==typeof setTimeout?void 0:setTimeout); -------------------------------------------------------------------------------- /eatem/eatem.pro: -------------------------------------------------------------------------------- 1 | QT += quick 2 | CONFIG += c++11 3 | 4 | # The following define makes your compiler emit warnings if you use 5 | # any feature of Qt which as been marked deprecated (the exact warnings 6 | # depend on your compiler). Please consult the documentation of the 7 | # deprecated API in order to know how to port your code away from it. 8 | DEFINES += QT_DEPRECATED_WARNINGS 9 | 10 | # You can also make your code fail to compile if you use deprecated APIs. 11 | # In order to do so, uncomment the following line. 12 | # You can also select to disable deprecated APIs only up to a certain version of Qt. 13 | #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 14 | 15 | SOURCES += \ 16 | main.cpp 17 | 18 | RESOURCES += qml.qrc 19 | 20 | # Additional import path used to resolve QML modules in Qt Creator's code model 21 | QML_IMPORT_PATH = 22 | 23 | # Additional import path used to resolve QML modules just for Qt Quick Designer 24 | QML_DESIGNER_IMPORT_PATH = 25 | 26 | # Default rules for deployment. 27 | qnx: target.path = /tmp/$${TARGET}/bin 28 | else: unix:!android: target.path = /opt/$${TARGET}/bin 29 | !isEmpty(target.path): INSTALLS += target 30 | 31 | -------------------------------------------------------------------------------- /eatem/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | 6 | int main(int argc, char *argv[]) 7 | { 8 | // Enable High Dpi Scaling because .... 9 | QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); 10 | 11 | // Create our application, which controls our event loop 12 | QGuiApplication app(argc, argv); 13 | 14 | // Create our QML application engine, which handles our QML 15 | QQmlApplicationEngine engine; 16 | // Load our `main.qml` page 17 | engine.load(QUrl(QStringLiteral("qrc:/main.qml"))); 18 | 19 | // Check to see if we loaded the file correctly 20 | if (engine.rootObjects().isEmpty()) 21 | // if we didn't load correctly, exit the main loop with the error/integer, `-1` 22 | return -1; 23 | 24 | return app.exec(); 25 | } 26 | -------------------------------------------------------------------------------- /eatem/main.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.11 2 | import QtQuick.Controls 1.4 3 | 4 | import "../core/app.js" as App 5 | import "../core/qwebchannel.js" as WebChannel 6 | import Qt.WebSockets 1.0 7 | 8 | 9 | ApplicationWindow { 10 | id: window 11 | title: "eatem" 12 | visible: true 13 | property var game_interface 14 | property string authentication; 15 | 16 | WebSocket { 17 | id: socket 18 | active: true 19 | url: "ws://localhost:5555" 20 | 21 | property var send: function(arg) { 22 | sendTextMessage(arg); 23 | } 24 | 25 | onTextMessageReceived: { 26 | onmessage({data: message}); 27 | } 28 | 29 | property var onmessage 30 | 31 | function set_auth_code(new_auth) 32 | { 33 | window.authentication = new_auth; 34 | 35 | } 36 | 37 | onStatusChanged: { 38 | switch (socket.status) { 39 | case WebSocket.Error: 40 | console.error("Error: " + socket.errorString); 41 | break; 42 | case WebSocket.Closed: 43 | console.error("Error: Socket at " + url + " closed."); 44 | break; 45 | case WebSocket.Open: 46 | //open the webchannel with the socket as transport 47 | var webchannel = new WebChannel.QWebChannel(socket, set_auth_code, function(channel) { 48 | var game_interface = channel.objects.interface; 49 | window.game_interface = game_interface; 50 | canvas.feed = game_interface.food; 51 | 52 | var hue_num; 53 | 54 | for (var i = 0; i < canvas.feed.length; i++){ 55 | hue_num = Math.round(Math.random() * 360) 56 | canvas.feed[i].hue = 'hsl(' + hue_num + ', 100%, 50%)'; 57 | } 58 | 59 | canvas.players = game_interface.players; 60 | for (i = 0; i < canvas.players.length; i++){ 61 | hue_num = Math.round(Math.random() * 360) 62 | canvas.players[i].hue = 'hsl(' + hue_num + ', 100%, 50%)'; 63 | } 64 | 65 | canvas.viruses = game_interface.viruses; 66 | game_interface.new_virus.connect(function(new_virus){ 67 | canvas.viruses.push(new_virus); 68 | }); 69 | 70 | game_interface.new_player.connect(function(new_player){ 71 | canvas.players.push(new_player); 72 | }); 73 | 74 | game_interface.new_food.connect(function(new_food){ 75 | canvas.feed.push(new_food); 76 | }); 77 | 78 | game_interface.get_player(authentication, function(this_player){ 79 | canvas.this_player = this_player; 80 | canvas.running = true; 81 | }); 82 | 83 | }); 84 | break; 85 | } 86 | } 87 | } 88 | 89 | 90 | Canvas { 91 | id: canvas 92 | anchors.fill: parent 93 | focus: true 94 | contextType: "2d" 95 | property color clear_color: 'white' 96 | property var context 97 | property var feed 98 | property var players 99 | property var viruses 100 | property var this_player 101 | property int gridSize: 30 102 | property string authentication 103 | property bool running: false 104 | 105 | onRunningChanged: 106 | { 107 | game_loop(); 108 | lineTimer.start(); 109 | } 110 | 111 | onPaint: { 112 | context = getContext("2d"); 113 | } 114 | 115 | function game_loop() 116 | { 117 | // You can't block with JavaScript... 118 | // So normally this would look like a `while (true):` loop 119 | // But since you never go back into the event loop with that pattern, 120 | // your user interface would hang and become unresponsive. 121 | // So instead we recursively register this same function 122 | // on the next animation frame, using the `requestAnimationFrame` method 123 | requestAnimationFrame(game_loop); 124 | 125 | // Set the fill style to clear the background 126 | context.fillStyle = clear_color; 127 | 128 | // Clear the background 129 | context.clearRect(0, 0, canvas.width, canvas.height); 130 | // draw_grid(); 131 | 132 | App.draw_objects(context, 133 | feed, 134 | players, 135 | viruses, 136 | this_player, 137 | canvas.width, 138 | canvas.height); 139 | } 140 | 141 | Keys.onSpacePressed: { 142 | if (!running) 143 | return; 144 | 145 | var x_y = translate_mouse(mouse); 146 | this_player.request_split(x_y[0], x_y[1], window.authentication); 147 | } 148 | 149 | Keys.onPressed: { 150 | if (!running) 151 | return; 152 | 153 | if (event.key === Qt.Key_W) 154 | { 155 | var x_y = translate_mouse(mouse); 156 | this_player.request_fire_food(x_y[0], x_y[1], window.authentication); 157 | event.accepted = true 158 | } 159 | } 160 | 161 | MouseArea { 162 | id: mouse 163 | anchors.fill: parent 164 | hoverEnabled: true 165 | } 166 | 167 | function translate_mouse(mouse) 168 | { 169 | return [mouse.mouseX - width/2 + this_player.x, 170 | mouse.mouseY - height/2 + this_player.y]; 171 | } 172 | 173 | Timer { 174 | id: lineTimer 175 | interval: 10 176 | repeat: true 177 | onTriggered: { 178 | var x_y = canvas.translate_mouse(mouse); 179 | canvas.this_player.request_coordinates(x_y[0], x_y[1], window.authentication); 180 | } 181 | } 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /eatem/qml.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | main.qml 4 | ../core/app.js 5 | ../core/qwebchannel.js 6 | 7 | 8 | --------------------------------------------------------------------------------