エンジニアのソフトウェア的愛情

または私は如何にして心配するのを止めてプログラムを・愛する・ようになったか

わかりやすいコード・その5

  • エイリアンが攻撃してくるようになりました
  • エイリアンの弾にあったたら即終了

プログラムを実現する、という目的の他に、タイトルにもしている「わかりやすいコード」が実現できているか、考えながら進行中。個人的にはBounds(0, 0, width, height).includes(position_)というのが、意味通りにコードを書けた気がしているところ(BoundsよりBoundaryの方がいいかもしれませんが)。

以下、コード。

#include <cstdlib>
#include <functional>
#include <algorithm>
#include <vector>
#include <sstream>
#include <iomanip>
#include <iostream>

#include <GLUT/glut.h>

static const int Interval = 100;
static const int Width    = 320;
static const int Height   = 240;

static int width  = Width;
static int height = Height;

class RGBColor
{
public:
    RGBColor()
    {
        set(0, 0, 0);
    }

    RGBColor(GLdouble r, GLdouble g, GLdouble b)
    {
        set(r, g, b);
    }

    void set(GLdouble r, GLdouble g, GLdouble b)
    {
        rgb[0] = r;
        rgb[1] = g;
        rgb[2] = b;
    }

    const GLdouble* get() const { return rgb; }

    void     setRed(GLdouble   value) { rgb[0] = value; }
    void     setGreen(GLdouble value) { rgb[1] = value; }
    void     setBlue(GLdouble  value) { rgb[2] = value; }
    GLdouble getRed() const           { return rgb[0];  }
    GLdouble getGreen() const         { return rgb[1];  }
    GLdouble getBlue() const          { return rgb[2];  }

private:
    GLdouble rgb[3];
};

struct Point
{
    int x;
    int y;

    Point() : x(0), y(0) {}
    Point(int x, int y) : x(x), y(y) {}

    Point& operator += (const Point& point) { x += point.x; y += point.y; return *this; }
    Point& operator -= (const Point& point) { x -= point.x; y -= point.y; return *this; }
};

Point operator + (Point lhs, const Point& rhs)
{
    lhs += rhs;
    return lhs;
}

Point operator - (Point lhs, const Point& rhs)
{
    lhs -= rhs;
    return lhs;
}

std::ostream& operator << (std::ostream& out, const Point& point)
{
    return out << "(" << point.x << ", " << point.y << ")";
}

typedef std::vector<Point> Points;

class Bounds
{
public:
    Bounds() : leftTop_(0, 0), rightBottom_(0, 0) {}

    Bounds(const Point& leftTop, const Point& rightBottom) : leftTop_(leftTop), rightBottom_(rightBottom)
    {
        rearranges();
    }

    Bounds(int x0, int y0, int x1, int y1) : leftTop_(x0, y0), rightBottom_(x1, y1)
    {
        rearranges();
    }

    const Point& getLeftTop() const     { return leftTop_;                          }
    const Point  getRightTop() const    { return Point(leftTop_.x, rightBottom_.y); }
    const Point  getLeftBottom() const  { return Point(rightBottom_.x, leftTop_.y); }
    const Point& getRightBottom() const { return rightBottom_;                      }

    bool includes(const Point& point) const
    {
        return (leftTop_.x <= point.x) && (point.x <= rightBottom_.x) &&
               (leftTop_.y <= point.y) && (point.y <= rightBottom_.y);
    }

    bool isOverlapWith(const Bounds& bounds) const
    {
        return includes(bounds.getLeftTop())    || includes(bounds.getRightTop())    ||
               includes(bounds.getLeftBottom()) || includes(bounds.getRightBottom()) ||
               bounds.includes(getLeftTop())    || bounds.includes(getRightTop())    ||
               bounds.includes(getLeftBottom()) || bounds.includes(getRightBottom()); 
    }

private:
    void rearranges()
    {
        if(rightBottom_.x < leftTop_.x) { std::swap(leftTop_.x, rightBottom_.x); }
        if(rightBottom_.y < leftTop_.y) { std::swap(leftTop_.y, rightBottom_.y); }
    }

    Point leftTop_;
    Point rightBottom_;
};

std::ostream& operator << (std::ostream& out, const Bounds& bounds)
{
    return out << bounds.getLeftTop() << "-" << bounds.getRightBottom();
}

struct Vertex : public std::unary_function<const Point&, void>
{
    explicit Vertex(const Point& origin) : origin_(origin) {}

    void operator() (const Point& point)
    {
        glVertex2i(origin_.x + point.x, origin_.y + point.y);
    }

    const Point origin_;
};

class Shape
{
public:
    void addPoint(const Point& point)
    {
        points_.push_back(point);
    }

    void draw(const Point& position, const RGBColor& color, GLenum mode)
    {
        glColor3dv(color.get());
        glBegin(mode);
        std::for_each(points_.begin(), points_.end(), Vertex(position));
        glEnd();
    }

private:
    Points points_;
};

class Object
{
public:
    explicit Object(const Point& position) : position_(position)
    {
    }

    virtual ~Object()
    {
    }

    const Point& getPosition() const
    {
        return position_;
    }

    void setPosition(const Point& position)
    {
        position_ = position;
    }

    virtual Bounds getBounds() const = 0;

    virtual bool isVisible() const
    {
        return Bounds(0, 0, width, height).includes(position_);
    }

    void move(const Point& delta)
    {
        position_.x += delta.x;
        position_.y += delta.y;
    }

    void drawWithColor(const RGBColor& color)
    {
        shape_.draw(position_, color, GL_POLYGON);
    }

    virtual void draw() = 0;

protected:
    void addPoint(const Point& point)
    {
        shape_.addPoint(point);
    }

private:
    Shape shape_;
    Point position_;
};

class Bullet : public Object
{
public:
    Bullet() : Object(Point(0, 0)), deltaY_(0), color_(), visible_(false)
    {
    }

    Bullet(const Point& point, int delta, const RGBColor& color) :
        Object(point), deltaY_(delta), color_(color), visible_(true)
    {
        addPoint(Point(-3,  -5));
        addPoint(Point( 0, -10));
        addPoint(Point( 3,  -5));
        addPoint(Point( 0,   0));
    }

    ~Bullet()
    {
    }

    void move()
    {
        if(visible_)
        {
            Object::move(Point(0, deltaY_));
            visible_ = Object::isVisible();
        }
    }

    void setVisible(bool value)
    {
        visible_ = value;
    }

    bool isVisible() const
    {
        return visible_;
    }

    Bounds getBounds() const
    {
        return Bounds(getPosition() + Point(-3, -10), getPosition() + Point(3, 0));
    }

    void draw()
    {
        if(visible_)
        {
            drawWithColor(color_);
        }
    }

private:
    int      deltaY_;
    RGBColor color_;
    bool     visible_;
};

class Ship : public Object
{
public:
    Ship() : Object(Point(Width / 2, Height / 2))
    {
        addPoint(Point(-10,   0));
        addPoint(Point(  0, -10));
        addPoint(Point( 10,   0));
        addPoint(Point( 10,  10));
        addPoint(Point(-10,  10));
    }

    static const int Delta = 10;

    void moveLeft()  { move(Point(-Delta, 0)); }
    void moveRight() { move(Point(+Delta, 0)); }
    void moveUp()    { move(Point(0, -Delta)); }
    void moveDown()  { move(Point(0, +Delta)); }

    Bullet fire()
    {
        return Bullet(getPosition(), -10, RGBColor(0, 0, 1));
    }

    Bounds getBounds() const
    {
        return Bounds(getPosition() + Point(-10, -10), getPosition() + Point(10, 10));
    }

    void draw()
    {
        drawWithColor(RGBColor(0, 0, 1));
    }
};

class Alien : public Object
{
public:
    Alien() : Object(Point(20, 20)), deltaX_(5)
    {
        addPoint(Point( -5, -5));
        addPoint(Point(  5, -5));
        addPoint(Point( 10,  0));
        addPoint(Point(  5,  5));
        addPoint(Point(  2,  2));
        addPoint(Point( -2,  2));
        addPoint(Point( -5,  5));
        addPoint(Point(-10,  0));
    }

    ~Alien()
    {
    }

    Bounds getBounds() const
    {
        return Bounds(getPosition() + Point(-10, -5), getPosition() + Point(10, 5));
    }

    Bullet fire()
    {
        return Bullet(getPosition(), 10, RGBColor(1, 0.5, 0));
    }

    void turn()
    {
        deltaX_ = -deltaX_;
    }

    void move()
    {
        Object::move(Point(deltaX_, 0));
        if((getPosition().x < 0) || (width < getPosition().x))
        {
            turn();
            Object::move(Point(0, 10));
        }
    }

    void draw()
    {
        drawWithColor(RGBColor(1, 0.5, 0));
    }

private:
    int deltaX_;
};

struct Overlap : public std::unary_function<const Object&, bool>
{
    const Bounds& bounds;

    explicit Overlap(const Bounds& bounds) : bounds(bounds) {}

    bool operator() (const Object& object)
    {
        return object.getBounds().isOverlapWith(bounds);
    }
};

class Aliens
{
public:
    void add()
    {
        aliens_.push_back(Alien());
    }

    bool isShotBy(const Object& object)
    {
        if( ! object.isVisible())
        {
            return false;
        }

        std::vector<Alien>::iterator i = std::find_if(aliens_.begin(), aliens_.end(), Overlap(object.getBounds()));
        if(i != aliens_.end())
        {
            aliens_.erase(i);
            return true;
        }
        return false;
    }

    bool hasShot(const Object& object)
    {
        return std::find_if(bullets_.begin(), bullets_.end(), Overlap(object.getBounds())) != bullets_.end();
    }

    void draw()
    {
        std::for_each(aliens_.begin(), aliens_.end(), std::mem_fun_ref(&Alien::draw));
        std::for_each(bullets_.begin(), bullets_.end(), std::mem_fun_ref(&Bullet::draw));
    }

    void move()
    {
        std::for_each(aliens_.begin(), aliens_.end(), std::mem_fun_ref(&Alien::move));
        std::for_each(bullets_.begin(), bullets_.end(), std::mem_fun_ref(&Bullet::move));
        bullets_.erase(remove_if(bullets_.begin(), bullets_.end(), std::not1(std::mem_fun_ref(&Bullet::isVisible))), bullets_.end());

        for(std::vector<Alien>::iterator i = aliens_.begin(); i != aliens_.end(); ++i)
        {
            if((std::rand() % 20) == 0) // エイリアン1匹当たりの発射頻度
            {
                bullets_.push_back(i->fire());
            }
        }
    }

private:
    std::vector<Alien>  aliens_;
    std::vector<Bullet> bullets_;
};

//-- こっから先がゲームのコード

static Ship   myShip;
static Bullet myBullet;
static Aliens aliens;
static int    score = 0;

void drawString(int x, int y, const char* text)
{
    glColor3dv(RGBColor(1, 0, 0).get());
    glPushMatrix();
    glTranslatef(x, y, 0);
    glScalef(0.10, -0.10, 0);
    for(const char* p = text; *p != '?0'; ++p)
    {
        glutStrokeCharacter(GLUT_STROKE_ROMAN, *p);
    }
    glPopMatrix();
 }

void display()
{
    glClear(GL_COLOR_BUFFER_BIT);

    myShip.draw();
    myBullet.draw();
    aliens.draw();

    glutSwapBuffers();
}

void resize(int w, int h)
{
    glViewport(0, 0, w, h);
    width  = w;
    height = h;
    glLoadIdentity();
    glOrtho(-0.5, w - 0.5, h - 0.5, -0.5, -1.0, 1.0);
}

void keyboard(unsigned char key, int, int)
{
    switch(key)
    {
    case ' ':
        if( ! myBullet.isVisible())
        {
            myBullet = myShip.fire();
        }
        break;

    case 'q':
    case 'Q':
    case '?033':
        std::exit(0);
        break;

    default:
        break;
    }
}

void specialKey(int key, int x, int y)
{
    switch(key)
    {
    case GLUT_KEY_LEFT:  myShip.moveLeft();  break;
    case GLUT_KEY_UP:    myShip.moveUp();    break;
    case GLUT_KEY_RIGHT: myShip.moveRight(); break;
    case GLUT_KEY_DOWN:  myShip.moveDown();  break;
    default:                                 break;
    }
}

void timer(int)
{
    glClear(GL_COLOR_BUFFER_BIT);

    if(aliens.isShotBy(myBullet))
    {
        score += 10;
        myBullet.setVisible(false);
    }

    if(aliens.hasShot(myShip))
    {
        exit(0);
    }

    if((std::rand() % 20) == 0) // エイリアン発生頻度
    {
        aliens.add();
    }

    myBullet.move();
    aliens.move();

    myShip.draw();
    myBullet.draw();
    aliens.draw();

    std::stringstream oss;
    oss.fill('0');
    oss << "SCORE : " << std::setw(10) << score;

    drawString(10, 12, oss.str().c_str());

    glutSwapBuffers();

    glutTimerFunc(Interval, timer, 0);
}

int main(int argc, char* argv[])
{
    glutInitWindowPosition(100, 100);
    glutInitWindowSize(width, height);
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE);

    glutCreateWindow("to write more comprehensible code");

    glutDisplayFunc(display);
    glutReshapeFunc(resize);
    glutKeyboardFunc(keyboard);
    glutSpecialFunc(specialKey);
    glutTimerFunc(Interval, timer, 0);

    glClearColor(1.0, 1.0, 1.0, 1.0);

    glutMainLoop();

    return 0;
}