Python implementation of the classic Apple II game, "Taipan!"
Based on Jay Link's C port which can be found here as well as in this repository.
Port of the classic Apple II game
License: MIT License
Python implementation of the classic Apple II game, "Taipan!"
Based on Jay Link's C port which can be found here as well as in this repository.
max_purchase is supposed to show up in a little box
Line 10 in 71f4320
while True:
max_purchase = int(player.cash / player.port[good])
# TODO max_purchase is supposed to show up in a little box
amount = player.ui.ask_num(f"{string.goods_str(player.port)}\nYou can afford {max_purchase}. How much "
f"{good} shall I buy, Taipan?")
29b05d468a2691e89fd026d37ccccccded1fbe0d
this should take a 'max' argument and return it if 'A' is entered
Line 259 in 71f4320
elif wait:
self.wait(wait)
def ask(self, prompt='', validate=None, **kwargs): # TODO is anything using kwargs here?
self.tell(prompt + ' ')
while True:
result = get_str(**kwargs)
if not validate or validate(result): # TODO is anything using this?
return result
# TODO this should take a 'max' argument and return it if 'A' is entered
def ask_num(self, prompt, max_length=8):
num_str = self.ask(prompt,
allowed=lambda c: str(c).isdigit(),
max_length=max_length)
return int(num_str)
def ask_orders(self, prompt, orders_list, other=None):
685c61d8dde962edf2f15d40367ff93203471908
refactoring this statement can wait until the whole method is refactored
rating_str = taipan.ui.abstract.term.reverse + rating_str + taipan.ui.abstract.term.normal
Line 219 in e0e792d
for minimum, title, desc in ratings:
rating_str = '{r:<13} {d:>15}'.format(r=title, d=desc)
if score <= minimum and not rating:
# TODO refactoring this statement can wait until the whole method is refactored
# rating_str = taipan.ui.abstract.term.reverse + rating_str + taipan.ui.abstract.term.normal
rating = title
self.ui.tell(rating_str)
d22c5f70bd21d115f235d7a40118e022ebca6fb2
"Comprador's Report" / "Captain's Report" should prefix some messages
Line 305 in 71f4320
super().__init__(game)
self.messages = None
# TODO "Comprador's Report" / "Captain's Report" should prefix some messages
def tell(self, message, **kwargs):
self.messages.tell(message, **kwargs)
67564b9af3b53756837a8932f9c5dd583e0a9dea
make sure this displays as expected
Line 141 in 71f4320
self.ui.tell(f"{ran} ran away, Taipan!")
self.ui.update(self)
# TODO make sure this displays as expected
def throw_cargo(self):
self.throw_cargo()
prompt = string.goods_str(self.ship,
"You have the following on board, Taipan:"
) + "What shall I throw overboard, Taipan?"
37644aaa1f8816930f29ceb81f569e1cc7a4bce7
I changed them to use all(...) and self.ui.yes_no(...) for much more appealing game play. Like:
self.player.cash >= self.cost,
self.player.ship.free >= 10,
self.player.ship.guns < 1000
]) and self.ui.yes_or_no(f"Do you wish to buy a ship's gun for {self.cost}, Taipan?")
Nice work, thanks!
'damaged' should be set in term.reverse if part of message
Line 227 in b036001
capacity_multiplier = int(game.player.ship.capacity / 50)
self.cost = random.randint(0, time_multiplier) * capacity_multiplier + 1000
def condition(self):
return all([
self.player.cash >= self.cost,
self.ui.yes_or_no(f"Do you wish to trade in your {'damaged' if self.player.ship.damage else 'fine'} "
f"ship for one with 50 more capacity by paying an additional {self.cost}, Taipan?")
])
def do(self):
# TODO 'damaged' should be set in term.reverse if part of message
self.player.cash -= self.cost
self.player.ship.capacity += 50
self.player.ship.damage = 0
return True
class OpiumSeizure(Event):
base_rate = 0.05
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fine = 0 if not self.player.cash else int((self.player.cash / 1.8) * random.random() + 1)
def condition(self):
return self.player.port.value != 1 and self.player.ship[Goods.OPIUM] > 0
def do(self):
self.ui.tell("Bad Joss!! The local authorities have seized your Opium cargo"
+ (f" and have also fined you {self.fine}" if self.fine else "")
+ ", Taipan!", wait=5)
self.player.ship[Goods.OPIUM] = 0
self.player.cash -= self.fine
return True
class WarehouseTheft(Event):
base_rate = 0.02
def condition(self):
return self.player.warehouse.used > 0
def do(self):
self.ui.tell("Messenger reports large theft from warehouse, Taipan.", wait=5)
for good in Goods:
self.player.warehouse[good] = int((self.player.warehouse[good] / 1.8) * random.random())
return True
class LiWaits(Event):
base_rate = 0.05
def do(self):
if self.player.li_timer > 0:
self.player.li_timer += 1
if self.player.li_timer == 4:
self.player.li_timer = 0
return True
class LiMessenger(Event):
base_rate = 0.25
def condition(self):
return self.player.port.value != 1
def do(self):
self.ui.tell("Li Yuen has sent a Lieutenant, Taipan. He says his admiral wishes to see you in Hong Kong, "
"posthaste!", wait=True)
return True
class GoodPrices(Event):
base_rate = 0.1
def do(self):
good = random.choice(list(Goods))
current_price = self.player.port[good]
if random.randint(0, 2) == 0:
self.player.port[good] = int(current_price / 5)
price_str = "has dropped to {p}!!"
else:
self.player.port[good] = int(current_price * (random.randint(0, 5) + 5))
price_str = "has risen to {p}!!"
msg = str("Taipan!! The price of {n} " + price_str).format(n=str(good), p=self.player.port[good])
self.ui.tell(msg, wait=True)
return True
class Mugging(Event):
base_rate = 0.05
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.amount = int((self.player.cash / 1.4) * random.random())
def condition(self):
return self.player.cash > 25000
def do(self):
self.player.cash -= self.amount
self.ui.update()
self.ui.tell(f"Bad Joss!! You've been beaten up and robbed of {self.amount} in cash, Taipan!!", wait=5)
return True
class Storm(Event):
base_rate = 0.1
def do(self):
self.ui.tell("Storm, Taipan!!", wait=True)
if random.randint(0, 29):
self.ui.tell("I think we're going down!!", wait=True)
if self.player.ship.damage / self.player.ship.capacity * 3 * random.random() >= 1:
self.ui.tell("We're going down, Taipan!!", wait=5)
return False
self.ui.tell("We made it!!", wait=True)
if random.randint(0, 3) == 0:
new_port = random.choice(list(set(Ports) - {self.player.port}))
self.ui.tell(f"We've been blown off course to {new_port}", wait=True)
self.player.port = new_port
return True
class HostileEncounter(Event):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.ships = min(9999, (random.randint(0, self.player.ship.capacity / 10) + self.player.ship.guns) + 1)
def condition(self):
return random.random() <= 1 / self.player.bp
def do(self):
self.ui.tell(f'{self.ships} hostile ships approaching, Taipan!', wait=True)
result = Battle(self.game, self.ships).do()
if result is BattleResult.BATTLE_INTERRUPTED:
self.ui.update() # TODO 'Location' in UI should read 'At sea' here
self.ui.tell("Li Yuen's fleet drove them off!", wait=True)
if any([
result is BattleResult.BATTLE_INTERRUPTED,
result is BattleResult.BATTLE_NOT_FINISHED
and random.randint(0, 8 * self.player.li_timer + 4) == 0
]):
self.ui.tell("Li Yuen's pirates, Taipan!!", wait=True)
if self.player.li_timer > 0:
self.ui.tell("Good joss!! They let us be!!", wait=True)
return True
else:
self.ships = random.randint(0, (self.player.ship.capacity / 5) + self.player.ship.guns) + 5
self.ui.tell(f"{self.ships} ships of Li Yuen's pirate fleet, Taipan!!", wait=True)
result = Battle(self.game, self.ships, li=True).do()
if result is not BattleResult.BATTLE_NOT_FINISHED:
self.ui.update() # TODO 'Location' in UI should read 'At sea' here
if result is BattleResult.BATTLE_WON:
booty = int((self.player.months / 4 * 1000 * self.ships) + random.randint(0, 1000) + 250)
self.ui.tell(f"We captured some booty. It's worth {booty}!", wait=3)
self.player.cash += booty
elif result is BattleResult.BATTLE_FLED:
self.ui.tell(f"We made it!", wait=3)
else:
assert (result != BattleResult.BATTLE_INTERRUPTED)
self.ui.tell("The buggers got us, Taipan!!! It's all over, now!!!", wait=5)
return False
return True
f2ff2c6423423e692e99707abaaa7a1095cb58fa
most of this code should go into a separate UI/view class
Line 173 in 71f4320
else:
break
# TODO most of this code should go into a separate UI/view class
def end(self):
"""Displays final stats"""
self.running = False
c18e959eb00e4f51116e7ef0c966731e06655cff
I'd love to give this a try, but I'm new to python and aren't familiar with the build tool you used. Some real quick instructions on how to run this would be fantastic.
"Comprador's Report" / "Captain's Report" should prefix some messages
taipan/src/taipan/ui/abstract.py
Line 267 in e0e792d
from textwrap import wrap
from time import sleep
import blessed
from .functions import get_char, get_str
term = blessed.Terminal()
ALLOW_SKIP_WAIT = False
class UIObject:
def __init__(self, x1=None, y1=None, x2=None, y2=None,
*,
parent=None,
corner_tr='┐',
corner_br='┘',
corner_bl='└',
corner_tl='┌',
side_t='─',
side_r='│',
side_b='─',
side_l='│',
border=True,
ignore_space=False
):
self.x1 = x1 or 0
self.x2 = x2 or term.width - 1
self.y1 = y1 or 0
self.y2 = y2 or term.height - 1
self.corner_tr = corner_tr
self.corner_br = corner_br
self.corner_tl = corner_tl
self.corner_bl = corner_bl
self.side_t = side_t
self.side_r = side_r
self.side_b = side_b
self.side_l = side_l
self.border = border
self.ignore_space = ignore_space
self.parent = parent
self.children = []
self.clear()
@staticmethod
def sleep(s):
with term.cbreak(), term.hidden_cursor():
sleep(s)
@property
def top(self):
return self.parent.top_element if self.parent else self
@property
def size(self):
x = self.x2 - self.x1 + 1
y = self.y2 - self.y1 + 1
return x, y
@property
def origin(self):
return self.x1, self.x2
def absolute_xy(self, x, y):
return x + self.x1, y + self.y1
def add_child(self, *children):
for child in children:
child.parent = self
self.children.append(child)
if len(children) == 1:
return children[0]
return children
def move(self, x1, y1):
self.x1 = x1
self.y1 = y1
self.x2 = x1 + self.size[0] - 1
self.y2 = y1 + self.size[1] - 1
def center(self, v=True, h=True):
parent_size = self.parent.size if self.parent else (term.width, term.height)
x1 = parent_size[0] // 2 - self.size[0] // 2 if v else self.x1
y1 = parent_size[1] // 2 - self.size[1] // 2 if h else self.y1
self.move(x1, y1)
def print_border(self):
if not self.border:
return
with term.hidden_cursor():
# Draw corners
print(term.move_xy(self.x1, self.y1) + self.corner_tl, end='')
print(term.move_xy(self.x2, self.y1) + self.corner_tr, end='')
print(term.move_xy(self.x2, self.y2) + self.corner_br, end='')
print(term.move_xy(self.x1, self.y2) + self.corner_bl, end='')
# Draw horizontal sides
side_width = self.x2 - self.x1 - 1
if self.side_t:
print(term.move_xy(self.x1 + 1, self.y1) + self.side_t * side_width, end='')
if self.side_b:
print(term.move_xy(self.x1 + 1, self.y2) + self.side_b * side_width, end='')
# Draw vertical sides
side_height = self.y2 - self.y1 - 1
for i in range(side_height):
if self.side_l:
print(term.move_xy(self.x1, self.y1 + 1 + i) + self.side_l, end='')
if self.side_r:
print(term.move_xy(self.x2, self.y1 + 1 + i) + self.side_r, end='')
@staticmethod
def flush():
with term.hidden_cursor():
print('', end='', flush=True)
def print(self, x, y, string, flush=True):
x += self.x1 + int(self.border)
y += self.y1 + int(self.border)
with term.hidden_cursor():
print(term.move_xy(x, y), end='')
for char in str(string):
if char == ' ' and self.ignore_space:
char = term.move_right(1)
print(char, end='')
if flush:
self.flush()
def clear(self):
cols = self.size[0] - 2 * int(self.border)
rows = self.size[1] - 2 * int(self.border)
lines = [' ' * cols] * rows
for y, line in enumerate(lines):
self.print(0, y, line)
def _update(self, *args, **kwargs):
pass
def update_all(self, *args, **kwargs):
self.top.update(*args, **kwargs)
def update(self, *args, **kwargs):
self.print_border()
self._update(*args, **kwargs)
self.flush()
for child in self.children:
child.update(*args, **kwargs)
class StaticUIObject(UIObject):
def __init__(self, x1, y1, content, color=None, ignore_space=True, **kwargs):
self.color = color or term.normal
self.lines = content.strip('\n').split('\n')
x2 = x1 + len(self.lines[0]) - 1
y2 = y1 + len(self.lines) - 1
super().__init__(x1, y1, x2, y2, border=False, ignore_space=ignore_space, **kwargs)
def animate(self, *gfx, delay=0.1):
for graphic in gfx:
self.graphic = graphic
self.sleep(delay)
@property
def graphic(self):
return '\n'.join(self.lines)
@graphic.setter
def graphic(self, string):
self.lines = string.split('\n')
self.update()
def _update(self, *args, **kwargs):
with term.hidden_cursor():
print(self.color, end='')
for y, line in enumerate(self.lines):
self.print(0, y, line, flush=True) # TODO are the two hidden cursor calls needed?
print(term.normal, end='', flush=True)
class InteractiveUI(UIObject):
def __init__(self, game, *args, **kwargs):
self.game = game
UIObject.__init__(self, *args, **kwargs)
@property
def player(self):
return self.game.player
@staticmethod
def wait(timeout=3):
if timeout is True:
timeout = 3
with term.cbreak(), term.hidden_cursor():
if ALLOW_SKIP_WAIT:
term.inkey(timeout=timeout)
else:
sleep(timeout)
def tell(self, message, wait=False, clear=True):
if clear:
self.clear()
# Reflow text
lines = [line for block in message.split('\n')
for line in wrap(block, width=self.size[0] - 4)]
for y, line in enumerate(lines):
self.print(0, y, line)
if wait:
self.wait(wait)
def ask(self, prompt='', **kwargs):
self.tell(prompt + ' ')
return get_str(**kwargs)
def ask_num(self, prompt, max_length=8, max_num=None):
num_str = self.ask(prompt,
allowed=lambda c: str(c).isdigit() or max_num and c.lower() == 'a',
max_length=max_length)
if max_num and num_str.lower() == 'a':
return max_num
return int(num_str)
def ask_orders(self, prompt, orders_list, other=None):
def comma_list(str_list, conjunction='or'):
return ', '.join(str_list[:-1]) + f', {conjunction} ' + str_list[-1]
if '{}' in prompt:
prompt = prompt.format(comma_list([str(order) for order in orders_list]))
allowed = [order.shortcut for order in orders_list]
if other:
allowed += other
self.tell(prompt)
choice = get_char(allowed=allowed)
for order in orders_list:
if order.shortcut == choice:
return order
return choice
def yes_or_no(self, prompt=''):
self.tell(prompt)
choice = get_char(allowed=['Y', 'y', 'N', 'n'])
return choice.lower() == 'y'
class GameUI(InteractiveUI):
def __init__(self, game):
super().__init__(game)
self.messages = None
# TODO "Comprador's Report" / "Captain's Report" should prefix some messages
def tell(self, message, **kwargs):
self.messages.tell(message, **kwargs)
8a62dc1ef3ad01b1355b1b5649468a8d641d5eb0
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.