Найти - Пользователи
Полная версия: игра Сапер
Начало » Python для новичков » игра Сапер
1 2
xam1816
поиграйте в игрульку, сделайте замечания по коду,что в нем плохо, а что хорошо, и почему
P.S игра писалась ради структуры кода,в целях обучения,а не самой игры,

 import random as rd
import tkinter as tk
import tkinter.messagebox as mb
class MineField:
	def __init__(self, size, count_mines):
		"""
		создает матрицу с минами,и числом мин вокруг каждой ячейки
		:param size:размер матрицы
		:param count_mines: количество мин
		"""
		self.coords_mines = self.random_coords(0, size, count_mines)
		self.matrix = self.get_minefield_matrix(size, self.coords_mines)
	def random_coords(self, begin, end, count):
		coords = set()
		while len(coords) != count:
			coords.add((rd.randint(begin, end - 1), rd.randint(begin, end - 1)))
		return coords
	def get_minefield_matrix(self, size, coords_mines):
		m = [[0 for _ in range(size)] for _ in range(size)]
		for r, c in coords_mines:
			m[r][c] = 'm'
		out = self.calc_target_around_cell(m, 'm')
		return out
	def calc_target_around_cell(self, matrix, target):
		size = len(matrix)
		for x in range(size):
			for y in range(size):
				for i in range(x - 1, x + 2):
					if size > i >= 0:
						for j in range(y - 1, y + 2):
							if size > j >= 0:
								if matrix[i][j] == target:
									if matrix[x][y] == target:
										continue
									else:
										matrix[x][y] += 1
		return matrix
class Button(tk.Button):
	def __init__(self, r, c, f1, f2, f3):
		"""
		создает кнопку tkinter
		:param r: координата строки в матрице
		:param c: координата колонки в матрице
		:param f1: функция для лкм
		:param f2: функция для скм
		:param f3: функция для пкм
		"""
		self.default_option = {
			'text': '',
			'width': 3,
			'font': 'Arial 10',
			'foreground': 'yellow',
			'state': 'normal',
			'bg': 'SystemButtonFace',
			'relief': 'raised'
		}
		super().__init__(**self.default_option)
		self.coord = r, c
		self.state = ''
		self.bind('<ButtonRelease - 1>', lambda e: f1(*self.coord))
		self.bind('<ButtonRelease - 3>', lambda e: f2(*self.coord))
		self.bind('<ButtonRelease - 2>', lambda e: f3(*self.coord))
		self.grid(row=r, column=c)
	def set_color(self, color):
		self.config(bg=color)
	def set_text(self, text):
		self.config(text=text)
	def set_fg(self, color):
		self.config(fg=color)
	def set_default(self):
		self.config(self.default_option)
class MineClearance:
	def __init__(self, master, size, mines_count):
		"""
		контроллер игры сапер
		:param master: окно для игры
		:param size: размер
		:param mines_count:количество мин
		"""
		self.size = size
		self.mines_count = mines_count
		self.mf = MineField(self.size, self.mines_count)
		self.btns = self.create_btn_matrix(self.size)
		self.flags = self.mines_count
		self.mines_clear = 0
		self.master = master
		self.master.title(f'flags {self.flags}|mines {self.mines_count}')
	def create_btn_matrix(self, size):
		matrix = [[Button(r, c, self.open, self.flag, self.multi_open) for c in range(size)] for r in range(size)]
		return matrix
	# левая кнопка мыши
	def open(self, r, c):
		btn = self.btns[r][c]
		value = self.mf.matrix[r][c]
		if btn.state == 'open' or btn.state == 'flag':
			return
		if btn.state == 'ask':
			btn.config(btn.default_option)
			btn.state = ''
		btn.state = 'open'
		if value == 'm':
			btn.set_color('red')
			self.game_over()
			return True
		elif value == 0:
			btn.set_color('green')
			return self.multi_open(r, c)
		else:
			btn.set_color('brown')
			btn.set_text(value)
	# средняя кнопка мыши
	def multi_open(self, r, c):
		for i in range(r - 1, r + 2):
			if 0 <= i < self.size:
				for j in range(c - 1, c + 2):
					if 0 <= j < self.size:
						if self.open(i, j):
							return
	# правая кнопка мыши
	def flag(self, r, c):
		btn = self.btns[r][c]
		value = self.mf.matrix[r][c]
		if btn.state == 'open':
			return
		if btn.state == 'ask':
			btn.config(btn.default_option)
			btn.state = ''
		elif btn.state != 'flag' and self.flags > 0:
			btn.set_color('yellow')
			btn.state = 'flag'
			self.flags -= 1
			if value == 'm':
				self.mines_clear += 1
				if self.mines_clear == self.mines_count:
					self.win()
					return
		elif btn.state == 'flag':
			btn.config(btn.default_option)
			btn.state = 'ask'
			btn.set_text('?')
			btn.set_fg('black')
			self.flags += 1
			if value == 'm':
				self.mines_clear -= 1
		self.master.title(f'flags {self.flags}|mines {self.mines_count}')
	def win(self):
		mb.showinfo("Этап пройден", "Вы выйграли")
		self.__init__(self.master, self.size, self.mines_count + 1)
	def game_over(self):
		mb.showinfo("Взрыв", "Вы проиграли")
		self.__init__(self.master, self.size, self.mines_count)
def main():
	root = tk.Tk()
	MineClearance(root, 10, 15)
	mb.showinfo("Сапер",
					"""
						Разминируйте поле,
							число в ячейке подскажет вам сколько мин вокруг клетки,
						чтобы открыть клетку левая кнопка мыши
						устанавливайте флаг на мину правой кнопкой мыши
						открыть клетки вокруг клетки - средняя кнопка мыши
							Будте внимательны,прислушивайтесь к интуиции,первые шаги самые опасные
					"""
				)
	root.mainloop()
if __name__ == '__main__':
	main()
py.user.next
Не очень похоже на сапёр. Видимо, нарушена логика. Так что будем считать, что это код нипойми чего.

В коде встречается несколько моментов где много уровней вложенности. Нужно делать уровни вложенности не более трёх. Самый идеальный уровень вложенности - это ноль, но бывает, что ноль уровней вложенности сделать невозможно, и тогда можно добавить один уровень вложенности. Если же у тебя появляется четвёртый уровень вложенности, то с кодом что-то не то.

Это идеально
оператор
оператор
Это нормально
оператор
оператор
Это можно
оператор
оператор
оператор
Это уже нельзя
оператор
оператор
оператор
оператор
В таком случае тебе нужно подумать “а что я делаю?”. Обычно это значит, что код надо разделить на несколько кодов.

Дальше по классам.

Класс MineField отвечает на вопрос “что это?”
Класс Button отвечает на вопрос “что это?”
Класс MineClearance не отвечает на вопрос “что это?” и, соответственно, внутри тоже похож на своё название - на свалку из чего-то.
Все классы должны описывать отдельные самостоятельные объекты, которые потом будут создаваться и взаимодействовать друг с другом через отправку сообщений друг другу.

У класса MineField метод get_minefield_matrix() отвечает на вопрос “что сделать?”
У класса MineField метод calc_target_around_cell() отвечает на вопрос “что сделать?”
У класса MineField метод random_coords() не отвечает на вопрос “что сделать?”, поэтому неясно, что за координаты и подаются ли они снаружи или внутри производятся и возвращаются.
Все методы должны быть либо императивными и отвечать на вопрос “что сделать?”, либо должны быть свойствами, которое не вызывается, а к которому идёт прямое обращение как к атрибуту объекта.

У класса Button методы set_color(), set_text(), set_fg(), set_default() отвечают на вопрос “что сделать?”
У класса Button метод set_fg() в имени содержит слово fg, которое может трактоваться несколькими способами.
Все имена должны читаться и быстро формировать определённую картинку происходящего у читателя кода. Если у читателя кода возникает сомнение в том, что значит имя, это значит, что писатель этого имени плохо его написал. Поэтому тут лучше написать set_foreground().

Естественно, свалку MineClearance не описываю, потому что это свалка и организовано там всё, как на свалке, - то есть никак.
PEHDOM
ну если в целях (само)обучения то посмотрите как тут http://itnotesblog.ru/note.php?id=10
реализовано и сравните
rami
py.user.next
Не очень похоже на сапёр. Видимо, нарушена логика.
Что именно? У меня (можно сказать) работает.

1. xam1816, у меня на MacOS, tkinter использует системные кнопки (без цветного фона), поэтому Button для этой цели не подходит, нужно использовать Label.
Вместо кнопки:
 class Button(tk.Button):

использовать Label, больше ничего менять не нужно (в этом случае), хотя стоит изменить имя класса с “Button” на что-нибудь другое (“Cell”):
 class Button(tk.Label):


2. Не понятно, зачем нужны три кнопки мыши? Достаточно же одной: нажал на поле и “ой” … узнал есть мина или нет.
py.user.next
rami
py.user.next
Не очень похоже на сапёр. Видимо, нарушена логика.
Что именно? У меня (можно сказать) работает.
https://imageup.ru/img175/3714392/minesweeper.png.html
Цифры окружают три неоткрытые клетки, которые продолжают нажиматься. Определить, что это открытые клетки, а не закрытые, можно при попытке установить туда флаг, который тоже не ставится. То есть сначала нужно определять, клетки являются закрытыми или уже открытыми.

Вот ещё одна конфигурация
https://imageup.ru/img137/3714394/minesweeper_nomine.png.html
Вот в этой белой клетке мины нет. Это открытая клетка, которая выглядит как клетка с миной.

И ещё там есть такое, что флаги нужно все проставить, даже когда все клетки без мин открыты. Без этого игра ждёт, когда поставишь последний флаг.
rami
py.user.next
И ещё там есть такое, что флаги нужно все проставить, даже когда все клетки без мин открыты. Без этого игра ждёт, когда поставишь последний флаг.
У меня флаги вообще не устанавливаются (или я не знаю как). Игра не завершается.

Работает:
чтобы открыть клетку левая кнопка мыши

не работает, пишет что проиграл:
устанавливайте флаг на мину правой кнопкой мыши

пустые соседние клетки открываются сами:
открыть клетки вокруг клетки - средняя кнопка мыши

Я думаю, правая и средняя кнопки мыши не нужны, мины обозначать не обязательно.


На картинке светлые клетки ещё не нажаты:
py.user.next
rami
У меня флаги вообще не устанавливаются (или я не знаю как).
Правой кнопкой мыши у меня ставятся жёлтые флаги.

Выиграл игру, проставив все флаги
https://imageup.ru/img183/3714499/minesweeper_win.png.html

Проиграл игру, чтобы увидеть взрыв
https://imageup.ru/img297/3714500/minesweeper_lose.png.html

xam1816
py.user.next
Все классы должны описывать отдельные самостоятельные объекты, которые потом будут создаваться и взаимодействовать друг с другом через отправку сообщений друг другу.
Типа такого пишут программы? или так слишком заморочно, и отправка сообщений понимается мною буквально
 import time
class Net:
	"""
	это класс Сеть,в которой общаются объекты
	ее функции - это законы по которым они общаются между собой
	"""
	def process(self, data):
		"""
		обработка данных объектом
		:param data: входящие данные
		:return: обработанные данные, ответ на сообщение
		"""
		print(f'данные получены в родительский класс,\n'\
			  f'переопределите эту функцию в дочернем классе, куда должны придти данные>>>\n{data}')
		return
	def event_handler(self, sender, recipient, data):
		"""
		обработчик события(кто-то отправил сообщение)
		:param sender: получатель ответа на сообщение(по умолчанию отправитель)
		:param recipient: получатель
		:param data: данные
		:return:
		"""
		new_data = recipient.process(data)
		if new_data is not None:
			return sender.send_message(recipient, sender, new_data)
	def send_message(self, sender, recipient, data):
		"""
		отправка сообщения объектом
		:param sender: получатель ответа на сообщение(по умолчанию отправитель)
		:param recipient: получатель сообщения
		:param data: данные
		:return:
		"""
		return recipient.event_handler(sender, recipient, data)
class A(Net):
	def process(self, data):
		time.sleep(1)
		if data == 'привет':
			print('A: привет')
			return data
		elif data == 'давай дружить':
			print('A: сложи 2+2')
			return 2,2
		elif data == 4:
			print(f'A: верно, это {data}')
			return
		else:
			print("A: Кроме слова привет я больше ничего не понимаю")
			return "A: Кроме слова привет я больше ничего не понимаю"
class B(Net):
	def process(self, data):
		time.sleep(1)
		if data == 'привет':
			print("B: давай дружить")
			return 'давай дружить'
		elif isinstance(data, tuple):
			print('B: подожди 2 секунды,складываю...')
			time.sleep(2)
			a, b = data
			print(f"В: {a+b}")
			return a+b
		else:
			print("B: я разочарован")
			return
class C(Net):
	pass
a = A()
b = B()
c = C()
while True:
	s = input(">>>")
	if s == 'x':
		a.send_message(a,c,'входные данные для С')
		break
	else:
		b.send_message(b,a,s)
py.user.next
xam1816
Типа такого пишут программы?
Вот пример:
Я, в лице объекта <главная программа>, объекту <informer> посылаю сообщение “узнай длину объекта и сообщи мне” , в котором передаю ссылку на объект <lst>.
Объект <informer> получает сообщение “узнай длину объекта и сообщи отправителю”, в котором есть ссылка на отправителя сообщения и ссылка на исследуемый объект, и отправляет исследуемому объекту <lst> сообщение “сообщи мне свою длину”.
Объект <lst> получает сообщение “сообщи мне свою длину”, в котором есть ссылка на отправителя сообщения, узнаёт свою длину и посылает отправителю сообщение “моя длина равна пяти”.
Объект <informer> получает сообщение “моя длина равна пяти”, в котором есть ссылка на отправителя сообщения, и отправляет отправителю ранее полученного сообщения “узнай длину объекта и сообщи мне” сообщение “длина этого объекта равна пяти”.
Объект <главная программа> получает сообщение “длина этого объекта равна пяти” и остаётся с этой информацией.
И вот так оно выглядит на питоне:
  
>>> class Informer:
...     def know_length(self, obj):
...         print(obj, 'has length', len(obj))
... 
>>> lst = [1, 2, 3, 4, 5]
>>> 
>>> informer = Informer()
>>> informer.know_length(lst)
[1, 2, 3, 4, 5] has length 5
>>>

Так вот для всех языков обмен сообщениями между объектами будет одинаковым, а на питоне он будет записан вот так: где-то что-то видно, а где-то что-то скрыто, как будто этого нет. Это особенности питона, что он скрывает вещи, которые там есть, но ты о них не знаешь. В других языках тоже свои особенности есть, где что-то тоже скрыто, хотя оно там есть. Но на всех языках реализовываться будет вот эта одна общая и одинаковая вещь, которая полная и в которой есть всё. И когда ты делаешь игру Сапёр, ты должен сделать (разработать) вот эту полную вещь, в которой всё всем передаётся. И вот когда она у тебя готова, тогда ты её готовую берёшь и реализуешь на питоне, на C, на Java, на C++, на Objective C, на JavaScript, на Go, на Rust и даже на Erlang. И она везде будет по-разному выглядеть: где-то будет видно всё только наполовину; где-то это всё будет разбросано по разным местам; где-то будет требоваться какой-то синтаксис лишний. Но это будет одно и то же.

ООП - это не выучивание синтаксиса языка. Это большая и обширная область, которая к синтаксисам языков вообще никаким боком не относится. Тебе надо нарисовать кошку - на, держи карандаш, им можно нарисовать кошку вообще замечательно. Ты спросишь “вот карандаш у меня есть уже, а как теперь из карандаша добыть знание о том, как нарисовать им кошку?”, ответ “никак не добыть, в карандаше их нет”. Так вот питон - это такой карандаш, далеко не самый прекрасный. А кошка - это игра Сапёр, которую ты не разработал.


tags: oop
xam1816
py.user.next
Спасибо за разъяснение,вот что я понял из вашего сообщения.

* У нас есть сущность,которая что-то умеет делать.

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

* Ее методы не видны,но видна или доступна команда,приказ на выполнение,который она понимает и который мы ей сообщаем.

* С приказом,если нужно,ей передаются аргументы(входные данные),которые нужно обработать и вернуть = ответ от сущности

*При создании сущности, ей присваивается адрес нахождения в памяти,эта ссылка храниться в переменной,которую потом используем как адрес сущности(аналогом переменной в интернете это доменное имя сайта,вместо ip и порта)

Выглядит в се это вот так:

 первый_ответ = адресат.выполнить_действие(аргументы),
второй_ответ = адрес_2.преобразовать_данные(первый_ответ)

*Передать сообщение = сделать некий запрос по адресу,получить на него ответ
*имя переменной = удобное_название ссылки на объект(адрес в памяти)
*команда объекта = это сообщение после которого объект начинает действовать(название команды должно быть написано понятно,и быть в приказном тоне,императивном

 def сдеалай_что-либо(вот_данные)
    обрабатываю данные своими методами
    вернуть измененные_данные

ООП = программирование ориентированное на объекты(сущности)

??? = программирование ориентированное на организацию передачи сообщения объекту и получения от него обратной связи,ответа,реакции

объект(сущность) создается по канонам ООП
взаимодействие между обЪектами пишется по канонам ???

Интернет - это пример,когда объект(сущность) - браузер отправляет сообщение типа адрес.?номер_страницы серверу => возвращает текст html, брауз интерпретирует код на экран.Что там на сервере происходило х.з

Предложение

Кто имеет опыт организации программистов или хочет попробовать,сможете ли посодействовать.
Допустим нужен я и еще два человека.или вообще любой желающий

Одному - дается написать класс - интерфейс
Другому - класс который получает данные,обрабатывает,возвращает результат
Третьему - класс который связывает два класса.

Мы не знаем Как работают классы друг друга, третий кто пишет связывание классов должен нам дать название команд и принимаемые аргументы, которые должны понимать наши объекты

Организатор должен дать идею программы(только не очень сложной пока) ну и смотреть,подправлять,давать наставления и наверное как-то придерживать нас общим курсом.

Должно получиться так что Интерфейс каждого человека, или обработчик мог подходить к программе.

P.S. переписал сапера, Чтобы выйграть нужно расставить все флажки, средняя кнопка мыши открывать клетки,вокруг клетки(это если знаешь что точно нет мины или она помечена флажком)Пкм - установить флаг(пишу на windows, позже linux установлю)
 import tkinter as tk
import random as rd
import tkinter.messagebox as mb
class ModelCell:
	def __init__(self):
		self.mined = False
		self.flaged = False
		self.opened = False
class ModelMinefield:
	def __init__(self, size, count_mines):
		self.size = size
		self.count_mines = count_mines
		self.mines_coords = self._get_random_cords()
		self.cells = self._get_matrix()
		self._set_mines_by_coords()
		self.count_mines_cleared = 0
		self.count_flags = self.count_mines
	def show_cells_in_cmd(self):
		for row in self.cells:
			print()
			for c in row:
				print("@"if c.mined else "*",end='\t')
	def _get_random_cords(self):
		coords = set()
		while len(coords) != self.count_mines:
			coords.add((rd.randint(0, self.size - 1), rd.randint(0, self.size - 1)))
		return coords
	def _get_matrix(self):
		matrix = [[ModelCell() for _ in range(self.size)] for _ in range(self.size)]
		return matrix
	def _set_mines_by_coords(self):
		for r, c in self.mines_coords:
			self.cells[r][c].mined = True
	def _get_count_mines_around_cell(self, r, c):
		value = 0
		for i in range(r - 1, r + 2):
			if self.size > i >= 0:
				for j in range(c - 1, c + 2):
					if self.size > j >= 0:
						value += int(self.cells[i][j].mined)
		return value
	def get_value_from_cell(self, r, c):
		cell = self.cells[r][c]
		if cell.opened:
			return
		if not cell.flaged:
			self.cells[r][c].opened = True
			if cell.mined:
				return 'mine'
			else:
				return self._get_count_mines_around_cell(r, c)
	def set_flag(self,r, c):
		cell = self.cells[r][c]
		if cell.opened:
			return None, None
		elif not cell.flaged and self.count_flags > 0:
			self.cells[r][c].flaged = True
			self.count_flags -= 1
			if cell.mined:
				self.count_mines_cleared += 1
				if self.count_mines_cleared == self.count_mines:
					return 'win', self.count_flags
			return 'flag', self.count_flags
		elif cell.flaged:
			self.cells[r][c].flaged = False
			self.count_flags += 1
			if cell.mined:
				self.count_mines_cleared -= 1
			return 'opened', self.count_flags
		else:
			return None,None
class ViewCell(tk.Label):
	def __init__(self, r, c, **kw):
		super().__init__(width=3,bg='grey', relief='raised',fg = 'yellow',font = "Arial 12 bold",**kw)
		self.grid(row=r, column=c)
		self.coords = (r, c)
	def set_color(self, color):
		self.config(bg=color)
	def set_text(self, text):
		self.config(text=text)
	def set_option_default(self):
		self.config(text='', bg='grey')
	def bind_command(self, event, command):
		self.bind(event, lambda e: command(*self.coords))
class MatrixCell(tk.Frame):
	def __init__(self, size, **kw):
		super().__init__(**kw)
		self.cells = [[ViewCell(r, c, master=self) for c in range(size)] for r in range(size)]
		self.pack(side='bottom')
class ProcessGame:
	def __init__(self, size, count_mines):
		self.size = size
		self.count_mines = count_mines
		self.mf = ModelMinefield(size, count_mines)
		self.matrix = MatrixCell(size)
		self.lbl = tk.Label(text=f'мин = {self.mf.count_mines} | флагов = {self.mf.count_flags}',bg='light green')
		self.lbl.pack(side='top')
		for i in range(self.mf.size):
			for j in range(self.mf.size):
				self.matrix.cells[i][j].bind_command('<ButtonRelease - 1>',self.on_right)
				self.matrix.cells[i][j].bind_command('<ButtonRelease - 2>', self.on_middle)
				self.matrix.cells[i][j].bind_command('<ButtonRelease - 3>', self.on_left)
	def on_right(self, r, c):
		cell = self.matrix.cells[r][c]
		value = self.mf.get_value_from_cell(r, c)
		if value is None:
			return
		if value == 'mine':
			self.show_mine(cell)
			self.show_game_over()
			return True
		else:
			if value == 0:
				self.show_zero(cell)
				self.on_middle(r, c)
			else:
				self.show_value(cell, value)
	def on_left(self, r, c):
		cell = self.matrix.cells[r][c]
		value, count_flags = self.mf.set_flag(r, c)
		if value is None:
			return
		self.lbl['text'] = f'мин = {self.mf.count_mines} | флагов = {count_flags}'
		if value == 'flag':
			self.show_flag(cell)
		elif value == 'opened':
			cell.set_option_default()
		elif value == 'win':
			self.show_flag(cell)
			self.show_win()
	def on_middle(self, r, c):
		for i in range(r - 1, r + 2):
			if 0 <= i < self.mf.size:
				for j in range(c - 1, c + 2):
					if 0 <= j < self.mf.size:
						if self.on_right(i, j):
							return
	def show_mine(self, cell:ViewCell):
		cell.set_color('red')
	def show_value(self, cell:ViewCell, value):
		cell.set_text(value)
		cell.set_color('brown')
	def show_zero(self, cell):
		cell.set_color('green')
	def show_flag(self, cell):
		cell.set_color('yellow')
	def show_win(self):
		mb.showinfo("Этап пройден", "Вы выйграли")
		self.matrix.destroy()
		self.lbl.destroy()
		self.__init__(self.size + 1,self.count_mines + 5)
	def show_game_over(self):
		mb.showinfo("Взрыв", "Вы проиграли")
		self.matrix.destroy()
		self.lbl.destroy()
		self.__init__(self.size, self.count_mines)
class MainWindow(tk.Tk):
	def __init__(self):
		super().__init__()
		self.title('Сапер')
		self.resizable(width=0,height=0)
		self.config(bg='light green')
def main():
	window =MainWindow()
	ProcessGame(10, 10)
	window.mainloop()
if __name__ == '__main__':
    main()
This is a "lo-fi" version of our main content. To view the full version with more information, formatting and images, please click here.
Powered by DjangoBB