Bizonyos táblázatos, mátrixos feladatokban, de különösen egyes négyzetrácsos táblás játékokban /pl. Blokus vagy Torpedó (angolul Battleship)/ szükség lehet a táblázatos formában elhelyezett elemek, illetve alakzatok szomszédos celláinak meghatározására. Ráadásul az igények, illetve játékszabályok eltérhetnek abban, hogy megengedett-e az alakzatok sarkokon érintkezése vagy sem. Ezért szükség lehet nem csak az összes szomszédos cella ismeretére, hanem külön a sarkokon érintkező szomszédok és külön az oldalhatáron érintkező szomszédok beazonosítására is. Egy négyzethálóban elhelyezett alakzatot és szomszédait mutatja az 1. ábra felső sora.

Adott alakzat szomszédos cellái meghatározását halmazokkal és halmazműveletekkel viszonylag egyszerűen el lehet végezni. Ennek elvét mutatja és írja le az 1. ábra további része.
Hogy a szomszédok meghatározását vizuálisan szemléltessük, az elvet az alábbi GUI programban implementáltuk, a get_all_neighbors_coords(), get_corner_neighbors_coords() és a get_side_touching_neighbors_coords() metódusokkal.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 |
import tkinter as tk from itertools import product class NeighborDisplayDemo(tk.Tk): def __init__(self, rowcount:int, columncount:int): super().__init__() self.rowcount, self.columncount = rowcount, columncount self.matrix = [[0] * self.columncount for _ in range(self.rowcount)] self.frame_upper = tk.Frame(self) self.frame_lower = tk.Frame(self) self.frame_upper.pack(fill=tk.BOTH, expand=True) self.frame_lower.pack() self.rb_var = tk.IntVar(self, value=1) radiobuttons = (tk.Radiobutton(self.frame_upper, variable=self.rb_var, value=i + 1, font=('Calibre', 10, 'bold')) for i in range(4)) text_callbacks = {1: ['nincs szomszédok mutatása', self.clear_neighbors], 2: ['minden szomszéd mutatása', self.show_all_neighbors], 3: ['sarok szomszédok mutatása', self.show_corner_neighbors], 4: ['oldalhatáros szomszédok mutatása', self.show_side_touching_neighbors]} for rb in radiobuttons: txt, callback = text_callbacks[rb.cget('value')] rb.config(text=txt.capitalize(), command=callback) rb.grid(sticky='w') for ri, ci in product(range(self.rowcount), range(self.columncount)): canvas = tk.Canvas(self.frame_lower, bg='white', width=35, height=35) canvas.grid(row=ri, column=ci) canvas.bind('<1>', self.cell_on_click_event_handler) def cell_on_click_event_handler(self, event): """Egérkattintásra az eseménnyel érintett cella kék színű lesz. Újabb bal egérgomb kattintásra fehérre vált.""" cnv: tk.Canvas = event.widget ri, ci = cnv.grid_info()['row'], cnv.grid_info()['column'] if cnv.cget('bg') == 'blue': cnv.config(bg='white') self.matrix[ri][ci] = 0 else: cnv.config(bg='blue') self.matrix[ri][ci] = 1 def get_set_cells_coords(self) -> set[tuple[int, int]]: """A kijelölt cellák sor- és oszlopindex párjainak halmazát adja vissza""" return {(ri, ci) for ri, ci in product(range(self.rowcount), range(self.columncount)) if self.matrix[ri][ci] == 1} def get_all_neighbors_coords(self) -> set[tuple[int, int]]: """A definiált alakzatok összes szomszédja sor- és oszlopindex párjainak halmazát adja vissza.""" # A vizsgált cellák (alakzatok) sor- és oszlopindex párjainak halmaza. targets_coords = self.get_set_cells_coords() adjacent_cells_coords = set() # A szomszédos cellák sor- és oszlopindex párjainak halmaza. for ri, ci in targets_coords: potential_adjacent_cells = {(ri - 1, ci), (ri + 1, ci), (ri, ci + 1), (ri, ci - 1), (ri - 1, ci - 1), (ri + 1, ci - 1), (ri - 1, ci + 1), (ri + 1, ci + 1)} valid_adjacent_cells = {(ri, ci) for ri, ci in potential_adjacent_cells if ri in range(self.rowcount) and ci in range(self.columncount)} adjacent_cells_coords |= valid_adjacent_cells adjacent_cells_coords -= set(targets_coords) return adjacent_cells_coords def get_corner_neighbors_coords(self) -> set[tuple[int, int]]: """A definiált alakzatok sarkokon érintkező szomszédjai sor- és oszlopindex párjainak halmazát adja vissza.""" # Sarkon érintkező szomszéd az, amelyet 1 indexértékkel fel, le, jobbra vagy balra tolva # nem egyezik meg egyik vizsgált cellával sem. return {(x, y) for x, y in self.get_all_neighbors_coords() if not ({(x - 1, y), (x + 1, y), (x, y - 1), (x, y + 1)} & set(self.get_set_cells_coords()))} def get_side_touching_neighbors_coords(self) -> set[tuple[int, int]]: """A definiált alakzatok oldalhatárokon érintkező szomszédjai sor- és oszlopindex párjainak halmazát adja vissza.""" return self.get_all_neighbors_coords() - self.get_corner_neighbors_coords() def clear_neighbors(self): """Eltávolítja a négyzetrácsról a szomszédokat.""" for ri, ci in product(range(self.rowcount), range(self.columncount)): cnv = self.frame_lower.grid_slaves(ri, ci)[0] if self.matrix[ri][ci] == 0: cnv.config(bg='white') def show_all_neighbors(self): """Minden szomszédot megjelenít.""" self.clear_neighbors() for ri, ci in self.get_all_neighbors_coords(): cnv = self.frame_lower.grid_slaves(ri, ci)[0] cnv.config(bg='silver') def show_corner_neighbors(self): """A sarkokon érintkező szomszédokat jeleníti meg.""" self.clear_neighbors() for ri, ci in self.get_corner_neighbors_coords(): cnv = self.frame_lower.grid_slaves(ri, ci)[0] cnv.config(bg='silver') def show_side_touching_neighbors(self): """Az oldalakkal érintkező szomszédokat jeleníti meg.""" self.clear_neighbors() for ri, ci in self.get_side_touching_neighbors_coords(): cnv = self.frame_lower.grid_slaves(ri, ci)[0] cnv.config(bg='silver') def run(self): self.mainloop() NeighborDisplayDemo(10, 20).run() |
Az alkalmazás futtatása után egy négyzethálóban egérkattintással kijelölhetünk cellákat, amelyek kék színnel jelennek meg. Ezzel alakzatokat definiálhatunk. Újabb kattintással az adott cella kijelölése megszüntethető. A választógombokkal az összes szomszédot, a sarkokon érintkező szomszédokat, vagy az oldalhatárokon érintkező szomszédokat jeleníthetjük meg. A megfelelő választógombbal a szomszédokat eltüntethetjük.
Egy példaelrendezést és az ebben szereplő alakzatok különböző kategóriájú szomszédait a 2. ábra mutatja.

E feladatban a halmazok és azokkal végzett műveletek adták a viszonylag egyszerű megoldást. Halmazokról, halmazműveletekről, valamint halmaz típusokon (set, frozenset) hívható metódusokról a Python tudásépítés lépésről lépésre című e-könyv „Beépített konténerobjektumok”, „Műveletek”, valamint a „Beépített típusok nyilvános metódusai” fejezetekben lehet részleteiben tájékozódni. A „Grafikus felhasználói felület készítése” fejezet pedig a bemutatott demo program megértéséhez, illetve egyéni igényekre szabott GUI alkalmazások készítéséhez adja meg az alapokat.