Welcome, Guest. Please login or register.

Login with username, password and session length

 
Advanced search

1411512 Posts in 69376 Topics- by 58430 Members - Latest Member: Jesse Webb

April 26, 2024, 10:27:30 PM

Need hosting? Check out Digital Ocean
(more details in this thread)
TIGSource ForumsDeveloperTechnical (Moderator: ThemsAllTook)Restarting a game
Pages: [1]
Print
Author Topic: Restarting a game  (Read 4554 times)
ralphgr77
Level 0
*


View Profile
« on: March 23, 2023, 08:55:16 PM »

Hi all,

I started recently with pygame and I'm developing a 'basic' game. Since I'm testing all the time and need to see how it behaves, specially at the beginning, a question mark already appeared in my head, which is how it is the usual procedure to 'restart' a game. Let's say, you have a menu that you can click, and then click on the submenu "New Game". So all the variables  and initial settings will roll back as if you're running the code for the first time.

I did a little bit of research but all I could find was people recommending making a function to reset the values of all variables. Problem is... my game, although 'basic' (it is starting to get complex, hence the quotes), it's having already quite a lot of variables. The complexity probably would grow as I'm having new ideas and so will the number of variables. So I'm finding the solution of having ALL variables copied and pasted into a function with the default values that can be called later on to 'restart' not very appealing.

So how it is done on complex games?

Thanks in advance!
Logged
ThemsAllTook
Administrator
Level 10
******



View Profile WWW
« Reply #1 on: March 24, 2023, 05:51:18 AM »

This would be a matter of having a layer of abstraction around state management. How I usually handle this is to use a "game state" object, which is a mutable collection of all of the nonvisual core logic variables involved in running the game. For some games, the game state object is the same thing as how I store my level data, so starting or restarting is a matter of making a fresh copy of the immutable level data object.

In more recent projects, I've kept an immutable "game data" object, which is organized a bit differently from game state, and only stores data related to the initial state of a level without including anything involved in running it. Game state would have a function that knows how to initialize itself from game data.

In addition to this, it's crucial to partition your code in such a way that logic updates are cleanly separated from visuals and user input. The pattern I follow is that "view code" (input, visuals, and ideally sounds) reads from game state and calls functions on it to indicate that the player did something, but doesn't mutate it directly. State code doesn't know anything about view code other than that something might be calling functions on it at some point to tell it when things happen, and it might be necessary to dispatch events when things that would be of interest to a visual representation have occurred.

With this pattern, restarting is just a matter of throwing away the current game state and view objects and creating new ones based on the immutable data you've loaded from disk for your level's initial state. Sometimes there are a few extra complications, like effects that need to persist through a reset, but those can be added as special cases on top of that basic pattern.
Logged

michaelplzno
Level 10
*****



View Profile WWW
« Reply #2 on: April 04, 2023, 12:32:14 PM »

For me, usually the question you are asking here is more "how do I clear out the current game?"

The first time the game starts, it runs your startup procedure on an "empty" game. Thus to re-start, you simply have to get your game back to being "empty" and run the start procedure again.

Clearing usually means destroying all allocated game objects which I usually keep in lists inside singletons that manage different types of objects.
Logged

ralphgr77
Level 0
*


View Profile
« Reply #3 on: April 14, 2023, 05:51:47 PM »

Sorry for the late reply, but after I read both of your replies, I went and look for what I could find on game states and how to organize the code around it. Problem is I'm quite new to coding. My background is more on computer graphics and I'm trying to get my head around all these concepts. I did find a few examples of how some people were doing BUT it went way over my head. I'm either too dumb or way too inexperienced to understand why they were doing the way they were doing.

Since I was struggling to implement on the game I was doing, I decided to try something different. I wrote a way simpler game, a tic tac toe using pygame and see if I could implement there but after a few tries, I'm either too tired or I need more pointers. I can even paste the code I did for tic tac toe. It's functional apart from being able to click on the new game button after is finishing because I simply don't know how to tackle it. I'll paste it here, maybe some of you would have the patience to read my terrible newbie code and give me some pointers. Many thanks already for the hints. If you copy and paste it, you should be able to run it. I placed a few comments for better understanding my craziness. Cheers!

import pygame as pg
from pygame.locals import *
import sys

pg.init()
WW, WH = 600,600
screen = pg.display.set_mode((WW,WH))
pg.display.set_caption('Tic Tac Toe')
# pg.display.set_icon(pg.image.load('tictactoe_logo_32x32.png').convert())
clock = pg.time.Clock()


# Areas established just to use as rect collide information on events
A1_area = pg.draw.rect(screen,'red', (0,0,200,200))
A2_area = pg.draw.rect(screen,'red', (200,0,200,200))
A3_area = pg.draw.rect(screen,'red', (400,0,200,200))

B1_area = pg.draw.rect(screen,'red', (0,200,200,200))
B2_area = pg.draw.rect(screen,'red', (200,200,200,200))
B3_area = pg.draw.rect(screen,'red', (400,200,200,200))

C1_area = pg.draw.rect(screen,'red', (0,400,200,200))
C2_area = pg.draw.rect(screen,'red', (200,400,200,200))
C3_area = pg.draw.rect(screen,'red', (400,400,200,200))

gameover_dialog_surf = pg.Surface((500,240), pg.SRCALPHA)
gameover_dialog_surf.fill((100,200,100,245))
gameover_dialog_rect = (50,180)

# Button class for game over dialog buttons New and Quit
class Button:
   def __init__(self, text, box_width, box_height, pos_x, pos_y):
      self.text = text
      self.box_width = box_width
      self.box_height = box_height
      self.pos_x = pos_x
      self.pos_y = pos_y

      self.button_surf = pg.Surface((self.box_width, self.box_height))
      self.button_surf.fill(('white'))
      self.button_rect = self.button_surf.get_rect(center = (self.pos_x, self.pos_y))


      font = pg.font.SysFont(None, 36)
      self.text_surf = font.render(self.text, False, 'black')
      self.text_rect = self.text_surf.get_rect(center = (self.pos_x, self.pos_y))

      self.clicked = False
      self.click_timer = 0
      self.click_delay = 100

   def display(self):
      screen.blit(self.button_surf, self.button_rect)
      screen.blit(self.text_surf, self.text_rect)

   def is_clicked(self):
      current_time = pg.time.get_ticks()
      if pg.mouse.get_pressed()[0]:
         pos = pg.mouse.get_pos()
         if self.button_rect.collidepoint(pos) and self.clicked == False:
            print ('clicked')
            self.clicked = True
            self.click_timer = current_time
            return True

      else:
         if self.clicked and current_time - self.click_timer > self.click_delay:
            self.clicked = False
            return False

button_new = Button('New Game', 180, 50, 200, 320)
button_quit = Button('Quit', 100, 50, 400, 320)

# Marks an X
class X:
   def __init__(self, offset_x, offset_y):
      self.offset_x = offset_x
      self.offset_y = offset_y

   def display(self):
      pg.draw.line(screen,'black', (40 + self.offset_x, 40 + self.offset_y),(160 + self.offset_x, 160 + self.offset_y),(16))
      pg.draw.line(screen,'black', (40 + self.offset_x, 160 + self.offset_y),(160 + self.offset_x, 40 + self.offset_y),(16))

# Marks an O
class O:
   def __init__(self, offset_x, offset_y):
      self.offset_x = offset_x
      self.offset_y = offset_y

   def display(self):
      pg.draw.circle(screen, 'red', (100 + self.offset_x, 100 + self.offset_y), 75, 10)


def draw(var):
   if globals()[var + '_clicked']:
      if globals()[var] == 'x':
         globals()[var + 'x'].display()
      if globals()[var] == 'o':
         globals()[var + 'o'].display()


def click(var):
   global click_nr
   if not globals()[var + '_clicked']:
      if globals()[var +'_area'].collidepoint(mouse_pos):
         globals()[var + '_clicked'] = True
         click_nr += 1
         if click_nr % 2 == 0:
            globals()[var] = 'x'
         else:
            globals()[var] = 'o'

# Instances of O and X classes for the positioning of Xs and Os in all squares
A1x = X(0,0)
A2x = X(200,0)
A3x = X(400,0)

B1x = X(0,200)
B2x = X(200,200)
B3x = X(400,200)

C1x = X(0,400)
C2x = X(200,400)
C3x = X(400,400)


A1o = O(0,0)
A2o = O(200,0)
A3o = O(400,0)

B1o = O(0,200)
B2o = O(200,200)
B3o = O(400,200)

C1o = O(0,400)
C2o = O(200,400)
C3o = O(400,400)


# Used to know if square was clicked or not
A1_clicked = False
A2_clicked = False
A3_clicked = False

B1_clicked = False
B2_clicked = False
B3_clicked = False

C1_clicked = False
C2_clicked = False
C3_clicked = False

click_nr = 1 # Odds or even numbers will be used to choose either X or O

gameover = False

# variables for strike lines
A1 = None
A2 = None
A3 = None

B1 = None
B2 = None
B3 = None

C1 = None
C2 = None
C3 = None

def strikes(player):
   global gameover
   if A1 == player and A2 == player and A3 == player:
      pg.draw.line(screen,'red', (20,100),(580,100),(10))
      gameover = True

   if B1 == player and B2 == player and B3 == player:
      pg.draw.line(screen,'red', (20,300),(580,300),(10))
      gameover = True

   if C1 == player and C2 == player and C3 == player:
      pg.draw.line(screen,'red', (20,500),(580,500),(10))
      gameover = True


   if A1 == player and B1 == player and C1 == player:
      pg.draw.line(screen,'red', (100,20),(100,580),(10))
      gameover = True

   if A2 == player and B2 == player and C2 == player:
      pg.draw.line(screen,'red', (300,20),(300,580),(10))
      gameover = True

   if A3 == player and B3 == player and C3 == player:
      pg.draw.line(screen,'red', (500,20),(500,580),(10))
      gameover = True


   if A1 == player and B2 == player and C3 == player:
      pg.draw.line(screen,'red', (20,20),(580,580),(10))
      gameover = True

   if A3 == player and B2 == player and C1 == player:
      pg.draw.line(screen,'red', (20,580),(580,20),(10))
      gameover = True


running = True

while running:
   for event in pg.event.get():
      if event.type == pg.QUIT or pg.key.get_pressed()[K_ESCAPE]:
         running = False

      # mouse events
      if event.type == pg.MOUSEBUTTONUP:         
         mouse_pos = pg.mouse.get_pos()
         if not gameover:
            click('A1')
            click('A2')
            click('A3')

            click('B1')
            click('B2')
            click('B3')

            click('C1')
            click('C2')
            click('C3')

      # Shortcut to start a new game ctrl + n
      if event.type == pg.KEYDOWN:
         if event.key == pg.K_n and pg.key.get_mods() & pg.KMOD_CTRL:
            print("CTRL + N was pressed for a New Game")
            # new_game = True
            # THAT'S THE PART I DON'T KNOW HOW TO DO


   screen.fill((200,200,200))
   pg.draw.line(screen,'black', (10,200),(590,200),(4))
   pg.draw.line(screen,'black', (10,400),(590,400),(4))
   pg.draw.line(screen,'black', (200,10),(200,590),(4))
   pg.draw.line(screen,'black', (400,10),(400,590),(4))

   # Xs and Os are drawn on screen
   draw('A1')
   draw('A2')
   draw('A3')

   draw('B1')
   draw('B2')
   draw('B3')

   draw('C1')
   draw('C2')
   draw('C3')
   
   # Red strike lines are drawn on the scren
   strikes('x')
   strikes('o')


   # game over dialog screen that appears on top
   if gameover:
      screen.blit(gameover_dialog_surf, gameover_dialog_rect)

      font = pg.font.SysFont(None, 72)
      gameover_text_surf = font.render('Game Over', False, 'white')
      gameover_text_rect = gameover_text_surf.get_rect(center = (300,240))
      screen.blit (gameover_text_surf, gameover_text_rect)

      button_new.display()
      if button_new.is_clicked():
         # DON'T KNOW HOW TO IMPLEMENT THIS PART
         pass

      button_quit.display()
      if button_quit.is_clicked():
         running = False

   pg.display.update()

pg.quit()
sys.exit()
Logged
Thaumaturge
Level 10
*****



View Profile WWW
« Reply #4 on: April 17, 2023, 04:18:55 AM »

I'll confess that your style is one that I'm not all that great at reading (which, to be clear, is to do with my reading it, not your writing it), but from the looks of it the main thing to do on reset would be to clear your "A1", "A2", etc. variables, and to redraw your screen.

In short, it looks like you're taking a relatively-functional approach (as opposed to a relatively-class-based approach), in which case resetting will I think tend to be a matter of going over your various variables and resetting their values to those that they had at the start of the game.

(In a class-based approach, one might instead keep lists of class-instances, and on resetting destroy those instances in order to make way for new ones.)
Logged

ralphgr77
Level 0
*


View Profile
« Reply #5 on: April 17, 2023, 05:19:17 PM »

the part of resetting the classes and destroying the instances that I'm confused. So if I would as in this example below, simply reload new_game(), would that reconstruct all the variables with their default values?


class Game(Settings):
   def __init__(self):
      super().__init__()
      self.new_game()
      self.gmouse.draw()


   def new_game(self):
      self.gmouse = gMouse(self)
      self.bg = Bg(self)
      self.date = Date(self)
      self.gameclock = GameClock(self)
      self.menufile = MenuFile(self)
      self.menuwindow = MenuWindow(self)
      self.menuhelp = MenuHelp(self)
      self.windowlabel = WindowLabel(self)
      self.button_makehuman = Button('Make Human', 10, 72, self)
      self.pop = Pop(self)
      self.humans = Humans(self)
      self.habitat = Habitat(self)
      self.log = Log(4, 33, self)
      self.pause = Pause(self)
Logged
michaelplzno
Level 10
*****



View Profile WWW
« Reply #6 on: April 18, 2023, 07:47:57 AM »

you may not be using Object Oriented Programming, which involves destroying chunks of memory that are "objects" but the goal you want is to reset the game state.

Essentially you want a function "Init()" that does all the one time initialization stuff like setting up your mouse handlers and such. Then you want a "StartLevel()" function that sets up all the stuff that is part of the game state of the level. As long as StartLevel() resets all the state information that is part of the game you can call it over and over to restart the game.

Logged

Thaumaturge
Level 10
*****



View Profile WWW
« Reply #7 on: April 24, 2023, 02:11:30 AM »

the part of resetting the classes and destroying the instances that I'm confused. So if I would as in this example below, simply reload new_game(), would that reconstruct all the variables with their default values?

Does anything in there reset the contents of the "A1", "A2", etc. variables? (And all the other "A1<something here>", "A2<something here>", etc. variables.) I don't see that happening--but again, I fear that I'm not great at reading your style, and it looks like your might have it in mind to create new classes which I imagine might contain these things.

But otherwise, I think that michaelplzno is pretty much correct, above: it would make sense to me to create a function or set of functions that perform initialisation, and which can then be called in order to perform reinitialisation.

And indeed, that does look like what you're in the process of doing with your most-recent post! ^_^

(If any of your objects require cleanup--things like removing assets from the "world" or breaking cyclic links, etc.--then make a point of doing so before reinitialising. Of course, if your program requires none such, then you perhaps needn't worry about this!)
Logged

Pages: [1]
Print
Jump to:  

Theme orange-lt created by panic