Пьяньчуга — Моделирование движения пьяного человека на C#

28 января 2010

Надоели скучные и однообразные программы? Давайте немного пофантазируем и напишем программу, которая будет моделировать движение пьяного человека, и представим все это графически.

Суть программы будет заключаться в следующем:

Будем моделировать движение пьяного человека (он пьян до того, что не знает что делает, но лежать ему не хочется). В начальный момент времени задается положение человека в некотором дворе с непроницаемыми стенками и одним выходом. Кроме того даются 5 вероятностей - остаться на месте и перемещаться в 4 стороны. Если появляется попытка переместиться через стенку, то «пьяньчуга» остается на месте. Сумма вероятностей строго равно 1. Если «пьяньчуга» нашел выход или попал в один из люков, то моделирование заканчивается и выводится соответствующее сообщение.

И так, в Visual Studio в C# создайте форму, и объекты как показано на рисунке «Рисунок 1.1». Соответственно, к каждому объекту подписано его имя.

Моделирование движения пьяного человека на C#

Моделирование движения пьяного человека на C#

Рисунок 1.1

После создания формы и всех ее объектов приступим к написанию кода программы.
Для начала я перечислю, какие библиотеки и глобальные переменные необходимы для работы программы.
Библиотеки:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Drawing.Drawing2D;

Глобальные переменные:

Point[,] coord = new Point[11, 11];
Point[] coord_courses;
coord_points[] mass_points;
Point PointUser = new Point();
Point PointUser_For_searche = new Point();
int shag = 5;
Graphics Line;
Rectangle EllipseDraw = new Rectangle();
Rectangle EllipseDraw2 = new Rectangle();
GraphicsPath GraphicsPathEllipse;

Для того чтобы запоминать сколько раз «пьяньчуга» попадал в ту или иную точку, мы будем использовать структуру «coord_points», которая включает в себя координаты точки x и y и количество попаданий «Count». Структуру объявляем глобально.

public struct coord_points
{
	int x;
	int y;
	int Count;
	public coord_points(int X, int Y, int count)
	{
		x = X;
		y = Y;
		Count = count;
	}
	public int X
	{
		get { return x; }
		set { x = value; }
	}
	public int Y
	{
		get { return y; }
		set { y = value; }
	}
	public int count
	{
		get { return Count; }
		set { Count = value; }
	}
};

Теперь напишем код для кнопки «Нарисовать местность».

	pictureBox.Refresh(); //Обновить область для рисования
	listBox1.Items.Clear(); // Очищаем ListBox с координатами
	isleovanie.Text = ""; // Очищаем значение исследования
	Line = Graphics.FromHwnd(pictureBox.Handle); // Создаем объект для рисования на pictureBox

	count_iter.Text = 0.ToString();//Обнуляем поле «Кол-во итераций»
	distance.Text = 0.ToString();//Обнуляем поле «Расстояние (пиксеметры)»

	count_courses.Text = 0.ToString();//Обнуляем поле «Общее кол-во шагов»
	count_down.Text = 0.ToString();//Обнуляем поле количество шагов «Вниз»
	count_up.Text = 0.ToString();//Обнуляем поле количество шагов «Вверх»
	count_l.Text = 0.ToString();//Обнуляем поле количество шагов «Влево»
	count_r.Text = 0.ToString();//Обнуляем поле количество шагов «Вправо»

	System.Drawing.Pen red = new Pen(Color.Red, 1);
	SolidBrush EllipseBrush = new SolidBrush(Color.Black);
	Graphics Ellipse = Graphics.FromHwnd(pictureBox.Handle);
	Graphics Ellipse2 = Graphics.FromHwnd(pictureBox.Handle);
	GraphicsPathEllipse = new GraphicsPath();
	Random rnd = new Random();
	int max_h = pictureBox.Height / 2 + pictureBox.Height / 4;
	int max_w = pictureBox.Width / 2 + pictureBox.Width / 4;
	int temp = 0;

	Point start1 = new Point(10, 10);
	Point start2 = new Point(10, 10);
	Point end1 = new Point(10, pictureBox.Height - 50);
	Point end2 = new Point(pictureBox.Width - 10, 10);
	Point end3 = new Point(pictureBox.Width - 10, pictureBox.Height - 50);
	//Координаты помещения
	coord[0, 0].X = end2.X - rnd.Next(pictureBox.Width / 3, pictureBox.Width / 2);
	coord[0, 0].Y = start1.Y;
	coord[0, 1].X = end2.X - rnd.Next(5, pictureBox.Width / 3);
	coord[0, 1].Y = end2.Y;
	//Слева линия
	coord[1, 0].X = start1.X;
	coord[1, 0].Y = start1.Y;
	coord[1, 1].X = end1.X;
	coord[1, 1].Y = end1.Y;
	//Верхняя
	coord[2, 0].X = start1.X;
	coord[2, 0].Y = start1.Y;
	coord[2, 1].X = coord[0, 0].X;
	coord[2, 1].Y = end2.Y;
	//Верхняя 2
	coord[6, 0].X = coord[0, 1].X;
	coord[6, 0].Y = start1.Y;
	coord[6, 1].X = end2.X;
	coord[6, 1].Y = end2.Y;
	//Правая
	coord[3, 0].X = end2.X;
	coord[3, 0].Y = end2.Y;
	coord[3, 1].X = end3.X;
	coord[3, 1].Y = end3.Y;
	//Нижняя
	coord[4, 0].X = end1.X;
	coord[4, 0].Y = end1.Y;
	coord[4, 1].X = end3.X - pictureBox.Width / 2 - pictureBox.Width / 3 - rnd.Next(0, 30);
	coord[4, 1].Y = end3.Y;
	//Нижняя
	coord[7, 0].X = end3.X - pictureBox.Width / 4 - pictureBox.Width / 2 - rnd.Next(0, 50);
	coord[7, 0].Y = end1.Y;
	coord[7, 1].X = end3.X;
	coord[7, 1].Y = end3.Y;
	//Нижняя 2
	coord[8, 0].X = coord[4, 1].X;
	coord[8, 0].Y = end3.Y;
	coord[8, 1].X = coord[4, 1].X;
	coord[8, 1].Y = end3.Y + 40;
	//Нижняя 2
	coord[9, 0].X = coord[7, 0].X;
	coord[9, 0].Y = end3.Y;
	coord[9, 1].X = coord[7, 0].X;
	coord[9, 1].Y = coord[8, 1].Y;
	//Нижняя 3
	coord[10, 0].X = coord[8, 1].X;
	coord[10, 0].Y = coord[8, 1].Y;
	coord[10, 1].X = coord[9, 1].X;
	coord[10, 1].Y = coord[9, 1].Y;
	//Координаты для эллипса
	coord[5, 0].X = rnd.Next(50, max_w);
	coord[5, 0].Y = rnd.Next(50, max_h);
	coord[5, 1].X = rnd.Next(50, max_w);
	coord[5, 1].Y = rnd.Next(50, max_h);
	for (; ; )
	{
		if (coord[5, 1].X == 0 || coord[5, 1].Y == 0)
		{
			temp = rnd.Next(50, max_w);
			if (temp != coord[5, 0].X)
				coord[5, 1].X = temp;

			temp = rnd.Next(50, max_w);
			if (temp != coord[5, 0].Y)
				coord[5, 1].Y = temp;
		}
		else
			break;
	}
	//////////////////
	EllipseDraw = new Rectangle(coord[5, 0].X, coord[5, 0].Y, 10, 10);
	EllipseDraw2 = new Rectangle(coord[5, 1].X, coord[5, 1].Y, 10, 10);

	Line.DrawLine(red, start1.X, start1.Y, end1.X, end1.Y); //Левая

	Line.DrawLine(red, coord[2, 0].X, coord[2, 0].Y, coord[2, 1].X, coord[2, 1].Y); //Верхняя
	Line.DrawLine(red, coord[6, 0].X, coord[6, 0].Y, coord[6, 1].X, coord[6, 1].Y); //Верхняя

	Line.DrawLine(red, end2, end3); //Правая

	Line.DrawLine(red, coord[4, 0].X, coord[4, 0].Y, coord[4, 1].X, coord[4, 1].Y); //Нижняя прямая
	Line.DrawLine(red, coord[7, 0].X, coord[7, 0].Y, coord[7, 1].X, coord[7, 1].Y); //Нижняя прямая

	Line.DrawLine(red, coord[8, 0].X, coord[8, 0].Y, coord[8, 1].X, coord[8, 1].Y); //Нижняя 2 слева

	Line.DrawLine(red, coord[9, 0].X, coord[9, 0].Y, coord[9, 1].X, coord[9, 1].Y); //Нижняя 2 справа

	Line.DrawLine(red, coord[10, 0].X, coord[10, 0].Y, coord[10, 1].X, coord[10, 1].Y); //Нижняя 3 прямая

	int el_x1 = rnd.Next(50, max_w);
	int el_y1 = rnd.Next(50, max_h);

	GraphicsPathEllipse.AddEllipse(new Rectangle(el_x1, el_y1, 10, 10));//Рисуем первый люк
	Ellipse.FillEllipse(EllipseBrush, new Rectangle(el_x1, el_y1, 10, 10));
	//Рисуем случайно все остальные люки (kolvolukov.Text – кол-во люков)
	for (int i = 2; i < = Convert.ToInt32(kolvolukov.Text); i++) 	{ 		int el_x2 = rnd.Next(50, max_w); 		int el_y2 = rnd.Next(50, max_h); 		for (; ; ) 		{ 			if (el_x2 == 0 || el_y2 == 0) 			{ 				temp = rnd.Next(50, max_w); 				if (temp != el_x1) 					el_x2 = temp; 				temp = rnd.Next(50, max_w); 				if (temp != el_y1) 					el_y2 = temp; 			} 			else 				break; 		} 		el_x1 = el_x2; 		el_y1 = el_y2; 		GraphicsPathEllipse.AddEllipse(new Rectangle(el_x2, el_y2, 10, 10)); 		Ellipse.FillEllipse(EllipseBrush, new Rectangle(el_x2, el_y2, 10, 10)); 	} 	searching.Enabled = true; //Активируем кнопку «Провести исследование»

Напишем метод «MessageShow», который будет выводить сообщение на экран при завершении моделирования. void MessageShow(int type) { if(type == 1) MessageBox.Show("Моделирование закончено!"); else MessageBox.Show("Пьянчуга попал в люк"); } Теперь напишем объект «check», который осуществляет ряд проверок движения «пьяньчуги». Если «пьяньчуга» нашел выход или попал в один из люков, выводится соответствующее сообщение и моделирование прекращается.

int check(int type) { 	//Конец пути пьянчуги 	if (PointUser.X >= coord[0, 0].X && PointUser.X < = coord[0, 1].X && PointUser.Y 	{
		if(type == 1)
			MessageShow(1);
		return -1;
	}
	//Пьянчуга попал в люк
	if (GraphicsPathEllipse.IsVisible(PointUser))
	{
		if(type == 1)
			MessageShow(2);
		return -1;
	}
	//Слева линия
	if (PointUser.X = coord[1, 0].Y && PointUser.Y < = coord[1, 1].Y) 	{ 		PointUser.X += shag; 	} 	//Верхняя 	if(PointUser.X >= coord[2, 0].X && PointUser.X < = coord[2, 1].X && PointUser.Y = coord[6, 0].X && PointUser.X < = coord[6, 1].X && PointUser.Y = coord[3, 0].X && PointUser.Y >= coord[3, 0].Y && PointUser.Y < = coord[3, 1].Y) 	{ 		PointUser.X -= shag; 	} 	//Нижняя 	if ((PointUser.X >= coord[4, 0].X && PointUser.X < = coord[4, 1].X) && PointUser.Y >= coord[4, 1].Y)
	{
		PointUser.Y -= shag;
	}
	if ((PointUser.X >= coord[7, 0].X && PointUser.X < = coord[7, 1].X) && PointUser.Y >= coord[7, 1].Y)
	{
		PointUser.Y -= shag;
	}
	if (PointUser.X < = coord[8, 0].X && PointUser.Y >= coord[8, 0].Y && PointUser.Y < = coord[8, 1].Y) 	{ 		PointUser.X += shag; 	} 	if ((PointUser.X >= coord[10, 0].X && PointUser.X < = coord[10, 1].X) && PointUser.Y >= coord[10, 1].Y)
	{
		PointUser.Y -= shag;
	}
	if (PointUser.X >= coord[9, 0].X && PointUser.Y >= coord[9, 0].Y && PointUser.Y < = coord[9, 1].Y)//Нижняя 2 справа 	{ 		PointUser.X -= shag; 	} 	return 0; }

Следующие объекты и методы необходимы для проверки правильного указания коэффициентов вероятности перемещения и количества люков.

bool check_ver() { 	double sum = Convert.ToDouble(go_up.Text) + Convert.ToDouble(go_down.Text) + Convert.ToDouble(go_right.Text) + Convert.ToDouble(go_left.Text); 	if (sum > 1 || sum < 1)
		MessageBox.Show("Сумма коэффициентов направленности превышает допустимое значение равное 1.");
	else if (Convert.ToInt32(kolvolukov.Text) < 1 || Convert.ToInt32(kolvolukov.Text) > 50)
		MessageBox.Show("Количество люков не может быть меньше 1 или больше 50.");
	else
		return true;

	return false;
}
//Запускается, когда происходит изменение Вероятности перемещения «Вверх»
private void go_up_TextChanged(object sender, EventArgs e)
{
	check_ver();
}
//Запускается, когда происходит изменение Вероятности перемещения «Вниз»
private void go_down_TextChanged(object sender, EventArgs e)
{
	check_ver();
}
//Запускается, когда происходит изменение Вероятности перемещения «Вправо»
private void go_right_TextChanged(object sender, EventArgs e)
{
	check_ver();
}
//Запускается, когда происходит изменение Вероятности перемещения «Влево»
private void go_left_TextChanged(object sender, EventArgs e)
{
	check_ver();
}
//Запускается, когда происходит изменение поля Количество люков
private void kolvolukov_TextChanged(object sender, EventArgs e)
{
	check_ver();
}

Напишем методы, которые изменяют шаг движения. Если на объект выводятся «точки», шаг составляет 5, если цифры – 15. Создано для удобства вывода на экран.

private void pic_point_CheckedChanged(object sender, EventArgs e)
{
	shag = 5;
}
private void pic_char_CheckedChanged(object sender, EventArgs e)
{
	shag = 15;
}
//Объект «isPoint» возвращает положение координаты точки, если она есть в массиве mass_points
int isPoint(Point mass_nums, coord_points[] mass_points)
{
	for (int i = 0; i < mass_points.Count(); i++)
	{
		if (mass_points[i].X == mass_nums.X && mass_points[i].Y == mass_nums.Y)
			return i;
	}
	return -1;
}
//Метод «setCount» увеличивает счетчик попадания человека в точку на 1
void setCount(coord_points[] mass_points, int index)
{
	mass_points[index].count = mass_points[index].count + 1;
}
// Метод «listBox1_MouseDoubleClick» вызывается при двойном клике на объект «listBox1», где хранятся координаты перемещения «пьяньчуги». Метод выделяет красным цветом точку по указанной координате.
private void listBox1_MouseDoubleClick(object sender, MouseEventArgs e)
{
	System.Drawing.Pen red = new Pen(Color.Red, 2);
	Graphics Ellipse = Graphics.FromHwnd(pictureBox.Handle);

	Ellipse.DrawEllipse(red, new Rectangle(mass_points[listBox1.Items.IndexOf(listBox1.Text)].X, mass_points[listBox1.Items.IndexOf(listBox1.Text)].Y, 2, 2));

}

Наконец, метод «pictureBox_MouseDown», который осуществляет моделирование движения пьяного человека.

private void pictureBox_MouseDown(object sender, MouseEventArgs e)
{
	if (check_ver() == true)//Если все проверки прошли
	{
		PointUser.X = e.X; //Получаем X-координату точки
		PointUser.Y = e.Y; //Получаем Y-координату точки

		PointUser_For_searche.X = e.X;
		PointUser_For_searche.Y = e.Y;

		Line = Graphics.FromHwnd(pictureBox.Handle);
		System.Drawing.Graphics graph = Graphics.FromHwnd(pictureBox.Handle);

		System.Drawing.Pen WhiteSmoke = new Pen(Color.WhiteSmoke, 1);
		System.Drawing.Pen Black = new Pen(Color.Black, 2);
		EllipseDraw = new Rectangle(PointUser.X, PointUser.Y, 2, 2);
		Graphics Ellipse = Graphics.FromHwnd(pictureBox.Handle);
		Ellipse.DrawEllipse(Black, EllipseDraw);

		Random rnd = new Random();
		int count = 0, countiter = 0, c_left = 0, c_right = 0, c_up = 0, c_down = 0;
		double hod, left, right, up, down;
		coord_courses = new Point[Convert.ToInt32(count_steps.Text)];

		List mass_nums = new List();

		for (int i = 1; i < = Convert.ToInt32(count_steps.Text); i++) 		{ 			if (rnd.Next(1, 5) != 1)//Если вероятность не «стоять на месте» 			{ 				coord_courses[count] = PointUser; 				mass_nums.Add(PointUser); 				hod = rnd.NextDouble(); 				up = Convert.ToDouble(go_up.Text); 				down = Convert.ToDouble(go_down.Text); 				right = up + down; 				left = up + down + Convert.ToDouble(go_right.Text); //Если вероятность «идти влево» 				if (hod > left && hod < left + Convert.ToDouble(go_left.Text)) 				{ 					PointUser.X -= shag; 					if (check(1) == -1) 						break; 					else 					{ 						if (pic_point.Checked) 							Ellipse.DrawEllipse(Black, new Rectangle(PointUser.X, PointUser.Y, 2, 2)); 					} 					c_left++; 				} 				else if (hod > 0 && hod < up) //Если вероятность «идти вверх» 				{ 					PointUser.Y -= shag; 					if (check(1) == -1) 						break; 					else 					{ 						if (pic_point.Checked) 							Ellipse.DrawEllipse(Black, new Rectangle(PointUser.X, PointUser.Y, 2, 2)); 					} 					c_up++; 				} 				else if (hod > right && hod < right + Convert.ToDouble(go_right.Text)) //вправо 				{ 					PointUser.X += shag; 					if (check(1) == -1) 						break; 					else 					{ 						if (pic_point.Checked) 							Ellipse.DrawEllipse(Black, new Rectangle(PointUser.X, PointUser.Y, 2, 2)); 					} 					c_right++; 				} 				else if (hod > up && hod < up + down) //вниз 				{ 					PointUser.Y += shag; 					if (check(1) == -1) 						break; 					else 					{ 						if (pic_point.Checked) 							Ellipse.DrawEllipse(Black, new Rectangle(PointUser.X, PointUser.Y, 2, 2)); 					} 					c_down++; 				} 			} 			countiter++; 		} 		count = c_down + c_left + c_right + c_up; 		count_iter.Text = countiter.ToString(); 		distance.Text = (count).ToString(); 		count_courses.Text = count.ToString(); 		count_down.Text = c_down.ToString(); 		count_up.Text = c_up.ToString(); 		count_l.Text = c_left.ToString(); 		count_r.Text = c_right.ToString(); 		up = Convert.ToDouble(go_up.Text); 		down = Convert.ToDouble(go_down.Text); 		right = Convert.ToDouble(go_right.Text); 		left = Convert.ToDouble(go_left.Text); 		if (up > down && up > right && up > left)
			isleovanie.Text = "При к.н. (Вверх) пьянчуга прошел " + Math.Round(((double)c_up / (double)count) * 100) + "% пути";
		else if (down > up && down > right && down > right)
			isleovanie.Text = "При к.н. (Вниз) пьянчуга прошел " + Math.Round(((double)c_down / (double)count) * 100) + "% пути";
		else if (right > up && right > left && right > down)
			isleovanie.Text = "При к.н. (Вправо) пьянчуга прошел " + Math.Round(((double)c_right / (double)count) * 100) + "% пути";
		else if (left > up && left > right && left > down)
			isleovanie.Text = "При к.н. (Влево) пьянчуга прошел " + Math.Round(((double)c_left / (double)count) * 100) + "% пути";

		mass_points = new coord_points[mass_nums.Count];
		Point[] coord_courses2 = new Point[mass_nums.Count];
		int index = 0, j = 0;
		for (int i = 0; i < mass_nums.Count; i++) 			if ((index = isPoint(mass_nums[i], mass_points)) > -1)
			{
				setCount(mass_points, index);
			}
			else
			{
				mass_points[j].X = mass_nums[i].X;
				mass_points[j].Y = mass_nums[i].Y;
				mass_points[j].count = 1;
				j++;
			}

		for (int i = j - 1; i >= 0; i--)
		{
			if (!pic_point.Checked)
			{
				if (i + 1 == j)
					graph.DrawString(mass_points[i].count.ToString(), new Font("Times New Roman", 8), new SolidBrush(Color.Red), mass_points[i].X - 3, mass_points[i].Y - 8);
				else
					graph.DrawString(mass_points[i].count.ToString(), new Font("Times New Roman", 8), new SolidBrush(Color.Black), mass_points[i].X - 3, mass_points[i].Y - 8);
			}
			listBox1.Items.Add("X:" + mass_points[i].X + "; Y:" + mass_points[i].Y + "; Кол-во:" + mass_points[i].count);
		}
	}
}

Напишем последний метод «searching_Click», чтобы завершить написание программы. Здесь выполняется исследование.

private void searching_Click(object sender, EventArgs e)
{
	Random rnd = new Random();
	int count = 0, countiter = 0, c_left = 0, c_right = 0, c_up = 0, c_down = 0;
	double hod, left, right, up, down;

	for (int sch = 1; sch < = 10; sch++)
	{
		PointUser = PointUser_For_searche;
		for (int i = 1; i  left && hod < left + Convert.ToDouble(go_left.Text)) //влево 				{ 					PointUser.X -= shag; 					if (check(2) == -1) 						break; 					c_left++; 				} 				else if (hod > 0 && hod < up) //вверх 				{ 					PointUser.Y -= shag; 					if (check(2) == -1) 						break; 					c_up++; 				} 				else if (hod > right && hod < right + Convert.ToDouble(go_right.Text)) //вправо 				{ 					PointUser.X += shag; 					if (check(2) == -1) 						break; 					c_right++; 				} 				else if (hod > up && hod < up + down) //вниз 				{ 					PointUser.Y += shag; 					if (check(2) == -1) 						break; 					c_down++; 				} 			} 			countiter++; 		} 	} 	count = c_down + c_left + c_right + c_up; 	count_iter.Text = countiter.ToString(); 	distance.Text = (count).ToString(); 	count_courses.Text = count.ToString(); 	count_down.Text = c_down.ToString(); 	count_up.Text = c_up.ToString(); 	count_l.Text = c_left.ToString(); 	count_r.Text = c_right.ToString(); 	up = Convert.ToDouble(go_up.Text); 	down = Convert.ToDouble(go_down.Text); 	right = Convert.ToDouble(go_right.Text); 	left = Convert.ToDouble(go_left.Text); 	if (up > down && up > right && up > left)
	{
		isleovanie.Text = "При к.н. (Вверх) пьянчуга прошел " + Math.Round(((double)c_up / (double)count) * 100) + "% пути";
		label10.Text = "Среднее значение: " + Math.Round((double)c_up / (double)count, 2).ToString();
	}
	else if (down > up && down > right && down > right)
	{
		isleovanie.Text = "При к.н. (Вниз) пьянчуга прошел " + Math.Round(((double)c_down / (double)count) * 100) + "% пути";
		label10.Text = "Среднее значение: " + Math.Round((double)c_down / (double)count, 2).ToString();
	}
	else if (right > up && right > left && right > down)
	{
		isleovanie.Text = "При к.н. (Вправо) пьянчуга прошел " + Math.Round(((double)c_right / (double)count) * 100) + "% пути";
		label10.Text = "Среднее значение: " + Math.Round((double)c_right / (double)count, 2).ToString();
	}
	else if (left > up && left > right && left > down)
	{
		isleovanie.Text = "При к.н. (Влево) пьянчуга прошел " + Math.Round(((double)c_left / (double)count) * 100) + "% пути";
		label10.Text = "Среднее значение: " + Math.Round((double)c_left / (double)count, 2).ToString();
	}
}

После написание кода программы проверим ее работу:

  • Указать вероятности перемещения
  • Указать количество люков и тактов
  • Выбрать метод представления пути перемещения («Точки» или «Цифры»)
  • Кликнуть по кнопке «Нарисовать местность»
  • Кликнуть мышкой по области рисования «pictureBox»
  • Посмеяться над «пьяньчугой»
  • Кликнуть по кнопке «Провести исследование»

Примеры работы программы:

«Пьяньчуга» попал в люк:

Пьяньчуга попал в люк

Пьяньчуга попал в люк

«Пьяньчуга» нашел выход:

Пьяньчуга нашел выход

Пьяньчуга нашел выход

Данную статью Вы можете скачать в формате doc
Скачать Пьяньчуга - Моделирование движения пьяного человека на C#

Автор: Евтеев Евгений Александрович

Email: [email protected]