-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.py
198 lines (175 loc) · 7.21 KB
/
main.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
import os
import glob
import argparse
from typing import List
import pygame
from corewars.mars import MARS
from corewars.core import CoreWarrior
CELLS_PER_LINE = 100
LINES = 80
CELL_SIZE = 10
SPACING = 2
SIDEBAR_WIDTH = 350
WINDOW_WIDTH = SPACING + (CELLS_PER_LINE * (CELL_SIZE + SPACING)) + SIDEBAR_WIDTH
WINDOW_HEIGHT = SPACING + (LINES * (CELL_SIZE + SPACING))
SIDEBAR_START = WINDOW_WIDTH - SIDEBAR_WIDTH
INFO_MARGIN = 20
ENTRY_SPACING = (WINDOW_WIDTH - 200) // 15
COLOURS = [
(55, 183, 106), # green
(75, 160, 227), # blue
(255, 159, 16), # orange
(206, 55, 70), # red
(240, 76, 169), # dark blue
(204, 175, 175) # pink-ish
]
BASE_CELL_COLOUR = (30, 30, 30)
SIDEBAR_COLOUR = (20, 20, 20)
FONT_SIZE = 20
def main():
parser = argparse.ArgumentParser(description='Core Wars')
parser.add_argument('--cycles', '-c', dest='cycles', type=int, nargs='?',
default=80000, help='Max sim. cycles before round end')
parser.add_argument('--warriors', type=str, default='warriors',
help='Name of the folder containing warrior files')
args = parser.parse_args()
# laod warriors
warrior_files = glob.glob(os.path.join(os.getcwd(), "warriors", "*.red"))
if not warrior_files:
print('ERROR: No warrior files found. Aborting...')
return
elif not (2 <= len(warrior_files) <= 6):
print('ERROR: Only battles between 2-6 warriors are supported.')
return
pygame.init()
pygame.display.set_caption('Core Wars')
# 1202px horizontal, 962px vertical needed at minimum (10px per square, 2px spacing)
screen = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT))
warriors_data = [open(file).readlines() for file in warrior_files]
run_simulation(screen, warriors_data, args.cycles)
def run_simulation(screen, warriors_data: List[List[str]], max_cycles: int):
# initialize the simulator and load up provided warriors
mars = MARS()
mars.load_warriors(warriors_data)
mars.core.assign_colors(COLOURS)
# initial stats display
screen.fill((0, 0, 0))
sidebar = pygame.Surface((SIDEBAR_WIDTH, WINDOW_HEIGHT))
sidebar.fill(SIDEBAR_COLOUR)
screen.blit(sidebar, (SIDEBAR_START, 0))
# initial cell display
for i, _ in enumerate(mars.core):
cell = create_cursor_cell(BASE_CELL_COLOUR)
pos = get_position(i, mars.core.size)
screen.blit(cell, pos)
# initial cursor display
display_cursor(screen, mars)
pygame.display.flip()
# main game loop
loop = True
game_ended = False
run_again = False
cycles = 1
while loop:
for event in pygame.event.get():
if event.type == pygame.QUIT:
loop = False
elif event.type == pygame.KEYDOWN:
# simulation reset
if event.key == pygame.K_r and pygame.key.get_mods() & pygame.KMOD_CTRL:
run_again = True
loop = False
if game_ended:
# prevents closing the window after game is finished
continue
# change previous cursor to warrior's colour
display_cursor(screen, mars)
# save current execution pointer to prevent erasing it afterwards
pointer = mars.core.current_warrior.current_pointer
# save current warrior's colour for later use
color = mars.core.current_warrior.color
# run simulation cycle and save accessed cells
addresses = mars.cycle()
# display sidebar content
sidebar.fill((20, 20, 20))
write_text(sidebar, f'CYCLE {cycles + 1}', INFO_MARGIN, 20)
for i, warrior in enumerate(mars.core.warriors + mars.core.dead_warriors):
print_warrior_info(sidebar, i, warrior)
# one warrior left = win
if mars.core.warriors_count == 1:
game_ended = True
write_text(sidebar, f'{mars.core.current_warrior.name.upper()} WINS!',
INFO_MARGIN, 100, 'Gold')
# max cycles passed = tie
elif cycles + 1 >= max_cycles:
game_ended = True
write_text(sidebar, 'GAME OVER - NO WINNER.', INFO_MARGIN, 50, 'Red')
write_text(
sidebar,
f'instruction: {str(mars.core[mars.core.current_warrior.current_pointer])}',
INFO_MARGIN, WINDOW_HEIGHT - 60
)
write_text(sidebar, 'CTRL-R to reset', INFO_MARGIN, WINDOW_HEIGHT - 30)
screen.blit(sidebar, (SIDEBAR_START, 0))
# cells accessed / written to during this cycle
if pointer in addresses:
addresses.remove(pointer)
for address in addresses:
cell = create_written_cell(color)
pos = get_position(address, mars.core.size)
screen.blit(cell, pos)
# show current warrior's pointer (blink white)
display_cursor(screen, mars, True)
pygame.display.flip()
cycles += 1
if run_again:
run_simulation(screen, warriors_data, max_cycles)
def display_cursor(screen, mars: MARS, white=False):
"Displays where in the memory the current process is pointing to."
cursor = mars.core.current_warrior.current_pointer
color = (255, 255, 255) if white else mars.core.current_warrior.color
cell = create_cursor_cell(color)
pos = get_position(cursor, mars.core.size)
screen.blit(cell, pos)
def create_cursor_cell(color):
"Creates a simple square surface filled with the given color."
cell = pygame.Surface((CELL_SIZE, CELL_SIZE))
cell.fill(color)
return cell
def create_written_cell(color):
"Creates a surface with an X symbol in the provided color on it."
cell = pygame.Surface((CELL_SIZE, CELL_SIZE))
pygame.draw.aaline(cell, color, (0, 0), (CELL_SIZE, CELL_SIZE))
pygame.draw.aaline(cell, color, (0, CELL_SIZE), (CELL_SIZE, 0))
return cell
def print_warrior_info(sidebar, pos: int, warrior: CoreWarrior):
"""
Shows information about the given warrior on the sidebar.
Vertical drawing offset is determined based on the provided "position" parameter.
"""
v_margin = 200 + (pos * ENTRY_SPACING)
warrior_entry = pygame.Surface((SIDEBAR_WIDTH, ENTRY_SPACING))
warrior_entry.fill(SIDEBAR_COLOUR)
square = pygame.Surface((20, 20))
square.fill(warrior.color)
warrior_entry.blit(square, (0, 5))
write_text(warrior_entry, warrior.name, 30, 5)
if len(warrior) > 0:
write_text(warrior_entry, f'processes: {len(warrior)}', 30, 30)
else:
write_text(warrior_entry, 'processes: 0 (dead)', 30, 30)
sidebar.blit(warrior_entry, (INFO_MARGIN, v_margin))
def write_text(screen, text: str, x: int, y: int, color='White'):
"Shows provided text at the given position on screen."
font = pygame.font.Font(pygame.font.get_default_font(), FONT_SIZE)
text = font.render(text, True, pygame.Color(color))
screen.blit(text, (x, y))
def get_position(address: int, core_size):
"Returns on-screen position of a memory cell from the given address."
# addresses might be passed here before Core itself normalizes them
address = address % core_size
y = SPACING + (address // CELLS_PER_LINE) * (CELL_SIZE + SPACING)
x = SPACING + (address % CELLS_PER_LINE) * (CELL_SIZE + SPACING)
return (x, y)
if __name__ == '__main__':
main()