From cf642ffb90f15495100b107582f11a7a0bae412f Mon Sep 17 00:00:00 2001 From: Feynman's Fedora Date: Sat, 29 Jun 2019 23:06:59 -0400 Subject: final commit --- README.md | 30 ++++++++++ explore.py | 97 ++++++++++++++++++++++++++++++ inv.py | 1 + isportal.py | 56 +++++++++++++++++ isrock.py | 55 +++++++++++++++++ rocks.py | 41 +++++++++++++ sorter.py | 96 +++++++++++++++++++++++++++++ worlds/home | 196 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 572 insertions(+) create mode 100644 README.md create mode 100644 explore.py create mode 100644 inv.py create mode 100644 isportal.py create mode 100644 isrock.py create mode 100644 rocks.py create mode 100644 sorter.py create mode 100644 worlds/home 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) diff --git a/inv.py b/inv.py new file mode 100644 index 0000000..8d97b59 --- /dev/null +++ b/inv.py @@ -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 -- cgit