Gold Horse

In honour of the Raspberry Pi, I have rewritten an old program I first wrote over thirty years ago on a time-shared computer my school shared with every other school in Birmingham, using a 110 baud teletype.

History

Back in 1979 I was doing my A-level in Computer Science, and this was one of the programs I submitted. I wrote it on a 110baud teletype talking to a time-shared computer somewhere in the Birmingham Council Treasury department. As I left school, they took possession of a new Research Machines 380Z that the PTA had bought them for around £10,000. But, with only a longing glance over my shoulder, I got my A-level and proceeded to Manchester University. That's relevant because that is where the JMB exam board was based, which came in very handy when on registering for our courses we found that we did not have the right paperwork and had to queue up outside the JMB office to get it.

So I registered on the course and started lectures, and was introduced to the computer centre's computer. As part of that I was looking around to see what was there and how it was used and stuff. Imagine my surprise, when I found the program I had written, and sent to the JMB office just around the corner, was already on the computer!

After a few enquiries, it turned out that, in the intervening time between June and October, an enterprising pupil at my old school had converted the program to run on the 380Z and submitted it to a magazine. A staff member in the computer centre had seen it and reconverted it to run on the rather more impressive computer there.

I've not got the original listing unfortunately, so I've re-written it in Python.

Backstory

You fall down a pit into a dangerous dungeon. The dungeon is a grid of identical rooms, some of which contain monsters. It is dark and you start stumbling around. If you can survive long enough you can gather items to help you find your way out, some of the monsters are friendly and will give you stuff and if you can beat the evil ones you can take stuff off them, but as you work your way up and out, the monster become more evil and more difficult to beat.

In the centre of the dungeon is a deep well of healing water, but there is a cost.

The Code

You don't have to type this in if you don't want to; copy-and-paste if you must.

There are still a few bugs in there and there is plenty of room for enhancements. I've used BASIC style for the most part, except where I could not bear not to use objects, so it should be accessible to those of you who learned to program on the Spectrum or BBC Micro by typing in programs from magazines. There are no comments; there never were in the magazines.

#! /usr/bin/python
from __future__ import absolute_import, division, generators, unicode_literals, print_function, nested_scopes, with_statement
from random import randint, seed, randrange
import sys
from math import sqrt,atan2
from time import sleep
 
boardsize = 100
exitlevel = 6
 
if sys.version_info < (3,):
    input = raw_input
 
class MonsterClass(object):
    def __init__(self, name, probgood, health, offence=0, defence=0):
        self.name = name
        self.probgood = probgood
        self.health = health
        self.offence = offence
        self.defence = defence
 
MonsterClasses = [ MonsterClass("Gold Horse", 80, 100,10,10), \
        MonsterClass("Goblin", 20, 30), \
        MonsterClass("Ghost", 20, 80,5,5), \
        MonsterClass("Demon", 10, 200,15,15), \
        MonsterClass("Knight", 100, 100,5,5), \
        MonsterClass("Mage", 50, 100,0,0), \
        MonsterClass("Kobold", 20, 10,-3,-3), \
        MonsterClass("Elf", 80, 100,0,0), \
        MonsterClass("Dragon", 50, 300,20,20), \
        MonsterClass("Vampire", 20, 200,10,10), \
        MonsterClass("Giant Spider", 20, 100,0,0), \
        MonsterClass("Dwarf", 80, 100,0,0), \
        MonsterClass("Mummy", 50, 100,5,5), \
        MonsterClass("Goblin", 20, 30,3,3)]
 
class Monster(object):
    def __init__(self, mclass, level):
        self.mclass = mclass
        self.name = mclass.name
        probgood = mclass.probgood/level
        if randint(1,100) < probgood:
            self.align = "good"
        elif randint(1,100) < 50/level:
            self.align = "neutral"
        else:
            self.align = "evil"
 
        self.health = mclass.health
        self.offence = mclass.offence
        self.defence = mclass.defence
 
    def __str__(self):
        return  self.name
 
    def take_damage(self,damage):
        self.health = self.health - damage
 
    def isalive(self):
        return self.health > 0
 
def populate_board():
    num_monsters = boardsize**2 / ((exitlevel-level)/2+1)
    for m in range(int(num_monsters)):
        m_x, m_y = (randint(1,boardsize), randint(1,boardsize))
        while (m_x, m_y) in board:
            m_x, m_y = (randint(1,boardsize), randint(1,boardsize))
        monster = Monster(MonsterClasses[randrange(len(MonsterClasses))], level)    
        board[(m_x, m_y)] = monster
 
def new_level():
    global x, y, exit_x, exit_y, board
    x  = randint(1,100)
    y =  randint(1,100)
    exit_x = x
    exit_y = y
    while exit_x == x and exit_y == y:
        exit_x = randint(1,100)
        exit_y = randint(1,100)
 
    board = dict()
    populate_board()
 
def get_move():
    global x, y, health
 
    if "light" not in inventory.get_capabilities():
        input("Hit Enter to enter a room")
        nx = x
        ny = y
        while nx == x and ny == y:
            nx = randint(1,boardsize)
            ny = randint(1,boardsize)
        x = nx
        y = ny
    else:
        gotit = False
        while not gotit:
            try:
                x, y = [int(n) for n in input("Which room do you want to enter? (x,y): ").split(",")]
                if not (1 <= x <= boardsize and 1 <= y <= boardsize):
                    raise ValueError()
            except ValueError:
                print("Oops!  Enter two signed numbers (1 -",boardsize,") separated by a comma.  Try again...")
            else:
                gotit = True
 
class Item(object):
    def __init__(self, name, defence=0, offence=0, capabilities=None, gold=0):
        self.name = name
        self.defence = defence
        self.offence = offence
           self.capabilities = set()
        if capabilities != None:
            self.capabilities.add(capabilities)
        print( self.name, "capabilities=", capabilities, self.capabilities)
        self.gold = gold
 
    def __str__(self):
        s = "a " + self.name
        if self.gold > 0:
            s = s + " containing "+str(self.gold)+" gold pieces"
        return s
 
class Inventory(object):
    def __init__(self):
        self.items = []
        self.wealth = 0
 
    def print(self):
        print (strlist(self.items))
 
    def append(self, item):
        if type(item) is list:
            self.items = self.items + item
        else:
            self.items.append(item)
 
    def get_capabilities(self):
        capabilities = set()
        for item in self.items:
            capabilities = capabilities | item.capabilities
        return capabilities
 
    def len(self):
        return len(self.items)
 
    def lose(self):
        i = randrange(len(self.items))
        item = self.items[i]
        del self.items[i]
        return item
 
    def defence(self):
        d = 0
        for item in self.items:
            d = d + item.defence
        return d
 
    def offence(self):
        a = 0
        for item in self.items:
            a = a + item.offence
        return a
 
def minimum(x,y):
    return x if x < y else y
 
def booty():
    items = []
    if randrange(0,100) < 75:
        capabilities = inventory.get_capabilities()
        if "light" not in capabilities:
            items.append(Item("lantern", 0,0, "light"))
        elif "distance" not in capabilities:
            items.append(Item("distance meter", 0,0, "distance"))
        elif "compass" not in capabilities and level > 1:
            items.append(Item("compass", 0,0, "compass"))
        elif "bearing" not in capabilities and level > 2:
            items.append(Item("compass calibration", 0,0, "bearing"))
    if randrange(0,100) < 50:
        if randrange(0,100) < 25:
            items.append(Item("sword", 3,0))
        elif randrange(0,100) < 25:
            items.append(Item("spear", 2,0))
        elif randrange(0,100) < 25:
            items.append(Item("dagger", 1,0))
        elif randrange(0,100) < 25:
            items.append(Item("mace", 4,0))
        elif randrange(0,100) < 25:
            items.append(Item("pike", 3,0))
    if randrange(0,100) < 50:
        if randrange(0,100) < 50:
            items.append(Item("piece of leather armour", 0,1))
        elif randrange(0,100) < 50:
            items.append(Item("piece of chain armour", 0,2))
        elif randrange(0,100) < 50:
            items.append(Item("piece of plate armour", 0,3))
 
    money = randint(1,10)
    items.append(Item("Money Pouch",gold=money))
    return items
 
def strlist(items):
    i = len(items)
    if i == 0:
        s = "nothing"
    elif i == 1:
        s = str(items[0])
    elif i == 2:
        s = str(items[0])+" and "+str(items[1])
    else:
        s = ""
        for j in range(i-2):
            s = s + str(items[j]) + ", "
        s = s + strlist(items[-2:])
    return s
 
def good_encounter(monster):
    global health
 
    print ("The", str(monster), "is friendly")
    if health < 100:
        extra = minimum(100-health, monster.health/2)
        print ("The", str(monster), "heals you of", extra, "points")
        health = health + extra
        return True
    items = booty()
    print ("The", str(monster), "gives you", strlist(items))
    inventory.append(items)
    return True
 
def attack(offence, defence):
    damage = randint(1,20)+offence-defence
    if damage < 0:
        damage = 0
    return damage
 
def evil_encounter(monster, surprise=False):
    global health
 
    while monster.isalive() and health > 0:
        if surprise:
            damage = attack(inventory.offence(), monster.defence)
            print ("You attack the", str(monster), "and wound it for", damage,"points")
            monster.take_damage(damage)
            if not monster.isalive():
                print ("You killed it!")
                items = booty()
                print ("You find", strlist(items))
                inventory.append(items)
                return True
 
        if monster.isalive() and health > 0:
            print ("The", str(monster), "attacks")
            surprise = True
            damage = attack(monster.offence, inventory.defence())
            print ("The", str(monster), "attacks you and wounds you for", damage,"points")
            health = health - damage
            print ("Your health is", health,"%")
            if health <= 0:
                print ("You are dead!")
 
        if monster.isalive() and health > 0:
            action = ""
            while len(action) == 0 or action[0:1] not in "raRA":
                action = input("Do you want to Run or Attack? ")
            if action[0] in "Rr":
                damage = attack(monster.offence, inventory.defence())
                print ("The", str(monster), "attacks you and wounds you for", damage,"points")
                health = health - damage
                print ("Your health is", health,"%")
                if health <= 0:
                    print ("You are dead!")
                    return False
                else:
                    get_move()
                    if randrange(100) < 50:
                        print ("The ", str(monster)," follows you")
                    else:
                        encounter()
                        return False
    return False
 
def well_encounter():
    global health
 
    print ("In the room there is a deep well.")
    action = ""
    while len(action) == 0 or action[0:1] not in "yYnN":
        action = input("Do you want to drink from the well? ")
    if action[0] in "yY":
        if health == 100 or inventory.len() == 0:
            print ("It is very refreshing")
        else:
            print("As you take a long refreshing drink, ",end="")
            item = inventory.lose()
            print(str(item), "falls down the well!")
            health = 100
 
def encounter():
    if (x,y) in board:
        monster = board[(x,y)]
        mx,my = (x,y)
        print ("In the room there is a ", str(monster))
        if monster.align == "evil":
            if evil_encounter(monster):
                del board[(mx,my)]
        elif monster.align == "good":
            if good_encounter(monster): 
                del board[(mx,my)]
        else:
            if randrange(100) < 50:
                if good_encounter(monster):
                    del board[(mx,my)]
            elif randrange(100) < 50 and "light" in inventory.get_capabilities():
                if evil_encounter(monster):
                    del board[(mx,my)]
            else:
                action = ""
                while len(action) == 0 or action[0:1] not in "iaIA":
                    action = input("Do you want to Ignore or Attack? ")
                if action[0] in "Aa":
                    if evil_encounter(monster, surprise = True):
                        del board[(mx,my)]
    if x == 50 and y == 50 and health > 0:
        well_encounter()
 
def print_position():
    capabilities = inventory.get_capabilities()
    dx = x - exit_x
    dy = y - exit_y
 
    if "light" in capabilities:
        print ("You are in room (", x, ",", y, ")")
 
    if "compass" in capabilities:
        dir =""
        if abs(dy) > abs(dx)/3:
            if dy < 0:
                dir = dir + "N"
            else:
                dir = dir + "S"
        if abs(dx) > abs(dy)/3:
            if dx < 0:
                dir = dir + "E"
            else:
                dir = dir + "W"
 
        print ("You need to head", dir)
 
    if "bearing" in capabilities:
        b = atan2(-dy,-dx)
        print (b)
        b = b/3.1415926/2*360
        b = b - 90
        b = 360 - b
        print (b)
        if b < 0: b = b + 360
        if b > 360: b = b - 360
        print ("The exit is on a bearing of", int(b), "degrees")
 
    if "distance" in capabilities:
        print ("The exit is ", sqrt(dx*dx + dy*dy), "away")
 
level = 1
moves = 0
health = 100
alive = True
seed()
new_level()
inventory = Inventory()
#magic_box = Item("magic box", 0,0, {"light", "compass", "distance", "bearing"})
#inventory.append(magic_box)
 
while health > 0 and level < exitlevel:
    moves = moves + 1
    print ("\nMove ", moves, ":")
    print("Your health is ", health, "%")
    old_health = health
    sleep(0.5)
    print_position()
    sleep(0.5)
    get_move()
    if health > 0:
        encounter()
    if x == exit_x and y == exit_y:
        print ("**** Well done! you have found the stairs ****")
        sleep(3)
        level = level + 1
        new_level()
 
    if health < 100 and old_health == health:
        health = health + 1
 
sleep(2)
 
if health > 0:
    print ("Sunlight! You got out alive after", moves, "moves, with...")
    inventory.print()
else:
    print ("Sorry; you died on level ", level)
 
input("(Hit Enter to Exit)")

What is a Gold Horse? Blowed if I know. Back in the 1970's I simply made it up when I ran out of monster names.
Add a New Comment
Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-NonCommercial-ShareAlike 3.0 License