This is the C++ source code for the "Arkanoid" demo. It was compiled by GCC and then converted into C# code by Mips2cs.
/* * FILE: * bouncy_ball_game.cpp * * PURPOSE: * example game using coercri + xna * * AUTHOR: * Stephen Thompson <stephen@solarflare.org.uk> * * CREATED: * 23-Jun-2011 * * COPYRIGHT: * Copyright (C) Stephen Thompson, 2011. * * This file is part of Mips2cs. Mips2cs is distributed under the terms * of the Boost Software License, Version 1.0, the text of which * appears below. * * Boost Software License - Version 1.0 - August 17th, 2003 * * Permission is hereby granted, free of charge, to any person or organization * obtaining a copy of the software and accompanying documentation covered by * this license (the "Software") to use, reproduce, display, distribute, * execute, and transmit the Software, and to prepare derivative works of the * Software, and to permit third-parties to whom the Software is furnished to * do so, all subject to the following: * * The copyright notices in the Software and this entire statement, including * the above license grant, this restriction and the following disclaimer, * must be included in all copies of the Software, in whole or in part, and * all derivative works of the Software, unless such copies or derivative * works are solely in the form of machine-executable object code generated by * a source language processor. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ #include "gfx/gfx_context.hpp" #include "gfx/pixel_array.hpp" #include "gfx/window.hpp" #include "gfx/window_listener.hpp" #ifdef USE_SDL #include "sdl/gfx/sdl_gfx_driver.hpp" #include "sdl/timer/sdl_timer.hpp" #elif defined(USE_XNA) #include "xna_gfx_driver.hpp" #include "xna_timer.hpp" #endif #include "boost/shared_ptr.hpp" #include <cmath> #include <cstring> #include <exception> #include <fstream> #include <iostream> #include <list> using namespace boost; using namespace Coercri; using namespace std; // Constants const int DT_MSEC = 10; const float DT = float(DT_MSEC) / 1000; const float BRICK_SIZE[2] = { 30, 20 }; const float BAT_SIZE[2] = { 75, 10 }; const float BALL_RADIUS = 5; const float PLAYFIELD_SIZE[2] = { 25*BRICK_SIZE[0], 25*BRICK_SIZE[1] }; const int BAT_Y = int(PLAYFIELD_SIZE[1] - 3*BRICK_SIZE[1]); const int BAT_MIN_X = 0; const int BAT_MAX_X = int(PLAYFIELD_SIZE[0] - BAT_SIZE[0]); const float PI = 4 * atan(1.0f); inline float DEG_TO_RAD(float deg) { return deg / 180.0f * PI; } // Bricks struct Brick { float x; float y; }; // Game class -- handles the game itself class Game : public WindowListener { public: explicit Game(GfxDriver &drv) : bat_pos(50), ball_speed(450), window_closed(false), game_running(false), last_time(0), window_active(true) { // create brick graphic const Color light(255, 180, 180), mid (255, 0, 0), dark (128, 0, 0); const int bw = int(BRICK_SIZE[0]), bh = int(BRICK_SIZE[1]); shared_ptr<PixelArray> parr(new PixelArray(bw, bh)); for (int i = 0; i < bw; ++i) { for (int j = 0; j < bh; ++j) { if (i==0 || j == 0) { (*parr)(i,j) = light; } else if (i==bw-1 || j==bh-1) { (*parr)(i,j) = dark; } else { (*parr)(i,j) = mid; } } } brick_graphic = drv.createGraphic(parr); // create ball graphic const Color ball_color(255, 255, 255); const int ball_size = int(BALL_RADIUS + 1); parr.reset(new PixelArray(2*ball_size+1, 2*ball_size+1)); for (int i = -ball_size; i <= ball_size; ++i) { for (int j = -ball_size; j <= ball_size; ++j) { const float r = std::sqrt(float(i*i+j*j)); // Make alpha vary from 255 at ball_radius - 0.5 to 0 at ball_radius + 0.5 Color col(ball_color); if (r < BALL_RADIUS - 0.5f) { col.a = 255; } else if (r > BALL_RADIUS + 0.5f) { col.a = 0; } else { col.a = (unsigned char)(((BALL_RADIUS+0.5f)-r)*255); } (*parr)(i+ball_size, j+ball_size) = col; } } ball_graphic = drv.createGraphic(parr, ball_size, ball_size); // create bat graphic const Color blight(255, 255, 255), bmid (190, 190, 190), bdark (128, 128, 128); const int batw = int(BAT_SIZE[0]), bath = int(BAT_SIZE[1]); parr.reset(new PixelArray(batw, bath)); for (int i = 0; i < batw; ++i) { for (int j = 0; j < bath; ++j) { if (i==0 || j==0) { (*parr)(i,j) = blight; } else if (i==batw-1 || j==bath-1) { (*parr)(i,j) = bdark; } else { (*parr)(i,j) = bmid; } } } bat_graphic = drv.createGraphic(parr); } void setupLevel() { ball_pos[0] = 10; ball_pos[1] = 200; ball_vel[0] = sqrt(0.5f) * ball_speed; ball_vel[1] = sqrt(0.5f) * ball_speed; for (int i = 2; i < int(PLAYFIELD_SIZE[0]/BRICK_SIZE[0])-2; ++i) { Brick b; b.x = i*BRICK_SIZE[0]; b.y = 3*BRICK_SIZE[1]; bricks.push_back(b); b.y = 7*BRICK_SIZE[1]; bricks.push_back(b); } } // clips the line (x,y)+lambda*(dx,dy), lambda_min<=lambda<=lambda_max // against the plane nx*x + ny*y >= a. // sets changed_min and/or changed_max to TRUE if lambda_min or lambda_max was changed. // sets rejected to TRUE and leaves lambda_min, lambda_max unchanged if the line is clipped out totally. void clipLine(float x, float y, float dx, float dy, float &lambda_min, float &lambda_max, float nx, float ny, float a, bool &changed_min, bool &changed_max, bool &rejected) { const float tol = 1e-4; changed_min = changed_max = rejected = false; const float dist_at_min = nx*(x+lambda_min*dx) + ny*(y+lambda_min*dy) - a; const float dist_at_max = nx*(x+lambda_max*dx) + ny*(y+lambda_max*dy) - a; if (dist_at_min < -tol && dist_at_max < -tol) { // Both endpoints are outside the good region. Reject. rejected = true; } else if (dist_at_min > tol && dist_at_max > tol) { // Both endpoints are inside the half-region. // Do nothing. } else if (fabs(dist_at_min) < tol && fabs(dist_at_max) < tol) { // The line segment is parallel to one of the boundaries. We let this one go. // Do nothing. } else { // One endpoint is inside and one is outside. const float lambda_intercept = (a - nx*x - ny*y) / (nx*dx + ny*dy); if (dist_at_min > tol || dist_at_max < -tol) { // MIN is on the good side, MAX on the bad side, so clip max lambda_max = lambda_intercept; changed_max = true; } else { // MAX is on the good side, MIN on the bad side, so clip min lambda_min = lambda_intercept; changed_min = true; } } } // returns axis==-1, lambda=1 on miss // on hit, returns lambda as hit point, and axis=0 if hit left/right side, or axis=1 if hit top/bottom side. void findCollision(float x, float y, float dx, float dy, float bx, float by, float bw, float bh, float &lambda, int &axis) { float lambda_min = 0, lambda_max = 1; // Clip line (x,y) + lambda*(dx,dy) to the interior of the brick. bool changed_min, changed_max, rejected; axis = -1; clipLine(x, y, dx, dy, lambda_min, lambda_max, 1, 0, bx, changed_min, changed_max, rejected); if (changed_min) axis = 0; if (!rejected) { clipLine(x, y, dx, dy, lambda_min, lambda_max, -1, 0, -bx-bw, changed_min, changed_max, rejected); if (changed_min) axis = 0; if (!rejected) { clipLine(x, y, dx, dy, lambda_min, lambda_max, 0, 1, by, changed_min, changed_max, rejected); if (changed_min) axis = 1; if (!rejected) { clipLine(x, y, dx, dy, lambda_min, lambda_max, 0, -1, -by-bh, changed_min, changed_max, rejected); if (changed_min) axis = 1; } } } if (rejected) { // Line was totally clipped out. We missed the brick. lambda = 1; axis = -1; } else if (axis == -1) { // This means we started inside the brick. Let it go lambda = 1; axis = -1; } else { lambda = lambda_min; // axis was set above. } } void moveBall(float amount) { for (int ntries = 0; ntries < 100; ++ntries) { // This is the line segment that we are trying to move the ball through const float x = ball_pos[0]; const float y = ball_pos[1]; const float dx = ball_vel[0] * amount; const float dy = ball_vel[1] * amount; // Check for collisions with bricks. // Note, v. inefficient algorithm, we literally just loop through the bricks and check each one. // It would be better to use some sort of culling first (ie only check bricks which are "near" the ball). std::list<Brick>::iterator which_brick = bricks.end(); int coll_axis = -1; float best_lambda = 1; bool hit_bat = false; for (std::list<Brick>::iterator it = bricks.begin(); it != bricks.end(); ++it) { float lambda; int axis; findCollision(x, y, dx, dy, it->x, it->y, BRICK_SIZE[0], BRICK_SIZE[1], lambda, axis); if (axis != -1 && lambda < best_lambda) { best_lambda = lambda; coll_axis = axis; which_brick = it; } } // Check for collision with bat { float lambda; int axis; findCollision(x, y, dx, dy, bat_pos, BAT_Y, BAT_SIZE[0], BAT_SIZE[1], lambda, axis); if (lambda < best_lambda) { best_lambda = lambda; coll_axis = axis; which_brick = bricks.end(); hit_bat = true; } } // Move the ball by best_lambda. for (int i = 0; i < 2; ++i) ball_pos[i] += ball_vel[i] * amount * best_lambda; // Check for out-of-bounds, and reflect velocity if necessary for (int i = 0; i < 2; ++i) { if (ball_pos[i] < 0) ball_vel[i] = fabs(ball_vel[i]); if (ball_pos[i] > PLAYFIELD_SIZE[i]) ball_vel[i] = -fabs(ball_vel[i]); } // If there is no collision then we are done if (coll_axis == -1) break; // OK we have to do a collision with either a brick or the bat. if (hit_bat && coll_axis==1 && ball_vel[1] > 0) { // bounce off the top edge of the bat. const float INTERP_FACTOR = 0.6f; const float MIN_ANGLE = DEG_TO_RAD(20); const float MAX_ANGLE = DEG_TO_RAD(70); // work out current ball angle. float angle = atan(ball_vel[0]/ball_vel[1]); const float absangle = abs(angle); // work out target angle (based on where it hit the bat). const float mu = 2*((ball_pos[0] - bat_pos) / BAT_SIZE[0]) - 1; // from -1 to 1 const float absmu = fabs(mu); const float sgnmu = mu<0 ? -1 : 1; float target_absangle; if (absmu < 0.3f) target_absangle = DEG_TO_RAD(15); else if (absmu < 0.7f) target_absangle = DEG_TO_RAD(45); else target_absangle = DEG_TO_RAD(75); // interpolate current and target to get the post-bounce angle. const float new_absangle = (1-INTERP_FACTOR) * absangle + INTERP_FACTOR * target_absangle; angle = sgnmu * new_absangle; // apply min/max limits. if (angle < -MAX_ANGLE) angle = -MAX_ANGLE; else if (angle > -MIN_ANGLE && angle <= 0) angle = -MIN_ANGLE; else if (angle >= 0 && angle < MIN_ANGLE) angle = MIN_ANGLE; else if (angle > MAX_ANGLE) angle = MAX_ANGLE; // set ball_vel accordingly ball_vel[0] = sin(angle) * ball_speed; ball_vel[1] = -cos(angle) * ball_speed; } else { // bounce off a brick (or side of the bat or something). ball_vel[coll_axis] = -ball_vel[coll_axis]; if (which_brick != bricks.end()) { const float SPEEDUP = 1.01f; bricks.erase(which_brick); for (int i = 0; i < 2; ++i) ball_vel[i] *= SPEEDUP; ball_speed *= SPEEDUP; } } amount *= (1-best_lambda); // Loop again with reduced amount (for the rest of the timestep). } } // event handlers void onClose() { window_closed = true; } void onMouseMove(int x, int y) { bat_pos = x - BAT_SIZE[0]/2; if (bat_pos < BAT_MIN_X) bat_pos = BAT_MIN_X; if (bat_pos > BAT_MAX_X) bat_pos = BAT_MAX_X; } void onActivate() { window_active = true; } void onDeactivate() { window_active = false; } // update bool update(unsigned int time_now, Window &window) { if (!game_running) return false; if (time_now - last_time > (unsigned int) (DT_MSEC)) { if (window_active) { moveBall(DT); window.invalidateAll(); } last_time += DT_MSEC; return true; } else { return false; } } void catchUp(unsigned int time_now) { while (time_now - last_time > (unsigned int) DT_MSEC) last_time += DT_MSEC; } // draw void draw(GfxContext &gc) { if (!game_running) return; gc.clearScreen(Color(0,0,0)); gc.drawGraphic(int(ball_pos[0]), int(ball_pos[1]), *ball_graphic); for (std::list<Brick>::const_iterator it = bricks.begin(); it != bricks.end(); ++it) { gc.drawGraphic(int(it->x), int(it->y), *brick_graphic); } gc.drawGraphic(int(bat_pos), BAT_Y, *bat_graphic); } // miscellaneous functions bool windowClosed() const { return window_closed; } void startGame(Timer &timer) { game_running = true; setupLevel(); last_time = timer.getMsec(); } void stopGame() { game_running = false; } bool isRunning() const { return game_running; } private: float ball_vel[2]; float ball_pos[2]; float bat_pos; float ball_speed; std::list<Brick> bricks; shared_ptr<Graphic> brick_graphic, ball_graphic, bat_graphic; bool window_closed; bool game_running; unsigned int last_time; bool window_active; }; extern "C" int main(int argc, char **argv) { try { // Initialize GfxDriver, Timer #ifdef USE_SDL shared_ptr<GfxDriver> gfx_driver(new SDLGfxDriver); shared_ptr<Timer> timer(new SDLTimer); #elif defined(USE_XNA) shared_ptr<GfxDriver> gfx_driver(new XNAGfxDriver); shared_ptr<Timer> timer(new XNATimer); #endif // Create the Window shared_ptr<Window> my_window = gfx_driver->createWindow(750, 500, true, false, "Bouncy Ball Game"); // Create the Game and add it to the window Game game(*gfx_driver); my_window->addWindowListener(&game); // Start the game game.startGame(*timer); // Main Loop. while (!game.windowClosed()) { // Process all pending events. bool did_something = false; while (gfx_driver->pollEvents()) did_something = true; // Run game update if needed int nupdates = 5; const unsigned int time_now = timer->getMsec(); while (nupdates > 0 && game.update(time_now, *my_window)) { did_something = true; --nupdates; } // "catch up" the timer if necessary. game.catchUp(timer->getMsec()); // If window is invalid then repaint it if (my_window->needsRepaint()) { std::auto_ptr<GfxContext> gc = my_window->createGfxContext(); game.draw(*gc); my_window->cancelInvalidRegion(); did_something = true; } // If nothing was done then we should sleep for a bit. if (!did_something) timer->sleepMsec(10); } } catch (std::exception &e) { cout << "caught exception: " << e.what() << endl; } catch (...) { cout << "caught unknown exception" << endl; } return 0; }