= How to create a game for wxPython - Part 3 (Phoenix) =
'''Keywords :''' Minesweeper, Pentomino, Sudoku.

<<TableOfContents>>

--------
= Demonstrating : =
__'''''Tested''' py3.x, wx4.x and Win10.''__

__'' ''__ Are you ready to use some samples ? ;)

Test, modify, correct, complete, improve and share your discoveries ! (!)

--------
== Minesweeper ==
{{attachment:img_minesweeper.png}}

{{{#!python
# minesweeper.py

"""

Author : ???
Website : https://www.programmersought.com/article/50683537740/

"""

import wx
from random import randint

# class MyFrame

#---------------------------------------------------------------------------

class MyFrame(wx.Frame):
    def __init__(self):
        # Set the window to a fixed size.
        wx.Frame.__init__(self, None, -1,
                          'Minesweeper',
                          size=(515, 503),
                          style=wx.SYSTEM_MENU |
                                wx.MINIMIZE_BOX |
                                wx.CLOSE_BOX |
                                wx.CAPTION)

        self.time_count = 0     # Initialize the timing to 0.
        self.mine_last = 100    # Initialize the number of mines to 100.
        self.get_mine_set()     # Get Ray's index collection.
        self.get_info_set()     # Get prompt information (index and mine number) collection.
        self.del_index_set = set()  # Store the deleted button index.
        self.get_bmp_list()     # Get bitmap object list.
        self.SetIcon(wx.Icon(self.bmp_list[9]))  # Set title icon.
        self.create_menu()      # Generate menu bar.
        self.create_panel()     # Generate artboard.
        self.create_timer()     # Generate timer.
        self.create_text_info() # Generate prompt information (thunder and time).
        self.create_state_btn() # Generate status button.
        self.create_btn_list()  # Generate all buttons.

    #-----------------------------------------------------------------------

    # Menu bar events.
    def e_menu_new(self, event):
        pass


    def e_menu_level1(self, event):
        pass


    def e_menu_level2(self, event):
        pass


    def e_menu_level3(self, event):
        pass


    def e_menu_custom(self, event):
        pass


    def e_menu_mark(self, event):
        pass


    def e_menu_color(self, event):
        pass


    def e_menu_sound(self, event):
        pass


    def e_menu_list(self, event):
        pass


    def e_menu_exit(self, event):
        self.Destroy()


    def e_menu_catalogue(self, event):
        pass


    def e_menu_search(self, event):
        pass


    def e_menu_help(self, event):
        pass


    def e_menu_about(self, event):
        dlg = wx.MessageDialog(self,
                               "This is a small game.\n",
                               "About Minesweeper",
                               wx.OK | wx.CENTRE | wx.ICON_INFORMATION)
        dlg.ShowModal()
        dlg.Destroy()


    def create_menu(self):
        menu_bar = wx.MenuBar()  # Create menu bar.

        # Game menu.
        gm_menu = wx.Menu()  # Create a menu object.
        new = gm_menu.Append(-1,"Start (N) \ tF2")  # Add submenu to menu object.
        gm_menu.AppendSeparator()  # Insert a dividing line.
        level1 = gm_menu.Append(-1,"Elementary (B)")
        level2 = gm_menu.Append(-1,"Intermediate (I)")
        level3 = gm_menu.Append(-1,"Advanced (E)")
        custom = gm_menu.Append(-1, "Custom (C) ...")
        gm_menu.AppendSeparator()  # Insert a dividing line.
        mark = gm_menu.Append(-1,"Mark (?) (M)")
        color = gm_menu.Append(-1,"Color (L)")
        sound = gm_menu.Append(-1,"Sound (S)")
        gm_menu.AppendSeparator()
        rs_list = gm_menu.Append(-1, "League of Minesweeper (T) ...")
        exit_gm = gm_menu.Append(-1, "Exit (X)")
        menu_bar.Append(gm_menu,"& Game")  # Add the File menu to the menu bar.

        # Help menu.
        self.help_menu = wx.Menu()
        catalogue = self.help_menu.Append(2,"Directory (C) \ tF1")
        self.help_menu.AppendSeparator()
        search_help = self.help_menu.Append(3,"Find Help Topic (S) ...")
        use_help = self.help_menu.Append(4,"Use Help (H)")
        self.help_menu.AppendSeparator()
        about = self.help_menu.Append(9,"About mine clearance (A) ...")
        menu_bar.Append(self.help_menu,"& Help (H)")

        # Setup menu bar.
        self.SetMenuBar(menu_bar)

        # Bind menu events.
        self.Bind(wx.EVT_MENU, self.e_menu_new, new)
        self.Bind(wx.EVT_MENU, self.e_menu_level1, level1)
        self.Bind(wx.EVT_MENU, self.e_menu_level2, level2)
        self.Bind(wx.EVT_MENU, self.e_menu_level3, level3)
        self.Bind(wx.EVT_MENU, self.e_menu_custom, custom)
        self.Bind(wx.EVT_MENU, self.e_menu_mark, mark)
        self.Bind(wx.EVT_MENU, self.e_menu_color, color)
        self.Bind(wx.EVT_MENU, self.e_menu_sound, sound)
        self.Bind(wx.EVT_MENU, self.e_menu_list, rs_list)
        self.Bind(wx.EVT_MENU, self.e_menu_exit, exit_gm)
        self.Bind(wx.EVT_MENU, self.e_menu_catalogue, catalogue)
        self.Bind(wx.EVT_MENU, self.e_menu_search, search_help)
        self.Bind(wx.EVT_MENU, self.e_menu_help, use_help)
        self.Bind(wx.EVT_MENU, self.e_menu_about, about)


    def create_panel(self):
        self.panel = wx.Panel(self)
        self.panel.Bind(wx.EVT_PAINT, self.e_paint)


    def get_mine_set(self):
        self.mine_set = set()
        while(len(self.mine_set)<100):
            self.mine_set.add(randint(0, 719))


    def get_info_set(self):
        self.info_set = set()
        info_set = set()
        for i in self.mine_set:
            info_set.update({i-31, i-30, i-29, i-1, i+1, i+29, i+30, i+31})
        for i in info_set:
            if i not in self.mine_set and i in range(0,720):
                count = 0
                for j in [i-31, i-30, i-29, i-1, i+1, i+29, i+30, i+31]:
                    if j in self.mine_set:
                        count += 1
                self.info_set.add((i,count))


    def get_bmp_list(self):
        image_list = ['smile.png', 'oh.png', 'cry.png', 'com.png', 'flag.png', 'ques.png',
                      'mine.png', 'emine.png', 'redmine.png','icon.png']
        self.bmp_list = [wx.Image(i, wx.BITMAP_TYPE_PNG).ConvertToBitmap() for i in image_list]


    def create_state_btn(self):
        self.state_btn = wx.BitmapButton(self.panel, -1, self.bmp_list[0], (240, 10), (25, 25))
        self.state_btn.Bind(wx.EVT_BUTTON, self.e_restart)


    def create_text_info(self):
        self.mine_text = wx.StaticText(self.panel, -1, '100', (25, 10))
        self.mine_text.SetFont(wx.Font(20, wx.DEFAULT, wx.FONTSTYLE_NORMAL, wx.NORMAL, faceName="Bold Body"))
        self.mine_text.SetForegroundColour(wx.RED)
        self.time_text = wx.StaticText(self.panel, -1, '000', (440, 10))
        self.time_text.SetFont(wx.Font(20, wx.DEFAULT, wx.FONTSTYLE_NORMAL, wx.NORMAL, faceName="Bold Body"))
        self.time_text.SetForegroundColour(wx.RED)


    def create_timer(self):
        self.timer = wx.Timer(self)
        self.Bind(wx.EVT_TIMER, self.e_time, self.timer)


    def draw_panel(self):
        self.dc = wx.ClientDC(self.panel)
        self.dc.SetBrush(wx.Brush(wx.Colour(192, 192, 192)))  # Set fill color.
        self.dc.SetPen(wx.GREY_PEN)  # Set line color.
        self.dc.DrawRectangle(9, 50, 480, 384)  # Draw a frame.
        for i in range(1, 24):
            self.dc.DrawLine(9, 16 * i + 50, 480 + 9, 16 * i + 50)  # Draw horizontal lines.
        for i in range(1, 30):
            self.dc.DrawLine(16 * i + 9, 50, 16 * i + 9, 384 + 50)  # Draw a vertical line.
        for i in self.mine_set:
            self.dc.DrawBitmap(self.bmp_list[6],i % 30 * 16 + 10, i // 30 * 16 + 1 + 50)  # Paint mine.
        text_color_dict = {1: '#0000ff', 2: '#008000', 3: '#ff0000', 4: '#000080', 5: '#800000',
                           6: '#e4007f', 7: '#6a3906', 8: '#000000'}  # Quantity and color dictionary.
        for i in self.info_set:
            self.dc.SetTextForeground(text_color_dict[i[1]])  # Find the corresponding color.
            self.dc.DrawText(str(i[1]), i[0] % 30 * 16 + 5 + 9, i[0] // 30 * 16 + 1 + 50)  # Show thunder.


    def create_btn_list(self):
        self.btn_list = []
        index = 0
        for i in range(0, 24):
            for j in range(0, 30):
                btn = wx.BitmapButton(self.panel,-1,self.bmp_list[3],(j * 16+9, i * 16+50),(17, 17))
                btn.index = index
                btn.state = 0
                self.btn_list.append(btn)
                index += 1
        for i in self.btn_list:
            i.Bind(wx.EVT_LEFT_DOWN,self.e_left_down)
            i.Bind(wx.EVT_LEFT_UP,self.e_left_up)
            i.Bind(wx.EVT_RIGHT_DOWN,self.e_right_down)


    def stop_game(self,index):
        self.state_btn.SetBitmap(self.bmp_list[2])
        self.timer.Stop()
        error_marked_set = set()
        for i, j in enumerate(self.btn_list):
            if i not in self.del_index_set:
                if i in self.mine_set:
                    if j.state != 1:
                        j.Destroy()
                        self.del_index_set.add(i)
                else:
                    if j.state == 1:
                        j.Destroy()
                        self.del_index_set.add(i)
                        error_marked_set.add(i)
            if i not in self.del_index_set:
                j.Unbind(wx.EVT_LEFT_UP, handler=self.e_left_up)  # The remaining buttons unbind all events.
                j.Unbind(wx.EVT_LEFT_DOWN, handler=self.e_left_down)
                j.Unbind(wx.EVT_RIGHT_DOWN, handler=self.e_right_down)
        self.panel.Unbind(wx.EVT_PAINT, handler=self.e_paint)  # Unbind drawing events.
        self.draw_panel()  # Regenerate the panel.
        self.dc.DrawBitmap(self.bmp_list[8],index % 30 * 16 + 10, index // 30 * 16 + 1 + 50)  # Draw red fried mine.
        self.dc.SetBrush(wx.TRANSPARENT_BRUSH)
        self.dc.DrawRectangle(index % 30 * 16 + 9, index // 30 * 16 + 50, 16, 16)
        for i in error_marked_set:
            self.dc.DrawBitmap(self.bmp_list[7], i % 30 * 16 + 10, i // 30 * 16 + 1 + 50)  # Paint mine.


    def del_btn(self,btn,index):
        self.state_btn.SetBitmap(self.bmp_list[0])
        if index not in self.del_index_set and btn.state != 1:
            if index in [i[0] for i in self.info_set]:
                btn.Destroy()
                self.del_index_set.add(index)
            else:
                if index not in self.mine_set:
                    btn.Destroy()
                    self.del_index_set.add(index)
                for j in [index - 31, index - 30, index - 29, index - 1, index + 1, index + 29, index + 30,
                          index + 31]:
                    if j in range(0, 720) and j not in self.mine_set \
                            and j not in self.del_index_set \
                            and self.btn_list[j].state != 1:
                        self.btn_list[j].Destroy()
                        self.del_index_set.add(j)


    def e_paint(self, event):
        self.draw_panel()


    def e_time(self, event):
        self.time_count += 1
        self.time_text.SetLabel('%03d'%self.time_count)


    def e_left_up(self, event):
        if not self.timer.IsRunning():
            self.timer.Start(1000)
        btn = event.GetEventObject()
        index = btn.index
        if btn.state == 1:
            self.state_btn.SetBitmap(self.bmp_list[0])
        else:
            if index in self.mine_set:
                self.stop_game(index)
            else:
                self.del_btn(btn,index)


    def e_left_down(self, event):
        self.state_btn.SetBitmap(self.bmp_list[1])


    def e_right_down(self, event):
        btn = event.GetEventObject()
        index = btn.index
        label = btn.state
        if label == 0:
            btn.SetBitmap(self.bmp_list[4])
            btn.state = 1
            self.mine_last -= 1
            self.mine_text.SetLabel('%03d' %self.mine_last)
        elif label == 1:
            btn.SetBitmap(self.bmp_list[5])
            btn.state = 2
            self.mine_last += 1
            self.mine_text.SetLabel('%03d' % self.mine_last)
        else:
            btn.SetBitmap(self.bmp_list[3])
            btn.state = 0


    def e_restart(self, event):
        # End.
        if self.timer.IsRunning():
            self.timer.Stop()  # Clear timer.
        for i,j in enumerate(self.btn_list):
            if i not in self.del_index_set:
                j.Destroy()  # Delete all buttons.
        # Restart.
        self.get_mine_set()
        self.get_info_set()
        self.panel.Bind(wx.EVT_PAINT, self.e_paint)
        self.time_count = 0
        self.mine_last = 100
        self.del_index_set = set()
        self.state_btn.SetBitmap(self.bmp_list[0])
        self.mine_text.SetLabel('100')
        self.time_text.SetLabel('000')
        self.create_btn_list()

#---------------------------------------------------------------------------

if __name__=='__main__':
    app = wx.App()
    frame = MyFrame()
    frame.Show()
    app.MainLoop()
}}}
--------
== Pentomino ==
{{attachment:img_pentomino.png}}

{{{#!python
# pentomino.py

"""

Pentomino puzzle solver
Author : Jean-Claude Rimbault (pynokio.org, 2005)
Modified for wx : 14-november-2011 by keiji imoto
Website : https://www.nips.ac.jp/huinfo/documents/python/python_ex02.html
Department of information physiology
National institute for physiological sciences
Okazaki, japan

"""

import wx

# class MyPentomino
# class MyFrame
# class MyPanel

#---------------------------------------------------------------------------

class MyPentomino():
    def __init__(self, parent):

        self.parent = parent
        self.w = 10
        self.h = 6
        self.board = ['#'] * 160

        for row in range(self.w):
            for col in range(self.h):
                self.board[row*10+col+11] = ' '

        self.runmode = 0
        self.n = 0

        self.pieces = [ 'C', 'X', 'T', 'Y', 'F', 'W',
                        'P', 'I', 'S', 'L', 'V', 'N' ]
        self.shapes = {
            'C': ((0, 1, 10, 20, 21),
                (0, 1, 11, 20, 21),
                (0, -10, -9, -8, 2),
                (0, 10, 11, 12, 2)),
            'F': ((0, 1, -9, 2, 12),
                (0, 1, 11, 2, -8),
                (0, 10, 11, 21, 12),
                (0, -10, -9, -19, -8),
                (0, 1, 11, 21, 12),
                (0, 1, -9, -19, -8),
                (0, 1, 11, -9, 12),
                (0, 1, 11, -9, -8)),
            'I': ((0, 1, 2, 3, 4),
                (0, 10, 20, 30, 40)),
            'L': ((0, 1, 2, 3, 13),
                (0, 1, 2, 3, -7),
                (0, 10, 20, 30, 1),
                (0, -10, -20, -30, 1),
                (0, 1, 11, 21, 31),
                (0, 1, -9, -19, -29),
                (0, 10, 1, 2, 3),
                (0, -10, 1, 2, 3)),
            'N': ((0, 1, 11, 12, 13),
                (0, 1, -9, -8, -7),
                (0, 1, 2, 12, 13),
                (0, 1, 2, -8, -7),
                (0, 10, 20, 21, 31),
                (0, 10, 20, 19, 29),
                (0, 10, 11, 21, 31),
                (0, 10, 9, 19, 29)),
            'P': ((0, 1, 2, 11, 12),
                (0, 1, 2, -9, -8),
                (0, 1, 2, 10, 11),
                (0, 1, 2, -10, -9),
                (0, 1, 10, 11, 20),
                (0, 1, 10, 11, 21),
                (0, 1, -10, -9, -20),
                (0, 1, -10, -9, -19)),
            'S': ((0, 1, 11, 21, 22),
                (0, 1, -9, -19, -18),
                (0, 10, 11, 12, 22),
                (0, -10, -9, -8, -18)),
            'T': ((0, 1, 11, 21, 2),
                (0, 1, -9, -19, 2),
                (0, 1, 2, -8, 12),
                (0, 10, -10, 1, 2)),
            'V': ((0, 1, 2, 12, 22),
                (0, 1, 2, -8, -18),
                (0, 1, 2, 10, 20),
                (0, 1, 2, -10, -20)),
            'W': ((0, 1, 11, 12, 22),
                (0, 1, -9, -8, -18),
                (0, 10, 11, 21, 22),
                (0, -10, -9, -19, -18)),
            'X': ((0, -9, 1, 11, 2),),
            'Y': ((0, 1, 2, 3, 12),
                (0, 1, 2, 3, -8),
                (0, 10, 20, 30, 11),
                (0, -10, -20, -30, -9),
                (0, 1, 11, 21, -9),
                (0, 1, -9, -19, 11),
                (0, 11, 1, 2, 3),
                (0, -9, 1, 2, 3))
        }

    #-----------------------------------------------------------------------

    def display(self):
        self.parent.statusbar.SetStatusText('n = ' + str(self.n))
        self.panel.Refresh()
        self.panel.Update()

#
# main loop
#
    def solve(self):
        for q in range(len(self.board)):
            try:
                if self.board[q] == ' ':
                    for p in self.pieces:
                        for s in self.shapes[p]:
                            for c in s:
                                for d in s:
                                    if self.board[q+d-c] != ' ':
                                        break
                                else:
                                    for d in s:
                                        self.board[q+d-c] = p
                                    i = self.pieces.index(p)
                                    self.pieces.remove(p)
                                    if not self.pieces:
                                        self.n += 1
                                        self.display()
                                    else:
                                        if self.runmode:
                                            self.display()
                                        self.solve()
                                    self.pieces.insert(i, p)
                                    for d in s:
                                        self.board[q+d-c] = ' '
                    return
            except(KeyboardInterrupt, SystemExit):
                raise


    def start(self, event):
        self.solve()

#---------------------------------------------------------------------------

class MyFrame(wx.Frame):
    def __init__(self, parent, id, title, ww, wh):
        wx.Frame.__init__(self, parent, id,
                          title, size=(ww, wh),
                          style=wx.SYSTEM_MENU |
                                wx.MINIMIZE_BOX |
                                wx.CLOSE_BOX |
                                wx.CAPTION)

        self.SetIcon(wx.Icon('./icons/wxwin.ico', wx.BITMAP_TYPE_ICO))

        #------------

        self.pen = MyPentomino(self)

        self.panel = MyPanel(self, self.pen)
        self.pen.panel = self.panel

        self.isRunning = False


        self.statusbar = self.CreateStatusBar()
        self.startButton = wx.Button(self.panel, wx.ID_ANY, '&Start', (140, 240))
        self.Bind(wx.EVT_BUTTON, self.buttonControl)

        self.Centre()
        self.Show(True)

    #-----------------------------------------------------------------------

    def buttonControl(self, event):

        if self.isRunning == False:
            self.pen.start(event)
            isRunning = True


#---------------------------------------------------------------------------

class MyPanel(wx.Panel):
    def __init__(self, parent, pen):
        wx.Panel.__init__(self, parent)

        self.pen = pen
        self.w = self.pen.w
        self.h = self.pen.h

        self.Bind(wx.EVT_PAINT, self.OnPaint)

        self.SetFocus()

        self.colors = {
            ' ': (0,0,0),
            'X': (0xEF, 0x84, 0x5c),
            'T': (0xF9, 0xC2, 0x70),
            'C': (0xFF, 0xF6, 0x7F),
            'W': (0xC1, 0xDB, 0x81),
            'I': (0x69, 0xBD, 0x83),
            'S': (0x61, 0xC1, 0xBE),
            'F': (0x54, 0xC3, 0xF1),
            'P': (0x6C, 0x9B, 0xD2),
            'L': (0x79, 0x6B, 0xAF),
            'V': (0xBA, 0x79, 0xB1),
            'Y': (0xEE, 0x87, 0xB4),
            'N': (0xEF, 0x85, 0x8C),
        }

        self.light = {
            ' ': (0,0,0),
            'X': (0xF8, 0xC5, 0xAC),
            'T': (0xFC, 0xE2, 0xBA),
            'C': (0xFF, 0xFB, 0xC7),
            'W': (0xE2, 0xEE, 0xC5),
            'I': (0xBE, 0xDF, 0xC2),
            'S': (0xBC, 0xE1, 0xDF),
            'F': (0xBA, 0xE3, 0xF9),
            'P': (0xBB, 0xCC, 0xE9),
            'L': (0xBB, 0xB3, 0xD8),
            'V': (0xDB, 0xBE, 0xDA),
            'Y': (0xF7, 0xC9, 0xDD),
            'N': (0xF7, 0xC7, 0xC6),
        }

        self.dark = {
            ' ': (0, 0, 0),
            'X': (0xE6, 0x00, 0x12),
            'T': (0xF3, 0x98, 0x00),
            'C': (0xFF, 0xF1, 0x00),
            'W': (0x8F, 0xC3, 0x1F),
            'I': (0x00, 0x99, 0x44),
            'S': (0x00, 0x9E, 0x96),
            'F': (0x00, 0xA0, 0xE9),
            'P': (0x00, 0x68, 0xB7),
            'L': (0x1D, 0x20, 0x88),
            'V': (0x92, 0x07, 0x83),
            'Y': (0xE4, 0x00, 0x7F),
            'N': (0xE5, 0x00, 0x4F),
        }

    #-----------------------------------------------------------------------

    def OnPaint(self, event):
        dc = wx.PaintDC(self)
        for col in range(self.h):
            for row in range(self.w):
                x = 30*row + 20
                y = 30*col + 40
                c = self.pen.board[row*10+col+11]

                dc.SetBrush(wx.Brush(self.colors[c]))
                dc.DrawRectangle(x, y, 30, 30)

                dc.SetPen(wx.Pen(self.light[c]))
                dc.DrawLine(x, y + 29, x, y)
                dc.DrawLine(x, y, x + 29, y)

                dc.SetPen(wx.Pen(self.dark[c]))
                dc.DrawLine(x + 1, y + 29, x + 29, y + 29)
                dc.DrawLine(x + 29, y + 29, x + 29, y + 1)

#---------------------------------------------------------------------------

def main():
    app   = wx.App()
    frame = MyFrame(None, -1, 'Pentomino puzzle solver', 355, 340)
    app.MainLoop()

#---------------------------------------------------------------------------

if __name__ == '__main__':
    main()
}}}
--------
== Sudoku ==
{{attachment:img_sudoku.png}}

Link : https://www.daniweb.com/programming/software-development/tutorials/520379/how-to-write-a-sudoku-gui-in-python-wxpython

--------

= Download source =
[[attachment:source.zip]]

--------
= Additional Information =
'''Link :'''

https://wiki.python.org/moin/GameProgramming

http://mientki.ruhosting.nl/data_www/pylab_works/pw_bricks_2d_scene.html

- - - - -

https://wiki.wxpython.org/TitleIndex

https://docs.wxpython.org/

--------
= Thanks to =
??? (minesweeper.py coding), Jean-Claude Rimbault (pentomino.py coding),  Reverend Jim & Daniweb (sudoku.py coding), the wxPython community...

--------
= About this page =
Date(d/m/y)     Person (bot)    Comments :

12/02/21 - Ecco (Created page for wxPython Phoenix).

--------
= Comments =
- blah, blah, blah...