Имя: Пароль:
1C
 
Почему у нейронки в крестики-нолики лучший ход всегда в 0?
0 program345
 
01.08.25
17:32
привет!

Код модуля формы:

&НаКлиенте
Перем КоличествоПолей; // 3x3
//Перем КоличествоПолей = 9; // 3x3
&НаКлиенте
Перем Веса; // Массив весов нейронной сети
&НаКлиенте
Перем Ввод; // Массив весов нейронной сети

// Инициализация нейронной сети
&НаКлиенте
Процедура ИнициализироватьСеть()
	Веса = Новый Массив(КоличествоПолей);
	Для i = 0 По КоличествоПолей-1 Цикл
		Веса[i] = СлучайноеЧисло(0, 1); // Инициализация случайными весами
	КонецЦикла;
КонецПроцедуры 

&НаКлиенте
Функция СлучайноеЧисло(Число1,Число2)
	
	ГСЧ = Новый ГенераторСлучайныхЧисел;
	Возврат ГСЧ.СлучайноеЧисло(Число1,Число2);	

КонецФункции // ()

// Функция активации (ReLU)
&НаКлиенте
Функция ReLU(х)
	Возврат Макс(0, х);
КонецФункции

// Получить лучший ход на основе текущего состояния 
&НаКлиенте
Функция ПолучитьЛучшийХод(СостояниеДоски)
	
	ЛучшийХод = -1;
	МаксОценка = -9999;
	
	Для i = 0 По КоличествоПолей-1 Цикл
		Если СостояниеДоски[i] = 0 Тогда // Если клетка свободна
			Оценка = 0;
			Для j = 0 По КоличествоПолей-1 Цикл
				Оценка = Оценка + Веса[j] * СостояниеДоски[j];
			КонецЦикла;
			Оценка = Оценка + Веса[i] * 1; // Предполагаем наш ход в эту клетку
			Оценка = ReLU(Оценка);
			
			Если Оценка > МаксОценка Тогда
				МаксОценка = Оценка;
				ЛучшийХод = i;
			КонецЕсли;
		КонецЕсли;
	КонецЦикла;
	
	Возврат ЛучшийХод;
КонецФункции

// Обновление весов на основе результата игры
&НаКлиенте
Процедура ОбновитьВеса(СостояниеДоски, ВыбранныйХод, Результат, СкоростьОбучения = 0.1)
	
	// Вычисляем целевую оценку
	ЦелеваяОценка = Результат; // 1 - победа, 0 - ничья, -1 - поражение
	
	// Предсказанная оценка до обучения
	Предсказание = 0;
	Для i = 0 По КоличествоПолей-1 Цикл
		Предсказание = Предсказание + Веса[i] * СостояниеДоски[i];
	КонецЦикла;
	Предсказание = ReLU(Предсказание);
	
	// Ошибка
	Ошибка = ЦелеваяОценка - Предсказание;
	
	// Обновляем веса
	Для i = 0 По КоличествоПолей-1 Цикл
		Веса[i] = Веса[i] + СкоростьОбучения * Ошибка * СостояниеДоски[i];
	КонецЦикла;
	Веса[ВыбранныйХод] = Веса[ВыбранныйХод] + СкоростьОбучения * Ошибка * 1;
	
КонецПроцедуры

// Проверка победы 
&НаКлиенте
Функция ПроверитьПобеду(Доска, Игрок)
	// Горизонтальные линии
	Если (Доска[0] = Игрок И Доска[1] = Игрок И Доска[2] = Игрок) Или
		(Доска[3] = Игрок И Доска[4] = Игрок И Доска[5] = Игрок) Или
		(Доска[6] = Игрок И Доска[7] = Игрок И Доска[8] = Игрок) Тогда
		Возврат Истина;
	КонецЕсли;
	
	// Вертикальные линии
	Если (Доска[0] = Игрок И Доска[3] = Игрок И Доска[6] = Игрок) Или
		(Доска[1] = Игрок И Доска[4] = Игрок И Доска[7] = Игрок) Или
		(Доска[2] = Игрок И Доска[5] = Игрок И Доска[8] = Игрок) Тогда
		Возврат Истина;
	КонецЕсли;
	
	// Диагонали
	Если (Доска[0] = Игрок И Доска[4] = Игрок И Доска[8] = Игрок) Или
		(Доска[2] = Игрок И Доска[4] = Игрок И Доска[6] = Игрок) Тогда
		Возврат Истина;
	КонецЕсли;
	
	Возврат Ложь;
КонецФункции

// Проверка ничьи
&НаКлиенте
Функция ПроверитьНичью(Доска)
	Для i = 0 По КоличествоПолей-1 Цикл
		Если Доска[i] = 0 Тогда
			Возврат Ложь;
		КонецЕсли;
	КонецЦикла;
	Возврат Истина;
КонецФункции

// Процедура обучения 
&НаКлиенте
Процедура ОбучитьНейронку(КоличествоЭпох = 10000)
	
	ИнициализироватьСеть();
	
	Для эпоха = 1 По КоличествоЭпох Цикл
		
		Доска = Новый Массив(КоличествоПолей);
		Для i = 0 По КоличествоПолей-1 Цикл
			Доска[i] = 0; // 0 - пусто, 1 - X, -1 - O
		КонецЦикла;
		
		Ход = 1; // 1 - X (нейронка), -1 - O (оппонент)
		
		Пока Истина Цикл
			Если Ход = 1 Тогда
				// Ход нейронки
				ЛучшийХод = ПолучитьЛучшийХод(Доска);
				Если ЛучшийХод = -1 Тогда
					Прервать; // Нет доступных ходов
				КонецЕсли;
				Доска[ЛучшийХод] = 1;
				
				Если ПроверитьПобеду(Доска, 1) Тогда
					ОбновитьВеса(Доска, ЛучшийХод, 1); // Победа
					Прервать;
				ИначеЕсли ПроверитьНичью(Доска) Тогда
					ОбновитьВеса(Доска, ЛучшийХод, 0); // Ничья
					Прервать;
				КонецЕсли;
				
			Иначе
				// Ход оппонента (случайный)
				СвободныеКлетки = Новый Массив;
				Для i = 0 По КоличествоПолей-1 Цикл
					Если Доска[i] = 0 Тогда
						СвободныеКлетки.Добавить(i);
					КонецЕсли;
				КонецЦикла;
				
				Если СвободныеКлетки.Количество() = 0 Тогда
					Прервать;
				КонецЕсли;
				
				СлучайныйХод = СвободныеКлетки[СлучайноеЧисло(0, СвободныеКлетки.Количество()-1)];
				Доска[СлучайныйХод] = -1;
				
				Если ПроверитьПобеду(Доска, -1) Тогда
					ОбновитьВеса(Доска, ЛучшийХод, -1); // Поражение
					Прервать;
				ИначеЕсли ПроверитьНичью(Доска) Тогда
					ОбновитьВеса(Доска, ЛучшийХод, 0); // Ничья
					Прервать;
				КонецЕсли;
			КонецЕсли;
			
			Ход = -Ход; // Смена хода
		КонецЦикла;
	КонецЦикла;
	
	Сообщить("Обучение завершено!");
КонецПроцедуры

// Пример игры против обученной нейронки
&НаКлиенте
Процедура ИгратьПротивНейронки()
	
	Доска = Новый Массив(КоличествоПолей);
	Для i = 0 По КоличествоПолей-1 Цикл
		Доска[i] = 0;
	КонецЦикла;
	
	Ход = 1; // 1 - нейронка (X), -1 - игрок (O)
	
	Пока Истина Цикл
		// Отображаем доску
		СтрокаДоски = "";
		Для i = 0 По 8 Цикл
			Если Доска[i] = 1 Тогда
				СтрокаДоски = СтрокаДоски + "X";
			ИначеЕсли Доска[i] = -1 Тогда
				СтрокаДоски = СтрокаДоски + "O";
			Иначе
				СтрокаДоски = СтрокаДоски + i;
			КонецЕсли;
			
			Если (i+1) % 3 = 0 Тогда
				СтрокаДоски = СтрокаДоски + Символы.ПС;
			Иначе
				СтрокаДоски = СтрокаДоски + "|";
			КонецЕсли;
		КонецЦикла;
		
		Сообщить(СтрокаДоски);
		
		Если Ход = 1 Тогда
			// Ход нейронки
			ЛучшийХод = ПолучитьЛучшийХод(Доска);
			Если ЛучшийХод = -1 Тогда
				Сообщить("Ничья!");
				Возврат;
			КонецЕсли;
			Доска[ЛучшийХод] = 1;
			Сообщить("Нейронка походила в клетку " + ЛучшийХод);
			
			Если ПроверитьПобеду(Доска, 1) Тогда
				Сообщить("Нейронка победила!");
				Возврат;
			ИначеЕсли ПроверитьНичью(Доска) Тогда
				Сообщить("Ничья!");
				Возврат;
			КонецЕсли;
			
		Иначе
			// Ход игрока
			Сообщить("Ваш ход (введите номер клетки 0-8):");
			ВвестиЧислоМодально = ВвестиЧисло(Ввод);
			
			Если Не ВвестиЧислоМодально Тогда
			
				Пока Ввод < 0 Или Ввод > 8 Или Доска[Ввод] <> 0 Цикл
					Сообщить("Некорректный ход. Попробуйте еще раз:");
					ВвестиЧислоМодально = ВвестиЧисло(Ввод);
				КонецЦикла;
				
			КонецЕсли;
			
			Доска[Ввод] = -1;
			
			Если ПроверитьПобеду(Доска, -1) Тогда
				Сообщить("Вы победили!");
				Возврат;
			ИначеЕсли ПроверитьНичью(Доска) Тогда
				Сообщить("Ничья!");
				Возврат;
			КонецЕсли;
		КонецЕсли;
		
		Ход = -Ход; // Смена хода
	КонецЦикла;
	
КонецПроцедуры

// Запуск обучения и игры 
&НаКлиенте
Процедура Главная()
	ОбучитьНейронку(10000); // Обучаем на 10,000 играх
	ИгратьПротивНейронки(); // Играем против обученной нейронки
КонецПроцедуры

&НаКлиенте
Процедура ОбучитьНейронку_1(Команда)
	ОбучитьНейронку();
КонецПроцедуры

&НаКлиенте
Процедура ИгратьПротивНейронки_2(Команда)
	ИгратьПротивНейронки();
КонецПроцедуры

КоличествоПолей =9;



Что мне кажется тут где-то ошибка в лучшем коде или все верно?
1 OldCondom
 
02.08.25
14:12
Ошибка где-то в лучшем коде или вам кажется. Возможно, все верно.
2 Greeen
 
02.08.25
21:45
Лучше уберите этот код из открытого доступа, иначе этот ИИ сможет развиться и уничтожить человечество
3 Волшебник
 
02.08.25
21:46
Если противник первым ходом поставил в центр, то надо ставить в угол, иначе проиграешь
4 b_ru
 
03.08.25
01:00
(3) Если противник первым ходом поставил в центре, то он уже не выиграл.
5 Волшебник
 
03.08.25
15:49
(4) Если после первого хода противника в центр вы поставите в боковушечку, то вы уже проиграли
6 Chai Nic
 
03.08.25
10:26
(2) Раньше это называли "метод Монте-Карло", а теперь "нейросеть")
7 Волшебник
 
03.08.25
20:19
(4) Вы не понимаете, что крестики-нолики - это ничья? Вы совсем дурак и в школе не учились? Вы вообще хоть раз проигрывали в крестики-нолики? Если да, то я вам так скажу: когда вы перестали проигрывать в крестики-нолики без поддавков, тогда вы стали программистом. А потом вы начали поддаваться и ребёнок начал иногда выигрывать. Так вы стали родителем, то есть начали программировать новых программистов.
Независимо от того, куда вы едете — это в гору и против ветра!