Advertisement
nonogamer9

nono CHIP-8 (A Extremely Light CHIP-8 Emulator Written In Python)

Feb 3rd, 2025 (edited)
113
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 18.49 KB | Software | 0 0
  1. import pygame
  2. import random
  3. import sys
  4. import time
  5. import numpy as np
  6. import tkinter as tk
  7. from tkinter import filedialog
  8. import os
  9.  
  10. class Chip8:
  11.     def __init__(self):
  12.         self.memory = bytearray(4096)
  13.         self.V = bytearray(16)
  14.         self.I = 0
  15.         self.pc = 0x200
  16.         self.stack = []
  17.         self.delay_timer = 0
  18.         self.sound_timer = 0
  19.         self.keypad = [0] * 16
  20.         self.opcode = 0
  21.         self.draw_flag = False
  22.         self.is_super_chip8 = False
  23.         self.is_xo_chip = False
  24.         self.high_res = False
  25.         self.display_planes = [np.zeros((64, 128), dtype=np.uint8) for _ in range(2)]
  26.         self.flag_registers = bytearray(16)
  27.         self.plane_mask = 1
  28.         self.audio_buffer = bytearray(16)
  29.         self.pitch = 4000
  30.        
  31.         self.font = bytes([
  32.             0xF0, 0x90, 0x90, 0x90, 0xF0,  # 0
  33.             0x20, 0x60, 0x20, 0x20, 0x70,  # 1
  34.             0xF0, 0x10, 0xF0, 0x80, 0xF0,  # 2
  35.             0xF0, 0x10, 0xF0, 0x10, 0xF0,  # 3
  36.             0x90, 0x90, 0xF0, 0x10, 0x10,  # 4
  37.             0xF0, 0x80, 0xF0, 0x10, 0xF0,  # 5
  38.             0xF0, 0x80, 0xF0, 0x90, 0xF0,  # 6
  39.             0xF0, 0x10, 0x20, 0x40, 0x40,  # 7
  40.             0xF0, 0x90, 0xF0, 0x90, 0xF0,  # 8
  41.             0xF0, 0x90, 0xF0, 0x10, 0xF0,  # 9
  42.             0xF0, 0x90, 0xF0, 0x90, 0x90,  # A
  43.             0xE0, 0x90, 0xE0, 0x90, 0xE0,  # B
  44.             0xF0, 0x80, 0x80, 0x80, 0xF0,  # C
  45.             0xE0, 0x90, 0x90, 0x90, 0xE0,  # D
  46.             0xF0, 0x80, 0xF0, 0x80, 0xF0,  # E
  47.             0xF0, 0x80, 0xF0, 0x80, 0x80   # F
  48.         ])
  49.        
  50.         self.memory[:len(self.font)] = self.font
  51.  
  52.         self.big_font = bytes([
  53.             0x7C, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x7C, 0x00,  # 0
  54.             0x08, 0x18, 0x38, 0x08, 0x08, 0x08, 0x08, 0x08, 0x3C, 0x00,  # 1
  55.             0x7C, 0x82, 0x02, 0x02, 0x04, 0x18, 0x20, 0x40, 0xFE, 0x00,  # 2
  56.             0x7C, 0x82, 0x02, 0x02, 0x3C, 0x02, 0x02, 0x82, 0x7C, 0x00,  # 3
  57.             0x84, 0x84, 0x84, 0x84, 0xFE, 0x04, 0x04, 0x04, 0x04, 0x00,  # 4
  58.             0xFE, 0x80, 0x80, 0x80, 0xFC, 0x02, 0x02, 0x82, 0x7C, 0x00,  # 5
  59.             0x7C, 0x82, 0x80, 0x80, 0xFC, 0x82, 0x82, 0x82, 0x7C, 0x00,  # 6
  60.             0xFE, 0x02, 0x04, 0x08, 0x10, 0x20, 0x20, 0x20, 0x20, 0x00,  # 7
  61.             0x7C, 0x82, 0x82, 0x82, 0x7C, 0x82, 0x82, 0x82, 0x7C, 0x00,  # 8
  62.             0x7C, 0x82, 0x82, 0x82, 0x7E, 0x02, 0x02, 0x82, 0x7C, 0x00,  # 9
  63.             0x38, 0x44, 0x82, 0x82, 0x82, 0xFE, 0x82, 0x82, 0x82, 0x00,  # A
  64.             0xFC, 0x82, 0x82, 0x82, 0xFC, 0x82, 0x82, 0x82, 0xFC, 0x00,  # B
  65.             0x7C, 0x82, 0x80, 0x80, 0x80, 0x80, 0x80, 0x82, 0x7C, 0x00,  # C
  66.             0xFC, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0xFC, 0x00,  # D
  67.             0xFE, 0x80, 0x80, 0x80, 0xF8, 0x80, 0x80, 0x80, 0xFE, 0x00,  # E
  68.             0xFE, 0x80, 0x80, 0x80, 0xF8, 0x80, 0x80, 0x80, 0x80, 0x00   # F
  69.         ])
  70.  
  71.         self.memory[len(self.font):len(self.font) + len(self.big_font)] = self.big_font
  72.  
  73.         self.opcode_table = {
  74.             0x0000: self._0xxx,
  75.             0x1000: self._1nnn,
  76.             0x2000: self._2nnn,
  77.             0x3000: self._3xnn,
  78.             0x4000: self._4xnn,
  79.             0x5000: self._5xy0,
  80.             0x6000: self._6xnn,
  81.             0x7000: self._7xnn,
  82.             0x8000: self._8xxx,
  83.             0x9000: self._9xy0,
  84.             0xA000: self._Annn,
  85.             0xB000: self._Bnnn,
  86.             0xC000: self._Cxnn,
  87.             0xD000: self._Dxyn,
  88.             0xE000: self._Exxx,
  89.             0xF000: self._Fxxx,
  90.         }
  91.  
  92.     def load_rom(self, filename):
  93.         with open(filename, 'rb') as f:
  94.             rom_data = f.read()
  95.         self.memory[0x200:0x200 + len(rom_data)] = rom_data
  96.        
  97.         self.is_super_chip8 = self._detect_super_chip8(rom_data)
  98.         self.is_xo_chip = self._detect_xo_chip(rom_data)
  99.  
  100.     def _detect_super_chip8(self, rom_data):
  101.         return any(opcode in [0x00FD, 0x00FE, 0x00FF] or (opcode & 0xF000) == 0xF000 and (opcode & 0x00F0) == 0x0030
  102.                    for opcode in ((rom_data[i] << 8) | rom_data[i + 1] for i in range(0, len(rom_data), 2)))
  103.  
  104.     def _detect_xo_chip(self, rom_data):
  105.         return any((opcode & 0xF000) == 0xF000 and (opcode & 0x00FF) in [0x00, 0x01, 0x02, 0x3A]
  106.                    for opcode in ((rom_data[i] << 8) | rom_data[i + 1] for i in range(0, len(rom_data), 2)))
  107.  
  108.     def emulate_cycle(self):
  109.         self.opcode = (self.memory[self.pc] << 8) | self.memory[self.pc + 1]
  110.         self.pc += 2
  111.  
  112.         op_group = self.opcode & 0xF000
  113.         self.opcode_table[op_group]()
  114.  
  115.     def _0xxx(self):
  116.         if self.opcode == 0x00E0:
  117.             self.display_planes = [np.zeros((64, 128), dtype=np.uint8) for _ in range(2)]
  118.             self.draw_flag = True
  119.         elif self.opcode == 0x00EE:
  120.             if self.stack:
  121.                 self.pc = self.stack.pop()
  122.             else:
  123.                 print("Warning: Attempted to return from an empty stack. Ignoring instruction.")
  124.         elif self.is_super_chip8 or self.is_xo_chip:
  125.             if self.opcode == 0x00FB:
  126.                 self._scroll_right()
  127.             elif self.opcode == 0x00FC:
  128.                 self._scroll_left()
  129.             elif self.opcode == 0x00FD:
  130.                 sys.exit()
  131.             elif self.opcode == 0x00FE:
  132.                 self._toggle_high_res(False)
  133.             elif self.opcode == 0x00FF:
  134.                 self._toggle_high_res(True)
  135.             elif (self.opcode & 0xFFF0) == 0x00C0:
  136.                 self._scroll_down(self.opcode & 0x000F)
  137.             elif (self.opcode & 0xFFF0) == 0x00D0 and self.is_xo_chip:
  138.                 self._scroll_up(self.opcode & 0x000F)
  139.  
  140.     def _1nnn(self):
  141.         self.pc = self.opcode & 0x0FFF
  142.  
  143.     def _2nnn(self):
  144.         self.stack.append(self.pc)
  145.         self.pc = self.opcode & 0x0FFF
  146.  
  147.     def _3xnn(self):
  148.         x = (self.opcode & 0x0F00) >> 8
  149.         nn = self.opcode & 0x00FF
  150.         if self.V[x] == nn:
  151.             self.pc += 2
  152.  
  153.     def _4xnn(self):
  154.         x = (self.opcode & 0x0F00) >> 8
  155.         nn = self.opcode & 0x00FF
  156.         if self.V[x] != nn:
  157.             self.pc += 2
  158.  
  159.     def _5xy0(self):
  160.         x = (self.opcode & 0x0F00) >> 8
  161.         y = (self.opcode & 0x00F0) >> 4
  162.         n = self.opcode & 0x000F
  163.         if n == 0x0:
  164.             if self.V[x] == self.V[y]:
  165.                 self.pc += 2
  166.         elif self.is_xo_chip:
  167.             if n == 0x2:  # save vx - vy
  168.                 self.memory[self.I:self.I + y - x + 1] = self.V[x:y + 1]
  169.             elif n == 0x3:  # load vx - vy
  170.                 self.V[x:y + 1] = self.memory[self.I:self.I + y - x + 1]
  171.  
  172.     def _6xnn(self):
  173.         x = (self.opcode & 0x0F00) >> 8
  174.         nn = self.opcode & 0x00FF
  175.         self.V[x] = nn
  176.  
  177.     def _7xnn(self):
  178.         x = (self.opcode & 0x0F00) >> 8
  179.         nn = self.opcode & 0x00FF
  180.         self.V[x] = (self.V[x] + nn) & 0xFF
  181.  
  182.     def _8xxx(self):
  183.         x = (self.opcode & 0x0F00) >> 8
  184.         y = (self.opcode & 0x00F0) >> 4
  185.         n = self.opcode & 0x000F
  186.         if n == 0x0:
  187.             self.V[x] = self.V[y]
  188.         elif n == 0x1:
  189.             self.V[x] |= self.V[y]
  190.         elif n == 0x2:
  191.             self.V[x] &= self.V[y]
  192.         elif n == 0x3:
  193.             self.V[x] ^= self.V[y]
  194.         elif n == 0x4:
  195.             sum = self.V[x] + self.V[y]
  196.             self.V[0xF] = 1 if sum > 0xFF else 0
  197.             self.V[x] = sum & 0xFF
  198.         elif n == 0x5:
  199.             self.V[0xF] = 1 if self.V[x] > self.V[y] else 0
  200.             self.V[x] = (self.V[x] - self.V[y]) & 0xFF
  201.         elif n == 0x6:
  202.             self.V[0xF] = self.V[x] & 0x1
  203.             self.V[x] >>= 1
  204.         elif n == 0x7:
  205.             self.V[0xF] = 1 if self.V[y] > self.V[x] else 0
  206.             self.V[x] = (self.V[y] - self.V[x]) & 0xFF
  207.         elif n == 0xE:
  208.             self.V[0xF] = (self.V[x] & 0x80) >> 7
  209.             self.V[x] = (self.V[x] << 1) & 0xFF
  210.  
  211.     def _9xy0(self):
  212.         x = (self.opcode & 0x0F00) >> 8
  213.         y = (self.opcode & 0x00F0) >> 4
  214.         if self.V[x] != self.V[y]:
  215.             self.pc += 2
  216.  
  217.     def _Annn(self):
  218.         self.I = self.opcode & 0x0FFF
  219.  
  220.     def _Bnnn(self):
  221.         self.pc = (self.opcode & 0x0FFF) + self.V[0]
  222.  
  223.     def _Cxnn(self):
  224.         x = (self.opcode & 0x0F00) >> 8
  225.         nn = self.opcode & 0x00FF
  226.         self.V[x] = random.randint(0, 255) & nn
  227.  
  228.     def _Dxyn(self):
  229.         x = self.V[(self.opcode & 0x0F00) >> 8]
  230.         y = self.V[(self.opcode & 0x00F0) >> 4]
  231.         n = self.opcode & 0x000F
  232.         self.V[0xF] = 0
  233.        
  234.         if n == 0 and (self.high_res or self.is_xo_chip):
  235.             sprite = np.frombuffer(self.memory[self.I:self.I + 32], dtype=np.uint8).reshape(16, 2)
  236.             sprite = np.unpackbits(sprite, axis=1).reshape(16, 16)
  237.             height, width = 16, 16
  238.         else:
  239.             sprite = np.unpackbits(np.frombuffer(self.memory[self.I:self.I + n], dtype=np.uint8), axis=0).reshape(n, 8)
  240.             height, width = n, 8
  241.  
  242.         max_height = min(height, 64 - y if self.high_res else 32 - y)
  243.         max_width = min(width, 128 - x if self.high_res else 64 - x)
  244.  
  245.         if max_height <= 0 or max_width <= 0:
  246.             return  # Nothing to draw if sprite is entirely off-screen
  247.  
  248.         for plane in range(2):
  249.             if self.plane_mask & (1 << plane):
  250.                 display_slice = self.display_planes[plane][y:y+max_height, x:x+max_width]
  251.                 sprite_slice = sprite[:max_height, :max_width]
  252.                
  253.                 collision = np.logical_and(display_slice, sprite_slice)
  254.                 display_slice ^= sprite_slice
  255.                
  256.                 if collision.any():
  257.                     self.V[0xF] = 1
  258.  
  259.         self.draw_flag = True
  260.  
  261.  
  262.     def _Exxx(self):
  263.         x = (self.opcode & 0x0F00) >> 8
  264.         if self.opcode & 0x00FF == 0x9E:
  265.             if self.keypad[self.V[x]]:
  266.                 self.pc += 2
  267.         elif self.opcode & 0x00FF == 0xA1:
  268.             if not self.keypad[self.V[x]]:
  269.                 self.pc += 2
  270.  
  271.     def _Fxxx(self):
  272.         x = (self.opcode & 0x0F00) >> 8
  273.         if self.opcode & 0x00FF == 0x07:
  274.             self.V[x] = self.delay_timer
  275.         elif self.opcode & 0x00FF == 0x0A:
  276.             key_pressed = False
  277.             for i in range(16):
  278.                 if self.keypad[i]:
  279.                     self.V[x] = i
  280.                     key_pressed = True
  281.                     break
  282.             if not key_pressed:
  283.                 self.pc -= 2
  284.         elif self.opcode & 0x00FF == 0x15:
  285.             self.delay_timer = self.V[x]
  286.         elif self.opcode & 0x00FF == 0x18:
  287.             self.sound_timer = self.V[x]
  288.         elif self.opcode & 0x00FF == 0x1E:
  289.             self.I += self.V[x]
  290.             self.V[0xF] = 1 if self.I > 0xFFF else 0
  291.         elif self.opcode & 0x00FF == 0x29:
  292.             self.I = self.V[x] * 5
  293.         elif self.opcode & 0x00FF == 0x30 and (self.is_super_chip8 or self.is_xo_chip):
  294.             self.I = self.V[x] * 10 + len(self.font)
  295.         elif self.opcode & 0x00FF == 0x33:
  296.             self.memory[self.I] = self.V[x] // 100
  297.             self.memory[self.I + 1] = (self.V[x] % 100) // 10
  298.             self.memory[self.I + 2] = self.V[x] % 10
  299.         elif self.opcode & 0x00FF == 0x55:
  300.             self.memory[self.I:self.I + x + 1] = self.V[:x + 1]
  301.         elif self.opcode & 0x00FF == 0x65:
  302.             self.V[:x + 1] = self.memory[self.I:self.I + x + 1]
  303.         elif self.opcode & 0x00FF == 0x75 and (self.is_super_chip8 or self.is_xo_chip):
  304.             self.flag_registers[:x + 1] = self.V[:x + 1]
  305.         elif self.opcode & 0x00FF == 0x85 and (self.is_super_chip8 or self.is_xo_chip):
  306.             self.V[:x + 1] = self.flag_registers[:x + 1]
  307.         elif self.is_xo_chip:
  308.             if self.opcode & 0x00FF == 0x00:
  309.                 self.I = (self.memory[self.pc] << 8) | self.memory[self.pc + 1]
  310.                 self.pc += 2
  311.             elif self.opcode & 0x00FF == 0x01:
  312.                 self.plane_mask = x
  313.             elif self.opcode & 0x00FF == 0x02:
  314.                 self.audio_buffer = self.memory[self.I:self.I + 16]
  315.             elif self.opcode & 0x00FF == 0x3A:
  316.                 self.pitch = int(4000 * 2**((self.V[x] - 64) / 48))
  317.  
  318.     def update_timers(self):
  319.         if self.delay_timer > 0:
  320.             self.delay_timer -= 1
  321.         if self.sound_timer > 0:
  322.             self.sound_timer -= 1
  323.  
  324.     def _toggle_high_res(self, enable):
  325.         self.high_res = enable
  326.         self.draw_flag = True
  327.  
  328.     def _scroll_down(self, n):
  329.         for plane in range(2):
  330.             if self.plane_mask & (1 << plane):
  331.                 self.display_planes[plane] = np.roll(self.display_planes[plane], n, axis=0)
  332.                 self.display_planes[plane][:n] = 0
  333.         self.draw_flag = True
  334.  
  335.     def _scroll_up(self, n):
  336.         for plane in range(2):
  337.             if self.plane_mask & (1 << plane):
  338.                 self.display_planes[plane] = np.roll(self.display_planes[plane], -n, axis=0)
  339.                 self.display_planes[plane][-n:] = 0
  340.         self.draw_flag = True
  341.  
  342.     def _scroll_right(self):
  343.         for plane in range(2):
  344.             if self.plane_mask & (1 << plane):
  345.                 self.display_planes[plane] = np.roll(self.display_planes[plane], 4, axis=1)
  346.                 self.display_planes[plane][:, :4] = 0
  347.         self.draw_flag = True
  348.  
  349.     def _scroll_left(self):
  350.         for plane in range(2):
  351.             if self.plane_mask & (1 << plane):
  352.                 self.display_planes[plane] = np.roll(self.display_planes[plane], -4, axis=1)
  353.                 self.display_planes[plane][:, -4:] = 0
  354.         self.draw_flag = True
  355.  
  356. def generate_beep(duration_ms=100, freq=440, volume=0.5):
  357.     sample_rate = 44100
  358.     t = np.linspace(0, duration_ms / 1000, int(sample_rate * duration_ms / 1000), False)
  359.     wave = np.sin(2 * np.pi * freq * t) * volume
  360.     sound = np.asarray([wave, wave]).T.astype(np.float32)
  361.     sound = np.ascontiguousarray(sound)
  362.     return pygame.sndarray.make_sound(sound)
  363.  
  364. def select_rom():
  365.     root = tk.Tk()
  366.     root.withdraw()
  367.     file_path = filedialog.askopenfilename(
  368.         title="Select CHIP-8, Super CHIP-8, or XO-CHIP ROM",
  369.         filetypes=[("CHIP-8 ROMs", "*.ch8"), ("Super CHIP-8 ROMs", "*.sc8"), ("XO-CHIP ROMs", "*.xo8"), ("All files", "*.*")]
  370.     )
  371.     return file_path
  372.  
  373. def draw_text(screen, text, pos, color=(255, 255, 255), font_size=20):
  374.     font = pygame.font.Font(None, font_size)
  375.     text_surface = font.render(text, True, color)
  376.     screen.blit(text_surface, pos)
  377.  
  378. def main():
  379.     pygame.init()
  380.     pygame.mixer.init(frequency=44100, size=-16, channels=2)
  381.    
  382.     screen = pygame.display.set_mode((640, 360))
  383.     pygame.display.set_caption("nono CHIP-8")
  384.     clock = pygame.time.Clock()
  385.  
  386.     draw_surface = pygame.Surface((640, 320))
  387.     beep_sound = generate_beep()
  388.  
  389.     key_mapping = {
  390.         pygame.K_1: 0x1, pygame.K_2: 0x2, pygame.K_3: 0x3, pygame.K_4: 0xC,
  391.         pygame.K_q: 0x4, pygame.K_w: 0x5, pygame.K_e: 0x6, pygame.K_r: 0xD,
  392.         pygame.K_a: 0x7, pygame.K_s: 0x8, pygame.K_d: 0x9, pygame.K_f: 0xE,
  393.         pygame.K_z: 0xA, pygame.K_x: 0x0, pygame.K_c: 0xB, pygame.K_v: 0xF
  394.     }
  395.  
  396.     chip8 = None
  397.     emulation_speed = 500
  398.     paused = False
  399.  
  400.     while True:
  401.         screen.fill((0, 0, 0))
  402.        
  403.         if chip8 is None:
  404.             draw_text(screen, "nono CHIP-8 (Coded by nonogamer9)", (240, 100))
  405.             draw_text(screen, "Press SPACE to select a ROM", (180, 150))
  406.             draw_text(screen, "Press ESC to quit", (220, 200))
  407.         else:
  408.             if chip8.draw_flag:
  409.                 draw_surface.fill((0, 0, 0))
  410.                 scale_x = 5 if chip8.high_res else 10
  411.                 scale_y = 5 if chip8.high_res else 10
  412.                 height = 64 if chip8.high_res else 32
  413.                 width = 128 if chip8.high_res else 64
  414.                
  415.                 combined_display = np.logical_or(chip8.display_planes[0][:height, :width], chip8.display_planes[1][:height, :width])
  416.                 pixels = np.where(combined_display)
  417.                 for y, x in zip(*pixels):
  418.                     color = (255, 0, 0) if chip8.display_planes[0][y, x] else (0, 255, 0)
  419.                     if chip8.display_planes[0][y, x] and chip8.display_planes[1][y, x]:
  420.                         color = (255, 255, 0)
  421.                     pygame.draw.rect(draw_surface, color, (x * scale_x, y * scale_y, scale_x, scale_y))
  422.                
  423.                 chip8.draw_flag = False
  424.            
  425.             screen.blit(draw_surface, (0, 0))
  426.             draw_text(screen, f"Speed: {emulation_speed}", (10, 330), font_size=16)
  427.             draw_text(screen, "Up/Down: Adjust speed", (200, 330), font_size=16)
  428.             draw_text(screen, "P: Pause/Resume", (450, 330), font_size=16)
  429.             if chip8.is_xo_chip:
  430.                 draw_text(screen, "XO-CHIP", (10, 10), font_size=16)
  431.             elif chip8.is_super_chip8:
  432.                 draw_text(screen, "Super CHIP-8", (10, 10), font_size=16)
  433.             else:
  434.                 draw_text(screen, "CHIP-8", (10, 10), font_size=16)
  435.  
  436.         pygame.display.flip()
  437.  
  438.         for event in pygame.event.get():
  439.             if event.type == pygame.QUIT:
  440.                 pygame.quit()
  441.                 sys.exit()
  442.             elif event.type == pygame.KEYDOWN:
  443.                 if event.key == pygame.K_ESCAPE:
  444.                     pygame.quit()
  445.                     sys.exit()
  446.                 elif event.key == pygame.K_SPACE and chip8 is None:
  447.                     rom_path = select_rom()
  448.                     if rom_path:
  449.                         chip8 = Chip8()
  450.                         chip8.load_rom(rom_path)
  451.                 elif event.key == pygame.K_p:
  452.                     paused = not paused
  453.                 elif event.key == pygame.K_UP:
  454.                     emulation_speed = min(1000, emulation_speed + 50)
  455.                 elif event.key == pygame.K_DOWN:
  456.                     emulation_speed = max(50, emulation_speed - 50)
  457.                 elif chip8 and event.key in key_mapping:
  458.                     chip8.keypad[key_mapping[event.key]] = 1
  459.             elif event.type == pygame.KEYUP:
  460.                 if chip8 and event.key in key_mapping:
  461.                     chip8.keypad[key_mapping[event.key]] = 0
  462.  
  463.         if chip8 and not paused:
  464.             for _ in range(10):  # Run multiple instructions per frame
  465.                 chip8.emulate_cycle()
  466.             chip8.update_timers()
  467.  
  468.             if chip8.sound_timer > 0:
  469.                 beep_sound.play()
  470.             else:
  471.                 beep_sound.stop()
  472.  
  473.         clock.tick(60)  # Cap at 60 FPS
  474.  
  475. if __name__ == '__main__':
  476.     main()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement