I'm stuck on one final piece of a calculation puzzle below. I know how to generate a percentage score of correct parts from total correct possible parts ((correctNumPartsOnBoard / totalPossibleCorrectParts)*100) but I want to the final percentage score to factor in the number the incorrect parts on the board as well. (even if all the right parts are on the board you still won't get 100% if there are also incorrect parts). Right now my current formula percentCorrectParts = ((correctNumPartsOnBoard / totalPossibleCorrectParts) / totalNumPartsOnBoard) * 100); is wrong and I'm having trouble pinpointing the correct calculation.
So, the way the calc would need to work is: a user needs to match one of the six possible animals, each animal has around 15 correct parts, but users can also drag incorrect parts onto the board (parts from the other animals are still visible so they could drag a different set of legs or horns on a lizard head, they could make frankenstein type creatures as well this way). So the total number of parts available would be 6*15. But seeing as how they're not all correct they would influence the score as well by bringing the overall score average of pieces on the board down.
What's the correct formula for this?
// Scoring System
using UnityEngine;
using System.Linq;
using System.Collections.Generic;
public class ScoreManager : MonoBehaviour
{
public List<string> totalBuildBoardParts; // Running list of all parts on board (by Tag)
public int numCorrectPartsOnBoard;
public int numIncorrectPartsOnBoard;
public int totalPossibleCorrectParts;
public float percentCorrectParts;
void Start()
{
GameObject gameController = GameObject.FindGameObjectWithTag("gc");
GameSetup gameSetup = gameController.GetComponent<GameSetup>();
totalPossibleCorrectParts = gameSetup.totalPossibleCorrectParts;
Debug.Log("TOTAL POSSIBLE CORRECT PARTS ARE: " + totalPossibleCorrectParts);
}
public void AddAnimalPartByTag(string tag)
{
// Add object tag to List
totalBuildBoardParts.Add(tag);
Debug.Log ("Added an object tagged as: " + tag);
GameObject gameController = GameObject.FindGameObjectWithTag("gc");
GameSetup gameSetup = gameController.GetComponent<GameSetup>();
if (tag == gameSetup.activeTag)
{
numCorrectPartsOnBoard ++;
Debug.Log ("There are " + numCorrectPartsOnBoard + " correct parts on the board");
} else {
numIncorrectPartsOnBoard ++;
}
CalculateScore();
}
public void RemoveAnimalPartByTag(string tag)
{
// Add object tag to List
totalBuildBoardParts.Remove(tag);
Debug.Log ("Removed an object tagged as: " + tag);
GameObject gameController = GameObject.FindGameObjectWithTag("gc");
GameSetup gameSetup = gameController.GetComponent<GameSetup>();
if (tag == gameSetup.activeTag)
{
numCorrectPartsOnBoard --;
Debug.Log ("There are " + numCorrectPartsOnBoard + " correct parts on the board");
} else {
numIncorrectPartsOnBoard --;
}
CalculateScore();
}
public void CalculateScore()
{
float totalNumPartsOnBoard = totalBuildBoardParts.Count();
float correctNumPartsOnBoard = numCorrectPartsOnBoard;
percentCorrectParts = ((correctNumPartsOnBoard / totalPossibleCorrectParts) / totalNumPartsOnBoard) * 100);
Debug.Log ("Your current score is: " + percentCorrectParts);
}
}
Your formula is probably correct. However, your datatypes are not.
You are currently doing an integer division, which results in an int too. So let's say that correctNumPartsOnBoard is 3 and totalPossibleCorrectParts is 5, 3/5 gives 0 because an int does not have any decimals.
You need to cast one of the two operands in the division as a datatype with decimals ( float, double or decimal for example):
percentCorrectParts = ((correctNumPartsOnBoard / (float)totalPossibleCorrectParts) / totalNumPartsOnBoard) * 100);
By setting denominator totalPossibleCorrectParts as a float, the first division will return a float. That float is then used in the second division, also returning correctly a float.
I think your formula should look like this:
int correctParts;
int possibleCorrect;
int incorrectParts;
int parts;
float percentFinished =
Mathf.Max((((float)correctParts/possibleCorrect) // Correct percent
- ((float)incorrectParts/parts)) // Minus incorrect percent
* 100f, // Normalized to 100
0f); // Always a minimum of 0
Also with this formula unlike other answers, you don't have to use all of the parts to get 100%, just get the total possible correct parts which doesn't necessarily have to use up all of your parts ;)
Scenario
Lets say you have 100 parts, with 3 right and 3 wrong. Total right we are aiming for here is 20.
int correctParts = 3;
int possibleCorrect = 20;
int incorrectParts = 3;
int parts = 100;
float percentFinished =
Mathf.Max((((float)correctParts/possibleCorrect) // Correct percent is 0.15 or 15%
- ((float)incorrectParts/parts)) // Minus incorrect percent which is .03 or 3%
* 100f, // Normalized to 100 which gives us 15% - 3% = 12%
0f); // Always a minimum of 0
I think your final (%age) score should be:
correctNumPartsOnBoard / totalNumPartsOnBoard * 100
If you have 80 correctparts and 20 incorrect then the total parts is 100 and you've got 80 of them correct so you should score 80% like this:
80 / (80+20) * 100
Related
I am trying to create a program that will spawn balls from the top randomly at random times. The problem is it is not fast enough, but if I change the value to like 1/2 it spawns 50 super fast.
using System.Collections.Generic;
using UnityEngine;
public class SpawnAstroids : MonoBehaviour
{
public GameObject astriod;
public float xBounds, yBounds;
public int playerPoints = 0;
public int enemyPoints = 0;
void Start()
{
StartCoroutine(SpawnRandomGameObject());
}
IEnumerator SpawnRandomGameObject()
{
yield return new WaitForSeconds(Random.Range(1,2)); //Random.Range(1/2, 2)
Instantiate(astriod, new Vector2(Random.Range(-xBounds, xBounds), yBounds), Quaternion.identity);
StartCoroutine(SpawnRandomGameObject());
}
}
Unity C# requires that you specify whether your decimal is specifically a float or a double. Add in f to the end of each decimal number. For example: Random.Range(0.5f, 2);
(Minor Note that Random.Range is inclusive vs exclusive depending on whether you use integers or floats.)
Similarly when you define a Vector2 bob = new Vector2(0.5f,0);, the f is also needed to denote explicitly that it is a float.
Random.Range has two overloads.
public static float Range(float minInclusive, float maxInclusive);
and
public static int Range(int minInclusive, int maxExclusive);
you are passing in
Random.Range(1, 2);
which are two int values so the second overload is used where the result will be an int between 1 and 2 - 1 ... not many options here ;)
Also your attempt
Random.Range(1/2, 2);
are again int values! 1/2 is an integer division which results in 0! So this random can either return 0 or 1.
What you rather want to do is passing in float values like e.g.
Random.Range(1f, 2f);
which can result in any floating point value between 1 and 2 or accordingly
Random.Range(0.5f, 2);
or back to your attempt
Random.Range(1 / 2f, 2);
which now uses a float division and thereby the result is automatically a float so the first overload will be used.
In general btw there is no need to call a Coroutine recursively, you can simply loop forever and also note that Start itself can be a Coroutine like e.g.
private IEnumerator Start()
{
while(true)
{
yield return new WaitForSeconds(Random.Range(0.5f, 2f));
Instantiate(astriod, new Vector2(Random.Range(-xBounds, xBounds), yBounds), Quaternion.identity);
}
}
I'm trying to create a random float generator (range of 0.0-1.0), where I can supply a single target value, and a strength value that increases or decreases the chance that this target will be hit. For example, if my target is 0.7, and I have a high strength value, I would expect the function to return mostly values around 0.7.
Put another way, I want a function that, when run a lot of times, would produce a distribution graph something like this:
Histogram
Something like a bell curve, yes, but with a strict range limit (instead of the -inf/+inf range limit of a normal distribution). Clamping a normal distribution is not ideal, I want the distribution to naturally end at the range limits.
The approach I've been attempting is to come up with a formula to transform a value from uniform distribution to the mythical distribution I'm envisioning. Something like an inverse sine:
Inverse Sine
with the ability to widen out that middle point, via the strength value:
Widened Midpoint
and also the ability to move that midpoint up and down, via the target value:
Target changed to 0.7 (courtesy of MS Paint because I couldn't figure this part out mathematically)
The range of this theoretical "strength value" is up for debate. I could imagine either a limited value, say between 0 and 1, where 0 means it's uniform distribution and 1 means it's a 100% chance of hitting the target; or, I could imagine a value that approaches a 100% chance the higher it gets, without ever reaching it. Something along either line would work.
I'm working in C# but this can be language-agnostic. Any help pointing me in the right direction is appreciated. Also happy to clarify further.
I'm not a mathematician but I took a look and I feel like I got something that might work for you.
All i did was take the normal distribution formula:
and use 0.7 as mu to shift the distribution towards 0.7. I added a leading coefficient of 0.623 to shift the values to be between 0 and 1 and migrated it from formula to C#, this can be found below.
Usage:
DistributedRandom random = new DistributedRandom();
// roll for the chance to hit
double roll = random.NextDouble();
// add a strength modifier to lower or strengthen the roll based on level or something
double actualRoll = 0.7d * roll;
Definition
public class DistributedRandom : Random
{
public double Mean { get; set; } = 0.7d;
private const double limit = 0.623d;
private const double alpha = 0.25d;
private readonly double sqrtOf2Pi;
private readonly double leadingCoefficient;
public DistributedRandom()
{
sqrtOf2Pi = Math.Sqrt(2 * Math.PI);
leadingCoefficient = 1d / (alpha * sqrtOf2Pi);
leadingCoefficient *= limit;
}
public override double NextDouble()
{
double x = base.NextDouble();
double exponent = -0.5d * Math.Pow((x - Mean) / alpha, 2d);
double result = leadingCoefficient * Math.Pow(Math.E,exponent);
return result;
}
}
Edit:
In case you're not looking for output similar to the distribution histogram that you provided and instead want something more similar to the sigmoid function you drew I have created an alternate version.
Thanks to Ruzihm for pointing this out.
I went ahead and used the CDF for the normal distribution: where erf is defined as the error function: . I added a coefficient of 1.77 to scale the output to keep it within 0d - 1d.
It should produce numbers similar to this:
Here you can find the alternate class:
public class DistributedRandom : Random
{
public double Mean { get; set; } = 0.7d;
private const double xOffset = 1d;
private const double yOffset = 0.88d;
private const double alpha = 0.25d;
private readonly double sqrtOf2Pi = Math.Sqrt(2 * Math.PI);
private readonly double leadingCoefficient;
private const double cdfLimit = 1.77d;
private readonly double sqrt2 = Math.Sqrt(2);
private readonly double sqrtPi = Math.Sqrt(Math.PI);
private readonly double errorFunctionCoefficient;
private readonly double cdfDivisor;
public DistributedRandom()
{
leadingCoefficient = 1d / (alpha * sqrtOf2Pi);
errorFunctionCoefficient = 2d / sqrtPi;
cdfDivisor = alpha * sqrt2;
}
public override double NextDouble()
{
double x = base.NextDouble();
return CDF(x) - yOffset;
}
private double DistributionFunction(double x)
{
double exponent = -0.5d * Math.Pow((x - Mean) / alpha, 2d);
double result = leadingCoefficient * Math.Pow(Math.E, exponent);
return result;
}
private double ErrorFunction(double x)
{
return errorFunctionCoefficient * Math.Pow(Math.E,-Math.Pow(x,2));
}
private double CDF(double x)
{
x = DistributionFunction(x + xOffset)/cdfDivisor;
double result = 0.5d * (1 + ErrorFunction(x));
return cdfLimit * result;
}
}
I came up with a workable solution. This isn't quite as elegant as I was aiming for because it requires 2 random numbers per result, but it definitely fulfills the requirement. Basically it takes one random number, uses another random number that's exponentially curved towards 1, and lerps towards the target.
I wrote it out in python because it was easier for me to visualize the histogram of it:
import math
import random
# Linearly interpolate between a and b by t.
def lerp(a, b, t):
return ((1.0 - t) * a) + (t * b)
# What we want the median value to be.
target = 0.7
# How often we will hit that median value. (0 = uniform distribution, higher = greater chance of hitting median)
strength = 1.0
values = []
for i in range(0, 1000):
# Start with a base float between 0 and 1.
base = random.random()
# Get another float between 0 and 1, that trends towards 1 with a higher strength value.
adjust = random.random()
adjust = 1.0 - math.pow(1.0 - adjust, strength)
# Lerp the base float towards the target by the adjust amount.
value = lerp(base, target, adjust)
values.append(value)
# Graph histogram
import matplotlib.pyplot as plt
import scipy.special as sps
count, bins, ignored = plt.hist(values, 50, density=True)
plt.show()
Target = 0.7, Strength = 1
Target = 0.2, Strength = 1
Target = 0.7, Strength = 3
Target = 0.7, Strength = 0
(This is meant to be uniform distribution - it might look kinda jagged, but I tested and that's just python's random number generator.)
Here is the condition of the task:
For his birthday, Lubomir received an aquarium in the shape of a parallelepiped. Initially, we read from the console in separate rows its dimensions - length, width and height in centimeters. It is necessary to calculate how many liters of water the aquarium will collect, if it is known that a certain percentage of its capacity is occupied by sand, plants, heater and pump.
1 liter = 1 cubic diameter
Input: length, width, height, percentage
Output: liters of water that the aqarium will collect (hold)
Test numbers: (85cm) - length, (75) - width, (47) - height, (17) - percentage;
and the output has to be: 248.68875
using System;
namespace FishTank
{
class Program
{
static void Main(string[] args)
{
int length = int.Parse(Console.ReadLine());
int width = int.Parse(Console.ReadLine());
int hight = int.Parse(Console.ReadLine());
double percent = double.Parse(Console.ReadLine());
double aqariumVol = length * width * hight;
double volInLiters = aqariumVol * 000.1;
double spaceOcu = aqariumVol - (percent / 100.0) * aqariumVol;
double litersNeeded = volInLiters * (1 - spaceOcu);
Console.WriteLine(litersNeeded);
}
}
}
The space occupied is the volume of the tank (your calculation for which I agree with, except you've written 000.1) multiplied by the percentage occupation; your formula for spaceOcu calculates the free space instead
The free space volume is the volume of the tank minus the occupied volume; I've got no idea what your code is trying to calculate with that one. It looks like you're trying to treat spaceOcu as a percentage (between 0 and 1) but spaceOcu is a number of liters, not a percentage, so doing 1 - some_hundreds_of_liters isn't going to work
In essence, the answer the assignment seeks should already be in spaceOcu, but will be some powers of 10 out due to a) the typo in 000.1 and b) the spaceOcu formula not actually using the attempt to convert cubic centimeters to litres
Change your writeline to use spaceOcu, rename it to something sensible and adjust its magnitude (and the remove any unused code before you hand in)
Since the volume is calculated in cubiccentimeters, the volume should be calculated as volInLiters = aquariumVol * 0.001. And then you have to reduce that by the percentage. Use of aquariumVol in the spaceOcu line is very likely wrong, as it compares (=subtracts) apples from oranges.
I've been working on a small project for some days, everything was working fine until I changed my "map" implementation to be the same as in the game (Dofus) I'm based on (it's a little helper for the community).
Basically, I've a grid layout rotated at 45° (see image below), contructed from top left to bottom right. Every cell as an xIndex and zIndex to represent where it is (xIndex ; zIndex) on the image, and I just want to get the distance between two cells, without traveling diagonally.
As I tried to explain on the picture:
GetDistanceBetweenTiles(A, B) should be 3
GetDistanceBetweenTiles(A, C) should be 5
GetDistanceBetweenTiles(B, C) should be 2
I found the "Manhattan distance" which looks like it is what I want, but it's not giving me the values above.
Here is the code:
private int GetDistanceBetweenTiles(MovableObject a, MovableObject b)
{
//int dist = Mathf.Abs(a.xIndex - b.xIndex) + Mathf.Abs(a.zIndex - b.zIndex);
int minX = a.xIndex < b.xIndex ? a.xIndex : b.xIndex;
int maxX = a.xIndex > b.xIndex ? a.xIndex : b.xIndex;
int minZ = a.zIndex < b.zIndex ? a.zIndex : b.zIndex;
int maxZ = a.zIndex > b.zIndex ? a.zIndex : b.zIndex;
int distX = (maxX - minX);
int distZ = (maxZ - minZ);
int dist = Mathf.Abs(maxX - minX) + Mathf.Abs(maxZ - minZ);
print($"Distance between {a.name} and {b.name} is {dist}");
return dist;
}
Any help would be gladly appreciated.
If it can help, here is the project working with the first map implementation I did (but not translated yet).
Let make new coordinates in inclined rows with simple formulae:
row = z/2 - x ("/" for **integer division**)
col = z - row
Now we can just calculate Manhattan distance as
abs(row2 - row1) + abs(col2 - col1)
For your example
x z r c
4, 2 => -3, 5
1, 4 => 1, 4
distance = (1-(-3)) + (5-4) = 4 + 1 = 5
To explain: your grid rotated by 45 degrees:
0 1 2 3 4 5 6 7 8 \column
40|41 row -4
30|31|42|43 row -3
20|21|32|33|44|45 row -2
10|11|22|23|34|35|46|47 row -1
00|01|12|13|24|15|36|37|48 row 0
02|03|14|15|26|27|38 row 1
04|05|16|17|28 row 2
06|07|18 row 3
The "No-Maths" solution
I maybe have a workaround solution for you. I'm kind of a lazy person and very bad in maths ... so I usually let Unity do the maths for me in situations like yours ;)
For that you would need one dedicated GameObject that is rotated in the way that it represents the grid "rotation" so 0,45,0.
Then - since your tiles move always in steps of exactly 1 just in the rotated coordinate system - you could inetad of using an index based distance rather directly compare the absolute positions using Transform.InverseTransformPoint in order to get the positions relative to that rotated object.
InverseTransformPoint retuns as said the given world position in the local space of the used transform so that if the object was originally placed at e.g. x=1, z=1 in our rotated local space it will have the position z=1.1414..., x=0.
I simply attached this component to my rotated object .. actually I totate in Awake just to be sure ;)
public class PositionsManager : MonoBehaviour
{
// I know .. singleton pattern .. buuu
// but that's the fastest way to prototype ;)
public static PositionsManager Singleton;
private void Awake()
{
// just for making sure this object is at world origin
transform.position = Vector3.zero;
// rotate the object liek you need it
// possible that in your case you rather wanted -45°
transform.eulerAngles = new Vector3(0, 45, 0);
// since InverseTransformPoint is affacted by scale
// just make sure this object has the default scale
transform.localScale = Vector3.one;
// set the singleton so we can easily access this reference
Singleton = this;
}
public Vector2Int GetDistance(Transform from, Transform to)
{
var localPosFrom = transform.InverseTransformPoint(from.position);
var localPosTo = transform.InverseTransformPoint(to.position);
// Now you can simply get the actual position distance and return
// them as vector2 so you can even still see the components
// seperately
var difference = localPosTo - localPosFrom;
// since you are using X-Z not X-Y you have to convert the vector "manually"
return new Vector2Int(Mathf.RoundToInt(difference.x), Mathf.RoundToInt(difference.z));
}
public int GetAbsoluteDistance(Transform from, Trasnform to)
{
var difference = GetDistance(from, to);
return Mathf.Abs(difference.x) + Mathf.Abs(difference.y);
}
}
Now when you need to get the absolute distance you could simply do
var difference = PositionsManager.Singleton.GetDistance(objectA.transform, objectB.transform);
var absoluteDistance = PositionsManager.Singleton.GetAbsoluteDistance(objectA.transform, objectB.transform);
Little Demo (used a chess board drawer since I had that ^^)
The maths solution
It just came to me while writing the upper explenation:
You already know your steps between the tiles: It is allways Mathf.Sqrt(2)!
So again you could simply use the absolute positions in your world and compare them like
private float Sqrt2;
private void Awake()
{
Sqrt2 = Mathf.Sqrt(2);
}
...
// devide the actual difference by Sqrt(2)
var difference = (objectA.position - objectB.position) / Mathf.Sqrt(2);
// again set the Vector2 manually since we use Z not Y
// This step is optional if you anyway aren't interrested in the Vector2
// distance .. jsut added it for completeness
// You might need the rounding part though
var fixedDifference = new Vector2Int(Mathf.RoundToInt(difference.x), Mathf.RoundToInt(difference.z));
// get the absolute difference
var absoluteDistance = Mathf.Abs(fixedDifference.x) + Mathf.Abs(fixedDifference.y);
...
still completely without having to deal with the indexes at all.
I'd like to take a range of four random integers between 1-64, and generate a random value somewhere within the range but leaning towards a more weighted average.
The practical application is that you take a pixel, grab the 4 surrounding pixels and use those plus the current pixel to generate a value that can then be used as the base weight for a Gaussian random number generator. So you have a pixel of 10 brightness, surrounded by 8,8,9,9. Add them all up, average out to 8.8. 8.8 is then the weight for the Gaussian random number generator. So you have a random result within a range, but close to the average brightness which is 8.8, and still with some element of randomness.
The issue comes when you have wide variations because of random noise.
To give a pseudo example of how I would like it to work..
Input = [1,16,19,21]
The average of this is 14.25, but that has too much movement because of the "1" bringing the average way down. The average of this should be more around the 18 mark, because more of the numbers are clustered around that area.
I would like to see a random result coming out that is between 1 and 64, but heavily weighted between 15 and 22, with a lower possibility of it being towards the 1(Because it is still within the range as a whole) and a much lower possibility of it being over 22(Because that is completely outside of the range).
Additional The purpose of this is to generate a galactic map. I have got to the point where I have a good set of galaxy shaped data, giving me the rough density of each sector on the map. Now I need to generate specific sets of data and generate exact numbers of stars in each sector. Taking the average of the 4 surrounding sectors and using that to work out how "dense" this sector should be is the main purpose. The main thing I want to avoid is that sectors bordering an empty region of space do not also end up mostly empty, as this does not fit with general observations of galaxies.
You could imagine that the four numbers are points in a line, the x axis. Around that points there is a sphere of probability with a radius of 64, with the probability more concentrated in the proximity of the points rather than on the edges. Pick randomly one of the four points, calculate a random point inside the sphere of that number and take its x coordinate. Repeat if it is out of the range 1..64.
using System;
using System.Collections.Generic;
namespace ProbabilityDistribution1
{
class Program
{
// This derived class converts the uniformly distributed random
// numbers generated by base.Sample( ) to another distribution.
class RandomProportional : Random
{
// The Sample method generates a distribution more concentrated around the 0, in the range [0.0, 1.0].
protected override double Sample()
{
double BSample = base.Sample();
const double concentrationAroundInputs = 5;//more concentrated when greater
double result = Math.Pow(BSample, concentrationAroundInputs);
return result;
}
}
static double XCoordinateOfRandomUnitInsideSphere(Random aRandom)
{
//Even with uniform distribution the probability of exiting is greater than 0.5 on each iteration
while (true)
{
double x = aRandom.NextDouble();
double y = aRandom.NextDouble();
double z = aRandom.NextDouble();
if ((x * x + y * y + z * z) < 1) //inside the sphere
{
return x;
}
}
}
static void TestDistribution()
{
double[] Input = { 1, 16, 19, 21 };
List<int> sampleValues = new List<int>();
Random aRandom = new Random();
RandomProportional aRandomProportinal = new RandomProportional();
for (int i = 0; i < 100; i++)
{
int value = 0;
do
{
int indexChosen = aRandom.Next(4);
double xCoordinate = XCoordinateOfRandomUnitInsideSphere(aRandomProportinal);
if (aRandom.Next(2)==0)
{
xCoordinate = -xCoordinate;
}
double xRandomResult = xCoordinate * 64;
value = (int)(Input[indexChosen] + xRandomResult);
} while (value < 1 || value > 64);
sampleValues.Add((int)value);
}
sampleValues.Sort();
Console.WriteLine();
foreach (int i in sampleValues)
{
Console.Write(" {0:00} ", i);
}
Console.WriteLine();
}
static void Main(string[] args)
{
TestDistribution();
Console.WriteLine("end");
Console.ReadLine();
}
}
}