From 04b5a62b8d9f5ff8240f25361046f2a5d58e8262 Mon Sep 17 00:00:00 2001 From: Mavridis Philippe Date: Wed, 7 Aug 2024 19:13:02 +0300 Subject: [PATCH] Add Kue billiards game Signed-off-by: Mavridis Philippe --- CMakeLists.txt | 2 + ConfigureChecks.cmake | 14 +- kue/CMakeLists.txt | 64 +++++ kue/ConfigureChecks.cmake | 22 ++ kue/billiard.cpp | 87 +++++++ kue/billiard.h | 33 +++ kue/circle.h | 27 +++ kue/config.h.cmake | 8 + kue/cue.cpp | 74 ++++++ kue/cue.h | 13 ++ kue/disc.cpp | 46 ++++ kue/disc.h | 15 ++ kue/global.cpp | 39 ++++ kue/global.h | 41 ++++ kue/graphics.cpp | 176 ++++++++++++++ kue/graphics.h | 37 +++ kue/hi16-app-kue.png | Bin 0 -> 328 bytes kue/hi32-app-kue.png | Bin 0 -> 980 bytes kue/hi48-app-kue.png | Bin 0 -> 1053 bytes kue/hi64-app-kue.png | Bin 0 -> 1507 bytes kue/interface.cpp | 319 +++++++++++++++++++++++++ kue/interface.h | 86 +++++++ kue/kue.desktop | 52 +++++ kue/localplayer.cpp | 82 +++++++ kue/localplayer.h | 40 ++++ kue/main.cpp | 218 +++++++++++++++++ kue/main.h | 34 +++ kue/modules/8ball/8ball.cpp | 337 +++++++++++++++++++++++++++ kue/modules/8ball/8ball.h | 88 +++++++ kue/modules/8ball/8ball.plugin | 6 + kue/modules/8ball/CMakeLists.txt | 42 ++++ kue/modules/9ball/9ball.cpp | 217 +++++++++++++++++ kue/modules/9ball/9ball.h | 71 ++++++ kue/modules/9ball/9ball.plugin | 6 + kue/modules/9ball/CMakeLists.txt | 42 ++++ kue/modules/CMakeLists.txt | 8 + kue/modules/freeplay/CMakeLists.txt | 42 ++++ kue/modules/freeplay/freeplay.cpp | 93 ++++++++ kue/modules/freeplay/freeplay.h | 46 ++++ kue/modules/freeplay/freeplay.h.save | 85 +++++++ kue/modules/freeplay/freeplay.plugin | 5 + kue/newgame.cpp | 242 +++++++++++++++++++ kue/newgame.h | 64 +++++ kue/physics.cpp | 189 +++++++++++++++ kue/physics.h | 76 ++++++ kue/player.h | 32 +++ kue/pluginloader.cpp | 40 ++++ kue/pluginloader.h | 25 ++ kue/pocket.h | 10 + kue/point.cpp | 19 ++ kue/point.h | 32 +++ kue/rules.cpp | 22 ++ kue/rules.h | 34 +++ kue/sphere.cpp | 61 +++++ kue/sphere.h | 11 + kue/table.cpp | 119 ++++++++++ kue/table.h | 18 ++ kue/team.h | 26 +++ kue/texture.cpp | 140 +++++++++++ kue/texture.h | 38 +++ kue/textures/1.png | Bin 0 -> 958 bytes kue/textures/10.png | Bin 0 -> 1193 bytes kue/textures/11.png | Bin 0 -> 1022 bytes kue/textures/12.png | Bin 0 -> 1136 bytes kue/textures/13.png | Bin 0 -> 1249 bytes kue/textures/14.png | Bin 0 -> 1296 bytes kue/textures/15.png | Bin 0 -> 1366 bytes kue/textures/2.png | Bin 0 -> 1106 bytes kue/textures/3.png | Bin 0 -> 1156 bytes kue/textures/4.png | Bin 0 -> 1044 bytes kue/textures/5.png | Bin 0 -> 1238 bytes kue/textures/6.png | Bin 0 -> 1365 bytes kue/textures/7.png | Bin 0 -> 1200 bytes kue/textures/8.png | Bin 0 -> 527 bytes kue/textures/9.png | Bin 0 -> 1170 bytes kue/textures/cue-player1.png | Bin 0 -> 26201 bytes kue/textures/cue-player2.png | Bin 0 -> 25940 bytes kue/textures/table.png | Bin 0 -> 825 bytes kue/utility.cpp | 190 +++++++++++++++ kue/utility.h | 31 +++ kue/vector.cpp | 85 +++++++ kue/vector.h | 65 ++++++ 82 files changed, 4185 insertions(+), 1 deletion(-) create mode 100644 kue/CMakeLists.txt create mode 100644 kue/ConfigureChecks.cmake create mode 100644 kue/billiard.cpp create mode 100644 kue/billiard.h create mode 100644 kue/circle.h create mode 100644 kue/config.h.cmake create mode 100644 kue/cue.cpp create mode 100644 kue/cue.h create mode 100644 kue/disc.cpp create mode 100644 kue/disc.h create mode 100644 kue/global.cpp create mode 100644 kue/global.h create mode 100644 kue/graphics.cpp create mode 100644 kue/graphics.h create mode 100644 kue/hi16-app-kue.png create mode 100644 kue/hi32-app-kue.png create mode 100644 kue/hi48-app-kue.png create mode 100644 kue/hi64-app-kue.png create mode 100644 kue/interface.cpp create mode 100644 kue/interface.h create mode 100644 kue/kue.desktop create mode 100644 kue/localplayer.cpp create mode 100644 kue/localplayer.h create mode 100644 kue/main.cpp create mode 100644 kue/main.h create mode 100644 kue/modules/8ball/8ball.cpp create mode 100644 kue/modules/8ball/8ball.h create mode 100644 kue/modules/8ball/8ball.plugin create mode 100644 kue/modules/8ball/CMakeLists.txt create mode 100644 kue/modules/9ball/9ball.cpp create mode 100644 kue/modules/9ball/9ball.h create mode 100644 kue/modules/9ball/9ball.plugin create mode 100644 kue/modules/9ball/CMakeLists.txt create mode 100644 kue/modules/CMakeLists.txt create mode 100644 kue/modules/freeplay/CMakeLists.txt create mode 100644 kue/modules/freeplay/freeplay.cpp create mode 100644 kue/modules/freeplay/freeplay.h create mode 100644 kue/modules/freeplay/freeplay.h.save create mode 100644 kue/modules/freeplay/freeplay.plugin create mode 100644 kue/newgame.cpp create mode 100644 kue/newgame.h create mode 100644 kue/physics.cpp create mode 100644 kue/physics.h create mode 100644 kue/player.h create mode 100644 kue/pluginloader.cpp create mode 100644 kue/pluginloader.h create mode 100644 kue/pocket.h create mode 100644 kue/point.cpp create mode 100644 kue/point.h create mode 100644 kue/rules.cpp create mode 100644 kue/rules.h create mode 100644 kue/sphere.cpp create mode 100644 kue/sphere.h create mode 100644 kue/table.cpp create mode 100644 kue/table.h create mode 100644 kue/team.h create mode 100644 kue/texture.cpp create mode 100644 kue/texture.h create mode 100644 kue/textures/1.png create mode 100644 kue/textures/10.png create mode 100644 kue/textures/11.png create mode 100644 kue/textures/12.png create mode 100644 kue/textures/13.png create mode 100644 kue/textures/14.png create mode 100644 kue/textures/15.png create mode 100644 kue/textures/2.png create mode 100644 kue/textures/3.png create mode 100644 kue/textures/4.png create mode 100644 kue/textures/5.png create mode 100644 kue/textures/6.png create mode 100644 kue/textures/7.png create mode 100644 kue/textures/8.png create mode 100644 kue/textures/9.png create mode 100644 kue/textures/cue-player1.png create mode 100644 kue/textures/cue-player2.png create mode 100644 kue/textures/table.png create mode 100644 kue/utility.cpp create mode 100644 kue/utility.h create mode 100644 kue/vector.cpp create mode 100644 kue/vector.h 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 # +# # +# 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 +#include +#include + +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 +#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 +#include + +#include +#include + +#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 +#include + +#include + +#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 + +#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* KueGlobal::_teams = nullptr; + +KueRulesEngine* KueGlobal::rules() +{ + return _rules; +} + +KuePhysics* KueGlobal::physics() +{ + return _physics; +} + +GLUserInterface *KueGlobal::glWidget() +{ + return _glWidget; +} + +MainWindow *KueGlobal::mainWindow() +{ + return _mainWindow; +} + +TQPtrVector *KueGlobal::teams() +{ + if (!_teams) + { + _teams = new TQPtrVector; + } + 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 +#include +#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 *teams(); + + private: + static KueRulesEngine *_rules; + static KuePhysics *_physics; + + static GLUserInterface *_glWidget; + static MainWindow *_mainWindow; + + static TQPtrVector *_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 +#include +#include +#include +#include + +#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& pockets = KueGlobal::physics()->pockets(); + const TQPtrVector& 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 new file mode 100644 index 0000000000000000000000000000000000000000..f8f1e1c7b68ca3807174d5b16abc3bdab490c0e9 GIT binary patch literal 328 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`Y)RhkE)4%caKYZ?lYt_f1s;*b z3=G^tAk28_ZrvZCAbW|YuPgf{Mp+gEewTwrCxAj%JzX3_D(0-6yu0^^0gp@idVbMG zP9ht4uPWZy82!QQ;F;DH9BuEFq+iSD74AM`F~fX;%gcqYAGy{4QN7c+{Z96Q-5>h? z?rb{i^KM;7fXj)yscFX<`gkX0ti4#R{(Mj7)-Z;s>^)Wf8)h;FWV;F-HVE)?6gs_$ zAt7z~gtdj`4ZKV`#!r13&Y;>Ag?ELJT(Ko64!RzU|lo)M__G9%Uzbs zCZ3amye2Ivl=)g?SAOf&&bg25v#&TFIa5%s)YNg{?)q!(Us{*{xqAMK--VO^x!;Su Vw?1?GnjO%C44$rjF6*2UngAN`irfGI literal 0 HcmV?d00001 diff --git a/kue/hi32-app-kue.png b/kue/hi32-app-kue.png new file mode 100644 index 0000000000000000000000000000000000000000..d3638685668375030fba04d90c833226a701681b GIT binary patch literal 980 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 JPDHLkV1hE$!3O{U literal 0 HcmV?d00001 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 +#include +#include +#include +#include +#include +#include +#include "physics.h" +#include +#include + +#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 +#include +#include +#include + +#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 +#include +#include + +#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 +#include + +#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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 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, ""); + 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 + +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 +#include +#include +#include + +#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 +#include + +#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 +#include +#include +#include + +#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 +#include + +#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 +#include +#include +#include +#include + +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 +#include + +#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 +#include + +#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 + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#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::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::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 newGameDialog::selectedTeams() +{ + TQValueList 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 +#include "pluginloader.h" +#include + +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 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 _players; + + KComboBox *_game_type; + TQValueList _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 + +#include +#include +#include +#include +#include +#include + +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 + +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 & billiards() { return _billiards; } + TQPtrVector & 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 _billiards; + TQPtrVector _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 +#include + +#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 +#include +#include +#include +#include +#include +#include + +#include "pluginloader.h" + +TQValueList KuePluginLoader::available() { + TQValueList 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 + +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 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 + +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 + +// 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 + +#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 +#include + +#include +#include +#include +#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 +#include +#include + +#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 +#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 _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 + +#include +#include +#include +#include +#include +#include +#include + +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 + +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 new file mode 100644 index 0000000000000000000000000000000000000000..154ac8ce2125c3cd48f40c0e386e7ae1d2654b98 GIT binary patch literal 958 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K58aSAMWcGE1SRlog|H(?D8gCb z5n0T@z%2yAjF;}#{Q(NHmw5WRvR`826fuxBZjE}uz`*S7>EaktG3V_aLqDZLiMEf| z&xXYvOmN(^bSqER{;92;BJ&)%k5%)u&N=8L#I^cuFJqRsu+$f>ZJX~Vu^kirn-=k{ zwr-)M#J)eKHYW`X-aOgK5PjclFGIp)4u)$3i}vMLF{;hFw6a7=)}#8;x-VLLJ^C;0 zEm1#fw$~}xx^mx3)j!YsmV{XSzMZG6Ia%&igy21e2CrCe@z-zcC+Mw?;to@6=rUO< z{j%V%y5G{`zK$$shR_VJ{Y&Ie^V`_lFVFmyZtOWJZvFNx*C&6O%$7Cj`JF@gllQnR zzg+qI{QD9sRck-9zv?^gSnx`{+x6x|x`hm1`{90G%PH%aueTVn2)thLCieZ+tD)O( z$EIItEfLwIwx^jPY3b>5U$x0)yXU4xo-jG0)ZlRT(9eH$`+;D-%E=0we^(9|uaw+m zG)1w&VR`FY6W`~bE8qBl)TzI>kFj7CF%ph;6fT^ecIF5Z zBTLntNi!o98XUey8wqo>2)xMiPD^TJU^=@b@WlbaaMqAshNRj@()n9`4|4%M|H(?D8gCb z5n0T@z%2yAjF;}#{Q(NHmw5WRvR`826fsgizW?D31_l;0PZ!6KiaBrZ?#;UuAk+5I z{%?elLg_Kd4Odt-jVEeUFVizv(BpA8a0>T}d8|jKXeV?|_qlu7EA3pzk)~Q^$xXYM zJD)nt;xBq&Qa6A8xd#m!{Z#CkZX2#liA-x)#lKIT;fD*u1h;`eVLv~${ukko6*XpR z(6eK5yrq6$c!EINx4*l$-YzRh*gEU&+naCKE?pTLfBWjx*tlCY@2a~F86ErPwtA{- z;I}mzr=oQKZ=NdiFFoLvp`88x*W$g$|2 zCR8V8?613deZBO)8nexdmHW8LS{UwhCuf`8-_rW>3Ln4hSvLPiCM%L3e~kWKXPj-8 zYg1+@ZPQ-PFg;55_utz$ukEYdY+pYom$(0sRfD&>`s)5AnN?L)yJF_~sA-?~TYg!i zORT$f=G*u0%dO_7J$(E4Nee^S!T%F0EZrpRio%v(e_gxp`;p+CaqGil+KruV%LCm# z$xy=P#{S7FkvETapXX~o{O4oY?z?&0Kg+itPTY4Z;lcm!TE9QoH&5#M)Ajz>y8Utv zd*jZ3|Myh6lKH1{14{J_xD|o#@cW+J$3r{!Tf!C>g<9(hB-HH?~dkZdhq5= zj@^9wUElM!0W~dmoV#=H-rMoB&!&BTD}MOh_Rh7BWDm?Vsg=L&BFJb_T>JNuDL;4p zi^=l5_PY+9-rjjupuspiTt9Ymn8miU{2lswde_1$ewuBRWGZ;}>6557KU?!tClS}h z7c)$xo~GA*S{}FCH|N;@qaBxezV4R)|8=>5=cFgs4|TozS~W=}hV}N%9~s{+mp4A0 zxa`{d^){2jR$p}k>D_fV?_b5%sJCUir+TS+PMSSi+L*g(!TtC1=g)sWzrOCzhNDUC zo5P>(KV;N<{T}-t{X6n|W*_{^@UGDPhV>!G4b{hwo6XMs_vz-DzW%$wNLOO`{;b&A zYSYV-$=Cgs-x1uO{m;IAYUzi)4(}6x%y*p0exK+2e15*B^2YaUfAa+ymJ|x`WU?@1 o&Ns4Sbd-2M5IKincKs$^b$q8+QM1coV4=d`>FVdQ&MBb@01>niP5=M^ literal 0 HcmV?d00001 diff --git a/kue/textures/11.png b/kue/textures/11.png new file mode 100644 index 0000000000000000000000000000000000000000..14feb101fb1b2ab07d08273c566b14f7cdef9456 GIT binary patch literal 1022 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K58aSAMWcGE1SRlog|H(?D8gCb z5n0T@z%2yAjF;}#{Q(NHmw5WRvR`826fqUMsZg%Uz`)$?>EaktG3V_a`)nmgnf8zK z-L-aKN!mQYXls{e{#2!e$EyY6PniAIW6@o6X?DZ4GA^;e`%Q0S9=6`L{y#fX{oVJU z1_g809IiN1yh}U#pk;c)s`!QC3@t7U6Wj&@g%y1NzVJE&OK`!$a0Zqm?g}2ex%V>g z2BzrifBTcu-yfzGxiRDQ*0o_~Gj9}RG&i1ozHs`AQ-(&eUw6I!dM9x2yw%qauMf*_ ziFcgmK1t^OuS`#&y0b~jf4DwbE;`Zc_WbR?-is$(1Alxy&9javRiW$6+p}_VdNsdt z(t6p_S+6@XNJ{js3Ho`B=kdnS)$_udopI<sWtEzAdYIttZy~<=j2V10R!bz5TFW`#Q*>#{2etynFB3?b*Ml zB+n^ccb)tHt|Hq*>lN&it?uUBs-6G!vNTvG&x#i$19YJO$|?V%cgDOc+YO9U`^Pit zpSP5=Jn-MRhVjanPF`uM)%wohl+7b|)1 zVg9i>>h7^Q9*PY0YLjoz5Y}XQaK4~Ix>%`|;g2LwUb5d3fd>1}JHP(6Q-AY)r?x_N z4C^PYzPDdZ|2nJFqH{Yc$0 z-`0Y2A6K39m*q+f9*Tfm&k;OXk; Jvd$@?2>>bdvS|PS literal 0 HcmV?d00001 diff --git a/kue/textures/12.png b/kue/textures/12.png new file mode 100644 index 0000000000000000000000000000000000000000..adc6474c718133a5c793ccfc4383f0ecc1d54903 GIT binary patch literal 1136 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K58aSAMWcGE1SRlog?NMQuI!hXI7Q6NPA0SHF)%Ry^>lFzshIQj&R)OU0Ss*q z*NX-pU)MW5M0Tbp%U$cz6;F6#oV7!D3K$J*t?A;g>^qT z=Lm2AdCz^C#=%a%1M6#L)N8{+r!s_}pDo95L4~QobRf`H-n}_h91g|vLRZa_W4<7< z%wfZ}i(eRQwk-XADLON_N_FpqI`8mHGfPxzC+p3UTd2LvVPdX#?aRZpt7W}rekm4J zc9La0vPHi(Chaft%iuK|6BL;`7VFkd+%|3hTUvE<*)jW;< zKYvlSJ2lepTDJM$CD&>d8wyT8&q@=Xe`~Hk`-Qhz^&PPw;R^Zm*utDOA&XIpWDBzHe&XlUqlU;CA%@jLhg&UIhBuHv~# zXZM~xF&2We!bM!yUze_*oqF?6veL9;!ZGuoRvldT_nz{BE6?hDY~WZ`)J zf>FNaXLf%6`hD;HFK?-nvzcij+bW~*>|@-5f?Jo?-@N^5_s>;Qe^<31et78gf#k=g zUuS)HJ$_U~V-EMr502XRYNsYVny}X1YMFy$>gLT0eto^Wi+!)tth@WU=5=TWWSp+! z`(ajNSE0(Gz+@-*?f(NAg{Ih-e>ltSr5zw~nPyXsTwkHWhc9_^T5 zb?M-w?^YA6a{g+&G8ooQDwF$m>&3z?ha>ZuEjZ&i|K(q(7iByk6|4}U%n)%qfsG|y ihB2X+VbGzs{1bMR`bAFMU;r!|7(8A5T-G@yGywp-Y~I)a literal 0 HcmV?d00001 diff --git a/kue/textures/13.png b/kue/textures/13.png new file mode 100644 index 0000000000000000000000000000000000000000..a9dd93026a900e7d92c22c873a8035b15464d210 GIT binary patch literal 1249 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K58aSAMWcGE1SRlog|H(?D8gCb z5n0T@z%2yAjF;}#{Q(NHmw5WRvR`826tUEb`sK2Yfq^B@)5S5QV$Rz;hB>z)WR8Ed zU(Of$+)qS*j**Mk?qdR9^WKK6RDY;gy={(CLZ8*0Fin@`DZ6c%rwOeLejbuOXRGZ? zwi2F$zSGZznp^#NBEX?|z&PYrtvxr(o8{&|x<7IUNbs<+t@xFBRhS{HlR=?(AkbQa zAEj4=8N%9)8I}k#EU}IF%3>mNI;=MH{JiL&D{bqN_~mbIU;pjtE_RsTcf|7zwbR$c%7ibyl;JPuD;{BJzw_r{Qb|(r0+B_ zdG0{;k>NVLH3#3Fo;pSl+PT zc9YYd zdj2z8h?=;`c}!Yz@yGvzWxH)Z<;WaQl=$|$*7&o{`R{+D)|Mrkh`ALX)0$v?bkp2F z=AR-2SP~_ceP?ezxMaD})~L5`>bLDTD^1b9$FwuJ*78sIB$e%Qk5;B_o@pDpGDK^t zSCu`_VIc5KK4i&M@cicIFJ`u1Y|qaw`7%55*S6zlY|eKt%J>nw|MJV8{?0iCybt_- zW>(#PS@O=}OYlp<`6`~1RFwTI=b7876&;d1F!#8B+y75C>+j~>-?2hASZiv{eg38e zQEStxufF~Ywc_F8FV{Zr30qxSSy}o0;{20hO=s4`t)K3-^rPWKVW1OkU;lFL`(Nqx zk1q4XvzSZuDsUV-?&8OBV6OiBXSemc7HM>CsuyoblsNveDf!Vnp!mJxuHU}Z+V7sf zS2?P{$H$f7T}?&(dEUnsd`$`*O->6Zto_BW&mODKa<|&&OnYiT`J(OHsx4}bjqiVd zY~dIGY%{~A?G7)~mDU}1nPBl#}EiKEjwsZe)RLDEQxK8rjaW@9R&X#}+QHUf r!ql+k(24v~h9C=O2Pwuuhi;XNyQ@SnSWEr_7Df!7u6{1-oD!M|H(?D8gCb z5n0T@z%2yAjF;}#{Q(NHmw5WRvR`826lD_gc)C}Ofq`YNr;B4q#hkZy3^Sy|8QMP< z-#*ykWVE7N_hQk(X2o+7-bGi|&EhHDA-$zq!-6G#fw!jQuB`=&ZUi_PS5I(YOfP8j zDHD3RAUNcN!lHwd<{W*Xn6PPO^DE~g*F4Mad*=SXpFip7-N(G&U;gncpY`%%-_IhW%}yN* zNn1+mvmf8?{kZ*p-KN_2*Q-CCWq&T{p!3GE`uXv>Pmex5{Z3*JXP3R`&;E=gjsrH; z=jy(F-rRcS(ABd1vRSR~xlIftSrX#@ZvQ1}_nVzHzv)c5WbkLl7XP*Dm)W-dpT72Y zboNg{?|T!2;+IUiKB@ZPyos8MOz8}hfiBu}qb@OPi?ww54m}a`ee4D&i}xP1x1QVg z{O@1CoiTFTu0Ke-nNwC=Y%J5u@aUN20STbxyFZtGkiA~2J=N=f>H6!t3)cHBpM3i1 zt+!?W{f`&21@8>pb1=&y><(ArnXEk1n!7Jc*!ur*tb5(&UjJoA2Q#C@Zhf6U1R4yzzfDSw+!~cT$NB!IH9;${+`shw?+Zggc_xE2J4^X}e#s69HI8j$Q~KSwQ1c&3lql|l>VAgpf2p&d|NKj|w10P@kEkM(!Lo~4F|XKmaIgQr<@VdM-ES@Y>>J!xC+5`c z;K*i3=8Kd6qkim&!a0UxZ}#2N&7IT_OOY~Xb-StKCE_XN%sO*2k&n8eeproGSWZTA2B`Sqy2_~Pwr-i2SUkF5Xl(S vEXC+>>&%AvuNg!N*ajvu;x7TV>}Qy#=5%Rg+Gl@Y*~Q@L>gTe~DWM4f{Y5gl literal 0 HcmV?d00001 diff --git a/kue/textures/15.png b/kue/textures/15.png new file mode 100644 index 0000000000000000000000000000000000000000..50d5db95a6e82be5187b05b65b8145fa1d8cf1e6 GIT binary patch literal 1366 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K58aSAMWcGE1SRlog|H(?D8gCb z5n0T@z%2yAjF;}#{Q(NHmw5WRvR`826qS*FB>K;Vfq~_*r;B4q#hkZyY%`?O8QKe1 z>axUeD4NblkX^{tRPkZP50&G)t&cb#%DfQfF>Ml$&|NRZ0IpV(q65oRAKYwak$TeP zbmAGyT!WMq?IJEIN^>>NCHNbr1hp$)?|#G|S?RC!*M9%YnrnOa%&4xBzodEZ_1C0^ zRp*b{GA7L7V7N3O=#lv`Tjqow3nm9CMu%JCkLNYCtW5cI>e8h%hYlURdGqt-%js%r z_he<>l|CX$Mw(87ta)B zVi8yvt*w3k&Q9YWud`RL&MtrNW-edT!=Pk1)#usu>%qmv%kOTunW6K%XpUen5918; zsXqUH{P@@!lC|UP|2(t$sF)><7a!%#B zFV0R)eYeE>o^b6{k2!Pa)C8`UoGH|IP;-Lk^ykm(>%N`2l3^dS`&jM56(8*yJg;-N z?lYbDT=d?0ovB_Add}T=k&JRdK0D&DR+kCWs^?ckK6kydgj@*X4$>hU$efp zJ-=D!^@T#2#vH(S_{BS#)dZ!&r!kMEtBkLE&t)IW+`|fqG^>1vuTb^6+VnzqY za))Dc=Fj8%pAi|hGNef6`33QEE7|&krFVVQg!_*_{#cz7!~eNeR^ZvFDhFF#yVmJk+HWJm_a)er7%xz9iUd>k*s zGQn@T_tKz^5j=HM&!*YS2@5`yIxu5zUik-qhb_0?M(8}Nj8mG(5ytjjV#?{%|1UOP ze>-cQn%x1x12eAQzPg_f?L8mOD?~Bkg*|jt(!scpKj#_!`*AA zMX%)f@ra#Yj^%W#lj4ove`hcMw<&t%2g{BXg)9b{izD7t|1tlwEpNtSi@AQw12kCL znNDw#Iezl_=Y#wI_g+}5=+eS}ShDQa^M?LIpARl}WO(-X)voyY|1uwJnCtfVH^03? zOGg23!o9yS?|e5~etDKZ>+J8Ha}NG|AiG#{){&KcbXO9RxP^u95k$2(04pW1PUXx-kgMzNyzjb^LyzUGh%7YL6W}k8-e2by3 Smo2aaWbkzLb6Mw<&;$T7`BY~B literal 0 HcmV?d00001 diff --git a/kue/textures/2.png b/kue/textures/2.png new file mode 100644 index 0000000000000000000000000000000000000000..e9daf2a8f9de3905ce2a2790e332edf6c372ffb7 GIT binary patch literal 1106 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K58aSAMWcGE1SRlog|H(?D8gCb z5n0T@z%2yAjF;}#{Q(NHmw5WRvR`826jk65jX0Ldz`%Up)5S5QV$Rz;`~8#>McO_- zPSehJJf75}y7Z<`hpp({7&gu#MUy+MEyqsXOIon)u!nIK=Z$k>YSM?yKOH|H^Sye0 zyx(T6XNTvuOSUx{Z$8Bkx_^&2LxmE9hw^};-*5LaaAs!RU3d3x~{UuNssq8Wu%+j+WT_7bnl<*Ys~*? zcNAROGV3hg?O*n1(?VzcS|6W&?u^#^A33f1UB}_S49{DoB>#&ZF@7j{{$Rp}eN)vvgG7#|OGx`OY|^>C<88^S z`hJ#U^{f+pW?h?e^`^xadA|1OGQNjrEtN}NJq+qt{IV>2Uqxl*%;!J%v;PKyulcp_ z%OCCdqurDIt@3F3>5G9C97h5Jm=DW6$cS3YTvuTQZvFqas}|_dlR1{$B6)v3C$;GRZL{Dm z`2`FQmCNt1=O1wmlqtEef8G7>`{N7wr#s90&bswF`|S1g>(-gYeL1^n&9~4E1@48l zXG<@tKaWo|5td`ko%81YNBe^x`w#zEuP~2uJsYq8?>F)c;mu}7KnG-9yU)Mj|JDnM z4144k%zT&}*th2T=G)P~KOaBy=Iz_H-|H(?D8gCb z5n0T@z%2yAjF;}#{Q(NHmw5WRvR`826je4&lfAi}fq_NP)5S5QV$Rz;_WnwaGVLE9 z7ER4}+*TmuUCps^^M*|ZlJV^~#ho7SK5!u-^2Wp+(V33zonqbjjb-a^$Q<9e_>bY^ z89(jj|LN*(PucOfuv~IOu(ZSV^*6W~l9U)clm`v^EnCf)w8m6i{MnZ)bLPxA>|9)s zAjbXqU*57{R;dk#G ziQjzeNW9~tK`*&o;EuFm0>+M>+B!e9>r1XEBf3PZ&k1Qe0ZNm z*ID+)j~4%wy-IFy@*RjOuH5-;>(82ZbE4-lSw5e|eLrsf^EuIbpMHAuWBzIH$_VkCqCy0xc>EP>7V0M9|1kswE1k``}l18f4~3k`(HMP zwRI-Y)`GOnw)1`+o1t^sW_^oU+U6hq+D9KsDKs2DoSPRq?dh%XImLJLzH5K)jNK?K z z;ahdx9`S?6c8ed=i`dY)zscnI_q+?=&V|pe*Y7EBW&?$5Sl?LN literal 0 HcmV?d00001 diff --git a/kue/textures/4.png b/kue/textures/4.png new file mode 100644 index 0000000000000000000000000000000000000000..3a7d37b7e42201bf63c994fc295c8b647cbcd0b6 GIT binary patch literal 1044 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K58aSAMWcGE1SRlog?NMQuI!hXI7Kyiw|wYa%)r3B(9^{+q+-t7JA3_ZCrGqC zR1a16W8CDDWqzz{#({H38dCUe*(5yrm?a)aSltL>TlP+b(KvcyM1g8@)v%g>^*n|RUMvjRg9^!q+cMhoX2#!|YPPgA)3<6` zo#*#UQ%f{$*HyLoWldcbdj6oBca}H%}OL7UA2StUf$}D zmrUN)TVFDfjeA^n)&5X@?PG(CC7(H??dJL||9|~|M)akhOwU^wSp=4uCa&ME(RKOq zWpmzAjjduv>O9H~25$?wPj5Q2DJO7oVj7FUV#9Y8^^wzV-@bjBw?e`6*&Javg$9Y= zkHyj=zkT`grO4iMQfp7=O#z3VyZiM{ZhG_V*|Vp0d*jZ3uK8zZXw1~H(el^J&6_v> zJ8V}uFFz;ejHTK$M+U=d?l$2fuDk#4GM@eX=YjG#v3)6 z1iSOLhCGk-9eRG38!fYC>L~o`cWjG0gQ4F2lQX{UP_sC;_)4v?H>2j6_C^-v&(sdeWxJu#Cjtn>eA5YQVkKHXStx+&*Y})&9)`LQz@e;Eg4$3XHeVMoH`n``Y`P~^l$}N5N z)^mS#w#KbA8CGvY)(EFSZ|@jpOQ*{m z|G3|~ZNj>lB6o{aS(_(cRp03u=HJLt;`~C|V24EZiD#P82a1|lYnhKWhvd9?ax-tq z=T|H|ycZu-zgn5?ye(x%!s+rulay@39(@1n^XTE14?N$ezMJSb>-V1%43YL{su&OG z2r?`gROs%EuS{R2IIXtZ_M2a4Px|)vH_k?Xd%5`U_w`@z-TJt&^xvk<_ts3;NO|{p zaiqTE@k5i2mRA2LIg!Y|`&!nG-R9Rfsg<%6#9rI>_T(#xHXf!AnYp`v+^R1u>d&aVcx~JAZ7MNt4EJ<&dbhvcduVcK zbi}N4Av<~)_H@2Jx^ZLtzx8u?CqM2su=jfS;r#EjufA4^xE}rJwRDnv)sHJ)it{;q zWf^}2*4h+UeCOM+bEoCKIraNyr37jyG1$Memw2l2JZAUZcV)ZN#8@0fT(?HOU1iCc zaEs$W|A%`A9;$erlV^7nU~zocpuk}@*KcQxT&M2R*49^Z zq(QsX=9qd7AKzBB#Y_`m*RP3gJP-FI{3-^^JZ zqV;_CgRa&HNKWa{pKi+;+|Ij3rlVquv zlG7dbO>w&YbAt5g*e51Zyt*L&?C(GZ>F8Wq|$hskYzxvVpM^rp7WtePQ&$?;i zXX!?^Cxt~R+RX1Hj)n*{q~FYMI{wG+K~r`9`RO}`|Mc?-u}qH@Sc;=L8_X;LwPWv-%K}7 WNW?o-<^}^xAO=rYKbLh*2~7YjLlW-* literal 0 HcmV?d00001 diff --git a/kue/textures/6.png b/kue/textures/6.png new file mode 100644 index 0000000000000000000000000000000000000000..6847b742a34c8028f3f833c0f513e6e37b4141af GIT binary patch literal 1365 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K58aSAMWcGE1SRlog|H(?D8gCb z5n0T@z%2yAjF;}#{Q(NHmw5WRvR`826x9(odD=3Efq~_br;B4q#hkZy3^Sy|8QMP< z-*zZmaKz+nQY;gfV#SB7*H0v@r1<5M{G4Be%+g&LA!VKNDnrvXifLIeM~J)WjteO# zp8fDMTH4Jn?89<$?sNx179}RRD~>KQ>aG)&ZFhh9|5xqh%aT10?e_1U{6zEhJk}Gx z-mFn)n4`+nU^1A{7X5QoOhuQBUv7P!eL8&FbN#dD>rAa{^LEwVv8yWH^1rJ7`O|aX zKEF*{U3slo#3=Qy^=r)&5xe$GDgR{tJdf}Gnz8_Gwxf5$cP}^AE@Ub2I<@+#HFvq& z{e7Flcm94JTm0EkoKa%>JWu`e!GRTQ5VA*uk%ah*L;6+>-zOy z@tP zAvJUD69u)_+Y8vHhM(?SeE+k}?QOsN>rQWKQYesF{(tGwD@m1Ga|)*2ieU7K+_mS+ zo8Rdl18#Rg^n6hWGe~$=P>yOJx8*jWUIb={0Yi?+F z>Yjn%8V0u|>6i5DY6Rsy|NJbMIsX0cyT=+`Ik(@%vhQ9u+hft|SNh>QrRG`KahfH) z{S}p@QdqQqTG_a2L!=s0rmzv8;rR(-UtRV7_th-Zf0qv2Ual$Cy$B8~_!uA)x+vv|eTQx6SYwD((IW?z` ze02zayWrS&1)m4S48gzl^Gdk}n$7O5|6v*+(e`&bOO5mufiQ;PviP@Q-?TnX4AFY} z``?6a#@pMP6<*l?(ykWmt8x|UMr9qk;O+PB@zx%Ctz3Z>L{eBK(VB(Vc zH2Xo|huI02Em)0K-^^NP6O-xE>tB(toK^5;_P3p1-$wdB{J-w&x~*ToUpxA;n|m|& z;{B^Pg-8C+-TQT2YF^R?jt5t4>}vjO`T6D0bn$2VW=ii%eY>FScmHkve^mum!otT* t4`e>BZ~nJjfMH3YAWtR>!yrQcncUXra3r^eYzG#A44$rjF6*2UngFiFL*W1b literal 0 HcmV?d00001 diff --git a/kue/textures/7.png b/kue/textures/7.png new file mode 100644 index 0000000000000000000000000000000000000000..4fb73b04f858e133f63d019521850485cb967c27 GIT binary patch literal 1200 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K58aSAMWcGE1SRlog|H(?D8gCb z5n0T@z%2yAjF;}#{Q(NHmw5WRvR`826xB89pLt|G0|Se#r;B4q#hkZy>^-D&8IFHE zzOS|Ekhf-Qz^gzDju{6xni{5Tca{w*Igw*7JL&J-U8N^Pg)~^7^ROA+aPle?He7gN zA)hWw%PY^9LF&wT?-g2<4Nu0Nne0)2|9siUo$bd9;`i?UGxMaB!}YnD`V2GFm>Nt5 z6WX%-a$Lil8L#bPV|`PM{KdrOZR%?B%f4;8`>(j{+vgWAe*gP7{qEiS$B$o*TRdUr zn=RoBk`?FH1!|~lcAodiZSA*(5q}B`3}%LQDmQT6)R;OiKd((bUVr-b?c3h0We?{# zuxrNaXWCPLTnf4(8+~@!^wWiIDWZ%wM2qkKx2vrZ>F?k6dwtmJucx?pZyb(Q*!^kh zsT1z|4W9+t%v&FFY=>NlB15;C*U|?w&-;j6&H9?P{>P+qrXsUw^6=P<3>i2-o&9vl4*@QN!!9Rr~7=ju$q~KAB_oY?pbo$hw2t2g7V) zW8c;mq^B>x|Ksb|uj?kBOxaX^u0Q{NUuxwG1qN-QnLh8Ev$M0W^S!hEwO)4Si4=~6 zHx{g$9-VxYbYkMwtgXd|k4pq=i2N!Qs`G0(&Yc(*qNn%n>^VN!XTSe_`t<4L%a>P| zJ+@F+SO3cvzwP$UojW(`3LcaA#~dN_?1lNUih0*9f1N$h-SVMWNr-8~qfK|^td4#E zo3!!9O`oMfd&Tz!K6eRWNjN%x^X7%gHs`tX*pekG{ajob!v5G;Nm;2?9I@^*{&Bwk zgr%S;W5mbW`uYzI59Z1z``FFj|8tH6BTy{0y!=DMI)w!v}*)AzzDT zo<9-o#2}WNpSyL|*?Y&Od)*3(Kh!sbZ9cjD@!N-I3(6{Y#=K8&dj9-*GkfiI>-o)f zTchIi(hF-pRp?JBt=Z+91l|Ly%}_5*pG1s;*b z3=G^tAk28_ZrvZCAbW|YuPgf{CQea(o~di)HUo`W;OXKRQZeW4O+&xK1|r8k^1H2L zh*L7$WWMUg%w12FEIp>(QwkDS`^NY3-dYcxHsvW-dHQaPn6d5G`uBf{R;Te3)zW*L zWjTR{qk+#w$rW$2zWynx|NeLD{e5*3ZHcKnIn!q^Ew#A0_VCiJr4E&$yFc7sXViH_ zjXfm4zcBr|>h_M`@AaRiH2wJhyUZr1?FPF~@+r-I|L@I>{#~ z;!gT2t>XFjhTyFTUf;*3mUhW5n7$-!m$1``c+R}d@qw#%6i)hL`R|F|xwFwyj8>jm zzx*bdv=rTIJp6;YQVxQ#w->!uG-uN;@BHZldzuBI%z5Ng9tm-)?cfoU# zbklm(m3k}Kdn^}BOM2J6^MLeIhGwH3{j(h>iA|CdTYk7iPq~?W!{6sSZ>~@~-0;ps z?VFpjBlm1i!JOacJT*DR4Eek^JJuUY%XrP=727X2A*{ve`jMXw&$*)ogQh-X4sJIx wN+_{ieJG9Vzutn?KaGxTcSH+8VEpd<$rygRMdR<04}U?Dp00i_>zopr04$5yI{*Lx literal 0 HcmV?d00001 diff --git a/kue/textures/9.png b/kue/textures/9.png new file mode 100644 index 0000000000000000000000000000000000000000..fb7f4af332217dd35fbb7acb91162aa36a48e4c2 GIT binary patch literal 1170 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K58aSAMWcGE1SRlog|H(?D8gCb z5n0T@z%2yAjF;}#{Q(NHmw5WRvR`826g9N|8Q7A{z`!E!>EaktG3V`_y?M987}_58 zw=Tcubi~!+_!-fsDd(pwY&H2JCAHvcYX|F_>=zD)<%~-Cxh@qSXNo&<$0@8#AnSyR z>gkDneh1#y7PLj!ADMV4d`ZS7&)eIWVrs>!nH+pr7_tWg5)Trp=5sK~VNB>{NO-IC zjfKZ|>Gw;~FSma2`W5#}sdn1GiFTgyOWSW4n@rl0`$kh@=ajHZ{^xDq_(gfw&rf#y z#_=F$-IDnDPd~C>IGX%j47SKgRuPwOzD;Lt)FxFVnv`|Lea4$ zf2>b$I+i>R^%O!i`wllA~p`Un0`5%v)?K-R1 zMUm4oXTAGr6Qv{O%o}sGPI0m9$8>{xg*X4dxxfG0``iac-%d+De;}Utu;hF3C!6{^ zFQ-pgmKizi@7Y6V&-#Xjp4a1DE@*pjVqfNeW1G{P-j>&G{$~G9D);`@WMzZizl+=~ zEVx8Y>JVctkB+drQiFpX|GixiI;S?hk>NkP zX^qZl+2>5Q`+a1ZV;4B&eV_mRzRB5V*?F^;ZNBNlF3Yxea-H2c{i~6y$%KKdrQze}1xx=cJUKG0!UZ)n5Om+`jK0pT6+%u7A5< zeC)gZZ{>oy=Ji{nL|rdhT3h*E-nFj!x#OKDxDPB@PZ%6}64+c+H KKbLh*2~7a};qP() literal 0 HcmV?d00001 diff --git a/kue/textures/cue-player1.png b/kue/textures/cue-player1.png new file mode 100644 index 0000000000000000000000000000000000000000..df6b04c4e904eab9f0587198c2299a59489bb4d8 GIT binary patch literal 26201 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 IM6N<$g6F%y>Hq)$ literal 0 HcmV?d00001 diff --git a/kue/textures/cue-player2.png b/kue/textures/cue-player2.png new file mode 100644 index 0000000000000000000000000000000000000000..374cf88b2e52f3a0eae3cbf4ab94d165b6e00da2 GIT binary patch literal 25940 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 Hu0mjfnLR&{ literal 0 HcmV?d00001 diff --git a/kue/textures/table.png b/kue/textures/table.png new file mode 100644 index 0000000000000000000000000000000000000000..b878017dfb120d5eafe0fc1e73db8e100e008abe GIT binary patch literal 825 zcmV-91IGM`P)dGJBX)cIbydB zzFB_4feSqoyz53!%BAarn!GQi+3okKrp-fp!|CuSHf!q1&~?vY$g_=q)r>QD_gHG3 zbQoqs1hBfp&9Ir6by`yh{Ltg1^-8|JWl*PsYC5F}0%{e#)FeY3zX46Mlr&I*7}YEP z8>+z_Qc{blx!N)crf?D<98ooZ8(o646Q6s%1s3IlM${=++Jpzwlc{hC`9m`)r!3BD;TY$P-r-E7~&&Q03d8)zG+;~h}+*BBPM=EWP0xE`y7^1J8M5SH0 z-*arv7P=bz(T@S>1u6la3UCC~DBoIct)3`Qf5CYj&lw1F*c)1^RUOxYqX&p&5KJ>M zez<(HpLPYXyTPfQ*#rebm90xpU4n(LEK%bCy&9P9`s+1HY-%S_qntZEmp~tP+1|8Z z-$~$pdCb+R1q-|XN}U%~=`uXKqLv*7Yt)!=T#FSN6Bx)xI z9}s=887&8;b-sMM-|kO zAa09EzW0_E1rr~@m&RMOnQhz0sXvcF1wQ7SoRHhdTU=vG!;vKRGvqpBKM<$l8+FPM zx3-(&d}9OpaYq%Ei?|HKoQbPKU*=}=l+&PW#kqqH9HV}rS00000NkvXXu0mjf D=D2xF literal 0 HcmV?d00001 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 +#include +#include + +#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 +#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