diff options
82 files changed, 4185 insertions, 1 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index b73f9797..e7fa34de 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -95,6 +95,7 @@ option( BUILD_KTUBERLING "Build ktuberling" ${BUILD_ALL} ) option( BUILD_LSKAT "Build lskat" ${BUILD_ALL} ) option( BUILD_TWIN4 "Build twin4" ${BUILD_ALL} ) option( BUILD_TDEFIFTEEN "Build tdefifteen" ${BUILD_ALL} ) +option( BUILD_KUE "Build kue" ${BUILD_ALL} ) ##### configure checks @@ -186,6 +187,7 @@ tde_conditional_add_subdirectory( BUILD_KTUBERLING ktuberling ) tde_conditional_add_subdirectory( BUILD_LSKAT lskat ) tde_conditional_add_subdirectory( BUILD_TWIN4 twin4 ) tde_conditional_add_subdirectory( BUILD_TDEFIFTEEN tdefifteen ) +tde_conditional_add_subdirectory( BUILD_KUE kue ) if( BUILD_KSIRTET OR BUILD_KFOULEGGS OR BUILD_KLICKETY ) add_subdirectory( libksirtet ) diff --git a/ConfigureChecks.cmake b/ConfigureChecks.cmake index 765e7393..f10fb006 100644 --- a/ConfigureChecks.cmake +++ b/ConfigureChecks.cmake @@ -66,6 +66,17 @@ if( WITH_ARTS ) endif( WITH_ARTS ) +# Kue needs GL +if( BUILD_KUE ) + check_include_file( "GL/gl.h" HAVE_GL_H ) + check_include_file( "GL/glu.h" HAVE_GLU_H ) + check_include_file( "GL/glx.h" HAVE_GLX_H ) + + if( NOT HAVE_GL_H OR NOT HAVE_GLU_H OR NOT HAVE_GLX_H) + tde_message_fatal("GL required for Kue, but not found on your system") + endif() +endif() + ##### Import libtdegames ##### All these games require libtdegames @@ -100,6 +111,7 @@ if( BUILD_ATLANTIK OR BUILD_KTRON OR BUILD_KTUBERLING OR BUILD_LSKAT OR - BUILD_TWIN4 ) + BUILD_TWIN4 OR + BUILD_KUE ) tde_import ( libtdegames ) endif() diff --git a/kue/CMakeLists.txt b/kue/CMakeLists.txt new file mode 100644 index 00000000..1033bb7d --- /dev/null +++ b/kue/CMakeLists.txt @@ -0,0 +1,64 @@ +################################################################################ +# Improvements and feedback are welcome! # +# This software is licensed under the terms of the GNU GPL v3 license. # +################################################################################ + +include_directories( + ${CMAKE_BINARY_DIR} + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_SOURCE_DIR}/libtdegames + ${TDE_INCLUDE_DIR} + ${TQT_INCLUDE_DIRS} +) + +link_directories( + ${TQT_LIBRARY_DIRS} +) + +### kue (library) ############################################################ +tde_add_library( + kue SHARED AUTOMOC + + SOURCES + billiard.cpp disc.cpp interface.cpp physics.cpp table.cpp global.cpp + vector.cpp cue.cpp graphics.cpp point.cpp sphere.cpp texture.cpp + utility.cpp newgame.cpp pluginloader.cpp rules.cpp localplayer.cpp + + LINK + tdegames-shared + GL + GLU + + DESTINATION ${LIB_INSTALL_DIR} +) + +### kue (executable) ######################################################### +tde_add_executable( + kue AUTOMOC + + SOURCES + main.cpp + + LINK + kue-shared + + DESTINATION ${BIN_INSTALL_DIR} +) + +### modules #################################################################### +add_subdirectory(modules) + +### data ####################################################################### +tde_install_icons() + +install( + DIRECTORY textures/ + DESTINATION ${DATA_INSTALL_DIR}/kue/textures + FILES_MATCHING PATTERN *.png +) + +### translated desktop files ################################################### +tde_create_translated_desktop(kue.desktop) + +# kate: replace-tabs true; tab-width 2;
\ No newline at end of file diff --git a/kue/ConfigureChecks.cmake b/kue/ConfigureChecks.cmake new file mode 100644 index 00000000..3ce5420c --- /dev/null +++ b/kue/ConfigureChecks.cmake @@ -0,0 +1,22 @@ +################################################################################ +# kue - Simple billiards game # +# Copyright (C) 2023 Mavridis Philippe <[email protected]> # +# # +# Improvements and feedback are welcome! # +# This software is licensed under the terms of the GNU GPL v3 license. # +################################################################################ + +find_package( TQt ) +find_package( TDE ) + +tde_setup_architecture_flags( ) + +include( TestBigEndian ) +test_big_endian( WORDS_BIGENDIAN ) + +tde_setup_largefiles( ) + + +if( WITH_GCC_VISIBILITY ) + tde_setup_gcc_visibility( ) +endif( WITH_GCC_VISIBILITY )
\ No newline at end of file diff --git a/kue/billiard.cpp b/kue/billiard.cpp new file mode 100644 index 00000000..7237d7f8 --- /dev/null +++ b/kue/billiard.cpp @@ -0,0 +1,87 @@ +#include "billiard.h" +#include "graphics.h" + +#include <math.h> +#include <tqstring.h> +#include <kdebug.h> + +const double FRICTIONAL_FORCE = 0.025; + +KueBilliard::KueBilliard(double x, double y, double r, const KueTexture &texture) : circle(x, y, r), _texture(texture) +{ +} + +KueBilliard::~KueBilliard() +{ +} + +void KueBilliard::step(double seconds) +{ + double delta_x; + double delta_y; + + if (isStopped()) + return; + + vector new_velocity = _velocity - (FRICTIONAL_FORCE * seconds); + + if (new_velocity.magnitude() < 0.0) + new_velocity.setMagnitude(0.0); + + delta_x = (_velocity.componentX() + new_velocity.componentX()) / 2.0 * seconds; + delta_y = (_velocity.componentY() + new_velocity.componentY()) / 2.0 * seconds; + + _pos_x += delta_x; + _pos_y += delta_y; + + _velocity = new_velocity; +} + +bool KueBilliard::isStopped() +{ + return (_velocity.magnitude() == 0.0); +} + +void KueBilliard::reflect(double normal) +{ + _velocity.setDirection(M_PI - (_velocity.direction()) + (normal * 2.0)); +} + +void KueBilliard::collide(KueBilliard &b) { + _velocity -= b._velocity; + + double mv = _velocity.magnitude(); + + vector unit1 = vector(*this, b); + unit1 = unit1.unit(); + + vector unit2 = _velocity.unit(); + + double cos = unit1 * unit2; + + unit1 *= mv * cos; + _velocity -= unit1; + _velocity += b._velocity; + + b._velocity += unit1; +} + +vector& KueBilliard::velocity() +{ + return _velocity; +} + +void KueBilliard::setVelocity(const vector &velocity) +{ + _velocity = velocity; +} + +KueTexture& KueBilliard::texture() +{ + return _texture; +} + +void KueBilliard::setTexture(const KueTexture &texture) +{ + _texture = texture; +} diff --git a/kue/billiard.h b/kue/billiard.h new file mode 100644 index 00000000..ea345201 --- /dev/null +++ b/kue/billiard.h @@ -0,0 +1,33 @@ +#ifndef _BILLIARD_H +#define _BILLIARD_H + +#include <tqstring.h> +#include "texture.h" +#include "vector.h" +#include "circle.h" + +class KueBilliard : public circle +{ + public: + KueBilliard(double x, double y, double r, const KueTexture &texure = KueTexture::null()); + ~KueBilliard(); + + void step(double seconds); + + bool isStopped(); + void reflect(double normal); + void collide(KueBilliard &other_billiard); + + vector& velocity(); + void setVelocity(const vector &velocity); + + KueTexture& texture(); + void setTexture(const KueTexture &); + + protected: + KueTexture _texture; + + vector _velocity; +}; + +#endif diff --git a/kue/circle.h b/kue/circle.h new file mode 100644 index 00000000..d412b41d --- /dev/null +++ b/kue/circle.h @@ -0,0 +1,27 @@ +#ifndef _CIRCLE_H +#define _CIRCLE_H + +#include "point.h" + +// Billards and pockets are both circles +// The collison detection code likes to deal with circles, not billards and pockets + +class circle : public point { + public: + circle(double x, double y, double r) : point(x, y) { _radius = r; } + ~circle() {} + + // Accessors + double radius() { return _radius; } + void setRadius(double radius) { _radius = radius; } + + // Returns true if we intersect the other circle + double intersects(const circle &other_circle) { return (distance(other_circle) - other_circle._radius) <= _radius; } + // Returns the distance from the edge of this circle to the edge of the other + double edgeDistance(const circle &other_circle) { return distance(other_circle) - _radius - other_circle._radius; } + + protected: + double _radius; +}; + +#endif diff --git a/kue/config.h.cmake b/kue/config.h.cmake new file mode 100644 index 00000000..1370df9e --- /dev/null +++ b/kue/config.h.cmake @@ -0,0 +1,8 @@ +#define VERSION "@VERSION@" + +// Defined if you have fvisibility and fvisibility-inlines-hidden support. +#cmakedefine __KDE_HAVE_GCC_VISIBILITY 1 + +/* Define WORDS_BIGENDIAN to 1 if your processor stores words with the most + significant byte first (like Motorola and SPARC, unlike Intel). */ +#cmakedefine WORDS_BIGENDIAN @WORDS_BIGENDIAN@
\ No newline at end of file diff --git a/kue/cue.cpp b/kue/cue.cpp new file mode 100644 index 00000000..561ad89a --- /dev/null +++ b/kue/cue.cpp @@ -0,0 +1,74 @@ +#include <math.h> +#include <tqcolor.h> + +#include <GL/gl.h> +#include <GL/glu.h> + +#include "cue.h" +#include "texture.h" + +const int CUE_DISPLAY_LIST = 4; +const int TIP_DISPLAY_LIST = 5; + +const double RADIUS = 0.00286; + +void cue::draw(double x, double y, double angle, KueTexture &texture, const TQColor &tip_color) +{ + glPushMatrix(); + + // Go to the specified location + glTranslated(x, y, RADIUS); + + // Rotate to the specificed angle + glRotated(angle, 0.0, 0.0, 1.0); + // And tip the cue upwards + glRotated(80, 0.0, 1.0, 0.0); + + // Have we built the cue display list? + if (!glIsList(CUE_DISPLAY_LIST) == GL_TRUE) + { + // Make a new quadtic object + GLUquadricObj *qobj = gluNewQuadric(); + + // We need normals and texturing for lighting and textures + gluQuadricNormals(qobj, GLU_SMOOTH); + gluQuadricTexture(qobj, GL_TRUE); + + // Make a new display list + glNewList(TIP_DISPLAY_LIST, GL_COMPILE); + + // Draw the tip + gluCylinder(qobj, RADIUS / 2.5, RADIUS / 2.5, 0.003, 10, 10); + + // End the tip list + glEndList(); + + // Make a new display list + glNewList(CUE_DISPLAY_LIST, GL_COMPILE); + + // Draw the main part of the cue + glTranslated(0.0, 0.0, 0.003); + gluCylinder(qobj, RADIUS / 2.5, RADIUS / 1.5, 0.047, 10, 10); + + // End the cue list + glEndList(); + + // Draw the quadric + gluDeleteQuadric(qobj); + } + + + KueTexture::null().makeCurrent(); + glColor3d( + tip_color.red() / 255.0, + tip_color.green() / 255.0, + tip_color.blue() / 255.0 + ); + glCallList(TIP_DISPLAY_LIST); + + texture.makeCurrent(); + glColor3d(1.0, 1.0, 1.0); + glCallList(CUE_DISPLAY_LIST); + + glPopMatrix(); +} diff --git a/kue/cue.h b/kue/cue.h new file mode 100644 index 00000000..d92bc7a2 --- /dev/null +++ b/kue/cue.h @@ -0,0 +1,13 @@ +#ifndef _CUE_H +#define _CUE_H + +class TQColor; +class KueTexture; + +// Draws a pool cue +class cue { + public: + static void draw(double x, double y, double angle, KueTexture &texture, const TQColor &tip_color); +}; + +#endif diff --git a/kue/disc.cpp b/kue/disc.cpp new file mode 100644 index 00000000..2b6946e5 --- /dev/null +++ b/kue/disc.cpp @@ -0,0 +1,46 @@ +#include <GL/gl.h> +#include <GL/glu.h> + +#include <math.h> + +#include "disc.h" + +// The pre-allocated number of our display list +const int DISC_DISPLAY_LIST = 3; +// The disc radius currently cached in our display list +double disc_list_radius = 0.0; + +void disc::draw(double x, double y, double r) +{ + glPushMatrix(); + + // Move to the specified location + glTranslatef(x, y, 0.0001); + + // Have we cached this radius + if ((r == disc_list_radius) && (glIsList(DISC_DISPLAY_LIST) == GL_TRUE)) + { + // Yes, just call the display list + glCallList(DISC_DISPLAY_LIST); + } + else + { + // Nope, make a new quadric object + GLUquadricObj *quad = gluNewQuadric(); + + // Make a new display list + glNewList(DISC_DISPLAY_LIST, GL_COMPILE_AND_EXECUTE); + // Draw the disc + gluDisk(quad, 0.0, r, 10, 1); + // End the display list + glEndList(); + + // Update the cached value + disc_list_radius = r; + + // Delete the quadric object + gluDeleteQuadric(quad); + } + + glPopMatrix(); +} diff --git a/kue/disc.h b/kue/disc.h new file mode 100644 index 00000000..9c937d81 --- /dev/null +++ b/kue/disc.h @@ -0,0 +1,15 @@ +#ifndef _DISC_H +#define _DISC_H + +#include <math.h> + +#ifndef M_PI +#define M_PI 3.14159 +#endif + +class disc { + public: + static void draw(double x, double y, double radius); +}; + +#endif diff --git a/kue/global.cpp b/kue/global.cpp new file mode 100644 index 00000000..99c90eea --- /dev/null +++ b/kue/global.cpp @@ -0,0 +1,39 @@ +#include "rules.h" +#include "physics.h" +#include "global.h" +#include "main.h" + +KueRulesEngine* KueGlobal::_rules = nullptr; +KuePhysics* KueGlobal::_physics = nullptr; +GLUserInterface* KueGlobal::_glWidget = nullptr; +MainWindow* KueGlobal::_mainWindow = nullptr; +TQPtrVector<KueTeam>* KueGlobal::_teams = nullptr; + +KueRulesEngine* KueGlobal::rules() +{ + return _rules; +} + +KuePhysics* KueGlobal::physics() +{ + return _physics; +} + +GLUserInterface *KueGlobal::glWidget() +{ + return _glWidget; +} + +MainWindow *KueGlobal::mainWindow() +{ + return _mainWindow; +} + +TQPtrVector<KueTeam> *KueGlobal::teams() +{ + if (!_teams) + { + _teams = new TQPtrVector<KueTeam>; + } + return _teams; +}
\ No newline at end of file diff --git a/kue/global.h b/kue/global.h new file mode 100644 index 00000000..0c0188c2 --- /dev/null +++ b/kue/global.h @@ -0,0 +1,41 @@ +#ifndef __GLOBAL_H_ +#define __GLOBAL_H_ + +#include <tqgl.h> +#include <tqptrvector.h> +#include "team.h" + +class MainWindow; +class GLUserInterface; +class KueRulesEngine; +class KuePhysics; + +class KueGlobal { + public: + // Get our version string + static const char *version() { return "1.0"; } + + // Exit menu, start the game + static void stopMenu(); + + // Member instances + static KueRulesEngine *rules(); + static KuePhysics *physics(); + + static GLUserInterface *glWidget(); + static MainWindow *mainWindow(); + static TQPtrVector<KueTeam> *teams(); + + private: + static KueRulesEngine *_rules; + static KuePhysics *_physics; + + static GLUserInterface *_glWidget; + static MainWindow *_mainWindow; + + static TQPtrVector<KueTeam> *_teams; + + friend class MainWindow; +}; + +#endif
\ No newline at end of file diff --git a/kue/graphics.cpp b/kue/graphics.cpp new file mode 100644 index 00000000..a0568aff --- /dev/null +++ b/kue/graphics.cpp @@ -0,0 +1,176 @@ +#include <GL/gl.h> +#include <GL/glu.h> +#include <tdeglobal.h> +#include <tdeconfig.h> +#include <tqptrvector.h> + +#include "physics.h" +#include "global.h" +#include "pocket.h" +#include "billiard.h" +#include "config.h" +#include "graphics.h" + +// Constants + +const double FIELD_WIDTH = 0.127; +const double FIELD_LENGTH = 0.254; + +GLfloat LightAmbient[] = { 0.2, 0.2, 0.2, 1.0 }; +GLfloat LightDiffuse[] = { 0.8, 0.8, 0.8, 1.0 }; +GLfloat LightPosition[] = { FIELD_LENGTH / 2.0, FIELD_WIDTH / 2.0, 0.0, 1.0 }; + +GLdouble eyex = FIELD_LENGTH / 2.0; +GLdouble eyey = FIELD_WIDTH / 2.0; +GLdouble eyez = 0.1; + +GLdouble centerx = FIELD_LENGTH / 2.0; +GLdouble centery = FIELD_WIDTH / 2.0; +GLdouble centerz = -FIELD_LENGTH / 2.0; + +void graphics::resize(int width, int height) +{ + // We want to use the whole window + glViewport(0, 0, width, height); + + // 3D-specific OpenGL setup + + // Modify the projection matrix + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + + // Set up a perspective view based on our window's aspect ratio + gluPerspective(45.0f, (GLdouble)width / (GLdouble)height, 0.1f, 100.0f); + + // Go back to modifying the modelview matrix (the default) + glMatrixMode(GL_MODELVIEW); +} + +bool graphics::init() +{ + // Clear the window to black when we start to draw + glClearColor(0.0f, 0.0f, 0.0f, 0.0f); + // Clear the depth buffer to 1.0 + glClearDepth(1.0); + glShadeModel(GL_SMOOTH); // Enables Smooth Color Shading + + // Enable texturing + glEnable(GL_TEXTURE_2D); + + // Initialize our light + glLightfv(GL_LIGHT0, GL_AMBIENT, LightAmbient); + glLightfv(GL_LIGHT0, GL_DIFFUSE, LightDiffuse); + glLightfv(GL_LIGHT0, GL_POSITION, LightPosition); + + // Enable our light + glEnable(GL_LIGHT0); + + // Don't draw polygons that aren't facing us (big speedup on cheap 486s ;) +// glEnable(GL_CULL_FACE); + + // Have we enabled lighting? + TDEGlobal::config()->setGroup("Graphics"); + if (TDEGlobal::config()->readBoolEntry("Lighting", true)) { + // Turn on lighting + glEnable(GL_LIGHTING); + + // Whenever we use glColor(), set the lighting system automatically know what + // the new color is. + glEnable(GL_COLOR_MATERIAL); + } + + glDepthFunc(GL_LESS); // The Type Of Depth Test To Do + glEnable(GL_DEPTH_TEST); // Enables Depth Testing + + // Makes texturing drawing slight more accurate + glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); + + return true; +} + +void graphics::startDraw() +{ + // Reset our origin, rotation, and scaling + glLoadIdentity(); + + glLightfv(GL_LIGHT0, GL_POSITION, LightPosition); + + glTranslatef(0.0, 0.0, -FIELD_LENGTH / 2.0); + + // Set the camera position and angle + gluLookAt(eyex, eyey, eyez, centerx, centery, centerz, 0.0, 0.0, 1.0); + + // Clear our window to the default color set by glClearColor (i.e. black) + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + +} + +void graphics::endDraw() +{ + // Execute any backlogged commands right now + glFlush();; +} + +void graphics::lookAt(GLdouble _eyex, GLdouble _eyey, GLdouble _eyez, GLdouble _centerx, GLdouble _centery, GLdouble _centerz) +{ + // Update our internal viewpoint variables + // These values are used in startDraw() to update the view + + // The eye coordinates are where we're looking from + eyex = _eyex; + eyey = _eyey; + eyez = _eyez; + + // And the center coordinates are what we're looing at + centerx = _centerx; + centery = _centery; + centerz = _centerz; +} + +// Simple OpenGL wrapper +void graphics::setColor(double r, double g, double b) +{ + glColor3d(r, g, b); +} + +void graphics::drawScene() +{ + const TQPtrVector<KuePocket>& pockets = KueGlobal::physics()->pockets(); + const TQPtrVector<KueBilliard>& billiards = KueGlobal::physics()->billiards(); + + // Display the billiards and pockets + + // The pockets are black without a texture + graphics::setColor(0.0, 0.0, 0.0); + KueTexture::null().makeCurrent(); + + // Draw all of the textures + for (unsigned int i = 0;i < pockets.size();i++) + { + if (pockets[i]) + disc::draw(pockets[i]->positionX(), pockets[i]->positionY(), pockets[i]->radius()); + } + + // Now we need to turn the color to white, + // so we don't interfere with the texture + // color + graphics::setColor(1.0, 1.0, 1.0); + + for (unsigned int i = 0;i < billiards.size();i++) + { + if (billiards[i]) + { + double circ = billiards[i]->radius() * 2.0 * M_PI; + double pos_x = billiards[i]->positionX(); + double pos_y = billiards[i]->positionY(); + double rot_x = fmod(pos_x, circ) / circ * 360.0; + double rot_y = fmod(pos_y, circ) / circ * 360.0; + + billiards[i]->texture().makeCurrent(); + + sphere::draw(pos_x, pos_y, billiards[i]->radius(), rot_x, rot_y); + } + } + +} diff --git a/kue/graphics.h b/kue/graphics.h new file mode 100644 index 00000000..810fe8d8 --- /dev/null +++ b/kue/graphics.h @@ -0,0 +1,37 @@ +// Main header for the graphics subsystem + +#ifndef _GRAPHICS_H +#define _GRAPHICS_H + +#include "cue.h" +#include "disc.h" +#include "sphere.h" +#include "table.h" +#include "texture.h" + +const int GRAPHICS_NOTEXTURE = 0; + +class graphics { + public: + // Setup OpenGL + static bool init(); + // Shut down OpenGL + static bool deinit(); + + static void resize(int width, int height); + + // Start and end drawing mode + static void startDraw(); + static void endDraw(); + + // Draw the basic scene + static void drawScene(); + + // Set the pen color + static void setColor(double r, double g, double b); + + // Sets the viewing angle + static void lookAt(double eyex, double eyey, double eyez, double centerx, double centery, double centerz); +}; + +#endif diff --git a/kue/hi16-app-kue.png b/kue/hi16-app-kue.png Binary files differnew file mode 100644 index 00000000..f8f1e1c7 --- /dev/null +++ b/kue/hi16-app-kue.png diff --git a/kue/hi32-app-kue.png b/kue/hi32-app-kue.png Binary files differnew file mode 100644 index 00000000..d3638685 --- /dev/null +++ b/kue/hi32-app-kue.png diff --git a/kue/hi48-app-kue.png b/kue/hi48-app-kue.png Binary files differnew file mode 100644 index 00000000..813151a2 --- /dev/null +++ b/kue/hi48-app-kue.png diff --git a/kue/hi64-app-kue.png b/kue/hi64-app-kue.png Binary files differnew file mode 100644 index 00000000..3f9b2b03 --- /dev/null +++ b/kue/hi64-app-kue.png diff --git a/kue/interface.cpp b/kue/interface.cpp new file mode 100644 index 00000000..5e99a8ff --- /dev/null +++ b/kue/interface.cpp @@ -0,0 +1,319 @@ +#include <math.h> +#include <stdlib.h> +#include <string.h> +#include <GL/gl.h> +#include <tdemainwindow.h> +#include <tdeapplication.h> +#include <kstatusbar.h> +#include "physics.h" +#include <kdebug.h> +#include <tqptrvector.h> + +#include "interface.h" +#include "graphics.h" +#include "vector.h" +#include "global.h" + +// Radius of the billiards +const double RADIUS = 0.00286; + +// How long the user can hold down the mouse button to increase shot power, it levels off if this value is exceeded +const int MAX_WINDUP_TIME = 2500; +// What's the value in meters/second of that maximum shot +const double MAX_SHOT_SPEED = 0.45; + +const int UPDATE_TIME = 15; + +GLUserInterface::GLUserInterface(TQWidget *parent) : + TQGLWidget(parent), + _cue_location(0.0, 0.0, 0.0), + _mouse_location(0.5, 0.5), + _cue_texture("cue-player1") +{ + // Set all our variables to known safe values + _placing_cue = false; + _shooting = false; + _forward_only = false; + + setMouseTracking(true); + _table = new table; +} + + +GLUserInterface::~GLUserInterface() +{ + delete _table; +} + +double GLUserInterface::windowToInternalX(int x) +{ + // Make sure the value isn't outside the window (yes, we + // used to get invalid values from GLUT sometimes) + if (x < 0) + return 0.0; + + if (x > width()) + return 1.0; + + // Now divide the x value by the windows width, so the left edge + // has a value of 0.0, the middle has 0.5, and the right edge 1.0 + return (x / (double)width()); +} + +double GLUserInterface::windowToInternalY(int y) +{ + if (y < 0) + return 0.0; + + if (y > height()) + return 1.0; + + // Now divide the y value by the window's height, so the left edge + // has a value of 0.0, the middle has 0.5, and the right edge 1.0 + return (y / (double)height()); +} + +void GLUserInterface::initializeGL() +{ + // Initialize our graphics subsystem + if (!graphics::init()) + kdWarning() << "Unable to initialize graphics" << endl; +} + +void GLUserInterface::resizeGL(int width, int height) +{ + graphics::resize(width, height); +} + +void GLUserInterface::paintGL() +{ + // Tell the graphics code to enter drawing mode + graphics::startDraw(); + + // Draw the table + _table->draw(KueGlobal::physics()->fieldWidth(), KueGlobal::physics()->fieldHeight()); + + + // Draw the basic physics scene + graphics::drawScene(); + + // Are we shooting? + if (_shooting) { + double angle_rad; + double angle_deg; + + // Calculate the current view angle + angle_rad = positionToAngle(_mouse_location.positionX()); + // Convert it to degrees for OpenGL's benefit + angle_deg = angle_rad / M_PI * 180; + + // Calculate the 'focus' (where the cue is pointing) + double focusx = _cue_location.positionX() + (cos(angle_rad) / 150.0 * ((shotPower() * 2.0) + 0.7)); + double focusy = _cue_location.positionY() + (sin(angle_rad) / 150.0 * ((shotPower() * 2.0) + 0.7)); + + // Draw + cue::draw(focusx, focusy, angle_deg, _cue_texture, _player_color); + } + + // Now we're done with drawing + graphics::endDraw(); +} + +void GLUserInterface::mouseMoveEvent(TQMouseEvent *e) +{ + double x = windowToInternalX(e->x()); + double y = windowToInternalY(e->y()); + + // Invert the mouse along the X-axis + x = 1.0 - x; + + // Update the mouse location variable + _mouse_location = point(x, y); + // Update our 3D view + updateView(); + + if (_placing_cue) + { + // If we're placing a cue ball, the mouse location affects its position on the table + point cue_ball_point(_cue_line, positionToCuePlacement(x)); + emit(previewBilliardPlace(cue_ball_point)); + } +} + +void GLUserInterface::mousePressEvent(TQMouseEvent *e) +{ + mouseClicked(windowToInternalX(e->x()), windowToInternalY(e->y()), e->button(), true); +} + +void GLUserInterface::mouseReleaseEvent(TQMouseEvent *e) +{ + mouseClicked(windowToInternalX(e->x()), windowToInternalY(e->y()), e->button(), false); +} + +void GLUserInterface::mouseClicked(double x, double y, int button, int state) { + // Invert the mouse along the X-axis + x = 1.0 - x; + + // Regardless of the button press event, we'll take a free mouse position update + _mouse_location = point(x, y); + updateView(); + + // But no non-left buttons past this point; + if (button != TQt::LeftButton) + return; + + // Are we placing the cue ball? + // The "mouse down only" check is so we don't catch a mouse button + // coming up that was pressed down before we started placing + // the cue ball. It can be very confusing otherwise, and makes + // the game seem "glitchy" + if (_placing_cue && (state)) + { + // Calculate the cues new position + point cue_ball_point(_cue_line, positionToCuePlacement(x)); + // The the rules engine what we've decided + emit(billiardPlaced(cue_ball_point)); + + // We're done placing the cue + _placing_cue = false; + // We calculate the view differently depending on if we're + // placing the cue or taking a shot, so update the view + // with both the new cue position and with the "taking a shot" + // view mode. + updateView(); + + return; + } + + if (_shooting) + { + if (state) + { + if (!_shot_started) + { + _shot_started = true; + _shot_time.start(); + startTimer(UPDATE_TIME); + } + } + else if (_shot_started) + { + // Calculate the angle + double angle = positionToAngle(_mouse_location.positionX()) + M_PI; + + // Take the shot + vector velocity(shotPower() * MAX_SHOT_SPEED, angle); + emit(shotTaken(velocity)); + + // We're no longer shooting + _shooting = false; + _shot_started = false; + killTimers(); + } + } +} + +void GLUserInterface::placeBilliard(double cue_line) +{ + // We've been asked to place the cue ball + + // Enter 'cue placing' mode + _placing_cue = true; + _cue_line = cue_line; + + // Show it in the position that is associated with our current mouse position + point cue_ball_point(_cue_line, positionToCuePlacement(_mouse_location.positionX())); + emit(previewBilliardPlace(cue_ball_point)); + + // Set up our stupid placing-cue-specific view + updateView(); +} + +void GLUserInterface::startShot(circle cue_location, TQColor player_color, bool forward_only) { + // Enter 'shooting' mode + _shot_started = false; + _shooting = true; + + // Copy over our parameters + _forward_only = forward_only; + _cue_location = cue_location; + _player_color = player_color; + + // Set up our new view + updateView(); +} + +void GLUserInterface::updateView() { + if (_placing_cue) + { + // Our eye is slightly behind the cue line + double eyex = _cue_line - (1 / 200.0); + // And right in the middle of the table horizontally + double eyey = KueGlobal::physics()->fieldHeight() / 2.0; + + // Look at the cue line from our eye position + graphics::lookAt(eyex, eyey, (_cue_location.radius() * 4.0 * _mouse_location.positionY()) + _cue_location.radius(), _cue_line, KueGlobal::physics()->fieldHeight() / 2.0, RADIUS); + } + else + { + // Figure out our view angle + double angle = positionToAngle(_mouse_location.positionX()); + // Use that to calculate the position of our eye + double eyex = _cue_location.positionX() + (cos(angle) / 200.0); + double eyey = _cue_location.positionY() + (sin(angle) / 200.0); + + // Look at the cue ball + graphics::lookAt(eyex, eyey, (RADIUS * 4.0 * _mouse_location.positionY()) + RADIUS, _cue_location.positionX(), _cue_location.positionY(), RADIUS); + } + + // We most certainly need to redraw, unless the physics engine is runnung + if (!KueGlobal::physics()->running()) + KueGlobal::glWidget()->updateGL(); +} + +double GLUserInterface::shotPower() +{ + if (!_shot_started) + return 0.0; + + int difference = _shot_time.elapsed(); + + if (difference > MAX_WINDUP_TIME) + return 1.0; + + return (double(difference) / double(MAX_WINDUP_TIME)); +} + +double GLUserInterface::positionToAngle(double position) +{ + // Convert the mouse x-position to a view angle, depending if we're only allow to shoot forward or not + if (_forward_only) + return (position * M_PI) + (M_PI / 2.0); + else + return (((position - 0.5) * 1.1) + 0.5) * M_PI * 2.0; +} + +double GLUserInterface::positionToCuePlacement(double position) +{ + // Convert the mouse x-position to a cue x-location + + // Direct linear mapping to the table + double y_pos = position * KueGlobal::physics()->fieldHeight(); + + // Except we must be careful not to go off the table + if (y_pos < RADIUS) + y_pos = RADIUS; + + if ((y_pos + RADIUS) > KueGlobal::physics()->fieldHeight()) + y_pos = KueGlobal::physics()->fieldHeight() - RADIUS; + + return y_pos; +} + +void GLUserInterface::timerEvent( TQTimerEvent * ) +{ + KueGlobal::glWidget()->updateGL(); +} + +#include "interface.moc" + diff --git a/kue/interface.h b/kue/interface.h new file mode 100644 index 00000000..72565d31 --- /dev/null +++ b/kue/interface.h @@ -0,0 +1,86 @@ +#ifndef _INTERFACE_H +#define _INTERFACE_H + +#include <tqdatetime.h> +#include <tqobject.h> +#include <tqcolor.h> +#include <tqgl.h> + +#include "circle.h" +#include "vector.h" +#include "texture.h" + +class table; +class rules; +class TQString; + +// Handles user interface functionality +class GLUserInterface : public TQGLWidget { + TQ_OBJECT + public: + GLUserInterface(TQWidget *parent); + ~GLUserInterface(); + + // Rules' functions + void placeBilliard(double cue_line); + void startShot(circle cue_location, TQColor player_color, bool forward_only = false); + + void update(); + + signals: + // Called by the interface when the user has decided on a cue location + void billiardPlaced(point &location); + // Called by the interface when the user is deciding on a cue location + void previewBilliardPlace(point &location); + void shotTaken(vector &velocity); + + private: + void initializeGL(); + void resizeGL(int widget, int height); + void paintGL(); + + void mouseMoveEvent(TQMouseEvent *e); + void mousePressEvent(TQMouseEvent *e); + void mouseReleaseEvent(TQMouseEvent *e); + + double windowToInternalX(int x); + double windowToInternalY(int y); + + void timerEvent( TQTimerEvent * ); + void updateView(); + double shotPower(); + + void mouseClicked(double x, double y, int button, int state); + + double positionToAngle(double position); + double positionToCuePlacement(double position); + + // Are we placing the cue ball? + bool _placing_cue; + double _cue_line; + + // Are we shooting? + bool _shooting; + // Can we shoot only in front of the start line? + bool _forward_only; + + + // Where is the cue? And how big is it? + circle _cue_location; + // Where is the mouse? + point _mouse_location; + + // When did the user begin the 'shot' by pressing down the mouse button + bool _shot_started; + TQTime _shot_time; + + // Cue texture + KueTexture _cue_texture; + + // The current player's color + TQColor _player_color; + + table *_table; +}; + +#endif diff --git a/kue/kue.desktop b/kue/kue.desktop new file mode 100644 index 00000000..a4d7767b --- /dev/null +++ b/kue/kue.desktop @@ -0,0 +1,52 @@ +[Desktop Entry] +Type=Application +Exec=kue -caption "%c" %i %m +Icon=kue +X-DocPath=ktron/index.html +GenericName=Simple billiards game +GenericName[bs]=Jednostavna igra bilijara +GenericName[ca]=Un senzill joc de billar +GenericName[cs]=Jednoduchá kulečníková hra +GenericName[da]=Simpelt billardspil +GenericName[de]=Einfaches Billiardgames +GenericName[el]=Απλό παιχνίδι μπιλιάρδου +GenericName[es]=Sencillo juego de billar +GenericName[et]=Lihtne piljardimäng +GenericName[eu]=Billar txiki bat +GenericName[fi]=Yksinkertainen biljardipeli +GenericName[fr]=Jeu de billard simple +GenericName[gl]=Xogo de Billar +GenericName[he]=משחק ביליארד פשוט +GenericName[hr]=Jednostavna igra bilijara +GenericName[hu]=Billiárd +GenericName[it]=Semplice gioco di biliardo +GenericName[ja]=シンプルなビリヤードゲーム +GenericName[lv]=Vienkārša biljarda spēle +GenericName[nl]=Eenvoudig biljartspel +GenericName[nn]=Enkelt biljardspel +GenericName[pl]=Prosta gra w bilard +GenericName[pt]=Jogo de Bilhar +GenericName[pt_BR]=Simples jogo de bilhar +GenericName[ro]=Un joc simplu de biliard +GenericName[ru]=Простой бильярд +GenericName[sk]=Jednoduchý billiard +GenericName[sl]=Preprosta igra biljarda +GenericName[sr]=Једноставна игра билијара +GenericName[sr@Latn]=Jednostavna igra bilijara +GenericName[sv]=Enkelt biljardspel +GenericName[th]=เกมบิลเลียดยอดนิยม +GenericName[tr]=Basit bilardo oyunu +GenericName[uk]=Простий більярд +GenericName[ven]=Mitambo ya billiards isa kondi +GenericName[wa]=On djeu di biyård simpe +GenericName[xx]=xxSimple billiards gamexx +GenericName[zh_CN]=简单的弹子游戏 +GenericName[zh_TW]=簡單的撞球遊戲 +GenericName[zu]=Alula amabhodi omdlalo +Terminal=false +Name=Kue +Name[th]=สอยคิว - K +Name[xx]=xxKuexx +X-TDE-StartupNotify=true +X-DCOP-ServiceType=Multi +Encoding=UTF-8 diff --git a/kue/localplayer.cpp b/kue/localplayer.cpp new file mode 100644 index 00000000..51b157b3 --- /dev/null +++ b/kue/localplayer.cpp @@ -0,0 +1,82 @@ +#include <tdeapplication.h> +#include <kdebug.h> +#include <tqtimer.h> + +#include "localplayer.h" +#include "global.h" +#include "interface.h" +#include "physics.h" + + +KueLocalPlayer::KueLocalPlayer(TQString n, TQColor color) : KuePlayer(n), _dest(0) +{ + _color = color; +} + +// Rules' functions +void KueLocalPlayer::placeBilliard(int index, const KueBilliard &b, double line, TQObject *dest, const char *dest_slot) +{ + _dest = dest; + _dest_slot = dest_slot; + + connect(KueGlobal::glWidget(), TQ_SIGNAL(billiardPlaced(point &)), this, TQ_SLOT(billiardPlaced(point &))); + connect(KueGlobal::glWidget(), TQ_SIGNAL(previewBilliardPlace(point &)), this, TQ_SLOT(previewBilliardPlace(point &))); + + _index = index; + // We use the passed billiard as a template, and just move it around + KueGlobal::physics()->insertBilliard(index, b); + + KueGlobal::glWidget()->placeBilliard(line); +} + +void KueLocalPlayer::takeShot(int index, bool forward_only, TQObject *dest, const char *dest_slot) +{ + _dest = dest; + _dest_slot = dest_slot; + + connect(KueGlobal::glWidget(), TQ_SIGNAL(shotTaken(vector &)), this, TQ_SLOT(shotTaken(vector &))); + + _index = index; + KueGlobal::glWidget()->startShot(*KueGlobal::physics()->billiards()[index], _color, forward_only); +} + +// Called by the interface when the user has decided on a cue location +void KueLocalPlayer::billiardPlaced(point &location) +{ + KueGlobal::physics()->billiards()[_index]->setPosition(location); + KueGlobal::physics()->run(0); + + disconnect(); + callBack(); +} + +// Called by the interface when the user is deciding on a cue location +void KueLocalPlayer::previewBilliardPlace(point &location) +{ + // Inform the physics engine + KueGlobal::physics()->billiards()[_index]->setPosition(location); + + // Redraw + KueGlobal::glWidget()->updateGL(); +} + +void KueLocalPlayer::shotTaken(vector &velocity) +{ + KueGlobal::physics()->billiards()[_index]->setVelocity(velocity); + + disconnect(); + callBack(); +} + +void KueLocalPlayer::callBack() +{ + // Call the completion callback + if (_dest) + { + TQTimer::singleShot(0, _dest, _dest_slot); + _dest = 0; + } +} + + +#include "localplayer.moc" diff --git a/kue/localplayer.h b/kue/localplayer.h new file mode 100644 index 00000000..fa307625 --- /dev/null +++ b/kue/localplayer.h @@ -0,0 +1,40 @@ +#ifndef _LOCALPLAYER_H +#define _LOCALPLAYER_H + +#include <tqstring.h> +#include <tqcolor.h> + +#include "player.h" +#include "interface.h" +#include "main.h" + + +class KueLocalPlayer : public KuePlayer { + TQ_OBJECT + public: + KueLocalPlayer(TQString name = TQString::null, TQColor = TQt::white); + ~KueLocalPlayer() {} + + // Rules' functions + void placeBilliard(int index, const KueBilliard &b, double line, TQObject *dest = 0, const char *slot = 0); + void takeShot(int billiard, bool forward_only = false, TQObject *dest = 0, const char *slot = 0); + + protected slots: + // Called by the interface when the user has decided on a cue location + void billiardPlaced(point &location); + // Called by the interface when the user is deciding on a cue location + void previewBilliardPlace(point &location); + void shotTaken(vector &velocity); + + protected: + void callBack(); + + TQObject *_dest; + const char *_dest_slot; + TQColor _color; + + double _radius; + int _index; +}; + +#endif diff --git a/kue/main.cpp b/kue/main.cpp new file mode 100644 index 00000000..f6779afe --- /dev/null +++ b/kue/main.cpp @@ -0,0 +1,218 @@ +#include <stdio.h> +#include <time.h> +#include <stdlib.h> +#include <tdeapplication.h> +#include <tqtimer.h> +#include <tdelocale.h> +#include <tdecmdlineargs.h> +#include <tdeaboutdata.h> +#include <tdeconfig.h> +#include <tdeglobal.h> +#include <tdemenubar.h> +#include <tdemessagebox.h> +#include <tdepopupmenu.h> +#include <kstdaction.h> +#include <tdeaction.h> +#include <kstatusbar.h> +#include <kstdgameaction.h> +#include <klibloader.h> +#include <kdebug.h> + +#include "newgame.h" +#include "main.h" +#include "graphics.h" +#include "physics.h" +#include "rules.h" +#include "interface.h" +#include "localplayer.h" +#include "global.h" +#include "config.h" + +MainWindow::MainWindow() +{ + // We have a statusbar, show it now + statusBar(); + + // Make a game menu + TDEPopupMenu *game_menu = new TDEPopupMenu(this); + _new_action = KStdGameAction::gameNew(this, TQ_SLOT(newGame()), actionCollection()); + _new_action->plug(game_menu); + _end_action = KStdGameAction::end(this, TQ_SLOT(endGame()), actionCollection()); + _end_action->plug(game_menu); + TDEAction *quit_action = KStdGameAction::quit(this, TQ_SLOT(close()), actionCollection()); + quit_action->plug(game_menu); + menuBar()->insertItem(i18n("&Game"), game_menu); + + // Make a settings menu + TDEPopupMenu *settings_menu = new TDEPopupMenu(this); + TDEAction *menubar_action = KStdAction::showMenubar(this, TQ_SLOT(toggleMenubar()), actionCollection()); + menubar_action->plug(settings_menu); + TDEAction *statusbar_action = KStdAction::showStatusbar(this, TQ_SLOT(toggleStatusbar()), actionCollection()); + statusbar_action->plug(settings_menu); + menuBar()->insertItem(i18n("&Settings"), settings_menu); + + // Make a help menu + TDEPopupMenu *help_menu = helpMenu(); + menuBar()->insertItem(i18n("&Help"), help_menu); + + // Restore our window size + TDEGlobal::config()->setGroup("Window Settings"); + restoreWindowSize(TDEGlobal::config()); + + _in_game = false; + _end_action->setEnabled(false); +} + +MainWindow::~MainWindow() +{ + TDEGlobal::config()->setGroup("Window Settings"); + saveWindowSize(TDEGlobal::config()); +} + +void MainWindow::toggleMenubar() +{ + if (!menuBar()->isHidden()) + menuBar()->hide(); + else + menuBar()->show(); +} + +void MainWindow::toggleStatusbar() +{ + if (!statusBar()->isHidden()) + statusBar()->hide(); + else + statusBar()->show(); +} + +void MainWindow::newGame() +{ + // Show the "New Game" dialog + newGameDialog *new_dialog = new newGameDialog(this, "newgame"); + if (new_dialog->exec() == TQDialog::Accepted) { + // Reset our state + if (_in_game) + endGame(); + + // Recreate our basic objects + KueGlobal::_physics = new KuePhysics; + KueGlobal::_glWidget = new GLUserInterface(this); + setCentralWidget(KueGlobal::_glWidget); + + // Set up the teams + TQValueList<KueTeam*> selectedTeams = new_dialog->selectedTeams(); + + KueGlobal::teams()->resize(selectedTeams.count()); + for (unsigned int i = 0;i < KueGlobal::teams()->size(); i++) + { + KueGlobal::teams()->insert(i, selectedTeams.first()); + selectedTeams.pop_front(); + } + + // Load the plugin the user requested + KLibFactory *factory = KLibLoader::self()->factory(new_dialog->selectedPlugin().filename.local8Bit()); + + // Do we even have an object factory? + if (!factory) { + kdWarning() << "Unable to retrieve KLibFactory for " << new_dialog->selectedPlugin().filename << endl; + delete new_dialog; + return; + } + + // Actually request an object of type "KueKueGlobal::rules()" + TQObject *rules_object = factory->create(this, "KueGlobal::rules()", "KueRulesEngine"); + + // Did they return something at all? + if (!rules_object) { + kdWarning() << "Plugin unable to provide a KueRulesEngine" << endl; + delete new_dialog; + return; + } + + // Is the object -actually- a KueRulesEngine? + // Some broken plugins may not check their object type parameter, so this is a sanity check + if (!rules_object->inherits("KueRulesEngine")) { + kdWarning() << "Plugin returned an object of an unexpected type" << endl; + + delete rules_object; + delete new_dialog; + + return; + } + + // It checked out, set our KueGlobal::rules() to this object + KueGlobal::_rules = (KueRulesEngine*)rules_object; + + connect(KueGlobal::physics(), TQ_SIGNAL(billiardHit(unsigned int, unsigned int)), KueGlobal::rules(), TQ_SLOT(billiardHit(unsigned int, unsigned int))); + connect(KueGlobal::physics(), TQ_SIGNAL(billiardSunk(unsigned int, unsigned int)), KueGlobal::rules(), TQ_SLOT(billiardSunk(unsigned int, unsigned int))); + connect(KueGlobal::physics(), TQ_SIGNAL(motionStopped()), KueGlobal::rules(), TQ_SLOT(motionStopped())); + + connect(KueGlobal::rules(), TQ_SIGNAL(showMessage(const TQString &)), KueGlobal::mainWindow()->statusBar(), TQ_SLOT(message(const TQString &))); + connect(KueGlobal::rules(), TQ_SIGNAL(gameOver(const TQString &)), this, TQ_SLOT(endGame(const TQString &))); + + _in_game = true; + _end_action->setEnabled(true); + + KueGlobal::rules()->start(); + KueGlobal::glWidget()->show(); + } + + delete new_dialog; +} + +void MainWindow::endGame() +{ + delete KueGlobal::_rules; + delete KueGlobal::_physics; + delete KueGlobal::_glWidget; + + KueGlobal::_rules = nullptr; + KueGlobal::_physics = nullptr; + KueGlobal::_glWidget = nullptr; + + statusBar()->message(TQString::null); + _in_game = false; + _end_action->setEnabled(false); +} + +void MainWindow::endGame(const TQString &reason) +{ + // Stop the physics engine + KueGlobal::physics()->stop(); + + // Notify the user + KMessageBox::information(this, reason, i18n("Game Over")); + + // We do this delayed, because most modules don't (and can't) call this + // at a place where they are ready to have the entire game state + // destroyed + TQTimer::singleShot(0, this, TQ_SLOT(endGame())); +} + +MainWindow* MainWindow::the() +{ + if (!KueGlobal::_mainWindow) + KueGlobal::_mainWindow = new MainWindow; + return KueGlobal::_mainWindow; +} + +// Program starts here +int main(int argc, char *argv[]) +{ + TDEAboutData aboutData("kue", I18N_NOOP("Kue"), VERSION, + I18N_NOOP("A simple billiards game"), + TDEAboutData::License_GPL, + I18N_NOOP("(c) 2002 Ryan Cumming")); + + aboutData.addAuthor("Ryan Cumming", 0, "<[email protected]>"); + TDECmdLineArgs::init(argc, argv, &aboutData); + + TDEApplication a; + TDEGlobal::locale()->insertCatalogue("libtdegames"); + TDEGlobal::locale()->insertCatalogue("libkdehighscores"); + + MainWindow::the()->show(); + return a.exec(); +} + +#include "main.moc" diff --git a/kue/main.h b/kue/main.h new file mode 100644 index 00000000..22eea472 --- /dev/null +++ b/kue/main.h @@ -0,0 +1,34 @@ +#ifndef __MAIN_H_ +#define __MAIN_H_ + +#include <tdemainwindow.h> + +class TDEAction; + +class MainWindow : public TDEMainWindow +{ + TQ_OBJECT + public: + static MainWindow* the(); + + public slots: + void toggleMenubar(); + void toggleStatusbar(); + void newGame(); + void endGame(); + + protected slots: + // Called by plugins + void endGame(const TQString &reason); + + protected: + MainWindow(); + ~MainWindow(); + + private: + bool _in_game; + TDEAction *_new_action; + TDEAction *_end_action; +}; + +#endif diff --git a/kue/modules/8ball/8ball.cpp b/kue/modules/8ball/8ball.cpp new file mode 100644 index 00000000..2ac2cff8 --- /dev/null +++ b/kue/modules/8ball/8ball.cpp @@ -0,0 +1,337 @@ +#include <kdebug.h> +#include <tdelocale.h> +#include <stdlib.h> +#include <stdio.h> + +#include "8ball.h" +#include "interface.h" +#include "physics.h" +#include "utility.h" +#include "team.h" +#include "player.h" +#include "global.h" + +const unsigned int BILLIARDS_COUNT = 15; + +K_EXPORT_COMPONENT_FACTORY( libkue8ball, EightBallFactory ) + +TQObject *EightBallFactory::createObject (TQObject *parent, const char* name, const char* classname, const TQStringList &args = TQStringList() ) +{ + Q_UNUSED(args); + + if (classname != TQString("KueRulesEngine")) + return 0; + + return new EightBall(parent, name); +} + +EightBall::EightBall(TQObject *parent, const char *name) : KueRulesEngine(parent, name) +{ + KueUtility::layoutTable(); + KueUtility::layoutPockets(); + KueUtility::layoutBilliards(KueUtility::Triangle); + + // Reset our state (NOTE: anyone see anything missing?) + _game_called = GAME_UNCALLED; + _current_team = 0; + _first_hit = -1; + _first_sunk = -1; + _scratch = false; + _broke = false; + + _current_player = KueGlobal::teams()->at(_current_team)->nextPlayer(); +} + +EightBall::~EightBall() +{ +} + +void EightBall::start() +{ + cuePlaced(); +} + +void EightBall::billiardSunk(unsigned int ball, unsigned int /* pocket */) +{ + // Called when the physics engine sinks a billiard + + // Somebody must win the game if the 8ball is sunk, the question is who + if (ballIsMagic(ball)) + { + if (onlyMagicLeft(_current_team)) + { + // It was our only ball left, we win + playerWins(_current_team); + } + else + { + // We messed up real bad + playerWins(!_current_team); + } + } + + // Have we sunk nothing yet? Or ist the cue ball? + if ((ownsBall(!_current_team, ball)) || ballIsCue(ball)) + { + // Oops, we shouldn't have sunk that... scratch! + _scratch = true; + } + else if (_first_sunk == -1) + { + // Ah, it's all good... + _first_sunk = ball; + } +} + +void EightBall::billiardHit(unsigned int ball1, unsigned int ball2) { + // Is this our first hit? + if (_first_hit == -1) + { + // Count the one that isn't the cue ball ;) + if (ballIsCue(ball1)) + { + _first_hit = ball2; + _broke = true; + } + else if (ballIsCue(ball2)) + { + _first_hit = ball1; + _broke = true; + } + } +} + +void EightBall::motionStopped() +{ + // The physics engine has finished its job, turn it off to save CPU time + KueGlobal::physics()->stop(); + + // Did we hit a ball? And did we own that ball? + if ((_first_hit == -1) || ownsBall(!_current_team, _first_hit)) + { + // Nope, scratch + _scratch = true; + } + + // Did we hit a magic ball first when there are other balls left? + if ((!onlyMagicLeft(_current_team)) && ballIsMagic(_first_hit)) + { + // Scratch! + _scratch = true; + } + + // We downright lose if we scratch on the 8-ball (HA!) + if (onlyMagicLeft(_current_team) && _scratch) + { + playerWins(!_current_team); + return; + } + + // We lose our turn if the shot was a scratch, or we sunk nothing + if ((_scratch) || (_first_sunk == -1)) + { + if (_current_team == 0) + _current_team = 1; + else + _current_team = 0; + + _current_player = KueGlobal::teams()->at(_current_team)->nextPlayer(); + } + + if (_first_sunk != -1) + { + if (_game_called == GAME_UNCALLED) + { + if (_current_team) + { + _game_called = (ballIsSolid(_first_sunk) ? GAME_PLAYER1_STRIPES : GAME_PLAYER1_SOLIDS); + } + else + { + _game_called = (ballIsSolid(_first_sunk) ? GAME_PLAYER1_SOLIDS : GAME_PLAYER1_STRIPES); + } + } + } + + // Reset our shot state + _first_hit = -1; + _first_sunk = -1; + + // Did we scratch? + if (_scratch) + { + // Create the cue ball again + KueBilliard cue( + KueGlobal::physics()->fieldWidth() / 4.0, + KueGlobal::physics()->fieldHeight() / 2.0, + KueUtility::defaultBilliardRadius() + ); + + + if (_broke) + { + // We scratched, the cue ball goes back home + emit(showMessage(placeCueBallMessage())); + _current_player->placeBilliard( + 0, + cue, + KueGlobal::physics()->fieldWidth() / 4.0, + this, + TQ_SLOT(cuePlaced()) + ); + } + else + { + KueGlobal::physics()->insertBilliard(0, cue); + cuePlaced(); + } + } + else + { + emit(showMessage(startShotMessage())); + // The cue ball stays where it is, go right to the shot + _current_player->takeShot(0, false, this, TQ_SLOT(shotTaken())); + } +} + +// Is a ball solid? +bool EightBall::ballIsSolid(unsigned int number) +{ + return ((number > 0) && (number < 8)); +} + +// Is a ball 'magic' (8 ball)? +bool EightBall::ballIsMagic(unsigned int number) +{ + return (number == 8); +} + +// Is a ball striped? +bool EightBall::ballIsStripe(unsigned int number) +{ + return (number > 8); +} + +// Is a ball the cue ball (ball 0) +bool EightBall::ballIsCue(unsigned int number) +{ + return (number == 0); +} + +// Does the given player only have the 8 ball left to sink? +bool EightBall::onlyMagicLeft(int player) +{ + // It's impossible if the game is uncalled (any game with a sunk ball is called) + if (_game_called == GAME_UNCALLED) + return false; + + // Check all the billiards belonging to the player + for (unsigned int x = 1;x < BILLIARDS_COUNT;x++) + { + // Does the player own it, and does it still exist + if (ownsBall(player, x) && KueGlobal::physics()->billiards()[x]) + { + // So apparently there is more than magic left + return false; + } + } + + // Nope, only magic + return true; +} + +void EightBall::playerWins(int player) +{ + Q_UNUSED(player); + TQString message; + + // Announce the winner + message = i18n("%1 wins!").arg(_current_player->name()); + + // Show the message + emit(showMessage(message)); + + // Tell the rest of the game about the stunning victory + emit(gameOver(message)); +} + +// Does the player own the given ball +bool EightBall::ownsBall(int player, unsigned int ball) +{ + // Until the game is called, nobody owns anything + if (_game_called == GAME_UNCALLED) + { + return false; + } + + // And nobody ever owns the 8 ball + if (ballIsMagic(ball)) + return false; + + if (player) + { + return (_game_called == GAME_PLAYER1_STRIPES) ? ballIsSolid(ball) : ballIsStripe(ball); + } + else + { + return (_game_called == GAME_PLAYER1_STRIPES) ? ballIsStripe(ball) : ballIsSolid(ball); + } +} + +TQString EightBall::startShotMessage() +{ + TQString message; + // What type of shot is this? + if (_broke) + message = i18n("%1's shot").arg(_current_player->name()); + else + message = i18n("%1's break shot").arg(_current_player->name()); + + message = message + " " + sideString(); + + return message; +} + +TQString EightBall::placeCueBallMessage() +{ + TQString message; + + // Tell the user what is going on + message = i18n("%1 placing cue ball").arg(_current_player->name()); + message = message + " " + sideString(); + + return message; +} + + +TQString EightBall::sideString() +{ + if (_game_called == GAME_UNCALLED) + return ""; + + if (_current_team) + return (_game_called == GAME_PLAYER1_STRIPES) ? i18n("(solids)") : i18n("(stripes)"); + else + return (_game_called == GAME_PLAYER1_STRIPES) ? i18n("(stripes)") : i18n("(solids)"); +} + +void EightBall::cuePlaced() +{ + // Tell the interface code to start the shot + emit(showMessage(startShotMessage())); + _current_player->takeShot(0, true, this, TQ_SLOT(shotTaken())); +} + +void EightBall::shotTaken() +{ + // Start the physics engine + KueGlobal::physics()->start(); + + // Reset the shot-related variables + _scratch = false; + _first_hit = -1; + _first_sunk = -1; +} + + +#include "8ball.moc" diff --git a/kue/modules/8ball/8ball.h b/kue/modules/8ball/8ball.h new file mode 100644 index 00000000..73ee3864 --- /dev/null +++ b/kue/modules/8ball/8ball.h @@ -0,0 +1,88 @@ +#ifndef _EIGHTBALL_H +#define _EIGHTBALL_H + +#include <tqobject.h> +#include <klibloader.h> + +#include "vector.h" +#include "point.h" +#include "rules.h" + + +// Forward declarations of our helper classes +class TQString; +class KuePlayer; + +// Possible values of _game_called +enum gameCallType {GAME_UNCALLED, GAME_PLAYER1_STRIPES, GAME_PLAYER1_SOLIDS}; + + +class EightBallFactory : KLibFactory { + public: + TQObject* createObject(TQObject*, const char*, const char*, const TQStringList &); +}; + +class EightBall : public KueRulesEngine { + TQ_OBJECT + public: + EightBall(TQObject *parent, const char *name); + ~EightBall(); + + void start(); + + protected slots: + // Called by physics engine when a billiard is sunk + void billiardSunk(unsigned int ball, unsigned int pocket); + // Called by physics engine when a billiard is struck (by the cue ball or another billiard) + void billiardHit(unsigned int ball1, unsigned int ball2); + // Called by the physics engine when all billiards have stopped moving + void motionStopped(); + + void cuePlaced(); + void shotTaken(); + + private: + // Ask the interface to start the shot + TQString startShotMessage(); + // Ask the interface to place the cue ball + TQString placeCueBallMessage(); + + // Does a player only have an 8 ball left to shoot at? + bool onlyMagicLeft(int player); + // Does a player own a given ball? + bool ownsBall(int player, unsigned int ball); + + // Is a ball solid? + bool ballIsSolid(unsigned int number); + // Is a ball stripped? + bool ballIsStripe(unsigned int number); + // Is a ball the cue ball? + bool ballIsCue(unsigned int number); + // Is a ball the magic ball (8)? + bool ballIsMagic(unsigned int number); + + // Handle a player's victory + void playerWins(int player); + + // Is this shot a scratch? + bool _scratch; + // First ball sunk + int _first_sunk; + // First ball hit + int _first_hit; + + // The current player + KuePlayer *_current_player; + // The current team + int _current_team; + // Who's stripes? And who's solids? + gameCallType _game_called; + + // Have we had a successful break? + bool _broke; + + TQString sideString(); +}; + + +#endif diff --git a/kue/modules/8ball/8ball.plugin b/kue/modules/8ball/8ball.plugin new file mode 100644 index 00000000..eb2e0669 --- /dev/null +++ b/kue/modules/8ball/8ball.plugin @@ -0,0 +1,6 @@ +Filename=libkue8ball +Type=rulesengine +Name=8-Ball +Description=A very simple version of 8-ball pool +MinTeams=2 +MaxTeams=2 diff --git a/kue/modules/8ball/CMakeLists.txt b/kue/modules/8ball/CMakeLists.txt new file mode 100644 index 00000000..5009d05c --- /dev/null +++ b/kue/modules/8ball/CMakeLists.txt @@ -0,0 +1,42 @@ +################################################################################ +# Improvements and feedback are welcome! # +# This software is licensed under the terms of the GNU GPL v3 license. # +################################################################################ + +include_directories( + ${CMAKE_BINARY_DIR} + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_CURRENT_SOURCE_DIR} + ${TDE_INCLUDE_DIR} + ${TQT_INCLUDE_DIRS} +) + +link_directories( + ${TQT_LIBRARY_DIRS} +) + +### kue8ball (library) ######################################################### +tde_add_library( + kue8ball SHARED AUTOMOC + + SOURCES + 8ball.cpp + + LINK + tdeio-shared + kue-shared + + DESTINATION + ${LIB_INSTALL_DIR} +) + +### data ####################################################################### +install( + FILES + 8ball.plugin + + DESTINATION + ${DATA_INSTALL_DIR}/kue +) + +# kate: replace-tabs true; tab-width 2;
\ No newline at end of file diff --git a/kue/modules/9ball/9ball.cpp b/kue/modules/9ball/9ball.cpp new file mode 100644 index 00000000..eafaa2a8 --- /dev/null +++ b/kue/modules/9ball/9ball.cpp @@ -0,0 +1,217 @@ +#include <kdebug.h> +#include <tdelocale.h> +#include <stdlib.h> +#include <stdio.h> + +#include "9ball.h" +#include "interface.h" +#include "physics.h" +#include "utility.h" +#include "team.h" +#include "player.h" +#include "global.h" + +const unsigned int BILLIARDS_COUNT = 15; + +K_EXPORT_COMPONENT_FACTORY( libkue9ball, NineBallFactory ) + +TQObject *NineBallFactory::createObject (TQObject *parent, const char* name, const char* classname, const TQStringList &args = TQStringList() ) +{ + Q_UNUSED(args); + + if (classname != TQString("KueRulesEngine")) + return 0; + + return new NineBall(parent, name); +} + +NineBall::NineBall(TQObject *parent, const char *name) : KueRulesEngine(parent, name) +{ + KueUtility::layoutTable(); + KueUtility::layoutPockets(); + KueUtility::layoutBilliards(KueUtility::Diamond); + + // Reset our state (NOTE: anyone see anything missing?) + _current_team = 0; + _first_hit = -1; + _first_sunk = -1; + _foul = false; + _broke = false; + + _current_player = KueGlobal::teams()->at(_current_team)->nextPlayer(); +} + +NineBall::~NineBall() +{ +} + +void NineBall::start() +{ + cuePlaced(); +} + +void NineBall::billiardSunk(unsigned int ball, unsigned int pocket) +{ + Q_UNUSED(pocket); + + // Ah, it's all good... + if (_first_sunk == -1) + _first_sunk = ball; + + if (ball == 0) + _foul = true; +} + +void NineBall::billiardHit(unsigned int ball1, unsigned int ball2) +{ + // Is this our first hit? + if (_first_hit == -1) + { + // Select the ball involved which isn't the cue ball + _first_hit = ball1 ? ball1 : ball2; + + if (!ballIsLowest(_first_hit)) + _foul = true; + + _broke = true; + } +} + +void NineBall::motionStopped() +{ + // The physics engine has finished its job, turn it off to save CPU time + KueGlobal::physics()->stop(); + + // Are all the balls 1-9 gone? + if (ballIsLowest(10)) + { + playerWins(); + return; + } + + // We lose our turn if the shot was a scratch, or we sunk nothing + if ((_foul) || (_first_sunk == -1)) + { + if (_current_team == 0) + _current_team = 1; + else + _current_team = 0; + + _current_player = KueGlobal::teams()->at(_current_team)->nextPlayer(); + } + + // Reset our shot state + _first_hit = -1; + _first_sunk = -1; + + // Did we scratch? + if (_foul) + { + // Recreate the cue call + KueBilliard cue( + KueGlobal::physics()->fieldWidth() / 4.0, + KueGlobal::physics()->fieldHeight() / 2.0, + KueUtility::defaultBilliardRadius() + ); + + if (_broke) + { + // Ask the user where to place the billiard + emit(showMessage(placeCueBallMessage())); + _current_player->placeBilliard( + 0, + cue, + KueGlobal::physics()->fieldWidth() / 4.0, + this, + TQ_SLOT(cuePlaced()) + ); + } + else + { + // We scratched, the cue ball goes back home + KueGlobal::physics()->insertBilliard(0, cue); + cuePlaced(); + } + } + else + { + emit(showMessage(startShotMessage())); + // The cue ball stays where it is, go right to the shot + _current_player->takeShot(0, false, this, TQ_SLOT(shotTaken())); + } +} + +// Is a ball 'magic' (8 ball)? +bool NineBall::ballIsLowest(unsigned int number) +{ + for (unsigned int x = 1;x < number;x++) + if (KueGlobal::physics()->billiards()[x]) + return false; + + return true; +} + +// Is a ball the cue ball (ball 0) +bool NineBall::ballIsCue(unsigned int number) +{ + return (number == 0); +} + +void NineBall::playerWins() +{ + TQString message; + + // Announce the winner + message = i18n("%1 wins!").arg(_current_player->name()); + + // Show the message + emit(showMessage(message)); + + // Tell the rest of the game about the stunning victory + emit(gameOver(message)); +} + +TQString NineBall::startShotMessage() +{ + TQString message; + // What type of shot is this? + if (_broke) + message = i18n("%1's shot").arg(_current_player->name()); + else + message = i18n("%1's break shot").arg(_current_player->name()); + + return message; +} + +TQString NineBall::placeCueBallMessage() +{ + TQString message; + + // Tell the user what is going on + message = i18n("%1 placing cue ball").arg(_current_player->name()); + + return message; +} + + + +void NineBall::cuePlaced() +{ + // Tell the interface code to start the shot + emit(showMessage(startShotMessage())); + _current_player->takeShot(0, true, this, TQ_SLOT(shotTaken())); +} + +void NineBall::shotTaken() +{ + // Start the physics engine + KueGlobal::physics()->start(); + + // Reset the shot-related variables + _foul = false; + _first_hit = -1; + _first_sunk = -1; +} + + +#include "9ball.moc" diff --git a/kue/modules/9ball/9ball.h b/kue/modules/9ball/9ball.h new file mode 100644 index 00000000..25a1774e --- /dev/null +++ b/kue/modules/9ball/9ball.h @@ -0,0 +1,71 @@ +#ifndef _EIGHTBALL_H +#define _EIGHTBALL_H + +#include <tqobject.h> +#include <klibloader.h> + +#include "vector.h" +#include "point.h" +#include "rules.h" + + +// Forward declarations of our helper classes +class TQString; +class KuePlayer; + +class NineBallFactory : KLibFactory { + public: + TQObject* createObject(TQObject*, const char*, const char*, const TQStringList &); +}; + +class NineBall : public KueRulesEngine { + TQ_OBJECT + public: + NineBall(TQObject *parent, const char *name); + ~NineBall(); + + void start(); + + protected slots: + // Called by physics engine when a billiard is sunk + void billiardSunk(unsigned int ball, unsigned int pocket); + // Called by physics engine when a billiard is struck (by the cue ball or another billiard) + void billiardHit(unsigned int ball1, unsigned int ball2); + // Called by the physics engine when all billiards have stopped moving + void motionStopped(); + + void cuePlaced(); + void shotTaken(); + + private: + // Ask the interface to start the shot + TQString startShotMessage(); + // Ask the interface to place the cue ball + TQString placeCueBallMessage(); + + // Is a ball the cue ball? + bool ballIsCue(unsigned int number); + // Is a ball the magic ball (8)? + bool ballIsLowest(unsigned int number); + + // Handle a player's victory + void playerWins(); + + // Is this shot a scratch? + bool _foul; + // First ball sunk + int _first_sunk; + // First ball hit + int _first_hit; + + // The current player + KuePlayer *_current_player; + // The current team + int _current_team; + + // Have we had a successful break? + bool _broke; +}; + + +#endif diff --git a/kue/modules/9ball/9ball.plugin b/kue/modules/9ball/9ball.plugin new file mode 100644 index 00000000..8c6e0292 --- /dev/null +++ b/kue/modules/9ball/9ball.plugin @@ -0,0 +1,6 @@ +Filename=libkue9ball +Type=rulesengine +Name=9-Ball +Description=A very simple version of 9-ball pool +MinTeams=2 +MaxTeams=2 diff --git a/kue/modules/9ball/CMakeLists.txt b/kue/modules/9ball/CMakeLists.txt new file mode 100644 index 00000000..8e046cdc --- /dev/null +++ b/kue/modules/9ball/CMakeLists.txt @@ -0,0 +1,42 @@ +################################################################################ +# Improvements and feedback are welcome! # +# This software is licensed under the terms of the GNU GPL v3 license. # +################################################################################ + +include_directories( + ${CMAKE_BINARY_DIR} + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_CURRENT_SOURCE_DIR} + ${TDE_INCLUDE_DIR} + ${TQT_INCLUDE_DIRS} +) + +link_directories( + ${TQT_LIBRARY_DIRS} +) + +### kue9ball (library) ######################################################### +tde_add_library( + kue9ball SHARED AUTOMOC + + SOURCES + 9ball.cpp + + LINK + tdeio-shared + kue-shared + + DESTINATION + ${LIB_INSTALL_DIR} +) + +### data ####################################################################### +install( + FILES + 9ball.plugin + + DESTINATION + ${DATA_INSTALL_DIR}/kue +) + +# kate: replace-tabs true; tab-width 2;
\ No newline at end of file diff --git a/kue/modules/CMakeLists.txt b/kue/modules/CMakeLists.txt new file mode 100644 index 00000000..bf9d6f43 --- /dev/null +++ b/kue/modules/CMakeLists.txt @@ -0,0 +1,8 @@ +################################################################################ +# Improvements and feedback are welcome! # +# This software is licensed under the terms of the GNU GPL v3 license. # +################################################################################ + +add_subdirectory(8ball) +add_subdirectory(9ball) +add_subdirectory(freeplay)
\ No newline at end of file diff --git a/kue/modules/freeplay/CMakeLists.txt b/kue/modules/freeplay/CMakeLists.txt new file mode 100644 index 00000000..0f935a3c --- /dev/null +++ b/kue/modules/freeplay/CMakeLists.txt @@ -0,0 +1,42 @@ +################################################################################ +# Improvements and feedback are welcome! # +# This software is licensed under the terms of the GNU GPL v3 license. # +################################################################################ + +include_directories( + ${CMAKE_BINARY_DIR} + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_CURRENT_SOURCE_DIR} + ${TDE_INCLUDE_DIR} + ${TQT_INCLUDE_DIRS} +) + +link_directories( + ${TQT_LIBRARY_DIRS} +) + +### kuefreeplay (library) ####################################################### +tde_add_library( + kuefreeplay SHARED AUTOMOC + + SOURCES + freeplay.cpp + + LINK + tdeio-shared + kue-shared + + DESTINATION + ${LIB_INSTALL_DIR} +) + +### data ####################################################################### +install( + FILES + freeplay.plugin + + DESTINATION + ${DATA_INSTALL_DIR}/kue +) + +# kate: replace-tabs true; tab-width 2;
\ No newline at end of file diff --git a/kue/modules/freeplay/freeplay.cpp b/kue/modules/freeplay/freeplay.cpp new file mode 100644 index 00000000..022c0ec9 --- /dev/null +++ b/kue/modules/freeplay/freeplay.cpp @@ -0,0 +1,93 @@ +#include "freeplay.h" +#include "interface.h" +#include "physics.h" +#include "utility.h" +#include "player.h" +#include "global.h" +#include "team.h" + +#include <tdelocale.h> +#include <tdemessagebox.h> +#include <stdlib.h> +#include <stdio.h> +#include <krandomsequence.h> + +const int BILLIARDS_COUNT = 15; + +K_EXPORT_COMPONENT_FACTORY( libkuefreeplay, FreePlayFactory ) + +TQObject *FreePlayFactory::createObject (TQObject *parent, const char* name, const char* classname, const TQStringList &args = TQStringList() ) +{ + Q_UNUSED(args); + + if (classname != TQString("KueRulesEngine")) + return 0; + + return new FreePlay(parent, name); +} + +FreePlay::FreePlay(TQObject *parent, const char *name) : KueRulesEngine(parent, name) +{ + KueUtility::layoutTable(); + KueUtility::layoutPockets(); + KueUtility::layoutBilliards(KueUtility::Triangle); + + _current_team = 0; +} + +FreePlay::~FreePlay() +{ +} + +void FreePlay::start() +{ + startShot(); +} + +void FreePlay::motionStopped() +{ + // The physics engine has finished its job, turn it off to save CPU time + KueGlobal::physics()->stop(); + + _current_team++; + + if (_current_team == KueGlobal::teams()->count()) + _current_team = 0; + + startShot(); +} + +void FreePlay::shotTaken() +{ + // Start the physics engine + KueGlobal::physics()->start(); +} + +void FreePlay::startShot() +{ + TQString message; + KuePlayer *current_player = KueGlobal::teams()->at(_current_team)->nextPlayer(); + + // Did the cue ball get sunk? Replace it. + if (!KueGlobal::physics()->billiards()[0]) + { + KueBilliard cue( + KueGlobal::physics()->fieldWidth() / 4.0, + KueGlobal::physics()->fieldHeight() / 2.0, + KueUtility::defaultBilliardRadius() + ); + + KueGlobal::physics()->insertBilliard(0, cue); + } + + // What type of shot is this? + message = i18n("%1's shot").arg(current_player->name()); + + // Show the generated message + emit(showMessage(message)); + + // Tell the interface to start the shot UI. + current_player->takeShot(0, false, this, TQ_SLOT(shotTaken())); +} + +#include "freeplay.moc" diff --git a/kue/modules/freeplay/freeplay.h b/kue/modules/freeplay/freeplay.h new file mode 100644 index 00000000..f51ba950 --- /dev/null +++ b/kue/modules/freeplay/freeplay.h @@ -0,0 +1,46 @@ +#ifndef _FREEPLAY_H +#define _FREEPLAY_H + +#include <tqobject.h> +#include <klibloader.h> + +#include "vector.h" +#include "point.h" +#include "rules.h" + + +// Forward declarations of our helper classes +class TQString; + +// Possible values of _game_called +enum gameCallType {GAME_UNCALLED, GAME_PLAYER1_STRIPES, GAME_PLAYER1_SOLIDS}; + + +class FreePlayFactory : KLibFactory { + public: + TQObject* createObject(TQObject*, const char*, const char*, const TQStringList &); +}; + +class FreePlay : public KueRulesEngine { + TQ_OBJECT + public: + FreePlay(TQObject *parent, const char *name); + ~FreePlay(); + + void start(); + + protected slots: + // Called by the physics engine when all billiards have stopped moving + void motionStopped(); + // Called by the interface after the user has decided on a shot + void shotTaken(); + + private: + // Ask the interface to start the shot + void startShot(); + + unsigned int _current_team; +}; + + +#endif diff --git a/kue/modules/freeplay/freeplay.h.save b/kue/modules/freeplay/freeplay.h.save new file mode 100644 index 00000000..36b9404e --- /dev/null +++ b/kue/modules/freeplay/freeplay.h.save @@ -0,0 +1,85 @@ +#ifndef _EIGHTBALL_H +#define _EIGHTBALL_H + +#include <ntqobject.h> +#include <klibloader.h> + +#include "vector.h" +#include "point.h" +#include "rules.h" + + +// Forward declarations of our helper classes +class TQString; + +// Possible values of _game_called +enum gameCallType {GAME_UNCALLED, GAME_PLAYER1_STRIPES, GAME_PLAYER1_SOLIDS}; + + +class FreePlayFactory : KLibFactory { + public: + TQObject* createObject(TQObject*, const char*, const char*, const TQStringList &); +}; + +class FreePlay : public TQObject, public KueRulesEngine { + Q_OBJECT + public: + FreePlay(); + ~FreePlay(); + + // Is the game over? + bool gameIsOver() { return false; } + + protected slots: + // Called by physics engine when a billiard is sunk + void billiardSunk(unsigned int ball, unsigned int pocket); + // Called by physics engine when a billiard is struck (by the cue ball or another billiard) + void billiardHit(unsigned int ball1, unsigned int ball2); + // Called by the physics engine when all billiards have stopped moving + void motionStopped(); + + // Called by the interface when the user has decided on a cue location + void cuePlaced(point &location); + // Called by the interface when the user is deciding on a cue location + void previewCuePlace(point &location); + // Called by the interface after the user has decided on a hot + void shotTaken(vector &velocity); + + private: + // Ask the interface to start the shot + void startShot(bool forward_only); + // Ask the interface to place the cue ball + void placeCueBall(); + + // Does a player only have an 8 ball left to shoot at? + bool onlyMagicLeft(int player); + // Does a player own a given ball? + bool ownsBall(int player, unsigned int ball); + + // Is a ball solid? + bool ballIsSolid(unsigned int number); + // Is a ball stripped? + bool ballIsStripe(unsigned int number); + // Is a ball the cue ball? + bool ballIsCue(unsigned int number); + // Is a ball the magic ball (8)? + bool ballIsMagic(unsigned int number); + + // Handle a player's victory + void playerWins(int player); + + // Is this shot a scratch? + bool _scratch; + // First ball sunk + int _first_sunk; + // First ball hit + int _first_hit; + + // The current player + int _current_player; + // Who's stripes? And who's solids? + gameCallType _game_called; +}; + + +#endif diff --git a/kue/modules/freeplay/freeplay.plugin b/kue/modules/freeplay/freeplay.plugin new file mode 100644 index 00000000..9e559e15 --- /dev/null +++ b/kue/modules/freeplay/freeplay.plugin @@ -0,0 +1,5 @@ +Filename=libkuefreeplay +Type=rulesengine +Name=Free Play +Description=A very simple version of 8-ball pool +# There are no minimum or maximum number of teams diff --git a/kue/newgame.cpp b/kue/newgame.cpp new file mode 100644 index 00000000..9ddf8f1b --- /dev/null +++ b/kue/newgame.cpp @@ -0,0 +1,242 @@ +#include <assert.h> + +#include <tqhbox.h> +#include <tqvbox.h> +#include <tqlayout.h> +#include <tqlabel.h> + +#include <tdelocale.h> +#include <kcombobox.h> +#include <klineedit.h> +#include <kseparator.h> +#include <tqpushbutton.h> +#include <kdebug.h> +#include <kcolorbutton.h> + +#include "team.h" +#include "localplayer.h" +#include "newgame.h" + +newGameDialog::newGameDialog(TQWidget *parent, const char *name) : + KDialogBase(parent, name, true, i18n("New Game"), Ok|Cancel) +{ + _page = new TQWidget( this ); + // Just makes it look nicer + setMinimumSize(400, 300); + + setMainWidget(_page); + _top_layout = new TQVBoxLayout(_page, 0, KDialogBase::spacingHint()); + + _game_type = new KComboBox(_page); + + // Get a list of available rules plugins + _plugins_list = KuePluginLoader::available(); + TQValueList<KuePluginInfo>::const_iterator plugin = _plugins_list.begin(); + + // Go over the plugins that KuePluginLoader::available claims we have + for(;plugin != _plugins_list.end();plugin++) + { + // We only want plugins of the type "rulesengine" + if ((*plugin).type == "rulesengine") + _game_type->insertItem((*plugin).name); + } + + connect(_game_type, TQ_SIGNAL(activated(int)), this, TQ_SLOT(slotNewPluginSelection())); + + // Set up an hbox to put the combo and its label in + TQHBoxLayout *hbox = new TQHBoxLayout(_top_layout, KDialogBase::spacingHint()); + hbox->addWidget(new TQLabel(_game_type, i18n("Game &rules:"), _page)); + hbox->addWidget(_game_type); + hbox->setStretchFactor(_game_type, 2); + + // Add a separator + _top_layout->addWidget(new KSeparator(KSeparator::HLine, _page)); + + // Add the players widget + _player_widget = new TQWidget(_page); + _player_layout = new TQGridLayout(_player_widget); + _player_layout->setSpacing(KDialogBase::spacingHint()); + _top_layout->addWidget(_player_widget); + + // Buttons to add/remove players + _add_player_button = new TQPushButton(i18n("&Add Player"), _page); + connect(_add_player_button, TQ_SIGNAL(clicked()), this, TQ_SLOT(addPlayerRow())); + _remove_player_button = new TQPushButton(i18n("Remove &Player"), _page); + connect(_remove_player_button, TQ_SIGNAL(clicked()), this, TQ_SLOT(removePlayerRow())); + + // Set up a second hbox for the add/remove player buttons + hbox = new TQHBoxLayout(_top_layout, KDialogBase::spacingHint()); + hbox->addStretch(2); + hbox->addWidget(_add_player_button); + hbox->addWidget(_remove_player_button); + + // This dialog is slightly less confusing with separator + enableButtonSeparator(true); + + // Populates the player widget initially, and sets up the player + // buttons' enabled state + slotNewPluginSelection(); +} + +newGameDialog::~newGameDialog() +{ +} + +void newGameDialog::slotOk() +{ + KDialogBase::slotOk(); +} + +KuePluginInfo newGameDialog::selectedPlugin() +{ + // Find the plugin the user requested + TQValueList<KuePluginInfo>::const_iterator plugin = _plugins_list.begin(); + + for(;plugin != _plugins_list.end();plugin++) + { + if (((*plugin).name == _game_type->currentText()) && ((*plugin).type == "rulesengine")) + return *plugin; + } + + kdWarning() << "Unable to find KuePluginInfo matching user selection" << endl; + + // Oops + return KuePluginInfo(); +} + +TQValueList<KueTeam*> newGameDialog::selectedTeams() +{ + TQValueList<KueTeam *> ret; + + while (_players.count()) + { + KueTeam *temp = new KueTeam; + playerRow *row = _players.first(); + + // Add the player + temp->addPlayer(new KueLocalPlayer(row->nameEdit->text(), row->colorButton->color())); + ret.append(temp); + + // Remove the row + removePlayerRow(row); + } + + return ret; +} + +void newGameDialog::addPlayerRow() +{ + playerRow *row = new playerRow; + int new_row = _players.count(); + + row->label = new TQLabel(i18n("Player %1:").arg(new_row + 1), _player_widget); + row->nameEdit = new KLineEdit(i18n("Player %1").arg(new_row + 1), _player_widget); + row->colorButton = new KColorButton(defaultPlayerColor(new_row), _player_widget); + + _player_layout->addWidget(row->label, new_row, 0); + _player_layout->addWidget(row->nameEdit, new_row, 1); + _player_layout->addWidget(row->colorButton, new_row, 2); + _player_layout->setRowStretch(new_row, 0); + _player_layout->setRowStretch(new_row + 1, 1); + + row->label->show(); + row->nameEdit->show(); + row->colorButton->show(); + + + _players.append(row); + updateButtons(); +} + +void newGameDialog::removePlayerRow(playerRow *row) +{ + delete row->label; + delete row->nameEdit; + delete row->colorButton; + delete row; + + _players.remove(row); + updateButtons(); +} + +void newGameDialog::removePlayerRow() +{ + playerRow *row = _players.last(); + removePlayerRow(row); +} + +void newGameDialog::slotNewPluginSelection() +{ + KuePluginInfo info = selectedPlugin(); + + // Cache the result so we don't have to call the (relatively) expensive + // selectedPlugin() every time we need these values + _min_players = info.minTeams; + _max_players = info.maxTeams; + + assert(_min_players <= _max_players); + + while (_players.count() < _min_players) + { + addPlayerRow(); + } + + while (_players.count() > _max_players) + { + removePlayerRow(); + } + + updateButtons(); +} + + +void newGameDialog::updateButtons() +{ + _remove_player_button->setEnabled(_players.count() > _min_players); + _add_player_button->setEnabled(_players.count() < _max_players); +} + +TQColor newGameDialog::defaultPlayerColor(unsigned int player) +{ + // We only have 16 colors :( + player = player % 16; + + switch (player) + { + case 0: + return TQt::white; + case 1: + return TQt::black; + case 2: + return TQt::red; + case 3: + return TQt::green; + case 4: + return TQt::blue; + case 5: + return TQt::cyan; + case 6: + return TQt::magenta; + case 7: + return TQt::yellow; + case 8: + return TQt::gray; + case 9: + return TQt::darkRed; + case 10: + return TQt::darkGreen; + case 11: + return TQt::darkBlue; + case 12: + return TQt::darkCyan; + case 13: + return TQt::darkMagenta; + case 14: + return TQt::darkYellow; + default: + return TQt::darkGray; + } +} + +#include "newgame.moc" + diff --git a/kue/newgame.h b/kue/newgame.h new file mode 100644 index 00000000..6cc0da5b --- /dev/null +++ b/kue/newgame.h @@ -0,0 +1,64 @@ +#ifndef _NEWGAME_H +#define _NEWGAME_H + +#include <kdialogbase.h> +#include "pluginloader.h" +#include <ntqvaluelist.h> + +class KColorButton; +class KComboBox; +class KueTeam; +class TQGridLayout; +class TQVBoxLayout; +class TQLabel; +class KLineEdit; +class TQPushButton; + +struct playerRow +{ + TQLabel *label; + KLineEdit *nameEdit; + KColorButton *colorButton; +}; + +class newGameDialog : public KDialogBase +{ + TQ_OBJECT + public: + newGameDialog(TQWidget *parent, const char *name = 0); + ~newGameDialog(); + + KuePluginInfo selectedPlugin(); + TQValueList<KueTeam*> selectedTeams(); + + protected slots: + void slotOk(); + void slotNewPluginSelection(); + + void addPlayerRow(); + void removePlayerRow(); + + protected: + void removePlayerRow(playerRow *); + void updateButtons(); + TQColor defaultPlayerColor(unsigned int player); + + TQWidget *_page; + TQVBoxLayout *_top_layout; + + TQPushButton *_add_player_button; + TQPushButton *_remove_player_button; + + TQWidget *_player_widget; + TQGridLayout *_player_layout; + TQPtrList<playerRow> _players; + + KComboBox *_game_type; + TQValueList<KuePluginInfo> _plugins_list; + + unsigned int _max_players; + unsigned int _min_players; +}; + +#endif + diff --git a/kue/physics.cpp b/kue/physics.cpp new file mode 100644 index 00000000..f9822f2e --- /dev/null +++ b/kue/physics.cpp @@ -0,0 +1,189 @@ +#include "physics.h" +#include "rules.h" +#include "interface.h" +#include "global.h" + +#include <GL/gl.h> + +#include <math.h> +#include <stdlib.h> +#include <string.h> +#include <ntqsize.h> +#include <stdio.h> +#include <kdebug.h> + +const double COLLISION_DRAG = 0.95; +const double BUMPER_DRAG = 0.8; + +KuePhysics::KuePhysics() : _running(false), _field_width(0.0), _field_height(0.0) +{ + _billiards.setAutoDelete(true); + _pockets.setAutoDelete(true); +} + +KuePhysics::~KuePhysics() +{ +} + +void KuePhysics::timerEvent ( TQTimerEvent * ) +{ + // Have all the balls stopped? + if (!run(TIME_CHUNK)) + { + // Tell the rules manager + emit(motionStopped()); + return; + } + + // We need a redisplay + KueGlobal::glWidget()->updateGL(); +} + +void KuePhysics::seperate(KueBilliard *a, KueBilliard *b) +{ + double distance = a->distance(*b); + double delta = (a->radius() + b->radius() - distance) / 2.0; + double angle = a->angle(*b); + double delta_x = cos(angle) * delta; + double delta_y = sin(angle) * delta; + + a->setPositionX(a->positionX() - delta_x); + a->setPositionY(a->positionY() - delta_y); + + b->setPositionX(b->positionX() + delta_x); + b->setPositionY(b->positionY() + delta_y); +} + +bool KuePhysics::allStop() +{ + for (unsigned int i = 0;i < _billiards.size();i++) + if (_billiards[i]) + if (!_billiards[i]->isStopped()) + return false; + + return true; +} + +void KuePhysics::doPocketing() +{ + for (unsigned int b = 0;b < _billiards.size();b++) + { + if (!_billiards[b]) + continue; + + for (unsigned int p = 0;p < _pockets.size();p++) + { + if (!_pockets[p]) + continue; + + if (_billiards[b]->distance(*_pockets[p]) <= _pockets[p]->radius()) + { + emit(billiardSunk(b, p)); + + _billiards.remove(b); + + break; + } + } + } +} + +void KuePhysics::insertBilliard(unsigned int index, const KueBilliard &b) +{ + // Do we need to grow the vector? + if (index >= _billiards.size()) + _billiards.resize(index + 1); + + // Insert the new billiard + _billiards.insert(index, new KueBilliard(b)); +} + +void KuePhysics::insertPocket(unsigned int index, const KuePocket &p) +{ + // Grow the vector as needed + if (index >= _pockets.size()) + _pockets.resize(index + 1); + + // Insert the new pocket + _pockets.insert(index, new KuePocket(p)); +} + +bool KuePhysics::run(int milliseconds) +{ + // The collison code accepts values in seconds, not milliseconds + double seconds = milliseconds / 1000.0; + + for (int i = _billiards.size() - 1;i >= 0;i--) { + if (!_billiards[i]) + continue; + + // Move the billiards forwards along their velocity vectors + _billiards[i]->step(seconds); + + // Save the x, and radius y values so we don't waste a bunch of + // function calls + double x = _billiards[i]->positionX(); + double y = _billiards[i]->positionY(); + double radius = _billiards[i]->radius(); + + // Check if the billiard intersects with any edge, and if it does + // reflect it along the side it hit, and then move the billiard + // back on the playing field. + // Pretty terse code, but it really needs to be fast + if ((x + radius) > _field_width) + { + _billiards[i]->reflect(0.0); + _billiards[i]->velocity() *= BUMPER_DRAG; + _billiards[i]->setPositionX(_field_width - radius); + } + else if (x < radius) + { + _billiards[i]->reflect(0.0); + _billiards[i]->velocity() *= BUMPER_DRAG; + _billiards[i]->setPositionX(radius); + } + + if ((y + radius) > _field_height) + { + _billiards[i]->reflect(M_PI / 2.0); + _billiards[i]->velocity() *= BUMPER_DRAG; + _billiards[i]->setPositionY(_field_height - radius); + } + else if (y < radius) + { + _billiards[i]->reflect(M_PI / 2.0); + _billiards[i]->velocity() *= BUMPER_DRAG; + _billiards[i]->setPositionY(radius); + } + + for (unsigned int j = i + 1;j <= _billiards.size() - 1;j++) { + // If this isn't a billiard anymore, skip it + if (!_billiards[j]) + continue; + + // Are these billiards intersecting (colliding)? + if (_billiards[i]->intersects(*_billiards[j])) + { + // Update their velocity vectors + _billiards[i]->collide(*_billiards[j]); + // Physically seperate the two balls so we don't + // call the collision code again + seperate(_billiards[i], _billiards[j]); + + // Factor in collision drag + _billiards[i]->velocity() *= COLLISION_DRAG; + _billiards[j]->velocity() *= COLLISION_DRAG; + + // Tell the rules engine that we hit a ball + emit(billiardHit(i, j)); + } + } + } + + doPocketing(); + + // Return true if we aren't done yet + return (!allStop()); +} + +#include "physics.moc" diff --git a/kue/physics.h b/kue/physics.h new file mode 100644 index 00000000..e1affc8b --- /dev/null +++ b/kue/physics.h @@ -0,0 +1,76 @@ +#ifndef _PHYSICS_H +#define _PHYSICS_H + +#include "main.h" +#include "billiard.h" +#include "pocket.h" +#include "circle.h" +#include "point.h" +#include "vector.h" + +#include <ntqptrvector.h> + +const int TIME_CHUNK = 10; + +class KuePhysics : public TQObject { + TQ_OBJECT + + public: + KuePhysics(); + ~KuePhysics(); + + // Start the physics engine + void start() { _running = true; startTimer(TIME_CHUNK); } + // Full stop + void stop() { _running = false; killTimers(); } + + bool running() { return _running; } + + // Run the physics state for a set number of milliseconds + // You should really only pass this 0, to seperate billiards on demand + // If you can think of a clever use for it, go right ahead, though + bool run(int milliseconds); + + // Inserts a billard at a specific location + void insertBilliard(unsigned int billiard, const KueBilliard &b); + + // Removes a given billiard + void removeBilliard(unsigned int billiard) { _billiards.remove(billiard); } + + // Inserts pocket at a specific location + void insertPocket(unsigned int pocket, const KuePocket &p); + + // Removes a given pocket + void removePocket(unsigned int pocket) { _pockets.remove(pocket); } + + TQPtrVector<KueBilliard> & billiards() { return _billiards; } + TQPtrVector<KuePocket> & pockets() { return _pockets; } + + double fieldWidth() { return _field_width; } + double fieldHeight() { return _field_height; } + + void setFieldWidth(double field_width) { _field_width = field_width; } + void setFieldHeight(double field_height) { _field_height = field_height; } + + signals: + void billiardHit(unsigned int b1, unsigned int b2); + void billiardSunk(unsigned int b, unsigned int p); + void motionStopped(); + + protected: + void doPocketing(); + void timerEvent ( TQTimerEvent * ); + + bool _running; + void seperate(KueBilliard *a, KueBilliard *b); + + bool allStop(); + + TQPtrVector<KueBilliard> _billiards; + TQPtrVector<KuePocket> _pockets; + + double _field_width; + double _field_height; +}; + +#endif diff --git a/kue/player.h b/kue/player.h new file mode 100644 index 00000000..ad324db1 --- /dev/null +++ b/kue/player.h @@ -0,0 +1,32 @@ +#ifndef _PLAYER_H +#define _PLAYER_H + +#include <tqstring.h> +#include <tqobject.h> + +#include "circle.h" +#include "point.h" +#include "vector.h" +#include "billiard.h" + +class KuePlayer : public TQObject { + public: + KuePlayer(TQString name = TQString::null) { _name = name; } + ~KuePlayer() {} + + virtual const TQString &name() { return _name; } + void setName(const TQString &name) { _name = name; } + + // Rules' functions + + // Place the billiard 'b' at a user defined location + virtual void placeBilliard(int index, const KueBilliard &b, double line, TQObject *_dest = 0, const char *slot = 0) = 0; + + // Take a shot on billiard with index 'billiard_index' + virtual void takeShot(int billiard_index, bool forward_only = false, TQObject *_dest = 0, const char *slot = 0) = 0; + + protected: + TQString _name; +}; + +#endif diff --git a/kue/pluginloader.cpp b/kue/pluginloader.cpp new file mode 100644 index 00000000..39a64d63 --- /dev/null +++ b/kue/pluginloader.cpp @@ -0,0 +1,40 @@ +#include <tqstring.h> +#include <tqvaluelist.h> +#include <tdeglobal.h> +#include <kstandarddirs.h> +#include <tqfile.h> +#include <ksimpleconfig.h> +#include <limits.h> + +#include "pluginloader.h" + +TQValueList<KuePluginInfo> KuePluginLoader::available() { + TQValueList<KuePluginInfo> items; + + TQStringList files=TDEGlobal::dirs()->findAllResources("appdata", "*.plugin", false, true); + for (TQStringList::Iterator i=files.begin(); i!=files.end(); ++i) { + items.append(getInformation(*i)); + } + + return items; +} + +KuePluginInfo KuePluginLoader::getInformation(const TQString &filename) { + KuePluginInfo info; + + if (!TQFile::exists(filename)) + return info; + + KSimpleConfig file(filename); + + info.filename = file.readPathEntry("Filename"); + info.type = file.readEntry("Type"); + info.name = file.readEntry("Name"); + info.description = file.readEntry("Description"); + + info.minTeams = TQMAX(file.readUnsignedNumEntry("MinTeams", 1), 1); + info.maxTeams = TQMAX(file.readUnsignedNumEntry("MaxTeams", UINT_MAX), 1); + + return info; +} + diff --git a/kue/pluginloader.h b/kue/pluginloader.h new file mode 100644 index 00000000..de5e1aa0 --- /dev/null +++ b/kue/pluginloader.h @@ -0,0 +1,25 @@ +#ifndef _PLUGINLOADER_H +#define _PLUGINLOADER_H + +#include <tqvaluelist.h> + +class TQString; + +class KuePluginInfo { + public: + TQString name; + TQString description; + TQString filename; + TQString type; + + // Minimum and maximum number of teams + unsigned int minTeams; + unsigned int maxTeams; +}; + +namespace KuePluginLoader { + TQValueList<KuePluginInfo> available(); + KuePluginInfo getInformation(const TQString &filename); +}; + +#endif diff --git a/kue/pocket.h b/kue/pocket.h new file mode 100644 index 00000000..4e05ca6a --- /dev/null +++ b/kue/pocket.h @@ -0,0 +1,10 @@ +#ifndef _POCKET_H +#define _POCKET_H + +// Pockets are just circles with a set radius +class KuePocket : public circle { + public: + KuePocket(double x, double y, double r) : circle(x, y, r) {} +}; + +#endif diff --git a/kue/point.cpp b/kue/point.cpp new file mode 100644 index 00000000..3ee12888 --- /dev/null +++ b/kue/point.cpp @@ -0,0 +1,19 @@ +#include "point.h" +#include <math.h> + +point::point(double x, double y) { + _pos_x = x; + _pos_y = y; +} + +point::~point() { +} + +double point::distance(const point &other_point) const { + // Finds the distance between two points, using: + // d^2 = (x1 - x2)^2 + (y1 - y2)^2 + double delta_x = _pos_x - other_point._pos_x; + double delta_y = _pos_y - other_point._pos_y; + + return sqrt((delta_x * delta_x) + (delta_y * delta_y)); +} diff --git a/kue/point.h b/kue/point.h new file mode 100644 index 00000000..c748e7d3 --- /dev/null +++ b/kue/point.h @@ -0,0 +1,32 @@ +#ifndef _POINT_H +#define _POINT_H + +#include <math.h> + +// Point is just a point on a 2D plane +class point { + public: + point(double x = 0.0, double y = 0.0); + ~point(); + + // Gets our position + double positionX() const { return _pos_x; } + double positionY() const { return _pos_y; } + + // Sets our position + void setPositionX(double new_pos) {_pos_x = new_pos;} + void setPositionY(double new_pos) {_pos_y = new_pos;} + void setPosition(double new_x, double new_y) {_pos_x = new_x; _pos_y = new_y; } + void setPosition(const point &p) {_pos_x = p._pos_x; _pos_y = p._pos_y; } + + // Finds the distance between us and another point + double distance(const point &other_point) const; + // Finds the angle between us and another point + double angle(const point &other_point) const { return atan2(other_point._pos_y - _pos_y, other_point._pos_x - _pos_x); } + + protected: + double _pos_x; + double _pos_y; +}; + +#endif diff --git a/kue/rules.cpp b/kue/rules.cpp new file mode 100644 index 00000000..4a15ac48 --- /dev/null +++ b/kue/rules.cpp @@ -0,0 +1,22 @@ +#include "rules.h" + +// Stub implementations +// These functions exist purely to define an interface to inherit from + +void KueRulesEngine::billiardSunk(unsigned int ball, unsigned int pocket) +{ + Q_UNUSED(ball); + Q_UNUSED(pocket); +} + +void KueRulesEngine::billiardHit(unsigned int ball1, unsigned int ball2) +{ + Q_UNUSED(ball1); + Q_UNUSED(ball2); +} + +void KueRulesEngine::motionStopped() +{ +} + +#include "rules.moc" diff --git a/kue/rules.h b/kue/rules.h new file mode 100644 index 00000000..8b6e66aa --- /dev/null +++ b/kue/rules.h @@ -0,0 +1,34 @@ +#ifndef _RULES_H +#define _RULES_H + +#include <tqobject.h> + +#include "vector.h" +#include "point.h" +#include "rules.h" + +// Temple for rules engine plugins +class KueRulesEngine : public TQObject { + TQ_OBJECT + public: + KueRulesEngine(TQObject *parent = 0, const char *name = 0) : TQObject(parent, name) {} + virtual ~KueRulesEngine() {} + + virtual void start() = 0; + + signals: + // Emitting gameOver notifies the user of the result, stops the physics + // engine, and schedules us for deletion + void gameOver(const TQString &result); + void showMessage(const TQString &text); + + protected slots: + // Called by physics engine when a billiard is sunk + virtual void billiardSunk(unsigned int ball, unsigned int pocket); + // Called by physics engine when a billiard is struck (by the cue ball or another billiard) + virtual void billiardHit(unsigned int ball1, unsigned int ball2); + // Called by the physics engine when all billiards have stopped moving + virtual void motionStopped(); +}; + +#endif diff --git a/kue/sphere.cpp b/kue/sphere.cpp new file mode 100644 index 00000000..321b8bad --- /dev/null +++ b/kue/sphere.cpp @@ -0,0 +1,61 @@ +#include <GL/gl.h> +#include <GL/glu.h> + +#include <math.h> +#include <tdeconfig.h> +#include <tdeglobal.h> +#include "sphere.h" + +const int SPHERE_DISPLAY_LIST = 2; +double sphere_list_radius = 0.0; + +void sphere::draw(double x, double y, double r, double rot_x, double rot_y) +{ + glPushMatrix(); + glTranslated(x, y, r); + + // Rotate the balls the specified the amount + glRotated(rot_x, 0.0, 1.0, 0.0); + glRotated(rot_y, 1.0, 0.0, 0.0); + + // Do we have this sphere cached? + if ((r == sphere_list_radius) && (glIsList(SPHERE_DISPLAY_LIST) == GL_TRUE)) + { + // It was cached, call the list + glCallList(SPHERE_DISPLAY_LIST); + } + else + { + // Figure out the number of sphere divisons we need + TDEGlobal::config()->setGroup("Graphics"); + int sphere_divisions = TDEGlobal::config()->readNumEntry("Sphere Divisions", 8); + + // sphere_divisions < 3 causes OpenGL to draw nothing at all, + // so clamp to value to 3. + if (sphere_divisions < 3) + sphere_divisions = 3; + + // Make the quadratic object + GLUquadricObj *quad = gluNewQuadric(); + + // We need normals for lighting to work + gluQuadricNormals(quad, GLU_SMOOTH); + // We also need texture points + gluQuadricTexture(quad, GL_TRUE); + + // Create the display list + glNewList(SPHERE_DISPLAY_LIST, GL_COMPILE_AND_EXECUTE); + // Draw the sphere + gluSphere(quad, r, sphere_divisions, sphere_divisions); + // End the display list + glEndList(); + + // Update the cached radius + sphere_list_radius = r; + + // Delete quadatric object + gluDeleteQuadric(quad); + } + + glPopMatrix(); +} diff --git a/kue/sphere.h b/kue/sphere.h new file mode 100644 index 00000000..f106142e --- /dev/null +++ b/kue/sphere.h @@ -0,0 +1,11 @@ +#ifndef _SPHERE_H +#define _SPHERE_H + + +// Draws a sphere +class sphere { + public: + static void draw(double x, double y, double radius, double rot_x, double rot_y); +}; + +#endif diff --git a/kue/table.cpp b/kue/table.cpp new file mode 100644 index 00000000..5f2f44ec --- /dev/null +++ b/kue/table.cpp @@ -0,0 +1,119 @@ +#include <GL/gl.h> +#include <GL/glu.h> +#include <tqstring.h> + +#include "table.h" +#include "graphics.h" + + +table::table() : _texture("table") { +} + +table::~table() { +} + + +void table::draw(double play_area_width, double play_area_height) +{ // The location of the head string + double head_string = play_area_width / 4.0; + + // Set the texture to the felt texture + _texture.makeCurrent(); + + glColor3d(0.1, 1.0, 0.1); + + glBegin(GL_QUADS); + + // Draw the table as a green textured square + glNormal3d(0.0, 0.0, 1.0); + + glTexCoord2d(0.0, 0.0); + glVertex2d(0.0, 0.0); + + glTexCoord2d(16.0, 0.0); + glVertex2d(play_area_width, 0.0); + + glTexCoord2d(16.0, 8.0); + glVertex2d(play_area_width, play_area_height); + + glTexCoord2d(0.0, 8.0); + glVertex2d(0.0, play_area_height); + + + // Draw the table as a green textured square + glNormal3d(0.0, 1.0, 0.0); + + glTexCoord2d(0.0, 1.0); + glVertex3d(0.0, 0.0, 0.0); + + glTexCoord2d(0.0, 0.0); + glVertex3d(0.0, 0.0, 0.005); + + glTexCoord2d(16.0, 1.0); + glVertex3d(play_area_width, 0.0, 0.005); + + glTexCoord2d(16.0, 0.0); + glVertex3d(play_area_width, 0.0, 0.0); + + + glNormal3d(0.0, -1.0, 0.0); + + glTexCoord2d(0.0, 0.0); + glVertex3d(0.0, play_area_height, 0.0); + + glTexCoord2d(0.0, 1.0); + glVertex3d(0.0, play_area_height, 0.005); + + glTexCoord2d(16.0, 0.0); + glVertex3d(play_area_width, play_area_height, 0.005); + + glTexCoord2d(16.0, 1.0); + glVertex3d(play_area_width, play_area_height, 0.0); + + glNormal3d(1.0, 0.0, 0.0); + + glTexCoord2d(0.0, 0.0); + glVertex3d(0.0, 0.0, 0.0); + + glTexCoord2d(0.0, 1.0); + glVertex3d(0.0, 0.0, 0.005); + + glTexCoord2d(16.0, 0.0); + glVertex3d(0.0, play_area_height, 0.005); + + glTexCoord2d(16.0, 1.0); + glVertex3d(0.0, play_area_height, 0.0); + + glNormal3d(-1.0, 0.0, 0.0); + + glTexCoord2d(0.0, 0.0); + glVertex3d(play_area_width, 0.0, 0.0); + + glTexCoord2d(0.0, 1.0); + glVertex3d(play_area_width, 0.0, 0.005); + + glTexCoord2d(16.0, 0.0); + glVertex3d(play_area_width, play_area_height, 0.005); + + glTexCoord2d(16.0, 1.0); + glVertex3d(play_area_width, play_area_height, 0.0); + + + glEnd(); + + // Draw the head string line on in a color slightly lighter than the table + glColor3d(0.2, 1.0, 0.2); + + glNormal3d(0.0, 0.0, 1.0); + + // Make the line two pixels thick + glLineWidth(2.0); + + // Actually draw the line + glBegin(GL_LINES); + + glVertex2d(head_string, 0.0); + glVertex2d(head_string, play_area_height); + + glEnd(); +} diff --git a/kue/table.h b/kue/table.h new file mode 100644 index 00000000..4604f72c --- /dev/null +++ b/kue/table.h @@ -0,0 +1,18 @@ +#ifndef _TABLE_H +#define _TABLE_H + +#include "texture.h" + +// Draws a table +class table { + public: + table(); + ~table(); + + void draw(double width, double height); + + private: + KueTexture _texture; +}; + +#endif diff --git a/kue/team.h b/kue/team.h new file mode 100644 index 00000000..f2b39816 --- /dev/null +++ b/kue/team.h @@ -0,0 +1,26 @@ +#ifndef _TEAM_H +#define _TEAM_H + +#include <tqptrlist.h> +#include "player.h" + +class KueTeam { + public: + KueTeam() {} + ~KueTeam() {} + + // Moves currentPlayer forward to the next player, and returns its new value + KuePlayer *nextPlayer() { if (!_players.next()) return _players.first(); else return _players.current();} + // Returns the current player + KuePlayer *currentPlayer() { return _players.current(); } + + // Adds a new player + void addPlayer(KuePlayer *p) { _players.append(p); } + // Removes an existing player + void removePlayer(KuePlayer *p) { _players.remove(p); } + + private: + TQPtrList<KuePlayer> _players; +}; + +#endif diff --git a/kue/texture.cpp b/kue/texture.cpp new file mode 100644 index 00000000..cf753766 --- /dev/null +++ b/kue/texture.cpp @@ -0,0 +1,140 @@ +#include "texture.h" +#include "config.h" + +#include <stdio.h> + +#include <GL/gl.h> +#include <tqimage.h> +#include <tqgl.h> +#include <tdeglobal.h> +#include <tdeconfig.h> +#include <tqstring.h> +#include <kstandarddirs.h> + +KueTexture::KueTexture(const TQString &filename) +{ + _filename = filename; + _texture_id = 0; + + if (filename.isNull()) + { + // filename == TQString::null is an alias for the null texture + _loaded = true; + } + else + { + _loaded = false; + } +} + +KueTexture::KueTexture(unsigned int texture_id) +{ + _filename = TQString::null; + _texture_id = texture_id; + _loaded = true; +} + +KueTexture::KueTexture(const KueTexture &t) +{ + // Is the texture file backed? + if (t._filename.isNull()) + { + // This is easy, copy over the texture id + _texture_id = t._texture_id; + _loaded = true; + } + else + { + // Yes, copy over the filename + _filename = t._filename; + _loaded = false; + } +} + +KueTexture KueTexture::null() { + return KueTexture(0); +} + +KueTexture::~KueTexture() +{ + // We only "own" the texture ID if we were created from a filename + // Also check that we've allocated a valid texture ID. That means + // that the texture is loaded, and it's non-NULL. + // We don't use isNull(), because that forces a file load + if (_loaded && _texture_id && (!_filename.isNull())) + { + // Free a texture ID and its associated texture + glDeleteTextures(1, &_texture_id); + } +} + +bool KueTexture::isNull() +{ + load(); + + return (_texture_id == 0); +} + +void KueTexture::load() +{ + if (_loaded) + { + // The texture is already loaded, nothing to do here + return; + } + + // Get the full pathname for the texture + TQImage raw_image, gl_image; + TQString fullname; + + // Find the real filename + fullname = TDEGlobal::dirs()->findResource("appdata", "textures/" + _filename + ".png"); + + // Try to load the file + if (raw_image.load(fullname)) + { + gl_image = TQGLWidget::convertToGLFormat(raw_image); + + // Ask OpenGL for a new texture ID + glGenTextures(1, &_texture_id); + + // Make it the current texture (blank right now) + glBindTexture(GL_TEXTURE_2D, _texture_id); + + // Should we filter textures? + TDEGlobal::config()->setGroup("Graphics"); + if (TDEGlobal::config()->readBoolEntry("Filter Textures", false)) + { + // Yes, enable smooth scaling + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + } + else + { + // No, enable fast scaling + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + } + + // Load the image data in to the texture + glTexImage2D(GL_TEXTURE_2D, 0, 3, gl_image.width(), gl_image.height(), 0, + GL_RGBA, GL_UNSIGNED_BYTE, gl_image.bits()); + } + else + { + // Unable to load image, use null texture + _texture_id = 0; + } + + _loaded = true; +} + +bool KueTexture::makeCurrent() +{ + load(); + + // Sets the current 2D texture, where 0 means no texture + glBindTexture(GL_TEXTURE_2D, _texture_id); + + return true; +} diff --git a/kue/texture.h b/kue/texture.h new file mode 100644 index 00000000..cac39d87 --- /dev/null +++ b/kue/texture.h @@ -0,0 +1,38 @@ +#ifndef _TEXTURE_H +#define _TEXTURE_H + +#include <tqstring.h> + +class KueTexture { + public: + KueTexture(const TQString &filename); + KueTexture(unsigned int texture_id); + KueTexture(const KueTexture &); + ~KueTexture(); + + bool makeCurrent(); + + // Is this a null texture? + bool isNull(); + // The null texture + static KueTexture null(); + + protected: + // Loads the texture immediately + void load(); + + // The filename of the texture + // Will be a null string for textures created using the texture_id + // version of the constructor + TQString _filename; + + // The texture ID for the texture + // Undefined until a texture is loaded, 0 for the null texture + unsigned int _texture_id; + + // Stores if the texture is currently loaded or not + // This is required to support loading file-backed textures on demand + bool _loaded; +}; + +#endif // _TEXTURE_H diff --git a/kue/textures/1.png b/kue/textures/1.png Binary files differnew file mode 100644 index 00000000..154ac8ce --- /dev/null +++ b/kue/textures/1.png diff --git a/kue/textures/10.png b/kue/textures/10.png Binary files differnew file mode 100644 index 00000000..c16340aa --- /dev/null +++ b/kue/textures/10.png diff --git a/kue/textures/11.png b/kue/textures/11.png Binary files differnew file mode 100644 index 00000000..14feb101 --- /dev/null +++ b/kue/textures/11.png diff --git a/kue/textures/12.png b/kue/textures/12.png Binary files differnew file mode 100644 index 00000000..adc6474c --- /dev/null +++ b/kue/textures/12.png diff --git a/kue/textures/13.png b/kue/textures/13.png Binary files differnew file mode 100644 index 00000000..a9dd9302 --- /dev/null +++ b/kue/textures/13.png diff --git a/kue/textures/14.png b/kue/textures/14.png Binary files differnew file mode 100644 index 00000000..5d2b0691 --- /dev/null +++ b/kue/textures/14.png diff --git a/kue/textures/15.png b/kue/textures/15.png Binary files differnew file mode 100644 index 00000000..50d5db95 --- /dev/null +++ b/kue/textures/15.png diff --git a/kue/textures/2.png b/kue/textures/2.png Binary files differnew file mode 100644 index 00000000..e9daf2a8 --- /dev/null +++ b/kue/textures/2.png diff --git a/kue/textures/3.png b/kue/textures/3.png Binary files differnew file mode 100644 index 00000000..6ab15fd4 --- /dev/null +++ b/kue/textures/3.png diff --git a/kue/textures/4.png b/kue/textures/4.png Binary files differnew file mode 100644 index 00000000..3a7d37b7 --- /dev/null +++ b/kue/textures/4.png diff --git a/kue/textures/5.png b/kue/textures/5.png Binary files differnew file mode 100644 index 00000000..dafba177 --- /dev/null +++ b/kue/textures/5.png diff --git a/kue/textures/6.png b/kue/textures/6.png Binary files differnew file mode 100644 index 00000000..6847b742 --- /dev/null +++ b/kue/textures/6.png diff --git a/kue/textures/7.png b/kue/textures/7.png Binary files differnew file mode 100644 index 00000000..4fb73b04 --- /dev/null +++ b/kue/textures/7.png diff --git a/kue/textures/8.png b/kue/textures/8.png Binary files differnew file mode 100644 index 00000000..1af757fe --- /dev/null +++ b/kue/textures/8.png diff --git a/kue/textures/9.png b/kue/textures/9.png Binary files differnew file mode 100644 index 00000000..fb7f4af3 --- /dev/null +++ b/kue/textures/9.png diff --git a/kue/textures/cue-player1.png b/kue/textures/cue-player1.png Binary files differnew file mode 100644 index 00000000..df6b04c4 --- /dev/null +++ b/kue/textures/cue-player1.png diff --git a/kue/textures/cue-player2.png b/kue/textures/cue-player2.png Binary files differnew file mode 100644 index 00000000..374cf88b --- /dev/null +++ b/kue/textures/cue-player2.png diff --git a/kue/textures/table.png b/kue/textures/table.png Binary files differnew file mode 100644 index 00000000..b878017d --- /dev/null +++ b/kue/textures/table.png diff --git a/kue/utility.cpp b/kue/utility.cpp new file mode 100644 index 00000000..7565228a --- /dev/null +++ b/kue/utility.cpp @@ -0,0 +1,190 @@ +#include <kdebug.h> +#include <krandomsequence.h> +#include <stdlib.h> + +#include "physics.h" +#include "main.h" +#include "utility.h" +#include "global.h" + +const double PLAY_AREA_WIDTH = 0.254; +const double PLAY_AREA_HEIGHT = 0.127; + +void rackTriangle() +{ + int next_ball = 0; + unsigned int rack_order[] = { 1, 2, 3, 4, 8, 5, 6, 7, 9, 10, 11, 12, 13, 14, 15 }; + + double field_width = KueGlobal::physics()->fieldWidth(); + double field_height = KueGlobal::physics()->fieldHeight(); + + // The initial spacing of the billiards + const double initial_spacing = (0.00286 * 2.0); + + // The location of the cue and rack lines + double rack_line = (field_width * 3.0) / 4.0; + + // The location of the mid line + double mid_line = field_height / 2.0; + + KRandomSequence r; + + for (int x = 0;x < 15;x++) + { + // The eight-ball must stay where it is + if (rack_order[x] != 8) + { + int swap_index; + + swap_index = r.getLong(14); + + if (rack_order[swap_index] != 8) { + int temp = rack_order[x]; + + rack_order[x] = rack_order[swap_index]; + rack_order[swap_index] = temp; + } + } + + } + + // These loops build a triangle out of the billiards + for (int row = 0;row < 5;row++) + { + double row_start = mid_line - ((row / 2.0) * initial_spacing); + + for (int pos = 0;pos <= row;pos++) + { + // Calculate its position + double x = rack_line + (row * initial_spacing); + double y = row_start + (pos * initial_spacing); + + // Create the billiard + KueBilliard billiard(x, y, KueUtility::defaultBilliardRadius(), KueUtility::textureForBilliard(rack_order[next_ball])); + + // Actually place it + KueGlobal::physics()->insertBilliard(rack_order[next_ball], billiard); + next_ball++; + } + } +} + +void rackDiamond() +{ + int next_ball = 0; + unsigned int rack_order[] = { 1, 2, 3, 4, 8, 5, 6, 7, 9 }; + + double field_width = KueGlobal::physics()->fieldWidth(); + double field_height = KueGlobal::physics()->fieldHeight(); + + // The initial spacing of the billiards + const double initial_spacing = (0.00286 * 2.0); + // The location of the cue and rack lines + double rack_line = (field_width * 3.0) / 4.0; + // The location of the mid line + double mid_line = field_height / 2.0; + + KRandomSequence r; + + // Randomize the billiard order of billiards [1] -> [7] + for (int x = 1;x < 8;x++) + { + // The the value of another billiard in the same range + int swap_index = r.getLong(6) + 1; + + int temp = rack_order[x]; + rack_order[x] = rack_order[swap_index]; + rack_order[swap_index] = temp; + } + + // These loops build a triangle out of the billiards + for (int row = 0;row < 5;row++) { + // Number of billiards on this row + int row_count = 3 - abs(row - 2); + double row_start = mid_line - ((row_count / 2.0) * initial_spacing); + + for (int pos = 0;pos < row_count;pos++) + { + // Calculate its position + double x = rack_line + (row * initial_spacing); + double y = row_start + (pos * initial_spacing); + + // Create the billiard + KueBilliard billiard(x, y, KueUtility::defaultBilliardRadius(), TQString::number(rack_order[next_ball])); + + // Actually place it + KueGlobal::physics()->insertBilliard(rack_order[next_ball], billiard); + next_ball++; + } + } +} + +void KueUtility::layoutTable() { + KueGlobal::physics()->setFieldWidth(PLAY_AREA_WIDTH); + KueGlobal::physics()->setFieldHeight(PLAY_AREA_HEIGHT); +} + +void KueUtility::layoutPockets() +{ + double field_width = KueGlobal::physics()->fieldWidth(); + double field_height = KueGlobal::physics()->fieldHeight(); + double radius = KueUtility::defaultPocketRadius(); + + // Place the pockets in the four corners + KueGlobal::physics()->insertPocket(0, KuePocket(0.0, 0.0, radius)); + KueGlobal::physics()->insertPocket(1, KuePocket(field_width / 2.0, 0.0, radius)); + KueGlobal::physics()->insertPocket(2, KuePocket(field_width, 0.0, radius)); + + KueGlobal::physics()->insertPocket(3, KuePocket(0.0, field_height, radius)); + KueGlobal::physics()->insertPocket(4, KuePocket(field_width / 2.0, field_height, radius)); + KueGlobal::physics()->insertPocket(5, KuePocket(field_width, field_height, radius)); +} + +void KueUtility::layoutBilliards(rackType rack_type) +{ + if (rack_type == Triangle) + { + rackTriangle(); + } + else if (rack_type == Diamond) + { + rackDiamond(); + } + else if (rack_type == None) + { + // Do nothing + } + else + { + kdWarning() << "Unknown rack type, no racking done" << endl; + } + + // Place the cue ball + KueBilliard cue(KueGlobal::physics()->fieldWidth() / 4.0, KueGlobal::physics()->fieldHeight() / 2.0, KueUtility::defaultBilliardRadius()); + KueGlobal::physics()->insertBilliard(0, cue); +} + +KueTexture KueUtility::textureForBilliard(unsigned int index) +{ + if (index) + { + return KueTexture(TQString::number(index)); + } + else + { + return KueTexture::null(); + } +} + +// Regulation radius of billiards, in meters +double KueUtility::defaultBilliardRadius() +{ + return 0.00286; +} + +// Regulation radius of pockets, in meters +double KueUtility::defaultPocketRadius() +{ + return 0.006; +} + diff --git a/kue/utility.h b/kue/utility.h new file mode 100644 index 00000000..56343acb --- /dev/null +++ b/kue/utility.h @@ -0,0 +1,31 @@ +#ifndef _UTILITY_H +#define _UTILITY_H + +#include "texture.h" + +// Helper functions for rules implementations +namespace KueUtility { + enum rackType {None, Triangle, Diamond}; + + // Regulation table layout + void layoutTable(); + // Regulation pocket layout + void layoutPockets(); + + // Lays out the billiard in either 8-ball or 9-ball style, or just + // place the cue ball (with rack_type = None) + // Billiard 0 becomes the cue ball, and the rest of the billiards + // are indexed according to their face number + void layoutBilliards(rackType rack_type = None); + + // The texture for a given billiard index + KueTexture textureForBilliard(unsigned int index); + + // Regulation radius of billiards, in meters + double defaultBilliardRadius(); + + // Regulation radius of pockets, in meters + double defaultPocketRadius(); +}; + +#endif diff --git a/kue/vector.cpp b/kue/vector.cpp new file mode 100644 index 00000000..40aed057 --- /dev/null +++ b/kue/vector.cpp @@ -0,0 +1,85 @@ +#include "vector.h" + +// Creates a vector with between two points +vector::vector(const point &source, const point &dest) { + _magnitude = source.distance(dest); + _direction = source.angle(dest); +} + +// Creates an empty vector +vector::vector() { + _magnitude = 0.0; + _direction = 0.0; +} + +// Copy another vector object +vector::vector(const vector& v) { + _magnitude = v._magnitude; + _direction = v._direction; +} + +// Set the X component +void vector::setComponentX(double x) { + setComponents(x, componentY()); +} + +// Set the Y component +void vector::setComponentY(double y) { + setComponents(componentX(), y); +} + +// Operations with another vector performs vector math +vector vector::operator+(const vector& v) { + double x = componentX() + v.componentX(); + double y = componentY() + v.componentY(); + + return vector(sqrt((x * x) + (y * y)), atan2(y, x)); +} + +vector vector::operator-(const vector& v) { + double x = componentX() - v.componentX(); + double y = componentY() - v.componentY(); + + return vector(sqrt((x * x) + (y * y)), atan2(y, x)); +} + +vector& vector::operator+=(const vector& v) { + setComponents(componentX() + v.componentX(), componentY() + v.componentY()); + return *this; +} + +vector& vector::operator-=(const vector& v) { + setComponents(componentX() - v.componentX(), componentY() - v.componentY()); + return *this; +} + +double vector::operator*(const vector& v) { + return ((componentX() * v.componentX()) + (componentY() * v.componentY())); +} + +// Operations with a single double value affects the magnitude +vector& vector::operator+= (double m) { + _magnitude += m; + return *this; +} + +vector& vector::operator-= (double m) { + _magnitude -= m; + return *this; +} + +vector& vector::operator*= (double m) { + _magnitude *= m; + return *this; +} + +vector& vector::operator/= (double m) { + _magnitude /= m; + return *this; +} + +// Sets both components at once (the only way to do it efficently) +void vector::setComponents(double x, double y) { + _direction = atan2(y, x); + _magnitude = sqrt((x * x) + (y * y)); +} diff --git a/kue/vector.h b/kue/vector.h new file mode 100644 index 00000000..f1a0947a --- /dev/null +++ b/kue/vector.h @@ -0,0 +1,65 @@ +#ifndef _VECTOR_H +#define _VECTOR_H + +#include <math.h> +#include "point.h" + +// Implements a vector in 2D +class vector { + public: + // Normal constructors + vector(double magnitude, double direction) { _magnitude = magnitude; _direction = direction; } + vector(const point& source, const point& dest); + vector(); + + // Copy constructor + vector(const vector&); + + // Accessors, sorta + double componentX() const { return (_magnitude * cos(_direction)); }; + double componentY() const { return (_magnitude * sin(_direction)); }; + + // Sets individual components + // Wrappers around setComponents(double, double) - below + void setComponentX(double x); + void setComponentY(double y); + + // Sets both components at once + void setComponents(double x, double y); + + // Accessors + double magnitude() const { return _magnitude; } + double direction() const { return _direction; } + void setMagnitude(double m) { _magnitude = m; } + void setDirection(double d) { _direction = d; } + + // Vector math + vector operator+(const vector&); + vector operator-(const vector&); + + vector& operator+=(const vector&); + vector& operator-=(const vector&); + + // Dot product + double operator*(const vector&); + + // Magnitude math + vector operator+(double m) { return vector(_magnitude + m, _direction); } + vector operator-(double m) { return vector(_magnitude - m, _direction); } + vector operator*(double m) { return vector(_magnitude * m, _direction); } + vector operator/(double m) { return vector(_magnitude / m, _direction); } + + vector& operator+=(double m); + vector& operator-=(double m); + vector& operator*=(double m); + vector& operator/=(double m); + + // Return the vector's equalivent on the unit circle + vector unit() const { return vector(1.0, _direction); } + + protected: + double _magnitude; + double _direction; +}; + +#endif |