Created
August 16, 2018 06:00
-
-
Save pintman/52cd40876946c0e0b08f3ee92297af95 to your computer and use it in GitHub Desktop.
FlipDotHero - minigamejam in Dortmund (2018-08-05)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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