Skip to content

Instantly share code, notes, and snippets.

@pintman
Created August 16, 2018 06:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pintman/52cd40876946c0e0b08f3ee92297af95 to your computer and use it in GitHub Desktop.
Save pintman/52cd40876946c0e0b08f3ee92297af95 to your computer and use it in GitHub Desktop.
FlipDotHero - minigamejam in Dortmund (2018-08-05)
import math
import time
import random
import levels
try:
import rogueflip
import pygame
except ImportError as e:
print("Unable to import pygame or rogueflip. \
Some games may not be runnable!", e)
class Fdh:
""" FlipDotHero """
def __init__(self, flipdotdisplay):
self.fdd = flipdotdisplay
self.ch1 = Channel(self.fdd, levels.l3[0], False)
self.ch2 = Channel(self.fdd, levels.l3[1], True)
self.ch3 = Channel(self.fdd, levels.l3[2], True)
#self.ch4 = Channel(self.fdd, 3, 3, 3)
def run(self):
while True:
self.ch1.draw()
self.ch2.draw()
self.ch3.draw()
#self.ch4.draw()
self.fdd.show(True)
class Channel():
def __init__(self, fdd, channelconf, visible=False):
self.visible = visible
self.fdd = fdd
self.cc = channelconf
self.dreh = channelconf.dreh
self.schieb = channelconf.schieb
self.position = (channelconf.chan-1)*6
self.takte = channelconf.takte
self.dreh_ist = 0
self.schieb_ist = 0
self.btn_ist = False
self.taktschritt = 0
def setconf(self, channelconf):
self.cc = channelconf
self.dreh = channelconf.dreh
self.schieb = channelconf.schieb
self.position = (channelconf.chan-1)*6
self.takte = channelconf.takte
def settakt(self, taktschritt):
self.taktschritt = taktschritt
def draw(self):
""" dreh """
posx = [1,1,3,3]
posy = [2,0,0,2]
if self.visible:
self.fdd.px(self.position+2, 1, True)
self.fdd.px(self.position+posx[self.dreh], posy[self.dreh], True)
""" schieb """
for y in range(4):
self.fdd.px(self.position+2, 4+y, self.schieb>=(3-y))
""" solltakt """
for x in range(4):
self.fdd.px(self.position+1+x, self.fdd.height-2, self.takte[x])
""" line """
for y in range(self.fdd.height-4):
self.fdd.px(self.position+5, y, True)
""" takt """
for x in range(4):
self.fdd.px(self.position+1+x, self.fdd.height-1, x == self.taktschritt)
self.fdd.show()
def update(self, dreh, schieb, btn):
#print(self.cc.chan)
if dreh != None:
#print("Dreh: ", dreh)
self.dreh_ist = dreh
if schieb != None:
#print("Schieb: ", schieb)
self.schieb_ist = schieb
if btn != None:
#print("Button: ", btn)
self.btn_ist = btn
def check(self):
""" return 1: OK
return 0: ERROR
return -1: IGNORIEREN
"""
check = True
#check &= (self.dreh_ist // 32) == self.dreh
#check &= (self.schieb_ist // 32) == self.schieb
check &= self.takte[self.taktschritt]
#print((self.dreh_ist // 32),' ', self.schieb_ist // 32), ' ')
#print("BUT: ", self.btn_ist)
if not self.btn_ist:
if (not self.visible) or (not self.takte[self.taktschritt]):
print("IGNOR")
return -1
elif self.takte[self.taktschritt]:
print("FALSCH")
return 0
else:
if check:
print("RICHTIG!")
return 1
else:
print("FALSCH!")
return 0
class DemoBase:
def __init__(self, flipdotdisplay):
self.fdd = flipdotdisplay
def run(self):
while True:
self.prepare()
for x in range(self.fdd.width):
for y in range(self.fdd.height):
val = self.handle_px(x, y)
self.fdd.px(x, y, val)
self.fdd.show()
def handle_px(self, x, y):
"""Specifiy the color pixel a x|y should have by returning truth val."""
raise Exception("Must be overriden!")
def prepare(self):
"""Do some preparation before update."""
pass
class PlasmaDemo(DemoBase):
"""Plasma"""
def __init__(self, flipdotdisplay):
super().__init__(flipdotdisplay)
self.i = 0
self.s = 1
def prepare(self):
self.i += 2
self.s = math.sin(self.i / 50.0) * 2.0 + 6.0
def handle_px(self, x, y):
v = 0.3 + (0.3 * math.sin((x * self.s) + self.i / 4.0) *
math.cos((y * self.s) + self.i / 4.0))
return v > 0.3
class RotatingPlasmaDemo(DemoBase):
"""Rotating Plasma"""
def __init__(self, flipdotdisplay):
super().__init__(flipdotdisplay)
self.current = time.time()
def prepare(self):
self.current = time.time()
def handle_px(self, x, y):
v = math.sin(1*(0.5*x*math.sin(self.current/2) +
0.5*y*math.cos(self.current/3)) + self.current)
# -1 < sin() < +1
# therfore correct the value and bring into range [0, 1]
v = (v+1.0) / 2.0
return v > 0.5
class SwirlDemo(DemoBase):
"""Rotating Swirl"""
def __init__(self, flipdotdisplay):
super().__init__(flipdotdisplay)
self.timestep = math.sin(time.time() / 18) * 1500
def prepare(self):
self.timestep = math.sin(time.time() / 18) * 1500
def handle_px(self, x, y):
return self.swirl(x, y, self.timestep) > 0.2
def swirl(self, x, y, step):
x -= (self.fdd.width/2.0)
y -= (self.fdd.height/2.0)
dist = math.sqrt(pow(x, 2) + pow(y, 2))
angle = (step / 10.0) + dist / 1.5
s = math.sin(angle)
c = math.cos(angle)
xs = x * c - y * s
ys = x * s + y * c
r = abs(xs + ys)
return max(0.0, 0.7 - min(1.0, r/8.0))
class PingPong(DemoBase):
"""PingPong Demo"""
def __init__(self, flipdotdisplay):
super().__init__(flipdotdisplay)
self.vel = [1, 1]
self.pos = [0, self.fdd.height // 2]
def handle_px(self, x, y):
if x == self.pos[0] and y == self.pos[1]:
return True
else:
return False
def prepare(self):
if self.pos[0] + self.vel[0] > self.fdd.width or \
self.pos[0] + self.vel[0] < 0:
self.vel[0] = -self.vel[0]
if self.pos[1] + self.vel[1] > self.fdd.height or \
self.pos[1] + self.vel[1] < 0:
self.vel[1] = -self.vel[1]
self.pos = [self.pos[0] + self.vel[0],
self.pos[1] + self.vel[1]]
# TODO just for the hardware version of the display. Can be removed
# if it controls the framerate itself.
time.sleep(0.05)
class RandomDot(DemoBase):
"""Random Dots"""
def __init__(self, flipdotdisplay):
super().__init__(flipdotdisplay)
def handle_px(self, x, y):
return random.randint(0, 1)
class GameOfLife(DemoBase):
"""Conway's Game of Life"""
glider = {
(2, 2),
(1, 2),
(0, 2),
(2, 1),
(1, 0),
}
MAX_ITERATIONS = 100
def __init__(self, flipdotdisplay):
super().__init__(flipdotdisplay)
# self.cells = GameOfLife.glider
self.cells = set()
self.iterations = 0
self.reset()
def reset(self):
self.iterations = 0
for i in range(self.fdd.width * self.fdd.height // 2):
x = random.randint(0, self.fdd.width - 1)
y = random.randint(0, self.fdd.height - 1)
self.cells.add((x, y))
def _iterate(self):
new_board = set()
for cell in self.cells:
neighbours = self._neighbours(cell)
if len(self.cells.intersection(neighbours)) in (2, 3):
new_board.add(cell)
for nb in neighbours:
if len(self.cells.intersection(self._neighbours(nb))) == 3:
new_board.add(nb)
return new_board
def _neighbours(self, cell):
x, y = cell
r = range(-1, 2) # -1, 0, +1
return set((x+i, y+j) for i in r for j in r if not i == j == 0)
def prepare(self):
self.cells = self._iterate()
self.iterations += 1
if self.iterations > GameOfLife.MAX_ITERATIONS:
self.reset()
def handle_px(self, x, y):
return (x, y) in self.cells
class SnakeGame(DemoBase):
"""Snake Game. Control with WASD or an attached Joystick."""
def __init__(self, flipflopdisplay):
super().__init__(flipflopdisplay)
self.snake_body = None
self.snake_direction = None
self.pills = []
self.max_pills = self.fdd.width // 10
self.joystick = None
self.reset()
def run(self):
"""Overriden from base class."""
# add joystick if present
pygame.joystick.init()
print("found", pygame.joystick.get_count(), "Joysticks")
if pygame.joystick.get_count() > 0:
self.joystick = pygame.joystick.Joystick(0)
self.joystick.init()
super().run()
def reset(self):
self.snake_body = [(3, 1), (2, 1), (1, 1)]
self.snake_direction = [1, 0]
for _i in range(self.max_pills):
self.create_new_pill()
def prepare(self):
self.move_snake()
if len(self.snake_body) != len(set(self.snake_body)):
# snake eats itself
self.reset()
# if pills have been eaten, fill up with new ones
while len(self.pills) <= self.max_pills:
self.create_new_pill()
self.handle_input()
def move_snake(self):
# move the snakes head
head = self.snake_body[0]
new_head = ((head[0] + self.snake_direction[0]) % self.fdd.width,
(head[1] + self.snake_direction[1]) % self.fdd.height)
if new_head in self.pills:
# eat pill
new_body = self.snake_body
self.pills.remove(new_head)
else:
# move forward
new_body = self.snake_body[:-1]
self.snake_body = [new_head] + new_body
def handle_input(self):
xdir, ydir = self.snake_direction
xax, yax = 0, 1
for event in pygame.event.get():
if event.type == pygame.JOYAXISMOTION and self.joystick:
if self.joystick.get_axis(yax) < 0 and ydir != 1:
self.snake_direction = [0, -1]
elif self.joystick.get_axis(yax) > 0 and ydir != -1:
self.snake_direction = [0, 1]
elif self.joystick.get_axis(xax) > 0 and xdir != -1:
self.snake_direction = [1, 0]
elif self.joystick.get_axis(xax) < 0 and xdir != 1:
self.snake_direction = [-1, 0]
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_w and ydir != 1:
self.snake_direction = [0, -1]
elif event.key == pygame.K_s and ydir != -1:
self.snake_direction = [0, 1]
elif event.key == pygame.K_d and xdir != -1:
self.snake_direction = [1, 0]
elif event.key == pygame.K_a and xdir != 1:
self.snake_direction = [-1, 0]
def create_new_pill(self):
new_pill = None
while new_pill is None or new_pill in self.snake_body or\
new_pill in self.pills:
new_pill = (random.randint(0, self.fdd.width-1),
random.randint(0, self.fdd.height-1))
self.pills.append(new_pill)
def handle_px(self, x, y):
return (x, y) in self.snake_body or (x, y) in self.pills
# TODO add support for joystick
class FlappyDot(DemoBase):
"""Flappy Dot. Control the bird with the w-key."""
def __init__(self, flipdotdisplay, max_lines=3):
super().__init__(flipdotdisplay)
self.pos = (1, 1)
# each line is a dictionary with entries x, gap_start, gap_end
self.lines = []
self.score = 0
self.max_lines = max_lines
self.reset()
def add_line(self, x, gap_start, gap_end):
self.lines.append({"x": x, "gap_start": gap_start, "gap_end": gap_end})
def reset(self):
self.pos = (1, 1)
self.lines = []
for i in range(self.max_lines):
self.add_line(i * self.fdd.width // self.max_lines, 1, 5)
self.score = 0
def handle_input(self):
newx, newy = self.pos
for event in pygame.event.get():
if event.type == pygame.KEYDOWN and event.key == pygame.K_w:
newy -= 1
self.pos = (newx, newy)
return
newy += 1
self.pos = (newx, newy)
def prepare(self):
time.sleep(0.1) # TODO remove this when display handles framerate
self.handle_input()
self.move_lines()
if not self.bird_is_alive():
self.reset()
def bird_is_alive(self):
if self.pos[1] < 0 or self.pos[1] > self.fdd.height:
# bird outside screen (top or bottom)
return False
# check collision with one of the lines
for l in self.lines:
if not (l['x'] != self.pos[0] or
l['gap_start'] < self.pos[1] < l['gap_end']):
return False
return True
def move_lines(self):
for l in self.lines:
l['x'] -= 1
# remove lines off screen
self.lines = [l for l in self.lines if l['x'] >= 0]
if len(self.lines) < self.max_lines:
# create new line
gap_start = random.randint(0, self.fdd.height - 5)
self.add_line(self.fdd.width-1, gap_start, gap_start+5)
self.score += 1
def handle_px(self, x, y):
if self.pos == (x, y):
# draw flappy
return True
elif x == self.fdd.width - 1:
# show score at last column
return y < self.score
else:
# draw line with gap
for l in self.lines:
if l['x'] == x:
return not (l['gap_start'] <= y <= l['gap_end'])
return False
class BinaryClock(DemoBase):
"""A binary clock"""
def __init__(self, flipdotdisplay, offset=(1, 1)):
super().__init__(flipdotdisplay)
self.pixels = []
self.offset = offset
def prepare(self):
self.pixels.clear()
tt = time.localtime()
for i, t in enumerate([tt.tm_hour, tt.tm_min, tt.tm_sec]):
self.add_pixels(t, y=i)
def add_pixels(self, num, y):
x = self.offset[0]
# convert to binary, cut off '0b' at the beginning and fill with
# 0's to a maximum length of 7 digits (needed for max number 60).
for c in bin(num)[2:].zfill(7):
if c == "1":
self.pixels.append((x, self.offset[1] + y))
x += 1
def handle_px(self, x, y):
return (x, y) in self.pixels
def main():
import displayprovider
import configuration
fdd = displayprovider.get_display(
width=configuration.WIDTH, height=configuration.HEIGHT,
fallback=displayprovider.Fallback.SIMULATOR)
demos = [PlasmaDemo(fdd), SwirlDemo(fdd), PingPong(fdd), RandomDot(fdd),
RotatingPlasmaDemo(fdd), GameOfLife(fdd), SnakeGame(fdd),
FlappyDot(fdd), BinaryClock(fdd), rogueflip.Game(fdd), Fdh(fdd)]
print("\n".join([str(i) + ": " + d.__doc__ for i, d in enumerate(demos)]))
num = int(input(">"))
print("Running demo. CTRL-C to abort.")
demos[num].run()
if __name__ == "__main__":
main()
import channel
import flipdotdisplay
import midi
import levels
import pygame.mixer
import time
class Sounds:
def __init__(self):
self.sounds = {}
for chan in range(1, 5):
for dreh in range(4):
for schieb in range(4):
filename = str(chan)+str(dreh)+str(schieb)
try:
snd = pygame.mixer.Sound(
file="./sounds/"+filename + ".wav")
self.sounds[filename] = snd
snd_err = pygame.mixer.Sound(
file="./sounds/" + filename + "e.wav")
self.sounds[filename+"e"] = snd_err
except Exception as e:
print("! File missing " + filename, e)
class Fdh:
""" FlipDotHero """
def __init__(self, flipdotdisplay):
self.fdd = flipdotdisplay
self.channels = [
channel.Channel(self.fdd, levels.ChannelConf(1)),
channel.Channel(self.fdd, levels.ChannelConf(2)),
channel.Channel(self.fdd, levels.ChannelConf(3)),
channel.Channel(self.fdd, levels.ChannelConf(4))]
self.midiin = midi.midi_input_device()
assert self.midiin is not None
self.levels = levels.demolevels
self.current_level = 0
self.sounds = Sounds()
def start_level(self, level):
print("level", level)
for chan in self.channels:
chan.visible = False
for chanconf in self.levels[level]:
self.channels[chanconf.chan-1].setconf(chanconf)
self.channels[chanconf.chan-1].visible = True
def start_bg_music(self):
pygame.mixer.music.load("sounds/music.ogg")
pygame.mixer.music.play(loops=-1)
def run(self):
self.start_level(self.current_level)
self.start_bg_music()
taktschritt = 0
takt_last_update = time.time()
while True:
self.handle_input()
self.drehen_schieben()
for ch in self.channels:
ch.draw()
self.fdd.show2()
#self.check(taktschritt)
if time.time() - takt_last_update > 1:
print("taktschritt", taktschritt)
taktschritt = (taktschritt + 1) % 4
for ch in self.channels:
ch.settakt(taktschritt)
takt_last_update = time.time()
def drehen_schieben(self):
for ch in self.channels:
if not ch.visible:
continue
ch.dreh = (ch.dreh + 1) % 4
import random
ch.schieb = random.randint(0, 3)
#ch.schieb = (ch.schieb + 1) % 4
def handle_input(self):
events = self.midiin.read(1)
if not events:
return
event = events[0]
num, dreh_schieb_btn, val = midi.event_info(event)
num = num - 1
if dreh_schieb_btn == "dreh":
self.channels[num].update(val, None, None)
elif dreh_schieb_btn == "schieb":
self.channels[num].update(None, val, None)
elif dreh_schieb_btn == "btn":
self.channels[num].update(None, None, val > 0)
self.check()
def check(self):
for ch in self.channels:
if not ch.visible:
continue
name = str(ch.cc.chan)+str(ch.cc.dreh)+str(ch.cc.schieb)
checkres = ch.check()
if checkres == 1:
# play ok sound
self.sounds.sounds[name].play()
self.current_level = \
(self.current_level + 1) % len(self.levels)
self.start_level(self.current_level)
elif checkres == 0:
# play error sound
#self.sounds.sounds[name+"e"].play()
pass
if __name__ == "__main__":
pygame.init()
fdd = flipdotdisplay.FlipDotDisplay(module=[14])
fdh = Fdh(fdd)
fdh.run()
class ChannelConf:
def __init__(self, chan, dreh=0, schieb=0,
takte=[False, False, False, False]):
assert len(takte) == 4
assert 0 <= dreh <= 3
assert 0 <= schieb <= 3
self.chan = chan
self.dreh = dreh
self.schieb = schieb
self.takte = takte
l0 = [ChannelConf(1, 0,0, (True, False, True, False))]
l1 = [ChannelConf(1, 0,3, (True, False, False, True)),
ChannelConf(2, 3,0, (False, False, False, True))]
l2 = [ChannelConf(1, 0,3, (False, True, False, True)),
ChannelConf(2, 3,0, (False, True, False, True)),
ChannelConf(3, 3,3, (True, True, False, True))]
l3 = [ChannelConf(1, 0,3, (True, False, True, False)),
ChannelConf(2, 3,0, (False, True, False, True)),
ChannelConf(3, 3,0, (False, False, True, True)),
ChannelConf(4, 3,3, (True, True, False, True))]
l4 = [ChannelConf(1, 0,3, (True, False, True, False)),
ChannelConf(2, 3,3, (False, True, False, True)),
ChannelConf(3, 2,2, (False, False, True, True)),
ChannelConf(4, 1,1, (True, True, False, True))]
demolevels = [l0, l1, l2, l3, l4]
import pygame.midi
pygame.midi.init()
_dreh = "dreh"
_schieb = "schieb"
_btn = "btn"
_midinote2info = {
2: [1, _schieb],
3: [2, _schieb],
4: [3, _schieb],
5: [4, _schieb],
14: [1, _dreh],
15: [2, _dreh],
16: [3, _dreh],
17: [4, _dreh],
23: [1, _btn],
24: [2, _btn],
25: [3, _btn],
26: [4, _btn]
}
def midi_input_device(devname="nanoKONTROL MIDI 1"):
"""Return an instance pygame.midi.Input of the default midi input
device."""
print("Number of dev.", pygame.midi.get_count())
for devid in range(pygame.midi.get_count()):
interf, name, inp, outp, op = pygame.midi.get_device_info(devid)
print("dev", devid, "name", name)
if name == bytes(devname, "UTF8") and inp:
return pygame.midi.Input(devid)
def event_info(midievent):
"""Return event information as tuple (n, t) where n is the number
of the Kanal and t is "schieb", "dreh" or "btn"."""
event, time = midievent
status, data1, data2, data3 = event
return _midinote2info.get(data1, None) + [data2]
if __name__ == "__main__":
midiin = midi_input_device()
print("default device", midiin)
while True:
events = midiin.read(1)
if events:
event = events[0]
print(event, "info", event_info(event))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment