From 47a9e1abe39883c5878a2e6d2d4bc7709be47a07 Mon Sep 17 00:00:00 2001 From: angelotr Date: Mon, 8 Dec 2025 23:39:17 -0500 Subject: [PATCH] I did not realize this had to be three submissions. My first progress was the add lives to the asteroid game. For this feature, I added a health system so the player has three lives instead of losing instantly. In nav_game.py, I added "health": 3 to the game state, and in spaceship.py I created a take_damage method that subtracts one life when the ship is hit. If the player's health reaches zero, the game ends. This change is important because it makes the game less punishing, gives the player more chances to recover from mistakes, and makes gameplay feel more balanced and fun. --- __pycache__/asteroid.cpython-313.pyc | Bin 0 -> 1415 bytes __pycache__/asteroid_spawner.cpython-313.pyc | Bin 0 -> 1355 bytes __pycache__/bullet.cpython-313.pyc | Bin 0 -> 1451 bytes __pycache__/spaceship.cpython-313.pyc | Bin 0 -> 2216 bytes asteroid.py | 36 +++++++++++ asteroid_spawner.py | 30 ++++++++++ bullet.py | 31 ++++++++++ nav_game.py | 23 ++++++++ spaceship.py | 59 +++++++++++++++++++ 9 files changed, 179 insertions(+) create mode 100644 __pycache__/asteroid.cpython-313.pyc create mode 100644 __pycache__/asteroid_spawner.cpython-313.pyc create mode 100644 __pycache__/bullet.cpython-313.pyc create mode 100644 __pycache__/spaceship.cpython-313.pyc create mode 100644 asteroid.py create mode 100644 asteroid_spawner.py create mode 100644 bullet.py create mode 100644 nav_game.py create mode 100644 spaceship.py diff --git a/__pycache__/asteroid.cpython-313.pyc b/__pycache__/asteroid.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4201d65ff762e7264921d6d925e490c2db110e83 GIT binary patch literal 1415 zcma)6-D}%c6u;NsvE|u`+|4-2<~CuiN?j3_?jekgLi;cn8H;K#tZ-4JTUm)LX;*T+ z3<7x?3ELQZ2slp(dusn2lXW(iJp}fYw=%Evb>~V^7Q!BO9*%U*`JIn@^t+mt%S8n8 z=J${7r3^xU3L_D)BbnCWafs^Z7Sd1yY4|}Z4lrb;l(Z2WvA&3)>76L&DPmYdETv(V z)>15^<+XGJZDE$xG7ZFXT9)OloKoP0he1f0XPf6aNaXlSBYmZ;s4DE~0;ogOsXCXz zPSe4OVIc*tzfAFh?*(>fdoIrgKBcCT=9z#x&sYJFYz9;ysx_NU{*~GeC<|2GwW#BT zj5-bt)JHVf4n1G(>D#tzk%1j{iQ$=4?G20-weQ;DN2<@fEoy|s(tA|ZFQ&im@9+X4 zHXsP$v&o_EC$6Zs1ikmrS-tsVd2*xiQ*jD5GLp>+Lu@`euQ=NwYU2&zy(WFg@RuNV zHS+?~MjOf3yOPy{-D-GkfRGM#Ed(gB?Q0U%ecaKsR13JV)&cY!5;KV5uSpv{xY&ME z)WaL4YXC5{bb@hnoySlWe5)xp-+BID-J^c12z^~nDz$T3CSjall@NUvCdzG8V(Y4I zz?9@hjQfAuiB`=ev5KhaqD!v{4F7+(DiZOyb#rZH8T* z>r&h5h8%y(@eYIiBQCjgKoVvLg33VPNSN3pTUf4H{)1>{s0j(3lCPb>HSkQ>9* zlf|*Dj%0OGtB-5TquTPxlizB0C)L`xdUI60d9wVfdi$(-@373otSk>|UBTyq-Qov9 zqSe8zDiYH3%)Ue6T_WV0zV3*nf>j~pnazUGv0dtV;FvMGI@1mKvoLY#*ax8a4MKuY z4{d{pPhfBMLmCkB62)!BDw$wJq{Q_IfNSV`G^xsapZ}dnXXoD(-_5pSv;e}8ae^S{ izeRZAlX%QenF0Yps6g=bbsA&*R(c<|{z9Olgnt0~<1_jI literal 0 HcmV?d00001 diff --git a/__pycache__/asteroid_spawner.cpython-313.pyc b/__pycache__/asteroid_spawner.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..82e635f7ae758310ec788fcd5b756f353acc0b14 GIT binary patch literal 1355 zcmZux&2JM&6rb5IJO1c~uO^UGv1klfC^o1~LQ6ri1hfL-11le@AdNQO!CqMJIy37A zIOS9&Cy=U0=7f*szrl(i(Wpq3ngh8-Sd=SocD)p=K12lDU& zixjhc$MwS!7U?PvDGOYu5j(~lj}F6m5Vp}>#IS{!VqvCQ3ezMu(=83SVd*T@G)gI9 zCP-GtUiT?Gn1I+DjZpy+pbi|agoYX<*hY1HCsKdOx)9O%7HpqKj$AB!MY*C#7)x2m zBg9p%oP|>z-I2;M0t{nJ1;@qF<<5;v_GN@tWUUcdYFz zb!f@=N?rdHV?b`GZ=C8zlD&YM@GI#xq4P$-=z*+?$txeo4naRNtAWiN!rjLdd_p^< z;skF6T@Q4!!^9pF=0Cnv6~d;oDv$R-aVl;?KYsTyH3~= zMvJ=5Rw(d8!0%A%h;#zUQvgtiN&H%=bIN$d_M6lTLPkB0^2#;JAA~`t(zYMCev_=b zVT&{ZhgRC_jf<78?}p!1IxM(P8zGTdD+zX7J!R$2BQeqO>_DlMzGjl7ysUI3>BL5__m3g|nlb-3V4blbp|2u2$W{ZPtvG44jSsI*K+RZKx zvdjCEQ$5^U>|cEL!_H)Jv$}tLrdRE){c(Hq`d?FX{UiNq-}qDCnJRDovX`5DQroKa zF1^alALdc+(hxGEFVBaZ6ie8SrrBAJ-3qyX0f_A*L v)1ZttH-Z?$7U`ej@~^V9;3r$or+^)*7~}UjglFEMZ$4sWwuRE_oS-E zb%F_dl37&5!{T@lbMWAv{1^1(dCBTdiU#LgT>q(rBuckuy9E#y0v0QN=-2brh;OD%Bm08jfB;G~*bcvrdNQnprz1GH-Rl zknlqQh;CiJWUVExbv>ZOx7NBAUndr8yB=ZdK^r`mTdnvL!mLhv!==9EM!w|}#%bJT zmODbmmW1+|H$tBPleRo!XoA{CD`uE1OVtEY^&Q~Vj#ltGZJ2>vV2TtT9){* z)?kFPnj1Aq7;{PhHLJZ%*bN@HYc2Oi5H;~ez}K-C`=r*|@Ls8PqJY0zYt#5+;&I$` zTcnoKTWWVj4&wmg7>jaRf~E07Rvs(WCK}9IUzLVnqW`fmU?E|13(CHY8tAIr@|1J{ zd3(O0Im)>igdzkG|=*>jTTg8W&Sm_fu7W9Ri2s%WM{_qllM4x z0=*lP1zOcJy;4Xo<$uMet;8jwRYQ>W8vmRL+Pnm={xWD4E*XWOhW65;?5cZIXx! zJq#EZnZ#kD5U?O(+>Ja!WiKF~yPVhtJtjR}j*Cz}6-rkaX^MnmNud(0$hSkci+P7e z!W>P|Sg>~}(G7csusal=uhj4}Kf7!QMpL@|W382hmYM&4U@sq4X? zi9)Xst%4F$7|Wst9(FmW!CHqChVc$c50+J=vZ#rYJz$fdK1cU;XJAfjUOv(lWA5+F zlrfVg$h~!26S?t!CTU#yiBs}B37@1nxi?YUf6y;A;{YQUEnK?qB HlAM15j4(TN literal 0 HcmV?d00001 diff --git a/__pycache__/spaceship.cpython-313.pyc b/__pycache__/spaceship.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7b31af504fb51e28dc155a5cced533dd30fa9cb0 GIT binary patch literal 2216 zcmZ`)&2JM&6rWk|I_ubuO+p%yf>=rjEFW>xBv6Hq76_ED29&J@RS~1LJ=ojWYj<`{ zNF=13s`!8s4pGA`M>v&Z|BP5}6>HQ(rJiz&h)}P6v+IpRL`K>-AMf`*=FNL=x8w0n z1ml8y!Fm@)=wGo3hTK|g55i&v8OXqQ(H+dNh7O@GkRc5qBQ!4gC?@4)UD*^H9rN(D zYOzS&tk3f)Zf&-QVX=Z9BZdvcBm=XMAu-tqF~yJv5DOa$&<-OER5dzSB(Lfb9x*Cr zmO2HivZ;bRm&KCfC)t*LPk9Ka__j|2SwU%B1#abd8jPHSRT>PPgZqw2X;kzC1j!>k zG_@(zYzi0OVy;%oj^$cqTbIEOEk5O)nX<`p#IfcnkNWFbE9Vw?%-`$nkvm^M^aVWaz|4YR{oi0 zU6MCTH01<$9TZ}gQ2jAd&zy9XZAIIuv zF0LQCv?#xfZvzHpMUa9aoc4mILJS~iXgc8Rn!P}d8OT=Bcv{42&oJ;sI5FXAf*_~m zG09f$zuE^M`UnrQE&SX*c-g~uAoOS~ctp^i*$1tBga+-NFL(DrhduN}TX*GITAEh& zToJa9@?+9?E00gL*ya(0egePzu^VBlpd}J;+H+bBbZC%#d6ZT%H{t%Dw3t=`?2f&Z zhmE%N1*=bBMZ6yDPIY+Qcz|!u>785o9zjWTOz9!6-o5c7xp(8{4;-5uUuD8597n|Q z^zH9%!Fi73NjM6Nqx1ugXE}ZtNKW2=IK{EUv7t*GmpJ}j?_k0_mu*OIu9j`$)ejT- z$0KGgN6b96U9MUVp{0sD$D^(}LrKmo0Yi_lE)O_E=Ny-nXDF9NopAh|<2fF)=`0CK zmrI%F@XhgX#%o26cnDFO-eBEAPqJXzxgsTPbp!3n+RICVgcE_shYCydEWN0`sL6}C z+4uQkY%7dny-QbWSJsb=Hlm}=!$;Qe>lTfO>PS(aKZt!7pqZ6t>3iJ`yykFIsEsf~VpIowPh z{H^O%*XpxBlS9o|H@Fn<5k4QPM~}aee~-V7zbkH>x?VqZJy7#_BXPQ(INj9FuEYU( zpm!sw)sx!V!M~D2TT!G>;`Qip)(au|sQ>P?l6l~W{`;v`V&KctWl2;Q6mlN~HRMPq zVYeUJLxv`xJk0|hYIpMjIWfll2|@bG;y*NhcADm@S*jJNS#%2*HEQQHkt&+?v~{L2 zYGw=6(Y_q!VISt@Hxu1$3KCXvT+^jI1fsg+B_2BiEJyG)&C4LB!p_AmpjV9JgrM)GiakP6*#3*@DT;tV_?4$#@^5j4eG6xzqdEtHY=tn!A7Tg( O{3nm%zV`@-C-py|Rpy`o literal 0 HcmV?d00001 diff --git a/asteroid.py b/asteroid.py new file mode 100644 index 0000000..299a38f --- /dev/null +++ b/asteroid.py @@ -0,0 +1,36 @@ +# asteroid.py +# ------------ +# By MWC Contributors +# This module defines an asteroid agent class. + +class Asteroid: + character = 'O' + + def __init__(self, position, speed=2): + # speed = how often it moves (1 = every turn, 2 = every 2 turns, etc.) + self.position = position + self.speed = speed + + def play_turn(self, game): + width, height = game.board_size + + # Only move on some turns (for variable speed) + if game.turn_number % self.speed != 0: + return + + x, y = self.position + + # If at bottom, remove asteroid + if y == height - 1: + game.remove_agent(self) + else: + ship = game.get_agent_by_name('ship') + new_position = (x, y + 1) + + # If we hit the ship, damage it and remove this asteroid + if new_position == ship.position: + if hasattr(ship, "take_damage"): + ship.take_damage(game) + game.remove_agent(self) + else: + self.position = new_position \ No newline at end of file diff --git a/asteroid_spawner.py b/asteroid_spawner.py new file mode 100644 index 0000000..0439af8 --- /dev/null +++ b/asteroid_spawner.py @@ -0,0 +1,30 @@ +# asteroid_spawner.py +# ------------------- +# By MWC Contributors +# This module defines an AsteroidSpawner agent class. + +from random import randint +from asteroid import Asteroid + +class AsteroidSpawner: + display = False + + def play_turn(self, game): + width, height = game.board_size + + # Increase score each turn survived + game.state['score'] += 1 + + if self.should_spawn_asteroid(game.turn_number): + # Random x position at the top + x = randint(0, width - 1) + # Random speed: 1 = fast, 2–3 = slower + speed = randint(1, 3) + asteroid = Asteroid((x, 0), speed) + game.add_agent(asteroid) + + def should_spawn_asteroid(self, turn_number): + # More asteroids as the game goes on + return randint(0, 1000) < turn_number + + \ No newline at end of file diff --git a/bullet.py b/bullet.py new file mode 100644 index 0000000..56ad548 --- /dev/null +++ b/bullet.py @@ -0,0 +1,31 @@ +from asteroid import Asteroid + +class Bullet: + """A bullet fired by the spaceship that moves upward and destroys asteroids.""" + character = '|' + + def __init__(self, position): + self.position = position + + def play_turn(self, game): + # Move bullet up one row each turn + x, y = self.position + y -= 1 + + # If the bullet goes off the top, remove it + if y < 0: + game.remove_agent(self) + return + + self.position = (x, y) + + # Check if we hit an asteroid + # We loop over a copy of the agents list because we may remove agents + for agent in list(game.agents): + if isinstance(agent, Asteroid) and agent.position == self.position: + # Destroy the asteroid and the bullet + game.remove_agent(agent) + game.remove_agent(self) + # Reward points for hitting an asteroid + game.state["score"] += 10 + return \ No newline at end of file diff --git a/nav_game.py b/nav_game.py new file mode 100644 index 0000000..8931b3e --- /dev/null +++ b/nav_game.py @@ -0,0 +1,23 @@ +# nav_game.py +# ------------ +# By MWC Contributors +# This class implements a simple game where a spaceship avoids asteroids. + +from spaceship import Spaceship +from asteroid import Asteroid + +from retro.game import Game +from spaceship import Spaceship +from asteroid_spawner import AsteroidSpawner + +board_size = (25, 25) +ship = Spaceship(board_size) +spawner = AsteroidSpawner() + +#adds health to the game +state = { + "score":0, + "health":3 #3 lives +} +game = Game([ship, spawner], state, board_size=board_size) +game.play() \ No newline at end of file diff --git a/spaceship.py b/spaceship.py new file mode 100644 index 0000000..3b34c83 --- /dev/null +++ b/spaceship.py @@ -0,0 +1,59 @@ +# spaceship.py +# ------------ +# By MWC Contributors +# This module defines a spaceship agent class. + +from bullet import Bullet + +class Spaceship: + name = "ship" + character = '^' + + def __init__(self, board_size): + board_width, board_height = board_size + self.position = (board_width // 2, board_height - 1) + + def handle_keystroke(self, keystroke, game): + x, y = self.position + new_position = None + + # Move LEFT + if keystroke.name in ("KEY_LEFT", "a", "A"): + new_position = (x - 1, y) + + # Move RIGHT + elif keystroke.name in ("KEY_RIGHT", "d", "D"): + new_position = (x + 1, y) + + # Move UP + elif keystroke.name in ("KEY_UP", "w", "W"): + new_position = (x, y - 1) + + # Move DOWN + elif keystroke.name in ("KEY_DOWN", "s", "S"): + new_position = (x, y + 1) + + # SHOOT (m or M) + elif keystroke.name in ("m", "M"): + bx, by = x, y - 1 + if game.on_board((bx, by)): + bullet = Bullet((bx, by)) + game.add_agent(bullet) + return # don’t move on shoot + + else: + return # ignore other keys + + # If we are trying to move: + if new_position is not None and game.on_board(new_position): + if game.is_empty(new_position): + self.position = new_position + else: + # We bumped into something (probably an asteroid) + self.take_damage(game) + + def take_damage(self, game): + """Reduce health; end game if health reaches 0.""" + game.state["health"] -= 1 + if game.state["health"] <= 0: + game.end() \ No newline at end of file