-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathship.py
More file actions
353 lines (285 loc) · 10.9 KB
/
ship.py
File metadata and controls
353 lines (285 loc) · 10.9 KB
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
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
#!/usr/bin/env python3
"""
Spaceship Builder
Learning goals:
- Class definitions with __init__
- Inheritance: Component -> Engine/Weapon/Shield
- Composition: a Ship "has a" Hull and Components
- Methods that operate on object state
- Very small text UI
"""
# ---------------------------
# Model (very small & readable)
# ---------------------------
class Hull:
"""
A Ship must have exactly one Hull.
The hull only tracks a name and how many component slots it has.
"""
def __init__(self, name, slots):
self.name = name
self.slots = slots
def summary(self):
return f"{self.name} (slots: {self.slots})"
class Component:
"""
Base class for all components.
Subclasses only add one 'stat' field to keep it very simple.
"""
def __init__(self, name):
self.name = name
def kind(self):
# self.__class__ means "what class is this object an instance of?"
# Example: if self is an Engine, then self.__class__ is Engine
#
# __name__ gives us the *name* of that class as a string.
# Example: Engine.__name__ is the string "Engine"
#
# So this line will return the class name of the current object.
return self.__class__.__name__
def summary(self):
# Here we call kind() to print the class name (like "Engine" or "Weapon"),
# followed by the component's name (like "Ion-90 Engine").
return f"{self.kind()}: {self.name}"
# Each of these classes (Engine, Weapon, Shield) extends the base class "Component".
# That means they "inherit" from Component: they automatically get its variables
# (like self.name) and methods (like kind()), but they can also add new variables
# and override methods to customize behavior.
class Engine(Component):
def __init__(self, name, thrust):
# super() means "go up to the parent class"
# In this case, Engine's parent class is Component.
#
# Component has its own __init__ method:
# def __init__(self, name):
# self.name = name
#
# By calling super().__init__(name),
# we are *reusing* that code so we don’t have to write "self.name = name" again here.
#
# Without this call, the Engine object would not have self.name set up!
super().__init__(name)
# Now we add the Engine-specific attribute.
# This is unique to Engine, so it is written here instead of in Component.
self.thrust = thrust # measured in kilonewtons (kN)
# Override the summary() method so that Engines display their thrust
def summary(self):
return f"{self.kind()}: {self.name} (thrust: {self.thrust} kN)"
class Weapon(Component):
def __init__(self, name, dps):
# Reuse Component's constructor to set self.name
super().__init__(name)
# Add a new attribute specific to Weapon
self.dps = dps # damage per second
# Override the summary() method so Weapons display their dps
def summary(self):
return f"{self.kind()}: {self.name} (dps: {self.dps})"
class Shield(Component):
def __init__(self, name, capacity):
# Again, call Component's constructor to set self.name
super().__init__(name)
# Add a new attribute specific to Shield
self.capacity = capacity # shield capacity in some units (e.g., MJ)
# Override the summary() method so Shields display their capacity
def summary(self):
return f"{self.kind()}: {self.name} (capacity: {self.capacity})"
class Ship:
"""
A Ship has a name, one hull, and a list of components.
We enforce only one simple rule: you can't exceed the hull's slot count.
"""
def __init__(self, name):
self.name = name
self.hull = None
self.components = []
def set_hull(self, hull):
self.hull = hull
# If the new hull has fewer slots than current components,
# we *do not* remove components (kept simple). Students can extend this.
print(f"[+] Hull installed: {hull.summary()}")
def slots_used(self):
return len(self.components)
def slots_free(self):
if self.hull is None:
return 0
return max(self.hull.slots - self.slots_used(), 0)
def add_component(self, component):
if self.hull is None:
print("[!] Install a hull first.")
return False
if self.slots_used() >= self.hull.slots:
print("[!] No free slots left on this hull.")
return False
self.components.append(component)
print(f"[+] Added: {component.summary()}")
return True
def remove_last_component(self):
if not self.components:
print("[!] No components to remove.")
return
removed = self.components.pop()
print(f"[-] Removed: {removed.summary()}")
def total_thrust(self):
return sum(c.thrust for c in self.components if isinstance(c, Engine))
def total_dps(self):
return sum(c.dps for c in self.components if isinstance(c, Weapon))
def total_shield(self):
return sum(c.capacity for c in self.components if isinstance(c, Shield))
def spec_sheet(self):
lines = []
lines.append(f"=== SHIP: {self.name} ===")
if self.hull is None:
lines.append("Hull: (none)")
else:
lines.append(f"Hull: {self.hull.summary()}")
lines.append(f"Slots used: {self.slots_used()}" + (f"/{self.hull.slots}" if self.hull else ""))
if not self.components:
lines.append("Components: (none)")
else:
lines.append("Components:")
for i, c in enumerate(self.components, start=1):
lines.append(f" {i:02d}. {c.summary()}")
# Tiny roll-up stats for fun
lines.append("")
lines.append(f"Totals -> Thrust: {self.total_thrust()} kN | DPS: {self.total_dps()} | Shield: {self.total_shield()}")
return "\n".join(lines)
def ascii_schematic(self):
"""
Very simple text 'diagram'. Students can easily edit this.
"""
title = f"{self.name} - {self.hull.summary() if self.hull else 'No Hull'}"
width = max(40, len(title) + 4)
top = "+" + "-" * (width - 2) + "+"
lines = [top, "|" + title.center(width - 2) + "|", "|" + " " * (width - 2) + "|"]
engines = [] # start with an empty list
for c in self.components: # go through each component in the ship
if isinstance(c, Engine): # check if this component is an Engine
engines.append(c.name) # if yes, add its name to the engines list
weapons = []
for c in self.components:
if isinstance(c, Weapon): # check if it's a Weapon
weapons.append(c.name) # add the weapon's name
shields = []
for c in self.components:
if isinstance(c, Shield): # check if it's a Shield
shields.append(c.name) # add the shield's name
def section(label, items):
lines.append("|" + f"[{label}]".ljust(width - 2) + "|")
if items:
text = ", ".join(items)
else:
text = "(none)"
# simple wrap that keeps it readable
chunk = text
while len(chunk) > 0:
line = chunk[:width - 4]
chunk = chunk[width - 4:]
lines.append("| " + line.ljust(width - 4) + " |")
lines.append("|" + " " * (width - 2) + "|")
section("ENGINES", engines)
section("WEAPONS", weapons)
section("SHIELDS", shields)
lines.append(top)
return "\n".join(lines)
# ---------------------------
# Tiny Catalog (plain data)
# ---------------------------
HULL_CATALOG = [
Hull("Sparrow (Light)", 4),
Hull("Kestrel (Medium)", 6),
Hull("Condor (Heavy)", 8),
]
ENGINE_CATALOG = [
("Ion-90", 120), # (name, thrust)
("Ion-160", 220),
("Vector-X", 360),
]
WEAPON_CATALOG = [
("Pulse Laser", 15), # (name, dps)
("Railgun", 24),
("Plasma Arc", 35),
]
SHIELD_CATALOG = [
("MK-I Shield", 120), # (name, capacity)
("MK-II Shield", 220),
]
# ---------------------------
# Super Small CLI
# ---------------------------
def choose_from_list(title, items):
print(f"\n-- {title} --")
for i, it in enumerate(items, start=1):
# print a friendly preview
if isinstance(it, Hull):
print(f" {i}. {it.summary()}")
elif isinstance(it, tuple):
# tuple form in component catalogs
print(f" {i}. {it[0]}")
else:
print(f" {i}. {it}")
print(" 0. Cancel")
while True:
raw = input("Choose #: ").strip()
if not raw.isdigit():
print("Enter a number.")
continue
n = int(raw)
if n == 0:
return None
if 1 <= n <= len(items):
return items[n - 1]
print("Invalid choice.")
def main():
print("Welcome to the SIMPLE Spaceship Builder")
name = input("Name your ship: ").strip() or "Untitled"
ship = Ship(name)
while True:
print("\n==============================")
print("1) Install/Change Hull")
print("2) Add Engine")
print("3) Add Weapon")
print("4) Add Shield")
print("5) View Ship Specs")
print("6) View ASCII Schematic")
print("7) Remove Last Component")
print("0) Finish & Exit")
print("==============================")
choice = input("Select: ").strip()
if choice == "1":
h = choose_from_list("Select a Hull", HULL_CATALOG)
if h:
ship.set_hull(h)
elif choice == "2":
pick = choose_from_list("Select an Engine", ENGINE_CATALOG)
if pick:
name, thrust = pick
ship.add_component(Engine(name, thrust))
elif choice == "3":
pick = choose_from_list("Select a Weapon", WEAPON_CATALOG)
if pick:
name, dps = pick
ship.add_component(Weapon(name, dps))
elif choice == "4":
pick = choose_from_list("Select a Shield", SHIELD_CATALOG)
if pick:
name, cap = pick
ship.add_component(Shield(name, cap))
elif choice == "5":
print()
print(ship.spec_sheet())
elif choice == "6":
print()
print(ship.ascii_schematic())
elif choice == "7":
ship.remove_last_component()
elif choice == "0":
print("\nFinal Specs:")
print(ship.spec_sheet())
print("\nSchematic:")
print(ship.ascii_schematic())
print("\nGoodbye.")
break
else:
print("Invalid choice.")
if __name__ == "__main__":
main()