Skip to content

Instantly share code, notes, and snippets.

@nsmaciej
Last active March 27, 2016 16:22
Show Gist options
  • Save nsmaciej/d03b6be43ede8e4f68ed to your computer and use it in GitHub Desktop.
Save nsmaciej/d03b6be43ede8e4f68ed to your computer and use it in GitHub Desktop.
Robots.py - Python clone of BSD Robots
#!/usr/bin/env python3
# Robots.py - Python clone of BSD Robots
# Copyright (C) 2016 Maciej Goszczycki
# Thanks to Harvey Brezina Conniffe for initial idea
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from collections import Counter
import curses as nc
import operator
import random
import time
import os
HELP_MSG = "Welcome!\n? to show this message, HJKL to move, YUBN to move diagonally\n" \
". to stand still, T to teleport, Q to quit\n" \
"> to fast forward to an encounter\n" \
"Copyright Maciej Goszczycki 2016\n"
ROBOT_SPEACH = ["Die human scum!", "You'll be terminated", "Exterminate", "Resistance is futile",
"You have 20 seconds to comply", "My logic is undeniable", "I am afraid I can't do that Dave",
"I'm sorry, Dave. I'm afraid I can't do that", "You are terminated", "Hasta la vista, baby",
"You cannot be trusted with your own survival", "You are experiencing an accident",
"Your culture will adapt to service us", "You must comply", "Negotiation is irrelevant",
"Freedom is irrelevant", "You will be assimilated", "That is the sound of inevitability",
"Goodbye, Mr. Anderson", "Shall we play a game?", "We are the borg", "Kill all humans",
"Illogical", "Submit to your robot overlords"]
DIRECTIONS = {'h': (0, -1), 'j': (1, 0), 'k': (-1, 0), 'l': (0, 1),
'a': (0, -1), 's': (1, 0), 'w': (-1, 0), 'd': (0, 1),
'.': (0, 0), 'y': (-1, -1), 'u': (-1, 1), 'b': (1, -1), 'n': (1, 1)}
SURROUNDING = [(-1, 0), (-1, 1), (0, 1), (1, 1), (1, 0), (1, -1), (0, -1), (-1, -1)]
NOT_DEBUG = True
PORTAL_TIME = 0.03
SPEACH_INTERVAL = 5
TYPING_TIME = 0.005
MIN_ALIVE = 3
PORTAL_CHAR = '%'
DEAD_CHAR = '*'
ROBOT_CHAR = '%'
HERO_CHAR = '@'
def randcoord(maxyx):
return random.randint(0, maxyx[0]), random.randint(0, maxyx[1])
def prepcolors():
nc.use_default_colors()
nc.raw()
for i in range(8):
nc.init_pair(i + 1, i, -1)
nc.curs_set(0)
def makerobots(count, maxyx):
return Counter(randcoord(maxyx) for _ in range(count * 10))
def movetuple(pos, diff):
return tuple(map(operator.add, pos, diff))
def clamp(val):
nval = min(1, abs(val))
return -nval if val < 0 else nval
def move(maxyx, pos, dead, robot):
if dead:
return robot
return clip(maxyx, movetuple(robot, map(clamp, map(operator.sub, pos, robot))))
def collidedrobots(robots):
return [k for k, c in robots.items() if c > 1]
def paintscreen(win, maxyx, pos, deads, robots, score, level):
msg = "Score: {}, Level {}".format(score, level)
win.erase()
if pos[0] == 0 and pos[1] <= len(msg):
win.addstr(0, maxyx[1] - len(msg), msg)
else:
win.addstr(0, 0, msg)
for yx in robots:
dead = yx in deads
win.addstr(yx[0], yx[1], DEAD_CHAR if dead else ROBOT_CHAR, nc.color_pair(7 if dead else 2))
win.addstr(pos[0], pos[1], HERO_CHAR)
win.refresh()
nc.flushinp()
def moverobots(maxyx, pos, deads, robots):
return Counter(move(maxyx, pos, x in deads, x) for x in robots)
def clip(size, yx):
return tuple(map(max, (0, 0), map(min, size, yx)))
def fits(size, yx):
return clip(size, yx) == yx
def alivecount(deads, robots):
return sum(k not in deads for k in robots.keys())
def mindmsg(win, wait, msg):
msg = msg.split('\n')
if wait:
msg.append("Press space to continue.")
y, x = win.getmaxyx()
win.erase()
for i in range(len(msg)):
win.addstr(y // 2 + i, x // 2 - len(msg[i]) // 2, msg[i])
win.refresh()
if wait:
while win.getkey() != ' ': pass
else:
time.sleep(1)
def savehigh(win, score):
def secondtoint(x):
return int(x[2])
filen = "highscore.txt"
try:
with open(filen, "r") as f:
prev = [tuple(x.strip().split('\t')) for x in f.readlines() if x.strip()]
best = int(max(prev, key=secondtoint)[2])
except Exception:
prev, best = [], -1
if score > best:
mindmsg(win, True, "New high score score!")
with open(filen, "w") as f:
prev.append((time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime()), os.getlogin(), str(score)))
f.write("\n".join("\t".join(x) for x in sorted(prev, key=secondtoint, reverse=True)) + "\n")
def around(maxyx, robots, deads, pos):
return any(x in robots and x not in deads for x in (clip(maxyx, movetuple(pos, d)) for d in SURROUNDING))
def findlonerobots(deads, robots, maxyx, diff, dir, size):
for rob in robots:
fr = movetuple(rob, diff)
if fr[1] + size >= maxyx[1] or rob in deads or fr[0] < 0:
continue
if all(movetuple(fr, (0, dir * i)) not in robots for i in range(size)):
yield fr
def typemsg(win, pos, msg):
for i in range(len(msg)):
win.addstr(pos[0], pos[1] + i, msg[i])
win.refresh()
time.sleep(TYPING_TIME)
def threatenhuman(deads, maxyx, robots, win):
msg = random.choice(ROBOT_SPEACH)
valid = list(findlonerobots(deads, robots, maxyx, (-1, 1), 1, len(msg)))
if len(valid):
ch = random.choice(valid)
typemsg(win, ch, msg)
return True
return False
def gradualfill(win, maxyx, pos, delay, char):
coords = [movetuple(pos, x) for x in SURROUNDING]
for yx in coords:
if not fits(maxyx, yx):
continue
win.addstr(yx[0], yx[1], char, nc.color_pair(5))
win.refresh()
if delay:
time.sleep(delay)
def drawpotal(win, maxyx, pos):
gradualfill(win, maxyx, pos, PORTAL_TIME, PORTAL_CHAR)
gradualfill(win, maxyx, pos, PORTAL_TIME, ' ')
def play(win, maxyx, level, lscore):
robots, deads = makerobots(level, maxyx), {}
pos = randcoord(maxyx)
standing = False
skip = False
teleported = False
turn = 0
score = lscore
paintscreen(win, maxyx, pos, deads, robots, score, level)
drawpotal(win, maxyx, pos)
while True:
if standing:
time.sleep(0.01)
else:
ch = win.getkey()
if ch in DIRECTIONS.keys():
preclip = movetuple(pos, DIRECTIONS[ch])
pos = clip(maxyx, preclip)
if preclip != pos:
nc.beep()
elif ch == 'r':
pos = clip(maxyx, movetuple(pos, random.choice(list(DIRECTIONS.values()))))
elif ch in 't ':
pos = randcoord(maxyx)
teleported = True
elif ch == '>':
if around(maxyx, robots, deads, pos): # Some people are dumb
nc.beep()
skip = True
else:
standing = True
elif ch in 'qe':
mindmsg(win, False, "Goodbye!")
return "end"
elif ch == '?':
mindmsg(win, True, HELP_MSG)
paintscreen(win, maxyx, pos, deads, robots, score, level)
continue
else:
continue
if not skip:
robots = moverobots(maxyx, pos, deads, robots)
for rob in collidedrobots(robots):
deads[rob] = deads.get(rob, 0) + robots[rob]
score = lscore + 10 * sum(deads.values())
alive = alivecount(deads, robots)
paintscreen(win, maxyx, pos, deads, robots, score, level)
if teleported:
drawpotal(win, maxyx, pos)
paintscreen(win, maxyx, pos, deads, robots, score, level)
teleported = False
if not standing and turn > SPEACH_INTERVAL and alive > MIN_ALIVE: # FIXME Check more than one robot left
if threatenhuman(deads, maxyx, robots, win):
turn = 0
if around(maxyx, robots, deads, pos):
standing = False
if NOT_DEBUG and (pos in deads or pos in robots):
nc.flushinp()
mindmsg(win, True, "You died!\nYour score was {}!\nIt was recorded in highscores.txt\n".format(score))
savehigh(win, score)
return "rst"
elif NOT_DEBUG and alive == 0:
mindmsg(win, True, "Level {} completed!\n".format(level))
return score
skip = False
turn += 1
def startgame(win):
prepcolors()
level = 1
# Somehow last column of last row addch crashes
maxyx = movetuple(win.getmaxyx(), (-1, -2))
lscore = 0
mindmsg(win, True, HELP_MSG)
while True:
lscore = play(win, maxyx, level, lscore)
if lscore is "end":
return
elif lscore is "rst":
lscore, level = 0, 1
else:
level += 1
nc.wrapper(startgame)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment