3
\$\begingroup\$

Firstly, I wrote standard snake game, then I decided to increase difficulty and made game with various snake number. My main point was to try the curses module, the second - to create game with OOP architecture, maybe apply some design pattern.

Questions:

  1. Is curses usage optimal?
  2. Should I split game() function some way (move some functionality out of there)?
  3. Where is the better place for the directions dictionary - game() function or Snake class from the design point of view?
  4. Do you see more suitable/beautiful architecture for this program? May be Game class should be added or something else.

Rules

  • A player should manage multiple snakes by switching them by keyboard.
  • Every snake has a name matched to key on keyboard: '1' -> 1, '2' -> 2, etc.
  • Snake movements are controlled by arrow keys , , ,.
  • Snakes intersection allowed for simplicity.

The number and starting parameters of snakes are hard coded for simplicity.

import curses
from random import randint

class Snake:
    def __init__(self, size, coords, g_win, color_pair_num):
        y, x = coords
        # The snake coordinates at the beginning
        self.snk = [(y, x - i) for i in range(size)]
        self.g_win = g_win
        self.hght, self.wdth = g_win.getmaxyx()
        self.key = curses.KEY_RIGHT
        self.color_pair_num = color_pair_num

        self.draw()

    def draw(self):
        for y, x in self.snk:
            self.g_win.addch(y, x, curses.ACS_CKBOARD, curses.color_pair(self.color_pair_num))
        self.g_win.refresh()

    def move_head(self, directions):
        y, x = self.snk[0]
        dy, dx = directions[self.key]
        y, x = y + dy, x + dx
        # Move out of g_win
        if y < 0 or y > self.hght or x < 0 or x > self.wdth:
            return
        # Bites itself
        if (y, x) in self.snk[2:]:
            return
        # The reverse direction movement
        if (y, x) == self.snk[1]:
            self.snk = self.snk[::-1] 
            y, x = self.snk[0]
            y, x = y + dy, x + dx

        self.snk.insert(0, (y, x))
        self.g_win.addch(y, x, curses.ACS_CKBOARD, curses.color_pair(self.color_pair_num))
        return (y, x)

    def move_tail(self):
        tail_y, tail_x = self.snk.pop()
        self.g_win.addch(tail_y, tail_x, ' ')

def change_active_snake(name, snakes, active, passive):
    for num, snake in snakes.items():
        color = active if num == name else passive
        snake.color_pair_num = color 
        snake.draw()

def game(screen):
    directions = {
                    curses.KEY_LEFT  : (+0, -1),
                    curses.KEY_RIGHT : (+0, +1),
                    curses.KEY_UP    : (-1, +0),
                    curses.KEY_DOWN  : (+1, +0)
                }

    curses.use_default_colors()
    curses.curs_set(0)

    food_cnt = 0
    speed = 1

    hght, wdth = screen.getmaxyx()
    score_win_width = 15
    game_win_width = wdth - score_win_width

    score_win = curses.newwin(hght, score_win_width, 0, game_win_width)
    score_win.border()

    game_win = curses.newwin(hght, game_win_width, 0, 0)
    game_win.keypad(1)
    game_win.border()
    interval = 500
    game_win.timeout(interval)

    game_win.refresh()
    score_win.refresh()

    active = 1
    passive = 2

    curses.init_pair(active, curses.COLOR_RED, -1)
    curses.init_pair(passive, curses.COLOR_BLUE, -1)

    x = game_win_width // 2
    snakes = {
                1 : Snake(3, (int(hght * 0.2), x), game_win, active),
                2 : Snake(3, (int(hght * 0.5), x), game_win, passive),
                3 : Snake(3, (int(hght * 0.8), x), game_win, passive)
            } 

    food = None
    snk_num = 1

    while True:
        score_win.addstr(1, 1, f"Score: {food_cnt}")
        score_win.addstr(2, 1, f"Speed: {speed}")
        score_win.refresh()

        for snk in snakes.values():
            if not food:
                y = randint(1, hght - 2)
                x = randint(1, game_win_width - 2)
                game_win.addch(y, x, curses.ACS_CKBOARD)
                food = (y, x)

            head = snk.move_head(directions)
            if head == None:
                return

            if head == food:
                food_cnt += 1
                if food_cnt % 5 == 0:
                    interval = int(interval * 0.7) 
                    game_win.timeout(interval)
                    speed += 1
                food = None
            else:
                snk.move_tail()

        new_key = game_win.getch()
        if new_key in directions:
            snakes[snk_num].key = new_key
        # 48 is the decimal ascii code of '0'
        # '1' is 49, '2' is 50, etc, so subtract 48 
        # to get snake name from new_key.
        elif new_key - 48 in snakes.keys():
            snk_num = new_key - 48
            change_active_snake(snk_num, snakes, active, passive)

        game_win.refresh()

curses.wrapper(game)

Appearance

enter image description here

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.