diff options
| -rw-r--r-- | README.md | 30 | ||||
| -rw-r--r-- | explore.py | 97 | ||||
| -rw-r--r-- | inv.py | 1 | ||||
| -rw-r--r-- | isportal.py | 56 | ||||
| -rw-r--r-- | isrock.py | 55 | ||||
| -rw-r--r-- | rocks.py | 41 | ||||
| -rw-r--r-- | sorter.py | 96 | ||||
| -rw-r--r-- | worlds/home | 196 | 
8 files changed, 572 insertions, 0 deletions
diff --git a/README.md b/README.md new file mode 100644 index 0000000..6ec72dd --- /dev/null +++ b/README.md @@ -0,0 +1,30 @@ +# rocks + +An experimental terminal-based game about rocks + +## File Structure and Entry Point +``` +| rocks.py ; the entry point, runs curses and manages explore/graphics +| explore.py ; provides Explore, a class which manages saving, loading, movement, some graphics, inventory, and rock positioning +| inv.py ;one line: "quantity = 0". This sections off the inventory into inv.quantity for explore and could permit more extension in the future +| isrock.py ; makes use of sorter.py and manages all items as rocks (including portals to prevent intersection --- however, "type of rock" is noted) +| isportal.py ; also makes use of sorter.py. manages all portals' saving, movement, and interlinking +| sorter.py ; creates a new, efficient data type that automatically insorts coordinates with bisection and allows indexing, removal, etc. +| README.md ; you're reading it right now +| worlds ; a directory which holds all files created by save data +``` + +## Necessary Tools + +Python3.7.2 was used to develop this app, and to run it, `curses`, `hashlib`, and `math` must be available. + +## Controls + +`rocks.py` runs as a full window terminal application with a few controls, namely: +- `q`: quits the program and returns to normal terminal mode +- `s`: saves at a requested file location in worlds +- `l`: loads from a file in worlds (if not available, does nothing) +- arrow keys: moves cursor around world and through portals +- `.` picks up rocks +- `/` puts down rocks in your inventory +- `\\` puts down portals (cost 10 rocks) diff --git a/explore.py b/explore.py new file mode 100644 index 0000000..fa8239c --- /dev/null +++ b/explore.py @@ -0,0 +1,97 @@ +import isrock +import isportal +from math import floor +from curses import KEY_LEFT as left, KEY_RIGHT as right, KEY_DOWN as down, KEY_UP as up +import inv + +def splitlist(liststr, strsplit): +  newlist = [] +  sublist = [] +  for item in liststr: +    if item == strsplit: +      newlist.append(sublist) +      sublist = [] +    else: +      sublist.append(item) +  newlist.append(sublist) +  return newlist + +class Explore: +  def __init__(self, density, x=0, y=0): +    self.density = density +    self.x = x +    self.y = y + +  def get_area(self, dim): +    out = '' +    for y in range(dim[0]): +      y += self.y +      for x in range(floor(dim[1]/2)): +        x += self.x +        pos = isrock.isrock(self.density, (x,y), typereq=True) +        if not pos[0]: +          out += ' ' +        elif pos[1] == 'n': +          out += '*' +        else: +          out += '\\' +        out += ' ' +      out = out[:-1] +      out += '\n' +    return out[:-1] + +  def displace(self, dim, dis): +    self.move((self.x + dim[0],  self.y + dim[1]), dis) + +  def move(self, dim, dis): +    self.x, self.y = dim[0], dim[1] +    dim = (dim[0]+dis[1], dim[1]+dis[0]) +    if isportal.isportal(dim):  +      dim = isportal.link(dim) +      self.x, self.y = dim[0]-dis[1], dim[1]-dis[0] + +  def interpretkey(self, key): +    return {up:(0,-1), left:(-1,0), down:(0,1), right:(1,0)}[key]  + +  def rockmod(self, dim, state): +    dim = (self.x + dim[1], self.y + dim[0]) +    if state != isrock.isrock(self.density, dim): +      if state and inv.quantity <= 0: +        return +      isrock.modrock(dim, state) +      inv.quantity += -1 if state else 1 + +  def getinv(self): +    return inv.quantity + +  def load(self, file): +    try: +      f = open('./worlds/'+str(file)[2:-1],'r') +      data = [line[:-1] for line in f.readlines()] #removes newlines +      f.close() +    except: +      return "File Doesn't Exist" +    loc = [int(num) for num in data[0].split(' ')] +    self.x, self.y, inv.quantity = loc[0], loc[1], loc[2] +    data = splitlist(data[1:], '______') +    isrock.load(data[0]) +    isportal.load(data[1]) + +  def save(self, file): +    f = open('./worlds/'+str(file)[2:-1],'w') +    data = str(self.x) + ' ' + str(self.y) + ' ' + str(inv.quantity) + '\n' +    data += isrock.save() +    data += '______\n' + isportal.save() +    f.write(data) +    f.close() + +  def setportal(self, dim): +    dim = (dim[1]+self.x, dim[0]+self.y) +    if not isrock.isrock(self.density, dim) and inv.quantity >= 10: +      inv.quantity -= 10 +      isrock.modrock(dim, True, 'p') +      isportal.modportal(dim) +    elif isrock.isrock(self.density, dim, typereq=True)[1] == 'p': +      inv.quantity += 10 +      isrock.modrock(dim, False) +      isportal.modportal(dim) @@ -0,0 +1 @@ +quantity = 0 diff --git a/isportal.py b/isportal.py new file mode 100644 index 0000000..3e88968 --- /dev/null +++ b/isportal.py @@ -0,0 +1,56 @@ +from sorter import Sorter + +portals = Sorter() +links = [] +lastportal = None #dim of last portal + +def conlast(cur): +  global lastportal +  ind = portals.index(cur) +  if lastportal == None: #self-connect and set as lastportal +    links[ind] = cur +    lastportal = cur +  else: #connect to last portal, connect last portal to self, and set lastportal as None +    links[ind] = lastportal +    links[portals.index(lastportal)] = cur +    lastportal = None + +def isportal(dim): +  if dim in portals: +    return True +  return False + +def link(dim): +  return links[portals.index(dim)] + +def unlink(dim): #remove portal from main list, and update the link of its pair +  global lastportal +  plink = links[portals.index(dim)] +  if plink != dim: +    conlast(plink) +  else: +    if dim == lastportal: +      lastportal = None + +def modportal(dim): +  if dim in portals: #remove portal +    unlink(dim) +    links.pop(portals.remove(dim)) +  else: #add portal +    links.insert(portals.insert(dim),()) +    conlast(dim) + +def load(data): +  lastportal = None if data[0] == 'None' else int(data[0]) +  portals = Sorter() +  links = [] +  for line in data[1:]: +    line = [int(num) for num in line.split(' ')] +    ind = portals.insert((line[0],line[1])) +    links.insert(ind, (line[2], line[3])) + +def save(): +  outtext = str(lastportal) + '\n' +  for portalind in range(len(portals)): +    outtext += str(portals[portalind][0]) + ' ' + str(portals[portalind][1]) + ' ' + str(links[portalind][0]) + ' ' + str(links[portalind][1]) + '\n' +  return outtext diff --git a/isrock.py b/isrock.py new file mode 100644 index 0000000..085b25b --- /dev/null +++ b/isrock.py @@ -0,0 +1,55 @@ +from hashlib import md5 +from sorter import Sorter + +rockmodp = Sorter() #positions of rockmod +rockmodv = [] #values of rockmod +rockmodt = [] #rock type (to include portals) + +def modrock(dim, state, type='n'): #type is by default normal; p is portal +  if state and type == 'n': +    type = 'n' +  if dim in rockmodp: +    loc = rockmodp.index(dim) +    rockmodv[loc] = state +    rockmodt[loc] = type +  else: +    loc = rockmodp.insert(dim) +    rockmodv.insert(loc, state) +    rockmodt.insert(loc, type) + + +def isrock(density, dim, typereq=False): #if typereq, returns if a rock or portal +  x, y = dim[0], dim[1] +  m = md5() +  m.update(intbyt(x)) +  m.update(intbyt(y)) +  rockpres = (bytint(m.digest()[:2])/(256**2) < density) +  if dim in rockmodp: +    loc = rockmodp.index(dim) +    rockpres = rockmodv[loc] +    if typereq: +      return (rockpres, rockmodt[loc]) +  if typereq: +    return (rockpres, 'n') +  return rockpres + +def intbyt(num): +  return int(num).to_bytes(8, 'big', signed=True) + +def bytint(byt): +  return int.from_bytes(byt, byteorder='big') + +def save(): +  outtext = '' +  for mvind in range(len(rockmodp)): +    outtext += str(rockmodp[mvind][0]) + ' ' + str(rockmodp[mvind][1]) + ' ' + str(rockmodv[mvind]) + ' ' + str(rockmodt[mvind]) + '\n' +  return outtext + +def load(data): +  global rockmodp, rockmodv, rockmodt +  rockmodp, rockmodv, rockmodt = Sorter(), [], [] +  for line in data: +    line = line.split(' ') +    ind = rockmodp.insert((int(line[0]), int(line[1]))) +    rockmodv.insert(ind, line[2] == 'True') +    rockmodt.insert(ind, line[3]) diff --git a/rocks.py b/rocks.py new file mode 100644 index 0000000..975c799 --- /dev/null +++ b/rocks.py @@ -0,0 +1,41 @@ +from explore import Explore +import curses +from math import floor + +explore = Explore(0.0025) + +screen = curses.initscr() +curses.noecho() #makes keys not put text on screen +curses.cbreak() #removes buffer, allowing prog to receive control immediately +screen.keypad(True) #allows arrow keys and things to work +screen.scrollok(False) #disables scrolling + +key = '' +while key != ord('q'): +  dim = screen.getmaxyx() +  if key in [curses.KEY_DOWN, curses.KEY_RIGHT, curses.KEY_UP, curses.KEY_LEFT]: +    explore.displace(explore.interpretkey(key),(floor(dim[0]/2),floor(dim[1]/4))) +  if key in [ord('.'), ord('/')]: +    explore.rockmod((floor(dim[0]/2), floor(dim[1]/4)), key == ord('/')) +  if key in [ord('s'), ord('l')]: +    screen.addstr(dim[0]-1, 0, 'File:'+' '*(dim[1]-6)) +    curses.echo() +    file = screen.getstr(dim[0]-1, 6) +    curses.noecho() +    if key == ord('s'): +      explore.save(file) +    else: +      screen.clear() +      explore.load(file) +  if key == ord('\\'): +    explore.setportal((floor(dim[0]/2), floor(dim[1]/4))) +  screen.addstr(0, 0, explore.get_area(dim)) +  screen.addstr(dim[0]-1, 0, str(explore.getinv())) +  screen.refresh() +  key = screen.getch(floor(dim[0]/2), 2*floor(dim[1]/4)) + +screen.scrollok(True) +curses.nocbreak() +screen.keypad(False) +curses.echo() +curses.endwin() #finished program; resets to normal diff --git a/sorter.py b/sorter.py new file mode 100644 index 0000000..ced8d7f --- /dev/null +++ b/sorter.py @@ -0,0 +1,96 @@ +from math import floor + +class Sorter: +  def __init__(self): +    self.data = [] + +  def __len__(self): +    return len(self.data) + +  def comp(self, item1, item2): #these should be dim's +    #will return 'e' for equal, 'l' for less (i.e. item1 < item2), and 'g' for greater +    if item1[0] < item2[0]: +      return 'l' +    if item1[0] == item2[0] and item1[1] < item2[1]: +      return 'l' +    if item1[0] == item2[0] and item1[1] == item2[1]: +      return 'e' +    if item1[0] == item2[0] and item1[1] > item2[1]: +      return 'g' +    if item1[0] > item2[0]: +      return 'g' + +  def __contains__(self, item): +    if len(self) == 0: #messes up the code if length == 0 +      return False +    lo = 0 +    hi = len(self) - 1 +    while hi > lo: +      mid = floor((hi-lo)/2)+lo +      dif = self.comp(self[mid],item) +      if dif == 'l': +        hi = mid-1 +      if dif == 'g': +        lo = mid+1 +      if dif == 'e': +        return True +    if self[lo] == item: +      return True +    return False + +  def __getitem__(self, ind): +    return self.data[ind] + +  def insert(self, item): +    if len(self) == 0: +      self.data.insert(0, item) +      return 0 +    lo = 0 +    hi = len(self) - 1 +    while hi > lo: +      mid = floor((hi-lo)/2)+lo +      dif = self.comp(self[mid],item) +      if dif == 'l': +        hi = mid-1 +      if dif == 'g': +        lo = mid+1 +      if dif == 'e': +        self.data.insert(mid, item) +        return mid+1 +    dif = self.comp(self[lo],item) +    if dif == 'l': +      self.data.insert(lo,item) +      return lo +    if dif == 'e' or dif == 'g': +      self.data.insert(lo+1,item) +      return lo+1 + +  def index(self, item): +    lo = 0 +    hi = len(self) - 1 +    while hi > lo: +      mid = floor((hi-lo)/2)+lo +      dif = self.comp(self[mid],item) +      if dif == 'l': +        hi = mid-1 +      if dif == 'g': +        lo = mid+1 +      if dif == 'e': +        return mid +    if item == self[lo]: +      return lo +    else: +      raise(IndexError) + +  def remove(self, item): +    ind = self.index(item) +    self.data.pop(ind) +    return ind + +  def __repr__(self): +    return str(self.data) +test = Sorter() +test.insert((1,2)) +test.insert((1,3)) +print(test.insert((0,2))) +print((1,4) in test) diff --git a/worlds/home b/worlds/home new file mode 100644 index 0000000..d5e131e --- /dev/null +++ b/worlds/home @@ -0,0 +1,196 @@ +247 -49 23 +454 -30 False n +451 -22 False n +447 -55 False n +442 -15 False n +440 -41 False n +437 -9 False n +436 -27 False n +434 36 False n +434 -75 False n +424 48 False n +423 21 False n +421 6 False n +420 22 False n +417 12 False n +412 46 False n +412 -68 False n +406 -17 False n +403 54 False n +397 53 False n +395 -83 False n +392 33 False n +392 1 False n +390 56 False n +389 -89 False n +388 58 False n +388 40 False n +387 -31 False n +387 -66 False n +383 57 False n +382 -44 False n +381 14 False n +366 -15 False n +359 23 False n +356 -6 False n +354 41 False n +351 -26 False n +349 -7 False n +344 20 False n +344 -25 False n +342 11 False n +341 52 False n +334 11 False n +334 4 False n +325 50 False n +320 -12 True p +319 48 False n +316 7 False n +316 -11 True n +316 -15 True n +315 -11 True n +315 -13 True n +315 -15 True n +314 -11 True n +314 -12 True n +314 -13 True n +314 -14 True n +314 -15 True n +313 2 False n +312 -11 True n +312 -12 True n +312 -13 True n +312 -14 True n +312 -15 True n +311 -11 False n +311 -12 False n +311 -13 False n +311 -14 True n +311 -15 False n +310 -13 True n +310 -14 False n +310 -15 False n +309 -13 False n +309 -14 True n +308 31 False n +308 -11 True n +308 -12 True n +308 -13 True n +308 -14 True n +308 -15 True n +307 -7 False n +307 -8 False n +307 -9 True p +307 -19 True p +306 -11 True n +306 -12 True n +306 -13 True n +306 -14 True n +306 -15 True n +305 40 False n +305 -11 True n +305 -15 True n +304 -11 True n +304 -12 False n +304 -13 False n +304 -14 False n +304 -15 True n +303 -7 False n +303 -11 True n +303 -12 True n +303 -13 True n +303 -14 True n +303 -15 True n +302 -11 False n +302 -12 False n +302 -13 False n +302 -14 False n +302 -15 False n +301 -11 True n +301 -12 True n +301 -13 True n +301 -14 True n +301 -15 True n +300 27 False n +300 -11 False n +300 -12 False n +300 -13 True n +300 -14 False n +300 -15 False n +299 -13 True n +298 -11 True n +298 -12 True n +298 -13 True n +298 -14 True n +298 -15 True n +296 22 False n +293 21 False n +293 17 False n +293 7 False n +293 -13 True p +278 13 False n +277 5 False n +276 22 False n +270 13 False n +268 11 False n +262 4 False n +252 -40 False n +252 -48 False n +248 4 False n +247 -23 False n +246 -19 False n +245 -47 False n +244 -5 False n +244 -16 False n +244 -36 False n +240 26 False n +238 3 False n +237 8 False n +230 29 False n +213 29 False n +213 28 False n +213 18 False n +213 -5 False n +211 -22 False n +211 -36 False n +209 -25 False n +203 -30 False n +186 -28 False n +178 -27 False n +167 6 False n +162 -41 False n +159 -12 False n +153 -15 False n +145 1 False n +135 -51 False n +126 -20 False n +123 4 False n +109 56 False n +104 47 False n +100 6 False n +97 40 False n +96 10 False n +89 48 False n +86 8 False n +85 14 False n +82 48 False n +77 -25 False n +74 43 False n +74 14 False n +71 -24 False n +70 59 False n +67 2 False n +66 -43 False n +64 -39 False n +57 -43 False n +56 38 False n +52 55 False n +49 45 False n +46 6 False n +43 -12 False n +______ +None +320 -12 293 -13 +307 -9 307 -19 +307 -19 307 -9 +293 -13 320 -12  | 
