I have this code that draws the boxes on a Qbert board, how would i figure out how to detect what color blocks are stepped on?
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
namespace QBert
{
public class Map
{
public int[,] board;
public Color[] blockColors = new Color[] { Color.Blue, Color.Green }; //this makes it so it takes one time to step to get to green
Texture2D block;
public Map(Texture2D block) //draws the map of the blocks
{
this.block = block;
board = new int[8, 7]
{
{ 0, 0, 0, 1, 0, 0, 0 },
{ 0, 0, 1, 1, 0, 0, 0 },
{ 0, 0, 1, 1, 1, 0, 0 },
{ 0, 1, 1, 1, 1, 0, 0 },
{ 0, 1, 1, 1, 1, 1, 0 },
{ 1, 1, 1, 1, 1, 1, 0 },
{ 1, 1, 1, 1, 1, 1, 1 },
{ -1, -1, -1, -1, -1, -1, -1 },
}
;
}
public Vector2 GetSquareCoords(int x, int y) //cordinates of the block
{
int ofs = block.Width / 2;
ofs *= y % 2;
return new Vector2(x * block.Width + ofs, y * 96); // 96
}
public Vector2 GetSquareCenter(int x, int y) //method for to jump on the middle of a block
{
Vector2 coords = GetSquareCoords(x, y);
return new Vector2(coords.X + block.Width / 2, coords.Y + 32); //32
}
public Vector2 GetNextSquare(bool down, bool left, Vector2 position) //this is how you jump to a next square
{
// If on even row, right is directly below and left is below and to the left
// If on odd row, left is directly below and right is below and to the right
int next_x = 0, next_y = 0;
int x = (int)position.X;
int y = (int)position.Y;
if (down)
{
next_y = y + 1;
if (left)
{
next_x = x - 1; // -1
}
else
{
next_x = x;
}
}
else
{
next_y = y - 1;
}
if (y % 2 == 0)
{
if (left)
next_x = x - 1;
else
next_x = x;
}
else
{
if (left)
next_x = x;
else
next_x = x + 1; //+1
}
if (next_x < 0)
{
next_x += 1;
}
if (next_x > 6)
{
next_x -= 1;
}
if (next_y < 0)
{
next_y += 1;
}
if (next_y > 7)
{
next_y -= 1;
}
if (board[next_y, next_x] == 0)
{
return new Vector2(x, y);
}
else
{
return new Vector2(next_x, next_y);
}
}
public void Draw(SpriteBatch spriteBatch) //draws the blocks and colors of the block
{
int drawXOffset = 30;
int drawYOffset = 60;
for (int x = 0; x < 7; x++)
for (int y = 0; y < 7; y++)
{
Vector2 coord = GetSquareCoords(x, y);
if (board[y, x] > 0)
spriteBatch.Draw(block, new Rectangle(drawXOffset + (int)coord.X, drawYOffset + (int)coord.Y, block.Width, block.Height), blockColors[board[y, x] - 1]);
}
}
}
}
I am trying to have the code detect the number of blocks drawn so that I know when they are all a certain color.
I need to make it a certain color of a block to end the game.
Right now, i have it starting out as a Blue Block Color then changing to a Green Block, how would i make it detect that if all the green blocks are stepped on that the game ends?
Somewhere in your Update method, you will want something like this:
bool finished = true;
for (int x = 0; x < 7; x++)
{
for (int y = 0; y < 7; y++)
{
if (board != 0 && board != 2) // 2 is green
{
finished = true;
break;
}
}
if (finished)
break;
}
if (finished)
{
// Move to next level
}
I think somthing like this is what you want
public Vector2 GetNextSquare(bool down, bool left, Vector2 position)
{
int x = (int)position.X;
int y = (int)position.Y;
//...other code
if (blockColors[board[next_y, next_x]] == Color.Green)
{
//End
}
else if (board[next_y, next_x] == 0)
{
return new Vector2(x, y);
}
else
{
return new Vector2(next_x, next_y);
}
}
Usually you have some sort of data representing your game field and rendering code simply renders visual representation of the field. Your game code only works with internal field representation (i.e. in your case set of cubes objects with "Color" property).
You definitely can check color on the screen, but it will require significantly more effort.
#Jaview "what do you mean, can you show me an example how i can check?"
Here is an example how to get a pixel in XNA:
ResolveTexture2D backBufferData;
backBufferData = new ResolveTexture2D(
graphics.GraphicsDevice,
graphics.GraphicsDevice.PresentationParameters.BackBufferWidth,
graphics.GraphicsDevice.PresentationParameters.BackBufferHeight,
1,
graphics.GraphicsDevice.PresentationParameters.BackBufferFormat
);
Rectangle sourceRectangle = new Rectangle(Mouse.GetState().X, Mouse.GetState().Y, 1, 1);
Color[] retrievedColor = new Color[1];
graphics.GraphicsDevice.ResolveBackBuffer(backBufferData);
backBufferData.GetData<Color>(
0,
sourceRectangle,
retrievedColor,
0,
1);
selectedColor = retrievedColor[0];
Related
I have a QuadTree but the insert method is not working like I want it to. Right now it only inserts in the first intersecting quad it sees. The aim is that all nodes are inserted in all the quads its intersecting with. For example: when a node is on the border of two quads, it is inserted in both quads. Can somebody help me to get the insert method to where it is inserted in all intersecting quads?
This is my current implementation:
This is the call to insert the nodes:
QuadTree = new QuadTreeNode < Artist > (new Rect(0, 0, Width, Height), 1);
foreach(Artist artist in Artists) {
QuadTree.Insert(artist);
}
This is my QuadTree class:
public interface WithRect {
Rect Rect {
get;
}
}
public class QuadTreeNode < T > where T: WithRect {
Rect bounds;
List < T > contents;
int numberOfNodesInserted = 0;
int max = 4;
int depth = 0;
int maxDepth = 6;
bool divided;
QuadTreeNode < T > TopLeft;
QuadTreeNode < T > TopRight;
QuadTreeNode < T > BottomLeft;
QuadTreeNode < T > BottomRight;
public QuadTreeNode(Rect _bounds, int _depth) {
depth = _depth;
bounds = _bounds;
contents = new List < T > ();
divided = false;
}
public void DivideQuad() {
int newDepth = depth + 1;
double x = bounds.X;
double y = bounds.Y;
double width = bounds.Width;
double height = bounds.Height;
TopLeft = new QuadTreeNode < T > (new Rect(x, y, width / 2, height / 2), newDepth);
TopRight = new QuadTreeNode < T > (new Rect(x + width / 2, y, width / 2, height / 2), newDepth);
BottomLeft = new QuadTreeNode < T > (new Rect(x, y + height / 2, width / 2, height / 2), newDepth);
BottomRight = new QuadTreeNode < T > (new Rect(x + width / 2, y + height / 2, width / 2, height / 2), newDepth);
divided = true;
foreach(T item in contents) {
Insert(item);
}
contents.Clear();
}
public bool Insert(T item) {
if (!bounds.IntersectsWith(item.Rect)) return false;
if (numberOfNodesInserted < max || depth == maxDepth) {
contents.Add(item);
numberOfNodesInserted++;
return true;
}
else {
if (!divided) {
DivideQuad();
}
if (TopLeft.Insert(item)) return true;
else if (TopRight.Insert(item)) return true;
else if (BottomLeft.Insert(item)) return true;
else if (BottomRight.Insert(item)) return true;
else return false;
}
}
public void GetBounds(ref List < Rect > results) {
if (bounds != null) results.Add(bounds);
if (TopLeft != null) TopLeft.GetBounds(ref results);
if (TopRight != null) TopRight.GetBounds(ref results);
if (BottomLeft != null) BottomLeft.GetBounds(ref results);
if (BottomRight != null) BottomRight.GetBounds(ref results);
}
public void Query(T item, ref List < T > collisions, List < Square > squares, int squareWidth, int squareHeight) {
if (item.Rect.IntersectsWith(bounds)) {
if (divided) {
TopLeft.Query(item, ref collisions, squares, squareWidth, squareHeight);
TopRight.Query(item, ref collisions, squares, squareWidth, squareHeight);
BottomLeft.Query(item, ref collisions, squares, squareWidth, squareHeight);
BottomRight.Query(item, ref collisions, squares, squareWidth, squareHeight);
}
for (int i = 0; i < contents.Count; i++) {
if (item.Rect != contents[i].Rect) {
if (item.Rect.IntersectsWith(contents[i].Rect)) {
if (!collisions.Contains(contents[i])) {
collisions.Add(contents[i]);
}
}
}
foreach(Square square in squares) {
if (square.IsVisited) {
if (item.Rect.IntersectsWith(new Rect(square.X * squareWidth, square.Y * squareHeight, squareWidth, squareHeight))) {
if (!collisions.Contains(contents[i])) {
collisions.Add(contents[i]);
}
}
}
}
}
}
}
}
Remove the else from else if to ensure a item is inserted to each of its subnodes.
if (TopLeft.Insert(item)) return true;
else if (TopRight.Insert(item)) return true;
else if (BottomLeft.Insert(item)) return true;
else if (BottomRight.Insert(item)) return true;
else return false;
should be
var result = TopLeft.Insert(item);
result |= TopRight.Insert(item);
result |= BottomLeft.Insert(item);
result |= BottomRight.Insert(item);
return result;
Another problem is
if (numberOfNodesInserted < max || depth == maxDepth)
you should never insert items to the node if it is divided, so it should be:
if (!divided && (numberOfNodesInserted < max || depth == maxDepth))
That is, assuming you are only storing items in the leaf-nodes.
I want to fix OnGUI method inside in a script to a panel. Especially what I want to do is, In free aspect, when I change the size of game screen, also my text which is writen with onGUI changes depending on size of the game screen.
public HumanBodyVisualizer bodyVisualizer;
public GameObject text;
Vector2 scrollPosition = Vector2.zero;
float scrollPosition_y = 30;
private void OnGUI()
{
//Debug.Log(text.transform.position.y);
int subPartsSpacing = 0;
float spacing = 30;
float x = 7 + spacing;
float y = text.transform.position.y;
HumanBodyPart mainBodyPart = bodyVisualizer.BodyData.Body.SubParts[0];
List<HumanBodyPart> nextPartsToRender = new List<HumanBodyPart>(new HumanBodyPart[] { mainBodyPart });
List<HumanBodyPart> allPartsToRender = new List<HumanBodyPart>(new HumanBodyPart[] { mainBodyPart });
scrollPosition = GUI.BeginScrollView(new Rect(7, 68, 236, 426), scrollPosition, new Rect(7, 68, 500, scrollPosition_y));
GUI.backgroundColor = Color.clear;
while (nextPartsToRender.Count > 0)
{
HumanBodyPart currentPart = nextPartsToRender[0];
nextPartsToRender.RemoveAt(0);
if(GUI.Button(new Rect(currentPart.DrawDepth * spacing + x + subPartsSpacing, y, 200, 20), currentPart.EnglishTitle))
{
HumanBodyVisualizer.ShowMode showModeFullBody = HumanBodyVisualizer.ShowMode.Invisible;
bodyVisualizer.ShowBody(showModeFullBody);
List<HumanBodyPart> AllSubPartsAndRoot = new List<HumanBodyPart>();
AllSubPartsAndRoot.Insert(0, currentPart);
allSubPartsOfClickButton(currentPart, AllSubPartsAndRoot, 1);
HumanBodyVisualizer.ShowMode showModeCurrentPart = HumanBodyVisualizer.ShowMode.LowTransparent;
//bodyVisualizer.ShowBodyPart(showModeCurrentPart, currentPart);
for (int i = 0; i < AllSubPartsAndRoot.Count; i++)
{
bodyVisualizer.ShowBodyPart(showModeCurrentPart,AllSubPartsAndRoot[i]);
}
/*
List<String> modelList = currentPart.ModelList;
for(int i = 0; i < modelList.Count; i++)
{
Debug.Log(modelList[i]);
}
*/
}
if (currentPart.SubParts.Count != 0)
{
if (GUI.Button(new Rect(x - spacing + currentPart.DrawDepth * spacing + subPartsSpacing, y, 20, 20), "+"))
{
if (!currentPart.IsExpanded)
{
currentPart.IsExpanded = true;
subPartsSpacing += 20;
}
else
currentPart.IsExpanded = false;
}
if (currentPart.IsExpanded)
{
nextPartsToRender.InsertRange(0, currentPart.SubParts);
allPartsToRender.InsertRange(allPartsToRender.Count - 1, currentPart.SubParts);
scrollPosition_y = allPartsToRender.Count * spacing ;
}
}
y += spacing;
}
// End the scroll view that we began above.
GUI.EndScrollView();
}
private void allSubPartsOfClickButton(HumanBodyPart root, List<HumanBodyPart> AllSubparts,int a)
{
AllSubparts.Insert(a, root);
a++;
for(int i = 0; i < root.SubParts.Count; i++)
{
allSubPartsOfClickButton(root.SubParts[i], AllSubparts, a);
}
}
When I run like that, even I change the size of game screen, OnGUI text(Buttons , labels and scrollview) stay constant on the screen.
I can not use Unity UI. I have to handle this problem with using onGUI.
I'm working on a Marching Cubes implementation in Unity. My code is based on Paul Bourke's code actually with a lot of modifications, but anyway i'm checking if a block at a position is null if it is than a debug texture will be placed on it.
This is my MC script
public class MarchingCubes
{
private World world;
private Chunk chunk;
private List<Vector3> vertices = new List<Vector3> ();
private List<Vector3> normals = new List<Vector3> ();
private Vector3[] ns;
private List<int> triangles = new List<int> ();
private List<Vector2> uvs = new List<Vector2> ();
private Vector3[] positions = new Vector3[8];
private float[] corners = new float[8];
private Vector3i size = new Vector3i (16, 128, 16);
Vector3[] vertlist = new Vector3[12];
private float isolevel = 1f;
private float Corner (Vector3i pos)
{
int x = pos.x;
int y = pos.y;
int z = pos.z;
if (x < size.x && z < size.z) {
return chunk.GetValue (x, y, z);
} else {
int ix = chunk.X, iz = chunk.Z;
int rx = chunk.region.x, rz = chunk.region.z;
if (x >= size.x) {
ix++;
x = 0;
}
if (z >= size.z) {
iz++;
z = 0;
}
return chunk.region.GetChunk (ix, iz).GetValue (x, y, z);
}
}
Block block;
public Mesh MarchChunk (World world, Chunk chunk, Mesh mesh)
{
this.world = world;
this.chunk = chunk;
vertices.Clear ();
triangles.Clear ();
uvs.Clear ();
for (int x = 0; x < size.x; x++) {
for (int y = 1; y < size.y - 2; y++) {
for (int z = 0; z < size.z; z++) {
block = chunk.GetBlock (x, y, z);
int cubeIndex = 0;
for (int i = 0; i < corners.Length; i++) {
corners [i] = Corner (new Vector3i (x, y, z) + offset [i]);
positions [i] = (new Vector3i (x, y, z) + offset [i]).ToVector3 ();
if (corners [i] < isolevel)
cubeIndex |= (1 << i);
}
if (eTable [cubeIndex] == 0)
continue;
for (int i = 0; i < vertlist.Length; i++) {
if ((eTable [cubeIndex] & 1 << i) == 1 << i)
vertlist [i] = LinearInt (positions [eCons [i, 0]], positions [eCons [i, 1]], corners [eCons [i, 0]], corners [eCons [i, 1]]);
}
for (int i = 0; triTable [cubeIndex, i] != -1; i += 3) {
int index = vertices.Count;
vertices.Add (vertlist [triTable [cubeIndex, i]]);
vertices.Add (vertlist [triTable [cubeIndex, i + 1]]);
vertices.Add (vertlist [triTable [cubeIndex, i + 2]]);
float tec = (0.125f);
Vector2 uvBase = block != null ? block.UV : new Vector2 ();
uvs.Add (uvBase);
uvs.Add (uvBase + new Vector2 (0, tec));
uvs.Add (uvBase + new Vector2 (tec, tec));
triangles.Add (index + 0);
triangles.Add (index + 1);
triangles.Add (index + 2);
}
}
}
}
if (mesh == null)
mesh = new Mesh ();
mesh.Clear ();
mesh.vertices = vertices.ToArray ();
mesh.triangles = triangles.ToArray ();
mesh.uv = uvs.ToArray ();
mesh.RecalculateNormals ();
return mesh;
}
bool IsBitSet (int b, int pos)
{
return ((b & pos) == pos);
}
Vector3 LinearInt (Vector3 p1, Vector3 p2, float v1, float v2)
{
Vector3 p;
p.x = p1.x + (isolevel - v1) * (p2.x - p1.x) / (v2 - v1);
p.y = p1.y + (isolevel - v1) * (p2.y - p1.y) / (v2 - v1);
p.z = p1.z + (isolevel - v1) * (p2.z - p1.z) / (v2 - v1);
return p;
}
private static int[,] eCons = new int[12, 2] {
{ 0, 1 },
{ 1, 2 },
{ 2, 3 },
{ 3, 0 },
{ 4, 5 },
{ 5, 6 },
{ 6, 7 },
{ 7, 4 },
{ 0, 4 },
{ 1, 5 },
{ 2, 6 },
{ 3, 7 }
};
private static Vector3i[] offset = new Vector3i[8] {
new Vector3i (0, 0, 1),
new Vector3i (1, 0, 1),
new Vector3i (1, 0, 0),
new Vector3i (0, 0, 0),
new Vector3i (0, 1, 1),
new Vector3i (1, 1, 1),
new Vector3i (1, 1, 0),
new Vector3i (0, 1, 0)
};
}
I didn't put the tables in the sample, because they are the same as the ones in Bourke's code.
EDIT:
What I figured out yet is that the cell's value at the blue triangles are 0 so they don't have to be triangulated, but the cell's value under them is 1 and because of this a top triangle is created to complete the mesh.
i have some issues with radio buttons in c#.
i have this little class called histogram and a function inside it that draws the histogram of any picture. i want to assign the function to a radio button so that when its clicked the histogram would be drawn. button 8 would draw all channels together, button 9 would draw only the red channel, button 10 - green, button 11 - blue. here is a picture of my form design and also my histogram class code.
http://i.imgur.com/x9Hd0TL.png the code that needs to be assigned to radio buttons is at the end of drawHistogram function. i marked them with cases.
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace WindowsFormsApplication4
{
public class histogram
{
public int[] hR;
public int[] hG;
public int[] hB;
public int[] hI;
public histogram()
{
hR = new int[257];
hG = new int[257];
hB = new int[257];
hI = new int[257];
}
~histogram()
{
hR = null;
hG = null;
hB = null;
hI = null;
}
public void eraseHistogram()
{
for (int i = 0; i <257; i++)
{
hR[i] = 0;
hG[i] = 0;
hB[i] = 0;
hI[i] = 0;
}
}
public void readHistogram(rgbiPixels[,] imgArray)
{
eraseHistogram();
int W = imgArray.GetLength(0);
int H = imgArray.GetLength(1);
for (int x = 0; x < W; x++)
{
for (int y = 0; y < H; y++)
{
hR[imgArray[x, y].R]++;
hR[256] = Math.Max(hR[imgArray[x, y].R], hR[256]);
hI[imgArray[x, y].I]++;
hI[256] = Math.Max(hI[imgArray[x, y].I], hI[256]);
hG[imgArray[x, y].G]++;
hG[256] = Math.Max(hG[imgArray[x, y].G], hR[256]);
hB[imgArray[x, y].B]++;
hB[256] = Math.Max(hB[imgArray[x, y].B], hR[256]);
}
}
}
public Bitmap drawHistogram()
{
int r;
int g;
int b;
int i;
int normalizedMax = Math.Max(hI[256], Math.Max(hR[256], Math.Max(hG[256], hB[256])));
var bmp = new Bitmap(256, 100, PixelFormat.Format24bppRgb);
for (int x = 0; x < 256; x++)
{
for (int y = 0; y < 100; y++)
{
int normalizedValue = 100 * hI[x] / normalizedMax;
if (y < normalizedValue) { i = 255; }
else { i = 0; }
normalizedValue = 100 * hR[x] / normalizedMax;
if (y < normalizedValue) { r = 255; }
else { r = 0; }
normalizedValue = 100 * hG[x] / normalizedMax;
if (y < normalizedValue) { g = 255; }
else { g = 0; }
normalizedValue = 100 * hB[x] / normalizedMax;
if (y < normalizedValue) { b = 255; }
else { b = 0; }
//case 1, intensity
if (r == 0 && g == 0 && b == 0)
{ bmp.SetPixel(x, 99 - y, Color.FromArgb(i, i, i)); }
else if (i == 255)
{ bmp.SetPixel(x, 99 - y, Color.FromArgb((i + r) / 2, (i + g) / 2, (i + b) / 2)); }
else
{ bmp.SetPixel(x, 99 - y, Color.FromArgb(r, g, b)); }
/*//case 2, red
if (r == 255)
{ bmp.SetPixel(x, 99 - y, Color.FromArgb(255, 0, 0)); }*/
/*//case 3, green
if (g == 255)
{ bmp.SetPixel(x, 99 - y, Color.FromArgb(0, 255, 0)); }*/
/*//case 4, blue
if (b == 255)
{ bmp.SetPixel(x, 99 - y, Color.FromArgb(0, 0, 255)); }*/
}
}
return bmp;
}
}
}
I'm attempting to add semi-realistic water into my tile-based, 2D platformer. The water must act somewhat lifelike, with a pressure model that runs entirely local. (IE. Can only use data from cells near it) This model is needed because of the nature of my game, where you cannot be certain that the data you need isn't inside an area that isn't in memory.
I've tried one method so far, but I could not refine it enough to work with my constraints.
For that model, each cell would be slightly compressible, depending on the amount of water in the above cell. When a cell's water content was larger than the normal capacity, the cell would try to expand upwards. This created a fairly nice simulation, abeit slow (Not lag; Changes in the water were taking a while to propagate.), at times. When I tried to implement this into my engine, I found that my limitations lacked the precision required for it to work. I can provide a more indepth explanation or a link to the original concept if you wish.
My constraints:
Only 256 discrete values for water level. (No floating point variables :( ) -- EDIT. Floats are fine.
Fixed grid size.
2D Only.
U-Bend Configurations must work.
The language that I'm using is C#, but I can probably take other languages and translate it to C#.
The question is, can anyone give me a pressure model for water, following my constraints as closely as possible?
How about a different approach?
Forget about floats, that's asking for roundoff problems in the long run. Instead, how about a unit of water?
Each cell contains a certain number of units of water. Each iteration you compare the cell with it's 4 neighbors and move say 10% (change this to alter the propagation speed) of the difference in the number of units of water. A mapping function translates the units of water into a water level.
To avoid calculation order problems use two values, one for the old units, one for the new. Calculate everything and then copy the updated values back. 2 ints = 8 bytes per cell. If you have a million cells that's still only 8mb.
If you are actually trying to simulate waves you'll need to also store the flow--4 values, 16 mb. To make a wave put some inertia to the flow--after you calculate the desired flow then move the previous flow say 10% of the way towards the desired value.
Try treating each contiguous area of water as a single area (like flood fill) and track 1) the lowest cell(s) where water can escape and 2) the highest cell(s) from which water can come, then move water from the top to the bottom. This isn't local, but I think you can treat the edges of the area you want to affect as not connected and process any subset that you want. Re-evaluate what areas are contiguous on each frame (re-flood on each frame) so that when blobs converge, they can start being treated as one.
Here's my code from a Windows Forms demo of the idea. It may need some fine tuning, but seems to work quite well in my tests:
public partial class Form1 : Form
{
byte[,] tiles;
const int rows = 50;
const int cols = 50;
public Form1()
{
SetStyle(ControlStyles.ResizeRedraw, true);
InitializeComponent();
tiles = new byte[cols, rows];
for (int i = 0; i < 10; i++)
{
tiles[20, i+20] = 1;
tiles[23, i+20] = 1;
tiles[32, i+20] = 1;
tiles[35, i+20] = 1;
tiles[i + 23, 30] = 1;
tiles[i + 23, 32] = 1;
tiles[21, i + 15] = 2;
tiles[21, i + 4] = 2;
if (i % 2 == 0) tiles[22, i] = 2;
}
tiles[20, 30] = 1;
tiles[20, 31] = 1;
tiles[20, 32] = 1;
tiles[21, 32] = 1;
tiles[22, 32] = 1;
tiles[33, 32] = 1;
tiles[34, 32] = 1;
tiles[35, 32] = 1;
tiles[35, 31] = 1;
tiles[35, 30] = 1;
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
using (SolidBrush b = new SolidBrush(Color.White))
{
for (int y = 0; y < rows; y++)
{
for (int x = 0; x < cols; x++)
{
switch (tiles[x, y])
{
case 0:
b.Color = Color.White;
break;
case 1:
b.Color = Color.Black;
break;
default:
b.Color = Color.Blue;
break;
}
e.Graphics.FillRectangle(b, x * ClientSize.Width / cols, y * ClientSize.Height / rows,
ClientSize.Width / cols + 1, ClientSize.Height / rows + 1);
}
}
}
}
private bool IsLiquid(int x, int y)
{
return tiles[x, y] > 1;
}
private bool IsSolid(int x, int y)
{
return tiles[x, y] == 1;
}
private bool IsEmpty(int x, int y)
{
return IsEmpty(tiles, x, y);
}
public static bool IsEmpty(byte[,] tiles, int x, int y)
{
return tiles[x, y] == 0;
}
private void ProcessTiles()
{
byte processedValue = 0xFF;
byte unprocessedValue = 0xFF;
for (int y = 0; y < rows; y ++)
for (int x = 0; x < cols; x++)
{
if (IsLiquid(x, y))
{
if (processedValue == 0xff)
{
unprocessedValue = tiles[x, y];
processedValue = (byte)(5 - tiles[x, y]);
}
if (tiles[x, y] == unprocessedValue)
{
BlobInfo blob = GetWaterAt(new Point(x, y), unprocessedValue, processedValue, new Rectangle(0, 0, 50, 50));
blob.ProcessMovement(tiles);
}
}
}
}
class BlobInfo
{
private int minY;
private int maxEscapeY;
private List<int> TopXes = new List<int>();
private List<int> BottomEscapeXes = new List<int>();
public BlobInfo(int x, int y)
{
minY = y;
maxEscapeY = -1;
TopXes.Add(x);
}
public void NoteEscapePoint(int x, int y)
{
if (maxEscapeY < 0)
{
maxEscapeY = y;
BottomEscapeXes.Clear();
}
else if (y < maxEscapeY)
return;
else if (y > maxEscapeY)
{
maxEscapeY = y;
BottomEscapeXes.Clear();
}
BottomEscapeXes.Add(x);
}
public void NoteLiquidPoint(int x, int y)
{
if (y < minY)
{
minY = y;
TopXes.Clear();
}
else if (y > minY)
return;
TopXes.Add(x);
}
public void ProcessMovement(byte[,] tiles)
{
int min = TopXes.Count < BottomEscapeXes.Count ? TopXes.Count : BottomEscapeXes.Count;
for (int i = 0; i < min; i++)
{
if (IsEmpty(tiles, BottomEscapeXes[i], maxEscapeY) && (maxEscapeY > minY))
{
tiles[BottomEscapeXes[i], maxEscapeY] = tiles[TopXes[i], minY];
tiles[TopXes[i], minY] = 0;
}
}
}
}
private BlobInfo GetWaterAt(Point start, byte unprocessedValue, byte processedValue, Rectangle bounds)
{
Stack<Point> toFill = new Stack<Point>();
BlobInfo result = new BlobInfo(start.X, start.Y);
toFill.Push(start);
do
{
Point cur = toFill.Pop();
while ((cur.X > bounds.X) && (tiles[cur.X - 1, cur.Y] == unprocessedValue))
cur.X--;
if ((cur.X > bounds.X) && IsEmpty(cur.X - 1, cur.Y))
result.NoteEscapePoint(cur.X - 1, cur.Y);
bool pushedAbove = false;
bool pushedBelow = false;
for (; ((cur.X < bounds.X + bounds.Width) && tiles[cur.X, cur.Y] == unprocessedValue); cur.X++)
{
result.NoteLiquidPoint(cur.X, cur.Y);
tiles[cur.X, cur.Y] = processedValue;
if (cur.Y > bounds.Y)
{
if (IsEmpty(cur.X, cur.Y - 1))
{
result.NoteEscapePoint(cur.X, cur.Y - 1);
}
if ((tiles[cur.X, cur.Y - 1] == unprocessedValue) && !pushedAbove)
{
pushedAbove = true;
toFill.Push(new Point(cur.X, cur.Y - 1));
}
if (tiles[cur.X, cur.Y - 1] != unprocessedValue)
pushedAbove = false;
}
if (cur.Y < bounds.Y + bounds.Height - 1)
{
if (IsEmpty(cur.X, cur.Y + 1))
{
result.NoteEscapePoint(cur.X, cur.Y + 1);
}
if ((tiles[cur.X, cur.Y + 1] == unprocessedValue) && !pushedBelow)
{
pushedBelow = true;
toFill.Push(new Point(cur.X, cur.Y + 1));
}
if (tiles[cur.X, cur.Y + 1] != unprocessedValue)
pushedBelow = false;
}
}
if ((cur.X < bounds.X + bounds.Width) && (IsEmpty(cur.X, cur.Y)))
{
result.NoteEscapePoint(cur.X, cur.Y);
}
} while (toFill.Count > 0);
return result;
}
private void timer1_Tick(object sender, EventArgs e)
{
ProcessTiles();
Invalidate();
}
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
int x = e.X * cols / ClientSize.Width;
int y = e.Y * rows / ClientSize.Height;
if ((x >= 0) && (x < cols) && (y >= 0) && (y < rows))
tiles[x, y] = 2;
}
}
}
From a fluid dynamics viewpoint, a reasonably popular lattice-based algorithm family is the so-called Lattice Boltzmann method. A simple implementation, ignoring all the fine detail that makes academics happy, should be relatively simple and fast and also get reasonably correct dynamics.