beta heart system

This commit is contained in:
Thomas 2022-01-09 00:35:09 +01:00
parent b8c29cad9d
commit c5f08c8bcc
No known key found for this signature in database
GPG Key ID: E538821A6CDFDAD7
16 changed files with 111 additions and 82 deletions

View File

@ -8,6 +8,7 @@ players:
startXPosition: 50
fireCooldown: 10
speed: 20
lives: 3
user1:
color: red
keys:

View File

@ -20,6 +20,7 @@ struct ConfigData {
unsigned playersSpeed;
unsigned playersWidth;
unsigned playersFireCooldown;
unsigned playersLives;
vector<PlayerDef> playerDefs;

View File

@ -28,7 +28,7 @@ private:
bool direction = true;
vector<missile> missiles;
vector<torpedo> torpedos;
vector<Torpedo> torpedos;
PlayMode playMode;
vector<Player> players;
@ -37,6 +37,7 @@ private:
unsigned fireCooldown=120;
// basic methods
void initGame();
bool updateColumns();
void handleScoreSaving();
Position invIndexToPos(unsigned x, unsigned y) const;
@ -69,7 +70,7 @@ public:
// in case someone wants to mess with the code, here's a minimal API, costs nothing to us
Game();
void managedGames();
WinValue playGame();
WinValue enterGameLoop();
bool reloadConfig();
};

View File

@ -5,7 +5,6 @@ enum class PlayMode {
NONE,
SINGLE,
TWO_LOCAL,
TWO_TCPIP,
EXIT,
};

View File

@ -11,7 +11,10 @@ struct Player{
unsigned fireCooldown=0;
// TODO remove ?
bool isEliminated();
bool hasDeathAnimation() const;
bool isEliminated() const;
bool isPlaying() const;
void damage();
};
#endif

View File

@ -5,10 +5,10 @@
typedef Position missile;
class torpedo : public Position {
class Torpedo : public Position {
public:
playerID owner;
torpedo(int x, int y, playerID owner);
Torpedo(int x, int y, playerID owner);
};
#endif

View File

@ -1,16 +1,16 @@
#ifndef GUARD_SCORESMANAGER_H
#define GUARD_SCORESMANAGER_H
#include <utility>
#include <vector>
#include <string>
#include<utility>
#include<vector>
#include<string>
#include "utils.h"
using namespace std;
struct ScoreLink{
string name;
unsigned score;
ScoreLink() = default;
ScoreLink(const string& name, unsigned score);
};

View File

@ -9,6 +9,9 @@
#define PROJ_LENGTH_FACTOR 2
// TODO utiliser ca de partout
/* Copy constructuor and assignement are disabled in most of
* our classes so we're sure we can't accidentally copy players
* (We need to explicitly specify the default constructor)*/
#define INV_GET_POS(i) confData.invadersSize*(i)+confData.invadersDistance*(i)

View File

@ -169,6 +169,7 @@ void ConfigBuilder::readConfig() {
collectedData.startXPosition = getInt("players.startXPosition");
collectedData.playersSpeed = getInt("players.speed");
collectedData.playersFireCooldown = getInt("players.fireCooldown");
collectedData.playersLives = getInt("players.lives");
// the scalability behind the vector of players is only an illusion, because we force player count to be 1 or 2
// It was done so the 2+ players implementation could be easier in the future, if wanted
@ -272,6 +273,6 @@ bool Game::reloadConfig() {
cerr << "(The old configuration was kept in memory)" << endl;
return false;
}
confData = builder.collectedData;
confData = move(builder.collectedData);
return true;
}

View File

@ -19,7 +19,7 @@ void Game::displayAll(unsigned fps) const {
for(const missile& miss : missiles){
pm.drawMissile(miss, confData.missilesWidth, confData.missilesColor);
}
for(const torpedo& tor : torpedos){
for(const Torpedo& tor : torpedos){
pm.drawTorpedo(tor, confData.torpedosWidth, confData.torpedosColor);
}
@ -30,17 +30,16 @@ void Game::displayAll(unsigned fps) const {
for(size_t i=0;i<players.size();++i){
if(players[i].deathAnimCounter%2==0){
pm.drawPlayer(players[i].x, confData.playersWidth, confData.playerDefs[i].color);
}
// As said before, the player loop is an illusion, 2 players max
for(unsigned i=0;i<players[0].lives;++i){
displayHearts(i);
}
pm.drawHeart(Position(0, 70));
}
void Game::displayHearts(playerID pID) const {
// As said before, the player loop is an illusion, 2 players max
unsigned x;
if(pID==PLAYER1)x = 0;
else x = pm.getScreenWidth()-HEART_LENGTH;
@ -50,14 +49,14 @@ void Game::displayHearts(playerID pID) const {
pm.drawHeart(Position(x, y));
y+=HEART_LENGTH+5;
}
if(players[pID].deathAnimCounter>0){
if(players[pID].hasDeathAnimation()){
pm.drawHeart(Position(x, y+players[pID].deathAnimCounter*5));
}
}
void Game::displayInvader(const Position& pos, InvaderType type) const {
if(type==InvaderType::NONE)return;
InvaderTypeDef invDef = confData.invadersDef.at(type);
const InvaderTypeDef& invDef = confData.invadersDef.at(type);
switch(type){
case InvaderType::TYPEA:{
pm.drawInvaderA(pos, confData.invadersSize, invDef.color);
@ -74,7 +73,6 @@ void Game::displayInvader(const Position& pos, InvaderType type) const {
}
}
void applyBezier(Position& pos, const Position& point, const double percent) {
pos += (point-pos)*percent;
}

View File

@ -55,7 +55,8 @@ void Game::managedGames() {
if(playMode==PlayMode::NONE){
playMode = pm.showInitialMenu();
}else{
playGame(); // will read the playMode
initGame();
enterGameLoop(); // will read the playMode
handleScoreSaving();
cout << "END OF GAME" << endl;
break; // TODO remove
@ -66,19 +67,13 @@ void Game::managedGames() {
}
// TODO maybe directly call theses from pm and do not use getters ?
/**
* Plays the game, and returns once the game is finished
*
* @return @WinValue::PLAYERS if the players won, @WinValue::INVADERS is the invaders won, WinValue::NOBODY else (also in case of error)
*/
WinValue Game::playGame(){ // returns when game is finished
// INIT
// we assume the game has been played before, and so we need to clean used members
// we assume the game has been played before, and so we need to clean used members
void Game::initGame(){
grid = confData.grid; // will copy the grid
updateColumns(); // Would have liked to to that in the "config grid", but.. I'm lazy
// we re-construct players objects, we don't have to clear all members and can rely on the construction value (set in .h file)
players.clear();
if(playMode==PlayMode::SINGLE){
players.resize(1);
@ -89,9 +84,21 @@ WinValue Game::playGame(){ // returns when game is finished
}
players[0].x = confData.startXPosition;
basePos = Position(200, 0);
// GAMELOOP
for(unsigned i=0;i<players.size();++i){
players[i].id = i;
players[i].lives = confData.playersLives;
}
basePos = Position(200, 0);
}
/**
* Plays the game, and returns once the game is finished
*
* @return @WinValue::PLAYERS if the players won, @WinValue::INVADERS is the invaders won, WinValue::NOBODY else (also in case of error)
*/
WinValue Game::enterGameLoop(){ // returns when game is finished
// computed in advance for performance reasons
chrono::milliseconds maxFrameTime = chrono::milliseconds(1000/confData.maxFPS);

View File

@ -2,39 +2,33 @@
#define ISPRESSED(ID, X) window.isPressed({confData.playerDefs[ID].keys.X, false})
void Game::manageOnePlayer(playerID pID){
// manage death
// we do not use isEliminated here because we can handle it better with if/else
/*
* lives = 0 && counter == 0 -> elim
* lives = 1 && counter == 0 -> play
* lives = 0 && counter == 1 -> anim
* lives = 1 && counter == 1 -> anim
*/
if(players[pID].deathAnimCounter==0){
if(players[pID].lives==0)return;
}else{
++players[pID].deathAnimCounter;
}
Player& p = players[pID];
if (ISPRESSED(pID, left)){
if(players[pID].x < confData.playersSpeed) players[pID].x = 0;
else players[pID].x -= confData.playersSpeed;
if(p.x < confData.playersSpeed) p.x = 0;
else p.x -= confData.playersSpeed;
}
if (ISPRESSED(pID, right)){
if(players[pID].x + confData.playersWidth + confData.playersSpeed >= pm.getScreenWidth()) players[pID].x = pm.getScreenWidth() - confData.playersWidth - 1;
else players[pID].x += confData.playersSpeed;
if(p.x + confData.playersWidth + confData.playersSpeed >= pm.getScreenWidth()) p.x = pm.getScreenWidth() - confData.playersWidth - 1;
else p.x += confData.playersSpeed;
}
if(players[pID].fireCooldown==0) {
if(p.hasDeathAnimation()) {
++p.deathAnimCounter;
if (p.deathAnimCounter == 100) {
p.deathAnimCounter = 0;
}
}else{
if(p.isEliminated())return;
if(p.fireCooldown==0) {
if (ISPRESSED(pID, shoot)) {
torpedos.emplace_back(players[pID].x + confData.playersWidth / 2, pm.getScreenHeight() - PLAYER_HEIGHT, pID);
players[pID].fireCooldown = confData.playersFireCooldown;
torpedos.emplace_back(p.x + confData.playersWidth / 2, pm.getScreenHeight() - PLAYER_HEIGHT, pID);
p.fireCooldown = confData.playersFireCooldown;
}
}else --p.fireCooldown;
}
}else --players[pID].fireCooldown;
}
/** Makes the players play once
@ -48,7 +42,7 @@ void Game::managePlayers(){
* @return true if the invaders went down from one line (and we should check lower boundary), else false
*/
bool Game::manageInvaders(){
if(grid.size()==0)return false; // If there are no more invaders we don't need to manage them
if(grid.empty())return false; // If there are no more invaders we don't need to manage them
// shoot
if(fireCooldown==0) {
fireCooldown = confData.invadersFireCooldown + rand() % 60;
@ -152,15 +146,17 @@ bool Game::checkMissilesAndPlayers() {
if(miss_ite->getY()>=pm.getScreenHeight()-PLAYER_HEIGHT){ // check collision on Y
// now check collision on X (with both players)
for(Player& p : players){
if(p.isPlaying()){
if(areLinesColliding(
miss_ite->getX(), miss_ite->getX() + confData.missilesWidth,
p.x, p.x + confData.playersWidth)){
wasColliding = true;
if(p.deathAnimCounter)p.deathAnimCounter = 1;
p.damage();
// do not break, the second player also deserves to be hit
}
}
}
}
if(wasColliding)missiles.erase(miss_ite);
else ++miss_ite;
}
@ -190,7 +186,7 @@ bool Game::checkTorpedosAndInvaders() {
InvaderType invType = grid[i][grid[i].size()];
players[tor_ite->owner].score += confData.invadersDef[invType].points;
players[tor_ite->owner].score += confData.invadersDef.at(invType).points;
torpedos.erase(tor_ite);
grid[i][alienIndex] = InvaderType::NONE;

View File

@ -69,6 +69,10 @@ bool Game::manageGod() {
playerID target;
if (players.size() == 1)target = PLAYER1; // don't want to use random if not needed
else target = rand() % players.size();
/*
* Let's just pretend god is drunk and can fire at a player that have a death animation, because
* honestly at this point I want to re-code the whole game engine to allow a better handling of cases like this...
*/
Position playerMiddlePos(players[target].x + confData.playersWidth / 2,
pm.getScreenHeight() - PLAYER_HEIGHT / 2);
@ -96,16 +100,18 @@ bool Game::manageGod() {
// check player collision
} else if (invaderPos.getY() + confData.invadersSize >= pm.getScreenHeight() - PLAYER_HEIGHT) {
for (Player &p: players) {
if(p.isPlaying()){
if (areLinesColliding(
p.x, p.x + confData.playersWidth,
invaderPos.getX(), invaderPos.getX() + confData.invadersSize
)) {
// TODO manage player death
p.damage();
touched = true;
// do not break, the other player also deserves to be hit
}
}
}
}
if (touched) {
god.state = GodState::WAIT;
god.counter = 0;

View File

@ -1,5 +1,18 @@
#include "player.h"
bool Player::isEliminated() {
return lives == 0 && deathAnimCounter == 0;
bool Player::isPlaying() const {
return !isEliminated() && !hasDeathAnimation();
}
bool Player::hasDeathAnimation() const {
return deathAnimCounter!=0;
}
bool Player::isEliminated() const {
return lives == 0;
}
void Player::damage() {
--lives;
deathAnimCounter = 1;
}

View File

@ -1,5 +1,5 @@
#include "projectiles.h"
torpedo::torpedo(int x, int y, playerID owner) : Position(x, y) {
Torpedo::Torpedo(int x, int y, playerID owner) : Position(x, y) {
this->owner = owner;
}

View File

@ -84,7 +84,7 @@ void ScoresManager::inputScore(const string& name, unsigned score) {
++ite;
}
if(ite==scores.end())scores.emplace(ite, name, score);
if(scores.size()==6)scores.resize(5);
if(scores.size()==6)scores.pop_back();
}