Files
  • main.py
main.py
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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
"""This module provides famous russian Pyatnashi game for whole family"""
from tkinter import *
from tkinter.messagebox import askyesno, showinfo
from random import shuffle


class Sheet:
    """Implementation of inner logic of the game"""
    __slots__ = ["__table", "__sixteen_row", "__sixteen_column"]

    def __init__(self, initial=True):
        self.__table = [[i for i in range(s, e + 1)]
                        for s, e in ((1, 4), (5, 8), (9, 12), (13, 16))]
        self.__sixteen_row = 3
        self.__sixteen_column = 3
        if initial:
            self.generate()

    def __iter__(self):
        for row in self.__table:
            yield row

    def __getitem__(self, item):
        return self.__table[item]

    @property
    def plain(self):
        return [i for row in self.__table for i in row]

    @property
    def sixteen_row(self):
        return self.__sixteen_row

    @property
    def sixteen_column(self):
        return self.__sixteen_column

    @property
    def table(self):
        return self.__table

    @property
    def moveable(self):
        """Returns list of cells available for movement"""
        moveable = []
        right, down, left, up = (self.sixteen_column + 1, self.sixteen_row + 1,
                                 self.sixteen_column - 1, self.sixteen_row - 1)
        if right <= 3:
            moveable.append((self.sixteen_row, right))
        if left >= 0:
            moveable.append((self.sixteen_row, left))
        if up >= 0:
            moveable.append((up, self.sixteen_column))
        if down <= 3:
            moveable.append((down, self.sixteen_column))
        return moveable

    @property
    def complete(self):
        return self.plain == [i for i in range(1, 17)]

    def move(self, y, x):
        """Switches two cells"""
        self.table[self.sixteen_row][self.sixteen_column] = self.table[y][x]
        self.table[y][x] = 16
        self.__sixteen_row = y
        self.__sixteen_column = x

    def __is_completable(self):
        """Checks whereas generated sheet can be completed"""
        summ = 0
        plain = self.plain
        for pos, num in enumerate(plain):
            n = sum(filter(lambda x, num=num: x < num, plain[pos:]))
            summ += n
        summ += self.sixteen_row + 1
        return not (summ % 2)

    def randomize(self):
        """Creates random sheet"""
        nums = [i for i in range(1, 17)]
        shuffle(nums)
        for row in range(4):
            for square in range(4):
                self.__table[row][square] = nums.pop(0)

    def generate(self):
        """Creates random completable sheet"""
        self.randomize()
        while not self.__is_completable():
            self.randomize()
        sixteen = self.plain.index(16)
        self.__sixteen_row, self.__sixteen_column = divmod(sixteen, 4)


class Game(Frame):
    """The widget itself"""
    __slots__ = ("bonesize", "fields", "game_sheet", "bones", "moves",
                 "game_frame", "status_frame")

    def __init__(self, parent, *, sidesize=200, embeeded=True):
        Frame.__init__(self, parent, bd=10, relief=RIDGE, width=sidesize)
        self.bonesize = sidesize // 4
        self.embeeded = embeeded
        self.fields, self.bones = {}, {}
        self.game_sheet = Sheet()
        self.moves = 0
        self.game_frame = Frame(self, width=sidesize, height=sidesize,
                                bd=5, relief=RIDGE, bg='red')
        self.status_frame = Frame(self, width=sidesize, bd=5, relief=RIDGE,
                                  bg='green')
        self.moves_label = Label(self.status_frame, anchor=W)
        self.moves_label.pack(side=LEFT, expand=YES, fill=X)
        self.game_frame.grid(row=0, column=0, sticky=NSEW)
        self.status_frame.grid(row=1, column=0, sticky=EW)
        for i, j in ((0, 20), (1, 1)):
            self.rowconfigure(i, weight=j)
        self.columnconfigure(0, weight=1)
        self.place_fields()
        self.new_game()

    def place_fields(self):
        """Places all cells on the game field frame"""
        for field in self.fields.values():
            field.destroy()
        self.fields.clear()
        for rownum in range(4):
            self.game_frame.rowconfigure(rownum, weight=1)
            for colnum in range(4):
                field = Frame(self.game_frame, width=self.bonesize,
                              height=self.bonesize, bd=5, relief=RIDGE)
                field.grid(row=rownum, column=colnum, sticky=NSEW)
                field.pack_propagate(0)
                self.fields[(rownum, colnum)] = field
        for colnum in range(4):
            self.game_frame.columnconfigure(colnum, weight=1)

    def move(self, y, x):
        """Moves one of the clickable cells"""
        self.game_sheet.move(y, x)
        self.place_bones()
        self.update_moves()
        if self.game_sheet.complete:
            if self.embeeded:
                showinfo("Winner winner chicken dinner", "You have won!")
                return
            answer = askyesno("Winner winner chicken dinner",
                              "You have won!\nWanna try again?")
            if answer:
                self.new_game()
            else:
                self.quit()

    def place_bones(self):
        """Places bones on the fields"""
        for bone in self.bones.values():
            bone.destroy()
        self.bones.clear()
        clickable = self.game_sheet.moveable
        for rownum, row in enumerate(self.game_sheet):
            for colnum, num in enumerate(row):
                if num == 16:
                    continue
                field = self.fields[(rownum, colnum)]
                text = str(num)
                if (rownum, colnum) in clickable:
                    bone = Button(field, text=text,
                                  command=lambda y=rownum, x=colnum:
                                  self.move(y, x))
                else:
                    bone = Label(field, text=text)
                self.bones[(rownum, colnum)] = bone
                bone.pack(expand=YES, fill=BOTH)

    def new_game(self):
        self.place_bones()
        self.moves = -1
        self.update_moves()

    def update_moves(self):
        self.moves += 1
        self.moves_label.config(text=f"Moves: {self.moves}")


if __name__ == '__main__':
    root = Tk()
    root.title("Сука блять Пятнашки™")
    Game(root, embeeded=False).pack(expand=YES, fill=BOTH)
    root.mainloop()
Python 3.6.1 (default, Dec 2015, 13:05:11) [GCC 4.8.2] on linux