I'm creating a Game as a school project and thought it would be a good idea to use colormapping for level creation, however the method I'm using is very slow for some reason.
public List<Entity> LoadLevel(Level level)
{
List<Entity> ents = new List<Entity>();
Color[] clrs = new Color[level.getColorMap.Height*level.getColorMap.Width];
level.getColorMap.GetData(clrs);
for (int x = 0; x < level.getColorMap.Width; x++)
{
for (int y = 0; y < level.getColorMap.Height; y++)
{
if (clrs[x + y * level.getColorMap.Width] == new Color(0, 0, 0))
{
ents.Add(new Terrain(new Vector2(x, y)));
ents.Last().Animation.setImageIndex(0);
ents.Last().Animation.Play();
}
if (clrs[x + y * level.getColorMap.Width] == new Color(6, 6, 6))
{
ents.Add(new Terrain(new Vector2(x, y)));
ents.Last().Animation.setImageIndex(6);
ents.Last().Animation.setSpeed(69);
ents.Last().Animation.Play();
}
if (clrs[x + y * level.getColorMap.Width] == new Color(9, 9, 9))
{
ents.Add(new Terrain(new Vector2(x, y)));
ents.Last().Animation.setImageIndex(9);
ents.Last().Animation.setSpeed(69);
ents.Last().Animation.Play();
}
}
}
return ents;
}
I call this function in LoadContent() and it takes about half a minute to execute, why is it so slow?
(Posted on behalf of the OP).
The problem was actually in the constructor of the terrainobject, the function for creating its collider was appearantly very poorly optimized, thanks for the help!
I have some notes to the coding style. Most probably it is not cause of your problems, but better know sooner than later. Generally speaking, you should avoid doing duplicated and unnecessary work. I would not even call it optimization, I would call it a rule. It should be the way you always code.
What if level.getColorMap takes a while? You call it over and over again even when you do not need to do that. You should generally not rely on the fact, that property is cheap. Call it once and remember its result.
ents.Last() is quite fast, but not for free. Do not call it if you do not need it. Build new terrain and remember pointer to it.
The new Color(0, 0, 0) in every loop is bad. Do not take loop hoisting as granted. Most probably you would construct 12000000 color object even when You actually need three.
Your code also has a lot of duplicity. Generally, think twice before you copy and paste code. DRY
The loop order is bad. You jump from row to row ruining the cache locality. You should process the whole row and then go to next.
Avoid multiplication when You need just addition.
Example of improvement:
public void AddTerrain(List<Entity> ents, int selector, int x, int y)
{
Terrain newT = new Terrain(new Vector2(x, y));
ents.Add(newT);
var animation = newT.Animation;
animation.setImageIndex(selector);
if (selector > 0)
{
animation.setSpeed(69);
}
animation.Play();
}
public List<Entity> LoadLevel(Level level)
{
List<Entity> ents = new List<Entity>();
var colorMap = level.getColorMap;
int colorMapWidth = colorMap.Width;
int colorMapHeight = colorMap.Height;
Color[] clrs = new Color[colorMapWidth * colorMapHeight];
Color[] colors = new Color[] { new Color(0, 0, 0), new Color(6, 6, 6), new Color(9, 9, 9) };
colorMap.GetData(clrs);
int ci = 0;
for (int y = 0; y < colorMapHeight; y++)
{
for (int x = 0; x < colorMapWidth; x++)
{
Color c = clrs[ci++];
for (int i = 0; i < colors.Length; ++i)
{
if (c == colors[i])
{
AddTerrain(ents, c.R, x, y);
break;
}
}
}
}
return ents;
}
Related
I did this so I can know how many asterisks appear in the adjacent squares.
private int CheckAdjacents(Coordinate cord)
{
List<Coordinate> coordinates = new List<Coordinate>()
{
new Coordinate(cord.X - 1, cord.Y - 1),
new Coordinate(cord.X, cord.Y-1),
new Coordinate(cord.X + 1, cord.Y -1),
new Coordinate(cord.X + 1, cord.Y),
new Coordinate(cord.X + 1, cord.Y + 1),
new Coordinate(cord.X, cord.Y + 1),
new Coordinate(cord.X - 1, cord.Y + 1),
new Coordinate(cord.X - 1, cord.Y)
};
return coordinates.Count(x => _matrix.At(x).Value == '*');
}
The thing here is that obviously it returns an exception because is checking indexes that wouldn't be checked. What'd be the best way to skip those kind of indexes? Using a try/catch could be kinda tricky? Thanks!
EDIT:
Matrix class
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace MineSweeper
{
public record Coordinate (int X, int Y);
public record Size(int M, int N);
public class Matrix
{
private readonly Size _size;
private readonly Cell[,] _matrix;
private const char InitValue = '.';
public Matrix(Size size)
{
_size = size;
_matrix = new Cell[size.M, size.N];
Initialize();
}
private void Initialize()
{
for (int m = 0; m < _size.M; m++)
for (int n = 0; n < _size.N; n++)
_matrix[m, n] = new Cell(InitValue);
}
public Size GetSize()
=> _size;
public Cell At(Coordinate coordinate)
=> _matrix[coordinate.X, coordinate.Y];
public void SetMine(Coordinate coordinate)
=> _matrix[coordinate.X, coordinate.Y] = new Cell('*');
public void ChangeValue(Coordinate coordinate, char value)
=> _matrix[coordinate.X, coordinate.Y] = new Cell(value);
public Cell Open(Coordinate coordinate)
=> _matrix[coordinate.X, coordinate.Y];
public IEnumerable ToList()
=> _matrix.Cast<Cell>().ToList();
private string CellsAsString()
=> string.Concat(_matrix.OfType<Cell>().Select(c => c.Value));
public override bool Equals(object other)
=> this.CellsAsString().Equals((other as Matrix)?.CellsAsString());
public override int GetHashCode()
=> this.CellsAsString().GetHashCode();
}
}
EDIT(2):
PrintMatrix and Open methods from the main class.
public void Open(Coordinate coordinate)
{
if (_matrix.At(coordinate).Value == '*')
HasLose = true;
int numOfMines = _matrix.NeighborsOf(coordinate).Count(cell => cell.Value == '*');
_showedMatrix.ChangeValue(coordinate, char.Parse(numOfMines.ToString()));
HasWin = PlayerHasWin();
}
public String PrintMatrix()
{
string temp = "";
for (int x = 0; x < _size.M; x++)
{
for (int y = 0; y < _size.N; y++)
{
temp += _showedMatrix.At(new Coordinate(x, y)).Value;
}
temp += '\n';
}
return temp;
}
Notice that I'm using a showedMatrix which is another matrix with cells and its value for each one is a simple .. I'm using this new matrix so I can change its value and printing it.
These are the two tests that fail.
[Fact]
public void CellIsOpenWithoutAMineButWithOneMineAdjacent()
{
string printExpected = "1...\n....\n....\n....\n";
Matrix matrix = new Matrix(new(4, 4));
matrix.SetMine(new(0,1));
MineSweeper mineSweeper = new(matrix, 2);
mineSweeper.Open(new(0,0));
mineSweeper.PrintMatrix().Should().Be(printExpected);
}
[Fact]
public void CellIsOpenWithoutAMineButWithTwoMineAdjacent()
{
string printExpected = "2...\n....\n....\n....\n";
Matrix matrix = new Matrix(new(4, 4));
matrix.SetMine(new(0,1));
matrix.SetMine(new(1,0));
MineSweeper mineSweeper = new(matrix, 2);
mineSweeper.Open(new(0,0));
mineSweeper.PrintMatrix().Should().Be(printExpected);
}
Since I'm aware that my main class for these tests is putting 2 random mines plus the mines I'm putting by myself with the SetMine() method, I executed these tests several times to make sure that it was failing. The conclusion was that "2...\n....\n....\n....\n"; for some reason is always a 0 instead of a 2, or a 1.
I'd suggest an iterator method on the matrix itself, that took care of checking the bounds before returning the cells, something like this:
IEnumerable<Cell> NeighborsOf(Coordinate coord)
{
// if coord is not in bounds of the matrix, throw an argument exception
for (int x = Math.Max(coord.X - 1, 0); x <= Math.Min(coord.X + 1, _size.M - 1); x++)
{
for (int y = Math.Max(coord.Y - 1, 0); y <= Math.Min(coord.Y + 1, _size.N - 1); y++)
{
if ((x,y) == (coord.X, coord.Y)) continue;
yield return At(new Coordinate(x, y));
}
}
}
The for loops define the "3x3 window" of coordinates (central cell +/- 1) describing a maximum block of 9 cells, that has its edges limited by the Math.Min and Math.Max calls. Then, the central cell itself is skipped by the if check, resulting only on adjacent cells being returned.
Then, to count the asterisks, you just leverage this method:
_matrix.NeighborsOf(coordinate).Count(cell => cell.Value == '*');
Relying on exceptions to ignore the out-of-bounds coordinates would be considered a bad practice (as it would be an use of the "exception as control flow" anti-pattern) and also fairly poor performance-wise.
The neighbor idea also allows you to parameterize the "size" of the neighbor region, if that's useful to you.
You could add a method to the Matrix class, that returns true if both x and y are within their allowed ranges (0 <= x < M, 0 <= y < N) for a given Coordinate:
public bool IsValid(Coordinate coordinate) =>
0 <= coordinate.X && coordinate.X < _size.M &&
0 <= coordinate.Y && coordinate.Y < _size.N;
Then you can count only the valid cells:
return coordinates.Count(x => _matrix.IsValid(x) && _matrix.At(x).Value == '*');
Additionally, it makes sense to use this method for validation purpose in all other public methods of the Matrix class accepting Coordinate as an argument.
Developing a 2D tile-based "boardgame" I'm struggling with the restriction I have to make, when a player rolls the dice(Move 5 tiles if you land a 5 etc.)
I'm trying to use the following logic:
Start on starting point
Check the position to the sides, above and below
Check if the neighbour tiles are walkable, if they are, change them to reachable
Go to neighbour tile, repeat
I've been looking on A* and D* pathing, but it's a new subject to me and they seem more focused on getting from point A to B, not "reach" which is what I need.
How do I do this through code?
I created a 2D array from an array which was holding my tile(I need a normal array of the tilemap for another purpose):
for(int i = 0; i < 27; i++)
{
for(int j = 0; j < 33; j++)
{
tileMap[i, j] = goTile[i * 33 + j];
}
}
I now use the tileMap as my positiong factor, e.g. my players current position is tileMap[2,4].
I then tried to develop a function:
void pathFinding(Vector2 playerPosition, int diceNumber)
{
GameObject currentPos = tileMap[(int)playerPosition.x, (int)playerPosition.y];
for (int i = 0; i < diceNumber; i++) {
if (tileMap[(int)playerPosition.x + 1, (int)playerPosition.y].tag == "walkableGrid")
{
tileMap[(int)playerPosition.x + 1, (int)playerPosition.y].gameObject.tag = "reachable";
playerPosition.x++;
}
if (tileMap[(int)playerPosition.x - 1, (int)playerPosition.y].tag == "walkableGrid")
{
playerPosition.x--;
}
if (tileMap[(int)playerPosition.x, (int)playerPosition.y + 1].tag == "walkableGrid")
{
playerPosition.y++;
}
if (tileMap[(int)playerPosition.x, (int)playerPosition.y - 1].tag == "walkableGrid")
{
playerPosition.y--;
}
}
}
But as finishing this (if it even would work), would require MANY lines of code, and I believe there's a swifter method using a nested for loop maybe?
//I have now edited the code to better reflect your real data
public void ShowMoves(Vector2 playerPosition, int diceNumber, bool[] blocks)
{
int x = (int)playerPosition.x;
int y = (int)playerPosition.y;
if(tileMap.GetUpperBound(0) < x + 1)
{
if(tileMap[x + 1, y].tag == "walkableGrid" && blocks[0])
{
/*Light up the tile*/
if(diceNumber > 0)
ShowMoves(new Vector2(x + 1, y), diceNumber - 1, new bool[] { x != tileMap.GetUpperBound(0), false, y != tileMap.GetUpperBound(1), y != 0 });
}
}
if(x - 1 >= 0)
{
if(tileMap[x - 1, y].tag == "walkableGrid" && blocks[1])
{
/*Light up the tile*/
if(diceNumber > 0)
ShowMoves(new Vector2(x - 1, y), diceNumber - 1, new bool[] { false, x != 0, y != tileMap.GetUpperBound(1), y != 0 });
}
}
if(tileMap.GetUpperBound(1) < y + 1)
{
if(tileMap[x, y + 1].tag == "walkableGrid" && blocks[2])
{
/*Light up the tile*/
if(diceNumber > 0)
ShowMoves(new Vector2(x, y + 1), diceNumber - 1, new bool[] { x != tileMap.GetUpperBound(0), x != 0, y != tileMap.GetUpperBound(1), false });
}
}
if(y - 1 >= 0)
{
if(tileMap[x, y - 1].tag == "walkableGrid" && blocks[3])
{
/*Light up the tile*/
if(diceNumber > 0)
ShowMoves(new Vector2(x, y - 1), diceNumber - 1, new bool[] { x != tileMap.GetUpperBound(0), x != 0, false, y != 0 });
}
}
}
This code might not compile, but it's an example to help you along - it will loop until there are no available moves, and exhaust every option. It will also not go back on itself due to the blocks boolean array. The input format would be the position they're at two ints, one for x and one for y, the tiles available, the number of moves left in their roll, and the block available from the beginning (always new bool[] {true, true, true, true}
Be careful, there may be errors in my code, I wrote it in SO and have no clue how well it runs, if it runs at all or anything. Even if it does not, it should be a good starting point for you to create your logic and code it all
EDIT: Code has been changed so that it better fits how your code looks and the data types it uses
To avoid always calling the method by inputting a blocks variable of new bool[] {true, true, true, true}; you can make it an optional operator by making this the method parameters
public void ShowMoves(Vector2 playerPosition, int diceNumber, bool[] blocks = new bool[] {true, true, true, true})
I'm trying to randomly generate blocks on a flat map and make it so that they don't overlap each other.
I have made a matrix (c# array) of the size of the map (500x500), the blocks have a scale between 1 and 5.
The code works but if a generated block overlaps another one, it is destroyed and not regenerated somewhere else.
Only around 80 of the 1000 blocks I try to generate don't overlap another block.
Here is a picture of the map with around 80 blocks generated, the green squares are blocks
void generateElement(int ratio, int minScale, int maxScale, GameObject g) {
bool elementFound = false;
for (int i = 0; i < ratio * generationDefault; i++) {
GameObject el;
// Randomly generate block size and position
int size = Random.Range(minScale, maxScale + 1);
int x = Random.Range(0, mapSizex + 1 - size);
int y = Random.Range(0, mapSizey + 1 - size);
// Check if there is already an element
for (int j = x; j < x + size; j++)
for (int k = y; k < y + size; k++)
if (map[j][k] != null)
elementFound = true;
if (elementFound)
continue;
else {
el = (GameObject)Instantiate(g, new Vector3(x + (float)size / 2, (float)size / 2, y + (float)size / 2), Quaternion.Euler(0, 0, 0));
el.transform.localScale *= size;
}
// Create element on map array
for (int j = x; j < x + size; j++)
for (int k = y; k < y + size; k++)
if (map[j][k] == null) {
map[j][k] = el.GetComponent<ObjectInterface>();
}
}
}
I thought of 3 possible fixes
I should set the size of the block depending of the place it has.
I should use another randomization algorithm.
I'm not doing this right.
What do you think is the best idea ?
UPDATE
I got the code working much better. I now try to instantiate the blocks multiple times if needed (maximum 5 for the moment) and I fixed the bugs. If there are already many elements on the map, they will not always be instantiated and that's what I wanted, I just have to find the right amount of times it will try to instantiate the block.
I tried instantiating 1280 elements on a 500x500 map. It takes only about 1.5 second and it instantiated 1278/1280 blocks (99.843%).
void generateElement(int ratio, int minScale, int maxScale, GameObject g) {
bool elementFound = false;
int cnt = 0;
// Generate every block
for (int i = 0; i < ratio * generationDefault; i++) {
GameObject el = null;
// Randomly generate block size and position
int size, x, y, tryCnt = 0;
// Try maximum 5 times to generate the block
do {
elementFound = false;
// Randomly set block size and position
size = Random.Range(minScale, maxScale + 1);
x = Random.Range(0, mapSizex + 1 - size);
y = Random.Range(0, mapSizey + 1 - size);
// Check if there is already an element
for (int j = x; j < x + size; j++)
for (int k = y; k < y + size; k++)
if (map[j][k] != null)
elementFound = true;
tryCnt++;
} while (elementFound && tryCnt < 5);
if (tryCnt >= 5 && elementFound) continue;
// Instantiate the block
el = (GameObject)Instantiate(g, new Vector3(x + (float)size / 2, (float)size / 2, y + (float)size / 2), Quaternion.Euler(0, 0, 0));
el.transform.localScale *= size;
// Create element on map array
for (int j = x; j < x + size; j++)
for (int k = y; k < y + size; k++)
if (map[j][k] == null) {
map[j][k] = el.GetComponent<ObjectInterface>();
}
cnt++;
}
print("Instantiated " + cnt + "/" + ratio * generationDefault);
}
This is incredibly difficult to do well.
Here's a quick solution you'll maybe like ... depending on your scene.
actualWidth = 500 //or whatever. assume here is square
// your blocks are up to 5 size
chunkWidth = actualWidth / 5
// it goes without saying, everything here is an int
kChunks = chunkWidth*chunkWidth
List<int> shuf = Enumerable.Range(1,kChunks).OrderBy(r=>Random.value).ToList();
howManyWanted = 1000
shuf = shuf.Take(howManyWanted)
foreach( i in shuf )
x = i % actualWidth
y = i / actualWidth
make block at x y
put block in list allBlocks
HOWEVER ............
...... you'll see that this looks kind of "regular", so do this:
Just randomly perturb all the blocks. Remember, video game programming is about clever tricks!
Ideally, you have to start from the middle and work your way out; in any event you can't just do them in a line. Shuffling is OK. So, do this ..
harmonic = 3 //for example. TRY DIFFERENT VALUES
function rh = Random.Range(1,harmonic) (that's 1 not 0)
function rhPosNeg
n = rh
n = either +n or -n
return n
function onePerturbation
{
allBlocks = allBlocks.OrderBy(r => Random.value) //essential
foreach b in allBlocks
newPotentialPosition = Vector2(rhPosNeg,rhPosNeg)
possible = your function to check if it is possible
to have a block at newPotentialPosition,
however be careful not to check "yourself"
if possible, move block to newPotentialPosition
}
The simplest approach is just run onePerturbation, say, three times. Have a look at it between each run. Also try different values of the harmonic tuning factor.
There are many ways to perturb fields of differently-sized blocks, above is a KISS solution that hopefully looks good for your situation.
Coding note...
How to get sets of unique random numbers.
Just to explain this line of code...
List<int> shuf = Enumerable.Range(1,kChunks).OrderBy(r=>Random.value).ToList();
If you are new to coding: say you want to do this: "get a hundred random numbers, from 1 to million, but with no repeats".
Fortunately, this is a very well known problem with a very simple solution.
The way you get numbers with no repeats, is simply shuffle all the numbers, and then take how many you want off the top.
For example, say you need a random couple of numbers from 1-10 but with no repeats.
So, here's the numbers 1-10 shuffled: 3,8,6,1,2,7,10,9,4,5
Simply take what you need off the front: so, 3, 8, 6 etc.
So to make an example let's say you want twelve numbers, no repeats, from 1 through 75. So the first problem is, you want a List with all the numbers up to 75, but shuffled. In fact you do that like this ..
List<int> shuf = Enumerable.Range(1,75).OrderBy(r=>Random.value).ToList();
So that list is 75 items long. You can check it by saying foreach(int r in shuf) Debug.Log(r);. Next in the example you only want 12 of those numbers. Fortunately there's a List call that does this:
shuf = shuf.Take(12)
So, that's it - you now have 12 numbers, no repeats, all random between 1 and 75. Again you can check with foreach(int r in shuf) Debug.Log(r);
In short, when you want "n" numbers, no repeats, between 1 and Max, all you have to so is this:
List<int> shuf = Enumerable.Range(1,Max).OrderBy(r=>Random.value).ToList();
shuf = shuf.Take(n);
et voilĂ , you can check the result with foreach(int r in shuf) Debug.Log(r);
I just explain this at length because the question is often asked "how to get random numbers that are unique". This is an "age-old" programming trick and the answer is simply that you shuffle an array of all the integers involved.
Interestingly, if you google this question ("how to get random numbers that are unique") it's one of those rare occasions where google is not much help, because: whenever this question is asked, you get a plethora of keen new programmers (who have not heard the simple trick to do it properly!!) writing out huge long complicated ideas, leading to further confusion and complication.
So that's how you make random numbers with no repeats, fortunately it is trivial.
if (elementFound) continue; will skip out this current loop iteration. You need to wrap the int x=Random..; int y=Random()..; part in a while loop with the condition being while(/* position x/y already occupued*/) { /* generate new valid point */} like this for example:
void generateElement(int ratio, int minScale, int maxScale, GameObject g) {
for (int i = 0; i < ratio * generationDefault; i++) {
GameObject el;
// Randomly generate block size and position
bool elementFound = false;
int size, x, y;
do
{
elementFound = false;
size = Random.Range(minScale, maxScale + 1);
x = Random.Range(0, mapSizex + 1 - size);
y = Random.Range(0, mapSizey + 1 - size);
// Check if there is already an element
for (int j = x; j < x + size; j++)
for (int k = y; k < y + size; k++)
if (map[j][k] != null)
elementFound = true;
} while(elementFound);
el = (GameObject)Instantiate(g, new Vector3(x + (float)size / 2, (float)size / 2, y + (float)size / 2), Quaternion.Euler(0, 0, 0));
el.transform.localScale *= size;
// Create element on map array
for (int j = x; j < x + size; j++)
for (int k = y; k < y + size; k++)
if (map[j][k] == null) {
map[j][k] = el.GetComponent<ObjectInterface>();
}
}
}
You shouldn't be getting that many collisions.
Assuming your blocks were ALL 5 units wide and you're trying to fit them into a grid of 500,500 you would have 100*100 spaces for them at minimum, which gives 10,000 spaces into which to fit 1,000 blocks.
Try playing around with this code:
using System;
using System.Collections.Generic;
namespace ConsoleApplication1
{
class Program
{
static void Main()
{
var result = PlaceNonOverlappingBlocks(1000, 5, 500, 500);
}
static List<Block> PlaceNonOverlappingBlocks(int count, int maxBlockSize, int mapX, int mapY)
{
var map = new bool[mapY, mapX];
var rng = new Random();
var result = new List<Block>(count);
int collisions = 0;
while (count > 0)
{
int size = rng.Next(1, maxBlockSize + 1);
int x = rng.Next(0, mapX - size);
int y = rng.Next(0, mapY - size);
if (fits(map, x, y, size))
{
result.Add(new Block(x, y, size));
addToMap(map, x, y, size);
--count;
}
else
{
if (++collisions> 100000)
throw new InvalidOperationException("Hell has frozen over");
}
}
// This is just for diagnostics, and can be removed.
Console.WriteLine($"There were {collisions} collisions.");
return result;
}
static void addToMap(bool[,] map, int px, int py, int size)
{
for (int x = px; x < px+size; ++x)
for (int y = py; y < py + size; ++y)
map[y, x] = true;
}
static bool fits(bool[,] map, int px, int py, int size)
{
for (int x = px; x < px + size; ++x)
for (int y = py; y < py + size; ++y)
if (map[y, x])
return false;
return true;
}
internal class Block
{
public int X { get; }
public int Y { get; }
public int Size { get; }
public Block(int x, int y, int size)
{
X = x;
Y = y;
Size = size;
}
}
}
}
I am trying to quantize an image into 10 colors in C# and I have a problem in draw the quantized image, I have made the mapping table and it is correct, I have made a copy of the original image and I am changing the color of pixels based on the mapping table , I am using the below code:
bm = new Bitmap(pictureBox1.Image);
Dictionary<Color, int> histo = new Dictionary<Color, int>();
for (int x = 0; x < bm.Size.Width; x++)
for (int y = 0; y < bm.Size.Height; y++)
{
Color c = bm.GetPixel(x, y);
if (histo.ContainsKey(c))
histo[c] = histo[c] + 1;
else
histo.Add(c, 1);
}
var result1 = histo.OrderByDescending(a => a.Value);
int ind = 0;
List<Color> mostusedcolor = new List<Color>();
foreach (var entry in result1)
{
if (ind < 10)
{
mostusedcolor.Add(entry.Key);
ind++;
}
else
break;
}
Double temp_red,temp_green,temp_blue,temp;
Dictionary<Color, Double> dist = new Dictionary<Color, double>();
Dictionary<Color, Color> mapping = new Dictionary<Color, Color>();
foreach (var p in result1)
{
dist.Clear();
foreach (Color pp in mostusedcolor)
{
temp_red = Math.Pow((Convert.ToDouble(p.Key.R) - Convert.ToDouble(pp.R)), 2.0);
temp_green = Math.Pow((Convert.ToDouble(p.Key.G) - Convert.ToDouble(pp.G)), 2.0);
temp_blue = Math.Pow((Convert.ToDouble(p.Key.B) - Convert.ToDouble(pp.B)), 2.0);
temp = Math.Sqrt((temp_red + temp_green + temp_blue));
dist.Add(pp, temp);
}
var min = dist.OrderBy(k=>k.Value).FirstOrDefault();
mapping.Add(p.Key, min.Key);
}
Bitmap copy = new Bitmap(bm);
for (int x = 0; x < copy.Size.Width; x++)
for (int y = 0; y < copy.Size.Height; y++)
{
Color c = copy.GetPixel(x, y);
Boolean flag = false;
foreach (var entry3 in mapping)
{
if (c.R == entry3.Key.R && c.G == entry3.Key.G && c.B == entry3.Key.B)
{
copy.SetPixel(x, y, entry3.Value);
flag = true;
}
if (flag == true)
break;
}
}
pictureBox2.Image=copy;
Your code has two problems:
it is terribly slow
the quantization is not what I would expect.
Here is an original image, the result of your code and what Photoshop does when asked to reduce to 10 colors:
Speeding up the code can be done in two steps:
Get rid of the most obnoxious time wasters
Turn the GetPixel and the SetPixel loops into Lockbits loops.
Here is a solution for step one, that speeds up the code by at least 100x:
Bitmap bm = (Bitmap)Bitmap.FromFile("d:\\ImgA_VGA.png");
pictureBox1.Image = bm;
Dictionary<Color, int> histo = new Dictionary<Color, int>();
for (int x = 0; x < bm.Size.Width; x++)
for (int y = 0; y < bm.Size.Height; y++)
{
Color c = bm.GetPixel(x, y); // **1**
if (histo.ContainsKey(c)) histo[c] = histo[c] + 1;
else histo.Add(c, 1);
}
var result1 = histo.OrderByDescending(a => a.Value);
int number = 10;
var mostusedcolor = result1.Select(x => x.Key).Take(number).ToList();
Double temp;
Dictionary<Color, Double> dist = new Dictionary<Color, double>();
Dictionary<Color, Color> mapping = new Dictionary<Color, Color>();
foreach (var p in result1)
{
dist.Clear();
foreach (Color pp in mostusedcolor)
{
temp = Math.Abs(p.Key.R - pp.R) +
Math.Abs(p.Key.R - pp.R) +
Math.Abs(p.Key.R - pp.R);
dist.Add(pp, temp);
}
var min = dist.OrderBy(k => k.Value).FirstOrDefault();
mapping.Add(p.Key, min.Key);
}
Bitmap copy = new Bitmap(bm);
for (int x = 0; x < copy.Size.Width; x++)
for (int y = 0; y < copy.Size.Height; y++)
{
Color c = copy.GetPixel(x, y); // **2**
copy.SetPixel(x, y, mapping[c]);
}
pictureBox2.Image = copy;
Note that there is no need to calculate the distances with the full force of Pythagoras if all we want is to order the colors. The Manhattan distance will do just fine.
Also note that we already have the lookup dictionary mapping, which contains every color in the image as its key, so we can access the values directly. (This was by far the worst waste of time..)
The test image is processed in ~1s, so I don't even go for the LockBits modifications..
But correcting the quantization is not so simple, I'm afraid and imo goes beyond the scope of a good SO question.
But let's look at what goes wrong: Looking at the result we can see it pretty much at the first glance: There is a lot of sky and all those many many blues pixels have more than 10 hues and so all colors on your top-10 list are blue.
So there are no other hues left for the whole image!
To work around that you best study the common quantization algorithms..
One simplistic approach at repairing the code would be to discard/map together all colors from the most-used-list that are too close to any one of those you already have. But finding the best minimum distance would require soma data analysis..
Update Another very simple way to improve on the code is to mask the real colors by a few of its lower bits to map similar colors together. Picking only 10 colors will still be too few, but the improvement is quite visible, even for this test image:
Color cutOff(Color c, byte mask)
{ return Color.FromArgb(255, c.R & mask, c.G & mask, c.B & mask ); }
Insert this here (1) :
byte mask = (byte)255 << 5 & 0xff; // values of 3-5 worked best
Color c = cutOff(bm.GetPixel(x, y), mask);
and here (2) :
Color c = cutOff(copy.GetPixel(x, y), mask);
And we get:
Still all yellow, orange or brown hues are missing, but a nice improvement with only one extra line..
I'm having another problem in my Bejeweled clone. I want to make Star Gems act like they do in Bejeweled 3, meaning they destroy gems outward from the star gem(the center). So, say the star gem was at (4, 4) in a 10x10 2D array; it would destroy the positions (3, 4), (5, 4), (4, 3) and (4, 5) first, then, say, 10 frames later, destroy (2, 4), (6, 4), (4, 2), and (4, 6), and so on.
Right now I have the StarDestruction() method storing the position of the star gem to a couple of Board-scope variables, and the positions to destroy in a List<Gem>, like so:
Board.starPosX = i;
Board.starPosY = j;
for (int x = 0; x < gems.GetLength(0); x++)
{
moveTimer = 0;
int k = x;
int m = x;
int q = x;
int n = x;
if (i - k < 0) k = 0;
if (i + m > gems.GetLength(0) - 1) m = 0;
if (j - q < 0) q = 0;
if (j + n > gems.GetLength(1) - 1) n = 0;
gemQ.Add(gems[i - k, j]);
gemQ.Add(gems[i + m, j]);
gemQ.Add(gems[i, j - q]);
gemQ.Add(gems[i, j + n]);
}
where gemQ is the List<Gem> and gems is the 2D Gem array.
This is how I currently destroy the gems, in Update():
foreach (Gem g in gemQ)
{
if (timer2 % 12 == 0)
g.KillGem(gems[starPosX, starPosY]);
}
where timer2 is the timer for destroying the gems.
I have a bit simpler code for the original gem destroying, but it didn't seem to work any differently than this version. Here's the simpler code:
for (int x = 0; x < gems.GetLength(0); x++)
{
if (x != i)
{
gems[x, j].KillGem(gems[i, j]);
}
if (x != j)
{
gems[i, x].KillGem(gems[i, j]);
}
}
Any ideas?
Complete edit of my reply, based on our conversation in the comments.
I understand now that:
You want the star gem to destroy all other gems in the same column and same row as the star gem.
You want four gems to be destroyed at a time, with a delay between each four.
The explosion should move outward from the star gem, i.e. destroying the closest gems first.
Your foreach uses the time like this:
Timer % 12 == 0
At the time that is true for one gem, its true for all of them typically. You don't want to stall between destructions either, otherwise the destruction won't get rendered or the game will visibly lag.
The second issue is that even if you did space out the destruction of the gems, you'll likely find that the destruction occurs in a spiral, instead of four at a time.
With these points in mind, you'll need to do this instead:
// The initial destroy gem code
var gemsToDestroy = new List<Gem>();
for (int x = 0; x < gems.GetLength(0); x++)
{
if (x != i)
{
gemsToDestroy.add(gems[x, j]);
}
if (x != j)
{
gemsToDestroy.add(gems[i, x]);
}
}
// You can change your for loop above to achieve this directly, but this is the idea
// We are putting them in order of closest first.
gemsToDestroy = gemsToDestroy.OrderBy(o => o.DistanceFromStar).ToList();
// Your periodic UPDATE code - This is pseudo code but should convey the general idea
// I've been very lazy with my use of LINQ here, you should refactor this solution to
// to remove as many iterations of the list as possible.
if (gemsToDestroy.Any() && timer.Ready)
{
var closestDistance = gemsToDestroy[0].DistanceFromStar;
foreach (var gem in gemsToDestroy.Where(gem => gem.DistanceFromStar == closestDistance))
{
gem.Destroy();
}
// Again you can do this without LINQ, point is I've removed the now destroyed gems from the list
gemsToDestroy = gemsToDestroy.Where(gem => gem.DistanceFromStar != closestDistance).ToList();
timer.Reset(); // So that we wait X time before destroying the next set
}
Don't forget to prevent player input while there are items in the gemsToDestroy list and also to stop the game timer while destroying, so that the player isn't penalised time for playing well.