2

Hi so I am making a minesweeper game and I am a bit stuck with the grid generation part. This is my code so far:

from random import randint
import pygame

def MineGen():
    mineamount = 100
    grid_across = 40
    grid_up = 25
    mine_list = []
    my2dthatlist = []
    numacoss = 0
    for i in range (mineamount):
        numacoss = randint(1,40)
        my2dthatlist.append(numacoss)
        numup = randint(1,25)
        my2dthatlist.append(numup)
        mine_list.append(my2dthatlist)
        my2dthatlist = []
    return mine_list

def GridGen():
    grid_across = 40
    grid_up = 25
    GRIDD = [[0]* grid_across for i in range(grid_up)]

    return GRIDD

def MineGrid(GridOutMine, mine_list):
    mineplace = 0
    placeX = 0
    placeY = 0
    for i in range(100):
        mineplace = mine_list[i]
        placeX = mineplace[0]
        placeY = mineplace[1]
        GridOutMine[placeX][placeY] = 1
    print(GridOutMine)

mine_list = MineGen()
GridOutMine = GridGen()
MineGrid(GridOutMine, mine_list)

My issue is that i am getting a list index out of range for the

GridOutMine[placeX][placeY] = 1

part. I don't really know why this is. If you could give me some assistance in what to do, or just some general comments on my code, I would really appreciate it thanks.

whackamadoodle3000
  • 6,684
  • 4
  • 27
  • 44
crazy_angel
  • 65
  • 1
  • 10
  • You have 25 and 40 appearing at least thrice; surely, they should only appear once? That's probably wholly tangential to your problem, but… – Jonathan Leffler Jun 26 '18 at 21:02
  • Have you printed the contents of `mine_list`? What happens when you do? – Jonathan Leffler Jun 26 '18 at 21:05
  • mine list prints out as a 20 lists on 40 '0's in each – crazy_angel Jun 26 '18 at 21:16
  • That gives you a pointer to one set of problems — you need to work on the generation of the mine list and make sure you can print something other than 0,0 without cheating. You may also have to look at other issues — like ranges for indexes vs random numbers as answers have suggested — but you also need a list of not-all-zero numbers for mine positions if the game is to be interesting. Arguably, this reduces the amount of code you need, too, for the MCVE ([MCVE]) — which you've already done a decent job on AFAICS. (My Python is rusty enough that I can't help much more on the details; sorry!) – Jonathan Leffler Jun 26 '18 at 21:19
  • I replaced the `GRIDD = [[0]* grid_across for i in range(grid_up)]` part with ` GRIDD = [['NoMine']* grid_across for i in range(grid_up)] ` and the list now has all NoMine instead of 0s so that works – crazy_angel Jun 26 '18 at 21:21

2 Answers2

2

That's because, unlike range, random.randint outputs numbers within the specified bounds inclusively. That is, randint(1, 25) could output 25, which is not a valid index for a list that's only 25 elements long (since the last index is 24).

In MineGen, you need to change randint(1, 25) to randint(1, 25-1) or randint(1, 24), and likewise for randint(1, 40) which needs to be randint(1, 39). I'd actually suggest randint(0, 24) and (0, 39), but I don't know if that's intentional.

There are many other things that could/should be improved about this code, but I'd suggest you ask for that kind of input over on CodeReview instead of here once your code is working (they don't fix broken code).


EDIT:

Also, you're indexing your grid in the wrong order. It's a list (25-long) of rows (40-long), so you need to index it in the Y dimension first, then X: GridOutMine[placeY][placeX] = 1

scnerd
  • 5,836
  • 2
  • 21
  • 36
  • I did but my post got removed for being off topic. And also i tried this and i still get the error – crazy_angel Jun 26 '18 at 21:14
  • @crazy_angel It's off topic there if your code isn't working. If this fixes your bug and the code then works, post the working code there to get reviewed. Stack Overflow helps you with bugs but generally not style, Code Review helps you with style but definitely not bugs. – scnerd Jun 26 '18 at 21:16
  • @crazy_angel It would really help if you could post the actual error message and stacktrace rather than just telling us what the error type is – scnerd Jun 26 '18 at 21:17
  • `File "C:\Users\Cameron\Downloads\timer.py", line 41, in MineGrid(GridOutMine, mine_list) File "C:\Users\Cameron\Downloads\timer.py", line 35, in MineGrid GridOutMine[placeX][placeY] = 1 IndexError: list index out of range` thats the error – crazy_angel Jun 26 '18 at 21:22
  • Thanks very much! That was a stupid mistake on my part. – crazy_angel Jun 26 '18 at 21:29
1

If you are trying to build a grid for the game and want to display buttons in a GUI your users can interact with, you may want to start with the following code as a basic framework for the rest of the code you will be writing.

import tkinter
import functools

class MineSweep(tkinter.Frame):

    @classmethod
    def main(cls, width, height):
        root = tkinter.Tk()
        window = cls(root, width, height)
        root.mainloop()

    def __init__(self, master, width, height):
        super().__init__(master)
        self.__width = width
        self.__height = height
        self.__build_buttons()
        self.grid()

    def __build_buttons(self):
        self.__buttons = []
        for y in range(self.__height):
            row = []
            for x in range(self.__width):
                button = tkinter.Button(self)
                button.grid(column=x, row=y)
                button['text'] = '?'
                command = functools.partial(self.__push, x, y)
                button['command'] = command
                row.append(button)
            self.__buttons.append(row)

    def __push(self, x, y):
        print('Column = {}\nRow = {}'.format(x, y))

if __name__ == '__main__':
    MineSweep.main(10, 10)

If you want a more complete example of a minesweeper game to either borrow ideas from or adapt for your own needs, the following program implements much of the functionality you might want from a finished game.

import tkinter
import functools
import random
from tkinter.simpledialog import askstring, Dialog
from tkinter.messagebox import showinfo
import os.path

################################################################################

class MineSweep(tkinter.Frame):

    @classmethod
    def main(cls, width, height, mines, scores):
        root = tkinter.Tk()
        root.resizable(False, False)
        root.title('MineSweep')
        window = cls(root, width, height, mines, scores)
        root.protocol('WM_DELETE_WINDOW', window.close)
        root.mainloop()

################################################################################

    def __init__(self, master, width, height, mines, scores):
        super().__init__(master)
        self.__width = width
        self.__height = height
        self.__mines = mines
        self.__wondering = width * height
        self.__started = False
        self.__playing = True
        self.__scores = ScoreTable()
        self.__record_file = scores
        if os.path.isfile(scores):
            self.__scores.load(scores)
        self.__build_timer()
        self.__build_buttons()
        self.grid()

    def close(self):
        self.__scores.save(self.__record_file)
        self.quit()

    def __build_timer(self):
        self.__secs = tkinter.IntVar()
        self.__timer = tkinter.Label(textvariable=self.__secs)
        self.__timer.grid(columnspan=self.__width, sticky=tkinter.EW)
        self.__after_handle = None

    def __build_buttons(self):
        self.__reset_button = tkinter.Button(self)
        self.__reset_button['text'] = 'Reset'
        self.__reset_button['command'] = self.__reset
        self.__reset_button.grid(column=0, row=1,
                                 columnspan=self.__width, sticky=tkinter.EW)
        self.__reset_button.blink_handle = None
        self.__buttons = []
        for y in range(self.__height):
            row = []
            for x in range(self.__width):
                button = tkinter.Button(self, width=2, height=1,
                                        text='?', fg='red')
                button.grid(column=x, row=y+2)
                command = functools.partial(self.__push, x, y)
                button['command'] = command
                row.append(button)
            self.__buttons.append(row)

    def __reset(self):
        for row in self.__buttons:
            for button in row:
                button.config(text='?', fg='red')
        self.__started = False
        self.__playing = True
        self.__wondering = self.__width * self.__height
        if self.__after_handle is not None:
            self.after_cancel(self.__after_handle)
            self.__after_handle = None
        self.__secs.set(0)

    def __push(self, x, y, real=True):
        button = self.__buttons[y][x]
        if self.__playing:
            if not self.__started:
                self.__build_mines()
                while self.__buttons[y][x].mine:
                    self.__build_mines()
                self.__started = True
                self.__after_handle = self.after(1000, self.__tick)
            if not button.pushed:
                self.__push_button(button, x, y)
            elif real:
                self.__blink(button, button['bg'], 'red')
        elif real:
            self.__blink(button, button['bg'], 'red')

    def __blink(self, button, from_bg, to_bg, times=8):
        if button.blink_handle is not None and times == 8:
            return
        button['bg'] = (to_bg, from_bg)[times & 1]
        times -= 1
        if times:
            blinker = functools.partial(self.__blink, button,
                                        from_bg, to_bg, times)
            button.blink_handle = self.after(250, blinker)
        else:
            button.blink_handle = None

    def __tick(self):
        self.__after_handle = self.after(1000, self.__tick)
        self.__secs.set(self.__secs.get() + 1)

    def __push_button(self, button, x, y):
        button.pushed = True
        if button.mine:
            button['text'] = 'X'
            self.__playing = False
            self.after_cancel(self.__after_handle)
            self.__after_handle = None
            self.__blink(self.__reset_button, button['bg'], 'red')
        else:
            button['fg'] = 'SystemButtonText'
            count = self.__total(x, y)
            button['text'] = count and str(count) or ' '
            self.__wondering -= 1
            if self.__wondering == self.__mines:
                self.after_cancel(self.__after_handle)
                self.__after_handle = None
                self.__finish_game()

    def __finish_game(self):
        self.__playing = False
        score = self.__secs.get()
        for row in self.__buttons:
            for button in row:
                if button.mine:
                    button['text'] = 'X'
        if self.__scores.eligible(score):
            name = askstring('New Record', 'What is your name?')
            if name is None:
                name = 'Anonymous'
            self.__scores.add(name, score)
        else:
            showinfo('You did not get on the high score table.')
        HighScoreView(self, 'High Scores', self.__scores.listing())

    def __total(self, x, y):
        count = 0
        for x_offset in range(-1, 2):
            x_index = x + x_offset
            for y_offset in range(-1, 2):
                y_index = y + y_offset
                if 0 <= x_index < self.__width and 0 <= y_index < self.__height:
                    count += self.__buttons[y_index][x_index].mine
        if not count:
            self.__propagate(x, y)
        return count

    def __propagate(self, x, y):
        for x_offset in range(-1, 2):
            x_index = x + x_offset
            for y_offset in range(-1, 2):
                y_index = y + y_offset
                if 0 <= x_index < self.__width and 0 <= y_index < self.__height:
                    self.__push(x_index, y_index, False)

    def __build_mines(self):
        mines = [True] * self.__mines
        empty = [False] * (self.__width * self.__height - self.__mines)
        total = mines + empty
        random.shuffle(total)
        iterator = iter(total)
        for row in self.__buttons:
            for button in row:
                button.mine = next(iterator)
                button.pushed = False
                button.blink_handle = None

################################################################################

class ScoreTable:

    def __init__(self, size=10):
        self.__data = {999: [''] * size}

    def add(self, name, score):
        assert self.eligible(score)
        if score in self.__data:
            self.__data[score].insert(0, name)
        else:
            self.__data[score] = [name]
        if len(self.__data[max(self.__data)]) == 1:
            del self.__data[max(self.__data)]
        else:
            del self.__data[max(self.__data)][-1]

    def eligible(self, score):
        return score <= max(self.__data)

    def listing(self):
        for key in sorted(self.__data.keys()):
            for name in self.__data[key]:
                yield name, key

    def load(self, filename):
        self.__data = eval(open(filename, 'r').read())

    def save(self, filename):
        open(filename, 'w').write(repr(self.__data))

################################################################################

class HighScoreView(Dialog):

    def __init__(self, parent, title, generator):
        self.__scores = generator
        super().__init__(parent, title)

    def body(self, master):
        self.__labels = []
        for row, (name, score) in enumerate(self.__scores):
            label = tkinter.Label(master, text=name)
            self.__labels.append(label)
            label.grid(row=row, column=0)
            label = tkinter.Label(master, text=str(score))
            self.__labels.append(label)
            label.grid(row=row, column=1)
        self.__okay = tkinter.Button(master, command=self.ok, text='Okay')
        self.__okay.grid(ipadx=100, columnspan=2, column=0, row=row+1)
        return self.__okay

    def buttonbox(self):
        pass

################################################################################

if __name__ == '__main__':
    MineSweep.main(10, 10, 10, 'scores.txt')

Reference: ActiveState Code » Recipes » MineSweep

Noctis Skytower
  • 21,433
  • 16
  • 79
  • 117
  • thanks for the code! but the gui part im not so worried about, my friend and I already have the gui framework done using pygame. But thanks anyway! – crazy_angel Jun 26 '18 at 21:19