Skip to content

Commit

Permalink
Implemented fractional fall speed
Browse files Browse the repository at this point in the history
The specifications for fall speed can now have a fractional part
(e.g. 1.2). This creates a more organic look and enables further
fine-tuning and customization of the of the visuals.
  • Loading branch information
M4444 committed Nov 22, 2021
1 parent 77a8e7d commit cbbb12e
Show file tree
Hide file tree
Showing 10 changed files with 166 additions and 18 deletions.
69 changes: 69 additions & 0 deletions include/DecimalFraction.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* Copyright (C) 2018-2021 Miloš Stojanović
*
* SPDX-License-Identifier: GPL-2.0-only
*/

#ifndef DECIMAL_FRACTION_H
#define DECIMAL_FRACTION_H

#include <stdexcept>
#include <string>

class DecimalFraction {
// For example 1.2 is represented as 12.
int Representation;
public:
constexpr DecimalFraction() : Representation{0} {}
constexpr DecimalFraction(const DecimalFraction& DF) :
Representation{DF.Representation} {}
constexpr DecimalFraction(int IntegerPart, int FractionalPart = 0) :
Representation{
IntegerPart * 10 + (IntegerPart < 0 ? - FractionalPart : FractionalPart)
}
{
if (FractionalPart > 9 || FractionalPart < 0) {
throw std::invalid_argument("Fractional part must to be in range 0-9.");
}
}

constexpr int GetIntegerPart() const { return Representation / 10; }
constexpr int GetFractionalPart() const { return Representation % 10; }

constexpr DecimalFraction GetFloor() const { return Representation / 10; }

constexpr bool operator>(const DecimalFraction& DF) const
{
return Representation > DF.Representation;
}

constexpr bool operator==(const DecimalFraction& DF) const
{
return Representation == DF.Representation;
}

constexpr DecimalFraction& operator+=(const DecimalFraction& DF)
{
Representation += DF.Representation;
return *this;
}

constexpr DecimalFraction operator-(const DecimalFraction& DF) const
{
int result {Representation - DF.Representation};
return {result / 10, result % 10};
}

constexpr DecimalFraction& operator=(const DecimalFraction& DF)
{
Representation = DF.Representation;
return *this;
}

std::string to_string() const
{
return std::to_string(Representation / 10) + "." + std::to_string(Representation % 10);
}
};

#endif
7 changes: 4 additions & 3 deletions include/Parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -125,10 +125,11 @@ namespace Parser {
RANGE, "-f", "--fall-speed",
{
"Set the range for the fall speed",
"The maximal fall speed value is " + std::to_string(Rain::MAX_FALL_SPEED),
"The speeds can have decimal parts (e.g. 1.2)",
"The maximal fall speed value is " + Rain::MAX_FALL_SPEED.to_string(),
"Default: " +
std::to_string(Rain::DEFAULT_PROPERTIES.RainColumnSpeed.GetMin()) + ',' +
std::to_string(Rain::DEFAULT_PROPERTIES.RainColumnSpeed.GetMax())
Rain::DEFAULT_PROPERTIES.RainColumnSpeed.GetMin().to_string() + ',' +
Rain::DEFAULT_PROPERTIES.RainColumnSpeed.GetMax().to_string()

},
[](std::string_view range, const OutputVariables& out)
Expand Down
9 changes: 5 additions & 4 deletions include/Rain.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,14 @@
#include <vector>
#include "Active.h"
#include "Color.h"
#include "DecimalFraction.h"
#include "RainColumn.h"
#include "RainStreak.h"
#include "Range.h"
#include "Terminal.h"

struct RainProperties {
Range<int> RainColumnSpeed;
Range<DecimalFraction> RainColumnSpeed;
Range<int> RainColumnStartingGap;
Range<int> RainColumnGap;
Range<int> RainStreakLength;
Expand All @@ -34,11 +35,11 @@ class Rain : public Active {
const RainProperties Properties;
Terminal* terminal;

int GetRandomSpeed() const;
DecimalFraction GetRandomSpeed() const;
int GetRandomStartingGap() const;
public:
static constexpr RainProperties DEFAULT_PROPERTIES {
{1, 1}, {10, 50}, {0, 40}, {1, 30}, {10, 20},
{{0, 5}, {1, 5}}, {10, 30}, {0, 40}, {1, 30}, {10, 20},
true, Color::GetColor("green"), Color::GetColor("black"),
L" T M A T R I X "
};
Expand All @@ -47,7 +48,7 @@ class Rain : public Active {
false, Color::GetColor("green"), Color::GetColor("default"),
L" T M A T R I X "
};
static constexpr int MAX_FALL_SPEED {10};
static constexpr DecimalFraction MAX_FALL_SPEED {10};
static constexpr int MIN_LENGTH {1};

Rain(const RainProperties& RP, Terminal* T);
Expand Down
15 changes: 12 additions & 3 deletions include/RainColumn.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,33 @@
#include <list>
#include "Active.h"
#include "CountdownTimer.h"
#include "DecimalFraction.h"
#include "HasTerminal.h"
#include "RainStreak.h"
#include "Random.h"

class Rain;

class RainColumn final : public Active, public HasTerminal {
const Rain *rain;
const unsigned x;
const int Speed;
std::vector<int> Speeds;
int SpeedIndex;
CountdownTimer GapTimer;
wchar_t TitleChar;
const RainStreak *FirstRainStreak {nullptr};
bool CreatedRainStreak {false};
bool EmptyRainStreakSlot {true};
std::list<RainStreak> RainStreaks;
public:
RainColumn(const Rain *R, unsigned X, int S, int G, wchar_t TC) :
rain{R}, x{X}, Speed{S}, GapTimer{G}, TitleChar{TC} {}
RainColumn(const Rain *R, unsigned X, DecimalFraction S, int G, wchar_t TC) :
rain{R}, x{X}, GapTimer{G}, TitleChar{TC}
{
GenerateSpeeds(S);
SpeedIndex = Random::Random(Speeds.size());
}

void GenerateSpeeds(DecimalFraction Speed);

void Step();
void Update() final;
Expand Down
3 changes: 3 additions & 0 deletions include/Random.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#ifndef RANDOM_H
#define RANDOM_H

#include "DecimalFraction.h"
#include "Range.h"

namespace Random {
Expand All @@ -16,6 +17,8 @@ namespace Random {
int Random(int min, int max);
// Returns a random integer in the range [0, range-1]
int Random(int range);
// Returns a random decimal fraction in the range [range.min, range.max]
DecimalFraction Random(Range<DecimalFraction> range);
}

#endif
36 changes: 34 additions & 2 deletions src/Parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ namespace Parser {
PrintSpecificOptionType(VERSION, longestLiterals);
std::cout << '\n';
std::cout << "RANGE is a pair of numbers separated by a comma: MIN,MAX." << '\n';
std::cout << "It specifies the boundaries of an integer set used in random operations." << '\n';
std::cout << "It specifies the boundaries of a set from which random numbers are picked." << '\n';
}
}

Expand Down Expand Up @@ -344,6 +344,27 @@ namespace Parser {
return std::atoi(value.data());
}

DecimalFraction ReturnValidDecimalFraction(std::string_view value)
{
int IntegerPart {0};
int FractionPart {0};

auto pointPlace {value.find('.')};
if (pointPlace == 0) {
FractionPart = ReturnValidNumber(value.substr(pointPlace + 1));
}
else if (pointPlace == value.length() - 1) {
IntegerPart = ReturnValidNumber(value.substr(0, pointPlace));
}
else if (pointPlace == std::string_view::npos) {
IntegerPart = ReturnValidNumber(value);
}
else {
IntegerPart = ReturnValidNumber(value.substr(0, pointPlace));
FractionPart = ReturnValidNumber(value.substr(pointPlace + 1));
}
return {IntegerPart, FractionPart};
}
//----------------------------------------------------------------------
void SetRainProperties(std::string_view mode, RainProperties &rainProperties)
{
Expand All @@ -367,10 +388,21 @@ namespace Parser {
int max {ReturnValidNumber(range.substr(commaPlace + 1))};
return {min, max};
}

Range<DecimalFraction> SplitDecimalFractionRange(std::string_view range)
{
auto commaPlace {range.find(',')};
if (commaPlace == std::string_view::npos) {
throw std::invalid_argument("No comma found in range argument.");
}
DecimalFraction min {ReturnValidDecimalFraction(range.substr(0, commaPlace))};
DecimalFraction max {ReturnValidDecimalFraction(range.substr(commaPlace + 1))};
return {min, max};
}
//---SPEED--------------------------------------------------------------
void SetSpeedRange(std::string_view range, RainProperties &rainProperties)
{
rainProperties.RainColumnSpeed = SplitRange(range);
rainProperties.RainColumnSpeed = SplitDecimalFractionRange(range);
if (rainProperties.RainColumnSpeed.GetMax() > Rain::MAX_FALL_SPEED) {
throw std::out_of_range("Value is out of range.");
}
Expand Down
2 changes: 1 addition & 1 deletion src/Rain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ void Rain::Update()
}
}

int Rain::GetRandomSpeed() const
DecimalFraction Rain::GetRandomSpeed() const
{
return Random::Random(Properties.RainColumnSpeed);
}
Expand Down
23 changes: 22 additions & 1 deletion src/RainColumn.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,26 @@
#include "RainColumn.h"
#include "Terminal.h"

void RainColumn::GenerateSpeeds(DecimalFraction speed)
{
// Generates an array of integer speeds whose cumulative average is equal
// to the specified speed. The order is also such that the average of any
// sampled subset should be as close to the specified speed as possible
// to create a nice, consistent visual.
DecimalFraction speedAproximation {speed};
DecimalFraction trueSum {speed};
DecimalFraction sumOfIntegerSpeeds {0};
while (true) {
DecimalFraction integerSpeed {speedAproximation.GetFloor()};
Speeds.emplace_back(integerSpeed.GetIntegerPart());
if (integerSpeed == speedAproximation)
return;
sumOfIntegerSpeeds += integerSpeed;
trueSum += speed;
speedAproximation = trueSum - sumOfIntegerSpeeds;
}
}

void RainColumn::Step()
{
if (EmptyRainStreakSlot) {
Expand Down Expand Up @@ -57,7 +77,8 @@ void RainColumn::Step()

void RainColumn::Update()
{
for (int i = 0; i < Speed; i++) {
for (int i = 0; i < Speeds[SpeedIndex]; i++) {
Step();
}
SpeedIndex = (SpeedIndex + 1) % Speeds.size();
}
10 changes: 10 additions & 0 deletions src/Random.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,14 @@ namespace Random {
{
return Random(Range(range));
}

DecimalFraction Random(Range<DecimalFraction> range)
{
const DecimalFraction minDF {range.GetMin()};
const DecimalFraction maxDF {range.GetMax()};
const int min {minDF.GetIntegerPart() * 10 + minDF.GetFractionalPart()};
const int max {maxDF.GetIntegerPart() * 10 + maxDF.GetFractionalPart()};
const int random {Random(min, max)};
return {random / 10, random % 10};
}
}
10 changes: 6 additions & 4 deletions tmatrix.6
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.TH TMatrix 6 "16 January 2020" "TMatrix Version 1.3"
.TH TMatrix 6 "22 November 2021" "TMatrix Version 1.3"
.SH NAME
TMatrix \- Terminal based replica of the digital rain from The Matrix
.SH SYNOPSIS
Expand Down Expand Up @@ -35,9 +35,11 @@ Default: \fI10\fR.
\-f, --fall-speed=RANGE
Set the range for the fall speed.
.br
The maximal fall speed value is \fB10\fR.
The speeds can have \fBdecimal parts\fR (e.g. \fB1.2\fR)
.br
Default: \fI1,1\fR.
The maximal fall speed value is \fB10.0\fR.
.br
Default: \fI0.5,1.5\fR.
.TP 4
\-G, --start-gap=RANGE
Set the range for the starting gaps.
Expand Down Expand Up @@ -96,7 +98,7 @@ in order to be displayed.
.PP
RANGE is a pair of numbers separated by a comma: MIN,MAX.
.br
It specifies the boundaries of an integer set used in random operations.
It specifies the boundaries of a set from which random numbers are picked.
.SH Real-time commands
.TP 4
p
Expand Down

0 comments on commit cbbb12e

Please sign in to comment.